From 5ada904040e6c39f628a44e249fc83db27a87320 Mon Sep 17 00:00:00 2001 From: David Bailey Date: Mon, 17 Apr 2023 16:56:33 +0200 Subject: [PATCH] test: :test_tube: add a first working test for VHDL execution <3 --- bin/console | 102 +++++++++++++-- test/faux_build_dir/UQDS_specifics_pkg.vhd | 138 +++++++++++++++++++++ test/faux_build_dir/comf.yml | 1 + test/faux_build_dir/lib_test/comf.yml | 3 + test/faux_build_dir/spi_master.vhd | 129 +++++++++++++++++++ test/faux_build_dir/spi_master_tb.vhd | 108 ++++++++++++++++ 6 files changed, 472 insertions(+), 9 deletions(-) create mode 100644 test/faux_build_dir/UQDS_specifics_pkg.vhd create mode 100644 test/faux_build_dir/comf.yml create mode 100644 test/faux_build_dir/lib_test/comf.yml create mode 100644 test/faux_build_dir/spi_master.vhd create mode 100644 test/faux_build_dir/spi_master_tb.vhd diff --git a/bin/console b/bin/console index a79ec9a..db8daa8 100755 --- a/bin/console +++ b/bin/console @@ -23,6 +23,98 @@ $core.add_artefact_engine Comfpile::ParserEngine, regex: /\/\/\s*comf\.(?\w+)[=:]\s*(?.+)/ } ] +$core.add_artefact_engine Comfpile::ParserEngine, + file_regex: /^(.+)\.vhd$/, + search_regexes: [ + { + regex: /--+\s*comf\.(?[^:]+)[=:]\s*(?.+)/ + } +] + +$core.add_artefact_engine do |engine| + engine.add_recipe(:ghdl_analysed, /^(.+)\.vhd/) do |match, a| + a.parent_artefact :dependency_analysis_include, a.target + + a.add_step do + @parent_artefact.dependencies.each do |dependency| + next if dependency.target == a.target + + log "Adding dependency for ghdl analysis of #{dependency.target}..." + + require_artefact :ghdl_analysed, dependency.target + end + end + + a.add_step do + work_library = find_parsed_parameter('vhdl.work') + if work_library.nil? + work_library = '' + else + work_library = '--work=' + work_library + end + + cmd = "ghdl -a -fsynopsys --std=08 #{work_library} #{self.file}" + log "Executing: #{cmd}" + + `#{cmd}` + end + end + + engine.add_recipe(:ghdl_elaborated, /^(.+)\.vhd/) do |match, a| + a.parent_artefact :dependency_analysis, a.target + + a.add_step do + dependencies.each do |dep| + require_artefact :ghdl_analysed, dep.target + end + end + + a.add_step do + work_library = find_parsed_parameter('vhdl.work') + if work_library.nil? + work_library = '' + else + work_library = '--work=' + work_library + end + + elaborate_arch = find_parsed_parameter('vhdl.elaborate') || File.basename(@target).chomp(File.extname(@target)) + + cmd = "ghdl -e -fsynopsys --std=08 #{work_library} #{elaborate_arch}" + log "Executing: #{cmd}" + + `#{cmd}` + end + end + + engine.add_recipe :ghdl_run, /^(.+)\.vhd/ do |match, a| + a.parent_artefact :ghdl_elaborated, a.target + + a.add_step do + work_library = find_parsed_parameter('vhdl.work') + if work_library.nil? + work_library = '' + else + work_library = '--work=' + work_library + end + + elaborate_arch = find_parsed_parameter('vhdl.elaborate') || File.basename(@target).chomp(File.extname(@target)) + + @parameters[:ghdl_arch] = elaborate_arch + + cmd = "ghdl -r -fsynopsys --std=08 #{work_library} #{elaborate_arch} --wave=#{elaborate_arch}.ghw" + log "Executing: #{cmd}" + + `#{cmd}` + end + end + + engine.add_recipe(:gtkwave_output, /^(.+)\.vhd/) do |match, a| + a.parent_artefact :ghdl_run, a.target + a.add_step do + `gtkwave #{@parent_artefact[:ghdl_arch]}.ghw` + end + end +end $core.add_artefact_engine do |engine| @@ -54,20 +146,12 @@ $core.add_artefact_engine do |engine| end t_start = Time.now() -dep_art = $core.craft_and_complete(:dependency_analysis, "main.cpp") +dep_art = $core.craft_and_complete(:gtkwave_output, "spi_master_tb.vhd") t_end = Time.now() puts "Full dependency list is: #{dep_art.dependencies.map(&:target)} (took #{t_end - t_start})" puts "Includes of all source files:" -dep_art.dependencies.each do |art| - next unless ['.cpp', '.c'].include? File.extname(art.target) - - include_art = $core.craft_and_complete(:dependency_analysis_include, art.target) - - puts "Included dependencies for #{art.target} are #{include_art.dependencies.map(&:target)}" -end - # (If you use this, don't forget to add pry to your Gemfile!) require "pry" Pry.start diff --git a/test/faux_build_dir/UQDS_specifics_pkg.vhd b/test/faux_build_dir/UQDS_specifics_pkg.vhd new file mode 100644 index 0000000..9870f7e --- /dev/null +++ b/test/faux_build_dir/UQDS_specifics_pkg.vhd @@ -0,0 +1,138 @@ +-- ! @author David Bailey (d.bailey@cern.ch) + +-- comf.reference: spi_master.vhd +-- comf.vhdl.work: uqdslib + +LIBRARY IEEE; +USE IEEE.std_logic_1164.ALL; +USE IEEE.numeric_std.ALL; + +package UQDS_specifics_pkg is + type spi_control_to_t is record + --! Byte to be sent out through the SPI MOSI pin. + --! Data is latched when send_request is 1 AND SPI port is idle. + --! When the data is latched, byte_complete goes 1 for 1 clk tick + data_tx : std_logic_vector(7 downto 0); + + --! Hold high to request another byte to be sent out. + --! Once the byte has been latched, byte_complete will go high + --! for one clk. The code may then prepare the next byte to + --! be sent out. + send_request : std_logic; + end record spi_control_to_t; + + type spi_control_from_t is record + --! Byte that was received from the MISO pin. Latched in with together + --! with the byte_complete signal. Valid for the entire byte. + data_rx : std_logic_vector(7 downto 0); + + --! High for one clk whenever a SPI transaction has occured. + --! This signal has two functons: + --! 1. The data_tx byte has been latched, next byte may be prepared + byte_tx_complete : std_logic; + byte_rx_complete : std_logic; + end record spi_control_from_t; + + component uart_simple_rx is + generic ( + --! Baudrate clock divider. Set to `clk / baudrate` + baud_clk_div : natural := 40000; + --! Internal value, do not modify + baud_clk_div_max : natural := baud_clk_div*3/2 + ); + port ( + --! Active-high, synchronous reset + rst : in std_logic; + --! Main clock input + clk : in std_logic; + + --! UART RX IO Pin + pin_rx : in std_logic; + + --! Last fully captured byte of data, valid + --! on the same clk cycle that `data_rx_pulse` goes high + data_rx : out unsigned(7 downto 0); + --! Pulsed for one clk cycle to indicate reception of a new byte + data_rx_pulse : out std_logic + ); + end component; + + type uqds_powersupply_data_t is record + temperature_k : integer; + voltage_5V_mv : integer; + voltage_5V0_mv : integer; + voltage_15V_mv : integer; + end record uqds_powersupply_data_t; + + type uqds_powersupply_data_array_t is array (natural range <>) of uqds_powersupply_data_t; + + component powersupply_monitor + generic ( + --! Clock divider for the device timeout generation. + device_timeout_clkdiv : natural := 500 * 40000; + --! Clock divider for the UART packet reset. Set to approx. + --! twice the clock cycles of one byte transmission. + uart_rx_timeout_clkdiv : natural := 30 * 40000 + ); + port ( + --! Synchronous reset + rst : in std_logic; + --! Clock, rising edge logic + clk : in std_logic; + + --! Connect to UART RX module + uart_data_rx : in unsigned(7 downto 0); + uart_data_rx_pulse : in std_logic; + + --! Array of pre-converted measurements. Zeroed upon + --! device timeout + measurements : out uqds_powersupply_data_t; + --! '1' as long as valid data is being received from the + --! power supply + psu_ok : out std_logic + ); + end component; + + + component powersupply_monitor_top + generic ( + --! Number of PSUs to instantiate + psu_count : positive := 2; + --! Clock divider for the UART Baudrate + psu_uart_clkdiv : natural := 1 * 40000; + --! Clock divider for the timeout of the PSU. + --! Once timed out, the device will be assumed nonfunctional + psu_timeout_clkdiv : natural := 500 * 40000 + ); + port ( + rst : in std_logic; + clk : in std_logic; + + pins_uart_rx : in std_logic_vector(psu_count-1 downto 0); + + --! Array of PSU measurements. Each power supply reports on its temperature, + --! internal and external 5V levels, and 15V level. + --! If the PSU is offline, these values will be reset to zero for the + --! respective power supply. + psu_measurements : out uqds_powersupply_data_array_t(psu_count-1 downto 0) + ); + end component; + + component spi_master + generic ( + clkdiv : natural := 400; + clk_idle : std_logic := '1' + ); + port ( + rst : in std_logic; + clk : in std_logic; + + spi_control_to : in spi_control_to_t; + spi_control_from : out spi_control_from_t; + + pin_mosi : out std_logic; + pin_miso : in std_logic; + pin_clk : out std_logic + ); + end component; +end package; \ No newline at end of file diff --git a/test/faux_build_dir/comf.yml b/test/faux_build_dir/comf.yml new file mode 100644 index 0000000..2646a61 --- /dev/null +++ b/test/faux_build_dir/comf.yml @@ -0,0 +1 @@ +test: Hellooooo diff --git a/test/faux_build_dir/lib_test/comf.yml b/test/faux_build_dir/lib_test/comf.yml new file mode 100644 index 0000000..f53e038 --- /dev/null +++ b/test/faux_build_dir/lib_test/comf.yml @@ -0,0 +1,3 @@ +defaults: + test: yeah + nom: nom \ No newline at end of file diff --git a/test/faux_build_dir/spi_master.vhd b/test/faux_build_dir/spi_master.vhd new file mode 100644 index 0000000..60d25fc --- /dev/null +++ b/test/faux_build_dir/spi_master.vhd @@ -0,0 +1,129 @@ +--! @author David Bailey (d.bailey@cern.ch) + +-- comf.include: UQDS_specifics_pkg.vhd +-- comf.vhdl.work: uqdslib + +library ieee; +use ieee.std_logic_1164.all; +use ieee.std_logic_unsigned.all; +use ieee.numeric_std.all; + +Library UQDSLib; +use UQDSLib.UQDS_specifics_pkg.all; + +--! @brief Simple 8-bit fixed length SPI master +--! @details This VHDL code is intended to provide a basic data I/O +--! interface to easily interface with standard SPI-based peripherals. +--! Its main purpose is to clock data in/out. It does *not* contain +--! arbitratrion nor chip-select handling! +entity spi_master is + generic( + --! Will divide the FPGA-Clock by this value for the SPI clock + clkdiv : natural := 40; + clk_idle : std_logic := '1' + ); + port( + --! Resets the SPI port. Will stop it mid-transition. + --! Forces data_rx to 0. + rst : in std_logic; + --! FPGA Clock to run internal logic at + clk : in std_logic; + + spi_control_to : in spi_control_to_t; + spi_control_from : out spi_control_from_t; + + pin_mosi : out std_logic; + pin_miso : in std_logic; + pin_clk : out std_logic + ); +end; + +architecture rtl of spi_master is + type spi_state_t is ( IDLE, HIGH_CLK, LOW_CLK ); + signal spi_state : spi_state_t := IDLE; + + signal clk_divider : integer range 0 to clkdiv/2 ; + signal bit_cnt : integer range 0 to 7; + + signal data_rx_fragment : std_logic_vector(7 downto 0); + signal data_tx_fragment : std_logic_vector(6 downto 0); + + signal byte_tx_complete_i : std_logic := '0'; + signal byte_rx_complete_i : std_logic := '0'; +begin + + spi_control_from.byte_tx_complete <= byte_tx_complete_i; + spi_control_from.byte_rx_complete <= byte_rx_complete_i; + + spi_transmission: process(clk) + begin + if(rising_edge(clk)) then + byte_tx_complete_i <= '0'; + byte_rx_complete_i <= '0'; + + --! Reset clause, the rest of the code won't get to run + --! while reset asserts + if(rst = '1') then + spi_state <= IDLE; + + clk_divider <= 0; + bit_cnt <= 0; + + data_rx_fragment <= (others => '0'); + data_tx_fragment <= (others => '0'); + + spi_control_from.data_rx <= (others => '0'); + + byte_tx_complete_i <= '0'; + byte_rx_complete_i <= '0'; + + pin_mosi <= '0'; + pin_clk <= clk_idle; + elsif(clk_divider > 0) then + clk_divider <= clk_divider - 1; + else + case spi_state is + when IDLE => + if(spi_control_to.send_request) then + data_tx_fragment <= spi_control_to.data_tx(6 downto 0); + data_rx_fragment <= (others => '0'); + + clk_divider <= clkdiv / 2; + bit_cnt <= 7; + + pin_clk <= not clk_idle; + pin_mosi <= spi_control_to.data_tx(spi_control_to.data_tx'left); + + spi_state <= HIGH_CLK; + + byte_tx_complete_i <= '1'; + end if; + when HIGH_CLK => + pin_clk <= clk_idle; + + data_rx_fragment <= data_rx_fragment(6 downto 0) & pin_miso; + + clk_divider <= clkdiv / 2; + spi_state <= LOW_CLK; + + if(bit_cnt = 0) then + spi_control_from.data_rx <= data_rx_fragment(6 downto 0) & pin_miso; + byte_rx_complete_i <= '1'; + + spi_state <= IDLE; + else + bit_cnt <= bit_cnt - 1; + end if; + when LOW_CLK => + pin_clk <= not clk_idle; + pin_mosi <= data_tx_fragment(data_tx_fragment'left); + data_tx_fragment <= data_tx_fragment(data_tx_fragment'left-1 downto 0) & '0'; + + clk_divider <= clkdiv / 2; + spi_state <= HIGH_CLK; + end case; + end if; + end if; + end process; + +end architecture; \ No newline at end of file diff --git a/test/faux_build_dir/spi_master_tb.vhd b/test/faux_build_dir/spi_master_tb.vhd new file mode 100644 index 0000000..e299bcb --- /dev/null +++ b/test/faux_build_dir/spi_master_tb.vhd @@ -0,0 +1,108 @@ + +-- comf.include: UQDS_specifics_pkg.vhd + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library uqdslib; +use uqdslib.UQDS_Specifics_pkg.all; + +entity spi_master_tb is +end; + +architecture bench of spi_master_tb is + -- Clock period + constant clk_period : time := 5 ns; + -- Generics + constant clkdiv : integer := 40; + constant clk_idle : std_logic := '1'; + + -- Ports + signal rst : std_logic := '0'; + signal clk : std_logic; + signal data_tx : std_logic_vector(7 downto 0); + signal data_rx : std_logic_vector(7 downto 0); + signal send_request : std_logic; + signal byte_tx_complete : std_logic; + signal byte_rx_complete : std_logic; + signal pin_mosi : std_logic; + signal pin_miso : std_logic; + signal pin_clk : std_logic; + + signal test_done : std_logic := '0'; + +begin + + spi_master_inst : uqdslib.UQDS_Specifics_pkg.spi_master + generic map ( + clkdiv => clkdiv, + clk_idle => clk_idle + ) + port map ( + rst => rst, + clk => clk, + spi_control_to.data_tx => data_tx, + spi_control_to.send_request => send_request, + spi_control_from.data_rx => data_rx, + spi_control_from.byte_tx_complete => byte_tx_complete, + spi_control_from.byte_rx_complete => byte_rx_complete, + pin_mosi => pin_mosi, + pin_miso => pin_miso, + pin_clk => pin_clk + ); + + pin_miso <= pin_mosi; + + clk_process : process + begin + clk <= '1'; + wait for clk_period/2; + clk <= '0'; + wait for clk_period/2; + + if(test_done) then wait; end if; + end process clk_process; + + data_process : process + begin + + rst <= '1'; + wait for 100 ns; + rst <= '0'; + + for i in 0 to 4 loop + data_tx <= std_logic_vector(to_unsigned(i, data_tx'length)); + send_request <= '1'; + wait until falling_edge(byte_tx_complete) for 1 ms; + send_request <= '0'; + end loop; + + for i in 0 to 4 loop + data_tx <= std_logic_vector(to_unsigned(i, data_tx'length)); + send_request <= '1'; + wait until falling_edge(byte_tx_complete) for 1 ms; + send_request <= '0'; + + wait for 1.5 us; + end loop; + + data_tx <= std_logic_vector(to_unsigned(69, data_tx'length)); + send_request <= '1'; + wait until falling_edge(byte_tx_complete) for 1 ms; + send_request <= '0'; + + wait for 400 ns; + rst <= '1'; + wait for 100 ns; + rst <= '0'; + wait for 3 us; + + test_done <= '1'; + + wait until falling_edge(byte_rx_complete) for 1 us; + + wait; + + end process data_process; +end;