-- -------------------------------------------------------------------------- -- -- TRASHERNET - A Trashy Ethernet Stack for FPGAs -- -- -------------------------------------------------------------------------- -- -- trashernet_arp.vhd : Address Resolution Protocol -- Implements simple single-entry cache ARP. -- -------------------------------------------------------------------------- -- -- Author : Markus Koch -- Contributors : None -- License : Mozilla Public License (MPL) Version 2 -- -------------------------------------------------------------------------- -- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.trashernet_pkg.all; entity trashernet_arp is generic( SYSTICK_FREQ : integer ); port( -- Global clk : in std_logic; -- Global clock rst : in std_logic; -- Asynchronous reset systick : in std_logic; -- Global time reference for slow events (here: timeout) -- Configuration mac_config : in configuration_t; -- Trashernet MAC configuration ip_config : in ipv4_configuration_t; -- Trashernet IP configuration -- ARP application interface arp_out : out arp_out_t; -- ARP IF (out from ARP) arp_in : in arp_in_t; -- ARP IF (into ARP) -- Ethernet II application interface ethernet_ii_out : in ethernet_ii_out_t; -- Ethernet II IF (out from MAC) ethernet_ii_in : out ethernet_ii_in_t -- Ethernet II IF (into MAC) ); end entity trashernet_arp; architecture rtl of trashernet_arp is constant HTYPE : byte_vector := (x"00", x"01"); constant PTYPE : byte_vector := (x"08", x"00"); constant HLEN : byte := x"06"; constant PLEN : byte := x"04"; constant OPER_REQUEST : byte_vector := (x"00", x"01"); constant OPER_REPLY : byte_vector := (x"00", x"02"); constant ARP_TIMEOUT : time := 1 sec; signal arp_tx_reply_rq : std_logic; -- Request to transmit an ARP reply signal arp_tx_reply_tha : mac_addr_t; -- Target MAC to use in reply (SHA in received packet) signal arp_tx_reply_tpa : ip_addr_t; -- Target IP to use in reply (SPA in received packet) signal arp_tx_reply_ack : std_logic; -- ARP reply has been latched and is being sent signal arp_tx_request_rq : std_logic; -- Request to transmit an ARP request signal arp_tx_request_tpa : ip_addr_t; -- IP address that we want to know the MAC address of signal arp_tx_request_ack : std_logic; -- ARP request has been latched and is being sent signal arp_rx_reply_stb : std_logic; -- An ARP reply was received (strobe) signal arp_rx_sha : mac_addr_t; -- The MAC address of the reply sender, valid with arp_rx_reply signal arp_rx_spa : ip_addr_t; -- The IP address of the reply sender, valid with arp_rx_reply begin resolver : block type resolver_state_t is (IDLE, QUERY_MAC); signal state : resolver_state_t; signal query_mac_found : std_logic; signal query_timeout : std_logic := '0'; -- TODO: Implement signal replied_ip : ip_addr_t; signal replied_mac : mac_addr_t; begin arp_resolver_main : process(rst, clk) is begin if rst then state <= IDLE; replied_ip <= (others => x"00"); -- 0.0.0.0 arp_out.arp_ok_stb <= '0'; arp_out.arp_fail_stb <= '0'; arp_tx_request_rq <= '0'; elsif rising_edge(clk) then arp_out.arp_ok_stb <= '0'; arp_out.arp_fail_stb <= '0'; if arp_rx_reply_stb then if arp_tx_request_tpa = arp_rx_spa then -- If this reply is actually for our request replied_mac <= arp_rx_sha; replied_ip <= arp_rx_spa; end if; end if; case state is when IDLE => if arp_in.arp_query_stb then if query_mac_found then arp_out.arp_ok_stb <= '1'; else arp_tx_request_rq <= '1'; state <= QUERY_MAC; end if; end if; when QUERY_MAC => if arp_tx_request_ack then arp_tx_request_rq <= '0'; end if; if query_mac_found or query_timeout then arp_out.arp_ok_stb <= query_mac_found; arp_out.arp_fail_stb <= not query_mac_found; state <= IDLE; end if; end case; end if; end process arp_resolver_main; query_mac_found <= '1' when (arp_tx_request_tpa = replied_ip) else '0'; arp_out.arp_mac <= replied_mac; arp_tx_request_tpa <= arp_in.arp_ip; timeout_timer_inst : entity work.timer generic map( F_TICK => SYSTICK_FREQ, DURATION => ARP_TIMEOUT, AUTOSTART => false ) port map( clk => clk, rst => rst, tick => systick, start => arp_tx_request_rq, expired => query_timeout, expired_stb => open ); end block resolver; rx : block constant BYTECOUNT_HEAD : integer := 8; -- HTYPE -> OPER constant BYTECOUNT_ADDRESSES : integer := 20; -- SHA -> TPA signal sr : byte_vector(0 to BYTECOUNT_ADDRESSES - 1); constant SR_HEAD_OFFSET : integer := BYTECOUNT_ADDRESSES - BYTECOUNT_HEAD; alias sr_head_htype is sr(SR_HEAD_OFFSET + 0 to SR_HEAD_OFFSET + 1); alias sr_head_ptype is sr(SR_HEAD_OFFSET + 2 to SR_HEAD_OFFSET + 3); alias sr_head_hlen is sr(SR_HEAD_OFFSET + 4); alias sr_head_plen is sr(SR_HEAD_OFFSET + 5); alias sr_head_oper is sr(SR_HEAD_OFFSET + 6 to SR_HEAD_OFFSET + 7); constant SR_ADDRESSES_OFFSET : integer := -8; alias sr_addresses_sha is sr(8 + SR_ADDRESSES_OFFSET to 13 + SR_ADDRESSES_OFFSET); alias sr_addresses_spa is sr(14 + SR_ADDRESSES_OFFSET to 17 + SR_ADDRESSES_OFFSET); alias sr_addresses_tha is sr(18 + SR_ADDRESSES_OFFSET to 23 + SR_ADDRESSES_OFFSET); -- @suppress "Unused declaration": We don't need to check this as this is already done by the MAC alias sr_addresses_tpa is sr(24 + SR_ADDRESSES_OFFSET to 27 + SR_ADDRESSES_OFFSET); type state_t is (HEAD, ADDRESSES, WAITCRC, SENDREPLY, IGNORE); signal state : state_t; signal bytecount : integer range 0 to BYTECOUNT_ADDRESSES; signal shifted : std_logic; signal block_done : std_logic; signal header_ok : std_logic; signal we_are_asked : std_logic; signal is_request : std_logic; begin block_done <= '1' when shifted = '1' and bytecount = 0 else '0'; header_ok <= '1' when -- sr_head_htype = HTYPE and -- sr_head_ptype = PTYPE and -- sr_head_hlen = HLEN and sr_head_plen = PLEN -- else '0'; we_are_asked <= '1' when -- sr_addresses_tpa = ip_config.ip_address else '0'; rx_fsm : process(clk, rst) is begin if rst then state <= HEAD; shifted <= '0'; bytecount <= BYTECOUNT_HEAD; arp_rx_reply_stb <= '0'; elsif rising_edge(clk) then arp_rx_reply_stb <= '0'; if (ethernet_ii_out.rx_crc_ok or ethernet_ii_out.rx_crc_error) then state <= HEAD; -- Safe return from any state, may be overridden below bytecount <= BYTECOUNT_HEAD; end if; shifted <= ethernet_ii_out.rx_data_valid; if (ethernet_ii_out.rx_data_valid = '1') and (bytecount > 0) then sr <= sr(sr'low + 1 to sr'high) & ethernet_ii_out.rx_data; bytecount <= bytecount - 1; end if; case state is when HEAD => if block_done then if header_ok then bytecount <= BYTECOUNT_ADDRESSES; state <= ADDRESSES; is_request <= '1' when (sr_head_oper = OPER_REQUEST) else '0'; else state <= IGNORE; end if; end if; when ADDRESSES => if block_done then if (we_are_asked) then state <= WAITCRC; else state <= IGNORE; end if; end if; when WAITCRC => if ethernet_ii_out.rx_crc_ok then if is_request then state <= SENDREPLY; else arp_rx_reply_stb <= '1'; end if; end if; when SENDREPLY => if arp_tx_reply_ack then bytecount <= BYTECOUNT_HEAD; state <= HEAD; end if; when IGNORE => -- @suppress "Dead state 'IGNORE'": Outgoing transition provided outside of case statement (on RX ok/err) null; end case; end if; end process rx_fsm; arp_tx_reply_rq <= '1' when state = SENDREPLY else '0'; arp_tx_reply_tha <= sr_addresses_sha; arp_tx_reply_tpa <= sr_addresses_spa; arp_rx_sha <= sr_addresses_sha; arp_rx_spa <= sr_addresses_spa; end block rx; tx : block signal sr : byte_vector(0 to 27); signal arp_reply_preload : byte_vector(sr'range); signal arp_request_preload : byte_vector(sr'range); constant BYTECOUNT_MAX : integer := sr'length + 1; signal bytecount : integer range 0 to BYTECOUNT_MAX; type state_t is (IDLE, TRANSMIT); signal state : state_t; begin arp_reply_preload <= HTYPE & PTYPE & HLEN & PLEN & OPER_REPLY & -- Header mac_config.mac_address & ip_config.ip_address & -- Sender arp_tx_reply_tha & arp_tx_reply_tpa; -- Target arp_request_preload <= HTYPE & PTYPE & HLEN & PLEN & OPER_REQUEST & -- Header mac_config.mac_address & ip_config.ip_address & -- Sender mac_addr_t'(x"00", x"00", x"00", x"00", x"00", x"00") & arp_tx_request_tpa; -- Target tx_fsm : process(clk, rst) is begin if rst then arp_tx_reply_ack <= '0'; arp_tx_request_ack <= '0'; elsif rising_edge(clk) then arp_tx_reply_ack <= '0'; arp_tx_request_ack <= '0'; if (ethernet_ii_out.tx_data_ack = '1') and (bytecount > 0) then sr <= sr(sr'low + 1 to sr'high) & x"00"; bytecount <= bytecount - 1; end if; case state is when IDLE => if arp_tx_reply_rq then state <= TRANSMIT; arp_tx_reply_ack <= '1'; sr <= arp_reply_preload; ethernet_ii_in.tx_mac_address <= arp_tx_reply_tha; elsif arp_tx_request_rq then state <= TRANSMIT; arp_tx_request_ack <= '1'; sr <= arp_request_preload; ethernet_ii_in.tx_mac_address <= MAC_ADDR_BROADCAST; end if; bytecount <= BYTECOUNT_MAX; when TRANSMIT => if bytecount = 0 then state <= IDLE; end if; end case; end if; end process tx_fsm; ethernet_ii_in.tx_en <= '1' when state = TRANSMIT else '0'; ethernet_ii_in.tx_data <= sr(0); end block tx; end architecture rtl;