description: SPI Controller with multiple state machine, clock divider, and protocol handling

module_code:
module spi_controller (
    input clk,
    input rst_n,
    input [7:0] data_in,
    input start,
    input cpol,
    input cpha,
    output reg [7:0] data_out,
    output reg busy,
    output reg spi_clk,
    output reg mosi,
    input miso
);
    localparam IDLE = 4'd0, CLK_LOW = 4'd1, CLK_HIGH = 4'd2, SAMPLE = 4'd3, SHIFT = 4'd4, STOP = 4'd5, ERROR = 4'd6, PREPARE = 4'd7, TURNAROUND = 4'd8, DUMMY = 4'd9;
    localparam DIV_2 = 3'd0, DIV_4 = 3'd1, DIV_8 = 3'd2, DIV_16 = 3'd3;
    reg [3:0] state, next_state;
    reg [3:0] bit_count;
    reg [2:0] clk_div;
    reg [2:0] div_counter;
    reg internal_clk;
    reg [7:0] shift_reg;
    reg error_flag;
    reg mode_fault;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            internal_clk <= 1'b0;
            div_counter <= 3'd0;
        end else if (state == IDLE) begin
            internal_clk <= 1'b0;
            div_counter <= 3'd0;
        end else begin
            if (div_counter >= clk_div) begin
                div_counter <= 3'd0;
                internal_clk <= ~internal_clk;
            end else begin
                div_counter <= div_counter + 1'b1;
            end
        end
    end

    always @(*) begin
        next_state = state;
        case (state)
            IDLE: if (start && !busy) next_state = PREPARE;
            PREPARE: next_state = TURNAROUND;
            TURNAROUND: next_state = CLK_LOW;
            CLK_LOW: next_state = (!cpha) ? SAMPLE : SHIFT;
            CLK_HIGH: begin
                if (cpha) next_state = SAMPLE;
                else if (bit_count == 4'd0) next_state = STOP;
                else next_state = CLK_LOW;
            end
            SAMPLE: next_state = (bit_count == 4'd0) ? CLK_HIGH : SHIFT;
            SHIFT: next_state = CLK_HIGH;
            STOP: next_state = DUMMY;
            DUMMY: next_state = IDLE;
            ERROR: next_state = (mode_fault) ? ERROR : IDLE;
            default: next_state = IDLE;
        endcase
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
            bit_count <= 4'd7;
            shift_reg <= 8'd0;
            data_out <= 8'd0;
            busy <= 1'b0;
            mosi <= 1'b0;
            error_flag <= 1'b0;
            mode_fault <= 1'b0;
            clk_div <= DIV_4;
        end else begin
            state <= next_state;
            case (state)
                IDLE: begin
                    busy <= 1'b0;
                    bit_count <= 4'd7;
                    if (start && !busy) begin busy <= 1'b1; shift_reg <= data_in; end
                end
                PREPARE: begin busy <= 1'b1; mosi <= shift_reg[7]; end
                TURNAROUND: mosi <= 1'b0;
                CLK_LOW: if (cpha == 1'b0) mosi <= shift_reg[7];
                SAMPLE: begin
                    if (cpha == 1'b1) mosi <= shift_reg[7];
                    shift_reg <= {shift_reg[6:0], miso};
                end
                SHIFT: begin
                    shift_reg <= {shift_reg[6:0], miso};
                    if (bit_count > 4'd0) bit_count <= bit_count - 1'b1;
                end
                CLK_HIGH: begin
                    if (cpha == 1'b0) begin
                        shift_reg <= {shift_reg[6:0], miso};
                        if (bit_count > 4'd0) bit_count <= bit_count - 1'b1;
                    end
                    if (error_flag) mode_fault <= 1'b1;
                end
                STOP: begin data_out <= shift_reg; busy <= 1'b0; mosi <= 1'b0; end
                DUMMY: busy <= 1'b0;
                ERROR: busy <= 1'b1;
            endcase
        end
    end
endmodule

testbench:
`timescale 1ns / 1ps
module tb_spi_controller;
    reg clk, rst_n, start, cpol, cpha, miso;
    reg [7:0] data_in;
    wire [7:0] data_out;
    wire busy, spi_clk, mosi;
    spi_controller dut (.clk(clk), .rst_n(rst_n), .data_in(data_in), .start(start), .cpol(cpol), .cpha(cpha), .data_out(data_out), .busy(busy), .spi_clk(spi_clk), .mosi(mosi), .miso(miso));
    always #5 clk = ~clk;
    initial begin clk=0; rst_n=0; data_in=8'h00; start=0; cpol=0; cpha=0; miso=0; #20 rst_n=1; #10;
        @(posedge clk); data_in=8'hA5; start=1; @(posedge clk); start=0;
        wait(busy==0); #50;
        @(posedge clk); data_in=8'h5A; start=1; cpol=1; cpha=1; @(posedge clk); start=0;
        wait(busy==0); #50;
        @(posedge clk); data_in=8'hFF; start=1; cpol=0; cpha=1; @(posedge clk); start=0;
        wait(busy==0); #100; $finish;
    end
endmodule


description: I2C Master Controller with FSM, clock stretching, and arbitration

module_code:
module i2c_master (
    input clk, input rst_n, input [6:0] addr, input rw, input [7:0] data_in, input start_tx,
    output reg [7:0] data_out, output reg busy, output reg ack_err, inout scl, inout sda
);
    localparam IDLE=5'd0, START=5'd1, ADDR_W=5'd2, ADDR_R=5'd3, ACK1=5'd4, DATA_WR=5'd5, DATA_RD=5'd6, ACK2=5'd7, NACK=5'd8, STOP=5'd9, ERROR=5'd10, CLOCK_STRETCH=5'd11, ARBITRATION_LOST=5'd12, RESTART=5'd13;
    localparam THIGH=4'd2, TLOW=4'd2;
    reg [4:0] state, next_state;
    reg [3:0] bit_count;
    reg [7:0] tx_shift, rx_shift;
    reg scl_en, sda_en, sda_in, stretch_en;
    reg [3:0] stretch_cnt;
    reg arbitration_lost, first_bit;
    assign scl = scl_en ? 1'b0 : 1'bz;
    assign sda = sda_en ? 1'b0 : 1'bz;
    assign sda_in = sda;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin stretch_cnt <= 4'd0;
        end else if (stretch_en) begin
            if (stretch_cnt >= THIGH) begin stretch_cnt <= 4'd0; stretch_en <= 1'b0; end
            else stretch_cnt <= stretch_cnt + 1'b1;
        end
    end

    always @(*) begin
        next_state = state;
        case (state)
            IDLE: if (start_tx && !busy) next_state = START;
            START: next_state = (scl_in==1'b1 && sda_in==1'b0) ? ADDR_W : ERROR;
            ADDR_W: if (bit_count==4'd0) next_state = ACK1;
            ADDR_R: if (bit_count==4'd0) next_state = ACK1;
            ACK1: begin
                if (sda_in==1'b0) next_state = (rw==1'b0) ? DATA_WR : DATA_RD;
                else next_state = (arbitration_lost) ? ARBITRATION_LOST : NACK;
            end
            ACK2: next_state = (sda_in==1'b0) ? DATA_WR : STOP;
            STOP: next_state = (scl_in==1'b1 && sda_in==1'b1) ? IDLE : CLOCK_STRETCH;
            CLOCK_STRETCH: if (scl_in==1'b1) next_state = (sda_in==1'b1) ? IDLE : STOP;
            ERROR: next_state = STOP;
            default: next_state = IDLE;
        endcase
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin state<=IDLE; busy<=0; bit_count<=4'd7; tx_shift<=0; rx_shift<=0; data_out<=0; ack_err<=0; scl_en<=0; sda_en<=0; stretch_en<=0; arbitration_lost<=0; first_bit<=1;
        end else begin
            state <= next_state;
            case (state)
                IDLE: begin busy<=0; scl_en<=0; sda_en<=0; ack_err<=0; bit_count<=4'd7; if (start_tx&&!busy) begin busy<=1; tx_shift<={addr,rw}; end end
                START: begin busy<=1; sda_en<=1; tx_shift<={addr,rw}; bit_count<=4'd7; end
                ADDR_W: begin scl_en<=1; sda_en<=1; if(bit_count>0) begin sda_en<=tx_shift[7]; tx_shift<={tx_shift[6:0],1'b0}; bit_count<=bit_count-1; end else begin sda_en<=0; bit_count<=4'd7; end end
                ACK1: sda_en<=0;
                DATA_WR: begin scl_en<=1; sda_en<=1; tx_shift<=data_in; bit_count<=4'd7; end
                DATA_RD: begin scl_en<=1; sda_en<=0; rx_shift<={rx_shift[6:0],sda_in}; if(bit_count>0) bit_count<=bit_count-1; else data_out<={rx_shift[6:0],sda_in}; end
                ACK2: sda_en<=(rw==0)?0:1;
                NACK: sda_en<=1;
                STOP: begin sda_en<=1; scl_en<=1; end
                CLOCK_STRETCH: begin scl_en<=0; stretch_en<=1; end
            endcase
        end
    end
endmodule

testbench:
`timescale 1ns/1ps
module tb_i2c_master;
    reg clk,rst_n,start_tx,rw; reg[6:0] addr; reg[7:0] data_in;
    wire[7:0] data_out; wire busy,ack_err,scl,sda;
    pullup(sda); pullup(scl);
    i2c_master dut(.clk(clk),.rst_n(rst_n),.addr(addr),.rw(rw),.data_in(data_in),.start_tx(start_tx),.data_out(data_out),.busy(busy),.ack_err(ack_err),.scl(scl),.sda(sda));
    always #5 clk=~clk;
    initial begin clk=0; rst_n=0; addr=7'h50; rw=0; data_in=8'h00; start_tx=0; #20 rst_n=1; #30;
        @(posedge clk); addr=7'h50; rw=0; data_in=8'hA5; start_tx=1; @(posedge clk); start_tx=0;
        wait(busy==0); #100;
        @(posedge clk); addr=7'h50; rw=1; start_tx=1; @(posedge clk); start_tx=0;
        wait(busy==0); #100; $finish;
    end
endmodule