Skip to content
Snippets Groups Projects
Commit 64c69e63 authored by Reinier van der Walle's avatar Reinier van der Walle
Browse files

Merge branch 'L2SDP-210' into 'master'

Resolve L2SDP-210

Closes L2SDP-210

See merge request desp/hdl!190
parents 87cdfe9f 73d815ce
No related branches found
No related tags found
1 merge request!190Resolve L2SDP-210
Pipeline #23669 passed
...@@ -57,6 +57,7 @@ PACKAGE common_str_pkg IS ...@@ -57,6 +57,7 @@ PACKAGE common_str_pkg IS
FUNCTION real_to_str(re: REAL; width : INTEGER; digits : INTEGER) RETURN STRING; FUNCTION real_to_str(re: REAL; width : INTEGER; digits : INTEGER) RETURN STRING;
PROCEDURE print_str(str : STRING); PROCEDURE print_str(str : STRING);
PROCEDURE print_str(str: STRING; enable: BOOLEAN);
FUNCTION str_to_ascii_integer_arr(s: STRING) RETURN t_integer_arr; FUNCTION str_to_ascii_integer_arr(s: STRING) RETURN t_integer_arr;
FUNCTION str_to_ascii_slv_8_arr( s: STRING) RETURN t_slv_8_arr; FUNCTION str_to_ascii_slv_8_arr( s: STRING) RETURN t_slv_8_arr;
...@@ -262,13 +263,21 @@ PACKAGE BODY common_str_pkg IS ...@@ -262,13 +263,21 @@ PACKAGE BODY common_str_pkg IS
END; END;
PROCEDURE print_str(str: STRING) IS PROCEDURE print_str(str: STRING) IS
VARIABLE v_line: LINE; VARIABLE v_line: LINE;
BEGIN BEGIN
write(v_line, str); write(v_line, str);
writeline(output, v_line); writeline(output, v_line);
deallocate(v_line); deallocate(v_line);
END; END;
PROCEDURE print_str(str: STRING; enable: BOOLEAN) IS
VARIABLE v_line: LINE;
BEGIN
IF enable THEN
print_str(str);
END IF;
END;
FUNCTION str_to_ascii_integer_arr(s: STRING) RETURN t_integer_arr IS FUNCTION str_to_ascii_integer_arr(s: STRING) RETURN t_integer_arr IS
VARIABLE r: t_integer_arr(0 TO s'RIGHT-1); VARIABLE r: t_integer_arr(0 TO s'RIGHT-1);
BEGIN BEGIN
......
...@@ -347,6 +347,7 @@ test_bench_files = ...@@ -347,6 +347,7 @@ test_bench_files =
tb/vhdl/tb_tb_tb_dp_backpressure.vhd tb/vhdl/tb_tb_tb_dp_backpressure.vhd
tb/vhdl/tb_dp_offload_tx_v3.vhd tb/vhdl/tb_dp_offload_tx_v3.vhd
tb/vhdl/tb_tb_dp_offload_tx_v3.vhd
tb/vhdl/tb_dp_offload_rx_filter.vhd tb/vhdl/tb_dp_offload_rx_filter.vhd
tb/vhdl/tb_dp_selector_arr.vhd tb/vhdl/tb_dp_selector_arr.vhd
tb/vhdl/tb_mms_dp_scale.vhd tb/vhdl/tb_mms_dp_scale.vhd
...@@ -416,6 +417,7 @@ regression_test_vhdl = ...@@ -416,6 +417,7 @@ regression_test_vhdl =
tb/vhdl/tb_tb_dp_xonoff.vhd tb/vhdl/tb_tb_dp_xonoff.vhd
tb/vhdl/tb_dp_selector_arr.vhd tb/vhdl/tb_dp_selector_arr.vhd
tb/vhdl/tb_mms_dp_scale.vhd tb/vhdl/tb_mms_dp_scale.vhd
tb/vhdl/tb_tb_dp_offload_tx_v3.vhd
[modelsim_project_file] [modelsim_project_file]
......
...@@ -19,6 +19,27 @@ ...@@ -19,6 +19,27 @@
-- --
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Author: D. van der Schuur, E. Kooistra
-- Purpose:
-- . Split a user-defined header from a data block e.g. to from an Eth frame
-- Description:
-- . The dp_offload_tx_v3 --> dp_offload_rx form a pair.
-- . The header and data block can be split at symbol level, to support a header
-- that does not have an integer number of g_data_w. The header length is
-- defined by c_nof_header_symbols.
-- . The header contents can be monitored and is defined by g_hdr_field_arr.
-- . The data block is output via src_out_arr. If the data block contains an
-- integer number of g_data_w words, then src_out_arr.empty = 0.
-- . Default the g_symbol_w = 0 which implies g_symbol_w = g_data_w.
-- - If the header length is not an integer number of g_data_w, then
-- g_symbol_w has to be set such that c_nof_symbols_per_data and
-- c_nof_header_symbols are both integers. The g_symbol_w is then used
-- to control the split.
-- - If the data block length is not an integer number of g_data_w, then
-- g_symbol_w defines the symbol width of the data and is used to
-- control the snk_out_arr empty field.
LIBRARY IEEE, common_lib, dp_lib; LIBRARY IEEE, common_lib, dp_lib;
USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL; USE IEEE.NUMERIC_STD.ALL;
...@@ -31,6 +52,7 @@ ENTITY dp_offload_rx IS ...@@ -31,6 +52,7 @@ ENTITY dp_offload_rx IS
GENERIC ( GENERIC (
g_nof_streams : NATURAL; g_nof_streams : NATURAL;
g_data_w : NATURAL; g_data_w : NATURAL;
g_symbol_w : NATURAL := 0; -- default 0 yields g_symbol_w = g_data_w
g_hdr_field_arr : t_common_field_arr; g_hdr_field_arr : t_common_field_arr;
g_remove_crc : BOOLEAN := FALSE; g_remove_crc : BOOLEAN := FALSE;
g_crc_nof_words : NATURAL := 0 g_crc_nof_words : NATURAL := 0
...@@ -59,7 +81,9 @@ END dp_offload_rx; ...@@ -59,7 +81,9 @@ END dp_offload_rx;
ARCHITECTURE str OF dp_offload_rx IS ARCHITECTURE str OF dp_offload_rx IS
CONSTANT c_nof_header_words : NATURAL := field_slv_len(g_hdr_field_arr) / g_data_w; CONSTANT c_symbol_w : NATURAL := sel_a_b(g_symbol_w = 0, g_data_w, g_symbol_w);
CONSTANT c_nof_symbols_per_data : NATURAL := g_data_w / g_symbol_w;
CONSTANT c_nof_header_symbols : NATURAL := field_slv_len(g_hdr_field_arr) / c_symbol_w;
CONSTANT c_field_sel : STD_LOGIC_VECTOR(g_hdr_field_arr'RANGE) := (OTHERS=>'0');-- Not used in sink mode but requires set range CONSTANT c_field_sel : STD_LOGIC_VECTOR(g_hdr_field_arr'RANGE) := (OTHERS=>'0');-- Not used in sink mode but requires set range
...@@ -92,8 +116,8 @@ BEGIN ...@@ -92,8 +116,8 @@ BEGIN
u_dp_split : ENTITY work.dp_split u_dp_split : ENTITY work.dp_split
GENERIC MAP ( GENERIC MAP (
g_data_w => g_data_w, g_data_w => g_data_w,
g_symbol_w => g_data_w, g_symbol_w => c_symbol_w,
g_nof_symbols => c_nof_header_words g_nof_symbols => c_nof_header_symbols
) )
PORT MAP ( PORT MAP (
rst => dp_rst, rst => dp_rst,
...@@ -117,10 +141,12 @@ BEGIN ...@@ -117,10 +141,12 @@ BEGIN
gen_dp_field_blk : FOR i IN 0 TO g_nof_streams-1 GENERATE gen_dp_field_blk : FOR i IN 0 TO g_nof_streams-1 GENERATE
u_dp_field_blk : ENTITY work.dp_field_blk u_dp_field_blk : ENTITY work.dp_field_blk
GENERIC MAP ( GENERIC MAP (
g_field_arr => field_arr_set_mode(g_hdr_field_arr , "RO"), g_field_arr => field_arr_set_mode(g_hdr_field_arr , "RO"),
g_field_sel => c_field_sel, g_field_sel => c_field_sel,
g_snk_data_w => c_dp_field_blk_snk_data_w, --g_data_w, g_snk_data_w => c_dp_field_blk_snk_data_w, --g_data_w,
g_src_data_w => c_dp_field_blk_src_data_w --field_slv_in_len(field_arr_set_mode(g_hdr_field_arr , "RO")) g_src_data_w => c_dp_field_blk_src_data_w, --field_slv_in_len(field_arr_set_mode(g_hdr_field_arr , "RO"))
g_in_symbol_w => c_symbol_w,
g_out_symbol_w => c_symbol_w
) )
PORT MAP ( PORT MAP (
dp_rst => dp_rst, dp_rst => dp_rst,
...@@ -152,7 +178,7 @@ BEGIN ...@@ -152,7 +178,7 @@ BEGIN
u_dp_tail_remove : ENTITY work.dp_tail_remove u_dp_tail_remove : ENTITY work.dp_tail_remove
GENERIC MAP ( GENERIC MAP (
g_data_w => g_data_w, g_data_w => g_data_w,
g_symbol_w => g_data_w, g_symbol_w => c_symbol_w,
g_nof_symbols => sel_a_b(g_remove_crc, g_crc_nof_words, 0) g_nof_symbols => sel_a_b(g_remove_crc, g_crc_nof_words, 0)
) )
PORT MAP ( PORT MAP (
......
...@@ -19,21 +19,25 @@ ...@@ -19,21 +19,25 @@
-- --
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Author: D. van der Schuur, E. Kooistra
-- Purpose: -- Purpose:
-- . Concatenate a user-defined header to a DP frame e.g. to create an Ethernet frame -- . Concatenate a user-defined header to a data block e.g. to create an Eth
-- frame
-- Description: -- Description:
-- . The header contents can be controlled dynamically by data path or MM control (selected by g_hdr_field_sel) -- . The dp_offload_tx_v3 --> dp_offload_rx form a pair.
-- . The header and data can be concatened at symbol level. The g_symbol_w defines the -- . The header contents can be controlled dynamically by data path or MM
-- resolution of the empty field. The g_data_w must be an integer multiple of the -- control (selected by g_hdr_field_sel)
-- g_symbol_w. If the empty field is not used or if the empty field is always 0 then -- . The header and data can be concatened at symbol level. The g_symbol_w
-- set g_symbol_w = g_data_w. -- defines the resolution of the empty field. The g_data_w must be an
-- . For example to concat header and data for an Ethernet frame use: -- integer multiple of the g_symbol_w. If the empty field is not used or
-- if the empty field is always 0 then set g_symbol_w = g_data_w.
-- . For example to concat header and data block for an Ethernet frame use:
-- - g_data_w = 32 (1GbE) or 64 (10GbE) -- - g_data_w = 32 (1GbE) or 64 (10GbE)
-- - g_symbol_w = c_byte_w = 8 if either the header or the data can have an -- - g_symbol_w = c_byte_w = 8 if either the header or the data block can
-- non-zero empty field, so when they are not a multiple of 4 bytes -- have an non-zero empty field, so when they are not a multiple of
-- (= 32b) or 8 bytes (= 64b). -- 4 bytes (= 32b) or 8 bytes (= 64b).
-- g_symbol_w = g_data_w if the empty field is always 0, so the number of bits in -- g_symbol_w = g_data_w if the empty field is always 0, so the number of
-- the header and data are an integer number of g_data_w. -- bits in the header and data are an integer number of g_data_w.
LIBRARY IEEE, common_lib, technology_lib, mm_lib; LIBRARY IEEE, common_lib, technology_lib, mm_lib;
...@@ -50,7 +54,7 @@ ENTITY dp_offload_tx_v3 IS ...@@ -50,7 +54,7 @@ ENTITY dp_offload_tx_v3 IS
g_nof_streams : NATURAL; g_nof_streams : NATURAL;
g_data_w : NATURAL; g_data_w : NATURAL;
g_symbol_w : NATURAL; g_symbol_w : NATURAL;
g_hdr_field_arr : t_common_field_arr; -- User defined header fields g_hdr_field_arr : t_common_field_arr; -- User defined header fields
g_hdr_field_sel : STD_LOGIC_VECTOR; -- For each header field, select the source: 0=data path, 1=MM controlled g_hdr_field_sel : STD_LOGIC_VECTOR; -- For each header field, select the source: 0=data path, 1=MM controlled
g_pipeline_ready : BOOLEAN := FALSE g_pipeline_ready : BOOLEAN := FALSE
); );
......
...@@ -24,12 +24,29 @@ ...@@ -24,12 +24,29 @@
-- Description: -- Description:
-- u_tx u_rx -- u_tx u_rx
-- ___________________ ___________________ -- ___________________ ___________________
-- |dp_offload_tx_v3| |dp_offload_rx | -- |dp_offload_tx_v3 | |dp_offload_rx |
-- stimuli_src -->| |--->| |--> verify_snk -- stimuli_src -->| |--->| |--> verify_snk
-- | in out | | | in out | -- | in out | | | in out |
-- |___________________| | |___________________| -- |___________________| | |___________________|
-- | -- |
-- link_offload_sosi -- link_offload_sosi
--
-- The verification of the header - data block boundary is controlled via
-- g_symbol_w:
-- . g_symbol_w = g_data_w : boundary at g_data_w
-- . g_symbol_w < g_data_w : boundary at g_symbol_w, by reducing the number
-- of the header dp_bsn field by 1 symbol. If the c_bsn_w <= 32 then the
-- header MM interface only needs one MM word to read the header, so
-- therefore there are two sizes of c_expected_tx_hdr_word_arr_* and
-- c_expected_rx_hdr_word_arr_*.
--
-- Remarks:
-- . The g_flow_control_verify has to be e_active, otherwise the tb fails,
-- probably due to limitation in dp_offload_rx.vhd.
-- . It appears that the tx_hdr_word read values are the MM write values, so
-- read of value from logic fields (with MM override '0', e.g. dp_bsn,
-- eth_src_mac) is not supported.
--
-- Usage: -- Usage:
-- > as 10 -- > as 10
-- > run -all -- > run -all
...@@ -54,9 +71,10 @@ ENTITY tb_dp_offload_tx_v3 IS ...@@ -54,9 +71,10 @@ ENTITY tb_dp_offload_tx_v3 IS
-- general -- general
g_flow_control_stimuli : t_dp_flow_control_enum := e_pulse; -- always e_active, e_random or e_pulse flow control g_flow_control_stimuli : t_dp_flow_control_enum := e_pulse; -- always e_active, e_random or e_pulse flow control
g_flow_control_verify : t_dp_flow_control_enum := e_active; -- always e_active, e_random or e_pulse flow control g_flow_control_verify : t_dp_flow_control_enum := e_active; -- always e_active, e_random or e_pulse flow control
g_print_en : BOOLEAN := TRUE;
-- specific -- specific
g_data_w : NATURAL := 64; g_data_w : NATURAL := 64;
g_nof_repeat : NATURAL := 100; g_symbol_w : NATURAL := 32;
g_pkt_len : NATURAL := 240; g_pkt_len : NATURAL := 240;
g_pkt_gap : NATURAL := 16 g_pkt_gap : NATURAL := 16
); );
...@@ -68,6 +86,18 @@ ARCHITECTURE tb OF tb_dp_offload_tx_v3 IS ...@@ -68,6 +86,18 @@ ARCHITECTURE tb OF tb_dp_offload_tx_v3 IS
CONSTANT c_mm_clk_period : TIME := 1 ns; CONSTANT c_mm_clk_period : TIME := 1 ns;
CONSTANT c_dp_clk_period : TIME := 5 ns; CONSTANT c_dp_clk_period : TIME := 5 ns;
-- Simulate header / payload boundary at g_data_w boundary or at g_symbol_w
-- boundary with one empty symbol, by adapting the size of the header dp_bsn
-- field:
-- . If g_symbol_w = g_data_w then boundary is at g_data_w, so empty is 0.
-- . If g_symbol_w < g_data_w then boundary is at last symbol, so empty is 1.
CONSTANT c_nof_symbols_per_data : NATURAL := g_data_w / g_symbol_w;
CONSTANT c_nof_symbols_per_bsn : NATURAL := c_dp_stream_bsn_w / g_symbol_w; -- = 64 / g_symbol_w
CONSTANT c_bsn_w : NATURAL := sel_a_b(c_nof_symbols_per_data = 1,
g_symbol_w * c_nof_symbols_per_bsn,
g_symbol_w * (c_nof_symbols_per_bsn - 1));
CONSTANT c_use_shortened_header : BOOLEAN := c_bsn_w <= c_word_w;
-- dp_stream_stimuli -- dp_stream_stimuli
CONSTANT c_stimuli_pulse_active : NATURAL := 3; CONSTANT c_stimuli_pulse_active : NATURAL := 3;
CONSTANT c_stimuli_pulse_period : NATURAL := 4; CONSTANT c_stimuli_pulse_period : NATURAL := 4;
...@@ -87,48 +117,53 @@ ARCHITECTURE tb OF tb_dp_offload_tx_v3 IS ...@@ -87,48 +117,53 @@ ARCHITECTURE tb OF tb_dp_offload_tx_v3 IS
CONSTANT c_sync_period : NATURAL := 5; CONSTANT c_sync_period : NATURAL := 5;
CONSTANT c_sync_offset : NATURAL := 2; CONSTANT c_sync_offset : NATURAL := 2;
CONSTANT c_bsn_init : STD_LOGIC_VECTOR(c_dp_stream_bsn_w-1 DOWNTO 0) := TO_DP_BSN(0); CONSTANT c_bsn_init : STD_LOGIC_VECTOR(c_dp_stream_bsn_w-1 DOWNTO 0) := TO_DP_BSN(0);
CONSTANT c_nof_sync : NATURAL := 3;
CONSTANT c_nof_packets : NATURAL := c_sync_period * c_nof_sync;
CONSTANT c_hdr_len : NATURAL := 7; CONSTANT c_hdr_len : NATURAL := 7;
CONSTANT c_wait_last_evt : NATURAL := 100 + g_nof_repeat * c_hdr_len; CONSTANT c_wait_last_evt : NATURAL := 100 + c_nof_packets * c_hdr_len;
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Tx offload -- Tx offload
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- From apertif_udp_offload_pkg.vhd: -- From apertif_udp_offload_pkg.vhd:
CONSTANT c_udp_offload_nof_hdr_fields : NATURAL := 3+12+4+3; -- 22, 448b; 7 64b words CONSTANT c_udp_offload_nof_hdr_fields : NATURAL := 3+12+4+3; -- 22, 448b; 7 64b words
CONSTANT c_udp_offload_nof_hdr_words : NATURAL := 26; -- 23 single word + 3 double word = 26 32b words CONSTANT c_udp_offload_nof_hdr_words_default : NATURAL := 26; -- 23 single word + 3 double word = 26 32b words
CONSTANT c_udp_offload_nof_hdr_words_shortened : NATURAL := c_udp_offload_nof_hdr_words_default - 1;
CONSTANT c_udp_offload_nof_hdr_words : NATURAL := sel_a_b(c_use_shortened_header, c_udp_offload_nof_hdr_words_shortened, c_udp_offload_nof_hdr_words_default);
-- Notes: -- Notes:
-- . pre-calculated ip_header_checksum is valid only for UNB0, FN0 targeting IP 10.10.10.10 -- . pre-calculated ip_header_checksum is valid only for UNB0, FN0 targeting IP 10.10.10.10
-- . udp_total_length = 176 beamlets * 64b / 8b = 1408B + 14 DP bytes + 8 UDP bytes = 1430B -- . udp_total_length = 176 beamlets * 64b / 8b = 1408B + 14 DP bytes + 8 UDP bytes = 1430B
CONSTANT c_udp_offload_hdr_field_arr : t_common_field_arr(c_udp_offload_nof_hdr_fields-1 DOWNTO 0) := ( -- index CONSTANT c_udp_offload_hdr_field_arr : t_common_field_arr(c_udp_offload_nof_hdr_fields-1 DOWNTO 0) := ( -- index
( field_name_pad("eth_dst_mac" ), "RW", 48, field_default(x"001B214368AC") ), -- 21 ( field_name_pad("eth_dst_mac" ), "RW", 48, field_default(x"001B214368AC") ), -- 21
( field_name_pad("eth_src_mac" ), "RW", 48, field_default(x"0123456789AB") ), -- 20 ( field_name_pad("eth_src_mac" ), "RW", 48, field_default(x"0123456789AB") ), -- 20
( field_name_pad("eth_type" ), "RW", 16, field_default(x"0800") ), -- 19 ( field_name_pad("eth_type" ), "RW", 16, field_default(x"0800") ), -- 19
( field_name_pad("ip_version" ), "RW", 4, field_default(4) ), -- 18 ( field_name_pad("ip_version" ), "RW", 4, field_default(4) ), -- 18
( field_name_pad("ip_header_length" ), "RW", 4, field_default(5) ), -- 17 ( field_name_pad("ip_header_length" ), "RW", 4, field_default(5) ), -- 17
( field_name_pad("ip_services" ), "RW", 8, field_default(0) ), -- 16 ( field_name_pad("ip_services" ), "RW", 8, field_default(0) ), -- 16
( field_name_pad("ip_total_length" ), "RW", 16, field_default(1450) ), -- 15 ( field_name_pad("ip_total_length" ), "RW", 16, field_default(1450) ), -- 15
( field_name_pad("ip_identification" ), "RW", 16, field_default(0) ), -- 14 ( field_name_pad("ip_identification" ), "RW", 16, field_default(0) ), -- 14
( field_name_pad("ip_flags" ), "RW", 3, field_default(2) ), -- 13 ( field_name_pad("ip_flags" ), "RW", 3, field_default(2) ), -- 13
( field_name_pad("ip_fragment_offset" ), "RW", 13, field_default(0) ), -- 12 ( field_name_pad("ip_fragment_offset" ), "RW", 13, field_default(0) ), -- 12
( field_name_pad("ip_time_to_live" ), "RW", 8, field_default(127) ), -- 11 ( field_name_pad("ip_time_to_live" ), "RW", 8, field_default(127) ), -- 11
( field_name_pad("ip_protocol" ), "RW", 8, field_default(17) ), -- 10 ( field_name_pad("ip_protocol" ), "RW", 8, field_default(17) ), -- 10
( field_name_pad("ip_header_checksum" ), "RW", 16, field_default(29928) ), -- 9 ( field_name_pad("ip_header_checksum" ), "RW", 16, field_default(29928) ), -- 9
( field_name_pad("ip_src_addr" ), "RW", 32, field_default(x"C0A80009") ), -- 8 ( field_name_pad("ip_src_addr" ), "RW", 32, field_default(x"C0A80009") ), -- 8
( field_name_pad("ip_dst_addr" ), "RW", 32, field_default(x"C0A80001") ), -- 7 ( field_name_pad("ip_dst_addr" ), "RW", 32, field_default(x"C0A80001") ), -- 7
( field_name_pad("udp_src_port" ), "RW", 16, field_default(0) ), -- 6 ( field_name_pad("udp_src_port" ), "RW", 16, field_default(0) ), -- 6
( field_name_pad("udp_dst_port" ), "RW", 16, field_default(0) ), -- 5 ( field_name_pad("udp_dst_port" ), "RW", 16, field_default(0) ), -- 5
( field_name_pad("udp_total_length" ), "RW", 16, field_default(1430) ), -- 4 ( field_name_pad("udp_total_length" ), "RW", 16, field_default(1430) ), -- 4
( field_name_pad("udp_checksum" ), "RW", 16, field_default(0) ), -- 3 ( field_name_pad("udp_checksum" ), "RW", 16, field_default(0) ), -- 3
( field_name_pad("dp_reserved" ), "RW", 47, field_default(x"010203040506") ), -- 2 ( field_name_pad("dp_reserved" ), "RW", 47, field_default(x"010203040506") ), -- 2
( field_name_pad("dp_sync" ), "RW", 1, field_default(0) ), -- 1 ( field_name_pad("dp_sync" ), "RW", 1, field_default(0) ), -- 1
( field_name_pad("dp_bsn" ), "RW", 64, field_default(0) ) ); -- 0 ( field_name_pad("dp_bsn" ), "RW", c_bsn_w, field_default(0) ) ); -- 0
-- TX: Corresponding storage of c_udp_offload_hdr_field_arr in MM register words -- TX: Corresponding storage of c_udp_offload_hdr_field_arr in MM register words
-- . Note: It appears that the tx_hdr_word read values are the MM write values, so read of value from logic fields (with MM override '0', e.g. dp_bsn, eth_src_mac) is not supported. -- . Note: It appears that the tx_hdr_word read values are the MM write values, so read of value from logic fields (with MM override '0', e.g. dp_bsn, eth_src_mac) is not supported.
CONSTANT c_expected_tx_hdr_word_arr : t_slv_32_arr(0 TO c_udp_offload_nof_hdr_words-1) := ( -- word address CONSTANT c_expected_tx_hdr_word_arr_default : t_slv_32_arr(0 TO c_udp_offload_nof_hdr_words_default-1) := ( -- word address
X"00000000", -- 0 = dp_bsn[31:0] -- readback is MM value, not the logic value X"00000000", -- 0 = dp_bsn[31:0] -- readback is MM value, not the logic value
X"00000000", -- 1 = dp_bsn[63:32] X"00000000", -- 1 = dp_bsn[c_bsn_w-1:32]
X"00000000", -- 2 = dp_sync X"00000000", -- 2 = dp_sync
X"03040506", -- 3 = dp_reserved[31:0] X"03040506", -- 3 = dp_reserved[31:0]
X"00000102", -- 4 = dp_reserved[47:32] X"00000102", -- 4 = dp_reserved[47:32]
...@@ -154,11 +189,38 @@ ARCHITECTURE tb OF tb_dp_offload_tx_v3 IS ...@@ -154,11 +189,38 @@ ARCHITECTURE tb OF tb_dp_offload_tx_v3 IS
X"214368AC", -- 24 = eth_dst_mac[31:0] X"214368AC", -- 24 = eth_dst_mac[31:0]
X"0000001B"); -- 25 = eth_dst_mac[47:32] X"0000001B"); -- 25 = eth_dst_mac[47:32]
CONSTANT c_expected_tx_hdr_word_arr_shortened : t_slv_32_arr(0 TO c_udp_offload_nof_hdr_words_shortened-1) := ( -- word address
X"00000000", -- 0 = dp_bsn[31:0] -- readback is MM value, not the logic value
X"00000000", -- 1 = dp_sync
X"03040506", -- 2 = dp_reserved[31:0]
X"00000102", -- 3 = dp_reserved[47:32]
X"00000000", -- 4 = udp_checksum
X"00000596", -- 5 = udp_total_length
X"00000000", -- 6 = udp_dst_port
X"00000000", -- 7 = udp_src_port -- readback is MM value, not the logic value
X"C0A80001", -- 8 = ip_dst_addr
X"C0A80009", -- 9 = ip_src_addr
X"000074E8", -- 10 = ip_header_checksum
X"00000011", -- 11 = ip_protocol
X"0000007F", -- 12 = ip_time_to_live
X"00000000", -- 13 = ip_fragment_offset
X"00000002", -- 14 = ip_flags
X"00000000", -- 15 = ip_identification
X"000005AA", -- 16 = ip_total_length
X"00000000", -- 17 = ip_services
X"00000005", -- 18 = ip_header_length
X"00000004", -- 19 = ip_version
X"00000800", -- 20 = eth_type[15:0]
X"456789AB", -- 21 = eth_src_mac[31:0] -- readback is MM value, not the logic value
X"00000123", -- 22 = eth_src_mac[47:32]
X"214368AC", -- 23 = eth_dst_mac[31:0]
X"0000001B"); -- 24 = eth_dst_mac[47:32]
-- RX: Corresponding storage of c_udp_offload_hdr_field_arr in MM register words -- RX: Corresponding storage of c_udp_offload_hdr_field_arr in MM register words
CONSTANT c_expected_rx_hdr_word_arr : t_slv_32_arr(0 TO c_udp_offload_nof_hdr_words-1) := ( -- word address CONSTANT c_expected_rx_hdr_word_arr_default : t_slv_32_arr(0 TO c_udp_offload_nof_hdr_words_default-1) := ( -- word address
X"0000000B", -- 0 = dp_bsn[31:0] -- dynamic value obtained from simulation X"00000002", -- 0 = dp_bsn[31:0] -- dynamic value obtained from simulation
X"00000000", -- 1 = dp_bsn[63:32] X"00000000", -- 1 = dp_bsn[c_bsn_w-1:32]
X"00000000", -- 2 = dp_sync -- dynamic value obtained from simulation X"00000001", -- 2 = dp_sync -- dynamic value obtained from simulation
X"03040506", -- 3 = dp_reserved[31:0] X"03040506", -- 3 = dp_reserved[31:0]
X"00000102", -- 4 = dp_reserved[47:32] X"00000102", -- 4 = dp_reserved[47:32]
X"00000000", -- 5 = udp_checksum X"00000000", -- 5 = udp_checksum
...@@ -183,6 +245,33 @@ ARCHITECTURE tb OF tb_dp_offload_tx_v3 IS ...@@ -183,6 +245,33 @@ ARCHITECTURE tb OF tb_dp_offload_tx_v3 IS
X"214368AC", -- 24 = eth_dst_mac[31:0] X"214368AC", -- 24 = eth_dst_mac[31:0]
X"0000001B"); -- 25 = eth_dst_mac[47:32] X"0000001B"); -- 25 = eth_dst_mac[47:32]
CONSTANT c_expected_rx_hdr_word_arr_shortened : t_slv_32_arr(0 TO c_udp_offload_nof_hdr_words_shortened-1) := ( -- word address
X"00000002", -- 0 = dp_bsn[31:0] -- dynamic value obtained from simulation
X"00000001", -- 1 = dp_sync -- dynamic value obtained from simulation
X"03040506", -- 2 = dp_reserved[31:0]
X"00000102", -- 3 = dp_reserved[47:32]
X"00000000", -- 4 = udp_checksum
X"00000596", -- 5 = udp_total_length
X"00000000", -- 6 = udp_dst_port
X"00000000", -- 7 = udp_src_port
X"C0A80001", -- 8 = ip_dst_addr
X"C0A80009", -- 9 = ip_src_addr
X"000074E8", -- 10 = ip_header_checksum
X"00000011", -- 11 = ip_protocol
X"0000007F", -- 12 = ip_time_to_live
X"00000000", -- 13 = ip_fragment_offset
X"00000002", -- 14 = ip_flags
X"00000000", -- 15 = ip_identification
X"000005AA", -- 16 = ip_total_length
X"00000000", -- 17 = ip_services
X"00000005", -- 18 = ip_header_length
X"00000004", -- 19 = ip_version
X"00000800", -- 20 = eth_type[15:0]
X"86080000", -- 21 = eth_src_mac[31:0] -- readback is the logic value x"00228608" & id_backplane = 0 & id_chip = 0 (c_NODE_ID = 0)
X"00000022", -- 22 = eth_src_mac[47:32]
X"214368AC", -- 23 = eth_dst_mac[31:0]
X"0000001B"); -- 24 = eth_dst_mac[47:32]
-- From apertif_unb1_fn_beamformer_udp_offload.vhd: 221 111111111000 0000 000 -- From apertif_unb1_fn_beamformer_udp_offload.vhd: 221 111111111000 0000 000
-- Override ('1') only the Ethernet fields so we can use MM defaults there. 109 876543210987 6543 210 -- Override ('1') only the Ethernet fields so we can use MM defaults there. 109 876543210987 6543 210
CONSTANT c_hdr_field_ovr_init : STD_LOGIC_VECTOR(c_udp_offload_nof_hdr_fields-1 DOWNTO 0) := "101"&"111111111111"&"1111"&"100"; CONSTANT c_hdr_field_ovr_init : STD_LOGIC_VECTOR(c_udp_offload_nof_hdr_fields-1 DOWNTO 0) := "101"&"111111111111"&"1111"&"100";
...@@ -250,6 +339,7 @@ ARCHITECTURE tb OF tb_dp_offload_tx_v3 IS ...@@ -250,6 +339,7 @@ ARCHITECTURE tb OF tb_dp_offload_tx_v3 IS
SIGNAL verify_snk_out : t_dp_siso := c_dp_siso_rdy; SIGNAL verify_snk_out : t_dp_siso := c_dp_siso_rdy;
SIGNAL verify_snk_in : t_dp_sosi; SIGNAL verify_snk_in : t_dp_sosi;
SIGNAL verify_snk_in_data : STD_LOGIC_VECTOR(g_data_w-1 DOWNTO 0); SIGNAL verify_snk_in_data : STD_LOGIC_VECTOR(g_data_w-1 DOWNTO 0);
SIGNAL prev_verify_snk_in_data : STD_LOGIC_VECTOR(g_data_w-1 DOWNTO 0);
BEGIN BEGIN
...@@ -279,7 +369,7 @@ BEGIN ...@@ -279,7 +369,7 @@ BEGIN
g_bsn_init => c_bsn_init, g_bsn_init => c_bsn_init,
-- specific -- specific
g_in_dat_w => g_data_w, g_in_dat_w => g_data_w,
g_nof_repeat => g_nof_repeat, g_nof_repeat => c_nof_packets,
g_pkt_len => g_pkt_len, g_pkt_len => g_pkt_len,
g_pkt_gap => g_pkt_gap, g_pkt_gap => g_pkt_gap,
g_wait_last_evt => c_wait_last_evt g_wait_last_evt => c_wait_last_evt
...@@ -403,13 +493,13 @@ BEGIN ...@@ -403,13 +493,13 @@ BEGIN
tx_hdr_fields_in_arr(0)(field_hi(c_udp_offload_hdr_field_arr, "ip_src_addr" ) DOWNTO field_lo(c_udp_offload_hdr_field_arr, "ip_src_addr" )) <= x"0A63" & id_backplane & INCR_UVEC(id_chip, 1); tx_hdr_fields_in_arr(0)(field_hi(c_udp_offload_hdr_field_arr, "ip_src_addr" ) DOWNTO field_lo(c_udp_offload_hdr_field_arr, "ip_src_addr" )) <= x"0A63" & id_backplane & INCR_UVEC(id_chip, 1);
tx_hdr_fields_in_arr(0)(field_hi(c_udp_offload_hdr_field_arr, "dp_sync" ) DOWNTO field_lo(c_udp_offload_hdr_field_arr, "dp_sync" )) <= slv(dp_offload_tx_snk_in_arr(0).sync); tx_hdr_fields_in_arr(0)(field_hi(c_udp_offload_hdr_field_arr, "dp_sync" ) DOWNTO field_lo(c_udp_offload_hdr_field_arr, "dp_sync" )) <= slv(dp_offload_tx_snk_in_arr(0).sync);
tx_hdr_fields_in_arr(0)(field_hi(c_udp_offload_hdr_field_arr, "dp_bsn" ) DOWNTO field_lo(c_udp_offload_hdr_field_arr, "dp_bsn" )) <= dp_offload_tx_snk_in_arr(0).bsn(63 DOWNTO 0); tx_hdr_fields_in_arr(0)(field_hi(c_udp_offload_hdr_field_arr, "dp_bsn" ) DOWNTO field_lo(c_udp_offload_hdr_field_arr, "dp_bsn" )) <= dp_offload_tx_snk_in_arr(0).bsn(c_bsn_w-1 DOWNTO 0);
u_tx : ENTITY work.dp_offload_tx_v3 u_tx : ENTITY work.dp_offload_tx_v3
GENERIC MAP ( GENERIC MAP (
g_nof_streams => 1, g_nof_streams => 1,
g_data_w => g_data_w, g_data_w => g_data_w,
g_symbol_w => g_data_w, g_symbol_w => g_symbol_w,
g_hdr_field_arr => c_udp_offload_hdr_field_arr, g_hdr_field_arr => c_udp_offload_hdr_field_arr,
g_hdr_field_sel => c_hdr_field_ovr_init g_hdr_field_sel => c_hdr_field_ovr_init
) )
...@@ -438,20 +528,23 @@ BEGIN ...@@ -438,20 +528,23 @@ BEGIN
VARIABLE v_word : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0); VARIABLE v_word : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
BEGIN BEGIN
proc_common_wait_until_hi_lo(dp_clk, tx_offload_sosi_arr(0).sync); proc_common_wait_until_hi_lo(dp_clk, tx_offload_sosi_arr(0).sync);
proc_common_wait_until_hi_lo(dp_clk, tx_offload_sosi_arr(0).sync); print_str("", g_print_en);
print_str("");
FOR I IN 0 TO c_udp_offload_nof_hdr_words-1 LOOP FOR I IN 0 TO c_udp_offload_nof_hdr_words-1 LOOP
proc_mem_mm_bus_rd(I, mm_clk, reg_dp_offload_tx_hdr_dat_mosi); proc_mem_mm_bus_rd(I, mm_clk, reg_dp_offload_tx_hdr_dat_mosi);
proc_mem_mm_bus_rd_latency(c_mem_reg_rd_latency, mm_clk); proc_mem_mm_bus_rd_latency(c_mem_reg_rd_latency, mm_clk);
v_word := reg_dp_offload_tx_hdr_dat_miso.rddata(31 DOWNTO 0); v_word := reg_dp_offload_tx_hdr_dat_miso.rddata(31 DOWNTO 0);
-- Log word in transcript window -- Log word in transcript window
print_str("tx_hdr_word(" & int_to_str(I) & ") = " & slv_to_hex(v_word)); print_str("tx_hdr_word(" & int_to_str(I) & ") = " & slv_to_hex(v_word), g_print_en);
-- View word in wave window -- View word in wave window
tx_hdr_word <= v_word; tx_hdr_word <= v_word;
-- Verify expected word -- Verify expected word
ASSERT c_expected_tx_hdr_word_arr(I) = v_word REPORT "Unexpected tx_hdr_word at address " & int_to_str(I) & ", expected " & slv_to_hex(c_expected_tx_hdr_word_arr(I)) SEVERITY ERROR; IF c_use_shortened_header THEN
ASSERT c_expected_tx_hdr_word_arr_shortened(I) = v_word REPORT "Unexpected tx_hdr_word at address " & int_to_str(I) & ", expected " & slv_to_hex(c_expected_tx_hdr_word_arr_shortened(I)) SEVERITY ERROR;
ELSE
ASSERT c_expected_tx_hdr_word_arr_default(I) = v_word REPORT "Unexpected tx_hdr_word at address " & int_to_str(I) & ", expected " & slv_to_hex(c_expected_tx_hdr_word_arr_default(I)) SEVERITY ERROR;
END IF;
END LOOP; END LOOP;
print_str(""); print_str("", g_print_en);
WAIT; WAIT;
END PROCESS; END PROCESS;
...@@ -481,6 +574,7 @@ BEGIN ...@@ -481,6 +574,7 @@ BEGIN
GENERIC MAP ( GENERIC MAP (
g_nof_streams => 1, g_nof_streams => 1,
g_data_w => g_data_w, g_data_w => g_data_w,
g_symbol_w => g_symbol_w,
g_hdr_field_arr => c_udp_offload_hdr_field_arr, g_hdr_field_arr => c_udp_offload_hdr_field_arr,
g_remove_crc => FALSE, g_remove_crc => FALSE,
g_crc_nof_words => 0 g_crc_nof_words => 0
...@@ -520,24 +614,69 @@ BEGIN ...@@ -520,24 +614,69 @@ BEGIN
VARIABLE v_word : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0); VARIABLE v_word : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
BEGIN BEGIN
proc_common_wait_until_hi_lo(dp_clk, verify_snk_in.sync); proc_common_wait_until_hi_lo(dp_clk, verify_snk_in.sync);
proc_common_wait_until_hi_lo(dp_clk, verify_snk_in.sync); -- Check first packet after sync with dp_sync = 1
proc_common_wait_until_hi_lo(dp_clk, verify_snk_in.sync); -- wait some latency until header fields of this sync packet are available via MM
print_str(""); proc_common_wait_some_cycles(dp_clk, 10);
print_str("", g_print_en);
FOR I IN 0 TO c_udp_offload_nof_hdr_words-1 LOOP FOR I IN 0 TO c_udp_offload_nof_hdr_words-1 LOOP
proc_mem_mm_bus_rd(I, mm_clk, reg_dp_offload_rx_hdr_dat_mosi); proc_mem_mm_bus_rd(I, mm_clk, reg_dp_offload_rx_hdr_dat_mosi);
proc_mem_mm_bus_rd_latency(c_mem_reg_rd_latency, mm_clk); proc_mem_mm_bus_rd_latency(c_mem_reg_rd_latency, mm_clk);
v_word := reg_dp_offload_rx_hdr_dat_miso.rddata(31 DOWNTO 0); v_word := reg_dp_offload_rx_hdr_dat_miso.rddata(31 DOWNTO 0);
-- Log word in transcript window -- Log word in transcript window
print_str("rx_hdr_word(" & int_to_str(I) & ") : " & slv_to_hex(v_word)); print_str("rx_hdr_word(" & int_to_str(I) & ") : " & slv_to_hex(v_word), g_print_en);
-- View word in wave window -- View word in wave window
rx_hdr_word <= v_word; rx_hdr_word <= v_word;
-- Verify expected word -- Verify expected word
ASSERT c_expected_rx_hdr_word_arr(I) = v_word REPORT "Unexpected rx_hdr_word at address " & int_to_str(I) & ", expected " & slv_to_hex(c_expected_rx_hdr_word_arr(I)) SEVERITY ERROR; IF c_use_shortened_header THEN
ASSERT v_word = c_expected_rx_hdr_word_arr_shortened(I) REPORT "Unexpected rx_hdr_word at address " & int_to_str(I) & ", expected " & slv_to_hex(c_expected_rx_hdr_word_arr_shortened(I)) SEVERITY ERROR;
ELSE
ASSERT v_word = c_expected_rx_hdr_word_arr_default(I) REPORT "Unexpected rx_hdr_word at address " & int_to_str(I) & ", expected " & slv_to_hex(c_expected_rx_hdr_word_arr_default(I)) SEVERITY ERROR;
END IF;
END LOOP; END LOOP;
print_str(""); print_str("", g_print_en);
-- Check dp_bsn and dp_sync of second packet after sync with dp_sync = 0
proc_common_wait_until_hi_lo(dp_clk, verify_snk_in.sop);
-- wait some latency until header fields of this sync packet are available via MM
proc_common_wait_some_cycles(dp_clk, 10);
-- dp_bsn lo
proc_mem_mm_bus_rd(0, mm_clk, reg_dp_offload_rx_hdr_dat_mosi);
proc_mem_mm_bus_rd_latency(c_mem_reg_rd_latency, mm_clk);
v_word := reg_dp_offload_rx_hdr_dat_miso.rddata(31 DOWNTO 0);
rx_hdr_word <= v_word; -- View word in wave window
IF c_use_shortened_header THEN
ASSERT v_word = INCR_UVEC(c_expected_rx_hdr_word_arr_shortened(0), 1) REPORT "Unexpected dp_bsn from MM" SEVERITY ERROR;
ELSE
ASSERT v_word = INCR_UVEC(c_expected_rx_hdr_word_arr_default(0), 1) REPORT "Unexpected dp_bsn from MM" SEVERITY ERROR;
END IF;
-- dp_sync
IF c_use_shortened_header THEN
proc_mem_mm_bus_rd(1, mm_clk, reg_dp_offload_rx_hdr_dat_mosi);
ELSE
proc_mem_mm_bus_rd(2, mm_clk, reg_dp_offload_rx_hdr_dat_mosi);
END IF;
proc_mem_mm_bus_rd_latency(c_mem_reg_rd_latency, mm_clk);
v_word := reg_dp_offload_rx_hdr_dat_miso.rddata(31 DOWNTO 0);
rx_hdr_word <= v_word; -- View word in wave window
ASSERT v_word = TO_UVEC(0, 32) REPORT "Unexpected dp_sync from MM" SEVERITY ERROR;
WAIT; WAIT;
END PROCESS; END PROCESS;
p_verify_snk_in_data : PROCESS
BEGIN
-- Note: This verification overlaps with u_dp_stream_verify because that also verifies incrementing valid data.
WAIT UNTIL rising_edge(dp_clk);
prev_verify_snk_in_data <= verify_snk_in.data(g_data_w-1 DOWNTO 0);
IF verify_snk_in.sop = '1' THEN
ASSERT TO_UINT(verify_snk_in.data) MOD g_pkt_len = 0 REPORT "Wrong decoded data at sop." SEVERITY ERROR;
ELSIF verify_snk_in.eop = '1' THEN
ASSERT TO_UINT(verify_snk_in.data) MOD g_pkt_len = g_pkt_len - 1 REPORT "Wrong decoded data at eop." SEVERITY ERROR;
ELSIF verify_snk_in.valid = '1' THEN
ASSERT TO_UINT(verify_snk_in.data) = TO_UINT(prev_verify_snk_in_data) + 1 REPORT "Wrong decoded data at valid." SEVERITY ERROR;
END IF;
END PROCESS;
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
-- Auxiliary -- Auxiliary
......
-- --------------------------------------------------------------------------
-- Copyright 2021
-- 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: E. Kooistra, Jan 2022
-- Purpose: Regression multi tb for dp_offload_tx_v3 and dp_offload_rx
-- Description:
-- Usage:
-- > as 5
-- > run -all
LIBRARY IEEE;
USE IEEE.std_logic_1164.ALL;
USE work.tb_dp_pkg.ALL; -- for t_dp_flow_control_enum
ENTITY tb_tb_dp_offload_tx_v3 IS
END tb_tb_dp_offload_tx_v3;
ARCHITECTURE tb OF tb_tb_dp_offload_tx_v3 IS
SIGNAL tb_end : STD_LOGIC := '0'; -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end'
BEGIN
-- -- general
-- g_flow_control_stimuli : t_dp_flow_control_enum := e_pulse; -- always e_active, e_random or e_pulse flow control
-- g_flow_control_verify : t_dp_flow_control_enum := e_active; -- always e_active, e_random or e_pulse flow control
-- g_print_en : BOOLEAN := TRUE;
-- -- specific
-- g_data_w : NATURAL := 64;
-- g_symbol_w : NATURAL := 16;
-- g_pkt_len : NATURAL := 240;
-- g_pkt_gap : NATURAL := 16
u_pls_act_data_w_64 : ENTITY work.tb_dp_offload_tx_v3 GENERIC MAP (e_pulse, e_active, FALSE, 64, 64, 240, 16);
u_pls_act_data_w_64_no_gap : ENTITY work.tb_dp_offload_tx_v3 GENERIC MAP (e_pulse, e_active, FALSE, 64, 64, 240, 0);
u_rnd_act_data_w_64 : ENTITY work.tb_dp_offload_tx_v3 GENERIC MAP (e_random, e_active, FALSE, 64, 64, 240, 16);
u_rnd_act_data_w_32 : ENTITY work.tb_dp_offload_tx_v3 GENERIC MAP (e_random, e_active, FALSE, 32, 32, 240, 16);
--u_act_rnd_data_w : ENTITY work.tb_dp_offload_tx_v3 GENERIC MAP (e_active, e_random, FALSE, 64, 64, 240, 16);
u_rnd_act_data_64_symbol_16 : ENTITY work.tb_dp_offload_tx_v3 GENERIC MAP (e_random, e_active, FALSE, 64, 16, 240, 16);
u_rnd_act_data_64_symbol_32 : ENTITY work.tb_dp_offload_tx_v3 GENERIC MAP (e_random, e_active, FALSE, 64, 32, 240, 16);
u_rnd_act_data_32_symbol_8 : ENTITY work.tb_dp_offload_tx_v3 GENERIC MAP (e_random, e_active, FALSE, 32, 8, 240, 16);
u_rnd_act_data_32_symbol_16 : ENTITY work.tb_dp_offload_tx_v3 GENERIC MAP (e_random, e_active, FALSE, 32, 16, 240, 16);
END tb;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment