[Verilog HDL]第七章 调试用系统任务和常用编译预处理语句

一、调试显示类系统任务

1. $monitor 监控打印任务

1. 核心特性:仿真启动仅初始化一次,参数信号任意跳变自动打印,无需重复调用;

2. 配套控制开关:$monitoron开启监控、$monitoroff关闭监控;

3. 语法模板

$monitor(格式字符串, 参数1,参数2...);

4. 底层执行位置:层次化事件队列监控队列,同一时间片所有赋值完成后执行,永远打印更新后最新数值;

5. 使用场景:多模块长期监测信号变化,无需反复写打印语句。

2. Verilog 层次化事件队列

区分display/strobe 底层原理

同一仿真时刻,5 个队列固定执行先后顺序:动态事件队列 → #0 延时队列 → 非阻塞更新队列 → 监控队列 → PLI 队列

  1. 动态事件队列(最先执行) 执行阻塞赋值=、计算<=右侧表达式、$display打印、assign 连续赋值; 缺陷:$display会在非阻塞变量更新前执行,容易打印旧值、不定态 x。
  2. #0 延时队列:存放带 #0 延迟的阻塞赋值,动态队列跑完才执行。
  3. 非阻塞更新队列:统一批量刷新所有<=左侧寄存器变量。
  4. 监控事件队列(最后执行):$monitor$strobe,全部变量刷新完毕再输出,一定打印当前周期最终值。
  5. PLI 队列:C 语言交互接口。

3. display 与 strobe 核心区分

任务执行队列打印结果适用场景
$display动态队列赋值未完成,可能打印旧值即时查看中间运算值
$strobe监控队列时间片全部赋值结束后打印,结果准确时序逻辑打印,避免时序 bug

二、时间读取系统函数time/realtime

1. `timescale 时间尺度指令语法

格式:`timescale 时间单位 / 时间精度

约束规则:

  1. 精度≤单位,单位 / 精度仅支持 1/10/100,单位 fs/ps/ns/us/ms/s。
  2. 模块内所有 #延时、time/realtime 均以该单位为基准。
  3. 精度代表最小仿真刻度,延时数值会四舍五入到精度整数倍。 示例`timescale 10ns/1ns:单位 10ns,精度 1ns,#1.6 等价 16ns。

2. $time 系统函数

  1. 返回 64 位整数,会根据 timescale 单位四舍五入,存在时间误差。
  2. 例题:`timescale 10ns/1ns,#1.6 延时后 $time 输出 2。

3. $realtime 系统函数

  1. 返回实数,完整保留小数,无取整,精准获取仿真时间。
  2. 同上例题延时后输出 1.6,适合需要精确时间的仿真场景。

三、仿真流程控制任务finish/stop

1. $finish:退出整个仿真软件,结束全部仿真进程;可带参数输出不同等级调试信息。

2. $stop:仅暂停仿真,保留仿真窗口,可手动继续运行,多用于分段调试波形。

3. 适用场景:测试用例跑完调用$finish,中间关键节点插入$stop断点调试。

四、存储器初始化 $readmemb / $readmemh

1. 基础语法

  1. $readmemb("文件名", 存储器数组):读取二进制文本文件;
  2. $readmemh("文件名", 存储器数组):读取十六进制文本文件;
  3. 扩展语法可指定加载起始、结束地址。

2. 数据文件规范

  1. 数字仅允许对应进制,可包含空白、换行、注释;
  2. @地址标识:十六进制数字,指定下一行数据存入的存储单元下标;
  3. 不定态规则:单 bit x/z 小写,4bit 完整字节全不定输出大写 X/Z;无数据地址默认填充 x。

3. 加载匹配规则

  1. 未指定起止地址:从存储器最低下标连续填充;
  2. 文件与代码地址范围不匹配、数据数量不符,仿真直接报报错;
  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. 仿真实战场景

  1. 随机延时:# 20 * (1+{$random}%3)
  2. 随机脉冲高低电平、随机输入总线,构建全覆盖测试激励;
  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

  1. 语法:`define 宏名 替换字符串;调用`宏名。
  2. 特性:单纯文本替换,无语法检查,编译报错在展开后才出现。
  3. 支持层叠嵌套宏,宏名区分大小写,建议大写区分变量。
  4. 易错坑:
    • 宏末尾不能加分号,分号会一同带入替换产生语法错误;
    • 运算类宏不加括号,运算优先级错乱。

2. 文件包含 `include

  1. 作用:将外部.v 完整文本插入当前文件,实现多模块拆分复用;
  2. 语法:`include "文件名.v",支持相对 / 绝对文件路径;
  3. 规则:支持多层嵌套包含;可同一行写多个 include;被包含文件宏定义全局生效;
  4. 工程用途:顶层文件包含所有子模块,简化编译脚本。

3. 时间尺度 `timescale

前文 3.1 完整讲解,全局 / 模块单独定义,多模块可设置不同单位精度。

4. 条件编译ifdef /else / `endif

1. 语法结构:

`ifdef 宏名 程序段1 `else 程序段2 `endif

2. 执行逻辑:定义过目标宏则编译第一段,否则编译 else 分支;

3. 工程用途:区分仿真 / 综合两套代码、多版本硬件兼容、屏蔽调试代码;

4. 拓展配套指令:ifndef(未定义则编译)、elsif 多分支。

七、本章重点汇总

1. 打印系统任务核心考点

  1. 事件队列执行顺序决定打印结果:display即时、strobe/$monitor 同步延时打印最终值。
  2. %m格式符自动打印模块层级路径,多模块调试必备。
  3. x/z 打印固定规则:单 bit 小写、4bit 完整字节全不定大写 X/Z。

2. 时间系统函数易错点

  1. time受timescale影响会四舍五入,存在仿真时间误差;高精度仿真必须用realtime 实数。
  2. timescale 精度不能大于时间单位,延时会自动对齐精度整数倍。

3. 存储器与随机激励专用任务

  1. readmemb/readmemh 仅仿真,文件地址标识 @、进制格式严格匹配,否则加载失败。
  2. {$random} 消除符号位,生成 0~N-1 随机数,用于构建随机测试激励,不能综合硬件。

4. 四大预处理指令必考区分

  1. `define 文本直接替换,无语法校验,严禁末尾加分号。
  2. `include 拼接外部源文件,支持多层嵌套,模块化工程必备。
  3. `timescale 统一模块仿真单位与精度,影响时间系统函数输出。
  4. `ifdef 条件编译,一套代码适配仿真、综合多场景,屏蔽调试代码。

5. 通用仿真任务通用规则

  1. 全部以 $ 开头系统任务仅仿真 Testbench 可用,无法综合生成数字电路。
  2. 多模块调试优先使用monitor自动监测、strobe 时序打印,规避赋值时序错位打印错误。
  3. 仿真结束用finish退出软件,中间断点调试使用stop 暂停。

6. 高频踩坑清单

  1. 混淆display与strobe 时序,时钟沿赋值后打印旧值。
  2. `define 宏末尾写分号,展开后产生多余分号语法报错。
  3. timescale 精度大于单位,语法违规。
  4. $readmem 文件进制与存储器匹配错误、地址范围不匹配导致加载 x。
  5. 直接使用 $random 不加大括号,出现负数随机数。
  6. 组合逻辑内混用非阻塞赋值,未把中间变量加入敏感列表,输出延迟一拍。