verilog_移位寄存器_仿真(程序逐句解释)

verilog_移位寄存器_仿真(程序逐句解释)前言  之前老是想着学的快点,就直接编译了程序就下载在开发板上跑,后来发现这样不行,因为如果程序有问题,验证和纠错的时间成本太高了(毕竟vivado跑一次花的时间很长),反过来学习仿真,下面是一点心得和体会。开发环境编译软件及版本:vivado2019.2编译语言:verilog  网上随便找了一个简单程序和仿真,先实现复现,再谈其他。下面我将先给出代码和仿真截图,再说具体的东西。移位寄存器程序代码:`timescale1ns/1ps/////////////////////////

大家好,又见面了,我是你们的朋友全栈君。

前言

  之前老是想着学的快点,就直接编译了程序就下载在开发板上跑,后来发现这样不行,因为如果程序有问题,验证和纠错的时间成本太高了(毕竟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


每过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账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号