如何将算法翻译成RTL(二):带小数的加减法实现

简介: 如何将算法翻译成RTL(二):带小数的加减法实现

上一篇文章给大家讲解了溢出保护相关的话题。本篇文章趁热打铁,给大家讲解一下相对复杂的带小数的加减法实现。

1、小数加法模块简介

我们考虑两个小数的加法:

  • 其中输入a为3位整数、4位小数,一共7位;
  • 输入的b为2位整数、3位小数,一共5位;
  • 输出的c为3位整数、1位小数,一共4位;

对于输出c要做四舍五入的操作,并且还要做相关的溢出保护。

首先我们明确一下,由于输入a有着4位小数、输入b有着3位小数,而输出c只有1位小数,那么一定会有精度的损失,这点相信大家可以理解。那我们的精度有多少呢?实际上精度应该是0.25,因为我们只有1位小数,即要么是0.5,要么是0,又由于我们做了四舍五入,因此最终的结果距离真正正确的值,最差只会差0.25。

又由于输出c只有3位整数,即存在溢出的情况,这里我们规定要做溢出保护。

2、Python的仿真模型

对于算法翻译成RTL的例子,其实我们一般都会用高级语言进行仿真,以初步确定是否符合预期的结果,同时也可以用于后期和RTL结果的对比。本例子实际上很简单,可以不写。但为了便于大家理解,在这里还是给大家讲解如何用Python进行建模。

这里不讲解Python的语法,默认大家都会。有一点值得说明一下,如果用Python进行建模的话,Python的函数或者类其实就类似于一个模块或者是一个组合逻辑(Python实际上也做不了时钟级别的精确,只能做行为级别的结果对比)。其规定了输入和输出以及相应的运算,用这种方式和RTL对应写起来非常的方便。

对于输入a而言,由于其有4位小数,因此我们先将其定点化,转换为整数。即乘上2的4次方。(我们用高级语言仿真的时候,是真的会有小数的,因此我们要算好其真正的值),并且我们认为这个数最大是不可以超过7'b1111111的,如果超过这个值,比如它大到8'b10000000了,我们就认为它是0,也就是我们对输入是不做溢出保护的,我们默认它是不能超过规定的最大值的,这个由别的模块来保证,我这个模块不管这件事。

对于输入b也是同样的操作,但输入b乘上2的3次方以后,因为要和a做加法,所以小数点要对齐,要再乘以2。相当于两个小数相加的时候,你小数点本身就要对齐,因为b的小数点后只有3位,你实际上还要补个0,才能保证小数点对齐。所以要再乘以2。

然后基于定点化以后的a和b我们算出了c,这里我们认为c是不会超过2^8-1的,因为输入最大就是2^7-1和2^6-1。又因为我们要做四舍五入,回忆一下之前的文章怎么说的做四舍五入。我们先多保留1位小数点,然后加1。然后再将小数点移动一位,这个值就是四舍五入的值。

这个例子中我们本来是要将小数点向左移动3位的,我们先移动2位,再加1。然后再向小数点向左移动1位。于是就得到了c3,对于c3同样不做溢出保护,因为位宽已经留足够了。紧接着我们再截取1位小数,得到c4结果,其中c4实际上就是3位整数,1位小数了。对于RTL,直接就可以将这个结果作为输出了,因为RTL的定点数实际上是看不到点的,点在哪里是彼此约定好的,其看上起似乎就是一个整数。但是对于Python仿真模型,你应该将结果再除以2,因为Python是真的知道它是个小数的,你不除以2就不符合你仿真的预期了。(大家结合代码好好理解一下)

def plus_float(a, b):
#a 3位整数、4位小数 
#b 2位整数,3位小数
#c 2位整数、3位小数
    a2 = int(a*(2**4))
    if a2 > 2**7-1:
        a2 = a2-2**7
    b2 = int(b*(2**3))*2
    if b2 > 2**6-1:
        b2 = b2-2**6
    c2 = a2 + b2 #c2 4位小数,4位整数
    if c2 > 2**8-1:
        c2 = c2-2**8
    c3 = int(c2/2**2) + 1 #5位整数,2位小数
    if c3 > 2**7-1:
        c3 = c3-2**7
    c4 = int(c3/2) # 3位整数,1位小数
    # 保护
    if c4 > 2**4-1:
        c = 2**4 - 1
    else:
        c = c4
    c = c/2 #非RTL代码
    return c

完成了模型的编写,我们写一个Python的tb,如下所示。我们让输入a从0开始,步长为2^-4方,逐渐增加,其实就对应我们规定好的3位整数,4位小数可能的变化范围。b也是类似的。我们相应的可以得到c的结果,然后和真正的c的结果c_real进行对比。我们可以看到误差在0.25以内,符合我们的预期。说明我们的算法没有问题,规定的位宽也没有问题。于是我们就可以开始写RTL了。

