情况:1.没学过 SystemVerilog2.有Questa仿真工具3.工作中项目
适合走 "边用边学" 的路线 ------ 不先啃 SystemVerilog 语法书,而是直接上手 UVM 例子 ,遇到不懂的语法现学现用。因为你有 Questa 工具,能随时跑起来看波形,这是最好的学习方式。
给你的定制学习计划(4 周,每天 1 小时)
| 周 | 目标 | 产出 |
|---|---|---|
| 第1周 | 跑通加法器 UVM 平台 | 能跑出波形 + 打印 |
| 第2周 | 搞懂 sequence / driver / monitor | 会改数据、看结果 |
| 第3周 | 加 scoreboard + coverage | 能自动比对 |
| 第4周 | 套用到你的工作项目 | 能自己搭 |
第 1 课(今天):先跑起来,再理解
我给你的第一个任务 不是背语法 ,而是 把下面 6 个文件复制到 Questa 里跑通。跑通了你就有感觉了。
文件清单(6 个)
text
Copy
Download
adder_if.sv ← 接口(连接 DUT 和验证平台)
adder.sv ← DUT(被验证的加法器)
adder_transaction.sv ← 事务(数据包)
adder_driver.sv ← 驱动器
adder_monitor.sv ← 监视器
adder_test.sv ← 顶层测试
tb_top.sv ← 仿真顶层(连 DUT + 启动 UVM)
① adder_if.sv
systemverilog
Copy
Download
interface adder_if (input logic clk, input logic rst_n);
logic [7:0] a;
logic [7:0] b;
logic [8:0] sum;
endinterface
说明: interface 就是"一组信号的打包",把 a、b、sum 装在一起,方便传递。
② adder.sv(DUT)
systemverilog
Copy
Download
module adder (
input logic clk,
input logic rst_n,
input logic [7:0] a,
input logic [7:0] b,
output logic [8:0] sum
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) sum <= 9'd0;
else sum <= a + b;
end
endmodule
③ adder_transaction.sv
systemverilog
Copy
Download
class adder_transaction extends uvm_sequence_item;
rand bit [7:0] a;
rand bit [7:0] b;
bit [8:0] sum;
`uvm_object_utils(adder_transaction)
function new(string name = "adder_transaction");
super.new(name);
endfunction
endclass
④ adder_driver.sv
systemverilog
Copy
Download
class adder_driver extends uvm_driver #(adder_transaction);
`uvm_component_utils(adder_driver)
virtual adder_if vif;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual adder_if)::get(this, "", "vif", vif))
`uvm_fatal("DRV", "vif 没拿到!")
endfunction
task run_phase(uvm_phase phase);
adder_transaction tr;
forever begin
seq_item_port.get_next_item(tr);
vif.a <= tr.a;
vif.b <= tr.b;
@(posedge vif.clk);
seq_item_port.item_done();
end
endtask
endclass
⑤ adder_monitor.sv
systemverilog
Copy
Download
class adder_monitor extends uvm_monitor;
`uvm_component_utils(adder_monitor)
virtual adder_if vif;
uvm_analysis_port #(adder_transaction) ap; // 分析端口,往外发数据
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual adder_if)::get(this, "", "vif", vif))
`uvm_fatal("MON", "vif 没拿到!")
endfunction
task run_phase(uvm_phase phase);
forever begin
adder_transaction tr;
@(posedge vif.clk);
tr = adder_transaction::type_id::create("tr");
tr.a = vif.a;
tr.b = vif.b;
tr.sum = vif.sum;
`uvm_info("MON", $sformatf("采样: a=%0d b=%0d sum=%0d", tr.a, tr.b, tr.sum), UVM_LOW)
ap.write(tr);
end
endtask
endclass
⑥ adder_test.sv(顶层)
systemverilog
Copy
Download
class adder_test extends uvm_test;
`uvm_component_utils(adder_test)
adder_driver drv;
adder_monitor mon;
uvm_sequencer #(adder_transaction) sqr;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = adder_driver::type_id::create("drv", this);
mon = adder_monitor::type_id::create("mon", this);
sqr = uvm_sequencer#(adder_transaction)::type_id::create("sqr", this);
// 连接 driver 和 sequencer
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
task run_phase(uvm_phase phase);
// 简单的 sequence:直接手写
adder_transaction tr;
phase.raise_objection(this);
repeat (5) begin
tr = adder_transaction::type_id::create("tr");
tr.randomize(); // 随机 a、b
sqr.execute_item(tr); // 发给 sequencer → driver
end
#50;
phase.drop_objection(this);
endtask
endclass
注意:
sqr.execute_item(tr)是简化写法,正式写法用 sequence 类。第 2 课我们改正式写法。
⑦ tb_top.sv(仿真入口)
systemverilog
Copy
Download
`timescale 1ns/1ps
module tb_top;
import uvm_pkg::*;
`include "uvm_macros.svh"
logic clk = 0;
logic rst_n;
// 时钟
always #5 clk = ~clk;
// 接口
adder_if intf(.clk(clk), .rst_n(rst_n));
// DUT
adder dut (
.clk(clk), .rst_n(rst_n),
.a(intf.a), .b(intf.b), .sum(intf.sum)
);
// 把接口注册到 config_db
initial begin
uvm_config_db#(virtual adder_if)::set(null, "*", "vif", intf);
run_test("adder_test");
end
// 复位
initial begin
rst_n = 0;
#20 rst_n = 1;
end
// 把之前几个文件 include 进来(也可以分文件编译)
`include "adder_transaction.sv"
`include "adder_driver.sv"
`include "adder_monitor.sv"
`include "adder_test.sv"
endmodule
怎么在 Questa 里跑?
步骤 1:建工程
text
Copy
Download
vlib work
vmap work work
步骤 2:编译(注意顺序:先接口,再 DUT,最后 top)
text
Copy
Download
vlog adder_if.sv adder.sv tb_top.sv
步骤 3:仿真
text
Copy
Download
vsim -c work.tb_top -do "run -all; quit"
或者用 GUI:
text
Copy
Download
vsim work.tb_top
# 在 GUI 里 run -all
预期输出(你会看到)
text
Copy
Download
UVM_INFO ... [MON] 采样: a=42 b=17 sum=59
UVM_INFO ... [MON] 采样: a=133 b=201 sum=334
UVM_INFO ... [MON] 采样: a=7 b=250 sum=257
...(共 5 条左右)
UVM_INFO ... TEST_DONE
如果看到 [MON] 采样 打印 → 恭喜你,第一个 UVM 平台跑通了! 🎉
现在轮到你了 ✋
请你按下面步骤做,然后告诉我结果:
-
建一个文件夹
uvm_adder -
把 7 个文件复制进去(我可以再拆开发给你,或你按上面直接建)
-
执行 3 条命令(vlib / vlog / vsim)
-
把屏幕输出贴给我
可能遇到的坑(提前预警)
| 报错 | 原因 | 解决 |
|---|---|---|
uvm_pkg not found |
没 import | 检查 import uvm_pkg::*; |
vif 没拿到 |
config_db set/get 名字不一致 | 检查 "vif" 字符串是否一样 |
run_test 找不到 |
类名写错 | run_test("adder_test") |
| 编译报 include 错 | 文件顺序 | 先 interface 再 module 再 top |
一句话总结今天
不要试图先懂所有语法,先把平台跑通,看到现象,再回来理解每一行。 这是工程师学习 UVM 最快的方式。