/* -----------------------------------------------------------------------------
* Project Name   : Architectures of Processor Systems (APS) lab work
* Organization   : National Research University of Electronic Technology (MIET)
* Department     : Institute of Microdevices and Control Systems
* Author(s)      : Alexander Kharlamov
* Email(s)       : sasha_xarlamov@org.miet.ru

See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details.
* ------------------------------------------------------------------------------
*/
typedef enum {
  INSTR_ALU   , // branch and computational
  INSTR_LI    , // const load
  INSTR_IN    , // periphery load
  INSTR_JUMP  ,
  INSTR_NOP     // ws == 3
} Instruction_type;

typedef enum {
  CH_0 = 0,
  CH_1,
  CH_2,
  CH_3,
  CH_4,
  CH_5,
  CH_6,
  CH_7,
  CH_8,
  CH_9,
  CH_A,
  CH_b,
  CH_c,
  CH_d,
  CH_E,
  CH_F,
  CH_G,
  CH_L,
  CH_n,
  CH_o,
  CH_r,
  CH_S,
  CH_t,
  CH_u,
  CH_X,
  CH_P,
  CH_J,
  CH_q,
  CH_i,
  CH_m,
  CH_y,
  CH_h,

  CH_SPACE
} Char;

typedef struct {
  logic ca;
  logic cb;
  logic cc;
  logic cd;
  logic ce;
  logic cf;
  logic cg;
  logic dp;
} Semseg;

