基于FPGA的数字信号处理(19)--行波进位加法器

news2025/1/15 12:50:20

1、10进制加法是如何实现的?

        10进制加法是大家在小学就学过的内容,不过在这里我还是帮大家回忆一下。考虑2个2位数的10进制加法,例如:15 + 28 = 43,它的运算过程如下:

image-20240426153428462

个位两数相加,结果为5 + 8 = 13,结果的1作为进位传递到十位,而3则作为和的低位保留

十位的两数相加同时加上来自低位的进位1,即1 + 2 + 1 = 4,且没有向高位产生进位

        因为没有产生进位也可以看做是产生了数值为0的进位,所以我们把十位和个位都添加上来自低位的进位,以及去往高位的进位,如下:

image-20240426153604608

        这样的两位数加法,实际上就拆解成两个加法器的级联了。单个加法器和2进制全加器一样,可以计算2个1位数的加法,同时接受来自低位的进位,以及产生向高位的进位,就像这样:

image-20240426153911874

2、行波进位加法器RCA

        同10进制加法相加类似,2个多bits的2进制数相加,也可以通过这种级联的形式来构成。考虑2个4bits数的加法,每个全加器都可以处理它对应位数的两个数的加法,同时接收来自低级的进位,并向高位产生进位,所以它的结构是这样的:

image-20240426154209316

        这样的加法器叫做 行波进位加法器 或 纹波进位加法器(Ripple Carry Adder,RCA),这个取名大概是因为它的进位传递是一级一级往外(前)扩散的,就好像水面泛起的波纹一样。

        以两个4bits数相加为例:5 + 6 = 11,即 0101 + 0110 = 1011,它的过程如下:

image-20240426155144935

        根据RCA的结构,可以很快地写出它的Verilog实现形式:

//使用多个全加器级联构建RCA加法器
module rca(
    input   [3:0]   x,      //加数1
    input   [3:0]   y,      //加数2
    input           cin,    //来自低位的进位
    output  [3:0]   sum,    //和
    output          cout    //向高位的进位
);
​
wire    c1,c2,c3;           //进位连接
​
//例化全加器来构建RCA加法器
full_adder  u0(
    .x      (x[0]),
    .y      (y[0]), 
    .sum    (sum[0]),
    .cin    (cin),
    .cout   (c1)
);
full_adder  u1(
    .x      (x[1]),
    .y      (y[1]), 
    .sum    (sum[1]),
    .cin    (c1),
    .cout   (c2)
);
full_adder  u2(
    .x      (x[2]),
    .y      (y[2]), 
    .sum    (sum[2]),
    .cin    (c2),
    .cout   (c3)
);
full_adder  u3(
    .x      (x[3]),
    .y      (y[3]), 
    .sum    (sum[3]),
    .cin    (c3),
    .cout   (cout)
);
​
endmodule 

        这里记得把全加器的代码也要添加进工程。生成的示意图如下(虽然这个排布不能很好地看出来层次结构,但确实没错):

image-20240426160920178

        然后写个TB测试一下这个加法器电路,因为4个bits即16×16=256种情况,加上低位进位的两种情况,也才256×2=512种情况,所以可以用穷举法来测试:

