4. 组合逻辑设计:基本门电路实现、多路选择器、译码器、加法器、比较器、三态门设计

组合逻辑,说白了就是“输入一变,输出立马跟着变”,没有记忆功能,也不依赖时钟。这是CPLD设计里最基础、也最常用的部分。我刚开始学CPLD时,就是从这些基本门电路入手的。你想想看,再复杂的系统,拆到最底层,无非就是与门、或门、非门这些基本单元的排列组合。

4.1 基本门电路实现

在CPLD里实现基本门电路,其实就是在写硬件描述语言。我个人习惯用Verilog,因为它语法简洁,上手快。下面这几个例子,是我在项目中几乎每次都会用到的“老面孔”。

核心要点:CPLD内部的基本逻辑单元(如宏单元)可以直接映射为与门、或门、非门。你写的代码,最终会变成实实在在的硬件连线。

// 基本门电路示例
module basic_gates (
    input  wire a, b,
    output wire y_and,
    output wire y_or,
    output wire y_not,
    output wire y_nand,
    output wire y_nor,
    output wire y_xor,
    output wire y_xnor
);

    assign y_and  = a & b;   // 与门
    assign y_or   = a | b;   // 或门
    assign y_not  = ~a;      // 非门
    assign y_nand = ~(a & b); // 与非门
    assign y_nor  = ~(a | b); // 或非门
    assign y_xor  = a ^ b;   // 异或门
    assign y_xnor = a ~^ b;  // 同或门

endmodule

嗯,这里要注意:assign语句是连续赋值,它描述的就是组合逻辑。你写y_and = a & b,CPLD综合工具就会在内部把a和b两个引脚连到一个与门的输入端,输出接到y_and上。就这么直接。

我的小技巧:在写代码前,我习惯先画个简单的逻辑图。哪怕只是草稿,也能帮你理清信号流向。尤其是当逻辑复杂时,画图比硬想快得多。

4.2 多路选择器

多路选择器(MUX)在传感器接口里太常见了。比如,一个ADC可能有多个通道,你需要用MUX来选通其中一个。我记得有一次做多通道温度采集,就是用MUX来切换8个热电偶的信号。

用Verilog实现MUX,最直接的方式就是用case语句或assign配合三元运算符。

// 4选1多路选择器
module mux4to1 (
    input  wire [1:0] sel,
    input  wire [3:0] data_in,
    output reg        data_out
);

    always @(*) begin
        case (sel)
            2'b00: data_out = data_in[0];
            2'b01: data_out = data_in[1];
            2'b10: data_out = data_in[2];
            2'b11: data_out = data_in[3];
            default: data_out = 1'b0;
        endcase
    end

endmodule

这里我用的是always @(*),它表示“只要输入信号有任何变化,就重新计算输出”。这就是组合逻辑的典型写法。你想想看,如果不用@(*),而是写@(sel or data_in),万一漏掉某个信号,仿真时就会出bug。所以我建议你直接用@(*),省心。

避坑指南:我曾经在写MUX时,忘了写default分支。结果综合后生成了一个锁存器(latch),导致整个逻辑行为异常。记住:组合逻辑的case语句,一定要覆盖所有可能的情况,否则综合工具会“好心”地给你补个锁存器,但往往不是你想要的。

4.3 译码器

译码器在CPLD里常用于地址译码或显示驱动。比如,你要驱动一个7段数码管,就需要把4位二进制数译成7段码。我做过一个项目,用CPLD控制16个LED,就是靠3-8译码器来选通每一行。

3-8译码器的实现很简单,本质上就是一个真值表的映射。

// 3-8译码器
module decoder3to8 (
    input  wire [2:0] addr,
    output reg  [7:0] y
);

    always @(*) begin
        case (addr)
            3'b000: y = 8'b0000_0001;
            3'b001: y = 8'b0000_0010;
            3'b010: y = 8'b0000_0100;
            3'b011: y = 8'b0000_1000;
            3'b100: y = 8'b0001_0000;
            3'b101: y = 8'b0010_0000;
            3'b110: y = 8'b0100_0000;
            3'b111: y = 8'b1000_0000;
            default: y = 8'b0000_0000;
        endcase
    end

endmodule

