1. 复习verilog语法

【选做题】

- reg和wire的区别


寄存器数据类型

Verilog中规定,凡是在程序块中被赋值的变量,都必须是寄存器类型的。(程序块:例如always块)

这里未免还是会让人产生疑惑?寄存器数据类型的变量最后一定会被综合成寄存器吗?

对应于实际的数字电路中,如果该程序块描述的是时序逻辑,则该寄存器变量对应为寄存器;如果该程序块描述的是组合逻辑,该寄存器变量对应为硬件逻辑;如果该程序块描述的是不完全组合逻辑,那么该寄存器变量也可以对应为锁存器。由此可见,寄存器类型的变量不一定会综合为寄存器。

线网数据类型

Verilog中规定,模块的input和inout端口必须是线网类型;连续赋值语句的被赋值对象必须是线网类型。对应于实际的数字电路,线网类型实际上就对应着硬件的连线,起到连接作用。

线网数据类型包括wire和tri等,wire最常见,不必多说,很多情况下直接声明为wire即可。

至于tri其实和wire在用法上是一模一样的,不过有时候,我们需要定义一些会被三态门驱动的硬件连线,用tri来命名会让代码更具有可读性,让人一看就知道这根连线上会出现Z状态,仅此而已!

--------------------- 

作者:李锐博恩(Reborn) 

这是我以前写过的博文:【 Verilog HDL 】寄存器数据类型(reg)与线网数据类型(wire,tri)


- 阻塞赋值与非阻塞赋值的区别


1、非阻塞(Non_Blocking)赋值方式(如 b <= a;)

块结束后才完成赋值操作;

b的值并不是立刻就改变;

这是一种比较常用的赋值方法。(特别在编写可综合模块时)

2、阻塞(Blocking)赋值方式(如: b = a;)

赋值语句执行完后,块才结束;

b的值在赋值语句执行完后立刻就改变;

可能产生意想不到的结果。

非阻塞赋值方式和阻塞赋值方式的区别常给设计人员带来问题。问题主要是给“always”块内的reg型信号的赋值方式不易把握。

如果“always”模块中的reg型信号采用非阻塞赋值方式:

b <= a;

这种方式的赋值并不是马上执行的,也就是说“always” 块内的下一条语句执行后,b并不等于a,而是保持原来的值。“always”块结束后,才进行赋值。

如果采用阻塞赋值方式:

b = a;

这种赋值方式是马上执行的。也就是说,执行下一条语句时,b已经等于a了。尽管这种方式看起来直观,但是可能引起麻烦。

下面对两种赋值方式举例分析:

例1:

非阻塞赋值

always @(posedge clk)

begin


    b <= a;

    c <= b;


end

上例中的"always"块中用了非阻塞赋值方式,定义了两个reg型信号b和c,clk信号的上升沿到来时,b就等于a,c就等于b,这里应该用到了两个触发器。请注意:赋值是在"always"块结束后执行的,c应为原来b的值,b为原来的a值。这个"always"块实际描述的电路功能如下图所示:

【Verilog HDL 训练】第 06 天(边沿检测)_赋值

例2:

阻塞赋值

always @(posedge clk)

begin


    b = a;

    c = b;


end

上例中的 "always"块用了阻塞赋值方式。clk信号的上升沿到来时,将发生如下的变化:b马上取a的值,c马上取b的值(即等于a),生成的电路图如下所示只用了一个触发器来寄存器a的值,又输出给b和c。这大概不是设计者的初衷,如果采用[例1]所示的非阻塞赋值方式就可以避免这种错误。

【Verilog HDL 训练】第 06 天(边沿检测)_非阻塞_02

--------------------- 

作者:李锐博恩(Reborn) 

这也是我以前写过的博文:【Verilog HDL】赋值语句之阻塞赋值方式与非阻塞赋值方式

其他博文:【 Verilog HDL 】进一步了解 Verilog HDL 的赋值运算符


- parameter与define的区别


作用域区别:

parameter 作用于声明的那个文件;`define 从编译器读到这条指令开始到编译结束都有效,或者遇到`undef命令使之失效。

如果想让parameter或`define作用于整个项目,可以将如下声明写于单独文件,并用`include让每个文件都包含声明文件:

