SpinalHDL之总线

本文作为SpinalHDL学习笔记第二十三篇,介绍SpinalHDL总线相关API。

SpinalHDL学习笔记总纲链接如下:

SpinalHDL 学习笔记_spinalhdl blackbox-CSDN博客

目录:

1.AHB-Lite3

2.Apb3

3.Axi4

4.AvalonMM

5.Tilelink

6.tilelink.fabric.Node

1.AHB-Lite3

配置和实例化

首先,想要创建 AHB-Lite3 总线时,都需要一个配置对象。该配置对象是一个 AhbLite3Config,并具有以下参数:

参数名称

类型

默认值

描述

addressWidth

Int

HADDR 的位宽(字节粒度)

HADDR位宽

dataWidth

Int

HWDATA 和 HRDATA 的位宽

HWDATA和HRDATA位宽

简而言之, AHB-Lite3 总线在 SpinalHDL 库中是如下定义的:

case class AhbLite3(config: AhbLite3Config) extends Bundle with IMasterSlave{
// Address and control
val HADDR = UInt(config.addressWidth bits)
val HSEL = Bool()
val HREADY = Bool()
val HWRITE = Bool()
val HSIZE = Bits(3 bits)
val HBURST = Bits(3 bits)
val HPROT = Bits(4 bits)
val HTRANS = Bits(2 bits)
val HMASTLOCK = Bool()

// Data
val HWDATA = Bits(config.dataWidth bits)
val HRDATA = Bits(config.dataWidth bits)
// Transfer response
val HREADYOUT = Bool()
val HRESP = Bool()
override def asMaster(): Unit = {
out(HADDR,HWRITE,HSIZE,HBURST,HPROT,HTRANS,HMASTLOCK,HWDATA,HREADY,HSEL)
in(HREADYOUT,HRESP,HRDATA)
}
}

 这是一个简单的使用示例:

val ahbConfig = AhbLite3Config(
addressWidth = 12,
dataWidth = 32
)
val ahbX = AhbLite3(ahbConfig)
val ahbY = AhbLite3(ahbConfig)
when(ahbY.HSEL){
//...
}

变体

有一个 AhbLite3Master 变体,唯一的区别是缺少 HREADYOUT 信号。当互连线和从端使用 AhbLite3 时,此变体只能由主端使用。

2.Apb3

AMBA3-APB 总线通常用于连接低带宽外设。

配置和实例化

首先,每当想要创建 APB3 总线时,都需要一个配置对象。该配置对象是一个 Apb3Config 并具有以下参数:

参数名称

类型

描述

addressWidth

Int

PADDR 的位宽(字节粒度)

dataWidth

Int

PWDATA 和 PRDATA 的位宽

selWidth

Int

PSEL的位宽

useSlaveError

Boolean

指定是否出现PSLVERROR

简而言之, APB3 总线在 SpinalHDL 库中定义方式如下:

case class Apb3(config: Apb3Config) extends Bundle with IMasterSlave {
val PADDR = UInt(config.addressWidth bits)
val PSEL = Bits(config.selWidth bits)
val PENABLE = Bool()
val PREADY = Bool()
val PWRITE = Bool()
val PWDATA = Bits(config.dataWidth bits)
val PRDATA = Bits(config.dataWidth bits)
val PSLVERROR = if(config.useSlaveError) Bool() else null
//...
}

这是一个简单的使用示例:

val apbConfig = Apb3Config(
addressWidth = 12,
dataWidth = 32
)
val apbX = Apb3(apbConfig)
val apbY = Apb3(apbConfig)
when(apbY.PENABLE){
//...
}

函数和运算符

名称

描述

X » Y

将 X 连接到 Y。 Y 的地址可以小于 X 的地址

X « Y

执行 » 运算符相反的操作

3.Axi4

AXI4 是 ARM 定义的高带宽总线。

配置和实例化

首先,想要创建 AXI4 总线时需要一个配置对象。该配置对象是一个 Axi4Config 并具有以下参数:

Note: useXXX 用于指定总线是否存在 XXX 信号。

参数名称

类型

默认值

addressWidth

Int

dataWidth

Int

idWidth

Int

userWidth

Int

useId

Boolean

true

useRegion

Boolean

true

useBurst

Boolean

true

useLock

Boolean

true

useCache

Boolean

true

useSize

Boolean

true

useQos

Boolean

true

useLen

Boolean

true

useLast

Boolean

true

useResp

Boolean

true

useProt

Boolean

true

useStrb

Boolean

true

useUser

Boolean

false

简而言之, AXI4 总线在 SpinalHDL 库中定义方式如下:

case class Axi4(config: Axi4Config) extends Bundle with IMasterSlave{
val aw = Stream(Axi4Aw(config))
val w = Stream(Axi4W(config))
val b = Stream(Axi4B(config))
val ar = Stream(Axi4Ar(config))
val r = Stream(Axi4R(config))
override def asMaster(): Unit = {
master(ar,aw,w)
slave(r,b)
}
}

这是一个简单的使用示例:

val axiConfig = Axi4Config(
addressWidth = 32,
dataWidth = 32,
idWidth = 4
)
val axiX = Axi4(axiConfig)
val axiY = Axi4(axiConfig)
when(axiY.aw.valid){
//...
}

