SpinalHDL(九):不变应万变-MemWrap适配

对于IC开发工程师我们可能都有这样的痛苦经历,经常需要手动为不同工艺库替换Mem。有些团队的MemWrapper可能是有专门的脚本来生成,内部会用宏来区分不同工艺,相对来说还比较省事一点,即便这样MemWrpper的集成也是需要你手动连线。但是如果你是IP提供商,不同的团队或者公司的MemWrapper的信号命名也各不相同。同样替换Mem是一件极其无聊并且容易出错的事情。

1

另外我发现SpinalHDL自带的mem类型并不适合直接拿来做例化,所以需要为此创建一个新的解决方案来满足IC设计中这种常见的需求。spinal的mem模型我会放到Blackbox内部做为行为模型,这样在仿真的时候你可以clearBox来使用mem参考模型来仿真,不需要真正的去包含一个memwrap.v文件,这样在前期设计中非常方便。

因此对于设计者来讲,我本来只需要关心这地方例化的Mem类型(双口,真双口,单口),数据位宽,mem深度即可。除此之外工艺相关的事情对设计者应该是透明的。

可以总结为两点诉求

  • MemWrap替换不应修改HDL代码, 对开发者透明
  • 添加新的Vendor memWrap不应修改Spinal源码,保持前向兼容

2

因此,对于开发者来讲,只需要知道统一的RAM例化接口,为此为Spinal设计常见的3种MEM类型.

SpinalHDL(八):开发仿真测试一条龙

鉴于被chisel的peek poke test愚弄以后,我一度对Scala上的仿真环境不是特别有信心,当看到spinal.sim的时候觉得可能只是一个能work的demo而已,并没有足够重视。

我用Spinal开发的前几个模块依然走的传统的验证流程,先生成Verilog,然后再用scala生成激励,最后打包丢到Linux服务器上, 用Verilog/SV编写tesbech, 搭建测试比对环境,仿真工具不是VCS就是NcSim这些老牌商业软件。

直到一个偶然的机会,有一些很零碎的模块需要测试,不想再为此搭建一整套仿真环境,我想干脆用spinal.sim 简单做个测试,不测不知道,一测一发不可收拾。

SpinalHDL就是这样的汉子,一次又一次的轻视它,但它又不时的给你惊喜。

  • 不同于iotest的peak poke, 它的后台是verilator, verilator非常强大,性能比起商业软件VCS、NC毫不逊色。
  • spinal.sim 通过包装verilator提供的VPI,能够很方便灵活,实时的跟dut交互通信, 加上Scala本身语言的灵活性,它能做的不比UVM少。

今年chisel貌似后知后觉的回过神了,摒弃iotest,重新设计iotester2, iotester2和spinal.sim的思路很类似了, 后台是verilator, 有兴趣的可以自己试试

接下来我主要借助通信基带的两个模块来介绍一些如何使用SpinalHDL进行仿真测试,以及如何构建组织管理回归你的测试CASE。

对于spinal.sim的基础操作和使用请浏览官方文档Spinal-sim,这里不再赘述。

Spinal-Bootcamp 在线新手教程

最近花了点时间基于Jupyter-notebook做了一个SpinalHDL教程.
方便立即运行查看一个Spinal用法和对应生成的verilog代码,大多数例子来源于SpinalHDL的官方文档,也新增了一些实例和Scala的高级用法,这些可以帮助你理解SpinalHDL如何工作。

目前可以直接点击Binder运行,国内访问Binder速度有点慢,也可以
clone到本地运行。

为什么做这个事情

  • 提供一个SpinalHDL的在线环境,方便测试功能用法,直接查看verilog,而不用新建工程
  • 提供一个Scala的在线环境,方便你测试一些Scala的用法

spinal-bootcamp

SpinalHDL(七):逢山开路-寄存器接口

目前大量存在手写寄存器接口的情况,对于几十个甚至几百个寄存器的IP,即便你有体力,也不能保证不犯错。
70

