第二讲:RTL设计与仿真环境搭建

各位同学,咱们今天聊聊RTL设计与仿真环境搭建。说实话,这一讲是整个数字芯片设计流程的基石。你想想看,如果连仿真环境都搭不好,后面做综合、做STA,那不都是空中楼阁吗?

我刚开始带团队的时候,发现很多新人上来就急着写代码,结果仿真环境一塌糊涂,debug花的时间比写代码还多。所以这一讲,咱们把基础打扎实了。

Verilog/VHDL基础回顾——别小看这些基础

先说说Verilog和VHDL。很多人觉得这俩语言太简单,不值得花时间。但我告诉你,恰恰是这些基础的东西,最容易出问题。

我个人习惯用Verilog,因为它在数字芯片设计领域更主流。但VHDL也有它的优势,比如类型检查更严格。不过咱们课程主要用Verilog,毕竟Synopsys工具链对Verilog支持得最好。

这里我列几个容易踩坑的点:

  • 阻塞赋值与非阻塞赋值:这个我见过太多人搞混了。always块里用阻塞赋值(=)描述组合逻辑,用非阻塞赋值(<=)描述时序逻辑。千万别混用,否则仿真结果和综合结果对不上。
  • 敏感列表:组合逻辑的敏感列表一定要把所有输入信号都列全。我曾经有个项目,就因为少写了一个信号,仿真跑得好好的,综合出来功能全错。
  • wire和reg:简单记,assign左边用wire,always块里赋值用reg。但注意,综合出来都是连线,reg不一定是寄存器。

重点提醒:写RTL代码时,脑子里要有电路结构。你不是在写软件,你是在描述硬件。这个思维转变很重要。

使用VCS进行RTL仿真——工具链的第一步

VCS是Synopsys的旗舰仿真器,性能没得说。咱们来看看怎么用它做RTL仿真。

最基本的编译命令是这样的:

vcs -full64 -sverilog -debug_access+all \
    -f filelist.f \
    -l compile.log

解释一下这些选项:

  • -full64:64位模式,处理大设计时不会爆内存
  • -sverilog:支持SystemVerilog语法
  • -debug_access+all:开启全部调试能力,方便后面用Verdi看波形
  • -f filelist.f:文件列表,把所有RTL文件路径写进去

编译完生成可执行文件simv,然后运行:

./simv -l run.log +UVM_TESTNAME=my_test

这里+UVM_TESTNAME是UVM的选项,如果你用UVM的话。不用UVM的话,直接跑就行。

小技巧:我习惯在Makefile里封装这些命令。每次改完代码,make一下就行,省得敲一长串命令。

编写高效的Testbench——别让仿真成为瓶颈

Testbench写得好不好,直接决定你的验证效率。我见过有人写testbench,一个简单的加法器,仿真跑了10分钟还没出结果。为什么?因为testbench写得低效。

高效testbench的几个原则:

  1. 使用时钟和复位生成器:别在每个testbench里都写一遍时钟生成逻辑。封装成一个模块,到处复用。
  2. 任务和函数封装:把常用的操作,比如写寄存器、读数据,封装成task。这样主程序看起来清晰,也容易维护。
  3. 自检查机制:好的testbench应该能自动判断结果对不对,而不是让人盯着波形看。
  4. 随机化测试:用$random或者SystemVerilog的randomize,覆盖更多边界情况。

举个例子,一个简单的testbench框架:

module tb_top;

  reg clk, rst_n;
  reg [7:0] data_in;
  wire [7:0] data_out;

  // 时钟生成
  initial begin
    clk = 0;
    forever #5 clk = ~clk;  // 100MHz时钟
  end

  // 复位
  initial begin
    rst_n = 0;
    #20 rst_n = 1;
  end

  // DUT实例化
  my_design u_dut (
    .clk(clk),
    .rst_n(rst_n),
    .data_in(data_in),
    .data_out(data_out)
  );

  // 测试序列
  initial begin
    // 初始化
    data_in = 0;
    // 等待复位完成
    @(posedge rst_n);
    #10;

    // 测试用例1
    data_in = 8'hA5;
    #10;
    if (data_out !== 8'hA5) $error("Test1 failed!");

    // 测试用例2
    data_in = 8'h5A;
    #10;
    if (data_out !== 8'h5A) $error("Test2 failed!");

    #50;
    $finish;
  end

endmodule

注意:testbench里不要用绝对延时(比如#1000)来等结果。用@(posedge clk)或者wait语句,这样仿真速度更快,而且不受时钟频率影响。

波形调试入门(Verdi)——Debug的利器

Verdi是我最喜欢的调试工具,没有之一。它比VCS自带的DVE好用太多了。

用Verdi看波形,首先得生成波形文件。在testbench里加上:

initial begin
  $fsdbDumpfile("wave.fsdb");
  $fsdbDumpvars(0, tb_top);
end

然后运行仿真,就会生成wave.fsdb文件。用Verdi打开:

verdi -ssf wave.fsdb

Verdi的几个常用功能:

  • nWave窗口:显示波形,可以缩放、测量时间
  • nTrace窗口:显示RTL代码,可以高亮显示信号
  • 原理图窗口:自动生成电路原理图,方便追踪信号路径
  • FSDB Navigator:快速定位信号变化点

我个人调试的流程是这样的:

  1. 先看波形,找到出问题的时间点
  2. 在nTrace里找到对应的RTL代码
  3. 用原理图追踪信号路径,看哪里逻辑不对
  4. 修改代码,重新仿真,对比波形

这个流程看起来简单,但效率很高。我曾经用这个方法,半天就定位了一个困扰团队三天的bug。

调试技巧:Verdi里有个"Active Trace"功能,可以实时显示信号值。把鼠标悬停在信号上,就能看到当前值。这个功能在调试复杂状态机时特别有用。

避坑指南——我踩过的那些坑

最后分享几个我亲身经历过的坑:

  • 仿真和综合结果不一致:多半是阻塞赋值和非阻塞赋值用混了。检查一下always块里的赋值方式。
  • 波形文件太大:如果设计很大,全dump的话文件会爆炸。用$fsdbDumpvars指定dump的层次,或者用$fsdbDumpMDA只dump数组。
  • 仿真跑不动:检查一下是不是testbench里有死循环。我曾经有个同事,forever后面忘了加延时,仿真直接卡死。
  • Verdi打不开波形:检查一下fsdb文件是不是完整。有时候仿真没跑完就中断了,文件不完整。

嗯,这一讲的内容就这些。记住,仿真环境搭建是基本功,别嫌麻烦。基础打好了,后面做综合、做STA才能事半功倍。

下一讲咱们聊聊逻辑综合,到时候见。