diff --git a/exi_bba/bba_register_file.py b/exi_bba/bba_register_file.py index 2267901..b424b95 100644 --- a/exi_bba/bba_register_file.py +++ b/exi_bba/bba_register_file.py @@ -15,8 +15,7 @@ Addresses 0x0100–0x1FFF : SPRAM ring buffer (sync domain, prefetch FIFOs). """ from amaranth import * -from amaranth.lib.cdc import PulseSynchronizer -from amaranth.lib.fifo import AsyncFIFO +from amaranth.lib.fifo import SyncFIFO __all__ = ["BBARegisterFile"] @@ -120,12 +119,17 @@ class BBARegisterFile(Elaboratable): m = Module() # ── CDC FIFOs ──────────────────────────────────────────────────── - spram_req = AsyncFIFO(width=16, depth=4, w_domain="exi", r_domain="sync") - spram_rsp = AsyncFIFO(width=8, depth=4, w_domain="sync", r_domain="exi") - tx_bytes = AsyncFIFO(width=8, depth=16, w_domain="exi", r_domain="sync") - tx_ctrl = AsyncFIFO(width=16, depth=4, w_domain="exi", r_domain="sync") - rx_wptr = AsyncFIFO(width=8, depth=4, w_domain="sync", r_domain="exi") - rx_rptr = AsyncFIFO(width=8, depth=4, w_domain="exi", r_domain="sync") + # Plain SyncFIFOs: `exi` and `sync` were always the SAME clock net + # (bba_top wired ClockSignal("sync") to ClockSignal("exi")), so the + # AsyncFIFOs between them bought nothing and cost ~3 cycles each way. + # On the SPRAM path that latency exceeded the protocol's turnaround + # byte and shifted every DMA read by one byte. One slow domain now. + spram_req = SyncFIFO(width=16, depth=4) + spram_rsp = SyncFIFO(width=8, depth=4) + tx_bytes = SyncFIFO(width=8, depth=16) + tx_ctrl = SyncFIFO(width=16, depth=4) + rx_wptr = SyncFIFO(width=8, depth=4) + rx_rptr = SyncFIFO(width=8, depth=4) m.submodules.spram_req = spram_req m.submodules.spram_rsp = spram_rsp @@ -162,19 +166,11 @@ class BBARegisterFile(Elaboratable): ] # ── PulseSynchronizers ─────────────────────────────────────────── - ncra_rst_ps = PulseSynchronizer(i_domain="exi", o_domain="sync") - rx_irq_ps = PulseSynchronizer(i_domain="sync", o_domain="exi") - tx_irq_ps = PulseSynchronizer(i_domain="sync", o_domain="exi") - - m.submodules.ncra_rst_ps = ncra_rst_ps - m.submodules.rx_irq_ps = rx_irq_ps - m.submodules.tx_irq_ps = tx_irq_ps - - m.d.comb += [ - self.ncra_rst_o .eq(ncra_rst_ps.o), - rx_irq_ps.i .eq(self.rx_irq_i), - tx_irq_ps.i .eq(self.tx_irq_i), - ] + # Single slow domain, so these are plain wires rather than + # PulseSynchronizers. A pulse generated here is already in the only + # domain that exists. + ncra_rst_pulse = Signal() + m.d.comb += self.ncra_rst_o.eq(ncra_rst_pulse) # ── Register file (sparse individual Signals, exi domain) ──────── # Only the registers actually read/written by the GC or sync domain. @@ -287,15 +283,19 @@ class BBARegisterFile(Elaboratable): m.d.comb += rd_val.eq(reg_rdval) # ── Default strobes ────────────────────────────────────────────── - m.d.exi += [ - spram_req.w_en .eq(0), + m.d.sync += [ tx_bytes.w_en .eq(0), tx_ctrl.w_en .eq(0), rx_rptr.w_en .eq(0), rx_wptr.r_en .eq(0), - ncra_rst_ps.i .eq(0), + ncra_rst_pulse .eq(0), ] + # The SPRAM request write port is COMBINATIONAL (see RHDR_LO): a + # registered w_en costs a 24 MHz cycle the turnaround byte cannot + # spare. Default it off here and let the states assert it. m.d.comb += [ + spram_req.w_en .eq(0), + spram_req.w_data.eq(0), self.rx_en .eq(0), self.tx_en .eq(0), self.tx_data.eq(0), @@ -308,7 +308,7 @@ class BBARegisterFile(Elaboratable): # The SPI bit cadence lives in the capture domain; here we just consume # received bytes and, for reads, push response bytes into tx_fifo during # the EXI clock-idle gap before the GC clocks the data phase. - with m.FSM(domain="exi", name="exi_fsm"): + with m.FSM(domain="sync", name="exi_fsm"): with m.State("HEADER0"): # Byte 0 selects the space and direction. The REAL protocol @@ -316,13 +316,13 @@ class BBARegisterFile(Elaboratable): # clocks as many data bytes as it wants and ends with CS. with m.If(self.rx_rdy): m.d.comb += self.rx_en.eq(1) - m.d.exi += [is_write.eq(self.rx_data[6]), prefetched.eq(0), + m.d.sync += [is_write.eq(self.rx_data[6]), prefetched.eq(0), byte_ctr.eq(0), sp_req.eq(0), outstanding.eq(0)] with m.If(self.rx_data[7]): # register space - m.d.exi += cmd_space.eq(0) + m.d.sync += cmd_space.eq(0) m.next = "RHDR_HI" with m.Else(): # command space - m.d.exi += [cmd_space.eq(1), + m.d.sync += [cmd_space.eq(1), cmd_reg.eq(self.rx_data[0:6]), addr.eq(self.rx_data[0:6]), eff_addr.eq(self.rx_data[0:6])] @@ -333,14 +333,14 @@ class BBARegisterFile(Elaboratable): with m.If((self.rx_data == 0x00) & id_phase & self.tx_rdy): m.d.comb += [self.tx_data.eq(0x04), self.tx_en.eq(1)] - m.d.exi += byte_ctr.eq(1) + m.d.sync += byte_ctr.eq(1) m.next = "CHDR_1" # ── register space: [0x80|w<<6, ah, al, 0x00] ────────────────── with m.State("RHDR_HI"): with m.If(self.rx_rdy): m.d.comb += self.rx_en.eq(1) - m.d.exi += addr[8:16].eq(self.rx_data) + m.d.sync += addr[8:16].eq(self.rx_data) m.next = "RHDR_LO" with m.State("RHDR_LO"): @@ -353,13 +353,18 @@ class BBARegisterFile(Elaboratable): with m.If(self.rx_rdy): m.d.comb += self.rx_en.eq(1) full = Cat(self.rx_data, addr[8:16]) # 16-bit address - m.d.exi += [addr.eq(full), eff_addr.eq(full), + m.d.sync += [addr.eq(full), eff_addr.eq(full), sp_addr.eq(full)] with m.If(~is_write & (full >= 0x100) & spram_req.w_rdy): - m.d.exi += [spram_req.w_data.eq(full), - spram_req.w_en.eq(1), - sp_addr.eq(full + 1), - outstanding.eq(1)] + # COMBINATIONAL write: the turnaround byte gives only + # ~296 ns and the SPRAM round trip needs most of it, so + # a registered w_en (one more 24 MHz cycle, ~42 ns) is + # enough to miss the window and shift the whole DMA + # read by a byte. Same-domain SyncFIFO now, so this is + # legal. + m.d.comb += [spram_req.w_data.eq(full), + spram_req.w_en.eq(1)] + m.d.sync += [sp_addr.eq(full + 1), outstanding.eq(1)] m.next = "RHDR_PAD" with m.State("RHDR_PAD"): @@ -382,7 +387,7 @@ class BBARegisterFile(Elaboratable): # arrived, and repeating it would consume the register # stream early (symptom: reads shifted by a byte). m.d.comb += [self.tx_data.eq(rd_val), self.tx_en.eq(1)] - m.d.exi += [eff_addr.eq(eff_addr + 1), prefetched.eq(1)] + m.d.sync += [eff_addr.eq(eff_addr + 1), prefetched.eq(1)] # ── command space: [reg, 00, 00, data] in ONE 4-byte transfer ── with m.State("CHDR_1"): @@ -405,11 +410,11 @@ class BBARegisterFile(Elaboratable): # NOT one-shot -- the FIFO is only 2 deep, and stopping # after one byte leaves a hole in the next slot. m.d.comb += [self.tx_data.eq(devid), self.tx_en.eq(1)] - m.d.exi += byte_ctr.eq(byte_ctr + 1) + m.d.sync += byte_ctr.eq(byte_ctr + 1) with m.Elif(~is_write & self.tx_rdy & ~prefetched): # Ordinary command read: response lands in byte slot 3. m.d.comb += [self.tx_data.eq(rd_val), self.tx_en.eq(1)] - m.d.exi += prefetched.eq(1) + m.d.sync += prefetched.eq(1) with m.State("CHDR_2"): with m.If(self.rx_rdy): @@ -420,8 +425,8 @@ class BBARegisterFile(Elaboratable): with m.If(self.rx_rdy): m.d.comb += self.rx_en.eq(1) with m.If(is_write): - m.d.exi += cmd_wr.eq(1) # applied below - m.d.exi += cmd_wdata.eq(self.rx_data) + m.d.sync += cmd_wr.eq(1) # applied below + m.d.sync += cmd_wdata.eq(self.rx_data) m.next = "TRAILER" with m.State("TRAILER"): @@ -437,37 +442,37 @@ class BBARegisterFile(Elaboratable): m.d.comb += self.rx_en.eq(1) with m.Switch(rd_sel): with m.Case(_NCRA): - m.d.exi += r_ncra.eq(self.rx_data) + m.d.sync += r_ncra.eq(self.rx_data) with m.If(self.rx_data[0]): - m.d.exi += r_ncra[0].eq(0) # RESET self-clears - m.d.exi += ncra_rst_ps.i.eq(1) - m.d.exi += id_phase.eq(0) + m.d.sync += r_ncra[0].eq(0) # RESET self-clears + m.d.sync += ncra_rst_pulse.eq(1) + m.d.sync += id_phase.eq(0) # Fallback trigger: TWD_HI normally queues the # frame (see _TWD_HI). Only fire here if it did # not, so software that skips TWD still transmits # and we never double-queue one frame. with m.If(self.rx_data[1:3].any() & ~tx_queued): with m.If(tx_ctrl.w_rdy): - m.d.exi += tx_ctrl.w_data.eq(tx_frame_len) - m.d.exi += tx_ctrl.w_en.eq(1) - m.d.exi += tx_queued.eq(1) + m.d.sync += tx_ctrl.w_data.eq(tx_frame_len) + m.d.sync += tx_ctrl.w_en.eq(1) + m.d.sync += tx_queued.eq(1) with m.Case(_IMR): - m.d.exi += r_imr.eq(self.rx_data) + m.d.sync += r_imr.eq(self.rx_data) with m.Case(_IR): - m.d.exi += r_ir.eq(r_ir & ~self.rx_data) # write-1-clear + m.d.sync += r_ir.eq(r_ir & ~self.rx_data) # write-1-clear with m.Case(_RRP_LO): - m.d.exi += r_rrp_lo.eq(self.rx_data) + m.d.sync += r_rrp_lo.eq(self.rx_data) with m.If(rx_rptr.w_rdy): - m.d.exi += rx_rptr.w_data.eq(self.rx_data) - m.d.exi += rx_rptr.w_en.eq(1) + m.d.sync += rx_rptr.w_data.eq(self.rx_data) + m.d.sync += rx_rptr.w_en.eq(1) with m.Case(_PAR0, _PAR1, _PAR2, _PAR3, _PAR4, _PAR5): - m.d.exi += r_par[eff_addr[0:3]].eq(self.rx_data) + m.d.sync += r_par[eff_addr[0:3]].eq(self.rx_data) with m.Case(_TWD_LO): - m.d.exi += r_twd_lo.eq(self.rx_data) - m.d.exi += tx_frame_len[0:8].eq(self.rx_data) + m.d.sync += r_twd_lo.eq(self.rx_data) + m.d.sync += tx_frame_len[0:8].eq(self.rx_data) with m.Case(_TWD_HI): - m.d.exi += r_twd_hi.eq(self.rx_data) - m.d.exi += tx_frame_len[8:16].eq(self.rx_data) + m.d.sync += r_twd_hi.eq(self.rx_data) + m.d.sync += tx_frame_len[8:16].eq(self.rx_data) # Hand the length to TXFrameDrain NOW, not at the ST # trigger. The drain blocks on tx_bytes_r_rdy, so # starting it here lets it consume bytes AS the GC @@ -476,16 +481,16 @@ class BBARegisterFile(Elaboratable): # to fit in the 16-deep FIFO, and anything past 16 # bytes was silently dropped (hardware 2026-09-30: # frames truncated at exactly 16 bytes on the wire). - m.d.exi += twd_armed.eq(1) + m.d.sync += twd_armed.eq(1) with m.Case(_TXDATA): with m.If(tx_bytes.w_rdy): - m.d.exi += tx_bytes.w_data.eq(self.rx_data) - m.d.exi += tx_bytes.w_en.eq(1) + m.d.sync += tx_bytes.w_data.eq(self.rx_data) + m.d.sync += tx_bytes.w_en.eq(1) # All other addresses silently ignored # No length field in the real protocol: consume data # bytes until CS rises. - m.d.exi += eff_addr.eq(eff_addr + 1) + m.d.sync += eff_addr.eq(eff_addr + 1) with m.If(~self.cs_active): m.next = "HEADER0" @@ -494,7 +499,7 @@ class BBARegisterFile(Elaboratable): # path. The real protocol has no length field. with m.If(self.tx_rdy & self.cs_active): m.d.comb += [self.tx_data.eq(rd_val), self.tx_en.eq(1)] - m.d.exi += eff_addr.eq(eff_addr + 1) + m.d.sync += eff_addr.eq(eff_addr + 1) with m.If(self.rx_rdy): m.d.comb += self.rx_en.eq(1) # drain dummies with m.If(~self.cs_active): @@ -504,7 +509,7 @@ class BBARegisterFile(Elaboratable): # Stream 04 02 02 00 until CS rises. with m.If(self.tx_rdy & self.cs_active): m.d.comb += [self.tx_data.eq(devid), self.tx_en.eq(1)] - m.d.exi += byte_ctr.eq(byte_ctr + 1) + m.d.sync += byte_ctr.eq(byte_ctr + 1) with m.If(self.rx_rdy): m.d.comb += self.rx_en.eq(1) with m.If(~self.cs_active): @@ -523,14 +528,14 @@ class BBARegisterFile(Elaboratable): m.d.comb += pop.eq(spram_rsp.r_rdy & self.tx_rdy) with m.If(issue): - m.d.exi += spram_req.w_data.eq(sp_addr) - m.d.exi += spram_req.w_en.eq(1) - m.d.exi += sp_addr.eq(sp_addr + 1) + m.d.comb += spram_req.w_data.eq(sp_addr) + m.d.comb += spram_req.w_en.eq(1) + m.d.sync += sp_addr.eq(sp_addr + 1) with m.If(pop): m.d.comb += self.tx_data.eq(spram_rsp.r_data) m.d.comb += self.tx_en.eq(1) m.d.comb += spram_rsp.r_en.eq(1) - m.d.exi += outstanding.eq(outstanding + issue - pop) + m.d.sync += outstanding.eq(outstanding + issue - pop) with m.If(self.rx_rdy): m.d.comb += self.rx_en.eq(1) # drain dummy bytes @@ -544,7 +549,7 @@ class BBARegisterFile(Elaboratable): # ExiCapture on the next CS assertion. with m.If(spram_rsp.r_rdy): m.d.comb += spram_rsp.r_en.eq(1) - m.d.exi += outstanding.eq(outstanding - 1) + m.d.sync += outstanding.eq(outstanding - 1) with m.If(self.rx_rdy): m.d.comb += self.rx_en.eq(1) with m.If((outstanding == 0) & ~self.rx_rdy & ~spram_rsp.r_rdy): @@ -552,35 +557,35 @@ class BBARegisterFile(Elaboratable): # ── Command-space write, applied the cycle after CHDR_3 ────────── with m.If(cmd_wr): - m.d.exi += cmd_wr.eq(0) + m.d.sync += cmd_wr.eq(0) with m.Switch(cmd_reg): - with m.Case(0x02): m.d.exi += r_cmd_imr.eq(cmd_wdata) - with m.Case(0x03): m.d.exi += r_cmd_ir.eq(r_cmd_ir & ~cmd_wdata) + with m.Case(0x02): m.d.sync += r_cmd_imr.eq(cmd_wdata) + with m.Case(0x03): m.d.sync += r_cmd_ir.eq(r_cmd_ir & ~cmd_wdata) # ── Interrupt output ───────────────────────────────────────────── - m.d.exi += self.exi_int_n.eq(~(r_ir & r_imr).any()) + m.d.sync += self.exi_int_n.eq(~(r_ir & r_imr).any()) # ── Consume RWP updates from sync domain ────────────────────────── with m.If(rx_wptr.r_rdy): - m.d.exi += rx_wptr.r_en.eq(1) - m.d.exi += r_rwp_lo.eq(rx_wptr.r_data) + m.d.sync += rx_wptr.r_en.eq(1) + m.d.sync += r_rwp_lo.eq(rx_wptr.r_data) # ── PulseSynchronizer arrivals ──────────────────────────────────── - with m.If(rx_irq_ps.o): - m.d.exi += r_ir[1].eq(1) # RI bit + with m.If(self.rx_irq_i): + m.d.sync += r_ir[1].eq(1) # RI bit # ── Deferred TX queue (armed by a TWD_HI write) ────────────────── # One cycle after TWD_HI so tx_frame_len holds both bytes. Held until # tx_ctrl has room so a full control FIFO cannot drop the trigger. with m.If(twd_armed & tx_ctrl.w_rdy): - m.d.exi += tx_ctrl.w_data.eq(tx_frame_len) - m.d.exi += tx_ctrl.w_en.eq(1) - m.d.exi += twd_armed.eq(0) - m.d.exi += tx_queued.eq(1) + m.d.sync += tx_ctrl.w_data.eq(tx_frame_len) + m.d.sync += tx_ctrl.w_en.eq(1) + m.d.sync += twd_armed.eq(0) + m.d.sync += tx_queued.eq(1) - with m.If(tx_irq_ps.o): - m.d.exi += r_ir[2].eq(1) # TI bit - m.d.exi += r_ncra[1:3].eq(0) # clear ST bits - m.d.exi += tx_queued.eq(0) # ready for the next frame + with m.If(self.tx_irq_i): + m.d.sync += r_ir[2].eq(1) # TI bit + m.d.sync += r_ncra[1:3].eq(0) # clear ST bits + m.d.sync += tx_queued.eq(0) # ready for the next frame return m @@ -615,7 +620,7 @@ if __name__ == "__main__": while True: en = ctx.get(reg.rx_en) sample_tx(ctx) - await ctx.tick("exi") + await ctx.tick("sync") if en: break ctx.set(reg.rx_rdy, 0) @@ -630,7 +635,7 @@ if __name__ == "__main__": break if ctx.get(reg.tx_en): out.append(ctx.get(reg.tx_data)) - await ctx.tick("exi") + await ctx.tick("sync") return out # Real protocol (docs/exi_protocol_real.md): register space is a 4-byte @@ -645,7 +650,7 @@ if __name__ == "__main__": await push_rx(ctx, 0x00) # turnaround / prefetch window result = await collect_tx(ctx, length) ctx.set(reg.cs_active, 0) - await ctx.tick("exi").repeat(6) + await ctx.tick("sync").repeat(6) return result async def exi_write(ctx, addr, data): @@ -657,7 +662,7 @@ if __name__ == "__main__": for b in data: await push_rx(ctx, b) ctx.set(reg.cs_active, 0) - await ctx.tick("exi").repeat(6) + await ctx.tick("sync").repeat(6) async def exi_devid(ctx): """EXI_GetID: write 0x0000 (2 bytes), then clock 4 read bytes.""" @@ -667,7 +672,7 @@ if __name__ == "__main__": await push_rx(ctx, 0x00) result = await collect_tx(ctx, 4) ctx.set(reg.cs_active, 0) - await ctx.tick("exi").repeat(6) + await ctx.tick("sync").repeat(6) return result # SPRAM contents the streaming-read test reads back (byte i = 0xA0+i). @@ -701,51 +706,51 @@ if __name__ == "__main__": async def testbench(ctx): ctx.set(reg.tx_rdy, 1) # tx_fifo always has room in this model - await ctx.tick("exi").repeat(8) + await ctx.tick("sync").repeat(8) # T1: Device ID (addr=0, 4-byte read) result = await exi_devid(ctx) if result != _DEVICE_ID: errors.append(f"T1 device ID: expected {_DEVICE_ID}, got {result}") print(f"T1 device ID: {[f'0x{b:02X}' for b in result]}") - await ctx.tick("exi").repeat(4) + await ctx.tick("sync").repeat(4) # T2: Write and read back PAR0-PAR3 await exi_write(ctx, _PAR0, [0xDE, 0xAD, 0xBE, 0xEF]) - await ctx.tick("exi").repeat(4) + await ctx.tick("sync").repeat(4) result = await exi_read(ctx, _PAR0, length=4) if result != [0xDE, 0xAD, 0xBE, 0xEF]: errors.append(f"T2 PAR readback: {result}") print(f"T2 PAR0-3: {[f'0x{b:02X}' for b in result]}") - await ctx.tick("exi").repeat(4) + await ctx.tick("sync").repeat(4) # T3: NWAYS hardcoded 0x17 result = await exi_read(ctx, _NWAYS, length=1) if result != [0x17]: errors.append(f"T3 NWAYS: expected 0x17, got {result}") print(f"T3 NWAYS: 0x{result[0]:02X}") - await ctx.tick("exi").repeat(4) + await ctx.tick("sync").repeat(4) # T4: HIPR hardcoded 0x01 result = await exi_read(ctx, _HIPR, length=1) if result != [0x01]: errors.append(f"T4 HIPR: expected 0x01, got {result}") print(f"T4 HIPR: 0x{result[0]:02X}") - await ctx.tick("exi").repeat(4) + await ctx.tick("sync").repeat(4) # T5: IMR write, rx_irq pulse, INT_N asserts, then IR clear await exi_write(ctx, _IMR, [0x02]) # enable RI (bit 1) - await ctx.tick("exi").repeat(4) + await ctx.tick("sync").repeat(4) ctx.set(reg.rx_irq_i, 1) await ctx.tick("sync").repeat(1) ctx.set(reg.rx_irq_i, 0) - await ctx.tick("exi").repeat(12) # wait for PS propagation + await ctx.tick("sync").repeat(12) # wait for PS propagation int_n = ctx.get(reg.exi_int_n) if int_n != 0: errors.append(f"T5 INT_N after RI: expected 0, got {int_n}") print(f"T5 INT_N after RI pulse: {int_n} (want 0)") await exi_write(ctx, _IR, [0x02]) # write-1-to-clear RI - await ctx.tick("exi").repeat(4) + await ctx.tick("sync").repeat(4) int_n = ctx.get(reg.exi_int_n) if int_n != 1: errors.append(f"T5 INT_N after clear: expected 1, got {int_n}") @@ -767,9 +772,9 @@ if __name__ == "__main__": got.append(ctx.get(reg.tx_data)) if len(got) >= N: break - await ctx.tick("exi") + await ctx.tick("sync") ctx.set(reg.cs_active, 0) # end the transaction - await ctx.tick("exi").repeat(40) # let SPRAM_END drain/clean up + await ctx.tick("sync").repeat(40) # let SPRAM_END drain/clean up want = [spram_mem[0x100 + i] for i in range(N)] print(f"T6 DMA read {N}B: {[f'0x{b:02X}' for b in got]}") if got != want: @@ -783,7 +788,6 @@ if __name__ == "__main__": print(f"T7 NWAYS after DMA read: 0x{result[0]:02X}") sim = Simulator(reg) - sim.add_clock(Period(MHz=24), domain="exi") sim.add_clock(Period(MHz=24), domain="sync") sim.add_testbench(testbench) sim.add_process(spram_model) diff --git a/exi_bba/bba_top.py b/exi_bba/bba_top.py index ca8e363..a05b731 100644 --- a/exi_bba/bba_top.py +++ b/exi_bba/bba_top.py @@ -159,16 +159,14 @@ class BBATop(Elaboratable): # The bulky register-file / SPRAM / W5500 logic is routing-bound at # ~33–44 MHz on the UP5K; 24 MHz closes with large margin. The byte # rate (27 MHz EXI ÷ 8 ≈ 3.4 MHz) leaves ~7 slow cycles per byte. - m.domains += ClockDomain("exi") m.domains += ClockDomain("sync") m.submodules.hfosc = Instance( "SB_HFOSC", p_CLKHF_DIV = "0b01", # 48 ÷ 2 → 24 MHz i_CLKHFEN = Const(1, 1), i_CLKHFPU = Const(1, 1), - o_CLKHF = ClockSignal("exi"), + o_CLKHF = ClockSignal("sync"), ) - m.d.comb += ClockSignal("sync").eq(ClockSignal("exi")) # (simulation: test harness provides capture/exi/sync clocks via add_clock) # ── Submodules ──────────────────────────────────────────────────── @@ -676,7 +674,6 @@ if __name__ == "__main__": print(f"T8 TX frame matches byte-for-byte ({len(TX_FRAME)} bytes)") sim = Simulator(dut) - sim.add_clock(Period(MHz=24), domain="exi") sim.add_clock(Period(MHz=24), domain="sync") sim.add_testbench(testbench) sim.add_process(w5100_model) diff --git a/exi_bba/exi_capture.py b/exi_bba/exi_capture.py index 909d8ed..19e7ab7 100644 --- a/exi_bba/exi_capture.py +++ b/exi_bba/exi_capture.py @@ -120,9 +120,9 @@ class ExiCapture(Elaboratable): # (~296 ns at 27 MHz) of slack -- unlike the old design, where # bit-level MISO timing crossed a domain with a 37 ns budget. rx_fifo = AsyncFIFO(width=8, depth=self._rx_depth, - w_domain="exififo_w", r_domain="exi") + w_domain="exififo_w", r_domain="sync") tx_fifo = AsyncFIFO(width=8, depth=self._tx_depth, - w_domain="exi", r_domain="exififo_r") + w_domain="sync", r_domain="exififo_r") m.submodules.rx_fifo = rx_fifo m.submodules.tx_fifo = tx_fifo @@ -160,9 +160,9 @@ class ExiCapture(Elaboratable): # there is no longer any reason to detect it in a fast domain. cs_n_s = Signal(init=1) m.submodules.cs_sync = FFSynchronizer(self.spi_cs_n, cs_n_s, - o_domain="exi", init=1) + o_domain="sync", init=1) cs_n_prev = Signal(init=1) - m.d.exi += cs_n_prev.eq(cs_n_s) + m.d.sync += cs_n_prev.eq(cs_n_s) m.d.comb += self.cs_active.eq(~cs_n_s) return m @@ -204,7 +204,7 @@ if __name__ == "__main__": if ctx.get(dut.rx_rdy): into.append(ctx.get(dut.rx_data)) ctx.set(dut.rx_en, 1) - await ctx.tick("exi").repeat(1) + await ctx.tick("sync").repeat(1) ctx.set(dut.rx_en, 0) return True return False @@ -212,7 +212,7 @@ if __name__ == "__main__": async def push_tx(ctx, b): ctx.set(dut.tx_data, b) ctx.set(dut.tx_en, 1) - await ctx.tick("exi").repeat(1) + await ctx.tick("sync").repeat(1) ctx.set(dut.tx_en, 0) async def do_txn(ctx, hdr, responses, n_data, rx_seen): @@ -226,7 +226,7 @@ if __name__ == "__main__": await spi_byte(ctx, h) for _ in range(20): # clock-idle gap await core_drain_rx(ctx, rx_seen) - await ctx.tick("exi").repeat(1) + await ctx.tick("sync").repeat(1) for r in responses: await push_tx(ctx, r) await ctx.delay(HP * (2)) @@ -235,7 +235,7 @@ if __name__ == "__main__": await ctx.delay(HP * (HALF)) for _ in range(20): # drain data-phase dummies await core_drain_rx(ctx, rx_seen) - await ctx.tick("exi").repeat(1) + await ctx.tick("sync").repeat(1) return miso async def testbench(ctx): @@ -262,7 +262,7 @@ if __name__ == "__main__": errors.append(f"T2 flush failed — stale byte leaked: {[hex(b) for b in misoB]}") sim = Simulator(dut) - sim.add_clock(Period(MHz=24), domain="exi") + sim.add_clock(Period(MHz=24), domain="sync") sim.add_testbench(testbench) with sim.write_vcd("ExiCapture.vcd"): diff --git a/exi_bba/exi_j4.py b/exi_bba/exi_j4.py index 97e9cc9..9709a3b 100644 --- a/exi_bba/exi_j4.py +++ b/exi_bba/exi_j4.py @@ -183,7 +183,6 @@ if __name__ == "__main__": unplug(ctx, top.j4) sim = Simulator(top) - sim.add_clock(Period(MHz=24), domain="exi") sim.add_clock(Period(MHz=24), domain="sync") sim.add_testbench(testbench) with sim.write_vcd("exi_j4_merge.vcd"):