文章目录
- 项目结构
- 准备工作
- 一、user_sequence部分
- 1. rkv_i2c_master_abrt_10b_rd_norstrt_virt_seq
- 2. rkv_i2c_master_abrt_7b_addr_noack_virt_seq
- 3. rkv_i2c_master_abrt_sbyte_norstrt_virt_seq
- 4.rkv_i2c_master_abrt_txdata_noack_virt_seq
- 5.rkv_i2c_master_activity_intr_output_virt_seq
- 6.rkv_i2c_master_address_cg_virt_seq
- 7.rkv_i2c_master_enabled_cg_virt_seq
- 8.rkv_i2c_master_fs_cnt_virt_seq
--------------------------------------------------------------------------------
👉声明:以下均为路科验证I2C虚拟项目学习笔记。仅以学习为目的,不可用于其他任何商业用途,违者必究👎。
👉如需获取详细的代码和资料,请咨询公众号“路科验证”🏬。拒绝盗版✋,支持正版👊
👉注:以下均为本人的理解,如有解释不清和错误,请私信我或在评论区留言,谢谢。
👉后续精彩请看:I2C虚拟项目笔记(二)-virtual sequence
👉持续更新中…👊
--------------------------------------------------------------------------------
项目结构
项目结构以框图:有点潦草,可以直接pass
scoreboard的数据流向:
准备工作
- 开始之前,一定要看清设计上裸露出来的中断intr信号与interface上的连接。这是你测不同中断响应时要抓出来的信号,top_if中intr的不同bit位信号拉起,就代表触发了不同的中断。注意:ic_start_det_intr等这是硬件信号上的中断,而不是读中断寄存器读出的中断标志。
👇对应的参数定义如下,这样做的目的是增加参数的可读性。路桑的这一点做的很好,记笔记了。
3.测中断以及中的TX_ABRT之前,一定要明白IC_RAW_INTR_STAT和IC_TX_ABRT_SOURCE中的不同比特位代表什么中断。
4.心里要记住的寄存器偏移地址有:
- IC_CON 0x0
- IC_DATA_CMD 0x10
- IC_RAW_INTR_STAT 0x34
- IC_TX_ABRT_SOURCE 0x80
- IC_STATUS 0x6c
根据波形中PADDR,再根据PENABLE信号拉高后,PWRITE和PREAD的值就可以明白当前操作是在对哪个寄存器写入什么样地指令或数据。比如,当paddr为10,enable信号拉高后,pwdata为9’b1_0000_0000,说明此时要对I2C 的slave进行读操作;如果此时pwdata为9’b0_0000_1111,表示要想对应地址的I2C slave写入0000_1111。
5.最后开始前啰嗦一句,这也是之前困扰我很久的一块。DW_APB_I2C到底是在干嘛?两个VIP又在干嘛?
以DW_APB_I2C的master模式为例,它会接收来自APB总线的数据,然后通过DW_APB_I2C将这个数据变成I2C线上可以认识的信号(也就是要符合I2C的协议),这也就是DW_APB_I2C上的两组FIFO和移位寄存器的作用,可以分别将从APB到I2C的数据进行串并转换。
写操作:当APB要往I2C写数据了,数据会先写到DW_APB_I2C的TX FIFO中。并且通过APB写入到DW_APB_I2C的IC_TAR寄存器的地址值,先把把这个发到I2C线上,寻找对应的I2C设备。有设备回应后,再把寄存在DW_APB_I2C 内部TX FIFO的数据,通过TX shift一拍一拍的打到I2C线上,这样I2C上的SLAVE就可以收到写入的数据。
读操作:同样的道理,读操作的过程也是类似。APB上先给DW_APB_I2C发过来一个地址,并且对应一个数据,但是这个数据可能不是要发送到I2C上的数据,而是要写入DW_APB_I2C内部对应的控制寄存器的值。比如要向地址为0x10的IC_DATA_CMD寄存器写入9’b1_0000_0000,表示我要读取I2C 总线上SLAVE设备里面的数据。但是光给这一个肯定不够,肯定还要给一个寻址地址,所以还要往IC_TAR寄存器写入要寻找的I2C slave设备的地址。然后其他的一些配置好了之后,DW_APB_I2C开始把地址发送到I2C线上,寻找对应的I2C设备。设备确认传输方向后,把数据一拍一拍的放到I2C线上,然后DW_APB_I2C把这个数据经过RX SHIFT和RX FIFO,以并行数据的形式打到APB线上,然后apb master就可以拿到这个数据。
说到这里,我猜你也差不多明白为什么要用两个VIP了,这是因为:首先,设备DW_APB_I2C只负责APB和I2C线上的数据转换,他相当于一个桥的作用,或者某种意义上,你可以认为它是一个"APB_I2C的桥"。所有我们需要一个APB VIP 来模拟 APB线上的master,这个master通过APB的接口向DW_APB_I2C发送数据和请求等,并获取DW_APB_I2C从I2C读那里拿过来的数据。同样的我们要一个I2C VIP来模拟 i2c SLAVE 设备,这个slave可以通过I2C 的接口来响应APB master的请求,可以从I2C接收APB MASTER写入的数据,也可以把自己内部存的数据通过I2C总线,在经过DW_APB_I2C转换,送到APB线上。。
PS:原谅我这里像写作文一样罗里吧嗦一大堆,因为我自己当时一直受到这个困惑,不愿看见我的读者继续遭此毒打,希望我的解答可以帮助到你。
一、user_sequence部分
1. rkv_i2c_master_abrt_10b_rd_norstrt_virt_seq
1️⃣测试的目的:这个造的测试环境是 产生TX_ABRT_SORTCE中的ABRT_10B_RD_NORSTRT中断。
2️⃣ABRT_10B_RD_NORSTRT中断:master尝试在10bit地址下read数据,但是此时RESTART的功能被disable了。
3️⃣virtual sequence中:配置IC_CON寄存器,使能10bit master 地址,并关闭master的restart功能。
4️⃣仿真结果是如下:
👇要想知道具体的触发了哪个ABRT,有两种方法:
☑️查看info信息。因为你在mirror ABRT_SOURCE寄存器时,mirror后,寄存器模型中ABRT_SOURCE各个域的值也会被打印出来。
☑️看中断信号拉起来后,当sequence中有调用rgm.IC_TX_ABRT_SOURCE.mirror()时,APB的总线上会向对应偏移地址0x80读取数据,penable信号拉高后,prdata的数据值就反应了触发的中断。这个virtual sequence中并没有
调用rgm.IC_TX_ABRT_SOURCE.mirror(),所以没办法用这个方法。后面有这个方法的实例。
如下,info中打印的消息:ABRT_SOURCE中10 bit 位对应 ABRT_10B_RD_NORSTRT中断
2. rkv_i2c_master_abrt_7b_addr_noack_virt_seq
1️⃣测试目的:产生TX_ABRT_SOURCE中的ABRT_7B_ADDR_NOACK中断,并产生相应的IC_CLR_TX_ABRT中断清除。
2️⃣ABRT_7B_ADDR_NOACK中断:在7bit地址下,slave未应答(NACK),产生ABRT。
3️⃣virtual sequence中:
☑️配置使能关闭10bit,打开TX_EMPTY中断
☑️配置I2C VIP,应答NACK
☑️产生IC_CLR_TX_ABRT中断清除
4️⃣仿真结果:👇
3. rkv_i2c_master_abrt_sbyte_norstrt_virt_seq
1️⃣测试目的:产生TX_ABRT_SOURCE中的 ABRT_SBYTE_NORSTRT中断,并产生相应的IC_CLR_TX_ABRT中断清除。
2️⃣ABRT_SBYTE_NORSTRT中断:当master开始START BYTE时,restart条件(IC_CON中的bit5位 IC_RESTART_EN)被关闭,产生ABRT。
因为I2C协议规定,当发送0000_0001时,最后需要restart master,产生重复启动条件。详细可以看I2C中的START BYTE。
3️⃣virtual sequence中:
☑️配置使能关闭10bit,打开TX_EMPTY中断
☑️配置I2C VIP,应答NACK
☑️产生IC_CLR_TX_ABRT中断清除
4️⃣仿真结果如下:👇
首先通过mirror() 对应的IC_RAW_INTR_STAT寄存器,更新寄存器模型,当前时刻触发两个中断(TX_ABRT 和 TX_EMPTY)
通过mirror() 对应的 IC_TX_ABRT_SOURCE,知道ABRT_SBYTE_NOPRSTRT触发 ;
然后对应 TX_ABRT的中断清除寄存器会清除中断。
4.rkv_i2c_master_abrt_txdata_noack_virt_seq
1️⃣测试目的:产生TX_ABRT_SOURCE中的ABRT_TXDATA_NOACK中断。
2️⃣ABRT_TXDATA_NOACK中断:master 根据一个地址进行寻址,收到了slave的ACK,但是向这个地址的slave发送数据的时候,
slave没有回应ACK,触发ABRT_TXDATA_NOACK中断。
3️⃣virtual sequence:
- 首先要找到怎么让slave回应data时 NACK,那你就必须要从sequencer发送什么item入手,那就要去看slave_transaction 的定义了。知道这一点,virtual sequence自然就很好写了。如下图所示:
4️⃣仿真结果:👇
我的习惯是通过info信息中获取到底是触发的哪个ABRT。
5.rkv_i2c_master_activity_intr_output_virt_seq
1️⃣ 测试目的:测试IC_RAW_INTR_STAT的ACTIVITY、STAT_DET和STOP_DET中断的产生,然后读中断寄存器IC_CLR_ACTIVITY清除中断。
2️⃣ ACTIVITY中断:表示I2C 处于活动状态,包括IDLE。可以通过禁用I2C 接口、系统复位、读取IC_CLR_ACTIVITY 或IC_CLR_INTR 寄存器来清除。
3️⃣ virtual interface中:
- 首先要打开对应的中断触发, 默认情况IC_INTR_MASK是将ACTIVITY给mask掉的
- 通过IC_INTR_MASK给对应的寄存器域写入1,打开这几个中断
- 注意IC_INTR_MASK是一个读写寄存器。
4️⃣仿真结果:👇
6.rkv_i2c_master_address_cg_virt_seq
1️⃣测试目的:测试TAR的10bit 和7bit地址访问,触发TX_ABRT_SOURCE中的ABRT_7B_ADDR_NOACK 和ABRT_10ADDR1_NOACK。
注意:如果要想改变I2C VIP中slave是否支持10bit,还是更改slave的地址,都需要通过config来配置slave。
//10 bit 地址使能
cfg.i2c_cfg.slave_cfg[0].enable_10bit_addr = 1;
env.i2c_slv.reconfigure_via_task(cfg.i2c_cfg.slave_cfg[0]);
//更改slave的地址
bit [9:0] address[] = '{10'b1001110011,`LVC_I2C_SLAVE0_ADDRESS,10'b0001110011,10'b0110110011};
foreach(address[j]) begin
cfg.i2c_cfg.slave_cfg[0].slave_address = address[j];
env.i2c_slv.reconfigure_via_task(cfg.i2c_cfg.slave_cfg[0]);
end
这是因为,
👉slave_cfg[ ]动态数组中元素的类型为 lvc_i2c_agent_configuration👈
👉lvc_i2c_agent_configuration 继承于 lvc_i2c_configuration;👈
👉然后在lvc_i2c_configuration 中定义了 slave_address 和 enable_10bit_addr;👈
👉在i2c的slave_driver 中,是根据config传递的address和enable_10bits_addr来控制driver的行为👈
这里的lvc_slave_driver_common 才是 i2c 的slave_driver 核心部分,因为在slave_driver的run_phase内调用了 lvc_slave_driver_common ::send_xact(trans)任务,而这个send_xact则是精华所在(可以细品)。这也就是为什么需要在
2️⃣virtual sequence中:
- 首先做了一个地址数组,因为covergroup中分别只关心7bit和10bit地址的高位,具体可以看地址的covergroup。如下👇 :
- 分别在使能和关闭10bit地址,然后需要遍历address[ ]数组,master分别向对应地址写入一个数据
- 如下👇:写完正确的地址后,再让master向错误的地址写入数据,触发ABRT中断
3️⃣仿真结果:
7bit地址的正常寻址和异常寻址。第一次为正常的传输,对7bit地址的1个Byte和data回应ACK;第二次传输错误地址,slave对7bit地址的Byte回应NACK,触发ABRT_7B_ADDR_NOACK。
注意:7bit地址在波形上,比如遍历address数组时(注意foreach时是从左往右遍历),发送的第一个7bit地址,对应的向TAR(0x04)写入的地址数据为addr=10’b1001110011 。因为I2C在发送地址时,都是按一个Byte(8bit)进行发送,写入的10bit地址截取低7位,是7’b1110011,在发送地址周期时,先发送1110011,在最后一位发送R/W位,构成I2C总线上的地址周期。反应在I2C总线上,因为是写擦操作R/W位为0,就可以看到地址周期是11100110。
👇7bit地址结构:
10bit地址的正常寻址和异常寻址。第一次为正常的传输,对10bit地址的两个Byte和data回应ACK;第二次传输错误地址,slave对10比特地址的第一个Byte回应NACK,触发 ABRT_10ADDR1_NOACK。
👇10bit地址结构如下:第一个10bit地址Byte的高5位补上1111_0,
PS:从上面的波形都可以看出,10bit地址下都是在发送第一个Byte就返回NACK了,但是按理说第一个Byte应该是吻合的(因为高9、10位并没有改变,只改变了低8位),而第二个Byte才应该返回NACK。
尝试以下两种方式,均无法触发ABRT(ABRT_10ADDR2_NOACK,bit 2 ):10bit地址模式下,发送第二个Byte后返回NACK。
`uvm_do_on_with(apb_user_address_check_seq,p_sequencer.apb_mst_sqr,{ADDR == (address[j] + 10'b00_0000_1000);})
`uvm_do_on_with(apb_user_address_check_seq,p_sequencer.apb_mst_sqr,{ADDR == (address[j] + 10'b01_0000_0000);})
个人认为:
- 有可能需要在多个slave响应master时,才可以触发;
- 要么是设计的确存在问题,无法触发这个中断。
【暂时留个坑吧,以后再来填…】
7.rkv_i2c_master_enabled_cg_virt_seq
这个sequence有点难搞,个人建议如果对波形不熟悉,就把这sequence拆开,变成三个sequence,分别测试和Debug。设断点进行调试是阅读别人代码很好的手段。
测试的目的:
- 测试1:先关闭I2C,再写数据;
- 测试2:先写数据,再I2C收到数据前,关闭I2C;
- 测试3:在读的过程中,关闭I2C;
- 测试4:对设计一顿糟蹋蹂躏后,切换到正常的写操作,看看设计有没有疯。没有疯掉就是好样的,测试通过;
仿真结果如下所示:
下面开始分别解释不同的测试:
1️⃣ 测试1和测试2(代码16~55行):I2C总线上来不急做响应,I2C就被关闭了,所以只能在APB总线上看到对寄存器的写操作,看不到I2C slave做响应。如下图所示👇 :只能看到APB master对寄存器IC_DATA_CMD寄存器做了三次写操作,但是I2C总线并没有接收到。
首先,我对代码做一下梳理:
//============ 测试1 ==============
//在写操作发送之前,就把I2C总线关闭了
rgm.IC_ENABLE.ENABLE.set(0);
rgm.IC_ENABLE.update(status);
// APB bus not blocked, and data lost
// NO I2C TX data out, and no need to give
// i2c write response sequence
`uvm_do_on_with(apb_write_packet_seq,
p_sequencer.apb_mst_sqr,
{packet.size() == 1;
packet[0] == 8'b11110000;
})
rgm.IC_STATUS.mirror(status);
//=========== 测试2 ============
//APB master 对寄存器做读操作
rgm.IC_ENABLE.ENABLE.set(1);
rgm.IC_ENABLE.update(status);
`uvm_do_on_with(apb_write_packet_seq,
p_sequencer.apb_mst_sqr,
{packet.size() == 2;
packet[0] == 8'b11110001;
packet[1] == 8'b11110010;
})
//在写操作完成后,立即关闭I2C
rgm.IC_STATUS.mirror(status);
rgm.IC_ENABLE.ENABLE.set(0);
rgm.IC_ENABLE.update(status);
rgm.IC_STATUS.mirror(status);
//但是在I2C slave还没有做响应之前,就关闭了I2C
`uvm_do_on_with(apb_noread_packet_seq,p_sequencer.apb_mst_sqr,{packet.size() == 1;})
仿真中结果如下,只能看到master对寄存器做了访问,I2C总线不会做的响应。
2️⃣ 测试3(从58行开始):测试在读的过程中,关闭I2C。
先解释如何实现在读的过程中,关闭I2C设备。如下代码:👇
//================ 测试3 ==============
//fork...join_none表示不会阻碍后续的线程的正常运行
//apb_noread_packet_seq是向I2C的slave发送read请求,所以需要一定的总线传输滞后
//i2c_slv_read_resp_seq是I2C的slave直接响应一个数据
fork
`uvm_do_on_with(apb_noread_packet_seq,p_sequencer.apb_mst_sqr,{packet.size() == 3;})
`uvm_do_on_with(i2c_slv_read_resp_seq,
p_sequencer.i2c_slv_sqr,
{packet.size() == 1;
packet[0] == 8'b00001001;
})
join_none
//一直等到RX FIFO中有数据,不为空的时候,
//跳出while循环
//关闭I2C
while(1) begin
rgm.IC_STATUS.mirror(status);
if(rgm.IC_STATUS.RFNE.get() == 1) begin
rgm.IC_ENABLE.ENABLE.set(0);
rgm.IC_ENABLE.update(status);
rgm.IC_STATUS.mirror(status);
break;
end
end
但是从下图的波形上看👇,总线上发生了三个读操作,ACK了前两个数据,NCAK最后数据,但是在ACK后,APB总线上prdata并没有接收到响应的数据。也就是说,在测试3的条件下,尽管APB master对寄存器做了访问,写操作也确实都写入了对应的寄存器,但是apb master 却并没有获取到读取的数据
如下图👇:apb master 却并没有获取到读取的数据的原因,因为I2C回应ACK时,还没等到APB来接收这个I2C上读取的数据,ENABLE就被拉低了。
3️⃣ 测试4:经过一番非人的开关折磨,重新给一次正常的写操作。
如下图👇,I打开I2C,并相对应地址的SLAVE写入数据。
8.rkv_i2c_master_fs_cnt_virt_seq
1️⃣测试目的:
仿真结果:
上图virtual sequence中的操作,刚好对应下图的波形👇,mirror会对总线上对应寄存器的地址做读操作,update底层调用的其实就是各个reg的write(),所以会对对应的寄存器做写操作。
- 先对IC_**_SPKLEN寄存器做了mirror,
- 然后分别对两个IC_**_SPKLEN寄存器先set一个期望值(因为设置期望是对寄存器模型做操作,不会反映在总线上),然后做了updata+mirror操作。