文章目录
- 1. EDA工具对代码的处理与输出
- 2. System Verilog Coding Guide
-
- 2.1 状态机【Design】
- 2.2 Behavorial Verilog再到Always模块【Design】
- 2.3 便捷写法【Design】
- 2.4 变量运算【Both DV】
- 2.5 Verilog Stratified Event Queue【Verification】
- 2.6 Fork methods【Verification】
- 2.7 Assertion【Verification】
- 2.8 import package & `include【Both DV】
- 2.9 Random Testing【Verification】
- 2.10 Fuction & Task【Verification】
- 2.11 Coding for Synthesis【Design】
- 3. Extension
接触硬件描述语言(HDL)也有几个年头了,由于之后research会偏向Architecture,做偏软件的活,算是走入一个新的阶段,因此想写一篇关于SV的笔记进行总结复习。选择SV的原因在于它目前是业界主流。SV是Verilog的继承扩展版本,类似于Cpp和C的关系,扩展内容可以分为Declaration Enhancement(多了变量类型),和Programming Enhancement(一些写法的shortcut,硬件行为描述的支持,运算符,directive等等),具体参见下图,Coding会在Part 2详细说明。总之sv对design和verification的支持都很显著,本文会挑个人觉得比较有用的点去记,所以这不是一个入门的指导,我会跳过蛮多基础的东西,Bear in mind!!!。
1. EDA工具对代码的处理与输出
在讲Coding前,还是稍微skim一下它的工作步骤,只想看sv的Coding Guide的话直接跳到Part 2,这里不细说底层的运行,因为这与不同EDA工具和FAB的lib有关系 (例如说Synopsy和Cadence的综合程序算法就不大一样),目前还没有能力和兴趣去探究,主要还是说一些已经standardized的东西。我们使用HDL的最终目的在于生成可靠可知的IC layout,我们将layout及其设计步骤抽象为代码,人写完代码后借由一系列Computer Aided Design(CAD)工具(在EE领域我们特指它为EDA)再将代码转变为layout的输出文件。因为我们需要其可靠可知,所以设计流程中的一些中间产物也是很重要的。因此这里将对HDL代码的处理分步为:Compile, Simulation和Synthesis。对应着不同类的EDA工具对应的功能目的和输出结果。
PS: HDL可服务于不同类型的DIC实现:Full Custom, Semi-Custom和Programmable。这更分化了不同的EDA功用,但是大道至简,殊途同归,后文主要还是讲general的部分和一些经验tips方便回忆
1.1 Compile
第一步是编译,实际上对于这一名词不同工具我们也能看到不同的结果。比如说对于vivado或者dc shell这类需要后续制作netlist等的软件,当我们输入filelist进行编译后除了进行syntax analysis外,还会附带生成对应的RTL Schematic,也就是说我们可以看到电路的hierarchy,甚至有些情况Compile就直接是Synthesis操作本身。而对于仿真器而言,Compile的含义很多只代指对与各个文件的代码分析,当要去执行Simulation的时候才会load各个模块生成Schematic和对应的Hierarchy。
这里仿真软件举的例子为Modelsim(vsim这个软件的组分也是比较多的,包括compiler, linter, simulator, waveform visioner等,支持GUI或者TCL对于各个模块的调用)。它在compile worklib后点击View–Schematic依旧是空的,在Run Simulation过后可以在Sim窗口中看到目标top module 的Hierarchy结构还有schematic。而Dc shell和vivado等可以直接set top module并查看这些东西。Anyway,在这里我还是想将对RTL代码Compile的概念统一描述,就当是Syntax Analyze 和 Linter,进行纠错并整合信息方便后面的程序进行处理。Compile是可以分辨一些directives(主要应用于Syn),挑出基本的组分。此外,关于Tb层面的compile,默认的timescale为1ns/xx(时间单位/仿真精度)。还有package和#include之于compile的区别,会在Part 2提及,Makefile的特性,我们重复compile不需要update没有更改的成员。在编写code的时候利用文本插件(推荐Vim或者VSC等,能装插件就行)的辅助可以很快通过第一次compile(减少很多syntax层面的typo)。
1.2 Simulation
当然在通过1.1的Compile后我们只是独自对各个file进行检查,想要把DUT作为一个整体,还需要各个模块进行完整的interconnection。因此当我们在Modelsim跑某个testbench时,即便Compile全部PASS,也会出现例如组件名字mismatch,端口连接mismatch或不能识别某个名字等的情况。总之,针对DUT部分,我们需要将它作为电路进行编写并进行严格的连接,这些组件都能在Simulation后的hierarchy中单独显示,合理地划分组件和功能能更方便Debug,SV对于Simulation有非常多额外的支持。
这里标记一些用Simulation Tool的小tips。
- 首先跑Sim后,代码和各组件建立了映射关系,因此我们可以通过看代码添加所需的信号,也可以邮件代码中的一些信号trace driver或reference,这个对于debug一些陌生的代码很有好处,可以快速地了解信号,寄存器之间的关系,反之亦然-go to source。
- 在写代码的时候,除了一些需要用parameters customize Hardware,尽量添加信号位宽的标注可以方便Debug发现忘记添加变量声明的情况。或者说直接添加以下代码取消掉SV对default type的设置。
`default_nettype none
这样的话如果我们不小心用了未声明的变量就会直接报错,而非默认为logic[0:0],在一些现有的代码中,有人习惯直接用未声明的变量做interconnection,个人认为这不是个好习惯,尽管说这些变量不具备其他的功能,但标注出来对debug而言会更方便。
-
添加Divider在waveform中可以方便区分不同module的信号,也可以在preference设置代码名称的path深度来简化信号来源的描述。信号的默认名字一般就是main/sub/signal name。我一般习惯设置max path长度为2~3,一般来讲记性越差,工程越大,所需要的path长度更大吧。一次性拉了过多的信号的话,可以通过set property改变wave的颜色使重要成员显著
-
做self check的时候可以多使用 $stop or $fatal(“log”)并fork timeout thread来做debug,step by step的debug是我们所喜欢的方式
fork
//Error occurs when pwr_up is never asserted
begin: timeout
repeat(30000) @(posedge clk);
$fatal("Timeout for waiting pwr_up");
end: timeout1
//If pwr_up is asserted, disable timeout
begin
@(posedge iDUT.iAuth.pwr_up);
disable timeout; //Once pwr_up is asserted, disable timeout and keep testing
end
join
- $strobe $display $monitor 在event-driven simulation下发生在哪个queue呢(时刻),参见Stratified Event Queue,它详细描述了Simulation的运行规范,这里就记住strobe和monitor能发生在非阻塞赋值后就行。
- $stop $finish stop类似于断点,可以continue。finish类似于terminate当前thread,在GUI中它通常会问我们finish后要不要exit。Debug还是stop用的比较多
- class 中的变量无法生成add到wave,我们需要将它转为interface才可以拉出来看到波形。这不太方便,因此要注意信号尽量例化或连接到外面。
- 后仿真需要注意精度的问题,会对结果产生出入。而且对Netlist仿真有时候就很玄学,应该和Lib有关系,个人看法还是图个乐呵。除此之外尽量也不要依靠内部信号,很容易出现X value,内部信号名很多也会被优化或者处理掉,尤其是synthesize flatten的情况,DUT变成了一层。如果一定要refer一个内部信号,可以在综合时加入一下代码,或者一些软件可以识别RTL代码中的dont touch,下面一些代码是dc shell一些代码是vivado的,使用的时候参见其TCL文档即可。
# in scripts
set_dont_touch [find pin main/sub/sig_name]
set_property DONT_TOUCH TRUE [~]
set_property MARK_DEBUG TRUE [[get_nets –of [get_pins hier1/hier2/<flop_name>/Q]]]
# or in the RTL codes
(*DONT_TOUCH = "TRUE"*) wire xxx...
(* MARK_DEBUG = "{TRUE|FALSE}" *) logic yyy...
1.3 Synthesis
这里只讲Code->Netlist这一步也就是,后续Netlist->layout的APR部分不在此列,以DC shell为例子。在Compile并进行完成前仿后,我们将RTL代码mapping到对应库的门级网标中,这一步我们实际上无论是Semi-Custom,Full Custom抑或是FPGA,都是必要的,因为从Code到Gate List是比较外层的由抽象到具象的程序,到了具体的implementation再分化。但无论如何,Netlist所依赖的Lib各不相同,对于Full Custom而言,那就不是单纯依赖Fab提供的Lib了,而是需要更多人工的设计。以DC shell为例子的话,Synthesis在以下方面可以额外注意下,基础共通部分我不赘述,随便看一个完整的脚本代码再参照资料就可以理解:
- Cost Function = -Slack + Area + power 此为综合trade off的标准,往往不可兼得
- Timing Constraints: 就是数电的基础时序约束,Setup和Hold。Tsu+Td+Tco+Tuncertainty < Tclk; Tco + Td-Tuncertainty > Thold。Tuncertainty来源于Skew也就是Clock Tree的不均衡
- 一般在DC shell中我们不会touch clk。会在CTS(Clock Tree Syn)中单独去解决它。
- Input delay 和 Output delay一般默认是同步的环境,可当作Td的一部分,针对异步的模块我们一般手动去设置,不对它纳入时序分析的整体和优化。
set_multicycle_path 2 -setup -from [find pin Main/Sub/ptch_*_reg*/CLK]
set_multicycle_path 2 -setup -from [find pin Main/Sub/AZ*_reg*/CLK]
set_clock_groups -name async_clk0_clk1 -asynchronous -group {clk0 usrclk itfclk} \
-group {clk1 gtclkrx gtclktx} ...
- drive以及Capacitive load决定了门的延时,可以参照那个Ids的方程,信号的传输就是经历一个又一个transition,对应的Slope自然就一定上决定了速度。举个例子,Fanout过大drive弱,因此消耗的delay也就多,我们一般也会加以约束,添加buffer,只不过说这又要增加Area了,正好也对应了1的tradeoff
- 多次综合的优势:我们之前说过compile定义的区分,对于综合工具而言,Compile有时可以定义为综合操作本身,这一点可以在对应脚本命令中看出,map effort越高,所用以fix timing的area消耗越大。这里我们统一描述为综合Syn,但是tcl还是照常。多次综合可以把Netlist做Flatten,取缔掉原版设计的hierarchy,尽力去服用Gate本身。也能添加一些额外的约束到Netlist以便于重新组织,例如说之前提到的asynchronous和hold time fix。示例代码如下:
compile -map_effort high
set_multicycle_path 2 ...
ungroup -all -flatten
set_fix_hold clk
compile -map_effort medium
- 还有一个小tip,有些人会告诉你GUI is just children’s work,cmd mode才是硬核。然而我个人的看法是工具不分高低贵贱,好用就行,至少我认为一些GUI的terminal要比直接调用的shell>要好用多了(我说的就是Synopsy!)。而且GUI兼顾脚本的运行,又能够方便调用到一些直观的功能,比如说hierarchy和schematic。所以以偏概全地跟风并不好,自己实践可以得到更优的选择吧,大概!
2. System Verilog Coding Guide
扯了那么多有的没的,终于到正题了,也就是Coding的部分,如前文所说,SV是Verilog的扩展,因此这个PART的内容也会夹杂Verilog本身的东西,只挑选我认为很重要或者比较有意思的内容。开头我先列一点优秀的文字资料,虽说coding是一门实践的技术,但就我的感觉来看,实践过后回归理论,能够很有效的补全知识漏洞。
- IEEE SV官方指南:IEEE 1800-2017 SV官方链接 有些东西网上搜的不太明白可以查一查。
- 2013 SNUG会议文章 “Synthesizing systemverilog busting the myth that systemverilog is only for verification”,关于System Verilog的使用,例子很丰富,讲解很详细:PDF link
- 2000 SNUG会议文章 “Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!”,Verilog的Coding Guideline:PDF link
2.1 状态机【Design】
从结构上来看,数电状态机就是时序逻辑+组合逻辑形成的环路,最简单的状态机例子就是计数器,counter即是它的状态,一般来讲寄存器保有current state,Comb的输出决定next state。Moore SM和Mealy SM的区别就在于状态机输出的构成,Moore的输出只由current state决定,比如说计数器的counter,告之当前时刻。而Mealy的输出还由外部输入决定,比如说给计数器输出添加一个mask,这样就可以选定时间的scale,从输出性质来讲,Moore为同步,Mealy可能为异步,如果外部输入的是同步信号,那其实输出的也就同步了。
从功能上来看,