From 41c57e96455f92dc8edf111f009a021792ee5946 Mon Sep 17 00:00:00 2001 From: Tobias Platen Date: Sun, 3 Nov 2019 16:45:49 +0100 Subject: [PATCH] add working preprocessor (creates docstrings) --- absyn.py | 7 +- examples/load_store_unit.sv | 481 ++++++++++++++++++++++++++++++++++++ examples/mmu.sv | 365 +++++++++++++++++++++++++++ examples/tlb.py | 272 ++++++++++++++++++++ examples/tlb.sv | 248 +++++++++++++++++++ parse_sv.py | 6 +- pypreproc.py | 39 +++ svparse.py | 5 +- 8 files changed, 1416 insertions(+), 7 deletions(-) create mode 100644 examples/load_store_unit.sv create mode 100644 examples/mmu.sv create mode 100644 examples/tlb.py create mode 100644 examples/tlb.sv create mode 100644 pypreproc.py diff --git a/absyn.py b/absyn.py index 6ccbb05..7d54d6e 100644 --- a/absyn.py +++ b/absyn.py @@ -148,9 +148,10 @@ class Absyn: return clsdecl def appendComments(self,data): - lines = data.split("\n") - for line in lines: - self.printpy("#"+line) + self.outputfile.write(data) + #lines = data.split("\n") + #for line in lines: + # self.printpy("#"+line) # combinatorical assign def cont_assign_1(self,p): diff --git a/examples/load_store_unit.sv b/examples/load_store_unit.sv new file mode 100644 index 0000000..1d15633 --- /dev/null +++ b/examples/load_store_unit.sv @@ -0,0 +1,481 @@ +// 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: Load Store Unit, handles address calculation and memory interface signals + +//import ariane_pkg::*; + +module load_store_unit #( + parameter int ASID_WIDTH = 1 + // parameter ariane_pkg::ariane_cfg_t ArianeCfg = ariane_pkg::ArianeDefaultConfig +)( + input logic clk_i, + input logic rst_ni, + input logic flush_i, + output logic no_st_pending_o, + input logic amo_valid_commit_i, + + + //input fu_data_t fu_data_i, + output logic lsu_ready_o, // FU is ready e.g. not busy + input logic lsu_valid_i, // Input is valid + + output logic [TRANS_ID_BITS-1:0] load_trans_id_o, // ID of scoreboard entry at which to write back + output logic [63:0] load_result_o, + output logic load_valid_o, + //output exception_t load_exception_o, // to WB, signal exception status LD exception + + output logic [TRANS_ID_BITS-1:0] store_trans_id_o, // ID of scoreboard entry at which to write back + output logic [63:0] store_result_o, + output logic store_valid_o, + //output exception_t store_exception_o, // to WB, signal exception status ST exception + + input logic commit_i, // commit the pending store + output logic commit_ready_o, // commit queue is ready to accept another commit request + + input logic enable_translation_i, // enable virtual memory translation + input logic en_ld_st_translation_i, // enable virtual memory translation for load/stores + + // icache translation requests + //input icache_areq_o_t icache_areq_i, + //output icache_areq_i_t icache_areq_o, + + //input riscv::priv_lvl_t priv_lvl_i, // From CSR register file + //input riscv::priv_lvl_t ld_st_priv_lvl_i, // From CSR register file + input logic sum_i, // From CSR register file + input logic mxr_i, // From CSR register file + input logic [43:0] satp_ppn_i, // From CSR register file + input logic [ASID_WIDTH-1:0] asid_i, // From CSR register file + input logic flush_tlb_i, + // Performance counters + output logic itlb_miss_o, + output logic dtlb_miss_o + + // interface to dcache + //input dcache_req_o_t [2:0] dcache_req_ports_i, + //output dcache_req_i_t [2:0] dcache_req_ports_o, + // AMO interface + //output amo_req_t amo_req_o, + //input amo_resp_t amo_resp_i +); + #docstring_begin + // data is misaligned + logic data_misaligned; + // -------------------------------------- + // 1st register stage - (stall registers) + // -------------------------------------- + // those are the signals which are always correct + // e.g.: they keep the value in the stall case + lsu_ctrl_t lsu_ctrl; + + logic pop_st; + logic pop_ld; + + // ------------------------------ + // Address Generation Unit (AGU) + // ------------------------------ + // virtual address as calculated by the AGU in the first cycle + logic [63:0] vaddr_i; + logic [7:0] be_i; + + assign vaddr_i = $unsigned($signed(fu_data_i.imm) + $signed(fu_data_i.operand_a)); + + logic st_valid_i; + logic ld_valid_i; + logic ld_translation_req; + logic st_translation_req; + logic [63:0] ld_vaddr; + logic [63:0] st_vaddr; + logic translation_req; + logic translation_valid; + logic [63:0] mmu_vaddr; + logic [63:0] mmu_paddr; + exception_t mmu_exception; + logic dtlb_hit; + + logic ld_valid; + logic [TRANS_ID_BITS-1:0] ld_trans_id; + logic [63:0] ld_result; + logic st_valid; + logic [TRANS_ID_BITS-1:0] st_trans_id; + logic [63:0] st_result; + + logic [11:0] page_offset; + logic page_offset_matches; + + exception_t misaligned_exception; + exception_t ld_ex; + exception_t st_ex; + + // ------------------- + // MMU e.g.: TLBs/PTW + // ------------------- + mmu #( + .INSTR_TLB_ENTRIES ( 16 ), + .DATA_TLB_ENTRIES ( 16 ), + .ASID_WIDTH ( ASID_WIDTH ), + .ArianeCfg ( ArianeCfg ) + ) i_mmu ( + // misaligned bypass + .misaligned_ex_i ( misaligned_exception ), + .lsu_is_store_i ( st_translation_req ), + .lsu_req_i ( translation_req ), + .lsu_vaddr_i ( mmu_vaddr ), + .lsu_valid_o ( translation_valid ), + .lsu_paddr_o ( mmu_paddr ), + .lsu_exception_o ( mmu_exception ), + .lsu_dtlb_hit_o ( dtlb_hit ), // send in the same cycle as the request + // connecting PTW to D$ IF + .req_port_i ( dcache_req_ports_i [0] ), + .req_port_o ( dcache_req_ports_o [0] ), + // icache address translation requests + .icache_areq_i ( icache_areq_i ), + .icache_areq_o ( icache_areq_o ), + .* + ); + // ------------------ + // Store Unit + // ------------------ + store_unit i_store_unit ( + .clk_i, + .rst_ni, + .flush_i, + .no_st_pending_o, + + .valid_i ( st_valid_i ), + .lsu_ctrl_i ( lsu_ctrl ), + .pop_st_o ( pop_st ), + .commit_i, + .commit_ready_o, + .amo_valid_commit_i, + + .valid_o ( st_valid ), + .trans_id_o ( st_trans_id ), + .result_o ( st_result ), + .ex_o ( st_ex ), + // MMU port + .translation_req_o ( st_translation_req ), + .vaddr_o ( st_vaddr ), + .paddr_i ( mmu_paddr ), + .ex_i ( mmu_exception ), + .dtlb_hit_i ( dtlb_hit ), + // Load Unit + .page_offset_i ( page_offset ), + .page_offset_matches_o ( page_offset_matches ), + // AMOs + .amo_req_o, + .amo_resp_i, + // to memory arbiter + .req_port_i ( dcache_req_ports_i [2] ), + .req_port_o ( dcache_req_ports_o [2] ) + ); + + // ------------------ + // Load Unit + // ------------------ + load_unit i_load_unit ( + .valid_i ( ld_valid_i ), + .lsu_ctrl_i ( lsu_ctrl ), + .pop_ld_o ( pop_ld ), + + .valid_o ( ld_valid ), + .trans_id_o ( ld_trans_id ), + .result_o ( ld_result ), + .ex_o ( ld_ex ), + // MMU port + .translation_req_o ( ld_translation_req ), + .vaddr_o ( ld_vaddr ), + .paddr_i ( mmu_paddr ), + .ex_i ( mmu_exception ), + .dtlb_hit_i ( dtlb_hit ), + // to store unit + .page_offset_o ( page_offset ), + .page_offset_matches_i ( page_offset_matches ), + // to memory arbiter + .req_port_i ( dcache_req_ports_i [1] ), + .req_port_o ( dcache_req_ports_o [1] ), + .* + ); + + // ---------------------------- + // Output Pipeline Register + // ---------------------------- + shift_reg #( + .dtype ( logic[$bits(ld_valid) + $bits(ld_trans_id) + $bits(ld_result) + $bits(ld_ex) - 1: 0]), + .Depth ( NR_LOAD_PIPE_REGS ) + ) i_pipe_reg_load ( + .clk_i, + .rst_ni, + .d_i ( {ld_valid, ld_trans_id, ld_result, ld_ex} ), + .d_o ( {load_valid_o, load_trans_id_o, load_result_o, load_exception_o} ) + ); + + shift_reg #( + .dtype ( logic[$bits(st_valid) + $bits(st_trans_id) + $bits(st_result) + $bits(st_ex) - 1: 0]), + .Depth ( NR_STORE_PIPE_REGS ) + ) i_pipe_reg_store ( + .clk_i, + .rst_ni, + .d_i ( {st_valid, st_trans_id, st_result, st_ex} ), + .d_o ( {store_valid_o, store_trans_id_o, store_result_o, store_exception_o} ) + ); + + // determine whether this is a load or store + always_comb begin : which_op + + ld_valid_i = 1'b0; + st_valid_i = 1'b0; + + translation_req = 1'b0; + mmu_vaddr = 64'b0; + + // check the operator to activate the right functional unit accordingly + unique case (lsu_ctrl.fu) + // all loads go here + LOAD: begin + ld_valid_i = lsu_ctrl.valid; + translation_req = ld_translation_req; + mmu_vaddr = ld_vaddr; + end + // all stores go here + STORE: begin + st_valid_i = lsu_ctrl.valid; + translation_req = st_translation_req; + mmu_vaddr = st_vaddr; + end + // not relevant for the LSU + default: ; + endcase + end + + + // --------------- + // Byte Enable + // --------------- + // we can generate the byte enable from the virtual address since the last + // 12 bit are the same anyway + // and we can always generate the byte enable from the address at hand + assign be_i = be_gen(vaddr_i[2:0], extract_transfer_size(fu_data_i.operator)); + + // ------------------------ + // Misaligned Exception + // ------------------------ + // we can detect a misaligned exception immediately + // the misaligned exception is passed to the functional unit via the MMU, which in case + // can augment the exception if other memory related exceptions like a page fault or access errors + always_comb begin : data_misaligned_detection + + misaligned_exception = { + 64'b0, + 64'b0, + 1'b0 + }; + + data_misaligned = 1'b0; + + if (lsu_ctrl.valid) begin + case (lsu_ctrl.operator) + // double word + LD, SD, FLD, FSD, + AMO_LRD, AMO_SCD, + AMO_SWAPD, AMO_ADDD, AMO_ANDD, AMO_ORD, + AMO_XORD, AMO_MAXD, AMO_MAXDU, AMO_MIND, + AMO_MINDU: begin + if (lsu_ctrl.vaddr[2:0] != 3'b000) begin + data_misaligned = 1'b1; + end + end + // word + LW, LWU, SW, FLW, FSW, + AMO_LRW, AMO_SCW, + AMO_SWAPW, AMO_ADDW, AMO_ANDW, AMO_ORW, + AMO_XORW, AMO_MAXW, AMO_MAXWU, AMO_MINW, + AMO_MINWU: begin + if (lsu_ctrl.vaddr[1:0] != 2'b00) begin + data_misaligned = 1'b1; + end + end + // half word + LH, LHU, SH, FLH, FSH: begin + if (lsu_ctrl.vaddr[0] != 1'b0) begin + data_misaligned = 1'b1; + end + end + // byte -> is always aligned + default:; + endcase + end + + if (data_misaligned) begin + + if (lsu_ctrl.fu == LOAD) begin + misaligned_exception = { + riscv::LD_ADDR_MISALIGNED, + lsu_ctrl.vaddr, + 1'b1 + }; + + end else if (lsu_ctrl.fu == STORE) begin + misaligned_exception = { + riscv::ST_ADDR_MISALIGNED, + lsu_ctrl.vaddr, + 1'b1 + }; + end + end + + // we work with SV39, so if VM is enabled, check that all bits [63:38] are equal + if (en_ld_st_translation_i && !((&lsu_ctrl.vaddr[63:38]) == 1'b1 || (|lsu_ctrl.vaddr[63:38]) == 1'b0)) begin + + if (lsu_ctrl.fu == LOAD) begin + misaligned_exception = { + riscv::LD_ACCESS_FAULT, + lsu_ctrl.vaddr, + 1'b1 + }; + + end else if (lsu_ctrl.fu == STORE) begin + misaligned_exception = { + riscv::ST_ACCESS_FAULT, + lsu_ctrl.vaddr, + 1'b1 + }; + end + end + end + + // ------------------ + // LSU Control + // ------------------ + // new data arrives here + lsu_ctrl_t lsu_req_i; + + assign lsu_req_i = {lsu_valid_i, vaddr_i, fu_data_i.operand_b, be_i, fu_data_i.fu, fu_data_i.operator, fu_data_i.trans_id}; + + lsu_bypass lsu_bypass_i ( + .lsu_req_i ( lsu_req_i ), + .lus_req_valid_i ( lsu_valid_i ), + .pop_ld_i ( pop_ld ), + .pop_st_i ( pop_st ), + + .lsu_ctrl_o ( lsu_ctrl ), + .ready_o ( lsu_ready_o ), + .* + ); +#docstring_end +endmodule + +#docstring_begin +// ------------------ +// LSU Control +// ------------------ +// The LSU consists of two independent block which share a common address translation block. +// The one block is the load unit, the other one is the store unit. They will signal their readiness +// with separate signals. If they are not ready the LSU control should keep the last applied signals stable. +// Furthermore it can be the case that another request for one of the two store units arrives in which case +// the LSU control should sample it and store it for later application to the units. It does so, by storing it in a +// two element FIFO. This is necessary as we only know very late in the cycle whether the load/store will succeed (address check, +// TLB hit mainly). So we better unconditionally allow another request to arrive and store this request in case we need to. +module lsu_bypass ( + input logic clk_i, + input logic rst_ni, + input logic flush_i, + + input lsu_ctrl_t lsu_req_i, + input logic lus_req_valid_i, + input logic pop_ld_i, + input logic pop_st_i, + + output lsu_ctrl_t lsu_ctrl_o, + output logic ready_o + ); + + lsu_ctrl_t [1:0] mem_n, mem_q; + logic read_pointer_n, read_pointer_q; + logic write_pointer_n, write_pointer_q; + logic [1:0] status_cnt_n, status_cnt_q; + + logic empty; + assign empty = (status_cnt_q == 0); + assign ready_o = empty; + + always_comb begin + automatic logic [1:0] status_cnt; + automatic logic write_pointer; + automatic logic read_pointer; + + status_cnt = status_cnt_q; + write_pointer = write_pointer_q; + read_pointer = read_pointer_q; + + mem_n = mem_q; + // we've got a valid LSU request + if (lus_req_valid_i) begin + mem_n[write_pointer_q] = lsu_req_i; + write_pointer++; + status_cnt++; + end + + if (pop_ld_i) begin + // invalidate the result + mem_n[read_pointer_q].valid = 1'b0; + read_pointer++; + status_cnt--; + end + + if (pop_st_i) begin + // invalidate the result + mem_n[read_pointer_q].valid = 1'b0; + read_pointer++; + status_cnt--; + end + + if (pop_st_i && pop_ld_i) + mem_n = '0; + + if (flush_i) begin + status_cnt = '0; + write_pointer = '0; + read_pointer = '0; + mem_n = '0; + end + // default assignments + read_pointer_n = read_pointer; + write_pointer_n = write_pointer; + status_cnt_n = status_cnt; + end + + // output assignment + always_comb begin : output_assignments + if (empty) begin + lsu_ctrl_o = lsu_req_i; + end else begin + lsu_ctrl_o = mem_q[read_pointer_q]; + end + end + + // registers + always_ff @(posedge clk_i or negedge rst_ni) begin + if (~rst_ni) begin + mem_q <= '0; + status_cnt_q <= '0; + write_pointer_q <= '0; + read_pointer_q <= '0; + end else begin + mem_q <= mem_n; + status_cnt_q <= status_cnt_n; + write_pointer_q <= write_pointer_n; + read_pointer_q <= read_pointer_n; + end + end +endmodule +#docstring_end diff --git a/examples/mmu.sv b/examples/mmu.sv new file mode 100644 index 0000000..c6471d4 --- /dev/null +++ b/examples/mmu.sv @@ -0,0 +1,365 @@ +// 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::*; + +module mmu #( + parameter int INSTR_TLB_ENTRIES = 4, + parameter int DATA_TLB_ENTRIES = 4, + parameter int ASID_WIDTH = 1 + // parameter ariane_pkg::ariane_cfg_t ArianeCfg = ariane_pkg::ArianeDefaultConfig +) ( + input logic clk_i, + input logic rst_ni, + input logic flush_i, + input logic enable_translation_i, + input logic en_ld_st_translation_i, // enable virtual memory translation for load/stores + // IF interface + #docstring_begin + input icache_areq_o_t icache_areq_i, + output icache_areq_i_t icache_areq_o, + #docstring_end + // 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 + //input exception_t misaligned_ex_i, + input logic lsu_req_i, // request address translation + input logic [63:0] lsu_vaddr_i, // virtual address in + input logic lsu_is_store_i, // 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 + output logic lsu_dtlb_hit_o, // sent in the same cycle as the request if translation hits in the DTLB + // Cycle 1 + output logic lsu_valid_o, // translation is valid + output logic [63:0] lsu_paddr_o, // translated address + //output exception_t lsu_exception_o, // address translation threw an exception + // General control signals + //input riscv::priv_lvl_t priv_lvl_i, + //input riscv::priv_lvl_t ld_st_priv_lvl_i, + input logic sum_i, + input logic mxr_i, + // input logic flag_mprv_i, + input logic [43:0] satp_ppn_i, + input logic [ASID_WIDTH-1:0] asid_i, + input logic flush_tlb_i, + // Performance counters + output logic itlb_miss_o, + output logic dtlb_miss_o + // PTW memory interface + //input dcache_req_o_t req_port_i, + //output dcache_req_i_t req_port_o +); + + #docstring_begin + logic iaccess_err; // insufficient privilege to access this instruction page + logic daccess_err; // insufficient privilege to access this 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 + //----------------------- + logic match_any_execute_region; + // 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 + // if it didn't match any execute region throw an `Instruction Access Fault` + if (!match_any_execute_region) begin + icache_areq_o.fetch_exception = {riscv::INSTR_ACCESS_FAULT, icache_areq_o.fetch_paddr, 1'b1}; + end + end + + // check for execute flag on memory + assign match_any_execute_region = ariane_pkg::is_inside_execute_regions(ArianeCfg, icache_areq_o.fetch_paddr); + + //----------------------- + // 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 +#docstring_end +endmodule diff --git a/examples/tlb.py b/examples/tlb.py new file mode 100644 index 0000000..5e08a9f --- /dev/null +++ b/examples/tlb.py @@ -0,0 +1,272 @@ +# this file has been generated by sv2nmigen + +from nmigen import Signal, Module, Const, Cat, Elaboratable + + + +class tlb(Elaboratable): + + def __init__(self): + self.clk_i = Signal() # input + self.rst_ni = Signal() # input + self.flush_i = Signal() # input + self.lu_access_i = Signal() # input + self.lu_asid_i = Signal(ASID_WIDTH) # input + self.lu_vaddr_i = Signal(64) # input + self.lu_is_2M_o = Signal() # output + self.lu_is_1G_o = Signal() # output + self.lu_hit_o = Signal() # output + def elaborate(self, platform=None): + m = Module() + return m + +#// 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 +# +#//import ariane_pkg::*; +# +#module tlb #( +# parameter int TLB_ENTRIES = 4, +# parameter int ASID_WIDTH = 1 +# )( +# input logic clk_i, // Clock +# input logic rst_ni, // Asynchronous reset active low +# input logic flush_i, // Flush signal +# // Update TLB +# //input tlb_update_t update_i, +# // Lookup signals +# input logic lu_access_i, +# input logic [ASID_WIDTH-1:0] lu_asid_i, +# input logic [63:0] lu_vaddr_i, +# //output riscv::pte_t lu_content_o, +# output logic lu_is_2M_o, +# output logic lu_is_1G_o, +# output logic lu_hit_o +#); +# +""" #docstring_begin + // SV39 defines three levels of page tables + struct packed { + logic [ASID_WIDTH-1:0] asid; + logic [8:0] vpn2; + logic [8:0] vpn1; + logic [8:0] vpn0; + logic is_2M; + logic is_1G; + logic valid; + } [TLB_ENTRIES-1:0] tags_q, tags_n; + + riscv::pte_t [TLB_ENTRIES-1:0] content_q, content_n; + logic [8:0] vpn0, vpn1, vpn2; + logic [TLB_ENTRIES-1:0] lu_hit; // to replacement logic + logic [TLB_ENTRIES-1:0] replace_en; // replace the following entry, set by replacement strategy + //------------- + // Translation + //------------- + always_comb begin : translation + vpn0 = lu_vaddr_i[20:12]; + vpn1 = lu_vaddr_i[29:21]; + vpn2 = lu_vaddr_i[38:30]; + + // default assignment + lu_hit = '{default: 0}; + lu_hit_o = 1'b0; + lu_content_o = '{default: 0}; + lu_is_1G_o = 1'b0; + lu_is_2M_o = 1'b0; + + for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin + // first level match, this may be a giga page, check the ASID flags as well + if (tags_q[i].valid && lu_asid_i == tags_q[i].asid && vpn2 == tags_q[i].vpn2) begin + // second level + if (tags_q[i].is_1G) begin + lu_is_1G_o = 1'b1; + lu_content_o = content_q[i]; + lu_hit_o = 1'b1; + lu_hit[i] = 1'b1; + // not a giga page hit so check further + end else if (vpn1 == tags_q[i].vpn1) begin + // this could be a 2 mega page hit or a 4 kB hit + // output accordingly + if (tags_q[i].is_2M || vpn0 == tags_q[i].vpn0) begin + lu_is_2M_o = tags_q[i].is_2M; + lu_content_o = content_q[i]; + lu_hit_o = 1'b1; + lu_hit[i] = 1'b1; + end + end + end + end + end + + // ------------------ + // Update and Flush + // ------------------ + always_comb begin : update_flush + tags_n = tags_q; + content_n = content_q; + + for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin + if (flush_i) begin + // invalidate logic + if (lu_asid_i == 1'b0) // flush everything if ASID is 0 + tags_n[i].valid = 1'b0; + else if (lu_asid_i == tags_q[i].asid) // just flush entries from this ASID + tags_n[i].valid = 1'b0; + + // normal replacement + end else if (update_i.valid & replace_en[i]) begin + // update tag array + tags_n[i] = '{ + asid: update_i.asid, + vpn2: update_i.vpn [26:18], + vpn1: update_i.vpn [17:9], + vpn0: update_i.vpn [8:0], + is_1G: update_i.is_1G, + is_2M: update_i.is_2M, + valid: 1'b1 + }; + // and content as well + content_n[i] = update_i.content; + end + end + end + + // ----------------------------------------------- + // PLRU - Pseudo Least Recently Used Replacement + // ----------------------------------------------- + logic[2*(TLB_ENTRIES-1)-1:0] plru_tree_q, plru_tree_n; + always_comb begin : plru_replacement + plru_tree_n = plru_tree_q; + // The PLRU-tree indexing: + // lvl0 0 + // / \ + // / \ + // lvl1 1 2 + // / \ / \ + // lvl2 3 4 5 6 + // / \ /\/\ /\ + // ... ... ... ... + // 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_n[0, 2, 6] = {1, 1, 1}; + // lu_hit[6]: plru_tree_n[0, 2, 6] = {1, 1, 0}; + // lu_hit[5]: plru_tree_n[0, 2, 5] = {1, 0, 1}; + // lu_hit[4]: plru_tree_n[0, 2, 5] = {1, 0, 0}; + // lu_hit[3]: plru_tree_n[0, 1, 4] = {0, 1, 1}; + // lu_hit[2]: plru_tree_n[0, 1, 4] = {0, 1, 0}; + // lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1}; + // lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0}; + // default: begin /* No hit */ end + // endcase + for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin + automatic int unsigned idx_base, shift, new_index; + // we got a hit so update the pointer as it was least recently used + if (lu_hit[i] & lu_access_i) begin + // Set the nodes to the values we would expect + for (int unsigned lvl = 0; lvl < $clog2(TLB_ENTRIES); lvl++) begin + idx_base = $unsigned((2**lvl)-1); + // lvl0 <=> MSB, lvl1 <=> MSB-1, ... + shift = $clog2(TLB_ENTRIES) - lvl; + // to circumvent the 32 bit integer arithmetic assignment + new_index = ~((i >> (shift-1)) & 32'b1); + plru_tree_n[idx_base + (i >> shift)] = new_index[0]; + end + end + end + // 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_q[ 6, 2, 0]; //plru_tree_q[0,2,6]=={1,1,1} + // replace_en[6] = &plru_tree_q[~6, 2, 0]; //plru_tree_q[0,2,6]=={1,1,0} + // replace_en[5] = &plru_tree_q[ 5,~2, 0]; //plru_tree_q[0,2,5]=={1,0,1} + // replace_en[4] = &plru_tree_q[~5,~2, 0]; //plru_tree_q[0,2,5]=={1,0,0} + // replace_en[3] = &plru_tree_q[ 4, 1,~0]; //plru_tree_q[0,1,4]=={0,1,1} + // replace_en[2] = &plru_tree_q[~4, 1,~0]; //plru_tree_q[0,1,4]=={0,1,0} + // replace_en[1] = &plru_tree_q[ 3,~1,~0]; //plru_tree_q[0,1,3]=={0,0,1} + // replace_en[0] = &plru_tree_q[~3,~1,~0]; //plru_tree_q[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. + for (int unsigned i = 0; i < TLB_ENTRIES; i += 1) begin + automatic logic en; + automatic int unsigned idx_base, shift, new_index; + en = 1'b1; + for (int unsigned lvl = 0; lvl < $clog2(TLB_ENTRIES); lvl++) begin + idx_base = $unsigned((2**lvl)-1); + // lvl0 <=> MSB, lvl1 <=> MSB-1, ... + shift = $clog2(TLB_ENTRIES) - lvl; + + // en &= plru_tree_q[idx_base + (i>>shift)] == ((i >> (shift-1)) & 1'b1); + new_index = (i >> (shift-1)) & 32'b1; + if (new_index[0]) begin + en &= plru_tree_q[idx_base + (i>>shift)]; + end else begin + en &= ~plru_tree_q[idx_base + (i>>shift)]; + end + end + replace_en[i] = en; + end + end + + // sequential process + always_ff @(posedge clk_i or negedge rst_ni) begin + if(~rst_ni) begin + tags_q <= '{default: 0}; + content_q <= '{default: 0}; + plru_tree_q <= '{default: 0}; + end else begin + tags_q <= tags_n; + content_q <= content_n; + plru_tree_q <= plru_tree_n; + end + end + //-------------- + // Sanity checks + //-------------- + + //pragma translate_off + `ifndef VERILATOR + + initial begin : p_assertions + assert ((TLB_ENTRIES % 2 == 0) && (TLB_ENTRIES > 1)) + else begin $error("TLB size must be a multiple of 2 and greater than 1"); $stop(); end + assert (ASID_WIDTH >= 1) + else begin $error("ASID width must be at least 1"); $stop(); end + end + + // Just for checking + function int countSetBits(logic[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 begin $error("More then one hit in TLB!"); $stop(); end + assert property (@(posedge clk_i)(countSetBits(replace_en) <= 1)) + else begin $error("More then one TLB entry selected for next replace!"); $stop(); end + + `endif + //pragma translate_on +""" +#endmodule +# +# diff --git a/examples/tlb.sv b/examples/tlb.sv new file mode 100644 index 0000000..d9b8c69 --- /dev/null +++ b/examples/tlb.sv @@ -0,0 +1,248 @@ +// 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 + +//import ariane_pkg::*; + +module tlb #( + parameter int TLB_ENTRIES = 4, + parameter int ASID_WIDTH = 1 + )( + input logic clk_i, // Clock + input logic rst_ni, // Asynchronous reset active low + input logic flush_i, // Flush signal + // Update TLB + //input tlb_update_t update_i, + // Lookup signals + input logic lu_access_i, + input logic [ASID_WIDTH-1:0] lu_asid_i, + input logic [63:0] lu_vaddr_i, + //output riscv::pte_t lu_content_o, + output logic lu_is_2M_o, + output logic lu_is_1G_o, + output logic lu_hit_o +); + + #docstring_begin + // SV39 defines three levels of page tables + struct packed { + logic [ASID_WIDTH-1:0] asid; + logic [8:0] vpn2; + logic [8:0] vpn1; + logic [8:0] vpn0; + logic is_2M; + logic is_1G; + logic valid; + } [TLB_ENTRIES-1:0] tags_q, tags_n; + + riscv::pte_t [TLB_ENTRIES-1:0] content_q, content_n; + logic [8:0] vpn0, vpn1, vpn2; + logic [TLB_ENTRIES-1:0] lu_hit; // to replacement logic + logic [TLB_ENTRIES-1:0] replace_en; // replace the following entry, set by replacement strategy + //------------- + // Translation + //------------- + always_comb begin : translation + vpn0 = lu_vaddr_i[20:12]; + vpn1 = lu_vaddr_i[29:21]; + vpn2 = lu_vaddr_i[38:30]; + + // default assignment + lu_hit = '{default: 0}; + lu_hit_o = 1'b0; + lu_content_o = '{default: 0}; + lu_is_1G_o = 1'b0; + lu_is_2M_o = 1'b0; + + for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin + // first level match, this may be a giga page, check the ASID flags as well + if (tags_q[i].valid && lu_asid_i == tags_q[i].asid && vpn2 == tags_q[i].vpn2) begin + // second level + if (tags_q[i].is_1G) begin + lu_is_1G_o = 1'b1; + lu_content_o = content_q[i]; + lu_hit_o = 1'b1; + lu_hit[i] = 1'b1; + // not a giga page hit so check further + end else if (vpn1 == tags_q[i].vpn1) begin + // this could be a 2 mega page hit or a 4 kB hit + // output accordingly + if (tags_q[i].is_2M || vpn0 == tags_q[i].vpn0) begin + lu_is_2M_o = tags_q[i].is_2M; + lu_content_o = content_q[i]; + lu_hit_o = 1'b1; + lu_hit[i] = 1'b1; + end + end + end + end + end + + // ------------------ + // Update and Flush + // ------------------ + always_comb begin : update_flush + tags_n = tags_q; + content_n = content_q; + + for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin + if (flush_i) begin + // invalidate logic + if (lu_asid_i == 1'b0) // flush everything if ASID is 0 + tags_n[i].valid = 1'b0; + else if (lu_asid_i == tags_q[i].asid) // just flush entries from this ASID + tags_n[i].valid = 1'b0; + + // normal replacement + end else if (update_i.valid & replace_en[i]) begin + // update tag array + tags_n[i] = '{ + asid: update_i.asid, + vpn2: update_i.vpn [26:18], + vpn1: update_i.vpn [17:9], + vpn0: update_i.vpn [8:0], + is_1G: update_i.is_1G, + is_2M: update_i.is_2M, + valid: 1'b1 + }; + // and content as well + content_n[i] = update_i.content; + end + end + end + + // ----------------------------------------------- + // PLRU - Pseudo Least Recently Used Replacement + // ----------------------------------------------- + logic[2*(TLB_ENTRIES-1)-1:0] plru_tree_q, plru_tree_n; + always_comb begin : plru_replacement + plru_tree_n = plru_tree_q; + // The PLRU-tree indexing: + // lvl0 0 + // / \ + // / \ + // lvl1 1 2 + // / \ / \ + // lvl2 3 4 5 6 + // / \ /\/\ /\ + // ... ... ... ... + // 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_n[0, 2, 6] = {1, 1, 1}; + // lu_hit[6]: plru_tree_n[0, 2, 6] = {1, 1, 0}; + // lu_hit[5]: plru_tree_n[0, 2, 5] = {1, 0, 1}; + // lu_hit[4]: plru_tree_n[0, 2, 5] = {1, 0, 0}; + // lu_hit[3]: plru_tree_n[0, 1, 4] = {0, 1, 1}; + // lu_hit[2]: plru_tree_n[0, 1, 4] = {0, 1, 0}; + // lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1}; + // lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0}; + // default: begin /* No hit */ end + // endcase + for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin + automatic int unsigned idx_base, shift, new_index; + // we got a hit so update the pointer as it was least recently used + if (lu_hit[i] & lu_access_i) begin + // Set the nodes to the values we would expect + for (int unsigned lvl = 0; lvl < $clog2(TLB_ENTRIES); lvl++) begin + idx_base = $unsigned((2**lvl)-1); + // lvl0 <=> MSB, lvl1 <=> MSB-1, ... + shift = $clog2(TLB_ENTRIES) - lvl; + // to circumvent the 32 bit integer arithmetic assignment + new_index = ~((i >> (shift-1)) & 32'b1); + plru_tree_n[idx_base + (i >> shift)] = new_index[0]; + end + end + end + // 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_q[ 6, 2, 0]; //plru_tree_q[0,2,6]=={1,1,1} + // replace_en[6] = &plru_tree_q[~6, 2, 0]; //plru_tree_q[0,2,6]=={1,1,0} + // replace_en[5] = &plru_tree_q[ 5,~2, 0]; //plru_tree_q[0,2,5]=={1,0,1} + // replace_en[4] = &plru_tree_q[~5,~2, 0]; //plru_tree_q[0,2,5]=={1,0,0} + // replace_en[3] = &plru_tree_q[ 4, 1,~0]; //plru_tree_q[0,1,4]=={0,1,1} + // replace_en[2] = &plru_tree_q[~4, 1,~0]; //plru_tree_q[0,1,4]=={0,1,0} + // replace_en[1] = &plru_tree_q[ 3,~1,~0]; //plru_tree_q[0,1,3]=={0,0,1} + // replace_en[0] = &plru_tree_q[~3,~1,~0]; //plru_tree_q[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. + for (int unsigned i = 0; i < TLB_ENTRIES; i += 1) begin + automatic logic en; + automatic int unsigned idx_base, shift, new_index; + en = 1'b1; + for (int unsigned lvl = 0; lvl < $clog2(TLB_ENTRIES); lvl++) begin + idx_base = $unsigned((2**lvl)-1); + // lvl0 <=> MSB, lvl1 <=> MSB-1, ... + shift = $clog2(TLB_ENTRIES) - lvl; + + // en &= plru_tree_q[idx_base + (i>>shift)] == ((i >> (shift-1)) & 1'b1); + new_index = (i >> (shift-1)) & 32'b1; + if (new_index[0]) begin + en &= plru_tree_q[idx_base + (i>>shift)]; + end else begin + en &= ~plru_tree_q[idx_base + (i>>shift)]; + end + end + replace_en[i] = en; + end + end + + // sequential process + always_ff @(posedge clk_i or negedge rst_ni) begin + if(~rst_ni) begin + tags_q <= '{default: 0}; + content_q <= '{default: 0}; + plru_tree_q <= '{default: 0}; + end else begin + tags_q <= tags_n; + content_q <= content_n; + plru_tree_q <= plru_tree_n; + end + end + //-------------- + // Sanity checks + //-------------- + + //pragma translate_off + `ifndef VERILATOR + + initial begin : p_assertions + assert ((TLB_ENTRIES % 2 == 0) && (TLB_ENTRIES > 1)) + else begin $error("TLB size must be a multiple of 2 and greater than 1"); $stop(); end + assert (ASID_WIDTH >= 1) + else begin $error("ASID width must be at least 1"); $stop(); end + end + + // Just for checking + function int countSetBits(logic[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 begin $error("More then one hit in TLB!"); $stop(); end + assert property (@(posedge clk_i)(countSetBits(replace_en) <= 1)) + else begin $error("More then one TLB entry selected for next replace!"); $stop(); end + + `endif + //pragma translate_on +#docstring_end +endmodule diff --git a/parse_sv.py b/parse_sv.py index 4973540..02f315d 100644 --- a/parse_sv.py +++ b/parse_sv.py @@ -23,9 +23,9 @@ from lib2to3.pytree import Node, Leaf from lib2to3.pgen2 import token from lib2to3.pygram import python_symbols as syms -yacc1_debug = 0 -yacc2_debug = 0 -parse_debug = 0 +yacc1_debug = 1 +yacc2_debug = 1 +parse_debug = 1 from ply import yacc, lex diff --git a/pypreproc.py b/pypreproc.py new file mode 100644 index 0000000..4569506 --- /dev/null +++ b/pypreproc.py @@ -0,0 +1,39 @@ +quote = "\"" +import sys +class Preprocessor: + def __init__(self): + self.docstrings = [] + def removeComments(self,data): + #print(data) + ret = "" + in_docstring = False + docstring = "" + for line in data.split("\n"): + docstring_end = ("#docstring_end" in line) + if "#docstring_begin" in line: + ret += "//DOCSTRING_PLACEHOLDER\n" + in_docstring = True + docstring = "" + if(in_docstring==False): + ret += line + "\n" + else: + if(not docstring_end): docstring += line + "\n" + if(docstring_end): + in_docstring = False + self.docstrings += [docstring] + print(docstring) + return ret + def insertDocstrings(self,data): + ret ="" + docstring_counter = 0 + for line in data.split("\n"): + if("//DOCSTRING_PLACEHOLDER" in line): + ret += quote*3 + ret += self.docstrings[docstring_counter] + ret += quote*3 + ret += "\n" + docstring_counter += 1 + else: + ret += "#"+line + "\n" + return ret + diff --git a/svparse.py b/svparse.py index 9822485..3a495ed 100644 --- a/svparse.py +++ b/svparse.py @@ -3,6 +3,7 @@ import sys import lexor import parse_sv import absyn +import pypreproc from ply import * import os @@ -13,7 +14,9 @@ if __name__ == '__main__': print(outputfn) with open(fname) as f: data = f.read() + preproc = pypreproc.Preprocessor() + data = preproc.removeComments(data) parse_sv.absyn = absyn.Absyn(outputfn) yacc.parse(data, debug=parse_sv.yacc2_debug) print("No Error") - parse_sv.absyn.appendComments(data) + parse_sv.absyn.appendComments(preproc.insertDocstrings(data)) -- 2.30.2