你看,代码很直观。addr输入3位,y输出8位,每个addr对应y的某一位为1。这就是“独热码”输出。实际项目中,你可能需要输出“低电平有效”的译码,那就把y的值取反即可。

4.4 加法器

加法器是算术运算的基础。在CPLD里,你可以用+运算符直接实现加法,综合工具会自动调用内部的加法器硬核。但如果你想深入理解,也可以自己搭一个全加器。

全加器的逻辑是:S = A ^ B ^ CinCout = (A & B) | (A & Cin) | (B & Cin)。说白了,就是三个输入(A、B、进位Cin)相加,得到和S与进位Cout。

// 1位全加器
module full_adder (
    input  wire a, b, cin,
    output wire s, cout
);

    assign s    = a ^ b ^ cin;
    assign cout = (a & b) | (a & cin) | (b & cin);

endmodule

// 4位加法器(使用运算符)
module adder4 (
    input  wire [3:0] a, b,
    input  wire       cin,
    output wire [3:0] s,
    output wire       cout
);

    assign {cout, s} = a + b + cin;

endmodule

我个人习惯,在需要快速实现时直接用+运算符。但如果你对资源消耗有严格要求,比如要节省几个宏单元,那手动搭全加器会更可控。我曾经在一个资源紧张的CPLD里,为了省下十几个宏单元,硬是把32位加法器拆成了4个8位加法器级联。嗯,效果还不错。

4.5 比较器

比较器用来判断两个数的大小或是否相等。在传感器接口中,我常用它来做阈值判断。比如,当温度超过某个值时,输出报警信号。

Verilog里比较器用关系运算符实现:==!=<><=>=

// 4位比较器
module comparator4 (
    input  wire [3:0] a, b,
    output reg        a_eq_b,
    output reg        a_lt_b,
    output reg        a_gt_b
);

    always @(*) begin
        a_eq_b = (a == b);
        a_lt_b = (a < b);
        a_gt_b = (a > b);
    end

endmodule

这里要注意:比较器也是组合逻辑,所以用always @(*)assign都可以。我建议用assign,因为代码更简洁,而且不容易出错。

我的经验:在做比较器时,如果比较的数据位宽很大(比如16位以上),综合工具可能会生成一个比较复杂的逻辑树。这时候,你可以考虑分步比较:先比较高位,高位相等再比较低位。这样能节省一些资源。

4.6 三态门设计

三态门是CPLD里一个很特殊的存在。它除了输出0和1,还能输出高阻态(Z)。高阻态意味着输出引脚被“断开”,相当于没接任何东西。这在总线共享时特别有用——多个设备可以挂在同一根线上,但同一时刻只能有一个设备驱动它。

在Verilog里,三态门用assign配合条件赋值来实现。

// 三态门示例
module tri_state (
    input  wire       en,
    input  wire       data_in,
    output wire       data_out
);

    assign data_out = en ? data_in : 1'bz;

endmodule

en为1时,data_out等于data_in;当en为0时,data_out变成高阻态。这样,多个三态门的输出就可以连到一起,形成一根共享总线。

避坑指南:我曾经在一个项目里,把两个三态门的使能信号同时拉高了。结果两个输出同时驱动同一根线,一个想拉高,一个想拉低,直接导致信号冲突,芯片发热严重。记住:三态门的总线,同一时刻只能有一个使能有效。否则,轻则逻辑错误,重则烧毁芯片。

知识体系总览

下面这张图,是我自己总结的组合逻辑设计知识结构。你可以把它当作一个快速索引。

组合逻辑设计 基本门电路 与或非 异或同或 多路选择器 (MUX) case / 三元运算符 译码器 3-8译码器 加法器 全加器 / 运算符+ 比较器 == < > 运算符 三态门设计 高阻态 (Z) / 总线共享

这张图把本章的六个核心知识点串了起来。从最底层的基本门电路,到更复杂的MUX、译码器、加法器、比较器,再到特殊的三态门,它们共同构成了组合逻辑设计的完整体系。你在实际项目中,往往需要把这些模块组合起来使用。比如,一个传感器数据采集系统,可能会先用MUX选通通道,再用加法器做累加,最后用比较器判断是否超限。嗯,这就是组合逻辑的魅力——简单、直接、高效。


专注资料整理