From 393d87f9d27cd7e9e0db46bb28f2191ef16eb76f Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Mon, 26 Aug 2024 23:11:16 +0200 Subject: [PATCH] Add monitoring of ADC sample and min/max range --- fpga/rtl/acquisition_chain.vhd | 24 +------- fpga/rtl/adc_range_monitor.vhd | 107 +++++++++++++++++++++++++++++++++ fpga/rtl/puzzlefw_pkg.vhd | 14 ++++- fpga/rtl/puzzlefw_top.vhd | 69 ++++++++++++++------- fpga/rtl/registers.vhd | 20 ++++++ fpga/rtl/trigger_detector.vhd | 9 +-- fpga/vivado/nonproject.tcl | 1 + os/src/userspace/testje.c | 30 +++++++++ 8 files changed, 221 insertions(+), 53 deletions(-) create mode 100644 fpga/rtl/adc_range_monitor.vhd diff --git a/fpga/rtl/acquisition_chain.vhd b/fpga/rtl/acquisition_chain.vhd index 2495c29..bf0e9f3 100644 --- a/fpga/rtl/acquisition_chain.vhd +++ b/fpga/rtl/acquisition_chain.vhd @@ -54,9 +54,6 @@ entity acquisition_chain is -- Ignored if num_channels == 2. ch4_mode: in std_logic; - -- High to use simulated samples in place of real ADC samples. - simulate_adc: in std_logic; - -- High to enable automatic (continuous) triggering. trig_auto_en: in std_logic; @@ -142,25 +139,6 @@ begin sample_integrate => s_sample_integrate, sample_done => s_sample_done ); - -- Optionally generate simulated ADC data. - inst_adc_sample_stream: entity work.adc_sample_stream - port map ( - clk => clk, - reset => reset, - simulate => simulate_adc, - in_data => adc_data_in(0 to 1), - out_data => s_adc_sample(0 to 1) ); - - inst_adc_sample_stream_gen: if num_channels > 2 generate - inst_adc_sample_stream2: entity work.adc_sample_stream - port map ( - clk => clk, - reset => reset, - simulate => simulate_adc, - in_data => adc_data_in(2 to 3), - out_data => s_adc_sample(2 to 3) ); - end generate; - -- Shift and decimation chains for each ADC channel. inst_channels: for i in 0 to num_channels - 1 generate @@ -172,7 +150,7 @@ begin signed_data => false ) port map ( clk => clk, - in_data => s_adc_sample(i), + in_data => adc_data_in(i), in_shift => shift_steps, out_data => s_adc_shifted(i) ); diff --git a/fpga/rtl/adc_range_monitor.vhd b/fpga/rtl/adc_range_monitor.vhd new file mode 100644 index 0000000..125ae7b --- /dev/null +++ b/fpga/rtl/adc_range_monitor.vhd @@ -0,0 +1,107 @@ +-- +-- Monitor min/max sample values from ADC. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.puzzlefw_pkg.all; + + +entity adc_range_monitor is + + generic ( + -- True if ADC samples are signed values. + -- False if ADC samples are unsigned binary offset values. + signed_data: boolean + ); + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Reset, active high, synchronous to main clock. + reset: in std_logic; + + -- High to clear min/max sample values. + clear: in std_logic; + + -- Input sample stream. + in_data: in adc_data_type; + + -- Minimum and maximum sample value observed. + min_value: out adc_data_type; + max_value: out adc_data_type + ); + +end entity; + +architecture arch of adc_range_monitor is + + type regs_type is record + min_value: adc_data_type; + max_value: adc_data_type; + end record; + + signal r: regs_type; + signal rnext: regs_type; + + -- Return True if X is less than Y. + function sample_less(x: adc_data_type; y: adc_data_type) + return boolean + is begin + if signed_data then + return signed(x) < signed(y); + else + return unsigned(x) < unsigned(y); + end if; + end function; + +begin + + -- Drive output. + min_value <= r.min_value; + max_value <= r.max_value; + + -- + -- Combinatorial process. + -- + process (all) is + variable v: regs_type; + begin + -- Load current register values. + v := r; + + -- Update min value. + if (reset = '1') + or (clear = '1') + or sample_less(in_data, r.min_value) then + v.min_value := in_data; + end if; + + -- Update max value. + if (reset = '1') + or (clear = '1') + or sample_less(r.max_value, in_data) then + v.max_value := in_data; + end if; + + -- Drive new register values to synchronous process. + rnext <= v; + + end process; + + -- + -- Synchronous process. + -- + process (clk) is + begin + if rising_edge(clk) then + r <= rnext; + end if; + end process; + +end architecture; diff --git a/fpga/rtl/puzzlefw_pkg.vhd b/fpga/rtl/puzzlefw_pkg.vhd index ad960e6..91e8d50 100644 --- a/fpga/rtl/puzzlefw_pkg.vhd +++ b/fpga/rtl/puzzlefw_pkg.vhd @@ -67,6 +67,13 @@ package puzzlefw_pkg is constant reg_trigger_mode: natural := 16#000240#; constant reg_trigger_delay: natural := 16#000244#; constant reg_trigger_status: natural := 16#000248#; + constant reg_adc_sample: natural := 16#000280#; + constant reg_adc23_sample: natural := 16#000284#; + constant reg_adc_range_clear: natural := 16#00028c#; + constant reg_adc0_minmax: natural := 16#000290#; + constant reg_adc1_minmax: natural := 16#000294#; + constant reg_adc2_minmax: natural := 16#000298#; + constant reg_adc3_minmax: natural := 16#00029c#; constant reg_test_led: natural := 16#000404#; constant reg_test_divider: natural := 16#000408#; constant reg_dma_buf_addr: natural := 16#100000#; @@ -75,7 +82,7 @@ package puzzlefw_pkg is -- Firmware info word. constant fw_api_version: natural := 1; constant fw_version_major: natural := 0; - constant fw_version_minor: natural := 3; + constant fw_version_minor: natural := 4; constant fw_info_word: std_logic_vector(31 downto 0) := x"4a" & std_logic_vector(to_unsigned(fw_api_version, 8)) @@ -116,6 +123,7 @@ package puzzlefw_pkg is trig_ext_select: std_logic_vector(1 downto 0); trig_ext_falling: std_logic; trigger_delay: std_logic_vector(15 downto 0); + adc_range_clear: std_logic; dma_buf_addr: std_logic_vector(31 downto 12); dma_buf_size: std_logic_vector(31 downto 12); end record; @@ -132,6 +140,9 @@ package puzzlefw_pkg is acq_addr_ptr: std_logic_vector(31 downto 3); acq_channel_busy: std_logic; trig_waiting: std_logic; + adc_sample: adc_data_array(0 to 3); + adc_min_value: adc_data_array(0 to 3); + adc_max_value: adc_data_array(0 to 3); end record; constant registers_control_init: registers_control := ( @@ -162,6 +173,7 @@ package puzzlefw_pkg is trig_ext_select => (others => '0'), trig_ext_falling => '0', trigger_delay => (others => '0'), + adc_range_clear => '0', dma_buf_addr => (others => '0'), dma_buf_size => (others => '0') ); diff --git a/fpga/rtl/puzzlefw_top.vhd b/fpga/rtl/puzzlefw_top.vhd index 7a505f1..464c483 100644 --- a/fpga/rtl/puzzlefw_top.vhd +++ b/fpga/rtl/puzzlefw_top.vhd @@ -136,21 +136,22 @@ architecture arch of puzzlefw_top is signal s_reg_control: registers_control; signal s_reg_status: registers_status; - signal s_dma_write_cmd_addr: dma_address_array(0 downto 0); - signal s_dma_write_cmd_length: dma_burst_length_array(0 to 0); - signal s_dma_write_cmd_valid: std_logic_vector(0 downto 0); - signal s_dma_write_cmd_ready: std_logic_vector(0 downto 0); - signal s_dma_write_data: dma_data_array(0 downto 0); - signal s_dma_write_data_ready: std_logic_vector(0 downto 0); - signal s_dma_write_finished: std_logic_vector(0 downto 0); + signal s_dma_write_cmd_addr: dma_address_array(0 downto 0); + signal s_dma_write_cmd_length: dma_burst_length_array(0 to 0); + signal s_dma_write_cmd_valid: std_logic_vector(0 downto 0); + signal s_dma_write_cmd_ready: std_logic_vector(0 downto 0); + signal s_dma_write_data: dma_data_array(0 downto 0); + signal s_dma_write_data_ready: std_logic_vector(0 downto 0); + signal s_dma_write_finished: std_logic_vector(0 downto 0); signal s_timestamp: std_logic_vector(timestamp_bits - 1 downto 0); signal s_adc_data: adc_data_array(0 to 1); + signal s_adc_sample: adc_data_array(0 to 1); - signal s_acq_dma_valid: std_logic; - signal s_acq_dma_ready: std_logic; - signal s_acq_dma_empty: std_logic; - signal s_acq_dma_data: dma_data_type; + signal s_acq_dma_valid: std_logic; + signal s_acq_dma_ready: std_logic; + signal s_acq_dma_empty: std_logic; + signal s_acq_dma_data: dma_data_type; signal r_test_prefetch: std_logic; signal r_test_raddr: std_logic_vector(9 downto 0); @@ -397,17 +398,44 @@ begin -- Capture ADC data. -- Ignore the 2 LSB bits which are not-connected on the ADC side. - inst_adc_capture1: entity work.adc_capture - port map ( - clk => clk_adc, - in_data => adc_dat_i(0)(15 downto 2), - out_data => s_adc_data(0) ); + inst_capture_gen: for i in 0 to 1 generate + inst_adc_capture: entity work.adc_capture + port map ( + clk => clk_adc, + in_data => adc_dat_i(i)(15 downto 2), + out_data => s_adc_data(i) ); + end generate; - inst_adc_capture2: entity work.adc_capture + -- Optionally generate simulated ADC samples. + inst_adc_sample_stream: entity work.adc_sample_stream port map ( clk => clk_adc, - in_data => adc_dat_i(1)(15 downto 2), - out_data => s_adc_data(1) ); + reset => s_reset, + simulate => s_reg_control.simulate_adc, + in_data => s_adc_data(0 to 1), + out_data => s_adc_sample(0 to 1) ); + + -- Monitor range of ADC samples. + inst_monitor_gen: for i in 0 to 1 generate + inst_range_monitor: entity work.adc_range_monitor + generic map ( + signed_data => false ) + port map ( + clk => clk_adc, + reset => s_reset, + clear => s_reg_control.adc_range_clear, + in_data => s_adc_sample(i), + min_value => s_reg_status.adc_min_value(i), + max_value => s_reg_status.adc_max_value(i) ); + end generate; + + -- Monitor current ADC sample value. + s_reg_status.adc_sample(0 to 1) <= s_adc_sample(0 to 1); + + -- Drive dummy values to not-implemented channels 2, 3. + s_reg_status.adc_sample(2 to 3) <= (others => (others => '0')); + s_reg_status.adc_min_value(2 to 3) <= (others => (others => '0')); + s_reg_status.adc_max_value(2 to 3) <= (others => (others => '0')); -- Analog acquisition data chain. inst_acquisition_chain: entity work.acquisition_chain @@ -423,14 +451,13 @@ begin averaging => s_reg_control.averaging_en, shift_steps => s_reg_control.shift_steps, ch4_mode => s_reg_control.ch4_mode, - simulate_adc => s_reg_control.simulate_adc, trig_auto_en => s_reg_control.trig_auto_en, trig_ext_en => s_reg_control.trig_ext_en, trig_force => s_reg_control.trig_force, trig_ext_select => s_reg_control.trig_ext_select, trig_ext_falling => s_reg_control.trig_ext_falling, timestamp_in => s_timestamp, - adc_data_in => s_adc_data, + adc_data_in => s_adc_sample, trig_ext_in => "0000", -- TODO trig_waiting => s_reg_status.trig_waiting, out_valid => s_acq_dma_valid, diff --git a/fpga/rtl/registers.vhd b/fpga/rtl/registers.vhd index 91dc874..ddb8b62 100644 --- a/fpga/rtl/registers.vhd +++ b/fpga/rtl/registers.vhd @@ -75,6 +75,7 @@ begin v.reg_control.acq_channel_init := '0'; v.reg_control.acq_intr_clear := '0'; v.reg_control.trig_force := '0'; + v.reg_control.adc_range_clear := '0'; -- Respond to each APB access on the next clock cycle (no wait states). v.pready := apb_psel and (not apb_penable); @@ -122,6 +123,24 @@ begin v.prdata(7) := r.reg_control.trig_ext_falling; when reg_trigger_delay => v.prdata(15 downto 0) := r.reg_control.trigger_delay; when reg_trigger_status => v.prdata(0) := reg_status.trig_waiting; + when reg_adc_sample => + v.prdata(adc_data_bits - 1 downto 0) := reg_status.adc_sample(0); + v.prdata(adc_data_bits + 15 downto 16) := reg_status.adc_sample(1); + when reg_adc23_sample => + v.prdata(adc_data_bits - 1 downto 0) := reg_status.adc_sample(2); + v.prdata(adc_data_bits + 15 downto 16) := reg_status.adc_sample(3); + when reg_adc0_minmax => + v.prdata(adc_data_bits - 1 downto 0) := reg_status.adc_min_value(0); + v.prdata(adc_data_bits + 15 downto 16) := reg_status.adc_max_value(0); + when reg_adc1_minmax => + v.prdata(adc_data_bits - 1 downto 0) := reg_status.adc_min_value(1); + v.prdata(adc_data_bits + 15 downto 16) := reg_status.adc_max_value(1); + when reg_adc2_minmax => + v.prdata(adc_data_bits - 1 downto 0) := reg_status.adc_min_value(2); + v.prdata(adc_data_bits + 15 downto 16) := reg_status.adc_max_value(2); + when reg_adc3_minmax => + v.prdata(adc_data_bits - 1 downto 0) := reg_status.adc_min_value(3); + v.prdata(adc_data_bits + 15 downto 16) := reg_status.adc_max_value(3); when reg_test_led => v.prdata(7 downto 0) := r.reg_control.test_led; when reg_test_divider => v.prdata(15 downto 0) := r.reg_control.test_divider; when reg_dma_buf_addr => v.prdata(31 downto 12) := r.reg_control.dma_buf_addr; @@ -162,6 +181,7 @@ begin v.reg_control.trig_ext_falling := apb_pwdata(7); v.reg_control.trig_force := apb_pwdata(8); when reg_trigger_delay => v.reg_control.trigger_delay := apb_pwdata(15 downto 0); + when reg_adc_range_clear => v.reg_control.adc_range_clear := apb_pwdata(0); when reg_test_led => v.reg_control.test_led := apb_pwdata(7 downto 0); when reg_test_divider => v.reg_control.test_divider := apb_pwdata(15 downto 0); when reg_dma_buf_addr => v.reg_control.dma_buf_addr := apb_pwdata(31 downto 12); diff --git a/fpga/rtl/trigger_detector.vhd b/fpga/rtl/trigger_detector.vhd index cf8bac1..9ebe2d2 100644 --- a/fpga/rtl/trigger_detector.vhd +++ b/fpga/rtl/trigger_detector.vhd @@ -52,13 +52,7 @@ architecture arch of trigger_detector is trig_out: std_logic; end record; - constant regs_init: regs_type := ( - prev_level => '0', - ext_trig => '0', - trig_out => '0' - ); - - signal r: regs_type := regs_init; + signal r: regs_type; signal rnext: regs_type; begin @@ -71,7 +65,6 @@ begin -- process (all) is variable v: regs_type; - variable v_trig: std_logic; begin -- Load current register values. v := r; diff --git a/fpga/vivado/nonproject.tcl b/fpga/vivado/nonproject.tcl index 00fa01f..362c033 100644 --- a/fpga/vivado/nonproject.tcl +++ b/fpga/vivado/nonproject.tcl @@ -23,6 +23,7 @@ read_vhdl -vhdl2008 ../rtl/acquisition_chain.vhd read_vhdl -vhdl2008 ../rtl/acquisition_manager.vhd read_vhdl -vhdl2008 ../rtl/acquisition_stream.vhd read_vhdl -vhdl2008 ../rtl/adc_capture.vhd +read_vhdl -vhdl2008 ../rtl/adc_range_monitor.vhd read_vhdl -vhdl2008 ../rtl/adc_sample_stream.vhd read_vhdl -vhdl2008 ../rtl/dma_axi_master.vhd read_vhdl -vhdl2008 ../rtl/dma_write_channel.vhd diff --git a/os/src/userspace/testje.c b/os/src/userspace/testje.c index 07f3f17..32d67bd 100644 --- a/os/src/userspace/testje.c +++ b/os/src/userspace/testje.c @@ -51,6 +51,13 @@ #define REG_TRIGGER_MODE 0x0240 #define REG_TRIGGER_DELAY 0x0244 #define REG_TRIGGER_STATUS 0x0248 +#define REG_ADC_SAMPLE 0x0280 +#define REG_ADC23_SAMPLE 0x0284 +#define REG_ADC_RANGE_CLEAR 0x028c +#define REG_ADC0_MINMAX 0x0290 +#define REG_ADC1_MINMAX 0x0294 +#define REG_ADC2_MINMAX 0x0298 +#define REG_ADC3_MINMAX 0x029c struct puzzlefw_context { @@ -601,6 +608,19 @@ static void show_status(struct puzzlefw_context *ctx) v = puzzlefw_read_reg(ctx, REG_AVERAGING_EN); printf(" averaging_en = 0x%08x\n", v); + + v = puzzlefw_read_reg(ctx, REG_ADC_SAMPLE); + uint32_t adc_range = puzzlefw_read_reg(ctx, REG_ADC0_MINMAX); + printf(" channel 0 = %5u (min = %u, max = %u)\n", + v & 0xffff, + adc_range & 0xffff, + (adc_range >> 16) & 0xffff); + + adc_range = puzzlefw_read_reg(ctx, REG_ADC1_MINMAX); + printf(" channel 1 = %5u (min = %u, max = %u)\n", + (v >> 16) & 0xffff, + adc_range & 0xffff, + (adc_range >> 16) & 0xffff); } @@ -1037,6 +1057,7 @@ int main(int argc, char **argv) int set_shift = 0, shift_steps = 0; int avgon = 0, avgoff = 0; int simon = 0, simoff = 0; + int rangeclear = 0; if (argc == 2 && strcmp(argv[1], "show") == 0) { show = 1; @@ -1084,6 +1105,8 @@ int main(int argc, char **argv) simon = 1; } else if (argc == 2 && strcmp(argv[1], "simoff") == 0) { simoff = 1; + } else if (argc == 2 && strcmp(argv[1], "rangeclear") == 0) { + rangeclear = 1; } else if (argc == 2 && strcmp(argv[1], "server") == 0) { server = 1; } else { @@ -1152,6 +1175,9 @@ int main(int argc, char **argv) " testje simoff\n" " Use real ADC data.\n" "\n" + " testje rangeclear\n" + " Clear min/max ADC sample monitor.\n" + "\n" " testje server\n" " Open TCP port 5001 to stream DMA data.\n" "\n"); @@ -1251,6 +1277,10 @@ int main(int argc, char **argv) puzzlefw_write_reg(&ctx, REG_SIMULATE_ADC, 0); } + if (rangeclear) { + puzzlefw_write_reg(&ctx, REG_ADC_RANGE_CLEAR, 1); + } + if (server) { run_server(&ctx); }