Dynamic Address Assignment

Walk an ENTDAA round bit by bit, predict the order in which targets get addresses, and say what SETDASA and SETAASA do instead.

Lesson
P9.4
Time
35 min
Runs on
Your browser
Check
4 questions

Two identical sensors from one reel had the same static address, which every I2C designer has suffered: the fix is a strap pin or a multiplexer. On I3C the controller asks every unaddressed target for its unique ID and lets the wired-AND sort them bit by bit. The winner gets an address; then the controller asks again (MIPI I3C Basic v1.0 §5.1.4).

One ENTDAA RoundThe controller repeats the round until nobody answers 7E/R.
One ENTDAA roundThe controller repeats the round until nobody answers 7E/R. component S 7E/W, ENTDAA broadcast CCC 0x07 component Sr 7E/R unassigned targetsACK component 64 bits PID, BCR, DCR, opendrain component address + PAR controller, odd parity component ACK the winner keeps theaddress component P after a NACKed 7E/R next roundno ACK
  1. Enter

    ENTDAA is a broadcast CCC with no data; targets that already have an address stay out (MIPI §5.1.9.3.4).

  2. Arbitrate

    Each target sends its 48-bit provisional ID, then BCR and DCR, most significant bit first, with no ACK in between (§5.1.4.2 steps 6 and 7).

  3. Assign

    Seven address bits plus PAR = NOT XOR of the seven; the winner ACKs if the parity checks (step 9).

  4. Repeat or stop

    Back to Sr 7E/R; when nobody ACKs, STOP ends DAA (steps 10 and 11).

Read the diagram as text
  • S 7E/W, ENTDAA: broadcast CCC 0x07
  • Sr 7E/R: unassigned targets ACK
  • 64 bits: PID, BCR, DCR, open drain
  • address + PAR: controller, odd parity
  • ACK: the winner keeps the address
  • P: after a NACKed 7E/R
  • S 7E/W, ENTDAA → Sr 7E/R
  • Sr 7E/R → 64 bits
  • 64 bits → address + PAR
  • address + PAR → ACK
  • ACK → Sr 7E/R: next round
  • Sr 7E/R → P: no ACK

MIPI I3C Basic v1.0 §5.1.4.2 and Figure 19. The whole procedure is open drain; the Sr and 7E/R may be push-pull.

One Round on the Wire

Two Targets Send Their IDs; One Drops OutMeasured, 20 ns per column: bits 33 to 38 of the first DAA round. The provisional IDs differ only in bit 12, the lowest bit of the instance ID.
0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172SCLSDActrl_lowtgt1_lowtgt2_low
  1. Cycle 10

    Both IDs have a 0 here: both targets pull, open drain. The controller only clocks.

  2. Cycle 34

    PID bit 12: target1 sends 0, target2 sends 1 and lets go. It samples 0 at the rise: it has lost (MIPI §5.1.4.2 step 8).

  3. Cycle 46

    From here target2 stays silent for the rest of the round, even where its own ID has a 0.

Measured: the round ends with CTRL DAA PID 02aa01230000 BCR 06 DCR 00 -> 08. The lowest 64-bit value wins, exactly as a 0 wins I2C arbitration (UM10204 §3.1.8).

The 48-bit provisional ID holds a manufacturer ID, a type bit and either a part ID with a 4-bit instance ID or a random value (§5.1.4.1.1). Two parts from one reel differ in the instance ID, set by straps, fuses or memory.

The Order

RoundProvisional IDBCRDCRAssigned
10x02aa012300000x060x000x08
20x02aa012310000x060x000x09
30x02aa012320000x060x000x0a

Measured on three targets. The IDs are an illustrative manufacturer ID 0x155, part 0x0123 and instance IDs 0, 1, 2 (§5.1.4.1.1). With the instance IDs given as 2, 0, 1 instead, the order follows the IDs, not the instances' names: target1 0a, target2 08, target3 09.

Lowest value first, because a 0 wins. The address the controller hands out also sets interrupt priority: a lower address wins an IBI collision, so the controller gives low addresses to urgent targets (§5.1.6.1).

Two Broken Targets

TwinMeasured
target2.pid_tie_bug = 1: keeps sending after it losesCTRL DAA PID 02aa01230000 BCR 06 DCR 00 -> 08 then I3C S 7e W A 07 T0 Sr 7e R A id 02aa01230000 bcr 06 dcr 00 da 08 par 0 A Sr 7e R NA P then CTRL DAA assigned 1 of 2 expected addresses then x on SDA at 32291 then I3C CHECK no_pp_contention at 32292 then I3C S 7e W A Sr 08 W A 3c T1 Sr 08 R A Xc T0 P then target1 08, target2 08, read Xc
target1.addr_parity_wrong = 1: inverts its parity checkCTRL DAA 08 NACKed, offering it again then CTRL DAA 08 NACKed twice: DAA ends with an error then I3C S 7e W A 07 T0 Sr 7e R A id 02aa01230000 bcr 06 dcr 00 da 08 par 0 NA Sr 7e R A id 0... then CTRL DAA assigned 0 of 2 expected addresses then target1 has_da 0, target2 has_da 0, daa_error 1

Measured. In the tie twin target2 has register contents mem[i] = i ^ 0x80, so the read shows where the two targets disagree: bit 7 is x.

The tie: the IDs differ only in bit 12, where target2 has the 1. A target that ignores its loss keeps pulling only where target1 pulls too, so neither notices. Both ACK 0x08; the controller counted on two addresses, got one, and reports the collision as §5.1.4.3 asks.

The parity twin NACKs a good address. The controller offers it once more, the same target wins again and NACKs again, so DAA ends with an error (§5.1.4.2 notes; error type S3, §5.1.10.1.4). Target2, still waiting behind it, never gets an address.

Three Ways to Get an Address

MethodWho can use itFrameMeasured
ENTDAA (0x07)every target without an addressS 7E/W 07, then roundsI3C S 7e W A 07 T0 Sr 7e R A id 02aa01232000 bcr 06 dcr 00 da 08 par 0 A...
SETDASA (0x87)a target with a static address, one at a time7E/W 87, Sr static/W, DA<<1I3C S 7e W A 87 T1 Sr 31 W A 16 T0 P
SETAASA (0x29)targets that support it: static becomes dynamicS 7E/W 29, PI3C S 7e W A 29 T0 P

MIPI I3C Basic v1.0 §5.1.4.2 step 2, §5.1.9.3.10, §5.1.9.3.22 (SETAASA came from the I3C v1.1 draft, preface §4.2). Measured order: SETDASA, then SETAASA, then ENTDAA for the target with no static address: target1 30, target2 0b, target3 08.

The address is not permanent. SETNEWDA (0x88) changes one target's address, RSTDAA (0x06 broadcast, 0x86 direct) makes targets forget theirs (§5.1.4, §5.1.9.3.3, §5.1.9.3.11). Measured: read at 0x0c: 3c, then after RSTDAA: target1 has_da 0, target2 has_da 0, then a new ENTDAA: after ENTDAA: target1 0a, target2 0b.

What the IDs Describe: BCR, DCR and LVR

BCR bitNameMeaning (Table 6)Target, 0x06Secondary controller, 0x46
7:6Device role00 target, 01 controller, 10 and 11 reserved0001
5reserved000
4Bridge1: a bridge device00
3Offline capable1: may not always answer00
2IBI payload1: a mandatory byte follows each accepted IBI11
1IBI request capable1: may raise IBIs11
0Max data speed limit1: ask with GETMXDS00

MIPI I3C Basic v1.0 §5.1.1.2.1, Table 6. Values measured: GETBCR returned 0x06 (P9.5) and DEFSLVS carried 0x46 for the secondary controller (P9.9). The DCR is an 8-bit device type, 0x00 for a generic device (Table 7). A legacy I2C device has a Legacy Virtual Register instead, kept by the controller: bits 7:5 the Table 5 index, bit 4 Fm (1) or Fm+ (0) (Table 8).

Some addresses are never handed out: 0x00 to 0x02, 0x7E, 0x7F and the one-bit corruptions of 7E (0x3E, 0x5E, 0x6E, 0x76, 0x7A, 0x7C). A damaged broadcast address is therefore always detectable (§5.1.2.2.5, Table 9).

Every I3C device must carry these registers; a device without them may not sit on an I3C bus (§5.1.1.2). Before DAA each target must be identifiable by a static address the controller knows, or always by its 48-bit provisional ID (§5.1.4.1).

Try It

Three targets, no addresses yet. ctrl.entdaa() runs rounds until nobody answers; the controller prints each ID it won and the address it gave. The decoder line shows the whole frame: three rounds, then Sr 7e R NA P.

