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