实验二 加法器设计与仿真
Verilog 是一种用于描述、设计电路的 硬件描述语言 HDL (Hardware Description Language) , 在实验一,我们已经尝试在 logisim 中设计并绘制电路图,并通过输入输出测试电路的功能。 本次实验我们使用 Verilog 的各种基础语法完成一些电路设计,并通过仿真代码和波形图测试电路的功能。
为此,我们需要使用 Verilog 仿真器,常见的仿真器有很多:VCS 、modelsim 、Verilog-XL 、iverilog 、verilator 等。 考虑到后续实验我们将使用 Xilinx 的 FPGA 教学实验板,为此我们直接使用 Vivado 的 xsim 仿真器,能够在一体式软件中完成整个实验的流程。
本次实验课我们使用 Verilog HDL 设计加法器等一些简单的电路,并编写 Testbench 程序, 使用 Vivado 的仿真器测试我们设计的电路。
Verilog 代码编写环境
工欲善其事,必先利其器。 一个好的代码 Coding 环境能极大的帮助你编写代码。
Full Adder 全加器
我们给出一种 Full Adder 全加器的 Verilog 实现方式参考:
1module ref_fa (
2 a // <<i<<
3 ,b // <<i<<
4 ,cin // <<i<<
5 ,sum // >>o>>
6 ,cout // >>o>>
7);
8 input a, b, cin;
9 output sum, cout;
10 wire a, b, cin;
11 wire sum, cout;
12 assign {cout, sum} = a + b + cin;
13endmodule
加法器的代码实现
加法器的 RTL 实现方式有很多,行波进位加法器、选择进位加法器、超前进位加法器、进位旁路加法器等。 当然还有上面参考代码的朴实无华的 + 号运算符实现的加法器,你觉得哪种代码编写的实现更好呢,这个问题开放答案,欢迎留下你的想法。
第一个 Testbench 程序
Verilog 代码设计完成后,还需要进行重要的步骤,即逻辑功能仿真,仿真激励文件称之为 testbench, 就像你之前使用 Logisim 设计完电路之后,需要你输入信号,观察输出,检查你的电路设计功能是否符合你的要求。
我们从一个全加器的简单 Testbench 入手,编写你自己的第一个 Testbench 程序。
1`timescale 1ns/1ps
2module ref_tb ();
3 reg [2:0] in;
4 wire sum, cout;
5
6 ref_fa u_ref_fa (
7 .a (in[0]) // <<i<<
8 ,.b (in[1]) // <<i<<
9 ,.cin (in[2]) // <<i<<
10 ,.sum (sum) // >>o>>
11 ,.cout (cout) // >>o>>
12 );
13
14 initial begin
15 in = 3'b0;
16 #100;
17 for (integer i = 0; i < 8; i = i + 1) begin
18 in = in + 1;
19 #100;
20 end
21 $stop;
22 end
23
24endmodule
相信你理解这个程序并不会感到困难,在 Logisim 中,你可以把一个画布中的电路引出输入输出口,封装成模块,然后在另一个画布中放置这个模块。
其中高亮的一大段,代表对 ref_fa 进行实例化,即在另一个模块中使用这个模块,然后将这个模块的信号与
对应的信号相连。 .a 代表 ref_fa 中的 a 端口,后面括号中的 in[0] 是 ref_tb 中的信号,这样就完成了相连的操作。
Testbench 中一定会使用到 `timescale [timeunit]/[timeprecision] 。用来表示仿真时间的基本单位和时间精度。
#100 也就是延迟 100个时间单位之后,再接着执行后面的语句。而时间精度代表最小的仿真尺度,对于 `timescale 1ns/1ps 你写 #1.1111 ,
则延迟 1.1111 ns,但由于 0.0001 是 0.1 ps,时间精度没这么高,因此会四舍五入变成延迟 1.111 ns。
$stop 系统任务则是将仿真暂停,有点像是 Debug 打断点,你运行就会发现箭头指向 $stop ,仿真停下来了,可以手动继续运行仿真。
然后我们对 in[2:0] 信号进行驱动,也就是对它进行赋值操作,我们直接使用 for 循环进行遍历所有的输入情况,然后你可以对比 所有的输入是否会得到正确的输出,即可完成 Testbench 仿真测试。
Vivado 使用教程
Vivado 是 Xilinx 公司的 FPGA 集成设计环境,本次实验我们将使用 Vivado 对 Verilog 设计进行仿真。 我们推荐使用 Vivado 2018.3 ,安装 WebPACK 版本。 该版本安装体积约 20 GB,而近几年的版本安装体积已经约 100 GB,WebPACK 是免费的,不需要许可证的版本,并且已经能够满足我们的实验需求。 如果你此前已经安装 2015 年之后的版本,都是能够满足完成实验的。Vivado 不同的版本之间功能差异也比较小,在学习和使用上也不会造成困难。
创建 adder 工程
打开 Vivado 软件,会来到 Vivado 软件初始界面
Vivado 软件很复杂,我们一点点来了解它。创建一个名为 adder 的新项目,并保存在合适的位置, 一定要是全英文的目录 。
勾选 Create project subdirectory 则会在保存新项目的地方创建一个项目名称的文件夹,用于存放项目的文件。
你也可以手动创建一个项目名称的文件夹,作为保存项目的位置,就不勾选该选项了。
来到器件选择页面, Family 系列选择 Artix-7 , Package 封装方式选择 fgg484 ,
然后选择 xc7a100tfgg484 ,完成器件选择,其余的步骤直接下一步即可。
完成创建项目,来到该项目初始页面,本次实验内容我们只需要关心红色方框标记出来的区域。
左侧是 Flow Navigator 流程导航,我们完整的实验整个流程就是依次从上往下进行的。
左上角是 项目管理 ,目前由于正处于项目管理界面,因此 PROJECT MANAGER 是蓝色的。
完成了加法器和 Testbench 程序的编写,我们就可以进行仿真验证了。
首先需要将编写好的源代码添加到工程中,我们可以通过这两个地方添加源文件。
有三种类型的源文件,如下图所示,有 design source 设计文件、 simulation source 仿真文件和 constraints 约束文件。
设计文件就是我们描述的电路,仿真文件就是 Testbench,约束文件下一次课才会使用。
依次添加设计文件和约束文件,添加完成之后,会自动更新源代码的结构,如下图所示。
源文件自动更新了顶层文件,如果你想修改顶层文件,可以对源文件右键 Set as Top 修改为顶层文件。点击行为仿真,即可进行仿真操作。
打开仿真界面后,我们可以看到仿真产生的信号波形,如下图所示。
序号1的播放键按钮用于运行仿真,序号2的按钮用于重启仿真。
序号3的方框可以选择模块,序号4的方框可以添加模块中的信号显示波形。
序号5的方框用以显示信号波形,序号6的两个放大镜按钮可以放大和缩小波形显示范围,序号7的按钮将显示完整的仿真波形信号。
观察波形,每个输入信号是否都对应着正确的输出信号。
如果你有 $display 或者 $monitor 等函数,输出内容会显示在 Vivado 下方的 Tcl Console 中。
Carry-look-ahead 超前进位加法器
超前进位加法器是一种进位链延迟更短的加法器,我们已经在理论课上学习了4位超前进位加法器的原理。
4位超前进位加法器
4位超前进位加法器的实现
按照逻辑公式或者电路,完成4位超前进位加法器的代码实现。 下面给出了代码框架,在代码框架的基础上完成代码的编写。
1module cla_4bit(a, b, cin, sum, cout);
2
3 input a, b, cin;
4 output sum, cout;
5
6 wire [3:0] a, b, sum;
7 wire cin, cout;
8
9 // Your codes should start from here.
10
11 // End of your codes.
12
13endmodule
全加器你写好了,你很自信,这真的还需要测试吗?4位超前进位加法器你也写好了,这个也不难,只需要非常的细心。 那这个需要写 Testbench 测试吗,你现在有信心认为代码一定是正确的吗?
那该怎么写呢,还是和参考的 Testbench 一样吗,不过这次的信号数量特别多,真值表一共有512列,我需要依次去看 512次的波形,然后检查是否符合预期吗,这看起来很蠢 : (
一种 Testbench 测试思路
我们能否改造一下 ref_fa ,其使用 + 号运算符,看起来会得到正确的结果。那我们能否同时测试我们写的4位超前进位加法器模块 和改造好的4位 ref_fa 模块,每次对比一下输出的值是否相同。如果结果不相同则说明4位超前进位加法器模块有问题, 我们把参考机称为 ref (Reference) ,而我们的待测电路称为 dut (Device Under Test) ,原理如下图所示。 可以打印一句提示信息,这样就不用我们去一个个看波形了。打印信息可以使用 $display() 函数,Vivado 会将信息显示在下方的 Tcl Console 中。 使用方法很像 printf 函数,具体可以 STFW。
测试4位超前进位加法器
你编写的这个4位超前进位加法器后续会用于组成更大位宽的加法器, 以及之后的实验中,请你好好测试你的代码,不要留下Bug。 编写一个 Testbench 用于测试你的4位超前进位加法器, 命名规则最好类似于 tb_cla_4bit ,这样很清晰的能够看得出这是用于测试什么模块的激励文件。
层次化超前进位加法器
理论课上已经讲过层次化超前进位加法器,这样可以显著提升加法器的性能,但是随着加法器位宽的提升,进位的计算会花费指数级增加的电路开销。 因此对于32位、64位等更大位宽的加法器,可以将低位宽的加法器块之间用行波进位等方式连接。
16位层次化超前进位加法器
我们可以改造一下之前的4位超前进位加法器代码,将 generate 进位生成信号 g 和 propagate 进位传递信号 p 输出, 给第二级超前进位电路使用,组成16位层次化超前进位加法器。下面给出了代码框架。
1module cla_16bit(a, b, cin, sum, cout);
2
3 input a, b, cin;
4 output sum, cout;
5
6 wire [15:0] a, b, sum;
7 wire cin, cout;
8
9 // Your codes should start from here.
10
11 // End of your codes.
12
13endmodule
选择进位加法器
选择进位加法器可以由3个16位加法器组成,低16位加法计算不变,另外两个加法器对高16位进行计算。一个进位假设为0,另一个假设为1, 最后由低16位加法器实际计算出来的进位值输入 2-1 多路选通器(多路复用器),得到高16位的结果。这样可以有效减少了进位传播延迟。 这种设计与超前进位加法器相比,所需的电路数量并非指数级增长,而是大约多花50%的电路开销,也是一种常见的加法器设计方法,可以用于组成位宽很大的加法器。
设计32位层次化选择进位加法器
按照上图所示的选择进位加法器结构,将16位层次化超前进位加法器作为模块, 使用选择进位的方式,完成最终的32位的加法器模块。
验证16位、32位加法器
电路复杂度又上升了,你不验证还有信心保证你的电路一定是正确的吗? 那么问题又来了,怎么验证呢?对于64位的加法器验证,难道将所有的情况都穷举,然后与64位的 ref_fa 比较结果吗? 我已经算不清输入有多少种情况了, 2^64 * 2^64 * 2 种情况,即便是使用无比强大的计算机仿真, 也不能轻松搞定,这看起来真的很蠢。
另一种 Testbench 测试思路
在其他编程语言中有生成随机数的函数, Verilog 也不例外,我们可以利用 $random 这个函数帮我们生成一些随机数, 帮助我们随机测试一些样例,然后循环10000次,或者100000次,或者更多。此外,对于一些特殊的情况,我们还可以手动地去测试, 以确保我们的电路符合要求。比如输入全0,全1;除法器我们可能需要对除0进行测试等。
1initial begin
2 for (integer i = 0; i < 100000; i = i + 1) begin
3 a = $random;
4 b = $random;
5 cin = $random;
6
7 #100;
8
9 if ((ref_sum != dut_sum) || (ref_cout != dut_cout)) begin
10 $display("Print tests failed information");
11 $stop; // 如果有错误暂停仿真
12 end
13 end
14end
有错误怎么去 Debug
更重要的可能是有错误怎么去 Debug,但毕竟这次加法器的原理和结构并不难。只要你仔仔细细对照公式或者电路, 就能得到正确的功能。但 Bug 是在所难免的,那么遇到错误了,怎么去 Debug 呢。 我们的改造的 ref_fa 可以认为是参考机、标准答案,当然前提是你设计正确。那么与参考机不同的输出结果就认为是有问题的, 这时候我们就可以查看我们打印的错误信息,或者查看错误的波形,手动验算一下,核对哪些信号有问题,就去检查相应的代码。
假如你觉得信号数量太多了,根本无从下手,对于你搭建的32位层次化选择进位加法器,我们建议你先把16位层次化超前进位加法器模块验证正确, 毕竟选择进位加法器的结构是很简单的,而16位层次化超前进位加法器模块稍微复杂一点。你直接验证32位层次化选择进位加法器, 可能没办法定位出问题出在32位的选择进位加法器结构,还是16位层次化超前进位加法器结构,甚至是4位的超前进位加法器。 所以好的办法当然是写一个模块验证一个,写一点代码验证一点,这样可以快速定位问题,也可能更早的暴露代码的Bug, 而不是写了1000行代码了,再去验证。 因此我们16位层次化超前进位加法器、32位层次化选择进位加法器并没有显式地让你设计完就做验证,是希望你能够一开始就养成好的习惯,自己设计好后主动去验证所有的代码模块, 而不是最后只能求助于老师、助教或者同学。