☰
FPGA层次化设计入门:VHDL逻辑门例化与ISE综合仿真
2026/10/28 3:35:38 网站建设 项目流程

简介:这份面向西安电子科技大学软件工程专业院选课程学习者的FPGA实验报告文档,聚焦数字系统设计入门,围绕简单逻辑门的RTL描述、结构化描述与VHDL层次化设计展开,并依托Xilinx ISE完成工程创建、语法检查、原理图生成和仿真验证。文档共1个docx文件,压缩包约267KB,内容紧凑,涵盖实验目的、原理、实现步骤、测试结果与总结,还涉及MY_AND2、MY_OR2及顶层AND_OR模块的搭建思路,可帮助初学者理解底层门电路到顶层模块的调用关系、信号连接方式与VHDL基本编程方法。已有345人学习,适合作为FPGA开发入门实验的参考范例,也便于对照复盘ISE HDL编辑器与RTL描述流程。读者可据此快速把握实验报告的组织框架、关键截图位置和结果分析写法,为同类数字系统设计实验提供可借鉴的模板。

1. 从两个逻辑门到一个层次化工程:这份报告解决的是「拼不起来」的问题

第一次在 ISE 里做 FPGA 实验,真正让人卡住的往往不是「与门怎么写」,而是三个文件怎么拼成一个能综合、能出层次原理图的工程。这份某高校软件工程专业选修课的《第 1 次 FPGA 实验报告》,核心只做一件事:用 VHDL 分别写 MY_AND2、MY_OR2 两个逻辑门的 RTL 描述,再在顶层模块 AND_OR 里用结构化描述把它们例化、连线、映射端口,最后在 ISE 集成环境里走完建工程、查语法、综合、出 RTL 原理图这一整条链路。它适合刚接触 fpga开发、手上有块教学板却不知道从哪下手的人,也适合想把「层次化设计」这件事一次讲透的助教和自学者。报告本身篇幅不长,但背后那套「底层实体 + 顶层例化」的组织方式,是后面所有稍大一点工程的起点,层次化工程这个动作练不熟,后面做计数器、状态机、分频器都会在同一处反复出问题。

2. 层次化工程的骨架:RTL、结构化与 port map 三件事

2.1 三种描述风格在这个实验里的分工

VHDL 里同一个电路可以写出完全不同的三段代码,新手最容易犯的错是把它们当成互斥的三种写法去背,结果一上机就不知道哪一层该用哪种。实际工程里它们是分层使用的工具,各管一段。

数据流(RTL)描述指的是用并行信号赋值把输入输出的逻辑关系直接写出来,比如Y <= A and B;。它不关心综合成几个门、几级 LUT,只描述「Y 等于 A 与 B」这个事实。结构化描述指的是把已经定义好的元件当成积木,用 component 声明加 port map 把它们连起来,代码里看不到任何逻辑运算符。行为描述指的是 process 里那一套顺序语句,用 if、case、wait 描述「在什么条件下输出什么」,更接近软件思维。

放到这个实验里,分工就非常清晰:两个底层实体 MY_AND2 和 MY_OR2 用 RTL 描述,因为它们各自只有一个逻辑运算,一句话就能写完;顶层 AND_OR 用结构化描述,因为它的职责不是「算」,而是「连」。这个分工不是形式主义,它直接决定了综合出来的网表长什么样。

描述风格典型语句本实验用在哪一层综合后的样子单独可仿真
数据流 / RTLY <= A and B;MY_AND2、MY_OR2一个 LUT 或一个门可以
结构化component+port mapAND_OR 顶层保留层次,子模块不被拍平可以
行为process+if/case本实验不用,后续时序逻辑用视写法可能生成寄存器可以

关键在第三列和第四列。假设偷懒,把顶层也写成一句oY <= (iA and iB) or iC;,功能上完全等价,仿真波形一模一样,但综合器会把它整体优化成两三个 LUT 的组合,View RTL Schematic 打开后顶层只有一个模块,实验要求里的「AND_OR 的底层电路」那张图就彻底没了。这是很多人第一次做这个实验时最典型的翻车现场:代码没错、波形也对,就是出不来报告要的图。结构化描述的价值就在这里——它让综合器知道「这三个东西是独立元件,请保留层次」,也让每个子模块可以脱离顶层单独仿真。

