Get the complete sources after cloning github repository from \ac_inout_psu\source\system_control\system_components\ethernet\mdio_driver\
Gigabit Ethernet is probably the most convenient way to communicate with an embedded system. The link is full duplex, extremely robust even through ridiculously long cables and the cable is even galvanically isolated from both ends. The ethernet port is available on all PC hardware and the operating system takes care of the heavy lifting, like protocols and error detection. Overall the gigabit ethernet is fantastic and should be utilized whenever communication is needed as it allows access to gigabit connection available with minimal to no code interface from the user. The ethernet standard can be freely downloaded from IEEE through the IEEE get program https://ieeexplore.ieee.org/document/8457469
The ethernet communication module is divided into physical layer IC control through management data input output (MDIO) and the physical data connection to the chip with the dual data rate reduced gigabit media independent interface (RGMII) connection. RGMII bits bits are handled by the media access controller or commonly MAC laying inside the FPGA. MAC is the lowest level transmission protocol handler responsible for the received or transmitted ethernet frame with the RGMII.
The ethernet communication is developed using the same idea of high level interface in order to make a gigabit ethernet communication that is easy to use and set up. Although 10/100MBit connections are also available with the hardware, for the sake of minimal complexity only the gigabit connection is developed for now.
Ethernet basics
The major compoments of ethernet communication hardware are shown in Figure 1. The cable is connected with the basic ethernet jack to ethernet magnetics, which galvanically isolates the cable from rest of the electronics. The magnetics are driven with a dedicated driver called phy and the fpga is supplying data to the phy.
Figure 1 : Ethernet main components, 1) the ethernet cable and 2) RJ45/8p8c connector, which is connected to 3) the ethernet magnetics. Magnetics are driven by 4) phy chip which acts as a physical interface between the physical ethernet connection and the 5) FPGA, which is connected to the phy using a reduced gigabit media independent interface(RGMII).
The phy is where magic of ethernet hardware lies as it is responsible for interfacing the digital data to the analog voltages driven into the ethernet cable. These chips cost practically nothing and are engineered to sheer perfection with advanced features like error logging, cable diagnostics and the signal to noise ratio that can all be read from a dedicated register through the management interface. For the shown power supply control board a PEF7071 phy chip is chosen as it is the same that is present in the cyclone 10lp evaluation kit and it even has integrated converter for the phy core voltage generation. The phy supports management data input/output(MDIO) up to 25MHz clock speeds.
Management Data Input/Output (MDIO) control
The first thing that is designed is the MDIO connection. The MDIO is actually optional, but it allows a lot of insight into the state of the communication, most importantly it allows visibility to the presence of connected link partner as well as provides access to debug features.
The MDIO is bidirectional two wire communication where the FPGA acts as the master device. When data is written to the phy, FPGA as the master initiates communication by first transmitting a communication frame preamble, then a command word for read/write command, then specifies the phy address followed by a register address and then transmits or receives 16 bits of data. A write frame followed by a read frame communication waveform is shown in Figure 2.
The write frame is started with write command, which is succession of bits “110101”. This is followed by phy address x”00″, then register address x”0e” and lastly 16 bits of data. The preable, phy and register addresses are 5 bit long and there is an additional delimiter “10” between data and register address, thus the total MDIO communication frame is 33 bits.
When data is read from the phy, the read command “110110” is sent, then phy address followed by the register address that is read. FPGA IO goes to high Z state after the register address is sent and the data bus is then driven by the phy. The transition is visible in the scope capture as the phy is driving the line with higher drive strength thus the data goes to lower voltage than with the FPGA io. The mdio data line is idling 3 stated when frame is not being transmitted.
Figure 2. Oscilloscope capture of the MDIO waveforms for write frame flollowed by a read frame. The slow rise at 1.25µs is due 3 stated bus at the end of write frame. The register read through the MDIO is all zeros, thus when the phy drives the bus, only low data is seen.
VHDL declaration of the MDIO interface
The MDIO module interface is found in the mdio_driver_pkg.vhd. The module interface allows access to the MDIO through interface functions for fetching the MDIO data, triggering reads and writes and functions for read_is_ready and write_is_ready. The module is initialized in the application code with the init_mdio_driver procedure.
component mdio_driver is
port (
mdio_driver_clocks : in mdio_driver_clock_group;
mdio_driver_FPGA_out : out mdio_driver_FPGA_output_group;
mdio_driver_FPGA_inout : inout mdio_driver_FPGA_three_state_record;
mdio_driver_data_in : in mdio_driver_data_input_group;
mdio_driver_data_out : out mdio_driver_data_output_group
);
end component mdio_driver;
----------------------------------------------------------------
procedure init_mdio_driver ( signal mdio_input : out mdio_driver_data_input_group);
----------------------------------------------------------------
function get_data_from_mdio ( mdio_output : in mdio_driver_data_output_group)
return std_logic_vector;
----------------------------------------------------------------
function mdio_data_write_is_ready ( mdio_output : mdio_driver_data_output_group)
return boolean;
----------------------------------------------------------------
function mdio_data_read_is_ready ( mdio_output : mdio_driver_data_output_group)
return boolean;
----------------------------------------------------------------
procedure read_data_from_mdio (
signal mdio_input : out mdio_driver_data_input_group;
phy_address : std_logic_vector(7 downto 0);
phy_register_address : std_logic_vector(7 downto 0));
----------------------------------------------------------------
procedure write_data_to_mdio (
signal mdio_input : out mdio_driver_data_input_group;
phy_address : in std_logic_vector(7 downto 0);
register_address : in std_logic_vector(7 downto 0);
data_to_mdio : in std_logic_vector(15 downto 0));
MDIO driver module implementation
The VHDL source for the MDIO driver has three main procedures. The “generate_mdio_io_waveforms” creates the MDIO clock and data waveforms and write/read_data_with_mdio is triggered with the application code through the MDIO module interface. The three state driver is in its own own module which is instantiated in the MDIO.
architecture rtl of mdio_driver is
alias core_clock is mdio_driver_clocks.clock;
signal mdio_transmit_control : mdio_transmit_control_group := mdio_transmit_control_init;
signal mdio_three_state_io_driver_data_in : mdio_three_state_io_driver_data_input_group;
signal mdio_three_state_io_driver_data_out : mdio_three_state_io_driver_data_output_group;
------------------------------------------------------------------------
begin
------------------------------------------------------------------------
mdio_driver_FPGA_out <= ( MDIO_clock => mdio_transmit_control.mdio_clock);
mdio_driver_data_out <= (
mdio_write_is_ready => mdio_transmit_control.mdio_write_is_ready,
mdio_read_is_ready => mdio_transmit_control.mdio_read_is_ready,
data_from_mdio => mdio_transmit_control.mdio_data_receive_register,
mdio_read_when_1 => mdio_transmit_control.MDIO_io_direction_is_out_when_1
);
------------------------------------------------------------------------
mdio_io_driver : process(core_clock)
begin
if rising_edge(core_clock) then
generate_mdio_io_waveforms(mdio_transmit_control, mdio_three_state_io_driver_data_out);
write_data_with_mdio(mdio_driver_data_in, mdio_transmit_control);
read_data_with_mdio(mdio_driver_data_in, mdio_transmit_control);
end if; --rising_edge
end process mdio_io_driver;
------------------------------------------------------------------------
mdio_three_state_io_driver_data_in <= (io_output_data => mdio_transmit_control.mdio_transmit_register(mdio_transmit_control.mdio_transmit_register'left),
direction_is_out_when_1 => mdio_transmit_control.MDIO_io_direction_is_out_when_1);
u_mdio_three_state_io_driver : mdio_three_state_io_driver
port map(
mdio_driver_FPGA_inout.mdio_three_state_io_driver_FPGA_inout,
mdio_three_state_io_driver_data_in,
mdio_three_state_io_driver_data_out);
------------------------------------------------------------------------
end rtl;
The MDIO transmit_control is a signal generated from a record type which has all of the signals required by the MDIO operation.
type mdio_transmit_control_group is record
mdio_clock : std_logic;
MDIO_io_direction_is_out_when_1 : std_logic;
mdio_clock_counter : natural range 0 to 15;
mdio_transmit_register : std_logic_vector(33 downto 0);
mdio_write_clock : natural range 0 to 511;
mdio_write_is_ready : boolean;
mdio_data_write_is_pending : boolean;
mdio_data_receive_register : std_logic_vector(15 downto 0);
mdio_read_clock : natural range 0 to 511;
mdio_read_is_ready : boolean;
mdio_data_read_is_pending : boolean;
end record;
The MDIO clock is generated using a 0 to 4 mdio_clock_counter with the clock IO being driven to low for 2 clock cycles and high for 3 clocks. Datasheet specifies minimum low/high times at 10ns and a maximum clock speed of 25MHz. Thus with the 5 step division, the MDIO code can be run at most 125MHz clock.
For the data shifting the MDIO uses shift register, which when triggered shifts the data in the register one bit to the left. Thus when the register is shifted, the first bit becomes the second, second moves to third place, third to fourth and so on. The left most bit position is transmitted out to the 3 state driver at every falling edge of the MDIO clock. The MDIO clock counter is left free running and the data is shifted continuously with eery falling edge of the MDIO clock.
The shift register is loaded with the read/write_data_with_mdio procedure
--------------------------------------------------
procedure load_data_to_mdio_transmit_shift_register
(
signal mdio_control : out mdio_transmit_control_group;
data : std_logic_vector
) is
begin
mdio_control.mdio_transmit_register(mdio_control.mdio_transmit_register'left downto mdio_control.mdio_transmit_register'left-data'high) <= data;
end load_data_to_mdio_transmit_shift_register;
--------------------------------------------------
procedure write_data_with_mdio
(
mdio_input : in mdio_driver_data_input_group;
signal mdio_control : inout mdio_transmit_control_group
) is
begin
if mdio_input.mdio_data_write_is_requested then
mdio_control.mdio_data_write_is_pending <= true;
end if;
if (mdio_input.mdio_data_write_is_requested or mdio_control.mdio_data_write_is_pending) and mdio_control.mdio_clock_counter = 0 then
mdio_control.mdio_data_write_is_pending <= false;
load_data_to_mdio_transmit_shift_register(mdio_control ,
MDIO_write_command &
mdio_input.phy_address(4 downto 0) &
mdio_input.phy_register_address(4 downto 0) &
mdio_input.data_to_mdio(15 downto 0) &
MDIO_write_data_delimiter);
mdio_control.mdio_write_clock <= mdio_transmit_counter_high;
mdio_control.MDIO_io_direction_is_out_when_1 <= '1';
end if;
end write_data_with_mdio;
The pending signal is used to load the shift register at the correct counter value to time the shift register loading to the falling clock edge of the MDIO clock.
The MDIO ready signals are triggered with separate decrementing read/write counters at counter value 1.
Test with FPGA
From the phy side MDIO is a standardized interface consisting of 32 registers which allow for reading and writing various configuration and status registers of the ethernet connection. The most interesting ones are the control register which allows forcing a gigabit connection, and status register which is used for checking whether the communication link is up.
The MDIO is tested with hardware setup which reads all of the MDIO registers at 600ms interval and transmits the data from mdio through the previously designed UART. The test code is modified such that the bandpass filter data is transmitted if the received uart data is anything other than integers 10 to 15. The test code triggers at 100kHz. In the 100kHz time step, a 16 bit counter is incremented and for counter values 0 to 31 the corresponding mdio registers read and transmitted through uart and printed to console.
--------------------------------------------------
test_with_uart : process(clock)
--------------------------------------------------
function get_square_wave_from_counter
(
counter_value : integer
)
return int18
is
begin
if counter_value > 32767 then
return 55e3;
else
return 15e3;
end if;
end get_square_wave_from_counter;
--------------------------------------------------
variable register_counter : natural range 0 to 31 := 0;
begin
if rising_edge(clock) then
create_bandpass_filter(bandpass_filter);
init_mdio_driver(mdio_driver_data_in);
idle_adc(spi_sar_adc_data_in);
init_uart(uart_data_in);
receive_data_from_uart(uart_data_out, uart_rx_data);
system_components_FPGA_out.test_ad_mux <= integer_to_std(number_to_be_converted => uart_rx_data, bits_in_word => 3);
uart_transmit_counter <= uart_transmit_counter - 1;
if uart_transmit_counter = 0 then
uart_transmit_counter <= counter_at_100khz;
start_ad_conversion(spi_sar_adc_data_in);
end if;
if ad_conversion_is_ready(spi_sar_adc_data_out) then
CASE uart_rx_data is
WHEN 10 => transmit_16_bit_word_with_uart(uart_data_in, get_filter_output(bandpass_filter.low_pass_filter) );
WHEN 11 => transmit_16_bit_word_with_uart(uart_data_in, (bandpass_filter.low_pass_filter.filter_input - get_filter_output(bandpass_filter.low_pass_filter))/2+32768);
WHEN 12 => transmit_16_bit_word_with_uart(uart_data_in, get_filter_output(bandpass_filter)/2+32768);
WHEN 13 => transmit_16_bit_word_with_uart(uart_data_in, bandpass_filter.low_pass_filter.filter_input - get_filter_output(bandpass_filter));
WHEN 14 => transmit_16_bit_word_with_uart(uart_data_in, get_adc_data(spi_sar_adc_data_out));
WHEN 15 => transmit_16_bit_word_with_uart(uart_data_in, uart_rx_data);
WHEN others => -- get data from MDIO
register_counter := register_counter + 1;
read_data_from_mdio(mdio_driver_data_in, x"00", integer_to_std(register_counter, 8));
end CASE;
filter_data(bandpass_filter, get_square_wave_from_counter(test_counter));
test_counter <= test_counter + 1;
if test_counter = 65535 then
test_counter <= 0;
end if;
end if;
if mdio_data_read_is_ready(mdio_driver_data_out) then
mdio_registers(register_counter) <= get_data_from_mdio(mdio_driver_data_out);
if test_counter < 32 then
transmit_16_bit_word_with_uart(uart_data_in, get_data_from_mdio(mdio_driver_data_out));
end if;
end if;
end if; --rising_edge
end process test_with_uart;
------------------------------------------------------------------------
mdio_driver_clocks <= (clock => clock);
u_mdio_driver : mdio_driver
port map(
mdio_driver_clocks ,
system_components_FPGA_out.mdio_driver_FPGA_out ,
system_components_FPGA_inout.mdio_driver_FPGA_inout ,
mdio_driver_data_in ,
mdio_driver_data_out);
------------------------------------------------------------------------
The link status is shown in register x”0001″. The the auto-negotiation is bit 5, which is ‘1’ when auto negotiation is complete and link status is indicated with bit 2 which is ‘1’ when link is up. Upon connecting the ethernet cable, the corresponding bits change state from zero to one, thus the register changes from 0x7949 to 0x796D, indicating that the link is up and auto negotiation is complete.
Figure 3. PEF7071 MDIO registers with ethernet cable left unconnected.
Figure 4. PEF7071 MDIO registers with ethernet cable connected
Now that the connection can be established, in the ethernet vol2. ethernet frame receiver is designed which can catch the gigabit frame and print it to the console.
Test commenting