7c1745848f9dcae58b8267af4c7bc3f4955a971c
[litex.git] / milkymist / asmicon / multiplexer.py
1 from migen.fhdl.structure import *
2 from migen.corelogic.roundrobin import *
3 from migen.corelogic.misc import optree
4 from migen.corelogic.fsm import FSM
5
6 class CommandRequest:
7 def __init__(self, a, ba):
8 self.a = Signal(a)
9 self.ba = Signal(ba)
10 self.cas_n = Signal(reset=1)
11 self.ras_n = Signal(reset=1)
12 self.we_n = Signal(reset=1)
13
14 class CommandRequestRW(CommandRequest):
15 def __init__(self, a, ba, tagbits):
16 CommandRequest.__init__(self, a, ba)
17 self.stb = Signal()
18 self.ack = Signal()
19 self.is_read = Signal()
20 self.is_write = Signal()
21 self.tag = Signal(tagbits)
22
23 class _CommandChooser:
24 def __init__(self, requests, tagbits):
25 self.requests = requests
26
27 self.want_reads = Signal()
28 self.want_writes = Signal()
29 # NB: cas_n/ras_n/we_n are 1 when stb is inactive
30 self.cmd = CommandRequestRW(len(self.requests[0].a), len(self.requests[0].ba), tagbits)
31
32 def get_fragment(self):
33 comb = []
34 sync = []
35
36 rr = RoundRobin(len(self.requests), SP_CE)
37
38 comb += [rr.request[i].eq(req.stb & ((req.is_read == self.want_reads) | (req.is_write == self.want_writes)))
39 for i, req in enumerate(self.requests)]
40
41 stb = Signal()
42 comb.append(stb.eq(Array(req.stb for req in self.requests)[rr.grant]))
43 for name in ["a", "ba", "is_read", "is_write", "tag"]:
44 choices = Array(getattr(req, name) for req in self.requests)
45 comb.append(getattr(self.cmd, name).eq(choices[rr.grant]))
46 for name in ["cas_n", "ras_n", "we_n"]:
47 # we should only assert those signals when stb is 1
48 choices = Array(getattr(req, name) for req in self.requests)
49 comb.append(If(self.cmd.stb, getattr(self.cmd, name).eq(choices[rr.grant])))
50 comb.append(self.cmd.stb.eq(stb \
51 & (self.cmd.is_read == self.want_reads) \
52 & (self.cmd.is_write == self.want_writes)))
53
54 comb += [If(self.cmd.stb & self.cmd.ack & (rr.grant == i), req.ack.eq(1))
55 for i, req in enumerate(self.requests)]
56 comb.append(rr.ce.eq(self.cmd.ack))
57
58 return Fragment(comb, sync) + rr.get_fragment()
59
60 class _Steerer:
61 def __init__(self, commands, dfi):
62 self.commands = commands
63 self.dfi = dfi
64
65 ncmd = len(self.commands)
66 nph = len(self.dfi.phases)
67 self.sel = [Signal(bits_for(ncmd-1)) for i in range(nph)]
68
69 def get_fragment(self):
70 comb = []
71 sync = []
72 def stb_and(cmd, attr):
73 if not hasattr(cmd, "stb"):
74 return 0
75 else:
76 return cmd.stb & getattr(cmd, attr)
77 for phase, sel in zip(self.dfi.phases, self.sel):
78 comb += [
79 phase.cke.eq(1),
80 phase.cs_n.eq(0)
81 ]
82 sync += [
83 phase.address.eq(Array(cmd.a for cmd in self.commands)[sel]),
84 phase.bank.eq(Array(cmd.ba for cmd in self.commands)[sel]),
85 phase.cas_n.eq(Array(cmd.cas_n for cmd in self.commands)[sel]),
86 phase.ras_n.eq(Array(cmd.ras_n for cmd in self.commands)[sel]),
87 phase.we_n.eq(Array(cmd.we_n for cmd in self.commands)[sel]),
88 phase.rddata_en.eq(Array(stb_and(cmd, "is_read") for cmd in self.commands)[sel]),
89 phase.wrdata_en.eq(Array(stb_and(cmd, "is_write") for cmd in self.commands)[sel])
90 ]
91 return Fragment(comb, sync)
92
93 class _Datapath:
94 def __init__(self, timing_settings, command, dfi, hub):
95 self.timing_settings = timing_settings
96 self.command = command
97 self.dfi = dfi
98 self.hub = hub
99
100 def get_fragment(self):
101 comb = []
102 sync = []
103 tagbits = len(self.hub.tag_call)
104
105 rd_valid = Signal()
106 rd_tag = Signal(tagbits)
107 wr_valid = Signal()
108 wr_tag = Signal(tagbits)
109 comb += [
110 self.hub.call.eq(rd_valid | wr_valid),
111 If(wr_valid,
112 self.hub.tag_call.eq(wr_tag)
113 ).Else(
114 self.hub.tag_call.eq(rd_tag)
115 )
116 ]
117
118 rd_delay = self.timing_settings.rd_delay + 1
119 rd_valid_d = [Signal() for i in range(rd_delay)]
120 rd_tag_d = [Signal(tagbits) for i in range(rd_delay)]
121 for i in range(rd_delay):
122 if i:
123 sync += [
124 rd_valid_d[i].eq(rd_valid_d[i-1]),
125 rd_tag_d[i].eq(rd_tag_d[i-1])
126 ]
127 else:
128 sync += [
129 rd_valid_d[i].eq(self.command.stb & self.command.ack & self.command.is_read),
130 rd_tag_d[i].eq(self.command.tag)
131 ]
132 comb += [
133 rd_valid.eq(rd_valid_d[-1]),
134 rd_tag.eq(rd_tag_d[-1]),
135 wr_valid.eq(self.command.stb & self.command.ack & self.command.is_write),
136 wr_tag.eq(self.command.tag),
137 ]
138
139 all_rddata = [p.rddata for p in self.dfi.phases]
140 all_wrdata = [p.wrdata for p in self.dfi.phases]
141 all_wrdata_mask = [p.wrdata_mask for p in self.dfi.phases]
142 comb += [
143 self.hub.dat_r.eq(Cat(*all_rddata)),
144 Cat(*all_wrdata).eq(self.hub.dat_w),
145 Cat(*all_wrdata_mask).eq(self.hub.dat_wm)
146 ]
147
148 return Fragment(comb, sync)
149
150 class Multiplexer:
151 def __init__(self, phy_settings, geom_settings, timing_settings, bank_machines, refresher, dfi, hub):
152 self.phy_settings = phy_settings
153 self.geom_settings = geom_settings
154 self.timing_settings = timing_settings
155 self.bank_machines = bank_machines
156 self.refresher = refresher
157 self.dfi = dfi
158 self.hub = hub
159
160 assert(self.phy_settings.nphases == len(dfi.phases))
161 if self.phy_settings.nphases != 2:
162 raise NotImplementedError("TODO: multiplexer only supports 2 phases")
163
164 def get_fragment(self):
165 comb = []
166 sync = []
167
168 # Command choosing
169 requests = [bm.cmd for bm in self.bank_machines]
170 tagbits = len(self.hub.tag_call)
171 choose_cmd = _CommandChooser(requests, tagbits)
172 choose_req = _CommandChooser(requests, tagbits)
173 comb += [
174 choose_cmd.want_reads.eq(0),
175 choose_cmd.want_writes.eq(0)
176 ]
177
178 # Command steering
179 nop = CommandRequest(self.geom_settings.mux_a, self.geom_settings.bank_a)
180 commands = [nop, choose_cmd.cmd, choose_req.cmd, self.refresher.cmd] # nop must be 1st
181 (STEER_NOP, STEER_CMD, STEER_REQ, STEER_REFRESH) = range(4)
182 steerer = _Steerer(commands, self.dfi)
183
184 # Read/write turnaround
185 read_available = Signal()
186 write_available = Signal()
187 comb += [
188 read_available.eq(optree("|", [req.stb & req.is_read for req in requests])),
189 write_available.eq(optree("|", [req.stb & req.is_write for req in requests]))
190 ]
191
192 def anti_starvation(timeout):
193 en = Signal()
194 max_time = Signal()
195 if timeout:
196 t = timeout - 1
197 time = Signal(bits_for(t))
198 comb.append(max_time.eq(time == 0))
199 sync.append(
200 If(~en,
201 time.eq(t)
202 ).Elif(~max_time,
203 time.eq(time - 1)
204 )
205 )
206 else:
207 comb.append(max_time.eq(0))
208 return en, max_time
209 read_time_en, max_read_time = anti_starvation(self.timing_settings.read_time)
210 write_time_en, max_write_time = anti_starvation(self.timing_settings.write_time)
211
212 # Refresh
213 comb += [bm.refresh_req.eq(self.refresher.req)
214 for bm in self.bank_machines]
215 go_to_refresh = Signal()
216 comb.append(go_to_refresh.eq(
217 optree("&", [bm.refresh_gnt for bm in self.bank_machines])))
218
219 # Datapath
220 datapath = _Datapath(self.timing_settings, choose_req.cmd, self.dfi, self.hub)
221
222 # Control FSM
223 fsm = FSM("READ", "WRITE", "REFRESH", delayed_enters=[
224 ("RTW", "WRITE", self.timing_settings.rd_delay),
225 ("WTR", "READ", self.timing_settings.tWR)
226 ])
227 fsm.act(fsm.READ,
228 read_time_en.eq(1),
229 choose_req.want_reads.eq(1),
230 choose_cmd.cmd.ack.eq(1),
231 choose_req.cmd.ack.eq(1),
232 steerer.sel[1-self.phy_settings.rdphase].eq(STEER_CMD),
233 steerer.sel[self.phy_settings.rdphase].eq(STEER_REQ),
234 If(write_available,
235 # TODO: switch only after several cycles of ~read_available?
236 If(~read_available | max_read_time, fsm.next_state(fsm.RTW))
237 ),
238 If(go_to_refresh, fsm.next_state(fsm.REFRESH))
239 )
240 fsm.act(fsm.WRITE,
241 write_time_en.eq(1),
242 choose_req.want_writes.eq(1),
243 choose_cmd.cmd.ack.eq(1),
244 choose_req.cmd.ack.eq(1),
245 steerer.sel[1-self.phy_settings.wrphase].eq(STEER_CMD),
246 steerer.sel[self.phy_settings.wrphase].eq(STEER_REQ),
247 If(read_available,
248 If(~write_available | max_write_time, fsm.next_state(fsm.WTR))
249 ),
250 If(go_to_refresh, fsm.next_state(fsm.REFRESH))
251 )
252 fsm.act(fsm.REFRESH,
253 steerer.sel[0].eq(STEER_REFRESH),
254 If(~self.refresher.req, fsm.next_state(fsm.READ))
255 )
256 # FIXME: workaround for zero-delay loop simulation problem with Icarus Verilog
257 comb.append(self.refresher.ack.eq(fsm._state == fsm.REFRESH))
258
259 return Fragment(comb, sync) + \
260 choose_cmd.get_fragment() + \
261 choose_req.get_fragment() + \
262 steerer.get_fragment() + \
263 datapath.get_fragment() + \
264 fsm.get_fragment()