简介:这份PPT课件面向电子设计与EDA学习者,聚焦系统仿真这一关键环节,帮助读者在硬件制造之前借助计算机模型验证和调试电路设计,降低试错成本。内容围绕仿真概念、VHDL源程序仿真流程与仿真激励信号生成展开,配有AND门、三态缓冲器、四位加法器等典型示例,并演示用VHDL编写波形信号发生器与顶层测试平台的方法,适合作为课堂讲义或自学参考。资源包共1个文件,为PPT格式,整体约654KB,篇幅紧凑、结构清晰,便于按章节检索知识点。目前已有169人学习,可作为理解仿真流程与激励设计的入门材料,帮助读者掌握从源码编译、设计库管理到波形分析的完整思路,并借助示例代码体会行为描述与测试激励的编写要点。
1. 从一份课件说起:系统仿真到底在仿什么
打开这份《系统仿真.ppt》,第一页就把话说得很直白:仿真(Simulation)是对电路设计的一种间接检测方法,用计算机把整个硬件系统的行为跑一遍,全程不碰真实芯片。这句话看着朴素,落到工程里其实解决了一个很现实的问题——流片和打样都贵,逻辑写错了如果等到板子回来才发现,返工成本可能是几个数量级。课件里给的 VHDL 例子(与门、三态缓冲器、4 位加法器、8 位计数器)都不复杂,但它串起来的是一条完整的验证链路:源程序进设计库、编译、仿真、出波形和数据。适合谁?正在学 EDA 课程、要交 VHDL 仿真作业的学生,以及刚转到 FPGA/CPLD 方向、需要把“功能仿真”和“时序仿真”分清楚的初级工程师。这份 PPT 的价值不在深度,而在于它把仿真流程、激励产生、测试基准三件事压缩在几十页里,拿来当复现清单刚好。
2. VHDL 源程序仿真:从实体到波形的完整链路
课件 12.2 节给了一张 VHDL 仿真流程图,路径是:VHDL 文本或图形输入 → 翻译成文本 → 编译进设计库 → 仿真 → 输出数据和波形。这条链路和 FPGA/CPLD 的设计流程是咬合的:设计准备与系统划分之后先做行为仿真,综合成门级网表后做功能仿真,布局布线拿到延时信息后再做时序仿真。很多人第一次翻车就翻在这里——把行为仿真当成了最终结论,结果上板时序不收敛。
2.1 实体与架构:两个最小可仿真单元
课件例 12-1 是一个与门,实体声明端口,架构写行为,这是 VHDL 最基本的组织方式。别小看这两段,仿真器认的就是“实体定接口、架构定行为”这个约定。
LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY and1 IS PORT( aaa, bbb : IN STD_LOGIC; ccc : OUT STD_LOGIC ); END and1; ARCHITECTURE one OF and1 IS BEGIN ccc <= aaa AND bbb; -- 组合逻辑,无时钟,仿真时输入一变输出立即跟随 END one;逻辑说明:STD_LOGIC是九值逻辑,除了 0/1 还有 X(不定)、Z(高阻)、U(未初始化)等,仿真时波形上出现 X 往往就是初始化或驱动冲突。参数说明:端口方向IN/OUT必须写全,实体名和文件名建议一致,否则部分工具在编译进设计库时会报找不到单元。这个例子适合做第一次“编译—仿真—看波形”的闭环,输入 aaa、bbb 手动翻转,确认 ccc 的波形与真值表一致,链路就算通了。
2.2 三态缓冲器:进程与 TRANSPORT 延时
例 12-2 的 TRIBUF_and1 比与门进了一步,用 PROCESS 对 oe 的边沿敏感,并用 GENERIC 定义了三个延时参数。这段代码是课件里信息密度最高的部分,值得逐行拆。
LIBRARY IEEE; USE IEEE.std_logic_1164.all; ENTITY TRIBUF_and1 IS GENERIC ( ttri : TIME := 1 ns; -- 输出跟随输入的传输延时 ttxz : TIME := 1 ns; -- 使能关闭到高阻的延时 ttzx : TIME := 1 ns -- 高阻到使能输出的延时 ); PORT ( in1 : IN std_logic; oe : IN std_logic; y : OUT std_logic ); END TRIBUF_and1; ARCHITECTURE behavior OF TRIBUF_and1 IS BEGIN PROCESS (in1, oe) BEGIN IF oe'EVENT THEN -- 只在使能信号变化时判断方向 IF oe = '0' THEN y <= TRANSPORT 'Z' AFTER ttxz; ELSIF oe = '1' THEN y <= TRANSPORT in1 AFTER ttzx; END IF; ELSIF oe = '1' THEN -- 使能有效期间,输入变化直接透传 y <= TRANSPORT in1 AFTER ttri; ELSIF oe = '0' THEN y <= TRANSPORT 'Z' AFTER ttxz; END IF; END PROCESS; END behavior;逻辑说明:oe'EVENT判断的是使能信号是否发生了跳变,跳变时按新电平决定输出是高阻还是透传;没有跳变但使能有效时,输入 in1 的变化要按 ttri 延时传到 y。参数说明:三个 GENERIC 默认都是 1 ns,仿真时可以把它们改大(比如 5 ns、10 ns),在波形上就能肉眼分辨出“先关断再导通”的时序关系,这是理解三态总线竞争最直观的办法。注意TRANSPORT关键字——它表示延时是“传输延时”,脉冲不会被惯性模型吞掉,做总线仿真时用它比默认的惯性延时更贴近真实行为。
2.3 编译进设计库与仿真结果判读
课件流程图里“编译设计库”这一步,实操中对应的是把源文件编译到某个库(常见做法是 work 库),然后由仿真器从库里调单元。我一般会按这个顺序走:新建工程 → 添加 VHDL 源文件 → 指定顶层实体 → 编译 → 打开波形窗口 → 手动施加激励或挂测试基准 → 运行固定时长 → 看波形。判读波形时重点看三样:输出是否出现不该有的 X、三态总线是否在同一时刻有两个驱动源、延时参数改动后波形边沿是否按预期平移。如果编译报“entity not found”,九成是实体名和文件名不一致或者库没选对;如果波形全是 U,说明仿真时间没跑或者复位没给。这一步没有玄学,就是按流程核对。
3. 仿真激励信号的产生:两种方法怎么选
课件 12.3 节把激励产生分成两条路:一是用仿真器的波形设置命令直接 force 信号,二是用 VHDL 写一个波形信号发生器。两条路没有优劣,只有场景之分——临时看几个点用命令快,要反复回归、要交给别人复现的,必须写成代码。
3.1 用 force 命令快速施加激励
课件给的命令格式是force <信号名> <值> [<时间>][, <值> <时间> …] [-repeat <周期>],配合run推进仿真时间。这套命令在多数 VHDL 仿真器的交互界面里都能用,适合调试初期快速试波形。
# 强制信号 a 当前值为 0 force a 0 # b 在 0 时刻为 0,10 时刻变 1 force b 0 0, 1 10 # clk 为周期信号,周期 20(0 时刻 0,15 时刻 1,20 时刻回到 0) force clk 0 0, 1 15 -repeat 20 # 对应课件例 12-3 的加法器输入:a 依次 10、5、8,b 依次 3、4、6 force a 10 0, 5 200, 8 400 force b 3 0, 4 100, 6 300 run 500逻辑说明:每条 force 后面的“值 时间”对表示在该时刻把信号置为该值,-repeat让波形周期性重复,run 500把仿真推进 500 个时间单位。参数说明:时间单位取决于仿真器设置(常见是 ns),force 的值必须落在信号类型的合法范围内——例 12-3 的端口是INTEGER RANGE 0 TO 15,你 force 一个 20 进去,仿真器要么报错要么给出不确定结果。这套命令的短板是不留痕,关掉会话就没了,所以只适合临时验证。
3.2 用 VHDL 写波形发生器
课件例 12-4 的 SIGGEN 就是一个纯激励实体,没有输入,只有两个输出,靠并行赋值语句按时间点产生波形。
ENTITY SIGGEN IS PORT ( sig1 : OUT INTEGER RANGE 0 TO 15; sig2 : OUT INTEGER RANGE 0 TO 15 ); END; ARCHITECTURE Sim OF SIGGEN IS BEGIN -- 0 时刻 10,200 ns 变 5,400 ns 变 8 sig1 <= 10, 5 AFTER 200 ns, 8 AFTER 400 ns; -- 0 时刻 3,100 ns 变 4,300 ns 变 6 sig2 <= 3, 4 AFTER 100 ns, 6 AFTER 300 ns; END;逻辑说明:这种写法叫波形赋值,逗号分隔的多个“值 AFTER 时间”会在对应时刻依次驱动信号,适合产生确定性的分段激励。参数说明:时间单位写在字面量里(ns),信号范围必须和被测实体端口一致,否则端口映射时会报类型不匹配。和 force 命令相比,它的好处是可版本管理、可复用、可交给测试基准调用,缺点是改一次波形就要重新编译,迭代速度不如命令行。
3.3 顶层测试基准:把激励和被测件接起来
例 12-5 的 BENCH 是课件里第一个完整的测试基准雏形:用 COMPONENT 声明被测件和激励源,用 SIGNAL 做内部连线,用 PORT MAP 把两边接起来。
ENTITY BENCH IS END ENTITY BENCH; ARCHITECTURE one OF BENCH IS COMPONENT ADDER4 PORT ( a, b : IN INTEGER RANGE 0 TO 15; c : OUT INTEGER RANGE 0 TO 15 ); END COMPONENT; COMPONENT SIGGEN PORT ( sig1 : OUT INTEGER RANGE 0 TO 15; sig2 : OUT INTEGER RANGE 0 TO 15 ); END COMPONENT; SIGNAL a, b, c : INTEGER RANGE 0 TO 15; BEGIN U1 : ADDER4 PORT MAP (a, b, c); -- 被测件 U2 : SIGGEN PORT MAP (sig1 => a, sig2 => b); -- 激励源 END;逻辑说明:测试基准本身没有端口,所有连接都通过内部 SIGNAL 完成,仿真结果从波形窗口观察 c 的变化即可。参数说明:PORT MAP支持位置映射和命名映射两种写法,命名映射(sig1 => a)在端口多的时候更不容易接错,建议养成习惯。这里有个容易忽略的点——ADDER4 的端口是INTEGER RANGE 0 TO 15,两个 15 相加会溢出到 30,超出范围后仿真结果不可信,课件没展开讲,但实际做加法器仿真时必须把输出范围放宽或者做饱和处理。
4. VHDL 测试基准:把验证写成可复现的代码
课件 12.4 节把测试基准(Test Bench)定义成“用来测试一个 VHDL 实体的程序”,核心动作是元件例化、加激励、观察内部信号。例 12-6 的 8 位计数器带 CLK、CE、LOAD、DIR、RESET 五个控制端,例 12-7 的 testbench 则演示了更工程化的做法:用文件记录仿真结果。这一节是整份课件里最接近真实验证流程的部分。
4.1 被测计数器:时钟、复位与方向控制
先把例 12-6 的计数器逻辑理清楚,后面写测试基准才知道该测什么。
LIBRARY IEEE; USE IEEE.std_logic_1164.all; ENTITY counter8 IS PORT ( CLK, CE, LOAD, DIR, RESET : IN STD_LOGIC; DIN : IN INTEGER RANGE 0 TO 255; COUNT : OUT INTEGER RANGE 0 TO 255 ); END counter8; ARCHITECTURE counter8_arch OF counter8 IS BEGIN PROCESS (CLK, RESET) VARIABLE COUNTER : INTEGER RANGE 0 TO 255; BEGIN IF RESET = '1' THEN COUNTER := 0; -- 异步复位,优先级最高 ELSIF CLK = '1' AND CLK'EVENT THEN -- 上升沿 IF LOAD = '1' THEN COUNTER := DIN; -- 同步装载 ELSE IF CE = '1' THEN -- 计数使能 IF DIR = '1' THEN IF COUNTER = 255 THEN COUNTER := 0; ELSE COUNTER := COUNTER + 1; END IF; ELSE IF COUNTER = 0 THEN COUNTER := 255; ELSE COUNTER := COUNTER - 1; END IF; END IF; END IF; END IF; END IF; COUNT <= COUNTER; END PROCESS; END counter8_arch;逻辑说明:复位是异步的(在敏感表里),装载和计数是同步的(只在上升沿动作),DIR 控制加/减,到边界自动回绕。参数说明:VARIABLE在进程内保存状态,COUNT <= COUNTER把变量值送到输出端口。这段代码有个典型写法问题——CLK = '1' AND CLK'EVENT是上升沿的经典判断,现代写法更推荐rising_edge(CLK),两者在仿真里行为基本一致,但后者对 X 态的处理更严谨。测试时要覆盖的场景至少四个:复位清零、装载初值、加计数到 255 回绕、减计数到 0 回绕。
4.2 测试基准里的文件输出:把波形变成可 diff 的文本
例 12-7 的 testbench 引入了TEXTIO,把每个时刻的信号值写进 results.txt。这个做法在批量回归时特别有用——波形图靠眼睛看,文本可以脚本比对。
LIBRARY IEEE; USE IEEE.std_logic_1164.all; USE STD.TEXTIO.all; ENTITY testbench IS END ENTITY testbench; ARCHITECTURE testbench_arch OF testbench IS FILE RESULTS : TEXT open WRITE_MODE is "results.txt"; COMPONENT counter8 PORT ( CLK, RESET, CE, LOAD, DIR : IN STD_LOGIC; DIN : IN INTEGER RANGE 0 TO 255; COUNT : OUT INTEGER RANGE 0 TO 255 ); END COMPONENT; SIGNAL CLK, RESET, CE, LOAD, DIR : STD_LOGIC := '0'; SIGNAL DIN : INTEGER RANGE 0 TO 255 := 0; SIGNAL COUNT : INTEGER RANGE 0 TO 255; PROCEDURE WRITE_RESULTS ( CLK, CE, LOAD, RESET : STD_LOGIC; DIN, COUNT : INTEGER ) IS VARIABLE V_OUT : LINE; BEGIN write(V_OUT, now, right, 16, ps); -- 当前仿真时间 write(V_OUT, CLK, right, 2); write(V_OUT, RESET, right, 2); write(V_OUT, CE, right, 2); write(V_OUT, LOAD, right, 2); write(V_OUT, DIR, right, 2); write(V_OUT, DIN, right, 257); write(V_OUT, COUNT, right, 257); writeline(RESULTS, V_OUT); END WRITE_RESULTS; BEGIN UUT : counter8 PORT MAP (CLK => CLK, RESET => RESET, CE => CE, LOAD => LOAD, DIR => DIR, DIN => DIN, COUNT => COUNT); -- 时钟生成:周期 20 ns CLK <= NOT CLK AFTER 10 ns; -- 激励序列 PROCESS BEGIN RESET <= '1'; wait for 25 ns; RESET <= '0'; LOAD <= '1'; DIN <= 100; wait for 20 ns; LOAD <= '0'; CE <= '1'; DIR <= '1'; wait for 200 ns; DIR <= '0'; wait for 200 ns; wait; END PROCESS; -- 每个时钟上升沿记录一次 PROCESS (CLK) BEGIN IF rising_edge(CLK) THEN WRITE_RESULTS(CLK, CE, LOAD, RESET, DIN, COUNT); END IF; END PROCESS; END testbench_arch;逻辑说明:时钟用NOT CLK AFTER 10 ns自激产生,激励进程按时间片给复位、装载、加减计数;记录进程挂在时钟上升沿,每个沿写一行文本。参数说明:now返回当前仿真时间,right, 16, ps表示右对齐、宽度 16、单位 ps;LINE是 TEXTIO 的行缓冲类型,写完必须writeline才会真正落盘。注意课件原例里 WRITE_RESULTS 的参数列表把 LOAD 写了两遍、漏了 DIR,这是排版笔误,照抄会编译不过,上面这版已经修正。跑完之后 results.txt 里就是一张时间、控制信号、计数值的表,用 diff 对比两次修改前后的输出,比盯波形靠谱得多。
5. 避坑与排查:仿真链路上最容易翻车的五处
5.1 波形全是 U 或 X
现象:仿真跑完,输出端口从头到尾是 U,或者中途突然变 X。原因:U 通常是信号没初始化、复位没生效或仿真时间没推进;X 多半是同一信号被两个驱动源同时驱动,或者整数端口越界。解决:先确认测试基准里给了复位序列并且run了足够时长;再检查三态总线是否在同一时刻有两个使能;整数端口越界的情况把范围放宽后重跑,波形会立刻恢复正常。
5.2 实体编译不进设计库
现象:编译报“entity not found”或“design unit not found”。原因:实体名与文件名不一致、库没指定、或者源文件没加入工程。解决:VHDL 工具普遍要求文件名与顶层实体名一致(大小写视工具而定),先把命名对齐;再确认编译目标库是 work 还是自定义库,测试基准引用被测件时库名要写对。
5.3 force 命令的值超出端口范围
现象:force 之后波形出现异常值或仿真器直接报错。原因:例 12-3 这类端口是INTEGER RANGE 0 TO 15,force 一个 20 进去就违约了。解决:force 之前先看端口声明的范围,激励值必须落在区间内;如果确实需要更大范围,改被测件的端口声明而不是硬塞。
5.4 测试基准端口映射接反
现象:仿真能跑,但计数值不动或者方向反了。原因:PORT MAP用了位置映射,端口顺序和组件声明不一致。解决:统一改成命名映射(CLK => CLK这种),接错一眼就能看出来;位置映射只在端口极少且顺序确定时用。
5.5 把行为仿真结果当成上板结论
现象:仿真波形完美,综合布局布线后上板行为不对。原因:行为仿真不含门延时和布线延时,课件流程图里行为仿真、功能仿真、时序仿真三个阶段是分开的。解决:综合后跑一次门级功能仿真,布局布线后再跑时序仿真,三个结果都过再谈上板;只跑行为仿真就下结论,是这门课里最常见的血泪经验。
6. 进阶用法:把课件例子改造成可回归的验证小框架
课件给的是单点例子,真正干活时我一般把它们拼成一个小框架:一个激励发生器实体、一个被测件、一个带文件输出的测试基准,三层分离。具体做法是把例 12-4 的 SIGGEN 扩展成带参数的激励源,用 GENERIC 把时间点做成可配置,这样同一套测试基准换个参数就能测不同场景。
| 层次 | 对应课件例子 | 改造要点 |
|---|---|---|
| 激励层 | 例 12-4 SIGGEN | 时间点抽成 GENERIC,支持多组激励切换 |
| 被测层 | 例 12-1/12-2/12-6 | 端口范围留余量,避免溢出 |
| 观测层 | 例 12-7 testbench | 文件输出加时间戳,便于 diff |
验证方法上,我习惯做两轮对比:第一轮只改被测件逻辑、激励不变,看 results.txt 的差异是否只出现在预期的时间点;第二轮只改激励、被测件不变,确认输出随激励线性变化。两轮都干净,才认为这次修改没有引入回归。这个习惯来自一次教训——当时只看了波形图觉得没问题,结果换了个仿真器重新跑,文件输出里多出一行 X,追下去发现是整数端口在边界回绕时越界,波形上因为缩放比例根本没看出来。从那以后我每次改完 VHDL 都强制走一遍“编译—行为仿真—文件 diff”三件套,波形只用来定位、不用来下结论。希望帮到你。
本文还有配套的精品资源,点击获取