大家好,又见面了,我是你们的朋友全栈君。
前言
之前老是想着学的快点,就直接编译了程序就下载在开发板上跑,后来发现这样不行,因为如果程序有问题,验证和纠错的时间成本太高了(毕竟vivado跑一次花的时间很长),反过来学习仿真,下面是一点心得和体会。
开发环境
编译软件及版本:vivado 2019.2
编译语言:verilog
网上随便找了一个简单程序和仿真,先实现复现,再谈其他。下面我将先给出代码和仿真截图,再说具体的东西。
移位寄存器程序代码:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/10/16 19:42:58
// Design Name:
// Module Name: shift_register
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module shift_register(
input clock,
input reset,
input load,
input [1:0] sel,
input [4:0] data,
output [4:0] shiftreg
);
reg [4:0] shiftreg;
always @(posedge clock)
begin
if(reset)
shiftreg = 0;
else if(load)
shiftreg = data;
else
case(sel)
2'b00 : shiftreg = shiftreg;
2'b01 : shiftreg = shiftreg << 1;
2'b10 : shiftreg = shiftreg >> 1;
default : shiftreg = shiftreg;
endcase
end
endmodule
移位寄存器的testbench代码:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/10/17 11:00:37
// Design Name:
// Module Name: shift_register_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module shift_register_tb(
); //declare testbench name
reg clock;
reg load;
reg reset; // declaration of signals
wire [4:0] shiftreg;
reg [4:0] data;
reg [1:0] sel;
// instantiation of the shift_reg design below
shift_register dut(
.clock (clock),
.load (load),
.reset (reset),
.shiftreg (shiftreg),
.data (data),
.sel (sel)
);
//this process block sets up the free running clock
initial
begin
clock = 0;
forever #50 clock = ~clock;
end
// this process block specifies the stimulus.
initial
begin
reset = 1;
data = 5'b00000;
load = 0;
sel = 2'b00;
#200
reset = 0;
load = 1;
#200
data = 5'b00001;
#100
sel = 2'b01;
load = 0;
#200
sel = 2'b10;
#1000
$stop;
end
initial // this process block pipes the ASCII results to the terminal or text editor
begin
$timeformat(-9,1,"ns",12);
$display(" Time Clk Rst Ld SftRg Data Sel");
$monitor("%t %b %b %b %b %b %b", $realtime,clock, reset, load, shiftreg, data, sel);
end
endmodule
仿真截图:
正文
先说说为什么要写这个博客,首先是vivado更新了,用的vivado 2019.2,其次是有些地方感觉别的博主写的不详细,还有就是自己的积累,怕以后忘了。我就详详细细地写一次,一句一句地写,让更多初学者不惧怕或者说不讨厌去读代码。
我自己看别人的代码,如果格式不规整,我就看的很难受,可能有点强迫症吧。规整的代码给人清清爽爽的感觉,一目了然。我刚开始学,一句一句地代码都自己去查意思,觉得很花时间,下面我就系统一点地标注每一句代码,节约大家学习时间和成本。
我自己最开始很不习惯,编硬件描述语言需要编两份,除了主文件,还要写一个testbench???这是什么鬼???其实可以这么理解,因为verilog是硬件描述语言,所以我们编的module这个模块,相当于是实现某种功能,但我们进行仿真的时候,是需要给这个模块信号的,也就是常说的激励。testbench相当于编的是激励,是信号,然后加载在module这个模块上,最后才能验证我们编的module对不对。verilog是用来描述硬件的。
这里理解了以后,下面我开始讲代码的含义。
移位寄存器程序代码逐句讲解:
`timescale 1ns / 1ps
timescale表示模块的时间精度;
1ns就是下面程序模块的仿真时间单位是1ns,1ps的意思是仿真时间精度是1ps。
module shift_register(
input clock,
input reset,
input load,
input [1:0] sel,
input [4:0] data,
output [4:0] shiftreg
);
这里表示建立了一个移位寄存器模块;
它的输入有clock、reset、load、sel([1:0]代表sel这个变量是两位的(0~1))、
data([4:0]表示data这个变量是5位的(0~4)),
输出有shiftreg([4:0]表示shiftreg这个变量是5位的(0~4))。
reg [4:0] shiftreg;
reg是寄存器,reg [4:0]shiftreg表示了一组寄存器。
always @(posedge clock)
begin
if(reset)
shiftreg = 0;
else if(load)
shiftreg = data;
else
case(sel)
2'b00 : shiftreg = shiftreg;
2'b01 : shiftreg = shiftreg << 1;
2'b10 : shiftreg = shiftreg >> 1;
default : shiftreg = shiftreg;
endcase
end
always是一个过程块,当敏感信号表达式的值改变的时候,就执行一遍块内语句;
注意:always过程块是不能嵌套使用的;
always @(<敏感信号表达式>)
begin
块内语句
end
begin和end相当于c/c++里面的{};
posedge clock是指上升沿触发,相当于每次上升沿时,always过程块执行一次;
关键字posedge和negedge分别是上升沿和下降沿;
eg.同步时序电路的时钟信号为clk,clear为异步清零信号;
敏感信号可写为:
always @(posedge clk or posedge clear)
上升沿触发,高电平清0有效;
always @(posedge clk or negedge clear)
上升沿触发,低电平清0有效;
if(...)
...
else if(...)
...
else
...
if(条件1)
如果条件1为真,执行这里;(条件为真才执行)
else if(条件2)
否则,当条件2为真执行这里;(当条件1不为真,条件2为真时执行这里)
else
否则,执行这里。(条件1,条件2都不为真,执行这里)
if(reset)
shiftreg = 0;
如果reset(复位信号)为1时,将寄存器全部置0;
else if(load)
shiftreg = data;
如果load(加载信号)为1时,将data(数据)的值赋给shiftreg(寄存器);
else
case(sel)
2'b00 : shiftreg = shiftreg;
2'b01 : shiftreg = shiftreg << 1;
2'b10 : shiftreg = shiftreg >> 1;
default : shiftreg = shiftreg;
endcase
否则,当sel(选择)的值为0时,寄存器值不变;
当sel(选择)的值为1时,寄存器值左移一位;
当sel(选择)的值为2时,寄存器值右移一位;
default(默认值)是寄存器值不变;
2'b10
<位宽><进制><数字>
2表示这个数的位宽是2(位宽指的是时间所占位数);这里初学者就简单地理解成位数就行;
b表示二进制;
10表示十进制的2;
移位寄存器testbench程序代码逐句讲解:
module shift_register_tb(
); //declare testbench name
reg clock;
reg load;
reg reset; // declaration of signals
wire [4:0] shiftreg;
reg [4:0] data;
reg [1:0] sel;
这里定义了一个叫shift_register_tb的模块;
在testbench(测试程序)中,
逻辑设计中的输入信号在这里对应reg型变量;
逻辑设计中的输出信号在这里对应wire型变量;
module shift_register(
input clock,
input reset,
input load,
input [1:0] sel,
input [4:0] data,
output [4:0] shiftreg
);
这里我们可以对比之前的输入输出定义;
input的信号(或者说变量)都在testbench中变成了reg型;
output的信号(或者说变量)都在testbench中变成了wire型;
shift_register dut(
.clock (clock),
.load (load),
.reset (reset),
.shiftreg (shiftreg),
.data (data),
.sel (sel)
);
dut(device under test)
这个部分说白了就是把咱们之后要加的激励端口,和咱们之前写的端口连接在一起;
以.clock (clock)为例:
.clock表示原模块(shift_register)的端口,clock表示当前模块shift_register_tb中的端口,
.clock (clock)就表示把两个模块的端口衔接起来;
这在verilog中叫做模块例化,初学者给它看成两个端口衔接的程序就行;
initial
begin
clock = 0;
forever #50 clock = ~clock;
end
testbench中使用initial或者always语句块产生激励,这里使用的是initial;
forever循环语句常用于产生周期性的波形,它只能写在initial块中;
#表示延迟的意思
#50表示延迟50个单位,这里延迟的单位由一开始的timescale 1ns/1ps控制;
timescale 1ns/1ps表示时间单位是1ns,精度是1ps;
这里#50 就表示延迟50ns;
~表示取反
clock = ~clock的意思就是每过50ns,时钟取反一次;
每过50ns,时钟取反一次
initial
begin
reset = 1;
data = 5'b00000;
load = 0;
sel = 2'b00;
#200
reset = 0;
load = 1;
#200
data = 5'b00001;
#100
sel = 2'b01;
load = 0;
#200
sel = 2'b10;
#1000
$stop;
end
这一段连起来的意思就是:
初始时:
reset值为1、data值为00000(二进制)、load值为0、sel值为00(二进制);
过200ns后:
将reset值置为0、load值置为1;
再过200ns(相当于400ns后):
将data的值置为00001(二进制);
再过100ns(相当于500ns后):
将sel的值置为01(二进制)、load置为0;
再过200ns(相当于700ns后):
将sel的值置为10(二进制);
再过1000ns(相当于1700ns后):
激励(程序)停止赋值;
initial // this process block pipes the ASCII results to the terminal or text editor
begin
$timeformat(-9,1,"ns",12);
$display(" Time Clk Rst Ld SftRg Data Sel");
$monitor("%t %b %b %b %b %b %b", $realtime,clock, reset, load, shiftreg, data, sel);
end
$timeformat(-9,1,"ns",12);
$是一种标识符,timeformat是打印的格式设置,类似于C语言的printf();
-9表示ns、1是打印时间值时,小数点后保留1位、
“ns”是在时间值后面打印的一个后缀字符串、
12表示这所有的字符串长度为12(相当于9+1+2),不足长度的在之前补空格;
$display(" Time Clk Rst Ld SftRg Data Sel");
$monitor("%t %b %b %b %b %b %b", $realtime,clock, reset, load, shiftreg, data, sel);
display是让控制台显示出字符串,monitor表示动态监测,具体如下图:
仿真结果
初始时:
reset值为1、data值为00000(二进制)、load值为0、sel值为00(二进制);
200ns时:
reset值为0、data值为00000(二进制)、load值为1、sel值为00(二进制);
400ns时:
reset值为0、data值为00001(二进制)、load值为1、sel值为00(二进制);
500ns时:
reset值为0、data值为00001(二进制)、load值为0、sel值为01(二进制);
550ns时在上升沿:
由于sel值为01,功能是寄存器值左移一位,
所以data的值由00001变成了00010(1左移1位);
650ns时在上升沿:
由于sel值为01,功能是寄存器值左移一位,
所以data的值由00010变成了00100(1再左移1位);
700ns时:
reset值为0、data值为00001(二进制)、load值为0、sel值为10(二进制);
750ns时在上升沿:
由于sel值为10,功能是寄存器值右移一位,
所以data的值由00100变成了00010(1右移1位);
850ns时在上升沿:
由于sel值为10,功能是寄存器值右移一位,
所以data的值由00010变成了00001(1再右移1位);
950ns时在上升沿:
由于sel值为10,功能是寄存器值右移一位,
所以data的值由00001变成了00000(1再右移1位);
1700ns时,仿真程序停止。
谢谢阅读~
参考文献
链接:https://blog.csdn.net/leon_zeng0/article/details/78441871
链接:https://blog.csdn.net/liudongdong19/article/details/80993203
链接:https://blog.csdn.net/qq_38791897/article/details/107870029
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/160233.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...