tilelink2: support ready-valid enqueue+dequeue on register fields
This commit is contained in:
parent
77cf186cf0
commit
967d8f108c
@ -4,38 +4,214 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
|
||||
case class RegField(width: Int, read: RegField.ReadFn, write: RegField.WriteFn)
|
||||
case class RegReadFn private(combinational: Boolean, fn: (Bool, Bool) => (Bool, Bool, UInt))
|
||||
object RegReadFn
|
||||
{
|
||||
// (ivalid: Bool, oready: Bool) => (iready: Bool, ovalid: Bool, data: UInt)
|
||||
// iready may combinationally depend on oready
|
||||
// all other combinational dependencies forbidden (e.g. ovalid <= ivalid)
|
||||
// effects must become visible only on the cycle after ovalid && oready
|
||||
implicit def apply(x: (Bool, Bool) => (Bool, Bool, UInt)) =
|
||||
new RegReadFn(false, x)
|
||||
// (ofire: Bool) => (data: UInt)
|
||||
// effects must become visible on the cycle after ofire
|
||||
implicit def apply(x: Bool => UInt) =
|
||||
new RegReadFn(true, { case (_, oready) =>
|
||||
(Bool(true), Bool(true), x(oready))
|
||||
})
|
||||
// read from a register
|
||||
implicit def apply(x: UInt) =
|
||||
new RegReadFn(true, { case (_, _) =>
|
||||
(Bool(true), Bool(true), x)
|
||||
})
|
||||
// noop
|
||||
implicit def apply(x: Unit) =
|
||||
new RegReadFn(true, { case (_, _) =>
|
||||
(Bool(true), Bool(true), UInt(0))
|
||||
})
|
||||
}
|
||||
|
||||
case class RegWriteFn private(combinational: Boolean, fn: (Bool, Bool, UInt) => (Bool, Bool))
|
||||
object RegWriteFn
|
||||
{
|
||||
// (ivalid: Bool, oready: Bool, data: UInt) => (iready: Bool, ovalid: Bool)
|
||||
// iready may combinationally depend on both oready and data
|
||||
// all other combinational dependencies forbidden (e.g. ovalid <= ivalid)
|
||||
// effects must become visible only on the cycle after ovalid && oready
|
||||
implicit def apply(x: (Bool, Bool, UInt) => (Bool, Bool)) =
|
||||
new RegWriteFn(false, x)
|
||||
// (ofire: Bool, data: UInt) => ()
|
||||
// effects must become visible on the cycle after ofire
|
||||
implicit def apply(x: (Bool, UInt) => Unit) =
|
||||
new RegWriteFn(true, { case (_, oready, data) =>
|
||||
x(oready, data)
|
||||
(Bool(true), Bool(true))
|
||||
})
|
||||
// updates a register
|
||||
implicit def apply(x: UInt) =
|
||||
new RegWriteFn(true, { case (_, oready, data) =>
|
||||
when (oready) { x := data }
|
||||
(Bool(true), Bool(true))
|
||||
})
|
||||
// noop
|
||||
implicit def apply(x: Unit) =
|
||||
new RegWriteFn(true, { case (_, _, _) =>
|
||||
(Bool(true), Bool(true))
|
||||
})
|
||||
}
|
||||
|
||||
case class RegField(width: Int, read: RegReadFn, write: RegWriteFn)
|
||||
{
|
||||
require (width > 0)
|
||||
def pipelined = !read.combinational || !write.combinational
|
||||
}
|
||||
|
||||
object RegField
|
||||
{
|
||||
type ReadFn = Bool => (Bool, UInt)
|
||||
type WriteFn = (Bool, UInt) => Bool
|
||||
type Map = (Int, Seq[RegField])
|
||||
|
||||
def apply(n: Int) : RegField = apply(n, noR, noW)
|
||||
def apply(n: Int, rw: UInt) : RegField = apply(n, regR(rw), regW(rw))
|
||||
def apply(n: Int, r: UInt, w: UInt) : RegField = apply(n, regR(r), regW(w))
|
||||
def apply(n: Int, r: UInt, w: WriteFn) : RegField = apply(n, regR(r), w)
|
||||
def apply(n: Int, r: ReadFn, w: UInt) : RegField = apply(n, r, regW(w))
|
||||
def R(n: Int, r: ReadFn) : RegField = apply(n, r, noW)
|
||||
def R(n: Int, r: UInt) : RegField = R(n, regR(r))
|
||||
def W(n: Int, w: WriteFn) : RegField = apply(n, noR, w)
|
||||
def W(n: Int, w: UInt) : RegField = W(n, regW(w))
|
||||
|
||||
private val noR = (en: Bool) => (Bool(true), UInt(0))
|
||||
private val noW = (en: Bool, in: UInt) => Bool(true)
|
||||
private def regR(reg: UInt) = (en: Bool) => (Bool(true), reg)
|
||||
private def regW(reg: UInt) = (en: Bool, in: UInt) =>
|
||||
{
|
||||
when (en) { reg := in }
|
||||
Bool(true)
|
||||
}
|
||||
def apply(n: Int) : RegField = apply(n, (), ())
|
||||
def apply(n: Int, rw: UInt) : RegField = apply(n, rw, rw)
|
||||
def R(n: Int, r: RegReadFn) : RegField = apply(n, r, ())
|
||||
def W(n: Int, w: RegWriteFn) : RegField = apply(n, (), w)
|
||||
}
|
||||
|
||||
trait HasRegMap
|
||||
{
|
||||
def regmap(mapping: RegField.Map*): Unit
|
||||
}
|
||||
|
||||
case class RegFieldParams(indexBits: Int, maskBits: Int, extraBits: Int)
|
||||
|
||||
class RegFieldInput(params: RegFieldParams) extends GenericParameterizedBundle(params)
|
||||
{
|
||||
val read = Bool()
|
||||
val index = UInt(width = params.indexBits)
|
||||
val data = UInt(width = params.maskBits*8)
|
||||
val mask = UInt(width = params.maskBits)
|
||||
val extra = UInt(width = params.extraBits)
|
||||
}
|
||||
|
||||
class RegFieldOutput(params: RegFieldParams) extends GenericParameterizedBundle(params)
|
||||
{
|
||||
val read = Bool()
|
||||
val data = UInt(width = params.maskBits*8)
|
||||
val extra = UInt(width = params.extraBits)
|
||||
}
|
||||
|
||||
object RegFieldHelper
|
||||
{
|
||||
// Create a generic register-based device
|
||||
def apply(bytes: Int, concurrency: Option[Int], in: DecoupledIO[RegFieldInput], mapping: RegField.Map*) = {
|
||||
val regmap = mapping.toList
|
||||
require (!regmap.isEmpty)
|
||||
|
||||
// Flatten the regmap into (Reg:Int, Offset:Int, field:RegField)
|
||||
val flat = regmap.map { case (reg, fields) =>
|
||||
val offsets = fields.scanLeft(0)(_ + _.width).init
|
||||
(offsets zip fields) map { case (o, f) => (reg, o, f) }
|
||||
}.flatten
|
||||
require (!flat.isEmpty)
|
||||
|
||||
val endIndex = 1 << log2Ceil(regmap.map(_._1).max+1)
|
||||
val params = RegFieldParams(log2Up(endIndex), bytes, in.bits.params.extraBits)
|
||||
|
||||
val out = Wire(Decoupled(new RegFieldOutput(params)))
|
||||
val front = Wire(Decoupled(new RegFieldInput(params)))
|
||||
front.bits := in.bits
|
||||
|
||||
// Must this device pipeline the control channel?
|
||||
val pipelined = flat.map(_._3.pipelined).reduce(_ || _)
|
||||
val depth = concurrency.getOrElse(if (pipelined) 1 else 0)
|
||||
require (depth >= 0)
|
||||
require (!pipelined || depth > 0)
|
||||
val back = if (depth > 0) Queue(front, depth, pipe = depth == 1) else front
|
||||
|
||||
// Forward declaration of all flow control signals
|
||||
val rivalid = Wire(Vec(flat.size, Bool()))
|
||||
val wivalid = Wire(Vec(flat.size, Bool()))
|
||||
val riready = Wire(Vec(flat.size, Bool()))
|
||||
val wiready = Wire(Vec(flat.size, Bool()))
|
||||
val rovalid = Wire(Vec(flat.size, Bool()))
|
||||
val wovalid = Wire(Vec(flat.size, Bool()))
|
||||
val roready = Wire(Vec(flat.size, Bool()))
|
||||
val woready = Wire(Vec(flat.size, Bool()))
|
||||
|
||||
// Per-register list of all control signals needed for data to flow
|
||||
val rifire = Array.tabulate(endIndex) { i => Seq(Bool(true)) }
|
||||
val wifire = Array.tabulate(endIndex) { i => Seq(Bool(true)) }
|
||||
val rofire = Array.tabulate(endIndex) { i => Seq(Bool(true)) }
|
||||
val wofire = Array.tabulate(endIndex) { i => Seq(Bool(true)) }
|
||||
|
||||
// The output values for each register
|
||||
val dataOut = Array.tabulate(endIndex) { _ => UInt(0) }
|
||||
|
||||
// Which bits are touched?
|
||||
val frontMask = FillInterleaved(8, front.bits.mask)
|
||||
val backMask = FillInterleaved(8, back .bits.mask)
|
||||
|
||||
// Connect the fields
|
||||
for (i <- 0 until flat.size) {
|
||||
val (reg, low, field) = flat(i)
|
||||
val high = low + field.width - 1
|
||||
// Confirm that no register is too big
|
||||
require (high <= 8*bytes)
|
||||
val rimask = frontMask(high, low).orR()
|
||||
val wimask = frontMask(high, low).andR()
|
||||
val romask = backMask(high, low).orR()
|
||||
val womask = backMask(high, low).andR()
|
||||
val data = if (field.write.combinational) back.bits.data else front.bits.data
|
||||
val (f_riready, f_rovalid, f_data) = field.read.fn(rivalid(i) && rimask, roready(i) && romask)
|
||||
val (f_wiready, f_wovalid) = field.write.fn(wivalid(i) && wimask, woready(i) && womask, data)
|
||||
riready(i) := f_riready || !rimask
|
||||
wiready(i) := f_wiready || !wimask
|
||||
rovalid(i) := f_rovalid || !romask
|
||||
wovalid(i) := f_wovalid || !womask
|
||||
rifire(reg) = riready(i) +: rifire(reg)
|
||||
wifire(reg) = wiready(i) +: wifire(reg)
|
||||
rofire(reg) = rovalid(i) +: rofire(reg)
|
||||
wofire(reg) = wovalid(i) +: wofire(reg)
|
||||
dataOut(reg) = dataOut(reg) | (f_data << low)
|
||||
}
|
||||
|
||||
// Is the selected register ready?
|
||||
val rifireMux = Vec(rifire.map(_.reduce(_ && _)))
|
||||
val wifireMux = Vec(wifire.map(_.reduce(_ && _)))
|
||||
val rofireMux = Vec(rofire.map(_.reduce(_ && _)))
|
||||
val wofireMux = Vec(wofire.map(_.reduce(_ && _)))
|
||||
val iready = Mux(front.bits.read, rifireMux(front.bits.index), wifireMux(front.bits.index))
|
||||
val oready = Mux(back .bits.read, rofireMux(back .bits.index), wofireMux(back .bits.index))
|
||||
|
||||
// Connect the pipeline
|
||||
in.ready := front.ready && iready
|
||||
front.valid := in.valid && iready
|
||||
back.ready := out.ready && oready
|
||||
out.valid := back.valid && oready
|
||||
|
||||
// Which register is touched?
|
||||
val frontSel = UIntToOH(front.bits.index)
|
||||
val backSel = UIntToOH(back.bits.index)
|
||||
|
||||
// Include the per-register one-hot selected criteria
|
||||
for (reg <- 0 until endIndex) {
|
||||
rifire(reg) = (in.valid && front.ready && front.bits.read && frontSel(reg)) +: rifire(reg)
|
||||
wifire(reg) = (in.valid && front.ready && !front.bits.read && frontSel(reg)) +: wifire(reg)
|
||||
rofire(reg) = (back.valid && out.ready && back .bits.read && backSel (reg)) +: rofire(reg)
|
||||
wofire(reg) = (back.valid && out.ready && !back .bits.read && backSel (reg)) +: wofire(reg)
|
||||
}
|
||||
|
||||
// Connect the field's ivalid and oready
|
||||
for (i <- 0 until flat.size) {
|
||||
val (reg, _, _ ) = flat(i)
|
||||
rivalid(i) := rifire(reg).filter(_ ne riready(i)).reduce(_ && _)
|
||||
wivalid(i) := wifire(reg).filter(_ ne wiready(i)).reduce(_ && _)
|
||||
roready(i) := rofire(reg).filter(_ ne rovalid(i)).reduce(_ && _)
|
||||
woready(i) := wofire(reg).filter(_ ne wovalid(i)).reduce(_ && _)
|
||||
}
|
||||
|
||||
out.bits.read := back.bits.read
|
||||
out.bits.data := Vec(dataOut)(back.bits.index)
|
||||
out.bits.extra := back.bits.extra
|
||||
|
||||
(endIndex, out)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
|
||||
class TLRegisterNode(address: AddressSet, beatBytes: Int = 4)
|
||||
class TLRegisterNode(address: AddressSet, concurrency: Option[Int] = None, beatBytes: Int = 4)
|
||||
extends TLManagerNode(beatBytes, TLManagerParameters(
|
||||
address = Seq(address),
|
||||
supportsGet = TransferSizes(1, beatBytes),
|
||||
@ -17,87 +17,51 @@ class TLRegisterNode(address: AddressSet, beatBytes: Int = 4)
|
||||
// Calling this method causes the matching TL2 bundle to be
|
||||
// configured to route all requests to the listed RegFields.
|
||||
def regmap(mapping: RegField.Map*) = {
|
||||
val regmap = mapping.toList
|
||||
require (!regmap.isEmpty)
|
||||
val a = bundleIn(0).a
|
||||
val d = bundleIn(0).d
|
||||
val edge = edgesIn(0)
|
||||
|
||||
// Flatten the regmap into (Reg:Int, Offset:Int, field:RegField)
|
||||
val flat = regmap.map { case (reg, fields) =>
|
||||
val offsets = fields.scanLeft(0)(_ + _.width).init
|
||||
(offsets zip fields) map { case (o, f) => (reg, o, f) }
|
||||
}.flatten
|
||||
val params = RegFieldParams(log2Up(address.mask+1), beatBytes, edge.bundle.sourceBits + edge.bundle.sizeBits)
|
||||
val in = Wire(Decoupled(new RegFieldInput(params)))
|
||||
in.bits.read := a.bits.opcode === TLMessages.Get
|
||||
in.bits.index := a.bits.address >> log2Ceil(beatBytes)
|
||||
in.bits.data := a.bits.data
|
||||
in.bits.mask := a.bits.wmask
|
||||
in.bits.extra := Cat(a.bits.source, a.bits.size)
|
||||
|
||||
// Confirm that no register is too big
|
||||
require (flat.map(_._2).max <= beatBytes*8)
|
||||
// Invoke the register map builder
|
||||
val (endIndex, out) = RegFieldHelper(beatBytes, concurrency, in, mapping:_*)
|
||||
|
||||
// All registers must fit inside the device address space
|
||||
val maxIndex = regmap.map(_._1).max
|
||||
require (address.mask >= maxIndex*beatBytes)
|
||||
require (address.mask >= (endIndex-1)*beatBytes)
|
||||
|
||||
// Which register is touched?
|
||||
val alignBits = log2Ceil(beatBytes)
|
||||
val addressBits = log2Up(maxIndex+1)
|
||||
val a = bundleIn(0).a // Must apply Queue !!! (so no change once started)
|
||||
val d = bundleIn(0).d
|
||||
val regIdx = a.bits.address(addressBits+alignBits-1, alignBits)
|
||||
val regSel = UIntToOH(regIdx)
|
||||
// No flow control needed
|
||||
in.valid := a.valid
|
||||
a.ready := in.ready
|
||||
d.valid := out.valid
|
||||
out.ready := d.ready
|
||||
|
||||
// What is the access?
|
||||
val opcode = a.bits.opcode
|
||||
val read = a.valid && opcode === TLMessages.Get
|
||||
val write = a.valid && (opcode === TLMessages.PutFullData || opcode === TLMessages.PutPartialData)
|
||||
val wmaskWide = Vec.tabulate(beatBytes*8) { i => a.bits.wmask(i/8) } .toBits.asUInt
|
||||
val dataIn = a.bits.data & wmaskWide // zero undefined bits
|
||||
|
||||
// The output values for each register
|
||||
val dataOutAcc = Array.tabulate(maxIndex+1) { _ => UInt(0) }
|
||||
// The ready state for read and write
|
||||
val rReadyAcc = Array.tabulate(maxIndex+1) { _ => Bool(true) }
|
||||
val wReadyAcc = Array.tabulate(maxIndex+1) { _ => Bool(true) }
|
||||
|
||||
// Apply all the field methods
|
||||
flat.foreach { case (reg, low, field) =>
|
||||
val high = low + field.width - 1
|
||||
val rfire = wmaskWide(high, low).orR()
|
||||
val wfire = wmaskWide(high, low).andR()
|
||||
val sel = regSel(reg)
|
||||
val ren = read && sel && rfire
|
||||
val wen = write && sel && wfire
|
||||
val (rReady, rResult) = field.read(ren)
|
||||
val wReady = field.write(wen, dataIn(high, low))
|
||||
dataOutAcc(reg) = dataOutAcc(reg) | (rResult << low)
|
||||
rReadyAcc(reg) = rReadyAcc(reg) && (!rfire || rReady)
|
||||
wReadyAcc(reg) = wReadyAcc(reg) && (!wfire || wReady)
|
||||
}
|
||||
|
||||
// Create the output data signal
|
||||
val dataOut = Vec(dataOutAcc)(regIdx)
|
||||
val rReady = Vec(rReadyAcc)(regIdx)
|
||||
val wReady = Vec(wReadyAcc)(regIdx)
|
||||
|
||||
val ready = (read && rReady) || (write && wReady)
|
||||
a.ready := ready && d.ready
|
||||
d.valid := a.valid && ready
|
||||
|
||||
val edge = edgesIn(0)
|
||||
d.bits := edge.AccessAck(a.bits.source, a.bits.size)
|
||||
val sizeBits = edge.bundle.sizeBits
|
||||
d.bits := edge.AccessAck(out.bits.extra >> sizeBits, out.bits.extra(sizeBits-1, 0))
|
||||
// avoid a Mux on the data bus by manually overriding two fields
|
||||
d.bits.data := dataOut
|
||||
d.bits.opcode := Mux(opcode === TLMessages.Get, TLMessages.AccessAck, TLMessages.AccessAckData)
|
||||
d.bits.data := out.bits.data
|
||||
d.bits.opcode := Mux(out.bits.read, TLMessages.AccessAckData, TLMessages.AccessAck)
|
||||
}
|
||||
}
|
||||
|
||||
object TLRegisterNode
|
||||
{
|
||||
def apply(address: AddressSet, beatBytes: Int = 4) = new TLRegisterNode(address, beatBytes)
|
||||
def apply(address: AddressSet, concurrency: Option[Int] = None, beatBytes: Int = 4) =
|
||||
new TLRegisterNode(address, concurrency, beatBytes)
|
||||
}
|
||||
|
||||
// These convenience methods below combine to make it possible to create a TL2
|
||||
// register mapped device from a totally abstract register mapped device.
|
||||
// See GPIO.scala in this directory for an example
|
||||
|
||||
abstract class TLRegFactory(address: AddressSet, beatBytes: Int) extends TLFactory
|
||||
abstract class TLRegFactory(address: AddressSet, concurrency: Option[Int], beatBytes: Int) extends TLFactory
|
||||
{
|
||||
val node = TLRegisterNode(address, beatBytes)
|
||||
val node = TLRegisterNode(address, concurrency, beatBytes)
|
||||
}
|
||||
|
||||
class TLRegBundle[P](val params: P, val tl_in: Vec[TLBundle]) extends Bundle
|
||||
@ -110,10 +74,10 @@ class TLRegModule[P, B <: Bundle](val params: P, bundleBuilder: => B, factory: T
|
||||
}
|
||||
|
||||
class TLRegisterRouter[B <: Bundle, M <: TLModule]
|
||||
(address: Option[BigInt] = None, size: BigInt = 4096, beatBytes: Int = 4)
|
||||
(address: Option[BigInt] = None, size: BigInt = 4096, concurrency: Option[Int] = None, beatBytes: Int = 4)
|
||||
(bundleBuilder: Vec[TLBundle] => B)
|
||||
(moduleBuilder: (=> B, TLRegFactory) => M)
|
||||
extends TLRegFactory(AddressSet(size-1, address), beatBytes)
|
||||
extends TLRegFactory(AddressSet(size-1, address), concurrency, beatBytes)
|
||||
{
|
||||
require (size % 4096 == 0) // devices should be 4K aligned
|
||||
require (isPow2(size))
|
||||
|
Loading…
Reference in New Issue
Block a user