当前位置:   article > 正文

【数字IC前端】Verilog语法-基础_数字ic并行结构

数字ic并行结构

数据类型

最常用的 2 种数据类型就是线网(wire)与寄存器(reg)

线网(wire)

如果没有驱动元件连接到 wire 型变量,缺省值一般为 “Z”

寄存器(reg)

表示存储单元,它会保持数据原有的值,直到被改写

寄存器不需要驱动源,也不一定需要时钟信号。在仿真时,寄存器的值可在任意时刻通过赋值操作进行改写

整数(integer)

声明时不用指明位宽,位宽和编译器有关,一般为32 bit。

实数(real)

可用十进制或科学计数法来表示。实数声明不能带有范围,默认值为 0。

时间(time)

对仿真时间进行保存。其宽度一般为 64 bit,通过调用系统函数 $time 获取当前仿真时间。

参数

参数用来表示常量,用关键字 parameter 声明,只能赋值一次。

但是,通过实例化的方式,可以更改参数在模块中的值。

字符串

字符串保存在 reg 中,每个字符占一个字节(8bit)

字符串不能多行书写,即字符串中不能包含回车符

如果寄存器变量的宽度大于字符串的大小,则使用 0 来填充左边的空余位;如果寄存器变量的宽度小于字符串大小,则会截去字符串左边多余的数据。例如,为存储字符串 “run.runoob.com”, 需要 14*8bit 的存储单元:

表达式

表达式由操作符和操作数构成

操作数

可以为常数,整数,实数,线网,寄存器,时间,位选,域选,存储器及函数调用等

操作符

算术、关系、等价、逻辑、按位、归约、移位、拼接、条件操作符

在这里插入图片描述

算术操作符

乘(*)、除(/)、加(+)、减(-)、求幂(**)、取模(%)

关系操作符

关系操作符有大于(>),小于(<),大于等于(>=),小于等于(<=)。

关系操作符的正常结果有 2 种,真(1)或假(0)。

等价操作符

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

等价操作符的正常结果有 2 种:为真(1)或假(0)。

逻辑操作符

&&(逻辑与), ||(逻辑或),!(逻辑非)。

逻辑操作符的计算结果是一个 1bit 的值,0 表示假,1 表示真,x 表示不确定

按位操作符

取反(),与(&),或(|),异或(^),同或(^)。

按位操作符对 2 个操作数的每 1bit 数据进行按位操作。

如果 2 个操作数位宽不相等,则用 0 向左扩展补充较短的操作数。

归约操作符

归约与(&),归约与非(&),归约或(|),归约或非(|),归约异或(),归约同或(~)。

归约操作符只有一个操作数,它对这个向量操作数逐位进行操作,最终产生一个 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
  • 1
  • 2
  • 3
  • 4
移位操作符

左移(<<),右移(>>),算术左移(<<<),算术右移(>>>)

算术左移和逻辑左移时,右边低位会补 0。

逻辑右移时,左边高位会补 0;而算术右移时,左边高位会补充符号位,以保证数据缩小后值的正确性。

拼接操作符

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

条件操作符
condition_expression ? true_expression : false_expression
  • 1

编译指令

`timescale

`timescale 编译指令将时间单位与实际时间相关联。

该指令用于定义时延、仿真的单位和精度

`timescale      time_unit / time_precision     // 时间单位/时间精度
  • 1
  • time_unit 表示时间单位,time_precision 表示时间精度,它们均是由数字以及单位 s(秒),ms(毫秒),us(微妙),ns(纳秒),ps(皮秒)和 fs(飞秒)组成。
  • 时间精度可以和时间单位一样,但是时间精度大小不能超过时间单位大小

`default_nettype

为隐式的线网变量指定为线网类型,即将没有被声明的连线定义为线网类型

`resetall

使得缺省连线类型为线网类型

`celldefine

用于将模块标记为单元模块

