本文参考:Verilog中的系统任务(显示/打印类)--$display, $write,$strobe,$monitor-CSDN博客
Verilog:parameter、localparam的区别和用法-CSDN博客
Verilog的系统任务----$fopen、$fclose和$fdisplay, $fwrite,$fstrobe,$fmonitor_verilog fopen-CSDN博客
Verilog的系统任务----$readmemh和$readmemb-CSDN博客
1,$display
$display可以直接打印一条文本信息,并在每一次$display执行后会自动换行,比如:
`timescale 1ns/1ns
module test_tb();
initial begin
$display("China NO1!");
$display("USA NO2!");
end
2,$write
$write的用法与$display一致,区别在于,一条$write语句执行完后,不会自动换行。比如下面的代码:
`timescale 1ns/1ns
module test_tb();
initial begin
$write("China NO1!");
$write("USA NO2!");
end
这两个系统函数除了直接打印文本外,也可以打印变量的值,其格式为(以$display为例):
$display("%b %b",a,b) ;
3,$strobe
$strobe 为选通显示任务。$strobe 使用方法与 $display 一致,但打印信息的时间和 $display 有所差异(也可以直接打印文本)。
当许多语句与 $display 任务在同一时间内执行时,这些语句和 $display 的执行顺序是不确定的,一般按照程序的顺序结构执行。
两者的区别在于:$strobe命令会在当前时间部结束时完成;
而$display是只要仿真器看到就会立即执行。
$strobe 是在其他语句执行完毕之后,才执行显示任务。例如:
`timescale 1ns/1ns
module test_tb();
reg [3:0] a ;
initial begin
$strobe("begin!");
a = 1 ;
#1 ;
a <= a + 1 ;
//第一次显示
$display("$display excuting result: %d.", a);
$strobe("$strobe excuting result: %d.", a);
#1 ;
$display();
//第二次显示
$display("$display excuting result: %d.", a);
$strobe("$strobe excuting result: %d.", a);
$strobe("end!");
end
endmodule
总结:$display() 和他前面一条语句同时执行,没有延时,
而 $strobe 会等待上一条指令执行完成后输出
可以看到,$strobe与$display的打印内容不是一致的。
这是因为该语句: a <= a + 1 ;也就是说a的第二次赋值是非阻塞赋值,而非阻塞赋值是需要时间的。
在第一次打印时,$display不会管你a是阻塞赋值还是非阻塞赋值,它就直接打印a当前的值1。而$strobe则会等到非阻塞赋值完成后再打印,所以其打印的值为2。
在第二次打印时,又延时了1ns,所以此时的非阻塞赋值完成,那么$strobe与$display的打印内容就均为2了。
所以$strobe这个系统任务通常是用来打印当前非阻塞赋值的变量值的。
4,$monitor
$monitor 为监测任务,用于变量的持续监测。只要变量发生了变化,$monitor 就会打印显示出对应的信息。 其使用方法与 $display一致。
下面的代码用$monitor 来实现监控a,b,c这3个变量,只要其中一个发生了变化,就会立马在终端打印。
`timescale 1ns/1ns
module test_tb();
reg [1:0] a ;
reg [1:0] b ;
reg [1:0] c ;
initial begin
a = 0 ;
b = 0 ;
c = 0 ;
$monitor("a=%d b=%d c=%d",a,b,c);
#50 $finish; //50ns后停止
end
always #10 begin //每10ns,随机生成a,b,c
a = {$random}%4;
b = {$random}%4;
c = {$random}%4;
end
终端打印结果如下:
5,$fopen
- $fopen:打开指定文件
- $fclose:关闭指定文件
integer handle1; //定义句柄1
handle1 = $fopen("file_name",type) //以指定类型(type)的方式来打开文件,并将其返回值赋给handle
integer handle1; //定义句柄1
handle1 = $fopen("D:/file_test/file_test1.txt","w"); //以w类型(写)的方式来打开文件,并将其返回值赋给handle1
6,$fdisplay, $fwrite,$fstrobe,$fmonitor
这4个函数都可以对指定文件进行打印或写入操作,其用法与对应的$display, $write,$strobe,$monitor几乎一致。建议参考以上用法
区别在于$display等函数是直接在仿真中进行打印,而$fdisplay等函数是对指定文件进行打印,需要通过句柄来指定是对具体哪个文件进行操作。如:
reg data;
integer handle1; //定义句柄1
handle1 = $fopen("D:/file_test/file_test1.txt","w"); //以w类型(写)的方式来打开文件,并将其返回值赋给handle1
//对指定文件写入数据
$fdisplay(handle1,"%d\n",data); //按照十进制格式写入数据到handle1对应的文件中,只能逐个写入
7,$fclose
$fclose系统任务用来关闭指定文件,关闭后即无法对该文件进行操作。其格式如下:
$fclose(handle1); //关闭文件file_test1,至此文件操作结束
8,$readmemh和$readmemb
$readmemh(h,hexadecimal,十六进制)用来读取16进制的数据
$readmemb(b,binary,二进制)则用来读取2进制的数据。
$readmemb("<数据文件名>",<数组名>)
$readmemb ("<数据文件名>",<数组名>,<起始地址>)
$readmemb ("<数据文件名>",<存贮器名>,<起始地址>,<结束地址>)
$readmemh("<数据文件名>",<数组名>)
$readmemh ("<数据文件名>",<数组名>,<起始地址>)
$readmemh ("<数据文件名>",<数组名>,<起始地址>,<结束地址>)
使用方法1:不指定起始地址和结束对应地址
即$readmemh("<数据文件名>",<数组名>)
`timescale 1ns / 1ns
module tb_read_test();
integer i;
reg [7:0] mem_test [9:0]; //mem_test是位宽8bit,个数为10的数组
initial $readmemh("D:/read_test/read_test.srcs/sim_1/new/data.txt",mem_test); //绝对路径
//显示数组的10个值
initial begin
for(i=0; i<10; i=i+1)
$display("%d: %h", i, mem_test[i]);
end
仿真结果如下:
- 去除部分数据文件中的数据,导致数组无法被填满(注释掉后面4个数据):
仿真结果如下:
数据文件仅有6个数据,而数组个数为10,所以数组的后4个数据仍没有被赋值。
使用方法2:指定起始地址,但不指定结束地址
即$readmemh ("<数据文件名>",<数组名>,<起始地址>),此时会将从数据文件中读到的第1个数据填入数组的起始地址,此后类推,直到数组被填满。而之前被起始地址跳过的数组的数据则不会被赋值。如果数据的个数大于数据文件中数据的个数,则数组无法被填满,未被填满的部分则依然是未知状态。
指定数据从数组的地址2开始赋值,则地址2应该被赋值0,地址3被赋值1,···等等。而地址0和地址1则仍保持未赋值状态:
`timescale 1ns / 1ns
module tb_read_test();
integer i;
reg [7:0] mem_test [9:0];//mem_test是位宽8bit,个数为10的数组
initial $readmemh("D:/read_test/read_test.srcs/sim_1/new/data.txt",mem_test,2);//绝对路径,从地址2开始
//显示数组的10个值
initial begin
for(i=0; i<10; i=i+1)
$display("%d: %h", i, mem_test[i]);
end
endmodule
仿真结果如下:
从地址2开始依次被赋值00~07,而地址0和地址1则仍保持未赋值状态。
使用方法3:同时指定起始地址和结束地址
即$readmemh ("<数据文件名>",<数组名>,<起始地址>,<结束地址>),此时会将从数据文件中读到的第1个数据填入数组的起始地址,此后类推,直到指定的终结地址对应的数组也被赋值。处于起始地址和终结地址构成的区间范围外的地址对应的数组则不会被赋值。如果数据的个数大于数据文件中数据的个数,则数组无法被填满,未被填满的部分则依然是未知状态。
指定数据从数组的地址2开始赋值,直到地址8终结,则地址2应该被赋值0,地址3被赋值1,···等等,直到地址8也被赋值。而地址0、1、9则仍保持未赋值状态:
`timescale 1ns / 1ns
module tb_read_test();
integer i;
reg [7:0] mem_test [9:0];//mem_test是位宽8bit,个数为10的数组
initial $readmemh("D:/read_test/read_test.srcs/sim_1/new/data.txt",mem_test,2,8);//绝对路径,从地址2开始
//显示数组的10个值
initial begin
for(i=0; i<10; i=i+1)
$display("%d: %h", i, mem_test[i]);
end
endmodule
仿真结果如下:
从地址2开始直到地址8依次被赋值00~06,而地址0、1、9则仍保持未赋值状态。
9,parameter、localparam的区别和用法
一、区别
parameter: 可以在实例化时修改参数值
localparam:只能在当前模块使用,不能进行实例化
二、用法
2.1 设计文件中parameter的用法
直接在模块名后面 #(parameter 参数名=参数值)
2.2 例化模块时parameter的用法
在模块名后面直接 #(.参数名 (参数值))
实列操作
需求1:
雷码转换设计的仿真测试结果写入txt文本
使用绝对路径(注意绝对路径下的文件夹命名都要遵循verilog变量命名规范)
需求分析:
1,创建文件夹,创建文件
2,将内容写入文件
3,关闭文件
`timescale 1ns/1ps
module testbench_top();
//参数定义
`define CLK_PERIORD 20
//接口申明
reg i_clk;
reg i_rst_n;
reg i_en;
reg[9:0] i_data;
wire o_vld; //有效信号
wire[15:0] o_data;
vlg_design vlg_design_inst (
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_en(i_en),
.i_data(i_data),
.o_vld(o_vld),
.o_data(o_data)
);
integer i;
initial begin
i_en <= 0;
i_clk <= 0;
i_rst_n <= 0;
i_data <= 0;
#2000;
i_rst_n <= 1;
end
always #(`CLK_PERIORD/2) i_clk = ~i_clk;
//产生激励
initial begin
@(posedge i_clk);
@(posedge i_rst_n);
i_en <= 1;
@(posedge i_clk);
for (i = 1;i <= 1024;i = i+1) begin
i_data <= i;
@(posedge i_clk);
end
@(posedge i_clk);
i_en <= 0;
#50_000;
$fclose(handle1); //关闭文件
$stop;
end
//实时显示,并写入文件
always@(posedge i_clk) begin
if(o_vld) begin
$display("%d",o_data);
$fdisplay(handle1,"%d",o_data); //往handle1 句柄的文件中写入文件
end
else ;
end
//创建file_test1.txt 文件
integer handle1; //定义句柄1
initial begin
//以w类型(写)的方式来打开文件,并将其返回值赋给handle1
//使用绝对路径(注意绝对路径下的文件夹命名都要遵循verilog变量命名规范)
//注意绝对路径的文件夹要存在,文件可以新生成,即先手动新建outputfile 文件夹
handle1 = $fopen("./outputfile/file_test1.txt","w");
end
endmodule
需求2:
txt文本文件中8位格雷码数据的读取与使用
/
//EDA工具平台:Vivado 2019.1 + ModelSim SE-64 10.5
//开发套件型号: STAR 入门FPGA开发套件
//版 权 申 明: 本例程由《深入浅出玩转FPGA》作者“特权同学”原创,
// 仅供特权同学相关FPGA开发套件学习使用,谢谢支持
//官方淘宝店铺: http://myfpga.taobao.com/
//微 信 公 众 号:“FPGA快乐学习”
// 欢迎关注,获取更多更新的FPGA学习资料
/
`timescale 1ns/1ps
module testbench_top();
//参数定义
`define CLK_PERIORD 10 //时钟周期设置为10ns(100MHz)
parameter GRAY_MSB = 7;
//接口申明
reg clk;
reg rst_n;
reg i_en;
reg[GRAY_MSB:0] i_data;
wire o_vld;
wire[GRAY_MSB:0] o_gray;
//对被测试的设计进行例化
vlg_design #(
.MSB(GRAY_MSB)
)
uut_vlg_design(
.i_clk(clk),
.i_rst_n(rst_n),
.i_en(i_en),
.i_data(i_data),
.o_vld(o_vld),
.o_gray(o_gray)
);
//复位和时钟产生
//时钟和复位初始化、复位产生
initial begin
clk <= 0;
rst_n <= 0;
#1000;
rst_n <= 1;
end
//时钟产生
always #(`CLK_PERIORD/2) clk = ~clk;
//文本文件的读取
reg[7:0] data_mem[255:0];
initial $readmemb("./input_file/8bit_grayencode.txt",data_mem);
//测试激励产生
initial begin
i_en <= 'b0;
i_data <= 'b0;
$display("The value of GRAY_MSB is %0d",GRAY_MSB);
@(posedge rst_n); //等待复位完成
@(posedge clk);
i_en <= 'b1;
i_data <= 'b0;
repeat(2**(GRAY_MSB+1)-1) begin
@(posedge clk);
i_en <= 'b1;
i_data <= i_data+1;
end
@(posedge clk);
i_en <= 'b0;
#100;
//$fclose(wfile);
$stop;
end
/*integer wfile;
initial begin
wfile = $fopen("./output_file/result_data.txt","w");
end*/
integer cnt;
always @(posedge clk) begin
if(!rst_n) cnt <= 0;
else if(o_vld) cnt <= cnt+1;
end
always @(posedge clk) begin
if(o_vld) $display("%b\n%b\n\n",o_gray,data_mem[cnt]);
end
endmodule