/* (c) Raphaƫl Jacquot 2019 This file is part of hp_saturn. hp_saturn is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. hp_saturn is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar. If not, see . */ `ifndef _SATURN_ALU_PC `define _SATURN_ALU_PC `default_nettype none // module saturn_alu_pc ( i_clk, i_reset, i_stalled, i_just_reset, i_alu_active, i_cycle_ctr, i_phase, i_phase_0, i_phase_2, i_phase_3, i_alu_initializing, i_v_dest_counter_ptr, o_bus_address, o_bus_load_pc, i_bus_nibble_in, i_alu_stall_dec, i_ins_rtn, i_ins_test_go, i_push, i_pop, i_mode_jmp, i_carry, i_op_jump, i_op_jmp_rel_2, i_op_jmp_rel_3, i_op_jmp_rel_4, i_op_jmp_abs_5, o_do_apply_jump, `ifdef SIM o_pc, o_rstk_ptr, o_rstk_0, o_rstk_1, o_rstk_2, o_rstk_3, o_rstk_4, o_rstk_5, o_rstk_6, o_rstk_7 `else o_pc `endif ); input wire [0:0] i_clk; input wire [0:0] i_reset; input wire [0:0] i_stalled; input wire [0:0] i_just_reset; input wire [0:0] i_alu_active; input wire [31:0] i_cycle_ctr; input wire [1:0] i_phase; input wire [0:0] i_phase_0; input wire [0:0] i_phase_2; input wire [0:0] i_phase_3; input wire [0:0] i_alu_initializing; input wire [2:0] i_v_dest_counter_ptr; output reg [19:0] o_bus_address; output reg [0:0] o_bus_load_pc; input wire [3:0] i_bus_nibble_in; input wire [0:0] i_alu_stall_dec; input wire [0:0] i_ins_rtn; input wire [0:0] i_ins_test_go; input wire [0:0] i_push; input wire [0:0] i_pop; input wire [0:0] i_mode_jmp; input wire [0:0] i_carry; input wire [0:0] i_op_jump; input wire [0:0] i_op_jmp_rel_2; input wire [0:0] i_op_jmp_rel_3; input wire [0:0] i_op_jmp_rel_4; input wire [0:0] i_op_jmp_abs_5; output wire [0:0] o_do_apply_jump; output wire [19:0] o_pc; `ifdef SIM output wire [2:0] o_rstk_ptr; output wire [19:0] o_rstk_0; output wire [19:0] o_rstk_1; output wire [19:0] o_rstk_2; output wire [19:0] o_rstk_3; output wire [19:0] o_rstk_4; output wire [19:0] o_rstk_5; output wire [19:0] o_rstk_6; output wire [19:0] o_rstk_7; `endif /* module 5: * manages all that is linked with the program counter */ /* main PC and RSTK registers */ reg [2:0] rstk_ptr; reg [19:0] PC; reg [19:0] RSTK[0:7]; assign o_pc = PC; `ifdef SIM assign o_rstk_ptr = rstk_ptr; assign o_rstk_0 = RSTK[0]; assign o_rstk_1 = RSTK[1]; assign o_rstk_2 = RSTK[2]; assign o_rstk_3 = RSTK[3]; assign o_rstk_4 = RSTK[4]; assign o_rstk_5 = RSTK[5]; assign o_rstk_6 = RSTK[6]; assign o_rstk_7 = RSTK[7]; `endif // assign goyes_off = {{12{i_imm_value[3]}}, i_imm_value, jump_off[3:0]}; // assign goyes_pc = jump_bse + goyes_off; // // rtnyes is already handled by i_ins_test_go // assign is_rtn_rel2 = (alu_op == `ALU_OP_JMP_REL2) && (goyes_off == 0); // assign is_jmp_rel2 = (alu_op == `ALU_OP_JMP_REL2) && !(goyes_off == 0); // assign jmp_carry_test = (i_test_carry && (CARRY == i_carry_val)); // assign exec_rtn_rel2 = is_rtn_rel2 && jmp_carry_test && alu_done; // // assign set_jmp_rel2 = is_jmp_rel2 && jmp_carry_test && alu_finish; // assign exec_jmp_rel2 = is_jmp_rel2 && jmp_carry_test && alu_done; /* jump values generator */ reg [2:0] jump_offset_counter; reg [19:0] jump_base; reg [15:0] jump_offset; reg [19:0] new_jump_offset; reg [0:0] jump_start; reg [0:0] jump_done; wire [0:0] jump_relative; assign jump_relative = i_op_jmp_rel_2 || i_op_jmp_rel_3 || i_op_jmp_rel_4; // wire [0:0] do_set_jump_base; wire [0:0] do_pre_calc_jump; wire [0:0] do_calc_jump; // assign do_set_jump_base = start_in_jmp_mode && !jump_done && jump_start; assign do_pre_calc_jump = !i_stalled && i_op_jump && i_phase_2 && !jump_done; assign do_calc_jump = i_mode_jmp && i_phase_3 && !jump_done; assign o_do_apply_jump = i_mode_jmp && i_phase_3 && jump_done; wire [19:0] jump_pc; assign jump_pc = jump_relative?(jump_base+new_jump_offset):new_jump_offset; /* pc update generator */ reg [19:0] pc_plus_1; reg [2:0] rstk_ptr_plus_1; reg [2:0] rstk_ptr_minus_1; wire [19:0] next_pc; wire [0:0] update_pc; wire [0:0] reload_pc; wire [0:0] pop_pc; wire [0:0] push_pc; wire [0:0] pc_lines_cleanup; assign next_pc = (jump_done)?jump_pc:pc_plus_1; assign update_pc = (!i_reset && i_just_reset) || i_alu_active && i_phase_3 && (!i_alu_stall_dec) /* || exec_unc_jmp || exec_jmp_rel2 */; assign pop_pc = i_alu_active && i_phase_3 && i_pop && i_ins_rtn && ((!i_ins_test_go) || (i_ins_test_go && i_carry)); assign push_pc = i_alu_active && i_push && o_do_apply_jump; assign reload_pc = (!i_reset && i_just_reset) || o_do_apply_jump || pop_pc; assign pc_lines_cleanup = i_alu_active && i_phase_0; always @(posedge i_clk) begin /* * initializes default values */ if (i_reset) begin PC <= ~0; o_bus_load_pc <= 0; rstk_ptr <= 0; jump_offset_counter <= 0; jump_base <= 0; jump_offset <= 0; jump_done <= 0; end /* on every clock, we update * pc + 1 * rstk_ptr - 1 * rstk_ptr + 1 */ pc_plus_1 <= PC + 20'd1; rstk_ptr_minus_1 <= rstk_ptr - 3'd1; rstk_ptr_plus_1 <= rstk_ptr + 3'd1; /* * Similarly to the data registers, * initializes the RSTK while the PC is first loaded * */ if (i_alu_initializing) RSTK[i_v_dest_counter_ptr] <= 0; /** * handles jumps * */ /* nibble was read in phase 1 * in phase 2, we precalculate all values for a jump */ if (do_pre_calc_jump) begin $display("ALU_PC %0d: [%d] pre_calc_jump %0d %h", i_phase, i_cycle_ctr, jump_offset_counter, i_bus_nibble_in); case (jump_offset_counter) 0: begin new_jump_offset <= {{16{i_bus_nibble_in[3] && jump_relative}}, i_bus_nibble_in}; jump_start <= 1'b1; jump_base <= PC; end 1: begin new_jump_offset <= {{12{i_bus_nibble_in[3] && jump_relative}}, i_bus_nibble_in, jump_offset[ 3:0]}; if (i_op_jmp_rel_2) jump_done <= 1'b1; end 2: begin new_jump_offset <= {{ 8{i_bus_nibble_in[3] && jump_relative}}, i_bus_nibble_in, jump_offset[ 7:0]}; if (i_op_jmp_rel_3) jump_done <= 1'b1; end 3: begin new_jump_offset <= {{ 4{i_bus_nibble_in[3] && jump_relative}}, i_bus_nibble_in, jump_offset[11:0]}; if (i_op_jmp_rel_4) jump_done <= 1'b1; end 4: begin new_jump_offset <= {i_bus_nibble_in, jump_offset[15:0]}; if (i_op_jmp_abs_5) jump_done <= 1'b1; end default: begin end endcase end /* * in phase 3, we either update the counter */ if (do_calc_jump) begin $display("ALU_PC %0d: [%d] calc jump %0d | nibble %h | rel %b | base %h | offset %h | jump_pc %h", i_phase, i_cycle_ctr, jump_offset_counter, i_bus_nibble_in, jump_relative, jump_base, new_jump_offset, jump_pc); jump_offset <= new_jump_offset[15:0]; jump_offset_counter <= jump_offset_counter + 3'b1; end /* * or apply the jump */ if (o_do_apply_jump) begin $display("ALU_PC %0d: [%d] apply jump %0d | nibble %h | rel %b | base %h | offset %h | jump_pc %h", i_phase, i_cycle_ctr, jump_offset_counter, i_bus_nibble_in, jump_relative, jump_base, new_jump_offset, jump_pc); jump_offset_counter <= 3'b0; new_jump_offset <= 20'b0; jump_start <= 1'b0; jump_done <= 1'b0; end /** * * Update the PC. * Request the new PC be loaded to the other modules through * the bus if necessary * */ if (update_pc) begin // $display("ALU_PC %0d: [%d] update pc to %h", phase, i_cycle_ctr, next_pc); PC <= pop_pc ? RSTK[rstk_ptr_minus_1] : next_pc; end if (push_pc) begin $display("ALU_PC %0d: [%d] PUSH PC %5h to RSTK[%0d]", i_phase, i_cycle_ctr, pc_plus_1, rstk_ptr); RSTK[rstk_ptr] <= pc_plus_1; rstk_ptr <= rstk_ptr_plus_1; end // $display("pop %b && rtn %b && ((!go %b) || (go %b && c %b))", // i_pop, i_ins_rtn, !i_ins_test_go, i_ins_test_go, c_carry); if (pop_pc) begin $display("ALU_PC %0d: [%d] POP RSTK[%0d] to PC %5h", i_phase, i_cycle_ctr, rstk_ptr_minus_1, RSTK[rstk_ptr_minus_1]); rstk_ptr <= rstk_ptr_minus_1; RSTK[rstk_ptr_minus_1] <= 20'b0; end if (reload_pc) begin $display("ALU_PC %0d: [%d] $$$$ RELOADING PC to %h $$$$", i_phase, i_cycle_ctr, (pop_pc ? RSTK[rstk_ptr_minus_1] : next_pc)); o_bus_address <= pop_pc ? RSTK[rstk_ptr_minus_1] : next_pc; o_bus_load_pc <= 1'b1; end if (pc_lines_cleanup && o_bus_load_pc) o_bus_load_pc <= 1'b0; end endmodule `endif