c632194ac994bfc392f597023424a8b4a1c0ffee
[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 # SV48 defines four 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 LVL4 = Const(3, 2)
155
156
157 class PTW(Elaboratable):
158 def __init__(self, asid_width=8):
159 self.asid_width = asid_width
160
161 self.flush_i = Signal() # flush everything, we need to do this because
162 # actually everything we do is speculative at this stage
163 # e.g.: there could be a CSR instruction that changes everything
164 self.ptw_active_o = Signal(reset=1) # active if not IDLE
165 self.walking_instr_o = Signal() # set when walking for TLB
166 self.ptw_error_o = Signal() # set when an error occurred
167 self.enable_translation_i = Signal() # CSRs indicate to enable SV48
168 self.en_ld_st_translation_i = Signal() # enable VM translation for ld/st
169
170 self.lsu_is_store_i = Signal() # translation triggered by store
171 # PTW memory interface
172 self.req_port_i = DCacheReqO()
173 self.req_port_o = DCacheReqI()
174
175 # to TLBs, update logic
176 self.itlb_update_o = TLBUpdate(asid_width)
177 self.dtlb_update_o = TLBUpdate(asid_width)
178
179 self.update_vaddr_o = Signal(48)
180
181 self.asid_i = Signal(self.asid_width)
182 # from TLBs
183 # did we miss?
184 self.itlb_access_i = Signal()
185 self.itlb_hit_i = Signal()
186 self.itlb_vaddr_i = Signal(64)
187
188 self.dtlb_access_i = Signal()
189 self.dtlb_hit_i = Signal()
190 self.dtlb_vaddr_i = Signal(64)
191 # from CSR file
192 self.satp_ppn_i = Signal(44) # ppn from satp
193 self.mxr_i = Signal()
194 # Performance counters
195 self.itlb_miss_o = Signal()
196 self.dtlb_miss_o = Signal()
197
198 def ports(self):
199 return [self.ptw_active_o, self.walking_instr_o, self.ptw_error_o,
200 ]
201 return [
202 self.enable_translation_i, self.en_ld_st_translation_i,
203 self.lsu_is_store_i, self.req_port_i, self.req_port_o,
204 self.update_vaddr_o,
205 self.asid_i,
206 self.itlb_access_i, self.itlb_hit_i, self.itlb_vaddr_i,
207 self.dtlb_access_i, self.dtlb_hit_i, self.dtlb_vaddr_i,
208 self.satp_ppn_i, self.mxr_i,
209 self.itlb_miss_o, self.dtlb_miss_o
210 ] + self.itlb_update_o.ports() + self.dtlb_update_o.ports()
211
212 def elaborate(self, platform):
213 m = Module()
214
215 # input registers
216 data_rvalid = Signal()
217 data_rdata = Signal(64)
218
219 # NOTE: pte decodes the incoming bit-field (data_rdata). data_rdata
220 # is spec'd in 64-bit binary-format: better to spec as Record?
221 pte = PTE()
222 m.d.comb += pte.flatten().eq(data_rdata)
223
224 # SV48 defines four levels of page tables
225 ptw_lvl = Signal(2) # default=0=LVL1 on reset (see above)
226 ptw_lvl1 = Signal()
227 ptw_lvl2 = Signal()
228 ptw_lvl3 = Signal()
229 ptw_lvl4 = Signal()
230 m.d.comb += [ptw_lvl1.eq(ptw_lvl == LVL1),
231 ptw_lvl2.eq(ptw_lvl == LVL2),
232 ptw_lvl3.eq(ptw_lvl == LVL3),
233 ptw_lvl4.eq(ptw_lvl == LVL4)
234 ]
235
236 # is this an instruction page table walk?
237 is_instr_ptw = Signal()
238 global_mapping = Signal()
239 # latched tag signal
240 tag_valid = Signal()
241 # register the ASID
242 tlb_update_asid = Signal(self.asid_width)
243 # register VPN we need to walk, SV48 defines a 48 bit virtual addr
244 vaddr = Signal(64)
245 # 4 byte aligned physical pointer
246 ptw_pptr = Signal(56)
247
248 end = DCACHE_INDEX_WIDTH + DCACHE_TAG_WIDTH
249 m.d.sync += [
250 # Assignments
251 self.update_vaddr_o.eq(vaddr),
252
253 self.walking_instr_o.eq(is_instr_ptw),
254 # directly output the correct physical address
255 self.req_port_o.address_index.eq(ptw_pptr[0:DCACHE_INDEX_WIDTH]),
256 self.req_port_o.address_tag.eq(ptw_pptr[DCACHE_INDEX_WIDTH:end]),
257 # we are never going to kill this request
258 self.req_port_o.kill_req.eq(0), # XXX assign comb?
259 # we are never going to write with the HPTW
260 self.req_port_o.data_wdata.eq(Const(0, 64)), # XXX assign comb?
261 # -----------
262 # TLB Update
263 # -----------
264 self.itlb_update_o.vpn.eq(vaddr[12:48]),
265 self.dtlb_update_o.vpn.eq(vaddr[12:48]),
266 #TODO_PLATEN: extend by 9 bits
267 # update the correct page table level
268 self.itlb_update_o.is_2M.eq(ptw_lvl2),
269 self.itlb_update_o.is_1G.eq(ptw_lvl1),
270 self.dtlb_update_o.is_2M.eq(ptw_lvl2),
271 self.dtlb_update_o.is_1G.eq(ptw_lvl1),
272 # output the correct ASID
273 self.itlb_update_o.asid.eq(tlb_update_asid),
274 self.dtlb_update_o.asid.eq(tlb_update_asid),
275 # set the global mapping bit
276 self.itlb_update_o.content.eq(pte),
277 self.itlb_update_o.content.g.eq(global_mapping),
278 self.dtlb_update_o.content.eq(pte),
279 self.dtlb_update_o.content.g.eq(global_mapping),
280
281 self.req_port_o.tag_valid.eq(tag_valid),
282 ]
283
284 #-------------------
285 # Page table walker #needs update
286 #-------------------
287 # A virtual address va is translated into a physical address pa as
288 # follows:
289 # 1. Let a be sptbr.ppn × PAGESIZE, and let i = LEVELS-1. (For Sv48,
290 # PAGESIZE=2^12 and LEVELS=4.)
291 # 2. Let pte be the value of the PTE at address a+va.vpn[i]×PTESIZE.
292 # (For Sv32, PTESIZE=4.)
293 # 3. If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise an
294 # access exception.
295 # 4. Otherwise, the PTE is valid. If pte.r = 1 or pte.x = 1, go to
296 # step 5. Otherwise, this PTE is a pointer to the next level of
297 # the page table.
298 # Let i=i-1. If i < 0, stop and raise an access exception.
299 # Otherwise, let a = pte.ppn × PAGESIZE and go to step 2.
300 # 5. A leaf PTE has been found. Determine if the requested memory
301 # access is allowed by the pte.r, pte.w, and pte.x bits. If not,
302 # stop and raise an access exception. Otherwise, the translation is
303 # successful. Set pte.a to 1, and, if the memory access is a
304 # store, set pte.d to 1.
305 # The translated physical address is given as follows:
306 # - pa.pgoff = va.pgoff.
307 # - If i > 0, then this is a superpage translation and
308 # pa.ppn[i-1:0] = va.vpn[i-1:0].
309 # - pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i].
310 # 6. If i > 0 and pa.ppn[i − 1 : 0] != 0, this is a misaligned
311 # superpage stop and raise a page-fault exception.
312
313 m.d.sync += tag_valid.eq(0)
314
315 # default assignments
316 m.d.comb += [
317 # PTW memory interface
318 self.req_port_o.data_req.eq(0),
319 self.req_port_o.data_be.eq(Const(0xFF, 8)),
320 self.req_port_o.data_size.eq(Const(0b11, 2)),
321 self.req_port_o.data_we.eq(0),
322 self.ptw_error_o.eq(0),
323 self.itlb_update_o.valid.eq(0),
324 self.dtlb_update_o.valid.eq(0),
325
326 self.itlb_miss_o.eq(0),
327 self.dtlb_miss_o.eq(0),
328 ]
329
330 # ------------
331 # State Machine
332 # ------------
333
334 with m.FSM() as fsm:
335
336 with m.State("IDLE"):
337 self.idle(m, is_instr_ptw, ptw_lvl, global_mapping,
338 ptw_pptr, vaddr, tlb_update_asid)
339
340 with m.State("WAIT_GRANT"):
341 self.grant(m, tag_valid, data_rvalid)
342
343 with m.State("PTE_LOOKUP"):
344 # we wait for the valid signal
345 with m.If(data_rvalid):
346 self.lookup(m, pte, ptw_lvl, ptw_lvl1, ptw_lvl2, ptw_lvl3, ptw_lvl4,
347 data_rvalid, global_mapping,
348 is_instr_ptw, ptw_pptr)
349
350 # Propagate error to MMU/LSU
351 with m.State("PROPAGATE_ERROR"):
352 m.next = "IDLE"
353 m.d.comb += self.ptw_error_o.eq(1)
354
355 # wait for the rvalid before going back to IDLE
356 with m.State("WAIT_RVALID"):
357 with m.If(data_rvalid):
358 m.next = "IDLE"
359
360 m.d.sync += [data_rdata.eq(self.req_port_i.data_rdata),
361 data_rvalid.eq(self.req_port_i.data_rvalid)
362 ]
363
364 return m
365
366 def set_grant_state(self, m):
367 # should we have flushed before we got an rvalid,
368 # wait for it until going back to IDLE
369 with m.If(self.flush_i):
370 with m.If (self.req_port_i.data_gnt):
371 m.next = "WAIT_RVALID"
372 with m.Else():
373 m.next = "IDLE"
374 with m.Else():
375 m.next = "WAIT_GRANT"
376
377 def idle(self, m, is_instr_ptw, ptw_lvl, global_mapping,
378 ptw_pptr, vaddr, tlb_update_asid):
379 # by default we start with the top-most page table
380 m.d.sync += [is_instr_ptw.eq(0),
381 ptw_lvl.eq(LVL1),
382 global_mapping.eq(0),
383 self.ptw_active_o.eq(0), # deactive (IDLE)
384 ]
385 # work out itlb/dtlb miss
386 m.d.comb += self.itlb_miss_o.eq(self.enable_translation_i & \
387 self.itlb_access_i & \
388 ~self.itlb_hit_i & \
389 ~self.dtlb_access_i)
390 m.d.comb += self.dtlb_miss_o.eq(self.en_ld_st_translation_i & \
391 self.dtlb_access_i & \
392 ~self.dtlb_hit_i)
393 # we got an ITLB miss?
394 with m.If(self.itlb_miss_o):
395 pptr = Cat(Const(0, 3), self.itlb_vaddr_i[30:48],
396 self.satp_ppn_i)
397 m.d.sync += [ptw_pptr.eq(pptr),
398 is_instr_ptw.eq(1),
399 vaddr.eq(self.itlb_vaddr_i),
400 tlb_update_asid.eq(self.asid_i),
401 ]
402 self.set_grant_state(m)
403
404 # we got a DTLB miss?
405 with m.Elif(self.dtlb_miss_o):
406 pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[30:48],
407 self.satp_ppn_i)
408 m.d.sync += [ptw_pptr.eq(pptr),
409 vaddr.eq(self.dtlb_vaddr_i),
410 tlb_update_asid.eq(self.asid_i),
411 ]
412 self.set_grant_state(m)
413
414 def grant(self, m, tag_valid, data_rvalid):
415 # we've got a data WAIT_GRANT so tell the
416 # cache that the tag is valid
417
418 # send a request out
419 m.d.comb += self.req_port_o.data_req.eq(1)
420 # wait for the WAIT_GRANT
421 with m.If(self.req_port_i.data_gnt):
422 # send the tag valid signal one cycle later
423 m.d.sync += tag_valid.eq(1)
424 # should we have flushed before we got an rvalid,
425 # wait for it until going back to IDLE
426 with m.If(self.flush_i):
427 with m.If (~data_rvalid):
428 m.next = "WAIT_RVALID"
429 with m.Else():
430 m.next = "IDLE"
431 with m.Else():
432 m.next = "PTE_LOOKUP"
433
434 def lookup(self, m, pte, ptw_lvl, ptw_lvl1, ptw_lvl2, ptw_lvl3, ptw_lvl4,
435 data_rvalid, global_mapping,
436 is_instr_ptw, ptw_pptr):
437 # temporaries
438 pte_rx = Signal(reset_less=True)
439 pte_exe = Signal(reset_less=True)
440 pte_inv = Signal(reset_less=True)
441 pte_a = Signal(reset_less=True)
442 st_wd = Signal(reset_less=True)
443 m.d.comb += [pte_rx.eq(pte.r | pte.x),
444 pte_exe.eq(~pte.x | ~pte.a),
445 pte_inv.eq(~pte.v | (~pte.r & pte.w)),
446 pte_a.eq(pte.a & (pte.r | (pte.x & self.mxr_i))),
447 st_wd.eq(self.lsu_is_store_i & (~pte.w | ~pte.d))]
448
449 l1err = Signal(reset_less=True)
450 l2err = Signal(reset_less=True)
451 m.d.comb += [l2err.eq((ptw_lvl2) & pte.ppn[0:9] != Const(0, 9)),
452 l1err.eq((ptw_lvl1) & pte.ppn[0:18] != Const(0, 18)) ]
453
454 # check if the global mapping bit is set
455 with m.If (pte.g):
456 m.d.sync += global_mapping.eq(1)
457
458 m.next = "IDLE"
459
460 # -------------
461 # Invalid PTE
462 # -------------
463 # If pte.v = 0, or if pte.r = 0 and pte.w = 1,
464 # stop and raise a page-fault exception.
465 with m.If (pte_inv):
466 m.next = "PROPAGATE_ERROR"
467
468 # -----------
469 # Valid PTE
470 # -----------
471
472 # it is a valid PTE
473 # if pte.r = 1 or pte.x = 1 it is a valid PTE
474 with m.Elif (pte_rx):
475 # Valid translation found (either 1G, 2M or 4K)
476 with m.If(is_instr_ptw):
477 # ------------
478 # Update ITLB
479 # ------------
480 # If page not executable, we can directly raise error.
481 # This doesn't put a useless entry into the TLB.
482 # The same idea applies to the access flag since we let
483 # the access flag be managed by SW.
484 with m.If (pte_exe):
485 m.next = "IDLE"
486 with m.Else():
487 m.d.comb += self.itlb_update_o.valid.eq(1)
488
489 with m.Else():
490 # ------------
491 # Update DTLB
492 # ------------
493 # Check if the access flag has been set, otherwise
494 # throw page-fault and let software handle those bits.
495 # If page not readable (there are no write-only pages)
496 # directly raise an error. This doesn't put a useless
497 # entry into the TLB.
498 with m.If(pte_a):
499 m.d.comb += self.dtlb_update_o.valid.eq(1)
500 with m.Else():
501 m.next = "PROPAGATE_ERROR"
502 # Request is a store: perform additional checks
503 # If the request was a store and the page not
504 # write-able, raise an error
505 # the same applies if the dirty flag is not set
506 with m.If (st_wd):
507 m.d.comb += self.dtlb_update_o.valid.eq(0)
508 m.next = "PROPAGATE_ERROR"
509
510 # check if the ppn is correctly aligned: Case (6)
511 with m.If(l1err | l2err):
512 m.next = "PROPAGATE_ERROR"
513 m.d.comb += [self.dtlb_update_o.valid.eq(0),
514 self.itlb_update_o.valid.eq(0)]
515
516 # this is a pointer to the next TLB level
517 with m.Else():
518 # pointer to next level of page table
519 with m.If (ptw_lvl1):
520 # we are in the second level now
521 pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[30:39], pte.ppn)
522 m.d.sync += [ptw_pptr.eq(pptr),
523 ptw_lvl.eq(LVL2)
524 ]
525 with m.If(ptw_lvl2):
526 # here we received a pointer to the third level
527 pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[21:30], pte.ppn)
528 m.d.sync += [ptw_pptr.eq(pptr),
529 ptw_lvl.eq(LVL3)
530 ]
531 with m.If(ptw_lvl3): #guess: shift page levels by one
532 # here we received a pointer to the fourth level
533 # the last one is near the page offset
534 pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[12:21], pte.ppn)
535 m.d.sync += [ptw_pptr.eq(pptr),
536 ptw_lvl.eq(LVL4)
537 ]
538 self.set_grant_state(m)
539
540 with m.If (ptw_lvl4):
541 # Should already be the last level
542 # page table => Error
543 m.d.sync += ptw_lvl.eq(LVL4)
544 m.next = "PROPAGATE_ERROR"
545
546
547 if __name__ == '__main__':
548 ptw = PTW()
549 vl = rtlil.convert(ptw, ports=ptw.ports())
550 with open("test_ptw.il", "w") as f:
551 f.write(vl)