赞
踩
接触硬件描述语言(HDL)也有几个年头了,由于之后research会偏向Architecture,做偏软件的活,算是走入一个新的阶段,因此想写一篇关于SV的笔记进行总结复习。选择SV的原因在于它目前是业界主流。SV是Verilog的继承扩展版本,类似于Cpp和C的关系,扩展内容可以分为Declaration Enhancement(多了变量类型),和Programming Enhancement(一些写法的shortcut,硬件行为描述的支持,运算符,directive等等),具体参见下图,Coding会在Part 2详细说明。总之sv对design和verification的支持都很显著,本文会挑个人觉得比较有用的点去记,所以这不是一个入门的指导,我会跳过蛮多基础的东西,Bear in mind!!!。
在讲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方便回忆
第一步是编译,实际上对于这一名词不同工具我们也能看到不同的结果。比如说对于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.1的Compile后我们只是独自对各个file进行检查,想要把DUT作为一个整体,还需要各个模块进行完整的interconnection。因此当我们在Modelsim跑某个testbench时,即便Compile全部PASS,也会出现例如组件名字mismatch,端口连接mismatch或不能识别某个名字等的情况。总之,针对DUT部分,我们需要将它作为电路进行编写并进行严格的连接,这些组件都能在Simulation后的hierarchy中单独显示,合理地划分组件和功能能更方便Debug,SV对于Simulation有非常多额外的支持。
这里标记一些用Simulation Tool的小tips。
`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
# 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...
这里只讲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在以下方面可以额外注意下,基础共通部分我不赘述,随便看一个完整的脚本代码再参照资料就可以理解:
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} ...
compile -map_effort high
set_multicycle_path 2 ...
ungroup -all -flatten
set_fix_hold clk
compile -map_effort medium
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。