`timescale 1ns / 1ps
`include "cpu.h"

module exe_stage(
    input                          clk           ,
    input                          reset         ,
    input                          es_flush,
    
    input                          ms_allowin    ,
    output                         es_allowin    ,
    
    input                          ds_to_es_valid,
    input  [`DS_TO_ES_BUS_WD -1:0] ds_to_es_bus  ,
    
    input  [`MS_TO_ES_BUS_WD -1:0] ms_to_es_bus  ,
    input  [`WS_TO_ES_BUS_WD -1:0] ws_to_es_bus  ,
    input  [`FW_TO_ES_BUS_WD -1:0] fw_to_es_bus  ,
    output [`ES_TO_FW_BUS_WD -1:0] es_to_fw_bus  ,
    
    output [`ES_TO_LU_BUS_WD -1:0] es_to_lu_bus  ,

    output                         es_to_ms_valid,
    output [`ES_TO_MS_BUS_WD -1:0] es_to_ms_bus  ,
    
    output        data_ram_en   ,
    output [ 3:0] data_ram_wen  ,
    output [31:0] data_ram_addr ,
    output [31:0] data_ram_wdata
    );
    
    reg         es_valid      ;
    wire        es_ready_go   ;
    
    reg  [`DS_TO_ES_BUS_WD -1:0] ds_to_es_bus_r;
    
    wire [ 1:0] es_ALUAsrc  ;
    wire [ 2:0] es_ALUBsrc  ;
    wire [10:0] es_ALUctr   ;
    wire [ 5:0] es_Branch   ;
    wire [ 1:0] es_Jump     ;
    wire        es_MemtoReg ;
    wire        es_MemWr    ;
    wire        es_RegWr    ;
    wire [ 4:0] es_Load     ;
    wire [ 2:0] es_Store    ;
    wire        es_shamt    ;
    wire [31:0] es_imm      ;
    wire [31:0] es_rs1_value;
    wire [31:0] es_rs2_value;
    wire [ 4:0] es_rd       ;
    wire [ 4:0] es_rs1      ;
    wire [ 4:0] es_rs2      ;
    wire [31:0] es_pc       ;
    
    assign { es_ALUctr      ,  //177:167
             es_shamt       ,  //166:166
             es_Load        ,  //166:162
             es_Store       ,  //161:159
             es_MemWr       ,  //158:158
             es_Branch      ,  //157:152
             es_Jump        ,  //151:150
             es_MemtoReg    ,  //149:149
             es_RegWr       ,  //148:148
             es_ALUAsrc     ,  //147:146
             es_ALUBsrc     ,  //145:143
             es_rd          ,  //142:138
             es_rs1         ,  //137:133
             es_rs2         ,  //132:128
             es_imm         ,  //127:96
             es_rs1_value   ,  //95 :64
             es_rs2_value   ,  //63 :32
             es_pc             //31 :0
           } = ds_to_es_bus_r;
    
    wire [ 31:0] br_target   ;
    
    wire [31:0] es_alu_src1  ;
    wire [31:0] es_alu_src2  ;
    wire [31:0] es_alu_result;
    wire        es_Carry     ;
    wire        es_Sign      ;
    wire        es_Overflow  ;
    wire        es_Zero      ;   
    
    assign es_to_ms_bus = { es_Carry        ,  //119:119
                            es_Sign         ,  //118:118
                            es_Overflow     ,  //117:117
                            es_Zero         ,  //116:116
                            br_target       ,  //115:84
                            es_Load         ,  //83 :79
                            es_Branch       ,  //78 :73
                            es_Jump         ,  //72 :71
                            es_MemtoReg     ,  //70 :70
                            es_RegWr        ,  //69 :69
                            es_rd           ,  //68 :64
                            es_alu_result   ,  //63 :32
                            es_pc              //31 :0
                           };
                           
    wire [ 1:0] BusAFw;
    wire [ 1:0] BusBFw;
    wire        DiSrc;
    
    assign {BusAFw,
            BusBFw,
            DiSrc } = fw_to_es_bus;
    
    wire [31:0] rf_wdata;
    wire [31:0] ms_alu_result;
    
    assign rf_wdata       = ws_to_es_bus[31:0];
    assign ms_alu_result  = ms_to_es_bus[31:0];
    assign es_to_fw_bus   = {es_rs2, es_rd, es_RegWr, es_MemWr};
    
    assign es_to_lu_bus   = {es_rd, es_Load};
    
    wire [31:0] es_rs1_forward;
    wire [31:0] es_rs2_forward;
    
    assign es_ready_go    = 1'b1;
    assign es_allowin     = !es_valid || es_ready_go && ms_allowin;
    assign es_to_ms_valid =  es_valid && es_ready_go;
    always @(posedge clk) begin
        if (reset) begin
            es_valid <= 1'b0;
        end
        else if (es_allowin) begin
            es_valid <= ds_to_es_valid;
        end
    
        if (reset || es_flush) begin
            ds_to_es_bus_r <= 0;
        end
        else if (ds_to_es_valid && es_allowin) begin
            ds_to_es_bus_r <= ds_to_es_bus;
        end
//        else begin
//            ds_to_es_bus_r <= 0;
//        end
    end
    
    assign es_rs1_forward = (BusAFw == 2'b00) ? es_rs1_value  :
                            (BusAFw == 2'b01) ? rf_wdata      :
                            (BusAFw == 2'b10) ? ms_alu_result :
                                                32'b0;
    assign es_rs2_forward = (BusBFw == 2'b00) ? es_rs2_value  :
                            (BusBFw == 2'b01) ? rf_wdata      :
                            (BusBFw == 2'b10) ? ms_alu_result :
                                                32'b0;
    
    assign es_alu_src1 = ({32{es_ALUAsrc[1]}} &  es_pc)
                       | ({32{es_ALUAsrc[0]}} &  es_rs1_forward);
    assign es_alu_src2 = ({32{es_ALUBsrc[2]}} & (es_shamt ? {27'b0, es_imm[4:0]} : es_imm))
                       | ({32{es_ALUBsrc[1]}} &  32'd4)
                       | ({32{es_ALUBsrc[0]}} &  es_rs2_forward);
                       
    alu u_alu(
        .alu_op         (es_ALUctr    ),
        .alu_src1       (es_alu_src1  ),
        .alu_src2       (es_alu_src2  ),
        .alu_result     (es_alu_result),
        
        .Carry          (es_Carry    ),
        .Sign           (es_Sign     ),
        .Overflow       (es_Overflow ),   
        .Zero           (es_Zero     )
        );
        

    assign data_ram_en    = 1'b1;
    assign data_ram_wen   = (es_MemWr && es_valid) ?
                                  (({4{es_Store[0]}} &
                                       ({4{es_alu_result[1:0] == 2'b00}} & 4'b0001)
                                     | ({4{es_alu_result[1:0] == 2'b01}} & 4'b0010)
                                     | ({4{es_alu_result[1:0] == 2'b10}} & 4'b0100)
                                     | ({4{es_alu_result[1:0] == 2'b11}} & 4'b1000))
                                |  ({4{es_Store[1]}} &                    
                                       ({4{es_alu_result[1:0] == 2'b01}} & 4'b0011 )
                                     | ({4{es_alu_result[1:0] == 2'b10}} & 4'b1100 ))
                                |  ({4{es_Store[2]}}                     & 4'b1111  ))
                                                                         : 4'b0000   ;
    assign data_ram_addr  = es_alu_result;
    assign data_ram_wdata = DiSrc ? rf_wdata :
                                   (es_Store[0] ? {4{es_rs2_value[ 7:0]}} :
                                    es_Store[1] ? {2{es_rs2_value[15:0]}} :
                                    es_Store[2] ?    es_rs2_value         :
                                                     32'b0                );
    
    assign br_target      = ({{31{es_Jump[1]               }}, 1'b0} & (es_alu_src1 + es_imm))
                          | ( {32{es_Jump[0] | (^es_Branch)}}        & (es_pc       + es_imm));
                          
endmodule
