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