From 716d16e6a35a5a6534535f625c18d8c0baca9980 Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Mon, 26 Aug 2024 12:52:35 +0200 Subject: [PATCH] Test analog acquisition chain --- fpga/rtl/acquisition_chain.vhd | 210 +++++++++++++++ fpga/rtl/acquisition_manager.vhd | 201 +++++++++++++++ fpga/rtl/acquisition_stream.vhd | 201 +++++++++++++++ fpga/rtl/adc_capture.vhd | 61 +++++ fpga/rtl/adc_sample_stream.vhd | 80 ++++++ fpga/rtl/dma_write_channel.vhd | 5 +- fpga/rtl/puzzlefw_pkg.vhd | 92 +++++-- fpga/rtl/puzzlefw_top.vhd | 113 +++++--- fpga/rtl/registers.vhd | 52 +++- fpga/rtl/sample_decimation.vhd | 124 +++++++++ fpga/rtl/shift_engine.vhd | 90 +++++++ fpga/rtl/timestamp_gen.vhd | 57 +++++ fpga/rtl/trigger_detector.vhd | 112 ++++++++ fpga/vivado/nonproject.tcl | 11 +- os/src/userspace/testje.c | 424 ++++++++++++++++++++++++++----- 15 files changed, 1686 insertions(+), 147 deletions(-) create mode 100644 fpga/rtl/acquisition_chain.vhd create mode 100644 fpga/rtl/acquisition_manager.vhd create mode 100644 fpga/rtl/acquisition_stream.vhd create mode 100644 fpga/rtl/adc_capture.vhd create mode 100644 fpga/rtl/adc_sample_stream.vhd create mode 100644 fpga/rtl/sample_decimation.vhd create mode 100644 fpga/rtl/shift_engine.vhd create mode 100644 fpga/rtl/timestamp_gen.vhd create mode 100644 fpga/rtl/trigger_detector.vhd diff --git a/fpga/rtl/acquisition_chain.vhd b/fpga/rtl/acquisition_chain.vhd new file mode 100644 index 0000000..2495c29 --- /dev/null +++ b/fpga/rtl/acquisition_chain.vhd @@ -0,0 +1,210 @@ +-- +-- Analog acquisition chain. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.puzzlefw_pkg.all; + + +entity acquisition_chain is + + generic ( + -- Number of analog input channels. It should be either 2 or 4. + -- If num_channels == 2, the acquisition always runs in 2-channel mode. + -- IF num_channels == 4, the acquisition can run in 2-channel mode + -- or 4-channel mode. + num_channels: integer range 2 to 4 + ); + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Reset, active high, synchronous to main clock. + reset: in std_logic; + + -- High to enable data acquisition. + -- Low to disable data acquisition, ignore triggers, stop emitting + -- samples and clear the trigger status. + acquisition_en: in std_logic; + + -- Trigger delay in clock cycles. + trigger_delay: in std_logic_vector(15 downto 0); + + -- Number of decimated samples per trigger minus 1. + record_length: in std_logic_vector(15 downto 0); + + -- Decimation factor minus 1. + decimation_factor: in std_logic_vector(17 downto 0); + + -- High to sum input samples; low to select single samples. + averaging: in std_logic; + + -- Number of right-shift steps to apply to ADC data. + -- When set to zero, the least significant ADC sample bit aligns + -- with the least significant decimated sample bit. + shift_steps: in std_logic_vector(3 downto 0); + + -- High to enable 4-channel mode. + -- 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; + + -- High to enable external triggering. + trig_ext_en: in std_logic; + + -- High to force a trigger event (if the acquisition chain is ready). + trig_force: in std_logic; + + -- Select external input signal to use as trigger. + trig_ext_select: in std_logic_vector(1 downto 0); + + -- High to trigger on falling edge, low to trigger on rising edge. + trig_ext_falling: in std_logic; + + -- Global timestamp counter. + timestamp_in: in std_logic_vector(timestamp_bits - 1 downto 0); + + -- ADC input data. + adc_data_in: in adc_data_array(0 to num_channels - 1); + + -- External trigger signals, already synchronized and de-glitched. + trig_ext_in: in std_logic_vector(3 downto 0); + + -- High if the acquisition chain is waiting for a trigger. + trig_waiting: out std_logic; + + -- Output data stream. + out_valid: out std_logic; + out_ready: in std_logic; + out_empty: in std_logic; + out_data: out dma_data_type + ); + +end entity; + +architecture arch of acquisition_chain is + + -- Number of data bits for accumulating ADC samples when averaging. + constant accum_data_bits: natural := 32; + + subtype accum_data_type is std_logic_vector(accum_data_bits - 1 downto 0); + type accum_data_array is array(natural range <>) of accum_data_type; + + signal s_trigger: std_logic; + signal s_trig_ack: std_logic; + signal s_sample_start: std_logic; + signal s_sample_integrate: std_logic; + signal s_sample_done: std_logic; + signal s_adc_sample: adc_data_array(0 to num_channels - 1); + signal s_adc_shifted: accum_data_array(0 to num_channels - 1); + signal s_sample_decimated: sample_data_array(0 to num_channels - 1); + +begin + + -- External trigger detector. + inst_trigger_detector: entity work.trigger_detector + port map ( + clk => clk, + reset => reset, + trig_auto_en => trig_auto_en, + trig_ext_en => trig_ext_en, + trig_force => trig_force, + trig_select => trig_ext_select, + trig_falling => trig_ext_falling, + trig_ext_in => trig_ext_in, + trig_out => s_trigger ); + + -- Timing of sampling and decimation. + inst_acquisition_manager: entity work.acquisition_manager + port map ( + clk => clk, + reset => reset, + acquisition_en => acquisition_en, + trigger_delay => trigger_delay, + record_length => record_length, + decimation_factor => decimation_factor, + averaging => averaging, + trig_in => s_trigger, + trig_waiting => trig_waiting, + trig_ack => s_trig_ack, + sample_start => s_sample_start, + 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 + + inst_shift: entity work.shift_engine + generic map ( + input_data_bits => adc_data_bits, + output_data_bits => accum_data_bits, + pre_shift_left => accum_data_bits - sample_data_bits, + signed_data => false ) + port map ( + clk => clk, + in_data => s_adc_sample(i), + in_shift => shift_steps, + out_data => s_adc_shifted(i) ); + + inst_decimation: entity work.sample_decimation + generic map ( + input_data_bits => accum_data_bits, + output_data_bits => sample_data_bits ) + port map ( + clk => clk, + reset => reset, + start => s_sample_start, + integrate => s_sample_integrate, + in_data => s_adc_shifted(i), + out_data => s_sample_decimated(i) ); + + end generate; + + -- Format sample data stream and insert trigger timestamps. + inst_stream: entity work.acquisition_stream + generic map ( + num_channels => num_channels ) + port map ( + clk => clk, + reset => reset, + ch4_mode => ch4_mode, + timestamp_in => timestamp_in, + trig_ack => s_trig_ack, + sample_valid => s_sample_done, + sample_data => s_sample_decimated, + out_valid => out_valid, + out_ready => out_ready, + out_empty => out_empty, + out_data => out_data ); + +end architecture; diff --git a/fpga/rtl/acquisition_manager.vhd b/fpga/rtl/acquisition_manager.vhd new file mode 100644 index 0000000..efb9b2e --- /dev/null +++ b/fpga/rtl/acquisition_manager.vhd @@ -0,0 +1,201 @@ +-- +-- Handle sample acquisition and triggering. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + + +entity acquisition_manager is + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Reset, active high, synchronous to main clock. + reset: in std_logic; + + -- High to enable data acquisition. + -- Low to disable data acquisition, ignore triggers, stop emitting + -- samples and clear the trigger status. + acquisition_en: in std_logic; + + -- Trigger delay in clock cycles. + trigger_delay: in std_logic_vector(15 downto 0); + + -- Number of decimated samples per trigger minus 1. + record_length: in std_logic_vector(15 downto 0); + + -- Decimation factor minus 1. + decimation_factor: in std_logic_vector(17 downto 0); + + -- High to sum input samples; low to select single samples. + averaging: in std_logic; + + -- High when a trigger condition occurs. + trig_in: in std_logic; + + -- High if the acquisition chain is waiting for a trigger. + -- This not a handshake signal. It does not accurately predict + -- whether a trigger will be accepted or ignored. + trig_waiting: out std_logic; + + -- High for one cycle when a trigger occurs, after the configured + -- trigger delay has passed. This indicates that a trigger timestamp + -- record must be generated. + trig_ack: out std_logic; + + -- High to initiate the capture of a new decimated sample. + sample_start: out std_logic; + + -- High to accumulate additional raw samples into a decimated sample. + sample_integrate: out std_logic; + + -- High to emit a decimated sample. + sample_done: out std_logic + ); + +end entity; + +architecture arch of acquisition_manager is + + type state_type is (STATE_IDLE, STATE_DELAY, STATE_CAPTURE); + + type regs_type is record + state: state_type; + delay_cnt: unsigned(15 downto 0); + record_cnt: unsigned(15 downto 0); + decimation_cnt: unsigned(17 downto 0); + trig_waiting: std_logic; + trig_ack: std_logic; + sample_start: std_logic; + sample_integrate: std_logic; + sample_done: std_logic; + end record; + + constant regs_init: regs_type := ( + state => STATE_IDLE, + delay_cnt => (others => '0'), + record_cnt => (others => '0'), + decimation_cnt => (others => '0'), + trig_waiting => '0', + trig_ack => '0', + sample_start => '0', + sample_integrate => '0', + sample_done => '0' + ); + + signal r: regs_type := regs_init; + signal rnext: regs_type; + +begin + + -- Drive output ports. + trig_waiting <= r.trig_waiting; + trig_ack <= r.trig_ack; + sample_start <= r.sample_start; + sample_integrate <= r.sample_integrate; + sample_done <= r.sample_done; + + -- + -- Combinatorial process. + -- + process (all) is + variable v: regs_type; + begin + -- Load current register values. + v := r; + + -- Default assignments. + v.trig_ack := '0'; + v.sample_start := '0'; + v.sample_integrate := '0'; + v.sample_done := '0'; + + -- State machine. + case r.state is + + when STATE_IDLE => + -- Waiting for trigger. + v.delay_cnt := unsigned(trigger_delay); + v.record_cnt := unsigned(record_length); + v.decimation_cnt := unsigned(decimation_factor); + v.trig_waiting := acquisition_en; + if acquisition_en = '1' and trig_in = '1' then + if unsigned(trigger_delay) = 0 then + v.trig_ack := '1'; + v.sample_start := '1'; + v.state := STATE_CAPTURE; + else + v.state := STATE_DELAY; + end if; + end if; + + when STATE_DELAY => + -- Wait for end of trigger delay. + v.delay_cnt := r.delay_cnt - 1; + if r.delay_cnt = 1 then + v.trig_ack := '1'; + v.sample_start := '1'; + v.state := STATE_CAPTURE; + end if; + + when STATE_CAPTURE => + -- Capture samples. + v.delay_cnt := unsigned(trigger_delay); + v.decimation_cnt := r.decimation_cnt - 1; + if r.decimation_cnt = 0 then + v.sample_done := '1'; + v.record_cnt := r.record_cnt - 1; + v.decimation_cnt := unsigned(decimation_factor); + if r.record_cnt = 0 then + v.record_cnt := unsigned(record_length); + if trig_in = '1' then + if unsigned(trigger_delay) = 0 then + v.trig_ack := '1'; + v.sample_start := '1'; + v.state := STATE_CAPTURE; + else + v.state := STATE_DELAY; + end if; + else + v.state := STATE_IDLE; + end if; + else + v.sample_start := '1'; + end if; + else + v.sample_integrate := averaging; + end if; + + end case; + + -- Stop acquisition immediately when disabled. + if acquisition_en = '0' then + v.state := STATE_IDLE; + end if; + + -- Synchronous reset. + if reset = '1' then + v := regs_init; + 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/acquisition_stream.vhd b/fpga/rtl/acquisition_stream.vhd new file mode 100644 index 0000000..8511c4c --- /dev/null +++ b/fpga/rtl/acquisition_stream.vhd @@ -0,0 +1,201 @@ +-- +-- Format analog acquisition data into a stream of 64-bit words. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.puzzlefw_pkg.all; + + +entity acquisition_stream is + + generic ( + -- Number of analog input channels. It should be either 2 or 4. + num_channels: integer range 2 to 4 + ); + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Reset, active high, synchronous to main clock. + reset: in std_logic; + + -- High to enable 4-channel mode. + -- Ignored if num_channels == 2. + ch4_mode: in std_logic; + + -- Global timestamp counter. + timestamp_in: in std_logic_vector(timestamp_bits - 1 downto 0); + + -- High for one cycle when a trigger occurs. + trig_ack: in std_logic; + + -- Decimated sample stream. + sample_valid: in std_logic; + sample_data: in sample_data_array(0 to num_channels - 1); + + -- Output data stream. + out_valid: out std_logic; + out_ready: in std_logic; + out_empty: in std_logic; + out_data: out dma_data_type + ); + +end entity; + +architecture arch of acquisition_stream is + + type regs_type is record + overflow: std_logic; + trig_pending: std_logic; + trig_timestamp: std_logic_vector(timestamp_bits - 1 downto 0); + sample_pending: std_logic; + sample_data: sample_data_array(0 to num_channels - 3); + out_valid: std_logic; + out_data: dma_data_type; + end record; + + constant regs_init: regs_type := ( + overflow => '0', + trig_pending => '0', + trig_timestamp => (others => '0'), + sample_pending => '0', + sample_data => (others => (others => '0')), + out_valid => '0', + out_data => (others => '0') + ); + + signal r: regs_type := regs_init; + signal rnext: regs_type; + +begin + + -- Drive output ports. + out_valid <= r.out_valid; + out_data <= r.out_data; + + -- + -- Combinatorial process. + -- + process (all) is + variable v: regs_type; + begin + -- Load current register values. + v := r; + + -- By default, do not emit data. + v.out_valid := '0'; + + if sample_valid = '1' then + + -- Emit first sample data word. + v.out_valid := '1'; + v.out_data(63 downto 56) := msg_adc_data; + v.out_data(55 downto 48) := x"10"; + v.out_data(23 downto 0) := sample_data(0); + v.out_data(47 downto 24) := sample_data(1); + + -- Detect internal overflow. + if (r.sample_pending = '1') or (r.trig_pending = '1') then + v.overflow := '1'; + end if; + + elsif r.sample_pending = '1' then + + -- Emit second sample data word. + v.sample_pending := '0'; + v.out_valid := '1'; + v.out_data(63 downto 56) := msg_adc_data; + v.out_data(55 downto 48) := x"32"; + v.out_data(47 downto 0) := (others => '0'); + if num_channels > 2 then + v.out_data(23 downto 0) := r.sample_data(0); + end if; + if num_channels > 3 then + v.out_data(47 downto 24) := r.sample_data(1); + end if; + + elsif r.trig_pending = '1' then + + -- Emit pending trigger record. + v.trig_pending := '0'; + v.out_valid := '1'; + v.out_data(63 downto 56) := msg_trigger; + v.out_data(55 downto 48) := x"00"; + v.out_data(47 downto 0) := r.trig_timestamp; + + elsif trig_ack = '1' then + + -- Emit trigger record. + v.out_valid := '1'; + v.out_data(63 downto 56) := msg_trigger; + v.out_data(55 downto 48) := x"00"; + v.out_data(47 downto 0) := timestamp_in; + + end if; + + -- Latch second sample data word. + if (num_channels > 2) and (sample_valid = '1') then + v.sample_pending := '1'; + v.sample_data := sample_data(2 to num_channels - 1); + end if; + + -- Latch timestamp when a trigger occurs. + if trig_ack = '1' then + v.trig_timestamp := timestamp_in; + if (sample_valid = '1') or (r.sample_pending = '1') then + v.trig_pending := '1'; + end if; + + -- Detect internal overflow. + if r.trig_pending = '1' then + v.overflow := '1'; + end if; + end if; + + -- Detect overflow of external data buffer. + if (r.out_valid = '1') and (out_ready = '0') then + v.overflow := '1'; + end if; + + -- If there is a pending overflow, discard data until the buffer + -- is empty, then emit an overflow record. + if r.overflow = '1' then + v.sample_pending := '0'; + v.trig_pending := '0'; + v.out_valid := '0'; + v.out_data(63 downto 56) := msg_overflow; + v.out_data(55 downto 0) := (others => '0'); + + if out_empty = '1' then + v.out_valid := '1'; + v.overflow := '0'; + end if; + end if; + + -- Synchronous reset. + if reset = '1' then + v := regs_init; + 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/adc_capture.vhd b/fpga/rtl/adc_capture.vhd new file mode 100644 index 0000000..1c6f942 --- /dev/null +++ b/fpga/rtl/adc_capture.vhd @@ -0,0 +1,61 @@ +-- +-- Capture ADC sample data from FPGA input ports. +-- +-- Input signals are captured on the rising edge of CLK. +-- This entity adds 2 clock cycles delay. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.puzzlefw_pkg.all; + + +entity adc_capture is + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Input signals. + in_data: in std_logic_vector(adc_data_bits - 1 downto 0); + + -- Output sample stream. + -- Produces one new ADC sample per clock cycle. + out_data: out adc_data_type + ); + +end entity; + +architecture arch of adc_capture is + + signal r_stage1: std_logic_vector(adc_data_bits - 1 downto 0); + signal r_stage2: std_logic_vector(adc_data_bits - 1 downto 0); + +begin + + -- Drive output ports. + out_data <= r_stage2; + + -- + -- Synchronous process. + -- + process (clk) is + begin + if rising_edge(clk) then + + -- Capture input signals into registers. + -- These registers will be placed in IO flipflops through IOB constraints. + r_stage1 <= in_data; + + -- Second register stage. + -- These can be placed anywhere in the FPGA to optimize timing. + r_stage2 <= r_stage1; + + end if; + end process; + +end architecture; diff --git a/fpga/rtl/adc_sample_stream.vhd b/fpga/rtl/adc_sample_stream.vhd new file mode 100644 index 0000000..78ef168 --- /dev/null +++ b/fpga/rtl/adc_sample_stream.vhd @@ -0,0 +1,80 @@ +-- +-- Manage the ADC sample stream. +-- +-- Optionally generates simulated samples in place of real ADC data. +-- The simulated sample stream works as follows: +-- - Both simulated channels output a simple increasing ramp. +-- - The output of channel 0 increments at a rate of 1 per clock cycle. +-- - The output of channel 1 increments by 1 whenever channel 0 wraps around. +-- +-- This entity adds 1 clock cycle delay in the ADC sample stream. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.puzzlefw_pkg.all; + + +entity adc_sample_stream is + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Reset, active high, synchronous to main clock. + reset: in std_logic; + + -- High to select simulated samples in place of ADC data. + simulate: in std_logic; + + -- Input sample stream from ADC. + in_data: in adc_data_array(0 to 1); + + -- Output sample stream. + out_data: out adc_data_array(0 to 1) + ); + +end entity; + +architecture arch of adc_sample_stream is + + signal r_counter: unsigned(2 * adc_data_bits - 1 downto 0); + signal r_out_data: adc_data_array(0 to 1); + +begin + + -- Drive output ports. + out_data <= r_out_data; + + -- + -- Synchronous process. + -- + process (clk) is + begin + if rising_edge(clk) then + + -- Select ADC sample or simulated sample. + if simulate = '1' then + -- Output simulated sample. + r_out_data(0) <= std_logic_vector(r_counter(adc_data_bits - 1 downto 0)); + r_out_data(1) <= std_logic_vector(r_counter(2 * adc_data_bits - 1 downto adc_data_bits)); + else + -- Output real ADC sample. + r_out_data <= in_data; + end if; + + -- Update simulated sample stream. + if reset = '1' then + r_counter <= (others => '0'); + else + r_counter <= r_counter + 1; + end if; + + end if; + end process; + +end architecture; diff --git a/fpga/rtl/dma_write_channel.vhd b/fpga/rtl/dma_write_channel.vhd index 08cdd6f..5f01fa8 100644 --- a/fpga/rtl/dma_write_channel.vhd +++ b/fpga/rtl/dma_write_channel.vhd @@ -83,6 +83,7 @@ entity dma_write_channel is -- Input data stream to the channel. in_valid: in std_logic; in_ready: out std_logic; + in_empty: out std_logic; in_data: in dma_data_type; -- Signals to AXI master. @@ -173,6 +174,7 @@ begin channel_busy <= r.channel_busy; addr_pointer <= r.addr_pointer; intr_out <= r.intr_out; + in_empty <= not s_fifo_valid; write_cmd_addr <= r.cmd_addr; write_cmd_length <= "0000" when (r.cmd_full_burst = '0') else std_logic_vector(to_unsigned(transfer_size - 1, 4)); @@ -226,7 +228,6 @@ begin v.cmd_valid := '1'; v.channel_busy := '1'; - v.pending_beats := to_unsigned(0, v.pending_beats'length); v.state := STATE_SINGLE_START; end if; @@ -260,7 +261,7 @@ begin when STATE_SINGLE_DATA => -- Wait until data accepted. - if (write_data_ready = '1') and (r.pending_beats = 0) then + if write_data_ready = '1' then v.state := STATE_SINGLE_IDLE; end if; diff --git a/fpga/rtl/puzzlefw_pkg.vhd b/fpga/rtl/puzzlefw_pkg.vhd index d574368..ad960e6 100644 --- a/fpga/rtl/puzzlefw_pkg.vhd +++ b/fpga/rtl/puzzlefw_pkg.vhd @@ -36,6 +36,9 @@ package puzzlefw_pkg is -- 48-bit timestamp. constant timestamp_bits: integer := 48; + -- ADC input port type. + type adc_data_input_type is array(0 to 1) of std_logic_vector(15 downto 0); + -- Register addresses. constant reg_addr_mask: std_logic_vector(31 downto 0) := x"0010fffc"; constant reg_info: natural := 16#000000#; @@ -44,6 +47,9 @@ package puzzlefw_pkg is constant reg_dma_en: natural := 16#000100#; constant reg_dma_status: natural := 16#000104#; constant reg_dma_clear: natural := 16#000108#; + constant reg_timestamp_lo: natural := 16#000180#; + constant reg_timestamp_hi: natural := 16#000184#; + constant reg_timestamp_clear: natural := 16#000188#; constant reg_acq_addr_start: natural := 16#000200#; constant reg_acq_addr_end: natural := 16#000204#; constant reg_acq_addr_limit: natural := 16#000208#; @@ -51,6 +57,16 @@ package puzzlefw_pkg is constant reg_acq_addr_ptr: natural := 16#000210#; constant reg_acq_channel_ctrl: natural := 16#000214#; constant reg_acq_intr_ctrl: natural := 16#000218#; + constant reg_acquisition_en: natural := 16#000220#; + constant reg_record_length: natural := 16#000224#; + constant reg_decimation_factor: natural := 16#000228#; + constant reg_shift_steps: natural := 16#00022c#; + constant reg_averaging_en: natural := 16#000230#; + constant reg_ch4_mode: natural := 16#000234#; + constant reg_simulate_adc: natural := 16#000238#; + constant reg_trigger_mode: natural := 16#000240#; + constant reg_trigger_delay: natural := 16#000244#; + constant reg_trigger_status: natural := 16#000248#; constant reg_test_led: natural := 16#000404#; constant reg_test_divider: natural := 16#000408#; constant reg_dma_buf_addr: natural := 16#100000#; @@ -71,21 +87,35 @@ package puzzlefw_pkg is constant msg_trigger: std_logic_vector(7 downto 0) := x"02"; constant msg_overflow: std_logic_vector(7 downto 0) := x"10"; - -- ADC input port type. - type adc_data_input_type is array(0 to 1) of std_logic_vector(15 downto 0); - -- Control registers: read/write access by processor, output signals to FPGA. type registers_control is record irq_enable: std_logic; test_led: std_logic_vector(7 downto 0); test_divider: std_logic_vector(15 downto 0); dma_en: std_logic; + dma_clear: std_logic; -- single cycle + timestamp_clear: std_logic; -- single cycle acq_addr_start: std_logic_vector(31 downto 7); acq_addr_end: std_logic_vector(31 downto 7); acq_addr_limit: std_logic_vector(31 downto 7); acq_addr_intr: std_logic_vector(31 downto 3); acq_channel_en: std_logic; + acq_channel_init: std_logic; -- single cycle acq_intr_en: std_logic; + acq_intr_clear: std_logic; -- single cycle + acquisition_en: std_logic; + record_length: std_logic_vector(15 downto 0); + decimation_factor: std_logic_vector(17 downto 0); + shift_steps: std_logic_vector(3 downto 0); + averaging_en: std_logic; + ch4_mode: std_logic; + simulate_adc: std_logic; + trig_auto_en: std_logic; + trig_ext_en: std_logic; + trig_force: std_logic; -- single cycle + trig_ext_select: std_logic_vector(1 downto 0); + trig_ext_falling: std_logic; + trigger_delay: std_logic_vector(15 downto 0); dma_buf_addr: std_logic_vector(31 downto 12); dma_buf_size: std_logic_vector(31 downto 12); end record; @@ -98,36 +128,42 @@ package puzzlefw_pkg is dma_err_write: std_logic; dma_err_address: std_logic; dma_err_any: std_logic; + timestamp: std_logic_vector(timestamp_bits - 1 downto 0); acq_addr_ptr: std_logic_vector(31 downto 3); acq_channel_busy: std_logic; - end record; - - -- Trigger registers: write-only access by processor, single-cycle pulse signals to FPGA. - type registers_trigger is record - dma_clear: std_logic; - acq_channel_init: std_logic; - acq_intr_clear: std_logic; + trig_waiting: std_logic; end record; constant registers_control_init: registers_control := ( - irq_enable => '0', - test_led => (others => '0'), - test_divider => (others => '0'), - dma_en => '0', - acq_addr_start => (others => '0'), - acq_addr_end => (others => '0'), - acq_addr_limit => (others => '0'), - acq_addr_intr => (others => '0'), - acq_channel_en => '0', - acq_intr_en => '0', - dma_buf_addr => (others => '0'), - dma_buf_size => (others => '0') - ); - - constant registers_trigger_init: registers_trigger := ( - dma_clear => '0', - acq_channel_init => '0', - acq_intr_clear => '0' + irq_enable => '0', + test_led => (others => '0'), + test_divider => (others => '0'), + dma_en => '0', + dma_clear => '0', + timestamp_clear => '0', + acq_addr_start => (others => '0'), + acq_addr_end => (others => '0'), + acq_addr_limit => (others => '0'), + acq_addr_intr => (others => '0'), + acq_channel_en => '0', + acq_channel_init => '0', + acq_intr_en => '0', + acq_intr_clear => '0', + acquisition_en => '0', + record_length => (others => '0'), + decimation_factor => (others => '0'), + shift_steps => (others => '0'), + averaging_en => '0', + ch4_mode => '0', + simulate_adc => '0', + trig_auto_en => '0', + trig_ext_en => '0', + trig_force => '0', + trig_ext_select => (others => '0'), + trig_ext_falling => '0', + trigger_delay => (others => '0'), + dma_buf_addr => (others => '0'), + dma_buf_size => (others => '0') ); end package; diff --git a/fpga/rtl/puzzlefw_top.vhd b/fpga/rtl/puzzlefw_top.vhd index b4290e0..7a505f1 100644 --- a/fpga/rtl/puzzlefw_top.vhd +++ b/fpga/rtl/puzzlefw_top.vhd @@ -74,7 +74,9 @@ architecture arch of puzzlefw_top is -- Main reset signal, derived from FCLK_RESET0, active high, synchronous to clk_adc. signal s_reset: std_logic; + -- Internal clock signal. signal s_adc_clk_ibuf: std_logic; + signal r_fclk_cnt: unsigned(26 downto 0); signal r_fclk_led: std_logic; signal r_adcclk_cnt: unsigned(26 downto 0); @@ -128,13 +130,11 @@ architecture arch of puzzlefw_top is signal s_axi_rvalid: std_logic; signal s_axi_rready: std_logic; - signal s_dma_interrupt: std_logic; signal s_irq_pending: std_logic_vector(0 downto 0); signal s_irq_f2p: std_logic_vector(7 downto 0); signal s_reg_control: registers_control; signal s_reg_status: registers_status; - signal s_reg_trigger: registers_trigger; 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); @@ -144,9 +144,13 @@ architecture arch of puzzlefw_top is 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_in_valid: std_logic; - signal s_dma_in_ready: std_logic; - signal s_dma_in_data: std_logic_vector(63 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_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); @@ -276,8 +280,7 @@ begin apb_pslverr => s_apb_pslverr, apb_prdata => s_apb_prdata, reg_control => s_reg_control, - reg_status => s_reg_status, - reg_trigger => s_reg_trigger + reg_status => s_reg_status ); -- AXI master. @@ -296,7 +299,7 @@ begin err_write => s_reg_status.dma_err_write, err_address => s_reg_status.dma_err_address, err_any => s_reg_status.dma_err_any, - clear_errors => s_reg_trigger.dma_clear, + clear_errors => s_reg_control.dma_clear, read_cmd_addr => (others => (others => '0')), read_cmd_length => (others => (others => '0')), read_cmd_valid => (others => '0'), @@ -361,18 +364,19 @@ begin reset => s_reset, channel_en => s_reg_control.acq_channel_en, channel_busy => s_reg_status.acq_channel_busy, - channel_init => s_reg_trigger.acq_channel_init, + channel_init => s_reg_control.acq_channel_init, addr_start => s_reg_control.acq_addr_start, addr_end => s_reg_control.acq_addr_end, addr_limit => s_reg_control.acq_addr_limit, addr_interrupt => s_reg_control.acq_addr_intr, addr_pointer => s_reg_status.acq_addr_ptr, intr_en => s_reg_control.acq_intr_en, - intr_clear => s_reg_trigger.acq_intr_clear, - intr_out => s_dma_interrupt, - in_valid => s_dma_in_valid, - in_ready => s_dma_in_ready, - in_data => s_dma_in_data, + intr_clear => s_reg_control.acq_intr_clear, + intr_out => s_irq_pending(0), + in_valid => s_acq_dma_valid, + in_ready => s_acq_dma_ready, + in_empty => s_acq_dma_empty, + in_data => s_acq_dma_data, write_cmd_addr => s_dma_write_cmd_addr(0), write_cmd_length => s_dma_write_cmd_length(0), write_cmd_valid => s_dma_write_cmd_valid(0), @@ -381,39 +385,64 @@ begin write_data_ready => s_dma_write_data_ready(0), write_finished => s_dma_write_finished(0) ); + -- Timestamp generator. + inst_timestamp_gen: entity work.timestamp_gen + port map ( + clk => clk_adc, + reset => s_reset, + clear => s_reg_control.timestamp_clear, + timestamp => s_timestamp ); + + s_reg_status.timestamp <= s_timestamp; + + -- 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_adc_capture2: entity work.adc_capture + port map ( + clk => clk_adc, + in_data => adc_dat_i(1)(15 downto 2), + out_data => s_adc_data(1) ); + + -- Analog acquisition data chain. + inst_acquisition_chain: entity work.acquisition_chain + generic map ( + num_channels => 2 ) + port map ( + clk => clk_adc, + reset => s_reset, + acquisition_en => s_reg_control.acquisition_en, + trigger_delay => s_reg_control.trigger_delay, + record_length => s_reg_control.record_length, + decimation_factor => s_reg_control.decimation_factor, + 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, + trig_ext_in => "0000", -- TODO + trig_waiting => s_reg_status.trig_waiting, + out_valid => s_acq_dma_valid, + out_ready => s_acq_dma_ready, + out_empty => s_acq_dma_empty, + out_data => s_acq_dma_data ); + -- Collect interrupt signals from peripherals and generate interrupt to PS. - s_irq_pending(0) <= s_dma_interrupt; s_reg_status.irq_pending <= s_irq_pending; s_irq_f2p(0) <= s_reg_control.irq_enable and or_reduce(s_irq_pending); s_irq_f2p(7 downto 1) <= (others => '0'); --- little test machine for DMA - process (clk_adc) is - begin - if rising_edge(clk_adc) then - if s_reg_control.test_divider = x"ffff" then - s_dma_in_valid <= '1'; - if s_dma_in_ready = '1' then - s_dma_in_data(47 downto 0) <= std_logic_vector(unsigned(s_dma_in_data(47 downto 0)) + 1); - s_dma_in_data(63 downto 48) <= (others => '0'); - end if; - else - if s_dma_in_ready = '1' then - s_dma_in_valid <= '0'; - end if; - if r_test_cnt >= unsigned(s_reg_control.test_divider) then - r_test_cnt <= (others => '0'); - s_dma_in_valid <= '1'; - s_dma_in_data(47 downto 0) <= std_logic_vector(unsigned(s_dma_in_data(47 downto 0)) + 1); - s_dma_in_data(63 downto 48) <= (others => '0'); - else - r_test_cnt <= r_test_cnt + 1; - end if; - end if; - end if; - end process; --- END test - process (clk_fclk) is begin if rising_edge(clk_fclk) then diff --git a/fpga/rtl/registers.vhd b/fpga/rtl/registers.vhd index b510876..91dc874 100644 --- a/fpga/rtl/registers.vhd +++ b/fpga/rtl/registers.vhd @@ -32,8 +32,7 @@ entity registers is -- FPGA interface. reg_control: out registers_control; - reg_status: in registers_status; - reg_trigger: out registers_trigger + reg_status: in registers_status ); end entity; @@ -44,14 +43,12 @@ architecture arch of registers is pready: std_logic; prdata: std_logic_vector(31 downto 0); reg_control: registers_control; - reg_trigger: registers_trigger; end record; constant regs_init: regs_type := ( pready => '0', prdata => (others => '0'), - reg_control => registers_control_init, - reg_trigger => registers_trigger_init + reg_control => registers_control_init ); signal r: regs_type := regs_init; @@ -63,9 +60,7 @@ begin apb_pready <= r.pready; apb_pslverr <= '0'; -- never signal errors apb_prdata <= r.prdata; - reg_control <= r.reg_control; - reg_trigger <= r.reg_trigger; -- Combinatorial process. process (all) is @@ -75,7 +70,11 @@ begin v := r; -- Clear single-cycle trigger pulses. - v.reg_trigger := registers_trigger_init; + v.reg_control.dma_clear := '0'; + v.reg_control.timestamp_clear := '0'; + v.reg_control.acq_channel_init := '0'; + v.reg_control.acq_intr_clear := '0'; + v.reg_control.trig_force := '0'; -- Respond to each APB access on the next clock cycle (no wait states). v.pready := apb_psel and (not apb_penable); @@ -98,6 +97,8 @@ begin v.prdata(2) := reg_status.dma_err_write; v.prdata(3) := reg_status.dma_err_address; v.prdata(4) := reg_status.dma_err_any; + when reg_timestamp_lo => v.prdata := reg_status.timestamp(31 downto 0); + when reg_timestamp_hi => v.prdata(15 downto 0) := reg_status.timestamp(47 downto 32); when reg_acq_addr_start => v.prdata(31 downto 7) := r.reg_control.acq_addr_start; when reg_acq_addr_end => v.prdata(31 downto 7) := r.reg_control.acq_addr_end; when reg_acq_addr_limit => v.prdata(31 downto 7) := r.reg_control.acq_addr_limit; @@ -107,6 +108,20 @@ begin v.prdata(0) := r.reg_control.acq_channel_en; v.prdata(8) := reg_status.acq_channel_busy; when reg_acq_intr_ctrl => v.prdata(0) := r.reg_control.acq_intr_en; + when reg_acquisition_en => v.prdata(0) := r.reg_control.acquisition_en; + when reg_record_length => v.prdata(15 downto 0) := r.reg_control.record_length; + when reg_decimation_factor => v.prdata(17 downto 0) := r.reg_control.decimation_factor; + when reg_shift_steps => v.prdata(3 downto 0) := r.reg_control.shift_steps; + when reg_averaging_en => v.prdata(0) := r.reg_control.averaging_en; + when reg_ch4_mode => v.prdata(0) := r.reg_control.ch4_mode; + when reg_simulate_adc => v.prdata(0) := r.reg_control.simulate_adc; + when reg_trigger_mode => + v.prdata(0) := r.reg_control.trig_auto_en; + v.prdata(1) := r.reg_control.trig_ext_en; + v.prdata(5 downto 4) := r.reg_control.trig_ext_select; + 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_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; @@ -121,17 +136,32 @@ begin case to_integer(unsigned(apb_paddr and reg_addr_mask)) is when reg_irq_enable => v.reg_control.irq_enable := apb_pwdata(0); when reg_dma_en => v.reg_control.dma_en := apb_pwdata(0); - when reg_dma_clear => v.reg_trigger.dma_clear := apb_pwdata(0); + when reg_dma_clear => v.reg_control.dma_clear := apb_pwdata(0); + when reg_timestamp_clear => v.reg_control.timestamp_clear := apb_pwdata(0); when reg_acq_addr_start => v.reg_control.acq_addr_start := apb_pwdata(31 downto 7); when reg_acq_addr_end => v.reg_control.acq_addr_end := apb_pwdata(31 downto 7); when reg_acq_addr_limit => v.reg_control.acq_addr_limit := apb_pwdata(31 downto 7); when reg_acq_addr_intr => v.reg_control.acq_addr_intr := apb_pwdata(31 downto 3); when reg_acq_channel_ctrl => v.reg_control.acq_channel_en := apb_pwdata(0); - v.reg_trigger.acq_channel_init := apb_pwdata(1); + v.reg_control.acq_channel_init := apb_pwdata(1); when reg_acq_intr_ctrl => v.reg_control.acq_intr_en := apb_pwdata(0); - v.reg_trigger.acq_intr_clear := apb_pwdata(1); + v.reg_control.acq_intr_clear := apb_pwdata(1); + when reg_acquisition_en => v.reg_control.acquisition_en := apb_pwdata(0); + when reg_record_length => v.reg_control.record_length := apb_pwdata(15 downto 0); + when reg_decimation_factor => v.reg_control.decimation_factor := apb_pwdata(17 downto 0); + when reg_shift_steps => v.reg_control.shift_steps := apb_pwdata(3 downto 0); + when reg_averaging_en => v.reg_control.averaging_en := apb_pwdata(0); + when reg_ch4_mode => v.reg_control.ch4_mode := apb_pwdata(0); + when reg_simulate_adc => v.reg_control.simulate_adc := apb_pwdata(0); + when reg_trigger_mode => + v.reg_control.trig_auto_en := apb_pwdata(0); + v.reg_control.trig_ext_en := apb_pwdata(1); + v.reg_control.trig_ext_select := apb_pwdata(5 downto 4); + 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_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/sample_decimation.vhd b/fpga/rtl/sample_decimation.vhd new file mode 100644 index 0000000..88422a9 --- /dev/null +++ b/fpga/rtl/sample_decimation.vhd @@ -0,0 +1,124 @@ +-- +-- Decimation or averaging of ADC samples. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + + +entity sample_decimation is + + generic ( + -- Input word length. + -- This is also the word length of the internal accumulator. + input_data_bits: integer range 4 to 64; + + -- Output word length. + -- If the output has fewer bits than the input, it represents the + -- most significant bits of the accumulator, rounded to the + -- nearest integer. + output_data_bits: integer range 4 to 64 + ); + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Reset, active high, synchronous to main clock. + reset: in std_logic; + + -- High to initialize the accumulator to the input word plus rounding bias. + start: in std_logic; + + -- High to add the input word to the accumulator. + integrate: in std_logic; + + -- Input data word. + in_data: in std_logic_vector(input_data_bits - 1 downto 0); + + -- Output data word. + -- Results from input signals appear on the output after 1 clock cycle. + out_data: out std_logic_vector(output_data_bits - 1 downto 0) + ); + +end entity; + +architecture arch of sample_decimation is + + -- Return the rounding bias to add to the accumulator before truncating + -- the least significant bits. + function rounding_bias return unsigned + is begin + if output_data_bits < input_data_bits then + -- Set the most significant truncated bit to '1'. + return shift_left(to_unsigned(1, input_data_bits), + input_data_bits - output_data_bits - 1); + else + -- No truncation. + return to_unsigned(0, input_data_bits); + end if; + end function; + + type regs_type is record + accumulator: std_logic_vector(input_data_bits - 1 downto 0); + end record; + + constant regs_init: regs_type := ( + accumulator => (others => '0') + ); + + signal r: regs_type := regs_init; + signal rnext: regs_type; + +begin + + -- Drive output from accumulator. + out_data <= r.accumulator(input_data_bits - 1 downto input_data_bits - output_data_bits) + when (output_data_bits <= input_data_bits) + else r.accumulator & std_logic_vector(to_unsigned(0, output_data_bits - input_data_bits)); + + -- + -- Combinatorial process. + -- + process (all) is + variable v: regs_type; + begin + -- Load current register values. + v := r; + + if start = '1' then + + -- Initialize accumulator and add input word. + v.accumulator := std_logic_vector(rounding_bias + unsigned(in_data)); + + elsif integrate = '1' then + + -- Add input word to the accumulator. + v.accumulator := std_logic_vector(unsigned(r.accumulator) + unsigned(in_data)); + + end if; + + -- Synchronous reset. + if reset = '1' then + v := regs_init; + 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/shift_engine.vhd b/fpga/rtl/shift_engine.vhd new file mode 100644 index 0000000..f8531d3 --- /dev/null +++ b/fpga/rtl/shift_engine.vhd @@ -0,0 +1,90 @@ +-- +-- Signed or unsigned variable right-shift. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + + +entity shift_engine is + + generic ( + -- Input word length. + input_data_bits: integer range 4 to 64; + + -- Output word length. + output_data_bits: integer range 4 to 64; + + -- Number of bit positions to pre-shift left before shifting right. + pre_shift_left: integer range 0 to 16; + + -- True to apply sign extension when shifting. + -- False to apply zero extension. + signed_data: boolean + ); + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Data input operand. + -- A new input word is accepted on every clock cycle. + in_data: in std_logic_vector(input_data_bits - 1 downto 0); + + -- Shift input operand. + -- It indicates the number of bit positions to shift right, expressed + -- as an unsigned integer in range 0 to 15. + -- A new input word is accepted on every clock cycle. + in_shift: in std_logic_vector(3 downto 0); + + -- Shifted output data. + -- The output corresponds to the input delayed by 1 clock cycle. + out_data: out std_logic_vector(output_data_bits - 1 downto 0) + ); + +end entity; + +architecture arch of shift_engine is + + -- Output register. + signal r_data: std_logic_vector(output_data_bits - 1 downto 0); + +begin + + -- Drive output. + out_data <= r_data; + + -- + -- Synchronous process. + -- + process (clk) is + begin + if rising_edge(clk) then + + if signed_data then + + r_data <= std_logic_vector( + shift_right( + shift_left( + resize(signed(in_data), output_data_bits), + pre_shift_left), + to_integer(unsigned(in_shift)))); + + else + + r_data <= std_logic_vector( + shift_right( + shift_left( + resize(unsigned(in_data), output_data_bits), + pre_shift_left), + to_integer(unsigned(in_shift)))); + + end if; + + end if; + end process; + +end architecture; diff --git a/fpga/rtl/timestamp_gen.vhd b/fpga/rtl/timestamp_gen.vhd new file mode 100644 index 0000000..ba41d63 --- /dev/null +++ b/fpga/rtl/timestamp_gen.vhd @@ -0,0 +1,57 @@ +-- +-- Generate timestamps. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.puzzlefw_pkg.all; + + +entity timestamp_gen is + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Reset, active high, synchronous to main clock. + reset: in std_logic; + + -- High to reset the timestamp counter to 0. + clear: in std_logic; + + -- Timestamp. + timestamp: out std_logic_vector(timestamp_bits - 1 downto 0) + ); + +end entity; + +architecture arch of timestamp_gen is + + signal r_counter: unsigned(timestamp_bits - 1 downto 0); + +begin + + -- Drive output. + timestamp <= std_logic_vector(r_counter); + + -- + -- Synchronous process. + -- + process (clk) is + begin + if rising_edge(clk) then + + if (reset = '1') or (clear = '1') then + r_counter <= (others => '0'); + else + r_counter <= r_counter + 1; + end if; + + end if; + end process; + +end architecture; diff --git a/fpga/rtl/trigger_detector.vhd b/fpga/rtl/trigger_detector.vhd new file mode 100644 index 0000000..cf8bac1 --- /dev/null +++ b/fpga/rtl/trigger_detector.vhd @@ -0,0 +1,112 @@ +-- +-- Detect external triggers. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.puzzlefw_pkg.all; + + +entity trigger_detector is + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Reset, active high, synchronous to main clock. + reset: in std_logic; + + -- High to enable automatic (continuous) triggering. + trig_auto_en: in std_logic; + + -- High to enable external triggering. + trig_ext_en: in std_logic; + + -- High to force a trigger event (if the acquisition chain is ready). + trig_force: in std_logic; + + -- Select external input signal to use as trigger. + trig_select: in std_logic_vector(1 downto 0); + + -- High to trigger on falling edge, low to trigger on rising edge. + trig_falling: in std_logic; + + -- Digital input signals. + trig_ext_in: in std_logic_vector(3 downto 0); + + -- High in the clock cycle following the occurrence of a trigger. + trig_out: out std_logic + ); + +end entity; + +architecture arch of trigger_detector is + + type regs_type is record + prev_level: std_logic; + ext_trig: std_logic; + 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 rnext: regs_type; + +begin + + -- Drive output. + trig_out <= r.trig_out; + + -- + -- Combinatorial process. + -- + process (all) is + variable v: regs_type; + variable v_trig: std_logic; + begin + -- Load current register values. + v := r; + + -- Select external input signal. + v.prev_level := trig_ext_in(to_integer(unsigned(trig_select))); + + -- Detect active edge. + v.ext_trig := (not (r.prev_level xor trig_falling)) and + (v.prev_level xor trig_falling); + + -- Combine trigger sources. + v.trig_out := trig_auto_en or + (trig_ext_en and r.ext_trig) or + trig_force; + + -- Synchronous reset. + if reset = '1' then + v.ext_trig := '0'; + v.trig_out := '0'; + 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/vivado/nonproject.tcl b/fpga/vivado/nonproject.tcl index 395d769..00fa01f 100644 --- a/fpga/vivado/nonproject.tcl +++ b/fpga/vivado/nonproject.tcl @@ -19,10 +19,19 @@ set_property target_language VHDL [current_project] # Load VHDL files. read_vhdl -vhdl2008 ../rtl/puzzlefw_pkg.vhd -read_vhdl -vhdl2008 ../rtl/simple_fifo.vhd +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_sample_stream.vhd read_vhdl -vhdl2008 ../rtl/dma_axi_master.vhd read_vhdl -vhdl2008 ../rtl/dma_write_channel.vhd read_vhdl -vhdl2008 ../rtl/registers.vhd +read_vhdl -vhdl2008 ../rtl/sample_decimation.vhd +read_vhdl -vhdl2008 ../rtl/shift_engine.vhd +read_vhdl -vhdl2008 ../rtl/simple_fifo.vhd +read_vhdl -vhdl2008 ../rtl/timestamp_gen.vhd +read_vhdl -vhdl2008 ../rtl/trigger_detector.vhd read_vhdl -vhdl2008 ../rtl/puzzlefw_top.vhd # Load Zynq block design. diff --git a/os/src/userspace/testje.c b/os/src/userspace/testje.c index f6db422..07f3f17 100644 --- a/os/src/userspace/testje.c +++ b/os/src/userspace/testje.c @@ -31,14 +31,26 @@ #define REG_DMA_EN 0x0100 #define REG_DMA_STATUS 0x0104 #define REG_DMA_CLEAR 0x0108 -#define REG_DMA_ADDR_START 0x0200 -#define REG_DMA_ADDR_END 0x0204 -#define REG_DMA_ADDR_LIMIT 0x0208 -#define REG_DMA_ADDR_INTR 0x020c -#define REG_DMA_ADDR_PTR 0x0210 -#define REG_DMA_CHANNEL_CTRL 0x0214 -#define REG_DMA_INTR_CTRL 0x0218 -#define REG_TEST_DIVIDER 0x0408 +#define REG_TIMESTAMP_LO 0x0180 +#define REG_TIMESTAMP_HI 0x0184 +#define REG_TIMESTAMP_CLEAR 0x0188 +#define REG_ACQ_ADDR_START 0x0200 +#define REG_ACQ_ADDR_END 0x0204 +#define REG_ACQ_ADDR_LIMIT 0x0208 +#define REG_ACQ_ADDR_INTR 0x020c +#define REG_ACQ_ADDR_PTR 0x0210 +#define REG_ACQ_CHANNEL_CTRL 0x0214 +#define REG_ACQ_INTR_CTRL 0x0218 +#define REG_ACQUISITION_EN 0x0220 +#define REG_RECORD_LENGTH 0x0224 +#define REG_DECIMATION_FACTOR 0x0228 +#define REG_SHIFT_STEPS 0x022c +#define REG_AVERAGING_EN 0x0230 +#define REG_CH4_MODE 0x0234 +#define REG_SIMULATE_ADC 0x0238 +#define REG_TRIGGER_MODE 0x0240 +#define REG_TRIGGER_DELAY 0x0244 +#define REG_TRIGGER_STATUS 0x0248 struct puzzlefw_context { @@ -51,6 +63,12 @@ struct puzzlefw_context { char uio_path[128]; }; +enum puzzlefw_trigger_mode { + TRIG_NONE = 0, + TRIG_AUTO = 1, + TRIG_EXTERNAL = 2 +}; + /* * Find PuzzleFW device in /sys filesystem. @@ -395,49 +413,194 @@ void puzzlefw_copy_from_dma(void *dst, volatile void *src, size_t len) } +/* + * Return the current value of the timestamp counter. + */ +uint64_t puzzlefw_get_timestamp(struct puzzlefw_context *ctx) +{ + uint32_t vhi0, vlo, vhi; + vhi0 = puzzlefw_read_reg(ctx, REG_TIMESTAMP_HI); + vlo = puzzlefw_read_reg(ctx, REG_TIMESTAMP_LO); + vhi = puzzlefw_read_reg(ctx, REG_TIMESTAMP_HI); + if (vhi != vhi0) { + vlo = puzzlefw_read_reg(ctx, REG_TIMESTAMP_LO); + } + return (((uint64_t)vhi) << 32) | vlo; +} + + +/* + * Clear the timestamp counter. + */ +void puzzlefw_clear_timestamp(struct puzzlefw_context *ctx) +{ + puzzlefw_write_reg(ctx, REG_TIMESTAMP_CLEAR, 1); +} + + +/* + * Return the current trigger mode. + */ +enum puzzlefw_trigger_mode puzzlefw_get_trigger_mode( + struct puzzlefw_context *ctx) +{ + uint32_t v = puzzlefw_read_reg(ctx, REG_TRIGGER_MODE); + if ((v & 0x01) != 0) { + return TRIG_AUTO; + } + if ((v & 0x02) != 0) { + return TRIG_EXTERNAL; + } + return TRIG_NONE; +} + + +/* + * Set trigger mode. + */ +void puzzlefw_set_trigger_mode( + struct puzzlefw_context *ctx, + enum puzzlefw_trigger_mode mode) +{ + uint32_t v = puzzlefw_read_reg(ctx, REG_TRIGGER_MODE); + v &= 0xfc; + if (mode == TRIG_AUTO) { + v |= 0x01; + } + if (mode == TRIG_EXTERNAL) { + v |= 0x02; + } + puzzlefw_write_reg(ctx, REG_TRIGGER_MODE, v); +} + + +/* + * Return selected external trigger channel. + */ +int puzzlefw_get_trigger_ext_channel(struct puzzlefw_context *ctx) +{ + uint32_t v = puzzlefw_read_reg(ctx, REG_TRIGGER_MODE); + return ((v >> 4) & 7); +} + + +/* + * Select external trigger channel. + */ +void puzzlefw_set_trigger_ext_channel(struct puzzlefw_context *ctx, int channel) +{ + uint32_t v = puzzlefw_read_reg(ctx, REG_TRIGGER_MODE); + v &= 0x8f; + v |= (channel << 4); + puzzlefw_write_reg(ctx, REG_TRIGGER_MODE, v); +} + + +/* + * Return external trigger polarity. + */ +int puzzlefw_get_trigger_ext_falling(struct puzzlefw_context *ctx) +{ + uint32_t v = puzzlefw_read_reg(ctx, REG_TRIGGER_MODE); + return ((v & 0x80) != 0); +} + + +/* + * Select external trigger polarity. + */ +void puzzlefw_set_trigger_ext_falling(struct puzzlefw_context *ctx, int falling) +{ + uint32_t v = puzzlefw_read_reg(ctx, REG_TRIGGER_MODE); + v &= 0x7f; + if (falling) { + v |= 0x80; + } + puzzlefw_write_reg(ctx, REG_TRIGGER_MODE, v); +} + + +/* + * Force a single trigger event. + */ +void puzzlefw_trigger_force(struct puzzlefw_context *ctx) +{ + uint32_t v = puzzlefw_read_reg(ctx, REG_TRIGGER_MODE); + puzzlefw_write_reg(ctx, REG_TRIGGER_MODE, v | 0x100); +} + + static void show_status(struct puzzlefw_context *ctx) { uint32_t v; printf("PuzzleFW registers:\n"); v = puzzlefw_read_reg(ctx, REG_INFO); - printf(" info = 0x%08x\n", v); + printf(" info = 0x%08x\n", v); v = puzzlefw_read_reg(ctx, REG_IRQ_ENABLE); - printf(" irq_enable = 0x%08x\n", v); + printf(" irq_enable = 0x%08x\n", v); v = puzzlefw_read_reg(ctx, REG_IRQ_PENDING); - printf(" irq_pending = 0x%08x\n", v); + printf(" irq_pending = 0x%08x\n", v); v = puzzlefw_read_reg(ctx, REG_DMA_EN); - printf(" dma_en = 0x%08x\n", v); + printf(" dma_en = 0x%08x\n", v); v = puzzlefw_read_reg(ctx, REG_DMA_STATUS); - printf(" dma_status = 0x%08x\n", v); + printf(" dma_status = 0x%08x\n", v); - v = puzzlefw_read_reg(ctx, REG_DMA_ADDR_START); - printf(" dma_addr_start = 0x%08x\n", v); + uint64_t ts = puzzlefw_get_timestamp(ctx); + printf(" timestamp = %llu\n", (unsigned long long)ts); - v = puzzlefw_read_reg(ctx, REG_DMA_ADDR_END); - printf(" dma_addr_end = 0x%08x\n", v); + v = puzzlefw_read_reg(ctx, REG_ACQ_ADDR_START); + printf(" acq_addr_start = 0x%08x\n", v); - v = puzzlefw_read_reg(ctx, REG_DMA_ADDR_LIMIT); - printf(" dma_addr_limit = 0x%08x\n", v); + v = puzzlefw_read_reg(ctx, REG_ACQ_ADDR_END); + printf(" acq_addr_end = 0x%08x\n", v); - v = puzzlefw_read_reg(ctx, REG_DMA_ADDR_INTR); - printf(" dma_addr_intr = 0x%08x\n", v); + v = puzzlefw_read_reg(ctx, REG_ACQ_ADDR_LIMIT); + printf(" acq_addr_limit = 0x%08x\n", v); - v = puzzlefw_read_reg(ctx, REG_DMA_ADDR_PTR); - printf(" dma_addr_ptr = 0x%08x\n", v); + v = puzzlefw_read_reg(ctx, REG_ACQ_ADDR_INTR); + printf(" acq_addr_intr = 0x%08x\n", v); - v = puzzlefw_read_reg(ctx, REG_DMA_CHANNEL_CTRL); - printf(" dma_channel_ctrl = 0x%08x\n", v); + v = puzzlefw_read_reg(ctx, REG_ACQ_ADDR_PTR); + printf(" acq_addr_ptr = 0x%08x\n", v); - v = puzzlefw_read_reg(ctx, REG_DMA_INTR_CTRL); - printf(" dma_intr_ctrl = 0x%08x\n", v); + v = puzzlefw_read_reg(ctx, REG_ACQ_CHANNEL_CTRL); + printf(" acq_channel_ctrl = 0x%08x\n", v); - v = puzzlefw_read_reg(ctx, REG_TEST_DIVIDER); - printf(" test_divider = 0x%08x\n", v); + v = puzzlefw_read_reg(ctx, REG_ACQ_INTR_CTRL); + printf(" acq_intr_ctrl = 0x%08x\n", v); + + v = puzzlefw_read_reg(ctx, REG_SIMULATE_ADC); + printf(" simulate_adc = 0x%08x\n", v); + + enum puzzlefw_trigger_mode trigger_mode = puzzlefw_get_trigger_mode(ctx); + int trigger_channel = puzzlefw_get_trigger_ext_channel(ctx); + int trigger_falling = puzzlefw_get_trigger_ext_falling(ctx); + printf(" trigger mode = %s, channel=%d, falling=%d\n", + (trigger_mode == TRIG_AUTO) ? "auto" : + (trigger_mode == TRIG_EXTERNAL) ?"external" : + "none", + trigger_channel, + trigger_falling); + + v = puzzlefw_read_reg(ctx, REG_TRIGGER_DELAY); + printf(" trigger_delay = 0x%08x\n", v); + + v = puzzlefw_read_reg(ctx, REG_RECORD_LENGTH); + printf(" record_length = 0x%08x\n", v); + + v = puzzlefw_read_reg(ctx, REG_DECIMATION_FACTOR); + printf(" decimation_fac = 0x%08x\n", v); + + v = puzzlefw_read_reg(ctx, REG_SHIFT_STEPS); + printf(" shift_steps = 0x%08x\n", v); + + v = puzzlefw_read_reg(ctx, REG_AVERAGING_EN); + printf(" averaging_en = 0x%08x\n", v); } @@ -489,20 +652,20 @@ static void blast_dma(struct puzzlefw_context *ctx) printf("Starting DMA blaster ...\n"); // Disable DMA writer. - puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 0); + puzzlefw_write_reg(ctx, REG_ACQ_CHANNEL_CTRL, 0); // Setup DMA buffer. - puzzlefw_write_reg(ctx, REG_DMA_ADDR_START, 0); - puzzlefw_write_reg(ctx, REG_DMA_ADDR_END, ctx->dma_buf_size); + puzzlefw_write_reg(ctx, REG_ACQ_ADDR_START, 0); + puzzlefw_write_reg(ctx, REG_ACQ_ADDR_END, ctx->dma_buf_size); // Set invalid limit to keep the writer blasting. - puzzlefw_write_reg(ctx, REG_DMA_ADDR_LIMIT, 0xffffffff); + puzzlefw_write_reg(ctx, REG_ACQ_ADDR_LIMIT, 0xffffffff); // Initialize DMA writer. - puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 2); + puzzlefw_write_reg(ctx, REG_ACQ_CHANNEL_CTRL, 2); // Enable DMA writer. - puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 1); + puzzlefw_write_reg(ctx, REG_ACQ_CHANNEL_CTRL, 1); struct timespec tp; tp.tv_sec = 10; @@ -510,7 +673,7 @@ static void blast_dma(struct puzzlefw_context *ctx) clock_nanosleep(CLOCK_MONOTONIC, 0, &tp, NULL); // Disable DMA writer. - puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 0); + puzzlefw_write_reg(ctx, REG_ACQ_CHANNEL_CTRL, 0); printf("Stopped DMA blaster\n"); } @@ -590,20 +753,20 @@ static int wait_dma_data( if (addr_intr >= ctx->dma_buf_size) { addr_intr -= ctx->dma_buf_size; } - puzzlefw_write_reg(ctx, REG_DMA_ADDR_INTR, addr_intr); - puzzlefw_write_reg(ctx, REG_DMA_INTR_CTRL, 3); + puzzlefw_write_reg(ctx, REG_ACQ_ADDR_INTR, addr_intr); + puzzlefw_write_reg(ctx, REG_ACQ_INTR_CTRL, 3); // Check if data are already available. // This is necessary to prevent a race condition when data becomes // available just before the interrupt is enabled. - uint32_t write_pointer = puzzlefw_read_reg(ctx, REG_DMA_ADDR_PTR); + uint32_t write_pointer = puzzlefw_read_reg(ctx, REG_ACQ_ADDR_PTR); uint32_t navail = (write_pointer >= read_pointer) ? (write_pointer - read_pointer) : (ctx->dma_buf_size + write_pointer - read_pointer); if (navail >= wait_avail) { // Data already available; disable DMA writer interrupts. - puzzlefw_write_reg(ctx, REG_DMA_INTR_CTRL, 2); + puzzlefw_write_reg(ctx, REG_ACQ_INTR_CTRL, 2); return 0; } @@ -615,7 +778,7 @@ static int wait_dma_data( fds[0].fd = ctx->uio_fd; fds[0].events = POLLIN; fds[1].fd = conn; - fds[1].events = 0; + fds[1].events = POLLIN; int ret = poll(fds, 2, timeout_ms); if (ret < 0) { @@ -624,7 +787,7 @@ static int wait_dma_data( } // Disable DMA writer interrupt and clear pending interrupt. - puzzlefw_write_reg(ctx, REG_DMA_INTR_CTRL, 2); + puzzlefw_write_reg(ctx, REG_ACQ_INTR_CTRL, 2); if ((fds[0].revents & POLLIN) != 0) { // Interrupt occurred. @@ -633,8 +796,10 @@ static int wait_dma_data( return 0; } - if ((fds[1].revents & POLLHUP) != 0) { - // Connection closed. + if ((fds[1].revents & (POLLHUP | POLLIN)) != 0) { + // Connection closed, or client sent unexpected data. + // POLLHUP does not happen on Linux as long as our side of the + // connection remains open, but check it anyway to be sure. return 2; } @@ -671,16 +836,16 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn) puzzlefw_write_reg(ctx, REG_DMA_EN, 0); // Disable DMA write channel. - puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 0); + puzzlefw_write_reg(ctx, REG_ACQ_CHANNEL_CTRL, 0); // Initialize DMA write buffer. - puzzlefw_write_reg(ctx, REG_DMA_ADDR_START, 0); - puzzlefw_write_reg(ctx, REG_DMA_ADDR_END, ctx->dma_buf_size); - puzzlefw_write_reg(ctx, REG_DMA_ADDR_LIMIT, + puzzlefw_write_reg(ctx, REG_ACQ_ADDR_START, 0); + puzzlefw_write_reg(ctx, REG_ACQ_ADDR_END, ctx->dma_buf_size); + puzzlefw_write_reg(ctx, REG_ACQ_ADDR_LIMIT, ctx->dma_buf_size - pointer_margin); // Disable DMA writer interrupts; clear interrupt status. - puzzlefw_write_reg(ctx, REG_DMA_INTR_CTRL, 2); + puzzlefw_write_reg(ctx, REG_ACQ_INTR_CTRL, 2); // Clear AXI DMA state. puzzlefw_write_reg(ctx, REG_DMA_CLEAR, 1); @@ -689,10 +854,10 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn) puzzlefw_write_reg(ctx, REG_DMA_EN, 1); // Initialize DMA writer. - puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 2); + puzzlefw_write_reg(ctx, REG_ACQ_CHANNEL_CTRL, 2); // Enable DMA writer. - puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 1); + puzzlefw_write_reg(ctx, REG_ACQ_CHANNEL_CTRL, 1); uint32_t read_pointer = 0; int ret; @@ -709,7 +874,7 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn) } // Determine number of bytes available. - uint32_t write_pointer = puzzlefw_read_reg(ctx, REG_DMA_ADDR_PTR); + uint32_t write_pointer = puzzlefw_read_reg(ctx, REG_ACQ_ADDR_PTR); uint32_t navail = (write_pointer >= read_pointer) ? (write_pointer - read_pointer) : (ctx->dma_buf_size + write_pointer - read_pointer); @@ -729,7 +894,7 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn) break; } - write_pointer = puzzlefw_read_reg(ctx, REG_DMA_ADDR_PTR); + write_pointer = puzzlefw_read_reg(ctx, REG_ACQ_ADDR_PTR); } // Determine number of bytes available. @@ -759,7 +924,7 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn) // Update read pointer and update DMA writer limit. read_pointer += block_size; if (read_pointer > pointer_margin) { - puzzlefw_write_reg(ctx, REG_DMA_ADDR_LIMIT, + puzzlefw_write_reg(ctx, REG_ACQ_ADDR_LIMIT, read_pointer - pointer_margin); } @@ -786,10 +951,10 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn) puzzlefw_write_reg(ctx, REG_DMA_EN, 0); // Disable DMA write channel. - puzzlefw_write_reg(ctx, REG_DMA_CHANNEL_CTRL, 0); + puzzlefw_write_reg(ctx, REG_ACQ_CHANNEL_CTRL, 0); // Disable DMA writer interrupts; clear interrupt status. - puzzlefw_write_reg(ctx, REG_DMA_INTR_CTRL, 2); + puzzlefw_write_reg(ctx, REG_ACQ_INTR_CTRL, 2); return ret; } @@ -860,8 +1025,18 @@ int main(int argc, char **argv) int dmaclear = 0; int blastdma = 0; int server = 0; - int set_divider = 0; - int divider; + int tsclear = 0; + int acqon = 0; + int acqoff = 0; + int trigger = 0; + int trigauto = 0; + int trignone = 0; + int set_trigdelay = 0, trigdelay = 0; + int set_reclen = 0, reclen = 0; + int set_decimate = 0, decimate = 0; + int set_shift = 0, shift_steps = 0; + int avgon = 0, avgoff = 0; + int simon = 0, simoff = 0; if (argc == 2 && strcmp(argv[1], "show") == 0) { show = 1; @@ -877,9 +1052,38 @@ int main(int argc, char **argv) dmaclear = 1; } else if (argc == 2 && strcmp(argv[1], "blastdma") == 0) { blastdma = 1; - } else if (argc == 3 && strcmp(argv[1], "divider") == 0) { - set_divider = 1; - divider = atoi(argv[2]); + } else if (argc == 2 && strcmp(argv[1], "tsclear") == 0) { + tsclear = 1; + } else if (argc == 2 && strcmp(argv[1], "acqon") == 0) { + acqon = 1; + } else if (argc == 2 && strcmp(argv[1], "acqoff") == 0) { + acqoff = 1; + } else if (argc == 2 && strcmp(argv[1], "trigger") == 0) { + trigger = 1; + } else if (argc == 2 && strcmp(argv[1], "trigauto") == 0) { + trigauto = 1; + } else if (argc == 2 && strcmp(argv[1], "trignone") == 0) { + trignone = 1; + } else if (argc == 3 && strcmp(argv[1], "trigdelay") == 0) { + set_trigdelay = 1; + trigdelay = atoi(argv[2]); + } else if (argc == 3 && strcmp(argv[1], "reclen") == 0) { + set_reclen = 1; + reclen = atoi(argv[2]); + } else if (argc == 3 && strcmp(argv[1], "decimate") == 0) { + set_decimate = 1; + decimate = atoi(argv[2]); + } else if (argc == 3 && strcmp(argv[1], "shift") == 0) { + set_shift = 1; + shift_steps = atoi(argv[2]); + } else if (argc == 2 && strcmp(argv[1], "avgon") == 0) { + avgon = 1; + } else if (argc == 2 && strcmp(argv[1], "avgoff") == 0) { + avgoff = 1; + } else if (argc == 2 && strcmp(argv[1], "simon") == 0) { + simon = 1; + } else if (argc == 2 && strcmp(argv[1], "simoff") == 0) { + simoff = 1; } else if (argc == 2 && strcmp(argv[1], "server") == 0) { server = 1; } else { @@ -906,6 +1110,48 @@ int main(int argc, char **argv) " testje blastdma\n" " Run unlimited DMA into buffer for 10 seconds.\n" "\n" + " testje tsclear\n" + " Clear timestamp counter.\n" + "\n" + " testje acqon\n" + " Enable acquisition.\n" + "\n" + " testje acqoff\n" + " Disable acquisition.\n" + "\n" + " testje trigger\n" + " Trigger once.\n" + "\n" + " testje trigauto\n" + " Enable continuous triggering.\n" + "\n" + " testje trignone\n" + " Disable triggering.\n" + "\n" + " testje trigdelay N\n" + " Set trigger delay N cycles.\n" + "\n" + " testje reclen N\n" + " Set record length N + 1.\n" + "\n" + " testje decimate N\n" + " Set decimation factor N + 1.\n" + "\n" + " testje shift N\n" + " Set shift-right by N.\n" + "\n" + " testje avgon\n" + " Enable averaging.\n" + "\n" + " testje avgoff\n" + " Disable averaging.\n" + "\n" + " testje simon\n" + " Use simulated ADC data.\n" + "\n" + " testje simoff\n" + " Use real ADC data.\n" + "\n" " testje server\n" " Open TCP port 5001 to stream DMA data.\n" "\n"); @@ -949,8 +1195,60 @@ int main(int argc, char **argv) blast_dma(&ctx); } - if (set_divider) { - puzzlefw_write_reg(&ctx, REG_TEST_DIVIDER, divider); + if (tsclear) { + puzzlefw_clear_timestamp(&ctx); + } + + if (acqon) { + puzzlefw_write_reg(&ctx, REG_ACQUISITION_EN, 1); + } + + if (acqoff) { + puzzlefw_write_reg(&ctx, REG_ACQUISITION_EN, 0); + } + + if (trigger) { + puzzlefw_trigger_force(&ctx); + } + + if (trigauto) { + puzzlefw_set_trigger_mode(&ctx, TRIG_AUTO); + } + + if (trignone) { + puzzlefw_set_trigger_mode(&ctx, TRIG_NONE); + } + + if (set_trigdelay) { + puzzlefw_write_reg(&ctx, REG_TRIGGER_DELAY, trigdelay); + } + + if (set_reclen) { + puzzlefw_write_reg(&ctx, REG_RECORD_LENGTH, reclen); + } + + if (set_decimate) { + puzzlefw_write_reg(&ctx, REG_DECIMATION_FACTOR, decimate); + } + + if (set_shift) { + puzzlefw_write_reg(&ctx, REG_SHIFT_STEPS, shift_steps); + } + + if (avgon) { + puzzlefw_write_reg(&ctx, REG_AVERAGING_EN, 1); + } + + if (avgoff) { + puzzlefw_write_reg(&ctx, REG_AVERAGING_EN, 0); + } + + if (simon) { + puzzlefw_write_reg(&ctx, REG_SIMULATE_ADC, 1); + } + + if (simoff) { + puzzlefw_write_reg(&ctx, REG_SIMULATE_ADC, 0); } if (server) {