it编程 > 硬件开发 > fpga开发

基于FPGA的数字信号处理(17)--定点运算的实现实例(饱和Saturate与四舍五入Round)

75人参与 2024-08-01 fpga开发

前言

        在前面文章中,我们谈到了如何对一个定点数的整数和小数分布进行截位处理,对小数截位相当于四舍五入(或floor、ceil等其他方式),而对整数截位则相当于如何处理溢出,常见的办法有两种:wrap和saturate。wrap相当于不对溢出进行处理,任何其从高位绕回到低位。saturate则是进行饱和处理,将所有超出范围的数值都用最大值/最小值来表示。

        今天我们来通过几个例子,看看在运算中具体要如果处理这些位扩展和截位的问题。

        首先对定点数的格式做如下约定:

例1:无符号定点数的加法

考虑两个无符号定点数的加法:c = a + b,它们的数据格式如下:

        运算过程可以分为步骤:

        a的格式是uq7.4,b的格式是uq5.3,二者的小数点没有对齐,所以无法相加,需要把b的格式变为uq6.4(就是左移1位),才能将二者的小数点对齐,然后相加。整数部分其实也可以对齐(因为高位会自动补0,所以对齐不对齐都差不多,不过还是统一下吧),所以有

        然后做 a+b 的加法,但是为了防止和溢出,所以要把结果在它们的位宽扩展1位,即

        接着四舍五入(方法就不讲了,看前面的文章),同样的,为了仿真溢出,也把结果扩展1位,即

        然后对整数部分进行饱和处理,结果要求保留3位整数,而c_u6q1共有5位整数,所以只要判断高两位有没有1存在就知道数据是否溢出了,如果溢出了,就锁定在4bits数的最大值即4'hf,如果没有溢出,就不动它,所以有:

        综上,rtl部分的代码如下:

module test(
    input   [6:0]   a_u7q4, //无符号数,整数3位,小数4位
    input   [4:0]   b_u5q3, //无符号数,整数2位,小数3位    
    output  [3:0]   c_u4q1  //无符号数,整数3位,小数1位    
);
​
wire [6:0] b_u7q4;
wire [7:0] c_u8q4;
wire [5:0] c_u6q1;
​
assign b_u7q4 = {1'b0,b_u5q3,1'b0};
assign c_u8q4 = a_u7q4 + b_u7q4;
​
//四舍五入          
assign c_u6q1 = c_u8q4[7:3] + c_u8q4[2];            //防止加1产生溢出,扩展一位
​
//饱和处理
assign c_u4q1 = ( |c_u6q1[5:4] ) ? 4'hf : c_u6q1[3:0];
​
endmodule 

        验证的话,因为数据量也不大(4096个),可以把输入和输出都用matlab来生成,因为matlab有能直接处理定点数的函数 fi ,所以作为验证手段还是非常方便的。

        matlab部分的代码:

%--------------------------------------------------
% 关闭无关内容
clear;
close all;
clc;
​
%--------------------------------------------------
% 确定a的数据范围和精度
a_u7q4_min = 0;
a_u7q4_max = 2^3-1/2^4; 
a_u7q4_step = 1/2^4;
​
% 确定b的数据范围和精度
b_u5q3_min = 0;
b_u5q3_max = 2^2-1/2^3;
b_u5q3_step = 1/2^3;
​
%--------------------------------------------------
% 将a、b转换为对应格式的定点数
f = fimath('roundingmethod','round');                   % 确定舍入方式为四舍五入
a_u7q4 = fi(a_u7q4_min:a_u7q4_step:a_u7q4_max,0,7,4);   % fi格式:fi(数据,符号,字长,小数长度)
b_u5q3 = fi(b_u5q3_min:b_u5q3_step:b_u5q3_max,0,5,3);
​
% 生成定点数c_u8q4
for i = 1:length(a_u7q4)
    for k = 1:length(b_u5q3)
        c_u8q4(k+(i-1)*length(b_u5q3)) = a_u7q4(i) + b_u5q3(k);
    end    
