Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
axi4_stream_dp_bridge.vhd 6.99 KiB
-- --------------------------------------------------------------------------
-- Copyright 2023
-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- --------------------------------------------------------------------------

-- Author:
-- . Reinier van der Walle
-- Purpose:
-- . Bridge between AXI4 stream and DP stream interfaces. 
-- Description:
-- . This core consists of:
--   . Combinatorial translation of one interface to the other.
--   . SOP insertion for AXI4 -> DP as AXI4 does not have a SOP equivalent.
--   . Implementation for deriving dp.empty from axi4.tkeep (see details).
--   . Ready latency adapters. 
-- . Details:
--   . g_nof_bytes is used to indicate the number of bytes in the data field. Only 
--     used for deriving the DP empty field.
--   . g_use_empty: when true, it will derive the DP empty field from the AXI4 tkeep signal. 
--     tkeep is the AXI4 signal that indicates with a bit for each byte of the data whether 
--     it is considered a valid or null byte. '1' for a valid byte or '0' for a null byte.--     
--     The DP empty field is derived by counting the number of '0' bits in 
--     tkeep(g_nof_bytes DOWNTO 0). This means that it is only possible to derive the empty
--     field correctly when tkeep only has bits set to '0' at the end of the vector. For example:
--     . tkeep = "11111000" is valid and will translate to empty = 0011 (3 Decimal).
--     . tkeep = "11011101" is not valid and will result in a wrong empty signal of 0010 (2 Decimal).
--     . Note that AXI4 always uses a symbol width of 8 (= byte) and thus the only DP symbol width
--       used by the empty signal that is supported is also 8.
--   . g_axi4_rl is the ready latency of the axi4_in/out interfaces.
--   . g_dp_rl is the ready latency of the dp_in/out interfaces.
--   . g_active_low_rst should be set to TRUE when in_rst is active low. This is useful as an
--     AXI4 interface often comes with an active-low reset while DP comes with an active-high 
--     reset.
-- Remarks:
-- . AXI4 does not have a DP Xon or sync equivalent.

LIBRARY IEEE, common_lib, dp_lib, technology_lib;
USE IEEE.STD_LOGIC_1164.ALL;
USE common_lib.common_pkg.ALL;
USE dp_lib.dp_stream_pkg.ALL;
USE work.axi4_stream_pkg.ALL;

ENTITY axi4_stream_dp_bridge IS
  GENERIC (
    g_nof_bytes      : NATURAL := 64; -- Max = 64 bytes
    g_use_empty      : BOOLEAN := FALSE; -- When True the dp empty field is derived from axi4 tkeep.
    g_axi4_rl        : NATURAL := 0;
    g_dp_rl          : NATURAL := 1;
    g_active_low_rst : BOOLEAN := FALSE -- When True, in_rst is interpreted as active-low.
  );
  PORT (
    in_clk  : IN STD_LOGIC := '0';
    in_rst  : IN STD_LOGIC := is_true(g_active_low_rst); -- Default state is "not in reset".

    aresetn : OUT STD_LOGIC := '1'; -- AXI4 active-low reset
    dp_rst  : OUT STD_LOGIC := '0'; -- DP active-high reset
    axi4_in_sosi  : IN  t_axi4_sosi := c_axi4_sosi_rst;
    axi4_in_siso  : OUT t_axi4_siso := c_axi4_siso_rst;

    axi4_out_sosi : OUT t_axi4_sosi := c_axi4_sosi_rst;
    axi4_out_siso : IN  t_axi4_siso := c_axi4_siso_rst;

    dp_in_sosi    : IN  t_dp_sosi   := c_dp_sosi_rst;
    dp_in_siso    : OUT t_dp_siso   := c_dp_siso_rst;

    dp_out_sosi   : OUT t_dp_sosi   := c_dp_sosi_rst;
    dp_out_siso   : IN  t_dp_siso   := c_dp_siso_rst
  );
END axi4_stream_dp_bridge;

