I2C协议介绍
I2C(Inter-Integrated Circuit)是一种简单、双向二线制同步串行总线,使用多主从架构,它只需要SCL和SDA两根线即可在连接于总线上的器件之间传送信息,由于其简单性,它被广泛用于微控制器与传感器阵列、显示器、IoT设备、EEPROM等之间的通信。
I2C总线结构图如下图所示:
I2C总线时序图:
I2C总线特点:
1、master/slave可任意配置,同一时刻,只能配置为其中一种模式;
2、I2C将SCL处于高时SDA拉低的动作作为开始信号,SCL处于高时SDA拉高的动作作为结束信号;
3、传输数据时,SDA在SCL低电平时改变数据,在SCL高电平时保持数据,每个SCL脉冲的高电平传递1位数据。
4、Idle状态,SCL和SDA都是高电平。
5、I2C总线上可以挂载一个主设备和多个从设备,实现一对一(一个主设备对接一个从设备)或者一对多(一个主设备对接多个从设备)的通信;
6、主设备负责调度总线,决定某个时间和其中一个从设备通信。在同一时间,只有主设备和其中一个从设备通信,其余的从设备处于等待状态,等待主设备与其通信;
7、每个从设备在I2C总线上都有唯一的地址,主设备就是通过地址来区分不同的从设备,从而决定和哪一个从设备通信;
DW_APB_I2C模块
模块作用:接收来自APB总线的数据,通过两组FIFO和移位寄存器,将从APB到I2C的数据进行串并转换。(类似APB-UART)进行写操作时,数据会先写到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内部对应的控制寄存器的值。配置完成之后,DW_APB_I2C开始把地址发送到I2C线上,寻找对应的I2C设备。设备确认传输方向后,把数据一拍一拍打到I2C线上,然后DW_APB_I2C把这个数据经过RX SHIFT和RX FIFO,以并行数据的形式打到APB线上,然后apb master就可以拿到这个数据。
TX FIFO:接收CPU从APB总线上送来的并行数据,并加以保存。
Tx Shift:以SCL的速率把TX FIFO里的并行数据一位一位的输出到I2C的SDA数据线上。
RX FIFO:接收经过RX Shift转换为并行的数据,并加以保存。
RX Shift:以SCL的速率把采样SDA数据线上的数据,并转化为并行数据送至RX FIFO。
Clock Generator:当DW_apb_i2c作为Master时,根据目前的工作速度模式,输出合适的时钟频率到SCL信号线上。
Master/Slave FSM:控制DW_apb_i2c作为Master/Slave时工作的状态机。
中断产生:与中断寄存器里的值对应,实时反映 Dw_apb_i2c的中断情况,以便CPU做出相应的处理操作。
APB Slave Interface:接收CPU的指令,有配置内部寄存器决定Dw_apb_i2c的工作模式、发出指令使DW_apb_i2c进行数据传输、清除中断等功能。
I2C Interface:与系统I2C总线连接。
寄存器模块:包括配置寄存器、指令寄存器、状态寄存器、中断寄存器。
DW_APB_I2C验证环境组成:
rkv_i2c_pkg:lvc_apb_pkg 、lvc_i2c_pkg 、 ral_rkv_i2c、 rkv_i2c_configs
rkv_i2c_master_scoreboard 、rkv_i2c_cgm 、rkv_i2c_virtual_sequencer
rkv_i2c_env、 rkv_element_sequences 、rkv_i2c_virtual_sequences
rkv_i2c_tests、 rkv_user_element_sequences、 rkv_i2c_user_virtual_sequences
rkv_i2c_user_tests
rkv_i2c_env:在env中例化rkv_i2c_config、rkv_i2c_if、apb_mst、i2c_mst、i2c_slv、
scb、virtual sqr、cgm、rgm、adapter、predictor、
在build_phase中get 到rkv_i2c_config、rkv_i2c_if、rgm的句柄,并set rgm、lvc_apb_config、scb、sqr、cgm的句柄。
在connect_phase中,将apb_mst和i2c_slv的monitor中的port连接到scb中的imp和cgm的imp上,将vip中的sequencer连接到virtual sequencer上。并对寄存器模型进行集成。
Virtual sequencer:传递三个sequencer、rkv_i2c_config、rgm的句柄。
Config:在defines中定义中断的ID,在config中定义两个使能信号:
master_scoreboard_enable,coverage_model_enable。在do_i2c_cfg()函数中对master_cfg与slave_cfg进行配置。
Scb:定义analysis import,run_phase()有五个函数:i2c_refmod(),i2c_write_comparer(),i2c_read_comparer(),i2c_mon_interrupt(),compare_transactin()。
write_apb_master:将lvc_apb_transfer中的transaction压进apb_trans_observed。
virtual function void write_apb_master(lvc_apb_transfer tr);
uvm_reg r;
if(enable) begin
r = cfg.rgm.default_map.get_reg_by_offset(tr.addr);
if(r.get_name() == "IC_DATA_CMD" &&
( (tr.trans_kind == lvc_apb_pkg::WRITE && cfg.rgm.IC_DATA_CMD_CMD.get() == RGM_WRITE && cfg.rgm.IC_STATUS_TFNF.get()) ||
(tr.trans_kind == lvc_apb_pkg::READ && cfg.rgm.IC_DATA_CMD_CMD.get() == RGM_READ && cfg.rgm.IC_STATUS_RFNE.get()) )
)
apb_trans_observed.push_back(tr);
end
endfunction: write_apb_master
write_i2c_slave:将lvc_i2c_slave_transaction中的transaction压进write_data_observed或read_data_observed。
virtual function void write_i2c_slave(lvc_i2c_slave_transaction tr);
if(enable) begin
i2c_trans_observed.push_back(tr);
foreach(tr.data[i]) begin
if(tr.cmd == I2C_WRITE) begin
write_data_observed.push_back(tr.data[i]);
write_count_observed++;
end
else if(tr.cmd == I2C_READ) begin
read_data_observed.push_back(tr.data[i]);
read_count_observed++;
end
end
end
endfunction: write_i2c_slave
i2c_refmod():当apb_trans_observed的size()大于0时,将transaction从中pop出来,并将其push进write_data_expected()或read_data_expected()中。
(write_count_expected++ read_count_expected++)
task i2c_refmod();
lvc_apb_transfer tr;
ral_reg_rkv_i2c_IC_DATA_CMD data_cmd_r;
bit[7:0] data;
data_cmd_r = new("data_cmd_r");
data_cmd_r.build();
forever begin
wait(apb_trans_observed.size() > 0) tr = apb_trans_observed.pop_front();
data_cmd_r.set(tr.data);
if(tr.trans_kind == lvc_apb_pkg::WRITE && cfg.rgm.IC_DATA_CMD_CMD.get() == RGM_WRITE) begin
write_data_expected.push_back(data_cmd_r.DAT.get());
write_count_expected++;
end
else if(tr.trans_kind == lvc_apb_pkg::READ && cfg.rgm.IC_DATA_CMD_CMD.get() == RGM_READ) begin
read_data_expected.push_back(data_cmd_r.DAT.get());
read_count_expected++;
end
end
endtask
i2c_write_comparer(exp,obs):比对write_data
task i2c_write_comparer();
bit[7:0] exp, obs;
forever begin
fork
wait(write_data_expected.size() > 0) exp = write_data_expected.pop_front();
wait(write_data_observed.size() > 0) obs = write_data_observed.pop_front();
join
compare_transaction(exp, obs);
end
endtask
i2c_read_comparer():比对read_data
task i2c_read_comparer();
bit[7:0] exp, obs;
forever begin
fork
wait(read_data_expected.size() > 0) exp = read_data_expected.pop_front();
wait(read_data_observed.size() > 0) obs = read_data_observed.pop_front();
join
compare_transaction(exp, obs);
end
endtask
compare_transaction():比对exp与obs,如果不匹配,则mismatch_detected=1,且mismatch_count++。
function void compare_transaction(bit[7:0] exp, bit[7:0] obs);
bit mismatch_detected = 0 ;
if(exp != obs) begin
`uvm_error(get_type_name(), $sformatf("Byte transferred different in expected value is %h and slave observed value is %h", exp, obs))
mismatch_detected = 1;
end
// check for no mismatch
if(!mismatch_detected)
`uvm_info(get_type_name(), $sformatf("Trans match between expected %h and observed %h", exp, obs), UVM_LOW)
else
mismatch_count++;
endfunction: compare_transaction
i2c_mon_interrupt(): 监控中断/异常中断断言,并禁用socreboard。
task i2c_mon_interrupt();
forever begin
// wait any interrupt asserted
cfg.vif.wait_intr();
if( cfg.vif.get_intr(IC_RX_OVER_INTR_ID)
|| cfg.vif.get_intr(IC_TX_ABRT_INTR_ID)
|| cfg.vif.get_intr(IC_TX_EMPTY_INTR_ID)
) begin
interrupt_abort = 1;
enable = 0;
`uvm_info(get_type_name(), "monitored aborted/exceptional interrupt asserted, and disable scoreboard.", UVM_LOW)
end
end
endtask
I2C的sequence和testcase没写,太多了,建议记几个比较典型的就可以了,比如说常见的寄存器是怎么验的。以及element sequence,virtual sequence,test case之间是怎么样的一个关系,比如在element sequence中定义具体要测试的signal和transfer,在virtual sequence中调用和管理element sequence,在test case中对virtual sequence进行挂载以及对objection进行控制。
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拉低,下一个周期再将其拉高。
寄存器模型是怎么实现读写的?
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端数据的判断条件的设置。
virtual function void write_apb_master(lvc_apb_transfer tr);
uvm_reg r;
if(enable) begin
r = cfg.rgm.default_map.get_reg_by_offset(tr.addr);
if(r.get_name() == "IC_DATA_CMD" &&
( (tr.trans_kind == lvc_apb_pkg::WRITE && cfg.rgm.IC_DATA_CMD_CMD.get() == RGM_WRITE && cfg.rgm.IC_STATUS_TFNF.get()) ||
(tr.trans_kind == lvc_apb_pkg::READ && cfg.rgm.IC_DATA_CMD_CMD.get() == RGM_READ && cfg.rgm.IC_STATUS_RFNE.get()) )
)
apb_trans_observed.push_back(tr);
end
endfunction: write_apb_master
在写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中的数据。
7、时钟
tb中一共有三个时钟,分别是apb_clk、i2c_clk、ref_clk,apb_clk是apb端的时钟信号,因此将其传入apb的接口中以及DUT中的apb端时钟输入口,i2c_clk是i2c端的时钟信号,因此将其传入DUT的i2c端时钟输入口,两个时钟在DUT中运行互不影响。设置DUT的i2c总线传输速率时候的源时钟就是i2c_clk,而与i2c端连接的接口传入的时钟为ref_clk,此时钟不起任何作用,因为lvc_i2c_if中的有用的信号为SCL和SDA,他们不受传入的ref_clk影响,这两个信号正常来说由DUT和i2c_slave控制,但是由于i2c_slave写的不够全面,因此其只控制SDA,SCL完全由DUT来控制。
8. IIC验证结构
1.lvc_apb_master_agent 包括driver, monitor, sequencer,在agent的build_phase中完成了对driver, monitor, sequencer的例化和配置(由于配置为master模式,所以含有driver和sequencer,monitor无论master/slave模式均有);在connect_phase完成了虚接口的传递,以及driver的seq_item_port与sequencer的seq_item_export的端口连接(这两个端口是uvm专门定义的端口,无需例化)
1.1. driver功能:get_and_drive(): 主体任务为drive_transfer(), 根据从sequencer侧获得的seq的trans_kind属性对apb总线执行响应的读写任务。reset_listener(): 在复位信号有效时对apb总线执行复位操作。
1.2. monitor功能:collect_transfer(): 通过lvc_apb_if 监测apb接口的地址线、数据线、控制线以及传输情况;根据check_enable/coverage_enable两个开关,通过analysis_port传递监测到的信号执行相应的功能。
lvc_apb_transfer事务包括:addr
data
idle_cycles
typedef enum {IDLE, WRITE, READ } lvc_apb_trans_kind
typedef enum {OK, ERROR} lvc_apb_trans_status
2.lvc_i2c_slave_agent 包括driver, monitor, sequencer,在build_phase中将agent配置为passive模式(slave),并例化driver, monitor, sequencer组件;(由于对I2C配置是可以更改的(可以控制slave和master的数量),为了便于后续配置更改,i2c_slave_agent在build_phase中具有层次化的配置,代码看起来比apb复杂);在connect_phase完成driver和sequencer的端口连接(代码与apb_master_agent相似);run_phase中根据agent配置模式通过reconfigure_via_task完成对driver, sequencer的重新配置(monitor在slave/master模式下均有)。
2.1 driver功能:wait_for_reset():等待复位信号到来(该任务位于lvc_i2c_bfm_common中);
consum_from_seq_item_port():在通过seq_item_port收到有效事务后,将事务按照协议驱动给i2c总线,并为此事务做标记后复制一份resp发回给sequencer(主体任务send_xact位于lvc_i2c_driver_common中)
2.2 monitor功能:包含两个analysis_port,xact_observed_port(当观测到完整事务,monitor通过write方法将信号放进port); data_observed_port(monitor一旦监测到数据就会通过port传递信息,而不会等整个事务都出现在总线上);wait_for_reset(): 等待复位信号到来(该任务位于lvc_i2c_bfm_common中);received_and_sent():按照i2c协议监测事务,并在数据不为空或监测未结束时,两个analysis_port持续执行write方法将信号写入端口。
lvc_i2c_transaction事务包括:
command_enum cmd 它指定要执行的命令类型。
可能的命令有
*—I2C_WRITE:写命令
*—I2C_READ:读命令
*—I2C_GEN_CALL:广播通用命令
*—I2C_DEVICE_ID:设备ID请求命令
cfg 配置
addr 从地址
addr_st 存储从地址从主地址开始的时间。
addr_et 存储从地址从主地址结束的时间。
data 它包含要在总线上生成的用户数据.
data_st 存储DATA所有字节的起始时间。
data_et存储DATA所有字节的结束时间。
start_detected 检测Master发出start信号。可能的值为
* - 1:检测到启动
* - 0:未检测到启动
stop_detected检测Master发出stop信号。可能的值为
* - 1:检测到停止
* - 0:未检测到停止
rep_start_detected *重复启动信号。
* - 1:检测到重复启动
* - 0:未检测到重复启动
start_detected_st 存储Master发送的START条件的开始时间。
start_detected_et 存储Master发送的START条件的结束时间。
stop_detected_st 存储Master发送的STOP条件的开始时间。
stop_detected_et存储由Master发送的STOP状态结束时间。
rep_start_detected_st存储Master的repeat start的开始时间。
rep_start_detected_et存储Master的repeat start的结束时间。
ack_detected 信号响应为ACK/NACK。
* - 1 : ACK detected
* - 0 : NACK detected
ack_detected_st 保存所有数据字节的ACK/NACK响应的开始时间。
ack_detected_et保存所有数据字节的ACK/NACK响应的结束时间。
read_write 读/写 信号
* 1:检测到读命令
* 0:检测到写命令
read_write_st 存储Master中READ/WRITE命令的起始时间。
read_write_et存储Master中READ/WRITE命令的结束时间。
enable_cmd 启用/禁用从Master触发的命令。可能的值有:
* 1:命令发送给BFM。
* 0:不下发命令给BFM。
enable_pkt 启用/禁用序列数据触发驱动。可能的值有:
* 1: Agent Driver使用序列数据
* 0: Agent Driver忽略序列数据
enable_cfg 启用/禁用配置驱动。可能的值有:
* 1:向BFM发送配置。
* 0:不发送配置给BFM。
addr_10bit 指定主机启用10位寻址。
ADDR_10BIT_ON_wt 于控制10bit寻址的相对频率,由rationable_addr_10bit约束的随机化产生。
ADDR_10BIT_OFF_wt 用于控制7bit寻址的相对频率,由rationable_addr_10bit约束的随机化产生。
9. Scoreboard功能
socreboard包含两个analysis port的接收端口,分别来自apb_master和i2c_slave中的monitor,负责接收来自两边的观测数据,并通过访问寄存器模型获得读写指令,再依据apb写入寄存器模型中的数据模拟出refmod的期望数据,最后将其与i2c侧观测到的数据进行对比。
10. Predictor功能
更新寄存器模型有两种途径,一种是自动预测途径,当driver将读取值返回后,寄存器模型会更新寄存器的镜像值和期望值;
另一种是经由predictor的途径,monitor从apb总线上收集到的transaction交给寄存器模型,再由寄存器模型更新相应寄存器的值,这种方式则需要实例化一个reg_predictor并为这个reg_predictor例化一个adapter。
a. 使用predictor时,需要为其设置adapter及map变量,只有设置了map后,才能将predictor和寄存器模型关联在一起; uvm_predictor使用adapter将monitor传递来的bus transaction转换成uvm_reg_item, 然后使用map根据uvm_reg_item中的address信息找到对应的register,并更新其镜像值和期望值。
5. Virtual sequencer功能:实现对apb_mst_sqr, i2c_slave_sqr的挂载,最终实现各类seq在test中的启动。
11.IIC验证功能点拆分
总线时序检查、寄存器测试、发送测试和接收测试。目标地址和从机地址测试;速度模式测试;7bit和10bit测试;复位测试。
1.IIC协议测试,
总线时序检查主要通过在接口模型中添加相关的断言,利用断言机制实时监测总线信号之间的时序是否满足协议要求。
2.寄存器是外部主机访问 APB-I2C 控制器的接口,因此需要首先进行寄存器测试确保外部主机能够正常访问控制器。寄存器测试包括复位值和寄存器读写访问,IP模块寄存器实现功能点,其中复位值测试用于检查上电复位后寄存器模型与DUT中寄存器的默认值是否相同;寄存器读写访问测试确保所有寄存器都能被所有的访问模式正确访问;验证一个模块中所有寄存器的实现。
3.状态测试;模块是否处于activity模式;如果模块已启用,则进行enabled状态检查,其覆盖策略均为覆盖组。
4.对TX_FIFO, RX_FIFO 检查(TX and RX FIFO status,empty/full/overflow),其覆盖策略均为覆盖组。
5.中断测试,中断状态测试,清除中断测试,中断源识别测试,其覆盖策略均为覆盖组。
6.SDA control,设置建立时间和保持时间测试,其覆盖策略均为覆盖组。
7.timeout counter,此计数器为零将中断等待PSLVERR为高的事务,其覆盖策略均为覆盖组。
功能测试点 | 测试内容 |
rkv_i2c_master_abrt_10b_rd_norstrt_test | 中断场景为:restart被禁止使能时,主机在10bit addressing模式下发送了一个读命令。 |
rkv_i2c_master_abrt_7b_addr_noack_test | 中断场景为:主机在7 bit address模式,且第一个 7地址字节未被任何从机识别。 |
rkv_i2c_master_abrt_sbyte_norstrt_test | 中断场景为: RESTART被禁用,用户尝试发送 START BYTE。 |
rkv_i2c_master_abrt_txdata_noack_test | 在master模式下,收到地址确认后,发送数据字 节却没有收到远程slave的确认。 |
rkv_i2c_master_activity_intr_output_test | 监测I2C总线的活动测试,包括ACTIVITY, START_DET, STOP_DET信号的产生与消失。 |
rkv_i2c_master_address_cg_test | DUT作为master, 7bit/10bit寻址正确/错误的目标 地址;以及DUT作为slave,配置从机地址。 |
rkv_i2c_master_enabled_cg_test | DUT enable测试。 |
rkv_i2c_master_fs_cnt_test | fast_speed模式不同速度下write数据测试。 |
rkv_i2c_master_hs_cnt_test | high_speed模式不同速度下write数据。 |
rkv_i2c_master_hs_master_code_test | 高速模式测试。 |
rkv_i2c_master_rx_full_intr_test | RX_FIFO FULL 触发中断测试。 |
rkv_i2c_master_rx_over_intr_test | RX_FIFO 中数据超出临界值(8个),溢出, 触发中断。 |
rkv_i2c_master_sda_control_cg_test | 设置建立时间、保持时间。 |
rkv_i2c_master_ss_cnt_test | slow_speed模式不同速度下write数据。 |
rkv_i2c_master_start_byte_test | 发送start_byte测试。 |
rkv_i2c_master_timeout_cg_test | 退出时间计数测试。 |
rkv_i2c_master_tx_abrt_intr_test | TX_ABRT中断测试。 |
rkv_i2c_master_tx_empty_intr_test | TX_FIFO EMPTY触发中断测试。 |
rkv_i2c_master_tx_full_intr_test | TX_FIFO FULL触发中断测试。 |
rkv_i2c_master_directed_interrupt_test | 检查中断输出是否与中断状态值相同。 |
rkv_i2c_master_directed_read_packet_test | DUT读测试。 |
rkv_i2c_master_directed_write_packet_test | DUT写测试。 |
rkv_i2c_quick_reg_access_test | 通过updata_regs()将designed values写到 DUT的regs中。 |
rkv_i2c_reg_access_test | 寄存器的读写测试。 |
rkv_i2c_reg_bit_bash_test | 寄存器的实现测试。 |
rkv_i2c_reg_hw_reset_test | 硬件复位值检查测试。 |
10.Covergroup
covergroup | coverpoint | bins | cross |
1.cg_lvc_apb_command | pwrite | write、read | cmd_write/cmd_read/cmd_idle |
psel | sel、unsel | ||
2.cg_lvc_apb_trans_timing_group | psel | single、burst_2、burst_4,6,8,16,32 | |
penable | single | ||
burst | |||
3.cg_lvc_apb_write_read_order_group | pwrite | write_write | |
write_read | |||
read_write | |||
read_read | |||
4.cg_addr | m_offset | 覆盖了各个寄存器的offset(偏移地址) |
11.IIC中断功能的验证,举例
rkv_i2c_master_abrt_10b_rd_norstrt_test | 中断场景为:restart被禁止使能时,主机在10bit addressing模式下发送了一个读命令。 |
rkv_i2c_master_abrt_7b_addr_noack_test | 中断场景为:主机在7 bit address模式,且第一个7地址字节未被任何从机识别。 |
rkv_i2c_master_abrt_sbyte_norstrt_test | 中断场景为: RESTART被禁用,用户尝试发送START BYTE。 |
rkv_i2c_master_abrt_txdata_noack_test | 在master模式下,收到地址确认后,发送数据字节却没有收到远程slave的确认。 |
rkv_i2c_master_tx_abrt_intr_test | TX_ABRT中断测试。 |
12.验证计划
Section | Title | Description | Link | Type | Weight | Goal |
1 | registers | register access test. | 1 | 100 | ||
1.1 | hardware reset value | apply uvm_reg_hw_reset_seq | rkv_i2c_reg_hw_reset_test | Test | 1 | 100 |
1.2 | bit bash | apply uvm_reg_bit_bash_seq | rkv_i2c_reg_bit_bash_test | Test | 1 | 100 |
1.3 | register access | apply uvm_reg_access_seq | rkv_i2c_reg_access_test | Test | 1 | 100 |
2 | I2C protocol | |||||
2.1 | target and slave address | IC_TAR, IC_SAR register set | target_address_and_slave_address_cg | CoverGroup | 1 | 100 |
2.2 | speed modes | standard mode, fast mode, high speed(optional) | speed_modes_cg | CoverGroup | 1 | 100 |
2.3 | 7-or-10 bits addressing | 7bits or 10bits addressing | bits7_or_bits10_addressing_cg | CoverGroup | 1 | 100 |
2.4 | restart condition | restart enabled and detected | restart_condition_cg | CoverGroup | 1 | 100 |
3 | status | i2c module status | 1 | 100 | ||
3.1 | activity | if module is in activity mode | activity_cg | CoverGroup | 1 | 100 |
3.2 | enabled | if module is enabled, and would be checked with enabled status | enabled_cg | CoverGroup | 1 | 100 |
4 | data buffer | TX and RX FIFO status | 1 | 100 | ||
4.1 | tx fifo status | TX FIFO empty/full/overflow | tx_fifo_status_cg | CoverGroup | 1 | 100 |
4.2 | rx fifo status | RX FIFO empty/full/overflow | rx_fifo_status_cg | CoverGroup | 1 | 100 |
5 | interrupt | 1 | 100 | |||
5.1 | status register | if interrupt status register would reflect the state | interrupt_status_cg | CoverGroup | 1 | 100 |
5.2 | clear register | if dedicated register would clear interrupt | interrupt_clear_cg | CoverGroup | 1 | 100 |
5.3 | hardware signals | if hardware coresponding output would be raised | interrupt_hardware_outputs_cg | CoverGroup | 1 | 100 |
5.4 | tx abort source | different tx abort source should be identified | interrupt_tx_abort_sources_cg | CoverGroup | 1 | 100 |
6 | SDA control | setup and hold timing with dedicated registers | sda_control_cg | CoverGroup | 1 | 100 |
7 | timeout counter | A zero on this counter will break the waited transaction with PSLVERR as high. | timeout_counter_cg | CoverGroup | 1 | 100 |
8 | test status | All tests status and pass rate recorded | rkv_i2c_master_directed_write_packet_test rkv_i2c_master_directed_read_packet_test rkv_i2c_master_directed_interrupt_test rkv_i2c_master_rx_over_intr_test rkv_i2c_master_rx_full_intr_test rkv_i2c_master_tx_full_intr_test rkv_i2c_master_tx_empty_intr_test rkv_i2c_master_tx_abrt_intr_test rkv_i2c_master_activity_intr_output_test rkv_i2c_quick_reg_access_test rkv_i2c_reg_access_test rkv_i2c_reg_bit_bash_test rkv_i2c_reg_hw_reset_test rkv_i2c_master_abrt_7b_addr_noack_test rkv_i2c_master_abrt_10b_rd_norstrt_test rkv_i2c_master_abrt_txdata_noack_test rkv_i2c_master_abrt_sbyte_norstrt_test rkv_i2c_master_start_byte_test rkv_i2c_master_enabled_cg_test rkv_i2c_master_address_cg_test rkv_i2c_master_sda_control_cg_test rkv_i2c_master_timeout_cg_test rkv_i2c_master_ss_cnt_test rkv_i2c_master_fs_cnt_test rkv_i2c_master_hs_cnt_test rkv_i2c_master_hs_master_code_test | Test | 1 | 100 |
elem_seqs | 测试内容 |
rkv_apb_base_sequence | 随机化RGM register field value,声明自定义类型(lvc_apb_master_sequencer)的句柄p_sequencer,定义寄存器模型变量,检查寄存器RAL模型是否可以检查到。 |
rkv_apb_config_seq | 配置寄存器 |
rkv_apb_intr_clear_seq | (清除中断)负责将传入的中断对应的中断清除。此事务mirror更新IC_RAW_INTR_STAT中断状态寄存器模型与dut,将传入的中断对应的中断清除寄存器里面的对应位mirror更新,从而清除对应的IC_TX_ABRT_INTR_ID中断(直接读取dut中此值就可以清除中断)。 |
rkv_apb_intr_enable_seq | 产生中断 |
rkv_apb_intr_wait_seq | (监控中断产生)一直处于监听状态,dut数据实时mirror到IC_RAW_INTR_STAT寄存器模型,当IC_RAW_INTR_STAT中断状态寄存器里面的中断位置为1(表示i2c发送数据时候无法正常完成操作),则监听到中断,事务结束。 |
rkv_apb_read_packet_seq | 读数据(不检查RX_FIFO为空) |
rkv_apb_wait_empty_seq | 一直监听,并mirror更新IC_STATUS状态寄存器模型,当ACTIVITY=0(总线处于idle状态),且TFE=1(TX FIFO为空)且RFNE=0(RX FIFO为空),事务就结束。 |
rkv_apb_write_packet_seq | 写数据(不检查TX_FIFO为满) |
rkv_i2c_slave_base_sequence | |
rkv_i2c_slave_read_response_seq | (读回应)对ACK\NACK位及数量进行约束。i2c协议传输时候,slave里通过nack_addr等参数数量判断是否应该回应ack及nack。此处设置nack_data=1表示对第一个数据data回应nack。 |
rkv_i2c_slave_write_response_seq | 写回应 |
user_elem_seqs | 测试内容 |
rkv_apb_noread_packet_seq | 在read数据的时候,不再监控RX_FIFO是否是空的,直接读数据。 |
rkv_apb_user_address_check_seq | apb_master检查目标从机地址 |
rkv_apb_user_config_seq | 对寄存器模型的配置参数做约束,其父类对配置参数做声明及约束 |
rkv_apb_user_read_packet_seq | (读数据)从i2c端获取数据,将read命令写进dut的cmd中。 |
rkv_apb_user_read_rx_fifo_seq | |
rkv_apb_user_wait_detect_abort_source_seq | 实时mirror监控dut的IC_RAW_INTR_STAT寄存器,如果其TX_ABRT域为1,就表示监控到TX_ABRT中断的产生。跳出监控,然后,mirror查看DUT的IC_TX_ABRT_SOURCE寄存器,某个域为1就表示某个中断触发了TX_ABRT的中断。 |
rkv_apb_user_wait_empty_seq | 一直监听,并mirror更新IC_STATUS状态寄存器模型,当ACTIVITY=0(总线处于idle状态),且TFE=1(TX FIFO为空)且RFNE=0(RX FIFO为空),事务就结束。 |
rkv_apb_user_write_packet_seq | 写数据,里面将要写的数据包数据先set到寄存器模型,然后write到dut。 |
rkv_apb_write_nocheck_packet_seq. | 在write数据的时候,不再监控TX_FIFO是否是满的,直接将数据写入。 |
apb_sequences | 测试内容 |
lvc_apb_master_single_write_sequence | 写操作,拿到返回的rsp的trans_status后存储在成员变量trans_status里。 |
lvc_apb_master_single_read_sequence | 读操作,拿到返回的rsp的数据后存储在成员变量data、trans_status里。 |
lvc_apb_master_write_read_sequence | 写操作后进行读操作,拿到返回的rsp的数据后存储在成员变量data、trans_status里。 |
lvc_apb_master_burst_write_sequence | 连续的写操作,按照地址增长的顺序,把所有的数据写到data数组中。因为是连续写操作,所以idle_cycles == 0,即数据之间没有空闲周期。全部发送完成后,将总线置为IDLE。判断trans_staus的状态。 |
lvc_apb_master_burst_read_sequence | 连续的读操作,每次读取回来的数据,idle_cycles == 0,从rsp中拿出来放到data数组中。全部读取完成之后,将总线置为IDLE。判断trans_staus的状态。 |
apb_single_transaction_sequence | 随机化addr,测试连续写操作。 |
随机化addr,测试连续读操作,并比较数据是否一致。 | |
随机化addr,先进行写操作,再进行读操作,并比较读取的数据是否一致。 | |
随机化addr,写完立即读,中间没有idle空闲,并检查读取数据是否一致。 | |
先全部写操作完毕,在完全读出来,地址是连续增长的。 |
user_virt_seqs | 测试内容 |
rkv_i2c_base_virtual_sequence | |
rkv_i2c_master_abrt_10b_rd_norstrt_virt_seq | 中断场景为:restart被禁止使能时,主机在10bit addressing模式下发送了一个读命令。 |
rkv_i2c_master_abrt_7b_addr_noack_virt_seq | 中断场景为:主机在7 bit address模式,且第一个7地址字节未被任何从机识别。 |
rkv_i2c_master_abrt_sbyte_norstrt_virt_seq | 中断场景为:RESTART被禁用,用户尝试发送START BYTE。对寄存器模型的配置参数做约束,其父类对配置参数做声明及约束。配置为10位地址,IC_RESTART=0(禁止i2c主机产生restart),TX_EMPTY_CTRL=1(打开TX_EMPTY中断),SPECIAL=1(允许gener_call或Start_byte),GC_OR_START=1(start_byte传输,=0表示gen_call)。/配置为start_byte,写数据,写回应,wait_empty. |
rkv_i2c_master_abrt_txdata_noack_virt_seq | 在master模式下,收到地址确认后,发送数据字节却没有收到远程slave的确认。 |
rkv_i2c_master_activity_intr_output_virt_seq | 监测I2C总线的活动,包括ACTIVITY, START_DET, STOP_DET信号的产生与消失。 |
rkv_i2c_master_address_cg_virt_seq | DUT作为master, 7bit/10bit寻址正确/错误的目标地址;以及DUT作为slave,配置从机地址。 |
rkv_i2c_master_enabled_cg_virt_seq | DUT enable测试;1、关闭I2C写数据。2、写数据,在I2C接收数据之前,关闭I2C。3、关闭i2c后,read数据。4、在读的过程中关闭I2C。正常写操作。 |
rkv_i2c_master_fs_cnt_virt_seq | fast_speed模式不同速度下write数据 |
rkv_i2c_master_hs_cnt_virt_seq | high_speed模式不同速度下write数据 |
rkv_i2c_master_hs_master_code_virt_seq | 高速模式测试。1.配置highspeed 。2.更新寄存器模型。3.设置最小值并更新。4.配置highspeed对应的3。5.发送一个数据。6.回应。 7.等待数据发送结束。8.关闭i2c |
rkv_i2c_master_rx_full_intr_virt_seq | RX_FIFO FULL 触发中断。1.设置RX_FIFO_FULL中断触发数量为3。2.配置7bit等参数。3.给cmd发送8个读请求。4. i2c回应8个数据。5.从cmd寄存器读值同时监控中断的发生,随着数据的读出,中断会被清除。 |
rkv_i2c_master_rx_over_intr_virt_seq | RX_FIFO数据超出临界值(8个),溢出,触发中断。1. 配置。2.发送读8个数据请求。3.i2c回应8个数据,存入将dut的数据更新到寄存器模型时,对CMD寄存器执行了一次read,导致一个数据的读出。4.发送2个数据请求并回应两个数据。5.等待中断触发。 |
rkv_i2c_master_sda_control_cg_virt_seq | 建立保持时间 |
rkv_i2c_master_ss_cnt_virt_seq | slow_speed模式不同速度下write数据 |
rkv_i2c_master_start_byte_virt_seq | TX_EMPTY_CTRL控制是否产生TX_EMPTY中断,1表示产生。GC_OR_START置1表示使用start_byte命令;置0表示使用general_call命令,此时只能执行写入命令,否则将使IC_RAW_INTR_START寄存器第6位置1。 |
rkv_i2c_master_timeout_cg_virt_seq | 退出时间计数 |
rkv_i2c_master_tx_abrt_intr_virt_seq | TX_ABRT中断;1.配置。2.写8个数据。3.强制。4.中断。4.检测中断。6.中断清除。 |
rkv_i2c_master_tx_empty_intr_virt_seq | TX_FIFO EMPTY触发中断。写一些数据,等待TX EMPTY中断。1.配置apb_user_cfg_seq。2.写数据apb_user_write_packet_seq。3.写回应i2c_slv_write_resp_seq。4监测中断apb_intr_wait_seq。5.写数据apb_user_write_packet_seq。6.结束apb_user_wait_empty_seq。 |
rkv_i2c_master_tx_full_intr_virt_seq | TX_FIFO FULL触发中断。1.配置。2.apb_write_nocheck_packet_seq写8个数据。3.i2c_slave_write_response_seq。4.结束apb_wait_empty_seq。 |
rkv_i2c_master_directed_interrupt_virt_seq | 写一些数据,等待IC_TX _EMPTY_INTR_ID中断,检查中断输出是否与中断状态值相同。 |
rkv_i2c_master_directed_read_packet_virt_seq | 读测试。1.配置apb_cfg_seq。2.读数据apb_read_packet_seq。3.读回应i2c_slv_read_response_seq,将读回的数据放到packet。4.结束apb_wait_empty_seq。 |
rkv_i2c_master_directed_write_packet_virt_seq | 写测试。1.配置apb_cfg_seq。2.写数据apb_write_packet_seq。3.写回应i2c_slv_write_resp_seq。4.结束apb_wait_empty_seq。 |
rkv_i2c_quick_reg_access_virt_seq | |
rkv_i2c_reg_access_virt_seq | uvm_reg_access_seq用于检查寄存器的读写(先前门写后门读再后门写前门读);创建reg_access_seq,rgm寄存器reset,把rgm寄存器赋值给seq的model,start启动。 |
rkv_i2c_reg_bit_bash_virt_seq | 验证一个模块中所有寄存器的实现;创建uvm_reg_bit_bash_seq,rgm寄存器reset,把rgm寄存器赋值给seq的model,start启动。 |
rkv_i2c_reg_hw_reset_virt_seq | uvm_reg_hw_reset_seq用于检查上电复位后寄存器模型与DUT中寄存器的默认值是否相同;创建reg_rst_seq,rgm寄存器reset,把rgm寄存器赋值给seq的model,start启动。 |
还有许多没有验证到的地方:
①整个模块作为从机的响应。
②地址值的随机组合,错误地址(如与保留地址冲突,地址错误等)
③多从机,目前只有单个从机
④范围性的参数,如速度和时间的验证不完备
⑤个别中断的触发未成功验证