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