From 31deb6fb0024b0776568808bab8fc95506878b03 Mon Sep 17 00:00:00 2001 From: Karol Gugala Date: Tue, 12 May 2026 16:02:42 +0200 Subject: [PATCH 1/2] test_ccc: add test for TE2 with disabled detection Signed-off-by: Karol Gugala --- .../top/lib_i3c_top/i3c_controller_fixed.py | 3 + .../cocotb/top/lib_i3c_top/test_ccc.py | 55 +++++++++++++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/verification/cocotb/top/lib_i3c_top/i3c_controller_fixed.py b/verification/cocotb/top/lib_i3c_top/i3c_controller_fixed.py index 3a699d852..089bda590 100644 --- a/verification/cocotb/top/lib_i3c_top/i3c_controller_fixed.py +++ b/verification/cocotb/top/lib_i3c_top/i3c_controller_fixed.py @@ -892,6 +892,7 @@ async def send_te1_error(self, ccc: int = 0x20) -> None: await self.send_byte_tbit(ccc, inject_tbit_err=True) async def send_te2_error(self, ccc: int, defining_byte: int = None, + target_addr: int = None, corrupt_defining_byte: bool = True) -> None: """ Trigger a TE2 error by sending a CCC with bad T-bit parity on the @@ -915,6 +916,8 @@ async def send_te2_error(self, ccc: int, defining_byte: int = None, await self.send_start() await self.write_addr_header(I3C_RSVD_BYTE) await self.send_byte_tbit(ccc, inject_tbit_err=False) + if target_addr is not None: + await self.write_addr_header(target_addr) if defining_byte is not None: await self.send_byte_tbit(defining_byte, inject_tbit_err=corrupt_defining_byte) diff --git a/verification/cocotb/top/lib_i3c_top/test_ccc.py b/verification/cocotb/top/lib_i3c_top/test_ccc.py index f37c4f8b1..503b4f66f 100644 --- a/verification/cocotb/top/lib_i3c_top/test_ccc.py +++ b/verification/cocotb/top/lib_i3c_top/test_ccc.py @@ -2287,7 +2287,7 @@ async def test_ccc_te2_parity(dut): # ---- Test 1: Bad T-bit on RSTACT defining byte ---- # RSTACT (0x9A) has a defining byte. Corrupt the defining byte T-bit. log.info("Sending RSTACT with bad defining byte T-bit parity (TE2)") - await i3c_controller.send_te2_error(ccc=0x9A, defining_byte=0x01, + await i3c_controller.send_te2_error(ccc=CCC.DIRECT.RSTACT, defining_byte=0x01, corrupt_defining_byte=True) await i3c_controller.send_stop() i3c_controller.give_bus_control() @@ -2316,13 +2316,11 @@ async def test_ccc_te2_parity(dut): # ---- Test 3: Bad T-bit on GETCAPS defining byte ---- log.info("Sending GETCAPS with bad defining byte T-bit parity (TE2)") await tb.write_csr_field(err_intr_addr, te2_stat_field, 1) - await ClockCycles(tb.clk, 5) - await i3c_controller.send_te2_error(ccc=0x95, defining_byte=0x00, + await i3c_controller.send_te2_error(ccc=CCC.DIRECT.GETCAPS, defining_byte=0x00, corrupt_defining_byte=True) await i3c_controller.send_stop() i3c_controller.give_bus_control() - await ClockCycles(tb.clk, 20) te2_stat = await tb.read_csr_field(err_intr_addr, te2_stat_field) assert te2_stat == 1, f"TE2_ERR_STAT should be 1 after GETCAPS bad def byte, got {te2_stat}" @@ -2331,6 +2329,55 @@ async def test_ccc_te2_parity(dut): responses = await i3c_controller.i3c_ccc_read( ccc=CCC.DIRECT.GETBCR, addr=DYNAMIC_ADDR, count=1) assert responses[0][0] == True, "Target should ACK after second TE2 recovery" + + # ---- Test 4: TE2 with detection disabled — status and counter must not change ---- + log.info("Disabling TE2 error detection (TE2_ERR_DET_EN=0)") + err_ctrl_addr = tb.reg_map.I3C_EC.TTI.TARGET_ERR_CTRL.base_addr + te2_det_field = tb.reg_map.I3C_EC.TTI.TARGET_ERR_CTRL.TE2_ERR_DET_EN + await tb.write_csr_field(err_ctrl_addr, te2_det_field, 0) + await tb.write_csr_field(err_intr_addr, te2_stat_field, 1) + + cnt_before = await tb.read_csr_field(te2_cnt_addr, te2_cnt_field) + + log.info("Sending RSTACT with bad defining byte T-bit (TE2_ERR_DET_EN=0)") + await i3c_controller.send_te2_error(ccc=0x9A, defining_byte=0x01, + corrupt_defining_byte=True) + await i3c_controller.send_stop() + i3c_controller.give_bus_control() + + te2_stat = await tb.read_csr_field(err_intr_addr, te2_stat_field) + assert te2_stat == 0, \ + f"TE2_ERR_STAT should be 0 with detection disabled (RSTACT), got {te2_stat}" + + cnt_after = await tb.read_csr_field(te2_cnt_addr, te2_cnt_field) + assert cnt_after == cnt_before, \ + f"TE2 counter must not increment with det_en=0: before={cnt_before}, after={cnt_after}" + + log.info("Sending GETCAPS with bad defining byte T-bit (TE2_ERR_DET_EN=0)") + await tb.write_csr_field(err_intr_addr, te2_stat_field, 1) + cnt_before = cnt_after + + await i3c_controller.send_te2_error(ccc=0x95, target_addr=DYNAMIC_ADDR, defining_byte=0x00, + corrupt_defining_byte=True) + await i3c_controller.send_stop() + i3c_controller.give_bus_control() + + te2_stat = await tb.read_csr_field(err_intr_addr, te2_stat_field) + assert te2_stat == 0, \ + f"TE2_ERR_STAT should be 0 with detection disabled (GETCAPS), got {te2_stat}" + + cnt_after = await tb.read_csr_field(te2_cnt_addr, te2_cnt_field) + assert cnt_after == cnt_before, \ + f"TE2 counter must not increment with det_en=0: before={cnt_before}, after={cnt_after}" + + # Target should still respond normally while detection is disabled + responses = await i3c_controller.i3c_ccc_read( + ccc=CCC.DIRECT.GETBCR, addr=DYNAMIC_ADDR, count=1) + assert responses[0][0] == True, "Target should ACK with TE2 detection disabled" + + # Re-enable TE2 detection + await tb.write_csr_field(err_ctrl_addr, te2_det_field, 1) + tb.te_error_monitor.check() await tb.teardown() From 68b66feeb41e8a2daeefcba42d471c14deeec5fb Mon Sep 17 00:00:00 2001 From: Karol Gugala Date: Tue, 12 May 2026 17:30:52 +0200 Subject: [PATCH 2/2] verification: add test_ccc_setdasa_padding_err_det_disabled Signed-off-by: Karol Gugala --- .../cocotb/top/lib_i3c_top/test_ccc.py | 98 +++++++++++++++++++ verification/testplan/top/target_ccc.hjson | 2 +- 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/verification/cocotb/top/lib_i3c_top/test_ccc.py b/verification/cocotb/top/lib_i3c_top/test_ccc.py index 503b4f66f..945e207ee 100644 --- a/verification/cocotb/top/lib_i3c_top/test_ccc.py +++ b/verification/cocotb/top/lib_i3c_top/test_ccc.py @@ -2255,6 +2255,104 @@ async def test_ccc_setdasa_padding_err(dut): @cocotb.test() +async def test_ccc_setdasa_padding_err_det_disabled(dut): + """ + Verify that SETDASA/SETNEWDA with padding bit[0]=1 and FRAMING_ERR_DET_EN=0 + applies the address normally (rx_data_valid fires) and does NOT set the framing + error status or increment the framing error counter. Per ccc.sv:1119-1127. + """ + log = logging.getLogger("test_ccc_setdasa_padding_err_det_disabled") + + (STATIC_ADDR, VIRT_STATIC_ADDR, DYNAMIC_ADDR, VIRT_DYNAMIC_ADDR) = random.sample(VALID_I3C_ADDRESSES, 4) + i3c_controller, i3c_target, tb = await test_setup(dut, STATIC_ADDR, VIRT_STATIC_ADDR) + + err_intr_addr = tb.reg_map.I3C_EC.TTI.TARGET_ERR_INTR_STATUS.base_addr + framing_stat_field = tb.reg_map.I3C_EC.TTI.TARGET_ERR_INTR_STATUS.FRAMING_ERR_STAT + framing_cnt_addr = tb.reg_map.I3C_EC.TTI.TARGET_ERR_CNT_FRAMING.base_addr + framing_cnt_field = tb.reg_map.I3C_EC.TTI.TARGET_ERR_CNT_FRAMING.CNT + + da_reg_addr = tb.reg_map.I3C_EC.STDBYCTRLMODE.STBY_CR_DEVICE_ADDR.base_addr + da_valid_field = tb.reg_map.I3C_EC.STDBYCTRLMODE.STBY_CR_DEVICE_ADDR.DYNAMIC_ADDR_VALID + da_field = tb.reg_map.I3C_EC.STDBYCTRLMODE.STBY_CR_DEVICE_ADDR.DYNAMIC_ADDR + + err_ctrl_addr = tb.reg_map.I3C_EC.TTI.TARGET_ERR_CTRL.base_addr + framing_det_field = tb.reg_map.I3C_EC.TTI.TARGET_ERR_CTRL.FRAMING_ERR_DET_EN + + # Enable framing error interrupt so any spurious error would be captured + err_en_addr = tb.reg_map.I3C_EC.TTI.TARGET_ERR_INTR_ENABLE.base_addr + framing_en_field = tb.reg_map.I3C_EC.TTI.TARGET_ERR_INTR_ENABLE.FRAMING_ERR_EN + await tb.write_csr_field(err_en_addr, framing_en_field, 1) + + # Clear any stale framing error status (W1C) + await tb.write_csr_field(err_intr_addr, framing_stat_field, 1) + + log.info("Disabling FRAMING_ERR_DET_EN") + await tb.write_csr_field(err_ctrl_addr, framing_det_field, 0) + + # ---- Test 1: SETDASA with bad padding and det_en=0 -> address IS applied ---- + cnt_before = await tb.read_csr_field(framing_cnt_addr, framing_cnt_field) + + log.info(f"SETDASA with bad padding (det_en=0): addr={STATIC_ADDR:#x} -> DA={DYNAMIC_ADDR:#x} | 1") + bad_data_byte = (DYNAMIC_ADDR << 1) | 1 + await i3c_controller.i3c_ccc_write( + ccc=CCC.DIRECT.SETDASA, directed_data=[(STATIC_ADDR, [bad_data_byte])]) + + # Detection disabled: bad padding is ignored, address IS applied + da_valid = await tb.read_csr_field(da_reg_addr, da_valid_field) + assert da_valid == 1, f"DA_VALID should be 1 with framing det disabled, got {da_valid}" + da_val = await tb.read_csr_field(da_reg_addr, da_field) + assert da_val == DYNAMIC_ADDR, f"DA should be {DYNAMIC_ADDR:#x} with det disabled, got {da_val:#x}" + + framing_stat = await tb.read_csr_field(err_intr_addr, framing_stat_field) + assert framing_stat == 0, f"FRAMING_ERR_STAT should be 0 with det disabled (SETDASA), got {framing_stat}" + + cnt_after = await tb.read_csr_field(framing_cnt_addr, framing_cnt_field) + assert cnt_after == cnt_before, ( + f"Framing counter must not increment with det_en=0 (SETDASA): " + f"before={cnt_before}, after={cnt_after}" + ) + + # Assign virtual target address for later GETBCR verification + await i3c_controller.i3c_ccc_write( + ccc=CCC.DIRECT.SETDASA, directed_data=[(VIRT_STATIC_ADDR, [VIRT_DYNAMIC_ADDR << 1])]) + + # ---- Test 2: SETNEWDA with bad padding and det_en=0 -> address IS changed ---- + NEW_ADDR = random.choice([a for a in VALID_I3C_ADDRESSES + if a not in (STATIC_ADDR, VIRT_STATIC_ADDR, DYNAMIC_ADDR, VIRT_DYNAMIC_ADDR)]) + await tb.write_csr_field(err_intr_addr, framing_stat_field, 1) + cnt_before = cnt_after + + log.info(f"SETNEWDA with bad padding (det_en=0): DA={DYNAMIC_ADDR:#x} -> {NEW_ADDR:#x} | 1") + bad_data_byte = (NEW_ADDR << 1) | 1 + await i3c_controller.i3c_ccc_write( + ccc=CCC.DIRECT.SETNEWDA, directed_data=[(DYNAMIC_ADDR, [bad_data_byte])]) + + # Detection disabled: bad padding is ignored, address IS changed to NEW_ADDR + da_val = await tb.read_csr_field(da_reg_addr, da_field) + assert da_val == NEW_ADDR, f"DA should be {NEW_ADDR:#x} with det disabled, got {da_val:#x}" + + framing_stat = await tb.read_csr_field(err_intr_addr, framing_stat_field) + assert framing_stat == 0, f"FRAMING_ERR_STAT should be 0 with det disabled (SETNEWDA), got {framing_stat}" + + cnt_after = await tb.read_csr_field(framing_cnt_addr, framing_cnt_field) + assert cnt_after == cnt_before, ( + f"Framing counter must not increment with det_en=0 (SETNEWDA): " + f"before={cnt_before}, after={cnt_after}" + ) + + # Re-enable framing error detection + await tb.write_csr_field(err_ctrl_addr, framing_det_field, 1) + + # Target should still respond at updated address + responses = await i3c_controller.i3c_ccc_read( + ccc=CCC.DIRECT.GETBCR, addr=NEW_ADDR, count=1) + assert responses[0][0] == True, "Target should ACK at new DA with detection re-enabled" + + tb.te_error_monitor.check() + + await tb.teardown() + + async def test_ccc_te2_parity(dut): """ Verify TE2 error detection: bad T-bit parity on CCC defining byte causes diff --git a/verification/testplan/top/target_ccc.hjson b/verification/testplan/top/target_ccc.hjson index 6f46db71f..b65328a89 100644 --- a/verification/testplan/top/target_ccc.hjson +++ b/verification/testplan/top/target_ccc.hjson @@ -325,7 +325,7 @@ Verifies that SETDASA/SETNEWDA with padding bit[0]=1 triggers a framing error and does NOT apply the address. ''' - tests: ["ccc_setdasa_padding_err"] + tests: ["ccc_setdasa_padding_err", "ccc_setdasa_padding_err_det_disabled"] tags: ["top"] } {