Merge the exi and sync domains into one
bba_top wired ClockSignal("sync") to ClockSignal("exi"), so the two "domains"
were always the same physical clock net. Everything between them -- six
AsyncFIFOs and three PulseSynchronizers -- was pure latency and logic for a
crossing that did not exist.
* the six AsyncFIFOs become plain SyncFIFOs
* the three PulseSynchronizers become wires
* BBARegisterFile and ExiCapture move from m.d.exi to m.d.sync
* the exi ClockDomain is gone; HFOSC drives sync directly
The SPRAM request write port is also combinational now. A registered w_en
costs a 24 MHz cycle, and the protocol's turnaround byte (~296 ns at 27 MHz)
has no cycles to spare.
Passing: bba_register_file, spram_arbiter, rx_frame_assembler, tx_frame_drain,
w5100_parallel_master, uart_shell, status_panel. bba_top at EXI_HALF=1
(27 MHz, the hardware rate) passes device ID, PAR read/write, NWAYS, RWP and
the 34-byte TX frame.
STILL OPEN: the SPRAM/DMA read is one byte late. Removing the CDC was not
enough -- the remaining round trip is the rx crossing (exiclk->sync, a REAL
CDC, ~125 ns) plus FSM decode plus the spram_req FIFO, arbiter, SPRAM access
and spram_rsp FIFO, which still exceeds the turnaround byte. Next step: now
that it is all one domain, drop the two SPRAM FIFOs entirely and give the
register file a direct request/grant port on the arbiter -- that should take
the round trip to ~2-3 cycles and fit.
exi_capture and exi_j4 testbenches still assert removed behaviour (the CS-fall
flush and the old MISO slot timing) and need rewriting for the clocked engine.
Still WIP -- do not flash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+105
-101
@@ -15,8 +15,7 @@ Addresses 0x0100–0x1FFF : SPRAM ring buffer (sync domain, prefetch FIFOs).
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from amaranth import *
|
from amaranth import *
|
||||||
from amaranth.lib.cdc import PulseSynchronizer
|
from amaranth.lib.fifo import SyncFIFO
|
||||||
from amaranth.lib.fifo import AsyncFIFO
|
|
||||||
|
|
||||||
__all__ = ["BBARegisterFile"]
|
__all__ = ["BBARegisterFile"]
|
||||||
|
|
||||||
@@ -120,12 +119,17 @@ class BBARegisterFile(Elaboratable):
|
|||||||
m = Module()
|
m = Module()
|
||||||
|
|
||||||
# ── CDC FIFOs ────────────────────────────────────────────────────
|
# ── CDC FIFOs ────────────────────────────────────────────────────
|
||||||
spram_req = AsyncFIFO(width=16, depth=4, w_domain="exi", r_domain="sync")
|
# Plain SyncFIFOs: `exi` and `sync` were always the SAME clock net
|
||||||
spram_rsp = AsyncFIFO(width=8, depth=4, w_domain="sync", r_domain="exi")
|
# (bba_top wired ClockSignal("sync") to ClockSignal("exi")), so the
|
||||||
tx_bytes = AsyncFIFO(width=8, depth=16, w_domain="exi", r_domain="sync")
|
# AsyncFIFOs between them bought nothing and cost ~3 cycles each way.
|
||||||
tx_ctrl = AsyncFIFO(width=16, depth=4, w_domain="exi", r_domain="sync")
|
# On the SPRAM path that latency exceeded the protocol's turnaround
|
||||||
rx_wptr = AsyncFIFO(width=8, depth=4, w_domain="sync", r_domain="exi")
|
# byte and shifted every DMA read by one byte. One slow domain now.
|
||||||
rx_rptr = AsyncFIFO(width=8, depth=4, w_domain="exi", r_domain="sync")
|
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_req = spram_req
|
||||||
m.submodules.spram_rsp = spram_rsp
|
m.submodules.spram_rsp = spram_rsp
|
||||||
@@ -162,19 +166,11 @@ class BBARegisterFile(Elaboratable):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# ── PulseSynchronizers ───────────────────────────────────────────
|
# ── PulseSynchronizers ───────────────────────────────────────────
|
||||||
ncra_rst_ps = PulseSynchronizer(i_domain="exi", o_domain="sync")
|
# Single slow domain, so these are plain wires rather than
|
||||||
rx_irq_ps = PulseSynchronizer(i_domain="sync", o_domain="exi")
|
# PulseSynchronizers. A pulse generated here is already in the only
|
||||||
tx_irq_ps = PulseSynchronizer(i_domain="sync", o_domain="exi")
|
# domain that exists.
|
||||||
|
ncra_rst_pulse = Signal()
|
||||||
m.submodules.ncra_rst_ps = ncra_rst_ps
|
m.d.comb += self.ncra_rst_o.eq(ncra_rst_pulse)
|
||||||
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),
|
|
||||||
]
|
|
||||||
|
|
||||||
# ── Register file (sparse individual Signals, exi domain) ────────
|
# ── Register file (sparse individual Signals, exi domain) ────────
|
||||||
# Only the registers actually read/written by the GC or sync 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)
|
m.d.comb += rd_val.eq(reg_rdval)
|
||||||
|
|
||||||
# ── Default strobes ──────────────────────────────────────────────
|
# ── Default strobes ──────────────────────────────────────────────
|
||||||
m.d.exi += [
|
m.d.sync += [
|
||||||
spram_req.w_en .eq(0),
|
|
||||||
tx_bytes.w_en .eq(0),
|
tx_bytes.w_en .eq(0),
|
||||||
tx_ctrl.w_en .eq(0),
|
tx_ctrl.w_en .eq(0),
|
||||||
rx_rptr.w_en .eq(0),
|
rx_rptr.w_en .eq(0),
|
||||||
rx_wptr.r_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 += [
|
m.d.comb += [
|
||||||
|
spram_req.w_en .eq(0),
|
||||||
|
spram_req.w_data.eq(0),
|
||||||
self.rx_en .eq(0),
|
self.rx_en .eq(0),
|
||||||
self.tx_en .eq(0),
|
self.tx_en .eq(0),
|
||||||
self.tx_data.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
|
# 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
|
# received bytes and, for reads, push response bytes into tx_fifo during
|
||||||
# the EXI clock-idle gap before the GC clocks the data phase.
|
# 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"):
|
with m.State("HEADER0"):
|
||||||
# Byte 0 selects the space and direction. The REAL protocol
|
# 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.
|
# clocks as many data bytes as it wants and ends with CS.
|
||||||
with m.If(self.rx_rdy):
|
with m.If(self.rx_rdy):
|
||||||
m.d.comb += self.rx_en.eq(1)
|
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)]
|
byte_ctr.eq(0), sp_req.eq(0), outstanding.eq(0)]
|
||||||
with m.If(self.rx_data[7]): # register space
|
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"
|
m.next = "RHDR_HI"
|
||||||
with m.Else(): # command space
|
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]),
|
cmd_reg.eq(self.rx_data[0:6]),
|
||||||
addr.eq(self.rx_data[0:6]),
|
addr.eq(self.rx_data[0:6]),
|
||||||
eff_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
|
with m.If((self.rx_data == 0x00) & id_phase
|
||||||
& self.tx_rdy):
|
& self.tx_rdy):
|
||||||
m.d.comb += [self.tx_data.eq(0x04), self.tx_en.eq(1)]
|
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"
|
m.next = "CHDR_1"
|
||||||
|
|
||||||
# ── register space: [0x80|w<<6, ah, al, 0x00] ──────────────────
|
# ── register space: [0x80|w<<6, ah, al, 0x00] ──────────────────
|
||||||
with m.State("RHDR_HI"):
|
with m.State("RHDR_HI"):
|
||||||
with m.If(self.rx_rdy):
|
with m.If(self.rx_rdy):
|
||||||
m.d.comb += self.rx_en.eq(1)
|
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"
|
m.next = "RHDR_LO"
|
||||||
|
|
||||||
with m.State("RHDR_LO"):
|
with m.State("RHDR_LO"):
|
||||||
@@ -353,13 +353,18 @@ class BBARegisterFile(Elaboratable):
|
|||||||
with m.If(self.rx_rdy):
|
with m.If(self.rx_rdy):
|
||||||
m.d.comb += self.rx_en.eq(1)
|
m.d.comb += self.rx_en.eq(1)
|
||||||
full = Cat(self.rx_data, addr[8:16]) # 16-bit address
|
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)]
|
sp_addr.eq(full)]
|
||||||
with m.If(~is_write & (full >= 0x100) & spram_req.w_rdy):
|
with m.If(~is_write & (full >= 0x100) & spram_req.w_rdy):
|
||||||
m.d.exi += [spram_req.w_data.eq(full),
|
# COMBINATIONAL write: the turnaround byte gives only
|
||||||
spram_req.w_en.eq(1),
|
# ~296 ns and the SPRAM round trip needs most of it, so
|
||||||
sp_addr.eq(full + 1),
|
# a registered w_en (one more 24 MHz cycle, ~42 ns) is
|
||||||
outstanding.eq(1)]
|
# 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"
|
m.next = "RHDR_PAD"
|
||||||
|
|
||||||
with m.State("RHDR_PAD"):
|
with m.State("RHDR_PAD"):
|
||||||
@@ -382,7 +387,7 @@ class BBARegisterFile(Elaboratable):
|
|||||||
# arrived, and repeating it would consume the register
|
# arrived, and repeating it would consume the register
|
||||||
# stream early (symptom: reads shifted by a byte).
|
# stream early (symptom: reads shifted by a byte).
|
||||||
m.d.comb += [self.tx_data.eq(rd_val), self.tx_en.eq(1)]
|
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 ──
|
# ── command space: [reg, 00, 00, data] in ONE 4-byte transfer ──
|
||||||
with m.State("CHDR_1"):
|
with m.State("CHDR_1"):
|
||||||
@@ -405,11 +410,11 @@ class BBARegisterFile(Elaboratable):
|
|||||||
# NOT one-shot -- the FIFO is only 2 deep, and stopping
|
# NOT one-shot -- the FIFO is only 2 deep, and stopping
|
||||||
# after one byte leaves a hole in the next slot.
|
# 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.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):
|
with m.Elif(~is_write & self.tx_rdy & ~prefetched):
|
||||||
# Ordinary command read: response lands in byte slot 3.
|
# 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.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.State("CHDR_2"):
|
||||||
with m.If(self.rx_rdy):
|
with m.If(self.rx_rdy):
|
||||||
@@ -420,8 +425,8 @@ class BBARegisterFile(Elaboratable):
|
|||||||
with m.If(self.rx_rdy):
|
with m.If(self.rx_rdy):
|
||||||
m.d.comb += self.rx_en.eq(1)
|
m.d.comb += self.rx_en.eq(1)
|
||||||
with m.If(is_write):
|
with m.If(is_write):
|
||||||
m.d.exi += cmd_wr.eq(1) # applied below
|
m.d.sync += cmd_wr.eq(1) # applied below
|
||||||
m.d.exi += cmd_wdata.eq(self.rx_data)
|
m.d.sync += cmd_wdata.eq(self.rx_data)
|
||||||
m.next = "TRAILER"
|
m.next = "TRAILER"
|
||||||
|
|
||||||
with m.State("TRAILER"):
|
with m.State("TRAILER"):
|
||||||
@@ -437,37 +442,37 @@ class BBARegisterFile(Elaboratable):
|
|||||||
m.d.comb += self.rx_en.eq(1)
|
m.d.comb += self.rx_en.eq(1)
|
||||||
with m.Switch(rd_sel):
|
with m.Switch(rd_sel):
|
||||||
with m.Case(_NCRA):
|
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]):
|
with m.If(self.rx_data[0]):
|
||||||
m.d.exi += r_ncra[0].eq(0) # RESET self-clears
|
m.d.sync += r_ncra[0].eq(0) # RESET self-clears
|
||||||
m.d.exi += ncra_rst_ps.i.eq(1)
|
m.d.sync += ncra_rst_pulse.eq(1)
|
||||||
m.d.exi += id_phase.eq(0)
|
m.d.sync += id_phase.eq(0)
|
||||||
# Fallback trigger: TWD_HI normally queues the
|
# Fallback trigger: TWD_HI normally queues the
|
||||||
# frame (see _TWD_HI). Only fire here if it did
|
# frame (see _TWD_HI). Only fire here if it did
|
||||||
# not, so software that skips TWD still transmits
|
# not, so software that skips TWD still transmits
|
||||||
# and we never double-queue one frame.
|
# and we never double-queue one frame.
|
||||||
with m.If(self.rx_data[1:3].any() & ~tx_queued):
|
with m.If(self.rx_data[1:3].any() & ~tx_queued):
|
||||||
with m.If(tx_ctrl.w_rdy):
|
with m.If(tx_ctrl.w_rdy):
|
||||||
m.d.exi += tx_ctrl.w_data.eq(tx_frame_len)
|
m.d.sync += tx_ctrl.w_data.eq(tx_frame_len)
|
||||||
m.d.exi += tx_ctrl.w_en.eq(1)
|
m.d.sync += tx_ctrl.w_en.eq(1)
|
||||||
m.d.exi += tx_queued.eq(1)
|
m.d.sync += tx_queued.eq(1)
|
||||||
with m.Case(_IMR):
|
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):
|
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):
|
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):
|
with m.If(rx_rptr.w_rdy):
|
||||||
m.d.exi += rx_rptr.w_data.eq(self.rx_data)
|
m.d.sync += rx_rptr.w_data.eq(self.rx_data)
|
||||||
m.d.exi += rx_rptr.w_en.eq(1)
|
m.d.sync += rx_rptr.w_en.eq(1)
|
||||||
with m.Case(_PAR0, _PAR1, _PAR2, _PAR3, _PAR4, _PAR5):
|
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):
|
with m.Case(_TWD_LO):
|
||||||
m.d.exi += r_twd_lo.eq(self.rx_data)
|
m.d.sync += r_twd_lo.eq(self.rx_data)
|
||||||
m.d.exi += tx_frame_len[0:8].eq(self.rx_data)
|
m.d.sync += tx_frame_len[0:8].eq(self.rx_data)
|
||||||
with m.Case(_TWD_HI):
|
with m.Case(_TWD_HI):
|
||||||
m.d.exi += r_twd_hi.eq(self.rx_data)
|
m.d.sync += r_twd_hi.eq(self.rx_data)
|
||||||
m.d.exi += tx_frame_len[8:16].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
|
# Hand the length to TXFrameDrain NOW, not at the ST
|
||||||
# trigger. The drain blocks on tx_bytes_r_rdy, so
|
# trigger. The drain blocks on tx_bytes_r_rdy, so
|
||||||
# starting it here lets it consume bytes AS the GC
|
# 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
|
# to fit in the 16-deep FIFO, and anything past 16
|
||||||
# bytes was silently dropped (hardware 2026-09-30:
|
# bytes was silently dropped (hardware 2026-09-30:
|
||||||
# frames truncated at exactly 16 bytes on the wire).
|
# 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.Case(_TXDATA):
|
||||||
with m.If(tx_bytes.w_rdy):
|
with m.If(tx_bytes.w_rdy):
|
||||||
m.d.exi += tx_bytes.w_data.eq(self.rx_data)
|
m.d.sync += tx_bytes.w_data.eq(self.rx_data)
|
||||||
m.d.exi += tx_bytes.w_en.eq(1)
|
m.d.sync += tx_bytes.w_en.eq(1)
|
||||||
# All other addresses silently ignored
|
# All other addresses silently ignored
|
||||||
|
|
||||||
# No length field in the real protocol: consume data
|
# No length field in the real protocol: consume data
|
||||||
# bytes until CS rises.
|
# 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):
|
with m.If(~self.cs_active):
|
||||||
m.next = "HEADER0"
|
m.next = "HEADER0"
|
||||||
|
|
||||||
@@ -494,7 +499,7 @@ class BBARegisterFile(Elaboratable):
|
|||||||
# path. The real protocol has no length field.
|
# path. The real protocol has no length field.
|
||||||
with m.If(self.tx_rdy & self.cs_active):
|
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.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):
|
with m.If(self.rx_rdy):
|
||||||
m.d.comb += self.rx_en.eq(1) # drain dummies
|
m.d.comb += self.rx_en.eq(1) # drain dummies
|
||||||
with m.If(~self.cs_active):
|
with m.If(~self.cs_active):
|
||||||
@@ -504,7 +509,7 @@ class BBARegisterFile(Elaboratable):
|
|||||||
# Stream 04 02 02 00 until CS rises.
|
# Stream 04 02 02 00 until CS rises.
|
||||||
with m.If(self.tx_rdy & self.cs_active):
|
with m.If(self.tx_rdy & self.cs_active):
|
||||||
m.d.comb += [self.tx_data.eq(devid), self.tx_en.eq(1)]
|
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):
|
with m.If(self.rx_rdy):
|
||||||
m.d.comb += self.rx_en.eq(1)
|
m.d.comb += self.rx_en.eq(1)
|
||||||
with m.If(~self.cs_active):
|
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)
|
m.d.comb += pop.eq(spram_rsp.r_rdy & self.tx_rdy)
|
||||||
|
|
||||||
with m.If(issue):
|
with m.If(issue):
|
||||||
m.d.exi += spram_req.w_data.eq(sp_addr)
|
m.d.comb += spram_req.w_data.eq(sp_addr)
|
||||||
m.d.exi += spram_req.w_en.eq(1)
|
m.d.comb += spram_req.w_en.eq(1)
|
||||||
m.d.exi += sp_addr.eq(sp_addr + 1)
|
m.d.sync += sp_addr.eq(sp_addr + 1)
|
||||||
with m.If(pop):
|
with m.If(pop):
|
||||||
m.d.comb += self.tx_data.eq(spram_rsp.r_data)
|
m.d.comb += self.tx_data.eq(spram_rsp.r_data)
|
||||||
m.d.comb += self.tx_en.eq(1)
|
m.d.comb += self.tx_en.eq(1)
|
||||||
m.d.comb += spram_rsp.r_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):
|
with m.If(self.rx_rdy):
|
||||||
m.d.comb += self.rx_en.eq(1) # drain dummy bytes
|
m.d.comb += self.rx_en.eq(1) # drain dummy bytes
|
||||||
@@ -544,7 +549,7 @@ class BBARegisterFile(Elaboratable):
|
|||||||
# ExiCapture on the next CS assertion.
|
# ExiCapture on the next CS assertion.
|
||||||
with m.If(spram_rsp.r_rdy):
|
with m.If(spram_rsp.r_rdy):
|
||||||
m.d.comb += spram_rsp.r_en.eq(1)
|
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):
|
with m.If(self.rx_rdy):
|
||||||
m.d.comb += self.rx_en.eq(1)
|
m.d.comb += self.rx_en.eq(1)
|
||||||
with m.If((outstanding == 0) & ~self.rx_rdy & ~spram_rsp.r_rdy):
|
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 ──────────
|
# ── Command-space write, applied the cycle after CHDR_3 ──────────
|
||||||
with m.If(cmd_wr):
|
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.Switch(cmd_reg):
|
||||||
with m.Case(0x02): m.d.exi += r_cmd_imr.eq(cmd_wdata)
|
with m.Case(0x02): m.d.sync += 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(0x03): m.d.sync += r_cmd_ir.eq(r_cmd_ir & ~cmd_wdata)
|
||||||
|
|
||||||
# ── Interrupt output ─────────────────────────────────────────────
|
# ── 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 ──────────────────────────
|
# ── Consume RWP updates from sync domain ──────────────────────────
|
||||||
with m.If(rx_wptr.r_rdy):
|
with m.If(rx_wptr.r_rdy):
|
||||||
m.d.exi += rx_wptr.r_en.eq(1)
|
m.d.sync += rx_wptr.r_en.eq(1)
|
||||||
m.d.exi += r_rwp_lo.eq(rx_wptr.r_data)
|
m.d.sync += r_rwp_lo.eq(rx_wptr.r_data)
|
||||||
|
|
||||||
# ── PulseSynchronizer arrivals ────────────────────────────────────
|
# ── PulseSynchronizer arrivals ────────────────────────────────────
|
||||||
with m.If(rx_irq_ps.o):
|
with m.If(self.rx_irq_i):
|
||||||
m.d.exi += r_ir[1].eq(1) # RI bit
|
m.d.sync += r_ir[1].eq(1) # RI bit
|
||||||
# ── Deferred TX queue (armed by a TWD_HI write) ──────────────────
|
# ── Deferred TX queue (armed by a TWD_HI write) ──────────────────
|
||||||
# One cycle after TWD_HI so tx_frame_len holds both bytes. Held until
|
# 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.
|
# tx_ctrl has room so a full control FIFO cannot drop the trigger.
|
||||||
with m.If(twd_armed & tx_ctrl.w_rdy):
|
with m.If(twd_armed & tx_ctrl.w_rdy):
|
||||||
m.d.exi += tx_ctrl.w_data.eq(tx_frame_len)
|
m.d.sync += tx_ctrl.w_data.eq(tx_frame_len)
|
||||||
m.d.exi += tx_ctrl.w_en.eq(1)
|
m.d.sync += tx_ctrl.w_en.eq(1)
|
||||||
m.d.exi += twd_armed.eq(0)
|
m.d.sync += twd_armed.eq(0)
|
||||||
m.d.exi += tx_queued.eq(1)
|
m.d.sync += tx_queued.eq(1)
|
||||||
|
|
||||||
with m.If(tx_irq_ps.o):
|
with m.If(self.tx_irq_i):
|
||||||
m.d.exi += r_ir[2].eq(1) # TI bit
|
m.d.sync += r_ir[2].eq(1) # TI bit
|
||||||
m.d.exi += r_ncra[1:3].eq(0) # clear ST bits
|
m.d.sync += r_ncra[1:3].eq(0) # clear ST bits
|
||||||
m.d.exi += tx_queued.eq(0) # ready for the next frame
|
m.d.sync += tx_queued.eq(0) # ready for the next frame
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
||||||
@@ -615,7 +620,7 @@ if __name__ == "__main__":
|
|||||||
while True:
|
while True:
|
||||||
en = ctx.get(reg.rx_en)
|
en = ctx.get(reg.rx_en)
|
||||||
sample_tx(ctx)
|
sample_tx(ctx)
|
||||||
await ctx.tick("exi")
|
await ctx.tick("sync")
|
||||||
if en:
|
if en:
|
||||||
break
|
break
|
||||||
ctx.set(reg.rx_rdy, 0)
|
ctx.set(reg.rx_rdy, 0)
|
||||||
@@ -630,7 +635,7 @@ if __name__ == "__main__":
|
|||||||
break
|
break
|
||||||
if ctx.get(reg.tx_en):
|
if ctx.get(reg.tx_en):
|
||||||
out.append(ctx.get(reg.tx_data))
|
out.append(ctx.get(reg.tx_data))
|
||||||
await ctx.tick("exi")
|
await ctx.tick("sync")
|
||||||
return out
|
return out
|
||||||
|
|
||||||
# Real protocol (docs/exi_protocol_real.md): register space is a 4-byte
|
# 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
|
await push_rx(ctx, 0x00) # turnaround / prefetch window
|
||||||
result = await collect_tx(ctx, length)
|
result = await collect_tx(ctx, length)
|
||||||
ctx.set(reg.cs_active, 0)
|
ctx.set(reg.cs_active, 0)
|
||||||
await ctx.tick("exi").repeat(6)
|
await ctx.tick("sync").repeat(6)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def exi_write(ctx, addr, data):
|
async def exi_write(ctx, addr, data):
|
||||||
@@ -657,7 +662,7 @@ if __name__ == "__main__":
|
|||||||
for b in data:
|
for b in data:
|
||||||
await push_rx(ctx, b)
|
await push_rx(ctx, b)
|
||||||
ctx.set(reg.cs_active, 0)
|
ctx.set(reg.cs_active, 0)
|
||||||
await ctx.tick("exi").repeat(6)
|
await ctx.tick("sync").repeat(6)
|
||||||
|
|
||||||
async def exi_devid(ctx):
|
async def exi_devid(ctx):
|
||||||
"""EXI_GetID: write 0x0000 (2 bytes), then clock 4 read bytes."""
|
"""EXI_GetID: write 0x0000 (2 bytes), then clock 4 read bytes."""
|
||||||
@@ -667,7 +672,7 @@ if __name__ == "__main__":
|
|||||||
await push_rx(ctx, 0x00)
|
await push_rx(ctx, 0x00)
|
||||||
result = await collect_tx(ctx, 4)
|
result = await collect_tx(ctx, 4)
|
||||||
ctx.set(reg.cs_active, 0)
|
ctx.set(reg.cs_active, 0)
|
||||||
await ctx.tick("exi").repeat(6)
|
await ctx.tick("sync").repeat(6)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# SPRAM contents the streaming-read test reads back (byte i = 0xA0+i).
|
# SPRAM contents the streaming-read test reads back (byte i = 0xA0+i).
|
||||||
@@ -701,51 +706,51 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
async def testbench(ctx):
|
async def testbench(ctx):
|
||||||
ctx.set(reg.tx_rdy, 1) # tx_fifo always has room in this model
|
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)
|
# T1: Device ID (addr=0, 4-byte read)
|
||||||
result = await exi_devid(ctx)
|
result = await exi_devid(ctx)
|
||||||
if result != _DEVICE_ID:
|
if result != _DEVICE_ID:
|
||||||
errors.append(f"T1 device ID: expected {_DEVICE_ID}, got {result}")
|
errors.append(f"T1 device ID: expected {_DEVICE_ID}, got {result}")
|
||||||
print(f"T1 device ID: {[f'0x{b:02X}' for b in 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
|
# T2: Write and read back PAR0-PAR3
|
||||||
await exi_write(ctx, _PAR0, [0xDE, 0xAD, 0xBE, 0xEF])
|
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)
|
result = await exi_read(ctx, _PAR0, length=4)
|
||||||
if result != [0xDE, 0xAD, 0xBE, 0xEF]:
|
if result != [0xDE, 0xAD, 0xBE, 0xEF]:
|
||||||
errors.append(f"T2 PAR readback: {result}")
|
errors.append(f"T2 PAR readback: {result}")
|
||||||
print(f"T2 PAR0-3: {[f'0x{b:02X}' for b in 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
|
# T3: NWAYS hardcoded 0x17
|
||||||
result = await exi_read(ctx, _NWAYS, length=1)
|
result = await exi_read(ctx, _NWAYS, length=1)
|
||||||
if result != [0x17]:
|
if result != [0x17]:
|
||||||
errors.append(f"T3 NWAYS: expected 0x17, got {result}")
|
errors.append(f"T3 NWAYS: expected 0x17, got {result}")
|
||||||
print(f"T3 NWAYS: 0x{result[0]:02X}")
|
print(f"T3 NWAYS: 0x{result[0]:02X}")
|
||||||
await ctx.tick("exi").repeat(4)
|
await ctx.tick("sync").repeat(4)
|
||||||
|
|
||||||
# T4: HIPR hardcoded 0x01
|
# T4: HIPR hardcoded 0x01
|
||||||
result = await exi_read(ctx, _HIPR, length=1)
|
result = await exi_read(ctx, _HIPR, length=1)
|
||||||
if result != [0x01]:
|
if result != [0x01]:
|
||||||
errors.append(f"T4 HIPR: expected 0x01, got {result}")
|
errors.append(f"T4 HIPR: expected 0x01, got {result}")
|
||||||
print(f"T4 HIPR: 0x{result[0]:02X}")
|
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
|
# T5: IMR write, rx_irq pulse, INT_N asserts, then IR clear
|
||||||
await exi_write(ctx, _IMR, [0x02]) # enable RI (bit 1)
|
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)
|
ctx.set(reg.rx_irq_i, 1)
|
||||||
await ctx.tick("sync").repeat(1)
|
await ctx.tick("sync").repeat(1)
|
||||||
ctx.set(reg.rx_irq_i, 0)
|
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)
|
int_n = ctx.get(reg.exi_int_n)
|
||||||
if int_n != 0:
|
if int_n != 0:
|
||||||
errors.append(f"T5 INT_N after RI: expected 0, got {int_n}")
|
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)")
|
print(f"T5 INT_N after RI pulse: {int_n} (want 0)")
|
||||||
await exi_write(ctx, _IR, [0x02]) # write-1-to-clear RI
|
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)
|
int_n = ctx.get(reg.exi_int_n)
|
||||||
if int_n != 1:
|
if int_n != 1:
|
||||||
errors.append(f"T5 INT_N after clear: expected 1, got {int_n}")
|
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))
|
got.append(ctx.get(reg.tx_data))
|
||||||
if len(got) >= N:
|
if len(got) >= N:
|
||||||
break
|
break
|
||||||
await ctx.tick("exi")
|
await ctx.tick("sync")
|
||||||
ctx.set(reg.cs_active, 0) # end the transaction
|
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)]
|
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]}")
|
print(f"T6 DMA read {N}B: {[f'0x{b:02X}' for b in got]}")
|
||||||
if got != want:
|
if got != want:
|
||||||
@@ -783,7 +788,6 @@ if __name__ == "__main__":
|
|||||||
print(f"T7 NWAYS after DMA read: 0x{result[0]:02X}")
|
print(f"T7 NWAYS after DMA read: 0x{result[0]:02X}")
|
||||||
|
|
||||||
sim = Simulator(reg)
|
sim = Simulator(reg)
|
||||||
sim.add_clock(Period(MHz=24), domain="exi")
|
|
||||||
sim.add_clock(Period(MHz=24), domain="sync")
|
sim.add_clock(Period(MHz=24), domain="sync")
|
||||||
sim.add_testbench(testbench)
|
sim.add_testbench(testbench)
|
||||||
sim.add_process(spram_model)
|
sim.add_process(spram_model)
|
||||||
|
|||||||
+1
-4
@@ -159,16 +159,14 @@ class BBATop(Elaboratable):
|
|||||||
# The bulky register-file / SPRAM / W5500 logic is routing-bound at
|
# 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
|
# ~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.
|
# rate (27 MHz EXI ÷ 8 ≈ 3.4 MHz) leaves ~7 slow cycles per byte.
|
||||||
m.domains += ClockDomain("exi")
|
|
||||||
m.domains += ClockDomain("sync")
|
m.domains += ClockDomain("sync")
|
||||||
m.submodules.hfosc = Instance(
|
m.submodules.hfosc = Instance(
|
||||||
"SB_HFOSC",
|
"SB_HFOSC",
|
||||||
p_CLKHF_DIV = "0b01", # 48 ÷ 2 → 24 MHz
|
p_CLKHF_DIV = "0b01", # 48 ÷ 2 → 24 MHz
|
||||||
i_CLKHFEN = Const(1, 1),
|
i_CLKHFEN = Const(1, 1),
|
||||||
i_CLKHFPU = 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)
|
# (simulation: test harness provides capture/exi/sync clocks via add_clock)
|
||||||
|
|
||||||
# ── Submodules ────────────────────────────────────────────────────
|
# ── Submodules ────────────────────────────────────────────────────
|
||||||
@@ -676,7 +674,6 @@ if __name__ == "__main__":
|
|||||||
print(f"T8 TX frame matches byte-for-byte ({len(TX_FRAME)} bytes)")
|
print(f"T8 TX frame matches byte-for-byte ({len(TX_FRAME)} bytes)")
|
||||||
|
|
||||||
sim = Simulator(dut)
|
sim = Simulator(dut)
|
||||||
sim.add_clock(Period(MHz=24), domain="exi")
|
|
||||||
sim.add_clock(Period(MHz=24), domain="sync")
|
sim.add_clock(Period(MHz=24), domain="sync")
|
||||||
sim.add_testbench(testbench)
|
sim.add_testbench(testbench)
|
||||||
sim.add_process(w5100_model)
|
sim.add_process(w5100_model)
|
||||||
|
|||||||
@@ -120,9 +120,9 @@ class ExiCapture(Elaboratable):
|
|||||||
# (~296 ns at 27 MHz) of slack -- unlike the old design, where
|
# (~296 ns at 27 MHz) of slack -- unlike the old design, where
|
||||||
# bit-level MISO timing crossed a domain with a 37 ns budget.
|
# bit-level MISO timing crossed a domain with a 37 ns budget.
|
||||||
rx_fifo = AsyncFIFO(width=8, depth=self._rx_depth,
|
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,
|
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.rx_fifo = rx_fifo
|
||||||
m.submodules.tx_fifo = tx_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.
|
# there is no longer any reason to detect it in a fast domain.
|
||||||
cs_n_s = Signal(init=1)
|
cs_n_s = Signal(init=1)
|
||||||
m.submodules.cs_sync = FFSynchronizer(self.spi_cs_n, cs_n_s,
|
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)
|
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)
|
m.d.comb += self.cs_active.eq(~cs_n_s)
|
||||||
|
|
||||||
return m
|
return m
|
||||||
@@ -204,7 +204,7 @@ if __name__ == "__main__":
|
|||||||
if ctx.get(dut.rx_rdy):
|
if ctx.get(dut.rx_rdy):
|
||||||
into.append(ctx.get(dut.rx_data))
|
into.append(ctx.get(dut.rx_data))
|
||||||
ctx.set(dut.rx_en, 1)
|
ctx.set(dut.rx_en, 1)
|
||||||
await ctx.tick("exi").repeat(1)
|
await ctx.tick("sync").repeat(1)
|
||||||
ctx.set(dut.rx_en, 0)
|
ctx.set(dut.rx_en, 0)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -212,7 +212,7 @@ if __name__ == "__main__":
|
|||||||
async def push_tx(ctx, b):
|
async def push_tx(ctx, b):
|
||||||
ctx.set(dut.tx_data, b)
|
ctx.set(dut.tx_data, b)
|
||||||
ctx.set(dut.tx_en, 1)
|
ctx.set(dut.tx_en, 1)
|
||||||
await ctx.tick("exi").repeat(1)
|
await ctx.tick("sync").repeat(1)
|
||||||
ctx.set(dut.tx_en, 0)
|
ctx.set(dut.tx_en, 0)
|
||||||
|
|
||||||
async def do_txn(ctx, hdr, responses, n_data, rx_seen):
|
async def do_txn(ctx, hdr, responses, n_data, rx_seen):
|
||||||
@@ -226,7 +226,7 @@ if __name__ == "__main__":
|
|||||||
await spi_byte(ctx, h)
|
await spi_byte(ctx, h)
|
||||||
for _ in range(20): # clock-idle gap
|
for _ in range(20): # clock-idle gap
|
||||||
await core_drain_rx(ctx, rx_seen)
|
await core_drain_rx(ctx, rx_seen)
|
||||||
await ctx.tick("exi").repeat(1)
|
await ctx.tick("sync").repeat(1)
|
||||||
for r in responses:
|
for r in responses:
|
||||||
await push_tx(ctx, r)
|
await push_tx(ctx, r)
|
||||||
await ctx.delay(HP * (2))
|
await ctx.delay(HP * (2))
|
||||||
@@ -235,7 +235,7 @@ if __name__ == "__main__":
|
|||||||
await ctx.delay(HP * (HALF))
|
await ctx.delay(HP * (HALF))
|
||||||
for _ in range(20): # drain data-phase dummies
|
for _ in range(20): # drain data-phase dummies
|
||||||
await core_drain_rx(ctx, rx_seen)
|
await core_drain_rx(ctx, rx_seen)
|
||||||
await ctx.tick("exi").repeat(1)
|
await ctx.tick("sync").repeat(1)
|
||||||
return miso
|
return miso
|
||||||
|
|
||||||
async def testbench(ctx):
|
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]}")
|
errors.append(f"T2 flush failed — stale byte leaked: {[hex(b) for b in misoB]}")
|
||||||
|
|
||||||
sim = Simulator(dut)
|
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_testbench(testbench)
|
||||||
|
|
||||||
with sim.write_vcd("ExiCapture.vcd"):
|
with sim.write_vcd("ExiCapture.vcd"):
|
||||||
|
|||||||
@@ -183,7 +183,6 @@ if __name__ == "__main__":
|
|||||||
unplug(ctx, top.j4)
|
unplug(ctx, top.j4)
|
||||||
|
|
||||||
sim = Simulator(top)
|
sim = Simulator(top)
|
||||||
sim.add_clock(Period(MHz=24), domain="exi")
|
|
||||||
sim.add_clock(Period(MHz=24), domain="sync")
|
sim.add_clock(Period(MHz=24), domain="sync")
|
||||||
sim.add_testbench(testbench)
|
sim.add_testbench(testbench)
|
||||||
with sim.write_vcd("exi_j4_merge.vcd"):
|
with sim.write_vcd("exi_j4_merge.vcd"):
|
||||||
|
|||||||
Reference in New Issue
Block a user