forgot to pull ld_o/st_o through from LDST CompUnits
[soc.git] / src / TLB / ariane / ptw.py
1 """
2 # Copyright 2018 ETH Zurich and University of Bologna.
3 # Copyright and related rights are licensed under the Solderpad Hardware
4 # License, Version 0.51 (the "License"); you may not use this file except in
5 # compliance with the License. You may obtain a copy of the License at
6 # http:#solderpad.org/licenses/SHL-0.51. Unless required by applicable law
7 # or agreed to in writing, software, hardware and materials distributed under
8 # this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
9 # CONDITIONS OF ANY KIND, either express or implied. See the License for the
10 # specific language governing permissions and limitations under the License.
11 #
12 # Author: David Schaffenrath, TU Graz
13 # Author: Florian Zaruba, ETH Zurich
14 # Date: 24.4.2017
15 # Description: Hardware-PTW
16
17 /* verilator lint_off WIDTH */
18 import ariane_pkg::*;
19
20 see linux kernel source:
21
22 * "arch/riscv/include/asm/page.h"
23 * "arch/riscv/include/asm/mmu_context.h"
24 * "arch/riscv/Kconfig" (CONFIG_PAGE_OFFSET)
25
26 """
27
28 from nmigen import Const, Signal, Cat, Module, Elaboratable
29 from nmigen.hdl.ast import ArrayProxy
30 from nmigen.cli import verilog, rtlil
31 from math import log2
32
33
34 DCACHE_SET_ASSOC = 8
35 CONFIG_L1D_SIZE = 32*1024
36 DCACHE_INDEX_WIDTH = int(log2(CONFIG_L1D_SIZE / DCACHE_SET_ASSOC))
37 DCACHE_TAG_WIDTH = 56 - DCACHE_INDEX_WIDTH
38
39 ASID_WIDTH = 8
40
41
42 class DCacheReqI:
43 def __init__(self):
44 self.address_index = Signal(DCACHE_INDEX_WIDTH)
45 self.address_tag = Signal(DCACHE_TAG_WIDTH)
46 self.data_wdata = Signal(64)
47 self.data_req = Signal()
48 self.data_we = Signal()
49 self.data_be = Signal(8)
50 self.data_size = Signal(2)
51 self.kill_req = Signal()
52 self.tag_valid = Signal()
53
54 def eq(self, inp):
55 res = []
56 for (o, i) in zip(self.ports(), inp.ports()):
57 res.append(o.eq(i))
58 return res
59
60 def ports(self):
61 return [self.address_index, self.address_tag,
62 self.data_wdata, self.data_req,
63 self.data_we, self.data_be, self.data_size,
64 self.kill_req, self.tag_valid,
65 ]
66
67 class DCacheReqO:
68 def __init__(self):
69 self.data_gnt = Signal()
70 self.data_rvalid = Signal()
71 self.data_rdata = Signal(64) # actually in PTE object format
72
73 def eq(self, inp):
74 res = []
75 for (o, i) in zip(self.ports(), inp.ports()):
76 res.append(o.eq(i))
77 return res
78
79 def ports(self):
80 return [self.data_gnt, self.data_rvalid, self.data_rdata]
81
82
83 class PTE: #(RecordObject):
84 def __init__(self):
85 self.v = Signal()
86 self.r = Signal()
87 self.w = Signal()
88 self.x = Signal()
89 self.u = Signal()
90 self.g = Signal()
91 self.a = Signal()
92 self.d = Signal()
93 self.rsw = Signal(2)
94 self.ppn = Signal(44)
95 self.reserved = Signal(10)
96
97 def flatten(self):
98 return Cat(*self.ports())
99
100 def eq(self, x):
101 if isinstance(x, ArrayProxy):
102 res = []
103 for o in self.ports():
104 i = getattr(x, o.name)
105 res.append(i)
106 x = Cat(*res)
107 else:
108 x = x.flatten()
109 return self.flatten().eq(x)
110
111 def __iter__(self):
112 """ order is critical so that flatten creates LSB to MSB
113 """
114 yield self.v
115 yield self.r
116 yield self.w
117 yield self.x
118 yield self.u
119 yield self.g
120 yield self.a
121 yield self.d
122 yield self.rsw
123 yield self.ppn
124 yield self.reserved
125
126 def ports(self):
127 return list(self)
128
129
130 class TLBUpdate:
131 def __init__(self, asid_width):
132 self.valid = Signal() # valid flag
133 self.is_2M = Signal()
134 self.is_1G = Signal()
135 self.vpn = Signal(27)
136 self.asid = Signal(asid_width)
137 self.content = PTE()
138
139 def flatten(self):
140 return Cat(*self.ports())
141
142 def eq(self, x):
143 return self.flatten().eq(x.flatten())
144
145 def ports(self):
146 return [self.valid, self.is_2M, self.is_1G, self.vpn, self.asid] + \
147 self.content.ports()
148
149
150 # SV39 defines three levels of page tables
151 LVL1 = Const(0, 2) # defined to 0 so that ptw_lvl default-resets to LVL1
152 LVL2 = Const(1, 2)
153 LVL3 = Const(2, 2)
154
155
156 class PTW(Elaboratable):
157 def __init__(self, asid_width=8):
158 self.asid_width = asid_width
159
160 self.flush_i = Signal() # flush everything, we need to do this because
161 # actually everything we do is speculative at this stage
162 # e.g.: there could be a CSR instruction that changes everything
163 self.ptw_active_o = Signal(reset=1) # active if not IDLE
164 self.walking_instr_o = Signal() # set when walking for TLB
165 self.ptw_error_o = Signal() # set when an error occurred
166 self.enable_translation_i = Signal() # CSRs indicate to enable SV39
167 self.en_ld_st_translation_i = Signal() # enable VM translation for ld/st
168
169 self.lsu_is_store_i = Signal() # translation triggered by store
170 # PTW memory interface
171 self.req_port_i = DCacheReqO()
172 self.req_port_o = DCacheReqI()
173
174 # to TLBs, update logic
175 self.itlb_update_o = TLBUpdate(asid_width)
176 self.dtlb_update_o = TLBUpdate(asid_width)
177
178 self.update_vaddr_o = Signal(39)
179
180 self.asid_i = Signal(self.asid_width)
181 # from TLBs
182 # did we miss?
183 self.itlb_access_i = Signal()
184 self.itlb_hit_i = Signal()
185 self.itlb_vaddr_i = Signal(64)
186
187 self.dtlb_access_i = Signal()
188 self.dtlb_hit_i = Signal()
189 self.dtlb_vaddr_i = Signal(64)
190 # from CSR file
191 self.satp_ppn_i = Signal(44) # ppn from satp
192 self.mxr_i = Signal()
193 # Performance counters
194 self.itlb_miss_o = Signal()
195 self.dtlb_miss_o = Signal()
196
197 def ports(self):
198 return [self.ptw_active_o, self.walking_instr_o, self.ptw_error_o,
199 ]
200 return [
201 self.enable_translation_i, self.en_ld_st_translation_i,
202 self.lsu_is_store_i, self.req_port_i, self.req_port_o,
203 self.update_vaddr_o,
204 self.asid_i,
205 self.itlb_access_i, self.itlb_hit_i, self.itlb_vaddr_i,
206 self.dtlb_access_i, self.dtlb_hit_i, self.dtlb_vaddr_i,
207 self.satp_ppn_i, self.mxr_i,
208 self.itlb_miss_o, self.dtlb_miss_o
209 ] + self.itlb_update_o.ports() + self.dtlb_update_o.ports()
210
211 def elaborate(self, platform):
212 m = Module()
213
214 # input registers
215 data_rvalid = Signal()
216 data_rdata = Signal(64)
217
218 # NOTE: pte decodes the incoming bit-field (data_rdata). data_rdata
219 # is spec'd in 64-bit binary-format: better to spec as Record?
220 pte = PTE()
221 m.d.comb += pte.flatten().eq(data_rdata)
222
223 # SV39 defines three levels of page tables
224 ptw_lvl = Signal(2) # default=0=LVL1 on reset (see above)
225 ptw_lvl1 = Signal()
226 ptw_lvl2 = Signal()
227 ptw_lvl3 = Signal()
228 m.d.comb += [ptw_lvl1.eq(ptw_lvl == LVL1),
229 ptw_lvl2.eq(ptw_lvl == LVL2),
230 ptw_lvl3.eq(ptw_lvl == LVL3)]
231
232 # is this an instruction page table walk?
233 is_instr_ptw = Signal()
234 global_mapping = Signal()
235 # latched tag signal
236 tag_valid = Signal()
237 # register the ASID
238 tlb_update_asid = Signal(self.asid_width)
239 # register VPN we need to walk, SV39 defines a 39 bit virtual addr
240 vaddr = Signal(64)
241 # 4 byte aligned physical pointer
242 ptw_pptr = Signal(56)
243
244 end = DCACHE_INDEX_WIDTH + DCACHE_TAG_WIDTH
245 m.d.sync += [
246 # Assignments
247 self.update_vaddr_o.eq(vaddr),
248
249 self.walking_instr_o.eq(is_instr_ptw),
250 # directly output the correct physical address
251 self.req_port_o.address_index.eq(ptw_pptr[0:DCACHE_INDEX_WIDTH]),
252 self.req_port_o.address_tag.eq(ptw_pptr[DCACHE_INDEX_WIDTH:end]),
253 # we are never going to kill this request
254 self.req_port_o.kill_req.eq(0), # XXX assign comb?
255 # we are never going to write with the HPTW
256 self.req_port_o.data_wdata.eq(Const(0, 64)), # XXX assign comb?
257 # -----------
258 # TLB Update
259 # -----------
260 self.itlb_update_o.vpn.eq(vaddr[12:39]),
261 self.dtlb_update_o.vpn.eq(vaddr[12:39]),
262 # update the correct page table level
263 self.itlb_update_o.is_2M.eq(ptw_lvl2),
264 self.itlb_update_o.is_1G.eq(ptw_lvl1),
265 self.dtlb_update_o.is_2M.eq(ptw_lvl2),
266 self.dtlb_update_o.is_1G.eq(ptw_lvl1),
267 # output the correct ASID
268 self.itlb_update_o.asid.eq(tlb_update_asid),
269 self.dtlb_update_o.asid.eq(tlb_update_asid),
270 # set the global mapping bit
271 self.itlb_update_o.content.eq(pte),
272 self.itlb_update_o.content.g.eq(global_mapping),
273 self.dtlb_update_o.content.eq(pte),
274 self.dtlb_update_o.content.g.eq(global_mapping),
275
276 self.req_port_o.tag_valid.eq(tag_valid),
277 ]
278
279 #-------------------
280 # Page table walker
281 #-------------------
282 # A virtual address va is translated into a physical address pa as
283 # follows:
284 # 1. Let a be sptbr.ppn × PAGESIZE, and let i = LEVELS-1. (For Sv39,
285 # PAGESIZE=2^12 and LEVELS=3.)
286 # 2. Let pte be the value of the PTE at address a+va.vpn[i]×PTESIZE.
287 # (For Sv32, PTESIZE=4.)
288 # 3. If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise an
289 # access exception.
290 # 4. Otherwise, the PTE is valid. If pte.r = 1 or pte.x = 1, go to
291 # step 5. Otherwise, this PTE is a pointer to the next level of
292 # the page table.
293 # Let i=i-1. If i < 0, stop and raise an access exception.
294 # Otherwise, let a = pte.ppn × PAGESIZE and go to step 2.
295 # 5. A leaf PTE has been found. Determine if the requested memory
296 # access is allowed by the pte.r, pte.w, and pte.x bits. If not,
297 # stop and raise an access exception. Otherwise, the translation is
298 # successful. Set pte.a to 1, and, if the memory access is a
299 # store, set pte.d to 1.
300 # The translated physical address is given as follows:
301 # - pa.pgoff = va.pgoff.
302 # - If i > 0, then this is a superpage translation and
303 # pa.ppn[i-1:0] = va.vpn[i-1:0].
304 # - pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i].
305 # 6. If i > 0 and pa.ppn[i − 1 : 0] != 0, this is a misaligned
306 # superpage stop and raise a page-fault exception.
307
308 m.d.sync += tag_valid.eq(0)
309
310 # default assignments
311 m.d.comb += [
312 # PTW memory interface
313 self.req_port_o.data_req.eq(0),
314 self.req_port_o.data_be.eq(Const(0xFF, 8)),
315 self.req_port_o.data_size.eq(Const(0b11, 2)),
316 self.req_port_o.data_we.eq(0),
317 self.ptw_error_o.eq(0),
318 self.itlb_update_o.valid.eq(0),
319 self.dtlb_update_o.valid.eq(0),
320
321 self.itlb_miss_o.eq(0),
322 self.dtlb_miss_o.eq(0),
323 ]
324
325 # ------------
326 # State Machine
327 # ------------
328
329 with m.FSM() as fsm:
330
331 with m.State("IDLE"):
332 self.idle(m, is_instr_ptw, ptw_lvl, global_mapping,
333 ptw_pptr, vaddr, tlb_update_asid)
334
335 with m.State("WAIT_GRANT"):
336 self.grant(m, tag_valid, data_rvalid)
337
338 with m.State("PTE_LOOKUP"):
339 # we wait for the valid signal
340 with m.If(data_rvalid):
341 self.lookup(m, pte, ptw_lvl, ptw_lvl1, ptw_lvl2, ptw_lvl3,
342 data_rvalid, global_mapping,
343 is_instr_ptw, ptw_pptr)
344
345 # Propagate error to MMU/LSU
346 with m.State("PROPAGATE_ERROR"):
347 m.next = "IDLE"
348 m.d.comb += self.ptw_error_o.eq(1)
349
350 # wait for the rvalid before going back to IDLE
351 with m.State("WAIT_RVALID"):
352 with m.If(data_rvalid):
353 m.next = "IDLE"
354
355 m.d.sync += [data_rdata.eq(self.req_port_i.data_rdata),
356 data_rvalid.eq(self.req_port_i.data_rvalid)
357 ]
358
359 return m
360
361 def set_grant_state(self, m):
362 # should we have flushed before we got an rvalid,
363 # wait for it until going back to IDLE
364 with m.If(self.flush_i):
365 with m.If (self.req_port_i.data_gnt):
366 m.next = "WAIT_RVALID"
367 with m.Else():
368 m.next = "IDLE"
369 with m.Else():
370 m.next = "WAIT_GRANT"
371
372 def idle(self, m, is_instr_ptw, ptw_lvl, global_mapping,
373 ptw_pptr, vaddr, tlb_update_asid):
374 # by default we start with the top-most page table
375 m.d.sync += [is_instr_ptw.eq(0),
376 ptw_lvl.eq(LVL1),
377 global_mapping.eq(0),
378 self.ptw_active_o.eq(0), # deactive (IDLE)
379 ]
380 # work out itlb/dtlb miss
381 m.d.comb += self.itlb_miss_o.eq(self.enable_translation_i & \
382 self.itlb_access_i & \
383 ~self.itlb_hit_i & \
384 ~self.dtlb_access_i)
385 m.d.comb += self.dtlb_miss_o.eq(self.en_ld_st_translation_i & \
386 self.dtlb_access_i & \
387 ~self.dtlb_hit_i)
388 # we got an ITLB miss?
389 with m.If(self.itlb_miss_o):
390 pptr = Cat(Const(0, 3), self.itlb_vaddr_i[30:39],
391 self.satp_ppn_i)
392 m.d.sync += [ptw_pptr.eq(pptr),
393 is_instr_ptw.eq(1),
394 vaddr.eq(self.itlb_vaddr_i),
395 tlb_update_asid.eq(self.asid_i),
396 ]
397 self.set_grant_state(m)
398
399 # we got a DTLB miss?
400 with m.Elif(self.dtlb_miss_o):
401 pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[30:39],
402 self.satp_ppn_i)
403 m.d.sync += [ptw_pptr.eq(pptr),
404 vaddr.eq(self.dtlb_vaddr_i),
405 tlb_update_asid.eq(self.asid_i),
406 ]
407 self.set_grant_state(m)
408
409 def grant(self, m, tag_valid, data_rvalid):
410 # we've got a data WAIT_GRANT so tell the
411 # cache that the tag is valid
412
413 # send a request out
414 m.d.comb += self.req_port_o.data_req.eq(1)
415 # wait for the WAIT_GRANT
416 with m.If(self.req_port_i.data_gnt):
417 # send the tag valid signal one cycle later
418 m.d.sync += tag_valid.eq(1)
419 # should we have flushed before we got an rvalid,
420 # wait for it until going back to IDLE
421 with m.If(self.flush_i):
422 with m.If (~data_rvalid):
423 m.next = "WAIT_RVALID"
424 with m.Else():
425 m.next = "IDLE"
426 with m.Else():
427 m.next = "PTE_LOOKUP"
428
429 def lookup(self, m, pte, ptw_lvl, ptw_lvl1, ptw_lvl2, ptw_lvl3,
430 data_rvalid, global_mapping,
431 is_instr_ptw, ptw_pptr):
432 # temporaries
433 pte_rx = Signal(reset_less=True)
434 pte_exe = Signal(reset_less=True)
435 pte_inv = Signal(reset_less=True)
436 pte_a = Signal(reset_less=True)
437 st_wd = Signal(reset_less=True)
438 m.d.comb += [pte_rx.eq(pte.r | pte.x),
439 pte_exe.eq(~pte.x | ~pte.a),
440 pte_inv.eq(~pte.v | (~pte.r & pte.w)),
441 pte_a.eq(pte.a & (pte.r | (pte.x & self.mxr_i))),
442 st_wd.eq(self.lsu_is_store_i & (~pte.w | ~pte.d))]
443
444 l1err = Signal(reset_less=True)
445 l2err = Signal(reset_less=True)
446 m.d.comb += [l2err.eq((ptw_lvl2) & pte.ppn[0:9] != Const(0, 9)),
447 l1err.eq((ptw_lvl1) & pte.ppn[0:18] != Const(0, 18)) ]
448
449 # check if the global mapping bit is set
450 with m.If (pte.g):
451 m.d.sync += global_mapping.eq(1)
452
453 m.next = "IDLE"
454
455 # -------------
456 # Invalid PTE
457 # -------------
458 # If pte.v = 0, or if pte.r = 0 and pte.w = 1,
459 # stop and raise a page-fault exception.
460 with m.If (pte_inv):
461 m.next = "PROPAGATE_ERROR"
462
463 # -----------
464 # Valid PTE
465 # -----------
466
467 # it is a valid PTE
468 # if pte.r = 1 or pte.x = 1 it is a valid PTE
469 with m.Elif (pte_rx):
470 # Valid translation found (either 1G, 2M or 4K)
471 with m.If(is_instr_ptw):
472 # ------------
473 # Update ITLB
474 # ------------
475 # If page not executable, we can directly raise error.
476 # This doesn't put a useless entry into the TLB.
477 # The same idea applies to the access flag since we let
478 # the access flag be managed by SW.
479 with m.If (pte_exe):
480 m.next = "IDLE"
481 with m.Else():
482 m.d.comb += self.itlb_update_o.valid.eq(1)
483
484 with m.Else():
485 # ------------
486 # Update DTLB
487 # ------------
488 # Check if the access flag has been set, otherwise
489 # throw page-fault and let software handle those bits.
490 # If page not readable (there are no write-only pages)
491 # directly raise an error. This doesn't put a useless
492 # entry into the TLB.
493 with m.If(pte_a):
494 m.d.comb += self.dtlb_update_o.valid.eq(1)
495 with m.Else():
496 m.next = "PROPAGATE_ERROR"
497 # Request is a store: perform additional checks
498 # If the request was a store and the page not
499 # write-able, raise an error
500 # the same applies if the dirty flag is not set
501 with m.If (st_wd):
502 m.d.comb += self.dtlb_update_o.valid.eq(0)
503 m.next = "PROPAGATE_ERROR"
504
505 # check if the ppn is correctly aligned: Case (6)
506 with m.If(l1err | l2err):
507 m.next = "PROPAGATE_ERROR"
508 m.d.comb += [self.dtlb_update_o.valid.eq(0),
509 self.itlb_update_o.valid.eq(0)]
510
511 # this is a pointer to the next TLB level
512 with m.Else():
513 # pointer to next level of page table
514 with m.If (ptw_lvl1):
515 # we are in the second level now
516 pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[21:30], pte.ppn)
517 m.d.sync += [ptw_pptr.eq(pptr),
518 ptw_lvl.eq(LVL2)
519 ]
520 with m.If(ptw_lvl2):
521 # here we received a pointer to the third level
522 pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[12:21], pte.ppn)
523 m.d.sync += [ptw_pptr.eq(pptr),
524 ptw_lvl.eq(LVL3)
525 ]
526 self.set_grant_state(m)
527
528 with m.If (ptw_lvl3):
529 # Should already be the last level
530 # page table => Error
531 m.d.sync += ptw_lvl.eq(LVL3)
532 m.next = "PROPAGATE_ERROR"
533
534
535 if __name__ == '__main__':
536 ptw = PTW()
537 vl = rtlil.convert(ptw, ports=ptw.ports())
538 with open("test_ptw.il", "w") as f:
539 f.write(vl)