第11章:组合逻辑电路设计—多路选择器
本章对应原书第 326~330 页,属于「第四篇 数字电路篇」。
本章在讲什么?(先看这个)
上一章的门电路只会"算",这一章的电路学会了"选"。多路选择器就像一个多档位的开关:给你好几路输入信号,再给你一个"选择信号"(sel),由它决定哪一路信号被送到输出端。它是 FPGA 内部最常用的基本资源之一,主要用于内部信号的选通。这一章你将设计一个二选一多路选择器,同时一口气学会 Verilog 描述"选择"的三种语法——三目运算符、if-else 和 case 语句,并继续走仿真 + 上板的完整流程。
核心概念拆解
多路选择器简介
多路选择器 MUX(Multiplexer)是一个多输入、单输出的组合逻辑电路。一个 N 输入的多路选择器就是一个 N 路的数字开关,可以根据通道选择信号,从 N 个输入中选取一个输出到公共的输出端。
打个比方:公司前台有一部话机(输出),有 N 条外部线路打进来(输入),前台小姐(sel 信号)决定把哪条线路接通到话机上。
多路选择器是 FPGA 内部的一个基本资源,主要用于内部信号的选通。简单的多路选择器还可以通过级联生成更大的多路选择器——比如多个二选一可以搭出四选一。
硬件设计
开发板右下侧板载 5 个按键和 4 个 LED:最左边红色按键为 PL 端复位按键,它右边两个按键连接至 ZYNQ 的 PL 引脚,最右边两个按键连接至 PS 引脚;最左边两个 LED 连接 PL 端,最右边两个连接 PS 端。



从原理图可以看出:按键默认状态为高电平,按下后为低电平;LED 灯为高电平点亮。每个按键都连接了一个 10K 限流电阻,防止按键被按下时电源直接接地造成短路。本章只使用 PL 端的外设。
实验任务
设计一个 2 选 1 多路选择器:通过选通控制信号 sel 确定输入信号 in1 和 in2 哪一个作为输出。sel 为 1 时输出 A 端信号;sel 为 0 时输出 B 端信号。
程序设计——模块设计
模块命名为 mux2_1。输入为三个 1 位信号:in1、in2 为两路数据输入,sel 为控制信号;out 为 1 位输出。

输入端可以用两个用户按键加一个复位按键表示,输出端用一个 LED 表示——这样 sel 按下(低电平)或不按(高电平)就能切换数据通道。
绘制波形图

