[Verilog HDL]第七章 调试用系统任务和常用编译预处理语句
一、调试显示类系统任务
1. $monitor 监控打印任务
1. 核心特性:仿真启动仅初始化一次,参数信号任意跳变自动打印,无需重复调用;
2. 配套控制开关:$monitoron开启监控、$monitoroff关闭监控;
3. 语法模板
$monitor(格式字符串, 参数1,参数2...);4. 底层执行位置:层次化事件队列监控队列,同一时间片所有赋值完成后执行,永远打印更新后最新数值;
5. 使用场景:多模块长期监测信号变化,无需反复写打印语句。
2. Verilog 层次化事件队列
区分display/strobe 底层原理
同一仿真时刻,5 个队列固定执行先后顺序:动态事件队列 → #0 延时队列 → 非阻塞更新队列 → 监控队列 → PLI 队列
- 动态事件队列(最先执行) 执行阻塞赋值
=、计算<=右侧表达式、$display打印、assign 连续赋值; 缺陷:$display会在非阻塞变量更新前执行,容易打印旧值、不定态 x。 - #0 延时队列:存放带 #0 延迟的阻塞赋值,动态队列跑完才执行。
- 非阻塞更新队列:统一批量刷新所有
<=左侧寄存器变量。 - 监控事件队列(最后执行):
$monitor、$strobe,全部变量刷新完毕再输出,一定打印当前周期最终值。 - PLI 队列:C 语言交互接口。
3. display 与 strobe 核心区分
| 任务 | 执行队列 | 打印结果 | 适用场景 |
|---|---|---|---|
| $display | 动态队列 | 赋值未完成,可能打印旧值 | 即时查看中间运算值 |
| $strobe | 监控队列 | 时间片全部赋值结束后打印,结果准确 | 时序逻辑打印,避免时序 bug |
二、时间读取系统函数time/realtime
1. `timescale 时间尺度指令语法
格式:`timescale 时间单位 / 时间精度
约束规则:
- 精度≤单位,单位 / 精度仅支持 1/10/100,单位 fs/ps/ns/us/ms/s。
- 模块内所有 #延时、time/realtime 均以该单位为基准。
- 精度代表最小仿真刻度,延时数值会四舍五入到精度整数倍。 示例
`timescale 10ns/1ns:单位 10ns,精度 1ns,#1.6 等价 16ns。
2. $time 系统函数
- 返回 64 位整数,会根据 timescale 单位四舍五入,存在时间误差。
- 例题:
`timescale 10ns/1ns,#1.6 延时后 $time 输出 2。
3. $realtime 系统函数
- 返回实数,完整保留小数,无取整,精准获取仿真时间。
- 同上例题延时后输出 1.6,适合需要精确时间的仿真场景。
三、仿真流程控制任务finish/stop
1. $finish:退出整个仿真软件,结束全部仿真进程;可带参数输出不同等级调试信息。
2. $stop:仅暂停仿真,保留仿真窗口,可手动继续运行,多用于分段调试波形。
3. 适用场景:测试用例跑完调用$finish,中间关键节点插入$stop断点调试。
四、存储器初始化 $readmemb / $readmemh
1. 基础语法
$readmemb("文件名", 存储器数组):读取二进制文本文件;$readmemh("文件名", 存储器数组):读取十六进制文本文件;- 扩展语法可指定加载起始、结束地址。
2. 数据文件规范
- 数字仅允许对应进制,可包含空白、换行、注释;
@地址标识:十六进制数字,指定下一行数据存入的存储单元下标;- 不定态规则:单 bit x/z 小写,4bit 完整字节全不定输出大写 X/Z;无数据地址默认填充 x。
3. 加载匹配规则
- 未指定起止地址:从存储器最低下标连续填充;
- 文件与代码地址范围不匹配、数据数量不符,仿真直接报报错;
- 仅仿真可用,不能综合硬件电路。
示例代码
reg [7:0] mem[0:15]; initial begin $readmemb("init.dat", mem); end五、随机激励 $random 系统函数
1. 基础用法
{$random}%N:生成 0~N-1 无符号随机整数;拼接 {} 消除符号位; 示例:rand = {$random}%60生成 0~59 随机数。
2. 仿真实战场景
- 随机延时:
# 20 * (1+{$random}%3); - 随机脉冲高低电平、随机输入总线,构建全覆盖测试激励;
- 仅适用于 Testbench 仿真,无法综合生成电路。
完整随机脉冲示例
reg [9:0] dout; integer delay1,delay2; initial begin #10 dout=0; for(k=0;k<100;k=k+1)begin delay1 = 20 * ({$random}%6); delay2 = 20 * (1+{$random}%3); #delay1 dout = 1 << ({$random}%10); #delay2 dout = 0; end end六、编译预处理指令(` 开头,编译阶段执行)
1. 宏定义 `define
- 语法:
`define 宏名 替换字符串;调用`宏名。 - 特性:单纯文本替换,无语法检查,编译报错在展开后才出现。
- 支持层叠嵌套宏,宏名区分大小写,建议大写区分变量。
- 易错坑:
- 宏末尾不能加分号,分号会一同带入替换产生语法错误;
- 运算类宏不加括号,运算优先级错乱。
2. 文件包含 `include
- 作用:将外部.v 完整文本插入当前文件,实现多模块拆分复用;
- 语法:
`include "文件名.v",支持相对 / 绝对文件路径; - 规则:支持多层嵌套包含;可同一行写多个 include;被包含文件宏定义全局生效;
- 工程用途:顶层文件包含所有子模块,简化编译脚本。
3. 时间尺度 `timescale
前文 3.1 完整讲解,全局 / 模块单独定义,多模块可设置不同单位精度。
4. 条件编译ifdef /else / `endif
1. 语法结构:
`ifdef 宏名 程序段1 `else 程序段2 `endif2. 执行逻辑:定义过目标宏则编译第一段,否则编译 else 分支;
3. 工程用途:区分仿真 / 综合两套代码、多版本硬件兼容、屏蔽调试代码;
4. 拓展配套指令:ifndef(未定义则编译)、elsif 多分支。
七、本章重点汇总
1. 打印系统任务核心考点
- 事件队列执行顺序决定打印结果:display即时、strobe/$monitor 同步延时打印最终值。
%m格式符自动打印模块层级路径,多模块调试必备。- x/z 打印固定规则:单 bit 小写、4bit 完整字节全不定大写 X/Z。
2. 时间系统函数易错点
- time受timescale影响会四舍五入,存在仿真时间误差;高精度仿真必须用realtime 实数。
- timescale 精度不能大于时间单位,延时会自动对齐精度整数倍。
3. 存储器与随机激励专用任务
- readmemb/readmemh 仅仿真,文件地址标识 @、进制格式严格匹配,否则加载失败。
- {$random} 消除符号位,生成 0~N-1 随机数,用于构建随机测试激励,不能综合硬件。
4. 四大预处理指令必考区分
- `define 文本直接替换,无语法校验,严禁末尾加分号。
- `include 拼接外部源文件,支持多层嵌套,模块化工程必备。
- `timescale 统一模块仿真单位与精度,影响时间系统函数输出。
- `ifdef 条件编译,一套代码适配仿真、综合多场景,屏蔽调试代码。
5. 通用仿真任务通用规则
- 全部以 $ 开头系统任务仅仿真 Testbench 可用,无法综合生成数字电路。
- 多模块调试优先使用monitor自动监测、strobe 时序打印,规避赋值时序错位打印错误。
- 仿真结束用finish退出软件,中间断点调试使用stop 暂停。
6. 高频踩坑清单
- 混淆display与strobe 时序,时钟沿赋值后打印旧值。
- `define 宏末尾写分号,展开后产生多余分号语法报错。
- timescale 精度大于单位,语法违规。
- $readmem 文件进制与存储器匹配错误、地址范围不匹配导致加载 x。
- 直接使用 $random 不加大括号,出现负数随机数。
- 组合逻辑内混用非阻塞赋值,未把中间变量加入敏感列表,输出延迟一拍。
