diff --git a/docs/verification_components/user_guide.rst b/docs/verification_components/user_guide.rst index 6ba1b35d1..a09707437 100644 --- a/docs/verification_components/user_guide.rst +++ b/docs/verification_components/user_guide.rst @@ -90,3 +90,115 @@ A VC typically has an associated package defining procedures for sending to and Each VC instance is associated with a handle that is created in the test bench and set as a generic on the VC instantiation. The handle is given as an argument to the procedure calls to direct messages to the specific VC instance. + + +VC and VCI Compliance Testing +============================= + +VUnit establishes a standard for VCs and VCIs, designed around a set of rules that promote flexibility, +reusability, interoperability, and future-proofing of VCs and VCIs. + +Rule 1 +------ + +The file containing the VC entity shall contain only that entity, and the file containing the VCI package shall +contain only that package. + +**Rationale**: This structure simplifies compliance testing, as each VC or VCI can be directly referenced by its +file name. + +Rule 2 +------ + +A VC shall have only **one** generic, the *handle*, and it shall be of a record type containing **private** fields. + +**Rationale**: Using a record allows future updates to add and/or remove fields in the record without breaking +backward compatibility. + +**Recommendation**: Mark the fields as private by using a naming convention and/or including comments. This minimizes +the risk of users accessing fields directly. + +Rule 3 +------ + +The VC handle shall be created by a function, the *constructor*, which shall have a name beginning with ``new``. + +**Rationale**: Using a constructor removes the need for users to directly access the private fields of the handle +record. The naming convention also enables compliance tests to easily locate the constructor and verify it against +other applicable rules. + +Rule 4 +------ + +The VC constructor shall include an ``id`` parameter of type ``id_t`` to enable the user to specify the VC's identity. + +**Rationale**: This gives users control over the namespace assigned to the VC. + +Rule 5 +------ + +The ``id`` parameter shall default to ``null_id``. If not overridden, ``id`` shall be assigned a value on the format +``::``, where ```` starts at 1 for the first instance of the VC and increments with each +additional instance. + +**Rationale**: This format ensures clear identification while preventing naming collisions when VCs from different +providers are combined. + +Rule 6 +------ + +All identity-supporting objects associated with the VC (such as loggers, actors, and events) shall be assigned an +identity within the namespace defined by the constructor’s ``id`` parameter. + +**Rationale**: This gives users control over these objects and simplifies the association of log messages with a +specific VC instance. + +Rule 7 +------ + +All logging performed by the VC, including indirect logging (such that error logs from checkers), shall use the +VUnit logging mechanism. + +**Rationale**: Using a unified logging mechanism ensures consistency and compatibility across logging outputs +from different VCs. + +Rule 8 +------ + +Communication with the VC shall be based on VUnit message passing, and the VC actor’s identity shall match the +``id`` parameter provided to the constructor. + +**Rationale**: This ensures a consistent communication framework and enables the reuse of VCIs across multiple VCs. + +Rule 9 +------ + +All VCs shall support the sync interface. + +**Rationale**: The ability to verify if a VC is idle and to introduce delays between transactions are frequently +needed features for VC users. + +Rule 10 +------- + +The VC constructor shall include an ``unexpected_msg_type_policy`` parameter, allowing users to specify the action +taken when the VC receives an unexpected message type. + +**Rationale**: This policy enables flexibility in handling situations where a VC actor, subscribed to another actor, +might receive unsupported messages, while VCs addressed directly should only receive supported messages. + +Rule 11 +------- + +The standard configuration (of type ``std_cfg_t``), which includes the required parameters for the constructor, shall +be accessible by calling ``get_std_cfg`` with the VC handle. + +**Rationale**: This enables reuse of common functions across multiple VCs. + +Rule 12 +------- + +A VC shall keep the ``test_runner_cleanup`` phase entry gate locked as long as there are pending operations. + +**Rationale**: Locking the gate ensures that the simulation does not terminate prematurely before all operations have +completed. diff --git a/vunit/vhdl/verification_components/src/apb_completer.vhd b/vunit/vhdl/verification_components/src/apb_completer.vhd new file mode 100644 index 000000000..c4d17d6f0 --- /dev/null +++ b/vunit/vhdl/verification_components/src/apb_completer.vhd @@ -0,0 +1,84 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2024, Lars Asplund lars.anders.asplund@gmail.com + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library osvvm; +use osvvm.RandomPkg.RandomPType; + +use work.memory_pkg.all; +use work.apb_completer_pkg.all; +use work.logger_pkg.all; + +entity apb_completer is + generic ( + bus_handle : apb_completer_t + ); + port ( + clk : in std_logic; + reset : in std_logic; + psel_i : in std_logic; + penable_i : in std_logic; + paddr_i : in std_logic_vector; + pwrite_i : in std_logic; + pwdata_i : in std_logic_vector; + prdata_o : out std_logic_vector; + pready_o : out std_logic + ); +end entity; + +architecture a of apb_completer is + +begin + + PROC_MAIN: process + procedure drive_outputs_invalid is + begin + if bus_handle.p_drive_invalid then + prdata_o <= (prdata_o'range => bus_handle.p_drive_invalid_val); + pready_o <= bus_handle.p_drive_invalid_val; + end if; + end procedure; + + variable addr : integer; + variable rnd : RandomPType; + begin + drive_outputs_invalid; + wait until rising_edge(clk); + + loop + -- IDLE/SETUP state + drive_outputs_invalid; + + wait until psel_i = '1' and rising_edge(clk); + -- ACCESS state + + while rnd.Uniform(0.0, 1.0) > bus_handle.p_ready_high_probability loop + pready_o <= '0'; + wait until rising_edge(clk); + end loop; + + pready_o <= '1'; + + addr := to_integer(unsigned(paddr_i)); + + if pwrite_i = '1' then + write_word(bus_handle.p_memory, addr, pwdata_i); + else + prdata_o <= read_word(bus_handle.p_memory, addr, prdata_o'length/8); + end if; + + wait until rising_edge(clk); + + if penable_i = '0' then + failure(bus_handle.p_logger, "penable_i must be active in the ACCESS phase."); + end if; + end loop; + end process; + +end architecture; \ No newline at end of file diff --git a/vunit/vhdl/verification_components/src/apb_completer_pkg.vhd b/vunit/vhdl/verification_components/src/apb_completer_pkg.vhd new file mode 100644 index 000000000..7564646a1 --- /dev/null +++ b/vunit/vhdl/verification_components/src/apb_completer_pkg.vhd @@ -0,0 +1,76 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2024, Lars Asplund lars.anders.asplund@gmail.com + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.bus_master_pkg.all; +use work.com_pkg.all; +use work.com_types_pkg.all; +use work.logger_pkg.all; +use work.memory_pkg.memory_t; +use work.memory_pkg.to_vc_interface; + +package apb_completer_pkg is + + type apb_completer_t is record + -- Private + p_actor : actor_t; + p_memory : memory_t; + p_logger : logger_t; + p_drive_invalid : boolean; + p_drive_invalid_val : std_logic; + p_ready_high_probability : real range 0.0 to 1.0; + end record; + + constant apb_completer_logger : logger_t := get_logger("vunit_lib:apb_completer_pkg"); + impure function new_apb_completer( + memory : memory_t; + logger : logger_t := null_logger; + actor : actor_t := null_actor; + drive_invalid : boolean := true; + drive_invalid_val : std_logic := 'X'; + ready_high_probability : real := 1.0) + return apb_completer_t; + + constant slave_write_msg : msg_type_t := new_msg_type("apb slave write"); + constant slave_read_msg : msg_type_t := new_msg_type("apb slave read"); +end package; + +package body apb_completer_pkg is + + impure function new_apb_completer( + memory : memory_t; + logger : logger_t := null_logger; + actor : actor_t := null_actor; + drive_invalid : boolean := true; + drive_invalid_val : std_logic := 'X'; + ready_high_probability : real := 1.0) + return apb_completer_t is + variable actor_tmp : actor_t := null_actor; + variable logger_tmp : logger_t := null_logger; + begin + if actor = null_actor then + actor_tmp := new_actor; + else + actor_tmp := actor; + end if; + if logger = null_logger then + logger_tmp := bus_logger; + else + logger_tmp := logger; + end if; + return ( + p_memory => to_vc_interface(memory, logger), + p_logger => logger_tmp, + p_actor => actor_tmp, + p_drive_invalid => drive_invalid, + p_drive_invalid_val => drive_invalid_val, + p_ready_high_probability => ready_high_probability + ); + end; +end package body; \ No newline at end of file diff --git a/vunit/vhdl/verification_components/src/apb_requester.vhd b/vunit/vhdl/verification_components/src/apb_requester.vhd new file mode 100644 index 000000000..eedd67c09 --- /dev/null +++ b/vunit/vhdl/verification_components/src/apb_requester.vhd @@ -0,0 +1,174 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2024, Lars Asplund lars.anders.asplund@gmail.com + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.bus_master_pkg.all; +use work.check_pkg.all; +use work.com_pkg.all; +use work.com_types_pkg.all; +use work.queue_pkg.all; +use work.sync_pkg.all; +use work.logger_pkg.all; +use work.vc_pkg.all; +use work.runner_pkg.all; +use work.run_pkg.all; +use work.run_types_pkg.all; +use work.log_levels_pkg.all; +use work.apb_requester_pkg.all; + +entity apb_requester is + generic ( + bus_handle : apb_requester_t + ); + port ( + clk : in std_logic; + reset : in std_logic; + psel_o : out std_logic; + penable_o : out std_logic; + paddr_o : out std_logic_vector(address_length(bus_handle.p_bus_handle) - 1 downto 0); + pwrite_o : out std_logic; + pwdata_o : out std_logic_vector(data_length(bus_handle.p_bus_handle) - 1 downto 0); + prdata_i : in std_logic_vector(data_length(bus_handle.p_bus_handle) - 1 downto 0); + pready_i : in std_logic; + pslverr_i : in std_logic := '0' + ); +end entity; + +architecture behav of apb_requester is + constant message_queue : queue_t := new_queue; + signal idle_bus : boolean := true; + + impure function queues_empty return boolean is + begin + return is_empty(message_queue); + end function; + + impure function is_idle return boolean is + begin + return idle_bus; + end function; + +begin + + PROC_MAIN: process + variable request_msg : msg_t; + variable msg_type : msg_type_t; + begin + DISPATCH_LOOP : loop + receive(net, bus_handle.p_bus_handle.p_actor, request_msg); + msg_type := message_type(request_msg); + + if msg_type = bus_read_msg or msg_type = apb_read_msg + or msg_type = bus_write_msg or msg_type = apb_write_msg then + push(message_queue, request_msg); + elsif msg_type = wait_until_idle_msg then + if not is_idle or not queues_empty then + wait until is_idle and queues_empty and rising_edge(clk); + end if; + handle_wait_until_idle(net, msg_type, request_msg); + elsif msg_type = wait_for_time_msg then + push(message_queue, request_msg); + else + if bus_handle.p_unexpected_msg_type_policy = fail then + unexpected_msg_type(msg_type); + end if; + end if; + end loop; + end process; + + BUS_PROCESS: process + procedure drive_bus_invalid is + begin + if bus_handle.p_drive_invalid then + penable_o <= bus_handle.p_drive_invalid_val; + paddr_o <= (paddr_o'range => bus_handle.p_drive_invalid_val); + pwrite_o <= bus_handle.p_drive_invalid_val; + pwdata_o <= (pwdata_o'range => bus_handle.p_drive_invalid_val); + end if; + end procedure; + + variable request_msg, reply_msg : msg_t; + variable msg_type : msg_type_t; + variable addr_this_transaction : std_logic_vector(paddr_o'range) := (others => '0'); + variable data_this_transaction : std_logic_vector(prdata_i'range) := (others => '0'); + variable byte_enable_this_transaction : std_logic_vector(byte_enable_length(bus_handle)-1 downto 0); + variable error_this_transaction : std_logic := '0'; + constant key : key_t := get_entry_key(test_runner_cleanup); + begin + loop + drive_bus_invalid; + psel_o <= '0'; + + if is_empty(message_queue) then + unlock(runner, key); + wait until rising_edge(clk) and not is_empty(message_queue); + end if; + lock(runner, key); + idle_bus <= false; + wait for 0 ns; + + request_msg := pop(message_queue); + msg_type := message_type(request_msg); + + if msg_type = apb_write_msg then + addr_this_transaction := pop_std_ulogic_vector(request_msg); + data_this_transaction := pop_std_ulogic_vector(request_msg); + byte_enable_this_transaction := pop_std_ulogic_vector(request_msg); + error_this_transaction := pop_std_ulogic(request_msg); + + psel_o <= '1'; + penable_o <= '0'; + pwrite_o <= '1'; + paddr_o <= addr_this_transaction; + pwdata_o <= data_this_transaction; + + wait until rising_edge(clk); + penable_o <= '1'; + wait until (pready_i and penable_o) = '1' and rising_edge(clk); + + check_equal(pslverr_i, error_this_transaction, "Unexpected pslverror response for write request."); + + if is_visible(bus_handle.p_bus_handle.p_logger, debug) then + debug(bus_handle.p_bus_handle.p_logger, + "Wrote 0x" & to_hstring(data_this_transaction) & + " to address 0x" & to_hstring(addr_this_transaction)); + end if; + + reply_msg := new_msg; + reply(net, request_msg, reply_msg); + + elsif msg_type = apb_read_msg then + addr_this_transaction := pop_std_ulogic_vector(request_msg); + error_this_transaction := pop_std_ulogic(request_msg); + + psel_o <= '1'; + penable_o <= '0'; + pwrite_o <= '0'; + paddr_o <= addr_this_transaction; + + wait until rising_edge(clk); + penable_o <= '1'; + wait until (pready_i and penable_o) = '1' and rising_edge(clk); + + check_equal(pslverr_i, error_this_transaction, "Unexpected pslverror response for read request."); + + reply_msg := new_msg; + push_std_ulogic_vector(reply_msg, prdata_i); + reply(net, request_msg, reply_msg); + + elsif msg_type = wait_for_time_msg then + handle_wait_for_time(net, msg_type, request_msg); + -- Re-align with the clock when a wait for time message was handled, because this breaks edge alignment. + wait until rising_edge(clk); + end if; + + idle_bus <= true; + end loop; + end process; +end architecture; diff --git a/vunit/vhdl/verification_components/src/apb_requester_pkg.vhd b/vunit/vhdl/verification_components/src/apb_requester_pkg.vhd new file mode 100644 index 000000000..e50de5024 --- /dev/null +++ b/vunit/vhdl/verification_components/src/apb_requester_pkg.vhd @@ -0,0 +1,348 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2024, Lars Asplund lars.anders.asplund@gmail.com + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.bus_master_pkg.all; +use work.com_pkg.all; +use work.com_types_pkg.all; +use work.logger_pkg.all; +use work.sync_pkg.all; +use work.id_pkg.all; +use work.vc_pkg.all; +use work.memory_pkg.memory_t; +use work.memory_pkg.to_vc_interface; + +package apb_requester_pkg is + + type apb_requester_t is record + -- Private + p_id : id_t; + p_bus_handle : bus_master_t; + p_drive_invalid : boolean; + p_drive_invalid_val : std_logic; + p_unexpected_msg_type_policy : unexpected_msg_type_policy_t; + end record; + + impure function new_apb_requester( + id : id_t := null_id; + data_length : natural; + address_length : natural; + logger : logger_t := null_logger; + actor : actor_t := null_actor; + unexpected_msg_type_policy : unexpected_msg_type_policy_t := fail; + drive_invalid : boolean := true; + drive_invalid_val : std_logic := 'X' + ) return apb_requester_t; + + function get_logger(bus_handle : apb_requester_t) return logger_t; + + impure function byte_enable_length(bus_handle : apb_requester_t) return natural; + + -- Blocking: Write the bus + procedure write_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : std_logic_vector; + constant data : std_logic_vector; + constant expected_error : std_logic := '0'; + -- default byte enable is all bytes + constant byte_enable : std_logic_vector := ""); + procedure write_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : natural; + constant data : std_logic_vector; + constant expected_error : std_logic := '0'; + -- default byte enable is all bytes + constant byte_enable : std_logic_vector := ""); + + -- Non blocking: Read the bus returning a reference to the future reply + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : std_logic_vector; + variable reference : inout bus_reference_t; + constant expected_error : std_logic := '0'); + + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : natural; + variable reference : inout bus_reference_t; + constant expected_error : std_logic := '0'); + + -- Blocking: read bus with immediate reply + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : std_logic_vector; + variable data : inout std_logic_vector; + constant expected_error : std_logic := '0'); + + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : natural; + variable data : inout std_logic_vector; + constant expected_error : std_logic := '0'); + + -- Blocking: Read bus and check result against expected data + procedure check_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : std_logic_vector; + constant expected : std_logic_vector; + constant msg : string := ""); + + procedure check_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : natural; + constant expected : std_logic_vector; + constant msg : string := ""); + + -- Blocking: Wait until a read from address equals the value using + -- std_match If timeout is reached error with msg + procedure wait_until_read_equals( + signal net : inout network_t; + bus_handle : apb_requester_t; + addr : std_logic_vector; + value : std_logic_vector; + timeout : delay_length := delay_length'high; + msg : string := ""); + + -- Blocking: Wait until a read from address has the bit with this + -- index set to value If timeout is reached error with msg + procedure wait_until_read_bit_equals( + signal net : inout network_t; + bus_handle : apb_requester_t; + addr : std_logic_vector; + idx : natural; + value : std_logic; + timeout : delay_length := delay_length'high; + msg : string := ""); + + procedure wait_until_idle(signal net : inout network_t; + handle : apb_requester_t; + timeout : delay_length := max_timeout); + + procedure wait_for_time(signal net : inout network_t; + handle : apb_requester_t; + delay : delay_length); + + constant apb_write_msg : msg_type_t := new_msg_type("write apb bus"); + constant apb_read_msg : msg_type_t := new_msg_type("read apb bus"); +end package; + +package body apb_requester_pkg is + + impure function new_apb_requester( + id : id_t := null_id; + data_length : natural; + address_length : natural; + logger : logger_t := null_logger; + actor : actor_t := null_actor; + unexpected_msg_type_policy : unexpected_msg_type_policy_t := fail; + drive_invalid : boolean := true; + drive_invalid_val : std_logic := 'X' + ) return apb_requester_t is + impure function create_bus (logger : logger_t) return bus_master_t is + begin + return new_bus( + data_length => data_length, + address_length => address_length, + logger => logger, + actor => actor + ); + end function; + variable logger_tmp : logger_t := null_logger; + variable id_tmp : id_t := null_id; + constant parent : id_t := get_id("vunit_lib:apb_requester"); + begin + if id = null_id then + id_tmp := get_id(to_string(num_children(parent) + 1), parent); + else + id_tmp := id; + end if; + if logger = null_logger then + logger_tmp := get_logger(id_tmp); + else + logger_tmp := logger; + end if; + return ( + p_id => id_tmp, + p_bus_handle => create_bus(logger_tmp), + p_drive_invalid => drive_invalid, + p_drive_invalid_val => drive_invalid_val, + p_unexpected_msg_type_policy => unexpected_msg_type_policy + ); + end; + + function get_logger(bus_handle : apb_requester_t) return logger_t is + begin + return get_logger(bus_handle.p_bus_handle); + end function; + + impure function address_length(bus_handle : apb_requester_t) return natural is + begin + return bus_handle.p_bus_handle.p_address_length; + end; + + impure function byte_enable_length(bus_handle : apb_requester_t) return natural is + begin + return (bus_handle.p_bus_handle.p_data_length + bus_handle.p_bus_handle.p_byte_length - 1) + / bus_handle.p_bus_handle.p_byte_length; + end; + + impure function to_address(constant bus_handle : apb_requester_t; address : natural) return std_logic_vector is + begin + return std_logic_vector(to_unsigned(address, address_length(bus_handle))); + end; + + -- Blocking: Write the bus + procedure write_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : std_logic_vector; + constant data : std_logic_vector; + constant expected_error : std_logic := '0'; + -- default byte enable is all bytes + constant byte_enable : std_logic_vector := "") is + variable request_msg : msg_t := new_msg(apb_write_msg); + variable full_data : std_logic_vector(bus_handle.p_bus_handle.p_data_length-1 downto 0) := (others => '0'); + variable full_address : std_logic_vector(bus_handle.p_bus_handle.p_address_length-1 downto 0) := (others => '0'); + variable full_byte_enable : std_logic_vector(byte_enable_length(bus_handle)-1 downto 0); + begin + full_address(address'length-1 downto 0) := address; + push_std_ulogic_vector(request_msg, full_address); + + full_data(data'length-1 downto 0) := data; + push_std_ulogic_vector(request_msg, full_data); + + if byte_enable = "" then + full_byte_enable := (others => '1'); + else + full_byte_enable(byte_enable'length-1 downto 0) := byte_enable; + end if; + push_std_ulogic_vector(request_msg, full_byte_enable); + push_std_ulogic(request_msg, expected_error); + + send(net, bus_handle.p_bus_handle.p_actor, request_msg); + end procedure; + + procedure write_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : natural; + constant data : std_logic_vector; + constant expected_error : std_logic := '0'; + -- default byte enable is all bytes + constant byte_enable : std_logic_vector := "") is + begin + write_bus(net, bus_handle, to_address(bus_handle, address), data, expected_error, byte_enable); + end procedure; + + -- Blocking: read bus with immediate reply + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : std_logic_vector; + variable data : inout std_logic_vector; + constant expected_error : std_logic := '0') is + variable reference : bus_reference_t; + begin + read_bus(net, bus_handle, address, reference, expected_error); + await_read_bus_reply(net, reference, data); + end procedure; + + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : natural; + variable data : inout std_logic_vector; + constant expected_error : std_logic := '0') is + variable reference : bus_reference_t; + begin + read_bus(net, bus_handle, to_address(bus_handle, address), reference, expected_error); + await_read_bus_reply(net, reference, data); + end procedure; + + -- Non blocking read with delayed reply + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : natural; + variable reference : inout bus_reference_t; + constant expected_error : std_logic := '0') is + begin + read_bus(net, bus_handle, to_address(bus_handle, address), reference, expected_error); + end procedure; + + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : std_logic_vector; + variable reference : inout bus_reference_t; + constant expected_error : std_logic := '0') is + variable full_address : std_logic_vector(bus_handle.p_bus_handle.p_address_length-1 downto 0) := (others => '0'); + alias request_msg : msg_t is reference; + begin + request_msg := new_msg(apb_read_msg); + full_address(address'length-1 downto 0) := address; + push_std_ulogic_vector(request_msg, full_address); + push_std_ulogic(request_msg, expected_error); + send(net, bus_handle.p_bus_handle.p_actor, request_msg); + end procedure; + + -- Blocking: Read bus and check result against expected data + procedure check_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : std_logic_vector; + constant expected : std_logic_vector; + constant msg : string := "") is + begin + check_bus(net, bus_handle.p_bus_handle, address, expected, msg); + end procedure; + + procedure check_bus(signal net : inout network_t; + constant bus_handle : apb_requester_t; + constant address : natural; + constant expected : std_logic_vector; + constant msg : string := "") is + begin + check_bus(net, bus_handle.p_bus_handle, address, expected, msg); + end procedure; + + -- Blocking: Wait until a read from address equals the value using + -- std_match If timeout is reached error with msg + procedure wait_until_read_equals( + signal net : inout network_t; + bus_handle : apb_requester_t; + addr : std_logic_vector; + value : std_logic_vector; + timeout : delay_length := delay_length'high; + msg : string := "") is + begin + wait_until_read_equals(net, bus_handle.p_bus_handle, addr, value, timeout, msg); + end procedure; + + -- Blocking: Wait until a read from address has the bit with this + -- index set to value If timeout is reached error with msg + procedure wait_until_read_bit_equals( + signal net : inout network_t; + bus_handle : apb_requester_t; + addr : std_logic_vector; + idx : natural; + value : std_logic; + timeout : delay_length := delay_length'high; + msg : string := "") is + begin + wait_until_read_bit_equals(net, bus_handle.p_bus_handle, addr, idx, value, timeout, msg); + end procedure; + + procedure wait_until_idle(signal net : inout network_t; + handle : apb_requester_t; + timeout : delay_length := max_timeout) is + begin + wait_until_idle(net, handle.p_bus_handle.p_actor, timeout); + end procedure; + + procedure wait_for_time(signal net : inout network_t; + handle : apb_requester_t; + delay : delay_length) is + begin + wait_for_time(net, handle.p_bus_handle.p_actor, delay); + end procedure; +end package body; \ No newline at end of file diff --git a/vunit/vhdl/verification_components/src/vc_pkg.vhd b/vunit/vhdl/verification_components/src/vc_pkg.vhd new file mode 100644 index 000000000..00d7a5023 --- /dev/null +++ b/vunit/vhdl/verification_components/src/vc_pkg.vhd @@ -0,0 +1,143 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2024, Lars Asplund lars.anders.asplund@gmail.com +-- +-- This package contains common functionality for VCs. + +context work.vunit_context; +context work.com_context; + +package vc_pkg is + type unexpected_msg_type_policy_t is (fail, ignore); + + type std_cfg_t is record + p_id : id_t; + p_actor : actor_t; + p_logger : logger_t; + p_checker : checker_t; + p_unexpected_msg_type_policy : unexpected_msg_type_policy_t; + end record; + + constant null_std_cfg : std_cfg_t := ( + p_id => null_id, + p_actor => null_actor, + p_logger => null_logger, + p_checker => null_checker, + p_unexpected_msg_type_policy => ignore + ); + + -- Creates a standard VC configuration with an id, an actor, a logger, a + -- checker, and an unexpected message type policy. + -- + -- If id = null_id, the id will be assigned the name provider:vc_name:n where n is 1 + -- for the first instance and increasing with one for every additional instance. + -- + -- The id must not have an associated actor before the call as that may indicate + -- several users of the same actor. + -- + -- If a logger exist for the id, it will be reused. If not, a new logger is created. + -- A new checker is created that reports to the logger. + impure function create_std_cfg( + id : id_t := null_id; + provider : string := ""; + vc_name : string := ""; + unexpected_msg_type_policy : unexpected_msg_type_policy_t := fail + ) return std_cfg_t; + + -- These functions extracts information from the standard VC configuration + impure function get_id(std_cfg : std_cfg_t) return id_t; + impure function get_actor(std_cfg : std_cfg_t) return actor_t; + impure function get_logger(std_cfg : std_cfg_t) return logger_t; + impure function get_checker(std_cfg : std_cfg_t) return checker_t; + impure function unexpected_msg_type_policy(std_cfg : std_cfg_t) return unexpected_msg_type_policy_t; + + -- Handle messages with unexpected message type according to the standard configuration + procedure unexpected_msg_type(msg_type : msg_type_t; std_cfg : std_cfg_t); + +end package; + +package body vc_pkg is + constant vc_pkg_logger : logger_t := get_logger("vunit_lib:vc_pkg"); + constant vc_pkg_checker : checker_t := new_checker(vc_pkg_logger); + + impure function create_std_cfg( + id : id_t := null_id; + provider : string := ""; + vc_name : string := ""; + unexpected_msg_type_policy : unexpected_msg_type_policy_t := fail + ) return std_cfg_t is + variable result : std_cfg_t; + variable provider_id : id_t; + variable vc_id : id_t; + begin + if id /= null_id then + result.p_id := id; + else + if provider = "" then + check_failed(vc_pkg_checker, "A provider must be provided."); + + -- Simplifies testing when vc_pkg_checker logger is mocked + return null_std_cfg; + end if; + + if vc_name = "" then + check_failed(vc_pkg_checker, "A VC name must be provided."); + + -- Simplifies testing when vc_pkg_checker logger is mocked + return null_std_cfg; + end if; + + provider_id := get_id(provider); + vc_id := get_id(vc_name, parent => provider_id); + result.p_id := get_id(to_string(num_children(vc_id) + 1), parent => vc_id); + end if; + + result.p_unexpected_msg_type_policy := unexpected_msg_type_policy; + + if find(result.p_id, enable_deferred_creation => false) /= null_actor then + check_failed(vc_pkg_checker, "An actor already exists for " & full_name(result.p_id) & "."); + else + result.p_actor := new_actor(result.p_id); + end if; + + result.p_logger := get_logger(result.p_id); + result.p_checker := new_checker(result.p_logger); + + return result; + end; + + impure function get_id(std_cfg : std_cfg_t) return id_t is + begin + return std_cfg.p_id; + end; + + impure function get_actor(std_cfg : std_cfg_t) return actor_t is + begin + return std_cfg.p_actor; + end; + + impure function get_logger(std_cfg : std_cfg_t) return logger_t is + begin + return std_cfg.p_logger; + end; + + impure function get_checker(std_cfg : std_cfg_t) return checker_t is + begin + return std_cfg.p_checker; + end; + + impure function unexpected_msg_type_policy(std_cfg : std_cfg_t) return unexpected_msg_type_policy_t is + begin + return std_cfg.p_unexpected_msg_type_policy; + end; + + procedure unexpected_msg_type(msg_type : msg_type_t; + std_cfg : std_cfg_t) is + begin + if unexpected_msg_type_policy(std_cfg) = fail then + unexpected_msg_type(msg_type, get_logger(std_cfg)); + end if; + end; +end package body; diff --git a/vunit/vhdl/verification_components/test/tb_apb_requester.vhd b/vunit/vhdl/verification_components/test/tb_apb_requester.vhd new file mode 100644 index 000000000..d78fe11f5 --- /dev/null +++ b/vunit/vhdl/verification_components/test/tb_apb_requester.vhd @@ -0,0 +1,199 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2024, Lars Asplund lars.anders.asplund@gmail.com + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +context work.vunit_context; +context work.com_context; +use work.memory_pkg.all; +use work.bus_master_pkg.all; +use work.apb_completer_pkg.all; +use work.apb_requester_pkg.all; +use work.logger_pkg.all; + +library osvvm; +use osvvm.RandomPkg.all; + +entity tb_apb_requester is + generic ( + runner_cfg : string + ); +end entity; + +architecture a of tb_apb_requester is + + constant BUS_DATA_WIDTH : natural := 16; + constant BUS_ADDRESS_WIDTH : natural := 32; + + signal clk : std_logic := '0'; + signal reset : std_logic := '0'; + signal psel : std_logic; + signal penable : std_logic; + signal paddr : std_logic_vector(BUS_ADDRESS_WIDTH-1 downto 0); + signal pwrite : std_logic; + signal pwdata : std_logic_vector(BUS_DATA_WIDTH-1 downto 0); + signal prdata : std_logic_vector(BUS_DATA_WIDTH-1 downto 0); + signal pready : std_logic := '0'; + + constant bus_handle : apb_requester_t := new_apb_requester(data_length => pwdata'length, + address_length => paddr'length); + constant memory : memory_t := new_memory; + constant slave_handle : apb_completer_t := new_apb_completer(memory => memory, + logger => get_logger("apb slave"), + ready_high_probability => 0.5); + + signal start : boolean := false; +begin + + main_stim : process + variable buf : buffer_t; + variable data, data2 : std_logic_vector(prdata'range); + variable bus_ref1, bus_ref2 : bus_reference_t; + constant unexpected_message_type : msg_type_t := new_msg_type("unexpected message"); + variable unexpected_message : msg_t := new_msg(unexpected_message_type); + begin + show(get_logger("apb slave"), display_handler, debug); + + test_runner_setup(runner, runner_cfg); + start <= true; + wait for 0 ns; + + if run("single_write") then + buf := allocate(memory => memory, num_bytes => 2, permissions => write_only); + mock(get_logger(bus_handle), debug); + set_expected_word(memory, base_address(buf), x"1122"); + write_bus(net, bus_handle, base_address(buf), x"1122"); + wait_until_idle(net, bus_handle); + check_only_log(get_logger(bus_handle), "Wrote 0x1122 to address 0x00000000", debug); + unmock(get_logger(bus_handle)); + check_expected_was_written(memory); + + elsif run("single_read") then + buf := allocate(memory => memory, num_bytes => 2, permissions => read_only); + write_word(memory, base_address(buf), x"1234"); + read_bus(net, bus_handle, base_address(buf), data); + check_equal(data, std_logic_vector'(x"1234"), "Check read data."); + + elsif run("consecutive_reads") then + buf := allocate(memory => memory, num_bytes => 4, permissions => read_only); + write_word(memory, base_address(buf), x"1234"); + write_word(memory, base_address(buf)+2, x"5678"); + read_bus(net, bus_handle, base_address(buf), bus_ref1); + read_bus(net, bus_handle, base_address(buf)+2, bus_ref2); + await_read_bus_reply(net, bus_ref1, data); + check_equal(data, std_logic_vector'(x"1234"), "Check read data."); + await_read_bus_reply(net, bus_ref2, data); + check_equal(data, std_logic_vector'(x"5678"), "Check read data."); + + elsif run("consecutive_writes") then + buf := allocate(memory => memory, num_bytes => 4, permissions => write_only); + set_expected_word(memory, base_address(buf), x"1234"); + set_expected_word(memory, base_address(buf)+2, x"5678"); + write_bus(net, bus_handle, base_address(buf), x"1234"); + write_bus(net, bus_handle, base_address(buf)+2, x"5678"); + wait_until_idle(net, bus_handle); + check_expected_was_written(memory); + + elsif run("many_reads") then + for i in 1 to 100 loop + buf := allocate(memory => memory, num_bytes => 2, permissions => read_only); + data := std_logic_vector(to_unsigned(i, BUS_DATA_WIDTH)); + write_word(memory, base_address(buf), data); + read_bus(net, bus_handle, base_address(buf), data2); + check_equal(data2, data, "Check read data."); + end loop; + + elsif run("many_writes") then + for i in 1 to 100 loop + buf := allocate(memory => memory, num_bytes => 2, permissions => write_only); + data := std_logic_vector(to_unsigned(i, BUS_DATA_WIDTH)); + set_expected_word(memory, base_address(buf), data); + write_bus(net, bus_handle, base_address(buf), data); + end loop; + wait_until_idle(net, bus_handle); + check_expected_was_written(memory); + + elsif run("wait_between_writes") then + buf := allocate(memory => memory, num_bytes => 4, permissions => write_only); + set_expected_word(memory, base_address(buf), x"1234"); + set_expected_word(memory, base_address(buf)+2, x"5678"); + write_bus(net, bus_handle, base_address(buf), x"1234"); + wait_for_time(net, bus_handle, 500 ns); + write_bus(net, bus_handle, base_address(buf)+2, x"5678"); + wait_until_idle(net, bus_handle); + check_expected_was_written(memory); + + elsif run("pslverror_during_read") then + buf := allocate(memory => memory, num_bytes => 2, permissions => read_only); + write_word(memory, base_address(buf), x"1234"); + mock(get_logger("check"), error); + read_bus(net, bus_handle, base_address(buf), data, '1'); + wait_until_idle(net, bus_handle); + check_only_log(get_logger("check"), + "Unexpected pslverror response for read request. - Got 0. Expected 1.", error); + unmock(get_logger("check")); + + elsif run("pslverror_during_write") then + buf := allocate(memory => memory, num_bytes => 2, permissions => write_only); + mock(get_logger("check"), error); + write_bus(net, bus_handle, base_address(buf), x"1122", '1'); + wait_until_idle(net, bus_handle); + check_only_log(get_logger("check"), + "Unexpected pslverror response for write request. - Got 0. Expected 1.", error); + unmock(get_logger("check")); + + elsif run("unexpected_msg_type_policy_fail") then + mock(get_logger("vunit_lib:com"), failure); + send(net, bus_handle.p_bus_handle.p_actor, unexpected_message); + check_only_log(get_logger("vunit_lib:com"), + "Got unexpected message unexpected message", failure); + unmock(get_logger("vunit_lib:com")); + + end if; + + wait for 100 ns; + + test_runner_cleanup(runner); + wait; + end process; + test_runner_watchdog(runner, 100 us); + + U_DUT_REQUESTER: entity work.apb_requester + generic map ( + bus_handle => bus_handle + ) + port map ( + clk => clk, + reset => reset, + psel_o => psel, + penable_o => penable, + paddr_o => paddr, + pwrite_o => pwrite, + pwdata_o => pwdata, + prdata_i => prdata, + pready_i => pready + ); + + U_DUT_COMPLETER: entity work.apb_completer + generic map ( + bus_handle => slave_handle + ) + port map ( + clk => clk, + reset => reset, + psel_i => psel, + penable_i => penable, + paddr_i => paddr, + pwrite_i => pwrite, + pwdata_i => pwdata, + prdata_o => prdata, + pready_o => pready + ); + + clk <= not clk after 5 ns; +end architecture; diff --git a/vunit/vhdl/verification_components/test/tb_vc_pkg.vhd b/vunit/vhdl/verification_components/test/tb_vc_pkg.vhd new file mode 100644 index 000000000..805420e26 --- /dev/null +++ b/vunit/vhdl/verification_components/test/tb_vc_pkg.vhd @@ -0,0 +1,102 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2024, Lars Asplund lars.anders.asplund@gmail.com + +library vunit_lib; +context vunit_lib.vunit_context; +context work.com_context; +use work.vc_pkg.all; + +entity tb_vc_pkg is + generic(runner_cfg : string); +end entity; + +architecture a of tb_vc_pkg is +begin + + main : process + variable std_cfg : std_cfg_t; + variable id : id_t; + variable actor : actor_t; + + constant vc_pkg_logger : logger_t := get_logger("vunit_lib:vc_pkg"); + constant unknown_msg_type : msg_type_t := new_msg_type("unknown_msg"); + begin + test_runner_setup(runner, runner_cfg); + while test_suite loop + if run("Test that provider must be supplied with null_id") then + mock(vc_pkg_logger, error); + std_cfg := create_std_cfg(vc_name => "my_vc"); + check_only_log(vc_pkg_logger, "A provider must be provided.", error); + unmock(vc_pkg_logger); + + elsif run("Test that vc_name must be supplied with null_id") then + mock(vc_pkg_logger, error); + std_cfg := create_std_cfg(provider => "provider"); + check_only_log(vc_pkg_logger, "A VC name must be provided.", error); + unmock(vc_pkg_logger); + + elsif run("Test standard config with specified id") then + id := get_id("id"); + std_cfg := create_std_cfg(id => id); + check(std_cfg.p_id = id); + check(std_cfg.p_actor = find(id, enable_deferred_creation => false)); + check(std_cfg.p_logger = get_logger(id)); + check(get_logger(std_cfg.p_checker) = get_logger(id)); + check(std_cfg.p_unexpected_msg_type_policy = fail); + + elsif run("Test standard config with null_id") then + for instance in 1 to 3 loop + std_cfg := create_std_cfg(provider => "provider", vc_name => "vc_name"); + id := std_cfg.p_id; + check(id = get_id("provider:vc_name:" & to_string(instance))); + check(std_cfg.p_actor = find(id, enable_deferred_creation => false)); + check(std_cfg.p_logger = get_logger(id)); + check(get_logger(std_cfg.p_checker) = get_logger(id)); + check(std_cfg.p_unexpected_msg_type_policy = fail); + end loop; + + elsif run("Test standard config with specified unexpected message type policy") then + std_cfg := create_std_cfg( + provider => "provider", + vc_name => "vc_name", + unexpected_msg_type_policy => ignore + ); + id := std_cfg.p_id; + check(id = get_id("provider:vc_name:1")); + check(std_cfg.p_actor = find(id, enable_deferred_creation => false)); + check(std_cfg.p_logger = get_logger(id)); + check(get_logger(std_cfg.p_checker) = get_logger(id)); + check(std_cfg.p_unexpected_msg_type_policy = ignore); + + elsif run("Test failing on reused actor") then + mock(vc_pkg_logger, error); + id := get_id("foo:bar"); + actor := new_actor(id); + std_cfg := create_std_cfg(id => id); + check_only_log(vc_pkg_logger, "An actor already exists for foo:bar.", error); + unmock(vc_pkg_logger); + + elsif run("Test failing on unexpected message") then + id := get_id("id"); + std_cfg := create_std_cfg(id => id); + mock(get_logger(id), failure); + unexpected_msg_type(unknown_msg_type, std_cfg); + check_only_log(get_logger(id), "Got unexpected message unknown_msg", failure); + unmock(get_logger(id)); + + elsif run("Test ignoring unexpected message") then + id := get_id("id"); + std_cfg := create_std_cfg(id => id, unexpected_msg_type_policy => ignore); + mock(get_logger(id), failure); + unexpected_msg_type(unknown_msg_type, std_cfg); + check_no_log; + unmock(get_logger(id)); + + end if; + end loop; + test_runner_cleanup(runner, fail_on_warning => true); + end process; +end architecture;