`endcelldefine

用于将模块标记为单元模块

连续赋值

关键词:assign, 全加器

  • 连续赋值语句是 Verilog 数据流建模的基本语句,用于对 wire 型变量进行赋值

  • assign 为关键词,任何已经声明 wire 变量的连续赋值语句都是以 assign 开头

wire      Cout, A, B ;
assign    Cout  = A & B ;     //实现计算A与B的功能
  • 1
  • 2

或在 wire 型变量声明的时候同时对其赋值。

wire      A, B ;
wire      Cout = A & B ;
  • 1
  • 2

时延

关键词:时延, 惯性时延

寄存器的时延也是可以控制的,这部分在时序控制里加以说明

连续赋值时延一般可分为普通赋值时延、隐式时延、声明时延

//普通时延,A&B计算结果延时10个时间单位赋值给Z
wire 			Z, 		A, 		B;
assign #10    	Z = A & B ;
  • 1
  • 2
  • 3
//隐式时延,声明一个wire型变量时对其进行包含一定时延的连续赋值。
wire A, B;
wire #10        Z = A & B;
  • 1
  • 2
  • 3
//声明时延,声明一个wire型变量是指定一个时延。因此对该变量所有的连续赋值都会被推迟到指定的时间。
//除非门级建模中,一般不推荐使用此类方法建模。
wire 			A,		B;
wire 	#10 	Z ;
assign          Z = A & B;
  • 1
  • 2
  • 3
  • 4
  • 5

惯性时延

信号脉冲宽度小于时延时,对输出没有影响。

仿真时,时延一定要合理设置,防止某些信号不能进行有效的延迟。

对一个有延迟的与门逻辑进行时延仿真。

过程结构

关键词:initial, always

一个模块中可以包含多个 initial 和 always 语句,但 2 种语句不能嵌套使用。

这些语句在模块间并行执行,与其在模块的前后顺序没有关系。

顺序执行,非阻塞赋值除外

每个 initial 语句或 always 语句都会产生一个独立的控制流,执行时间都是从 0 时刻开始。

initial语句

initial 语句从 0 时刻开始执行,只执行一次,多个 initial 块之间是相互独立的。

多用于初始化、信号检测等

always 语句

从 0 时刻开始执行其中的行为语句;执行完最后一条语句后,再次执行语句块中的第一条语句,如此循环反复

多用于仿真时钟的产生,信号行为的检测

过程赋值

关键词:阻塞赋值,非阻塞赋值,并行

过程性赋值是在 initial 或 always 语句块里的赋值,赋值对象是寄存器、整数、实数等类型。

这些变量在被赋值后,其值将保持不变,直到重新被赋予新值。

  • 连续性赋值总是处于激活状态,任何操作数的改变都会影响表达式的结果;

  • 过程赋值只有在语句执行时,才会起作用。

阻塞赋值=

阻塞赋值属于顺序执行,即下一条语句执行前,当前语句一定会执行完毕。

前面的仿真中,initial 里面的赋值语句都是用的阻塞赋值。

非阻塞赋值<=

非阻塞赋值属于并行执行语句,即下一条语句的执行和当前语句的执行是同时进行的,它不会阻塞位于同一个语句块中后面语句的执行。

使用非阻塞赋值避免竞争冒险

不要在一个过程结构中混合使用阻塞赋值与非阻塞赋值。两种赋值方式混用时,时序不容易控制,很容易得到意外的结果。

在设计电路时,always 时序逻辑块中多用非阻塞赋值,always 组合逻辑块中多用阻塞赋值;在仿真电路时,initial 块中一般多用阻塞赋值。

//2个always 块中语句并行执行,赋值操作右端操作数使用的是上一个时钟周期的旧值,此时a<=b与b<=a就可以相互不干扰的执行,达到交换寄存器值的目的。
always @(posedge clk) begin
    a <= b ;
end
 
always @(posedge clk) begin
    b <= a;
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

等效于——

always @(posedge clk) begin
    temp    = a ;
    a       = b ;
    b       = temp ;
end
  • 1
  • 2
  • 3
  • 4
  • 5

时序控制

关键词:时延控制,事件触发,边沿触发,电平触发

Verilog 提供了 2 大类时序控制方法:时延控制事件控制

事件控制主要分为边沿触发事件控制电平敏感事件控制

时延控制

当延时语句的赋值符号右端是常量时,2 种时延控制都能达到相同的延时赋值效果。

当延时语句的赋值符号右端是变量时,2 种时延控制可能会产生不同的延时赋值效果。

常规时延
reg  value_test ;
reg  value_general ;
#10  value_general    = value_test ;
  • 1
  • 2
  • 3
#10 ;
value_ single         = value_test ;
  • 1
  • 2
内嵌时延

先将计算结果保存,然后等待一定的时间后赋值给目标信号。

内嵌时延控制加在赋值号之后。

reg  value_test ;
reg  value_embed ;
value_embed = #10 value_test ;
  • 1
  • 2
  • 3
  • 一般延时的两种表达方式执行的结果都是一致的。
  • 一般时延赋值方式:遇到延迟语句后先延迟一定的时间,然后将当前操作数赋值给目标信号,并没有"惯性延迟"的特点,不会漏掉相对较窄的脉冲。
  • 内嵌时延赋值方式:遇到延迟语句后,先计算出表达式右端的结果,然后再延迟一定的时间,赋值给目标信号。

边沿触发事件控制

一般事件控制-@

条件是信号的值发生特定的变化

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

//信号clk只要发生变化,就执行q<=d,双边沿D触发器模型
always @(clk) q <= d ;                
//在信号clk上升沿时刻,执行q<=d,正边沿D触发器模型
always @(posedge clk) q <= d ;  
//在信号clk下降沿时刻,执行q<=d,负边沿D触发器模型
always @(negedge clk) q <= d ; 
//立刻计算d的值,并在clk上升沿时刻赋值给q,不推荐这种写法
q = @(posedge clk) d ;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
命名事件控制-event

用户可以声明 event(事件)类型的变量,并触发该变量来识别该事件是否发生。

触发信号用 -> 表示

event     start_receiving ;
always @( posedge clk_samp) begin
        -> start_receiving ;       //采样时钟上升沿作为时间触发时刻
end
always @(start_receiving) begin
    data_buf = {data_if[0], data_if[1]} ; //触发时刻,对多维数据整合
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
敏感列表

多个信号或事件中任意一个发生变化都能够触发语句的执行

用关键字 or 连接多个事件或信号。这些事件或信号组成的列表称为"敏感列表"。

or 也可以用逗号 , 来代替。

always @(posedge clk or negedge rstn)    begin      
//always @(posedge clk , negedge rstn)    begin 
  • 1
  • 2

更为简洁的写法是 @*@(*),表示对语句块中的所有输入变量的变化都是敏感的

//两种写法等价
always @(*) begin
//always @(a, b, c, d, e, f, g, h, i, j, k, l, m) begin 
  • 1
  • 2
  • 3
电平敏感事件控制-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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

语句块

关键词:顺序块,并行块,嵌套块,命名块,disable

顺序块—begin … end …

一条条执行的,非阻塞赋值除外

每条语句的时延总是与其前面语句执行的时间相关

并行块—fork … join …

并行执行的,即便是阻塞形式的赋值

每条语句的时延都是与块语句开始执行的时间相关

而非阻塞赋值,也能达到和并行块同等的赋值效果。

嵌套块

顺序块和并行块可以嵌套使用

在这里插入图片描述

命名块

命名的块中可以声明局部变量,通过层次名引用的方法对变量进行访问。

module test;
    initial begin: runoob   //命名模块名字为runoob,分号不能少
        integer    i ;       //此变量可以通过test.runoob.i 被其他模块使用
  • 1
  • 2
  • 3

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

if (i_d >= 50) begin       			//累加5次停止累加
    disable runoob_d3.clk_gen ; 	//stop 外部block: clk_gen
    disable runoob_d2 ;         	//stop 当前block: runoob_d2
end
  • 1
  • 2
  • 3
  • 4

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

条件语句

关键词:if,选择器

加入 begin 与 and 关键字是一个很好的习惯

多路分支语句

关键词:case,选择器

case 语句

case(case_expr)
    condition1     :             true_statement1 ;
    condition2     :             true_statement2 ;
    ……
    default        :             default_statement ;
endcase
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

case 语句中的条件选项表单式不必都是常量,也可以是 x 值或 z 值,但一般不建议在 case 语句中使用 x 或 z 作为比较值。

casex/casez 语句

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

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

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

循环语句

关键词:while, for, repeat, forever

while 循环

while (condition) begin
    …
end
  • 1
  • 2
  • 3

for 循环

  • 初始条件和自加操作等过程都已经包含在 for 循环中,所以 for 循环写法比 while 更为紧凑

  • 不是所有的情况下都能使用 for 循环来代替 while 循环。

  • i = i + 1 不能像 C 语言那样写成 i++ 的形式,i = i -1 也不能写成 i – 的形式

for (i=0; i<=10; i=i+1) begin
    #10 ;
    counter2 = counter2 + 1'b1 ;
end
  • 1
  • 2
  • 3
  • 4

repeat 循环

repeat 循环的次数必须是一个常量、变量或信号

repeat (loop_times) begin
    …
end
  • 1
  • 2
  • 3

forever 循环

  • forever 语句表示永久循环,不包含任何条件表达式,一旦执行便无限的执行下去,系统函数 $finish 可退出 forever。

  • 相当于 while(1)

  • forever 循环是和时序控制结构配合使用的

过程连续赋值

关键词:deassign,force,release

  • 过程连续赋值是过程赋值的一种。这种赋值语句能够替换其他所有 wire 或 reg 的赋值,改写了 wire 或 reg 型变量的当前值。

  • 过程连续赋值的表达式能被连续地驱动到 wire 或 reg 型变量中,即过程连续赋值发生作用时,右端表达式中任意操作数的变化都会引起过程连续赋值语句的重新执行。

assign — deassign

  1. assign(过程赋值操作)与 deassign (取消过程赋值操作)表示第一类过程连续赋值语句
  2. 赋值对象只能是寄存器寄存器组,而不能是 wire 型变量
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在复位信号为 0 时,Q 端被 assign 语句赋值,始终输出为 0。

在复位信号为 1 时,Q 端被 deassign 语句取消赋值,在时钟上升沿被重新赋值

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

force — release

  1. force (强制赋值操作)与 release(取消强制赋值)表示第二类过程连续赋值语句
  2. 赋值对象可以是 reg 型变量,也可以是 wire 型变量
  3. 为无条件强制赋值,一般多用于交互式调试过程,不要在设计模块中使用
  • 当 force 作用在寄存器上时,寄存器当前值被覆盖;release 时该寄存器值将强制赋值时的值。之后,该寄存器的值可以被原有的过程赋值语句改变。
  • 当 force 作用在线网上时,线网值也会被强制赋值。但是,一旦 release 该线网型变量,其值马上变为原有的驱动值。
@(negedge clk) ;
force     test.u_counter.cnt_temp = 4'd6 ;
force     test.u_counter.cout     = 1'b1 ;
#40 ;
@(negedge clk) ;
release   test.u_counter.cnt_temp ;
release   test.u_counter.cout ;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

模块与端口

关键词:模块,端口,双向端口,PAD

结构建模方式有 3 类描述语句:

​ ①Gate(门级)例化语句

​ ②UDP(用户定义原语)例化语句

③module(模块)例化语句

模块

变量声明数据流语句行为级语句低层模块例化及任务函数这 5 部分出现顺序、出现位置都是任意的。但是,各种变量都应在使用之前声明。变量具体声明的位置不要求,但必须保证在使用之前的位置。

module module_name 
#(parameter_list)
(port_list) ;
              Declarations_and_Statements ;
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

端口

端口列表
  • 一般将不带类型不带位宽的信号变量罗列在模块声明里

  • 一个模块如果和外部环境没有交互,则可以不用声明端口列表

端口声明
  • 端口类型(端口方向): 输入(input),输出(output)和双向端口(inout)。

  • input、inout 类型不能声明为 reg 数据类型

    • reg 类型是用于保存数值的,而输入端口只能反映与其相连的外部信号的变化,不能保存这些信号的值。
  • output 可以声明为 wire 或 reg 数据类型

  • 端口隐式的声明为 wire 型变量

    output reg      DOUT ;
    
    • 1
  • reg 型端口要么在 module 声明时声明,要么在 module 实体中声明

    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 ;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

模块例化

关键字:例化,generate,全加器,层次访问

**模块例化:**在一个模块中引用另一个模块,对其端口进行相关连接

命名端口连接(√)

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

顺序端口连接(×)

  • 位置要严格保持一致
  • 代码可读性降低,也不易于调试
//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]));

//顺序端口连接
full_adder1  u_adder1(
    a[1], b[1], co_temp[0], so_bit1, co_temp[1]);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

端口连接规则

  1. 从模块内部来讲,input 端口必须是 wire 型变量

  2. 从模块内部来讲,output 端口可以是 wire 或 reg 型变量

  3. 从模块外部来讲,inout 端口必须连接 wire 型变量

  4. 悬空端口

    • output 端口正常悬空时,我们甚至可以在例化时将其删除;
    • input 端口正常悬空时,悬空信号的逻辑功能表现为高阻状态(逻辑值为 z)

    ​ 但是,例化时一般不能将悬空的 input 端口删除,否则编译会报错

  5. 位宽不匹配时,端口会通过无符号数的右对齐截断方式进行匹配

  6. 连接端口的信号类型可以是

    1)标识符,2)位选择,3)部分选择,4)上述类型的合并,5)用于输入端口的表达式

用 generate 进行模块例化

用 generate 语句可对多个模块进行重复例化

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

层次访问

通过使用一连串的 . 符号对各个模块的标识符进行层次分隔连接

层次访问多见于仿真中。

带参数例化

关键词: defparam,参数,例化,ram

当一个模块被另一个模块引用例化时,高层模块可以对低层模块的参数值进行改写

参数覆盖有 2 种方式

  • 使用关键字 defparam
  • 带参数值模块例化

defparam 语句

可以用关键字 defparam 通过模块层次调用的方法,来改写低层次模块的参数值。

defparam     u_ram_4x4.MASK = 7 ;
  • 1

带参数模块例化

将新的参数值写入模块例化语句

  • 如果有模块在端口声明时的参数,那么实体中的参数将视为 localparam 类型,使用 defparam 将不能改写模块实体中声明的参数

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/969664
推荐阅读
相关标签
  

闽ICP备14008679号