注意:这里的plus_float运算得到的c,其实就是我们认为RTL会得到的结果。而c_real是我们认为reference model的结果。

from plus_float import plus_float
import matplotlib.pyplot as plt
import numpy as np
def plot_line_chart(data):
    x = np.arange(len(data))
    plt.plot(x, data)
    plt.xlabel('X轴')
    plt.ylabel('Y轴')
    plt.title('折线图')
    plt.show()
err_group = []
for cnt1 in np.arange(0, 8-2**-4, 2**-4):
    a = cnt1
    for cnt2 in np.arange(0, 4-2**-3, 2**-3):
        b = cnt2
        c = plus_float(a, b)
        c_real = a+b
        if c_real > 8-2**-1:
            c_real = 8-2**-1
        err = abs(c_real-c)
        err_group.append(err)
plot_line_chart(err_group)

3、RTL代码编写

有了前面的Python仿真模型,可以证明我们的思路符合预期,我们设计的位宽也没有任何问题。因此我们可以编写相应的RTL代码:

我们首先看对于DUT而言,怎么从Python到RTL:

  • 输入输出没什么好说的,根据需求来就行;
  • a2其实就对应a,因为Verilog是看不到那个点的,其实仿真运算的时候默认这些数就是定点数;
  • b2要乘以2,这样才能和a是对齐的,相加才不出错;(你拿1.34+2.4,你都认为是定点数,于是你用134+24来算,这合理吗?)
  • c2就是a2+b2;
  • c3对应四舍五入多保留一位小数再加1;
  • c4把多保留的那个小数移动一位,得到真正四舍五入的结果;
  • c是c4做溢出保护;

这样我们就完成了RTL的编写,可以认为RTL和行为模型一模一样。

module plus_float(
    input   [6:0]   a, //3 4
    input   [4:0]   b, //2 3
    output  [3:0]   c  //3 1
);
//信号声明我不写了
assign a2=a;
assign b2={b,1'b0};;
assign c2=a2+b2;
assign c3=c2[7:2]+6'd1;
assign c4=c3[5:1]
assign c=c4>4'hf?4'hf:c4[3:0]
endmodule

然后我们写相应的Testbench,同样的信号声明和波形生成逻辑我也不写了。主要写整体逻辑,和Python的tb实际上是一模一样的,其实就是遍历输入a,b。比较一下DUT和reference model是否一致,如下图所示:

`timescale  1ns/1ps
module tb;
initial begin
    a=0;
    b=0;
    cnt1=0;
    cnt2=0;
    while(cnt1<8-0.0625)
    begin
        cnt1 = cnt1 + 0.0625;
        a=int'(cnt1*(2**4));
        cnt2=0;
        while(cnt2<4-0.125)
        begin
            b=int'(cnt2*(2**3));
            c_real=cnt1+cnt2;
            if(c_real>7.5) c_real=7.5;
            #10;
        end
    end
    #100;
    $finish;
end
assign c2=real'(c)/2.0;
assign err=$abs(c_real-c2);
plus_float u_plus_float
(
    .a(a),
    .b(b),
    .c(c)
)
endmodule

最终得到仿真的结果和之前Python仿真得到的结果一致,误差在0.25以内,符合预期。到此为止整个带小数考虑溢出保护和四舍五入的加法模块编写完成,希望大家举一反三,彻底掌握定点化的机制所在。以后可以独立完成类似模块的编写。


目录
相关文章
|
存储 算法 C++
剑指offer(C++)-JZ46:把数字翻译成字符串(算法-动态规划)
剑指offer(C++)-JZ46:把数字翻译成字符串(算法-动态规划)
|
机器学习/深度学习 编解码 算法
如何将算法翻译成RTL(零):绪论
如何将算法翻译成RTL(零):绪论
277 0
|
算法 Python
如何将算法翻译成RTL(四)有符号数的加减法实现
如何将算法翻译成RTL(四)有符号数的加减法实现
197 0
|
算法
如何将算法翻译成RTL(三):Verilog中的Signed本质及用法
如何将算法翻译成RTL(三):Verilog中的Signed本质及用法
266 0
|
算法
如何将算法翻译成RTL(一):溢出保护
如何将算法翻译成RTL(一):溢出保护
202 0
|
算法 C++ Python
【每日算法Day 86】面试经典题:把数字翻译成字符串
【每日算法Day 86】面试经典题:把数字翻译成字符串
|
算法
数据结构与算法之[把数字翻译成字符串]&&动态规划
数据结构与算法之[把数字翻译成字符串]&&动态规划
75 0
数据结构与算法之[把数字翻译成字符串]&&动态规划
|
存储 算法
【蚁狮算法】《The Ant Lion Optimizer》原文翻译(附源代码)
【蚁狮算法】《The Ant Lion Optimizer》原文翻译(附源代码)
|
15天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。