在这一部分我们将完成对于sequence_layer层次的搭建
3.1 vip_lib中的sequence
:::info
(1)在VIP_lib中创建sequence_lib目录,并在其中补充ahb_sequence_lib.svh文件;
创建ahb_base_sequence.sv文件;
创建ahb_master_single_trans.sv:
- 对该sequence中的一些变量进行随机化的声明;
- 在uvm_do_with()中对随机化的变量进行约束;
:::
``systemverilog
ifndef AHB_BASE_SEQUENCE_SV `define AHB_BASE_SEQUENCE_SV
class ahb_base_sequence extends uvm_sequence #(ahb_transaction);
`uvm_object_utils(ahb_base_sequence) function new (string name = “ahb_base_sequence”); super.new(name); endfunction
endclass
`endif // AHB_BASE_SEQUENCE_SV
```systemverilog
`ifndef AHB_MASTER_SINGLE_TRANS_SV
`define AHB_MASTER_SINGLE_TRANS_SV
class ahb_master_single_trans extends ahb_base_sequence;
rand bit [`AHB_MAX_DATA_WIDTH-1 : 0] addr;
rand bit [`AHB_MAX_DATA_WIDTH-1 : 0] data;
rand xact_type_enum xact;
rand burst_size_enum bsize;
constraint single_trans_cstr {
xact inside {READ, WRITE};
}
`uvm_object_utils(ahb_master_single_trans)
function new (string name = "ahb_master_single_trans");
super.new(name);
endfunction
virtual task body();
`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
`uvm_do_with(req, {data.size == 1;
data[0] == data;
burst_size == bsize;
burst_type == SINGLE;
xact_type == xact;
})
get_response(rsp);
`uvm_info(get_type_name(),$psprintf("Done sequence : %s", req.convert2string()), UVM_HIGH)
endtask : body
endclass
`endif // AHB_MASTER_SINGLE_TRANS_SV
transaction中包含了data,addr,burst_type,burst_size,xact_type,其他的变量我们暂不声明为随机变量。
:::danger
【重点 & 思考】
req和rsp是uvm_sequence预定义两个变量,它的类型就是ahb_transaction【原因是ahb_base_sequence在集成uvm_sequence的时候,这个参数类的类型就是ahb_transaction】,所以不需要再次进行声明。
:::
:::info
(2)更新编译文件:
更新Makefile:将新建的sequence_lib路径插入到Makefile文件中;
更新pkg文件:
:::
`ifndef AHB_PKG_SV
`define AHB_PKG_SV
package ahb_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "ahb_defines.svh"
`include "ahb_types.sv"
`include "ahb_configuration.sv"
`include "ahb_agent_configuration.sv"
`include "ahb_transaction.sv"
`include "ahb_sequencer.sv"
`include "ahb_driver.sv"
`include "ahb_monitor.sv"
`include "ahb_master_transaction.sv"
`include "ahb_master_driver.sv"
`include "ahb_master_monitor.sv"
`include "ahb_master_sequencer.sv"
`include "ahb_master_agent.sv"
`include "ahb_sequence_lib.svh"
endpackage
`endif // AHB_PKG_SV
3.2 seq_lib中的element_seq
:::danger element_sequence的作用:
继承于它的子类将利用VIP自己的sequence发送一个single_write和single_read,完成了sequence在环境中的嵌套。 ::: :::info (1)在element_seq中添加以下文件:
ahb_ram_element_base_seq.sv; ahb_ram_element_sequences.svh; ahb_ram_single_write_seq.sv;ahb_ram_single_read_seq.sv ::: :::info (2)将添加的这些seq.sv文件导入到.svh头文件中: :::``systemverilog
ifndef AHB_RAM_ELEMENT_SEQUENCES_SVH `define AHB_RAM_ELEMENT_SEQUENCES_SVHinclude "ahb_ram_element_base_seq.sv"
include “ahb_ram_single_read_seq.sv” `include “ahb_ram_single_read_seq.sv”
`endif // AHB_RAM_ELEMENT_SEQUENCES_SVH
:::info
(3)编辑ahb_ram_single_write_seq.sv文件:
- 在这个seq中我们会将VIP_lib中的master_single_trans声明并挂载到p_sequencer上ahb_master_sequencer上;
- 其中还会给定一些约束,需要在class中提前声明随机变量。
:::
```systemverilog
`ifndef AHB_RAM_SINGLE_WRITE_SEQ_SV
`define AHB_RAM_SINGLE_WRITE_SEQ_SV
class ahb_ram_single_write_seq extends ahb_ram_element_base_seq;
rand bit [31:0] addr;
rand bit [31:0] data;
rand xact_type_enum xact;
rand burst_size_enum bsize;
constraint single_write_cstr {
soft addr == [1:0];
soft bsize == BURST_SIZE_32BIT;
}
`uvm_object_utils(ahb_ram_single_write_seq)
function new (string name = "ahb_ram_single_write_seq");
super.new(name);
endfunction
virtual task body();
`uvm_info("body", "Entered...", UVM_LOW)
ahb_master_single_trans ahb_single;
`uvm_do_on_with(ahb_single, p_sequencer.ahb_mst_sqr
{addr == local::addr;
data == local::data;
xact == WRITE;
bsize == local::bsize;
})
`uvm_info("body", "Excited...", UVM_LOW)
endtask
endclass
`endif // AHB_RAM_SINGLE_WRITE_SEQ_SV
这里的p_sequencer已经在ahb_ram_element_base_seq这个父类中声明过了,所以此处直接使用即可。
:::danger
注:
在使用uvm_do_on_with宏的时候我们会对这个sequence添加一定的约束,与VIP_LIB中seq不同的是,我们这里的sequence是阅读spec文档以后对某一个具体的DUT进行测试,所以数据和地址的位宽都是确定的。而VIP是可能被用在任何地方的,所以我们使用了
AHB_MAX_DATA_WIDTH这个宏。
:::
:::info
(4)同样的,我们也编辑ahb_ram_single_read_seq.sv文件:
:::
`ifndef AHB_RAM_SINGLE_READ_SEQ_SV
`define AHB_RAM_SINGLE_READ_SEQ_SV
class ahb_ram_single_read_seq extends ahb_ram_element_base_seq;
rand bit [31:0] addr;
rand bit [31:0] data;
rand xact_type_enum xact;
rand burst_size_enum bsize;
constraint ahb_single_cstr {
soft addr[1:0] == 0;
soft bsize == BURST_SIZE_32BIT;
}
`uvm_object_utils(ahb_ram_single_read_seq)
function new (string name = "ahb_ram_single_read_seq");
super.new(name);
endfunction
virtual task body();
`uvm_info("body", "Entered...", UVM_LOW)
ahb_master_single_trans ahb_single;
`uvm_do_on_with(ahb_single, p_sequencer.ahb_mst_sqr
{addr == local::addr;
xact == local::READ;
bsie == local::bsize;
})
data = ahb_single.data;
`uvm_info("body", "Exited...", UVM_LOW)
endtask
endclass
`endif // AHB_RAM_SINGLE_READ_SEQ_SV
- 与write不同的是:read_seq中的数据是从总线上读下来的,因此data就是ahb_single这个transaciton中的data。
- 而在ahb_master_single_trans中,当判断到当前transaction的操作是读操作以后,就会从返回的rsp transaction上读取数据到声明的变量中。
:::info (5)更新编译文件的顺序:ahb_ram_seq_lib.svh及Makefile :::
`ifndef AHB_RAM_SEQ_LIB_SVH
`define AHB_RAM_SEQ_LIB_SVH
`include "ahb_ram_element_sequences.svh"
`include "ahb_ram_base_virt_seq.sv"
`include "ahb_ram_smoke_virt_seq.sv"
`endif // AHB_RAM_SEQ_LIB_SVH
3.3 seq_lib中的其他sequence
:::info (1)在ahb_ram_base_virt_seq中声明所有的seq: :::
`ifndef AHB_RAM_BASE_VIRT_SEQ_SV
`define AHB_RAM_BASE_VIRT_SEQ_SV
class ahb_ram_base_virt_seq extends uvm_sequence;
ahb_ram_config cfg;
virtual ahb_ram_if vif;
ahb_ram_rgm rgm;
bit [31:0] rd_val, wr_val;
uvm_status_e status;
// declaration of element sequence
ahb_ram_single_write_seq single_write;
ahb_ram_single_read_seq single_read;
`uvm_object_utils(ahb_ram_base_virt_seq)
`uvm_declare_p_sequencer(ahb_ram_virtual_sequencer)
function new(string name = "ahb_ram_base_virt_seq");
super.new(name);
endfunction
virtual task body();
`uvm_info("body","Entered...",UVM_LOW)
//Get cfg from p_sequencer
cfg = p_sequencer.cfg;
vif = cfg.vif;
rgm = cfg.rgm;
`uvm_info("body","Exiting...",UVM_LOW)
endtask
...
endclass
`endif //AHB_RAM_BASE_VIRT_SEQ
:::info (2)通过3.1和3.2我们就完成了sequence代码中的基本搭建,并将他们集成在上一层的代码中,接下来就是对smoke_test的这个sequence进行编辑:
- 使用uvm_do挂载write_sequence。给定的约束就是地址,写入的数据是我们随机化以后的数据;
- 挂载read_sequence,给定的约束就是地址;
- 根据前两点我们可以发现:无论是写还是读,地址都作为一个随机变量进行操作,所以我们对它进行随机化,低两位为0以保证地址对齐;
- 同样,对于写操作而言,我们也需要对它进行随机化过程;
- 将写入的数据和读出来的数据就可以进行比较,比较的方法是base_virt_seq中声明的compare方法。
:::
``systemverilog
ifndef AHB_RAM_SMOKE_VIRT_SEQ_SV `define AHB_RAM_SMOKE_VIRT_SEQ_SV
class ahb_ram_smoke_virt_seq extends ahb_ram_base_virt_seq;
`uvm_object_utils(ahb_ram_smoke_virt_seq)
function new(string name = “ahb_ram_smoke_virt_seq”); super.new(name); endfunction
virtual task body();
bit [31:0] addr;
super.body();
uvm_info("body","Entered...",UVM_LOW)
for(int i=0;i<10;i=i+1) begin
std::randomize(addr) with {addr[1:0] == 0; addr inside {['h1000:'h1FFF]};}
std::randomize(wr_val) with {wr_val == (i << 4) +1;}
uvm_do_with(single_write, {addr == local::addr; data == wr_val})
uvm_do_with(single_read, {addr == local::addr;})
rd_val == single_read.data;
compare_data(wr_val, rd_val);
end
uvm_info(“body”,”EXited…”, UVM_LOW)
endtask
endclass
`endif //AHB_RAM_SMOKE_VIRT_SEQ_SV
<a name="yN1vU"></a>
## 3.3.1 【调试及结果】
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22348254/1656674514445-13781c16-c4c8-4c31-8adb-fc078fd4686b.png#clientId=ub4d52e77-0311-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=637&id=ue9fb6640&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1274&originWidth=2005&originalType=binary&ratio=1&rotation=0&showTitle=false&size=215187&status=done&style=none&taskId=uaceec593-c06f-484c-bad5-c523cd08bd9&title=&width=1002.5)<br />使用DVT进行编译的时候一定要注意编译的顺序:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22348254/1656725793358-32b9b4be-ff79-45a8-8194-055ce472a737.png#clientId=ub4d52e77-0311-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=365&id=ub66b445b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=729&originWidth=1464&originalType=binary&ratio=1&rotation=0&showTitle=false&size=181058&status=done&style=none&taskId=u7bd0cd80-82ec-4afe-8984-2229fa00099&title=&width=732)<br />由于不是使用Makefile进行的编译,所以DVT编译的时候需要在default.build中编辑编译的顺序。<br />这样就完成了对write和read的搭建。
<a name="gHxBT"></a>
## 3.3.2 添加仿真后的波形文件
:::info
添加ahb_ram_sim_run.do文件
:::
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22348254/1656727223785-9df606f5-9996-4501-b7f4-a900d9920da1.png#clientId=ub4d52e77-0311-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=436&id=uc600da9f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=871&originWidth=2874&originalType=binary&ratio=1&rotation=0&showTitle=false&size=124488&status=done&style=none&taskId=uc06d14e7-79a5-475c-8363-e7033936f57&title=&width=1437)
> 从波形上可以看出,write_data信号应当在复位完成,reset信号拉高以后再发送激励,此部分需要在virtual_sequence上解决;
> 另外一个显而易见的问题就是write信号没有拉起来。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22348254/1656728647486-6b8e6abf-9682-412a-89e8-b2c78de5b9f2.png#clientId=ub4d52e77-0311-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=702&id=u9076a5e6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1404&originWidth=2880&originalType=binary&ratio=1&rotation=0&showTitle=false&size=273531&status=done&style=none&taskId=uce059baf-9d03-4f25-bdc6-1324523c154&title=&width=1440)
> 从上面的波形中可以发现:WRITE信号并没有被作为激励发送出去,trans也跟我们给定的激励不一致。所以需要重新对激励进行检查
将上面这个波形保存为ahb_ram_sim_run.do文件,方便以后打开查看。
<a name="s7khP"></a>
## 3.3.3 修改sequence
为了使得激励是在复位以后才发送的,我们需要等待复位信号释放以后,再等待若干拍最后发送激励,这两个过程可以封装到一个方法中:
```systemverilog
`ifndef AHB_RAM_BASE_VIRT_SEQ_SV
`define AHB_RAM_BASE_VIRT_SEQ_SV
class ahb_ram_base_virt_seq extends uvm_sequence;
ahb_ram_config cfg;
virtual ahb_ram_if vif;
ahb_ram_rgm rgm;
bit [31:0] rd_val, wr_val;
uvm_status_e status;
// declaration of element sequence
ahb_ram_single_write_seq single_write;
ahb_ram_single_read_seq single_read;
`uvm_object_utils(ahb_ram_base_virt_seq)
`uvm_declare_p_sequencer(ahb_ram_virtual_sequencer)
function new(string name = "ahb_ram_base_virt_seq");
super.new(name);
endfunction
virtual task body();
`uvm_info("body","Entered...",UVM_LOW)
//Get cfg from p_sequencer
cfg = p_sequencer.cfg;
vif = cfg.vif;
rgm = cfg.rgm;
`uvm_info("body","Exiting...",UVM_LOW)
endtask
virtual function void compare_data(logic[31:0] val1, logic[31:0] val2);
cfg.seq_check_count++;
if(val1 === val2)
`uvm_info("CMPSUC", $sformatf("val1 'h%0x === val2 'h%0x", val1, val2), UVM_LOW)
else begin
cfg.seq_check_error++;
`uvm_error("CMPSUC", $sformatf("val1 'h%0x !== val2 'h%0x", val1, val2))
end
endfunction
task wait_reset_asserted();
@(posedge vif.rst_n);
endtask
task wait_reset_released();
@(negedge vif.rst_n);
endtask
task wait_cycles(int n=1);
repeat(n) @(posedge vif.clk);
endtask
task wait_ready_for_stim();
wait_reset_released();
wait_cycles();
endtask
endclass
`endif //AHB_RAM_BASE_VIRT_SEQ
为了检查激励为什么没有传进去,使用的思路是TOP_DOWN:也就是自顶向下进行检查,首先需要检查的就是smoke_virt_seq。 :::info
- 先检查随机化以后的值;
- 随机化后的值符合我们给定的期望的话,就向自一层的seq进一步筛查;
- 对seq筛查完毕以后,如果波形上给定的激励还是不对的话,那么可以从driver层面再次检查激励中的数值。
:::
设置断点以后,可以发现随机化后wr_val的值是1,与约束条件wr_val == (i << 4) +1相同,addr后两位也是0。 双击到ahb_ram_single_write_seq以后再次设置断点
再次双击进入到VIP_lib的seq_lib中的ahb_master_single_transaction中:
可以看到在约束条件中我们忘了给addr。
:::info (4)在vip_lib中的ahb_ram_single_transaction.sv文件中的约束条件中添加对于addr的约束。 :::
`ifndef AHB_MASTER_SINGLE_TRANS_SV
`define AHB_MASTER_SINGLE_TRANS_SV
class ahb_master_single_trans extends ahb_base_sequence;
rand bit [`AHB_MAX_DATA_WIDTH-1 : 0] addr;
rand bit [`AHB_MAX_DATA_WIDTH-1 : 0] data;
rand xact_type_enum xact;
rand burst_size_enum bsize;
constraint single_trans_cstr {
xact inside {READ, WRITE};
}
`uvm_object_utils(ahb_master_single_trans)
function new (string name = "ahb_master_single_trans");
super.new(name);
endfunction
virtual task body();
`uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
`uvm_do_with(req, {addr == local::addr;
data.size == 1;
data[0] == local::data;
burst_size == bsize;
burst_type == SINGLE;
xact_type == xact;
})
get_response(rsp);
if(xact == READ) begin
data = rsp.data[0];
end
`uvm_info(get_type_name(),$psprintf("Done sequence : %s", req.convert2string()), UVM_HIGH)
endtask : body
endclass
`endif // AHB_MASTER_SINGLE_TRANS_SV
在class中输入master_driver并在drive_transfer的方法该位置设置断点:
可以看到addr的值和seq一侧中激励的值一致,xact_type也是write类型的。
双击driver_transfer进入到子类中并设置断点。
检查发现是ahb_master_driver这个子类中在进行SINGLE数据发送的时候变量名错误,应该是xact_type而非trans_type。
重新仿真以后的结果如下,结合ahb_master_driver中的代码发现:
- 在做do_init_write()的时候,先等了一拍以后将trans的内容发送到接口上;
- 之后再等了一拍将wrdata发送出去。
回顾时序图发现,第二拍中htrans的状态变成了BUSY而不是NSEQ,也就是说在第二拍的时候我们少做了操作:
在T7-T8的时候,上一次传输的数据已经完成,此时应当置于IDLE的状态,但是HADDR可以保持不变。 如果对于一个SINGLE数据而言,在第二拍发送完数据以后应当处于一个IDLE的状态,所以将这个逻辑做到ahb_ram_master_driver中。
【回顾整个激励发送的写过程】
- 父类ahb_driver调用drive_transfer(req);
- 实际上调用的是子类ahb_master_driver的drive_transfer,原因是使用使用了virtual关键字,使得父类的句柄能够指向子类的方法;
- 通过case语句判断并调用用于SINGLE激励的do_atomic_trans(req t)方法;
- 进行写操作do_init_write();
- 等待一个时钟信号的上升沿并在这个时刻进行一些驱动;
- 再等一拍以后进行一个IDLE的写操作,同时将transaction中的data给到接口上。 :::
ps: 如果在给定激励的过程中发现wdata的数据并不是随机化以后的数据而始终保持为0,那么debug的方法就是先给一个确定的激励而非一个随机化的数值,此时观察结果是否一一致。
`ifndef AHB_RAM_SMOKE_VIRT_SEQ_SV
`define AHB_RAM_SMOKE_VIRT_SEQ_SV
class ahb_ram_smoke_virt_seq extends ahb_ram_base_virt_seq;
`uvm_object_utils(ahb_ram_smoke_virt_seq)
function new(string name = "ahb_ram_smoke_virt_seq");
super.new(name);
endfunction
virtual task body();
bit [31:0] addr, data;
super.body();
`uvm_info("body","Entered...",UVM_LOW)
for(int i=0; i<10; i++) begin
std::randomize(addr) with {addr[1:0] == 0; addr inside {['h1000:'h1FFF]};};
std::randomize(wr_val) with {wr_val == (i << 4) + i;};
`uvm_do_with(single_write, {addr == local::addr; data == wr_val;})
`uvm_do_with(single_read, {addr == local::addr;})
rd_val = single_read.data;
compare_data(wr_val, rd_val);
end
`uvm_info("body","EXited...", UVM_LOW)
endtask
endclass
`endif //AHB_RAM_SMOKE_VIRT_SEQ_SV
- 测试过程中wr_val没有被vcs认出来,因此给定的wr_val始终为0,由于smoke_virt_seq是继承于base_virt_seq的,所以这个wr_val是之前声明过的,在这里不用再声明。
- 但是给定一个确定的数值比如’hff的时候是可以认出来的。
- 被随机化的变量是成员变量data和addr,这两个成员变量在ahb_single_write_seq和read_seq已经声明成了随机变量,wr_val和addr不需要声明成随机化变量,只作为约束。这跟对一个变量进行随机化是不一样的。
解决方法是声明一个局部变量data,将成员变量wr_val先传到这个局部变量中,之后再约束的过程中将这个局部变量再进行传递。
经过修改重新编译以后可以发现:写入的数据是0011,在时钟信号下降沿读出来的数据也是0011,写入的数值和读出的数值一样,case可以正常通过。