`timescale 1ns/1ns              //时间刻度:单位1ns,精度1ns
​
module tb_rca();            
​
//定义变量  
reg     [3:0]   x;      //加数1
reg     [3:0]   y;      //加数2
reg             cin;    //来自低位的进位
wire    [3:0]   sum;    //和
wire            cout;   //向高位进位
​
reg [3:0]   sum_real;   //和的真实值,作为对比
reg         cout_real;  //向高位进位的真实值,作为对比
wire        sum_flag;   //sum正确标志信号
wire        cout_flag;  //cout正确标志信号
​
assign sum_flag  = sum  == sum_real;    //和的结果正确时拉高该信号
assign cout_flag = cout == cout_real;   //进位结果正确时拉高该信号
​
integer z,i,j;  //循环变量
​
//设置初始化条件
initial begin
    //初始化
    x =1'b0;    
    y =1'b0;    
    cin =1'b0;  
    //穷举所有情况
    for(z=0;z<=1;z=z+1)begin
        cin = z;
        for(i=0;i<16;i=i+1)begin
            x = i;
            for(j=0;j<16;j=j+1)begin
                y = j;
                if((i+j+z)>15)begin                 //如果加法的结果产生了进位
                    sum_real = (i+j+z) - 16;        //减掉进位值
                    cout_real = 1;                  //向高位的进位为1
                end
                else begin                          //如果加法的结果没有产生了进位
                    sum_real = i+j+z;               //结果就是加法本身
                    cout_real = 0;                  //向高位的进位为0
                end
                #5;             
            end 
        end
    end
    #10 $stop();    //结束仿真  
end
​
//例化被测试模块
rca u_rca(
    .x      (x),
    .y      (y),    
    .sum    (sum),
    .cin    (cin),
    .cout   (cout)
);
    
endmodule

        TB中分别用3个嵌套的循环将所有情况穷举出来,即cin=0~1、x=0~15和y=0~15的所有情况。加法运算的预期结果也是很容易就可以找出来的,就是在TB中直接写加法就行。接着构建了两个标志向量sum_flag和cout_flag作为电路输出与预期正确结果的对比值,当二者一致时即拉高这两个信号。这样我们只要观察这两个信号,即可知道电路输出是否正确。仿真结果如下:

image-20240426161105042

        可以看到,sum_flag和cout_flag都是一直拉高的,说明电路输出正确。

3、RCA加法器的缺陷

        因为RCA的结构是从低到高依次级联的,所以它的进位链特别长,比如加法 1111 + 0000 + 1(最后的1表示来自低位的进位即cin),它的进位从最低位开始,需要经过4级全加器才能传递到最高级,如下:

image-20240426161537866

        这条进位cin传递的路径也是拖垮整个电路速度的关键路径(Critical Path),它的长度(延迟)为 4*全加器 的延迟。可以预见,随着加法器位宽的增加,这条路径也会越来越长,所以RCA不适合位宽很大的加法,因为它的延迟实在是太高了。

        以RCA的基础组成部分全加器FA为例,它的结构是这样的:

image-20240426201523357

        图中的红色路径就是关键路径,即延迟最高的路径,它由 1个异或门延迟 + 1个与门延迟 + 1个或门延迟 + 布线延迟 组成,若忽略布线延迟(和门电路延迟比起来,布线延迟相对较小),并将3种门电路的延迟都近似看做同一个数值的话,则单个全加器的延迟是 3个门电路延迟

        这么说,从直观上感觉多个全加器构成的行波进位加法器的关键路径延迟应该是 3×全加器数量(即加法位宽),比如两个4bits数相加,其关键路径延迟应该是 4×3=12个门电路延迟,但实际上不是,我们看下具体结构:

image-20240426202724901

        除了在第一个全加器有3个门电路的延迟外,后面经过的全加器都只有两个门电路的延迟,所以总共的延迟是 3 + 3*2 = 9个,由此可以推广到Nbits数,其延迟为 3 + 2×(N - 1) = 2N + 1 个门电路

        在RCA的基础上,工程师们又设计了很多种其他的加法器结构,它们的延迟较之RCA加法器有了显著的降低,其中比较有名的一种加法器是 超前进位加法器(Lookahead Carry Adder),我们将在下一篇文章介绍它。

4、RCA加法器的参数化设计

        在上面的内容种,对RCA的举例是两个4bits数相加实现的形式,为了满足不同位宽的加法,这里也给出参数化设计形式的Verilog代码:

//使用多个全加器级联构建RCA加法器
module rca 
#(
    parameter integer WIDTH = 4
)
(
    input   [WIDTH-1:0] x,      //加数1
    input   [WIDTH-1:0] y,      //加数2
    input               cin,    //来自低位的进位
    output  [WIDTH-1:0] sum,    //和
    output              cout    //向高位的进位
);
​
wire [WIDTH:0] c_wire;          //用来连线传递的进位变量
​
assign c_wire[0] = cin;         //最低位是输入的进位
assign cout = c_wire[WIDTH];    //最高位是输出的进位
​
//用generate来例化多个模块   
genvar i;    
generate
    for(i=0;i<WIDTH;i=i+1)
    begin:full_adder
        full_adder u_full_adder(
            .x      (x[i]       ),
            .y      (y[i]       ),
            .sum    (sum[i]     ),
            .cin    (c_wire[i]  ),
            .cout   (c_wire[i+1])
        );
    end
endgenerate  
​
endmodule 

配套的TB也改成参数化形式:

`timescale 1ns/1ns              //时间刻度:单位1ns,精度1ns
​
module tb_rca();
​
parameter integer WIDTH = 'd4;          
​
//定义变量  
reg     [WIDTH-1:0] x;      //加数1
reg     [WIDTH-1:0] y;      //加数2
reg                 cin;    //来自低位的进位
wire    [WIDTH-1:0] sum;    //和
wire                cout;   //向高位进位
​
reg [WIDTH-1:0] sum_real;   //和的真实值,作为对比
reg             cout_real;  //向高位进位的真实值,作为对比
wire            sum_flag;   //sum正确标志信号
wire            cout_flag;  //cout正确标志信号
​
assign sum_flag  = sum  == sum_real;    //和的结果正确时拉高该信号
assign cout_flag = cout == cout_real;   //进位结果正确时拉高该信号
​
integer z,i,j;  //循环变量
​
//设置初始化条件
initial begin
    //初始化
    x = 0;  
    y = 0;  
    cin = 0;    
    //穷举所有情况
    for(z=0;z<=1;z=z+1)begin
        cin = z;
        for(i=0;i<(2**WIDTH);i=i+1)begin
            x = i;
            for(j=0;j<(2**WIDTH);j=j+1)begin
                y = j;
                if((i+j+z)>(2**WIDTH-1))begin           //如果加法的结果产生了进位
                    sum_real = (i+j+z) - (2**WIDTH);    //减掉进位值
                    cout_real = 1;                      //向高位的进位为1
                end 
                else begin                              //如果加法的结果没有产生了进位
                    sum_real = i+j+z;                   //结果就是加法本身
                    cout_real = 0;                      //向高位的进位为0
                end
                #5;             
            end 
        end
    end
    #10 $stop();    //结束仿真  
