Testbench中文本数据的存储读取操作对比

本文主要讲述Testbench中如何读取和存储文本文件的数据,涉及到基础的Verilog语法。


对一些数据处理的模块进行调试仿真,模块需要特定的数据输入,比如单一频率的正弦波;为了解决这个问题,我们可以用matlab,python等工具生成文本数据,然后使用Verilog将数据读取进来;Testbench可以使用2种方法进行文本数据操作

  1. readmemb, readmemh, writememb, writememh操作
  2. fscanf, fwrite等操作

readmemb, readmemh, writememb, writememh操作

从字面意思理解,readmem是读取数据到memory,后缀的b, h代表了数据的进制;同理,writemem是将memory的数据写入到文件中;

所以,在使用这一类系统自带函数时,首先要有一个memory类型的变量。定义方法如下:

reg	[M-1:0]	mem	[N:1];

mem”变量“(应该叫寄存器组)有N个”一维“变量,每个”一维“变量的bit宽度为M;你可以将mem理解为C语言中的二维数组,里面包含了N个一维数组,每个一维数组有M个元素,元素为bit。

事实上,我们称M为mem的数据宽度,N为mem的数据深度。

以readmemb为例,进行数据读取操作

initial
begin
	$readmemb("data.txt", mem);
end

readmemb的第一个参数为文件名,第二个参数为memory变量名;至此,data.txt内部的N行数据存入了mem里。readmemh操作类似,不同的是data.txt的数据要求为16进制。那么可能有人会有疑问了,假如有以下问题,mem存入的数据会是啥样:

  • data.txt每行的数据位宽小于M或者大于M

图1 数据位宽小于M波形

图2 数据位宽小于M存储

图3 数据位宽大于M

图4 数据位宽大于M,VCS警告

经过试验,M大于数据位宽,数据可以正常读取,高位补0;小于数据位宽,数据无法正常读取。

  • data.txt的数据不是二进制,或者不是纯数字

与M小于数据位宽的情况一致,无法正常读取数据。

  • 如果data.txt的行数小于N或大于N

图5 行数小于N

图6 行数大于N

经过试验,行数大于N,仿真器会出警告,但数据可以正常读取。小于N时,多余的部分memory的值为不定状态。

数据存入mem但还没有进入到模块的输入,接下来的操作可以参考下列代码:

reg	[M-1:0]	data_in;
integer index = 1;

initial
begin
	forever
	begin
		@(posegde clk);
		data_in = mem[index];
		index = (index >= N) ? 1 : index + 1;
	end
end

代码里面,等待clk的上升沿,然后将mem的index元素赋值给data_in,然后index完成加1操作;整个过程不断循环;这里设置了index计数到N返回1的计数保护,防止出现无效数据。再将data_in与被测模块的数据输入端口相连,数据就送入进去了。

图7 送入的数据波形

writememb的操作与readmemb反过来,将mem的数据存储为文本操作如下:

initial
begin
    $writememb("new_data_b.txt", mem);
    $writememh("new_data_h.txt", mem);
end

存储之后的

图8 writememh

图9 writememb

fscanf, fwrite等操作

Verilog本身的语法与C类似,其自身也有文本操作的函数,也与C类似。使用Verilog对文本操作,首先需要进行如下操作:

integer fid;
initial
begin
	fid = $fopen("data.txt", "r");
    //fid = $fopen("data.txt", "w"); //write
    if (!fid)
        $display("file open error");
end

如同C语言中的fopen一样,第一个参数为文件名,第二个参数为操作模式,包括读(r, rb),写(w, wb)等操作;根据返回值判断文件操作是否有错误。

然后,根据文本文件的数据格式,进行数据读取操作。

reg [M-1:0] data_in;

always @ (posedge clk)
    $fscanf(fid, "%d %d %d", data_in, mem[0], mem[1]);

fscanf用法与C语言类似,文件句柄为第一个参数,第二个参数为格式参数,第三个为数据保存变量,但不需要加&了。读取文件的时候第二个参数与第三个参数需要对应,否则数据读取可能会出错。(亲身经历)

数据存储操作如下,在前面fopen使用w模式下:

