implement page table lookup using 4 levels
[soc.git] / src / TLB / ariane / mmu.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: Florian Zaruba, ETH Zurich
13 # Date: 19/04/2017
14 # Description: Memory Management Unit for Ariane, contains TLB and
15 # address translation unit. SV48 as defined in
16 # Volume II: RISC-V Privileged Architectures V1.10 Page 63
17
18 import ariane_pkg::*;
19 """
20
21 from nmigen import Const, Signal, Cat, Module, Mux
22 from nmigen.cli import verilog, rtlil
23
24 from ptw import DCacheReqI, DCacheReqO, TLBUpdate, PTE, PTW
25 from tlb import TLB
26 from exceptcause import (INSTR_ACCESS_FAULT, INSTR_PAGE_FAULT,
27 LOAD_PAGE_FAULT, STORE_PAGE_FAULT)
28
29 PRIV_LVL_M = Const(0b11, 2)
30 PRIV_LVL_S = Const(0b01, 2)
31 PRIV_LVL_U = Const(0b00, 2)
32
33
34 class RVException:
35 def __init__(self):
36 self.cause = Signal(64) # cause of exception
37 self.tval = Signal(64) # more info of causing exception
38 # (e.g.: instruction causing it),
39 # address of LD/ST fault
40 self.valid = Signal()
41
42 def eq(self, inp):
43 res = []
44 for (o, i) in zip(self.ports(), inp.ports()):
45 res.append(o.eq(i))
46 return res
47
48 def __iter__(self):
49 yield self.cause
50 yield self.tval
51 yield self.valid
52
53 def ports(self):
54 return list(self)
55
56
57 class ICacheReqI:
58 def __init__(self):
59 self.fetch_valid = Signal() # address translation valid
60 self.fetch_paddr = Signal(64) # physical address in
61 self.fetch_exception = RVException() # exception occurred during fetch
62
63 def __iter__(self):
64 yield self.fetch_valid
65 yield self.fetch_paddr
66 yield from self.fetch_exception
67
68 def ports(self):
69 return list(self)
70
71
72 class ICacheReqO:
73 def __init__(self):
74 self.fetch_req = Signal() # address translation request
75 self.fetch_vaddr = Signal(64) # virtual address out
76
77 def __iter__(self):
78 yield self.fetch_req
79 yield self.fetch_vaddr
80
81 def ports(self):
82 return list(self)
83
84
85 class MMU:
86 def __init__(self, instr_tlb_entries = 4,
87 data_tlb_entries = 4,
88 asid_width = 1):
89 self.instr_tlb_entries = instr_tlb_entries
90 self.data_tlb_entries = data_tlb_entries
91 self.asid_width = asid_width
92
93 self.flush_i = Signal()
94 self.enable_translation_i = Signal()
95 self.en_ld_st_translation_i = Signal() # enable VM translation for LD/ST
96 # IF interface
97 self.icache_areq_i = ICacheReqO()
98 self.icache_areq_o = ICacheReqI()
99 # LSU interface
100 # this is a more minimalistic interface because the actual addressing
101 # logic is handled in the LSU as we distinguish load and stores,
102 # what we do here is simple address translation
103 self.misaligned_ex_i = RVException()
104 self.lsu_req_i = Signal() # request address translation
105 self.lsu_vaddr_i = Signal(64) # virtual address in
106 self.lsu_is_store_i = Signal() # the translation is requested by a store
107 # if we need to walk the page table we can't grant in the same cycle
108
109 # Cycle 0
110 self.lsu_dtlb_hit_o = Signal() # sent in the same cycle as the request
111 # if translation hits in the DTLB
112 # Cycle 1
113 self.lsu_valid_o = Signal() # translation is valid
114 self.lsu_paddr_o = Signal(64) # translated address
115 self.lsu_exception_o = RVException() # addr translate threw exception
116
117 # General control signals
118 self.priv_lvl_i = Signal(2)
119 self.ld_st_priv_lvl_i = Signal(2)
120 self.sum_i = Signal()
121 self.mxr_i = Signal()
122 # input logic flag_mprv_i,
123 self.satp_ppn_i = Signal(44)
124 self.asid_i = Signal(self.asid_width)
125 self.flush_tlb_i = Signal()
126 # Performance counters
127 self.itlb_miss_o = Signal()
128 self.dtlb_miss_o = Signal()
129 # PTW memory interface
130 self.req_port_i = DCacheReqO()
131 self.req_port_o = DCacheReqI()
132
133 def elaborate(self, platform):
134 m = Module()
135
136 iaccess_err = Signal() # insufficient priv to access instr page
137 daccess_err = Signal() # insufficient priv to access data page
138 ptw_active = Signal() # PTW is currently walking a page table
139 walking_instr = Signal() # PTW is walking because of an ITLB miss
140 ptw_error = Signal() # PTW threw an exception
141
142 update_vaddr = Signal(48) # guessed
143 uaddr64 = Cat(update_vaddr, Const(0, 25)) # extend to 64bit with zeros
144 update_ptw_itlb = TLBUpdate(self.asid_width)
145 update_ptw_dtlb = TLBUpdate(self.asid_width)
146
147 itlb_lu_access = Signal()
148 itlb_content = PTE()
149 itlb_is_2M = Signal()
150 itlb_is_1G = Signal()
151 itlb_lu_hit = Signal()
152
153 dtlb_lu_access = Signal()
154 dtlb_content = PTE()
155 dtlb_is_2M = Signal()
156 dtlb_is_1G = Signal()
157 dtlb_lu_hit = Signal()
158
159 # Assignments
160 m.d.comb += [itlb_lu_access.eq(self.icache_areq_i.fetch_req),
161 dtlb_lu_access.eq(self.lsu_req_i)
162 ]
163
164 # ITLB
165 m.submodules.i_tlb = i_tlb = TLB(self.instr_tlb_entries,
166 self.asid_width)
167 m.d.comb += [i_tlb.flush_i.eq(self.flush_tlb_i),
168 i_tlb.update_i.eq(update_ptw_itlb),
169 i_tlb.lu_access_i.eq(itlb_lu_access),
170 i_tlb.lu_asid_i.eq(self.asid_i),
171 i_tlb.lu_vaddr_i.eq(self.icache_areq_i.fetch_vaddr),
172 itlb_content.eq(i_tlb.lu_content_o),
173 itlb_is_2M.eq(i_tlb.lu_is_2M_o),
174 itlb_is_1G.eq(i_tlb.lu_is_1G_o),
175 itlb_lu_hit.eq(i_tlb.lu_hit_o),
176 ]
177
178 # DTLB
179 m.submodules.d_tlb = d_tlb = TLB(self.data_tlb_entries,
180 self.asid_width)
181 m.d.comb += [d_tlb.flush_i.eq(self.flush_tlb_i),
182 d_tlb.update_i.eq(update_ptw_dtlb),
183 d_tlb.lu_access_i.eq(dtlb_lu_access),
184 d_tlb.lu_asid_i.eq(self.asid_i),
185 d_tlb.lu_vaddr_i.eq(self.lsu_vaddr_i),
186 dtlb_content.eq(d_tlb.lu_content_o),
187 dtlb_is_2M.eq(d_tlb.lu_is_2M_o),
188 dtlb_is_1G.eq(d_tlb.lu_is_1G_o),
189 dtlb_lu_hit.eq(d_tlb.lu_hit_o),
190 ]
191
192 # PTW
193 m.submodules.ptw = ptw = PTW(self.asid_width)
194 m.d.comb += [ptw_active.eq(ptw.ptw_active_o),
195 walking_instr.eq(ptw.walking_instr_o),
196 ptw_error.eq(ptw.ptw_error_o),
197 ptw.enable_translation_i.eq(self.enable_translation_i),
198
199 update_vaddr.eq(ptw.update_vaddr_o),
200 update_ptw_itlb.eq(ptw.itlb_update_o),
201 update_ptw_dtlb.eq(ptw.dtlb_update_o),
202
203 ptw.itlb_access_i.eq(itlb_lu_access),
204 ptw.itlb_hit_i.eq(itlb_lu_hit),
205 ptw.itlb_vaddr_i.eq(self.icache_areq_i.fetch_vaddr),
206
207 ptw.dtlb_access_i.eq(dtlb_lu_access),
208 ptw.dtlb_hit_i.eq(dtlb_lu_hit),
209 ptw.dtlb_vaddr_i.eq(self.lsu_vaddr_i),
210
211 ptw.req_port_i.eq(self.req_port_i),
212 self.req_port_o.eq(ptw.req_port_o),
213 ]
214
215 # ila_1 i_ila_1 (
216 # .clk(clk_i), # input wire clk
217 # .probe0({req_port_o.address_tag, req_port_o.address_index}),
218 # .probe1(req_port_o.data_req), # input wire [63:0] probe1
219 # .probe2(req_port_i.data_gnt), # input wire [0:0] probe2
220 # .probe3(req_port_i.data_rdata), # input wire [0:0] probe3
221 # .probe4(req_port_i.data_rvalid), # input wire [0:0] probe4
222 # .probe5(ptw_error), # input wire [1:0] probe5
223 # .probe6(update_vaddr), # input wire [0:0] probe6
224 # .probe7(update_ptw_itlb.valid), # input wire [0:0] probe7
225 # .probe8(update_ptw_dtlb.valid), # input wire [0:0] probe8
226 # .probe9(dtlb_lu_access), # input wire [0:0] probe9
227 # .probe10(lsu_vaddr_i), # input wire [0:0] probe10
228 # .probe11(dtlb_lu_hit), # input wire [0:0] probe11
229 # .probe12(itlb_lu_access), # input wire [0:0] probe12
230 # .probe13(icache_areq_i.fetch_vaddr), # input wire [0:0] probe13
231 # .probe14(itlb_lu_hit) # input wire [0:0] probe13
232 # );
233
234 #-----------------------
235 # Instruction Interface
236 #-----------------------
237 # The instruction interface is a simple request response interface
238
239 # MMU disabled: just pass through
240 m.d.comb += [self.icache_areq_o.fetch_valid.eq(
241 self.icache_areq_i.fetch_req),
242 # play through in case we disabled address translation
243 self.icache_areq_o.fetch_paddr.eq(
244 self.icache_areq_i.fetch_vaddr)
245 ]
246 # two potential exception sources:
247 # 1. HPTW threw an exception -> signal with a page fault exception
248 # 2. We got an access error because of insufficient permissions ->
249 # throw an access exception
250 m.d.comb += self.icache_areq_o.fetch_exception.valid.eq(0)
251 # Check whether we are allowed to access this memory region
252 # from a fetch perspective
253
254 # XXX TODO: use PermissionValidator instead [we like modules]
255 m.d.comb += iaccess_err.eq(self.icache_areq_i.fetch_req & \
256 (((self.priv_lvl_i == PRIV_LVL_U) & \
257 ~itlb_content.u) | \
258 ((self.priv_lvl_i == PRIV_LVL_S) & \
259 itlb_content.u)))
260
261 # MMU enabled: address from TLB, request delayed until hit.
262 # Error when TLB hit and no access right or TLB hit and
263 # translated address not valid (e.g. AXI decode error),
264 # or when PTW performs walk due to ITLB miss and raises
265 # an error.
266 with m.If (self.enable_translation_i):
267 # we work with SV48, so if VM is enabled, check that
268 # all bits [63:38] are equal
269 with m.If (self.icache_areq_i.fetch_req & \
270 ~(((~self.icache_areq_i.fetch_vaddr[38:64]) == 0) | \
271 (self.icache_areq_i.fetch_vaddr[38:64]) == 0)):
272 fe = self.icache_areq_o.fetch_exception
273 m.d.comb += [fe.cause.eq(INSTR_ACCESS_FAULT),
274 fe.tval.eq(self.icache_areq_i.fetch_vaddr),
275 fe.valid.eq(1)
276 ]
277
278 m.d.comb += self.icache_areq_o.fetch_valid.eq(0)
279
280 # 4K page
281 paddr = Signal.like(self.icache_areq_o.fetch_paddr)
282 paddr4k = Cat(self.icache_areq_i.fetch_vaddr[0:12],
283 itlb_content.ppn)
284 m.d.comb += paddr.eq(paddr4k)
285 # Mega page
286 with m.If(itlb_is_2M):
287 m.d.comb += paddr[12:21].eq(
288 self.icache_areq_i.fetch_vaddr[12:21])
289 # Giga page
290 with m.If(itlb_is_1G):
291 m.d.comb += paddr[12:30].eq(
292 self.icache_areq_i.fetch_vaddr[12:30])
293 m.d.comb += self.icache_areq_o.fetch_paddr.eq(paddr)
294
295 # ---------
296 # ITLB Hit
297 # --------
298 # if we hit the ITLB output the request signal immediately
299 with m.If(itlb_lu_hit):
300 m.d.comb += self.icache_areq_o.fetch_valid.eq(
301 self.icache_areq_i.fetch_req)
302 # we got an access error
303 with m.If (iaccess_err):
304 # throw a page fault
305 fe = self.icache_areq_o.fetch_exception
306 m.d.comb += [fe.cause.eq(INSTR_ACCESS_FAULT),
307 fe.tval.eq(self.icache_areq_i.fetch_vaddr),
308 fe.valid.eq(1)
309 ]
310 # ---------
311 # ITLB Miss
312 # ---------
313 # watch out for exceptions happening during walking the page table
314 with m.Elif(ptw_active & walking_instr):
315 m.d.comb += self.icache_areq_o.fetch_valid.eq(ptw_error)
316 fe = self.icache_areq_o.fetch_exception
317 m.d.comb += [fe.cause.eq(INSTR_PAGE_FAULT),
318 fe.tval.eq(uaddr64),
319 fe.valid.eq(1)
320 ]
321
322 #-----------------------
323 # Data Interface
324 #-----------------------
325
326 lsu_vaddr = Signal(64)
327 dtlb_pte = PTE()
328 misaligned_ex = RVException()
329 lsu_req = Signal()
330 lsu_is_store = Signal()
331 dtlb_hit = Signal()
332 dtlb_is_2M = Signal()
333 dtlb_is_1G = Signal()
334
335 # check if we need to do translation or if we are always
336 # ready (e.g.: we are not translating anything)
337 m.d.comb += self.lsu_dtlb_hit_o.eq(Mux(self.en_ld_st_translation_i,
338 dtlb_lu_hit, 1))
339
340 # The data interface is simpler and only consists of a
341 # request/response interface
342 m.d.comb += [
343 # save request and DTLB response
344 lsu_vaddr.eq(self.lsu_vaddr_i),
345 lsu_req.eq(self.lsu_req_i),
346 misaligned_ex.eq(self.misaligned_ex_i),
347 dtlb_pte.eq(dtlb_content),
348 dtlb_hit.eq(dtlb_lu_hit),
349 lsu_is_store.eq(self.lsu_is_store_i),
350 dtlb_is_2M.eq(dtlb_is_2M),
351 dtlb_is_1G.eq(dtlb_is_1G),
352 ]
353 m.d.sync += [
354 self.lsu_paddr_o.eq(lsu_vaddr),
355 self.lsu_valid_o.eq(lsu_req),
356 self.lsu_exception_o.eq(misaligned_ex),
357 ]
358
359 sverr = Signal()
360 usrerr = Signal()
361
362 m.d.comb += [
363 # mute misaligned exceptions if there is no request
364 # otherwise they will throw accidental exceptions
365 misaligned_ex.valid.eq(self.misaligned_ex_i.valid & self.lsu_req_i),
366
367 # SUM is not set and we are trying to access a user
368 # page in supervisor mode
369 sverr.eq(self.ld_st_priv_lvl_i == PRIV_LVL_S & ~self.sum_i & \
370 dtlb_pte.u),
371 # this is not a user page but we are in user mode and
372 # trying to access it
373 usrerr.eq(self.ld_st_priv_lvl_i == PRIV_LVL_U & ~dtlb_pte.u),
374
375 # Check if the User flag is set, then we may only
376 # access it in supervisor mode if SUM is enabled
377 daccess_err.eq(sverr | usrerr),
378 ]
379
380 # translation is enabled and no misaligned exception occurred
381 with m.If(self.en_ld_st_translation_i & ~misaligned_ex.valid):
382 m.d.comb += lsu_req.eq(0)
383 # 4K page
384 paddr = Signal.like(lsu_vaddr)
385 paddr4k = Cat(lsu_vaddr[0:12], itlb_content.ppn)
386 m.d.comb += paddr.eq(paddr4k)
387 # Mega page
388 with m.If(dtlb_is_2M):
389 m.d.comb += paddr[12:21].eq(lsu_vaddr[12:21])
390 # Giga page
391 with m.If(dtlb_is_1G):
392 m.d.comb += paddr[12:30].eq(lsu_vaddr[12:30])
393 m.d.sync += self.lsu_paddr_o.eq(paddr)
394
395 # ---------
396 # DTLB Hit
397 # --------
398 with m.If(dtlb_hit & lsu_req):
399 m.d.comb += lsu_req.eq(1)
400 # this is a store
401 with m.If (lsu_is_store):
402 # check if the page is write-able and
403 # we are not violating privileges
404 # also check if the dirty flag is set
405 with m.If(~dtlb_pte.w | daccess_err | ~dtlb_pte.d):
406 le = self.lsu_exception_o
407 m.d.sync += [le.cause.eq(STORE_PAGE_FAULT),
408 le.tval.eq(lsu_vaddr),
409 le.valid.eq(1)
410 ]
411
412 # this is a load, check for sufficient access
413 # privileges - throw a page fault if necessary
414 with m.Elif(daccess_err):
415 le = self.lsu_exception_o
416 m.d.sync += [le.cause.eq(LOAD_PAGE_FAULT),
417 le.tval.eq(lsu_vaddr),
418 le.valid.eq(1)
419 ]
420 # ---------
421 # DTLB Miss
422 # ---------
423 # watch out for exceptions
424 with m.Elif (ptw_active & ~walking_instr):
425 # page table walker threw an exception
426 with m.If (ptw_error):
427 # an error makes the translation valid
428 m.d.comb += lsu_req.eq(1)
429 # the page table walker can only throw page faults
430 with m.If (lsu_is_store):
431 le = self.lsu_exception_o
432 m.d.sync += [le.cause.eq(STORE_PAGE_FAULT),
433 le.tval.eq(uaddr64),
434 le.valid.eq(1)
435 ]
436 with m.Else():
437 m.d.sync += [le.cause.eq(LOAD_PAGE_FAULT),
438 le.tval.eq(uaddr64),
439 le.valid.eq(1)
440 ]
441
442 return m
443
444 def ports(self):
445 return [self.flush_i, self.enable_translation_i,
446 self.en_ld_st_translation_i,
447 self.lsu_req_i,
448 self.lsu_vaddr_i, self.lsu_is_store_i, self.lsu_dtlb_hit_o,
449 self.lsu_valid_o, self.lsu_paddr_o,
450 self.priv_lvl_i, self.ld_st_priv_lvl_i, self.sum_i, self.mxr_i,
451 self.satp_ppn_i, self.asid_i, self.flush_tlb_i,
452 self.itlb_miss_o, self.dtlb_miss_o] + \
453 self.icache_areq_i.ports() + self.icache_areq_o.ports() + \
454 self.req_port_i.ports() + self.req_port_o.ports() + \
455 self.misaligned_ex_i.ports() + self.lsu_exception_o.ports()
456
457 if __name__ == '__main__':
458 mmu = MMU()
459 vl = rtlil.convert(mmu, ports=mmu.ports())
460 with open("test_mmu.il", "w") as f:
461 f.write(vl)
462