//////////////////////////////////////////////////////////////////////////////////
// Company                  :   NPK TAIR
// Engineer                 :   Yuri Donskoy
// 
// Create Date (dd/mm/yyyy) :
// Design Name              :
// Module Name              :
// Project Name             :
// Target Devices           :
// Tool versions            :
// Description              :
//
// Dependencies             : 
// 
// Revision                 :   1.0 - It only send data (no miso port)
// Additional Comments      :   MISO port need to be add. What about multiple slave select?
//        
//////////////////////////////////////////////////////////////////////////////////

module SpiMaster (
    clk_i,
    rst_i,

    data_i,
    valid_i,
    ready_o,

    mosi_o,
    sck_o,
    ss_o
);

//================================================================================
//
//  FUNCTIONS
//
//================================================================================

    function integer bit_num;
        input integer value;
        begin
            bit_num = 0;
            while (value > 0) begin
                value   = value >> 1;
                bit_num = bit_num + 1;
            end
        end
    endfunction

//================================================================================
//
//  PARAMETER/LOCALPARAM
//
//================================================================================

    parameter   CLK_DIVISOR_POWER   = 4; //WAS 2 !! DONT FORGET TO CHANGE!
    parameter   DATA_WIDTH          = 24;
    parameter   CPOL                = 0;
    parameter   CPHA                = 0;
    parameter   DATA_DIRECTION      = "MSBT";   //  MSB or LSB
    parameter   EN_START_DELAY      = "NO";     //  YES or NO

    localparam  BIT_CNT_W           = bit_num(DATA_WIDTH);

//================================================================================
//
//  STATE MACHINE STATES
//
//================================================================================

    localparam  SM_IDLE_S   = 2'b00;
    localparam  SM_START_S  = 2'b01;
    localparam  SM_DATA_S   = 2'b10;
    localparam  SM_STOP_S   = 2'b11;

//================================================================================
//
//  PORTS
//
//================================================================================

    input                               clk_i;
    input                               rst_i;

    input           [DATA_WIDTH-1:0]    data_i;
    input                               valid_i;
    output                              ready_o;

    output  reg                         mosi_o;
    output  reg                         sck_o;
    output  reg                         ss_o;

//================================================================================
//
//  REG/WIRE
//
//================================================================================

    reg     [1:0]               sm_curr_state;
    reg     [1:0]               sm_next_state;

    reg                         sm_clk_div_en;

    //  Clock divider outputs

    wire                        clk_divider_redge;
    wire                        clk_divider_fedge;

    //  Bits counter

    reg     [BIT_CNT_W-1:0]     bit_cnt_r;
    reg     [BIT_CNT_W-1:0]     bit_cnt_next;

    //  Data buffers

    reg     [DATA_WIDTH-1:0]    tx_buffer_r;
    reg     [DATA_WIDTH-1:0]    tx_buffer_next;
    wire    [DATA_WIDTH-1:0]    tx_buffer_shifted;
    wire                        tx_curr_bit;

    //  Output data next
    reg                         mosi_next;
    reg                         sck_next;
    reg                         ss_next;

    //  Edges

    wire                        mosi_shift_edge;

    wire                        ss_start_edge;
    wire                        ss_stop_edge;

    wire                        sck_leading_edge;
    wire                        sck_trailing_edge;

//================================================================================
//
//  INTEGER/GENVAR
//
//================================================================================



//================================================================================
//
//  ASSIGN
//
//================================================================================

    assign  mosi_shift_edge     = (CPHA[0] == 1'b1) && (EN_START_DELAY != "YES") || (CPHA[0] == 1'b0) && (EN_START_DELAY == "YES") ? clk_divider_fedge : clk_divider_redge;
    assign  ss_start_edge       = (EN_START_DELAY == "YES") ? clk_divider_fedge : clk_divider_redge;
    assign  ss_stop_edge        = (EN_START_DELAY == "YES") ? clk_divider_redge : clk_divider_fedge;
    assign  sck_leading_edge    = (EN_START_DELAY == "YES") ? clk_divider_redge : clk_divider_fedge;
    assign  sck_trailing_edge   = (EN_START_DELAY == "YES") ? clk_divider_fedge : clk_divider_redge;
    assign  tx_buffer_shifted   = (DATA_DIRECTION == "MSB") ? tx_buffer_r << 1 : tx_buffer_r >> 1;
    assign  tx_curr_bit         = (DATA_DIRECTION == "MSB") ? tx_buffer_r[DATA_WIDTH-1] : tx_buffer_r[0];

    assign  ready_o             = sm_curr_state == SM_IDLE_S;

