module pci_spoci_ctrl
(
    reset_i             ,
    clk_i               ,
                        
    do_rnd_read_i       ,
    do_seq_read_i       ,
    do_write_i          ,
                        
    write_done_o        ,
    dat_rdy_o           ,
    no_ack_o            ,
                        
    adr_i               ,
    dat_i               ,
    dat_o               ,

    pci_spoci_sda_i     ,
    pci_spoci_sda_oe_o  ,
    pci_spoci_scl_oe_o
);

parameter tx_rx_state_width = 9     ;
parameter tx_rx_idle        = 'h1   ;
parameter tx_rx_start       = 'h2   ;
parameter tx_rx_restart     = 'h4   ;
parameter tx_rx_send_bits   = 'h8   ;
parameter tx_rx_rec_bits    = 'h10  ;
parameter tx_rx_send_ack    = 'h20  ;
parameter tx_rx_rec_ack     = 'h40  ;
parameter tx_rx_send_nack   = 'h80  ;
parameter tx_rx_stop        = 'h100 ;

parameter rw_seq_state_width    = 5     ;
parameter rw_seq_idle           = 'h1   ;
parameter rw_seq_tx_ctrl        = 'h2   ;
parameter rw_seq_tx_adr         = 'h4   ;
parameter rw_seq_tx_byte        = 'h8   ;
parameter rw_seq_rx_byte        = 'h10  ;

`ifdef PCI33
parameter cnt_width     = 9     ;
parameter period_cnt    = 334   ;
`endif

`ifdef PCI66
parameter cnt_width     = 10    ;
parameter period_cnt    = 667   ;
`endif

input   reset_i ,
        clk_i   ;

input   do_rnd_read_i   ,
        do_seq_read_i   ,
        do_write_i      ;

output  write_done_o    ,
        dat_rdy_o       ,
        no_ack_o        ;

input   [10: 0] adr_i   ;
input   [ 7: 0] dat_i   ;
output  [ 7: 0] dat_o   ;

input   pci_spoci_sda_i     ;

output  pci_spoci_sda_oe_o  ,
        pci_spoci_scl_oe_o  ;

reg write_done_o    ,
    dat_rdy_o       ,
    no_ack_o        ;

reg [ 7: 0] dat_o   ;

reg pci_spoci_sda_oe_o  ,
    pci_spoci_scl_oe_o  ;

reg clk_gen_cnt_en  ;
reg clk_gen_cnt_clr ;
reg [cnt_width - 1:0] clk_gen_cnt   ;

reg [tx_rx_state_width - 1:0] tx_rx_state       ;
reg [tx_rx_state_width - 1:0] tx_rx_next_state  ;
reg tx_rx_sm_idle ;

reg scl_oe      ;
reg scl_oe_en   ;
reg sda_oe      ;
reg sda_oe_en   ;

reg sda_i_reg_en    ;
reg sda_i_reg       ;

always@(posedge clk_i or posedge reset_i)
begin
    if (reset_i)
    begin
        clk_gen_cnt <= 'h0  ;

        tx_rx_state <= tx_rx_idle   ;

    `ifdef ACTIVE_LOW_OE
        pci_spoci_sda_oe_o <= 1'b1 ;
        pci_spoci_scl_oe_o <= 1'b1 ;
    `endif

    `ifdef ACTIVE_HIGH_OE
        pci_spoci_sda_oe_o <= 1'b0 ;
        pci_spoci_scl_oe_o <= 1'b0 ;
    `endif

        sda_i_reg <= 1'b1 ;
    end
    else
    begin
        tx_rx_state <= tx_rx_next_state ;
        
        if (clk_gen_cnt_clr)
            clk_gen_cnt <= 'h0  ;
        else if (clk_gen_cnt_en)
            clk_gen_cnt <= clk_gen_cnt + 1'b1 ;

    
        if (sda_oe_en)
        begin
        `ifdef ACTIVE_LOW_OE
            pci_spoci_sda_oe_o <= ~sda_oe   ;
        `endif

        `ifdef ACTIVE_HIGH_OE
            pci_spoci_sda_oe_o <= sda_oe    ;
        `endif
        end

        if (scl_oe_en)
        begin
        `ifdef ACTIVE_LOW_OE
            pci_spoci_scl_oe_o <= ~scl_oe   ;
        `endif

        `ifdef ACTIVE_HIGH_OE
            pci_spoci_scl_oe_o <= scl_oe    ;
        `endif
        end

        if (sda_i_reg_en)
            sda_i_reg <= pci_spoci_sda_i ;
    end
