remove unuses dsisr signal
[soc.git] / src / soc / fu / ldst / loadstore.py
1 """LoadStore1 FSM.
2
3 based on microwatt loadstore1.vhdl, but conforming to PortInterface.
4 unlike loadstore1.vhdl this does *not* deal with actual Load/Store
5 ops: that job is handled by LDSTCompUnit, which talks to LoadStore1
6 by way of PortInterface. PortInterface is where things need extending,
7 such as adding dcbz support, etc.
8
9 this module basically handles "pure" load / store operations, and
10 its first job is to ask the D-Cache for the data. if that fails,
11 the second task (if virtual memory is enabled) is to ask the MMU
12 to perform a TLB, then to go *back* to the cache and ask again.
13
14 Links:
15
16 * https://bugs.libre-soc.org/show_bug.cgi?id=465
17
18 """
19
20 from nmigen import (Elaboratable, Module, Signal, Shape, unsigned, Cat, Mux,
21 Record, Memory,
22 Const)
23 from nmutil.iocontrol import RecordObject
24 from nmutil.util import rising_edge, Display
25 from enum import Enum, unique
26
27 from soc.experiment.dcache import DCache
28 from soc.experiment.pimem import PortInterfaceBase
29 from soc.experiment.mem_types import LoadStore1ToMMUType
30 from soc.experiment.mem_types import MMUToLoadStore1Type
31
32 from soc.minerva.wishbone import make_wb_layout
33 from soc.bus.sram import SRAM
34 from nmutil.util import Display
35
36
37 @unique
38 class State(Enum):
39 IDLE = 0 # ready for instruction
40 ACK_WAIT = 1 # waiting for ack from dcache
41 MMU_LOOKUP = 2 # waiting for MMU to look up translation
42 TLBIE_WAIT = 3 # waiting for MMU to finish doing a tlbie
43
44
45 # captures the LDSTRequest from the PortInterface, which "blips" most
46 # of this at us (pipeline-style).
47 class LDSTRequest(RecordObject):
48 def __init__(self, name=None):
49 RecordObject.__init__(self, name=name)
50
51 self.load = Signal()
52 self.dcbz = Signal()
53 self.addr = Signal(64)
54 # self.store_data = Signal(64) # this is already sync (on a delay)
55 self.byte_sel = Signal(8)
56 self.nc = Signal() # non-cacheable access
57 self.virt_mode = Signal()
58 self.priv_mode = Signal()
59 self.align_intr = Signal()
60
61 # glue logic for microwatt mmu and dcache
62 class LoadStore1(PortInterfaceBase):
63 def __init__(self, pspec):
64 self.pspec = pspec
65 self.disable_cache = (hasattr(pspec, "disable_cache") and
66 pspec.disable_cache == True)
67 regwid = pspec.reg_wid
68 addrwid = pspec.addr_wid
69
70 super().__init__(regwid, addrwid)
71 self.dcache = DCache()
72 # these names are from the perspective of here (LoadStore1)
73 self.d_out = self.dcache.d_in # in to dcache is out for LoadStore
74 self.d_in = self.dcache.d_out # out from dcache is in for LoadStore
75 self.m_out = LoadStore1ToMMUType() # out *to* MMU
76 self.m_in = MMUToLoadStore1Type() # in *from* MMU
77 self.req = LDSTRequest(name="ldst_req")
78
79 # TODO, convert dcache wb_in/wb_out to "standard" nmigen Wishbone bus
80 self.dbus = Record(make_wb_layout(pspec))
81
82 # for creating a single clock blip to DCache
83 self.d_valid = Signal()
84 self.d_w_valid = Signal()
85 self.d_validblip = Signal()
86
87 # state info for LD/ST
88 self.done = Signal()
89 # latch most of the input request
90 self.load = Signal()
91 self.tlbie = Signal()
92 self.dcbz = Signal()
93 self.addr = Signal(64)
94 self.store_data = Signal(64)
95 self.load_data = Signal(64)
96 self.byte_sel = Signal(8)
97 #self.xerc : xer_common_t;
98 #self.reserve = Signal()
99 #self.atomic = Signal()
100 #self.atomic_last = Signal()
101 #self.rc = Signal()
102 self.nc = Signal() # non-cacheable access
103 self.virt_mode = Signal()
104 self.priv_mode = Signal()
105 self.state = Signal(State)
106 self.instr_fault = Signal()
107 self.align_intr = Signal()
108 self.busy = Signal()
109 self.wait_dcache = Signal()
110 self.wait_mmu = Signal()
111 #self.mode_32bit = Signal()
112 #self.intr_vec : integer range 0 to 16#fff#;
113 #self.nia = Signal(64)
114 #self.srr1 = Signal(16)
115
116 def set_wr_addr(self, m, addr, mask, misalign, msr_pr, is_dcbz):
117 m.d.comb += self.req.load.eq(0) # store operation
118 m.d.comb += self.req.byte_sel.eq(mask)
119 m.d.comb += self.req.addr.eq(addr)
120 m.d.comb += self.req.priv_mode.eq(~msr_pr) # not-problem ==> priv
121 m.d.comb += self.req.virt_mode.eq(msr_pr) # problem-state ==> virt
122 m.d.comb += self.req.align_intr.eq(misalign)
123 m.d.comb += self.req.dcbz.eq(is_dcbz)
124
125 # m.d.comb += Display("set_wr_addr %i dcbz %i",addr,is_dcbz)
126
127 # option to disable the cache entirely for write
128 if self.disable_cache:
129 m.d.comb += self.req.nc.eq(1)
130 return None
131
132 def set_rd_addr(self, m, addr, mask, misalign, msr_pr):
133 m.d.comb += self.d_valid.eq(1)
134 m.d.comb += self.req.load.eq(1) # load operation
135 m.d.comb += self.req.byte_sel.eq(mask)
136 m.d.comb += self.req.align_intr.eq(misalign)
137 m.d.comb += self.req.addr.eq(addr)
138 m.d.comb += self.req.priv_mode.eq(~msr_pr) # not-problem ==> priv
139 m.d.comb += self.req.virt_mode.eq(msr_pr) # problem-state ==> virt
140 # BAD HACK! disable cacheing on LD when address is 0xCxxx_xxxx
141 # this is for peripherals. same thing done in Microwatt loadstore1.vhdl
142 with m.If(addr[28:] == Const(0xc, 4)):
143 m.d.comb += self.req.nc.eq(1)
144 # option to disable the cache entirely for read
145 if self.disable_cache:
146 m.d.comb += self.req.nc.eq(1)
147 return None #FIXME return value
148
149 def set_wr_data(self, m, data, wen):
150 # do the "blip" on write data
151 m.d.comb += self.d_valid.eq(1)
152 # put data into comb which is picked up in main elaborate()
153 m.d.comb += self.d_w_valid.eq(1)
154 m.d.comb += self.store_data.eq(data)
155 #m.d.sync += self.d_out.byte_sel.eq(wen) # this might not be needed
156 st_ok = self.done # TODO indicates write data is valid
157 return st_ok
158
159 def get_rd_data(self, m):
160 ld_ok = self.done # indicates read data is valid
161 data = self.load_data # actual read data
162 return data, ld_ok
163
164 def elaborate(self, platform):
165 m = super().elaborate(platform)
166 comb, sync = m.d.comb, m.d.sync
167
168 # create dcache module
169 m.submodules.dcache = dcache = self.dcache
170
171 # temp vars
172 d_out, d_in, dbus = self.d_out, self.d_in, self.dbus
173 m_out, m_in = self.m_out, self.m_in
174 exc = self.pi.exc_o
175 exception = exc.happened
176 mmureq = Signal()
177
178 # copy of address, but gets over-ridden for OP_FETCH_FAILED
179 maddr = Signal(64)
180 m.d.comb += maddr.eq(self.addr)
181
182 # create a blip (single pulse) on valid read/write request
183 # this can be over-ridden in the FSM to get dcache to re-run
184 # a request when MMU_LOOKUP completes.
185 m.d.comb += self.d_validblip.eq(rising_edge(m, self.d_valid))
186 ldst_r = LDSTRequest("ldst_r")
187 comb += Display("MMUTEST: LoadStore1 d_in.error=%i",d_in.error)
188
189 # fsm skeleton
190 with m.Switch(self.state):
191 with m.Case(State.IDLE):
192 with m.If(self.d_validblip & ~exc.happened):
193 comb += self.busy.eq(1)
194 sync += self.state.eq(State.ACK_WAIT)
195 sync += ldst_r.eq(self.req) # copy of LDSTRequest on "blip"
196 # sync += Display("validblip self.req.virt_mode=%i",
197 # self.req.virt_mode)
198 with m.Else():
199 sync += ldst_r.eq(0)
200
201 # waiting for completion
202 with m.Case(State.ACK_WAIT):
203 comb += Display("MMUTEST: ACK_WAIT")
204 comb += self.busy.eq(~exc.happened)
205
206 with m.If(d_in.error):
207 # cache error is not necessarily "final", it could
208 # be that it was just a TLB miss
209 with m.If(d_in.cache_paradox):
210 comb += exception.eq(1)
211 sync += self.state.eq(State.IDLE)
212 sync += ldst_r.eq(0)
213 sync += Display("cache error -> update dsisr")
214 #sync += self.dsisr[63 - 38].eq(~self.load)
215 # XXX there is no architected bit for this
216 # (probably should be a machine check in fact)
217 #sync += self.dsisr[63 - 35].eq(d_in.cache_paradox)
218
219 with m.Else():
220 # Look up the translation for TLB miss
221 # and also for permission error and RC error
222 # in case the PTE has been updated.
223 comb += mmureq.eq(1)
224 sync += self.state.eq(State.MMU_LOOKUP)
225 with m.If(d_in.valid):
226 m.d.comb += self.done.eq(~mmureq) # done if not doing MMU
227 with m.If(self.done):
228 sync += Display("ACK_WAIT, done %x", self.addr)
229 sync += self.state.eq(State.IDLE)
230 sync += ldst_r.eq(0)
231 with m.If(self.load):
232 m.d.comb += self.load_data.eq(d_in.data)
233
234 # waiting here for the MMU TLB lookup to complete.
235 # either re-try the dcache lookup or throw MMU exception
236 with m.Case(State.MMU_LOOKUP):
237 comb += self.busy.eq(1)
238 with m.If(m_in.done):
239 with m.If(~self.instr_fault):
240 sync += Display("MMU_LOOKUP, done %x -> %x",
241 self.addr, d_out.addr)
242 # retry the request now that the MMU has
243 # installed a TLB entry, if not exception raised
244 m.d.comb += self.d_out.valid.eq(~exception)
245 sync += self.state.eq(State.ACK_WAIT)
246 sync += ldst_r.eq(0)
247 with m.Else():
248 sync += Display("MMU_LOOKUP, exception %x", self.addr)
249 # instruction lookup fault: store address in DAR
250 comb += exc.happened.eq(1) # reason = MMU_LOOKUP
251 # mark dar as updated ?
252 comb += self.pi.dar_o.eq(self.addr)
253 sync += self.state.eq(State.IDLE)
254
255 with m.If(m_in.err):
256 # MMU RADIX exception thrown
257 comb += exception.eq(1)
258 sync += Display("MMU RADIX exception thrown")
259 #sync += self.dsisr[63 - 33].eq(m_in.invalid)
260 #sync += self.dsisr[63 - 36].eq(m_in.perm_error)
261 #sync += self.dsisr[63 - 38].eq(self.load)
262 #sync += self.dsisr[63 - 44].eq(m_in.badtree)
263 #sync += self.dsisr[63 - 45].eq(m_in.rc_error)
264 sync += self.state.eq(State.IDLE)
265
266 with m.Case(State.TLBIE_WAIT):
267 pass
268
269 # alignment error: store address in DAR
270 with m.If(self.align_intr):
271 comb += exc.happened.eq(1) # reason = alignment
272 sync += Display("alignment error: store addr in DAR %x", self.addr)
273 comb += self.pi.dar_o.eq(self.addr)
274
275 # happened, alignment, instr_fault, invalid.
276 # note that all of these flow through - eventually to the TRAP
277 # pipeline, via PowerDecoder2.
278 comb += exc.invalid.eq(m_in.invalid)
279 comb += exc.alignment.eq(self.align_intr)
280 comb += exc.instr_fault.eq(self.instr_fault)
281 # badtree, perm_error, rc_error, segment_fault
282 comb += exc.badtree.eq(m_in.badtree)
283 comb += exc.perm_error.eq(m_in.perm_error)
284 comb += exc.rc_error.eq(m_in.rc_error)
285 comb += exc.segment_fault.eq(m_in.segerr)
286
287 # TODO, connect dcache wb_in/wb_out to "standard" nmigen Wishbone bus
288 comb += dbus.adr.eq(dcache.wb_out.adr)
289 comb += dbus.dat_w.eq(dcache.wb_out.dat)
290 comb += dbus.sel.eq(dcache.wb_out.sel)
291 comb += dbus.cyc.eq(dcache.wb_out.cyc)
292 comb += dbus.stb.eq(dcache.wb_out.stb)
293 comb += dbus.we.eq(dcache.wb_out.we)
294
295 comb += dcache.wb_in.dat.eq(dbus.dat_r)
296 comb += dcache.wb_in.ack.eq(dbus.ack)
297 if hasattr(dbus, "stall"):
298 comb += dcache.wb_in.stall.eq(dbus.stall)
299
300 # update out d data when flag set
301 with m.If(self.d_w_valid):
302 m.d.sync += d_out.data.eq(self.store_data)
303 #with m.Else():
304 # m.d.sync += d_out.data.eq(0)
305 # unit test passes with that change
306
307 # this must move into the FSM, conditionally noticing that
308 # the "blip" comes from self.d_validblip.
309 # task 1: look up in dcache
310 # task 2: if dcache fails, look up in MMU.
311 # do **NOT** confuse the two.
312 with m.If(self.d_validblip):
313 m.d.comb += self.d_out.valid.eq(~exc.happened)
314 m.d.comb += d_out.load.eq(self.req.load)
315 m.d.comb += d_out.byte_sel.eq(self.req.byte_sel)
316 m.d.comb += self.addr.eq(self.req.addr)
317 m.d.comb += d_out.nc.eq(self.req.nc)
318 m.d.comb += d_out.priv_mode.eq(self.req.priv_mode)
319 m.d.comb += d_out.virt_mode.eq(self.req.virt_mode)
320 m.d.comb += self.align_intr.eq(self.req.align_intr)
321 #m.d.comb += Display("validblip dcbz=%i addr=%x",
322 #self.req.dcbz,self.req.addr)
323 m.d.comb += d_out.dcbz.eq(self.req.dcbz)
324 with m.Else():
325 m.d.comb += d_out.load.eq(ldst_r.load)
326 m.d.comb += d_out.byte_sel.eq(ldst_r.byte_sel)
327 m.d.comb += self.addr.eq(ldst_r.addr)
328 m.d.comb += d_out.nc.eq(ldst_r.nc)
329 m.d.comb += d_out.priv_mode.eq(ldst_r.priv_mode)
330 m.d.comb += d_out.virt_mode.eq(ldst_r.virt_mode)
331 m.d.comb += self.align_intr.eq(ldst_r.align_intr)
332 #m.d.comb += Display("no_validblip dcbz=%i addr=%x",
333 #ldst_r.dcbz,ldst_r.addr)
334 m.d.comb += d_out.dcbz.eq(ldst_r.dcbz)
335
336 # XXX these should be possible to remove but for some reason
337 # cannot be... yet. TODO, investigate
338 m.d.comb += self.load_data.eq(d_in.data)
339 m.d.comb += d_out.addr.eq(self.addr)
340
341 # Update outputs to MMU
342 m.d.comb += m_out.valid.eq(mmureq)
343 m.d.comb += m_out.iside.eq(self.instr_fault)
344 m.d.comb += m_out.load.eq(ldst_r.load)
345 # m_out.priv <= r.priv_mode; TODO
346 m.d.comb += m_out.tlbie.eq(self.tlbie)
347 # m_out.mtspr <= mmu_mtspr; # TODO
348 # m_out.sprn <= sprn; # TODO
349 m.d.comb += m_out.addr.eq(maddr)
350 # m_out.slbia <= l_in.insn(7); # TODO: no idea what this is
351 # m_out.rs <= l_in.data; # nope, probably not needed, TODO investigate
352
353 return m
354
355 def ports(self):
356 yield from super().ports()
357 # TODO: memory ports
358
359
360 class TestSRAMLoadStore1(LoadStore1):
361 def __init__(self, pspec):
362 super().__init__(pspec)
363 pspec = self.pspec
364 # small 32-entry Memory
365 if (hasattr(pspec, "dmem_test_depth") and
366 isinstance(pspec.dmem_test_depth, int)):
367 depth = pspec.dmem_test_depth
368 else:
369 depth = 32
370 print("TestSRAMBareLoadStoreUnit depth", depth)
371
372 self.mem = Memory(width=pspec.reg_wid, depth=depth)
373
374 def elaborate(self, platform):
375 m = super().elaborate(platform)
376 comb = m.d.comb
377 m.submodules.sram = sram = SRAM(memory=self.mem, granularity=8,
378 features={'cti', 'bte', 'err'})
379 dbus = self.dbus
380
381 # directly connect the wishbone bus of LoadStoreUnitInterface to SRAM
382 # note: SRAM is a target (slave), dbus is initiator (master)
383 fanouts = ['dat_w', 'sel', 'cyc', 'stb', 'we', 'cti', 'bte']
384 fanins = ['dat_r', 'ack', 'err']
385 for fanout in fanouts:
386 print("fanout", fanout, getattr(sram.bus, fanout).shape(),
387 getattr(dbus, fanout).shape())
388 comb += getattr(sram.bus, fanout).eq(getattr(dbus, fanout))
389 comb += getattr(sram.bus, fanout).eq(getattr(dbus, fanout))
390 for fanin in fanins:
391 comb += getattr(dbus, fanin).eq(getattr(sram.bus, fanin))
392 # connect address
393 comb += sram.bus.adr.eq(dbus.adr)
394
395 return m
396