2.2 底层实体:MY_AND2 的接口先定死

层次化设计的第一步不是写逻辑,是定接口。底层元件的端口名、方向、类型一旦确定,顶层才能照着写 component 声明;接口反复改,顶层就得跟着改,来回几轮之后代码里全是注释掉的旧端口。所以我的习惯是先把两个底层实体的 entity 段写完、检查一遍,再动 architecture。

library IEEE; use IEEE.STD_LOGIC_1164.ALL; -- 注意:只用到 STD_LOGIC 的话不需要引 NUMERIC_STD,少引一个库少一类冲突 entity MY_AND2 is Port ( A : in STD_LOGIC; -- 输入 A B : in STD_LOGIC; -- 输入 B Y : out STD_LOGIC); -- 输出 Y = A and B end MY_AND2; architecture RTL of MY_AND2 is begin Y <= A and B; -- 并行信号赋值,综合成一级组合逻辑 end RTL;

这段代码里只有三处值得展开。第一处是library IEEE; use IEEE.STD_LOGIC_1164.ALL;,STD_LOGIC 这个类型定义在这个包里,漏掉就报「identifier std_logic is not declared」,这是新手第一个高频报错。第二处是端口方向in/out,VHDL 的 out 端口在实体内部是只读的,不能在 architecture 里读回来参与运算,真要读就先声明一个内部 signal 再赋值给输出,这个限制在后面写计数器时会反复遇到。第三处是end MY_AND2;后面跟的名字,建议和 entity 名保持一致,虽然 VHDL 允许省略,但文件一多之后不一致的结尾会让 review 的人非常痛苦。

端口名方向类型含义综合后对应
AinSTD_LOGIC第一个操作数器件输入引脚或上一级输出
BinSTD_LOGIC第二个操作数器件输入引脚或上一级输出
YoutSTD_LOGIC与运算结果器件输出引脚或下一级输入

提示:端口名别在底层用 A/B/Y 这种单字母、到顶层又原样复用。顶层如果也叫 A/B,port map 就会写成A => A,看着像没写,排查连线错误时对着这行代码怀疑人生。

2.3 顶层 AND_OR:component 声明、内部连线与 port map

顶层是整个实验的重点,报告里要求的原理图、底层电路图都从这一段代码综合出来。它要做三件事:声明要用到的元件、声明内部连线、把元件例化并按名字连好。

library IEEE; use IEEE.STD_LOGIC_1164.ALL; -- 顶层只负责例化和连线,不写任何逻辑表达式 entity AND_OR is Port ( iA : in STD_LOGIC; -- 与门输入 A iB : in STD_LOGIC; -- 与门输入 B iC : in STD_LOGIC; -- 或门第二输入 oAND : out STD_LOGIC; -- 与门输出,单独引出便于观察 oY : out STD_LOGIC); -- 最终输出 (iA and iB) or iC end AND_OR; architecture Structural of AND_OR is -- 1. 元件声明:端口列表必须和底层 entity 逐字对应 component MY_AND2 Port ( A : in STD_LOGIC; B : in STD_LOGIC; Y : out STD_LOGIC); end component; component MY_OR2 Port ( A : in STD_LOGIC; B : in STD_LOGIC; Y : out STD_LOGIC); end component; -- 2. 内部连线:对应原理图上两个器件之间的那根线 signal w_and : STD_LOGIC; begin -- 3. 例化 + 命名关联映射(左边是形参,右边是实参) U_AND : MY_AND2 port map (A => iA, B => iB, Y => w_and); U_OR : MY_OR2 port map (A => w_and, B => iC, Y => oY); oAND <= w_and; -- 旁路输出,纯粹为了在波形和原理图上多一个观察点 end Structural;

先看 component 声明。它不是定义元件,只是「告诉编译器有这么个黑盒,端口长这样」,真正的定义在 MY_AND2 的 entity 里。所以 component 的端口列表必须和 entity 一字不差:名字、方向、类型、位宽全都要对上,少一个端口综合阶段就报端口不匹配。这里最容易出的错是底层后来加了端口、顶层 component 忘了同步,代码能通过语法检查,一到综合就挂。

