硬件描述语言的设计和调试
本节中,我们将在 vivado 中创建一个新工程,同时在工程中加入代码,进行基本的仿真和调试。
创建空白工程的方式
-
首先打开 Vivado,创建一个新的工程
-
简介页面点击 Next,在路径选择页面选择好项目的位置和项目的名称,再点击 Next
-
选择 RTL Project,继续 Next
-
添加源码界面和添加约束界面,暂时不添加,点击两次 Next 跳过
-
在选择器件的界面,选择合适的器件,本课程使用的是 xc7a35tfgg484-2。在搜索框进行搜索后,选择添加。
-
总结页面中确认 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
加入代码的方式
-
单击源码窗口上的加号,进入添加源码界面
-
选择加入设计源码,点击 Next
-
Add Files 可以添加已有的文件,Add Directories 可以添加已有的文件夹,Create File 可以创立新文件。这里选择 Create File
-
在第一个下拉框中选择 System Verilog,填写文件名,单击 OK
-
回到刚才的界面,单击 Finish 即可完成文件的添加,你也可以选择同时多添加几个文件。
-
点击 Finish 后会弹出窗口要你设计新文件对应模块的输入和输出,直接忽略,点击 OK 即可。
-
添加完成后稍等片刻,文件就会出现在左侧的源码栏中了,这时可以打开文件,开始编写。
设置顶层文件的方法
顶层文件就是你最后要生成电路对应的代码的根。所有在顶层文件中出现的代码才会在综合时生成电路。
设置文件为顶层文件的方法很简单,右键点击要设置成顶层文件的文件,选择 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,我们需要继续仿真,看完所有的波形。单击最上方的“播放”继续仿真:
仿真一定时间后,点击“暂停”,暂停仿真。
点击波形图上方的缩小放大镜,缩小波形图的比例调整到合适的位置。
观察仿真波形:
Z
为高阻态,在仿真中表示对应信号线没有人驱动这跟导线,这在 seg_7_h_o 这个输出中很明显不应该存在。seg_7_l_o
低位输出中,在输入为 a~f 时的输出与 8~9 时相同,这很明显也不正确。
我们首先要定位这个 Z 的来源,因此向上进行溯源。
seg7_h_o
接到 hex_to_seg7
的 seg_7_high_o
输出端上;该信号由 seg7
s7h
这个实例的 seg7_o
输出端驱动。
找到对应的信号,加入波形图,重新仿真,如下图:
可以发现只有 seg7_h_o
有 Z
,因此判断 hex_to_seg7
的 seg_7_high_o
输出端有问题。
转到代码,发现 output 的位宽设置错误,应为 6:0
需要观察该错误信号的来源,依次添加至波形图中:
hex_to_seg7
的seg_7_low_o
输出端seg7
s7l
的seg7_o
输出端seg7
s7l
的dec_i
输入端hex_to_dec
的dec_low_o
输出端hex_to_dec
的hex_i
输入端
重新仿真,结果如下:
发现错误最早出现在 hex_to_dec
的 dec_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
,导致出现问题。
修改后重新综合上板即可,此时实验现象正确,我们完成了一份硬件代码的仿真的调试。
小结
-
仿真代码的编写
-
forever
-
initial
-
integer
-
#
-
-
硬件设计流程
-
设计硬件逻辑框架
-
编写代码
-
设计仿真
-
修改设计通过仿真
-
综合
-
检查综合 Message
-
上板检查实验结果
-
-
Vivado 行为仿真的使用
-
简单调试方法
-
仿真溯源,改正错误信号
-
查看综合 Message,检查 latch
-