组合逻辑设计:加法器、乘法器、比较器、多路选择器、译码器

各位同学,咱们今天聊聊组合逻辑。说白了,组合逻辑就是那种“输入一变,输出立马跟着变”的电路,没有时钟,没有记忆,纯粹靠逻辑门搭出来的硬功夫。

我在做雷达信号处理的时候,组合逻辑用得最多的就是这些基本单元:加法器、乘法器、比较器、多路选择器、译码器。你别看它们基础,但FPGA里90%的运算都离不开它们。今天我就把这几样东西掰开了揉碎了讲给你听。

1. 加法器:FPGA里的“算盘”

加法器是数字信号处理的基础。雷达里做FFT、做滤波,说到底就是一堆加法。

半加器与全加器

半加器只管两个bit相加,不考虑进位输入。全加器则多了一个进位输入。我刚开始学的时候觉得这有啥区别?后来做多bit加法器才发现,进位链才是关键。

// 全加器代码
module full_adder (
    input  a, b, cin,
    output sum, cout
);
    assign sum  = a ^ b ^ cin;
    assign cout = (a & b) | (a & cin) | (b & cin);
endmodule

行波进位加法器

把多个全加器串起来,就是行波进位加法器。优点是结构简单,缺点是速度慢——进位像波浪一样一级一级传下去。我在项目中用过32位的行波进位加法器,结果时序跑不过,后来换了超前进位加法器才搞定。

注意:行波进位加法器的延迟跟位数成正比。位数多了,速度就上不去。FPGA里做高速加法,建议直接用LUT或者DSP48单元。

超前进位加法器

超前进位加法器提前算好进位,不用等上一级。速度更快,但逻辑资源消耗也大。FPGA综合工具一般会自动优化,但我建议你写代码时还是用“+”运算符,让工具去处理。

// 推荐写法:直接用加法运算符
assign result = a + b;

我的经验:在Xilinx的FPGA里,直接用“+”会映射到CARRY4原语,效率很高。别自己手写加法器,除非你有特殊需求。

2. 乘法器:DSP48的用武之地

乘法器在雷达里太常见了。做混频、做相关运算、做矩阵乘法,哪哪都有它。

组合乘法器

最简单的乘法器就是直接用“*”运算符。综合工具会自动调用DSP48硬核。我个人习惯是:能不用LUT实现乘法就不用,因为DSP48又快又省资源。

// 组合乘法器
module mult_comb (
    input  [7:0] a, b,
    output [15:0] result
);
    assign result = a * b;
endmodule

乘法器的位宽问题

两个N位二进制数相乘,结果最多是2N位。这个坑我踩过。有一次做雷达脉冲压缩,乘法结果截位没处理好,导致信号失真。嗯,这里要注意:截位时一定要考虑符号位和溢出。

输入位宽 输出位宽 说明
8位 × 8位 16位 无符号数
8位 × 8位 15位 有符号数(去掉符号扩展位)

关键点:FPGA里的DSP48支持25×18的乘法,做雷达信号处理时尽量把数据位宽对齐到这个范围,效率最高。

3. 比较器:判断大小的利器

比较器在雷达里用来做门限检测、峰值搜索。说白了就是比大小。

等值比较器

判断两个数是否相等,用“==”运算符。综合工具会生成XNOR门组成的比较电路。

// 等值比较
assign equal = (a == b);

大小比较器

判断大于、小于,用“>”、“<”运算符。有符号数和无符号数的比较结果不一样,这个要小心。

// 有符号数比较
assign greater = ($signed(a) > $signed(b));

我曾经踩过的坑:用无符号比较去比有符号数,结果负数被当成很大的正数。比如8'b10000000(-128)和8'b00000001(1)比较,无符号下10000000更大,但实际-128小于1。所以一定要用$signed()。

4. 多路选择器:数据通道的“开关”

多路选择器(MUX)用来从多个输入中选一个输出。雷达里做通道切换、参数选择时经常用。

2选1多路选择器

// 2选1 MUX
module mux2to1 (
    input  sel,
    input  [7:0] a, b,
    output [7:0] y
);
    assign y = sel ? a : b;
endmodule

多路选择器的实现方式

FPGA里的MUX可以用LUT实现,也可以用三态门实现。我个人建议用case语句写多路选择器,代码可读性好,综合效果也不错。

// 4选1 MUX
always @(*) begin
    case (sel)
        2'b00: y = a;
        2'b01: y = b;
        2'b10: y = c;
        2'b11: y = d;
        default: y = 8'b0;
    endcase
end

小技巧:如果选择信号是常数,综合工具会优化掉MUX。比如sel一直为0,那MUX就变成了直连线。所以写代码时别担心多余的选择器,工具会帮你优化。

5. 译码器:地址解析的“翻译官”

译码器把输入的编码转换成对应的输出信号。雷达里做地址译码、状态机状态解析时常用。

3-8译码器

// 3-8译码器
module decoder3to8 (
    input  [2:0] in,
    output reg [7:0] out
);
    always @(*) begin
        case (in)
            3'b000: out = 8'b00000001;
            3'b001: out = 8'b00000010;
            3'b010: out = 8'b00000100;
            3'b011: out = 8'b00001000;
            3'b100: out = 8'b00010000;
            3'b101: out = 8'b00100000;
            3'b110: out = 8'b01000000;
            3'b111: out = 8'b10000000;
            default: out = 8'b00000000;
        endcase
    end
endmodule

译码器的应用场景

在雷达信号处理中,译码器常用于寄存器地址映射。比如通过地址总线选择不同的配置寄存器。

地址 寄存器 功能
0x00 CTRL_REG 控制寄存器
0x01 STATUS_REG 状态寄存器
0x02 THRESHOLD_REG 门限寄存器

实战建议:写译码器时一定要加default分支,否则综合出来会有latch。我见过有人没加default,结果仿真正常,上板子就出问题。嗯,这个坑我帮你们踩过了。

总结

组合逻辑设计是FPGA的基石。加法器、乘法器、比较器、多路选择器、译码器,这五个基本单元你玩熟了,雷达信号处理的硬件实现就入门了。

最后说一句:写代码时多用“assign”和“always @(*)”,少用“always @(posedge clk)”。组合逻辑就是组合逻辑,别混进时序逻辑的写法。你想想看,如果组合逻辑里带了时钟,那还叫组合逻辑吗?

好了,今天就到这里。下一章咱们讲时序逻辑设计,到时候会聊到触发器、计数器、移位寄存器这些。记得预习。