diff --git a/vunit/vhdl/verification_components/src/apb_master.vhd b/vunit/vhdl/verification_components/src/apb_master.vhd new file mode 100644 index 000000000..70d3d7880 --- /dev/null +++ b/vunit/vhdl/verification_components/src/apb_master.vhd @@ -0,0 +1,146 @@ +-- 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.log_levels_pkg.all; +use work.apb_master_pkg.all; + +entity apb_master is + generic ( + bus_handle : apb_master_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 + ); +end entity; + +architecture behav of apb_master 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 then + push(message_queue, request_msg); + elsif msg_type = bus_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); + else + unexpected_msg_type(msg_type); + 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'); + begin + loop + drive_bus_invalid; + psel_o <= '0'; + + if is_empty(message_queue) then + wait until rising_edge(clk) and not is_empty(message_queue); + end if; + idle_bus <= false; + wait for 0 ns; + + request_msg := pop(message_queue); + msg_type := message_type(request_msg); + + if msg_type = bus_write_msg then + addr_this_transaction := pop_std_ulogic_vector(request_msg); + data_this_transaction := pop_std_ulogic_vector(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); + + 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; + + elsif msg_type = bus_read_msg then + addr_this_transaction := pop_std_ulogic_vector(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); + + reply_msg := new_msg; + push_std_ulogic_vector(reply_msg, prdata_i); + reply(net, request_msg, reply_msg); + end if; + + idle_bus <= true; + end loop; + end process; +end architecture; diff --git a/vunit/vhdl/verification_components/src/apb_master_pkg.vhd b/vunit/vhdl/verification_components/src/apb_master_pkg.vhd new file mode 100644 index 000000000..3f73745c4 --- /dev/null +++ b/vunit/vhdl/verification_components/src/apb_master_pkg.vhd @@ -0,0 +1,255 @@ +-- 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_master_pkg is + + type apb_master_t is record + -- Private + p_bus_handle : bus_master_t; + p_drive_invalid : boolean; + p_drive_invalid_val : std_logic; + end record; + + impure function new_apb_master( + data_length : natural; + address_length : natural; + logger : logger_t := null_logger; + actor : actor_t := null_actor; + drive_invalid : boolean := true; + drive_invalid_val : std_logic := 'X' + ) return apb_master_t; + + function get_logger(bus_handle : apb_master_t) return logger_t; + + -- Blocking: Write the bus + procedure write_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : std_logic_vector; + constant data : std_logic_vector; + -- default byte enable is all bytes + constant byte_enable : std_logic_vector := ""); + procedure write_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : natural; + constant data : std_logic_vector; + -- default byte enable is all bytes + constant byte_enable : std_logic_vector := ""); + + procedure wait_until_idle(signal net : inout network_t; + bus_handle : apb_master_t); + + -- Non blocking: Read the bus returning a reference to the future reply + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : std_logic_vector; + variable reference : inout bus_reference_t); + + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : natural; + variable reference : inout bus_reference_t); + + -- Blocking: read bus with immediate reply + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : std_logic_vector; + variable data : inout std_logic_vector); + + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : natural; + variable data : inout std_logic_vector); + + -- Blocking: Read bus and check result against expected data + procedure check_bus(signal net : inout network_t; + constant bus_handle : apb_master_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_master_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_master_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_master_t; + addr : std_logic_vector; + idx : natural; + value : std_logic; + timeout : delay_length := delay_length'high; + msg : string := ""); +end package; + +package body apb_master_pkg is + + impure function new_apb_master( + data_length : natural; + address_length : natural; + logger : logger_t := null_logger; + actor : actor_t := null_actor; + drive_invalid : boolean := true; + drive_invalid_val : std_logic := 'X' + ) return apb_master_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; + begin + if logger = null_logger then + logger_tmp := bus_logger; + else + logger_tmp := logger; + end if; + return ( + p_bus_handle => create_bus(logger_tmp), + p_drive_invalid => drive_invalid, + p_drive_invalid_val => drive_invalid_val + ); + end; + + function get_logger(bus_handle : apb_master_t) return logger_t is + begin + return get_logger(bus_handle.p_bus_handle); + end function; + + -- Blocking: Write the bus + procedure write_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : std_logic_vector; + constant data : std_logic_vector; + -- default byte enable is all bytes + constant byte_enable : std_logic_vector := "") is + begin + write_bus(net, bus_handle.p_bus_handle, address, data, byte_enable); + end procedure; + + procedure write_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : natural; + constant data : std_logic_vector; + -- default byte enable is all bytes + constant byte_enable : std_logic_vector := "") is + begin + write_bus(net, bus_handle.p_bus_handle, address, data, byte_enable); + end procedure; + + procedure wait_until_idle(signal net : inout network_t; + bus_handle : apb_master_t) is + begin + wait_until_idle(net, bus_handle.P_bus_handle); + end procedure; + + -- Blocking: read bus with immediate reply + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : std_logic_vector; + variable data : inout std_logic_vector) is + begin + read_bus(net, bus_handle.p_bus_handle, address, data); + end procedure; + + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : natural; + variable data : inout std_logic_vector) is + begin + read_bus(net, bus_handle.p_bus_handle, address, data); + end procedure; + + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : natural; + variable reference : inout bus_reference_t) is + begin + read_bus(net, bus_handle.p_bus_handle, address, reference); + end procedure; + + procedure read_bus(signal net : inout network_t; + constant bus_handle : apb_master_t; + constant address : std_logic_vector; + variable reference : inout bus_reference_t) is + begin + read_bus(net, bus_handle.p_bus_handle, address, reference); + end procedure; + + -- Blocking: Read bus and check result against expected data + procedure check_bus(signal net : inout network_t; + constant bus_handle : apb_master_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_master_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_master_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_master_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; +end package body; \ No newline at end of file diff --git a/vunit/vhdl/verification_components/src/apb_slave.vhd b/vunit/vhdl/verification_components/src/apb_slave.vhd new file mode 100644 index 000000000..b4b61a7a4 --- /dev/null +++ b/vunit/vhdl/verification_components/src/apb_slave.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_slave_pkg.all; +use work.logger_pkg.all; + +entity apb_slave is + generic ( + bus_handle : apb_slave_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_slave 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_slave_pkg.vhd b/vunit/vhdl/verification_components/src/apb_slave_pkg.vhd new file mode 100644 index 000000000..54adb5f71 --- /dev/null +++ b/vunit/vhdl/verification_components/src/apb_slave_pkg.vhd @@ -0,0 +1,77 @@ +-- 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_slave_pkg is + + type apb_slave_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_slave_logger : logger_t := get_logger("vunit_lib:apb_slave_pkg"); + impure function new_apb_slave( + 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_slave_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_slave_pkg is + + impure function new_apb_slave( + 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_slave_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/test/tb_apb_master.vhd b/vunit/vhdl/verification_components/test/tb_apb_master.vhd new file mode 100644 index 000000000..719e96f30 --- /dev/null +++ b/vunit/vhdl/verification_components/test/tb_apb_master.vhd @@ -0,0 +1,161 @@ +-- 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_slave_pkg.all; +use work.apb_master_pkg.all; +use work.logger_pkg.all; + +library osvvm; +use osvvm.RandomPkg.all; + +entity tb_apb_master is + generic ( + runner_cfg : string + ); +end entity; + +architecture a of tb_apb_master 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_master_t := new_apb_master(data_length => pwdata'length, + address_length => paddr'length); + constant memory : memory_t := new_memory; + constant slave_handle : apb_slave_t := new_apb_slave(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; + 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); + + end if; + + wait for 100 ns; + + test_runner_cleanup(runner); + wait; + end process; + test_runner_watchdog(runner, 100 us); + + U_DUT_MASTER: entity work.apb_master + 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_SLAVE: entity work.apb_slave + 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;