第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 端。

图11.2.1 按键与LED实物图

图11.2.2 底板按键原理图

图11.2.3 底板LED灯原理图

从原理图可以看出:按键默认状态为高电平,按下后为低电平;LED 灯为高电平点亮。每个按键都连接了一个 10K 限流电阻,防止按键被按下时电源直接接地造成短路。本章只使用 PL 端的外设。

实验任务

设计一个 2 选 1 多路选择器:通过选通控制信号 sel 确定输入信号 in1in2 哪一个作为输出。sel 为 1 时输出 A 端信号;sel 为 0 时输出 B 端信号。

程序设计——模块设计

模块命名为 mux2_1。输入为三个 1 位信号:in1、in2 为两路数据输入,sel 为控制信号;out 为 1 位输出。

图11.4.1 模块框图与端口

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

绘制波形图

图11.4.2 绘制波形图

从波形图可以直观看到: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 mux2_1仿真波形a

图11.4.4 mux2_1仿真波形b

图 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]

图11.5.1 IO Planning引脚约束界面

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

图11.5.2 打开RTL原理图

图11.5.3 RTL原理图

图中 RTL_MUX 就是选择器:in1 和 in2 两个输入信号,根据控制选择信号 sel 的数据进行输出选择。提醒:RTL 分析原理图只针对 RTL 层面;想看代码和 FPGA 内部资源的对应关系,需要综合后在 SYNTHESIS → Open Synthesized Design → Schematic 查看。

上板验证:生成比特流文件后点击 Open Hardware Manager,把 Xilinx 下载器一端连电脑、另一端连开发板 JTAG 下载口,再连接电源。

图11.5.5 领航者开发板连接实物图

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

图11.5.7 与下载器连接成功

点击 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 语句的掌握程度。