SpinalHDL(四) 一窥总线四两拨千斤

SpinalHDL(四) 一窥总线四两拨千斤

Spinal的lib.bus除了AMBA总线,并且还支持AvalonMM,当然你也很容易扩展自定义的总线。

Apb3总线例化

一个简单的Apb3总线接口例化示例

1
2
3
4
5
6
7
import spinal.lib.bus.amba3.apb._
class T2 extends Component{
val busin = slave(Apb3(Apb3Config(12,32)))
val busout = master(Apb3(Apb3Config(12,32)))
busout <> busin
}
SpinalVerilog(new T2)

总线的例化也非方便简单,`Apb3`
传入Apb3Config(addrWidth=12,dataWidth=32)即可生成一个总线,关键字slave,master设计非常漂亮,可以瞬间
指定总线内部的接口方向,除此之外,`\<>`
操作符为SpinalHDL定义的总线自动互联函数,不用你手动一个一个连。像这类操作符SpinalHDL有很多,像\”>>\”
\”>-/->\”, \”\</\<\”
等等,不用害怕,这些只不过是定义的一个恰巧叫这个符号的函数而已,本质上跟你定义的autoConnect名字的函数并无二致,但是它要比文字函数要形象的多,并且假装操作符也是那么的自然。除了spinalHDL定义的操作符以外,你自己也可以定义各种各样简便的符号帮助你完成复杂琐碎的事情。
41
可以看到生成的Verilog总线已经完全连上了。

Apb3总线译码

Apb3Decoder是一个工厂函数,它支持多种参数的传入

  • 一:传入总线配置,和地址Mapping
1
2
3
4
Apb3Decoder(Apb3Config(16,32),
List((0x0000,2 KiB),
(0x1000,1 KiB),
(0x5000,3 KiB))))

可以得到
42

  • 二:传入master实例,和slave映射实例
1
2
3
4
5
6
7
8
9
10
11
12
class Top extends Component{
val m0 = slave(Apb3(Apb3Config(16,32)))
val s1 = master(Apb3(Apb3Config( 8,32)))
val s2 = master(Apb3(Apb3Config(12,32)))
val s3 = master(Apb3(Apb3Config(12,32)))
val s4 = master(Apb3(Apb3Config( 2,32)))

val mux = Apb3Decoder(master = m0,
slaves = List(s1 -> (0x0000, 64 ),
s2 -> (0x1000,1 KiB),
s3 -> (0x2000,3 KiB),
s4 -> (0x3000, 32 )))

得到:

43

Axi-crossbar

Spinal pinsec的总线拓扑结构
44

源码:
45

这么复杂一个链接关系,在SpinalHDL上不到50行代码,相信一般的伪代码都做不到这么简洁。因此阅读Spinal的源码简直就是一种享受,有时候我在恍惚这是文档还是代码。

想想一下,如果这个工作让你用Verilog实现,那是多么的操蛋。当然目前cross-bar都是由脚本或者ARM的工具来自动生成。但是对于外挂,互联,顶层的例化
还得要手动做,这同样是一件极其繁琐容易出错的工作。但是现在放在Scala上是一个极其简单的伪代码式的描述即可完成。除此之外还可以做一些高级的检查,
参数的协商,DAG(有向无环图)的检查都很容易办到。

总线转换

AHB总线到APB总线的转换示例

1
2
3
4
5
6
7
8
9
10
import spinal.lib.bus.amba3.apb._
import spinal.lib.bus.amba3.ahblite._

class Top(ahbConfig:AhbLite3Config, apbConfig:Apb3Config) extends Component{
val ahb = slave(AhbLite3(ahbConfig))
val apb = master(Apb3(apbConfig))
val bridge = AhbLite3ToApb3Bridge(ahbConfig,apbConfig)
ahb <> bridge.io.ahb
apb <> bridge.io.apb
}

AXI到APB3总线转换示例

1
2
3
4
5
6
7
8
9
10
import spinal.lib.bus.amba3.apb._
import spinal.lib.bus.amba4.axi._

class Top(aw: Int,dw: Int,iw: Int) extends Component{
val axi = slave(Axi4Shared(Axi4Config(aw,dw,iw)))
val apb = master(Apb3(Apb3Config(aw,dw,iw)))
val bridge = Axi4SharedToApb3Bridge(aw,dw,iw)
axi <> bridge.io.axi
apb <> bridge.io.apb
}

以下是pinsec中的源码,创建一个AXI到APB3的桥,然后从桥可以互联到其他外围。

1
2
3
4
5
6
7
8
9
10
val apbDecoder = Apb3Decoder(
master = apbBridge.io.apb,
slaves = List(
gpioACtrl.io.apb -> (0x00000, 4 KiB),
gpioBCtrl.io.apb -> (0x01000, 4 KiB),
uartCtrl.io.apb -> (0x10000, 4 KiB),
timerCtrl.io.apb -> (0x20000, 4 KiB),
vgaCtrl.io.apb -> (0x30000, 4 KiB),
core.io.debugBus -> (0xF0000, 4 KiB))
)

可以非常方便的将这些外围连起来:
46
以上的代码绝大多数都能直接运行起来,能用如此简单寥寥几行就将原来复杂繁琐的工作安全可靠的完成,还有什么理由不使用它。

评论