end
​
//例化被测试模块
rca #(
    .WIDTH  (WIDTH)
)
u_rca(
    .x      (x),
    .y      (y),    
    .sum    (sum),
    .cin    (cin),
    .cout   (cout)
);
    
endmodule

(1)把位宽width改成4

        生成的4bits加法的RCA示意图:

image-20240428155131457

        仿真结果证明电路设计无误:

image-20240428155210128

(2)把位宽width改成8

        生成的8bits加法的RCA示意图:

image-20240428155239697

        仿真结果证明电路设计无误:

image-20240428155307270

5、RCA加法器的时序性能

        为了探究RCA加法器的时序性能,需要再原有代码的基础上,做一些小小的改变:在输入和输出分别添加上寄存器。如下:

//使用多个全加器级联构建RCA加法器
module rca 
#(
    parameter integer WIDTH = 32
)
(
    input               clk,
    input   [WIDTH-1:0] x,      //加数1
    input   [WIDTH-1:0] y,      //加数2
    input               cin,    //来自低位的进位
    output  [WIDTH-1:0] sum,    //和
    output              cout    //向高位的进位
);
​
reg                 cin_r,cout_r;
reg     [WIDTH-1:0] x_r,y_r,sum_r;
​
wire    [WIDTH:0]   c_wire;         //用来连线传递的进位变量
wire    [WIDTH-1:0] sum_w;          //用来连线传递和
​
​
//输入寄存
always@(posedge clk)begin
    x_r <= x;
    y_r <= y;
    cin_r <= cin;
end
​
assign c_wire[0] = cin_r;       //最低位是输入的进位 
​
//输出寄存
always@(posedge clk)begin
    sum_r <= sum_w;
    cout_r <= c_wire[WIDTH];    //最高位是输出的进位
