/* -----------------------------------------------------------------------------
* 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_SPACE
} Char;

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

module nexys_CYBERcobra(
  input  logic        clk_i,
  input  logic        arstn_i,
  input  logic [15:0] sw_i,
  input  logic        btnd_i,
  output logic [15:0] led_o,
  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 [31:0] cobra_out;

  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     )
  );

  CYBERcobra dut (
    .clk_i (bufg_clk     ),
    .rst_i (!arstn_i     ),
    .sw_i  (sw_i         ),
    .out_o (cobra_out    )
  );

  logic [31:0] instr_addr;
  logic [31:0] instr;
  assign instr_addr = dut.imem.read_addr_i;
  assign instr      = dut.imem.read_data_o;

  import alu_opcodes_pkg::*;

  Instruction_type         instr_type;
  logic [ALU_OP_WIDTH-1:0] alu_op;
  logic                    illegal_instr;
  nexys_CYBERcobra_decoder nexys_CYBERcobra_decoder (
    .instr_i         (instr        ),
    .instr_type_o    (instr_type   ),
    .alu_op_o        (alu_op       ),
    .illegal_instr_o (illegal_instr)
  );

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

    case (instr_type)
      INSTR_ALU:
        case (alu_op)
          ALU_ADD : op_chars[0:2] = '{CH_A, CH_d, CH_d};
          ALU_SUB : op_chars[0:2] = '{CH_S, CH_u, CH_b};
          ALU_XOR : op_chars[0:2] = '{CH_X, CH_o, CH_r};
          ALU_OR  : op_chars[0:1] = '{CH_o, CH_r};
          ALU_AND : op_chars[0:2] = '{CH_A, CH_n, CH_d};
          ALU_SRA : op_chars[0:2] = '{CH_S, CH_r, CH_A};
          ALU_SRL : op_chars[0:2] = '{CH_S, CH_r, CH_L};
          ALU_SLL : op_chars[0:2] = '{CH_S, CH_L, CH_L};
          ALU_LTS : op_chars[0:2] = '{CH_L, CH_t, CH_S};
          ALU_LTU : op_chars[0:2] = '{CH_L, CH_t, CH_u};
          ALU_GES : op_chars[0:2] = '{CH_G, CH_E, CH_S};
          ALU_GEU : op_chars[0:2] = '{CH_G, CH_E, CH_u};
          ALU_EQ  : op_chars[0:1] = '{CH_E, CH_q};
          ALU_NE  : op_chars[0:1] = '{CH_n, CH_E};
          ALU_SLTS: op_chars      = '{CH_S, CH_L, CH_t, CH_S};
          ALU_SLTU: op_chars      = '{CH_S, CH_L, CH_t, CH_u};

          default : ;
        endcase
      INSTR_LI  : op_chars[0:1] = '{CH_L, CH_i};
      INSTR_JUMP: op_chars      = '{CH_J, CH_u, CH_m, CH_P};
      INSTR_NOP : op_chars[0:2] = '{CH_n, CH_o, CH_P};
      INSTR_IN  : op_chars[0:1] = '{CH_i, CH_n};
    endcase
  end

  Char all_chars[0:7];
  assign all_chars[0:3] = {
      Char'(led_o[7:4])     ,
      Char'(led_o[3:0])     ,
      Char'(instr_addr[7:4]),
      Char'(instr_addr[3:0])
  };
  localparam Char ILL_INSTR_MSG[0:3] = '{CH_i, CH_L, CH_L, CH_SPACE};
  assign all_chars[4:7] = illegal_instr ? ILL_INSTR_MSG : op_chars;

  Semseg all_semsegs[0:7];
  for (genvar semseg_num = 0; semseg_num < 8; ++semseg_num) begin : CHAR2SEMSEG_GEN
    char2semseg char2semseg (
      .char_i   (all_chars  [semseg_num]),
      .semseg_o (all_semsegs[semseg_num])
    );
  end


  Semseg all_semsegs_dotted[0:7];
  assign all_semsegs_dotted[0]   = all_semsegs[0];
  assign all_semsegs_dotted[2:7] = all_semsegs[2:7];
  assign all_semsegs_dotted[1].ca = all_semsegs[1].ca;
  assign all_semsegs_dotted[1].cb = all_semsegs[1].cb;
  assign all_semsegs_dotted[1].cc = all_semsegs[1].cc;
  assign all_semsegs_dotted[1].cd = all_semsegs[1].cd;
  assign all_semsegs_dotted[1].ce = all_semsegs[1].ce;
  assign all_semsegs_dotted[1].cf = all_semsegs[1].cf;
  assign all_semsegs_dotted[1].cg = all_semsegs[1].cg;
  assign all_semsegs_dotted[1].dp = 1'b0;

  Semseg current_semseg;
  logic [7:0] an;
  semseg_one2many semseg_one2many (
    .clk100m_i        (clk_i             ),
    .arstn_i          (arstn_i           ),
    .all_semsegs_i    (all_semsegs_dotted),
    .current_semseg_o (current_semseg    ),
    .an_o             (an                )
  );

  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;

  assign led_o = cobra_out[15:0];

endmodule

module nexys_CYBERcobra_decoder
  import alu_opcodes_pkg::*;
(
  input  logic [31:0]             instr_i,
  output Instruction_type         instr_type_o,
  output logic [ALU_OP_WIDTH-1:0] alu_op_o,
  output logic                    illegal_instr_o
);

  logic       j;
  logic       b;
  logic [1:0] ws;

  assign j  = instr_i[31];
  assign b  = instr_i[30];
  assign ws = instr_i[29:28];

  logic  is_branch_instr;
  assign is_branch_instr = b;

  always_comb begin
    instr_type_o    = INSTR_NOP;

    if (j) begin
      instr_type_o = INSTR_JUMP;
    end else if (b) begin
      instr_type_o    = INSTR_ALU;
    end else begin
      case (ws)
        2'd0: instr_type_o    = INSTR_LI;
        2'd1: instr_type_o    = INSTR_ALU;
        2'd2: instr_type_o    = INSTR_IN;
        2'd3: instr_type_o    = INSTR_NOP;
      endcase
    end
  end

  assign alu_op_o        = instr_i[27:23];

  import alu_opcodes_pkg::*;

  typedef enum {
    ALU_OP_BRANCH,
    ALU_OP_COMPUTATIONAL,
    ALU_OP_ILLEGAL
  } Alu_op_type;
  Alu_op_type alu_op_type;
  always_comb begin
    alu_op_type = ALU_OP_ILLEGAL;

    case (alu_op_o) inside
      ALU_LTS,
      ALU_LTU,
      ALU_GES,
      ALU_GEU,
      ALU_EQ ,
      ALU_NE : alu_op_type = ALU_OP_BRANCH;

      ALU_ADD ,
      ALU_SUB ,
      ALU_XOR ,
      ALU_OR  ,
      ALU_AND ,
      ALU_SRA ,
      ALU_SRL ,
      ALU_SLL ,
      ALU_SLTS,
      ALU_SLTU: alu_op_type = ALU_OP_COMPUTATIONAL;

      default : alu_op_type = ALU_OP_ILLEGAL;
    endcase
  end

  assign illegal_instr_o = (instr_type_o == INSTR_ALU) && ((alu_op_type == ALU_OP_ILLEGAL) ||
      ((alu_op_type == ALU_OP_BRANCH) ^ is_branch_instr));
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;
      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
) (
  input  Semseg all_semsegs_i[0:SEMSEGS_NUM-1],
  input  logic  clk100m_i,
  input  logic  arstn_i,
  output Semseg 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

  Semseg 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
