信号查询¶
获取信号有两种方式:知道确切路径时直接访问,名字有规律时用批量查询。拿到 Signal 后,需要加载对应波形才能参与分析。
直接访问¶
reader[path] 返回指定路径对应的 Signal 或 Scope:
signal = reader['tb.dut.data']
scope = reader['tb.dut']
selected = reader['tb.dut.data[31:16]'] # 末尾可以带位选择
scope 和复合信号(struct、array)支持同样的查找方式,可以继续用相对路径:
tb = reader['tb']
signal = tb['dut']['data'] # 一级一级往下取
signal = tb['dut.data'] # 也可以一次写完
member = tb['pkt']['valid'] # struct 成员同样适用
用整数或切片下标对信号取位,语义和 Verilog 一致:
加载波形¶
reader[path] 拿到的是 Signal,不是波形Waveform。要获取波形数据,先进入一个时钟域,然后使用 Signal.w/load_waveform() :
with VcdReader('simulation.vcd') as reader:
with reader.clock_domain('tb.clk'):
valid = reader['tb.dut.valid'].w
data = reader.load_waveform('tb.dut.data[7:0]')
如果想在时钟域外获取波形,或者想临时指定不同的时钟/窗口,就需要显式传 clock、边沿、窗口等参数:
data = reader.load_waveform(
'tb.dut.data[7:0]', clock='tb.clk',
sample_on_posedge=True,
start_cycle=100, end_cycle=200,
)
采样默认发生在下降沿,以避免信号变化瞬间带来的采样错误;可以使用 sample_on_posedge=True改为上升沿采样。可以用 start_time/end_time 按仿真时间指定采样窗口,也可以用start_cycle/end_cycle 按绝对时钟周期指定,但两者不能混用。
用于同一次计算或模式匹配的波形,必须使用同一个时钟源、相同的采样边沿,以及相同的采样窗口,所以更推荐使用统一的时钟域,而不是每次都重复传参。
加载掩码¶
加载波形会把 X/Z 状态替换成 xz_value(默认为 0)。如果需要保留这些状态(比如需要排除 X 态,或者需要追踪哪些位是 X/Z 时),可以用 Signal.m 或 load_unknown_mask() 加载对应的掩码:
with reader.clock_domain(clock='tb.clk'):
value = reader['tb.data[7:0]'].w
unknown = reader['tb.data[7:0]'].m # X/Z 存在性掩码
# 或者:unknown = reader.load_unknown_mask('tb.data[7:0]')
known_value = value.mask(unknown == 0)
Signal.m 是 Signal.unknown_mask() 的默认参数写法。掩码的每一位对应源信号的一位,标记该位在源文件里是否为 X/Z。
批量查询¶
如果信号名字有规律——同一模块复制了多份、信号带编号、名字共享前缀——可以用一条 查询把它们一起匹配出来。
查询路径由点号分隔,每一级要么是固定名称(精确匹配),要么是一个匹配表达式, 比如大括号、正则或通配符。每个匹配表达式都会在结果 key 里留下 一个 capture,描述它具体匹配到的内容。
get_matched_signals() 返回一个字典:每个匹配到的信号一条记录,key 是按查询路径
顺序排好的 capture tuple(精确匹配的key不会被包含在capture tuple中)。
下面这条查询同时匹配两个维度——FIFO 编号和信号类型:
with VcdReader('simulation.vcd') as reader:
signals = reader.get_matched_signals('tb.fifo_{0..3}.{wr,rd}_en')
for key, signal in signals.items():
print(key, signal.full_name)
输出:
(BraceCapture(groups=('0',)), BraceCapture(groups=('wr',))) tb.fifo_0.wr_en
(BraceCapture(groups=('0',)), BraceCapture(groups=('rd',))) tb.fifo_0.rd_en
(BraceCapture(groups=('1',)), BraceCapture(groups=('wr',))) tb.fifo_1.wr_en
(BraceCapture(groups=('1',)), BraceCapture(groups=('rd',))) tb.fifo_1.rd_en
...
要把匹配成功的信号一次加载成波形,可以用 load_matched_waveforms():
with VcdReader('simulation.vcd') as reader:
with reader.clock_domain('tb.clk'):
waves = reader.load_matched_waveforms('tb.fifo_{0..3}.{wr,rd}_en')
查询语法¶
查询路径支持以下语法:
| 语法 | 示例 | 捕获的 key 组件 |
|---|---|---|
| 精确路径 | tb.dut.valid |
无 capture |
| 大括号列表 | sig_{read,write} |
BraceCapture |
| 整数范围 | fifo_{0..3}.ptr |
每个索引一个 BraceCapture |
| 步进范围 | lane_{0..6..2}.valid |
BraceCapture 为 0、2、4、6 |
| 规范正则 | tb.u0./J_([a-z]+)/ |
RegexCapture |
| 兼容正则 | @([a-z]+)_valid |
RegexCapture |
| 单层通配符 | tb.*.valid |
WildcardCapture |
| 递归通配符 | tb.**.valid |
WildcardCapture |
| 直接模块定义 | tb.$fifo_unit.ptr |
ExactCapture(FSDB) |
| 递归模块定义 | tb.$$fifo_unit.ptr |
ExactCapture(FSDB) |
get_matched_scopes()、get_matched_nodes()、load_matched_unknown_masks() 和
Reader.eval() 都支持上述语法。
clock_path 只匹配到一个信号时,所有结果共用它作为时钟;匹配到多个信号时,
每个信号会选取 key 是其最长前缀的一个时钟。
$ 和 $$ 只有 FSDB 的模块定义匹配能用。
计算表达式¶
对于只需要写一行的简单计算,使用Reader.eval() 比较方便。
single 模式(默认)下,表达式里的每条路径都必须对应唯一信号:
表达式里也能调波形操作函数:
byte_count = reader.eval(
'bit_count(tb.axi.wstrb[7:0] * (tb.axi.wvalid & tb.axi.wready))',
clock='tb.clk',
)
zip 模式下,带匹配的路径按各自的 capture tuple 分组展开;只匹配到一个信号的路径
会广播到所有组: