// MicroBlitz // // Harry Porter - 18 January 2020 - ISA #1 // Harry Porter - 29 November 2022 - ISA #2 // Harry Porter - 21 February 2023 - Starting changes for MMU // Harry Porter - 17 April 2023 - Finshed changes for MMU // Harry Porter - 2 May 2023 - Added ENTERFUN, EXITFUN, StackOverflow // // This circuit implements the Blitz-64 Instruction Set Architecture. // All instructions in the version 2 ISA (as of this date) are implemented. // The Memory Management Unit (MMU), including TLB registers, is implemented. // ROM: The bootloader (from MBBooter.s) is hardcoded in this file. // RAM: The RAM is hardcode in this file and is 256 KBytes on the FPGA. // The CONTROL instruction has a number of options including // digital output and serial input/output. // Digital I/O: Via CONTROL instruction, 64 bits each direction. // Memory-mapped I/O devices: Not implemented. // Secure Storage: Not implemented. // Compressed Instructions: Not implemented. // Instruction Alignment: 4 byte alignment is required. // The following cause exceptions: // DIV, REM, & all floating point instructions. // // Module hierarcy. (All these modules are defined in this file.) // CONTROL // REGS // CSRREGS // MMU_SHELL // FETCH_UNIT // MMU // TLB_REGISTERS // EXTRACTOR - Purely combinational // INJECTOR - Purely combinational // MEMORY_SYSTEM // ROM_MEMORY // RAM_MEMORY // EXECUTE - Purely combinational // ALU - Purely combinational // // There is single instance of each module, with these names: // MyControl // MyRegs // MyCSRRegs // MyMMUShell // MyFetchUnit // MyMMU // MyTLBRegs // MyExtractor // MyInjector // MyMemorySys // MyRom // MyRam // MyExecute // MyALU // // The CONTROL module is instantiated in either: // SecondLevelMain.sv - Interface to Cyclone V GX FPGA board // MB_TestBench.sv - Test bench for ModelSim // // Some parts of this file are copied and modified from Kirk Weedman's code. // Thank you Kirk. // `include "op_codes.svh" import op_codes::*; // The 50 MHz clock is too fast for this design and the timing contraints are not met. // To deal with this, a clock enable signal is used. Registers are loaded on the clock's // positive edge, but only when the enable is high. Thus, the effective clock rate is thereby // slowed down by the rate determined by CLOCK_DIVISOR, giving the combinational circuitry time // to work. The CSR_CYCLE and CSR_TIMER registers work in terms of this "effective cycle rate". localparam CYCLES_PER_MILLISEC = 32'd50000; // For a 50 MHz clock //localparam CYCLES_PER_MILLISEC = 32'd125000; // For a 125 MHz clock localparam CLOCK_DIVISOR = 32'd2; // 1=50MHz, 2=25MHz, 3=16.67MHz, ... localparam EFFECTIVE_CYCLES_PER_MILLISEC = CYCLES_PER_MILLISEC / CLOCK_DIVISOR; // These values are hard-wired into read-only csr regs: localparam ISA_VERSION = 15'h0002; localparam ORGANIZATION = 16'h0001; localparam VERSION_DATA = { EFFECTIVE_CYCLES_PER_MILLISEC, 1'b1, ISA_VERSION, ORGANIZATION }; localparam PRODUCT_ID = 64'h0000_0000_0000_0000; localparam CORE_ZERO = 64'h0001_0001_0001_0000; // cols=1, rows=1, planes=1, core=0 localparam CAUSE_ARITHMETIC = 16'h2000; localparam CAUSE_UNALIGNED_LOAD_STORE = 16'h2008; localparam CAUSE_NULL_ADDRESS = 16'h2010; localparam CAUSE_ILLEGAL_INSTR = 16'h2018; localparam CAUSE_PAGE_ILLEGAL_ADDR = 16'h2020; localparam CAUSE_PAGE_TABLE = 16'h2028; localparam CAUSE_PAGE_INVALID = 16'h2030; localparam CAUSE_PAGE_WRITE = 16'h2038; localparam CAUSE_PAGE_FETCH = 16'h2040; localparam CAUSE_PAGE_COPY_ON_WRITE = 16'h2048; localparam CAUSE_PAGE_FIRST_DIRTY = 16'h2050; localparam CAUSE_DEBUG = 16'h2058; localparam CAUSE_BREAKPOINT = 16'h2060; localparam CAUSE_SINGLESTEP = 16'h2068; localparam CAUSE_EMULATED = 16'h2070; localparam CAUSE_HARDWARE = 16'h2078; localparam CAUSE_BAD_ARRAY_INDEX = 16'h2080; localparam CAUSE_STACK_OVERFLOW = 16'h2088; localparam CAUSE_TIMER_INTERRUPT = 16'h2090; // Number of TLB registers localparam NUMBER_OF_TLB_REGS = 8; localparam TLB_ADDR_SIZE = 64'd3; // Number of bits to address a TLB reg, e.g., 000 ... 111 ==> 3 localparam TRUE = 1'b1; localparam FALSE = 1'b0; localparam ONE = 1'b1; localparam ZERO = 1'b0; // Size Codes localparam SZ_BYTE = 6'b00, SZ_HALFWORD = 6'b01, SZ_WORD = 6'b10, SZ_DOUBLEWORD = 6'b11; localparam OP_RS1 = 2'b00, OP_RS2 = 2'b01, OP_IMM = 2'b10, OP_PC = 2'b11; localparam FORMAT_A = 2'b00, FORMAT_B = 2'b01, FORMAT_C = 2'b10, FORMAT_D = 2'b11; // Function Codes for the ALU localparam A_CHB = 6'd0, A_CHH = 6'd1, A_CHW = 6'd2, A_NULLTEST = 6'd3, A_INDH = 6'd4, A_INDW = 6'd5, A_INDD = 6'd6, A_EXTB = 6'd7, A_EXTH = 6'd8, A_EXTW = 6'd9, A_ADD = 6'd10, A_SUB = 6'd11, A_AND = 6'd12, A_OR = 6'd13, A_XOR = 6'd14, A_SLL = 6'd15, A_SLA = 6'd16, A_SRL = 6'd17, A_SRA = 6'd18, A_ADDOK = 6'd19, A_ROTR = 6'd20, A_TESTEQ = 6'd21, A_TESTNE = 6'd22, A_TESTLT = 6'd23, A_TESTLE = 6'd24, A_TESTGT = 6'd25, A_TESTGE = 6'd26, A_ALNH = 6'd27, A_ALNW = 6'd28, A_ALND = 6'd29, A_INJ1H = 6'd30, A_INJ2H = 6'd31, A_INJ1W = 6'd32, A_INJ2W = 6'd33, A_INJ1D = 6'd34, A_INJ2D = 6'd35, A_UPPER16 = 6'd36, A_UPPER20 = 6'd37, A_SHIFT16 = 6'd38, A_AUIPC = 6'd39, A_ADD3 = 6'd40, A_MULADD = 6'd41, A_MULADDU = 6'd42, A_IND0 = 6'd43, A_IND1 = 6'd44, A_IND2 = 6'd45, A_IND4 = 6'd46, A_IND8 = 6'd47, A_IND16 = 6'd48, A_IND24 = 6'd49, A_IND32 = 6'd50, A_DIV = 6'd51, A_REM = 6'd52, A_ADD_ST = 6'd53; // Function Codes for misc instructions localparam SEL_CSR_SWAP = 6'd0, SEL_CSR_READ = 6'd1, SEL_CSR_SET = 6'd2, SEL_CSR_CLR = 6'd3, SEL_TLBCLEAR = 6'd4, SEL_TLBFLUSH = 6'd5, SEL_FENCE = 6'd6, SEL_CHECKADDR = 6'd7, SEL_SYSRET = 6'd8, SEL_SLEEP1 = 6'd9, SEL_SLEEP2 = 6'd10, SEL_DEBUG = 6'd11, SEL_BREAKPOINT = 6'd12, SEL_SYSCALL = 6'd13, SEL_GETSTAT = 6'd14, SEL_PUTSTAT = 6'd15, SEL_RESTART = 6'd16; // ********** NOTE: THE FOLLOWING ENUM SHOULD MACTH THE SAME ENUM IN "SecondLevel.sv"! ********** // Finite-State-Machine states (4 bits) typedef enum logic [3:0] { BEGIN_STATE, PRE_EXEC_STATE, EXEC_STATE, RD_WR_STATE, BRANCH_STATE, EXCEPT_STATE, FENCE_STATE, SLEEP_STATE, ILLEGAL_STATE } STATE_TYPE; // Categories of instruction (4 bits) typedef enum logic [3:0] {ALU_INSTR, EMUL_INSTR, LD_ST_INSTR, BR_INSTR, ENTER_INSTR, EXIT_INSTR, SYS_INSTR, USER_INSTR, FP_INSTR, ILL_INSTR, CTRL_INSTR} INST_TYPE; // localparam ALU_INSTR = 4'd0, // EMUL_INSTR = 4'd1, // LD_ST_INSTR = 4'd2, // BR_INSTR = 4'd3, // ...etc... localparam NOP_INSTRUCTION = 32'h0100_0000; // Dummy for startup: NOP = ADD r0,r0,r0 //******************** CONTROL ******************** // module CONTROL ( input logic clock, input logic reset_in, input wire [63:0] digital_input, output wire [63:0] digital_output, input wire clk50MHz, input wire clk125MHz, input wire UART_RX, output wire UART_TX, output wire [63:0] csr_prevpc_out, // For FPGA debugging output wire [35:0] pc_of_current_inst, // . output wire [15:0] csr_cause_out, // . output wire [3:0] state_out, // . output logic line // Never assigned or referenced (Used to draw a big red line in ModelSim wave display) ); // -------------------- Registers -------------------- logic [2:0] reset_count; STATE_TYPE state; logic [35:0] pc; logic [31:0] inst; // The instruction regsiter logic [15:0] inst_code; // . and the exception code from its fetch logic [63:0] hold; // Might hold an instruction, might hold a reg. logic [15:0] hold_code; // . the exception code (if an intstruction) logic [63:0] cycle_count; logic [63:0] inst_count; logic ss_trig_save; // Use to compute singlestep_trap_triggered. logic [63:0] digital_out_reg; logic [7:0] serial_out_reg; logic serial_out_start; logic [63:0] m_data_save; logic [15:0] m_code_save; logic clock_enable; logic [7:0] clock_enable_counter; // -------------------- Not registers -------------------- wire reset; STATE_TYPE next_state; logic in_final_state_of_instr; wire is_sleep; // Want to go into SLEEP_STATE wire is_illegal; // Want to go into ILLEGAL_STATE wire is_load_store; // Want to go into RD_WR_STATE wire is_branch_taken; // Want to go into BRANCH_STATE wire is_fence; // Want to go into FENCE_STATE logic exception_triggered; // Want to go into EXCEPT_STATE (don't update) logic singlestep_trap_triggered; // Want to go into EXCEPT_STATE (perform updates) logic timer_interrupt_triggered; // Want to go into EXCEPT_STATE (perform updates) logic restart_instruction; // Driven HIGH by RESTART instruction wire want_reg_write; wire want_digital_write; wire [63:0] digital_wr_data; wire want_serial_send; wire [7:0] serial_send_data; wire serial_out_busy; wire o_Tx_Active; wire clear_input_avail; // from EXECUTE wire [7:0] data8; wire data8_ready; reg [7:0] byte_reg; reg got_byte_reg; reg serial_input_avail; logic [3:0] reg1_addr; wire [3:0] reg2_addr; wire [3:0] reg3_addr; wire [63:0] reg1_data; wire [63:0] reg2_data; wire [63:0] reg3_data; wire [63:0] regLR_data; wire [63:0] regSP_data; wire [3:0] regD_addr; wire [63:0] regD_data; MMU_COMMAND ld_st_command; logic [35:0] pc_of_next_inst; logic [35:0] target_address; logic [35:0] mem_target_address; logic [35:0] physical_address; MMU_COMMAND mem_command_to_use; logic is_checkaddr; logic [63:0] mem_write_data; logic [63:0] mem_cas_old_data; logic [35:0] mem_addr_to_use; logic [63:0] m_data_out; // Only high while done in high logic [15:0] m_code_out; // . logic mmu_done; logic mmu_ready; logic [63:0] mmu_result_data; // Retains previous value until done goes high again logic [15:0] mmu_result_code; // . logic [63:0] data_to_csr, data_from_csr, csr_status; logic csr_timer_is_neg, want_csr_write, is_csrstatus_write_wanted; logic want_csr_copy_stat2_to_status; logic [35:0] csr_prevpc_from_CSR; logic [35:0] csr_trapvec_from_CSR; logic [63:0] current_csr_pgtable; logic current_single_step, current_interrupts_enabled, current_mode; // These come from the EXECUTE Module... wire is_except_wanted_from_EXECUTE; // Want to go into EXCEPT_STATE logic [63:0] exc_prevpc_from_EXECUTE; logic [15:0] exc_cause_from_EXECUTE; logic [63:0] exc_bad_from_EXECUTE; logic [63:0] exc_addr_from_EXECUTE; logic control_load_status_en; // For CONTROL ENABLE_KERNEL / DISABLE_KERNEL logic [63:0] control_load_status_data; // These are the values after adjusting for Timer or SingleStep Exception... logic [63:0] exc_prevpc_to_use; logic [15:0] exc_cause_to_use; logic [63:0] exc_bad_to_use; logic [63:0] exc_addr_to_use; // Generate a slow clock_enable signal... always_ff @(posedge clock) begin if (reset) begin clock_enable_counter <= 0; clock_enable <= 1; end else if (clock_enable_counter == (CLOCK_DIVISOR[7:0] - 1)) begin clock_enable_counter <= 0; clock_enable <= 1; end else begin clock_enable_counter <= clock_enable_counter + 8'b1; clock_enable <= 0; end end // Synchronize the reset signal to eliminate metastability... always_ff @(posedge clock) begin if (reset_in | restart_instruction) reset_count <= 4; else if (reset_count == 0) reset_count <= 0; else reset_count <= reset_count - 3'd1; end assign reset = (reset_count == 0) ? 1'b0 : 1'b1; // Increment cycle_count ... always_ff @(posedge clock) if (clock_enable) begin if (reset) cycle_count <= 0; else cycle_count <= cycle_count + 1; end // Compute the next_state from the current state and info from EXECUTE module... always_comb begin case (state) BEGIN_STATE: next_state = PRE_EXEC_STATE; PRE_EXEC_STATE: next_state = EXEC_STATE; EXEC_STATE: begin if (is_sleep) next_state = SLEEP_STATE; else if (is_illegal) next_state = ILLEGAL_STATE; else if (is_load_store) next_state = RD_WR_STATE; else if (is_branch_taken) next_state = BRANCH_STATE; else next_state = EXEC_STATE; end RD_WR_STATE: begin if (is_fence) next_state = FENCE_STATE; else if (is_branch_taken) next_state = BRANCH_STATE; else next_state = EXEC_STATE; end BRANCH_STATE: next_state = EXEC_STATE; EXCEPT_STATE: next_state = PRE_EXEC_STATE; FENCE_STATE: next_state = PRE_EXEC_STATE; SLEEP_STATE: next_state = SLEEP_STATE; ILLEGAL_STATE: next_state = ILLEGAL_STATE; default: next_state = ILLEGAL_STATE; endcase // This is the time at which the gen. purp. regs & CSRs should be updated. in_final_state_of_instr = (next_state != RD_WR_STATE) & (next_state != BRANCH_STATE) & (state != SLEEP_STATE) & (state != EXCEPT_STATE) & (state != FENCE_STATE); // If a trap has been signalled, then override the above to go into EXCEPT_STATE... if (exception_triggered & ((state == EXEC_STATE) | (state == RD_WR_STATE) | (state == BRANCH_STATE))) next_state = EXCEPT_STATE; else if ((singlestep_trap_triggered | timer_interrupt_triggered) & (next_state == EXEC_STATE)) // After any instruction has completed successully, the next_state must be EXEC. // If next_state is anything else, then the trap is ignored. // NOTE: The SLEEP instructions will ignore the trap. next_state = EXCEPT_STATE; end // On each clock edge, go to next state... // Load these registers: state, pc, inst, inst_code, hold, hold_code, and inst_count. always_ff @(posedge clock) begin if (clock_enable) if (reset) begin state <= BEGIN_STATE; pc <= 36'h4_0000_0000; // Starting address (i.e., first byte of ROM) inst <= NOP_INSTRUCTION; // Dummy for startup inst_code <= 16'b0; hold <= 64'b0; hold_code <= 16'b0; inst_count <= 0; end else if (state == BEGIN_STATE) if (!mmu_ready | (cycle_count < 2)) state <= BEGIN_STATE; else begin state <= PRE_EXEC_STATE; inst <= NOP_INSTRUCTION; // Dummy for startup inst_code <= 0; pc <= pc + 4; end else if (mmu_ready) begin if (next_state == EXEC_STATE) if (state == RD_WR_STATE) begin inst <= hold [63:32]; inst_code <= hold_code; inst_count <= inst_count + 1; end else begin inst <= mmu_result_data [63:32]; inst_code <= mmu_result_code; inst_count <= inst_count + 1; end if ((next_state == RD_WR_STATE) | (state==EXEC_STATE)) begin hold <= mmu_result_data; hold_code <= mmu_result_code; end if ( (next_state == EXEC_STATE) | (next_state == PRE_EXEC_STATE) ) begin pc <= pc + 4; end else if (next_state == BRANCH_STATE) begin pc <= target_address+4; hold <= regD_data; end else if (next_state == EXCEPT_STATE) begin pc <= csr_trapvec_from_CSR; // Load PC from csr_trapvec inst <= NOP_INSTRUCTION; // Dummy instruction inst_code <= 0; end else if (next_state == FENCE_STATE) begin pc <= pc_of_next_inst ; // Addr of instruction after the FENCE inst <= NOP_INSTRUCTION; inst_code <= 0; end state <= next_state; end end assign pc_of_current_inst = pc - 8; assign pc_of_next_inst = pc - 4; assign csr_prevpc_out = csr_prevpc_from_CSR; assign csr_cause_out = exc_cause_to_use; assign state_out = state; // The value in csr_status is valid in the EXEC_STATE. However, the SYSRET // instruction loads csr_status earlier (at the BRANCH_STATE) but we must not // trigger a singlestep trap for the SYSRET. So, the code below will make // "singlestep_trap_triggered" true for the duration of any instrution that // begins execution (i.e., in its EXEC_STATE) with the bits set. always_ff @(posedge clock) if (reset) ss_trig_save <= 0; else if (clock_enable & mmu_done) if (state == EXEC_STATE) ss_trig_save <= (csr_status [2:1] == 2'b11); else if (state == EXCEPT_STATE) ss_trig_save <= 0; always_comb if (state == EXCEPT_STATE) singlestep_trap_triggered = 0; else if (mmu_done & (state == EXEC_STATE)) // When Singlestep and InterruptsEnabled are both TRUE, this singlestep_trap_triggered = (csr_status [2:1] == 2'b11); // instruction will execute with singlestep_trap_triggered high. else singlestep_trap_triggered = ss_trig_save; // Execution is completed. Now compute exception/interrupt/trap signals... always_comb begin exception_triggered = 0; timer_interrupt_triggered = 0; exc_prevpc_to_use = 0; exc_cause_to_use = 0; exc_bad_to_use = 0; exc_addr_to_use = 0; // If the EXECUTE module is calling for an exception... exception_triggered = is_except_wanted_from_EXECUTE & ( (state == EXEC_STATE) | (state == RD_WR_STATE) | (state == BRANCH_STATE) ); exc_prevpc_to_use = exc_prevpc_from_EXECUTE; exc_cause_to_use = exc_cause_from_EXECUTE; exc_bad_to_use = exc_bad_from_EXECUTE; exc_addr_to_use = exc_addr_from_EXECUTE; // If no exceptions so far, then check to see if a singlestep trap or timer interrupt is needed. if (!exception_triggered) if (singlestep_trap_triggered) begin // If Singlestep is true, then signal that exception. Singlestep has the lowest priority, // so if there is any other kind of exception, the other exception will override and cancel it. exc_prevpc_to_use = pc_of_next_inst; exc_cause_to_use = CAUSE_SINGLESTEP; exc_bad_to_use = {32'h0000_0000 , inst}; exc_addr_to_use = 0; end else if (csr_timer_is_neg & current_interrupts_enabled) begin timer_interrupt_triggered = 1; exc_prevpc_to_use = pc_of_next_inst; exc_cause_to_use = CAUSE_TIMER_INTERRUPT; exc_bad_to_use = 0; exc_addr_to_use = 0; end end always_comb begin mem_addr_to_use = pc; mem_command_to_use = MMU_FETCH; // if (is_load_store & (state == EXEC_STATE) & !exception_triggered) // Is !exception_triggered really necessary? if (is_load_store & (state == EXEC_STATE)) begin mem_command_to_use = ld_st_command; mem_addr_to_use = mem_target_address; end else if (is_branch_taken & ((state == EXEC_STATE) | (state == RD_WR_STATE))) begin mem_command_to_use = MMU_FETCH; mem_addr_to_use = target_address; end end // ******************** DIGITAL I/O ******************** // Update the digital output register, as required... always_ff @(posedge clock) begin if (clock_enable) if (reset) digital_out_reg <= 0; else if (want_digital_write & !exception_triggered) digital_out_reg <= digital_wr_data; end assign digital_output = digital_out_reg; // ******************** SERIAL I/O ******************** // Using the 50 MHz clock // Clock period = 20 nSec (20 nSec x 50,000,000 = 1 Sec) // For 300 baud... // 50,000,000 / 1200 = 41,666.66 Clocks Per Bit. // For 9600 baud... // 50,000,000 / 9600 = 5208.33 Clocks Per Bit. // For 19200 baud... // 50,000,000 / 19200 = 2604.17 Clocks Per Bit. // For 115,200 baud... // 50,000,000 / 115,200 = 434 Clocks Per Bit. // For 230,400 baud... // 50,000,000 / 230,400 = 217.01 Clocks Per Bit. // Using the 125 MHz clock // Clock period = 8 nSec (8 nSec x 125,000,000 = 1 Sec) // For 300 baud... // 125,000,000 / 1200 = 104,1667 Clocks Per Bit. (16-bit counter overflow!) // For 9600 baud... // 125,000,000 / 9600 = 13,020.83 Clocks Per Bit. // For 19200 baud... // 125,000,000 / 19200 = 6,510.42 Clocks Per Bit. // For 115,200 baud... // 125,000,000 / 115,200 = 1,085.07 Clocks Per Bit. // For 230,400 baud... // 125,000,000 / 230,400 = 542.53 Clocks Per Bit. // parameter c_CLKS_PER_BIT = 2604; // 19,200 baud - 50 MHz // parameter c_CLKS_PER_BIT = 434; // 115,200 baud - 50 MHz parameter c_CLKS_PER_BIT = 217; // 230,400 baud - 50 MHz // parameter c_CLKS_PER_BIT = 542; // 230,400 baud - 125 MHz uart_tx #(.CLKS_PER_BIT(c_CLKS_PER_BIT)) My_uart_tx (.clock (clk50MHz), // (.clock (clk125MHz), .i_data_avail (serial_out_start), .i_data_byte (serial_out_reg), .o_active (o_Tx_Active), .o_tx (UART_TX), // previously GPIO[35] .o_done () // ignored ); uart_rx #(.CLKS_PER_BIT(c_CLKS_PER_BIT)) My_uart_rx (.clock (clk50MHz), // (.clock (clk125MHz), .i_rx (UART_RX), .o_data_avail (data8_ready), // Will go high for 1 clock cycle when byte recv'd. .o_data_byte (data8) ); // When we have a request to SEND, we need to raise the START line // to the UART and keep it high until it becomes busy with sending. // Then we need to lower it so that only one byte is sent. always_ff @(posedge clock) begin if (clock_enable) if (reset) serial_out_start <= 0; else if (o_Tx_Active) serial_out_start <= 0; else if (want_serial_send & !exception_triggered) serial_out_start <= 1; end // Update the serial output register, as required... assign serial_out_busy = serial_out_start | o_Tx_Active; always_ff @(posedge clock) begin if (clock_enable) if (reset) serial_out_reg <= 0; else if (want_serial_send & ~serial_out_busy & !exception_triggered) serial_out_reg <= serial_send_data; end always_ff @(posedge clk50MHz) begin if (reset) begin byte_reg <= 8'b0000_0000; got_byte_reg <= 0; end else if (~data8_ready) begin got_byte_reg <= 0; end else if (data8_ready) begin byte_reg <= data8; got_byte_reg <= 1; end end always_ff @(posedge clock) begin if (reset) serial_input_avail <= 0; else if (clear_input_avail) serial_input_avail <= 0; else if (got_byte_reg) serial_input_avail <= 1; end //******************************************************************************************************* //******************************************************************************************************* //************************************** ************************************** //************************************** MODULE INSTANTIATIONS ************************************** //************************************** ************************************** //******************************************************************************************************* //******************************************************************************************************* // ******************** GENERAL PURPOSE REGISTERS ******************** REGS MyRegs ( .clock (clock), .clock_enable (clock_enable), .reset_in (reset), .mmu_ready (mmu_ready), .reg1_addr (reg1_addr), .reg2_addr (reg2_addr), .reg3_addr (reg3_addr), .reg1_data (reg1_data), .reg2_data (reg2_data), .reg3_data (reg3_data), .regLR_data (regLR_data), .regSP_data (regSP_data), .regD_addr (regD_addr), .regD_data ((state == BRANCH_STATE) ? hold : regD_data), .reg_write_en (want_reg_write & in_final_state_of_instr & !exception_triggered) ); // ******************** CSRs ******************** CSRREGS MyCSRRegs ( .clock (clock), .clock_enable (clock_enable), .reset_in (reset), .cycle_count_in (cycle_count), .inst_count_in (inst_count), .mmu_ready (mmu_ready), .csr_addr (reg1_addr), .csr_data_in (data_to_csr), .csr_data_out (data_from_csr), .csr_status_out (csr_status), .current_csr_pgtable (current_csr_pgtable), .current_single_step (current_single_step), .current_interrupts_enabled (current_interrupts_enabled), .current_mode (current_mode), .csr_timer_is_neg (csr_timer_is_neg), .csr_write_en ((want_csr_write | is_csrstatus_write_wanted) & in_final_state_of_instr & !exception_triggered), .is_csrstatus_write_wanted (is_csrstatus_write_wanted), .want_csr_copy_stat2_to_status (want_csr_copy_stat2_to_status & in_final_state_of_instr & !exception_triggered), .csr_prevpc_from_CSR (csr_prevpc_from_CSR), .csr_trapvec_from_CSR (csr_trapvec_from_CSR), .control_load_status_en (control_load_status_en), .control_load_status_data (control_load_status_data), .csr_exception_en (next_state == EXCEPT_STATE), .exc_prevpc (exc_prevpc_to_use), .exc_cause (exc_cause_to_use), .exc_bad (exc_bad_to_use), .exc_addr (exc_addr_to_use) ); // ******************** MEMORY ******************** MMU_SHELL MyMMUShell ( .clock (clock), .clock_enable (clock_enable), .reset (reset), .root_addr_in ({current_csr_pgtable [43:14], 14'h0}), .asid_in (current_csr_pgtable [63:48]), .mode_in (current_mode), .address_in (mem_addr_to_use), .command_in (mmu_ready ? mem_command_to_use : MMU_NOP), .is_checkaddr_in (is_checkaddr& (state == EXEC_STATE)), .st_data_in (mem_write_data), .cas_old_data_in (mem_cas_old_data), .result_data_out (m_data_out), .result_code_out (m_code_out), .done_out (mmu_done), .ready_out (mmu_ready) ); // The outputs from the MMU (result_data and result_code) are only valid // while "done" is high. Here we latch them into "mmu_result_data" and // "mmu_result_code" so they stay valid until the next time done is raised. always_ff @(posedge clock) if (clock_enable & mmu_done) begin m_data_save <= m_data_out; m_code_save <= m_code_out; end always_comb if (mmu_done) begin mmu_result_data = m_data_out; mmu_result_code = m_code_out; end else begin mmu_result_data = m_data_save; mmu_result_code = m_code_save; end // ******************** EXECUTE ******************** EXECUTE MyExecute ( // Purely combinational .inst (inst), .inst_code (inst_code), .pc (pc_of_current_inst), .csr_status (csr_status), .reg1_addr (reg1_addr), .reg2_addr (reg2_addr), .reg3_addr (reg3_addr), .reg1_data (reg1_data), .reg2_data (reg2_data), .reg3_data (reg3_data), .regLR_data (regLR_data), .regSP_data (regSP_data), .want_reg_write (want_reg_write), .regD_addr (regD_addr), .regD_data (regD_data), .target_address (target_address), .mem_target_address (mem_target_address), .ld_st_command (ld_st_command), .mmu_result_data (mmu_result_data), .mmu_result_code (mmu_result_code), .mem_wr_data (mem_write_data), .mem_cas_old_data (mem_cas_old_data), .digital_input (digital_input), .digital_wr_data (digital_wr_data), .want_digital_write (want_digital_write), .want_serial_send (want_serial_send), .serial_send_data (serial_send_data), .serial_out_busy (serial_out_busy), .serial_input_avail (serial_input_avail), .serial_input_reg (byte_reg), .clear_input_avail (clear_input_avail), .want_csr_write (want_csr_write), .is_csrstatus_write_wanted (is_csrstatus_write_wanted), .data_to_csr (data_to_csr), .data_from_csr (data_from_csr), .want_csr_copy_stat2_to_status (want_csr_copy_stat2_to_status), .csr_prevpc_from_CSR (csr_prevpc_from_CSR), .csr_trapvec_from_CSR (csr_trapvec_from_CSR), .is_sleep (is_sleep), .is_illegal (is_illegal), .is_load_store (is_load_store), .is_branch_taken (is_branch_taken), .is_fence (is_fence), .is_except_wanted (is_except_wanted_from_EXECUTE), .is_checkaddr (is_checkaddr), .restart_instruction (restart_instruction), .control_load_status_en (control_load_status_en), .control_load_status_data (control_load_status_data), .exc_prevpc (exc_prevpc_from_EXECUTE), .exc_cause (exc_cause_from_EXECUTE), .exc_bad (exc_bad_from_EXECUTE), .exc_addr (exc_addr_from_EXECUTE) ); endmodule // CONTROL //***************************************************************************************** //***************************************************************************************** //************************************** ************************************** //************************************** EXECUTE ************************************** //************************************** ************************************** //***************************************************************************************** //***************************************************************************************** // // This module performs the execution. It is purely combinational. // // Basically, it takes the instruction as input and computes all the signals // we'll need to perform updates to the state. // // It generates outputs (reg1_addr, reg2_addr, reg3_addr) and the register file // will return their current values for use here. // // If a register needs to be updated, it generates: want_reg_write, regD_addr, regD_data. // // If memory needs to be read, it generates: ld_st_command, mem_target_address. // On a subsequent cycle, it takes signals (mmu_result_data, mmu_result_code) and figures out // what to do with the result. // // If memory needs to be written, it generates: ld_st_command, mem_target_address, mem_wr_data, // mem_cas_old_data. // // For some instructions, it generates: want_digital_write, digital_wr_data. // // For some instructions, it generates: want_serial_send, serial_send_data. // // If exceptions occur, it generates: is_except_wanted, exc_cause, exc_prevpc, exc_bad, exc_addr. // // It generates signals to tell what kind of instruction this is, which are used to determine the // next state: is_sleep, is_illegal, is_load_store, is_branch_taken, is_fence, is_except_wanted, is_checkaddr. // // If we have a CONTROL instruction modifying csr_status, it generates: control_load_status_data, // control_load_status_en. // // module EXECUTE ( input logic [31:0] inst, input logic [15:0] inst_code, input logic [35:0] pc, // The addr of this instruction (valid during EXEC_STATE) input logic [63:0] csr_status, output logic [3:0] reg1_addr, output logic [3:0] reg2_addr, output logic [3:0] reg3_addr, input wire [63:0] reg1_data, input wire [63:0] reg2_data, input wire [63:0] reg3_data, input wire [63:0] regLR_data, input wire [63:0] regSP_data, output logic want_reg_write, output logic [3:0] regD_addr, output logic [63:0] regD_data, output logic [35:0] target_address, // for BRANCH, SYSRET, EXITFUN output logic [35:0] mem_target_address, // for LOAD, STORE, CAS, TLB_FLUSH, CHECKADDR output MMU_COMMAND ld_st_command, input logic [63:0] mmu_result_data, input logic [15:0] mmu_result_code, output logic [63:0] mem_wr_data, output logic [63:0] mem_cas_old_data, input wire [63:0] digital_input, output logic [63:0] digital_wr_data, output logic want_digital_write, output logic want_serial_send, output logic [7:0] serial_send_data, input wire serial_out_busy, input wire serial_input_avail, input wire [7:0] serial_input_reg, output logic clear_input_avail, output logic want_csr_write, output logic is_csrstatus_write_wanted, // Otherise use reg1_addr output logic [63:0] data_to_csr, input logic [63:0] data_from_csr, output logic want_csr_copy_stat2_to_status, input logic [35:0] csr_prevpc_from_CSR, input logic [35:0] csr_trapvec_from_CSR, output logic is_sleep, output logic is_illegal, output logic is_load_store, output logic is_branch_taken, output logic is_fence, output logic is_except_wanted, output logic is_checkaddr, output logic restart_instruction, output logic control_load_status_en, // For CONTROL ENABLE_KERNEL / DISABLE_KERNEL output logic [63:0] control_load_status_data, output logic [63:0] exc_prevpc, output logic [15:0] exc_cause, output logic [63:0] exc_bad, output logic [63:0] exc_addr ); // logic [3:0] Reg1, Reg2, Reg3, RegD; logic [15:0] immed16_B; // logic [15:0] immed16_C; // logic [19:0] immed20; logic [63:0] immed64_B, immed64_C, immed64_D; logic [63:0] immed64; logic [63:0] addxy; logic [15:0] cntrl_sigs; INST_TYPE inst_type; logic [1:0] op_x; logic [1:0] op_y; logic [5:0] sel; logic [1:0] format; logic [7:0] op1, op2; logic [35:0] branch_target, mem_addr; logic [63:0] alu_out; logic alu_produces_data_out; logic arith_exception; logic bad_array_index_exception; logic null_exception; logic stack_exception; logic add_overflow; logic stack_overflow; ALU MyALU ( .alu_fun (sel), // This code tells what operation to perform .op_x_in (op_x), // This code selects the "x" input from reg1/reg2/immed/pc .op_y_in (op_y), // This code selects the "y" input from reg1/reg2/immed/pc .reg1_in (reg1_data), // Input values .reg2_in (reg2_data), .reg3_in (reg3_data), .imm_in (immed64), .pc_in (pc), .regD_addr (regD_addr), .stack_limit (csr_status[63:28]), .data_out (alu_out), // Output result .alu_produces_data_out (alu_produces_data_out), // Is an output producesd, or just exception checking? .arith_exception_out (arith_exception), // TRUE = exception detected, FALSE = no exception .bad_array_index_exception_out (bad_array_index_exception), // TRUE = exception detected, FALSE = no exception .null_exception_out (null_exception), // TRUE = exception detected, FALSE = no exception .stack_exception_out (stack_exception) // TRUE = exception detected, FALSE = no exception ); always_comb begin op1 = inst[31:24]; op2 = inst[23:16]; reg1_addr = inst[7:4]; reg2_addr = inst[11:8]; reg3_addr = inst[15:12]; regD_addr = inst[3:0]; immed16_B = inst[23:8]; // immed16_C = {inst[23:12], inst[3:0]}; // immed20 = inst[23:4]; immed64_B = { {48{inst[23]}}, inst[23:8] }; immed64_C = { {48{inst[23]}}, inst[23:12], inst[3:0] }; immed64_D = { {44{inst[23]}}, inst[23:4] }; cntrl_sigs = 16'b0000_00_00_000000_00; regD_data = 0; want_reg_write = 0; target_address = 0; mem_target_address = 0; is_checkaddr = 0; mem_wr_data = 0; mem_cas_old_data = 0; is_sleep = 0; is_illegal = 0; is_load_store = 0; is_branch_taken = 0; is_fence = 0; is_except_wanted = 0; restart_instruction = 0; ld_st_command = MMU_NOP; want_digital_write = 0; digital_wr_data = 0; want_serial_send = 0; serial_send_data = 8'b0; clear_input_avail = 0; data_to_csr = 0; want_csr_write = 0; is_csrstatus_write_wanted = 0; want_csr_copy_stat2_to_status = 0; exc_prevpc = 0; exc_cause = 0; exc_bad = 0; exc_addr = 0; control_load_status_en = 0; control_load_status_data = 0; // Compute these values for use in ENTERFUN and EXITFUN instructions addxy = reg1_data + immed64_B; add_overflow = (!(reg1_data[63] ^ immed64_B[63])) & (addxy[63] ^ reg1_data[63]); stack_overflow = ((regD_addr == 4'd15) & (addxy < csr_status[63:28])); if (op1 == 8'b0) // Format A opcodes... case (op2) // inst_type op_x op_y SEL Format `OP2_ADD : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_ADD_ST , FORMAT_A }; `OP2_ADDOK : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_ADDOK , FORMAT_A }; `OP2_SUB : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_SUB , FORMAT_A }; `OP2_AND : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_AND , FORMAT_A }; `OP2_OR : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_OR , FORMAT_A }; `OP2_XOR : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_XOR , FORMAT_A }; `OP2_MULADD : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_MULADD , FORMAT_A }; `OP2_MULADDU : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_MULADDU, FORMAT_A }; // `OP2_DIV : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_DIV , FORMAT_A }; // `OP2_REM : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_REM , FORMAT_A }; `OP2_DIV : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_REM : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FADD : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FSUB : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FMUL : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FDIV : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FMIN : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FMAX : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FNEG : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FABS : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FSQRT : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FEQ : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FLT : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FLE : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FCVTFI : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FCVTIF : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FMADD : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FNMADD : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FMSUB : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_FNMSUB : cntrl_sigs = {EMUL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; `OP2_ADD3 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_ADD3 , FORMAT_A }; `OP2_SLL : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_SLL , FORMAT_A }; `OP2_SLA : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_SLA , FORMAT_A }; `OP2_SRL : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_SRL , FORMAT_A }; `OP2_SRA : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_SRA , FORMAT_A }; `OP2_ROTR : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_ROTR , FORMAT_A }; `OP2_SEXTB : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_EXTB , FORMAT_A }; `OP2_SEXTH : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_EXTH , FORMAT_A }; `OP2_SEXTW : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_EXTW , FORMAT_A }; `OP2_NULLTEST : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_NULLTEST, FORMAT_A }; `OP2_CHECKB : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_CHB , FORMAT_A }; `OP2_CHECKH : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_CHH , FORMAT_A }; `OP2_CHECKW : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_CHW , FORMAT_A }; `OP2_INDEX0 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_IND0 , FORMAT_A }; `OP2_INDEX1 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_IND1 , FORMAT_A }; `OP2_INDEX2 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_IND2 , FORMAT_A }; `OP2_INDEX4 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_IND4 , FORMAT_A }; `OP2_INDEX8 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_IND8 , FORMAT_A }; `OP2_INDEX16 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_IND16 , FORMAT_A }; `OP2_INDEX24 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_IND24 , FORMAT_A }; `OP2_INDEX32 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_IND32 , FORMAT_A }; `OP2_INDIANH : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_INDH , FORMAT_A }; `OP2_INDIANW : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_INDW , FORMAT_A }; `OP2_INDIAND : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_INDD , FORMAT_A }; `OP2_TESTEQ : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_TESTEQ , FORMAT_A }; `OP2_TESTNE : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_TESTNE , FORMAT_A }; `OP2_TESTLT : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_TESTLT , FORMAT_A }; `OP2_TESTLE : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_TESTLE , FORMAT_A }; `OP2_ALIGNH : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_ALNH , FORMAT_A }; `OP2_ALIGNW : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_ALNW , FORMAT_A }; `OP2_ALIGND : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_ALND , FORMAT_A }; `OP2_INJECT1H : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_INJ1H , FORMAT_A }; `OP2_INJECT2H : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_INJ2H , FORMAT_A }; `OP2_INJECT1W : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_INJ1W , FORMAT_A }; `OP2_INJECT2W : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_INJ2W , FORMAT_A }; `OP2_INJECT1D : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_INJ1D , FORMAT_A }; `OP2_INJECT2D : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_RS2, A_INJ2D , FORMAT_A }; `OP2_CSRSWAP : cntrl_sigs = {SYS_INSTR, OP_RS1, OP_RS2, SEL_CSR_SWAP, FORMAT_A }; `OP2_CSRREAD : cntrl_sigs = {SYS_INSTR, OP_RS1, OP_IMM, SEL_CSR_READ, FORMAT_A }; `OP2_SYSRET : cntrl_sigs = {SYS_INSTR, 2'd0, 2'd0, SEL_SYSRET, FORMAT_A }; `OP2_SLEEP1 : cntrl_sigs = {SYS_INSTR, 2'd0, 2'd0, SEL_SLEEP1, FORMAT_A }; `OP2_SLEEP2 : cntrl_sigs = {SYS_INSTR, 2'd0, 2'd0, SEL_SLEEP2, FORMAT_A }; `OP2_DEBUG : cntrl_sigs = {USER_INSTR, 2'd0, 2'd0, SEL_DEBUG, FORMAT_A }; `OP2_BREAKPOINT : cntrl_sigs = {USER_INSTR, 2'd0, 2'd0, SEL_BREAKPOINT,FORMAT_A }; `OP2_GETSTAT : cntrl_sigs = {USER_INSTR, 2'd0, 2'd0, SEL_GETSTAT, FORMAT_A }; `OP2_PUTSTAT : cntrl_sigs = {USER_INSTR, 2'd0, 2'd0, SEL_PUTSTAT, FORMAT_A }; `OP2_TLBCLEAR : cntrl_sigs = {SYS_INSTR, 2'd0, 2'd0, SEL_TLBCLEAR, FORMAT_A }; `OP2_TLBFLUSH : cntrl_sigs = {SYS_INSTR, 2'd0, 2'd0, SEL_TLBFLUSH, FORMAT_A }; `OP2_FENCE : cntrl_sigs = {USER_INSTR, 2'd0, 2'd0, SEL_FENCE, FORMAT_A }; `OP2_CAS : cntrl_sigs = {LD_ST_INSTR,2'd0, 2'd0, {2'b01,MMU_CAS}, FORMAT_A }; `OP2_RESTART : cntrl_sigs = {SYS_INSTR, 2'd0, 2'd0, SEL_RESTART, FORMAT_B }; default : cntrl_sigs = {ILL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_A }; // Illegal instruction endcase else // Format B, C, and D opcodes... case (op1) `OP1_ADDI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_ADD_ST , FORMAT_B }; `OP1_ANDI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_AND , FORMAT_B }; `OP1_ORI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_OR , FORMAT_B }; `OP1_XORI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_XOR , FORMAT_B }; `OP1_SLLI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_SLL , FORMAT_B }; `OP1_SLAI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_SLA , FORMAT_B }; `OP1_SRLI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_SRL , FORMAT_B }; `OP1_SRAI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_SRA , FORMAT_B }; `OP1_ROTRI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_ROTR , FORMAT_B }; `OP1_TESTEQI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_TESTEQ , FORMAT_B }; `OP1_TESTNEI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_TESTNE , FORMAT_B }; `OP1_TESTLTI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_TESTLT , FORMAT_B }; `OP1_TESTLEI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_TESTLE , FORMAT_B }; `OP1_TESTGTI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_TESTGT , FORMAT_B }; `OP1_TESTGEI : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_TESTGE , FORMAT_B }; `OP1_UPPER16 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_UPPER16, FORMAT_B }; `OP1_SHIFT16 : cntrl_sigs = {ALU_INSTR, OP_RS1, OP_IMM, A_SHIFT16, FORMAT_B }; `OP1_ADDPC : cntrl_sigs = {ALU_INSTR, OP_PC, OP_IMM, A_ADD , FORMAT_D }; `OP1_AUIPC : cntrl_sigs = {ALU_INSTR, OP_PC, OP_IMM, A_AUIPC , FORMAT_D }; `OP1_UPPER20 : cntrl_sigs = {ALU_INSTR, 2'd0, OP_IMM, A_UPPER20, FORMAT_D }; `OP1_BEQ : cntrl_sigs = {BR_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_C }; `OP1_BNE : cntrl_sigs = {BR_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_C }; `OP1_BLT : cntrl_sigs = {BR_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_C }; `OP1_BLE : cntrl_sigs = {BR_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_C }; `OP1_LOADB : cntrl_sigs = {LD_ST_INSTR,2'd0, 2'd0,{2'b01,MMU_LOADB }, FORMAT_B }; // MMU_COMMANDS are 4 bits `OP1_LOADH : cntrl_sigs = {LD_ST_INSTR,2'd0, 2'd0,{2'b01,MMU_LOADH }, FORMAT_B }; // . but the SEL field is 6 bits. `OP1_LOADW : cntrl_sigs = {LD_ST_INSTR,2'd0, 2'd0,{2'b01,MMU_LOADW }, FORMAT_B }; // . NOTE: SEL[4] is used for want_reg_write `OP1_LOADD : cntrl_sigs = {LD_ST_INSTR,2'd0, 2'd0,{2'b01,MMU_LOADD }, FORMAT_B }; `OP1_STOREB : cntrl_sigs = {LD_ST_INSTR,2'd0, 2'd0,{2'b00,MMU_STOREB}, FORMAT_C }; `OP1_STOREH : cntrl_sigs = {LD_ST_INSTR,2'd0, 2'd0,{2'b00,MMU_STOREH}, FORMAT_C }; `OP1_STOREW : cntrl_sigs = {LD_ST_INSTR,2'd0, 2'd0,{2'b00,MMU_STOREW}, FORMAT_C }; `OP1_STORED : cntrl_sigs = {LD_ST_INSTR,2'd0, 2'd0,{2'b00,MMU_STORED}, FORMAT_C }; `OP1_ENTERFUN : cntrl_sigs = {ENTER_INSTR,OP_RS1,2'd0, 6'b0 , FORMAT_B }; `OP1_EXITFUN : cntrl_sigs = {EXIT_INSTR, OP_RS1,2'd0, 6'b0 , FORMAT_B }; `OP1_CONTROL : cntrl_sigs = {CTRL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_B }; `OP1_CONTROLU : cntrl_sigs = {CTRL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_B }; `OP1_JAL : cntrl_sigs = {BR_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_D }; `OP1_JALR : cntrl_sigs = {BR_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_B }; `OP1_CSRSET : cntrl_sigs = {SYS_INSTR, OP_RS1, OP_IMM, SEL_CSR_SET , FORMAT_B }; `OP1_CSRCLR : cntrl_sigs = {SYS_INSTR, OP_RS1, OP_IMM, SEL_CSR_CLR , FORMAT_B }; `OP1_CHECKADDR : cntrl_sigs = {SYS_INSTR, 2'd0, 2'd0, SEL_CHECKADDR , FORMAT_B }; `OP1_SYSCALL : cntrl_sigs = {USER_INSTR, 2'd0, 2'd0, SEL_SYSCALL , FORMAT_B }; default : cntrl_sigs = {ILL_INSTR, 2'd0, 2'd0, 6'b0 , FORMAT_B }; // Illegal instruction endcase // Pull out the individual control signals from "cntrl_sigs"... // inst_type op_x op_y SEL Format // 0000_______00______00___000000_______00 // // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 // 0 0 0 0______0 0______0 0______0 0 0 0 0 0______0 0 // inst_type = INST_TYPE'(cntrl_sigs [15:12]); // e.g., ALU_INSTR, LD_INSTR, ... op_x = cntrl_sigs [11:10]; // OP_RS1, OP_RS2, OP_PC, OP_IMM op_y = cntrl_sigs [9:8]; // OP_RS1, OP_RS2, OP_PC, OP_IMM sel = cntrl_sigs [7:2]; // e.g., A_ADD, A_SUB, A_XOR, ... (for the ALU) format = cntrl_sigs [1:0]; // FORMAT_A, FORMAT_B, FORMAT_C, FORMAT_D // Compute the immediate value... case (format) FORMAT_A: immed64 = 0; FORMAT_B: immed64 = immed64_B; FORMAT_C: immed64 = immed64_C; FORMAT_D: immed64 = immed64_D; default: immed64 = 0; endcase // BRANCH Bxx addresses are only 36 bits; ignore upper 28 bits. branch_target = pc + immed64[35:0]; // LOADs and STOREs are only 36 bits; ignore upper 28 bits. mem_addr = immed64 [35:0] + reg1_data [35:0]; if (inst_code != 0) begin is_except_wanted = 1; exc_prevpc = pc; exc_cause = inst_code; exc_bad = 0; // These values are what is required for page-related exceptions. But if we exc_addr = pc; // had a NULL_ADDRESS or UNALIGNED exception, these values are wrong. // The Blitz-64 ISA specifically allows them to be wrong for FETCHes. end else case (inst_type) ALU_INSTR: begin regD_data = alu_out; want_reg_write = alu_produces_data_out; if (arith_exception) begin is_except_wanted = 1; exc_prevpc = pc; // Address of the offending instruction exc_cause = CAUSE_ARITHMETIC; exc_bad = inst; exc_addr = 0; end else if (bad_array_index_exception) begin is_except_wanted = 1; exc_prevpc = pc; // Address of INDEXx, the offending instruction exc_cause = CAUSE_BAD_ARRAY_INDEX; exc_bad = inst; exc_addr = 0; end else if (null_exception) begin is_except_wanted = 1; exc_prevpc = pc; // Address of NULLTEST, the offending instruction exc_cause = CAUSE_NULL_ADDRESS; exc_bad = inst; exc_addr = 0; end else if (stack_exception) begin is_except_wanted = 1; exc_prevpc = pc; exc_cause = CAUSE_STACK_OVERFLOW; exc_bad = inst; exc_addr = 0; end end EMUL_INSTR: begin is_except_wanted = 1; exc_prevpc = pc+4; // Address of the FOLLOWING instruction exc_cause = CAUSE_EMULATED; exc_bad = inst; exc_addr = 0; end ENTER_INSTR: begin if (add_overflow) begin is_except_wanted = 1; exc_prevpc = pc; // Address of the offending instruction exc_cause = CAUSE_ARITHMETIC; exc_bad = inst; exc_addr = 0; end else if (stack_overflow) begin is_except_wanted = 1; exc_prevpc = pc; exc_cause = CAUSE_STACK_OVERFLOW; exc_bad = inst; exc_addr = 0; end else begin regD_data = addxy; // A write to RegD occurs on transition from RD_WR_STATE to EXEC_STATE, unless an exception occurred want_reg_write = 1; // Set signals needed in EXEC state to direct the STORE of lr ld_st_command = MMU_STORED; mem_target_address = regSP_data[35:0] + 36'hf_ffff_fff8; // -8 with overflow ignored is_load_store = 1; mem_wr_data = regLR_data; if (mmu_result_code != 0) // Any errors will appear in the RD_WR_STATE begin is_except_wanted = 1; exc_prevpc = pc; exc_cause = mmu_result_code; exc_bad = 0; exc_addr = {28'b0 , mem_addr}; case (mmu_result_code) CAUSE_UNALIGNED_LOAD_STORE: begin exc_bad = inst; end CAUSE_NULL_ADDRESS: begin exc_bad = inst; exc_addr = 0; end // For all page-related exceptions, uss defaults (bad=0, addr=offending address) default: ; endcase end end end EXIT_INSTR: begin if (add_overflow) begin is_except_wanted = 1; exc_prevpc = pc; // Address of the offending instruction exc_cause = CAUSE_ARITHMETIC; exc_bad = inst; exc_addr = 0; end else begin regD_data = addxy; // A write to RegD occurs on transition from RD_WR_STATE to EXEC_STATE, unless an exception occurred want_reg_write = 1; // Set signals needed in EXEC state to direct the LOAD of lr ld_st_command = MMU_LOADD; mem_target_address = addxy[35:0] + 36'hf_ffff_fff8; // -8 with overflow ignored is_load_store = 1; // Check to see if this gets an exception from the MMU... // Set signals needed for the branch is_branch_taken = 1; target_address = mmu_result_data[35:0]; // Any error with the LOAD will appear in the RD_WR_STATE if (mmu_result_code != 0) begin is_except_wanted = 1; exc_prevpc = pc; exc_cause = mmu_result_code; exc_bad = 0; exc_addr = {28'b0 , mem_addr}; case (mmu_result_code) CAUSE_UNALIGNED_LOAD_STORE: begin exc_bad = inst; end CAUSE_NULL_ADDRESS: begin exc_bad = inst; exc_addr = 0; end // For all page-related exceptions, uss defaults (bad=0, addr=offending address) default: ; endcase end end end LD_ST_INSTR: begin ld_st_command = MMU_COMMAND'(sel [3:0]); // Check to see if this LOAD/STORE instruction has an exception from the MMU... if (mmu_result_code != 0) // Any errors will appear in the RD_WR_STATE begin is_except_wanted = 1; exc_prevpc = pc; // Address of the OFFENDING instruction exc_cause = mmu_result_code; exc_bad = 0; exc_addr = {28'b0 , mem_addr}; case (mmu_result_code) CAUSE_UNALIGNED_LOAD_STORE: begin exc_bad = inst; end CAUSE_NULL_ADDRESS: begin exc_bad = inst; exc_addr = 0; end // For all page-related exceptions, use defaults (bad=0, addr=offending address) // CAUSE_PAGE_ILLEGAL_ADDR: // CAUSE_PAGE_TABLE: // CAUSE_PAGE_INVALID: // CAUSE_PAGE_WRITE: // CAUSE_PAGE_FETCH: // CAUSE_PAGE_COPY_ON_WRITE: // CAUSE_PAGE_FIRST_DIRTY: default: ; endcase end // Else we are good to go... else begin // Set signals needed in EXEC state mem_target_address = mem_addr; is_load_store = 1; if (MMU_CAS == MMU_COMMAND'(sel [3:0])) begin mem_wr_data = reg3_data; // Want to redefine CAS to eliminate this IF??? mem_cas_old_data = reg2_data; end else mem_wr_data = reg2_data; // Set signals needed in READ-WRITE state regD_data = mmu_result_data; want_reg_write = sel[4]; end end BR_INSTR: begin if (op1 == `OP1_BEQ) begin if (reg1_data == reg2_data) is_branch_taken = 1; target_address = branch_target; end else if (op1 == `OP1_BNE) begin if (!(reg1_data == reg2_data)) is_branch_taken = 1; target_address = branch_target; end else if (op1 == `OP1_BLT) begin if ($signed(reg1_data) < $signed(reg2_data)) is_branch_taken = 1; target_address = branch_target; end else if (op1 == `OP1_BLE) begin if ($signed(reg1_data) <= $signed(reg2_data)) is_branch_taken = 1; target_address = branch_target; end else if (op1 == `OP1_JAL) begin want_reg_write = 1; regD_data = pc + 36'd4; // Result of + is 36 bits, so upper bits remain unchanged (i.e., 0) is_branch_taken = 1; target_address = branch_target; end else if (op1 == `OP1_JALR) begin want_reg_write = 1; regD_data = pc + 36'd4; // Result of + is 36 bits, so upper bits remain unchanged (i.e., 0) is_branch_taken = 1; target_address = reg1_data[35:0] + immed64[35:0]; // Upper 28 bits are lost end // Check to see if this BRANCH instruction is UNALIGNED or NULL ADDRESS. // Other checks, such as PAGE-related exceptions, will be checked by the // MMU and will be available later... if (is_branch_taken && (target_address [35:3] == 33'b0)) begin is_branch_taken = 0; is_except_wanted = 1; exc_prevpc = pc; exc_cause = CAUSE_NULL_ADDRESS; exc_bad = inst; exc_addr = 0; end else if (is_branch_taken && (target_address[1:0] != 2'b00)) begin is_branch_taken = 0; is_except_wanted = 1; exc_prevpc = pc; exc_cause = CAUSE_UNALIGNED_LOAD_STORE; exc_bad = inst; exc_addr = {28'b0 , target_address}; end end FP_INSTR: begin is_illegal = 1; end USER_INSTR: begin case (sel) SEL_FENCE: begin ld_st_command = MMU_FENCE; is_load_store = 1; is_fence = 1; end SEL_SYSCALL: begin is_except_wanted = 1; exc_prevpc = pc+4; // Address of the FOLLOWING instruction // For the SYSCALL Exception Cause Code, take immed-10, multiply by 8, and sign-extend as a positive... exc_cause = { 3'b0, immed64 [9:0], 3'b000 }; exc_bad = inst; exc_addr = 0; end SEL_DEBUG: begin is_except_wanted = 1; exc_prevpc = pc+4; // Address of the FOLLOWING instruction exc_cause = CAUSE_DEBUG; exc_bad = inst; exc_addr = 0; end SEL_BREAKPOINT: begin is_except_wanted = 1; exc_prevpc = pc; // Address of the BREAKPOINT instruction exc_cause = CAUSE_BREAKPOINT; exc_bad = inst; exc_addr = 0; end SEL_GETSTAT: begin want_reg_write = 1; regD_data = csr_status & 64'h0000_0000_0000_03f8; end SEL_PUTSTAT: begin data_to_csr = (csr_status & 64'hffff_ffff_ffff_fc07) | (reg1_data & 64'h0000_0000_0000_03f8); is_csrstatus_write_wanted = 1; want_csr_write = 1; end default: begin is_illegal = 1; end endcase end SYS_INSTR: begin // If we are in user mode... if (csr_status[0] == 1'b0) begin // Then signal an Illegal Instruction / Privilege Violation Exception... is_except_wanted = 1; exc_prevpc = pc; // Address of the offending instruction exc_cause = CAUSE_ILLEGAL_INSTR; exc_bad = inst; exc_addr = 0; end // Otherwise handle each instruction... else begin case (sel) SEL_RESTART: begin restart_instruction = 1; end SEL_SLEEP1: begin is_sleep = 1; end SEL_SLEEP2: begin is_sleep = 1; end SEL_SYSRET: begin is_branch_taken = 1; want_csr_copy_stat2_to_status = 1; target_address = csr_prevpc_from_CSR; // Check to see if target address is UNALIGNED or NULL ADDRESS. if (target_address [35:3] == 33'b0) begin is_except_wanted = 1; exc_prevpc = pc; exc_cause = CAUSE_NULL_ADDRESS; exc_bad = inst; exc_addr = 0; end else if (target_address[1:0] != 2'b00) begin is_except_wanted = 1; exc_prevpc = pc; exc_cause = CAUSE_UNALIGNED_LOAD_STORE; exc_bad = inst; exc_addr = {28'b0 , target_address}; end end SEL_CSR_SET: begin data_to_csr = data_from_csr | immed64; want_csr_write = 1; end SEL_CSR_CLR: begin data_to_csr = data_from_csr & ~immed64; want_csr_write = 1; end SEL_CSR_SWAP: begin data_to_csr = reg2_data; want_csr_write = 1; want_reg_write = 1; regD_data = data_from_csr; end SEL_CSR_READ: begin want_reg_write = 1; regD_data = data_from_csr; end SEL_TLBFLUSH: begin ld_st_command = MMU_TLBFLUSH; is_load_store = 1; mem_target_address = reg1_data [35:0]; end SEL_TLBCLEAR: begin ld_st_command = MMU_TLBCLEAR; is_load_store = 1; end SEL_CHECKADDR: begin case (immed16_B [2:0]) 3'd0: ld_st_command = MMU_LOADB; 3'd1: ld_st_command = MMU_LOADH; 3'd2: ld_st_command = MMU_LOADW; 3'd3: ld_st_command = MMU_LOADD; 3'd4: ld_st_command = MMU_STOREB; 3'd5: ld_st_command = MMU_STOREH; 3'd6: ld_st_command = MMU_STOREW; 3'd7: ld_st_command = MMU_STORED; endcase mem_target_address = reg1_data [35:0]; // Important in EXEC state is_load_store = 1; // . is_checkaddr = 1; // . want_reg_write = 1; // Important in READ-WRITE state // Take a look at what error code came back in the READ-WRITE state and save in the dest reg. case (mmu_result_code) CAUSE_NULL_ADDRESS: regD_data = 64'd1; CAUSE_UNALIGNED_LOAD_STORE: regD_data = 64'd2; CAUSE_PAGE_ILLEGAL_ADDR: regD_data = 64'd3; CAUSE_PAGE_TABLE: regD_data = 64'd4; CAUSE_PAGE_INVALID: regD_data = 64'd5; CAUSE_PAGE_WRITE: regD_data = 64'd6; CAUSE_PAGE_COPY_ON_WRITE: regD_data = 64'd7; CAUSE_PAGE_FIRST_DIRTY: regD_data = 64'd8; default: regD_data = 64'd0; endcase end default: begin is_illegal = 1; end endcase end end CTRL_INSTR: begin // Only CONTROLU is implemented; CONTROL is always an illegal instruction. if (op1 == `OP1_CONTROL) // ... || (op1 == `OP1_CONTROL) & (csr_status[0] == 1'b0)) // Use this later if CONTROL is implemented... begin // Then signal an Illegal Instruction / Privilege Violation Exception... is_except_wanted = 1; exc_prevpc = pc; // Address of the offending instruction exc_cause = CAUSE_ILLEGAL_INSTR; exc_bad = inst; exc_addr = 0; end // Instruction: "CONTROL Regd,____,0" digital input (e.g., from buttons and switches) else if (immed16_B == 16'h0000) begin want_reg_write = 1; regD_data = digital_input; end // Instruction: "CONTROL ____,Reg1,1" digital output (e.g., to LEDs) else if (immed16_B == 16'h0001) begin want_digital_write = 1; digital_wr_data = reg1_data; end else if (immed16_B == 16'h0002) // go into SLEEP_STATE begin is_sleep = 1; end // Instruction: "CONTROL Regd,____,3" GET SERIAL STATUS else if (immed16_B == 16'h0003) begin want_reg_write = 1; regD_data = {62'b0, ~serial_out_busy, serial_input_avail}; end // Instruction: "CONTROL Regd,____,4" READ/RECV SERIAL BYTE else if (immed16_B == 16'h0004) begin want_reg_write = 1; regD_data = serial_input_reg; clear_input_avail = 1; end // Instruction: "CONTROL ____,Reg1,5" WRITE/SEND SERIAL BYTE else if (immed16_B == 16'h0005) begin want_serial_send = 1; serial_send_data = reg1_data[7:0]; end // Instruction: "CONTROL Regd,____,6" ENABLE_KERNEL else if (immed16_B == 16'h0006) begin want_reg_write = 1; regD_data = csr_status; control_load_status_en = 1; control_load_status_data = csr_status | 64'h0000_0000_0000_0001; end // Instruction: "CONTROL ____,Reg1,7" SET_STATUS else if (immed16_B == 16'h0007) begin control_load_status_en = 1; control_load_status_data = reg1_data; end // Instruction: "CONTROL ____,Reg1,8" TLB_DEBUG else if (immed16_B == 16'h0008) begin ld_st_command = MMU_TLBDEBUG; mem_wr_data = reg1_data; // Important in EXEC state is_load_store = 1; // . regD_data = mmu_result_data; // Important in READ-WRITE state want_reg_write = 1; // . end else begin // Signal an Illegal Instruction / Privilege Violation Exception... is_except_wanted = 1; exc_prevpc = pc; // Address of the offending instruction exc_cause = CAUSE_ILLEGAL_INSTR; exc_bad = inst; exc_addr = 0; end end // ILL_INSTR: // Handled by "default case"... default: begin // Signal that exception processing is wanted... is_except_wanted = 1; exc_prevpc = pc; exc_cause = CAUSE_ILLEGAL_INSTR; exc_bad = inst; exc_addr = 0; end endcase end endmodule // EXECUTE //******************** ALU ******************** // // This module describes the ALU. It is fully combinational. // // Copied and modified from Kirk Weedman's code. // // WORKING BUT NOT EXHAUSTIVELY TESTED. // module ALU ( input wire [5:0] alu_fun, // This code tells what operation to perform input wire [1:0] op_x_in, // This code selects the "x" input from reg1/reg2/immed/pc input wire [1:0] op_y_in, // This code selects the "y" input from reg1/reg2/immed/pc input wire [63:0] reg1_in, // Input values input wire [63:0] reg2_in, input wire [63:0] reg3_in, input wire [63:0] imm_in, input wire [35:0] pc_in, input wire [3:0] regD_addr, input wire [35:0] stack_limit, output logic [63:0] data_out, // Output result output logic alu_produces_data_out, // Is there a result (or just exception checking?) output logic arith_exception_out, // TRUE = exception detected, FALSE = no exception output logic bad_array_index_exception_out, // TRUE = exception detected, FALSE = no exception output logic null_exception_out, // TRUE = exception detected, FALSE = no exception output logic stack_exception_out // TRUE = exception detected, FALSE = no exception ); logic [63:0] mux_x; logic [63:0] mux_y; logic [63:0] mulxy64; logic [127:0] mulxy128; logic mulxy_overflow; logic [63:0] muladd; logic muladd_overflow; logic [63:0] pos_reg1_in; logic [63:0] pos_reg2_in; logic [63:0] div_xy; logic [63:0] rem_xy; logic div_overflow; logic [63:0] addxy, addxyz, subxy; logic add_overflow; logic sub_overflow; logic stack_overflow; logic [127:0] sl0; logic [127:0] sl1; logic sla_overflow; logic [31:0] alnh; logic [63:0] alnw; logic [127:0] alnd; logic [63:0] inj1h_R1mask, inj1h_R2data; logic [63:0] inj1w_R1mask, inj1w_R2data; logic [127:0] inj1d_R1mask, inj1d_R2data; logic [15:0] inj2h_R1mask; logic [31:0] inj2h_R2data; logic [31:0] inj2w_R1mask; logic [63:0] inj2w_R2data; logic [63:0] inj2d_R1mask; logic [127:0] inj2d_R2data; logic [127:0] rotr64_shift; logic [63:0] rotr64; logic [63:0] current_array_size; logic [63:0] max_array_size; always_comb begin case(op_x_in) OP_RS1: mux_x = reg1_in; OP_RS2: mux_x = reg2_in; OP_IMM: mux_x = imm_in; OP_PC: mux_x = { 28'b0, pc_in }; endcase end always_comb begin case(op_y_in) OP_RS1: mux_y = reg1_in; OP_RS2: mux_y = reg2_in; OP_IMM: mux_y = imm_in; OP_PC: mux_y = { 28'b0, pc_in }; endcase end assign addxy = mux_x + mux_y; assign addxyz = reg1_in + reg2_in + reg3_in; assign subxy = mux_x - mux_y; // Determine if overflow occurs on "x + y". assign add_overflow = (!(mux_x[63] ^ mux_y[63])) & (addxy[63] ^ mux_x[63]); // Determine if overflow occurs on "x - y". assign sub_overflow = (!(mux_x[63] ^ !mux_y[63])) & (subxy[63] ^ mux_x[63]); // Determine if a Stack Overflow Exception occurs on "x + y". assign stack_overflow = ((regD_addr == 4'd15) & (addxy < stack_limit)); // Are these MULTIPLYs causing big setup time violations??? // assign mulxy128 = mux_x * mux_y; // This doesn't work since * is unsigned assign mulxy128 = ({{64{mux_x[63]}}, mux_x} * {{64{mux_y[63]}}, mux_y}); assign mulxy64 = mulxy128[63:0]; // If the sign 64 sign bits are the same as bit [63] -- i.e., all zeros or all ones -- then no overflow... assign mulxy_overflow = ~(({65{1'b0}} == mulxy128[127:63]) | ({65{1'b1}} == mulxy128[127:63])); /********** // Compute truncated division, using division on positive numbers assign pos_reg1_in = reg1_in[63] ? (!reg1_in + 1) : reg1_in; assign pos_reg2_in = reg2_in[63] ? (!reg2_in + 1) : reg2_in; assign div_xy = pos_reg1_in / pos_reg2_in; // assign rem_xy = pos_reg1_in - (pos_reg2_in * div_xy); // assign rem_xy = pos_reg1_in % pos_reg2_in; assign rem_xy = 0; assign div_overflow = (reg2_in == 0) | ((reg1_in == 64'h8000_0000_0000_0000) & (reg2_in == 64'hffff_ffff_ffff_ffff)); **********/ assign muladd = mulxy64 + reg3_in; assign muladd_overflow = (!(mulxy64[63] ^ reg3_in[63])) & (muladd[63] ^ mulxy64[63]); assign sl0 = {{64{1'b0}}, mux_x} << mux_y [5:0]; assign sl1 = {{64{1'b1}}, mux_x} << mux_y [5:0]; assign sla_overflow = sl0[63] ? (sl1[127:64] != {64{1'b1}}) : (sl0[127:64] != {64{1'b0}}); assign rotr64_shift = {mux_x, mux_x} >> mux_y[5:0]; // only lower 6 bits are needed as it repeats every 64 bits of rotating assign rotr64 = rotr64_shift[63:0]; assign alnh = {mux_x[15:0], mux_y[15:0]} << {reg3_in[0], 3'b000}; assign alnw = {mux_x[31:0], mux_y[31:0]} << {reg3_in[1:0], 3'b000}; assign alnd = {mux_x[63:0], mux_y[63:0]} << {reg3_in[2:0], 3'b000}; assign inj1h_R1mask = $signed({{48{1'b1}}, 16'b0}) >>> {reg3_in[0],3'b000}; // {48 bits of 1,16 bits of 0} >>> reg3_in*8 assign inj1h_R2data = {48'b0, mux_y[15:0]} >> {reg3_in[0],3'b000}; // {48 bits of 0,mux_y[15:0]} >>> reg3_in*8 assign inj1w_R1mask = $signed({{32{1'b1}}, 32'b0}) >>> {reg3_in[1:0],3'b000}; // {32 bits of 1,32 bits of 0} >>> reg3_in*8 assign inj1w_R2data = {32'b0, mux_y[31:0]} >> {reg3_in[1:0],3'b000}; // {32 bits of 0, mux_y[31:0]} >>> reg3_in*8 assign inj1d_R1mask = $signed({{64{1'b1}},64'b0}) >>> {reg3_in[2:0],3'b000}; // {64 bits of 1,64 bits of 0} >>> reg3_in*8 assign inj1d_R2data = {64'b0, mux_y} >> {reg3_in[2:0],3'b000}; // {64 bits of 0, mux_y} >>> reg3_in*8 assign inj2h_R1mask = {16{1'b1}} >> {reg3_in[0],3'b000}; // {16 bits of 1} >> reg3_in*8 assign inj2h_R2data = {mux_y[15:0],16'b0} >> {reg3_in[0],3'b000}; // {mux_y[15:0], 16'b0} << reg3_in*8 assign inj2w_R1mask = {32{1'b1}} >> {reg3_in[1:0],3'b000}; // {32 bits of 1} >> reg3_in*8 assign inj2w_R2data = {mux_y[31:0],32'b0} >> {reg3_in[1:0],3'b000}; // {mux_y[31:0],32'b0} >> reg3_in*8 assign inj2d_R1mask = {64{1'b1}} >> {reg3_in[2:0],3'b000}; // {64 bits 1} >> reg3_in*8 assign inj2d_R2data = {mux_y,64'b0} >> {reg3_in[2:0],3'b000}; // {mux_y[63:0], 64'b0} >> reg3_in*8 always_comb begin data_out = 1'b0; arith_exception_out = FALSE; bad_array_index_exception_out = FALSE; null_exception_out = FALSE; stack_exception_out = FALSE; alu_produces_data_out = TRUE; current_array_size = { 32'h0000_0000 , reg2_in [31:0] }; max_array_size = { 32'h0000_0000 , reg2_in [63:32] }; case (alu_fun) A_MULADD: begin data_out = muladd; arith_exception_out = mulxy_overflow | muladd_overflow; end A_MULADDU: begin data_out = muladd; end /*** A_DIV: begin case ( { reg1_in [63] , reg2_in [63] } ) 2'b00: data_out = div_xy; 2'b10: data_out = !div_xy + 1; 2'b01: data_out = !div_xy + 1; 2'b11: data_out = div_xy; endcase arith_exception_out = div_overflow; end A_REM: begin case ( { reg1_in [63] , reg2_in [63] } ) 2'b00: data_out = rem_xy; 2'b10: data_out = !rem_xy + 1; 2'b01: data_out = rem_xy; 2'b11: data_out = !rem_xy + 1; endcase arith_exception_out = div_overflow; end ***/ A_ADD: begin data_out = addxy; arith_exception_out = add_overflow; end A_ADD_ST: begin data_out = addxy; arith_exception_out = add_overflow; stack_exception_out = stack_overflow; end A_SUB: begin data_out = subxy; arith_exception_out = sub_overflow; end A_ADD3: data_out = addxyz; A_ADDOK: data_out = !add_overflow; A_AND: data_out = mux_x & mux_y; A_OR: data_out = mux_x | mux_y; A_XOR: data_out = mux_x ^ mux_y; A_IND0: begin data_out = reg1_in + 64'd8; // addr of a[0] bad_array_index_exception_out = (reg3_in [63] == 1'b1) // reg3 < 0 | (reg3_in >= current_array_size) // reg3 too large | (max_array_size == 0); // array is uninitialized end A_IND1: begin data_out = reg1_in + 64'd8 + reg3_in; // element size is 1 bad_array_index_exception_out = (reg3_in [63] == 1'b1) // reg3 < 0 | (reg3_in >= current_array_size) // reg3 too large | (max_array_size == 0); // array is uninitialized end A_IND2: begin data_out = reg1_in + 64'd8 + (reg3_in << 1); // multiply by 2 bad_array_index_exception_out = (reg3_in [63] == 1'b1) // reg3 < 0 | (reg3_in >= current_array_size) // reg3 too large | (max_array_size == 0); // array is uninitialized end A_IND4: begin data_out = reg1_in + 64'd8 + (reg3_in << 2); // multiply by 4 bad_array_index_exception_out = (reg3_in [63] == 1'b1) // reg3 < 0 | (reg3_in >= current_array_size) // reg3 too large | (max_array_size == 0); // array is uninitialized end A_IND8: begin data_out = reg1_in + 64'd8 + (reg3_in << 3); // multiply by 8 bad_array_index_exception_out = (reg3_in [63] == 1'b1) // reg3 < 0 | (reg3_in >= current_array_size) // reg3 too large | (max_array_size == 0); // array is uninitialized end A_IND16: begin data_out = reg1_in + 64'd8 + (reg3_in << 4); // multiply by 16 bad_array_index_exception_out = (reg3_in [63] == 1'b1) // reg3 < 0 | (reg3_in >= current_array_size) // reg3 too large | (max_array_size == 0); // array is uninitialized end A_IND24: begin data_out = reg1_in + 64'd8 + (reg3_in << 4) + (reg3_in << 3); // multiply by 24 = (r*16 + r*8) bad_array_index_exception_out = (reg3_in [63] == 1'b1) // reg3 < 0 | (reg3_in >= current_array_size) // reg3 too large | (max_array_size == 0); // array is uninitialized end A_IND32: begin data_out = reg1_in + 64'd8 + (reg3_in << 5); // multiply by 32 bad_array_index_exception_out = (reg3_in [63] == 1'b1) // reg3 < 0 | (reg3_in >= current_array_size) // reg3 too large | (max_array_size == 0); // array is uninitialized end A_NULLTEST: begin null_exception_out = (mux_x [35:3] == 33'd0); alu_produces_data_out = FALSE; end A_CHB: begin arith_exception_out = !(($signed(mux_x) >= $signed(-128)) && ($signed(mux_x) <= $signed(127))); alu_produces_data_out = FALSE; end A_CHH: begin arith_exception_out = !(($signed(mux_x) >= $signed(-32768)) && ($signed(mux_x) <= $signed(32767))); alu_produces_data_out = FALSE; end A_CHW: begin arith_exception_out = !(($signed(mux_x) >= $signed(-2147483648)) && ($signed(mux_x) <= $signed(2147483647))); alu_produces_data_out = FALSE; end A_INDH: data_out = { mux_x [6*8 +: 8], // For instruction ENDIANH mux_x [7*8 +: 8], mux_x [4*8 +: 8], mux_x [5*8 +: 8], mux_x [2*8 +: 8], mux_x [3*8 +: 8], mux_x [0*8 +: 8], mux_x [1*8 +: 8] }; A_INDW: data_out = { mux_x [4*8 +: 8], // For iInstruction ENDIANW mux_x [5*8 +: 8], mux_x [6*8 +: 8], mux_x [7*8 +: 8], mux_x [0*8 +: 8], mux_x [1*8 +: 8], mux_x [2*8 +: 8], mux_x [3*8 +: 8] }; A_INDD: data_out = { mux_x [0*8 +: 8], // For instruction ENDIAND mux_x [1*8 +: 8], mux_x [2*8 +: 8], mux_x [3*8 +: 8], mux_x [4*8 +: 8], mux_x [5*8 +: 8], mux_x [6*8 +: 8], mux_x [7*8 +: 8] }; A_EXTB: data_out = { {56{mux_x[ 7]}}, mux_x[ 7:0] }; A_EXTH: data_out = { {48{mux_x[15]}}, mux_x[15:0] }; A_EXTW: data_out = { {32{mux_x[31]}}, mux_x[31:0] }; A_SLL: begin data_out = sl0[63:0]; if (mux_y[63:6]) // If shift value too large arith_exception_out = TRUE; end A_SLA: begin data_out = sl0[63:0]; if (mux_y[63:6] | sla_overflow) // If shift value too large or overflow arith_exception_out = TRUE; end A_SRL: begin data_out = mux_x >> mux_y[5:0]; if (mux_y[63:6]) // If shift value too large arith_exception_out = TRUE; end A_SRA: begin data_out = $signed(mux_x) >>> mux_y[5:0]; if (mux_y[63:6]) // If shift value too large arith_exception_out = TRUE; end A_ROTR: data_out = rotr64; A_TESTEQ: data_out = ($signed(mux_x) == $signed(mux_y)) ? 'd1 : 'd0; A_TESTNE: data_out = ($signed(mux_x) != $signed(mux_y)) ? 'd1 : 'd0; A_TESTLT: data_out = ($signed(mux_x) < $signed(mux_y)) ? 'd1 : 'd0; A_TESTLE: data_out = ($signed(mux_x) <= $signed(mux_y)) ? 'd1 : 'd0; A_TESTGT: data_out = ($signed(mux_x) > $signed(mux_y)) ? 'd1 : 'd0; A_TESTGE: data_out = ($signed(mux_x) >= $signed(mux_y)) ? 'd1 : 'd0; A_ALNH: data_out = {{48{alnh[31]}}, alnh[31:16] }; A_ALNW: data_out = {{32{alnw[63]}}, alnw[63:32] }; A_ALND: data_out = alnd[127:64] ; A_INJ1H: data_out = (inj1h_R1mask & mux_x) | inj1h_R2data; A_INJ2H: data_out = {mux_x[63:16], (inj2h_R1mask[15:0] & mux_x[15:0]) | inj2h_R2data[15:0]}; A_INJ1W: data_out = (inj1w_R1mask & mux_x) | inj1w_R2data; A_INJ2W: data_out = {mux_x[63:32], (inj2w_R1mask[31:0] & mux_x[31:0]) | inj2w_R2data[31:0]}; A_INJ1D: data_out = (inj1d_R1mask[63:0] & mux_x) | inj1d_R2data[63:0]; A_INJ2D: data_out = (inj2d_R1mask & mux_x) | inj2d_R2data[63:0]; A_UPPER16: data_out = mux_x + {{32{mux_y[15]}},mux_y[15:0],16'b0}; A_SHIFT16: data_out = {mux_x[47:16],mux_y[15:0],16'b0}; A_UPPER20: data_out = {{28{mux_y[19]}},mux_y[19:0],16'b0}; A_AUIPC: data_out = mux_x + {{28{mux_y[19]}},mux_y[19:0],16'b0}; default: data_out = 1'b0; endcase end endmodule // ALU //******************** REGS ******************** // // This module describes the 16 registers (r0...r15); with r0 always == zero. // // On the pos-edge of the clock... // If "reset_in" is high, then the regs are set to zero. // Otherwise, if "reg_write_en" is high, one reg will be updated. // Three regs may be read, and this reading is not clocked. // // The "reset_in" input is not strictly necessary, since the ISA does // not specify that the regs must be zeroed upon RESET, but we do it anyway. // // Copied and modified from Kirk Weedman's code. // module REGS ( input wire clock, input wire clock_enable, input wire reset_in, input wire mmu_ready, input wire [3:0] reg1_addr, input wire [3:0] reg2_addr, input wire [3:0] reg3_addr, output wire [63:0] reg1_data, output wire [63:0] reg2_data, output wire [63:0] reg3_data, output wire [63:0] regLR_data, output wire [63:0] regSP_data, input wire [3:0] regD_addr, input wire [63:0] regD_data, input wire reg_write_en ); reg [15:0] [63:0] regs; assign regs[0] = 0; assign reg1_data = (reg1_addr == 1'b0) ? 64'b0 : regs[reg1_addr]; assign reg2_data = (reg2_addr == 1'b0) ? 64'b0 : regs[reg2_addr]; assign reg3_data = (reg3_addr == 1'b0) ? 64'b0 : regs[reg3_addr]; assign regLR_data = regs[14]; assign regSP_data = regs[15]; genvar k; generate for (k = 1; k < 16; k++) begin : uselessName always_ff @(posedge clock) if (clock_enable) begin if (reset_in) regs[k] <= 1'b0; else if (reg_write_en & (regD_addr == k) & mmu_ready) regs[k] <= regD_data; end end endgenerate endmodule // REGS //******************** CSRREGS ******************** // // This module describes the 16 CSR registers. // // On the pos-edge of the clock... // If "reset_in" is high, then the regs are set to their initial values. // Otherwise, if "csr_write_en" is high, one reg will be updated. // The reg selected by "csr_addr" is always output. This read is not clocked. // // The "csr_timer" is decremented on every clock cycle. // // "csr_instr" and "csr_cycle" are maintained externally as "inst_count" and // "cycle_count" and simply copied to the registers. // `define CSR_VERSION 4'd0 `define CSR_PROD 4'd1 `define CSR_CORE 4'd2 `define CSR_INSTR 4'd3 `define CSR_CYCLE 4'd4 `define CSR_TIMER 4'd5 `define CSR_STATUS 4'd6 `define CSR_STAT2 4'd7 `define CSR_TRAPVEC 4'd8 `define CSR_PGTABLE 4'd9 `define CSR_PREVPC 4'd10 `define CSR_CAUSE 4'd11 `define CSR_BAD 4'd12 `define CSR_ADDR 4'd13 `define CSR_PTR 4'd14 `define CSR_TEMP 4'd15 module CSRREGS ( input wire clock, input wire clock_enable, input wire reset_in, input wire mmu_ready, input wire [63:0] cycle_count_in, input wire [63:0] inst_count_in, input wire [3:0] csr_addr, input wire [63:0] csr_data_in, output logic [63:0] csr_data_out, output logic [63:0] csr_status_out, output logic csr_timer_is_neg, output logic [63:0] current_csr_pgtable, output logic current_single_step, // 0=off, 1=single step enabled output logic current_interrupts_enabled, // 0=Disabled, 1=Enabled output logic current_mode, // 0=User, 1=Kernel input wire control_load_status_en, // For CONTROL ENABLE_KERNEL / DISABLE_KERNEL input wire [63:0] control_load_status_data, input wire csr_write_en, input wire is_csrstatus_write_wanted, input wire want_csr_copy_stat2_to_status, output logic [35:0] csr_prevpc_from_CSR, output logic [35:0] csr_trapvec_from_CSR, input wire csr_exception_en, input wire [63:0] exc_prevpc, input wire [15:0] exc_cause, input wire [63:0] exc_bad, input wire [63:0] exc_addr ); reg [15:0] [63:0] regs; always_ff @(posedge clock) if (clock_enable) begin if (reset_in) begin regs[`CSR_VERSION] <= 64'd0; // read-only regs: 0...4; these slots are ignored. regs[`CSR_PROD] <= 64'd0; // . regs[`CSR_CORE] <= 64'd0; // . regs[`CSR_INSTR] <= 64'd0; // . regs[`CSR_CYCLE] <= 64'd0; // . regs[`CSR_TIMER] <= 64'd0; regs[`CSR_STATUS] <= 64'h0000_0000_0000_0001; // Initial csr_status has MODE=Kernel; regs[`CSR_STAT2] <= 64'd0; regs[`CSR_TRAPVEC] <= 64'd0; regs[`CSR_PGTABLE] <= 64'd0; regs[`CSR_PREVPC] <= 64'd0; regs[`CSR_CAUSE] <= 64'd0; regs[`CSR_BAD] <= 64'd0; regs[`CSR_ADDR] <= 64'd0; regs[`CSR_PTR] <= 64'd0; regs[`CSR_TEMP] <= 64'd0; end else if (mmu_ready) begin // If we have a CONTROL instruction that sets csr_status (debugging Verilog)... if (control_load_status_en) regs[`CSR_STATUS] <= control_load_status_data; // If we are doing an exception, then... else if (csr_exception_en) begin regs[`CSR_STAT2] <= regs[`CSR_STATUS]; // Set csr_stat2 regs[`CSR_PREVPC] <= exc_prevpc; // Set csr_prevpc regs[`CSR_CAUSE] <= { 48'b0, exc_cause }; // Set csr_cause regs[`CSR_BAD] <= exc_bad; // Set csr_bad regs[`CSR_ADDR] <= exc_addr; // Set csr_addr // Set csr_status: Set KernelMode [bit 0], clear Interrupts Enabled and SingleStep [bits 1,2] regs[`CSR_STATUS] <= (regs[`CSR_STATUS] | 64'h0000_0000_0000_0001) & ~64'h0000_0000_0000_0006; end // Otherwise, if we need to write to csr_status... else if ((csr_write_en) & (is_csrstatus_write_wanted)) regs[`CSR_STATUS] <= csr_data_in; // Otherwise, if we need to write to a CSR reg... else if ((csr_write_en) & (!is_csrstatus_write_wanted)) regs[csr_addr] <= csr_data_in; // Okay to write to read-only regs; we just ignore their valaues // Otherwise, if this is a SYSRET instruction... else if (want_csr_copy_stat2_to_status) regs[`CSR_STATUS] <= regs[`CSR_STAT2] ; // If we did not write to csr_timer, then decrement it... if (control_load_status_en | csr_exception_en | is_csrstatus_write_wanted | (!csr_write_en) | (csr_addr != `CSR_TIMER)) regs[`CSR_TIMER] <= regs[`CSR_TIMER] - 1; end end always_comb begin csr_status_out = { regs[`CSR_STATUS][63:28], 18'b0 , regs[`CSR_STATUS][9:0] }; current_single_step = regs[`CSR_STATUS][2]; current_interrupts_enabled = regs[`CSR_STATUS][1]; current_mode = regs[`CSR_STATUS][0]; current_csr_pgtable = regs[`CSR_PGTABLE]; csr_timer_is_neg = regs[`CSR_TIMER][63]; csr_prevpc_from_CSR = regs[`CSR_PREVPC][35:0]; csr_trapvec_from_CSR = regs[`CSR_TRAPVEC][35:0]; case (csr_addr) `CSR_VERSION: csr_data_out = VERSION_DATA; // Read-only reg 0 `CSR_PROD: csr_data_out = PRODUCT_ID; // Read-only reg 1 `CSR_CORE: csr_data_out = CORE_ZERO; // Read-only reg 2 `CSR_INSTR: csr_data_out = inst_count_in; // Read-only reg 3 `CSR_CYCLE: csr_data_out = cycle_count_in; // Read-only reg 4 `CSR_STATUS: csr_data_out = csr_status_out; default: csr_data_out = regs[csr_addr]; endcase end endmodule // CSRREGS //******************** EXTRACTOR ******************** // // This module is purely compbinational. // // It takes a doubleword, an address within the doubleword (that is, the last 3 bits // of some desired address), and the desired size (byte/halfword/word/doubleword). // // It extracts a byte/halfword/word/doubleword, sign-extends it, and returns // it as a 64-bit value. // module EXTRACTOR ( input wire [63:0] data_in, input wire [2:0] addr, input wire [1:0] size, // e.g., SZ_BYTE, SZ_HALFWORD, SZ_WORD, SZ_DOUBLEWORD output logic [63:0] data_out ); logic [ 7:0] byte_out; logic [15:0] halfword_out; logic [31:0] word_out; always_comb begin byte_out = 0; halfword_out = 0; word_out = 0; data_out = 0; case (addr[2:0]) // Look at the LSB 3 bits to determine which byte. 3'b000: byte_out = data_in [63:56]; 3'b001: byte_out = data_in [55:48]; 3'b010: byte_out = data_in [47:40]; 3'b011: byte_out = data_in [39:32]; 3'b100: byte_out = data_in [31:24]; 3'b101: byte_out = data_in [23:16]; 3'b110: byte_out = data_in [15:8]; 3'b111: byte_out = data_in [7:0]; endcase case (addr[2:1]) // Ignore the LSB bit. 2'b00: halfword_out = data_in [63:48]; 2'b01: halfword_out = data_in [47:32]; 2'b10: halfword_out = data_in [31:16]; 2'b11: halfword_out = data_in [15:0]; endcase case (addr[2]) // Ignore the LSB 2 bits. 1'b0: word_out = data_in [63:32]; 1'b1: word_out = data_in [31:0]; endcase case (size) SZ_BYTE[1:0]: data_out = { {56{byte_out [7]}}, byte_out [7:0]}; SZ_HALFWORD[1:0]: data_out = { {48{halfword_out [15]}}, halfword_out [15:0]}; SZ_WORD[1:0]: data_out = { {32{word_out [31]}}, word_out [31:0]}; SZ_DOUBLEWORD[1:0]: data_out = data_in; endcase end endmodule // EXTRACTOR //******************** INJECTOR ******************** // // This module is purely compbinational. It injects a 8, 16, 32, or 64 bit quantity // into the old_dword and returns the modified version. // // It takes the old doubleword, an position within that doubleword (that is, the last 3 bits // of some desired address), the desired size (byte/halfword/word/doubleword), and the // new_data to insert. The new_data to be injected is a byte, halfword, word, or doubleword. // The value to be injected comes from the lower bits of new_data, and the upper bits // are ignored. // module INJECTOR ( input logic [63:0] old_dword_in, input logic [63:0] new_data_in, input logic [2:0] addr_in, input logic [1:0] size_in, // e.g., SZ_BYTE, SZ_HALFWORD, SZ_WORD, SZ_DOUBLEWORD output wire [63:0] new_dword_out ); logic [63:0] result; always_comb begin case (size_in) SZ_BYTE [1:0]: case (addr_in[2:0]) // Look at the LSB 3 bits to determine which byte 3'b000: result = { new_data_in[7:0], old_dword_in [55:0] }; 3'b001: result = { old_dword_in [63:56], new_data_in[7:0], old_dword_in [47:0] }; 3'b010: result = { old_dword_in [63:48], new_data_in[7:0], old_dword_in [39:0] }; 3'b011: result = { old_dword_in [63:40], new_data_in[7:0], old_dword_in [31:0] }; 3'b100: result = { old_dword_in [63:32], new_data_in[7:0], old_dword_in [23:0] }; 3'b101: result = { old_dword_in [63:24], new_data_in[7:0], old_dword_in [15:0] }; 3'b110: result = { old_dword_in [63:16], new_data_in[7:0], old_dword_in [7 :0] }; 3'b111: result = { old_dword_in [63:8 ], new_data_in[7:0] }; endcase SZ_HALFWORD [1:0]: case (addr_in[2:1]) // Ignore the LSB bit 2'b00: result = { new_data_in[15:0], old_dword_in [47:0] }; 2'b01: result = { old_dword_in [63:48], new_data_in[15:0], old_dword_in [31:0] }; 2'b10: result = { old_dword_in [63:32], new_data_in[15:0], old_dword_in [15:0] }; 2'b11: result = { old_dword_in [63:16], new_data_in[15:0] }; endcase SZ_WORD [1:0]: case (addr_in[2]) // Ignore the LSB 2 bits 1'b0: result = { new_data_in[31:0], old_dword_in [31:0] }; 1'b1: result = { old_dword_in [63:32], new_data_in[31:0] }; endcase SZ_DOUBLEWORD [1:0]: result = new_data_in; endcase end assign new_dword_out = result; endmodule // INJECTOR //******************** RAM_MEMORY ******************** // // This module describes the main memory. // // The read and write are separate channels. // The write occurs on the rising edge of the clock. // The read occurs on the rising edge of the clock. // // All accesses are in blocks of 64 bits. An update to an // individual byte will require a READ followed by a WRITE. // // The low-order 3 bits of the address are ignored. // localparam RAM_START = 36'h0_0000_0000; localparam RAM_MEMORY_SIZE_IN_DWORDS = 32768; // At 8 bytes per, this is 256 KiBytes. module RAM_MEMORY ( input logic clock, input logic clock_enable, input logic [35:0] addr, output logic [63:0] data_out, input logic write_enab, input logic [63:0] data_in ); logic [63:0] mem [RAM_MEMORY_SIZE_IN_DWORDS:0]; always_ff @(posedge clock) if (clock_enable) begin if (write_enab) mem [addr[35:3]] <= data_in; // Ignore 3 LSB bits of address. data_out <= mem [addr[35:3]]; // Ignore 3 LSB bits of address. end endmodule // RAM_MEMORY //******************** ROM_MEMORY ******************** // // This module describes the ROM memory. // // All reads are in doublewords of 64 bits. The input address // is taken "mod 8", i.e., the low-order 3 bits are ignored. // // If asked for an address outside of the valid range, this module returns 0. // // This module delays the output by one clock cycle, to match the // timing of the RAM module. // localparam ROM_START = 36'h4_0000_0000; localparam ROM_MEMORY_SIZE_IN_DWORDS = 32768; // At 8 bytes per, this is 256 KiBytes. module ROM_MEMORY ( input logic [35:0] addr, output logic [63:0] data_val ); always_comb case (addr & ~36'b000111) /***** It's the bitch.s // The following statements were generated by the "hexify" tool // from "Bitch.exe" on Tue Dec 12 16:08:33 2023 36'h400000000: data_val = 64'h01000000_01000000; 36'h400000008: data_val = 64'h170004c1_00380180; 36'h400000010: data_val = 64'h01000000_01000000; 36'h400000018: data_val = 64'h01000000_01000000; 36'h400000020: data_val = 64'h01000000_01000000; 36'h400000028: data_val = 64'h01000000_01000000; 36'h400000030: data_val = 64'h19fffe80_01000000; 36'h400000038: data_val = 64'h01000000_01000000; 36'h400000040: data_val = 64'h01000000_01000000; 36'h400000048: data_val = 64'h01000000_01000000; 36'h400000050: data_val = 64'h01000000_04787801; 36'h400000058: data_val = 64'h25000110_25000200; *****/ // The following statements were generated by the "hexify" tool // from "MBBooter.o" on Mon Dec 11 15:28:20 2023 36'h400000000: data_val = 64'h1400004f_040000ff; 36'h400000008: data_val = 64'h17000a01_1900474e; 36'h400000010: data_val = 64'h17003a41_00380180; 36'h400000018: data_val = 64'h1900638e_03000037; 36'h400000020: data_val = 64'h1900630e_00010731; 36'h400000028: data_val = 64'h14ffffc8_04ff6088; 36'h400000030: data_val = 64'h13001810_17003381; 36'h400000038: data_val = 64'h1900448e_19fffc40; 36'h400000040: data_val = 64'h03000074_03000035; 36'h400000048: data_val = 64'h03000037_04000006; 36'h400000050: data_val = 64'h19005d8e_1f000150; 36'h400000058: data_val = 64'h01000155_01ffff44; 36'h400000060: data_val = 64'h12fff400_19005c4e; 36'h400000068: data_val = 64'h25000160_10001060; 36'h400000070: data_val = 64'h17002d11_190040ce; 36'h400000078: data_val = 64'h19fff880_17002aa1; 36'h400000080: data_val = 64'h1900400e_15636f01; 36'h400000088: data_val = 64'h166c6411_16626f11; 36'h400000090: data_val = 64'h046f7411_14000042; 36'h400000098: data_val = 64'h04000022_04000003; 36'h4000000a0: data_val = 64'h04000004_1a000070; 36'h4000000a8: data_val = 64'h0a2a2a2a_2a2a2042; 36'h4000000b0: data_val = 64'h4f4f5420_50524f47; 36'h4000000b8: data_val = 64'h52414d20_284d4242; 36'h4000000c0: data_val = 64'h6f6f7465_722e7329; 36'h4000000c8: data_val = 64'h202a2a2a_2a2a0a20; 36'h4000000d0: data_val = 64'h2020456e_74657220; 36'h4000000d8: data_val = 64'h64617461_20696e20; 36'h4000000e0: data_val = 64'h74686973_20666f72; 36'h4000000e8: data_val = 64'h6d61743a_0a202020; 36'h4000000f0: data_val = 64'h20203820_62797465; 36'h4000000f8: data_val = 64'h733a204e_3d6e756d; 36'h400000100: data_val = 64'h62657220_6f662064; 36'h400000108: data_val = 64'h61746120_62797465; 36'h400000110: data_val = 64'h730a2020_20202038; 36'h400000118: data_val = 64'h20627974_65733a20; 36'h400000120: data_val = 64'h413d6164_64726573; 36'h400000128: data_val = 64'h73206174_20776869; 36'h400000130: data_val = 64'h63682074_6f20706c; 36'h400000138: data_val = 64'h61636520_74686520; 36'h400000140: data_val = 64'h64617461_20627974; 36'h400000148: data_val = 64'h65732028_6e6f726d; 36'h400000150: data_val = 64'h616c6c79_20303030; 36'h400000158: data_val = 64'h30303030_30303030; 36'h400000160: data_val = 64'h30303030_38290a20; 36'h400000168: data_val = 64'h20202020_4e206279; 36'h400000170: data_val = 64'h7465733a_20446174; 36'h400000178: data_val = 64'h61206279_7465730a; 36'h400000180: data_val = 64'h20202020_20312062; 36'h400000188: data_val = 64'h7974653a_20204368; 36'h400000190: data_val = 64'h65636b73_756d0a20; 36'h400000198: data_val = 64'h2020456e_74657220; 36'h4000001a0: data_val = 64'h616c6c20_62797465; 36'h4000001a8: data_val = 64'h73206173_20322068; 36'h4000001b0: data_val = 64'h65782063_68617273; 36'h4000001b8: data_val = 64'h3b206361_73652069; 36'h4000001c0: data_val = 64'h73206e6f_74207369; 36'h4000001c8: data_val = 64'h676e6966_6963616e; 36'h4000001d0: data_val = 64'h743b2073_70616365; 36'h4000001d8: data_val = 64'h2c205c6e_2c205c72; 36'h4000001e0: data_val = 64'h2c20616e_64205c74; 36'h4000001e8: data_val = 64'h2069676e_6f726564; 36'h4000001f0: data_val = 64'h2e0a2020_20546865; 36'h4000001f8: data_val = 64'h20636865_636b7375; 36'h400000200: data_val = 64'h6d206973_20746865; 36'h400000208: data_val = 64'h20584f52_206f6620; 36'h400000210: data_val = 64'h616c6c20_4e206461; 36'h400000218: data_val = 64'h74612062_79746573; 36'h400000220: data_val = 64'h2e204120_6d69736d; 36'h400000228: data_val = 64'h61746368_20726573; 36'h400000230: data_val = 64'h756c7473_20696e20; 36'h400000238: data_val = 64'h616e2065_72726f72; 36'h400000240: data_val = 64'h206d6573_73616765; 36'h400000248: data_val = 64'h2e0a2020_20446174; 36'h400000250: data_val = 64'h61206973_20706c61; 36'h400000258: data_val = 64'h63656420_61742041; 36'h400000260: data_val = 64'h2c20666f_6c6c6f77; 36'h400000268: data_val = 64'h65642062_79204a55; 36'h400000270: data_val = 64'h4d502074_6f206164; 36'h400000278: data_val = 64'h64726573_7320412e; 36'h400000280: data_val = 64'h20496620_70726f62; 36'h400000288: data_val = 64'h6c656d73_2c207065; 36'h400000290: data_val = 64'h72666f72_6d205245; 36'h400000298: data_val = 64'h53544152_5420616e; 36'h4000002a0: data_val = 64'h64207472_79206167; 36'h4000002a8: data_val = 64'h61696e2e_0a202020; 36'h4000002b0: data_val = 64'h20202020_4578616d; 36'h4000002b8: data_val = 64'h706c6520_696e7075; 36'h4000002c0: data_val = 64'h743a0a20_20202020; 36'h4000002c8: data_val = 64'h20202020_30303030; 36'h4000002d0: data_val = 64'h30303030_30303030; 36'h4000002d8: data_val = 64'h30303034_0a202020; 36'h4000002e0: data_val = 64'h20202020_20203030; 36'h4000002e8: data_val = 64'h30303030_30303030; 36'h4000002f0: data_val = 64'h30303030_30380a20; 36'h4000002f8: data_val = 64'h20202020_20202020; 36'h400000300: data_val = 64'h33313030_30303046; 36'h400000308: data_val = 64'h0a202020_20202020; 36'h400000310: data_val = 64'h20203345_0a504c45; 36'h400000318: data_val = 64'h41534520_454e5445; 36'h400000320: data_val = 64'h522e2e2e_0a000a0d; 36'h400000328: data_val = 64'h45786563_7574696f; 36'h400000330: data_val = 64'h6e206265_67696e6e; 36'h400000338: data_val = 64'h696e672e_2e2e0a0d; 36'h400000340: data_val = 64'h000a0d2a_2a2a2a2a; 36'h400000348: data_val = 64'h20204552_524f523a; 36'h400000350: data_val = 64'h20436865_636b7375; 36'h400000358: data_val = 64'h6d206661_696c7572; 36'h400000360: data_val = 64'h65212020_2a2a2a2a; 36'h400000368: data_val = 64'h2a0a0d00_0a0d2a2a; 36'h400000370: data_val = 64'h2a2a2a20_20455252; 36'h400000378: data_val = 64'h4f523a20_54686973; 36'h400000380: data_val = 64'h2070726f_6772616d; 36'h400000388: data_val = 64'h20646f65_73206e6f; 36'h400000390: data_val = 64'h74206669_7420696e; 36'h400000398: data_val = 64'h746f2061_7661696c; 36'h4000003a0: data_val = 64'h61626c65_2052414d; 36'h4000003a8: data_val = 64'h2120202a_2a2a2a2a; 36'h4000003b0: data_val = 64'h0a0d0000_003900b1; 36'h4000003b8: data_val = 64'h25000110_17000541; 36'h4000003c0: data_val = 64'h19000c0e_17000791; 36'h4000003c8: data_val = 64'h19000b8e_003900a1; 36'h4000003d0: data_val = 64'h190014ce_17000771; 36'h4000003d8: data_val = 64'h19000a8e_003900b1; 36'h4000003e0: data_val = 64'h190013ce_17000771; 36'h4000003e8: data_val = 64'h1900098e_003900c1; 36'h4000003f0: data_val = 64'h190012ce_17000771; 36'h4000003f8: data_val = 64'h1900088e_003900d1; 36'h400000400: data_val = 64'h190011ce_17000771; 36'h400000408: data_val = 64'h1900078e_25000200; 36'h400000410: data_val = 64'h0a0d2a2a_2a2a2a20; 36'h400000418: data_val = 64'h20414e20_45584345; 36'h400000420: data_val = 64'h5054494f_4e204841; 36'h400000428: data_val = 64'h53204f43_43555252; 36'h400000430: data_val = 64'h45442120_202a2a2a; 36'h400000438: data_val = 64'h2a2a0a0d_00637372; 36'h400000440: data_val = 64'h5f707265_7670633a; 36'h400000448: data_val = 64'h2020000a_0d637372; 36'h400000450: data_val = 64'h5f636175_73653a20; 36'h400000458: data_val = 64'h2020000a_0d637372; 36'h400000460: data_val = 64'h5f626164_3a202020; 36'h400000468: data_val = 64'h2020000a_0d637372; 36'h400000470: data_val = 64'h5f616464_723a2020; 36'h400000478: data_val = 64'h2020000a_0d000000; 36'h400000480: data_val = 64'h1b000012_11000028; 36'h400000488: data_val = 64'h1a0000e0_25000303; 36'h400000490: data_val = 64'h02000233_10fff038; 36'h400000498: data_val = 64'h25000520_01000111; 36'h4000004a0: data_val = 64'h19fffe00_01fff0ff; 36'h4000004a8: data_val = 64'h220001f0_220002f8; 36'h4000004b0: data_val = 64'h25000302_02000222; 36'h4000004b8: data_val = 64'h10fff028_25000510; 36'h4000004c0: data_val = 64'h1e0000f1_1e0008f2; 36'h4000004c8: data_val = 64'h010010ff_1a0000e0; 36'h4000004d0: data_val = 64'h01ffe8ff_22000ef0; 36'h4000004d8: data_val = 64'h220001f8_220012f0; 36'h4000004e0: data_val = 64'h02000f11_04000a08; 36'h4000004e8: data_val = 64'h1300018c_01003011; 36'h4000004f0: data_val = 64'h19000080_01005711; 36'h4000004f8: data_val = 64'h25000302_02000222; 36'h400000500: data_val = 64'h10fff028_25000510; 36'h400000508: data_val = 64'h1e0000fe_1e0008f1; 36'h400000510: data_val = 64'h1e0010f2_010018ff; 36'h400000518: data_val = 64'h1a0000e0_01ffe8ff; 36'h400000520: data_val = 64'h22000ef0_220001f8; 36'h400000528: data_val = 64'h220012f0_04001002; 36'h400000530: data_val = 64'h09003c11_19fff9ce; 36'h400000538: data_val = 64'h01ffff22_12fff204; 36'h400000540: data_val = 64'h1e0000fe_1e0008f1; 36'h400000548: data_val = 64'h1e0010f2_010018ff; 36'h400000550: data_val = 64'h1a0000e0_25000301; 36'h400000558: data_val = 64'h02000111_10fff018; 36'h400000560: data_val = 64'h25000401_04000a08; 36'h400000568: data_val = 64'h10ffe81c_04000d08; 36'h400000570: data_val = 64'h10ffe814_04000908; 36'h400000578: data_val = 64'h10ffd81c_04002008; 36'h400000580: data_val = 64'h10ffd814_1a0000e0; 36'h400000588: data_val = 64'h01fff8ff_22000ef0; 36'h400000590: data_val = 64'h19fffc4e_25000110; 36'h400000598: data_val = 64'h04003008_12001814; 36'h4000005a0: data_val = 64'h04003908_1200018c; 36'h4000005a8: data_val = 64'h02000f11_19000400; 36'h4000005b0: data_val = 64'h04006108_12001814; 36'h4000005b8: data_val = 64'h04006608_1200018c; 36'h4000005c0: data_val = 64'h01ffa911_19000280; 36'h4000005c8: data_val = 64'h04004108_12001814; 36'h4000005d0: data_val = 64'h04004608_1200018c; 36'h4000005d8: data_val = 64'h01ffc911_19000100; 36'h4000005e0: data_val = 64'h17000181_19ffe9ce; 36'h4000005e8: data_val = 64'h19ffa180_1e0000fe; 36'h4000005f0: data_val = 64'h010008ff_1a0000e0; 36'h4000005f8: data_val = 64'h0a0d2a2a_2a2a2a20; 36'h400000600: data_val = 64'h20455252_4f523a20; 36'h400000608: data_val = 64'h496e7661_6c696420; 36'h400000610: data_val = 64'h68657820_63686172; 36'h400000618: data_val = 64'h61637465_72212020; 36'h400000620: data_val = 64'h2a2a2a2a_2a0a0d00; 36'h400000628: data_val = 64'h01fff8ff_22000ef0; 36'h400000630: data_val = 64'h19fff58e_05000412; 36'h400000638: data_val = 64'h19fff50e_000a0211; 36'h400000640: data_val = 64'h000b0166_1e0000fe; 36'h400000648: data_val = 64'h010008ff_1a0000e0; 36'h400000650: data_val = 64'h01fff8ff_22000ef0; 36'h400000658: data_val = 64'h04000003_04000804; 36'h400000660: data_val = 64'h19fffc8e_05000833; 36'h400000668: data_val = 64'h000a0133_01ffff44; 36'h400000670: data_val = 64'h11fff040_1e0000fe; 36'h400000678: data_val = 64'h010008ff_1a0000e0; default: data_val = 64'h00000000_00000000; endcase endmodule // ROM_MEMORY /***** OLDER ROM PROGRAMS - WORKING; OK TO DELETE *****/ /****************************** HelloWorld Program ****************************** 36'h400000000: data_val = { 32'h170000c1, // movi r1,String1 32'h1900018e }; // call PrintString 36'h400000008: data_val = { 32'h19ffff80, // jump _entry 32'h48656c6c }; // String1: .string "Hello, world!\n\0" 36'h400000010: data_val = { 32'h6f2c2077, // 32'h6f726c64 }; // 36'h400000018: data_val = { 32'h210a0000, // // ############################################################################ // # // # PrintString // # // # Passed: // # r1 = pointer to an array of bytes, terminated with \0. // # Uses: // # r1, r2, r3; Leaf funciton (does not use sp) // # // ############################################################################ // PrintString: # LOOP 32'h1b000012 }; // load.b r2,0(r1) # r2 = the next byte from the string 36'h400000020: data_val = { 32'h11000028, // bnez r2,printLoop # If r2 == 0 32'h1a0000e0 }; // ret # return // printLoop: # LOOP: 36'h400000028: data_val = { 32'h28000303, // control r3,r0,SERIAL_STAT # Get serial status 32'h02000233 }; // andi r3,r3,0x02 # Look at bit [1] only 36'h400000030: data_val = { 32'h10fff038, // beqz r3,printLoop # UNTIL OUTPUT READY = 1 32'h28000520 }; // control r0,r2,SERIAL_SEND # Send a byte from r2 36'h400000038: data_val = { 32'h01000111, // addi r1,r1,1 # Increment pointer 32'h19fffe00 }; // jump PrintString # ENDLOOP **********************************************************************/ /****************************** Echo Program ****************************** // Blitz-64 Micro Blitz Echo Program -- ISA #2 // // Harry Porter III - 30 November 2022 // // This program echoes characters from/to the serial port. // Also echos to digital output. Instructions used: // andi // beq // bne // control (1,3,4, and 5) // All branching is relative so it can be located anywhere in memory. // loopA: 36'h4_0000_0000: data_val = 64'h24000301_02000111; // control r1,r0,0x03 # Get serial status // andi r1,r1,0x01 # . Look at bit [0] only 36'h4_0000_0008: data_val = 64'h10fff018_24000402; // beqz r1,loopA # . Wait for INPUT AVAIL = 1 // // control r2,r0,0x04 # Recv serial into r2 36'h4_0000_0010: data_val = 64'h24000120_24000301; // control r0,r2,0x01 # Write r2 to digital output // // loopB: // control r1,r0,0x03 # Get serial status 36'h4_0000_0018: data_val = 64'h02000211_10fff018; // andi r1,r1,0x02 # . Look at bit [1] only // beqz r1,loopB # . Wait for OUTPUT READY = 1 36'h4_0000_0020: data_val = 64'h24000520_11ffd01c; // control r0,r2,0x05 # Send from r2 // // bnez r1,loopA # Repeat // // DIGITAL_READ: .equ 0 # control XXX,r0,DIGITAL_READ // DIGITAL_WRITE: .equ 1 # control r0,XXX,DIGITAL_WRITE // HALT: .equ 2 # control r0,r0,HALT // SERIAL_STAT: .equ 3 # control XXX,r0,SERIAL_STAT // SERIAL_RECV: .equ 4 # control XXX,r0,SERIAL_RECV // SERIAL_SEND: .equ 5 # control r0,XXX,SERIAL_SEND **********************************************************************/ //------------------------------------------------------------- // // MMU SHELL // // This module encapsultes the FETCH_UNIT, MMU, and MEMORY_SYSTEM. It contains // no logic. Here is the module connectivity; // // CORE <---> FETCH_UNIT <---> MMU <---> MEMORY_SYSTEM // //------------------------------------------------------------- module MMU_SHELL ( input clock, input clock_enable, input reset, input [43:0] root_addr_in, // Communication from the core input [15:0] asid_in, // . input mode_in, // . input [35:0] address_in, // . input MMU_COMMAND command_in, // . input is_checkaddr_in, // . input [63:0] st_data_in, // . input [63:0] cas_old_data_in, // . output [63:0] result_data_out, // Communication to the core output [15:0] result_code_out, // . output done_out, // . output ready_out // . ); wire [15:0] pass_asid; wire pass_mode; wire [35:0] pass_address; MMU_COMMAND pass_command; wire [63:0] pass_result_data; wire [15:0] pass_result_code; wire pass_done; wire pass_ready; wire [43:0] to_mem_sys_address; // Doubleword-aligned, so maybe eliminate LSB 3 bits later? MEM_COMMAND to_mem_sys_command; wire [63:0] to_mem_sys_st_data; wire [63:0] to_mem_sys_cas_old_data; wire [63:0] from_mem_sys_result_data; wire from_mem_sys_done; wire from_mem_sys_ready; FETCH_UNIT MyFetchUnit ( .clock (clock), .clock_enable (clock_enable), .reset (reset), .asid_in (asid_in), // Communication to/from the core .mode_in (mode_in), // . .address_in (address_in), // . .command_in (command_in), // . .result_data_out (result_data_out), // . .result_code_out (result_code_out), // . .done_out (done_out), // . .ready_out (ready_out), // . .mmu_asid_out (pass_asid), // Communication to/from the MMU .mmu_mode_out (pass_mode), // . .mmu_address_out (pass_address), // . .mmu_command_out (pass_command), // . .mmu_result_data_in (pass_result_data), // . .mmu_result_code_in (pass_result_code), // . .mmu_done_in (pass_done), // . .mmu_ready_in (pass_ready) // . ); MMU MyMMU ( .clock (clock), .clock_enable (clock_enable), .reset (reset), .root_addr_in (root_addr_in), // Communication to/from the FETCH_UNIT .asid_in (pass_asid), // . .mode_in (pass_mode), // . .address_in (pass_address), // . .command_in (pass_command), // . .result_data_out (pass_result_data), // . .result_code_out (pass_result_code), // . .done_out (pass_done), // . .ready_out (pass_ready), // . .is_checkaddr_in (is_checkaddr_in), // Communication to/from the CORE .st_data_in (st_data_in), // . .cas_old_data_in (cas_old_data_in), // . .mem_phys_address_out (to_mem_sys_address), // Communication to/from the MEMORY_SYSTEM .mem_command_out (to_mem_sys_command), // . .mem_st_data_out (to_mem_sys_st_data), // . .mem_result_data_in (from_mem_sys_result_data), // . .mem_done_in (from_mem_sys_done), // . .mem_ready_in (from_mem_sys_ready) // . ); MEMORY_SYSTEM MyMemorySys ( .clock (clock), .clock_enable (clock_enable), .reset (reset), .phys_address_in (to_mem_sys_address), .command_in (to_mem_sys_command), .st_data_in (to_mem_sys_st_data), .result_data_out (from_mem_sys_result_data), .done_out (from_mem_sys_done), .ready_out (from_mem_sys_ready) ); endmodule // MMU_SHELL //------------------------------------------------------------- // // FETCH UNIT // // This unit sits between the core and the MMU. It intercepts FETCH commands // from the core and processes them. A FETCH command from the core can be // unaligned, while FETCH commands to the MMU must be doubleword aligned // and will return 8 bytes. This unit deals with that mismatch. It also saves // some previously fetched data so involving the MMU can often be avoided. // // This unit always returns a 4 byte instruction. But since compressed instructions // may be as short as one byte and occupy only the first byte or two of the 4 byte // value, the core will often issue sequential FETCH commands with addresses that // are incremented by only 1 or 2. // // The 4 byte instruction is returned in the upper 32 bits of "result_data_out", // which is 64 bits. Furthermore, an exception code is returned (in // "result_code_out"), which is the 16 bit exception cause code. (The upper // 48 bits of all cause codes are always zeros.) // // NOTE: A compressed instruction starting within the last 3 bytes at the end of // a page may return an exception code associated with the following page, even // though the compressed instruction itself lies fully within the previous page! // // This unit maintains a buffer of two doublewords, called "dword_0" and "dword_1". // The register "addr" contains the address from which dword_0 was fetched; // dword_1 will always come from the following address, i.e., addr+8. The // registers "mode" and "asid" indicate under which conditions the data in the // buffer was fetched. Associated with each dword is an exception code (stored in // "except_0" and "except_1") which tells the outcome from the MMU when that // dword was fetched. // // The data contained in the dword and its associated exception can be either // valid or invalid, as given by registers "buf_valid_0" and "buf_valid_1". // (If dword_0 is invalid, then dword_1 will also be invalid.) // // This unit has a "state" register. In the READY state, the unit is ready to receive // the next command from the core. If it is a FETCH command, it will be processed; // otherwise, the command will be passed thru to the MMU and the results from the // MMU will be passed back thru to the core. // // If the command is FETCH, then the first thing that happens is that the request // is examined in light of the current state of the buffers to determine what // needs to happen. This choice is computed in "case_comb". Then, the unit will // go into a state determined by the case. For example, CASE_3 will lead to STATE_3. // // To handle the case, this unit will issue either one FETCH request to the MMU, or // two requests (in CASE_4), or no requests (in CASE_2). Ultimately, this unit will // send the resulting instruction and exception code back to the core (in // "result_data_out" and "result_code_out"). It will then return to the READY state. // //------------------------------------------------------------- module FETCH_UNIT ( input clock, input clock_enable, input reset, input [15:0] asid_in, // Communication to/from the core input mode_in, // . input [35:0] address_in, // . input MMU_COMMAND command_in, // . output logic [63:0] result_data_out, // . output logic [15:0] result_code_out, // . output logic done_out, // . output logic ready_out, // . output logic [15:0] mmu_asid_out, // Communication to/from the MMU output logic mmu_mode_out, // . output logic [35:0] mmu_address_out, // . output MMU_COMMAND mmu_command_out, // . input [63:0] mmu_result_data_in, // . input [15:0] mmu_result_code_in, // . input mmu_done_in, // . input mmu_ready_in // . ); typedef enum logic [2:0] { READY, // 000 STATE_1, // 001 STATE_3, // 010 STATE_4, // 011 STATE_4A, // 100 STATE_5, // 101 STATE_6, // 110 STATE_INVALID // 111 -- Error; should never happen } FETCH_STATE; typedef enum logic [2:0] { CASE_OTHER, // 000 Includes NOP and all non-fetch commands (LOADx, STOREx, CAS, FENCE, ...) CASE_1, // 001 No usable data present; read dword_0 and mark dword_1 as invalid CASE_2, // 010 The desired instruction is in the buffer; no reading is required CASE_3, // 011 Shift dword_1 left into dword_0 and read into dword_1 CASE_4, // 100 No usable data present & desired value spans boundary; two reads required CASE_5, // 101 dword_1 is needed but it is invalid; read into dword_1 CASE_6 // 110 The dword directly before buffer is needed. Shift right & read into dword_0 } FETCH_CASE; // ---------- Registers ---------- FETCH_STATE state; // The current state FETCH_STATE prev_state; // . FETCH_CASE prev_case_comb; // Holds the previous value of case_comb reg [31:0] saved_instruction; // Holds the values when they are computed reg [15:0] saved_exception; // . in case they will be sent to the core later reg buf_valid_0; // Does the first doubleword in the buffer contain valid data & code? reg buf_valid_1; // Does the second? reg mode; // Under what circumstance were earlier FETCHes to the buffer performed. reg [15:0] asid; // . reg [35:0] addr; // The address in memory from which dword_0 came. dword_1 came from addr+8. reg [63:0] dword_0; // The data in the buffer... reg [63:0] dword_1; // . reg [15:0] except_0; // . and what exception code happened when it was FETCHed. reg [15:0] except_1; // . reg [35:0] saved_address; // Used to extract instruction in any state after READY. // ---------- Combinational ---------- FETCH_STATE next_state; FETCH_CASE case_comb; logic usable_0; logic usable_1; logic mode_and_asid_ok; logic first_byte_in_dword_0; // Given "address", would first instr byte be in dword_0? logic x_first_byte_in_dword_0; // . logic first_byte_in_dword_1; // . or in dword_1? (Assuming the buffer is valid) logic first_byte_in_dword_2; // . or in dword_2? (If it existed, which it doesn't) logic first_byte_in_dword_p; // . or in the doubleword before dword_0? logic last_byte_in_dword_0; // Assuming the first byte is in the buffer, would the logic last_byte_in_dword_1; // . last instr byte be in dword_0? ... Or dword_1? logic last_byte_in_dword_2; // Assuming the first byte is in dword_2... logic x_last_byte_in_dword_1; // . logic y_last_byte_in_dword_0; // . logic spans_2_doublewords; // Does a 4 byte value starting at "address" cross an 8 byte boundary? logic [31:0] instruction; // The 4 byte instruction, extracted from the buffer logic [31:0] x_instruction; // . ...and from memory results logic [31:0] y_instruction; // . logic [15:0] exception; // The exception code associated with this instruction. logic [15:0] x_exception; // . logic [15:0] y_exception; // . always_comb begin instruction = 0; // Computed from current buffer contents using address_in. exception = 0; // . x_instruction = 0; // Computed from dword_0 and the mmu results (for the 2nd doubleword) x_exception = 0; // . using saved_address. y_instruction = 0; // Computed using mmu results (for the 1st doubleword) y_exception = 0; // . using saved_address. case_comb = CASE_OTHER; spans_2_doublewords = 0; case (address_in[3:0]) 4'b0101: spans_2_doublewords = 1; 4'b0110: spans_2_doublewords = 1; 4'b0111: spans_2_doublewords = 1; 4'b1101: spans_2_doublewords = 1; 4'b1110: spans_2_doublewords = 1; 4'b1111: spans_2_doublewords = 1; default: spans_2_doublewords = 0; endcase // Compute values, based on address_in and current values of addr, dword_x, except_x. // The outputs are only valid when state==READY. // first_byte_in_dword_p = (addr - 36'd8 <= address_in) & (address_in <= addr - 36'd1); first_byte_in_dword_0 = (addr <= address_in) & (address_in <= addr + 36'd7); first_byte_in_dword_1 = (addr + 36'd8 <= address_in) & (address_in <= addr + 36'd15); first_byte_in_dword_2 = (addr + 36'd16 <= address_in) & (address_in <= addr + 36'd23); last_byte_in_dword_0 = (addr - 36'd4 < address_in) & (address_in <= addr + 36'd4); if (addr == 36'hf_ffff_fff8) // i.e., if buffer wraps around & dword_1 is at 0_0000_0000 begin last_byte_in_dword_1 = (address_in > 36'hf_ffff_fffc); last_byte_in_dword_2 = (36'd4 < address_in) & (address_in <= 36'd12); end else // dword_0 and dword_1 are contiguous, so addr + 8..15 will not overflow begin last_byte_in_dword_1 = (addr + 36'd4 < address_in) & (address_in <= addr + 36'd12); if (addr == 36'hf_ffff_fff0) // i.e., if buffer wraps around & dword_2 is at 0_0000_0000 last_byte_in_dword_2 = (addr + 36'd12 < address_in) | (address_in <= 36'd4); else // No wrap-around, dword_0, dword_1, and dword_2 are contiguous, so addr + 8..23 will not overflow last_byte_in_dword_2 = (addr + 36'd12 < address_in) & (address_in <= addr + 36'd20); end if ((first_byte_in_dword_0) & (except_0 != 0)) exception = except_0; else if (last_byte_in_dword_1) exception = except_1; else exception = 0; case (address_in[3:0] - addr[3:0]) // Shift amount 4'b0000: instruction = dword_0 [63:32]; 4'b0001: instruction = dword_0 [55:24]; 4'b0010: instruction = dword_0 [47:16]; 4'b0011: instruction = dword_0 [39: 8]; 4'b0100: instruction = dword_0 [31: 0]; 4'b0101: instruction = { dword_0 [23: 0] , dword_1 [63:56] }; 4'b0110: instruction = { dword_0 [15: 0] , dword_1 [63:48] }; 4'b0111: instruction = { dword_0 [ 7: 0] , dword_1 [63:40] }; 4'b1000: instruction = dword_1 [63:32]; 4'b1001: instruction = dword_1 [55:24]; 4'b1010: instruction = dword_1 [47:16]; 4'b1011: instruction = dword_1 [39: 8]; 4'b1100: instruction = dword_1 [31: 0]; 4'b1101: instruction = 32'h7878_7878; // Will never happen 4'b1110: instruction = 32'h7878_7878; 4'b1111: instruction = 32'h7878_7878; endcase // Compute values, based on saved_address and current values of saved_address, dword_0, except_0, // mmu_result_data, and mmu_result_code. This is useful in determining the output for CASE_3, _4, and _5. // x_first_byte_in_dword_0 = (addr <= saved_address) & (saved_address <= addr + 36'd7); if (addr == 36'hf_ffff_fff8) // i.e., if buffer wraps around & dword_1 is at 0_0000_0000 x_last_byte_in_dword_1 = (saved_address > 36'hf_ffff_fffc); else x_last_byte_in_dword_1 = (addr + 36'd4 < saved_address) & (saved_address <= addr + 36'd12); if ((x_first_byte_in_dword_0) & (except_0 != 0)) x_exception = except_0; else if (x_last_byte_in_dword_1) x_exception = mmu_result_code_in; else x_exception = 0; case (saved_address[3:0] - addr[3:0]) // Shift amount 4'b0000: x_instruction = dword_0 [63:32]; 4'b0001: x_instruction = dword_0 [55:24]; 4'b0010: x_instruction = dword_0 [47:16]; 4'b0011: x_instruction = dword_0 [39: 8]; 4'b0100: x_instruction = dword_0 [31: 0]; 4'b0101: x_instruction = { dword_0 [23: 0] , mmu_result_data_in [63:56] }; 4'b0110: x_instruction = { dword_0 [15: 0] , mmu_result_data_in [63:48] }; 4'b0111: x_instruction = { dword_0 [ 7: 0] , mmu_result_data_in [63:40] }; 4'b1000: x_instruction = mmu_result_data_in [63:32]; 4'b1001: x_instruction = mmu_result_data_in [55:24]; 4'b1010: x_instruction = mmu_result_data_in [47:16]; 4'b1011: x_instruction = mmu_result_data_in [39: 8]; 4'b1100: x_instruction = mmu_result_data_in [31: 0]; 4'b1101: x_instruction = 32'h7878_7878; // Will never happen 4'b1110: x_instruction = 32'h7878_7878; 4'b1111: x_instruction = 32'h7878_7878; endcase // Compute values, based on saved_address and current values of saved_address, mmu_result_data, // mmu_result_code, dword_0, and except_0. For CASE_1, the instruction will lie entirely within // the doubleword from memory. For CASE_6, it will begin in the doubleword from memory but // may spill into dword_0 (which has already been shifted into dword_1). // y_exception = mmu_result_code_in; // We can assume the first byte is in "pre" word. y_last_byte_in_dword_0 = (saved_address - 36'd4 < addr) & (saved_address <= addr + 36'd4); if ((state == STATE_6) & (mmu_result_code_in == 0) & (!y_last_byte_in_dword_0)) y_exception = except_1; case (saved_address[2:0] - addr[2:0]) // Shift amount 3'b000: y_instruction = mmu_result_data_in [63:32]; 3'b001: y_instruction = mmu_result_data_in [55:24]; 3'b010: y_instruction = mmu_result_data_in [47:16]; 3'b011: y_instruction = mmu_result_data_in [39: 8]; 3'b100: y_instruction = mmu_result_data_in [31: 0]; 3'b101: y_instruction = { mmu_result_data_in [23: 0] , dword_1 [63:56] }; 3'b110: y_instruction = { mmu_result_data_in [15: 0] , dword_1 [63:48] }; 3'b111: y_instruction = { mmu_result_data_in [ 7: 0] , dword_1 [63:40] }; endcase case_comb = CASE_OTHER; // In order to use data already in the buffer, the mode must match and, if we are // fetching from the virtual space, the ASID must also match. If we are in kernel // mode and the address is within the kernel space, then the ASID can be ignored. // NOTE: If we are in kernel mode and dword_0 happens to be the last doubleword // of the memory-mapped area (0x7_ffff_fff8), dword_1 will be the first // doubleword of some virtual address space. Assuming dword_1 is // not valid, we would be doing a fetch. In this case, the ASIDs // must match. By setting "mode_and_asid_ok" to false, we simply trigger // CASE_1 or CASE_4, causing a reload from scratch of any needed data. // To be used, the mode of data in the buffer must match the core's current mode. if (mode_in != mode) mode_and_asid_ok = 0; else // If we are in user mode... if (mode == 0) mode_and_asid_ok = (asid_in == asid); // ...then the ASIDs must also match else // If we're in kernel mode, but the requested instruction could lie within the virtual space... if (36'h7_ffff_fffc < address_in) mode_and_asid_ok = (asid_in == asid); // ...require ASIDs to match else mode_and_asid_ok = 1; // ...else ignore the ASIDs usable_0 = buf_valid_0 & mode_and_asid_ok; usable_1 = buf_valid_1 & mode_and_asid_ok; // Compute the current case if ((state == READY) & (command_in == MMU_FETCH) & mmu_ready_in) begin if ((usable_0 & first_byte_in_dword_0 & last_byte_in_dword_0) | (usable_1 & first_byte_in_dword_1 & last_byte_in_dword_1) | (usable_0 & usable_1 & first_byte_in_dword_0 & last_byte_in_dword_1)) case_comb = CASE_2; // No reading needed else if (usable_0 & !usable_1 & first_byte_in_dword_0 & !last_byte_in_dword_0) case_comb = CASE_5; // Read dword_1 before returning else if (usable_0 & !usable_1 & first_byte_in_dword_1 & last_byte_in_dword_1) case_comb = CASE_5; // Read dword_1 before returning else if (usable_1 & ( (first_byte_in_dword_1 & !last_byte_in_dword_1) | (first_byte_in_dword_2 & last_byte_in_dword_2) ) ) case_comb = CASE_3; // Shift left and read new dword_1 before returning else if (usable_0 & first_byte_in_dword_p) case_comb = CASE_6; // Shift right and read new dword_0 before returning else // Either the buffer is invalid, or the desired instruction is not in the buffer if (spans_2_doublewords) case_comb = CASE_4; // Read dword_0 and dword_1 before returning else case_comb = CASE_1; // Read dword_0 and mark dword_1 as invalid end end always_ff @(posedge clock) if (clock_enable) begin if (reset) begin state <= READY; prev_state <= READY; prev_case_comb <= CASE_OTHER; buf_valid_0 <= 0; buf_valid_1 <= 0; mode <= 0; asid <= 0; addr <= 0; dword_0 <= 0; dword_1 <= 0; except_0 <= 0; except_1 <= 0; saved_address <= 0; saved_instruction <= 0; saved_exception <= 0; end else begin state <= next_state; prev_state <= state; prev_case_comb <= case_comb; case (state) READY: begin case (case_comb) CASE_1: begin buf_valid_0 <= 1; buf_valid_1 <= 0; addr <= address_in & 36'hf_ffff_fff8; mode <= mode_in; asid <= asid_in; saved_address <= address_in; // Save desired address for y_ calculation end CASE_2: begin saved_instruction <= instruction; saved_exception <= exception; end CASE_3: begin addr <= addr + 8; // Shift left (both are already marked valid) dword_0 <= dword_1; except_0 <= except_1; saved_address <= address_in; // Save desired address for x_ calculation end CASE_4: begin buf_valid_0 <= 1; buf_valid_1 <= 1; addr <= address_in & 36'hf_ffff_fff8; mode <= mode_in; asid <= asid_in; saved_address <= address_in; end CASE_5: begin saved_address <= address_in; // Save desired address for x_ calculation end CASE_6: begin addr <= addr - 8; // Shift right dword_1 <= dword_0; except_1 <= except_0; buf_valid_1 <= buf_valid_0; saved_address <= address_in; // Save desired address for y_ calculation end default: begin end endcase // If we are pssing through a FENCE command to the MMU, invalidate both buffers. if ((mmu_ready_in) & (command_in == MMU_FENCE)) begin buf_valid_0 <= 0; buf_valid_1 <= 0; end end STATE_1: if (mmu_done_in) begin dword_0 <= mmu_result_data_in; except_0 <= mmu_result_code_in; saved_instruction <= y_instruction; saved_exception <= y_exception; end STATE_3: if (mmu_done_in) begin dword_1 <= mmu_result_data_in; except_1 <= mmu_result_code_in; saved_instruction <= x_instruction; saved_exception <= x_exception; end STATE_4: if (mmu_done_in) begin dword_0 <= mmu_result_data_in; except_0 <= mmu_result_code_in; end STATE_4A: if (mmu_done_in) begin dword_1 <= mmu_result_data_in; except_1 <= mmu_result_code_in; saved_instruction <= x_instruction; saved_exception <= x_exception; end STATE_5: if (mmu_done_in) begin dword_1 <= mmu_result_data_in; except_1 <= mmu_result_code_in; buf_valid_1 <= 1; saved_instruction <= x_instruction; saved_exception <= x_exception; end STATE_6: if (mmu_done_in) begin dword_0 <= mmu_result_data_in; except_0 <= mmu_result_code_in; saved_instruction <= y_instruction; saved_exception <= y_exception; end default: begin end endcase end // if (reset) end // always_ff @(posedge clock) // Compute "next_state" always_comb begin case (state) READY: if (mmu_ready_in) if (case_comb == CASE_2) next_state = READY; else if (case_comb == CASE_1) next_state = STATE_1; else if (case_comb == CASE_3) next_state = STATE_3; else if (case_comb == CASE_4) next_state = STATE_4; else if (case_comb == CASE_5) next_state = STATE_5; else if (case_comb == CASE_6) next_state = STATE_6; else next_state = READY; else next_state = READY; STATE_1: // Stay in STATE_1 until mmu is done if (mmu_done_in) next_state = READY; else next_state = STATE_1; STATE_3: // Stay in STATE_3 until mmu is done if (mmu_done_in) next_state = READY; else next_state = STATE_3; STATE_4: // Stay in STATE_4 until mmu is done if (mmu_done_in) next_state = STATE_4A; // ...then go to STATE_4A else next_state = STATE_4; STATE_4A: // Stay in STATE_4A until mmu is done if (mmu_done_in) next_state = READY; else next_state = STATE_4A; STATE_5: // Stay in STATE_5 until mmu is done if (mmu_done_in) next_state = READY; else next_state = STATE_5; STATE_6: // Stay in STATE_6 until mmu is done if (mmu_done_in) next_state = READY; else next_state = STATE_6; default: next_state = STATE_INVALID; endcase end // Compute the output signals to the mmu and back to the core... always_comb begin // Back to the core... ready_out = 0; done_out = 0; result_data_out = 64'hffff_ffff_ffff_ffff; result_code_out = 16'hffff; // To the MMU... mmu_asid_out = 16'hffff; mmu_mode_out = 0; mmu_address_out = 36'hf_ffff_ffff; mmu_command_out = MMU_NOP; case (state) READY: begin if (!mmu_ready_in) begin // Pass though ready_out = 0; done_out = mmu_done_in; result_data_out = mmu_result_data_in; result_code_out = mmu_result_code_in; mmu_asid_out = asid_in; mmu_mode_out = mode_in; mmu_address_out = address_in; mmu_command_out = command_in; if (prev_state == STATE_4A) begin if (mmu_done_in) begin done_out = 1; result_data_out = { x_instruction, 32'h0 }; result_code_out = x_exception; end else begin done_out = 1; result_data_out = { saved_instruction, 32'h0 }; result_code_out = saved_exception; end end end else // state == READY & mmu is ready begin ready_out = 1; // Let the core know we are ready to accept a new command. if (prev_state == STATE_4A) begin if (mmu_done_in) begin done_out = 1; result_data_out = { x_instruction, 32'h0 }; result_code_out = x_exception; end else begin done_out = 1; result_data_out = { saved_instruction, 32'h0 }; result_code_out = saved_exception; end end else if (prev_case_comb == CASE_2) begin done_out = 1; result_data_out = { saved_instruction, 32'h0 }; result_code_out = saved_exception; end else begin // Pass MMU results through, back to the core done_out = mmu_done_in; result_data_out = mmu_result_data_in; result_code_out = mmu_result_code_in; end // Compute signals to send to the MMU (during READY state)... case (case_comb) CASE_1: begin mmu_command_out = MMU_FETCH; mmu_asid_out = asid_in; mmu_mode_out = mode_in; mmu_address_out = address_in & 36'hf_ffff_fff8; end CASE_2: begin mmu_command_out = MMU_NOP; mmu_asid_out = 0; mmu_mode_out = 0; mmu_address_out = 0; end CASE_3: begin mmu_command_out = MMU_FETCH; mmu_asid_out = asid; mmu_mode_out = mode; mmu_address_out = addr + 16; end CASE_4: begin mmu_command_out = MMU_FETCH; mmu_asid_out = asid_in; mmu_mode_out = mode_in; mmu_address_out = address_in & 36'hf_ffff_fff8; end CASE_5: begin mmu_command_out = MMU_FETCH; mmu_asid_out = asid; mmu_mode_out = mode; mmu_address_out = addr + 8; end CASE_6: begin mmu_command_out = MMU_FETCH; mmu_asid_out = asid; mmu_mode_out = mode; mmu_address_out = addr - 8; end default: // CASE_OTHER begin mmu_command_out = command_in; // Pass command thru to MMU mmu_asid_out = asid_in; mmu_mode_out = mode_in; mmu_address_out = address_in; end endcase end end STATE_1: begin ready_out = 0; if (mmu_done_in) done_out = 1; else done_out = 0; result_data_out = { y_instruction, 32'h0 }; result_code_out = y_exception; mmu_command_out = MMU_NOP; end STATE_3: begin ready_out = 0; if (mmu_done_in) done_out = 1; else done_out = 0; result_data_out = { x_instruction, 32'h0 }; result_code_out = x_exception; mmu_command_out = MMU_NOP; end STATE_4: begin ready_out = 0; done_out = 0; mmu_command_out = MMU_FETCH; mmu_address_out = addr + 8; mmu_asid_out = asid; mmu_mode_out = mode; end STATE_4A: begin ready_out = 0; done_out = 0; mmu_command_out = MMU_FETCH; mmu_address_out = addr + 8; mmu_asid_out = asid; mmu_mode_out = mode; end STATE_5: begin ready_out = 0; if (mmu_done_in) done_out = 1; else done_out = 0; mmu_command_out = MMU_NOP; result_data_out = { x_instruction, 32'h0 }; result_code_out = x_exception; end STATE_6: begin ready_out = 0; if (mmu_done_in) done_out = 1; else done_out = 0; mmu_command_out = MMU_NOP; result_data_out = { y_instruction, 32'h0 }; result_code_out = y_exception; end default: // state = STATE_INVALID ; endcase end endmodule // FETCH_UNIT //------------------------------------------------------------- // // MMU - MEMORY MANAGEMENT UNIT // // Commands to be handled: // LOADB, LOADH, LOADW, LOADD, // STOREB, STOREH, STOREW, STORED, // FETCH, FENCE, CAS, TLBFLUSH, TLBCLEAR, NOP // TLBDEBUG // // NOTE: We assume root_addr_in is held constant throughout the command. This // matters for FETCH operations since the FETCH_UNIT may send multiple commands // to the MMU over time, and the root_addr_in will be sampled at the beginning // of each command to the MMU. // // TLBDEBUG does not correspond to any Blitz-64 instruction. Instead, it is // intended to be used to debug and verify the MMU. It is meant to be accessed // from a CONTROL instruction. The argument in (i.e., Reg1) will be fed to the // st_data_in and the result (i.e., RegD) will come from result_data_out. // The argument in is the number of a TLB register (0,1,2,...) and the result // is the value of that register. To make the TLB register (72 bits) fit into // 64 bits, the PPN is truncated from 30 to 28 bits. The result is: // ASID[63:48], VPN[47:27], PPN[26:5], C[4], D[3], W[2], X[1], V[0]. // The other inputs (root_addr, asid, mode, and address_in) are ignored. // //------------------------------------------------------------- module MMU ( input clock, input clock_enable, input reset, input [43:0] root_addr_in, // Communication to/from the CORE & FETCH_UNIT input [15:0] asid_in, // . input mode_in, // . input [35:0] address_in, // . input MMU_COMMAND command_in, // . input is_checkaddr_in, // . input [63:0] st_data_in, // . input [63:0] cas_old_data_in, // . output logic [63:0] result_data_out, // . output logic [15:0] result_code_out, // . output logic done_out, // . output reg ready_out, // . output logic [43:0] mem_phys_address_out, // Communication to/from the MEMORY_SYSTEM output MEM_COMMAND mem_command_out, // . output logic [63:0] mem_st_data_out, // . input [63:0] mem_result_data_in, // . input mem_done_in, // . input mem_ready_in // . ); typedef enum logic [2:0] { READY, // 000 STATE_READ_ROOT, // 001 STATE_READ_INDEX, // 010 STATE_FINISH_OP, // 011 STATE_INVALID // --- -- Error; should never happen } MMU_STATE; typedef enum logic [3:0] { CASE_NOP, // 0000 CASE_EASY, // 0001 An "easy" exception has been detected and except_code will be output on next cycle CASE_LOADB, // 0010 CASE_LOADH, // 0011 CASE_LOADW, // 0100 CASE_LOADD, // 0101 CASE_STOREB, // 0110 CASE_STOREH, // 0111 CASE_STOREW, // 1000 CASE_STORED, // 1001 CASE_FETCH, // 1010 CASE_CAS, // 1011 CASE_TLBCLEAR, // 1100 CASE_TLBFLUSH, // 1101 CASE_CHECKADDR, // 1110 CASE_TLBDEBUG // 1111 } MMU_CASE; // ---------- Registers ---------- MMU_STATE state; // The current state MMU_CASE prev_case_comb; // Holds the previous value of case_comb reg [63:0] save_pte; // Holds a recently fetched page table entry (either from root or index) reg [35:0] save_address_in; // Captured whenever a command is accepted reg [15:0] save_asid_in; // . reg [63:0] save_st_data_in; // . reg [63:0] save_cas_old_data_in; // . reg save_is_checkaddr_in; // . reg [15:0] save_except_code; // Captured on 1st state of command when exception is obvious reg [63:0] save_mem_result_data; // The full dword after READ cycle for STOREB/H/W and CAS reg [29:0] save_ppn_from_tlb; // Saved when TLB hits. Used for WRITE cycle in STOREB/H/W and CAS reg [63:0] save_tlb_out_debug_regval; reg save_cas_result; // Captured in STATE_FINISH_OP and used in READY state. reg ld_st_aborted_early; // Set when the operation produces an exception code // ---------- Combinational ---------- MMU_STATE next_state; MMU_CASE case_comb; // Only valid in state READY logic [3:0] alignment_reqd; // Only valid in state READY: 1, 2, 4, or 8; 0 for other logic address_is_virtual; // Only valid in state READY logic effective_mode; // Only valid in state READY: If is_checkaddr then USER, else mode_in. logic page_table_lookup_reqd; // Only valid in state READY // The TLB Entry to store or look up; key = {asid,vpn} logic [15:0] tlb_in_asid; // . Address Space Identifier logic [20:0] tlb_in_vpn; // . Virtual Page Number (note 21 bits + 14 bit offset = 35 bits) logic [29:0] tlb_in_ppn; // . Physical Page Number (note 30 bits + 14 bit offset = 44 bits) logic [4:0] tlb_in_bits; // . 4=Copy-on-write, 3=Dirty, 2=Writable, 1=eXecutable, 0=Valid TLB_COMMAND tlb_in_command; // . // The TLB Entry that was retrieved logic [15:0] tlb_out_asid; // . ASID logic [20:0] tlb_out_vpn; // . Virtual Page Number logic [29:0] tlb_out_ppn; // . Physical Page Number logic [4:0] tlb_out_bits; // . 4=C, 3=D, 2=W, 1=X, 0=V logic tlb_out_match_found; // 1=the search key (asid-vpn) matched and output is valid logic [15:0] tlb_in_debug_regnum; // For CONTROL, the TLB register number logic [63:0] tlb_out_debug_regval; // . The TLB register contents (with PPN reduced to 22 bits) logic [63:0] ex_data_in; logic [2:0] ex_addr_in; // LSB 3 bits only logic [1:0] ex_size_in; // e.g., SZ_BYTE, SZ_HALFWORD, SZ_WORD, SZ_DOUBLEWORD wire [63:0] ex_data_out; logic [63:0] inj_old_dword_in; logic [63:0] inj_new_data_in; logic [2:0] inj_addr_in; logic [1:0] inj_size_in; // e.g., SZ_BYTE, SZ_HALFWORD, SZ_WORD, SZ_DOUBLEWORD logic [63:0] inj_new_dword_out; logic [15:0] except_code; logic exception_detected; // 1 = an exception has been detected logic goto_to_finish_op; // 1 = ready to proceed to STATE_FINISH_OP logic cas_result; // Set in STATE_FINISH_OP // Compute "next_state" always_comb begin case (state) READY: if (!mem_ready_in) next_state = READY; else if (exception_detected) next_state = READY; else if (case_comb == CASE_EASY) next_state = READY; else if (page_table_lookup_reqd) next_state = STATE_READ_ROOT; else if (goto_to_finish_op) next_state = STATE_FINISH_OP; else next_state = READY; STATE_READ_ROOT: if (exception_detected) next_state = READY; else if (mem_ready_in) next_state = STATE_READ_INDEX; else next_state = STATE_READ_ROOT; STATE_READ_INDEX: if (exception_detected) next_state = READY; else if (!mem_ready_in) next_state = STATE_READ_INDEX; else if ( (prev_case_comb == CASE_STOREB) | (prev_case_comb == CASE_STOREH) | (prev_case_comb == CASE_STOREW) | (prev_case_comb == CASE_CAS) ) next_state = STATE_FINISH_OP; else next_state = READY; STATE_FINISH_OP: if (mem_ready_in) next_state = READY; else next_state = STATE_FINISH_OP; default: next_state = STATE_INVALID; endcase end always_ff @(posedge clock) if (clock_enable) begin if (reset) begin state <= READY; prev_case_comb <= CASE_NOP; save_except_code <= 0; save_pte <= 0; save_address_in <= 0; save_asid_in <= 0; save_st_data_in <= 0; save_cas_old_data_in <= 0; save_is_checkaddr_in <= 0; save_mem_result_data <= 0; save_pte <= 0; save_ppn_from_tlb <= 0; save_tlb_out_debug_regval <= 0; save_cas_result <= 0; ld_st_aborted_early <= 0; end else begin state <= next_state; save_ppn_from_tlb <= tlb_out_ppn; // Needed for STOREB/H/W and CAS when a TLB lookup hits case (state) READY: begin if (mem_ready_in) begin prev_case_comb <= case_comb; save_address_in <= address_in; save_asid_in <= asid_in; save_st_data_in <= st_data_in; save_cas_old_data_in <= cas_old_data_in; save_is_checkaddr_in <= is_checkaddr_in; save_except_code <= except_code; save_tlb_out_debug_regval <= tlb_out_debug_regval; save_cas_result <= 0; ld_st_aborted_early <= 0; end end STATE_READ_ROOT: if (mem_done_in) begin save_pte <= mem_result_data_in; ld_st_aborted_early <= exception_detected; end STATE_READ_INDEX: if (mem_done_in) begin save_pte <= mem_result_data_in; save_ppn_from_tlb <= tlb_in_ppn; // Needed for STOREB/H/W and CAS when a TLB lookup misses ld_st_aborted_early <= exception_detected; end STATE_FINISH_OP: if (mem_done_in) begin save_mem_result_data <= mem_result_data_in; ld_st_aborted_early <= 0; save_cas_result <= cas_result; end default: begin end endcase end end // always_ff @(posedge clock) // ----- Here is the overall structure of the following code ----- // // // Determine basic signals, including case_comb. // // (These are only valid when the next command is being accepted.) // case (command_in) // endcase // // case (state) // READY: // // Send results from last operation back to CORE (possibly using EXTRACTOR) // case (prev_case_comb) // endcase // // if (the MEMORY_UNIT is READY) // // The next command can now be accepted. // // Compute signals to send to the TLB and to the MEMORY_UNIT // case (case_comb) // endcase // // STATE_READ_ROOT: // // If PTE_1 not valid, abort. Else start read of PTE_2 // STATE_READ_INDEX: // // If problems with PTE_2, abort. Else start next memory operation. // STATE_FINISH_OP: // // Perform the WRITE for STOREB/H/W // // Compute the output signals to the MEMORY_UNIT, to the TLB, and back to the CORE... always_comb begin // Signals back to the core... ready_out = 0; done_out = 0; result_data_out = 0; result_code_out = 0; // To the MEMORY_UNIT... mem_command_out = MEM_NOP; mem_phys_address_out = 0; mem_st_data_out = 0; // To the TLB... tlb_in_asid = asid_in; tlb_in_vpn = address_in [34:14]; tlb_in_ppn = 0; tlb_in_bits = 0; tlb_in_command = TLB_NOP; tlb_in_debug_regnum = 0; // To the EXTRACTOR unit ex_data_in = 0; ex_addr_in = 0; ex_size_in = 0; // To the INJECTOR unit inj_old_dword_in = 0; inj_new_data_in = 0; inj_addr_in = 0; inj_size_in = 0; // Misc signals (valid in all states) exception_detected = 0; except_code = 0; goto_to_finish_op = 0; cas_result = 0; // Misc signals (valid only in READY state) case_comb = CASE_NOP; address_is_virtual = 0; effective_mode = is_checkaddr_in ? 1'b0 : mode_in; page_table_lookup_reqd = 0; // Compute the following which is needed in -- and only valid in -- the READY state // case_comb // address_is_virtual // page_table_lookup_reqd // except_code (detect NULL_ADDRESS, UNALIGNED, PAGE_ILLEGAL, PAGE_TABLE) // case (command_in) MMU_NOP: begin alignment_reqd = 0; case_comb = CASE_NOP; end MMU_LOADB: begin alignment_reqd = 1; case_comb = CASE_LOADB; end MMU_LOADH: begin alignment_reqd = 2; case_comb = CASE_LOADH; end MMU_LOADW: begin alignment_reqd = 4; case_comb = CASE_LOADW; end MMU_LOADD: begin alignment_reqd = 8; case_comb = CASE_LOADD; end MMU_STOREB: begin alignment_reqd = 1; case_comb = CASE_STOREB; end MMU_STOREH: begin alignment_reqd = 2; case_comb = CASE_STOREH; end MMU_STOREW: begin alignment_reqd = 4; case_comb = CASE_STOREW; end MMU_STORED: begin alignment_reqd = 8; case_comb = CASE_STORED; end MMU_FETCH: begin alignment_reqd = 8; case_comb = CASE_FETCH; end MMU_FENCE: begin alignment_reqd = 0; case_comb = CASE_NOP; end MMU_TLBDEBUG: begin alignment_reqd = 0; case_comb = CASE_EASY; end MMU_CAS: begin alignment_reqd = 8; case_comb = CASE_CAS; end MMU_TLBFLUSH: begin alignment_reqd = 0; case_comb = CASE_TLBFLUSH; end MMU_TLBCLEAR: begin alignment_reqd = 0; case_comb = CASE_TLBCLEAR; end default: begin alignment_reqd = 0; case_comb = CASE_NOP; end endcase address_is_virtual = address_in [35]; except_code = 0; if (state != READY) case_comb = CASE_NOP; else if (command_in == MMU_TLBDEBUG) case_comb = CASE_TLBDEBUG; else if ( (command_in == MMU_FENCE) | (command_in == MMU_TLBFLUSH) | (command_in == MMU_TLBCLEAR) | (command_in == MMU_NOP) ) ; else // i.e., state is READY and command is LOADx, STOREx, FETCH, or CAS begin // Detect all NULL_ADDRESS, UNALIGNED LOAD-STORE, PAGE_ILLEGAL_ADDRESS, and PAGE_TABLE exceptions. // If found, set except_code and return immediately. if (mem_ready_in & (alignment_reqd > 0)) if (address_in [35:3] == 33'b0) begin except_code = CAUSE_NULL_ADDRESS; case_comb = CASE_EASY; end // Check to see if this address is unaligned... else if ( ((alignment_reqd == 8) & (address_in[2:0] != 3'b000)) | ((alignment_reqd == 4) & (address_in[1:0] != 2'b00)) | ((alignment_reqd == 2) & (address_in[ 0] != 1'b0)) ) begin except_code = CAUSE_UNALIGNED_LOAD_STORE; case_comb = CASE_EASY; end else if ((!address_is_virtual) & (effective_mode == 0)) begin except_code = CAUSE_PAGE_ILLEGAL_ADDR; case_comb = CASE_EASY; end else // Either address_is_virtual or mode = KERNEL begin if (address_is_virtual) if (root_addr_in [43:14] == 0) begin except_code = CAUSE_PAGE_TABLE; case_comb = CASE_EASY; end else if (!tlb_out_match_found) page_table_lookup_reqd = 1; else // Address is in kernel space and mode = kernel ; end end // command is LOADx, STOREx, FETCH, or CAS if (except_code != 0) case_comb = CASE_EASY; case (state) READY: begin // Pass results from MEMORY back to CORE, modifying as needed viz. previous operation. ready_out = mem_ready_in; done_out = mem_done_in; case (prev_case_comb) CASE_NOP: // including FENCE begin // result_code_out = 0; (the default) end CASE_TLBCLEAR: begin if (mem_ready_in) done_out = 1; // result_code_out = 0; (the default) end CASE_TLBFLUSH: begin if (mem_ready_in) done_out = 1; // result_code_out = 0; (the default) end CASE_TLBDEBUG: begin if (mem_ready_in) begin done_out = 1; // result_code_out = 0; (the default) result_data_out = save_tlb_out_debug_regval; end end CASE_EASY: // state==READY, prev_case_comb==... begin result_data_out = mem_result_data_in; result_code_out = 0; if (mem_ready_in) begin // There was an exception, so pass it back to the core. done_out = 1; result_code_out = save_except_code; end end CASE_LOADB: // state==READY, prev_case_comb==... begin ex_data_in = mem_result_data_in; ex_addr_in = save_address_in [2:0]; ex_size_in = SZ_BYTE[1:0]; result_data_out = ex_data_out; if ((mem_ready_in) & (save_is_checkaddr_in) & !ld_st_aborted_early) begin done_out = 1; result_code_out = save_except_code; end end CASE_LOADH: // state==READY, prev_case_comb==... begin ex_data_in = mem_result_data_in; ex_addr_in = save_address_in [2:0]; ex_size_in = SZ_HALFWORD[1:0]; result_data_out = ex_data_out; if ((mem_ready_in) & (save_is_checkaddr_in) & !ld_st_aborted_early) begin done_out = 1; result_code_out = save_except_code; end end CASE_LOADW: // state==READY, prev_case_comb==... begin ex_data_in = mem_result_data_in; ex_addr_in = save_address_in [2:0]; ex_size_in = SZ_WORD[1:0]; result_data_out = ex_data_out; if ((mem_ready_in) & (save_is_checkaddr_in) & !ld_st_aborted_early) begin done_out = 1; result_code_out = save_except_code; end end CASE_LOADD: // state==READY, prev_case_comb==... begin result_data_out = mem_result_data_in; if ((mem_ready_in) & (save_is_checkaddr_in) & !ld_st_aborted_early) begin done_out = 1; result_code_out = save_except_code; end end CASE_STOREB: // state==READY, prev_case_comb==... begin // result_data_out is not needed for STOREx if ( mem_ready_in & !ld_st_aborted_early & ((save_except_code != 0) | save_is_checkaddr_in) ) begin done_out = 1; result_code_out = save_except_code; end end CASE_STOREH: // state==READY, prev_case_comb==... begin if ( mem_ready_in & !ld_st_aborted_early & ((save_except_code != 0) | save_is_checkaddr_in) ) begin done_out = 1; result_code_out = save_except_code; end end CASE_STOREW: // state==READY, prev_case_comb==... begin if ( mem_ready_in & !ld_st_aborted_early & ((save_except_code != 0) | save_is_checkaddr_in) ) begin done_out = 1; result_code_out = save_except_code; end end CASE_STORED: // state==READY, prev_case_comb==... begin if ( mem_ready_in & !ld_st_aborted_early & ((save_except_code != 0) | save_is_checkaddr_in) ) begin done_out = 1; result_code_out = save_except_code; end end CASE_CAS: // state==READY, prev_case_comb==... begin if ((mem_ready_in) & !ld_st_aborted_early) begin done_out = 1; result_code_out = save_except_code; // Could be zero or not result_data_out = save_cas_result; // Output result either way end else done_out = 0; end CASE_FETCH: // state==READY, prev_case_comb==... begin result_data_out = mem_result_data_in; result_code_out = 0; if ((mem_ready_in) & (save_except_code != 0)) begin done_out = 1; result_code_out = save_except_code; end end default: begin // result_data_out = 64'hffff_ffff_ffff_ffff; // result_code_out = 16'hffff; // done_out = 0; end endcase // Compute signals to send to the TLB and to the MEMORY_UNIT... // The defaults: // tlb_in_command = TLB_NOP; // tlb_in_asid = asid_in; // tlb_in_vpn = address_in [34:14]; // // mem_command_out = MEM_NOP; // mem_phys_address_out = 0; // mem_st_data_out = 0; // Compute these signals when the next command is accepted // i.e., when (state == READY) & (mem_ready_in == ready_out == 1) if (mem_ready_in) begin case (case_comb) CASE_NOP: begin if (command_in == MMU_FENCE) mem_command_out = MEM_FENCE; else mem_command_out = MEM_NOP; end CASE_TLBCLEAR: begin tlb_in_command = TLB_CLEAR; end CASE_TLBFLUSH: begin tlb_in_command = TLB_FLUSH; end CASE_TLBDEBUG: begin tlb_in_debug_regnum = st_data_in [15:0]; end CASE_EASY: // An "easy" exception was detected; no MEMORY_UNIT involvement needed begin mem_command_out = MEM_NOP; end CASE_LOADB: // state==READY, memory is ready, case_comb==... begin if (address_is_virtual) // If we need to do a page table lookup if (page_table_lookup_reqd) begin mem_phys_address_out = { root_addr_in [43:14] , 14'b00_0000_0000_0000 + (address_in [34:25] << 3)}; // add VPN_1 * 8 mem_command_out = MEM_READ; end else // Else the TLB matched begin mem_phys_address_out = { tlb_out_ppn , address_in [13:0] }; // PPN + offset mem_command_out = MEM_READ; tlb_in_command = TLB_PROMOTE; // If this is a CHECKADDR, skip the actual read and treat it as if an exception happened. if (is_checkaddr_in) begin mem_command_out = MEM_NOP; exception_detected = 1; // except_code defaults to 0. end end else // The address is in the kernel space... begin mem_command_out = MEM_READ; mem_phys_address_out = { 8'b0000_0000, address_in }; end end CASE_LOADH: // state==READY, memory is ready, case_comb==... begin if (address_is_virtual) // If we need to do a page table lookup if (page_table_lookup_reqd) begin mem_phys_address_out = { root_addr_in [43:14] , 14'b00_0000_0000_0000 + (address_in [34:25] << 3)}; // add VPN_1 * 8 mem_command_out = MEM_READ; end else // Else the TLB matched begin mem_phys_address_out = { tlb_out_ppn , address_in [13:0] }; // PPN + offset mem_command_out = MEM_READ; tlb_in_command = TLB_PROMOTE; // If this is a CHECKADDR, skip the actual read and treat it as if an exception happened. if (is_checkaddr_in) begin mem_command_out = MEM_NOP; exception_detected = 1; // except_code defaults to 0. end end else // The address is in the kernel space... begin mem_command_out = MEM_READ; mem_phys_address_out = { 8'b0000_0000, address_in }; end end CASE_LOADW: // state==READY, memory is ready, case_comb==... begin if (address_is_virtual) // If we need to do a page table lookup if (page_table_lookup_reqd) begin mem_phys_address_out = { root_addr_in [43:14] , 14'b00_0000_0000_0000 + (address_in [34:25] << 3)}; // add VPN_1 * 8 mem_command_out = MEM_READ; end else // Else the TLB matched begin mem_phys_address_out = { tlb_out_ppn , address_in [13:0] }; // PPN + offset mem_command_out = MEM_READ; tlb_in_command = TLB_PROMOTE; // If this is a CHECKADDR, skip the actual read and treat it as if an exception happened. if (is_checkaddr_in) begin mem_command_out = MEM_NOP; exception_detected = 1; // except_code defaults to 0. end end else // The address is in the kernel space... begin mem_command_out = MEM_READ; mem_phys_address_out = { 8'b0000_0000, address_in }; end end CASE_LOADD: // state==READY, memory is ready, case_comb==... begin if (address_is_virtual) // If we need to do a page table lookup if (page_table_lookup_reqd) begin mem_phys_address_out = { root_addr_in [43:14] , 14'b00_0000_0000_0000 + (address_in [34:25] << 3)}; // add VPN_1 * 8 mem_command_out = MEM_READ; end else // Else the TLB matched begin mem_phys_address_out = { tlb_out_ppn , address_in [13:0] }; // PPN + offset mem_command_out = MEM_READ; tlb_in_command = TLB_PROMOTE; // If this is a CHECKADDR, skip the actual read and treat it as if an exception happened. if (is_checkaddr_in) begin mem_command_out = MEM_NOP; exception_detected = 1; // except_code defaults to 0. end end else // The address is in the kernel space... begin mem_command_out = MEM_READ; mem_phys_address_out = { 8'b0000_0000, address_in }; end end CASE_FETCH: // state==READY, memory is ready, case_comb==... begin // If the TLB is needed if (address_is_virtual) // If we need to do a page table lookup if (page_table_lookup_reqd) begin mem_command_out = MEM_READ; mem_phys_address_out = { root_addr_in [43:14] , 14'b00_0000_0000_0000 + (address_in [34:25] << 3)}; // add VPN_1 * 8 end else if (tlb_out_bits [1] == 0) // Else the TLB matched but page is not EXECUTABLE begin mem_command_out = MEM_NOP; exception_detected = 1; except_code = CAUSE_PAGE_FETCH; end else // Else the PTE from the TLB is good to use begin mem_command_out = MEM_READ; mem_phys_address_out = { tlb_out_ppn , address_in [13:0] }; // PPN + offset tlb_in_command = TLB_PROMOTE; end else // The address is in the kernel space... begin mem_command_out = MEM_READ; mem_phys_address_out = { 8'b0000_0000, address_in }; end end CASE_STOREB: // state==READY, memory is ready, case_comb==... begin if (address_is_virtual) // If we need to do a page table lookup if (page_table_lookup_reqd) begin mem_phys_address_out = { root_addr_in [43:14] , 14'b00_0000_0000_0000 + (address_in [34:25] << 3)}; // add VPN_1 * 8 mem_command_out = MEM_READ; end else if (tlb_out_bits [3:2] != 2'b11) // If DIRTY bit or WRITABLE bit is 0, then exception begin mem_command_out = MEM_NOP; exception_detected = 1; if (tlb_out_bits [2] == 0) // If this page is not WRITABLE except_code = CAUSE_PAGE_WRITE; else if (tlb_out_bits [4] == 1) // (page is not DIRTY) If COPY-ON_WRITE set, then exception is needed except_code = CAUSE_PAGE_COPY_ON_WRITE; else // (page is not DIRTY) Otherwise signal PAGE_FIRST_DIRTY exception except_code = CAUSE_PAGE_FIRST_DIRTY; end else // Else the PTE from the TLB is good to use begin mem_command_out = MEM_READ_AND_LOCK; mem_phys_address_out = { tlb_out_ppn , address_in [13:3] , 3'b000 }; // PPN + offset (aligned) tlb_in_command = TLB_PROMOTE; goto_to_finish_op = 1; // If this is a CHECKADDR, skip the actual read and treat it as if an exception happened. if (is_checkaddr_in) begin mem_command_out = MEM_NOP; exception_detected = 1; // except_code defaults to 0. end end else // The address is in the kernel space... begin mem_command_out = MEM_READ_AND_LOCK; mem_phys_address_out = { 8'b0000_0000, address_in [35:3] , 3'b000 }; // rounded to dword alignment goto_to_finish_op = 1; end end CASE_STOREH: // state==READY, memory is ready, case_comb==... begin if (address_is_virtual) // If we need to do a page table lookup if (page_table_lookup_reqd) begin mem_phys_address_out = { root_addr_in [43:14] , 14'b00_0000_0000_0000 + (address_in [34:25] << 3)}; // add VPN_1 * 8 mem_command_out = MEM_READ; end else if (tlb_out_bits [3:2] != 2'b11) // If DIRTY bit or WRITABLE bit is 0, then exception begin mem_command_out = MEM_NOP; exception_detected = 1; if (tlb_out_bits [2] == 0) // If this page is not WRITABLE except_code = CAUSE_PAGE_WRITE; else if (tlb_out_bits [4] == 1) // (page is not DIRTY) If COPY-ON_WRITE set, then exception is needed except_code = CAUSE_PAGE_COPY_ON_WRITE; else // (page is not DIRTY) Otherwise signal PAGE_FIRST_DIRTY exception except_code = CAUSE_PAGE_FIRST_DIRTY; end else // Else the PTE from the TLB is good to use begin mem_command_out = MEM_READ_AND_LOCK; mem_phys_address_out = { tlb_out_ppn , address_in [13:3] , 3'b000 }; // PPN + offset (aligned) tlb_in_command = TLB_PROMOTE; goto_to_finish_op = 1; // If this is a CHECKADDR, skip the actual read and treat it as if an exception happened. if (is_checkaddr_in) begin mem_command_out = MEM_NOP; exception_detected = 1; // except_code defaults to 0. end end else // The address is in the kernel space... begin mem_command_out = MEM_READ_AND_LOCK; mem_phys_address_out = { 8'b0000_0000, address_in [35:3] , 3'b000 }; // rounded to dword alignment goto_to_finish_op = 1; end end CASE_STOREW: // state==READY, memory is ready, case_comb==... begin if (address_is_virtual) // If we need to do a page table lookup if (page_table_lookup_reqd) begin mem_phys_address_out = { root_addr_in [43:14] , 14'b00_0000_0000_0000 + (address_in [34:25] << 3)}; // add VPN_1 * 8 mem_command_out = MEM_READ; end else if (tlb_out_bits [3:2] != 2'b11) // If DIRTY bit or WRITABLE bit is 0, then exception begin mem_command_out = MEM_NOP; exception_detected = 1; if (tlb_out_bits [2] == 0) // If this page is not WRITABLE except_code = CAUSE_PAGE_WRITE; else if (tlb_out_bits [4] == 1) // (page is not DIRTY) If COPY-ON_WRITE set, then exception is needed except_code = CAUSE_PAGE_COPY_ON_WRITE; else // (page is not DIRTY) Otherwise signal PAGE_FIRST_DIRTY exception except_code = CAUSE_PAGE_FIRST_DIRTY; end else // Else the PTE from the TLB is good to use begin mem_command_out = MEM_READ_AND_LOCK; mem_phys_address_out = { tlb_out_ppn , address_in [13:3] , 3'b000 }; // PPN + offset (aligned) tlb_in_command = TLB_PROMOTE; goto_to_finish_op = 1; // If this is a CHECKADDR, skip the actual read and treat it as if an exception happened. if (is_checkaddr_in) begin mem_command_out = MEM_NOP; exception_detected = 1; // except_code defaults to 0. end end else // The address is in the kernel space... begin mem_command_out = MEM_READ_AND_LOCK; mem_phys_address_out = { 8'b0000_0000, address_in [35:3] , 3'b000 }; // rounded to dword alignment goto_to_finish_op = 1; end end CASE_STORED: // state==READY, memory is ready, case_comb==... begin if (address_is_virtual) // If we need to do a page table lookup if (page_table_lookup_reqd) begin mem_phys_address_out = { root_addr_in [43:14] , 14'b00_0000_0000_0000 + (address_in [34:25] << 3)}; // add VPN_1 * 8 mem_command_out = MEM_READ; end else if (tlb_out_bits [3:2] != 2'b11) // If DIRTY bit or WRITABLE bit is 0, then exception begin mem_command_out = MEM_NOP; exception_detected = 1; if (tlb_out_bits [2] == 0) // If this page is not WRITABLE except_code = CAUSE_PAGE_WRITE; else if (tlb_out_bits [4] == 1) // (page is not DIRTY) If COPY-ON_WRITE set, then exception is needed except_code = CAUSE_PAGE_COPY_ON_WRITE; else // (page is not DIRTY) Otherwise signal PAGE_FIRST_DIRTY exception except_code = CAUSE_PAGE_FIRST_DIRTY; end else // Else the PTE from the TLB is good to use begin mem_command_out = MEM_WRITE; mem_phys_address_out = { tlb_out_ppn , address_in [13:0] }; // PPN + offset mem_st_data_out = st_data_in; tlb_in_command = TLB_PROMOTE; // If this is a CHECKADDR, skip the actual read and treat it as if an exception happened. if (is_checkaddr_in) begin mem_command_out = MEM_NOP; exception_detected = 1; // except_code defaults to 0. end end else // The address is in the kernel space... begin mem_command_out = MEM_WRITE; mem_phys_address_out = { 8'b0000_0000, address_in }; mem_st_data_out = st_data_in; end end CASE_CAS: // state==READY, memory is ready, case_comb==... begin if (address_is_virtual) // If we need to do a page table lookup if (page_table_lookup_reqd) begin mem_phys_address_out = { root_addr_in [43:14] , 14'b00_0000_0000_0000 + (address_in [34:25] << 3)}; // add VPN_1 * 8 mem_command_out = MEM_READ; end else if (tlb_out_bits [3:2] != 2'b11) // If DIRTY bit or WRITABLE bit is 0, then exception begin mem_command_out = MEM_NOP; exception_detected = 1; if (tlb_out_bits [2] == 0) // If this page is not WRITABLE except_code = CAUSE_PAGE_WRITE; else if (tlb_out_bits [4] == 1) // (page is not DIRTY) If COPY-ON_WRITE set, then exception is needed except_code = CAUSE_PAGE_COPY_ON_WRITE; else // (page is not DIRTY) Otherwise signal PAGE_FIRST_DIRTY exception except_code = CAUSE_PAGE_FIRST_DIRTY; end else // Else the PTE from the TLB is good to use begin mem_command_out = MEM_READ_AND_LOCK; mem_phys_address_out = { tlb_out_ppn , address_in [13:3] , 3'b000 }; // PPN + offset (aligned) tlb_in_command = TLB_PROMOTE; goto_to_finish_op = 1; end else // The address is in the kernel space... begin mem_command_out = MEM_READ_AND_LOCK; mem_phys_address_out = { 8'b0000_0000, address_in [35:3] , 3'b000 }; // rounded to dword alignment goto_to_finish_op = 1; end end default: begin end endcase end end STATE_READ_ROOT: // i.e., if state == READ_ROOT begin ready_out = 0; done_out = 0; mem_command_out = MEM_READ; mem_phys_address_out = { mem_result_data_in [63:34], 14'b00_0000_0000_0000 + (save_address_in [24:14] << 3) }; // Add VPN_2 * 8 if (mem_done_in) begin // NOTE: mem_result_data_in is also latched into save_pte at this point. if (mem_result_data_in [0] == 0) // if PTE is not valid begin mem_command_out = MEM_NOP; exception_detected = 1; done_out = 1; result_code_out = CAUSE_PAGE_INVALID; end end else // i.e., DONE has already happened in an earlier clock cycle begin mem_phys_address_out = { save_pte [63:34], 14'b00_0000_0000_0000 + (save_address_in [24:14] << 3) }; // Add VPN_2 * 8 end end STATE_READ_INDEX: // i.e., if state == READ_INDEX begin ready_out = 0; done_out = 0; result_code_out = 0; mem_st_data_out = save_st_data_in; if ( (prev_case_comb == CASE_LOADB) | (prev_case_comb == CASE_LOADH) | (prev_case_comb == CASE_LOADW) | (prev_case_comb == CASE_LOADD) | (prev_case_comb == CASE_FETCH) ) mem_command_out = MEM_READ; else if (prev_case_comb == CASE_STORED) mem_command_out = MEM_WRITE; else if ( (prev_case_comb == CASE_STOREB) | (prev_case_comb == CASE_STOREH) | (prev_case_comb == CASE_STOREW) | (prev_case_comb == CASE_CAS) ) begin mem_command_out = MEM_READ_AND_LOCK; goto_to_finish_op = 1; end // TLBCLEAR, TLBFLUSH, and FENCE do not get to this state; default MEM_NOP applies. if (mem_done_in) begin // NOTE: mem_result_data_in is also latched into save_pte at this point. mem_phys_address_out = { mem_result_data_in [63:34], save_address_in [13:0] }; // PPN + offset if (mem_result_data_in [0] == 0) // if PTE is not valid begin mem_command_out = MEM_NOP; exception_detected = 1; done_out = 1; result_code_out = CAUSE_PAGE_INVALID; // except_code = CAUSE_PAGE_INVALID; end else if ((prev_case_comb == CASE_FETCH) & (mem_result_data_in [1] == 0)) // if PTE is not executable begin mem_command_out = MEM_NOP; exception_detected = 1; done_out = 1; result_code_out = CAUSE_PAGE_FETCH; end else if ( (prev_case_comb == CASE_CAS) // CAS treated like store, even when no update | (prev_case_comb == CASE_STOREB) | (prev_case_comb == CASE_STOREH) | (prev_case_comb == CASE_STOREW) | (prev_case_comb == CASE_STORED) ) begin if (mem_result_data_in [3:2] != 2'b11) // If DIRTY and WRITABLE, then no exception begin // otherwise DIRTY bit or WRITABLE bit is 0 mem_command_out = MEM_NOP; exception_detected = 1; done_out = 1; if (mem_result_data_in [2] == 0) // If this page is not WRITABLE result_code_out = CAUSE_PAGE_WRITE; else if (mem_result_data_in [4] == 1) // (page is not DIRTY) COPY-ON_WRITE exception wanted result_code_out = CAUSE_PAGE_COPY_ON_WRITE; else // (page is not DIRTY) COPY-ON_WRITE not wanted result_code_out = CAUSE_PAGE_FIRST_DIRTY; end end // If this PTE is valid, then add it to the TLB registers, pushing it on top. if (result_code_out != CAUSE_PAGE_INVALID) begin tlb_in_asid = save_asid_in; tlb_in_vpn = save_address_in [34:14]; tlb_in_ppn = mem_result_data_in [63:34]; tlb_in_bits = mem_result_data_in [4:0]; tlb_in_command = TLB_ADD; end if (save_is_checkaddr_in) begin mem_command_out = MEM_NOP; exception_detected = 1; done_out = 1; end; end else // i.e., DONE has already happened in an earlier clock cycle begin mem_phys_address_out = { save_pte [63:34], save_address_in [13:0] }; // PPN + offset end end STATE_FINISH_OP: // Now perform the WRITE for STOREB/H/W or CAS begin ready_out = 0; done_out = 0; mem_command_out = MEM_WRITE; if (save_address_in [35] == 1) // if address in virtual space mem_phys_address_out = { save_ppn_from_tlb, save_address_in [13:3] , 3'b000 }; // PPN + offset else mem_phys_address_out = { 8'b0000_0000, save_address_in [35:3] , 3'b000 }; if (mem_done_in) // NOTE: mem_result_data_in is also latched into save_mem_result_data at this point. // inject the byte/halfword/word into the current output from the MEMORY_UNIT. inj_old_dword_in = mem_result_data_in; else // i.e., DONE has already happened in an earlier clock cycle // inject the byte/halfword/word into previously saved output from the MEMORY_UNIT. inj_old_dword_in = save_mem_result_data; inj_new_data_in = save_st_data_in; inj_addr_in = save_address_in [2:0]; if (prev_case_comb == CASE_STOREB) inj_size_in = SZ_BYTE [1:0]; else if (prev_case_comb == CASE_STOREH) inj_size_in = SZ_HALFWORD [1:0]; else if (prev_case_comb == CASE_STOREW) inj_size_in = SZ_WORD [1:0]; if (prev_case_comb != CASE_CAS) // i.e., STOREB/H/W mem_st_data_out = inj_new_dword_out; else if(save_cas_old_data_in == inj_old_dword_in) // i.e., if the expected value matches the old value begin // mem_command_out = MEM_WRITE; (default from above) mem_st_data_out = save_st_data_in; if (mem_done_in) begin // result_data_out = 1; // result_code_out = 0; // done_out = 1; cas_result = 1; end end else begin mem_command_out = MEM_NOP; if (mem_done_in) begin // result_data_out = 0; // result_code_out = 0; // done_out = 1; cas_result = 0; end end end default: // state = STATE_INVALID ; endcase end TLB_REGISTERS MyTLB ( .clock (clock), .clock_enable (clock_enable), .reset (reset), // The TLB Entry to store or look up; key = {asid,vpn} .tlb_in_asid (tlb_in_asid), // . Address Space Identifier .tlb_in_vpn (tlb_in_vpn), // . Virtual Page Number (note 21 bits + 14 bit offset = 35 bits) .tlb_in_ppn (tlb_in_ppn), // . Physical Page Number (note 30 bits + 14 bit offset = 44 bits) .tlb_in_bits (tlb_in_bits), // . 4=Copy-on-write, 3=Dirty, 2=Writable, 1=eXecutable, 0=Valid .tlb_in_command (tlb_in_command), // . // The TLB Entry that was retrieved .tlb_out_asid (tlb_out_asid), // . ASID .tlb_out_vpn (tlb_out_vpn), // . Virtual Page Number .tlb_out_ppn (tlb_out_ppn), // . Physical Page Number .tlb_out_bits (tlb_out_bits), // . 4=C, 3=D, 2=W, 1=X, 0=V .tlb_out_match_found (tlb_out_match_found), // 1=the search key (asid-vpn) matched and output is valid .tlb_in_debug_regnum (tlb_in_debug_regnum), // For CONTROL, the TLB register number .tlb_out_debug_regval (tlb_out_debug_regval) // . The TLB register contents (with PPN reduced to 22 bits) ); EXTRACTOR MyExtractor ( .data_in (ex_data_in), .addr (ex_addr_in), .size (ex_size_in), // e.g., SZ_BYTE, SZ_HALFWORD, SZ_WORD, SZ_DOUBLEWORD .data_out (ex_data_out) ); INJECTOR MyInjector ( .old_dword_in (inj_old_dword_in), .new_data_in (inj_new_data_in), .addr_in (inj_addr_in), .size_in (inj_size_in), // e.g., SZ_BYTE, SZ_HALFWORD, SZ_WORD, SZ_DOUBLEWORD .new_dword_out (inj_new_dword_out) ); endmodule // MMU //------------------------------------------------------------- // // TLB - TRANSLATION LOOKASIDE BUFFERS // // This module contains the TLB registers. It is used by the Memory Management Unit (MMU). // //------------------------------------------------------------- module TLB_REGISTERS ( input clock, input clock_enable, input reset, // The TLB Entry to store or look up; key = {asid,vpn} input [15:0] tlb_in_asid, // . Address Space Identifier input [20:0] tlb_in_vpn, // . Virtual Page Number (note 21 bits + 14 bit offset = 35 bits) input [29:0] tlb_in_ppn, // . Physical Page Number (note 30 bits + 14 bit offset = 44 bits) input [4:0] tlb_in_bits, // . 4=Copy-on-write, 3=Dirty, 2=Writable, 1=eXecutable, 0=Valid input TLB_COMMAND tlb_in_command, // . // The TLB Entry that was retrieved output logic [15:0] tlb_out_asid, // . ASID output logic [20:0] tlb_out_vpn, // . Virtual Page Number output logic [29:0] tlb_out_ppn, // . Physical Page Number output logic [4:0] tlb_out_bits, // . 4=C, 3=D, 2=W, 1=X, 0=V output logic tlb_out_match_found, // 1=the search key (asid-vpn) matched and output is valid input [15:0] tlb_in_debug_regnum, // For CONTROL, the TLB register number output logic [63:0] tlb_out_debug_regval // . The TLB register contents (with PPN reduced to 22 bits) ); reg [NUMBER_OF_TLB_REGS-1:0] [71:0] tlb_regs; // { asid, vpn, ppn, bits } logic [TLB_ADDR_SIZE-1:0] addr_of_first_match; logic [71:0] matching_tlb_entry; logic [NUMBER_OF_TLB_REGS-1:0] shift_down; // shift_down[N]: Do we need to shift down from [N-1] to [N]? // Given a search key (tlb_in_asid and tlb_in_vpn), search the TLB as // content-addressable-memory. If a valid matching entry is found, // set "tlb_out_match_found" to TRUE and set "addr_of_first_match" // to the number of the TLB reg that matched. always_comb begin tlb_out_debug_regval = 0; tlb_out_match_found = 0; addr_of_first_match = 0; for (integer k = 0; k < NUMBER_OF_TLB_REGS; k = k + 1) begin if ( (tlb_regs[k][71:56] == tlb_in_asid) & // Check the ASID (tlb_regs[k][55:35] == tlb_in_vpn) & // Check the VIRT_PAGE_NUMBER (tlb_regs[k][0] == 1'b1) & // Check the VALID bit !tlb_out_match_found ) begin tlb_out_match_found = 1; addr_of_first_match = k[TLB_ADDR_SIZE-1:0]; end else begin addr_of_first_match = addr_of_first_match; end if (k == tlb_in_debug_regnum) tlb_out_debug_regval = { tlb_regs[k][71:35] , tlb_regs[k][26:0] }; end // Get the contents of the reg that was found. If not found, we don't care. matching_tlb_entry = tlb_regs[addr_of_first_match]; // Always send matching entry to the output. tlb_out_asid = matching_tlb_entry [71:56]; tlb_out_vpn = matching_tlb_entry [55:35]; tlb_out_ppn = matching_tlb_entry [34:5]; tlb_out_bits = matching_tlb_entry [4:0]; // Compute shift_down shift_down [0] = 1; // (never used) for (integer k = 0; k < NUMBER_OF_TLB_REGS-1; k++) if ( tlb_regs[k][0] // If this reg is valid & ( (tlb_regs[k][71:56] != tlb_in_asid) // and it does not match ASID or VPN | (tlb_regs[k][55:35] != tlb_in_vpn) ) & shift_down [k] ) // and shift_down from above is 1... shift_down [k+1] = 1; else shift_down [k+1] = 0; end always_ff @(posedge clock) if (clock_enable) begin if (reset) // Set VALID to 0 for all TLB regs for (integer k = 0; k < NUMBER_OF_TLB_REGS; k++) tlb_regs[k] <= 0; else case (tlb_in_command) TLB_CLEAR: // For any entry that matches on ASID, clear its "VALID" bit for (integer k = 0; k < NUMBER_OF_TLB_REGS; k++) if (tlb_regs[k][71:56] == tlb_in_asid) tlb_regs[k][0] <= 0; TLB_FLUSH: // For any entry that matches on ASID and VPN, clear its "VALID" bit for (integer k = 0; k < NUMBER_OF_TLB_REGS; k++) if ((tlb_regs[k][71:56] == tlb_in_asid) & (tlb_regs[k][55:35] == tlb_in_vpn)) tlb_regs[k][0] <= 0; TLB_PROMOTE: begin // For all registers, if shift down from above is true, do the shifting for (integer k = NUMBER_OF_TLB_REGS-1; k > 0; k--) if (shift_down [k]) tlb_regs[k] <= tlb_regs[k-1]; // Else if this entry matches the search key, mark it invalid else if ((tlb_regs[k][71:56] == tlb_in_asid) & (tlb_regs[k][55:35] == tlb_in_vpn)) tlb_regs[k][0] <= 0; // Load reg 0 from the reg that matched. tlb_regs [0] = matching_tlb_entry; end TLB_ADD: begin // For all registers, if shift down from above is true, do the shifting for (integer k = NUMBER_OF_TLB_REGS-1; k > 0; k--) if (shift_down [k]) tlb_regs[k] <= tlb_regs[k-1]; // Else if this entry matches the search key, mark it invalid else if ((tlb_regs[k][71:56] == tlb_in_asid) & (tlb_regs[k][55:35] == tlb_in_vpn)) tlb_regs[k][0] <= 0; // Load reg 0 from tlb_in. tlb_regs [0] = { tlb_in_asid, tlb_in_vpn, tlb_in_ppn, tlb_in_bits }; end default: // including TLB_NOP begin end endcase end // if clock_enable endmodule // TLB_REGISTERS //------------------------------------------------------------- // // MEMORY SUBSYSTEM // // This module encapsulates the RAM, ROM, and Memory-Mapped I/O devices. // It is used by the Memory Management Unit (MMU): The MMU communicates // with this module which communicates with RAM, ROM, and the Memory-Mapped // I/O devices. // // Sub-modules included within this module: // MyRAM RAM_MEMORY // MyROM ROM_MEMORY // Not yet implemented: // Any memory-mapped I/O // // This version works for a single core and is not meant for multi-core systems. // // This module handles the following commands: // MEM_NOP Do nothing; remain ready, do not raise done // MEM_READ Read a doubleword (using d-cache if any) // MEM_READ_INST Read a doubleword (using i-cache if any) // MEM_READ_AND_LOCK Read a doubleword and lock until next command // MEM_WRITE Write a doubleword // MEM_FENCE Clear all store buffers, propagating updates to other cores. // // The MEM_READ_AND_LOCK is needed for the correct implementation of CAS and // STOREB/H/W in the presence of other cores. Since there are no other cores, // this command is identical to MEM_READ. But the idea is that a MMU must // acquire exclusive access to (and lock down) the memory doubleword in question, // preventing other cores from accessing it until a subsequent MEM_WRITE has been // performed. MEM_READ_AND_LOCK will always be followed by a MEM_WRITE, but any // other operation would release the lock. // // In the future, this memory system may be modified to include a "store buffer". // To enforce proper concurrency control, this buffer must be emptied, and this // is the function of the MEM_FENCE command. This command will cause all pending // write operations to be completed and all changes to memory to be propagated to // other cores, so that no out-of-date versions of the data written will ever again // be seen by other cores. This command must delay and not complete (i.e., raise // DONE) until this has been achieved. In this implementation, it does nothing // since there are no other cores and no store buffer. // // This module raises the READY output when it is open to receiving a command. A // command will always be accepted when READY is high, but this could be MEM_NOP. // The address_in must be valid and will be accepted on the same cycle. For // WRITE operations, the st_data_in will also be accepted on the same cycle. When // the operation is completed, this module will raise the DONE output for a single // cycle. For READ operations, the result data will be valid simultaneously, only // when DONE is high. The READY line will once again be raised when this module is // again ready and able to accept a command. Generally speaking, the READY line // will be raised at the same time as the DONE, allowing the next command to be // accepted while the output from the previous command is valid. This permits one // incoming command on every cycle, and one result on every cycle, with the output // appearing in the cycle following the command. // // This module defines the following parameters: // READY_DELAY // DONE_DELAY // which can be adjusted to set add extra cycles before responding with DONE and // additional cycles to wait before becoming READY for the next command. The // purpose of these is to test and verify the core can deal properly with delays. // // With DONE_DELAY and READY_DELAY both set to 0, this module responds on the // very next cycle. Thus, a series of simple (e.g., ALU) instructions will be // executed with one instruction per cycle. // // With DONE_DELAY + READY_DELAY = N, the memory will introduce a delay; Each // ALU instruction will now take 1+N cycles. // // (Actually, the FETCH_UNIT contains an instruction buffer which will be used // whenever possible, so even if there is a significant delay with RAM accesses, // the FETCH_UNIT will improve performance somewhat. The FETCH_UNIT will be useful // in future systems where RAM access takes more than a single cycle.) // //------------------------------------------------------------- module MEMORY_SYSTEM ( input clock, input clock_enable, input reset, input [43:0] phys_address_in, // Communication to/from the MMU input MEM_COMMAND command_in, // . input [63:0] st_data_in, // . output logic [63:0] result_data_out, // . output logic done_out, // . output reg ready_out // . ); localparam READY_DELAY = 2; // 2 localparam DONE_DELAY = 2; // 1 localparam RAM_BEYOND = RAM_START + (8 * RAM_MEMORY_SIZE_IN_DWORDS); localparam ROM_BEYOND = ROM_START + (8 * ROM_MEMORY_SIZE_IN_DWORDS); localparam NOTHING = 0; // "select" -- which is 3 bits localparam RAM_READ = 1; localparam RAM_WRITE = 2; localparam ROM_READ = 3; localparam UNIMPL = 4; reg [3:0] r_cnt; reg [3:0] d_cnt; reg ready; reg done; reg prev_ready; reg [63:0] save_dword; reg [2:0] save_select; // NOTHING, RAM_READ, RAM_WRITE, ROM_READ, UNIMPL logic [2:0] select; // NOTHING, RAM_READ, RAM_WRITE, ROM_READ, UNIMPL logic want_read; logic want_write; logic ram_wr_enable; logic [63:0] ram_out; logic [63:0] rom_out; logic [35:0] phys_address; assign phys_address = phys_address_in [35:0]; // Ignore upper 8 bits of 16TB phys address. ROM_MEMORY MyROM ( .addr (phys_address), .data_val (rom_out) ); RAM_MEMORY MyRAM ( .clock (clock), .clock_enable (clock_enable), .addr (phys_address - RAM_START), .data_out (ram_out), .write_enab (ram_wr_enable), .data_in (st_data_in) ); assign done_out = done; always_comb begin ready_out = ready; select = NOTHING; ram_wr_enable = 0; want_read = 0; want_write = 0; result_data_out = 0; case (command_in) MEM_READ: want_read = 1; MEM_READ_INST: want_read = 1; MEM_READ_AND_LOCK: want_read = 1; MEM_WRITE: want_write = 1; default: ; // i.e., MEM_NOP, MEM_FENCE endcase if ((phys_address >= RAM_START) & (phys_address < RAM_BEYOND) & (want_read | want_write)) if (want_read) select = RAM_READ; else select = RAM_WRITE; else if ((phys_address >= ROM_START) & (phys_address < ROM_BEYOND) & want_read) select = ROM_READ; else if (command_in == MEM_NOP) select = NOTHING; else // i.e., MEM_FENCE select = UNIMPL; if (done) begin if (save_select == ROM_READ) result_data_out = save_dword; else if (save_select == RAM_READ) if (prev_ready) result_data_out = ram_out; else result_data_out = save_dword; end else result_data_out = 0; if (ready & (select == RAM_WRITE)) ram_wr_enable = 1; end always_ff @(posedge clock) begin if (clock_enable) if (reset) begin ready <= 1; prev_ready <= 0; prev_ready <= 0; done <= 0; d_cnt <= DONE_DELAY [3:0]; r_cnt <= READY_DELAY [3:0]; save_select <= NOTHING; save_dword <= 0; end else begin if ((DONE_DELAY == 0) & (READY_DELAY == 0)) begin ready <= 1; // d_cnt <= 0; // Initialized at reset and never changing // r_cnt <= 0; if (select == NOTHING) done <= 1; else done <= 0; end else if ((d_cnt == 0) & (r_cnt == 0)) begin ready <= 1; d_cnt <= DONE_DELAY [3:0]; r_cnt <= READY_DELAY [3:0]; end else if (ready & (select == NOTHING)) ; // stay ready, with d_cnt and r_cnt already initialized else begin ready <= 0; if (d_cnt != 0) d_cnt <= d_cnt - 4'd1; else r_cnt <= r_cnt - 4'd1; end if ((DONE_DELAY == 0) & (READY_DELAY == 0) & (select != NOTHING)) done <= 1; else if (DONE_DELAY == 0) if (ready & (select != NOTHING)) done <= 1; else done <= 0; else // DONE_DELAY > 0 if ((d_cnt == 0) & (r_cnt == READY_DELAY)) done <= 1; else done <= 0; prev_ready <= ready; if (ready) begin save_select <= select; end if (ready & (select == ROM_READ)) save_dword <= rom_out; else if (prev_ready & (save_select == RAM_READ)) save_dword <= ram_out; end end endmodule