文章目录
- SV对verilog的扩展
- 📘 标准文档名称:
- 从SV到仿真
- 通用过程解读
- 实例解读
- SV的仿真过程
- 并行
- 仿真颗粒度
- SV仿真调度
- 调度区域
SV对verilog的扩展
SystemVerilog 和 Verilog 的语法标准由 **IEEE(美国电气和电子工程师协会)**制定,正式文档如下:
📘 标准文档名称:
名称 | 简称 | 最新版本 | 标准编号 | 是否免费 |
---|---|---|---|---|
Verilog | Verilog HDL | IEEE 1364-2005 | IEEE Std 1364-2005 | ❌ 付费 |
SystemVerilog | SystemVerilog HDL | IEEE 1800-2017 | IEEE Std 1800-2017 | ❌ 付费 |
SystemVerilog 最新合并版 | Unified HDL (Verilog + SystemVerilog) | IEEE 1800-2023 | IEEE Std 1800-2023 | ❌ 付费 |
- SystemVerilog(SV)相对于传统 Verilog 的主要扩展和增强,涵盖语法、数据类型、验证特性等方面。
类别 | Verilog 特性 | SystemVerilog 扩展 | 说明 | 参考文献 |
---|---|---|---|---|
数据类型 | reg 、wire | 新增 logic 、bit 、int 、byte 、shortint 、longint 、time 、enum 、struct 、union 、string 、chandle | 更强的类型安全、结构化数据、枚举和字符串支持 | (代码覆盖率原理简介原创 - CSDN博客) (see [1]) |
数组与集合 | 连续向量 reg [7:0] a; | 动态数组 int da[]; 、关联数组 int aa[string]; 、队列 int q[$]; | 支持可变长度和哈希索引结构 | (代码覆盖率原理简介原创 - CSDN博客) (see [2]) |
接口与包 | module 、function 、task | interface 、program 、package 、import /export | 接口封装信号集合,package 封装共享声明 | (代码覆盖率原理简介原创 - CSDN博客) (see [3]) |
模块化 & 生成 | generate 块 | generate 扩展语法、localparam 、typedef | 更灵活的生成语法,本地参数和类型重命名 | (代码覆盖率原理简介原创 - CSDN博客) (see [4]) |
线程 & 时间控制 | always 、initial | always_ff 、always_comb 、always_latch 、unique 、priority | 明确组合/时序/锁存块,优化仿真和综合 | (代码覆盖率原理简介原创 - CSDN博客) (see [5]) |
约束随机化 | 无 | class 、rand 、constraint 、solve...before | 面向验证的随机化对象支持 | (代码覆盖率原理简介原创 - CSDN博客) (see [6]) |
面向对象 | 无 | 支持 class 、继承 extends 、多态、new() 构造函数 | 引入面向对象编程,验证组件化 | (代码覆盖率原理简介原创 - CSDN博客) (see [7]) |
断言 & 覆盖率 | 无 | SVA 断言 assert 、covergroup 、coverpoint 、cross | 内置断言和功能覆盖率建模 | (代码覆盖率原理简介原创 - CSDN博客) (see [8]) |
TLM 接口 | 无 | uvm_sequence_item 、uvm_port 、uvm_export 、uvm_analysis_port | 事务级建模接口,为 UVM 提供通信通道 | (代码覆盖率原理简介原创 - CSDN博客) (see [9]) |
并发扩展 | fork/join | fork...join_any 、fork...join_none -多种 join 语义 | 更灵活的并发控制 | (代码覆盖率原理简介原创 - CSDN博客) (see [10]) |
简要说明:
- 数据类型和数组:SV 增加了丰富的强类型和容器类型,解决了 Verilog 只有
reg
/wire
、固定向量的限制。 - 接口与包:通过
interface
和package
,SV 实现了代码重用与封装,提高了可维护性。 - 线程与时间控制:引入更语义化的
always_ff
等,帮助综合工具和仿真工具区分不同用途。 - 面向对象 & 约束随机:SV 将验证带入语言层面,结合 UVM 形成强大的验证生态。
- 断言 & 覆盖率:SystemVerilog Assertions (SVA) 和功能覆盖率让验证更加系统化。
- 事务级建模 (TLM):提供通用接口,让组件间事务通信高度解耦,支撑 UVM 框架。
从SV到仿真
通用过程解读
- 编译
语法和语义分析 , 进行编译 , 编译出 中间文件
- 建模
按照设计集成关系, 组成顶层模块1. 模块例化2. 接口例化3. 程序例化4. 层次集成5. 计算参数6. 解决层次信号引用7. 建立模块连接
// 类似C代码的link阶段
- 仿真
建立RTL模型和参数验证环境, 以周期驱动(cycle-driven)或事件驱动(event-driven)的方式进行仿真
实例解读
- Icarus Verilog(
iverilog
), VCS 和 Verilator 的典型使用流程
阶段 | vcs | verilator | iverilog |
---|---|---|---|
编译(compilation) | .sv → .o | .sv → .o | iverilog -g2005-sv -o tb.vvp <files>.sv |
建模(elaboration) | .o → simv | .o → obj_dir/V$(TB_NAME) | 隐式在生成 tb.vvp 时完成,不再单独产物 |
仿真(simulation) | ./simv | ./obj_dir/V$(TB_NAME) | vvp tb.vvp |
与 VCS 和 Verilator 不同,
Icarus Verilog **不生成可执行 ELF**,也不生成 C++ 代码。
它在编译阶段就完成了 elaboration,将所有模块实例化和连接,并将结果编码进 `tb.vvp`。
无需单独生成可执行文件(`.o` 或 `simv`)。
SV的仿真过程
并行
SystemVerilog 采用 模块(module
) 作为设计的基本单元,整个电路从 顶层模块 开始,逐层实例化与端口互联,最终形成完整的层次化结构。
在 SV世界中, 仿真一旦运行起来, 一个module 就类似于 软件世界中的 一个线程(不区分线程进程)
那么就必然有线程之间的同步与通信1. 信号的变化(事件触发) // @(posedge clk)2. 对特定事件的等待(时钟周期) // wait 3. 延时(固定延时) // #200
一般来说, module 中的语句都是顺序的, 但是如果你想 并行, 就需要用fork 关键字 来创建 并行结构(即创建新线程)即 软件世界中的 一个线程 在硬件世界中的类比为 // 拓展知识 请查看 https://blog.csdn.net/u011011827/article/details/1474447921. module2. initial 包围的 硬件代码 3. always 包围的 硬件代码2. fork 包围的 硬件代码
仿真颗粒度
仿真需要指定 仿真时的 时间单位和精度不管是 用户TB ,还是设计指定的 时间都要 以 timeprecision 算(四舍五入)出来.
搞 timeunit 这个关键字,只是为了 代码的可读性 : 让用户无需每次都写带单位的延时,只需写 #1 即可代表 #(1 * timeunit)
一般 timeprecision 指定的单位 比 timeunit 小
timeprecision 不是越小越好,太细反而开销大(调度器要管理的时间槽变多,导致仿真慢). // 这个要解读 调度, 来深入理解
合适的 timeprecision看设计的最快时钟——假如最高 500 MHz(周期 2ns),那么 timeprecision 设 100ps 就够(20 个步)。看需要模拟的最小延时——如果你要模拟 #0.5ns 这类延时,就要精度细于 500ps;否则会被舍入。按 10 的级数对齐——常用 1ns/100ps、1ns/10ps、100ps/10ps 这样的十倍关系,既好计算又利于读写。
SV仿真调度
sv代码中module是并行的,但是cpu(仿真sv代码的时候)是顺序执行
那么在 0 时刻 ,有很多 并行代码(假设有10条语句) 需要仿真那么 谁先做谁后做呢 , 这就是 仿真调度 要考虑的事情部分规则:时间片 : timeprecision指定的单位 // 如果 timeprecision = 100ps , 那么带 时间槽所在的时刻 只能是 100ps的倍数同槽不同事:同一槽位的多条语句,靠 “事件区域” + “申明顺序” 决定先后。效率至上 :调度器只跳到下一个有事的槽位,不会浪费时间检查空槽 // 即 存在一种可能, 200ps 时, 没有事件位于这一时刻, 那么 根本就不会 检查 200ps 是否有 事情要做 // 即 不会 在 200ps 这个刻度 浪费 一丁点cpu时间
- 同一时间槽内的执行次序就是“仿真调度”
在每个离散的时间点(时间槽)上,仿真器会把所有待执行的事件分到几个“事件区域”(Active、Inactive、NBA、Monitor…),再在各区域内按声明顺序执行。这套从区域到区域、从语句到语句的调度机制,就称作仿真调度(event scheduling)。 - 仿真调度是为模拟并行硬件而设计的
真实 CPU 是串行执行指令的,而硬件模块在同一时刻是并行更新欧。仿真器内部用“调度”来用顺序算法准确地再现硬件的并行行为——保证同时发生的并行事件能在同一个时间点“好像同时”地被执行,只是按照调度规则分批跑完。 - 实际硬件里没有“调度器”这层概念
硬件的寄存器、门电路、互连网是真正同时更新的,不存在“先做 A 再做 B”的软件调度。调度只是仿真环境里用来序列化并行行为的实现细节。 - 仿真的时候 有 仿真调度, 但是硬件上没有, 怎么保证在仿真时能过的代码在硬件上一定没问题呢?
- 可综合子集:设计时只用可综合的 SV/Verilog 语法(符合综合工具的语义),这样仿真行为和综合后硬件逻辑在功能上是等价的。
- 静态时序分析 (STA):综合后用 STA 工具检查硬件的时序收敛,保证所有信号路径在目标时钟下都能按预期收敛。
- 门级仿真:在部分关键场景下,还会跑门级网表加 SDF back-annotation 仿真,验证时序与 RTL 仿真的一致性。
通过以上流程,仿真“看得见”的并行逻辑,以及综合后验证的时序约束,都能确保最终硅片上真实并行电路的正确性。
调度区域
SystemVerilog 离散事件调度器在每个时间槽(time slot)内依次执行的主要仿真调度区域(也称为“事件区域”)及其作用:
区域序号 | 调度区域 | 英文名 / 缩写 | 执行内容示例 | 说明 |
---|---|---|---|---|
1 | Active | Active Region | always 、initial 块中无延时语句之后的语句 | 执行所有因为当前时间到达而“就绪”的过程后的第一批语句 |
2 | Inactive | Inactive Region | disable fork 、@ (事件触发) | 处理在 Active 区域产生的新就绪过程,但不再执行延时、NBATasks |
3 | NBA | Non-Blocking Assigns | 所有<= 非阻塞赋值更新 | 收集并一次性更新非阻塞赋值,避免与 Active 中的顺序冲突 |
4 | Monitor | Monitor Region | uvm_monitor 、$monitor 、覆盖率采集、断言 | 观察信号变化,不影响驱动;常用于更新覆盖率、断言检查 |
5 | Observed | Observed Region | @(<signal>) 等带延时或事件触发的观察者 | 用于“观察”在 Monitor 区域中改变的信号或变量 |
6 | Reactive | Reactive Region | PLI/VPI/VHPI 回调、DPI-C 调用 | 外部仿真接口的回调处理,保证与内部更新同步 |
调度流程简述:
在仿真器中,每到一个时间槽,依次从 Active → Inactive → NBA → Monitor → Observed → Reactive 执行对应区域的所有就绪事件;然后仿真时间跳到下一个有事件的槽位。
这样设计可以:
- 将顺序、非阻塞赋值、监视、外部回调等不同种类的操作有序分隔,
- 保证同一时间点内的并行硬件行为被准确、可预测地“序列化”执行。