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.
12 # Author: Florian Zaruba, ETH Zurich
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
21 from nmigen
import Const
, Signal
, Cat
, Module
, Mux
22 from nmigen
.cli
import verilog
, rtlil
24 from ptw
import DCacheReqI
, DCacheReqO
, TLBUpdate
, PTE
, PTW
26 from exceptcause
import (INSTR_ACCESS_FAULT
, INSTR_PAGE_FAULT
,
27 LOAD_PAGE_FAULT
, STORE_PAGE_FAULT
)
29 PRIV_LVL_M
= Const(0b11, 2)
30 PRIV_LVL_S
= Const(0b01, 2)
31 PRIV_LVL_U
= Const(0b00, 2)
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
44 for (o
, i
) in zip(self
.ports(), inp
.ports()):
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
64 yield self
.fetch_valid
65 yield self
.fetch_paddr
66 yield from self
.fetch_exception
74 self
.fetch_req
= Signal() # address translation request
75 self
.fetch_vaddr
= Signal(64) # virtual address out
79 yield self
.fetch_vaddr
86 def __init__(self
, instr_tlb_entries
= 4,
89 self
.instr_tlb_entries
= instr_tlb_entries
90 self
.data_tlb_entries
= data_tlb_entries
91 self
.asid_width
= asid_width
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
97 self
.icache_areq_i
= ICacheReqO()
98 self
.icache_areq_o
= ICacheReqI()
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
110 self
.lsu_dtlb_hit_o
= Signal() # sent in the same cycle as the request
111 # if translation hits in the DTLB
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
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()
133 def elaborate(self
, platform
):
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
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
)
147 itlb_lu_access
= Signal()
149 itlb_is_2M
= Signal()
150 itlb_is_1G
= Signal()
151 itlb_is_512G
= Signal()
152 itlb_lu_hit
= Signal()
154 dtlb_lu_access
= Signal()
156 dtlb_is_2M
= Signal()
157 dtlb_is_1G
= Signal()
158 dtlb_is_512G
= Signal()
159 dtlb_lu_hit
= Signal()
162 m
.d
.comb
+= [itlb_lu_access
.eq(self
.icache_areq_i
.fetch_req
),
163 dtlb_lu_access
.eq(self
.lsu_req_i
)
167 m
.submodules
.i_tlb
= i_tlb
= TLB(self
.instr_tlb_entries
,
169 m
.d
.comb
+= [i_tlb
.flush_i
.eq(self
.flush_tlb_i
),
170 i_tlb
.update_i
.eq(update_ptw_itlb
),
171 i_tlb
.lu_access_i
.eq(itlb_lu_access
),
172 i_tlb
.lu_asid_i
.eq(self
.asid_i
),
173 i_tlb
.lu_vaddr_i
.eq(self
.icache_areq_i
.fetch_vaddr
),
174 itlb_content
.eq(i_tlb
.lu_content_o
),
175 itlb_is_2M
.eq(i_tlb
.lu_is_2M_o
),
176 itlb_is_1G
.eq(i_tlb
.lu_is_1G_o
),
177 itlb_is_512G
.eq(i_tlb
.lu_is_512G_o
),
178 itlb_lu_hit
.eq(i_tlb
.lu_hit_o
),
182 m
.submodules
.d_tlb
= d_tlb
= TLB(self
.data_tlb_entries
,
184 m
.d
.comb
+= [d_tlb
.flush_i
.eq(self
.flush_tlb_i
),
185 d_tlb
.update_i
.eq(update_ptw_dtlb
),
186 d_tlb
.lu_access_i
.eq(dtlb_lu_access
),
187 d_tlb
.lu_asid_i
.eq(self
.asid_i
),
188 d_tlb
.lu_vaddr_i
.eq(self
.lsu_vaddr_i
),
189 dtlb_content
.eq(d_tlb
.lu_content_o
),
190 dtlb_is_2M
.eq(d_tlb
.lu_is_2M_o
),
191 dtlb_is_1G
.eq(d_tlb
.lu_is_1G_o
),
192 dtlb_is_512G
.eq(d_tlb
.lu_is_512G_o
),
193 dtlb_lu_hit
.eq(d_tlb
.lu_hit_o
),
197 m
.submodules
.ptw
= ptw
= PTW(self
.asid_width
)
198 m
.d
.comb
+= [ptw_active
.eq(ptw
.ptw_active_o
),
199 walking_instr
.eq(ptw
.walking_instr_o
),
200 ptw_error
.eq(ptw
.ptw_error_o
),
201 ptw
.enable_translation_i
.eq(self
.enable_translation_i
),
203 update_vaddr
.eq(ptw
.update_vaddr_o
),
204 update_ptw_itlb
.eq(ptw
.itlb_update_o
),
205 update_ptw_dtlb
.eq(ptw
.dtlb_update_o
),
207 ptw
.itlb_access_i
.eq(itlb_lu_access
),
208 ptw
.itlb_hit_i
.eq(itlb_lu_hit
),
209 ptw
.itlb_vaddr_i
.eq(self
.icache_areq_i
.fetch_vaddr
),
211 ptw
.dtlb_access_i
.eq(dtlb_lu_access
),
212 ptw
.dtlb_hit_i
.eq(dtlb_lu_hit
),
213 ptw
.dtlb_vaddr_i
.eq(self
.lsu_vaddr_i
),
215 ptw
.req_port_i
.eq(self
.req_port_i
),
216 self
.req_port_o
.eq(ptw
.req_port_o
),
220 # .clk(clk_i), # input wire clk
221 # .probe0({req_port_o.address_tag, req_port_o.address_index}),
222 # .probe1(req_port_o.data_req), # input wire [63:0] probe1
223 # .probe2(req_port_i.data_gnt), # input wire [0:0] probe2
224 # .probe3(req_port_i.data_rdata), # input wire [0:0] probe3
225 # .probe4(req_port_i.data_rvalid), # input wire [0:0] probe4
226 # .probe5(ptw_error), # input wire [1:0] probe5
227 # .probe6(update_vaddr), # input wire [0:0] probe6
228 # .probe7(update_ptw_itlb.valid), # input wire [0:0] probe7
229 # .probe8(update_ptw_dtlb.valid), # input wire [0:0] probe8
230 # .probe9(dtlb_lu_access), # input wire [0:0] probe9
231 # .probe10(lsu_vaddr_i), # input wire [0:0] probe10
232 # .probe11(dtlb_lu_hit), # input wire [0:0] probe11
233 # .probe12(itlb_lu_access), # input wire [0:0] probe12
234 # .probe13(icache_areq_i.fetch_vaddr), # input wire [0:0] probe13
235 # .probe14(itlb_lu_hit) # input wire [0:0] probe13
238 #-----------------------
239 # Instruction Interface
240 #-----------------------
241 # The instruction interface is a simple request response interface
243 # MMU disabled: just pass through
244 m
.d
.comb
+= [self
.icache_areq_o
.fetch_valid
.eq(
245 self
.icache_areq_i
.fetch_req
),
246 # play through in case we disabled address translation
247 self
.icache_areq_o
.fetch_paddr
.eq(
248 self
.icache_areq_i
.fetch_vaddr
)
250 # two potential exception sources:
251 # 1. HPTW threw an exception -> signal with a page fault exception
252 # 2. We got an access error because of insufficient permissions ->
253 # throw an access exception
254 m
.d
.comb
+= self
.icache_areq_o
.fetch_exception
.valid
.eq(0)
255 # Check whether we are allowed to access this memory region
256 # from a fetch perspective
258 # PLATEN TODO: use PermissionValidator instead [we like modules]
259 m
.d
.comb
+= iaccess_err
.eq(self
.icache_areq_i
.fetch_req
& \
260 (((self
.priv_lvl_i
== PRIV_LVL_U
) & \
262 ((self
.priv_lvl_i
== PRIV_LVL_S
) & \
265 # MMU enabled: address from TLB, request delayed until hit.
266 # Error when TLB hit and no access right or TLB hit and
267 # translated address not valid (e.g. AXI decode error),
268 # or when PTW performs walk due to ITLB miss and raises
270 with m
.If (self
.enable_translation_i
):
271 # we work with SV48, so if VM is enabled, check that
272 # all bits [47:38] are equal
273 with m
.If (self
.icache_areq_i
.fetch_req
& \
274 ~
(((~self
.icache_areq_i
.fetch_vaddr
[47:64]) == 0) | \
275 (self
.icache_areq_i
.fetch_vaddr
[47:64]) == 0)):
276 fe
= self
.icache_areq_o
.fetch_exception
277 m
.d
.comb
+= [fe
.cause
.eq(INSTR_ACCESS_FAULT
),
278 fe
.tval
.eq(self
.icache_areq_i
.fetch_vaddr
),
282 m
.d
.comb
+= self
.icache_areq_o
.fetch_valid
.eq(0)
285 paddr
= Signal
.like(self
.icache_areq_o
.fetch_paddr
)
286 paddr4k
= Cat(self
.icache_areq_i
.fetch_vaddr
[0:12],
288 m
.d
.comb
+= paddr
.eq(paddr4k
)
290 with m
.If(itlb_is_2M
):
291 m
.d
.comb
+= paddr
[12:21].eq(
292 self
.icache_areq_i
.fetch_vaddr
[12:21])
294 with m
.If(itlb_is_1G
):
295 m
.d
.comb
+= paddr
[12:30].eq(
296 self
.icache_areq_i
.fetch_vaddr
[12:30])
297 m
.d
.comb
+= self
.icache_areq_o
.fetch_paddr
.eq(paddr
)
299 with m
.If(itlb_is_512G
):
300 m
.d
.comb
+= paddr
[12:39].eq(
301 self
.icache_areq_i
.fetch_vaddr
[12:39])
302 m
.d
.comb
+= self
.icache_areq_o
.fetch_paddr
.eq(paddr
)
307 # if we hit the ITLB output the request signal immediately
308 with m
.If(itlb_lu_hit
):
309 m
.d
.comb
+= self
.icache_areq_o
.fetch_valid
.eq(
310 self
.icache_areq_i
.fetch_req
)
311 # we got an access error
312 with m
.If (iaccess_err
):
314 fe
= self
.icache_areq_o
.fetch_exception
315 m
.d
.comb
+= [fe
.cause
.eq(INSTR_ACCESS_FAULT
),
316 fe
.tval
.eq(self
.icache_areq_i
.fetch_vaddr
),
322 # watch out for exceptions happening during walking the page table
323 with m
.Elif(ptw_active
& walking_instr
):
324 m
.d
.comb
+= self
.icache_areq_o
.fetch_valid
.eq(ptw_error
)
325 fe
= self
.icache_areq_o
.fetch_exception
326 m
.d
.comb
+= [fe
.cause
.eq(INSTR_PAGE_FAULT
),
331 #-----------------------
333 #-----------------------
335 lsu_vaddr
= Signal(64)
337 misaligned_ex
= RVException()
339 lsu_is_store
= Signal()
341 #dtlb_is_2M = Signal()
342 #dtlb_is_1G = Signal()
343 #dtlb_is_512 = Signal()
345 # check if we need to do translation or if we are always
346 # ready (e.g.: we are not translating anything)
347 m
.d
.comb
+= self
.lsu_dtlb_hit_o
.eq(Mux(self
.en_ld_st_translation_i
,
350 # The data interface is simpler and only consists of a
351 # request/response interface
353 # save request and DTLB response
354 lsu_vaddr
.eq(self
.lsu_vaddr_i
),
355 lsu_req
.eq(self
.lsu_req_i
),
356 misaligned_ex
.eq(self
.misaligned_ex_i
),
357 dtlb_pte
.eq(dtlb_content
),
358 dtlb_hit
.eq(dtlb_lu_hit
),
359 lsu_is_store
.eq(self
.lsu_is_store_i
),
360 #dtlb_is_2M.eq(dtlb_is_2M),
361 #dtlb_is_1G.eq(dtlb_is_1G),
362 ##dtlb_is_512.eq(self.dtlb_is_512G) #????
365 self
.lsu_paddr_o
.eq(lsu_vaddr
),
366 self
.lsu_valid_o
.eq(lsu_req
),
367 self
.lsu_exception_o
.eq(misaligned_ex
),
374 # mute misaligned exceptions if there is no request
375 # otherwise they will throw accidental exceptions
376 misaligned_ex
.valid
.eq(self
.misaligned_ex_i
.valid
& self
.lsu_req_i
),
378 # SUM is not set and we are trying to access a user
379 # page in supervisor mode
380 sverr
.eq(self
.ld_st_priv_lvl_i
== PRIV_LVL_S
& ~self
.sum_i
& \
382 # this is not a user page but we are in user mode and
383 # trying to access it
384 usrerr
.eq(self
.ld_st_priv_lvl_i
== PRIV_LVL_U
& ~dtlb_pte
.u
),
386 # Check if the User flag is set, then we may only
387 # access it in supervisor mode if SUM is enabled
388 daccess_err
.eq(sverr | usrerr
),
391 # translation is enabled and no misaligned exception occurred
392 with m
.If(self
.en_ld_st_translation_i
& ~misaligned_ex
.valid
):
393 m
.d
.comb
+= lsu_req
.eq(0)
395 paddr
= Signal
.like(lsu_vaddr
)
396 paddr4k
= Cat(lsu_vaddr
[0:12], itlb_content
.ppn
)
397 m
.d
.comb
+= paddr
.eq(paddr4k
)
399 with m
.If(dtlb_is_2M
):
400 m
.d
.comb
+= paddr
[12:21].eq(lsu_vaddr
[12:21])
402 with m
.If(dtlb_is_1G
):
403 m
.d
.comb
+= paddr
[12:30].eq(lsu_vaddr
[12:30])
404 m
.d
.sync
+= self
.lsu_paddr_o
.eq(paddr
)
405 # TODO platen tera_page
410 with m
.If(dtlb_hit
& lsu_req
):
411 m
.d
.comb
+= lsu_req
.eq(1)
413 with m
.If (lsu_is_store
):
414 # check if the page is write-able and
415 # we are not violating privileges
416 # also check if the dirty flag is set
417 with m
.If(~dtlb_pte
.w | daccess_err | ~dtlb_pte
.d
):
418 le
= self
.lsu_exception_o
419 m
.d
.sync
+= [le
.cause
.eq(STORE_PAGE_FAULT
),
420 le
.tval
.eq(lsu_vaddr
),
424 # this is a load, check for sufficient access
425 # privileges - throw a page fault if necessary
426 with m
.Elif(daccess_err
):
427 le
= self
.lsu_exception_o
428 m
.d
.sync
+= [le
.cause
.eq(LOAD_PAGE_FAULT
),
429 le
.tval
.eq(lsu_vaddr
),
435 # watch out for exceptions
436 with m
.Elif (ptw_active
& ~walking_instr
):
437 # page table walker threw an exception
438 with m
.If (ptw_error
):
439 # an error makes the translation valid
440 m
.d
.comb
+= lsu_req
.eq(1)
441 # the page table walker can only throw page faults
442 with m
.If (lsu_is_store
):
443 le
= self
.lsu_exception_o
444 m
.d
.sync
+= [le
.cause
.eq(STORE_PAGE_FAULT
),
449 m
.d
.sync
+= [le
.cause
.eq(LOAD_PAGE_FAULT
),
457 return [self
.flush_i
, self
.enable_translation_i
,
458 self
.en_ld_st_translation_i
,
460 self
.lsu_vaddr_i
, self
.lsu_is_store_i
, self
.lsu_dtlb_hit_o
,
461 self
.lsu_valid_o
, self
.lsu_paddr_o
,
462 self
.priv_lvl_i
, self
.ld_st_priv_lvl_i
, self
.sum_i
, self
.mxr_i
,
463 self
.satp_ppn_i
, self
.asid_i
, self
.flush_tlb_i
,
464 self
.itlb_miss_o
, self
.dtlb_miss_o
] + \
465 self
.icache_areq_i
.ports() + self
.icache_areq_o
.ports() + \
466 self
.req_port_i
.ports() + self
.req_port_o
.ports() + \
467 self
.misaligned_ex_i
.ports() + self
.lsu_exception_o
.ports()
469 if __name__
== '__main__':
471 vl
= rtlil
.convert(mmu
, ports
=mmu
.ports())
472 with
open("test_mmu.il", "w") as f
: