这一篇在干嘛?
数码管是 FPGA 开发板上最直观的「显示器」,而驱动它的核心是一段七段译码器——把 4 位二进制数翻译成 7 个段的亮灭组合。这一篇从共阳数码管的结构和真值表讲起,用纯组合逻辑(case 语句)实现 0~F 的十六进制译码,并完成仿真与上板验证。这是组合逻辑设计的入门必修课。
实验目标与电路原理
实验任务:学习共阳数码管 7 段数码显示译码器的硬件设计。
数码管的结构
一个七段数码管由 7 个条形 LED(段)组成,习惯上记为 g、f、e、d、c、b、a(g 是中间横段,按从上到下、顺时针排列),如果想显示小数点还要加第 8 段 h。
「共阳」的意思是:7 个 LED 的阳极全部连在一起接电源正极,每个段的阴极由 FPGA 引脚单独控制。因此——
共阳数码管是「低电平点亮」
段信号输出 0(低电平)→ 该段导通发亮;输出 1(高电平)→ 该段熄灭。这一点和点亮普通 LED 的直觉相反,是七段译码最容易写反的地方。写真值表时先问自己一句:我的段亮是 0 还是 1?
为什么要用 FPGA 做译码
7 段数码显示本质上是纯组合电路(输出只由当前输入决定,没有时钟参与)。传统的小规模专用 IC,如 74 系列或 4000 系列器件,只能做十进制 BCD 码译码;然而数字系统中的数据处理和运算都是二进制的,输出表达通常是十六进制(0~F)。为了满足十六进制数的译码显示,最方便的方法就是利用 Verilog/VHDL 译码程序在 FPGA 中来实现。
共阳数码管示意图如下:

图 1.1 共阳数码管
从真值表到段码
设输入的 4 位码为 A[3:0],输出控制 7 段共阳数码管的 7 位数据为 LED7S[6:0]。输出信号 LED7S 的 7 位分别接数码管的 7 个段,高位在左、低位在右,即 LED7S[6] 对应 g 段、LED7S[0] 对应 a 段。
举例验证一下极性:当 LED7S 输出为 7'b1000_000 时,g 段为 1(灭),f、e、d、c、b、a 六段全为 0(亮),正好拼出一个「0」。按这个规则(亮 = 0),0~F 的完整真值表如下:
| 输入 A | 显示字符 | LED7S[6:0](g f e d c b a) |
|---|---|---|
| 4’b0000 | 0 | 7’b1000_000 |
| 4’b0001 | 1 | 7’b1111_001 |
| 4’b0010 | 2 | 7’b0100_100 |
| 4’b0011 | 3 | 7’b0110_000 |
| 4’b0100 | 4 | 7’b0011_001 |
| 4’b0101 | 5 | 7’b0010_010 |
| 4’b0110 | 6 | 7’b0000_010 |
| 4’b0111 | 7 | 7’b1111_000 |
| 4’b1000 | 8 | 7’b0000_000 |
| 4’b1001 | 9 | 7’b0010_000 |
| 4’b1010 | A | 7’b0001_000 |
| 4’b1011 | b | 7’b0000_011 |
| 4’b1100 | C | 7’b1000_110 |
| 4’b1101 | d | 7’b0100_001 |
| 4’b1110 | E | 7’b0000_110 |
| 4’b1111 | F | 7’b0001_110 |
对照这张表自检几行:显示「8」要 7 段全亮,所以是 7'b0000_000(全 0);显示「1」只亮 b、c 两段,所以是 7'b1111_001。看懂这两行,整张表你就掌握了。
Verilog 实现
程序按照 case 语句的表述方法,根据上面的真值表直接写出:
module DECL7S (A,LED7S,B);
input [3:0] A; //四位二进制输入
output [6:0] LED7S; //动态数码管七段
output reg[7:0] B; //动态数码8位位选信号
reg [6:0] LED7S;
always @(A)
begin
B <= 8'b11111110; //置最右侧数码管点亮,左侧七个数码管灭
case(A)
4'b0000 : LED7S <= 7'b1000_000; //七段译码:显示 0
4'b0001 : LED7S <= 7'b1111_001; //显示 1
4'b0010 : LED7S <= 7'b0100_100; //显示 2
4'b0011 : LED7S <= 7'b0110_000; //显示 3
4'b0100 : LED7S <= 7'b0011_001; //显示 4
4'b0101 : LED7S <= 7'b0010_010; //显示 5
4'b0110 : LED7S <= 7'b0000_010; //显示 6
4'b0111 : LED7S <= 7'b1111_000; //显示 7
4'b1000 : LED7S <= 7'b0000_000; //显示 8
4'b1001 : LED7S <= 7'b0010_000; //显示 9
4'b1010 : LED7S <= 7'b0001_000; //显示 A
4'b1011 : LED7S <= 7'b0000_011; //显示 b
4'b1100 : LED7S <= 7'b1000_110; //显示 C
4'b1101 : LED7S <= 7'b0100_001; //显示 d
4'b1110 : LED7S <= 7'b0000_110; //显示 E
4'b1111 : LED7S <= 7'b0001_110; //显示 F
default : LED7S <= 7'b1000_000;
endcase
end
endmodule(说明:原文 PDF 经 OCR 后,case 中 6 之后的分支损毁严重,以上按原文可辨识的 0~5 分支与同系列动态扫描实验的段码表补全,段码值与开发板通用译码表一致。)
几个代码要点:
- 敏感列表是
@(A)而不是@(posedge clk)——没有时钟,这就是纯组合逻辑。输入 A 一变,LED7S 立即重新译码; B <= 8'b11111110放在 always 里:位选信号B是 8 位数码管的位选(低电平选通),置11111110表示只点亮最右侧一个数码管,左侧七个灭。本实验只演示一位显示,所以位选固定不动;- 如果要考虑小数点,需要增加段 h,然后把
LED7S改为 8 位输出即可。
组合逻辑的 always 也可以用 assign 改写
这个译码器完全可以用
assign LED7S = ...+ 函数/三目运算实现,但 16 个分支用 case 写最清晰易查表。记住组合逻辑两条铁律:敏感列表写全输入;case 记得补 default,否则综合器会推断出锁存器(latch),引入意想不到的毛刺。
实验仿真
根据描述语言进行仿真测试,分析输入 A 变化时 LED7S 的译码输出波形是否符合真值表:

