我最近的学习趋势,完全是在弥补之前一些没来得及探究的问题导致的遗憾。因为我是从vmm入门的,而vmm方法学是更加接近于朴素的systemverilog验证思路,因此环境搭建比较散当然也比较灵活。而之后转到uvm方法学后,我只是简单了解了下思路就直接转方向了,因此一直遗留几个问题没有搞清楚,今天的被动响应型uvm_model环境就是其中一个。
这个场景最初的需求是,环境产生一些原始数据,然后从中拆分出一些信息给DUT,另外一些给环境,DUT再根据得到信息从环境中获取剩余的信息,如果搭建朴素的验证环境结构基本就是下图:
这个结构在vmm中是很好实现的,因为generate和各类driver都是散装的,想怎么例化都可以,但是如何用uvm实现却难倒了当时的我。最近因为工作比较忙,所以突然有时间研究了一下这个问题(不是,在仔仔细细的把sequence和virtual sequence看完了之后,我突然有些顿悟。其实sequence就类似于上面的model,他的内部是可以做很多的操作的比如等待一个请求到来后做出相应发送一个包返回,而类似于多个drv协调工作的场景是可以通过virtual sequence来实现的。
于是呢今天我就要做一个简单的ram_model来实操下被动相应的模型搭建,同时后面工作中也可能会用到这样一个model。
最后做出来的环境大体上就是这样的当然了并没有其中的dut。ram_model为uvm_env类型,直接例化于base_test中,只接收总线的读写信息,更新内部的ram array,并且根据读请求返回rdata。
最终完成的项目路径为 uvm_test_root: 汇总下各种uvm test 中的uvm_rw_test目录,其中agent的生成还是通过 gen_uvm_agent: 创建一个简单的握手型uvm随机接口agent 来快速实现,当然这个脚本我也有一些更新已经同步上传。
关键处理就在于对sequence理解,以前我始终不明白,seq并不是一个component组件(我不太确定)可以挂在uvm_test这颗大树上,那除了发包之外还能怎么处理复杂的行为呢?看了书我才知道,原来只要sqr挂在了书上就可以了,而seq是可以借助`uvm_declare_p_sequencer(ram_sequencer)来通过p_sequencer使用对应sqr中的各种对象的。
所以最终vritual sequence的代码如下:
- class ram_sequence extends uvm_sequence;
-
- bit[31:0] ram[15:0];
-
- extern function new(string name = "ram_sequence");
- extern virtual task body();
- extern virtual task r_body();
- extern virtual task w_body();
-
- `uvm_object_utils(ram_sequence)
- `uvm_declare_p_sequencer(ram_sequencer)
-
- endclass: ram_sequence
-
- function ram_sequence::new(string name = "ram_sequence");
- super.new(name);
- endfunction: new
-
- task ram_sequence::body();
- fork
- r_body();
- w_body();
- join_none
- endtask: body
-
- task ram_sequence::r_body();
- ar_transaction a_trc;
- //fork
- while(1)begin
- p_sequencer.a_port.get(a_trc);
- `uvm_warning("ram_sequence", "get a ar_trc!");
- for(int i=0; i<a_trc.size; i=i+32)begin
- r_transaction r_trc = new();
- bit pkt_last = (i >= a_trc.size-32) && (i < a_trc.size);
- bit [31:0]pkt_data = ram[a_trc.addr];
- `uvm_warning("r_body", $sformatf("send r_trc.data='h%0h, last='h%0h", pkt_data, pkt_last));
- fork
- `uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == pkt_data; r_trc.last == pkt_last; r_trc.cycle_post == 0;})
- join_none
- end
- end
- endtask: r_body
-
- task ram_sequence::w_body();
- w_transaction w_trc;
- while(1)begin
- p_sequencer.w_port.get(w_trc);
- ram[w_trc.addr] = w_trc.data;
- //`uvm_warning("w_body", $sformatf("ram = 'h%0h", w_trc.data));
- end
- endtask: w_body
而virtual sqr的任务就更加简单了,就是把a_agt.mon采集到的包和w_agt.mon采集到的包放在port里,等待seq使用。
- class ram_sequencer extends uvm_sequencer;
-
- ar_sequencer a_sqr;
- r_sequencer r_sqr;
- w_sequencer w_sqr;
- uvm_blocking_get_port #(ar_transaction) a_port;
- uvm_blocking_get_port #(w_transaction) w_port;
-
- extern function new(string name = "ram_sequencer", uvm_component parent=null);
-
- `uvm_component_utils(ram_sequencer)
- endclass: ram_sequencer
-
- function ram_sequencer::new(string name = "ram_sequencer", uvm_component parent=null);
- super.new(name, parent);
- a_port = new("a_port", this);
- w_port = new("w_port", this);
- endfunction: new
在调行为的时候还发现了一个问题,那就是`uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == pkt_data; r_trc.last == pkt_last; r_trc.cycle_post == 0;})这个操作的结束是依靠driver中的seq_item_port.item_done()来实现的,这也就导致如果DUT的r_ready一直为低会影响进程中取出ar_trc的时间点,明明ar早就到了也被接收了但是由于内部环境中取完了最终导致返回了错误的数据(这期间w_trc可能已经修改了ram值),因此这里采用fork-join_none的方式避免阻塞取ar_trc的操作。