【总结】
- 需要明确的一点是,我们在这里是对ahb_master_monitor进行实现,从vif中采集数据并传递到subsriber\scoreboard中;
- 对于收集数据向其他组件发送的这些函数,我们是在monitor中进行实现的;
- 但是在整个环境中我们使用的是ahb_master_monitor,因为在agent中我们已经将它和sequencer连接在了一起,也就是说在agnet中已经完成了对ahb_master_monitor的例化。
4.1 优化ahb_driver时序
- 根据上一节的内容我们知道write和read操作都是通过两拍来实现的,为了使得时序更加紧凑我们需要对driver进行一些微调:
我们暂时先不考虑do_proc_write()和do_proc_read()这两个情况,所以将他们里面的idle操作删除,另外删除read动作中多余的一个一拍。
:::info
为什么在这里需要优化driver的时序呢?
原因是在符合时序的状态下我们去除掉一些多余的时序,尽可能地使得它理想,因为在之后的burst下要实现连续读和连续写的操作,使得发送的数据更紧凑,避免loose的情况出现;
如果想要在发送完数据以后添加一些idle的时序该怎么做呢?
我们可以在transaction中插入一些成员来实现对idle数量的控制;
或者在write_transaction和read中插入一些idle,但是对driver时序的调优是一定要完成的。
:::
调整以后的时序变得更加紧凑
4.2 ahb_master_monitor
与driver不同的是:driver中我们通常带有带着驱动的环节也就是drive_transaction出现,但是在monitor中我们不需要这样的驱动,直接从vif拿信号即可。所以一般用的都是vif中的monitor_clocking来采样数据。 如果使用了clocking块来进行采样和驱动,那么在之后的环节中就应当一直使用它。
:::info monitor中需要执行以下几个函数:
- collect_transaction(从vif一侧采样transaction):先创建再采样之后return t作为output;
- monitor_transaction:拿到从collect_transaction中采集到的ahb_transaction并通过port广播出去。注意这个过程是forever的,也就是说是不断需要采样数据的。
- 采样逻辑:
- monitor_trans_start()和monitor_trans_proc:这两个方法中的形参都是作为input,原因是ahb_transaction在collect_transaction中创建过了,因此只需要记录数据即可;
- 那么记录的数据是从哪里来的呢?根据uvm_monitor的作用我们知道monitor用来观测DUT的interface并采样总线的信息。
⚓思考:
- 对于一个NSEQ的动作而言,在之前的single_write中我们已经知道了它是在write拉高以后的下一拍才会发送数据出去;
- 那么如果是一个burst的情况,由于ahb是一个follow_order的情况,不会出现乱序的类型,一个写出去的数据在下一拍有可能被接收也有可能没有。 :::
在这里我们首先考虑对NSEQ数据的采:
- 我们回顾一下ahb_transaction中有哪些成员变量?
``systemverilog
ifndef 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
- 接下来判断transaction的类型并对成员变量进行采集:
- 判断当前的transaction是否是NSEQ类型的;
- 判断完以后看当前的这个transaction进行的是读操作还是写操作,将vif上的hwrite转换成枚举类型然后放到句柄t中;
- 接下来就是data这个成员变量,我们将采集到的新的data数据放到拓展后的数组中;
- 之后就是对trans_type和current_data_beat_num也放到句柄t中;
- 最后我们将新采集到的all_beat_response放到拓展后的数组中。
```systemverilog
`ifndef AHB_MONITOR_SV
`define AHB_MONITOR_SV
class ahb_monitor extends uvm_monitor;
uvm_analysis_port #(ahb_transaction) item_observed_port;
`uvm_component_utils(ahb_monitor)
function new (string name = "ahb_monitor", uvm_component parent = null);
super.new(name,parent);
item_observed_port = new("item_observed_port", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
monitor_transaction();
join_none
endtask
task monitor_transaction();
ahb_transaction t;
forever begin
collect_transaction(t);
item_observed_port.write(t);
end
endtask
task collect_transaction(output ahb_transaction t);
// collect transaction from interfacena
t = ahb_transaction::type_id::create("ahb_transaction");
monitor_transaction_start(t);
monitor_transaction_proc(t);
endtask
task monitor_transaction_start(ahb_transaction t);
@(vif.cb_mon iff vif.cb_mon.trans_type == NSEQ);
t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
fork
begin
t.data = new[1];
t.data[0] = t.xact_type = WRITE ? vif.cb_mon.hwdata : vif.cb_mon.hrdata;
t.trans_type = NSEQ;
t.current_data_beat_num = 0;
t.all_beat_response = new[1];
t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.cb_mon.hresp);
end
join_none
endtask
task monitor_transaction_proc(ahb_transaction t);
endtask
endclass
`endif // AHB_MONITOR_SV
在51、52和55、56行中,我们都对data和all_beat_reponse这两个数组进行了拓展并将原来的数据赋值到拓展后的数组中。 数据是一个一个捕捉并进行扩展的,因此可以在ahb_transaction中声明一个function来完成。
`ifndef AHB_TRANSACTION_SV
`define AHB_TRANSACTION_SV
class ahb_transaction extends uvm_sequence_item;
...
function new (string name = "ahb_transaction");
super.new(name);
endfunction
function void increase_data(int n=1);
data = new[data.size + 1] data;
all_beat_response = new[all_beat_response.size + 1] all_beat_response;
endfunction
endclass
`endif // AHB_TRANSACTION_SV
声明好这个方法以后就可以对数据采集中的一些语句进行替换:
`ifndef AHB_MONITOR_SV
`define AHB_MONITOR_SV
class ahb_monitor extends uvm_monitor;
uvm_analysis_port #(ahb_transaction) item_observed_port;
`uvm_component_utils(ahb_monitor)
function new (string name = "ahb_monitor", uvm_component parent = null);
super.new(name,parent);
item_observed_port = new("item_observed_port", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
monitor_transaction();
join_none
endtask
task monitor_transaction();
ahb_transaction t;
forever begin
collect_transaction(t);
item_observed_port.write(t);
end
endtask
task collect_transaction(output ahb_transaction t);
// collect transaction from interfacena
t = ahb_transaction::type_id::create("ahb_transaction");
monitor_transaction_start(t);
monitor_transaction_proc(t);
endtask
task monitor_transaction_start(ahb_transaction t);
@(vif.cb_mon iff vif.cb_mon.trans_type == NSEQ);
t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
fork
begin
@(vif.cb_mon iff vif.cb_mon.hready);
t.increase_data();
t.data[0] = t.xact_type = WRITE ? vif.cb_mon.hwdata : vif.cb_mon.hrdata;
t.trans_type = NSEQ;
t.current_data_beat_num = 0;
t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.cb_mon.hresp);
end
join_none
endtask
task monitor_transaction_proc(ahb_transaction t);
endtask
endclass
`endif // AHB_MONITOR_SV
代码重构:
之前我们探讨了在NSEQ状态下采集数据的情况,那么对于发送完NSEQ数据以后的IDLE状态,该状态下的数据又该如何采集呢?实际上我们发现无论transaction是什么类型,trans_type和xact_type都需要在第一拍的时候就进行采集,当发生了数据交换的时候就可以在第二拍也进行trans_type和xact_type的采集。
`ifndef AHB_MONITOR_SV
`define AHB_MONITOR_SV
class ahb_monitor extends uvm_monitor;
...
task monitor_transaction_start(ahb_transaction t);
@(vif.cb_mon iff vif.cb_mon.trans_type == NSEQ);
t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
fork
begin
monitor_valid_data(t);
end
join_none
endtask
task monitor_valid_data(ahb_transaction t);
@(vif.cb_mon iff vif.cb_mon.hready);
t.increase_data();
t.data[0] = t.xact_type = WRITE ? vif.cb_mon.hwdata : vif.cb_mon.hrdata;
t.current_data_beat_num = 0;
t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.cb_mon.hresp);
t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
endtask
task monitor_transaction_proc(ahb_transaction t);
endtask
endclass
`endif // AHB_MONITOR_SV
【思考】 那么我们为什么在采集数据的时候用到了fork join_none语句呢? 如果数据进行了SEQ的连续操作,也就是说在写数据第二拍的同时又有新的数据发送过来,那么应该如何进行采样呢?对此我们将代码进行重构:
task collect_transaction(output ahb_transaction t);
// collect transaction from interfacena
t = ahb_transaction::type_id::create("ahb_transaction");
@(vif.cb_mon iff vif.cb_mon.trans_type == NSEQ);
t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
forever begin
monitor_valid_data(t);
if(vif.cb_mon.htrans == IDLE) begin
break;
end
end
endtask
task monitor_valid_data(ahb_transaction t);
@(vif.cb_mon iff vif.cb_mon.hready);
t.increase_data();
t.data[0] = t.xact_type = WRITE ? vif.cb_mon.hwdata : vif.cb_mon.hrdata;
t.current_data_beat_num = t.data.size() - 1;
t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.cb_mon.hresp);
t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
endtask
task monitor_transaction_proc(ahb_transaction t);
endtask
【调试及结果】
我们在monitor中设置这样的三个断点,分别用于采集vif上的数据,将数据广播出去和等待一个IDLE信号。
第一次在IDLE的位置的位置break,此时Local变量中进行的是一个写操作,写入的数据是0;
接下来进行了一个读操作,可以看到读到的数据也是0,
之后进行了第二次的写操作,在IDLE的时候break,写入的值是11;
之后进行第二次读操作,读出来的值也是11。
经过上述的测试以后我们发现monitor对整个激励的采样是正确的,接下来需要补充采样的内容:
`ifndef AHB_MONITOR_SV
`define AHB_MONITOR_SV
class ahb_monitor extends uvm_monitor;
...
task collect_transaction(output ahb_transaction t);
// collect transaction from interfacena
t = ahb_transaction::type_id::create("ahb_transaction");
@(vif.cb_mon iff vif.cb_mon.htrans == NSEQ);
t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
t.burst_size = burst_size_enum'(vif.cb_mon.hsize);
t.burst_type = burst_type_enum'(vif.cb_mon.hburst);
t.addr = vif.cb_mon.haddr;
forever begin
monitor_valid_data(t);
if(vif.cb_mon.htrans == IDLE) begin
break;
end
end
endtask
...
endclass
`endif // AHB_MONITOR_SV
总线上的addr是会发生变化的,但是addr只记录头地址,所以我们只记录首地址即可,对于剩下的地址通过data的个数来计算即可。非常巧妙!
至此我们就完成了monitor的搭建,可以看到我们在之前的这些比较测试都是在test中执行的,也就是通过single_write和single_read读写到的数据进行比较检查。在后面的章节中我们搭建了scoreboard,就可以自动比较memory_model。
检查VIP时序的思路: 当根据波形做coding的时候,我们是要有时序的概念的,也就是说driver和monitor我们都是根据波形来进行描述的:
- 设置断点检查程序是否按照需要运行;
- 如果data检测到的数据和波形一样,那么就不需要再看波形了,直接检查每次读写的数据即可。