MDIO 管理接口是以太网 MAC 和 PHY 之间的接口,用于管理/配置以太网 PHY 芯片。本文主要介绍 MDIO 管理接口定义,以及 MDIO 控制器设计。
目录
1 MDIO 管理接口
2 MDIO 控制器设计
1 MDIO 管理接口
MDIO 管理接口是以太网 MAC 和 PHY 之间的接口,用于管理/配置以太网 PHY 芯片。MDIO 接口通过 MDC 和 MDIO 两根引脚访问 PHY 芯片的内部寄存器,详细可参考 IEEE 802.3u secton 22 中的定义。
序号 | 名称 | 方向 | 功能说明 |
1 | MDC | 单向,MAC -> PHY | MDIO 参考时钟 |
2 | MDIO | 双向,MAC <-> PHY | MDIO 数据 |
MDC 信号是由以太网 MAC 提供的参考时钟,MDIO 是数据输入/输出,与 MDC 信号同步运行的双向信号。在硬件设计上,MDIO 引脚需要一个 1.5k 欧姆的上拉电阻,以在空闲和周转期间保持 MDIO 高电平。
多个 PHY 芯片可以共享同一个 MDIO 接口。在交换机或路由器应用中,每个 PHY 芯片会分配一个唯一的地址,并且只能通过该唯一的 PHY 地址对其进行寻址。有关管理寄存器的详细信息,请参阅 PHY 对应芯片手册。
2 MDIO 控制器设计
MDIO 管理接口分读和写两种操作方式,读/写帧格式见下表。
MDIO 读/写时序按照先后顺序分别为:32 位前导码、帧头、操作符、PHY 地址、寄存器地址、TA 标志,最后是 16 位数据。
可以使用有限状态机设计 MDIO 控制器,设计代码如下:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity mdio_core is
port(
-- System level
sys_rst : in std_logic;
sys_clk : in std_logic;
-- MDIO control/data ports
mdio_req : in std_logic;
mdio_ack : out std_logic;
mdio_done : out std_logic;
phy_addr : in std_logic_vector(7 downto 0);
reg_addr : in std_logic_vector(7 downto 0);
reg_wdata : in std_logic_vector(15 downto 0);
reg_rdata : out std_logic_vector(15 downto 0);
reg_rw : in std_logic;
-- MDIO Interface
phy_mdc : out std_logic;
phy_mdio_id : in std_logic;
phy_mdio_od : out std_logic;
phy_mdio_oe : out std_logic
);
end entity;
architecture behav of mdio_core is
-- internal signal declarations
type state is(
idle,
preamble,
start,
op_read,
op_write,
write_addr,
turn_around,
read_data,
write_data
);
signal pstate : state := idle;
signal phy_mdio_id_r1 : std_logic;
signal phy_mdio_id_r2 : std_logic;
signal reg_rdwr_r : std_logic;
signal mdc_buf : std_logic_vector(7 downto 0);
signal buf_mdio_ack : std_logic;
signal pulse_cnt : std_logic_vector(5 downto 0);
signal step_cnt : std_logic_vector(6 downto 0);
signal addr_buf : std_logic_vector(9 downto 0);
signal reg_wdata_buf : std_logic_vector(15 downto 0);
signal reg_rdata_buf : std_logic_vector(15 downto 0);
---------------------------------------------------------
begin
---------------------------------------------------------
phy_mdc <= mdc_buf(mdc_buf'high);
process(sys_rst,sys_clk)
begin
if sys_rst = '1' then
pulse_cnt <= (0 => '1', others => '0');
elsif rising_edge(sys_clk) then
if pstate /= idle then
if pulse_cnt(5) = '1' then
pulse_cnt <= (0 => '1', others => '0');
else
pulse_cnt <= pulse_cnt + 1;
end if;
else
pulse_cnt <= (others => '0');
end if;
end if;
end process;
process(sys_rst,sys_clk)
begin
if sys_rst = '1' then
mdc_buf <= (others => '1');
elsif rising_edge(sys_clk) then
mdc_buf <= mdc_buf(mdc_buf'high-1 downto 0) & pulse_cnt(4);
end if;
end process;
process(sys_rst,sys_clk)
begin
if sys_rst = '1' then
phy_mdio_id_r1 <= '1';
phy_mdio_id_r2 <= '1';
elsif rising_edge(sys_clk) then
phy_mdio_id_r1 <= phy_mdio_id;
phy_mdio_id_r2 <= phy_mdio_id_r1;
end if;
end process;
--================================================================
mdio_ack <= buf_mdio_ack;
reg_rdata <= reg_rdata_buf;
process(sys_rst,sys_clk)
begin
if sys_rst = '1' then
step_cnt <= (0 => '1', others => '0');
elsif rising_edge(sys_clk) then
case(pstate) is
when idle =>
step_cnt <= (0 => '1', others => '0');
when preamble =>
if mdc_buf(1 downto 0) = "10" then
if step_cnt(5) = '1' then
step_cnt <= (0 => '1', others => '0');
else
step_cnt <= step_cnt + 1;
end if;
end if;
when start | op_read | op_write | turn_around =>
if mdc_buf(1 downto 0) = "10" then
if step_cnt(1) = '1' then
step_cnt <= (0 => '1', others => '0');
else
step_cnt <= step_cnt + 1;
end if;
end if;
when write_addr =>
if mdc_buf(1 downto 0) = "10" then
if step_cnt(3) = '1' and step_cnt(1) = '1' then
step_cnt <= (0 => '1', others => '0');
else
step_cnt <= step_cnt + 1;
end if;
end if;
when write_data | read_data =>
if mdc_buf(1 downto 0) = "10" then
if step_cnt(4) = '1' then
step_cnt <= (0 => '1', others => '0');
else
step_cnt <= step_cnt + 1;
end if;
end if;
when others => NULL;
end case;
end if;
end process;
process(sys_rst,sys_clk)
begin
if sys_rst = '1' then
pstate <= idle;
reg_rdwr_r <= '0';
buf_mdio_ack <= '0';
mdio_done <= '0';
elsif rising_edge(sys_clk) then
case(pstate) is
when idle =>
mdio_done <= '0';
if mdio_req = '1' and buf_mdio_ack = '1' then
buf_mdio_ack <= '0';
reg_rdwr_r <= reg_rw;
pstate <= preamble;
elsif mdio_req = '1' and buf_mdio_ack = '0' then
buf_mdio_ack <= '1';
pstate <= idle;
end if;
when preamble =>
if mdc_buf(1 downto 0) = "10" and step_cnt(5) = '1' then
pstate <= start;
else
pstate <= preamble;
end if;
when start =>
if mdc_buf(1 downto 0) = "10" then
if step_cnt(1) = '1' and reg_rdwr_r = '1' then
pstate <= op_write;
elsif step_cnt(1) = '1' and reg_rdwr_r = '0' then
pstate <= op_read;
end if;
end if;
when op_read | op_write =>
-- Send Read or Write OP Code
if mdc_buf(1 downto 0) = "10" and step_cnt(1) = '1' then
pstate <= write_addr;
end if;
when write_addr =>
-- Send PHY Address and REG Address
if mdc_buf(1 downto 0) = "10" then
if step_cnt(3) = '1' and step_cnt(1) = '1' then
pstate <= turn_around;
end if;
else
pstate <= write_addr;
end if;
when turn_around =>
if mdc_buf(1 downto 0) = "10" then
if step_cnt(1) = '1' and reg_rdwr_r = '1' then
pstate <= write_data;
elsif step_cnt(1) = '1' and reg_rdwr_r = '0' then
pstate <= read_data;
end if;
else
pstate <= turn_around;
end if;
when write_data | read_data =>
if mdc_buf(1 downto 0) = "10" and step_cnt(4) = '1' then
pstate <= idle;
mdio_done <= '1';
end if;
when others => NULL;
end case;
end if;
end process;
process(sys_rst,sys_clk)
begin
if sys_rst = '1' then
addr_buf <= (others => '0');
reg_wdata_buf <= (others => '0');
reg_rdata_buf <= (others => '0');
elsif rising_edge(sys_clk) then
case(pstate) is
when idle =>
if mdio_req = '1' and buf_mdio_ack = '1' then
reg_wdata_buf <= reg_wdata;
addr_buf <= phy_addr(4 downto 0) & reg_addr(4 downto 0);
end if;
when preamble =>
reg_rdata_buf <= (others => '1');
when write_addr =>
if mdc_buf(1 downto 0) = "10" then
addr_buf <= addr_buf(8 downto 0) & '0';
end if;
when write_data =>
if mdc_buf(1 downto 0) = "10" then
reg_wdata_buf <= reg_wdata_buf(14 downto 0) & '0';
end if;
when read_data =>
if mdc_buf(1 downto 0)= "01" then
reg_rdata_buf <= reg_rdata_buf(14 downto 0) & phy_mdio_id_r2;
end if;
when others => NULL;
end case;
end if;
end process;
mdio_od_pro: process(sys_rst,sys_clk)
begin
if sys_rst = '1' then
phy_mdio_od <= '1';
elsif rising_edge(sys_clk) then
case(pstate) is
when idle | read_data | preamble =>
phy_mdio_od <= '1';
when start | op_write =>
case(step_cnt(1 downto 0)) is
when "01" => phy_mdio_od <= '0';
when "10" => phy_mdio_od <= '1';
when others => phy_mdio_od <= '1';
end case;
when op_read =>
case(step_cnt(1 downto 0)) is
when "01" => phy_mdio_od <= '1';
when "10" => phy_mdio_od <= '0';
when others => phy_mdio_od <= '1';
end case;
when write_addr =>
phy_mdio_od <= addr_buf(9);
when turn_around =>
if reg_rdwr_r = '1' then
-- Turn around for write
case(step_cnt(1 downto 0)) is
when "01" => phy_mdio_od <= '1';
when "10" => phy_mdio_od <= '0';
when others => phy_mdio_od <= '1';
end case;
else
-- Turn around for read
phy_mdio_od <= '1';
end if;
when write_data =>
phy_mdio_od <= reg_wdata_buf(15);
when others =>
phy_mdio_od <= '1';
end case;
end if;
end process;
mdio_oe_pro: process(sys_rst,sys_clk)
begin
if sys_rst = '1' then
phy_mdio_oe <= '0';
elsif rising_edge(sys_clk) then
case(pstate) is
when idle | read_data =>
phy_mdio_oe <= '0';
when preamble | start | op_write | op_read | write_addr | write_data =>
phy_mdio_oe <= '1';
when turn_around =>
if reg_rdwr_r = '1' then
phy_mdio_oe <= '1';
else
phy_mdio_oe <= '0';
end if;
when others =>
phy_mdio_oe <= '0';
end case;
end if;
end process;
end architecture;