存在的问题

  • 纯体力活,工作量繁重
  • 容易引入错误,增加验证工作量
  • 文档和代码不同步,增加软件调试工作量

在这类问题上耗费测试debug时间非常不值当,只要你手写寄存器必然会遇到这了问题,软件调了好半天,最后发现文档跟代码不一致,
人是懒惰的,谁能完全保证代码更文档能同步更新,也肯定会犯错的,所以只能通过验证和测试来覆盖,如果问题能够越早发现,成本越低

这里分享二张图:

一张是某软件产品在各个阶段bug的成本
71

另外一张是IC开发各个阶段bug的成本
72

所以尽可能的在前期解决问题,第一次把事情做对,就是节约成本。

SpinalHDL(六):定点化-饱和截位

首先为什么要定点化?
在性能可接受的范围内尽可能的的压缩数据bit位宽以便节省资源
一个扰码识别的定点流程
61
灰色的全精度
蓝色是定点截位以后的位宽,对于这个算法,定点以后在大大节省资源的情况下性能几乎没有任何损失。
我们在定点的时候不是说每一个节点都需要压缩bit,而是在关键的节点尽可能压缩位宽才能做到事半功倍,
比如以上例子中的求平方,和最后的累计和输出,这两个点对硬件来说非常敏感,一个是乘法器,另外一个是缓存MEM,是面积的开销大户,
所以我们在定点的时候要特别有意去照顾这些节点,在性能可接受的情况下,能省1bit是1bit

定点介绍(低截高饱)

一般我们会分为两步来操作

  1. 低位Round操作

62
低位的Round操作有非常多类型。参见
https://en.wikipedia.org/wiki/Rounding

SpinalHDL(五):时钟和时钟域

SpinalHDL的时钟和时钟域设计一开始就设计的相对比较完整,Chisel起初只能支持同步复位,这个在工程上带来了很多麻烦,最近chisel3好像可以支持异步复位。
例化方式非常别扭,我更喜欢SpinalHDL的清晰直观。非常重要的一个区别写SpinalHDL你很清楚哪些是硬件哪些是软件,而chisel就特别模糊,充斥着大量的软件思维,去了解他们各自开发者的背景也就不难理解。
所以一个合格的产品设计者,首先是一个合格的用户。

几个简单的示例

  1. 默认时钟
1
2
3
4
class Top extends Component{
val a = in Bits(8 bits)
val b = RegNext(a) init 0
}

默认会得到一个clk,reset时钟复位信号,并且SpinalHDL复位默认是上升沿

