tilelink2: move general-purpose code out of tilelink2 package
This commit is contained in:
@ -5,6 +5,7 @@ package uncore.devices
|
||||
import Chisel._
|
||||
import junctions._
|
||||
import junctions.NastiConstants._
|
||||
import regmapper._
|
||||
import uncore.tilelink2._
|
||||
import uncore.util._
|
||||
import util._
|
||||
|
@ -3,6 +3,7 @@ package uncore.devices
|
||||
import Chisel._
|
||||
import unittest.UnitTest
|
||||
import junctions._
|
||||
import diplomacy._
|
||||
import uncore.tilelink._
|
||||
import uncore.tilelink2._
|
||||
import uncore.util._
|
||||
|
@ -1,131 +0,0 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import scala.math.{max,min}
|
||||
|
||||
object AddressDecoder
|
||||
{
|
||||
type Port = Seq[AddressSet]
|
||||
type Ports = Seq[Port]
|
||||
type Partition = Ports
|
||||
type Partitions = Seq[Partition]
|
||||
|
||||
val addressOrder = Ordering.ordered[AddressSet]
|
||||
val portOrder = Ordering.Iterable(addressOrder)
|
||||
val partitionOrder = Ordering.Iterable(portOrder)
|
||||
|
||||
// Find the minimum subset of bits needed to disambiguate port addresses.
|
||||
// ie: inspecting only the bits in the output, you can look at an address
|
||||
// and decide to which port (outer Seq) the address belongs.
|
||||
def apply(ports: Ports): BigInt = if (ports.size <= 1) 0 else {
|
||||
// Every port must have at least one address!
|
||||
ports.foreach { p => require (!p.isEmpty) }
|
||||
// Verify the user did not give us an impossible problem
|
||||
ports.combinations(2).foreach { case Seq(x, y) =>
|
||||
x.foreach { a => y.foreach { b =>
|
||||
require (!a.overlaps(b)) // it must be possible to disambiguate ports!
|
||||
} }
|
||||
}
|
||||
|
||||
val maxBits = log2Ceil(ports.map(_.map(_.max).max).max + 1)
|
||||
val bits = (0 until maxBits).map(BigInt(1) << _).toSeq
|
||||
val selected = recurse(Seq(ports.map(_.sorted).sorted(portOrder)), bits)
|
||||
val output = selected.reduceLeft(_ | _)
|
||||
|
||||
// Modify the AddressSets to allow the new wider match functions
|
||||
val widePorts = ports.map { _.map { _.widen(~output) } }
|
||||
// Verify that it remains possible to disambiguate all ports
|
||||
widePorts.combinations(2).foreach { case Seq(x, y) =>
|
||||
x.foreach { a => y.foreach { b =>
|
||||
require (!a.overlaps(b))
|
||||
} }
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
// A simpler version that works for a Seq[Int]
|
||||
def apply(keys: Seq[Int]): Int = {
|
||||
val ports = keys.map(b => Seq(AddressSet(b, 0)))
|
||||
apply(ports).toInt
|
||||
}
|
||||
|
||||
// The algorithm has a set of partitions, discriminated by the selected bits.
|
||||
// Each partion has a set of ports, listing all addresses that lead to that port.
|
||||
// Seq[Seq[Seq[AddressSet]]]
|
||||
// ^^^^^^^^^^^^^^^ set of addresses that are routed out this port
|
||||
// ^^^ the list of ports
|
||||
// ^^^ cases already distinguished by the selected bits thus far
|
||||
//
|
||||
// Solving this problem is NP-hard, so we use a simple greedy heuristic:
|
||||
// pick the bit which minimizes the number of ports in each partition
|
||||
// as a secondary goal, reduce the number of AddressSets within a partition
|
||||
|
||||
def bitScore(partitions: Partitions): Seq[Int] = {
|
||||
val maxPortsPerPartition = partitions.map(_.size).max
|
||||
val sumPortsPerPartition = partitions.map(_.size).sum
|
||||
val maxSetsPerPartition = partitions.map(_.map(_.size).sum).max
|
||||
val sumSetsPerPartition = partitions.map(_.map(_.size).sum).sum
|
||||
Seq(maxPortsPerPartition, sumPortsPerPartition, maxSetsPerPartition, sumSetsPerPartition)
|
||||
}
|
||||
|
||||
def partitionPort(port: Port, bit: BigInt): (Port, Port) = {
|
||||
val addr_a = AddressSet(0, ~bit)
|
||||
val addr_b = AddressSet(bit, ~bit)
|
||||
// The addresses were sorted, so the filtered addresses are still sorted
|
||||
val subset_a = port.filter(_.overlaps(addr_a))
|
||||
val subset_b = port.filter(_.overlaps(addr_b))
|
||||
(subset_a, subset_b)
|
||||
}
|
||||
|
||||
def partitionPorts(ports: Ports, bit: BigInt): (Ports, Ports) = {
|
||||
val partitioned_ports = ports.map(p => partitionPort(p, bit))
|
||||
// because partitionPort dropped AddresSets, the ports might no longer be sorted
|
||||
val case_a_ports = partitioned_ports.map(_._1).filter(!_.isEmpty).sorted(portOrder)
|
||||
val case_b_ports = partitioned_ports.map(_._2).filter(!_.isEmpty).sorted(portOrder)
|
||||
(case_a_ports, case_b_ports)
|
||||
}
|
||||
|
||||
def partitionPartitions(partitions: Partitions, bit: BigInt): Partitions = {
|
||||
val partitioned_partitions = partitions.map(p => partitionPorts(p, bit))
|
||||
val case_a_partitions = partitioned_partitions.map(_._1).filter(!_.isEmpty)
|
||||
val case_b_partitions = partitioned_partitions.map(_._2).filter(!_.isEmpty)
|
||||
val new_partitions = (case_a_partitions ++ case_b_partitions).sorted(partitionOrder)
|
||||
// Prevent combinational memory explosion; if two partitions are equal, keep only one
|
||||
// Note: AddressSets in a port are sorted, and ports in a partition are sorted.
|
||||
// This makes it easy to structurally compare two partitions for equality
|
||||
val keep = (new_partitions.init zip new_partitions.tail) filter { case (a,b) => partitionOrder.compare(a,b) != 0 } map { _._2 }
|
||||
new_partitions.head +: keep
|
||||
}
|
||||
|
||||
// requirement: ports have sorted addresses and are sorted lexicographically
|
||||
val debug = false
|
||||
def recurse(partitions: Partitions, bits: Seq[BigInt]): Seq[BigInt] = {
|
||||
if (debug) {
|
||||
println("Partitioning:")
|
||||
partitions.foreach { partition =>
|
||||
println(" Partition:")
|
||||
partition.foreach { port =>
|
||||
print(" ")
|
||||
port.foreach { a => print(s" ${a}") }
|
||||
println("")
|
||||
}
|
||||
}
|
||||
}
|
||||
val candidates = bits.map { bit =>
|
||||
val result = partitionPartitions(partitions, bit)
|
||||
val score = bitScore(result)
|
||||
(score, bit, result)
|
||||
}
|
||||
val (bestScore, bestBit, bestPartitions) = candidates.min(Ordering.by[(Seq[Int], BigInt, Partitions), Iterable[Int]](_._1.toIterable))
|
||||
if (debug) println("=> Selected bit 0x%x".format(bestBit))
|
||||
if (bestScore(0) <= 1) {
|
||||
if (debug) println("---")
|
||||
Seq(bestBit)
|
||||
} else {
|
||||
bestBit +: recurse(bestPartitions, bits.filter(_ != bestBit))
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.util.IrrevocableIO
|
||||
import diplomacy._
|
||||
|
||||
object TLArbiter
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import diplomacy._
|
||||
import scala.math.{min,max}
|
||||
|
||||
// Ensures that all downstream RW managers support Atomic operationss.
|
||||
|
@ -4,6 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import diplomacy._
|
||||
import scala.math.max
|
||||
|
||||
// pipe is only used if a queue has depth = 1
|
||||
|
@ -4,22 +4,8 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.util.{Irrevocable, IrrevocableIO, ReadyValidIO}
|
||||
import util.{AsyncQueueSource, AsyncQueueSink}
|
||||
|
||||
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 =>
|
||||
throw new Exception("Unable to use GenericParameterizedBundle.cloneType on " +
|
||||
this.getClass + ", probably because " + this.getClass +
|
||||
"() takes more than one argument. Consider overriding " +
|
||||
"cloneType() on " + this.getClass, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
import diplomacy._
|
||||
import util.{AsyncQueueSource, AsyncQueueSink, GenericParameterizedBundle}
|
||||
|
||||
abstract class TLBundleBase(params: TLBundleParameters) extends GenericParameterizedBundle(params)
|
||||
|
||||
|
@ -4,6 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import diplomacy._
|
||||
import util._
|
||||
|
||||
class TLAsyncCrossingSource(sync: Int = 3) extends LazyModule
|
||||
|
@ -4,6 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import diplomacy._
|
||||
|
||||
class TLEdge(
|
||||
client: TLClientPortParameters,
|
||||
|
@ -3,6 +3,7 @@
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import regmapper._
|
||||
|
||||
case class ExampleParams(num: Int, address: BigInt)
|
||||
|
||||
|
@ -4,6 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import diplomacy._
|
||||
import scala.math.{min,max}
|
||||
|
||||
// minSize: minimum size of transfers supported by all outward managers
|
||||
|
@ -2,6 +2,7 @@
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import diplomacy._
|
||||
|
||||
class IDMapGenerator(numIds: Int) extends Module {
|
||||
val w = log2Up(numIds)
|
||||
|
@ -4,6 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import diplomacy._
|
||||
|
||||
// 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
|
||||
|
@ -3,9 +3,10 @@
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import diplomacy._
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.math.max
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
|
||||
// A potentially empty half-open range; [start, end)
|
||||
case class IntRange(start: Int, end: Int)
|
||||
|
@ -4,6 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import diplomacy._
|
||||
|
||||
class TLIsolation(f: (Bool, UInt) => UInt) extends LazyModule
|
||||
{
|
||||
|
@ -1,96 +0,0 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.{SourceInfo, SourceLine, UnlocatableSourceInfo}
|
||||
|
||||
abstract class LazyModule
|
||||
{
|
||||
protected[tilelink2] var bindings = List[() => Unit]()
|
||||
protected[tilelink2] var children = List[LazyModule]()
|
||||
protected[tilelink2] var nodes = List[BaseNode]()
|
||||
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)
|
||||
|
||||
def name = getClass.getName.split('.').last
|
||||
def line = sourceLine(info)
|
||||
|
||||
def module: LazyModuleImp
|
||||
|
||||
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 () }
|
||||
}
|
||||
|
||||
def omitGraphML = nodes.isEmpty && children.isEmpty
|
||||
lazy val graphML: String = parent.map(_.graphML).getOrElse {
|
||||
val buf = new StringBuilder
|
||||
buf ++= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
buf ++= "<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\" xmlns:y=\"http://www.yworks.com/xml/graphml\">\n"
|
||||
buf ++= " <key for=\"node\" id=\"n\" yfiles.type=\"nodegraphics\"/>\n"
|
||||
buf ++= " <key for=\"edge\" id=\"e\" yfiles.type=\"edgegraphics\"/>\n"
|
||||
buf ++= " <graph id=\"G\" edgedefault=\"directed\">\n"
|
||||
nodesGraphML(buf, " ")
|
||||
edgesGraphML(buf, " ")
|
||||
buf ++= " </graph>\n"
|
||||
buf ++= "</graphml>\n"
|
||||
buf.toString
|
||||
}
|
||||
|
||||
private val index = { LazyModule.index = LazyModule.index + 1; LazyModule.index }
|
||||
|
||||
private def nodesGraphML(buf: StringBuilder, pad: String) {
|
||||
buf ++= s"""${pad}<node id=\"${index}\">\n"""
|
||||
buf ++= s"""${pad} <data key=\"n\"><y:ShapeNode><y:NodeLabel modelName=\"sides\" modelPosition=\"w\" fontSize=\"10\" borderDistance=\"1.0\" rotationAngle=\"270.0\">${module.name}</y:NodeLabel></y:ShapeNode></data>\n"""
|
||||
buf ++= s"""${pad} <graph id=\"${index}::\" edgedefault=\"directed\">\n"""
|
||||
nodes.filter(!_.omitGraphML).foreach { n =>
|
||||
buf ++= s"""${pad} <node id=\"${index}::${n.index}\"/>\n"""
|
||||
}
|
||||
children.filter(!_.omitGraphML).foreach { _.nodesGraphML(buf, pad + " ") }
|
||||
buf ++= s"""${pad} </graph>\n"""
|
||||
buf ++= s"""${pad}</node>\n"""
|
||||
}
|
||||
private def edgesGraphML(buf: StringBuilder, pad: String) {
|
||||
nodes.filter(!_.omitGraphML) foreach { n => n.outputs.filter(!_.omitGraphML).foreach { o =>
|
||||
buf ++= pad
|
||||
buf ++= "<edge"
|
||||
buf ++= s""" source=\"${index}::${n.index}\""""
|
||||
buf ++= s""" target=\"${o.lazyModule.index}::${o.index}\"><data key=\"e\"><y:PolyLineEdge><y:Arrows source=\"none\" target=\"standard\"/><y:LineStyle color=\"${o.colour}\" type=\"line\" width=\"1.0\"/></y:PolyLineEdge></data></edge>\n"""
|
||||
} }
|
||||
children.filter(!_.omitGraphML).foreach { c => c.edgesGraphML(buf, pad) }
|
||||
}
|
||||
}
|
||||
|
||||
object LazyModule
|
||||
{
|
||||
protected[tilelink2] var stack = List[LazyModule]()
|
||||
private var index = 0
|
||||
|
||||
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, s"LazyModule() applied to ${bc.name} twice ${sourceLine(sourceInfo)}")
|
||||
require (stack.head eq bc, s"LazyModule() applied to ${bc.name} before ${stack.head.name} ${sourceLine(sourceInfo)}")
|
||||
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, s"${outer.name}.module was constructed before LazyModule() was run on ${LazyModule.stack.head.name}")
|
||||
|
||||
override def desiredName = outer.name
|
||||
outer.instantiate()
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import diplomacy._
|
||||
import cde.Parameters
|
||||
import uncore.tilelink._
|
||||
import uncore.constants._
|
||||
|
@ -4,6 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.{SourceInfo, SourceLine}
|
||||
import diplomacy._
|
||||
|
||||
class TLMonitor(gen: () => TLBundleSnoop, edge: () => TLEdge, sourceInfo: SourceInfo) extends LazyModule
|
||||
{
|
||||
|
@ -3,191 +3,124 @@
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import diplomacy._
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
// DI = Downwards flowing Parameters received on the inner side of the node
|
||||
// UI = Upwards flowing Parameters generated by the inner side of the node
|
||||
// EI = Edge Parameters describing a connection on the inner side of the node
|
||||
// BI = Bundle type used when connecting to the inner side of the node
|
||||
trait InwardNodeImp[DI, UI, EI, BI <: Data]
|
||||
object TLImp extends NodeImp[TLClientPortParameters, TLManagerPortParameters, TLEdgeOut, TLEdgeIn, TLBundle]
|
||||
{
|
||||
def edgeI(pd: DI, pu: UI): EI
|
||||
def bundleI(ei: Seq[EI]): Vec[BI]
|
||||
def mixI(pu: UI, node: InwardNode[DI, UI, BI]): UI = pu
|
||||
def colour: String
|
||||
def connect(bo: => BI, bi: => BI, e: => EI)(implicit sourceInfo: SourceInfo): (Option[LazyModule], () => Unit)
|
||||
}
|
||||
|
||||
// DO = Downwards flowing Parameters generated by the outner side of the node
|
||||
// UO = Upwards flowing Parameters received on the outner side of the node
|
||||
// EO = Edge Parameters describing a connection on the outer side of the node
|
||||
// BO = Bundle type used when connecting to the outer side of the node
|
||||
trait OutwardNodeImp[DO, UO, EO, BO <: Data]
|
||||
{
|
||||
def edgeO(pd: DO, pu: UO): EO
|
||||
def bundleO(eo: Seq[EO]): Vec[BO]
|
||||
def mixO(pd: DO, node: OutwardNode[DO, UO, BO]): DO = pd
|
||||
}
|
||||
|
||||
abstract class NodeImp[D, U, EO, EI, B <: Data]
|
||||
extends Object with InwardNodeImp[D, U, EI, B] with OutwardNodeImp[D, U, EO, B]
|
||||
|
||||
abstract class BaseNode
|
||||
{
|
||||
// You cannot create a Node outside a LazyModule!
|
||||
require (!LazyModule.stack.isEmpty)
|
||||
|
||||
val lazyModule = LazyModule.stack.head
|
||||
val index = lazyModule.nodes.size
|
||||
lazyModule.nodes = this :: lazyModule.nodes
|
||||
|
||||
def name = lazyModule.name + "." + getClass.getName.split('.').last
|
||||
def omitGraphML = outputs.isEmpty && inputs.isEmpty
|
||||
|
||||
protected[tilelink2] def outputs: Seq[BaseNode]
|
||||
protected[tilelink2] def inputs: Seq[BaseNode]
|
||||
protected[tilelink2] def colour: String
|
||||
}
|
||||
|
||||
trait InwardNode[DI, UI, BI <: Data] extends BaseNode
|
||||
{
|
||||
protected[tilelink2] val numPI: Range.Inclusive
|
||||
require (!numPI.isEmpty, s"No number of inputs would be acceptable to ${name}${lazyModule.line}")
|
||||
require (numPI.start >= 0, s"${name} accepts a negative number of inputs${lazyModule.line}")
|
||||
|
||||
private val accPI = ListBuffer[(Int, OutwardNode[DI, UI, BI])]()
|
||||
private var iRealized = false
|
||||
|
||||
protected[tilelink2] def iPushed = accPI.size
|
||||
protected[tilelink2] def iPush(index: Int, node: OutwardNode[DI, UI, BI])(implicit sourceInfo: SourceInfo) {
|
||||
val info = sourceLine(sourceInfo, " at ", "")
|
||||
val noIs = numPI.size == 1 && numPI.contains(0)
|
||||
require (!noIs, s"${name}${lazyModule.line} was incorrectly connected as a sink" + info)
|
||||
require (!iRealized, s"${name}${lazyModule.line} was incorrectly connected as a sink after it's .module was used" + info)
|
||||
accPI += ((index, node))
|
||||
def edgeO(pd: TLClientPortParameters, pu: TLManagerPortParameters): TLEdgeOut = new TLEdgeOut(pd, pu)
|
||||
def edgeI(pd: TLClientPortParameters, pu: TLManagerPortParameters): TLEdgeIn = new TLEdgeIn(pd, pu)
|
||||
def bundleO(eo: 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
|
||||
}
|
||||
|
||||
private def reqI() = require(numPI.contains(accPI.size), s"${name} has ${accPI.size} inputs, expected ${numPI}${lazyModule.line}")
|
||||
protected[tilelink2] lazy val iPorts = { iRealized = true; reqI(); accPI.result() }
|
||||
|
||||
protected[tilelink2] val iParams: Seq[UI]
|
||||
protected[tilelink2] def iConnect: Vec[BI]
|
||||
}
|
||||
|
||||
trait OutwardNode[DO, UO, BO <: Data] extends BaseNode
|
||||
{
|
||||
protected[tilelink2] val numPO: Range.Inclusive
|
||||
require (!numPO.isEmpty, s"No number of outputs would be acceptable to ${name}${lazyModule.line}")
|
||||
require (numPO.start >= 0, s"${name} accepts a negative number of outputs${lazyModule.line}")
|
||||
|
||||
private val accPO = ListBuffer[(Int, InwardNode [DO, UO, BO])]()
|
||||
private var oRealized = false
|
||||
|
||||
protected[tilelink2] def oPushed = accPO.size
|
||||
protected[tilelink2] def oPush(index: Int, node: InwardNode [DO, UO, BO])(implicit sourceInfo: SourceInfo) {
|
||||
val info = sourceLine(sourceInfo, " at ", "")
|
||||
val noOs = numPO.size == 1 && numPO.contains(0)
|
||||
require (!noOs, s"${name}${lazyModule.line} was incorrectly connected as a source" + info)
|
||||
require (!oRealized, s"${name}${lazyModule.line} was incorrectly connected as a source after it's .module was used" + info)
|
||||
accPO += ((index, node))
|
||||
def colour = "#000000" // black
|
||||
def connect(bo: => TLBundle, bi: => TLBundle, ei: => TLEdgeIn)(implicit sourceInfo: SourceInfo): (Option[LazyModule], () => Unit) = {
|
||||
val monitor = LazyModule(new TLMonitor(() => new TLBundleSnoop(bo.params), () => ei, sourceInfo))
|
||||
(Some(monitor), () => {
|
||||
bi <> bo
|
||||
monitor.module.io.in := TLBundleSnoop(bo)
|
||||
})
|
||||
}
|
||||
|
||||
private def reqO() = require(numPO.contains(accPO.size), s"${name} has ${accPO.size} outputs, expected ${numPO}${lazyModule.line}")
|
||||
protected[tilelink2] lazy val oPorts = { oRealized = true; reqO(); accPO.result() }
|
||||
|
||||
protected[tilelink2] val oParams: Seq[DO]
|
||||
protected[tilelink2] def oConnect: Vec[BO]
|
||||
override def mixO(pd: TLClientPortParameters, node: OutwardNode[TLClientPortParameters, TLManagerPortParameters, TLBundle]): TLClientPortParameters =
|
||||
pd.copy(clients = pd.clients.map { c => c.copy (nodePath = node +: c.nodePath) })
|
||||
override def mixI(pu: TLManagerPortParameters, node: InwardNode[TLClientPortParameters, TLManagerPortParameters, TLBundle]): TLManagerPortParameters =
|
||||
pu.copy(managers = pu.managers.map { m => m.copy (nodePath = node +: m.nodePath) })
|
||||
}
|
||||
|
||||
class MixedNode[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data](
|
||||
inner: InwardNodeImp [DI, UI, EI, BI],
|
||||
outer: OutwardNodeImp[DO, UO, EO, BO])(
|
||||
private val dFn: (Int, Seq[DI]) => Seq[DO],
|
||||
private val uFn: (Int, Seq[UO]) => Seq[UI],
|
||||
protected[tilelink2] val numPO: Range.Inclusive,
|
||||
protected[tilelink2] val numPI: Range.Inclusive)
|
||||
extends BaseNode with InwardNode[DI, UI, BI] with OutwardNode[DO, UO, BO]
|
||||
{
|
||||
// meta-data for printing the node graph
|
||||
protected[tilelink2] def colour = inner.colour
|
||||
protected[tilelink2] def outputs = oPorts.map(_._2)
|
||||
protected[tilelink2] def inputs = iPorts.map(_._2)
|
||||
case class TLIdentityNode() extends IdentityNode(TLImp)
|
||||
case class TLOutputNode() extends OutputNode(TLImp)
|
||||
case class TLInputNode() extends InputNode(TLImp)
|
||||
|
||||
private def reqE(o: Int, i: Int) = require(i == o, s"${name} has ${i} inputs and ${o} outputs; they must match${lazyModule.line}")
|
||||
protected[tilelink2] lazy val oParams: Seq[DO] = {
|
||||
val o = dFn(oPorts.size, iPorts.map { case (i, n) => n.oParams(i) })
|
||||
reqE(oPorts.size, o.size)
|
||||
o.map(outer.mixO(_, this))
|
||||
}
|
||||
protected[tilelink2] lazy val iParams: Seq[UI] = {
|
||||
val i = uFn(iPorts.size, oPorts.map { case (o, n) => n.iParams(o) })
|
||||
reqE(i.size, iPorts.size)
|
||||
i.map(inner.mixI(_, this))
|
||||
case class TLClientNode(portParams: TLClientPortParameters, numPorts: Range.Inclusive = 1 to 1)
|
||||
extends SourceNode(TLImp)(portParams, numPorts)
|
||||
case class TLManagerNode(portParams: TLManagerPortParameters, numPorts: Range.Inclusive = 1 to 1)
|
||||
extends SinkNode(TLImp)(portParams, numPorts)
|
||||
|
||||
object TLClientNode
|
||||
{
|
||||
def apply(params: TLClientParameters) =
|
||||
new TLClientNode(TLClientPortParameters(Seq(params)), 1 to 1)
|
||||
}
|
||||
|
||||
object TLManagerNode
|
||||
{
|
||||
def apply(beatBytes: Int, params: TLManagerParameters) =
|
||||
new TLManagerNode(TLManagerPortParameters(Seq(params), beatBytes, 0), 1 to 1)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import unittest._
|
||||
|
||||
class TLInputNodeTest extends UnitTest(500000) {
|
||||
class Acceptor extends LazyModule {
|
||||
val node = TLInputNode()
|
||||
val tlram = LazyModule(new TLRAM(AddressSet(0x54321000, 0xfff)))
|
||||
tlram.node := node
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy val edgesOut = (oPorts zip oParams).map { case ((i, n), o) => outer.edgeO(o, n.iParams(i)) }
|
||||
lazy val edgesIn = (iPorts zip iParams).map { case ((o, n), i) => inner.edgeI(n.oParams(o), i) }
|
||||
val fuzzer = LazyModule(new TLFuzzer(5000))
|
||||
LazyModule(new Acceptor).node := TLFragmenter(4, 64)(fuzzer.node)
|
||||
|
||||
lazy val bundleOut = outer.bundleO(edgesOut)
|
||||
lazy val bundleIn = inner.bundleI(edgesIn)
|
||||
io.finished := Module(fuzzer.module).io.finished
|
||||
}
|
||||
|
||||
def oConnect = bundleOut
|
||||
def iConnect = bundleIn
|
||||
|
||||
// connects the outward part of a node with the inward part of this node
|
||||
def := (y: OutwardNode[DI, UI, BI])(implicit sourceInfo: SourceInfo): Option[LazyModule] = {
|
||||
val x = this // x := y
|
||||
val info = sourceLine(sourceInfo, " at ", "")
|
||||
require (!LazyModule.stack.isEmpty, s"${y.name} cannot be connected to ${x.name} outside of LazyModule scope" + info)
|
||||
val i = x.iPushed
|
||||
val o = y.oPushed
|
||||
y.oPush(i, x)
|
||||
x.iPush(o, y)
|
||||
val (out, binding) = inner.connect(y.oConnect(o), x.iConnect(i), x.edgesIn(i))
|
||||
LazyModule.stack.head.bindings = binding :: LazyModule.stack.head.bindings
|
||||
out
|
||||
object TLAsyncImp extends NodeImp[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncEdgeParameters, TLAsyncEdgeParameters, TLAsyncBundle]
|
||||
{
|
||||
def edgeO(pd: TLAsyncClientPortParameters, pu: TLAsyncManagerPortParameters): TLAsyncEdgeParameters = TLAsyncEdgeParameters(pd, pu)
|
||||
def edgeI(pd: TLAsyncClientPortParameters, pu: TLAsyncManagerPortParameters): TLAsyncEdgeParameters = TLAsyncEdgeParameters(pd, pu)
|
||||
def bundleO(eo: Seq[TLAsyncEdgeParameters]): Vec[TLAsyncBundle] = {
|
||||
require (eo.size == 1)
|
||||
Vec(eo.size, new TLAsyncBundle(eo(0).bundle))
|
||||
}
|
||||
def bundleI(ei: Seq[TLAsyncEdgeParameters]): Vec[TLAsyncBundle] = {
|
||||
require (ei.size == 1)
|
||||
Vec(ei.size, new TLAsyncBundle(ei(0).bundle)).flip
|
||||
}
|
||||
|
||||
def colour = "#ff0000" // red
|
||||
def connect(bo: => TLAsyncBundle, bi: => TLAsyncBundle, ei: => TLAsyncEdgeParameters)(implicit sourceInfo: SourceInfo): (Option[LazyModule], () => Unit) = {
|
||||
(None, () => { bi <> bo })
|
||||
}
|
||||
|
||||
override def mixO(pd: TLAsyncClientPortParameters, node: OutwardNode[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncBundle]): TLAsyncClientPortParameters =
|
||||
pd.copy(base = pd.base.copy(clients = pd.base.clients.map { c => c.copy (nodePath = node +: c.nodePath) }))
|
||||
override def mixI(pu: TLAsyncManagerPortParameters, node: InwardNode[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncBundle]): TLAsyncManagerPortParameters =
|
||||
pu.copy(base = pu.base.copy(managers = pu.base.managers.map { m => m.copy (nodePath = node +: m.nodePath) }))
|
||||
}
|
||||
|
||||
class SimpleNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])(
|
||||
oFn: (Int, Seq[D]) => Seq[D],
|
||||
iFn: (Int, Seq[U]) => Seq[U],
|
||||
numPO: Range.Inclusive,
|
||||
numPI: Range.Inclusive)
|
||||
extends MixedNode[D, U, EI, B, D, U, EO, B](imp, imp)(oFn, iFn, numPO, numPI)
|
||||
case class TLAsyncIdentityNode() extends IdentityNode(TLAsyncImp)
|
||||
case class TLAsyncOutputNode() extends OutputNode(TLAsyncImp)
|
||||
case class TLAsyncInputNode() extends InputNode(TLAsyncImp)
|
||||
|
||||
class IdentityNode[PO, PI, EO, EI, B <: Data](imp: NodeImp[PO, PI, EO, EI, B])
|
||||
extends SimpleNode(imp)({case (_, s) => s}, {case (_, s) => s}, 0 to 999, 0 to 999)
|
||||
case class TLAsyncSourceNode() extends MixedNode(TLImp, TLAsyncImp)(
|
||||
dFn = { case (1, s) => s.map(TLAsyncClientPortParameters(_)) },
|
||||
uFn = { case (1, s) => s.map(_.base) },
|
||||
numPO = 1 to 1,
|
||||
numPI = 1 to 1)
|
||||
|
||||
class OutputNode[PO, PI, EO, EI, B <: Data](imp: NodeImp[PO, PI, EO, EI, B]) extends IdentityNode(imp)
|
||||
{
|
||||
override def oConnect = bundleOut
|
||||
override def iConnect = bundleOut
|
||||
}
|
||||
|
||||
class InputNode[PO, PI, EO, EI, B <: Data](imp: NodeImp[PO, PI, EO, EI, B]) extends IdentityNode(imp)
|
||||
{
|
||||
override def oConnect = bundleIn
|
||||
override def iConnect = bundleIn
|
||||
}
|
||||
|
||||
class SourceNode[PO, PI, EO, EI, B <: Data](imp: NodeImp[PO, PI, EO, EI, B])(po: PO, num: Range.Inclusive = 1 to 1)
|
||||
extends SimpleNode(imp)({case (n, Seq()) => Seq.fill(n)(po)}, {case (0, _) => Seq()}, num, 0 to 0)
|
||||
{
|
||||
require (num.end >= 1, s"${name} is a source which does not accept outputs${lazyModule.line}")
|
||||
}
|
||||
|
||||
class SinkNode[PO, PI, EO, EI, B <: Data](imp: NodeImp[PO, PI, EO, EI, B])(pi: PI, num: Range.Inclusive = 1 to 1)
|
||||
extends SimpleNode(imp)({case (0, _) => Seq()}, {case (n, Seq()) => Seq.fill(n)(pi)}, 0 to 0, num)
|
||||
{
|
||||
require (num.end >= 1, s"${name} is a sink which does not accept inputs${lazyModule.line}")
|
||||
}
|
||||
|
||||
class InteriorNode[PO, PI, EO, EI, B <: Data](imp: NodeImp[PO, PI, EO, EI, B])
|
||||
(oFn: Seq[PO] => PO, iFn: Seq[PI] => PI, numPO: Range.Inclusive, numPI: Range.Inclusive)
|
||||
extends SimpleNode(imp)({case (n,s) => Seq.fill(n)(oFn(s))}, {case (n,s) => Seq.fill(n)(iFn(s))}, numPO, numPI)
|
||||
{
|
||||
require (numPO.end >= 1, s"${name} is an adapter which does not accept outputs${lazyModule.line}")
|
||||
require (numPI.end >= 1, s"${name} is an adapter which does not accept inputs${lazyModule.line}")
|
||||
}
|
||||
case class TLAsyncSinkNode(depth: Int) extends MixedNode(TLAsyncImp, TLImp)(
|
||||
dFn = { case (1, s) => s.map(_.base) },
|
||||
uFn = { case (1, s) => s.map(TLAsyncManagerPortParameters(depth, _)) },
|
||||
numPO = 1 to 1,
|
||||
numPI = 1 to 1)
|
||||
|
@ -3,147 +3,9 @@
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import diplomacy._
|
||||
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 || max < x.min) 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) extends Ordered[AddressSet]
|
||||
{
|
||||
// Forbid misaligned base address (and empty sets)
|
||||
require ((base & mask) == 0)
|
||||
require (base >= 0) // TL2 address widths are not fixed => negative is ambiguous
|
||||
// We do allow negative mask (=> ignore all high bits)
|
||||
|
||||
def contains(x: BigInt) = ((x ^ base) & ~mask) == 0
|
||||
def contains(x: UInt) = ((x ^ UInt(base)).zext() & SInt(~mask)) === SInt(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
|
||||
|
||||
// The number of bytes to which the manager must be aligned
|
||||
def alignment = ((mask + 1) & ~mask)
|
||||
// Is this a contiguous memory range
|
||||
def contiguous = alignment == mask+1
|
||||
|
||||
def finite = mask >= 0
|
||||
def max = { require (finite); base | mask }
|
||||
|
||||
// Widen the match function to ignore all bits in imask
|
||||
def widen(imask: BigInt) = AddressSet(base & ~imask, mask | imask)
|
||||
|
||||
// AddressSets have one natural Ordering (the containment order, if contiguous)
|
||||
def compare(x: AddressSet) = {
|
||||
val primary = (this.base - x.base).signum // smallest address first
|
||||
val secondary = (x.mask - this.mask).signum // largest mask first
|
||||
if (primary != 0) primary else secondary
|
||||
}
|
||||
|
||||
// We always want to see things in hex
|
||||
override def toString() = {
|
||||
if (mask >= 0) {
|
||||
"AddressSet(0x%x, 0x%x)".format(base, mask)
|
||||
} else {
|
||||
"AddressSet(0x%x, ~0x%x)".format(base, ~mask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object AddressSet
|
||||
{
|
||||
def misaligned(base: BigInt, size: BigInt): Seq[AddressSet] = {
|
||||
val largestPow2 = BigInt(1) << log2Floor(size)
|
||||
val mostZeros = (base + size - 1) & ~(largestPow2 - 1)
|
||||
def splitLo(low: BigInt, high: BigInt, tail: Seq[AddressSet]): Seq[AddressSet] = {
|
||||
if (low == high) tail else {
|
||||
val toggleBits = low ^ high
|
||||
val misalignment = toggleBits & (-toggleBits)
|
||||
splitLo(low+misalignment, high, AddressSet(low, misalignment-1) +: tail)
|
||||
}
|
||||
}
|
||||
def splitHi(low: BigInt, high: BigInt, tail: Seq[AddressSet]): Seq[AddressSet] = {
|
||||
if (low == high) tail else {
|
||||
val toggleBits = low ^ high
|
||||
val misalignment = toggleBits & (-toggleBits)
|
||||
splitHi(low, high-misalignment, AddressSet(high-misalignment, misalignment-1) +: tail)
|
||||
}
|
||||
}
|
||||
splitLo(base, mostZeros, splitHi(mostZeros, base+size, Seq())).sorted
|
||||
}
|
||||
}
|
||||
|
||||
case class TLManagerParameters(
|
||||
address: Seq[AddressSet],
|
||||
sinkId: IdRange = IdRange(0, 1),
|
||||
|
@ -3,6 +3,7 @@
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import diplomacy._
|
||||
|
||||
// We detect concurrent puts that put memory into an undefined state.
|
||||
// put0, put0Ack, put1, put1Ack => ok: defined
|
||||
|
@ -1,115 +0,0 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.util.{ReadyValidIO}
|
||||
import util.{SimpleRegIO}
|
||||
|
||||
case class RegReadFn private(combinational: Boolean, fn: (Bool, Bool) => (Bool, Bool, UInt))
|
||||
object RegReadFn
|
||||
{
|
||||
// (ivalid: Bool, oready: Bool) => (iready: Bool, ovalid: Bool, data: UInt)
|
||||
// iready may combinationally depend on oready
|
||||
// all other combinational dependencies forbidden (e.g. ovalid <= ivalid)
|
||||
// effects must become visible on the cycle after ovalid && oready
|
||||
// data is only inspected when ovalid && oready
|
||||
implicit def apply(x: (Bool, Bool) => (Bool, Bool, UInt)) =
|
||||
new RegReadFn(false, x)
|
||||
implicit def apply(x: RegisterReadIO[UInt]): RegReadFn =
|
||||
RegReadFn((ivalid, oready) => {
|
||||
x.request.valid := ivalid
|
||||
x.response.ready := oready
|
||||
(x.request.ready, x.response.valid, x.response.bits)
|
||||
})
|
||||
// (ready: Bool) => (valid: Bool, data: UInt)
|
||||
// valid must not combinationally depend on ready
|
||||
// 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 ReadyValidIO (only safe if there is a consistent source of data)
|
||||
implicit def apply(x: ReadyValidIO[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)
|
||||
// effects must become visible on the cycle after ovalid && oready
|
||||
// data should only be used for an effect when ivalid && iready
|
||||
implicit def apply(x: (Bool, Bool, UInt) => (Bool, Bool)) =
|
||||
new RegWriteFn(false, x)
|
||||
implicit def apply(x: RegisterWriteIO[UInt]): RegWriteFn =
|
||||
RegWriteFn((ivalid, oready, data) => {
|
||||
x.request.valid := ivalid
|
||||
x.request.bits := data
|
||||
x.response.ready := oready
|
||||
(x.request.ready, x.response.valid)
|
||||
})
|
||||
// (valid: Bool, data: UInt) => (ready: Bool)
|
||||
// ready may combinationally depend on data (but not valid)
|
||||
// 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)
|
||||
// NOTE: this is not an IrrevocableIO (even on TL2) because other fields could cause a lowered valid
|
||||
implicit def apply(x: DecoupledIO[UInt]): RegWriteFn = RegWriteFn((valid, data) => { x.valid := valid; x.bits := data; x.ready })
|
||||
// updates a register (or adds a mux to a wire)
|
||||
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, name: String, description: String)
|
||||
{
|
||||
require (width > 0)
|
||||
def pipelined = !read.combinational || !write.combinational
|
||||
}
|
||||
|
||||
object RegField
|
||||
{
|
||||
// Byte address => sequence of bitfields, lowest index => lowest address
|
||||
type Map = (Int, Seq[RegField])
|
||||
|
||||
def apply(n: Int) : RegField = apply(n, (), (), "", "")
|
||||
def apply(n: Int, r: RegReadFn, w: RegWriteFn) : RegField = apply(n, r, w, "", "")
|
||||
def apply(n: Int, rw: UInt) : RegField = apply(n, rw, rw, "", "")
|
||||
def apply(n: Int, rw: UInt, name: String, description: String) : RegField = apply(n, rw, rw, name, description)
|
||||
def r(n: Int, r: RegReadFn, name: String = "", description: String = "") : RegField = apply(n, r, (), name, description)
|
||||
def w(n: Int, w: RegWriteFn, name: String = "", description: String = "") : RegField = apply(n, (), w, name, description)
|
||||
|
||||
// This RegField allows 'set' to set bits in 'reg'.
|
||||
// and to clear bits when the bus writes bits of value 1.
|
||||
// Setting takes priority over clearing.
|
||||
def w1ToClear(n: Int, reg: UInt, set: UInt): RegField =
|
||||
RegField(n, reg, RegWriteFn((valid, data) => { reg := ~(~reg | Mux(valid, data, UInt(0))) | set; Bool(true) }))
|
||||
|
||||
// This RegField wraps an explicit register
|
||||
// (e.g. Black-Boxed Register) to create a R/W register.
|
||||
def rwReg(n: Int, bb: SimpleRegIO) : RegField =
|
||||
RegField(n, bb.q, RegWriteFn((valid, data) => {
|
||||
bb.en := valid
|
||||
bb.d := data
|
||||
Bool(true)
|
||||
}))
|
||||
}
|
||||
|
||||
trait HasRegMap
|
||||
{
|
||||
def regmap(mapping: RegField.Map*): Unit
|
||||
val interrupts: Vec[Bool]
|
||||
}
|
||||
|
||||
// See GPIO.scala for an example of how to use regmap
|
@ -1,194 +0,0 @@
|
||||
// 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: Int, undefZero: Boolean, in: DecoupledIO[RegMapperInput], mapping: RegField.Map*) = {
|
||||
val bytemap = mapping.toList
|
||||
// Don't be an asshole...
|
||||
bytemap.foreach { byte => require (byte._1 >= 0) }
|
||||
|
||||
// Transform all fields into bit offsets Seq[(bit, field)]
|
||||
val bitmap = bytemap.map { case (byte, fields) =>
|
||||
val bits = fields.scanLeft(byte * 8)(_ + _.width).init
|
||||
bits zip fields
|
||||
}.flatten.sortBy(_._1)
|
||||
|
||||
// Detect overlaps
|
||||
(bitmap.init zip bitmap.tail) foreach { case ((lbit, lfield), (rbit, rfield)) =>
|
||||
require (lbit + lfield.width <= rbit, s"Register map overlaps at bit ${rbit}.")
|
||||
}
|
||||
|
||||
// Group those fields into bus words Map[word, List[(bit, field)]]
|
||||
val wordmap = bitmap.groupBy(_._1 / (8*bytes))
|
||||
|
||||
// Make sure registers fit
|
||||
val inParams = in.bits.params
|
||||
val inBits = inParams.indexBits
|
||||
assert (wordmap.keySet.max < (1 << inBits), "Register map does not fit in device")
|
||||
|
||||
val out = Wire(Decoupled(new RegMapperOutput(inParams)))
|
||||
val front = Wire(Decoupled(new RegMapperInput(inParams)))
|
||||
front.bits := in.bits
|
||||
|
||||
// Must this device pipeline the control channel?
|
||||
val pipelined = wordmap.values.map(_.map(_._2.pipelined)).flatten.reduce(_ || _)
|
||||
val depth = concurrency
|
||||
require (depth >= 0)
|
||||
require (!pipelined || depth > 0, "Register-based device with request/response handshaking needs concurrency > 0")
|
||||
val back = if (depth > 0) Queue(front, depth, pipe = depth == 1) else front
|
||||
|
||||
// Convert to and from Bits
|
||||
def toBits(x: Int, tail: List[Boolean] = List.empty): List[Boolean] =
|
||||
if (x == 0) tail.reverse else toBits(x >> 1, ((x & 1) == 1) :: tail)
|
||||
def ofBits(bits: List[Boolean]) = bits.foldRight(0){ case (x,y) => (if (x) 1 else 0) | y << 1 }
|
||||
|
||||
// Find the minimal mask that can decide the register map
|
||||
val mask = AddressDecoder(wordmap.keySet.toList)
|
||||
val maskMatch = ~UInt(mask, width = inBits)
|
||||
val maskFilter = toBits(mask)
|
||||
val maskBits = maskFilter.filter(x => x).size
|
||||
|
||||
// Calculate size and indexes into the register map
|
||||
val regSize = 1 << maskBits
|
||||
def regIndexI(x: Int) = ofBits((maskFilter zip toBits(x)).filter(_._1).map(_._2))
|
||||
def regIndexU(x: UInt) = if (maskBits == 0) UInt(0) else
|
||||
Cat((maskFilter zip x.toBools).filter(_._1).map(_._2).reverse)
|
||||
|
||||
// Protection flag for undefined registers
|
||||
val iRightReg = Array.fill(regSize) { Bool(true) }
|
||||
val oRightReg = Array.fill(regSize) { Bool(true) }
|
||||
|
||||
// Transform the wordmap into minimal decoded indexes, Seq[(index, bit, field)]
|
||||
val flat = wordmap.toList.map { case (word, fields) =>
|
||||
val index = regIndexI(word)
|
||||
val uint = UInt(word, width = inBits)
|
||||
if (undefZero) {
|
||||
iRightReg(index) = ((front.bits.index ^ uint) & maskMatch) === UInt(0)
|
||||
oRightReg(index) = ((back .bits.index ^ uint) & maskMatch) === UInt(0)
|
||||
}
|
||||
// Confirm that no field spans a word boundary
|
||||
fields foreach { case (bit, field) =>
|
||||
val off = bit - 8*bytes*word
|
||||
// println(s"Reg ${word}: [${off}, ${off+field.width})")
|
||||
require (off + field.width <= bytes * 8, s"Field at word ${word}*(${bytes}B) has bits [${off}, ${off+field.width}), which exceeds word limit.")
|
||||
}
|
||||
// println("mapping 0x%x -> 0x%x for 0x%x/%d".format(word, index, mask, maskBits))
|
||||
fields.map { case (bit, field) => (index, bit-8*bytes*word, field) }
|
||||
}.flatten
|
||||
|
||||
// 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(regSize) { i => Seq(Bool(true)) }
|
||||
val wifire = Array.tabulate(regSize) { i => Seq(Bool(true)) }
|
||||
val rofire = Array.tabulate(regSize) { i => Seq(Bool(true)) }
|
||||
val wofire = Array.tabulate(regSize) { i => Seq(Bool(true)) }
|
||||
|
||||
// The output values for each register
|
||||
val dataOut = Array.tabulate(regSize) { _ => 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.zipWithIndex.map { case (seq, i) => !iRightReg(i) || seq.reduce(_ && _)})
|
||||
val wifireMux = Vec(wifire.zipWithIndex.map { case (seq, i) => !iRightReg(i) || seq.reduce(_ && _)})
|
||||
val rofireMux = Vec(rofire.zipWithIndex.map { case (seq, i) => !oRightReg(i) || seq.reduce(_ && _)})
|
||||
val wofireMux = Vec(wofire.zipWithIndex.map { case (seq, i) => !oRightReg(i) || seq.reduce(_ && _)})
|
||||
val iindex = regIndexU(front.bits.index)
|
||||
val oindex = regIndexU(back .bits.index)
|
||||
val iready = Mux(front.bits.read, rifireMux(iindex), wifireMux(iindex))
|
||||
val oready = Mux(back .bits.read, rofireMux(oindex), wofireMux(oindex))
|
||||
|
||||
// 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(iindex) & Cat(iRightReg.reverse)
|
||||
val backSel = UIntToOH(oindex) & Cat(oRightReg.reverse)
|
||||
|
||||
// Include the per-register one-hot selected criteria
|
||||
for (reg <- 0 until regSize) {
|
||||
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 := Mux(Vec(oRightReg)(oindex), Vec(dataOut)(oindex), UInt(0))
|
||||
out.bits.extra := back.bits.extra
|
||||
|
||||
out
|
||||
}
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.util.{Irrevocable}
|
||||
import util.{AsyncResetRegVec, AsyncQueue, AsyncScope}
|
||||
|
||||
// A very simple flow control state machine, run in the specified clock domain
|
||||
class BusyRegisterCrossing(clock: Clock, reset: Bool)
|
||||
extends Module(_clock = clock, _reset = reset) {
|
||||
val io = new Bundle {
|
||||
val progress = Bool(INPUT)
|
||||
val request_valid = Bool(INPUT)
|
||||
val response_ready = Bool(INPUT)
|
||||
val busy = Bool(OUTPUT)
|
||||
}
|
||||
|
||||
val busy = RegInit(Bool(false))
|
||||
when (io.progress) {
|
||||
busy := Mux(busy, !io.response_ready, io.request_valid)
|
||||
}
|
||||
io.busy := busy
|
||||
}
|
||||
|
||||
// RegField should support connecting to one of these
|
||||
class RegisterWriteIO[T <: Data](gen: T) extends Bundle {
|
||||
val request = Decoupled(gen).flip()
|
||||
val response = Irrevocable(Bool()) // ignore .bits
|
||||
}
|
||||
|
||||
// To turn on/off a domain:
|
||||
// 1. lower allow on the other side
|
||||
// 2. wait for inflight traffic to resolve
|
||||
// 3. assert reset in the domain
|
||||
// 4. turn off the domain
|
||||
// 5. turn on the domain
|
||||
// 6. deassert reset in the domain
|
||||
// 7. raise allow on the other side
|
||||
|
||||
class RegisterWriteCrossingIO[T <: Data](gen: T) extends Bundle {
|
||||
// Master clock domain
|
||||
val master_clock = Clock(INPUT)
|
||||
val master_reset = Bool(INPUT)
|
||||
val master_allow = Bool(INPUT) // actually wait for the slave
|
||||
val master_port = new RegisterWriteIO(gen)
|
||||
// Slave clock domain
|
||||
val slave_clock = Clock(INPUT)
|
||||
val slave_reset = Bool(INPUT)
|
||||
val slave_allow = Bool(INPUT) // honour requests from the master
|
||||
val slave_register = gen.asOutput
|
||||
val slave_valid = Bool(OUTPUT) // is high on 1st cycle slave_register has a new value
|
||||
}
|
||||
|
||||
class RegisterWriteCrossing[T <: Data](gen: T, sync: Int = 3) extends Module {
|
||||
val io = new RegisterWriteCrossingIO(gen)
|
||||
// The crossing must only allow one item inflight at a time
|
||||
val crossing = Module(new AsyncQueue(gen, 1, sync))
|
||||
|
||||
// We can just randomly reset one-side of a single entry AsyncQueue.
|
||||
// If the enq side is reset, at worst deq.bits is reassigned from mem(0), which stays fixed.
|
||||
// If the deq side is reset, at worst the master rewrites mem(0) once, deq.bits stays fixed.
|
||||
crossing.io.enq_clock := io.master_clock
|
||||
crossing.io.enq_reset := io.master_reset
|
||||
crossing.io.deq_clock := io.slave_clock
|
||||
crossing.io.deq_reset := io.slave_reset
|
||||
|
||||
crossing.io.enq.bits := io.master_port.request.bits
|
||||
io.slave_register := crossing.io.deq.bits
|
||||
io.slave_valid := crossing.io.deq.valid
|
||||
|
||||
// If the slave is not operational, just drop the write.
|
||||
val progress = crossing.io.enq.ready || !io.master_allow
|
||||
|
||||
val reg = Module(new BusyRegisterCrossing(io.master_clock, io.master_reset))
|
||||
reg.io.progress := progress
|
||||
reg.io.request_valid := io.master_port.request.valid
|
||||
reg.io.response_ready := io.master_port.response.ready
|
||||
|
||||
crossing.io.deq.ready := Bool(true)
|
||||
crossing.io.enq.valid := io.master_port.request.valid && !reg.io.busy
|
||||
io.master_port.request.ready := progress && !reg.io.busy
|
||||
io.master_port.response.valid := progress && reg.io.busy
|
||||
}
|
||||
|
||||
// RegField should support connecting to one of these
|
||||
class RegisterReadIO[T <: Data](gen: T) extends Bundle {
|
||||
val request = Decoupled(Bool()).flip() // ignore .bits
|
||||
val response = Irrevocable(gen)
|
||||
}
|
||||
|
||||
class RegisterReadCrossingIO[T <: Data](gen: T) extends Bundle {
|
||||
// Master clock domain
|
||||
val master_clock = Clock(INPUT)
|
||||
val master_reset = Bool(INPUT)
|
||||
val master_allow = Bool(INPUT) // actually wait for the slave
|
||||
val master_port = new RegisterReadIO(gen)
|
||||
// Slave clock domain
|
||||
val slave_clock = Clock(INPUT)
|
||||
val slave_reset = Bool(INPUT)
|
||||
val slave_allow = Bool(INPUT) // honour requests from the master
|
||||
val slave_register = gen.asInput
|
||||
}
|
||||
|
||||
class RegisterReadCrossing[T <: Data](gen: T, sync: Int = 3) extends Module {
|
||||
val io = new RegisterReadCrossingIO(gen)
|
||||
// The crossing must only allow one item inflight at a time
|
||||
val crossing = Module(new AsyncQueue(gen, 1, sync))
|
||||
|
||||
// We can just randomly reset one-side of a single entry AsyncQueue.
|
||||
// If the enq side is reset, at worst deq.bits is reassigned from mem(0), which stays fixed.
|
||||
// If the deq side is reset, at worst the slave rewrites mem(0) once, deq.bits stays fixed.
|
||||
crossing.io.enq_clock := io.slave_clock
|
||||
crossing.io.enq_reset := io.slave_reset
|
||||
crossing.io.deq_clock := io.master_clock
|
||||
crossing.io.deq_reset := io.master_reset
|
||||
|
||||
crossing.io.enq.bits := io.slave_register
|
||||
io.master_port.response.bits := crossing.io.deq.bits
|
||||
|
||||
// If the slave is not operational, just repeat the last value we saw.
|
||||
val progress = crossing.io.deq.valid || !io.master_allow
|
||||
|
||||
val reg = Module(new BusyRegisterCrossing(io.master_clock, io.master_reset))
|
||||
reg.io.progress := progress
|
||||
reg.io.request_valid := io.master_port.request.valid
|
||||
reg.io.response_ready := io.master_port.response.ready
|
||||
|
||||
io.master_port.response.valid := progress && reg.io.busy
|
||||
io.master_port.request.ready := progress && !reg.io.busy
|
||||
crossing.io.deq.ready := io.master_port.request.valid && !reg.io.busy
|
||||
crossing.io.enq.valid := Bool(true)
|
||||
}
|
||||
|
||||
/** Wrapper to create an
|
||||
* asynchronously reset slave register which can be
|
||||
* both read and written
|
||||
* using crossing FIFOs.
|
||||
* The reset and allow assertion & de-assertion
|
||||
* should be synchronous to their respective
|
||||
* domains.
|
||||
*/
|
||||
|
||||
object AsyncRWSlaveRegField {
|
||||
|
||||
def apply(slave_clock: Clock,
|
||||
slave_reset: Bool,
|
||||
width: Int,
|
||||
init: Int,
|
||||
name: Option[String] = None,
|
||||
master_allow: Bool = Bool(true),
|
||||
slave_allow: Bool = Bool(true)
|
||||
): (UInt, RegField) = {
|
||||
|
||||
val async_slave_reg = Module(new AsyncResetRegVec(width, init))
|
||||
name.foreach(async_slave_reg.suggestName(_))
|
||||
async_slave_reg.reset := slave_reset
|
||||
async_slave_reg.clock := slave_clock
|
||||
|
||||
val wr_crossing = Module (new RegisterWriteCrossing(UInt(width = width)))
|
||||
name.foreach(n => wr_crossing.suggestName(s"${n}_wcrossing"))
|
||||
|
||||
val scope = Module (new AsyncScope())
|
||||
|
||||
wr_crossing.io.master_clock := scope.clock
|
||||
wr_crossing.io.master_reset := scope.reset
|
||||
wr_crossing.io.master_allow := master_allow
|
||||
wr_crossing.io.slave_clock := slave_clock
|
||||
wr_crossing.io.slave_reset := slave_reset
|
||||
wr_crossing.io.slave_allow := slave_allow
|
||||
|
||||
async_slave_reg.io.en := wr_crossing.io.slave_valid
|
||||
async_slave_reg.io.d := wr_crossing.io.slave_register
|
||||
|
||||
val rd_crossing = Module (new RegisterReadCrossing(UInt(width = width )))
|
||||
name.foreach(n => rd_crossing.suggestName(s"${n}_rcrossing"))
|
||||
|
||||
rd_crossing.io.master_clock := scope.clock
|
||||
rd_crossing.io.master_reset := scope.reset
|
||||
rd_crossing.io.master_allow := master_allow
|
||||
rd_crossing.io.slave_clock := slave_clock
|
||||
rd_crossing.io.slave_reset := slave_reset
|
||||
rd_crossing.io.slave_allow := slave_allow
|
||||
|
||||
rd_crossing.io.slave_register := async_slave_reg.io.q
|
||||
|
||||
(async_slave_reg.io.q, RegField(width, rd_crossing.io.master_port, wr_crossing.io.master_port))
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import diplomacy._
|
||||
import regmapper._
|
||||
import scala.math.{min,max}
|
||||
|
||||
class TLRegisterNode(address: AddressSet, concurrency: Int = 0, beatBytes: Int = 4, undefZero: Boolean = true)
|
||||
|
@ -3,8 +3,10 @@
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import diplomacy._
|
||||
import regmapper._
|
||||
import unittest._
|
||||
import util.Pow2ClockDivider
|
||||
import util.{Pow2ClockDivider}
|
||||
|
||||
object LFSR16Seed
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import diplomacy._
|
||||
|
||||
class TLRAM(address: AddressSet, executable: Boolean = true, beatBytes: Int = 4) extends LazyModule
|
||||
{
|
||||
|
@ -1,125 +0,0 @@
|
||||
// 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(pd: TLClientPortParameters, pu: TLManagerPortParameters): TLEdgeOut = new TLEdgeOut(pd, pu)
|
||||
def edgeI(pd: TLClientPortParameters, pu: TLManagerPortParameters): TLEdgeIn = new TLEdgeIn(pd, pu)
|
||||
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 colour = "#000000" // black
|
||||
def connect(bo: => TLBundle, bi: => TLBundle, ei: => TLEdgeIn)(implicit sourceInfo: SourceInfo): (Option[LazyModule], () => Unit) = {
|
||||
val monitor = LazyModule(new TLMonitor(() => new TLBundleSnoop(bo.params), () => ei, sourceInfo))
|
||||
(Some(monitor), () => {
|
||||
bi <> bo
|
||||
monitor.module.io.in := TLBundleSnoop(bo)
|
||||
})
|
||||
}
|
||||
|
||||
override def mixO(pd: TLClientPortParameters, node: OutwardNode[TLClientPortParameters, TLManagerPortParameters, TLBundle]): TLClientPortParameters =
|
||||
pd.copy(clients = pd.clients.map { c => c.copy (nodePath = node +: c.nodePath) })
|
||||
override def mixI(pu: TLManagerPortParameters, node: InwardNode[TLClientPortParameters, TLManagerPortParameters, TLBundle]): TLManagerPortParameters =
|
||||
pu.copy(managers = pu.managers.map { m => m.copy (nodePath = node +: m.nodePath) })
|
||||
}
|
||||
|
||||
case class TLIdentityNode() extends IdentityNode(TLImp)
|
||||
case class TLOutputNode() extends OutputNode(TLImp)
|
||||
case class TLInputNode() extends InputNode(TLImp)
|
||||
|
||||
case class TLClientNode(portParams: TLClientPortParameters, numPorts: Range.Inclusive = 1 to 1)
|
||||
extends SourceNode(TLImp)(portParams, numPorts)
|
||||
case class TLManagerNode(portParams: TLManagerPortParameters, numPorts: Range.Inclusive = 1 to 1)
|
||||
extends SinkNode(TLImp)(portParams, numPorts)
|
||||
|
||||
object TLClientNode
|
||||
{
|
||||
def apply(params: TLClientParameters) =
|
||||
new TLClientNode(TLClientPortParameters(Seq(params)), 1 to 1)
|
||||
}
|
||||
|
||||
object TLManagerNode
|
||||
{
|
||||
def apply(beatBytes: Int, params: TLManagerParameters) =
|
||||
new TLManagerNode(TLManagerPortParameters(Seq(params), beatBytes, 0), 1 to 1)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
/** Synthesizeable unit tests */
|
||||
import unittest._
|
||||
|
||||
class TLInputNodeTest extends UnitTest(500000) {
|
||||
class Acceptor extends LazyModule {
|
||||
val node = TLInputNode()
|
||||
val tlram = LazyModule(new TLRAM(AddressSet(0x54321000, 0xfff)))
|
||||
tlram.node := node
|
||||
|
||||
lazy val module = new LazyModuleImp(this) {
|
||||
val io = new Bundle {
|
||||
val in = node.bundleIn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val fuzzer = LazyModule(new TLFuzzer(5000))
|
||||
LazyModule(new Acceptor).node := TLFragmenter(4, 64)(fuzzer.node)
|
||||
|
||||
io.finished := Module(fuzzer.module).io.finished
|
||||
}
|
||||
|
||||
object TLAsyncImp extends NodeImp[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncEdgeParameters, TLAsyncEdgeParameters, TLAsyncBundle]
|
||||
{
|
||||
def edgeO(pd: TLAsyncClientPortParameters, pu: TLAsyncManagerPortParameters): TLAsyncEdgeParameters = TLAsyncEdgeParameters(pd, pu)
|
||||
def edgeI(pd: TLAsyncClientPortParameters, pu: TLAsyncManagerPortParameters): TLAsyncEdgeParameters = TLAsyncEdgeParameters(pd, pu)
|
||||
def bundleO(eo: Seq[TLAsyncEdgeParameters]): Vec[TLAsyncBundle] = {
|
||||
require (eo.size == 1)
|
||||
Vec(eo.size, new TLAsyncBundle(eo(0).bundle))
|
||||
}
|
||||
def bundleI(ei: Seq[TLAsyncEdgeParameters]): Vec[TLAsyncBundle] = {
|
||||
require (ei.size == 1)
|
||||
Vec(ei.size, new TLAsyncBundle(ei(0).bundle)).flip
|
||||
}
|
||||
|
||||
def colour = "#ff0000" // red
|
||||
def connect(bo: => TLAsyncBundle, bi: => TLAsyncBundle, ei: => TLAsyncEdgeParameters)(implicit sourceInfo: SourceInfo): (Option[LazyModule], () => Unit) = {
|
||||
(None, () => { bi <> bo })
|
||||
}
|
||||
|
||||
override def mixO(pd: TLAsyncClientPortParameters, node: OutwardNode[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncBundle]): TLAsyncClientPortParameters =
|
||||
pd.copy(base = pd.base.copy(clients = pd.base.clients.map { c => c.copy (nodePath = node +: c.nodePath) }))
|
||||
override def mixI(pu: TLAsyncManagerPortParameters, node: InwardNode[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncBundle]): TLAsyncManagerPortParameters =
|
||||
pu.copy(base = pu.base.copy(managers = pu.base.managers.map { m => m.copy (nodePath = node +: m.nodePath) }))
|
||||
}
|
||||
|
||||
case class TLAsyncIdentityNode() extends IdentityNode(TLAsyncImp)
|
||||
case class TLAsyncOutputNode() extends OutputNode(TLAsyncImp)
|
||||
case class TLAsyncInputNode() extends InputNode(TLAsyncImp)
|
||||
|
||||
case class TLAsyncSourceNode() extends MixedNode(TLImp, TLAsyncImp)(
|
||||
dFn = { case (1, s) => s.map(TLAsyncClientPortParameters(_)) },
|
||||
uFn = { case (1, s) => s.map(_.base) },
|
||||
numPO = 1 to 1,
|
||||
numPI = 1 to 1)
|
||||
|
||||
case class TLAsyncSinkNode(depth: Int) extends MixedNode(TLAsyncImp, TLImp)(
|
||||
dFn = { case (1, s) => s.map(_.base) },
|
||||
uFn = { case (1, s) => s.map(TLAsyncManagerPortParameters(depth, _)) },
|
||||
numPO = 1 to 1,
|
||||
numPI = 1 to 1)
|
@ -5,6 +5,7 @@ package uncore.tilelink2
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.SourceInfo
|
||||
import chisel3.util.{Irrevocable, IrrevocableIO}
|
||||
import diplomacy._
|
||||
import scala.math.{min,max}
|
||||
|
||||
// innBeatBytes => the new client-facing bus width
|
||||
|
@ -4,6 +4,7 @@ package uncore.tilelink2
|
||||
|
||||
import Chisel._
|
||||
import chisel3.util.IrrevocableIO
|
||||
import diplomacy._
|
||||
|
||||
class TLXbar(policy: TLArbiter.Policy = TLArbiter.lowestIndexFirst) extends LazyModule
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
package uncore
|
||||
|
||||
import Chisel._
|
||||
import chisel3.internal.sourceinfo.{SourceInfo, SourceLine, UnlocatableSourceInfo}
|
||||
import diplomacy._
|
||||
|
||||
package object tilelink2
|
||||
{
|
||||
@ -17,9 +17,4 @@ package object tilelink2
|
||||
if (s >= w) x else helper(s+s, x | (x << s)(w-1,0))
|
||||
helper(1, x)
|
||||
}
|
||||
|
||||
def sourceLine(sourceInfo: SourceInfo, prefix: String = " (", suffix: String = ")") = sourceInfo match {
|
||||
case SourceLine(filename, line, col) => s"$prefix$filename:$line:$col$suffix"
|
||||
case _ => ""
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user