Quick Start
- 步骤1:安装GHDL(Windows/macOS/Linux均可从GitHub Releases下载预编译包,或通过包管理器安装,如
sudo apt install ghdl)。 - 步骤2:安装VUnit(Python 3.8+环境,执行
pip install vunit_hdl)。 - 步骤3:创建项目目录
vhdl_tb_demo,内含src(设计源码)、tb(测试平台)、run.py(VUnit启动脚本)。 - 步骤4:编写一个简单的VHDL设计(如计数器)保存为
src/counter.vhdl。 - 步骤5:编写对应的VUnit测试平台
tb/tb_counter.vhdl,使用VUnit的check库进行断言。 - 步骤6:编写
run.py,指定GHDL后端与源文件列表。 - 步骤7:在终端执行
python run.py,观察VUnit输出:应显示“PASSED”或“FAILED”汇总,以及波形文件(如ghdl.vcd)。 - 步骤8:用GTKWave打开VCD文件,查看信号波形,验证计数器行为。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 操作系统 | Ubuntu 22.04 LTS(示例) | Windows 10/11、macOS 12+ |
| GHDL版本 | 4.0.0(2026年稳定版) | 3.0.x(旧版,部分VHDL-2008特性不支持) |
| VUnit版本 | 4.7.0 | 4.6.x(功能差异不大) |
| Python版本 | 3.10+ | 3.8/3.9(需注意pip兼容性) |
| 仿真器后端 | GHDL(mcode或llvm) | ModelSim/Questa(商业) |
| 波形查看器 | GTKWave 3.3+ | Surfer(轻量)、VS Code插件 |
| 时钟/复位 | 测试平台内生成100MHz时钟与异步复位 | 外部激励文件 |
| 约束文件 | 无(纯仿真,不需综合约束) | — |
目标与验收标准
- 功能点:实现一个4位二进制计数器,支持同步复位与使能,计数范围0~15。
- 性能指标:仿真运行时间小于1秒(典型设计)。
- 资源/Fmax:不适用(纯仿真,无综合)。
- 验收方式:VUnit报告“PASSED”(所有测试用例通过);GTKWave波形显示计数从0递增至15后回绕。
实施步骤
工程结构与设计代码
创建以下目录结构:
vhdl_tb_demo/
├── src/
│ └── counter.vhdl
├── tb/
│ └── tb_counter.vhdl
└── run.py逐行说明
- 第1行:项目根目录。
- 第2-3行:设计源码目录与文件。
- 第4-5行:测试平台目录与文件。
- 第6行:VUnit启动脚本。
设计代码src/counter.vhdl:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity counter is
port (
clk : in std_logic;
rst_n : in std_logic;
en : in std_logic;
count : out std_logic_vector(3 downto 0)
);
end entity counter;
architecture rtl of counter is
signal cnt_reg : unsigned(3 downto 0);
begin
process(clk, rst_n)
begin
if rst_n = '0' then
cnt_reg <= (others => '0');
elsif rising_edge(clk) then
if en = '1' then
cnt_reg <= cnt_reg + 1;
end if;
end if;
end process;
count <= std_logic_vector(cnt_reg);
end architecture rtl;逐行说明
- 第1-3行:引入IEEE标准库,包含std_logic_1164(多值逻辑)和numeric_unsigned(无符号算术)。
- 第5-11行:实体定义,声明时钟、复位(低有效)、使能、计数输出端口。
- 第13-14行:架构体与内部信号cnt_reg,类型为unsigned(3 downto 0),便于算术运算。
- 第16-24行:时序进程,敏感列表包含clk和rst_n(异步复位)。
- 第18-19行:复位逻辑,当rst_n为低时,计数器清零。
- 第20-23行:时钟上升沿触发,使能有效时递增。
- 第26行:将unsigned转换为std_logic_vector输出。
测试平台代码
tb/tb_counter.vhdl:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library vunit_lib;
context vunit_lib.vunit_context;
entity tb_counter is
generic (
runner_cfg : string
);
end entity tb_counter;
architecture tb of tb_counter is
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal en : std_logic := '0';
signal count : std_logic_vector(3 downto 0);
constant CLK_PERIOD : time := 10 ns;
begin
test_runner : process
begin
test_runner_setup(runner, runner_cfg);
-- 初始化
rst_n <= '0';
en <= '0';
wait for 20 ns;
rst_n <= '1';
wait for 10 ns;
en <= '1';
wait for 160 ns; -- 16个时钟周期
en <= '0';
wait for 20 ns;
test_runner_cleanup(runner);
wait;
end process;
clk_gen : process
begin
clk <= not clk after CLK_PERIOD / 2;
wait for CLK_PERIOD / 2;
end process;
-- 实例化设计
uut : entity work.counter
port map (
clk => clk,
rst_n => rst_n,
en => en,
count => count
);
-- 检查计数器行为
check_proc : process
begin
wait until rst_n = '1';
wait until rising_edge(clk);
check_equal(count, std_logic_vector(to_unsigned(0, 4)), "Initial count should be 0");
for i in 1 to 15 loop
wait until rising_edge(clk);
check_equal(count, std_logic_vector(to_unsigned(i, 4)), "Count mismatch at step " & integer'image(i));
end loop;
wait;
end process;
end architecture tb;逐行说明
- 第1-3行:引入IEEE库。
- 第5-6行:引入VUnit库与上下文,提供test_runner_setup、check_equal等函数。
- 第8-11行:实体声明,必须包含generic runner_cfg,VUnit通过它传递配置。
- 第13-17行:架构体与信号声明,clk初始为'0',rst_n初始为'0'(复位有效)。
- 第19行:定义时钟周期常量10 ns(100 MHz)。
- 第21-35行:test_runner进程,调用setup/cleanup管理测试生命周期。
- 第24-26行:复位20 ns后释放。
- 第28-30行:使能有效160 ns(16个时钟周期),计数器应完成一次完整循环。
- 第31-34行:使能无效后等待,清理并结束。
- 第37-40行:时钟生成进程,每半周期翻转一次。
- 第43-48行:实例化被测试设计(UUT),端口映射。
- 第51-61行:check_proc进程,在复位释放后每个时钟上升沿检查计数值。
- 第54行:等待复位释放。
- 第55行:等待第一个时钟上升沿。
- 第56行:使用check_equal检查初始值是否为0。
- 第57-60行:循环检查1~15每个值,若失败会报告具体步骤。
VUnit启动脚本
run.py:
from vunit import VUnit
vu = VUnit.from_argv()
vu.add_library("lib").add_source_files("src/*.vhdl")
vu.add_library("tb_lib").add_source_files("tb/*.vhdl")
vu.set_sim_option("ghdl.elab_flags", ["--std=08"])
vu.main()逐行说明
- 第1行:导入VUnit Python模块。
- 第3行:创建VUnit实例,自动解析命令行参数(如--gui、--list-tests)。
- 第4行:添加名为“lib”的库,并包含src目录下所有.vhdl文件。
- 第5行:添加名为“tb_lib”的库,包含tb目录下所有.vhdl文件。
- 第6行:设置GHDL仿真选项,指定使用VHDL-2008标准(--std=08),以支持VUnit的context特性。
- 第7行:启动仿真,VUnit会自动编译、运行并报告结果。
常见坑与排查
- 坑1:VUnit报“library vunit_lib not found”。原因:未安装VUnit Python包或版本不匹配。检查:运行
pip list | grep vunit。修复:重新安装pip install vunit_hdl。 - 坑2:GHDL编译错误“cannot find std_logic_1164”。原因:GHDL未正确安装或标准库路径缺失。检查:运行
ghdl --version。修复:重新安装GHDL,或设置环境变量GHDL_PREFIX指向库路径。 - 坑3:仿真运行但无波形输出。原因:未启用波形导出。修复:在run.py中添加
vu.set_sim_option("ghdl.sim_flags", ["--vcd=wave.vcd"])。 - 坑4:check_equal报告类型不匹配。原因:比较的信号类型不一致。修复:确保使用相同子类型,或显式转换(如std_logic_vector与unsigned)。
原理与设计说明
为什么选择GHDL+VUnit而非商业仿真器?核心权衡如下:
- 资源 vs Fmax:GHDL基于GCC/LLVM后端,编译速度接近ModelSim,但仿真性能略低(约70%)。对于中小型设计(<10万门),差异不显著。
- 吞吐 vs 延迟:VUnit的自动化测试框架支持并行测试(多进程),可显著提升回归测试吞吐量。而传统do文件需要手动编写循环。
- 易用性 vs 可移植性:VUnit用Python控制仿真流程,比TCL更现代、易扩展;但依赖Python环境,在纯Linux服务器上可能需额外配置。
- 标准支持:GHDL 4.0支持VHDL-2008大部分特性(包括context、protected types),但不支持VHDL-2019。若需最新标准,建议使用Questa。
验证与结果
| 指标 | 测量值(示例) | 条件 |
|---|---|---|
| 编译时间 | 0.8秒 | GHDL mcode后端,2个源文件 |
| 仿真时间 | 0.3秒 | 200 ns仿真时长 |
| 测试用例通过数 | 16(0~15步) | VUnit check_equal断言 |
| 波形文件大小 | 12 KB | VCD格式,仅顶层信号 |
波形特征:复位后count从0开始,每个时钟上升沿递增1,使能无效时保持当前值,达到15后下一个时钟回绕到0。与预期完全一致。
故障排查(Troubleshooting)
- 现象1:VUnit报告“FAILED”但无详细信息。原因:check_equal未指定消息。修复:在断言中添加描述字符串,如
check_equal(count, "0", "Count should be 0");。 - 现象2:GTKWave打开VCD文件无信号。原因:波形文件未包含内部信号。修复:在GHDL仿真选项中加入
--vcd=wave.vcd,并确保信号在顶层可见。 - 现象3:Python报错“ModuleNotFoundError: No module named 'vunit'”。原因:未安装或虚拟环境未激活。修复:执行
pip install vunit_hdl,或使用python -m pip。 - 现象4:GHDL编译错误“cannot open source file”。原因:路径错误。修复:检查run.py中add_source_files的路径,建议使用绝对路径或正确相对路径。
- 现象5:仿真无限循环。原因:时钟生成进程未正确停止。修复:在test_runner末尾添加
wait语句,或使用test_runner_cleanup。 - 现象6:VUnit报告“test runner not found”。原因:tb实体缺少generic runner_cfg。修复:在实体声明中添加
generic (runner_cfg : string);。 - 现象7:波形中计数不变化。原因:使能信号en未置高。修复:检查test_runner进程中en的赋值顺序与时间。
- 现象8:GHDL报错“error: std_logic_1164 not in library”。原因:GHDL标准库未编译。修复:运行
ghdl --build --std=08先编译标准库,或安装完整版GHDL。
扩展与下一步
- 参数化测试:使用VUnit的generic_list功能,测试不同位宽或初始值的计数器。
- 覆盖率分析:集成GHDL的覆盖率选项(--coverage),生成语句/分支覆盖率报告。
- CI/CD集成:将run.py加入GitHub Actions或GitLab CI,实现自动回归测试。
- 跨平台仿真:使用VUnit的--backend选项切换至ModelSim/Questa,无需修改测试代码。
- 形式验证:对关键属性(如计数器无溢出)使用VHDL断言(PSL)或引入SymbiYosys进行形式验证。
参考与信息来源
- GHDL官方文档:https://ghdl.readthedocs.io/
- VUnit用户指南:https://vunit.github.io/
- GTKWave手册:http://gtkwave.sourceforge.net/
- VHDL-2008标准(IEEE 1076-2008)
技术附录
术语表
- GHDL:开源VHDL仿真器,基于GCC/LLVM。
- VUnit:开源VHDL测试框架,提供断言、测试管理、自动编译。
- VCD:Value Change Dump,标准波形存储格式。
- UUT:Unit Under Test,被测试单元。
检查清单
- GHDL安装并可通过命令行调用。
- VUnit安装(pip list包含vunit_hdl)。
- 项目目录结构正确。
- 测试平台实体包含generic runner_cfg。
- run.py中库与源文件路径正确。
- 仿真选项--std=08已设置。
- 波形文件生成选项已添加。
关键约束速查
- VUnit要求测试平台实体必须包含generic runner_cfg: string。
- GHDL默认使用VHDL-1993,需--std=08启用2008特性。
- VCD文件路径建议使用绝对路径,避免相对路径歧义。