end

reg [ 7: 0] tx_shift_reg ;

reg send_start  ;
reg start_sent  ;

reg send_bit    ;
reg bit_sent    ;

reg rec_bit     ;
reg bit_rec     ;

reg rec_ack     ;
reg ack_rec     ;
reg nack_rec    ;

reg send_ack    ;
reg ack_sent    ;
reg send_nack   ;
reg nack_sent   ;

reg send_stop   ;
reg stop_sent   ;

always@
(
    tx_rx_state     or
    clk_gen_cnt     or
    send_start      or
    send_bit        or
    tx_shift_reg    or
    send_stop       or
    rec_ack         or
    sda_i_reg       or
    rec_bit         or
    send_ack        or
    send_nack
)
begin
    clk_gen_cnt_clr     = 1'b0          ;
    clk_gen_cnt_en      = 1'b0          ;
    tx_rx_next_state    = tx_rx_state   ;
    tx_rx_sm_idle       = 1'b0          ;
    scl_oe              = 1'b0          ;
    sda_oe              = 1'b0          ;
    scl_oe_en           = 1'b0          ;
    sda_oe_en           = 1'b0          ;
    start_sent          = 1'b0          ;
    bit_sent            = 1'b0          ;
    ack_rec             = 1'b0          ;
    nack_rec            = 1'b0          ;
    sda_i_reg_en        = 1'b0          ;
    stop_sent           = 1'b0          ;
    bit_rec             = 1'b0          ;
    ack_sent            = 1'b0          ;
    nack_sent           = 1'b0          ;

    case (tx_rx_state)

    tx_rx_idle:
    begin
        tx_rx_sm_idle = 1'b1 ;

        // from idle state, the only transition can be to the send start bit
        if (send_start)
        begin
            tx_rx_next_state = tx_rx_start  ;
            clk_gen_cnt_clr  = 1'b1         ;
        end
    end

    tx_rx_start:
    begin
        clk_gen_cnt_en  = 1'b1  ;
        sda_oe          = 1'b1  ;

        // start bit is sent by transmiting 0 on the sda line
        if (clk_gen_cnt == (period_cnt >> 1))
        begin
            start_sent = 1'b1 ;
            sda_oe_en  = 1'b1 ;
        end
        
        // after half clock period of driving the sda low, the only possible
        // transition is to send state.
        // if send bit is not active, stop the procedure - undrive sda
        if (clk_gen_cnt == period_cnt)
        begin
            clk_gen_cnt_clr = 1'b1 ;
            if (send_bit)
            begin
                tx_rx_next_state = tx_rx_send_bits ;
            end
            else
            begin
                sda_oe              = 1'b0          ;
                sda_oe_en           = 1'b1          ;
                tx_rx_next_state    = tx_rx_idle    ;
            end
        end
    end

    tx_rx_send_bits:
    begin
        clk_gen_cnt_en = 1'b1 ;

        // generate high to low transition on the scl line immediately
        if (clk_gen_cnt == 'h0)
        begin
            scl_oe      = 1'b1  ;
            scl_oe_en   = 1'b1  ;
        end

        // after half of clock low time, load new value for sda oe, depending on the
        // msb bit in the shift register
        if (clk_gen_cnt == (period_cnt >> 2))
        begin
            sda_oe      = ~tx_shift_reg[7]  ;
            sda_oe_en   = 1'b1              ;
            bit_sent    = 1'b1              ;
        end

        // after clock low time, generate low to high transition on the scl line
        if (clk_gen_cnt == (period_cnt >> 1))
        begin
            scl_oe      = 1'b0  ;
            scl_oe_en   = 1'b1  ;
        end

        // after clock high time, check what to do next
        if (clk_gen_cnt == (period_cnt))
        begin
            clk_gen_cnt_clr = 1'b1 ;

            if (~send_bit)
            begin
                // after transmiting all the bits, the only possible transition is to the state
                // that checks the eprom acknowledge
                if (rec_ack)
                    tx_rx_next_state = tx_rx_rec_ack ;
                else
                begin
                    sda_oe              = 1'b0          ;
                    sda_oe_en           = 1'b1          ;
                    tx_rx_next_state    = tx_rx_idle    ;
                end
            end
        end
    end

    tx_rx_rec_bits:
    begin
        clk_gen_cnt_en = 1'b1 ;
        sda_i_reg_en   = 1'b1 ;

        // generate high to low transition on the scl line immediately
        if (clk_gen_cnt == 'h0)
        begin
            scl_oe      = 1'b1  ;
            scl_oe_en   = 1'b1  ;
        end

        // after half of clock low time, disable sda driver
        if (clk_gen_cnt == (period_cnt >> 2))
        begin
            sda_oe      = 1'b0  ;
            sda_oe_en   = 1'b1  ;
        end

        // after clock low time, generate low to high transition on the scl line
        if (clk_gen_cnt == (period_cnt >> 1))
        begin
            scl_oe      = 1'b0  ;
            scl_oe_en   = 1'b1  ;
        end

        // after half of clock high time, report received bit
        if (clk_gen_cnt == ((period_cnt >> 1) + (period_cnt >> 2)) )
        begin
            bit_rec = 1'b1  ;
        end

        // after clock period is finished, check the next operation
        if (clk_gen_cnt == (period_cnt))
        begin
            clk_gen_cnt_clr = 1'b1 ;

            if (~rec_bit)
            begin
                // when all bits are received, only nack or ack next states are possible
                if (send_ack)
                    tx_rx_next_state = tx_rx_send_ack   ;
                else if (send_nack)
                    tx_rx_next_state = tx_rx_send_nack  ;
                else
                begin
                    tx_rx_next_state = tx_rx_idle    ;
                end
            end
        end
    end

    tx_rx_send_ack:
    begin
        clk_gen_cnt_en  = 1'b1  ;

        // generate high to low transition on the scl line
        if (clk_gen_cnt == 'h0)
        begin
            scl_oe      = 1'b1  ;
            scl_oe_en   = 1'b1  ;
        end

        // after half of clock low time, enable the sda driver
        if (clk_gen_cnt == (period_cnt >> 2))
        begin
            sda_oe      = 1'b1  ;
            sda_oe_en   = 1'b1  ;
            ack_sent    = 1'b1  ;
        end

        // after clock low time, disable the scl driver - generate low to high transition on the scl line
        if (clk_gen_cnt == (period_cnt >> 1))
        begin
            scl_oe      = 1'b0  ;
            scl_oe_en   = 1'b1  ;
        end

        // after clock period time expires, check what to do next
        if (clk_gen_cnt == period_cnt)
        begin
            clk_gen_cnt_clr = 1'b1 ;

            // after the byte is acknowledged, the only possible next state is receive bits
            // state
            if (rec_bit)
                tx_rx_next_state = tx_rx_rec_bits   ;
            else
            begin
                // this should never happen
                sda_oe      = 1'b0  ;
                sda_oe_en   = 1'b1  ;

                tx_rx_next_state = tx_rx_idle ;
            end
        end
    end

    tx_rx_rec_ack:
    begin

        clk_gen_cnt_en  = 1'b1  ;
        sda_i_reg_en    = 1'b1  ;

        // generate high to low transition on the scl line
        if (clk_gen_cnt == 'h0)
        begin
            scl_oe      = 1'b1  ;
            scl_oe_en   = 1'b1  ;
        end

        // after half of clock low time, disable the sda driver
        if (clk_gen_cnt == (period_cnt >> 2))
        begin
            sda_oe      = 1'b0  ;
            sda_oe_en   = 1'b1  ;
        end

        // after clock low time, disable the scl driver - generate low to high transition on the scl line
        if (clk_gen_cnt == (period_cnt >> 1))
        begin
            scl_oe      = 1'b0  ;
            scl_oe_en   = 1'b1  ;
        end

        // after 1/2 clock high time, report ack or nack condition, depending on the sda input state
        if (clk_gen_cnt == ((period_cnt >> 1) + (period_cnt >> 2)) )
        begin
            ack_rec     = ~sda_i_reg ;
            nack_rec    =  sda_i_reg ;
        end

        // after clock period time expires, check what to do next
        if (clk_gen_cnt == period_cnt)
        begin
            clk_gen_cnt_clr = 1'b1 ;

            if (send_bit)
                tx_rx_next_state = tx_rx_send_bits  ;
            else if (rec_bit)
                tx_rx_next_state = tx_rx_rec_bits   ;
            else if (send_stop)
                tx_rx_next_state = tx_rx_stop       ;
            else if (send_start)
                tx_rx_next_state = tx_rx_restart    ;
            else
            begin
                // this should never happen
                tx_rx_next_state = tx_rx_idle ;
            end
        end
    end

    tx_rx_send_nack:
    begin
        clk_gen_cnt_en  = 1'b1  ;

        // generate high to low transition on the scl line
        if (clk_gen_cnt == 'h0)
        begin
            scl_oe      = 1'b1  ;
            scl_oe_en   = 1'b1  ;
        end

        // after half of clock low time, disable the sda driver
        if (clk_gen_cnt == (period_cnt >> 2))
        begin
            sda_oe      = 1'b0  ;
            sda_oe_en   = 1'b1  ;
            nack_sent   = 1'b1  ;
        end

        // after clock low time, disable the scl driver - generate low to high transition on the scl line
        if (clk_gen_cnt == (period_cnt >> 1))
        begin
            scl_oe      = 1'b0  ;
            scl_oe_en   = 1'b1  ;
        end

        // after clock period time expires, check what to do next
        if (clk_gen_cnt == period_cnt)
        begin
            clk_gen_cnt_clr = 1'b1 ;

            // after the no acknowledge is sent, the only possible next state is stop
            // state
            if (send_stop)
                tx_rx_next_state = tx_rx_stop   ;
            else
            begin
                // this should never happen
                tx_rx_next_state = tx_rx_idle ;
            end
        end
    end

    tx_rx_restart:
    begin
        clk_gen_cnt_en = 1'b1 ;

        // generate high to low transition
        if (clk_gen_cnt == 'h0)
        begin
            scl_oe      = 1'b1  ;
            scl_oe_en   = 1'b1  ;
        end

        // after half of clock low time, release sda line
        if (clk_gen_cnt == (period_cnt >> 2))
        begin
            sda_oe      = 1'b0  ;
            sda_oe_en   = 1'b1  ;
        end

        // generate low to high transition
        if (clk_gen_cnt == (period_cnt >> 1))
        begin
            clk_gen_cnt_clr = 1'b1 ;

            scl_oe      = 1'b0  ;
            scl_oe_en   = 1'b1  ;

            if (send_start)
                tx_rx_next_state = tx_rx_start ;
            else
                tx_rx_next_state = tx_rx_idle ;
        end
    end

    tx_rx_stop:
    begin
        clk_gen_cnt_en = 1'b1 ;

        // generate high to low transition
        if (clk_gen_cnt == 'h0)
        begin
            scl_oe      = 1'b1  ;
            scl_oe_en   = 1'b1  ;
        end

        // after half of clock low time, drive sda line low
        if (clk_gen_cnt == (period_cnt >> 2))
        begin
            sda_oe      = 1'b1  ;
            sda_oe_en   = 1'b1  ;
        end

        // generate low to high transition
        if (clk_gen_cnt == (period_cnt >> 1))
        begin
            scl_oe      = 1'b0  ;
            scl_oe_en   = 1'b1  ;
        end

        // after full clock period, release the sda line
        if (clk_gen_cnt == period_cnt)
        begin
            sda_oe      = 1'b0  ;
            sda_oe_en   = 1'b1  ;
            stop_sent   = 1'b1  ;

            tx_rx_next_state = tx_rx_idle ;
        end
    end

    endcase
end

reg [rw_seq_state_width - 1:0]  rw_seq_state    ;

reg doing_read      , 
    doing_write     , 
    doing_seq_read  ,
    adr_set         ;

reg [ 3: 0] bits_transfered ;

always@(posedge clk_i or posedge reset_i)
begin
    if (reset_i)
    begin
        rw_seq_state    <= rw_seq_idle  ;
        adr_set         <= 1'b0         ;
        doing_read      <= 1'b0         ;
        doing_write     <= 1'b0         ;
        doing_seq_read  <= 1'b0         ;
        dat_o           <= 'h0          ;
        tx_shift_reg    <= 'h0          ;
        send_start      <= 'h0          ;
        send_stop       <= 'h0          ;
        send_bit        <= 'h0          ;
        send_nack       <= 'h0          ;
        rec_ack         <= 'h0          ;
        no_ack_o        <= 'h0          ;
        bits_transfered <= 'h0          ;
        write_done_o    <= 'h0          ;
        dat_rdy_o       <= 'h0          ;
        send_ack        <= 'h0          ;
        rec_bit         <= 'h0          ;
    end
    else
    begin

        case (rw_seq_state)
            
        rw_seq_idle:
        begin
            tx_shift_reg <= {4'b1010, adr_i[10: 8], 1'b0} ;
            adr_set      <= 1'b0                          ;

            if ( tx_rx_sm_idle & ~(doing_write | doing_read | doing_seq_read) )
            begin
                if (do_write_i | do_rnd_read_i | do_seq_read_i)
                begin
                    rw_seq_state <= rw_seq_tx_ctrl  ;
                    send_start   <= 1'b1            ;
                end

                if (do_write_i)
                    doing_write     <= 1'b1 ;
                else if (do_rnd_read_i)
                    doing_read      <= 1'b1 ;
                else if (do_seq_read_i)
                    doing_seq_read  <= 1'b1 ;
            end
            else
            begin
                doing_write     <= 1'b0 ;
                doing_read      <= 1'b0 ;
                doing_seq_read  <= 1'b0 ;
            end
        end
    
        rw_seq_tx_ctrl:
        begin
            if (send_start)
            begin
                bits_transfered <= 'h0 ;

                if (start_sent)
                begin
                    send_start <= 1'b0 ;
                    send_bit   <= 1'b1 ;
                end
            end
            else if (send_bit)
            begin
                if (bit_sent)
                begin
                    bits_transfered <= bits_transfered + 1'b1 ;
                    tx_shift_reg <= {tx_shift_reg[6:0], tx_shift_reg[0]} ;
                end

                if (bits_transfered == 'h8)
                begin
                    send_bit <= 1'b0 ;
                    rec_ack  <= 1'b1 ;
                end
            end
            else if (rec_ack)
            begin
                bits_transfered <= 'h0 ;

                if (ack_rec | nack_rec)
                    rec_ack <= 1'b0 ;

                if (ack_rec)
                begin
                    if (doing_write | ~adr_set)
                    begin
                        rw_seq_state    <= rw_seq_tx_adr    ;
                        tx_shift_reg    <= adr_i[ 7: 0]     ;
                        send_bit        <= 1'b1             ;
                    end
                    else
                    begin
                        rw_seq_state    <= rw_seq_rx_byte   ;
                        rec_bit         <= 1'b1             ;
                    end
                end
                else if (nack_rec)
                begin
                    no_ack_o    <= 1'b1 ;
                    send_stop   <= 1'b1 ;
                end
            end
            else if (send_stop)
            begin
                no_ack_o <= 1'b0 ;

                if (stop_sent)
                begin
                    send_stop       <= 1'b0         ;
                    rw_seq_state    <= rw_seq_idle  ;
                end
            end
        end
    
        rw_seq_tx_adr:
        begin
            if (send_bit)
            begin
                if (bit_sent)
                begin
                    bits_transfered <= bits_transfered + 1'b1 ;
                    tx_shift_reg <= {tx_shift_reg[6:0], tx_shift_reg[0]} ;
                end

                if (bits_transfered == 'h8)
                begin
                    send_bit <= 1'b0 ;
                    rec_ack  <= 1'b1 ;
                end
            end
            else if (rec_ack)
            begin
                bits_transfered <= 'h0 ;

                if (ack_rec | nack_rec)
                    rec_ack <= 1'b0 ;

                if (ack_rec)
                begin

                    adr_set <= 1'b1 ;

                    if (doing_write)
                    begin
                        send_bit        <= 1'b1             ;
                        rw_seq_state    <= rw_seq_tx_byte   ;
                        tx_shift_reg    <= dat_i            ;
                    end
                    else if (doing_read | doing_seq_read)
                    begin
                        send_start      <= 1'b1             ;
                        rw_seq_state    <= rw_seq_tx_ctrl   ;
                        tx_shift_reg    <= 8'b10100001      ;
                    end
                end
                else if (nack_rec)
                begin
                    no_ack_o    <= 1'b1 ;
                    send_stop   <= 1'b1 ;
                end
            end
            else if (send_stop)
            begin
                no_ack_o    <= 1'b0 ;

                if (stop_sent)
                begin
                    send_stop       <= 1'b0         ;
                    rw_seq_state    <= rw_seq_idle  ;
                end
            end
        end
    
        rw_seq_tx_byte:
        begin
            if (send_bit)
            begin
                if (bit_sent)
                begin
                    bits_transfered <= bits_transfered + 1'b1 ;
                    tx_shift_reg <= {tx_shift_reg[6:0], tx_shift_reg[0]} ;
                end

                if (bits_transfered == 'h8)
                begin
                    send_bit <= 1'b0 ;
                    rec_ack  <= 1'b1 ;
                end
            end
            else if (rec_ack)
            begin
                bits_transfered <= 'h0 ;

                if (ack_rec | nack_rec)
                begin
                    rec_ack   <= 1'b0   ;
                    send_stop <= 1'b1   ;
                end

                if (nack_rec)
                    no_ack_o <= 1'b1 ;

                if (ack_rec)
                    write_done_o <= 1'b1 ;
            end
            else if (send_stop)
            begin
                no_ack_o        <= 1'b0 ;
                write_done_o    <= 1'b0 ;

                if (stop_sent)
                begin
                    send_stop       <= 1'b0         ;
                    rw_seq_state    <= rw_seq_idle  ;
                end
            end
        end
    
        rw_seq_rx_byte:
        begin
            if (rec_bit)
            begin
                if (bit_rec)
                begin
                    bits_transfered <= bits_transfered + 1'b1   ;
                    dat_o           <= {dat_o[6:0], sda_i_reg}  ;
                end

                if (bits_transfered == 'h8)
                begin
                    rec_bit   <= 1'b0 ;
                    dat_rdy_o <= 1'b1 ;
                    if (doing_read)
                        send_nack <= 1'b1 ;
                    else
                        send_ack  <= 1'b1 ;
                end
            end
            else if (send_nack)
            begin
                dat_rdy_o       <= 1'b0 ;
                bits_transfered <= 'h0  ;

                if (nack_sent)
                begin
                    send_stop <= 1'b1 ;
                    send_nack <= 1'b0 ;
                end
            end
            else if (send_ack)
            begin
                dat_rdy_o       <= 1'b0 ;
                bits_transfered <= 'h0  ;

                if (~do_seq_read_i)
                begin
                    send_ack    <= 1'b0 ;
                    send_nack   <= 1'b1 ;
                end
                else if (ack_sent)
                begin
                    send_ack <= 1'b0 ;
                    rec_bit  <= 1'b1 ;
                end
            end
            else if (send_stop)
            begin
                if (stop_sent)  
                begin
                    send_stop       <= 1'b0         ;
                    rw_seq_state    <= rw_seq_idle  ;
                end
            end
        end
        endcase
    end
end

endmodule // pci_spoci_ctrl