end
​
%--------------------------------------------------
% 把c从u4q4格式转成u4q1格式,四舍五入和饱和截位都在这一步
c_u4q1 = fi(c_u8q4,0,4,1,f);
​
% 把a写入txt文件中
fid_a = fopen('a_u7q4_ref.txt','w');
for k = 1:length(a_u7q4)
    fprintf(fid_a, '%s\n', hex( a_u7q4(k) ) );
end
fclose(fid_a);
​
%--------------------------------------------------
% 把b写入txt文件中
fid_b = fopen('b_u5q3_ref.txt','w');
for k = 1:length(b_u5q3)
    fprintf(fid_b, '%s\n', hex( b_u5q3(k) ) );
end
fclose(fid_b);
​
% 把c写入txt文件中
fid_c = fopen('c_u4q1_ref.txt','w');
for k = 1:length(c_u4q1)
    fprintf(fid_c, '%s\n', hex( c_u4q1(k) ) );
end
fclose(fid_c);

        现在我们把所有正确的输入和输出分别存在三个文件里:<a_u7q4_ref.txt>,<b_u5q3_ref.txt>,<c_u4q1_ref.txt>。然后写个tb文件,把这些输入和输出读取进去。把这些输入送到被测电路,然后将被测电路的输出和matlab的输出做比较就可以判断被测电路功能是否正确了。tb如下:

