/****************************************************************************** * * 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_stalled, i_pc, i_nibble, i_reg_p, o_inc_pc, o_push, o_pop, o_dec_error, o_ins_addr, o_ins_decoded, o_fields_table, o_field, o_field_start, o_field_last, o_alu_op, o_reg_dest, o_reg_src1, o_reg_src2, o_direction, o_ins_rtn, o_set_xm, o_set_carry, o_carry_val, o_ins_set_mode, o_mode_dec, o_ins_alu_op ); /* * module input / output ports */ input wire [0:0] i_clk; input wire [0:0] i_reset; input wire [31:0] i_cycles; input wire i_en_dbg; input wire i_en_dec; input wire i_stalled; input wire [19:0] i_pc; input wire [3:0] i_nibble; input wire [3:0] i_reg_p; output reg o_inc_pc; output reg o_push; output reg o_pop; output reg o_dec_error; // instructions related outputs output reg [19:0] o_ins_addr; output reg o_ins_decoded; output reg [1:0] o_fields_table; output reg [3:0] o_field; output reg [3:0] o_field_start; output reg [3:0] o_field_last; output reg [4:0] o_alu_op; output reg [4:0] o_reg_dest; output reg [4:0] o_reg_src1; output reg [4:0] o_reg_src2; // generic output reg o_direction; // rtn specific output reg o_ins_rtn; output reg o_set_xm; output reg o_set_carry; output reg o_carry_val; // setdec/hex output reg o_ins_set_mode; output reg o_mode_dec; // alu_operations output reg o_ins_alu_op; /* * state registers */ reg [31:0] instr_ctr; initial begin `ifdef SIM // $monitor({"i_clk %b | i_reset %b | i_cycles %d | i_en_dec %b | i_en_exec %b |", // " continue %b | instr_start %b | i_nibble %h"}, // i_clk, i_reset, i_cycles, i_en_dec, i_en_exec, continue, // 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, continue, block_0x, ins_rtn, set_xm, set_carry, carry_val); `endif end /* * debugger * */ wire [19:0] new_pc; assign new_pc = i_pc + 1; always @(posedge i_clk) begin if (!i_reset && i_en_dbg && !i_stalled) if (!continue) begin `ifdef SIM if (o_ins_decoded) begin $write("\n%5h ", o_ins_addr); if (o_ins_rtn) begin $write("RTN"); if (o_set_xm) $write("SXM"); if (o_set_carry) $write("%sC", o_carry_val?"S":"C"); $display(""); end if (o_ins_set_mode) begin $display("SET%s", o_mode_dec?"DEC":"HEX"); end if (o_ins_alu_op) begin case (o_reg_dest) `ALU_REG_A: $write("A"); `ALU_REG_C: $write("C"); `ALU_REG_RSTK: $write("RSTK"); `ALU_REG_ST: if (o_alu_op!=`ALU_OP_ZERO) $write("ST"); `ALU_REG_P: $write("P"); default: $write("[dest:%d]", o_reg_dest); endcase case (o_alu_op) `ALU_OP_ZERO: if (o_reg_dest==`ALU_REG_ST) $write("CLRST"); else $write("=0"); `ALU_OP_COPY, `ALU_OP_INC, `ALU_OP_DEC: $write("="); `ALU_OP_EXCH: begin end default: $write("[op:%d]", o_alu_op); endcase case (o_alu_op) `ALU_OP_COPY, `ALU_OP_EXCH, `ALU_OP_AND, `ALU_OP_OR, `ALU_OP_INC, `ALU_OP_DEC: case (o_reg_src1) `ALU_REG_A: $write("A"); `ALU_REG_C: $write("C"); `ALU_REG_RSTK: $write("RSTK"); `ALU_REG_ST: $write("ST"); `ALU_REG_P: $write("P"); default: $write("[src1:%d]", o_reg_src1); endcase endcase if (o_alu_op == `ALU_OP_EXCH) $write("EX"); case (o_alu_op) `ALU_OP_AND, `ALU_OP_OR: begin case (o_alu_op) default: $write("[op:%d]", o_alu_op); endcase case (o_reg_src2) `ALU_REG_A: $write("A"); `ALU_REG_C: $write("C"); `ALU_REG_RSTK: $write("RSTK"); default: $write("[src2:%d]", o_reg_src2); endcase end `ALU_OP_INC: $write("+1"); `ALU_OP_DEC: $write("-1"); `ALU_OP_ZERO, `ALU_OP_COPY, `ALU_OP_EXCH: begin end endcase if (!((o_reg_dest == `ALU_REG_RSTK) || (o_reg_src1 == `ALU_REG_RSTK) || (o_reg_dest == `ALU_REG_ST) || (o_reg_src1 == `ALU_REG_ST ) || (o_reg_dest == `ALU_REG_P) || (o_reg_src1 == `ALU_REG_P ))) begin $write("\t"); case (o_field) default: $write("[f:%d]", o_field); endcase end $display(""); end end $display("new [%5h]--------------------------------------------------------------------", new_pc); `endif end end /****************************************************************************** * * handle decoding of the fist nibble * that's pretty simple though, will get tougher later on :-) * *****************************************************************************/ // general variables reg continue; reg block_0x; reg block_0Efx; reg go_fields_table; // wires to make all this supposedly faster wire decoder_active; wire do_on_first_nibble; wire do_on_other_nibbles; assign decoder_active = !i_reset && i_en_dec && !i_stalled; assign do_on_first_nibble = decoder_active && !continue; assign do_on_other_nibbles = decoder_active && continue; wire do_block_0x; assign do_block_0x = do_on_other_nibbles && block_0x; wire do_block_0Efx; assign do_block_0Efx = do_on_other_nibbles && block_0Efx; wire in_fields_table; assign in_fields_table = go_fields_table && !fields_table_done; always @(posedge i_clk) begin if (i_reset) begin continue <= 0; o_inc_pc <= 1; o_dec_error <= 0; o_ins_decoded <= 0; o_alu_op <= 0; end if (decoder_active) begin /* * stuff that is always done */ o_inc_pc <= 1; // may be set to 0 later end /* * cleanup */ if (do_on_first_nibble) begin continue <= 1; o_push <= 0; o_pop <= 0; o_ins_decoded <= 0; // store the address where the instruction starts o_ins_addr <= i_pc; // cleanup block variables block_0x <= 0; block_0Efx <= 0; // cleanup fields table variables go_fields_table <= 0; o_fields_table <= 3; o_field <= 0; o_field_start <= 0; o_field_last <= 0; o_alu_op <= 0; // cleanup o_direction <= 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; /* * x first nibble */ // assign block regs case (i_nibble) 4'h0: block_0x <= 1; default: begin `ifdef SIM $display("new_instruction: nibble %h not handled", i_nibble); `endif o_dec_error <= 1; end endcase end /****************************************************************************** * * 0x * * 00 RTNSXM * 01 RTN * 02 RTNSC * 03 RTNCC * 04 SETHEX * 05 SETDEC * 06 RSTK=C * 07 C=RSTK * *****************************************************************************/ if (do_block_0x) begin case (i_nibble) 4'h0, 4'h1, 4'h2, 4'h3: begin o_ins_rtn <= 1; o_set_xm <= (i_nibble == 4'h0); o_set_carry <= (i_nibble[3:1] == 1); o_carry_val <= (i_nibble[1] && i_nibble[0]); end 4'h4, 4'h5: begin o_ins_set_mode <= 1; o_mode_dec <= (i_nibble[0]); end /* RSTK=C * C=RSTK * those 2 are alu copy ops between RSTK and C */ 4'h6, 6'h7: begin 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: begin block_0x <= 0; o_fields_table <= `FT_TABLE_f; end default: begin `ifdef SIM $display("block_0x: nibble %h not handled", i_nibble); `endif o_dec_error <= 1; end endcase continue <= (i_nibble == 4'hE); block_0Efx <= (i_nibble == 4'hE); go_fields_table <= (i_nibble == 4'hE); o_ins_decoded <= (i_nibble != 4'hE); end /****************************************************************************** * * 0Ex * * *****************************************************************************/ 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; continue <= 0; o_ins_decoded <= 1; end end /****************************************************************************** * * set registers from instruction nibble * *****************************************************************************/ always @(posedge i_clk) begin if (i_reset) begin o_reg_dest <= 0; o_reg_src1 <= 0; o_reg_src2 <= 0; end if (do_on_first_nibble) begin // reset values on instruction decode start o_reg_dest <= 0; o_reg_src1 <= 0; o_reg_src2 <= 0; end /************************************************************************ * * set registers for specific instructions * ************************************************************************/ if (do_block_0x) begin case (i_nibble) 4'h6: begin o_reg_dest <= `ALU_REG_RSTK; o_reg_src1 <= `ALU_REG_C; end 4'h7: begin o_reg_dest <= `ALU_REG_C; o_reg_src1 <= `ALU_REG_RSTK; end 4'h8: o_reg_dest <= `ALU_REG_ST; 4'h9, 4'hB: begin o_reg_dest <= `ALU_REG_C; o_reg_src1 <= `ALU_REG_ST; end 4'hA: begin o_reg_dest <= `ALU_REG_ST; o_reg_src1 <= `ALU_REG_C; end 4'hC, 4'hD: begin o_reg_dest <= `ALU_REG_P; o_reg_src1 <= `ALU_REG_P; end endcase end if (do_block_0Efx && !in_fields_table) begin `ifdef SIM $write("\nset registers for block_0Efx"); `endif end end /****************************************************************************** * * set fields from instruction nibble * *****************************************************************************/ `ifdef SIM // `define DEBUG_FIELDS_TABLE `endif reg fields_table_done; wire do_fields_table; assign do_fields_table = decoder_active && go_fields_table && !fields_table_done; wire table_a; wire table_b; wire table_f; wire table_value; assign table_a = (o_fields_table == `FT_TABLE_a); assign table_b = (o_fields_table == `FT_TABLE_b); assign table_f = (o_fields_table == `FT_TABLE_f); assign table_value = (o_fields_table == `FT_TABLE_value); wire do_tables_a_f_b; assign do_tables_a_f_b = do_fields_table && !table_value; wire table_a_nb_ok; wire table_b_nb_ok; wire table_f_cond; wire table_f_nb_ok; wire table_a_f_b_nb_ok; assign table_a_nb_ok = table_a && !i_nibble[3]; assign table_b_nb_ok = table_b && i_nibble[3]; assign table_f_cond = !i_nibble[3] || (i_nibble == 4'hF); assign table_f_nb_ok = table_f && table_f_cond; assign table_a_f_b_nb_ok = table_a_nb_ok || table_b_nb_ok || table_f_nb_ok; wire [3:0] table_f_nibble_value; wire [3:0] table_a_f_b_case_value; assign table_f_nibble_value = (i_nibble & {4{table_f}}); assign table_a_f_b_case_value = (i_nibble & 4'h7) | table_f_nibble_value; always @(posedge i_clk) begin if (i_reset || do_on_first_nibble) begin // reset values fields_table_done <= 0; o_field <= 0; o_field_start <= 0; o_field_last <= 0; end /****************************************************************************** * * set field for specific instructions * *****************************************************************************/ if (do_block_0x) begin case (i_nibble) 4'h6, 4'h7: begin // virtual A o_field_start <= 0; o_field_last <= 4; end 4'h8, 4'h9, 4'hA, 4'hB: begin // ST is 0-3 o_field_start <= 0; o_field_last <= 3; end endcase end /****************************************************************************** * * set field from a table * * *****************************************************************************/ if (do_tables_a_f_b) begin // debug info `ifdef DEBUG_FIELDS_TABLE $display("====== fields_table | table %h | nibble %b", o_fields_table, i_nibble); $display("table_a : %b", table_a_nb_ok); $display("table_b : %b", table_b_nb_ok); $display("table_f_cond: %b", table_f_cond); $display("table_f : %b", table_f_nb_ok); // $display("table_f nbl : %h", {4{o_fields_table == `FT_TABLE_f}} ); $display("table_f val : %h", table_f_nibble_value); $display("case nibble : %h", table_a_f_b_case_value); `endif end // if (do_tables_a_f_b) begin if (table_a_f_b_nb_ok) begin case (table_a_f_b_case_value) 4'h0: begin o_field <= `FT_FIELD_P; o_field_start <= i_reg_p; o_field_last <= i_reg_p; `ifdef DEBUG_FIELDS_TABLE $display("fields_table: field P (%h)", i_reg_p); `endif end 4'h1: begin o_field <= `FT_FIELD_WP; o_field_start <= 0; o_field_last <= i_reg_p; `ifdef DEBUG_FIELDS_TABLE $display("fields_table: field WP (0-%h)", i_reg_p); `endif end 4'h2: begin o_field <= `FT_FIELD_XS; o_field_start <= 2; o_field_last <= 2; `ifdef DEBUG_FIELDS_TABLE $display("fields_table: field XS"); `endif end 4'h3: begin o_field <= `FT_FIELD_X; o_field_start <= 0; o_field_last <= 2; `ifdef DEBUG_FIELDS_TABLE $display("fields_table: field X"); `endif end 4'h4: begin o_field <= `FT_FIELD_S; o_field_start <= 15; o_field_last <= 15; `ifdef DEBUG_FIELDS_TABLE $display("fields_table: field S"); `endif end 4'h5: begin o_field <= `FT_FIELD_M; o_field_start <= 3; o_field_last <= 14; `ifdef DEBUG_FIELDS_TABLE $display("fields_table: field M"); `endif end 4'h6: begin o_field <= `FT_FIELD_B; o_field_start <= 0; o_field_last <= 1; `ifdef DEBUG_FIELDS_TABLE $display("fields_table: field B"); `endif end 4'h7: begin o_field <= `FT_FIELD_W; o_field_start <= 0; o_field_last <= 15; `ifdef DEBUG_FIELDS_TABLE $display("fields_table: field W"); `endif end 4'hF: begin if (table_f) begin o_field <= `FT_FIELD_A; o_field_start <= 0; o_field_last <= 4; `ifdef DEBUG_FIELDS_TABLE $display("fields_table: field A"); `endif end else begin // should never get here o_dec_error <= 1; `ifdef SIM $display("fields_table: table %h nibble %h", o_fields_table, i_nibble); `endif end end default: begin o_dec_error <= 1; `ifdef SIM $display("fields_table: table %h nibble %h not handled", o_fields_table, i_nibble); `endif end endcase fields_table_done <= 1; end else begin o_dec_error <= 1; `ifdef SIM $display("fields_table: table %h invalid nibble %h", o_fields_table, i_nibble); `endif end end // if (begin // o_dec_error <= 1; // `ifdef SIM // $display("fields_table: there is nothing to decode for table FT_TABLE_value"); // `endif // end end endmodule