1、项目里面用到哪些接口?都是怎么用的?
项目里面用到了rkv_i2c_if、lvc_i2c_if、lvc_apb_if。rkv_i2c_if用来将DUT内部的intr中断信号、debug信号、ic_en使能信号、i2c和apb的时钟复位信号引出,在接口中声明了一个大位宽信号用来表示intr中断的所有信号,每一位代表一个中断信号,每个debug信号对应声明一个单独的信号,内部有wait_apb和wait_intr任务,wait_intr(id)任务等待对应id位的变化。get_intr(id)函数返回对应intr[id]位。lvc_i2c_if接口用来连接dut与外部i2c的IP(具体几个assign还未理解)。lvc_apb_if用来连接apb的IP。在tb中声明接口并连接,向底层通过config_db传递。env层接收到rkv_i2c_if接口都赋值到cfg配置中,并将带接口的cfg配置向下set传递给sbd_mst、sqr、cgm,在它们中就可以使用端口了。env层接收到的lvc_i2c_if接口set到cfg中的对应成员cfg中,各个成员cfg又通过config分别set到I2C的env的下一层。lvc_apb_if则从tb中直接set到apb_mst_agent中,其agent get到接口后再赋值给其monitor等组件。
2、验证环境中的配置是怎么传递和设置的?
rkv_i2c_config中包含了lvc_apb_config、lvc_i2c_system_config,在env中声一个rkv_i2c_config用来接收从test配置过来的cfg,并将其分别set给sbd_mst、sqr、cgm。在env中将rkv_i2c_config cfg中的成员配置信息分别配置使用,并set给apb_mst、i2c_mst、i2c_slv,在这个几个component中再进一步赋值给各个monitor,sequencer等。
默认7bit寻址、主机码3‘b000、从地址10‘b1100110011,deviceid为24'b0,标准速度模式等
3、覆盖率是怎么收集的?
rgm中有两个imp端口分别用来接收来自apb_master和i2c_slave的monitor监控到的总线的事务。声明了两个事件分别表示前门访问触发和后门访问触发。声明寄存器域模型、rkv_i2c_if虚接口、cfg配置信息。根据提取的功能点定义功能覆盖率,每个coverage都由sample函数来触发。通过imp端口的隐式调用的write_apb_master任务和write_i2c_slave任务来接收monitor监控到的总线的事务(其中只有在apb端的发来数据才有效,对于i2c端的数据不做处理,即write_i2c_slave任务内部为空),通过apb_master_monitor端传来事务的addr,利用cfg.default_map计算出是哪一个寄存器,并将这个寄存器作为触发前门访问事件的寄存器。该事件被触发后,延迟一段时间使寄存器模型的值已经被更新,然后根据触发事件的寄存器的名字来判断触发哪一个coverage的sample来收集相应寄存器里面数据的覆盖率(将相应寄存器的期望值传递过去)。
断言的覆盖率怎么写?在哪个模块实现?
断言分为立即断言与并行断言,立即断言放在不消耗时间的语句中,与时序无关,并行断言中要有时钟,与时序有关,并行断言只会在时钟边沿激活,变量的值是采样到的值。apb总线上的时序关系的验证用并行断言来验证,根据apb的时序关系写并行断言,且一般在接口中实现。
代码覆盖率、功能覆盖率?
代码覆盖率由工具自动收集,包括line_coverage(行)、condition_coverage(条件)、toggle_coverage(跳转)、branch_coverage(分支)、FSM_coverag(状态机)。
功能覆盖率主要验证是否符合设计说明书的功能要求主要有面向数据的覆盖率:coverage_geoup(覆盖组)、coverage_point(覆盖点)、cross_coverage(交叉覆盖)面向控制的覆盖率:检查行为序列(sequences of behaviors)是否已经发生.通过编写SVA来获得断言覆盖率(assertion coverage).
4、寄存器模型是怎么定义的?
每一个寄存器class都extends uvm_reg,定义好各个寄存器的域并做配置。然后定义一个ral_block_rkv_i2c extends uvm_reg_block,里面声明所有的寄存器(可以随机化的声明为rand)并做build配置。定义覆盖组,对几个寄存器偏移地址的访问。寄存器模型中有new方法,可以设置是否启动覆盖率的采集,有build函数,里面设置default_map,例化、配置寄存器并做赋值。添加后门访问路径,以regfile的形式添加各个寄存器的路径。里面还有sample方法启动覆盖率收集。
寄存器总线是什么?
寄存器总线就是apb总线,apb总线主要有paddr、pwrite、psel、penable、pready、prdata、pslverr,读操作的实现是将地址addr驱动到总线上,同时pwrite信号置0表示读,同时psel信号拉高表示选通,同时penable应该为低,等下一个时钟周期再将penable信号拉高,等待ready拉高表示传输成功,然后将总线上读取到的prdata信号取回。写操作的实现是将paddr、pwdata同时驱动到总线上,同时pwrite置1表示写同时sel置1表示选通,同时penable拉低,下一个周期再将其拉高。
寄存器模型是怎么实现读写的?
参考UVM的寄存器篇幅!
5、seq是和seqr是怎么写的?
rkv_i2c_virtual_sequencer里面从env中get到cfg配置信息,然后将cfg中的rgm和vif赋值给本地变量。
rkv_i2c_base_virtual_sequence先定义p_sequencer为rkv_i2c_virtual_sequencer,然后将p_sequence里面的rgm、vif、cfg赋值给本地变量。这里还有一个env,由于其子类seq中需要用到env中的重配置task,所以需要有一个env传进去,所以就在这里面声明一个env并通过$cast(env,p_sequencer.m_parent)的方法将env传进来。
rkv_apb_base_sequence为apb端seq的基础。里面声明寄存器模型和所有需要用到的寄存器的域,里面有更新寄存器的任务,它被作为成员变量声明在rkv_i2c_base_virtual_sequence里面,所有声明的有关apb端的seq都要继承于它。apb端的element事务主要有配置的seq、中断清除seq、等待中断清除完成seq、读seq(会检查FIFO)、写seq(会检查FIFO)、等待FIFO为空seq、读seq(不检查FIFO)、写seq(不检查FIFO)、将RX_FIFO中数据读出seq、TX_ABRT中断源检查seq。
rkv_i2c_slave_base_sequence为i2c端seq的基础,里面声明控制ack和nack的参数,所有声明的有关i2c端的seq都要继承于它,它被作为成员变量声明在rkv_i2c_base_virtual_sequence里面,i2c端的element主要有读回应seq、写回应seq。
user_virtual_sequence为自己根据各个element为验证提取的功能点而写的seq,它继承与rkv_i2c_base_virtual_sequence,那么它就可以使用它父类中的所有的成员seq及继承与成员seq的所有element_seq。
test里面,各个user_virtual_sequence在env.sqr(rkv_i2c_virtual_sequencer)上start,内部element_squence在相应的sequencer上挂载。
6、I2C项目中的一个难点?
scoreboard中接收apb端数据的判断条件的设置。
在写scoreboard的时候,需要接收apb_master端的monitor从总线上监控到的apb_transfer,将监控到的事务通过调用端口的write_函数来存入FIFO中,但是读和写的事务的存入稍微有些不同。monitor监控到了总线上的事务,可以通过事务中的控制命令write是1还是0来判断是读还是写,如果是write,而且与寄存器模型中控制命令一致,且TX_FIFO未满(写事务中每写一个数据都会mirror此状态寄存器),那么可以事务放进FIFO。如果是read,事务同样还是会发送data(=0)数据到总线上,这个事务也会被monitor监控到并传递给scoreboard,而且这时候i2c端已经将数据传入,RX_FIFO中已经有数据,已经不是空了,正常来说,此时已经满足了if判断条件,但是,由于if中判断的是寄存器模型的值,此时寄存器模型的值和dut的值还不一样,dut中的状态寄存器中RX_FIFO已经不是空了,但是没有mirror操作将dut的值同步到寄存器模型中,所以,if中判断的寄存器模型的值还是表示是空的,判断不通过,不会被scoreboard存入FIFO,所以,apb端在发送读事务的时候,发送完所有的读事务以后,需要有一个mirror操作,将dut中的寄存器值的值更新到寄存器模型中,这样,在接下来apb端从cmd寄存器中读数据时候,monitor监控到事务数据才是实际上读到的需要放进scoreboard的FIFO中的数据。
(待补充。。。)