`timescale 1ns/1ns
module test_tb();
​
reg     [6:0]   a_u7q4;             //无符号数,整数3位,小数4位
reg     [4:0]   b_u5q3;             //无符号数,整数2位,小数3位    
wire    [3:0]   c_u4q1;             //无符号数,整数3位,小数1位
    
integer     i,j,cnt;                //循环变量
reg [12:0]  err;                    //错误统计计数器
​
reg [6:0]   a_u7q4_ref  [0:127];    //将matlab生成的a_u7q4数据保存到该memory中,做为标准输入
reg [4:0]   b_u5q3_ref  [0:31];     //将matlab生成的b_u5q3数据保存到该memory中,做为标准输入
reg [3:0]   c_u4q1_ref  [0:4095];   //将matlab生成的c_u4q1数据保存到该memory中,做为标准输出,与rtl输出进行比较
​
//从文件中读取数据写入到对应的mem中
initial begin
    $readmemh("g:/matlab_test/a_u7q4_ref.txt",a_u7q4_ref);  
    $readmemh("g:/matlab_test/b_u5q3_ref.txt",b_u5q3_ref);  
    $readmemh("g:/matlab_test/c_u4q1_ref.txt",c_u4q1_ref);  
end
​
initial begin
    a_u7q4 = 0; //输入赋初值
    b_u5q3 = 0;
    cnt = 0;
    err = 0;
    //遍历所有的输入,共128*32=4096个
    for(i=0;i<128;i=i+1)begin   
        a_u7q4 = a_u7q4_ref[i];                 //从matlab生成的文件里载入输入
        for(j=0;j<32;j=j+1)begin
            b_u5q3 = b_u5q3_ref[j];             //从matlab生成的文件里载入输入         
            #5; 
            if(c_u4q1 != c_u4q1_ref[cnt])begin  //如果matlab的输出和rtl的输出不同
                err = err + 1;                  //错误个数加1
                $display("a_u7q4:%b     b_u5q3:%b       matlab output:%b    rtl output:%b",a_u7q4_ref[i],b_u5q3_ref[j],c_u4q1_ref[cnt],c_u4q1);
            end 
            cnt = cnt + 1;
        end
    end
    #20 
    if(err == 0)
        $display("pass");
    else
        $display("fail,there is %d errors",err);
    $stop();        //结束仿真
end
​
//例化被测试模块
test    test_inst(
    .a_u7q4     (a_u7q4),   
    .b_u5q3     (b_u5q3),       
    .c_u4q1     (c_u4q1)    
);
​
endmodule

        仿真结束后,在窗口打印了仿真成功的信息:

        同时观察波形,也会发现错误统计的个数为0:

例2:有符号定点数的加法

        在例1的基础上,把输入和输出都改成有符号的定点数。两个有符号定点数的加法:c = a + b,它们的数据格式如下:

        运算过程可以其实和例1是一样的,只是由于负数的存在,所以要多考虑一些边界情况,步骤如下:

        a的格式是q8.4,b的格式是q6.3,二者的小数点没有对齐和整数都没有对齐,所以无法相加,需要把b的格式变为q8.4,然后相加。需要注意的是两个符号数数的位宽不一致只要把和的位宽扩展一位结果就不会有问题。但是两个有符号数的加法,只扩展结果的位宽是有可能出问题的,比如:

        为了防止上面这种情况,可以采用的办法是:把和的位宽扩展1位,同时也把两个加数的位宽扩展到跟和的位宽一致(注意高位补符号位)。比如:

        把b的格式转换为q8.4,有

        然后做 a+b 的加法,但是为了防止和溢出,所以要把结果在它们的位宽扩展1位,同时两个加数也要扩展1位符号位,即

        接着四舍五入(方法就不讲了,看前面的文章),同样的,为了仿真溢出,也把结果扩展1位,即

        然后对整数部分进行饱和处理。c_7q1比c_5q1多了两位,如果数据没有溢出,那这两位肯定就是符号位,再加上c_5q1的最高位,3个数一起构成了符号位,也就是只要判断c_7q1的高三位是否为全1或全0即可。之所以可以这么推断是因为一个有符号数往高位扩展符号位,它的数值是不会改变的。比如:

        如果不为全0或全1则溢出了,需要将它饱和到最大值/最小值:

        因为符号位就是c_7q1的最高位,所以有:

        综上,rtl部分的代码如下:

module test(
    input   [7:0]   a_8q4,  //有符号数,符号1位,整数3位,小数4位
    input   [5:0]   b_6q3,  //有符号数,符号1位,整数2位,小数3位   
    output  [4:0]   c_5q1   //有符号数,符号1位,整数3位,小数1位   
);
​
wire       carry;
wire [7:0] b_8q4;
wire [8:0] c_9q4;
wire [6:0] c_7q1;
​
assign b_8q4 = {b_6q3[5],b_6q3,1'b0};
assign c_9q4 = {a_8q4[7],a_8q4} + {b_8q4[7],b_8q4};
​
//四舍五入  .x xxx
assign carry = c_9q4[8] ? (c_9q4[2] && (|c_9q4[1:0]) ) : c_9q4[2];  
assign c_7q1 = {c_9q4[8],c_9q4[8:3]} + carry;       
​
//饱和处理
assign c_5q1 = ( c_7q1[6:4] == 3'b000 || c_7q1[6:4] == 3'b111) ? c_7q1[4:0] : {c_7q1[6],{4{~c_7q1[6]}}};
​
endmodule

        测试的话,依然用matlab来生成输入和输出,代码和上面差不多,只有部分修改,如下:

%--------------------------------------------------
% 关闭无关内容
clear;
close all;
clc;
​
%--------------------------------------------------
% 确定a的数据范围和精度
a_8q4_min = -2^3;
a_8q4_max = 2^3-1/2^4; 
a_8q4_step = 1/2^4;
​
% 确定b的数据范围和精度
b_6q3_min = -2^2;
b_6q3_max = 2^2-1/2^3;
b_6q3_step = 1/2^3;
​
%--------------------------------------------------
% 将a、b转换为对应格式的定点数
f = fimath('roundingmethod','round');                   % 确定舍入方式为四舍五入
a_8q4 = fi(a_8q4_min:a_8q4_step:a_8q4_max,1,8,4);       % fi格式:fi(数据,符号,字长,小数长度)
b_6q3 = fi(b_6q3_min:b_6q3_step:b_6q3_max,1,6,3);
​
%c_9q4 = fi(zeros(1,256*64),1,9,4);
% 生成定点数c_9q4
for i = 1:length(a_8q4)
    for k = 1:length(b_6q3)
        c_9q4(k+(i-1)*length(b_6q3)) = a_8q4(i) + b_6q3(k);
    end    
end
​
%--------------------------------------------------
% 把c从9q4格式转成5q1格式,四舍五入和饱和截位都在这一步
c_5q1 = fi(c_9q4,1,5,1,f);
​
% 把a写入txt文件中
fid_a = fopen('a_8q4_ref.txt','w');
for k = 1:length(a_8q4)
    fprintf(fid_a, '%s\n', hex( a_8q4(k) ) );
end
fclose(fid_a);
​
%--------------------------------------------------
% 把b写入txt文件中
fid_b = fopen('b_6q3_ref.txt','w');
for k = 1:length(b_6q3)
    fprintf(fid_b, '%s\n', hex( b_6q3(k) ) );
end
fclose(fid_b);
​
% 把c写入txt文件中
fid_c = fopen('c_5q1_ref.txt','w');
for k = 1:length(c_5q1)
    fprintf(fid_c, '%s\n', hex( c_5q1(k) ) );
end
fclose(fid_c);

        tb部分也差不多,稍微改了一些数值和变量名,如下:

`timescale 1ns/1ns
module test_tb();
​
reg     [7:0]   a_8q4;  //有符号数,符号1位,整数3位,小数4位
reg     [5:0]   b_6q3;  //有符号数,符号1位,整数2位,小数3位   
wire    [4:0]   c_5q1;  //有符号数,符号1位,整数3位,小数1位
    
