【总结】

  1. 需要明确的一点是,我们在这里是对ahb_master_monitor进行实现,从vif中采集数据并传递到subsriber\scoreboard中;
  2. 对于收集数据向其他组件发送的这些函数,我们是在monitor中进行实现的;
  3. 但是在整个环境中我们使用的是ahb_master_monitor,因为在agent中我们已经将它和sequencer连接在了一起,也就是说在agnet中已经完成了对ahb_master_monitor的例化。

4.1 优化ahb_driver时序

image.png

  • 根据上一节的内容我们知道write和read操作都是通过两拍来实现的,为了使得时序更加紧凑我们需要对driver进行一些微调:

我们暂时先不考虑do_proc_write()和do_proc_read()这两个情况,所以将他们里面的idle操作删除,另外删除read动作中多余的一个一拍。 :::info 为什么在这里需要优化driver的时序呢?
原因是在符合时序的状态下我们去除掉一些多余的时序,尽可能地使得它理想,因为在之后的burst下要实现连续读和连续写的操作,使得发送的数据更紧凑,避免loose的情况出现;
如果想要在发送完数据以后添加一些idle的时序该怎么做呢?
我们可以在transaction中插入一些成员来实现对idle数量的控制;
或者在write_transaction和read中插入一些idle,但是对driver时序的调优是一定要完成的。 ::: image.png

调整以后的时序变得更加紧凑

4.2 ahb_master_monitor

与driver不同的是:driver中我们通常带有带着驱动的环节也就是drive_transaction出现,但是在monitor中我们不需要这样的驱动,直接从vif拿信号即可。所以一般用的都是vif中的monitor_clocking来采样数据。 如果使用了clocking块来进行采样和驱动,那么在之后的环节中就应当一直使用它。

:::info monitor中需要执行以下几个函数:

  1. collect_transaction(从vif一侧采样transaction):先创建再采样之后return t作为output;
  2. monitor_transaction:拿到从collect_transaction中采集到的ahb_transaction并通过port广播出去。注意这个过程是forever的,也就是说是不断需要采样数据的。
  3. 采样逻辑:
    1. monitor_trans_start()和monitor_trans_proc:这两个方法中的形参都是作为input,原因是ahb_transaction在collect_transaction中创建过了,因此只需要记录数据即可;
    2. 那么记录的数据是从哪里来的呢?根据uvm_monitor的作用我们知道monitor用来观测DUT的interface并采样总线的信息。

⚓思考:

  • 对于一个NSEQ的动作而言,在之前的single_write中我们已经知道了它是在write拉高以后的下一拍才会发送数据出去;
  • 那么如果是一个burst的情况,由于ahb是一个follow_order的情况,不会出现乱序的类型,一个写出去的数据在下一拍有可能被接收也有可能没有。 :::