再看signal w_and : STD_LOGIC;。signal 在 VHDL 里表示「连线」,它的赋值有延迟语义(哪怕是零延迟也要经过 delta 周期),不能写在 process 里当变量用。这一根线连接了与门的输出和或门的输入,是层次化描述里唯一需要手工声明的东西,元件越多这根线的数量越多,命名混乱是后面最大的维护成本。

最后看 port map 的写法。A => iA这种叫命名关联,左边是元件定义里的形参名,右边是当前层次里实际接上去的信号名,顺序可以随便调。与之对应的是位置关联port map (iA, iB, w_and),按端口声明顺序一一代入,代码短但极其脆弱——只要底层端口顺序调整一次,所有位置关联的例化全部悄悄接错,功能不对但编译通过,属于典型的「黑匣子」故障。带多个端口的元件我一律用命名关联,多敲几行换来的是可读和可维护。

关联方式写法优点风险
命名关联A => iA, B => iB顺序无关、可读性好代码较长
位置关联port map (iA, iB, w_and)简短端口顺序一变就静默接错

2.4 自底向上还是自顶向下:这个实验为什么选自底向上

层次化设计有两条路径。自底向上是先写好底层元件、逐个验证,再往上搭顶层;自顶向下是先画框图、定顶层端口,再往下填每个子模块的实现。这个实验的元件只有两个、逻辑只有一级,自底向上明显更稳:两个门电路各自单独仿真一遍,真值表对上了再进顶层,顶层一旦出错,问题范围立刻缩小到「连线接错」这一种可能。

工程一旦大起来(十几个子模块、跨时钟域),自底向上就不合适了,因为顶层接口没定之前底层写了也可能白写,这时候通常是自顶向下:先定顶层端口和模块划分,每个子模块先给个空壳 architecture,能综合通过(此时综合器会给黑盒警告),再逐个填实现。

流程适合规模本实验适用性主要风险
自底向上2~5 个底层模块适用,推荐顶层接口反复改
自顶向下10 个以上模块不必要空壳模块忘记实现,综合出黑盒

ISE 里自底向上还有一个具体操作上的要求:文件名必须和 entity 名一致。MY_AND2 的代码放在MY_AND2.vhd里,ISE 添加源文件时才会自动把它识别为对应的实体;文件名叫and2.vhd而 entity 叫 MY_AND2,综合器会按 entity 名去找,找不到就当成缺失模块处理,报错信息往往指向顶层而不是文件名,查起来很绕。

3. 在 ISE 里把工程跑通:建工程、查语法、出 RTL 原理图的六个动作

3.1 新建工程时四个必须一次设对的选项

ISE 的 New Project 向导里有几项设置一旦定错,后面要么综合不出来,要么仿真器起不来,返工成本比一开始多花两分钟高得多。

设置项建议值设错的后果
Project Name英文、无空格,如lab1_and_or名字带空格或中文,部分流程生成脚本会中断
Project Location纯英文路径,如D:\fpga_lab\lab1路径含中文,综合或生成原理图时无提示失败
Top-Level Source TypeHDL选错成综合网表流程,后面加不了 VHDL 源文件
Family / Device / Package / Speed按实验室板卡上的器件型号逐项对号器件选错不影响功能仿真,但引脚约束和时序报告全部无效
SimulatorISim(ISE 自带)选成未安装的第三方仿真器,仿真启动直接报找不到可执行文件

路径这一项是血泪经验。ISE 那一代工具链里有若干环节对非 ASCII 路径处理得不干净,症状是综合日志看起来跑完了、进度条也走到底,但 View RTL Schematic 点开是空白,或者弹出「Failed to generate schematic」。很多人第一反应是代码写错了,回头把三个文件翻来覆去改半小时,其实只要把工程挪到D:\fpga_lab\下面重开一次就好了。从那以后我建工程第一件事是看路径。

