Verilog部分笔记

1.1 Verilog 教程 | 菜鸟教程

当用 Verilog 设计完成数字模块后进行仿真时,需要在外部添加激励,激励文件叫 testbench。

 

1.4 Verilog 设计方法 | 菜鸟教程

功能仿真(前仿真)

时序仿真(后仿真)

 

2.1 Verilog 基础语法 | 菜鸟教程

用 // 进行单行注释

用 /* 与 */ 进行跨行注释

标识符区分大小写

 

2.2 Verilog 数值表示 | 菜鸟教程

一般直接写数字时,默认为十进制表示

负数表示:

-6'd15  
-15

字符串是由双引号包起来的字符队列,不能多行书写

 

2.3 Verilog 数据类型 | 菜鸟教程

wire [32-1:0] gpio_data;
//等价于
wire [31:0] gpio_data;
  • [bit+: width] : 从起始 bit 位开始递增,位宽为 width。
  • [bit-: width] : 从起始 bit 位开始递减,位宽为 width。
//下面 2 种赋值是等效的
A = data1[31-: 8] ;
A = data1[31:24] ;

//下面 2 种赋值是等效的
B = data1[0+ : 8] ;
B = data1[0:7] ;

对信号重新进行组合成新的向量时,需要借助大括号。例如:

