begin experimental ariane mmu.sv conversion
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Sun, 21 Apr 2019 06:04:54 +0000 (07:04 +0100)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Sun, 21 Apr 2019 06:04:54 +0000 (07:04 +0100)
TLB/src/ariane/mmu.py [new file with mode: 0644]

diff --git a/TLB/src/ariane/mmu.py b/TLB/src/ariane/mmu.py
new file mode 100644 (file)
index 0000000..d85abc9
--- /dev/null
@@ -0,0 +1,408 @@
+"""
+# 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
+from ptw import DCacheReqI, DCacheReqO
+
+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 __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.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(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()
+);
+
+    logic        iaccess_err;   # insufficient priv to access instr page
+    logic        daccess_err;   # insufficient priv to access data page
+    logic        ptw_active;    # PTW is currently walking a page table
+    logic        walking_instr; # PTW is walking because of an ITLB miss
+    logic        ptw_error;     # PTW threw an exception
+
+    logic [38:0] update_vaddr;
+    tlb_update_t update_ptw_itlb, update_ptw_dtlb;
+
+    logic        itlb_lu_access;
+    riscv::pte_t itlb_content;
+    logic        itlb_is_2M;
+    logic        itlb_is_1G;
+    logic        itlb_lu_hit;
+
+    logic        dtlb_lu_access;
+    riscv::pte_t dtlb_content;
+    logic        dtlb_is_2M;
+    logic        dtlb_is_1G;
+    logic        dtlb_lu_hit;
+
+
+    # Assignments
+    assign itlb_lu_access = icache_areq_i.fetch_req;
+    assign dtlb_lu_access = lsu_req_i;
+
+
+    tlb #(
+        .TLB_ENTRIES      ( INSTR_TLB_ENTRIES          ),
+        .ASID_WIDTH       ( ASID_WIDTH                 )
+    ) i_itlb (
+        .clk_i            ( clk_i                      ),
+        .rst_ni           ( rst_ni                     ),
+        .flush_i          ( flush_tlb_i                ),
+
+        .update_i         ( update_ptw_itlb            ),
+
+        .lu_access_i      ( itlb_lu_access             ),
+        .lu_asid_i        ( asid_i                     ),
+        .lu_vaddr_i       ( icache_areq_i.fetch_vaddr              ),
+        .lu_content_o     ( itlb_content               ),
+
+        .lu_is_2M_o       ( itlb_is_2M                 ),
+        .lu_is_1G_o       ( itlb_is_1G                 ),
+        .lu_hit_o         ( itlb_lu_hit                )
+    );
+
+    tlb #(
+        .TLB_ENTRIES     ( DATA_TLB_ENTRIES             ),
+        .ASID_WIDTH      ( ASID_WIDTH                   )
+    ) i_dtlb (
+        .clk_i            ( clk_i                       ),
+        .rst_ni           ( rst_ni                      ),
+        .flush_i          ( flush_tlb_i                 ),
+
+        .update_i         ( update_ptw_dtlb             ),
+
+        .lu_access_i      ( dtlb_lu_access              ),
+        .lu_asid_i        ( asid_i                      ),
+        .lu_vaddr_i       ( lsu_vaddr_i                 ),
+        .lu_content_o     ( dtlb_content                ),
+
+        .lu_is_2M_o       ( dtlb_is_2M                  ),
+        .lu_is_1G_o       ( dtlb_is_1G                  ),
+        .lu_hit_o         ( dtlb_lu_hit                 )
+    );
+
+
+    ptw  #(
+        .ASID_WIDTH             ( ASID_WIDTH            )
+    ) i_ptw (
+        .clk_i                  ( clk_i                 ),
+        .rst_ni                 ( rst_ni                ),
+        .ptw_active_o           ( ptw_active            ),
+        .walking_instr_o        ( walking_instr         ),
+        .ptw_error_o            ( ptw_error             ),
+        .enable_translation_i   ( enable_translation_i  ),
+
+        .update_vaddr_o         ( update_vaddr          ),
+        .itlb_update_o          ( update_ptw_itlb       ),
+        .dtlb_update_o          ( update_ptw_dtlb       ),
+
+        .itlb_access_i          ( itlb_lu_access        ),
+        .itlb_hit_i             ( itlb_lu_hit           ),
+        .itlb_vaddr_i           ( icache_areq_i.fetch_vaddr         ),
+
+        .dtlb_access_i          ( dtlb_lu_access        ),
+        .dtlb_hit_i             ( dtlb_lu_hit           ),
+        .dtlb_vaddr_i           ( lsu_vaddr_i           ),
+
+        .req_port_i            ( req_port_i             ),
+        .req_port_o            ( 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
+    always_comb begin : instr_interface
+        # MMU disabled: just pass through
+        icache_areq_o.fetch_valid  = icache_areq_i.fetch_req;
+        icache_areq_o.fetch_paddr  = icache_areq_i.fetch_vaddr; # play through in case we disabled address translation
+        # 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
+        icache_areq_o.fetch_exception      = '0;
+        # Check whether we are allowed to access this memory region from a fetch perspective
+        iaccess_err   = icache_areq_i.fetch_req && (((priv_lvl_i == riscv::PRIV_LVL_U) && ~itlb_content.u)
+                                                 || ((priv_lvl_i == riscv::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.
+        if (enable_translation_i) begin
+            # we work with SV39, so if VM is enabled, check that all bits [63:38] are equal
+            if (icache_areq_i.fetch_req && !((&icache_areq_i.fetch_vaddr[63:38]) == 1'b1 || (|icache_areq_i.fetch_vaddr[63:38]) == 1'b0)) begin
+                icache_areq_o.fetch_exception = {riscv::INSTR_ACCESS_FAULT, icache_areq_i.fetch_vaddr, 1'b1};
+            end
+
+            icache_areq_o.fetch_valid = 1'b0;
+
+            # 4K page
+            icache_areq_o.fetch_paddr = {itlb_content.ppn, icache_areq_i.fetch_vaddr[11:0]};
+            # Mega page
+            if (itlb_is_2M) begin
+                icache_areq_o.fetch_paddr[20:12] = icache_areq_i.fetch_vaddr[20:12];
+            end
+            # Giga page
+            if (itlb_is_1G) begin
+                icache_areq_o.fetch_paddr[29:12] = icache_areq_i.fetch_vaddr[29:12];
+            end
+
+            # ---------
+            # ITLB Hit
+            # --------
+            # if we hit the ITLB output the request signal immediately
+            if (itlb_lu_hit) begin
+                icache_areq_o.fetch_valid = icache_areq_i.fetch_req;
+                # we got an access error
+                if (iaccess_err) begin
+                    # throw a page fault
+                    icache_areq_o.fetch_exception = {riscv::INSTR_PAGE_FAULT, icache_areq_i.fetch_vaddr, 1'b1};
+                end
+            end else
+            # ---------
+            # ITLB Miss
+            # ---------
+            # watch out for exceptions happening during walking the page table
+            if (ptw_active && walking_instr) begin
+                icache_areq_o.fetch_valid = ptw_error;
+                icache_areq_o.fetch_exception = {riscv::INSTR_PAGE_FAULT, {25'b0, update_vaddr}, 1'b1};
+            end
+        end
+    end
+
+    #-----------------------
+    # Data Interface
+    #-----------------------
+    logic [63:0] lsu_vaddr_n,     lsu_vaddr_q;
+    riscv::pte_t dtlb_pte_n,      dtlb_pte_q;
+    exception_t  misaligned_ex_n, misaligned_ex_q;
+    logic        lsu_req_n,       lsu_req_q;
+    logic        lsu_is_store_n,  lsu_is_store_q;
+    logic        dtlb_hit_n,      dtlb_hit_q;
+    logic        dtlb_is_2M_n,    dtlb_is_2M_q;
+    logic        dtlb_is_1G_n,    dtlb_is_1G_q;
+
+    # check if we need to do translation or if we are always ready (e.g.: we are not translating anything)
+    assign lsu_dtlb_hit_o = (en_ld_st_translation_i) ? dtlb_lu_hit :  1'b1;
+
+    # The data interface is simpler and only consists of a request/response interface
+    always_comb begin : data_interface
+        # save request and DTLB response
+        lsu_vaddr_n           = lsu_vaddr_i;
+        lsu_req_n             = lsu_req_i;
+        misaligned_ex_n       = misaligned_ex_i;
+        dtlb_pte_n            = dtlb_content;
+        dtlb_hit_n            = dtlb_lu_hit;
+        lsu_is_store_n        = lsu_is_store_i;
+        dtlb_is_2M_n          = dtlb_is_2M;
+        dtlb_is_1G_n          = dtlb_is_1G;
+
+        lsu_paddr_o           = lsu_vaddr_q;
+        lsu_valid_o           = lsu_req_q;
+        lsu_exception_o       = misaligned_ex_q;
+        # mute misaligned exceptions if there is no request otherwise they will throw accidental exceptions
+        misaligned_ex_n.valid = misaligned_ex_i.valid & lsu_req_i;
+
+        # Check if the User flag is set, then we may only access it in supervisor mode
+        # if SUM is enabled
+        daccess_err = (ld_st_priv_lvl_i == riscv::PRIV_LVL_S && !sum_i && dtlb_pte_q.u) || # SUM is not set and we are trying to access a user page in supervisor mode
+                      (ld_st_priv_lvl_i == riscv::PRIV_LVL_U && !dtlb_pte_q.u);            # this is not a user page but we are in user mode and trying to access it
+        # translation is enabled and no misaligned exception occurred
+        if (en_ld_st_translation_i && !misaligned_ex_q.valid) begin
+            lsu_valid_o = 1'b0;
+            # 4K page
+            lsu_paddr_o = {dtlb_pte_q.ppn, lsu_vaddr_q[11:0]};
+            # Mega page
+            if (dtlb_is_2M_q) begin
+              lsu_paddr_o[20:12] = lsu_vaddr_q[20:12];
+            end
+            # Giga page
+            if (dtlb_is_1G_q) begin
+                lsu_paddr_o[29:12] = lsu_vaddr_q[29:12];
+            end
+            # ---------
+            # DTLB Hit
+            # --------
+            if (dtlb_hit_q && lsu_req_q) begin
+                lsu_valid_o = 1'b1;
+                # this is a store
+                if (lsu_is_store_q) begin
+                    # check if the page is write-able and we are not violating privileges
+                    # also check if the dirty flag is set
+                    if (!dtlb_pte_q.w || daccess_err || !dtlb_pte_q.d) begin
+                        lsu_exception_o = {riscv::STORE_PAGE_FAULT, lsu_vaddr_q, 1'b1};
+                    end
+
+                # this is a load, check for sufficient access privileges - throw a page fault if necessary
+                end else if (daccess_err) begin
+                    lsu_exception_o = {riscv::LOAD_PAGE_FAULT, lsu_vaddr_q, 1'b1};
+                end
+            end else
+
+            # ---------
+            # DTLB Miss
+            # ---------
+            # watch out for exceptions
+            if (ptw_active && !walking_instr) begin
+                # page table walker threw an exception
+                if (ptw_error) begin
+                    # an error makes the translation valid
+                    lsu_valid_o = 1'b1;
+                    # the page table walker can only throw page faults
+                    if (lsu_is_store_q) begin
+                        lsu_exception_o = {riscv::STORE_PAGE_FAULT, {25'b0, update_vaddr}, 1'b1};
+                    end else begin
+                        lsu_exception_o = {riscv::LOAD_PAGE_FAULT, {25'b0, update_vaddr}, 1'b1};
+                    end
+                end
+            end
+        end
+    end
+    # ----------
+    # Registers
+    # ----------
+    always_ff @(posedge clk_i or negedge rst_ni) begin
+        if (~rst_ni) begin
+            lsu_vaddr_q      <= '0;
+            lsu_req_q        <= '0;
+            misaligned_ex_q  <= '0;
+            dtlb_pte_q       <= '0;
+            dtlb_hit_q       <= '0;
+            lsu_is_store_q   <= '0;
+            dtlb_is_2M_q     <= '0;
+            dtlb_is_1G_q     <= '0;
+        end else begin
+            lsu_vaddr_q      <=  lsu_vaddr_n;
+            lsu_req_q        <=  lsu_req_n;
+            misaligned_ex_q  <=  misaligned_ex_n;
+            dtlb_pte_q       <=  dtlb_pte_n;
+            dtlb_hit_q       <=  dtlb_hit_n;
+            lsu_is_store_q   <=  lsu_is_store_n;
+            dtlb_is_2M_q     <=  dtlb_is_2M_n;
+            dtlb_is_1G_q     <=  dtlb_is_1G_n;
+        end
+    end
+endmodule