正式开始学习总线VIP,一方面增加对总线协议的理解,一方面增强自己的coding能力
APB3.0_VIP
apb3.0总线协议
APB 3.0属于AMBA 3 协议系列,它提供了一个低功耗的接口,并降低了接口的复杂性。APB接口用在低带宽和不需要高性能总线的外围设备上。APB是非流水线结构,所有的信号仅与时钟上升沿相关,这样就可以简化APB外围设备的设计流程,每个传输至少耗用两个周期。
APB可以与AMBA高级高性能总线(AHB-Lite) 和AMBA 高级可扩展接口(AXI)连接。该版本新增两个信号,一个准备好信号PREADY, 来扩展APB传输;一个错误信号PSLVERR, 来指示传输失败。
interface
status
和之前的apb相比可以很明显的看出:通过ready信号为0,则无法退出该次传输,需等待ready信号拉高后才能进入setup或进入idle
传输时序
写和读都多了一个由ready信号控制的等待状态
写传输
无等待状态:
地址、写入数据、写入信号和选择信号都在时钟上升沿后改变。第一个时钟周期叫做Setup phase。下一个时钟沿后使能信号PENABLE被置位,表示Access phase就位。地址、数据和控制信号在Access phase期间有效。传输在该周期后结束。使能信号PENABLE, 在传输结束后清空。选择信号PSELx同样被置低,除非紧接着下一传输开始。
有等待状态:
通過PREADY 信号扩展到器件的传输時間。在Access phase期间,当PENABLE为高,传输可以通过拉低PREADY来扩展传输。当PENABLE为低的时候,PREADY可以为任何值。确保外围器件有固定的两个周期来使PREADY为高。推荐地址和写信号在传输结束后不要立即更改,保持当前状态直到下一个传输,这样可以降低功耗。
读
和写的区别主要就是write信号,还有数据传输到总线上的时间
无等待
等待
错误响应
开发阶段
阶段1——定义APB
功能特性提取
APB总线有哪些功能,对这些功能作拆分
单个transaction操作
- 单个transaction写操作
- 单个transaction读操作
- 单个transaction写/读+idle+写/读
多粒度transaction操作——关联数组、队列、动态数组等
- 对数组进行写操作
- 对数组进行读操作
1 |
|
特性覆盖率创建及映射
简单映射,核心功能检查还是应该依赖断言覆盖率
1 | bit has_coverage = 1; |
阶段2——VIP环境搭建
master_agent—driver,sequencer,monitor
master_driver
通过svh可以直观的看到driver的结构
1.拿到一个apb_if的接口vif,不需要管怎么拿到的,拿来用就行,别的交给env,agent来传
2.拿到一个apb_config的配置cfg,同样不要管怎么拿的,要用什么直接用,在交给env,agent中把例化对象传进来就行
比如下面的写、读操作:通过配置文件中进行修改,就能决定碰到错误响应信号时,消息管理的严重级别是error还是warning(感觉完全可以用1/0替代)
1
2
3
4
5
6
7if(vif.pslverr === 1) begin
t.trans_status = ERROR;
if(cfg.master_pslverr_status_severity == UVM_ERROR)
else
enddo_write():两个周期不同的驱动方式,penable为高时需要考虑两点
- pready是否为1,不为1则等待其为1
- pslverr是否为1,为1则打印错误或警告
注意这里的两次等待:#10ps;#1ps:
10ps:因为在wait中没有使用时钟块里面的pready和pslverr信号,因为它想采的是当前时钟拍子中的pready信号
1ps:可以看到slave_driver中的驱动任务do_write(),是先将pready<=1驱动到总线上,再将pslverr驱动到总线上,这里就差了一个date_cycle!!需要等待1ps再采pslverr信号
- do_read():同样是两拍
- do_idle():停一拍,除paddr和pwrite外全部归零
- reset_listener():复位信号监控
1 | //svh |
master_monitor
问题1:TLM通信为啥选择用uvm_analysis_port端口?
monitor的run_phase核心就是monitor_transactions()方法;
又因为check和coverage方法都是空的,其实就只执行collect_transfer()采样方法,在penable为0时进入采样准备,在pready信号为1的同一拍进行采样
1 | //svh |
master_sequencer
只需要注册并定义new()方法
master_agent
build_phase:
driver,monitor,sequencer的创建
config_db传入接口和配置信息
注意如果配置给agent的is_active为0,则不用创建driver和sequencer
connect_phase:
driver和sequencer连接
注意:通过configuration的is_active判断driver和sequencer是否创建,没创建就不用连了
接口的传入,将接口赋值给driver和monitor
1 | // svh |
slave_agent
和master_agent的区别,因为总线是单向传输的,master端发送数据,slave端只需要被动接收master发送的数据就好
slave_driver的get_and_drive()方法就是个摆设,sequence也是个摆设
slave_driver
定义关联数组:bit[31:0] mem [bit[31:0]];
用来将master发送的数据进行存储,用于后面的数据比对
核心方法:drive_response(),其中的do_write和do_read方法类似
- cfg的get_pready_additional_cycles()方法决定了penable信号被扩展几个周期
- cfg.get_pslverr_status():返回的pslverr信号是1还是0
- do_write:
- penable为1,就可以把输入地址和数据写进关联数组,此时将pready信号置为0
- 等待get_pready_additional_cycles()个时钟周期后,驱动pready信号拉为1,pslverr信号为cfg.get_pslverr_status(),此时是真正的进行数据传输
- 下一拍将pready置为默认值,pslverr信号置为0
- do_read:
- penable为1,就可以根据地址把数据(如果当前地址有的话)拿出来,存到data变量,此时将pready信号置为0
- 等待get_pready_additional_cycles()个时钟周期后,驱动pready信号拉为1,然后将pready信号,pslverr信号,prdata=data信号驱动到总线
- 下一拍将pready置为默认值,pslverr信号置为0
1 | //svh |
slave_monitor
无需,master_monitor就是在数据有效传输时进行采样,读和写时的apb_transfer都能够获取到
slave_sequencer
无需,被动接收,和get_and_driver就是摆设
slave_agent
和master_agent完全一样,这也体现了分层次环境构建的好处
apb_env
1 | class apb_env extends uvm_env; |
configuration
梳理一下哪里需要用到配置
- master_driver:cfg.master_pslverr_status_severity={UVM_ERROR, UVM_WARNING}选择对于psvlerr信号为1时的通信方式
- agent:判断组件是否是is_active = UVM_ACTIVE,不是则不会例化sequencer,driver及其创建连接
- slave_driver:
- cfg.slave_pready_default_value:在idle状态,reset状态时的默认驱动
- int pready_add_cycles = cfg.get_pready_additional_cycles():可通过随机化或定义的方式,决定pready信号扩展几个时钟周期
- bit pslverr_status = cfg.get_pslverr_status():可通过随机化或定义的方式,决定pslverr信号的发送概率
3个bit位可进行随机化:
- slave_pready_random:对pready信号的随机化,通过
get_pready_additional_cycles()
方法控制ready信号持续几拍,也就是对传输周期进行扩展 - slave_pslverr_random:尝试返回错误响应,通过
get_pslverr_status()
方法,当slave_pslverr_random的值为1,且$urandom_range(0, 20) == 0满足时,返回1,也就是pslverr错误响应信号为1
1 |
|
阶段3——基础序列
根据特性覆盖率的映射编写基础的测试序列
- apb_master_single_write_sequence:单次写,只发送一次data
- apb_master_single_read_sequence:单次读,只读一次data
- apb_master_write_read_sequence:单次写,中间等idle_cycle拍,从同一个地址单次读
- apb_master_burst_write_sequence:连续写
- apb_master_burst_read_sequence:连续读
1 |
|
阶段4——顶层序列+test
apb_base_test
1 | class apb_base_test extends uvm_test; |
apb_single_transaction_test
顶层sequence挂载到master_sequencer上
1 | class apb_single_transaction_test extends apb_base_test; |
apb_base_test_sequence
没有body方法,单纯用来给别的test_sequence继承用的
为啥这里不继承virtual_sequence?
因为就一个master的sequencer,所有的sequence全都绑到这一个sequencer上,所以是Hierarchical Sequence
check_mem_data(bit[31:0] addr, bit[31:0] data)
:数据比对wait_reset_release()
:执行复位wait_cycles(int n)
:等待一定周期get_rand_addr()
:获取随机地址
1 | class apb_base_test_sequence extends uvm_sequence #(apb_transfer); |
apb_single_transaction_sequence
将3个单次的sequence包装起来,进行组合
重复的随机地址单次写+随机地址单次读+对比
重复的相同地址单次写完就单次读+对比
重复的写后读(这个sequence不用组合)+对比(顶层没有定义idle_cycle,由底层sequence进行随机)
没有复用基层sequence,有重新通过req的挂载加了一个序列
重复的连续写两次+读一次+对比(idle_cycle为0)
1 | class apb_single_transaction_sequence extends apb_base_test_sequence; |
apb_burst_transaction_sequence
对两次连续的读、写进行组合
- 执行连续写,将写的数据放入test的关联数组mem,连续读,将读的数据和test中的mem数据进行比对
1 | class apb_burst_transaction_sequence extends apb_base_test_sequence; |
apb_if+assertion
注意3个时钟块:
根据主端和从端的时钟块中的信号方向,连接方式一目了然
注意两个initial块:
initial begin : coverage_control:通过automatic—+例化执行特性覆盖率收集
问1:psel的burst和penable的single和实际的test cases应该有关系吧~
问2:按理说除了
cg_lvc_apb_trans_timing_group
,别的特性覆盖率定义都是被断言覆盖率覆盖了啊不过也有可能断言覆盖率不满足,但是特性覆盖率满足
initial begin: assertion_control:通过\$assertoff和$asserton系统方法执行断言覆盖率收集
1 |
|
apb_tb
- 定义时钟周期
- 例化接口+接口传入agent
- run_test()
1 |
|