1 # experimentation with async clocks, clock domains, and AsyncFFSynchronizer
3 from nmigen
import (Elaboratable
, Module
, Signal
, ClockDomain
, DomainRenamer
,
5 from nmigen
.lib
.cdc
import AsyncFFSynchronizer
6 from nmigen
.back
.pysim
import Simulator
, Delay
, Settle
, Tick
7 from nmutil
.util
import wrap
10 # simple "tick-driven counter" thing
11 class Domain1(Elaboratable
):
14 self
.tick_clear_wait
= Signal()
15 self
.counter
= Signal(64)
18 def elaborate(self
, platform
):
21 m
.d
.comb
+= ResetSignal().eq(self
.rst
)
23 # increments the counter each time "tick" goes HI
24 with m
.If(self
.tick
& ~self
.tick_clear_wait
):
25 m
.d
.sync
+= self
.counter
.eq(self
.counter
+ 1)
26 m
.d
.sync
+= self
.tick_clear_wait
.eq(1)
28 # clears waiting when tick is LO
29 with m
.If(~self
.tick
& self
.tick_clear_wait
):
30 m
.d
.sync
+= self
.tick_clear_wait
.eq(0)
33 # simple "counter" thing
34 class Domain2(Elaboratable
):
36 self
.counter
= Signal(64)
39 def elaborate(self
, platform
):
42 m
.d
.comb
+= ResetSignal().eq(self
.rst
)
43 m
.d
.sync
+= self
.counter
.eq(self
.counter
+ 1)
48 class AsyncThing(Elaboratable
):
51 self
.core_clk
= Signal()
52 self
.core_tick
= Signal()
54 def elaborate(self
, platform
):
56 core_sync
= ClockDomain("coresync")
57 m
.domains
+= core_sync
58 comb
, sync
, coresync
= m
.d
.comb
, m
.d
.sync
, m
.d
.coresync
60 self
.core2
= core2
= Domain2()
61 m
.submodules
.core2
= DomainRenamer("coresync")(core2
)
62 m
.submodules
.core
= self
.core
= core
= Domain1()
64 comb
+= core_sync
.clk
.eq(self
.core_clk
) # driven externally
65 comb
+= core
.rst
.eq(ResetSignal())
67 m
.submodules
+= AsyncFFSynchronizer(self
.core_tick
, core
.tick
,
73 # loops and displays the counter in the main (sync) clock-driven module
76 yield Tick("coresync")
77 counter
= (yield dut
.core
.counter
)
78 print ("count i", i
, counter
)
81 # fires the manually-driven clock at 1/3 the rate (actually about 1/4)
82 def async_sim_clk(dut
):
85 yield dut
.core_clk
.eq(1)
96 # deliberately "unbalance" the duty cycle
97 yield dut
.core_clk
.eq(0)
102 counter
= yield dut
.core2
.counter
103 print ("async counter", counter
)
104 assert counter
== 100 # same as number of loops
107 # runs at the *sync* simulation rate but yields *coresync*-sized ticks,
108 # arbitrarily switching core_tick on and off
109 # this deliberately does not quite match up with when the *clock* ticks
110 # (see async_sim_clk above)
112 # experimenting by deleting some of these coresyncs (both the on and off ones)
113 # does in fact "miss" things.
117 yield dut
.core_tick
.eq(1)
118 yield Tick("coresync")
119 yield Tick("coresync")
120 yield Tick("coresync")
122 # switch off but must wait at least 3 coresync ticks because
123 # otherwise the coresync domain that the counter is in might
124 # miss it (actually AsyncFFSynchronizer would)
125 yield dut
.core_tick
.eq(0)
126 yield Tick("coresync")
127 yield Tick("coresync")
128 yield Tick("coresync")
129 yield Tick("coresync")
130 yield Tick("coresync")
131 yield Tick("coresync")
133 if __name__
== '__main__':
137 m
.submodules
.ast
= dut
140 sim
.add_clock(1e-6, domain
="sync") # standard clock
142 # nooo don't do this, it requests that the simulation start driving
143 # coresync_clk! and it's to be *manually* driven by async_sim_clk
144 #sim.add_clock(3e-6, domain="coresync") # manually-driven. 1/3 rate
146 sim
.add_sync_process(wrap(domain_sim(dut
)))
147 sim
.add_sync_process(wrap(async_sim(dut
)), domain
="coresync")
148 sim
.add_sync_process(wrap(async_sim_clk(dut
)))
150 with sim
.write_vcd("async_sim.vcd"):