
`ifndef MY_SEQUENCE_SV_
`define MY_SEQUENCE_SV_
`include "transaction_channel.sv"
`include "transaction_formater.sv"
`include "transaction_bus.sv"
`include "sequencer_pkg.sv"
class sequence_channel extends uvm_sequence #(transaction_channel);
rand int pkt_id = 0;
rand int ch_id = -1;
rand int data_nidles = -1;
rand int pkt_nidles = -1;
rand int data_size = -1;
rand int ntrans = 10;
`uvm_object_utils_begin(sequence_channel)
`uvm_field_int( pkt_id , UVM_ALL_ON )
`uvm_field_int( ch_id , UVM_ALL_ON )
`uvm_field_int( data_nidles , UVM_ALL_ON )
`uvm_field_int( pkt_nidles , UVM_ALL_ON )
`uvm_field_int( data_size , UVM_ALL_ON )
`uvm_field_int( ntrans , UVM_ALL_ON )
`uvm_object_utils_end
function new(string name = "sequence_channel");
super.new(name);
endfunction
`uvm_declare_p_sequencer(sequencer_channel)
constraint cstr{
soft data_size inside {4:32};
soft ch_id == 0;
soft pkt_id == 0;
soft data_nidles inside {0:2};
soft pkt_nidles inside {1:10};
soft ntrans inside {5:10};
}
virtual task body();
repeat(ntrans) begin
transaction_channel m_trans, rsp;
`uvm_do_with(m_trans, { local::ch_id >= 0 -> ch_id == local::ch_id;
local::pkt_id >= 0 -> pkt_id == local::pkt_id;
local::data_nidles >= 0 -> data_nidles == local::data_nidles;
local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
local::data_size >0 -> data.size() == local::data_size;
})
this.pkt_id++;
`uvm_info("sequence_channel", {"m_trans: \n", m_trans.sprint()}, UVM_HIGH)
get_response(rsp);
`uvm_info("sequence_channel", {"rsp: \n", rsp.sprint()}, UVM_HIGH)
assert(rsp.rsp)
else error("\[RSPERR\] %0t error response received!", time);
end
endtask
function void post_randomize();
string s;
s = {s, $sformatf("\nAfter randomization, sequence_channel body will run %0d transaction.\n", ntrans)};
s = {s, "sequence_channel object content is as below: \n"};
s = {s, super.sprint()};
`uvm_info("sequence_channel", s, UVM_LOW)
endfunction
endclass
class sequence_bus extends uvm_sequence #(transaction_bus);
rand bit7:0 addr = 0;
rand bit1:0 cmd = 0;
rand bit31:0 data = 0;
`uvm_object_utils_begin(sequence_bus)
`uvm_field_int( addr , UVM_ALL_ON )
`uvm_field_int( cmd , UVM_ALL_ON )
`uvm_field_int( data , UVM_ALL_ON )
`uvm_object_utils_end
function new(string name = "sequence_bus");
super.new(name);
endfunction
`uvm_declare_p_sequencer(sequencer_bus)
virtual task body();
transaction_bus m_trans, rsp;
`uvm_do_with(m_trans, { addr == local::addr;
cmd == local::cmd;
data == local::data;
})
`uvm_info("sequence_bus", $sformatf("m_trans send to driver:\n%s", m_trans.sprint()), UVM_HIGH)
get_response(rsp);
`uvm_info("sequence_bus", $sformatf("rsp receive from driver:\n%s", rsp.sprint()), UVM_HIGH)
if(m_trans.cmd == `READ)
this.data = rsp.data;
assert(rsp.rsp)
else error("\[RSPERR\] %0t error response received!", time);
endtask
function void post_randomize();
string s;
s = {s, "After randomization \n"};
s = {s, "sequence_bus object content is as below: \n"};
s = {s, super.sprint()};
`uvm_info("sequence_bus", s, UVM_LOW)
endfunction
endclass
class sequence_formatter extends uvm_sequence #(transaction_formatter);
rand fmt_fifo_t fifo = MED_FIFO;
rand fmt_bandwidth_t bandwidth = MED_WIDTH;
constraint cstr{
soft fifo == MED_FIFO;
soft bandwidth == MED_WIDTH;
}
`uvm_object_utils_begin(sequence_formatter)
`uvm_field_enum( fmt_fifo_t, fifo, UVM_ALL_ON )
`uvm_field_enum( fmt_bandwidth_t, bandwidth, UVM_ALL_ON )
`uvm_object_utils_end
function new(string name = "sequence_formatter");
super.new(name);
endfunction
`uvm_declare_p_sequencer(sequencer_formatter)
virtual task body();
transaction_formatter m_trans, rsp;
`uvm_do_with(m_trans, { fifo == local::fifo;
bandwidth == local::bandwidth;
})
`uvm_info("sequence_formatter", m_trans.sprint(), UVM_HIGH)
get_response(rsp);
`uvm_info("sequence_formatter", rsp.sprint(), UVM_HIGH)
assert(rsp.rsp)
else error("\[RSPERR\] %0t error response received!", time);
endtask
function void post_randomize();
string s;
s = {s, "After randomization \n"};
s = {s, "sequence_formatter object content is as below: \n"};
s = {s, super.sprint()};
`uvm_info("sequence_formatter", s, UVM_LOW)
endfunction
endclass
`endif
sequence.sv路科验证有一段代码如上,可以讲讲嘛,可以把我当做一个学生,老师,谢谢啦,
同学,坐。看到你把 sequence.sv 拿出来了,老师特别高兴。这可以说是整个验证平台里**最有趣、最能体现验证工程师"创造力"**的地方。前面咱们讲的 Driver 和 Monitor 是"死"的执行者,而 Sequence 是"活"的,是它决定了每天给 DUT 灌什么样的数据、搞什么样的破坏。
这个文件里定义了三个不同的 Sequence 类,分别对应 MCDF 的三个主要接口:通道数据流、寄存器总线、格式化器配置。咱们继续用"剧组"和"弹夹"的比喻,把这三段代码逐一拆解。
🎬 第一幕:通道数据弹夹 ------ sequence_channel
这是最核心的一个 Sequence,它负责往 3 个数据通道里疯狂灌数据。
1. 弹夹的规格参数
systemverilog
class sequence_channel extends uvm_sequence #(transaction_channel);
rand int pkt_id = 0;
rand int ch_id = -1;
rand int data_nidles = -1;
rand int pkt_nidles = -1;
rand int data_size = -1;
rand int ntrans = 10;
-
大白话 :这些
rand变量就是**"剧本的旋钮"**。你想演什么剧情,通过控制这些变量来实现。-
ntrans:总共要发多少包(默认10发)。 -
ch_id:这是哪个通道发的(0、1还是2)。 -
data_size:每一包的数据长度是多少(4到32个32bit数据)。 -
data_nidles和pkt_nidles:这就是之前 Driver 里用到的"空闲周期",插在数据中间或包与包之间,用来测试 DUT 的 FIFO 容错能力。
-
2. 自动打印的装备(Field Automation)
systemverilog
`uvm_object_utils_begin(sequence_channel)
`uvm_field_int( pkt_id , UVM_ALL_ON )
`uvm_field_int( ch_id , UVM_ALL_ON )
...
`uvm_object_utils_end
- 大白话 :还记得咱们讲的"field_automation机制"吗?就是给这个类里的所有变量"上户口"。上了户口以后,你不需要自己写
my_print,直接调用sprint()就能把所有变量的值打印出来,方便调试。
3. 手枪的指定目标(p_sequencer)
systemverilog
`uvm_declare_p_sequencer(sequencer_channel)
- 大白话 :告诉这个弹夹:"你的目标手枪是
sequencer_channel这种类型的。" 有了这行,你才能在后面的body()里用p_sequencer来指代目标手枪。
4. "软约束"的智慧(constraint)
systemverilog
constraint cstr{
soft data_size inside {[4:32]};
soft ch_id == 0;
soft pkt_id == 0;
soft data_nidles inside {[0:2]};
soft pkt_nidles inside {[1:10]};
soft ntrans inside {[5:10]};
}
-
大白话 :
soft是**"软约束"** 。为什么用软的?如果全是硬约束,这个 Sequence 就被写死了,只能发 ch_id=0、长度4~32的数据。
加上
soft,意味着它有一个默认值,但如果上层(比如case0_vseq)用uvm_do_with硬性指定了特定的值,软约束就会自动让路,不报错。 这极大地提高了代码的复用性!你写一个 Sequence,就能适应各种测试用例的需求。
5. 核心"剧本"------ body()
systemverilog
virtual task body();
repeat(ntrans) begin
transaction_channel m_trans, rsp;
`uvm_do_with(m_trans, { local::ch_id >= 0 -> ch_id == local::ch_id;
...
})
this.pkt_id++;
...
get_response(rsp);
...
assert(rsp.rsp) else $error(...);
end
endtask
-
repeat(ntrans):循环发ntrans次子弹。 -
\uvm_do_with(m_trans, { 约束 })`:这是一个宏,它做三件事:-
创建
m_trans对象。 -
在随机化时,叠加(Overriding)括号里的约束。
-
交给 Sequencer 发射。
-
local::的妙用(重点!) :你看local::ch_id指的是 Sequence 里的ch_id,而右边的ch_id == local::ch_id是指 transaction 里的ch_id。这就是在告诉 UVM:"如果我在弹夹层面设定了ch_id为 2,那你打出的这颗子弹ch_id也必须是 2。" -
->是条件约束:如果local::ch_id >= 0,才应用右边的约束;如果local::ch_id是默认的 -1,就忽略它。
-
-
this.pkt_id++:包编号自增,保证每一包的数据都不重复。 -
get_response(rsp):极其关键! 子弹打出去后,弹夹不会立刻发下一发,它会阻塞等待 Driver 的回执(还记得 Driver 里的put_response(rsp)吗?)。如果 Driver 打完了,回执来了,弹夹才会继续往下走。 -
assert(rsp.rsp):如果回执里的rsp是 0(表示失败),断言就会报错,提示"收到了错误回执"。
6. 打印调试信息
systemverilog
function void post_randomize();
...
`uvm_info("sequence_channel", s, UVM_LOW)
endfunction
- 大白话 :
post_randomize()是 SystemVerilog 的一个自动回调函数。每次这个类被随机化之后,UVM 会自动调用这个函数。所以每次你创建并随机化一个 Sequence,它都会自动打印一行日志,告诉你这次发多少个包。
🎬 第二幕:寄存器总线弹夹 ------ sequence_bus
这个 Sequence 专门用于通过 APB 总线读写寄存器,配置 MCDF 的通道优先级、长度等。
systemverilog
virtual task body();
transaction_bus m_trans, rsp;
`uvm_do_with(m_trans, { addr == local::addr; ... })
...
if(m_trans.cmd == `READ)
this.data = rsp.data; // 关键!
...
endtask
-
独门秘籍 :看
if(m_trans.cmd == \READ)` 这一句。-
对于寄存器操作,如果是 写操作(WRITE),发出去就结束了。
-
如果是 读操作(READ) ,DUT 会在几周期后把数据吐出来,Driver 会把它放在回执
rsp里返回。 -
所以这里必须把
rsp.data赋值给 Sequence 自己的this.data。这就实现了一个闭环:Sequence 发读命令,Sequence 自己也能拿到读到数据。这在后面配置寄存器时非常有用。
-
🎬 第三幕:格式化器配置弹夹 ------ sequence_formatter
这个 Sequence 用于配置 Formatter 的 FIFO 深度(FIFO)和带宽(bandwidth)。
systemverilog
class sequence_formatter extends uvm_sequence #(transaction_formatter);
rand fmt_fifo_t fifo = MED_FIFO;
rand fmt_bandwidth_t bandwidth = MED_WIDTH;
constraint cstr{
soft fifo == MED_FIFO;
soft bandwidth == MED_WIDTH;
}
...
virtual task body();
transaction_formatter m_trans, rsp;
`uvm_do_with(m_trans, { fifo == local::fifo; bandwidth == local::bandwidth; })
get_response(rsp);
assert(rsp.rsp) else $error(...);
endtask
-
大白话 :
fmt_fifo_t和fmt_bandwidth_t是在transaction_formater.sv里定义的枚举类型(enum)。 -
这个弹夹不产生常规数据,它产生的子弹是"配置指令"。它告诉 DUT 的 Formatter:"你的 FIFO 深度设为 SHORT/MED/LONG/ULTRA,你的带宽设为 LOW/MED/HIGH/ULTRA"。这也是为什么 Driver 的
do_config里会case(req.fifo)来调整内部fifo_bound的大小。
💡 老师的终极总结与实操建议
同学,你看完这三个 Sequence,应该能感受到 UVM Sequence 机制的强大:
-
高度复用 :三种不同的接口(通道数据、APB寄存器、格式配置),用相同的 Sequence 模板(继承、rand、body、
uvm_do_with),就能轻松应对。 -
完全解耦:Sequence 只负责"造子弹",完全不知道 Driver 是怎么把子弹变成信号的。
-
闭环反馈 :通过
get_response(rsp),Sequence 能收到 Driver 的回执,知道子弹是否成功打出,甚至能拿到读寄存器的值。 -
软硬结合 :Sequence 里的
soft约束和local::变量,让顶层的virtual sequence(case0_vseq)可以像"调音师"一样,自由组合这些旋钮,创造出千变万化的测试场景(比如:ch0连发40包,ch1连发20包,同时改变 ch2 的包长度)。
你的实操任务:
去 QuestaSim 里跑一下 case0,把波形拉出来。找到 case0_vseq 里的这一句:
systemverilog
`uvm_do_on_with(chnl_seq, p_sequencer.chnl_sqrs[0], {ntrans == 40; ch_id==0; data_nidles==0; pkt_nidles==1; data_size==8; });
试着把这个约束里的 ntrans == 40 改成 ntrans == 5,重新编译跑一次。你会发现在波形里,ch0 的数据包数量肉眼可见地减少了。这就是 Sequence 控制力的体现------你写什么约束,DUT 就吃什么数据,一切尽在掌握中。
加油!你已经把 Sequence 机制吃得很透了。接下来就是 Scoreboard 和 Reference Model 的比对逻辑了,遇到不懂的随时扔过来,老师陪你一起啃!