Test analog acquisition chain

This commit is contained in:
Joris van Rantwijk 2024-08-26 12:52:35 +02:00
parent 131fe91c67
commit 716d16e6a3
15 changed files with 1686 additions and 147 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

61
fpga/rtl/adc_capture.vhd Normal file
View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,15 +128,10 @@ 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 := (
@ -114,20 +139,31 @@ package puzzlefw_pkg is
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')
);
constant registers_trigger_init: registers_trigger := (
dma_clear => '0',
acq_channel_init => '0',
acq_intr_clear => '0'
);
end package;

View File

@ -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

View File

@ -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);

View File

@ -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;

90
fpga/rtl/shift_engine.vhd Normal file
View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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,6 +413,123 @@ 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;
@ -415,29 +550,57 @@ static void show_status(struct puzzlefw_context *ctx)
v = puzzlefw_read_reg(ctx, REG_DMA_STATUS);
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) {