UVM军事系列 · 第二篇:monitor——那个躲在暗处,把战场上发生的一切都记下来的人

1 阅读6分钟

凌晨两点,前线一片寂静。 特种部队已经突入目标区域,正在执行拆除任务。你坐在后方指挥部的监控屏幕前,眼睛一刻不敢离开。 通讯频道里不断传来零碎的信息:

  • "A组已进入"
  • "B组发现异常"
  • "目标建筑内有动静" 但你比通讯频道知道得更多。 你的热成像仪显示目标建筑东侧有两个人影正在移动,比通讯频道早了整整三十秒。你的红外探测器捕捉到楼顶有一个微弱的热源——那是一把狙击步枪的枪管,余温还没散尽。而这两条情报,通讯频道里没人提到过。 你按下了静音键,在作战记录本上写下了: "0232,目标建筑东侧有人员活动,疑似巡逻。楼顶存在狙击手预置阵地,建议C组绕行。" 然后你继续盯着屏幕,不说话,不干预,不表态。 你的工作只有一个:看见一切,如实记录。 在UVM的世界里,这个人叫monitor。

monitor到底在干什么 monitor是战场上最安静的角色。 它不发出任何指令,不做任何判断,不参与任何决策。它只是蹲在DUT的接口旁边,像一个隐蔽的观察哨,把所有经过接口的信号全部记录下来。 士兵(driver)→ 发送动作 → 物理接口 → monitor监听 ↓ 所有信号被记录 ↓ 情报上报给scoreboard

monitor做的事情本质上只有两件: 第一,实时监听DUT的接口信号。 第二,把监听到的内容打包成transaction,发送给上层组件。 比如,DUT是一个通信电台模块,它会发送数据: tx_data[7:0] → 发送的数据 tx_valid → 数据有效信号 rx_data[7:0] → 接收到的数据 rx_valid → 接收有效信号

monitor的工作是蹲在电台旁边,盯着这两组信号,一旦发现tx_valid拉高,立刻把tx_data的值记下来;一旦rx_valid拉高,立刻把rx_data的值记下来。然后把这些情报整理成一份结构化的报告,发送给scoreboard。


monitor为什么会存在 有人会问:driver不是已经知道"发送了什么"吗?为什么还要monitor来"观察"? 因为driver知道的,是"我让士兵打了什么"。但战场上真实发生的,是另一回事。 driver的认知: monitor的真实记录: "我命令士兵开火了" "通讯频道显示,实际开火延迟了3秒" "火力覆盖正常" "东侧掩体被友军误伤" "目标已清除" "热成像仪显示有人员撤离,未被报告"

driver发送的是"意图",monitor记录的是"现实"。这两件事之间,往往有差距。 而且monitor的监听是被动的——它不影响任何事情,不干预任何信号,只是看。这和通讯兵的主动发送完全不同。monitor甚至不知道自己在看什么,它只负责:看见了,就记下来。


见过几种典型的monitor错误 第一种:monitor"自作主张" 有人在monitor里写: monitor里写:if (data == 0xFF) { 报告"发现异常目标" } else { 正常记录 }

这是monitor最常见的越权行为。monitor不是裁判,它不判断对错。如果driver发了0xFF,那是driver的事情,monitor应该如实记录"0xFF",而不是加上自己的解读。 正确的做法是: monitor记录:data = 0xFF ← 就这一句 时间戳:0232:15 通道:tx

让scoreboard去判断"0xFF是不是异常"

第二种:monitor漏掉了关键情报 这是最致命的问题。 monitor写:always @(posedge clk) begin if (tx_valid) tx_data_buffer <= tx_data; end

看起来没问题,但仔细看——这个写法在tx_valid只有1个时钟周期宽度的时候,可能会漏采。 为什么?因为在Verilog里,@(posedge clk)是边沿触发,如果tx_valid刚好只持续了1个时钟周期,而这个时钟周期恰好和采样时钟错开了,monitor就会漏掉。 正确的做法是: // 更安全的写法:使用组合逻辑直接采样 always_comb begin if (tx_valid) recorded_data = tx_data; // 只要valid有效,就持续记录 end

或者在接口里用clocking block来做同步采样: interface uart_monitor_if; clocking cb @(posedge clk); property tx_mon @(posedge clk) tx_valid; recorded_data <= tx_data; endproperty endclocking endinterface

漏采是monitor最不能接受的错误。漏了一条情报,scoreboard的判断就可能完全偏离。


monitor的正确打开方式 一个合格的侦察兵(monitor),是这样的: class comm_monitor extends uvm_monitor; virtual comm_if vif; // 电台物理接口 uvm_analysis_port #(trans_item) ap; // 情报上报端口

function void build_phase(uvm_phase phase); super.build_phase(phase); // 获取接口 if (!uvm_config_db #(virtual comm_if)::get(this, "", "vif", vif)) `uvm_fatal("MONITOR_NOCFG", "monitor没有拿到接口配置"); ap = new("ap", this); // 创建情报上报端口 endfunction

task run_phase(uvm_phase phase); trans_item tr;

forever begin
  @(posedge vif.clk);  // 等待时钟上升沿
  
  // 监听TX通道:发送情报
  if (vif.tx_valid) begin
    tr = trans_item::type_id::create("tr");
    tr.kind    = TX;
    tr.payload = vif.tx_data;
    tr.time_stamp = $time();
    ap.write(tr);  // 把情报上报
    `uvm_info("MONITOR", $sformatf("监听到TX发送: %h", tr.payload), UVM_MEDIUM)
  end
  
  // 监听RX通道:接收情报
  if (vif.rx_valid) begin
    tr = trans_item::type_id::create("tr");
    tr.kind    = RX;
    tr.payload = vif.rx_data;
    tr.time_stamp = $time();
    ap.write(tr);
    `uvm_info("MONITOR", $sformatf("监听到RX接收: %h", tr.payload), UVM_MEDIUM)
  end
end

endtask endclass

关键点:

  1. ap = new(...) —— 建立情报上报通道(没有这一步,情报上不去)
  2. ap.write(tr) —— 情报实时上报,不积压
  3. @(posedge vif.clk) —— 严格时钟同步采样(不漏采的关键)
  4. 不判断对错 —— monitor只记录,不做裁判

monitor和driver的关系 很多人容易混淆这两个角色。这里有一个简单的判断标准: driver在信号线的"发送端",主动改变信号的值。 monitor在信号线的"任意位置",只读取,不改变。 driver ──→ [信号线] ──→ monitor ↑ ↑ 发送方 监听方

driver是运动员,上场奔跑。monitor是边线裁判,只看着,不吹哨。 两者可以同时监听同一根信号线,互不干扰。driver发它的,monitor看它的。


打个比方收尾 通讯兵(driver)发完命令之后,战场上有两件事在同时发生: 第一,通讯频道里报告"命令已执行"。但通讯频道是通讯兵自己发的,你不知道是真实情况还是"报平安"。 第二,侦察兵(monitor)从隐蔽点记录下了一切:枪口焰的方向、目标的倒下、友军的位置。然后把这份原始记录原封不动地上报指挥部。 scoreboard拿到这两份情报,一对比,就知道命令有没有真正被执行,有没有出现偏差,是谁的问题。 这就是monitor:不干预,不判断,不表态,只记录一切。 战场越混乱,monitor的价值就越大。因为在混乱中,唯一可靠的,就是真实发生过的记录。


下篇预告:sequencer——作战参谋。把所有任务排序,决定谁先打谁后打的那个人。参谋不扣扳机,但整场战斗的节奏由他控制。