`ifndef data

`define data  8’d14

或者

parameter data = 8‘d14;

`endif

`define也可以写在编译器最先编译的文件顶部。通常编译器都可以定义编译顺序,或者从最底层模块开始编译。因此写在最底层就可以了。

区别:

parameter可以用作例化时的参数传递。

在使用状态机时候区别挺大的。状态机的定义可以用parameter 定义,但是不推荐使用`define 宏定义的方式,因为'define 宏定义在编译时自动替换整个设计中所定义的宏,而parameter 仅仅定义模块内部的参数,定义的参数不会与模块外的其他状态机混淆。例如一个工程里面有两个module 各包含一个FSM,如果设计时都有IDLE 这一名称的状态,如果使用'define 宏定义就会混淆起来,如果使用parameter 则不会造成任何不良影响。

一旦`define指令被编译,其在整个编译过程中都有效。例如,通过另一个文件中的`define指令,定义的常量可以被其他文件中被调用。直到遇到`undef;parameter只在定义的文件中有效,在其它文件中无效。

这块内容参考:Verilog中parameter和define的区别


- task与function的区别


1.函数可以返回一个值而任务可以返回多个值

2.函数一经调用必须立即执行,里面不能包含任何的时序控制,而task中可以有时序控制

3.函数可以调用函数,但不可以调用任务,任务既可以调用函数也可以调用任务

4.函数必须要有一个输入参数,而任务可以没有参数输入。

4.任务输出的信号,在模块中必须定义为reg信号

参考:verilog 中任务与函数的区别


2. 用verilog实现边沿检测电路:上升沿,下降沿,双沿(上升或下降沿)。


昨天刚刚写了一篇类似的博文,里面用到了边沿检测,边沿检测应用可算是十分广泛呀, 写flash控制器时候也用到了。

使用握手协议的方式处理跨时钟域传输问题

从这篇博文中节选出边沿检测的部分吧:


//req上升沿检测
    reg reqr1, reqr2, reqr3; //定义reqrx代表延迟x拍
    //--------------------------------------------------------
    //第一种方法
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            reqr1 <= 1'b0;
            reqr2 <= 1'b0;
            reqr3 <= 1'b0;
        end
        else begin
            reqr1 <= req;
            reqr2 <= reqr1;
            reqr3 <= reqr2;
        end
    
    end
    
    //pos_req3比pos_req2延后一拍,确保数据被稳定锁存
    wire pos_req1, pos_req2, pos_req3;
    
    assign pos_req1 = ( { req, reqr1 } == 2'b10 ) ? 1'b1 : 1'b0;
    assign pos_req2 = ( { reqr1, reqr2 } == 2'b10 ) ? 1'b1 : 1'b0;
    assign pos_req3 = ( { reqr2, reqr3 } == 2'b10 ) ? 1'b1 : 1'b0;
    //------------------------------------------------------------------
    // 检测上升沿的第二种方法
    // assign pos_req1 = ~reqr1 & req;
    // assign pos_req2 = ~reqr2 & reqr1;
    // assign pos_req3 = ~reqr3 & reqr2;

--------------------- 


下面是仿真图,只看上面用到的变量部分:

【Verilog HDL 训练】第 06 天(边沿检测)_寄存器_03

更多细节​​​​

上面的答案是昨天写的,所以优先附上,但之前也转载过类似的东西,看下之前的:

reg     [1:0]   signal_r;
//-------------------------------------------------------
//
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
signal_r <= 2'b00;
end
else begin
signal_r <= {signal_r[0], signal_in};
end
end

assign singal_posedge = ~signal_r[1] & signal_r[0];//检测上升沿
assign singal_negedge = signal_r[1] & ~signal_r[0];//检测下降沿

其实原理都是一样的。



3. 记录一下第2题中用到的工具,包括工具版本,操作步骤或命令选项,遇到的错误,提示信息等。


工具是:Vivado 2018

操作步骤:无非是建立工程,然后编写Verilog代码,之后编写Testbench文件,然后仿真即可。

遇到的问题:仿真发现,检测到边沿有时候并不能持续一个时钟,这是由于要检测的信号与时钟上升沿之间的位置关系。在应用边沿检测的时候,如果时间不够,可以多延迟几拍,见第二题的代码吧。