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