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).
- Enter
ENTDAA is a broadcast CCC with no data; targets that already have an address stay out (MIPI §5.1.9.3.4).
- 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).
- Assign
Seven address bits plus PAR = NOT XOR of the seven; the winner ACKs if the parity checks (step 9).
- 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
- Cycle 10
Both IDs have a 0 here: both targets pull, open drain. The controller only clocks.
- 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).
- 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
| Round | Provisional ID | BCR | DCR | Assigned |
|---|---|---|---|---|
| 1 | 0x02aa01230000 | 0x06 | 0x00 | 0x08 |
| 2 | 0x02aa01231000 | 0x06 | 0x00 | 0x09 |
| 3 | 0x02aa01232000 | 0x06 | 0x00 | 0x0a |
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
| Twin | Measured |
|---|---|
target2.pid_tie_bug = 1: keeps sending after it loses | CTRL 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 check | CTRL 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
| Method | Who can use it | Frame | Measured |
|---|---|---|---|
| ENTDAA (0x07) | every target without an address | S 7E/W 07, then rounds | I3C 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 time | 7E/W 87, Sr static/W, DA<<1 | I3C S 7e W A 87 T1 Sr 31 W A 16 T0 P |
| SETAASA (0x29) | targets that support it: static becomes dynamic | S 7E/W 29, P | I3C 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 bit | Name | Meaning (Table 6) | Target, 0x06 | Secondary controller, 0x46 |
|---|---|---|---|---|
| 7:6 | Device role | 00 target, 01 controller, 10 and 11 reserved | 00 | 01 |
| 5 | reserved | 0 | 0 | 0 |
| 4 | Bridge | 1: a bridge device | 0 | 0 |
| 3 | Offline capable | 1: may not always answer | 0 | 0 |
| 2 | IBI payload | 1: a mandatory byte follows each accepted IBI | 1 | 1 |
| 1 | IBI request capable | 1: may raise IBIs | 1 | 1 |
| 0 | Max data speed limit | 1: ask with GETMXDS | 0 | 0 |
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.
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 PRead 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
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
- Withdraw After Losing DAA Arbitration Fix · Medium