器件那一项,教学板上通常会印着主芯片的型号,按 Family、Device、Package、Speed Grade 四项逐字对上即可。这一项选错不会立刻报错,功能仿真照样能跑,只有在做引脚约束、看时序报告、生成比特流的时候才会暴露,属于典型的「现在不疼、上板要命」。

注意:工程建好之后不要随手改器件型号。改了之后已有的综合结果全部失效,需要 Cleanup Project Files 再重跑一遍综合,否则看到的是旧网表的原理图。

3.2 三个源文件的建立顺序与 MY_OR2 的代码

工程建好后,右键工程 → New Source → VHDL Module,文件名填MY_AND2,向导会生成一个带端口声明的空壳。我一般不用向导生成的模板,直接新建空白 VHDL 文件自己写,因为模板里的注释块和默认端口经常要清掉,留着反而干扰阅读。

三个文件的建立顺序按自底向上的流程走:先 MY_AND2,再 MY_OR2,最后 AND_OR。前两个建完可以立刻右键 → Check Syntax 单独查一遍,不通过就先别往上搭,否则顶层报错时你分不清是自己的连线问题还是底层语法问题。

library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity MY_OR2 is Port ( A : in STD_LOGIC; B : in STD_LOGIC; Y : out STD_LOGIC); end MY_OR2; architecture RTL of MY_OR2 is begin Y <= A or B; -- 与 MY_AND2 结构完全对称,只换运算符 end RTL;

这段和 MY_AND2 唯一的差别就是or换成了and,端口名保持一致。保持对称是有意为之:两个底层元件接口一样,顶层写 component 声明时可以直接复制粘贴改名字,减少手抖写错方向的概率,也方便后面把这两个元件换成别的逻辑函数做对比实验。

三个文件都加进工程后,在 Hierarchy 面板里应该能看到 AND_OR 是加粗的顶层,MY_AND2 和 MY_OR2 挂在它下面。如果两个底层模块显示成独立的顶层(都加粗),说明它们没有被正确识别为子模块,这时候综合出来的顶层会是空壳,原因通常是顶层里 component 声明没写或者名字拼错。

3.3 Check Syntax 与 Synthesize:两次检查查的不是一回事

很多人把这两个按钮当成一回事,点哪个都行,实际上它们检查的层次完全不同。

Check Syntax 只做语法和语义检查:少分号、括号不配对、类型不匹配、use 语句漏了、signal 没声明就使用,这一类问题在这一步报出来。它的检查范围是单个文件,不跨文件,所以 component 声明和底层 entity 的端口不一致这种问题它发现不了。

Synthesize(XST)才是真正把三个文件放到一起看的地方。它要解析顶层里的每个 component 声明,去工程里找对应的 entity,把端口逐个对上,然后展开成网表。这一步的典型报错有三类:找不到实体(Module <name> is not found)、端口不匹配(宽度或方向对不上)、多驱动(同一个 signal 被两个输出端口同时驱动)。第一类多半是文件名和 entity 名不一致或者文件没加进工程;第二类基本是 component 和 entity 不同步;第三类是把两根线误接成了一个信号。

实际排查顺序我一般这样走:先对每个文件单独 Check Syntax,全绿了再做一次 Synthesize。综合日志里 WARNING 要看,但不必每个都改——「信号被优化掉」「端口未使用」这类是正常的;「latch inferred」「combinational loop」这两类必须处理,前者说明你的组合逻辑里存在未覆盖的分支,后者说明信号绕成了环。

提示:综合前先执行一次 Project → Cleanup Project Files。ISE 的增量编译有时候会复用上一次的中间文件,改了代码但综合结果没变,八成是这里没清。

3.4 RTL Schematic 与 Technology Schematic:报告里那两张图分别是什么

综合成功后,在 Design 面板里展开 Synthesize - XST,双击 View RTL Schematic,这是报告要求的第一张图——它显示的是代码的逻辑结构,AND_OR 顶层里能看到两个方块,分别标着 U_AND 和 U_OR,中间有一根线,跟你的代码一一对应。双击顶层方块可以下钻,看到它内部具体是怎么连的,这就是报告里「AND_OR 的底层电路」那张图的来源。

