224 lines
8.7 KiB
Scala
224 lines
8.7 KiB
Scala
// See LICENSE.SiFive for license details.
|
|
|
|
package freechips.rocketchip.tilelink
|
|
|
|
import Chisel._
|
|
import chisel3.experimental.chiselName
|
|
import freechips.rocketchip.config.Parameters
|
|
import freechips.rocketchip.diplomacy._
|
|
import freechips.rocketchip.util._
|
|
|
|
class TLRAM(
|
|
address: AddressSet,
|
|
cacheable: Boolean = true,
|
|
executable: Boolean = true,
|
|
beatBytes: Int = 4,
|
|
eccBytes: Int = 1,
|
|
devName: Option[String] = None,
|
|
errors: Seq[AddressSet] = Nil,
|
|
code: Code = new IdentityCode)
|
|
(implicit p: Parameters) extends DiplomaticSRAM(address, beatBytes, devName)
|
|
{
|
|
require (eccBytes >= 1 && isPow2(eccBytes))
|
|
require (beatBytes >= 1 && isPow2(beatBytes))
|
|
require (eccBytes <= beatBytes, s"TLRAM eccBytes (${eccBytes}) > beatBytes (${beatBytes}). Use a WidthWidget=>Fragmenter=>SRAM if you need high density and narrow ECC; it will do bursts efficiently")
|
|
|
|
val node = TLManagerNode(Seq(TLManagerPortParameters(
|
|
Seq(TLManagerParameters(
|
|
address = List(address) ++ errors,
|
|
resources = device.reg("mem"),
|
|
regionType = if (cacheable) RegionType.UNCACHED else RegionType.UNCACHEABLE,
|
|
executable = executable,
|
|
supportsGet = TransferSizes(1, beatBytes),
|
|
supportsPutPartial = TransferSizes(1, beatBytes),
|
|
supportsPutFull = TransferSizes(1, beatBytes),
|
|
fifoId = Some(0))), // requests are handled in order
|
|
beatBytes = beatBytes,
|
|
minLatency = 1))) // no bypass needed for this device
|
|
|
|
lazy val module = new LazyModuleImp(this) {
|
|
val (in, edge) = node.in(0)
|
|
|
|
val width = code.width(eccBytes*8)
|
|
val lanes = beatBytes/eccBytes
|
|
val addrBits = (mask zip edge.addr_hi(in.a.bits).toBools).filter(_._1).map(_._2)
|
|
val mem = makeSinglePortedByteWriteSeqMem(1 << addrBits.size, lanes, width)
|
|
|
|
/* This block uses a two-stage pipeline; A=>D
|
|
* Both stages vie for access to the single SRAM port.
|
|
* Stage D has absolute priority over stage A.
|
|
* - read-modify-writeback for sub-lane access happens here
|
|
* - writeback of correctable data happens here
|
|
* - both actions may occur concurrently
|
|
* Stage A has lower priority and will stall if blocked
|
|
* - read operations happen here
|
|
* - full-lane write operations happen here
|
|
*/
|
|
|
|
// D stage registers from A
|
|
val d_full = RegInit(Bool(false))
|
|
val d_ram_valid = RegInit(Bool(false)) // true if we just read-out from SRAM
|
|
val d_size = Reg(UInt())
|
|
val d_source = Reg(UInt())
|
|
val d_legal = Reg(Bool())
|
|
val d_read = Reg(Bool())
|
|
val d_address = Reg(UInt(width = addrBits.size))
|
|
val d_rmw_mask = Reg(UInt(width = beatBytes))
|
|
val d_rmw_data = Reg(UInt(width = 8*beatBytes))
|
|
|
|
// Decode raw unregistered SRAM output
|
|
val d_raw_data = Wire(Vec(lanes, Bits(width = width)))
|
|
val d_decoded = d_raw_data.map(lane => code.decode(lane))
|
|
val d_corrected = Cat(d_decoded.map(_.corrected).reverse)
|
|
val d_uncorrected = Cat(d_decoded.map(_.uncorrected).reverse)
|
|
val d_correctable = d_decoded.map(_.correctable)
|
|
val d_uncorrectable = d_decoded.map(_.uncorrectable)
|
|
val d_need_fix = d_correctable.reduce(_ || _)
|
|
val d_error = d_uncorrectable.reduce(_ || _)
|
|
|
|
// What does D-stage want to write-back?
|
|
val d_wb_data = Vec(Seq.tabulate(beatBytes) { i =>
|
|
val upd = d_rmw_mask(i)
|
|
val rmw = d_rmw_data (8*(i+1)-1, 8*i)
|
|
val fix = d_corrected(8*(i+1)-1, 8*i) // safe to use, because D-stage write-back always wins arbitration
|
|
Mux(upd, rmw, fix)
|
|
}.grouped(eccBytes).map(lane => Cat(lane.reverse)).toList)
|
|
val (d_wb_lanes, d_wb_poison) = Seq.tabulate(lanes) { i =>
|
|
val upd = d_rmw_mask(eccBytes*(i+1)-1, eccBytes*i)
|
|
(upd.orR || d_correctable(i),
|
|
!upd.andR && d_uncorrectable(i)) // sub-lane writes should not correct uncorrectable
|
|
}.unzip
|
|
val d_wb = d_rmw_mask.orR || (d_ram_valid && d_need_fix)
|
|
|
|
// Extend the validity of SRAM read-out
|
|
val d_held_data = RegEnable(d_corrected, d_ram_valid)
|
|
val d_held_error = RegEnable(d_error, d_ram_valid)
|
|
|
|
in.d.bits.opcode := Mux(d_read, TLMessages.AccessAckData, TLMessages.AccessAck)
|
|
in.d.bits.param := UInt(0)
|
|
in.d.bits.size := d_size
|
|
in.d.bits.source := d_source
|
|
in.d.bits.sink := UInt(0)
|
|
// It is safe to use uncorrected data here because of d_pause
|
|
in.d.bits.data := Mux(d_ram_valid, d_uncorrected, d_held_data)
|
|
in.d.bits.error := !d_legal || Mux(d_ram_valid, d_error, d_held_error)
|
|
|
|
// Formulate a response only when SRAM output is unused or correct
|
|
val d_pause = d_read && d_ram_valid && d_need_fix
|
|
in.d.valid := d_full && !d_pause
|
|
in.a.ready := !d_full || (in.d.ready && !d_pause && !d_wb)
|
|
|
|
val a_legal = Bool(errors.isEmpty) || address.contains(in.a.bits.address)
|
|
val a_address = Cat(addrBits.reverse)
|
|
val a_read = in.a.bits.opcode === TLMessages.Get
|
|
val a_data = Vec(Seq.tabulate(lanes) { i => in.a.bits.data(eccBytes*8*(i+1)-1, eccBytes*8*i) })
|
|
|
|
/*
|
|
val a_sublane = Seq.tabulate(lanes) { i =>
|
|
val upd = in.a.bits.mask(eccBytes*(i+1)-1, eccBytes*i)
|
|
upd.orR && !upd.andR
|
|
}.reduce(_ || _)
|
|
*/
|
|
val a_sublane = if (eccBytes == 1) Bool(false) else
|
|
in.a.bits.opcode === TLMessages.PutPartialData ||
|
|
in.a.bits.size < UInt(log2Ceil(eccBytes))
|
|
val a_ren = a_read || a_sublane
|
|
val a_lanes = Seq.tabulate(lanes) { i => in.a.bits.mask(eccBytes*(i+1)-1, eccBytes*i).orR }
|
|
|
|
when (in.d.fire()) { d_full := Bool(false) }
|
|
d_ram_valid := Bool(false)
|
|
d_rmw_mask := UInt(0)
|
|
when (in.a.fire()) {
|
|
d_full := Bool(true)
|
|
d_ram_valid := a_ren && a_legal
|
|
d_size := in.a.bits.size
|
|
d_source := in.a.bits.source
|
|
d_legal := a_legal
|
|
d_read := a_read
|
|
d_address := a_address
|
|
d_rmw_mask := UInt(0)
|
|
when (!a_read && a_sublane) {
|
|
d_rmw_mask := in.a.bits.mask
|
|
d_rmw_data := in.a.bits.data
|
|
}
|
|
d_held_error:= Bool(false)
|
|
}
|
|
|
|
// SRAM arbitration
|
|
val a_fire = in.a.fire() && a_legal
|
|
val wen = d_wb || (a_fire && !a_ren)
|
|
// val ren = !d_wb && (a_fire && a_ren)
|
|
val ren = !wen && a_fire // help Chisel infer a RW-port
|
|
|
|
val addr = Mux(d_wb, d_address, a_address)
|
|
val sel = Mux(d_wb, Vec(d_wb_lanes), Vec(a_lanes))
|
|
val dat = Mux(d_wb, d_wb_data, a_data)
|
|
val poison = Mux(d_wb, Vec(d_wb_poison), Vec.fill(lanes) { Bool(false) })
|
|
val coded = Vec((dat zip poison) map { case (d, p) =>
|
|
if (code.canDetect) code.encode(d, p) else code.encode(d)
|
|
})
|
|
|
|
d_raw_data := mem.read(addr, ren)
|
|
when (wen) { mem.write(addr, coded, sel) }
|
|
|
|
// Tie off unused channels
|
|
in.b.valid := Bool(false)
|
|
in.c.ready := Bool(true)
|
|
in.e.ready := Bool(true)
|
|
}
|
|
}
|
|
|
|
object TLRAM
|
|
{
|
|
def apply(
|
|
address: AddressSet,
|
|
cacheable: Boolean = true,
|
|
executable: Boolean = true,
|
|
beatBytes: Int = 4,
|
|
eccBytes: Int = 1,
|
|
devName: Option[String] = None,
|
|
errors: Seq[AddressSet] = Nil,
|
|
code: Code = new IdentityCode)(implicit p: Parameters): TLInwardNode =
|
|
{
|
|
val ram = LazyModule(new TLRAM(address, cacheable, executable, beatBytes, eccBytes, devName, errors, code))
|
|
ram.node
|
|
}
|
|
}
|
|
|
|
/** Synthesizeable unit testing */
|
|
import freechips.rocketchip.unittest._
|
|
|
|
class TLRAMSimple(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
|
|
val fuzz = LazyModule(new TLFuzzer(txns))
|
|
val model = LazyModule(new TLRAMModel("SRAMSimple"))
|
|
val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff), beatBytes = ramBeatBytes))
|
|
|
|
ram.node := TLDelayer(0.25) := model.node := fuzz.node
|
|
|
|
lazy val module = new LazyModuleImp(this) with UnitTestModule {
|
|
io.finished := fuzz.module.io.finished
|
|
}
|
|
}
|
|
|
|
class TLRAMSimpleTest(ramBeatBytes: Int, txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
|
val dut = Module(LazyModule(new TLRAMSimple(ramBeatBytes, txns)).module)
|
|
io.finished := dut.io.finished
|
|
}
|
|
|
|
class TLRAMECC(ramBeatBytes: Int, eccBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
|
|
val fuzz = LazyModule(new TLFuzzer(txns))
|
|
val model = LazyModule(new TLRAMModel("SRAMSimple"))
|
|
val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff), beatBytes = ramBeatBytes, eccBytes = eccBytes, code = new SECDEDCode))
|
|
|
|
ram.node := TLDelayer(0.25) := model.node := fuzz.node
|
|
|
|
lazy val module = new LazyModuleImp(this) with UnitTestModule {
|
|
io.finished := fuzz.module.io.finished
|
|
}
|
|
}
|
|
|
|
class TLRAMECCTest(ramBeatBytes: Int, eccBytes: Int, txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
|
val dut = Module(LazyModule(new TLRAMECC(ramBeatBytes, eccBytes, txns)).module)
|
|
io.finished := dut.io.finished
|
|
}
|