参考菜鸟教程
2.1 基本语法 Verilog 是区分大小写的.
可在一行内编写,也可跨多行编写。
每个语句必须以分号为结束符。空白符(换行、制表、空格)都没有实际的意义,在编译阶段可忽略。
不换行如:
1 wire [1 :0 ] results ;assign results = (a == 1'b0 ) ? 2'b01 : (b==1'b0 ) ? 2'b10 : 2'b11 ;
换行如:
1 2 3 4 wire [1 :0 ] results ;assign results = (a == 1'b0 ) ? 2'b01 : (b==1'b0 ) ? 2'b10 : 2'b11 ;
注释 和 C 语言相同.
Verilog 中关键字全部为小写.
2.2 数值表示 四种基本值, 表示硬件电路中的电平逻辑:
0: 假
1: 真
x 或 X: 未知
z 或 Z: 高阻
整数数值表示方法 四种:
'b
或 'B
, 二进制
'o
或 'O
, 八进制
'd
或 'D
, 十进制
'h
或 'H
, 十六进制
可在其前面加上位数也可以不加, 如:
1 2 3 4 5 6 7 8 9 num = 'b100 num = ' o100num = 'd100 num = ' h100num = 10 'b100 num = 8' o100num = 16 'd100 num = 32' h100
不加时, 默认位数根据编译器而定.
负数表示 加 -
号. 如:
实数表示法 十进制
科学记数法 1 2 3 1 .2 e4 //大小为12000 1_0001e4 //大小为100010000 1E -3 //大小为0 .001
字符串表示方法 字符串是由双引号 包起来的字符队列。字符串不能多行书写 ,即字符串中不能包含回车符。Verilog 将字符串当做一系列的单字节 ASCII 字符队列
1 2 3 4 reg [0 : 14 *8 -1 ] str ;initial begin str = "www.runoob.com" ;end
2.3 数据类型 最常用的 2 种数据类型就是线网(wire)与寄存器(reg),其余类型可以理解为这两种数据类型的扩展或辅助。
线网 (wire) wire 类型表示硬件单元之间的物理连线.
声明如:
1 2 3 wire interrupt ;wire flag1, flag2 ;wire gnd = 1'b0 ;
寄存器 (reg) 寄存器(reg)用来表示存储单元,它会保持数据原有的值,直到被改写, 声明如:
1 2 reg clk_temp; reg flag1 , flag2 ;
寄存器的值可在任意时刻通过赋值操作进行改写, 如:
1 2 3 4 5 6 reg rstn ;initial begin rstn = 1'b0 ; #100 ; rstn = 1'b1 ;end
向量 当位宽大于 1 时,wire 或 reg 即可声明为向量的形式.
如:
其表示为 4bit 位宽的寄存器 counter.
其中, 3
表示最高位, 0
表示最低位. (可以理解为数组, index 最高为 3
, 最低为 0
)
同样:
1 2 3 wire [32 -1 :0 ] gpio_data; // 声明32 bit位宽的线型变量gpio_data wire [8 :2 ] addr; // 声明7 bit位宽的线型变量addr,位宽范围为8 :2 reg [0 :31 ] data; // 声明32 bit位宽的寄存器变量data, 最高有效位为0
可以指定向量的某一位或若干相邻位,作为其他逻辑使用:
1 2 wire data_low = data; addr_temp = addr + 1'b1;
Verillog 还支持指定 bit 位后固定位宽的向量域选择访问。
[bit+: width]
: 从起始 bit 位开始递增,位宽为 width。
[bit-: width]
: 从起始 bit 位开始递减,位宽为 width。
如:
1 2 3 4 5 6 7 A = data1[31 -: 8 ]; A = data1[31 :24 ]; B = data1[0 + : 8 ]; B = data1[0 :7 ];
可用大括号进行数据拼接 :
1 2 3 wire [31 :0 ] temp1, temp2 ;assign temp1 = {byte1[0 ][7 :0 ], data1[31 :8 ]}; assign temp2 = {32 {1'b0 }};
整数, 实数, 时间寄存器变量 整数,实数,时间等数据类型实际也属于寄存器类型.
整数 (integer) 整数类型用关键字 integer 来声明。声明时不用指明位宽,位宽和编译器有关,一般为32 bit。reg 型变量为无符号数,而 integer 型变量为有符号数 .
如:
1 2 3 4 5 6 7 8 9 reg [31 :0 ] data1 ;reg [3 :0 ] byte1 [7 :0 ]; integer j ; always @* begin for (j=0 ; j<=3 ;j=j+1 ) begin byte1[j] = data1[(j+1 )*8 -1 : j*8 ]; end end
实数 (real) 实数用关键字 real 来声明,可用十进制或科学计数法来表示。实数声明不能带有范围,默认值为 0。如果将一个实数赋值给一个整数,则只有实数的整数部分会赋值给整数。
如:
1 2 3 4 5 6 7 8 9 10 real data1 ;integer temp ; initial begin data1 = 2e3 ; data1 = 3.75 ;end initial begin temp = data1 ; //temp 值的大小为3 end
时间 (time) Verilog 使用特殊的时间寄存器 time 型变量,对仿真时间进行保存。其宽度一般为 64 bit,通过调用系统函数 $time 获取当前仿真时间.
如:
1 2 3 4 5 time current_time ;initial begin #100 ; current_time = $time ; / / current_time 的大小为 100 end
数组 在 Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。
数组维数没有限制
数组中的每个元素都可以作为一个标量或者向量,以同样的方式来使用.
声明如:
1 2 3 4 5 integer flag [7:0] ; reg [3:0] counter [3:0] ; wire [7:0] addr_bus [3:0] ; wire data_bit [7:0] [5:0] ; reg [31:0] data_4d [11:0] [3:0] [3:0] [255:0] ;
赋值如:
1 2 3 4 5 flag = 32'd0 ; //将flag数组中第二个元素赋值为32bit的0值 counter = 4'hF ; //将数组counter中第4个元素的值赋值为4bit 十六进制数F,等效于counter = 4'hF,即可省略宽度; assign addr_bus = 8'b0 ; //将数组addr_bus中第一个元素的值赋值为0 assign data_bit = 1'b1; //将数组data_bit的第1行第2列的元素赋值为1,这里不能省略第二个访问标号,即 assign data_bit = 1'b1; 是非法的。 data_4d = 15'd3 ; //将数组data_4d中标号为的寄存器单元的15~0bit赋值为3
需区分向量和数组 :
向量是一个单独的元件,位宽为 n
数组由多个元件组成,其中每个元件的位宽为 n 或 1
存储器 存储器变量就是一种寄存器数组,可用来描述 RAM 或 ROM 的行为.
如:
1 2 3 reg membit[0 :255 ] ; reg [7 :0 ] mem[0 :1023 ] ; mem[511 ] = 8'b0 ;
参数 参数用来表示常量,用关键字 parameter 声明,只能赋值一次.
如:
1 2 3 parameter data_width = 10 'd32 ;parameter i=1 , j=2 , k=3 ;parameter mem_size = data_width * 10 ;
通过实例化的方式,可以更改参数在模块中的值.
局部参数 用 localparam 来声明,其作用和用法与 parameter 相同,区别在于它的值不能被改变.
字符串 字符串保存在 reg 类型的变量中,每个字符占用一个字节(8bit)。因此寄存器变量的宽度应该足够大,以保证不会溢出。
字符串不能多行书写,即字符串中不能包含回车符。如果寄存器变量的宽度大于字符串的大小,则使用 0 来填充左边的空余位;如果寄存器变量的宽度小于字符串大小,则会截去字符串左边多余的数据.
如:
1 2 3 4 reg [0 : 14 *8 -1 ] str ;initial begin str = "run.runoob.com" ;end
一些特殊字符:
2.4 表达式 操作符大体和 C 相同:
以下为几个不同.
等价操作符 全等 ===
. 非全等 !==
全等比较:a === b
表示当且仅当 a 和 b 的值和类型都相等时,表达式的值为 1,否则为 0。
不全等比较:a !== b
表示当且仅当 a 和 b 的值或类型至少有一个不相等时,表达式的值为 1,否则为 0。
如:
1 2 3 4 5 6 7 8 9 A = 4 ; B = 8'h04 ; C = 4'bxxxx ; D = 4'hx ; A == B //为真 A == (B + 1) //为假 A == C //为X,不确定 A === C //为假,返回值为0 C === D //为真,返回值为1
归约操作符 归约操作符只有一个操作数,它对这个向量操作数逐位进行操作,最终产生一个 1bit 结果。
包括:
&
, 归约与
~&
, 归约与非
|
, 归约或
~|
, 归约或非
^
, 归约异或
~^
, 归约同或
如:
1 2 3 4 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 5 A = 4 'b1010 ;B = 1 'b1 ;Y1 = {B, A[3 :2 ], A[0 ], 4 'h3 }; //结果为Y1='b1100_0011Y2 = {4 {B}, 3 'd4}; //结果为 Y2=7 'b111_1100Y3 = {32 {1 'b0}}; //结果为 Y3=32 h0,常用作寄存器初始化时匹配位宽的赋初值
2.5 编译指令 以反引号 ` 开始的某些标识符是 Verilog 系统编译指令.
`define, `undef `define 和 C 中的 #define
类似.
如:
`undef 用于取消宏定义:
1 2 3 4 5 `define DATA_DW 32 ……reg [DATA_DW-1: 0 ] data_in …… `undef DATA_DW
条件编译指令 1 2 3 4 5 6 7 8 9 10 11 12 13 `ifdef MCU51 parameter DATA_DW = 8 ;`elsif WINDOW parameter DATA_DW = 64 ;`else parameter DATA_DW = 32 ;`endif `ifndef WINDOW parameter DATA_DW = 32 ; `else parameter DATA_DW = 64 ;`endif
`include `include 和 C 中的 #include
类似, 用于编译时将一个 Verilog 文件内嵌到另一个 Verilog 文件中, 可以是相对路径, 也可以是绝对路径, 如:
1 2 `include "../../param.v" `include "header.v"
`timescale `default_nettype `resetall 3.1 连续赋值 assign 任何已经声明 wire 变量的连续赋值语句都是以 assign 开头, 如:
1 2 wire Cout, A, B ; assign Cout = A & B
注意 :
左值必须是一个标量或者线型向量,不能是寄存器类型 而
右值没有类型要求
3.2 时延 用于控制任意操作数发生变化到语句左端赋予新值之间的时间延时.
连续赋值时延一般可分为:
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 wire Z, A, B ;assign #10 Z = A & B ; wire A, B;wire #10 Z = A & B; wire A, B;wire #10 Z ;assign Z =A & B
惯性时延 4.1 过程结构 过程结构语句有 2 种,initial 与 always 语句
一个模块中可以包含多个 initial 和 always 语句,但 2 种语句不能嵌套使用。
这些语句在模块间并行执行 ,与其在模块的前后顺序没有关系, 但是 initial 语句或 always 语句内部可以理解为是顺序执行的.
每个 initial 语句或 always 语句都会产生一个独立的控制流,执行时间都是从 0 时刻开始.
initial 语句 initial 语句从 0 时刻开始执行,只执行一次 ,多个 initial 块之间是相互独立的。
如果 initial 块内包含多个语句,需要使用关键字 begin 和 end 组成一个块语句。
如果 initial 块内只要一条语句,关键字 begin 和 end 可使用也可不使用.
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 `timescale 1ns/1ns module test ; reg ai, bi ; initial begin ai = 0 ; #25 ; ai = 1 ; #35 ; ai = 0 ; #40 ; ai = 1 ; #10 ; ai = 0 ; end initial begin bi = 1 ; #70 ; bi = 0 ; #20 ; bi = 1 ; end initial begin forever begin #100 ; if ($time >= 1000 ) begin $finish ; end end end endmodule
always 语句 always 语句是重复执行 的。always 语句块从 0 时刻开始执行其中的行为语句;当执行完最后一条语句后,便再次执行语句块中的第一条语句,如此循环反复.
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 `timescale 1ns/1ns module test ; parameter CLK_FREQ = 100 ; parameter CLK_CYCLE = 1 e9 / (CLK_FREQ * 1 e6) ; reg clk ; initial clk = 1'b0 ; always # (CLK_CYCLE/2 ) clk = ~clk ; always begin #10 ; if ($time >= 1000 ) begin $finish ; end end endmodule
在Verilog中,@符号用于always块中的敏感列表中,表示该always块应该对列表中指定的变量进行敏感性检测。敏感性列表中的变量发生改变时,该always块将被激活并执行相应的操作。
具体来说,@(*)表示always块应该对敏感性列表中的所有变量进行敏感性检测,包括输入端口、输出端口和内部变量等。这种方式通常称为“敏感性列表自动感知”,它可以简化设计,并且可以在设计变更时自动更新敏感性列表,从而避免遗漏任何敏感变量。
需要注意的是,always块中的敏感性列表必须是完整的,否则可能导致行为与预期不符。因此,在设计时,应该仔细检查always块的敏感性列表,并确保它包含了所有必要的变量。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 module mux4to1( input [1 :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 @(*) begin if (sel == 2'b00 ) sout_t = p0 ; else if (sel == 2'b01 ) sout_t = p1 ; else if (sel == 2'b10 ) sout_t = p2 ; else sout_t = p3 ; end assign sout = sout_t ; endmodule
4.2 过程赋值 过程性赋值是在 initial 或 always 语句块里的赋值,赋值对象是寄存器、整数、实数等类型。
这些变量在被赋值后,其值将保持不变,直到重新被赋予新值。
阻塞赋值 阻塞赋值属于顺序执行,即下一条语句执行前,当前语句一定会执行完毕。
阻塞赋值语句使用等号 = 作为赋值符。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 initial begin ai = 4'd1 ; bi = 4'd2 ; ai2 = 4'd7 ; bi2 = 4'd8 ; #20 ; ai = 4'd3 ; bi = 4'd4 ; value_blk = ai + bi ; value_non <= ai + bi ; ai2 <= 4'd5 ; bi2 <= 4'd6 ; value_non2 <= ai2 + bi2 ; end
非阻塞赋值 非阻塞赋值属于并行执行语句,即下一条语句的执行和当前语句的执行是同时进行的,它不会阻塞位于同一个语句块中后面语句的执行
非阻塞赋值语句使用小于等于号 <=
作为赋值符
实际 Verilog 代码设计时,切记不要在一个过程结构中混合使用阻塞赋值与非阻塞赋值 。两种赋值方式混用时,时序不容易控制,很容易得到意外的结果。
更多时候,在设计电路时,always 时序逻辑块中多用非阻塞赋值,always 组合逻辑块中多用阻塞赋值;在仿真电路时,initial 块中一般多用阻塞赋值。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 initial begin ai = 4'd1 ; bi = 4'd2 ; ai2 = 4'd7 ; bi2 = 4'd8 ; #20 ; ai = 4'd3 ; bi = 4'd4 ; value_blk = ai + bi ; value_non <= ai + bi ; ai2 <= 4'd5 ; bi2 <= 4'd6 ; value_non2 <= ai2 + bi2 ; end
4.3 时序控制 4.4 语句块 顺序块 顺序块用关键字 begin 和 end 来表示。
顺序块中的语句是一条条执行的。当然,非阻塞赋值除外。
并行块 并行块有关键字 fork 和 join 来表示。
并行块中的语句是并行执行的,即便是阻塞形式的赋值。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 `timescale 1ns/1ns module test ; reg [3 :0 ] ai_sequen, bi_sequen ; reg [3 :0 ] ai_paral, bi_paral ; reg [3 :0 ] ai_nonblk, bi_nonblk ; initial begin #5 ai_sequen = 4'd5 ; #5 bi_sequen = 4'd8 ; end initial fork #5 ai_paral = 4'd5 ; #5 bi_paral = 4'd8 ; join initial fork #5 ai_nonblk <= 4'd5 ; #5 bi_nonblk <= 4'd8 ; join endmodule
嵌套块 顺序块和并行块还可以嵌套使用。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 `timescale 1ns/1ns module test ; reg [3 :0 ] ai_sequen2, bi_sequen2 ; reg [3 :0 ] ai_paral2, bi_paral2 ; initial begin ai_sequen2 = 4'd5 ; fork #10 ai_paral2 = 4'd5 ; #15 bi_paral2 = 4'd8 ; join #20 bi_sequen2 = 4'd8 ; end endmodule
命名块 可以给块语句结构命名。
命名的块中可以声明局部变量,通过层次名引用的方法对变量进行访问。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 `timescale 1ns/1ns module test; initial begin : runoob integer 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 $display ("Now you can stop the simulation!!!" ); stop_flag = 1'b1 ; end #10 ; end endmodule
可以用 disable
关键字来终止命名块的执行,可以用来从循环中退出、处理错误等。
与 C 语言中 break 类似,但是 break 只能退出当前所在循环,而 disable 可以禁用设计中任何一个命名的块。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 `timescale 1ns/1ns module test; initial begin : runoob_d integer i_d ; i_d = 0 ; while (i_d<=100 ) begin : runoob_d2 # 10 ; if (i_d >= 50 ) begin disable runoob_d3.clk_gen ; disable 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
4.5 条件语句 条件语句 条件表达式必须在圆括号中.
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 module mux4to1( input [1 :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 @(*) begin if (sel == 2'b00 ) sout_t = p0 ; else if (sel == 2'b01 ) sout_t = p1 ; else if (sel == 2'b10 ) sout_t = p2 ; else sout_t = p3 ; end assign sout = sout_t ; endmodule
同样可以加上 begin
和 end
来区分嵌套的 if
和 else
的配对关系:
1 2 3 4 5 6 7 8 if (en) begin if (sel == 2 'b1 ) begin sout = p1s ; end else begin sout = p0 ; end end
4.6 多路分支语句 也就是 case
语句.
如:
1 2 3 4 5 6 case (case_expr) condition1 : true_statement1 ; condition2 : true_statement2 ; …… default : default_statement ; endcase
casex/casez 语句 4.7 循环语句 while 循环 如:
1 2 3 while (condition) begin …end
for 循环 1 2 3 for (initial_assignment; condition ; step_assignment) begin …end
repeat 循环 1 2 3 repeat (loop_times) begin …end
forever 循环
4.8 过程连续赋值 assign, deassign assign(过程赋值操作)与 deassign (取消过程赋值操作)表示第一类过程连续赋值语句。赋值对象只能是寄存器或寄存器组,而不能是 wire 型变量。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 module dff_assign( input rstn, input clk, input D, output reg Q ); always @(posedge clk) begin Q <= D ; end always @(negedge rstn) begin if (!rstn) begin assign Q = 1'b0 ; end else begin deassign Q ; end end endmodule
force, release 使用方法和效果,和 assign 与 deassign 类似,但赋值对象可以是 reg 型变量,也可以是 wire 型变量。
因为是无条件强制赋值,一般多用于交互式调试过程,不要在设计模块中使用。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 `timescale 1ns/1ns module test ; reg rstn ; reg clk ; reg [3 :0 ] cnt ; wire cout ; counter10 u_counter ( .rstn (rstn), .clk (clk), .cnt (cnt), .cout (cout)); initial begin clk = 0 ; rstn = 0 ; #10 ; rstn = 1'b1 ; wait (test.u_counter .cnt_temp == 4'd4 ) ; @(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 ; end initial begin clk = 0 ; forever #10 clk = ~ clk ; end always begin #1000 ; if ($time >= 1000 ) $finish ; end endmodule
5.1 模块与端口 结构建模方式有 3 类描述语句: Gate(门级)例化语句,UDP (用户定义原语)例化语句和 module (模块) 例化语句.
模块 模块是 Verilog 中基本单元的定义形式,是与外界交互的接口.
定义如:
1 2 3 4 5 module module_name # (parameter_list ) (port_list) ; Declarations_and_Statements ; endmodule
端口 端口是模块与外界交互的接口。对于外部环境来说,模块内部是不可见的,对模块的调用只能通过端口连接进行。
端口列表 模块的定义中包含一个可选的端口列表,一般将不带类型、不带位宽的信号变量罗列在模块声明里.
如:
1 2 3 module pad ( DIN, OEN, PULL, DOUT, PAD );
一个模块如果和外部环境没有交互,则可以不用声明端口列表.
如:
1 2 3 module test ; // 直接分号结束 ...... // 数据流或行为级描述 endmodule
端口声明 端口信号在端口列表中罗列出来以后,就可以在模块实体中进行声明了。
根据端口的方向,端口类型有 3 种:
输入 (input)
输出 (output)
双向端口(inout)
input
、inout
类型不能声明为 reg 数据类型,因为 reg 类型是用于保存数值的,而输入端口只能反映与其相连的外部信号的变化,不能保存这些信号的值。
output
可以声明为 wire 或 reg 数据类型。
先声明端口类型, 再声明端口数据类型, 如:
1 2 3 4 5 6 7 8 9 10 11 input DIN, OEN ; input [1 :0 ] PULL ; inout PAD ; output DOUT ; wire DIN, OEN ; wire [1 :0 ] PULL ; wire PAD ; reg DOUT ;
也可直接在端口列表中表明端口的类型声明:
1 2 3 4 5 6 module pad( input DIN, OEN , input [1 :0 ] PULL , inout PAD , output reg DOUT );
5.2 模块例化 也就是调用一个模块. (在一个模块中调用另一个模块)
有点像面向对象中的创建实例.
Verilog 模块例化是指在 Verilog 代码中创建一个新的模块实例,并将其连接到当前模块中的信号线。在 Verilog 中,模块是一组硬件电路的描述,可以像子程序一样调用。模块例化可用于将多个模块组合在一起,以构建更复杂的硬件电路。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 module my_module(input clk , input reset , output reg data ) ; endmodulemodule top_module(input clk , input reset ) ; my_module my_instance(.clk (clk ) , .reset(reset), .data(output_data)); endmodule
在这个例子中,my_module
是一个包含一个时钟输入 (clk),一个复位输入 (reset) 和一个数据输出 (data) 的模块。在 top_module
中,我们实例化了一个 my_module 模块,并将 clk 和 reset 输入连接到 top_module 的输入信号线上。输出信号线 output_data 必须先在 top_module
中定义,然后才能在实例化语句中使用。
(这里是命名端口连接)
input 端口正常悬空时,悬空信号的逻辑功能表现为高阻状态(逻辑值为 z)。
用 generate 进行模块例化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 module full_adder4( input [3 :0 ] a , input [3 :0 ] b , input c , output [3 :0 ] so , output co ); 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
层次访问 Verilog 中,通过使用一连串的 .
符号对各个模块的标识符进行层次分隔连接.
如:
1 2 3 4 5 6 7 8 //u _n1模块中访问u_n3模块信号: a = top.u_m2.u_n3.c ;//u _n1模块中访问top模块信号if (top.p == 'b0) a = 1' b1 ;// top模块中访问u_n4模块信号 assign p = top.u_m2.u_n4.d ;
5.3 带参数例化 参数覆盖有两种方式:
defparam 语句 如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 defparam u_ram_4x4.MASK = 7 ; ram_4x4 u_ram_4x4 ( .CLK (clk), .A (a[4 -1 :0 ]), .D (d), .EN (en), .WR (wr), .Q (q) );module ram_4x4 ( input CLK , input [4 -1 :0 ] A , input [4 -1 :0 ] D , input EN , input WR , output reg [4 -1 :0 ] Q ); parameter MASK = 3 ; reg [4 -1 :0 ] mem [0 :(1 <<4 )-1 ] ; always @(posedge CLK) begin if (EN && WR) begin mem[A] <= D & MASK; end else if (EN && !WR) begin Q <= mem[A] & MASK; end end endmodule
带参数模块例化 将新的参数值写入模块例化语句.
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ram #(.AW(4), .DW(4)) u_ram ( .CLK (clk), .A (a[AW-1 :0 ]), .D (d), .EN (en), .WR (wr), .Q (q) );module ram #( parameter AW = 2 , parameter DW = 3 ) ( input CLK , input [AW-1 :0 ] A , input [DW-1 :0 ] D , input EN , input WR , output reg [DW-1 :0 ] Q ); reg [DW-1 :0 ] mem [0 :(1 <<AW)-1 ] ; always @(posedge CLK) begin if (EN && WR) begin mem[A] <= D ; end else if (EN && !WR) begin Q <= mem[A] ; end end endmodule
6.1 函数 函数 函数只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块.
特点如:
不含有任何延迟、时序或时序控制逻辑
至少有一个输入变量
只有一个返回值,且没有输出
不含有非阻塞赋值语句
函数可以调用其他函数,但是不能调用任务
声明格式:
1 2 3 4 5 function [range-1:0] function_id ; input_declaration ; other_declaration ; procedural_statement ; endfunction
调用格式:
1 function _id(input1 , input2 , …) ;
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 module endian_rvs #(parameter N = 4 ) ( input en, input [N-1 :0 ] a , output [N-1 :0 ] b ); reg [N-1 :0 ] b_temp ; always @(*) begin if (en) begin b_temp = data_rvs(a); end else begin b_temp = 0 ; end end assign b = b_temp ; function [N-1 :0 ] data_rvs ; input [N-1 :0 ] data_in ; parameter MASK = 32'h3 ; integer k ; begin for (k=0 ; k<N; k=k+1 ) begin data_rvs[N-k-1 ] = data_in[k] ; end end endfunction endmodule
automatic 函数 在 Verilog 中,一般函数的局部变量是静态的,即函数的每次调用,函数的局部变量都会使用同一个存储空间。若某个函数在两个不同的地方同时并发的调用,那么两个函数调用行为同时对同一块地址进行操作,会导致不确定的函数结果。
Verilog 用关键字 automatic
来对函数进行说明,此类函数在调用时是可以自动分配新的内存空间的,也可以理解为是可递归的.
如:
1 2 3 4 5 6 7 8 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
6.2 任务 任务更像一个过程,不仅能完成函数的功能,还可以包含时序控制逻辑.
和函数一样,任务(task)可以用来描述共同的代码段,并在模块内任意位置被调用.
任务和函数的区别:
声明格式:
1 2 3 4 task task_id port_declaration procedural_statement endtask
如:
1 2 3 4 5 6 task xor_oper_iner( input [N-1 :0 ] numa, input [N-1 :0 ] numb, output [N-1 :0 ] numco ) ; #3 numco = numa ^ numb ;endtask
调用格式:
1 task_id(input1 , input2 , …,outpu1 , output2 , …) ;
任务调用时,端口必须按顺序对应。
输入端连接的模块内信号可以是 wire 型,也可以是 reg 型。输出端连接的模块内信号要求一定是 reg 型.