From 0cc8a90ab7cbe89c01383ad24bdc27067f934274 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Wed, 12 Aug 2026 14:33:04 +0200 Subject: [PATCH] Improved VC behavior on AXI stream slave. - Reset will asynchronously abort transmission and reception of active transactions. -Transactions in the VC inboxes can either be flushed or left unaffected as controlled by the reset_policy parameter to the new master/slave functions. Note that inbox messages not related to stream transactions, for example VC configuration messages, are unaffected. --- docs/news.d/1240.breaking.rst | 6 + vunit/vhdl/verification_components/run.py | 51 +++-- .../src/axi_stream_master.vhd | 52 ++++- .../src/axi_stream_pkg.vhd | 27 ++- .../src/axi_stream_slave.vhd | 194 +++++++++++------- .../test/tb_axi_stream.vhd | 91 +++++++- 6 files changed, 300 insertions(+), 121 deletions(-) create mode 100644 docs/news.d/1240.breaking.rst diff --git a/docs/news.d/1240.breaking.rst b/docs/news.d/1240.breaking.rst new file mode 100644 index 000000000..daf9ebfea --- /dev/null +++ b/docs/news.d/1240.breaking.rst @@ -0,0 +1,6 @@ +Improved behavior of AXI stream verification components on AXI stream reset: +- Any active transaction (`push`, `pop`, `check`, `wait_for_time`) is aborted asynchronously rather than synchronously. +- Pending messages in the VC inbox that relates to transactions on the stream can be either + left unaffected or be cancelled. This is controlled by the `reset_policy` parameter when calling + the VC `new` function. +- VC inbox messages related to VC configuration are never affected by the reset. diff --git a/vunit/vhdl/verification_components/run.py b/vunit/vhdl/verification_components/run.py index 3758b618c..6dc8c5e9d 100644 --- a/vunit/vhdl/verification_components/run.py +++ b/vunit/vhdl/verification_components/run.py @@ -8,15 +8,15 @@ from itertools import product from vunit import VUnit -ROOT = Path(__file__).parent +root = Path(__file__).parent -UI = VUnit.from_argv() -UI.add_vhdl_builtins() -UI.add_random() -UI.add_verification_components() +ui = VUnit.from_argv() +ui.add_vhdl_builtins() +ui.add_random() +ui.add_verification_components() -LIB = UI.library("vunit_lib") -LIB.add_source_files(ROOT / "test" / "*.vhd") +lib = ui.library("vunit_lib") +lib.add_source_files(root / "test" / "*.vhd") def encode(tb_cfg): @@ -70,12 +70,12 @@ def gen_avalon_master_tests(obj, *args): obj.add_config(name=config_name, generics=dict(encoded_tb_cfg=encode(tb_cfg))) -tb_avalon_slave = LIB.test_bench("tb_avalon_slave") +tb_avalon_slave = lib.test_bench("tb_avalon_slave") for test in tb_avalon_slave.get_tests(): gen_avalon_tests(test, [32], [1, 2, 64], [1.0, 0.3], [0.0, 0.4]) -tb_avalon_master = LIB.test_bench("tb_avalon_master") +tb_avalon_master = lib.test_bench("tb_avalon_master") for test in tb_avalon_master.get_tests(): if test.name == "wr single rd single": @@ -83,9 +83,9 @@ def gen_avalon_master_tests(obj, *args): else: gen_avalon_master_tests(test, [64], [1.0, 0.3], [0.0, 0.7], [1.0, 0.3], [1.0, 0.3]) -TB_WISHBONE_SLAVE = LIB.test_bench("tb_wishbone_slave") +tb_wishbone_slave = lib.test_bench("tb_wishbone_slave") -for test in TB_WISHBONE_SLAVE.get_tests(): +for test in tb_wishbone_slave.get_tests(): # TODO strobe_prob not implemented in slave tb gen_wb_tests( test, @@ -100,9 +100,9 @@ def gen_avalon_master_tests(obj, *args): ) -TB_WISHBONE_MASTER = LIB.test_bench("tb_wishbone_master") +tb_wishbone_master = lib.test_bench("tb_wishbone_master") -for test in TB_WISHBONE_MASTER.get_tests(): +for test in tb_wishbone_master.get_tests(): if test.name == "slave comb ack": gen_wb_tests( test, @@ -129,13 +129,13 @@ def gen_avalon_master_tests(obj, *args): ) -TB_AXI_STREAM = LIB.test_bench("tb_axi_stream") +tb_axi_stream = lib.test_bench("tb_axi_stream") for id_length in [0, 8]: for dest_length in [0, 8]: for user_length in [0, 8]: for data_length in [8, 16]: - for test in TB_AXI_STREAM.get_tests("*check"): + for test in tb_axi_stream.get_tests("*check"): test.add_config( name=f"id_l={id_length} dest_l={dest_length} user_l={user_length} data_l={data_length}", generics=dict( @@ -146,22 +146,29 @@ def gen_avalon_master_tests(obj, *args): ), ) -TB_AXI_STREAM.test("test passing with no tkeep").set_generic("g_data_length", 16) +tb_axi_stream.test("test passing with no tkeep").set_generic("g_data_length", 16) -TB_AXI_STREAM_PROTOCOL_CHECKER = LIB.test_bench("tb_axi_stream_protocol_checker") +for reset_policy_value in [0, 1]: + tb_axi_stream.test("test reset of transactions").add_config( + name="abort_all_transactions" if reset_policy_value == 0 else "abort_active_transaction", + generics=dict(reset_policy_value=reset_policy_value), + ) + + +tb_axi_stream_protocol_checker = lib.test_bench("tb_axi_stream_protocol_checker") for data_length in [0, 8, 32]: - for test in TB_AXI_STREAM_PROTOCOL_CHECKER.get_tests("*passing*tdata*"): + for test in tb_axi_stream_protocol_checker.get_tests("*passing*tdata*"): test.add_config(name="data_length=%d" % data_length, generics=dict(data_length=data_length)) -for test in TB_AXI_STREAM_PROTOCOL_CHECKER.get_tests("*failing*tid width*"): +for test in tb_axi_stream_protocol_checker.get_tests("*failing*tid width*"): test.add_config(name="dest_length=25", generics=dict(dest_length=25)) test.add_config(name="id_length=8 dest_length=17", generics=dict(id_length=8, dest_length=17)) -TEST_FAILING_MAX_WAITS = TB_AXI_STREAM_PROTOCOL_CHECKER.test( +test_failing_max_waits = tb_axi_stream_protocol_checker.test( "Test failing check of that tready comes within max_waits after valid" ) for max_waits in [0, 8]: - TEST_FAILING_MAX_WAITS.add_config(name="max_waits=%d" % max_waits, generics=dict(max_waits=max_waits)) + test_failing_max_waits.add_config(name="max_waits=%d" % max_waits, generics=dict(max_waits=max_waits)) -UI.main() +ui.main() diff --git a/vunit/vhdl/verification_components/src/axi_stream_master.vhd b/vunit/vhdl/verification_components/src/axi_stream_master.vhd index 7c1e019ac..531cead8b 100644 --- a/vunit/vhdl/verification_components/src/axi_stream_master.vhd +++ b/vunit/vhdl/verification_components/src/axi_stream_master.vhd @@ -97,6 +97,7 @@ begin variable inactive_bus_policy : inactive_bus_policy_t; variable axi_stream_signal : axi_stream_signal_t; variable stall_config : integer_vector_ptr_t; + variable delay : delay_length; impure function get_inactive_axi_stream_policy(master : axi_stream_master_t) return inactive_axi_stream_policy_t is impure function to_inactive_axi_stream_policy(vec : integer_vector_ptr_t) return inactive_axi_stream_policy_t is @@ -140,6 +141,11 @@ begin return p_to_stall_config(to_integer_vector_ptr(get(master.p_config, p_stall_config_idx))); end; + impure function get_reset_policy(master : axi_stream_master_t) return axi_stream_reset_policy_t is + begin + return axi_stream_reset_policy_t'val(get(master.p_config, p_reset_policy_idx)); + end; + procedure drive_inactive( signal l_tdata : out std_logic_vector(data_length(master)-1 downto 0); signal l_tlast : out std_logic; @@ -192,6 +198,28 @@ begin drive_policy(l_tuser, inactive_axi_stream_policy(work.axi_stream_pkg.tuser)); end procedure; + procedure flush_pending_transactions(queue : queue_t) is + constant total_length : natural := length(queue); + variable consumed_length : natural := 0; + variable before_pop_length : natural; + variable msg : msg_t; + variable msg_type : msg_type_t; + begin + while consumed_length < total_length loop + before_pop_length := length(queue); + msg := pop(queue); + consumed_length := consumed_length + (before_pop_length - length(queue)); + + -- Messages to keep are pushed back into the queue. + msg_type := message_type(msg); + if msg_type = stream_push_msg or msg_type = push_axi_stream_msg or msg_type = wait_for_time_msg then + null; + else + push(message_queue, msg); + end if; + end loop; + end; + begin rnd.InitSeed(rnd'instance_name); loop @@ -199,20 +227,28 @@ begin if areset_n = '0' then tvalid <= '0'; wait until areset_n = '1' and rising_edge(aclk); + if get_reset_policy(master) = abort_all_transactions then + flush_pending_transactions(message_queue); + end if; else if is_empty(message_queue) then -- Wait for messages to arrive on the queue, posted by the process above - wait until (not is_empty(message_queue) or areset_n = '0') and rising_edge(aclk); + wait until areset_n = '0' or (not is_empty(message_queue) and rising_edge(aclk)); end if; - while not is_empty(message_queue) loop + while not is_empty(message_queue) and areset_n = '1' loop msg := pop(message_queue); msg_type := message_type(msg); if msg_type = wait_for_time_msg then - handle_sync_message(net, msg_type, msg); - -- Re-align with the clock when a wait for time message was handled, because this breaks edge alignment. - wait until rising_edge(aclk); + handle_message(msg_type); + delay := pop_time(msg); + wait until areset_n = '0' for delay; + + if areset_n /= '0' then + -- Re-align with the clock when a wait for time message was handled, because this breaks edge alignment. + wait until rising_edge(aclk); + end if; elsif msg_type = notify_request_msg then -- Ignore this message, but expect it @@ -238,7 +274,7 @@ begin tdest <= (others => '0'); tuser <= (others => '0'); end if; - wait until ((tvalid and tready) = '1' or areset_n = '0') and rising_edge(aclk); + wait until areset_n = '0' or ((tvalid and tready) = '1' and rising_edge(aclk)); tvalid <= '0'; elsif msg_type = set_inactive_axi_stream_policy_msg then @@ -271,7 +307,9 @@ begin delete(msg); end loop; - notify(bus_process_done); + if is_empty(message_queue) then + notify(bus_process_done); + end if; end if; end loop; end process; diff --git a/vunit/vhdl/verification_components/src/axi_stream_pkg.vhd b/vunit/vhdl/verification_components/src/axi_stream_pkg.vhd index ab74afe95..157ffadfc 100644 --- a/vunit/vhdl/verification_components/src/axi_stream_pkg.vhd +++ b/vunit/vhdl/verification_components/src/axi_stream_pkg.vhd @@ -39,14 +39,18 @@ package axi_stream_pkg is type inactive_axi_stream_policy_t is array (tdata to tuser) of inactive_bus_policy_t; -- The standard protocol checker requires tuser to be a known value when the reset is released - constant default_axi_stream_policy : inactive_axi_stream_policy_t := (tuser => '0', others => 'X'); + constant default_inactive_axi_stream_policy : inactive_axi_stream_policy_t := (tuser => '0', others => 'X'); constant all_0_policy : inactive_axi_stream_policy_t := (others => '0'); constant all_1_policy : inactive_axi_stream_policy_t := (others => '1'); constant all_x_policy : inactive_axi_stream_policy_t := (others => 'X'); constant all_hold_policy : inactive_axi_stream_policy_t := (others => hold); - type axi_stream_component_type_t is (null_component, default_component, custom_component); + -- abort_all_transactions will abort any active transaction and any pending transaction in the VC + -- inbox. Note that only pending messages related to bus transactions are affected. Other messages, + -- such as those related to VC configurations, will not be deleted. + type axi_stream_reset_policy_t is (abort_all_transactions, abort_active_transaction); + type axi_stream_component_type_t is (null_component, default_component, custom_component); type axi_stream_protocol_checker_t is record p_type : axi_stream_component_type_t; @@ -184,7 +188,8 @@ package axi_stream_pkg is actor : actor_t := null_actor; monitor : axi_stream_monitor_t := null_axi_stream_monitor; protocol_checker : axi_stream_protocol_checker_t := null_axi_stream_protocol_checker; - inactive_policy : inactive_axi_stream_policy_t := default_axi_stream_policy + inactive_policy : inactive_axi_stream_policy_t := default_inactive_axi_stream_policy; + reset_policy : axi_stream_reset_policy_t := abort_all_transactions ) return axi_stream_master_t; impure function new_axi_stream_slave( @@ -196,7 +201,8 @@ package axi_stream_pkg is logger : logger_t := axi_stream_logger; actor : actor_t := null_actor; monitor : axi_stream_monitor_t := null_axi_stream_monitor; - protocol_checker : axi_stream_protocol_checker_t := null_axi_stream_protocol_checker + protocol_checker : axi_stream_protocol_checker_t := null_axi_stream_protocol_checker; + reset_policy : axi_stream_reset_policy_t := abort_all_transactions ) return axi_stream_slave_t; impure function new_axi_stream_monitor( @@ -400,6 +406,7 @@ package axi_stream_pkg is -- Private constant p_stall_config_idx : natural := 0; constant p_inactive_policy_idx : natural := 1; + constant p_reset_policy_idx : natural := 2; impure function p_to_stall_config(vec : integer_vector_ptr_t) return stall_config_t; end package; @@ -529,7 +536,8 @@ package body axi_stream_pkg is actor : actor_t := null_actor; monitor : axi_stream_monitor_t := null_axi_stream_monitor; protocol_checker : axi_stream_protocol_checker_t := null_axi_stream_protocol_checker; - inactive_policy : inactive_axi_stream_policy_t := default_axi_stream_policy + inactive_policy : inactive_axi_stream_policy_t := default_inactive_axi_stream_policy; + reset_policy : axi_stream_reset_policy_t := abort_all_transactions ) return axi_stream_master_t is variable p_actor : actor_t; variable p_monitor : axi_stream_monitor_t; @@ -552,11 +560,12 @@ package body axi_stream_pkg is p_logger => logger, p_monitor => p_monitor, p_protocol_checker => p_protocol_checker, - p_config => new_integer_vector_ptr(p_inactive_policy_idx + 1) + p_config => new_integer_vector_ptr(p_reset_policy_idx + 1) ); set(handle.p_config, p_stall_config_idx, to_integer(to_integer_vector_ptr(stall_config))); set(handle.p_config, p_inactive_policy_idx, to_integer(to_integer_vector_ptr(inactive_policy))); + set(handle.p_config, p_reset_policy_idx, axi_stream_reset_policy_t'pos(reset_policy)); return handle; end; @@ -570,7 +579,8 @@ package body axi_stream_pkg is logger : logger_t := axi_stream_logger; actor : actor_t := null_actor; monitor : axi_stream_monitor_t := null_axi_stream_monitor; - protocol_checker : axi_stream_protocol_checker_t := null_axi_stream_protocol_checker + protocol_checker : axi_stream_protocol_checker_t := null_axi_stream_protocol_checker; + reset_policy : axi_stream_reset_policy_t := abort_all_transactions ) return axi_stream_slave_t is variable p_actor : actor_t; variable p_monitor : axi_stream_monitor_t; @@ -593,9 +603,10 @@ package body axi_stream_pkg is p_logger => logger, p_monitor => p_monitor, p_protocol_checker => p_protocol_checker, - p_config => new_integer_vector_ptr(p_stall_config_idx + 1)); + p_config => new_integer_vector_ptr(p_reset_policy_idx + 1)); set(handle.p_config, p_stall_config_idx, to_integer(to_integer_vector_ptr(stall_config))); + set(handle.p_config, p_reset_policy_idx, axi_stream_reset_policy_t'pos(reset_policy)); return handle; end; diff --git a/vunit/vhdl/verification_components/src/axi_stream_slave.vhd b/vunit/vhdl/verification_components/src/axi_stream_slave.vhd index 978f18b80..36392db3d 100644 --- a/vunit/vhdl/verification_components/src/axi_stream_slave.vhd +++ b/vunit/vhdl/verification_components/src/axi_stream_slave.vhd @@ -105,93 +105,139 @@ begin variable mismatch : boolean; variable tstrb_resolved : std_logic_vector(tstrb'range); variable stall_config : integer_vector_ptr_t; + variable delay : delay_length; + + impure function get_reset_policy(slave : axi_stream_slave_t) return axi_stream_reset_policy_t is + begin + return axi_stream_reset_policy_t'val(get(slave.p_config, p_reset_policy_idx)); + end; + + procedure flush_pending_transactions(queue : queue_t) is + constant total_length : natural := length(queue); + variable consumed_length : natural := 0; + variable before_pop_length : natural; + variable msg : msg_t; + variable msg_type : msg_type_t; + begin + while consumed_length < total_length loop + before_pop_length := length(queue); + msg := pop(queue); + consumed_length := consumed_length + (before_pop_length - length(queue)); + + -- Messages to keep are pushed back into the queue. + msg_type := message_type(msg); + if msg_type = stream_pop_msg or msg_type = pop_axi_stream_msg or + msg_type = check_axi_stream_msg or msg_type = wait_for_time_msg then + null; + else + push(message_queue, msg); + end if; + end loop; + end; begin rnd.InitSeed(rnd'instance_name); loop - if is_empty(message_queue) then - -- Wait for messages to arrive on the queue, posted by the process above - wait until rising_edge(aclk) and (not is_empty(message_queue)); - end if; + if areset_n = '0' then + tready <= '0'; + wait until areset_n = '1' and rising_edge(aclk); + if get_reset_policy(slave) = abort_all_transactions then + flush_pending_transactions(message_queue); + end if; + else + if is_empty(message_queue) then + -- Wait for messages to arrive on the queue, posted by the process above + wait until areset_n = '0' or (not is_empty(message_queue) and rising_edge(aclk)); + end if; - while not is_empty(message_queue) loop - msg := pop(message_queue); - msg_type := message_type(msg); + while not is_empty(message_queue) and areset_n = '1' loop + msg := pop(message_queue); + msg_type := message_type(msg); - if msg_type = wait_for_time_msg then - handle_sync_message(net, msg_type, msg); - wait until rising_edge(aclk); - - elsif msg_type = notify_request_msg then - -- Ignore this message, but expect it - - elsif msg_type = stream_pop_msg or msg_type = pop_axi_stream_msg or msg_type = check_axi_stream_msg then - - -- stall according to probability configuration - probability_stall_axi_stream( - aclk, - p_to_stall_config(to_integer_vector_ptr(get(slave.p_config, p_stall_config_idx))), - rnd); - - tready <= '1'; - wait until (tvalid and tready) = '1' and rising_edge(aclk); - tready <= '0'; - - tstrb_resolved := resolve_tstrb(tkeep, tstrb); - if msg_type = stream_pop_msg or msg_type = pop_axi_stream_msg then - axi_stream_transaction := ( - tdata => tdata, - tlast => tlast = '1', - tkeep => tkeep, - tstrb => tstrb_resolved, - tid => tid, - tdest => tdest, - tuser => tuser - ); - - reply_msg := new_axi_stream_transaction_msg(axi_stream_transaction); - reply(net, msg, reply_msg); - elsif msg_type = check_axi_stream_msg then - report_msg := new_string_ptr(pop_string(msg)); - - expected_tdata := pop_std_ulogic_vector(msg); - mismatch := false; - for idx in tkeep'range loop - if tkeep(idx) and tstrb_resolved(idx) then - mismatch := tdata(8 * idx + 7 downto 8 * idx) /= expected_tdata(8 * idx + 7 downto 8 * idx); - exit when mismatch; - end if; - end loop; - if mismatch then - check_field(tdata, expected_tdata, "TDATA mismatch, " & to_string(report_msg)); + if msg_type = wait_for_time_msg then + handle_message(msg_type); + delay := pop_time(msg); + wait until areset_n = '0' for delay; + + if areset_n /= '0' then + wait until rising_edge(aclk); end if; - check_field(tkeep, pop_std_ulogic_vector(msg), "TKEEP mismatch, " & to_string(report_msg)); - check_field(tstrb_resolved, pop_std_ulogic_vector(msg), "TSTRB mismatch, " & to_string(report_msg)); - check_equal(tlast, pop_std_ulogic(msg), "TLAST mismatch, " & to_string(report_msg)); - check_field(tid, pop_std_ulogic_vector(msg), "TID mismatch, " & to_string(report_msg)); - check_field(tdest, pop_std_ulogic_vector(msg), "TDEST mismatch, " & to_string(report_msg)); - check_field(tuser, pop_std_ulogic_vector(msg), "TUSER mismatch, " & to_string(report_msg)); - end if; + elsif msg_type = notify_request_msg then + -- Ignore this message, but expect it + elsif msg_type = stream_pop_msg or msg_type = pop_axi_stream_msg or msg_type = check_axi_stream_msg then - elsif msg_type = set_stall_config_msg then - deallocate(to_integer_vector_ptr(get(slave.p_config, p_stall_config_idx))); - set(slave.p_config, p_stall_config_idx, to_integer(pop_integer_vector_ptr_ref(msg))); + -- stall according to probability configuration + probability_stall_axi_stream( + aclk, + p_to_stall_config(to_integer_vector_ptr(get(slave.p_config, p_stall_config_idx))), + rnd); - elsif msg_type = get_stall_config_msg then - reply_msg := new_msg(get_stall_config_reply_msg); - stall_config := to_integer_vector_ptr(get(slave.p_config, p_stall_config_idx)); - push(reply_msg, stall_config); - reply(net, msg, reply_msg); + tready <= '1'; + wait until areset_n = '0' or ((tvalid and tready) = '1' and rising_edge(aclk)); + tready <= '0'; - else - unexpected_msg_type(msg_type); - end if; + tstrb_resolved := resolve_tstrb(tkeep, tstrb); + if areset_n = '0' then + null; + elsif msg_type = stream_pop_msg or msg_type = pop_axi_stream_msg then + axi_stream_transaction := ( + tdata => tdata, + tlast => tlast = '1', + tkeep => tkeep, + tstrb => tstrb_resolved, + tid => tid, + tdest => tdest, + tuser => tuser + ); + + reply_msg := new_axi_stream_transaction_msg(axi_stream_transaction); + reply(net, msg, reply_msg); + elsif msg_type = check_axi_stream_msg then + report_msg := new_string_ptr(pop_string(msg)); + + expected_tdata := pop_std_ulogic_vector(msg); + mismatch := false; + for idx in tkeep'range loop + if tkeep(idx) and tstrb_resolved(idx) then + mismatch := tdata(8 * idx + 7 downto 8 * idx) /= expected_tdata(8 * idx + 7 downto 8 * idx); + exit when mismatch; + end if; + end loop; + if mismatch then + check_field(tdata, expected_tdata, "TDATA mismatch, " & to_string(report_msg)); + end if; + + check_field(tkeep, pop_std_ulogic_vector(msg), "TKEEP mismatch, " & to_string(report_msg)); + check_field(tstrb_resolved, pop_std_ulogic_vector(msg), "TSTRB mismatch, " & to_string(report_msg)); + check_equal(tlast, pop_std_ulogic(msg), "TLAST mismatch, " & to_string(report_msg)); + check_field(tid, pop_std_ulogic_vector(msg), "TID mismatch, " & to_string(report_msg)); + check_field(tdest, pop_std_ulogic_vector(msg), "TDEST mismatch, " & to_string(report_msg)); + check_field(tuser, pop_std_ulogic_vector(msg), "TUSER mismatch, " & to_string(report_msg)); + end if; - delete(msg); - end loop; - notify(bus_process_done); + elsif msg_type = set_stall_config_msg then + deallocate(to_integer_vector_ptr(get(slave.p_config, p_stall_config_idx))); + set(slave.p_config, p_stall_config_idx, to_integer(pop_integer_vector_ptr_ref(msg))); + + elsif msg_type = get_stall_config_msg then + reply_msg := new_msg(get_stall_config_reply_msg); + stall_config := to_integer_vector_ptr(get(slave.p_config, p_stall_config_idx)); + push(reply_msg, stall_config); + reply(net, msg, reply_msg); + + else + unexpected_msg_type(msg_type); + end if; + + delete(msg); + end loop; + + if is_empty(message_queue) then + notify(bus_process_done); + end if; + end if; end loop; end process; diff --git a/vunit/vhdl/verification_components/test/tb_axi_stream.vhd b/vunit/vhdl/verification_components/test/tb_axi_stream.vhd index 9dd2d2d04..78bb4d4a1 100644 --- a/vunit/vhdl/verification_components/test/tb_axi_stream.vhd +++ b/vunit/vhdl/verification_components/test/tb_axi_stream.vhd @@ -28,19 +28,22 @@ entity tb_axi_stream is g_data_length : positive := 8; g_id_length : natural := 8; g_dest_length : natural := 8; - g_user_length : natural := 8 + g_user_length : natural := 8; + reset_policy_value : natural := 1 -- 0 = abort_all_transactions, 1 = abort_active_transaction ); end entity; architecture a of tb_axi_stream is - + constant clk_period : time := 10 ns; constant min_stall_cycles : natural := 5; constant max_stall_cycles : natural := 15; + constant reset_policy : axi_stream_reset_policy_t := axi_stream_reset_policy_t'val(reset_policy_value); constant master_axi_stream : axi_stream_master_t := new_axi_stream_master( data_length => g_data_length, id_length => g_id_length, dest_length => g_dest_length, user_length => g_user_length, logger => get_logger("master"), actor => new_actor("master"), - monitor => default_axi_stream_monitor, protocol_checker => default_axi_stream_protocol_checker + monitor => default_axi_stream_monitor, protocol_checker => default_axi_stream_protocol_checker, + reset_policy => reset_policy ); constant master_stream : stream_master_t := as_stream(master_axi_stream); constant master_sync : sync_handle_t := as_sync(master_axi_stream); @@ -48,7 +51,8 @@ architecture a of tb_axi_stream is constant slave_axi_stream : axi_stream_slave_t := new_axi_stream_slave( data_length => g_data_length, id_length => g_id_length, dest_length => g_dest_length, user_length => g_user_length, logger => get_logger("slave"), actor => new_actor("slave"), - monitor => default_axi_stream_monitor, protocol_checker => default_axi_stream_protocol_checker + monitor => default_axi_stream_monitor, protocol_checker => default_axi_stream_protocol_checker, + reset_policy => reset_policy ); constant slave_stream : stream_slave_t := as_stream(slave_axi_stream); constant slave_sync : sync_handle_t := as_sync(slave_axi_stream); @@ -135,6 +139,7 @@ begin variable inactive_policy_read_back : inactive_bus_policy_t; variable loop_count : natural := 0; variable cov : CoverageIDType; + variable com_status : com_status_t; impure function select_policy( modified_signal, signal_to_check : axi_stream_signal_t; @@ -266,13 +271,79 @@ begin ); check_true(axi_stream_transaction.tlast, result("for axi_stream_transaction.tlast")); end loop; - elsif run("test reset") then - wait until rising_edge(aclk); + + elsif run("test reset of transactions") then + -- Depending on the reset policy, a reset will abort only the active transactions + -- or the active + any pending transaction. Pending configuration messages should not be affected. + push_stream(net, master_stream, x"01", true); + stall_config := new_stall_config(0.17, 0, 0); + set_stall_config(net, master_axi_stream, stall_config); + push_stream(net, master_stream, x"02", true); + + pop_stream(net, slave_stream, reference); + push(reference_queue, reference); + stall_config := new_stall_config(0.21, 0, 0); + set_stall_config(net, slave_axi_stream, stall_config); + pop_stream(net, slave_stream, reference); + push(reference_queue, reference); + + wait until tvalid = '1'; + wait until falling_edge(aclk); areset_n <= '0'; - wait until rising_edge(aclk); - check_equal(tvalid, '0', result("for valid low check while in reset")); + timestamp := now; + wait until tvalid = '0' for 1 ps; + check_equal(now - timestamp, 0 ns, result("for reset activation delay.")); + check_equal(tvalid, '0', result("for tvalid low check while in reset")); + wait for 1 ns; areset_n <= '1'; - wait until rising_edge(aclk); + wait for 0 ns; + + timestamp := now; + reference := pop(reference_queue); + wait_for_reply(net, reference, com_status, timeout => 100 ns); + check_equal(now - timestamp, 100 ns, result("for first transaction timeout")); + + timestamp := now; + reference := pop(reference_queue); + if reset_policy = abort_active_transaction then + await_pop_stream_reply(net, reference, data); + check_equal(data, std_logic_vector'(x"02"), result("for data in second transaction")); + else + wait_for_reply(net, reference, com_status, timeout => 100 ns); + check_equal(now - timestamp, 100 ns, result("for second transaction timeout")); + end if; + get_stall_config(net, master_axi_stream, stall_config_read_back); + check_equal(stall_config_read_back.stall_probability, 0.17, + result(" for master stall probability"), max_diff => 0.001); + get_stall_config(net, slave_axi_stream, stall_config_read_back); + check_equal(stall_config_read_back.stall_probability, 0.21, + result(" for slave stall probability"), max_diff => 0.001); + + -- Check that the VCs are fully recovered. + push_stream(net, master_stream, x"03", true); + pop_stream(net, slave_stream, data, last_bool); + check_equal(data, std_logic_vector'(x"03"), result("for pop stream data")); + check_true(last_bool, result("for pop stream last")); + + elsif run("test reset of time delays") then + push_stream(net, master_stream, x"01", true); + wait_for_time(net, master_sync, clk_period * 5); + push_stream(net, master_stream, x"02", true); + + pop_stream(net, slave_stream, data, last_bool); + check_equal(data, std_logic_vector'(x"01"), result("for pop stream data")); + wait_for_time(net, slave_sync, clk_period * 5); + wait for clk_period / 10; + + areset_n <= '0'; + wait for clk_period / 10; + areset_n <= '1'; + wait for 0 ns; + timestamp := now; + pop_stream(net, slave_stream, data, last_bool); + + check_equal(now - timestamp, 18 * clk_period / 10, result("for pop delay.")); + check_equal(data, std_logic_vector'(x"02"), result("for pop stream data")); elsif run("test single push and pop with tlast") then push_stream(net, master_stream, x"88", true); @@ -1070,5 +1141,5 @@ begin end if; end process; - aclk <= not aclk after 5 ns; + aclk <= not aclk after clk_period / 2; end architecture;