From: Daniel Benusovich Date: Sat, 4 May 2019 20:48:58 +0000 (-0700) Subject: Move files into correct folders within ariane X-Git-Tag: div_pipeline~2141 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=187b01df04db545587cf13116dca6e67c182e24f;p=soc.git Move files into correct folders within ariane --- diff --git a/TLB/src/SetAssociativeCache.py b/TLB/src/SetAssociativeCache.py index 9afa1a28..0acd3488 100644 --- a/TLB/src/SetAssociativeCache.py +++ b/TLB/src/SetAssociativeCache.py @@ -7,7 +7,7 @@ Python simulator of a N-way set-associative cache: https://github.com/vaskevich/CacheSim/blob/master/cachesim.py """ import sys -sys.path.append("../src/ariane") +sys.path.append("ariane/src/") from nmigen import Array, Cat, Memory, Module, Signal, Mux, Elaboratable from nmigen.compat.genlib import fsm diff --git a/TLB/src/ariane/exceptcause.py b/TLB/src/ariane/exceptcause.py deleted file mode 100644 index 4c5cb2d5..00000000 --- a/TLB/src/ariane/exceptcause.py +++ /dev/null @@ -1,16 +0,0 @@ -from nmigen import Const - -INSTR_ADDR_MISALIGNED = Const(0, 64) -INSTR_ACCESS_FAULT = Const(1, 64) -ILLEGAL_INSTR = Const(2, 64) -BREAKPOINT = Const(3, 64) -LD_ADDR_MISALIGNED = Const(4, 64) -LD_ACCESS_FAULT = Const(5, 64) -ST_ADDR_MISALIGNED = Const(6, 64) -ST_ACCESS_FAULT = Const(7, 64) -ENV_CALL_UMODE = Const(8, 64) # environment call from user mode -ENV_CALL_SMODE = Const(9, 64) # environment call from supervisor mode -ENV_CALL_MMODE = Const(11, 64) # environment call from machine mode -INSTR_PAGE_FAULT = Const(12, 64) # Instruction page fault -LOAD_PAGE_FAULT = Const(13, 64) # Load page fault -STORE_PAGE_FAULT = Const(15, 64) # Store page fault diff --git a/TLB/src/ariane/mmu.py b/TLB/src/ariane/mmu.py deleted file mode 100644 index c0739cc0..00000000 --- a/TLB/src/ariane/mmu.py +++ /dev/null @@ -1,462 +0,0 @@ -""" -# Copyright 2018 ETH Zurich and University of Bologna. -# Copyright and related rights are licensed under the Solderpad Hardware -# License, Version 0.51 (the "License"); you may not use this file except in -# compliance with the License. You may obtain a copy of the License at -# http:#solderpad.org/licenses/SHL-0.51. Unless required by applicable law -# or agreed to in writing, software, hardware and materials distributed under -# this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -# -# Author: Florian Zaruba, ETH Zurich -# Date: 19/04/2017 -# Description: Memory Management Unit for Ariane, contains TLB and -# address translation unit. SV39 as defined in RISC-V -# privilege specification 1.11-WIP - -import ariane_pkg::*; -""" - -from nmigen import Const, Signal, Cat, Module, Mux -from nmigen.cli import verilog, rtlil - -from ptw import DCacheReqI, DCacheReqO, TLBUpdate, PTE, PTW -from tlb import TLB -from exceptcause import (INSTR_ACCESS_FAULT, INSTR_PAGE_FAULT, - LOAD_PAGE_FAULT, STORE_PAGE_FAULT) - -PRIV_LVL_M = Const(0b11, 2) -PRIV_LVL_S = Const(0b01, 2) -PRIV_LVL_U = Const(0b00, 2) - - -class RVException: - def __init__(self): - self.cause = Signal(64) # cause of exception - self.tval = Signal(64) # more info of causing exception - # (e.g.: instruction causing it), - # address of LD/ST fault - self.valid = Signal() - - def eq(self, inp): - res = [] - for (o, i) in zip(self.ports(), inp.ports()): - res.append(o.eq(i)) - return res - - def __iter__(self): - yield self.cause - yield self.tval - yield self.valid - - def ports(self): - return list(self) - - -class ICacheReqI: - def __init__(self): - self.fetch_valid = Signal() # address translation valid - self.fetch_paddr = Signal(64) # physical address in - self.fetch_exception = RVException() # exception occurred during fetch - - def __iter__(self): - yield self.fetch_valid - yield self.fetch_paddr - yield from self.fetch_exception - - def ports(self): - return list(self) - - -class ICacheReqO: - def __init__(self): - self.fetch_req = Signal() # address translation request - self.fetch_vaddr = Signal(64) # virtual address out - - def __iter__(self): - yield self.fetch_req - yield self.fetch_vaddr - - def ports(self): - return list(self) - - -class MMU: - def __init__(self, instr_tlb_entries = 4, - data_tlb_entries = 4, - asid_width = 1): - self.instr_tlb_entries = instr_tlb_entries - self.data_tlb_entries = data_tlb_entries - self.asid_width = asid_width - - self.flush_i = Signal() - self.enable_translation_i = Signal() - self.en_ld_st_translation_i = Signal() # enable VM translation for LD/ST - # IF interface - self.icache_areq_i = ICacheReqO() - self.icache_areq_o = ICacheReqI() - # LSU interface - # this is a more minimalistic interface because the actual addressing - # logic is handled in the LSU as we distinguish load and stores, - # what we do here is simple address translation - self.misaligned_ex_i = RVException() - self.lsu_req_i = Signal() # request address translation - self.lsu_vaddr_i = Signal(64) # virtual address in - self.lsu_is_store_i = Signal() # the translation is requested by a store - # if we need to walk the page table we can't grant in the same cycle - - # Cycle 0 - self.lsu_dtlb_hit_o = Signal() # sent in the same cycle as the request - # if translation hits in the DTLB - # Cycle 1 - self.lsu_valid_o = Signal() # translation is valid - self.lsu_paddr_o = Signal(64) # translated address - self.lsu_exception_o = RVException() # addr translate threw exception - - # General control signals - self.priv_lvl_i = Signal(2) - self.ld_st_priv_lvl_i = Signal(2) - self.sum_i = Signal() - self.mxr_i = Signal() - # input logic flag_mprv_i, - self.satp_ppn_i = Signal(44) - self.asid_i = Signal(self.asid_width) - self.flush_tlb_i = Signal() - # Performance counters - self.itlb_miss_o = Signal() - self.dtlb_miss_o = Signal() - # PTW memory interface - self.req_port_i = DCacheReqO() - self.req_port_o = DCacheReqI() - - def elaborate(self, platform): - m = Module() - - iaccess_err = Signal() # insufficient priv to access instr page - daccess_err = Signal() # insufficient priv to access data page - ptw_active = Signal() # PTW is currently walking a page table - walking_instr = Signal() # PTW is walking because of an ITLB miss - ptw_error = Signal() # PTW threw an exception - - update_vaddr = Signal(39) - uaddr64 = Cat(update_vaddr, Const(0, 25)) # extend to 64bit with zeros - update_ptw_itlb = TLBUpdate(self.asid_width) - update_ptw_dtlb = TLBUpdate(self.asid_width) - - itlb_lu_access = Signal() - itlb_content = PTE() - itlb_is_2M = Signal() - itlb_is_1G = Signal() - itlb_lu_hit = Signal() - - dtlb_lu_access = Signal() - dtlb_content = PTE() - dtlb_is_2M = Signal() - dtlb_is_1G = Signal() - dtlb_lu_hit = Signal() - - # Assignments - m.d.comb += [itlb_lu_access.eq(self.icache_areq_i.fetch_req), - dtlb_lu_access.eq(self.lsu_req_i) - ] - - # ITLB - m.submodules.i_tlb = i_tlb = TLB(self.instr_tlb_entries, - self.asid_width) - m.d.comb += [i_tlb.flush_i.eq(self.flush_tlb_i), - i_tlb.update_i.eq(update_ptw_itlb), - i_tlb.lu_access_i.eq(itlb_lu_access), - i_tlb.lu_asid_i.eq(self.asid_i), - i_tlb.lu_vaddr_i.eq(self.icache_areq_i.fetch_vaddr), - itlb_content.eq(i_tlb.lu_content_o), - itlb_is_2M.eq(i_tlb.lu_is_2M_o), - itlb_is_1G.eq(i_tlb.lu_is_1G_o), - itlb_lu_hit.eq(i_tlb.lu_hit_o), - ] - - # DTLB - m.submodules.d_tlb = d_tlb = TLB(self.data_tlb_entries, - self.asid_width) - m.d.comb += [d_tlb.flush_i.eq(self.flush_tlb_i), - d_tlb.update_i.eq(update_ptw_dtlb), - d_tlb.lu_access_i.eq(dtlb_lu_access), - d_tlb.lu_asid_i.eq(self.asid_i), - d_tlb.lu_vaddr_i.eq(self.lsu_vaddr_i), - dtlb_content.eq(d_tlb.lu_content_o), - dtlb_is_2M.eq(d_tlb.lu_is_2M_o), - dtlb_is_1G.eq(d_tlb.lu_is_1G_o), - dtlb_lu_hit.eq(d_tlb.lu_hit_o), - ] - - # PTW - m.submodules.ptw = ptw = PTW(self.asid_width) - m.d.comb += [ptw_active.eq(ptw.ptw_active_o), - walking_instr.eq(ptw.walking_instr_o), - ptw_error.eq(ptw.ptw_error_o), - ptw.enable_translation_i.eq(self.enable_translation_i), - - update_vaddr.eq(ptw.update_vaddr_o), - update_ptw_itlb.eq(ptw.itlb_update_o), - update_ptw_dtlb.eq(ptw.dtlb_update_o), - - ptw.itlb_access_i.eq(itlb_lu_access), - ptw.itlb_hit_i.eq(itlb_lu_hit), - ptw.itlb_vaddr_i.eq(self.icache_areq_i.fetch_vaddr), - - ptw.dtlb_access_i.eq(dtlb_lu_access), - ptw.dtlb_hit_i.eq(dtlb_lu_hit), - ptw.dtlb_vaddr_i.eq(self.lsu_vaddr_i), - - ptw.req_port_i.eq(self.req_port_i), - self.req_port_o.eq(ptw.req_port_o), - ] - - # ila_1 i_ila_1 ( - # .clk(clk_i), # input wire clk - # .probe0({req_port_o.address_tag, req_port_o.address_index}), - # .probe1(req_port_o.data_req), # input wire [63:0] probe1 - # .probe2(req_port_i.data_gnt), # input wire [0:0] probe2 - # .probe3(req_port_i.data_rdata), # input wire [0:0] probe3 - # .probe4(req_port_i.data_rvalid), # input wire [0:0] probe4 - # .probe5(ptw_error), # input wire [1:0] probe5 - # .probe6(update_vaddr), # input wire [0:0] probe6 - # .probe7(update_ptw_itlb.valid), # input wire [0:0] probe7 - # .probe8(update_ptw_dtlb.valid), # input wire [0:0] probe8 - # .probe9(dtlb_lu_access), # input wire [0:0] probe9 - # .probe10(lsu_vaddr_i), # input wire [0:0] probe10 - # .probe11(dtlb_lu_hit), # input wire [0:0] probe11 - # .probe12(itlb_lu_access), # input wire [0:0] probe12 - # .probe13(icache_areq_i.fetch_vaddr), # input wire [0:0] probe13 - # .probe14(itlb_lu_hit) # input wire [0:0] probe13 - # ); - - #----------------------- - # Instruction Interface - #----------------------- - # The instruction interface is a simple request response interface - - # MMU disabled: just pass through - m.d.comb += [self.icache_areq_o.fetch_valid.eq( - self.icache_areq_i.fetch_req), - # play through in case we disabled address translation - self.icache_areq_o.fetch_paddr.eq( - self.icache_areq_i.fetch_vaddr) - ] - # two potential exception sources: - # 1. HPTW threw an exception -> signal with a page fault exception - # 2. We got an access error because of insufficient permissions -> - # throw an access exception - m.d.comb += self.icache_areq_o.fetch_exception.valid.eq(0) - # Check whether we are allowed to access this memory region - # from a fetch perspective - - # XXX TODO: use PermissionValidator instead [we like modules] - m.d.comb += iaccess_err.eq(self.icache_areq_i.fetch_req & \ - (((self.priv_lvl_i == PRIV_LVL_U) & \ - ~itlb_content.u) | \ - ((self.priv_lvl_i == PRIV_LVL_S) & \ - itlb_content.u))) - - # MMU enabled: address from TLB, request delayed until hit. - # Error when TLB hit and no access right or TLB hit and - # translated address not valid (e.g. AXI decode error), - # or when PTW performs walk due to ITLB miss and raises - # an error. - with m.If (self.enable_translation_i): - # we work with SV39, so if VM is enabled, check that - # all bits [63:38] are equal - with m.If (self.icache_areq_i.fetch_req & \ - ~(((~self.icache_areq_i.fetch_vaddr[38:64]) == 0) | \ - (self.icache_areq_i.fetch_vaddr[38:64]) == 0)): - fe = self.icache_areq_o.fetch_exception - m.d.comb += [fe.cause.eq(INSTR_ACCESS_FAULT), - fe.tval.eq(self.icache_areq_i.fetch_vaddr), - fe.valid.eq(1) - ] - - m.d.comb += self.icache_areq_o.fetch_valid.eq(0) - - # 4K page - paddr = Signal.like(self.icache_areq_o.fetch_paddr) - paddr4k = Cat(self.icache_areq_i.fetch_vaddr[0:12], - itlb_content.ppn) - m.d.comb += paddr.eq(paddr4k) - # Mega page - with m.If(itlb_is_2M): - m.d.comb += paddr[12:21].eq( - self.icache_areq_i.fetch_vaddr[12:21]) - # Giga page - with m.If(itlb_is_1G): - m.d.comb += paddr[12:30].eq( - self.icache_areq_i.fetch_vaddr[12:30]) - m.d.comb += self.icache_areq_o.fetch_paddr.eq(paddr) - - # --------- - # ITLB Hit - # -------- - # if we hit the ITLB output the request signal immediately - with m.If(itlb_lu_hit): - m.d.comb += self.icache_areq_o.fetch_valid.eq( - self.icache_areq_i.fetch_req) - # we got an access error - with m.If (iaccess_err): - # throw a page fault - fe = self.icache_areq_o.fetch_exception - m.d.comb += [fe.cause.eq(INSTR_ACCESS_FAULT), - fe.tval.eq(self.icache_areq_i.fetch_vaddr), - fe.valid.eq(1) - ] - # --------- - # ITLB Miss - # --------- - # watch out for exceptions happening during walking the page table - with m.Elif(ptw_active & walking_instr): - m.d.comb += self.icache_areq_o.fetch_valid.eq(ptw_error) - fe = self.icache_areq_o.fetch_exception - m.d.comb += [fe.cause.eq(INSTR_PAGE_FAULT), - fe.tval.eq(uaddr64), - fe.valid.eq(1) - ] - - #----------------------- - # Data Interface - #----------------------- - - lsu_vaddr = Signal(64) - dtlb_pte = PTE() - misaligned_ex = RVException() - lsu_req = Signal() - lsu_is_store = Signal() - dtlb_hit = Signal() - dtlb_is_2M = Signal() - dtlb_is_1G = Signal() - - # check if we need to do translation or if we are always - # ready (e.g.: we are not translating anything) - m.d.comb += self.lsu_dtlb_hit_o.eq(Mux(self.en_ld_st_translation_i, - dtlb_lu_hit, 1)) - - # The data interface is simpler and only consists of a - # request/response interface - m.d.comb += [ - # save request and DTLB response - lsu_vaddr.eq(self.lsu_vaddr_i), - lsu_req.eq(self.lsu_req_i), - misaligned_ex.eq(self.misaligned_ex_i), - dtlb_pte.eq(dtlb_content), - dtlb_hit.eq(dtlb_lu_hit), - lsu_is_store.eq(self.lsu_is_store_i), - dtlb_is_2M.eq(dtlb_is_2M), - dtlb_is_1G.eq(dtlb_is_1G), - ] - m.d.sync += [ - self.lsu_paddr_o.eq(lsu_vaddr), - self.lsu_valid_o.eq(lsu_req), - self.lsu_exception_o.eq(misaligned_ex), - ] - - sverr = Signal() - usrerr = Signal() - - m.d.comb += [ - # mute misaligned exceptions if there is no request - # otherwise they will throw accidental exceptions - misaligned_ex.valid.eq(self.misaligned_ex_i.valid & self.lsu_req_i), - - # SUM is not set and we are trying to access a user - # page in supervisor mode - sverr.eq(self.ld_st_priv_lvl_i == PRIV_LVL_S & ~self.sum_i & \ - dtlb_pte.u), - # this is not a user page but we are in user mode and - # trying to access it - usrerr.eq(self.ld_st_priv_lvl_i == PRIV_LVL_U & ~dtlb_pte.u), - - # Check if the User flag is set, then we may only - # access it in supervisor mode if SUM is enabled - daccess_err.eq(sverr | usrerr), - ] - - # translation is enabled and no misaligned exception occurred - with m.If(self.en_ld_st_translation_i & ~misaligned_ex.valid): - m.d.comb += lsu_req.eq(0) - # 4K page - paddr = Signal.like(lsu_vaddr) - paddr4k = Cat(lsu_vaddr[0:12], itlb_content.ppn) - m.d.comb += paddr.eq(paddr4k) - # Mega page - with m.If(dtlb_is_2M): - m.d.comb += paddr[12:21].eq(lsu_vaddr[12:21]) - # Giga page - with m.If(dtlb_is_1G): - m.d.comb += paddr[12:30].eq(lsu_vaddr[12:30]) - m.d.sync += self.lsu_paddr_o.eq(paddr) - - # --------- - # DTLB Hit - # -------- - with m.If(dtlb_hit & lsu_req): - m.d.comb += lsu_req.eq(1) - # this is a store - with m.If (lsu_is_store): - # check if the page is write-able and - # we are not violating privileges - # also check if the dirty flag is set - with m.If(~dtlb_pte.w | daccess_err | ~dtlb_pte.d): - le = self.lsu_exception_o - m.d.sync += [le.cause.eq(STORE_PAGE_FAULT), - le.tval.eq(lsu_vaddr), - le.valid.eq(1) - ] - - # this is a load, check for sufficient access - # privileges - throw a page fault if necessary - with m.Elif(daccess_err): - le = self.lsu_exception_o - m.d.sync += [le.cause.eq(LOAD_PAGE_FAULT), - le.tval.eq(lsu_vaddr), - le.valid.eq(1) - ] - # --------- - # DTLB Miss - # --------- - # watch out for exceptions - with m.Elif (ptw_active & ~walking_instr): - # page table walker threw an exception - with m.If (ptw_error): - # an error makes the translation valid - m.d.comb += lsu_req.eq(1) - # the page table walker can only throw page faults - with m.If (lsu_is_store): - le = self.lsu_exception_o - m.d.sync += [le.cause.eq(STORE_PAGE_FAULT), - le.tval.eq(uaddr64), - le.valid.eq(1) - ] - with m.Else(): - m.d.sync += [le.cause.eq(LOAD_PAGE_FAULT), - le.tval.eq(uaddr64), - le.valid.eq(1) - ] - - return m - - def ports(self): - return [self.flush_i, self.enable_translation_i, - self.en_ld_st_translation_i, - self.lsu_req_i, - self.lsu_vaddr_i, self.lsu_is_store_i, self.lsu_dtlb_hit_o, - self.lsu_valid_o, self.lsu_paddr_o, - self.priv_lvl_i, self.ld_st_priv_lvl_i, self.sum_i, self.mxr_i, - self.satp_ppn_i, self.asid_i, self.flush_tlb_i, - self.itlb_miss_o, self.dtlb_miss_o] + \ - self.icache_areq_i.ports() + self.icache_areq_o.ports() + \ - self.req_port_i.ports() + self.req_port_o.ports() + \ - self.misaligned_ex_i.ports() + self.lsu_exception_o.ports() - -if __name__ == '__main__': - mmu = MMU() - vl = rtlil.convert(mmu, ports=mmu.ports()) - with open("test_mmu.il", "w") as f: - f.write(vl) - diff --git a/TLB/src/ariane/plru.py b/TLB/src/ariane/plru.py deleted file mode 100644 index 7c4a4041..00000000 --- a/TLB/src/ariane/plru.py +++ /dev/null @@ -1,101 +0,0 @@ -from nmigen import Signal, Module, Cat, Const, Elaboratable -from math import log2 - -from ptw import TLBUpdate, PTE, ASID_WIDTH - -class PLRU(Elaboratable): - """ PLRU - Pseudo Least Recently Used Replacement - - PLRU-tree indexing: - lvl0 0 - / \ - / \ - lvl1 1 2 - / \ / \ - lvl2 3 4 5 6 - / \ /\/\ /\ - ... ... ... ... - """ - def __init__(self, entries): - self.entries = entries - self.lu_hit = Signal(entries) - self.replace_en_o = Signal(entries) - self.lu_access_i = Signal() - # Tree (bit per entry) - self.TLBSZ = 2*(self.entries-1) - self.plru_tree = Signal(self.TLBSZ) - self.plru_tree_o = Signal(self.TLBSZ) - - def elaborate(self, platform): - m = Module() - - # Just predefine which nodes will be set/cleared - # E.g. for a TLB with 8 entries, the for-loop is semantically - # equivalent to the following pseudo-code: - # unique case (1'b1) - # lu_hit[7]: plru_tree[0, 2, 6] = {1, 1, 1}; - # lu_hit[6]: plru_tree[0, 2, 6] = {1, 1, 0}; - # lu_hit[5]: plru_tree[0, 2, 5] = {1, 0, 1}; - # lu_hit[4]: plru_tree[0, 2, 5] = {1, 0, 0}; - # lu_hit[3]: plru_tree[0, 1, 4] = {0, 1, 1}; - # lu_hit[2]: plru_tree[0, 1, 4] = {0, 1, 0}; - # lu_hit[1]: plru_tree[0, 1, 3] = {0, 0, 1}; - # lu_hit[0]: plru_tree[0, 1, 3] = {0, 0, 0}; - # default: begin /* No hit */ end - # endcase - LOG_TLB = int(log2(self.entries)) - print(LOG_TLB) - for i in range(self.entries): - # we got a hit so update the pointer as it was least recently used - hit = Signal(reset_less=True) - m.d.comb += hit.eq(self.lu_hit[i] & self.lu_access_i) - with m.If(hit): - # Set the nodes to the values we would expect - for lvl in range(LOG_TLB): - idx_base = (1< MSB, lvl1 <=> MSB-1, ... - shift = LOG_TLB - lvl; - new_idx = Const(~((i >> (shift-1)) & 1), (1, False)) - plru_idx = idx_base + (i >> shift) - print ("plru", i, lvl, hex(idx_base), - plru_idx, shift, new_idx) - m.d.comb += self.plru_tree_o[plru_idx].eq(new_idx) - - # Decode tree to write enable signals - # Next for-loop basically creates the following logic for e.g. - # an 8 entry TLB (note: pseudo-code obviously): - # replace_en[7] = &plru_tree[ 6, 2, 0]; #plru_tree[0,2,6]=={1,1,1} - # replace_en[6] = &plru_tree[~6, 2, 0]; #plru_tree[0,2,6]=={1,1,0} - # replace_en[5] = &plru_tree[ 5,~2, 0]; #plru_tree[0,2,5]=={1,0,1} - # replace_en[4] = &plru_tree[~5,~2, 0]; #plru_tree[0,2,5]=={1,0,0} - # replace_en[3] = &plru_tree[ 4, 1,~0]; #plru_tree[0,1,4]=={0,1,1} - # replace_en[2] = &plru_tree[~4, 1,~0]; #plru_tree[0,1,4]=={0,1,0} - # replace_en[1] = &plru_tree[ 3,~1,~0]; #plru_tree[0,1,3]=={0,0,1} - # replace_en[0] = &plru_tree[~3,~1,~0]; #plru_tree[0,1,3]=={0,0,0} - # For each entry traverse the tree. If every tree-node matches - # the corresponding bit of the entry's index, this is - # the next entry to replace. - replace = [] - for i in range(self.entries): - en = [] - for lvl in range(LOG_TLB): - idx_base = (1< MSB, lvl1 <=> MSB-1, ... - shift = LOG_TLB - lvl; - new_idx = (i >> (shift-1)) & 1; - plru_idx = idx_base + (i>>shift) - plru = Signal(reset_less=True, - name="plru-%d-%d-%d" % (i, lvl, plru_idx)) - m.d.comb += plru.eq(self.plru_tree[plru_idx]) - # en &= plru_tree_q[idx_base + (i>>shift)] == new_idx; - if new_idx: - en.append(~plru) # yes inverted (using bool()) - else: - en.append(plru) # yes inverted (using bool()) - print ("plru", i, en) - # boolean logic manipulation: - # plru0 & plru1 & plru2 == ~(~plru0 | ~plru1 | ~plru2) - replace.append(~Cat(*en).bool()) - m.d.comb += self.replace_en_o.eq(Cat(*replace)) - - return m diff --git a/TLB/src/ariane/ptw.py b/TLB/src/ariane/ptw.py deleted file mode 100644 index 05ec2d7d..00000000 --- a/TLB/src/ariane/ptw.py +++ /dev/null @@ -1,539 +0,0 @@ -""" -# Copyright 2018 ETH Zurich and University of Bologna. -# Copyright and related rights are licensed under the Solderpad Hardware -# License, Version 0.51 (the "License"); you may not use this file except in -# compliance with the License. You may obtain a copy of the License at -# http:#solderpad.org/licenses/SHL-0.51. Unless required by applicable law -# or agreed to in writing, software, hardware and materials distributed under -# this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -# -# Author: David Schaffenrath, TU Graz -# Author: Florian Zaruba, ETH Zurich -# Date: 24.4.2017 -# Description: Hardware-PTW - -/* verilator lint_off WIDTH */ -import ariane_pkg::*; - -see linux kernel source: - -* "arch/riscv/include/asm/page.h" -* "arch/riscv/include/asm/mmu_context.h" -* "arch/riscv/Kconfig" (CONFIG_PAGE_OFFSET) - -""" - -from nmigen import Const, Signal, Cat, Module -from nmigen.hdl.ast import ArrayProxy -from nmigen.cli import verilog, rtlil -from math import log2 - - -DCACHE_SET_ASSOC = 8 -CONFIG_L1D_SIZE = 32*1024 -DCACHE_INDEX_WIDTH = int(log2(CONFIG_L1D_SIZE / DCACHE_SET_ASSOC)) -DCACHE_TAG_WIDTH = 56 - DCACHE_INDEX_WIDTH - -ASID_WIDTH = 8 - - -class DCacheReqI: - def __init__(self): - self.address_index = Signal(DCACHE_INDEX_WIDTH) - self.address_tag = Signal(DCACHE_TAG_WIDTH) - self.data_wdata = Signal(64) - self.data_req = Signal() - self.data_we = Signal() - self.data_be = Signal(8) - self.data_size = Signal(2) - self.kill_req = Signal() - self.tag_valid = Signal() - - def eq(self, inp): - res = [] - for (o, i) in zip(self.ports(), inp.ports()): - res.append(o.eq(i)) - return res - - def ports(self): - return [self.address_index, self.address_tag, - self.data_wdata, self.data_req, - self.data_we, self.data_be, self.data_size, - self.kill_req, self.tag_valid, - ] - -class DCacheReqO: - def __init__(self): - self.data_gnt = Signal() - self.data_rvalid = Signal() - self.data_rdata = Signal(64) # actually in PTE object format - - def eq(self, inp): - res = [] - for (o, i) in zip(self.ports(), inp.ports()): - res.append(o.eq(i)) - return res - - def ports(self): - return [self.data_gnt, self.data_rvalid, self.data_rdata] - - -class PTE: #(RecordObject): - def __init__(self): - self.v = Signal() - self.r = Signal() - self.w = Signal() - self.x = Signal() - self.u = Signal() - self.g = Signal() - self.a = Signal() - self.d = Signal() - self.rsw = Signal(2) - self.ppn = Signal(44) - self.reserved = Signal(10) - - def flatten(self): - return Cat(*self.ports()) - - def eq(self, x): - if isinstance(x, ArrayProxy): - res = [] - for o in self.ports(): - i = getattr(x, o.name) - res.append(i) - x = Cat(*res) - else: - x = x.flatten() - return self.flatten().eq(x) - - def __iter__(self): - """ order is critical so that flatten creates LSB to MSB - """ - yield self.v - yield self.r - yield self.w - yield self.x - yield self.u - yield self.g - yield self.a - yield self.d - yield self.rsw - yield self.ppn - yield self.reserved - - def ports(self): - return list(self) - - -class TLBUpdate: - def __init__(self, asid_width): - self.valid = Signal() # valid flag - self.is_2M = Signal() - self.is_1G = Signal() - self.vpn = Signal(27) - self.asid = Signal(asid_width) - self.content = PTE() - - def flatten(self): - return Cat(*self.ports()) - - def eq(self, x): - return self.flatten().eq(x.flatten()) - - def ports(self): - return [self.valid, self.is_2M, self.is_1G, self.vpn, self.asid] + \ - self.content.ports() - - -# SV39 defines three levels of page tables -LVL1 = Const(0, 2) # defined to 0 so that ptw_lvl default-resets to LVL1 -LVL2 = Const(1, 2) -LVL3 = Const(2, 2) - - -class PTW: - def __init__(self, asid_width=8): - self.asid_width = asid_width - - self.flush_i = Signal() # flush everything, we need to do this because - # actually everything we do is speculative at this stage - # e.g.: there could be a CSR instruction that changes everything - self.ptw_active_o = Signal(reset=1) # active if not IDLE - self.walking_instr_o = Signal() # set when walking for TLB - self.ptw_error_o = Signal() # set when an error occurred - self.enable_translation_i = Signal() # CSRs indicate to enable SV39 - self.en_ld_st_translation_i = Signal() # enable VM translation for ld/st - - self.lsu_is_store_i = Signal() # translation triggered by store - # PTW memory interface - self.req_port_i = DCacheReqO() - self.req_port_o = DCacheReqI() - - # to TLBs, update logic - self.itlb_update_o = TLBUpdate(asid_width) - self.dtlb_update_o = TLBUpdate(asid_width) - - self.update_vaddr_o = Signal(39) - - self.asid_i = Signal(self.asid_width) - # from TLBs - # did we miss? - self.itlb_access_i = Signal() - self.itlb_hit_i = Signal() - self.itlb_vaddr_i = Signal(64) - - self.dtlb_access_i = Signal() - self.dtlb_hit_i = Signal() - self.dtlb_vaddr_i = Signal(64) - # from CSR file - self.satp_ppn_i = Signal(44) # ppn from satp - self.mxr_i = Signal() - # Performance counters - self.itlb_miss_o = Signal() - self.dtlb_miss_o = Signal() - - def ports(self): - return [self.ptw_active_o, self.walking_instr_o, self.ptw_error_o, - ] - return [ - self.enable_translation_i, self.en_ld_st_translation_i, - self.lsu_is_store_i, self.req_port_i, self.req_port_o, - self.update_vaddr_o, - self.asid_i, - self.itlb_access_i, self.itlb_hit_i, self.itlb_vaddr_i, - self.dtlb_access_i, self.dtlb_hit_i, self.dtlb_vaddr_i, - self.satp_ppn_i, self.mxr_i, - self.itlb_miss_o, self.dtlb_miss_o - ] + self.itlb_update_o.ports() + self.dtlb_update_o.ports() - - def elaborate(self, platform): - m = Module() - - # input registers - data_rvalid = Signal() - data_rdata = Signal(64) - - # NOTE: pte decodes the incoming bit-field (data_rdata). data_rdata - # is spec'd in 64-bit binary-format: better to spec as Record? - pte = PTE() - m.d.comb += pte.flatten().eq(data_rdata) - - # SV39 defines three levels of page tables - ptw_lvl = Signal(2) # default=0=LVL1 on reset (see above) - ptw_lvl1 = Signal() - ptw_lvl2 = Signal() - ptw_lvl3 = Signal() - m.d.comb += [ptw_lvl1.eq(ptw_lvl == LVL1), - ptw_lvl2.eq(ptw_lvl == LVL2), - ptw_lvl3.eq(ptw_lvl == LVL3)] - - # is this an instruction page table walk? - is_instr_ptw = Signal() - global_mapping = Signal() - # latched tag signal - tag_valid = Signal() - # register the ASID - tlb_update_asid = Signal(self.asid_width) - # register VPN we need to walk, SV39 defines a 39 bit virtual addr - vaddr = Signal(64) - # 4 byte aligned physical pointer - ptw_pptr = Signal(56) - - end = DCACHE_INDEX_WIDTH + DCACHE_TAG_WIDTH - m.d.sync += [ - # Assignments - self.update_vaddr_o.eq(vaddr), - - self.walking_instr_o.eq(is_instr_ptw), - # directly output the correct physical address - self.req_port_o.address_index.eq(ptw_pptr[0:DCACHE_INDEX_WIDTH]), - self.req_port_o.address_tag.eq(ptw_pptr[DCACHE_INDEX_WIDTH:end]), - # we are never going to kill this request - self.req_port_o.kill_req.eq(0), # XXX assign comb? - # we are never going to write with the HPTW - self.req_port_o.data_wdata.eq(Const(0, 64)), # XXX assign comb? - # ----------- - # TLB Update - # ----------- - self.itlb_update_o.vpn.eq(vaddr[12:39]), - self.dtlb_update_o.vpn.eq(vaddr[12:39]), - # update the correct page table level - self.itlb_update_o.is_2M.eq(ptw_lvl2), - self.itlb_update_o.is_1G.eq(ptw_lvl1), - self.dtlb_update_o.is_2M.eq(ptw_lvl2), - self.dtlb_update_o.is_1G.eq(ptw_lvl1), - # output the correct ASID - self.itlb_update_o.asid.eq(tlb_update_asid), - self.dtlb_update_o.asid.eq(tlb_update_asid), - # set the global mapping bit - self.itlb_update_o.content.eq(pte), - self.itlb_update_o.content.g.eq(global_mapping), - self.dtlb_update_o.content.eq(pte), - self.dtlb_update_o.content.g.eq(global_mapping), - - self.req_port_o.tag_valid.eq(tag_valid), - ] - - #------------------- - # Page table walker - #------------------- - # A virtual address va is translated into a physical address pa as - # follows: - # 1. Let a be sptbr.ppn × PAGESIZE, and let i = LEVELS-1. (For Sv39, - # PAGESIZE=2^12 and LEVELS=3.) - # 2. Let pte be the value of the PTE at address a+va.vpn[i]×PTESIZE. - # (For Sv32, PTESIZE=4.) - # 3. If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise an - # access exception. - # 4. Otherwise, the PTE is valid. If pte.r = 1 or pte.x = 1, go to - # step 5. Otherwise, this PTE is a pointer to the next level of - # the page table. - # Let i=i-1. If i < 0, stop and raise an access exception. - # Otherwise, let a = pte.ppn × PAGESIZE and go to step 2. - # 5. A leaf PTE has been found. Determine if the requested memory - # access is allowed by the pte.r, pte.w, and pte.x bits. If not, - # stop and raise an access exception. Otherwise, the translation is - # successful. Set pte.a to 1, and, if the memory access is a - # store, set pte.d to 1. - # The translated physical address is given as follows: - # - pa.pgoff = va.pgoff. - # - If i > 0, then this is a superpage translation and - # pa.ppn[i-1:0] = va.vpn[i-1:0]. - # - pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i]. - # 6. If i > 0 and pa.ppn[i − 1 : 0] != 0, this is a misaligned - # superpage stop and raise a page-fault exception. - - m.d.sync += tag_valid.eq(0) - - # default assignments - m.d.comb += [ - # PTW memory interface - self.req_port_o.data_req.eq(0), - self.req_port_o.data_be.eq(Const(0xFF, 8)), - self.req_port_o.data_size.eq(Const(0b11, 2)), - self.req_port_o.data_we.eq(0), - self.ptw_error_o.eq(0), - self.itlb_update_o.valid.eq(0), - self.dtlb_update_o.valid.eq(0), - - self.itlb_miss_o.eq(0), - self.dtlb_miss_o.eq(0), - ] - - # ------------ - # State Machine - # ------------ - - with m.FSM() as fsm: - - with m.State("IDLE"): - self.idle(m, is_instr_ptw, ptw_lvl, global_mapping, - ptw_pptr, vaddr, tlb_update_asid) - - with m.State("WAIT_GRANT"): - self.grant(m, tag_valid, data_rvalid) - - with m.State("PTE_LOOKUP"): - # we wait for the valid signal - with m.If(data_rvalid): - self.lookup(m, pte, ptw_lvl, ptw_lvl1, ptw_lvl2, ptw_lvl3, - data_rvalid, global_mapping, - is_instr_ptw, ptw_pptr) - - # Propagate error to MMU/LSU - with m.State("PROPAGATE_ERROR"): - m.next = "IDLE" - m.d.comb += self.ptw_error_o.eq(1) - - # wait for the rvalid before going back to IDLE - with m.State("WAIT_RVALID"): - with m.If(data_rvalid): - m.next = "IDLE" - - m.d.sync += [data_rdata.eq(self.req_port_i.data_rdata), - data_rvalid.eq(self.req_port_i.data_rvalid) - ] - - return m - - def set_grant_state(self, m): - # should we have flushed before we got an rvalid, - # wait for it until going back to IDLE - with m.If(self.flush_i): - with m.If (self.req_port_i.data_gnt): - m.next = "WAIT_RVALID" - with m.Else(): - m.next = "IDLE" - with m.Else(): - m.next = "WAIT_GRANT" - - def idle(self, m, is_instr_ptw, ptw_lvl, global_mapping, - ptw_pptr, vaddr, tlb_update_asid): - # by default we start with the top-most page table - m.d.sync += [is_instr_ptw.eq(0), - ptw_lvl.eq(LVL1), - global_mapping.eq(0), - self.ptw_active_o.eq(0), # deactive (IDLE) - ] - # work out itlb/dtlb miss - m.d.comb += self.itlb_miss_o.eq(self.enable_translation_i & \ - self.itlb_access_i & \ - ~self.itlb_hit_i & \ - ~self.dtlb_access_i) - m.d.comb += self.dtlb_miss_o.eq(self.en_ld_st_translation_i & \ - self.dtlb_access_i & \ - ~self.dtlb_hit_i) - # we got an ITLB miss? - with m.If(self.itlb_miss_o): - pptr = Cat(Const(0, 3), self.itlb_vaddr_i[30:39], - self.satp_ppn_i) - m.d.sync += [ptw_pptr.eq(pptr), - is_instr_ptw.eq(1), - vaddr.eq(self.itlb_vaddr_i), - tlb_update_asid.eq(self.asid_i), - ] - self.set_grant_state(m) - - # we got a DTLB miss? - with m.Elif(self.dtlb_miss_o): - pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[30:39], - self.satp_ppn_i) - m.d.sync += [ptw_pptr.eq(pptr), - vaddr.eq(self.dtlb_vaddr_i), - tlb_update_asid.eq(self.asid_i), - ] - self.set_grant_state(m) - - def grant(self, m, tag_valid, data_rvalid): - # we've got a data WAIT_GRANT so tell the - # cache that the tag is valid - - # send a request out - m.d.comb += self.req_port_o.data_req.eq(1) - # wait for the WAIT_GRANT - with m.If(self.req_port_i.data_gnt): - # send the tag valid signal one cycle later - m.d.sync += tag_valid.eq(1) - # should we have flushed before we got an rvalid, - # wait for it until going back to IDLE - with m.If(self.flush_i): - with m.If (~data_rvalid): - m.next = "WAIT_RVALID" - with m.Else(): - m.next = "IDLE" - with m.Else(): - m.next = "PTE_LOOKUP" - - def lookup(self, m, pte, ptw_lvl, ptw_lvl1, ptw_lvl2, ptw_lvl3, - data_rvalid, global_mapping, - is_instr_ptw, ptw_pptr): - # temporaries - pte_rx = Signal(reset_less=True) - pte_exe = Signal(reset_less=True) - pte_inv = Signal(reset_less=True) - pte_a = Signal(reset_less=True) - st_wd = Signal(reset_less=True) - m.d.comb += [pte_rx.eq(pte.r | pte.x), - pte_exe.eq(~pte.x | ~pte.a), - pte_inv.eq(~pte.v | (~pte.r & pte.w)), - pte_a.eq(pte.a & (pte.r | (pte.x & self.mxr_i))), - st_wd.eq(self.lsu_is_store_i & (~pte.w | ~pte.d))] - - l1err = Signal(reset_less=True) - l2err = Signal(reset_less=True) - m.d.comb += [l2err.eq((ptw_lvl2) & pte.ppn[0:9] != Const(0, 9)), - l1err.eq((ptw_lvl1) & pte.ppn[0:18] != Const(0, 18)) ] - - # check if the global mapping bit is set - with m.If (pte.g): - m.d.sync += global_mapping.eq(1) - - m.next = "IDLE" - - # ------------- - # Invalid PTE - # ------------- - # If pte.v = 0, or if pte.r = 0 and pte.w = 1, - # stop and raise a page-fault exception. - with m.If (pte_inv): - m.next = "PROPAGATE_ERROR" - - # ----------- - # Valid PTE - # ----------- - - # it is a valid PTE - # if pte.r = 1 or pte.x = 1 it is a valid PTE - with m.Elif (pte_rx): - # Valid translation found (either 1G, 2M or 4K) - with m.If(is_instr_ptw): - # ------------ - # Update ITLB - # ------------ - # If page not executable, we can directly raise error. - # This doesn't put a useless entry into the TLB. - # The same idea applies to the access flag since we let - # the access flag be managed by SW. - with m.If (pte_exe): - m.next = "IDLE" - with m.Else(): - m.d.comb += self.itlb_update_o.valid.eq(1) - - with m.Else(): - # ------------ - # Update DTLB - # ------------ - # Check if the access flag has been set, otherwise - # throw page-fault and let software handle those bits. - # If page not readable (there are no write-only pages) - # directly raise an error. This doesn't put a useless - # entry into the TLB. - with m.If(pte_a): - m.d.comb += self.dtlb_update_o.valid.eq(1) - with m.Else(): - m.next = "PROPAGATE_ERROR" - # Request is a store: perform additional checks - # If the request was a store and the page not - # write-able, raise an error - # the same applies if the dirty flag is not set - with m.If (st_wd): - m.d.comb += self.dtlb_update_o.valid.eq(0) - m.next = "PROPAGATE_ERROR" - - # check if the ppn is correctly aligned: Case (6) - with m.If(l1err | l2err): - m.next = "PROPAGATE_ERROR" - m.d.comb += [self.dtlb_update_o.valid.eq(0), - self.itlb_update_o.valid.eq(0)] - - # this is a pointer to the next TLB level - with m.Else(): - # pointer to next level of page table - with m.If (ptw_lvl1): - # we are in the second level now - pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[21:30], pte.ppn) - m.d.sync += [ptw_pptr.eq(pptr), - ptw_lvl.eq(LVL2) - ] - with m.If(ptw_lvl2): - # here we received a pointer to the third level - pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[12:21], pte.ppn) - m.d.sync += [ptw_pptr.eq(pptr), - ptw_lvl.eq(LVL3) - ] - self.set_grant_state(m) - - with m.If (ptw_lvl3): - # Should already be the last level - # page table => Error - m.d.sync += ptw_lvl.eq(LVL3) - m.next = "PROPAGATE_ERROR" - - -if __name__ == '__main__': - ptw = PTW() - vl = rtlil.convert(ptw, ports=ptw.ports()) - with open("test_ptw.il", "w") as f: - f.write(vl) diff --git a/TLB/src/ariane/src/exceptcause.py b/TLB/src/ariane/src/exceptcause.py new file mode 100644 index 00000000..4c5cb2d5 --- /dev/null +++ b/TLB/src/ariane/src/exceptcause.py @@ -0,0 +1,16 @@ +from nmigen import Const + +INSTR_ADDR_MISALIGNED = Const(0, 64) +INSTR_ACCESS_FAULT = Const(1, 64) +ILLEGAL_INSTR = Const(2, 64) +BREAKPOINT = Const(3, 64) +LD_ADDR_MISALIGNED = Const(4, 64) +LD_ACCESS_FAULT = Const(5, 64) +ST_ADDR_MISALIGNED = Const(6, 64) +ST_ACCESS_FAULT = Const(7, 64) +ENV_CALL_UMODE = Const(8, 64) # environment call from user mode +ENV_CALL_SMODE = Const(9, 64) # environment call from supervisor mode +ENV_CALL_MMODE = Const(11, 64) # environment call from machine mode +INSTR_PAGE_FAULT = Const(12, 64) # Instruction page fault +LOAD_PAGE_FAULT = Const(13, 64) # Load page fault +STORE_PAGE_FAULT = Const(15, 64) # Store page fault diff --git a/TLB/src/ariane/src/mmu.py b/TLB/src/ariane/src/mmu.py new file mode 100644 index 00000000..c0739cc0 --- /dev/null +++ b/TLB/src/ariane/src/mmu.py @@ -0,0 +1,462 @@ +""" +# Copyright 2018 ETH Zurich and University of Bologna. +# Copyright and related rights are licensed under the Solderpad Hardware +# License, Version 0.51 (the "License"); you may not use this file except in +# compliance with the License. You may obtain a copy of the License at +# http:#solderpad.org/licenses/SHL-0.51. Unless required by applicable law +# or agreed to in writing, software, hardware and materials distributed under +# this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# Author: Florian Zaruba, ETH Zurich +# Date: 19/04/2017 +# Description: Memory Management Unit for Ariane, contains TLB and +# address translation unit. SV39 as defined in RISC-V +# privilege specification 1.11-WIP + +import ariane_pkg::*; +""" + +from nmigen import Const, Signal, Cat, Module, Mux +from nmigen.cli import verilog, rtlil + +from ptw import DCacheReqI, DCacheReqO, TLBUpdate, PTE, PTW +from tlb import TLB +from exceptcause import (INSTR_ACCESS_FAULT, INSTR_PAGE_FAULT, + LOAD_PAGE_FAULT, STORE_PAGE_FAULT) + +PRIV_LVL_M = Const(0b11, 2) +PRIV_LVL_S = Const(0b01, 2) +PRIV_LVL_U = Const(0b00, 2) + + +class RVException: + def __init__(self): + self.cause = Signal(64) # cause of exception + self.tval = Signal(64) # more info of causing exception + # (e.g.: instruction causing it), + # address of LD/ST fault + self.valid = Signal() + + def eq(self, inp): + res = [] + for (o, i) in zip(self.ports(), inp.ports()): + res.append(o.eq(i)) + return res + + def __iter__(self): + yield self.cause + yield self.tval + yield self.valid + + def ports(self): + return list(self) + + +class ICacheReqI: + def __init__(self): + self.fetch_valid = Signal() # address translation valid + self.fetch_paddr = Signal(64) # physical address in + self.fetch_exception = RVException() # exception occurred during fetch + + def __iter__(self): + yield self.fetch_valid + yield self.fetch_paddr + yield from self.fetch_exception + + def ports(self): + return list(self) + + +class ICacheReqO: + def __init__(self): + self.fetch_req = Signal() # address translation request + self.fetch_vaddr = Signal(64) # virtual address out + + def __iter__(self): + yield self.fetch_req + yield self.fetch_vaddr + + def ports(self): + return list(self) + + +class MMU: + def __init__(self, instr_tlb_entries = 4, + data_tlb_entries = 4, + asid_width = 1): + self.instr_tlb_entries = instr_tlb_entries + self.data_tlb_entries = data_tlb_entries + self.asid_width = asid_width + + self.flush_i = Signal() + self.enable_translation_i = Signal() + self.en_ld_st_translation_i = Signal() # enable VM translation for LD/ST + # IF interface + self.icache_areq_i = ICacheReqO() + self.icache_areq_o = ICacheReqI() + # LSU interface + # this is a more minimalistic interface because the actual addressing + # logic is handled in the LSU as we distinguish load and stores, + # what we do here is simple address translation + self.misaligned_ex_i = RVException() + self.lsu_req_i = Signal() # request address translation + self.lsu_vaddr_i = Signal(64) # virtual address in + self.lsu_is_store_i = Signal() # the translation is requested by a store + # if we need to walk the page table we can't grant in the same cycle + + # Cycle 0 + self.lsu_dtlb_hit_o = Signal() # sent in the same cycle as the request + # if translation hits in the DTLB + # Cycle 1 + self.lsu_valid_o = Signal() # translation is valid + self.lsu_paddr_o = Signal(64) # translated address + self.lsu_exception_o = RVException() # addr translate threw exception + + # General control signals + self.priv_lvl_i = Signal(2) + self.ld_st_priv_lvl_i = Signal(2) + self.sum_i = Signal() + self.mxr_i = Signal() + # input logic flag_mprv_i, + self.satp_ppn_i = Signal(44) + self.asid_i = Signal(self.asid_width) + self.flush_tlb_i = Signal() + # Performance counters + self.itlb_miss_o = Signal() + self.dtlb_miss_o = Signal() + # PTW memory interface + self.req_port_i = DCacheReqO() + self.req_port_o = DCacheReqI() + + def elaborate(self, platform): + m = Module() + + iaccess_err = Signal() # insufficient priv to access instr page + daccess_err = Signal() # insufficient priv to access data page + ptw_active = Signal() # PTW is currently walking a page table + walking_instr = Signal() # PTW is walking because of an ITLB miss + ptw_error = Signal() # PTW threw an exception + + update_vaddr = Signal(39) + uaddr64 = Cat(update_vaddr, Const(0, 25)) # extend to 64bit with zeros + update_ptw_itlb = TLBUpdate(self.asid_width) + update_ptw_dtlb = TLBUpdate(self.asid_width) + + itlb_lu_access = Signal() + itlb_content = PTE() + itlb_is_2M = Signal() + itlb_is_1G = Signal() + itlb_lu_hit = Signal() + + dtlb_lu_access = Signal() + dtlb_content = PTE() + dtlb_is_2M = Signal() + dtlb_is_1G = Signal() + dtlb_lu_hit = Signal() + + # Assignments + m.d.comb += [itlb_lu_access.eq(self.icache_areq_i.fetch_req), + dtlb_lu_access.eq(self.lsu_req_i) + ] + + # ITLB + m.submodules.i_tlb = i_tlb = TLB(self.instr_tlb_entries, + self.asid_width) + m.d.comb += [i_tlb.flush_i.eq(self.flush_tlb_i), + i_tlb.update_i.eq(update_ptw_itlb), + i_tlb.lu_access_i.eq(itlb_lu_access), + i_tlb.lu_asid_i.eq(self.asid_i), + i_tlb.lu_vaddr_i.eq(self.icache_areq_i.fetch_vaddr), + itlb_content.eq(i_tlb.lu_content_o), + itlb_is_2M.eq(i_tlb.lu_is_2M_o), + itlb_is_1G.eq(i_tlb.lu_is_1G_o), + itlb_lu_hit.eq(i_tlb.lu_hit_o), + ] + + # DTLB + m.submodules.d_tlb = d_tlb = TLB(self.data_tlb_entries, + self.asid_width) + m.d.comb += [d_tlb.flush_i.eq(self.flush_tlb_i), + d_tlb.update_i.eq(update_ptw_dtlb), + d_tlb.lu_access_i.eq(dtlb_lu_access), + d_tlb.lu_asid_i.eq(self.asid_i), + d_tlb.lu_vaddr_i.eq(self.lsu_vaddr_i), + dtlb_content.eq(d_tlb.lu_content_o), + dtlb_is_2M.eq(d_tlb.lu_is_2M_o), + dtlb_is_1G.eq(d_tlb.lu_is_1G_o), + dtlb_lu_hit.eq(d_tlb.lu_hit_o), + ] + + # PTW + m.submodules.ptw = ptw = PTW(self.asid_width) + m.d.comb += [ptw_active.eq(ptw.ptw_active_o), + walking_instr.eq(ptw.walking_instr_o), + ptw_error.eq(ptw.ptw_error_o), + ptw.enable_translation_i.eq(self.enable_translation_i), + + update_vaddr.eq(ptw.update_vaddr_o), + update_ptw_itlb.eq(ptw.itlb_update_o), + update_ptw_dtlb.eq(ptw.dtlb_update_o), + + ptw.itlb_access_i.eq(itlb_lu_access), + ptw.itlb_hit_i.eq(itlb_lu_hit), + ptw.itlb_vaddr_i.eq(self.icache_areq_i.fetch_vaddr), + + ptw.dtlb_access_i.eq(dtlb_lu_access), + ptw.dtlb_hit_i.eq(dtlb_lu_hit), + ptw.dtlb_vaddr_i.eq(self.lsu_vaddr_i), + + ptw.req_port_i.eq(self.req_port_i), + self.req_port_o.eq(ptw.req_port_o), + ] + + # ila_1 i_ila_1 ( + # .clk(clk_i), # input wire clk + # .probe0({req_port_o.address_tag, req_port_o.address_index}), + # .probe1(req_port_o.data_req), # input wire [63:0] probe1 + # .probe2(req_port_i.data_gnt), # input wire [0:0] probe2 + # .probe3(req_port_i.data_rdata), # input wire [0:0] probe3 + # .probe4(req_port_i.data_rvalid), # input wire [0:0] probe4 + # .probe5(ptw_error), # input wire [1:0] probe5 + # .probe6(update_vaddr), # input wire [0:0] probe6 + # .probe7(update_ptw_itlb.valid), # input wire [0:0] probe7 + # .probe8(update_ptw_dtlb.valid), # input wire [0:0] probe8 + # .probe9(dtlb_lu_access), # input wire [0:0] probe9 + # .probe10(lsu_vaddr_i), # input wire [0:0] probe10 + # .probe11(dtlb_lu_hit), # input wire [0:0] probe11 + # .probe12(itlb_lu_access), # input wire [0:0] probe12 + # .probe13(icache_areq_i.fetch_vaddr), # input wire [0:0] probe13 + # .probe14(itlb_lu_hit) # input wire [0:0] probe13 + # ); + + #----------------------- + # Instruction Interface + #----------------------- + # The instruction interface is a simple request response interface + + # MMU disabled: just pass through + m.d.comb += [self.icache_areq_o.fetch_valid.eq( + self.icache_areq_i.fetch_req), + # play through in case we disabled address translation + self.icache_areq_o.fetch_paddr.eq( + self.icache_areq_i.fetch_vaddr) + ] + # two potential exception sources: + # 1. HPTW threw an exception -> signal with a page fault exception + # 2. We got an access error because of insufficient permissions -> + # throw an access exception + m.d.comb += self.icache_areq_o.fetch_exception.valid.eq(0) + # Check whether we are allowed to access this memory region + # from a fetch perspective + + # XXX TODO: use PermissionValidator instead [we like modules] + m.d.comb += iaccess_err.eq(self.icache_areq_i.fetch_req & \ + (((self.priv_lvl_i == PRIV_LVL_U) & \ + ~itlb_content.u) | \ + ((self.priv_lvl_i == PRIV_LVL_S) & \ + itlb_content.u))) + + # MMU enabled: address from TLB, request delayed until hit. + # Error when TLB hit and no access right or TLB hit and + # translated address not valid (e.g. AXI decode error), + # or when PTW performs walk due to ITLB miss and raises + # an error. + with m.If (self.enable_translation_i): + # we work with SV39, so if VM is enabled, check that + # all bits [63:38] are equal + with m.If (self.icache_areq_i.fetch_req & \ + ~(((~self.icache_areq_i.fetch_vaddr[38:64]) == 0) | \ + (self.icache_areq_i.fetch_vaddr[38:64]) == 0)): + fe = self.icache_areq_o.fetch_exception + m.d.comb += [fe.cause.eq(INSTR_ACCESS_FAULT), + fe.tval.eq(self.icache_areq_i.fetch_vaddr), + fe.valid.eq(1) + ] + + m.d.comb += self.icache_areq_o.fetch_valid.eq(0) + + # 4K page + paddr = Signal.like(self.icache_areq_o.fetch_paddr) + paddr4k = Cat(self.icache_areq_i.fetch_vaddr[0:12], + itlb_content.ppn) + m.d.comb += paddr.eq(paddr4k) + # Mega page + with m.If(itlb_is_2M): + m.d.comb += paddr[12:21].eq( + self.icache_areq_i.fetch_vaddr[12:21]) + # Giga page + with m.If(itlb_is_1G): + m.d.comb += paddr[12:30].eq( + self.icache_areq_i.fetch_vaddr[12:30]) + m.d.comb += self.icache_areq_o.fetch_paddr.eq(paddr) + + # --------- + # ITLB Hit + # -------- + # if we hit the ITLB output the request signal immediately + with m.If(itlb_lu_hit): + m.d.comb += self.icache_areq_o.fetch_valid.eq( + self.icache_areq_i.fetch_req) + # we got an access error + with m.If (iaccess_err): + # throw a page fault + fe = self.icache_areq_o.fetch_exception + m.d.comb += [fe.cause.eq(INSTR_ACCESS_FAULT), + fe.tval.eq(self.icache_areq_i.fetch_vaddr), + fe.valid.eq(1) + ] + # --------- + # ITLB Miss + # --------- + # watch out for exceptions happening during walking the page table + with m.Elif(ptw_active & walking_instr): + m.d.comb += self.icache_areq_o.fetch_valid.eq(ptw_error) + fe = self.icache_areq_o.fetch_exception + m.d.comb += [fe.cause.eq(INSTR_PAGE_FAULT), + fe.tval.eq(uaddr64), + fe.valid.eq(1) + ] + + #----------------------- + # Data Interface + #----------------------- + + lsu_vaddr = Signal(64) + dtlb_pte = PTE() + misaligned_ex = RVException() + lsu_req = Signal() + lsu_is_store = Signal() + dtlb_hit = Signal() + dtlb_is_2M = Signal() + dtlb_is_1G = Signal() + + # check if we need to do translation or if we are always + # ready (e.g.: we are not translating anything) + m.d.comb += self.lsu_dtlb_hit_o.eq(Mux(self.en_ld_st_translation_i, + dtlb_lu_hit, 1)) + + # The data interface is simpler and only consists of a + # request/response interface + m.d.comb += [ + # save request and DTLB response + lsu_vaddr.eq(self.lsu_vaddr_i), + lsu_req.eq(self.lsu_req_i), + misaligned_ex.eq(self.misaligned_ex_i), + dtlb_pte.eq(dtlb_content), + dtlb_hit.eq(dtlb_lu_hit), + lsu_is_store.eq(self.lsu_is_store_i), + dtlb_is_2M.eq(dtlb_is_2M), + dtlb_is_1G.eq(dtlb_is_1G), + ] + m.d.sync += [ + self.lsu_paddr_o.eq(lsu_vaddr), + self.lsu_valid_o.eq(lsu_req), + self.lsu_exception_o.eq(misaligned_ex), + ] + + sverr = Signal() + usrerr = Signal() + + m.d.comb += [ + # mute misaligned exceptions if there is no request + # otherwise they will throw accidental exceptions + misaligned_ex.valid.eq(self.misaligned_ex_i.valid & self.lsu_req_i), + + # SUM is not set and we are trying to access a user + # page in supervisor mode + sverr.eq(self.ld_st_priv_lvl_i == PRIV_LVL_S & ~self.sum_i & \ + dtlb_pte.u), + # this is not a user page but we are in user mode and + # trying to access it + usrerr.eq(self.ld_st_priv_lvl_i == PRIV_LVL_U & ~dtlb_pte.u), + + # Check if the User flag is set, then we may only + # access it in supervisor mode if SUM is enabled + daccess_err.eq(sverr | usrerr), + ] + + # translation is enabled and no misaligned exception occurred + with m.If(self.en_ld_st_translation_i & ~misaligned_ex.valid): + m.d.comb += lsu_req.eq(0) + # 4K page + paddr = Signal.like(lsu_vaddr) + paddr4k = Cat(lsu_vaddr[0:12], itlb_content.ppn) + m.d.comb += paddr.eq(paddr4k) + # Mega page + with m.If(dtlb_is_2M): + m.d.comb += paddr[12:21].eq(lsu_vaddr[12:21]) + # Giga page + with m.If(dtlb_is_1G): + m.d.comb += paddr[12:30].eq(lsu_vaddr[12:30]) + m.d.sync += self.lsu_paddr_o.eq(paddr) + + # --------- + # DTLB Hit + # -------- + with m.If(dtlb_hit & lsu_req): + m.d.comb += lsu_req.eq(1) + # this is a store + with m.If (lsu_is_store): + # check if the page is write-able and + # we are not violating privileges + # also check if the dirty flag is set + with m.If(~dtlb_pte.w | daccess_err | ~dtlb_pte.d): + le = self.lsu_exception_o + m.d.sync += [le.cause.eq(STORE_PAGE_FAULT), + le.tval.eq(lsu_vaddr), + le.valid.eq(1) + ] + + # this is a load, check for sufficient access + # privileges - throw a page fault if necessary + with m.Elif(daccess_err): + le = self.lsu_exception_o + m.d.sync += [le.cause.eq(LOAD_PAGE_FAULT), + le.tval.eq(lsu_vaddr), + le.valid.eq(1) + ] + # --------- + # DTLB Miss + # --------- + # watch out for exceptions + with m.Elif (ptw_active & ~walking_instr): + # page table walker threw an exception + with m.If (ptw_error): + # an error makes the translation valid + m.d.comb += lsu_req.eq(1) + # the page table walker can only throw page faults + with m.If (lsu_is_store): + le = self.lsu_exception_o + m.d.sync += [le.cause.eq(STORE_PAGE_FAULT), + le.tval.eq(uaddr64), + le.valid.eq(1) + ] + with m.Else(): + m.d.sync += [le.cause.eq(LOAD_PAGE_FAULT), + le.tval.eq(uaddr64), + le.valid.eq(1) + ] + + return m + + def ports(self): + return [self.flush_i, self.enable_translation_i, + self.en_ld_st_translation_i, + self.lsu_req_i, + self.lsu_vaddr_i, self.lsu_is_store_i, self.lsu_dtlb_hit_o, + self.lsu_valid_o, self.lsu_paddr_o, + self.priv_lvl_i, self.ld_st_priv_lvl_i, self.sum_i, self.mxr_i, + self.satp_ppn_i, self.asid_i, self.flush_tlb_i, + self.itlb_miss_o, self.dtlb_miss_o] + \ + self.icache_areq_i.ports() + self.icache_areq_o.ports() + \ + self.req_port_i.ports() + self.req_port_o.ports() + \ + self.misaligned_ex_i.ports() + self.lsu_exception_o.ports() + +if __name__ == '__main__': + mmu = MMU() + vl = rtlil.convert(mmu, ports=mmu.ports()) + with open("test_mmu.il", "w") as f: + f.write(vl) + diff --git a/TLB/src/ariane/src/plru.py b/TLB/src/ariane/src/plru.py new file mode 100644 index 00000000..a98c03a6 --- /dev/null +++ b/TLB/src/ariane/src/plru.py @@ -0,0 +1,102 @@ +from nmigen import Signal, Module, Cat, Const +from nmigen.hdl.ir import Elaboratable +from math import log2 + +from ptw import TLBUpdate, PTE, ASID_WIDTH + +class PLRU(Elaboratable): + """ PLRU - Pseudo Least Recently Used Replacement + + PLRU-tree indexing: + lvl0 0 + / \ + / \ + lvl1 1 2 + / \ / \ + lvl2 3 4 5 6 + / \ /\/\ /\ + ... ... ... ... + """ + def __init__(self, entries): + self.entries = entries + self.lu_hit = Signal(entries) + self.replace_en_o = Signal(entries) + self.lu_access_i = Signal() + # Tree (bit per entry) + self.TLBSZ = 2*(self.entries-1) + self.plru_tree = Signal(self.TLBSZ) + self.plru_tree_o = Signal(self.TLBSZ) + + def elaborate(self, platform): + m = Module() + + # Just predefine which nodes will be set/cleared + # E.g. for a TLB with 8 entries, the for-loop is semantically + # equivalent to the following pseudo-code: + # unique case (1'b1) + # lu_hit[7]: plru_tree[0, 2, 6] = {1, 1, 1}; + # lu_hit[6]: plru_tree[0, 2, 6] = {1, 1, 0}; + # lu_hit[5]: plru_tree[0, 2, 5] = {1, 0, 1}; + # lu_hit[4]: plru_tree[0, 2, 5] = {1, 0, 0}; + # lu_hit[3]: plru_tree[0, 1, 4] = {0, 1, 1}; + # lu_hit[2]: plru_tree[0, 1, 4] = {0, 1, 0}; + # lu_hit[1]: plru_tree[0, 1, 3] = {0, 0, 1}; + # lu_hit[0]: plru_tree[0, 1, 3] = {0, 0, 0}; + # default: begin /* No hit */ end + # endcase + LOG_TLB = int(log2(self.entries)) + print(LOG_TLB) + for i in range(self.entries): + # we got a hit so update the pointer as it was least recently used + hit = Signal(reset_less=True) + m.d.comb += hit.eq(self.lu_hit[i] & self.lu_access_i) + with m.If(hit): + # Set the nodes to the values we would expect + for lvl in range(LOG_TLB): + idx_base = (1< MSB, lvl1 <=> MSB-1, ... + shift = LOG_TLB - lvl; + new_idx = Const(~((i >> (shift-1)) & 1), (1, False)) + plru_idx = idx_base + (i >> shift) + print ("plru", i, lvl, hex(idx_base), + plru_idx, shift, new_idx) + m.d.comb += self.plru_tree_o[plru_idx].eq(new_idx) + + # Decode tree to write enable signals + # Next for-loop basically creates the following logic for e.g. + # an 8 entry TLB (note: pseudo-code obviously): + # replace_en[7] = &plru_tree[ 6, 2, 0]; #plru_tree[0,2,6]=={1,1,1} + # replace_en[6] = &plru_tree[~6, 2, 0]; #plru_tree[0,2,6]=={1,1,0} + # replace_en[5] = &plru_tree[ 5,~2, 0]; #plru_tree[0,2,5]=={1,0,1} + # replace_en[4] = &plru_tree[~5,~2, 0]; #plru_tree[0,2,5]=={1,0,0} + # replace_en[3] = &plru_tree[ 4, 1,~0]; #plru_tree[0,1,4]=={0,1,1} + # replace_en[2] = &plru_tree[~4, 1,~0]; #plru_tree[0,1,4]=={0,1,0} + # replace_en[1] = &plru_tree[ 3,~1,~0]; #plru_tree[0,1,3]=={0,0,1} + # replace_en[0] = &plru_tree[~3,~1,~0]; #plru_tree[0,1,3]=={0,0,0} + # For each entry traverse the tree. If every tree-node matches + # the corresponding bit of the entry's index, this is + # the next entry to replace. + replace = [] + for i in range(self.entries): + en = [] + for lvl in range(LOG_TLB): + idx_base = (1< MSB, lvl1 <=> MSB-1, ... + shift = LOG_TLB - lvl; + new_idx = (i >> (shift-1)) & 1; + plru_idx = idx_base + (i>>shift) + plru = Signal(reset_less=True, + name="plru-%d-%d-%d" % (i, lvl, plru_idx)) + m.d.comb += plru.eq(self.plru_tree[plru_idx]) + # en &= plru_tree_q[idx_base + (i>>shift)] == new_idx; + if new_idx: + en.append(~plru) # yes inverted (using bool()) + else: + en.append(plru) # yes inverted (using bool()) + print ("plru", i, en) + # boolean logic manipulation: + # plru0 & plru1 & plru2 == ~(~plru0 | ~plru1 | ~plru2) + replace.append(~Cat(*en).bool()) + m.d.comb += self.replace_en_o.eq(Cat(*replace)) + + return m diff --git a/TLB/src/ariane/src/ptw.py b/TLB/src/ariane/src/ptw.py new file mode 100644 index 00000000..05ec2d7d --- /dev/null +++ b/TLB/src/ariane/src/ptw.py @@ -0,0 +1,539 @@ +""" +# Copyright 2018 ETH Zurich and University of Bologna. +# Copyright and related rights are licensed under the Solderpad Hardware +# License, Version 0.51 (the "License"); you may not use this file except in +# compliance with the License. You may obtain a copy of the License at +# http:#solderpad.org/licenses/SHL-0.51. Unless required by applicable law +# or agreed to in writing, software, hardware and materials distributed under +# this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# Author: David Schaffenrath, TU Graz +# Author: Florian Zaruba, ETH Zurich +# Date: 24.4.2017 +# Description: Hardware-PTW + +/* verilator lint_off WIDTH */ +import ariane_pkg::*; + +see linux kernel source: + +* "arch/riscv/include/asm/page.h" +* "arch/riscv/include/asm/mmu_context.h" +* "arch/riscv/Kconfig" (CONFIG_PAGE_OFFSET) + +""" + +from nmigen import Const, Signal, Cat, Module +from nmigen.hdl.ast import ArrayProxy +from nmigen.cli import verilog, rtlil +from math import log2 + + +DCACHE_SET_ASSOC = 8 +CONFIG_L1D_SIZE = 32*1024 +DCACHE_INDEX_WIDTH = int(log2(CONFIG_L1D_SIZE / DCACHE_SET_ASSOC)) +DCACHE_TAG_WIDTH = 56 - DCACHE_INDEX_WIDTH + +ASID_WIDTH = 8 + + +class DCacheReqI: + def __init__(self): + self.address_index = Signal(DCACHE_INDEX_WIDTH) + self.address_tag = Signal(DCACHE_TAG_WIDTH) + self.data_wdata = Signal(64) + self.data_req = Signal() + self.data_we = Signal() + self.data_be = Signal(8) + self.data_size = Signal(2) + self.kill_req = Signal() + self.tag_valid = Signal() + + def eq(self, inp): + res = [] + for (o, i) in zip(self.ports(), inp.ports()): + res.append(o.eq(i)) + return res + + def ports(self): + return [self.address_index, self.address_tag, + self.data_wdata, self.data_req, + self.data_we, self.data_be, self.data_size, + self.kill_req, self.tag_valid, + ] + +class DCacheReqO: + def __init__(self): + self.data_gnt = Signal() + self.data_rvalid = Signal() + self.data_rdata = Signal(64) # actually in PTE object format + + def eq(self, inp): + res = [] + for (o, i) in zip(self.ports(), inp.ports()): + res.append(o.eq(i)) + return res + + def ports(self): + return [self.data_gnt, self.data_rvalid, self.data_rdata] + + +class PTE: #(RecordObject): + def __init__(self): + self.v = Signal() + self.r = Signal() + self.w = Signal() + self.x = Signal() + self.u = Signal() + self.g = Signal() + self.a = Signal() + self.d = Signal() + self.rsw = Signal(2) + self.ppn = Signal(44) + self.reserved = Signal(10) + + def flatten(self): + return Cat(*self.ports()) + + def eq(self, x): + if isinstance(x, ArrayProxy): + res = [] + for o in self.ports(): + i = getattr(x, o.name) + res.append(i) + x = Cat(*res) + else: + x = x.flatten() + return self.flatten().eq(x) + + def __iter__(self): + """ order is critical so that flatten creates LSB to MSB + """ + yield self.v + yield self.r + yield self.w + yield self.x + yield self.u + yield self.g + yield self.a + yield self.d + yield self.rsw + yield self.ppn + yield self.reserved + + def ports(self): + return list(self) + + +class TLBUpdate: + def __init__(self, asid_width): + self.valid = Signal() # valid flag + self.is_2M = Signal() + self.is_1G = Signal() + self.vpn = Signal(27) + self.asid = Signal(asid_width) + self.content = PTE() + + def flatten(self): + return Cat(*self.ports()) + + def eq(self, x): + return self.flatten().eq(x.flatten()) + + def ports(self): + return [self.valid, self.is_2M, self.is_1G, self.vpn, self.asid] + \ + self.content.ports() + + +# SV39 defines three levels of page tables +LVL1 = Const(0, 2) # defined to 0 so that ptw_lvl default-resets to LVL1 +LVL2 = Const(1, 2) +LVL3 = Const(2, 2) + + +class PTW: + def __init__(self, asid_width=8): + self.asid_width = asid_width + + self.flush_i = Signal() # flush everything, we need to do this because + # actually everything we do is speculative at this stage + # e.g.: there could be a CSR instruction that changes everything + self.ptw_active_o = Signal(reset=1) # active if not IDLE + self.walking_instr_o = Signal() # set when walking for TLB + self.ptw_error_o = Signal() # set when an error occurred + self.enable_translation_i = Signal() # CSRs indicate to enable SV39 + self.en_ld_st_translation_i = Signal() # enable VM translation for ld/st + + self.lsu_is_store_i = Signal() # translation triggered by store + # PTW memory interface + self.req_port_i = DCacheReqO() + self.req_port_o = DCacheReqI() + + # to TLBs, update logic + self.itlb_update_o = TLBUpdate(asid_width) + self.dtlb_update_o = TLBUpdate(asid_width) + + self.update_vaddr_o = Signal(39) + + self.asid_i = Signal(self.asid_width) + # from TLBs + # did we miss? + self.itlb_access_i = Signal() + self.itlb_hit_i = Signal() + self.itlb_vaddr_i = Signal(64) + + self.dtlb_access_i = Signal() + self.dtlb_hit_i = Signal() + self.dtlb_vaddr_i = Signal(64) + # from CSR file + self.satp_ppn_i = Signal(44) # ppn from satp + self.mxr_i = Signal() + # Performance counters + self.itlb_miss_o = Signal() + self.dtlb_miss_o = Signal() + + def ports(self): + return [self.ptw_active_o, self.walking_instr_o, self.ptw_error_o, + ] + return [ + self.enable_translation_i, self.en_ld_st_translation_i, + self.lsu_is_store_i, self.req_port_i, self.req_port_o, + self.update_vaddr_o, + self.asid_i, + self.itlb_access_i, self.itlb_hit_i, self.itlb_vaddr_i, + self.dtlb_access_i, self.dtlb_hit_i, self.dtlb_vaddr_i, + self.satp_ppn_i, self.mxr_i, + self.itlb_miss_o, self.dtlb_miss_o + ] + self.itlb_update_o.ports() + self.dtlb_update_o.ports() + + def elaborate(self, platform): + m = Module() + + # input registers + data_rvalid = Signal() + data_rdata = Signal(64) + + # NOTE: pte decodes the incoming bit-field (data_rdata). data_rdata + # is spec'd in 64-bit binary-format: better to spec as Record? + pte = PTE() + m.d.comb += pte.flatten().eq(data_rdata) + + # SV39 defines three levels of page tables + ptw_lvl = Signal(2) # default=0=LVL1 on reset (see above) + ptw_lvl1 = Signal() + ptw_lvl2 = Signal() + ptw_lvl3 = Signal() + m.d.comb += [ptw_lvl1.eq(ptw_lvl == LVL1), + ptw_lvl2.eq(ptw_lvl == LVL2), + ptw_lvl3.eq(ptw_lvl == LVL3)] + + # is this an instruction page table walk? + is_instr_ptw = Signal() + global_mapping = Signal() + # latched tag signal + tag_valid = Signal() + # register the ASID + tlb_update_asid = Signal(self.asid_width) + # register VPN we need to walk, SV39 defines a 39 bit virtual addr + vaddr = Signal(64) + # 4 byte aligned physical pointer + ptw_pptr = Signal(56) + + end = DCACHE_INDEX_WIDTH + DCACHE_TAG_WIDTH + m.d.sync += [ + # Assignments + self.update_vaddr_o.eq(vaddr), + + self.walking_instr_o.eq(is_instr_ptw), + # directly output the correct physical address + self.req_port_o.address_index.eq(ptw_pptr[0:DCACHE_INDEX_WIDTH]), + self.req_port_o.address_tag.eq(ptw_pptr[DCACHE_INDEX_WIDTH:end]), + # we are never going to kill this request + self.req_port_o.kill_req.eq(0), # XXX assign comb? + # we are never going to write with the HPTW + self.req_port_o.data_wdata.eq(Const(0, 64)), # XXX assign comb? + # ----------- + # TLB Update + # ----------- + self.itlb_update_o.vpn.eq(vaddr[12:39]), + self.dtlb_update_o.vpn.eq(vaddr[12:39]), + # update the correct page table level + self.itlb_update_o.is_2M.eq(ptw_lvl2), + self.itlb_update_o.is_1G.eq(ptw_lvl1), + self.dtlb_update_o.is_2M.eq(ptw_lvl2), + self.dtlb_update_o.is_1G.eq(ptw_lvl1), + # output the correct ASID + self.itlb_update_o.asid.eq(tlb_update_asid), + self.dtlb_update_o.asid.eq(tlb_update_asid), + # set the global mapping bit + self.itlb_update_o.content.eq(pte), + self.itlb_update_o.content.g.eq(global_mapping), + self.dtlb_update_o.content.eq(pte), + self.dtlb_update_o.content.g.eq(global_mapping), + + self.req_port_o.tag_valid.eq(tag_valid), + ] + + #------------------- + # Page table walker + #------------------- + # A virtual address va is translated into a physical address pa as + # follows: + # 1. Let a be sptbr.ppn × PAGESIZE, and let i = LEVELS-1. (For Sv39, + # PAGESIZE=2^12 and LEVELS=3.) + # 2. Let pte be the value of the PTE at address a+va.vpn[i]×PTESIZE. + # (For Sv32, PTESIZE=4.) + # 3. If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise an + # access exception. + # 4. Otherwise, the PTE is valid. If pte.r = 1 or pte.x = 1, go to + # step 5. Otherwise, this PTE is a pointer to the next level of + # the page table. + # Let i=i-1. If i < 0, stop and raise an access exception. + # Otherwise, let a = pte.ppn × PAGESIZE and go to step 2. + # 5. A leaf PTE has been found. Determine if the requested memory + # access is allowed by the pte.r, pte.w, and pte.x bits. If not, + # stop and raise an access exception. Otherwise, the translation is + # successful. Set pte.a to 1, and, if the memory access is a + # store, set pte.d to 1. + # The translated physical address is given as follows: + # - pa.pgoff = va.pgoff. + # - If i > 0, then this is a superpage translation and + # pa.ppn[i-1:0] = va.vpn[i-1:0]. + # - pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i]. + # 6. If i > 0 and pa.ppn[i − 1 : 0] != 0, this is a misaligned + # superpage stop and raise a page-fault exception. + + m.d.sync += tag_valid.eq(0) + + # default assignments + m.d.comb += [ + # PTW memory interface + self.req_port_o.data_req.eq(0), + self.req_port_o.data_be.eq(Const(0xFF, 8)), + self.req_port_o.data_size.eq(Const(0b11, 2)), + self.req_port_o.data_we.eq(0), + self.ptw_error_o.eq(0), + self.itlb_update_o.valid.eq(0), + self.dtlb_update_o.valid.eq(0), + + self.itlb_miss_o.eq(0), + self.dtlb_miss_o.eq(0), + ] + + # ------------ + # State Machine + # ------------ + + with m.FSM() as fsm: + + with m.State("IDLE"): + self.idle(m, is_instr_ptw, ptw_lvl, global_mapping, + ptw_pptr, vaddr, tlb_update_asid) + + with m.State("WAIT_GRANT"): + self.grant(m, tag_valid, data_rvalid) + + with m.State("PTE_LOOKUP"): + # we wait for the valid signal + with m.If(data_rvalid): + self.lookup(m, pte, ptw_lvl, ptw_lvl1, ptw_lvl2, ptw_lvl3, + data_rvalid, global_mapping, + is_instr_ptw, ptw_pptr) + + # Propagate error to MMU/LSU + with m.State("PROPAGATE_ERROR"): + m.next = "IDLE" + m.d.comb += self.ptw_error_o.eq(1) + + # wait for the rvalid before going back to IDLE + with m.State("WAIT_RVALID"): + with m.If(data_rvalid): + m.next = "IDLE" + + m.d.sync += [data_rdata.eq(self.req_port_i.data_rdata), + data_rvalid.eq(self.req_port_i.data_rvalid) + ] + + return m + + def set_grant_state(self, m): + # should we have flushed before we got an rvalid, + # wait for it until going back to IDLE + with m.If(self.flush_i): + with m.If (self.req_port_i.data_gnt): + m.next = "WAIT_RVALID" + with m.Else(): + m.next = "IDLE" + with m.Else(): + m.next = "WAIT_GRANT" + + def idle(self, m, is_instr_ptw, ptw_lvl, global_mapping, + ptw_pptr, vaddr, tlb_update_asid): + # by default we start with the top-most page table + m.d.sync += [is_instr_ptw.eq(0), + ptw_lvl.eq(LVL1), + global_mapping.eq(0), + self.ptw_active_o.eq(0), # deactive (IDLE) + ] + # work out itlb/dtlb miss + m.d.comb += self.itlb_miss_o.eq(self.enable_translation_i & \ + self.itlb_access_i & \ + ~self.itlb_hit_i & \ + ~self.dtlb_access_i) + m.d.comb += self.dtlb_miss_o.eq(self.en_ld_st_translation_i & \ + self.dtlb_access_i & \ + ~self.dtlb_hit_i) + # we got an ITLB miss? + with m.If(self.itlb_miss_o): + pptr = Cat(Const(0, 3), self.itlb_vaddr_i[30:39], + self.satp_ppn_i) + m.d.sync += [ptw_pptr.eq(pptr), + is_instr_ptw.eq(1), + vaddr.eq(self.itlb_vaddr_i), + tlb_update_asid.eq(self.asid_i), + ] + self.set_grant_state(m) + + # we got a DTLB miss? + with m.Elif(self.dtlb_miss_o): + pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[30:39], + self.satp_ppn_i) + m.d.sync += [ptw_pptr.eq(pptr), + vaddr.eq(self.dtlb_vaddr_i), + tlb_update_asid.eq(self.asid_i), + ] + self.set_grant_state(m) + + def grant(self, m, tag_valid, data_rvalid): + # we've got a data WAIT_GRANT so tell the + # cache that the tag is valid + + # send a request out + m.d.comb += self.req_port_o.data_req.eq(1) + # wait for the WAIT_GRANT + with m.If(self.req_port_i.data_gnt): + # send the tag valid signal one cycle later + m.d.sync += tag_valid.eq(1) + # should we have flushed before we got an rvalid, + # wait for it until going back to IDLE + with m.If(self.flush_i): + with m.If (~data_rvalid): + m.next = "WAIT_RVALID" + with m.Else(): + m.next = "IDLE" + with m.Else(): + m.next = "PTE_LOOKUP" + + def lookup(self, m, pte, ptw_lvl, ptw_lvl1, ptw_lvl2, ptw_lvl3, + data_rvalid, global_mapping, + is_instr_ptw, ptw_pptr): + # temporaries + pte_rx = Signal(reset_less=True) + pte_exe = Signal(reset_less=True) + pte_inv = Signal(reset_less=True) + pte_a = Signal(reset_less=True) + st_wd = Signal(reset_less=True) + m.d.comb += [pte_rx.eq(pte.r | pte.x), + pte_exe.eq(~pte.x | ~pte.a), + pte_inv.eq(~pte.v | (~pte.r & pte.w)), + pte_a.eq(pte.a & (pte.r | (pte.x & self.mxr_i))), + st_wd.eq(self.lsu_is_store_i & (~pte.w | ~pte.d))] + + l1err = Signal(reset_less=True) + l2err = Signal(reset_less=True) + m.d.comb += [l2err.eq((ptw_lvl2) & pte.ppn[0:9] != Const(0, 9)), + l1err.eq((ptw_lvl1) & pte.ppn[0:18] != Const(0, 18)) ] + + # check if the global mapping bit is set + with m.If (pte.g): + m.d.sync += global_mapping.eq(1) + + m.next = "IDLE" + + # ------------- + # Invalid PTE + # ------------- + # If pte.v = 0, or if pte.r = 0 and pte.w = 1, + # stop and raise a page-fault exception. + with m.If (pte_inv): + m.next = "PROPAGATE_ERROR" + + # ----------- + # Valid PTE + # ----------- + + # it is a valid PTE + # if pte.r = 1 or pte.x = 1 it is a valid PTE + with m.Elif (pte_rx): + # Valid translation found (either 1G, 2M or 4K) + with m.If(is_instr_ptw): + # ------------ + # Update ITLB + # ------------ + # If page not executable, we can directly raise error. + # This doesn't put a useless entry into the TLB. + # The same idea applies to the access flag since we let + # the access flag be managed by SW. + with m.If (pte_exe): + m.next = "IDLE" + with m.Else(): + m.d.comb += self.itlb_update_o.valid.eq(1) + + with m.Else(): + # ------------ + # Update DTLB + # ------------ + # Check if the access flag has been set, otherwise + # throw page-fault and let software handle those bits. + # If page not readable (there are no write-only pages) + # directly raise an error. This doesn't put a useless + # entry into the TLB. + with m.If(pte_a): + m.d.comb += self.dtlb_update_o.valid.eq(1) + with m.Else(): + m.next = "PROPAGATE_ERROR" + # Request is a store: perform additional checks + # If the request was a store and the page not + # write-able, raise an error + # the same applies if the dirty flag is not set + with m.If (st_wd): + m.d.comb += self.dtlb_update_o.valid.eq(0) + m.next = "PROPAGATE_ERROR" + + # check if the ppn is correctly aligned: Case (6) + with m.If(l1err | l2err): + m.next = "PROPAGATE_ERROR" + m.d.comb += [self.dtlb_update_o.valid.eq(0), + self.itlb_update_o.valid.eq(0)] + + # this is a pointer to the next TLB level + with m.Else(): + # pointer to next level of page table + with m.If (ptw_lvl1): + # we are in the second level now + pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[21:30], pte.ppn) + m.d.sync += [ptw_pptr.eq(pptr), + ptw_lvl.eq(LVL2) + ] + with m.If(ptw_lvl2): + # here we received a pointer to the third level + pptr = Cat(Const(0, 3), self.dtlb_vaddr_i[12:21], pte.ppn) + m.d.sync += [ptw_pptr.eq(pptr), + ptw_lvl.eq(LVL3) + ] + self.set_grant_state(m) + + with m.If (ptw_lvl3): + # Should already be the last level + # page table => Error + m.d.sync += ptw_lvl.eq(LVL3) + m.next = "PROPAGATE_ERROR" + + +if __name__ == '__main__': + ptw = PTW() + vl = rtlil.convert(ptw, ports=ptw.ports()) + with open("test_ptw.il", "w") as f: + f.write(vl) diff --git a/TLB/src/ariane/src/tlb.py b/TLB/src/ariane/src/tlb.py new file mode 100644 index 00000000..f768571e --- /dev/null +++ b/TLB/src/ariane/src/tlb.py @@ -0,0 +1,170 @@ +""" +# Copyright 2018 ETH Zurich and University of Bologna. +# Copyright and related rights are licensed under the Solderpad Hardware +# License, Version 0.51 (the "License"); you may not use this file except in +# compliance with the License. You may obtain a copy of the License at +# http:#solderpad.org/licenses/SHL-0.51. Unless required by applicable law +# or agreed to in writing, software, hardware and materials distributed under +# this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# Author: David Schaffenrath, TU Graz +# Author: Florian Zaruba, ETH Zurich +# Date: 21.4.2017 +# Description: Translation Lookaside Buffer, SV39 +# fully set-associative + +Implementation in c++: +https://raw.githubusercontent.com/Tony-Hu/TreePLRU/master/TreePLRU.cpp + +Text description: +https://people.cs.clemson.edu/~mark/464/p_lru.txt + +Online simulator: +http://www.ntu.edu.sg/home/smitha/ParaCache/Paracache/vm.html +""" +from math import log2 +from nmigen import Signal, Module, Cat, Const, Array +from nmigen.cli import verilog, rtlil +from nmigen.lib.coding import Encoder + +from ptw import TLBUpdate, PTE, ASID_WIDTH +from plru import PLRU +from tlb_content import TLBContent + +TLB_ENTRIES = 8 + +class TLB: + def __init__(self, tlb_entries=8, asid_width=8): + self.tlb_entries = tlb_entries + self.asid_width = asid_width + + self.flush_i = Signal() # Flush signal + # Lookup signals + self.lu_access_i = Signal() + self.lu_asid_i = Signal(self.asid_width) + self.lu_vaddr_i = Signal(64) + self.lu_content_o = PTE() + self.lu_is_2M_o = Signal() + self.lu_is_1G_o = Signal() + self.lu_hit_o = Signal() + # Update TLB + self.pte_width = len(self.lu_content_o.flatten()) + self.update_i = TLBUpdate(asid_width) + + def elaborate(self, platform): + m = Module() + + vpn2 = Signal(9) + vpn1 = Signal(9) + vpn0 = Signal(9) + + #------------- + # Translation + #------------- + + # SV39 defines three levels of page tables + m.d.comb += [ vpn0.eq(self.lu_vaddr_i[12:21]), + vpn1.eq(self.lu_vaddr_i[21:30]), + vpn2.eq(self.lu_vaddr_i[30:39]), + ] + + tc = [] + for i in range(self.tlb_entries): + tlc = TLBContent(self.pte_width, self.asid_width) + setattr(m.submodules, "tc%d" % i, tlc) + tc.append(tlc) + # connect inputs + tlc.update_i = self.update_i # saves a lot of graphviz links + m.d.comb += [tlc.vpn0.eq(vpn0), + tlc.vpn1.eq(vpn1), + tlc.vpn2.eq(vpn2), + tlc.flush_i.eq(self.flush_i), + #tlc.update_i.eq(self.update_i), + tlc.lu_asid_i.eq(self.lu_asid_i)] + tc = Array(tc) + + #-------------- + # Select hit + #-------------- + + # use Encoder to select hit index + # XXX TODO: assert that there's only one valid entry (one lu_hit) + hitsel = Encoder(self.tlb_entries) + m.submodules.hitsel = hitsel + + hits = [] + for i in range(self.tlb_entries): + hits.append(tc[i].lu_hit_o) + m.d.comb += hitsel.i.eq(Cat(*hits)) # (goes into plru as well) + idx = hitsel.o + + active = Signal(reset_less=True) + m.d.comb += active.eq(~hitsel.n) + with m.If(active): + # active hit, send selected as output + m.d.comb += [ self.lu_is_1G_o.eq(tc[idx].lu_is_1G_o), + self.lu_is_2M_o.eq(tc[idx].lu_is_2M_o), + self.lu_hit_o.eq(1), + self.lu_content_o.flatten().eq(tc[idx].lu_content_o), + ] + + #-------------- + # PLRU. + #-------------- + + p = PLRU(self.tlb_entries) + plru_tree = Signal(p.TLBSZ) + m.submodules.plru = p + + # connect PLRU inputs/outputs + # XXX TODO: assert that there's only one valid entry (one replace_en) + en = [] + for i in range(self.tlb_entries): + en.append(tc[i].replace_en_i) + m.d.comb += [Cat(*en).eq(p.replace_en_o), # output from PLRU into tags + p.lu_hit.eq(hitsel.i), + p.lu_access_i.eq(self.lu_access_i), + p.plru_tree.eq(plru_tree)] + m.d.sync += plru_tree.eq(p.plru_tree_o) + + #-------------- + # Sanity checks + #-------------- + + assert (self.tlb_entries % 2 == 0) and (self.tlb_entries > 1), \ + "TLB size must be a multiple of 2 and greater than 1" + assert (self.asid_width >= 1), \ + "ASID width must be at least 1" + + return m + + """ + # Just for checking + function int countSetBits(logic[self.tlb_entries-1:0] vector); + automatic int count = 0; + foreach (vector[idx]) begin + count += vector[idx]; + end + return count; + endfunction + + assert property (@(posedge clk_i)(countSetBits(lu_hit) <= 1)) + else $error("More then one hit in TLB!"); $stop(); end + assert property (@(posedge clk_i)(countSetBits(replace_en) <= 1)) + else $error("More then one TLB entry selected for next replace!"); + """ + + def ports(self): + return [self.flush_i, self.lu_access_i, + self.lu_asid_i, self.lu_vaddr_i, + self.lu_is_2M_o, self.lu_is_1G_o, self.lu_hit_o, + ] + self.lu_content_o.ports() + self.update_i.ports() + +if __name__ == '__main__': + tlb = TLB() + vl = rtlil.convert(tlb, ports=tlb.ports()) + with open("test_tlb.il", "w") as f: + f.write(vl) + diff --git a/TLB/src/ariane/src/tlb_content.py b/TLB/src/ariane/src/tlb_content.py new file mode 100644 index 00000000..024c5697 --- /dev/null +++ b/TLB/src/ariane/src/tlb_content.py @@ -0,0 +1,125 @@ +from nmigen import Signal, Module, Cat, Const + +from ptw import TLBUpdate, PTE + +class TLBEntry: + def __init__(self, asid_width): + self.asid = Signal(asid_width) + # SV39 defines three levels of page tables + self.vpn0 = Signal(9) + self.vpn1 = Signal(9) + self.vpn2 = Signal(9) + self.is_2M = Signal() + self.is_1G = Signal() + self.valid = Signal() + + def flatten(self): + return Cat(*self.ports()) + + def eq(self, x): + return self.flatten().eq(x.flatten()) + + def ports(self): + return [self.asid, self.vpn0, self.vpn1, self.vpn2, + self.is_2M, self.is_1G, self.valid] + +class TLBContent: + def __init__(self, pte_width, asid_width): + self.asid_width = asid_width + self.pte_width = pte_width + self.flush_i = Signal() # Flush signal + # Update TLB + self.update_i = TLBUpdate(asid_width) + self.vpn2 = Signal(9) + self.vpn1 = Signal(9) + self.vpn0 = Signal(9) + self.replace_en_i = Signal() # replace the following entry, + # set by replacement strategy + # Lookup signals + self.lu_asid_i = Signal(asid_width) + self.lu_content_o = Signal(pte_width) + self.lu_is_2M_o = Signal() + self.lu_is_1G_o = Signal() + self.lu_hit_o = Signal() + + def elaborate(self, platform): + m = Module() + + tags = TLBEntry(self.asid_width) + content = Signal(self.pte_width) + + m.d.comb += [self.lu_hit_o.eq(0), + self.lu_is_2M_o.eq(0), + self.lu_is_1G_o.eq(0)] + + # temporaries for 1st level match + asid_ok = Signal(reset_less=True) + vpn2_ok = Signal(reset_less=True) + tags_ok = Signal(reset_less=True) + vpn2_hit = Signal(reset_less=True) + m.d.comb += [tags_ok.eq(tags.valid), + asid_ok.eq(tags.asid == self.lu_asid_i), + vpn2_ok.eq(tags.vpn2 == self.vpn2), + vpn2_hit.eq(tags_ok & asid_ok & vpn2_ok)] + # temporaries for 2nd level match + vpn1_ok = Signal(reset_less=True) + tags_2M = Signal(reset_less=True) + vpn0_ok = Signal(reset_less=True) + vpn0_or_2M = Signal(reset_less=True) + m.d.comb += [vpn1_ok.eq(self.vpn1 == tags.vpn1), + tags_2M.eq(tags.is_2M), + vpn0_ok.eq(self.vpn0 == tags.vpn0), + vpn0_or_2M.eq(tags_2M | vpn0_ok)] + # first level match, this may be a giga page, + # check the ASID flags as well + with m.If(vpn2_hit): + # second level + with m.If (tags.is_1G): + m.d.comb += [ self.lu_content_o.eq(content), + self.lu_is_1G_o.eq(1), + self.lu_hit_o.eq(1), + ] + # not a giga page hit so check further + with m.Elif(vpn1_ok): + # this could be a 2 mega page hit or a 4 kB hit + # output accordingly + with m.If(vpn0_or_2M): + m.d.comb += [ self.lu_content_o.eq(content), + self.lu_is_2M_o.eq(tags.is_2M), + self.lu_hit_o.eq(1), + ] + # ------------------ + # Update or Flush + # ------------------ + + # temporaries + replace_valid = Signal(reset_less=True) + m.d.comb += replace_valid.eq(self.update_i.valid & self.replace_en_i) + + # flush + with m.If (self.flush_i): + # invalidate (flush) conditions: all if zero or just this ASID + with m.If (self.lu_asid_i == Const(0, self.asid_width) | + (self.lu_asid_i == tags.asid)): + m.d.sync += tags.valid.eq(0) + + # normal replacement + with m.Elif(replace_valid): + m.d.sync += [ # update tag array + tags.asid.eq(self.update_i.asid), + tags.vpn2.eq(self.update_i.vpn[18:27]), + tags.vpn1.eq(self.update_i.vpn[9:18]), + tags.vpn0.eq(self.update_i.vpn[0:9]), + tags.is_1G.eq(self.update_i.is_1G), + tags.is_2M.eq(self.update_i.is_2M), + tags.valid.eq(1), + # and content as well + content.eq(self.update_i.content.flatten()) + ] + return m + + def ports(self): + return [self.flush_i, + self.lu_asid_i, + self.lu_is_2M_o, self.lu_is_1G_o, self.lu_hit_o, + ] + self.update_i.content.ports() + self.update_i.ports() diff --git a/TLB/src/ariane/test/test_ptw.py b/TLB/src/ariane/test/test_ptw.py new file mode 100644 index 00000000..e9c5324c --- /dev/null +++ b/TLB/src/ariane/test/test_ptw.py @@ -0,0 +1,127 @@ +import sys +sys.path.append("../src") +sys.path.append("../../../TestUtil") + +from nmigen.compat.sim import run_simulation + +from ptw import PTW, PTE + + +def testbench(dut): + + addr = 0x8000000 + + #pte = PTE() + #yield pte.v.eq(1) + #yield pte.r.eq(1) + + yield dut.req_port_i.data_gnt.eq(1) + yield dut.req_port_i.data_rvalid.eq(1) + yield dut.req_port_i.data_rdata.eq(0x43)#pte.flatten()) + + # data lookup + yield dut.en_ld_st_translation_i.eq(1) + yield dut.asid_i.eq(1) + + yield dut.dtlb_access_i.eq(1) + yield dut.dtlb_hit_i.eq(0) + yield dut.dtlb_vaddr_i.eq(0x400000000) + + yield + yield + yield + + yield dut.dtlb_access_i.eq(1) + yield dut.dtlb_hit_i.eq(0) + yield dut.dtlb_vaddr_i.eq(0x200000) + + yield + yield + yield + + yield dut.req_port_i.data_gnt.eq(0) + yield dut.dtlb_access_i.eq(1) + yield dut.dtlb_hit_i.eq(0) + yield dut.dtlb_vaddr_i.eq(0x400000011) + + yield + yield dut.req_port_i.data_gnt.eq(1) + yield + yield + + # data lookup, PTW levels 1-2-3 + addr = 0x4000000 + yield dut.dtlb_vaddr_i.eq(addr) + yield dut.mxr_i.eq(0x1) + yield dut.req_port_i.data_gnt.eq(1) + yield dut.req_port_i.data_rvalid.eq(1) + yield dut.req_port_i.data_rdata.eq(0x41 | (addr>>12)<<10)#pte.flatten()) + + yield dut.en_ld_st_translation_i.eq(1) + yield dut.asid_i.eq(1) + + yield dut.dtlb_access_i.eq(1) + yield dut.dtlb_hit_i.eq(0) + yield dut.dtlb_vaddr_i.eq(addr) + + yield + yield + yield + yield + yield + yield + yield + yield + + yield dut.req_port_i.data_gnt.eq(0) + yield dut.dtlb_access_i.eq(1) + yield dut.dtlb_hit_i.eq(0) + yield dut.dtlb_vaddr_i.eq(0x400000011) + + yield + yield dut.req_port_i.data_gnt.eq(1) + yield + yield + yield + yield + + + # instruction lookup + yield dut.en_ld_st_translation_i.eq(0) + yield dut.enable_translation_i.eq(1) + yield dut.asid_i.eq(1) + + yield dut.itlb_access_i.eq(1) + yield dut.itlb_hit_i.eq(0) + yield dut.itlb_vaddr_i.eq(0x800000) + + yield + yield + yield + + yield dut.itlb_access_i.eq(1) + yield dut.itlb_hit_i.eq(0) + yield dut.itlb_vaddr_i.eq(0x200000) + + yield + yield + yield + + yield dut.req_port_i.data_gnt.eq(0) + yield dut.itlb_access_i.eq(1) + yield dut.itlb_hit_i.eq(0) + yield dut.itlb_vaddr_i.eq(0x800011) + + yield + yield dut.req_port_i.data_gnt.eq(1) + yield + yield + + yield + + + +if __name__ == "__main__": + dut = PTW() + run_simulation(dut, testbench(dut), vcd_name="test_ptw.vcd") + print("PTW Unit Test Success") diff --git a/TLB/src/ariane/test/test_tlb.py b/TLB/src/ariane/test/test_tlb.py new file mode 100644 index 00000000..aab1d43c --- /dev/null +++ b/TLB/src/ariane/test/test_tlb.py @@ -0,0 +1,69 @@ +import sys +sys.path.append("../src") +sys.path.append("../../../TestUtil") + +from nmigen.compat.sim import run_simulation + +from tlb import TLB + +def set_vaddr(addr): + yield dut.lu_vaddr_i.eq(addr) + yield dut.update_i.vpn.eq(addr>>12) + + +def testbench(dut): + yield dut.lu_access_i.eq(1) + yield dut.lu_asid_i.eq(1) + yield dut.update_i.valid.eq(1) + yield dut.update_i.is_1G.eq(0) + yield dut.update_i.is_2M.eq(0) + yield dut.update_i.asid.eq(1) + yield dut.update_i.content.ppn.eq(0) + yield dut.update_i.content.rsw.eq(0) + yield dut.update_i.content.r.eq(1) + + yield + + addr = 0x80000 + yield from set_vaddr(addr) + yield + + addr = 0x90001 + yield from set_vaddr(addr) + yield + + addr = 0x28000000 + yield from set_vaddr(addr) + yield + + addr = 0x28000001 + yield from set_vaddr(addr) + + addr = 0x28000001 + yield from set_vaddr(addr) + yield + + addr = 0x1000040000 + yield from set_vaddr(addr) + yield + + addr = 0x1000040001 + yield from set_vaddr(addr) + yield + + yield dut.update_i.is_1G.eq(1) + addr = 0x2040000 + yield from set_vaddr(addr) + yield + + yield dut.update_i.is_1G.eq(1) + addr = 0x2040001 + yield from set_vaddr(addr) + yield + + yield + + +if __name__ == "__main__": + dut = TLB() + run_simulation(dut, testbench(dut), vcd_name="test_tlb.vcd") diff --git a/TLB/src/ariane/test_ptw.py b/TLB/src/ariane/test_ptw.py deleted file mode 100644 index a42a6da6..00000000 --- a/TLB/src/ariane/test_ptw.py +++ /dev/null @@ -1,122 +0,0 @@ -from nmigen.compat.sim import run_simulation - -from ptw import PTW, PTE - - -def testbench(dut): - - addr = 0x8000000 - - #pte = PTE() - #yield pte.v.eq(1) - #yield pte.r.eq(1) - - yield dut.req_port_i.data_gnt.eq(1) - yield dut.req_port_i.data_rvalid.eq(1) - yield dut.req_port_i.data_rdata.eq(0x43)#pte.flatten()) - - # data lookup - yield dut.en_ld_st_translation_i.eq(1) - yield dut.asid_i.eq(1) - - yield dut.dtlb_access_i.eq(1) - yield dut.dtlb_hit_i.eq(0) - yield dut.dtlb_vaddr_i.eq(0x400000000) - - yield - yield - yield - - yield dut.dtlb_access_i.eq(1) - yield dut.dtlb_hit_i.eq(0) - yield dut.dtlb_vaddr_i.eq(0x200000) - - yield - yield - yield - - yield dut.req_port_i.data_gnt.eq(0) - yield dut.dtlb_access_i.eq(1) - yield dut.dtlb_hit_i.eq(0) - yield dut.dtlb_vaddr_i.eq(0x400000011) - - yield - yield dut.req_port_i.data_gnt.eq(1) - yield - yield - - # data lookup, PTW levels 1-2-3 - addr = 0x4000000 - yield dut.dtlb_vaddr_i.eq(addr) - yield dut.mxr_i.eq(0x1) - yield dut.req_port_i.data_gnt.eq(1) - yield dut.req_port_i.data_rvalid.eq(1) - yield dut.req_port_i.data_rdata.eq(0x41 | (addr>>12)<<10)#pte.flatten()) - - yield dut.en_ld_st_translation_i.eq(1) - yield dut.asid_i.eq(1) - - yield dut.dtlb_access_i.eq(1) - yield dut.dtlb_hit_i.eq(0) - yield dut.dtlb_vaddr_i.eq(addr) - - yield - yield - yield - yield - yield - yield - yield - yield - - yield dut.req_port_i.data_gnt.eq(0) - yield dut.dtlb_access_i.eq(1) - yield dut.dtlb_hit_i.eq(0) - yield dut.dtlb_vaddr_i.eq(0x400000011) - - yield - yield dut.req_port_i.data_gnt.eq(1) - yield - yield - yield - yield - - - # instruction lookup - yield dut.en_ld_st_translation_i.eq(0) - yield dut.enable_translation_i.eq(1) - yield dut.asid_i.eq(1) - - yield dut.itlb_access_i.eq(1) - yield dut.itlb_hit_i.eq(0) - yield dut.itlb_vaddr_i.eq(0x800000) - - yield - yield - yield - - yield dut.itlb_access_i.eq(1) - yield dut.itlb_hit_i.eq(0) - yield dut.itlb_vaddr_i.eq(0x200000) - - yield - yield - yield - - yield dut.req_port_i.data_gnt.eq(0) - yield dut.itlb_access_i.eq(1) - yield dut.itlb_hit_i.eq(0) - yield dut.itlb_vaddr_i.eq(0x800011) - - yield - yield dut.req_port_i.data_gnt.eq(1) - yield - yield - - yield - - - -if __name__ == "__main__": - dut = PTW() - run_simulation(dut, testbench(dut), vcd_name="test_ptw.vcd") diff --git a/TLB/src/ariane/test_tlb.py b/TLB/src/ariane/test_tlb.py deleted file mode 100644 index bebca7f0..00000000 --- a/TLB/src/ariane/test_tlb.py +++ /dev/null @@ -1,65 +0,0 @@ -from nmigen.compat.sim import run_simulation - -from tlb import TLB - -def set_vaddr(addr): - yield dut.lu_vaddr_i.eq(addr) - yield dut.update_i.vpn.eq(addr>>12) - - -def testbench(dut): - yield dut.lu_access_i.eq(1) - yield dut.lu_asid_i.eq(1) - yield dut.update_i.valid.eq(1) - yield dut.update_i.is_1G.eq(0) - yield dut.update_i.is_2M.eq(0) - yield dut.update_i.asid.eq(1) - yield dut.update_i.content.ppn.eq(0) - yield dut.update_i.content.rsw.eq(0) - yield dut.update_i.content.r.eq(1) - - yield - - addr = 0x80000 - yield from set_vaddr(addr) - yield - - addr = 0x90001 - yield from set_vaddr(addr) - yield - - addr = 0x28000000 - yield from set_vaddr(addr) - yield - - addr = 0x28000001 - yield from set_vaddr(addr) - - addr = 0x28000001 - yield from set_vaddr(addr) - yield - - addr = 0x1000040000 - yield from set_vaddr(addr) - yield - - addr = 0x1000040001 - yield from set_vaddr(addr) - yield - - yield dut.update_i.is_1G.eq(1) - addr = 0x2040000 - yield from set_vaddr(addr) - yield - - yield dut.update_i.is_1G.eq(1) - addr = 0x2040001 - yield from set_vaddr(addr) - yield - - yield - - -if __name__ == "__main__": - dut = TLB() - run_simulation(dut, testbench(dut), vcd_name="test_tlb.vcd") diff --git a/TLB/src/ariane/tlb.py b/TLB/src/ariane/tlb.py deleted file mode 100644 index f768571e..00000000 --- a/TLB/src/ariane/tlb.py +++ /dev/null @@ -1,170 +0,0 @@ -""" -# Copyright 2018 ETH Zurich and University of Bologna. -# Copyright and related rights are licensed under the Solderpad Hardware -# License, Version 0.51 (the "License"); you may not use this file except in -# compliance with the License. You may obtain a copy of the License at -# http:#solderpad.org/licenses/SHL-0.51. Unless required by applicable law -# or agreed to in writing, software, hardware and materials distributed under -# this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -# -# Author: David Schaffenrath, TU Graz -# Author: Florian Zaruba, ETH Zurich -# Date: 21.4.2017 -# Description: Translation Lookaside Buffer, SV39 -# fully set-associative - -Implementation in c++: -https://raw.githubusercontent.com/Tony-Hu/TreePLRU/master/TreePLRU.cpp - -Text description: -https://people.cs.clemson.edu/~mark/464/p_lru.txt - -Online simulator: -http://www.ntu.edu.sg/home/smitha/ParaCache/Paracache/vm.html -""" -from math import log2 -from nmigen import Signal, Module, Cat, Const, Array -from nmigen.cli import verilog, rtlil -from nmigen.lib.coding import Encoder - -from ptw import TLBUpdate, PTE, ASID_WIDTH -from plru import PLRU -from tlb_content import TLBContent - -TLB_ENTRIES = 8 - -class TLB: - def __init__(self, tlb_entries=8, asid_width=8): - self.tlb_entries = tlb_entries - self.asid_width = asid_width - - self.flush_i = Signal() # Flush signal - # Lookup signals - self.lu_access_i = Signal() - self.lu_asid_i = Signal(self.asid_width) - self.lu_vaddr_i = Signal(64) - self.lu_content_o = PTE() - self.lu_is_2M_o = Signal() - self.lu_is_1G_o = Signal() - self.lu_hit_o = Signal() - # Update TLB - self.pte_width = len(self.lu_content_o.flatten()) - self.update_i = TLBUpdate(asid_width) - - def elaborate(self, platform): - m = Module() - - vpn2 = Signal(9) - vpn1 = Signal(9) - vpn0 = Signal(9) - - #------------- - # Translation - #------------- - - # SV39 defines three levels of page tables - m.d.comb += [ vpn0.eq(self.lu_vaddr_i[12:21]), - vpn1.eq(self.lu_vaddr_i[21:30]), - vpn2.eq(self.lu_vaddr_i[30:39]), - ] - - tc = [] - for i in range(self.tlb_entries): - tlc = TLBContent(self.pte_width, self.asid_width) - setattr(m.submodules, "tc%d" % i, tlc) - tc.append(tlc) - # connect inputs - tlc.update_i = self.update_i # saves a lot of graphviz links - m.d.comb += [tlc.vpn0.eq(vpn0), - tlc.vpn1.eq(vpn1), - tlc.vpn2.eq(vpn2), - tlc.flush_i.eq(self.flush_i), - #tlc.update_i.eq(self.update_i), - tlc.lu_asid_i.eq(self.lu_asid_i)] - tc = Array(tc) - - #-------------- - # Select hit - #-------------- - - # use Encoder to select hit index - # XXX TODO: assert that there's only one valid entry (one lu_hit) - hitsel = Encoder(self.tlb_entries) - m.submodules.hitsel = hitsel - - hits = [] - for i in range(self.tlb_entries): - hits.append(tc[i].lu_hit_o) - m.d.comb += hitsel.i.eq(Cat(*hits)) # (goes into plru as well) - idx = hitsel.o - - active = Signal(reset_less=True) - m.d.comb += active.eq(~hitsel.n) - with m.If(active): - # active hit, send selected as output - m.d.comb += [ self.lu_is_1G_o.eq(tc[idx].lu_is_1G_o), - self.lu_is_2M_o.eq(tc[idx].lu_is_2M_o), - self.lu_hit_o.eq(1), - self.lu_content_o.flatten().eq(tc[idx].lu_content_o), - ] - - #-------------- - # PLRU. - #-------------- - - p = PLRU(self.tlb_entries) - plru_tree = Signal(p.TLBSZ) - m.submodules.plru = p - - # connect PLRU inputs/outputs - # XXX TODO: assert that there's only one valid entry (one replace_en) - en = [] - for i in range(self.tlb_entries): - en.append(tc[i].replace_en_i) - m.d.comb += [Cat(*en).eq(p.replace_en_o), # output from PLRU into tags - p.lu_hit.eq(hitsel.i), - p.lu_access_i.eq(self.lu_access_i), - p.plru_tree.eq(plru_tree)] - m.d.sync += plru_tree.eq(p.plru_tree_o) - - #-------------- - # Sanity checks - #-------------- - - assert (self.tlb_entries % 2 == 0) and (self.tlb_entries > 1), \ - "TLB size must be a multiple of 2 and greater than 1" - assert (self.asid_width >= 1), \ - "ASID width must be at least 1" - - return m - - """ - # Just for checking - function int countSetBits(logic[self.tlb_entries-1:0] vector); - automatic int count = 0; - foreach (vector[idx]) begin - count += vector[idx]; - end - return count; - endfunction - - assert property (@(posedge clk_i)(countSetBits(lu_hit) <= 1)) - else $error("More then one hit in TLB!"); $stop(); end - assert property (@(posedge clk_i)(countSetBits(replace_en) <= 1)) - else $error("More then one TLB entry selected for next replace!"); - """ - - def ports(self): - return [self.flush_i, self.lu_access_i, - self.lu_asid_i, self.lu_vaddr_i, - self.lu_is_2M_o, self.lu_is_1G_o, self.lu_hit_o, - ] + self.lu_content_o.ports() + self.update_i.ports() - -if __name__ == '__main__': - tlb = TLB() - vl = rtlil.convert(tlb, ports=tlb.ports()) - with open("test_tlb.il", "w") as f: - f.write(vl) - diff --git a/TLB/src/ariane/tlb_content.py b/TLB/src/ariane/tlb_content.py deleted file mode 100644 index 024c5697..00000000 --- a/TLB/src/ariane/tlb_content.py +++ /dev/null @@ -1,125 +0,0 @@ -from nmigen import Signal, Module, Cat, Const - -from ptw import TLBUpdate, PTE - -class TLBEntry: - def __init__(self, asid_width): - self.asid = Signal(asid_width) - # SV39 defines three levels of page tables - self.vpn0 = Signal(9) - self.vpn1 = Signal(9) - self.vpn2 = Signal(9) - self.is_2M = Signal() - self.is_1G = Signal() - self.valid = Signal() - - def flatten(self): - return Cat(*self.ports()) - - def eq(self, x): - return self.flatten().eq(x.flatten()) - - def ports(self): - return [self.asid, self.vpn0, self.vpn1, self.vpn2, - self.is_2M, self.is_1G, self.valid] - -class TLBContent: - def __init__(self, pte_width, asid_width): - self.asid_width = asid_width - self.pte_width = pte_width - self.flush_i = Signal() # Flush signal - # Update TLB - self.update_i = TLBUpdate(asid_width) - self.vpn2 = Signal(9) - self.vpn1 = Signal(9) - self.vpn0 = Signal(9) - self.replace_en_i = Signal() # replace the following entry, - # set by replacement strategy - # Lookup signals - self.lu_asid_i = Signal(asid_width) - self.lu_content_o = Signal(pte_width) - self.lu_is_2M_o = Signal() - self.lu_is_1G_o = Signal() - self.lu_hit_o = Signal() - - def elaborate(self, platform): - m = Module() - - tags = TLBEntry(self.asid_width) - content = Signal(self.pte_width) - - m.d.comb += [self.lu_hit_o.eq(0), - self.lu_is_2M_o.eq(0), - self.lu_is_1G_o.eq(0)] - - # temporaries for 1st level match - asid_ok = Signal(reset_less=True) - vpn2_ok = Signal(reset_less=True) - tags_ok = Signal(reset_less=True) - vpn2_hit = Signal(reset_less=True) - m.d.comb += [tags_ok.eq(tags.valid), - asid_ok.eq(tags.asid == self.lu_asid_i), - vpn2_ok.eq(tags.vpn2 == self.vpn2), - vpn2_hit.eq(tags_ok & asid_ok & vpn2_ok)] - # temporaries for 2nd level match - vpn1_ok = Signal(reset_less=True) - tags_2M = Signal(reset_less=True) - vpn0_ok = Signal(reset_less=True) - vpn0_or_2M = Signal(reset_less=True) - m.d.comb += [vpn1_ok.eq(self.vpn1 == tags.vpn1), - tags_2M.eq(tags.is_2M), - vpn0_ok.eq(self.vpn0 == tags.vpn0), - vpn0_or_2M.eq(tags_2M | vpn0_ok)] - # first level match, this may be a giga page, - # check the ASID flags as well - with m.If(vpn2_hit): - # second level - with m.If (tags.is_1G): - m.d.comb += [ self.lu_content_o.eq(content), - self.lu_is_1G_o.eq(1), - self.lu_hit_o.eq(1), - ] - # not a giga page hit so check further - with m.Elif(vpn1_ok): - # this could be a 2 mega page hit or a 4 kB hit - # output accordingly - with m.If(vpn0_or_2M): - m.d.comb += [ self.lu_content_o.eq(content), - self.lu_is_2M_o.eq(tags.is_2M), - self.lu_hit_o.eq(1), - ] - # ------------------ - # Update or Flush - # ------------------ - - # temporaries - replace_valid = Signal(reset_less=True) - m.d.comb += replace_valid.eq(self.update_i.valid & self.replace_en_i) - - # flush - with m.If (self.flush_i): - # invalidate (flush) conditions: all if zero or just this ASID - with m.If (self.lu_asid_i == Const(0, self.asid_width) | - (self.lu_asid_i == tags.asid)): - m.d.sync += tags.valid.eq(0) - - # normal replacement - with m.Elif(replace_valid): - m.d.sync += [ # update tag array - tags.asid.eq(self.update_i.asid), - tags.vpn2.eq(self.update_i.vpn[18:27]), - tags.vpn1.eq(self.update_i.vpn[9:18]), - tags.vpn0.eq(self.update_i.vpn[0:9]), - tags.is_1G.eq(self.update_i.is_1G), - tags.is_2M.eq(self.update_i.is_2M), - tags.valid.eq(1), - # and content as well - content.eq(self.update_i.content.flatten()) - ] - return m - - def ports(self): - return [self.flush_i, - self.lu_asid_i, - self.lu_is_2M_o, self.lu_is_1G_o, self.lu_hit_o, - ] + self.update_i.content.ports() + self.update_i.ports()