跳转至

硬件描述语言的设计和调试

本节中,我们将在 vivado 中创建一个新工程,同时在工程中加入代码,进行基本的仿真和调试。

创建空白工程的方式
  1. 首先打开 Vivado,创建一个新的工程

  2. 简介页面点击 Next,在路径选择页面选择好项目的位置和项目的名称,再点击 Next

  3. 选择 RTL Project,继续 Next

  4. 添加源码界面和添加约束界面,暂时不添加,点击两次 Next 跳过

  5. 在选择器件的界面,选择合适的器件,本课程使用的是 xc7a35tfgg484-2。在搜索框进行搜索后,选择添加。

  6. 总结页面中确认 RTL-project 和 fpga 型号,单击 finish 完成

这样我们就完成了一个 vivado 空白工程的创建了。

加入代码

本节我们将要实现的是一个 4 位 2 进制信号到两个无驱动数码管的译码。下面将给出一个有错误的实现。

本节中将会使用这份错误的代码,带大家完成仿真和硬件描述代码的调试。

向工程中加入以下代码,将 hex_to_seg7.sv 设置为顶层项目:

hex_to_seg7.sv

`timescale 1ns / 1ps
module hex_to_seg7 (
    input wire[3:0] hex_i,
    output wire[3:0] seg7_high_o,
    output wire[6:0] seg7_low_o
);


wire [3:0] dec_high;
wire [3:0] dec_low;

hex_to_dec htd (
    .hex_i(hex_i),
    .dec_high_o(dec_high),
    .dec_low_o(dec_low)
);

seg7 s7h (
    .dec_i(dec_high),
    .seg7_o(seg7_high_o)
);

seg7 s7l (
    .dec_i(dec_low),
    .seg7_o(seg7_low_o)
);

endmodule

seg7.sv

`timescale 1ns / 1ps
module seg7 (
   input wire[3:0] dec_i,
   output reg[6:0] seg7_o
);

