如何将算法翻译成RTL(四)有符号数的加减法实现

简介: 如何将算法翻译成RTL(四)有符号数的加减法实现

之前的文章给大家介绍了带小数的加减法实现,学习本篇文章之前大家可以先复习一下之前这篇文章。

lawliet:如何将算法翻译成RTL(二):带小数的加减法实现8 赞同 · 1 评论文章

本篇文章的内容是之前的深入,既有浮点,又有符号,接下来我们进入正文。

注:这里的有符号不是说用signed声明,而是你默认这个数是有符号的数,按照有符号的方式对这些数进行处理。

1、有符号数加减法模块简介

我们考虑两个有符号小数的加减法:

  • 其中输入a为3位整数,4位小数,有符号,因此一共是8位;
  • 输入b为2位整数,3位小数,有符号,因此一共是6位;
  • 输出c为3为整数,1位小数,有符号,要求四舍五入输出,一共是5位;

同样的,我们需要对c做溢出保护。

2、Python的仿真模型

我们养成好习惯,对于这种算法类的IP先用高级语言进行建模,确定是否符合预期,可行了再编写RTL代码。

我把注释都写在代码里面好了,这样大家可能看起来更加方便一点。

import math
# a 3位整数 4位小数 位宽为8
# b 2位整数 3位小数 位宽为6
# c 3位整数 1位小数 位宽为5 要做四舍五入
# 都是有符号数
def plus_signed(a, b):
    a2 = math.floor(a*(2**4))  # 这一步RTL是不需要做的,RTL没有什么小数的概念,一输入进来就已经是a2了
    # 限定a2的最大值,这一步是用来模仿RTL限制位宽的情况。因为你用高级语言如Python写的时候是没有位宽的概念,这里是模拟位宽限制的概念
    if a2 > 2**7-1:
        a2 = a2-2**8  # 这个时候说明是负数了,你应该减去一大圈。比如说只有3位,一位符号位。你看上去是100,实际上是-4,你应该用4-8得到这个-4
    elif a2 < -2**7:
        a2 = a2+2**8  # 这个时候在位宽受限的情况下,是小不到这个程度的。说明此时已经会算出正数了,你应该加上一圈
        # 比如你用-4再减去1,你以为是-5,实际上三位有符号根本表示不了-5,你用100减去1得到011实际上是(-5+8)=3
        # 这些都是在模拟你写RTL的时候,在位宽受限的情况下可能出现的情况。
    # 将b定点化,位宽和a对齐,七位位宽
    b2 = math.floor(b*(2**3))*2
    if b2 > 2**6-1:
        b2 = b2-2**7
    elif b2 < -2**6:
        b2 = b2+2**7
    # c2:4位小数,4位整数,有符号,位宽为9位
    c2 = a2 + b2
    if c2 > 2**8-1:
        c2 = c2-2**9
    elif c2 < -2**8:
        c2 = c2+2**9
    # c3:5位整数,2位小数,有符号,位宽为8位,四舍五入多保留一位小数,再加1
    c3 = math.floor(c2/2**2) + 1
    if c3 > 2**7-1:
        c3 = c3-2**8
    elif c3 < -2**7:
        c3 = c3+2**8
    # 移除掉多保留的那一位小数,位宽为7位
    c4 = math.floor(c3/2)
    if c4 > 2**6-1:
        c4 = c4-2**7
    elif c4 < -2**6:
        c4 = c4+2**7
    # 保护
    if c4 > 2**4-1:
        c = 2**4 - 1
    elif c4 < -2**4:
        c = -2**4
    else:
        c = c4
    # RTL中没有下面这一部分,因为没有小数点的概念。是你认为小数点在那个位置
    # 这里除以2是在在模拟那个小数点的概念,所以要除以2
    c = c/2
    return c

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

注意,这里不是用int,而是用math.floor。应该要向下取整。二者在正数的时候效果相同,负数的时候int默认向上取整。

from plus_signed import plus_signed
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 axi')
    plt.ylabel('Y axi')
    plt.title('plus_signed')
    plt.show()
