第10章:逻辑门电路设计
本章对应原书第 305~314 页,属于「第四篇 数字电路篇」。
本章在讲什么?(先看这个)
数字世界里的一切运算,归根结底都建立在几种最简单的电路单元上——与门、或门、非门,它们就像积木中的基础块。这一章教你用 Verilog 语言把这三种门电路"画"到 FPGA 里,并完整走一遍开发流程:画框图 → 列真值表 → 画波形图 → 写代码 → 仿真验证 → 上板点亮 LED。这是你第一次把"代码"变成"电路",也是以后所有章节的基本功。学会了这一章,你就掌握了 FPGA 开发的"套路",后面学的只是更复杂的电路而已。
核心概念拆解
逻辑门电路简介
数字电路里常用的逻辑门电路(Logic Gate)有与门、或门和非门等。为了简化逻辑,实际电路中还扩展了与非门、或非门、异或门和同或门。这些门电路是构成数字电路的基本单元——好比汉字里的笔画,笔画虽少,组合起来却能写成千上万的文章。
在 FPGA 里,这些门电路不需要真的去搭三极管、电阻,而是用 Verilog 语言"描述"出来,由综合工具自动映射成 FPGA 内部的查找表等资源。
与门
与门(AND Gate)是执行"与"运算的基本门电路。它有多个输入端、1 个输出端。规则很好记:只有所有输入同时为高电平 1,输出才为 1;只要有一个是 0,输出就是 0。
生活化的理解:两把锁串联在同一扇门上,两把钥匙都有(都是1),门才能开。
与门的表达式是 F = A & B,其中 & 就是 Verilog 里定义的"与"逻辑符。

上图是与门的一种电路实现:用二极管和电阻搭出来。A、B 任意一端接低电平,输出就被拉低;两端都接高电平(+Vcc),Y 才输出高电平。

与门真值表可以整理成一句口诀:"有0出0,全1出1。" 二输入与门是最常见的数字电路基本单元。