end
​
assign sum = sum_r;
assign cout = cout_r;
​
//用generate来例化多个模块   
genvar i;    
generate
    for(i=0;i<WIDTH;i=i+1)
    begin:full_adder
        full_adder u_full_adder(
            .x      (x_r[i]     ),
            .y      (y_r[i]     ),
            .sum    (sum_w[i]   ),
            .cin    (c_wire[i]  ),
            .cout   (c_wire[i+1])
        );
    end
endgenerate  
​
endmodule 

        分别例化4位加法,8位加法,16位加法和32位加法,记录它们的逻辑级数logic levels、最差建立时间裕量WNS和电路面积,并算出最大运行频率Fmax。如下:

4位8位16位32位
WNS(ns)8.7778.1556.9174.429
Fmax(Mhz)818542324180
logic levels(级)24816
电路面积(不考虑FF)4 LUT8 LUT16 LUT32 LUT

        从上表可以看到:

  • 随着加法器位宽的增加,逻辑级数也越来越大,这是导致时序性能变差的直接原因

  • 时序性能从818M相关性地降低到180M,需要说明的是这里的最大频率Fmax只能作为一个参考,因为我整个工程只添加了这么一个加法器,而且Fmax一般还和FGPA的器件强挂钩,一般的器件肯定是跑不到800M的,这里我们主要是观察这个频率降低的趋势

  • 电路面积上是几位加法就用几个LUT(因为1个全加器用1个LUT),而且都是直接级联的

        作为参考,我们不使用任何加法器,就直接用加法运算符 + 来实现加法,电路就让综合工具vivado来自动生成,代码如下:

//直接写加法,看Vivado综合的结果
module rca 
#(
    parameter integer WIDTH = 32
)
(
    input               clk,
    input   [WIDTH-1:0] x,      //加数1
    input   [WIDTH-1:0] y,      //加数2
    input               cin,    //来自低位的进位
    output  [WIDTH-1:0] sum,    //和
    output              cout    //向高位的进位
);
​
reg                 cin_r,cout_r;
reg     [WIDTH-1:0] x_r,y_r,sum_r;
​
wire    [WIDTH-1:0] sum_w;
wire                cout_w;
​
//输入寄存
always@(posedge clk)begin
    x_r <= x;
    y_r <= y;
    cin_r <= cin;
end
​
assign {cout_w,sum_w} = x_r + y_r + cin_r; //直接写加法
​
//输出寄存
always@(posedge clk)begin
    sum_r <= sum_w;
    cout_r <= cout_w;
end
​
//端口连接
assign sum = sum_r;
assign cout = cout_r;
​
endmodule

        看看时序性能如何:

4位8位16位32位
WNS(ns)8.7778.7558.6578.461
Fmax(Mhz)818803745650
logic levels(级)2359
电路面积(不考虑FF)4 LUT8 LUT + 3 CARRY416 LUT + 5 CARRY432 LUT + 9 CARRY4

        从上表可以看到:

  • vivado综合出来的加法电路在时序性能上明显比RCA电路要强

  • 逻辑级数的增加并没有RCA电路那么明显,哪怕是32位的加法也只有9级逻辑层级。这也是它频率能跑很高的直接原因

  • 4位加法使用的电路面积和RCA是一样的,因为位宽较小,综合工具直接用LUT而不是CARRY4来生成电路,二者在小位宽时的时序性能差不多

  • 之所以大位宽加法的时序性能仍然比较好是因为综合工具使用CARRY4来实现加法,这种结构的加法电路有很快的进位速度,而且可以合并很多个进位链上的LUT从而减少逻辑级数

  • CARRY4的使用尽管可以提高时序性能,但是也会增大一部分电路面积。当然了,拿这点面积来换性能的提升,还是十分划算的

如果你不了解CARRY4,可以看看这篇文章:从底层结构开始学习FPGA(7)----进位链CARRY4

或者看看这个专栏:从底层结构开始学习FPGA

6、总结

        行波进位加法器RCA结构简单,进位链长,时序性能差,在实际应用尤其是FPGA设计中基本不会使用。对于FPGA设计来说,如今的综合工具已经非常智能了,一般的加法还是不要自己设计加法器了,直接让综合工具生成或者用IP就行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1956664.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux系统配置STM32的开发环境(代码编辑,编译,下载调试)