Look at par: 0x08 has one 1, so PAR = 0; 0x09 and 0x0a have two, so PAR = 1. Odd parity over eight bits.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: CTRL DAA PID 02aa01230000 BCR 06 DCR 00 -> 08 CTRL DAA PID 02aa01231000 BCR 06 DCR 00 -> 09 CTRL DAA PID 02aa01232000 BCR 06 DCR 00 -> 0a I3C S 7e W A 07 T0 Sr 7e R A id 02aa01230000 bcr 06 dcr 00 da 08 par 0 A Sr 7e R A id 02aa01231000 bcr 06 dcr 00 da 09 par 1 A Sr 7e R A id 02aa01232000 bcr 06 dcr 00 da 0a par 1 A Sr 7e R NA P
Read included file: i3c_ctrl.sv
`timescale 1ns/1ns
// I3C controller (v1.0: Master), SDR. Task calls drive the pins; SCL is push-pull and driven by this device
// alone (§5.1.2). SDA is open drain in the arbitrable header after S, in every ACK slot and in DAA; push-pull
// in the header after Sr and in write data (§5.1.2.2, §5.1.2.3, §5.1.4.2).
module i3c_ctrl(inout wire scl,sda,output reg sda_low=0,sda_high=0);
 parameter time PP_LOW=40,PP_HIGH=40,OD_LOW=200,OD_HIGH=40,HOLD=5,T_CAS=40,TBUF=1300,
                FM_LOW=1300,FM_HIGH=1200,FM_HOLD=100,FM_SETUP=600,T_MMOVERLAP=220;
 // START_AS_CONTROLLER = 0: a secondary controller's controller half, SCL released until a handoff (§5.1.6.3).
 parameter START_AS_CONTROLLER=1;
 // Fault switches.
 bit pp_too_early=0,od_always=0,directed_as_broadcast=0;
 // Policy and an error injector: flip_mask flips bits of the next written word on the wire (noise);
 // the T-bit is still the parity of the intended byte.
 bit ibi_accept=1,hj_accept=1,trace=0;reg [7:0] flip_mask=0;
 // Mastership: accept requests from secondary controllers; this device's own dynamic address for DEFSLVS.
 // Clock stalling (§5.1.2.5.1): extra SCL low in the next header ACK slot, at most 100 µs (Table 11).
 bit mr_accept=1,mr_seen=0,handed_over=0,scl_en=START_AS_CONTROLLER;reg [6:0] mr_addr=0,own_da=7'h20;
 int stall_ack_ns=0;time ext_low=0;reg [6:0] leg_addr=0;reg [7:0] leg_lvr=0;
 // State.
 reg scl_out=1;bit active=0,sr_done=0,lost=0,serviced=0,hj_seen=0,daa_error=0,legacy=0;time stopped=0;
 reg [6:0] req_addr=0;bit req_rw=0;logic [7:0] mdb;logic tbit;
 int daa_count=0,daa_expect=0,ibis=0;reg [6:0] daa_next=7'h08;
 reg [6:0] daa_addr[0:7];reg [47:0] daa_pid[0:7];reg [7:0] daa_bcr[0:7],daa_dcr[0:7];
 reg [7:0] wbuf[0:7];logic [7:0] rbuf[0:7];int rcount=0;
 assign scl=scl_en?scl_out:1'bz;assign sda=sda_low?1'b0:sda_high?1'b1:1'bz;
 localparam PP=0,OD=1,FM=2,REL=0,DRV_OD=1,DRV_PP=2;
 task automatic set_sda(input int how,input logic v);
  if(how==REL)begin sda_low=0;sda_high=0;end else if(how==DRV_OD)begin sda_low=!v;sda_high=0;end else begin sda_low=!v;sda_high=v;end
 endtask
 // One SCL pulse; SCL is low on entry and on exit. post: 0 nothing; 1 pull SDA low just after the rise
 // when the sample is 0 (ACK to write data handoff, §5.1.2.3.1); 2 release 12 ns after the rise (IBI ACK
 // to mandatory byte handoff, §5.1.2.3.2).
 task automatic clock(input int speed,input int how,input logic v,input int post,output logic s);
  time lo,hi,hd;lo=speed==FM?FM_LOW:speed==OD?OD_LOW:PP_LOW;hi=speed==FM?FM_HIGH:speed==OD?OD_HIGH:PP_HIGH;hd=speed==FM?FM_HOLD:HOLD;
  #(hd);set_sda(how,v);#(lo-hd+ext_low);ext_low=0;scl_out=1;#1;s=sda;
  if(post==1&&s===1'b0)begin sda_low=1;sda_high=0;end
  if(post==2)begin #11;sda_low=0;#(hi-12);end else #(hi-1);
  scl_out=0;
 endtask
 // Next free dynamic address, skipping the reserved values of Table 9.
 function automatic [6:0] next_free(input [6:0] a);
  reg [6:0] n;n=a+1;while(n==7'h3e||n==7'h5e||n==7'h6e||n==7'h76||n==7'h7a||n==7'h7c||n>=7'h7e)n=n+1;next_free=n;
 endfunction
 function automatic [7:0] bcr_of(input [6:0] a);
  bcr_of=0;for(int i=0;i<daa_count;i++)if(daa_addr[i]==a)bcr_of=daa_bcr[i];
 endfunction
 function automatic bit known(input [6:0] a);
  known=0;for(int i=0;i<daa_count;i++)if(daa_addr[i]==a)known=1;
 endfunction
 task automatic start();
  if(stopped>0&&$time-stopped<TBUF)#(TBUF-($time-stopped));
  serviced=0;lost=0;sr_done=0;legacy=0;
  if(trace&&sda===1'b0)$display("CTRL START request from a target at %0t",$time);
  sda_low=1;sda_high=0;#(T_CAS);scl_out=0;active=1;
 endtask
 // Repeated START. A read abort (T-bit) has already made one: then there is nothing to do.
 task automatic restart(input bit od);
  if(sr_done)sr_done=0;
  else begin
   #(HOLD);if(od)set_sda(REL,1);else set_sda(DRV_PP,1);#((od?OD_LOW:PP_LOW)-HOLD);
   scl_out=1;#(T_CAS/2);sda_low=1;sda_high=0;#(T_CAS);scl_out=0;
  end
 endtask
 task automatic stop();
  sr_done=0;#(legacy?FM_HOLD:HOLD);sda_low=1;sda_high=0;#(legacy?FM_LOW:PP_LOW);
  scl_out=1;#(legacy?FM_SETUP:T_CAS);sda_low=0;sda_high=0;stopped=$time;active=0;legacy=0;#1;
 endtask
 // Read one SDR word: eight data bits and the target's T-bit (§5.1.2.3.4). T = 0 ends the read: the
 // controller overlaps it low after the rise. T = 1 offers more: continue (more = 1) or abort with Sr.
 task automatic read_word(output logic [7:0] d,output logic t,input bit more);
  logic s;
  for(int b=7;b>=0;b--)begin clock(PP,REL,1,0,s);d[b]=s;end
  #(HOLD);set_sda(REL,1);#(PP_LOW-HOLD);scl_out=1;#1;t=sda;
  if(t===1'b0)begin sda_low=1;#(PP_HIGH-1);scl_out=0;end
  else if(more)begin #(PP_HIGH-1);scl_out=0;end
  else begin #9;sda_low=1;#(T_CAS);scl_out=0;sr_done=1;end
 endtask
 // Write one SDR word: eight data bits and the odd-parity T-bit, XOR(data, 1) (§5.1.2.3.3).
 task automatic write_word(input [7:0] d);
  logic s;reg [7:0] w;w=d^flip_mask;flip_mask=0;
  for(int b=7;b>=0;b--)clock(od_always?OD:PP,od_always?DRV_OD:DRV_PP,w[b],0,s);
  clock(od_always?OD:PP,od_always?DRV_OD:DRV_PP,~^d,0,s);
 endtask
 // Address header. arb = 1 after S: open drain, and a target may win it (§5.1.2.2.1). arb = 0 after Sr:
 // push-pull, never arbitrated (§5.1.2.2.4). The ACK slot is always open drain.
 task automatic header(input [6:0] addr,input bit rw,input bit arb,output bit ack);
  reg [7:0] v,seen;logic s;v={addr,rw};lost=0;ack=0;
  for(int b=7;b>=0;b--)begin
   if(arb)begin clock(OD,lost?REL:DRV_OD,v[b],0,s);if(!lost&&v[b]&&s===1'b0)lost=1;end
   else clock(PP,DRV_PP,v[b],0,s);
   seen[b]=(s===1'b1);
  end
  if(!lost)begin
   ext_low=stall_ack_ns;clock(OD,(pp_too_early&&!arb)?DRV_PP:REL,1,rw?0:1,s);ack=(s===1'b0);
  end else begin
   // A target won the header: an In-Band Interrupt (its address, R), a Hot-Join (7'h02, W) or a
   // controller-role request (its address, W) (§5.1.2.2).
   req_addr=seen[7:1];req_rw=seen[0];serviced=1;
   if(req_rw&&ibi_accept&&known(req_addr))begin
    clock(OD,DRV_OD,0,(bcr_of(req_addr)&8'h04)!=0 ? 2 : 0,s);ack=1;ibis++;
    if(bcr_of(req_addr)&8'h04)begin read_word(mdb,tbit,0);if(trace)$display("CTRL IBI from %02h accepted, MDB %02h T%0d",req_addr,mdb,tbit);end
    else if(trace)$display("CTRL IBI from %02h accepted, no MDB (BCR[2] = 0)",req_addr);
   end else if(!req_rw&&req_addr!=7'h02&&mr_accept&&known(req_addr)&&(bcr_of(req_addr)>>6)==2'b01)begin
    // A controller-role request from a secondary controller (BCR[7:6] = 01): ACK, then GETACCMST (§5.1.6.3).
    clock(OD,DRV_OD,0,0,s);ack=1;mr_seen=1;mr_addr=req_addr;if(trace)$display("CTRL controller-role request from %02h accepted",req_addr);
   end else if(!req_rw&&req_addr==7'h02&&hj_accept)begin
    clock(OD,DRV_OD,0,0,s);ack=1;hj_seen=1;if(trace)$display("CTRL Hot-Join request accepted");
   end else begin
    clock(OD,REL,1,0,s);if(trace)$display("CTRL request from %02h %s refused (NACK)",req_addr,req_rw?"R":"W");
   end
  end
 endtask
 // S 7'h7E/W, arbitrable. A request that wins is served first; then 7'h7E/W is sent again after Sr.
 task automatic open_frame(output bit ack);
  start();header(7'h7e,0,1,ack);
  if(serviced)begin restart(0);header(7'h7e,0,0,ack);end
 endtask
 // Broadcast CCC (codes 0x00 to 0x7F): S 7'h7E/W, code, optional byte, P (§5.1.9.1, Figure 21).
 task automatic ccc_broadcast(input [7:0] code,input bit has_data,input [7:0] data);
  bit ack;open_frame(ack);if(ack)begin write_word(code);if(has_data)write_word(data);end stop();
 endtask
 // Direct CCC (codes 0x80 to 0xFE): S 7'h7E/W, code, Sr, address/W, optional byte, P (Figure 22).
 // directed_as_broadcast: sends the broadcast form instead (code with bit 7 clear, the byte, P).
 task automatic ccc_direct(input [7:0] code,input [6:0] addr,input bit has_data,input [7:0] data,output bit ack);
  bit a;open_frame(a);
  if(directed_as_broadcast)begin write_word(code&8'h7f);if(has_data)write_word(data);ack=a;end
  else begin write_word(code);restart(0);header(addr,0,0,ack);if(ack&&has_data)write_word(data);end
  stop();
 endtask
 // Direct GET CCC: S 7'h7E/W, code, Sr, address/R, n bytes into rbuf (the target ends with T = 0).
 task automatic ccc_get(input [7:0] code,input [6:0] addr,input int n,output bit ack);
  bit a;logic t;logic [7:0] rb;open_frame(a);write_word(code);restart(0);header(addr,1,0,ack);rcount=0;
  if(ack)begin t=1;for(int i=0;i<n&&t!==1'b0;i++)begin read_word(rb,t,i<n-1);rbuf[i]=rb;rcount++;end end
  stop();
 endtask
 // Private transfers in the form that starts with 7'h7E (MIPI v1.0 Figure 47): S 7'h7E/W, Sr, address.
 task automatic write_regs(input [6:0] da,input [7:0] ptr,input int n,output bit ack);
  bit a;open_frame(a);restart(0);header(da,0,0,ack);
  if(ack)begin write_word(ptr);for(int i=0;i<n;i++)write_word(wbuf[i]);end
  stop();
 endtask
 task automatic write_reg(input [6:0] da,input [7:0] ptr,input [7:0] d,output bit ack);wbuf[0]=d;write_regs(da,ptr,1,ack);endtask
 // Pointer write, Sr, then read until the target ends (T = 0) or n bytes are in rbuf.
 task automatic read_regs(input [6:0] da,input [7:0] ptr,input int n,output bit ack);
  bit a;logic t;logic [7:0] rb;open_frame(a);restart(0);header(da,0,0,ack);rcount=0;
  if(ack)begin write_word(ptr);restart(0);header(da,1,0,ack);
   if(ack)begin t=1;for(int i=0;i<n&&t!==1'b0;i++)begin read_word(rb,t,i<n-1);rbuf[i]=rb;rcount++;end end
  end
  stop();
 endtask
 task automatic read_reg(input [6:0] da,input [7:0] ptr,output logic [7:0] v);bit ack;rbuf[0]=8'hxx;read_regs(da,ptr,1,ack);v=rbuf[0];endtask
 // Legacy I2C message to a static address at Fm timing (MIPI v1.0 Table 57): open drain, ninth bit = ACK.
 task automatic i2c_start();
  if(stopped>0&&$time-stopped<TBUF)#(TBUF-($time-stopped));
  serviced=0;lost=0;sr_done=0;legacy=1;sda_low=1;sda_high=0;#(FM_SETUP);scl_out=0;active=1;
 endtask
 task automatic i2c_byte(input [7:0] v,output bit ack);logic s;
  for(int b=7;b>=0;b--)clock(FM,DRV_OD,v[b],0,s);clock(FM,REL,1,0,s);ack=(s===1'b0);
 endtask
 task automatic i2c_write(input [6:0] a,input [7:0] ptr,input [7:0] d,output bit ack);
  bit k;i2c_start();i2c_byte({a,1'b0},ack);if(ack)begin i2c_byte(ptr,k);i2c_byte(d,k);end stop();
 endtask
 task automatic i2c_read(input [6:0] a,input [7:0] ptr,output logic [7:0] v);
  bit ack;logic s;i2c_start();i2c_byte({a,1'b0},ack);i2c_byte(ptr,ack);
  #(FM_HOLD);set_sda(REL,1);#(FM_LOW-FM_HOLD);scl_out=1;#(FM_SETUP);sda_low=1;#(FM_SETUP);scl_out=0;
  i2c_byte({a,1'b1},ack);for(int b=7;b>=0;b--)begin clock(FM,REL,1,0,s);v[b]=s;end clock(FM,REL,1,0,s);stop();
 endtask
 // ENTDAA (§5.1.4.2, Figure 19): broadcast 0x07, then rounds of Sr 7'h7E/R (open drain), 64 arbitrated bits
 // (provisional ID, BCR, DCR) from the targets, a 7-bit address plus odd parity from the controller and the
 // winner's ACK. A NACKed address is offered once more; a second NACK ends DAA (§5.1.4.2 notes).
 task automatic entdaa();
  bit ack;open_frame(ack);if(ack)write_word(8'h07);daa_rounds();
 endtask
 task automatic daa_rounds();
  bit ack,a2,retry,done;logic s;reg [63:0] id;reg [7:0] w;
  retry=0;done=0;
  while(!done)begin
   restart(1);header(7'h7e,1,1,ack);
   if(!ack)done=1;
   else begin
    for(int k=63;k>=0;k--)begin clock(OD,REL,1,0,s);id[k]=(s===1'b1);end
    w={daa_next,~^daa_next};
    for(int k=7;k>=0;k--)clock(OD,DRV_OD,w[k],0,s);
    clock(OD,REL,1,0,s);a2=(s===1'b0);
    if(a2)begin
     daa_addr[daa_count]=daa_next;daa_pid[daa_count]=id[63:16];daa_bcr[daa_count]=id[15:8];daa_dcr[daa_count]=id[7:0];
     if(trace)$display("CTRL DAA PID %012h BCR %02h DCR %02h -> %02h",id[63:16],id[15:8],id[7:0],daa_next);
     daa_count++;daa_next=next_free(daa_next);retry=0;
    end else if(!retry)begin retry=1;if(trace)$display("CTRL DAA %02h NACKed, offering it again",daa_next);end
    else begin daa_error=1;done=1;if(trace)$display("CTRL DAA %02h NACKed twice: DAA ends with an error",daa_next);end
   end
  end
  stop();
  if(daa_expect&&daa_count<daa_expect&&trace)$display("CTRL DAA assigned %0d of %0d expected addresses",daa_count,daa_expect);
 endtask
 // Wait for a target's START request on a free bus, then clock it out (§5.1.2.2 condition 2, §5.1.6.2).
 // A Hot-Join is answered with ENTDAA in the same frame (§5.1.5 step 4c).
 task automatic serve_request(input time limit);
  time t0;bit ack;t0=$time;
  while(sda!==1'b0&&$time-t0<limit)#10;
  if(sda===1'b0)begin
   start();header(7'h7e,0,1,ack);
   if(hj_seen)begin hj_seen=0;restart(0);header(7'h7e,0,0,ack);if(ack)write_word(8'h07);daa_rounds();end
   else if(mr_seen)begin mr_seen=0;getaccmst(mr_addr,1,ack);end
   else stop();
  end else if(trace)$display("CTRL no request within %0t ns",limit);
 endtask
 // GETACCMST (0x91, §5.1.9.3.16): a direct GET to a secondary controller, after its request (in the same
 // frame, after Sr) or as an offer. An accepted reply is its own address with a parity bit (the bench uses the
 // odd parity of §5.1.4.2); then STOP, and this controller releases SCL after tMMoverlap (§5.1.6.3, Figure 44).
 // A NACK or a wrong address cancels the handoff (Tables 43, 44).
 task automatic getaccmst(input [6:0] a,input bit in_frame,output bit ok);
  bit ack;logic [7:0] b;logic t;b=8'hxx;
  if(in_frame)begin restart(0);header(7'h7e,0,0,ack);end else open_frame(ack);
  write_word(8'h91);restart(0);header(a,1,0,ack);
  if(ack)read_word(b,t,0);
  ok=ack&&(b==={a,~^a});
  stop();
  if(ok)begin #(T_MMOVERLAP);scl_en=0;handed_over=1;end
  if(trace)begin
   if(!ack)$display("CTRL GETACCMST %02h NACKed: mastership kept",a);
   else if(ok)$display("CTRL GETACCMST %02h replied %02h: SCL released",a,b);
   else $display("CTRL GETACCMST %02h replied %02h, not %02h: mastership kept",a,b,{a,~^a});
  end
 endtask
 // DEFSLVS (0x08, §5.1.9.3.7, Table 30): a count, four bytes for this controller (its address, DCR, BCR, static
 // 7'h7E), then four per target (address<<1, DCR, BCR, static<<1); a legacy target has address 0 and its LVR.
 task automatic defslvs();
  bit ack;open_frame(ack);write_word(8'h08);write_word(daa_count+(leg_addr!=0));
  write_word({own_da,1'b0});write_word(8'h00);write_word(8'h40);write_word({7'h7e,1'b0});
  for(int i=0;i<daa_count;i++)begin write_word({daa_addr[i],1'b0});write_word(daa_dcr[i]);write_word(daa_bcr[i]);write_word(8'h00);end
  if(leg_addr!=0)begin write_word(8'h00);write_word(leg_lvr);write_word(8'h00);write_word({leg_addr,1'b0});end
  stop();
 endtask
 // Learn a target's BCR with GETBCR, for a target given its address by SETAASA or SETDASA (no DAA record).
 task automatic learn(input [6:0] a);bit ack;ccc_get(8'h8e,a,1,ack);
  if(ack)begin daa_addr[daa_count]=a;daa_bcr[daa_count]=rbuf[0];daa_pid[daa_count]=0;daa_dcr[daa_count]=0;daa_count++;end
 endtask
 // ENTHDRx (codes 0x20 to 0x27, Table 15). Basic v1.0 has no HDR mode, so nothing is sent in HDR.
 task automatic hdr_enter(input [2:0] mode);bit ack;open_frame(ack);write_word(8'h20|mode);endtask
 // HDR Exit Pattern (§5.2.1.1, Figure 25): SCL low, SDA falls four times at least tDIG_H apart, then SCL
 // rises and SDA rises: a STOP.
 task automatic hdr_exit();
  #(HOLD);sda_low=0;sda_high=1;#(PP_LOW);
  repeat(3)begin sda_low=1;sda_high=0;#(PP_HIGH);sda_low=0;sda_high=1;#(PP_HIGH);end
  sda_low=1;sda_high=0;#(PP_HIGH);scl_out=1;#(T_CAS);sda_low=0;sda_high=0;stopped=$time;active=0;#1;
 endtask
 // HDR Restart Pattern (§5.2.1.2, Figure 26): SCL low, SDA high, then fall, rise, fall, rise at least tDIG_H
 // apart; the next edge is SCL rising. The bus stays in HDR: a new HDR command would follow.
 task automatic hdr_restart();
  #(HOLD);sda_low=0;sda_high=1;#(PP_LOW);
  repeat(2)begin sda_low=1;sda_high=0;#(PP_HIGH);sda_low=0;sda_high=1;#(PP_HIGH);end
  scl_out=1;#(PP_HIGH);scl_out=0;
 endtask
 // A stand-in for HDR traffic: no HDR coding, only the property of §5.2.2's introduction that SDA may change
 // after either SCL edge. The 16 bits of w go out one per SCL edge, so SDA also changes while SCL is high.
 task automatic hdr_burst(input [15:0] w);
  for(int b=15;b>=0;b--)begin #(HOLD);sda_low=!w[b];sda_high=w[b];#(PP_LOW-HOLD);scl_out=!scl_out;end
  if(scl_out)begin #(PP_LOW);scl_out=0;end
 endtask
 // Error Type M2 recovery: S 7'h7E/W, ACK or NACK ignored, HDR Exit Pattern, STOP (§5.1.10.2.3, §5.1.10.2.4).
 task automatic recover();bit ack;start();header(7'h7e,0,1,ack);hdr_exit();endtask
endmodule
Read included file: i3c_target.sv
`timescale 1ns/1ns
// I3C target (v1.0: Slave), SDR. A register map like the P8 target: mem[i] = i ^ MEM_XOR; a private write's
// first byte is the pointer. It answers 7'h7E and its dynamic address only (§5.1.2.1), takes part in ENTDAA
// while it has no address, raises In-Band Interrupts with a mandatory byte (BCR[2]) and recognises the HDR
// Exit Pattern. HOT_JOIN targets stay silent until hot_join() is called (§5.1.5).
module i3c_target #(parameter [47:0] PID=48'h0,parameter [7:0] BCR=8'h06,DCR=8'h00,parameter [6:0] STATIC_ADDR=7'h00,
  parameter [7:0] MEM_XOR=8'h00,parameter HOT_JOIN=0,parameter time T_AVAL=1000,T_IDLE=200000)
 (inout wire scl,sda,output reg sda_low=0,sda_high=0);
 // Fault switches.
 bit pid_tie_bug=0,addr_parity_wrong=0,ibi_without_mdb=0,misses_hdr_exit=0,t_bit_wrong=0,pp_too_early=0,ibi_loses_and_retries_wrong=0,restart_as_exit=0,accmst_wrong_addr=0;
 // A secondary controller's target half (BCR[7:6] = 01): controller-role request, DEFSLVS list, GETACCMST.
 bit mr_req=0,mr_en=1,accept_mastership=1,accmst_sent=0,became_current=0,my_rw=1;reg [7:0] defs[0:39];int ndefs=0;
 // Activity state (ENTASx, §5.1.3.5) and the protocol-error flag GETSTATUS reports (Table 41).
 reg [1:0] activity=0;bit perr=0;
 // Counters: SDR STARTs acted on, HDR Restart patterns seen while in HDR.
 int sdr_starts=0,hdr_restarts=0;bit poss_restart=0;time requested=0,req_gap=0;   // when, and how long after the bus went free, it last pulled SDA
 // Configuration and state. max_read: bytes returned per private read before T = 0.
 int max_read=1;reg [7:0] mdb=8'ha5;
 reg [6:0] da=0;bit has_da=0,ibi_en=1,hj_en=1,powered=!HOT_JOIN,ibi_req=0,hj_req=0;
 reg [7:0] mem[0:255];reg [7:0] ptr=0;
 int st=0,cnt=0,sent=0,errors=0;reg [8:0] sh=0;reg [63:0] daa_word;reg [7:0] tx;reg [7:0] code=0;
 bit active=0,after_s=0,arb=0,arb_lost=0,lost_this_frame=0,daa_mode=0,direct=0,code_next=0,need_ptr=1,ccc_target=0,self_start=0;
 bit hdr_mode=0,err_exit=0,write_ack_release=0,reading=0,daa_won=0,t_ack=0;int falls=0;reg [6:0] my_hdr;time free_since=0;
 bit was_ibi=0,ibi_more=0,ack_pending=0,daa_hdr=0;
 // st: 0 idle, 1 header, 2 header ACK, 3 write word, 4 read word, 5 ignore until Sr/P, 6 DAA ID, 7 DAA address, 8 DAA ACK
 localparam IDLE=0,HDR=1,HACK=2,WR=3,RD=4,IGN=5,DID=6,DAD=7,DACK=8;
 assign sda=sda_low?1'b0:sda_high?1'b1:1'bz;
 initial for(int i=0;i<256;i++)mem[i]=i[7:0]^MEM_XOR;
 task automatic release_sda();sda_low=0;sda_high=0;endtask
 task automatic raise_ibi();ibi_req=1;endtask
 task automatic request_mastership();mr_req=1;endtask
 task automatic hot_join();powered=1;hj_req=1;free_since=$time;endtask
 function automatic bit bad_addr(input [6:0] a);bad_addr=a==7'h3e||a==7'h5e||a==7'h6e||a==7'h76||a==7'h7a||a==7'h7c||a==7'h7f;endfunction
 // START or Repeated START.
 always @(negedge sda)if(scl===1'b1&&powered&&!hdr_mode&&!err_exit)begin
  after_s=!active;sdr_starts++;
  if(!active)begin daa_mode=0;direct=0;lost_this_frame=0;end
  active=1;st=HDR;cnt=0;sh=0;code_next=0;ccc_target=0;reading=0;was_ibi=0;
  // A request rides only on the header after S (§5.1.2.2); ibi_loses_and_retries_wrong also tries after Sr.
  arb=0;arb_lost=0;
  if(has_da&&ibi_req&&ibi_en&&(after_s||(ibi_loses_and_retries_wrong&&lost_this_frame)))begin arb=1;my_hdr=da;my_rw=1;end
  else if(has_da&&mr_req&&mr_en&&after_s)begin arb=1;my_hdr=da;my_rw=0;end
  else if(hj_req&&hj_en&&after_s)begin arb=1;my_hdr=7'h02;my_rw=0;end
  if(accmst_sent&&!after_s)accmst_sent=0;     // Sr after GETACCMST cancels it (§5.1.9.3.16 step 4)
 end
 // STOP.
 always @(posedge sda)if(scl===1'b1&&powered&&!hdr_mode&&!err_exit&&active)begin
  if(accmst_sent)begin accmst_sent=0;became_current=1;end
  active=0;st=IDLE;daa_mode=0;direct=0;release_sda();free_since=$time;self_start=0;
 end
 // HDR Exit Pattern detector (§5.2.1.3, Figure 27): SDA falls counted while SCL stays low; SCL high resets.
 always @(negedge sda)if((hdr_mode||err_exit)&&scl===1'b0)begin falls++;
  if(falls==4&&!misses_hdr_exit)begin hdr_mode=0;err_exit=0;active=1;st=IGN;falls=0;end
 end
 // Restart (§5.2.1.4): exactly two falls, then an SDA rise, then SCL rises. A Basic target supports no HDR mode,
 // so it notes the Restart and stays in HDR until the Exit; restart_as_exit leaves HDR on it (a fault).
 always @(posedge sda)if((hdr_mode||err_exit)&&scl===1'b0)poss_restart=(falls==2);
 always @(posedge scl)if(hdr_mode||err_exit)begin
  if(poss_restart&&hdr_mode)begin hdr_restarts++;if(restart_as_exit)begin hdr_mode=0;active=1;st=IGN;end end
  poss_restart=0;falls=0;
 end
 // Bus Available (tAVAL) or Bus Idle (tIDLE for Hot-Join): pull SDA low to request a START (§5.1.3, §5.1.5).
 always #10 if(powered&&!active&&!self_start&&scl===1'b1&&sda===1'b1&&!hdr_mode&&!err_exit)begin
  if(has_da&&(ibi_req&&ibi_en||mr_req&&mr_en)&&$time-free_since>=T_AVAL)begin self_start=1;sda_low=1;requested=$time;req_gap=$time-free_since;end
  else if(hj_req&&hj_en&&$time-free_since>=T_IDLE)begin self_start=1;sda_low=1;end
 end
 // Sample on the rising edge.
 always @(posedge scl)if(powered&&active&&!hdr_mode&&!err_exit)begin
  case(st)
  HDR:begin
   sh={sh[7:0],sda};cnt++;
   if(arb&&!arb_lost&&my_bit(cnt-1)&&sda===1'b0)begin arb_lost=1;lost_this_frame=1;end
   if(cnt==8)decode_header();
  end
  HACK:begin
   if(arb&&!arb_lost)begin
    // My request won; the controller ACKs or NACKs it.
    if(sda===1'b0)begin
     if(my_hdr==7'h02)begin hj_req=0;st=IGN;end
     else if(!my_rw)begin mr_req=0;st=IGN;end
     else begin ibi_req=0;was_ibi=1;
      if(BCR[2]&&!ibi_without_mdb)begin #2;sda_low=1;sda_high=0;tx=mdb;st=RD;cnt=0;end else st=IGN;
     end
    end else st=IGN;
    self_start=0;
   end else if(daa_hdr)begin daa_hdr=0;st=DID;cnt=0;daa_word={PID,BCR,DCR};end
   else if(write_ack_release)begin #2;release_sda();write_ack_release=0;end
  end
  WR:begin
   sh={sh[7:0],sda};cnt++;
   if(cnt==9)begin cnt=0;
    if(sh[0]!==~^sh[8:1])begin errors++;
     perr=1;if(code_next)begin err_exit=1;falls=0;st=IGN;end else st=IGN;   // S1: wait for the HDR Exit Pattern; S2: wait for Sr/P
    end else if(code_next)begin code_next=0;do_ccc(sh[8:1]);end
    else if(ccc_target)begin ccc_data(sh[8:1]);end
    else if(need_ptr)begin ptr=sh[8:1];need_ptr=0;end
    else begin mem[ptr]=sh[8:1];ptr=ptr+1;end
   end
  end
  RD:begin
   cnt++;
   if(cnt==9)begin cnt=0;#2;release_sda();
    if(ccc_target&&code==8'h91)accmst_sent=1;   // GETACCMST answered: mastership passes at the STOP
    if(!ibi_more)st=IGN;
   end
  end
  DID:begin
   if(!pid_tie_bug&&daa_word[63-cnt]&&sda===1'b0)begin st=IGN;end   // lost: withdraw until the next Sr
   cnt++;if(cnt==64&&st==DID)begin st=DAD;cnt=0;sh=0;end
  end
  DAD:begin sh={sh[7:0],sda};cnt++;if(cnt==8)begin st=DACK;daa_won=((^sh[7:0])==1'b1)^addr_parity_wrong;end end
  DACK:begin if(daa_won)begin da=sh[7:1];has_da=1;end st=IGN;end
  endcase
 end
 function automatic bit my_bit(input int i);reg [7:0] v;v={my_hdr,my_rw};my_bit=v[7-i];endfunction
 task automatic decode_header();
  reg [6:0] a;bit rw;a=sh[7:1];rw=sh[0];cnt=0;
  if(arb&&!arb_lost)begin st=HACK;end
  else if(a==7'h7e&&!rw)begin st=HACK;sda_ack(1);code_next=1;need_ptr=1;end
  else if(a==7'h7e&&rw&&daa_mode&&!has_da&&!hj_req)begin st=HACK;sda_ack(1);daa_hdr=1;end   // then 64 bits (§5.1.4.2 steps 4 to 8)
  else if(a==7'h7e&&rw&&!daa_mode)begin errors++;err_exit=1;falls=0;st=IGN;end    // S0: 7'h7E/R outside DAA
  else if(daa_mode)begin st=IGN;end                                                  // S4 or already assigned
  else if(direct&&((has_da&&a==da)||(code==8'h87&&STATIC_ADDR!=0&&a==STATIC_ADDR)))begin st=HACK;sda_ack(1);ccc_target=1;reading=rw;
   if(code==8'h86)begin has_da=0;da=0;end
   if(code>=8'h82&&code<=8'h85)activity=code-8'h82;
   if(code==8'h91&&(BCR[7:6]!=2'b01||!accept_mastership))begin ack_pending=0;st=IGN;ccc_target=0;end end
  else if(has_da&&a==da)begin st=HACK;sda_ack(1);reading=rw;need_ptr=!rw;end
  else if(bad_addr(a)&&!rw)begin errors++;err_exit=1;falls=0;st=IGN;end              // S0: broken 7'h7E/W
  else st=IGN;
 endtask
 task automatic sda_ack(input bit a);ack_pending=a;endtask
 // Drive 6 ns after the falling edge, one after the controller's 5 ns hold, so no handoff overlaps a 1 and a 0.
 always @(negedge scl)if(powered&&active&&!hdr_mode&&!err_exit)begin
  #6;
  case(st)
  HDR:begin
   if(arb&&!arb_lost)begin
    if(pp_too_early)begin sda_low=!my_bit(cnt);sda_high=my_bit(cnt);end else begin sda_low=!my_bit(cnt);sda_high=0;end
   end else if(arb&&arb_lost)release_sda();
  end
  HACK:begin
   if(arb&&!arb_lost)release_sda();
   else if(ack_pending)begin sda_low=1;sda_high=0;ack_pending=0;
    // Write: release just after the rise (handoff, §5.1.2.3.1). Read: keep low, then send data.
    if(!reading)write_ack_release=1;
   end else if(reading)begin
    st=RD;cnt=0;ibi_more=0;
    if(ccc_target)begin tx=ccc_reply(0);end else begin tx=mem[ptr];ptr=ptr+1;end
    sent=0;drive_bit(tx[7]);
   end else begin
    st=WR;cnt=0;sh=0;
   end
  end
  WR:begin
   release_sda();
   if(cnt==8&&t_bit_wrong&&!code_next&&!ccc_target)sda_low=1;   // the I2C habit: ACK the ninth bit, hold it until SCL falls
  end
  RD:begin
   if(cnt<8)drive_bit(tx[7-cnt]);
   else begin
    // T-bit: 1 = more data (continue), 0 = end of data.
    sent++;ibi_more=was_ibi?0:(ccc_target?ccc_more(sent):sent<max_read);
    sda_low=!ibi_more;sda_high=ibi_more;
   end
  end
  DID:begin sda_low=!daa_word[63-cnt];sda_high=0;end
  DAD:release_sda();
  DACK:begin sda_low=daa_won;sda_high=0;end
  IGN:release_sda();
  endcase
 end
 task automatic drive_bit(input bit v);sda_low=!v;sda_high=v;endtask
 // After a T = 1 the controller either lets the next byte come or pulls SDA low (Sr = abort).
 always @(negedge scl)if(powered&&active&&st==RD&&cnt==0&&ibi_more)begin
  if(sda===1'b0)st=IGN;
  else begin if(ccc_target)tx=ccc_reply(sent);else begin tx=mem[ptr];ptr=ptr+1;end end
 end
 task automatic do_ccc(input [7:0] c);
  code=c;direct=c[7];
  if(c==8'h07)daa_mode=1;
  else if(c==8'h06)begin has_da=0;da=0;end
  else if(c==8'h29&&STATIC_ADDR!=0&&!has_da)begin da=STATIC_ADDR;has_da=1;end
  else if(c>=8'h20&&c<=8'h27)begin hdr_mode=1;falls=0;active=0;st=IDLE;end
  else if(c==8'h00||c==8'h01)begin ccc_target=1;end     // event byte follows
  else if(c>=8'h02&&c<=8'h05)activity=c-8'h02;
  else if(c==8'h08&&BCR[7:6]==2'b01)begin ccc_target=1;ndefs=0;end   // DEFSLVS: only secondary controllers keep it
 endtask
 task automatic ccc_data(input [7:0] d);
  if(code==8'h00||code==8'h80)begin if(d[0])ibi_en=1;if(d[1])mr_en=1;if(d[3])hj_en=1;end
  else if(code==8'h01||code==8'h81)begin if(d[0])ibi_en=0;if(d[1])mr_en=0;if(d[3])hj_en=0;end
  else if(code==8'h08)begin if(ndefs<40)defs[ndefs]=d;ndefs++;end
  else if(code==8'h87||code==8'h88)begin da=d[7:1];has_da=1;end
 endtask
 function automatic [7:0] ccc_reply(input int i);
  case(code)
   8'h8d:ccc_reply=PID>>(8*(5-i));
   8'h8e:ccc_reply=BCR;
   8'h8f:ccc_reply=DCR;
   8'h90:begin ccc_reply=i==0?8'h00:{activity,perr,5'b0};if(i==1)perr=0;end   // GETSTATUS MSB, LSB (Table 41)
   8'h91:ccc_reply={da^{6'b0,accmst_wrong_addr},~^da};
   default:ccc_reply=8'hff;
  endcase
 endfunction
 function automatic bit ccc_more(input int n);ccc_more=(code==8'h8d)?n<6:(code==8'h90)?n<2:0;endfunction
endmodule
Read included file: i3c_hdr_detect.sv
`timescale 1ns/1ns
module i3c_hdr_detect(input wire scl,sda,in_hdr);
 bit misses_restart=0,restart_as_exit=0;int restarts=0,exits=0,falls=0;bit poss=0;
 always @(negedge sda)if(in_hdr&&scl===1'b0)begin falls++;if(falls==4)exits++;end
 always @(posedge sda)if(in_hdr&&scl===1'b0)poss=(falls==2);
 always @(posedge scl)begin
  if(in_hdr&&poss&&!misses_restart)begin if(restart_as_exit)exits++;else restarts++;end
  poss=0;falls=0;
 end
endmodule
Read included file: i3c_secondary.sv
`timescale 1ns/1ns
module i3c_secondary #(parameter [47:0] PID=48'h0,parameter [7:0] BCR=8'h46,DCR=8'h00)
 (inout wire scl,sda,output wire tgt_low,tgt_high,ctl_low,ctl_high);
 bit takes_scl_early=0;
 i3c_target #(.PID(PID),.BCR(BCR),.DCR(DCR)) tgt(scl,sda,tgt_low,tgt_high);
 i3c_ctrl #(.START_AS_CONTROLLER(0)) ctl(scl,sda,ctl_low,ctl_high);
 always @(posedge tgt.accmst_sent)if(takes_scl_early)ctl.scl_en=1;
 always @(posedge tgt.became_current)begin
  #12;ctl.scl_en=1;ctl.stopped=$time;ctl.daa_count=0;ctl.own_da=tgt.da;
  for(int g=5;g+3<tgt.ndefs&&g+3<40;g+=4)if(tgt.defs[g]!=0&&tgt.defs[g][7:1]!=tgt.da)begin
   ctl.daa_addr[ctl.daa_count]=tgt.defs[g][7:1];ctl.daa_dcr[ctl.daa_count]=tgt.defs[g+1];ctl.daa_bcr[ctl.daa_count]=tgt.defs[g+2];ctl.daa_count++;
  end
 end
endmodule
Read included file: i2c_legacy.sv
`timescale 1ns/1ns
module i2c_legacy #(parameter [6:0] ADDRESS=7'h50,parameter time FILTER=50,STRETCH=3000)
 (inout wire scl,sda,output wire scl_low,sda_low);
 bit no_spike_filter=0,legacy_stretches=0;int starts=0,clocks=0,pulls=0;
 wire scl_f,sda_f;assign #(FILTER) scl_f=scl;assign #(FILTER) sda_f=sda;
 tri1 lscl,lsda;wire hd;
 assign lscl=((no_spike_filter?scl:scl_f)===1'b0)?1'b0:1'bz;
 assign lsda=((no_spike_filter?sda:sda_f)===1'b0)?1'b0:1'bz;
 i2c_target #(.ADDRESS({3'b000,ADDRESS})) dev(lscl,lsda,scl_low,sda_low,hd);
 assign scl=scl_low?1'b0:1'bz;assign sda=sda_low?1'b0:1'bz;
 always @(legacy_stretches)dev.stretch_ns=legacy_stretches?STRETCH:0;
 always @(negedge lsda)if(lscl===1'b1)starts++;
 always @(posedge lscl)clocks++;
 always @(posedge sda_low)pulls++;
endmodule
Read included file: i2c_target.sv
`timescale 1ns/1ns
module i2c_target #(parameter [9:0] ADDRESS=10'h050,parameter TEN_BIT=0)
 (inout wire scl,sda,output reg scl_low=0,sda_low=0,output wire high_drive);
 bit ack_not_released=0,ack_wrong_clock=0,stretch_never_released=0,wrong_addr_ack=0,no_stop_detect=0,autoinc_wrap_bug=0,push_pull_target=0,ignores_sr=0;
 // Legal receiver responses, not faults. rx_limit counts post-address bytes,
 // including the pointer byte; -1 is unlimited. A busy address is declined once.
 bit busy_nack=0;int rx_limit=-1,rx_count=0;
 // Optional general-call software reset: UM10204 §§3.1.13–14. This model
 // restores mem[i]=i and ptr=0; ADDRESS is fixed, not a programmable register.
 // Fault switches and bench configuration are not device reset state.
 time stretch_ns=0;bit general_call=1,software_reset=1,general_selected=0,reset_pending=0;int resets=0;
 reg [7:0] mem[0:255];reg [7:0] ptr=0,shift=0,tx=0;
 int state=0,count=0,bytes=0;bit active=0,selected=0,reading=0,need_ptr=1,ack_value=0,ack_active=0,low_addr_next=0,addressed10=0,reading_ack=0;
 // state: 0 receive, 1 receiver ACK, 2 transmit, 3 controller ACK, 4 ignore.
 assign high_drive=push_pull_target&&!sda_low;
 assign sda=sda_low?1'b0:high_drive?1'b1:1'bz;assign scl=scl_low?1'b0:1'bz;
 initial for(int i=0;i<256;i++)mem[i]=i[7:0];
 function automatic [7:0] increment(input reg [7:0] p);increment=autoinc_wrap_bug?((p+1)&8'h0f):p+1'b1;endfunction
 always @(negedge sda)if(scl===1)begin
  if(!(ignores_sr&&active))begin active=1;selected=0;state=0;count=0;bytes=0;rx_count=0;shift=0;reading=0;ack_active=0;sda_low=0;low_addr_next=0;general_selected=0;reset_pending=0;end
 end
 always @(posedge sda)if(scl===1&&active&&!no_stop_detect)begin active=0;selected=0;state=0;count=0;sda_low=0;addressed10=0;end
 always @(posedge scl)if(active)begin
  if(state==0)begin
   shift={shift[6:0],sda};count++;
   if(count==8)begin
    ack_value=0;
    if(bytes==0)begin
      reading=shift[0];
      general_selected=general_call&&shift==0;
      if(general_selected)begin selected=1;low_addr_next=0;end
      else if(TEN_BIT)begin selected=(shift[7:3]==5'b11110&&shift[2:1]==ADDRESS[9:8]&&(!reading||addressed10));low_addr_next=!reading;end
      else selected=(shift[7:1]==ADDRESS[6:0]||(wrong_addr_ack&&shift[7:1]==ADDRESS[6:0]+1'b1));
      if(selected&&busy_nack)begin selected=0;busy_nack=0;end
      ack_value=selected;need_ptr=1;
    end else if(low_addr_next)begin selected=selected&&shift==ADDRESS[7:0];ack_value=selected;addressed10=selected;low_addr_next=0;end
    else if(selected)begin
      ack_value=rx_limit<0||rx_count<rx_limit;
      if(general_selected)begin
       // Unsupported general-call commands are NACKed, never register writes.
       ack_value=ack_value&&bytes==1&&software_reset&&shift==8'h06;
       reset_pending=ack_value;
      end else if(ack_value)begin rx_count++;if(need_ptr)begin ptr=shift;need_ptr=0;end else begin mem[ptr]=shift;ptr=increment(ptr);end end
      if(!ack_value)selected=0;
    end
    bytes++;state=1;ack_active=0;count=0;
   end
  end else if(state==2)begin count++;if(count==8)begin state=3;count=0;reading_ack=0;end end
  else if(state==3&&reading_ack)begin if(sda===0)begin ptr=increment(ptr);tx=mem[ptr];state=2;count=0;end else state=4;end
 end
 always @(negedge scl)if(active)begin
  // Deliberate early-ACK fault: after seven samples, pull for clock 8.
  // The address prefix is already known; its R/W bit has not arrived yet.
  if(state==0&&count==7&&ack_wrong_clock)begin
   if(bytes==0)sda_low=!busy_nack&&((general_call&&shift[6:0]==0)||(TEN_BIT?(shift[6:2]==5'b11110&&shift[1:0]==ADDRESS[9:8]):(shift[6:0]==ADDRESS[6:0]||(wrong_addr_ack&&shift[6:0]==ADDRESS[6:0]+1'b1))));
   else sda_low=selected&&(rx_limit<0||rx_count<rx_limit);
  end else if(state==1)begin
   if(!ack_active)begin
    sda_low=ack_value&&!ack_wrong_clock;ack_active=1;
   end else begin
    sda_low=ack_not_released&&ack_value;ack_active=0;
    if(reset_pending)begin
     // Finish the ninth ACK clock before resetting and releasing the bus.
     for(int i=0;i<256;i++)mem[i]=i[7:0];
     ptr=0;shift=0;tx=0;count=0;bytes=0;rx_count=0;need_ptr=1;
     selected=0;reading=0;reading_ack=0;low_addr_next=0;addressed10=0;
     general_selected=0;reset_pending=0;resets++;sda_low=0;scl_low=0;state=4;
    end else if(!selected)state=4;
    else if(reading)begin state=2;tx=mem[ptr];count=0;sda_low=!tx[7];end
    else state=0;
    // Byte-level stretch after the ACK, as in UM10204 §3.1.9. Prepare
    // outgoing data before releasing SCL; overlap the controller's LOW.
    if(selected&&ack_value&&(stretch_ns>0||stretch_never_released))begin scl_low=1;if(!stretch_never_released)begin #(stretch_ns);scl_low=0;end end
   end
  end else if(state==2)sda_low=!tx[7-count];
  else if(state==3)begin sda_low=0;reading_ack=1;end
  else if(state==4)sda_low=0;
 end
endmodule
Read included file: i3c_mon.sv
`timescale 1ns/1ns
// Bus decoder: one line per frame, from SCL and SDA only. The ninth bit is an ACK after an address and a
// T-bit after a data word (§5.1.2.3); frames to the legacy address are decoded as I2C. phase (an output
// the checker reads): 0 idle, 1 header after S, 2 header after Sr, 3 ACK, 4 write word, 5 read word,
// 6 DAA ID, 7 DAA address, 8 DAA ACK, 9 HDR, 10 I2C byte, 11 I2C ACK, 12 waiting for Sr or P.
module i3c_mon #(parameter [6:0] LEGACY=7'h50)(input wire scl,sda,output reg [3:0] phase=0);
 bit trace=0,active=0,daa=0,hdr=0,legacy=0,code_next=0;int cnt=0,frames=0,starts=0,exits=0,falls=0;
 reg [63:0] sh=0;reg [6:0] addr=0;bit rw=0;string text="";
 always @(negedge sda)if(scl===1'b1&&!hdr)begin
  if(active)text={text," Sr"};else begin text=" S";daa=0;legacy=0;end
  phase=active?2:1;active=1;cnt=0;sh=0;starts++;code_next=0;
 end
 always @(posedge sda)if(scl===1'b1&&active&&!hdr)begin
  text={text," P"};if(trace)$display("%s%s",legacy?"I2C":"I3C",text);text="";active=0;phase=0;frames++;
 end
 // HDR Exit Pattern: four SDA falls while SCL stays low (§5.2.1.1). Seen in or out of HDR (M2 recovery).
 always @(negedge sda)if(scl===1'b0&&(hdr||active))begin falls++;if(falls==4)begin hdr=0;falls=0;text={text," EXIT"};exits++;phase=12;end end
 bit poss=0;int restarts=0;
 always @(posedge sda)if(scl===1'b0&&hdr)poss=(falls==2);
 always @(posedge scl)begin if(hdr&&poss)begin text={text," RESTART"};restarts++;end poss=0;falls=0;end
 always @(posedge scl)if(active&&!hdr)begin
  sh={sh[62:0],sda};cnt++;
  case(phase)
  1,2:if(cnt==8)begin addr=sh[7:1];rw=sh[0];cnt=0;
     if(phase==1&&addr==LEGACY)legacy=1;
     text={text,$sformatf(" %02h %s",addr,rw?"R":"W")};phase=legacy?11:3;end
  3:begin cnt=0;text={text,sda===1'b0 ? " A" : sda===1'b1 ? " NA" : " A=x"};
     if(sda!==1'b0)phase=12;
     else if(daa&&addr==7'h7e&&rw)phase=6;
     else if(!rw)begin phase=4;code_next=(addr==7'h7e);end
     else phase=5;
    end
  4,5:if(cnt==9)begin cnt=0;text={text,$sformatf(" %02h T%0d",sh[8:1],sh[0])};
     if(phase==4&&code_next)begin code_next=0;
      if(sh[8:1]==8'h07)daa=1;
      if(sh[8:1]>=8'h20&&sh[8:1]<=8'h27)begin hdr=1;falls=0;phase=9;text={text," HDR"};end
     end
     if(phase==5&&sh[0]===1'b0)phase=12;
    end
  6:if(cnt==64)begin cnt=0;text={text,$sformatf(" id %012h bcr %02h dcr %02h",sh[63:16],sh[15:8],sh[7:0])};phase=7;end
  7:if(cnt==8)begin cnt=0;text={text,$sformatf(" da %02h par %0d",sh[7:1],sh[0])};phase=8;end
  8:begin cnt=0;text={text,sda===1'b0 ? " A" : sda===1'b1 ? " NA" : " A=x"};phase=12;end
  10:if(cnt==8)begin cnt=0;text={text,$sformatf(" %02h",sh[7:0])};phase=11;end
  11:begin cnt=0;text={text,sda===1'b0 ? " A" : sda===1'b1 ? " NA" : " A=x"};phase=10;end
  default:cnt=0;
  endcase
 end
endmodule
Read included file: i3c_checker.sv
`timescale 1ns/1ns
// Wire rules with the driver view. Index 0 of lows/highs is the controller; the others are targets.
// Each rule prints at most once per frame (S to P) and counts every time it fires.
//  no_pp_contention   one device drives SDA high while another pulls it low (§5.1.2.3: handoffs are specified)
//  od_in_header       a device drives SDA high in the arbitrable header after S or its ACK (§5.1.2.2.1)
//  pp_in_data         the controller sends a 1 in a write word without driving it high (§5.1.2: data is push-pull)
//  t_bit              the ninth bit of a write word is not XOR(data, 1) (§5.1.2.3.3)
//  ibi_mdb_present    no target drives the byte after an accepted header after S with R (BCR[2] = 1, §5.1.6.2)
//  legacy_no_stretch  a legacy target pulls SCL low (MIPI v1.0 Table 56: no clock stretching on I3C)
module i3c_checker #(parameter N=4)(input wire scl,sda,input wire [N-1:0] lows,highs,input wire [3:0] phase,input wire legacy_scl_low);
 int broken=0,contention=0,od_header=0,pp_data=0,tbit=0,mdb=0,stretch=0;bit quiet=0;   // quiet: count, do not print
 reg [5:0] said=0;reg [3:0] ph=0,prev=0;reg [8:0] w=0;int k=0,hk=0,mk=0;bit arb_ack=0,hdr_rw=0,mdb_seen=0,mdb_watch=0,viol=0;
 task automatic bad(input int i,input string rule);broken++;if(!said[i]&&!quiet)$display("I3C CHECK %s at %0t",rule,$time);said[i]=1;endtask
 always @(negedge sda)if(scl===1'b1)begin k=0;viol=0;end
 always @(posedge sda)if(scl===1'b1)said=0;
 always @(negedge scl)begin prev=ph;ph=phase;if(ph!=prev)begin k=0;hk=0;end
  if(ph!=prev&&ph==3)arb_ack=(prev==1);
  if(ph==5&&prev==3&&arb_ack&&hdr_rw)begin mdb_watch=1;mdb_seen=0;mk=0;end
 end
 always @(lows or highs)begin #1;if(|lows&&|highs)begin contention++;bad(0,"no_pp_contention");end end
 always @(posedge scl)begin
  if((ph==1||(ph==3&&arb_ack))&&|highs)begin od_header++;bad(1,"od_in_header");end
  if(ph==1)begin hk++;if(hk==8)hdr_rw=(sda===1'b1);end
  if(ph==4)begin k++;w={w[7:0],sda};
   if(k<=8&&sda===1'b1&&!highs[0])viol=1;
   if(k==9)begin if(viol)begin pp_data++;bad(2,"pp_in_data");end if(w[0]!==~^w[8:1])begin tbit++;bad(3,"t_bit");end k=0;viol=0;end
  end
  if(mdb_watch)begin mk++;if(|lows[N-1:1]|||highs[N-1:1])mdb_seen=1;
   if(mk==8)begin mdb_watch=0;if(!mdb_seen)begin mdb++;bad(4,"ibi_mdb_present");end end
  end
 end
 always @(posedge legacy_scl_low)begin stretch++;bad(5,"legacy_no_stretch");end
 // A missing mandatory byte turns the controller's handoff release into a STOP inside the ACK slot.
 always @(posedge sda)if(scl===1'b1&&ph==3&&arb_ack&&hdr_rw)begin mdb++;bad(4,"ibi_mdb_present");end
endmodule

Check Yourself

Predict the outputThree targets on a pure bus get instance IDs 2, 0 and 1 (target1, target2, target3). After ctrl.entdaa(), the program prints target1.da. What does it print?
`timescale 1ns/1ns
module tb;
  tri1 scl, sda;                     // tri1: the pull-up and high-keeper of MIPI v1.0 §5.1.3.1
  wire c_low, c_high;                // driver view: the controller pulls SDA low / drives it high
  wire t1_low, t1_high, t2_low, t2_high, t3_low, t3_high;   // the same for each I3C target
  wire l_low = 0, l_scl = 0;         // no legacy target: a pure I3C bus
  wire [3:0] phase;                  // the decoder's frame phase, read by the checker
  bit ack; logic [7:0] value;
  i3c_ctrl    #(.TBUF(40)) ctrl(scl, sda, c_low, c_high);
  i3c_target  #(.PID(48'h02aa01232000)) target1(scl, sda, t1_low, t1_high);
  i3c_target  #(.PID(48'h02aa01230000)) target2(scl, sda, t2_low, t2_high);
  i3c_target  #(.PID(48'h02aa01231000)) target3(scl, sda, t3_low, t3_high);
  i3c_mon     mon(scl, sda, phase);                              // prints I3C S 7e W A ... P
  i3c_checker #(.N(4)) ck(scl, sda, {t3_low, t2_low, t1_low, c_low}, {t3_high, t2_high, t1_high, c_high}, phase, l_scl);   // prints I3C CHECK <rule>
  initial begin
    ctrl.daa_expect = 3;
    ck.quiet = 1;      // one printed line: the answer
    #2000;
    ctrl.entdaa();
    $display("%h", target1.da);
    #2000; $finish;
  end
  initial begin #3000000; $fatal(1, "I3C watchdog"); end
endmodule
Read included file: i3c_ctrl.sv
`timescale 1ns/1ns
// I3C controller (v1.0: Master), SDR. Task calls drive the pins; SCL is push-pull and driven by this device
// alone (§5.1.2). SDA is open drain in the arbitrable header after S, in every ACK slot and in DAA; push-pull
// in the header after Sr and in write data (§5.1.2.2, §5.1.2.3, §5.1.4.2).
module i3c_ctrl(inout wire scl,sda,output reg sda_low=0,sda_high=0);
 parameter time PP_LOW=40,PP_HIGH=40,OD_LOW=200,OD_HIGH=40,HOLD=5,T_CAS=40,TBUF=1300,
                FM_LOW=1300,FM_HIGH=1200,FM_HOLD=100,FM_SETUP=600,T_MMOVERLAP=220;
 // START_AS_CONTROLLER = 0: a secondary controller's controller half, SCL released until a handoff (§5.1.6.3).
 parameter START_AS_CONTROLLER=1;
 // Fault switches.
 bit pp_too_early=0,od_always=0,directed_as_broadcast=0;
 // Policy and an error injector: flip_mask flips bits of the next written word on the wire (noise);
 // the T-bit is still the parity of the intended byte.
 bit ibi_accept=1,hj_accept=1,trace=0;reg [7:0] flip_mask=0;
 // Mastership: accept requests from secondary controllers; this device's own dynamic address for DEFSLVS.
 // Clock stalling (§5.1.2.5.1): extra SCL low in the next header ACK slot, at most 100 µs (Table 11).
 bit mr_accept=1,mr_seen=0,handed_over=0,scl_en=START_AS_CONTROLLER;reg [6:0] mr_addr=0,own_da=7'h20;
 int stall_ack_ns=0;time ext_low=0;reg [6:0] leg_addr=0;reg [7:0] leg_lvr=0;
 // State.
 reg scl_out=1;bit active=0,sr_done=0,lost=0,serviced=0,hj_seen=0,daa_error=0,legacy=0;time stopped=0;
 reg [6:0] req_addr=0;bit req_rw=0;logic [7:0] mdb;logic tbit;
 int daa_count=0,daa_expect=0,ibis=0;reg [6:0] daa_next=7'h08;
 reg [6:0] daa_addr[0:7];reg [47:0] daa_pid[0:7];reg [7:0] daa_bcr[0:7],daa_dcr[0:7];
 reg [7:0] wbuf[0:7];logic [7:0] rbuf[0:7];int rcount=0;
 assign scl=scl_en?scl_out:1'bz;assign sda=sda_low?1'b0:sda_high?1'b1:1'bz;
 localparam PP=0,OD=1,FM=2,REL=0,DRV_OD=1,DRV_PP=2;
 task automatic set_sda(input int how,input logic v);
  if(how==REL)begin sda_low=0;sda_high=0;end else if(how==DRV_OD)begin sda_low=!v;sda_high=0;end else begin sda_low=!v;sda_high=v;end
 endtask
 // One SCL pulse; SCL is low on entry and on exit. post: 0 nothing; 1 pull SDA low just after the rise
 // when the sample is 0 (ACK to write data handoff, §5.1.2.3.1); 2 release 12 ns after the rise (IBI ACK
 // to mandatory byte handoff, §5.1.2.3.2).
 task automatic clock(input int speed,input int how,input logic v,input int post,output logic s);
  time lo,hi,hd;lo=speed==FM?FM_LOW:speed==OD?OD_LOW:PP_LOW;hi=speed==FM?FM_HIGH:speed==OD?OD_HIGH:PP_HIGH;hd=speed==FM?FM_HOLD:HOLD;
  #(hd);set_sda(how,v);#(lo-hd+ext_low);ext_low=0;scl_out=1;#1;s=sda;
  if(post==1&&s===1'b0)begin sda_low=1;sda_high=0;end
  if(post==2)begin #11;sda_low=0;#(hi-12);end else #(hi-1);
  scl_out=0;
 endtask
 // Next free dynamic address, skipping the reserved values of Table 9.
 function automatic [6:0] next_free(input [6:0] a);
  reg [6:0] n;n=a+1;while(n==7'h3e||n==7'h5e||n==7'h6e||n==7'h76||n==7'h7a||n==7'h7c||n>=7'h7e)n=n+1;next_free=n;
 endfunction
 function automatic [7:0] bcr_of(input [6:0] a);
  bcr_of=0;for(int i=0;i<daa_count;i++)if(daa_addr[i]==a)bcr_of=daa_bcr[i];
 endfunction
 function automatic bit known(input [6:0] a);
  known=0;for(int i=0;i<daa_count;i++)if(daa_addr[i]==a)known=1;
 endfunction
 task automatic start();
  if(stopped>0&&$time-stopped<TBUF)#(TBUF-($time-stopped));
  serviced=0;lost=0;sr_done=0;legacy=0;
  if(trace&&sda===1'b0)$display("CTRL START request from a target at %0t",$time);
  sda_low=1;sda_high=0;#(T_CAS);scl_out=0;active=1;
 endtask
 // Repeated START. A read abort (T-bit) has already made one: then there is nothing to do.
 task automatic restart(input bit od);
  if(sr_done)sr_done=0;
  else begin
   #(HOLD);if(od)set_sda(REL,1);else set_sda(DRV_PP,1);#((od?OD_LOW:PP_LOW)-HOLD);
   scl_out=1;#(T_CAS/2);sda_low=1;sda_high=0;#(T_CAS);scl_out=0;
  end
 endtask
 task automatic stop();
  sr_done=0;#(legacy?FM_HOLD:HOLD);sda_low=1;sda_high=0;#(legacy?FM_LOW:PP_LOW);
  scl_out=1;#(legacy?FM_SETUP:T_CAS);sda_low=0;sda_high=0;stopped=$time;active=0;legacy=0;#1;
 endtask
 // Read one SDR word: eight data bits and the target's T-bit (§5.1.2.3.4). T = 0 ends the read: the
 // controller overlaps it low after the rise. T = 1 offers more: continue (more = 1) or abort with Sr.
 task automatic read_word(output logic [7:0] d,output logic t,input bit more);
  logic s;
  for(int b=7;b>=0;b--)begin clock(PP,REL,1,0,s);d[b]=s;end
  #(HOLD);set_sda(REL,1);#(PP_LOW-HOLD);scl_out=1;#1;t=sda;
  if(t===1'b0)begin sda_low=1;#(PP_HIGH-1);scl_out=0;end
  else if(more)begin #(PP_HIGH-1);scl_out=0;end
  else begin #9;sda_low=1;#(T_CAS);scl_out=0;sr_done=1;end
 endtask
 // Write one SDR word: eight data bits and the odd-parity T-bit, XOR(data, 1) (§5.1.2.3.3).
 task automatic write_word(input [7:0] d);
  logic s;reg [7:0] w;w=d^flip_mask;flip_mask=0;
  for(int b=7;b>=0;b--)clock(od_always?OD:PP,od_always?DRV_OD:DRV_PP,w[b],0,s);
  clock(od_always?OD:PP,od_always?DRV_OD:DRV_PP,~^d,0,s);
 endtask
 // Address header. arb = 1 after S: open drain, and a target may win it (§5.1.2.2.1). arb = 0 after Sr:
 // push-pull, never arbitrated (§5.1.2.2.4). The ACK slot is always open drain.
 task automatic header(input [6:0] addr,input bit rw,input bit arb,output bit ack);
  reg [7:0] v,seen;logic s;v={addr,rw};lost=0;ack=0;
  for(int b=7;b>=0;b--)begin
   if(arb)begin clock(OD,lost?REL:DRV_OD,v[b],0,s);if(!lost&&v[b]&&s===1'b0)lost=1;end
   else clock(PP,DRV_PP,v[b],0,s);
   seen[b]=(s===1'b1);
  end
  if(!lost)begin
   ext_low=stall_ack_ns;clock(OD,(pp_too_early&&!arb)?DRV_PP:REL,1,rw?0:1,s);ack=(s===1'b0);
  end else begin
   // A target won the header: an In-Band Interrupt (its address, R), a Hot-Join (7'h02, W) or a
   // controller-role request (its address, W) (§5.1.2.2).
   req_addr=seen[7:1];req_rw=seen[0];serviced=1;
   if(req_rw&&ibi_accept&&known(req_addr))begin
    clock(OD,DRV_OD,0,(bcr_of(req_addr)&8'h04)!=0 ? 2 : 0,s);ack=1;ibis++;
    if(bcr_of(req_addr)&8'h04)begin read_word(mdb,tbit,0);if(trace)$display("CTRL IBI from %02h accepted, MDB %02h T%0d",req_addr,mdb,tbit);end
    else if(trace)$display("CTRL IBI from %02h accepted, no MDB (BCR[2] = 0)",req_addr);
   end else if(!req_rw&&req_addr!=7'h02&&mr_accept&&known(req_addr)&&(bcr_of(req_addr)>>6)==2'b01)begin
    // A controller-role request from a secondary controller (BCR[7:6] = 01): ACK, then GETACCMST (§5.1.6.3).
    clock(OD,DRV_OD,0,0,s);ack=1;mr_seen=1;mr_addr=req_addr;if(trace)$display("CTRL controller-role request from %02h accepted",req_addr);
   end else if(!req_rw&&req_addr==7'h02&&hj_accept)begin
    clock(OD,DRV_OD,0,0,s);ack=1;hj_seen=1;if(trace)$display("CTRL Hot-Join request accepted");
   end else begin
    clock(OD,REL,1,0,s);if(trace)$display("CTRL request from %02h %s refused (NACK)",req_addr,req_rw?"R":"W");
   end
  end
 endtask
 // S 7'h7E/W, arbitrable. A request that wins is served first; then 7'h7E/W is sent again after Sr.
 task automatic open_frame(output bit ack);
  start();header(7'h7e,0,1,ack);
  if(serviced)begin restart(0);header(7'h7e,0,0,ack);end
 endtask
 // Broadcast CCC (codes 0x00 to 0x7F): S 7'h7E/W, code, optional byte, P (§5.1.9.1, Figure 21).
 task automatic ccc_broadcast(input [7:0] code,input bit has_data,input [7:0] data);
  bit ack;open_frame(ack);if(ack)begin write_word(code);if(has_data)write_word(data);end stop();
 endtask
 // Direct CCC (codes 0x80 to 0xFE): S 7'h7E/W, code, Sr, address/W, optional byte, P (Figure 22).
 // directed_as_broadcast: sends the broadcast form instead (code with bit 7 clear, the byte, P).
 task automatic ccc_direct(input [7:0] code,input [6:0] addr,input bit has_data,input [7:0] data,output bit ack);
  bit a;open_frame(a);
  if(directed_as_broadcast)begin write_word(code&8'h7f);if(has_data)write_word(data);ack=a;end
  else begin write_word(code);restart(0);header(addr,0,0,ack);if(ack&&has_data)write_word(data);end
  stop();
 endtask
 // Direct GET CCC: S 7'h7E/W, code, Sr, address/R, n bytes into rbuf (the target ends with T = 0).
 task automatic ccc_get(input [7:0] code,input [6:0] addr,input int n,output bit ack);
  bit a;logic t;logic [7:0] rb;open_frame(a);write_word(code);restart(0);header(addr,1,0,ack);rcount=0;
  if(ack)begin t=1;for(int i=0;i<n&&t!==1'b0;i++)begin read_word(rb,t,i<n-1);rbuf[i]=rb;rcount++;end end
  stop();
 endtask
 // Private transfers in the form that starts with 7'h7E (MIPI v1.0 Figure 47): S 7'h7E/W, Sr, address.
 task automatic write_regs(input [6:0] da,input [7:0] ptr,input int n,output bit ack);
  bit a;open_frame(a);restart(0);header(da,0,0,ack);
  if(ack)begin write_word(ptr);for(int i=0;i<n;i++)write_word(wbuf[i]);end
  stop();
 endtask
 task automatic write_reg(input [6:0] da,input [7:0] ptr,input [7:0] d,output bit ack);wbuf[0]=d;write_regs(da,ptr,1,ack);endtask
 // Pointer write, Sr, then read until the target ends (T = 0) or n bytes are in rbuf.
 task automatic read_regs(input [6:0] da,input [7:0] ptr,input int n,output bit ack);
  bit a;logic t;logic [7:0] rb;open_frame(a);restart(0);header(da,0,0,ack);rcount=0;
  if(ack)begin write_word(ptr);restart(0);header(da,1,0,ack);
   if(ack)begin t=1;for(int i=0;i<n&&t!==1'b0;i++)begin read_word(rb,t,i<n-1);rbuf[i]=rb;rcount++;end end
  end
  stop();
 endtask
 task automatic read_reg(input [6:0] da,input [7:0] ptr,output logic [7:0] v);bit ack;rbuf[0]=8'hxx;read_regs(da,ptr,1,ack);v=rbuf[0];endtask
 // Legacy I2C message to a static address at Fm timing (MIPI v1.0 Table 57): open drain, ninth bit = ACK.
 task automatic i2c_start();
  if(stopped>0&&$time-stopped<TBUF)#(TBUF-($time-stopped));
  serviced=0;lost=0;sr_done=0;legacy=1;sda_low=1;sda_high=0;#(FM_SETUP);scl_out=0;active=1;
 endtask
 task automatic i2c_byte(input [7:0] v,output bit ack);logic s;
  for(int b=7;b>=0;b--)clock(FM,DRV_OD,v[b],0,s);clock(FM,REL,1,0,s);ack=(s===1'b0);
 endtask
 task automatic i2c_write(input [6:0] a,input [7:0] ptr,input [7:0] d,output bit ack);
  bit k;i2c_start();i2c_byte({a,1'b0},ack);if(ack)begin i2c_byte(ptr,k);i2c_byte(d,k);end stop();
 endtask
 task automatic i2c_read(input [6:0] a,input [7:0] ptr,output logic [7:0] v);
  bit ack;logic s;i2c_start();i2c_byte({a,1'b0},ack);i2c_byte(ptr,ack);
  #(FM_HOLD);set_sda(REL,1);#(FM_LOW-FM_HOLD);scl_out=1;#(FM_SETUP);sda_low=1;#(FM_SETUP);scl_out=0;
  i2c_byte({a,1'b1},ack);for(int b=7;b>=0;b--)begin clock(FM,REL,1,0,s);v[b]=s;end clock(FM,REL,1,0,s);stop();
 endtask
 // ENTDAA (§5.1.4.2, Figure 19): broadcast 0x07, then rounds of Sr 7'h7E/R (open drain), 64 arbitrated bits
 // (provisional ID, BCR, DCR) from the targets, a 7-bit address plus odd parity from the controller and the
 // winner's ACK. A NACKed address is offered once more; a second NACK ends DAA (§5.1.4.2 notes).
 task automatic entdaa();
  bit ack;open_frame(ack);if(ack)write_word(8'h07);daa_rounds();
 endtask
 task automatic daa_rounds();
  bit ack,a2,retry,done;logic s;reg [63:0] id;reg [7:0] w;
  retry=0;done=0;
  while(!done)begin
   restart(1);header(7'h7e,1,1,ack);
   if(!ack)done=1;
   else begin
    for(int k=63;k>=0;k--)begin clock(OD,REL,1,0,s);id[k]=(s===1'b1);end
    w={daa_next,~^daa_next};
    for(int k=7;k>=0;k--)clock(OD,DRV_OD,w[k],0,s);
    clock(OD,REL,1,0,s);a2=(s===1'b0);
    if(a2)begin
     daa_addr[daa_count]=daa_next;daa_pid[daa_count]=id[63:16];daa_bcr[daa_count]=id[15:8];daa_dcr[daa_count]=id[7:0];
     if(trace)$display("CTRL DAA PID %012h BCR %02h DCR %02h -> %02h",id[63:16],id[15:8],id[7:0],daa_next);
     daa_count++;daa_next=next_free(daa_next);retry=0;
    end else if(!retry)begin retry=1;if(trace)$display("CTRL DAA %02h NACKed, offering it again",daa_next);end
    else begin daa_error=1;done=1;if(trace)$display("CTRL DAA %02h NACKed twice: DAA ends with an error",daa_next);end
   end
  end
  stop();
  if(daa_expect&&daa_count<daa_expect&&trace)$display("CTRL DAA assigned %0d of %0d expected addresses",daa_count,daa_expect);
 endtask
 // Wait for a target's START request on a free bus, then clock it out (§5.1.2.2 condition 2, §5.1.6.2).
 // A Hot-Join is answered with ENTDAA in the same frame (§5.1.5 step 4c).
 task automatic serve_request(input time limit);
  time t0;bit ack;t0=$time;
  while(sda!==1'b0&&$time-t0<limit)#10;
  if(sda===1'b0)begin
   start();header(7'h7e,0,1,ack);
   if(hj_seen)begin hj_seen=0;restart(0);header(7'h7e,0,0,ack);if(ack)write_word(8'h07);daa_rounds();end
   else if(mr_seen)begin mr_seen=0;getaccmst(mr_addr,1,ack);end
   else stop();
  end else if(trace)$display("CTRL no request within %0t ns",limit);
 endtask
 // GETACCMST (0x91, §5.1.9.3.16): a direct GET to a secondary controller, after its request (in the same
 // frame, after Sr) or as an offer. An accepted reply is its own address with a parity bit (the bench uses the
 // odd parity of §5.1.4.2); then STOP, and this controller releases SCL after tMMoverlap (§5.1.6.3, Figure 44).
 // A NACK or a wrong address cancels the handoff (Tables 43, 44).
 task automatic getaccmst(input [6:0] a,input bit in_frame,output bit ok);
  bit ack;logic [7:0] b;logic t;b=8'hxx;
  if(in_frame)begin restart(0);header(7'h7e,0,0,ack);end else open_frame(ack);
  write_word(8'h91);restart(0);header(a,1,0,ack);
  if(ack)read_word(b,t,0);
  ok=ack&&(b==={a,~^a});
  stop();
  if(ok)begin #(T_MMOVERLAP);scl_en=0;handed_over=1;end
  if(trace)begin
   if(!ack)$display("CTRL GETACCMST %02h NACKed: mastership kept",a);
   else if(ok)$display("CTRL GETACCMST %02h replied %02h: SCL released",a,b);
   else $display("CTRL GETACCMST %02h replied %02h, not %02h: mastership kept",a,b,{a,~^a});
  end
 endtask
 // DEFSLVS (0x08, §5.1.9.3.7, Table 30): a count, four bytes for this controller (its address, DCR, BCR, static
 // 7'h7E), then four per target (address<<1, DCR, BCR, static<<1); a legacy target has address 0 and its LVR.
 task automatic defslvs();
  bit ack;open_frame(ack);write_word(8'h08);write_word(daa_count+(leg_addr!=0));
  write_word({own_da,1'b0});write_word(8'h00);write_word(8'h40);write_word({7'h7e,1'b0});
  for(int i=0;i<daa_count;i++)begin write_word({daa_addr[i],1'b0});write_word(daa_dcr[i]);write_word(daa_bcr[i]);write_word(8'h00);end
  if(leg_addr!=0)begin write_word(8'h00);write_word(leg_lvr);write_word(8'h00);write_word({leg_addr,1'b0});end
  stop();
 endtask
 // Learn a target's BCR with GETBCR, for a target given its address by SETAASA or SETDASA (no DAA record).
 task automatic learn(input [6:0] a);bit ack;ccc_get(8'h8e,a,1,ack);
  if(ack)begin daa_addr[daa_count]=a;daa_bcr[daa_count]=rbuf[0];daa_pid[daa_count]=0;daa_dcr[daa_count]=0;daa_count++;end
 endtask
 // ENTHDRx (codes 0x20 to 0x27, Table 15). Basic v1.0 has no HDR mode, so nothing is sent in HDR.
 task automatic hdr_enter(input [2:0] mode);bit ack;open_frame(ack);write_word(8'h20|mode);endtask
 // HDR Exit Pattern (§5.2.1.1, Figure 25): SCL low, SDA falls four times at least tDIG_H apart, then SCL
 // rises and SDA rises: a STOP.
 task automatic hdr_exit();
  #(HOLD);sda_low=0;sda_high=1;#(PP_LOW);
  repeat(3)begin sda_low=1;sda_high=0;#(PP_HIGH);sda_low=0;sda_high=1;#(PP_HIGH);end
  sda_low=1;sda_high=0;#(PP_HIGH);scl_out=1;#(T_CAS);sda_low=0;sda_high=0;stopped=$time;active=0;#1;
 endtask
 // HDR Restart Pattern (§5.2.1.2, Figure 26): SCL low, SDA high, then fall, rise, fall, rise at least tDIG_H
 // apart; the next edge is SCL rising. The bus stays in HDR: a new HDR command would follow.
 task automatic hdr_restart();
  #(HOLD);sda_low=0;sda_high=1;#(PP_LOW);
  repeat(2)begin sda_low=1;sda_high=0;#(PP_HIGH);sda_low=0;sda_high=1;#(PP_HIGH);end
  scl_out=1;#(PP_HIGH);scl_out=0;
 endtask
 // A stand-in for HDR traffic: no HDR coding, only the property of §5.2.2's introduction that SDA may change
 // after either SCL edge. The 16 bits of w go out one per SCL edge, so SDA also changes while SCL is high.
 task automatic hdr_burst(input [15:0] w);
  for(int b=15;b>=0;b--)begin #(HOLD);sda_low=!w[b];sda_high=w[b];#(PP_LOW-HOLD);scl_out=!scl_out;end
  if(scl_out)begin #(PP_LOW);scl_out=0;end
 endtask
 // Error Type M2 recovery: S 7'h7E/W, ACK or NACK ignored, HDR Exit Pattern, STOP (§5.1.10.2.3, §5.1.10.2.4).
 task automatic recover();bit ack;start();header(7'h7e,0,1,ack);hdr_exit();endtask
endmodule
Read included file: i3c_target.sv
`timescale 1ns/1ns
// I3C target (v1.0: Slave), SDR. A register map like the P8 target: mem[i] = i ^ MEM_XOR; a private write's
// first byte is the pointer. It answers 7'h7E and its dynamic address only (§5.1.2.1), takes part in ENTDAA
// while it has no address, raises In-Band Interrupts with a mandatory byte (BCR[2]) and recognises the HDR
// Exit Pattern. HOT_JOIN targets stay silent until hot_join() is called (§5.1.5).
module i3c_target #(parameter [47:0] PID=48'h0,parameter [7:0] BCR=8'h06,DCR=8'h00,parameter [6:0] STATIC_ADDR=7'h00,
  parameter [7:0] MEM_XOR=8'h00,parameter HOT_JOIN=0,parameter time T_AVAL=1000,T_IDLE=200000)
 (inout wire scl,sda,output reg sda_low=0,sda_high=0);
 // Fault switches.
 bit pid_tie_bug=0,addr_parity_wrong=0,ibi_without_mdb=0,misses_hdr_exit=0,t_bit_wrong=0,pp_too_early=0,ibi_loses_and_retries_wrong=0,restart_as_exit=0,accmst_wrong_addr=0;
 // A secondary controller's target half (BCR[7:6] = 01): controller-role request, DEFSLVS list, GETACCMST.
 bit mr_req=0,mr_en=1,accept_mastership=1,accmst_sent=0,became_current=0,my_rw=1;reg [7:0] defs[0:39];int ndefs=0;
 // Activity state (ENTASx, §5.1.3.5) and the protocol-error flag GETSTATUS reports (Table 41).
 reg [1:0] activity=0;bit perr=0;
 // Counters: SDR STARTs acted on, HDR Restart patterns seen while in HDR.
 int sdr_starts=0,hdr_restarts=0;bit poss_restart=0;time requested=0,req_gap=0;   // when, and how long after the bus went free, it last pulled SDA
 // Configuration and state. max_read: bytes returned per private read before T = 0.
 int max_read=1;reg [7:0] mdb=8'ha5;
 reg [6:0] da=0;bit has_da=0,ibi_en=1,hj_en=1,powered=!HOT_JOIN,ibi_req=0,hj_req=0;
 reg [7:0] mem[0:255];reg [7:0] ptr=0;
 int st=0,cnt=0,sent=0,errors=0;reg [8:0] sh=0;reg [63:0] daa_word;reg [7:0] tx;reg [7:0] code=0;
 bit active=0,after_s=0,arb=0,arb_lost=0,lost_this_frame=0,daa_mode=0,direct=0,code_next=0,need_ptr=1,ccc_target=0,self_start=0;
 bit hdr_mode=0,err_exit=0,write_ack_release=0,reading=0,daa_won=0,t_ack=0;int falls=0;reg [6:0] my_hdr;time free_since=0;
 bit was_ibi=0,ibi_more=0,ack_pending=0,daa_hdr=0;
 // st: 0 idle, 1 header, 2 header ACK, 3 write word, 4 read word, 5 ignore until Sr/P, 6 DAA ID, 7 DAA address, 8 DAA ACK
 localparam IDLE=0,HDR=1,HACK=2,WR=3,RD=4,IGN=5,DID=6,DAD=7,DACK=8;
 assign sda=sda_low?1'b0:sda_high?1'b1:1'bz;
 initial for(int i=0;i<256;i++)mem[i]=i[7:0]^MEM_XOR;
 task automatic release_sda();sda_low=0;sda_high=0;endtask
 task automatic raise_ibi();ibi_req=1;endtask
 task automatic request_mastership();mr_req=1;endtask
 task automatic hot_join();powered=1;hj_req=1;free_since=$time;endtask
 function automatic bit bad_addr(input [6:0] a);bad_addr=a==7'h3e||a==7'h5e||a==7'h6e||a==7'h76||a==7'h7a||a==7'h7c||a==7'h7f;endfunction
 // START or Repeated START.
 always @(negedge sda)if(scl===1'b1&&powered&&!hdr_mode&&!err_exit)begin
  after_s=!active;sdr_starts++;
  if(!active)begin daa_mode=0;direct=0;lost_this_frame=0;end
  active=1;st=HDR;cnt=0;sh=0;code_next=0;ccc_target=0;reading=0;was_ibi=0;
  // A request rides only on the header after S (§5.1.2.2); ibi_loses_and_retries_wrong also tries after Sr.
  arb=0;arb_lost=0;
  if(has_da&&ibi_req&&ibi_en&&(after_s||(ibi_loses_and_retries_wrong&&lost_this_frame)))begin arb=1;my_hdr=da;my_rw=1;end
  else if(has_da&&mr_req&&mr_en&&after_s)begin arb=1;my_hdr=da;my_rw=0;end
  else if(hj_req&&hj_en&&after_s)begin arb=1;my_hdr=7'h02;my_rw=0;end
  if(accmst_sent&&!after_s)accmst_sent=0;     // Sr after GETACCMST cancels it (§5.1.9.3.16 step 4)
 end
 // STOP.
 always @(posedge sda)if(scl===1'b1&&powered&&!hdr_mode&&!err_exit&&active)begin
  if(accmst_sent)begin accmst_sent=0;became_current=1;end
  active=0;st=IDLE;daa_mode=0;direct=0;release_sda();free_since=$time;self_start=0;
 end
 // HDR Exit Pattern detector (§5.2.1.3, Figure 27): SDA falls counted while SCL stays low; SCL high resets.
 always @(negedge sda)if((hdr_mode||err_exit)&&scl===1'b0)begin falls++;
  if(falls==4&&!misses_hdr_exit)begin hdr_mode=0;err_exit=0;active=1;st=IGN;falls=0;end
 end
 // Restart (§5.2.1.4): exactly two falls, then an SDA rise, then SCL rises. A Basic target supports no HDR mode,
 // so it notes the Restart and stays in HDR until the Exit; restart_as_exit leaves HDR on it (a fault).
 always @(posedge sda)if((hdr_mode||err_exit)&&scl===1'b0)poss_restart=(falls==2);
 always @(posedge scl)if(hdr_mode||err_exit)begin
  if(poss_restart&&hdr_mode)begin hdr_restarts++;if(restart_as_exit)begin hdr_mode=0;active=1;st=IGN;end end
  poss_restart=0;falls=0;
 end
 // Bus Available (tAVAL) or Bus Idle (tIDLE for Hot-Join): pull SDA low to request a START (§5.1.3, §5.1.5).
 always #10 if(powered&&!active&&!self_start&&scl===1'b1&&sda===1'b1&&!hdr_mode&&!err_exit)begin
  if(has_da&&(ibi_req&&ibi_en||mr_req&&mr_en)&&$time-free_since>=T_AVAL)begin self_start=1;sda_low=1;requested=$time;req_gap=$time-free_since;end
  else if(hj_req&&hj_en&&$time-free_since>=T_IDLE)begin self_start=1;sda_low=1;end
 end
 // Sample on the rising edge.
 always @(posedge scl)if(powered&&active&&!hdr_mode&&!err_exit)begin
  case(st)
  HDR:begin
   sh={sh[7:0],sda};cnt++;
   if(arb&&!arb_lost&&my_bit(cnt-1)&&sda===1'b0)begin arb_lost=1;lost_this_frame=1;end
   if(cnt==8)decode_header();
  end
  HACK:begin
   if(arb&&!arb_lost)begin
    // My request won; the controller ACKs or NACKs it.
    if(sda===1'b0)begin
     if(my_hdr==7'h02)begin hj_req=0;st=IGN;end
     else if(!my_rw)begin mr_req=0;st=IGN;end
     else begin ibi_req=0;was_ibi=1;
      if(BCR[2]&&!ibi_without_mdb)begin #2;sda_low=1;sda_high=0;tx=mdb;st=RD;cnt=0;end else st=IGN;
     end
    end else st=IGN;
    self_start=0;
   end else if(daa_hdr)begin daa_hdr=0;st=DID;cnt=0;daa_word={PID,BCR,DCR};end
   else if(write_ack_release)begin #2;release_sda();write_ack_release=0;end
  end
  WR:begin
   sh={sh[7:0],sda};cnt++;
   if(cnt==9)begin cnt=0;
    if(sh[0]!==~^sh[8:1])begin errors++;
     perr=1;if(code_next)begin err_exit=1;falls=0;st=IGN;end else st=IGN;   // S1: wait for the HDR Exit Pattern; S2: wait for Sr/P
    end else if(code_next)begin code_next=0;do_ccc(sh[8:1]);end
    else if(ccc_target)begin ccc_data(sh[8:1]);end
    else if(need_ptr)begin ptr=sh[8:1];need_ptr=0;end
    else begin mem[ptr]=sh[8:1];ptr=ptr+1;end
   end
  end
  RD:begin
   cnt++;
   if(cnt==9)begin cnt=0;#2;release_sda();
    if(ccc_target&&code==8'h91)accmst_sent=1;   // GETACCMST answered: mastership passes at the STOP
    if(!ibi_more)st=IGN;
   end
  end
  DID:begin
   if(!pid_tie_bug&&daa_word[63-cnt]&&sda===1'b0)begin st=IGN;end   // lost: withdraw until the next Sr
   cnt++;if(cnt==64&&st==DID)begin st=DAD;cnt=0;sh=0;end
  end
  DAD:begin sh={sh[7:0],sda};cnt++;if(cnt==8)begin st=DACK;daa_won=((^sh[7:0])==1'b1)^addr_parity_wrong;end end
  DACK:begin if(daa_won)begin da=sh[7:1];has_da=1;end st=IGN;end
  endcase
 end
 function automatic bit my_bit(input int i);reg [7:0] v;v={my_hdr,my_rw};my_bit=v[7-i];endfunction
 task automatic decode_header();
  reg [6:0] a;bit rw;a=sh[7:1];rw=sh[0];cnt=0;
  if(arb&&!arb_lost)begin st=HACK;end
  else if(a==7'h7e&&!rw)begin st=HACK;sda_ack(1);code_next=1;need_ptr=1;end
  else if(a==7'h7e&&rw&&daa_mode&&!has_da&&!hj_req)begin st=HACK;sda_ack(1);daa_hdr=1;end   // then 64 bits (§5.1.4.2 steps 4 to 8)
  else if(a==7'h7e&&rw&&!daa_mode)begin errors++;err_exit=1;falls=0;st=IGN;end    // S0: 7'h7E/R outside DAA
  else if(daa_mode)begin st=IGN;end                                                  // S4 or already assigned
  else if(direct&&((has_da&&a==da)||(code==8'h87&&STATIC_ADDR!=0&&a==STATIC_ADDR)))begin st=HACK;sda_ack(1);ccc_target=1;reading=rw;
   if(code==8'h86)begin has_da=0;da=0;end
   if(code>=8'h82&&code<=8'h85)activity=code-8'h82;
   if(code==8'h91&&(BCR[7:6]!=2'b01||!accept_mastership))begin ack_pending=0;st=IGN;ccc_target=0;end end
  else if(has_da&&a==da)begin st=HACK;sda_ack(1);reading=rw;need_ptr=!rw;end
  else if(bad_addr(a)&&!rw)begin errors++;err_exit=1;falls=0;st=IGN;end              // S0: broken 7'h7E/W
  else st=IGN;
 endtask
 task automatic sda_ack(input bit a);ack_pending=a;endtask
 // Drive 6 ns after the falling edge, one after the controller's 5 ns hold, so no handoff overlaps a 1 and a 0.
 always @(negedge scl)if(powered&&active&&!hdr_mode&&!err_exit)begin
  #6;
  case(st)
  HDR:begin
   if(arb&&!arb_lost)begin
    if(pp_too_early)begin sda_low=!my_bit(cnt);sda_high=my_bit(cnt);end else begin sda_low=!my_bit(cnt);sda_high=0;end
   end else if(arb&&arb_lost)release_sda();
  end
  HACK:begin
   if(arb&&!arb_lost)release_sda();
   else if(ack_pending)begin sda_low=1;sda_high=0;ack_pending=0;
    // Write: release just after the rise (handoff, §5.1.2.3.1). Read: keep low, then send data.
    if(!reading)write_ack_release=1;
   end else if(reading)begin
    st=RD;cnt=0;ibi_more=0;
    if(ccc_target)begin tx=ccc_reply(0);end else begin tx=mem[ptr];ptr=ptr+1;end
    sent=0;drive_bit(tx[7]);
   end else begin
    st=WR;cnt=0;sh=0;
   end
  end
  WR:begin
   release_sda();
   if(cnt==8&&t_bit_wrong&&!code_next&&!ccc_target)sda_low=1;   // the I2C habit: ACK the ninth bit, hold it until SCL falls
  end
  RD:begin
   if(cnt<8)drive_bit(tx[7-cnt]);
   else begin
    // T-bit: 1 = more data (continue), 0 = end of data.
    sent++;ibi_more=was_ibi?0:(ccc_target?ccc_more(sent):sent<max_read);
    sda_low=!ibi_more;sda_high=ibi_more;
   end
  end
  DID:begin sda_low=!daa_word[63-cnt];sda_high=0;end
  DAD:release_sda();
  DACK:begin sda_low=daa_won;sda_high=0;end
  IGN:release_sda();
  endcase
 end
 task automatic drive_bit(input bit v);sda_low=!v;sda_high=v;endtask
 // After a T = 1 the controller either lets the next byte come or pulls SDA low (Sr = abort).
 always @(negedge scl)if(powered&&active&&st==RD&&cnt==0&&ibi_more)begin
  if(sda===1'b0)st=IGN;
  else begin if(ccc_target)tx=ccc_reply(sent);else begin tx=mem[ptr];ptr=ptr+1;end end
 end
 task automatic do_ccc(input [7:0] c);
  code=c;direct=c[7];
  if(c==8'h07)daa_mode=1;
  else if(c==8'h06)begin has_da=0;da=0;end
  else if(c==8'h29&&STATIC_ADDR!=0&&!has_da)begin da=STATIC_ADDR;has_da=1;end
  else if(c>=8'h20&&c<=8'h27)begin hdr_mode=1;falls=0;active=0;st=IDLE;end
  else if(c==8'h00||c==8'h01)begin ccc_target=1;end     // event byte follows
  else if(c>=8'h02&&c<=8'h05)activity=c-8'h02;
  else if(c==8'h08&&BCR[7:6]==2'b01)begin ccc_target=1;ndefs=0;end   // DEFSLVS: only secondary controllers keep it
 endtask
 task automatic ccc_data(input [7:0] d);
  if(code==8'h00||code==8'h80)begin if(d[0])ibi_en=1;if(d[1])mr_en=1;if(d[3])hj_en=1;end
  else if(code==8'h01||code==8'h81)begin if(d[0])ibi_en=0;if(d[1])mr_en=0;if(d[3])hj_en=0;end
  else if(code==8'h08)begin if(ndefs<40)defs[ndefs]=d;ndefs++;end
  else if(code==8'h87||code==8'h88)begin da=d[7:1];has_da=1;end
 endtask
 function automatic [7:0] ccc_reply(input int i);
  case(code)
   8'h8d:ccc_reply=PID>>(8*(5-i));
   8'h8e:ccc_reply=BCR;
   8'h8f:ccc_reply=DCR;
   8'h90:begin ccc_reply=i==0?8'h00:{activity,perr,5'b0};if(i==1)perr=0;end   // GETSTATUS MSB, LSB (Table 41)
   8'h91:ccc_reply={da^{6'b0,accmst_wrong_addr},~^da};
   default:ccc_reply=8'hff;
  endcase
 endfunction
 function automatic bit ccc_more(input int n);ccc_more=(code==8'h8d)?n<6:(code==8'h90)?n<2:0;endfunction
endmodule
Read included file: i3c_hdr_detect.sv
`timescale 1ns/1ns
module i3c_hdr_detect(input wire scl,sda,in_hdr);
 bit misses_restart=0,restart_as_exit=0;int restarts=0,exits=0,falls=0;bit poss=0;
 always @(negedge sda)if(in_hdr&&scl===1'b0)begin falls++;if(falls==4)exits++;end
 always @(posedge sda)if(in_hdr&&scl===1'b0)poss=(falls==2);
 always @(posedge scl)begin
  if(in_hdr&&poss&&!misses_restart)begin if(restart_as_exit)exits++;else restarts++;end
  poss=0;falls=0;
 end
endmodule
Read included file: i3c_secondary.sv
`timescale 1ns/1ns
module i3c_secondary #(parameter [47:0] PID=48'h0,parameter [7:0] BCR=8'h46,DCR=8'h00)
 (inout wire scl,sda,output wire tgt_low,tgt_high,ctl_low,ctl_high);
 bit takes_scl_early=0;
 i3c_target #(.PID(PID),.BCR(BCR),.DCR(DCR)) tgt(scl,sda,tgt_low,tgt_high);
 i3c_ctrl #(.START_AS_CONTROLLER(0)) ctl(scl,sda,ctl_low,ctl_high);
 always @(posedge tgt.accmst_sent)if(takes_scl_early)ctl.scl_en=1;
 always @(posedge tgt.became_current)begin
  #12;ctl.scl_en=1;ctl.stopped=$time;ctl.daa_count=0;ctl.own_da=tgt.da;
  for(int g=5;g+3<tgt.ndefs&&g+3<40;g+=4)if(tgt.defs[g]!=0&&tgt.defs[g][7:1]!=tgt.da)begin
   ctl.daa_addr[ctl.daa_count]=tgt.defs[g][7:1];ctl.daa_dcr[ctl.daa_count]=tgt.defs[g+1];ctl.daa_bcr[ctl.daa_count]=tgt.defs[g+2];ctl.daa_count++;
  end
 end
endmodule
Read included file: i2c_legacy.sv
`timescale 1ns/1ns
module i2c_legacy #(parameter [6:0] ADDRESS=7'h50,parameter time FILTER=50,STRETCH=3000)
 (inout wire scl,sda,output wire scl_low,sda_low);
 bit no_spike_filter=0,legacy_stretches=0;int starts=0,clocks=0,pulls=0;
 wire scl_f,sda_f;assign #(FILTER) scl_f=scl;assign #(FILTER) sda_f=sda;
 tri1 lscl,lsda;wire hd;
 assign lscl=((no_spike_filter?scl:scl_f)===1'b0)?1'b0:1'bz;
 assign lsda=((no_spike_filter?sda:sda_f)===1'b0)?1'b0:1'bz;
 i2c_target #(.ADDRESS({3'b000,ADDRESS})) dev(lscl,lsda,scl_low,sda_low,hd);
 assign scl=scl_low?1'b0:1'bz;assign sda=sda_low?1'b0:1'bz;
 always @(legacy_stretches)dev.stretch_ns=legacy_stretches?STRETCH:0;
 always @(negedge lsda)if(lscl===1'b1)starts++;
 always @(posedge lscl)clocks++;
 always @(posedge sda_low)pulls++;
endmodule
Read included file: i2c_target.sv
`timescale 1ns/1ns
module i2c_target #(parameter [9:0] ADDRESS=10'h050,parameter TEN_BIT=0)
 (inout wire scl,sda,output reg scl_low=0,sda_low=0,output wire high_drive);
 bit ack_not_released=0,ack_wrong_clock=0,stretch_never_released=0,wrong_addr_ack=0,no_stop_detect=0,autoinc_wrap_bug=0,push_pull_target=0,ignores_sr=0;
 // Legal receiver responses, not faults. rx_limit counts post-address bytes,
 // including the pointer byte; -1 is unlimited. A busy address is declined once.
 bit busy_nack=0;int rx_limit=-1,rx_count=0;
 // Optional general-call software reset: UM10204 §§3.1.13–14. This model
 // restores mem[i]=i and ptr=0; ADDRESS is fixed, not a programmable register.
 // Fault switches and bench configuration are not device reset state.
 time stretch_ns=0;bit general_call=1,software_reset=1,general_selected=0,reset_pending=0;int resets=0;
 reg [7:0] mem[0:255];reg [7:0] ptr=0,shift=0,tx=0;
 int state=0,count=0,bytes=0;bit active=0,selected=0,reading=0,need_ptr=1,ack_value=0,ack_active=0,low_addr_next=0,addressed10=0,reading_ack=0;
 // state: 0 receive, 1 receiver ACK, 2 transmit, 3 controller ACK, 4 ignore.
 assign high_drive=push_pull_target&&!sda_low;
 assign sda=sda_low?1'b0:high_drive?1'b1:1'bz;assign scl=scl_low?1'b0:1'bz;
 initial for(int i=0;i<256;i++)mem[i]=i[7:0];
 function automatic [7:0] increment(input reg [7:0] p);increment=autoinc_wrap_bug?((p+1)&8'h0f):p+1'b1;endfunction
 always @(negedge sda)if(scl===1)begin
  if(!(ignores_sr&&active))begin active=1;selected=0;state=0;count=0;bytes=0;rx_count=0;shift=0;reading=0;ack_active=0;sda_low=0;low_addr_next=0;general_selected=0;reset_pending=0;end
 end
 always @(posedge sda)if(scl===1&&active&&!no_stop_detect)begin active=0;selected=0;state=0;count=0;sda_low=0;addressed10=0;end
 always @(posedge scl)if(active)begin
  if(state==0)begin
   shift={shift[6:0],sda};count++;
   if(count==8)begin
    ack_value=0;
    if(bytes==0)begin
      reading=shift[0];
      general_selected=general_call&&shift==0;
      if(general_selected)begin selected=1;low_addr_next=0;end
      else if(TEN_BIT)begin selected=(shift[7:3]==5'b11110&&shift[2:1]==ADDRESS[9:8]&&(!reading||addressed10));low_addr_next=!reading;end
      else selected=(shift[7:1]==ADDRESS[6:0]||(wrong_addr_ack&&shift[7:1]==ADDRESS[6:0]+1'b1));
      if(selected&&busy_nack)begin selected=0;busy_nack=0;end
      ack_value=selected;need_ptr=1;
    end else if(low_addr_next)begin selected=selected&&shift==ADDRESS[7:0];ack_value=selected;addressed10=selected;low_addr_next=0;end
    else if(selected)begin
      ack_value=rx_limit<0||rx_count<rx_limit;
      if(general_selected)begin
       // Unsupported general-call commands are NACKed, never register writes.
       ack_value=ack_value&&bytes==1&&software_reset&&shift==8'h06;
       reset_pending=ack_value;
      end else if(ack_value)begin rx_count++;if(need_ptr)begin ptr=shift;need_ptr=0;end else begin mem[ptr]=shift;ptr=increment(ptr);end end
      if(!ack_value)selected=0;
    end
    bytes++;state=1;ack_active=0;count=0;
   end
  end else if(state==2)begin count++;if(count==8)begin state=3;count=0;reading_ack=0;end end
  else if(state==3&&reading_ack)begin if(sda===0)begin ptr=increment(ptr);tx=mem[ptr];state=2;count=0;end else state=4;end
 end
 always @(negedge scl)if(active)begin
  // Deliberate early-ACK fault: after seven samples, pull for clock 8.
  // The address prefix is already known; its R/W bit has not arrived yet.
  if(state==0&&count==7&&ack_wrong_clock)begin
   if(bytes==0)sda_low=!busy_nack&&((general_call&&shift[6:0]==0)||(TEN_BIT?(shift[6:2]==5'b11110&&shift[1:0]==ADDRESS[9:8]):(shift[6:0]==ADDRESS[6:0]||(wrong_addr_ack&&shift[6:0]==ADDRESS[6:0]+1'b1))));
   else sda_low=selected&&(rx_limit<0||rx_count<rx_limit);
  end else if(state==1)begin
   if(!ack_active)begin
    sda_low=ack_value&&!ack_wrong_clock;ack_active=1;
   end else begin
    sda_low=ack_not_released&&ack_value;ack_active=0;
    if(reset_pending)begin
     // Finish the ninth ACK clock before resetting and releasing the bus.
     for(int i=0;i<256;i++)mem[i]=i[7:0];
     ptr=0;shift=0;tx=0;count=0;bytes=0;rx_count=0;need_ptr=1;
     selected=0;reading=0;reading_ack=0;low_addr_next=0;addressed10=0;
     general_selected=0;reset_pending=0;resets++;sda_low=0;scl_low=0;state=4;
    end else if(!selected)state=4;
    else if(reading)begin state=2;tx=mem[ptr];count=0;sda_low=!tx[7];end
    else state=0;
    // Byte-level stretch after the ACK, as in UM10204 §3.1.9. Prepare
    // outgoing data before releasing SCL; overlap the controller's LOW.
    if(selected&&ack_value&&(stretch_ns>0||stretch_never_released))begin scl_low=1;if(!stretch_never_released)begin #(stretch_ns);scl_low=0;end end
   end
  end else if(state==2)sda_low=!tx[7-count];
  else if(state==3)begin sda_low=0;reading_ack=1;end
  else if(state==4)sda_low=0;
 end
endmodule
Read included file: i3c_mon.sv
`timescale 1ns/1ns
// Bus decoder: one line per frame, from SCL and SDA only. The ninth bit is an ACK after an address and a
// T-bit after a data word (§5.1.2.3); frames to the legacy address are decoded as I2C. phase (an output
// the checker reads): 0 idle, 1 header after S, 2 header after Sr, 3 ACK, 4 write word, 5 read word,
// 6 DAA ID, 7 DAA address, 8 DAA ACK, 9 HDR, 10 I2C byte, 11 I2C ACK, 12 waiting for Sr or P.
module i3c_mon #(parameter [6:0] LEGACY=7'h50)(input wire scl,sda,output reg [3:0] phase=0);
 bit trace=0,active=0,daa=0,hdr=0,legacy=0,code_next=0;int cnt=0,frames=0,starts=0,exits=0,falls=0;
 reg [63:0] sh=0;reg [6:0] addr=0;bit rw=0;string text="";
 always @(negedge sda)if(scl===1'b1&&!hdr)begin
  if(active)text={text," Sr"};else begin text=" S";daa=0;legacy=0;end
  phase=active?2:1;active=1;cnt=0;sh=0;starts++;code_next=0;
 end
 always @(posedge sda)if(scl===1'b1&&active&&!hdr)begin
  text={text," P"};if(trace)$display("%s%s",legacy?"I2C":"I3C",text);text="";active=0;phase=0;frames++;
 end
 // HDR Exit Pattern: four SDA falls while SCL stays low (§5.2.1.1). Seen in or out of HDR (M2 recovery).
 always @(negedge sda)if(scl===1'b0&&(hdr||active))begin falls++;if(falls==4)begin hdr=0;falls=0;text={text," EXIT"};exits++;phase=12;end end
 bit poss=0;int restarts=0;
 always @(posedge sda)if(scl===1'b0&&hdr)poss=(falls==2);
 always @(posedge scl)begin if(hdr&&poss)begin text={text," RESTART"};restarts++;end poss=0;falls=0;end
 always @(posedge scl)if(active&&!hdr)begin
  sh={sh[62:0],sda};cnt++;
  case(phase)
  1,2:if(cnt==8)begin addr=sh[7:1];rw=sh[0];cnt=0;
     if(phase==1&&addr==LEGACY)legacy=1;
     text={text,$sformatf(" %02h %s",addr,rw?"R":"W")};phase=legacy?11:3;end
  3:begin cnt=0;text={text,sda===1'b0 ? " A" : sda===1'b1 ? " NA" : " A=x"};
     if(sda!==1'b0)phase=12;
     else if(daa&&addr==7'h7e&&rw)phase=6;
     else if(!rw)begin phase=4;code_next=(addr==7'h7e);end
     else phase=5;
    end
  4,5:if(cnt==9)begin cnt=0;text={text,$sformatf(" %02h T%0d",sh[8:1],sh[0])};
     if(phase==4&&code_next)begin code_next=0;
      if(sh[8:1]==8'h07)daa=1;
      if(sh[8:1]>=8'h20&&sh[8:1]<=8'h27)begin hdr=1;falls=0;phase=9;text={text," HDR"};end
     end
     if(phase==5&&sh[0]===1'b0)phase=12;
    end
  6:if(cnt==64)begin cnt=0;text={text,$sformatf(" id %012h bcr %02h dcr %02h",sh[63:16],sh[15:8],sh[7:0])};phase=7;end
  7:if(cnt==8)begin cnt=0;text={text,$sformatf(" da %02h par %0d",sh[7:1],sh[0])};phase=8;end
  8:begin cnt=0;text={text,sda===1'b0 ? " A" : sda===1'b1 ? " NA" : " A=x"};phase=12;end
  10:if(cnt==8)begin cnt=0;text={text,$sformatf(" %02h",sh[7:0])};phase=11;end
  11:begin cnt=0;text={text,sda===1'b0 ? " A" : sda===1'b1 ? " NA" : " A=x"};phase=10;end
  default:cnt=0;
  endcase
 end
endmodule
Read included file: i3c_checker.sv
`timescale 1ns/1ns
// Wire rules with the driver view. Index 0 of lows/highs is the controller; the others are targets.
// Each rule prints at most once per frame (S to P) and counts every time it fires.
//  no_pp_contention   one device drives SDA high while another pulls it low (§5.1.2.3: handoffs are specified)
//  od_in_header       a device drives SDA high in the arbitrable header after S or its ACK (§5.1.2.2.1)
//  pp_in_data         the controller sends a 1 in a write word without driving it high (§5.1.2: data is push-pull)
//  t_bit              the ninth bit of a write word is not XOR(data, 1) (§5.1.2.3.3)
//  ibi_mdb_present    no target drives the byte after an accepted header after S with R (BCR[2] = 1, §5.1.6.2)
//  legacy_no_stretch  a legacy target pulls SCL low (MIPI v1.0 Table 56: no clock stretching on I3C)
module i3c_checker #(parameter N=4)(input wire scl,sda,input wire [N-1:0] lows,highs,input wire [3:0] phase,input wire legacy_scl_low);
 int broken=0,contention=0,od_header=0,pp_data=0,tbit=0,mdb=0,stretch=0;bit quiet=0;   // quiet: count, do not print
 reg [5:0] said=0;reg [3:0] ph=0,prev=0;reg [8:0] w=0;int k=0,hk=0,mk=0;bit arb_ack=0,hdr_rw=0,mdb_seen=0,mdb_watch=0,viol=0;
 task automatic bad(input int i,input string rule);broken++;if(!said[i]&&!quiet)$display("I3C CHECK %s at %0t",rule,$time);said[i]=1;endtask
 always @(negedge sda)if(scl===1'b1)begin k=0;viol=0;end
 always @(posedge sda)if(scl===1'b1)said=0;
 always @(negedge scl)begin prev=ph;ph=phase;if(ph!=prev)begin k=0;hk=0;end
  if(ph!=prev&&ph==3)arb_ack=(prev==1);
  if(ph==5&&prev==3&&arb_ack&&hdr_rw)begin mdb_watch=1;mdb_seen=0;mk=0;end
 end
 always @(lows or highs)begin #1;if(|lows&&|highs)begin contention++;bad(0,"no_pp_contention");end end
 always @(posedge scl)begin
  if((ph==1||(ph==3&&arb_ack))&&|highs)begin od_header++;bad(1,"od_in_header");end
  if(ph==1)begin hk++;if(hk==8)hdr_rw=(sda===1'b1);end
  if(ph==4)begin k++;w={w[7:0],sda};
   if(k<=8&&sda===1'b1&&!highs[0])viol=1;
   if(k==9)begin if(viol)begin pp_data++;bad(2,"pp_in_data");end if(w[0]!==~^w[8:1])begin tbit++;bad(3,"t_bit");end k=0;viol=0;end
  end
  if(mdb_watch)begin mk++;if(|lows[N-1:1]|||highs[N-1:1])mdb_seen=1;
   if(mk==8)begin mdb_watch=0;if(!mdb_seen)begin mdb++;bad(4,"ibi_mdb_present");end end
  end
 end
 always @(posedge legacy_scl_low)begin stretch++;bad(5,"legacy_no_stretch");end
 // A missing mandatory byte turns the controller's handoff release into a STOP inside the ACK slot.
 always @(posedge sda)if(scl===1'b1&&ph==3&&arb_ack&&hdr_rw)begin mdb++;bad(4,"ibi_mdb_present");end
endmodule
Predict the outputTwo targets whose IDs differ only in bit 12; target2.pid_tie_bug = 1. After ctrl.entdaa(), the program prints ctrl.daa_count, the number of addresses the controller handed out. What does it print?
`timescale 1ns/1ns
module tb;
  tri1 scl, sda;                     // tri1: the pull-up and high-keeper of MIPI v1.0 §5.1.3.1
  wire c_low, c_high;                // driver view: the controller pulls SDA low / drives it high
  wire t1_low, t1_high, t2_low, t2_high;   // the same for each I3C target
  wire l_low = 0, l_scl = 0;         // no legacy target: a pure I3C bus
  wire [3:0] phase;                  // the decoder's frame phase, read by the checker
  bit ack; logic [7:0] value;
  i3c_ctrl    #(.TBUF(40)) ctrl(scl, sda, c_low, c_high);
  i3c_target  #(.PID(48'h02aa01230000)) target1(scl, sda, t1_low, t1_high);
  i3c_target  #(.PID(48'h02aa01231000)) target2(scl, sda, t2_low, t2_high);
  i3c_mon     mon(scl, sda, phase);                              // prints I3C S 7e W A ... P
  i3c_checker #(.N(3)) ck(scl, sda, {t2_low, t1_low, c_low}, {t2_high, t1_high, c_high}, phase, l_scl);   // prints I3C CHECK <rule>
  initial begin
    ctrl.daa_expect = 2;
    ck.quiet = 1;      // one printed line: the answer
    #2000;
    target2.pid_tie_bug = 1;
    ctrl.entdaa();
    $display("%0d", ctrl.daa_count);
    #2000; $finish;
  end
  initial begin #3000000; $fatal(1, "I3C watchdog"); end
endmodule
Read included file: i3c_ctrl.sv
`timescale 1ns/1ns
// I3C controller (v1.0: Master), SDR. Task calls drive the pins; SCL is push-pull and driven by this device
// alone (§5.1.2). SDA is open drain in the arbitrable header after S, in every ACK slot and in DAA; push-pull
// in the header after Sr and in write data (§5.1.2.2, §5.1.2.3, §5.1.4.2).
module i3c_ctrl(inout wire scl,sda,output reg sda_low=0,sda_high=0);
 parameter time PP_LOW=40,PP_HIGH=40,OD_LOW=200,OD_HIGH=40,HOLD=5,T_CAS=40,TBUF=1300,
                FM_LOW=1300,FM_HIGH=1200,FM_HOLD=100,FM_SETUP=600,T_MMOVERLAP=220;
 // START_AS_CONTROLLER = 0: a secondary controller's controller half, SCL released until a handoff (§5.1.6.3).
 parameter START_AS_CONTROLLER=1;
 // Fault switches.
 bit pp_too_early=0,od_always=0,directed_as_broadcast=0;
 // Policy and an error injector: flip_mask flips bits of the next written word on the wire (noise);
 // the T-bit is still the parity of the intended byte.
 bit ibi_accept=1,hj_accept=1,trace=0;reg [7:0] flip_mask=0;
 // Mastership: accept requests from secondary controllers; this device's own dynamic address for DEFSLVS.
 // Clock stalling (§5.1.2.5.1): extra SCL low in the next header ACK slot, at most 100 µs (Table 11).
 bit mr_accept=1,mr_seen=0,handed_over=0,scl_en=START_AS_CONTROLLER;reg [6:0] mr_addr=0,own_da=7'h20;
 int stall_ack_ns=0;time ext_low=0;reg [6:0] leg_addr=0;reg [7:0] leg_lvr=0;
 // State.
 reg scl_out=1;bit active=0,sr_done=0,lost=0,serviced=0,hj_seen=0,daa_error=0,legacy=0;time stopped=0;
 reg [6:0] req_addr=0;bit req_rw=0;logic [7:0] mdb;logic tbit;
 int daa_count=0,daa_expect=0,ibis=0;reg [6:0] daa_next=7'h08;
 reg [6:0] daa_addr[0:7];reg [47:0] daa_pid[0:7];reg [7:0] daa_bcr[0:7],daa_dcr[0:7];
 reg [7:0] wbuf[0:7];logic [7:0] rbuf[0:7];int rcount=0;
 assign scl=scl_en?scl_out:1'bz;assign sda=sda_low?1'b0:sda_high?1'b1:1'bz;
 localparam PP=0,OD=1,FM=2,REL=0,DRV_OD=1,DRV_PP=2;
 task automatic set_sda(input int how,input logic v);
  if(how==REL)begin sda_low=0;sda_high=0;end else if(how==DRV_OD)begin sda_low=!v;sda_high=0;end else begin sda_low=!v;sda_high=v;end
 endtask
 // One SCL pulse; SCL is low on entry and on exit. post: 0 nothing; 1 pull SDA low just after the rise
 // when the sample is 0 (ACK to write data handoff, §5.1.2.3.1); 2 release 12 ns after the rise (IBI ACK
 // to mandatory byte handoff, §5.1.2.3.2).
 task automatic clock(input int speed,input int how,input logic v,input int post,output logic s);
  time lo,hi,hd;lo=speed==FM?FM_LOW:speed==OD?OD_LOW:PP_LOW;hi=speed==FM?FM_HIGH:speed==OD?OD_HIGH:PP_HIGH;hd=speed==FM?FM_HOLD:HOLD;
  #(hd);set_sda(how,v);#(lo-hd+ext_low);ext_low=0;scl_out=1;#1;s=sda;
  if(post==1&&s===1'b0)begin sda_low=1;sda_high=0;end
  if(post==2)begin #11;sda_low=0;#(hi-12);end else #(hi-1);
  scl_out=0;
 endtask
 // Next free dynamic address, skipping the reserved values of Table 9.
 function automatic [6:0] next_free(input [6:0] a);
  reg [6:0] n;n=a+1;while(n==7'h3e||n==7'h5e||n==7'h6e||n==7'h76||n==7'h7a||n==7'h7c||n>=7'h7e)n=n+1;next_free=n;
 endfunction
 function automatic [7:0] bcr_of(input [6:0] a);
  bcr_of=0;for(int i=0;i<daa_count;i++)if(daa_addr[i]==a)bcr_of=daa_bcr[i];
 endfunction
 function automatic bit known(input [6:0] a);
  known=0;for(int i=0;i<daa_count;i++)if(daa_addr[i]==a)known=1;
 endfunction
 task automatic start();
  if(stopped>0&&$time-stopped<TBUF)#(TBUF-($time-stopped));
  serviced=0;lost=0;sr_done=0;legacy=0;
  if(trace&&sda===1'b0)$display("CTRL START request from a target at %0t",$time);
  sda_low=1;sda_high=0;#(T_CAS);scl_out=0;active=1;
 endtask
 // Repeated START. A read abort (T-bit) has already made one: then there is nothing to do.
 task automatic restart(input bit od);
  if(sr_done)sr_done=0;
  else begin
   #(HOLD);if(od)set_sda(REL,1);else set_sda(DRV_PP,1);#((od?OD_LOW:PP_LOW)-HOLD);
   scl_out=1;#(T_CAS/2);sda_low=1;sda_high=0;#(T_CAS);scl_out=0;
  end
 endtask
 task automatic stop();
  sr_done=0;#(legacy?FM_HOLD:HOLD);sda_low=1;sda_high=0;#(legacy?FM_LOW:PP_LOW);
  scl_out=1;#(legacy?FM_SETUP:T_CAS);sda_low=0;sda_high=0;stopped=$time;active=0;legacy=0;#1;
 endtask
 // Read one SDR word: eight data bits and the target's T-bit (§5.1.2.3.4). T = 0 ends the read: the
 // controller overlaps it low after the rise. T = 1 offers more: continue (more = 1) or abort with Sr.
 task automatic read_word(output logic [7:0] d,output logic t,input bit more);
  logic s;
  for(int b=7;b>=0;b--)begin clock(PP,REL,1,0,s);d[b]=s;end
  #(HOLD);set_sda(REL,1);#(PP_LOW-HOLD);scl_out=1;#1;t=sda;
  if(t===1'b0)begin sda_low=1;#(PP_HIGH-1);scl_out=0;end
  else if(more)begin #(PP_HIGH-1);scl_out=0;end
  else begin #9;sda_low=1;#(T_CAS);scl_out=0;sr_done=1;end
 endtask
 // Write one SDR word: eight data bits and the odd-parity T-bit, XOR(data, 1) (§5.1.2.3.3).
 task automatic write_word(input [7:0] d);
  logic s;reg [7:0] w;w=d^flip_mask;flip_mask=0;
  for(int b=7;b>=0;b--)clock(od_always?OD:PP,od_always?DRV_OD:DRV_PP,w[b],0,s);
  clock(od_always?OD:PP,od_always?DRV_OD:DRV_PP,~^d,0,s);
 endtask
 // Address header. arb = 1 after S: open drain, and a target may win it (§5.1.2.2.1). arb = 0 after Sr:
 // push-pull, never arbitrated (§5.1.2.2.4). The ACK slot is always open drain.
 task automatic header(input [6:0] addr,input bit rw,input bit arb,output bit ack);
  reg [7:0] v,seen;logic s;v={addr,rw};lost=0;ack=0;
  for(int b=7;b>=0;b--)begin
   if(arb)begin clock(OD,lost?REL:DRV_OD,v[b],0,s);if(!lost&&v[b]&&s===1'b0)lost=1;end
   else clock(PP,DRV_PP,v[b],0,s);
   seen[b]=(s===1'b1);
  end
  if(!lost)begin
   ext_low=stall_ack_ns;clock(OD,(pp_too_early&&!arb)?DRV_PP:REL,1,rw?0:1,s);ack=(s===1'b0);
  end else begin
   // A target won the header: an In-Band Interrupt (its address, R), a Hot-Join (7'h02, W) or a
   // controller-role request (its address, W) (§5.1.2.2).
   req_addr=seen[7:1];req_rw=seen[0];serviced=1;
   if(req_rw&&ibi_accept&&known(req_addr))begin
    clock(OD,DRV_OD,0,(bcr_of(req_addr)&8'h04)!=0 ? 2 : 0,s);ack=1;ibis++;
    if(bcr_of(req_addr)&8'h04)begin read_word(mdb,tbit,0);if(trace)$display("CTRL IBI from %02h accepted, MDB %02h T%0d",req_addr,mdb,tbit);end
    else if(trace)$display("CTRL IBI from %02h accepted, no MDB (BCR[2] = 0)",req_addr);
   end else if(!req_rw&&req_addr!=7'h02&&mr_accept&&known(req_addr)&&(bcr_of(req_addr)>>6)==2'b01)begin
    // A controller-role request from a secondary controller (BCR[7:6] = 01): ACK, then GETACCMST (§5.1.6.3).
    clock(OD,DRV_OD,0,0,s);ack=1;mr_seen=1;mr_addr=req_addr;if(trace)$display("CTRL controller-role request from %02h accepted",req_addr);
   end else if(!req_rw&&req_addr==7'h02&&hj_accept)begin
    clock(OD,DRV_OD,0,0,s);ack=1;hj_seen=1;if(trace)$display("CTRL Hot-Join request accepted");
   end else begin
    clock(OD,REL,1,0,s);if(trace)$display("CTRL request from %02h %s refused (NACK)",req_addr,req_rw?"R":"W");
   end
  end
 endtask
 // S 7'h7E/W, arbitrable. A request that wins is served first; then 7'h7E/W is sent again after Sr.
 task automatic open_frame(output bit ack);
  start();header(7'h7e,0,1,ack);
  if(serviced)begin restart(0);header(7'h7e,0,0,ack);end
 endtask
 // Broadcast CCC (codes 0x00 to 0x7F): S 7'h7E/W, code, optional byte, P (§5.1.9.1, Figure 21).
 task automatic ccc_broadcast(input [7:0] code,input bit has_data,input [7:0] data);
  bit ack;open_frame(ack);if(ack)begin write_word(code);if(has_data)write_word(data);end stop();
 endtask
 // Direct CCC (codes 0x80 to 0xFE): S 7'h7E/W, code, Sr, address/W, optional byte, P (Figure 22).
 // directed_as_broadcast: sends the broadcast form instead (code with bit 7 clear, the byte, P).
 task automatic ccc_direct(input [7:0] code,input [6:0] addr,input bit has_data,input [7:0] data,output bit ack);
  bit a;open_frame(a);
  if(directed_as_broadcast)begin write_word(code&8'h7f);if(has_data)write_word(data);ack=a;end
  else begin write_word(code);restart(0);header(addr,0,0,ack);if(ack&&has_data)write_word(data);end
  stop();
 endtask
 // Direct GET CCC: S 7'h7E/W, code, Sr, address/R, n bytes into rbuf (the target ends with T = 0).
 task automatic ccc_get(input [7:0] code,input [6:0] addr,input int n,output bit ack);
  bit a;logic t;logic [7:0] rb;open_frame(a);write_word(code);restart(0);header(addr,1,0,ack);rcount=0;
  if(ack)begin t=1;for(int i=0;i<n&&t!==1'b0;i++)begin read_word(rb,t,i<n-1);rbuf[i]=rb;rcount++;end end
  stop();
 endtask
 // Private transfers in the form that starts with 7'h7E (MIPI v1.0 Figure 47): S 7'h7E/W, Sr, address.
 task automatic write_regs(input [6:0] da,input [7:0] ptr,input int n,output bit ack);
  bit a;open_frame(a);restart(0);header(da,0,0,ack);
  if(ack)begin write_word(ptr);for(int i=0;i<n;i++)write_word(wbuf[i]);end
  stop();
 endtask
 task automatic write_reg(input [6:0] da,input [7:0] ptr,input [7:0] d,output bit ack);wbuf[0]=d;write_regs(da,ptr,1,ack);endtask
 // Pointer write, Sr, then read until the target ends (T = 0) or n bytes are in rbuf.
 task automatic read_regs(input [6:0] da,input [7:0] ptr,input int n,output bit ack);
  bit a;logic t;logic [7:0] rb;open_frame(a);restart(0);header(da,0,0,ack);rcount=0;
  if(ack)begin write_word(ptr);restart(0);header(da,1,0,ack);
   if(ack)begin t=1;for(int i=0;i<n&&t!==1'b0;i++)begin read_word(rb,t,i<n-1);rbuf[i]=rb;rcount++;end end
  end
  stop();
 endtask
 task automatic read_reg(input [6:0] da,input [7:0] ptr,output logic [7:0] v);bit ack;rbuf[0]=8'hxx;read_regs(da,ptr,1,ack);v=rbuf[0];endtask
 // Legacy I2C message to a static address at Fm timing (MIPI v1.0 Table 57): open drain, ninth bit = ACK.
 task automatic i2c_start();
  if(stopped>0&&$time-stopped<TBUF)#(TBUF-($time-stopped));
  serviced=0;lost=0;sr_done=0;legacy=1;sda_low=1;sda_high=0;#(FM_SETUP);scl_out=0;active=1;
 endtask
 task automatic i2c_byte(input [7:0] v,output bit ack);logic s;
  for(int b=7;b>=0;b--)clock(FM,DRV_OD,v[b],0,s);clock(FM,REL,1,0,s);ack=(s===1'b0);
 endtask
 task automatic i2c_write(input [6:0] a,input [7:0] ptr,input [7:0] d,output bit ack);
  bit k;i2c_start();i2c_byte({a,1'b0},ack);if(ack)begin i2c_byte(ptr,k);i2c_byte(d,k);end stop();
 endtask
 task automatic i2c_read(input [6:0] a,input [7:0] ptr,output logic [7:0] v);
  bit ack;logic s;i2c_start();i2c_byte({a,1'b0},ack);i2c_byte(ptr,ack);
  #(FM_HOLD);set_sda(REL,1);#(FM_LOW-FM_HOLD);scl_out=1;#(FM_SETUP);sda_low=1;#(FM_SETUP);scl_out=0;
  i2c_byte({a,1'b1},ack);for(int b=7;b>=0;b--)begin clock(FM,REL,1,0,s);v[b]=s;end clock(FM,REL,1,0,s);stop();
 endtask
 // ENTDAA (§5.1.4.2, Figure 19): broadcast 0x07, then rounds of Sr 7'h7E/R (open drain), 64 arbitrated bits
 // (provisional ID, BCR, DCR) from the targets, a 7-bit address plus odd parity from the controller and the
 // winner's ACK. A NACKed address is offered once more; a second NACK ends DAA (§5.1.4.2 notes).
 task automatic entdaa();
  bit ack;open_frame(ack);if(ack)write_word(8'h07);daa_rounds();
 endtask
 task automatic daa_rounds();
  bit ack,a2,retry,done;logic s;reg [63:0] id;reg [7:0] w;
  retry=0;done=0;
  while(!done)begin
   restart(1);header(7'h7e,1,1,ack);
   if(!ack)done=1;
   else begin
    for(int k=63;k>=0;k--)begin clock(OD,REL,1,0,s);id[k]=(s===1'b1);end
    w={daa_next,~^daa_next};
    for(int k=7;k>=0;k--)clock(OD,DRV_OD,w[k],0,s);
    clock(OD,REL,1,0,s);a2=(s===1'b0);
    if(a2)begin
     daa_addr[daa_count]=daa_next;daa_pid[daa_count]=id[63:16];daa_bcr[daa_count]=id[15:8];daa_dcr[daa_count]=id[7:0];
     if(trace)$display("CTRL DAA PID %012h BCR %02h DCR %02h -> %02h",id[63:16],id[15:8],id[7:0],daa_next);
     daa_count++;daa_next=next_free(daa_next);retry=0;
    end else if(!retry)begin retry=1;if(trace)$display("CTRL DAA %02h NACKed, offering it again",daa_next);end
    else begin daa_error=1;done=1;if(trace)$display("CTRL DAA %02h NACKed twice: DAA ends with an error",daa_next);end
   end
  end
  stop();
  if(daa_expect&&daa_count<daa_expect&&trace)$display("CTRL DAA assigned %0d of %0d expected addresses",daa_count,daa_expect);
 endtask
 // Wait for a target's START request on a free bus, then clock it out (§5.1.2.2 condition 2, §5.1.6.2).
 // A Hot-Join is answered with ENTDAA in the same frame (§5.1.5 step 4c).
 task automatic serve_request(input time limit);
  time t0;bit ack;t0=$time;
  while(sda!==1'b0&&$time-t0<limit)#10;
  if(sda===1'b0)begin
   start();header(7'h7e,0,1,ack);
   if(hj_seen)begin hj_seen=0;restart(0);header(7'h7e,0,0,ack);if(ack)write_word(8'h07);daa_rounds();end
   else if(mr_seen)begin mr_seen=0;getaccmst(mr_addr,1,ack);end
   else stop();
  end else if(trace)$display("CTRL no request within %0t ns",limit);
 endtask
 // GETACCMST (0x91, §5.1.9.3.16): a direct GET to a secondary controller, after its request (in the same
 // frame, after Sr) or as an offer. An accepted reply is its own address with a parity bit (the bench uses the
 // odd parity of §5.1.4.2); then STOP, and this controller releases SCL after tMMoverlap (§5.1.6.3, Figure 44).
 // A NACK or a wrong address cancels the handoff (Tables 43, 44).
 task automatic getaccmst(input [6:0] a,input bit in_frame,output bit ok);
  bit ack;logic [7:0] b;logic t;b=8'hxx;
  if(in_frame)begin restart(0);header(7'h7e,0,0,ack);end else open_frame(ack);
  write_word(8'h91);restart(0);header(a,1,0,ack);
  if(ack)read_word(b,t,0);
  ok=ack&&(b==={a,~^a});
  stop();
  if(ok)begin #(T_MMOVERLAP);scl_en=0;handed_over=1;end
  if(trace)begin
   if(!ack)$display("CTRL GETACCMST %02h NACKed: mastership kept",a);
   else if(ok)$display("CTRL GETACCMST %02h replied %02h: SCL released",a,b);
   else $display("CTRL GETACCMST %02h replied %02h, not %02h: mastership kept",a,b,{a,~^a});
  end
 endtask
 // DEFSLVS (0x08, §5.1.9.3.7, Table 30): a count, four bytes for this controller (its address, DCR, BCR, static
 // 7'h7E), then four per target (address<<1, DCR, BCR, static<<1); a legacy target has address 0 and its LVR.
 task automatic defslvs();
  bit ack;open_frame(ack);write_word(8'h08);write_word(daa_count+(leg_addr!=0));
  write_word({own_da,1'b0});write_word(8'h00);write_word(8'h40);write_word({7'h7e,1'b0});
  for(int i=0;i<daa_count;i++)begin write_word({daa_addr[i],1'b0});write_word(daa_dcr[i]);write_word(daa_bcr[i]);write_word(8'h00);end
  if(leg_addr!=0)begin write_word(8'h00);write_word(leg_lvr);write_word(8'h00);write_word({leg_addr,1'b0});end
  stop();
 endtask
 // Learn a target's BCR with GETBCR, for a target given its address by SETAASA or SETDASA (no DAA record).
 task automatic learn(input [6:0] a);bit ack;ccc_get(8'h8e,a,1,ack);
  if(ack)begin daa_addr[daa_count]=a;daa_bcr[daa_count]=rbuf[0];daa_pid[daa_count]=0;daa_dcr[daa_count]=0;daa_count++;end
 endtask
 // ENTHDRx (codes 0x20 to 0x27, Table 15). Basic v1.0 has no HDR mode, so nothing is sent in HDR.
 task automatic hdr_enter(input [2:0] mode);bit ack;open_frame(ack);write_word(8'h20|mode);endtask
 // HDR Exit Pattern (§5.2.1.1, Figure 25): SCL low, SDA falls four times at least tDIG_H apart, then SCL
 // rises and SDA rises: a STOP.
 task automatic hdr_exit();
  #(HOLD);sda_low=0;sda_high=1;#(PP_LOW);
  repeat(3)begin sda_low=1;sda_high=0;#(PP_HIGH);sda_low=0;sda_high=1;#(PP_HIGH);end
  sda_low=1;sda_high=0;#(PP_HIGH);scl_out=1;#(T_CAS);sda_low=0;sda_high=0;stopped=$time;active=0;#1;
 endtask
 // HDR Restart Pattern (§5.2.1.2, Figure 26): SCL low, SDA high, then fall, rise, fall, rise at least tDIG_H
 // apart; the next edge is SCL rising. The bus stays in HDR: a new HDR command would follow.
 task automatic hdr_restart();
  #(HOLD);sda_low=0;sda_high=1;#(PP_LOW);
  repeat(2)begin sda_low=1;sda_high=0;#(PP_HIGH);sda_low=0;sda_high=1;#(PP_HIGH);end
  scl_out=1;#(PP_HIGH);scl_out=0;
 endtask
 // A stand-in for HDR traffic: no HDR coding, only the property of §5.2.2's introduction that SDA may change
 // after either SCL edge. The 16 bits of w go out one per SCL edge, so SDA also changes while SCL is high.
 task automatic hdr_burst(input [15:0] w);
  for(int b=15;b>=0;b--)begin #(HOLD);sda_low=!w[b];sda_high=w[b];#(PP_LOW-HOLD);scl_out=!scl_out;end
  if(scl_out)begin #(PP_LOW);scl_out=0;end
 endtask
 // Error Type M2 recovery: S 7'h7E/W, ACK or NACK ignored, HDR Exit Pattern, STOP (§5.1.10.2.3, §5.1.10.2.4).
 task automatic recover();bit ack;start();header(7'h7e,0,1,ack);hdr_exit();endtask
endmodule
Read included file: i3c_target.sv
`timescale 1ns/1ns
// I3C target (v1.0: Slave), SDR. A register map like the P8 target: mem[i] = i ^ MEM_XOR; a private write's
// first byte is the pointer. It answers 7'h7E and its dynamic address only (§5.1.2.1), takes part in ENTDAA
// while it has no address, raises In-Band Interrupts with a mandatory byte (BCR[2]) and recognises the HDR
// Exit Pattern. HOT_JOIN targets stay silent until hot_join() is called (§5.1.5).
module i3c_target #(parameter [47:0] PID=48'h0,parameter [7:0] BCR=8'h06,DCR=8'h00,parameter [6:0] STATIC_ADDR=7'h00,
  parameter [7:0] MEM_XOR=8'h00,parameter HOT_JOIN=0,parameter time T_AVAL=1000,T_IDLE=200000)
 (inout wire scl,sda,output reg sda_low=0,sda_high=0);
 // Fault switches.
 bit pid_tie_bug=0,addr_parity_wrong=0,ibi_without_mdb=0,misses_hdr_exit=0,t_bit_wrong=0,pp_too_early=0,ibi_loses_and_retries_wrong=0,restart_as_exit=0,accmst_wrong_addr=0;
 // A secondary controller's target half (BCR[7:6] = 01): controller-role request, DEFSLVS list, GETACCMST.
 bit mr_req=0,mr_en=1,accept_mastership=1,accmst_sent=0,became_current=0,my_rw=1;reg [7:0] defs[0:39];int ndefs=0;
 // Activity state (ENTASx, §5.1.3.5) and the protocol-error flag GETSTATUS reports (Table 41).
 reg [1:0] activity=0;bit perr=0;
 // Counters: SDR STARTs acted on, HDR Restart patterns seen while in HDR.
 int sdr_starts=0,hdr_restarts=0;bit poss_restart=0;time requested=0,req_gap=0;   // when, and how long after the bus went free, it last pulled SDA
 // Configuration and state. max_read: bytes returned per private read before T = 0.
 int max_read=1;reg [7:0] mdb=8'ha5;
 reg [6:0] da=0;bit has_da=0,ibi_en=1,hj_en=1,powered=!HOT_JOIN,ibi_req=0,hj_req=0;
 reg [7:0] mem[0:255];reg [7:0] ptr=0;
 int st=0,cnt=0,sent=0,errors=0;reg [8:0] sh=0;reg [63:0] daa_word;reg [7:0] tx;reg [7:0] code=0;
 bit active=0,after_s=0,arb=0,arb_lost=0,lost_this_frame=0,daa_mode=0,direct=0,code_next=0,need_ptr=1,ccc_target=0,self_start=0;
 bit hdr_mode=0,err_exit=0,write_ack_release=0,reading=0,daa_won=0,t_ack=0;int falls=0;reg [6:0] my_hdr;time free_since=0;
 bit was_ibi=0,ibi_more=0,ack_pending=0,daa_hdr=0;
 // st: 0 idle, 1 header, 2 header ACK, 3 write word, 4 read word, 5 ignore until Sr/P, 6 DAA ID, 7 DAA address, 8 DAA ACK
 localparam IDLE=0,HDR=1,HACK=2,WR=3,RD=4,IGN=5,DID=6,DAD=7,DACK=8;
 assign sda=sda_low?1'b0:sda_high?1'b1:1'bz;
 initial for(int i=0;i<256;i++)mem[i]=i[7:0]^MEM_XOR;
 task automatic release_sda();sda_low=0;sda_high=0;endtask
 task automatic raise_ibi();ibi_req=1;endtask
 task automatic request_mastership();mr_req=1;endtask
 task automatic hot_join();powered=1;hj_req=1;free_since=$time;endtask
 function automatic bit bad_addr(input [6:0] a);bad_addr=a==7'h3e||a==7'h5e||a==7'h6e||a==7'h76||a==7'h7a||a==7'h7c||a==7'h7f;endfunction
 // START or Repeated START.
 always @(negedge sda)if(scl===1'b1&&powered&&!hdr_mode&&!err_exit)begin
  after_s=!active;sdr_starts++;
  if(!active)begin daa_mode=0;direct=0;lost_this_frame=0;end
  active=1;st=HDR;cnt=0;sh=0;code_next=0;ccc_target=0;reading=0;was_ibi=0;
  // A request rides only on the header after S (§5.1.2.2); ibi_loses_and_retries_wrong also tries after Sr.
  arb=0;arb_lost=0;
  if(has_da&&ibi_req&&ibi_en&&(after_s||(ibi_loses_and_retries_wrong&&lost_this_frame)))begin arb=1;my_hdr=da;my_rw=1;end
  else if(has_da&&mr_req&&mr_en&&after_s)begin arb=1;my_hdr=da;my_rw=0;end
  else if(hj_req&&hj_en&&after_s)begin arb=1;my_hdr=7'h02;my_rw=0;end
  if(accmst_sent&&!after_s)accmst_sent=0;     // Sr after GETACCMST cancels it (§5.1.9.3.16 step 4)
 end
 // STOP.
 always @(posedge sda)if(scl===1'b1&&powered&&!hdr_mode&&!err_exit&&active)begin
  if(accmst_sent)begin accmst_sent=0;became_current=1;end
  active=0;st=IDLE;daa_mode=0;direct=0;release_sda();free_since=$time;self_start=0;
 end
 // HDR Exit Pattern detector (§5.2.1.3, Figure 27): SDA falls counted while SCL stays low; SCL high resets.
 always @(negedge sda)if((hdr_mode||err_exit)&&scl===1'b0)begin falls++;
  if(falls==4&&!misses_hdr_exit)begin hdr_mode=0;err_exit=0;active=1;st=IGN;falls=0;end
 end
 // Restart (§5.2.1.4): exactly two falls, then an SDA rise, then SCL rises. A Basic target supports no HDR mode,
 // so it notes the Restart and stays in HDR until the Exit; restart_as_exit leaves HDR on it (a fault).
 always @(posedge sda)if((hdr_mode||err_exit)&&scl===1'b0)poss_restart=(falls==2);
 always @(posedge scl)if(hdr_mode||err_exit)begin
  if(poss_restart&&hdr_mode)begin hdr_restarts++;if(restart_as_exit)begin hdr_mode=0;active=1;st=IGN;end end
  poss_restart=0;falls=0;
 end
 // Bus Available (tAVAL) or Bus Idle (tIDLE for Hot-Join): pull SDA low to request a START (§5.1.3, §5.1.5).
 always #10 if(powered&&!active&&!self_start&&scl===1'b1&&sda===1'b1&&!hdr_mode&&!err_exit)begin
  if(has_da&&(ibi_req&&ibi_en||mr_req&&mr_en)&&$time-free_since>=T_AVAL)begin self_start=1;sda_low=1;requested=$time;req_gap=$time-free_since;end
  else if(hj_req&&hj_en&&$time-free_since>=T_IDLE)begin self_start=1;sda_low=1;end
 end
 // Sample on the rising edge.
 always @(posedge scl)if(powered&&active&&!hdr_mode&&!err_exit)begin
  case(st)
  HDR:begin
   sh={sh[7:0],sda};cnt++;
   if(arb&&!arb_lost&&my_bit(cnt-1)&&sda===1'b0)begin arb_lost=1;lost_this_frame=1;end
   if(cnt==8)decode_header();
  end
  HACK:begin
   if(arb&&!arb_lost)begin
    // My request won; the controller ACKs or NACKs it.
    if(sda===1'b0)begin
     if(my_hdr==7'h02)begin hj_req=0;st=IGN;end
     else if(!my_rw)begin mr_req=0;st=IGN;end
     else begin ibi_req=0;was_ibi=1;
      if(BCR[2]&&!ibi_without_mdb)begin #2;sda_low=1;sda_high=0;tx=mdb;st=RD;cnt=0;end else st=IGN;
     end
    end else st=IGN;
    self_start=0;
   end else if(daa_hdr)begin daa_hdr=0;st=DID;cnt=0;daa_word={PID,BCR,DCR};end
   else if(write_ack_release)begin #2;release_sda();write_ack_release=0;end
  end
  WR:begin
   sh={sh[7:0],sda};cnt++;
   if(cnt==9)begin cnt=0;
    if(sh[0]!==~^sh[8:1])begin errors++;
     perr=1;if(code_next)begin err_exit=1;falls=0;st=IGN;end else st=IGN;   // S1: wait for the HDR Exit Pattern; S2: wait for Sr/P
    end else if(code_next)begin code_next=0;do_ccc(sh[8:1]);end
    else if(ccc_target)begin ccc_data(sh[8:1]);end
    else if(need_ptr)begin ptr=sh[8:1];need_ptr=0;end
    else begin mem[ptr]=sh[8:1];ptr=ptr+1;end
   end
  end
  RD:begin
   cnt++;
   if(cnt==9)begin cnt=0;#2;release_sda();
    if(ccc_target&&code==8'h91)accmst_sent=1;   // GETACCMST answered: mastership passes at the STOP
    if(!ibi_more)st=IGN;
   end
  end
  DID:begin
   if(!pid_tie_bug&&daa_word[63-cnt]&&sda===1'b0)begin st=IGN;end   // lost: withdraw until the next Sr
   cnt++;if(cnt==64&&st==DID)begin st=DAD;cnt=0;sh=0;end
  end
  DAD:begin sh={sh[7:0],sda};cnt++;if(cnt==8)begin st=DACK;daa_won=((^sh[7:0])==1'b1)^addr_parity_wrong;end end
  DACK:begin if(daa_won)begin da=sh[7:1];has_da=1;end st=IGN;end
  endcase
 end
 function automatic bit my_bit(input int i);reg [7:0] v;v={my_hdr,my_rw};my_bit=v[7-i];endfunction
 task automatic decode_header();
  reg [6:0] a;bit rw;a=sh[7:1];rw=sh[0];cnt=0;
  if(arb&&!arb_lost)begin st=HACK;end
  else if(a==7'h7e&&!rw)begin st=HACK;sda_ack(1);code_next=1;need_ptr=1;end
  else if(a==7'h7e&&rw&&daa_mode&&!has_da&&!hj_req)begin st=HACK;sda_ack(1);daa_hdr=1;end   // then 64 bits (§5.1.4.2 steps 4 to 8)
  else if(a==7'h7e&&rw&&!daa_mode)begin errors++;err_exit=1;falls=0;st=IGN;end    // S0: 7'h7E/R outside DAA
  else if(daa_mode)begin st=IGN;end                                                  // S4 or already assigned
  else if(direct&&((has_da&&a==da)||(code==8'h87&&STATIC_ADDR!=0&&a==STATIC_ADDR)))begin st=HACK;sda_ack(1);ccc_target=1;reading=rw;
   if(code==8'h86)begin has_da=0;da=0;end
   if(code>=8'h82&&code<=8'h85)activity=code-8'h82;
   if(code==8'h91&&(BCR[7:6]!=2'b01||!accept_mastership))begin ack_pending=0;st=IGN;ccc_target=0;end end
  else if(has_da&&a==da)begin st=HACK;sda_ack(1);reading=rw;need_ptr=!rw;end
  else if(bad_addr(a)&&!rw)begin errors++;err_exit=1;falls=0;st=IGN;end              // S0: broken 7'h7E/W
  else st=IGN;
 endtask
 task automatic sda_ack(input bit a);ack_pending=a;endtask
 // Drive 6 ns after the falling edge, one after the controller's 5 ns hold, so no handoff overlaps a 1 and a 0.
 always @(negedge scl)if(powered&&active&&!hdr_mode&&!err_exit)begin
  #6;
  case(st)
  HDR:begin
   if(arb&&!arb_lost)begin
    if(pp_too_early)begin sda_low=!my_bit(cnt);sda_high=my_bit(cnt);end else begin sda_low=!my_bit(cnt);sda_high=0;end
   end else if(arb&&arb_lost)release_sda();
  end
  HACK:begin
   if(arb&&!arb_lost)release_sda();
   else if(ack_pending)begin sda_low=1;sda_high=0;ack_pending=0;
    // Write: release just after the rise (handoff, §5.1.2.3.1). Read: keep low, then send data.
    if(!reading)write_ack_release=1;
   end else if(reading)begin
    st=RD;cnt=0;ibi_more=0;
    if(ccc_target)begin tx=ccc_reply(0);end else begin tx=mem[ptr];ptr=ptr+1;end
    sent=0;drive_bit(tx[7]);
   end else begin
    st=WR;cnt=0;sh=0;
   end
  end
  WR:begin
   release_sda();
   if(cnt==8&&t_bit_wrong&&!code_next&&!ccc_target)sda_low=1;   // the I2C habit: ACK the ninth bit, hold it until SCL falls
  end
  RD:begin
   if(cnt<8)drive_bit(tx[7-cnt]);
   else begin
    // T-bit: 1 = more data (continue), 0 = end of data.
    sent++;ibi_more=was_ibi?0:(ccc_target?ccc_more(sent):sent<max_read);
    sda_low=!ibi_more;sda_high=ibi_more;
   end
  end
  DID:begin sda_low=!daa_word[63-cnt];sda_high=0;end
  DAD:release_sda();
  DACK:begin sda_low=daa_won;sda_high=0;end
  IGN:release_sda();
  endcase
 end
 task automatic drive_bit(input bit v);sda_low=!v;sda_high=v;endtask
 // After a T = 1 the controller either lets the next byte come or pulls SDA low (Sr = abort).
 always @(negedge scl)if(powered&&active&&st==RD&&cnt==0&&ibi_more)begin
  if(sda===1'b0)st=IGN;
  else begin if(ccc_target)tx=ccc_reply(sent);else begin tx=mem[ptr];ptr=ptr+1;end end
 end
 task automatic do_ccc(input [7:0] c);
  code=c;direct=c[7];
  if(c==8'h07)daa_mode=1;
  else if(c==8'h06)begin has_da=0;da=0;end
  else if(c==8'h29&&STATIC_ADDR!=0&&!has_da)begin da=STATIC_ADDR;has_da=1;end
  else if(c>=8'h20&&c<=8'h27)begin hdr_mode=1;falls=0;active=0;st=IDLE;end
  else if(c==8'h00||c==8'h01)begin ccc_target=1;end     // event byte follows
  else if(c>=8'h02&&c<=8'h05)activity=c-8'h02;
  else if(c==8'h08&&BCR[7:6]==2'b01)begin ccc_target=1;ndefs=0;end   // DEFSLVS: only secondary controllers keep it
 endtask
 task automatic ccc_data(input [7:0] d);
  if(code==8'h00||code==8'h80)begin if(d[0])ibi_en=1;if(d[1])mr_en=1;if(d[3])hj_en=1;end
  else if(code==8'h01||code==8'h81)begin if(d[0])ibi_en=0;if(d[1])mr_en=0;if(d[3])hj_en=0;end
  else if(code==8'h08)begin if(ndefs<40)defs[ndefs]=d;ndefs++;end
  else if(code==8'h87||code==8'h88)begin da=d[7:1];has_da=1;end
 endtask
 function automatic [7:0] ccc_reply(input int i);
  case(code)
   8'h8d:ccc_reply=PID>>(8*(5-i));
   8'h8e:ccc_reply=BCR;
   8'h8f:ccc_reply=DCR;
   8'h90:begin ccc_reply=i==0?8'h00:{activity,perr,5'b0};if(i==1)perr=0;end   // GETSTATUS MSB, LSB (Table 41)
   8'h91:ccc_reply={da^{6'b0,accmst_wrong_addr},~^da};
   default:ccc_reply=8'hff;
  endcase
 endfunction
 function automatic bit ccc_more(input int n);ccc_more=(code==8'h8d)?n<6:(code==8'h90)?n<2:0;endfunction
endmodule
Read included file: i3c_hdr_detect.sv
`timescale 1ns/1ns
module i3c_hdr_detect(input wire scl,sda,in_hdr);
 bit misses_restart=0,restart_as_exit=0;int restarts=0,exits=0,falls=0;bit poss=0;
 always @(negedge sda)if(in_hdr&&scl===1'b0)begin falls++;if(falls==4)exits++;end
 always @(posedge sda)if(in_hdr&&scl===1'b0)poss=(falls==2);
 always @(posedge scl)begin
  if(in_hdr&&poss&&!misses_restart)begin if(restart_as_exit)exits++;else restarts++;end
  poss=0;falls=0;
 end
endmodule
Read included file: i3c_secondary.sv
`timescale 1ns/1ns
module i3c_secondary #(parameter [47:0] PID=48'h0,parameter [7:0] BCR=8'h46,DCR=8'h00)
 (inout wire scl,sda,output wire tgt_low,tgt_high,ctl_low,ctl_high);
 bit takes_scl_early=0;
 i3c_target #(.PID(PID),.BCR(BCR),.DCR(DCR)) tgt(scl,sda,tgt_low,tgt_high);
 i3c_ctrl #(.START_AS_CONTROLLER(0)) ctl(scl,sda,ctl_low,ctl_high);
 always @(posedge tgt.accmst_sent)if(takes_scl_early)ctl.scl_en=1;
 always @(posedge tgt.became_current)begin
  #12;ctl.scl_en=1;ctl.stopped=$time;ctl.daa_count=0;ctl.own_da=tgt.da;
  for(int g=5;g+3<tgt.ndefs&&g+3<40;g+=4)if(tgt.defs[g]!=0&&tgt.defs[g][7:1]!=tgt.da)begin
   ctl.daa_addr[ctl.daa_count]=tgt.defs[g][7:1];ctl.daa_dcr[ctl.daa_count]=tgt.defs[g+1];ctl.daa_bcr[ctl.daa_count]=tgt.defs[g+2];ctl.daa_count++;
  end
 end
endmodule
Read included file: i2c_legacy.sv
`timescale 1ns/1ns
module i2c_legacy #(parameter [6:0] ADDRESS=7'h50,parameter time FILTER=50,STRETCH=3000)
 (inout wire scl,sda,output wire scl_low,sda_low);
 bit no_spike_filter=0,legacy_stretches=0;int starts=0,clocks=0,pulls=0;
 wire scl_f,sda_f;assign #(FILTER) scl_f=scl;assign #(FILTER) sda_f=sda;
 tri1 lscl,lsda;wire hd;
 assign lscl=((no_spike_filter?scl:scl_f)===1'b0)?1'b0:1'bz;
 assign lsda=((no_spike_filter?sda:sda_f)===1'b0)?1'b0:1'bz;
 i2c_target #(.ADDRESS({3'b000,ADDRESS})) dev(lscl,lsda,scl_low,sda_low,hd);
 assign scl=scl_low?1'b0:1'bz;assign sda=sda_low?1'b0:1'bz;
 always @(legacy_stretches)dev.stretch_ns=legacy_stretches?STRETCH:0;
 always @(negedge lsda)if(lscl===1'b1)starts++;
 always @(posedge lscl)clocks++;
 always @(posedge sda_low)pulls++;
endmodule
Read included file: i2c_target.sv
`timescale 1ns/1ns
module i2c_target #(parameter [9:0] ADDRESS=10'h050,parameter TEN_BIT=0)
 (inout wire scl,sda,output reg scl_low=0,sda_low=0,output wire high_drive);
 bit ack_not_released=0,ack_wrong_clock=0,stretch_never_released=0,wrong_addr_ack=0,no_stop_detect=0,autoinc_wrap_bug=0,push_pull_target=0,ignores_sr=0;
 // Legal receiver responses, not faults. rx_limit counts post-address bytes,
 // including the pointer byte; -1 is unlimited. A busy address is declined once.
 bit busy_nack=0;int rx_limit=-1,rx_count=0;
 // Optional general-call software reset: UM10204 §§3.1.13–14. This model
 // restores mem[i]=i and ptr=0; ADDRESS is fixed, not a programmable register.
 // Fault switches and bench configuration are not device reset state.
 time stretch_ns=0;bit general_call=1,software_reset=1,general_selected=0,reset_pending=0;int resets=0;
 reg [7:0] mem[0:255];reg [7:0] ptr=0,shift=0,tx=0;
 int state=0,count=0,bytes=0;bit active=0,selected=0,reading=0,need_ptr=1,ack_value=0,ack_active=0,low_addr_next=0,addressed10=0,reading_ack=0;
 // state: 0 receive, 1 receiver ACK, 2 transmit, 3 controller ACK, 4 ignore.
 assign high_drive=push_pull_target&&!sda_low;
 assign sda=sda_low?1'b0:high_drive?1'b1:1'bz;assign scl=scl_low?1'b0:1'bz;
 initial for(int i=0;i<256;i++)mem[i]=i[7:0];
 function automatic [7:0] increment(input reg [7:0] p);increment=autoinc_wrap_bug?((p+1)&8'h0f):p+1'b1;endfunction
 always @(negedge sda)if(scl===1)begin
  if(!(ignores_sr&&active))begin active=1;selected=0;state=0;count=0;bytes=0;rx_count=0;shift=0;reading=0;ack_active=0;sda_low=0;low_addr_next=0;general_selected=0;reset_pending=0;end
 end
 always @(posedge sda)if(scl===1&&active&&!no_stop_detect)begin active=0;selected=0;state=0;count=0;sda_low=0;addressed10=0;end
 always @(posedge scl)if(active)begin
  if(state==0)begin
   shift={shift[6:0],sda};count++;
   if(count==8)begin
    ack_value=0;
    if(bytes==0)begin
      reading=shift[0];
      general_selected=general_call&&shift==0;
      if(general_selected)begin selected=1;low_addr_next=0;end
      else if(TEN_BIT)begin selected=(shift[7:3]==5'b11110&&shift[2:1]==ADDRESS[9:8]&&(!reading||addressed10));low_addr_next=!reading;end
      else selected=(shift[7:1]==ADDRESS[6:0]||(wrong_addr_ack&&shift[7:1]==ADDRESS[6:0]+1'b1));
      if(selected&&busy_nack)begin selected=0;busy_nack=0;end
      ack_value=selected;need_ptr=1;
    end else if(low_addr_next)begin selected=selected&&shift==ADDRESS[7:0];ack_value=selected;addressed10=selected;low_addr_next=0;end
    else if(selected)begin
      ack_value=rx_limit<0||rx_count<rx_limit;
      if(general_selected)begin
       // Unsupported general-call commands are NACKed, never register writes.
       ack_value=ack_value&&bytes==1&&software_reset&&shift==8'h06;
       reset_pending=ack_value;
      end else if(ack_value)begin rx_count++;if(need_ptr)begin ptr=shift;need_ptr=0;end else begin mem[ptr]=shift;ptr=increment(ptr);end end
      if(!ack_value)selected=0;
    end
    bytes++;state=1;ack_active=0;count=0;
   end
  end else if(state==2)begin count++;if(count==8)begin state=3;count=0;reading_ack=0;end end
  else if(state==3&&reading_ack)begin if(sda===0)begin ptr=increment(ptr);tx=mem[ptr];state=2;count=0;end else state=4;end
 end
 always @(negedge scl)if(active)begin
  // Deliberate early-ACK fault: after seven samples, pull for clock 8.
  // The address prefix is already known; its R/W bit has not arrived yet.
  if(state==0&&count==7&&ack_wrong_clock)begin
   if(bytes==0)sda_low=!busy_nack&&((general_call&&shift[6:0]==0)||(TEN_BIT?(shift[6:2]==5'b11110&&shift[1:0]==ADDRESS[9:8]):(shift[6:0]==ADDRESS[6:0]||(wrong_addr_ack&&shift[6:0]==ADDRESS[6:0]+1'b1))));
   else sda_low=selected&&(rx_limit<0||rx_count<rx_limit);
  end else if(state==1)begin
   if(!ack_active)begin
    sda_low=ack_value&&!ack_wrong_clock;ack_active=1;
   end else begin
    sda_low=ack_not_released&&ack_value;ack_active=0;
    if(reset_pending)begin
     // Finish the ninth ACK clock before resetting and releasing the bus.
     for(int i=0;i<256;i++)mem[i]=i[7:0];
     ptr=0;shift=0;tx=0;count=0;bytes=0;rx_count=0;need_ptr=1;
     selected=0;reading=0;reading_ack=0;low_addr_next=0;addressed10=0;
     general_selected=0;reset_pending=0;resets++;sda_low=0;scl_low=0;state=4;
    end else if(!selected)state=4;
    else if(reading)begin state=2;tx=mem[ptr];count=0;sda_low=!tx[7];end
    else state=0;
    // Byte-level stretch after the ACK, as in UM10204 §3.1.9. Prepare
    // outgoing data before releasing SCL; overlap the controller's LOW.
    if(selected&&ack_value&&(stretch_ns>0||stretch_never_released))begin scl_low=1;if(!stretch_never_released)begin #(stretch_ns);scl_low=0;end end
   end
  end else if(state==2)sda_low=!tx[7-count];
  else if(state==3)begin sda_low=0;reading_ack=1;end
  else if(state==4)sda_low=0;
 end
endmodule
Read included file: i3c_mon.sv
`timescale 1ns/1ns
// Bus decoder: one line per frame, from SCL and SDA only. The ninth bit is an ACK after an address and a
// T-bit after a data word (§5.1.2.3); frames to the legacy address are decoded as I2C. phase (an output
// the checker reads): 0 idle, 1 header after S, 2 header after Sr, 3 ACK, 4 write word, 5 read word,
// 6 DAA ID, 7 DAA address, 8 DAA ACK, 9 HDR, 10 I2C byte, 11 I2C ACK, 12 waiting for Sr or P.
module i3c_mon #(parameter [6:0] LEGACY=7'h50)(input wire scl,sda,output reg [3:0] phase=0);
 bit trace=0,active=0,daa=0,hdr=0,legacy=0,code_next=0;int cnt=0,frames=0,starts=0,exits=0,falls=0;
 reg [63:0] sh=0;reg [6:0] addr=0;bit rw=0;string text="";
 always @(negedge sda)if(scl===1'b1&&!hdr)begin
  if(active)text={text," Sr"};else begin text=" S";daa=0;legacy=0;end
  phase=active?2:1;active=1;cnt=0;sh=0;starts++;code_next=0;
 end
 always @(posedge sda)if(scl===1'b1&&active&&!hdr)begin
  text={text," P"};if(trace)$display("%s%s",legacy?"I2C":"I3C",text);text="";active=0;phase=0;frames++;
 end
 // HDR Exit Pattern: four SDA falls while SCL stays low (§5.2.1.1). Seen in or out of HDR (M2 recovery).
 always @(negedge sda)if(scl===1'b0&&(hdr||active))begin falls++;if(falls==4)begin hdr=0;falls=0;text={text," EXIT"};exits++;phase=12;end end
 bit poss=0;int restarts=0;
 always @(posedge sda)if(scl===1'b0&&hdr)poss=(falls==2);
 always @(posedge scl)begin if(hdr&&poss)begin text={text," RESTART"};restarts++;end poss=0;falls=0;end
 always @(posedge scl)if(active&&!hdr)begin
  sh={sh[62:0],sda};cnt++;
  case(phase)
  1,2:if(cnt==8)begin addr=sh[7:1];rw=sh[0];cnt=0;
     if(phase==1&&addr==LEGACY)legacy=1;
     text={text,$sformatf(" %02h %s",addr,rw?"R":"W")};phase=legacy?11:3;end
  3:begin cnt=0;text={text,sda===1'b0 ? " A" : sda===1'b1 ? " NA" : " A=x"};
     if(sda!==1'b0)phase=12;
     else if(daa&&addr==7'h7e&&rw)phase=6;
     else if(!rw)begin phase=4;code_next=(addr==7'h7e);end
     else phase=5;
    end
  4,5:if(cnt==9)begin cnt=0;text={text,$sformatf(" %02h T%0d",sh[8:1],sh[0])};
     if(phase==4&&code_next)begin code_next=0;
      if(sh[8:1]==8'h07)daa=1;
      if(sh[8:1]>=8'h20&&sh[8:1]<=8'h27)begin hdr=1;falls=0;phase=9;text={text," HDR"};end
     end
     if(phase==5&&sh[0]===1'b0)phase=12;
    end
  6:if(cnt==64)begin cnt=0;text={text,$sformatf(" id %012h bcr %02h dcr %02h",sh[63:16],sh[15:8],sh[7:0])};phase=7;end
  7:if(cnt==8)begin cnt=0;text={text,$sformatf(" da %02h par %0d",sh[7:1],sh[0])};phase=8;end
  8:begin cnt=0;text={text,sda===1'b0 ? " A" : sda===1'b1 ? " NA" : " A=x"};phase=12;end
  10:if(cnt==8)begin cnt=0;text={text,$sformatf(" %02h",sh[7:0])};phase=11;end
  11:begin cnt=0;text={text,sda===1'b0 ? " A" : sda===1'b1 ? " NA" : " A=x"};phase=10;end
  default:cnt=0;
  endcase
 end
endmodule
Read included file: i3c_checker.sv
`timescale 1ns/1ns
// Wire rules with the driver view. Index 0 of lows/highs is the controller; the others are targets.
// Each rule prints at most once per frame (S to P) and counts every time it fires.
//  no_pp_contention   one device drives SDA high while another pulls it low (§5.1.2.3: handoffs are specified)
//  od_in_header       a device drives SDA high in the arbitrable header after S or its ACK (§5.1.2.2.1)
//  pp_in_data         the controller sends a 1 in a write word without driving it high (§5.1.2: data is push-pull)
//  t_bit              the ninth bit of a write word is not XOR(data, 1) (§5.1.2.3.3)
//  ibi_mdb_present    no target drives the byte after an accepted header after S with R (BCR[2] = 1, §5.1.6.2)
//  legacy_no_stretch  a legacy target pulls SCL low (MIPI v1.0 Table 56: no clock stretching on I3C)
module i3c_checker #(parameter N=4)(input wire scl,sda,input wire [N-1:0] lows,highs,input wire [3:0] phase,input wire legacy_scl_low);
 int broken=0,contention=0,od_header=0,pp_data=0,tbit=0,mdb=0,stretch=0;bit quiet=0;   // quiet: count, do not print
 reg [5:0] said=0;reg [3:0] ph=0,prev=0;reg [8:0] w=0;int k=0,hk=0,mk=0;bit arb_ack=0,hdr_rw=0,mdb_seen=0,mdb_watch=0,viol=0;
 task automatic bad(input int i,input string rule);broken++;if(!said[i]&&!quiet)$display("I3C CHECK %s at %0t",rule,$time);said[i]=1;endtask
 always @(negedge sda)if(scl===1'b1)begin k=0;viol=0;end
 always @(posedge sda)if(scl===1'b1)said=0;
 always @(negedge scl)begin prev=ph;ph=phase;if(ph!=prev)begin k=0;hk=0;end
  if(ph!=prev&&ph==3)arb_ack=(prev==1);
  if(ph==5&&prev==3&&arb_ack&&hdr_rw)begin mdb_watch=1;mdb_seen=0;mk=0;end
 end
 always @(lows or highs)begin #1;if(|lows&&|highs)begin contention++;bad(0,"no_pp_contention");end end
 always @(posedge scl)begin
  if((ph==1||(ph==3&&arb_ack))&&|highs)begin od_header++;bad(1,"od_in_header");end
  if(ph==1)begin hk++;if(hk==8)hdr_rw=(sda===1'b1);end
  if(ph==4)begin k++;w={w[7:0],sda};
   if(k<=8&&sda===1'b1&&!highs[0])viol=1;
   if(k==9)begin if(viol)begin pp_data++;bad(2,"pp_in_data");end if(w[0]!==~^w[8:1])begin tbit++;bad(3,"t_bit");end k=0;viol=0;end
  end
  if(mdb_watch)begin mk++;if(|lows[N-1:1]|||highs[N-1:1])mdb_seen=1;
   if(mk==8)begin mdb_watch=0;if(!mdb_seen)begin mdb++;bad(4,"ibi_mdb_present");end end
  end
 end
 always @(posedge legacy_scl_low)begin stretch++;bad(5,"legacy_no_stretch");end
 // A missing mandatory byte turns the controller's handoff release into a STOP inside the ACK slot.
 always @(posedge sda)if(scl===1'b1&&ph==3&&arb_ack&&hdr_rw)begin mdb++;bad(4,"ibi_mdb_present");end
endmodule
Spot the bugEach option is the body of pulls, a target's rule for one DAA bit. An option passes when target a, the lower ID, wins cleanly, target b loses, and the line carries a's bits. Which option fails?
ConceptWhy can I3C assign addresses on the wire when I2C cannot?

Remember

  • ENTDAA rounds arbitrate each target's provisional ID, BCR and DCR, 64 bits in open drain.
  • The lowest 64-bit value wins each round, so the ID decides the order.
  • The controller sends seven address bits and an odd-parity bit; the winner ACKs only a good parity.
  • A target that ignores its loss can share an address; the controller detects it by counting.
  • SETDASA and SETAASA turn static addresses into dynamic ones without arbitration.

Practice