Refactor package hierarchy and remove legacy bus protocol implementations (#845)
* Refactors package hierarchy. Additionally: - Removes legacy ground tests and configs - Removes legacy bus protocol implementations - Removes NTiles - Adds devices package - Adds more functions to util package
This commit is contained in:
122
src/main/scala/tilelink/Arbiter.scala
Normal file
122
src/main/scala/tilelink/Arbiter.scala
Normal file
@ -0,0 +1,122 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
object TLArbiter
|
||||
{
|
||||
// (valids, select) => readys
|
||||
type Policy = (Integer, UInt, Bool) => UInt
|
||||
|
||||
val lowestIndexFirst: Policy = (width, valids, select) => ~(leftOR(valids) << 1)(width-1, 0)
|
||||
|
||||
val roundRobin: Policy = (width, valids, select) => if (width == 1) UInt(1, width=1) else {
|
||||
val valid = valids(width-1, 0)
|
||||
assert (valid === valids)
|
||||
val mask = RegInit(~UInt(0, width=width))
|
||||
val filter = Cat(valid & ~mask, valid)
|
||||
val unready = (rightOR(filter, width*2, width) >> 1) | (mask << width)
|
||||
val readys = ~((unready >> width) & unready(width-1, 0))
|
||||
when (select && valid.orR) {
|
||||
mask := leftOR(readys & valid, width)
|
||||
}
|
||||
readys(width-1, 0)
|
||||
}
|
||||
|
||||
def lowestFromSeq[T <: TLChannel](edge: TLEdge, sink: DecoupledIO[T], sources: Seq[DecoupledIO[T]]) {
|
||||
apply(lowestIndexFirst)(sink, sources.map(s => (edge.numBeats1(s.bits), s)):_*)
|
||||
}
|
||||
|
||||
def lowest[T <: TLChannel](edge: TLEdge, sink: DecoupledIO[T], sources: DecoupledIO[T]*) {
|
||||
apply(lowestIndexFirst)(sink, sources.toList.map(s => (edge.numBeats1(s.bits), s)):_*)
|
||||
}
|
||||
|
||||
def robin[T <: TLChannel](edge: TLEdge, sink: DecoupledIO[T], sources: DecoupledIO[T]*) {
|
||||
apply(roundRobin)(sink, sources.toList.map(s => (edge.numBeats1(s.bits), s)):_*)
|
||||
}
|
||||
|
||||
def apply[T <: Data](policy: Policy)(sink: DecoupledIO[T], sources: (UInt, DecoupledIO[T])*) {
|
||||
if (sources.isEmpty) {
|
||||
sink.valid := Bool(false)
|
||||
} else {
|
||||
val pairs = sources.toList
|
||||
val beatsIn = pairs.map(_._1)
|
||||
val sourcesIn = pairs.map(_._2)
|
||||
|
||||
// The number of beats which remain to be sent
|
||||
val beatsLeft = RegInit(UInt(0))
|
||||
val idle = beatsLeft === UInt(0)
|
||||
val latch = idle && sink.ready // winner (if any) claims sink
|
||||
|
||||
// Who wants access to the sink?
|
||||
val valids = sourcesIn.map(_.valid)
|
||||
// Arbitrate amongst the requests
|
||||
val readys = Vec(policy(valids.size, Cat(valids.reverse), latch).toBools)
|
||||
// Which request wins arbitration?
|
||||
val winner = Vec((readys zip valids) map { case (r,v) => r&&v })
|
||||
|
||||
// Confirm the policy works properly
|
||||
require (readys.size == valids.size)
|
||||
// Never two winners
|
||||
val prefixOR = winner.scanLeft(Bool(false))(_||_).init
|
||||
assert((prefixOR zip winner) map { case (p,w) => !p || !w } reduce {_ && _})
|
||||
// If there was any request, there is a winner
|
||||
assert (!valids.reduce(_||_) || winner.reduce(_||_))
|
||||
|
||||
// Track remaining beats
|
||||
val maskedBeats = (winner zip beatsIn) map { case (w,b) => Mux(w, b, UInt(0)) }
|
||||
val initBeats = maskedBeats.reduce(_ | _) // no winner => 0 beats
|
||||
beatsLeft := Mux(latch, initBeats, beatsLeft - sink.fire())
|
||||
|
||||
// The one-hot source granted access in the previous cycle
|
||||
val state = RegInit(Vec.fill(sources.size)(Bool(false)))
|
||||
val muxState = Mux(idle, winner, state)
|
||||
state := muxState
|
||||
|
||||
if (sources.size > 1) {
|
||||
val allowed = Mux(idle, readys, state)
|
||||
(sourcesIn zip allowed) foreach { case (s, r) =>
|
||||
s.ready := sink.ready && r
|
||||
}
|
||||
} else {
|
||||
sourcesIn(0).ready := sink.ready
|
||||
}
|
||||
|
||||
sink.valid := Mux(idle, valids.reduce(_||_), Mux1H(state, valids))
|
||||
sink.bits := Mux1H(muxState, sourcesIn.map(_.bits))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
class TestRobin(txns: Int = 128, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
val sources = Wire(Vec(6, DecoupledIO(UInt(width=3))))
|
||||
val sink = Wire(DecoupledIO(UInt(width=3)))
|
||||
val count = RegInit(UInt(0, width=8))
|
||||
|
||||
val lfsr = LFSR16(Bool(true))
|
||||
val valid = lfsr(0)
|
||||
val ready = lfsr(15)
|
||||
|
||||
sources.zipWithIndex.map { case (z, i) => z.bits := UInt(i) }
|
||||
sources(0).valid := valid
|
||||
sources(1).valid := Bool(false)
|
||||
sources(2).valid := valid
|
||||
sources(3).valid := valid
|
||||
sources(4).valid := Bool(false)
|
||||
sources(5).valid := valid
|
||||
sink.ready := ready
|
||||
|
||||
TLArbiter(TLArbiter.roundRobin)(sink, sources.zipWithIndex.map { case (z, i) => (UInt(i), z) }:_*)
|
||||
when (sink.fire()) { printf("TestRobin: %d\n", sink.bits) }
|
||||
when (!sink.fire()) { printf("TestRobin: idle (%d %d)\n", valid, ready) }
|
||||
|
||||
count := count + UInt(1)
|
||||
io.finished := count >= UInt(txns)
|
||||
}
|
173
src/main/scala/tilelink/AsyncCrossing.scala
Normal file
173
src/main/scala/tilelink/AsyncCrossing.scala
Normal file
@ -0,0 +1,173 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class TLAsyncCrossingSource(sync: Int = 3)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLAsyncSourceNode(sync)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val sink_reset_n = out.a.sink_reset_n
|
||||
val bce = edgeIn.manager.anySupportAcquireB && edgeIn.client.anySupportProbe
|
||||
val depth = edgeOut.manager.depth
|
||||
|
||||
out.a <> ToAsyncBundle(in.a, depth, sync)
|
||||
in.d <> FromAsyncBundle(out.d, sync)
|
||||
|
||||
if (bce) {
|
||||
in.b <> FromAsyncBundle(out.b, sync)
|
||||
out.c <> ToAsyncBundle(in.c, depth, sync)
|
||||
out.e <> ToAsyncBundle(in.e, depth, sync)
|
||||
} else {
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
out.b.ridx := UInt(0)
|
||||
out.c.widx := UInt(0)
|
||||
out.e.widx := UInt(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TLAsyncCrossingSink(depth: Int = 8, sync: Int = 3)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLAsyncSinkNode(depth, sync)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val source_reset_n = in.a.source_reset_n
|
||||
val bce = edgeOut.manager.anySupportAcquireB && edgeOut.client.anySupportProbe
|
||||
|
||||
out.a <> FromAsyncBundle(in.a, sync)
|
||||
in.d <> ToAsyncBundle(out.d, depth, sync)
|
||||
|
||||
if (bce) {
|
||||
in.b <> ToAsyncBundle(out.b, depth, sync)
|
||||
out.c <> FromAsyncBundle(in.c, sync)
|
||||
out.e <> FromAsyncBundle(in.e, sync)
|
||||
} else {
|
||||
in.b.widx := UInt(0)
|
||||
in.c.ridx := UInt(0)
|
||||
in.e.ridx := UInt(0)
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLAsyncCrossingSource
|
||||
{
|
||||
// applied to the TL source node; y.node := TLAsyncCrossingSource()(x.node)
|
||||
def apply(sync: Int = 3)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLAsyncOutwardNode = {
|
||||
val source = LazyModule(new TLAsyncCrossingSource(sync))
|
||||
source.node := x
|
||||
source.node
|
||||
}
|
||||
}
|
||||
|
||||
object TLAsyncCrossingSink
|
||||
{
|
||||
// applied to the TL source node; y.node := TLAsyncCrossingSink()(x.node)
|
||||
def apply(depth: Int = 8, sync: Int = 3)(x: TLAsyncOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val sink = LazyModule(new TLAsyncCrossingSink(depth, sync))
|
||||
sink.node := x
|
||||
sink.node
|
||||
}
|
||||
}
|
||||
|
||||
class TLAsyncCrossing(depth: Int = 8, sync: Int = 3)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val nodeIn = TLInputNode()
|
||||
val nodeOut = TLOutputNode()
|
||||
val node = NodeHandle(nodeIn, nodeOut)
|
||||
|
||||
val source = LazyModule(new TLAsyncCrossingSource(sync))
|
||||
val sink = LazyModule(new TLAsyncCrossingSink(depth, sync))
|
||||
|
||||
val _ = (sink.node := source.node) // no monitor
|
||||
val in = (source.node := nodeIn)
|
||||
val out = (nodeOut := sink.node)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = nodeIn.bundleIn
|
||||
val in_clock = Clock(INPUT)
|
||||
val in_reset = Bool(INPUT)
|
||||
val out = nodeOut.bundleOut
|
||||
val out_clock = Clock(INPUT)
|
||||
val out_reset = Bool(INPUT)
|
||||
}
|
||||
|
||||
source.module.clock := io.in_clock
|
||||
source.module.reset := io.in_reset
|
||||
in.foreach { lm =>
|
||||
lm.module.clock := io.in_clock
|
||||
lm.module.reset := io.in_reset
|
||||
}
|
||||
|
||||
sink.module.clock := io.out_clock
|
||||
sink.module.reset := io.out_reset
|
||||
out.foreach { lm =>
|
||||
lm.module.clock := io.out_clock
|
||||
lm.module.reset := io.out_reset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
class TLRAMAsyncCrossing(txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val model = LazyModule(new TLRAMModel("AsyncCrossing"))
|
||||
val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff)))
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val cross = LazyModule(new TLAsyncCrossing)
|
||||
|
||||
model.node := fuzz.node
|
||||
cross.node := TLFragmenter(4, 256)(TLDelayer(0.1)(model.node))
|
||||
val monitor = (ram.node := cross.node)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzz.module.io.finished
|
||||
|
||||
// Shove the RAM into another clock domain
|
||||
val clocks = Module(new Pow2ClockDivider(2))
|
||||
ram.module.clock := clocks.io.clock_out
|
||||
|
||||
// ... and safely cross TL2 into it
|
||||
cross.module.io.in_clock := clock
|
||||
cross.module.io.in_reset := reset
|
||||
cross.module.io.out_clock := clocks.io.clock_out
|
||||
cross.module.io.out_reset := reset
|
||||
|
||||
// Push the Monitor into the right clock domain
|
||||
monitor.foreach { m =>
|
||||
m.module.clock := clocks.io.clock_out
|
||||
m.module.reset := reset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TLRAMAsyncCrossingTest(txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new TLRAMAsyncCrossing(txns)).module).io.finished
|
||||
}
|
306
src/main/scala/tilelink/AtomicAutomata.scala
Normal file
306
src/main/scala/tilelink/AtomicAutomata.scala
Normal file
@ -0,0 +1,306 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
import scala.math.{min,max}
|
||||
|
||||
// Ensures that all downstream RW managers support Atomic operationss.
|
||||
// If !passthrough, intercept all Atomics. Otherwise, only intercept those unsupported downstream.
|
||||
class TLAtomicAutomata(logical: Boolean = true, arithmetic: Boolean = true, concurrency: Int = 1, passthrough: Boolean = true)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
require (concurrency >= 1)
|
||||
|
||||
val node = TLAdapterNode(
|
||||
clientFn = { case cp => require (!cp.unsafeAtomics); cp.copy(unsafeAtomics = true) },
|
||||
managerFn = { case mp => mp.copy(managers = mp.managers.map { m =>
|
||||
val ourSupport = TransferSizes(1, mp.beatBytes)
|
||||
def widen(x: TransferSizes) = if (passthrough && x.min <= 2*mp.beatBytes) TransferSizes(1, max(mp.beatBytes, x.max)) else ourSupport
|
||||
val canDoit = m.supportsPutFull.contains(ourSupport) && m.supportsGet.contains(ourSupport)
|
||||
// Blow up if there are devices to which we cannot add Atomics, because their R|W are too inflexible
|
||||
require (!m.supportsPutFull || !m.supportsGet || canDoit)
|
||||
m.copy(
|
||||
supportsArithmetic = if (!arithmetic || !canDoit) m.supportsArithmetic else widen(m.supportsArithmetic),
|
||||
supportsLogical = if (!logical || !canDoit) m.supportsLogical else widen(m.supportsLogical))
|
||||
})})
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val managers = edgeOut.manager.managers
|
||||
val beatBytes = edgeOut.manager.beatBytes
|
||||
|
||||
// To which managers are we adding atomic support?
|
||||
val ourSupport = TransferSizes(1, edgeOut.manager.beatBytes)
|
||||
val managersNeedingHelp = managers.filter { m =>
|
||||
m.supportsPutFull.contains(ourSupport) &&
|
||||
m.supportsGet.contains(ourSupport) &&
|
||||
((logical && !m.supportsLogical .contains(ourSupport)) ||
|
||||
(arithmetic && !m.supportsArithmetic.contains(ourSupport)) ||
|
||||
!passthrough) // we will do atomics for everyone we can
|
||||
}
|
||||
// We cannot add atomcis to a non-FIFO manager
|
||||
managersNeedingHelp foreach { m => require (m.fifoId.isDefined) }
|
||||
// We need to preserve FIFO semantics across FIFO domains, not managers
|
||||
// Suppose you have Put(42) Atomic(+1) both inflight; valid results: 42 or 43
|
||||
// If we allow Put(42) Get() Put(+1) concurrent; valid results: 42 43 OR undef
|
||||
// Making non-FIFO work requires waiting for all Acks to come back (=> use FIFOFixer)
|
||||
val domainsNeedingHelp = managersNeedingHelp.map(_.fifoId.get).distinct
|
||||
// Don't overprovision the CAM
|
||||
val camSize = min(domainsNeedingHelp.size, concurrency)
|
||||
// Compact the fifoIds to only those we care about
|
||||
def camFifoId(m: TLManagerParameters) = m.fifoId.map(id => max(0, domainsNeedingHelp.indexOf(id))).getOrElse(0)
|
||||
|
||||
// CAM entry state machine
|
||||
val FREE = UInt(0) // unused waiting on Atomic from A
|
||||
val GET = UInt(3) // Get sent down A waiting on AccessDataAck from D
|
||||
val AMO = UInt(2) // AccessDataAck sent up D waiting for A availability
|
||||
val ACK = UInt(1) // Put sent down A waiting for PutAck from D
|
||||
|
||||
val params = TLAtomicAutomata.CAMParams(out.a.bits.params, domainsNeedingHelp.size)
|
||||
// Do we need to do anything at all?
|
||||
if (camSize > 0) {
|
||||
val initval = Wire(new TLAtomicAutomata.CAM_S(params))
|
||||
initval.state := FREE
|
||||
val cam_s = RegInit(Vec.fill(camSize)(initval))
|
||||
val cam_a = Reg(Vec(camSize, new TLAtomicAutomata.CAM_A(params)))
|
||||
val cam_d = Reg(Vec(camSize, new TLAtomicAutomata.CAM_D(params)))
|
||||
|
||||
val cam_free = cam_s.map(_.state === FREE)
|
||||
val cam_amo = cam_s.map(_.state === AMO)
|
||||
val cam_abusy = cam_s.map(e => e.state === GET || e.state === AMO) // A is blocked
|
||||
val cam_dmatch = cam_s.map(e => e.state =/= FREE) // D should inspect these entries
|
||||
|
||||
// Can the manager already handle this message?
|
||||
val a_address = edgeIn.address(in.a.bits)
|
||||
val a_size = edgeIn.size(in.a.bits)
|
||||
val a_canLogical = Bool(passthrough) && edgeOut.manager.supportsLogicalFast (a_address, a_size)
|
||||
val a_canArithmetic = Bool(passthrough) && edgeOut.manager.supportsArithmeticFast(a_address, a_size)
|
||||
val a_isLogical = in.a.bits.opcode === TLMessages.LogicalData
|
||||
val a_isArithmetic = in.a.bits.opcode === TLMessages.ArithmeticData
|
||||
val a_isSupported = Mux(a_isLogical, a_canLogical, Mux(a_isArithmetic, a_canArithmetic, Bool(true)))
|
||||
|
||||
// Must we do a Put?
|
||||
val a_cam_any_put = cam_amo.reduce(_ || _)
|
||||
val a_cam_por_put = cam_amo.scanLeft(Bool(false))(_||_).init
|
||||
val a_cam_sel_put = (cam_amo zip a_cam_por_put) map { case (a, b) => a && !b }
|
||||
val a_cam_a = PriorityMux(cam_amo, cam_a)
|
||||
val a_cam_d = PriorityMux(cam_amo, cam_d)
|
||||
val a_a = a_cam_a.bits.data
|
||||
val a_d = a_cam_d.data
|
||||
|
||||
// Does the A request conflict with an inflight AMO?
|
||||
val a_fifoId = edgeOut.manager.fastProperty(a_address, camFifoId _, (i:Int) => UInt(i))
|
||||
val a_cam_busy = (cam_abusy zip cam_a.map(_.fifoId === a_fifoId)) map { case (a,b) => a&&b } reduce (_||_)
|
||||
|
||||
// (Where) are we are allocating in the CAM?
|
||||
val a_cam_any_free = cam_free.reduce(_ || _)
|
||||
val a_cam_por_free = cam_free.scanLeft(Bool(false))(_||_).init
|
||||
val a_cam_sel_free = (cam_free zip a_cam_por_free) map { case (a,b) => a && !b }
|
||||
|
||||
// Logical AMO
|
||||
val indexes = Seq.tabulate(beatBytes*8) { i => Cat(a_a(i,i), a_d(i,i)) }
|
||||
val logic_out = Cat(indexes.map(x => a_cam_a.lut(x).asUInt).reverse)
|
||||
|
||||
// Arithmetic AMO
|
||||
val unsigned = a_cam_a.bits.param(1)
|
||||
val take_max = a_cam_a.bits.param(0)
|
||||
val adder = a_cam_a.bits.param(2)
|
||||
val mask = a_cam_a.bits.mask
|
||||
val signSel = ~(~mask | (mask >> 1))
|
||||
val signbits_a = Cat(Seq.tabulate(beatBytes) { i => a_a(8*i+7,8*i+7) } .reverse)
|
||||
val signbits_d = Cat(Seq.tabulate(beatBytes) { i => a_d(8*i+7,8*i+7) } .reverse)
|
||||
// Move the selected sign bit into the first byte position it will extend
|
||||
val signbit_a = ((signbits_a & signSel) << 1)(beatBytes-1, 0)
|
||||
val signbit_d = ((signbits_d & signSel) << 1)(beatBytes-1, 0)
|
||||
val signext_a = FillInterleaved(8, leftOR(signbit_a))
|
||||
val signext_d = FillInterleaved(8, leftOR(signbit_d))
|
||||
// NOTE: sign-extension does not change the relative ordering in EITHER unsigned or signed arithmetic
|
||||
val wide_mask = FillInterleaved(8, mask)
|
||||
val a_a_ext = (a_a & wide_mask) | signext_a
|
||||
val a_d_ext = (a_d & wide_mask) | signext_d
|
||||
val a_d_inv = Mux(adder, a_d_ext, ~a_d_ext)
|
||||
val adder_out = a_a_ext + a_d_inv
|
||||
val h = 8*beatBytes-1 // now sign-extended; use biggest bit
|
||||
val a_bigger_uneq = unsigned === a_a_ext(h) // result if high bits are unequal
|
||||
val a_bigger = Mux(a_a_ext(h) === a_d_ext(h), !adder_out(h), a_bigger_uneq)
|
||||
val pick_a = take_max === a_bigger
|
||||
val arith_out = Mux(adder, adder_out, Mux(pick_a, a_a, a_d))
|
||||
|
||||
// AMO result data
|
||||
val amo_data =
|
||||
if (!logical) arith_out else
|
||||
if (!arithmetic) logic_out else
|
||||
Mux(a_cam_a.bits.opcode(0), logic_out, arith_out)
|
||||
|
||||
// Potentially mutate the message from inner
|
||||
val source_i = Wire(in.a)
|
||||
val a_allow = !a_cam_busy && (a_isSupported || a_cam_any_free)
|
||||
in.a.ready := source_i.ready && a_allow
|
||||
source_i.valid := in.a.valid && a_allow
|
||||
source_i.bits := in.a.bits
|
||||
when (!a_isSupported) { // minimal mux difference
|
||||
source_i.bits.opcode := TLMessages.Get
|
||||
source_i.bits.param := UInt(0)
|
||||
}
|
||||
|
||||
// Potentially take the message from the CAM
|
||||
val source_c = Wire(in.a)
|
||||
source_c.valid := a_cam_any_put
|
||||
source_c.bits := edgeOut.Put(a_cam_a.bits.source, edgeIn.address(a_cam_a.bits), a_cam_a.bits.size, amo_data)._2
|
||||
|
||||
// Finishing an AMO from the CAM has highest priority
|
||||
TLArbiter(TLArbiter.lowestIndexFirst)(out.a, (UInt(0), source_c), (edgeOut.numBeats1(in.a.bits), source_i))
|
||||
|
||||
// Capture the A state into the CAM
|
||||
when (source_i.fire() && !a_isSupported) {
|
||||
(a_cam_sel_free zip cam_a) foreach { case (en, r) =>
|
||||
when (en) {
|
||||
r.fifoId := a_fifoId
|
||||
r.bits := in.a.bits
|
||||
r.lut := MuxLookup(in.a.bits.param(1, 0), UInt(0, width = 4), Array(
|
||||
TLAtomics.AND -> UInt(0x8),
|
||||
TLAtomics.OR -> UInt(0xe),
|
||||
TLAtomics.XOR -> UInt(0x6),
|
||||
TLAtomics.SWAP -> UInt(0xc)))
|
||||
}
|
||||
}
|
||||
(a_cam_sel_free zip cam_s) foreach { case (en, r) =>
|
||||
when (en) {
|
||||
r.state := GET
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Advance the put state
|
||||
when (source_c.fire()) {
|
||||
(a_cam_sel_put zip cam_s) foreach { case (en, r) =>
|
||||
when (en) {
|
||||
r.state := ACK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to deal with a potential D response in the same cycle as the A request
|
||||
val d_cam_sel_raw = cam_a.map(_.bits.source === in.d.bits.source)
|
||||
val d_cam_sel_match = (d_cam_sel_raw zip cam_dmatch) map { case (a,b) => a&&b }
|
||||
val d_cam_data = Mux1H(d_cam_sel_match, cam_d.map(_.data))
|
||||
val d_cam_sel_bypass = if (edgeOut.manager.minLatency > 0) Bool(false) else
|
||||
out.d.bits.source === in.a.bits.source && in.a.valid && !a_isSupported
|
||||
val d_cam_sel = (a_cam_sel_free zip d_cam_sel_match) map { case (a,d) => Mux(d_cam_sel_bypass, a, d) }
|
||||
val d_cam_sel_any = d_cam_sel_bypass || d_cam_sel_match.reduce(_ || _)
|
||||
val d_ackd = out.d.bits.opcode === TLMessages.AccessAckData
|
||||
val d_ack = out.d.bits.opcode === TLMessages.AccessAck
|
||||
|
||||
when (out.d.fire()) {
|
||||
(d_cam_sel zip cam_d) foreach { case (en, r) =>
|
||||
when (en && d_ackd) {
|
||||
r.data := out.d.bits.data
|
||||
}
|
||||
}
|
||||
(d_cam_sel zip cam_s) foreach { case (en, r) =>
|
||||
when (en) {
|
||||
// Note: it is important that this comes AFTER the := GET, so we can go FREE=>GET=>AMO in one cycle
|
||||
r.state := Mux(d_ackd, AMO, FREE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val d_drop = d_ackd && d_cam_sel_any
|
||||
val d_replace = d_ack && d_cam_sel_match.reduce(_ || _)
|
||||
|
||||
in.d.valid := out.d.valid && !d_drop
|
||||
out.d.ready := in.d.ready || d_drop
|
||||
|
||||
in.d.bits := out.d.bits
|
||||
when (d_replace) { // minimal muxes
|
||||
in.d.bits.opcode := TLMessages.AccessAckData
|
||||
in.d.bits.data := d_cam_data
|
||||
}
|
||||
} else {
|
||||
out.a.valid := in.a.valid
|
||||
in.a.ready := out.a.ready
|
||||
out.a.bits := in.a.bits
|
||||
|
||||
in.d.valid := out.d.valid
|
||||
out.d.ready := in.d.ready
|
||||
in.d.bits := out.d.bits
|
||||
}
|
||||
|
||||
if (edgeOut.manager.anySupportAcquireB && edgeIn.client.anySupportProbe) {
|
||||
in.b.valid := out.b.valid
|
||||
out.b.ready := in.b.ready
|
||||
in.b.bits := out.b.bits
|
||||
|
||||
out.c.valid := in.c.valid
|
||||
in.c.ready := out.c.ready
|
||||
out.c.bits := in.c.bits
|
||||
|
||||
out.e.valid := in.e.valid
|
||||
in.e.ready := out.e.ready
|
||||
out.e.bits := in.e.bits
|
||||
} else {
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLAtomicAutomata
|
||||
{
|
||||
// applied to the TL source node; y.node := TLAtomicAutomata(x.node)
|
||||
def apply(logical: Boolean = true, arithmetic: Boolean = true, concurrency: Int = 1, passthrough: Boolean = true)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val atomics = LazyModule(new TLAtomicAutomata(logical, arithmetic, concurrency, passthrough))
|
||||
atomics.node := x
|
||||
atomics.node
|
||||
}
|
||||
|
||||
case class CAMParams(a: TLBundleParameters, domainsNeedingHelp: Int)
|
||||
|
||||
class CAM_S(params: CAMParams) extends GenericParameterizedBundle(params) {
|
||||
val state = UInt(width = 2)
|
||||
}
|
||||
class CAM_A(params: CAMParams) extends GenericParameterizedBundle(params) {
|
||||
val bits = new TLBundleA(params.a)
|
||||
val fifoId = UInt(width = log2Up(params.domainsNeedingHelp))
|
||||
val lut = UInt(width = 4)
|
||||
}
|
||||
class CAM_D(params: CAMParams) extends GenericParameterizedBundle(params) {
|
||||
val data = UInt(width = params.a.dataBits)
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
//TODO ensure handler will pass through operations to clients that can handle them themselves
|
||||
|
||||
class TLRAMAtomicAutomata(txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val model = LazyModule(new TLRAMModel("AtomicAutomata"))
|
||||
val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff)))
|
||||
|
||||
model.node := fuzz.node
|
||||
ram.node := TLFragmenter(4, 256)(TLDelayer(0.1)(TLAtomicAutomata()(TLDelayer(0.1)(model.node))))
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzz.module.io.finished
|
||||
}
|
||||
}
|
||||
|
||||
class TLRAMAtomicAutomataTest(txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new TLRAMAtomicAutomata(txns)).module).io.finished
|
||||
}
|
63
src/main/scala/tilelink/Atomics.scala
Normal file
63
src/main/scala/tilelink/Atomics.scala
Normal file
@ -0,0 +1,63 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
|
||||
import TLMessages._
|
||||
import TLPermissions._
|
||||
|
||||
class Atomics(params: TLBundleParameters) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val write = Bool().flip // ignore opcode
|
||||
val a = new TLBundleA(params).flip
|
||||
val data_in = UInt(width = params.dataBits).flip
|
||||
val data_out = UInt(width = params.dataBits)
|
||||
}
|
||||
|
||||
// Arithmetic, what to do
|
||||
val adder = io.a.param(2)
|
||||
val unsigned = io.a.param(1)
|
||||
val take_max = io.a.param(0)
|
||||
|
||||
val signBit = io.a.mask & Cat(UInt(1), ~io.a.mask >> 1)
|
||||
val inv_d = Mux(adder, io.data_in, ~io.data_in)
|
||||
val sum = (FillInterleaved(8, io.a.mask) & io.a.data) + inv_d
|
||||
def sign(x: UInt): Bool = (Cat(x.toBools.grouped(8).map(_.last).toList.reverse) & signBit).orR()
|
||||
val sign_a = sign(io.a.data)
|
||||
val sign_d = sign(io.data_in)
|
||||
val sign_s = sign(sum)
|
||||
val a_bigger_uneq = unsigned === sign_a // result if high bits are unequal
|
||||
val a_bigger = Mux(sign_a === sign_d, !sign_s, a_bigger_uneq)
|
||||
val pick_a = take_max === a_bigger
|
||||
|
||||
// Logical, what to do
|
||||
val lut = Vec(Seq(
|
||||
UInt(0x6), // XOR
|
||||
UInt(0xe), // OR
|
||||
UInt(0x8), // AND
|
||||
UInt(0xc)))( // SWAP
|
||||
io.a.param(1,0))
|
||||
val logical = Cat((io.a.data.toBools zip io.data_in.toBools).map { case (a, d) =>
|
||||
lut(Cat(a, d))
|
||||
}.reverse)
|
||||
|
||||
// Operation, what to do? (0=d, 1=a, 2=sum, 3=logical)
|
||||
val select = Mux(io.write, UInt(1), Vec(Seq(
|
||||
UInt(1), // PutFullData
|
||||
UInt(1), // PutPartialData
|
||||
Mux(adder, UInt(2), Mux(pick_a, UInt(1), UInt(0))), // ArithmeticData
|
||||
UInt(3), // LogicalData
|
||||
UInt(0), // Get
|
||||
UInt(0), // Hint
|
||||
UInt(0), // Acquire
|
||||
UInt(0)))( // Overwrite
|
||||
io.a.opcode))
|
||||
|
||||
// Only the masked bytes can be modified
|
||||
val selects = io.a.mask.toBools.map(b => Mux(b, select, UInt(0)))
|
||||
io.data_out := Cat(selects.zipWithIndex.map { case (s, i) =>
|
||||
Vec(Seq(io.data_in, io.a.data, sum, logical).map(_((i + 1) * 8 - 1, i * 8)))(s)
|
||||
}.reverse)
|
||||
}
|
309
src/main/scala/tilelink/Broadcast.scala
Normal file
309
src/main/scala/tilelink/Broadcast.scala
Normal file
@ -0,0 +1,309 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
import scala.math.{min,max}
|
||||
|
||||
class TLBroadcast(lineBytes: Int, numTrackers: Int = 4, bufferless: Boolean = false)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
require (lineBytes > 0 && isPow2(lineBytes))
|
||||
require (numTrackers > 0)
|
||||
|
||||
val node = TLAdapterNode(
|
||||
clientFn = { cp =>
|
||||
cp.copy(clients = Seq(TLClientParameters(
|
||||
name = "TLBroadcast",
|
||||
sourceId = IdRange(0, 1 << log2Ceil(cp.endSourceId*4)))))
|
||||
},
|
||||
managerFn = { mp =>
|
||||
mp.copy(
|
||||
endSinkId = numTrackers,
|
||||
managers = mp.managers.map { m =>
|
||||
// We are the last level manager
|
||||
require (!m.supportsAcquireB)
|
||||
// We only manage addresses which are uncached
|
||||
if (m.regionType == RegionType.UNCACHED) {
|
||||
// The device had better support line transfers
|
||||
val lowerBound = max(m.supportsPutFull.min, m.supportsGet.min)
|
||||
require (!m.supportsPutFull || m.supportsPutFull.contains(lineBytes))
|
||||
require (!m.supportsGet || m.supportsGet .contains(lineBytes))
|
||||
m.copy(
|
||||
regionType = RegionType.TRACKED,
|
||||
supportsAcquireB = TransferSizes(lowerBound, lineBytes),
|
||||
supportsAcquireT = if (m.supportsPutFull) TransferSizes(lowerBound, lineBytes) else TransferSizes.none,
|
||||
// truncate supported accesses to lineBytes (we only ever probe for one line)
|
||||
supportsPutFull = TransferSizes(m.supportsPutFull .min, min(m.supportsPutFull .max, lineBytes)),
|
||||
supportsPutPartial = TransferSizes(m.supportsPutPartial.min, min(m.supportsPutPartial.max, lineBytes)),
|
||||
supportsGet = TransferSizes(m.supportsGet .min, min(m.supportsGet .max, lineBytes)),
|
||||
supportsHint = TransferSizes(m.supportsHint .min, min(m.supportsHint .max, lineBytes)),
|
||||
supportsArithmetic = TransferSizes(m.supportsArithmetic.min, min(m.supportsArithmetic.max, lineBytes)),
|
||||
supportsLogical = TransferSizes(m.supportsLogical .min, min(m.supportsLogical .max, lineBytes)),
|
||||
fifoId = None // trackers do not respond in FIFO order!
|
||||
)
|
||||
} else {
|
||||
m
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val clients = edgeIn.client.clients
|
||||
val managers = edgeOut.manager.managers
|
||||
val lineShift = log2Ceil(lineBytes)
|
||||
|
||||
import TLBroadcastConstants._
|
||||
|
||||
require (lineBytes >= edgeOut.manager.beatBytes)
|
||||
// For the probe walker, we need to identify all the caches
|
||||
val caches = clients.filter(_.supportsProbe).map(_.sourceId)
|
||||
val cache_targets = caches.map(c => UInt(c.start))
|
||||
|
||||
// Create the request tracker queues
|
||||
val trackers = Seq.tabulate(numTrackers) { id =>
|
||||
Module(new TLBroadcastTracker(id, lineBytes, log2Up(caches.size+1), bufferless, edgeIn, edgeOut)).io
|
||||
}
|
||||
|
||||
// We always accept E
|
||||
in.e.ready := Bool(true)
|
||||
(trackers zip UIntToOH(in.e.bits.sink).toBools) foreach { case (tracker, select) =>
|
||||
tracker.e_last := select && in.e.fire()
|
||||
}
|
||||
|
||||
// Depending on the high source bits, we might transform D
|
||||
val d_high = log2Ceil(edgeIn.client.endSourceId)
|
||||
val d_what = out.d.bits.source(d_high+1, d_high)
|
||||
val d_drop = d_what === DROP
|
||||
val d_hasData = edgeOut.hasData(out.d.bits)
|
||||
val d_normal = Wire(in.d)
|
||||
val d_trackerOH = Vec(trackers.map { t => !t.idle && t.source === d_normal.bits.source }).asUInt
|
||||
|
||||
assert (!out.d.valid || !d_drop || out.d.bits.opcode === TLMessages.AccessAck)
|
||||
|
||||
out.d.ready := d_normal.ready || d_drop
|
||||
d_normal.valid := out.d.valid && !d_drop
|
||||
d_normal.bits := out.d.bits // truncates source
|
||||
when (d_what(1)) { // TRANSFORM_*
|
||||
d_normal.bits.opcode := Mux(d_hasData, TLMessages.GrantData, TLMessages.ReleaseAck)
|
||||
d_normal.bits.param := Mux(d_hasData, Mux(d_what(0), TLPermissions.toT, TLPermissions.toB), UInt(0))
|
||||
}
|
||||
d_normal.bits.sink := OHToUInt(d_trackerOH)
|
||||
assert (!d_normal.valid || (d_trackerOH.orR() || d_normal.bits.opcode === TLMessages.ReleaseAck))
|
||||
|
||||
// A tracker response is anything neither dropped nor a ReleaseAck
|
||||
val d_response = d_hasData || !d_what(1)
|
||||
val d_last = edgeIn.last(d_normal)
|
||||
(trackers zip d_trackerOH.toBools) foreach { case (tracker, select) =>
|
||||
tracker.d_last := select && d_normal.fire() && d_response && d_last
|
||||
tracker.probedack := select && out.d.fire() && d_drop
|
||||
}
|
||||
|
||||
// Incoming C can be:
|
||||
// ProbeAck => decrement tracker, drop
|
||||
// ProbeAckData => decrement tracker, send out A as PutFull(DROP)
|
||||
// ReleaseData => send out A as PutFull(TRANSFORM)
|
||||
// Release => send out D as ReleaseAck
|
||||
|
||||
val c_probeack = in.c.bits.opcode === TLMessages.ProbeAck
|
||||
val c_probeackdata = in.c.bits.opcode === TLMessages.ProbeAckData
|
||||
val c_releasedata = in.c.bits.opcode === TLMessages.ReleaseData
|
||||
val c_release = in.c.bits.opcode === TLMessages.Release
|
||||
val c_trackerOH = trackers.map { t => t.line === (in.c.bits.address >> lineShift) }
|
||||
val c_trackerSrc = Mux1H(c_trackerOH, trackers.map { _.source })
|
||||
|
||||
// Decrement the tracker's outstanding probe counter
|
||||
(trackers zip c_trackerOH) foreach { case (tracker, select) =>
|
||||
tracker.probenack := in.c.fire() && c_probeack && select
|
||||
}
|
||||
|
||||
val releaseack = Wire(in.d)
|
||||
val putfull = Wire(out.a)
|
||||
|
||||
in.c.ready := c_probeack || Mux(c_release, releaseack.ready, putfull.ready)
|
||||
|
||||
releaseack.valid := in.c.valid && c_release
|
||||
releaseack.bits := edgeIn.ReleaseAck(in.c.bits.address, UInt(0), in.c.bits.source, in.c.bits.size)
|
||||
|
||||
val put_what = Mux(c_releasedata, TRANSFORM_B, DROP)
|
||||
val put_who = Mux(c_releasedata, in.c.bits.source, c_trackerSrc)
|
||||
putfull.valid := in.c.valid && (c_probeackdata || c_releasedata)
|
||||
putfull.bits := edgeOut.Put(Cat(put_what, put_who), in.c.bits.address, in.c.bits.size, in.c.bits.data)._2
|
||||
|
||||
// Combine ReleaseAck or the modified D
|
||||
TLArbiter.lowest(edgeOut, in.d, releaseack, d_normal)
|
||||
// Combine the PutFull with the trackers
|
||||
TLArbiter.lowestFromSeq(edgeOut, out.a, putfull +: trackers.map(_.out_a))
|
||||
|
||||
// The Probe FSM walks all caches and probes them
|
||||
val probe_todo = RegInit(UInt(0, width = max(1, caches.size)))
|
||||
val probe_line = Reg(UInt())
|
||||
val probe_perms = Reg(UInt(width = 2))
|
||||
val probe_next = probe_todo & ~(leftOR(probe_todo) << 1)
|
||||
val probe_busy = probe_todo.orR()
|
||||
val probe_target = if (caches.size == 0) UInt(0) else Mux1H(probe_next, cache_targets)
|
||||
|
||||
// Probe whatever the FSM wants to do next
|
||||
in.b.valid := probe_busy
|
||||
if (caches.size != 0) {
|
||||
in.b.bits := edgeIn.Probe(probe_line << lineShift, probe_target, UInt(lineShift), probe_perms)._2
|
||||
}
|
||||
when (in.b.fire()) { probe_todo := probe_todo & ~probe_next }
|
||||
|
||||
// Which cache does a request come from?
|
||||
val a_cache = if (caches.size == 0) UInt(1) else Vec(caches.map(_.contains(in.a.bits.source))).asUInt
|
||||
val a_first = edgeIn.first(in.a)
|
||||
|
||||
// To accept a request from A, the probe FSM must be idle and there must be a matching tracker
|
||||
val freeTrackers = Vec(trackers.map { t => t.idle }).asUInt
|
||||
val freeTracker = freeTrackers.orR()
|
||||
val matchTrackers = Vec(trackers.map { t => t.line === in.a.bits.address >> lineShift }).asUInt
|
||||
val matchTracker = matchTrackers.orR()
|
||||
val allocTracker = freeTrackers & ~(leftOR(freeTrackers) << 1)
|
||||
val selectTracker = Mux(matchTracker, matchTrackers, allocTracker)
|
||||
|
||||
val trackerReady = Vec(trackers.map(_.in_a.ready)).asUInt
|
||||
in.a.ready := (!a_first || !probe_busy) && (selectTracker & trackerReady).orR()
|
||||
(trackers zip selectTracker.toBools) foreach { case (t, select) =>
|
||||
t.in_a.valid := in.a.valid && select && (!a_first || !probe_busy)
|
||||
t.in_a.bits := in.a.bits
|
||||
t.in_a_first := a_first
|
||||
t.probe := (if (caches.size == 0) UInt(0) else Mux(a_cache.orR(), UInt(caches.size-1), UInt(caches.size)))
|
||||
}
|
||||
|
||||
when (in.a.fire() && a_first) {
|
||||
probe_todo := ~a_cache // probe all but the cache who poked us
|
||||
probe_line := in.a.bits.address >> lineShift
|
||||
probe_perms := MuxLookup(in.a.bits.opcode, Wire(UInt(width = 2)), Array(
|
||||
TLMessages.PutFullData -> TLPermissions.toN,
|
||||
TLMessages.PutPartialData -> TLPermissions.toN,
|
||||
TLMessages.ArithmeticData -> TLPermissions.toN,
|
||||
TLMessages.LogicalData -> TLPermissions.toN,
|
||||
TLMessages.Get -> TLPermissions.toB,
|
||||
TLMessages.Hint -> MuxLookup(in.a.bits.param, Wire(UInt(width = 2)), Array(
|
||||
TLHints.PREFETCH_READ -> TLPermissions.toB,
|
||||
TLHints.PREFETCH_WRITE -> TLPermissions.toN)),
|
||||
TLMessages.Acquire -> MuxLookup(in.a.bits.param, Wire(UInt(width = 2)), Array(
|
||||
TLPermissions.NtoB -> TLPermissions.toB,
|
||||
TLPermissions.NtoT -> TLPermissions.toN,
|
||||
TLPermissions.BtoT -> TLPermissions.toN))))
|
||||
}
|
||||
|
||||
// The outer TL connections may not be cached
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TLBroadcastTracker(id: Int, lineBytes: Int, probeCountBits: Int, bufferless: Boolean, edgeIn: TLEdgeIn, edgeOut: TLEdgeOut) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val in_a_first = Bool(INPUT)
|
||||
val in_a = Decoupled(new TLBundleA(edgeIn.bundle)).flip
|
||||
val out_a = Decoupled(new TLBundleA(edgeOut.bundle))
|
||||
val probe = UInt(INPUT, width = probeCountBits)
|
||||
val probenack = Bool(INPUT)
|
||||
val probedack = Bool(INPUT)
|
||||
val d_last = Bool(INPUT)
|
||||
val e_last = Bool(INPUT)
|
||||
val source = UInt(OUTPUT) // the source awaiting D response
|
||||
val line = UInt(OUTPUT) // the line waiting for probes
|
||||
val idle = Bool(OUTPUT)
|
||||
}
|
||||
|
||||
val lineShift = log2Ceil(lineBytes)
|
||||
import TLBroadcastConstants._
|
||||
|
||||
// Only one operation can be inflight per line, because we need to be sure
|
||||
// we send the request after all the probes we sent and before all the next probes
|
||||
val got_e = RegInit(Bool(true))
|
||||
val sent_d = RegInit(Bool(true))
|
||||
val opcode = Reg(io.in_a.bits.opcode)
|
||||
val param = Reg(io.in_a.bits.param)
|
||||
val size = Reg(io.in_a.bits.size)
|
||||
val source = Reg(io.in_a.bits.source)
|
||||
val address = RegInit(UInt(id << lineShift, width = io.in_a.bits.address.getWidth))
|
||||
val count = Reg(UInt(width = probeCountBits))
|
||||
val idle = got_e && sent_d
|
||||
|
||||
when (io.in_a.fire() && io.in_a_first) {
|
||||
assert (idle)
|
||||
sent_d := Bool(false)
|
||||
got_e := io.in_a.bits.opcode =/= TLMessages.Acquire
|
||||
opcode := io.in_a.bits.opcode
|
||||
param := io.in_a.bits.param
|
||||
size := io.in_a.bits.size
|
||||
source := io.in_a.bits.source
|
||||
address := io.in_a.bits.address
|
||||
count := io.probe
|
||||
}
|
||||
when (io.d_last) {
|
||||
assert (!sent_d)
|
||||
sent_d := Bool(true)
|
||||
}
|
||||
when (io.e_last) {
|
||||
assert (!got_e)
|
||||
got_e := Bool(true)
|
||||
}
|
||||
|
||||
when (io.probenack || io.probedack) {
|
||||
assert (count > UInt(0))
|
||||
count := count - Mux(io.probenack && io.probedack, UInt(2), UInt(1))
|
||||
}
|
||||
|
||||
io.idle := idle
|
||||
io.source := source
|
||||
io.line := address >> lineShift
|
||||
|
||||
val i_data = Wire(Decoupled(new TLBroadcastData(edgeIn.bundle)))
|
||||
val o_data = Queue(i_data, if (bufferless) 1 else (lineBytes / edgeIn.manager.beatBytes), pipe=bufferless)
|
||||
|
||||
io.in_a.ready := (idle || !io.in_a_first) && i_data.ready
|
||||
i_data.valid := (idle || !io.in_a_first) && io.in_a.valid
|
||||
i_data.bits.mask := io.in_a.bits.mask
|
||||
i_data.bits.data := io.in_a.bits.data
|
||||
|
||||
val probe_done = count === UInt(0)
|
||||
val acquire = opcode === TLMessages.Acquire
|
||||
|
||||
val transform = MuxLookup(param, Wire(UInt(width = 2)), Array(
|
||||
TLPermissions.NtoB -> TRANSFORM_B,
|
||||
TLPermissions.NtoT -> TRANSFORM_T,
|
||||
TLPermissions.BtoT -> TRANSFORM_T))
|
||||
|
||||
o_data.ready := io.out_a.ready && probe_done
|
||||
io.out_a.valid := o_data.valid && probe_done
|
||||
io.out_a.bits.opcode := Mux(acquire, TLMessages.Get, opcode)
|
||||
io.out_a.bits.param := Mux(acquire, UInt(0), param)
|
||||
io.out_a.bits.size := size
|
||||
io.out_a.bits.source := Cat(Mux(acquire, transform, PASS), source)
|
||||
io.out_a.bits.address := address
|
||||
io.out_a.bits.mask := o_data.bits.mask
|
||||
io.out_a.bits.data := o_data.bits.data
|
||||
}
|
||||
|
||||
object TLBroadcastConstants
|
||||
{
|
||||
def TRANSFORM_T = UInt(3)
|
||||
def TRANSFORM_B = UInt(2)
|
||||
def DROP = UInt(1)
|
||||
def PASS = UInt(0)
|
||||
}
|
||||
|
||||
class TLBroadcastData(params: TLBundleParameters) extends TLBundleBase(params)
|
||||
{
|
||||
val mask = UInt(width = params.dataBits/8)
|
||||
val data = UInt(width = params.dataBits)
|
||||
}
|
68
src/main/scala/tilelink/Buffer.scala
Normal file
68
src/main/scala/tilelink/Buffer.scala
Normal file
@ -0,0 +1,68 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import scala.math.{min,max}
|
||||
|
||||
class TLBuffer(
|
||||
a: BufferParams,
|
||||
b: BufferParams,
|
||||
c: BufferParams,
|
||||
d: BufferParams,
|
||||
e: BufferParams)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
def this(ace: BufferParams, bd: BufferParams)(implicit p: Parameters) = this(ace, bd, ace, bd, ace)
|
||||
def this(abcde: BufferParams)(implicit p: Parameters) = this(abcde, abcde)
|
||||
def this()(implicit p: Parameters) = this(BufferParams.default)
|
||||
|
||||
val node = TLAdapterNode(
|
||||
clientFn = { p => p.copy(minLatency = p.minLatency + b.latency + c.latency) },
|
||||
managerFn = { p => p.copy(minLatency = p.minLatency + a.latency + d.latency) })
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
out.a <> a(in .a)
|
||||
in .d <> d(out.d)
|
||||
|
||||
if (edgeOut.manager.anySupportAcquireB && edgeOut.client.anySupportProbe) {
|
||||
in .b <> b(out.b)
|
||||
out.c <> c(in .c)
|
||||
out.e <> e(in .e)
|
||||
} else {
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLBuffer
|
||||
{
|
||||
// applied to the TL source node; y.node := TLBuffer(x.node)
|
||||
def apply() (x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = apply(BufferParams.default)(x)
|
||||
def apply(abcde: BufferParams) (x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = apply(abcde, abcde)(x)
|
||||
def apply(ace: BufferParams, bd: BufferParams)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = apply(ace, bd, ace, bd, ace)(x)
|
||||
def apply(
|
||||
a: BufferParams,
|
||||
b: BufferParams,
|
||||
c: BufferParams,
|
||||
d: BufferParams,
|
||||
e: BufferParams)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val buffer = LazyModule(new TLBuffer(a, b, c, d, e))
|
||||
buffer.node := x
|
||||
buffer.node
|
||||
}
|
||||
}
|
262
src/main/scala/tilelink/Bundles.scala
Normal file
262
src/main/scala/tilelink/Bundles.scala
Normal file
@ -0,0 +1,262 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.util.{ReadyValidIO}
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
abstract class TLBundleBase(params: TLBundleParameters) extends GenericParameterizedBundle(params)
|
||||
|
||||
// common combos in lazy policy:
|
||||
// Put + Acquire
|
||||
// Release + AccessAck
|
||||
|
||||
object TLMessages
|
||||
{
|
||||
// A B C D E
|
||||
def PutFullData = UInt(0) // . . => AccessAck
|
||||
def PutPartialData = UInt(1) // . . => AccessAck
|
||||
def ArithmeticData = UInt(2) // . . => AccessAckData
|
||||
def LogicalData = UInt(3) // . . => AccessAckData
|
||||
def Get = UInt(4) // . . => AccessAckData
|
||||
def Hint = UInt(5) // . . => HintAck
|
||||
def Acquire = UInt(6) // . => Grant[Data]
|
||||
def Probe = UInt(6) // . => ProbeAck[Data]
|
||||
def AccessAck = UInt(0) // . .
|
||||
def AccessAckData = UInt(1) // . .
|
||||
def HintAck = UInt(2) // . .
|
||||
def ProbeAck = UInt(4) // .
|
||||
def ProbeAckData = UInt(5) // .
|
||||
def Release = UInt(6) // . => ReleaseAck
|
||||
def ReleaseData = UInt(7) // . => ReleaseAck
|
||||
def Grant = UInt(4) // . => GrantAck
|
||||
def GrantData = UInt(5) // . => GrantAck
|
||||
def ReleaseAck = UInt(6) // .
|
||||
def GrantAck = UInt(0) // .
|
||||
|
||||
def isA(x: UInt) = x <= Acquire
|
||||
def isB(x: UInt) = x <= Probe
|
||||
def isC(x: UInt) = x <= ReleaseData
|
||||
def isD(x: UInt) = x <= ReleaseAck
|
||||
}
|
||||
|
||||
/**
|
||||
* The three primary TileLink permissions are:
|
||||
* (T)runk: the agent is (or is on inwards path to) the global point of serialization.
|
||||
* (B)ranch: the agent is on an outwards path to
|
||||
* (N)one:
|
||||
* These permissions are permuted by transfer operations in various ways.
|
||||
* Operations can cap permissions, request for them to be grown or shrunk,
|
||||
* or for a report on their current status.
|
||||
*/
|
||||
object TLPermissions
|
||||
{
|
||||
val aWidth = 2
|
||||
val bdWidth = 2
|
||||
val cWidth = 3
|
||||
|
||||
// Cap types (Grant = new permissions, Probe = permisions <= target)
|
||||
def toT = UInt(0, bdWidth)
|
||||
def toB = UInt(1, bdWidth)
|
||||
def toN = UInt(2, bdWidth)
|
||||
def isCap(x: UInt) = x <= toN
|
||||
|
||||
// Grow types (Acquire = permissions >= target)
|
||||
def NtoB = UInt(0, aWidth)
|
||||
def NtoT = UInt(1, aWidth)
|
||||
def BtoT = UInt(2, aWidth)
|
||||
def isGrow(x: UInt) = x <= BtoT
|
||||
|
||||
// Shrink types (ProbeAck, Release)
|
||||
def TtoB = UInt(0, cWidth)
|
||||
def TtoN = UInt(1, cWidth)
|
||||
def BtoN = UInt(2, cWidth)
|
||||
def isShrink(x: UInt) = x <= BtoN
|
||||
|
||||
// Report types (ProbeAck)
|
||||
def TtoT = UInt(3, cWidth)
|
||||
def BtoB = UInt(4, cWidth)
|
||||
def NtoN = UInt(5, cWidth)
|
||||
def isReport(x: UInt) = x <= NtoN
|
||||
}
|
||||
|
||||
object TLAtomics
|
||||
{
|
||||
val width = 3
|
||||
|
||||
// Arithmetic types
|
||||
def MIN = UInt(0, width)
|
||||
def MAX = UInt(1, width)
|
||||
def MINU = UInt(2, width)
|
||||
def MAXU = UInt(3, width)
|
||||
def ADD = UInt(4, width)
|
||||
def isArithmetic(x: UInt) = x <= ADD
|
||||
|
||||
// Logical types
|
||||
def XOR = UInt(0, width)
|
||||
def OR = UInt(1, width)
|
||||
def AND = UInt(2, width)
|
||||
def SWAP = UInt(3, width)
|
||||
def isLogical(x: UInt) = x <= SWAP
|
||||
}
|
||||
|
||||
object TLHints
|
||||
{
|
||||
val width = 1
|
||||
|
||||
def PREFETCH_READ = UInt(0, width)
|
||||
def PREFETCH_WRITE = UInt(1, width)
|
||||
}
|
||||
|
||||
sealed trait TLChannel extends TLBundleBase {
|
||||
val channelName: String
|
||||
}
|
||||
sealed trait TLDataChannel extends TLChannel
|
||||
sealed trait TLAddrChannel extends TLDataChannel
|
||||
|
||||
final class TLBundleA(params: TLBundleParameters)
|
||||
extends TLBundleBase(params) with TLAddrChannel
|
||||
{
|
||||
val channelName = "'A' channel"
|
||||
// fixed fields during multibeat:
|
||||
val opcode = UInt(width = 3)
|
||||
val param = UInt(width = List(TLAtomics.width, TLPermissions.aWidth, TLHints.width).max) // amo_opcode || grow perms || hint
|
||||
val size = UInt(width = params.sizeBits)
|
||||
val source = UInt(width = params.sourceBits) // from
|
||||
val address = UInt(width = params.addressBits) // to
|
||||
// variable fields during multibeat:
|
||||
val mask = UInt(width = params.dataBits/8)
|
||||
val data = UInt(width = params.dataBits)
|
||||
}
|
||||
|
||||
final class TLBundleB(params: TLBundleParameters)
|
||||
extends TLBundleBase(params) with TLAddrChannel
|
||||
{
|
||||
val channelName = "'B' channel"
|
||||
// fixed fields during multibeat:
|
||||
val opcode = UInt(width = 3)
|
||||
val param = UInt(width = TLPermissions.bdWidth) // cap perms
|
||||
val size = UInt(width = params.sizeBits)
|
||||
val source = UInt(width = params.sourceBits) // to
|
||||
val address = UInt(width = params.addressBits) // from
|
||||
// variable fields during multibeat:
|
||||
val mask = UInt(width = params.dataBits/8)
|
||||
val data = UInt(width = params.dataBits)
|
||||
}
|
||||
|
||||
final class TLBundleC(params: TLBundleParameters)
|
||||
extends TLBundleBase(params) with TLAddrChannel
|
||||
{
|
||||
val channelName = "'C' channel"
|
||||
// fixed fields during multibeat:
|
||||
val opcode = UInt(width = 3)
|
||||
val param = UInt(width = TLPermissions.cWidth) // shrink or report perms
|
||||
val size = UInt(width = params.sizeBits)
|
||||
val source = UInt(width = params.sourceBits) // from
|
||||
val address = UInt(width = params.addressBits) // to
|
||||
// variable fields during multibeat:
|
||||
val data = UInt(width = params.dataBits)
|
||||
val error = Bool() // AccessAck[Data]
|
||||
}
|
||||
|
||||
final class TLBundleD(params: TLBundleParameters)
|
||||
extends TLBundleBase(params) with TLDataChannel
|
||||
{
|
||||
val channelName = "'D' channel"
|
||||
// fixed fields during multibeat:
|
||||
val opcode = UInt(width = 3)
|
||||
val param = UInt(width = TLPermissions.bdWidth) // cap perms
|
||||
val size = UInt(width = params.sizeBits)
|
||||
val source = UInt(width = params.sourceBits) // to
|
||||
val sink = UInt(width = params.sinkBits) // from
|
||||
val addr_lo = UInt(width = params.addrLoBits) // instead of mask
|
||||
// variable fields during multibeat:
|
||||
val data = UInt(width = params.dataBits)
|
||||
val error = Bool() // AccessAck[Data], Grant[Data]
|
||||
}
|
||||
|
||||
final class TLBundleE(params: TLBundleParameters)
|
||||
extends TLBundleBase(params) with TLChannel
|
||||
{
|
||||
val channelName = "'E' channel"
|
||||
val sink = UInt(width = params.sinkBits) // to
|
||||
}
|
||||
|
||||
class TLBundle(params: TLBundleParameters) extends TLBundleBase(params)
|
||||
{
|
||||
val a = Decoupled(new TLBundleA(params))
|
||||
val b = Decoupled(new TLBundleB(params)).flip
|
||||
val c = Decoupled(new TLBundleC(params))
|
||||
val d = Decoupled(new TLBundleD(params)).flip
|
||||
val e = Decoupled(new TLBundleE(params))
|
||||
}
|
||||
|
||||
object TLBundle
|
||||
{
|
||||
def apply(params: TLBundleParameters) = new TLBundle(params)
|
||||
}
|
||||
|
||||
final class DecoupledSnoop[+T <: Data](gen: T) extends Bundle
|
||||
{
|
||||
val ready = Bool()
|
||||
val valid = Bool()
|
||||
val bits = gen.asOutput
|
||||
|
||||
def fire(dummy: Int = 0) = ready && valid
|
||||
override def cloneType: this.type = new DecoupledSnoop(gen).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
object DecoupledSnoop
|
||||
{
|
||||
def apply[T <: Data](source: DecoupledIO[T], sink: DecoupledIO[T]) = {
|
||||
val out = Wire(new DecoupledSnoop(sink.bits))
|
||||
out.ready := sink.ready
|
||||
out.valid := source.valid
|
||||
out.bits := source.bits
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
class TLBundleSnoop(params: TLBundleParameters) extends TLBundleBase(params)
|
||||
{
|
||||
val a = new DecoupledSnoop(new TLBundleA(params))
|
||||
val b = new DecoupledSnoop(new TLBundleB(params))
|
||||
val c = new DecoupledSnoop(new TLBundleC(params))
|
||||
val d = new DecoupledSnoop(new TLBundleD(params))
|
||||
val e = new DecoupledSnoop(new TLBundleE(params))
|
||||
}
|
||||
|
||||
object TLBundleSnoop
|
||||
{
|
||||
def apply(source: TLBundle, sink: TLBundle) = {
|
||||
val out = Wire(new TLBundleSnoop(sink.params))
|
||||
out.a := DecoupledSnoop(source.a, sink.a)
|
||||
out.b := DecoupledSnoop(sink.b, source.b)
|
||||
out.c := DecoupledSnoop(source.c, sink.c)
|
||||
out.d := DecoupledSnoop(sink.d, source.d)
|
||||
out.e := DecoupledSnoop(source.e, sink.e)
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
class TLAsyncBundleBase(params: TLAsyncBundleParameters) extends GenericParameterizedBundle(params)
|
||||
|
||||
class TLAsyncBundle(params: TLAsyncBundleParameters) extends TLAsyncBundleBase(params)
|
||||
{
|
||||
val a = new AsyncBundle(params.depth, new TLBundleA(params.base))
|
||||
val b = new AsyncBundle(params.depth, new TLBundleB(params.base)).flip
|
||||
val c = new AsyncBundle(params.depth, new TLBundleC(params.base))
|
||||
val d = new AsyncBundle(params.depth, new TLBundleD(params.base)).flip
|
||||
val e = new AsyncBundle(params.depth, new TLBundleE(params.base))
|
||||
}
|
||||
|
||||
class TLRationalBundle(params: TLBundleParameters) extends TLBundleBase(params)
|
||||
{
|
||||
val a = RationalIO(new TLBundleA(params))
|
||||
val b = RationalIO(new TLBundleB(params)).flip
|
||||
val c = RationalIO(new TLBundleC(params))
|
||||
val d = RationalIO(new TLBundleD(params)).flip
|
||||
val e = RationalIO(new TLBundleE(params))
|
||||
}
|
129
src/main/scala/tilelink/CacheCork.scala
Normal file
129
src/main/scala/tilelink/CacheCork.scala
Normal file
@ -0,0 +1,129 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import scala.math.{min,max}
|
||||
import TLMessages._
|
||||
|
||||
class TLCacheCork(unsafe: Boolean = false)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLAdapterNode(
|
||||
clientFn = { case cp =>
|
||||
cp.copy(clients = cp.clients.map { c => c.copy(
|
||||
supportsProbe = TransferSizes.none,
|
||||
sourceId = IdRange(c.sourceId.start*2, c.sourceId.end*2))})},
|
||||
managerFn = { case mp =>
|
||||
mp.copy(
|
||||
endSinkId = 1,
|
||||
managers = mp.managers.map { m => m.copy(
|
||||
regionType = if (m.regionType == RegionType.UNCACHED) RegionType.TRACKED else m.regionType,
|
||||
supportsAcquireB = m.supportsGet,
|
||||
supportsAcquireT = m.supportsPutFull)})})
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val clients = edgeIn.client.clients
|
||||
val caches = clients.filter(_.supportsProbe)
|
||||
require (clients.size == 1 || caches.size == 0 || unsafe, "Only one client can safely use a TLCacheCork")
|
||||
require (caches.size <= 1 || unsafe, "Only one caching client allowed")
|
||||
edgeOut.manager.managers.foreach { case m =>
|
||||
require (!m.supportsAcquireB || unsafe, "Cannot support caches beyond the Cork")
|
||||
}
|
||||
|
||||
// The Cork turns [Acquire=>Get] => [AccessAckData=>GrantData]
|
||||
// and [ReleaseData=>PutFullData] => [AccessAck=>ReleaseAck]
|
||||
// We need to encode information sufficient to reverse the transformation in output.
|
||||
// A caveat is that we get Acquire+Release with the same source and must keep the
|
||||
// source unique after transformation onto the A channel.
|
||||
// The coding scheme is:
|
||||
// Put: 1, Release: 0 => AccessAck
|
||||
// *: 0, Acquire: 1 => AccessAckData
|
||||
|
||||
// Take requests from A to A
|
||||
val isPut = in.a.bits.opcode === PutFullData || in.a.bits.opcode === PutPartialData
|
||||
val a_a = Wire(out.a)
|
||||
a_a <> in.a
|
||||
a_a.bits.source := in.a.bits.source << 1 | Mux(isPut, UInt(1), UInt(0))
|
||||
|
||||
// Transform Acquire into Get
|
||||
when (in.a.bits.opcode === Acquire) {
|
||||
a_a.bits.opcode := Get
|
||||
a_a.bits.param := UInt(0)
|
||||
a_a.bits.source := in.a.bits.source << 1 | UInt(1)
|
||||
}
|
||||
|
||||
// Take ReleaseData from C to A; Release from C to D
|
||||
val c_a = Wire(out.a)
|
||||
c_a.valid := in.c.valid && in.c.bits.opcode === ReleaseData
|
||||
c_a.bits.opcode := PutFullData
|
||||
c_a.bits.param := UInt(0)
|
||||
c_a.bits.size := in.c.bits.size
|
||||
c_a.bits.source := in.c.bits.source << 1
|
||||
c_a.bits.address := in.c.bits.address
|
||||
c_a.bits.mask := edgeOut.mask(in.c.bits.address, in.c.bits.size)
|
||||
c_a.bits.data := in.c.bits.data
|
||||
|
||||
val c_d = Wire(in.d)
|
||||
c_d.valid := in.c.valid && in.c.bits.opcode === Release
|
||||
c_d.bits.opcode := ReleaseAck
|
||||
c_d.bits.param := UInt(0)
|
||||
c_d.bits.size := in.c.bits.size
|
||||
c_d.bits.source := in.c.bits.source
|
||||
c_d.bits.sink := UInt(0)
|
||||
c_d.bits.addr_lo := in.c.bits.address
|
||||
c_d.bits.data := UInt(0)
|
||||
c_d.bits.error := Bool(false)
|
||||
|
||||
assert (!in.c.valid || in.c.bits.opcode === Release || in.c.bits.opcode === ReleaseData)
|
||||
in.c.ready := Mux(in.c.bits.opcode === Release, c_d.ready, c_a.ready)
|
||||
|
||||
// Discard E
|
||||
in.e.ready := Bool(true)
|
||||
|
||||
// Block B; should never happen
|
||||
out.b.ready := Bool(false)
|
||||
assert (!out.b.valid)
|
||||
|
||||
// Take responses from D and transform them
|
||||
val d_d = Wire(in.d)
|
||||
d_d <> out.d
|
||||
d_d.bits.source := out.d.bits.source >> 1
|
||||
|
||||
when (out.d.bits.opcode === AccessAckData && out.d.bits.source(0)) {
|
||||
d_d.bits.opcode := GrantData
|
||||
d_d.bits.param := TLPermissions.toT
|
||||
}
|
||||
when (out.d.bits.opcode === AccessAck && !out.d.bits.source(0)) {
|
||||
d_d.bits.opcode := ReleaseAck
|
||||
}
|
||||
|
||||
// Combine the sources of messages into the channels
|
||||
TLArbiter(TLArbiter.lowestIndexFirst)(out.a, (edgeOut.numBeats1(c_a.bits), c_a), (edgeOut.numBeats1(a_a.bits), a_a))
|
||||
TLArbiter(TLArbiter.lowestIndexFirst)(in.d, (edgeIn .numBeats1(d_d.bits), d_d), (UInt(0), Queue(c_d, 2)))
|
||||
|
||||
// Tie off unused ports
|
||||
in.b.valid := Bool(false)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLCacheCork
|
||||
{
|
||||
// applied to the TL source node; y.node := TLCacheCork()(x.node)
|
||||
def apply(unsafe: Boolean = false)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val cork = LazyModule(new TLCacheCork(unsafe))
|
||||
cork.node := x
|
||||
cork.node
|
||||
}
|
||||
}
|
88
src/main/scala/tilelink/Delayer.scala
Normal file
88
src/main/scala/tilelink/Delayer.scala
Normal file
@ -0,0 +1,88 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
|
||||
// q is the probability to delay a request
|
||||
class TLDelayer(q: Double)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLIdentityNode()
|
||||
require (0.0 <= q && q < 1)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
def feed[T <: Data](sink: DecoupledIO[T], source: DecoupledIO[T], noise: T) {
|
||||
val allow = UInt((q * 65535.0).toInt) <= LFSR16(source.valid)
|
||||
sink.valid := source.valid && allow
|
||||
source.ready := sink.ready && allow
|
||||
sink.bits := source.bits
|
||||
when (!sink.valid) { sink.bits := noise }
|
||||
}
|
||||
|
||||
(io.in zip io.out) foreach { case (in, out) =>
|
||||
val anoise = Wire(in.a.bits)
|
||||
anoise.opcode := LFSRNoiseMaker(3)
|
||||
anoise.param := LFSRNoiseMaker(3)
|
||||
anoise.size := LFSRNoiseMaker(anoise.params.sizeBits)
|
||||
anoise.source := LFSRNoiseMaker(anoise.params.sourceBits)
|
||||
anoise.address := LFSRNoiseMaker(anoise.params.addressBits)
|
||||
anoise.mask := LFSRNoiseMaker(anoise.params.dataBits/8)
|
||||
anoise.data := LFSRNoiseMaker(anoise.params.dataBits)
|
||||
|
||||
val bnoise = Wire(out.b.bits)
|
||||
bnoise.opcode := LFSRNoiseMaker(3)
|
||||
bnoise.param := LFSRNoiseMaker(3)
|
||||
bnoise.size := LFSRNoiseMaker(bnoise.params.sizeBits)
|
||||
bnoise.source := LFSRNoiseMaker(bnoise.params.sourceBits)
|
||||
bnoise.address := LFSRNoiseMaker(bnoise.params.addressBits)
|
||||
bnoise.mask := LFSRNoiseMaker(bnoise.params.dataBits/8)
|
||||
bnoise.data := LFSRNoiseMaker(bnoise.params.dataBits)
|
||||
|
||||
val cnoise = Wire(in.c.bits)
|
||||
cnoise.opcode := LFSRNoiseMaker(3)
|
||||
cnoise.param := LFSRNoiseMaker(3)
|
||||
cnoise.size := LFSRNoiseMaker(cnoise.params.sizeBits)
|
||||
cnoise.source := LFSRNoiseMaker(cnoise.params.sourceBits)
|
||||
cnoise.address := LFSRNoiseMaker(cnoise.params.addressBits)
|
||||
cnoise.data := LFSRNoiseMaker(cnoise.params.dataBits)
|
||||
cnoise.error := LFSRNoiseMaker(1)(0)
|
||||
|
||||
val dnoise = Wire(out.d.bits)
|
||||
dnoise.opcode := LFSRNoiseMaker(3)
|
||||
dnoise.param := LFSRNoiseMaker(3)
|
||||
dnoise.size := LFSRNoiseMaker(dnoise.params.sizeBits)
|
||||
dnoise.source := LFSRNoiseMaker(dnoise.params.sourceBits)
|
||||
dnoise.sink := LFSRNoiseMaker(dnoise.params.sinkBits)
|
||||
dnoise.addr_lo := LFSRNoiseMaker(dnoise.params.addrLoBits)
|
||||
dnoise.data := LFSRNoiseMaker(dnoise.params.dataBits)
|
||||
dnoise.error := LFSRNoiseMaker(1)(0)
|
||||
|
||||
val enoise = Wire(in.e.bits)
|
||||
enoise.sink := LFSRNoiseMaker(enoise.params.sinkBits)
|
||||
|
||||
feed(out.a, in.a, anoise)
|
||||
feed(out.c, in.c, cnoise)
|
||||
feed(out.e, in.e, enoise)
|
||||
feed(in.b, out.b, bnoise)
|
||||
feed(in.d, out.d, dnoise)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLDelayer
|
||||
{
|
||||
// applied to the TL source node; y.node := TLDelayer(0.01)(x.node)
|
||||
def apply(q: Double)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val delayer = LazyModule(new TLDelayer(q))
|
||||
delayer.node := x
|
||||
delayer.node
|
||||
}
|
||||
}
|
671
src/main/scala/tilelink/Edges.scala
Normal file
671
src/main/scala/tilelink/Edges.scala
Normal file
@ -0,0 +1,671 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class TLEdge(
|
||||
client: TLClientPortParameters,
|
||||
manager: TLManagerPortParameters)
|
||||
extends TLEdgeParameters(client, manager)
|
||||
{
|
||||
def isAligned(address: UInt, lgSize: UInt): Bool = {
|
||||
if (maxLgSize == 0) Bool(true) else {
|
||||
val mask = UIntToOH1(lgSize, maxLgSize)
|
||||
(address & mask) === UInt(0)
|
||||
}
|
||||
}
|
||||
|
||||
def mask(address: UInt, lgSize: UInt): UInt =
|
||||
MaskGen(address, lgSize, manager.beatBytes)
|
||||
|
||||
def staticHasData(bundle: TLChannel): Option[Boolean] = {
|
||||
bundle match {
|
||||
case _:TLBundleA => {
|
||||
// Do there exist A messages with Data?
|
||||
val aDataYes = manager.anySupportArithmetic || manager.anySupportLogical || manager.anySupportPutFull || manager.anySupportPutPartial
|
||||
// Do there exist A messages without Data?
|
||||
val aDataNo = manager.anySupportAcquireB || manager.anySupportGet || manager.anySupportHint
|
||||
// Statically optimize the case where hasData is a constant
|
||||
if (!aDataYes) Some(false) else if (!aDataNo) Some(true) else None
|
||||
}
|
||||
case _:TLBundleB => {
|
||||
// Do there exist B messages with Data?
|
||||
val bDataYes = client.anySupportArithmetic || client.anySupportLogical || client.anySupportPutFull || client.anySupportPutPartial
|
||||
// Do there exist B messages without Data?
|
||||
val bDataNo = client.anySupportProbe || client.anySupportGet || client.anySupportHint
|
||||
// Statically optimize the case where hasData is a constant
|
||||
if (!bDataYes) Some(false) else if (!bDataNo) Some(true) else None
|
||||
}
|
||||
case _:TLBundleC => {
|
||||
// Do there eixst C messages with Data?
|
||||
val cDataYes = client.anySupportGet || client.anySupportArithmetic || client.anySupportLogical || client.anySupportProbe
|
||||
// Do there exist C messages without Data?
|
||||
val cDataNo = client.anySupportPutFull || client.anySupportPutPartial || client.anySupportHint || client.anySupportProbe
|
||||
if (!cDataYes) Some(false) else if (!cDataNo) Some(true) else None
|
||||
}
|
||||
case _:TLBundleD => {
|
||||
// Do there eixst D messages with Data?
|
||||
val dDataYes = manager.anySupportGet || manager.anySupportArithmetic || manager.anySupportLogical || manager.anySupportAcquireB
|
||||
// Do there exist D messages without Data?
|
||||
val dDataNo = manager.anySupportPutFull || manager.anySupportPutPartial || manager.anySupportHint || manager.anySupportAcquireT
|
||||
if (!dDataYes) Some(false) else if (!dDataNo) Some(true) else None
|
||||
}
|
||||
case _:TLBundleE => Some(false)
|
||||
}
|
||||
}
|
||||
|
||||
def isRequest(x: TLChannel): Bool = {
|
||||
x match {
|
||||
case a: TLBundleA => Bool(true)
|
||||
case b: TLBundleB => Bool(true)
|
||||
case c: TLBundleC => c.opcode(2) && c.opcode(1)
|
||||
// opcode === TLMessages.Release ||
|
||||
// opcode === TLMessages.ReleaseData
|
||||
case d: TLBundleD => d.opcode(2) && !d.opcode(1)
|
||||
// opcode === TLMessages.Grant ||
|
||||
// opcode === TLMessages.GrantData
|
||||
case e: TLBundleE => Bool(false)
|
||||
}
|
||||
}
|
||||
|
||||
def isResponse(x: TLChannel): Bool = {
|
||||
x match {
|
||||
case a: TLBundleA => Bool(false)
|
||||
case b: TLBundleB => Bool(false)
|
||||
case c: TLBundleC => !c.opcode(2) || !c.opcode(1)
|
||||
// opcode =/= TLMessages.Release &&
|
||||
// opcode =/= TLMessages.ReleaseData
|
||||
case d: TLBundleD => Bool(true) // Grant isResponse + isRequest
|
||||
case e: TLBundleE => Bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
def hasData(x: TLChannel): Bool = {
|
||||
val opdata = x match {
|
||||
case a: TLBundleA => !a.opcode(2)
|
||||
// opcode === TLMessages.PutFullData ||
|
||||
// opcode === TLMessages.PutPartialData ||
|
||||
// opcode === TLMessages.ArithmeticData ||
|
||||
// opcode === TLMessages.LogicalData
|
||||
case b: TLBundleB => !b.opcode(2)
|
||||
// opcode === TLMessages.PutFullData ||
|
||||
// opcode === TLMessages.PutPartialData ||
|
||||
// opcode === TLMessages.ArithmeticData ||
|
||||
// opcode === TLMessages.LogicalData
|
||||
case c: TLBundleC => c.opcode(0)
|
||||
// opcode === TLMessages.AccessAckData ||
|
||||
// opcode === TLMessages.ProbeAckData ||
|
||||
// opcode === TLMessages.ReleaseData
|
||||
case d: TLBundleD => d.opcode(0)
|
||||
// opcode === TLMessages.AccessAckData ||
|
||||
// opcode === TLMessages.GrantData
|
||||
case e: TLBundleE => Bool(false)
|
||||
}
|
||||
staticHasData(x).map(Bool(_)).getOrElse(opdata)
|
||||
}
|
||||
|
||||
def opcode(x: TLDataChannel): UInt = {
|
||||
x match {
|
||||
case a: TLBundleA => a.opcode
|
||||
case b: TLBundleB => b.opcode
|
||||
case c: TLBundleC => c.opcode
|
||||
case d: TLBundleD => d.opcode
|
||||
}
|
||||
}
|
||||
|
||||
def param(x: TLDataChannel): UInt = {
|
||||
x match {
|
||||
case a: TLBundleA => a.param
|
||||
case b: TLBundleB => b.param
|
||||
case c: TLBundleC => c.param
|
||||
case d: TLBundleD => d.param
|
||||
}
|
||||
}
|
||||
|
||||
def size(x: TLDataChannel): UInt = {
|
||||
x match {
|
||||
case a: TLBundleA => a.size
|
||||
case b: TLBundleB => b.size
|
||||
case c: TLBundleC => c.size
|
||||
case d: TLBundleD => d.size
|
||||
}
|
||||
}
|
||||
|
||||
def data(x: TLDataChannel): UInt = {
|
||||
x match {
|
||||
case a: TLBundleA => a.data
|
||||
case b: TLBundleB => b.data
|
||||
case c: TLBundleC => c.data
|
||||
case d: TLBundleD => d.data
|
||||
}
|
||||
}
|
||||
|
||||
def mask(x: TLDataChannel): UInt = {
|
||||
x match {
|
||||
case a: TLBundleA => a.mask
|
||||
case b: TLBundleB => b.mask
|
||||
case c: TLBundleC => mask(c.address, c.size)
|
||||
case d: TLBundleD => mask(d.addr_lo, d.size)
|
||||
}
|
||||
}
|
||||
|
||||
def full_mask(x: TLDataChannel): UInt = {
|
||||
x match {
|
||||
case a: TLBundleA => mask(a.address, a.size)
|
||||
case b: TLBundleB => mask(b.address, b.size)
|
||||
case c: TLBundleC => mask(c.address, c.size)
|
||||
case d: TLBundleD => mask(d.addr_lo, d.size)
|
||||
}
|
||||
}
|
||||
|
||||
def address(x: TLDataChannel): UInt = {
|
||||
x match {
|
||||
case a: TLBundleA => a.address
|
||||
case b: TLBundleB => b.address
|
||||
case c: TLBundleC => c.address
|
||||
case d: TLBundleD => d.addr_lo
|
||||
}
|
||||
}
|
||||
|
||||
def source(x: TLDataChannel): UInt = {
|
||||
x match {
|
||||
case a: TLBundleA => a.source
|
||||
case b: TLBundleB => b.source
|
||||
case c: TLBundleC => c.source
|
||||
case d: TLBundleD => d.source
|
||||
}
|
||||
}
|
||||
|
||||
def addr_hi(x: UInt): UInt = x >> log2Ceil(manager.beatBytes)
|
||||
def addr_lo(x: UInt): UInt =
|
||||
if (manager.beatBytes == 1) UInt(0) else x(log2Ceil(manager.beatBytes)-1, 0)
|
||||
|
||||
def addr_hi(x: TLAddrChannel): UInt = addr_hi(address(x))
|
||||
def addr_lo(x: TLDataChannel): UInt = addr_lo(address(x))
|
||||
|
||||
def numBeats(x: TLChannel): UInt = {
|
||||
x match {
|
||||
case _: TLBundleE => UInt(1)
|
||||
case bundle: TLDataChannel => {
|
||||
val hasData = this.hasData(bundle)
|
||||
val size = this.size(bundle)
|
||||
val cutoff = log2Ceil(manager.beatBytes)
|
||||
val small = if (manager.maxTransfer <= manager.beatBytes) Bool(true) else size <= UInt(cutoff)
|
||||
val decode = UIntToOH(size, maxLgSize+1) >> cutoff
|
||||
Mux(hasData, decode | small.asUInt, UInt(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def numBeats1(x: TLChannel): UInt = {
|
||||
x match {
|
||||
case _: TLBundleE => UInt(0)
|
||||
case bundle: TLDataChannel => {
|
||||
if (maxLgSize == 0) {
|
||||
UInt(0)
|
||||
} else {
|
||||
val decode = UIntToOH1(size(bundle), maxLgSize) >> log2Ceil(manager.beatBytes)
|
||||
Mux(hasData(bundle), decode, UInt(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def firstlastHelper(bits: TLChannel, fire: Bool): (Bool, Bool, Bool, UInt) = {
|
||||
val beats1 = numBeats1(bits)
|
||||
val counter = RegInit(UInt(0, width = log2Up(maxTransfer / manager.beatBytes)))
|
||||
val counter1 = counter - UInt(1)
|
||||
val first = counter === UInt(0)
|
||||
val last = counter === UInt(1) || beats1 === UInt(0)
|
||||
val done = last && fire
|
||||
val count = (beats1 & ~counter1)
|
||||
when (fire) {
|
||||
counter := Mux(first, beats1, counter1)
|
||||
}
|
||||
(first, last, done, count)
|
||||
}
|
||||
|
||||
def first(bits: TLChannel, fire: Bool): Bool = firstlastHelper(bits, fire)._1
|
||||
def first(x: DecoupledIO[TLChannel]): Bool = first(x.bits, x.fire())
|
||||
def first(x: ValidIO[TLChannel]): Bool = first(x.bits, x.valid)
|
||||
|
||||
def last(bits: TLChannel, fire: Bool): Bool = firstlastHelper(bits, fire)._2
|
||||
def last(x: DecoupledIO[TLChannel]): Bool = last(x.bits, x.fire())
|
||||
def last(x: ValidIO[TLChannel]): Bool = last(x.bits, x.valid)
|
||||
|
||||
def done(bits: TLChannel, fire: Bool): Bool = firstlastHelper(bits, fire)._3
|
||||
def done(x: DecoupledIO[TLChannel]): Bool = done(x.bits, x.fire())
|
||||
def done(x: ValidIO[TLChannel]): Bool = done(x.bits, x.valid)
|
||||
|
||||
def firstlast(bits: TLChannel, fire: Bool): (Bool, Bool, Bool) = {
|
||||
val r = firstlastHelper(bits, fire)
|
||||
(r._1, r._2, r._3)
|
||||
}
|
||||
def firstlast(x: DecoupledIO[TLChannel]): (Bool, Bool, Bool) = firstlast(x.bits, x.fire())
|
||||
def firstlast(x: ValidIO[TLChannel]): (Bool, Bool, Bool) = firstlast(x.bits, x.valid)
|
||||
|
||||
def count(bits: TLChannel, fire: Bool): (Bool, Bool, Bool, UInt) = {
|
||||
val r = firstlastHelper(bits, fire)
|
||||
(r._1, r._2, r._3, r._4)
|
||||
}
|
||||
def count(x: DecoupledIO[TLChannel]): (Bool, Bool, Bool, UInt) = count(x.bits, x.fire())
|
||||
def count(x: ValidIO[TLChannel]): (Bool, Bool, Bool, UInt) = count(x.bits, x.valid)
|
||||
|
||||
def addr_inc(bits: TLChannel, fire: Bool): (Bool, Bool, Bool, UInt) = {
|
||||
val r = firstlastHelper(bits, fire)
|
||||
(r._1, r._2, r._3, r._4 << log2Ceil(manager.beatBytes))
|
||||
}
|
||||
def addr_inc(x: DecoupledIO[TLChannel]): (Bool, Bool, Bool, UInt) = addr_inc(x.bits, x.fire())
|
||||
def addr_inc(x: ValidIO[TLChannel]): (Bool, Bool, Bool, UInt) = addr_inc(x.bits, x.valid)
|
||||
}
|
||||
|
||||
class TLEdgeOut(
|
||||
client: TLClientPortParameters,
|
||||
manager: TLManagerPortParameters)
|
||||
extends TLEdge(client, manager)
|
||||
{
|
||||
// Transfers
|
||||
def Acquire(fromSource: UInt, toAddress: UInt, lgSize: UInt, growPermissions: UInt) = {
|
||||
require (manager.anySupportAcquireB)
|
||||
val legal = manager.supportsAcquireBFast(toAddress, lgSize)
|
||||
val a = Wire(new TLBundleA(bundle))
|
||||
a.opcode := TLMessages.Acquire
|
||||
a.param := growPermissions
|
||||
a.size := lgSize
|
||||
a.source := fromSource
|
||||
a.address := toAddress
|
||||
a.mask := mask(toAddress, lgSize)
|
||||
a.data := UInt(0)
|
||||
(legal, a)
|
||||
}
|
||||
|
||||
def Release(fromSource: UInt, toAddress: UInt, lgSize: UInt, shrinkPermissions: UInt) = {
|
||||
require (manager.anySupportAcquireB)
|
||||
val legal = manager.supportsAcquireBFast(toAddress, lgSize)
|
||||
val c = Wire(new TLBundleC(bundle))
|
||||
c.opcode := TLMessages.Release
|
||||
c.param := shrinkPermissions
|
||||
c.size := lgSize
|
||||
c.source := fromSource
|
||||
c.address := toAddress
|
||||
c.data := UInt(0)
|
||||
c.error := Bool(false)
|
||||
(legal, c)
|
||||
}
|
||||
|
||||
def Release(fromSource: UInt, toAddress: UInt, lgSize: UInt, shrinkPermissions: UInt, data: UInt) = {
|
||||
require (manager.anySupportAcquireB)
|
||||
val legal = manager.supportsAcquireBFast(toAddress, lgSize)
|
||||
val c = Wire(new TLBundleC(bundle))
|
||||
c.opcode := TLMessages.ReleaseData
|
||||
c.param := shrinkPermissions
|
||||
c.size := lgSize
|
||||
c.source := fromSource
|
||||
c.address := toAddress
|
||||
c.data := data
|
||||
c.error := Bool(false)
|
||||
(legal, c)
|
||||
}
|
||||
|
||||
def ProbeAck(b: TLBundleB, reportPermissions: UInt): TLBundleC =
|
||||
ProbeAck(b.source, b.address, b.size, reportPermissions)
|
||||
|
||||
def ProbeAck(fromSource: UInt, toAddress: UInt, lgSize: UInt, reportPermissions: UInt): TLBundleC = {
|
||||
val c = Wire(new TLBundleC(bundle))
|
||||
c.opcode := TLMessages.ProbeAck
|
||||
c.param := reportPermissions
|
||||
c.size := lgSize
|
||||
c.source := fromSource
|
||||
c.address := toAddress
|
||||
c.data := UInt(0)
|
||||
c.error := Bool(false)
|
||||
c
|
||||
}
|
||||
|
||||
def ProbeAck(b: TLBundleB, reportPermissions: UInt, data: UInt): TLBundleC =
|
||||
ProbeAck(b.source, b.address, b.size, reportPermissions, data)
|
||||
|
||||
def ProbeAck(fromSource: UInt, toAddress: UInt, lgSize: UInt, reportPermissions: UInt, data: UInt): TLBundleC = {
|
||||
val c = Wire(new TLBundleC(bundle))
|
||||
c.opcode := TLMessages.ProbeAckData
|
||||
c.param := reportPermissions
|
||||
c.size := lgSize
|
||||
c.source := fromSource
|
||||
c.address := toAddress
|
||||
c.data := data
|
||||
c.error := Bool(false)
|
||||
c
|
||||
}
|
||||
|
||||
def GrantAck(d: TLBundleD): TLBundleE = GrantAck(d.sink)
|
||||
def GrantAck(toSink: UInt): TLBundleE = {
|
||||
val e = Wire(new TLBundleE(bundle))
|
||||
e.sink := toSink
|
||||
e
|
||||
}
|
||||
|
||||
// Accesses
|
||||
def Get(fromSource: UInt, toAddress: UInt, lgSize: UInt) = {
|
||||
require (manager.anySupportGet)
|
||||
val legal = manager.supportsGetFast(toAddress, lgSize)
|
||||
val a = Wire(new TLBundleA(bundle))
|
||||
a.opcode := TLMessages.Get
|
||||
a.param := UInt(0)
|
||||
a.size := lgSize
|
||||
a.source := fromSource
|
||||
a.address := toAddress
|
||||
a.mask := mask(toAddress, lgSize)
|
||||
a.data := UInt(0)
|
||||
(legal, a)
|
||||
}
|
||||
|
||||
def Put(fromSource: UInt, toAddress: UInt, lgSize: UInt, data: UInt) = {
|
||||
require (manager.anySupportPutFull)
|
||||
val legal = manager.supportsPutFullFast(toAddress, lgSize)
|
||||
val a = Wire(new TLBundleA(bundle))
|
||||
a.opcode := TLMessages.PutFullData
|
||||
a.param := UInt(0)
|
||||
a.size := lgSize
|
||||
a.source := fromSource
|
||||
a.address := toAddress
|
||||
a.mask := mask(toAddress, lgSize)
|
||||
a.data := data
|
||||
(legal, a)
|
||||
}
|
||||
|
||||
def Put(fromSource: UInt, toAddress: UInt, lgSize: UInt, data: UInt, mask : UInt) = {
|
||||
require (manager.anySupportPutPartial)
|
||||
val legal = manager.supportsPutPartialFast(toAddress, lgSize)
|
||||
val a = Wire(new TLBundleA(bundle))
|
||||
a.opcode := TLMessages.PutPartialData
|
||||
a.param := UInt(0)
|
||||
a.size := lgSize
|
||||
a.source := fromSource
|
||||
a.address := toAddress
|
||||
a.mask := mask
|
||||
a.data := data
|
||||
(legal, a)
|
||||
}
|
||||
|
||||
def Arithmetic(fromSource: UInt, toAddress: UInt, lgSize: UInt, data: UInt, atomic: UInt) = {
|
||||
require (manager.anySupportArithmetic)
|
||||
val legal = manager.supportsArithmeticFast(toAddress, lgSize)
|
||||
val a = Wire(new TLBundleA(bundle))
|
||||
a.opcode := TLMessages.ArithmeticData
|
||||
a.param := atomic
|
||||
a.size := lgSize
|
||||
a.source := fromSource
|
||||
a.address := toAddress
|
||||
a.mask := mask(toAddress, lgSize)
|
||||
a.data := data
|
||||
(legal, a)
|
||||
}
|
||||
|
||||
def Logical(fromSource: UInt, toAddress: UInt, lgSize: UInt, data: UInt, atomic: UInt) = {
|
||||
require (manager.anySupportLogical)
|
||||
val legal = manager.supportsLogicalFast(toAddress, lgSize)
|
||||
val a = Wire(new TLBundleA(bundle))
|
||||
a.opcode := TLMessages.LogicalData
|
||||
a.param := atomic
|
||||
a.size := lgSize
|
||||
a.source := fromSource
|
||||
a.address := toAddress
|
||||
a.mask := mask(toAddress, lgSize)
|
||||
a.data := data
|
||||
(legal, a)
|
||||
}
|
||||
|
||||
def Hint(fromSource: UInt, toAddress: UInt, lgSize: UInt, param: UInt) = {
|
||||
require (manager.anySupportHint)
|
||||
val legal = manager.supportsHintFast(toAddress, lgSize)
|
||||
val a = Wire(new TLBundleA(bundle))
|
||||
a.opcode := TLMessages.Hint
|
||||
a.param := param
|
||||
a.size := lgSize
|
||||
a.source := fromSource
|
||||
a.address := toAddress
|
||||
a.mask := mask(toAddress, lgSize)
|
||||
a.data := UInt(0)
|
||||
(legal, a)
|
||||
}
|
||||
|
||||
def AccessAck(b: TLBundleB): TLBundleC = AccessAck(b.source, address(b), b.size)
|
||||
def AccessAck(b: TLBundleB, error: Bool): TLBundleC = AccessAck(b.source, address(b), b.size, error)
|
||||
def AccessAck(fromSource: UInt, toAddress: UInt, lgSize: UInt): TLBundleC = AccessAck(fromSource, toAddress, lgSize, Bool(false))
|
||||
def AccessAck(fromSource: UInt, toAddress: UInt, lgSize: UInt, error: Bool) = {
|
||||
val c = Wire(new TLBundleC(bundle))
|
||||
c.opcode := TLMessages.AccessAck
|
||||
c.param := UInt(0)
|
||||
c.size := lgSize
|
||||
c.source := fromSource
|
||||
c.address := toAddress
|
||||
c.data := UInt(0)
|
||||
c.error := error
|
||||
c
|
||||
}
|
||||
|
||||
def AccessAck(b: TLBundleB, data: UInt): TLBundleC = AccessAck(b.source, address(b), b.size, data)
|
||||
def AccessAck(b: TLBundleB, data: UInt, error: Bool): TLBundleC = AccessAck(b.source, address(b), b.size, data, error)
|
||||
def AccessAck(fromSource: UInt, toAddress: UInt, lgSize: UInt, data: UInt): TLBundleC = AccessAck(fromSource, toAddress, lgSize, data, Bool(false))
|
||||
def AccessAck(fromSource: UInt, toAddress: UInt, lgSize: UInt, data: UInt, error: Bool) = {
|
||||
val c = Wire(new TLBundleC(bundle))
|
||||
c.opcode := TLMessages.AccessAckData
|
||||
c.param := UInt(0)
|
||||
c.size := lgSize
|
||||
c.source := fromSource
|
||||
c.address := toAddress
|
||||
c.data := data
|
||||
c.error := error
|
||||
c
|
||||
}
|
||||
|
||||
def HintAck(b: TLBundleB): TLBundleC = HintAck(b.source, address(b), b.size)
|
||||
def HintAck(fromSource: UInt, toAddress: UInt, lgSize: UInt) = {
|
||||
val c = Wire(new TLBundleC(bundle))
|
||||
c.opcode := TLMessages.HintAck
|
||||
c.param := UInt(0)
|
||||
c.size := lgSize
|
||||
c.source := fromSource
|
||||
c.address := toAddress
|
||||
c.data := UInt(0)
|
||||
c.error := Bool(false)
|
||||
c
|
||||
}
|
||||
}
|
||||
|
||||
class TLEdgeIn(
|
||||
client: TLClientPortParameters,
|
||||
manager: TLManagerPortParameters)
|
||||
extends TLEdge(client, manager)
|
||||
{
|
||||
// Transfers
|
||||
def Probe(fromAddress: UInt, toSource: UInt, lgSize: UInt, capPermissions: UInt) = {
|
||||
require (client.anySupportProbe)
|
||||
val legal = client.supportsProbe(toSource, lgSize)
|
||||
val b = Wire(new TLBundleB(bundle))
|
||||
b.opcode := TLMessages.Probe
|
||||
b.param := capPermissions
|
||||
b.size := lgSize
|
||||
b.source := toSource
|
||||
b.address := fromAddress
|
||||
b.mask := mask(fromAddress, lgSize)
|
||||
b.data := UInt(0)
|
||||
(legal, b)
|
||||
}
|
||||
|
||||
def Grant(fromAddress: UInt, fromSink: UInt, toSource: UInt, lgSize: UInt, capPermissions: UInt): TLBundleD = Grant(fromAddress, fromSink, toSource, lgSize, capPermissions, Bool(false))
|
||||
def Grant(fromAddress: UInt, fromSink: UInt, toSource: UInt, lgSize: UInt, capPermissions: UInt, error: Bool) = {
|
||||
val d = Wire(new TLBundleD(bundle))
|
||||
d.opcode := TLMessages.Grant
|
||||
d.param := capPermissions
|
||||
d.size := lgSize
|
||||
d.source := toSource
|
||||
d.sink := fromSink
|
||||
d.addr_lo := addr_lo(fromAddress)
|
||||
d.data := UInt(0)
|
||||
d.error := error
|
||||
d
|
||||
}
|
||||
|
||||
def Grant(fromAddress: UInt, fromSink: UInt, toSource: UInt, lgSize: UInt, capPermissions: UInt, data: UInt): TLBundleD = Grant(fromAddress, fromSink, toSource, lgSize, capPermissions, data, Bool(false))
|
||||
def Grant(fromAddress: UInt, fromSink: UInt, toSource: UInt, lgSize: UInt, capPermissions: UInt, data: UInt, error: Bool) = {
|
||||
val d = Wire(new TLBundleD(bundle))
|
||||
d.opcode := TLMessages.GrantData
|
||||
d.param := capPermissions
|
||||
d.size := lgSize
|
||||
d.source := toSource
|
||||
d.sink := fromSink
|
||||
d.addr_lo := addr_lo(fromAddress)
|
||||
d.data := data
|
||||
d.error := error
|
||||
d
|
||||
}
|
||||
|
||||
def ReleaseAck(fromAddress: UInt, fromSink: UInt, toSource: UInt, lgSize: UInt) = {
|
||||
val d = Wire(new TLBundleD(bundle))
|
||||
d.opcode := TLMessages.ReleaseAck
|
||||
d.param := UInt(0)
|
||||
d.size := lgSize
|
||||
d.source := toSource
|
||||
d.sink := fromSink
|
||||
d.addr_lo := addr_lo(fromAddress)
|
||||
d.data := UInt(0)
|
||||
d.error := Bool(false)
|
||||
d
|
||||
}
|
||||
|
||||
// Accesses
|
||||
def Get(fromAddress: UInt, toSource: UInt, lgSize: UInt) = {
|
||||
require (client.anySupportGet)
|
||||
val legal = client.supportsGet(toSource, lgSize)
|
||||
val b = Wire(new TLBundleB(bundle))
|
||||
b.opcode := TLMessages.Get
|
||||
b.param := UInt(0)
|
||||
b.size := lgSize
|
||||
b.source := toSource
|
||||
b.address := fromAddress
|
||||
b.mask := mask(fromAddress, lgSize)
|
||||
b.data := UInt(0)
|
||||
(legal, b)
|
||||
}
|
||||
|
||||
def Put(fromAddress: UInt, toSource: UInt, lgSize: UInt, data: UInt) = {
|
||||
require (client.anySupportPutFull)
|
||||
val legal = client.supportsPutFull(toSource, lgSize)
|
||||
val b = Wire(new TLBundleB(bundle))
|
||||
b.opcode := TLMessages.PutFullData
|
||||
b.param := UInt(0)
|
||||
b.size := lgSize
|
||||
b.source := toSource
|
||||
b.address := fromAddress
|
||||
b.mask := mask(fromAddress, lgSize)
|
||||
b.data := data
|
||||
(legal, b)
|
||||
}
|
||||
|
||||
def Put(fromAddress: UInt, toSource: UInt, lgSize: UInt, data: UInt, mask : UInt) = {
|
||||
require (client.anySupportPutPartial)
|
||||
val legal = client.supportsPutPartial(toSource, lgSize)
|
||||
val b = Wire(new TLBundleB(bundle))
|
||||
b.opcode := TLMessages.PutPartialData
|
||||
b.param := UInt(0)
|
||||
b.size := lgSize
|
||||
b.source := toSource
|
||||
b.address := fromAddress
|
||||
b.mask := mask
|
||||
b.data := data
|
||||
(legal, b)
|
||||
}
|
||||
|
||||
def Arithmetic(fromAddress: UInt, toSource: UInt, lgSize: UInt, data: UInt, atomic: UInt) = {
|
||||
require (client.anySupportArithmetic)
|
||||
val legal = client.supportsArithmetic(toSource, lgSize)
|
||||
val b = Wire(new TLBundleB(bundle))
|
||||
b.opcode := TLMessages.ArithmeticData
|
||||
b.param := atomic
|
||||
b.size := lgSize
|
||||
b.source := toSource
|
||||
b.address := fromAddress
|
||||
b.mask := mask(fromAddress, lgSize)
|
||||
b.data := data
|
||||
(legal, b)
|
||||
}
|
||||
|
||||
def Logical(fromAddress: UInt, toSource: UInt, lgSize: UInt, data: UInt, atomic: UInt) = {
|
||||
require (client.anySupportLogical)
|
||||
val legal = client.supportsLogical(toSource, lgSize)
|
||||
val b = Wire(new TLBundleB(bundle))
|
||||
b.opcode := TLMessages.LogicalData
|
||||
b.param := atomic
|
||||
b.size := lgSize
|
||||
b.source := toSource
|
||||
b.address := fromAddress
|
||||
b.mask := mask(fromAddress, lgSize)
|
||||
b.data := data
|
||||
(legal, b)
|
||||
}
|
||||
|
||||
def Hint(fromAddress: UInt, toSource: UInt, lgSize: UInt, param: UInt) = {
|
||||
require (client.anySupportHint)
|
||||
val legal = client.supportsHint(toSource, lgSize)
|
||||
val b = Wire(new TLBundleB(bundle))
|
||||
b.opcode := TLMessages.Hint
|
||||
b.param := param
|
||||
b.size := lgSize
|
||||
b.source := toSource
|
||||
b.address := fromAddress
|
||||
b.mask := mask(fromAddress, lgSize)
|
||||
b.data := UInt(0)
|
||||
(legal, b)
|
||||
}
|
||||
|
||||
def AccessAck(a: TLBundleA, fromSink: UInt): TLBundleD = AccessAck(address(a), fromSink, a.source, a.size)
|
||||
def AccessAck(a: TLBundleA, fromSink: UInt, error: Bool): TLBundleD = AccessAck(address(a), fromSink, a.source, a.size, error)
|
||||
def AccessAck(fromAddress: UInt, fromSink: UInt, toSource: UInt, lgSize: UInt): TLBundleD = AccessAck(fromAddress, fromSink, toSource, lgSize, Bool(false))
|
||||
def AccessAck(fromAddress: UInt, fromSink: UInt, toSource: UInt, lgSize: UInt, error: Bool) = {
|
||||
val d = Wire(new TLBundleD(bundle))
|
||||
d.opcode := TLMessages.AccessAck
|
||||
d.param := UInt(0)
|
||||
d.size := lgSize
|
||||
d.source := toSource
|
||||
d.sink := fromSink
|
||||
d.addr_lo := addr_lo(fromAddress)
|
||||
d.data := UInt(0)
|
||||
d.error := error
|
||||
d
|
||||
}
|
||||
|
||||
def AccessAck(a: TLBundleA, fromSink: UInt, data: UInt): TLBundleD = AccessAck(address(a), fromSink, a.source, a.size, data)
|
||||
def AccessAck(a: TLBundleA, fromSink: UInt, data: UInt, error: Bool): TLBundleD = AccessAck(address(a), fromSink, a.source, a.size, data, error)
|
||||
def AccessAck(fromAddress: UInt, fromSink: UInt, toSource: UInt, lgSize: UInt, data: UInt): TLBundleD = AccessAck(fromAddress, fromSink, toSource, lgSize, data, Bool(false))
|
||||
def AccessAck(fromAddress: UInt, fromSink: UInt, toSource: UInt, lgSize: UInt, data: UInt, error: Bool) = {
|
||||
val d = Wire(new TLBundleD(bundle))
|
||||
d.opcode := TLMessages.AccessAckData
|
||||
d.param := UInt(0)
|
||||
d.size := lgSize
|
||||
d.source := toSource
|
||||
d.sink := fromSink
|
||||
d.addr_lo := addr_lo(fromAddress)
|
||||
d.data := data
|
||||
d.error := error
|
||||
d
|
||||
}
|
||||
|
||||
def HintAck(a: TLBundleA, fromSink: UInt): TLBundleD = HintAck(address(a), fromSink, a.source, a.size)
|
||||
def HintAck(fromAddress: UInt, fromSink: UInt, toSource: UInt, lgSize: UInt) = {
|
||||
val d = Wire(new TLBundleD(bundle))
|
||||
d.opcode := TLMessages.HintAck
|
||||
d.param := UInt(0)
|
||||
d.size := lgSize
|
||||
d.source := toSource
|
||||
d.sink := fromSink
|
||||
d.addr_lo := addr_lo(fromAddress)
|
||||
d.data := UInt(0)
|
||||
d.error := Bool(false)
|
||||
d
|
||||
}
|
||||
}
|
40
src/main/scala/tilelink/Example.scala
Normal file
40
src/main/scala/tilelink/Example.scala
Normal file
@ -0,0 +1,40 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.regmapper._
|
||||
|
||||
case class ExampleParams(num: Int, address: BigInt)
|
||||
|
||||
trait ExampleBundle
|
||||
{
|
||||
val params: ExampleParams
|
||||
val gpio = UInt(width = params.num)
|
||||
}
|
||||
|
||||
trait ExampleModule extends HasRegMap
|
||||
{
|
||||
val params: ExampleParams
|
||||
val io: ExampleBundle
|
||||
val interrupts: Vec[Bool]
|
||||
|
||||
val state = RegInit(UInt(0))
|
||||
val pending = RegInit(UInt(0xf, width = 4))
|
||||
|
||||
io.gpio := state
|
||||
interrupts := pending.toBools
|
||||
|
||||
regmap(
|
||||
0 -> Seq(
|
||||
RegField(params.num, state)),
|
||||
4 -> Seq(
|
||||
RegField.w1ToClear(4, pending, state)))
|
||||
}
|
||||
|
||||
// Create a concrete TL2 version of the abstract Example slave
|
||||
class TLExample(params: ExampleParams)(implicit p: Parameters)
|
||||
extends TLRegisterRouter(params.address, "somedev", Seq("ucbbar,random-interface"), 4)(
|
||||
new TLRegBundle(params, _) with ExampleBundle)(
|
||||
new TLRegModule(params, _, _) with ExampleModule)
|
77
src/main/scala/tilelink/FIFOFixer.scala
Normal file
77
src/main/scala/tilelink/FIFOFixer.scala
Normal file
@ -0,0 +1,77 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import scala.math.max
|
||||
|
||||
class TLFIFOFixer(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLAdapterNode(
|
||||
clientFn = { cp => cp },
|
||||
managerFn = { mp => mp.copy(managers = mp.managers.map(m => m.copy(fifoId = Some(0)))) })
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val maxId = edgeOut.manager.managers.flatMap(_.fifoId).foldLeft(0)(max)
|
||||
val a_id = edgeOut.manager.findFifoIdFast(in.a.bits.address)
|
||||
val a_nid = a_id === UInt(0) // no id = not FIFO
|
||||
|
||||
val a_first = edgeIn.first(in.a)
|
||||
val d_first = edgeOut.first(out.d) && out.d.bits.opcode =/= TLMessages.ReleaseAck
|
||||
|
||||
val stalls = edgeIn.client.clients.filter(c => c.requestFifo && c.sourceId.size > 1).map { c =>
|
||||
val a_sel = c.sourceId.contains(in.a.bits.source)
|
||||
val d_sel = c.sourceId.contains(in.d.bits.source)
|
||||
val id = RegInit(UInt(0, width = log2Up(maxId+1))) // TODO zero-width
|
||||
val count = RegInit(UInt(0, width = log2Up(c.sourceId.size+1))) // TODO zero-width
|
||||
|
||||
val a_inc = in.a.fire() && a_first && a_sel
|
||||
val d_dec = in.d.fire() && d_first && d_sel
|
||||
count := count + a_inc.asUInt - d_dec.asUInt
|
||||
when (in.a.fire() && a_sel) { id := a_id }
|
||||
|
||||
a_sel && a_first && count =/= UInt(0) && (a_nid || id =/= a_id)
|
||||
}
|
||||
|
||||
val stall = stalls.foldLeft(Bool(false))(_||_)
|
||||
|
||||
out.a <> in.a
|
||||
in.d <> out.d
|
||||
out.a.valid := in.a.valid && !stall
|
||||
in.a.ready := out.a.ready && !stall
|
||||
|
||||
if (edgeOut.manager.anySupportAcquireB && edgeOut.client.anySupportProbe) {
|
||||
in .b <> out.b
|
||||
out.c <> in .c
|
||||
out.e <> in .e
|
||||
} else {
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLFIFOFixer
|
||||
{
|
||||
// applied to the TL source node; y.node := TLFIFOFixer()(x.node)
|
||||
def apply()(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val fixer = LazyModule(new TLFIFOFixer)
|
||||
fixer.node := x
|
||||
fixer.node
|
||||
}
|
||||
}
|
||||
|
54
src/main/scala/tilelink/Filter.scala
Normal file
54
src/main/scala/tilelink/Filter.scala
Normal file
@ -0,0 +1,54 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import scala.math.{min,max}
|
||||
|
||||
class TLFilter(select: AddressSet)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLAdapterNode(
|
||||
clientFn = { cp => cp },
|
||||
managerFn = { mp =>
|
||||
mp.copy(managers = mp.managers.map { m =>
|
||||
val filtered = m.address.map(_.intersect(select)).flatten
|
||||
val alignment = select.alignment /* alignment 0 means 'select' selected everything */
|
||||
val maxTransfer = 1 << 30
|
||||
val capTransfer = if (alignment == 0 || alignment > maxTransfer) maxTransfer else alignment.toInt
|
||||
val cap = TransferSizes(1, capTransfer)
|
||||
if (filtered.isEmpty) { None } else {
|
||||
Some(m.copy(
|
||||
address = filtered,
|
||||
supportsAcquireT = m.supportsAcquireT .intersect(cap),
|
||||
supportsAcquireB = m.supportsAcquireB .intersect(cap),
|
||||
supportsArithmetic = m.supportsArithmetic.intersect(cap),
|
||||
supportsLogical = m.supportsLogical .intersect(cap),
|
||||
supportsGet = m.supportsGet .intersect(cap),
|
||||
supportsPutFull = m.supportsPutFull .intersect(cap),
|
||||
supportsPutPartial = m.supportsPutPartial.intersect(cap),
|
||||
supportsHint = m.supportsHint .intersect(cap)))
|
||||
}
|
||||
}.flatten)
|
||||
})
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
io.out <> io.in
|
||||
}
|
||||
}
|
||||
|
||||
object TLFilter
|
||||
{
|
||||
// applied to the TL source node; y.node := TLBuffer(x.node)
|
||||
def apply(select: AddressSet)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val filter = LazyModule(new TLFilter(select))
|
||||
filter.node := x
|
||||
filter.node
|
||||
}
|
||||
}
|
316
src/main/scala/tilelink/Fragmenter.scala
Normal file
316
src/main/scala/tilelink/Fragmenter.scala
Normal file
@ -0,0 +1,316 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
import scala.math.{min,max}
|
||||
|
||||
// minSize: minimum size of transfers supported by all outward managers
|
||||
// maxSize: maximum size of transfers supported after the Fragmenter is applied
|
||||
// alwaysMin: fragment all requests down to minSize (else fragment to maximum supported by manager)
|
||||
// Fragmenter modifies: PutFull, PutPartial, LogicalData, Get, Hint
|
||||
// Fragmenter passes: ArithmeticData (truncated to minSize if alwaysMin)
|
||||
// Fragmenter cannot modify acquire (could livelock); thus it is unsafe to put caches on both sides
|
||||
class TLFragmenter(val minSize: Int, val maxSize: Int, val alwaysMin: Boolean = false, val earlyAck: Boolean = false)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
require (isPow2 (maxSize))
|
||||
require (isPow2 (minSize))
|
||||
require (minSize < maxSize)
|
||||
|
||||
// EarlyAck means that 1.999 transactions can be inflight at a time
|
||||
// Thus, we need an extra toggle bit to prevent source collisions
|
||||
val fragmentBits = log2Ceil(maxSize / minSize)
|
||||
val toggleBits = if (earlyAck) 1 else 0
|
||||
val addedBits = fragmentBits + toggleBits
|
||||
|
||||
def expandTransfer(x: TransferSizes) = if (!x) x else {
|
||||
require (x.max >= minSize) // validate that we can apply the fragmenter correctly
|
||||
TransferSizes(x.min, maxSize)
|
||||
}
|
||||
def shrinkTransfer(x: TransferSizes) =
|
||||
if (!alwaysMin) x else
|
||||
if (x.min <= minSize) TransferSizes(x.min, min(minSize, x.max)) else
|
||||
TransferSizes.none
|
||||
def mapManager(m: TLManagerParameters) = m.copy(
|
||||
supportsArithmetic = shrinkTransfer(m.supportsArithmetic),
|
||||
supportsLogical = shrinkTransfer(m.supportsLogical),
|
||||
supportsGet = expandTransfer(m.supportsGet),
|
||||
supportsPutFull = expandTransfer(m.supportsPutFull),
|
||||
supportsPutPartial = expandTransfer(m.supportsPutPartial),
|
||||
supportsHint = expandTransfer(m.supportsHint))
|
||||
|
||||
val node = TLAdapterNode(
|
||||
// We require that all the responses are mutually FIFO
|
||||
// Thus we need to compact all of the masters into one big master
|
||||
clientFn = { c => c.copy(clients = Seq(TLClientParameters(
|
||||
name = "TLFragmenter",
|
||||
sourceId = IdRange(0, c.endSourceId << addedBits),
|
||||
requestFifo = true))) },
|
||||
managerFn = { m => m.copy(managers = m.managers.map(mapManager)) })
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
// All managers must share a common FIFO domain (responses might end up interleaved)
|
||||
val manager = edgeOut.manager
|
||||
val managers = manager.managers
|
||||
val beatBytes = manager.beatBytes
|
||||
val fifoId = managers(0).fifoId
|
||||
require (fifoId.isDefined && managers.map(_.fifoId == fifoId).reduce(_ && _))
|
||||
require (manager.endSinkId <= 1)
|
||||
|
||||
// We don't support fragmenting to sub-beat accesses
|
||||
require (minSize >= beatBytes)
|
||||
// We can't support devices which are cached on both sides of us
|
||||
require (!edgeOut.manager.anySupportAcquireB || !edgeIn.client.anySupportProbe)
|
||||
|
||||
/* The Fragmenter is a bit tricky, because there are 5 sizes in play:
|
||||
* max size -- the maximum transfer size possible
|
||||
* orig size -- the original pre-fragmenter size
|
||||
* frag size -- the modified post-fragmenter size
|
||||
* min size -- the threshold below which frag=orig
|
||||
* beat size -- the amount transfered on any given beat
|
||||
*
|
||||
* The relationships are as follows:
|
||||
* max >= orig >= frag
|
||||
* max > min >= beat
|
||||
* It IS possible that orig <= min (then frag=orig; ie: no fragmentation)
|
||||
*
|
||||
* The fragment# (sent via TL.source) is measured in multiples of min size.
|
||||
* Meanwhile, to track the progress, counters measure in multiples of beat size.
|
||||
*
|
||||
* Here is an example of a bus with max=256, min=8, beat=4 and a device supporting 16.
|
||||
*
|
||||
* in.A out.A (frag#) out.D (frag#) in.D gen# ack#
|
||||
* get64 get16 6 ackD16 6 ackD64 12 15
|
||||
* ackD16 6 ackD64 14
|
||||
* ackD16 6 ackD64 13
|
||||
* ackD16 6 ackD64 12
|
||||
* get16 4 ackD16 4 ackD64 8 11
|
||||
* ackD16 4 ackD64 10
|
||||
* ackD16 4 ackD64 9
|
||||
* ackD16 4 ackD64 8
|
||||
* get16 2 ackD16 2 ackD64 4 7
|
||||
* ackD16 2 ackD64 6
|
||||
* ackD16 2 ackD64 5
|
||||
* ackD16 2 ackD64 4
|
||||
* get16 0 ackD16 0 ackD64 0 3
|
||||
* ackD16 0 ackD64 2
|
||||
* ackD16 0 ackD64 1
|
||||
* ackD16 0 ackD64 0
|
||||
*
|
||||
* get8 get8 0 ackD8 0 ackD8 0 1
|
||||
* ackD8 0 ackD8 0
|
||||
*
|
||||
* get4 get4 0 ackD4 0 ackD4 0 0
|
||||
* get1 get1 0 ackD1 0 ackD1 0 0
|
||||
*
|
||||
* put64 put16 6 15
|
||||
* put64 put16 6 14
|
||||
* put64 put16 6 13
|
||||
* put64 put16 6 ack16 6 12 12
|
||||
* put64 put16 4 11
|
||||
* put64 put16 4 10
|
||||
* put64 put16 4 9
|
||||
* put64 put16 4 ack16 4 8 8
|
||||
* put64 put16 2 7
|
||||
* put64 put16 2 6
|
||||
* put64 put16 2 5
|
||||
* put64 put16 2 ack16 2 4 4
|
||||
* put64 put16 0 3
|
||||
* put64 put16 0 2
|
||||
* put64 put16 0 1
|
||||
* put64 put16 0 ack16 0 ack64 0 0
|
||||
*
|
||||
* put8 put8 0 1
|
||||
* put8 put8 0 ack8 0 ack8 0 0
|
||||
*
|
||||
* put4 put4 0 ack4 0 ack4 0 0
|
||||
* put1 put1 0 ack1 0 ack1 0 0
|
||||
*/
|
||||
|
||||
val counterBits = log2Up(maxSize/beatBytes)
|
||||
val maxDownSize = if (alwaysMin) minSize else min(manager.maxTransfer, maxSize)
|
||||
|
||||
// First, handle the return path
|
||||
val acknum = RegInit(UInt(0, width = counterBits))
|
||||
val dOrig = Reg(UInt())
|
||||
val dFragnum = out.d.bits.source(fragmentBits-1, 0)
|
||||
val dFirst = acknum === UInt(0)
|
||||
val dLast = dFragnum === UInt(0)
|
||||
val dsizeOH = UIntToOH (out.d.bits.size, log2Ceil(maxDownSize)+1)
|
||||
val dsizeOH1 = UIntToOH1(out.d.bits.size, log2Up(maxDownSize))
|
||||
val dHasData = edgeOut.hasData(out.d.bits)
|
||||
|
||||
// calculate new acknum
|
||||
val acknum_fragment = dFragnum << log2Ceil(minSize/beatBytes)
|
||||
val acknum_size = dsizeOH1 >> log2Ceil(beatBytes)
|
||||
assert (!out.d.valid || (acknum_fragment & acknum_size) === UInt(0))
|
||||
val dFirst_acknum = acknum_fragment | Mux(dHasData, acknum_size, UInt(0))
|
||||
val ack_decrement = Mux(dHasData, UInt(1), dsizeOH >> log2Ceil(beatBytes))
|
||||
// calculate the original size
|
||||
val dFirst_size = OH1ToUInt((dFragnum << log2Ceil(minSize)) | dsizeOH1)
|
||||
|
||||
when (out.d.fire()) {
|
||||
acknum := Mux(dFirst, dFirst_acknum, acknum - ack_decrement)
|
||||
when (dFirst) { dOrig := dFirst_size }
|
||||
}
|
||||
|
||||
// Swallow up non-data ack fragments
|
||||
val drop = !dHasData && !(if (earlyAck) dFirst else dLast)
|
||||
out.d.ready := in.d.ready || drop
|
||||
in.d.valid := out.d.valid && !drop
|
||||
in.d.bits := out.d.bits // pass most stuff unchanged
|
||||
in.d.bits.addr_lo := out.d.bits.addr_lo & ~dsizeOH1
|
||||
in.d.bits.source := out.d.bits.source >> addedBits
|
||||
in.d.bits.size := Mux(dFirst, dFirst_size, dOrig)
|
||||
|
||||
if (earlyAck) {
|
||||
// If you do early Ack, errors may not be dropped
|
||||
// ... which roughly means: Puts may not fail
|
||||
assert (!out.d.valid || !out.d.bits.error || !drop)
|
||||
in.d.bits.error := out.d.bits.error
|
||||
} else {
|
||||
// Combine the error flag
|
||||
val r_error = RegInit(Bool(false))
|
||||
val d_error = r_error | out.d.bits.error
|
||||
when (out.d.fire()) { r_error := Mux(drop, d_error, UInt(0)) }
|
||||
in.d.bits.error := d_error
|
||||
}
|
||||
|
||||
// What maximum transfer sizes do downstream devices support?
|
||||
val maxArithmetics = managers.map(_.supportsArithmetic.max)
|
||||
val maxLogicals = managers.map(_.supportsLogical.max)
|
||||
val maxGets = managers.map(_.supportsGet.max)
|
||||
val maxPutFulls = managers.map(_.supportsPutFull.max)
|
||||
val maxPutPartials = managers.map(_.supportsPutPartial.max)
|
||||
val maxHints = managers.map(m => if (m.supportsHint) maxDownSize else 0)
|
||||
|
||||
// We assume that the request is valid => size 0 is impossible
|
||||
val lgMinSize = UInt(log2Ceil(minSize))
|
||||
val maxLgArithmetics = maxArithmetics.map(m => if (m == 0) lgMinSize else UInt(log2Ceil(m)))
|
||||
val maxLgLogicals = maxLogicals .map(m => if (m == 0) lgMinSize else UInt(log2Ceil(m)))
|
||||
val maxLgGets = maxGets .map(m => if (m == 0) lgMinSize else UInt(log2Ceil(m)))
|
||||
val maxLgPutFulls = maxPutFulls .map(m => if (m == 0) lgMinSize else UInt(log2Ceil(m)))
|
||||
val maxLgPutPartials = maxPutPartials.map(m => if (m == 0) lgMinSize else UInt(log2Ceil(m)))
|
||||
val maxLgHints = maxHints .map(m => if (m == 0) lgMinSize else UInt(log2Ceil(m)))
|
||||
|
||||
// Make the request repeatable
|
||||
val repeater = Module(new Repeater(in.a.bits))
|
||||
repeater.io.enq <> in.a
|
||||
val in_a = repeater.io.deq
|
||||
|
||||
// If this is infront of a single manager, these become constants
|
||||
val find = manager.findFast(edgeIn.address(in_a.bits))
|
||||
val maxLgArithmetic = Mux1H(find, maxLgArithmetics)
|
||||
val maxLgLogical = Mux1H(find, maxLgLogicals)
|
||||
val maxLgGet = Mux1H(find, maxLgGets)
|
||||
val maxLgPutFull = Mux1H(find, maxLgPutFulls)
|
||||
val maxLgPutPartial = Mux1H(find, maxLgPutPartials)
|
||||
val maxLgHint = Mux1H(find, maxLgHints)
|
||||
|
||||
val limit = if (alwaysMin) lgMinSize else
|
||||
MuxLookup(in_a.bits.opcode, lgMinSize, Array(
|
||||
TLMessages.PutFullData -> maxLgPutFull,
|
||||
TLMessages.PutPartialData -> maxLgPutPartial,
|
||||
TLMessages.ArithmeticData -> maxLgArithmetic,
|
||||
TLMessages.LogicalData -> maxLgLogical,
|
||||
TLMessages.Get -> maxLgGet,
|
||||
TLMessages.Hint -> maxLgHint))
|
||||
|
||||
val aOrig = in_a.bits.size
|
||||
val aFrag = Mux(aOrig > limit, limit, aOrig)
|
||||
val aOrigOH1 = UIntToOH1(aOrig, log2Ceil(maxSize))
|
||||
val aFragOH1 = UIntToOH1(aFrag, log2Up(maxDownSize))
|
||||
val aHasData = node.edgesIn(0).hasData(in_a.bits)
|
||||
val aMask = Mux(aHasData, UInt(0), aFragOH1)
|
||||
|
||||
val gennum = RegInit(UInt(0, width = counterBits))
|
||||
val aFirst = gennum === UInt(0)
|
||||
val old_gennum1 = Mux(aFirst, aOrigOH1 >> log2Ceil(beatBytes), gennum - UInt(1))
|
||||
val new_gennum = ~(~old_gennum1 | (aMask >> log2Ceil(beatBytes))) // ~(~x|y) is width safe
|
||||
val aFragnum = ~(~(old_gennum1 >> log2Ceil(minSize/beatBytes)) | (aFragOH1 >> log2Ceil(minSize)))
|
||||
val aLast = aFragnum === UInt(0)
|
||||
|
||||
when (out.a.fire()) { gennum := new_gennum }
|
||||
|
||||
// We need to alternate bits by source to handle the 1.999 txns inflight per Id
|
||||
val toggleBitOpt = if (!earlyAck) None else {
|
||||
val state = Reg(UInt(width = edgeIn.client.endSourceId))
|
||||
val toggle = Wire(init = UInt(0, width = edgeIn.client.endSourceId))
|
||||
when (in_a.fire() && aLast) { toggle := UIntToOH(in_a.bits.source) }
|
||||
state := state ^ toggle
|
||||
Some(state(in_a.bits.source))
|
||||
}
|
||||
|
||||
repeater.io.repeat := !aHasData && aFragnum =/= UInt(0)
|
||||
out.a <> in_a
|
||||
out.a.bits.address := in_a.bits.address | ~(old_gennum1 << log2Ceil(beatBytes) | ~aOrigOH1 | aFragOH1 | UInt(minSize-1))
|
||||
out.a.bits.source := Cat(Seq(in_a.bits.source) ++ toggleBitOpt.toList ++ Seq(aFragnum))
|
||||
out.a.bits.size := aFrag
|
||||
|
||||
// Optimize away some of the Repeater's registers
|
||||
assert (!repeater.io.full || !aHasData)
|
||||
out.a.bits.data := in.a.bits.data
|
||||
val fullMask = UInt((BigInt(1) << beatBytes) - 1)
|
||||
assert (!repeater.io.full || in_a.bits.mask === fullMask)
|
||||
out.a.bits.mask := Mux(repeater.io.full, fullMask, in.a.bits.mask)
|
||||
|
||||
// Tie off unused channels
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLFragmenter
|
||||
{
|
||||
// applied to the TL source node; y.node := TLFragmenter(x.node, 256, 4)
|
||||
def apply(minSize: Int, maxSize: Int, alwaysMin: Boolean = false, earlyAck: Boolean = false)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val fragmenter = LazyModule(new TLFragmenter(minSize, maxSize, alwaysMin, earlyAck))
|
||||
fragmenter.node := x
|
||||
fragmenter.node
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
class TLRAMFragmenter(ramBeatBytes: Int, maxSize: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val model = LazyModule(new TLRAMModel("Fragmenter"))
|
||||
val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff), beatBytes = ramBeatBytes))
|
||||
|
||||
model.node := fuzz.node
|
||||
ram.node :=
|
||||
TLDelayer(0.1)(
|
||||
TLBuffer(BufferParams.flow)(
|
||||
TLDelayer(0.1)(
|
||||
TLFragmenter(ramBeatBytes, maxSize, earlyAck = true)(
|
||||
TLDelayer(0.1)(
|
||||
TLBuffer(BufferParams.flow)(
|
||||
TLFragmenter(ramBeatBytes, maxSize/2)(
|
||||
TLDelayer(0.1)(
|
||||
TLBuffer(BufferParams.flow)(
|
||||
model.node)))))))))
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzz.module.io.finished
|
||||
}
|
||||
}
|
||||
|
||||
class TLRAMFragmenterTest(ramBeatBytes: Int, maxSize: Int, txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new TLRAMFragmenter(ramBeatBytes,maxSize,txns)).module).io.finished
|
||||
}
|
260
src/main/scala/tilelink/Fuzzer.scala
Normal file
260
src/main/scala/tilelink/Fuzzer.scala
Normal file
@ -0,0 +1,260 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class IDMapGenerator(numIds: Int) extends Module {
|
||||
val w = log2Up(numIds)
|
||||
val io = new Bundle {
|
||||
val free = Decoupled(UInt(width = w)).flip
|
||||
val alloc = Decoupled(UInt(width = w))
|
||||
}
|
||||
|
||||
// True indicates that the id is available
|
||||
val bitmap = RegInit(UInt((BigInt(1) << numIds) - 1, width = numIds))
|
||||
|
||||
io.free.ready := Bool(true)
|
||||
assert (!io.free.valid || !bitmap(io.free.bits)) // No double freeing
|
||||
|
||||
val select = ~(leftOR(bitmap) << 1) & bitmap
|
||||
io.alloc.bits := OHToUInt(select)
|
||||
io.alloc.valid := bitmap.orR()
|
||||
|
||||
val clr = Wire(init = UInt(0, width = numIds))
|
||||
when (io.alloc.fire()) { clr := UIntToOH(io.alloc.bits) }
|
||||
|
||||
val set = Wire(init = UInt(0, width = numIds))
|
||||
when (io.free.fire()) { set := UIntToOH(io.free.bits) }
|
||||
|
||||
bitmap := (bitmap & ~clr) | set
|
||||
}
|
||||
|
||||
object LFSR64
|
||||
{
|
||||
def apply(increment: Bool = Bool(true)): UInt =
|
||||
{
|
||||
val wide = 64
|
||||
val lfsr = Reg(UInt(width = wide)) // random initial value based on simulation seed
|
||||
val xor = lfsr(0) ^ lfsr(1) ^ lfsr(3) ^ lfsr(4)
|
||||
when (increment) {
|
||||
lfsr := Mux(lfsr === UInt(0), UInt(1), Cat(xor, lfsr(wide-1,1)))
|
||||
}
|
||||
lfsr
|
||||
}
|
||||
}
|
||||
|
||||
trait HasNoiseMakerIO
|
||||
{
|
||||
val io = new Bundle {
|
||||
val inc = Bool(INPUT)
|
||||
val random = UInt(OUTPUT)
|
||||
}
|
||||
}
|
||||
|
||||
class LFSRNoiseMaker(wide: Int) extends Module with HasNoiseMakerIO
|
||||
{
|
||||
val lfsrs = Seq.fill((wide+63)/64) { LFSR64(io.inc) }
|
||||
io.random := Cat(lfsrs)(wide-1,0)
|
||||
}
|
||||
|
||||
object LFSRNoiseMaker {
|
||||
def apply(wide: Int, increment: Bool = Bool(true)): UInt = {
|
||||
val nm = Module(new LFSRNoiseMaker(wide))
|
||||
nm.io.inc := increment
|
||||
nm.io.random
|
||||
}
|
||||
}
|
||||
|
||||
/** TLFuzzer drives test traffic over TL2 links. It generates a sequence of randomized
|
||||
* requests, and issues legal ones into the DUT. TODO: Currently the fuzzer only generates
|
||||
* memory operations, not permissions transfers.
|
||||
* @param nOperations is the total number of operations that the fuzzer must complete for the test to pass
|
||||
* @param inFlight is the number of operations that can be in-flight to the DUT concurrently
|
||||
* @param noiseMaker is a function that supplies a random UInt of a given width every time inc is true
|
||||
*/
|
||||
class TLFuzzer(
|
||||
nOperations: Int,
|
||||
inFlight: Int = 32,
|
||||
noiseMaker: (Int, Bool, Int) => UInt = {
|
||||
(wide: Int, increment: Bool, abs_values: Int) =>
|
||||
LFSRNoiseMaker(wide=wide, increment=increment)
|
||||
},
|
||||
noModify: Boolean = false,
|
||||
overrideAddress: Option[AddressSet] = None)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLClientNode(TLClientParameters(
|
||||
name = "Fuzzer",
|
||||
sourceId = IdRange(0,inFlight)))
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val out = node.bundleOut
|
||||
val finished = Bool()
|
||||
}
|
||||
|
||||
val out = io.out(0)
|
||||
val edge = node.edgesOut(0)
|
||||
|
||||
// Extract useful parameters from the TL edge
|
||||
val maxTransfer = edge.manager.maxTransfer
|
||||
val beatBytes = edge.manager.beatBytes
|
||||
val maxLgBeats = log2Up(maxTransfer/beatBytes)
|
||||
val addressBits = log2Up(overrideAddress.map(_.max).getOrElse(edge.manager.maxAddress))
|
||||
val sizeBits = edge.bundle.sizeBits
|
||||
val dataBits = edge.bundle.dataBits
|
||||
|
||||
// Progress through operations
|
||||
val num_reqs = Reg(init = UInt(nOperations, log2Up(nOperations+1)))
|
||||
val num_resps = Reg(init = UInt(nOperations, log2Up(nOperations+1)))
|
||||
if (nOperations>0) {
|
||||
io.finished := num_resps === UInt(0)
|
||||
} else {
|
||||
io.finished := Bool(false)
|
||||
}
|
||||
|
||||
// Progress within each operation
|
||||
val a = out.a.bits
|
||||
val (a_first, a_last, req_done) = edge.firstlast(out.a)
|
||||
|
||||
val d = out.d.bits
|
||||
val (d_first, d_last, resp_done) = edge.firstlast(out.d)
|
||||
|
||||
// Source ID generation
|
||||
val idMap = Module(new IDMapGenerator(inFlight))
|
||||
val alloc = Queue.irrevocable(idMap.io.alloc, 1, pipe = true)
|
||||
val src = alloc.bits
|
||||
alloc.ready := req_done
|
||||
idMap.io.free.valid := resp_done
|
||||
idMap.io.free.bits := out.d.bits.source
|
||||
|
||||
// Increment random number generation for the following subfields
|
||||
val inc = Wire(Bool())
|
||||
val inc_beat = Wire(Bool())
|
||||
val arth_op_3 = noiseMaker(3, inc, 0)
|
||||
val arth_op = Mux(arth_op_3 > UInt(4), UInt(4), arth_op_3)
|
||||
val log_op = noiseMaker(2, inc, 0)
|
||||
val amo_size = UInt(2) + noiseMaker(1, inc, 0) // word or dword
|
||||
val size = noiseMaker(sizeBits, inc, 0)
|
||||
val rawAddr = noiseMaker(addressBits, inc, 2)
|
||||
val addr = overrideAddress.map(_.legalize(rawAddr)).getOrElse(rawAddr) & ~UIntToOH1(size, addressBits)
|
||||
val mask = noiseMaker(beatBytes, inc_beat, 2) & edge.mask(addr, size)
|
||||
val data = noiseMaker(dataBits, inc_beat, 2)
|
||||
|
||||
// Actually generate specific TL messages when it is legal to do so
|
||||
val (glegal, gbits) = edge.Get(src, addr, size)
|
||||
val (pflegal, pfbits) = if(edge.manager.anySupportPutFull) {
|
||||
edge.Put(src, addr, size, data)
|
||||
} else { (glegal, gbits) }
|
||||
val (pplegal, ppbits) = if(edge.manager.anySupportPutPartial) {
|
||||
edge.Put(src, addr, size, data, mask)
|
||||
} else { (glegal, gbits) }
|
||||
val (alegal, abits) = if(edge.manager.anySupportArithmetic) {
|
||||
edge.Arithmetic(src, addr, size, data, arth_op)
|
||||
} else { (glegal, gbits) }
|
||||
val (llegal, lbits) = if(edge.manager.anySupportLogical) {
|
||||
edge.Logical(src, addr, size, data, log_op)
|
||||
} else { (glegal, gbits) }
|
||||
val (hlegal, hbits) = if(edge.manager.anySupportHint) {
|
||||
edge.Hint(src, addr, size, UInt(0))
|
||||
} else { (glegal, gbits) }
|
||||
|
||||
val legal_dest = edge.manager.containsSafe(addr)
|
||||
|
||||
// Pick a specific message to try to send
|
||||
val a_type_sel = noiseMaker(3, inc, 0)
|
||||
|
||||
val legal = legal_dest && MuxLookup(a_type_sel, glegal, Seq(
|
||||
UInt("b000") -> glegal,
|
||||
UInt("b001") -> (pflegal && !Bool(noModify)),
|
||||
UInt("b010") -> (pplegal && !Bool(noModify)),
|
||||
UInt("b011") -> (alegal && !Bool(noModify)),
|
||||
UInt("b100") -> (llegal && !Bool(noModify)),
|
||||
UInt("b101") -> hlegal))
|
||||
|
||||
val bits = MuxLookup(a_type_sel, gbits, Seq(
|
||||
UInt("b000") -> gbits,
|
||||
UInt("b001") -> pfbits,
|
||||
UInt("b010") -> ppbits,
|
||||
UInt("b011") -> abits,
|
||||
UInt("b100") -> lbits,
|
||||
UInt("b101") -> hbits))
|
||||
|
||||
// Wire both the used and un-used channel signals
|
||||
if (nOperations>0) {
|
||||
out.a.valid := legal && alloc.valid && num_reqs =/= UInt(0)
|
||||
} else {
|
||||
out.a.valid := legal && alloc.valid
|
||||
}
|
||||
out.a.bits := bits
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.d.ready := Bool(true)
|
||||
out.e.valid := Bool(false)
|
||||
|
||||
// Increment the various progress-tracking states
|
||||
inc := !legal || req_done
|
||||
inc_beat := !legal || out.a.fire()
|
||||
|
||||
if (nOperations>0) {
|
||||
when (out.a.fire() && a_last) {
|
||||
num_reqs := num_reqs - UInt(1)
|
||||
}
|
||||
|
||||
when (out.d.fire() && d_last) {
|
||||
num_resps := num_resps - UInt(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesizeable integration test */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
class TLFuzzRAM(txns: Int)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val model = LazyModule(new TLRAMModel("TLFuzzRAM"))
|
||||
val ram = LazyModule(new TLRAM(AddressSet(0x800, 0x7ff)))
|
||||
val ram2 = LazyModule(new TLRAM(AddressSet(0, 0x3ff), beatBytes = 16))
|
||||
val gpio = LazyModule(new RRTest1(0x400))
|
||||
val xbar = LazyModule(new TLXbar)
|
||||
val xbar2= LazyModule(new TLXbar)
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val cross = LazyModule(new TLAsyncCrossing)
|
||||
|
||||
model.node := fuzz.node
|
||||
xbar2.node := TLAtomicAutomata()(model.node)
|
||||
ram2.node := TLFragmenter(16, 256)(xbar2.node)
|
||||
xbar.node := TLWidthWidget(16)(TLHintHandler()(xbar2.node))
|
||||
cross.node := TLFragmenter(4, 256)(TLBuffer()(xbar.node))
|
||||
val monitor = (ram.node := cross.node)
|
||||
gpio.node := TLFragmenter(4, 32)(TLBuffer()(xbar.node))
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzz.module.io.finished
|
||||
|
||||
// Shove the RAM into another clock domain
|
||||
val clocks = Module(new Pow2ClockDivider(2))
|
||||
ram.module.clock := clocks.io.clock_out
|
||||
|
||||
// ... and safely cross TL2 into it
|
||||
cross.module.io.in_clock := clock
|
||||
cross.module.io.in_reset := reset
|
||||
cross.module.io.out_clock := clocks.io.clock_out
|
||||
cross.module.io.out_reset := reset
|
||||
|
||||
// Push the Monitor into the right clock domain
|
||||
monitor.foreach { m =>
|
||||
m.module.clock := clocks.io.clock_out
|
||||
m.module.reset := reset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TLFuzzRAMTest(txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
val dut = Module(LazyModule(new TLFuzzRAM(txns)).module)
|
||||
io.finished := dut.io.finished
|
||||
}
|
128
src/main/scala/tilelink/HintHandler.scala
Normal file
128
src/main/scala/tilelink/HintHandler.scala
Normal file
@ -0,0 +1,128 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import scala.math.min
|
||||
|
||||
// Acks Hints for managers that don't support them or Acks all Hints if !passthrough
|
||||
class TLHintHandler(supportManagers: Boolean = true, supportClients: Boolean = false, passthrough: Boolean = true)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLAdapterNode(
|
||||
clientFn = { c => if (!supportClients) c else c.copy(minLatency = min(1, c.minLatency), clients = c.clients .map(_.copy(supportsHint = TransferSizes(1, c.maxTransfer)))) },
|
||||
managerFn = { m => if (!supportManagers) m else m.copy(minLatency = min(1, m.minLatency), managers = m.managers.map(_.copy(supportsHint = TransferSizes(1, m.maxTransfer)))) })
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
// Don't add support for clients if there is no BCE channel
|
||||
val bce = edgeOut.manager.anySupportAcquireB && edgeIn.client.anySupportProbe
|
||||
require (!supportClients || bce)
|
||||
|
||||
// Does it even make sense to add the HintHandler?
|
||||
val smartClients = edgeIn.client.clients.map(_.supportsHint.max == edgeIn.client.maxTransfer).reduce(_&&_)
|
||||
val smartManagers = edgeOut.manager.managers.map(_.supportsHint.max == edgeOut.manager.maxTransfer).reduce(_&&_)
|
||||
|
||||
if (supportManagers && !(passthrough && smartManagers)) {
|
||||
val address = edgeIn.address(in.a.bits)
|
||||
val handleA = if (passthrough) !edgeOut.manager.supportsHintFast(address, edgeIn.size(in.a.bits)) else Bool(true)
|
||||
val hintBitsAtA = handleA && in.a.bits.opcode === TLMessages.Hint
|
||||
val hint = Wire(out.d)
|
||||
|
||||
hint.valid := in.a.valid && hintBitsAtA
|
||||
out.a.valid := in.a.valid && !hintBitsAtA
|
||||
in.a.ready := Mux(hintBitsAtA, hint.ready, out.a.ready)
|
||||
|
||||
hint.bits := edgeIn.HintAck(in.a.bits, UInt(0))
|
||||
out.a.bits := in.a.bits
|
||||
|
||||
TLArbiter(TLArbiter.lowestIndexFirst)(in.d, (edgeOut.numBeats1(out.d.bits), out.d), (UInt(0), Queue(hint, 1)))
|
||||
} else {
|
||||
out.a.valid := in.a.valid
|
||||
in.a.ready := out.a.ready
|
||||
out.a.bits := in.a.bits
|
||||
|
||||
in.d.valid := out.d.valid
|
||||
out.d.ready := in.d.ready
|
||||
in.d.bits := out.d.bits
|
||||
}
|
||||
|
||||
if (supportClients && !(passthrough && smartClients)) {
|
||||
val handleB = if (passthrough) !edgeIn.client.supportsHint(out.b.bits.source, edgeOut.size(out.b.bits)) else Bool(true)
|
||||
val hintBitsAtB = handleB && out.b.bits.opcode === TLMessages.Hint
|
||||
val hint = Wire(in.c)
|
||||
|
||||
hint.valid := out.b.valid && hintBitsAtB
|
||||
in.b.valid := out.b.valid && !hintBitsAtB
|
||||
out.b.ready := Mux(hintBitsAtB, hint.ready, in.b.ready)
|
||||
|
||||
hint.bits := edgeOut.HintAck(out.b.bits)
|
||||
in.b.bits := out.b.bits
|
||||
|
||||
TLArbiter(TLArbiter.lowestIndexFirst)(out.c, (edgeIn.numBeats1(in.c.bits), in.c), (UInt(0), Queue(hint, 1)))
|
||||
} else if (bce) {
|
||||
in.b.valid := out.b.valid
|
||||
out.b.ready := in.b.ready
|
||||
in.b.bits := out.b.bits
|
||||
|
||||
out.c.valid := in.c.valid
|
||||
in.c.ready := out.c.ready
|
||||
out.c.bits := in.c.bits
|
||||
} else {
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
}
|
||||
|
||||
if (bce) {
|
||||
// Pass E through unchanged
|
||||
out.e.valid := in.e.valid
|
||||
in.e.ready := out.e.ready
|
||||
out.e.bits := in.e.bits
|
||||
} else {
|
||||
in.e.ready := Bool(true)
|
||||
out.e.valid := Bool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLHintHandler
|
||||
{
|
||||
// applied to the TL source node; y.node := TLHintHandler(x.node)
|
||||
def apply(supportManagers: Boolean = true, supportClients: Boolean = false, passthrough: Boolean = true)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val hints = LazyModule(new TLHintHandler(supportManagers, supportClients, passthrough))
|
||||
hints.node := x
|
||||
hints.node
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
//TODO ensure handler will pass through hints to clients that can handle them themselves
|
||||
|
||||
class TLRAMHintHandler(txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val model = LazyModule(new TLRAMModel("HintHandler"))
|
||||
val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff)))
|
||||
|
||||
model.node := fuzz.node
|
||||
ram.node := TLFragmenter(4, 256)(TLDelayer(0.1)(TLHintHandler()(TLDelayer(0.1)(model.node))))
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzz.module.io.finished
|
||||
}
|
||||
}
|
||||
|
||||
class TLRAMHintHandlerTest(txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new TLRAMHintHandler(txns)).module).io.finished
|
||||
}
|
145
src/main/scala/tilelink/IntNodes.scala
Normal file
145
src/main/scala/tilelink/IntNodes.scala
Normal file
@ -0,0 +1,145 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.math.max
|
||||
|
||||
// A potentially empty half-open range; [start, end)
|
||||
case class IntRange(start: Int, end: Int)
|
||||
{
|
||||
require (start >= 0)
|
||||
require (start <= end)
|
||||
def size = end - start
|
||||
def overlaps(x: IntRange) = start < x.end && x.start < end
|
||||
def offset(x: Int) = IntRange(x+start, x+end)
|
||||
}
|
||||
|
||||
object IntRange
|
||||
{
|
||||
implicit def apply(end: Int): IntRange = apply(0, end)
|
||||
}
|
||||
|
||||
case class IntSourceParameters(
|
||||
range: IntRange,
|
||||
resources: Seq[Resource] = Seq(),
|
||||
nodePath: Seq[BaseNode] = Seq())
|
||||
{
|
||||
val name = nodePath.lastOption.map(_.lazyModule.name).getOrElse("disconnected")
|
||||
}
|
||||
|
||||
case class IntSinkParameters(
|
||||
nodePath: Seq[BaseNode] = Seq())
|
||||
{
|
||||
val name = nodePath.lastOption.map(_.lazyModule.name).getOrElse("disconnected")
|
||||
}
|
||||
|
||||
case class IntSourcePortParameters(sources: Seq[IntSourceParameters])
|
||||
{
|
||||
val num = sources.map(_.range.size).sum
|
||||
// The interrupts mapping must not overlap
|
||||
sources.map(_.range).combinations(2).foreach { case Seq(a, b) => require (!a.overlaps(b)) }
|
||||
// The interrupts must perfectly cover the range
|
||||
require (sources.isEmpty || sources.map(_.range.end).max == num)
|
||||
}
|
||||
object IntSourcePortSimple
|
||||
{
|
||||
def apply(num: Int = 1, ports: Int = 1, sources: Int = 1, resources: Seq[Resource] = Nil) =
|
||||
if (num == 0) Nil else
|
||||
Seq.fill(ports)(IntSourcePortParameters(Seq.fill(sources)(IntSourceParameters(range = IntRange(0, num), resources = resources))))
|
||||
}
|
||||
|
||||
case class IntSinkPortParameters(sinks: Seq[IntSinkParameters])
|
||||
object IntSinkPortSimple
|
||||
{
|
||||
def apply(ports: Int = 1, sinks: Int = 1) =
|
||||
Seq.fill(ports)(IntSinkPortParameters(Seq.fill(sinks)(IntSinkParameters())))
|
||||
}
|
||||
|
||||
case class IntEdge(source: IntSourcePortParameters, sink: IntSinkPortParameters)
|
||||
|
||||
object IntImp extends NodeImp[IntSourcePortParameters, IntSinkPortParameters, IntEdge, IntEdge, Vec[Bool]]
|
||||
{
|
||||
def edgeO(pd: IntSourcePortParameters, pu: IntSinkPortParameters): IntEdge = IntEdge(pd, pu)
|
||||
def edgeI(pd: IntSourcePortParameters, pu: IntSinkPortParameters): IntEdge = IntEdge(pd, pu)
|
||||
def bundleO(eo: IntEdge): Vec[Bool] = Vec(eo.source.num, Bool())
|
||||
def bundleI(ei: IntEdge): Vec[Bool] = Vec(ei.source.num, Bool())
|
||||
|
||||
def colour = "#0000ff" // blue
|
||||
override def reverse = true
|
||||
override def labelI(ei: IntEdge) = ei.source.sources.map(_.range.size).sum.toString
|
||||
override def labelO(eo: IntEdge) = eo.source.sources.map(_.range.size).sum.toString
|
||||
|
||||
def connect(bo: => Vec[Bool], bi: => Vec[Bool], ei: => IntEdge)(implicit p: Parameters, sourceInfo: SourceInfo): (Option[LazyModule], () => Unit) = {
|
||||
(None, () => {
|
||||
// Cannot use bulk connect, because the widths could differ
|
||||
(bo zip bi) foreach { case (o, i) => i := o }
|
||||
})
|
||||
}
|
||||
|
||||
override def mixO(pd: IntSourcePortParameters, node: OutwardNode[IntSourcePortParameters, IntSinkPortParameters, Vec[Bool]]): IntSourcePortParameters =
|
||||
pd.copy(sources = pd.sources.map { s => s.copy (nodePath = node +: s.nodePath) })
|
||||
override def mixI(pu: IntSinkPortParameters, node: InwardNode[IntSourcePortParameters, IntSinkPortParameters, Vec[Bool]]): IntSinkPortParameters =
|
||||
pu.copy(sinks = pu.sinks.map { s => s.copy (nodePath = node +: s.nodePath) })
|
||||
}
|
||||
|
||||
case class IntIdentityNode() extends IdentityNode(IntImp)
|
||||
case class IntSourceNode(portParams: Seq[IntSourcePortParameters]) extends SourceNode(IntImp)(portParams)
|
||||
case class IntSinkNode(portParams: Seq[IntSinkPortParameters]) extends SinkNode(IntImp)(portParams)
|
||||
|
||||
case class IntNexusNode(
|
||||
sourceFn: Seq[IntSourcePortParameters] => IntSourcePortParameters,
|
||||
sinkFn: Seq[IntSinkPortParameters] => IntSinkPortParameters,
|
||||
numSourcePorts: Range.Inclusive = 0 to 128,
|
||||
numSinkPorts: Range.Inclusive = 0 to 128)
|
||||
extends NexusNode(IntImp)(sourceFn, sinkFn, numSourcePorts, numSinkPorts)
|
||||
|
||||
case class IntOutputNode() extends OutputNode(IntImp)
|
||||
case class IntInputNode() extends InputNode(IntImp)
|
||||
|
||||
case class IntBlindOutputNode(portParams: Seq[IntSinkPortParameters]) extends BlindOutputNode(IntImp)(portParams)
|
||||
case class IntBlindInputNode(portParams: Seq[IntSourcePortParameters]) extends BlindInputNode(IntImp)(portParams)
|
||||
|
||||
case class IntInternalOutputNode(portParams: Seq[IntSinkPortParameters]) extends InternalOutputNode(IntImp)(portParams)
|
||||
case class IntInternalInputNode(portParams: Seq[IntSourcePortParameters]) extends InternalInputNode(IntImp)(portParams)
|
||||
|
||||
class IntXbar()(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val intnode = IntNexusNode(
|
||||
sinkFn = { _ => IntSinkPortParameters(Seq(IntSinkParameters())) },
|
||||
sourceFn = { seq =>
|
||||
IntSourcePortParameters((seq zip seq.map(_.num).scanLeft(0)(_+_).init).map {
|
||||
case (s, o) => s.sources.map(z => z.copy(range = z.range.offset(o)))
|
||||
}.flatten)
|
||||
})
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = intnode.bundleIn
|
||||
val out = intnode.bundleOut
|
||||
}
|
||||
|
||||
val cat = (intnode.edgesIn zip io.in).map{ case (e, i) => i.take(e.source.num) }.flatten
|
||||
io.out.foreach { _ := cat }
|
||||
}
|
||||
}
|
||||
|
||||
class IntXing(sync: Int = 3)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val intnode = IntIdentityNode()
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = intnode.bundleIn
|
||||
val out = intnode.bundleOut
|
||||
}
|
||||
|
||||
(io.in zip io.out) foreach { case (in, out) =>
|
||||
out := (0 to sync).foldLeft(in) { case (a, _) => RegNext(a) }
|
||||
}
|
||||
}
|
||||
}
|
85
src/main/scala/tilelink/Isolation.scala
Normal file
85
src/main/scala/tilelink/Isolation.scala
Normal file
@ -0,0 +1,85 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util.AsyncBundle
|
||||
|
||||
// READ the comments in the TLIsolation object before you instantiate this module
|
||||
class TLIsolation(fOut: (Bool, UInt) => UInt, fIn: (Bool, UInt) => UInt)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLAsyncIdentityNode()
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
val iso_out = Bool(INPUT) // Isolate from client to manager
|
||||
val iso_in = Bool(INPUT) // Isolate from manager to client
|
||||
}
|
||||
|
||||
def ISOo[T <: Data](x: T): T = x.fromBits(fOut(io.iso_out, x.asUInt))
|
||||
def ISOi[T <: Data](x: T): T = x.fromBits(fIn (io.iso_in, x.asUInt))
|
||||
|
||||
def ABo[T <: Data](x: AsyncBundle[T], y: AsyncBundle[T]) {
|
||||
x.mem := ISOo(y.mem)
|
||||
x.widx := ISOo(y.widx)
|
||||
x.widx_valid := ISOo(y.widx_valid)
|
||||
x.source_reset_n := ISOo(y.source_reset_n)
|
||||
y.ridx := ISOi(x.ridx)
|
||||
y.ridx_valid := ISOi(x.ridx_valid)
|
||||
y.sink_reset_n := ISOi(x.sink_reset_n)
|
||||
}
|
||||
|
||||
def ABi[T <: Data](x: AsyncBundle[T], y: AsyncBundle[T]) {
|
||||
x.mem := ISOi(y.mem)
|
||||
x.widx := ISOi(y.widx)
|
||||
x.widx_valid := ISOi(y.widx_valid)
|
||||
x.source_reset_n := ISOi(y.source_reset_n)
|
||||
y.ridx := ISOo(x.ridx)
|
||||
y.ridx_valid := ISOo(x.ridx_valid)
|
||||
y.sink_reset_n := ISOo(x.sink_reset_n)
|
||||
}
|
||||
|
||||
def ABz[T <: Data](x: AsyncBundle[T], y: AsyncBundle[T]) {
|
||||
x.widx := UInt(0)
|
||||
x.widx_valid := Bool(false)
|
||||
x.source_reset_n := Bool(false)
|
||||
y.ridx := UInt(0)
|
||||
y.ridx_valid := Bool(false)
|
||||
y.sink_reset_n := Bool(false)
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
ABo(out.a, in .a)
|
||||
ABi(in .d, out.d)
|
||||
|
||||
if (edgeOut.manager.base.anySupportAcquireB && edgeOut.client.base.anySupportProbe) {
|
||||
ABi(in .b, out.b)
|
||||
ABo(out.c, in .c)
|
||||
ABo(out.e, in .e)
|
||||
} else {
|
||||
ABz(in .b, out.b)
|
||||
ABz(out.c, in .c)
|
||||
ABz(out.e, in .e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLIsolation
|
||||
{
|
||||
// applied to the TL source node; y.node := TLIsolation(fOut, fIn)(x.node)
|
||||
// f* should insert an isolation gate between the input UInt and its result
|
||||
// fOut is applied to data flowing from client to manager
|
||||
// fIn is applied to data flowing from manager to client
|
||||
// **** WARNING: the isolation functions must bring the values to 0 ****
|
||||
def apply(fOut: (Bool, UInt) => UInt, fIn: (Bool, UInt) => UInt)(x: TLAsyncOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): (TLAsyncOutwardNode, () => (Bool, Bool)) = {
|
||||
val iso = LazyModule(new TLIsolation(fOut, fIn))
|
||||
iso.node := x
|
||||
(iso.node, () => (iso.module.io.iso_out, iso.module.io.iso_in))
|
||||
}
|
||||
}
|
166
src/main/scala/tilelink/Metadata.scala
Normal file
166
src/main/scala/tilelink/Metadata.scala
Normal file
@ -0,0 +1,166 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
// See LICENSE.Berkeley for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.rocket.constants.MemoryOpConstants
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
object ClientStates {
|
||||
val width = 2
|
||||
|
||||
def Nothing = UInt(0, width)
|
||||
def Branch = UInt(1, width)
|
||||
def Trunk = UInt(2, width)
|
||||
def Dirty = UInt(3, width)
|
||||
|
||||
def hasReadPermission(state: UInt): Bool = state > Nothing
|
||||
def hasWritePermission(state: UInt): Bool = state > Branch
|
||||
}
|
||||
|
||||
object MemoryOpCategories extends MemoryOpConstants {
|
||||
def wr = Cat(Bool(true), Bool(true)) // Op actually writes
|
||||
def wi = Cat(Bool(false), Bool(true)) // Future op will write
|
||||
def rd = Cat(Bool(false), Bool(false)) // Op only reads
|
||||
|
||||
def categorize(cmd: UInt): UInt = {
|
||||
val cat = Cat(isWrite(cmd), isWriteIntent(cmd))
|
||||
//assert(cat.isOneOf(wr,wi,rd), "Could not categorize command.")
|
||||
cat
|
||||
}
|
||||
}
|
||||
|
||||
/** Stores the client-side coherence information,
|
||||
* such as permissions on the data and whether the data is dirty.
|
||||
* Its API can be used to make TileLink messages in response to
|
||||
* memory operations, cache control oeprations, or Probe messages.
|
||||
*/
|
||||
class ClientMetadata extends Bundle {
|
||||
/** Actual state information stored in this bundle */
|
||||
val state = UInt(width = ClientStates.width)
|
||||
|
||||
/** Metadata equality */
|
||||
def ===(rhs: UInt): Bool = state === rhs
|
||||
def ===(rhs: ClientMetadata): Bool = state === rhs.state
|
||||
def =/=(rhs: ClientMetadata): Bool = !this.===(rhs)
|
||||
|
||||
/** Is the block's data present in this cache */
|
||||
def isValid(dummy: Int = 0): Bool = state > ClientStates.Nothing
|
||||
|
||||
/** Determine whether this cmd misses, and the new state (on hit) or param to be sent (on miss) */
|
||||
private def growStarter(cmd: UInt): (Bool, UInt) = {
|
||||
import MemoryOpCategories._
|
||||
import TLPermissions._
|
||||
import ClientStates._
|
||||
val c = categorize(cmd)
|
||||
MuxTLookup(Cat(c, state), (Bool(false), UInt(0)), Seq(
|
||||
//(effect, am now) -> (was a hit, next)
|
||||
Cat(rd, Dirty) -> (Bool(true), Dirty),
|
||||
Cat(rd, Trunk) -> (Bool(true), Trunk),
|
||||
Cat(rd, Branch) -> (Bool(true), Branch),
|
||||
Cat(wi, Dirty) -> (Bool(true), Dirty),
|
||||
Cat(wi, Trunk) -> (Bool(true), Trunk),
|
||||
Cat(wr, Dirty) -> (Bool(true), Dirty),
|
||||
Cat(wr, Trunk) -> (Bool(true), Dirty),
|
||||
//(effect, am now) -> (was a miss, param)
|
||||
Cat(rd, Nothing) -> (Bool(false), NtoB),
|
||||
Cat(wi, Branch) -> (Bool(false), BtoT),
|
||||
Cat(wi, Nothing) -> (Bool(false), NtoT),
|
||||
Cat(wr, Branch) -> (Bool(false), BtoT),
|
||||
Cat(wr, Nothing) -> (Bool(false), NtoT)))
|
||||
}
|
||||
|
||||
/** Determine what state to go to after miss based on Grant param
|
||||
* For now, doesn't depend on state (which may have been Probed).
|
||||
*/
|
||||
private def growFinisher(cmd: UInt, param: UInt): UInt = {
|
||||
import MemoryOpCategories._
|
||||
import TLPermissions._
|
||||
import ClientStates._
|
||||
val c = categorize(cmd)
|
||||
//assert(c === rd || param === toT, "Client was expecting trunk permissions.")
|
||||
MuxLookup(Cat(c, param), Nothing, Seq(
|
||||
//(effect param) -> (next)
|
||||
Cat(rd, toB) -> Branch,
|
||||
Cat(rd, toT) -> Trunk,
|
||||
Cat(wi, toT) -> Trunk,
|
||||
Cat(wr, toT) -> Dirty))
|
||||
}
|
||||
|
||||
/** Does this cache have permissions on this block sufficient to perform op,
|
||||
* and what to do next (Acquire message param or updated metadata). */
|
||||
def onAccess(cmd: UInt): (Bool, UInt, ClientMetadata) = {
|
||||
val r = growStarter(cmd)
|
||||
(r._1, r._2, ClientMetadata(r._2))
|
||||
}
|
||||
|
||||
/** Does a secondary miss on the block require another Acquire message */
|
||||
def onSecondaryAccess(first_cmd: UInt, second_cmd: UInt): (Bool, Bool, UInt, ClientMetadata, UInt) = {
|
||||
import MemoryOpCategories._
|
||||
val r1 = growStarter(first_cmd)
|
||||
val r2 = growStarter(second_cmd)
|
||||
val needs_second_acq = isWriteIntent(second_cmd) && !isWriteIntent(first_cmd)
|
||||
val hit_again = r1._1 && r2._1
|
||||
val dirties = categorize(second_cmd) === wr
|
||||
val biggest_grow_param = Mux(dirties, r2._2, r1._2)
|
||||
val dirtiest_state = ClientMetadata(biggest_grow_param)
|
||||
val dirtiest_cmd = Mux(dirties, second_cmd, first_cmd)
|
||||
(needs_second_acq, hit_again, biggest_grow_param, dirtiest_state, dirtiest_cmd)
|
||||
}
|
||||
|
||||
/** Metadata change on a returned Grant */
|
||||
def onGrant(cmd: UInt, param: UInt): ClientMetadata = ClientMetadata(growFinisher(cmd, param))
|
||||
|
||||
/** Determine what state to go to based on Probe param */
|
||||
private def shrinkHelper(param: UInt): (Bool, UInt, UInt) = {
|
||||
import ClientStates._
|
||||
import TLPermissions._
|
||||
MuxTLookup(Cat(param, state), (Bool(false), UInt(0), UInt(0)), Seq(
|
||||
//(wanted, am now) -> (hasDirtyData resp, next)
|
||||
Cat(toT, Dirty) -> (Bool(true), TtoT, Trunk),
|
||||
Cat(toT, Trunk) -> (Bool(false), TtoT, Trunk),
|
||||
Cat(toT, Branch) -> (Bool(false), BtoB, Branch),
|
||||
Cat(toT, Nothing) -> (Bool(false), NtoN, Nothing),
|
||||
Cat(toB, Dirty) -> (Bool(true), TtoB, Branch),
|
||||
Cat(toB, Trunk) -> (Bool(false), TtoB, Branch), // Policy: Don't notify on clean downgrade
|
||||
Cat(toB, Branch) -> (Bool(false), BtoB, Branch),
|
||||
Cat(toB, Nothing) -> (Bool(false), BtoN, Nothing),
|
||||
Cat(toN, Dirty) -> (Bool(true), TtoN, Nothing),
|
||||
Cat(toN, Trunk) -> (Bool(false), TtoN, Nothing), // Policy: Don't notify on clean downgrade
|
||||
Cat(toN, Branch) -> (Bool(false), BtoN, Nothing), // Policy: Don't notify on clean downgrade
|
||||
Cat(toN, Nothing) -> (Bool(false), NtoN, Nothing)))
|
||||
}
|
||||
|
||||
/** Translate cache control cmds into Probe param */
|
||||
private def cmdToPermCap(cmd: UInt): UInt = {
|
||||
import MemoryOpCategories._
|
||||
import TLPermissions._
|
||||
MuxLookup(cmd, toN, Seq(
|
||||
M_FLUSH -> toN,
|
||||
M_PRODUCE -> toB,
|
||||
M_CLEAN -> toT))
|
||||
}
|
||||
|
||||
def onCacheControl(cmd: UInt): (Bool, UInt, ClientMetadata) = {
|
||||
val r = shrinkHelper(cmdToPermCap(cmd))
|
||||
(r._1, r._2, ClientMetadata(r._3))
|
||||
}
|
||||
|
||||
def onProbe(param: UInt): (Bool, UInt, ClientMetadata) = {
|
||||
val r = shrinkHelper(param)
|
||||
(r._1, r._2, ClientMetadata(r._3))
|
||||
}
|
||||
}
|
||||
|
||||
/** Factories for ClientMetadata, including on reset */
|
||||
object ClientMetadata {
|
||||
def apply(perm: UInt) = {
|
||||
val meta = Wire(new ClientMetadata)
|
||||
meta.state := perm
|
||||
meta
|
||||
}
|
||||
def onReset = ClientMetadata(ClientStates.Nothing)
|
||||
def maximum = ClientMetadata(ClientStates.Dirty)
|
||||
}
|
493
src/main/scala/tilelink/Monitor.scala
Normal file
493
src/main/scala/tilelink/Monitor.scala
Normal file
@ -0,0 +1,493 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.{SourceInfo, SourceLine}
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util.{HeterogeneousBag, PlusArg}
|
||||
|
||||
case class TLMonitorArgs(edge: () => Seq[TLEdge], sourceInfo: SourceInfo, p: Parameters)
|
||||
|
||||
abstract class TLMonitorBase(args: TLMonitorArgs) extends MonitorBase()(args.sourceInfo, args.p)
|
||||
{
|
||||
def legalize(bundle: TLBundleSnoop, edge: TLEdge, reset: Bool): Unit
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val edges = args.edge()
|
||||
val io = new Bundle {
|
||||
val in = HeterogeneousBag(edges.map(p => new TLBundleSnoop(p.bundle))).flip
|
||||
}
|
||||
|
||||
(edges zip io.in).foreach { case (e, in) => legalize(in, e, reset) }
|
||||
}
|
||||
}
|
||||
|
||||
class TLMonitor(args: TLMonitorArgs) extends TLMonitorBase(args)
|
||||
{
|
||||
def extra(implicit sourceInfo: SourceInfo) = {
|
||||
sourceInfo match {
|
||||
case SourceLine(filename, line, col) => s" (connected at $filename:$line:$col)"
|
||||
case _ => ""
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeFormatA(bundle: TLBundleA, edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
assert (TLMessages.isA(bundle.opcode), "'A' channel has invalid opcode" + extra)
|
||||
|
||||
// Reuse these subexpressions to save some firrtl lines
|
||||
val source_ok = edge.client.contains(bundle.source)
|
||||
val is_aligned = edge.isAligned(bundle.address, bundle.size)
|
||||
val mask = edge.full_mask(bundle)
|
||||
|
||||
when (bundle.opcode === TLMessages.Acquire) {
|
||||
assert (edge.manager.supportsAcquireBSafe(edge.address(bundle), bundle.size), "'A' channel carries Acquire type unsupported by manager" + extra)
|
||||
assert (edge.client.supportsProbe(edge.source(bundle), bundle.size), "'A' channel carries Acquire from a client which does not support Probe" + extra)
|
||||
assert (source_ok, "'A' channel Acquire carries invalid source ID" + extra)
|
||||
assert (bundle.size >= UInt(log2Ceil(edge.manager.beatBytes)), "'A' channel Acquire smaller than a beat" + extra)
|
||||
assert (is_aligned, "'A' channel Acquire address not aligned to size" + extra)
|
||||
assert (TLPermissions.isGrow(bundle.param), "'A' channel Acquire carries invalid grow param" + extra)
|
||||
assert (~bundle.mask === UInt(0), "'A' channel Acquire contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.Get) {
|
||||
assert (edge.manager.supportsGetSafe(edge.address(bundle), bundle.size), "'A' channel carries Get type unsupported by manager" + extra)
|
||||
assert (source_ok, "'A' channel Get carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'A' channel Get address not aligned to size" + extra)
|
||||
assert (bundle.param === UInt(0), "'A' channel Get carries invalid param" + extra)
|
||||
assert (bundle.mask === mask, "'A' channel Get contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.PutFullData) {
|
||||
assert (edge.manager.supportsPutFullSafe(edge.address(bundle), bundle.size), "'A' channel carries PutFull type unsupported by manager" + extra)
|
||||
assert (source_ok, "'A' channel PutFull carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'A' channel PutFull address not aligned to size" + extra)
|
||||
assert (bundle.param === UInt(0), "'A' channel PutFull carries invalid param" + extra)
|
||||
assert (bundle.mask === mask, "'A' channel PutFull contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.PutPartialData) {
|
||||
assert (edge.manager.supportsPutPartialSafe(edge.address(bundle), bundle.size), "'A' channel carries PutPartial type unsupported by manager" + extra)
|
||||
assert (source_ok, "'A' channel PutPartial carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'A' channel PutPartial address not aligned to size" + extra)
|
||||
assert (bundle.param === UInt(0), "'A' channel PutPartial carries invalid param" + extra)
|
||||
assert ((bundle.mask & ~mask) === UInt(0), "'A' channel PutPartial contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.ArithmeticData) {
|
||||
assert (edge.manager.supportsArithmeticSafe(edge.address(bundle), bundle.size), "'A' channel carries Arithmetic type unsupported by manager" + extra)
|
||||
assert (source_ok, "'A' channel Arithmetic carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'A' channel Arithmetic address not aligned to size" + extra)
|
||||
assert (TLAtomics.isArithmetic(bundle.param), "'A' channel Arithmetic carries invalid opcode param" + extra)
|
||||
assert (bundle.mask === mask, "'A' channel Arithmetic contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.LogicalData) {
|
||||
assert (edge.manager.supportsLogicalSafe(edge.address(bundle), bundle.size), "'A' channel carries Logical type unsupported by manager" + extra)
|
||||
assert (source_ok, "'A' channel Logical carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'A' channel Logical address not aligned to size" + extra)
|
||||
assert (TLAtomics.isLogical(bundle.param), "'A' channel Logical carries invalid opcode param" + extra)
|
||||
assert (bundle.mask === mask, "'A' channel Logical contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.Hint) {
|
||||
assert (edge.manager.supportsHintSafe(edge.address(bundle), bundle.size), "'A' channel carries Hint type unsupported by manager" + extra)
|
||||
assert (source_ok, "'A' channel Hint carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'A' channel Hint address not aligned to size" + extra)
|
||||
assert (bundle.mask === mask, "'A' channel Hint contains invalid mask" + extra)
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeFormatB(bundle: TLBundleB, edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
assert (TLMessages.isB(bundle.opcode), "'B' channel has invalid opcode" + extra)
|
||||
|
||||
// Reuse these subexpressions to save some firrtl lines
|
||||
val address_ok = edge.manager.containsSafe(edge.address(bundle))
|
||||
val is_aligned = edge.isAligned(bundle.address, bundle.size)
|
||||
val mask = edge.full_mask(bundle)
|
||||
|
||||
when (bundle.opcode === TLMessages.Probe) {
|
||||
assert (edge.client.supportsProbe(bundle.source, bundle.size), "'B' channel carries Probe type unsupported by client" + extra)
|
||||
assert (address_ok, "'B' channel Probe carries unmanaged address" + extra)
|
||||
assert (is_aligned, "'B' channel Probe address not aligned to size" + extra)
|
||||
assert (TLPermissions.isCap(bundle.param), "'B' channel Probe carries invalid cap param" + extra)
|
||||
assert (bundle.mask === mask, "'B' channel Probe contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.Get) {
|
||||
assert (edge.client.supportsGet(bundle.source, bundle.size), "'B' channel carries Get type unsupported by client" + extra)
|
||||
assert (address_ok, "'B' channel Get carries unmanaged address" + extra)
|
||||
assert (is_aligned, "'B' channel Get address not aligned to size" + extra)
|
||||
assert (bundle.param === UInt(0), "'B' channel Get carries invalid param" + extra)
|
||||
assert (bundle.mask === mask, "'B' channel Get contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.PutFullData) {
|
||||
assert (edge.client.supportsPutFull(bundle.source, bundle.size), "'B' channel carries PutFull type unsupported by client" + extra)
|
||||
assert (address_ok, "'B' channel PutFull carries unmanaged address" + extra)
|
||||
assert (is_aligned, "'B' channel PutFull address not aligned to size" + extra)
|
||||
assert (bundle.param === UInt(0), "'B' channel PutFull carries invalid param" + extra)
|
||||
assert (bundle.mask === mask, "'B' channel PutFull contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.PutPartialData) {
|
||||
assert (edge.client.supportsPutPartial(bundle.source, bundle.size), "'B' channel carries PutPartial type unsupported by client" + extra)
|
||||
assert (address_ok, "'B' channel PutPartial carries unmanaged address" + extra)
|
||||
assert (is_aligned, "'B' channel PutPartial address not aligned to size" + extra)
|
||||
assert (bundle.param === UInt(0), "'B' channel PutPartial carries invalid param" + extra)
|
||||
assert ((bundle.mask & ~mask) === UInt(0), "'B' channel PutPartial contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.ArithmeticData) {
|
||||
assert (edge.client.supportsArithmetic(bundle.source, bundle.size), "'B' channel carries Arithmetic type unsupported by client" + extra)
|
||||
assert (address_ok, "'B' channel Arithmetic carries unmanaged address" + extra)
|
||||
assert (is_aligned, "'B' channel Arithmetic address not aligned to size" + extra)
|
||||
assert (TLAtomics.isArithmetic(bundle.param), "'B' channel Arithmetic carries invalid opcode param" + extra)
|
||||
assert (bundle.mask === mask, "'B' channel Arithmetic contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.LogicalData) {
|
||||
assert (edge.client.supportsLogical(bundle.source, bundle.size), "'B' channel carries Logical type unsupported by client" + extra)
|
||||
assert (address_ok, "'B' channel Logical carries unmanaged address" + extra)
|
||||
assert (is_aligned, "'B' channel Logical address not aligned to size" + extra)
|
||||
assert (TLAtomics.isLogical(bundle.param), "'B' channel Logical carries invalid opcode param" + extra)
|
||||
assert (bundle.mask === mask, "'B' channel Logical contains invalid mask" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.Hint) {
|
||||
assert (edge.client.supportsHint(bundle.source, bundle.size), "'B' channel carries Hint type unsupported by client" + extra)
|
||||
assert (address_ok, "'B' channel Hint carries unmanaged address" + extra)
|
||||
assert (is_aligned, "'B' channel Hint address not aligned to size" + extra)
|
||||
assert (bundle.mask === mask, "'B' channel Hint contains invalid mask" + extra)
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeFormatC(bundle: TLBundleC, edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
assert (TLMessages.isC(bundle.opcode), "'C' channel has invalid opcode" + extra)
|
||||
|
||||
val source_ok = edge.client.contains(bundle.source)
|
||||
val is_aligned = edge.isAligned(bundle.address, bundle.size)
|
||||
val address_ok = edge.manager.containsSafe(edge.address(bundle))
|
||||
|
||||
when (bundle.opcode === TLMessages.ProbeAck) {
|
||||
assert (address_ok, "'C' channel ProbeAck carries unmanaged address" + extra)
|
||||
assert (source_ok, "'C' channel ProbeAck carries invalid source ID" + extra)
|
||||
assert (bundle.size >= UInt(log2Ceil(edge.manager.beatBytes)), "'C' channel ProbeAck smaller than a beat" + extra)
|
||||
assert (is_aligned, "'C' channel ProbeAck address not aligned to size" + extra)
|
||||
assert (TLPermissions.isReport(bundle.param), "'C' channel ProbeAck carries invalid report param" + extra)
|
||||
assert (!bundle.error, "'C' channel Probe carries an error" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.ProbeAckData) {
|
||||
assert (address_ok, "'C' channel ProbeAckData carries unmanaged address" + extra)
|
||||
assert (source_ok, "'C' channel ProbeAckData carries invalid source ID" + extra)
|
||||
assert (bundle.size >= UInt(log2Ceil(edge.manager.beatBytes)), "'C' channel ProbeAckData smaller than a beat" + extra)
|
||||
assert (is_aligned, "'C' channel ProbeAckData address not aligned to size" + extra)
|
||||
assert (TLPermissions.isReport(bundle.param), "'C' channel ProbeAckData carries invalid report param" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.Release) {
|
||||
assert (edge.manager.supportsAcquireBSafe(edge.address(bundle), bundle.size), "'C' channel carries Release type unsupported by manager" + extra)
|
||||
assert (edge.client.supportsProbe(edge.source(bundle), bundle.size), "'C' channel carries Release from a client which does not support Probe" + extra)
|
||||
assert (source_ok, "'C' channel Release carries invalid source ID" + extra)
|
||||
assert (bundle.size >= UInt(log2Ceil(edge.manager.beatBytes)), "'C' channel Release smaller than a beat" + extra)
|
||||
assert (is_aligned, "'C' channel Release address not aligned to size" + extra)
|
||||
assert (TLPermissions.isShrink(bundle.param), "'C' channel Release carries invalid shrink param" + extra)
|
||||
assert (!bundle.error, "'C' channel Release carries an error" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.ReleaseData) {
|
||||
assert (edge.manager.supportsAcquireBSafe(edge.address(bundle), bundle.size), "'C' channel carries ReleaseData type unsupported by manager" + extra)
|
||||
assert (edge.client.supportsProbe(edge.source(bundle), bundle.size), "'C' channel carries Release from a client which does not support Probe" + extra)
|
||||
assert (source_ok, "'C' channel ReleaseData carries invalid source ID" + extra)
|
||||
assert (bundle.size >= UInt(log2Ceil(edge.manager.beatBytes)), "'C' channel ReleaseData smaller than a beat" + extra)
|
||||
assert (is_aligned, "'C' channel ReleaseData address not aligned to size" + extra)
|
||||
assert (TLPermissions.isShrink(bundle.param), "'C' channel ReleaseData carries invalid shrink param" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.AccessAck) {
|
||||
assert (address_ok, "'C' channel AccessAck carries unmanaged address" + extra)
|
||||
assert (source_ok, "'C' channel AccessAck carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'C' channel AccessAck address not aligned to size" + extra)
|
||||
assert (bundle.param === UInt(0), "'C' channel AccessAck carries invalid param" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.AccessAckData) {
|
||||
assert (address_ok, "'C' channel AccessAckData carries unmanaged address" + extra)
|
||||
assert (source_ok, "'C' channel AccessAckData carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'C' channel AccessAckData address not aligned to size" + extra)
|
||||
assert (bundle.param === UInt(0), "'C' channel AccessAckData carries invalid param" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.HintAck) {
|
||||
assert (address_ok, "'C' channel HintAck carries unmanaged address" + extra)
|
||||
assert (source_ok, "'C' channel HintAck carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'C' channel HintAck address not aligned to size" + extra)
|
||||
assert (bundle.param === UInt(0), "'C' channel HintAck carries invalid param" + extra)
|
||||
assert (!bundle.error, "'C' channel HintAck carries an error" + extra)
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeFormatD(bundle: TLBundleD, edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
assert (TLMessages.isD(bundle.opcode), "'D' channel has invalid opcode" + extra)
|
||||
|
||||
val source_ok = edge.client.contains(bundle.source)
|
||||
val is_aligned = edge.isAligned(bundle.addr_lo, bundle.size)
|
||||
val sink_ok = Bool(edge.manager.endSinkId == 0) || bundle.sink < UInt(edge.manager.endSinkId)
|
||||
|
||||
when (bundle.opcode === TLMessages.ReleaseAck) {
|
||||
assert (source_ok, "'D' channel ReleaseAck carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'D' channel ReleaseAck address not aligned to size" + extra)
|
||||
assert (sink_ok, "'D' channel ReleaseAck carries invalid sink ID" + extra)
|
||||
assert (bundle.size >= UInt(log2Ceil(edge.manager.beatBytes)), "'D' channel ReleaseAck smaller than a beat" + extra)
|
||||
assert (bundle.param === UInt(0), "'D' channel ReleaseeAck carries invalid param" + extra)
|
||||
assert (!bundle.error, "'D' channel ReleaseAck carries an error" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.Grant) {
|
||||
assert (source_ok, "'D' channel Grant carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'D' channel Grant address not aligned to size" + extra)
|
||||
assert (sink_ok, "'D' channel Grant carries invalid sink ID" + extra)
|
||||
assert (bundle.size >= UInt(log2Ceil(edge.manager.beatBytes)), "'D' channel Grant smaller than a beat" + extra)
|
||||
assert (TLPermissions.isCap(bundle.param), "'D' channel Grant carries invalid cap param" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.GrantData) {
|
||||
assert (source_ok, "'D' channel GrantData carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'D' channel GrantData address not aligned to size" + extra)
|
||||
assert (sink_ok, "'D' channel GrantData carries invalid sink ID" + extra)
|
||||
assert (bundle.size >= UInt(log2Ceil(edge.manager.beatBytes)), "'D' channel GrantData smaller than a beat" + extra)
|
||||
assert (TLPermissions.isCap(bundle.param), "'D' channel GrantData carries invalid cap param" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.AccessAck) {
|
||||
assert (source_ok, "'D' channel AccessAck carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'D' channel AccessAck address not aligned to size" + extra)
|
||||
assert (sink_ok, "'D' channel AccessAck carries invalid sink ID" + extra)
|
||||
// size is ignored
|
||||
assert (bundle.param === UInt(0), "'D' channel AccessAck carries invalid param" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.AccessAckData) {
|
||||
assert (source_ok, "'D' channel AccessAckData carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'D' channel AccessAckData address not aligned to size" + extra)
|
||||
assert (sink_ok, "'D' channel AccessAckData carries invalid sink ID" + extra)
|
||||
// size is ignored
|
||||
assert (bundle.param === UInt(0), "'D' channel AccessAckData carries invalid param" + extra)
|
||||
}
|
||||
|
||||
when (bundle.opcode === TLMessages.HintAck) {
|
||||
assert (source_ok, "'D' channel HintAck carries invalid source ID" + extra)
|
||||
assert (is_aligned, "'D' channel HintAck address not aligned to size" + extra)
|
||||
assert (sink_ok, "'D' channel HintAck carries invalid sink ID" + extra)
|
||||
// size is ignored
|
||||
assert (bundle.param === UInt(0), "'D' channel HintAck carries invalid param" + extra)
|
||||
assert (!bundle.error, "'D' channel HintAck carries an error" + extra)
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeFormatE(bundle: TLBundleE, edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
val sink_ok = Bool(edge.manager.endSinkId == 0) || bundle.sink < UInt(edge.manager.endSinkId)
|
||||
assert (sink_ok, "'E' channels carries invalid sink ID" + extra)
|
||||
}
|
||||
|
||||
def legalizeFormat(bundle: TLBundleSnoop, edge: TLEdge)(implicit sourceInfo: SourceInfo) = {
|
||||
when (bundle.a.valid) { legalizeFormatA(bundle.a.bits, edge) }
|
||||
when (bundle.d.valid) { legalizeFormatD(bundle.d.bits, edge) }
|
||||
if (edge.client.anySupportProbe && edge.manager.anySupportAcquireB) {
|
||||
when (bundle.b.valid) { legalizeFormatB(bundle.b.bits, edge) }
|
||||
when (bundle.c.valid) { legalizeFormatC(bundle.c.bits, edge) }
|
||||
when (bundle.e.valid) { legalizeFormatE(bundle.e.bits, edge) }
|
||||
} else {
|
||||
assert (!bundle.b.valid, "'B' channel valid and not TL-C" + extra)
|
||||
assert (!bundle.c.valid, "'C' channel valid and not TL-C" + extra)
|
||||
assert (!bundle.e.valid, "'E' channel valid and not TL-C" + extra)
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeMultibeatA(a: DecoupledSnoop[TLBundleA], edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
val a_first = edge.first(a.bits, a.fire())
|
||||
val opcode = Reg(UInt())
|
||||
val param = Reg(UInt())
|
||||
val size = Reg(UInt())
|
||||
val source = Reg(UInt())
|
||||
val address = Reg(UInt())
|
||||
when (a.valid && !a_first) {
|
||||
assert (a.bits.opcode === opcode, "'A' channel opcode changed within multibeat operation" + extra)
|
||||
assert (a.bits.param === param, "'A' channel param changed within multibeat operation" + extra)
|
||||
assert (a.bits.size === size, "'A' channel size changed within multibeat operation" + extra)
|
||||
assert (a.bits.source === source, "'A' channel source changed within multibeat operation" + extra)
|
||||
assert (a.bits.address=== address,"'A' channel address changed with multibeat operation" + extra)
|
||||
}
|
||||
when (a.fire() && a_first) {
|
||||
opcode := a.bits.opcode
|
||||
param := a.bits.param
|
||||
size := a.bits.size
|
||||
source := a.bits.source
|
||||
address := a.bits.address
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeMultibeatB(b: DecoupledSnoop[TLBundleB], edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
val b_first = edge.first(b.bits, b.fire())
|
||||
val opcode = Reg(UInt())
|
||||
val param = Reg(UInt())
|
||||
val size = Reg(UInt())
|
||||
val source = Reg(UInt())
|
||||
val address = Reg(UInt())
|
||||
when (b.valid && !b_first) {
|
||||
assert (b.bits.opcode === opcode, "'B' channel opcode changed within multibeat operation" + extra)
|
||||
assert (b.bits.param === param, "'B' channel param changed within multibeat operation" + extra)
|
||||
assert (b.bits.size === size, "'B' channel size changed within multibeat operation" + extra)
|
||||
assert (b.bits.source === source, "'B' channel source changed within multibeat operation" + extra)
|
||||
assert (b.bits.address=== address,"'B' channel addresss changed with multibeat operation" + extra)
|
||||
}
|
||||
when (b.fire() && b_first) {
|
||||
opcode := b.bits.opcode
|
||||
param := b.bits.param
|
||||
size := b.bits.size
|
||||
source := b.bits.source
|
||||
address := b.bits.address
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeMultibeatC(c: DecoupledSnoop[TLBundleC], edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
val c_first = edge.first(c.bits, c.fire())
|
||||
val opcode = Reg(UInt())
|
||||
val param = Reg(UInt())
|
||||
val size = Reg(UInt())
|
||||
val source = Reg(UInt())
|
||||
val address = Reg(UInt())
|
||||
when (c.valid && !c_first) {
|
||||
assert (c.bits.opcode === opcode, "'C' channel opcode changed within multibeat operation" + extra)
|
||||
assert (c.bits.param === param, "'C' channel param changed within multibeat operation" + extra)
|
||||
assert (c.bits.size === size, "'C' channel size changed within multibeat operation" + extra)
|
||||
assert (c.bits.source === source, "'C' channel source changed within multibeat operation" + extra)
|
||||
assert (c.bits.address=== address,"'C' channel address changed with multibeat operation" + extra)
|
||||
}
|
||||
when (c.fire() && c_first) {
|
||||
opcode := c.bits.opcode
|
||||
param := c.bits.param
|
||||
size := c.bits.size
|
||||
source := c.bits.source
|
||||
address := c.bits.address
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeMultibeatD(d: DecoupledSnoop[TLBundleD], edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
val d_first = edge.first(d.bits, d.fire())
|
||||
val opcode = Reg(UInt())
|
||||
val param = Reg(UInt())
|
||||
val size = Reg(UInt())
|
||||
val source = Reg(UInt())
|
||||
val sink = Reg(UInt())
|
||||
val addr_lo = Reg(UInt())
|
||||
when (d.valid && !d_first) {
|
||||
assert (d.bits.opcode === opcode, "'D' channel opcode changed within multibeat operation" + extra)
|
||||
assert (d.bits.param === param, "'D' channel param changed within multibeat operation" + extra)
|
||||
assert (d.bits.size === size, "'D' channel size changed within multibeat operation" + extra)
|
||||
assert (d.bits.source === source, "'D' channel source changed within multibeat operation" + extra)
|
||||
assert (d.bits.sink === sink, "'D' channel sink changed with multibeat operation" + extra)
|
||||
assert (d.bits.addr_lo=== addr_lo,"'D' channel addr_lo changed with multibeat operation" + extra)
|
||||
}
|
||||
when (d.fire() && d_first) {
|
||||
opcode := d.bits.opcode
|
||||
param := d.bits.param
|
||||
size := d.bits.size
|
||||
source := d.bits.source
|
||||
sink := d.bits.sink
|
||||
addr_lo := d.bits.addr_lo
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeMultibeat(bundle: TLBundleSnoop, edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
legalizeMultibeatA(bundle.a, edge)
|
||||
legalizeMultibeatD(bundle.d, edge)
|
||||
if (edge.client.anySupportProbe && edge.manager.anySupportAcquireB) {
|
||||
legalizeMultibeatB(bundle.b, edge)
|
||||
legalizeMultibeatC(bundle.c, edge)
|
||||
}
|
||||
}
|
||||
|
||||
def legalizeADSource(bundle: TLBundleSnoop, edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
val inflight = RegInit(UInt(0, width = edge.client.endSourceId))
|
||||
|
||||
val a_first = edge.first(bundle.a.bits, bundle.a.fire())
|
||||
val d_first = edge.first(bundle.d.bits, bundle.d.fire())
|
||||
|
||||
val a_set = Wire(init = UInt(0, width = edge.client.endSourceId))
|
||||
when (bundle.a.fire() && a_first && edge.isRequest(bundle.a.bits)) {
|
||||
a_set := UIntToOH(bundle.a.bits.source)
|
||||
assert(!inflight(bundle.a.bits.source), "'A' channel re-used a source ID" + extra)
|
||||
}
|
||||
|
||||
val d_clr = Wire(init = UInt(0, width = edge.client.endSourceId))
|
||||
val d_release_ack = bundle.d.bits.opcode === TLMessages.ReleaseAck
|
||||
when (bundle.d.fire() && d_first && edge.isResponse(bundle.d.bits) && !d_release_ack) {
|
||||
d_clr := UIntToOH(bundle.d.bits.source)
|
||||
assert((a_set | inflight)(bundle.d.bits.source), "'D' channel acknowledged for nothing inflight" + extra)
|
||||
}
|
||||
|
||||
if (edge.manager.minLatency > 0) {
|
||||
assert(a_set =/= d_clr || !a_set.orR, s"'A' and 'D' concurrent, despite minlatency ${edge.manager.minLatency}" + extra)
|
||||
}
|
||||
|
||||
inflight := (inflight | a_set) & ~d_clr
|
||||
|
||||
val watchdog = RegInit(UInt(0, width = 32))
|
||||
val limit = PlusArg("tilelink_timeout")
|
||||
assert (!inflight.orR || limit === UInt(0) || watchdog < limit, "TileLink timeout expired" + extra)
|
||||
|
||||
watchdog := watchdog + UInt(1)
|
||||
when (bundle.a.fire() || bundle.d.fire()) { watchdog := UInt(0) }
|
||||
}
|
||||
|
||||
def legalizeDESink(bundle: TLBundleSnoop, edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
val inflight = RegInit(UInt(0, width = edge.manager.endSinkId))
|
||||
|
||||
val d_first = edge.first(bundle.d.bits, bundle.d.fire())
|
||||
val e_first = Bool(true)
|
||||
|
||||
val d_set = Wire(init = UInt(0, width = edge.manager.endSinkId))
|
||||
when (bundle.d.fire() && d_first && edge.isRequest(bundle.d.bits)) {
|
||||
d_set := UIntToOH(bundle.d.bits.sink)
|
||||
assert(!inflight(bundle.d.bits.sink), "'D' channel re-used a sink ID" + extra)
|
||||
}
|
||||
|
||||
val e_clr = Wire(init = UInt(0, width = edge.manager.endSinkId))
|
||||
when (bundle.e.fire() && e_first && edge.isResponse(bundle.e.bits)) {
|
||||
e_clr := UIntToOH(bundle.e.bits.sink)
|
||||
assert((d_set | inflight)(bundle.e.bits.sink), "'E' channel acknowledged for nothing inflight" + extra)
|
||||
}
|
||||
|
||||
// edge.client.minLatency applies to BC, not DE
|
||||
|
||||
inflight := (inflight | d_set) & ~e_clr
|
||||
}
|
||||
|
||||
def legalizeUnique(bundle: TLBundleSnoop, edge: TLEdge)(implicit sourceInfo: SourceInfo) {
|
||||
val sourceBits = log2Ceil(edge.client.endSourceId)
|
||||
val tooBig = 14 // >16kB worth of flight information gets to be too much
|
||||
if (sourceBits > tooBig) {
|
||||
println(s"WARNING: TLMonitor instantiated on a bus with source bits (${sourceBits}) > ${tooBig}; A=>D transaction flight will not be checked")
|
||||
} else {
|
||||
legalizeADSource(bundle, edge)
|
||||
}
|
||||
if (edge.client.anySupportProbe && edge.manager.anySupportAcquireB && edge.manager.endSinkId > 1) {
|
||||
// legalizeBCSourceAddress(bundle, edge) // too much state needed to synthesize...
|
||||
val sinkBits = log2Ceil(edge.manager.endSinkId)
|
||||
if (sinkBits > tooBig) {
|
||||
println(s"WARNING: TLMonitor instantiated on a bus with sink bits (${sinkBits}) > ${tooBig}; D=>E transaction flight will not be checked")
|
||||
} else {
|
||||
legalizeDESink(bundle, edge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def legalize(bundle: TLBundleSnoop, edge: TLEdge, reset: Bool) {
|
||||
legalizeFormat (bundle, edge)
|
||||
legalizeMultibeat(bundle, edge)
|
||||
legalizeUnique (bundle, edge)
|
||||
}
|
||||
}
|
73
src/main/scala/tilelink/NodeNumberer.scala
Normal file
73
src/main/scala/tilelink/NodeNumberer.scala
Normal file
@ -0,0 +1,73 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
|
||||
case class TLNodeNumbererNode(nodeAddressOffset: Option[Int] = None) extends TLCustomNode(0 to 999, 0 to 999)
|
||||
{
|
||||
val externalIn = true
|
||||
val externalOut = true
|
||||
|
||||
def resolveStar(iKnown: Int, oKnown: Int, iStars: Int, oStars: Int): (Int, Int) = {
|
||||
require (oStars + iStars <= 1, s"${name} (a custom adapter) appears left of a :*= ${iStars} times and right of a :=* ${oStars} times; at most once is allowed${lazyModule.line}")
|
||||
if (oStars > 0) {
|
||||
require (iKnown >= oKnown, s"${name} (a custom adapter) has ${oKnown} outputs and ${iKnown} inputs; cannot assign ${iKnown-oKnown} edges to resolve :=*${lazyModule.line}")
|
||||
(0, iKnown - oKnown)
|
||||
} else {
|
||||
require (oKnown >= iKnown, s"${name} (a custom adapter) has ${oKnown} outputs and ${iKnown} inputs; cannot assign ${oKnown-iKnown} edges to resolve :*=${lazyModule.line}")
|
||||
(oKnown - iKnown, 0)
|
||||
}
|
||||
}
|
||||
|
||||
def mapParamsD(n: Int, p: Seq[TLClientPortParameters]): Seq[TLClientPortParameters] = {
|
||||
require(n == p.size, s"${name} has ${p.size} inputs and ${n} outputs; they must match${lazyModule.line}")
|
||||
p
|
||||
}
|
||||
|
||||
def mapParamsU(n: Int, p: Seq[TLManagerPortParameters]): Seq[TLManagerPortParameters] = {
|
||||
require(n == p.size, s"${name} has ${n} inputs and ${p.size} outputs; they must match${lazyModule.line}")
|
||||
val minNodeOffset = log2Ceil(p.map(_.maxAddress).max)
|
||||
val nodeOffset = nodeAddressOffset.getOrElse(minNodeOffset)
|
||||
require (nodeOffset >= minNodeOffset)
|
||||
|
||||
p.zipWithIndex.map { case (mp, i) =>
|
||||
val nodeIndex = BigInt(i+1) << nodeOffset
|
||||
mp.copy(managers = mp.managers.map(m => m.copy(address = m.address.map(a => a.copy(base = a.base | nodeIndex)))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TLNodeNumberer(nodeAddressOffset: Option[Int] = None)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLNodeNumbererNode(nodeAddressOffset)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
val minNodeOffset = log2Ceil(node.edgesOut.map(_.manager.maxAddress).max)
|
||||
val nodeOffset = nodeAddressOffset.getOrElse(minNodeOffset)
|
||||
|
||||
(io.in zip io.out).zipWithIndex foreach { case ((in, out), i) =>
|
||||
out <> in
|
||||
// a&c address already get truncated
|
||||
in.b.bits.address := (UInt(i+1) << nodeOffset) | out.b.bits.address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLNodeNumberer
|
||||
{
|
||||
// applied to the TL source node; y.node := TLBuffer(x.node)
|
||||
def apply(nodeAddressOffset: Option[Int] = None)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val numberer = LazyModule(new TLNodeNumberer(nodeAddressOffset))
|
||||
numberer.node := x
|
||||
numberer.node
|
||||
}
|
||||
}
|
201
src/main/scala/tilelink/Nodes.scala
Normal file
201
src/main/scala/tilelink/Nodes.scala
Normal file
@ -0,0 +1,201 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.{Field, Parameters}
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util.RationalDirection
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
case object TLMonitorBuilder extends Field[TLMonitorArgs => Option[TLMonitorBase]]
|
||||
case object TLCombinationalCheck extends Field[Boolean]
|
||||
|
||||
object TLImp extends NodeImp[TLClientPortParameters, TLManagerPortParameters, TLEdgeOut, TLEdgeIn, TLBundle]
|
||||
{
|
||||
def edgeO(pd: TLClientPortParameters, pu: TLManagerPortParameters): TLEdgeOut = new TLEdgeOut(pd, pu)
|
||||
def edgeI(pd: TLClientPortParameters, pu: TLManagerPortParameters): TLEdgeIn = new TLEdgeIn(pd, pu)
|
||||
|
||||
def bundleO(eo: TLEdgeOut): TLBundle = TLBundle(eo.bundle)
|
||||
def bundleI(ei: TLEdgeIn): TLBundle = TLBundle(ei.bundle)
|
||||
|
||||
def colour = "#000000" // black
|
||||
override def labelI(ei: TLEdgeIn) = (ei.manager.beatBytes * 8).toString
|
||||
override def labelO(eo: TLEdgeOut) = (eo.manager.beatBytes * 8).toString
|
||||
|
||||
override def connect(edges: () => Seq[TLEdgeIn], bundles: () => Seq[(TLBundle, TLBundle)], enableMonitoring: Boolean)
|
||||
(implicit p: Parameters, sourceInfo: SourceInfo): (Option[TLMonitorBase], () => Unit) = {
|
||||
val monitor = if (enableMonitoring) p(TLMonitorBuilder)(TLMonitorArgs(edges, sourceInfo, p)) else None
|
||||
(monitor, () => {
|
||||
val eval = bundles ()
|
||||
monitor.foreach { m => (eval zip m.module.io.in) foreach { case ((i,o), m) => m := TLBundleSnoop(o,i) } }
|
||||
eval.foreach { case (bi, bo) =>
|
||||
bi <> bo
|
||||
if (p(TLCombinationalCheck)) {
|
||||
// It is forbidden for valid to depend on ready in TL2
|
||||
// If someone did that, then this will create a detectable combinational loop
|
||||
bo.a.ready := bi.a.ready && bo.a.valid
|
||||
bi.b.ready := bo.b.ready && bi.b.valid
|
||||
bo.c.ready := bi.c.ready && bo.c.valid
|
||||
bi.d.ready := bo.d.ready && bi.d.valid
|
||||
bo.e.ready := bi.e.ready && bo.e.valid
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override def mixO(pd: TLClientPortParameters, node: OutwardNode[TLClientPortParameters, TLManagerPortParameters, TLBundle]): TLClientPortParameters =
|
||||
pd.copy(clients = pd.clients.map { c => c.copy (nodePath = node +: c.nodePath) })
|
||||
override def mixI(pu: TLManagerPortParameters, node: InwardNode[TLClientPortParameters, TLManagerPortParameters, TLBundle]): TLManagerPortParameters =
|
||||
pu.copy(managers = pu.managers.map { m => m.copy (nodePath = node +: m.nodePath) })
|
||||
override def getO(pu: TLManagerPortParameters): Option[BaseNode] = {
|
||||
val head = pu.managers.map(_.nodePath.headOption)
|
||||
if (head.exists(!_.isDefined) || head.map(_.get).distinct.size != 1) {
|
||||
None
|
||||
} else {
|
||||
val subproblem = pu.copy(managers = pu.managers.map(m => m.copy(nodePath = m.nodePath.tail)))
|
||||
getO(subproblem) match {
|
||||
case Some(x) => Some(x)
|
||||
case None => Some(head(0).get)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes implemented inside modules
|
||||
case class TLIdentityNode() extends IdentityNode(TLImp)
|
||||
case class TLClientNode(portParams: Seq[TLClientPortParameters]) extends SourceNode(TLImp)(portParams)
|
||||
case class TLManagerNode(portParams: Seq[TLManagerPortParameters]) extends SinkNode(TLImp)(portParams)
|
||||
|
||||
object TLClientNode
|
||||
{
|
||||
def apply(params: TLClientParameters) =
|
||||
new TLClientNode(Seq(TLClientPortParameters(Seq(params))))
|
||||
}
|
||||
|
||||
object TLManagerNode
|
||||
{
|
||||
def apply(beatBytes: Int, params: TLManagerParameters) =
|
||||
new TLManagerNode(Seq(TLManagerPortParameters(Seq(params), beatBytes, minLatency = 0)))
|
||||
}
|
||||
|
||||
case class TLAdapterNode(
|
||||
clientFn: TLClientPortParameters => TLClientPortParameters,
|
||||
managerFn: TLManagerPortParameters => TLManagerPortParameters,
|
||||
num: Range.Inclusive = 0 to 999)
|
||||
extends AdapterNode(TLImp)(clientFn, managerFn, num)
|
||||
|
||||
case class TLNexusNode(
|
||||
clientFn: Seq[TLClientPortParameters] => TLClientPortParameters,
|
||||
managerFn: Seq[TLManagerPortParameters] => TLManagerPortParameters,
|
||||
numClientPorts: Range.Inclusive = 1 to 999,
|
||||
numManagerPorts: Range.Inclusive = 1 to 999)
|
||||
extends NexusNode(TLImp)(clientFn, managerFn, numClientPorts, numManagerPorts)
|
||||
|
||||
case class TLSplitterNode(
|
||||
clientFn: SplitterArg[TLClientPortParameters] => Seq[TLClientPortParameters],
|
||||
managerFn: SplitterArg[TLManagerPortParameters] => Seq[TLManagerPortParameters],
|
||||
numClientPorts: Range.Inclusive = 0 to 999,
|
||||
numManagerPorts: Range.Inclusive = 0 to 999)
|
||||
extends SplitterNode(TLImp)(clientFn, managerFn, numClientPorts, numManagerPorts)
|
||||
|
||||
abstract class TLCustomNode(
|
||||
numClientPorts: Range.Inclusive,
|
||||
numManagerPorts: Range.Inclusive)
|
||||
extends CustomNode(TLImp)(numClientPorts, numManagerPorts)
|
||||
|
||||
// Nodes passed from an inner module
|
||||
case class TLOutputNode() extends OutputNode(TLImp)
|
||||
case class TLInputNode() extends InputNode(TLImp)
|
||||
|
||||
// Nodes used for external ports
|
||||
case class TLBlindOutputNode(portParams: Seq[TLManagerPortParameters]) extends BlindOutputNode(TLImp)(portParams)
|
||||
case class TLBlindInputNode(portParams: Seq[TLClientPortParameters]) extends BlindInputNode(TLImp)(portParams)
|
||||
|
||||
case class TLInternalOutputNode(portParams: Seq[TLManagerPortParameters]) extends InternalOutputNode(TLImp)(portParams)
|
||||
case class TLInternalInputNode(portParams: Seq[TLClientPortParameters]) extends InternalInputNode(TLImp)(portParams)
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
class TLInputNodeTest(txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
class Acceptor extends LazyModule {
|
||||
val node = TLInputNode()
|
||||
val tlram = LazyModule(new TLRAM(AddressSet(0x54321000, 0xfff)))
|
||||
tlram.node := node
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val fuzzer = LazyModule(new TLFuzzer(txns))
|
||||
LazyModule(new Acceptor).node := TLFragmenter(4, 64)(fuzzer.node)
|
||||
|
||||
io.finished := Module(fuzzer.module).io.finished
|
||||
}
|
||||
|
||||
object TLAsyncImp extends NodeImp[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncEdgeParameters, TLAsyncEdgeParameters, TLAsyncBundle]
|
||||
{
|
||||
def edgeO(pd: TLAsyncClientPortParameters, pu: TLAsyncManagerPortParameters): TLAsyncEdgeParameters = TLAsyncEdgeParameters(pd, pu)
|
||||
def edgeI(pd: TLAsyncClientPortParameters, pu: TLAsyncManagerPortParameters): TLAsyncEdgeParameters = TLAsyncEdgeParameters(pd, pu)
|
||||
|
||||
def bundleO(eo: TLAsyncEdgeParameters): TLAsyncBundle = new TLAsyncBundle(eo.bundle)
|
||||
def bundleI(ei: TLAsyncEdgeParameters): TLAsyncBundle = new TLAsyncBundle(ei.bundle)
|
||||
|
||||
def colour = "#ff0000" // red
|
||||
override def labelI(ei: TLAsyncEdgeParameters) = ei.manager.depth.toString
|
||||
override def labelO(eo: TLAsyncEdgeParameters) = eo.manager.depth.toString
|
||||
|
||||
override def mixO(pd: TLAsyncClientPortParameters, node: OutwardNode[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncBundle]): TLAsyncClientPortParameters =
|
||||
pd.copy(base = pd.base.copy(clients = pd.base.clients.map { c => c.copy (nodePath = node +: c.nodePath) }))
|
||||
override def mixI(pu: TLAsyncManagerPortParameters, node: InwardNode[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncBundle]): TLAsyncManagerPortParameters =
|
||||
pu.copy(base = pu.base.copy(managers = pu.base.managers.map { m => m.copy (nodePath = node +: m.nodePath) }))
|
||||
}
|
||||
|
||||
case class TLAsyncIdentityNode() extends IdentityNode(TLAsyncImp)
|
||||
case class TLAsyncOutputNode() extends OutputNode(TLAsyncImp)
|
||||
case class TLAsyncInputNode() extends InputNode(TLAsyncImp)
|
||||
|
||||
case class TLAsyncSourceNode(sync: Int)
|
||||
extends MixedAdapterNode(TLImp, TLAsyncImp)(
|
||||
dFn = { p => TLAsyncClientPortParameters(p) },
|
||||
uFn = { p => p.base.copy(minLatency = sync+1) }) // discard cycles in other clock domain
|
||||
|
||||
case class TLAsyncSinkNode(depth: Int, sync: Int)
|
||||
extends MixedAdapterNode(TLAsyncImp, TLImp)(
|
||||
dFn = { p => p.base.copy(minLatency = sync+1) },
|
||||
uFn = { p => TLAsyncManagerPortParameters(depth, p) })
|
||||
|
||||
object TLRationalImp extends NodeImp[TLRationalClientPortParameters, TLRationalManagerPortParameters, TLRationalEdgeParameters, TLRationalEdgeParameters, TLRationalBundle]
|
||||
{
|
||||
def edgeO(pd: TLRationalClientPortParameters, pu: TLRationalManagerPortParameters): TLRationalEdgeParameters = TLRationalEdgeParameters(pd, pu)
|
||||
def edgeI(pd: TLRationalClientPortParameters, pu: TLRationalManagerPortParameters): TLRationalEdgeParameters = TLRationalEdgeParameters(pd, pu)
|
||||
|
||||
def bundleO(eo: TLRationalEdgeParameters): TLRationalBundle = new TLRationalBundle(eo.bundle)
|
||||
def bundleI(ei: TLRationalEdgeParameters): TLRationalBundle = new TLRationalBundle(ei.bundle)
|
||||
|
||||
def colour = "#00ff00" // green
|
||||
|
||||
override def mixO(pd: TLRationalClientPortParameters, node: OutwardNode[TLRationalClientPortParameters, TLRationalManagerPortParameters, TLRationalBundle]): TLRationalClientPortParameters =
|
||||
pd.copy(base = pd.base.copy(clients = pd.base.clients.map { c => c.copy (nodePath = node +: c.nodePath) }))
|
||||
override def mixI(pu: TLRationalManagerPortParameters, node: InwardNode[TLRationalClientPortParameters, TLRationalManagerPortParameters, TLRationalBundle]): TLRationalManagerPortParameters =
|
||||
pu.copy(base = pu.base.copy(managers = pu.base.managers.map { m => m.copy (nodePath = node +: m.nodePath) }))
|
||||
}
|
||||
|
||||
case class TLRationalIdentityNode() extends IdentityNode(TLRationalImp)
|
||||
case class TLRationalOutputNode() extends OutputNode(TLRationalImp)
|
||||
case class TLRationalInputNode() extends InputNode(TLRationalImp)
|
||||
|
||||
case class TLRationalSourceNode()
|
||||
extends MixedAdapterNode(TLImp, TLRationalImp)(
|
||||
dFn = { p => TLRationalClientPortParameters(p) },
|
||||
uFn = { p => p.base.copy(minLatency = 1) }) // discard cycles from other clock domain
|
||||
|
||||
case class TLRationalSinkNode(direction: RationalDirection)
|
||||
extends MixedAdapterNode(TLRationalImp, TLImp)(
|
||||
dFn = { p => p.base.copy(minLatency = 1) },
|
||||
uFn = { p => TLRationalManagerPortParameters(direction, p) })
|
395
src/main/scala/tilelink/Parameters.scala
Normal file
395
src/main/scala/tilelink/Parameters.scala
Normal file
@ -0,0 +1,395 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util.RationalDirection
|
||||
import scala.math.max
|
||||
|
||||
case class TLManagerParameters(
|
||||
address: Seq[AddressSet],
|
||||
resources: Seq[Resource] = Seq(),
|
||||
regionType: RegionType.T = RegionType.GET_EFFECTS,
|
||||
executable: Boolean = false, // processor can execute from this memory
|
||||
nodePath: Seq[BaseNode] = Seq(),
|
||||
// Supports both Acquire+Release+Finish of these sizes
|
||||
supportsAcquireT: TransferSizes = TransferSizes.none,
|
||||
supportsAcquireB: TransferSizes = TransferSizes.none,
|
||||
supportsArithmetic: TransferSizes = TransferSizes.none,
|
||||
supportsLogical: TransferSizes = TransferSizes.none,
|
||||
supportsGet: TransferSizes = TransferSizes.none,
|
||||
supportsPutFull: TransferSizes = TransferSizes.none,
|
||||
supportsPutPartial: TransferSizes = TransferSizes.none,
|
||||
supportsHint: TransferSizes = TransferSizes.none,
|
||||
// If fifoId=Some, all accesses sent to the same fifoId are executed and ACK'd in FIFO order
|
||||
// Note: you can only rely on this FIFO behaviour if your TLClientParameters include requestFifo
|
||||
fifoId: Option[Int] = None)
|
||||
{
|
||||
require (!address.isEmpty)
|
||||
address.foreach { a => require (a.finite) }
|
||||
|
||||
address.combinations(2).foreach { case Seq(x,y) => require (!x.overlaps(y), s"$x and $y overlap.") }
|
||||
require (supportsPutFull.contains(supportsPutPartial))
|
||||
require (supportsPutFull.contains(supportsArithmetic))
|
||||
require (supportsPutFull.contains(supportsLogical))
|
||||
require (supportsGet.contains(supportsArithmetic))
|
||||
require (supportsGet.contains(supportsLogical))
|
||||
require (supportsAcquireB.contains(supportsAcquireT))
|
||||
|
||||
// Make sure that the regionType agrees with the capabilities
|
||||
require ((regionType == RegionType.CACHED || regionType == RegionType.TRACKED) != supportsAcquireB.none)
|
||||
require (regionType != RegionType.UNCACHED || supportsGet)
|
||||
|
||||
val name = nodePath.lastOption.map(_.lazyModule.name).getOrElse("disconnected")
|
||||
val maxTransfer = List( // Largest supported transfer of all types
|
||||
supportsAcquireT.max,
|
||||
supportsAcquireB.max,
|
||||
supportsArithmetic.max,
|
||||
supportsLogical.max,
|
||||
supportsGet.max,
|
||||
supportsPutFull.max,
|
||||
supportsPutPartial.max).max
|
||||
val maxAddress = address.map(_.max).max
|
||||
val minAlignment = address.map(_.alignment).min
|
||||
|
||||
// The device had better not support a transfer larger than its alignment
|
||||
require (minAlignment >= maxTransfer, s"minAlignment ($minAlignment) must be >= maxTransfer ($maxTransfer)")
|
||||
|
||||
def toResource: ResourceAddress = {
|
||||
ResourceAddress(address, ResourcePermissions(
|
||||
r = supportsAcquireB || supportsGet,
|
||||
w = supportsAcquireT || supportsPutFull,
|
||||
x = executable,
|
||||
c = supportsAcquireB))
|
||||
}
|
||||
}
|
||||
|
||||
case class TLManagerPortParameters(
|
||||
managers: Seq[TLManagerParameters],
|
||||
beatBytes: Int,
|
||||
endSinkId: Int = 0, // 0 = no sink ids, 1 = a reusable sink id, >1 = unique sink ids
|
||||
minLatency: Int = 0)
|
||||
{
|
||||
require (!managers.isEmpty)
|
||||
require (isPow2(beatBytes))
|
||||
require (endSinkId >= 0)
|
||||
require (minLatency >= 0)
|
||||
|
||||
def requireFifo() = managers.foreach { m =>require (m.fifoId == Some(0)) }
|
||||
|
||||
// Bounds on required sizes
|
||||
def maxAddress = managers.map(_.maxAddress).max
|
||||
def maxTransfer = managers.map(_.maxTransfer).max
|
||||
|
||||
// Operation sizes supported by all outward Managers
|
||||
val allSupportAcquireT = managers.map(_.supportsAcquireT) .reduce(_ intersect _)
|
||||
val allSupportAcquireB = managers.map(_.supportsAcquireB) .reduce(_ intersect _)
|
||||
val allSupportArithmetic = managers.map(_.supportsArithmetic).reduce(_ intersect _)
|
||||
val allSupportLogical = managers.map(_.supportsLogical) .reduce(_ intersect _)
|
||||
val allSupportGet = managers.map(_.supportsGet) .reduce(_ intersect _)
|
||||
val allSupportPutFull = managers.map(_.supportsPutFull) .reduce(_ intersect _)
|
||||
val allSupportPutPartial = managers.map(_.supportsPutPartial).reduce(_ intersect _)
|
||||
val allSupportHint = managers.map(_.supportsHint) .reduce(_ intersect _)
|
||||
|
||||
// Operation supported by at least one outward Managers
|
||||
val anySupportAcquireT = managers.map(!_.supportsAcquireT.none) .reduce(_ || _)
|
||||
val anySupportAcquireB = managers.map(!_.supportsAcquireB.none) .reduce(_ || _)
|
||||
val anySupportArithmetic = managers.map(!_.supportsArithmetic.none).reduce(_ || _)
|
||||
val anySupportLogical = managers.map(!_.supportsLogical.none) .reduce(_ || _)
|
||||
val anySupportGet = managers.map(!_.supportsGet.none) .reduce(_ || _)
|
||||
val anySupportPutFull = managers.map(!_.supportsPutFull.none) .reduce(_ || _)
|
||||
val anySupportPutPartial = managers.map(!_.supportsPutPartial.none).reduce(_ || _)
|
||||
val anySupportHint = managers.map(!_.supportsHint.none) .reduce(_ || _)
|
||||
|
||||
// These return Option[TLManagerParameters] for your convenience
|
||||
def find(address: BigInt) = managers.find(_.address.exists(_.contains(address)))
|
||||
|
||||
// The safe version will check the entire address
|
||||
def findSafe(address: UInt) = Vec(managers.map(_.address.map(_.contains(address)).reduce(_ || _)))
|
||||
// The fast version assumes the address is valid (you probably want fastProperty instead of this function)
|
||||
def findFast(address: UInt) = {
|
||||
val routingMask = AddressDecoder(managers.map(_.address))
|
||||
Vec(managers.map(_.address.map(_.widen(~routingMask)).distinct.map(_.contains(address)).reduce(_ || _)))
|
||||
}
|
||||
|
||||
// Compute the simplest AddressSets that decide a key
|
||||
def fastPropertyGroup[K](p: TLManagerParameters => K): Map[K, Seq[AddressSet]] = {
|
||||
val groups = managers.map(m => (p(m), m.address)).groupBy(_._1).mapValues(_.flatMap(_._2))
|
||||
val reductionMask = AddressDecoder(groups.values.toList)
|
||||
groups.mapValues(seq => AddressSet.unify(seq.map(_.widen(~reductionMask)).distinct))
|
||||
}
|
||||
// Select a property
|
||||
def fastProperty[K, D <: Data](address: UInt, p: TLManagerParameters => K, d: K => D): D =
|
||||
Mux1H(fastPropertyGroup(p).map { case (v, a) => (a.map(_.contains(address)).reduce(_||_), d(v)) })
|
||||
|
||||
// Note: returns the actual fifoId + 1 or 0 if None
|
||||
def findFifoIdFast(address: UInt) = fastProperty(address, _.fifoId.map(_+1).getOrElse(0), (i:Int) => UInt(i))
|
||||
def hasFifoIdFast(address: UInt) = fastProperty(address, _.fifoId.isDefined, (b:Boolean) => Bool(b))
|
||||
|
||||
// Does this Port manage this ID/address?
|
||||
def containsSafe(address: UInt) = findSafe(address).reduce(_ || _)
|
||||
|
||||
private def supportHelper(
|
||||
safe: Boolean,
|
||||
member: TLManagerParameters => TransferSizes,
|
||||
address: UInt,
|
||||
lgSize: UInt,
|
||||
range: Option[TransferSizes]): Bool = {
|
||||
def trim(x: TransferSizes) = range.map(_.intersect(x)).getOrElse(x)
|
||||
val supportCases = managers.groupBy(m => trim(member(m))).mapValues(_.flatMap(_.address))
|
||||
val mask = if (safe) ~BigInt(0) else AddressDecoder(supportCases.values.toList)
|
||||
val simplified = supportCases.mapValues(seq => AddressSet.unify(seq.map(_.widen(~mask)).distinct))
|
||||
simplified.map { case (s, a) =>
|
||||
(Bool(Some(s) == range) || s.containsLg(lgSize)) &&
|
||||
a.map(_.contains(address)).reduce(_||_)
|
||||
}.foldLeft(Bool(false))(_||_)
|
||||
}
|
||||
|
||||
// Check for support of a given operation at a specific address
|
||||
def supportsAcquireTSafe (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(true, _.supportsAcquireT, address, lgSize, range)
|
||||
def supportsAcquireBSafe (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(true, _.supportsAcquireB, address, lgSize, range)
|
||||
def supportsArithmeticSafe(address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(true, _.supportsArithmetic, address, lgSize, range)
|
||||
def supportsLogicalSafe (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(true, _.supportsLogical, address, lgSize, range)
|
||||
def supportsGetSafe (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(true, _.supportsGet, address, lgSize, range)
|
||||
def supportsPutFullSafe (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(true, _.supportsPutFull, address, lgSize, range)
|
||||
def supportsPutPartialSafe(address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(true, _.supportsPutPartial, address, lgSize, range)
|
||||
def supportsHintSafe (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(true, _.supportsHint, address, lgSize, range)
|
||||
|
||||
def supportsAcquireTFast (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(false, _.supportsAcquireT, address, lgSize, range)
|
||||
def supportsAcquireBFast (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(false, _.supportsAcquireB, address, lgSize, range)
|
||||
def supportsArithmeticFast(address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(false, _.supportsArithmetic, address, lgSize, range)
|
||||
def supportsLogicalFast (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(false, _.supportsLogical, address, lgSize, range)
|
||||
def supportsGetFast (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(false, _.supportsGet, address, lgSize, range)
|
||||
def supportsPutFullFast (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(false, _.supportsPutFull, address, lgSize, range)
|
||||
def supportsPutPartialFast(address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(false, _.supportsPutPartial, address, lgSize, range)
|
||||
def supportsHintFast (address: UInt, lgSize: UInt, range: Option[TransferSizes] = None) = supportHelper(false, _.supportsHint, address, lgSize, range)
|
||||
}
|
||||
|
||||
case class TLClientParameters(
|
||||
name: String,
|
||||
sourceId: IdRange = IdRange(0,1),
|
||||
nodePath: Seq[BaseNode] = Seq(),
|
||||
requestFifo: Boolean = false, // only a request, not a requirement
|
||||
// Supports both Probe+Grant of these sizes
|
||||
supportsProbe: TransferSizes = TransferSizes.none,
|
||||
supportsArithmetic: TransferSizes = TransferSizes.none,
|
||||
supportsLogical: TransferSizes = TransferSizes.none,
|
||||
supportsGet: TransferSizes = TransferSizes.none,
|
||||
supportsPutFull: TransferSizes = TransferSizes.none,
|
||||
supportsPutPartial: TransferSizes = TransferSizes.none,
|
||||
supportsHint: TransferSizes = TransferSizes.none)
|
||||
{
|
||||
require (supportsPutFull.contains(supportsPutPartial))
|
||||
// We only support these operations if we support Probe (ie: we're a cache)
|
||||
require (supportsProbe.contains(supportsArithmetic))
|
||||
require (supportsProbe.contains(supportsLogical))
|
||||
require (supportsProbe.contains(supportsGet))
|
||||
require (supportsProbe.contains(supportsPutFull))
|
||||
require (supportsProbe.contains(supportsPutPartial))
|
||||
require (supportsProbe.contains(supportsHint))
|
||||
// If you need FIFO, you better not be TL-C (due to independent A vs. C order)
|
||||
require (!requestFifo || !supportsProbe)
|
||||
|
||||
val maxTransfer = List(
|
||||
supportsProbe.max,
|
||||
supportsArithmetic.max,
|
||||
supportsLogical.max,
|
||||
supportsGet.max,
|
||||
supportsPutFull.max,
|
||||
supportsPutPartial.max).max
|
||||
}
|
||||
|
||||
case class TLClientPortParameters(
|
||||
clients: Seq[TLClientParameters],
|
||||
unsafeAtomics: Boolean = false,
|
||||
minLatency: Int = 0) // Only applies to B=>C
|
||||
{
|
||||
require (!clients.isEmpty)
|
||||
require (minLatency >= 0)
|
||||
|
||||
// Require disjoint ranges for Ids
|
||||
IdRange.overlaps(clients.map(_.sourceId)).foreach { case (x, y) =>
|
||||
require (!x.overlaps(y), s"TLClientParameters.sourceId ${x} overlaps ${y}")
|
||||
}
|
||||
|
||||
// Bounds on required sizes
|
||||
def endSourceId = clients.map(_.sourceId.end).max
|
||||
def maxTransfer = clients.map(_.maxTransfer).max
|
||||
|
||||
// Operation sizes supported by all inward Clients
|
||||
val allSupportProbe = clients.map(_.supportsProbe) .reduce(_ intersect _)
|
||||
val allSupportArithmetic = clients.map(_.supportsArithmetic).reduce(_ intersect _)
|
||||
val allSupportLogical = clients.map(_.supportsLogical) .reduce(_ intersect _)
|
||||
val allSupportGet = clients.map(_.supportsGet) .reduce(_ intersect _)
|
||||
val allSupportPutFull = clients.map(_.supportsPutFull) .reduce(_ intersect _)
|
||||
val allSupportPutPartial = clients.map(_.supportsPutPartial).reduce(_ intersect _)
|
||||
val allSupportHint = clients.map(_.supportsHint) .reduce(_ intersect _)
|
||||
|
||||
// Operation is supported by at least one client
|
||||
val anySupportProbe = clients.map(!_.supportsProbe.none) .reduce(_ || _)
|
||||
val anySupportArithmetic = clients.map(!_.supportsArithmetic.none).reduce(_ || _)
|
||||
val anySupportLogical = clients.map(!_.supportsLogical.none) .reduce(_ || _)
|
||||
val anySupportGet = clients.map(!_.supportsGet.none) .reduce(_ || _)
|
||||
val anySupportPutFull = clients.map(!_.supportsPutFull.none) .reduce(_ || _)
|
||||
val anySupportPutPartial = clients.map(!_.supportsPutPartial.none).reduce(_ || _)
|
||||
val anySupportHint = clients.map(!_.supportsHint.none) .reduce(_ || _)
|
||||
|
||||
// These return Option[TLClientParameters] for your convenience
|
||||
def find(id: Int) = clients.find(_.sourceId.contains(id))
|
||||
|
||||
// Synthesizable lookup methods
|
||||
def find(id: UInt) = Vec(clients.map(_.sourceId.contains(id)))
|
||||
def contains(id: UInt) = find(id).reduce(_ || _)
|
||||
|
||||
def requestFifo(id: UInt) = Mux1H(find(id), clients.map(c => Bool(c.requestFifo)))
|
||||
|
||||
private def safety_helper(member: TLClientParameters => TransferSizes)(id: UInt, lgSize: UInt) = {
|
||||
val allSame = clients.map(member(_) == member(clients(0))).reduce(_ && _)
|
||||
if (allSame) member(clients(0)).containsLg(lgSize) else {
|
||||
Mux1H(find(id), clients.map(member(_).containsLg(lgSize)))
|
||||
}
|
||||
}
|
||||
|
||||
// Check for support of a given operation at a specific id
|
||||
val supportsProbe = safety_helper(_.supportsProbe) _
|
||||
val supportsArithmetic = safety_helper(_.supportsArithmetic) _
|
||||
val supportsLogical = safety_helper(_.supportsLogical) _
|
||||
val supportsGet = safety_helper(_.supportsGet) _
|
||||
val supportsPutFull = safety_helper(_.supportsPutFull) _
|
||||
val supportsPutPartial = safety_helper(_.supportsPutPartial) _
|
||||
val supportsHint = safety_helper(_.supportsHint) _
|
||||
}
|
||||
|
||||
case class TLBundleParameters(
|
||||
addressBits: Int,
|
||||
dataBits: Int,
|
||||
sourceBits: Int,
|
||||
sinkBits: Int,
|
||||
sizeBits: Int)
|
||||
{
|
||||
// Chisel has issues with 0-width wires
|
||||
require (addressBits >= 1)
|
||||
require (dataBits >= 8)
|
||||
require (sourceBits >= 1)
|
||||
require (sinkBits >= 1)
|
||||
require (sizeBits >= 1)
|
||||
require (isPow2(dataBits))
|
||||
|
||||
val addrLoBits = log2Up(dataBits/8)
|
||||
|
||||
def union(x: TLBundleParameters) =
|
||||
TLBundleParameters(
|
||||
max(addressBits, x.addressBits),
|
||||
max(dataBits, x.dataBits),
|
||||
max(sourceBits, x.sourceBits),
|
||||
max(sinkBits, x.sinkBits),
|
||||
max(sizeBits, x.sizeBits))
|
||||
}
|
||||
|
||||
object TLBundleParameters
|
||||
{
|
||||
val emptyBundleParams = TLBundleParameters(
|
||||
addressBits = 1,
|
||||
dataBits = 8,
|
||||
sourceBits = 1,
|
||||
sinkBits = 1,
|
||||
sizeBits = 1)
|
||||
|
||||
def union(x: Seq[TLBundleParameters]) = x.foldLeft(emptyBundleParams)((x,y) => x.union(y))
|
||||
|
||||
def apply(client: TLClientPortParameters, manager: TLManagerPortParameters) =
|
||||
new TLBundleParameters(
|
||||
addressBits = log2Up(manager.maxAddress + 1),
|
||||
dataBits = manager.beatBytes * 8,
|
||||
sourceBits = log2Up(client.endSourceId),
|
||||
sinkBits = log2Up(manager.endSinkId),
|
||||
sizeBits = log2Up(log2Ceil(max(client.maxTransfer, manager.maxTransfer))+1))
|
||||
}
|
||||
|
||||
case class TLEdgeParameters(
|
||||
client: TLClientPortParameters,
|
||||
manager: TLManagerPortParameters)
|
||||
{
|
||||
val maxTransfer = max(client.maxTransfer, manager.maxTransfer)
|
||||
val maxLgSize = log2Ceil(maxTransfer)
|
||||
|
||||
// Sanity check the link...
|
||||
require (maxTransfer >= manager.beatBytes)
|
||||
|
||||
val bundle = TLBundleParameters(client, manager)
|
||||
}
|
||||
|
||||
case class TLAsyncManagerPortParameters(depth: Int, base: TLManagerPortParameters) { require (isPow2(depth)) }
|
||||
case class TLAsyncClientPortParameters(base: TLClientPortParameters)
|
||||
|
||||
case class TLAsyncBundleParameters(depth: Int, base: TLBundleParameters)
|
||||
{
|
||||
require (isPow2(depth))
|
||||
def union(x: TLAsyncBundleParameters) = TLAsyncBundleParameters(
|
||||
depth = max(depth, x.depth),
|
||||
base = base.union(x.base))
|
||||
}
|
||||
|
||||
object TLAsyncBundleParameters
|
||||
{
|
||||
val emptyBundleParams = TLAsyncBundleParameters(depth = 1, base = TLBundleParameters.emptyBundleParams)
|
||||
def union(x: Seq[TLAsyncBundleParameters]) = x.foldLeft(emptyBundleParams)((x,y) => x.union(y))
|
||||
}
|
||||
|
||||
case class TLAsyncEdgeParameters(client: TLAsyncClientPortParameters, manager: TLAsyncManagerPortParameters)
|
||||
{
|
||||
val bundle = TLAsyncBundleParameters(manager.depth, TLBundleParameters(client.base, manager.base))
|
||||
}
|
||||
|
||||
case class TLRationalManagerPortParameters(direction: RationalDirection, base: TLManagerPortParameters)
|
||||
case class TLRationalClientPortParameters(base: TLClientPortParameters)
|
||||
|
||||
case class TLRationalEdgeParameters(client: TLRationalClientPortParameters, manager: TLRationalManagerPortParameters)
|
||||
{
|
||||
val bundle = TLBundleParameters(client.base, manager.base)
|
||||
}
|
||||
|
||||
object ManagerUnification
|
||||
{
|
||||
def apply(managers: Seq[TLManagerParameters]) = {
|
||||
// To be unified, devices must agree on all of these terms
|
||||
case class TLManagerKey(
|
||||
resources: Seq[Resource],
|
||||
regionType: RegionType.T,
|
||||
executable: Boolean,
|
||||
supportsAcquireT: TransferSizes,
|
||||
supportsAcquireB: TransferSizes,
|
||||
supportsArithmetic: TransferSizes,
|
||||
supportsLogical: TransferSizes,
|
||||
supportsGet: TransferSizes,
|
||||
supportsPutFull: TransferSizes,
|
||||
supportsPutPartial: TransferSizes,
|
||||
supportsHint: TransferSizes)
|
||||
def key(x: TLManagerParameters) = TLManagerKey(
|
||||
resources = x.resources,
|
||||
regionType = x.regionType,
|
||||
executable = x.executable,
|
||||
supportsAcquireT = x.supportsAcquireT,
|
||||
supportsAcquireB = x.supportsAcquireB,
|
||||
supportsArithmetic = x.supportsArithmetic,
|
||||
supportsLogical = x.supportsLogical,
|
||||
supportsGet = x.supportsGet,
|
||||
supportsPutFull = x.supportsPutFull,
|
||||
supportsPutPartial = x.supportsPutPartial,
|
||||
supportsHint = x.supportsHint)
|
||||
val map = scala.collection.mutable.HashMap[TLManagerKey, TLManagerParameters]()
|
||||
managers.foreach { m =>
|
||||
val k = key(m)
|
||||
map.get(k) match {
|
||||
case None => map.update(k, m)
|
||||
case Some(n) => {
|
||||
map.update(k, m.copy(
|
||||
address = m.address ++ n.address,
|
||||
fifoId = None)) // Merging means it's not FIFO anymore!
|
||||
}
|
||||
}
|
||||
}
|
||||
map.values.map(m => m.copy(address = AddressSet.unify(m.address))).toList
|
||||
}
|
||||
}
|
349
src/main/scala/tilelink/RAMModel.scala
Normal file
349
src/main/scala/tilelink/RAMModel.scala
Normal file
@ -0,0 +1,349 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
// We detect concurrent puts that put memory into an undefined state.
|
||||
// put0, put0Ack, put1, put1Ack => ok: defined
|
||||
// put0, put1, put0Ack, put1Ack => ok: put1 clears valid (it sees busy>0) defined for FIFO
|
||||
// put0, put1, put1Ack, put0Ack => ok: put1 clears valid (it sees busy>0) defined for FIFO
|
||||
// When the region is FIFO, all writes leave 'valid' set (concurrent puts have defined behaviour)
|
||||
|
||||
// We detect concurrent puts that invalidate an inflight get.
|
||||
// get, getAck, put, putAck => ok: defined
|
||||
// get, put, getAck, putAck => ok: detected by getAck (it sees busy>0)
|
||||
// get, put, putAck, getAck => ok: putAck uses CAM to wipe get validity impossible for FIFO
|
||||
// put, putAck, get, getAck => ok: defined
|
||||
// put, get, putAck, getAck => ok: putAck uses CAM to wipe get validity defined for FIFO
|
||||
// put, get, getAck, putAck => ok: detected by getAck (it sees busy>0) impossible for FIFO
|
||||
// If FIFO, the getAck should check data even if its validity was wiped
|
||||
|
||||
class TLRAMModel(log: String = "")(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLIdentityNode()
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val edge = edgeIn
|
||||
val endAddress = edge.manager.maxAddress + 1
|
||||
val endSourceId = edge.client.endSourceId
|
||||
val maxTransfer = edge.manager.maxTransfer
|
||||
val beatBytes = edge.manager.beatBytes
|
||||
val endAddressHi = (endAddress / beatBytes).intValue
|
||||
val maxLgBeats = log2Up(maxTransfer/beatBytes)
|
||||
val shift = log2Ceil(beatBytes)
|
||||
val decTrees = log2Up(maxTransfer/beatBytes)
|
||||
val addressBits = log2Up(endAddress)
|
||||
val countBits = log2Up(endSourceId)
|
||||
val sizeBits = edge.bundle.sizeBits
|
||||
val divisor = CRC.CRC_16F_4_2
|
||||
|
||||
// Reset control logic
|
||||
val wipeIndex = RegInit(UInt(0, width = log2Ceil(endAddressHi) + 1))
|
||||
val wipe = !wipeIndex(log2Ceil(endAddressHi))
|
||||
wipeIndex := wipeIndex + wipe.asUInt
|
||||
|
||||
// Block traffic while wiping Mems
|
||||
in.a.ready := out.a.ready && !wipe
|
||||
out.a.valid := in.a.valid && !wipe
|
||||
out.a.bits := in.a.bits
|
||||
out.d.ready := in.d.ready && !wipe
|
||||
in.d.valid := out.d.valid && !wipe
|
||||
in.d.bits := out.d.bits
|
||||
|
||||
// BCE unsupported
|
||||
in.b.valid := Bool(false)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
out.b.ready := Bool(true)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
|
||||
val params = TLRAMModel.MonitorParameters(addressBits, sizeBits)
|
||||
|
||||
// Infer as simple dual port BRAM/M10k with write-first/new-data semantics (bypass needed)
|
||||
val shadow = Seq.fill(beatBytes) { Mem(endAddressHi, new TLRAMModel.ByteMonitor(params)) }
|
||||
val inc_bytes = Seq.fill(beatBytes) { Mem(endAddressHi, UInt(width = countBits)) }
|
||||
val dec_bytes = Seq.fill(beatBytes) { Mem(endAddressHi, UInt(width = countBits)) }
|
||||
val inc_trees = Seq.tabulate(decTrees) { i => Mem(endAddressHi >> (i+1), UInt(width = countBits)) }
|
||||
val dec_trees = Seq.tabulate(decTrees) { i => Mem(endAddressHi >> (i+1), UInt(width = countBits)) }
|
||||
|
||||
val shadow_wen = Wire(init = Fill(beatBytes, wipe))
|
||||
val inc_bytes_wen = Wire(init = Fill(beatBytes, wipe))
|
||||
val dec_bytes_wen = Wire(init = Fill(beatBytes, wipe))
|
||||
val inc_trees_wen = Wire(init = Fill(decTrees, wipe))
|
||||
val dec_trees_wen = Wire(init = Fill(decTrees, wipe))
|
||||
|
||||
// This must be registers b/c we build a CAM from it
|
||||
val flight = Reg(Vec(endSourceId, new TLRAMModel.FlightMonitor(params)))
|
||||
val valid = Reg(Vec(endSourceId, Bool()))
|
||||
|
||||
// We want to cross flight data from A to D in the same cycle (for combinational TL2 devices)
|
||||
val a_flight = Wire(new TLRAMModel.FlightMonitor(params))
|
||||
a_flight.base := edge.address(in.a.bits)
|
||||
a_flight.size := edge.size(in.a.bits)
|
||||
a_flight.opcode := in.a.bits.opcode
|
||||
|
||||
when (in.a.fire()) { flight(in.a.bits.source) := a_flight }
|
||||
val bypass = if (edge.manager.minLatency > 0) Bool(false) else in.a.valid && in.a.bits.source === out.d.bits.source
|
||||
val d_flight = RegNext(Mux(bypass, a_flight, flight(out.d.bits.source)))
|
||||
|
||||
// Process A access requests
|
||||
val a = Reg(next = in.a.bits)
|
||||
val a_fire = Reg(next = in.a.fire(), init = Bool(false))
|
||||
val (a_first, a_last, _, a_address_inc) = edge.addr_inc(a, a_fire)
|
||||
val a_size = edge.size(a)
|
||||
val a_sizeOH = UIntToOH(a_size)
|
||||
val a_address = a.address | a_address_inc
|
||||
val a_addr_hi = edge.addr_hi(a_address)
|
||||
val a_base = edge.address(a)
|
||||
val a_mask = edge.mask(a_base, a_size)
|
||||
val a_fifo = edge.manager.hasFifoIdFast(a_base) && edge.client.requestFifo(a.source)
|
||||
|
||||
// Grab the concurrency state we need
|
||||
val a_inc_bytes = inc_bytes.map(_.read(a_addr_hi))
|
||||
val a_dec_bytes = dec_bytes.map(_.read(a_addr_hi))
|
||||
val a_inc_trees = inc_trees.zipWithIndex.map{ case (m, i) => m.read(a_addr_hi >> (i+1)) }
|
||||
val a_dec_trees = dec_trees.zipWithIndex.map{ case (m, i) => m.read(a_addr_hi >> (i+1)) }
|
||||
val a_inc_tree = a_inc_trees.fold(UInt(0))(_ + _)
|
||||
val a_dec_tree = a_dec_trees.fold(UInt(0))(_ + _)
|
||||
val a_inc = a_inc_bytes.map(_ + a_inc_tree)
|
||||
val a_dec = a_dec_bytes.map(_ + a_dec_tree)
|
||||
|
||||
when (a_fire) {
|
||||
// Record the request so we can handle it's response
|
||||
assert (a.opcode =/= TLMessages.Acquire)
|
||||
|
||||
// Mark the operation as valid
|
||||
valid(a.source) := Bool(true)
|
||||
|
||||
// Increase the per-byte flight counter for the whole transaction
|
||||
when (a_first && a.opcode =/= TLMessages.Hint && a.opcode =/= TLMessages.Get) {
|
||||
when (a_size <= UInt(shift)) {
|
||||
inc_bytes_wen := a_mask
|
||||
}
|
||||
inc_trees_wen := a_sizeOH >> (shift+1)
|
||||
}
|
||||
|
||||
when (a.opcode === TLMessages.PutFullData || a.opcode === TLMessages.PutPartialData ||
|
||||
a.opcode === TLMessages.ArithmeticData || a.opcode === TLMessages.LogicalData) {
|
||||
shadow_wen := a.mask
|
||||
for (i <- 0 until beatBytes) {
|
||||
val busy = a_inc(i) - a_dec(i) - (!a_first).asUInt
|
||||
val byte = a.data(8*(i+1)-1, 8*i)
|
||||
when (a.mask(i)) {
|
||||
printf(log + " ")
|
||||
when (a.opcode === TLMessages.PutFullData) { printf("PF") }
|
||||
when (a.opcode === TLMessages.PutPartialData) { printf("PP") }
|
||||
when (a.opcode === TLMessages.ArithmeticData) { printf("A ") }
|
||||
when (a.opcode === TLMessages.LogicalData) { printf("L ") }
|
||||
printf(" 0x%x := 0x%x #%d %x\n", a_addr_hi << shift | UInt(i), byte, busy, a.param)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (a.opcode === TLMessages.Get) {
|
||||
printf(log + " G 0x%x - 0%x\n", a_base, a_base | UIntToOH1(a_size, addressBits))
|
||||
}
|
||||
}
|
||||
|
||||
val a_waddr = Mux(wipe, wipeIndex, a_addr_hi)
|
||||
val a_shadow = shadow.map(_.read(a_waddr))
|
||||
val a_known_old = !(Cat(a_shadow.map(!_.valid).reverse) & a_mask).orR
|
||||
val alu = Module(new Atomics(a.params))
|
||||
alu.io.write := Bool(false)
|
||||
alu.io.a := a
|
||||
alu.io.data_in := Cat(a_shadow.map(_.value).reverse)
|
||||
|
||||
val crc = Mem(endSourceId, UInt(width = 16))
|
||||
val crc_valid = Mem(endSourceId, Bool())
|
||||
val a_crc_acc = Mux(a_first, UInt(0), crc(a.source))
|
||||
val a_crc_new = Cat(a_shadow.zipWithIndex.map { case (z, i) => Mux(a_mask(i), z.value, UInt(0)) }.reverse)
|
||||
val a_crc = CRC(divisor, Cat(a_crc_acc, a_crc_new), 16 + beatBytes*8)
|
||||
val a_crc_valid = a_known_old && Mux(a_first, Bool(true), crc_valid(a.source))
|
||||
when (a_fire) {
|
||||
crc.write(a.source, a_crc)
|
||||
crc_valid.write(a.source, a_crc_valid)
|
||||
}
|
||||
|
||||
for (i <- 0 until beatBytes) {
|
||||
val data = Wire(new TLRAMModel.ByteMonitor(params))
|
||||
val busy = a_inc(i) =/= a_dec(i) + (!a_first).asUInt
|
||||
val amo = a.opcode === TLMessages.ArithmeticData || a.opcode === TLMessages.LogicalData
|
||||
val beat_amo = a.size <= UInt(log2Ceil(beatBytes))
|
||||
data.valid := Mux(wipe, Bool(false), (!busy || a_fifo) && (!amo || (a_known_old && beat_amo)))
|
||||
data.value := alu.io.data_out(8*(i+1)-1, 8*i)
|
||||
when (shadow_wen(i)) {
|
||||
shadow(i).write(a_waddr, data)
|
||||
}
|
||||
}
|
||||
|
||||
for (i <- 0 until beatBytes) {
|
||||
val data = Mux(wipe, UInt(0), a_inc_bytes(i) + UInt(1))
|
||||
when (inc_bytes_wen(i)) {
|
||||
inc_bytes(i).write(a_waddr, data)
|
||||
}
|
||||
}
|
||||
|
||||
for (i <- 0 until inc_trees.size) {
|
||||
val data = Mux(wipe, UInt(0), a_inc_trees(i) + UInt(1))
|
||||
when (inc_trees_wen(i)) {
|
||||
inc_trees(i).write(a_waddr >> (i+1), data)
|
||||
}
|
||||
}
|
||||
|
||||
// Process D access responses
|
||||
val d = RegNext(out.d.bits)
|
||||
val d_fire = Reg(next = out.d.fire(), init = Bool(false))
|
||||
val (d_first, d_last, _, d_address_inc) = edge.addr_inc(d, d_fire)
|
||||
val d_size = edge.size(d)
|
||||
val d_sizeOH = UIntToOH(d_size)
|
||||
val d_base = d_flight.base
|
||||
val d_address = d_base | d_address_inc
|
||||
val d_addr_hi = edge.addr_hi(d_address)
|
||||
val d_mask = edge.mask(d_base, d_size)
|
||||
val d_fifo = edge.manager.hasFifoIdFast(d_flight.base) && edge.client.requestFifo(d.source)
|
||||
|
||||
// Grab the concurrency state we need
|
||||
val d_inc_bytes = inc_bytes.map(_.read(d_addr_hi))
|
||||
val d_dec_bytes = dec_bytes.map(_.read(d_addr_hi))
|
||||
val d_inc_trees = inc_trees.zipWithIndex.map{ case (m, i) => m.read(d_addr_hi >> (i+1)) }
|
||||
val d_dec_trees = dec_trees.zipWithIndex.map{ case (m, i) => m.read(d_addr_hi >> (i+1)) }
|
||||
val d_inc_tree = d_inc_trees.fold(UInt(0))(_ + _)
|
||||
val d_dec_tree = d_dec_trees.fold(UInt(0))(_ + _)
|
||||
val d_inc = d_inc_bytes.map(_ + d_inc_tree)
|
||||
val d_dec = d_dec_bytes.map(_ + d_dec_tree)
|
||||
val d_shadow = shadow.map(_.read(d_addr_hi))
|
||||
val d_valid = valid(d.source)
|
||||
|
||||
// CRC check
|
||||
val d_crc_reg = Reg(UInt(width = 16))
|
||||
val d_crc_acc = Mux(d_first, UInt(0), d_crc_reg)
|
||||
val d_crc_new = FillInterleaved(8, d_mask) & d.data
|
||||
val d_crc = CRC(divisor, Cat(d_crc_acc, d_crc_new), 16 + beatBytes*8)
|
||||
val crc_bypass = if (edge.manager.minLatency > 0) Bool(false) else a_fire && a.source === d.source
|
||||
val d_crc_valid = Mux(crc_bypass, a_crc_valid, crc_valid.read(d.source))
|
||||
val d_crc_check = Mux(crc_bypass, a_crc, crc.read(d.source))
|
||||
|
||||
val d_no_race_reg = Reg(Bool())
|
||||
val d_no_race = Wire(init = d_no_race_reg)
|
||||
|
||||
when (d_fire) {
|
||||
d_crc_reg := d_crc
|
||||
d_no_race_reg := d_no_race
|
||||
|
||||
// Check the response is correct
|
||||
assert (d_size === d_flight.size)
|
||||
// addr_lo is allowed to differ
|
||||
|
||||
when (d_flight.opcode === TLMessages.Hint) {
|
||||
assert (d.opcode === TLMessages.HintAck)
|
||||
}
|
||||
|
||||
// Decrease the per-byte flight counter for the whole transaction
|
||||
when (d_last && d_flight.opcode =/= TLMessages.Hint && d_flight.opcode =/= TLMessages.Get) {
|
||||
when (d_size <= UInt(shift)) {
|
||||
dec_bytes_wen := d_mask
|
||||
}
|
||||
dec_trees_wen := d_sizeOH >> (shift+1)
|
||||
// NOTE: D channel carries uninterrupted multibeast op, so updating on last is fine
|
||||
for (i <- 0 until endSourceId) {
|
||||
// Does this modification overlap a Get? => wipe it's valid
|
||||
val f_base = flight(i).base
|
||||
val f_size = flight(i).size
|
||||
val f_bits = UIntToOH1(f_size, addressBits)
|
||||
val d_bits = UIntToOH1(d_size, addressBits)
|
||||
val overlap = ~(~(f_base ^ d_base) | (f_bits | d_bits)) === UInt(0)
|
||||
when (overlap) { valid(i) := Bool(false) }
|
||||
}
|
||||
}
|
||||
|
||||
when (d_flight.opcode === TLMessages.PutFullData || d_flight.opcode === TLMessages.PutPartialData) {
|
||||
assert (d.opcode === TLMessages.AccessAck)
|
||||
printf(log + " ")
|
||||
when (d_flight.opcode === TLMessages.PutFullData) { printf("pf") }
|
||||
when (d_flight.opcode === TLMessages.PutPartialData) { printf("pp") }
|
||||
printf(" 0x%x - 0x%x\n", d_base, d_base | UIntToOH1(d_size, addressBits))
|
||||
}
|
||||
|
||||
when (d_flight.opcode === TLMessages.Get || d_flight.opcode === TLMessages.ArithmeticData || d_flight.opcode === TLMessages.LogicalData) {
|
||||
assert (d.opcode === TLMessages.AccessAckData)
|
||||
for (i <- 0 until beatBytes) {
|
||||
val got = d.data(8*(i+1)-1, 8*i)
|
||||
val shadow = Wire(init = d_shadow(i))
|
||||
when (d_mask(i)) {
|
||||
val d_addr = d_addr_hi << shift | UInt(i)
|
||||
printf(log + " ")
|
||||
when (d_flight.opcode === TLMessages.Get) { printf("g ") }
|
||||
when (d_flight.opcode === TLMessages.ArithmeticData) { printf("a ") }
|
||||
when (d_flight.opcode === TLMessages.LogicalData) { printf("l ") }
|
||||
printf(" 0x%x := 0x%x", d_addr, got)
|
||||
when (!shadow.valid) {
|
||||
printf(", undefined (uninitialized or prior overlapping puts)\n")
|
||||
} .elsewhen (d_inc(i) =/= d_dec(i)) {
|
||||
printf(", undefined (concurrent incomplete puts #%d)\n", d_inc(i) - d_dec(i))
|
||||
} .elsewhen (!d_fifo && !d_valid) {
|
||||
printf(", undefined (concurrent completed put)\n")
|
||||
} .otherwise {
|
||||
printf("\n")
|
||||
when (shadow.value =/= got) { printf("EXPECTED: 0x%x\n", shadow.value) }
|
||||
assert (shadow.value === got)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (d_flight.opcode === TLMessages.ArithmeticData || d_flight.opcode === TLMessages.LogicalData) {
|
||||
val race = (d_inc zip d_dec) map { case (i, d) => i - d =/= UInt(1) }
|
||||
when (d_first) { d_no_race := Bool(true) }
|
||||
when ((Cat(race.reverse) & d_mask).orR) { d_no_race := Bool(false) }
|
||||
when (d_last) {
|
||||
val must_match = d_crc_valid && (d_fifo || (d_valid && d_no_race))
|
||||
printf(log + " crc = 0x%x %d\n", d_crc, must_match.asUInt)
|
||||
when (must_match && d_crc =/= d_crc_check) { printf("EXPECTED: 0x%x\n", d_crc_check) }
|
||||
assert (!must_match || d_crc === d_crc_check)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val d_waddr = Mux(wipe, wipeIndex, d_addr_hi)
|
||||
for (i <- 0 until beatBytes) {
|
||||
val data = Mux(wipe, UInt(0), d_dec_bytes(i) + UInt(1))
|
||||
when (dec_bytes_wen(i)) {
|
||||
dec_bytes(i).write(d_waddr, data)
|
||||
}
|
||||
}
|
||||
|
||||
for (i <- 0 until dec_trees.size) {
|
||||
val data = Mux(wipe, UInt(0), d_dec_trees(i) + UInt(1))
|
||||
when (dec_trees_wen(i)) {
|
||||
dec_trees(i).write(d_waddr >> (i+1), data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLRAMModel
|
||||
{
|
||||
case class MonitorParameters(addressBits: Int, sizeBits: Int)
|
||||
|
||||
class ByteMonitor(params: MonitorParameters) extends GenericParameterizedBundle(params) {
|
||||
val valid = Bool()
|
||||
val value = UInt(width = 8)
|
||||
}
|
||||
class FlightMonitor(params: MonitorParameters) extends GenericParameterizedBundle(params) {
|
||||
val base = UInt(width = params.addressBits)
|
||||
val size = UInt(width = params.sizeBits)
|
||||
val opcode = UInt(width = 3)
|
||||
}
|
||||
}
|
227
src/main/scala/tilelink/RationalCrossing.scala
Normal file
227
src/main/scala/tilelink/RationalCrossing.scala
Normal file
@ -0,0 +1,227 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
// If you know two clocks are related with a N:1 or 1:N relationship, you
|
||||
// can cross the clock domains with lower latency than an AsyncQueue.
|
||||
// This clock crossing behaves almost identically to a TLBuffer(2):
|
||||
// - It adds one cycle latency to each clock domain.
|
||||
// - All outputs of TLRational are registers (bits, valid, and ready).
|
||||
// - It costs 3*bits registers as opposed to 2*bits in a TLBuffer(2)
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class TLRationalCrossingSource(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLRationalSourceNode()
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val bce = edgeIn.manager.anySupportAcquireB && edgeIn.client.anySupportProbe
|
||||
val direction = edgeOut.manager.direction
|
||||
|
||||
out.a <> ToRational(in.a, direction)
|
||||
in.d <> FromRational(out.d, direction.flip)
|
||||
|
||||
if (bce) {
|
||||
in.b <> FromRational(out.b, direction.flip)
|
||||
out.c <> ToRational(in.c, direction)
|
||||
out.e <> ToRational(in.e, direction)
|
||||
} else {
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
out.b.sink := UInt(0)
|
||||
out.c.source := UInt(0)
|
||||
out.e.source := UInt(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TLRationalCrossingSink(direction: RationalDirection = Symmetric)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLRationalSinkNode(direction)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val bce = edgeOut.manager.anySupportAcquireB && edgeOut.client.anySupportProbe
|
||||
val direction = edgeIn.manager.direction
|
||||
|
||||
out.a <> FromRational(in.a, direction)
|
||||
in.d <> ToRational(out.d, direction.flip)
|
||||
|
||||
if (bce) {
|
||||
in.b <> ToRational(out.b, direction.flip)
|
||||
out.c <> FromRational(in.c, direction)
|
||||
out.e <> FromRational(in.e, direction)
|
||||
} else {
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
in.b.source := UInt(0)
|
||||
in.c.sink := UInt(0)
|
||||
in.e.sink := UInt(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLRationalCrossingSource
|
||||
{
|
||||
// applied to the TL source node; y.node := TLRationalCrossingSource()(x.node)
|
||||
def apply()(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLRationalOutwardNode = {
|
||||
val source = LazyModule(new TLRationalCrossingSource)
|
||||
source.node := x
|
||||
source.node
|
||||
}
|
||||
}
|
||||
|
||||
object TLRationalCrossingSink
|
||||
{
|
||||
// applied to the TL source node; y.node := TLRationalCrossingSink()(x.node)
|
||||
def apply(direction: RationalDirection = Symmetric)(x: TLRationalOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val sink = LazyModule(new TLRationalCrossingSink(direction))
|
||||
sink.node := x
|
||||
sink.node
|
||||
}
|
||||
}
|
||||
|
||||
class TLRationalCrossing(direction: RationalDirection = Symmetric)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val nodeIn = TLInputNode()
|
||||
val nodeOut = TLOutputNode()
|
||||
val node = NodeHandle(nodeIn, nodeOut)
|
||||
|
||||
val source = LazyModule(new TLRationalCrossingSource)
|
||||
val sink = LazyModule(new TLRationalCrossingSink(direction))
|
||||
|
||||
val _ = (sink.node := source.node) // no monitor
|
||||
val in = (source.node := nodeIn)
|
||||
val out = (nodeOut := sink.node)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = nodeIn.bundleIn
|
||||
val in_clock = Clock(INPUT)
|
||||
val in_reset = Bool(INPUT)
|
||||
val out = nodeOut.bundleOut
|
||||
val out_clock = Clock(INPUT)
|
||||
val out_reset = Bool(INPUT)
|
||||
}
|
||||
|
||||
source.module.clock := io.in_clock
|
||||
source.module.reset := io.in_reset
|
||||
in.foreach { lm =>
|
||||
lm.module.clock := io.in_clock
|
||||
lm.module.reset := io.in_reset
|
||||
}
|
||||
|
||||
sink.module.clock := io.out_clock
|
||||
sink.module.reset := io.out_reset
|
||||
out.foreach { lm =>
|
||||
lm.module.clock := io.out_clock
|
||||
lm.module.reset := io.out_reset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
class TLRAMRationalCrossingSource(name: String, txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val node = TLRationalOutputNode()
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val model = LazyModule(new TLRAMModel(name))
|
||||
|
||||
model.node := fuzz.node
|
||||
node := TLRationalCrossingSource()(TLDelayer(0.25)(model.node))
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val finished = Bool(OUTPUT)
|
||||
val out = node.bundleOut
|
||||
}
|
||||
io.finished := fuzz.module.io.finished
|
||||
}
|
||||
}
|
||||
|
||||
class TLRAMRationalCrossingSink(direction: RationalDirection)(implicit p: Parameters) extends LazyModule {
|
||||
val node = TLRationalInputNode()
|
||||
val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff)))
|
||||
|
||||
ram.node := TLFragmenter(4, 256)(TLDelayer(0.25)(TLRationalCrossingSink(direction)(node)))
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TLRAMRationalCrossing(txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val sym_fast_source = LazyModule(new TLRAMRationalCrossingSource("RationalCrossing sym_fast", txns))
|
||||
val sym_slow_sink = LazyModule(new TLRAMRationalCrossingSink(Symmetric))
|
||||
sym_slow_sink.node := sym_fast_source.node
|
||||
|
||||
val sym_slow_source = LazyModule(new TLRAMRationalCrossingSource("RationalCrossing sym_slow", txns))
|
||||
val sym_fast_sink = LazyModule(new TLRAMRationalCrossingSink(Symmetric))
|
||||
sym_fast_sink.node := sym_slow_source.node
|
||||
|
||||
val fix_fast_source = LazyModule(new TLRAMRationalCrossingSource("RationalCrossing fast", txns))
|
||||
val fix_slow_sink = LazyModule(new TLRAMRationalCrossingSink(FastToSlow))
|
||||
fix_slow_sink.node := fix_fast_source.node
|
||||
|
||||
val fix_slow_source = LazyModule(new TLRAMRationalCrossingSource("RationalCrossing slow", txns))
|
||||
val fix_fast_sink = LazyModule(new TLRAMRationalCrossingSink(SlowToFast))
|
||||
fix_fast_sink.node := fix_slow_source.node
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished :=
|
||||
sym_fast_source.module.io.finished &&
|
||||
sym_slow_source.module.io.finished &&
|
||||
fix_fast_source.module.io.finished &&
|
||||
fix_slow_source.module.io.finished
|
||||
|
||||
// Generate faster clock (still divided so verilator approves)
|
||||
val fast = Module(new Pow2ClockDivider(1))
|
||||
sym_fast_source.module.clock := fast.io.clock_out
|
||||
sym_fast_sink .module.clock := fast.io.clock_out
|
||||
fix_fast_source.module.clock := fast.io.clock_out
|
||||
fix_fast_sink .module.clock := fast.io.clock_out
|
||||
|
||||
// Generate slower clock
|
||||
val slow = Module(new Pow2ClockDivider(2))
|
||||
fix_slow_source.module.clock := slow.io.clock_out
|
||||
fix_slow_sink .module.clock := slow.io.clock_out
|
||||
|
||||
val odd = Module(new ClockDivider3)
|
||||
odd.io.clk_in := clock
|
||||
sym_slow_source.module.clock := odd.io.clk_out
|
||||
sym_slow_sink .module.clock := odd.io.clk_out
|
||||
}
|
||||
}
|
||||
|
||||
class TLRAMRationalCrossingTest(txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new TLRAMRationalCrossing(txns)).module).io.finished
|
||||
}
|
149
src/main/scala/tilelink/RegisterRouter.scala
Normal file
149
src/main/scala/tilelink/RegisterRouter.scala
Normal file
@ -0,0 +1,149 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.regmapper._
|
||||
import freechips.rocketchip.util.HeterogeneousBag
|
||||
import scala.math.{min,max}
|
||||
|
||||
class TLRegisterNode(
|
||||
address: Seq[AddressSet],
|
||||
device: Device,
|
||||
deviceKey: String = "reg/control",
|
||||
concurrency: Int = 0,
|
||||
beatBytes: Int = 4,
|
||||
undefZero: Boolean = true,
|
||||
executable: Boolean = false)
|
||||
extends TLManagerNode(Seq(TLManagerPortParameters(
|
||||
Seq(TLManagerParameters(
|
||||
address = address,
|
||||
resources = Seq(Resource(device, deviceKey)),
|
||||
executable = executable,
|
||||
supportsGet = TransferSizes(1, beatBytes),
|
||||
supportsPutPartial = TransferSizes(1, beatBytes),
|
||||
supportsPutFull = TransferSizes(1, beatBytes),
|
||||
fifoId = Some(0))), // requests are handled in order
|
||||
beatBytes = beatBytes,
|
||||
minLatency = min(concurrency, 1)))) // the Queue adds at most one cycle
|
||||
{
|
||||
val size = 1 << log2Ceil(1 + address.map(_.max).max - address.map(_.base).min)
|
||||
require (size >= beatBytes)
|
||||
address.foreach { case a =>
|
||||
require (a.widen(size-1).base == address.head.widen(size-1).base,
|
||||
s"TLRegisterNode addresses (${address}) must be aligned to its size ${size}")
|
||||
}
|
||||
|
||||
// Calling this method causes the matching TL2 bundle to be
|
||||
// configured to route all requests to the listed RegFields.
|
||||
def regmap(mapping: RegField.Map*) = {
|
||||
val a = bundleIn(0).a
|
||||
val d = bundleIn(0).d
|
||||
val edge = edgesIn(0)
|
||||
|
||||
// Please forgive me ...
|
||||
val baseEnd = 0
|
||||
val (sizeEnd, sizeOff) = (edge.bundle.sizeBits + baseEnd, baseEnd)
|
||||
val (sourceEnd, sourceOff) = (edge.bundle.sourceBits + sizeEnd, sizeEnd)
|
||||
val (addrLoEnd, addrLoOff) = (log2Up(beatBytes) + sourceEnd, sourceEnd)
|
||||
|
||||
val params = RegMapperParams(log2Up(size/beatBytes), beatBytes, addrLoEnd)
|
||||
val in = Wire(Decoupled(new RegMapperInput(params)))
|
||||
in.bits.read := a.bits.opcode === TLMessages.Get
|
||||
in.bits.index := edge.addr_hi(a.bits)
|
||||
in.bits.data := a.bits.data
|
||||
in.bits.mask := a.bits.mask
|
||||
in.bits.extra := Cat(edge.addr_lo(a.bits), a.bits.source, a.bits.size)
|
||||
|
||||
// Invoke the register map builder
|
||||
val out = RegMapper(beatBytes, concurrency, undefZero, in, mapping:_*)
|
||||
|
||||
// No flow control needed
|
||||
in.valid := a.valid
|
||||
a.ready := in.ready
|
||||
d.valid := out.valid
|
||||
out.ready := d.ready
|
||||
|
||||
// We must restore the size and addr_lo to enable width adapters to work
|
||||
d.bits := edge.AccessAck(
|
||||
fromAddress = out.bits.extra(addrLoEnd-1, addrLoOff),
|
||||
fromSink = UInt(0), // our unique sink id
|
||||
toSource = out.bits.extra(sourceEnd-1, sourceOff),
|
||||
lgSize = out.bits.extra(sizeEnd-1, sizeOff))
|
||||
|
||||
// avoid a Mux on the data bus by manually overriding two fields
|
||||
d.bits.data := out.bits.data
|
||||
d.bits.opcode := Mux(out.bits.read, TLMessages.AccessAckData, TLMessages.AccessAck)
|
||||
|
||||
// Tie off unused channels
|
||||
bundleIn(0).b.valid := Bool(false)
|
||||
bundleIn(0).c.ready := Bool(true)
|
||||
bundleIn(0).e.ready := Bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
object TLRegisterNode
|
||||
{
|
||||
def apply(
|
||||
address: Seq[AddressSet],
|
||||
device: Device,
|
||||
deviceKey: String = "reg/control",
|
||||
concurrency: Int = 0,
|
||||
beatBytes: Int = 4,
|
||||
undefZero: Boolean = true,
|
||||
executable: Boolean = false) =
|
||||
new TLRegisterNode(address, device, deviceKey, concurrency, beatBytes, undefZero, executable)
|
||||
}
|
||||
|
||||
// These convenience methods below combine to make it possible to create a TL2
|
||||
// register mapped device from a totally abstract register mapped device.
|
||||
// See GPIO.scala in this directory for an example
|
||||
|
||||
abstract class TLRegisterRouterBase(devname: String, devcompat: Seq[String], val address: AddressSet, interrupts: Int, concurrency: Int, beatBytes: Int, undefZero: Boolean, executable: Boolean)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val device = new SimpleDevice(devname, devcompat)
|
||||
val node = TLRegisterNode(Seq(address), device, "reg/control", concurrency, beatBytes, undefZero, executable)
|
||||
val intnode = IntSourceNode(IntSourcePortSimple(num = interrupts, resources = Seq(Resource(device, "int"))))
|
||||
}
|
||||
|
||||
case class TLRegBundleArg(interrupts: HeterogeneousBag[Vec[Bool]], in: HeterogeneousBag[TLBundle])(implicit val p: Parameters)
|
||||
|
||||
class TLRegBundleBase(arg: TLRegBundleArg) extends Bundle
|
||||
{
|
||||
implicit val p = arg.p
|
||||
val interrupts = arg.interrupts
|
||||
val in = arg.in
|
||||
}
|
||||
|
||||
class TLRegBundle[P](val params: P, arg: TLRegBundleArg)(implicit p: Parameters) extends TLRegBundleBase(arg)
|
||||
|
||||
class TLRegModule[P, B <: TLRegBundleBase](val params: P, bundleBuilder: => B, router: TLRegisterRouterBase)
|
||||
extends LazyModuleImp(router) with HasRegMap
|
||||
{
|
||||
val io = bundleBuilder
|
||||
val interrupts = if (io.interrupts.isEmpty) Vec(0, Bool()) else io.interrupts(0)
|
||||
val address = router.address
|
||||
def regmap(mapping: RegField.Map*) = router.node.regmap(mapping:_*)
|
||||
}
|
||||
|
||||
class TLRegisterRouter[B <: TLRegBundleBase, M <: LazyModuleImp](
|
||||
val base: BigInt,
|
||||
val devname: String,
|
||||
val devcompat: Seq[String],
|
||||
val interrupts: Int = 0,
|
||||
val size: BigInt = 4096,
|
||||
val concurrency: Int = 0,
|
||||
val beatBytes: Int = 4,
|
||||
val undefZero: Boolean = true,
|
||||
val executable: Boolean = false)
|
||||
(bundleBuilder: TLRegBundleArg => B)
|
||||
(moduleBuilder: (=> B, TLRegisterRouterBase) => M)(implicit p: Parameters)
|
||||
extends TLRegisterRouterBase(devname, devcompat, AddressSet(base, size-1), interrupts, concurrency, beatBytes, undefZero, executable)
|
||||
{
|
||||
require (isPow2(size))
|
||||
// require (size >= 4096) ... not absolutely required, but highly recommended
|
||||
|
||||
lazy val module = moduleBuilder(bundleBuilder(TLRegBundleArg(intnode.bundleOut, node.bundleIn)), this)
|
||||
}
|
287
src/main/scala/tilelink/RegisterRouterTest.scala
Normal file
287
src/main/scala/tilelink/RegisterRouterTest.scala
Normal file
@ -0,0 +1,287 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.regmapper._
|
||||
import freechips.rocketchip.unittest._
|
||||
import freechips.rocketchip.util.{Pow2ClockDivider}
|
||||
|
||||
object LFSR16Seed
|
||||
{
|
||||
def apply(seed: Int): UInt =
|
||||
{
|
||||
val width = 16
|
||||
val lfsr = Reg(init=UInt((seed*0x7231) % 65536, width))
|
||||
lfsr := Cat(lfsr(0)^lfsr(2)^lfsr(3)^lfsr(5), lfsr(width-1,1))
|
||||
lfsr
|
||||
}
|
||||
}
|
||||
|
||||
class RRTestCombinational(val bits: Int, rvalid: Bool => Bool, wready: Bool => Bool) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val rvalid = Bool(OUTPUT)
|
||||
val rready = Bool(INPUT)
|
||||
val rdata = UInt(OUTPUT, width = bits)
|
||||
val wvalid = Bool(INPUT)
|
||||
val wready = Bool(OUTPUT)
|
||||
val wdata = UInt(INPUT, width = bits)
|
||||
}
|
||||
|
||||
val reg = RegInit(UInt(0, width = bits))
|
||||
|
||||
val rvalid_s = rvalid(io.rready)
|
||||
val wready_s = wready(io.wvalid)
|
||||
io.rvalid := rvalid_s
|
||||
io.wready := wready_s
|
||||
|
||||
io.rdata := Mux(rvalid_s && io.rready, reg, UInt(0))
|
||||
when (io.wvalid && wready_s) { reg := io.wdata }
|
||||
}
|
||||
|
||||
object RRTestCombinational
|
||||
{
|
||||
private var seed = 42
|
||||
|
||||
def always: Bool => Bool = _ => Bool(true)
|
||||
|
||||
def random: Bool => Bool = { ready =>
|
||||
seed = seed + 1
|
||||
LFSR16Seed(seed)(0)
|
||||
}
|
||||
|
||||
def delay(x: Int): Bool => Bool = { ready =>
|
||||
val reg = RegInit(UInt(0, width = log2Ceil(x+1)))
|
||||
val valid = reg === UInt(0)
|
||||
reg := Mux(ready && valid, UInt(x), Mux(valid, UInt(0), reg - UInt(1)))
|
||||
valid
|
||||
}
|
||||
|
||||
def combo(bits: Int, rvalid: Bool => Bool, wready: Bool => Bool): RegField = {
|
||||
val combo = Module(new RRTestCombinational(bits, rvalid, wready))
|
||||
RegField(bits,
|
||||
RegReadFn { ready => combo.io.rready := ready; (combo.io.rvalid, combo.io.rdata) },
|
||||
RegWriteFn { (valid, data) => combo.io.wvalid := valid; combo.io.wdata := data; combo.io.wready })
|
||||
}
|
||||
}
|
||||
|
||||
class RRTestRequest(val bits: Int,
|
||||
rflow: (Bool, Bool, UInt) => (Bool, Bool, UInt),
|
||||
wflow: (Bool, Bool, UInt) => (Bool, Bool, UInt)) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val rivalid = Bool(INPUT)
|
||||
val riready = Bool(OUTPUT)
|
||||
val rovalid = Bool(OUTPUT)
|
||||
val roready = Bool(INPUT)
|
||||
val rdata = UInt(OUTPUT, width = bits)
|
||||
val wivalid = Bool(INPUT)
|
||||
val wiready = Bool(OUTPUT)
|
||||
val wovalid = Bool(OUTPUT)
|
||||
val woready = Bool(INPUT)
|
||||
val wdata = UInt(INPUT, width = bits)
|
||||
}
|
||||
|
||||
val (riready, rovalid, _) = rflow(io.rivalid, io.roready, UInt(0, width = 1))
|
||||
val (wiready, wovalid, wdata) = wflow(io.wivalid, io.woready, io.wdata)
|
||||
val reg = RegInit(UInt(0, width = bits))
|
||||
|
||||
io.riready := riready
|
||||
io.rovalid := rovalid
|
||||
io.wiready := wiready
|
||||
io.wovalid := wovalid
|
||||
|
||||
val rofire = io.roready && rovalid
|
||||
val wofire = io.woready && wovalid
|
||||
|
||||
io.rdata := Mux(rofire, reg, UInt(0))
|
||||
when (wofire) { reg := wdata }
|
||||
}
|
||||
|
||||
object RRTestRequest
|
||||
{
|
||||
private var seed = 1231
|
||||
def pipe(x: Int): (Bool, Bool, UInt) => (Bool, Bool, UInt) = { (ivalid, oready, idata) =>
|
||||
val full = RegInit(Vec.fill(x)(Bool(false)))
|
||||
val ready = Wire(Vec(x, Bool()))
|
||||
val data = Reg(Vec(x, UInt(width = idata.getWidth)))
|
||||
// Construct a classic bubble-filling pipeline
|
||||
ready(x-1) := oready || !full(x-1)
|
||||
when (ready(0)) { data(0) := idata }
|
||||
when (ready(0)) { full(0) := ivalid }
|
||||
((ready.init zip ready.tail) zip full.init) foreach { case ((self, next), full) =>
|
||||
self := next || !full
|
||||
}
|
||||
((data.init zip data.tail) zip ready.tail) foreach { case ((prev, self), ready) =>
|
||||
when (ready) { self := prev }
|
||||
}
|
||||
((full.init zip full.tail) zip ready.tail) foreach { case ((prev, self), ready) =>
|
||||
when (ready) { self := prev }
|
||||
}
|
||||
(ready(0), full(x-1), data(x-1))
|
||||
}
|
||||
|
||||
def busy: (Bool, Bool, UInt) => (Bool, Bool, UInt) = {
|
||||
seed = seed + 1
|
||||
(ivalid, oready, idata) => {
|
||||
val lfsr = LFSR16Seed(seed)
|
||||
val busy = RegInit(Bool(false))
|
||||
val data = Reg(UInt(width = idata.getWidth))
|
||||
val progress = lfsr(0)
|
||||
val iready = progress && !busy
|
||||
val ovalid = progress && busy
|
||||
when (progress) {
|
||||
busy := Mux(busy, !oready, ivalid)
|
||||
}
|
||||
when (ivalid && iready) { data := idata }
|
||||
(iready, ovalid, data)
|
||||
}
|
||||
}
|
||||
|
||||
def request(bits: Int,
|
||||
rflow: (Bool, Bool, UInt) => (Bool, Bool, UInt),
|
||||
wflow: (Bool, Bool, UInt) => (Bool, Bool, UInt)): RegField = {
|
||||
val request = Module(new RRTestRequest(bits, rflow, wflow))
|
||||
RegField(bits,
|
||||
RegReadFn { (rivalid, roready) =>
|
||||
request.io.rivalid := rivalid
|
||||
request.io.roready := roready
|
||||
(request.io.riready, request.io.rovalid, request.io.rdata) },
|
||||
RegWriteFn { (wivalid, woready, wdata) =>
|
||||
request.io.wivalid := wivalid
|
||||
request.io.woready := woready
|
||||
request.io.wdata := wdata
|
||||
(request.io.wiready, request.io.wovalid) })
|
||||
}
|
||||
}
|
||||
|
||||
object RRTest0Map
|
||||
{
|
||||
import RRTestCombinational._
|
||||
|
||||
def aa(bits: Int) = combo(bits, always, always)
|
||||
def ar(bits: Int) = combo(bits, always, random)
|
||||
def ad(bits: Int) = combo(bits, always, delay(11))
|
||||
def ae(bits: Int) = combo(bits, always, delay(5))
|
||||
def ra(bits: Int) = combo(bits, random, always)
|
||||
def rr(bits: Int) = combo(bits, random, random)
|
||||
def rd(bits: Int) = combo(bits, random, delay(11))
|
||||
def re(bits: Int) = combo(bits, random, delay(5))
|
||||
def da(bits: Int) = combo(bits, delay(5), always)
|
||||
def dr(bits: Int) = combo(bits, delay(5), random)
|
||||
def dd(bits: Int) = combo(bits, delay(5), delay(5))
|
||||
def de(bits: Int) = combo(bits, delay(5), delay(11))
|
||||
def ea(bits: Int) = combo(bits, delay(11), always)
|
||||
def er(bits: Int) = combo(bits, delay(11), random)
|
||||
def ed(bits: Int) = combo(bits, delay(11), delay(5))
|
||||
def ee(bits: Int) = combo(bits, delay(11), delay(11))
|
||||
|
||||
// All fields must respect byte alignment, or else it won't behave like an SRAM
|
||||
def map = Seq(
|
||||
0 -> Seq(aa(8), ar(8), ad(8), ae(8)),
|
||||
4 -> Seq(ra(8), rr(8), rd(8), re(8)),
|
||||
8 -> Seq(da(8), dr(8), dd(8), de(8)),
|
||||
12 -> Seq(ea(8), er(8), ed(8), ee(8)),
|
||||
16 -> Seq(aa(3), ar(5), ad(1), ae(7), ra(2), rr(6), rd(4), re(4)),
|
||||
20 -> Seq(da(3), dr(5), dd(1), de(7), ea(2), er(6), ed(4), ee(4)),
|
||||
24 -> Seq(aa(8), rr(8), dd(8), ee(8)),
|
||||
28 -> Seq(ar(8), rd(8), de(8), ea(8)))
|
||||
}
|
||||
|
||||
object RRTest1Map
|
||||
{
|
||||
import RRTestRequest._
|
||||
|
||||
def pp(bits: Int) = request(bits, pipe(3), pipe(3))
|
||||
def pb(bits: Int) = request(bits, pipe(3), busy)
|
||||
def bp(bits: Int) = request(bits, busy, pipe(3))
|
||||
def bb(bits: Int) = request(bits, busy, busy)
|
||||
|
||||
def map = RRTest0Map.map.take(6) ++ Seq(
|
||||
24 -> Seq(pp(8), pb(8), bp(8), bb(8)),
|
||||
28 -> Seq(pp(3), pb(5), bp(1), bb(7), pb(5), bp(3), pp(4), bb(4)))
|
||||
}
|
||||
|
||||
trait RRTest0Bundle
|
||||
{
|
||||
}
|
||||
|
||||
trait RRTest0Module extends HasRegMap
|
||||
{
|
||||
regmap(RRTest0Map.map:_*)
|
||||
}
|
||||
|
||||
class RRTest0(address: BigInt)(implicit p: Parameters) extends TLRegisterRouter(address, "test0", Nil, 0, 32, 0, 4)(
|
||||
new TLRegBundle((), _) with RRTest0Bundle)(
|
||||
new TLRegModule((), _, _) with RRTest0Module)
|
||||
|
||||
trait RRTest1Bundle
|
||||
{
|
||||
}
|
||||
|
||||
trait RRTest1Module extends Module with HasRegMap
|
||||
{
|
||||
val clocks = Module(new Pow2ClockDivider(2))
|
||||
|
||||
def x(bits: Int) = {
|
||||
val field = UInt(width = bits)
|
||||
|
||||
val readCross = Module(new RegisterReadCrossing(field))
|
||||
readCross.io.master_clock := clock
|
||||
readCross.io.master_reset := reset
|
||||
readCross.io.master_bypass := Bool(false)
|
||||
readCross.io.slave_clock := clocks.io.clock_out
|
||||
readCross.io.slave_reset := reset
|
||||
|
||||
val writeCross = Module(new RegisterWriteCrossing(field))
|
||||
writeCross.io.master_clock := clock
|
||||
writeCross.io.master_reset := reset
|
||||
writeCross.io.master_bypass := Bool(false)
|
||||
writeCross.io.slave_clock := clocks.io.clock_out
|
||||
writeCross.io.slave_reset := reset
|
||||
|
||||
readCross.io.slave_register := writeCross.io.slave_register
|
||||
RegField(bits, readCross.io.master_port, writeCross.io.master_port)
|
||||
}
|
||||
|
||||
val map = RRTest1Map.map.drop(1) ++ Seq(0 -> Seq(x(8), x(8), x(8), x(8)))
|
||||
regmap(map:_*)
|
||||
}
|
||||
|
||||
class RRTest1(address: BigInt)(implicit p: Parameters) extends TLRegisterRouter(address, "test1", Nil, 0, 32, 6, 4)(
|
||||
new TLRegBundle((), _) with RRTest1Bundle)(
|
||||
new TLRegModule((), _, _) with RRTest1Module)
|
||||
|
||||
class FuzzRRTest0(txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val rrtr = LazyModule(new RRTest0(0x400))
|
||||
|
||||
rrtr.node := TLFragmenter(4, 32)(TLDelayer(0.1)(fuzz.node))
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzz.module.io.finished
|
||||
}
|
||||
}
|
||||
|
||||
class TLRR0Test(txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new FuzzRRTest0(txns)).module).io.finished
|
||||
}
|
||||
|
||||
class FuzzRRTest1(txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val rrtr = LazyModule(new RRTest1(0x400))
|
||||
|
||||
rrtr.node := TLFragmenter(4, 32)(TLDelayer(0.1)(fuzz.node))
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzz.module.io.finished
|
||||
}
|
||||
}
|
||||
|
||||
class TLRR1Test(txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new FuzzRRTest1(txns)).module).io.finished
|
||||
}
|
||||
|
110
src/main/scala/tilelink/SRAM.scala
Normal file
110
src/main/scala/tilelink/SRAM.scala
Normal file
@ -0,0 +1,110 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.experimental.chiselName
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
|
||||
class TLRAM(address: AddressSet, executable: Boolean = true, beatBytes: Int = 4, name: Option[String] = None)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
private val resources =
|
||||
name.map(new SimpleDevice(_, Seq("sifive,sram0")).reg("mem")).getOrElse(new MemoryDevice().reg)
|
||||
|
||||
val node = TLManagerNode(Seq(TLManagerPortParameters(
|
||||
Seq(TLManagerParameters(
|
||||
address = List(address),
|
||||
resources = resources,
|
||||
regionType = RegionType.UNCACHED,
|
||||
executable = executable,
|
||||
supportsGet = TransferSizes(1, beatBytes),
|
||||
supportsPutPartial = TransferSizes(1, beatBytes),
|
||||
supportsPutFull = TransferSizes(1, beatBytes),
|
||||
fifoId = Some(0))), // requests are handled in order
|
||||
beatBytes = beatBytes,
|
||||
minLatency = 1))) // no bypass needed for this device
|
||||
|
||||
// We require the address range to include an entire beat (for the write mask)
|
||||
require ((address.mask & (beatBytes-1)) == beatBytes-1)
|
||||
|
||||
lazy val module = new Implementation
|
||||
@chiselName class Implementation extends LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
}
|
||||
|
||||
def bigBits(x: BigInt, tail: List[Boolean] = List.empty[Boolean]): List[Boolean] =
|
||||
if (x == 0) tail.reverse else bigBits(x >> 1, ((x & 1) == 1) :: tail)
|
||||
val mask = bigBits(address.mask >> log2Ceil(beatBytes))
|
||||
|
||||
val in = io.in(0)
|
||||
val edge = node.edgesIn(0)
|
||||
|
||||
val addrBits = (mask zip edge.addr_hi(in.a.bits).toBools).filter(_._1).map(_._2)
|
||||
val memAddress = Cat(addrBits.reverse)
|
||||
val mem = SeqMem(1 << addrBits.size, Vec(beatBytes, Bits(width = 8)))
|
||||
|
||||
val d_full = RegInit(Bool(false))
|
||||
val d_read = Reg(Bool())
|
||||
val d_size = Reg(UInt())
|
||||
val d_source = Reg(UInt())
|
||||
val d_addr = Reg(UInt())
|
||||
val d_data = Wire(UInt())
|
||||
|
||||
// Flow control
|
||||
when (in.d.fire()) { d_full := Bool(false) }
|
||||
when (in.a.fire()) { d_full := Bool(true) }
|
||||
in.d.valid := d_full
|
||||
in.a.ready := in.d.ready || !d_full
|
||||
|
||||
in.d.bits := edge.AccessAck(d_addr, UInt(0), d_source, d_size)
|
||||
// avoid data-bus Mux
|
||||
in.d.bits.data := d_data
|
||||
in.d.bits.opcode := Mux(d_read, TLMessages.AccessAckData, TLMessages.AccessAck)
|
||||
|
||||
val read = in.a.bits.opcode === TLMessages.Get
|
||||
val rdata = Wire(Vec(beatBytes, Bits(width = 8)))
|
||||
val wdata = Vec.tabulate(beatBytes) { i => in.a.bits.data(8*(i+1)-1, 8*i) }
|
||||
d_data := Cat(rdata.reverse)
|
||||
when (in.a.fire()) {
|
||||
d_read := read
|
||||
d_size := in.a.bits.size
|
||||
d_source := in.a.bits.source
|
||||
d_addr := edge.addr_lo(in.a.bits)
|
||||
}
|
||||
|
||||
// exactly this pattern is required to get a RWM memory
|
||||
when (in.a.fire() && !read) {
|
||||
mem.write(memAddress, wdata, in.a.bits.mask.toBools)
|
||||
}
|
||||
val ren = in.a.fire() && read
|
||||
rdata := mem.readAndHold(memAddress, ren)
|
||||
|
||||
// Tie off unused channels
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesizeable unit testing */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
class TLRAMSimple(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val model = LazyModule(new TLRAMModel("SRAMSimple"))
|
||||
val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff), beatBytes = ramBeatBytes))
|
||||
|
||||
model.node := fuzz.node
|
||||
ram.node := TLDelayer(0.25)(model.node)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzz.module.io.finished
|
||||
}
|
||||
}
|
||||
|
||||
class TLRAMSimpleTest(ramBeatBytes: Int, txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new TLRAMSimple(ramBeatBytes, txns)).module).io.finished
|
||||
}
|
88
src/main/scala/tilelink/SourceShrinker.scala
Normal file
88
src/main/scala/tilelink/SourceShrinker.scala
Normal file
@ -0,0 +1,88 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
import scala.math.{min,max}
|
||||
|
||||
class TLSourceShrinker(maxInFlight: Int)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
require (maxInFlight > 0)
|
||||
|
||||
// The SourceShrinker completely destroys all FIFO property guarantees
|
||||
private val client = TLClientParameters(
|
||||
name = "TLSourceShrinker",
|
||||
sourceId = IdRange(0, maxInFlight))
|
||||
val node = TLAdapterNode(
|
||||
// We erase all client information since we crush the source Ids
|
||||
clientFn = { _ => TLClientPortParameters(clients = Seq(client)) },
|
||||
managerFn = { mp => mp.copy(managers = mp.managers.map(_.copy(fifoId = None))) })
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
// Acquires cannot pass this adapter; it makes Probes impossible
|
||||
require (!edgeIn.client.anySupportProbe ||
|
||||
!edgeOut.manager.anySupportAcquireB)
|
||||
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
|
||||
if (maxInFlight >= edgeIn.client.endSourceId) {
|
||||
out.a <> in.a
|
||||
in.d <> out.d
|
||||
} else {
|
||||
// State tracking
|
||||
val sourceIdMap = Mem(maxInFlight, in.a.bits.source)
|
||||
val allocated = RegInit(UInt(0, width = maxInFlight))
|
||||
val nextFreeOH = ~(leftOR(~allocated) << 1) & ~allocated
|
||||
val nextFree = OHToUInt(nextFreeOH)
|
||||
val full = allocated.andR()
|
||||
|
||||
val a_first = edgeIn.first(in.a)
|
||||
val d_last = edgeIn.last(in.d)
|
||||
|
||||
val block = a_first && full
|
||||
in.a.ready := out.a.ready && !block
|
||||
out.a.valid := in.a.valid && !block
|
||||
out.a.bits := in.a.bits
|
||||
out.a.bits.source := nextFree holdUnless a_first
|
||||
|
||||
in.d <> out.d
|
||||
in.d.bits.source := sourceIdMap(out.d.bits.source)
|
||||
|
||||
when (a_first && in.a.fire()) {
|
||||
sourceIdMap(nextFree) := in.a.bits.source
|
||||
}
|
||||
|
||||
val alloc = a_first && in.a.fire()
|
||||
val free = d_last && in.d.fire()
|
||||
val alloc_id = Mux(alloc, nextFreeOH, UInt(0))
|
||||
val free_id = Mux(free, UIntToOH(out.d.bits.source), UInt(0))
|
||||
allocated := (allocated | alloc_id) & ~free_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLSourceShrinker
|
||||
{
|
||||
// applied to the TL source node; y.node := TLSourceShrinker(n)(x.node)
|
||||
def apply(maxInFlight: Int)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val shrinker = LazyModule(new TLSourceShrinker(maxInFlight))
|
||||
shrinker.node := x
|
||||
shrinker.node
|
||||
}
|
||||
}
|
120
src/main/scala/tilelink/Splitter.scala
Normal file
120
src/main/scala/tilelink/Splitter.scala
Normal file
@ -0,0 +1,120 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
|
||||
class TLSplitter(policy: TLArbiter.Policy = TLArbiter.roundRobin)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLSplitterNode(
|
||||
clientFn = { case SplitterArg(newSize, ports) =>
|
||||
if (newSize == 0) Nil else
|
||||
Seq.fill(newSize / ports.size) { ports }.flatten
|
||||
},
|
||||
managerFn = { case SplitterArg(newSize, ports) =>
|
||||
if (newSize == 0) Nil else
|
||||
ports.grouped(newSize).toList.transpose.map { seq =>
|
||||
val fifoIdFactory = TLXbar.relabeler()
|
||||
val outputIdRanges = TLXbar.mapOutputIds(seq)
|
||||
seq(0).copy(
|
||||
minLatency = seq.map(_.minLatency).min,
|
||||
endSinkId = outputIdRanges.map(_.map(_.end).getOrElse(0)).max,
|
||||
managers = seq.zipWithIndex.flatMap { case (port, i) =>
|
||||
require (port.beatBytes == seq(0).beatBytes,
|
||||
s"Splitter data widths don't match: ${port.managers.map(_.name)} has ${port.beatBytes}B vs ${seq(0).managers.map(_.name)} has ${seq(0).beatBytes}B")
|
||||
val fifoIdMapper = fifoIdFactory()
|
||||
port.managers map { manager => manager.copy(
|
||||
fifoId = manager.fifoId.map(fifoIdMapper(_))
|
||||
)}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
def group[T](x: Seq[T]) =
|
||||
if (x.isEmpty) Nil else x.grouped(node.edgesIn.size).toList.transpose
|
||||
|
||||
if (node.edgesOut.size == node.edgesIn.size) {
|
||||
io.out <> io.in
|
||||
} else ((node.edgesIn zip io.in) zip (group(node.edgesOut) zip group(io.out))) foreach {
|
||||
case ((edgeIn, io_in), (edgesOut, io_out)) =>
|
||||
|
||||
// Grab the port ID mapping
|
||||
val outputIdRanges = TLXbar.mapOutputIds(edgesOut.map(_.manager))
|
||||
|
||||
// Find a good mask for address decoding
|
||||
val port_addrs = edgesOut.map(_.manager.managers.map(_.address).flatten)
|
||||
val routingMask = AddressDecoder(port_addrs)
|
||||
val route_addrs = port_addrs.map(seq => AddressSet.unify(seq.map(_.widen(~routingMask)).distinct))
|
||||
val outputPorts = route_addrs.map(seq => (addr: UInt) => seq.map(_.contains(addr)).reduce(_ || _))
|
||||
|
||||
// We need an intermediate size of bundle with the widest possible identifiers
|
||||
val wide_bundle = TLBundleParameters.union(Seq(io_in.params) ++ io_out.map(_.params))
|
||||
|
||||
// Transform input bundle sources (sinks use global namespace on both sides)
|
||||
val in = Wire(TLBundle(wide_bundle))
|
||||
in.a <> io_in.a
|
||||
io_in.d <> in.d
|
||||
|
||||
if (edgeIn.client.anySupportProbe && edgesOut.exists(_.manager.anySupportAcquireB)) {
|
||||
in.c <> io_in.c
|
||||
in.e <> io_in.e
|
||||
io_in.b <> in.b
|
||||
} else {
|
||||
in.c.valid := Bool(false)
|
||||
in.e.valid := Bool(false)
|
||||
in.b.ready := Bool(false)
|
||||
io_in.c.ready := Bool(true)
|
||||
io_in.e.ready := Bool(true)
|
||||
io_in.b.valid := Bool(false)
|
||||
}
|
||||
|
||||
// Handle size = 1 gracefully (Chisel3 empty range is broken)
|
||||
def trim(id: UInt, size: Int) = if (size <= 1) UInt(0) else id(log2Ceil(size)-1, 0)
|
||||
|
||||
// Transform output bundle sinks (sources use global namespace on both sides)
|
||||
val out = Wire(Vec(io_out.size, TLBundle(wide_bundle)))
|
||||
for (i <- 0 until out.size) {
|
||||
val r = outputIdRanges(i)
|
||||
|
||||
io_out(i).a <> out(i).a
|
||||
out(i).d <> io_out(i).d
|
||||
out(i).d.bits.sink := io_out(i).d.bits.sink | UInt(r.map(_.start).getOrElse(0))
|
||||
|
||||
if (edgesOut(i).manager.anySupportAcquireB && edgeIn.client.anySupportProbe) {
|
||||
io_out(i).c <> out(i).c
|
||||
io_out(i).e <> out(i).e
|
||||
out(i).b <> io_out(i).b
|
||||
io_out(i).e.bits.sink := trim(out(i).e.bits.sink, r.map(_.size).getOrElse(0))
|
||||
} else {
|
||||
out(i).c.ready := Bool(false)
|
||||
out(i).e.ready := Bool(false)
|
||||
out(i).b.valid := Bool(false)
|
||||
io_out(i).c.valid := Bool(false)
|
||||
io_out(i).e.valid := Bool(false)
|
||||
io_out(i).b.ready := Bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
val requestA = Vec(outputPorts.map { o => o(in.a.bits.address) })
|
||||
val requestC = Vec(outputPorts.map { o => o(in.c.bits.address) })
|
||||
val requestE = Vec(outputIdRanges.map { o => o.map(_.contains(in.e.bits.sink)).getOrElse(Bool(false)) })
|
||||
(out.map(_.a) zip TLXbar.fanout(in.a, requestA)) foreach { case (o, i) => o <> i }
|
||||
(out.map(_.c) zip TLXbar.fanout(in.c, requestC)) foreach { case (o, i) => o <> i }
|
||||
(out.map(_.e) zip TLXbar.fanout(in.e, requestE)) foreach { case (o, i) => o <> i }
|
||||
|
||||
val beatsB = Vec((out zip edgesOut) map { case (o, e) => e.numBeats1(o.b.bits) })
|
||||
val beatsD = Vec((out zip edgesOut) map { case (o, e) => e.numBeats1(o.d.bits) })
|
||||
TLArbiter(policy)(in.b, (beatsB zip out.map(_.b)):_*)
|
||||
TLArbiter(policy)(in.d, (beatsD zip out.map(_.d)):_*)
|
||||
}
|
||||
}
|
||||
}
|
202
src/main/scala/tilelink/ToAHB.scala
Normal file
202
src/main/scala/tilelink/ToAHB.scala
Normal file
@ -0,0 +1,202 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.amba.ahb._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
import scala.math.{min, max}
|
||||
import AHBParameters._
|
||||
|
||||
case class TLToAHBNode() extends MixedAdapterNode(TLImp, AHBImp)(
|
||||
dFn = { case TLClientPortParameters(clients, unsafeAtomics, minLatency) =>
|
||||
val masters = clients.map { case c => AHBMasterParameters(name = c.name, nodePath = c.nodePath) }
|
||||
AHBMasterPortParameters(masters)
|
||||
},
|
||||
uFn = { case AHBSlavePortParameters(slaves, beatBytes) =>
|
||||
val managers = slaves.map { case s =>
|
||||
TLManagerParameters(
|
||||
address = s.address,
|
||||
resources = s.resources,
|
||||
regionType = s.regionType,
|
||||
executable = s.executable,
|
||||
nodePath = s.nodePath,
|
||||
supportsGet = s.supportsRead,
|
||||
supportsPutFull = s.supportsWrite, // but not PutPartial
|
||||
fifoId = Some(0))
|
||||
}
|
||||
TLManagerPortParameters(managers, beatBytes, 1, 1)
|
||||
})
|
||||
|
||||
class AHBControlBundle(params: TLEdge) extends GenericParameterizedBundle(params)
|
||||
{
|
||||
val full = Bool()
|
||||
val send = Bool() // => full+data
|
||||
val first = Bool()
|
||||
val last = Bool()
|
||||
val write = Bool()
|
||||
val size = UInt(width = params.bundle.sizeBits)
|
||||
val source = UInt(width = params.bundle.sourceBits)
|
||||
val hsize = UInt(width = AHBParameters.sizeBits)
|
||||
val hburst = UInt(width = AHBParameters.burstBits)
|
||||
val addr = UInt(width = params.bundle.addressBits)
|
||||
val data = UInt(width = params.bundle.dataBits)
|
||||
}
|
||||
|
||||
// The input side has either a flow queue (aFlow=true) or a pipe queue (aFlow=false)
|
||||
// The output side always has a flow queue
|
||||
class TLToAHB(val aFlow: Boolean = false)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLToAHBNode()
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val beatBytes = edgeOut.slave.beatBytes
|
||||
val maxTransfer = edgeOut.slave.maxTransfer
|
||||
val lgMax = log2Ceil(maxTransfer)
|
||||
val lgBytes = log2Ceil(beatBytes)
|
||||
|
||||
// Initial FSM state
|
||||
val resetState = Wire(new AHBControlBundle(edgeIn))
|
||||
resetState.full := Bool(false)
|
||||
resetState.send := Bool(false)
|
||||
resetState.first := Bool(true)
|
||||
// These are needed to appease AHB VIP:
|
||||
resetState.hsize := UInt(0)
|
||||
resetState.hburst:= UInt(0)
|
||||
resetState.addr := UInt(0)
|
||||
|
||||
// The stages of the combinational pipeline
|
||||
val reg = RegInit(resetState)
|
||||
val send = Wire(init = reg)
|
||||
val step = Wire(init = send)
|
||||
val next = Wire(init = step)
|
||||
reg := next
|
||||
|
||||
// Advance the FSM based on the result of this AHB beat
|
||||
when (send.send && !out.hreadyout) /* retry AHB */ {
|
||||
step.full := Bool(true)
|
||||
step.send := Bool(true)
|
||||
} .elsewhen (send.full && !send.send) /* retry beat */ {
|
||||
step.full := Bool(true)
|
||||
step.send := Bool(false)
|
||||
} .elsewhen (send.full && !send.last) /* continue burst */ {
|
||||
step.full := Bool(true)
|
||||
step.send := Bool(false) // => looks like a retry to injector
|
||||
step.first := Bool(false)
|
||||
step.last := (if (lgBytes + 1 >= lgMax) Bool(true) else
|
||||
!((UIntToOH1(send.size, lgMax) & ~send.addr) >> (lgBytes + 1)).orR())
|
||||
step.addr := Cat(send.addr(edgeIn.bundle.addressBits-1, lgMax), send.addr(lgMax-1, 0) + UInt(beatBytes))
|
||||
} .otherwise /* new burst */ {
|
||||
step.full := Bool(false)
|
||||
step.send := Bool(false)
|
||||
step.first := Bool(true)
|
||||
}
|
||||
|
||||
val d_block = Wire(Bool())
|
||||
val pre = if (aFlow) reg else step
|
||||
val post = if (aFlow) send else next
|
||||
|
||||
// Transform TL size into AHB hsize+hburst
|
||||
val a_sizeDelta = Cat(UInt(0, width = 1), in.a.bits.size) - UInt(lgBytes+1)
|
||||
val a_singleBeat = Bool(lgBytes >= lgMax) || a_sizeDelta(edgeIn.bundle.sizeBits)
|
||||
val a_logBeats1 = a_sizeDelta(edgeIn.bundle.sizeBits-1, 0)
|
||||
|
||||
// Pulse this every time we commit to sending an AHB request
|
||||
val a_commit = Wire(Bool())
|
||||
|
||||
// Inject A channel into FSM
|
||||
when (pre.send) /* busy */ {
|
||||
a_commit := Bool(false)
|
||||
in.a.ready := Bool(false)
|
||||
} .elsewhen (pre.full) /* retry beat (or continue burst) */ {
|
||||
post.send := !d_block && (!pre.write || in.a.valid)
|
||||
post.data := in.a.bits.data
|
||||
a_commit := !d_block && !pre.write // only read beats commit to a D beat answer
|
||||
in.a.ready := !d_block && pre.write
|
||||
} .otherwise /* new burst */ {
|
||||
a_commit := in.a.fire() // every first beat commits to a D beat answer
|
||||
in.a.ready := !d_block
|
||||
when (in.a.fire()) {
|
||||
post.full := Bool(true)
|
||||
post.send := Bool(true)
|
||||
post.last := a_singleBeat
|
||||
post.write := edgeIn.hasData(in.a.bits)
|
||||
post.size := in.a.bits.size
|
||||
post.source:= in.a.bits.source
|
||||
post.hsize := Mux(a_singleBeat, in.a.bits.size, UInt(lgBytes))
|
||||
post.hburst:= Mux(a_singleBeat, BURST_SINGLE, (a_logBeats1<<1) | UInt(1))
|
||||
post.addr := in.a.bits.address
|
||||
post.data := in.a.bits.data
|
||||
}
|
||||
}
|
||||
|
||||
out.hmastlock := Bool(false) // for now
|
||||
out.htrans := Mux(send.send, Mux(send.first, TRANS_NONSEQ, TRANS_SEQ), Mux(send.first, TRANS_IDLE, TRANS_BUSY))
|
||||
out.hsel := send.send || !send.first
|
||||
out.hready := out.hreadyout
|
||||
out.hwrite := send.write
|
||||
out.haddr := send.addr
|
||||
out.hsize := send.hsize
|
||||
out.hburst := send.hburst
|
||||
out.hprot := PROT_DEFAULT
|
||||
out.hwdata := RegEnable(send.data, out.hreadyout)
|
||||
|
||||
// We need a skidpad to capture D output:
|
||||
// We cannot know if the D response will be accepted until we have
|
||||
// presented it on D as valid. We also can't back-pressure AHB in the
|
||||
// data phase. Therefore, we must have enough space to save the all
|
||||
// commited AHB requests (A+D phases = 2). To decouple d_ready from
|
||||
// a_ready and htrans, we add another entry for aFlow=false.
|
||||
val depth = if (aFlow) 2 else 3
|
||||
val d = Wire(in.d)
|
||||
in.d <> Queue(d, depth, flow=true)
|
||||
assert (!d.valid || d.ready)
|
||||
|
||||
val d_flight = RegInit(UInt(0, width = 2))
|
||||
assert (d_flight <= UInt(depth))
|
||||
d_flight := d_flight + a_commit.asUInt - in.d.fire().asUInt
|
||||
d_block := d_flight >= UInt(depth)
|
||||
|
||||
val d_valid = RegInit(Bool(false))
|
||||
val d_error = Reg(Bool())
|
||||
val d_write = RegEnable(send.write, out.hreadyout)
|
||||
val d_source = RegEnable(send.source, out.hreadyout)
|
||||
val d_addr = RegEnable(send.addr, out.hreadyout)
|
||||
val d_size = RegEnable(send.size, out.hreadyout)
|
||||
|
||||
when (out.hreadyout) {
|
||||
d_valid := send.send && (send.last || !send.write)
|
||||
when (out.hresp) { d_error := d_write }
|
||||
when (send.first) { d_error := Bool(false) }
|
||||
}
|
||||
|
||||
d.valid := d_valid && out.hreadyout
|
||||
d.bits := edgeIn.AccessAck(d_addr, UInt(0), d_source, d_size, out.hrdata, out.hresp || d_error)
|
||||
d.bits.opcode := Mux(d_write, TLMessages.AccessAck, TLMessages.AccessAckData)
|
||||
|
||||
// AHB has no cache coherence
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLToAHB
|
||||
{
|
||||
// applied to the TL source node; y.node := TLToAHB()(x.node)
|
||||
def apply(aFlow: Boolean = true)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): AHBOutwardNode = {
|
||||
val ahb = LazyModule(new TLToAHB(aFlow))
|
||||
ahb.node := x
|
||||
ahb.node
|
||||
}
|
||||
}
|
100
src/main/scala/tilelink/ToAPB.scala
Normal file
100
src/main/scala/tilelink/ToAPB.scala
Normal file
@ -0,0 +1,100 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.amba.apb._
|
||||
import scala.math.{min, max}
|
||||
import APBParameters._
|
||||
|
||||
case class TLToAPBNode() extends MixedAdapterNode(TLImp, APBImp)(
|
||||
dFn = { case TLClientPortParameters(clients, unsafeAtomics, minLatency) =>
|
||||
val masters = clients.map { case c => APBMasterParameters(name = c.name, nodePath = c.nodePath) }
|
||||
APBMasterPortParameters(masters)
|
||||
},
|
||||
uFn = { case APBSlavePortParameters(slaves, beatBytes) =>
|
||||
val managers = slaves.map { case s =>
|
||||
TLManagerParameters(
|
||||
address = s.address,
|
||||
resources = s.resources,
|
||||
regionType = s.regionType,
|
||||
executable = s.executable,
|
||||
nodePath = s.nodePath,
|
||||
supportsGet = if (s.supportsRead) TransferSizes(1, beatBytes) else TransferSizes.none,
|
||||
supportsPutPartial = if (s.supportsWrite) TransferSizes(1, beatBytes) else TransferSizes.none,
|
||||
supportsPutFull = if (s.supportsWrite) TransferSizes(1, beatBytes) else TransferSizes.none,
|
||||
fifoId = Some(0)) // a common FIFO domain
|
||||
}
|
||||
TLManagerPortParameters(managers, beatBytes, 1, 0)
|
||||
})
|
||||
|
||||
// The input side has either a flow queue (aFlow=true) or a pipe queue (aFlow=false)
|
||||
// The output side always has a flow queue
|
||||
class TLToAPB(val aFlow: Boolean = true)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLToAPBNode()
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val beatBytes = edgeOut.slave.beatBytes
|
||||
val lgBytes = log2Ceil(beatBytes)
|
||||
|
||||
// APB has no cache coherence
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
|
||||
// We need a skidpad to capture D output:
|
||||
// We cannot know if the D response will be accepted until we have
|
||||
// presented it on D as valid. We also can't back-pressure APB in the
|
||||
// data phase. Therefore, we must have enough space to save the data
|
||||
// phase result. Whenever we have a queued response, we can not allow
|
||||
// APB to present new responses, so we must quash the address phase.
|
||||
val d = Wire(in.d)
|
||||
in.d <> Queue(d, 1, flow = true)
|
||||
|
||||
// We need an irrevocable input for APB to stall
|
||||
val a = Queue(in.a, 1, flow = aFlow, pipe = !aFlow)
|
||||
|
||||
val a_enable = RegInit(Bool(false))
|
||||
val a_sel = a.valid && RegNext(!in.d.valid || in.d.ready)
|
||||
val a_write = edgeIn.hasData(a.bits)
|
||||
|
||||
when (a_sel) { a_enable := Bool(true) }
|
||||
when (d.fire()) { a_enable := Bool(false) }
|
||||
|
||||
out.psel := a_sel
|
||||
out.penable := a_enable
|
||||
out.pwrite := a_write
|
||||
out.paddr := a.bits.address
|
||||
out.pprot := PROT_DEFAULT
|
||||
out.pwdata := a.bits.data
|
||||
out.pstrb := Mux(a_write, a.bits.mask, UInt(0))
|
||||
|
||||
a.ready := a_enable && out.pready
|
||||
d.valid := a_enable && out.pready
|
||||
assert (!d.valid || d.ready)
|
||||
|
||||
d.bits := edgeIn.AccessAck(a.bits, UInt(0), out.prdata, out.pslverr)
|
||||
d.bits.opcode := Mux(a_write, TLMessages.AccessAck, TLMessages.AccessAckData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLToAPB
|
||||
{
|
||||
// applied to the TL source node; y.node := TLToAPB()(x.node)
|
||||
def apply(aFlow: Boolean = true)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): APBOutwardNode = {
|
||||
val apb = LazyModule(new TLToAPB(aFlow))
|
||||
apb.node := x
|
||||
apb.node
|
||||
}
|
||||
}
|
243
src/main/scala/tilelink/ToAXI4.scala
Normal file
243
src/main/scala/tilelink/ToAXI4.scala
Normal file
@ -0,0 +1,243 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
import freechips.rocketchip.amba.axi4._
|
||||
import scala.math.{min, max}
|
||||
|
||||
case class TLToAXI4Node(beatBytes: Int, stripBits: Int = 0) extends MixedAdapterNode(TLImp, AXI4Imp)(
|
||||
dFn = { p =>
|
||||
p.clients.foreach { c =>
|
||||
require (c.sourceId.start % (1 << stripBits) == 0 &&
|
||||
c.sourceId.end % (1 << stripBits) == 0,
|
||||
"Cannot strip bits of aligned client ${c.name}: ${c.sourceId}")
|
||||
}
|
||||
val clients = p.clients.sortWith(TLToAXI4.sortByType _)
|
||||
val idSize = clients.map { c => if (c.requestFifo) 1 else (c.sourceId.size >> stripBits) }
|
||||
val idStart = idSize.scanLeft(0)(_+_).init
|
||||
val masters = ((idStart zip idSize) zip clients) map { case ((start, size), c) =>
|
||||
AXI4MasterParameters(
|
||||
name = c.name,
|
||||
id = IdRange(start, start+size),
|
||||
aligned = true,
|
||||
maxFlight = Some(if (c.requestFifo) c.sourceId.size else (1 << stripBits)),
|
||||
nodePath = c.nodePath)
|
||||
}
|
||||
AXI4MasterPortParameters(
|
||||
masters = masters,
|
||||
userBits = log2Ceil(p.endSourceId) + 4 + log2Ceil(beatBytes))
|
||||
},
|
||||
uFn = { p => TLManagerPortParameters(
|
||||
managers = p.slaves.map { case s =>
|
||||
TLManagerParameters(
|
||||
address = s.address,
|
||||
resources = s.resources,
|
||||
regionType = s.regionType,
|
||||
executable = s.executable,
|
||||
nodePath = s.nodePath,
|
||||
supportsGet = s.supportsRead,
|
||||
supportsPutFull = s.supportsWrite,
|
||||
supportsPutPartial = s.supportsWrite,
|
||||
fifoId = Some(0))},
|
||||
beatBytes = p.beatBytes,
|
||||
minLatency = p.minLatency)
|
||||
})
|
||||
|
||||
class TLToAXI4(beatBytes: Int, combinational: Boolean = true, adapterName: Option[String] = None, stripBits: Int = 0)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLToAXI4Node(beatBytes, stripBits)
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
val slaves = edgeOut.slave.slaves
|
||||
|
||||
// All pairs of slaves must promise that they will never interleave data
|
||||
require (slaves(0).interleavedId.isDefined)
|
||||
slaves.foreach { s => require (s.interleavedId == slaves(0).interleavedId) }
|
||||
|
||||
val axiDigits = String.valueOf(edgeOut.master.endId-1).length()
|
||||
val tlDigits = String.valueOf(edgeIn.client.endSourceId-1).length()
|
||||
|
||||
// Construct the source=>ID mapping table
|
||||
adapterName.foreach { n => println(s"$n AXI4-ID <= TL-Source mapping:") }
|
||||
val sourceStall = Wire(Vec(edgeIn.client.endSourceId, Bool()))
|
||||
val sourceTable = Wire(Vec(edgeIn.client.endSourceId, out.aw.bits.id))
|
||||
val idStall = Wire(init = Vec.fill(edgeOut.master.endId) { Bool(false) })
|
||||
var idCount = Array.fill(edgeOut.master.endId) { None:Option[Int] }
|
||||
val maps = (edgeIn.client.clients.sortWith(TLToAXI4.sortByType) zip edgeOut.master.masters) flatMap { case (c, m) =>
|
||||
for (i <- 0 until c.sourceId.size) {
|
||||
val id = m.id.start + (if (c.requestFifo) 0 else (i >> stripBits))
|
||||
sourceStall(c.sourceId.start + i) := idStall(id)
|
||||
sourceTable(c.sourceId.start + i) := UInt(id)
|
||||
}
|
||||
if (c.requestFifo) { idCount(m.id.start) = Some(c.sourceId.size) }
|
||||
adapterName.map { n =>
|
||||
val fmt = s"\t[%${axiDigits}d, %${axiDigits}d) <= [%${tlDigits}d, %${tlDigits}d) %s%s"
|
||||
println(fmt.format(m.id.start, m.id.end, c.sourceId.start, c.sourceId.end, c.name, if (c.supportsProbe) " CACHE" else ""))
|
||||
s"""{"axi4-id":[${m.id.start},${m.id.end}],"tilelink-id":[${c.sourceId.start},${c.sourceId.end}],"master":["${c.name}"],"cache":[${!(!c.supportsProbe)}]}"""
|
||||
}
|
||||
}
|
||||
|
||||
adapterName.foreach { n =>
|
||||
println("")
|
||||
ElaborationArtefacts.add(s"${n}.axi4.json", s"""{"mapping":[${maps.mkString(",")}]}""")
|
||||
}
|
||||
|
||||
// We need to keep the following state from A => D: (addr_lo, size, source)
|
||||
// All of those fields could potentially require 0 bits (argh. Chisel.)
|
||||
// We will pack all of that extra information into the user bits.
|
||||
|
||||
val sourceBits = log2Ceil(edgeIn.client.endSourceId)
|
||||
val sizeBits = log2Ceil(edgeIn.maxLgSize+1)
|
||||
val addrBits = log2Ceil(edgeIn.manager.beatBytes)
|
||||
val stateBits = addrBits + sizeBits + sourceBits // could be 0
|
||||
require (stateBits <= out.aw.bits.params.userBits)
|
||||
|
||||
val a_address = edgeIn.address(in.a.bits)
|
||||
val a_addr_lo = edgeIn.addr_lo(a_address)
|
||||
val a_source = in.a.bits.source
|
||||
val a_size = edgeIn.size(in.a.bits)
|
||||
val a_isPut = edgeIn.hasData(in.a.bits)
|
||||
val a_last = edgeIn.last(in.a)
|
||||
|
||||
// Make sure the fields are within the bounds we assumed
|
||||
assert (a_source < UInt(BigInt(1) << sourceBits))
|
||||
assert (a_size < UInt(BigInt(1) << sizeBits))
|
||||
assert (a_addr_lo < UInt(BigInt(1) << addrBits))
|
||||
|
||||
// Carefully pack/unpack fields into the state we send
|
||||
val baseEnd = 0
|
||||
val (sourceEnd, sourceOff) = (sourceBits + baseEnd, baseEnd)
|
||||
val (sizeEnd, sizeOff) = (sizeBits + sourceEnd, sourceEnd)
|
||||
val (addrEnd, addrOff) = (addrBits + sizeEnd, sizeEnd)
|
||||
require (addrEnd == stateBits)
|
||||
|
||||
val a_state = (a_source << sourceOff) | (a_size << sizeOff) | (a_addr_lo << addrOff)
|
||||
|
||||
val r_state = out.r.bits.user.getOrElse(UInt(0))
|
||||
val r_source = if (sourceBits > 0) r_state(sourceEnd-1, sourceOff) else UInt(0)
|
||||
val r_size = if (sizeBits > 0) r_state(sizeEnd -1, sizeOff) else UInt(0)
|
||||
val r_addr_lo = if (addrBits > 0) r_state(addrEnd -1, addrOff) else UInt(0)
|
||||
|
||||
val b_state = out.b.bits.user.getOrElse(UInt(0))
|
||||
val b_source = if (sourceBits > 0) b_state(sourceEnd-1, sourceOff) else UInt(0)
|
||||
val b_size = if (sizeBits > 0) b_state(sizeEnd -1, sizeOff) else UInt(0)
|
||||
val b_addr_lo = if (addrBits > 0) b_state(addrEnd -1, addrOff) else UInt(0)
|
||||
|
||||
// We need these Queues because AXI4 queues are irrevocable
|
||||
val depth = if (combinational) 1 else 2
|
||||
val out_arw = Wire(Decoupled(new AXI4BundleARW(out.params)))
|
||||
val out_w = Wire(out.w)
|
||||
out.w <> Queue.irrevocable(out_w, entries=depth, flow=combinational)
|
||||
val queue_arw = Queue.irrevocable(out_arw, entries=depth, flow=combinational)
|
||||
|
||||
// Fan out the ARW channel to AR and AW
|
||||
out.ar.bits := queue_arw.bits
|
||||
out.aw.bits := queue_arw.bits
|
||||
out.ar.valid := queue_arw.valid && !queue_arw.bits.wen
|
||||
out.aw.valid := queue_arw.valid && queue_arw.bits.wen
|
||||
queue_arw.ready := Mux(queue_arw.bits.wen, out.aw.ready, out.ar.ready)
|
||||
|
||||
val beatBytes = edgeIn.manager.beatBytes
|
||||
val maxSize = UInt(log2Ceil(beatBytes))
|
||||
val doneAW = RegInit(Bool(false))
|
||||
when (in.a.fire()) { doneAW := !a_last }
|
||||
|
||||
val arw = out_arw.bits
|
||||
arw.wen := a_isPut
|
||||
arw.id := sourceTable(a_source)
|
||||
arw.addr := a_address
|
||||
arw.len := UIntToOH1(a_size, AXI4Parameters.lenBits + log2Ceil(beatBytes)) >> log2Ceil(beatBytes)
|
||||
arw.size := Mux(a_size >= maxSize, maxSize, a_size)
|
||||
arw.burst := AXI4Parameters.BURST_INCR
|
||||
arw.lock := UInt(0) // not exclusive (LR/SC unsupported b/c no forward progress guarantee)
|
||||
arw.cache := UInt(0) // do not allow AXI to modify our transactions
|
||||
arw.prot := AXI4Parameters.PROT_PRIVILEDGED
|
||||
arw.qos := UInt(0) // no QoS
|
||||
arw.user.foreach { _ := a_state }
|
||||
|
||||
val stall = sourceStall(in.a.bits.source)
|
||||
in.a.ready := !stall && Mux(a_isPut, (doneAW || out_arw.ready) && out_w.ready, out_arw.ready)
|
||||
out_arw.valid := !stall && in.a.valid && Mux(a_isPut, !doneAW && out_w.ready, Bool(true))
|
||||
|
||||
out_w.valid := !stall && in.a.valid && a_isPut && (doneAW || out_arw.ready)
|
||||
out_w.bits.data := in.a.bits.data
|
||||
out_w.bits.strb := in.a.bits.mask
|
||||
out_w.bits.last := a_last
|
||||
|
||||
// R and B => D arbitration
|
||||
val r_holds_d = RegInit(Bool(false))
|
||||
when (out.r.fire()) { r_holds_d := !out.r.bits.last }
|
||||
// Give R higher priority than B
|
||||
val r_wins = out.r.valid || r_holds_d
|
||||
|
||||
out.r.ready := in.d.ready
|
||||
out.b.ready := in.d.ready && !r_wins
|
||||
in.d.valid := Mux(r_wins, out.r.valid, out.b.valid)
|
||||
|
||||
val r_error = out.r.bits.resp =/= AXI4Parameters.RESP_OKAY
|
||||
val b_error = out.b.bits.resp =/= AXI4Parameters.RESP_OKAY
|
||||
|
||||
val r_d = edgeIn.AccessAck(r_addr_lo, UInt(0), r_source, r_size, UInt(0), r_error)
|
||||
val b_d = edgeIn.AccessAck(b_addr_lo, UInt(0), b_source, b_size, b_error)
|
||||
|
||||
in.d.bits := Mux(r_wins, r_d, b_d)
|
||||
in.d.bits.data := out.r.bits.data // avoid a costly Mux
|
||||
|
||||
// We need to track if any reads or writes are inflight for a given ID.
|
||||
// If the opposite type arrives, we must stall until it completes.
|
||||
val a_sel = UIntToOH(arw.id, edgeOut.master.endId).toBools
|
||||
val d_sel = UIntToOH(Mux(r_wins, out.r.bits.id, out.b.bits.id), edgeOut.master.endId).toBools
|
||||
val d_last = Mux(r_wins, out.r.bits.last, Bool(true))
|
||||
// If FIFO was requested, ensure that R+W ordering is preserved
|
||||
(a_sel zip d_sel zip idStall zip idCount) filter { case (_, n) => n.map(_ > 1).getOrElse(false) } foreach { case (((as, ds), s), n) =>
|
||||
val count = RegInit(UInt(0, width = log2Ceil(n.get + 1)))
|
||||
val write = Reg(Bool())
|
||||
val idle = count === UInt(0)
|
||||
|
||||
val inc = as && out_arw.fire()
|
||||
val dec = ds && d_last && in.d.fire()
|
||||
count := count + inc.asUInt - dec.asUInt
|
||||
|
||||
assert (!dec || count =/= UInt(0)) // underflow
|
||||
assert (!inc || count =/= UInt(n.get)) // overflow
|
||||
|
||||
when (inc) { write := arw.wen }
|
||||
s := !idle && write =/= arw.wen
|
||||
}
|
||||
|
||||
// Tie off unused channels
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLToAXI4
|
||||
{
|
||||
// applied to the TL source node; y.node := TLToAXI4(beatBytes)(x.node)
|
||||
def apply(beatBytes: Int, combinational: Boolean = true, adapterName: Option[String] = None, stripBits: Int = 0)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): AXI4OutwardNode = {
|
||||
val axi4 = LazyModule(new TLToAXI4(beatBytes, combinational, adapterName, stripBits))
|
||||
axi4.node := x
|
||||
axi4.node
|
||||
}
|
||||
|
||||
def sortByType(a: TLClientParameters, b: TLClientParameters): Boolean = {
|
||||
if ( a.supportsProbe && !b.supportsProbe) return false
|
||||
if (!a.supportsProbe && b.supportsProbe) return true
|
||||
if ( a.requestFifo && !b.requestFifo ) return false
|
||||
if (!a.requestFifo && b.requestFifo ) return true
|
||||
return false
|
||||
}
|
||||
}
|
203
src/main/scala/tilelink/WidthWidget.scala
Normal file
203
src/main/scala/tilelink/WidthWidget.scala
Normal file
@ -0,0 +1,203 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.util._
|
||||
import scala.math.{min,max}
|
||||
|
||||
// innBeatBytes => the new client-facing bus width
|
||||
class TLWidthWidget(innerBeatBytes: Int)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLAdapterNode(
|
||||
clientFn = { case c => c },
|
||||
managerFn = { case m => m.copy(beatBytes = innerBeatBytes) })
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
def merge[T <: TLDataChannel](edgeIn: TLEdge, in: DecoupledIO[T], edgeOut: TLEdge, out: DecoupledIO[T]) = {
|
||||
val inBytes = edgeIn.manager.beatBytes
|
||||
val outBytes = edgeOut.manager.beatBytes
|
||||
val ratio = outBytes / inBytes
|
||||
|
||||
val rdata = Reg(UInt(width = (ratio-1)*inBytes*8))
|
||||
val rmask = Reg(UInt(width = (ratio-1)*inBytes))
|
||||
val data = Cat(edgeIn.data(in.bits), rdata)
|
||||
val mask = Cat(edgeIn.mask(in.bits), rmask)
|
||||
val address = edgeIn.address(in.bits)
|
||||
val size = edgeIn.size(in.bits)
|
||||
val hasData = edgeIn.hasData(in.bits)
|
||||
|
||||
val count = RegInit(UInt(0, width = log2Ceil(ratio)))
|
||||
val first = count === UInt(0)
|
||||
val limit = UIntToOH1(size, log2Ceil(outBytes)) >> log2Ceil(inBytes)
|
||||
val last = count === limit || !hasData
|
||||
|
||||
when (in.fire()) {
|
||||
rdata := data >> inBytes*8
|
||||
rmask := mask >> inBytes
|
||||
count := count + UInt(1)
|
||||
when (last) { count := UInt(0) }
|
||||
}
|
||||
|
||||
val cases = Seq.tabulate(log2Ceil(ratio)+1) { i =>
|
||||
val high = outBytes
|
||||
val take = (1 << i)*inBytes
|
||||
(Fill(1 << (log2Ceil(ratio)-i), data(high*8-1, (high-take)*8)),
|
||||
Fill(1 << (log2Ceil(ratio)-i), mask(high -1, (high-take))))
|
||||
}
|
||||
val dataMux = Vec.tabulate(log2Ceil(edgeIn.maxTransfer)+1) { lgSize =>
|
||||
cases(min(max(lgSize - log2Ceil(inBytes), 0), log2Ceil(ratio)))._1
|
||||
}
|
||||
val maskMux = Vec.tabulate(log2Ceil(edgeIn.maxTransfer)+1) { lgSize =>
|
||||
cases(min(max(lgSize - log2Ceil(inBytes), 0), log2Ceil(ratio)))._2
|
||||
}
|
||||
|
||||
val dataOut = if (edgeIn.staticHasData(in.bits) == Some(false)) UInt(0) else dataMux(size)
|
||||
lazy val maskFull = edgeOut.mask(address, size)
|
||||
lazy val maskOut = Mux(hasData, maskMux(size) & maskFull, maskFull)
|
||||
|
||||
in.ready := out.ready || !last
|
||||
out.valid := in.valid && last
|
||||
out.bits := in.bits
|
||||
edgeOut.data(out.bits) := dataOut
|
||||
|
||||
out.bits match {
|
||||
case a: TLBundleA => a.mask := maskOut
|
||||
case b: TLBundleB => b.mask := maskOut
|
||||
case c: TLBundleC => ()
|
||||
case d: TLBundleD => ()
|
||||
// addr_lo gets padded with 0s on D channel, the only lossy transform in this core
|
||||
// this should be safe, because we only care about addr_lo on D to determine which
|
||||
// piece of data to extract when the D data bus is narrowed. Since we duplicated the
|
||||
// data to all locations, addr_lo still points at a valid copy.
|
||||
}
|
||||
}
|
||||
|
||||
def split[T <: TLDataChannel](edgeIn: TLEdge, in: DecoupledIO[T], edgeOut: TLEdge, out: DecoupledIO[T]) = {
|
||||
val inBytes = edgeIn.manager.beatBytes
|
||||
val outBytes = edgeOut.manager.beatBytes
|
||||
val ratio = inBytes / outBytes
|
||||
|
||||
val hasData = edgeIn.hasData(in.bits)
|
||||
val size = edgeIn.size(in.bits)
|
||||
val data = edgeIn.data(in.bits)
|
||||
val mask = edgeIn.mask(in.bits)
|
||||
|
||||
val dataSlices = Vec.tabulate(ratio) { i => data((i+1)*outBytes*8-1, i*outBytes*8) }
|
||||
val maskSlices = Vec.tabulate(ratio) { i => mask((i+1)*outBytes -1, i*outBytes) }
|
||||
val filter = Reg(UInt(width = ratio), init = SInt(-1, width = ratio).asUInt)
|
||||
val maskR = maskSlices.map(_.orR)
|
||||
|
||||
// decoded_size = 1111 (for smallest), 0101, 0001 (for largest)
|
||||
val sizeOH1 = UIntToOH1(size, log2Ceil(inBytes)) >> log2Ceil(outBytes)
|
||||
val decoded_size = Seq.tabulate(ratio) { i => trailingZeros(i).map(!sizeOH1(_)).getOrElse(Bool(true)) }
|
||||
|
||||
val first = filter(ratio-1)
|
||||
val new_filter = Mux(first, Cat(decoded_size.reverse), filter << 1)
|
||||
val last = new_filter(ratio-1) || !hasData
|
||||
when (out.fire()) {
|
||||
filter := new_filter
|
||||
when (!hasData) { filter := SInt(-1, width = ratio).asUInt }
|
||||
}
|
||||
|
||||
val select = Cat(maskR.reverse) & new_filter
|
||||
val dataOut = if (edgeIn.staticHasData(in.bits) == Some(false)) UInt(0) else Mux1H(select, dataSlices)
|
||||
val maskOut = Mux1H(select, maskSlices)
|
||||
|
||||
out <> in
|
||||
edgeOut.data(out.bits) := dataOut
|
||||
|
||||
out.bits match {
|
||||
case a: TLBundleA => a.mask := maskOut
|
||||
case b: TLBundleB => b.mask := maskOut
|
||||
case c: TLBundleC => ()
|
||||
case d: TLBundleD => () // addr_lo gets truncated automagically
|
||||
}
|
||||
|
||||
// Repeat the input if we're not last
|
||||
!last
|
||||
}
|
||||
|
||||
def splice[T <: TLDataChannel](edgeIn: TLEdge, in: DecoupledIO[T], edgeOut: TLEdge, out: DecoupledIO[T]) = {
|
||||
if (edgeIn.manager.beatBytes == edgeOut.manager.beatBytes) {
|
||||
// nothing to do; pass it through
|
||||
out <> in
|
||||
} else if (edgeIn.manager.beatBytes > edgeOut.manager.beatBytes) {
|
||||
// split input to output
|
||||
val repeat = Wire(Bool())
|
||||
val repeated = Repeater(in, repeat)
|
||||
val cated = Wire(repeated)
|
||||
cated <> repeated
|
||||
edgeIn.data(cated.bits) := Cat(
|
||||
edgeIn.data(repeated.bits)(edgeIn.manager.beatBytes*8-1, edgeOut.manager.beatBytes*8),
|
||||
edgeIn.data(in.bits)(edgeOut.manager.beatBytes*8-1, 0))
|
||||
repeat := split(edgeIn, cated, edgeOut, out)
|
||||
} else {
|
||||
// merge input to output
|
||||
merge(edgeIn, in, edgeOut, out)
|
||||
}
|
||||
}
|
||||
|
||||
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
|
||||
splice(edgeIn, in.a, edgeOut, out.a)
|
||||
splice(edgeOut, out.d, edgeIn, in.d)
|
||||
|
||||
if (edgeOut.manager.anySupportAcquireB && edgeIn.client.anySupportProbe) {
|
||||
splice(edgeOut, out.b, edgeIn, in.b)
|
||||
splice(edgeIn, in.c, edgeOut, out.c)
|
||||
in.e.ready := out.e.ready
|
||||
out.e.valid := in.e.valid
|
||||
out.e.bits := in.e.bits
|
||||
} else {
|
||||
in.b.valid := Bool(false)
|
||||
in.c.ready := Bool(true)
|
||||
in.e.ready := Bool(true)
|
||||
out.b.ready := Bool(true)
|
||||
out.c.valid := Bool(false)
|
||||
out.e.valid := Bool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLWidthWidget
|
||||
{
|
||||
// applied to the TL source node; y.node := WidthWidget(x.node, 16)
|
||||
def apply(innerBeatBytes: Int)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
|
||||
val widget = LazyModule(new TLWidthWidget(innerBeatBytes))
|
||||
widget.node := x
|
||||
widget.node
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
class TLRAMWidthWidget(first: Int, second: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val model = LazyModule(new TLRAMModel("WidthWidget"))
|
||||
val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff)))
|
||||
|
||||
model.node := fuzz.node
|
||||
ram.node := TLDelayer(0.1)(TLFragmenter(4, 256)(
|
||||
if (first == second ) { TLWidthWidget(first)(TLDelayer(0.1)(model.node)) }
|
||||
else {
|
||||
TLWidthWidget(second)(
|
||||
TLWidthWidget(first)(TLDelayer(0.1)(model.node)))}))
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzz.module.io.finished
|
||||
}
|
||||
}
|
||||
|
||||
class TLRAMWidthWidgetTest(little: Int, big: Int, txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new TLRAMWidthWidget(little,big,txns)).module).io.finished
|
||||
}
|
270
src/main/scala/tilelink/Xbar.scala
Normal file
270
src/main/scala/tilelink/Xbar.scala
Normal file
@ -0,0 +1,270 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip.tilelink
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.diplomacy._
|
||||
|
||||
class TLXbar(policy: TLArbiter.Policy = TLArbiter.roundRobin)(implicit p: Parameters) extends LazyModule
|
||||
{
|
||||
val node = TLNexusNode(
|
||||
numClientPorts = 1 to 32,
|
||||
numManagerPorts = 1 to 32,
|
||||
clientFn = { seq =>
|
||||
require (!seq.exists(_.unsafeAtomics) || seq.size == 1,
|
||||
"An unsafe atomic port can not be combined with any other!")
|
||||
seq(0).copy(
|
||||
minLatency = seq.map(_.minLatency).min,
|
||||
clients = (TLXbar.mapInputIds(seq) zip seq) flatMap { case (range, port) =>
|
||||
port.clients map { client => client.copy(
|
||||
sourceId = client.sourceId.shift(range.start)
|
||||
)}
|
||||
}
|
||||
)
|
||||
},
|
||||
managerFn = { seq =>
|
||||
val fifoIdFactory = TLXbar.relabeler()
|
||||
val outputIdRanges = TLXbar.mapOutputIds(seq)
|
||||
seq(0).copy(
|
||||
minLatency = seq.map(_.minLatency).min,
|
||||
endSinkId = outputIdRanges.map(_.map(_.end).getOrElse(0)).max,
|
||||
managers = seq.flatMap { port =>
|
||||
require (port.beatBytes == seq(0).beatBytes,
|
||||
s"Xbar data widths don't match: ${port.managers.map(_.name)} has ${port.beatBytes}B vs ${seq(0).managers.map(_.name)} has ${seq(0).beatBytes}B")
|
||||
val fifoIdMapper = fifoIdFactory()
|
||||
port.managers map { manager => manager.copy(
|
||||
fifoId = manager.fifoId.map(fifoIdMapper(_))
|
||||
)}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
val out = node.bundleOut
|
||||
}
|
||||
|
||||
// Grab the port ID mapping
|
||||
val inputIdRanges = TLXbar.mapInputIds(node.edgesIn.map(_.client))
|
||||
val outputIdRanges = TLXbar.mapOutputIds(node.edgesOut.map(_.manager))
|
||||
|
||||
// Find a good mask for address decoding
|
||||
val port_addrs = node.edgesOut.map(_.manager.managers.map(_.address).flatten)
|
||||
val routingMask = AddressDecoder(port_addrs)
|
||||
val route_addrs = port_addrs.map(seq => AddressSet.unify(seq.map(_.widen(~routingMask)).distinct))
|
||||
val outputPorts = route_addrs.map(seq => (addr: UInt) => seq.map(_.contains(addr)).reduce(_ || _))
|
||||
|
||||
// Print the address mapping
|
||||
if (false) {
|
||||
println("Xbar mapping:")
|
||||
route_addrs.foreach { p =>
|
||||
print(" ")
|
||||
p.foreach { a => print(s" ${a}") }
|
||||
println("")
|
||||
}
|
||||
println("--")
|
||||
}
|
||||
|
||||
// Print the ID mapping
|
||||
if (false) {
|
||||
println(s"XBar ${name} mapping:")
|
||||
(node.edgesIn zip inputIdRanges).zipWithIndex.foreach { case ((edge, id), i) =>
|
||||
println(s"\t$i assigned ${id} for ${edge.client.clients.map(_.name).mkString(", ")}")
|
||||
}
|
||||
println("")
|
||||
}
|
||||
|
||||
// We need an intermediate size of bundle with the widest possible identifiers
|
||||
val wide_bundle = TLBundleParameters.union(io.in.map(_.params) ++ io.out.map(_.params))
|
||||
|
||||
// Handle size = 1 gracefully (Chisel3 empty range is broken)
|
||||
def trim(id: UInt, size: Int) = if (size <= 1) UInt(0) else id(log2Ceil(size)-1, 0)
|
||||
|
||||
// Transform input bundle sources (sinks use global namespace on both sides)
|
||||
val in = Wire(Vec(io.in.size, TLBundle(wide_bundle)))
|
||||
for (i <- 0 until in.size) {
|
||||
val r = inputIdRanges(i)
|
||||
|
||||
in(i).a <> io.in(i).a
|
||||
io.in(i).d <> in(i).d
|
||||
in(i).a.bits.source := io.in(i).a.bits.source | UInt(r.start)
|
||||
io.in(i).d.bits.source := trim(in(i).d.bits.source, r.size)
|
||||
|
||||
if (node.edgesIn(i).client.anySupportProbe && node.edgesOut.exists(_.manager.anySupportAcquireB)) {
|
||||
in(i).c <> io.in(i).c
|
||||
in(i).e <> io.in(i).e
|
||||
io.in(i).b <> in(i).b
|
||||
in(i).c.bits.source := io.in(i).c.bits.source | UInt(r.start)
|
||||
io.in(i).b.bits.source := trim(in(i).b.bits.source, r.size)
|
||||
} else {
|
||||
in(i).c.valid := Bool(false)
|
||||
in(i).e.valid := Bool(false)
|
||||
in(i).b.ready := Bool(false)
|
||||
io.in(i).c.ready := Bool(true)
|
||||
io.in(i).e.ready := Bool(true)
|
||||
io.in(i).b.valid := Bool(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Transform output bundle sinks (sources use global namespace on both sides)
|
||||
val out = Wire(Vec(io.out.size, TLBundle(wide_bundle)))
|
||||
for (i <- 0 until out.size) {
|
||||
val r = outputIdRanges(i)
|
||||
|
||||
io.out(i).a <> out(i).a
|
||||
out(i).d <> io.out(i).d
|
||||
out(i).d.bits.sink := io.out(i).d.bits.sink | UInt(r.map(_.start).getOrElse(0))
|
||||
|
||||
if (node.edgesOut(i).manager.anySupportAcquireB && node.edgesIn.exists(_.client.anySupportProbe)) {
|
||||
io.out(i).c <> out(i).c
|
||||
io.out(i).e <> out(i).e
|
||||
out(i).b <> io.out(i).b
|
||||
io.out(i).e.bits.sink := trim(out(i).e.bits.sink, r.map(_.size).getOrElse(0))
|
||||
} else {
|
||||
out(i).c.ready := Bool(false)
|
||||
out(i).e.ready := Bool(false)
|
||||
out(i).b.valid := Bool(false)
|
||||
io.out(i).c.valid := Bool(false)
|
||||
io.out(i).e.valid := Bool(false)
|
||||
io.out(i).b.ready := Bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
val addressA = (in zip node.edgesIn) map { case (i, e) => e.address(i.a.bits) }
|
||||
val addressC = (in zip node.edgesIn) map { case (i, e) => e.address(i.c.bits) }
|
||||
|
||||
val requestAIO = Vec(addressA.map { i => Vec(outputPorts.map { o => o(i) }) })
|
||||
val requestCIO = Vec(addressC.map { i => Vec(outputPorts.map { o => o(i) }) })
|
||||
val requestBOI = Vec(out.map { o => Vec(inputIdRanges.map { i => i.contains(o.b.bits.source) }) })
|
||||
val requestDOI = Vec(out.map { o => Vec(inputIdRanges.map { i => i.contains(o.d.bits.source) }) })
|
||||
val requestEIO = Vec(in.map { i => Vec(outputIdRanges.map { o => o.map(_.contains(i.e.bits.sink)).getOrElse(Bool(false)) }) })
|
||||
|
||||
val beatsAI = Vec((in zip node.edgesIn) map { case (i, e) => e.numBeats1(i.a.bits) })
|
||||
val beatsBO = Vec((out zip node.edgesOut) map { case (o, e) => e.numBeats1(o.b.bits) })
|
||||
val beatsCI = Vec((in zip node.edgesIn) map { case (i, e) => e.numBeats1(i.c.bits) })
|
||||
val beatsDO = Vec((out zip node.edgesOut) map { case (o, e) => e.numBeats1(o.d.bits) })
|
||||
val beatsEI = Vec((in zip node.edgesIn) map { case (i, e) => e.numBeats1(i.e.bits) })
|
||||
|
||||
// Which pairs support support transfers
|
||||
def transpose[T](x: Seq[Seq[T]]) = Seq.tabulate(x(0).size) { i => Seq.tabulate(x.size) { j => x(j)(i) } }
|
||||
def filter[T](data: Seq[T], mask: Seq[Boolean]) = (data zip mask).filter(_._2).map(_._1)
|
||||
|
||||
// Fanout the input sources to the output sinks
|
||||
val portsAOI = transpose((in zip requestAIO) map { case (i, r) => TLXbar.fanout(i.a, r) })
|
||||
val portsBIO = transpose((out zip requestBOI) map { case (o, r) => TLXbar.fanout(o.b, r) })
|
||||
val portsCOI = transpose((in zip requestCIO) map { case (i, r) => TLXbar.fanout(i.c, r) })
|
||||
val portsDIO = transpose((out zip requestDOI) map { case (o, r) => TLXbar.fanout(o.d, r) })
|
||||
val portsEOI = transpose((in zip requestEIO) map { case (i, r) => TLXbar.fanout(i.e, r) })
|
||||
|
||||
// Arbitrate amongst the sources
|
||||
for (o <- 0 until out.size) {
|
||||
val allowI = Seq.tabulate(in.size) { i =>
|
||||
node.edgesIn(i).client.anySupportProbe &&
|
||||
node.edgesOut(o).manager.anySupportAcquireB
|
||||
}
|
||||
TLArbiter(policy)(out(o).a, (beatsAI zip portsAOI(o) ):_*)
|
||||
TLArbiter(policy)(out(o).c, filter(beatsCI zip portsCOI(o), allowI):_*)
|
||||
TLArbiter(policy)(out(o).e, filter(beatsEI zip portsEOI(o), allowI):_*)
|
||||
}
|
||||
|
||||
for (i <- 0 until in.size) {
|
||||
val allowO = Seq.tabulate(out.size) { o =>
|
||||
node.edgesIn(i).client.anySupportProbe &&
|
||||
node.edgesOut(o).manager.anySupportAcquireB
|
||||
}
|
||||
TLArbiter(policy)(in(i).b, filter(beatsBO zip portsBIO(i), allowO):_*)
|
||||
TLArbiter(policy)(in(i).d, (beatsDO zip portsDIO(i) ):_*)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLXbar
|
||||
{
|
||||
def mapInputIds (ports: Seq[TLClientPortParameters ]) = assignRanges(ports.map(_.endSourceId)).map(_.get)
|
||||
def mapOutputIds(ports: Seq[TLManagerPortParameters]) = assignRanges(ports.map(_.endSinkId))
|
||||
|
||||
def assignRanges(sizes: Seq[Int]) = {
|
||||
val pow2Sizes = sizes.map { z => if (z == 0) 0 else 1 << log2Ceil(z) }
|
||||
val tuples = pow2Sizes.zipWithIndex.sortBy(_._1) // record old index, then sort by increasing size
|
||||
val starts = tuples.scanRight(0)(_._1 + _).tail // suffix-sum of the sizes = the start positions
|
||||
val ranges = (tuples zip starts) map { case ((sz, i), st) =>
|
||||
(if (sz == 0) None else Some(IdRange(st, st+sz)), i)
|
||||
}
|
||||
ranges.sortBy(_._2).map(_._1) // Restore orignal order
|
||||
}
|
||||
|
||||
def relabeler() = {
|
||||
var idFactory = 0
|
||||
() => {
|
||||
val fifoMap = scala.collection.mutable.HashMap.empty[Int, Int]
|
||||
(x: Int) => {
|
||||
if (fifoMap.contains(x)) fifoMap(x) else {
|
||||
val out = idFactory
|
||||
idFactory = idFactory + 1
|
||||
fifoMap += (x -> out)
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replicate an input port to each output port
|
||||
def fanout[T <: TLChannel](input: DecoupledIO[T], select: Seq[Bool]) = {
|
||||
val filtered = Wire(Vec(select.size, input))
|
||||
for (i <- 0 until select.size) {
|
||||
filtered(i).bits := input.bits
|
||||
filtered(i).valid := input.valid && select(i)
|
||||
}
|
||||
input.ready := Mux1H(select, filtered.map(_.ready))
|
||||
filtered
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import freechips.rocketchip.unittest._
|
||||
|
||||
class TLRAMXbar(nManagers: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
val model = LazyModule(new TLRAMModel("Xbar"))
|
||||
val xbar = LazyModule(new TLXbar)
|
||||
|
||||
model.node := fuzz.node
|
||||
xbar.node := TLDelayer(0.1)(model.node)
|
||||
(0 until nManagers) foreach { n =>
|
||||
val ram = LazyModule(new TLRAM(AddressSet(0x0+0x400*n, 0x3ff)))
|
||||
ram.node := TLFragmenter(4, 256)(TLDelayer(0.1)(xbar.node))
|
||||
}
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzz.module.io.finished
|
||||
}
|
||||
}
|
||||
|
||||
class TLRAMXbarTest(nManagers: Int, txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new TLRAMXbar(nManagers,txns)).module).io.finished
|
||||
}
|
||||
|
||||
class TLMulticlientXbar(nManagers: Int, nClients: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
|
||||
val xbar = LazyModule(new TLXbar)
|
||||
|
||||
val fuzzers = (0 until nClients) map { n =>
|
||||
val fuzz = LazyModule(new TLFuzzer(txns))
|
||||
xbar.node := TLDelayer(0.1)(fuzz.node)
|
||||
fuzz
|
||||
}
|
||||
|
||||
(0 until nManagers) foreach { n =>
|
||||
val ram = LazyModule(new TLRAM(AddressSet(0x0+0x400*n, 0x3ff)))
|
||||
ram.node := TLFragmenter(4, 256)(TLDelayer(0.1)(xbar.node))
|
||||
}
|
||||
|
||||
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
|
||||
io.finished := fuzzers.last.module.io.finished
|
||||
}
|
||||
}
|
||||
|
||||
class TLMulticlientXbarTest(nManagers: Int, nClients: Int, txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
io.finished := Module(LazyModule(new TLMulticlientXbar(nManagers, nClients, txns)).module).io.finished
|
||||
}
|
17
src/main/scala/tilelink/package.scala
Normal file
17
src/main/scala/tilelink/package.scala
Normal file
@ -0,0 +1,17 @@
|
||||
// See LICENSE.SiFive for license details.
|
||||
|
||||
package freechips.rocketchip
|
||||
|
||||
import Chisel._
|
||||
import freechips.rocketchip.diplomacy._
|
||||
|
||||
package object tilelink
|
||||
{
|
||||
type TLInwardNode = InwardNodeHandle[TLClientPortParameters, TLManagerPortParameters, TLBundle]
|
||||
type TLOutwardNode = OutwardNodeHandle[TLClientPortParameters, TLManagerPortParameters, TLBundle]
|
||||
type TLAsyncOutwardNode = OutwardNodeHandle[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncBundle]
|
||||
type TLRationalOutwardNode = OutwardNodeHandle[TLRationalClientPortParameters, TLRationalManagerPortParameters, TLRationalBundle]
|
||||
type IntOutwardNode = OutwardNodeHandle[IntSourcePortParameters, IntSinkPortParameters, Vec[Bool]]
|
||||
type TLMixedNode = MixedNode[TLClientPortParameters, TLManagerPortParameters, TLEdgeIn, TLBundle,
|
||||
TLClientPortParameters, TLManagerPortParameters, TLEdgeOut, TLBundle]
|
||||
}
|
Reference in New Issue
Block a user