常见的stm32开发都是直接使用keil-MDK工具的&#xff0c;这是个集成开发环境&#xff0c;包含了代码编辑&#xff0c;编译&#xff0c;下载&#xff0c;调试&#xff0c;等功能&#xff0c;而且keil还是个图形化操作工具&#xff0c;直接可以点击图标案件就可以实现编译下载啥的…

实现物理数据库迁移到云上

实现物理数据库迁移到云上 以下是一个PHP脚本,用于实现物理数据库迁移到云上的步骤: <?php// 评估和规划 $databaseSize = "100GB"; $performanceRequirements = "high"; $dataComplexity = "medium";$cloudProvider = "AWS"; …

Python小工具之httpstat网络分析

一、简介 Python httpstat是一个基于Python的命令行工具&#xff0c;用于测量HTTP请求的性能和状态信息。它能够向目标服务器发送HTTP请求&#xff0c;并显示详细的统计信息&#xff0c;包括DNS解析时间、建立连接时间、TLS/SSL握手时间、首字节时间、总时间等。这些信息对于排…

AI写作有用?未成年不准看巴黎奥运会!——早读(逆天打工人爬取热门微信文章解读)

早上动力AI该作的念头&#xff0c;发觉改完&#xff0c;狗屁不是 引言Python 代码第一篇 洞见 最残忍的社会现实&#xff1a;你能挣多少钱&#xff0c;都是被设计好的第二篇 啦啦啦 开剪辑啦结尾 引言 呀 我们正年轻着 最近觉得有点烦 因为自己有点堕怠 但是呢 越烦越急躁 导致…

网络协议一 : 搭建tomacat,intellij IDEA Ultimate 的下载,安装,配置,启动, 访问

需要搭建的环境 1.客户端--服务器开发环境 客户端&#xff1a;浏览器&#xff08;HTMLCSSJS&#xff09; 服务器&#xff1a;JAVA 1.安装JDK&#xff0c;配置JAVA_HOME 和 PATH 2.安装Tomcat 3.安装IDE--intellij IDEA Ultimate 是旗舰版的意思。 2.TOMCAT 的下载和解…

基于嵌入-对比学习的联邦知识图谱补全

1 引言 1.1 现存问题 在联邦学习中&#xff0c;跨客户端的数据异质性(即非相同分布的数据)是主要挑战&#xff0c;因为当数据异质性存在时&#xff0c;本地目标可能与全局目标不同。 如图所示&#xff0c;School KG中的三元组表示Bob和Jack的学术信息&#xff0c;而ecommerc…

Redis与缓存

文章目录 Redis与缓存一致性问题大Key问题缓存穿透缓存击穿缓存雪崩 Redis与缓存 Redis作为缓存具有高性能、丰富的数据结构和灵活的过期机制等优点。由于Redis将数据存储在内存中&#xff0c;它能提供极低的延迟和高吞吐量&#xff0c;适合用于缓存数据库查询结果、会话数据和…

集成测试:确保软件模块协同工作的关键步骤

目录 前言1. 集成测试的概念1.1 增量集成测试1.2 大爆炸集成测试 2. 集成测试的主要作用2.1 确保模块间正确交互2.2 发现设计缺陷2.3 提高系统稳定性 3. 集成测试在整个测试过程中的地位3.1 从单元测试到集成测试3.2 从集成测试到系统测试 4. 常用的集成测试工具4.1 JUnit4.2 T…

嵌入式开发测试实训室解决方案

一、建设背景 随着物联网、人工智能等技术的飞速发展&#xff0c;嵌入式系统作为这些技术的重要载体&#xff0c;其重要性日益凸显。为了满足市场对嵌入式技术人才的需求&#xff0c;培养具有扎实理论基础、熟练实践技能及创新能力的嵌入式开发工程师&#xff0c;设计并建设一…

3. 类的生命周期

类的生命周期是指一个类被加载&#xff0c;使用&#xff0c;卸载的一个过程&#xff0c;如下图&#xff1a; 类的加载阶段&#xff1a; 加载(Loading)阶段第一步是类加载器根据类的**全限定名&#xff08;也就是类路径&#xff09;**通过不同的渠道以二进制流的方式获取字节码…

