一、`timescale介绍
在 SystemVerilog 中,`timescale 是一个编译器指令,用于定义仿真过程中的时间单位(Time Unit)和时间精度(Time Precision)。
它直接决定了代码中 #10 这种延迟到底代表多长时间。
1. 语法格式
`timescale <时间单位> / <时间精度>
- 时间单位 (Time Unit):定义数字延迟(如 #10)代表的具体时间。
- 时间精度 (Time Precision):定义仿真器处理时间的最小刻度。仿真器会将所有时间值舍入到该精度的整数倍。
合法的时间单位和精度: 1s, 100ms, 10ms, 1ms, 100us, 10us, 1us, 100ns, 10ns, 1ns, 100ps, 10ps, 1ps, 100fs, 10fs, 1fs。
cpp
`timescale 1ns / 1ps
module tb;
reg clk;
initial begin
clk = 0;
// #5 表示延迟 5 * 1ns = 5ns
// 精度是 1ps,所以可以写 #5.001 (5001ps)
#5.0005;
clk = 1;
end
endmodule
2. 注意事项
A. 单位必须大于或等于精度
- 1ns / 1ps 是合法的,但 1ps / 1ns 是非法的。精度必须比单位更细。
3. `timescale作用范围
`timescale 指令是**基于编译顺序(Compilation Order)**生效的:
如果一个文件没有定义 `timescale,它会继承上一个文件的设置。
一旦编译器在解析过程中遇到了一个 `timescale 指令,它就会对之后所有解析到的模块(module)、接口(interface)等生效,直到遇到下一个 `timescale 指令将其覆盖。
- 文件 A (1ns/1ps): 定义了 timescale。
- 文件 B (无定义): 如果在编译列表中紧随 A 之后,B 将继承 A 的 1ns/1ps。
- 文件 C (1ps/1fs): 重新定义后,后续文件将按 1ps/1fs 执行。
如果所有文件都没定义,仿真器会使用默认值(通常是 1ns/1ns 或 1ns/1ps)。
4. 单位与精度的关系
| 设置 | 延迟代码 | 实际仿真延迟 | 舍入说明 |
|---|---|---|---|
| ```timescale 1ns / 1ns`` | #1.4 |
1ns |
精度不够,0.4ns 被舍弃 |
| ```timescale 1ns / 100ps`` | #1.44 |
1.4ns |
精度到 0.1ns,0.04ns 被舍弃 |
| ```timescale 1ns / 1ps`` | #1.445 |
1.445ns |
精度足够,完全保留 |
二、timeunit 和 timeprecision
为了避免timescale带来的全局污染,SystemVerilog 引入了timeunit 和 timeprecision关键字声明。
这两个关键字可以出现在以下四个主要作用域中:
- Module / Interface / Program:最常见的位置,用于锁定特定硬件块的时间基准。
- Package:用于确保包内的 task 或 function 延迟不随引用者的环境而改变。
- Compilation Unit Scope ($unit):在所有模块外部(通常是文件顶层)。
- Class:SystemVerilog 允许在类定义中声明。
1. 在模块/接口内部
cpp
module my_design (
input logic clk,
input logic rst_n
);
// 必须放在开头
timeunit 1ns;
timeprecision 1ps;
// 逻辑部分...
always #5 clk = ~clk;
endmodule
2. 在 Package 内部
在 package 中使用它们是防止"时间单位污染"的最佳方案。
cpp
package my_utils;
timeunit 1ns;
timeprecision 1ps;
task automatic wait_cycles(int n);
# (n * 1.5); // 这里的 1.5 将严格解析为 1.5ns
endtask
endpackage
timescale的作用,不会受限于package,比如package中定义的timescale,不只局限于package中生效,依然是遵循上述的编译顺序生效
不要在 package 文件里写 `timescale:这会导致你在 import 这个包的其他文件中遇到莫名其妙的时间对不上的问题。
始终在 package 内部显式声明 timeunit:这样无论包在哪里被编译,其内部的时间逻辑始终保持一致。
3. 优先级与覆盖规则
当多种声明方式并存时,仿真器遵循以下优先级:
-
最高优先级:模块内部定义的 timeunit / timeprecision。
-
中等优先级:编译器指令 `timescale(受编译顺序影响)。
-
最低优先级:仿真器命令行指定的默认值(如 VCS 的 -timescale=1ns/1ps)。
-
就近原则:模块内部的 timeunit 优先级高于文件顶层的 `timescale。
-
不可重定义性:在一个作用域(如一个 module)内,只能声明一次 timeunit。你不能在同一个模块里改来改去。
-
继承性:
- 如果模块内没有声明 timeunit,它会查找当前的 `timescale 指令。
- 如果连 `timescale 也没有,它会回退到编译单元($unit)的定义或仿真器默认值。
三、`timescale设置精度太小时,引发的仿真异常问题
问题场景: 最近仿真遇到一个奇怪的问题,我验证环境中的文件 A,里面的 task 有延迟 #1000us;但是当我吃的 rtl 的 flist 中带上 Xilinx 的 memory 库文件 B 时,这个 1000us 的延迟,变成了 1000ns。#1000us 前后都加了打印确认的。如果不带这个文件 B,就是实际 delay 1000us。
疑点:而且 A 文件的编译顺序是在 B 文件之前,按理说不应该受到 B 文件 timescale 的影响。
排查:我使用 -diag timescale 命令查看了 A 的 timescale 是 1ns/1ps,B 的 timescale 是 1ps/1ps。
网上也有网友遇到了我类似的问题,就是 timescale 精度过高时,出现 1000us 被识别成 1000ns 的问题。
针对这个问题的深度解析:
之所以 A 在 B 之前编译仍受影响,是因为在 VCS 的 Elaboration(细化) 阶段,仿真器会确定一个全局的 最小仿真步长(Precision)。当 B 文件引入了 1ps 这种极高精度时,整个仿真引擎的单位换算逻辑可能会发生微妙的变化。如果 A 文件中没有显式的 timeunit 声明,仅靠 `timescale 指令,在复杂的混合精度环境下,带单位的延迟(如 us)有时会被工具错误地截断或舍入到当前的基准单位(ns)上。
终极方案 :使用大杀器 -override_timescale
如果你无法修改代码位置,或者 $unit 污染太严重,建议直接在 VCS 命令行强制统一。
由于你发现 Xilinx 库 B 是 1ps/1ps,而你想要 1ns/1ps 或 1us,可以在编译命令中加入:
vcs -override_timescale=1ns/1ps
原理:这会无视所有文件里的 `timescale 指令和 timeunit 声明,强行让整个仿真环境整齐划一。
注意:这会解决 #1000us 识别错的问题,但如果 Xilinx 的某些模拟电路 IP 必须依赖 1ps 精度才能工作,可能会导致那些 IP 行为异常。
这个本质问题,应该是仿真器版本是vcs 2017版本,可能比较老。