/*
 * Copyright (c) 2021, Marcos Medeiros
 * Licensed under BSD 3-clause.
 */
`include "riscvdefs.vh"

module ControlUnit(
  input             clk,
  input             reset,
  input [31:0]      dataIn,
  input [31:0]      ir,
  input             aluFlag,
  output reg        writeEnable,
  output reg        irWriteEnable,
  output reg        addressIsPc,
  output reg        incrementPc,
  output reg [1:0]  regDataSrc,
  output reg        regWriteEnable,
  output reg        pcWriteEnable,
  output reg [1:0]  aluSrcA,
  output reg [2:0]  aluSrcB,
  output reg [3:0]  aluOperation,
  output reg [1:0]  readLen,
  output reg [1:0]  writeLen,
  output reg        readSignExtend
);
  parameter [2:0] FETCH0  = 0;
  parameter [2:0] FETCH1  = 1;
  parameter [2:0] DECODE  = 2;
  parameter [2:0] EX1     = 3;
  parameter [2:0] EX2     = 4;
  parameter [2:0] EX3     = 5;
  parameter [2:0] EX4     = 6;

  
  reg [2:0] state;
  wire [2:0] funct3 = ir[14:12];
  wire [6:0] funct7 = ir[31:25];
  wire [6:0] opcode = ir[6:0];
  
  reg branchTestTrue = 0;
  
  always @(posedge clk)
  begin
    if (reset)
    begin
      state           <= FETCH0;
      writeEnable     <= 0;
      regWriteEnable  <= 0;
      incrementPc     <= 0;
      addressIsPc     <= 1;
      aluSrcA         <= `ALU_SRC_A_REG;
      aluSrcB         <= `ALU_SRC_B_REG;
      pcWriteEnable   <= 0;
      readLen         <= `LOAD_STORE_WORD;
      writeLen        <= `LOAD_STORE_WORD;
      readSignExtend  <= 0;
    end
    else
    begin
      if (state == FETCH0) 
      begin
        $display("FETCH0");
        writeEnable     <= 0;
        state           <= FETCH1;
        irWriteEnable   <= 1;
        addressIsPc     <= 1;
        incrementPc     <= 1;
        regWriteEnable  <= 0;
        regDataSrc      <= `REG_DATA_SRC_ALU;
        aluSrcA         <= `ALU_SRC_A_REG;
        aluSrcB         <= `ALU_SRC_B_REG;
        aluOperation    <= `ALU_A;
        pcWriteEnable   <= 0;
        branchTestTrue  <= 0;
        writeLen        <= `LOAD_STORE_WORD;
        readLen         <= `LOAD_STORE_WORD;
        readSignExtend  <= 0;
      end
      else
      if (state == FETCH1)
      begin
        $display("FETCH1");
        state         <= DECODE;
        irWriteEnable <= 0;
        incrementPc   <= 0;
      end
      else
      begin
        $display("Execute %x", ir);
        // LUI
        if (opcode == 7'b0110111)
        begin
          $display("LUI");
          aluSrcB         <= `ALU_SRC_B_IMM_U_TYPE;
          aluOperation    <= `ALU_B;
          regWriteEnable  <= 1;
          state           <= FETCH0;
        end
        else
        // AUIPC
        if (opcode == 7'b0010111)
        begin
          $display("AUIPC");
          aluSrcA         <= `ALU_SRC_A_PREV_PC;
          aluSrcB         <= `ALU_SRC_B_IMM_U_TYPE;
          aluOperation    <= `ALU_ADD;
          regWriteEnable  <= 1;
          pcWriteEnable   <= 0;
          state           <= FETCH0;
        end
        else
        // I-TYPE
        if (opcode == 7'b0010011)
        begin
          $display("I-TYPE");
          case (funct3)
          3'b000: aluOperation <= `ALU_ADD; 
          3'b001: aluOperation <= `ALU_SLL; 
          3'b010: aluOperation <= `ALU_SLT; 
          3'b011: aluOperation <= `ALU_SLTU; 
          3'b100: aluOperation <= `ALU_XOR; 
          3'b101: aluOperation <= (funct7 == 7'h20) ? `ALU_SRA : `ALU_SRL; 
          3'b110: aluOperation <= `ALU_OR; 
          3'b111: aluOperation <= `ALU_AND;
          endcase
          regWriteEnable  <= 1;
          aluSrcB         <= `ALU_SRC_B_IMM_I_TYPE;
          state           <= FETCH0;
        end
        else
        // R-TYPE
        if (opcode == 7'b0110011)
        begin
          $display("R-TYPE"); 
          case (funct3)
          3'b000: aluOperation <= (funct7 == 7'h20) ? `ALU_SUB : `ALU_ADD;
          3'b001: aluOperation <= `ALU_SLL;
          3'b010: aluOperation <= `ALU_SLT;
          3'b011: aluOperation <= `ALU_SLTU;
          3'b100: aluOperation <= `ALU_XOR;
          3'b101: aluOperation <= (funct7 == 7'h20) ? `ALU_SRA : `ALU_SRL;
          3'b110: aluOperation <= `ALU_OR;
          3'b111: aluOperation <= `ALU_AND;
          endcase
          regWriteEnable  <= 1;
          aluSrcB         <= `ALU_SRC_B_REG;
          state           <= FETCH0;
        end
        else
        // B-TYPE
        if (opcode == 7'b1100011)
        begin
          $display("B-TYPE");
          if (state == DECODE)
          begin
            case (funct3)
            3'b000: begin aluOperation <= `ALU_EQ;    branchTestTrue <= 1;  end // BEQ
            3'b001: begin aluOperation <= `ALU_EQ;                          end // BNE
            3'b100: begin aluOperation <= `ALU_SLT;   branchTestTrue <= 1;  end // BLT
            3'b101: begin aluOperation <= `ALU_SLT;                         end // BGE
            3'b110: begin aluOperation <= `ALU_SLTU;  branchTestTrue <= 1;  end // BLTU
            3'b111: begin aluOperation <= `ALU_SLTU;                        end // BGEU
            default:      aluOperation <= `ALU_EQ; 
            endcase
            state <= EX1;
          end
          else
          if (state == EX1)
          begin
            aluSrcA       <= `ALU_SRC_A_PREV_PC;
            aluSrcB       <= `ALU_SRC_B_IMM_B_TYPE;
            aluOperation  <= `ALU_ADD;
            
            if ((branchTestTrue & aluFlag) | (~branchTestTrue & ~aluFlag))
            begin
              pcWriteEnable <= 1;
              state         <= EX2;
            end
            else
            begin
              state         <= FETCH0;
            end
            
          end
          else
          if (state == EX2)
          begin
            // Delay?
            pcWriteEnable <= 0;
            state         <= FETCH0;
          end
        end
        else
        // JAL
        if (opcode == 7'b1101111)
        begin
          $display("JAL");
          if (state == DECODE)
          begin
            aluSrcA         <= `ALU_SRC_A_PREV_PC;
            aluSrcB         <= `ALU_SRC_B_IMM_J_TYPE;
            aluOperation    <= `ALU_ADD;
            regDataSrc      <= `REG_DATA_SRC_PC;
            pcWriteEnable   <= 1;
            regWriteEnable  <= 1;
            state           <= EX1;
          end
          else
          begin
            // Delay?
            pcWriteEnable   <= 0;
            regWriteEnable  <= 0;
            state           <= FETCH0;
          end
        end
        else
        // JALR
        if (opcode == 7'b1100111)
        begin
          $display("JALR");
          if (state == DECODE)
          begin
            aluSrcA         <= `ALU_SRC_A_REG;
            aluSrcB         <= `ALU_SRC_B_IMM_I_TYPE;
            aluOperation    <= `ALU_ADD;
            regDataSrc      <= `REG_DATA_SRC_PC;
            pcWriteEnable   <= 1;
            regWriteEnable  <= 1;
            state           <= EX1;
          end
          else
          begin
            // Delay?
            pcWriteEnable   <= 0;
            regWriteEnable  <= 0;
            state           <= FETCH0;
          end
        end
        else
        // Loads
        if (opcode == 7'b0000011)
        begin
          if (state == DECODE)
          begin
            aluSrcA       <= `ALU_SRC_A_REG;
            aluSrcB       <= `ALU_SRC_B_IMM_I_TYPE;
            aluOperation  <= `ALU_ADD;
            regDataSrc    <= `REG_DATA_SRC_MEM;
            addressIsPc   <= 0;
            state         <= EX1;
          end
          else
          if (state == EX1)
          begin
            case (funct3)
            3'b000: begin readLen <= `LOAD_STORE_BYTE; readSignExtend <= 1; end
            3'b001: begin readLen <= `LOAD_STORE_HALF; readSignExtend <= 1; end
            3'b010: begin readLen <= `LOAD_STORE_WORD; end
            3'b100: begin readLen <= `LOAD_STORE_BYTE; end
            3'b101: begin readLen <= `LOAD_STORE_HALF; end
            endcase
            regWriteEnable  <= 1;
            state           <= EX2;
          end
          else
          if (state == EX2)
          begin
            regWriteEnable  <= 0;
            addressIsPc     <= 1;
            state           <= FETCH0;
          end
        end
        else
        // Stores
        if (opcode == 7'b0100011)
        begin
          if (state == DECODE)
          begin
            aluSrcA         <= `ALU_SRC_A_REG;
            aluSrcB         <= `ALU_SRC_B_IMM_S_TYPE;
            aluOperation    <= `ALU_ADD;
            regDataSrc      <= `REG_DATA_SRC_MEM;
            readLen         <= `LOAD_STORE_WORD;
            readSignExtend  <= 0;
            addressIsPc     <= 0;
            state           <= EX1;
          end
          else
          if (state == EX1)
          begin
            // Delay?
            state       <= EX2;
          end
          else
          if (state == EX2)
          begin
            case (funct3)
            3'b000: writeLen  <= `LOAD_STORE_BYTE;
            3'b001: writeLen  <= `LOAD_STORE_HALF;
            3'b010: writeLen  <= `LOAD_STORE_WORD;
            endcase
            writeEnable     <= 1;
            state           <= EX3;
          end
          else
          if (state == EX3)
          begin
            addressIsPc <= 1;
            writeEnable <= 0;
            state       <= FETCH0;
          end
        end
        
        else
        begin
          $display("Error %x [%x]", ir, opcode);
          state       <= FETCH0;
        end
      end
      
    end
  end
  
  
endmodule