always @ (posegde clk)
    $fwrite(fid, "%d, %d, %d\n", $signed(data_in), $signed(data_in)+1, $signed(data_in)+2);

数据可以按照第二个参数的格式存储进文本文件。还有一系列如fdisplay, 相对于fwrite, 它的文件写入数据之后会自动到下一行,所以第二个参数不需要加入“\n”;ftell等函数。注意,想要存储十进制的负数,除了第二个参数用%d,第三个参数的寄存器变量还要使用$signed转换为有符号数形式

图10 正常情况文本操作读取后​存储的数据

图11 寄存器位宽小于数据位宽时,文本操作读取后存储的数据

注意,当存储的寄存器位宽小于数据位宽时,数据会被自动截去高位保留低位。

之前使用文件操作存储被测模块的输出时,每次文件的数据量(行数)都与理想中的数目对不上,找各种原因,最后才发现自己犯了一个低级错误,没有使用fclose关闭文件句柄。

initial 
begin
	#1000;
	$fclose(fid);
	$finish;
end

​停止仿真前,一定要用fclose关闭文件句柄,否则数据存取会出现不可预知的问题。

欢迎使用本文使用的Testbench做实验,注意,在windows下使用modelsim做实验时,文件名必须是绝对路径;tb中,使用"READMEM_ON"宏定义决定运行readmem或文件操作,可以尝试修改宏定义的值改变文件操作的函数类型;本文使用资源在公众号回复116获取;

两种方法差异对比

  1. readmem,writemem方法只能存取二进制或十六进制数据,数据格式固定,对多维其他格式数据读取不支持,没有文件操作灵活;文本操作方便其他的工具,如matlab,python处理数据
  2. readmem是可综合语句,所以可以用于对模块内的memory变量进行赋值,但其他语句是不可综合语句,只能用于仿真测试中
  3. 文件操作虽然支持各种格式的文本存取,但是操作上没有readmem, writemem简单;假如数据需要循环使用,readmem读取进memory之后,通过复位index就可以循环使用数据,而文本操作就麻烦一些。
  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的 Verilog testbench 代码,可以读取文本文件数据作为输入数据进行仿真: ```verilog `timescale 1ns / 1ps module tb; reg clk; reg reset; reg [7:0] data; // 文件句柄 integer infile; // 仿真时间 integer sim_time; // 仿真结束时间 parameter SIM_END_TIME = 1000; // 模块实例化 your_module dut(.clk(clk), .reset(reset), .data(data)); // 时钟生成器 always #5 clk = ~clk; // 重置信号 initial begin reset = 1; #10 reset = 0; end // 读取数据文件 initial begin // 打开文件 infile = $fopen("data.txt", "r"); // 检查文件是否存在 if (!infile) begin $display("Error: Could not open data file."); $finish; end // 读取数据并进行仿真 while (!$feof(infile)) begin // 读取一行数据 $fscanf(infile, "%h", data); // 输出数据 $display("Data = %h", data); // 模块输入赋值 #10 data = data + 1; // 增加仿真时间 sim_time = sim_time + 10; // 运行仿真到下一个时钟上升沿 #5; end // 关闭文件 $fclose(infile); end // 停止仿真 always @(posedge clk) begin if (sim_time >= SIM_END_TIME) begin $finish; end end endmodule ``` 在上面的代码,我们首先定义了一个 `reg [7:0] data` 信号作为模块的输入数据。然后通过 `integer infile` 定义了一个文件句柄,用于打开数据文件并读取数据。我们使用 Verilog 内置的 `$fopen()` 和 `$fclose()` 函数来打开和关闭文件,并使用 `$feof()` 函数来检查是否到达文件结尾。 在 `initial` 代码块,我们使用 `$fscanf()` 函数读取文件的一行数据,并将其赋值给 `data` 信号。然后进行仿真,并在每次仿真结束后增加仿真时间 `sim_time`,直到达到设定的仿真结束时间 `SIM_END_TIME`。 最后,我们使用 `always @(posedge clk)` 代码块来监测时钟上升沿,并在达到设定的仿真结束时间时调用 `$finish` 函数来停止仿真。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值