Diplomacy代表将SoC中的各个节点作为有向无环图中的节点。
TileLink Client是发起TileLink事务的节点,从A channel会发起请求,并且在D channel接受回复。如果实现了TL-C就可在B channel上接受probe,在C channel发起release,同时E channel上表示已经接受到回复。在Rocket Chip上L1 Cache和DMA设备可以作为TileLink的Client节点。
class MyClient(implicit p: Parameters) extends LazyModule {
val node = TLClientNode(Seq(TLMasterPortParameters.v1(Seq(TLClientParameters(
name = "my-client",
sourceId = IdRange(0, 4),
requestFifo = true,
visibility = Seq(AddressSet(0x10000, 0xffff)))))))
lazy val module = new LazyModule(this) {
val (tl, edge) = node.out(0)
//...
}
}name在表示在Diplomacy图中,这个节点的名字。SourceId用于区分在一系列的client中是哪个节点。requestFifo, 表示是否根据先到先回复的顺序进行回复。visibility,表示了这个节点会访问的区域,在这个例子中为AddressSet(0x10000, 0xffff), 意味着这个client只能访问0x10000到0x1ffff。
在LazyModule中使用了tl和edge:
tl表示连接到这个节点的一系列Bundle, 可以是TileLink中的五个通道,可以用于节点通信。edge表示在Diplomacy图中的边,包含一些有用的函数。
TileLink manager会从client处接受请求, 并且在D Channel上发起一次回复
class MyManager(implicit p: Parameters) extends LazyModule {
val device = new SimpleDevice("my-device", Seq("tutorial,my-device0"))
val beatBytes = 8
val node = TLManagerNode(Seq(TLSlavePortParameters, v1(Seq(TLManagerParameters(
address = Seq(AddressSet(0x20000, 0xfff)),
resources = device.reg,
regionType = RegionType.UNCACHED,
executable = true,
supportsArithmetic = TransferSizes(1, beatBytes),
supportsLogical = TransferSizes(1, beatBytes),
supportsGet = TransferSizes(1, beatBytes),
supportsPutFull = TransferSizes(1, beatBytes),
supportsPutPartial = TransferSiszes(1, beatBytes),
supportsHint = TransferSizes(1, beatBytes),
fifoId = Some(0))), beatBytes)))
lazy val module = new LazyModuleImpl(this) {
val (tl, edge) = node.in(0)
}
} 对于Manager节点需要两个参数,一个是beatBytes,一个是TLManagerParameters。其中beatBytes表示TileLink接口的宽度。接下来说明TLManagerParameters:
address:表示了manager会服务的地址,在这个例子中为0x20000到0x20fff。resources, 这里我们使用了一个SimpleDevice, 这个参数表示表示你想在BootRom中的设备树中添加一个设备。regionType, 表示了这些地址的caching行为,比如CACHED、TRACKED、UNCACHED、IDEMPOTENE等。executable, 表示是否允许从这个节点读取指令,大多数MMIO设备这个选项都设置为false。- 接下来的参数,用
support的类型,传输的大小,是否支持FIFO
这种类型的节点提供了regmap方法,可以运行通过操作寄存器来自动完成控制TileLink协议。
这种节点可以用于进行连接,例子如下:
class MyClient1(implicit p: Parameters) extends LazyModule {
val node = TLClientNode(Seq(TLMasterPortParameters.v1(Seq(TLClientParameter(
"my-client1", IdRange(0, 1))))))
lazy val module = new LazyModuleImpl(this) {
//...
}
}
class MyClient2(implicit p: Parameters) extends LazyModule {
val node = TLClientNode(Seq(TLMasterPortParameters.v1(Seq(TLClientParameters(
"my-client2", IdRange(0, 1))))))
lazy val module = new LazyModuleImpl(this) {
//...
}
} 接下来我们对这两个节点进行连接:
class MyClientGroup(implicit p: Parameters) extends LazyModule {
val client1 = LazyModule(new MyClient1)
val client2 = LazyModule(new MyClient2)
val node = TLIdentityNode()
node := client1.node
node := client2.node
lazy val module = new LazyModuleImpl(this) {
// Nothing to do here
}
} 我们也可对两个manager节点做同样的事,我们还可以用多条边连接两个点。
class MyClientManagerComplex(implicit p: Parameters) extends LazyModule {
val client = LazyModule(new MyClientGroup)
val manager = LazyModule(new MyManagerGroup(8))
manager.node :=* client.node
lazy val module = new LazyModuleImp(this) {
//...
}
} 这里我们使用了:*符号用于进行连接,这里client1.node会和manager1.node进行连接,client2.node会和manager2.node进行连接。
和Adapter Node节点类似,adapter需要相同数量的输入和输出。但是和Adapter Node节点不同的是,它可以改变连接关系。
val node = TLAdapterNode {
clientFn = { cp =>
// ..
},
managerFn = { mp =>
//..
}
} clientFn中我们需要传入TLClientPortParameters, 同时在managerFn我们需要传入TLManagerPortParameters。
Nexus节点和adapter节点相似,但是输入输出的接口数可以不同,这种节点主要由TLXbar使用,可以用于生成TileLink的crossbar。
val node = TLNexusNode {
clientFn = { Seq =>
//...
},
managerFn = { Seq =>
//...
}
}:=, 一对一的连接:=*, 进行多个连接,但是连接的边的数量由client决定:*=, 连接的数量由manager决定:*=*, 连接的数量由两边已知数量的一方决定。
我们刚才在节点类型一章中提到(tl, edge),其中edge中有很多有用的方法。这里进行介绍Get
Get请求可以获取一个BundleA,之后可以在D通道收到回复,需要的参数如下
fromSource, 源IDtoAddress,要读取的地址lgSize,需要读取的字节数
返回值(Bool, TLBunadleA), 表示是否获取成功和一个BundleA
用于为CPU暴露设备,通过一组寄存器可以操作设备,对于TileLink设备可以通过使用regmap接口来扩展TLRegisterRouter类,或者可以创建一个TLRegisterNode。
import chisel3._
import chisel3.util._
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.regmapper._
import freechips.rocketchip.tilelink.TLRegisterNode
class MyDeviceController(implicit p: Parameters) extends LazyModule {
val device = new SimpleDevice("my-device", Seq("tutorial,my-device0"))
val node = TLRegisterNode(
address = Seq(AddressSet(0x10028000, 0xfff)),
device = device,
beatBytes = 8,
concurrency = 1)
lazy val module = new LazyModuleImp(this) {
val bigReg = RegInit(0.U(64.W))
val mediumReg = RegInit(0.U(32.W))
val smallReg = RegInit(0.U(16.W))
val tinyReg0 = RegInit(0.U(4.W))
val tinyReg1 = RegInit(0.U(4.W))
node.regmap(
0x00 -> Seq(RegField(64, bigReg)),
0x08 -> Seq(RegField(32, mediumReg)),
0x0C -> Seq(RegField(16, smallReg)),
0x0E -> Seq(
RegField(4, tinyReg0),
RegField(4, tinyReg1)
)
)
}
} 上面这个例子中使用TLRegisterNode来映射不同大小的硬件寄存器,
-
address, 用于说明设备的地址,也是设备寄存器的基地址 -
device, 说明设备树的入口 -
beatBytes用于说明接口的宽度 -
concurrency用于说明TlileLink请求队列的大小。
和这个节点进行交互的方法就是调用regmap, 说明了偏移和写入的寄存器。
如果想要不仅仅是读写硬件寄存器,我们需要RegFiled提供对应DecoupledIO进行操作。如下有一个例子
val queue = Module(new Queue(UInt(64.W), 4))
node.regmap(
0x00 -> Seq(RegField(64, queue.io.deq, queue.io.enq))
) 这里RegField有三个参数,第一参数指名宽度,第二个参数指明读接口,第三个参数指明写接口。这里写操作将数据压入队列,而读操作从队列中取数据。
我们还可以使用函数创建硬件寄存器
val counter = RegInit(0.U(64.W))
def readCounter(ready: Bool): (Bool, UInt) = {
when(ready) { counter := counter - 1.U }
(true.B, counter)
}
def writeCounter(valid:Bool, bits: UInt): Bool = {
when(valid) { counter:= counter + 1.U }
true.B
}
node.regmap(
0x00 -> Seq(RegField.r(64, readCounter(_))),
0x08 -> Seq(RegField.w(64, writeCounter(_, _)))
) 当进行读时,会传入ready信号,之后返回valid和bits。当进行写实时会传入valid信号,返回ready。
我们可以将TLRegisterNode转化为AXI4Register节点。
import freechips.rocketchip.amba.axi4.AXI4RegisterNode
class MyAXI4DeviceController(implicit p: Parameters) extends LazyModule {
val node = AXI4RegisterNode(
address = AddressSet(0x10029000, 0xfff),
beatBytes = 8,
concurrency = 1
)
lazy val module = new LazyModuleImp(this) {
val bigReg = RegInit(0.U(64.W))
val mediumReg = RegInit(0.U(32.W))
val smallReg = RegInit(0.U(16.W))
val tinyReg0 = RegInit(0.U(4.W))
val tinyReg1 = RegInit(0.U(4.W))
node.regmap(
0x00 -> Seq(RegField(64, bigReg)),
0x08 -> Seq(RegField(32, mediumReg)),
0x0C -> Seq(RegField(16, smallReg)),
0x0E -> Seq(
RegField(4, tinyReg0),
RegField(4, tinyReg1),
)
)
}
} 除了AXI4不用接受device参数,只能用一个AddressSet, 其他一样。
TileLink Widgets在freechips.rocketchip.tilelink,AXI4 widgets在freechips.rocketchip.amba.axi4中。
这是一个用于buffering``TileLink事务的工具,它是用一个队列进行实现的,
depth: Int, 用于指明队列中可以容纳的数量。flow: Boolean, 用于组合valid信号,于是输入可以在一个周期内被处理。pipe: Boolean, 组合ready信号,可以队列全速运行。