//================================================================================
//
//  CODING
//
//================================================================================

//  Sequential logic

always @(posedge clk_i or posedge rst_i) begin
    if (rst_i) begin
        sm_curr_state   <= 0;
        tx_buffer_r     <= {DATA_WIDTH{1'b0}};
        bit_cnt_r       <= {BIT_CNT_W{1'b0}};
        mosi_o          <= 1'b0;
        sck_o           <= CPOL[0];
        ss_o            <= 1'b1;
    end else begin
        sm_curr_state   <= sm_next_state;
        tx_buffer_r     <= tx_buffer_next;
        bit_cnt_r       <= bit_cnt_next;
        mosi_o          <= mosi_next;
        sck_o           <= sck_next;
        ss_o            <= ss_next;
    end
end

//  Combinational logic

always @(*) begin
    sm_next_state   = SM_IDLE_S;
    tx_buffer_next  = tx_buffer_r;
    mosi_next       = mosi_o;
    sck_next        = sck_o;
    ss_next         = ss_o;
    sm_clk_div_en   = 1'b1;
    bit_cnt_next    = bit_cnt_r;

    case(sm_curr_state)

        SM_IDLE_S   : begin
            if (valid_i) begin
                sm_next_state   = SM_START_S;
            end else begin
                sm_next_state   = SM_IDLE_S;
            end
            tx_buffer_next  = data_i;
            sm_clk_div_en   = 1'b0;
            bit_cnt_next    = {BIT_CNT_W{1'b0}};
        end

        SM_START_S  : begin
            if (ss_start_edge) begin
                sm_next_state   = SM_DATA_S;
                ss_next         = 1'b0;
                if (!CPHA[0]) begin
                    mosi_next       = tx_curr_bit;
                    tx_buffer_next  = tx_buffer_shifted;
                    bit_cnt_next    = bit_cnt_r + {{(BIT_CNT_W-1){1'b0}}, 1'b1};
                end
            end else begin
                sm_next_state   = SM_START_S;
            end
        end

        SM_DATA_S   : begin
            sm_next_state   = SM_DATA_S;
            if (sck_leading_edge) begin
                sck_next    = ~CPOL[0];
            end

            if  (sck_trailing_edge) begin
                sck_next    = CPOL[0];
                if (bit_cnt_r == DATA_WIDTH[BIT_CNT_W-1:0]) begin
                    sm_next_state   = SM_STOP_S;
                end
            end

            if (mosi_shift_edge) begin
                mosi_next       = tx_curr_bit;
                tx_buffer_next  = tx_buffer_shifted;
                bit_cnt_next    = bit_cnt_r + {{(BIT_CNT_W-1){1'b0}}, 1'b1};
            end

        end

        SM_STOP_S   : begin
            if (ss_stop_edge) begin
                if (CPHA[0]) begin
                    mosi_next   = tx_curr_bit;
                end
                sm_next_state   = SM_IDLE_S;
                ss_next         = 1'b1;
            end else begin
                sm_next_state   = SM_STOP_S;
            end
        end

    endcase
end

//  Clock divider

Power2ClkDivider #(
    .DIVISOR_POWER      (CLK_DIVISOR_POWER)
) ClkDividerInst (
    .clk_i              (clk_i),
    .rst_i              (rst_i),
    .valid_i            (sm_clk_div_en),
    .signal_o           (),
    .rising_edge_o      (clk_divider_redge),
    .falling_edge_o     (clk_divider_fedge)
);

endmodule