EVP 仿真验证平台
基于 Verilator 的高性能仿真环境:把芯片 RTL 编译成可执行虚拟原型,在没有芯片、没有 FPGA 板卡的条件下运行并调试真实固件。
一、平台定位
EVP(可执行虚拟原型)是以 Verilator 为执行引擎的 MCU 可执行虚拟原型平台。 它不是重新实现一颗简化 CPU,而是在既有芯片 RTL 的基础上,替换工艺相关的存储器与模拟硬宏, 并在芯片顶层增加一层「主机—芯片」适配层,使主机软件能够以时钟周期粒度驱动、 调试和观察整个 SoC。
二、平台架构
平台自上而下分为主机调试软件、定宽握手端口、RTL 可执行模型与芯片 RTL 层次四层。 主机侧的调试命令并不直接窥探仿真器内部信号,而是作为硬件调试协议进入 RTL, 最终由片内调试模块执行——这保证了仿真环境中的调试路径与真实芯片保持一致, 而不是另开一条绕过 SoC 安全与调试机制的后门。
| 层次 | 职责 |
|---|---|
| 主机运行时 | 导出调用接口、后台线程推进时钟、执行复位序列、控制波形记录 |
| 调试协议层 | 寄存器 / 内存 / 断点命令的分层封装与组帧、对齐补齐 |
| 握手端口 | 定宽请求—应答通道,取代实体 JTAG 的物理链路 |
| 仿真顶层 | 时钟、复位、串口桥、串行 Flash 模型、低速时钟分频 |
| 芯片 RTL 层次 | CPU、总线互连、ROM / SRAM、Cache、片内调试模块与全部 SoC 外设 RTL |
2.1 工艺依赖的仿真替代
平台通过独立的文件列表引入一组仿真专用替换件,把不能直接用 Verilator 执行的工艺存储器 与模拟 IP 换成可仿真实现,同时保留真实的总线与控制逻辑:
| 被替代对象 | 替代方式 |
|---|---|
| ROM 硬宏 | 保留控制器的地址、时序与校验逻辑,后端换为可仿真存储器,启动镜像在初始化时由文本文件注入 |
| 工艺 SRAM 宏 | 行为级可仿真存储器,接口时序与原宏保持一致 |
| 时钟复位生成单元 | 提供确定性的时钟与复位序列,去除锁相环等模拟依赖 |
| 晶振与模拟硬宏 | 以理想时钟源与行为模型替代模拟电路 |
| 外部串行 Flash 器件 | 完整命令状态机模型:页编程、扇区与整片擦除、单/双/四线快速读、读 ID 与状态寄存器,可验证控制器的协议时序而非仅提供内存数组 |
| 串口物理连线 | 主机字节流与串行波形双向转换,自动估计波特率分频 |
三、构建与集成
3.1 编译仿真模型
在 Linux 或 WSL 中准备 Bash、GNU Make、C++ 编译器、线程开发库与 Verilator,然后执行构建脚本。 脚本提供三种入口:默认构建标准仿真模型、按开关构建整芯片版本、以及只清理构建产物; 它按自身位置定位源码,可从仓库内外任意工作目录调用。
构建流程是「把 RTL 编译成 C++」的静态仿真方式:Verilator 将 SystemVerilog 转换为 C++, 连同主机侧封装一起编译链接为共享库;运行时每次求值都计算电路在当前输入与时钟边沿下的新状态, 不依赖事件驱动 HDL 仿真器作为运行时。
脚本会自动识别可用 CPU 线程与容器配额并限制并行度。大型 RTL 首次构建建议先降低线程数, 以免占用过多内存;构建日志同时输出到终端与日志文件,便于回溯。
| 项目 | 分类 | 作用 |
|---|---|---|
| 仿真共享库 | RTL 编译输出 | Verilator 生成的 RTL 模型与主机封装,导出启动、停止与串口收发接口 |
| 中间构建产物 | 编译中间件 | Verilator 生成的 C++ 源码与目标文件,仅用于重建共享库 |
| 启动与 Flash 镜像 | 运行输入 | ROM 启动镜像与串行 Flash 模型输入,按相对路径从运行目录读取 |
| 波形文件 | 运行输出 | 启用波形记录后生成,供波形查看器离线分析 |
3.2 集成到调试服务
调试服务把仿真模型当作预构建的外部依赖:它不编译仿真源码, 只检查并链接共享库。整个流程分三步:
- 在独立的仿真工程中构建共享库;
- 将产物投放到调试服务的固定依赖目录;
- 用启用仿真支持的开关重新编译调试服务。
该开关会同时编入仿真传输层、波形控制命令与串口转发服务。仅修改主机侧源码时可用增量重编, 但首次启用仿真支持、切换开关或改动构建配置后必须执行完整编译。
仿真平台仅支持 Linux / WSL,Windows 发行包不包含仿真模型。 运行时动态加载器还须能找到共享库,未安装到系统库路径时需显式设置库搜索路径。
四、调试调用链
调试服务不启动外部仿真进程,而是把仿真共享库装载到自身进程内。仿真与实体硬件的选择点 位于调试适配器:选择虚拟适配器走进程内传输,选择实体适配器走物理 JTAG—— 两者复用同一份目标配置与同样的核心编号,调试脚本无需分叉维护。
每个调试事务由传输层封装为起止标记 + 调试载荷 + 校验的帧,写入与仿真库共享的 缓冲区并置位长度标志;后台仿真线程逐字完成握手,把帧送入片内调试模块, 响应再沿同一通道原路回填给 GDB。
五、使用方式
5.1 启动仿真与多核调试
启动调试服务时选择虚拟适配器并沿用通用目标配置,即可拉起仿真,无需任何真实调试器与硬件。
调试服务初始化目标时会自动启动仿真模型。GDB 端口分配与实体调试完全一致, 连接后即可执行暂停、下载、断点、继续等常规操作;也可用单核配置只启动其中某一个核心, 缩短仿真启动时间。
5.2 串口终端
启用仿真支持后会编入独立的串口转发服务,把模型的串口数据桥接到一个 TCP 端口, 与调试服务自身的命令行端口互不冲突。用普通 Telnet 客户端连接该端口, 即可像操作真实串口终端一样查看固件打印、与 Shell 交互。
端口可在配置阶段修改或整体关闭。当前实现固定转发首个串口,建议单客户端使用。
5.3 波形控制
波形录制通过调试服务命令行的一对开关命令随时打开与关闭,波形文件输出到模型运行目录, 可用通用波形查看器打开。
波形跟踪层次很深,长时间全量记录会显著增加运行时间与存储开销。 建议只在定位目标软件片段时开启波形窗口,其余时间关闭以保持仿真速度。
六、能力边界
平台面向软件 bring-up 与调试,使用时应清楚其精度边界,不能单独作为签核依据:
| 项目 | 说明 |
|---|---|
| 真实性范围 | CPU、总线与多数外设仍是 RTL;工艺存储器、模拟硬宏与外部器件为仿真变体或行为模型。晶振、锁相环、模数转换与 Flash 的真实电气特性不在模拟范围内。 |
| 确定性取舍 | 模型以执行速度优先,未初始化态、时序检查与断言不做严格验证;适合软件 bring-up,不能替代签核级验证流程。 |
| 并发约束 | 后台仿真线程与前台传输层共享缓冲区;多客户端或串口与调试并发调用时需自行补充同步约束。 |
| 握手无超时 | 调试事务等待应答与长度标志清零,没有超时或取消机制;RTL 死锁或复位异常时前台可能永久等待。 |
| 运行目录敏感 | 启动镜像与 Flash 镜像按相对路径加载,运行目录错误会导致镜像未加载或行为不符预期。 |
七、常见问题
| 现象 | 优先检查 |
|---|---|
| 构建时提示依赖目录不存在 | 仿真工程未放到调试服务约定的依赖路径下 |
| 构建时提示共享库缺失 | 需先在仿真工程中完成独立构建,并把产物投放到依赖目录 |
| 启动时报动态库加载失败 | 设置库搜索路径,或检查共享库的架构与依赖是否与宿主一致 |
| GDB 操作永久等待 | 依次检查握手信号、RTL 是否持续推进时钟、片内调试模块是否退出复位、帧标记与校验是否匹配 |
| 串口无数据 | 确认已启用仿真支持、串口转发端口未被关闭、模型已实现对应串口 |
| 复位或取指异常 | 检查运行目录中的启动镜像与 Flash 镜像是否就位 |
先观察复位握手与 ROM 取指,再启用串口或调试通道——这是最短的分层定位路径。