module nexys_processor_system(
  input  logic        clk_i,
  input  logic        arstn_i,
  input  logic        btnd_i,
  output logic        ca_o,
  output logic        cb_o,
  output logic        cc_o,
  output logic        cd_o,
  output logic        ce_o,
  output logic        cf_o,
  output logic        cg_o,
  output logic        dp_o,
  output logic [ 7:0] an_o
);

  logic btnd_sync;
  sync sync (
    .clk_i             ,
    .data_i (btnd_i   ),
    .data_o (btnd_sync)
  );
  logic btnd_debounce;
  debounce debounce (
    .clk_i                  ,
    .arstn_i (1'b1         ),
    .data_i  (btnd_sync    ),
    .data_o  (btnd_debounce)
  );
  logic bufg_clk;
  BUFG dut_bufg(
    .I (btnd_debounce),
    .O (bufg_clk     )
  );

  processor_system unit(
    .clk_i (bufg_clk),
    .rst_i (!arstn_i)
  );

  logic [31:0] instr_addr;
  logic [31:0] instr;
  assign instr_addr = unit.core.instr_addr_o;
  assign instr = unit.core.instr_i;

  import alu_opcodes_pkg::*;

  logic       illegal_instr;

  logic [6:0] opcode;
  assign      opcode = instr[6:0];

  Char op_chars[0:3];
  import decoder_pkg::*;
  always_comb begin
    op_chars = '{4{CH_SPACE}};

    case (opcode)
      {LOAD_OPCODE    , 2'b11}: op_chars      = '{CH_L, CH_o, CH_A, CH_d};
      {MISC_MEM_OPCODE, 2'b11}: op_chars      = '{CH_m, CH_i, CH_S, CH_c};
      {OP_IMM_OPCODE  , 2'b11}: op_chars      = '{CH_o, CH_P, CH_i, CH_m};
      {AUIPC_OPCODE   , 2'b11}: op_chars      = '{CH_A, CH_u, CH_i, CH_P};
      {STORE_OPCODE   , 2'b11}: op_chars      = '{CH_S, CH_t, CH_o, CH_r};
      {OP_OPCODE      , 2'b11}: op_chars[0:1] = '{CH_o, CH_P};
      {LUI_OPCODE     , 2'b11}: op_chars[0:2] = '{CH_L, CH_u, CH_i};
      {BRANCH_OPCODE  , 2'b11}: op_chars      = '{CH_b, CH_r, CH_c, CH_h};
      {JALR_OPCODE    , 2'b11}: op_chars      = '{CH_J, CH_A, CH_L, CH_r};
      {JAL_OPCODE     , 2'b11}: op_chars[0:2] = '{CH_J, CH_A, CH_L};
      {SYSTEM_OPCODE  , 2'b11}: op_chars[0:2] = '{CH_S, CH_y, CH_S};

      default                 : op_chars[0:2] = '{CH_i, CH_L, CH_L};
    endcase
  end

  Char all_chars[0:7];
  assign all_chars[0:3] = {
      Char'(instr_addr[15:12]),
      Char'(instr_addr[11: 8]),
      Char'(instr_addr[ 7: 4]),
      Char'(instr_addr[ 3: 0])
  };
  assign all_chars[4:7] = op_chars;

  Char current_char;
  logic [7:0] an;
  semseg_one2many #(
    .DATA_T (Char)
  ) semseg_one2many (
    .clk100m_i        (clk_i       ),
    .arstn_i          (arstn_i     ),
    .all_semsegs_i    (all_chars   ),
    .current_semseg_o (current_char),
    .an_o             (an          )
  );

  Semseg current_semseg;
  char2semseg char2semseg (
    .char_i   (current_char  ),
    .semseg_o (current_semseg)
  );

  assign ca_o = current_semseg.ca;
  assign cb_o = current_semseg.cb;
  assign cc_o = current_semseg.cc;
  assign cd_o = current_semseg.cd;
  assign ce_o = current_semseg.ce;
  assign cf_o = current_semseg.cf;
  assign cg_o = current_semseg.cg;
  assign dp_o = current_semseg.dp;

  assign an_o = an;

endmodule

module char2semseg #(
  parameter bit HEX_ONLY = 1'b0
) (
  input  Char   char_i,
  output Semseg semseg_o
);

  localparam bit [6:0] BLANK = '1;

  logic [6:0] semseg;
  always_comb begin
    case (char_i)
      CH_0    : semseg = ~7'h3F;
      CH_1    : semseg = ~7'h06;
      CH_2    : semseg = ~7'h5B;
      CH_3    : semseg = ~7'h4F;
      CH_4    : semseg = ~7'h66;
      CH_5    : semseg = ~7'h6D;
      CH_6    : semseg = ~7'h7D;
      CH_7    : semseg = ~7'h07;
      CH_8    : semseg = ~7'h7F;
      CH_9    : semseg = ~7'h6F;
      CH_A    : semseg = ~7'h5F;
      CH_b    : semseg = ~7'h7C;
      CH_c    : semseg = ~7'h58;
      CH_d    : semseg = ~7'h5E;
      CH_E    : semseg = ~7'h79;
      CH_F    : semseg = ~7'h71;
      CH_G    : semseg = ~7'h3D;
      CH_L    : semseg = ~7'h38;
      CH_n    : semseg = ~7'h54;
      CH_o    : semseg = ~7'h5C;
      CH_r    : semseg = ~7'h50;
      CH_S    : semseg = ~7'h64;
      CH_t    : semseg = ~7'h78;
      CH_u    : semseg = ~7'h1C;
      CH_X    : semseg = ~7'h76;
      CH_P    : semseg = ~7'h73;
      CH_J    : semseg = ~7'h1E;
      CH_q    : semseg = ~7'h67;
      CH_i    : semseg = ~7'h30;
      CH_m    : semseg = ~7'h77;
      CH_y    : semseg = ~7'h6E;
      CH_h    : semseg = ~7'h74;
      default : semseg = BLANK;
    endcase
  end

  assign semseg_o.ca = semseg[0];
  assign semseg_o.cb = semseg[1];
  assign semseg_o.cc = semseg[2];
  assign semseg_o.cd = semseg[3];
  assign semseg_o.ce = semseg[4];
  assign semseg_o.cf = semseg[5];
  assign semseg_o.cg = semseg[6];
  assign semseg_o.dp = 1'b1;

endmodule

module semseg_one2many #(
  parameter int unsigned SEMSEGS_NUM = 8,
  parameter type         DATA_T = logic
) (
  input  DATA_T all_semsegs_i[0:SEMSEGS_NUM-1],
  input  logic  clk100m_i,
  input  logic  arstn_i,
  output DATA_T current_semseg_o,
  output logic [7:0] an_o
);
  logic  clk_i;
  assign clk_i = clk100m_i;

  localparam int COUNTER_WIDTH = 10;
  logic [COUNTER_WIDTH-1:0] counter_next;
  logic [COUNTER_WIDTH-1:0] counter_ff;
  assign counter_next = counter_ff + COUNTER_WIDTH'('b1);
  always_ff @(posedge clk_i or negedge arstn_i) begin
    if (!arstn_i) counter_ff <= '0;
    else          counter_ff <= counter_next;
  end

  logic [7:0] an_ff;
  logic [7:0] an_next;
  logic       an_en;
  assign an_next = {an_ff[$left(an_ff)-1:0], an_ff[$left(an_ff)]};
  assign an_en   = ~|counter_ff;
  always_ff @(posedge clk_i or negedge arstn_i) begin
    if      (!arstn_i) an_ff <= ~8'b1;
    else if (an_en)    an_ff <= an_next;
  end

  DATA_T current_semseg;
  always_comb begin
    unique case (1'b0)
      an_ff[0]: current_semseg = all_semsegs_i[7];
      an_ff[1]: current_semseg = all_semsegs_i[6];
      an_ff[2]: current_semseg = all_semsegs_i[5];
      an_ff[3]: current_semseg = all_semsegs_i[4];
      an_ff[4]: current_semseg = all_semsegs_i[3];
      an_ff[5]: current_semseg = all_semsegs_i[2];
      an_ff[6]: current_semseg = all_semsegs_i[1];
      an_ff[7]: current_semseg = all_semsegs_i[0];
    endcase
  end

  assign current_semseg_o = current_semseg;

  assign an_o = an_ff;

endmodule

module debounce #(
  parameter int unsigned MAX_COUNT = 10000
) (
  input  logic clk_i,
  input  logic arstn_i,
  input  logic data_i,
  output logic data_o
);

  localparam int COUNTER_WIDTH = $clog2(MAX_COUNT);
  logic [COUNTER_WIDTH-1:0] counter_next;
  logic [COUNTER_WIDTH-1:0] counter_ff;
  assign counter_next = (data_o != data_i) ? counter_ff - COUNTER_WIDTH'('b1) :
                                             COUNTER_WIDTH'(MAX_COUNT);
  always_ff @(posedge clk_i or negedge arstn_i) begin
    if      (!arstn_i)         counter_ff <= COUNTER_WIDTH'(MAX_COUNT);
    else                       counter_ff <= counter_next;
  end

  always_ff @(posedge clk_i or negedge arstn_i) begin
    if      (!arstn_i)     data_o <= '0;
    else if (~|counter_ff) data_o <= data_i;
  end

endmodule

module sync #(
  parameter int unsigned SYNC_STAGES = 3
) (
  input  logic clk_i,
  input  logic data_i,
  output logic data_o
);

  logic [SYNC_STAGES-1:0] sync_buffer_ff;
  logic [SYNC_STAGES-1:0] sync_buffer_next;
  assign                  sync_buffer_next = {sync_buffer_ff[$left(sync_buffer_ff)-1:0], data_i};
  always_ff @(posedge clk_i) begin
    sync_buffer_ff <= sync_buffer_next;
  end

  assign data_o = sync_buffer_ff[$left(sync_buffer_ff)];

endmodule
