一、VHDL仿真中file I/O的核心作用
做硬件仿真的人都知道,最开始测电路功能的时候,要么是用仿真工具自带的信号强制功能手动输入,要么是写一堆重复的信号赋值语句。这种方式测小电路还行,要是测复杂的接口、时序,比如高速串口、存储控制器,光手动输信号就得花好几天,而且测完的结果也得手动看波形,错了还得反复改,效率特别低。这时候file I/O就派上大用场了,它能让仿真程序直接读电脑里的外部文件,比如提前写好的测试信号,也能把仿真跑出来的结果写到文件里,不用人工盯着看,相当于把整个测试流程给自动化了。
二、file I/O的基础语法(用示例讲透)
要做自动化测试,得先知道怎么用file I/O读、写文件。这里先把基础语法拆成读和写两部分,每个部分都给完整的示例,而且所有示例都用VHDL这个技术栈,不混别的语言。
2.1 读外部激励文件的基础操作
读文件的核心是用file关键字定义文件变量,然后用read过程把文件里的内容按行或者按字节读出来,赋值给仿真里的信号。比如我们要测一个8位加法器,提前把测试用的输入A、B和预期的输出Y写到一个txt文件里,仿真的时候直接读这个文件就能自动生成激励。
先看完整的示例代码,代码里的注释会把每一步的作用说清楚:
-- 技术栈:VHDL(用于硬件仿真的硬件描述语言)
-- 定义测试用的文件类型,这里是整数类型的文件,因为我们的激励是数字
type file_type_int is file of integer;
-- 定义文件变量,指定打开方式为读(read_mode)
file test_input_file : file_type_int open read_mode is "C:/test_input.txt"; -- 注意路径用正斜杠,或者双反斜杠
-- 定义仿真用的信号
signal A, B : integer range 0 to 255; -- 加法器的两个输入,8位
signal Y : integer range 0 to 510; -- 加法器的输出
signal test_done : std_logic := '0'; -- 测试完成标志
-- 读文件的进程
process
begin
-- 先等仿真初始化完成,避免读文件出错
wait for 10 ns;
-- 循环读文件,直到文件结束
while not endfile(test_input_file) loop
-- 读第一个数,赋值给A
read(test_input_file, A);
-- 读第二个数,赋值给B
read(test_input_file, B);
-- 等加法器运算完成,这里假设加法器的延迟是1ns
wait for 1 ns;
-- 把输出Y写到结果文件(后面写文件部分会讲)
write(test_output_file, Y);
-- 等下一个测试周期
wait for 10 ns;
end loop;
-- 测试完成,把标志位设为1
test_done <= '1';
wait; -- 进程结束,避免重复执行
end process;
这里要注意,VHDL里读文件的顺序是按文件里的内容顺序来的,所以我们提前写的激励文件得按顺序排,比如test_input.txt的内容应该是每行两个数,代表A和B的取值,比如:
1 2
3 4
100 200
这样仿真的时候就能自动按顺序读入这些值,不用手动改代码。
2.2 写仿真结果文件的基础操作
写文件和读文件类似,核心是用write过程把仿真里的信号值写到指定的文件里,这样仿真结束后直接打开这个文件就能看结果,不用看波形。比如上面的加法器仿真,我们可以把A、B、Y都写到结果文件里,方便核对。
写文件的示例代码:
-- 技术栈:VHDL
-- 定义结果文件的类型,也是整数类型
type file_type_res is file of integer;
-- 定义结果文件变量,打开方式为写(write_mode)
file test_output_file : file_type_res open write_mode is "C:/test_output.txt";
-- 写文件的进程,和读文件的进程配合使用
process
begin
-- 等仿真初始化
wait for 10 ns;
-- 循环读激励的同时写结果
while not endfile(test_input_file) loop
read(test_input_file, A);
read(test_input_file, B);
wait for 1 ns; -- 等加法器运算
-- 写结果,这里按顺序写A、B、Y,方便核对
write(test_output_file, A);
write(test_output_file, B);
write(test_output_file, Y);
wait for 10 ns;
end loop;
wait;
end process;
这样仿真结束后,test_output.txt里就会有A、B、Y的对应值,比如对应上面的输入,结果就是:
1 2 3
3 4 7
100 200 300
直接就能看到每一组输入对应的输出对不对。
三、自动化测试流程的完整搭建(高级应用)
基础的读、写只能算入门,真正的自动化测试得把整个流程串起来:从准备激励文件、仿真自动读激励、自动测、自动写结果、自动判断对错,整个过程不用人工干预,这才是高级应用。
3.1 自动化测试的完整流程步骤
完整的自动化测试流程大概分五步: 第一步:准备激励文件,把所有要测的输入按规则写到txt里,最好还要带上预期的输出,方便后面自动比对; 第二步:写VHDL测试平台(也就是testbench),里面包含读激励、写结果、自动比对对错的代码; 第三步:在仿真工具(比如ModelSim、Vivado Simulator)里配置好仿真的时间和参数,然后运行仿真; 第四步:仿真结束后,打开结果文件,或者看仿真里自动输出的对错标志; 第五步:如果测试不通过,修改电路代码,重复上面的步骤,直到所有测试用例都通过。
3.2 自动比对结果的高级示例
上面的示例只是把结果写出来,还得人工核对,真正的自动化得让仿真自己判断对错,这就要用到if语句把实际输出和预期输出比对。比如我们把激励文件改成每行三个数:A、B、预期输出Y_exp,仿真的时候读入A、B、Y_exp,然后把实际的Y和Y_exp比对,对的就写“PASS”,错的就写“FAIL”,这样仿真结束后直接看结果文件就能知道哪些用例过了,哪些没过。
完整的自动比对示例代码:
-- 技术栈:VHDL
-- 定义文件类型,这次是整数类型,用来存激励(A、B、Y_exp)
type file_type_stim is file of integer;
-- 定义结果文件类型,这次是字符串类型,用来存比对结果(PASS/FAIL)
type file_type_res_str is file of string;
-- 定义文件变量
file stim_file : file_type_stim open read_mode is "C:/stim_with_exp.txt";
file res_file : file_type_res_str open write_mode is "C:/auto_test_result.txt";
-- 定义仿真信号
signal A, B : integer range 0 to 255;
signal Y : integer range 0 to 510;
signal Y_exp : integer range 0 to 510;
signal test_count : integer := 0; -- 测试用例计数
signal pass_count : integer := 0; -- 通过用例计数
signal fail_count : integer := 0; -- 失败用例计数
-- 自动测试的核心进程
process
variable res_str : string(1 to 20); -- 用来存比对结果的字符串
begin
wait for 10 ns; -- 仿真初始化等待
while not endfile(stim_file) loop
-- 读激励文件的三个数:A、B、预期输出Y_exp
read(stim_file, A);
read(stim_file, B);
read(stim_file, Y_exp);
wait for 1 ns; -- 等加法器运算完成
test_count <= test_count + 1; -- 用例数加1
-- 比对实际输出和预期输出
if Y = Y_exp then
res_str := "Test Case " & integer'image(test_count) & " PASS ";
pass_count <= pass_count + 1;
else
res_str := "Test Case " & integer'image(test_count) & " FAIL ";
fail_count <= fail_count + 1;
end if;
-- 把比对结果写到结果文件
write(res_file, res_str);
wait for 10 ns; -- 下一个测试周期
end loop;
-- 所有用例测完后,写总结果
res_str := "Total: " & integer'image(test_count) & " PASS: " & integer'image(pass_count) & " FAIL: " & integer'image(fail_count);
write(res_file, res_str);
wait;
end process;
这里的integer'image(test_count)是VHDL里把整数转成字符串的方法,用来生成测试用例的编号,方便定位哪个用例错了。比如stim_with_exp.txt的内容是:
1 2 3
3 4 8 -- 这个是故意写错的预期输出,用来测比对功能
100 200 300
仿真结束后,auto_test_result.txt里的内容就是:
Test Case 1 PASS
Test Case 2 FAIL
Test Case 3 PASS
Total: 3 PASS: 2 FAIL: 1
这样一眼就能看到哪些用例过了,哪些没过,不用人工一个个核对。
四、应用场景、优缺点及注意事项
4.1 应用场景
file I/O的高级应用主要用在这些场景: 一是复杂电路的批量测试,比如高速串口、PCIe接口、存储控制器,这些电路需要几百甚至上千个测试用例,手动输入根本不可能; 二是回归测试,也就是每次修改电路代码后,都要跑一遍所有的测试用例,确保之前的功能没被破坏,用file I/O就能一键跑所有用例; 三是跨平台仿真,比如在Windows上写好激励文件,拿到Linux上跑仿真,不用改代码,直接读文件就行; 四是和其他工具联动,比如用Python生成复杂的激励(比如随机数、特定格式的数据包),然后存成txt,再用VHDL读,实现更复杂的测试。
4.2 技术优缺点
优点很明显:第一,效率高,批量测试不用人工干预,节省大量时间;第二,可重复性好,每次测试都用同一个激励文件,不会因为手动输入的差异导致结果不一样;第三,结果可追溯,所有的输入输出都存在文件里,方便后续排查问题;第四,灵活性高,激励文件可以用任何工具生成,比如Python、Excel,只要格式对就行。
缺点也有:第一,对新手不友好,刚学VHDL的人可能搞不清文件路径、文件类型的定义,容易出语法错误;第二,大文件的读取速度可能慢,比如几G的激励文件,仿真的时候读起来会比较卡,甚至可能超时;第三,文件格式的兼容性问题,比如不同的仿真工具对文件编码的要求不一样,有的用UTF-8,有的用GBK,编码不对可能读不出内容;第四,调试麻烦,要是读文件的时候出错,比如文件路径错了,很难定位是文件的问题还是代码的问题。
4.3 注意事项
用file I/O的时候要注意这几点: 第一,文件路径的写法,Windows上要用正斜杠或者双反斜杠,比如"C:/test.txt"或者"C:\test.txt",不能用单反斜杠,不然会被当成转义字符; 第二,文件类型的匹配,比如激励文件是整数,就要定义整数类型的文件,要是定义成字符串类型,读出来的内容会不对; 第三,仿真时间的设置,要确保所有的测试用例都能在仿真时间内跑完,比如有1000个用例,每个用例要10ns,那仿真时间至少要设成10000ns,不然会有部分用例没测到; 第四,文件的关闭,VHDL里不用手动关闭文件,仿真结束后会自动关闭,但要是中途仿真出错,可能会导致文件被占用,要手动关闭; 第五,编码格式的统一,最好用UTF-8编码,大部分仿真工具都支持,避免出现乱码。
五、文章总结
file I/O是VHDL仿真中非常实用的功能,从基础的读激励、写结果,到高级的自动化测试流程搭建,能极大地提高硬件仿真的效率,减少人工操作的错误。通过准备激励文件、写测试平台、自动比对结果,就能实现整个测试流程的自动化,适合各种复杂电路的批量测试和回归测试。虽然它有一些缺点,比如对新手不友好、大文件读取慢,但只要注意路径、类型、编码这些细节,就能避开大部分坑。掌握file I/O的高级应用,能让硬件仿真的工作效率提升好几倍,是硬件开发者必备的技能之一。
Comments