1
2
3
4
5
6
7
8
9
10
11
12
13
module Top (
input [7:0] a,
input clk,
input reset);
reg [7:0] b;
always @ (posedge clk or posedge reset) begin
if (reset) begin
b <= (8'b00000000);
end else begin
b <= a;
end
end
endmodule
  1. 自定义时钟源

如果我们不喜欢默认是时钟复位名称,可以自己修改时钟信号名,通过ClockDomain来创建一个时钟域(其实spinal的Component里隐含了一个叫
clockDomain变量的默认domain)

1
2
3
4
5
6
val myclk,myrst = in Bool()
val myClockDomain = ClockDomain(myclk,myrst)
new ClockingArea(myClockDomain){
val reg0 = RegNext(a) init 0
b := reg0
}

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

SpinalHDL(三) 一行代码生成Soc系统

如何生成一个Soc系统

(注:Soc系统本身是一个比较笼统的概念,有各种各样不同复杂程度的SOC系统,我们这里指的Soc是一个最小系统,带处理器,片上Mem,一些基本外设以及总线拓扑的基础框架)

在Spinal生成一个最小Soc系统只需要一行代码

1
2
import spinal.lib.soc.pinsec._
SpinalVerilog(new Pinsec(500 MHz))

即可生成一个完整的SOC系统,其中一个RISCV的处理器VexRiscv, 和AXI总线路由
以及APB桥。
pinsec-soc

没了,就这么屌

trump-no-more

如果需要定制,那么继续往下

如何定制Soc系统

定制CPU

1
2
3
4
5
6
7
8
9
10
11
12
13
14
val myCpuConfig = RiscvCoreConfig(
pcWidth = 32,
addrWidth = 32,
startAddress = 0x00000000,
regFileReadyKind = sync,
branchPrediction = dynamic,
bypassExecute0 = true,
bypassExecute1 = true,
bypassWriteBack = true,
bypassWriteBackBuffer = true,
collapseBubble = false,
fastFetchCmdPcCalculation = true,
dynamicBranchPredictorCacheSizeLog2 = 7
)

插件式扩展CPU, 除了已有的扩展,你可以添加自己的扩展, 比如浮点,矢量处理

1
2
3
4
5
myCpuConfig.add(new MulExtension)
myCpuConfig.add(new DivExtension)
myCpuConfig.add(new BarrelShifterFullExtension)
myCpuConfig.add(new MyFloatExtension)
myCpuConfig.add(new MyVectorExtension)

SpinalHDL(一):此CHISEL非彼Chisel

这里有两个概念

  • CHISEL泛指

基于Scala的硬件构筑语言(Constructing Hardware In Scala Embedded
Language)

Scala-hdl

包括chisel和SpinalHDL, 所以标题中为什么需要CHISEL是指为什么需要一个基于Scala的HDL语言,实际上chisel和SpinalHDL称为HDL框架更为合适,因为除了一些电路语法外,绝大多数都是在运用Scala的语言功能,一切强大都源于Scala语法。

  • Chisel特指

伯克利大学发布的Chisel硬件开发语言

我们文章中大CHISEL为泛指, 小Chisel为特指。

Verilog不够用吗?

需要一门新的语言吗?

VHDL诞生于1982年 ,Verilog诞生于1981年, 起初是用来电路存档描述,
都是硬件描述语言, 是用来描述数字电路的结构,行为,功能和接口的语言。

虽然Verilog/VHDL简单易用,在一定的历史时期确实是一个效率的巨大提升,
但是目前来看,槽点依然很多,
已经有点落伍时代。即便是SystermVerilog一定程度上改善了它 存在的问题,
并没有完全解决Verilog的问题,况且EDA工具对SV的支持并不是很积极,所以依然是尴尬的存在。

  • 例化不方便

    有人会说,有辅助插件帮你完成 (确实有很多好的插件,emacs verilog-mode
    , vim 的autoinst) 即便这样,但是对带参数的模块例化,
    一对多例化同样需要手动处理,非常不方便

  • 大量的重复声明

    无休止的变量声明,无休止的位宽声明,容易出错,
    作为一门上古时期的语言,对编译器不能要求太高

  • 函数不能带参数

    verilog中函数的使用只能是零零星星,哪怕是一个位宽的变化都要重写函数,
    作为一门语言函数不能广泛使用,实为鸡肋

  • 参数化实在是笨拙

    虽然支持参数化,parameter 也只能做一些简单的加减左移操作,
    没有基本math包。

    利用宏做参数化,对于变量比较多的设计,非常复杂,并且也不好维护
    目前我也看到

  • 错误检测很弱

    编译工具对错误的处理比较保守,
    这种保守可能也源于语言本身,以及编译器的能力不及。

    • 位宽不匹配,
    • input/output端口写反
    • 饱和截位弄错,
    • 跨时钟域问题
    • 锁存器检查
    • 组合逻辑环自己查
    • ….

    基于前仿的编译,还会遗漏大量的错误,必须要Lint, 综合检查,
    费时费力又费钱。

  • 重构、增减信号,Bist/DFT逻辑插入麻烦

    需要手动插入, 编写脚本, 即便是脚本也不通用

等等…