Added full design created with Claude
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
from amaranth import *
|
||||
from amaranth.sim import Simulator
|
||||
|
||||
|
||||
class SyncFF(Elaboratable):
|
||||
"""Width-N multi-flop synchronizer from `src_domain` to `dst_domain`.
|
||||
|
||||
Use when the source is a level signal that may be stable for multiple destination
|
||||
cycles. Not suitable for single-cycle pulses (use TogglePulseSync instead).
|
||||
"""
|
||||
|
||||
def __init__(self, width=1, src_domain="src", dst_domain="dst"):
|
||||
self.width = width
|
||||
self.src_domain = src_domain
|
||||
self.dst_domain = dst_domain
|
||||
self.src = Signal(self.width)
|
||||
self.dst = Signal(self.width)
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
reg_src = Signal(self.width)
|
||||
ff0 = Signal(self.width)
|
||||
ff1 = Signal(self.width)
|
||||
|
||||
m.d[self.src_domain] += reg_src.eq(self.src)
|
||||
m.d[self.dst_domain] += ff0.eq(reg_src)
|
||||
m.d[self.dst_domain] += ff1.eq(ff0)
|
||||
m.d.comb += self.dst.eq(ff1)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class TogglePulseSync(Elaboratable):
|
||||
"""Reliable pulse transfer from `src_domain` into `dst_domain`.
|
||||
|
||||
- Source toggles `toggle` whenever an event occurs.
|
||||
- Destination synchronizes the toggle and detects edges.
|
||||
Guarantees ordering and no lost pulses for single-bit events.
|
||||
"""
|
||||
|
||||
def __init__(self, src_domain="src", dst_domain="dst"):
|
||||
self.src_domain = src_domain
|
||||
self.dst_domain = dst_domain
|
||||
self.src_pulse = Signal()
|
||||
self.dst_pulse = Signal()
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
toggle = Signal()
|
||||
sync0 = Signal()
|
||||
sync1 = Signal()
|
||||
prev = Signal()
|
||||
edge = Signal()
|
||||
|
||||
# Source domain: flip the toggle when a pulse arrives
|
||||
m.d[self.src_domain] += If(self.src_pulse, toggle.eq(~toggle))
|
||||
|
||||
# Destination domain: two-flop synchronize the toggle
|
||||
m.d[self.dst_domain] += sync0.eq(toggle)
|
||||
m.d[self.dst_domain] += sync1.eq(sync0)
|
||||
|
||||
# Detect the change in the destination domain
|
||||
m.d[self.dst_domain] += edge.eq(sync1 ^ prev)
|
||||
m.d[self.dst_domain] += prev.eq(sync1)
|
||||
m.d.comb += self.dst_pulse.eq(edge)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def _sim_toggle_pulse():
|
||||
"""Simple simulation that drives pulses on the source domain and prints detections on the destination domain."""
|
||||
|
||||
top = Module()
|
||||
t = TogglePulseSync(src_domain="src", dst_domain="dst")
|
||||
top.submodules.t = t
|
||||
|
||||
sim = Simulator(top)
|
||||
# Create two asynchronous clocks (periods chosen arbitrarily for the sim)
|
||||
sim.add_clock(1e-6, domain="src")
|
||||
sim.add_clock(1.5e-6, domain="dst")
|
||||
|
||||
def process():
|
||||
# Wait a little, then generate three source pulses at different phases
|
||||
for _ in range(5):
|
||||
yield
|
||||
|
||||
for i in range(3):
|
||||
yield t.src_pulse.eq(1)
|
||||
yield
|
||||
yield t.src_pulse.eq(0)
|
||||
# let the domains run for a few cycles
|
||||
for _ in range(10):
|
||||
dp = (yield t.dst_pulse)
|
||||
if dp:
|
||||
print(f"dst detected pulse at sim tick")
|
||||
yield
|
||||
|
||||
# run a bit longer to observe behavior
|
||||
for _ in range(20):
|
||||
yield
|
||||
|
||||
sim.add_sync_process(process, domain="src")
|
||||
sim.run_until(100e-6)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_sim_toggle_pulse()
|
||||
Reference in New Issue
Block a user