View Technology Schematic 是另一张图,它显示的是映射到目标器件之后的实现:你的与门和或门已经被翻译成 LUT 和相应的连线,方块上标的是 LUT2、LUT3 这样的原语名。这张图用来回答「综合器到底把我的逻辑实现成了什么」,跟代码结构已经不是一一对应了——同样的两行逻辑,换个器件族可能映射成不同的 LUT 数量。

图类型入口反映的层次用途
RTL SchematicSynthesize - XST → View RTL Schematic代码级的模块与连线交实验报告、检查例化是否正确
Technology SchematicSynthesize - XST → View Technology Schematic映射后的 LUT 与原语看资源占用、判断逻辑是否被优化掉

如果 RTL Schematic 打开后顶层只有一个空白方块、没有任何子模块,先别怀疑综合器,回到代码里查两件事:component 声明写的名字和 entity 名是否一字不差,以及底层文件是不是真的在工程里(Hierarchy 面板里能看到才算)。这两个都对,图一定出得来。

导出图片时,ISE 的原理图窗口支持 File → Export,存成 PNG 或 EMF 都行。EMF 是矢量格式,插进 Word 报告放大不糊,比截图质量高,这是当年被导师退回重做截图之后学会的。

3.5 从综合报告确认三个模块都在

综合完成后打开 Synthesis Report,里面有一节 Hierarchical Design,会列出本次综合包含的所有模块名和层级关系。这一节是判断「层次化是否成功」最直接的证据:AND_OR 在根层级,MY_AND2 和 MY_OR2 在它下面作为子模块出现,说明结构化描述生效了。如果这里只有 AND_OR 一个模块,说明两个门被综合器拍平进了顶层,回头检查顶层是不是被写成了数据流描述。

同一个报告里还有资源占用(Slice、LUT、IO 数量)。这个实验小到几乎没有资源压力,但养成习惯看一眼没坏处:LUT 数量如果是 2 个,说明两个门各占一个;如果是 1 个,说明某一级被优化合并了,这时候要回头想想优化是不是改变了你想要的层次结构。

4. 验证不靠肉眼:8 行真值表的 testbench 与波形三个观察点

4.1 组合逻辑为什么也必须写 testbench

组合逻辑只有三个输入,看着真值表就能对,很多人因此跳过仿真直接看原理图。问题在于原理图只证明「连线接上了」,不证明「接对了」。把与门的输出接到或门的第一个输入、或门的输出接到与门的第二个输入,原理图照样画得漂漂亮亮,层次照样正确,功能全错。三个输入一共八种组合,人眼对着波形数一遍很容易漏。

另一种情况更隐蔽:端口映射时用了位置关联,底层 MY_OR2 的端口顺序是 A、B、Y,顶层写成port map (w_and, iC, oY),看着没问题;但如果哪天底层改成 B、A、Y,这段代码不报错、功能反了。仿真跑一遍八种输入,这种错误一秒现形。

写 testbench 的成本其实很低,这个实验的激励连时钟都不需要,八行赋值加几个 wait 就完了,比上板按开关快得多。

4.2 TB_AND_OR:8 行真值表的激励怎么写

testbench 本身也是一个 VHDL 实体,只是它没有端口,因为它不和外界的任何东西连接。它的 architecture 里例化被测模块(DUT),然后产生激励信号。

library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; -- 循环变量转 std_logic_vector 需要这个包 entity TB_AND_OR is -- 空端口:testbench 不综合、不上板,只做仿真 end TB_AND_OR; architecture Sim of TB_AND_OR is -- 把被测模块的端口原样声明成 component component AND_OR Port ( iA : in STD_LOGIC; iB : in STD_LOGIC; iC : in STD_LOGIC; oAND : out STD_LOGIC; oY : out STD_LOGIC); end component; -- 激励信号,初值给 0,避免仿真开始时的 U 态 signal sA, sB, sC : STD_LOGIC := '0'; signal sAND, sY : STD_LOGIC; begin -- 例化被测设计 uut : AND_OR port map (iA => sA, iB => sB, iC => sC, oAND => sAND, oY => sY); -- 激励进程:遍历 000 到 111 共 8 种输入 stim_proc : process variable v_in : std_logic_vector(2 downto 0); begin for i in 0 to 7 loop v_in := std_logic_vector(to_unsigned(i, 3)); sA <= v_in(2); sB <= v_in(1); sC <= v_in(0); wait for 10 ns; -- 每种输入保持 10 ns,波形上足够看清 end loop; report "TB_AND_OR finished" severity note; wait; -- 挂起进程,否则仿真会重复执行这段激励 end process; end Sim;

