Clock / Reset / Signal 使用指导
Author: syan · P0a / P0b
首次接入可先看 场景指南:按功能选择独立组件、phase、example 和测试入口,再按本文查具体 API。
1. 接入前先决定三件事
- 谁驱动真实 pin:TB source 或 DUT source。后者只观察。
- 同一物理 pin/vector 的 canonical root 是什么;alias/slice 必须归一,不能用不同逻辑名规避 ownership。
- 对哪个 clock 的 rising edge、哪个 phase 驱动/观察。时间统一为整数 1ps ticks。
绑定在 env 初始化时做一次,testcase 只使用 controller。没有每根 pin 的 agent/sequencer,也不需要新的 Python 运行时。
调用方的 timeunit 可能是 1ns;请用 svip_control_now() 取得当前 tick,用 23ns/1ps 表达 23ns 对应的整数 ticks。直接把调用方的 $time 或 23ns 传给整数 tick 参数会使用调用方的单位,不能这样换算。real 单位输入先经 svip_control_ticks(picoseconds, ticks) 检查精度,再提交。
2. 编译和最小接入
Clock、Reset、Signal 可以只编译需要的组件。以下命令从产品仓库根执行:
vcs -full64 -sverilog -ntb_opts uvm-1.2 -timescale=1ps/1ps \
-debug_access+all -f control/clock/files.f \
control/clock/examples/svip_clock_example.sv \
-top svip_clock_example -o simv
./simv
Reset 和 Signal 的独立 filelist 分别为 control/reset/files.f、control/signal/files.f。可复制的完整示例在 Clock、Reset、Signal 下;后两个使用外部 HDL clock,不需要 Clock UVC。
单组件直接导入公共层与自身 package:
import svip_control_common_pkg::*;
import svip_signal_pkg::*; // 或 svip_clock_pkg / svip_reset_pkg
按需组合使用一次 common/files.f 加所选组件的 sources.f,例如 Clock + Signal:
-f control/common/files.f
-f control/clock/sources.f
-f control/signal/sources.f
不要叠加多个自包含的组件 files.f,否则会重复编译公共 package 和 DPI。完整 Control 用根 control/files.f;包含全部 controller、coordinator 和原有 svip_control_pkg 重导出入口。原来的 import svip_control_pkg::* 以及 qualified 类型名仍有效,类型和静态计数器只有一份。
完整接入可运行的 quickstart:
vcs -full64 -sverilog -ntb_opts uvm-1.2 -timescale=1ps/1ps \
-debug_access+all -f control/files.f \
control/testbench/svip_control_quickstart.sv \
-top svip_control_quickstart -o simv
./simv
公共 filelist 已包含 control/common/backend/svip_control_read_only.cpp,不再单独追加。核心和单组件均不依赖 APB。需要 CSR adapter 时,在 Signal 或完整 Control 之后添加 -f control/signal/adapters/files_apb.f,并导入 svip_control_apb_pkg::*;真实项目仍使用自己的 APB agent 和 DUT。若项目已编译 APB,直接追加 adapter 的 .sv,避免再次编译 APB。
正常 TB 输入这样接:
wire [64:0] cfg;
svip_control_pin_if#(65) cfg_pin(cfg, core_clk);
assign cfg = cfg_pin.drive_value;
// 初始化阶段:实际 pin 已知后创建 binding。
svip_control_sv_binding#(65) binding = new(cfg_pin);
interface 的 observed 必须接真实 pin;capture clock 接真实接收域的 clock。binding 自动取得该 observed 路径,不能把 driver shadow 的路径当成观测路径。active-low 初始无效 reset 使用 svip_control_pin_if#(1,1'b1),其 binding 也使用相同参数。
显式注册物理身份:
if (!resources.register_pin("cfg", binding, "dut.cfg", 0, "tb", 0,
SVIP_CONTROL_SIGNAL, SVIP_CONTROL_DIRECT_PIN, cfg_ep, reason))
`uvm_fatal("CFG", reason)
signal_bank = new(cfg_ep, core_clock);
reset.attach(cfg_ep, '0);
0 offset 表示当前 interface 对应 canonical root 的最低物理 bit。另一 slice 应给出同 root 和正确 offset;相同 interface handle 也会自动识别。P0 的 recipe 对整个 canonical bank 独占。
观察 DUT 输出:不连接 drive_value 到 DUT,binding 创建时第三个参数 drive_allowed=0,注册 observe_only=1。reg-control 的 CSR 输出另指定 SVIP_CONTROL_CSR。对这种 endpoint 的普通 set() 会被拒绝。
3. Clock:启动、停低、变周期
core_clock = new(core_ep, 2000, 3000); // high=2ns, low=3ns
fork core_clock.run(); core_clock.monitor(); join_none
core_clock.start(svip_control_now()+20000, result);
core_clock.wait_edges(4, svip_control_now()+50000, result);
core_clock.stop_low(svip_control_now()+20000, result);
core_clock.start(svip_control_now()+20000, result); // resume,完整等 low
core_clock.set_period_at_boundary(1300, 2700,
svip_control_now()+20000, result);
start 成功表示接受启动;首个实际 edge 可用 wait_edges() 或 monitor 确认。wait_edges(N) 等 N 个未来 rising edges,不是任意时刻起 N 个完整周期。stop_low 成功则已经确认真实 pin 为 0。重复 start 不重新定相,重复 stop 稳定幂等。
周期切换不截断 high/low,在下一完整周期边界生效,boundary_edge() 给出实际接受 timing 的 tick。loss_timeout 可配置独立 clock loss 门限,intentional stop 不报 loss。创建 reference 时最后一个参数设为 1;普通 stop 被拒绝。
is_running() 是 engine 已接受运行状态,has_observed_edge() 还要求本次 start/resume 后已经出现真实 edge。domain registry 的 wait_ready() 使用后者和实际 reset 状态;它只证明外部 clock/reset 可用,DUT 内部 local-reset、配置 ack 和业务 ready 需要项目自己的观察条件。
P0 生成固定初值 0 的 TB clock。DUT generated clock 只观察,改变它应通过 DUT 的输入或 CSR。P1 才扩展派生关系。
4. Reset:source、epoch 和 gap
reset = new(reset_ep, 1); // active-low
reset.attach(cfg_ep, '0);
reset.attach(irq_ep, '0);
fork reset.monitor(); join_none
reset.set_source("software", 1, deadline, result);
reset.set_source("watchdog", 1, deadline, result);
reset.set_source("software", 0, deadline, result); // watchdog 仍保持 reset
reset.set_source("watchdog", 0, deadline, result);
asserted 是逻辑是否有效,controller 处理极性。X/Z 请求会拒绝。source_at() 指定绝对 tick,source_after_edge() 指定运行中 clock 的未来 rising edge ID。每个动作都有独立 deadline。
独立 Reset 只需公共层,attach() 接收公共 endpoint,不导入 Signal 组件。source_after_edge() 接受公共 svip_control_clock_ref;项目可提供外部 clock 的 reference,或直接传入 Clock 组件的 controller。
实际 epoch 从真实 pin 的 inactive→active 变化产生。重复 assert 同值不会虚增;初始就有效的 reset 使用 INITIAL_ASSERTED source,明确 release 该 token。source command 完成只表示外部 reset pin 行为,不能代替 DUT 的同步 reset 接收证明。
reset-clock gap:
coordinator = new(resources);
map = new("por_gap", reset, reference_clock);
map.add_clock(core_clock);
map.add_clock(aux_clock); // 异频也允许,重复添加会去重
map.pre_cycles = 3;
map.post_cycles = 2;
map.offset_ticks = 1;
map.release_guard_ticks = 1;
coordinator.gap(map, svip_control_now()+200000, result);
reference 为 100MHz 时,assert→resume 接受恰好 50ns;请求停钟到 R0 的等待另计,首个 functional edge 还要完整等 low_ticks。last_gap 包含实际 stop ack、R0、assert/release/resume 和首 edge 时间。
gap 中别的 source 阻止物理释放时返回 partial,功能 clock 保持停低;由 testcase 明确清除原因后恢复。reference loss 由独立 timer 结束。synchronous_assert=1 的整段停钟 plan 会被拒绝。这是停钟窗口释放外部 reset,不能称为停止 clock 上的同步释放。
5. Signal:value、mask 和 phase
when_at = new(SVIP_CONTROL_ASYNC_AT, svip_control_now()+17);
signal_bank.set(65'h10000000000000001, svip_control_mask(65),
when_at, deadline, result);
when_at = new(SVIP_CONTROL_BEFORE_CAPTURE, core_clock.next_rise_tick());
when_at.setup_ticks = 100;
signal_bank.set(value, write_mask, when_at, deadline, result);
when_at = new(SVIP_CONTROL_AFTER_SOURCE_EDGE, 0,
core_clock.endpoint.monitor().rises()+1);
signal_bank.set(value, write_mask, when_at, deadline, result);
BEFORE_CAPTURE 的 at_tick 是 capture 时刻,实际写入在正 setup 之前。AFTER_SOURCE_EDGE 在指定实际 edge 的 NBA 之后、同 timestamp 写入,让该 edge 采到旧值。ASYNC_AT 不悄悄对齐 clock。对未建立时序关系的外部 clock,不承诺 coincident Active 驱动的物理采样结果。
纯异步 Signal 用 signal_bank = new(cfg_ep),无需 Clock 组件。按边沿使用时,构造参数接受 svip_control_clock_ref。它只包含实际 clock endpoint、is_running()、predicts_rise(tick);外部时钟实现必须如实描述运行状态和可预测边界,不能预测的 BEFORE_CAPTURE 请求应被拒绝。完整外部时钟接法见 Signal 独立示例。
write_mask 只决定更新哪些 bit;checker 的 compare_mask 独立。value/mask 超宽、X/Z 和非法 phase 在写入前拒绝。默认 container 为 1024,可用编译宏 SVIP_CONTROL_MAX_W 调整;逻辑实际宽度仍由 interface 参数声明。整数负数不能默默截断到小 pin,请显式编码为所需宽度的 packed bits。
6. 观察和判错
signal_bank.expect_value(expected, compare_mask, deadline, result);
signal_bank.expect_value(expected, compare_mask, deadline, result, 1,
SVIP_CONTROL_CAPTURE_VALUE, last_seen_capture_id);
signal_bank.expect_stable(expected, until_tick, deadline, result);
默认 expect 使用最终 ReadOnly pin 值;capture expect 使用独立 HDL sampler 和明确 edge ID。未给 capture anchor 时,从调用时当前 ID 后的下一次采样开始。底层 monitor().sample() 可以取得最新的带时间历史 capture,不把它当作当前连续值。
known mask 保存真实 X/Z 信息,未知 bit 不能满足已知预期。expect_stable 使用真实变化计数和最终采样,连同 timestamp 内返回原值的变化也能发现。预期不满足会返回 timeout/partial 并报告 SVIP_CONTROL_EXPECT / SVIP_CONTROL_STABLE。专用负测可关闭普通报告再自己检查结果,正常使用保留默认报告。
7. Profile 和 TransitionPlan
profile = new("normal");
profile.add(mode_bank, mode_value, mode_mask);
profile.add(limit_bank, limit_value, limit_mask);
coordinator.capture_coherent(profile, core_clock,
core_clock.next_rise_tick(), 100, deadline, result);
适用成员必须同 source clock、owner、kind 和 reset dependencies;快照在指定 capture 前写完并稳定。非法字段 preflight 失败,不部分写。不同 delta 的异步观察者不在原子保证内。
跨域时用 svip_control_transition_plan 为 target 的每组目标提供一个 svip_control_when,再调用 coordinator.transition()。progress 保留逐步骤结果。request_global_atomic=1 被拒绝;barrier 只说明这些步骤均已完成。
8. Tieoff、Fuse/Strap 和 IRQ
tieoff 的 signal endpoint 使用 SVIP_CONTROL_TIEOFF:
tieoff_control = new(tieoff_signal);
tieoff_control.configure(1, deadline, result);
if (!tieoff_control.lock()) `uvm_fatal("TIEOFF", "busy")
lock 后普通 set/pulse 也不能改值。用 expect_stable 配合窗口检查实际连线。
fuse/strap 消费端镜像使用 SVIP_CONTROL_FUSE。svip_fuse_mirror 绑定指定 POR/load reset watch;该 reset 真实 active 且是新 epoch 时 load_image() 才打开窗口,完成后 lock。普通 functional reset 不给它添加清零依赖;它会保留 image。这里模拟消费端镜像,不实现 OTP 烧写协议。
IRQ source 用 svip_irq_source,提供 level()、继承的 pulse_time() 和 pulse_cycles()。时间 pulse 精确按整数 tick,不因窄于目标 clock 而拉长。cycle pulse 的含义是 N 次未来采样;停钟到 deadline 后清到 idle 并返回 timeout,reset/cancel 后旧 cleanup 被禁止。
DUT IRQ 输出用 svip_irq_observer:expect_level()、measure_pulse()。测量完整 pulse 需从真实 inactive 开始观察。输出清除走源条件或真实 CSR,observer 不提供反向 drive 接口。
9. 真实 CSR 接入
连接完成后创建 svip_control_apb_adapter,传入现有 active APB4 agent:
csr = new(apb_agent);
if (!csr.add_field("control", csr_output_ep, 'h0, 0, 32,
SVIP_CONTROL_CSR_WORD, reason)) `uvm_fatal("CSR", reason)
csr.write("control", 32'h80402001, deadline, bus_result);
csr_output_observer.expect_value(32'h80402001, svip_control_mask(32),
effect_deadline, effect_result);
csr_output_ep 必须为 CSR/passive。word 模式要求完整 register image;bytes 模式只允许完整 byte,使用 APB PSTRB;W1C 只向目标 bit 写 1,其他 bit 写 0;read-clear 只能显式 read。不提供盲目 RMW。
bus_result 的 applied_kind 为 CSR_TRANSACTION_COMPLETED,observed/effective 仍未知。checker 通过实际目标输出确认后,effect_result 才带独立证据。多 CSR 用 write_profile() 得到逐项 progress,失败返回 partial;无硬件 commit 时拒绝 atomic。
APB timeout 不等于事务取消。 adapter 不杀死现有 driver 的 sequence;transport 进入隔离,后续请求拒绝,直到 drain() 看到原事务真实完成或 reset-aborted。若不希望原写入随后发生,由项目显式 reset/恢复,再 drain,不能假装 rollback。详见 example_csr 和 csr_failures。
10. 结果、tracker 和回归
检查 result.status,保留 requested/scheduled/applied/observed/effective 时间与 reason。applied_kind 区分 pin 写入和 CSR 完成。普通 set 的 observed 是真实 pin;它不自动证明内部业务已消费。effective 只由明确的实际观测条件设置。
tracker 包含命令 ID、owner、generation、实际 epoch、phase、clock/edge、deadline、value/known mask。sequence 是本地日志序号,不能用来声称 simulator delta 编号。开启/关闭:
svip_control_result::open_tracker("control.log");
// execute commands
svip_control_result::close_tracker();
svip_control_result::write_coverage("control-coverage.json");
runner 会完成上述调用并校验 tracker terminal 与 coverage 一一对应。coverage 区分 command status/phase 计数和真实观测宽度/view/known、stop/reset/gap counters;验收完备性由完整测试矩阵判断。
examples 是能复制的使用方式,compliance 负责非法控制、冲突、deadline、reset 和故障判错。实际命令、配置、结果和产物哈希保存在 build 下。能力范围及本版实测记录见 verification。