always_comb begin
   if(dec_i == 4'h0) seg7_o = 7'b0111111;
   if(dec_i == 4'h1) seg7_o = 7'b0001001;
   if(dec_i == 4'h2) seg7_o = 7'b1011110;
   if(dec_i == 4'h3) seg7_o = 7'b1011011;
   if(dec_i == 4'h4) seg7_o = 7'b1101001;
   if(dec_i == 4'h5) seg7_o = 7'b1110011;
   if(dec_i == 4'h6) seg7_o = 7'b1110111;
   if(dec_i == 4'h7) seg7_o = 7'b0011001;
   if(dec_i == 4'h8) seg7_o = 7'b1111111;
   if(dec_i == 4'h9) seg7_o = 7'b1111001;
end


endmodule

hex_to_dec.sv

`timescale 1ns / 1ps
module hex_to_dec (
   input wire[3:0] hex_i,
   output reg[3:0] dec_high_o,
   output reg[3:0] dec_low_o
);

always_comb begin
   if(hex_i >= 4'd10) begin
      dec_high_o = 4'h1;
   end else begin
      dec_high_o = 4'h0;
   end

   if(dec_high_o == 4'h1) begin
      dec_low_o = hex_i - 4'b10;
   end else begin
      dec_low_o = hex_i;
   end
end


endmodule
加入代码的方式
  1. 单击源码窗口上的加号,进入添加源码界面

  2. 选择加入设计源码,点击 Next

  3. Add Files 可以添加已有的文件,Add Directories 可以添加已有的文件夹,Create File 可以创立新文件。这里选择 Create File

  4. 在第一个下拉框中选择 System Verilog,填写文件名,单击 OK

  5. 回到刚才的界面,单击 Finish 即可完成文件的添加,你也可以选择同时多添加几个文件。

  6. 点击 Finish 后会弹出窗口要你设计新文件对应模块的输入和输出,直接忽略,点击 OK 即可。

  7. 添加完成后稍等片刻,文件就会出现在左侧的源码栏中了,这时可以打开文件,开始编写。

设置顶层文件的方法

顶层文件就是你最后要生成电路对应的代码的根。所有在顶层文件中出现的代码才会在综合时生成电路。

设置文件为顶层文件的方法很简单,右键点击要设置成顶层文件的文件,选择 Set as Top 即可

设置完成后,该文件的文件名会加粗显示,表示这是顶层文件。

设计仿真

在开始编写仿真代码之前,先来了解一下仿真的含义和目的。

仿真,顾名思义,就是要模拟真实的情况。Vivado 可以根据你的仿真代码和你的硬件代码构建一个电路模型,同时在仿真文件中,你可以改变提供给电路的输入。从而可以在 Vivado 的模拟器中完成真实电路的模拟,同时尝试各种不同输入信号的组合,完成电路的验证。

因此,在设计仿真之前,我们先要给出一部分输入以及对应的期望输出。这有点像软件中的设计测例,但又不完全一样。

在本节的设计目标 "4 位 2 进制信号到两个无驱动数码管的译码" 中,输入只有 16 种,因此我们可以遍历这 16 种输入。注意在以后的复杂实验当中,遍历输入需要的时间可能会很长,同时复杂的电路模型的仿真会很慢。因此设计一个小但是覆盖情况全的测例可能会很重要。

作为例子,我们给出这样的仿真输入信号设计:

有 1 个计数器,每隔 500 ns 自动加 1,大于等于 16 时减去 16,使输入信号在 0~15 中循环出现。

在 system verilog 中,为了理解和软件处理起来的便捷性,仿真模型也被抽象成了一个没有输入输出的 module,按照时间顺序执行。

因此,我们首先向工程中加入仿真代码。与加入设计代码类似,点击加号,但这次我们选择加入仿真源码。

其余操作与添加源码类似,我们创建一个新的名为 tb 的 system verilog 文件用来仿真。

添加完成后,加入如下代码:

tb.sv

`timescale 1ns / 1ps
module tb;

integer i = 0;

reg [3:0] hex_i;
wire [6:0] seg7_h_o;
wire [6:0] seg7_l_o;

initial begin
   forever begin
      hex_i = i;
      #500;
      i = i + 1;
      if(i >= 16) i = i - 16;
   end
end

hex_to_seg7 hts (
   .hex_i(hex_i),
   .seg7_high_o(seg7_h_o),
   .seg7_low_o(seg7_l_o)
);

endmodule

下面将按行解释代码的功能:

第 1 行:timescale 宏用于指定仿真的精度和时间单位,除非特殊需要,否则这块不应该做改动,而且应该出现在每个源码文件的第一行。

第 2 行:module tb; 这句话定义了一个没有输入输出的模块,也即 module 后的输入输出列表可以省略。

第 4 行:integer 可以用于在 system verilog 中定义变量,注意不要在设计文件的非模板参数中使用(对于现在来讲,就是只能在仿真中使用),因为这个不能生成对应的电路(即不可综合)。

第 6~8 行:定义了后续需要使用的中间信号。

第 10 行:initial 块,功能与名称类似,为在仿真开始时执行的代码,注意该块不可综合。

第 11 行:forever 块,用于定义一个无限循环执行的块,注意该块不可综合。

第 12~15 行:实现刚刚所说的每过 500 ns 加 1 的功能,这块编写代码的逻辑与 c++ 类似。# 代表延迟,#500; 即让时间经过 500 个时间单位,也即第一行中定义的 ns。

第 18~22 行:例化我们将要测试的模块并接线。

同学们也可以自己尝试其他的仿真用功能,如 for 循环,function 函数等等功能。

在加入代码之后,设置 tb.sv 为 仿真源码 的顶层文件

这之后就可以点击左侧的 Run Simulation -> Run Behavioral Simulation 开始仿真了。

仿真调试

进入仿真后的界面如下:

右侧为波形显示界面,左侧可以选择信号加入到右侧的列表当中。

初始只能看到 tb.sv 中定义的信号。如果想要加入其他的信号要现在最左侧选择对应的模块,选中后再在中间的那一列中选择信号拖到最右边加入查看列表。比如,我想查看 hex_to_dec.sv 中的 dec_high_o,操作方式如下图:

注意,新加入的信号不能够直接查看,要点击最上方的重新仿真之后,才能够显示,如下图:

同时,由于现在的仿真时间还不够,只有 1000ns,我们需要继续仿真,看完所有的波形。单击最上方的“播放”继续仿真:

仿真一定时间后,点击“暂停”,暂停仿真。

点击波形图上方的缩小放大镜,缩小波形图的比例调整到合适的位置。

观察仿真波形:

  1. Z 为高阻态,在仿真中表示对应信号线没有人驱动这跟导线,这在 seg_7_h_o 这个输出中很明显不应该存在。
  2. seg_7_l_o 低位输出中,在输入为 a~f 时的输出与 8~9 时相同,这很明显也不正确。

我们首先要定位这个 Z 的来源,因此向上进行溯源。

seg7_h_o 接到 hex_to_seg7seg_7_high_o 输出端上;该信号由 seg7 s7h 这个实例的 seg7_o 输出端驱动。

找到对应的信号,加入波形图,重新仿真,如下图:

可以发现只有 seg7_h_oZ,因此判断 hex_to_seg7seg_7_high_o 输出端有问题。

转到代码,发现 output 的位宽设置错误,应为 6:0

需要观察该错误信号的来源,依次添加至波形图中:

  • hex_to_seg7seg_7_low_o 输出端
  • seg7 s7lseg7_o 输出端
  • seg7 s7ldec_i 输入端
  • hex_to_decdec_low_o 输出端
  • hex_to_dechex_i 输入端

重新仿真,结果如下:

发现错误最早出现在 hex_to_decdec_low_o 输出端。查看 hex_to_dec 逻辑,发现减法的减数的进制错误,应为 4'd10 而不是 4'b10,修改即可。

修改完上述两个错误之后,重新运行仿真,仿真波形正确,如下图:

这个时候,仿真通过,就可以在配置完约束之后,综合,实现,上板检查实验结果是否正确了。

上板错误调试

在上板之后,我们发现实验现象并不正确,7 段数码管的输出非常混乱,说明我们的设计仍有错误。

在发生这种仿真通过,但是上板异常的问题的时候,一般是综合期间出现了问题,需要观察综合器报出的警告。

【关闭仿真,完成综合后,】点击下方的 Messages 查看综合信息:

勾选对应的信息分级可以调整显示信息的级别。

我们看到有一条 Warning 为:

[Synth 8-327] inferring latch for variable 'seg7_o_reg' ["D:/Hardware/DebugSample/DebugSample.srcs/sources_1/new/seg7.sv":8]

这意味着我们在编写代码时,这个信号对应的 always_comb 块没有覆盖到所有可能的情况,导致生成了不稳定的锁存器。

点击链接,转到对应代码,发现 7 段数码管的驱动没有覆盖 a~f 的情况,也没将默认输出设为 X,导致出现问题。

修改后重新综合上板即可,此时实验现象正确,我们完成了一份硬件代码的仿真的调试。

小结

  1. 仿真代码的编写

    • forever

    • initial

    • integer

    • #

  2. 硬件设计流程

    1. 设计硬件逻辑框架

    2. 编写代码

    3. 设计仿真

    4. 修改设计通过仿真

    5. 综合

    6. 检查综合 Message

    7. 上板检查实验结果

  3. Vivado 行为仿真的使用

  4. 简单调试方法

    • 仿真溯源,改正错误信号

    • 查看综合 Message,检查 latch


最后更新: 2023年3月30日
作者:cuibst (98.66%), Jiajie Chen (1.34%)