逻辑说明分三段。component 声明这一段和顶层里的写法完全一样,因为 testbench 看待被测模块的眼光和顶层看待子模块是一样的。信号声明这一段,:= '0'这个初值不能省,否则仿真开始时 sA/sB/sC 是 'U'(未初始化),组合输出也是 'U',波形开头会有一段无法解释的红色区域,新手容易误判成电路有问题。激励进程这一段用 for 循环遍历 0~7,to_unsigned(i, 3)把整数转成 3 位无符号数,再转成 std_logic_vector 逐位赋给三个输入。

参数说明:wait for 10 ns是每一步的保持时间,这个值不影响功能仿真的正确性,只影响波形好不好看;如果做后仿真(带门延迟),这个间隔必须大于最长的组合逻辑延迟,否则前一级的输出还没稳定就采下一组输入,会看到一堆假的毛刺。末尾的wait;(不带时间)会让进程永久挂起,仿真器没有可执行的事件后自动结束,没有这一句的话进程会反复从头执行,波形变成无限循环。

4.3 波形上的三个观察点

仿真跑完打开波形窗口,把 sA、sB、sC、sAND、sY 五条信号都加进去,重点看三处。

第一处是 sAND 与 sA、sB 的关系。sA 和 sB 同时为 1 时 sAND 才是 1,其他时候都是 0,这是与门的定义,错了说明 MY_AND2 的代码或者例化有问题。

第二处是 sY 与 sAND、sC 的关系。sAND 为 1 或 sC 为 1 时 sY 为 1。这一处专门用来抓「连线接反」类错误:如果你把或门的两个输入接反了,组合逻辑的或运算结果是对称的,波形上看不出来;但如果误把 iC 接到了与门、把 w_and 接到了或门的第二个输入,这一处的波形立刻对不上。

第三处是信号跳变沿的对齐。功能仿真里组合逻辑是零延迟,sA 变化的那一瞬间 sAND 和 sY 就应该跟着变,波形上看起来是同一根竖线。如果你在波形上看到了明显的延迟台阶,说明你跑的是带延迟的门级仿真,不是功能仿真,先确认一下仿真模式。

关于毛刺,功能仿真里看不到,因为所有门都是零延迟。真正上板或者做 Post-Route 仿真时,sA 和 sB 同时翻转、而 sAND 因为两级 LUT 延迟不同产生一个几纳秒的窄脉冲,这个是组合逻辑的竞争冒险,属正常现象。第一次看到这种毛刺会紧张,其实只要它不被时钟采样到就没有影响,这个实验没有时钟,纯组合输出,毛刺不影响最终稳定值。

注意:ISim 默认只跑 1000 ns。八种输入按 10 ns 一步只需要 80 ns,够用;如果自己加了更多激励,记得在仿真工具栏把 Run time 改大,否则波形跑到一半就停了,看起来像是仿真卡死。

4.4 把断言写进激励:让仿真自己报错

靠肉眼看波形只适合这种八行真值表的小实验,输入一多就不现实。更靠得住的做法是把期望结果写成 assert,让仿真器自己判对错,日志里出现 error 就是有问题,不用一条条数波形。

for i in 0 to 7 loop v_in := std_logic_vector(to_unsigned(i, 3)); sA <= v_in(2); sB <= v_in(1); sC <= v_in(0); wait for 10 ns; -- 期望值直接写逻辑表达式,和设计意图逐条对照 assert (sAND = (sA and sB)) report "oAND mismatch at i=" & integer'image(i) severity error; assert (sY = ((sA and sB) or sC)) report "oY mismatch at i=" & integer'image(i) severity error; end loop;

