return d_out.valid instead of always "ok" in MMU FSM
[soc.git] / src / soc / fu / mmu / fsm.py
1 from nmigen import Elaboratable, Module, Signal, Shape, unsigned, Cat, Mux
2 from nmigen import Record, Memory
3 from nmigen import Const
4 from soc.fu.mmu.pipe_data import MMUInputData, MMUOutputData, MMUPipeSpec
5 from nmutil.singlepipe import ControlBase
6 from nmutil.util import rising_edge
7
8 from soc.experiment.mmu import MMU
9 from soc.experiment.dcache import DCache
10
11 from openpower.consts import MSR
12 from openpower.decoder.power_fields import DecodeFields
13 from openpower.decoder.power_fieldsn import SignalBitRange
14 from openpower.decoder.power_decoder2 import decode_spr_num
15 from openpower.decoder.power_enums import MicrOp, XER_bits
16
17 from soc.experiment.pimem import PortInterface
18 from soc.experiment.pimem import PortInterfaceBase
19
20 from soc.experiment.mem_types import LoadStore1ToDCacheType, LoadStore1ToMMUType
21 from soc.experiment.mem_types import DCacheToLoadStore1Type, MMUToLoadStore1Type
22
23 from soc.minerva.wishbone import make_wb_layout
24 from soc.bus.sram import SRAM
25
26
27 # glue logic for microwatt mmu and dcache
28 class LoadStore1(PortInterfaceBase):
29 def __init__(self, pspec):
30 self.pspec = pspec
31 regwid = pspec.reg_wid
32 addrwid = pspec.addr_wid
33
34 super().__init__(regwid, addrwid)
35 self.dcache = DCache()
36 self.d_in = self.dcache.d_in
37 self.d_out = self.dcache.d_out
38 self.l_in = LoadStore1ToMMUType()
39 self.l_out = MMUToLoadStore1Type()
40 # TODO microwatt
41 self.mmureq = Signal()
42 self.derror = Signal()
43
44 # TODO, convert dcache wb_in/wb_out to "standard" nmigen Wishbone bus
45 self.dbus = Record(make_wb_layout(pspec))
46
47 # for creating a single clock blip to DCache
48 self.d_valid = Signal()
49 self.d_validblip = Signal()
50
51 def set_wr_addr(self, m, addr, mask):
52 #m.d.comb += self.l_in.valid.eq(1)
53 #m.d.comb += self.l_in.addr.eq(addr)
54 #m.d.comb += self.l_in.load.eq(0)
55 m.d.comb += self.d_valid.eq(1)
56 m.d.comb += self.d_in.valid.eq(self.d_validblip)
57 m.d.comb += self.d_in.load.eq(0)
58 m.d.comb += self.d_in.byte_sel.eq(mask)
59 m.d.comb += self.d_in.addr.eq(addr)
60 return None
61
62 def set_rd_addr(self, m, addr, mask):
63 #m.d.comb += self.l_in.valid.eq(1)
64 #m.d.comb += self.l_in.load.eq(1)
65 #m.d.comb += self.l_in.addr.eq(addr)
66 m.d.comb += self.d_valid.eq(1)
67 m.d.comb += self.d_in.valid.eq(self.d_validblip)
68 m.d.comb += self.d_in.load.eq(1)
69 m.d.comb += self.d_in.byte_sel.eq(mask)
70 m.d.comb += self.d_in.addr.eq(addr)
71 # BAD HACK! disable cacheing on LD when address is 0xCxxx_xxxx
72 # this is for peripherals. same thing done in Microwatt loadstore1.vhdl
73 with m.If(addr[28:] == 0xc):
74 m.d.comb += self.d_in.nc.eq(1)
75 return None #FIXME return value
76
77 def set_wr_data(self, m, data, wen):
78 m.d.sync += self.d_in.data.eq(data) # one cycle **AFTER** valid raised
79 #m.d.sync += self.d_in.byte_sel.eq(wen) # ditto
80 st_ok = self.d_out.valid # indicates write data is valid
81 return st_ok
82
83 def get_rd_data(self, m):
84 ld_ok = self.d_out.valid # indicates read data is valid
85 data = self.d_out.data # actual read data
86 return data, ld_ok
87
88 """
89 if d_in.error = '1' then
90 if d_in.cache_paradox = '1' then
91 -- signal an interrupt straight away
92 exception := '1';
93 dsisr(63 - 38) := not r2.req.load;
94 -- XXX there is no architected bit for this
95 -- (probably should be a machine check in fact)
96 dsisr(63 - 35) := d_in.cache_paradox;
97 else
98 -- Look up the translation for TLB miss
99 -- and also for permission error and RC error
100 -- in case the PTE has been updated.
101 mmureq := '1';
102 v.state := MMU_LOOKUP;
103 v.stage1_en := '0';
104 end if;
105 end if;
106 """
107
108 def elaborate(self, platform):
109 m = super().elaborate(platform)
110 comb = m.d.comb
111
112 # create dcache module
113 m.submodules.dcache = dcache = self.dcache
114
115 # temp vars
116 d_out, l_out, dbus = self.d_out, self.l_out, self.dbus
117
118 with m.If(d_out.error):
119 with m.If(d_out.cache_paradox):
120 comb += self.derror.eq(1)
121 # dsisr(63 - 38) := not r2.req.load;
122 # -- XXX there is no architected bit for this
123 # -- (probably should be a machine check in fact)
124 # dsisr(63 - 35) := d_in.cache_paradox;
125 with m.Else():
126 # Look up the translation for TLB miss
127 # and also for permission error and RC error
128 # in case the PTE has been updated.
129 comb += self.mmureq.eq(1)
130 # v.state := MMU_LOOKUP;
131 # v.stage1_en := '0';
132
133 exc = self.pi.exception_o
134
135 #happened, alignment, instr_fault, invalid,
136 comb += exc.happened.eq(d_out.error | l_out.err)
137 comb += exc.invalid.eq(l_out.invalid)
138
139 #badtree, perm_error, rc_error, segment_fault
140 comb += exc.badtree.eq(l_out.badtree)
141 comb += exc.perm_error.eq(l_out.perm_error)
142 comb += exc.rc_error.eq(l_out.rc_error)
143 comb += exc.segment_fault.eq(l_out.segerr)
144
145 # TODO connect those signals somewhere
146 #print(d_out.valid) -> no error
147 #print(d_out.store_done) -> no error
148 #print(d_out.cache_paradox) -> ?
149 #print(l_out.done) -> no error
150
151 # TODO some exceptions set SPRs
152
153 # TODO, connect dcache wb_in/wb_out to "standard" nmigen Wishbone bus
154 comb += dbus.adr.eq(dcache.wb_out.adr)
155 comb += dbus.dat_w.eq(dcache.wb_out.dat)
156 comb += dbus.sel.eq(dcache.wb_out.sel)
157 comb += dbus.cyc.eq(dcache.wb_out.cyc)
158 comb += dbus.stb.eq(dcache.wb_out.stb)
159 comb += dbus.we.eq(dcache.wb_out.we)
160
161 comb += dcache.wb_in.dat.eq(dbus.dat_r)
162 comb += dcache.wb_in.ack.eq(dbus.ack)
163 if hasattr(dbus, "stall"):
164 comb += dcache.wb_in.stall.eq(dbus.stall)
165
166 # create a blip (single pulse) on valid read/write request
167 m.d.comb += self.d_validblip.eq(rising_edge(m, self.d_valid))
168
169 return m
170
171 def ports(self):
172 yield from super().ports()
173 # TODO: memory ports
174
175
176 class TestSRAMLoadStore1(LoadStore1):
177 def __init__(self, pspec):
178 super().__init__(pspec)
179 pspec = self.pspec
180 # small 32-entry Memory
181 if (hasattr(pspec, "dmem_test_depth") and
182 isinstance(pspec.dmem_test_depth, int)):
183 depth = pspec.dmem_test_depth
184 else:
185 depth = 32
186 print("TestSRAMBareLoadStoreUnit depth", depth)
187
188 self.mem = Memory(width=pspec.reg_wid, depth=depth)
189
190 def elaborate(self, platform):
191 m = super().elaborate(platform)
192 comb = m.d.comb
193 m.submodules.sram = sram = SRAM(memory=self.mem, granularity=8,
194 features={'cti', 'bte', 'err'})
195 dbus = self.dbus
196
197 # directly connect the wishbone bus of LoadStoreUnitInterface to SRAM
198 # note: SRAM is a target (slave), dbus is initiator (master)
199 fanouts = ['dat_w', 'sel', 'cyc', 'stb', 'we', 'cti', 'bte']
200 fanins = ['dat_r', 'ack', 'err']
201 for fanout in fanouts:
202 print("fanout", fanout, getattr(sram.bus, fanout).shape(),
203 getattr(dbus, fanout).shape())
204 comb += getattr(sram.bus, fanout).eq(getattr(dbus, fanout))
205 comb += getattr(sram.bus, fanout).eq(getattr(dbus, fanout))
206 for fanin in fanins:
207 comb += getattr(dbus, fanin).eq(getattr(sram.bus, fanin))
208 # connect address
209 comb += sram.bus.adr.eq(dbus.adr)
210
211 return m
212
213
214 class FSMMMUStage(ControlBase):
215 """FSM MMU
216
217 FSM-based MMU: must call set_ldst_interface and pass in an instance
218 of a LoadStore1. this to comply with the ConfigMemoryPortInterface API
219 """
220 def __init__(self, pspec):
221 super().__init__()
222 self.pspec = pspec
223
224 # set up p/n data
225 self.p.data_i = MMUInputData(pspec)
226 self.n.data_o = MMUOutputData(pspec)
227
228 # this Function Unit is extremely unusual in that it actually stores a
229 # "thing" rather than "processes inputs and produces outputs". hence
230 # why it has to be a FSM. linking up LD/ST however is going to have
231 # to be done back in Issuer (or Core)
232
233 self.mmu = MMU()
234
235 # make life a bit easier in Core XXX mustn't really do this,
236 # pspec is designed for config variables, rather than passing
237 # things around. have to think about it, design a way to do
238 # it that makes "sense"
239 # comment out for now self.pspec.mmu = self.mmu
240 # comment out for now self.pspec.dcache = self.dcache
241
242 # debugging output for gtkw
243 self.debug0 = Signal(4)
244 self.illegal = Signal()
245
246 # for SPR field number access
247 i = self.p.data_i
248 self.fields = DecodeFields(SignalBitRange, [i.ctx.op.insn])
249 self.fields.create_specs()
250
251 def set_ldst_interface(self, ldst):
252 """must be called back in Core, after FUs have been set up.
253 one of those will be the MMU (us!) but the LoadStore1 instance
254 must be set up in ConfigMemoryPortInterface. sigh.
255 """
256 # incoming PortInterface
257 self.ldst = ldst
258 self.dcache = self.ldst.dcache
259 self.pi = self.ldst.pi
260
261 def elaborate(self, platform):
262 assert hasattr(self, "dcache"), "remember to call set_ldst_interface"
263 m = super().elaborate(platform)
264 comb = m.d.comb
265 dcache = self.dcache
266
267 # link mmu and dcache together
268 m.submodules.mmu = mmu = self.mmu
269 ldst = self.ldst # managed externally: do not add here
270 m.d.comb += dcache.m_in.eq(mmu.d_out) # MMUToDCacheType
271 m.d.comb += mmu.d_in.eq(dcache.m_out) # DCacheToMMUType
272
273 l_in, l_out = mmu.l_in, mmu.l_out
274 d_in, d_out = dcache.d_in, dcache.d_out
275 wb_out, wb_in = dcache.wb_out, dcache.wb_in
276
277 # link ldst and MMU together
278 comb += l_in.eq(ldst.l_in)
279 comb += ldst.l_out.eq(l_out)
280
281 data_i, data_o = self.p.data_i, self.n.data_o
282 a_i, b_i, o, spr1_o = data_i.ra, data_i.rb, data_o.o, data_o.spr1
283 op = data_i.ctx.op
284 msr_i = op.msr
285
286 # TODO: link these SPRs somewhere
287 dsisr = Signal(64)
288 dar = Signal(64)
289
290 # busy/done signals
291 busy = Signal()
292 done = Signal()
293 m.d.comb += self.n.valid_o.eq(busy & done)
294 m.d.comb += self.p.ready_o.eq(~busy)
295
296 # take copy of X-Form SPR field
297 x_fields = self.fields.FormXFX
298 spr = Signal(len(x_fields.SPR))
299 comb += spr.eq(decode_spr_num(x_fields.SPR))
300
301 # based on MSR bits, set priv and virt mode. TODO: 32-bit mode
302 comb += d_in.priv_mode.eq(~msr_i[MSR.PR])
303 comb += d_in.virt_mode.eq(msr_i[MSR.DR])
304 #comb += d_in.mode_32bit.eq(msr_i[MSR.SF]) # ?? err
305
306 # ok so we have to "pulse" the MMU (or dcache) rather than
307 # hold the valid hi permanently. guess what this does...
308 valid = Signal()
309 blip = Signal()
310 m.d.comb += blip.eq(rising_edge(m, valid))
311
312 with m.If(~busy):
313 with m.If(self.p.valid_i):
314 m.d.sync += busy.eq(1)
315 with m.Else():
316
317 # based on the Micro-Op, we work out which of MMU or DCache
318 # should "action" the operation. one of MMU or DCache gets
319 # enabled ("valid") and we twiddle our thumbs until it
320 # responds ("done").
321
322 # FIXME: properly implement MicrOp.OP_MTSPR and MicrOp.OP_MFSPR
323
324 with m.Switch(op.insn_type):
325 with m.Case(MicrOp.OP_MTSPR):
326 # despite redirection this FU **MUST** behave exactly
327 # like the SPR FU. this **INCLUDES** updating the SPR
328 # regfile because the CSV file entry for OP_MTSPR
329 # categorically defines and requires the expectation
330 # that the CompUnit **WILL** write to the regfile.
331 comb += spr1_o.data.eq(spr)
332 comb += spr1_o.ok.eq(1)
333 # subset SPR: first check a few bits
334 with m.If(~spr[9] & ~spr[5]):
335 comb += self.debug0.eq(3)
336 with m.If(spr[0]):
337 comb += dsisr.eq(a_i[:32])
338 with m.Else():
339 comb += dar.eq(a_i)
340 comb += done.eq(1)
341 # pass it over to the MMU instead
342 with m.Else():
343 comb += self.debug0.eq(4)
344 # blip the MMU and wait for it to complete
345 comb += valid.eq(1) # start "pulse"
346 comb += l_in.valid.eq(blip) # start
347 comb += l_in.mtspr.eq(1) # mtspr mode
348 comb += l_in.sprn.eq(spr) # which SPR
349 comb += l_in.rs.eq(a_i) # incoming operand (RS)
350 comb += done.eq(1) # FIXME l_out.done
351
352 with m.Case(MicrOp.OP_MFSPR):
353 # subset SPR: first check a few bits
354 with m.If(~spr[9] & ~spr[5]):
355 comb += self.debug0.eq(5)
356 with m.If(spr[0]):
357 comb += o.data.eq(dsisr)
358 with m.Else():
359 comb += o.data.eq(dar)
360 comb += o.ok.eq(1)
361 comb += done.eq(1)
362 # pass it over to the MMU instead
363 with m.Else():
364 comb += self.debug0.eq(6)
365 # blip the MMU and wait for it to complete
366 comb += valid.eq(1) # start "pulse"
367 comb += l_in.valid.eq(blip) # start
368 comb += l_in.mtspr.eq(0) # mfspr!=mtspr
369 comb += l_in.sprn.eq(spr) # which SPR
370 comb += l_in.rs.eq(a_i) # incoming operand (RS)
371 comb += o.data.eq(l_out.sprval) # SPR from MMU
372 comb += o.ok.eq(l_out.done) # only when l_out valid
373 comb += done.eq(1) # FIXME l_out.done
374
375 # XXX this one is going to have to go through LDSTCompUnit
376 # because it's LDST that has control over dcache
377 # (through PortInterface). or, another means is devised
378 # so as not to have double-drivers of d_in.valid and addr
379 #
380 #with m.Case(MicrOp.OP_DCBZ):
381 # # activate dcbz mode (spec: v3.0B p850)
382 # comb += valid.eq(1) # start "pulse"
383 # comb += d_in.valid.eq(blip) # start
384 # comb += d_in.dcbz.eq(1) # dcbz mode
385 # comb += d_in.addr.eq(a_i + b_i) # addr is (RA|0) + RB
386 # comb += done.eq(d_out.store_done) # TODO
387 # comb += self.debug0.eq(1)
388
389 with m.Case(MicrOp.OP_TLBIE):
390 # pass TLBIE request to MMU (spec: v3.0B p1034)
391 # note that the spr is *not* an actual spr number, it's
392 # just that those bits happen to match with field bits
393 # RIC, PRS, R
394 comb += valid.eq(1) # start "pulse"
395 comb += l_in.valid.eq(blip) # start
396 comb += l_in.tlbie.eq(1) # mtspr mode
397 comb += l_in.sprn.eq(spr) # use sprn to send insn bits
398 comb += l_in.addr.eq(b_i) # incoming operand (RB)
399 comb += done.eq(l_out.done) # zzzz
400 comb += self.debug0.eq(2)
401
402 with m.Case(MicrOp.OP_ILLEGAL):
403 comb += self.illegal.eq(1)
404
405 with m.If(self.n.ready_i & self.n.valid_o):
406 m.d.sync += busy.eq(0)
407
408 return m
409
410 def __iter__(self):
411 yield from self.p
412 yield from self.n
413
414 def ports(self):
415 return list(self)