// SPDX-License-Identifier: CERN-OHL-S-2.0
/*

Copyright (c) 2015-2025 FPGA Ninja, LLC

Authors:
- Alex Forencich

*/

`resetall
`timescale 1ns / 1ps
`default_nettype none

/*
 * MDIO master
 */
module taxi_mdio_master (
    input  wire logic        clk,
    input  wire logic        rst,

    /*
     * Host interface
     */
    taxi_axis_if.snk         s_axis_cmd,
    taxi_axis_if.src         m_axis_rd_data,

    /*
     * MDIO to PHY
     */
    output wire logic        mdc_o,
    input  wire logic        mdio_i,
    output wire logic        mdio_o,
    output wire logic        mdio_t,

    /*
     * Status
     */
    output wire logic        busy,

    /*
     * Configuration
     */
    input  wire logic [7:0]  prescale
);

localparam [1:0]
    STATE_IDLE = 2'd0,
    STATE_PREAMBLE = 2'd1,
    STATE_TRANSFER = 2'd2;

logic [1:0] state_reg = STATE_IDLE, state_next;

logic [7:0] count_reg = '0, count_next;
logic [5:0] bit_count_reg = '0, bit_count_next;
logic cycle_reg = 1'b0, cycle_next;

logic [31:0] data_reg = '0, data_next;

logic [1:0] op_reg = 2'b00, op_next;

logic s_axis_cmd_ready_reg = 1'b0, cmd_ready_next;

logic [15:0] m_axis_rd_data_reg = '0, m_axis_rd_data_next;
logic m_axis_rd_data_valid_reg = 1'b0, m_axis_rd_data_valid_next;

logic mdio_i_reg = 1'b1;

logic mdc_o_reg = 1'b0, mdc_o_next;
logic mdio_o_reg = 1'b0, mdio_o_next;
logic mdio_t_reg = 1'b1, mdio_t_next;

logic busy_reg = 1'b0;

assign s_axis_cmd.tready = s_axis_cmd_ready_reg;

assign m_axis_rd_data.tdata = m_axis_rd_data_reg;
assign m_axis_rd_data.tkeep = '1;
assign m_axis_rd_data.tstrb = m_axis_rd_data.tkeep;
assign m_axis_rd_data.tvalid = m_axis_rd_data_valid_reg;
assign m_axis_rd_data.tlast = 1'b1;
assign m_axis_rd_data.tid = '0;
assign m_axis_rd_data.tdest = '0;
assign m_axis_rd_data.tuser = '0;

assign mdc_o = mdc_o_reg;
assign mdio_o = mdio_o_reg;
assign mdio_t = mdio_t_reg;

assign busy = busy_reg;

wire [1:0]   cmd_st = s_axis_cmd.tdata[31:30];
wire [1:0]   cmd_op = s_axis_cmd.tdata[29:28];
wire [9:0]   cmd_addr = s_axis_cmd.tdata[27:18];
wire [15:0]  cmd_data = s_axis_cmd.tdata[15:0];

always_comb begin
    state_next = STATE_IDLE;

    count_next = count_reg;
    bit_count_next = bit_count_reg;
    cycle_next = cycle_reg;

    data_next = data_reg;

    op_next = op_reg;

    cmd_ready_next = 1'b0;

    m_axis_rd_data_next = m_axis_rd_data_reg;
    m_axis_rd_data_valid_next = m_axis_rd_data_valid_reg && !m_axis_rd_data.tready;

    mdc_o_next = mdc_o_reg;
    mdio_o_next = mdio_o_reg;
    mdio_t_next = mdio_t_reg;

    if (count_reg != 0) begin
        count_next = count_reg - 8'd1;
        state_next = state_reg;
    end else if (cycle_reg) begin
        cycle_next = 1'b0;
        mdc_o_next = 1'b1;
        count_next = prescale;
        state_next = state_reg;
    end else begin
        mdc_o_next = 1'b0;
        case (state_reg)
            STATE_IDLE: begin
                // idle - accept new command
                if (s_axis_cmd.tvalid) begin
                    cmd_ready_next = 1'b1;
                    data_next = {cmd_st, cmd_op, cmd_addr, 2'b10, cmd_data};
                    op_next = cmd_op;
                    mdio_t_next = 1'b0;
                    mdio_o_next = 1'b1;
                    bit_count_next = 6'd32;
                    cycle_next = 1'b1;
                    count_next = prescale;
                    state_next = STATE_PREAMBLE;
                end else begin
                    state_next = STATE_IDLE;
                end
            end
            STATE_PREAMBLE: begin
                cycle_next = 1'b1;
                count_next = prescale;
                if (bit_count_reg > 6'd1) begin
                    bit_count_next = bit_count_reg - 6'd1;
                    state_next = STATE_PREAMBLE;
                end else begin
                    bit_count_next = 6'd32;
                    {mdio_o_next, data_next} = {data_reg, mdio_i_reg};
                    state_next = STATE_TRANSFER;
                end
            end
            STATE_TRANSFER: begin
                cycle_next = 1'b1;
                count_next = prescale;
                if (op_reg[1] && bit_count_reg == 6'd19) begin
                    mdio_t_next = 1'b1;
                end
                if (bit_count_reg > 6'd1) begin
                    bit_count_next = bit_count_reg - 6'd1;
                    {mdio_o_next, data_next} = {data_reg, mdio_i_reg};
                    state_next = STATE_TRANSFER;
                end else begin
                    if (op_reg[1]) begin
                        m_axis_rd_data_next = data_reg[15:0];
                        m_axis_rd_data_valid_next = 1'b1;
                    end
                    mdio_t_next = 1'b1;
                    state_next = STATE_IDLE;
                end
            end
            default: begin
                state_next = STATE_IDLE;
            end
        endcase
    end
end

always_ff @(posedge clk) begin
    state_reg <= state_next;

    count_reg <= count_next;
    bit_count_reg <= bit_count_next;
    cycle_reg <= cycle_next;

    data_reg <= data_next;
    op_reg <= op_next;

    s_axis_cmd_ready_reg <= cmd_ready_next;

    m_axis_rd_data_reg <= m_axis_rd_data_next;
    m_axis_rd_data_valid_reg <= m_axis_rd_data_valid_next;

    mdio_i_reg <= mdio_i;

    mdc_o_reg <= mdc_o_next;
    mdio_o_reg <= mdio_o_next;
    mdio_t_reg <= mdio_t_next;

    busy_reg <= (state_next != STATE_IDLE || count_reg != 0 || cycle_reg || mdc_o);

    if (rst) begin
        state_reg <= STATE_IDLE;
        count_reg <= '0;
        bit_count_reg <= '0;
        cycle_reg <= 1'b0;
        s_axis_cmd_ready_reg <= 1'b0;
        m_axis_rd_data_valid_reg <= 1'b0;
        mdc_o_reg <= 1'b0;
        mdio_o_reg <= 1'b0;
        mdio_t_reg <= 1'b1;
        busy_reg <= 1'b0;
    end
end

endmodule

`resetall