逻辑说明:断言放在wait for 10 ns之后,此时输入已经更新,组合输出也经过了 delta 周期传播到位,采样到的是稳定值。severity error会让仿真在日志里打出红色错误行但不中止,一次跑完能收集到全部错误;如果改成severity failure,第一个错误就停,适合定位单个问题。

参数说明:integer'image(i)把循环变量转成字符串拼进报错信息,日志里能直接看出是哪一组输入出的问题,不用自己数波形位置。这两条断言把设计意图(oY = (A and B) or C)用一行代码固定下来,后面任何人改了顶层连线,仿真会立刻失败,比看原理图靠谱得多。

写断言时容易犯的错是把期望值也写成sY = sY这种废话,或者忘了括号导致运算优先级不对——VHDL 里 and 的优先级高于 or,sA and sB or sC会按(sA and sB) or sC解析,但为了可读性我习惯把括号全写上。

4.5 ISim 的操作细节与仿真时长设置

在 ISE 里跑仿真,先确认工程的 Simulator 设成了 ISim。左边面板切到 Simulation 视图,选中 TB_AND_OR,展开 ISim Simulator,双击 Simulate Behavioral Model,ISim 会启动并自动加载波形。

几个实际会碰到的细节。第一,仿真视图和实现视图是分开的,切回 Implementation 才能综合,很多人卡在「为什么 Simulate 是灰的」,多半是当前在 Implementation 视图下选中了顶层设计而不是 testbench。第二,ISim 里默认加进波形的信号可能只有顶层端口,sAND、w_and 这类内部信号要手动在 Objects 窗口里拖进波形区。第三,跑完之后波形窗口里的黄色竖线是当前时间游标,拖动它可以在左侧看到该时刻所有信号的值。

关于仿真时长,默认 1000 ns 对这个小实验绰绰有余,但如果后面把激励改成 100 ns 一步、跑更多组合,就要在工具栏把 Run time 调大再点运行。另一种做法是在 testbench 末尾用wait for 100 ns;加一句std.env.finish;(VHDL-2008),仿真器跑完自动退出,不用手动点停止。老版本 ISE 的 ISim 对 VHDL-2008 支持有限,用wait;挂起进程的方式更稳妥。

仿真结果和报告里的「测试及结果」一节是对应的:八种输入的波形截图加上断言全过的日志,比只贴一张原理图有说服力得多。原理图证明结构对,仿真证明功能对,这两样凑齐,这个实验的验证部分才算完整。

5. 避坑:层次化工程综合失败的五个高频原因

5.1 综合报找不到模块,但文件明明在工程里

现象:Synthesize 报Module MY_AND2 is not found或者顶层被综合成空壳,RTL 原理图里没有子模块。

原因:有三种可能,按出现频率排——文件名和 entity 名不一致(文件叫myand2.vhd,entity 叫MY_AND2);文件添加进了工程但没被标记为 VHDL 源(在文件属性里看 File Type);component 声明里的名字和 entity 名差一个字符,比如少个下划线。

解决:先看 Hierarchy 面板里三个模块是不是都挂在 AND_OR 下面,没挂上去的先解决添加问题;再看每个文件名和它 entity 名是否严格一致(VHDL 不区分大小写,但文件名和 entity 名要能对上);最后把顶层 component 声明和底层 entity 的端口列表并排放着逐字比对一遍。这一步用文本编辑器的分屏对照最快。

5.2 语法检查全过,综合报端口不匹配

现象:三个文件单独 Check Syntax 都是绿的,一综合就报端口列表长度或方向不匹配。

原因:Check Syntax 是按文件做的,不跨文件比对。顶层的 component 声明写的是旧版本接口,底层 entity 后来加了一个端口或者改了方向,两边对不上。这种情况在改过代码又忘了同步的时候必现。

解决:把 component 声明和 entity 声明当成同一份东西维护,改一边立刻改另一边。更省事的做法是在 VHDL-93 之后直接用直接例化语法U_AND : entity work.MY_AND2 port map (...),不写 component 声明,编译器直接去库里找实体,从根上消除两边不同步的可能。

5.3 RTL 原理图是空白或者生成失败

