tilelink.fabric.Node
tilelink.fabric.Node is an additional layer over the regular tilelink hardware instantiation which handle negotiation and parameters propagation at a SoC level.
It is mostly based on the Fiber API, which allows to create elaboration time fibers (user-space threads), allowing to schedule future parameter propagation / negotiation and hardware elaboration.
可以通过3种方式创建节点(Node):
tilelink.fabric.Node.down():创建一个可以向下连接(向从端)的节点,因此它将用于CPU/DMA/桥的代理
tilelink.fabric.Node():创建中间节点
tilelink.fabric.Node.up():创建一个可以向上连接(向主端)的节点,因此它将用于外设/存储器/桥的代理
节点大多具有以下属性:
bus : Handle[tilelink.Bus];总线的硬件实例
m2s.proposed : Handle[tilelink.M2sSupport];由向上连接提出的功能集
m2s.supported : Handle[tilelink.M2sSupport]: 向下连接支持的功能集
m2s.parameter : Handle[tilelink.M2sParameter]: 最终的总线参数
您可以注意到它们都是句柄。Handle是SpinalHDL中在纤程之间共享值的一种方式。如果一个纤程读取一个句柄,而这个句柄还没有值,它将阻止该纤程的执行,直到另一个纤程向该句柄提供一个值。
There is also a set of attributes like m2s, but reversed (named s2m) which specify the parameters for the transactions initiated by the slave side of the interconnect (ex memory coherency).
有两个演讲介绍了tilelink.fabric.Node。这两个演讲可能并不完全遵循实际语法,它们仍然遵循以下概念:
顶层示例
以下是一个简单的虚拟SoC顶层设计示例:
val cpu = new CpuFiber()
val ram = new RamFiber()
ram.up at(0x10000, 0x200) of cpu.down // map the ram at [0x10000-0x101FF], the ram will infer its own size from it
val gpio = new GpioFiber()
gpio.up at 0x20000 of cpu.down // map the gpio at [0x20000-0x20FFF], its range of 4KB being fixed internally
您还可以定义互连中的中间节点,如下所示:
val cpu = new CpuFiber()
val ram = new RamFiber()
ram.up at(0x10000, 0x200) of cpu.down
// Create a peripherals namespace to keep things clean
val peripherals = new Area {
// Create a intermediate node in the interconnect
val access = tilelink.fabric.Node()
access at 0x20000 of cpu.down
val gpioA = new GpioFiber()
gpioA.up at 0x0000 of access
val gpioB = new GpioFiber()
gpioB.up at 0x1000 of access
}
GPIOFiber示例
GpioFiber是一个简单的tilelink外设,可以读取/驱动32位三态阵列。
import spinal.lib._
import spinal.lib.bus.tilelink
import spinal.core.fiber.Fiber
class GpioFiber extends Area {
// Define a node facing upward (toward masters only)
val up = tilelink.fabric.Node.up()
// Define a elaboration thread to specify the "up" parameters and generate the hardware
val fiber = Fiber build new Area {
// Here we first define what our up node support. m2s mean master to slave requests
up.m2s.supported load tilelink.M2sSupport(
addressWidth = 12,
dataWidth = 32,
// Transfers define which kind of memory transactions our up node will support.
// Here it only support 4 bytes get/putfull
transfers = tilelink.M2sTransfers(
get = tilelink.SizeRange(4),
putFull = tilelink.SizeRange(4)
)
)
// s2m mean slave to master requests, those are only use for memory coherency purpose
// So here we specify we do not need any
up.s2m.none()
// Then we can finally generate some hardware
// Starting by defining a 32 bits TriStateArray (Array meaning that each pin has its own writeEnable bit
val pins = master(TriStateArray(32 bits))
// tilelink.SlaveFactory is a utility allowing to easily generate the logic required
// to control some hardware from a tilelink bus.
val factory = new tilelink.SlaveFactory(up.bus, allowBurst = false)
// Use the SlaveFactory API to generate some hardware to read / drive the pins
val writeEnableReg = factory.drive(pins.writeEnable, 0x0) init (0)
val writeReg = factory.drive(pins.write, 0x4) init(0)
factory.read(pins.read, 0x8)
}
}
RamFiber示例
RamFiber是常规tilelink Ram组件的集成层。
import spinal.lib.bus.tilelink
import spinal.core.fiber.Fiber
class RamFiber() extends Area {
val up = tilelink.fabric.Node.up()
val thread = Fiber build new Area {
// Here the supported parameters are function of what the master would like us to ideally support.
// The tilelink.Ram support all addressWidth / dataWidth / burst length / get / put accesses
// but doesn't support atomic / coherency. So we take what is proposed to use and restrict it to
// all sorts of get / put request
up.m2s.supported load up.m2s.proposed.intersect(M2sTransfers.allGetPut)
up.s2m.none()
// Here we infer how many bytes our ram need to be, by looking at the memory mapping of the connected masters
val bytes = up.ups.map(e => e.mapping.value.highestBound - e.mapping.value.lowerBound + 1).max.toInt
// Then we finally generate the regular hardware
val logic = new tilelink.Ram(up.bus.p.node, bytes)
logic.io.up << up.bus
}
}
CpuFiber示例
CpuFiber是一个虚拟的主端集成的示例。
import spinal.lib.bus.tilelink
import spinal.core.fiber.Fiber
class CpuFiber extends Area {
// Define a node facing downward (toward slaves only)
val down = tilelink.fabric.Node.down()
val fiber = Fiber build new Area {
// Here we force the bus parameters to a specific configurations
down.m2s forceParameters tilelink.M2sParameters(
addressWidth = 32,
dataWidth = 64,
// We define the traffic of each master using this node. (one master => one M2sAgent)
// In our case, there is only the CpuFiber.
masters = List(
tilelink.M2sAgent(
name = CpuFiber.this, // Reference to the original agent.
// A agent can use multiple sets of source ID for different purposes
// Here we define the usage of every sets of source ID
// In our case, let's say we use ID [0-3] to emit get/putFull requests
mapping = List(
tilelink.M2sSource(
id = SizeMapping(0, 4),
emits = M2sTransfers(
get = tilelink.SizeRange(1, 64), // Meaning the get access can be any power of 2 size in [1, 64]
putFull = tilelink.SizeRange(1, 64)
)
)
)
)
)
)
// Lets say the CPU doesn't support any slave initiated requests (memory coherency)
down.s2m.supported load tilelink.S2mSupport.none()
// Then we can generate some hardware (nothing useful in this example)
down.bus.a.setIdle()
down.bus.d.ready := True
}
}
Tilelink的一个特殊性是,它假设主端不会向未映射的内存空间发出请求。为了让主机识别允许访问哪些内存,您可以使用spinal.lib.system.tag.MemoryConnection.getMemoryTransfers工具,如下所示:
val mappings = spinal.lib.system.tag.MemoryConnection.getMemoryTransfers(down)
// Here we just print the values out in stdout, but instead you can generate some hardware from it.
for(mapping <- mappings) {
println(s"- ${mapping.where} -> ${mapping.transfers}")
}
如果您在CPU的纤程中运行此命令,在下面的soc中:
val cpu = new CpuFiber()
val ram = new RamFiber()
ram.up at(0x10000, 0x200) of cpu.down
// Create a peripherals namespace to keep things clean
val peripherals = new Area {
// Create a intermediate node in the interconnect
val access = tilelink.fabric.Node()
access at 0x20000 of cpu.down
val gpioA = new GpioFiber()
gpioA.up at 0x0000 of access
val gpioB = new GpioFiber()
gpioB.up at 0x1000 of access
}
你会得到 :
- toplevel/ram_up mapped=SM(0x10000, 0x200) through=List(OT(0x10000)) -> GF
- toplevel/peripherals_gpioA_up mapped=SM(0x20000, 0x1000) through=List(OT(0x20000), OT(0x0)) -> GF
- toplevel/peripherals_gpioB_up mapped=SM(0x21000, 0x1000) through=List(OT(0x20000), OT(0x1000)) -> GF
“through=” 指定了到达目标所需的地址转换链。
“SM” 表示SizeMapping(address, size)
“OT” 表示OffsetTransformer(offset)
Note that you can also add PMA (Physical Memory Attributes) to nodes and retrieves them via this getMemoryTransfers utilities.
当前PMA的定义是:
object MAIN extends PMA
object IO extends PMA
object CACHABLE extends PMA // an intermediate agent may have cached a copy of the region for you
object TRACEABLE extends PMA // the region may have been cached by another master, but coherence is being provided
object UNCACHABLE extends PMA // the region has not been cached yet, but should be cached when possible
object IDEMPOTENT extends PMA // reads return most recently put content, but content should not be cached
object EXECUTABLE extends PMA // Allows an agent to fetch code from this region
object VOLATILE extends PMA // content may change without a write
object WRITE_EFFECTS extends PMA // writes produce side effects and so must not be combined/delayed
object READ_EFFECTS extends PMA // reads produce side effects and so must not be issued speculatively
getMemoryTransfers工具依赖于专用的SpinalTag:
trait MemoryConnection extends SpinalTag {
def up : Nameable with SpinalTagReady // Side toward the masters of the system
def down : Nameable with SpinalTagReady // Side toward the slaves of the system
def mapping : AddressMapping // Specify the memory mapping of the slave from the master address (before transformers are applied)
def transformers : List[AddressTransformer] // List of alteration done to the address on this connection (ex offset, interleaving, ...)
def sToM(downs : MemoryTransfers, args : MappedNode) : MemoryTransfers = downs // Convert the slave MemoryTransfers capabilities into the master ones
}
该SpinalTag可以应用于给定内存总线连接的两端,以保持该连接在生成时可被发现,从而创建内存连接(MemoryConnection)图。它的一个优点是它与总线无关,这意味着它不是tilelink特有的。
位宽适配器(WidthAdapter)示例
位宽适配器是桥的一个简单例子。
class WidthAdapterFiber() extends Area {
val up = Node.up()
val down = Node.down()
// Populate the MemoryConnection graph
new MemoryConnection {
override def up = up
override def down = down
override def transformers = Nil
override def mapping = SizeMapping(0, BigInt(1) << WidthAdapterFiber.this.up.m2s.parameters.addressWidth)
populate()
}
// Fiber in which we will negotiate the data width parameters and generate the hardware
val logic = Fiber build new Area {
// First, we propagate downward the parameter proposal, hopping that the downward side will agree
down.m2s.proposed.load(up.m2s.proposed)
// Second, we will propagate upward what is actually supported, but will take care of any dataWidth mismatch
up.m2s.supported load down.m2s.supported.copy(
dataWidth = up.m2s.proposed.dataWidth
)
// Third, we propagate downward the final bus parameter, but will take care of any dataWidth mismatch
down.m2s.parameters load up.m2s.parameters.copy(
dataWidth = down.m2s.supported.dataWidth
)
// No alteration on s2m parameters
up.s2m.from(down.s2m)
// Finally, we generate the hardware
val bridge = new tilelink.WidthAdapter(up.bus.p, down.bus.p)
bridge.io.up << up.bus
bridge.io.down >> down.bus
}
}