wire [31:0]    temp1, temp2 ;
assign temp1 = {byte1[0][7:0], data1[31:8]};  //数据拼接
assign temp2 = {32{1'b0}};  //赋值32位的数值0

 

2.4 Verilog 表达式 | 菜鸟教程

always块里赋值对象不能是wire型

条件操作符从右往左关联

//自右向左关联,两种写法等价,结果为 B 或 D 或 F
A ? B : C ? D : F ;
A ? B : (C ? D : F) ;

等价操作符包括逻辑相等(==),逻辑不等(!=),全等(===),非全等(!==)。

逻辑相等/不等操作符不能比较 x 或 z,当操作数包含一个 x 或 z,则结果为不确定值。

全等比较时,如果按位比较有相同的 x 或 z,返回结果也可以为 1。

逻辑运算【&&(逻辑与), ||(逻辑或),!(逻辑非)】中,如果一个操作数任意一位为 x 或 z,它等价于 x。

归约操作符包括:归约与(&),归约与非(~&),归约或(|),归约或非(~|),归约异或(^),归约同或(~^)。

归约操作符只有一个操作数,它对这个向量操作数逐位进行操作,最终产生一个 1bit 结果。

A = 4'b1010 ;
&A ;      //结果为 1 & 0 & 1 & 0 = 1'b0,可用来判断变量A是否全1
~|A ;     //结果为 ~(1 | 0 | 1 | 0) = 1'b0, 可用来判断变量A是否为全0
^A ;      //结果为 1 ^ 0 ^ 1 ^ 0 = 1'b0

移位操作符包括左移(<<),右移(>>),算术左移(<<<),算术右移(>>>)。

拼接操作符用大括号 {,} 来表示,用于将多个操作数(向量)拼接成新的操作数(向量),信号间用逗号隔开。

拼接符操作数必须指定位宽,常数的话也需要指定位宽。

A = 4'b1010 ;
B = 1'b1 ;
Y1 = {B, A[3:2], A[0], 4'h3 };  //结果为Y1='b1100_0011
Y2 = {4{B}, 3'd4};  //结果为 Y2=7'b111_1100
Y3 = {32{1'b0}};  //结果为 Y3=32h0,常用作寄存器初始化时匹配位宽的赋初值

 

3.1 Verilog 连续赋值 | 菜鸟教程

wire 型变量只能被赋值一次

全加器

module full_adder1(
    input    Ai, Bi, Ci
    output   So, Co);
 
    assign {Co, So} = Ai + Bi + Ci ;//Co进位输出,So和输出
endmodule

 

3.2 Verilog 时延 | 菜鸟教程

module time_delay_module(
    input   ai, bi,
    output  so_lose, so_get, so_normal);
 
    assign #20      so_lose      = ai & bi ;//计算结果延时20个时间单位赋值,此期间若ai或bi值变化,立即重新计算。
    assign  #5      so_get       = ai & bi ;
    assign          so_normal    = ai & bi ;//so_normal、so_get、so_lose的值依次变化。
endmodule

 

4.2 Verilog 过程赋值 | 菜鸟教程

 非阻塞赋值语句使用小于等于号 <= 作为赋值符

 

4.3 Verilog 时序控制 | 菜鸟教程

事件控制用符号 @ 表示。

关键字 posedge 指信号发生边沿正向跳变,negedge 指信号发生负向边沿跳变,未指明跳变方向时,则 2 种情况的边沿变化都会触发相关事件。

敏感列表:

//带有低有效复位端的D触发器模型
//@(敏感列表)
always @(posedge clk or negedge rstn)    begin      
//always @(posedge clk , negedge rstn)    begin      
//也可以使用逗号陈列多个事件触发
    if(! rstn)begin
        q <= 1'b ;      
    end
    else begin
        q <= d ;
    end
end

组合逻辑输入变量很多时

//@* 或 @(*),表示对语句块中的所有输入变量的变化都是敏感的
always @(*) begin
//always @(a, b, c, d, e, f, g, h, i, j, k, l, m) begin 
//两种写法等价
    assign s = a? b+c : d ? e+f : g ? h+i : j ? k+l : m ;
end

电平敏感:

//使用关键字 wait 来表示电平敏感情况
initial begin
    wait (start_enable) ;      //等待 start 信号
    forever begin
        //start信号使能后,在clk_samp上升沿,对数据进行整合
        @(posedge clk_samp)  ;
        data_buf = {data_if[0], data_if[1]} ;      
    end
end

 

4.4 Verilog 语句块 | 菜鸟教程

并行块中的语句是并行执行的,即便是阻塞形式的赋值。

命名块

`timescale 1ns/1ns
 
module test;
 
    initial begin: runoob   //命名模块名字为runoob,分号不能少
        integer    i ;       //此变量可以通过test.runoob.i 被其他模块使用
        i = 0 ;
        forever begin
            #10 i = i + 10 ;       
        end
    end
 
    reg stop_flag ;
    initial stop_flag = 1'b0 ;
    always begin : detect_stop
        if ( test.runoob.i == 100) begin //i累加10次,即100ns时停止仿真
            $display("Now you can stop the simulation!!!");
            stop_flag = 1'b1 ;
        end
        #10 ;
    end
 
endmodule

disable 可以终止命名块的执行,可以用来从循环中退出、处理错误等。

`timescale 1ns/1ns
 
module test;
 
    initial begin: runoob_d //命名模块名字为runoob_d
        integer    i_d ;
        i_d = 0 ;
        while(i_d<=100) begin: runoob_d2
            # 10 ;
            if (i_d >= 50) begin       //累加5次停止累加
                disable runoob_d3.clk_gen ;//stop 外部block: clk_gen
                disable runoob_d2 ;       //stop 当前block: runoob_d2
            end
            i_d = i_d + 10 ;
        end
    end
 
    reg clk ;
    initial begin: runoob_d3
        while (1) begin: clk_gen  //时钟产生模块
            clk=1 ;      #10 ;
            clk=0 ;      #10 ;
        end
    end
 
endmodule

disable 在 always 或 forever 块中使用时只能退出当前回合,下一次语句还是会在 always 或 forever 中执行。因为 always 块和 forever 块是一直执行的。

 

4.6 Verilog 多路分支语句 | 菜鸟教程

条件选项可以有多个,不仅限于 condition1、condition2 等,而且这些条件选项不要求互斥。

 

当多个条件选项下需要执行相同的语句时,多个条件选项可以用逗号分开,放在同一个语句块的候选项中。

case(sel)
    2'b00:   sout_t = p0 ;
    2'b01:   sout_t = p1 ;
    2'b10:   sout_t = p2 ;
    2'b11:     sout_t = p3 ;
    2'bx0, 2'bx1, 2'bxz, 2'bxx, 2'b0x, 2'b1x, 2'bzx :
        sout_t = 2'bxx ;
    2'bz0, 2'bz1, 2'bzz, 2'b0z, 2'b1z :
        sout_t = 2'bzz ;
    default:  $display("Unexpected input control!!!");
endcase

casex、 casez 语句是 case 语句的变形,用来表示条件选项中的无关项。

casex 用 "x" 来表示无关值,casez 用问号 "?" 来表示无关值。

module mux4to1(
    input [3:0]     sel ,
    input [1:0]     p0 ,
    input [1:0]     p1 ,
    input [1:0]     p2 ,
    input [1:0]     p3 ,
    output [1:0]    sout);
 
    reg [1:0]     sout_t ;
    always @(*)
        casez(sel)
            4'b???1:     sout_t = p0 ;
            4'b??1?:     sout_t = p1 ;
            4'b?1??:     sout_t = p2 ;
            4'b1???:     sout_t = p3 ;  
        default:         sout_t = 2'b0 ;
    endcase
    assign      sout = sout_t ;
 
endmodule

 

4.7 Verilog 循环语句 | 菜鸟教程

Verilog中没有i++和i--

// for 循环语句
integer      i ;
reg [3:0]    counter2 ;
initial begin
    counter2 = 'b0 ;
    for (i=0; i<=10; i=i+1) begin
        #10 ;
        counter2 = counter2 + 1'b1 ;
    end
end

repeat 的功能是执行固定次数的循环。repeat 循环的次数必须是一个常量、变量或信号。如果循环次数是变量信号,则循环次数是开始执行 repeat 循环时变量信号的值。即便执行期间,循环次数代表的变量信号值发生了变化,repeat 执行次数也不会改变。

// repeat 循环语句
reg [3:0]    counter3 ;
initial begin
    counter3 = 'b0 ;
    repeat (11) begin  //重复11次
        #10 ;
        counter3 = counter3 + 1'b1 ;
    end
end

forever 相当于 while(1) 。

通常,forever 循环是和时序控制结构配合使用的。

reg          clk ;
initial begin
    clk       = 0 ;
    forever begin
        clk = ~clk ;
        #5 ;
    end
end

assign(过程赋值操作)与 deassign (取消过程赋值操作)表示第一类过程连续赋值语句。赋值对象只能是寄存器或寄存器组,而不能是 wire 型变量。

一个带复位端的 D 触发器的两种写法:

一般写法:

module dff_normal(
    input       rstn,
    input       clk,
    input       D,
    output reg  Q
 );

    always @(posedge clk or negedge rstn) begin
        if(!rstn) begin   //Q = 0 after reset effective
            Q <= 1'b0 ;
        end
        else begin
            Q <= D ;       //Q = D at posedge of clock
        end
    end

endmodule

用 assign 与 deassign 改写:

module dff_assign(
    input       rstn,
    input       clk,
    input       D,
    output reg  Q
 );
 
    always @(posedge clk) begin
        Q <= D ;       //Q = D at posedge of clock
    end
 
    always @(negedge rstn) begin
        if(!rstn) begin
            assign Q = 1'b0 ; //change Q value when reset effective
        end
        else begin        //cancel the Q value overlay,
            deassign Q ;  //and Q remains 0-value until the coming of clock posedge
        end
    end
 
endmodule

force (强制赋值操作)与 release(取消强制赋值)表示第二类过程连续赋值语句。

赋值对象可以是 reg 型变量,也可以是 wire 型变量。

当 force 作用在寄存器上时,寄存器当前值被覆盖;release 时该寄存器值将继续保留强制赋值时的值。之后,该寄存器的值可以被原有的过程赋值语句改变。

当 force 作用在线网上时,线网值也会被强制赋值。但是,一旦 release 该线网型变量,其值马上变为原有的驱动值。

 

5.1 Verilog 模块与端口 | 菜鸟教程

结构建模方式有 3 类描述语句: Gate(门级)例化语句,UDP (用户定义原语)例化语句和 module (模块) 例化语句。

模块格式:

module module_name 
#(parameter_list)
(port_list) ;
              Declarations_and_Statements ;
endmodule

端口类型有 3 种: 输入(input),输出(output)和双向端口(inout)。

input、inout 类型不能声明为 reg 数据类型,因为 reg 类型是用于保存数值的,而输入端口只能反映与其相连的外部信号的变化,不能保存这些信号的值。

output 可以声明为 wire 或 reg 数据类型。

在 Verilog 中,端口隐式的声明为 wire 型变量,即当端口具有 wire 属性时,不用再次声明端口类型为 wire 型。但是,当端口有 reg 属性时,则 reg 声明不可省略。

以下 2 种写法等效:

module pad(
    input        DIN, OEN ,
    input [1:0]  PULL ,
    inout        PAD ,
    output reg   DOUT
    );
 
module pad(
    input        DIN, OEN ,
    input [1:0]  PULL ,
    inout        PAD ,
    output       DOUT
    );
 
    reg        DOUT ;

包含有 inout 端口类型的 pad 模型:

module pad(
    //DIN, pad driver when pad configured as output
    //OEN, pad direction(1-input, o-output)
    input        DIN, OEN ,
    //pull function (00,01-dispull, 10-pulldown, 11-pullup)
    input [1:0]  PULL ,
    inout        PAD ,
    //pad load when pad configured as input
    output reg   DOUT
    );
 
    //input:(not effect pad external input logic), output: DIN->PAD
    assign       PAD = OEN? 'bz : DIN ;//'bz,高阻态
 
    //input:(PAD->DOUT)
    always @(*) begin
        if (OEN == 1) begin //input
            DOUT   = PAD ;
        end
        else begin
            DOUT   = 'bz ;
        end
    end
 
    //use tristate gate in Verilog to realize pull up/down function
    bufif1  puller(PAD, PULL[0], PULL[1]);
    //PULL = 2'b10:启用下拉,PAD被拉低到GND。
    //PULL = 2'b11:启用上拉,PAD被拉高到VDD。
 
endmodule

 

5.2 Verilog 模块例化 | 菜鸟教程

命名端口连接

这种方法将需要实例化的模块端口与外部信号按照其名字进行连接,端口顺序随意,可以与引用 module 的声明端口顺序不一致。

//实例化一次 1bit 全加器
full_adder1  u_adder0(
    .Ai     (a[0]),
    .Bi     (b[0]),
    .Ci     (c==1'b1 ? 1'b0 : 1'b1),
    .So     (so_bit0),
    .Co     (co_temp[0]));

如果某些输出端口并不需要在外部连接,例化时 可以悬空不连接,甚至删除。一般来说,input 端口在例化时不能删除,否则编译报错,output 端口在例化时可以删除。

//output 端口 Co 悬空
full_adder1  u_adder0(
    .Ai     (a[0]),
    .Bi     (b[0]),
    .Ci     (c==1'b1 ? 1'b0 : 1'b1),
    .So     (so_bit0),
    .Co     ());
 
//output 端口 Co 删除
full_adder1  u_adder0(
    .Ai     (a[0]),
    .Bi     (b[0]),
    .Ci     (c==1'b1 ? 1'b0 : 1'b1),
    .So     (so_bit0));

端口连接规则

输入端口

模块例化时,从模块外部来讲, input 端口可以连接 wire 或 reg 型变量。这与模块声明是不同的,从模块内部来讲,input 端口必须是 wire 型变量。

输出端口

模块例化时,从模块外部来讲,output 端口必须连接 wire 型变量。这与模块声明是不同的,从模块内部来讲,output 端口可以是 wire 或 reg 型变量。

输入输出端口

模块例化时,从模块外部来讲,inout 端口必须连接 wire 型变量。这与模块声明是相同的。

悬空端口

模块例化时,如果某些信号不需要与外部信号进行连接交互,我们可以将其悬空,即端口例化处保留空白即可,上述例子中有提及。

output 端口正常悬空时,我们甚至可以在例化时将其删除。

input 端口正常悬空时,悬空信号的逻辑功能表现为高阻状态(逻辑值为 z)。一般来说,建议 input 端口不要做悬空处理,无其他外部连接时赋值其常量。

位宽匹配

当例化端口与连续信号位宽不匹配时,端口会通过无符号数的右对齐或截断方式进行匹配。

假如在模块 full_adder4 中,端口 a 和端口 b 的位宽都为 4bit,则下面代码的例化结果会导致:u_adder4.a = {2'bzz, a[1:0]}, u_adder4.b = b[3:0] 。

full_adder4  u_adder4(
    .a      (a[1:0]),      //input a[3:0]
    .b      (b[5:0]),      //input b[3:0]
    .c      (1'b0),
    .so     (so),
    .co     (co));

用 generate 进行模块例化

当例化多个相同的模块时,一个一个的手动例化会比较繁琐。用 generate 语句进行多个模块的重复例化,可大大简化程序的编写过程。

重复例化 4 个 1bit 全加器组成一个 4bit 全加器的代码如下:

module full_adder4(
    input [3:0]   a ,   //adder1
    input [3:0]   b ,   //adder2
    input         c ,   //input carry bit
 
    output [3:0]  so ,  //adding result
    output        co    //output carry bit
    );
 
    wire [3:0]    co_temp ; 
    //第一个例化模块一般格式有所差异,需要单独例化
    full_adder1  u_adder0(
        .Ai     (a[0]),
        .Bi     (b[0]),
        .Ci     (c==1'b1 ? 1'b1 : 1'b0),
        .So     (so[0]),
        .Co     (co_temp[0]));
 
    genvar        i ;
    generate
        for(i=1; i<=3; i=i+1) begin: adder_gen
        full_adder1  u_adder(
            .Ai     (a[i]),
            .Bi     (b[i]),
            .Ci     (co_temp[i-1]), //上一个全加器的溢位是下一个的进位
            .So     (so[i]),
            .Co     (co_temp[i]));
        end
    endgenerate
 
    assign co    = co_temp[3] ;
 
endmodule

defparam 语句

//instantiation
defparam     u_ram_4x4.MASK = 7 ;
ram_4x4    u_ram_4x4
    (
        .CLK    (clk),
        .A      (a[4-1:0]),
        .D      (d),
        .EN     (en),
        .WR     (wr),    //1 for write and 0 for read
        .Q      (q)    );

带参数模块例化

ram #(.AW(4), .DW(4))
    u_ram
    (
        .CLK    (clk),
        .A      (a[AW-1:0]),
        .D      (d),
        .EN     (en),
        .WR     (wr),    //1 for write and 0 for read
        .Q      (q)
     );

(5)建议,对已有模块进行实例化并将其相关参数进行改写时,不要采用 defparam 的方法。除了上述缺点外,defparam 一般也不可综合。

(6)而且建议,模块在编写时,如果预知将被实例化且有需要改写的参数,都将这些参数写入到模块端口声明之前的地方(用关键字井号 # 表示)。这样的代码格式不仅有很好的可读性,而且方便调试。

6.1 Verilog 函数 | 菜鸟教程

函数

函数只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块。函数主要有以下几个特点:

  • 1)不含有任何延迟、时序或时序控制逻辑
  • 2)至少有一个输入变量
  • 3)只有一个返回值,且没有输出
  • 4)不含有非阻塞赋值语句
  • 5)函数可以调用其他函数,但是不能调用任务

Verilog 函数声明格式如下:

function [range-1:0]     function_id ;
  input_declaration ;
  other_declaration ;
  procedural_statement ;
endfunction

函数在声明时,会隐式的声明一个宽度为 range、 名字为 function_id 的寄存器变量,函数的返回值通过这个变量进行传递。当该寄存器变量没有指定位宽时,默认位宽为 1。

常数函数

常数函数是指在仿真开始之前,在编译期间就计算出结果为常数的函数。常数函数不允许访问全局变量或者调用系统函数,但是可以调用另一个常数函数。

这种函数能够用来引用复杂的值,因此可用来代替常量。

例如下面一个常量函数,可以来计算模块中地址总线的宽度:

parameter    MEM_DEPTH = 256 ;
reg  [logb2(MEM_DEPTH)-1: 0] addr ; //可得addr的宽度为8bit
 
    function integer     logb2;
    input integer     depth ;
        //256为9bit,我们最终数据应该是8,所以需depth=2时提前停止循环
    for(logb2=0; depth>1; logb2=logb2+1) begin 
        depth = depth >> 1 ;
    end
endfunction

automatic 函数

在 Verilog 中,一般函数的局部变量是静态的,即函数的每次调用,函数的局部变量都会使用同一个存储空间。若某个函数在两个不同的地方同时并发的调用,那么两个函数调用行为同时对同一块地址进行操作,会导致不确定的函数结果。

Verilog 用关键字 automatic 来对函数进行说明,此类函数在调用时是可以自动分配新的内存空间的,也可以理解为是可递归的。因此,automatic 函数中声明的局部变量不能通过层次命名进行访问,但是 automatic 函数本身可以通过层次名进行调用。

下面用 automatic 函数,实现阶乘计算:

wire [31:0]          results3 = factorial(4);
function automatic   integer         factorial ;
    input integer     data ;
    integer           i ;
    begin
        factorial = (data>=2)? data * factorial(data-1) : 1 ;
    end
endfunction // factorial

 

6.2 Verilog 任务 | 菜鸟教程

函数一般用于组合逻辑的各种转换和计算,而任务更像一个过程,不仅能完成函数的功能,还可以包含时序控制逻辑。

比较点 函数 任务
输入 函数至少有一个输入,端口声明不能包含 inout 型 任务可以没有或者有多个输入,且端口声明可以为 inout 型
输出 函数没有输出 任务可以没有或者有多个输出
返回值 函数至少有一个返回值 任务没有返回值
仿真时刻 函数总在零时刻就开始执行 任务可以在非零时刻执行
时序逻辑 函数不能包含任何时序控制逻辑 任务不能出现 always 语句,但可以包含其他时序控制,如延时语句
调用 函数只能调用函数,不能调用任务 任务可以调用函数和任务
书写规范 函数不能单独作为一条语句出现,只能放在赋值语言的右端 任务可以作为一条单独的语句出现语句块中

任务在模块中任意位置定义,并在模块内任意位置引用,作用范围也局限于此模块。

Verilog 任务声明格式如下:

task       task_id ;
    port_declaration ;
    procedural_statement ;
endtask

进行任务的逻辑设计时,可以把 input 声明的端口变量看做 wire 型,把 output 声明的端口变量看做 reg 型。但是不需要用 reg 对 output 端口再次说明。

对 output 信号赋值时也不要用关键字 assign。为避免时序错乱,建议 output 信号采用阻塞赋值。

例如,一个带延时的异或功能 task 描述如下:

task xor_oper_iner;
    input [N-1:0]   numa;
    input [N-1:0]   numb;
    output [N-1:0]  numco ;
    //output reg [N-1:0]  numco ; //无需再注明 reg 类型,虽然注明也可能没错
    #3  numco = numa ^ numb ;
    //assign #3 numco = numa ^ numb ; //不用assign,因为输出默认是reg
endtask

任务在声明时,也可以在任务名后面加一个括号,将端口声明包起来:

task xor_oper_iner(
    input [N-1:0]   numa,
    input [N-1:0]   numb,
    output [N-1:0]  numco  ) ; 
    #3  numco       = numa ^ numb ;
endtask

输入端连接的模块内信号可以是 wire 型,也可以是 reg 型。输出端连接的模块内信号要求一定是 reg 型,这点需要注意。

任务操作全局变量

因为任务可以看做是过程性赋值,所以任务的 output 端信号返回时间是在任务中所有语句执行完毕之后。

任务内部变量也只有在任务中可见,如果想具体观察任务中对变量的操作过程,需要将观察的变量声明在模块之内、任务之外,可谓之"全局变量"。

//use task to operate global varialbes to generating clk
reg          clk_test2 ;
task clk_rvs_global ;
        # 5 ;     clk_test2 = 0 ;
        # 5 ;     clk_test2 = 1 ;
endtask // clk_rvs_iner
always clk_rvs_global;

automatic 任务

和函数一样,Verilog 中任务调用时的局部变量都是静态的。可以用关键字 automatic 来对任务进行声明,那么任务调用时各存储空间就可以动态分配,每个调用的任务都各自独立的对自己独有的地址空间进行操作,而不影响多个相同任务调用时的并发执行。

如果一任务代码段被 2 处及以上调用,一定要用关键字 automatic 声明。

task automatic test_flag ;

 

6.3 Verilog 状态机 | 菜鸟教程

有限状态机(Finite-State Machine,FSM),简称状态机。

Moore 型状态机

Moore 型状态机的输出只与当前状态有关,与当前输入无关。

输出会在一个完整的时钟周期内保持稳定,即使此时输入信号有变化,输出也不会变化。输入对输出的影响要到下一个时钟周期才能反映出来。这也是 Moore 型状态机的一个重要特点:输入与输出是隔离开来的。

Mealy 型状态机

Mealy 型状态机的输出,不仅与当前状态有关,还取决于当前的输入信号。

Mealy 型状态机的输出是在输入信号变化以后立刻发生变化,且输入变化可能出现在任何状态的时钟周期内。

IDLE状态通常被用作状态机的一个状态,表示系统处于空闲、等待或初始状态。

  • Mealy 的输出条件可基于 状态转移前的状态和当前输入,在输入满足时 立即触发输出(即使状态转移尚未完成)。

  • Moore 的输出必须等待 状态迁移到目标状态,导致输出比 Mealy 型 多一个时钟周期的延迟

 

6.4 Verilog 竞争与冒险 | 菜鸟教程

  • 在组合逻辑电路中,不同路径的输入信号变化传输到同一点门级电路时,在时间上有先有后,这种先后所形成的时间差称为竞争(Competition)。
  • 由于竞争的存在,输出信号需要经过一段时间才能达到期望状态,过渡时间内可能产生瞬间的错误输出,例如尖峰脉冲。这种现象被称为冒险(Hazard)。
  • 竞争不一定有冒险,但冒险一定会有竞争。

判断方法:

  • 代数法
  • 卡诺图法

消除方法

  • 增加滤波电容,滤除窄脉冲
  • 修改逻辑,增加冗余项

  • 使用时钟同步电路,利用触发器进行打拍延迟

  • 采用格雷码计数器

打拍延迟:输入信号 din_rvsen 被寄存到 din_rvs_ren_r,延迟一个时钟周期。
利用触发器在时钟同步电路下对异步信号进行打拍延时,是 Verilog 设计中经常用到的方法。

Verilog 书写规范

  • 1)时序电路建模时,用非阻塞赋值。
  • 2)组合逻辑建模时,用阻塞赋值。
  • 3)在同一个 always 块中建立时序和组合逻辑模型时,用非阻塞赋值。
  • 4)在同一个 always 块中不要既使用阻塞赋值又使用非阻塞赋值。
  • 5)不要在多个 always 块中为同一个变量赋值。
  • 6)避免 latch 产生。

Verilog 中不允许在多个 always 块中为同一个变量赋值。也不允许 assign 语句为同一个变量进行多次连线赋值。

 

posted @ 2025-04-07 17:15  infocodez  阅读(146)  评论(0)    收藏  举报