在这里我们首先考虑对NSEQ数据的采:

  • 我们回顾一下ahb_transaction中有哪些成员变量? ``systemverilogifndef AHB_TRANSACTION_SV `define AHB_TRANSACTION_SV

class ahb_transaction extends uvm_sequence_item;

rand bit [AHB_MAX_DATA_WIDTH - 1:0] data[]; rand bit [AHB_MAX_DATA_WIDTH - 1:0] addr = 0;

rand burst_size_enum burst_size = BURST_SIZE_8BIT;

rand burst_type_enum burst_type = SINGLE;

rand xact_type_enum xact_type = IDLE_XACT;

rand response_type_enum response_type = OKAY;

trans_type_enum trans_type;

response_type_enum all_beat_response[];

int current_data_beat_num;

status_enum status = INITIAL;

rand bit idle_xact_hwrite = 1;

function new (string name = “ahb_transaction”); super.new(name); endfunction

endclass

`endif // AHB_TRANSACTION_SV

  1. - 接下来判断transaction的类型并对成员变量进行采集:
  2. - 判断当前的transaction是否是NSEQ类型的;
  3. - 判断完以后看当前的这个transaction进行的是读操作还是写操作,将vif上的hwrite转换成枚举类型然后放到句柄t中;
  4. - 接下来就是data这个成员变量,我们将采集到的新的data数据放到拓展后的数组中;
  5. - 之后就是对trans_typecurrent_data_beat_num也放到句柄t中;
  6. - 最后我们将新采集到的all_beat_response放到拓展后的数组中。
  7. ```systemverilog
  8. `ifndef AHB_MONITOR_SV
  9. `define AHB_MONITOR_SV
  10. class ahb_monitor extends uvm_monitor;
  11. uvm_analysis_port #(ahb_transaction) item_observed_port;
  12. `uvm_component_utils(ahb_monitor)
  13. function new (string name = "ahb_monitor", uvm_component parent = null);
  14. super.new(name,parent);
  15. item_observed_port = new("item_observed_port", this);
  16. endfunction
  17. function void build_phase(uvm_phase phase);
  18. super.build_phase(phase);
  19. endfunction
  20. function void connect_phase(uvm_phase phase);
  21. super.connect_phase(phase);
  22. endfunction
  23. task run_phase(uvm_phase phase);
  24. super.run_phase(phase);
  25. fork
  26. monitor_transaction();
  27. join_none
  28. endtask
  29. task monitor_transaction();
  30. ahb_transaction t;
  31. forever begin
  32. collect_transaction(t);
  33. item_observed_port.write(t);
  34. end
  35. endtask
  36. task collect_transaction(output ahb_transaction t);
  37. // collect transaction from interfacena
  38. t = ahb_transaction::type_id::create("ahb_transaction");
  39. monitor_transaction_start(t);
  40. monitor_transaction_proc(t);
  41. endtask
  42. task monitor_transaction_start(ahb_transaction t);
  43. @(vif.cb_mon iff vif.cb_mon.trans_type == NSEQ);
  44. t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
  45. fork
  46. begin
  47. t.data = new[1];
  48. t.data[0] = t.xact_type = WRITE ? vif.cb_mon.hwdata : vif.cb_mon.hrdata;
  49. t.trans_type = NSEQ;
  50. t.current_data_beat_num = 0;
  51. t.all_beat_response = new[1];
  52. t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.cb_mon.hresp);
  53. end
  54. join_none
  55. endtask
  56. task monitor_transaction_proc(ahb_transaction t);
  57. endtask
  58. endclass
  59. `endif // AHB_MONITOR_SV

在51、52和55、56行中,我们都对data和all_beat_reponse这两个数组进行了拓展并将原来的数据赋值到拓展后的数组中。 数据是一个一个捕捉并进行扩展的,因此可以在ahb_transaction中声明一个function来完成。

  1. `ifndef AHB_TRANSACTION_SV
  2. `define AHB_TRANSACTION_SV
  3. class ahb_transaction extends uvm_sequence_item;
  4. ...
  5. function new (string name = "ahb_transaction");
  6. super.new(name);
  7. endfunction
  8. function void increase_data(int n=1);
  9. data = new[data.size + 1] data;
  10. all_beat_response = new[all_beat_response.size + 1] all_beat_response;
  11. endfunction
  12. endclass
  13. `endif // AHB_TRANSACTION_SV

声明好这个方法以后就可以对数据采集中的一些语句进行替换:

  1. `ifndef AHB_MONITOR_SV
  2. `define AHB_MONITOR_SV
  3. class ahb_monitor extends uvm_monitor;
  4. uvm_analysis_port #(ahb_transaction) item_observed_port;
  5. `uvm_component_utils(ahb_monitor)
  6. function new (string name = "ahb_monitor", uvm_component parent = null);
  7. super.new(name,parent);
  8. item_observed_port = new("item_observed_port", this);
  9. endfunction
  10. function void build_phase(uvm_phase phase);
  11. super.build_phase(phase);
  12. endfunction
  13. function void connect_phase(uvm_phase phase);
  14. super.connect_phase(phase);
  15. endfunction
  16. task run_phase(uvm_phase phase);
  17. super.run_phase(phase);
  18. fork
  19. monitor_transaction();
  20. join_none
  21. endtask
  22. task monitor_transaction();
  23. ahb_transaction t;
  24. forever begin
  25. collect_transaction(t);
  26. item_observed_port.write(t);
  27. end
  28. endtask
  29. task collect_transaction(output ahb_transaction t);
  30. // collect transaction from interfacena
  31. t = ahb_transaction::type_id::create("ahb_transaction");
  32. monitor_transaction_start(t);
  33. monitor_transaction_proc(t);
  34. endtask
  35. task monitor_transaction_start(ahb_transaction t);
  36. @(vif.cb_mon iff vif.cb_mon.trans_type == NSEQ);
  37. t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
  38. fork
  39. begin
  40. @(vif.cb_mon iff vif.cb_mon.hready);
  41. t.increase_data();
  42. t.data[0] = t.xact_type = WRITE ? vif.cb_mon.hwdata : vif.cb_mon.hrdata;
  43. t.trans_type = NSEQ;
  44. t.current_data_beat_num = 0;
  45. t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.cb_mon.hresp);
  46. end
  47. join_none
  48. endtask
  49. task monitor_transaction_proc(ahb_transaction t);
  50. endtask
  51. endclass
  52. `endif // AHB_MONITOR_SV

