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