/* (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 . */ /****************************************************************************** * * Instruction decoder module * *****************************************************************************/ `include "def-fields.v" `include "def-alu.v" module saturn_decoder( i_clk, i_reset, i_cycles, i_en_dbg, i_en_dec, i_bus_load_pc, i_stalled, i_pc, i_nibble, i_reg_p, o_inc_pc, o_push, o_pop, o_dec_error, `ifdef SIM o_unimplemented, `endif o_alu_debug, o_ins_addr, o_ins_decoded, o_fields_table, o_field, o_field_valid, o_field_start, o_field_last, o_imm_value, o_alu_op, o_alu_no_stall, o_reg_dest, o_reg_src1, o_reg_src2, o_ins_rtn, o_set_xm, o_en_intr, o_set_carry, o_test_carry, o_carry_val, o_ins_set_mode, o_mode_dec, o_ins_alu_op, o_ins_test_go, o_ins_reset, o_ins_config, o_ins_mem_xfr, o_xfr_dir_out, o_dbg_nibbles, o_dbg_nb_nbls, o_mem_load, o_mem_pos ); /* * module input / output ports */ input wire [0:0] i_clk; input wire [0:0] i_reset; input wire [31:0] i_cycles; input wire [0:0] i_en_dbg; input wire [0:0] i_en_dec; input wire [0:0] i_bus_load_pc; input wire [0:0] i_stalled; input wire [19:0] i_pc; input wire [3:0] i_nibble; input wire [3:0] i_reg_p; output reg [0:0] o_inc_pc; output reg [0:0] o_push; output reg [0:0] o_pop; output reg [0:0] o_dec_error; `ifdef SIM output reg [0:0] o_unimplemented; `endif output reg [0:0] o_alu_debug; // instructions related outputs output reg [19:0] o_ins_addr; output reg [0:0] o_ins_decoded; output reg [1:0] o_fields_table; output reg [3:0] o_field; output reg o_field_valid; output reg [3:0] o_field_start; output reg [3:0] o_field_last; output reg [3:0] o_imm_value; output reg [4:0] o_alu_op; output reg [0:0] o_alu_no_stall; output reg [4:0] o_reg_dest; output reg [4:0] o_reg_src1; output reg [4:0] o_reg_src2; // rtn specific output reg [0:0] o_ins_rtn; output reg [0:0] o_set_xm; output reg [0:0] o_set_carry; output reg [0:0] o_test_carry; output reg [0:0] o_carry_val; output reg [0:0] o_en_intr; // setdec/hex output reg [0:0] o_ins_set_mode; output reg [0:0] o_mode_dec; // alu_operations output reg [0:0] o_ins_alu_op; output reg [0:0] o_ins_test_go; // bus operations output reg [0:0] o_ins_reset; output reg [0:0] o_ins_config; output reg [0:0] o_ins_mem_xfr; output reg [0:0] o_xfr_dir_out; /* data used by the debugger * */ output reg [(21*4-1):0] o_dbg_nibbles; output reg [4:0] o_dbg_nb_nbls; output reg [63:0] o_mem_load; output reg [4:0] o_mem_pos; /* * state registers */ reg [31:0] inst_counter; reg [0:0] next_nibble; reg [4:0] inst_cycles; reg inval_opcode_regs; initial begin `ifdef SIM // $monitor({"i_clk %b | i_reset %b | i_cycles %d | i_en_dec %b | i_en_exec %b |", // " next_nibble %b | instr_start %b | i_nibble %h"}, // i_clk, i_reset, i_cycles, i_en_dec, i_en_exec, next_nibble, // instr_start, i_nibble); // $monitor("i_en_dec %b | i_cycles %d | nb %h | cont %b | b0x %b | rtn %b | sxm %b | sc %b | cv %b", // i_en_dec, i_cycles, i_nibble, next_nibble, block_0x, ins_rtn, set_xm, set_carry, carry_val); `endif end `include "saturn_decoder_debugger.v" /****************************************************************************** * * handles part of the instruction decoding, * acts as the main FSM * *****************************************************************************/ // general variables reg use_fields_tbl; wire count_cycles; wire decoder_active; // wire decoder_stalled; wire do_on_first_nibble; wire do_on_other_nibbles; assign count_cycles = !i_reset && i_en_dec && (next_nibble || i_stalled); assign decoder_active = !i_reset && i_en_dec && !i_stalled; // assign decoder_stalled = !i_reset && i_en_dec && i_stalled; assign do_on_first_nibble = decoder_active && !next_nibble; assign do_on_other_nibbles = decoder_active && next_nibble; // all regs and wires for the decoder states `include "saturn_decoder_block_vars.v" /* * variables specific to a particular use */ reg [4:0] mem_load_max; /* most instructions are groupped by sets of 4 with * varrying series of registers that are common * this generates all the required series from i_nibble */ wire [4:0] dbg_write_pos; assign dbg_write_pos = (!next_nibble?0:o_dbg_nb_nbls); always @(posedge i_clk) begin if (i_reset) begin inst_cycles <= 0; inst_counter <= 0; next_nibble <= 0; use_fields_tbl <= 0; o_inc_pc <= 1; o_dec_error <= 0; `ifdef SIM o_unimplemented <= 0; `endif o_alu_debug <= 0; o_ins_decoded <= 0; o_alu_op <= 0; o_ins_rtn <= 0; o_push <= 0; o_pop <= 0; o_ins_set_mode <= 0; o_ins_reset <= 0; o_ins_config <= 0; o_ins_mem_xfr <= 0; o_xfr_dir_out <= 0; o_test_carry <= 0; end if (decoder_active) begin /* * stuff that is always done */ `ifdef SIM // $display("DEC_RUN 2: nibble %h", i_nibble); `endif o_inc_pc <= 1; // may be set to 0 later o_dbg_nibbles[dbg_write_pos*4+:4] <= i_nibble; o_dbg_nb_nbls <= o_dbg_nb_nbls + 1; end // if (decoder_stalled) begin // $display("DEC_STAL 2:"); // end if (count_cycles) begin inst_cycles <= inst_cycles + 1; end /* * cleanup */ if (do_on_first_nibble) begin inst_counter <= inst_counter + 1; inst_cycles <= 1; next_nibble <= 1; use_fields_tbl <= 0; o_alu_debug <= 0; o_push <= 0; o_pop <= 0; o_ins_decoded <= 0; `ifdef SIM o_unimplemented <= 1; `endif // store the address where the instruction starts o_ins_addr <= i_pc; // decoder subroutine states block_load_reg_imm <= 0; // cleanup fields table variables go_fields_table <= 0; o_fields_table <= 3; o_alu_op <= 0; o_alu_no_stall <= 0; o_ins_rtn <= 0; o_set_xm <= 0; o_set_carry <= 0; o_carry_val <= 0; o_ins_set_mode <= 0; o_mode_dec <= 0; o_ins_alu_op <= 0; o_ins_test_go <= 0; // bus instructions $display("cleanup instruction modes"); o_ins_reset <= 0; o_ins_config <= 0; o_ins_mem_xfr <= 0; o_xfr_dir_out <= 0; // counters for debugger info o_dbg_nb_nbls <= 1; o_mem_pos <= 0; /* * x first nibble */ if (block_jump_test) begin // $display("BLOCK JUMP_TEST ON %h", i_nibble); o_pop <= i_nibble == 0; o_alu_no_stall <= 1; // o_alu_debug <= 1; o_ins_test_go <= 1; o_imm_value <= i_nibble; block_jump_test2 <= 1; block_jump_test <= 0; end else begin // assign block regs // $display("FIRST NIBBLE %h", i_nibble); case (i_nibble) 4'h0: block_0x <= 1; 4'h1: block_1x <= 1; 4'h2: block_2x <= 1; 4'h3: block_3x <= 1; 4'h4, 4'h5: begin // 400 RTNC // 420 NOP3 // 4xy GOC // 500 RTNNC // 5xy GONC o_alu_debug <= 1; o_alu_no_stall <= 1; o_alu_op <= `ALU_OP_JMP_REL2; mem_load_max <= 1; o_mem_pos <= 0; o_test_carry <= 1; o_carry_val <= !i_nibble[0]; block_jmp <= 1; end 4'h6, 4'h7: begin // 6xxx GOTO // 7xxx GOSUB o_alu_no_stall <= 1; o_alu_op <= `ALU_OP_JMP_REL3; mem_load_max <= 2; o_mem_pos <= 0; o_push <= i_nibble[0]; block_jmp <= 1; `ifdef SIM o_unimplemented <= 0; `endif end 4'h8: block_8x <= 1; 4'h9: begin go_fields_table <= 1; // we don't know, safe bet is table a, but could be table b, // works either way, table is fixed on the next nibble o_fields_table <= `FT_TABLE_a; block_9x <= 1; end 4'hA: begin go_fields_table <= 1; // we don't know, safe bet is table a, but could be table b, // works either way, table is fixed on the next nibble o_fields_table <= `FT_TABLE_a; block_Ax <= 1; end 4'hB: begin go_fields_table <= 1; // we don't know, safe bet is table a, but could be table b, // works either way, table is fixed on the next nibble o_fields_table <= `FT_TABLE_a; block_Bx <= 1; end 4'hC: block_Cx <= 1; 4'hD: block_Dx <= 1; 4'hF: block_Fx <= 1; default: begin `ifdef SIM $display("DEC_INIT 2: nibble %h not handled", i_nibble); `endif o_dec_error <= 1; end endcase end end if (do_block_jump_test2) begin // $display("BLOCK_JUMP_TEST_2 %h | pop %b | pop-nxt %b", i_nibble, o_pop, (i_nibble == 0) && o_pop); o_alu_op <= `ALU_OP_TEST_GO; o_imm_value <= i_nibble; o_ins_rtn <= (i_nibble == 0) && o_pop; block_jump_test2 <= 0; next_nibble <= 0; o_ins_decoded <= 1; end /****************************************************************************** * * 0x * * 00 RTNSXM 08 CLRST * 01 RTN 09 C=ST * 02 RTNSC 0A ST=C * 03 RTNCC 0B CSTEX * 04 SETHEX 0C P=P+1 * 05 SETDEC 0D P=P-1 * 06 RSTK=C * 07 C=RSTK 0F RTI * *****************************************************************************/ if (do_block_0x) begin case (i_nibble) 4'h0, 4'h1, 4'h2, 4'h3, 4'hF: begin o_ins_rtn <= 1; o_pop <= 1; o_set_xm <= i_nibble == 4'h0; o_set_carry <= !i_nibble[3] && i_nibble[1]; o_carry_val <= i_nibble[1] && !i_nibble[0]; o_en_intr <= i_nibble[3]; `ifdef SIM o_unimplemented <= i_nibble[3]; `endif end 4'h4, 4'h5: begin // 04 SETHEX // 05 SETDEC o_ins_set_mode <= 1; o_mode_dec <= (i_nibble[0]); `ifdef SIM o_unimplemented <= 0; `endif end 4'h6, 4'h7: begin // 06 RSTK=C // 07 C=RSTK // those 2 are alu copy ops between RSTK and C o_ins_alu_op <= 1; o_alu_op <= `ALU_OP_COPY; o_push <= !i_nibble[0]; o_pop <= i_nibble[0]; end 4'h8: begin o_ins_alu_op <= 1; o_alu_op <= `ALU_OP_ZERO; end 4'h9, 4'hA: begin o_ins_alu_op <= 1; o_alu_op <= `ALU_OP_COPY; end 4'hB: begin o_ins_alu_op <= 1; o_alu_op <= `ALU_OP_EXCH; end 4'hC, 4'hD: begin o_ins_alu_op <= 1; o_alu_op <= i_nibble[0]?`ALU_OP_DEC:`ALU_OP_INC; end 4'hE: o_fields_table <= `FT_TABLE_f; default: begin `ifdef SIM $display("block_0x: nibble %h not handled", i_nibble); `endif o_dec_error <= 1; end endcase next_nibble <= (i_nibble == 4'hE); block_0Efx <= (i_nibble == 4'hE); go_fields_table <= (i_nibble == 4'hE); o_ins_decoded <= (i_nibble != 4'hE); block_0x <= 0; end /****************************************************************************** * * 0Ex R1=R1[&!]R2 table_f * *****************************************************************************/ if (do_block_0Efx && !in_fields_table) begin o_ins_alu_op <= 1; o_alu_op <= (!i_nibble[3])?`ALU_OP_AND:`ALU_OP_OR; next_nibble <= 0; o_ins_decoded <= 1; end /****************************************************************************** * * 1x jump table to other things * *****************************************************************************/ if (do_block_1x) begin case (i_nibble) 4'h0: // save A/C to Rn W block_save_to_R_W <= 1; 4'h1: // restore A/C from Rn W block_rest_from_R_W <= 1; 4'h2: // exchange A/C with Rn W block_exch_with_R_W <= 1; 4'h3: // move/exch A/C with Dn A/[0:3] block_13x <= 1; 4'h4, 4'h5: // DAT[01]=[AC] begin o_alu_debug <= 0; `ifdef SIM $display("block_1x %h | use table <= %b", i_nibble, i_nibble[0]); `endif block_14x_15xx <= 1; use_fields_tbl <= i_nibble[0]; end 4'h6, 4'h7, 4'h8, 4'hC: // D[01]=D[01][+-] n+1; begin block_pointer_arith_const <= 1; o_ins_alu_op <= 1; o_alu_op <= i_nibble[1]?`ALU_OP_ADD:`ALU_OP_SUB; end 4'h9, 4'hA, 4'hB, 4'hD, 4'hE, 4'hF: // D[0]=([245]) begin mem_load_max <= {2'b00, i_nibble[1], !i_nibble[1], i_nibble[1] && i_nibble[0]}; o_mem_pos <= 0; block_load_reg_imm <= 1; o_alu_no_stall <= 1; o_ins_alu_op <= 1; o_alu_op <= `ALU_OP_COPY; `ifdef SIM o_unimplemented <= 0; `endif end endcase block_1x <= 0; end if (do_block_save_to_R_W || do_block_rest_from_R_W) begin o_ins_alu_op <= 1; o_alu_op <= `ALU_OP_COPY; next_nibble <= 0; o_ins_decoded <= 1; block_save_to_R_W <= 0; block_rest_from_R_W <= 0; end if (do_block_exch_with_R_W) begin o_ins_alu_op <= 1; o_alu_op <= `ALU_OP_EXCH; next_nibble <= 0; o_ins_decoded <= 1; end if (do_block_13x) begin o_ins_alu_op <= 1; o_alu_op <= i_nibble[1]?`ALU_OP_EXCH:`ALU_OP_COPY; next_nibble <= 0; o_ins_decoded <= 1; block_13x <= 0; end if (do_block_14x_15xx) begin `ifdef SIM $display("block_14x_15xx nibble %h | use_tbl %b", i_nibble, use_fields_tbl); $display("fields_table %d",i_nibble[3]?`FT_TABLE_value:`FT_TABLE_a); `endif // o_alu_debug <= 1; o_fields_table <= i_nibble[3]?`FT_TABLE_value:`FT_TABLE_a; // o_alu_op <= `ALU_OP_COPY; go_fields_table <= use_fields_tbl; use_fields_tbl <= 0; // do not block when we're reading // o_alu_no_stall <= !use_fields_tbl && i_nibble[1]; // o_alu_debug <= i_nibble[1]; // set the info about this being a memory transfer o_ins_mem_xfr <= !(use_fields_tbl); o_xfr_dir_out <= !i_nibble[1]; block_15xx <= use_fields_tbl; // o_ins_alu_op <= !(use_fields_tbl); next_nibble <= use_fields_tbl; o_ins_decoded <= !(use_fields_tbl); block_14x_15xx <= 0; end if (do_block_15xx) begin `ifdef SIM $display("block_15xx %h", i_nibble); `endif o_alu_debug <= 1; // o_alu_no_stall <= 1; // o_ins_alu_op <= 1; o_ins_mem_xfr <= 1; o_ins_decoded <= 1; next_nibble <= 0; block_15xx <= 0; end if (do_block_pointer_arith_const) begin next_nibble <= 0; o_imm_value <= i_nibble; o_ins_decoded <= 1; end if (do_block_2x) begin o_ins_alu_op <= 1; o_alu_op <= `ALU_OP_COPY; o_imm_value <= i_nibble; next_nibble <= 0; o_ins_decoded <= 1; `ifdef SIM o_unimplemented <= 0; `endif block_2x <= 0; end if (do_block_3x) begin $write("block load C hex %h\n", i_nibble); mem_load_max <= i_nibble + 1; o_mem_pos <= 0; o_alu_no_stall <= 1; o_alu_op <= `ALU_OP_COPY; block_load_reg_imm <= 1; o_ins_alu_op <= 1; block_3x <= 0; `ifdef SIM o_unimplemented <= 0; `endif end `include "saturn_decoder_block_8.v" /* * Block 9xx * * */ if (do_block_9x) begin `ifdef SIM $display("block_9x %h", i_nibble); `endif o_fields_table <= i_nibble[3]?`FT_TABLE_b:`FT_TABLE_a; block_9ax <= !i_nibble[3]; block_9bx <= i_nibble[3]; block_9x <= 0; end if (do_block_9ax) begin `ifdef SIM $display("block_9ax %h", i_nibble); `endif o_dec_error <= 1; block_9ax <= 0; end if (do_block_9bx) begin `ifdef SIM $display("block_9bx %h", i_nibble); `endif o_dec_error <= 1; block_9bx <= 0; end /* * Block Axx * ra=ra+rb a * ra=ra-1 a * ra=0 b * ra=rb b * rarbEX b */ if (do_block_Ax) begin o_fields_table <= i_nibble[3]?`FT_TABLE_b:`FT_TABLE_a; block_Aax <= !i_nibble[3]; block_Abx <= i_nibble[3]; block_Ax <= 0; end if (do_block_Aax) begin `ifdef SIM $display("block_Aax %h", i_nibble); `endif o_dec_error <= 1; end if (do_block_Abx) begin o_ins_alu_op <= 1; o_alu_op <= (i_nibble[3] && i_nibble[2])?`ALU_OP_EXCH:`ALU_OP_COPY; next_nibble <= 0; o_ins_decoded <= 1; `ifdef SIM o_unimplemented <= 0; `endif block_Abx <= 0; end /* * Block Bxx * * */ if (do_block_Bx) begin o_fields_table <= i_nibble[3]?`FT_TABLE_b:`FT_TABLE_a; block_Bax <= !i_nibble[3]; block_Bbx <= i_nibble[3]; block_Bx <= 0; end if (do_block_Bax) begin `ifdef SIM $display("block_Bax %h", i_nibble); `endif o_ins_alu_op <= 1; case ({i_nibble[3],i_nibble[2]}) 2'b00: o_alu_op <= `ALU_OP_SUB; 2'b01: o_alu_op <= `ALU_OP_INC; 2'b10: o_alu_op <= `ALU_OP_SUB; 2'b11: o_alu_op <= `ALU_OP_SUB; endcase next_nibble <= 0; o_ins_decoded <= 1; `ifdef SIM o_unimplemented <= 0; `endif block_Bax <= 0; end if (do_block_Bbx) begin o_ins_alu_op <= 1; case ({i_nibble[3],i_nibble[2]}) 2'b00: o_alu_op <= `ALU_OP_SHL; 2'b01: o_alu_op <= `ALU_OP_SHR; 2'b10: o_alu_op <= `ALU_OP_2CMPL; 2'b11: o_alu_op <= `ALU_OP_1CMPL; endcase next_nibble <= 0; o_ins_decoded <= 1; `ifdef SIM o_unimplemented <= 0; `endif block_Bbx <= 0; end /* * Block Cx * * */ if (do_block_Cx) begin `ifdef SIM $display("block_Cx %h", i_nibble); `endif // o_alu_debug <= 1; o_fields_table <= `FT_TABLE_f; o_ins_alu_op <= 1; o_alu_op <= (i_nibble[3] && i_nibble[2])?`ALU_OP_DEC:`ALU_OP_ADD; next_nibble <= 0; o_ins_decoded <= 1; `ifdef SIM // o_unimplemented <= 0; `endif block_Cx <= 0; end if (do_block_Dx) begin `ifdef SIM $display("block_Dx %h", i_nibble); `endif o_fields_table <= `FT_TABLE_f; o_ins_alu_op <= 1; o_alu_op <= (i_nibble[3] && i_nibble[2])?`ALU_OP_EXCH:`ALU_OP_COPY; next_nibble <= 0; o_ins_decoded <= 1; `ifdef SIM // o_unimplemented <= 0; `endif block_Dx <= 0; end if (do_block_Fx) begin `ifdef SIM `endif case (i_nibble) 4'h8, 4'h9, 4'hA, 4'hB: // r=-r A begin o_fields_table <= `FT_TABLE_f; //o_alu_debug <= 1; o_ins_alu_op <= 1; o_alu_op <= `ALU_OP_2CMPL; next_nibble <= 0; o_ins_decoded <= 1; end default: begin `ifdef SIM $display("block_Fx %h error", i_nibble); `endif o_dec_error <= 1; end endcase block_Fx <= 0; end // utilities if (do_load_reg_imm) begin // $write("load reg imm %h | ", i_nibble); // $write("pos %d | max %d | ", o_mem_pos, mem_load_max); // $write("next %b | dec %b | ", (o_mem_pos+1) != mem_load_max, (o_mem_pos+1) == mem_load_max); // $write("\n"); o_ins_alu_op <= 1; o_imm_value <= i_nibble; o_mem_load[o_mem_pos*4+:4] <= i_nibble; o_mem_pos <= o_mem_pos + 1; next_nibble <= (o_mem_pos+1) != mem_load_max; o_ins_decoded <= (o_mem_pos+1) == mem_load_max; block_load_reg_imm <= (o_mem_pos+1) != mem_load_max; end if (do_block_jmp) begin o_ins_alu_op <= 1; o_imm_value <= i_nibble; o_mem_load[o_mem_pos*4+:4] <= i_nibble; o_mem_pos <= o_mem_pos + 1; next_nibble <= mem_load_max != o_mem_pos; o_ins_decoded <= mem_load_max == o_mem_pos; block_jmp <= mem_load_max != o_mem_pos; end end `include "saturn_decoder_registers.v" `include "saturn_decoder_fields.v" endmodule