1
0

Compare commits

...

8 Commits

Author SHA1 Message Date
88f1cbe420 Use NonBlockingEnqueue for terminal writes
This provides feedback to the programm if the last value was already
printed to the terminal. If not, new values are ignored (unchanged).
2018-05-14 20:05:14 +02:00
48d8524c4a Move terminal into own clock domain using AsyncQueue 2018-04-30 22:50:39 +02:00
d7b9834d96 Add TLTerminal (write-only terminal TL slave) 2018-04-30 00:45:27 +02:00
7ac56c01af Merge pull request #53 from sifive/chiplink
devices: add support for the chiplink protocol
2018-03-22 16:18:30 -07:00
ac55313e8e msi: add a MSIMaster to bridge interrupts over ChipLink 2018-03-22 16:06:12 -07:00
3db375ef43 devices: add support for the chiplink protocol 2018-03-22 16:06:10 -07:00
48a9acc8a4 Merge pull request #52 from sifive/spi_sync
SPI: Use the standard synchronizer primitive for the SPI DQ inputs
2018-03-07 14:45:44 -08:00
fb4977b518 SPI: Use the standard synchronizer primitive for the SPI DQ inputs 2018-03-07 09:54:56 -08:00
20 changed files with 1607 additions and 2 deletions

View 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
}
}

View 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
}

View 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
}
}

View 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))
}

View 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
}
}
}

View 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 }
}

View 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))
}

View 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))
}

View 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)
}

View 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
}

View 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)
}

View 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
}
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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))
}

View 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)
}
}

View File

@ -3,6 +3,7 @@ package sifive.blocks.devices.spi
import Chisel._
import chisel3.experimental.{withClockAndReset}
import freechips.rocketchip.util.{SynchronizerShiftReg}
import sifive.blocks.devices.pinctrl.{PinCtrl, Pin}
class SPISignals[T <: Data](private val pingen: () => T, c: SPIParamsBase) extends SPIBundle(c) {
@ -22,11 +23,11 @@ object SPIPinsFromPort {
withClockAndReset(clock, reset) {
pins.sck.outputPin(spi.sck, ds = driveStrength)
(pins.dq zip spi.dq).foreach {case (p, s) =>
(pins.dq zip spi.dq).zipWithIndex.foreach {case ((p, s), i) =>
p.outputPin(s.o, pue = Bool(true), ds = driveStrength)
p.o.oe := s.oe
p.o.ie := ~s.oe
s.i := ShiftRegister(p.i.ival, syncStages)
s.i := SynchronizerShiftReg(p.i.ival, syncStages, name = Some(s"spi_dq_${i}_sync"))
}
(pins.cs zip spi.cs) foreach { case (c, s) =>

View File

@ -0,0 +1,115 @@
// See LICENSE for license details.
package sifive.blocks.devices.terminal
import Chisel._
import chisel3.core.{Input, Output}
import chisel3.experimental.{Analog, MultiIOModule}
import freechips.rocketchip.config.{Field, Parameters}
import freechips.rocketchip.diplomacy.{LazyModule, LazyModuleImp}
import freechips.rocketchip.regmapper._
import freechips.rocketchip.subsystem.{BaseSubsystem}
import freechips.rocketchip.tilelink._
import freechips.rocketchip.util.AsyncQueue
import sifive.blocks.util.NonBlockingEnqueue
case class TerminalParams (
address: BigInt
)
class TerminalSysIO extends Bundle {
val clk = Input(Clock())
val reset = Input(Bool())
}
class TerminalDVIIO extends Bundle {
val d = Output(Bits(12.W))
val clk_p = Output(Bits(1.W))
val clk_n = Output(Bits(1.W))
val hsync = Output(Bool())
val vsync = Output(Bool())
val de = Output(Bool())
val reset = Output(Bool())
val i2c_scl = Analog(1.W)
val i2c_sda = Analog(1.W)
}
class Terminal extends BlackBox {
val io = IO(new Bundle {
val sys = new TerminalSysIO
val dvi = new TerminalDVIIO
val write_enable = Input(Bool())
val write_data = Input(UInt(8.W))
})
override def desiredName: String = "terminal"
}
trait TerminalRegBundle extends Bundle {
val port_sys = new TerminalSysIO
val port_dvi = new TerminalDVIIO
}
trait TerminalRegModule extends MultiIOModule with HasRegMap {
val params: TerminalParams
val io: TerminalRegBundle
val term = Module(new Terminal)
io.port_sys <> term.io.sys
io.port_dvi <> term.io.dvi
val crossing = Module(new AsyncQueue(UInt(8.W), depth=1, safe=false))
crossing.io.enq_clock := clock
crossing.io.enq_reset := Bool(false)
crossing.io.deq_clock := io.port_sys.clk
crossing.io.deq_reset := Bool(false)
// wire up dequeue to terminal io
term.io.write_enable := crossing.io.deq.valid
term.io.write_data := crossing.io.deq.bits
crossing.io.deq.ready := Bool(true) // terminal can read at every cycle
regmap(
0 -> NonBlockingEnqueue(crossing.io.enq)
)
}
class TLTerminal(w: Int, params: TerminalParams)(implicit p: Parameters)
extends TLRegisterRouter (
params.address,
"terminal",
Seq("klemens,terminal0"),
beatBytes = w
)(
new TLRegBundle(params, _) with TerminalRegBundle
)(
new TLRegModule(params, _, _) with TerminalRegModule
)
//-- TerminalPeriphery
case object PeripheryTerminalKey extends Field[TerminalParams]
trait HasPeripheryTerminal { this: BaseSubsystem =>
val params = p(PeripheryTerminalKey)
val terminal_name = Some("terminal_0")
val terminal = LazyModule(new TLTerminal(pbus.beatBytes, params))
.suggestName(terminal_name)
pbus.toVariableWidthSlave(terminal_name) { terminal.node }
}
trait HasPeripheryTerminalBundle {
val terminal: TerminalSysIO
val dvi: TerminalDVIIO
}
trait HasPeripheryTerminalModuleImp extends LazyModuleImp with HasPeripheryTerminalBundle {
val outer: HasPeripheryTerminal
val terminal = IO(new TerminalSysIO)
val dvi = IO(new TerminalDVIIO)
// right sides defined in TerminalRegBundle
terminal <> outer.terminal.module.io.port_sys
dvi <> outer.terminal.module.io.port_dvi
}