资讯详情

资讯详情

VHDL Test Bench仿真:force激励到results.txt自检

简介《系统仿真》课件围绕电子设计中的仿真验证展开面向学习VHDL与FPGA/CPLD设计流程的读者帮助在硬件制造前理解电路功能验证、源程序仿真与激励信号生成等关键环节。压缩包共1个PPT文件大小约654KB内容以章节讲授和示例分析为主便于课堂演示或自学查阅。课件涉及仿真流程、设计库管理、AND门与三态缓冲器等VHDL实例并讨论用波形命令和VHDL代码产生激励信号的方法可让读者建立从行为描述到仿真波形分析的完整认识。已有169人学习适合作为数字系统设计与EDA课程的辅助材料也便于教师备课和初学者梳理仿真验证思路。1. 波形全 X 的那半小时把我拽回了系统仿真第一次给一个 8 位计数器写 testbench编译零报错跑起来波形全是 X。盯着屏幕改时钟周期改了半小时最后发现跟时钟没关系——实体没例化对激励压根没进去。后来翻出这份《系统仿真》课件从 12.1 的仿真定义一路读到 12.4 的测试基准才明白它把 VHDL 源程序仿真、激励信号生成、Test Bench 自检这三件事按依赖顺序摆好了先有能编译的设计单元再有不碰硬件的激励源最后才有可复现的波形和结果文件。这份课件面向数字电路与 FPGA/CPLD 入门阶段的学习者同样适合已经能写 HDL、但仿真总是「跑不出波形」的开发者。下面按课件章节顺序把每段代码怎么跑、怎么改讲清楚。2. 课件骨架从 12.1 到 12.4 的仿真链路与工具链落位2.1 四节内容其实是一条依赖链不是四个知识点把课件翻一遍会发现12.1 到 12.4 不是并列的四块知识而是一条有前后依赖的链路。12.1 定概念仿真也叫模拟Simulation是电路设计的一种间接检测方法用计算机对整个硬件系统做模拟检测不接触具体硬件。这句话决定了后面所有代码的写法——你写的不是能烧进芯片的东西而是能被仿真器调度、能打出波形的东西。12.2 定流程VHDL 文本或图形输入 → 翻译成文本 → VHDL 编译进设计库 → 仿真 → 输出数据和波形配一张设计库管理图。12.3 解决「激励从哪来」给了两条路仿真器的波形设置命令force和用 VHDL 写一个波形信号发生器SIGGEN。12.4 把前两者收口成测试基准Test Bench在新建的 VHDL 程序里对被测实体做元件例化、加激励、观察内部信号还能把结果写文件。这个顺序有个很实用的含义如果你的波形跑不出来排查方向也应该按这条链倒着走——先看激励有没有加进去12.3再看被测实体有没有被正确例化12.4最后才怀疑设计本身12.2。绝大多数新手卡在第一步。课件里还画了 FPGA/CPLD 的完整设计流程设计准备与系统划分 → 原理图/HDL 文本编辑 → 功能仿真 → 逻辑综合器 → 结构综合器适配→ 时序仿真 → FPGA/CPLD 编程下载 → 器件和电路系统。仿真在这个流程里出现两次位置不一样意义也完全不同。2.2 功能仿真与时序仿真是两次不同的活课件流程图上功能仿真在综合之前时序仿真在适配之后。这两次不是重复劳动。功能仿真也叫行为仿真、前仿真跑的是 RTL 代码用的是 VHDL 源程序里的行为描述。它验证的是逻辑对不对加法器加出来是不是 15104 位溢出、三态门在 oe0 时输出是不是 Z、计数器的 DIR 反向时是不是从 0 跳到 255。这时候没有真实布线延迟只有你在代码里显式写的AFTER时间。时序仿真后仿真跑的是适配之后回填的门级网表加延迟文件验证的是时序对不对建立时间够不够、关键路径延迟会不会吃掉一个时钟周期。课件里那个综合之后的例子很说明问题——同一个and1实体架构名变成了类似EPF10K10LC84_a3这样的形式带器件型号和适配批次后缀。这不是人写的是综合适配工具生成的回填网表里面的. . . . . .省略号代表工具插入的元件例化和布线延迟。我一般的做法是功能仿真在写代码的当天就跑时序仿真留到布局布线之后、上板之前跑一次。新手最容易犯的错是拿时序仿真当日常调试手段编译一次几十秒改一行代码等半天效率极低。2.3 工具链选型三种仿真器命令差别不小课件没绑定具体工具但代码是标准 VHDL主流三种环境都能跑。我按上手成本排一下环境安装成本适合场景激励方式GHDL GTKWave低命令行即可纯 VHDL 学习、脚本化回归VHDL testbenchModelSim / Questa 类中需图形界面波形调试、force 命令交互force 命令 testbench综合工具自带仿真器随综合工具安装已有时序库、要跑后仿真工程内 testbenchGHDL 的完整流程是四步分析-a、精化-e、运行-r、看波形。这里的精化一步很多人会漏它是把设计单元组织成可执行模型的过程纯组合逻辑的实体不精化直接跑会报找不到顶层。# 分析把 VHDL 源文件编译进工作库 work ghdl -a --std08 and1.vhd # 精化指定顶层实体生成可执行仿真模型 ghdl -e --std08 and1 # 运行导出 VCD 波形限制仿真时长防止死循环 ghdl -r --std08 and1 --vcdand1.vcd --stop-time1us # 打开波形查看器 gtkwave and1.vcd --std08指定用 VHDL-2008 标准课件代码用的是STD_LOGIC_1164用 93 标准也能过但 2008 对process(all)和条件表达式支持更好。--stop-time1us必须加否则带时钟的 testbench 会一直跑到你手动中断——我第一次跑计数器 testbench 没加这个参数进程挂了十几分钟才发现。ModelSim 一侧的等效流程是建库、编译、加载、加激励vlib work vcom -2008 and1.vhd vsim work.and1 # 下面这行是 12.3 讲的 force 命令先给 aaa、bbb 灌激励 force aaa 0 0, 1 10 force bbb 0 0, 1 20 run 50工作目录我习惯这样分rtl/放设计实体tb/放 testbenchsim/放仿真脚本和波形输出lib/是编译产生的工作库不纳入版本管理。课件里的例子都是单文件直接堆在一个目录里也能跑但改到第五个例子的时候work库里同名实体互相覆盖的问题就会冒出来——这个坑后面单独说。3. 源程序仿真and1 与三态缓冲器的完整代码链路3.1 and1最小的可仿真实体先跑通再说课件例 12-1 是整个链路里最干净的起点一个二输入与门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;三段结构要分清LIBRARY/USE是库声明把STD_LOGIC_1164包里的九值逻辑类型拉进来没有这一句STD_LOGIC根本不存在ENTITY是端口清单IN/OUT决定方向实体名and1就是后面精化和例化时用的名字ARCHITECTURE是行为描述one是这个架构的名字。ccc aaa AND bbb;是并发赋值语句写在进程外面它隐含一个对aaa、bbb都敏感的进程——任一输入变化输出在一个 delta 周期后更新。这句话很关键并发语句本身就是进程的语法糖理解这一点后面看 12.4 计数器为什么要把COUNT COUNTER;放进 process 就顺了。跑这个实体不需要 testbench用 force 命令就能看到波形vsim work.and1 force aaa 0 0, 1 10 force bbb 0 0, 1 20 run 50时刻 0 到 10aaa0、bbb0ccc010 到 20aaa1、bbb0ccc还是 020 之后两个都为 1ccc才变 1。波形上能看到三段台阶这就是「对硬件系统模拟检测」最直观的样子——没接任何电路逻辑已经验证完了。aaa、bbb这种命名我建议改掉。课件为了不和 VHDL 保留字冲突用了三字母重复实际项目里a、b、y或者din1、din2、dout更清楚。改名的时候实体、架构、testbench 三处要同步漏一处就是编译期报错反而好排查。3.2 TRIBUF_and1进程、敏感表与 TRANSPORT 延迟例 12-2 把难度拉上来了一个带延迟参数的三态缓冲器。这是课件里信息密度最高的一段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 oeEVENT 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;逐块看。GENERIC是参数化手段三个 TIME 类型参数默认都是 1 ns例化时可以覆盖成真实器件的延迟值比如ttri 3 ns。这是课件里唯一一个用 GENERIC 的例子也是它最有工程价值的地方——同一份逻辑描述换一组延迟参数就能逼近不同工艺的器件行为。PROCESS (in1, oe)的敏感表同时列了输入和使能。VHDL-93 里敏感表必须手写漏一个信号那个信号变化时进程不触发仿真结果就是错的但不是报错的属于最阴的一类 bug。VHDL-2008 支持process(all)自动推导我个人倾向在 2008 环境下一律写process(all)省掉这类问题。oeEVENT是属性用法判断oe在当前仿真周期内是否发生了事件。外层的 if 把情况分成两类使能端刚跳变走ttxz/ttzx两条延迟使能端稳定走ttri延迟。内层再按oe的电平决定输出是高阻Z还是跟随in1。这样拆的好处是延迟模型可区分——打开和关闭的延迟在真实器件里往往不对称。TRANSPORT这个关键字值得多说一句。VHDL 有两种延迟模型TRANSPORT表示纯传输延迟输入上的窄脉冲会原样传到输出INERTIAL默认会滤掉窄于延迟值的脉冲。课件这里用TRANSPORT意思是「不管脉冲多窄我都要看到」对三态总线的毛刺分析是必要的。如果换成默认的INERTIAL一个 0.5 ns 的毛刺配上 1 ns 延迟会被直接吞掉波形上看不见你会误以为设计没问题。提示把TRANSPORT改成INERTIAL再跑一遍同一个 testbench对比两条波形是理解两种延迟模型最快的办法。这段代码有个细节可以优化ELSIF oe 1和ELSIF oe 0两个分支覆盖了oe的所有取值但std_logic有九种取值X、U、-落不进任何分支输出保持原值。这正是仿真初期波形带 X 的常见来源之一排查时要先看使能端是不是还没被初始化。3.3 综合之后的同一个 and1为什么要分清 RTL 与网表课件在例 12-2 后面又贴了一段and1实体端口和例 12-1 一样但架构名变成了EPF10K10LC84_a3里面是省略号ARCHITECTURE EPF10K10LC84_a3 OF and1 IS . . . . . . END EPF10K10LC84_a3;这段不是拿来手写的是综合适配工具对例 12-1 那个与门做完布局布线之后回填出来的门级描述。架构名里的器件型号和批次后缀是工具用来区分不同适配结果的标识。省略号部分是工具插入的逻辑单元例化和互连线延迟。理解这一点才能理解 12.2 那张流程图为什么把仿真画了两次。你在例 12-1 上跑的是ARCHITECTURE one验证ccc aaa AND bbb这个等式在EPF10K10LC84_a3上跑的是带真实延迟的等效电路验证信号从输入脚走到输出脚的时间够不够。两个and1同名不同架构在同一个work库里会互相覆盖——这就是我前面提的那个坑后面避坑章节展开。我一般的工程约定是RTL 文件命名加_rtl后缀网表文件单独放netlist/目录并在独立库里编译绝不让两者进同一个工作库。课件受篇幅限制没有展开库管理但 12.2 那张「设计库管理图」画的就是这件事。4. 激励信号与测试基准从 force 命令到 results.txt 落盘4.1 force 命令不改一行代码把激励灌进去12.3 给了第一种激励方式——仿真器的波形设置命令。课件给的语法是force 信号名 值 [时间][, 值 时间 …] [-repeat 周期]三个例子层层递进。force a 0是把信号a的当前值强制为 0不做时间序列force b 0 0, 1 10是在时刻 0 赋 0、时刻 10 赋 1force clk 0 0, 1 15 -repeat 20是周期信号周期 20前半段低后半段高。对应到加法器例 12-3课件给的完整激励是这样的force a 10 0, 5 200, 8 400 force b 3 0, 4 100, 6 300 run 500把这四行和例 12-3 的ADDER4对应起来看c a ba是 0~15 的整数b也是。时刻 0 到 100c 10 3 13100 到 200b变成 4c 14200 到 300a变成 5c 9300 到 400b变成 6c 11400 之后a8c 14。跑 500 个时间单位波形上能看到五个台阶。这种方式的优点是零代码改动适合临时验证某个模块缺点是激励写不进版本库换台机器、换个人这套 force 命令就丢了。我一般用它做「探针式」验证——设计刚写完想快速看看输出对不对一旦验证逻辑稳定立刻转写成 testbench 固化下来。force的信号名在层次化设计里要写全路径。顶层是bench、例化标签是U1、内部信号是a那 force 的时候要写force /bench/U1/a 10 0。只写a在单层设计里能找到多层设计里找不到或者 force 到了错误的信号上——这是新手最常见的翻车点之一。4.2 SIGGEN BENCH用 VHDL 写一个波形发生器第二种方式是课件重点用 VHDL 写激励源。先看激励发生器本身例 12-4ENTITY SIGGEN IS PORT ( sig1 : OUT INTEGER RANGE 0 TO 15; sig2 : OUT INTEGER RANGE 0 TO 15 ); END; ARCHITECTURE Sim OF SIGGEN IS BEGIN sig1 10, 5 AFTER 200 ns, 8 AFTER 400 ns; sig2 3, 4 AFTER 100 ns, 6 AFTER 300 ns; END;注意这个实体的END;后面没有实体名VHDL 允许省略。架构Sim里两条并发赋值用的是波形表达式语法sig1 10, 5 AFTER 200 ns, 8 AFTER 400 ns;意思是时刻 0 取 10200 ns 后跳到 5400 ns 后再跳到 8。这正是 12.3 说的「用 VHDL 写一个波形信号发生器」把时间序列直接编码进信号赋值。有了激励源还要有个顶层把激励和被测设计连起来。例 12-5 的BENCH就是干这个的ENTITY BENCH IS END ENTITY BENCH; ARCHITECTURE one OF BENCH IS COMPONENT ADDER4 PORT ( a, b : 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;COMPONENT是元件声明把外部实体的端口抄一遍编译器据此检查例化是否匹配。两个PORT MAP用了两种关联方式U1是位置关联顺序必须和ADDER4的端口声明完全一致错一个位置就接错线U2是命名关联sig1 a明确写出「激励源的 sig1 接到本层的 a」。我强烈建议一律用命名关联位置关联在端口增加到五六个之后必然出错而且错了不报错、只出错波形。SIGNAL a, b, c是本层的内部信号它们不对外暴露只在仿真器里可见。被测设计ADDER4通过U1例化进来激励源SIGGEN通过U2例化进来两者通过a、b两个信号对接。这就是 Test Bench 的本质——一个没有端口的空壳实体内部把激励和被测对象用信号焊起来。跑出来的波形就是课件图 12-3 那张a、b按激励源的节奏跳变c跟着算出和。整个过程不碰任何硬件。4.3 counter8 的 Test Bench自检、时序判沿与结果写文件12.4 把前面的东西收口到最有工程价值的一个例子上被测设计是一个 8 位加减计数器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 CLKEVENT 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;这段代码有几个点值得逐条说。VARIABLE COUNTER声明在 process 内部只在本次进程执行内有效而且赋值用:立即生效不是延迟生效。变量不能跨进程共享也就不存在多驱动冲突用在计数器这种「读—改—写」的场景最合适。COUNT COUNTER;写在所有 if 外面每次进程触发都把变量的最新值同步到输出信号上这样输出才会跟着时钟更新。CLK 1 AND CLKEVENT是 VHDL-93 里判断上升沿的经典写法等价于rising_edge(CLK)。两者的区别在边界情况rising_edge会排除从X到1的跳变而裸写的判据不会。仿真初期信号还带U的时候这个差别会导致误触发。我一般直接用rising_edge(CLK)少踩一个坑。复位是异步的写在进程敏感表里RESET 1时不管时钟都会清零。装载和使能都是同步的只在沿上生效。这套设计是工业里最常见的组合看懂这个模板换成 16 位、32 位只是改RANGE上限的事。再看 12.4 后半的测试基准例 12-7这是整个课件里最接近真实工程的一段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: in STD_LOGIC; RESET: in STD_LOGIC; CE, LOAD, DIR: in STD_LOGIC; DIN: in INTEGER range 0 to 255; COUNT: out INTEGER range 0 to 255); end component; shared variable end_sim : BOOLEAN : false; -- 仿真结束标志 signal CLK, RESET, CE, LOAD, DIR: STD_LOGIC; signal DIN: INTEGER range 0 to 255; 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 ); end testbench_arch;File RESULTS: TEXT open WRITE_MODE is results.txt;是 VHDL 文件 I/OTEXT类型来自std.textioWRITE_MODE表示以写方式打开。文件路径是相对仿真器工作目录的跑完在当前目录下就能找到results.txt。shared variable end_sim是 VHDL-93 里的共享变量用来在多个进程之间传递「仿真该结束了」的信号。课件只声明了它没给配套的时钟生成进程和结束进程——这属于课件篇幅所限的省略。常见做法是补两个进程配一段会在end_sim置位后调用file_close的收尾逻辑-- 常见做法时钟生成进程周期 20 ns CLK_GEN: process begin while not end_sim loop CLK 0; wait for 10 ns; CLK 1; wait for 10 ns; end loop; wait; end process; -- 常见做法主控进程跑完预设激励后置位并关文件 STIM: process begin RESET 1; CE 1; LOAD 0; DIR 1; DIN 0; wait for 45 ns; RESET 0; -- 释放复位开始正常计数 wait for 500 ns; end_sim : true; -- 通知时钟进程退出 file_close(RESULTS); -- 必须显式关闭否则内容可能不落盘 wait; end process;WRITE_RESULTS这个过程的参数列表用的是;分隔而不是逗号VHDL 子程序语法就是这样写惯了 C 系语言的人第一次写会别扭。write重载了多种类型right是右对齐16、2、257是字段宽度。宽度给足很重要给窄了会截断高位COUNT在 255 的时候写进去就少一位事后分析数据根本对不上。writeline(RESULTS, V_OUT)是真正写盘的动作前面所有write都只是往行缓冲区V_OUT里拼字符串。这个「先拼行再落盘」的模式和 C 的fprintf、Python 的write一个道理。这套自检流程的价值在于波形是给人眼看图的一次只能看一段文本结果是给机器比对的可以跑几百组激励、几十个回归用例然后用diff做自动判定。课件把这一步放在了最后说明它认这条路径——从看波形到看数据是从「能跑」到「能验证」的分水岭。5. 避坑与常见问题排查五个真实踩过的坑5.1 现象波形全 U 或全 X仿真器不报错原因基本只有两类。一是被测实体没被例化testbench 顶层是空的信号声明了但没有任何驱动源STD_LOGIC默认初值就是U二是激励进程启动了但RESET一直是1寄存器永远停在复位态输出保持初值。解决办法是先看仿真器输出的层次结构确认UUT这个例化标签存在再看RESET信号在波形上的实际取值。我习惯在所有 testbench 开头先跑一段「复位脉冲 释放」的固定动作把复位这件事前置避免后面每个用例都怀疑一次。5.2 现象force 命令报「信号未找到」或者 force 了但波形没变原因是层次路径写错了。多层设计里信号名必须带例化标签/testbench/UUT/CLK才对直接写CLK在单层能过、多层必挂。还有一种情况是 force 到了 testbench 层的同名信号上而那个信号并没有连到被测设计的端口。解决办法是在波形窗口里点开层次树把目标信号的完整路径复制出来再 force。命名关联的port map会减少这类问题因为接错线在编译期就能被端口名检查抓到。5.3 现象三态缓冲器的输出一直是 Zoe 怎么拉都不动课件例 12-2 的进程敏感表是PROCESS (in1, oe)如果敏感表漏了oe使能变化时进程不触发输出永远停在初始的高阻。另一类原因是oe的初值没给仿真开始是U落不进IF oe 0或ELSIF oe 1任何分支。解决办法是把敏感表改成process(all)VHDL-2008或者在 testbench 开头给oe一个明确的初值再开始跑。U落到 if 分支里的行为属于仿真语义不报错只能靠人工检查。5.4 现象results.txt 是空文件或者只有前几行textio的文件缓冲不会每次writeline都立刻刷盘如果仿真中途被stop或quit打断没执行到file_close缓冲区里的内容就丢了。另一种情况是进程在end_sim置位前就wait住file_close那行永远不执行。解决办法是把file_close放进一个独立的收尾进程由end_sim的上升沿触发或者干脆在run命令后加足够长的仿真时间让主控进程自然走完。跑回归脚本时我一般会在结束前加一句file_close然后assert一下文件行数符合预期避免「跑完了但没数据」这种假成功。5.5 现象编译报「entity already defined」或者两个同名实体的行为对不上课件里and1出现了两次——例 12-1 是手写的行为架构例 12-2 之后是综合回填的EPF10K10LC84_a3架构。两者实体同名编译进同一个work库时后编译的会覆盖前面的精化时拿到的可能是你根本没想跑的那个版本。解决办法是分库。RTL 编进work网表编进gate库testbench 里用配置声明configuration指定绑定哪个架构-- 常见做法在 testbench 顶部显式指定使用的架构避免库覆盖带来的歧义 for UUT : and1 use entity work.and1(one); -- 明确绑定行为架构 end for;配置声明比改代码可靠得多因为它写在 testbench 里随代码进版本库谁跑都是同一个绑定关系。6. 二次开发把课件例子改造成可批量跑的自检平台课件里的四个例子都是单点验证改造成能复用的自检平台工作量比想象中小。我的做法分三步。第一步是参数化。把 testbench 里的仿真时长、时钟周期、期望结果都提到GENERIC里一个 testbench 文件能覆盖多个用例。第二步是把WRITE_RESULTS的判定逻辑从「人工看文本」改成「自动比对」——在 testbench 里加断言值不对直接报错退出退出码非零回归脚本就能捕获。第三步是把仿真命令写进 shell 脚本一次跑完整套用例并汇总结果。改造点课件原做法落地改法激励每个例子手写 force 命令激励写进 testbench参数化时长与周期判定人工看波形、看 results.txtassert断言 非零退出码结果比对肉眼核对数值脚本diff基准文件差异即失败组织单目录多文件同名实体rtl/、tb/、sim/分离网表独立库断言部分大概长这样加在主控进程的关键时间点上-- 常见做法计满 8 个时钟后检查输出是否为 8 wait until rising_edge(CLK); wait for 1 ns; -- 等一拍让输出稳定 assert COUNT 8 report counter8 正向计数错误期望 8实际 integerimage(COUNT) severity error;批量跑的脚本用 GHDL 四步命令循环即可每个用例生成独立的results_case.txt跑完diff一遍基准文件全通过就返回 0。这套东西上 CI 也能跑纯命令行不依赖图形界面。改造过程中有个顺序别搞反先让单用例断言跑通再做参数化和批量。我一开始贪快直接把五个用例塞进一个 testbench 用GENERIC切换结果一个用例失败牵连整轮日志里全是相互干扰的输出排查时间翻了三倍。再补一句工程习惯。这份课件的例子都是整数类型INTEGER RANGE 0 TO 255实际综合到硬件时整数会被映射成多位向量仿真能过不代表综合能过。我一般会把整数端口换成std_logic_vector配unsigned运算仿真和综合用同一套类型少一层映射歧义。这个改动量不大但能省掉「仿真对了、上板错了」这类最难查的问题。从那以后我每写一个新的 testbench都强制先跑一遍复位释放后第一个时钟沿的断言确认复位和时钟真的生效了再往下加激励。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →