`timescale 1ns/1ps
module spi_master_mode0 (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        start,
    input  logic [15:0] half_divider,
    input  logic [7:0]  tx_data,
    output logic        busy,
    output logic        done,
    output logic [7:0]  rx_data,
    output logic        spi_sck,
    output logic        spi_cs_n,
    output logic        spi_mosi,
    input  logic        spi_miso
);
    typedef enum logic [1:0] {IDLE, SHIFT, HOLD_CS} state_t;
    state_t state;
    logic [15:0] divider, count;
    logic [2:0] bit_index;
    logic [7:0] tx_shift, rx_shift;

    // Drive the pins from registers rather than a combinational state decode.
    assign busy = !spi_cs_n;
    assign spi_mosi = tx_shift[7];

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
            divider <= 16'd1;
            count <= '0;
            bit_index <= '0;
            tx_shift <= '0;
            rx_shift <= '0;
            rx_data <= '0;
            spi_sck <= 1'b0;
            spi_cs_n <= 1'b1;
            done <= 1'b0;
        end else begin
            done <= 1'b0;
            case (state)
                IDLE: if (start) begin
                    divider <= half_divider == 0 ? 16'd1 : half_divider;
                    count <= '0;
                    bit_index <= 3'd7;
                    tx_shift <= tx_data;
                    rx_shift <= '0;
                    spi_cs_n <= 1'b0;
                    state <= SHIFT;
                end

                SHIFT: if (count == divider - 16'd1) begin
                    count <= '0;
                    spi_sck <= !spi_sck;
                    if (!spi_sck) begin
                        rx_shift <= {rx_shift[6:0], spi_miso};
                    end else if (bit_index == 0) begin
                        state <= HOLD_CS;
                    end else begin
                        bit_index <= bit_index - 1'b1;
                        tx_shift <= {tx_shift[6:0], 1'b0};
                    end
                end else begin
                    count <= count + 1'b1;
                end

                // Give the selected device one half period after the last falling edge.
                HOLD_CS: if (count == divider - 16'd1) begin
                    rx_data <= rx_shift;
                    tx_shift <= '0;
                    spi_cs_n <= 1'b1;
                    done <= 1'b1;
                    state <= IDLE;
                end else begin
                    count <= count + 1'b1;
                end

                default: begin
                    state <= IDLE;
                    spi_sck <= 1'b0;
                    spi_cs_n <= 1'b1;
                    tx_shift <= '0;
                end
            endcase
        end
    end
endmodule