ARCHITECTURE str OF axi4_stream_dp_bridge IS
  SIGNAL i_rst : STD_LOGIC := '0'; -- Internal active high reset.

  SIGNAL axi4_from_dp_sosi : t_dp_sosi;
  SIGNAL axi4_from_dp_siso : t_dp_siso;
  SIGNAL dp_from_axi4_sosi : t_dp_sosi;
  SIGNAL dp_from_axi4_siso : t_dp_siso;

  TYPE t_reg IS RECORD
    r_eop             : STD_LOGIC;
    dp_from_axi4_sosi : t_dp_sosi;
  END RECORD;

  CONSTANT c_reg_init         : t_reg := ('1', c_dp_sosi_rst) ;

  -- Registers
  SIGNAL d_reg                : t_reg := c_reg_init;
  SIGNAL q_reg                : t_reg := c_reg_init;

BEGIN
  i_rst   <= NOT in_rst WHEN g_active_low_rst ELSE in_rst;
  aresetn <= NOT i_rst;
  dp_rst  <= i_rst;

  ----------------------------
  -- Translate DP to AXI4
  ----------------------------
  axi4_out_sosi <= func_axi4_stream_from_dp_sosi(axi4_from_dp_sosi);
  axi4_from_dp_siso <= func_axi4_stream_to_dp_siso(axi4_out_siso);

  -- Adapt Ready Latency
  u_dp_latency_adapter_dp_to_axi : ENTITY dp_lib.dp_latency_adapter
  GENERIC MAP (
    g_in_latency  => g_dp_rl,
    g_out_latency => g_axi4_rl 
  )
  PORT MAP (
    clk       => in_clk,
    rst       => i_rst,
  
    snk_in    => dp_in_sosi, 
    snk_out   => dp_in_siso, 
  
    src_out   => axi4_from_dp_sosi, 
    src_in    => axi4_from_dp_siso 
  );


  ----------------------------
  -- Translate AXI4 to DP
  ----------------------------
  q_reg <= d_reg WHEN rising_edge(in_clk);

  p_axi4_to_dp : PROCESS(i_rst, q_reg, axi4_in_sosi, dp_from_axi4_siso.ready)
    VARIABLE v : t_reg := c_reg_init;
  BEGIN
    v := q_reg;
    v.dp_from_axi4_sosi := func_axi4_stream_to_dp_sosi(axi4_in_sosi); -- Does not have sop or empty yet.

    -- Generate sop on first valid after eop.
    IF axi4_in_sosi.tvalid = '1' AND g_axi4_rl = 1 THEN -- axi4 ready latency = 1
      IF axi4_in_sosi.tlast = '1' THEN 
        v.r_eop := '1';
      ELSIF q_reg.r_eop = '1' THEN
        v.dp_from_axi4_sosi.sop := '1';
        v.r_eop := '0';
      ELSE
        v.dp_from_axi4_sosi.sop := '0';
      END IF;

    ELSIF axi4_in_sosi.tvalid = '1' AND g_axi4_rl = 0 THEN  -- axi4 ready latency = 0
      IF axi4_in_sosi.tlast = '1' AND dp_from_axi4_siso.ready = '1' THEN 
        v.r_eop := '1';
      ELSIF q_reg.r_eop = '1' AND dp_from_axi4_siso.ready = '1' THEN
        v.r_eop := '0';
      END IF;
      v.dp_from_axi4_sosi.sop := q_reg.r_eop;
    END IF;

    -- Derive Empty from tkeep
    IF g_use_empty THEN
      v.dp_from_axi4_sosi.empty := func_axi4_stream_tkeep_to_dp_empty(axi4_in_sosi.tkeep(g_nof_bytes-1 DOWNTO 0));
    END IF;

    -- Reset
    IF i_rst = '1' THEN
      v := c_reg_init;
    END IF;

    d_reg <= v;
  END PROCESS;  

  dp_from_axi4_sosi <= d_reg.dp_from_axi4_sosi;
  axi4_in_siso <= func_axi4_stream_from_dp_siso(dp_from_axi4_siso);

  -- Adapt Ready Latency
  u_dp_latency_adapter_axi_to_dp : ENTITY dp_lib.dp_latency_adapter
  GENERIC MAP (
    g_in_latency  => g_axi4_rl,
    g_out_latency => g_dp_rl 
  )
  PORT MAP (
    clk       => in_clk,
    rst       => i_rst,
  
    snk_in    => dp_from_axi4_sosi, 
    snk_out   => dp_from_axi4_siso, 
  
    src_out   => dp_out_sosi, 
    src_in    => dp_out_siso 
  );
    
END str;