在这一部分我们将完成对于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()中对随机化的变量进行约束; ::: ``systemverilogifndef 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

  1. ```systemverilog
  2. `ifndef AHB_MASTER_SINGLE_TRANS_SV
  3. `define AHB_MASTER_SINGLE_TRANS_SV
  4. class ahb_master_single_trans extends ahb_base_sequence;
  5. rand bit [`AHB_MAX_DATA_WIDTH-1 : 0] addr;
  6. rand bit [`AHB_MAX_DATA_WIDTH-1 : 0] data;
  7. rand xact_type_enum xact;
  8. rand burst_size_enum bsize;
  9. constraint single_trans_cstr {
  10. xact inside {READ, WRITE};
  11. }
  12. `uvm_object_utils(ahb_master_single_trans)
  13. function new (string name = "ahb_master_single_trans");
  14. super.new(name);
  15. endfunction
  16. virtual task body();
  17. `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
  18. `uvm_do_with(req, {data.size == 1;
  19. data[0] == data;
  20. burst_size == bsize;
  21. burst_type == SINGLE;
  22. xact_type == xact;
  23. })
  24. get_response(rsp);
  25. `uvm_info(get_type_name(),$psprintf("Done sequence : %s", req.convert2string()), UVM_HIGH)
  26. endtask : body
  27. endclass
  28. `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文件: :::

  1. `ifndef AHB_PKG_SV
  2. `define AHB_PKG_SV
  3. package ahb_pkg;
  4. import uvm_pkg::*;
  5. `include "uvm_macros.svh"
  6. `include "ahb_defines.svh"
  7. `include "ahb_types.sv"
  8. `include "ahb_configuration.sv"
  9. `include "ahb_agent_configuration.sv"
  10. `include "ahb_transaction.sv"
  11. `include "ahb_sequencer.sv"
  12. `include "ahb_driver.sv"
  13. `include "ahb_monitor.sv"
  14. `include "ahb_master_transaction.sv"
  15. `include "ahb_master_driver.sv"
  16. `include "ahb_master_monitor.sv"
  17. `include "ahb_master_sequencer.sv"
  18. `include "ahb_master_agent.sv"
  19. `include "ahb_sequence_lib.svh"
  20. endpackage
  21. `endif // AHB_PKG_SV

3.2 seq_lib中的element_seq

:::danger element_sequence的作用:

  1. 继承于它的子类将利用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头文件中: ::: ``systemverilogifndef AHB_RAM_ELEMENT_SEQUENCES_SVH `define AHB_RAM_ELEMENT_SEQUENCES_SVH

    include "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

  1. :::info
  2. 3)编辑ahb_ram_single_write_seq.sv文件:
  3. - 在这个seq中我们会将VIP_lib中的master_single_trans声明并挂载到p_sequencerahb_master_sequencer上;
  4. - 其中还会给定一些约束,需要在class中提前声明随机变量。
  5. :::
  6. ```systemverilog
  7. `ifndef AHB_RAM_SINGLE_WRITE_SEQ_SV
  8. `define AHB_RAM_SINGLE_WRITE_SEQ_SV
  9. class ahb_ram_single_write_seq extends ahb_ram_element_base_seq;
  10. rand bit [31:0] addr;
  11. rand bit [31:0] data;
  12. rand xact_type_enum xact;
  13. rand burst_size_enum bsize;
  14. constraint single_write_cstr {
  15. soft addr == [1:0];
  16. soft bsize == BURST_SIZE_32BIT;
  17. }
  18. `uvm_object_utils(ahb_ram_single_write_seq)
  19. function new (string name = "ahb_ram_single_write_seq");
  20. super.new(name);
  21. endfunction
  22. virtual task body();
  23. `uvm_info("body", "Entered...", UVM_LOW)
  24. ahb_master_single_trans ahb_single;
  25. `uvm_do_on_with(ahb_single, p_sequencer.ahb_mst_sqr
  26. {addr == local::addr;
  27. data == local::data;
  28. xact == WRITE;
  29. bsize == local::bsize;
  30. })
  31. `uvm_info("body", "Excited...", UVM_LOW)
  32. endtask
  33. endclass
  34. `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文件: :::

  1. `ifndef AHB_RAM_SINGLE_READ_SEQ_SV
  2. `define AHB_RAM_SINGLE_READ_SEQ_SV
  3. class ahb_ram_single_read_seq extends ahb_ram_element_base_seq;
  4. rand bit [31:0] addr;
  5. rand bit [31:0] data;
  6. rand xact_type_enum xact;
  7. rand burst_size_enum bsize;
  8. constraint ahb_single_cstr {
  9. soft addr[1:0] == 0;
  10. soft bsize == BURST_SIZE_32BIT;
  11. }
  12. `uvm_object_utils(ahb_ram_single_read_seq)
  13. function new (string name = "ahb_ram_single_read_seq");
  14. super.new(name);
  15. endfunction
  16. virtual task body();
  17. `uvm_info("body", "Entered...", UVM_LOW)
  18. ahb_master_single_trans ahb_single;
  19. `uvm_do_on_with(ahb_single, p_sequencer.ahb_mst_sqr
  20. {addr == local::addr;
  21. xact == local::READ;
  22. bsie == local::bsize;
  23. })
  24. data = ahb_single.data;
  25. `uvm_info("body", "Exited...", UVM_LOW)
  26. endtask
  27. endclass
  28. `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 :::

  1. `ifndef AHB_RAM_SEQ_LIB_SVH
  2. `define AHB_RAM_SEQ_LIB_SVH
  3. `include "ahb_ram_element_sequences.svh"
  4. `include "ahb_ram_base_virt_seq.sv"
  5. `include "ahb_ram_smoke_virt_seq.sv"
  6. `endif // AHB_RAM_SEQ_LIB_SVH

3.3 seq_lib中的其他sequence

:::info (1)在ahb_ram_base_virt_seq中声明所有的seq: :::

  1. `ifndef AHB_RAM_BASE_VIRT_SEQ_SV
  2. `define AHB_RAM_BASE_VIRT_SEQ_SV
  3. class ahb_ram_base_virt_seq extends uvm_sequence;
  4. ahb_ram_config cfg;
  5. virtual ahb_ram_if vif;
  6. ahb_ram_rgm rgm;
  7. bit [31:0] rd_val, wr_val;
  8. uvm_status_e status;
  9. // declaration of element sequence
  10. ahb_ram_single_write_seq single_write;
  11. ahb_ram_single_read_seq single_read;
  12. `uvm_object_utils(ahb_ram_base_virt_seq)
  13. `uvm_declare_p_sequencer(ahb_ram_virtual_sequencer)
  14. function new(string name = "ahb_ram_base_virt_seq");
  15. super.new(name);
  16. endfunction
  17. virtual task body();
  18. `uvm_info("body","Entered...",UVM_LOW)
  19. //Get cfg from p_sequencer
  20. cfg = p_sequencer.cfg;
  21. vif = cfg.vif;
  22. rgm = cfg.rgm;
  23. `uvm_info("body","Exiting...",UVM_LOW)
  24. endtask
  25. ...
  26. endclass
  27. `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方法。 ::: ``systemverilogifndef 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); enduvm_info(“body”,”EXited…”, UVM_LOW) endtask

endclass

`endif //AHB_RAM_SMOKE_VIRT_SEQ_SV

  1. <a name="yN1vU"></a>
  2. ## 3.3.1 【调试及结果】
  3. ![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的搭建。
  4. <a name="gHxBT"></a>
  5. ## 3.3.2 添加仿真后的波形文件
  6. :::info
  7. 添加ahb_ram_sim_run.do文件
  8. :::
  9. ![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)
  10. > 从波形上可以看出,write_data信号应当在复位完成,reset信号拉高以后再发送激励,此部分需要在virtual_sequence上解决;
  11. > 另外一个显而易见的问题就是write信号没有拉起来。
  12. ![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)
  13. > 从上面的波形中可以发现:WRITE信号并没有被作为激励发送出去,trans也跟我们给定的激励不一致。所以需要重新对激励进行检查
  14. 将上面这个波形保存为ahb_ram_sim_run.do文件,方便以后打开查看。
  15. <a name="s7khP"></a>
  16. ## 3.3.3 修改sequence
  17. 为了使得激励是在复位以后才发送的,我们需要等待复位信号释放以后,再等待若干拍最后发送激励,这两个过程可以封装到一个方法中:
  18. ```systemverilog
  19. `ifndef AHB_RAM_BASE_VIRT_SEQ_SV
  20. `define AHB_RAM_BASE_VIRT_SEQ_SV
  21. class ahb_ram_base_virt_seq extends uvm_sequence;
  22. ahb_ram_config cfg;
  23. virtual ahb_ram_if vif;
  24. ahb_ram_rgm rgm;
  25. bit [31:0] rd_val, wr_val;
  26. uvm_status_e status;
  27. // declaration of element sequence
  28. ahb_ram_single_write_seq single_write;
  29. ahb_ram_single_read_seq single_read;
  30. `uvm_object_utils(ahb_ram_base_virt_seq)
  31. `uvm_declare_p_sequencer(ahb_ram_virtual_sequencer)
  32. function new(string name = "ahb_ram_base_virt_seq");
  33. super.new(name);
  34. endfunction
  35. virtual task body();
  36. `uvm_info("body","Entered...",UVM_LOW)
  37. //Get cfg from p_sequencer
  38. cfg = p_sequencer.cfg;
  39. vif = cfg.vif;
  40. rgm = cfg.rgm;
  41. `uvm_info("body","Exiting...",UVM_LOW)
  42. endtask
  43. virtual function void compare_data(logic[31:0] val1, logic[31:0] val2);
  44. cfg.seq_check_count++;
  45. if(val1 === val2)
  46. `uvm_info("CMPSUC", $sformatf("val1 'h%0x === val2 'h%0x", val1, val2), UVM_LOW)
  47. else begin
  48. cfg.seq_check_error++;
  49. `uvm_error("CMPSUC", $sformatf("val1 'h%0x !== val2 'h%0x", val1, val2))
  50. end
  51. endfunction
  52. task wait_reset_asserted();
  53. @(posedge vif.rst_n);
  54. endtask
  55. task wait_reset_released();
  56. @(negedge vif.rst_n);
  57. endtask
  58. task wait_cycles(int n=1);
  59. repeat(n) @(posedge vif.clk);
  60. endtask
  61. task wait_ready_for_stim();
  62. wait_reset_released();
  63. wait_cycles();
  64. endtask
  65. endclass
  66. `endif //AHB_RAM_BASE_VIRT_SEQ

为了检查激励为什么没有传进去,使用的思路是TOP_DOWN:也就是自顶向下进行检查,首先需要检查的就是smoke_virt_seq。 :::info

  1. 先检查随机化以后的值;
  2. 随机化后的值符合我们给定的期望的话,就向自一层的seq进一步筛查;
  3. 对seq筛查完毕以后,如果波形上给定的激励还是不对的话,那么可以从driver层面再次检查激励中的数值。 ::: image.png

    设置断点以后,可以发现随机化后wr_val的值是1,与约束条件wr_val == (i << 4) +1相同,addr后两位也是0。 双击到ahb_ram_single_write_seq以后再次设置断点

image.png
再次双击进入到VIP_lib的seq_lib中的ahb_master_single_transaction中:
image.png

可以看到在约束条件中我们忘了给addr。

:::info (4)在vip_lib中的ahb_ram_single_transaction.sv文件中的约束条件中添加对于addr的约束。 :::

  1. `ifndef AHB_MASTER_SINGLE_TRANS_SV
  2. `define AHB_MASTER_SINGLE_TRANS_SV
  3. class ahb_master_single_trans extends ahb_base_sequence;
  4. rand bit [`AHB_MAX_DATA_WIDTH-1 : 0] addr;
  5. rand bit [`AHB_MAX_DATA_WIDTH-1 : 0] data;
  6. rand xact_type_enum xact;
  7. rand burst_size_enum bsize;
  8. constraint single_trans_cstr {
  9. xact inside {READ, WRITE};
  10. }
  11. `uvm_object_utils(ahb_master_single_trans)
  12. function new (string name = "ahb_master_single_trans");
  13. super.new(name);
  14. endfunction
  15. virtual task body();
  16. `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
  17. `uvm_do_with(req, {addr == local::addr;
  18. data.size == 1;
  19. data[0] == local::data;
  20. burst_size == bsize;
  21. burst_type == SINGLE;
  22. xact_type == xact;
  23. })
  24. get_response(rsp);
  25. if(xact == READ) begin
  26. data = rsp.data[0];
  27. end
  28. `uvm_info(get_type_name(),$psprintf("Done sequence : %s", req.convert2string()), UVM_HIGH)
  29. endtask : body
  30. endclass
  31. `endif // AHB_MASTER_SINGLE_TRANS_SV

在class中输入master_driver并在drive_transfer的方法该位置设置断点:
image.png
可以看到addr的值和seq一侧中激励的值一致,xact_type也是write类型的。
双击driver_transfer进入到子类中并设置断点。
image.png

检查发现是ahb_master_driver这个子类中在进行SINGLE数据发送的时候变量名错误,应该是xact_type而非trans_type。


重新仿真以后的结果如下,结合ahb_master_driver中的代码发现:

  • 在做do_init_write()的时候,先等了一拍以后将trans的内容发送到接口上;
  • 之后再等了一拍将wrdata发送出去。

回顾时序图发现,第二拍中htrans的状态变成了BUSY而不是NSEQ,也就是说在第二拍的时候我们少做了操作:
image.png

在T7-T8的时候,上一次传输的数据已经完成,此时应当置于IDLE的状态,但是HADDR可以保持不变。 如果对于一个SINGLE数据而言,在第二拍发送完数据以后应当处于一个IDLE的状态,所以将这个逻辑做到ahb_ram_master_driver中。

image.png

:::info

【回顾整个激励发送的写过程】

  1. 父类ahb_driver调用drive_transfer(req);
  2. 实际上调用的是子类ahb_master_driver的drive_transfer,原因是使用使用了virtual关键字,使得父类的句柄能够指向子类的方法;
  3. 通过case语句判断并调用用于SINGLE激励的do_atomic_trans(req t)方法;
  4. 进行写操作do_init_write();
    1. 等待一个时钟信号的上升沿并在这个时刻进行一些驱动;
    2. 再等一拍以后进行一个IDLE的写操作,同时将transaction中的data给到接口上。 ::: image.png

ps: 如果在给定激励的过程中发现wdata的数据并不是随机化以后的数据而始终保持为0,那么debug的方法就是先给一个确定的激励而非一个随机化的数值,此时观察结果是否一一致。

  1. `ifndef AHB_RAM_SMOKE_VIRT_SEQ_SV
  2. `define AHB_RAM_SMOKE_VIRT_SEQ_SV
  3. class ahb_ram_smoke_virt_seq extends ahb_ram_base_virt_seq;
  4. `uvm_object_utils(ahb_ram_smoke_virt_seq)
  5. function new(string name = "ahb_ram_smoke_virt_seq");
  6. super.new(name);
  7. endfunction
  8. virtual task body();
  9. bit [31:0] addr, data;
  10. super.body();
  11. `uvm_info("body","Entered...",UVM_LOW)
  12. for(int i=0; i<10; i++) begin
  13. std::randomize(addr) with {addr[1:0] == 0; addr inside {['h1000:'h1FFF]};};
  14. std::randomize(wr_val) with {wr_val == (i << 4) + i;};
  15. `uvm_do_with(single_write, {addr == local::addr; data == wr_val;})
  16. `uvm_do_with(single_read, {addr == local::addr;})
  17. rd_val = single_read.data;
  18. compare_data(wr_val, rd_val);
  19. end
  20. `uvm_info("body","EXited...", UVM_LOW)
  21. endtask
  22. endclass
  23. `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先传到这个局部变量中,之后再约束的过程中将这个局部变量再进行传递。
image.png
image.png
image.png

经过修改重新编译以后可以发现:写入的数据是0011,在时钟信号下降沿读出来的数据也是0011,写入的数值和读出的数值一样,case可以正常通过。