变体

Axi4 总线还有其他 3 种变体:

类型

描述

Axi4ReadOnly

只存在 AR 和 R 通道

Axi4WriteOnly

只存在 AW、 W 和 B 通道

Axi4Shared

此变体是该库的首创。
它使用 4 个通道, W、 B、 R,还有一个新通道,称为 AWR。
AWR 通道可用于传输 AR 和 AW 事务。为了分离它们,需要一个 write 信号。
这种 Axi4Shared 变体的优点是使用更少的面积,特别是在互连方面。

4.AvalonMM

AvalonMM 总线非常适合 FPGA。它非常灵活:

• 能够与 APB 一样简单

• 在许多需要带宽的应用中比 AHB 更好,因为 AvalonMM 有一种将读取响应与命令解耦的模式(减少延迟读延迟的影响)。

• 性能不如 AXI,但使用的逻辑面积少得多(读取和写入命令使用相同的握手通道。主端不需要存储挂起请求的地址,从而避免读取/写入冒险)

配置和实例化

AvalonMM 包有一个构造参数 AvalonMMConfig 。由于 Avalon 总线的灵活性,AvalonMMConfig 有很多配置元素。有关 Avalon 规范的更多信息,请访问英特尔网站。

case class AvalonMMConfig( addressWidth : Int,
dataWidth : Int,
burstCountWidth : Int,
useByteEnable : Boolean,
useDebugAccess : Boolean,
useRead : Boolean,
useWrite : Boolean,
useResponse : Boolean,
useLock : Boolean,
useWaitRequestn : Boolean,
useReadDataValid : Boolean,
useBurstCount : Boolean,
//useEndOfPacket : Boolean,
addressUnits : AddressUnits = symbols,
burstCountUnits : AddressUnits = words,
burstOnBurstBoundariesOnly : Boolean = false,
constantBurstBehavior : Boolean = false,
holdTime : Int = 0,
linewrapBursts : Boolean = false,
maximumPendingReadTransactions : Int = 1,
maximumPendingWriteTransactions : Int = 0, // unlimited
readLatency : Int = 0,
readWaitTime : Int = 0,
setupTime : Int = 0,
writeWaitTime : Int = 0
)

这个配置类还有一些函数:

名称

返回类型

描述

getReadOnlyConfg

AvalonMMConfg

返回一个类似的配置,但禁用所有写入属性

getWriteOnlyConfg

AvalonMMConfg

返回一个类似的配置,但禁用所有读取属性

// Create a write only AvalonMM configuration with burst capabilities and byte␣
,→enable
val myAvalonConfig = AvalonMMConfig.bursted(
addressWidth = addressWidth,
dataWidth = memDataWidth,
burstCountWidth = log2Up(burstSize + 1)
).copy(
useByteEnable = true,
constantBurstBehavior = true,
burstOnBurstBoundariesOnly = true
).getWriteOnlyConfig
// Create an instance of the AvalonMM bus by using this configuration
val bus = AvalonMM(myAvalonConfig)

 

5.Tilelink

配置和实例化

这是一个简单的示例,它定义了两个不相干的 tilelink 总线实例并将它们连接起来:

import spinal.lib.bus.tilelink
val param = tilelink.BusParameter.simple(
addressWidth = 32,
dataWidth = 64,
sizeBytes = 64,
sourceWidth = 4
)
val busA, busB = tilelink.Bus(param)
busA << busB

这里与上面相同,但是具有一致性通道:

import spinal.lib.bus.tilelink
val param = tilelink.BusParameter(
addressWidth = 32,
dataWidth = 64,
sizeBytes = 64,
sourceWidth = 4,
sinkWidth = 0,
withBCE = false,
withDataA = true,
withDataB = false,
withDataC = false,
withDataD = true,
node = null
)
val busA, busB = tilelink.Bus(param)
busA << busB

以上的内容是关于硬件实例化的,这是简单/容易的部分。当涉及 SoC/内存一致性时,可能需要一个额外的层来协调/传递参数。这就是 tilelink.fabric.Node 的作用。

6.tilelink.fabric.Node

tilelink.fabric.Node 是常规 tilelink 硬件实例之上的附加层,用于处理 SoC 级别的协调和参数递。它主要基于 Fiber API,它允许创建精化时间纤程(用户空间线程),从而允许确定未来的参数传递/协调和硬件生成。

可以通过 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 中在纤程之间共享值的一种方式。如果一个纤程读取一个句柄,而这个句柄还没有值,它将阻止该纤程的执行,直到另一个纤程向该句柄提供一个值。还有一组属性,类似于 m2s,但是反向的(名为 s2m),它们指定了由互连的从端发起的事务的参数(例如内存一致性)。

有两个演讲介绍了 tilelink.fabric.Node。这两个演讲可能并不完全遵循实际语法,它们仍然遵循以下概念:

• 介绍: https://youtu.be/hVi9xOGuuek

• 深入: https://peertube.f-si.org/videos/watch/bcf49c84-d21d-4571-a73e-96d7eb89e907

顶层示例

以下是一个简单的虚拟 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 idealy 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 finaly 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 usefull 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)

  • 21
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千穹凌帝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值