systemverilog中高精度浮点运算的一种求解思路
1. 问题背景
设计中有一个计时器,包含48bit的s和32bit的ns共同组成80 bit的时间数据存放在以下数据结构中
reg [79:0] timer;
该计时器允许存入一个负数值,符号位由s所在的48bit表示
现在有两个这样类型的计时器timer1和timer2,实时计算这两个计时器之间的时间差值,并填入到计时器offset中。
想要实现的算法如下所示:
bit [79:0] timer1;
bit [79:0] timer2;
bit [79:0] offset;
//求解offset为timer1与timer2的差值
//需要对其中的s和ns分别进行运算,还要考虑符号位,所以不能简单的进行相减
2. 问题难点
- 这里的时间由两部分构成,分别是s和ns,ns跟s之间的进制关系是10^9,因而无法直接用这两个80bit的数进行加减运算
- SystemVerilog数据结构中很难找到这么一种数据类型来描述48bit的s和32bit的ns的组合值,SystemVerilog中能够记录的最大的有符号数类型为real,所占空间也仅仅只有64bit
3. 解题思路
由于bit类型是无符号的,所以我们在进行bit类型的运算时最好将其转化为无符号之间的运算,同时符号位单独计算。
3.1 统一度量衡
原有的80 bit timer即包含了s又包含了ns,那么我们要进行运算首先得统一单位,同时还需要将符号位进行处理,进而得到一个符号位和以ns为单位的无符号正整数的组合,为后面的运算打好基础。
这里我们写了这么一个函数array_to_time进行转换,输入为一个包含符号的bit[79:0]类型的数据time_set(单位包含s和ns),输出为int类型的符号位sign(1表示正数,-1表示负数)和对应的无符号数bit[79:0]类型的数据set_time_ns(单位是ns)。
//combinational time [79:32].[31:0] s
//to
//realtime [79:0] ns
function bit[79:0] array_to_time(bit[79:0] time_set, output bit[79:0] set_time_ns, output int sign);
bit[47:0] time_s;
bit[31:0] time_ns;
sign = (time_set[79]==0) ? 1 : -1; //由最高位获取符号位
time_s = (sign == 1) ? time_set[79:32] : -time_set[79:32]; //获取s部分的无符号值
time_ns = time_set[31:0]; //获取ns部分的无符号值,符号位由s部分保存,ns部分不受影响
set_time_ns = time_s*1000000000 + time_ns;
endfunction
同时我们写了这个函数的逆函数,方便最后把运算结果写回到80bit的由s和ns组合的寄存器中
//realtime [79:0] ns
//to
//combinational time [79:32].[31:0] s
function time_to_array(bit[79:0] time_set_ns, int sign, output bit[79:0] time_set_a);
bit[47:0] time_s;
bit[31:0] time_ns;
time_s = time_set_ns/1000000000;
time_ns = time_set_ns%1000000000;
time_set_a[79:32] = time_s*sign;//s部分包含符号位
time_set_a[31:0] = time_ns;
endfunction
用这样的方法我们可以得到timer1和time2的ns为单位的无符号数和符号位信息
array_to_time(timer1, timer1_ns, sign1); array_to_time(timer2, timer2_ns, sign2);
3.2 无符号数之间的运算
timer1和timer2的无符号数之间的运算同时需要查看符号位信息,决定了两者运算后得到的无符号数是应该由两者相加还是相减,同时根据无符号数之间的运算结果判定最终的符号位是多少,我们写了一个函数time_sub_signed完成上述功能,根据符号位的组合总共分成了四种场景进行计算。
//time1, time2 are taken as positive number!
function void time_sub_signed(bit[79:0] time1, int sign1, bit[79:0] time2, int sign2, output bit[79:0] time_s, int sign);
case({sign1, sign2})
//time1 - time2
{1,1}: begin
if(time1 >= time2) begin
time_s = time1 - time2;
sign = 1;
end
else begin
time_s = time2 - time1;
sign = -1;
end
end
//time1 + time2
{1,-1}: begin
time_s = time1 + time2;
sign = 1;
end
//-(time1 + time2)
{-1,1}: begin
time_s = time1 + time2;
sign = -1;
end
//time2 - time1
{-1,-1}: begin
if(time1 >= time2) begin
time_s = time1 - time2;
sign = -1;
end
else begin
time_s = time2 - time1;
sign = 1;
end
end
default: begin
$error;
end
endcase
endfunction
于是我们就可以如下调用进一步计算无符号数的结果以及符号位信息
time_sub_signed(timer1_ns, sign1, timer2_ns, sign2, offset_ns, sign_offset);
3.3 转化为包含s和ns的寄存器值写入到寄存器中
调用array_to_time的逆函数time_to_array完成上述功能
time_to_array(offset_ns, sign_offset, offset);
这样就完成了offset的结果运算
4. 激励代码
为了打印演示的方便,还添加了函数sign_to_string把符号位转化为相应的字符串。
//sign 1/-1 to string ""/"-"
function string sign_to_string(int sign);
if(sign == 1)
return "";
else
return "-";
endfunction
initial begin
bit[79:0] timer1, timer2, offset;
bit[79:0] timer1_ns, timer2_ns, offset_ns;
longint timer;
int sign1, sign2, sign_offset;
for(int i=0; i<5; i++) begin
std::randomize(timer) with {timer inside {[-500 : 500]};};
timer1[79:32] = timer;
timer1[31:0] = $random;
std::randomize(timer) with {timer inside {[-500 : 500]};};
timer2[79:32] = timer;
timer2[31:0] = $random;
array_to_time(timer1, timer1_ns, sign1);
array_to_time(timer2, timer2_ns, sign2);
time_sub_signed(timer1_ns, sign1, timer2_ns, sign2, offset_ns, sign_offset);
time_to_array(offset_ns, sign_offset, offset);
$display("");
$display("TIME CAL :");
$display(" timer1 : %s%0d ns", sign_to_string(sign1), timer1_ns);
$display(" timer2 : %s%0d ns", sign_to_string(sign2), timer2_ns);
$display(" offset : %s%0d ns", sign_to_string(sign_offset), offset_ns);
$display(" timer1 register : %h", timer1);
$display(" timer2 register : %h", timer2);
$display(" offset register : %h", offset);
$display("");
end
end
仿真结果如下:
5. 总结
本文通过结合代码讲解了SystemVerilog中进行高精度浮点运算时的一种求解思路,以免大家淹没在进制转换和符号运算的汪洋大海里不能自拔。