diff --git a/src/main/scala/uncore/tilelink2/Buffer.scala b/src/main/scala/uncore/tilelink2/Buffer.scala new file mode 100644 index 00000000..26392d2b --- /dev/null +++ b/src/main/scala/uncore/tilelink2/Buffer.scala @@ -0,0 +1,48 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import chisel3.internal.sourceinfo.SourceInfo + +class TLBuffer(entries: Int = 2, pipe: Boolean = false) extends LazyModule +{ + val node = TLIdentityNode() + + lazy val module = new LazyModuleImp(this) { + val io = new Bundle { + val in = node.bundleIn + val out = node.bundleOut + } + + val in = io.in(0) + val out = io.out(0) + + out.a <> Queue(in .a, entries, pipe) + in .d <> Queue(out.d, entries, pipe) + + val edge = node.edgesOut(0) // same as edgeIn(0) + if (edge.manager.anySupportAcquire && edge.client.anySupportProbe) { + in .b <> Queue(out.b, entries, pipe) + out.c <> Queue(in .c, entries, pipe) + out.e <> Queue(out.e, entries, pipe) + } 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; connect (TLBuffer(x.node) -> y.node) + def apply(x: TLBaseNode, entries: Int = 2, pipe: Boolean = false)(implicit lazyModule: LazyModule, sourceInfo: SourceInfo): TLBaseNode = { + val buffer = LazyModule(new TLBuffer(entries, pipe)) + lazyModule.connect(x -> buffer.node) + buffer.node + } +} diff --git a/src/main/scala/uncore/tilelink2/Bundles.scala b/src/main/scala/uncore/tilelink2/Bundles.scala new file mode 100644 index 00000000..e071c466 --- /dev/null +++ b/src/main/scala/uncore/tilelink2/Bundles.scala @@ -0,0 +1,183 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ + +abstract class GenericParameterizedBundle[T <: Object](val params: T) extends Bundle +{ + override def cloneType = { + try { + this.getClass.getConstructors.head.newInstance(params).asInstanceOf[this.type] + } catch { + case e: java.lang.IllegalArgumentException => + throwException("Unable to use GenericParameterizedBundle.cloneType on " + + this.getClass + ", probably because " + this.getClass + + "() takes more than one argument. Consider overriding " + + "cloneType() on " + this.getClass, e) + } + } +} + +abstract class TLBundleBase(params: TLBundleParameters) extends GenericParameterizedBundle(params) + +// common combos in lazy policy: +// Put + Acquire +// Release + AccessAck + +object TLMessages +{ + // A B C D E + val PutFullData = UInt(0) // . . => AccessAck + val PutPartialData = UInt(1) // . . => AccessAck + val ArithmeticData = UInt(2) // . . => AccessAckData + val LogicalData = UInt(3) // . . => AccessAckData + val Get = UInt(4) // . . => AccessAckData + val Hint = UInt(5) // . . => HintAck + val Acquire = UInt(6) // . => Grant[Data] + val Probe = UInt(6) // . => ProbeAck[Data] + val AccessAck = UInt(0) // . . + val AccessAckData = UInt(1) // . . + val HintAck = UInt(2) // . . +//val PutThroughData = UInt(3) // . // future extension ? + val ProbeAck = UInt(4) // . + val ProbeAckData = UInt(5) // . + val Release = UInt(6) // . => ReleaseAck + val ReleaseData = UInt(7) // . => ReleaseAck + val Grant = UInt(4) // . => GrantAck + val GrantData = UInt(5) // . => GrantAck + val ReleaseAck = UInt(6) // . + val 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 +} + +object TLPermissions +{ + // Cap types (Grant = new permissions, Probe = permisions <= target) + val toT = UInt(0) + val toB = UInt(1) + val toN = UInt(2) + def isCap(x: UInt) = x <= toN + + // Grow types (Acquire = permissions >= target) + val NtoB = UInt(0) + val NtoT = UInt(1) + val BtoT = UInt(2) + def isGrow(x: UInt) = x <= BtoT + + // Shrink types (ProbeAck, Release) + val TtoB = UInt(0) + val TtoN = UInt(1) + val BtoN = UInt(2) + def isShrink(x: UInt) = x <= BtoN + + // Report types (ProbeAck) + val TtoT = UInt(3) + val BtoB = UInt(4) + val NtoN = UInt(5) + def isReport(x: UInt) = x <= NtoN +} + +object TLAtomics +{ + // Arithmetic types + val MIN = UInt(0) + val MAX = UInt(1) + val MINU = UInt(2) + val MAXU = UInt(3) + val ADD = UInt(4) + def isArithmetic(x: UInt) = x <= ADD + + // Logical types + val XOR = UInt(0) + val OR = UInt(1) + val AND = UInt(2) + val SWAP = UInt(3) + def isLogical(x: UInt) = x <= SWAP +} + +sealed trait TLChannel +sealed trait TLDataChannel extends TLChannel +sealed trait TLAddrChannel extends TLDataChannel + +final class TLBundleA(params: TLBundleParameters) + extends TLBundleBase(params) with TLAddrChannel +{ + // fixed fields during multibeat: + val opcode = UInt(width = 3) + val param = UInt(width = 3) // amo_opcode || perms || hint + val size = UInt(width = params.sizeBits) + val source = UInt(width = params.sourceBits) // from + val addr_hi = UInt(width = params.addrHiBits) // 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 +{ + // fixed fields during multibeat: + val opcode = UInt(width = 3) + val param = UInt(width = 3) + val size = UInt(width = params.sizeBits) + val source = UInt(width = params.sourceBits) // to + val addr_hi = UInt(width = params.addrHiBits) // 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 +{ + // fixed fields during multibeat: + val opcode = UInt(width = 3) + val param = UInt(width = 3) + val size = UInt(width = params.sizeBits) + val source = UInt(width = params.sourceBits) // from + val addr_hi = UInt(width = params.addrHiBits) // to + 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] +} + +final class TLBundleD(params: TLBundleParameters) + extends TLBundleBase(params) with TLDataChannel +{ + // fixed fields during multibeat: + val opcode = UInt(width = 3) + val param = UInt(width = 2) + 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 sink = UInt(width = params.sourceBits) // 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) +} diff --git a/src/main/scala/uncore/tilelink2/Edges.scala b/src/main/scala/uncore/tilelink2/Edges.scala new file mode 100644 index 00000000..75053b88 --- /dev/null +++ b/src/main/scala/uncore/tilelink2/Edges.scala @@ -0,0 +1,606 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import chisel3.internal.sourceinfo.SourceInfo + +class TLEdge( + client: TLClientPortParameters, + manager: TLManagerPortParameters) + extends TLEdgeParameters(client, manager) +{ + def isHiAligned(addr_hi: UInt, lgSize: UInt): Bool = { + if (maxLgSize == 0) Bool(true) else { + val mask = UIntToOH1(lgSize, maxLgSize) >> log2Ceil(manager.beatBytes) + (addr_hi & mask) === UInt(0) + } + } + + def isLoAligned(addr_lo: UInt, lgSize: UInt): Bool = { + if (maxLgSize == 0) Bool(true) else { + val mask = UIntToOH1(lgSize, maxLgSize) + (addr_lo & mask) === UInt(0) + } + } + + // This gets used everywhere, so make the smallest circuit possible ... + def mask(addr_lo: UInt, lgSize: UInt): UInt = { + val lgBytes = log2Ceil(manager.beatBytes) + val sizeOH = UIntToOH(lgSize, lgBytes) + def helper(i: Int): Seq[(Bool, Bool)] = { + if (i == 0) { + Seq((lgSize >= UInt(lgBytes), Bool(true))) + } else { + val sub = helper(i-1) + val size = sizeOH(lgBytes - i) + val bit = addr_lo(lgBytes - i) + val nbit = !bit + Seq.tabulate (1 << i) { j => + val (sub_acc, sub_eq) = sub(j/2) + val eq = sub_eq && (if (j % 2 == 1) bit else nbit) + val acc = sub_acc || (size && eq) + (acc, eq) + } + } + } + Cat(helper(lgBytes).map(_._1).reverse) + } + + def addr_lo(mask: UInt): UInt = { + // Almost OHToUInt, but bits set => bits not set + def helper(mask: UInt, width: Int): UInt = { + if (width <= 1) { + UInt(0) + } else if (width == 2) { + ~mask(0, 0) + } else { + val mid = 1 << (log2Up(width)-1) + val hi = mask(width-1, mid) + val lo = mask(mid-1, 0) + Cat(!lo.orR, helper(hi | lo, mid)) + } + } + helper(mask, bundle.dataBits/8) + } + + 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.anySupportAcquire || 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.anySupportAcquire + // Do there exist D messages without Data? + val dDataNo = manager.anySupportPutFull || manager.anySupportPutPartial || manager.anySupportHint || manager.anySupportAcquire + if (!dDataYes) Some(false) else if (!dDataNo) Some(true) else None + } + case _:TLBundleE => Some(false) + } + } + + def hasFollowUp(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 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 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.addr_lo, c.size) + case d: TLBundleD => mask(d.addr_lo, d.size) + } + } + + def addr_lo(x: TLDataChannel): UInt = { + x match { + case a: TLBundleA => addr_lo(a.mask) + case b: TLBundleB => addr_lo(b.mask) + case c: TLBundleC => c.addr_lo + case d: TLBundleD => d.addr_lo + } + } + + def address(x: TLAddrChannel): UInt = { + val hi = x match { + case a: TLBundleA => a.addr_hi + case b: TLBundleB => b.addr_hi + case c: TLBundleC => c.addr_hi + } + if (manager.beatBytes == 1) hi else Cat(hi, addr_lo(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 || small, UInt(1), decode) + } + } + } +} + +class TLEdgeOut( + client: TLClientPortParameters, + manager: TLManagerPortParameters) + extends TLEdge(client, manager) +{ + // Transfers + def Acquire(fromSource: UInt, toAddress: UInt, lgSize: UInt, growPermissions: UInt) = { + require (manager.anySupportAcquire) + val legal = manager.supportsAcquire(toAddress, lgSize) + val a = Wire(new TLBundleA(bundle)) + a.opcode := TLMessages.Acquire + a.param := growPermissions + a.size := lgSize + a.source := fromSource + a.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + a.mask := SInt(-1).asUInt + a.data := UInt(0) + (legal, a) + } + + def Release(fromSource: UInt, toAddress: UInt, lgSize: UInt, shrinkPermissions: UInt) = { + require (manager.anySupportAcquire) + val legal = manager.supportsAcquire(toAddress, lgSize) + val c = Wire(new TLBundleC(bundle)) + c.opcode := TLMessages.Release + c.param := shrinkPermissions + c.size := lgSize + c.source := fromSource + c.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + c.addr_lo := 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.anySupportAcquire) + val legal = manager.supportsAcquire(toAddress, lgSize) + val c = Wire(new TLBundleC(bundle)) + c.opcode := TLMessages.ReleaseData + c.param := shrinkPermissions + c.size := lgSize + c.source := fromSource + c.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + c.addr_lo := toAddress + c.data := data + c.error := Bool(false) + (legal, c) + } + + def ProbeAck(fromSource: UInt, toAddress: UInt, lgSize: UInt, reportPermissions: UInt) = { + val c = Wire(new TLBundleC(bundle)) + c.opcode := TLMessages.ProbeAck + c.param := reportPermissions + c.size := lgSize + c.source := fromSource + c.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + c.addr_lo := toAddress + c.data := UInt(0) + c.error := Bool(false) + c + } + + def ProbeAck(fromSource: UInt, toAddress: UInt, lgSize: UInt, reportPermissions: UInt, data: UInt) = { + val c = Wire(new TLBundleC(bundle)) + c.opcode := TLMessages.ProbeAckData + c.param := reportPermissions + c.size := lgSize + c.source := fromSource + c.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + c.addr_lo := toAddress + c.data := data + c.error := Bool(false) + c + } + + def GrantAck(toSink: UInt) = { + 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.supportsGet(toAddress, lgSize) + val a = Wire(new TLBundleA(bundle)) + a.opcode := TLMessages.Get + a.param := UInt(0) + a.size := lgSize + a.source := fromSource + a.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + 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.supportsPutFull(toAddress, lgSize) + val a = Wire(new TLBundleA(bundle)) + a.opcode := TLMessages.PutFullData + a.param := UInt(0) + a.size := lgSize + a.source := fromSource + a.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + 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.supportsPutPartial(toAddress, lgSize) + val a = Wire(new TLBundleA(bundle)) + a.opcode := TLMessages.PutPartialData + a.param := UInt(0) + a.size := lgSize + a.source := fromSource + a.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + 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.supportsArithmetic(toAddress, lgSize) + val a = Wire(new TLBundleA(bundle)) + a.opcode := TLMessages.ArithmeticData + a.param := atomic + a.size := lgSize + a.source := fromSource + a.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + 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.supportsLogical(toAddress, lgSize) + val a = Wire(new TLBundleA(bundle)) + a.opcode := TLMessages.LogicalData + a.param := atomic + a.size := lgSize + a.source := fromSource + a.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + 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.supportsHint(toAddress) + val a = Wire(new TLBundleA(bundle)) + a.opcode := TLMessages.Hint + a.param := param + a.size := lgSize + a.source := fromSource + a.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + 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.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + c.addr_lo := 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.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + c.addr_lo := 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.addr_hi := toAddress >> log2Ceil(manager.beatBytes) + c.addr_lo := 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(fromAddress, lgSize) + val b = Wire(new TLBundleB(bundle)) + b.opcode := TLMessages.Probe + b.param := capPermissions + b.size := lgSize + b.source := toSource + b.addr_hi := fromAddress >> log2Ceil(manager.beatBytes) + b.mask := SInt(-1).asUInt + 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 := 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 := 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 := 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.addr_hi := fromAddress >> log2Ceil(manager.beatBytes) + 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.addr_hi := fromAddress >> log2Ceil(manager.beatBytes) + 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.addr_hi := fromAddress >> log2Ceil(manager.beatBytes) + 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.addr_hi := fromAddress >> log2Ceil(manager.beatBytes) + 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.addr_hi := fromAddress >> log2Ceil(manager.beatBytes) + 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) + val b = Wire(new TLBundleB(bundle)) + b.opcode := TLMessages.Hint + b.param := param + b.size := lgSize + b.source := toSource + b.addr_hi := fromAddress >> log2Ceil(manager.beatBytes) + 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 := 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 := fromAddress + d.data := data + d.error := error + d + } + + def HintAck(a: TLBundleA, sink: UInt = UInt(0)): TLBundleD = HintAck(address(a), sink, 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 := fromAddress + d.data := UInt(0) + d.error := Bool(false) + d + } +} diff --git a/src/main/scala/uncore/tilelink2/Fragmenter.scala b/src/main/scala/uncore/tilelink2/Fragmenter.scala new file mode 100644 index 00000000..6364f72a --- /dev/null +++ b/src/main/scala/uncore/tilelink2/Fragmenter.scala @@ -0,0 +1,249 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import chisel3.internal.sourceinfo.SourceInfo +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 breaks: Acquire (and thus cuts BCE channels) +class TLFragmenter(minSize: Int, maxSize: Int, alwaysMin: Boolean = false) extends LazyModule +{ + require (isPow2 (maxSize)) + require (isPow2 (minSize)) + require (minSize < maxSize) + + val fragmentBits = log2Ceil(maxSize / minSize) + + 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( + supportsAcquire = TransferSizes.none, // this adapter breaks acquires + supportsArithmetic = shrinkTransfer(m.supportsArithmetic), + supportsLogical = expandTransfer(m.supportsLogical), + supportsGet = expandTransfer(m.supportsGet), + supportsPutFull = expandTransfer(m.supportsPutFull), + supportsPutPartial = expandTransfer(m.supportsPutPartial)) + def mapClient(c: TLClientParameters) = c.copy( + sourceId = IdRange(c.sourceId.start << fragmentBits, c.sourceId.end << fragmentBits), + // since we break Acquires, none of these work either: + supportsProbe = TransferSizes.none, + supportsArithmetic = TransferSizes.none, + supportsLogical = TransferSizes.none, + supportsGet = TransferSizes.none, + supportsPutFull = TransferSizes.none, + supportsPutPartial = TransferSizes.none, + supportsHint = false) + + val node = TLAdapterNode( + clientFn = { case Seq(c) => c.copy(clients = c.clients.map(mapClient)) }, + managerFn = { case Seq(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 + } + + // All managers must share a common FIFO domain (responses might end up interleaved) + val edgeOut = node.edgesOut(0) + val edgeIn = node.edgesIn(0) + 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(_ && _)) + + // We don't support fragmenting to sub-beat accesses + require (minSize >= beatBytes) + + /* 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 in = io.in(0) + val out = io.out(0) + + val counterBits = log2Up(maxSize/beatBytes) + val maxDownSize = if (alwaysMin) minSize else manager.maxTransfer + + // 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 dsizeOH = UIntToOH (out.d.bits.size, log2Ceil(maxDownSize)+1) + val dsizeOH1 = UIntToOH1(out.d.bits.size, log2Ceil(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 = (out.d.bits.opcode === TLMessages.AccessAck) && (dFragnum =/= UInt(0)) + 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.source := out.d.bits.source >> fragmentBits + in.d.bits.size := Mux(dFirst, dFirst_size, dOrig) + + // 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))) + + // If this is infront of a single manager, these become constants + val find = manager.find(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, log2Ceil(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))) + + when (out.a.fire()) { gennum := new_gennum } + + val delay = !aHasData && aFragnum =/= UInt(0) + out.a.valid := in.a.valid + in.a.ready := out.a.ready && !delay + out.a.bits := in.a.bits + out.a.bits.addr_hi := in.a.bits.addr_hi | (~aFragnum << log2Ceil(minSize/beatBytes) & aOrigOH1 >> log2Ceil(beatBytes)) + out.a.bits.source := Cat(in.a.bits.source, aFragnum) + out.a.bits.size := aFrag + + // 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; connect (TLFragmenter(x.node, 256, 4) -> y.node) + def apply(x: TLBaseNode, minSize: Int, maxSize: Int, alwaysMin: Boolean = false)(implicit lazyModule: LazyModule, sourceInfo: SourceInfo): TLBaseNode = { + val fragmenter = LazyModule(new TLFragmenter(minSize, maxSize, alwaysMin)) + lazyModule.connect(x -> fragmenter.node) + fragmenter.node + } +} diff --git a/src/main/scala/uncore/tilelink2/GPIO.scala b/src/main/scala/uncore/tilelink2/GPIO.scala new file mode 100644 index 00000000..a9050882 --- /dev/null +++ b/src/main/scala/uncore/tilelink2/GPIO.scala @@ -0,0 +1,29 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ + +case class GPIOParams(num: Int, address: BigInt) + +trait GPIOBundle +{ + val params: GPIOParams + val gpio = UInt(width = params.num) +} + +trait GPIOModule extends HasRegMap +{ + val params: GPIOParams + val io: GPIOBundle + + val state = RegInit(UInt(0)) + io.gpio := state + + regmap(0 -> Seq(RegField(params.num, state))) +} + +// Create a concrete TL2 version of the abstract GPIO slave +class TLGPIO(p: GPIOParams) extends TLRegisterRouter(p.address)( + new TLRegBundle(p, _) with GPIOBundle)( + new TLRegModule(p, _, _) with GPIOModule) diff --git a/src/main/scala/uncore/tilelink2/HintHandler.scala b/src/main/scala/uncore/tilelink2/HintHandler.scala new file mode 100644 index 00000000..0222991e --- /dev/null +++ b/src/main/scala/uncore/tilelink2/HintHandler.scala @@ -0,0 +1,99 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import chisel3.internal.sourceinfo.SourceInfo + +// 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) extends LazyModule +{ + val node = TLAdapterNode( + clientFn = { case Seq(c) => if (supportClients) c.copy(clients = c.clients .map(_.copy(supportsHint = true))) else c }, + managerFn = { case Seq(m) => if (supportManagers) m.copy(managers = m.managers.map(_.copy(supportsHint = true))) else m }) + + lazy val module = new LazyModuleImp(this) { + val io = new Bundle { + val in = node.bundleIn + val out = node.bundleOut + } + + val in = io.in(0) + val out = io.out(0) + val edgeIn = node.edgesIn(0) + val edgeOut = node.edgesOut(0) + + // Don't add support for clients if there is no BCE channel + val bce = edgeOut.manager.anySupportAcquire && edgeIn.client.anySupportProbe + require (!supportClients || bce) + + if (supportManagers) { + val handleA = if (passthrough) !edgeOut.manager.supportsHint(edgeIn.address(in.a.bits)) else Bool(true) + val bypassD = handleA && in.a.bits.opcode === TLMessages.Hint + + // Prioritize existing D traffic over HintAck + in.d.valid := out.d.valid || (bypassD && in.a.valid) + out.d.ready := in.d.ready + in.d.bits := Mux(out.d.valid, out.d.bits, edgeIn.HintAck(in.a.bits)) + + in.a.ready := Mux(bypassD, in.d.ready && !out.d.valid, out.a.ready) + out.a.valid := in.a.valid && !bypassD + out.a.bits := in.a.bits + } 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) { + val handleB = if (passthrough) !edgeIn.client.supportsHint(out.b.bits.source) else Bool(true) + val bypassC = handleB && out.b.bits.opcode === TLMessages.Hint + + // Prioritize existing C traffic over HintAck + out.c.valid := in.c.valid || (bypassC && in.b.valid) + in.c.ready := out.c.ready + out.c.bits := Mux(in.c.valid, in.c.bits, edgeOut.HintAck(out.b.bits)) + + out.b.ready := Mux(bypassC, out.c.ready && !in.c.valid, in.b.ready) + in.b.valid := out.b.valid && !bypassC + in.b.bits := out.b.bits + } 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; connect (TLHintHandler(x.node) -> y.node) + def apply(x: TLBaseNode, supportManagers: Boolean = true, supportClients: Boolean = false, passthrough: Boolean = true)(implicit lazyModule: LazyModule, sourceInfo: SourceInfo): TLBaseNode = { + val hints = LazyModule(new TLHintHandler(supportManagers, supportClients, passthrough)) + lazyModule.connect(x -> hints.node) + hints.node + } +} diff --git a/src/main/scala/uncore/tilelink2/LazyModule.scala b/src/main/scala/uncore/tilelink2/LazyModule.scala new file mode 100644 index 00000000..8504e711 --- /dev/null +++ b/src/main/scala/uncore/tilelink2/LazyModule.scala @@ -0,0 +1,60 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import chisel3.internal.sourceinfo._ + +abstract class LazyModule +{ + protected[tilelink2] var bindings = List[() => Unit]() + protected[tilelink2] var children = List[LazyModule]() + protected[tilelink2] var info: SourceInfo = UnlocatableSourceInfo + protected[tilelink2] val parent = LazyModule.stack.headOption + + LazyModule.stack = this :: LazyModule.stack + parent.foreach(p => p.children = this :: p.children) + + // Use as: connect(source -> sink, source2 -> sink2, ...) + def connect[PO, PI, EO, EI, B <: Bundle](edges: (BaseNode[PO, PI, EO, EI, B], BaseNode[PO, PI, EO, EI, B])*)(implicit sourceInfo: SourceInfo) = { + edges.foreach { case (source, sink) => + bindings = (source edge sink) :: bindings + } + } + + def module: LazyModuleImp + implicit val lazyModule = this + + protected[tilelink2] def instantiate() = { + children.reverse.foreach { c => + // !!! fix chisel3 so we can pass the desired sourceInfo + // implicit val sourceInfo = c.module.outer.info + Module(c.module) + } + bindings.reverse.foreach { f => f () } + } +} + +object LazyModule +{ + protected[tilelink2] var stack = List[LazyModule]() + + def apply[T <: LazyModule](bc: T)(implicit sourceInfo: SourceInfo): T = { + // Make sure the user put LazyModule around modules in the correct order + // If this require fails, probably some grandchild was missing a LazyModule + // ... or you applied LazyModule twice + require (!stack.isEmpty && (stack.head eq bc)) + stack = stack.tail + bc.info = sourceInfo + bc + } +} + +abstract class LazyModuleImp(outer: LazyModule) extends Module +{ + // .module had better not be accessed while LazyModules are still being built! + require (LazyModule.stack.isEmpty) + + override def desiredName = outer.getClass.getName.split('.').last + outer.instantiate() +} diff --git a/src/main/scala/uncore/tilelink2/Legacy.scala b/src/main/scala/uncore/tilelink2/Legacy.scala new file mode 100644 index 00000000..d21503cb --- /dev/null +++ b/src/main/scala/uncore/tilelink2/Legacy.scala @@ -0,0 +1,134 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import cde.Parameters +import uncore.tilelink._ +import uncore.constants._ + +// Instantiate 'val p' before HasTileLinkParameters tries to use it +abstract class LegacyLazyModuleImp(module: LazyModule)(implicit val p: Parameters) + extends LazyModuleImp(module) with HasTileLinkParameters + +class TLLegacy(implicit val p: Parameters) extends LazyModule with HasTileLinkParameters +{ + // TL legacy clients don't support anything fancy + val node = TLClientNode(TLClientParameters( + sourceId = IdRange(0, 1 << tlClientXactIdBits))) + + lazy val module = new LegacyLazyModuleImp(this) { + val io = new Bundle { + val legacy = new ClientUncachedTileLinkIO()(p).flip + val out = node.bundleOut + } + + // TL legacy is dumb. All managers must support it's accesses. + val edge = node.edgesOut(0) + require (edge.manager.beatBytes == tlDataBytes) + edge.manager.managers.foreach { m => + // If a slave supports read at all, it must support all TL Legacy requires + if (m.supportsGet) { + require (m.supportsGet.contains(TransferSizes(tlDataBytes))) + require (m.supportsGet.contains(TransferSizes(tlDataBeats * tlDataBytes))) + } + // Likewise, any put support must mean full put support + if (m.supportsPutPartial) { + require (m.supportsPutPartial.contains(TransferSizes(tlDataBytes))) + require (m.supportsPutPartial.contains(TransferSizes(tlDataBeats * tlDataBytes))) + } + // Any atomic support => must support 32-bit up to beat size of all types + if (m.supportsArithmetic || m.supportsLogical) { + require (m.supportsArithmetic.contains(TransferSizes(4, tlDataBytes))) + require (m.supportsLogical .contains(TransferSizes(4, tlDataBytes))) + } + // We straight-up require hints + require (edge.manager.allSupportHint) + } + // TL legacy will not generate PutFull + // During conversion from TL Legacy, we won't support Acquire + + // Must be able to fit TL2 sink_id into TL legacy + require ((1 << tlManagerXactIdBits) >= edge.manager.endSinkId) + + val out = io.out(0) + out.a.valid := io.legacy.acquire.valid + out.d.ready := io.legacy.grant .ready + io.legacy.acquire.ready := out.a.ready + io.legacy.grant .valid := out.d.valid + + val source = io.legacy.acquire.bits.client_xact_id + val data = io.legacy.acquire.bits.data + val wmask = io.legacy.acquire.bits.wmask() + val address = io.legacy.acquire.bits.full_addr() + + val beat = UInt(log2Ceil(tlDataBytes)) + val block = UInt(log2Ceil(tlDataBytes*tlDataBeats)) + + // Only create atomic messages if TL2 managers support them + val atomics = if (edge.manager.anySupportLogical) { + MuxLookup(io.legacy.acquire.bits.op_code(), Wire(new TLBundleA(edge.bundle)), Array( + MemoryOpConstants.M_XA_SWAP -> edge.Logical(source, address, beat, data, TLAtomics.SWAP)._2, + MemoryOpConstants.M_XA_XOR -> edge.Logical(source, address, beat, data, TLAtomics.XOR) ._2, + MemoryOpConstants.M_XA_OR -> edge.Logical(source, address, beat, data, TLAtomics.OR) ._2, + MemoryOpConstants.M_XA_AND -> edge.Logical(source, address, beat, data, TLAtomics.AND) ._2, + MemoryOpConstants.M_XA_ADD -> edge.Arithmetic(source, address, beat, data, TLAtomics.ADD)._2, + MemoryOpConstants.M_XA_MIN -> edge.Arithmetic(source, address, beat, data, TLAtomics.MIN)._2, + MemoryOpConstants.M_XA_MAX -> edge.Arithmetic(source, address, beat, data, TLAtomics.MAX)._2, + MemoryOpConstants.M_XA_MINU -> edge.Arithmetic(source, address, beat, data, TLAtomics.MINU)._2, + MemoryOpConstants.M_XA_MAXU -> edge.Arithmetic(source, address, beat, data, TLAtomics.MAXU)._2)) + } else { + Wire(new TLBundleA(edge.bundle)) + } + + out.a.bits := MuxLookup(io.legacy.acquire.bits.a_type, Wire(new TLBundleA(edge.bundle)), Array( + Acquire.getType -> edge.Get (source, address, beat) ._2, + Acquire.getBlockType -> edge.Get (source, address, block)._2, + Acquire.putType -> edge.Put (source, address, beat, data, wmask)._2, + Acquire.putBlockType -> edge.Put (source, address, block, data, wmask)._2, + Acquire.getPrefetchType -> edge.Hint(source, address, block, UInt(0))._2, + Acquire.putPrefetchType -> edge.Hint(source, address, block, UInt(1))._2, + Acquire.putAtomicType -> atomics)) + + val beatMask = UInt(tlDataBytes-1) + val blockMask = UInt(tlDataBytes*tlDataBeats-1) + val addressMask = MuxLookup(io.legacy.acquire.bits.a_type, beatMask, Array( + Acquire.getType -> beatMask, + Acquire.getBlockType -> blockMask, + Acquire.putType -> beatMask, + Acquire.putBlockType -> blockMask, + Acquire.getPrefetchType -> blockMask, + Acquire.putPrefetchType -> blockMask, + Acquire.putAtomicType -> beatMask)) + + // Get rid of some unneeded muxes + out.a.bits.source := source + out.a.bits.data := data + out.a.bits.addr_hi := ~(~address | addressMask) >> log2Ceil(tlDataBytes) + + // TL legacy does not support bus errors + assert (!out.d.bits.error) + + // Recreate the beat address counter + val beatCounter = RegInit(UInt(0, width = tlBeatAddrBits)) + when (out.d.fire() && edge.hasData(out.d.bits) && out.d.bits.size === block) { + beatCounter := beatCounter + UInt(1) + } + + val grant = io.legacy.grant.bits + grant.g_type := MuxLookup(out.d.bits.opcode, Grant.prefetchAckType, Array( + TLMessages.AccessAck -> Grant.putAckType, + TLMessages.AccessAckData -> Mux(out.d.bits.size === beat, Grant.getDataBeatType, Grant.getDataBlockType), + TLMessages.HintAck -> Grant.prefetchAckType)) + grant.is_builtin_type := Bool(true) + grant.client_xact_id := out.d.bits.source + grant.manager_xact_id := out.d.bits.sink + grant.data := out.d.bits.data + grant.addr_beat := beatCounter + + // Tie off unused channels + out.b.ready := Bool(true) + out.c.valid := Bool(false) + out.e.valid := Bool(false) + } +} diff --git a/src/main/scala/uncore/tilelink2/Monitor.scala b/src/main/scala/uncore/tilelink2/Monitor.scala new file mode 100644 index 00000000..a74a6c20 --- /dev/null +++ b/src/main/scala/uncore/tilelink2/Monitor.scala @@ -0,0 +1,411 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import chisel3.internal.sourceinfo.{SourceInfo, SourceLine} + +object TLMonitor +{ + def extra(implicit sourceInfo: SourceInfo) = { + sourceInfo match { + case SourceLine(filename, line, col) => s" (connected at $filename:$line:$col)" + case _ => "" + } + } + + def legalizeFormatA(bundle: TLBundleA, edge: TLEdgeOut)(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.isHiAligned(bundle.addr_hi, bundle.size) + val mask = edge.mask(edge.addr_lo(bundle.mask), bundle.size) + + when (bundle.opcode === TLMessages.Acquire) { + assert (edge.manager.supportsAcquire(edge.address(bundle), bundle.size), "'A' channel carries Acquire type unsupported by manager" + 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.supportsGet(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.supportsPutFull(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.supportsPutPartial(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) + assert (bundle.mask =/= UInt(0), "'A' channel PutPartial has a zero mask" + extra) + } + + when (bundle.opcode === TLMessages.ArithmeticData) { + assert (edge.manager.supportsArithmetic(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.supportsLogical(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.supportsHint(edge.address(bundle)), "'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: TLEdgeIn)(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.contains(bundle.source) + val is_aligned = edge.isHiAligned(bundle.addr_hi, bundle.size) + val mask = edge.mask(edge.addr_lo(bundle.mask), bundle.size) + + 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 (bundle.size >= UInt(log2Ceil(edge.manager.beatBytes)), "'B' channel Probe smaller than a beat" + 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 === UInt(0).asUInt, "'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, "'A' 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) + assert (bundle.mask =/= UInt(0), "'B' channel PutPartial has a zero 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), "'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: TLEdgeOut)(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.isHiAligned(bundle.addr_hi, bundle.size) && edge.isLoAligned(bundle.addr_lo, bundle.size) + val address_ok = edge.manager.contains(bundle.source) + + 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) + assert (!bundle.error, "'C' channel ProbeData carries an error" + extra) + } + + when (bundle.opcode === TLMessages.Release) { + assert (edge.manager.supportsAcquire(edge.address(bundle), bundle.size), "'C' channel carries Release type unsupported by manager" + 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.supportsAcquire(edge.address(bundle), bundle.size), "'C' channel carries ReleaseData type unsupported by manager" + 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) + assert (!bundle.error, "'C' channel ReleaseData carries an error" + 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: TLEdgeIn)(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.isLoAligned(bundle.addr_lo, bundle.size) + val sink_ok = edge.manager.containsById(bundle.sink) + + 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: TLEdgeOut)(implicit sourceInfo: SourceInfo) = { + assert (edge.manager.containsById(bundle.sink), "'E' channels carries invalid sink ID" + extra) + } + + def legalizeFormat(bundleOut: TLBundle, edgeOut: TLEdgeOut, bundleIn: TLBundle, edgeIn: TLEdgeIn)(implicit sourceInfo: SourceInfo) = { + when (bundleOut.a.valid) { legalizeFormatA(bundleOut.a.bits, edgeOut) } + when (bundleIn .b.valid) { legalizeFormatB(bundleIn .b.bits, edgeIn) } + when (bundleOut.c.valid) { legalizeFormatC(bundleOut.c.bits, edgeOut) } + when (bundleIn .d.valid) { legalizeFormatD(bundleIn .d.bits, edgeIn) } + when (bundleOut.e.valid) { legalizeFormatE(bundleOut.e.bits, edgeOut) } + } + + def legalizeMultibeatA(a: DecoupledIO[TLBundleA], edge: TLEdgeOut)(implicit sourceInfo: SourceInfo) = { + val counter = RegInit(UInt(0, width = log2Up(edge.maxTransfer))) + val opcode = Reg(UInt()) + val param = Reg(UInt()) + val size = Reg(UInt()) + val source = Reg(UInt()) + val addr_hi = Reg(UInt()) + when (a.valid && counter =/= UInt(0)) { + 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.addr_hi=== addr_hi,"'A' channel addr_hi changed with multibeat operation" + extra) + } + when (a.fire()) { + counter := counter - UInt(1) + when (counter === UInt(0)) { + counter := edge.numBeats(a.bits) - UInt(1) + opcode := a.bits.opcode + param := a.bits.param + size := a.bits.size + source := a.bits.source + addr_hi := a.bits.addr_hi + } + } + } + + def legalizeMultibeatB(b: DecoupledIO[TLBundleB], edge: TLEdgeIn)(implicit sourceInfo: SourceInfo) = { + val counter = RegInit(UInt(0, width = log2Up(edge.maxTransfer))) + val opcode = Reg(UInt()) + val param = Reg(UInt()) + val size = Reg(UInt()) + val source = Reg(UInt()) + val addr_hi = Reg(UInt()) + when (b.valid && counter =/= UInt(0)) { + 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.addr_hi=== addr_hi,"'B' channel addr_hi changed with multibeat operation" + extra) + } + when (b.fire()) { + counter := counter - UInt(1) + when (counter === UInt(0)) { + counter := edge.numBeats(b.bits) - UInt(1) + opcode := b.bits.opcode + param := b.bits.param + size := b.bits.size + source := b.bits.source + addr_hi := b.bits.addr_hi + } + } + } + + def legalizeMultibeatC(c: DecoupledIO[TLBundleC], edge: TLEdgeOut)(implicit sourceInfo: SourceInfo) = { + val counter = RegInit(UInt(0, width = log2Up(edge.maxTransfer))) + val opcode = Reg(UInt()) + val param = Reg(UInt()) + val size = Reg(UInt()) + val source = Reg(UInt()) + val addr_hi = Reg(UInt()) + val addr_lo = Reg(UInt()) + when (c.valid && counter =/= UInt(0)) { + 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.addr_hi=== addr_hi,"'C' channel addr_hi changed with multibeat operation" + extra) + assert (c.bits.addr_lo=== addr_lo,"'C' channel addr_lo changed with multibeat operation" + extra) + } + when (c.fire()) { + counter := counter - UInt(1) + when (counter === UInt(0)) { + counter := edge.numBeats(c.bits) - UInt(1) + opcode := c.bits.opcode + param := c.bits.param + size := c.bits.size + source := c.bits.source + addr_hi := c.bits.addr_hi + addr_lo := c.bits.addr_lo + } + } + } + + def legalizeMultibeatD(d: DecoupledIO[TLBundleD], edge: TLEdgeIn)(implicit sourceInfo: SourceInfo) = { + val counter = RegInit(UInt(0, width = log2Up(edge.maxTransfer))) + 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 && counter =/= UInt(0)) { + 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,"'C' channel addr_lo changed with multibeat operation" + extra) + } + when (d.fire()) { + counter := counter - UInt(1) + when (counter === UInt(0)) { + counter := edge.numBeats(d.bits) - UInt(1) + 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(bundleOut: TLBundle, edgeOut: TLEdgeOut, bundleIn: TLBundle, edgeIn: TLEdgeIn)(implicit sourceInfo: SourceInfo) = { + legalizeMultibeatA(bundleOut.a, edgeOut) + legalizeMultibeatB(bundleOut.b, edgeIn) + legalizeMultibeatC(bundleOut.c, edgeOut) + legalizeMultibeatD(bundleOut.d, edgeIn) + } + + def legalize(bundleOut: TLBundle, edgeOut: TLEdgeOut, bundleIn: TLBundle, edgeIn: TLEdgeIn)(implicit sourceInfo: SourceInfo) = { + legalizeFormat (bundleOut, edgeOut, bundleIn, edgeIn) + legalizeMultibeat(bundleOut, edgeOut, bundleIn, edgeIn) + // !!! validate source uniqueness + } +} diff --git a/src/main/scala/uncore/tilelink2/Narrower.scala b/src/main/scala/uncore/tilelink2/Narrower.scala new file mode 100644 index 00000000..599386d9 --- /dev/null +++ b/src/main/scala/uncore/tilelink2/Narrower.scala @@ -0,0 +1,138 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import chisel3.internal.sourceinfo.SourceInfo +import scala.math.{min,max} + +// innBeatBytes => the bus width after the adapter +class TLNarrower(innerBeatBytes: Int) extends LazyModule +{ + val node = TLAdapterNode( + clientFn = { case Seq(c) => c }, + managerFn = { case Seq(m) => m.copy(beatBytes = innerBeatBytes) }) + + lazy val module = new LazyModuleImp(this) { + val io = new Bundle { + val in = node.bundleIn + val out = node.bundleOut + } + + val edgeOut = node.edgesOut(0) + val edgeIn = node.edgesIn(0) + val outerBeatBytes = edgeOut.manager.beatBytes + require (outerBeatBytes < innerBeatBytes) + + val ratio = innerBeatBytes / outerBeatBytes + val bce = edgeOut.manager.anySupportAcquire && edgeIn.client.anySupportProbe + + def trailingZeros(x: Int) = if (x > 0) Some(log2Ceil(x & -x)) else None + + def split(edge: TLEdge, in: TLDataChannel, fire: Bool): (Bool, UInt, UInt) = { + val dataSlices = Vec.tabulate (ratio) { i => edge.data(in)((i+1)*outerBeatBytes*8-1, i*outerBeatBytes*8) } + val maskSlices = Vec.tabulate (ratio) { i => edge.mask(in)((i+1)*outerBeatBytes -1, i*outerBeatBytes) } + val filter = Reg(UInt(width = ratio), init = SInt(-1, width = ratio).asUInt) + val mask = maskSlices.map(_.orR) + val hasData = edge.hasData(in) + + // decoded_size = 1111 (for smallest), 0101, 0001 (for largest) + val sizeOH1 = UIntToOH1(edge.size(in), log2Ceil(innerBeatBytes)) >> log2Ceil(outerBeatBytes) + 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 (fire) { + filter := new_filter + when (!hasData) { filter := SInt(-1, width = ratio).asUInt } + } + + if (edge.staticHasData(in) == Some(false)) { + (Bool(true), UInt(0), UInt(0)) + } else { + val select = Cat(mask.reverse) & new_filter + (last, Mux1H(select, dataSlices), Mux1H(select, maskSlices)) + } + } + + def merge(edge: TLEdge, in: TLDataChannel, fire: Bool): (Bool, UInt) = { + val count = RegInit(UInt(0, width = log2Ceil(ratio))) + val rdata = Reg(UInt(width = (ratio-1)*outerBeatBytes*8)) + val data = Cat(edge.data(in), rdata) + val first = count === UInt(0) + val limit = UIntToOH1(edge.size(in), log2Ceil(innerBeatBytes)) >> log2Ceil(outerBeatBytes) + val last = count === limit || !edge.hasData(in) + + when (fire) { + rdata := data >> outerBeatBytes*8 + count := count + UInt(1) + when (last) { count := UInt(0) } + } + + val cases = Seq.tabulate(log2Ceil(ratio)+1) { i => + val high = innerBeatBytes*8 + val take = (1 << i)*outerBeatBytes*8 + Fill(1 << (log2Ceil(ratio)-i), data(high-1, high-take)) + } + val mux = Vec.tabulate(log2Ceil(edge.maxTransfer)+1) { lgSize => + cases(min(max(lgSize - log2Ceil(outerBeatBytes), 0), log2Ceil(ratio))) + } + + if (edge.staticHasData(in) == Some(false)) { + (Bool(true), UInt(0)) + } else { + (last, mux(edge.size(in))) + } + } + + val in = io.in(0) + val out = io.out(0) + + val (alast, adata, amask) = split(edgeIn, in.a.bits, out.a.fire()) + in.a.ready := out.a.ready && alast + out.a.valid := in.a.valid + out.a.bits := in.a.bits + out.a.bits.addr_hi := Cat(in.a.bits.addr_hi, edgeIn.addr_lo(in.a.bits) >> log2Ceil(outerBeatBytes)) + out.a.bits.data := adata + out.a.bits.mask := amask + + val (dlast, ddata) = merge(edgeOut, out.d.bits, out.d.fire()) + out.d.ready := in.d.ready || !dlast + in.d.valid := out.d.valid && dlast + in.d.bits := out.d.bits + in.d.bits.data := ddata + + if (bce) { + require (false) + // C has no wmask !!! +// val (clast, cdata, cmask) = split(in.c.bits, out.c.fire()) +// in.c.ready := out.c.ready && clast +// out.c.valid := in.c.valid +// out.c.bits := in.c.bits +// out.c.bits.data := cdata +// out.c.bits.mask := cmask + + 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 TLNarrower +{ + // applied to the TL source node; connect (Narrower(x.node, 16) -> y.node) + def apply(x: TLBaseNode, innerBeatBytes: Int)(implicit lazyModule: LazyModule, sourceInfo: SourceInfo): TLBaseNode = { + val narrower = LazyModule(new TLNarrower(innerBeatBytes)) + lazyModule.connect(x -> narrower.node) + narrower.node + } +} diff --git a/src/main/scala/uncore/tilelink2/Nodes.scala b/src/main/scala/uncore/tilelink2/Nodes.scala new file mode 100644 index 00000000..1679c17b --- /dev/null +++ b/src/main/scala/uncore/tilelink2/Nodes.scala @@ -0,0 +1,108 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import scala.collection.mutable.ListBuffer +import chisel3.internal.sourceinfo.SourceInfo + +// PI = PortInputParameters +// PO = PortOutputParameters +// EI = EdgeInput +// EO = EdgeOutput +abstract class NodeImp[PO, PI, EO, EI, B <: Bundle] +{ + def edgeO(po: PO, pi: PI): EO + def edgeI(po: PO, pi: PI): EI + def bundleO(eo: Seq[EO]): Vec[B] + def bundleI(ei: Seq[EI]): Vec[B] + def connect(bo: B, eo: EO, bi: B, ei: EI)(implicit sourceInfo: SourceInfo): Unit +} + +class BaseNode[PO, PI, EO, EI, B <: Bundle](imp: NodeImp[PO, PI, EO, EI, B])( + private val oFn: Option[Seq[PO] => PO], + private val iFn: Option[Seq[PI] => PI], + private val numPO: Range.Inclusive, + private val numPI: Range.Inclusive) +{ + // At least 0 ports must be supported + require (!numPO.isEmpty) + require (!numPI.isEmpty) + require (numPO.start >= 0) + require (numPI.start >= 0) + + val noOs = numPO.size == 1 && numPO.contains(0) + val noIs = numPI.size == 1 && numPI.contains(0) + + require (noOs || oFn.isDefined) + require (noIs || iFn.isDefined) + + private val accPO = ListBuffer[BaseNode[PO, PI, EO, EI, B]]() + private val accPI = ListBuffer[BaseNode[PO, PI, EO, EI, B]]() + private var oRealized = false + private var iRealized = false + + private lazy val oPorts = { oRealized = true; require (numPO.contains(accPO.size)); accPO.result() } + private lazy val iPorts = { iRealized = true; require (numPI.contains(accPI.size)); accPI.result() } + private lazy val oParams : Option[PO] = oFn.map(_(iPorts.map(_.oParams.get))) + private lazy val iParams : Option[PI] = iFn.map(_(oPorts.map(_.iParams.get))) + + lazy val edgesOut = oPorts.map { n => imp.edgeO(oParams.get, n.iParams.get) } + lazy val edgesIn = iPorts.map { n => imp.edgeI(n.oParams.get, iParams.get) } + + lazy val bundleOut = imp.bundleO(edgesOut) + lazy val bundleIn = imp.bundleI(edgesIn) + + def connectOut = bundleOut + def connectIn = bundleIn + + // source.edge(sink) + protected[tilelink2] def edge(x: BaseNode[PO, PI, EO, EI, B])(implicit sourceInfo: SourceInfo) = { + require (!noOs) + require (!oRealized) + require (!x.noIs) + require (!x.iRealized) + val i = x.accPI.size + val o = accPO.size + accPO += x + x.accPI += this + () => { + imp.connect(connectOut(o), edgesOut(o), x.connectIn(i), x.edgesIn(i)) + } + } +} + +class IdentityNode[PO, PI, EO, EI, B <: Bundle](imp: NodeImp[PO, PI, EO, EI, B]) + extends BaseNode(imp)(Some{case Seq(x) => x}, Some{case Seq(x) => x}, 1 to 1, 1 to 1) + +class OutputNode[PO, PI, EO, EI, B <: Bundle](imp: NodeImp[PO, PI, EO, EI, B]) extends IdentityNode(imp) +{ + override def connectOut = bundleOut + override def connectIn = bundleOut +} + +class InputNode[PO, PI, EO, EI, B <: Bundle](imp: NodeImp[PO, PI, EO, EI, B]) extends IdentityNode(imp) +{ + override def connectOut = bundleIn + override def connectIn = bundleIn +} + +class SourceNode[PO, PI, EO, EI, B <: Bundle](imp: NodeImp[PO, PI, EO, EI, B])(po: PO, num: Range.Inclusive = 1 to 1) + extends BaseNode(imp)(Some{case Seq() => po}, None, num, 0 to 0) +{ + require (num.end >= 1) +} + +class SinkNode[PO, PI, EO, EI, B <: Bundle](imp: NodeImp[PO, PI, EO, EI, B])(pi: PI, num: Range.Inclusive = 1 to 1) + extends BaseNode(imp)(None, Some{case Seq() => pi}, 0 to 0, num) +{ + require (num.end >= 1) +} + +class InteriorNode[PO, PI, EO, EI, B <: Bundle](imp: NodeImp[PO, PI, EO, EI, B]) + (oFn: Seq[PO] => PO, iFn: Seq[PI] => PI, numPO: Range.Inclusive, numPI: Range.Inclusive) + extends BaseNode(imp)(Some(oFn), Some(iFn), numPO, numPI) +{ + require (numPO.end >= 1) + require (numPI.end >= 1) +} diff --git a/src/main/scala/uncore/tilelink2/Parameters.scala b/src/main/scala/uncore/tilelink2/Parameters.scala new file mode 100644 index 00000000..355fdb3a --- /dev/null +++ b/src/main/scala/uncore/tilelink2/Parameters.scala @@ -0,0 +1,330 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import scala.math.max + +/** Options for memory regions */ +object RegionType { + sealed trait T + case object CACHED extends T + case object TRACKED extends T + case object UNCACHED extends T + case object PUT_EFFECTS extends T + case object GET_EFFECTS extends T // GET_EFFECTS => PUT_EFFECTS + val cases = Seq(CACHED, TRACKED, UNCACHED, PUT_EFFECTS, GET_EFFECTS) +} + +// A non-empty half-open range; [start, end) +case class IdRange(start: Int, end: Int) +{ + require (start >= 0) + require (start < end) // not empty + + // This is a strict partial ordering + def <(x: IdRange) = end <= x.start + def >(x: IdRange) = x < this + + def overlaps(x: IdRange) = start < x.end && x.start < end + def contains(x: IdRange) = start <= x.start && x.end <= end + // contains => overlaps (because empty is forbidden) + + def contains(x: Int) = start <= x && x < end + def contains(x: UInt) = + if (start+1 == end) { UInt(start) === x } + else if (isPow2(end-start) && ((end | start) & (end-start-1)) == 0) + { ~(~(UInt(start) ^ x) | UInt(end-start-1)) === UInt(0) } + else { UInt(start) <= x && x < UInt(end) } + + def shift(x: Int) = IdRange(start+x, end+x) + def size = end - start +} + +// An potentially empty inclusive range of 2-powers [min, max] (in bytes) +case class TransferSizes(min: Int, max: Int) +{ + def this(x: Int) = this(x, x) + + require (min <= max) + require (min >= 0 && max >= 0) + require (max == 0 || isPow2(max)) + require (min == 0 || isPow2(min)) + require (max == 0 || min != 0) // 0 is forbidden unless (0,0) + + def none = min == 0 + def contains(x: Int) = isPow2(x) && min <= x && x <= max + def containsLg(x: Int) = contains(1 << x) + def containsLg(x: UInt) = + if (none) Bool(false) + else if (min == max) { UInt(log2Ceil(min)) === x } + else { UInt(log2Ceil(min)) <= x && x <= UInt(log2Ceil(max)) } + + def contains(x: TransferSizes) = x.none || (min <= x.min && x.max <= max) + + def intersect(x: TransferSizes) = + if (x.max < min || min < x.max) TransferSizes.none + else TransferSizes(scala.math.max(min, x.min), scala.math.min(max, x.max)) +} + +object TransferSizes { + def apply(x: Int) = new TransferSizes(x) + val none = new TransferSizes(0) + + implicit def asBool(x: TransferSizes) = !x.none +} + +// AddressSets specify the address space managed by the manager +// Base is the base address, and mask are the bits consumed by the manager +// e.g: base=0x200, mask=0xff describes a device managing 0x200-0x2ff +// e.g: base=0x1000, mask=0xf0f decribes a device managing 0x1000-0x100f, 0x1100-0x110f, ... +case class AddressSet(base: BigInt, mask: BigInt) +{ + // Forbid misaligned base address (and empty sets) + require ((base & mask) == 0) + + def contains(x: BigInt) = ~(~(x ^ base) | mask) == 0 + def contains(x: UInt) = ~(~(x ^ UInt(base)) | UInt(mask)) === UInt(0) + + // overlap iff bitwise: both care (~mask0 & ~mask1) => both equal (base0=base1) + def overlaps(x: AddressSet) = (~(mask | x.mask) & (base ^ x.base)) == 0 + + // contains iff bitwise: x.mask => mask && contains(x.base) + def contains(x: AddressSet) = ((x.mask | (base ^ x.base)) & ~mask) == 0 + // 1 less than the number of bytes to which the manager should be aligned + def alignment1 = ((mask + 1) & ~mask) - 1 + def max = base | mask + + // A strided slave serves discontiguous ranges + def strided = alignment1 != mask +} + +case class TLManagerParameters( + address: Seq[AddressSet], + sinkId: IdRange = IdRange(0, 1), + regionType: RegionType.T = RegionType.GET_EFFECTS, + // Supports both Acquire+Release+Finish of these sizes + supportsAcquire: TransferSizes = TransferSizes.none, + supportsArithmetic: TransferSizes = TransferSizes.none, + supportsLogical: TransferSizes = TransferSizes.none, + supportsGet: TransferSizes = TransferSizes.none, + supportsPutFull: TransferSizes = TransferSizes.none, + supportsPutPartial: TransferSizes = TransferSizes.none, + supportsHint: Boolean = false, + // If fifoId=Some, all accesses sent to the same fifoId are executed and ACK'd in FIFO order + fifoId: Option[Int] = None) +{ + address.combinations(2).foreach({ case Seq(x,y) => + require (!x.overlaps(y)) + }) + address.foreach({ case a => + require (supportsAcquire.none || a.alignment1 >= supportsAcquire.max-1) + }) + + // Largest support transfer of all types + val maxTransfer = List( + supportsAcquire.max, + supportsArithmetic.max, + supportsLogical.max, + supportsGet.max, + supportsPutFull.max, + supportsPutPartial.max).max + + // The device had better not support a transfer larger than it's alignment + address.foreach({ case a => + require (a.alignment1 >= maxTransfer-1) + }) +} + +case class TLManagerPortParameters(managers: Seq[TLManagerParameters], beatBytes: Int) +{ + require (!managers.isEmpty) + require (isPow2(beatBytes)) + + // Require disjoint ranges for Ids and addresses + managers.combinations(2).foreach({ case Seq(x,y) => + require (!x.sinkId.overlaps(y.sinkId)) + x.address.foreach({ a => y.address.foreach({ b => + require (!a.overlaps(b)) + })}) + }) + + // Bounds on required sizes + def endSinkId = managers.map(_.sinkId.end).max + def maxAddress = managers.map(_.address.map(_.max).max).max + def maxTransfer = managers.map(_.maxTransfer).max + + // Operation sizes supported by all outward Managers + val allSupportAcquire = managers.map(_.supportsAcquire) .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(_ && _) + + // Operation supported by at least one outward Managers + val anySupportAcquire = managers.map(!_.supportsAcquire.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) .reduce(_ || _) + + // These return Option[TLManagerParameters] for your convenience + def find(address: BigInt) = managers.find(_.address.exists(_.contains(address))) + def findById(id: Int) = managers.find(_.sinkId.contains(id)) + + // Synthesizable lookup methods + def find(address: UInt) = Vec(managers.map(_.address.map(_.contains(address)).reduce(_ || _))) + def findById(id: UInt) = Vec(managers.map(_.sinkId.contains(id))) + + // !!! need a cheaper version of find, where we assume a valid address match exists + + // Does this Port manage this ID/address? + def contains(address: UInt) = find(address).reduce(_ || _) + def containsById(id: UInt) = findById(id).reduce(_ || _) + + private def safety_helper(member: TLManagerParameters => TransferSizes)(address: UInt, lgSize: UInt) = { + val allSame = managers.map(member(_) == member(managers(0))).reduce(_ && _) + if (allSame) member(managers(0)).containsLg(lgSize) else { + Mux1H(find(address), managers.map(member(_).containsLg(lgSize))) + } + } + + // Check for support of a given operation at a specific address + val supportsAcquire = safety_helper(_.supportsAcquire) _ + 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) _ + def supportsHint(address: UInt) = { + if (allSupportHint) Bool(true) else { + Mux1H(find(address), managers.map(m => Bool(m.supportsHint))) + } + } +} + +case class TLClientParameters( + sourceId: IdRange = IdRange(0,1), + // 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: Boolean = false) +{ + val maxTransfer = List( + supportsProbe.max, + supportsArithmetic.max, + supportsLogical.max, + supportsGet.max, + supportsPutFull.max, + supportsPutPartial.max).max +} + +case class TLClientPortParameters(clients: Seq[TLClientParameters]) { + require (!clients.isEmpty) + + // Require disjoint ranges for Ids + clients.combinations(2).foreach({ case Seq(x,y) => + require (!x.sourceId.overlaps(y.sourceId)) + }) + + // 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(_ && _) + + // 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) .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(_ || _) + + 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) _ + def supportsHint(id: UInt) = { + if (allSupportHint) Bool(true) else { + Mux1H(find(id), clients.map(c => Bool(c.supportsHint))) + } + } +} + +case class TLBundleParameters( + addrHiBits: Int, + dataBits: Int, + sourceBits: Int, + sinkBits: Int, + sizeBits: Int) +{ + // Chisel has issues with 0-width wires + require (addrHiBits >= 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(addrHiBits, x.addrHiBits), + max(dataBits, x.dataBits), + max(sourceBits, x.sourceBits), + max(sinkBits, x.sinkBits), + max(sizeBits, x.sizeBits)) +} + +case class TLEdgeParameters( + client: TLClientPortParameters, + manager: TLManagerPortParameters) +{ + val maxTransfer = max(client.maxTransfer, manager.maxTransfer) + val maxLgSize = log2Up(maxTransfer) + + // Sanity check the link... + require (maxTransfer >= manager.beatBytes) + + val bundle = TLBundleParameters( + addrHiBits = log2Up(manager.maxAddress + 1) - log2Up(manager.beatBytes), + dataBits = manager.beatBytes * 8, + sourceBits = log2Up(client.endSourceId), + sinkBits = log2Up(manager.endSinkId), + sizeBits = log2Up(maxLgSize+1)) +} diff --git a/src/main/scala/uncore/tilelink2/RegField.scala b/src/main/scala/uncore/tilelink2/RegField.scala new file mode 100644 index 00000000..444a3c62 --- /dev/null +++ b/src/main/scala/uncore/tilelink2/RegField.scala @@ -0,0 +1,85 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ + +case class RegReadFn private(combinational: Boolean, fn: (Bool, Bool) => (Bool, Bool, UInt)) +object RegReadFn +{ + // (ivalid: Bool, oready: Bool) => (iready: Bool, ovalid: Bool, data: UInt) + // iready may combinationally depend on oready + // all other combinational dependencies forbidden (e.g. ovalid <= ivalid) + // iready must eventually go high without requiring ivalid to go high + // ovalid must eventually go high without requiring oready to go high + // effects must become visible on the cycle after ovalid && oready + implicit def apply(x: (Bool, Bool) => (Bool, Bool, UInt)) = + new RegReadFn(false, x) + // (ready: Bool) => (valid: Bool, data: UInt) + // valid must not combinationally depend on ready + // valid must eventually go high without requiring ready to go high + // => this means that reading cannot trigger creation of the output data + // if you need this, use the more general i&o ready-valid method above + // effects must become visible on the cycle after valid && ready + implicit def apply(x: Bool => (Bool, UInt)) = + new RegReadFn(true, { case (_, oready) => + val (ovalid, data) = x(oready) + (Bool(true), ovalid, data) + }) + // read from a DecoupledIO (only safe if there is a consistent source of data) + implicit def apply(x: DecoupledIO[UInt]):RegReadFn = RegReadFn(ready => { x.ready := ready; (x.valid, x.bits) }) + // read from a register + implicit def apply(x: UInt):RegReadFn = RegReadFn(ready => (Bool(true), x)) + // noop + implicit def apply(x: Unit):RegReadFn = RegReadFn(UInt(0)) +} + +case class RegWriteFn private(combinational: Boolean, fn: (Bool, Bool, UInt) => (Bool, Bool)) +object RegWriteFn +{ + // (ivalid: Bool, oready: Bool, data: UInt) => (iready: Bool, ovalid: Bool) + // iready may combinationally depend on both oready and data + // all other combinational dependencies forbidden (e.g. ovalid <= ivalid) + // iready must eventually go high without requiring ivalid to go high + // ovalid must eventually go high without requiring oready to go high + // effects must become visible on the cycle after ovalid && oready + implicit def apply(x: (Bool, Bool, UInt) => (Bool, Bool)) = + new RegWriteFn(false, x) + // (valid: Bool, data: UInt) => (ready: Bool) + // ready may combinationally depend on data (but not valid) + // ready must eventually go high without requiring valid to go high + // effects must become visible on the cycle after valid && ready + implicit def apply(x: (Bool, UInt) => Bool) = + // combinational => data valid on oready + new RegWriteFn(true, { case (_, oready, data) => + (Bool(true), x(oready, data)) + }) + // write to a DecoupledIO (only safe if there is a consistent sink draining data) + implicit def apply(x: DecoupledIO[UInt]): RegWriteFn = RegWriteFn((valid, data) => { x.valid := valid; x.bits := data; x.ready }) + // updates a register + implicit def apply(x: UInt): RegWriteFn = RegWriteFn((valid, data) => { when (valid) { x := data }; Bool(true) }) + // noop + implicit def apply(x: Unit): RegWriteFn = RegWriteFn((valid, data) => { Bool(true) }) +} + +case class RegField(width: Int, read: RegReadFn, write: RegWriteFn) +{ + require (width > 0) + def pipelined = !read.combinational || !write.combinational +} + +object RegField +{ + type Map = (Int, Seq[RegField]) + def apply(n: Int) : RegField = apply(n, (), ()) + def apply(n: Int, rw: UInt) : RegField = apply(n, rw, rw) + def R(n: Int, r: RegReadFn) : RegField = apply(n, r, ()) + def W(n: Int, w: RegWriteFn) : RegField = apply(n, (), w) +} + +trait HasRegMap +{ + def regmap(mapping: RegField.Map*): Unit +} + +// See GPIO.scala for an example of how to use regmap diff --git a/src/main/scala/uncore/tilelink2/RegMapper.scala b/src/main/scala/uncore/tilelink2/RegMapper.scala new file mode 100644 index 00000000..f7e34e5d --- /dev/null +++ b/src/main/scala/uncore/tilelink2/RegMapper.scala @@ -0,0 +1,148 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ + +// A bus agnostic register interface to a register-based device + +case class RegMapperParams(indexBits: Int, maskBits: Int, extraBits: Int) + +class RegMapperInput(params: RegMapperParams) extends GenericParameterizedBundle(params) +{ + val read = Bool() + val index = UInt(width = params.indexBits) + val data = UInt(width = params.maskBits*8) + val mask = UInt(width = params.maskBits) + val extra = UInt(width = params.extraBits) +} + +class RegMapperOutput(params: RegMapperParams) extends GenericParameterizedBundle(params) +{ + val read = Bool() + val data = UInt(width = params.maskBits*8) + val extra = UInt(width = params.extraBits) +} + +object RegMapper +{ + // Create a generic register-based device + def apply(bytes: Int, concurrency: Option[Int], in: DecoupledIO[RegMapperInput], mapping: RegField.Map*) = { + val regmap = mapping.toList + require (!regmap.isEmpty) + + // Ensure no register appears twice + regmap.combinations(2).foreach { case Seq((reg1, _), (reg2, _)) => + require (reg1 != reg2) + } + + // Flatten the regmap into (Reg:Int, Offset:Int, field:RegField) + val flat = regmap.map { case (reg, fields) => + val offsets = fields.scanLeft(0)(_ + _.width).init + (offsets zip fields) map { case (o, f) => (reg, o, f) } + }.flatten + require (!flat.isEmpty) + + val endIndex = 1 << log2Ceil(regmap.map(_._1).max+1) + val params = RegMapperParams(log2Up(endIndex), bytes, in.bits.params.extraBits) + + val out = Wire(Decoupled(new RegMapperOutput(params))) + val front = Wire(Decoupled(new RegMapperInput(params))) + front.bits := in.bits + + // Must this device pipeline the control channel? + val pipelined = flat.map(_._3.pipelined).reduce(_ || _) + val depth = concurrency.getOrElse(if (pipelined) 1 else 0) + require (depth >= 0) + require (!pipelined || depth > 0) + val back = if (depth > 0) Queue(front, depth, pipe = depth == 1) else front + + // Forward declaration of all flow control signals + val rivalid = Wire(Vec(flat.size, Bool())) + val wivalid = Wire(Vec(flat.size, Bool())) + val riready = Wire(Vec(flat.size, Bool())) + val wiready = Wire(Vec(flat.size, Bool())) + val rovalid = Wire(Vec(flat.size, Bool())) + val wovalid = Wire(Vec(flat.size, Bool())) + val roready = Wire(Vec(flat.size, Bool())) + val woready = Wire(Vec(flat.size, Bool())) + + // Per-register list of all control signals needed for data to flow + val rifire = Array.tabulate(endIndex) { i => Seq(Bool(true)) } + val wifire = Array.tabulate(endIndex) { i => Seq(Bool(true)) } + val rofire = Array.tabulate(endIndex) { i => Seq(Bool(true)) } + val wofire = Array.tabulate(endIndex) { i => Seq(Bool(true)) } + + // The output values for each register + val dataOut = Array.tabulate(endIndex) { _ => UInt(0) } + + // Which bits are touched? + val frontMask = FillInterleaved(8, front.bits.mask) + val backMask = FillInterleaved(8, back .bits.mask) + + // Connect the fields + for (i <- 0 until flat.size) { + val (reg, low, field) = flat(i) + val high = low + field.width - 1 + // Confirm that no register is too big + require (high < 8*bytes) + val rimask = frontMask(high, low).orR() + val wimask = frontMask(high, low).andR() + val romask = backMask(high, low).orR() + val womask = backMask(high, low).andR() + val data = if (field.write.combinational) back.bits.data else front.bits.data + val (f_riready, f_rovalid, f_data) = field.read.fn(rivalid(i) && rimask, roready(i) && romask) + val (f_wiready, f_wovalid) = field.write.fn(wivalid(i) && wimask, woready(i) && womask, data(high, low)) + riready(i) := f_riready || !rimask + wiready(i) := f_wiready || !wimask + rovalid(i) := f_rovalid || !romask + wovalid(i) := f_wovalid || !womask + rifire(reg) = riready(i) +: rifire(reg) + wifire(reg) = wiready(i) +: wifire(reg) + rofire(reg) = rovalid(i) +: rofire(reg) + wofire(reg) = wovalid(i) +: wofire(reg) + dataOut(reg) = dataOut(reg) | ((f_data << low) & (~UInt(0, width = high+1))) + } + + // Is the selected register ready? + val rifireMux = Vec(rifire.map(_.reduce(_ && _))) + val wifireMux = Vec(wifire.map(_.reduce(_ && _))) + val rofireMux = Vec(rofire.map(_.reduce(_ && _))) + val wofireMux = Vec(wofire.map(_.reduce(_ && _))) + val iready = Mux(front.bits.read, rifireMux(front.bits.index), wifireMux(front.bits.index)) + val oready = Mux(back .bits.read, rofireMux(back .bits.index), wofireMux(back .bits.index)) + + // Connect the pipeline + in.ready := front.ready && iready + front.valid := in.valid && iready + back.ready := out.ready && oready + out.valid := back.valid && oready + + // Which register is touched? + val frontSel = UIntToOH(front.bits.index) + val backSel = UIntToOH(back.bits.index) + + // Include the per-register one-hot selected criteria + for (reg <- 0 until endIndex) { + rifire(reg) = (in.valid && front.ready && front.bits.read && frontSel(reg)) +: rifire(reg) + wifire(reg) = (in.valid && front.ready && !front.bits.read && frontSel(reg)) +: wifire(reg) + rofire(reg) = (back.valid && out.ready && back .bits.read && backSel (reg)) +: rofire(reg) + wofire(reg) = (back.valid && out.ready && !back .bits.read && backSel (reg)) +: wofire(reg) + } + + // Connect the field's ivalid and oready + for (i <- 0 until flat.size) { + val (reg, _, _ ) = flat(i) + rivalid(i) := rifire(reg).filter(_ ne riready(i)).reduce(_ && _) + wivalid(i) := wifire(reg).filter(_ ne wiready(i)).reduce(_ && _) + roready(i) := rofire(reg).filter(_ ne rovalid(i)).reduce(_ && _) + woready(i) := wofire(reg).filter(_ ne wovalid(i)).reduce(_ && _) + } + + out.bits.read := back.bits.read + out.bits.data := Vec(dataOut)(back.bits.index) + out.bits.extra := back.bits.extra + + (endIndex, out) + } +} diff --git a/src/main/scala/uncore/tilelink2/RegisterRouter.scala b/src/main/scala/uncore/tilelink2/RegisterRouter.scala new file mode 100644 index 00000000..e455efda --- /dev/null +++ b/src/main/scala/uncore/tilelink2/RegisterRouter.scala @@ -0,0 +1,103 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ + +class TLRegisterNode(address: AddressSet, concurrency: Option[Int] = None, beatBytes: Int = 4) + extends TLManagerNode(beatBytes, TLManagerParameters( + address = Seq(address), + supportsGet = TransferSizes(1, beatBytes), + supportsPutPartial = TransferSizes(1, beatBytes), + supportsPutFull = TransferSizes(1, beatBytes), + fifoId = Some(0))) // requests are handled in order +{ + require (!address.strided) + + // 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) = (log2Ceil(beatBytes) + sourceEnd, sourceEnd) + + val params = RegMapperParams(log2Up(address.mask+1), beatBytes, addrLoEnd) + val in = Wire(Decoupled(new RegMapperInput(params))) + in.bits.read := a.bits.opcode === TLMessages.Get + in.bits.index := a.bits.addr_hi + 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 (endIndex, out) = RegMapper(beatBytes, concurrency, in, mapping:_*) + + // All registers must fit inside the device address space + require (address.mask >= (endIndex-1)*beatBytes) + + // 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: AddressSet, concurrency: Option[Int] = None, beatBytes: Int = 4) = + new TLRegisterNode(address, concurrency, beatBytes) +} + +// These convenience methods below combine to make it possible to create a TL2 +// register mapped device from a totally abstract register mapped device. +// See GPIO.scala in this directory for an example + +abstract class TLRegisterRouterBase(address: AddressSet, concurrency: Option[Int], beatBytes: Int) extends LazyModule +{ + val node = TLRegisterNode(address, concurrency, beatBytes) +} + +class TLRegBundle[P](val params: P, val in: Vec[TLBundle]) extends Bundle + +class TLRegModule[P, B <: Bundle](val params: P, bundleBuilder: => B, router: TLRegisterRouterBase) + extends LazyModuleImp(router) with HasRegMap +{ + val io = bundleBuilder + def regmap(mapping: RegField.Map*) = router.node.regmap(mapping:_*) +} + +class TLRegisterRouter[B <: Bundle, M <: LazyModuleImp] + (base: BigInt, size: BigInt = 4096, concurrency: Option[Int] = None, beatBytes: Int = 4) + (bundleBuilder: Vec[TLBundle] => B) + (moduleBuilder: (=> B, TLRegisterRouterBase) => M) + extends TLRegisterRouterBase(AddressSet(base, size-1), concurrency, beatBytes) +{ + require (size % 4096 == 0) // devices should be 4K aligned + require (isPow2(size)) + require (size >= 4096) + + lazy val module = moduleBuilder(bundleBuilder(node.bundleIn), this) +} diff --git a/src/main/scala/uncore/tilelink2/SRAM.scala b/src/main/scala/uncore/tilelink2/SRAM.scala new file mode 100644 index 00000000..07d107df --- /dev/null +++ b/src/main/scala/uncore/tilelink2/SRAM.scala @@ -0,0 +1,74 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ + +class TLRAM(address: AddressSet, beatBytes: Int = 4) extends LazyModule +{ + val node = TLManagerNode(beatBytes, TLManagerParameters( + address = List(address), + regionType = RegionType.UNCACHED, + supportsGet = TransferSizes(1, beatBytes), + supportsPutPartial = TransferSizes(1, beatBytes), + supportsPutFull = TransferSizes(1, beatBytes), + fifoId = Some(0))) // requests are handled in order + + // 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 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 addrBits = (mask zip in.a.bits.addr_hi.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 + + val edge = node.edgesIn(0) + 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) + when (read) { + rdata := mem.read(memAddress) + } .otherwise { + mem.write(memAddress, wdata, in.a.bits.mask.toBools) + } + } + + // Tie off unused channels + in.b.valid := Bool(false) + in.c.ready := Bool(true) + in.e.ready := Bool(true) + } +} diff --git a/src/main/scala/uncore/tilelink2/TLNodes.scala b/src/main/scala/uncore/tilelink2/TLNodes.scala new file mode 100644 index 00000000..88a27042 --- /dev/null +++ b/src/main/scala/uncore/tilelink2/TLNodes.scala @@ -0,0 +1,44 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ +import scala.collection.mutable.ListBuffer +import chisel3.internal.sourceinfo.SourceInfo + +object TLImp extends NodeImp[TLClientPortParameters, TLManagerPortParameters, TLEdgeOut, TLEdgeIn, TLBundle] +{ + def edgeO(po: TLClientPortParameters, pi: TLManagerPortParameters): TLEdgeOut = new TLEdgeOut(po, pi) + def edgeI(po: TLClientPortParameters, pi: TLManagerPortParameters): TLEdgeIn = new TLEdgeIn(po, pi) + def bundleO(eo: Seq[TLEdgeOut]): Vec[TLBundle] = { + require (!eo.isEmpty) + Vec(eo.size, TLBundle(eo.map(_.bundle).reduce(_.union(_)))) + } + def bundleI(ei: Seq[TLEdgeIn]): Vec[TLBundle] = { + require (!ei.isEmpty) + Vec(ei.size, TLBundle(ei.map(_.bundle).reduce(_.union(_)))).flip + } + + def connect(bo: TLBundle, eo: TLEdgeOut, bi: TLBundle, ei: TLEdgeIn)(implicit sourceInfo: SourceInfo): Unit = { + require (eo.asInstanceOf[TLEdgeParameters] == ei.asInstanceOf[TLEdgeParameters]) + TLMonitor.legalize(bo, eo, bi, ei) + bi <> bo + } +} + +case class TLIdentityNode() extends IdentityNode(TLImp) +case class TLOutputNode() extends OutputNode(TLImp) +case class TLInputNode() extends InputNode(TLImp) + +case class TLClientNode(params: TLClientParameters, numPorts: Range.Inclusive = 1 to 1) + extends SourceNode(TLImp)(TLClientPortParameters(Seq(params)), numPorts) + +case class TLManagerNode(beatBytes: Int, params: TLManagerParameters, numPorts: Range.Inclusive = 1 to 1) + extends SinkNode(TLImp)(TLManagerPortParameters(Seq(params), beatBytes), numPorts) + +case class TLAdapterNode( + clientFn: Seq[TLClientPortParameters] => TLClientPortParameters, + managerFn: Seq[TLManagerPortParameters] => TLManagerPortParameters, + numClientPorts: Range.Inclusive = 1 to 1, + numManagerPorts: Range.Inclusive = 1 to 1) + extends InteriorNode(TLImp)(clientFn, managerFn, numClientPorts, numManagerPorts) diff --git a/src/main/scala/uncore/tilelink2/Xbar.scala b/src/main/scala/uncore/tilelink2/Xbar.scala new file mode 100644 index 00000000..9302ea10 --- /dev/null +++ b/src/main/scala/uncore/tilelink2/Xbar.scala @@ -0,0 +1,218 @@ +// See LICENSE for license details. + +package uncore.tilelink2 + +import Chisel._ + +object TLXbar +{ + def lowestIndex(requests: Vec[Bool], execute: Bool) = { + // lowest-index first is stateless; ignore execute + val ors = Vec(requests.scanLeft(Bool(false))(_ || _).init) // prefix-OR + Vec((ors zip requests) map { case (o, r) => !o && r }) + } +} + +class TLXbar(policy: (Vec[Bool], Bool) => Seq[Bool] = TLXbar.lowestIndex) extends LazyModule +{ + def mapInputIds (ports: Seq[TLClientPortParameters ]) = assignRanges(ports.map(_.endSourceId)) + def mapOutputIds(ports: Seq[TLManagerPortParameters]) = assignRanges(ports.map(_.endSinkId)) + + def assignRanges(sizes: Seq[Int]) = { + val pow2Sizes = sizes.map(1 << log2Ceil(_)) + 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) => (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 + } + } + } + } + + val node = TLAdapterNode( + numClientPorts = 1 to 32, + numManagerPorts = 1 to 32, + clientFn = { seq => + val clients = (mapInputIds(seq) zip seq) flatMap { case (range, port) => + port.clients map { client => client.copy( + sourceId = client.sourceId.shift(range.start) + )} + } + TLClientPortParameters(clients) + }, + managerFn = { seq => + val fifoIdFactory = relabeler() + val managers = (mapOutputIds(seq) zip seq) flatMap { case (range, port) => + require (port.beatBytes == seq(0).beatBytes) + val fifoIdMapper = fifoIdFactory() + port.managers map { manager => manager.copy( + sinkId = manager.sinkId.shift(range.start), + fifoId = manager.fifoId.map(fifoIdMapper(_)) + )} + } + TLManagerPortParameters(managers, seq(0).beatBytes) + }) + + 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 = mapInputIds(node.edgesIn.map(_.client)) + val outputIdRanges = mapOutputIds(node.edgesOut.map(_.manager)) + + // We need an intermediate size of bundle with the widest possible identifiers + val wide_bundle = io.in(0).params.union(io.out(0).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) + def transpose(x: Seq[Seq[Bool]]) = Vec.tabulate(x(0).size) { i => Vec.tabulate(x.size) { j => x(j)(i) } } + + // 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) <> io.in(i) + // prefix sources + in(i).a.bits.source := io.in(i).a.bits.source | UInt(r.start) + in(i).c.bits.source := io.in(i).c.bits.source | UInt(r.start) + // defix sources + io.in(i).b.bits.source := trim(in(i).b.bits.source, r.size) + io.in(i).d.bits.source := trim(in(i).d.bits.source, r.size) + } + + // 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) <> out(i) + // prefix sinks + out(i).d.bits.sink := io.out(i).d.bits.sink | UInt(r.start) + // defix sinks + io.out(i).e.bits.sink := trim(out(i).e.bits.sink, r.size) + } + + // The crossbar cross-connection state; defined later + val grantedAIO = Wire(Vec(in .size, Vec(out.size, Bool()))) + val grantedBOI = Wire(Vec(out.size, Vec(in .size, Bool()))) + val grantedCIO = Wire(Vec(in .size, Vec(out.size, Bool()))) + val grantedDOI = Wire(Vec(out.size, Vec(in .size, Bool()))) + val grantedEIO = Wire(Vec(in .size, Vec(out.size, Bool()))) + + val grantedAOI = transpose(grantedAIO) + val grantedBIO = transpose(grantedBOI) + val grantedCOI = transpose(grantedCIO) + val grantedDIO = transpose(grantedDOI) + val grantedEOI = transpose(grantedEIO) + + // Mux1H passes a single-source through unmasked. That's bad for control. + def Mux1C(sel: Seq[Bool], ctl: Seq[Bool]) = (sel zip ctl).map{ case (a,b) => a && b }.reduce(_ || _) + + // Mux clients to managers + for (o <- 0 until out.size) { + out(o).a.valid := Mux1C(grantedAOI(o), in.map(_.a.valid)) + out(o).a.bits := Mux1H(grantedAOI(o), in.map(_.a.bits)) + out(o).b.ready := Mux1C(grantedBOI(o), in.map(_.b.ready)) + out(o).c.valid := Mux1C(grantedCOI(o), in.map(_.c.valid)) + out(o).c.bits := Mux1H(grantedCOI(o), in.map(_.c.bits)) + out(o).d.ready := Mux1C(grantedDOI(o), in.map(_.d.ready)) + out(o).e.valid := Mux1C(grantedEOI(o), in.map(_.e.valid)) + out(o).e.bits := Mux1H(grantedEOI(o), in.map(_.e.bits)) + } + + // Mux managers to clients + for (i <- 0 until in.size) { + in(i).a.ready := Mux1C(grantedAIO(i), out.map(_.a.ready)) + in(i).b.valid := Mux1C(grantedBIO(i), out.map(_.b.valid)) + in(i).b.bits := Mux1H(grantedBIO(i), out.map(_.b.bits)) + in(i).c.ready := Mux1C(grantedCIO(i), out.map(_.c.ready)) + in(i).d.valid := Mux1C(grantedDIO(i), out.map(_.d.valid)) + in(i).d.bits := Mux1H(grantedDIO(i), out.map(_.d.bits)) + in(i).e.ready := Mux1C(grantedEIO(i), out.map(_.e.ready)) + } + + val requestAIO = Vec(in.map { i => Vec(node.edgesOut.map { o => i.a.valid && o.manager.contains(o.address(i.a.bits)) }) }) + val requestBOI = Vec(out.map { o => Vec(inputIdRanges.map { i => o.b.valid && i .contains(o.b.bits.source) }) }) + val requestCIO = Vec(in.map { i => Vec(node.edgesOut.map { o => i.c.valid && o.manager.contains(o.address(i.c.bits)) }) }) + val requestDOI = Vec(out.map { o => Vec(inputIdRanges.map { i => o.d.valid && i .contains(o.d.bits.source) }) }) + val requestEIO = Vec(in.map { i => Vec(outputIdRanges.map { o => i.e.valid && o .contains(i.e.bits.sink) }) }) + + val beatsA = Vec((in zip node.edgesIn) map { case (i, e) => e.numBeats(i.a.bits) }) + val beatsB = Vec((out zip node.edgesOut) map { case (o, e) => e.numBeats(o.b.bits) }) + val beatsC = Vec((in zip node.edgesIn) map { case (i, e) => e.numBeats(i.c.bits) }) + val beatsD = Vec((out zip node.edgesOut) map { case (o, e) => e.numBeats(o.d.bits) }) + val beatsE = Vec((in zip node.edgesIn) map { case (i, e) => e.numBeats(i.e.bits) }) + + // Which pairs support support transfers + val maskIO = Vec.tabulate(in.size) { i => Vec.tabulate(out.size) { o => + Bool(node.edgesIn(i).client.anySupportProbe && node.edgesOut(o).manager.anySupportAcquire) + } } + val maskOI = transpose(maskIO) + + // Mask out BCE channel connections (to be optimized away) for transfer-incapable pairings + def mask(a: Seq[Seq[Bool]], b: Seq[Seq[Bool]]) = + Vec((a zip b) map { case (x, y) => Vec((x zip y) map { case (a, b) => a && b }) }) + + grantedAIO := arbitrate( requestAIO, beatsA, out.map(_.a.fire())) + grantedBOI := mask(arbitrate(mask(requestBOI, maskOI), beatsB, in .map(_.b.fire())), maskOI) + grantedCIO := mask(arbitrate(mask(requestCIO, maskIO), beatsC, out.map(_.c.fire())), maskIO) + grantedDOI := arbitrate( requestDOI, beatsD, in .map(_.d.fire())) + grantedEIO := mask(arbitrate(mask(requestEIO, maskIO), beatsE, out.map(_.e.fire())), maskIO) + + def arbitrate(request: Seq[Seq[Bool]], beats: Seq[UInt], progress: Seq[Bool]) = { + request foreach { row => require (row.size == progress.size) } // consistent # of resources + request foreach { resources => // only one resource is requested + val prefixOR = resources.scanLeft(Bool(false))(_ || _).init + assert (!(prefixOR zip resources).map{case (a, b) => a && b}.reduce(_ || _)) + } + transpose((transpose(request) zip progress).map { case (r,p) => arbitrate1(r, beats, p) }) + } + + def arbitrate1(requests: Vec[Bool], beats: Seq[UInt], progress: Bool) = { + require (requests.size == beats.size) // consistent # of requesters + + val beatsLeft = RegInit(UInt(0)) + val idle = beatsLeft === UInt(0) + + // Apply policy to select which requester wins + val winners = Vec(policy(requests, idle)) + + // Winners must be a subset of requests + assert ((winners zip requests).map { case (w,r) => !w || r } .reduce(_ && _)) + // There must be only one winner + val prefixOR = winners.scanLeft(Bool(false))(_ || _).init + assert ((prefixOR zip winners).map { case (p,w) => !p || !w }.reduce(_ && _)) + + // Supposing we take the winner as input, how many beats must be sent? + val maskedBeats = (winners zip beats).map { case (w,b) => Mux(w, b, UInt(0)) } + val initBeats = maskedBeats.reduceLeft(_ | _) // no winner => 0 beats + // What is the counter state before progress? + val todoBeats = Mux(idle, initBeats, beatsLeft) + // Apply progress and register the result + beatsLeft := todoBeats - progress.asUInt + assert (!progress || todoBeats =/= UInt(0)) // underflow should be impossible + + // The previous arbitration state of the resource + val state = RegInit(Vec.fill(requests.size)(Bool(false))) + // Only take a new value while idle + val muxState = Mux(idle, winners, state) + state := muxState + + muxState + } + } +} diff --git a/src/main/scala/uncore/tilelink2/package.scala b/src/main/scala/uncore/tilelink2/package.scala new file mode 100644 index 00000000..621fc288 --- /dev/null +++ b/src/main/scala/uncore/tilelink2/package.scala @@ -0,0 +1,10 @@ +package uncore + +import Chisel._ + +package object tilelink2 +{ + type TLBaseNode = BaseNode[TLClientPortParameters, TLManagerPortParameters, TLEdgeOut, TLEdgeIn, TLBundle] + def OH1ToUInt(x: UInt) = OHToUInt((x << 1 | UInt(1)) ^ x) + def UIntToOH1(x: UInt, width: Int) = ~(SInt(-1, width=width).asUInt << x)(width-1, 0) +}