波形图是数字工程师的"第二语言":只有当 A 和 B 的波形同时为高时,Y 的波形才是高。以后设计任何电路,都建议先画波形再写代码。
编写代码:
module and_gate(
input A, //输入A
input B, //输入B
output Y //输出Y
);
//assign 相当于一条连线,将A和B相与后的值连接到Y。
assign Y = A & B;
endmodule
assign 语句相当于一条导线,把右边表达式的结果直接连到左边信号。左边信号必须是 wire 型,右边一变左边立刻跟着变——这是描述组合逻辑最简单的方式。
仿真验证(Testbench):
TestBench 用来验证功能模块是否符合预期,主要分三步: 1) 向被测模块的输入接口添加激励; 2) 对被测模块的顶层接口进行信号例化; 3) 判断输出是否满足设计预期。
`timescale 1ns / 1ns
module tb_and_gate();
//define reg
reg A;
reg B;
//define wire
wire Y;
//初始化
initial begin
A = 1'b0;
B = 1'b0;
#100
A = 1'b0;
B = 1'b1;
#100
A = 1'b1;
B = 1'b0;
#100
A = 1'b1;
B = 1'b1;
end
//例化逻辑与模块
and_gate and_gate_inst(
.A(A), //输入A
.B(B), //输入B
.Y(Y) //输出Y
);
endmodule
几个关键点:
- 第 1 行 1ns/1ns 表示仿真时间单位和仿真精度都是 1ns;
- testbench 模块名后面不写端口列表,因为它不会被别的模块例化;
- 在 initial/always 块中赋值的变量要声明为 reg 型(A、B 由我们手动给激励);输出 Y 由被测模块驱动,声明为 wire 型;
- initial 块先让 A=B=0,然后每隔 100ns 依次切换到 01、10、11 四种组合,正好覆盖真值表全部情况。

仿真波形与真值表完全一致,说明与门电路设计正确。
上板验证:
开发板右下侧板载 5 个按键和 4 个 LED。最左边的红色按键是 PL 端复位按键;它右边两个按键连接至 ZYNQ 的 PL 引脚,最右边两个连接至 PS 引脚。最左边两个 LED 连接 PL 端,最右边两个连接 PS 端。



从原理图可以看出:按键默认为高电平,按下后为低电平;LED 灯为高电平点亮。每个按键都串了一个 10K 限流电阻,防止按键按下时电源直接接地造成短路。本实验只用 PL 端外设。
引脚约束(A 用 PL_KEY0,B 用 PL_KEY1,Y 用 PL_LED0):
| 信号名 | 方向 | 管脚 | 端口说明 | 电平标准 |
|---|---|---|---|---|
| A | input | L14 | PL_KEY0(底板) | LVCMOS33 |
| B | input | K16 | PL_KEY1(底板) | LVCMOS33 |
| Y | output | H15 | PL_LED0(底板) | LVCMOS33 |
set_property -dict {PACKAGE_PIN L14 IOSTANDARD LVCMOS33} [get_ports A]
set_property -dict {PACKAGE_PIN K16 IOSTANDARD LVCMOS33} [get_ports B]
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports Y]

RTL 原理图:在 Vivado 左侧 Flow Navigator → RTL ANALYSIS → Open Elaborated Design → Schematic 可以打开 RTL 原理图,直观看到代码和电路的对应关系。


图中 RTL_AND 就是一个与门,A、B 是两个输入信号,输出 Y 由 A 与 B 经过与门输出。注意:RTL 分析原理图只反映 RTL 层面,不是代码和 FPGA 内部资源的对应关系图;想看真实资源映射,需要综合后在 Flow Navigator → SYNTHESIS → Open Synthesized Design → Schematic 查看。
下载步骤:生成比特流文件后,点击 Open Hardware Manager,将 Xilinx 下载器一端连电脑、另一端连开发板 JTAG 下载口,再连接电源。

注意!一定要先连接好下载器与 JTAG 接口,再给开发板上电!带电热插拔 JTAG 接口有一定概率会损坏接口。
连接成功后点击 Auto Connect → Program Device 下载。下载完成后核心板上的 PL 配置完成灯(LED3)点亮。

实验现象:按键默认高电平,A、B 均为高,与门输出高电平,PL_LED0 点亮。按下 PL_KEY0(A 变低)或 PL_KEY1(B 变低),Y 变低,LED 熄灭。两个键都松开时 LED 又亮——这就是"全1出1,有0出0"的实物演示。
或门
或门(OR Gate)执行"或"运算。含义是:决定一件事的任意一个条件具备,事件就会发生。它也是多输入、单输出:任意一个输入为 1,输出就为 1;全部输入为 0 时,输出才为 0。
生活化理解:家里两个开关并联控制一盏灯,按任意一个开关灯都会亮。
表达式 F = A | B,| 是 Verilog 的"或"逻辑符。


或门真值表口诀:"有1出1,全0出0。"

编写代码:
module or_gate(
input A, //输入A
input B, //输入B
output Y //输出Y
);
//assign 相当于一条连线,将A和B相或后的值连接到Y。
assign Y = A | B;
endmodule
仿真验证:或门的 TB 完全可以复用与门的仿真代码,只需把例化的模块改成或门模块即可。
`timescale 1ns / 1ns
module tb_or_gate ();
reg A;
reg B;
wire Y;
initial begin
A = 1'b0;
B = 1'b0;
#100
A = 1'b0;
B = 1'b1;
#100
A = 1'b1;
B = 1'b0;
#100
A = 1'b1;
B = 1'b1;
end
//例化逻辑或模块
or_gate or_gate_inst(
.A(A), //输入A
.B(B), //输入B
.Y(Y) //输出Y
);
endmodule

仿真结果与或门真值表一致,设计正确。
上板验证:或门实验与与门实验采用完全相同的管脚约束,上板步骤直接参考与门一节即可。区别只在于现象:因为"有1出1",默认按键都为高时 LED 亮;按下任意一个按键,只要另一个还是高电平,LED 依然亮——只有两个键同时按下才熄灭。
非门
非门(NOT Gate)就是取反:事情要发生,取反后就是不发生。它只有 1 个输入端、1 个输出端:输入为 1,输出为 0;输入为 0,输出为 1。
表达式 F = ~A,~ 是 Verilog 的"非"逻辑符。