图 1.2 VWF 仿真结果
检查波形的要点:每当 A[3:0] 从 0000 递增到 1111,LED7S[6:0] 应当依次跳变为真值表中的对应值。例如 A=0001 时 LED7S 应为 1111001;A=1000 时应为 0000000。用波形工具放大到每个输入跳变处逐一核对。
引脚约束
本实验的输入用 4 位拨码/按键开关 A[3:0],输出包括 7 段段选 LED7S[6:0] 和 8 位位选 B[7:0]:
set_pin_assignment { A[0] } { LOCATION = C8; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }
set_pin_assignment { A[1] } { LOCATION = C7; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }
set_pin_assignment { A[2] } { LOCATION = C6; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }
set_pin_assignment { A[3] } { LOCATION = C5; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }
set_pin_assignment { B[0] } { LOCATION = D11; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { B[1] } { LOCATION = E11; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { B[2] } { LOCATION = F10; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { B[3] } { LOCATION = C13; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { B[4] } { LOCATION = E10; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { B[5] } { LOCATION = E12; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { B[6] } { LOCATION = E16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { B[7] } { LOCATION = F16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { LED7S[0] } { LOCATION = C15; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { LED7S[1] } { LOCATION = C16; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { LED7S[2] } { LOCATION = B6; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { LED7S[3] } { LOCATION = A5; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { LED7S[4] } { LOCATION = B5; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { LED7S[5] } { LOCATION = A8; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { LED7S[6] } { LOCATION = A7; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }整理成表格便于核对:
| 信号 | FPGA 引脚 | 方向 | 说明 |
|---|---|---|---|
| A[0] | C8 | input | 输入第 0 位 |
| A[1] | C7 | input | 输入第 1 位 |
| A[2] | C6 | input | 输入第 2 位 |
| A[3] | C5 | input | 输入第 3 位 |
| LED7S[0]~[6] | C15, C16, B6, A5, B5, A8, A7 | output | 七段段选(a~g) |
| B[0]~[7] | D11, E11, F10, C13, E10, E12, E16, F16 | output | 8 位位选 |
引脚号别跨实验照搬
本实验中复位/按键用到的 C5 在这里被分配给了输入位 A[3],位选引脚与后面静态/动态数码管实验一致(它们驱动同一组数码管硬件)。每个实验的约束表都以其原文为准,跨实验「复制粘贴」引脚前先确认硬件连接没有变化。
下载测试
按标准流程编译布线并下载后,用开关 SW1~SW4 分别输入 0000、0001、0010、0011……1111,观察数码管 1 的显示效果:

正常现象:数码管依次显示 0、1、2、3、4、5、6、7、8、9、A、b、C、d、E、F,共 16 个字符,且只亮最右侧一位(位选 B 固定为 11111110)。若某段不亮或常亮,先对照真值表检查对应段的极性是否写反,再核对 LED7S 各位与引脚的对应顺序(高位在左 g、低位在右 a)。
现象与总结
本实验的关键收获:
- 组合逻辑的最小范例:
always @(A)+ case 查表,输入变输出立刻变,无时钟、无寄存器节拍; - 真值表驱动的设计方法:先在纸上把 16 种输入对应的输出列全,再机械地翻译成 case 分支——数字逻辑设计里「先表后码」的标准工作流;
- 共阳极性:亮 = 低电平。段码写反的故障现象是「该亮的灭、该灭的亮」,按位取反即可修正;
- 位选与段选分离:段选决定「显示什么」,位选决定「哪一位显示」,这个二维度控制结构是下一篇动态扫描显示的理论基础。
自测
自测
- 共阳数码管为什么是低电平点亮?如果误按「高电平点亮」写真值表,显示会出现什么现象?
- 要显示字符「8」和「1」,LED7S[6:0] 分别输出什么二进制值?逐段说明 g~a 的亮灭情况。
- 代码中 always 的敏感列表为什么写
@(A)而不是@(posedge clk)?写成后者会变成什么类型的电路?- case 语句中如果漏掉 default 分支,综合时可能推断出什么结构?为什么它是有害的?
- 位选信号
B <= 8'b11111110控制的是哪一个数码管点亮?如果想让最左边一位点亮,B 应该输出什么值?- 为什么 74 系列 BCD 译码器不够用,而要在 FPGA 里用 Verilog 实现译码?