module SPIMaster_341450853309219412(
  input       clock,
  input       reset,
  
  output      tx_ready,
  input       tx_valid,
  input [7:0] tx_byte,

  // whether or not to reset CS after sending data
  input       tx_clear_cs,

  output      sclk,
  output      mosi,
  output      n_cs
);

  localparam STATE_IDLE = 2'd0,
    STATE_CS_ASSERT = 2'd1,
    STATE_TX = 2'd2,
    STATE_CS_DEASSERT = 2'd3;

  localparam TX_COUNTER_MAX = 3'h7;

  // number of cycles-1 to wait after asserting / before deasserting CS 
  localparam CS_COUNTER_MAX = 4'd10;

  reg [1:0] state;
  reg [7:0] tx_byte_reg;
  reg sclk_mask;
  reg mosi_mask;
  reg tx_ready_reg;
  reg [2:0] tx_counter_reg;
  reg n_cs_reg;
  reg tx_clear_cs_reg;
  reg [3:0] cs_delay_counter;

  assign tx_ready = tx_ready_reg;
  assign sclk = ~clock & sclk_mask;
  assign mosi = tx_byte_reg[7] & mosi_mask;
  assign n_cs = n_cs_reg;

  always @(posedge clock) begin
    if (reset) begin

      state <= STATE_IDLE;
      tx_byte_reg <= 8'h0;
      sclk_mask <= 1'b0;
      mosi_mask <= 1'b0;
      tx_ready_reg <= 1'b0;
      tx_counter_reg <= 3'd0;
      n_cs_reg <= 1'b1;
      tx_clear_cs_reg <= 1'b1;
      cs_delay_counter <= 4'd0;

    end else begin

      if (state == STATE_IDLE) begin

        tx_ready_reg <= 1'b1;

        if (tx_valid == 1'b1) begin
          tx_byte_reg <= tx_byte;
          tx_clear_cs_reg <= tx_clear_cs;
          tx_ready_reg <= 1'b0;
          n_cs_reg <= 1'b0;

          if (n_cs_reg == 1'b1) begin
            // CS is not asserted: assert it first
            state <= STATE_CS_ASSERT;
          end else begin
            // CS is already asserted: transition to TX
            state <= STATE_TX;
            sclk_mask <= 1'b1;
            mosi_mask <= 1'b1;
          end
        end

      end else if (state == STATE_CS_ASSERT) begin

        // assert CS before transitioning to TX
        if (cs_delay_counter == CS_COUNTER_MAX) begin

          cs_delay_counter <= 4'd0;
          state <= STATE_TX;
          sclk_mask <= 1'b1;
          mosi_mask <= 1'b1;

        end else begin
          cs_delay_counter <= cs_delay_counter + 4'd1;
        end

      end else if (state == STATE_TX) begin

        tx_byte_reg <= {tx_byte_reg[6:0], 1'b0};

        if (tx_counter_reg == TX_COUNTER_MAX) begin
          tx_counter_reg <= 3'd0;
          sclk_mask <= 1'b0;
          mosi_mask <= 1'b0;

          // check if CS needs to be reset
          if (tx_clear_cs_reg == 1'b1) begin
            state <= STATE_CS_DEASSERT;
          end else begin
            state <= STATE_IDLE;
          end
        end else begin
          tx_counter_reg <= tx_counter_reg + 3'd1;
        end

      end else if (state == STATE_CS_DEASSERT) begin

        // wait before deasserting CS and transitioning to idle

        if (cs_delay_counter == CS_COUNTER_MAX) begin

          if (n_cs_reg == 1'b0) begin

            cs_delay_counter <= 4'd0;
            n_cs_reg <= 1'b1;

          end else begin

            cs_delay_counter <= 4'd0;
            state <= STATE_IDLE;

          end

        end else begin
          cs_delay_counter <= cs_delay_counter + 4'd1;
        end

      end
    end
  end

endmodule