上图用三极管实现非门:A 输入 0V 时三极管截止,Y 输出 5V;A 输入 5V 时三极管导通,Y 输出 0V——正好实现"取反"。


波形图上,Y 的波形就是 A 的上下翻转。
编写代码(这次用 4 位数据演示,一次对 4 个 bit 同时取反):
module not_gate(
input [3:0] A, //4位数据的输入信号A
output [3:0] Y //4位数据的输出信号Y
);
//assign 相当于一条连线,将A非后的值赋给Y。
//assign Y[3:0] = ~A[3:0];
assign Y = ~A;
endmodule
注意:如果输入端和输出端位数相同,可以直接省略位数的声明(如注释中的写法)。
仿真验证:
`timescale 1ns / 1ns
module tb_not_gate();
//define reg
reg [3:0] A;
//define wire
wire [3:0] Y;
//初始化
initial begin
A = 4'b0000;
#100
A = 4'b1111;
#100
A = 4'b1001;
#100
A = 4'b0110;
#100
A = 4'b0101;
#100
A = 4'b1010;
end
//例化逻辑非模块
not_gate not_gate_inst(
.A(A), //输入A
.Y(Y) //输出Y
);
endmodule

仿真结果与非门真值表一致。
上板验证:本次用了 4 位输入和 4 位输出,而板卡 LED 灯数目不够,所以不再进行上板测试。有兴趣的同学可以把输入输出改成 2 位,自己修改引脚分配做上板拓展练习。
本章总结
本章通过与门、或门、非门三个最基本的逻辑门,完整演示了一个 FPGA 设计项目的全流程。需要掌握:
- 逻辑门用法:
&(与)、|(或)、~(非); - 学会绘制和查看波形图;
- 仿真中常用的 initial 语句块。
initial 的标准形式:
initial begin
A = 0;
B = 0;
C = 0;
end
initial 语句是不可以被综合的,一般只出现在 testbench 中,而不出现在 RTL 代码里。initial 块中的语句上电后只执行一次,一般用于初始化输入信号——如果不赋初始值,仿真信号可能会产生不定态(既不是0也不是1的x态)。begin...end 相当于 C 语言里的大括号,同一个语句块给多个变量赋值时要加上它。
图片对照
本章图片已按讲解位置分散插入各小节,包括与门/或门/非门的电路图、逻辑符号、波形图、仿真波形以及上板验证的硬件图。
初学者容易踩的坑
- 忘记给 testbench 信号赋初值:initial 不初始化的话,仿真波形会出现红色不定态(x),看不到有意义的结果。
- reg 和 wire 搞混:在 initial/always 块中赋值的激励信号必须是 reg 型;被测模块输出的信号必须接 wire 型,写反了综合或仿真直接报错。
- testbench 写了端口列表:testbench 不会被别的模块例化,模块名后不要带端口。
- 上电顺序搞反:一定要先连接好下载器和 JTAG 接口,再给开发板上电,带电热插拔 JTAG 有损坏接口的风险。
- 只仿真不上板、或只上板不仿真:仿真通过只能说明逻辑正确,还要考虑板级现象(比如按键是低有效)才能解释上板现象;两者结合才是完整验证。
- 混淆"有0出0"和"有1出1":与门和或门口诀记反了,上板现象就完全对不上,建议对着真值表再推一遍。
小结 & 下一步
本章用与门、或门、非门三个最简单的电路,学会了"框图 → 真值表 → 波形图 → 代码 → 仿真 → 上板"的完整开发套路,以及 &、|、~ 三个运算符和 initial 语句的用法。建议动手做两个拓展:一是把与门和非门组合,设计一个与非门电路;二是把或门和非门组合,设计一个或非门——各走一遍波形绘制、代码编写和仿真验证,巩固本章所学。