err_group = []
for cnt1 in np.arange(-8, 8-2**-4, 2**-4):
    a = cnt1
    for cnt2 in np.arange(-4, 4-2**-3, 2**-3):
        b = cnt2
        c = plus_signed(a, b)
        c_real = a+b
        if c_real > 7.5:
            c_real = 7.5
        elif c_real <= -8:
            c_real = -8
        err = abs(c_real-c)
        if err >= 0.5:
            print("{}+{}={} not equal to {}".format(a, b, c, c_real))
        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!我们假设a2和b2都是负数,那么你直接相加出来完全可能c2[8]还是0,你用两个负数相加得到一个正数,那肯定不对啊!我举个简单的例子,比如a2是1000_0000,而b2是000_0000。直接相加得到1000_0000,你赋值给c2的时候就是0_1000_0000,一个负数加一个0得到一个正数,那当然不对啊。

回顾一下这篇文章的2.7节是怎么做的,对这种有符号数相加并且限制位宽的情况,我们应该扩展1bit,然后再相加。在这个例子中的b应该扩展2bit,让位宽一致再运算。还是上面的例子,a2是1000_0000,而b2是000_0000。扩展以后就是1_1000_0000+0_0000_0000得到1_1000_0000。这种情况下符合我们的预期。再比如a2是1000_0000,b2是111_1111。扩展以后就是1_1000_0000加上1_1111_1111得到1_0111_1111。也就是-128加上负一得到了-129。符合我们的预期。

当然你c2的比特宽度要声明对,比如你c2声明100比特,那肯定加出来是个正数。其实还有更简单的方法,那就是用signed声明,这种方式的话,你根本不用管有没有溢出,默认扩展符号位运算,不会出错的。大家可以试试。大家只要用signed的时候注意我上一篇文章说的注意事项,就不会出错。

https://zhuanlan.zhihu.com/p/648593117

总而言之,有符号数相加,还带位宽限制的情况,非常容易出错,需要小心处理。

我还想说明一下,其实出现结果不符合预期,说白了就是因为仿真器按照无符号数处理,但是你心里认为它是有符号数,那二者就存在偏差了!比如010加010算出来100,你能说仿真器错了吗?它没错,只不过你认为100是-4,2+2算出来了-4不符合你预期,你应该按照你的预期去处理这种情况,告诉仿真器,在代码里面体现这种情况应该怎么算。这样才不会出错。

https://zhuanlan.zhihu.com/p/645127918

c3对应四舍五入多保留一位小数再加1;

c4把多保留的那个小数移动一位,得到真正四舍五入的结果;

c是c4做溢出保护,分正负数两种情况进行讨论。如果是正数的话,但凡[5:4]有一个是1则溢出,负数的话[5:4]都是1才认为不溢出;

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

module plus_signed(
    input   [7:0]   a,
    input   [5:0]   b,
    output  reg [4:0]   c
);
wire [8:0] c2;
wire [7:0] c3;
wire [6:0] c4;
wire [7:0] a2;
wire [6:0] b2;
//-----------------------------------
assign a2 = a;
assign b2={b,1'b0};
assign c2={a2[7],a2}+{b2[6],b2[6],b2};
assign c3=c2[8:2]+6'd1;
assign c4=c3[7:1];
always @(*) begin
    if(~c4[6]) //c4>=0
    begin
        if(|c4[5:4]) //overflow
            c=5'd15;
        else 
            c=c4[4:0];
    end
    else begin
        if(~(&c4[5:4]))
            c=5'd16;
        else 
            c={c4[6],c4[3:0]};
    end
end
endmodule

然后我们写相应的Testbench,同样的信号声明和波形生成逻辑我也不写了。值得注意的是a,b要用signed进行声明,不然赋值的时候前面补数会有问题。我们主要写整体逻辑,和Python的tb实际上是一模一样的,其实就是遍历输入a,b。比较一下DUT和reference model是否一致。

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

image.png

最后大家可以将RTL代码中,变量都改成signed声明,想一想如果是signed声明的变量,相应的逻辑应该怎么写。


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