模块功能的描述方法
目录
行为描述方法
语句块
过程赋值语句
高级程序语句
循环语句
数据流描述
结构描述
混合描述方法
module 模块名(端口列表); // 模块声明// 端口定义input [数据类型] [位宽] 输入端口列表; output [数据类型] [位宽] 输出端口列表; inout [数据类型] [位宽] 双向端口列表; // 数据类型定义wire [位宽] 线网名,线网名,…; reg [位宽] 变量名,变量名,…;//函数与任务声明function [位宽] 函数名; ...; endfuctiontask 任务名; ...; endtask// 功能描述 assign 线网名=函数表达式; // 数据流描述方式always/initial过程语句; // 行为描述方式例化模块名 实例名(端口关联列表); // 结构描述方式endmodule
行为描述方法
行为描述(Behavioral Modeling)以过程语句为基本单位,在过程体内部应用高级语句和操作符描述模块的行为特性,不考虑具体的实现方法。过程语句有initial语句和always语句两种形式。
initial过程语句称为初始化语句,无触发条件,从0时刻开始只执行一次。initial语句用在testbench文件中,用于对变量进行初始化或者产生特定的信号波形。
initial语句的语法格式为:
initial begin块内变量说明;[延时控制1] 语句1; ...end
initial beginclk = 0;#10 clk = 1;#20 clk = 0;
end
当模块中存在多个的initial语句时,则所有initial语句同时从0时刻开始执行。
always 语句
always语句有两种过程状态:等待状态和执行状态。当事件列表中无事件发生时,always语句处于等待状态;当事件列表中有事件发生时,always语句进入执行状态,执行完毕后自动返回等待状态。
always语句应用的语法格式为:always @(事件列表)begin [:语句块名]块内变量说明;[延时控制1] 语句1; …… [延时控制n] 语句n; end
事件列表示触发启动always过程语句执行的条件,分为电平敏感事件和边沿触发事件两种类型。
电平敏感事件是指线网/变量的电平发生变化时,触发过程语句进入执行状态。 其语法格式为: @(电平敏感量1 or … or 电平敏感量n) 语句块;
always @ (*) begin……end
边沿触发事件是指线网/变量发生边沿跳变时执行语句块,分为上升沿触发和下降沿触发两种,分别用关键词posedge和negedge表示。
边沿触发事件的语法格式为: @(边沿触发事件1 or … or 边沿触发事件n) 语句块;
D触发器的描述。module d_ff(clk,d,q);input clk,d;output reg q;always @( posedge clk ) // 上升沿锁存数据q <= d;endmodule
4位二进制计数器的描述。module cnt4b(clk,q);parameter Nbits = 4;input clk;output reg [Nbits-1:0] q;// 下降沿计数always @( negedge clk ) q <= q + 1'b1;endmodule
always过程语句也可以没有触发条件,表示永远反复执行,用来产生周期性的波形,但不可综合,只能用于测试平台文件中。
需要强调的是:在always语句的事件列表中,电平敏感事件和边沿触发事件不能混合使用。一旦事件列表中含有由posedge或者negedge引导的边沿触发事件,则不能再出现电平敏感事件,即下述形式是错误的: always @( posedge clk or rst_n)
always @( posedge clk or negedge rst_n)if ( !rst_n ) ...else ...
过程语句应用示例。module clk_gen(clk1,clk2);output clk1,clk2;reg clk1,clk2;initial // 定义初值beginclk1 = 0;clk2 = 0;endalways // 产生周期性波形#50 clk1 = ~clk1;always#100 clk2 = ~clk2;endmodule
语句块
语句块(block)是将两条或者两条以上的语句组合在一起,使其在形式上如同一条语句,其作用与C语言中的大括号“{}”相同。 语句块有顺序语句块和并行语句块两种类型。
顺序语句块(sequential block)由关键词begin和end定义,按语句的书写顺序执行块中的语句,即前一条语句执行完后才能执行后一条语句。若使用延迟,则每条语句的延迟时间均相对于上一条语句的执行时刻而言。
顺序语句块定义的语法格式为:begin [:块名]语句1;...语句n;end
其中块名可以省略。
reg a,b,c,d;
initial begina = 1'b0; // 仿真时刻0时执行#5 b = 1'b1; // 仿真时刻5时执行#10 c = 1'b0; // 仿真时刻15(=10+5)时执行#15 d = 1'b1; // 仿真时刻30(=15+10+5)时执行end
并行语句块(parallel block)由关键词fork和join定义,块中的所有语句从块被调用的时刻同时开始执行。若使用延迟,则每条语句的延迟时间均相对于块调用的开始时刻而言,与语句的具体书写顺序无关。
reg a,b,c,d;
initial forka = 1'b0; // 仿真时刻0时执行#5 b = 1'b1; // 仿真时刻5时执行#10 c = 1'b0; // 仿真时刻10时执行#15 d = 1'b1; // 仿真时刻15时执行join
时序控制
时序控制用于定义从开始遇到语句到真正执行该语句时的等待时间。时序控制只用于仿真,综合时所有的延时控制将被忽略。
时序控制有常规延时和内嵌延时两种书写形式。
应用常规延时的语法格式为:
[#延时量] 线网/变量 = 表达式;
应用内嵌延时的语法格式为:
线网/变量 = [#延时量] 表达式;
在Verilog HDL中,延迟量的单位由预编译指令“`timescale”进行定义。例如,在文件头中添加语句: `timescale 1ns /1ps 表示仿真时间单位为1ns,仿真精度为1ps。根据该命令,仿真工具才认为 #10 表示延时时间为10ns。
过程赋值语句
过程赋值(procedural assignment)语句是指在initial/always语句内部对变量进行赋值的赋值语句。
过程赋值语句的语法格式为: <变量> <赋值操作符> <赋值表达式> ;
赋值操作符分为两类:“=”和“<=”,分别表示阻塞赋值(blocking assignment)和非阻塞赋值(non-blocking assignment)。
阻塞赋值按照语句的书写顺序进行赋值。也就是说,在前一条赋值语句执行结束之前,后一条语句被阻塞,不能执行。只有前一条赋值语句执行结束后,后一条语句才能被执行。
module eq1b(a,b,eq);input a,b;output reg eq;reg tmp1,tmp2;always @(a,b)begintmp1 = ~a & ~b;tmp2 = a & b;eq = tmp1 | tmp2;end
endmodule
非阻塞赋值是指多条赋值语句同时赋值,与语句的书写顺序无关,即后面赋值语句的执行不受前面赋值语句的影响。 由于阻塞赋值与非阻塞赋值的赋值方法不同,因此综合出的电路存在差异,所以必须正确地区分和应用这两类赋值语句。
module blocking(din,clk,reg1,reg2);input din,clk;output reg reg1,reg2;always @(posedge clk)beginreg1 = din;reg2 = reg1;endendmodule
当模块中既包含组合逻辑又包含时序逻辑时,应该将组合逻辑和时序逻辑分开进行描述。可以用一个always语句描述时序逻辑,用另一个always语句描述组合逻辑,或者改用连续赋值语句描述组合逻辑。
module blocking(din,clk,reg1,reg2);input din,clk;output reg reg1,reg2;always @(posedge clk)beginreg1 <= din;reg2 <= reg1;endendmodule
高级程序语句
Verilog HDL中的高级程序语句和C语言一样,用于控制代码的流向,分为条件语句、分支语句和循环语句三种类型。
条件语句(conditional statement)使用关键词if和else,根据条件表达式的真假确定执行的操作,用于对赋值过程进行控制,分为简单条件语句、分支条件语句和多重语句三种类型。
module d_latch(clk,d,q); input clk,d;output reg q;always @(clk,d)if (clk) q <= d;endmodule
由于简单条件语句没有定义条件表达式为假时执行的操作,隐含条件表达式为假时不执行任何操作,所以被赋值的变量应该保持不变,因而会综合出时序电路。
分支条件语句的语法格式为: if(条件表达式) 条件表达式为真时执行的语句块; else 条件表达式为假时执行的语句块;
module mux2to1 (y,d0,d1,sel);input d0,d1,sel;output reg y;always @(d0,d1,sel)beginif ( !sel ) y = d0;elsey = d1;endendmodule
如果用下述代码描述双向口:
module BiDir_Port(dir,a,b);input dir; // 双向控制端inout a,b; // 两个双向口reg atmp,btmp;assign a=atmp;assign b=btmp;always @ (dir,a,b) if ( dir ) atmp = b; else btmp = a;
endmodule
描述双向口正确的Verilog代码参考如下:
module BiDir_Port(dir,a,b);input dir; // 双向控制端inout a,b; // 两个双向口reg atmp,btmp;assign a=atmp;assign b=btmp;always @ (dir,a,b) if (dir) begin atmp = b; btmp = 1’bz; endelse begin btmp = a; atmp = 1’bz; end
endmodule
具有异步复位功能的4位二进制计数器的描述
module cnt4b(clk,rst_n,q);input clk,rst_n;output [3:0] q;reg [3:0] q;always @(posedge clk or negedge rst_n)beginif ( !rst_n ) // 低电平有效q <= 4'b0000;elseq <= q + 1'b1;end
endmodule
多重条件语句常用于多路选择, 其语法格式为:
if(条件表达式1) 条件表达式1为真时执行的语句块;
else if(条件表达式2) 条件表达式2为真时执行的语句块; ……
else if(条件表达式n) 条件表达式n为真时执行的语句块;
else 条件表达式1~n均为假时执行的语句块;
多重条件语句对条件表达式的判断有先后次序,隐含有优先级的关系,先判断的条件表达式优先级高,后判断的条件表达式优先级低。因此,多重条件语句通常用于描述有优先级的逻辑电路。
4线-2线优先编码器的描述。module prior_encoder(d,c,b,a,y);input d,c,b,a;output reg [1:0] y;always @ (d,c,b,a)beginif (d) y = 2'b11;else if (c) y = 2'b10;else if (b) y = 2'b01;else y = 2'b00;endendmodule
分支语句使用关键词case...endcase引导,功能相当于C语言中的switch语句,用于实现多路选择。
用分支语句描述2选一数据选择器。
module mux2to1(d0,d1,sel,y);input d0,d1,sel;output y;reg y;always @ (d0,d1,sel) case ( sel )1'b0: y = d0;1'b1: y = d1;default: y= d0;endcaseendmodule
在Verilog HDL中,通常用字符“?”代替字符x和z,表示无关位。
always @ (d,c,b,a)begincasez({d,c,b,a})4'b1???: y = 2'b11;4'b01??: y = 2'b10;4'b001?: y = 2'b01;4'b0001: y = 2'b00;default: y = 2'b00;endcaseendendmodule
循环语句
循环语句的作用与C语言相同。Verilog HDL支持4类循环语句:for、while、repeat和forever语句,其中for语句、while语句的用法与C语言相同。
用移位累加方法描述乘法器。module multi(result,op_a,op_b);parameter Nbits=8; // 参数定义input [Nbits:1] op_a,op_b; // 被乘数与乘数output [2*Nbits:1] result; // 乘法结果reg [2*Nbits:1] result;integer i; // 循环变量always @(op_a, op_b)begin result = 0;for(i=1;i<=Nbits;i=i+1)if(op_b[i]) result = result+(op_a<<(i-1)); endendmodule
需要注意的是,Verilog不支持i++和i--这种循环增量的书写方式,递加和递减只能写成i=i+1和i=i-1。
while语句的语法格式为: while(循环条件表达式) 语句块;
如果while循环中条件表达式的值为x或者z时,则循环次数为0。
parameter Nbits=8;
reg [2*Nbits:1] atmp; // 定义内部变量
reg [Nbits:1] btmp;
integer i; // 定义循环变量
always @(op_a,op_b)begin result = 0;atmp = {{Nbits{1'b0}},op_a}; // 拼接扩展btmp = op_b; i = Nbits;while (i>0) begin if ( btmp[1] ) result = result + atmp; i = i-1; // 循环次数减1atmp = atmp << 1; // 左移一位 btmp = btmp >> 1; // 右移一位end end
repeat语句的语法格式为: repeat(循环次数表达式) 语句块;
parameter Nbits=8;
reg [2*Nbits:1] atmp;
reg [Nbits:1] btmp;
always @(op_a,op_b)beginresult = 0;atmp = op_a;btmp = op_b;repeat (Nbits) // 循环次数由Nbits确定begin if ( btmp[1] ) result = result + atmp;atmp = atmp << 1; btmp = btmp >> 1;endend
数据流描述
数据流描述(Dataflow Modeling)采用连续赋值语句,基于表达式和操作符描述线网的功能,用于组合逻辑电路的描述。连续赋值语句的语法格式为: assign [#延迟量] 线网名 = 赋值表达式;
用数据流描述2选一数据选择器。module MUX2to1(y,d0,d1,sel);input d0,d1,sel;output y;assign y= ~sel & d0 | sel & d1; endmodule
需要说明的是:(1)连续赋值语句用于对线网进行赋值,不能对变量进行赋值;(2)连续赋值语句和过程语句为平等关系,不能相互嵌套使用;(3)连续赋值语句的执行顺序与语句书写的顺序无关。
结构描述
结构描述(Structural Modeling)方法类似于原理图设计,是将电路中的基元、模块和功能IP之间的连接关系由连线转换为文字表达。
Verilog HDL预定义了26个基本元器件(primitives,简称基元),包括逻辑门和三态门,上拉电阻和下拉电阻,以及MOS开关和双向开关。
Verilog基元分为以下六种类型: ▪ 多输入门:and, nand, or, nor, xor, xnor ▪ 多输出门:buf, not ▪ 三态门:bufif0, bufif1, notif0, notif1 ▪ 上拉电阻/下拉电阻:pullup, pulldown ▪ MOS开关:cmos, nmos, pmos, rcmos, rnmos, rpmos ▪ 双向开关:tran, tranif0, tranif1, rtran, rtranif0, rtranif1
结构描述的代码参考如下:module mux2to1(y,a,b,sel);output y;input a,b,sel;wire sel_n,a1,b1;not G1 (sel_n,sel);and G2 (a1,a,sel_n);and G3 (b1,b,sel);or G4 (y,a1,b1);endmodule
混合描述方法
Verilog HDL支持三种描述方式的混合使用,即在同一模块中可以混合使用过程语句、连续赋值语句和例化语句。
全加器的混合描述。module Full_Adder(A,B,Cin,Sum,Co);input A,B,Cin;output Sum,Co;reg Co;wire Wtmp;xor U1(Wtmp,A,B); // 结构描述,实现Wtmp=A^Bassign Sum = Wtmp ^ Cin; // 数据流描述,实现Sum=Wtmp^Cinalways @ ( A or B or Cin ) // 行为描述Co = A & Cin | B & Cin | A & B; // 过程赋值语句endmodule