目录
- 实现目标
- 直接截断低位
- 考虑四舍五入
- 模块化实现四舍五入功能
- Matlab对比验证程序
- Testbench编写
- Modelsim查看验证覆盖率(简易)
实现目标
由于FPGA以定点数运算为主,随着数字信号处理的流程增加,数据位宽会逐渐变大,有时,考虑到资源量问题,会对定点数进行截断处理。
对于定点数来说,低位表示小数部分,截断低位,意味着抛弃小数,也就是损失精度。
假设一乘法器的运算结果是32-bits,后续要对该结果进行滤波处理,由于32-bits过大,需要对齐进行截断处理。
直接截断低位
直接截断低位是最简单的办法,相当于FLOOR()函数。
考虑四舍五入
以32-bits截断成17-bits为例。
想保留的是[31:15]这部分数据,如果直接截断,直接舍弃[14:0]即可,也就是说,把[14:0]这部分看成了小数。
所以在理解定点数的四舍五入时,把[31:15]看做整数,把[14:0]看做小数,小数部分大于等于0.5,就进位1;小数部分不超过0.5,就舍弃。
得到下述代码:
module fixed_point_rounding (input wire [31:0] in_data, // 假设输入是 32位定点数,小数点在第 15 位后output reg [17:0] out_data // 输出是 17 位整数
);always @(*) begin// 判断要舍弃部分的最高位if (in_data[14]) begin// 如果该位为 1,向前进位out_data = in_data[31:15] + 1;end else begin// 如果该位为 0,直接舍弃out_data = in_data[31:15];end
endendmodule
再次基础上,考虑正数和负数在四舍五入时的区别。
1.3 四舍五入成 1
1.6 四舍五入成 2
-1.3 四舍五入成 -1
-1.6 四舍五入成 -2
正数向0的方向(数值变小的方向)舍,向数值变大的方向 入。
负数向0的方向(数值变大的方向)舍,向数值变小的方向 入。
这里的实现方法有很多,可以参考Verilog对数据进行四舍五入(round)与饱和(saturation)截位
这里再补充一种写法:
assign buf_data = in_data[31] ? in_data + 15'b011_1111_1111_1111 : in_data + 15'b100_0000_0000_0000 ;assign out_data = buf_data[31:15];
考虑正数情况,把要截断的尾数看成小数,要截断15位, .100_0000_0000_0000 就是0.5,比这个大,就应该进位,所以正数情况要加 15’b100_0000_0000_0000,这样就能保证大于等于0.5的情况都能进位上去。
考虑负数情况,同样把要截断的尾数看成小数,要截断15位,.100_0000_0000_0000也是0.5,不过要注意的是,对于负数的补码来说,符号位的权值是负数((10.10) = (-2+0.5=-1.5)),对于负数来说,-1.5四舍五入后是要变成-2的,如果-1.5的小数位要是进位给整数位,会变成-1,这与实际不符合!所以,要保证.100_0000_0000_0000正好不产生进位,也就是要加15’b011_1111_1111_1111。
模块化实现四舍五入功能
module round_module #(parameter P_PRE_DATA_WIDTH = 32 , // 截断前数据位宽P_POST_DATA_WIDTH = 18 // 截断后数据位宽
)(input [P_PRE_DATA_WIDTH - 1 : 0] i_data , // 输入数据output [P_POST_DATA_WIDTH - 1 : 0] o_data // 输出数据
);wire [P_PRE_DATA_WIDTH - 1 : 0] buf_data;//正确的四舍五入操作
assign buf_data = i_data[P_PRE_DATA_WIDTH - 1] ? (i_data + {{1'b0},{(P_PRE_DATA_WIDTH - P_POST_DATA_WIDTH -1){1'b1}}}) : &i_data[P_PRE_DATA_WIDTH-2:P_PRE_DATA_WIDTH - P_POST_DATA_WIDTH] ? i_data : (i_data + {{1'b1},{(P_PRE_DATA_WIDTH - P_POST_DATA_WIDTH -1){1'b0}}}); //最好补写成1<<14格式,因为涉及补高位做加法,可能会出现错误 // 截断操作
assign o_data = buf_data[P_PRE_DATA_WIDTH - 1 : P_PRE_DATA_WIDTH - P_POST_DATA_WIDTH];endmodule
Matlab对比验证程序
主函数:
clc;clear;close all;% 参数设置
P_PRE_DATA_WIDTH = 32; % 输入数据位宽
P_POST_DATA_WIDTH = 18; % 输出数据位宽% 生成随机测试数据
num_tests = 10000-3; % 测试用例数量min = -2^(P_PRE_DATA_WIDTH-1);max = 2^(P_PRE_DATA_WIDTH-1)-1;num_rand = fix(min + (max - min)*rand(1,num_tests)) ;num_rand = fix([num_rand min max 0]);fileID = fopen('data.txt', 'w');
for i=1:num_tests+3num_rand_comp(i,:) = twos_complement(num_rand(i),P_PRE_DATA_WIDTH);% 将二进制字符串写入文件fprintf(fileID, '%s\n', num_rand_comp(i,:));
endnum_q = num_rand / 2^(P_PRE_DATA_WIDTH - P_POST_DATA_WIDTH);ref = round(num_q); %标准的数值decimal_data = read_binary_twos_complement("output_data.txt", 18);decimal_data = decimal_data.';count = 0;
for i=1:num_testsif(ref(i) == decimal_data(i))count = count + 1;end
enddisp(['数据准确率为:',num2str(count/num_tests*100),'%']);
twos_complement函数:
function binary_str = twos_complement(decimal_num, bit_width)% 将十进制有符号数转换为补码形式的二进制字符串% decimal_num: 输入的十进制数% bit_width: 二进制位数% 检查输入是否为有符号数if decimal_num < 0% 负数:计算补码complement = 2^bit_width + decimal_num; % 补码 = 2^N + 负数binary_str = dec2bin(complement, bit_width);else% 正数:直接转换为二进制binary_str = dec2bin(decimal_num, bit_width);end
end
read_binary_twos_complement函数:
function decimal_data = read_binary_twos_complement(filename, bit_width)% 读取二进制补码文件并转换为十进制数% filename: 文件名% bit_width: 二进制位数% 打开文件并读取内容fileID = fopen(filename, 'r');if fileID == -1error('无法打开文件');end% 读取文件内容binary_data = textscan(fileID, '%s', 'Delimiter', '\n');fclose(fileID);% 初始化输出数组num_values = length(binary_data{1});decimal_data = zeros(num_values, 1);% 遍历每一行二进制数据for i = 1:num_valuesbinary_str = binary_data{1}{i}; % 获取二进制字符串decimal_data(i) = binary_twos_complement_to_decimal(binary_str, bit_width);endend
binary_twos_complement_to_decimal函数:
function decimal_value = binary_twos_complement_to_decimal(binary_str, bit_width)% 将二进制补码字符串转换为十进制数% binary_str: 二进制字符串% bit_width: 二进制位数% 检查二进制字符串长度if length(binary_str) ~= bit_widtherror('二进制字符串长度与位宽不匹配');end% 判断是否为负数(最高位为1)if binary_str(1) == '1'% 负数:计算补码值decimal_value = - (2^bit_width - bin2dec(binary_str));else% 正数:直接转换decimal_value = bin2dec(binary_str);end
end
Testbench编写
`timescale 1ns/1psmodule tb_test();reg [31:0] in_data ;
wire [17:0] out_data ;reg [31:0] test_data [0:10000-1] ;initial begin$readmemb("data.txt",test_data);
end// initial begin
// in_data = 'd0;
// #100
// in_data = 32'b0111_0000_1111_1111_1110_0000_0000_0000;
// #100
// in_data = 32'b1000_1111_0000_0000_0010_0000_0000_0000;// #100
// in_data = 32'b0111_0000_1111_1111_1110_0110_0110_0110;
// #100
// in_data = 32'b1000_1111_0000_0000_0001_1001_1001_1010;// #100
// in_data = 32'b0111_0000_1111_1111_1101_1001_1001_1010;
// #100
// in_data = 32'b1000_1111_0000_0000_0010_0110_0110_0110;
// #100
// in_data = 32'b0111_1111_1111_1111_1111_1111_1111_1111;
// #100
// in_data = 32'd2147483648;
// end// 输入测试数据
integer i;
integer file_handle;// 初始化:打开文件
initial beginfile_handle = $fopen("output_data.txt", "w");if (!file_handle) begin$display("can not open file!");$finish;end
endinitial beginfor (i = 0; i < 10000; i = i + 1) beginin_data = test_data[i];#50;$fwrite(file_handle,"%b\n",out_data);end$fclose(file_handle);// 打印完成信息$display("data already write in : output_data.txt file!");// 结束仿真$finish;
endround_module #(.P_PRE_DATA_WIDTH (32 ) ,.P_POST_DATA_WIDTH (18 )
)round_module_u0(.i_data (in_data ), // .o_data (out_data ) //
);endmodule
Matlab对比数据,随机产生9997个数据,并添加最大值、最小值、0三个特殊边界值,验证程序,得到该模块的准确率为100%
Modelsim查看验证覆盖率(简易)
主要参考文章:
modelsim中代码覆盖率使用详解
代码覆盖率,英文Code Coverage,包含statement语句、branch分支、condition条件、expression表达、toggle信号翻转,fsm有限状态机等多种覆盖情况。
首先,准备好两个文件:testbench文件和模块文件
打开modelsim,新建工程。
选择好路径后,在弹出的框中,把已经有的两个文件添进来
在Project窗口下,如果相对文件进行代码覆盖率分析,需要配置。这里的两个文件我都想分析,那就一个个来,先配置tb_test.v文件。
选中tb_test.v,右键,compile,compile properties
在弹出的窗口中,选择Coverage,全选后点击ok。同理,把round_module.v也重复上述操作。
都配置完后,进行编译,编译通过后,会有绿色对钩出现,没出现就是代码有逻辑错误,需要回去找。下图就是编译通过。
编译通过后,准备开始放着,点击仿真按钮
选择work中的tb_test,点击Others选项卡
勾选Enable code coverage,点击ok
点击ok后,modelsim的布局会kuku变,这是正常的,因为要添加很多分析的窗口。
这是需要配合Matlab进行验证。
在读取testbench输出数据的文件前,打一个断点,让matlba先生成一个输入数据的txt文件。
图中的data.txt就是输入数据。
有个这个data.txt后,可以在modelsim中restart一下
然后再run-all
点完后,选择否,不退出modelsim。
此时代码覆盖率信息就分析好了,界面的大概布局如下:
下面分别看看每个窗口里都有啥。首先是Files选项卡
拖动横向条可以看到,Stmt是Statement缩写,这里给出了语句覆盖率的的命中率,和分支Branch覆盖率。
analysis选项卡
【Bug记录】:在分析完代码覆盖率后,如果编辑了文本编码格式,utf-8这种,会报错。
Numeric coverage display disabled
尚未解决,先编辑文本编码格式,再分析代码覆盖率就没有错。