第12章:组合逻辑电路设计之译码器

本章对应原书第 338~342 页,属于「第四篇 数字电路篇」。

本章在讲什么?(先看这个)

上一章的选择器是"多选一",这一章的译码器则反过来——用很少的输入组合,去控制很多路输出。3-8 译码器就是用 3 个输入位的 8 种组合,一一对应地点亮 8 路输出中的一路。"译码"这个词很形象:把压缩过的二进制编码"翻译"回一条条具体的控制信号。本章继续用"框图 → 真值表 → 波形图 → 代码 → 仿真"的套路,重点学习位拼接符 {} 的用法,并强化 case 语句的规范写法。

核心概念拆解

译码器简介

译码器(Decoder)是一种多输入、多输出的组合逻辑电路器件。它的工作原理是:将输入的二进制代码的状态信息转换成对应的输出控制信号。译码器广泛应用于数字电路和通信系统中,具体应用包括内存地址译码、数字显示、数据选择等。

打个比方:酒店房间号 101~108 是 3 位"编码",前台(译码器)根据你报的号码,精确点亮对应房间的灯——3 位编码能表示 8 个房间。

译码器的分类包括二进制译码器BCD 译码器七段显示译码器。其中二进制译码器实现 n 位二进制输入到 2^n 路输出的转换,典型如 74HC138 型 3-8 译码器。

3-8 译码器属于二进制译码器,也称为变量译码器:它将 3 位二进制输入信号转换为 8 个输出信号中的一个有效电平,每个输出信号对应输入值的一个唯一组合。

硬件设计

由于 3-8 译码器的上板验证需要用到 8 个 LED 灯,而开发板的 LED 灯数量不够,所以本实验只进行仿真验证,不进行上板测试

实验任务

设计一个 3-8 译码器并进行仿真验证。

程序设计——模块设计

3-8 译码器借助 3 个输入信号形成 8 种二进制组合,进而控制 8 个输出端口呈现 8 种不同的状态。模块框图如下:

图12.4.1 模块框图与端口

模块端口与功能描述:

信号名 方向 端口说明
A 输入 输入信号A
B 输入 输入信号B
C 输入 输入信号C
out 输出 译码后的输出信号out

绘制波形图

输入为 3 个 1bit 信号,任意二进制组合共 8 种,每种组合与 out 输出 8bit 的 8 种状态一一对应。先列真值表,再画波形:

A B C out
0 0 0 0000_0001
0 0 1 0000_0010
0 1 0 0000_0100
0 1 1 0000_1000
1 0 0 0001_0000
1 0 1 0010_0000
1 1 0 0100_0000
1 1 1 1000_0000

图12.4.2 3-8译码器模块波形图

规律很明显:out 中为 1 的那一位的位置,正好就是输入 ABC 组成的二进制数的值——这就是"译码"。

编写代码

使用上一章学习的 case 语句来实现:

module decoder3_8(
    input               A,
    input               B,
    input               C,

    output  reg [7:0]   out
    );

//*****************************************************
//**                    main code
//*****************************************************

//define wire
wire [2:0] sel;

//使用assign语句进行位拼接
assign sel = {A, B, C};  //使用"{}"位拼接符将3个1bit数据按照顺序拼成一个3bit数据

//out:根据输入的3bit sel信号选择输出对应的8bit out信号
always@(*) begin
    case(sel)
        3'b000 : out = 8'b0000_0001;
        3'b001 : out = 8'b0000_0010;
        3'b010 : out = 8'b0000_0100;
        3'b011 : out = 8'b0000_1000;
        3'b100 : out = 8'b0001_0000;
        3'b101 : out = 8'b0010_0000;
        3'b110 : out = 8'b0100_0000;
        3'b111 : out = 8'b1000_0000;
        default : ;
    endcase
end

endmodule

代码解读:

  • 第 13 行定义了一个位宽为 3 的信号 sel;
  • 第 16 行使用 assign 语句,通过位拼接符 {} 把三个 1bit 输入按 A、B、C 顺序拼成一个 3bit 数据赋给 sel。{A,B,C} 中 A 是最高位、C 是最低位,这样 sel 的值就直接等于三个输入组成的二进制数;
  • 第 21~28 行用 case 语句把 8 种译码关系全部列举。因为情况已经全部覆盖,第 29 行的 default 其实可以省略,但为了代码规范性和良好习惯,默认都加上 default——可以指定任意一种情况,也可以像这里一样留空。

编写 TB 文件

`timescale 1ns / 1ns

module tb_decoder_3_8();

//reg define
reg      A;
reg      B;
reg      C;

//define wire
wire [7:0] out;

//信号初始化
initial begin
    A = 0; B = 0; C = 0;
    #200
    A = 0; B = 0; C = 1;
    #200
    A = 0; B = 1; C = 0;
    #200
    A = 0; B = 1; C = 1;
    #200
    A = 1; B = 0; C = 0;
    #200
    A = 1; B = 0; C = 1;
    #200
    A = 1; B = 1; C = 0;
    #200
    A = 1; B = 1; C = 1;
    #200
    A = 0; B = 0; C = 0;
end

//例化3-8译码器模块
decoder3_8 decoder3_8_inst(
    .A   (A),
    .B   (B),
    .C   (C),

    .out(out)
    );

endmodule

第 1 行定义仿真单位和仿真精度都是 1ns;第 14~32 行的 initial 块先对三个输入赋初值 000,然后每隔 200ns 依次遍历 8 种输入组合(最后回到 000 形成闭环),正好覆盖全部译码情况。

仿真验证

打开 Modelsim 运行 10us:

图12.4.3 3-8译码器模块仿真波形

波形中三个输入信号与输出信号 out 之间的对应关系和代码中的译码关系完全一致:输入 000 时 out 为 8'b0000_0001,输入 001 时 out 变为 8'b0000_0010……"1"的位置随输入值逐位左移,符合逻辑设计。

本章总结

本章讲解了如何用 Verilog 实现经典组合逻辑 3-8 译码器,并通过仿真验证。本章大部分语法(case、always@(*)、testbench 写法)都在上一章讲过,这里做了强化巩固,同时新增了两个要点:

  1. 位拼接 {} 的使用:可以把多个信号按顺序拼成一个更宽的信号,本章正是用它把 3 个 1bit 输入拼成 3bit 选择信号,变相实现了"移位"式的译码选择;
  2. case 语句的规范:即使条件已全部列举,也保留 default 分支。

图片对照

本章原文图片仅一张(模块框图与波形示意),已在模块设计小节插入。

初学者容易踩的坑

  • 位拼接顺序搞反{A,B,C} 中 A 在最高位,写成 {C,B,A} 译码结果就全错了,输出会对应到别的组合上。
  • case 漏写 default:本章虽然 8 种情况全部列举,但养成写 default 的习惯非常重要,否则后续写不完整的 case 时会综合出锁存器。
  • out 忘记声明为 reg:case 写在 always@(*) 块里,输出必须声明为 output reg [7:0]
  • 数字位宽不匹配:case 分支里的常量必须写足位宽,如 3'b0008'b0000_0001,漏写位宽可能导致比较和赋值出错。
  • 期望上板却无法上板:本实验需要 8 个 LED 而板载不足,只能仿真验证——这是正常的实验安排,不要误以为自己漏了步骤。

小结 & 下一步

本章通过 3-8 译码器巩固了组合逻辑设计流程,重点掌握了位拼接符 {} 和 case 语句的规范用法。建议动手拓展:上一章的 if 语句同样可以实现译码器——试着独立编写一个用 if-else 实现的 3-8 译码器代码,并仿真验证,对比两种写法的优缺点。