从波形图可以直观看到:sel 为高时,out 跟着 in1 走;sel 为低时,out 跟着 in2 走。写代码前先画波形,是本章延续的方法论。
编写代码——三种实现方式
方法一:assign + 条件运算符(三目运算符)
module mux2_1(
input in1, //输入信号in1
input in2, //输入信号in2
input sel, //选择控制信号sel
output out //输出信号out
);
//out:组合逻辑输出选择结果
//此处使用的是条件运算符(三目运算符),当括号里面的条件成立时
//执行"?"后面的结果;如果括号里面的条件不成立时,执行":"后面的结果
assign out = (sel == 1'b1) ? in1 : in2;
endmodule
方法二:always 块 + if-else
module mux2_1(
input in1, //输入信号in1
input in2, //输入信号in2
input sel, //选择控制信号sel
output reg out //输出信号out
);
//out:组合逻辑输出选择结果
always@(*) begin //"*"为通配符,在这个模块中的任何一个输入信号或电平发生变化时
if(sel == 1'b1) //该语句下方的模块将被执行。
out <= in1;
else
out <= in2;
end
endmodule
注意两点:always@(*) 里的 * 是通配符,表示任何一个输入变化就重新执行块内语句;组合逻辑用 always 描述时,输出必须声明为 output reg 型。
方法三:always 块 + case 语句
module mux2_1(
input in1, //输入信号in1
input in2, //输入信号in2
input sel, //选择控制信号sel
output reg out //输出信号out
);
//out:组合逻辑输出选择结果
always@(*) begin
case(sel)
1'b0 :
out <= in2;
1'b1 :
out <= in1;
//如果sel的情况没有全部列举出来一定要加default与默认判断
//因为此处sel只有两种情况,并且都列举了,所以default后面省略了判断
default : ;
endcase
end
endmodule
三种写法对比:条件简单时,第一种三目运算符最简洁方便。另外能看出 Verilog 和 C 语言有不少相似之处,但本质不同——Verilog 代码最终映射为具体硬件电路,而 C 代码编译为指令在 CPU 上运行。
仿真验证——编写 TB 文件
TestBench 验证分三步:添加激励 → 信号例化 → 判断输出。输入端口为 in1、in2、sel,只需对这三个信号加激励即可。
`timescale 1ns / 1ns
module tb_mux2_1();
//reg define
reg in1;
reg in2;
reg sel;
//wire define
wire out;
//initial语句是不可以被综合的,一般只在testbench中表达而不在RTL代码中表达。
initial
begin //在仿真中begin...end块中的内容都是顺序执行的
in1 = 1'b0; //初始状态赋值
in2 = 1'b0;
sel = 1'b0;
#100 //经过100ns的延时
in1 = 1'b0; //100ns时的输入值
in2 = 1'b0;
sel = 1'b1;
#100 //经过200ns的延时
in1 = 1'b0; //200ns时的输入值
in2 = 1'b1;
sel = 1'b0;
#100 //经过300ns的延时
in1 = 1'b0; //300ns时的输入值
in2 = 1'b1;
sel = 1'b1;
#100 //经过400ns的延时
in1 = 1'b1; //400ns时的输入值
in2 = 1'b0;
sel = 1'b0;
#100 //经过500ns的延时
in1 = 1'b1; //500ns时的输入值
in2 = 1'b0;
sel = 1'b1;
#100 //经过600ns的延时
in1 = 1'b1; //600ns时的输入值
in2 = 1'b1;
sel = 1'b1;
end
//例化2选一选择器模块
mux2_1 u_mux2_1(
.in1(in1), //输入信号in1
.in2(in2), //输入信号in2
.sel(sel), //选择控制信号sel
.out(out) //输出信号out
);
endmodule
要点回顾:testbench 只有模块名、没有端口列表(它不会被其他模块例化);initial/always 块中赋值的变量必须是 reg 型;输出变量由被测模块驱动,在 testbench 中定义为 wire 型;每隔 100ns 对输入赋一次值,产生完整激励。


图 11.4.3 中 sel=1'b0、in2=1'b1 时 out=1'b1;图 11.4.4 中 sel=1'b1、in1=1'b0 时 out=1'b0。仿真结果与真值表对应,有兴趣可以多截几组波形对比验证。
下载验证
引脚约束:in1、in2 用两个用户按键,sel 用 PL 端复位按键,out 用一个 LED:
| 信号名 | 方向 | 管脚 | 端口说明 | 电平标准 |
|---|---|---|---|---|
| in1 | input | L14 | PL_KEY0(底板) | LVCMOS33 |
| in2 | input | K16 | PL_KEY1(底板) | LVCMOS33 |
| sel | output | N16 | PL_RESET(底板) | LVCMOS33 |
| out | output | H15 | PL_LED0(底板) | LVCMOS33 |
set_property -dict {PACKAGE_PIN L14 IOSTANDARD LVCMOS33} [get_ports in1]
set_property -dict {PACKAGE_PIN K16 IOSTANDARD LVCMOS33} [get_ports in2]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sel]
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports out]

RTL 原理图:在 Flow Navigator → RTL ANALYSIS → Open Elaborated Design → Schematic 打开 RTL 原理图。


图中 RTL_MUX 就是选择器:in1 和 in2 两个输入信号,根据控制选择信号 sel 的数据进行输出选择。提醒:RTL 分析原理图只针对 RTL 层面;想看代码和 FPGA 内部资源的对应关系,需要综合后在 SYNTHESIS → Open Synthesized Design → Schematic 查看。
上板验证:生成比特流文件后点击 Open Hardware Manager,把 Xilinx 下载器一端连电脑、另一端连开发板 JTAG 下载口,再连接电源。

注意!一定要先把下载器连接到电脑和 JTAG 接口之后,再给开发板上电!带电热插拔 JTAG 接口有一定概率会损坏接口。

点击 Program Device,Bitstream File 会自动识别工程的比特流文件,点击 Program 下载。下载完成后核心板上 PL 配置完成灯点亮,底板上 PL_LED0 点亮。
实验现象分析:按键默认高电平,所以 in1、in2、sel 均为高电平,LED 点亮。sel 为高时,只有 PL_KEY0(in1)能控制 LED 的亮灭(按下 KEY0,in1 变低,out 变低,灯灭);如果想用 PL_KEY1(in2)控制 LED,必须按下 PL_RESET 使 sel 变为低电平。
本章总结(新语法速查)
本章掌握了 LED/按键电路原理(IO 输出 1 点亮 LED、输出 0 熄灭),以及 Verilog 三种选择语法:
三目运算符:
<表达式1>?<表达式2>:<表达式3>;
先求表达式 1 的值,为真则执行并返回表达式 2 的结果;为假则执行并返回表达式 3 的结果。
if 语句:
if(控制表达式/值)
执行语句;
else if(控制表达式/值)
执行语句;
……
else
执行语句;
if 语句自上向下执行,逐个判断分支条件,命中则执行对应操作;所有条件都不成立时执行 else。
case 语句:
case(控制表达式/值)
分支表达式1:执行语句;
分支表达式2:执行语句;
……
default:执行语句;
endcase
case 从上往下逐个比较分支表达式与控制表达式的值,相等则执行对应语句后跳出 case;都不相等时执行 default。
initial 语句:不可被综合,只用于 testbench;块内语句上电后只执行一次,用于初始化输入信号,否则仿真信号可能产生不定态;begin...end 相当于括号,块内给多个变量赋值时必须加上。
图片对照
本章图片已按讲解位置分散插入各小节,包括按键/LED 硬件图、模块框图、波形图、仿真波形、引脚约束界面、RTL 原理图和上板连接图。
初学者容易踩的坑
- 组合逻辑 always 块的输出忘写 reg:用 always + if-else/case 描述组合逻辑时,输出端口必须声明为
output reg,否则编译报错。 - case 语句漏写 default:条件没列举全又没有 default,综合器可能推断出锁存器(latch),带来时序隐患——务必养成补 default 的习惯。
- 误解上板现象:按键是低有效(按下为 0),sel 默认为高电平选通 in1,所以不按复位键时 KEY1 是"没用"的,不是程序错了。
- always@(*) 里写成具体信号:敏感列表写
*最保险,手写信号列表容易漏掉某个输入导致仿真与实际不符。 - RTL 原理图当成资源图:Elaborated Design 的 Schematic 只是 RTL 层面的架构图,不代表 FPGA 内部真实资源占用。
- JTAG 带电插拔:必须先接好下载器再上电,否则可能损坏 JTAG 接口。
小结 & 下一步
本章用二选一多路选择器走通了完整设计流程,重点掌握了三目运算符、if-else、case 三种"选择"写法和 initial 语句。建议动手拓展:把现有例程稍作修改,实现一个四路选择器(比如用 2 位 sel 从 4 路输入中选一路),编写代码并仿真验证,检验自己对 case 语句的掌握程度。