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