I2C虚拟项目笔记(一)-virtual sequence

--------------------------------------------------------------------------------
👉​声明:以下均为路科验证I2C虚拟项目学习笔记。仅以学习为目的,不可用于其他任何商业用途,违者必究👎。
👉​如需获取详细的代码和资料,请咨询公众号​“路科验证”🏬​。拒绝盗版​✋,支持正版👊​
👉​注:以下均为本人的理解,如有解释不清和错误,请私信我或在评论区留言,谢谢。
👉​后续精彩请看:I2C虚拟项目笔记(二)-virtual sequence
👉​持续更新中…​👊​
--------------------------------------------------------------------------------

项目结构

项目结构以框图:有点潦草,可以直接pass
在这里插入图片描述
scoreboard的数据流向:
在这里插入图片描述

准备工作

  1. 开始之前,一定要看清设计上裸露出来的中断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操作。

在这里插入图片描述

SystemVerilog的听课学习笔记,包括讲义截取、知识点记录、注意事项等细节的标注。 目录如下: 第一章 SV环境构建常识 1 1.1 数据类型 1 四、二值逻辑 4 定宽数组 9 foreach 13 动态数组 16 队列 19 关联数组 21 枚举类型 23 字符串 25 1.2 过程块和方法 27 initial和always 30 function逻辑电路 33 task时序电路 35 动态 静态变量 39 1.3 设计例化和连接 45 第二章 验证的方法 393 动态仿真 395 静态检查 397 虚拟模型 403 硬件加速 405 效能验证 408 性能验证 410 第三章 SV组件实现 99 3.1 接口 100 什么是interface 101 接口的优势 108 3.2 采样和数据驱动 112 竞争问题 113 接口中的时序块clocking 123 利于clocking的驱动 133 3.3 测试的开始和结束 136 仿真开始 139 program隐式结束 143 program显式结束 145 软件域program 147 3.4 调试方法 150 第四章 验证的计划 166 4.1 计划概述 166 4.2 计划的内容 173 4.3 计划的实现 185 4.4 计划的进程评估 194 第五章 验证的管理 277 6.1 验证的周期检查 277 6.2 管理三要素 291 6.3 验证的收敛 303 6.4 问题追踪 314 6.5 团队建设 321 6.6 验证的专业化 330 第六章 验证平台的结构 48 2.1 测试平台 49 2.2 硬件设计描述 55 MCDF接口描述 58 MCDF接口时序 62 MCDF寄存器描述 65 2.3 激励发生器 67 channel initiator 72 register initiator 73 2.4 监测器 74 2.5 比较器 81 2.6 验证结构 95 第七章 激励发生封装:类 209 5.1 概述 209 5.2 类的成员 233 5.3 类的继承 245 三种类型权限 protected/local/public 247 this super 253 成员覆盖 257 5.4 句柄的使用 263 5.5 包的使用 269 第八章 激励发生的随机化 340 7.1 随机约束和分布 340 权重分布 353 条件约束 355 7.2 约束块控制 358 7.3 随机函数 366 7.4 数组约束 373 7.5 随机控制 388 第九章 线程与通信 432 9.1 线程的使用 432 9.2 线程的控制 441 三个fork...join 443 等待衍生线程 451 停止线程disable 451 9.3 线程的通信 458 第十章 进程评估:覆盖率 495 10.1 覆盖率类型 495 10.2 功能覆盖策略 510 10.3 覆盖组 516 10.4 数据采样 524 10.5 覆盖选项 544 10.6 数据分析 550 第十一章 SV语言核心进阶 552 11.1 类型转换 552 11.2 虚方法 564 11.3 对象拷贝 575 11.4 回调函数 584 11.5 参数化的类 590 第十二章 UVM简介 392 8.2 UVM简介 414 8.3 UVM组件 420 8.4 UVM环境 425
评论 37
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小verifier

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值