现象:综合成功,点 View RTL Schematic 弹出失败提示,或者窗口打开后一片空白。

原因:九成是工程路径里有中文、空格或者特殊字符。ISE 生成原理图这一步会调用外部程序,路径处理不干净时它不会报「路径错误」,只会报一个笼统的失败。剩下的一成是工程文件损坏,需要 Cleanup 后重来。

解决:把整个工程目录挪到纯英文、无空格的路径下(比如D:\fpga_lab\lab1),重新打开工程、Cleanup Project Files、重新综合。这个坑我在不同机器上遇到过三次,症状一模一样,现在建工程第一件事就是看路径。

5.4 波形全对,但上板行为不对

现象:功能仿真波形完全正确,断言全过,下载到板子上按开关却没有反应或者响应错乱。

原因:这个实验如果只做到仿真,问题多半出在引脚约束上——约束文件里把输出绑到了没接 LED 的引脚,或者输入绑到了按键但极性搞反(按键按下是低电平,代码里当成高电平处理)。另一种是约束文件根本没加进工程,综合时被忽略,引脚分配全是默认值。

解决:先确认约束文件已经在工程里(Hierarchy 面板能看到 .ucf),再对一遍板卡原理图上的引脚编号;按键极性用示波器或者直接用一个最简单的直通设计验证一遍。约束文件的写法各家板卡不一样,按自己板子的引脚表填,别照抄别人的。

5.5 改了代码,综合结果没变

现象:明明改了顶层连线,重新综合后 RTL 原理图还是老样子,仿真波形也是旧的。

原因:ISE 默认走增量综合,会复用上一次的中间文件,某些情况下依赖关系判断失误,没有重新编译改动过的文件。

解决:每次改完代码,Project → Cleanup Project Files,然后重新 Synthesize。养成这个习惯之后,这类「玄学」问题基本消失。判断是不是踩了这个坑,看一眼综合日志的时间戳和文件列表,如果日志里根本没提到你刚改的那个文件,就是它。

6. 进阶:把这次实验改造成可复用的参数化模块

这个实验只有两个门、三个输入,代码量小到可以手写。但同一套层次化思路往后推一步,就能变成一个能复用的模板:把位宽参数化,把 component 声明换成直接例化,顶层只保留连线。

直接例化是 VHDL-93 引入的写法,省掉 component 声明这一段,编译器直接按实体名去库里找:

architecture Structural of AND_OR is signal w_and : STD_LOGIC; begin -- 不再写 component 声明,直接按实体名例化 U_AND : entity work.MY_AND2 port map (A => iA, B => iB, Y => w_and); U_OR : entity work.MY_OR2 port map (A => w_and, B => iC, Y => oY); oAND <= w_and; end Structural;

参数化的写法把单比特扩展成向量,一次处理多位:

entity MY_AND_N is generic ( WIDTH : integer := 4 ); -- 位宽可配,默认 4 位 port ( iA : in std_logic_vector(WIDTH-1 downto 0); iB : in std_logic_vector(WIDTH-1 downto 0); oY : out std_logic_vector(WIDTH-1 downto 0)); end MY_AND_N; architecture RTL of MY_AND_N is begin oY <= iA and iB; -- 向量按位与,不需要写循环 end RTL;
写法代码量端口同步风险适用场景
component + 命名关联多有,需手工同步教材、需要严格对应报告格式的场合
直接例化 entity work少无自己维护的工程,推荐
generic 参数化少无,且位宽可配同一逻辑要在多种位宽下复用

三者的取舍很清楚:交实验报告时用 component 写法,因为报告要展示「层次化描述」的标准形式;自己写工程用直接例化,少一段声明少一处出错的地方;一旦这个逻辑要在多个位宽下出现,就上 generic,把位宽提到端口之外。

从那以后我每次新建 ISE 工程,都会先把三样东西对齐再动手:工程路径(纯英文无空格)、文件名与 entity 名、顶层 component 与底层 entity 的端口表。宁可多花五分钟核对,也不愿意再对着一个空白的 RTL 原理图猜半小时——那种感觉比代码报错难受多了,报错至少还告诉你哪里错了。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询