包括常用的Verilog语法,以及个人遇到过的其他语法,便于书写代码时查找有关语法,不适合初学者学习Verilog
Verilog的基本语言要素
数据表示
-
Verilog 的数据类型支持4值逻辑:0,1以及
- x/X:不确定或未知,当用作条件判断时,表示不关心。 在实际实现的电路中,并没有x值,只存在0,1, z三种状态
- z/Z:高阻状态,通常用来对三态总线进行建模
在综合工具中,或者在实际实现的电路中,并没有x值,只存在0,1,z三种状态;
模拟工具中,x值多表示信号没被赋值。 -
数据常量:<位宽><‘进制><数字>,进制有二(b/B),八(o/O),十(d/D)和十六进制(h/H)
- 不说明位宽时,默认为32位, 无进制默认为十进制
- 下划线_可用来分割数以提高可读性,如16’b1011_0001_1111_1010
- 负数只需在位宽表达式前加一个减号,如**-8’d5(5的补数)**
Data = 0; // 赋全0 Data = ’bz; // 赋全Z Data=~0; // 赋全1
-
字符串型:每个字符占8位
reg[8*19:1] string_value;//19个字节宽 initial string_value=“Hello Verilog World”;
数据类型
Verilog共有19种数据类型: wire\reg\parameter\integer\time\large\medium\scalared\small\tri\trio\tril\triand\trir\trireg\vectored\wand\wor等
常用数据类型
-
线网(net)型:wire,tri,可以理解为实际电路中的导线、连线
综合工具支持net类型中wire、tri类型的综合。
其他的子类型前端逻辑设计很少使用,与基本逻辑单元工艺库有关。
-
寄存器型(变量型):reg,表示临时存储数据的变量,具有记忆特性;但不能就认为是实际电路中的寄存器
-
在过程语句块initial或always中被赋值的变量都必须定义为寄存器型。
-
reg:可定义的无符号整数变量,可以是标量(1位)或矢量,是RTL建模中最常用的寄存器类型.
-
integer :32位有符号整数变量,通常用作不会由硬件实现的数据处理。
-
real:双精度的带符号浮点变量,用法与integer相同。
-
time :64位无符号整数变量,用于仿真时间的保存与处理。
-
realtime:与real内容一致,但可以用作实数仿真时间的保 存
与处理
-
-
数组:由多个数据组成,每个数据位宽为n
-
Verilog-1995:只允许定义一维数组,数组声明仅限于reg,integer和time寄存器类数据类型,常用作存储器,为RAM和ROM行为级建模。如
reg [15:0] MEM [0:1023]
,1K × 16b的存储器 -
Verilog-2001:所有寄存器型和线网型变量都可用于声明数组 (reg, integer, time, real, realtime;所有net类型);允许定义多维数组,但没有定义EDA工具如何存储数组元素。如reg [31:0] array2 [0:255][0:15]
-
Verilog定义的数组一次只能访问数组的一个元素,或者一个元素的1位或部分位。为数组元素赋值需要定义索引,索引可以是整数或表达式。
-
System Verilog:压缩数组和非压缩数组
-
-
常量(参数):parameter,localparam,
-
其他SV扩展的相关类型:SV扩展了原有的整数、reg等变量数据类型(logic、bit等);增加了用户自定义(typedef)、枚举(enum)、结构体(struct)等很多类C的数据类型
操作符
- 按操作数数目:单目、双目、三目
- 按功能分成:
- 算术操作符(+、-、*、/、%、**)
- 关系操作符(>、<、>=、<=)
- 逻辑操作符(&&、||、!)
- 条件操作符(? : )
- 位操作符(~、|、^、&、^~)
- 归约运算符(&、~&、|、~|、^、~^)
- 移位操作符(<<、>>、<<<、>>>)
- 相等操作符(==、!=、===、!==)
- 赋值操作符(=、 <=)
- 拼接及复制操作符({ }、 {{ }})
- SV增加了递增、递减、一系列赋值操作符
可综合的操作符如下表所示
运算符类型 | 操作数 | 符号 | 结果 |
---|---|---|---|
位运算符 拼接及复制操作符 | 单目 | ~ { } {{ }} | |
逻辑运算符 | 单目 | ! | 1 bit |
算术运算符 | 单目 | + - (表示正负) | |
算术运算符 | 双目 | * / + - | |
移位运算符 | 双目 | << >> | |
关系运算符 | 双目 | > < >= <= | 1 bit |
相等运算符 | 双目 | == != | 1 bit |
位运算符 | 双目 | &, ~&, ^, ^~, |, ~| | |
缩减运算符 | 单目 | &, ~&, ^, ^~, |, ~| | 1 bit |
逻辑运算符 | 双目 | &&, || | 1 bit |
条件运算符 | 三目 | ?: |
算术操作符
注意integer和reg类型在算术运算时的差别。integer是有符号数,而reg是无符号数
归约操作符
对操作数的所有位进行位操作
全等操作符
===:按位比较,包括x、z值的比较,每位都必须相等,结果只能为true或false。
!==: 按位比较,包括x、z值的比较,比较两个值是否不等,结果只能为true或false。
和=的区别:后者比较两个数是否完全相等,对x和z都要进行比较,两个操作数必须完全一致,结果才是1(比较结果不是0,就是1,无不定状态)
复制操作符{{}}
复制一个变量或在{ }中的值,{n{m}}将m重复n次。比如,{3{2’b10}} = 6’b101010
+: 和 -:
+:
:变量[起始地址 +: 数据位宽] <–等价于–> 变量[(起始地址+数据位宽-1) : 起始地址]
data[0 +: 8] <--等价于--> data[7:0]
data[15 +: 2] <--等价于--> data[16:15]
- 变量[结束地址 -: 数据位宽] <–等价于–> 变量[结束地址 : (结束地址-数据位宽+1)]
data[7 -: 8] <--等价于--> data[7:0]
data[15 -: 2] <--等价于--> data[15:14]
module定义
端口和数据类型声明、模块实例化
module mult_acc(out, ina, inb, clk, reset_n);
// 输入输出端口声明: Verilog-2001
input [DATA_WIDTH-1:0] ina, inb;
input clk, reset_n;
output reg [2*DATA_WIDTH-1:0] out;
// 参数声明
parameter DATA_WIDTH = 8;
/* 另外两种端口和参数声明的方式
1. Verilog-1995
module mult_acc(out, ina, inb, clk, reset_n);
// 声明端口方向和位宽
input [DATA_WIDTH-1:0] ina, inb;
input clk, reset_n;
output [2*DATA_WIDTH-1:0] out;
// 声明端口类型
reg [2*DATA_WIDTH-1:0] out;
// ina和inb不再声明类型,默认为wire
parameter DATA_WIDTH = 8;
2. Verilog-2001
module mult_acc #(
parameter DATA_WIDTH = 8
) (
input [DATA_WIDTH-1:0] ina, inb;
input clk, reset_n;
output reg [2*DATA_WIDTH-1:0] out;
);
*/
// 功能实现
wire [2*DATA_WIDTH-1:0] adder_out, mult_out;
assign adder_out = out + mult_out;
always @(posedge clk) begin
if (~reset_n) begin
out <= {(2*DATA_WIDTH-1){1'b0}};
end
else begin
out <= adder_out;
end
end
// 模块实例化
multa #(
.DATA_WIDTH(DATA_WIDTH)
) u1(
.in_a(ina),
.in_b(inb),
.m_out(mult_out)
);
endmodule
- 在端口声明中被声明为input或inout的端口变量只能被定义为线网型变量;声明为output的端口变量可以显式定义为寄存器型变量(缺省为线网型变量)。
- 实例化模块时,与模块输入端口相连的信号可以是寄存器型或线网型,与模块输出端口相连的信号只能是线网型
行为级功能描述
assign:数据流建模
<assign> [strength] [#delay] <net_name> = <expressions>
- [#delay] [strength] 是可选项, delay缺省为0,强度设置缺省时为strong1, strong0
- 右边expressions(RHS)是带操作符的逻辑表达式或函数返回值,可以有线网型、寄存器型变量,如
assign sum= function_value(a,b)
- 一旦RHS中操作数发生了变化,表达式值会被重新计算,并在#delay定义的时延后更新左手边net_name变量
- <net_name>可以是线网(net)以及线网的连接,如
assign {carry_out, sum_out} = ina+inb+carry_in
- 可在等式左边有一个简单延时说明**(仿真时用)**,如
assign #10 a=10;
always/initial:过程结构
- initial只执行一次,always循环执行
- 每个initial或always语句都代表一个独立的执行过程,多个initial或always是完全并行的
- 只有寄存器型数据变量能够在这两种结构中被赋值,变量在被赋新值前保持原有值不变
- 过程结构中可包含以下几种行为建模语法结构:
- 时序控制:控制过程结构中语句的执行。
- 过程赋值语句:描述过程结构中的变量的赋值:有阻塞赋值/非阻塞赋值
- 行为建模语句:描述块的功能, 有循环/分支/条件等语句
initial结构用于仿真,因此其介绍放在章节 testbench编写
中
时序控制
过程结构中的时序控制有三类:简单延迟、使用wait表示的事件敏感控制和基于事件的时序控制,其中前两者只能用于仿真,因此其介绍放在章节 testbench编写
中。本小节只介绍基于事件的时序控制。
-
电平敏感:敏感列表内多个电平敏感信号用 or连接
always@(a or b or c) // Verilog-1995,常描述组合逻辑,需列出所有输入信号 always @(a, b, c) // Verilog-2001,可以使用','而不是or always @(*) // Verilog-2001,可以使用@(*)来代替所有输入信号列表
-
沿敏感:用关键字posedge和negedge限定信号敏感边沿,敏感表中可以有多个信号,用关键字or连接
always@(posedge clk or negedge reset)//常描述时序逻辑
过程赋值语句
-
阻塞赋值(blocking) “= ”:连续的多条阻塞赋值操作是顺序完成的,一般用于组合逻辑
// b值拷贝到a然后回传 begin a=1;b=3; a = #5 b; b = #5 a; #10 $display(a, b); // 输出3,3 end // a和b值安全交换 begin a=1;b=3; fork a = #5 b; b = #5 a; #10 $display(a, b); // 输出3,1 join end
-
非阻塞赋值(non-blocking) “<= “:连续的非阻塞赋值是同时完成操作的,一般用于时序逻辑
行为建模语句
条件语句
if(expression)
true_statement;
[else
fault_statement;]
//表达式为逻辑0、x、z时被认为是假
- if…else语句只能用在initial/always过程结构或任务、函数中,用于行为建模
多分支语句
-
case语句
always@(x or z) case(alu_control) 2'b00: y=x+z; 2'b01: y=x-z; 2'b10: y=x*z; default: y=0; // 避免产生隐含的锁存器 endcase
-
casez语句:认为表达式中的z值和?是无关值(即对应为无需匹配)
module prio_encoder_casez(input [3:0] r,output reg [2:0] y); always @* casez (r) 4'b1???: y = 3'b100; 4'b01??: y = 3'b011; 4'b001?: y = 3'b010; 4'b0001: y = 3'b001; 4'b0000: y = 3'b000; // 这里可以使用default endcase endmodule
-
casex语句:认为表达式中的z,x值和?为无关值
casex(encoder) 4'b1xxx: high_lvl = 3; 4'b01xx: high_lvl = 2; 4'b001x: high_lvl = 1; 4'b0001: high_lvl = 0; default: high_lvl = 0; endcase
循环语句
一共四类循环语句,即repeat、forever、while和for,一般只能用于initial/always/task/function
-
repeat:执行固定次数的循环,一般用于仿真
// 示例一 integer count; initial begin count=0; repeat(128) begin $display(“Count=%d”, count); count= count+1; end end // 示例二:两个8位数的二进制数的乘法 module mult_repeat ( input [8:1] op0, input [8:1] op1, output reg [16:1] result ); reg [16:1] tempa; reg [8:1] tempb; always @* begin result = 0; tempa = op0; tempb = op1; repeat(8) begin if(tempb[1]) result = result + tempa; tempa = tempa << 1; tempb = tempb >> 1; end end endmodule
-
forever:无穷循环,常用来产生周期性的波形,作为仿真激励信号。一般用在initial过程语句中,要退出只能采用强制退出循环的方法,如disable语句或执行$finish
reg clk ; initial begin clk = 0 ; forever begin clk = ~clk; #5; end end
-
while
// 两个8位数的二进制数的乘法 module mult_while (input [8:1] op0,input [8:1] op1,output reg [16:1] result); interger i=1; always @* begin result = 0; while(i<=8) if(op1[i]) result = result + (op0 << (i - 1)); i = i + 1; end endmodule
-
for
// 示例一:两个8位数的二进制数的乘法 module mult_for (input [8:1] op0, input [8:1] op1,output reg [16:1] result ); integer i; always @* begin result = 0; for(i=1;i<=8;i=i+1) if(op1[i]) result = result + (op0 << (i-1)); end endmodule // 示例二:行波进位加法器 module RippleCarryAdder #( WIDTH = 32 ) ( input [WIDTH-1:0] a, b, input cin, output reg [WIDTH-1:0] sum, output reg cout ); integer i; always @* begin sum = 0; cout = cin; for(i = 0; i < WIDTH; i = i + 1) begin sum[i] = ((a[i]) ^ b[i]) ^ cout; cout = (b[i]&cout) | (a[i]&cout) | (b[i]&a[i]); end end endmodule
task和function
- 用于模块中需要重复书写的代码段,使用时只需进行调用。
- 任务和函数必须在module内定义和调用,其作用范围仅限于该模块。
- 任务、函数内定义的变量都是局部变量,不会与其他变量冲突,所有输入/输出都是寄存器变量。
- 任务/函数执行完成后才返回结果。
- 使用任务、函数可将较大行为级描述划分为较小代码段,增强代码可读性。
没有仔细看,碰到的时候再说
task
task的可综合性:不同工具支持不一样,尽量只用于testbench
下面给出task的一个使用示例
localparam clk_period = 10;
localparam rst_delay = clk_period * 2;
reg clk, reset;
initial clk = 0;
always #(clk_period/2) clk = ~clk;
initial begin
reset = 1'b1;
#(rst_delay) reset = 1'b0;
end
reg [15:0] a;
reg [15:0] b;
initial begin
a = 0; b = 0;
#(rst_delay);
$display("######## Start Testing ########");
start_test(16'd1, 16'd2);
$display("######## Testing Done ########");
$finish;
end
task start_test;
input [15:0] ina;
input [15:0] inb;
#(clk_period + 1);
a = ina;
b = inb;
#(clk_period);
endtask
function
可综合
generate语句
在Verilog-2001中新增了语句generate,通过generate循环,可以完成一个模块的多次例化
// 示例一
//1bit width buffer_1
module buffer_1(
input wire in,
output wire out
);
assign out = ~in;
endmodule
//8bit width buffer
module buffer_8(
input wire[7:0] din,
output wire[7:0] dout
);
// Generate block
genvar i;
generate
for(i=0; i<8; i=i+1) begin:BLOCK1
buffer_1 buffer_1_1(.in(din[i]), .out(dout[i]));
end
endgenerate
endmodule
// 示例二
// 进位选择加法器,使用了上面给出的RippleCarryAdder模块
module CarrySelectAdder #(
WIDTH = 32, BLK_WIDTH = 4
) (
input [WIDTH-1:0] a, b,
input cin,
output reg [WIDTH-1:0] sum,
output cout
);
localparam BLK_NUM = WIDTH/BLK_WIDTH;
wire [BLK_NUM - 1:0] BLK_Cin;
wire [BLK_NUM - 1:0] BLK_Cout0, BLK_Cout1;
reg [BLK_NUM - 1:0] BLK_Cout;
wire [WIDTH-1:0] sum0, sum1;
assign cout = BLK_Cout[BLK_NUM - 1];
assign BLK_Cin = {BLK_Cout[BLK_NUM-2:0], cin};
genvar i;
generate
for(i = 0; i < BLK_NUM; i = i + 1) begin
RippleCarryAdder #(.WIDTH(BLK_WIDTH)) myadder0(.a(a[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .b(b[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .cin(1'b0), .sum(sum0[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .cout(BLK_Cout0[i]));
RippleCarryAdder #(.WIDTH(BLK_WIDTH)) myadder1(.a(a[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .b(b[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .cin(1'b1), .sum(sum1[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .cout(BLK_Cout1[i]));
always@* begin
case(BLK_Cin[i])
1'b0: {sum[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH], BLK_Cout[i]} = {sum0[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH], BLK_Cout0[i]};
1'b1: {sum[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH], BLK_Cout[i]} = {sum1[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH], BLK_Cout1[i]};
endcase
end
end
endgenerate
endmodule
门级结构描述
-
概念:通过基本的逻辑门基元互连来描述电路称为门级建模
-
优点:门级描述在实际电路和模型间提供了一种更紧密的点对点映射方法。
-
特点
- 行为级描述的综合结果满足不了性能、面积、功耗等指标要求时,才手工进行精确的电路级设计,建立门级模型。
- 很直观,但不适合大规模复杂系统建模。
-
Verilog提供一个由该语言定义的26个门级和开关级基元组成的集合
-
门级基元包括4类,有14个门类型
-
多输入门:一个标量输出和多个标量输入,常用的门类型关键字: and, nand, or, xor, nor, xnor;
实例化方法:
多输入门 实例名(输出,输入1,…输入n);
-
多输出门:一个标量输入和一个或多个标量输出,buf, not
实例化方法:
多输出门 实例名(输出1,输出2,…输出n,输入);
-
三态门:bufif0, bufif1, notif0, notif1
-
上拉门,下拉门:Pulldown, pullup
-
-
开关级基元
- mos开关( MOS switches):nmos, pmos, cmos, rcmos, rnmos, rpmos
- 双向开关(Bidirectional pass switches):tran, tranif1, tranif0, rtran, rtranif1, rtranif0
-
预编译指令
- 所有预编译指令都以反引号`开头
- Verilog常用预编译命令
- `define 和`undef :宏定义和宏取消
- `timescale 时间尺度指令
- `ifdef、`else、`endif:条件编译指令
- `include :文件包含
`define 和`undef
-
和C语言的含义一样
-
使用`define的意义:
- 使用有意义的标志符来取代程序中反复出现含义不明显的字符串或整数常量,增加程序的可读性
- 使用一个较短的标志符替代反复出现的较长的字符串。
- 和条件编译命令一起,实现参数化设计
-
使用示例
`define BYTE_SIZE 8 `define WORD_SIZE (BYTE_SIZE*2) reg [`WORD_SIZE-1] data;
`timescale
-
用来说明该命令后模块的仿真时间单位和时间精度
-
使用形式:`timescale 仿真时间单位/时间精度
-
时间单位
s 秒(1s) ms 毫秒( 1 0 − 3 10^{-3} 10−3s) us 微秒( 1 0 − 6 10^{-6} 10−6s) ns 纳秒( 1 0 − 9 10^{-9} 10−9s) ps 皮秒( 1 0 − 12 10^{-12} 10−12s) fs 飞秒( 1 0 − 15 10^{-15} 10−15s)
条件编译指令
- `ifdef, `ifndef, `else, `elsif, `endif
- 在不同条件下对不同的源码进行编译;目的是生成不同时序或结构;可以有条件地编译Verilog 模块、语句、语句块、声明(如define )以及其他编译指令
- 常用于参数化IP软核设计
`include
- 一个源文件可将另外的源文件内容全部包含进来,使用时包含的源文件内容全部插入到文件包含语句处
- 定义形式:`include “filename”
- filename可以是相对路径名,也可是绝对路径名
testbench编写
一个示例
`timescale 1ns/1ps // 测试时间基本单位为1ns,精度为1ps
module tb_mult_acc;
reg clk, reset_n;
reg [7:0] ina, inb;
wire [15:0] out;
// 系统任务和系统函数关键字前必须加上$
initial $monitor($time,"MAC=%h", out);
// 时钟生成
initial clk = 0;
always #5 clk = ~clk; // 由于测试时间基本单位为1ns,因此时钟为100MHz
// 复位信号生成
initial begin
reset_n = 1'b0;
#20 reset_n = 1'b1;
end
// 输入激励
initial begin
ina = 8'b0; inb = 8'b0;
#20 ina = 8'd1; inb = 8'd2;
#10 ina = 8'd2; inb = 8'd3;
#10 ina = 8'd3; inb = 8'd4;
#10 ina = 8'd4; inb = 8'd5;
end
// 退出仿真器,停止仿真
initial #55 $finish;
mult_acc # (
.DATA_WIDTH(8)
) test1(
.clk(clk),
.reset_n(reset_n),
.ina(ina),
.inb(inb),
.out(out)
);
endmodule
时序控制:延时设置
可以使用#delay来设置延时
-
assign语句的延迟设置:用于控制任一操作数发生变化到左边被赋予新值之间的时间延迟。指定赋值延迟有三种方法:
-
普通赋值延迟
wire Out; assign #10 Out = in1 & in2;
-
隐式赋值延迟
wire #10 Out = in1 & in2;
-
线网申明延迟
wire #10 Out; assign Out = in1 & in2;
-
线网延迟设置同样用于门延迟
-
-
initial或always语句块:在testbench中使用简单延时(#延时)施加激励,不可综合
always @( sl or a or b) if (! sl) #10 out = a; // 从a到out延时10个时间单位 else #12 out = b; //从b到out延时12个时间单位 //在简单延时中可以使用参数parameter parameter cycle = 20; initial clk = 0; always #(cycle/2) clk = ~clk;
时序控制:wait语句
wait用于行为级代码中电平敏感的时序控制,不可综合,只用于testbench等行为级仿真
always @(a or b)
begin
wait (!enable) // 当enable为低电平时执行加法
out = a + b;
end
initial结构
- 从仿真0时刻开始执行,每条initial只执行一次
- 如有多个initial块,则每个块的执行是并行且独立的
- 如果initial语句内包含2条或以上语句,必须使用begin …end或fork…join封装
- Verilog中定义变量时内嵌初始化和在initial语句0时刻对同一变量赋值会产生不确定性;也不要在不同initial中为同一变量赋值;在initial 中被赋值的只能是寄存器型变量。
- 用于仿真过程中变量的初始化、监控等进程,如测试模块,虚拟模块的编写,产生仿真测试激励信号,信号初始化等仿真环境。
- 不可综合
块语句
顺序块/并行块
两种过程结构(initial/always)或其他行为语句(if、for、case)中如果存在两条或两条以上的语句需要用块语句进行封装,即begin-end和fork-join。
-
顺序块:语句置于关键字begin和end之间,块中的语句常以顺序方式执行
- 使用阻塞赋值“=”赋值时,语句块内的语句按书写的次序执行;如果语句包含延迟或事件控制,则延迟总是相对于前面的语句执行完成的仿真事件
- 使用非阻塞赋值“<=”赋值时,语句块内的多条语句可并发执行
-
并行块:关键字fork和join之间的是并行块语句,块中的语句并行执行,常用于testbench描述
- 语句块内的语句并行执行;语句顺序无关紧要
- 如果fork语句块前有延时,延时的长度都是相对该块的开始时间而言
- fork…join块不可综合
-
顺序块和并行块可以混合使用,可以自我嵌套或互相嵌套
initial fork #10 a = 1; #15 b = 1; begin #20 c = 1; #10 d = 1; end #25 e = 1; join
有名块
有名块:定义了标识名称的顺序语句块或并行语句块, 即需在begin或fork后面加 :block_name
begin:Continue//该begin…end块定义为Continue
statement1;
statement2;
……
end
- 定义了新的层次范围,可以定义有名块内局部变量
- 允许定义的块被其他语句调用,如disable语句、generation块结构
系统任务和系统函数
是Verilog中预先定义好的,用于控制和检测仿真模拟过程的任务或函数,以$开头关键字,只能用于仿真验证
文本输出的系统任务
$display和$write
-
输出参数列表中信号的当前值,$display输出时会自动换行,$write不会换行
-
语法:$display/write([“format_specifiers”,] <argument_ list>);
-
示例:
$display($time, "%b \t %h \t %d \t %o", sig1, sig2, sig3, sig4); $display($time, "%b \t", sig1, "%h \t", sig2, "%d \t", sig3, "%o", sig4);
-
$display/$write支持二进制、八进制、十进制和十六进制的显示任务。缺省基数为十进制
$display (sig1, sig2, sig3, sig4); // $write $displayb (sig1, sig2, sig3, sig4); // $writeb $displayo (sig1, sig2, sig3, sig4); // $writeo $displayh (sig1, sig2, sig3, sig4); // $writeh
-
format_specifiers中会用到的显示格式符
%h hex %o octal %d decimal %b binary %c ASCII %s string %v strength %m module %t time %% 输出% %0d 无前导0的十进制数
$strobe
-
选通监视,提供一种显示数据的机制,在同一时间单元的所有赋值语句执行完毕后才执行
-
语法:$strobe([“format_specifiers”,] <argument_ list>);
-
$display显示的变量值是执行到该语句时变量的值,而$strobe显示的是执行该语句的仿真时刻的所有语句执行完后的结果
module tb; reg flag; reg [31: 0] data; initial begin $writeb("writeb", ,"%d", $time, ,"%h \t", data, , flag, "\n"); #15 flag = 1; data = 16; $displayh("displayh", ,$time, ,data, , flag); end initial begin #10 data = 20; // 注意下面两条语句的输出 $strobe("strobe", ,$time, , data); $display("display", ,$time, , data); data = 30; end endmodule /* 下面是输出 writeb 0 xxxxxxxx x display 10 20 strobe 10 30 displayh 000000000000000f 00000010 1 */
-
strobe/$strobeb/$strobeo/$strobeh
$monitor
-
持续检测(变量列表中)一个或多个信号的变化,每当被监测的信号值发生变化,就将在当拍结束时显示该信号值。
-
语法:$monitor([“format_specifiers”,] <argument_ list>);
-
显示参数定义方式与$display同
-
$monitor/$monitorb/$monitoro/$monitorh
-
将$monitor写到initial块中就可以在整个仿真过程对指定的变量值进行监测。
-
与$display不同,在仿真过程中只能有一个$monitor语句起作用,后面的$monitor将覆盖前面的$monitor。只有新的$monitor的参数列表中的信号被监视,而前面的$monitor的参数则不被监视
但是vivado好像不是这样,前面的$monitor的参数貌似也会被监视
module tb; reg flag; reg [31: 0] data; initial fork #10 data = 5; #15 flag = 1; #15 data = 16; join initial begin $monitor("Monitor(data)",, "%d", data); #11; $monitor("Monitor(flag)",, "%d", flag); end endmodule /* 下面是vivado仿真器的输出 Monitor(data) x Monitor(data) 5 Monitor(flag) x Monitor(flag) 1 Monitor(data) 16 */
-
$monitoron和$monitoroff任务用来启动和关闭监控功能。仿真开始时,仿真器的默认状态是$monitoron,使用户可以在仿真时只监视特定时间段的信号。
-
$monitor和$strobe一样,显示参数列表中信号的稳定状态值,也就是在仿真时间前进之前显示赋值后信号的值。参数列表中信号值的任何变化将触发$monitor 。但$time, $stime, $realtime不能触发。
显示层次
-
在任一显示任务中使用%m格式选项,可以显示任何级别的层次
-
当一个模块的多个实例同时执行同一段代码时, 使用%m选项可以区分哪个模块实例在进行输出
module tb; buff b0 (.buf_in(1'b0), .buf_out()); endmodule module buff ( input buf_in, output buf_out ); wire a; inv i0 (.in(buf_in), .out(a )); inv i1 (.in(a ), .out(buf_out)); initial $display("Inside hierarchy %m"); endmodule module inv ( input in, output out ); assign out = ~in; initial $display("Inside hierarchy %m",, out); endmodule /* 下面是输出 Inside hierarchy tb.b0.i0 1 // 实例i0的out值 Inside hierarchy tb.b0.i1 0 // 实例i1的out值 Inside hierarchy tb.b0 */
-
%m选项无需参数
获取当前仿真时间的系统函数
- $time,$realtime,$stime:返回当前仿真时间
- 返回值使用`timescale定义的模块仿真时间单位和精度
- $time返回一个64位的整数时间值,表示当前时间是第几个时间单位
- $realtime返回的结果是实数时间值,是更为精确的仿真时间
- $stime返回一个32位整数时间值。(对大于 2 32 2^{32} 232的时间,返回模 2 32 2^{32} 232的值。使用它可以节省显示及打印空间)
文件I/O系统任务
写文件操作任务
-
用于写文件的系统任务(常用于保存验证的输出结果)
-
用法:
-
文件的打开和关闭: $fopen,$fclose
integer descriptor;//32位整数,标识一个打开的文件 descriptor =$fopen(“filename”); /* 1. 系统任务$fopen打开指定文件,并返回一个32位整数给描述符descriptor(文件指针) 2. 32bit的descriptor中的一位对应一个通道;标准输出占用32bit多通道描述符的最低位,且始终是打开的;最高位是保留位。因此Verilog允许最多同时打开30个用户文件。 3. $fclose(descriptor); 一旦文件被关闭descriptor中的对应位被清0,不能再写入文件 */
-
$fdisplay/$fwrite/$fmonitor/$fstrobe:第一个参数是文件描述符,其余为带有参数表的格式定义,与对应的显示任务相同
-
读文件系统任务
-
$readmemb,$readmemh:分别用于将文件中的二进制或十六进制数读到存储器(即寄存器数组中),用数据文件对存储器初始化
- 数据文件格式:必须是二进制或十六进制数,数与数之间用空白符(空格、换行、换表符等)隔开,一个数的各位之间可用下画线
-
语法表达式:$readmemb/$readmemh(“filename”,memname[,start address][,finish address];
-
使用示例
reg[7:0] mem[1:256]; initial begin $readmemh("mem.data",mem); $readmemh("mem.data",mem,16); $readmemh("mem.data",mem,128,1); end
暂停和退出仿真的任务
- $stop:暂停仿真过程
- $stop;//同stop(0),暂停时不输出任何信息
- $stop(1):暂停时输出当前仿真时刻和暂停处在程序中的位置;
- $stop(2):除了完成stop(1),还给出运行统计数据行,如仿真程序占用内存大小和CPU时间;
- $finish:退出仿真过程
- $finish;//同finish(0)
- $finish(n);//n=0,1,2;含义同$stop的参数n。
随机函数
- $random([SEED]):// 返回32bit的整数,SEED参数可选,SEED可以是reg、integer或time变量
- $random % b;其中b>0,给出范围在(-b+1,b-1)的随机数
- {$random} % b;生成正随机数(0~b-1)
其他系统函数
-
转换函数
- $rtoi(real_val):将实数转化为整数
- $itor(int_val):整数转换为实数
- $realtobits(real_val)
- $bitstoreal(bit_val)
-
数学函数
- 整数数学函数:$clog2(n):底数为2,数N的对数
- 实数数学函数:对数、指数、平方、各类三角函数