【FPGA开发】Verilog-数据截断实现四舍五入效果、模块化改造、对比Matlab验证,Modelsim覆盖率
目录
- 实现目标
- 直接截断低位
- 考虑四舍五入
- 模块化实现四舍五入功能
- 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
end
endmodule
再次基础上,考虑正数和负数在四舍五入时的区别。
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+3
num_rand_comp(i,:) = twos_complement(num_rand(i),P_PRE_DATA_WIDTH);
% 将二进制字符串写入文件
fprintf(fileID, '%s\n', num_rand_comp(i,:));
end
num_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_tests
if(ref(i) == decimal_data(i))
count = count + 1;
end
end
disp(['数据准确率为:',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 == -1
error('无法打开文件');
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_values
binary_str = binary_data{1}{i}; % 获取二进制字符串
decimal_data(i) = binary_twos_complement_to_decimal(binary_str, bit_width);
end
end
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_width
error('二进制字符串长度与位宽不匹配');
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/1ps
module 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 begin
file_handle = $fopen("output_data.txt", "w");
if (!file_handle) begin
$display("can not open file!");
$finish;
end
end
initial begin
for (i = 0; i < 10000; i = i + 1) begin
in_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;
end
round_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
尚未解决,先编辑文本编码格式,再分析代码覆盖率就没有错。