基础概念
一、 宏观统称:EDA 工具 (Electronic Design Automation)
电子设计自动化(EDA) 是一个极其宽泛的总称,泛指所有用于设计、仿真、测试和制造电子系统(如集成电路芯片、印刷电路板 PCB、FPGA)的软件工具。
- EDA 工具就是硬件工程体系的工作台。后续所提及的 Ngspice、Logisim、Vivado 等,均属于 EDA 工具的细分范畴。
二、 电路仿真工具
在将芯片真正制造出来或烧录进硬件之前,必须在计算机上进行极为严谨的测试,这需要使用仿真工具。根据关注的抽象层级不同,仿真工具主要分为两大类:
1. 模拟与物理层仿真 (关注电压、电流、时间等底层物理量)
-
SPICE (Simulation Program with Integrated Circuit Emphasis):
-
一种工业标准的电路模拟软件规范和算法核心。
-
作用: 工作在最底层的“模拟”层面,专门用于计算并模拟电路中的电阻、电容、二极管和晶体管等物理元件的电压、电流随时间的变化特性。
-
-
Ngspice:
-
SPICE 算法目前最广泛使用的一个开源软件实现版本。
-
作用: SPICE 是理论标准,Ngspice 是实际执行运算的程序。它支持混合信号仿真,是许多现代开源 EDA 流程底层的核心计算引擎。
-
2. 数字与门级仿真 (关注抽象的逻辑 0 和 1)
-
Logisim-evolution:
-
一款带有直观图形界面的数字逻辑电路教育和仿真工具(原版 Logisim 的增强升级版)。
-
作用: 剥离了底层具体的物理电压考量,纯粹用于分析逻辑元件(如与门、或门、非门、触发器等)。使用者可通过鼠标拖拽元件并连线,直观搭建出简易计算器等数字系统,是计算机底层原理教学验证的主要工具。
-
三、 硬件描述语言 (HDL:Hardware Description Language)
当设计包含数百万个逻辑门的大型芯片时,传统的鼠标拖拽连线方式难以清晰描述复杂的庞大逻辑。因此,需要代码来描述电路的结构与行为,即 HDL。
目前工业界最主流的两大硬件描述语言是 VHDL 和 Verilog,对比概览如下:
| 特性对比 | VHDL (VHSIC Hardware Description Language) | Verilog |
|---|---|---|
| 设计风格 | 语法极其严谨、繁琐,属于强类型语言(设计思想类似 Ada 或 Pascal)。 | 语法相对简洁灵活,结构和操作符非常接近 C 语言。 |
| 优缺点 | 优点:极其严密,不易犯错。 缺点:代码量大,学习曲线陡峭。 |
优点:易学易写,开发效率高。 缺点:由于过于灵活,有时容易隐藏逻辑错误。 |
| 应用领域 | 在欧洲企业、航空航天、国防军工等对安全性要求极高的领域使用较多。 | 北美企业、国内商业半导体公司以及主流互联网大厂的芯片研发部门绝大多数使用 Verilog。 |
注: 本阶段实验主要采用 Verilog 作为开发语言。
四、 FPGA 与 FPGA 设计软件
1. FPGA 基本概念
- FPGA (Field Programmable Gate Array,现场可编程逻辑门阵列) 是一种出厂时内部电路未完全固定的芯片。工程师可以通过后期编程,自由改变其内部的硬件连线和逻辑功能,从而实现高度定制化的硬件系统。
2. FPGA 设计软件的核心功能流
FPGA 设计软件是用于完成“硬件编程”工作的核心 EDA 工具,等同于硬件工程领域的集成开发环境(IDE)与编译器。其标准工作流包含以下关键步骤:
-
设计输入 (Design Entry): 接收使用硬件描述语言(如 Verilog、VHDL)编写的代码或图形化连线输入的电路设计。
-
综合 (Synthesis): 类似软件开发的“编译”过程。将高级 HDL 代码翻译、优化为 FPGA 底层能理解的 “网表(Netlist)” —— 即基本的逻辑门(如查找表 LUT、与非门)和寄存器的物理连接图。
-
实现 (Implementation): 包含 布局(Placement) 和 布线(Routing) 两大步骤。软件通过复杂算法,决定逻辑门在芯片物理硅片版图上的具体摆放位置,并规划好它们之间的导线走向,同时必须确保所有信号传输的时序严格达标。
-
仿真 (Simulation): 在生成物理文件前,在计算机内存中模拟电路的输入输出波形,以排查逻辑漏洞。
-
生成比特流 (Bitstream Generation): 流程的最终步,生成包含全套硬件配置信息的二进制文件(通常为
.bit格式),用于下载并“固化”到真实的 FPGA 开发板中。
3. FPGA 平台:Vivado Design Suite
1. 基本简介
Vivado Design Suite 是由 FPGA 行业巨头 Xilinx(现已被 AMD 收购) 推出的一款极其强大的工业级 FPGA 设计自动化软件。它是目前全球使用最广泛的 FPGA 开发工具之一,专门针对 Xilinx 旗下的现代先进架构(如 7 系列、UltraScale、Versal)进行开发与深度优化。
- 全流程高度集成: 作为一个综合性的软件,Vivado 内部无缝集成了代码编辑、逻辑综合、物理实现、波形仿真以及最终的下板硬件调试等全套开发流程。
Lab0-1
实验1: 观察反相器(非门)的电压传输特性
-
对应电压的二进制表示相关的内容。(回顾 Threshold Region, Noise Margin 等相关概念)
-
使用 Ngspice,关注具体的电压。
实验2: Logisim 电路仿真
-
使用 Logisim,不再关注实际的电压,只关注逻辑(0,1)。
-
电路图的实际逻辑:
\[ O = (\overline{I0} \cdot \overline{I1} \cdot I2) + (\overline{I0} \cdot I1 \cdot \overline{I2}) + (I0 \cdot \overline{I1} \cdot \overline{I2}) + (I0 \cdot I1 \cdot I2) \]即
\[ O = I0 \oplus I1 \oplus I2 \] -
电路功能:
-
当输入端口
I0,I1,I2中1的总数为奇数(1 个或 3 个)时,最终输出O为1。 -
当输入端口中
1的总数为偶数(0 个或 2 个)时,最终输出O为0。
-
-
回顾奇校验器、偶校验器的功能和实现。(Parity Generators and Checkers)
Lab0-2
实验1:Verilator 仿真测试
Verilator 是一款高性能的开源 RTL(寄存器传输级)仿真器。(不考虑物理电路特性,如物理延时等,只关注时钟信号来临时,数据如何在寄存器之间移动和处理)
-
核心工作机制: 与传统的解释型仿真器不同,Verilator 会将硬件设计代码(Verilog/SystemVerilog)“翻译”并转化为可由主机执行的 C++ 模型,然后通过编译、运行该 C++ 程序来进行电路的模拟运行。
-
通过执行
make命令,系统会自动调用 Verilator 将 Verilog 转换为 C++ 并执行仿真生成波形数据;随后通过make wave命令启动波形查看工具(GTKWave)。
实验2:Vivado 使用
- 设置 top 模块是因为综合操作仅针对顶层模块及其内部子模块生效。
Verilog 与电路
1. 模块定义与端口声明 (module, input, output)
一个 Verilog 模块(Module)对应物理电路中的一个功能封装块(或芯片)。
-
基本结构:以
module 模块名(端口列表);开始,以endmodule结束。 -
引脚定义:
-
input:定义输入引脚。 -
output:定义输出引脚。
-
-
声明方式:推荐在模块声明的括号内直接完成端口类型的定义(ANSI 风格),代码更加简洁明了。
-
注:与 C 语言类似,Verilog 中的回车、换行、空格和缩进没有实际语法意义,仅用于提高代码可读性。
2. 线路声明 (wire)
wire 语句负责在模块内部定义一条物理导线,并为其命名。
-
在原理图(如 Logisim)中绘制的每一条连接线,在代码中都对应一个
wire声明。 -
整个相连的线网络(同一节点的连线)拥有相同的电平,属于同一个
wire的范围。
3. 线路连接 (assign) 与电气特性
assign 语句的作用是将定义的 wire 物理连接起来。
3.1 端口与连线的关系
-
input端口:特殊的 wire。输入端预留给模块外部连接,输出端在模块内部使用(即内部逻辑只能读取 input 的值,不能给它赋值)。 -
output端口:特殊的 wire。输出端预留给模块外部,输入端在模块内部使用(即内部逻辑必须给它赋值)。注意:output 端口在模块内部既可以被赋值,也可以作为信号源去驱动其他连线。 -
常量赋值:可以向线路中输入固定的电平。
-
assign wire3 = 1'b1;相当于将导线连接到 Vcc (高电平)。 -
assign wire4 = 1'b0;相当于将导线连接到 GND (低电平)。
-
3.2 常见错误连接示例
理解以下错误对于避免电路跑飞至关重要:
-
assign I0 = wire1;:不能将内部信号连接到input端口。 -
assign wire2 = wire3; assign wire3 = wire2;:形成相互驱动的环形电路 (组合逻辑环)。没有有效外部输入,会触发 warning,物理实现上存在极大隐患。 -
assign wire4 = wire5; assign wire4 = wire6;:多重驱动。一条线被两个信号源驱动,物理上会导致短路。 -
assign 1'b0 = wire8;:不能向 Vcc 或 GND 赋值,会导致短路。 -
assign wire9 = wire8;(其中 wire8 未被赋值):未被驱动的线处于高阻态 (Z)。用高阻态参与其他电路计算极其危险,类似于 C 语言中使用未初始化的变量。
3.3 wire 的电气特性规则
-
输入唯一性:任何一个
wire必须有且仅有一个assign输入(即驱动源)。 -
输出无限性:一个
wire可以有 0 到多个assign输出(即扇出,去驱动其他线路)。 -
关于
input:内部不能有输入(防止多重驱动);可以有 0 到多个输出(未连接无害)。 -
关于
output:内部必须有且仅有一个输入(否则外部无输出);可以有 0 到多个输出。
4. 逻辑门与运算符映射
Verilog 提供了类似 C 语言逻辑运算符的语法糖,用于快速描述基础门电路:
-
非门 (NOT):对应
~运算符。assign wire1 = ~wire2;等同于实例化一个非门。 -
与门 (AND):对应
&运算符。assign wire3 = wire1 & wire2;。 -
或门 (OR):对应
|运算符。assign wire3 = wire1 | wire2;。
5. 模块调用 (实例化 Instantiation)
5.1 显式声明端口连接
通过 .子模块端口名(当前模块线名) 的方式进行精准连接。
AND_GATE_3_INPUTS GATES_1 (
.input1(s_logisimNet2),
.input2(s_logisimNet4),
.input3(s_logisimNet3),
.result(s_logisimNet10)
);
连接注意事项:
-
子模块的每一个
input端口都必须被连接,不能悬空。 -
一旦当前模块的一根
wire被子模块的output端口驱动了,这根wire就不能再被其他模块或assign驱动(遵循单驱动原则)。 -
子模块的
output端口如果不需要使用,必须显式声明为空,例如.result(),以表明是故意悬空,否则可能会被认为是遗漏。
5.2 隐式顺序连接
不写端口名,按照子模块定义时的端口顺序,依次填入当前模块的连线。
AND_GATE_3_INPUTS GATE1 (s_logisimNet10, s_logisimNet6, s_logisimNet7, ...);
隐式连接的致命缺点:
-
不能与显式连接混用。
-
端口较多时,极其容易连错顺序。
-
一旦子模块修改了端口顺序,所有调用该模块的地方都必须重构。
-
无法优雅地处理输出端口悬空的情况。
-
缺乏语义,代码可读性和可维护性极差。
Lab0-3
1. 核心逻辑模块 main.v
作用: 不关注外设是什么(不关心是开关还是按键),只关注逻辑本身的输入输出。
2. 顶层封装模块 top.v
作用: 在稍微复杂的工程中,通常会有一个 Top 模块将各种子模块组装在一起。
编写逻辑:
-
定义面向硬件的引脚:
input SW0~SW3代表板子上的四个拨码开关,output LD0代表一个 LED 灯。 -
模块实例化:
-
显式端口连接:
3. 编写物理引脚约束 nexysa7.xdc
作用: FPGA 芯片内部的电路可自定义,但它的引脚是固定焊死在板子上的。XDC 文件(Xilinx Design Constraints)的作用就是向编译器指定,在 top.v 中定义的端口,具体对应 FPGA 芯片上的哪一个物理引脚。
编写逻辑:
-
set_property PACKAGE_PIN {J15} [get_ports {SW0}]:把物理引脚J15(根据开发板手册,它连接着最右边的拨码开关)和top.v中的变量SW0绑定。 -
IOSTANDARD {LVCMOS33}:定义电气特性,表示使用 3.3V 的 CMOS 电平标准(高电平为 3.3V,低电平为 0V)。
至此,可以通过 Vivado 进行综合(Synthesis)、实现(Implementation)并生成比特流(Bitstream)下载到板子上。
4. 仿真激励文件 testbench.v
注意:Testbench 是不能综合成电路烧录到板子上的,它只在电脑软件里运行。
编写逻辑:
-
闭环无端口模块:
module Testbench;没有输入输出,不需要外界提供信号。 -
产生激励信号 (
initial块): -
实例化被测模块 (DUT - Device Under Test):
main dut( ... );把要测试的核心逻辑放进虚拟实验台,接上刚才定义的虚拟信号源。 -
波形记录:
ifdef VERILATE ... `$dumpfile` ...这是 Verilator 仿真器特有的指令,意思是“准备一个录像机,把仿真过程中所有连线上电平的高低变化录制下来,存进 .vcd 文件中”,方便后续查看波形图。
标准的工程开发顺序:
-
分析需求,根据逻辑写出
main.v。 -
编写
testbench.v对main.v进行仿真。如果波形结果不对,修改main.v直到正确。 -
仿真通过后,编写
top.v准备上板。 -
编写
nexysa7.xdc绑定引脚。 -
使用 Vivado 综合、布线、生成 Bit 流文件,下载到 FPGA 开发板,拨动物理开关验证。
More
文件关系
-
真正的芯片设计是由几十上百个
.v文件组成的(比如alu.v负责计算,decoder.v负责译码,uart.v负责通信) -
top.v是整个工程的顶层文件。它就像 C 语言里的int main()入口函数,它的作用通常就是把所有的底层子模块组装起来,并把最终需要的接口引出到 FPGA 的物理引脚上。 -
实际上在逻辑非常简单,如 lab0-3 中只需要一个逻辑模块时,
top.v是不必须的。在 Vivado 等开发工具中,右键点击main.v,选择 "Set as Top" 设为顶层。把约束文件写成:set_property PACKAGE_PIN {J15} [get_ports {I0}],这样直接就把 J15 绑到了main.v的I0端口上,完全抛弃了top.v。
概念辨析
-
开关 (Switch / 外设层): 这是物理世界的元器件。命名规范通常由开发板厂商决定,印在板子上,如
SW0、SW1、BTN_U(上按键)、LED0等。 -
引脚 (Pin / 芯片硬件层): 这是 FPGA 芯片底部焊盘上的金属触点。命名规范由 FPGA 芯片原厂(如 Xilinx)决定,通常是“字母+数字”的坐标系结构,如
J15、H17、V10。 -
端口 (Port / 软件代码层): 这是在 Verilog
module中使用input和output定义的代码变量。-
底层模块命名规范: 通常根据逻辑功能命名,如
clk(时钟),rst_n(复位),data_in,valid_flag。 -
顶层模块命名规范: 为了可读性,顶层模块的端口通常沿用板子外设的名字,例如
input SW0,output LD0,这样写约束文件时一眼就能看出连到了哪里。- 也就是说我们仅仅为了可读性把端口名命名为开关的名字。厂商指定的实际是引脚与开关、LED 灯等外设的对应关系。我们只要把端口和指定的引脚对应好,就自然对应到了相应的外设。比如把顶层模块的输入定义为
apple,只要在.xdc文件里写上set_property PACKAGE_PIN {J15} [get_ports {apple}],编译器就会把硬件上的 J15 物理引脚和 apple 端口连接,从而映射到了 SW0 这个物理开关。
- 也就是说我们仅仅为了可读性把端口名命名为开关的名字。厂商指定的实际是引脚与开关、LED 灯等外设的对应关系。我们只要把端口和指定的引脚对应好,就自然对应到了相应的外设。比如把顶层模块的输入定义为
-