Merge pull request #53 from sifive/chiplink
devices: add support for the chiplink protocol
This commit is contained in:
commit
7ac56c01af
93
src/main/scala/devices/chiplink/Bundles.scala
Normal file
93
src/main/scala/devices/chiplink/Bundles.scala
Normal file
@ -0,0 +1,93 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.util.{rightOR,GenericParameterizedBundle}
|
||||
|
||||
class WideDataLayerPortLane(params: ChipLinkParams) extends GenericParameterizedBundle(params) {
|
||||
val clk = Clock(OUTPUT)
|
||||
val rst = Bool(OUTPUT)
|
||||
val send = Bool(OUTPUT)
|
||||
val data = UInt(OUTPUT, width=params.dataBits)
|
||||
}
|
||||
|
||||
class WideDataLayerPort(params: ChipLinkParams) extends GenericParameterizedBundle(params) {
|
||||
val c2b = new WideDataLayerPortLane(params)
|
||||
val b2c = new WideDataLayerPortLane(params).flip
|
||||
}
|
||||
|
||||
class DataLayer(params: ChipLinkParams) extends GenericParameterizedBundle(params) {
|
||||
val data = UInt(OUTPUT, width=params.dataBits)
|
||||
val last = Bool(OUTPUT)
|
||||
val beats = UInt(OUTPUT, width=params.xferBits + 1)
|
||||
}
|
||||
|
||||
class CreditBump(params: ChipLinkParams) extends GenericParameterizedBundle(params) {
|
||||
val a = UInt(OUTPUT, width = params.creditBits)
|
||||
val b = UInt(OUTPUT, width = params.creditBits)
|
||||
val c = UInt(OUTPUT, width = params.creditBits)
|
||||
val d = UInt(OUTPUT, width = params.creditBits)
|
||||
val e = UInt(OUTPUT, width = params.creditBits)
|
||||
def X: Seq[UInt] = Seq(a, b, c, d, e)
|
||||
|
||||
// saturating addition
|
||||
def +(that: CreditBump): CreditBump = {
|
||||
val out = Wire(new CreditBump(params))
|
||||
(out.X zip (X zip that.X)) foreach { case (o, (x, y)) =>
|
||||
val z = x +& y
|
||||
o := Mux((z >> params.creditBits).orR, ~UInt(0, width=params.creditBits), z)
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// Send the MSB of the credits
|
||||
def toHeader: (UInt, CreditBump) = {
|
||||
def msb(x: UInt) = {
|
||||
val mask = rightOR(x) >> 1
|
||||
val msbOH = ~(~x | mask)
|
||||
val msb = OHToUInt(msbOH << 1, params.creditBits + 1) // 0 = 0, 1 = 1, 2 = 4, 3 = 8, ...
|
||||
val pad = (msb | UInt(0, width=5))(4,0)
|
||||
(pad, x & mask)
|
||||
}
|
||||
val (a_msb, a_rest) = msb(a)
|
||||
val (b_msb, b_rest) = msb(b)
|
||||
val (c_msb, c_rest) = msb(c)
|
||||
val (d_msb, d_rest) = msb(d)
|
||||
val (e_msb, e_rest) = msb(e)
|
||||
val header = Cat(
|
||||
e_msb, d_msb, c_msb, b_msb, a_msb,
|
||||
UInt(0, width = 4), // padding
|
||||
UInt(5, width = 3))
|
||||
|
||||
val out = Wire(new CreditBump(params))
|
||||
out.a := a_rest
|
||||
out.b := b_rest
|
||||
out.c := c_rest
|
||||
out.d := d_rest
|
||||
out.e := e_rest
|
||||
(header, out)
|
||||
}
|
||||
}
|
||||
|
||||
object CreditBump {
|
||||
def apply(params: ChipLinkParams, x: Int): CreditBump = {
|
||||
val v = UInt(x, width = params.creditBits)
|
||||
val out = Wire(new CreditBump(params))
|
||||
out.X.foreach { _ := v }
|
||||
out
|
||||
}
|
||||
|
||||
def apply(params: ChipLinkParams, header: UInt): CreditBump = {
|
||||
def convert(x: UInt) =
|
||||
Mux(x > UInt(params.creditBits),
|
||||
~UInt(0, width = params.creditBits),
|
||||
UIntToOH(x, params.creditBits + 1) >> 1)
|
||||
val out = Wire(new CreditBump(params))
|
||||
out.a := convert(header(11, 7))
|
||||
out.b := convert(header(16, 12))
|
||||
out.c := convert(header(21, 17))
|
||||
out.d := convert(header(26, 22))
|
||||
out.e := convert(header(31, 27))
|
||||
out
|
||||
}
|
||||
}
|
37
src/main/scala/devices/chiplink/CAM.scala
Normal file
37
src/main/scala/devices/chiplink/CAM.scala
Normal file
@ -0,0 +1,37 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class CAM(keys: Int, dataBits: Int) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
// alloc.valid => allocate a key
|
||||
// alloc.ready => a key is avilable
|
||||
val alloc = Decoupled(UInt(width = dataBits)).flip
|
||||
val key = UInt(OUTPUT, width = log2Ceil(keys))
|
||||
// free.valid => release the key
|
||||
val free = Valid(UInt(width = log2Ceil(keys))).flip
|
||||
val data = UInt(OUTPUT, width = dataBits)
|
||||
}
|
||||
|
||||
val free = RegInit(UInt((BigInt(1) << keys) - 1, width = keys))
|
||||
val data = Mem(keys, UInt(width = dataBits))
|
||||
|
||||
val free_sel = ~(leftOR(free, keys) << 1) & free
|
||||
io.key := OHToUInt(free_sel, keys)
|
||||
|
||||
io.alloc.ready := free.orR
|
||||
when (io.alloc.fire()) { data.write(io.key, io.alloc.bits) }
|
||||
|
||||
// Support free in same cycle as alloc
|
||||
val bypass = io.alloc.fire() && io.free.bits === io.key
|
||||
io.data := Mux(bypass, io.alloc.bits, data(io.free.bits))
|
||||
|
||||
// Update CAM usage
|
||||
val clr = Mux(io.alloc.fire(), free_sel, UInt(0))
|
||||
val set = Mux(io.free.valid, UIntToOH(io.free.bits), UInt(0))
|
||||
free := (free & ~clr) | set
|
||||
}
|
207
src/main/scala/devices/chiplink/ChipLink.scala
Normal file
207
src/main/scala/devices/chiplink/ChipLink.scala
Normal file
@ -0,0 +1,207 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.{Field, Parameters}
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.devices.tilelink.TLBusBypass
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class ChipLink(val params: ChipLinkParams)(implicit p: Parameters) extends LazyModule() {
|
||||
|
||||
val device = new SimpleBus("chiplink", Seq("sifive,chiplink"))
|
||||
|
||||
private def maybeManager(x: Seq[AddressSet], f: Seq[AddressSet] => TLManagerParameters) =
|
||||
if (x.isEmpty) Nil else Seq(f(x))
|
||||
|
||||
private val slaveNode = TLManagerNode(Seq(TLManagerPortParameters(
|
||||
managers =
|
||||
maybeManager(params.TLUH, a => TLManagerParameters(
|
||||
address = a,
|
||||
resources = device.ranges,
|
||||
regionType = RegionType.GET_EFFECTS,
|
||||
executable = true,
|
||||
supportsArithmetic = params.atomicXfer,
|
||||
supportsLogical = params.atomicXfer,
|
||||
supportsGet = params.fullXfer,
|
||||
supportsPutFull = params.fullXfer,
|
||||
supportsPutPartial = params.fullXfer,
|
||||
supportsHint = params.fullXfer,
|
||||
fifoId = Some(0))) ++
|
||||
maybeManager(params.TLC, a => TLManagerParameters(
|
||||
address = a,
|
||||
resources = device.ranges,
|
||||
regionType = RegionType.TRACKED,
|
||||
executable = true,
|
||||
supportsAcquireT = params.acqXfer,
|
||||
supportsAcquireB = params.acqXfer,
|
||||
supportsArithmetic = params.atomicXfer,
|
||||
supportsLogical = params.atomicXfer,
|
||||
supportsGet = params.fullXfer,
|
||||
supportsPutFull = params.fullXfer,
|
||||
supportsPutPartial = params.fullXfer,
|
||||
supportsHint = params.fullXfer,
|
||||
fifoId = Some(0))),
|
||||
beatBytes = 4,
|
||||
endSinkId = params.sinks,
|
||||
minLatency = params.latency)))
|
||||
|
||||
// Masters 1+ require order; Master 0 is unordered and may cache
|
||||
private val masterNode = TLClientNode(Seq(TLClientPortParameters(
|
||||
clients = Seq.tabulate(params.domains) { i =>
|
||||
TLClientParameters(
|
||||
name = "ChipLink Domain #" + i,
|
||||
sourceId = IdRange(i*params.sourcesPerDomain, (i + 1)*params.sourcesPerDomain),
|
||||
requestFifo = i > 0,
|
||||
supportsProbe = if (i == 0) params.fullXfer else params.noXfer) },
|
||||
minLatency = params.latency)))
|
||||
|
||||
private val bypass = LazyModule(new TLBusBypass(beatBytes = 4))
|
||||
slaveNode := bypass.node
|
||||
|
||||
val node = NodeHandle(bypass.node, masterNode)
|
||||
|
||||
// Exported memory map. Used when connecting VIP
|
||||
lazy val managers = masterNode.edges.out(0).manager.managers
|
||||
lazy val mmap = {
|
||||
val (tlc, tluh) = managers.partition(_.supportsAcquireB)
|
||||
params.copy(
|
||||
TLUH = AddressSet.unify(tluh.flatMap(_.address)),
|
||||
TLC = AddressSet.unify(tlc.flatMap(_.address)))
|
||||
}
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = IO(new Bundle {
|
||||
val port = new WideDataLayerPort(params)
|
||||
val bypass = Bool(OUTPUT)
|
||||
// These are fed to port.c2b.{clk,rst} -- must be specified by creator
|
||||
val c2b_clk = Clock(INPUT)
|
||||
val c2b_rst = Bool(INPUT)
|
||||
})
|
||||
|
||||
// Ensure downstream devices support our requirements
|
||||
val (in, edgeIn) = slaveNode.in(0)
|
||||
val (out, edgeOut) = masterNode.out(0)
|
||||
|
||||
require (edgeIn.manager.beatBytes == 4)
|
||||
edgeOut.manager.requireFifo()
|
||||
|
||||
edgeOut.manager.managers.foreach { m =>
|
||||
require (m.supportsGet.contains(params.fullXfer),
|
||||
s"ChipLink requires ${m.name} support ${params.fullXfer} Get, not ${m.supportsGet}")
|
||||
if (m.supportsPutFull) {
|
||||
require (m.supportsPutFull.contains(params.fullXfer),
|
||||
s"ChipLink requires ${m.name} support ${params.fullXfer} PutFill, not ${m.supportsPutFull}")
|
||||
// !!! argh. AHB devices can't: require (m.supportsPutPartial.contains(params.fullXfer),
|
||||
// s"ChipLink requires ${m.name} support ${params.fullXfer} PutPartial not ${m.supportsPutPartial}")
|
||||
require (m.supportsArithmetic.contains(params.atomicXfer),
|
||||
s"ChipLink requires ${m.name} support ${params.atomicXfer} Arithmetic, not ${m.supportsArithmetic}")
|
||||
require (m.supportsLogical.contains(params.atomicXfer),
|
||||
s"ChipLink requires ${m.name} support ${params.atomicXfer} Logical, not ${m.supportsLogical}")
|
||||
}
|
||||
require (m.supportsHint.contains(params.fullXfer),
|
||||
s"ChipLink requires ${m.name} support ${params.fullXfer} Hint, not ${m.supportsHint}")
|
||||
require (!m.supportsAcquireT || m.supportsAcquireT.contains(params.acqXfer),
|
||||
s"ChipLink requires ${m.name} support ${params.acqXfer} AcquireT, not ${m.supportsAcquireT}")
|
||||
require (!m.supportsAcquireB || m.supportsAcquireB.contains(params.acqXfer),
|
||||
s"ChipLink requires ${m.name} support ${params.acqXfer} AcquireB, not ${m.supportsAcquireB}")
|
||||
require (!m.supportsAcquireB || !m.supportsPutFull || m.supportsAcquireT,
|
||||
s"ChipLink requires ${m.name} to support AcquireT if it supports Put and AcquireB")
|
||||
}
|
||||
|
||||
// Anything that is optional, must be supported by the error device (for redirect)
|
||||
val errorDevs = edgeOut.manager.managers.filter(_.nodePath.last.lazyModule.className == "TLError")
|
||||
require (!errorDevs.isEmpty, "There is no TLError reachable from ChipLink. One must be instantiated.")
|
||||
val errorDev = errorDevs.head
|
||||
require (errorDev.supportsPutFull.contains(params.fullXfer),
|
||||
s"ChipLink requires ${errorDev.name} support ${params.fullXfer} PutFill, not ${errorDev.supportsPutFull}")
|
||||
require (errorDev.supportsPutPartial.contains(params.fullXfer),
|
||||
s"ChipLink requires ${errorDev.name} support ${params.fullXfer} PutPartial not ${errorDev.supportsPutPartial}")
|
||||
require (errorDev.supportsArithmetic.contains(params.atomicXfer),
|
||||
s"ChipLink requires ${errorDev.name} support ${params.atomicXfer} Arithmetic, not ${errorDev.supportsArithmetic}")
|
||||
require (errorDev.supportsLogical.contains(params.atomicXfer),
|
||||
s"ChipLink requires ${errorDev.name} support ${params.atomicXfer} Logical, not ${errorDev.supportsLogical}")
|
||||
require (errorDev.supportsAcquireT.contains(params.acqXfer),
|
||||
s"ChipLink requires ${errorDev.name} support ${params.acqXfer} AcquireT, not ${errorDev.supportsAcquireT}")
|
||||
|
||||
// At most one cache can master ChipLink
|
||||
require (edgeIn.client.clients.filter(_.supportsProbe).size <= 1,
|
||||
s"ChipLink supports at most one caching master, ${edgeIn.client.clients.filter(_.supportsProbe).map(_.name)}")
|
||||
|
||||
// Construct the info needed by all submodules
|
||||
val info = ChipLinkInfo(params, edgeIn, edgeOut, errorDevs.head.address.head.base)
|
||||
|
||||
val sinkA = Module(new SinkA(info))
|
||||
val sinkB = Module(new SinkB(info))
|
||||
val sinkC = Module(new SinkC(info))
|
||||
val sinkD = Module(new SinkD(info))
|
||||
val sinkE = Module(new SinkE(info))
|
||||
val sourceA = Module(new SourceA(info))
|
||||
val sourceB = Module(new SourceB(info))
|
||||
val sourceC = Module(new SourceC(info))
|
||||
val sourceD = Module(new SourceD(info))
|
||||
val sourceE = Module(new SourceE(info))
|
||||
|
||||
val rx = Module(new RX(info))
|
||||
rx.clock := io.port.b2c.clk
|
||||
rx.reset := io.port.b2c.rst
|
||||
rx.io.b2c_data := io.port.b2c.data
|
||||
rx.io.b2c_send := io.port.b2c.send
|
||||
out.a <> sourceA.io.a
|
||||
in .b <> sourceB.io.b
|
||||
out.c <> sourceC.io.c
|
||||
in .d <> sourceD.io.d
|
||||
out.e <> sourceE.io.e
|
||||
sourceA.io.q <> FromAsyncBundle(rx.io.a)
|
||||
sourceB.io.q <> FromAsyncBundle(rx.io.b)
|
||||
sourceC.io.q <> FromAsyncBundle(rx.io.c)
|
||||
sourceD.io.q <> FromAsyncBundle(rx.io.d)
|
||||
sourceE.io.q <> FromAsyncBundle(rx.io.e)
|
||||
|
||||
val tx = Module(new TX(info))
|
||||
io.port.c2b.data := tx.io.c2b_data
|
||||
io.port.c2b.send := tx.io.c2b_send
|
||||
sinkA.io.a <> in .a
|
||||
sinkB.io.b <> out.b
|
||||
sinkC.io.c <> in .c
|
||||
sinkD.io.d <> out.d
|
||||
sinkE.io.e <> in .e
|
||||
if (params.syncTX) {
|
||||
tx.io.sa <> sinkA.io.q
|
||||
tx.io.sb <> sinkB.io.q
|
||||
tx.io.sc <> sinkC.io.q
|
||||
tx.io.sd <> sinkD.io.q
|
||||
tx.io.se <> sinkE.io.q
|
||||
} else {
|
||||
tx.clock := io.port.c2b.clk
|
||||
tx.reset := io.port.c2b.rst
|
||||
tx.io.a <> ToAsyncBundle(sinkA.io.q, params.crossingDepth)
|
||||
tx.io.b <> ToAsyncBundle(sinkB.io.q, params.crossingDepth)
|
||||
tx.io.c <> ToAsyncBundle(sinkC.io.q, params.crossingDepth)
|
||||
tx.io.d <> ToAsyncBundle(sinkD.io.q, params.crossingDepth)
|
||||
tx.io.e <> ToAsyncBundle(sinkE.io.q, params.crossingDepth)
|
||||
}
|
||||
|
||||
// Pass credits from RX to TX
|
||||
tx.io.rxc <> rx.io.rxc
|
||||
tx.io.txc <> rx.io.txc
|
||||
|
||||
// Connect the CAM source pools
|
||||
sinkD.io.a_clSource := sourceA.io.d_clSource
|
||||
sourceA.io.d_tlSource := sinkD.io.a_tlSource
|
||||
sinkD.io.c_clSource := sourceC.io.d_clSource
|
||||
sourceC.io.d_tlSource := sinkD.io.c_tlSource
|
||||
sourceD.io.e_tlSink := sinkE.io.d_tlSink
|
||||
sinkE.io.d_clSink := sourceD.io.e_clSink
|
||||
|
||||
// Create the TX clock domain from input
|
||||
io.port.c2b.clk := io.c2b_clk
|
||||
io.port.c2b.rst := io.c2b_rst
|
||||
|
||||
// Disable ChipLink while RX+TX are in reset
|
||||
val do_bypass = ResetCatchAndSync(clock, rx.reset) || ResetCatchAndSync(clock, tx.reset)
|
||||
bypass.module.io.bypass := do_bypass
|
||||
io.bypass := do_bypass
|
||||
}
|
||||
}
|
137
src/main/scala/devices/chiplink/Parameters.scala
Normal file
137
src/main/scala/devices/chiplink/Parameters.scala
Normal file
@ -0,0 +1,137 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.{Field, Parameters}
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.tilelink._
|
||||
|
||||
case class ChipLinkParams(TLUH: Seq[AddressSet], TLC: Seq[AddressSet], sourceBits: Int = 6, sinkBits: Int = 5, syncTX: Boolean = false)
|
||||
{
|
||||
val domains = 8 // hard-wired into chiplink protocol
|
||||
require (sourceBits >= log2Ceil(domains))
|
||||
require (sinkBits >= 0)
|
||||
val sources = 1 << sourceBits
|
||||
val sinks = 1 << sinkBits
|
||||
val sourcesPerDomain = sources / domains
|
||||
val latency = 8 // ChipLink has at least 4 cycles of synchronization per side
|
||||
val dataBytes = 4
|
||||
val dataBits = dataBytes*8
|
||||
val clSourceBits = 16
|
||||
val clSinkBits = 16
|
||||
val crossingDepth = 8
|
||||
val Qdepth = 8192 / dataBytes
|
||||
val maxXfer = 4096
|
||||
val xferBits = log2Ceil(maxXfer)
|
||||
val creditBits = 20 // use saturating addition => we can exploit at most 1MB of buffers
|
||||
val addressBits = 64
|
||||
require (log2Ceil(Qdepth + 1) <= creditBits)
|
||||
|
||||
// Protocol supported operations:
|
||||
val noXfer = TransferSizes.none
|
||||
val fullXfer = TransferSizes(1, 64) // !!! 4096)
|
||||
val acqXfer = TransferSizes(64, 64)
|
||||
val atomicXfer = TransferSizes(1, 8)
|
||||
|
||||
}
|
||||
|
||||
case object ChipLinkKey extends Field[Seq[ChipLinkParams]]
|
||||
|
||||
case class TXN(domain: Int, source: Int)
|
||||
case class ChipLinkInfo(params: ChipLinkParams, edgeIn: TLEdge, edgeOut: TLEdge, errorDev: BigInt)
|
||||
{
|
||||
// TL source => CL TXN
|
||||
val sourceMap: Map[Int, TXN] = {
|
||||
var alloc = 1
|
||||
val domains = Array.fill(params.domains) { 0 }
|
||||
println("ChipLink source mapping CLdomain CLsource <= TLsource:")
|
||||
val out = Map() ++ edgeIn.client.clients.flatMap { c =>
|
||||
// If the client needs order, pick a domain for it
|
||||
val domain = if (c.requestFifo) alloc else 0
|
||||
val offset = domains(domain)
|
||||
println(s"\t${domain} [${offset}, ${offset + c.sourceId.size}) <= [${c.sourceId.start}, ${c.sourceId.end}):\t${c.name}")
|
||||
if (c.requestFifo) {
|
||||
alloc = alloc + 1
|
||||
if (alloc == params.domains) alloc = 1
|
||||
}
|
||||
c.sourceId.range.map { id =>
|
||||
val source = domains(domain)
|
||||
domains(domain) = source + 1
|
||||
(id, TXN(domain, source))
|
||||
}
|
||||
}
|
||||
println("")
|
||||
out
|
||||
}
|
||||
|
||||
def mux(m: Map[Int, Int]): Vec[UInt] = {
|
||||
val maxKey = m.keys.max
|
||||
val maxVal = m.values.max
|
||||
val valBits = log2Up(maxVal + 1)
|
||||
val out = Wire(Vec(maxKey + 1, UInt(width = valBits)))
|
||||
m.foreach { case (k, v) => out(k) := UInt(v, width = valBits) }
|
||||
out
|
||||
}
|
||||
|
||||
// Packet format; little-endian
|
||||
def encode(format: UInt, opcode: UInt, param: UInt, size: UInt, domain: UInt, source: UInt): UInt = {
|
||||
def fmt(x: UInt, w: Int) = (x | UInt(0, width=w))(w-1, 0)
|
||||
Cat(
|
||||
fmt(source, 16),
|
||||
fmt(domain, 3),
|
||||
fmt(size, 4),
|
||||
fmt(param, 3),
|
||||
fmt(opcode, 3),
|
||||
fmt(format, 3))
|
||||
}
|
||||
|
||||
def decode(x: UInt): Seq[UInt] = {
|
||||
val format = x( 2, 0)
|
||||
val opcode = x( 5, 3)
|
||||
val param = x( 8, 6)
|
||||
val size = x(12, 9)
|
||||
val domain = x(15, 13)
|
||||
val source = x(31, 16)
|
||||
Seq(format, opcode, param, size, domain, source)
|
||||
}
|
||||
|
||||
def size2beats(size: UInt): UInt = {
|
||||
val shift = log2Ceil(params.dataBytes)
|
||||
Cat(UIntToOH(size|UInt(0, width=4), params.xferBits + 1) >> (shift + 1), size <= UInt(shift))
|
||||
}
|
||||
|
||||
def mask2beats(size: UInt): UInt = {
|
||||
val shift = log2Ceil(params.dataBytes*8)
|
||||
Cat(UIntToOH(size|UInt(0, width=4), params.xferBits + 1) >> (shift + 1), size <= UInt(shift))
|
||||
}
|
||||
|
||||
def beats1(x: UInt, forceFormat: Option[UInt] = None): UInt = {
|
||||
val Seq(format, opcode, _, size, _, _) = decode(x)
|
||||
val beats = size2beats(size)
|
||||
val masks = mask2beats(size)
|
||||
val grant = opcode === TLMessages.Grant || opcode === TLMessages.GrantData
|
||||
val partial = opcode === TLMessages.PutPartialData
|
||||
val a = Mux(opcode(2), UInt(0), beats) + UInt(2) + Mux(partial, masks, UInt(0))
|
||||
val b = Mux(opcode(2), UInt(0), beats) + UInt(2) + Mux(partial, masks, UInt(0))
|
||||
val c = Mux(opcode(0), beats, UInt(0)) + UInt(2)
|
||||
val d = Mux(opcode(0), beats, UInt(0)) + grant.asUInt
|
||||
val e = UInt(0)
|
||||
val f = UInt(0)
|
||||
Vec(a, b, c, d, e, f)(forceFormat.getOrElse(format))
|
||||
}
|
||||
|
||||
def firstlast(x: DecoupledIO[UInt], forceFormat: Option[UInt] = None): (Bool, Bool) = {
|
||||
val count = RegInit(UInt(0))
|
||||
val beats = beats1(x.bits, forceFormat)
|
||||
val first = count === UInt(0)
|
||||
val last = count === UInt(1) || (first && beats === UInt(0))
|
||||
when (x.fire()) { count := Mux(first, beats, count - UInt(1)) }
|
||||
(first, last)
|
||||
}
|
||||
|
||||
// You can't just unilaterally use error, because this would misalign the mask
|
||||
def makeError(legal: Bool, address: UInt): UInt =
|
||||
Cat(
|
||||
Mux(legal, address, UInt(errorDev))(params.addressBits-1, log2Ceil(params.maxXfer)),
|
||||
address(log2Ceil(params.maxXfer)-1, 0))
|
||||
}
|
106
src/main/scala/devices/chiplink/Partial.scala
Normal file
106
src/main/scala/devices/chiplink/Partial.scala
Normal file
@ -0,0 +1,106 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class ParitalExtractor[T <: TLDataChannel](gen: T) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val last = Bool(INPUT)
|
||||
val i = Decoupled(gen).flip
|
||||
val o = Decoupled(gen)
|
||||
}
|
||||
|
||||
io.o <> io.i
|
||||
|
||||
// Grab references to the fields we care about
|
||||
val (i_opcode, i_data) = io.i.bits match {
|
||||
case a: TLBundleA => (a.opcode, a.data)
|
||||
case b: TLBundleB => (b.opcode, b.data)
|
||||
}
|
||||
val (o_data, o_mask) = io.o.bits match {
|
||||
case a: TLBundleA => (a.data, a.mask)
|
||||
case b: TLBundleB => (b.data, b.mask)
|
||||
}
|
||||
|
||||
val state = RegInit(UInt(0, width=4)) // number of nibbles; [0,8]
|
||||
val shift = Reg(UInt(width=32))
|
||||
val enable = i_opcode === TLMessages.PutPartialData
|
||||
val empty = state === UInt(0)
|
||||
|
||||
when (enable) {
|
||||
val wide = shift | (i_data << (state << 2))
|
||||
o_data := Vec.tabulate(4) { i => wide(9*(i+1)-1, 9*i+1) } .asUInt
|
||||
o_mask := Vec.tabulate(4) { i => wide(9*i) } .asUInt
|
||||
|
||||
// Swallow beat if we have no nibbles
|
||||
when (empty) {
|
||||
io.i.ready := Bool(true)
|
||||
io.o.valid := Bool(false)
|
||||
}
|
||||
|
||||
// Update the FSM
|
||||
when (io.i.fire()) {
|
||||
shift := Mux(empty, i_data, wide >> 36)
|
||||
state := state - UInt(1)
|
||||
when (empty) { state := UInt(8) }
|
||||
when (io.last) { state := UInt(0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PartialInjector[T <: TLDataChannel](gen: T) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val i_last = Bool(INPUT)
|
||||
val o_last = Bool(OUTPUT)
|
||||
val i = Decoupled(gen).flip
|
||||
val o = Decoupled(gen)
|
||||
}
|
||||
|
||||
io.o <> io.i
|
||||
|
||||
// Grab references to the fields we care about
|
||||
val (i_opcode, i_data, i_mask) = io.i.bits match {
|
||||
case a: TLBundleA => (a.opcode, a.data, a.mask)
|
||||
case b: TLBundleB => (b.opcode, b.data, b.mask)
|
||||
}
|
||||
val o_data = io.o.bits match {
|
||||
case a: TLBundleA => a.data
|
||||
case b: TLBundleB => b.data
|
||||
}
|
||||
|
||||
val state = RegInit(UInt(0, width=4)) // number of nibbles; [0,8]
|
||||
val shift = RegInit(UInt(0, width=32))
|
||||
val full = state(3)
|
||||
val partial = i_opcode === TLMessages.PutPartialData
|
||||
|
||||
val last = RegInit(Bool(false))
|
||||
io.o_last := Mux(partial, last, io.i_last)
|
||||
|
||||
when (partial) {
|
||||
val bytes = Seq.tabulate(4) { i => i_data(8*(i+1)-1, 8*i) }
|
||||
val bits = i_mask.toBools
|
||||
val mixed = Cat(Seq(bits, bytes).transpose.flatten.reverse)
|
||||
val wide = shift | (mixed << (state << 2))
|
||||
o_data := wide
|
||||
|
||||
// Inject a beat
|
||||
when ((io.i_last || full) && !last) {
|
||||
io.i.ready := Bool(false)
|
||||
}
|
||||
|
||||
// Update the FSM
|
||||
when (io.o.fire()) {
|
||||
shift := wide >> 32
|
||||
state := state + UInt(1)
|
||||
when (full || last) {
|
||||
state := UInt(0)
|
||||
shift := UInt(0)
|
||||
}
|
||||
last := io.i_last && !last
|
||||
}
|
||||
}
|
||||
}
|
90
src/main/scala/devices/chiplink/RX.scala
Normal file
90
src/main/scala/devices/chiplink/RX.scala
Normal file
@ -0,0 +1,90 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class RX(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val b2c_send = Bool(INPUT)
|
||||
val b2c_data = UInt(INPUT, info.params.dataBits)
|
||||
val a = new AsyncBundle(info.params.crossingDepth, UInt(width = info.params.dataBits))
|
||||
val b = new AsyncBundle(info.params.crossingDepth, UInt(width = info.params.dataBits))
|
||||
val c = new AsyncBundle(info.params.crossingDepth, UInt(width = info.params.dataBits))
|
||||
val d = new AsyncBundle(info.params.crossingDepth, UInt(width = info.params.dataBits))
|
||||
val e = new AsyncBundle(info.params.crossingDepth, UInt(width = info.params.dataBits))
|
||||
val rxc = new AsyncBundle(1, new CreditBump(info.params))
|
||||
val txc = new AsyncBundle(1, new CreditBump(info.params))
|
||||
}
|
||||
|
||||
// Immediately register our input data
|
||||
val b2c_data = RegNext(RegNext(io.b2c_data))
|
||||
val b2c_send = RegNext(RegNext(io.b2c_send, Bool(false)), Bool(false))
|
||||
|
||||
// Fit b2c into the firstlast API
|
||||
val beat = Wire(Decoupled(UInt(width = info.params.dataBits)))
|
||||
beat.bits := b2c_data
|
||||
beat.valid := b2c_send
|
||||
beat.ready := Bool(true)
|
||||
|
||||
// Select the correct HellaQueue for the request
|
||||
val (first, _) = info.firstlast(beat)
|
||||
val formatBits = beat.bits(2, 0)
|
||||
val formatValid = beat.fire() && first
|
||||
val format = Mux(formatValid, formatBits, RegEnable(formatBits, formatValid))
|
||||
val formatOH = UIntToOH(format)
|
||||
|
||||
// Create the receiver buffers
|
||||
val hqa = Module(new HellaQueue(info.params.Qdepth)(beat.bits))
|
||||
val hqb = Module(new HellaQueue(info.params.Qdepth)(beat.bits))
|
||||
val hqc = Module(new HellaQueue(info.params.Qdepth)(beat.bits))
|
||||
val hqd = Module(new HellaQueue(info.params.Qdepth)(beat.bits))
|
||||
val hqe = Module(new HellaQueue(info.params.Qdepth)(beat.bits))
|
||||
|
||||
// Use these to save some typing; function to prevent renaming
|
||||
private def hqX = Seq(hqa, hqb, hqc, hqd, hqe)
|
||||
private def ioX = Seq(io.a, io.b, io.c, io.d, io.e)
|
||||
|
||||
// Enqueue to the HellaQueues
|
||||
(formatOH.toBools zip hqX) foreach { case (sel, hq) =>
|
||||
hq.io.enq.valid := beat.valid && sel
|
||||
hq.io.enq.bits := beat.bits
|
||||
assert (!hq.io.enq.valid || hq.io.enq.ready) // overrun impossible
|
||||
}
|
||||
|
||||
// Send HellaQueue output to their respective FSMs
|
||||
(hqX zip ioX) foreach { case (hq, io) =>
|
||||
io <> ToAsyncBundle(hq.io.deq, info.params.crossingDepth)
|
||||
}
|
||||
|
||||
// Credits we need to hand-off to the TX FSM
|
||||
val tx = RegInit(CreditBump(info.params, 0))
|
||||
val rx = RegInit(CreditBump(info.params, info.params.Qdepth))
|
||||
|
||||
// Constantly transmit credit updates
|
||||
val txOut = Wire(Decoupled(new CreditBump(info.params)))
|
||||
val rxOut = Wire(Decoupled(new CreditBump(info.params)))
|
||||
txOut.valid := Bool(true)
|
||||
rxOut.valid := Bool(true)
|
||||
txOut.bits := tx
|
||||
rxOut.bits := rx
|
||||
io.txc <> ToAsyncBundle(txOut, 1)
|
||||
io.rxc <> ToAsyncBundle(rxOut, 1)
|
||||
|
||||
// Generate new RX credits as the HellaQueues drain
|
||||
val rxInc = Wire(new CreditBump(info.params))
|
||||
(hqX zip rxInc.X) foreach { case (hq, inc) =>
|
||||
inc := hq.io.deq.fire().asUInt
|
||||
}
|
||||
|
||||
// Generate new TX credits as we receive F-format messages
|
||||
val txInc = Mux(beat.valid && formatOH(5), CreditBump(info.params, beat.bits), CreditBump(info.params, 0))
|
||||
|
||||
// As we hand-over credits, reset the counters
|
||||
tx := tx + txInc
|
||||
rx := rx + rxInc
|
||||
when (txOut.fire()) { tx := txInc }
|
||||
when (rxOut.fire()) { rx := rxInc }
|
||||
}
|
65
src/main/scala/devices/chiplink/SinkA.scala
Normal file
65
src/main/scala/devices/chiplink/SinkA.scala
Normal file
@ -0,0 +1,65 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
|
||||
class SinkA(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val a = Decoupled(new TLBundleA(info.edgeIn.bundle)).flip
|
||||
val q = Decoupled(new DataLayer(info.params))
|
||||
}
|
||||
|
||||
// Map TileLink sources to ChipLink sources+domain
|
||||
val tl2cl = info.sourceMap
|
||||
val source = info.mux(tl2cl.mapValues(_.source))
|
||||
val domain = info.mux(tl2cl.mapValues(_.domain))
|
||||
|
||||
// We need a Q because we stall the channel while serializing it's header
|
||||
val inject = Module(new PartialInjector(io.a.bits))
|
||||
inject.io.i <> Queue(io.a, 1, flow=true)
|
||||
inject.io.i_last := info.edgeIn.last(inject.io.i)
|
||||
val a = inject.io.o
|
||||
val a_last = inject.io.o_last
|
||||
val a_hasData = info.edgeIn.hasData(a.bits)
|
||||
val a_partial = a.bits.opcode === TLMessages.PutPartialData
|
||||
|
||||
// A simple FSM to generate the packet components
|
||||
val state = RegInit(UInt(0, width = 2))
|
||||
val s_header = UInt(0, width = 2)
|
||||
val s_address0 = UInt(1, width = 2)
|
||||
val s_address1 = UInt(2, width = 2)
|
||||
val s_data = UInt(3, width = 2)
|
||||
|
||||
when (io.q.fire()) {
|
||||
switch (state) {
|
||||
is (s_header) { state := s_address0 }
|
||||
is (s_address0) { state := s_address1 }
|
||||
is (s_address1) { state := Mux(a_hasData, s_data, s_header) }
|
||||
is (s_data) { state := Mux(!a_last, s_data, s_header) }
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the header beat
|
||||
val header = info.encode(
|
||||
format = UInt(0),
|
||||
opcode = a.bits.opcode,
|
||||
param = a.bits.param,
|
||||
size = a.bits.size,
|
||||
domain = domain(a.bits.source),
|
||||
source = source(a.bits.source))
|
||||
|
||||
// Construct the address beats
|
||||
val address0 = a.bits.address
|
||||
val address1 = a.bits.address >> 32
|
||||
|
||||
// Frame the output packet
|
||||
val isLastState = state === Mux(a_hasData, s_data, s_address1)
|
||||
a.ready := io.q.ready && isLastState
|
||||
io.q.valid := a.valid
|
||||
io.q.bits.last := a_last && isLastState
|
||||
io.q.bits.data := Vec(header, address0, address1, a.bits.data)(state)
|
||||
io.q.bits.beats := Mux(a_hasData, info.size2beats(a.bits.size), UInt(0)) + UInt(3) +
|
||||
Mux(a_partial, info.mask2beats(a.bits.size), UInt(0))
|
||||
}
|
62
src/main/scala/devices/chiplink/SinkB.scala
Normal file
62
src/main/scala/devices/chiplink/SinkB.scala
Normal file
@ -0,0 +1,62 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
|
||||
class SinkB(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val b = Decoupled(new TLBundleB(info.edgeOut.bundle)).flip
|
||||
val q = Decoupled(new DataLayer(info.params))
|
||||
}
|
||||
|
||||
// We need a Q because we stall the channel while serializing it's header
|
||||
val inject = Module(new PartialInjector(io.b.bits))
|
||||
inject.io.i <> Queue(io.b, 1, flow=true)
|
||||
inject.io.i_last := info.edgeOut.last(inject.io.i)
|
||||
val b = inject.io.o
|
||||
val b_last = inject.io.o_last
|
||||
val b_hasData = info.edgeOut.hasData(b.bits)
|
||||
val b_partial = b.bits.opcode === TLMessages.PutPartialData
|
||||
|
||||
// A simple FSM to generate the packet components
|
||||
val state = RegInit(UInt(0, width = 2))
|
||||
val s_header = UInt(0, width = 2)
|
||||
val s_address0 = UInt(1, width = 2)
|
||||
val s_address1 = UInt(2, width = 2)
|
||||
val s_data = UInt(3, width = 2)
|
||||
|
||||
when (io.q.fire()) {
|
||||
switch (state) {
|
||||
is (s_header) { state := s_address0 }
|
||||
is (s_address0) { state := s_address1 }
|
||||
is (s_address1) { state := Mux(b_hasData, s_data, s_header) }
|
||||
is (s_data) { state := Mux(!b_last, s_data, s_header) }
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the header beat
|
||||
val header = info.encode(
|
||||
format = UInt(1),
|
||||
opcode = b.bits.opcode,
|
||||
param = b.bits.param,
|
||||
size = b.bits.size,
|
||||
domain = UInt(0), // ChipLink only allows one remote cache, in domain 0
|
||||
source = UInt(0))
|
||||
|
||||
assert (!b.valid || b.bits.source === UInt(0))
|
||||
|
||||
// Construct the address beats
|
||||
val address0 = b.bits.address
|
||||
val address1 = b.bits.address >> 32
|
||||
|
||||
// Frame the output packet
|
||||
val isLastState = state === Mux(b_hasData, s_data, s_address1)
|
||||
b.ready := io.q.ready && isLastState
|
||||
io.q.valid := b.valid
|
||||
io.q.bits.last := b_last && isLastState
|
||||
io.q.bits.data := Vec(header, address0, address1, b.bits.data)(state)
|
||||
io.q.bits.beats := Mux(b_hasData, info.size2beats(b.bits.size), UInt(0)) + UInt(3) +
|
||||
Mux(b_partial, info.mask2beats(b.bits.size), UInt(0))
|
||||
}
|
63
src/main/scala/devices/chiplink/SinkC.scala
Normal file
63
src/main/scala/devices/chiplink/SinkC.scala
Normal file
@ -0,0 +1,63 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
|
||||
class SinkC(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val c = Decoupled(new TLBundleC(info.edgeIn.bundle)).flip
|
||||
val q = Decoupled(new DataLayer(info.params))
|
||||
}
|
||||
|
||||
// Map TileLink sources to ChipLink sources+domain
|
||||
val tl2cl = info.sourceMap
|
||||
val source = info.mux(tl2cl.mapValues(_.source))
|
||||
val domain = info.mux(tl2cl.mapValues(_.domain))
|
||||
|
||||
// We need a Q because we stall the channel while serializing it's header
|
||||
val c = Queue(io.c, 1, flow=true)
|
||||
val c_last = info.edgeIn.last(c)
|
||||
val c_hasData = info.edgeIn.hasData(c.bits)
|
||||
val c_release = c.bits.opcode === TLMessages.Release || c.bits.opcode === TLMessages.ReleaseData
|
||||
|
||||
// A simple FSM to generate the packet components
|
||||
val state = RegInit(UInt(0, width = 2))
|
||||
val s_header = UInt(0, width = 2)
|
||||
val s_address0 = UInt(1, width = 2)
|
||||
val s_address1 = UInt(2, width = 2)
|
||||
val s_data = UInt(3, width = 2)
|
||||
|
||||
when (io.q.fire()) {
|
||||
switch (state) {
|
||||
is (s_header) { state := s_address0 }
|
||||
is (s_address0) { state := s_address1 }
|
||||
is (s_address1) { state := Mux(c_hasData, s_data, s_header) }
|
||||
is (s_data) { state := Mux(!c_last, s_data, s_header) }
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the header beat
|
||||
val header = info.encode(
|
||||
format = UInt(2),
|
||||
opcode = c.bits.opcode,
|
||||
param = c.bits.param,
|
||||
size = c.bits.size,
|
||||
domain = UInt(0), // only caches (unordered) can release
|
||||
source = Mux(c_release, source(c.bits.source), UInt(0)))
|
||||
|
||||
assert (!c.valid || domain(c.bits.source) === UInt(0))
|
||||
|
||||
// Construct the address beats
|
||||
val address0 = c.bits.address
|
||||
val address1 = c.bits.address >> 32
|
||||
|
||||
// Frame the output packet
|
||||
val isLastState = state === Mux(c_hasData, s_data, s_address1)
|
||||
c.ready := io.q.ready && isLastState
|
||||
io.q.valid := c.valid
|
||||
io.q.bits.last := c_last && isLastState
|
||||
io.q.bits.data := Vec(header, address0, address1, c.bits.data)(state)
|
||||
io.q.bits.beats := Mux(c_hasData, info.size2beats(c.bits.size), UInt(0)) + UInt(3)
|
||||
}
|
60
src/main/scala/devices/chiplink/SinkD.scala
Normal file
60
src/main/scala/devices/chiplink/SinkD.scala
Normal file
@ -0,0 +1,60 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
|
||||
class SinkD(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val d = Decoupled(new TLBundleD(info.edgeOut.bundle)).flip
|
||||
val q = Decoupled(new DataLayer(info.params))
|
||||
val a_tlSource = Valid(UInt(width = info.params.sourceBits))
|
||||
val a_clSource = UInt(INPUT, width = info.params.clSourceBits)
|
||||
val c_tlSource = Valid(UInt(width = info.params.sourceBits))
|
||||
val c_clSource = UInt(INPUT, width = info.params.clSourceBits)
|
||||
}
|
||||
|
||||
// The FSM states
|
||||
val state = RegInit(UInt(0, width = 2))
|
||||
val s_header = UInt(0, width = 2)
|
||||
val s_sink = UInt(1, width = 2)
|
||||
val s_data = UInt(2, width = 2)
|
||||
|
||||
// We need a Q because we stall the channel while serializing it's header
|
||||
val d = Queue(io.d, 1, flow=true)
|
||||
val d_last = info.edgeOut.last(d)
|
||||
val d_hasData = info.edgeOut.hasData(d.bits)
|
||||
val d_grant = d.bits.opcode === TLMessages.Grant || d.bits.opcode === TLMessages.GrantData
|
||||
|
||||
when (io.q.fire()) {
|
||||
switch (state) {
|
||||
is (s_header) { state := Mux(d_grant, s_sink, Mux(d_hasData, s_data, s_header)) }
|
||||
is (s_sink) { state := Mux(d_hasData, s_data, s_header) }
|
||||
is (s_data) { state := Mux(d_last, s_header, s_data) }
|
||||
}
|
||||
}
|
||||
|
||||
// Release the TL source
|
||||
val relack = d.bits.opcode === TLMessages.ReleaseAck
|
||||
io.a_tlSource.valid := io.q.fire() && state === s_header && !relack
|
||||
io.a_tlSource.bits := d.bits.source
|
||||
io.c_tlSource.valid := io.q.fire() && state === s_header && relack
|
||||
io.c_tlSource.bits := d.bits.source
|
||||
|
||||
// Construct the header beat
|
||||
val header = info.encode(
|
||||
format = UInt(3),
|
||||
opcode = d.bits.opcode,
|
||||
param = d.bits.param,
|
||||
size = d.bits.size,
|
||||
domain = d.bits.source >> log2Ceil(info.params.sourcesPerDomain),
|
||||
source = Mux(relack, io.c_clSource, io.a_clSource))
|
||||
|
||||
val isLastState = state === Mux(d_hasData, s_data, Mux(d_grant, s_sink, s_header))
|
||||
d.ready := io.q.ready && isLastState
|
||||
io.q.valid := d.valid
|
||||
io.q.bits.last := d_last && isLastState
|
||||
io.q.bits.data := Vec(header, d.bits.sink, d.bits.data)(state)
|
||||
io.q.bits.beats := Mux(d_hasData, info.size2beats(d.bits.size), UInt(0)) + UInt(1) + d_grant.asUInt
|
||||
}
|
33
src/main/scala/devices/chiplink/SinkE.scala
Normal file
33
src/main/scala/devices/chiplink/SinkE.scala
Normal file
@ -0,0 +1,33 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
|
||||
class SinkE(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val e = Decoupled(new TLBundleE(info.edgeIn.bundle)).flip
|
||||
val q = Decoupled(new DataLayer(info.params))
|
||||
// Find the sink from D
|
||||
val d_tlSink = Valid(UInt(width = info.params.sinkBits))
|
||||
val d_clSink = UInt(INPUT, width = info.params.clSinkBits)
|
||||
}
|
||||
|
||||
io.d_tlSink.valid := io.e.fire()
|
||||
io.d_tlSink.bits := io.e.bits.sink
|
||||
|
||||
val header = info.encode(
|
||||
format = UInt(4),
|
||||
opcode = UInt(0),
|
||||
param = UInt(0),
|
||||
size = UInt(0),
|
||||
domain = UInt(0),
|
||||
source = io.d_clSink)
|
||||
|
||||
io.e.ready := io.q.ready
|
||||
io.q.valid := io.e.valid
|
||||
io.q.bits.last := Bool(true)
|
||||
io.q.bits.data := header
|
||||
io.q.bits.beats := UInt(1)
|
||||
}
|
104
src/main/scala/devices/chiplink/SourceA.scala
Normal file
104
src/main/scala/devices/chiplink/SourceA.scala
Normal file
@ -0,0 +1,104 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class SourceA(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val a = Decoupled(new TLBundleA(info.edgeOut.bundle))
|
||||
val q = Decoupled(UInt(width = info.params.dataBits)).flip
|
||||
// Used by D to find the txn
|
||||
val d_tlSource = Valid(UInt(width = info.params.sourceBits)).flip
|
||||
val d_clSource = UInt(OUTPUT, width = info.params.clSourceBits)
|
||||
}
|
||||
|
||||
// CAM of sources used for each domain
|
||||
val cams = Seq.fill(info.params.domains) {
|
||||
Module(new CAM(info.params.sourcesPerDomain, info.params.clSourceBits))
|
||||
}
|
||||
|
||||
// A simple FSM to generate the packet components
|
||||
val state = RegInit(UInt(0, width = 2))
|
||||
val s_header = UInt(0, width = 2)
|
||||
val s_address0 = UInt(1, width = 2)
|
||||
val s_address1 = UInt(2, width = 2)
|
||||
val s_data = UInt(3, width = 2)
|
||||
|
||||
private def hold(key: UInt)(data: UInt) = {
|
||||
val enable = state === key
|
||||
Mux(enable, data, RegEnable(data, enable))
|
||||
}
|
||||
|
||||
// Extract header fields
|
||||
val Seq(_, q_opcode, q_param, q_size, q_domain, q_source) =
|
||||
info.decode(io.q.bits).map(hold(s_header) _)
|
||||
|
||||
// Latch address
|
||||
val q_address0 = hold(s_address0)(io.q.bits)
|
||||
val q_address1 = hold(s_address1)(io.q.bits)
|
||||
|
||||
val (_, q_last) = info.firstlast(io.q, Some(UInt(0)))
|
||||
val q_hasData = !q_opcode(2)
|
||||
val a_first = RegEnable(state =/= s_data, io.q.fire())
|
||||
|
||||
when (io.q.fire()) {
|
||||
switch (state) {
|
||||
is (s_header) { state := s_address0 }
|
||||
is (s_address0) { state := s_address1 }
|
||||
is (s_address1) { state := Mux(q_hasData, s_data, s_header) }
|
||||
is (s_data) { state := Mux(!q_last, s_data, s_header) }
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if the request is legal. If not, route to error device.
|
||||
val q_address = Cat(q_address1, q_address0)
|
||||
val q_acq = q_opcode === TLMessages.AcquireBlock || q_opcode === TLMessages.AcquirePerm
|
||||
val q_write = Mux(q_acq, q_param === TLPermissions.NtoT || q_param === TLPermissions.BtoT, q_hasData)
|
||||
val exists = info.edgeOut.manager.containsSafe(q_address)
|
||||
private def writeable(m: TLManagerParameters): Boolean = if (m.supportsAcquireB) m.supportsAcquireT else m.supportsPutFull
|
||||
private def acquireable(m: TLManagerParameters): Boolean = m.supportsAcquireB || m.supportsAcquireT
|
||||
private def toBool(x: Boolean) = Bool(x)
|
||||
val writeOk = info.edgeOut.manager.fastProperty(q_address, writeable, toBool)
|
||||
val acquireOk = info.edgeOut.manager.fastProperty(q_address, acquireable, toBool)
|
||||
val q_legal = exists && (!q_write || writeOk) && (!q_acq || acquireOk)
|
||||
|
||||
// Look for an available source in the correct domain
|
||||
val source_ok = Vec(cams.map(_.io.alloc.ready))(q_domain)
|
||||
val source = Vec(cams.map(_.io.key))(q_domain) holdUnless a_first
|
||||
val a_sel = UIntToOH(q_domain)
|
||||
|
||||
// Feed our preliminary A channel via the Partial Extractor FSM
|
||||
val extract = Module(new ParitalExtractor(io.a.bits))
|
||||
io.a <> extract.io.o
|
||||
val a = extract.io.i
|
||||
extract.io.last := q_last
|
||||
|
||||
a.bits.opcode := q_opcode
|
||||
a.bits.param := q_param
|
||||
a.bits.size := q_size
|
||||
a.bits.source := Cat(q_domain, source)
|
||||
a.bits.address := info.makeError(q_legal, q_address)
|
||||
a.bits.mask := MaskGen(q_address0, q_size, info.params.dataBytes)
|
||||
a.bits.data := io.q.bits
|
||||
|
||||
val stall = a_first && !source_ok
|
||||
val xmit = q_last || state === s_data
|
||||
a.valid := (io.q.valid && !stall) && xmit
|
||||
io.q.ready := (a.ready && !stall) || !xmit
|
||||
(cams zip a_sel.toBools) foreach { case (cam, sel) =>
|
||||
cam.io.alloc.valid := sel && a_first && xmit && io.q.valid && a.ready
|
||||
cam.io.alloc.bits := q_source
|
||||
}
|
||||
|
||||
// Free the CAM entries
|
||||
val d_clDomain = io.d_tlSource.bits >> log2Ceil(info.params.sourcesPerDomain)
|
||||
val d_sel = UIntToOH(d_clDomain)
|
||||
io.d_clSource := Vec(cams.map(_.io.data))(d_clDomain)
|
||||
(cams zip d_sel.toBools) foreach { case (cam, sel) =>
|
||||
cam.io.free.bits := io.d_tlSource.bits
|
||||
cam.io.free.valid := io.d_tlSource.valid && sel
|
||||
}
|
||||
}
|
68
src/main/scala/devices/chiplink/SourceB.scala
Normal file
68
src/main/scala/devices/chiplink/SourceB.scala
Normal file
@ -0,0 +1,68 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class SourceB(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val b = Decoupled(new TLBundleB(info.edgeIn.bundle))
|
||||
val q = Decoupled(UInt(width = info.params.dataBits)).flip
|
||||
}
|
||||
|
||||
// Find the optional cache (at most one)
|
||||
val cache = info.edgeIn.client.clients.filter(_.supportsProbe).headOption
|
||||
|
||||
// A simple FSM to generate the packet components
|
||||
val state = RegInit(UInt(0, width = 2))
|
||||
val s_header = UInt(0, width = 2)
|
||||
val s_address0 = UInt(1, width = 2)
|
||||
val s_address1 = UInt(2, width = 2)
|
||||
val s_data = UInt(3, width = 2)
|
||||
|
||||
private def hold(key: UInt)(data: UInt) = {
|
||||
val enable = state === key
|
||||
Mux(enable, data, RegEnable(data, enable))
|
||||
}
|
||||
|
||||
// Extract header fields
|
||||
val Seq(_, q_opcode, q_param, q_size, _, _) =
|
||||
info.decode(io.q.bits).map(hold(s_header) _)
|
||||
|
||||
// Latch address
|
||||
val q_address0 = hold(s_address0)(io.q.bits)
|
||||
val q_address1 = hold(s_address1)(io.q.bits)
|
||||
|
||||
val (_, q_last) = info.firstlast(io.q, Some(UInt(1)))
|
||||
val q_hasData = !q_opcode(2)
|
||||
val b_first = RegEnable(state =/= s_data, io.q.fire())
|
||||
|
||||
when (io.q.fire()) {
|
||||
switch (state) {
|
||||
is (s_header) { state := s_address0 }
|
||||
is (s_address0) { state := s_address1 }
|
||||
is (s_address1) { state := Mux(q_hasData, s_data, s_header) }
|
||||
is (s_data) { state := Mux(!q_last, s_data, s_header) }
|
||||
}
|
||||
}
|
||||
|
||||
// Feed our preliminary B channel via the Partial Extractor FSM
|
||||
val extract = Module(new ParitalExtractor(io.b.bits))
|
||||
io.b <> extract.io.o
|
||||
val b = extract.io.i
|
||||
extract.io.last := q_last
|
||||
|
||||
b.bits.opcode := q_opcode
|
||||
b.bits.param := q_param
|
||||
b.bits.size := q_size
|
||||
b.bits.source := UInt(cache.map(_.sourceId.start).getOrElse(0))
|
||||
b.bits.address := Cat(q_address1, q_address0)
|
||||
b.bits.mask := MaskGen(q_address0, q_size, info.params.dataBytes)
|
||||
b.bits.data := io.q.bits
|
||||
|
||||
val xmit = q_last || state === s_data
|
||||
b.valid := io.q.valid && xmit
|
||||
io.q.ready := b.ready || !xmit
|
||||
}
|
87
src/main/scala/devices/chiplink/SourceC.scala
Normal file
87
src/main/scala/devices/chiplink/SourceC.scala
Normal file
@ -0,0 +1,87 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class SourceC(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val c = Decoupled(new TLBundleC(info.edgeOut.bundle))
|
||||
val q = Decoupled(UInt(width = info.params.dataBits)).flip
|
||||
// Used by D to find the txn
|
||||
val d_tlSource = Valid(UInt(width = info.params.sourceBits)).flip
|
||||
val d_clSource = UInt(OUTPUT, width = info.params.clSourceBits)
|
||||
}
|
||||
|
||||
// CAM of sources used for release
|
||||
val cam = Module(new CAM(info.params.sourcesPerDomain, info.params.clSourceBits))
|
||||
|
||||
// A simple FSM to generate the packet components
|
||||
val state = RegInit(UInt(0, width = 2))
|
||||
val s_header = UInt(0, width = 2)
|
||||
val s_address0 = UInt(1, width = 2)
|
||||
val s_address1 = UInt(2, width = 2)
|
||||
val s_data = UInt(3, width = 2)
|
||||
|
||||
private def hold(key: UInt)(data: UInt) = {
|
||||
val enable = state === key
|
||||
Mux(enable, data, RegEnable(data, enable))
|
||||
}
|
||||
|
||||
// Extract header fields
|
||||
val Seq(_, q_opcode, q_param, q_size, _, q_source) =
|
||||
info.decode(io.q.bits).map(hold(s_header) _)
|
||||
|
||||
// Latch address
|
||||
val q_address0 = hold(s_address0)(io.q.bits)
|
||||
val q_address1 = hold(s_address1)(io.q.bits)
|
||||
|
||||
val (_, q_last) = info.firstlast(io.q, Some(UInt(2)))
|
||||
val q_hasData = q_opcode(0)
|
||||
val c_first = RegEnable(state =/= s_data, io.q.fire())
|
||||
|
||||
when (io.q.fire()) {
|
||||
switch (state) {
|
||||
is (s_header) { state := s_address0 }
|
||||
is (s_address0) { state := s_address1 }
|
||||
is (s_address1) { state := Mux(q_hasData, s_data, s_header) }
|
||||
is (s_data) { state := Mux(!q_last, s_data, s_header) }
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if the request is legal. If not, route to error device.
|
||||
val q_address = Cat(q_address1, q_address0)
|
||||
val exists = info.edgeOut.manager.containsSafe(q_address)
|
||||
private def writeable(m: TLManagerParameters): Boolean = if (m.supportsAcquireB) m.supportsAcquireT else m.supportsPutFull
|
||||
private def acquireable(m: TLManagerParameters): Boolean = m.supportsAcquireB || m.supportsAcquireT
|
||||
private def toBool(x: Boolean) = Bool(x)
|
||||
val writeOk = info.edgeOut.manager.fastProperty(q_address, writeable, toBool)
|
||||
val acquireOk = info.edgeOut.manager.fastProperty(q_address, acquireable, toBool)
|
||||
val q_legal = exists && (!q_hasData || writeOk) && acquireOk
|
||||
|
||||
// Look for an available source in the correct domain
|
||||
val q_release = q_opcode === TLMessages.Release || q_opcode === TLMessages.ReleaseData
|
||||
val source_ok = !q_release || cam.io.alloc.ready
|
||||
val source = cam.io.key holdUnless c_first
|
||||
|
||||
io.c.bits.opcode := q_opcode
|
||||
io.c.bits.param := q_param
|
||||
io.c.bits.size := q_size
|
||||
io.c.bits.source := Mux(q_release, source, UInt(0)) // always domain 0
|
||||
io.c.bits.address := info.makeError(q_legal, q_address)
|
||||
io.c.bits.data := io.q.bits
|
||||
io.c.bits.error := Bool(false) // !!! need a packet footer
|
||||
|
||||
val stall = c_first && !source_ok
|
||||
val xmit = q_last || state === s_data
|
||||
io.c.valid := (io.q.valid && !stall) && xmit
|
||||
io.q.ready := (io.c.ready && !stall) || !xmit
|
||||
cam.io.alloc.valid := q_release && c_first && xmit && io.q.valid && io.c.ready
|
||||
cam.io.alloc.bits := q_source
|
||||
|
||||
// Free the CAM entries
|
||||
io.d_clSource := cam.io.data
|
||||
cam.io.free := io.d_tlSource
|
||||
}
|
82
src/main/scala/devices/chiplink/SourceD.scala
Normal file
82
src/main/scala/devices/chiplink/SourceD.scala
Normal file
@ -0,0 +1,82 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class SourceD(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val d = Decoupled(new TLBundleD(info.edgeIn.bundle))
|
||||
val q = Decoupled(UInt(width = info.params.dataBits)).flip
|
||||
// Used by E to find the txn
|
||||
val e_tlSink = Valid(UInt(width = info.params.sinkBits)).flip
|
||||
val e_clSink = UInt(OUTPUT, width = info.params.clSinkBits)
|
||||
}
|
||||
|
||||
// We need a sink id CAM
|
||||
val cam = Module(new CAM(info.params.sinks, info.params.clSinkBits))
|
||||
|
||||
// Map ChipLink transaction to TileLink source
|
||||
val cl2tl = info.sourceMap.map(_.swap)
|
||||
val nestedMap = cl2tl.groupBy(_._1.domain).mapValues(_.map { case (TXN(_, cls), tls) => (cls, tls) })
|
||||
val muxes = Seq.tabulate(info.params.domains) { i =>
|
||||
info.mux(nestedMap.lift(i).getOrElse(Map(0 -> 0)))
|
||||
}
|
||||
|
||||
// The FSM states
|
||||
val state = RegInit(UInt(0, width = 2))
|
||||
val s_header = UInt(0, width = 2)
|
||||
val s_sink = UInt(1, width = 2)
|
||||
val s_data = UInt(2, width = 2)
|
||||
|
||||
private def hold(key: UInt)(data: UInt) = {
|
||||
val enable = state === key
|
||||
Mux(enable, data, RegEnable(data, enable))
|
||||
}
|
||||
|
||||
// Extract header fields from the message
|
||||
val Seq(_, q_opcode, q_param, q_size, q_domain, q_source) =
|
||||
info.decode(io.q.bits).map(hold(s_header) _)
|
||||
|
||||
// Extract sink from the optional second beat
|
||||
val q_sink = hold(s_sink)(io.q.bits(15, 0))
|
||||
|
||||
val q_grant = q_opcode === TLMessages.Grant || q_opcode === TLMessages.GrantData
|
||||
val (_, q_last) = info.firstlast(io.q, Some(UInt(3)))
|
||||
val d_first = RegEnable(state =/= s_data, io.q.fire())
|
||||
val s_maybe_data = Mux(q_last, s_header, s_data)
|
||||
|
||||
when (io.q.fire()) {
|
||||
switch (state) {
|
||||
is (s_header) { state := Mux(q_grant, s_sink, s_maybe_data) }
|
||||
is (s_sink) { state := s_maybe_data }
|
||||
is (s_data) { state := s_maybe_data }
|
||||
}
|
||||
}
|
||||
|
||||
// Look for an available sink
|
||||
val sink_ok = !q_grant || cam.io.alloc.ready
|
||||
val sink = cam.io.key holdUnless d_first
|
||||
val stall = d_first && !sink_ok
|
||||
val xmit = q_last || state === s_data
|
||||
|
||||
io.d.bits.opcode := q_opcode
|
||||
io.d.bits.param := q_param
|
||||
io.d.bits.size := q_size
|
||||
io.d.bits.source := Vec(muxes.map { m => m(q_source) })(q_domain)
|
||||
io.d.bits.sink := Mux(q_grant, sink, UInt(0))
|
||||
io.d.bits.data := io.q.bits
|
||||
io.d.bits.error := Bool(false) // !!! frack => need packet footer?
|
||||
|
||||
io.d.valid := (io.q.valid && !stall) && xmit
|
||||
io.q.ready := (io.d.ready && !stall) || !xmit
|
||||
|
||||
cam.io.alloc.valid := q_grant && d_first && xmit && io.q.valid && io.d.ready
|
||||
cam.io.alloc.bits := q_sink
|
||||
|
||||
// Free the CAM
|
||||
io.e_clSink := cam.io.data
|
||||
cam.io.free := io.e_tlSink
|
||||
}
|
21
src/main/scala/devices/chiplink/SourceE.scala
Normal file
21
src/main/scala/devices/chiplink/SourceE.scala
Normal file
@ -0,0 +1,21 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class SourceE(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val e = Decoupled(new TLBundleE(info.edgeOut.bundle))
|
||||
val q = Decoupled(UInt(width = info.params.dataBits)).flip
|
||||
}
|
||||
|
||||
// Extract header fields
|
||||
val Seq(_, _, _, _, _, q_sink) = info.decode(io.q.bits)
|
||||
|
||||
io.q.ready := io.e.ready
|
||||
io.e.valid := io.q.valid
|
||||
io.e.bits.sink := q_sink
|
||||
}
|
99
src/main/scala/devices/chiplink/TX.scala
Normal file
99
src/main/scala/devices/chiplink/TX.scala
Normal file
@ -0,0 +1,99 @@
|
||||
// See LICENSE for license details.
|
||||
package sifive.blocks.devices.chiplink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class TX(info: ChipLinkInfo) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val c2b_send = Bool(OUTPUT)
|
||||
val c2b_data = UInt(OUTPUT, info.params.dataBits)
|
||||
val a = new AsyncBundle(info.params.crossingDepth, new DataLayer(info.params)).flip
|
||||
val b = new AsyncBundle(info.params.crossingDepth, new DataLayer(info.params)).flip
|
||||
val c = new AsyncBundle(info.params.crossingDepth, new DataLayer(info.params)).flip
|
||||
val d = new AsyncBundle(info.params.crossingDepth, new DataLayer(info.params)).flip
|
||||
val e = new AsyncBundle(info.params.crossingDepth, new DataLayer(info.params)).flip
|
||||
val sa = DecoupledIO(new DataLayer(info.params)).flip
|
||||
val sb = DecoupledIO(new DataLayer(info.params)).flip
|
||||
val sc = DecoupledIO(new DataLayer(info.params)).flip
|
||||
val sd = DecoupledIO(new DataLayer(info.params)).flip
|
||||
val se = DecoupledIO(new DataLayer(info.params)).flip
|
||||
val rxc = new AsyncBundle(1, new CreditBump(info.params)).flip
|
||||
val txc = new AsyncBundle(1, new CreditBump(info.params)).flip
|
||||
}
|
||||
|
||||
// Currently available credits
|
||||
val rx = RegInit(CreditBump(info.params, 0))
|
||||
val tx = RegInit(CreditBump(info.params, 0))
|
||||
val first = RegInit(Bool(true))
|
||||
|
||||
// Constantly pull credits from RX
|
||||
val rxInc = FromAsyncBundle(io.rxc)
|
||||
val txInc = FromAsyncBundle(io.txc)
|
||||
rxInc.ready := Bool(true)
|
||||
txInc.ready := Bool(true)
|
||||
|
||||
// Cross the requests (if necessary)
|
||||
val sync = info.params.syncTX
|
||||
val a = if (sync) ShiftQueue(io.sa, 2) else FromAsyncBundle(io.a)
|
||||
val b = if (sync) ShiftQueue(io.sb, 2) else FromAsyncBundle(io.b)
|
||||
val c = if (sync) ShiftQueue(io.sc, 2) else FromAsyncBundle(io.c)
|
||||
val d = if (sync) ShiftQueue(io.sd, 2) else FromAsyncBundle(io.d)
|
||||
val e = if (sync) ShiftQueue(io.se, 2) else FromAsyncBundle(io.e)
|
||||
|
||||
private def ioX = Seq(a, b, c, d, e)
|
||||
val validABCDE = Cat(ioX.map(_.valid).reverse)
|
||||
|
||||
// Calculate if the packet will fit
|
||||
val txDec = CreditBump(info.params, 0)
|
||||
val spaceABCDE = Cat(((tx.X zip txDec.X) zip ioX) .map { case ((credit, reduce), beat) =>
|
||||
val delta = credit -& beat.bits.beats
|
||||
reduce := Mux(beat.fire() && first, delta, credit)
|
||||
delta.asSInt >= SInt(0)
|
||||
}.reverse)
|
||||
val requestABCDE = validABCDE & spaceABCDE
|
||||
|
||||
// How often should we force transmission of a credit update? sqrt
|
||||
val xmitBits = log2Ceil(info.params.Qdepth) / 2
|
||||
val xmit = RegInit(UInt(0, width = xmitBits))
|
||||
val forceXmit = xmit === UInt(0)
|
||||
|
||||
// Frame an update of the RX credits
|
||||
val (header, rxLeft) = rx.toHeader
|
||||
val f = Wire(Decoupled(new DataLayer(info.params)))
|
||||
f.valid := requestABCDE === UInt(0) || forceXmit
|
||||
f.bits.data := header
|
||||
f.bits.last := Bool(true)
|
||||
f.bits.beats := UInt(1)
|
||||
|
||||
when (!forceXmit) { xmit := xmit - UInt(1) }
|
||||
when (f.fire()) { xmit := ~UInt(0, width = xmitBits) }
|
||||
|
||||
// Include the F credit channel in arbitration
|
||||
val ioF = ioX :+ f
|
||||
val space = Cat(UInt(1), spaceABCDE)
|
||||
val request = Cat(f.valid, requestABCDE)
|
||||
val valid = Cat(f.valid, validABCDE)
|
||||
|
||||
// Select a channel to transmit from those with data and space
|
||||
val lasts = Cat(ioF.map(_.bits.last).reverse)
|
||||
val readys = TLArbiter.roundRobin(6, request, first)
|
||||
val winner = readys & request
|
||||
val state = RegInit(UInt(0, width=6))
|
||||
val grant = Mux(first, winner, state)
|
||||
val allowed = Mux(first, readys & space, state)
|
||||
(ioF zip allowed.toBools) foreach { case (beat, sel) => beat.ready := sel }
|
||||
|
||||
state := grant
|
||||
first := (grant & lasts).orR
|
||||
|
||||
// Form the output beat
|
||||
io.c2b_send := RegNext(RegNext(first || (state & valid) =/= UInt(0), Bool(false)), Bool(false))
|
||||
io.c2b_data := RegNext(Mux1H(RegNext(grant), RegNext(Vec(ioF.map(_.bits.data)))))
|
||||
|
||||
// Update the credit trackers
|
||||
rx := Mux(f.fire(), rxLeft, rx) + Mux(rxInc.fire(), rxInc.bits, CreditBump(info.params, 0))
|
||||
tx := txDec + Mux(txInc.fire(), txInc.bits, CreditBump(info.params, 0))
|
||||
}
|
75
src/main/scala/devices/msi/MSIMaster.scala
Normal file
75
src/main/scala/devices/msi/MSIMaster.scala
Normal file
@ -0,0 +1,75 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
package sifive.blocks.devices.msi
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.interrupts._
|
||||
import freechips.rocketchip.tilelink._
|
||||
import freechips.rocketchip.util.leftOR
|
||||
|
||||
case class MSITarget(address: BigInt, spacing: Int, number: Int)
|
||||
{
|
||||
require (number >= 0)
|
||||
require (address >= 0)
|
||||
}
|
||||
|
||||
class MSIMaster(targets: Seq[MSITarget])(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val masterNode = TLClientNode(Seq(TLClientPortParameters(Seq(TLClientParameters("MSI Master", sourceId = IdRange(0,2))))))
|
||||
|
||||
// A terminal interrupt node of flexible number
|
||||
val intNode = IntNexusNode(
|
||||
sourceFn = { _ => IntSourcePortParameters(Seq(IntSourceParameters(1, Nil))) },
|
||||
sinkFn = { _ => IntSinkPortParameters(Seq(IntSinkParameters())) },
|
||||
inputRequiresOutput = false)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val (io, masterEdge) = masterNode.out(0)
|
||||
val interrupts = intNode.in.flatMap { case (i, e) => i.take(e.source.num) }
|
||||
|
||||
// Construct a map of the addresses to update for interrupts
|
||||
val targetMap = targets.flatMap { case MSITarget(address, spacing, number) =>
|
||||
address until (address+spacing*number) by spacing
|
||||
} .map { addr =>
|
||||
val m = masterEdge.manager.find(addr)
|
||||
require (m.isDefined, s"MSIMaster ${name} was pointed at address 0x${addr}%x which does not exist")
|
||||
require (m.get.supportsPutFull.contains(1), s"MSIMaster ${name} requires device ${m.get.name} supportPutFull of 1 byte (${m.get.supportsPutFull})")
|
||||
UInt(addr)
|
||||
}.take(interrupts.size max 1)
|
||||
|
||||
require (interrupts.size <= targetMap.size, s"MSIMaster ${name} has more interrupts (${interrupts.size}) than addresses to use (${targetMap.size})")
|
||||
require (intNode.out.isEmpty, s"MSIMaster ${name} intNode is not a source!")
|
||||
|
||||
val busy = RegInit(Bool(false))
|
||||
val remote = RegInit(UInt(0, width=interrupts.size max 1))
|
||||
val local = if (interrupts.isEmpty) UInt(0) else Cat(interrupts.reverse)
|
||||
val pending = remote ^ local
|
||||
val select = ~(leftOR(pending) << 1) & pending
|
||||
|
||||
io.a.valid := pending.orR && !busy
|
||||
io.a.bits := masterEdge.Put(
|
||||
fromSource = UInt(0),
|
||||
toAddress = Mux1H(select, targetMap),
|
||||
lgSize = UInt(0),
|
||||
data = (select & local).orR)._2
|
||||
|
||||
// When A is sent, toggle our model of the remote state
|
||||
when (io.a.fire()) {
|
||||
remote := remote ^ select
|
||||
busy := Bool(true)
|
||||
}
|
||||
|
||||
// Sink D messages to clear busy
|
||||
io.d.ready := Bool(true)
|
||||
when (io.d.fire()) {
|
||||
busy := Bool(false)
|
||||
}
|
||||
|
||||
// Tie off unused channels
|
||||
io.b.ready := Bool(false)
|
||||
io.c.valid := Bool(false)
|
||||
io.e.valid := Bool(false)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user