ClkLog:开源用户行为分析框架,让数据分析更轻松

ClkLog&#xff1a;开源用户行为分析框架&#xff0c;让数据分析更轻松 在数据驱动的时代&#xff0c;找到一个好用的用户行为分析工具真是难上加难。但是今天你有福了&#xff0c;开源免费的 ClkLog 就是你的不二选择&#xff01;本文将为你详细介绍 ClkLog 的功能特点、技术架…

OpenCV 彩色直方图

彩色直方图 &#xff08;1&#xff09;直方图是图像中像素强度分布。 &#xff08;2&#xff09;直方图统计了每一个强度值所具有的像素个数。 &#xff08;3&#xff09;cv2.calcHist(images, channels, mask, histSize, ranges) import cv2 import matplotlib.pyplot as …

2023年系统架构设计师考试总结

原文链接&#xff1a;https://www.cnblogs.com/zhaotianff/p/17812187.html 上周六参加了2023年系统架构设计师考试&#xff0c;这次考试与以前有点区别&#xff0c;是第一次采用电子化考试&#xff0c;也是教材改版后的第一次考试。 说说考前准备&#xff1a;为了准备这次考试…

cadence SPB17.4 - allegro - DRC error “Line to SMD Pin Same Net Spaing“

文章目录 cadence SPB17.4 - allegro - DRC error "Line to SMD Pin Same Net Spaing"概述笔记END cadence SPB17.4 - allegro - DRC error “Line to SMD Pin Same Net Spaing” 概述 铺铜前DRC正确。 铺铜后&#xff0c;报错 DRC error “Line to SMD Pin Same …

Squeeze-and-Excitation Networks

1、引言 论文链接&#xff1a;Squeeze-and-Excitation Networks (thecvf.com) 为了提高 CNN(convolutional neural network) 的表示能力&#xff0c;最近的几种方法已经显示出增强空间编码的好处。而 Jie Hu[1] 等专注于通道关系并提出了 SE(Squeeze and Excitation) 块&#x…

uniapp 使用css实现大转盘

思路&#xff1a; 1.一个原型的外壳包裹 2.使用要分配的个数&#xff0c;计算出角度&#xff0c;利用正切函数tan计算出角度对应对边长度 3.使用clip-path画出一个扇形 4.使用v-for循环出对应的份数&#xff0c;依次使用transform rotate旋转对应的角度。 注意&#xff1a…

DM集群的高可用的配置方式(多语言)

一、介绍 强调以下&#xff1a;dm_svc.conf必须放置到应用服务器上才行&#xff0c;放到其他服务器上识别不到。 文章中有的框架可能没写到并不代表不支持&#xff0c;有没提到的可文章下方留言。 1.dm_svc.conf的作用&#xff1a; dm_svc.conf文件主要是为了当DM数据库集群…

ES(ElasticSearch)倒排索引

目录 正排与倒排索引 1.正排索引 作用&#xff1a; 优点&#xff1a; 缺点&#xff1a; 2.倒排索引 原理&#xff1a; 倒排索引的构建流程&#xff1a; 倒排索引的搜索流程&#xff1a; 优点&#xff1a; 缺点&#xff1a; 3. 应用场景 倒排索引中有几个非常重要的概念…

Skyeye云智能制造企业版源代码全部开放

智能制造一体化管理系统 [SpringBoot2 - 快速开发平台]&#xff0c;适用于制造业、建筑业、汽车行业、互联网、教育、政府机关等机构的管理。包含文件在线操作、工作日志、多班次考勤、CRM、ERP 进销存、项目管理、EHR、拖拽式生成问卷、日程、笔记、工作计划、行政办公、薪资模…

【图文详解】Spring是如何解决循环依赖的?

Spring是如何解决循环依赖的呢&#xff1f; 很多小伙伴在面试时都被问到过这个问题&#xff0c;刷到过这个题的同学马上就能回答出来&#xff1a;“利用三级缓存”。面试官接着追问&#xff1a;“哪三级缓存呢&#xff1f;用两级行不行呢&#xff1f;” 这时候如果没有深入研究…