integer     i,j,cnt;                //循环变量
reg [12:0]  err;                    //错误统计计数器
​
reg [7:0]   a_8q4_ref   [0:255];    //将matlab生成的a_8q4数据保存到该memory中,做为标准输入
reg [5:0]   b_6q3_ref   [0:63];     //将matlab生成的b_6q3数据保存到该memory中,做为标准输入
reg [4:0]   c_5q1_ref   [0:16383];  //将matlab生成的c_5q1数据保存到该memory中,做为标准输出,与rtl输出进行比较
​
//从文件中读取数据写入到对应的mem中
initial begin
    $readmemh("g:/matlab_test/a_8q4_ref.txt",a_8q4_ref);    
    $readmemh("g:/matlab_test/b_6q3_ref.txt",b_6q3_ref);    
    $readmemh("g:/matlab_test/c_5q1_ref.txt",c_5q1_ref);    
end
​
initial begin
    a_8q4 = 0;  //输入赋初值
    b_6q3 = 0;
    cnt = 0;
    err = 0;
    //遍历所有的输入,共256*64=16384个
    for(i=0;i<256;i=i+1)begin   
        a_8q4 = a_8q4_ref[i];                   //从matlab生成的文件里载入输入
        for(j=0;j<64;j=j+1)begin
            b_6q3 = b_6q3_ref[j];               //从matlab生成的文件里载入输入         
            #5; 
            if(c_5q1 != c_5q1_ref[cnt])begin    //如果matlab的输出和rtl的输出不同
                err = err + 1;                  //错误个数加1
                $display("a_8q4:%b      b_6q3:%b        matlab output:%b    rtl output:%b",a_8q4_ref[i],b_6q3_ref[j],c_5q1_ref[cnt],c_5q1);
            end 
            cnt = cnt + 1;
        end
    end
    #20 
    if(err == 0)
        $display("pass");
    else
        $display("fail,there is %d errors",err);
    $stop();        //结束仿真
end
​
//例化被测试模块
test    test_inst(
    .a_8q4      (a_8q4),    
    .b_6q3      (b_6q3),        
    .c_5q1      (c_5q1) 
);
​
endmodule

        仿真结束后,在窗口打印了仿真成功的信息:

        同时观察波形,也会发现错误统计的个数为0:

(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

课程设计——基于FPGA的双向移位寄存器

08-01

【机组】基于FPGA的32位算术逻辑运算单元的设计(EP2C5扩充选配类)

08-01

【Verilog HDL 入门教程】 —— 学长带你学Verilog(基础篇)

08-01

一文看懂JTAG基本知识

08-01

FPGA学习笔记-1 FPGA原理与开发流程

08-01

【FPGA/IC】CRC电路的Verilog实现

08-01

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论