diff --git a/libraries/base/axi4/hdllib.cfg b/libraries/base/axi4/hdllib.cfg new file mode 100644 index 0000000000000000000000000000000000000000..3174cab5d7bd9c355e69cd1a246fa88ecbae21e9 --- /dev/null +++ b/libraries/base/axi4/hdllib.cfg @@ -0,0 +1,24 @@ +hdl_lib_name = axi4 +hdl_library_clause_name = axi4_lib +hdl_lib_uses_synth = common dp +hdl_lib_uses_sim = +hdl_lib_technology = + +synth_files = + src/vhdl/axi4_stream_pkg.vhd + src/vhdl/axi4_stream_dp_bridge.vhd + +test_bench_files = + tb/vhdl/tb_axi4_stream_dp_bridge.vhd + tb/vhdl/tb_tb_axi4_stream_dp_bridge.vhd + +regression_test_vhdl = + tb/vhdl/tb_tb_axi4_stream_dp_bridge.vhd + +[modelsim_project_file] + + +[quartus_project_file] + + +[vivado_project_file] diff --git a/libraries/base/axi4/src/vhdl/axi4_stream_dp_bridge.vhd b/libraries/base/axi4/src/vhdl/axi4_stream_dp_bridge.vhd new file mode 100644 index 0000000000000000000000000000000000000000..eac0affdf5d8716799a2c82b06a95dce6fc8401e --- /dev/null +++ b/libraries/base/axi4/src/vhdl/axi4_stream_dp_bridge.vhd @@ -0,0 +1,198 @@ +-- -------------------------------------------------------------------------- +-- 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; + diff --git a/libraries/base/axi4/src/vhdl/axi4_stream_pkg.vhd b/libraries/base/axi4/src/vhdl/axi4_stream_pkg.vhd new file mode 100644 index 0000000000000000000000000000000000000000..ccb98c6b67b875e5103d8ea6e883fd98baf36f77 --- /dev/null +++ b/libraries/base/axi4/src/vhdl/axi4_stream_pkg.vhd @@ -0,0 +1,781 @@ +-- -------------------------------------------------------------------------- +-- 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 (edits only, see Original) +-- Purpose: General AXI stream record defintion +-- Original: +-- https://git.astron.nl/desp/gemini/-/blob/master/libraries/base/axi4/src/vhdl/axi4_stream_pkg.vhd +-- Remarks: +-- * Choose smallest maximum SOSI slv lengths that fit all use cases, because unconstrained record +-- fields slv is not allowed. +-- * The large SOSI data field width of 256b has some disadvantages: +-- . about 10% extra simulation time and PC memory usage compared to 72b +-- (measured using tb_unb_tse_board) +-- . a 256b number has 64 hex digits in the Wave window which is awkward because of the leading +-- zeros when typically +-- only 32b are used, fortunately integer representation still works OK (except 0 which is shown +-- as blank). +-- However the alternatives are not attractive, because they affect the implementation of the +-- streaming +-- components that use the SOSI record. Alternatives are e.g.: +-- . define an extra long SOSI data field ldata[255:0] in addition to the existing data[71:0] +-- field +-- . use the array of SOSI records to contain wider data, all with the same SOSI control field +-- values +-- . define another similar SOSI record with data[255:0]. +-- Therefore define data width as 256b, because the disadvantages are acceptable and the benefit +-- is great, because all +-- streaming components can remain as they are. +-- * Added sync and bsn to SOSI to have timestamp information with the data +-- * Added re and im to SOSI to support complex data for DSP +-------------------------------------------------------------------------------- + +LIBRARY IEEE, common_lib, dp_lib; +USE IEEE.STD_LOGIC_1164.ALL; +USE IEEE.numeric_std.ALL; +USE common_lib.common_pkg.ALL; +USE dp_lib.dp_stream_pkg.ALL; + +PACKAGE axi4_stream_pkg Is + + CONSTANT c_axi4_stream_data_w : NATURAL := 512; -- Data width, upto 512bit for Xilinx IP + CONSTANT c_axi4_stream_user_w : NATURAL := 70; -- User data, upto 70bit for Xilinx IP + CONSTANT c_axi4_stream_tid_w : NATURAL := 4; -- Thread ID, upto 4bit for Xilinx IP + CONSTANT c_axi4_stream_dest_w : NATURAL := 32; -- Routing data, upto 32bit for Xilinx IP + CONSTANT c_axi4_stream_keep_w : NATURAL := c_axi4_stream_data_w/8; -- 1 bit for each byte in data. + CONSTANT c_axi4_stream_strb_w : NATURAL := c_axi4_stream_data_w/8; -- 1 bit for each byte in daya. + + TYPE t_axi4_siso IS RECORD -- Source In and Sink Out + tready : STD_LOGIC; -- Ready to accept data from destination + END RECORD; + + TYPE t_axi4_sosi IS RECORD -- Source Out and Sink In + tvalid : STD_LOGIC; -- Data valid + tdata : STD_LOGIC_VECTOR(c_axi4_stream_data_w-1 DOWNTO 0); -- Data bus + tstrb : STD_LOGIC_VECTOR(c_axi4_stream_strb_w-1 DOWNTO 0); -- Byte valids, indicates if data is position (0) or data (1). Generally not used + tkeep : STD_LOGIC_VECTOR(c_axi4_stream_keep_w-1 DOWNTO 0); -- Indicate valid data bytes (1) or null bytes (0). + tlast : STD_LOGIC; -- Last transaction in a burst + tid : STD_LOGIC_VECTOR(c_axi4_stream_tid_w-1 DOWNTO 0); -- Transaction ID + tdest : STD_LOGIC_VECTOR(c_axi4_stream_dest_w-1 DOWNTO 0); -- Destination rounting information + tuser : STD_LOGIC_VECTOR(c_axi4_stream_user_w-1 DOWNTO 0); -- Tranaction user fields + END RECORD; + + + -- Initialise signal declarations with c_axi4_stream_rst/rdy to ease the interpretation of slv fields with unused bits + CONSTANT c_axi4_siso_rst : t_axi4_siso := (tready => '0'); + CONSTANT c_axi4_siso_x : t_axi4_siso := (tready => 'X'); + CONSTANT c_axi4_siso_hold : t_axi4_siso := (tready => '0'); + CONSTANT c_axi4_siso_rdy : t_axi4_siso := (tready => '1'); + CONSTANT c_axi4_siso_flush : t_axi4_siso := (tready => '1'); + CONSTANT c_axi4_sosi_rst : t_axi4_sosi := (tvalid => '0', tdata => (OTHERS=>'0'), tstrb => (OTHERS=>'0'), tkeep => (OTHERS=>'0'), tlast => '0', tid => (OTHERS=>'0'), tdest => (OTHERS=>'0'), tuser => (OTHERS=>'0')); + CONSTANT c_axi4_sosi_x : t_axi4_sosi := ('X', (OTHERS=>'X'), (OTHERS=>'X'), (OTHERS=>'X'), 'X', (OTHERS=>'X'), (OTHERS=>'X'), (OTHERS=>'X')); + + + -- Multi port or multi register array for DP stream records + TYPE t_axi4_siso_arr IS ARRAY (INTEGER RANGE <>) OF t_axi4_siso; + TYPE t_axi4_sosi_arr IS ARRAY (INTEGER RANGE <>) OF t_axi4_sosi; + + -- Multi-dimemsion array types with fixed LS-dimension + -- . 2 dimensional array with 2 fixed LS sosi/siso interfaces (axi4_split, axi4_concat) + TYPE t_axi4_siso_2arr_2 IS ARRAY (INTEGER RANGE <>) OF t_axi4_siso_arr(1 DOWNTO 0); + TYPE t_axi4_sosi_2arr_2 IS ARRAY (INTEGER RANGE <>) OF t_axi4_sosi_arr(1 DOWNTO 0); + + -- 2-dimensional streaming array type: + -- Note: + -- This t_*_mat is less useful then a t_*_2arr array of arrays, because assignments can only be done per element (i.e. not per row). However for t_*_2arr + -- the arrays dimension must be fixed, so these t_*_2arr types are application dependent and need to be defined where used. + TYPE t_axi4_siso_mat IS ARRAY (INTEGER RANGE <>, INTEGER RANGE <>) OF t_axi4_siso; + TYPE t_axi4_sosi_mat IS ARRAY (INTEGER RANGE <>, INTEGER RANGE <>) OF t_axi4_sosi; + + -- Check sosi.valid against siso.ready + PROCEDURE proc_axi4_siso_alert(CONSTANT c_ready_latency : IN NATURAL; + SIGNAL clk : IN STD_LOGIC; + SIGNAL sosi : IN t_axi4_sosi; + SIGNAL siso : IN t_axi4_siso; + SIGNAL ready_reg : INOUT STD_LOGIC_VECTOR); + + -- Default RL=1 + PROCEDURE proc_axi4_siso_alert(SIGNAL clk : IN STD_LOGIC; + SIGNAL sosi : IN t_axi4_sosi; + SIGNAL siso : IN t_axi4_siso; + SIGNAL ready_reg : INOUT STD_LOGIC_VECTOR); + + -- SOSI/SISO array version + PROCEDURE proc_axi4_siso_alert(CONSTANT c_ready_latency : IN NATURAL; + SIGNAL clk : IN STD_LOGIC; + SIGNAL sosi_arr : IN t_axi4_sosi_arr; + SIGNAL siso_arr : IN t_axi4_siso_arr; + SIGNAL ready_reg : INOUT STD_LOGIC_VECTOR); + + -- SOSI/SISO array version with RL=1 + PROCEDURE proc_axi4_siso_alert(SIGNAL clk : IN STD_LOGIC; + SIGNAL sosi_arr : IN t_axi4_sosi_arr; + SIGNAL siso_arr : IN t_axi4_siso_arr; + SIGNAL ready_reg : INOUT STD_LOGIC_VECTOR); + + + -- Keep part of head data and combine part of tail data, use the other sosi from head_sosi + FUNCTION func_axi4_data_shift_first(head_sosi, tail_sosi : t_axi4_sosi; symbol_w, nof_symbols_per_data, nof_symbols_from_tail : NATURAL) RETURN t_axi4_sosi; + -- Shift and combine part of previous data and this data, use the other sosi from prev_sosi + FUNCTION func_axi4_data_shift( prev_sosi, this_sosi : t_axi4_sosi; symbol_w, nof_symbols_per_data, nof_symbols_from_this : NATURAL) RETURN t_axi4_sosi; + -- Shift part of tail data and account for input empty + FUNCTION func_axi4_data_shift_last( tail_sosi : t_axi4_sosi; symbol_w, nof_symbols_per_data, nof_symbols_from_tail, input_empty : NATURAL) RETURN t_axi4_sosi; + + -- Determine resulting empty if two streams are concatenated or split + FUNCTION func_axi4_empty_concat(head_empty, tail_empty : STD_LOGIC_VECTOR; nof_symbols_per_data : NATURAL) RETURN STD_LOGIC_VECTOR; + FUNCTION func_axi4_empty_split(input_empty, head_empty : STD_LOGIC_VECTOR; nof_symbols_per_data : NATURAL) RETURN STD_LOGIC_VECTOR; + + -- Multiplex the t_axi4_sosi_arr based on the valid, assuming that at most one input is active valid. + FUNCTION func_axi4_sosi_arr_mux(axi4 : t_axi4_sosi_arr) RETURN t_axi4_sosi; + + -- Determine the combined logical value of corresponding STD_LOGIC fields in t_axi4_*_arr (for all elements or only for the mask[]='1' elements) + FUNCTION func_axi4_stream_arr_and(axi4 : t_axi4_siso_arr; mask : STD_LOGIC_VECTOR; str : STRING) RETURN STD_LOGIC; + FUNCTION func_axi4_stream_arr_and(axi4 : t_axi4_sosi_arr; mask : STD_LOGIC_VECTOR; str : STRING) RETURN STD_LOGIC; + FUNCTION func_axi4_stream_arr_and(axi4 : t_axi4_siso_arr; str : STRING) RETURN STD_LOGIC; + FUNCTION func_axi4_stream_arr_and(axi4 : t_axi4_sosi_arr; str : STRING) RETURN STD_LOGIC; + FUNCTION func_axi4_stream_arr_or( axi4 : t_axi4_siso_arr; mask : STD_LOGIC_VECTOR; str : STRING) RETURN STD_LOGIC; + FUNCTION func_axi4_stream_arr_or( axi4 : t_axi4_sosi_arr; mask : STD_LOGIC_VECTOR; str : STRING) RETURN STD_LOGIC; + FUNCTION func_axi4_stream_arr_or( axi4 : t_axi4_siso_arr; str : STRING) RETURN STD_LOGIC; + FUNCTION func_axi4_stream_arr_or( axi4 : t_axi4_sosi_arr; str : STRING) RETURN STD_LOGIC; + + -- Functions to set or get a STD_LOGIC field as a STD_LOGIC_VECTOR to or from an siso or an sosi array + FUNCTION func_axi4_stream_arr_set(axi4 : t_axi4_siso_arr; slv : STD_LOGIC_VECTOR; str : STRING) RETURN t_axi4_siso_arr; + FUNCTION func_axi4_stream_arr_set(axi4 : t_axi4_sosi_arr; slv : STD_LOGIC_VECTOR; str : STRING) RETURN t_axi4_sosi_arr; + FUNCTION func_axi4_stream_arr_set(axi4 : t_axi4_siso_arr; sl : STD_LOGIC; str : STRING) RETURN t_axi4_siso_arr; + FUNCTION func_axi4_stream_arr_set(axi4 : t_axi4_sosi_arr; sl : STD_LOGIC; str : STRING) RETURN t_axi4_sosi_arr; + FUNCTION func_axi4_stream_arr_get(axi4 : t_axi4_siso_arr; str : STRING) RETURN STD_LOGIC_VECTOR; + FUNCTION func_axi4_stream_arr_get(axi4 : t_axi4_sosi_arr; str : STRING) RETURN STD_LOGIC_VECTOR; + + -- Functions to select elements from two siso or two sosi arrays (sel[] = '1' selects a, sel[] = '0' selects b) + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a, b : t_axi4_siso) RETURN t_axi4_siso_arr; + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a, b : t_axi4_sosi) RETURN t_axi4_sosi_arr; + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a : t_axi4_siso_arr; b : t_axi4_siso) RETURN t_axi4_siso_arr; + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a : t_axi4_sosi_arr; b : t_axi4_sosi) RETURN t_axi4_sosi_arr; + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a : t_axi4_siso; b : t_axi4_siso_arr) RETURN t_axi4_siso_arr; + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a : t_axi4_sosi; b : t_axi4_sosi_arr) RETURN t_axi4_sosi_arr; + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a, b : t_axi4_siso_arr) RETURN t_axi4_siso_arr; + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a, b : t_axi4_sosi_arr) RETURN t_axi4_sosi_arr; + + -- Fix reversed buses due to connecting TO to DOWNTO range arrays. + FUNCTION func_axi4_stream_arr_reverse_range(in_arr : t_axi4_sosi_arr) RETURN t_axi4_sosi_arr; + FUNCTION func_axi4_stream_arr_reverse_range(in_arr : t_axi4_siso_arr) RETURN t_axi4_siso_arr; + + -- Functions to combinatorially hold the data fields and to set or reset the control fields in an sosi array + FUNCTION func_axi4_stream_arr_set_control( axi4 : t_axi4_sosi_arr; ctrl : t_axi4_sosi) RETURN t_axi4_sosi_arr; + FUNCTION func_axi4_stream_arr_reset_control(axi4 : t_axi4_sosi_arr ) RETURN t_axi4_sosi_arr; + + -- Functions to convert dp streaming to axi4 streaming + FUNCTION func_axi4_stream_from_dp_sosi(dp_sosi : t_dp_sosi) RETURN t_axi4_sosi; + FUNCTION func_axi4_stream_from_dp_siso(dp_siso : t_dp_siso) RETURN t_axi4_siso; + + -- Functions to convert dp streaming to axi4 streaming + FUNCTION func_axi4_stream_to_dp_sosi(axi4_sosi : t_axi4_sosi) RETURN t_dp_sosi; + FUNCTION func_axi4_stream_to_dp_siso(axi4_siso : t_axi4_siso) RETURN t_dp_siso; + + -- Function to derive DP empty from AXI4 tkeep by counting the 0s in TKEEP. + FUNCTION func_axi4_stream_tkeep_to_dp_empty(tkeep : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR; + +END axi4_stream_pkg; + + +PACKAGE BODY axi4_stream_pkg IS + + -- Check sosi.valid against siso.ready + PROCEDURE proc_axi4_siso_alert(CONSTANT c_ready_latency : IN NATURAL; + SIGNAL clk : IN STD_LOGIC; + SIGNAL sosi : IN t_axi4_sosi; + SIGNAL siso : IN t_axi4_siso; + SIGNAL ready_reg : INOUT STD_LOGIC_VECTOR) IS + BEGIN + ready_reg(0) <= siso.tready; + -- Register siso.ready in c_ready_latency registers + IF rising_edge(clk) THEN + -- Check DP sink + IF sosi.tvalid = '1' AND ready_reg(c_ready_latency) = '0' THEN + REPORT "RL ERROR" SEVERITY FAILURE; + END IF; + ready_reg( 1 TO c_ready_latency) <= ready_reg( 0 TO c_ready_latency-1); + END IF; + END proc_axi4_siso_alert; + + -- Default RL=1 + PROCEDURE proc_axi4_siso_alert(SIGNAL clk : IN STD_LOGIC; + SIGNAL sosi : IN t_axi4_sosi; + SIGNAL siso : IN t_axi4_siso; + SIGNAL ready_reg : INOUT STD_LOGIC_VECTOR) IS + BEGIN + proc_axi4_siso_alert(1, clk, sosi, siso, ready_reg); + END proc_axi4_siso_alert; + + -- SOSI/SISO array version + PROCEDURE proc_axi4_siso_alert(CONSTANT c_ready_latency : IN NATURAL; + SIGNAL clk : IN STD_LOGIC; + SIGNAL sosi_arr : IN t_axi4_sosi_arr; + SIGNAL siso_arr : IN t_axi4_siso_arr; + SIGNAL ready_reg : INOUT STD_LOGIC_VECTOR) IS + BEGIN + FOR i IN 0 TO sosi_arr'LENGTH-1 LOOP + ready_reg(i*(c_ready_latency+1)) <= siso_arr(i).tready; -- SLV is used as an array: nof_streams*(0..c_ready_latency) + END LOOP; + -- Register siso.ready in c_ready_latency registers + IF rising_edge(clk) THEN + FOR i IN 0 TO sosi_arr'LENGTH-1 LOOP + -- Check DP sink + IF sosi_arr(i).tvalid = '1' AND ready_reg(i*(c_ready_latency+1)+1) = '0' THEN + REPORT "RL ERROR" SEVERITY FAILURE; + END IF; + ready_reg(i*(c_ready_latency+1)+1 TO i*(c_ready_latency+1)+c_ready_latency) <= ready_reg(i*(c_ready_latency+1) TO i*(c_ready_latency+1)+c_ready_latency-1); + END LOOP; + END IF; + END proc_axi4_siso_alert; + + -- SOSI/SISO array version with RL=1 + PROCEDURE proc_axi4_siso_alert(SIGNAL clk : IN STD_LOGIC; + SIGNAL sosi_arr : IN t_axi4_sosi_arr; + SIGNAL siso_arr : IN t_axi4_siso_arr; + SIGNAL ready_reg : INOUT STD_LOGIC_VECTOR) IS + BEGIN + proc_axi4_siso_alert(1, clk, sosi_arr, siso_arr, ready_reg); + END proc_axi4_siso_alert; + + -- Keep part of head data and combine part of tail data + FUNCTION func_axi4_data_shift_first(head_sosi, tail_sosi : t_axi4_sosi; symbol_w, nof_symbols_per_data, nof_symbols_from_tail : NATURAL) RETURN t_axi4_sosi IS + VARIABLE vN : NATURAL := nof_symbols_per_data; + VARIABLE v_sosi : t_axi4_sosi; + BEGIN + ASSERT nof_symbols_from_tail<vN REPORT "func_axi4_data_shift_first : no symbols from head" SEVERITY FAILURE; + -- use the other sosi from head_sosi + v_sosi := head_sosi; -- I = nof_symbols_from_tail = 0 + FOR I IN 1 TO vN-1 LOOP -- I > 0 + IF nof_symbols_from_tail = I THEN + v_sosi.tdata(I*symbol_w-1 DOWNTO 0) := tail_sosi.tdata(vN*symbol_w-1 DOWNTO (vN-I)*symbol_w); + END IF; + END LOOP; + RETURN v_sosi; + END func_axi4_data_shift_first; + + + -- Shift and combine part of previous data and this data, + FUNCTION func_axi4_data_shift(prev_sosi, this_sosi : t_axi4_sosi; symbol_w, nof_symbols_per_data, nof_symbols_from_this : NATURAL) RETURN t_axi4_sosi IS + VARIABLE vK : NATURAL := nof_symbols_from_this; + VARIABLE vN : NATURAL := nof_symbols_per_data; + VARIABLE v_sosi : t_axi4_sosi; + BEGIN + -- use the other sosi from this_sosi if nof_symbols_from_this > 0 else use other sosi from prev_sosi + IF vK>0 THEN + v_sosi := this_sosi; + ELSE + v_sosi := prev_sosi; + END IF; + + -- use sosi data from both if 0 < nof_symbols_from_this < nof_symbols_per_data (i.e. 0 < I < vN) + IF vK<nof_symbols_per_data THEN -- I = vK = nof_symbols_from_this < vN + -- Implementation using variable vK directly instead of via I in a LOOP + -- IF vK > 0 THEN + -- v_sosi.data(vN*symbol_w-1 DOWNTO vK*symbol_w) := prev_sosi.data((vN-vK)*symbol_w-1 DOWNTO 0); + -- v_sosi.data( vK*symbol_w-1 DOWNTO 0) := this_sosi.data( vN *symbol_w-1 DOWNTO (vN-vK)*symbol_w); + -- END IF; + -- Implementaion using LOOP vK rather than VARIABLE vK directly as index to help synthesis and avoid potential multiplier + v_sosi.tdata := prev_sosi.tdata; -- I = vK = nof_symbols_from_this = 0 + FOR I IN 1 TO vN-1 LOOP -- I = vK = nof_symbols_from_this > 0 + IF vK = I THEN + v_sosi.tdata(vN*symbol_w-1 DOWNTO I*symbol_w) := prev_sosi.tdata((vN-I)*symbol_w-1 DOWNTO 0); + v_sosi.tdata( I*symbol_w-1 DOWNTO 0) := this_sosi.tdata( vN *symbol_w-1 DOWNTO (vN-I)*symbol_w); + END IF; + END LOOP; + END IF; + RETURN v_sosi; + END func_axi4_data_shift; + + + -- Shift part of tail data and account for input empty + FUNCTION func_axi4_data_shift_last(tail_sosi : t_axi4_sosi; symbol_w, nof_symbols_per_data, nof_symbols_from_tail, input_empty : NATURAL) RETURN t_axi4_sosi IS + VARIABLE vK : NATURAL := nof_symbols_from_tail; + VARIABLE vL : NATURAL := input_empty; + VARIABLE vN : NATURAL := nof_symbols_per_data; + VARIABLE v_sosi : t_axi4_sosi; + BEGIN + ASSERT vK > 0 REPORT "func_axi4_data_shift_last : no symbols from tail" SEVERITY FAILURE; + ASSERT vK+vL<=vN REPORT "func_axi4_data_shift_last : impossible shift" SEVERITY FAILURE; + v_sosi := tail_sosi; + -- Implementation using variable vK directly instead of via I in a LOOP + -- IF vK > 0 THEN + -- v_sosi.data(vN*symbol_w-1 DOWNTO (vN-vK)*symbol_w) <= tail_sosi.data((vK+vL)*symbol_w-1 DOWNTO vL*symbol_w); + -- END IF; + -- Implementation using LOOP vK rather than VARIABLE vK directly as index to help synthesis and avoid potential multiplier + -- Implementation using LOOP vL rather than VARIABLE vL directly as index to help synthesis and avoid potential multiplier + FOR I IN 1 TO vN-1 LOOP + IF vK = I THEN + FOR J IN 0 TO vN-1 LOOP + IF vL = J THEN + v_sosi.tdata(vN*symbol_w-1 DOWNTO (vN-I)*symbol_w) := tail_sosi.tdata((I+J)*symbol_w-1 DOWNTO J*symbol_w); + END IF; + END LOOP; + END IF; + END LOOP; + RETURN v_sosi; + END func_axi4_data_shift_last; + + + -- Determine resulting empty if two streams are concatenated + -- . both empty must use the same nof symbols per data + FUNCTION func_axi4_empty_concat(head_empty, tail_empty : STD_LOGIC_VECTOR; nof_symbols_per_data : NATURAL) RETURN STD_LOGIC_VECTOR IS + VARIABLE v_a, v_b, v_empty : NATURAL; + BEGIN + v_a := TO_UINT(head_empty); + v_b := TO_UINT(tail_empty); + v_empty := v_a + v_b; + IF v_empty >= nof_symbols_per_data THEN + v_empty := v_empty - nof_symbols_per_data; + END IF; + RETURN TO_UVEC(v_empty, head_empty'LENGTH); + END func_axi4_empty_concat; + + FUNCTION func_axi4_empty_split(input_empty, head_empty : STD_LOGIC_VECTOR; nof_symbols_per_data : NATURAL) RETURN STD_LOGIC_VECTOR IS + VARIABLE v_a, v_b, v_empty : NATURAL; + BEGIN + v_a := TO_UINT(input_empty); + v_b := TO_UINT(head_empty); + IF v_a >= v_b THEN + v_empty := v_a - v_b; + ELSE + v_empty := (nof_symbols_per_data + v_a) - v_b; + END IF; + RETURN TO_UVEC(v_empty, head_empty'LENGTH); + END func_axi4_empty_split; + + + -- Multiplex the t_axi4_sosi_arr based on the valid, assuming that at most one input is active valid. + FUNCTION func_axi4_sosi_arr_mux(axi4 : t_axi4_sosi_arr) RETURN t_axi4_sosi IS + VARIABLE v_sosi : t_axi4_sosi := c_axi4_sosi_rst; + BEGIN + FOR I IN axi4'RANGE LOOP + IF axi4(I).tvalid='1' THEN + v_sosi := axi4(I); + EXIT; + END IF; + END LOOP; + RETURN v_sosi; + END func_axi4_sosi_arr_mux; + + + -- Determine the combined logical value of corresponding STD_LOGIC fields in t_axi4_*_arr (for all elements or only for the mask[]='1' elements) + FUNCTION func_axi4_stream_arr_and(axi4 : t_axi4_siso_arr; mask : STD_LOGIC_VECTOR; str : STRING) RETURN STD_LOGIC IS + VARIABLE v_vec : STD_LOGIC_VECTOR(axi4'RANGE) := (OTHERS=>'1'); -- set default v_vec such that unmasked input have no influence on operation result + VARIABLE v_any : STD_LOGIC := '0'; + BEGIN + -- map siso field to v_vec + FOR I IN axi4'RANGE LOOP + IF mask(I)='1' THEN + v_any := '1'; + IF str="READY" THEN v_vec(I) := axi4(I).tready; + ELSE REPORT "Error in func_axi4_stream_arr_and for t_axi4_siso_arr"; + END IF; + END IF; + END LOOP; + -- do operation on the selected record field + IF v_any='1' THEN + RETURN vector_and(v_vec); -- return AND of the masked input fields + ELSE + RETURN '0'; -- return '0' if no input was masked + END IF; + END func_axi4_stream_arr_and; + + FUNCTION func_axi4_stream_arr_and(axi4 : t_axi4_sosi_arr; mask : STD_LOGIC_VECTOR; str : STRING) RETURN STD_LOGIC IS + VARIABLE v_vec : STD_LOGIC_VECTOR(axi4'RANGE) := (OTHERS=>'1'); -- set default v_vec such that unmasked input have no influence on operation result + VARIABLE v_any : STD_LOGIC := '0'; + BEGIN + -- map siso field to v_vec + FOR I IN axi4'RANGE LOOP + IF mask(I)='1' THEN + v_any := '1'; + IF str="VALID" THEN v_vec(I) := axi4(I).tvalid; + ELSE REPORT "Error in func_axi4_stream_arr_and for t_axi4_sosi_arr"; + END IF; + END IF; + END LOOP; + -- do operation on the selected record field + IF v_any='1' THEN + RETURN vector_and(v_vec); -- return AND of the masked input fields + ELSE + RETURN '0'; -- return '0' if no input was masked + END IF; + END func_axi4_stream_arr_and; + + FUNCTION func_axi4_stream_arr_and(axi4 : t_axi4_siso_arr; str : STRING) RETURN STD_LOGIC IS + CONSTANT c_mask : STD_LOGIC_VECTOR(axi4'RANGE) := (OTHERS=>'1'); + BEGIN + RETURN func_axi4_stream_arr_and(axi4, c_mask, str); + END func_axi4_stream_arr_and; + + FUNCTION func_axi4_stream_arr_and(axi4 : t_axi4_sosi_arr; str : STRING) RETURN STD_LOGIC IS + CONSTANT c_mask : STD_LOGIC_VECTOR(axi4'RANGE) := (OTHERS=>'1'); + BEGIN + RETURN func_axi4_stream_arr_and(axi4, c_mask, str); + END func_axi4_stream_arr_and; + + FUNCTION func_axi4_stream_arr_or(axi4 : t_axi4_siso_arr; mask : STD_LOGIC_VECTOR; str : STRING) RETURN STD_LOGIC IS + VARIABLE v_vec : STD_LOGIC_VECTOR(axi4'RANGE) := (OTHERS=>'0'); -- set default v_vec such that unmasked input have no influence on operation result + VARIABLE v_any : STD_LOGIC := '0'; + BEGIN + -- map siso field to v_vec + FOR I IN axi4'RANGE LOOP + IF mask(I)='1' THEN + v_any := '1'; + IF str="READY" THEN v_vec(I) := axi4(I).tready; + ELSE REPORT "Error in func_axi4_stream_arr_or for t_axi4_siso_arr"; + END IF; + END IF; + END LOOP; + -- do operation on the selected record field + IF v_any='1' THEN + RETURN vector_or(v_vec); -- return OR of the masked input fields + ELSE + RETURN '0'; -- return '0' if no input was masked + END IF; + END func_axi4_stream_arr_or; + + FUNCTION func_axi4_stream_arr_or(axi4 : t_axi4_sosi_arr; mask : STD_LOGIC_VECTOR; str : STRING) RETURN STD_LOGIC IS + VARIABLE v_vec : STD_LOGIC_VECTOR(axi4'RANGE) := (OTHERS=>'0'); -- set default v_vec such that unmasked input have no influence on operation result + VARIABLE v_any : STD_LOGIC := '0'; + BEGIN + -- map siso field to v_vec + FOR I IN axi4'RANGE LOOP + IF mask(I)='1' THEN + v_any := '1'; + IF str="VALID" THEN v_vec(I) := axi4(I).tvalid; + ELSE REPORT "Error in func_axi4_stream_arr_or for t_axi4_sosi_arr"; + END IF; + END IF; + END LOOP; + -- do operation on the selected record field + IF v_any='1' THEN + RETURN vector_or(v_vec); -- return OR of the masked input fields + ELSE + RETURN '0'; -- return '0' if no input was masked + END IF; + END func_axi4_stream_arr_or; + + FUNCTION func_axi4_stream_arr_or(axi4 : t_axi4_siso_arr; str : STRING) RETURN STD_LOGIC IS + CONSTANT c_mask : STD_LOGIC_VECTOR(axi4'RANGE) := (OTHERS=>'1'); + BEGIN + RETURN func_axi4_stream_arr_or(axi4, c_mask, str); + END func_axi4_stream_arr_or; + + FUNCTION func_axi4_stream_arr_or(axi4 : t_axi4_sosi_arr; str : STRING) RETURN STD_LOGIC IS + CONSTANT c_mask : STD_LOGIC_VECTOR(axi4'RANGE) := (OTHERS=>'1'); + BEGIN + RETURN func_axi4_stream_arr_or(axi4, c_mask, str); + END func_axi4_stream_arr_or; + + + -- Functions to set or get a STD_LOGIC field as a STD_LOGIC_VECTOR to or from an siso or an sosi array + FUNCTION func_axi4_stream_arr_set(axi4 : t_axi4_siso_arr; slv : STD_LOGIC_VECTOR; str : STRING) RETURN t_axi4_siso_arr IS + VARIABLE v_axi4 : t_axi4_siso_arr(axi4'RANGE) := axi4; -- default + VARIABLE v_slv : STD_LOGIC_VECTOR(axi4'RANGE) := slv; -- map to ensure same range as for axi4 + BEGIN + FOR I IN axi4'RANGE LOOP + IF str="READY" THEN v_axi4(I).tready := v_slv(I); + ELSE REPORT "Error in func_axi4_stream_arr_set for t_axi4_siso_arr"; + END IF; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_set; + + FUNCTION func_axi4_stream_arr_set(axi4 : t_axi4_sosi_arr; slv : STD_LOGIC_VECTOR; str : STRING) RETURN t_axi4_sosi_arr IS + VARIABLE v_axi4 : t_axi4_sosi_arr(axi4'RANGE) := axi4; -- default + VARIABLE v_slv : STD_LOGIC_VECTOR(axi4'RANGE) := slv; -- map to ensure same range as for axi4 + BEGIN + FOR I IN axi4'RANGE LOOP + IF str="VALID" THEN v_axi4(I).tvalid := v_slv(I); + ELSE REPORT "Error in func_axi4_stream_arr_set for t_axi4_sosi_arr"; + END IF; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_set; + + FUNCTION func_axi4_stream_arr_set(axi4 : t_axi4_siso_arr; sl : STD_LOGIC; str : STRING) RETURN t_axi4_siso_arr IS + VARIABLE v_slv : STD_LOGIC_VECTOR(axi4'RANGE) := (OTHERS=>sl); + BEGIN + RETURN func_axi4_stream_arr_set(axi4, v_slv, str); + END func_axi4_stream_arr_set; + + FUNCTION func_axi4_stream_arr_set(axi4 : t_axi4_sosi_arr; sl : STD_LOGIC; str : STRING) RETURN t_axi4_sosi_arr IS + VARIABLE v_slv : STD_LOGIC_VECTOR(axi4'RANGE) := (OTHERS=>sl); + BEGIN + RETURN func_axi4_stream_arr_set(axi4, v_slv, str); + END func_axi4_stream_arr_set; + + FUNCTION func_axi4_stream_arr_get(axi4 : t_axi4_siso_arr; str : STRING) RETURN STD_LOGIC_VECTOR IS + VARIABLE v_ctrl : STD_LOGIC_VECTOR(axi4'RANGE); + BEGIN + FOR I IN axi4'RANGE LOOP + IF str="READY" THEN v_ctrl(I) := axi4(I).tready; + ELSE REPORT "Error in func_axi4_stream_arr_get for t_axi4_siso_arr"; + END IF; + END LOOP; + RETURN v_ctrl; + END func_axi4_stream_arr_get; + + FUNCTION func_axi4_stream_arr_get(axi4 : t_axi4_sosi_arr; str : STRING) RETURN STD_LOGIC_VECTOR IS + VARIABLE v_ctrl : STD_LOGIC_VECTOR(axi4'RANGE); + BEGIN + FOR I IN axi4'RANGE LOOP + IF str="VALID" THEN v_ctrl(I) := axi4(I).tvalid; + ELSE REPORT "Error in func_axi4_stream_arr_get for t_axi4_sosi_arr"; + END IF; + END LOOP; + RETURN v_ctrl; + END func_axi4_stream_arr_get; + + + -- Functions to select elements from two siso or two sosi arrays (sel[] = '1' selects a, sel[] = '0' selects b) + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a, b : t_axi4_siso) RETURN t_axi4_siso_arr IS + VARIABLE v_axi4 : t_axi4_siso_arr(sel'RANGE); + BEGIN + FOR I IN sel'RANGE LOOP + IF sel(I)='1' THEN + v_axi4(I) := a; + ELSE + v_axi4(I) := b; + END IF; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_select; + + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a : t_axi4_siso_arr; b : t_axi4_siso) RETURN t_axi4_siso_arr IS + VARIABLE v_axi4 : t_axi4_siso_arr(sel'RANGE); + BEGIN + FOR I IN sel'RANGE LOOP + IF sel(I)='1' THEN + v_axi4(I) := a(I); + ELSE + v_axi4(I) := b; + END IF; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_select; + + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a : t_axi4_siso; b : t_axi4_siso_arr) RETURN t_axi4_siso_arr IS + VARIABLE v_axi4 : t_axi4_siso_arr(sel'RANGE); + BEGIN + FOR I IN sel'RANGE LOOP + IF sel(I)='1' THEN + v_axi4(I) := a; + ELSE + v_axi4(I) := b(I); + END IF; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_select; + + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a, b : t_axi4_siso_arr) RETURN t_axi4_siso_arr IS + VARIABLE v_axi4 : t_axi4_siso_arr(sel'RANGE); + BEGIN + FOR I IN sel'RANGE LOOP + IF sel(I)='1' THEN + v_axi4(I) := a(I); + ELSE + v_axi4(I) := b(I); + END IF; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_select; + + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a, b : t_axi4_sosi) RETURN t_axi4_sosi_arr IS + VARIABLE v_axi4 : t_axi4_sosi_arr(sel'RANGE); + BEGIN + FOR I IN sel'RANGE LOOP + IF sel(I)='1' THEN + v_axi4(I) := a; + ELSE + v_axi4(I) := b; + END IF; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_select; + + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a : t_axi4_sosi_arr; b : t_axi4_sosi) RETURN t_axi4_sosi_arr IS + VARIABLE v_axi4 : t_axi4_sosi_arr(sel'RANGE); + BEGIN + FOR I IN sel'RANGE LOOP + IF sel(I)='1' THEN + v_axi4(I) := a(I); + ELSE + v_axi4(I) := b; + END IF; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_select; + + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a : t_axi4_sosi; b : t_axi4_sosi_arr) RETURN t_axi4_sosi_arr IS + VARIABLE v_axi4 : t_axi4_sosi_arr(sel'RANGE); + BEGIN + FOR I IN sel'RANGE LOOP + IF sel(I)='1' THEN + v_axi4(I) := a; + ELSE + v_axi4(I) := b(I); + END IF; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_select; + + FUNCTION func_axi4_stream_arr_select(sel : STD_LOGIC_VECTOR; a, b : t_axi4_sosi_arr) RETURN t_axi4_sosi_arr IS + VARIABLE v_axi4 : t_axi4_sosi_arr(sel'RANGE); + BEGIN + FOR I IN sel'RANGE LOOP + IF sel(I)='1' THEN + v_axi4(I) := a(I); + ELSE + v_axi4(I) := b(I); + END IF; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_select; + + FUNCTION func_axi4_stream_arr_reverse_range(in_arr : t_axi4_siso_arr) RETURN t_axi4_siso_arr IS + VARIABLE v_to_range : t_axi4_siso_arr(0 TO in_arr'HIGH); + VARIABLE v_downto_range : t_axi4_siso_arr(in_arr'HIGH DOWNTO 0); + BEGIN + FOR i IN in_arr'RANGE LOOP + v_to_range(i) := in_arr(in_arr'HIGH-i); + v_downto_range(i) := in_arr(in_arr'HIGH-i); + END LOOP; + IF in_arr'LEFT>in_arr'RIGHT THEN + RETURN v_downto_range; + ELSIF in_arr'LEFT<in_arr'RIGHT THEN + RETURN v_to_range; + ELSE + RETURN in_arr; + END IF; + END func_axi4_stream_arr_reverse_range; + + FUNCTION func_axi4_stream_arr_reverse_range(in_arr : t_axi4_sosi_arr) RETURN t_axi4_sosi_arr IS + VARIABLE v_to_range : t_axi4_sosi_arr(0 TO in_arr'HIGH); + VARIABLE v_downto_range : t_axi4_sosi_arr(in_arr'HIGH DOWNTO 0); + BEGIN + FOR i IN in_arr'RANGE LOOP + v_to_range(i) := in_arr(in_arr'HIGH-i); + v_downto_range(i) := in_arr(in_arr'HIGH-i); + END LOOP; + IF in_arr'LEFT>in_arr'RIGHT THEN + RETURN v_downto_range; + ELSIF in_arr'LEFT<in_arr'RIGHT THEN + RETURN v_to_range; + ELSE + RETURN in_arr; + END IF; + END func_axi4_stream_arr_reverse_range; + + -- Functions to combinatorially hold the data fields and to set or reset the control fields in an sosi array + FUNCTION func_axi4_stream_arr_set_control(axi4 : t_axi4_sosi_arr; ctrl : t_axi4_sosi) RETURN t_axi4_sosi_arr IS + VARIABLE v_axi4 : t_axi4_sosi_arr(axi4'RANGE) := axi4; -- hold sosi data + BEGIN + FOR I IN axi4'RANGE LOOP -- set sosi control + v_axi4(I).tvalid := ctrl.tvalid; + v_axi4(I).tuser := ctrl.tuser; + v_axi4(I).tdest := ctrl.tdest; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_set_control; + + FUNCTION func_axi4_stream_arr_reset_control(axi4 : t_axi4_sosi_arr) RETURN t_axi4_sosi_arr IS + VARIABLE v_axi4 : t_axi4_sosi_arr(axi4'RANGE) := axi4; -- hold sosi data + BEGIN + FOR I IN axi4'RANGE LOOP -- reset sosi control + v_axi4(I).tvalid := '0'; + END LOOP; + RETURN v_axi4; + END func_axi4_stream_arr_reset_control; + + -- Functions to convert dp streaming to axi4 streaming + FUNCTION func_axi4_stream_from_dp_sosi(dp_sosi : t_dp_sosi) RETURN t_axi4_sosi IS + CONSTANT c_max_empty_w : NATURAL := ceil_log2(c_axi4_stream_keep_w); + VARIABLE v_axi4_sosi : t_axi4_sosi := c_axi4_sosi_rst; + VARIABLE v_empty_int : NATURAL := 0; + VARIABLE v_keep : STD_LOGIC_VECTOR(c_axi4_stream_keep_w-1 DOWNTO 0) := (OTHERS => '1'); + BEGIN + v_empty_int := TO_UINT(dp_sosi.empty(c_max_empty_w-1 DOWNTO 0)); + IF dp_sosi.eop = '1' THEN -- empty is only valid on eop + v_keep(v_empty_int-1 DOWNTO 0) := (OTHERS => '0'); -- Keep is one hot encoded. + ELSE + v_keep := (OTHERS => '1'); + END IF; + + v_axi4_sosi.tvalid := dp_sosi.valid; + v_axi4_sosi.tdata := dp_sosi.data(c_axi4_stream_data_w-1 DOWNTO 0); + v_axi4_sosi.tstrb := v_keep; + v_axi4_sosi.tkeep := v_keep; + v_axi4_sosi.tlast := dp_sosi.eop; + v_axi4_sosi.tid := dp_sosi.channel(c_axi4_stream_tid_w-1 DOWNTO 0); + v_axi4_sosi.tdest := (OTHERS => '0'); + v_axi4_sosi.tuser := (OTHERS => '0'); + RETURN v_axi4_sosi; + END func_axi4_stream_from_dp_sosi; + + FUNCTION func_axi4_stream_from_dp_siso(dp_siso : t_dp_siso) RETURN t_axi4_siso IS + VARIABLE v_axi4_siso : t_axi4_siso := c_axi4_siso_rst; + -- Note that dp_siso.xon is not used. + BEGIN + v_axi4_siso.tready := dp_siso.ready; + RETURN v_axi4_siso; + END func_axi4_stream_from_dp_siso; + + -- Functions to convert dp streaming to axi4 streaming + FUNCTION func_axi4_stream_to_dp_sosi(axi4_sosi : t_axi4_sosi) RETURN t_dp_sosi IS + VARIABLE v_dp_sosi : t_dp_sosi := c_dp_sosi_rst; + BEGIN + v_dp_sosi.data(c_axi4_stream_data_w-1 DOWNTO 0) := axi4_sosi.tdata; + v_dp_sosi.valid := axi4_sosi.tvalid; + v_dp_sosi.sop := '0'; -- Should be generated (first valid after eop). + v_dp_sosi.eop := axi4_sosi.tlast; + + -- Cannot always derive from tkeep as it can have any bytes (not just the last bytes) + -- selected to be valid, the empty field can only select the last bytes to be invalid + -- for which you can use func_axi4_stream_tkeep_to_dp_empty. + v_dp_sosi.empty := (OTHERS => '0'); + v_dp_sosi.channel(c_axi4_stream_tid_w-1 DOWNTO 0) := axi4_sosi.tid; + RETURN v_dp_sosi; + END func_axi4_stream_to_dp_sosi; + + FUNCTION func_axi4_stream_to_dp_siso(axi4_siso : t_axi4_siso) RETURN t_dp_siso IS + VARIABLE v_dp_siso : t_dp_siso := c_dp_siso_rdy; + BEGIN + v_dp_siso.ready := axi4_siso.tready; + v_dp_siso.xon := '1'; + RETURN v_dp_siso; + END func_axi4_stream_to_dp_siso; + + -- Function to derive DP empty from AXI4 tkeep by counting the 0s in TKEEP. + -- This function assumes that only the the last bytes in an AXI4 data element are set to be null by TKEEP. + -- e.g. TKEEP = 11111000 will be valid but TKEEP = 11011111 is not. + FUNCTION func_axi4_stream_tkeep_to_dp_empty(tkeep : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR IS + VARIABLE v_count : NATURAL := 0; + BEGIN + FOR I IN tkeep'RANGE LOOP + IF tkeep(I) = '0' THEN + v_count := v_count + 1; + END IF; + END LOOP; + RETURN(TO_UVEC(v_count, c_dp_stream_empty_w)); + END func_axi4_stream_tkeep_to_dp_empty; + +END axi4_stream_pkg; + diff --git a/libraries/base/axi4/tb/vhdl/tb_axi4_stream_dp_bridge.vhd b/libraries/base/axi4/tb/vhdl/tb_axi4_stream_dp_bridge.vhd new file mode 100644 index 0000000000000000000000000000000000000000..9c830f2d666569729433696382cb70d1cd5ad6e0 --- /dev/null +++ b/libraries/base/axi4/tb/vhdl/tb_axi4_stream_dp_bridge.vhd @@ -0,0 +1,202 @@ +-- -------------------------------------------------------------------------- +-- 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: +-- . TB for axi4_stream_dp_bridge. +-- Description: +-- The testbench generates a stimuli DP stream which streams into the DUT. The +-- resulting AXI4 stream is looped back into the DUT that results in the final +-- DP stream. The resulting DP stream is verified. + +LIBRARY IEEE, common_lib, dp_lib; +USE IEEE.std_logic_1164.ALL; +USE IEEE.numeric_std.ALL; +USE common_lib.common_pkg.ALL; +USE dp_lib.dp_stream_pkg.ALL; +USE dp_lib.tb_dp_pkg.ALL; +USE work.axi4_stream_pkg.ALL; + +ENTITY tb_axi4_stream_dp_bridge IS + GENERIC ( + g_dp_rl : NATURAL := 1; + g_axi4_rl : NATURAL := 0 + ); +END tb_axi4_stream_dp_bridge; + + +ARCHITECTURE tb OF tb_axi4_stream_dp_bridge IS + + -- TX ready latency to DUT chain + CONSTANT c_tx_void : NATURAL := sel_a_b(g_dp_rl, 1, 0); -- used to avoid empty range VHDL warnings when g_dp_rl=0 + + CONSTANT c_tx_offset_sop : NATURAL := 0; + CONSTANT c_tx_period_sop : NATURAL := 7; -- sop in data valid cycle 0, 7, 14, ... + CONSTANT c_tx_offset_eop : NATURAL := 6; -- eop in data valid cycle 6, 13, 20, ... + CONSTANT c_tx_period_eop : NATURAL := c_tx_period_sop; + CONSTANT c_tx_offset_sync : NATURAL := 3; -- sync in data valid cycle 3, 20, 37, ... + CONSTANT c_tx_period_sync : NATURAL := 17; + + CONSTANT c_verify_en_wait : NATURAL := 10; -- wait some cycles before asserting verify enable + + CONSTANT c_empty_offset : NATURAL := 1; + CONSTANT c_channel_offset : NATURAL := 2; + + CONSTANT c_random_w : NATURAL := 19; + + CONSTANT c_max_empty_w : NATURAL := ceil_log2(c_axi4_stream_keep_w); + CONSTANT c_max_channel_w : NATURAL := c_axi4_stream_tid_w; + + SIGNAL tb_end : STD_LOGIC := '0'; + SIGNAL clk : STD_LOGIC := '0'; + SIGNAL rst : STD_LOGIC; + SIGNAL sync : STD_LOGIC; + SIGNAL lfsr1 : STD_LOGIC_VECTOR(c_random_w-1 DOWNTO 0) := (OTHERS=>'0'); + SIGNAL lfsr2 : STD_LOGIC_VECTOR(c_random_w DOWNTO 0) := (OTHERS=>'0'); + + SIGNAL cnt_dat : STD_LOGIC_VECTOR(c_dp_data_w-1 DOWNTO 0); + SIGNAL cnt_val : STD_LOGIC; + SIGNAL cnt_en : STD_LOGIC; + + SIGNAL tx_data : t_dp_data_arr(0 TO g_dp_rl + c_tx_void); + SIGNAL tx_val : STD_LOGIC_VECTOR(0 TO g_dp_rl + c_tx_void); + + SIGNAL in_ready : STD_LOGIC; + SIGNAL in_data : STD_LOGIC_VECTOR(c_dp_data_w-1 DOWNTO 0); + SIGNAL in_empty : STD_LOGIC_VECTOR(c_dp_stream_empty_w-1 DOWNTO 0); + SIGNAL in_channel : STD_LOGIC_VECTOR(c_dp_stream_channel_w-1 DOWNTO 0); + SIGNAL in_sync : STD_LOGIC; + SIGNAL in_val : STD_LOGIC; + SIGNAL in_sop : STD_LOGIC; + SIGNAL in_eop : STD_LOGIC; + SIGNAL valid_data : STD_LOGIC := '1'; + + -- DUT index c_nof_dut-1 = out_data + SIGNAL dut_siso : t_dp_siso; + SIGNAL dut_sosi : t_dp_sosi := c_dp_sosi_rst; + SIGNAL dut_axi4_sosi : t_axi4_sosi := c_axi4_sosi_rst; + SIGNAL dut_axi4_siso : t_axi4_siso := c_axi4_siso_rst; + SIGNAL dut_out_siso : t_dp_siso; + SIGNAL dut_out_sosi : t_dp_sosi := c_dp_sosi_rst; + + SIGNAL out_ready : STD_LOGIC; + SIGNAL prev_out_ready : STD_LOGIC_VECTOR(0 TO g_dp_rl); + SIGNAL out_data : STD_LOGIC_VECTOR(c_dp_data_w-1 DOWNTO 0); + SIGNAL out_empty : STD_LOGIC_VECTOR(c_dp_empty_w-1 DOWNTO 0); + SIGNAL out_channel : STD_LOGIC_VECTOR(c_dp_data_w-1 DOWNTO 0); + SIGNAL out_sync : STD_LOGIC; + SIGNAL out_val : STD_LOGIC; + SIGNAL out_sop : STD_LOGIC; + SIGNAL out_eop : STD_LOGIC; + SIGNAL hold_out_sop : STD_LOGIC; + SIGNAL prev_out_data : STD_LOGIC_VECTOR(out_data'RANGE); + + SIGNAL state : t_dp_state_enum; + SIGNAL verify_en : STD_LOGIC; + SIGNAL verify_done : STD_LOGIC; + + SIGNAL exp_data : STD_LOGIC_VECTOR(c_dp_data_w-1 DOWNTO 0) := sel_a_b(g_dp_rl = g_axi4_rl, TO_UVEC(18953, c_dp_data_w), TO_UVEC(19279, c_dp_data_w)); + SIGNAL exp_empty : STD_LOGIC_VECTOR(c_dp_stream_empty_w-1 DOWNTO 0); + SIGNAL exp_channel : STD_LOGIC_VECTOR(c_dp_channel_w-1 DOWNTO 0); + +BEGIN + + clk <= NOT clk OR tb_end AFTER clk_period/2; + rst <= '1', '0' AFTER clk_period*7; + + -- Sync interval + proc_dp_sync_interval(clk, sync); + + -- Input data + cnt_val <= in_ready AND cnt_en; + + proc_dp_cnt_dat(rst, clk, cnt_val, cnt_dat); + proc_dp_tx_data(g_dp_rl, rst, clk, cnt_val, cnt_dat, tx_data, tx_val, in_data, in_val); + proc_dp_tx_ctrl(c_tx_offset_sync, c_tx_period_sync, in_data, in_val, in_sync); + proc_dp_tx_ctrl(c_tx_offset_sop, c_tx_period_sop, in_data, in_val, in_sop); + proc_dp_tx_ctrl(c_tx_offset_eop, c_tx_period_eop, in_data, in_val, in_eop); + in_empty <= RESIZE_UVEC(INCR_UVEC(in_data, c_empty_offset)(c_max_empty_w-1 DOWNTO 0), c_dp_stream_empty_w) WHEN in_eop = '1' ELSE (OTHERS => '0'); + in_channel <= RESIZE_UVEC(INCR_UVEC(in_data, c_channel_offset)(c_max_channel_w-1 DOWNTO 0), c_dp_stream_channel_w); + + -- Stimuli control + proc_dp_count_en(rst, clk, sync, lfsr1, state, verify_done, tb_end, cnt_en); + proc_dp_out_ready(rst, clk, sync, lfsr2, out_ready); + + -- Output verify + proc_dp_verify_en(c_verify_en_wait, rst, clk, sync, verify_en); + proc_dp_verify_data("out_data", g_dp_rl, clk, verify_en, out_ready, out_val, out_data, prev_out_data); + proc_dp_verify_valid(g_dp_rl, clk, verify_en, out_ready, prev_out_ready, out_val); + proc_dp_verify_ctrl(c_tx_offset_sop, c_tx_period_sop, "sop", clk, verify_en, out_data, out_val, out_sop); + proc_dp_verify_ctrl(c_tx_offset_eop, c_tx_period_eop, "eop", clk, verify_en, out_data, out_val, out_eop); + proc_dp_verify_sop_and_eop(g_dp_rl, FALSE, clk, out_ready, out_val, out_sop, out_eop, hold_out_sop); -- Verify that sop and eop come in pairs, no check on valid between eop and sop + + exp_empty <= RESIZE_UVEC(INCR_UVEC(out_data, c_empty_offset)(c_max_empty_w-1 DOWNTO 0), c_dp_empty_w) WHEN out_eop = '1' ELSE (OTHERS => '0'); + exp_channel <= RESIZE_UVEC(INCR_UVEC(out_data, c_channel_offset)(c_max_channel_w-1 DOWNTO 0), c_dp_channel_w); + proc_dp_verify_other_sosi("empty", exp_empty, clk, out_val, out_empty); + proc_dp_verify_other_sosi("channel", exp_channel, clk, verify_en, out_channel); + + -- Check that the test has ran at all + proc_dp_verify_value(e_equal, clk, verify_done, exp_data, out_data); + + ------------------------------------------------------------------------------ + -- DUT + ------------------------------------------------------------------------------ + -- map sl, slv to record + in_ready <= dut_siso.ready; -- SISO + dut_sosi.data(c_dp_data_w-1 DOWNTO 0) <= in_data; -- SOSI + dut_sosi.empty <= in_empty; + dut_sosi.channel <= in_channel; + dut_sosi.sync <= in_sync; + dut_sosi.valid <= in_val; + dut_sosi.sop <= in_sop; + dut_sosi.eop <= in_eop; + + dut : ENTITY work.axi4_stream_dp_bridge + GENERIC MAP ( + g_use_empty => TRUE, + g_axi4_rl => g_axi4_rl, + g_dp_rl => g_dp_rl + ) + PORT MAP ( + in_rst => rst, + in_clk => clk, + -- ST sink + dp_in_siso => dut_siso, + dp_in_sosi => dut_sosi, + -- ST source + dp_out_siso => dut_out_siso, + dp_out_sosi => dut_out_sosi, + -- AXI4 Loopback + axi4_in_sosi => dut_axi4_sosi, + axi4_in_siso => dut_axi4_siso, + axi4_out_sosi => dut_axi4_sosi, + axi4_out_siso => dut_axi4_siso + ); + + -- map record to sl, slv + dut_out_siso.ready <= out_ready; -- SISO + out_data <= dut_out_sosi.data(c_dp_data_w-1 DOWNTO 0); -- SOSI + out_empty(c_dp_empty_w-1 DOWNTO 0) <= dut_out_sosi.empty; + out_channel(c_dp_channel_w-1 DOWNTO 0) <= dut_out_sosi.channel; + out_sync <= dut_out_sosi.sync; + out_val <= dut_out_sosi.valid; + out_sop <= dut_out_sosi.sop; + out_eop <= dut_out_sosi.eop; + +END tb; diff --git a/libraries/base/axi4/tb/vhdl/tb_tb_axi4_stream_dp_bridge.vhd b/libraries/base/axi4/tb/vhdl/tb_tb_axi4_stream_dp_bridge.vhd new file mode 100644 index 0000000000000000000000000000000000000000..6440bb538e6db6b943966fc2b7fd140953b037c9 --- /dev/null +++ b/libraries/base/axi4/tb/vhdl/tb_tb_axi4_stream_dp_bridge.vhd @@ -0,0 +1,50 @@ +-- -------------------------------------------------------------------------- +-- 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: +-- . Multi-TB for axi4_stream_dp_bridge. +-- Description: +-- . Tests multiple instances of tb_axi4_stream_dp_bridge with different +-- ready-latency configurations. + +LIBRARY IEEE; +USE IEEE.std_logic_1164.ALL; + + +ENTITY tb_tb_axi4_stream_dp_bridge IS +END tb_tb_axi4_stream_dp_bridge; + + +ARCHITECTURE tb OF tb_tb_axi4_stream_dp_bridge IS + SIGNAL tb_end : STD_LOGIC := '0'; -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end' +BEGIN + + -- > as 2 + -- > run -all --> OK + + -- g_dp_rl : NATURAL := 1; + -- g_axi4_rl : NATURAL := 0; + + u_dp_1_axi4_0 : ENTITY work.tb_axi4_stream_dp_bridge GENERIC MAP (1, 0); + u_dp_1_axi4_1 : ENTITY work.tb_axi4_stream_dp_bridge GENERIC MAP (1, 1); + u_dp_0_axi4_0 : ENTITY work.tb_axi4_stream_dp_bridge GENERIC MAP (0, 0); + u_dp_0_axi4_1 : ENTITY work.tb_axi4_stream_dp_bridge GENERIC MAP (0, 1); + +END tb;