代码重构:
之前我们探讨了在NSEQ状态下采集数据的情况,那么对于发送完NSEQ数据以后的IDLE状态,该状态下的数据又该如何采集呢?实际上我们发现无论transaction是什么类型,trans_type和xact_type都需要在第一拍的时候就进行采集,当发生了数据交换的时候就可以在第二拍也进行trans_type和xact_type的采集。

  1. `ifndef AHB_MONITOR_SV
  2. `define AHB_MONITOR_SV
  3. class ahb_monitor extends uvm_monitor;
  4. ...
  5. task monitor_transaction_start(ahb_transaction t);
  6. @(vif.cb_mon iff vif.cb_mon.trans_type == NSEQ);
  7. t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
  8. t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
  9. fork
  10. begin
  11. monitor_valid_data(t);
  12. end
  13. join_none
  14. endtask
  15. task monitor_valid_data(ahb_transaction t);
  16. @(vif.cb_mon iff vif.cb_mon.hready);
  17. t.increase_data();
  18. t.data[0] = t.xact_type = WRITE ? vif.cb_mon.hwdata : vif.cb_mon.hrdata;
  19. t.current_data_beat_num = 0;
  20. t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.cb_mon.hresp);
  21. t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
  22. endtask
  23. task monitor_transaction_proc(ahb_transaction t);
  24. endtask
  25. endclass
  26. `endif // AHB_MONITOR_SV

【思考】 那么我们为什么在采集数据的时候用到了fork join_none语句呢? 如果数据进行了SEQ的连续操作,也就是说在写数据第二拍的同时又有新的数据发送过来,那么应该如何进行采样呢?对此我们将代码进行重构:

  1. task collect_transaction(output ahb_transaction t);
  2. // collect transaction from interfacena
  3. t = ahb_transaction::type_id::create("ahb_transaction");
  4. @(vif.cb_mon iff vif.cb_mon.trans_type == NSEQ);
  5. t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
  6. t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
  7. forever begin
  8. monitor_valid_data(t);
  9. if(vif.cb_mon.htrans == IDLE) begin
  10. break;
  11. end
  12. end
  13. endtask
  14. task monitor_valid_data(ahb_transaction t);
  15. @(vif.cb_mon iff vif.cb_mon.hready);
  16. t.increase_data();
  17. t.data[0] = t.xact_type = WRITE ? vif.cb_mon.hwdata : vif.cb_mon.hrdata;
  18. t.current_data_beat_num = t.data.size() - 1;
  19. t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.cb_mon.hresp);
  20. t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
  21. endtask
  22. task monitor_transaction_proc(ahb_transaction t);
  23. endtask

【调试及结果】

image.png

我们在monitor中设置这样的三个断点,分别用于采集vif上的数据,将数据广播出去和等待一个IDLE信号。

image.png

第一次在IDLE的位置的位置break,此时Local变量中进行的是一个写操作,写入的数据是0;

image.png

接下来进行了一个读操作,可以看到读到的数据也是0,

image.png

之后进行了第二次的写操作,在IDLE的时候break,写入的值是11;

image.png

之后进行第二次读操作,读出来的值也是11。


经过上述的测试以后我们发现monitor对整个激励的采样是正确的,接下来需要补充采样的内容:

  1. `ifndef AHB_MONITOR_SV
  2. `define AHB_MONITOR_SV
  3. class ahb_monitor extends uvm_monitor;
  4. ...
  5. task collect_transaction(output ahb_transaction t);
  6. // collect transaction from interfacena
  7. t = ahb_transaction::type_id::create("ahb_transaction");
  8. @(vif.cb_mon iff vif.cb_mon.htrans == NSEQ);
  9. t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
  10. t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
  11. t.burst_size = burst_size_enum'(vif.cb_mon.hsize);
  12. t.burst_type = burst_type_enum'(vif.cb_mon.hburst);
  13. t.addr = vif.cb_mon.haddr;
  14. forever begin
  15. monitor_valid_data(t);
  16. if(vif.cb_mon.htrans == IDLE) begin
  17. break;
  18. end
  19. end
  20. endtask
  21. ...
  22. endclass
  23. `endif // AHB_MONITOR_SV

总线上的addr是会发生变化的,但是addr只记录头地址,所以我们只记录首地址即可,对于剩下的地址通过data的个数来计算即可。非常巧妙!


至此我们就完成了monitor的搭建,可以看到我们在之前的这些比较测试都是在test中执行的,也就是通过single_write和single_read读写到的数据进行比较检查。在后面的章节中我们搭建了scoreboard,就可以自动比较memory_model。

检查VIP时序的思路: 当根据波形做coding的时候,我们是要有时序的概念的,也就是说driver和monitor我们都是根据波形来进行描述的:

  1. 设置断点检查程序是否按照需要运行;
  2. 如果data检测到的数据和波形一样,那么就不需要再看波形了,直接检查每次读写的数据即可。