continuing async clock experimenting
[soc.git] / src / soc / experiment / test / async_sim.py
1 # experimentation with async clocks, clock domains, and AsyncFFSynchronizer
2
3 from nmigen import (Elaboratable, Module, Signal, ClockDomain, DomainRenamer,
4 ResetSignal)
5 from nmigen.lib.cdc import AsyncFFSynchronizer
6 from nmigen.back.pysim import Simulator, Delay, Settle, Tick
7 from nmutil.util import wrap
8
9
10 # simple "tick-driven counter" thing
11 class Domain1(Elaboratable):
12 def __init__(self):
13 self.tick = Signal()
14 self.tick_clear_wait = Signal()
15 self.counter = Signal(8)
16 self.rst = Signal()
17
18 def elaborate(self, platform):
19 m = Module()
20
21 m.d.comb += ResetSignal().eq(self.rst)
22
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)
27
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)
31 return m
32
33 # simple "counter" thing
34 class Domain2(Elaboratable):
35 def __init__(self):
36 self.counter = Signal(8)
37 self.rst = Signal()
38
39 def elaborate(self, platform):
40 m = Module()
41
42 m.d.comb += ResetSignal().eq(self.rst)
43 m.d.sync += self.counter.eq(self.counter + 1)
44
45 return m
46
47
48 class AsyncThing(Elaboratable):
49 def __init__(self):
50
51 self.core_clk = Signal()
52 self.core_tick = Signal()
53
54 def elaborate(self, platform):
55 m = Module()
56 core_sync = ClockDomain("coresync")
57 m.domains += core_sync
58 comb, sync, coresync = m.d.comb, m.d.sync, m.d.coresync
59
60 self.core2 = core2 = Domain2()
61 m.submodules.core2 = DomainRenamer("coresync")(core2)
62 m.submodules.core = self.core = core = Domain1()
63
64 comb += core_sync.clk.eq(self.core_clk) # driven externally
65 comb += core.rst.eq(ResetSignal())
66
67 m.submodules += AsyncFFSynchronizer(self.core_tick, core.tick)
68
69 return m
70
71
72 # loops and displays the counter in the main (sync) clock-driven module
73 def domain_sim(dut):
74 for i in range(50):
75 yield Tick("coresync")
76 counter = (yield dut.core.counter)
77 print ("count i", i, counter)
78
79
80 # fires the manually-driven clock at 1/3 the rate
81 def async_sim_clk(dut):
82
83 for i in range(100):
84 yield dut.core_clk.eq(1)
85 yield Tick("sync")
86 yield Tick("sync")
87 yield Tick("sync")
88 yield Tick("sync")
89 yield dut.core_clk.eq(0)
90 yield Tick("sync")
91 yield Tick("sync")
92 yield Tick("sync")
93 yield Tick("sync")
94
95 counter = yield dut.core2.counter
96 print ("async counter", counter)
97
98
99 # runs at the *sync* simulation rate but yields *coresync*-sized ticks,
100 # arbitrarily switching core_tick on and off
101 # this deliberately does not quite match up with when the *clock* ticks
102 # (see async_sim_clk above)
103 #
104 # experimenting by deleting some of these coresyncs (both the on and off ones)
105 # does in fact "miss" things.
106
107 def async_sim(dut):
108 for i in range(5):
109 yield dut.core_tick.eq(1)
110 yield Tick("coresync")
111 yield Tick("coresync")
112 yield Tick("coresync")
113 yield Tick("coresync")
114 yield Tick("coresync")
115 yield Tick("coresync")
116
117 # switch off but must wait at least 3 coresync ticks because
118 # otherwise the coresync domain that the counter is in might
119 # miss it (actually AsyncFFSynchronizer would)
120 yield dut.core_tick.eq(0)
121 yield Tick("coresync")
122 yield Tick("coresync")
123 yield Tick("coresync")
124
125 if __name__ == '__main__':
126
127 dut = AsyncThing()
128 m = Module()
129 m.submodules.ast = dut
130
131 sim = Simulator(m)
132 sim.add_clock(1e-6, domain="sync") # standard clock
133 sim.add_clock(3e-6, domain="coresync") # manually-driven. 1/3 rate
134
135 sim.add_sync_process(wrap(domain_sim(dut)))
136 sim.add_sync_process(wrap(async_sim(dut)), domain="coresync")
137 sim.add_sync_process(wrap(async_sim_clk(dut)))
138
139 with sim.write_vcd("async_sim.vcd"):
140 sim.run()
141
142