Add timetagger logic

This commit is contained in:
Joris van Rantwijk 2024-08-30 23:04:02 +02:00
parent 21da05d6cd
commit 96090ac31e
9 changed files with 615 additions and 54 deletions

View File

@ -161,6 +161,7 @@ begin
-- Detect overflow of external data buffer.
if (r.out_valid = '1') and (out_ready = '0') then
v.overflow := '1';
v.out_valid := '0';
end if;
-- If there is a pending overflow, discard data until the buffer

View File

@ -158,7 +158,8 @@ begin
inst_fifo: entity work.simple_fifo
generic map (
data_width => 64,
fifo_depth_bits => queue_size_bits )
fifo_depth_bits => queue_size_bits,
block_ram => true )
port map (
clk => clk,
reset => s_fifo_reset,

View File

@ -75,6 +75,16 @@ package puzzlefw_pkg is
constant reg_adc1_minmax: natural := 16#000294#;
constant reg_adc2_minmax: natural := 16#000298#;
constant reg_adc3_minmax: natural := 16#00029c#;
constant reg_tt_addr_start: natural := 16#000300#;
constant reg_tt_addr_end: natural := 16#000304#;
constant reg_tt_addr_limit: natural := 16#000308#;
constant reg_tt_addr_intr: natural := 16#00030c#;
constant reg_tt_addr_ptr: natural := 16#000310#;
constant reg_tt_dma_ctrl: natural := 16#000314#;
constant reg_tt_intr_ctrl: natural := 16#000318#;
constant reg_tt_dma_status: natural := 16#00031c#;
constant reg_timetagger_en: natural := 16#000320#;
constant reg_timetagger_mark: natural := 16#000324#;
constant reg_dig_simulate: natural := 16#000330#;
constant reg_dig_sample: natural := 16#000338#;
constant reg_led_state: natural := 16#000404#;
@ -84,7 +94,7 @@ package puzzlefw_pkg is
-- Firmware info word.
constant fw_api_version: natural := 1;
constant fw_version_major: natural := 0;
constant fw_version_minor: natural := 4;
constant fw_version_minor: natural := 6;
constant fw_info_word: std_logic_vector(31 downto 0) :=
x"4a"
& std_logic_vector(to_unsigned(fw_api_version, 8))
@ -92,9 +102,11 @@ package puzzlefw_pkg is
& std_logic_vector(to_unsigned(fw_version_minor, 8));
-- Data stream.
constant msg_adc_data: std_logic_vector(7 downto 0) := x"01";
constant msg_trigger: std_logic_vector(7 downto 0) := x"02";
constant msg_overflow: std_logic_vector(7 downto 0) := x"10";
constant msg_adc_data: std_logic_vector(7 downto 0) := x"10";
constant msg_trigger: std_logic_vector(7 downto 0) := x"11";
constant msg_timetagger_data: std_logic_vector(7 downto 4) := x"2";
constant msg_marker: std_logic_vector(7 downto 0) := x"30";
constant msg_overflow: std_logic_vector(7 downto 0) := x"40";
-- Control registers: read/write access by processor, output signals to FPGA.
type registers_control is record
@ -124,6 +136,16 @@ package puzzlefw_pkg is
trig_ext_falling: std_logic;
trigger_delay: std_logic_vector(15 downto 0);
adc_range_clear: std_logic;
tt_addr_start: std_logic_vector(31 downto 7);
tt_addr_end: std_logic_vector(31 downto 7);
tt_addr_limit: std_logic_vector(31 downto 7);
tt_addr_intr: std_logic_vector(31 downto 3);
tt_dma_en: std_logic;
tt_dma_init: std_logic; -- single cycle
tt_intr_en: std_logic;
tt_intr_clear: std_logic; -- single cycle
timetagger_en: std_logic_vector(7 downto 0);
timetagger_mark: std_logic; -- single cycle
dig_simulate: std_logic;
dig_sim_state: std_logic_vector(3 downto 0);
led_state: std_logic_vector(7 downto 0);
@ -133,7 +155,7 @@ package puzzlefw_pkg is
-- Status registers: input signals from FPGA, read-only access by processor.
type registers_status is record
irq_pending: std_logic_vector(0 downto 0);
irq_pending: std_logic_vector(1 downto 0);
dma_busy: std_logic;
dma_err_read: std_logic;
dma_err_write: std_logic;
@ -146,6 +168,8 @@ package puzzlefw_pkg is
adc_sample: adc_data_array(0 to 3);
adc_min_value: adc_data_array(0 to 3);
adc_max_value: adc_data_array(0 to 3);
tt_addr_ptr: std_logic_vector(31 downto 3);
tt_dma_busy: std_logic;
dig_sample: std_logic_vector(3 downto 0);
end record;
@ -176,6 +200,16 @@ package puzzlefw_pkg is
trig_ext_falling => '0',
trigger_delay => (others => '0'),
adc_range_clear => '0',
tt_addr_start => (others => '0'),
tt_addr_end => (others => '0'),
tt_addr_limit => (others => '0'),
tt_addr_intr => (others => '0'),
tt_dma_en => '0',
tt_dma_init => '0',
tt_intr_en => '0',
tt_intr_clear => '0',
timetagger_en => (others => '0'),
timetagger_mark => '0',
dig_simulate => '0',
dig_sim_state => (others => '0'),
led_state => (others => '0'),

View File

@ -58,7 +58,7 @@ entity puzzlefw_top is
dac_pwm_o: out std_logic_vector(3 downto 0); -- PWM DAC
exp_p_io: inout std_logic_vector(7 downto 0); -- extension I/O pos
exp_n_io: inout std_logic_vector(7 downto 0); -- extension I/O neg
led_o: inout std_logic_vector(7 downto 0) -- LEDs
led_o: out std_logic_vector(7 downto 0) -- LEDs
);
end puzzlefw_top;
@ -132,7 +132,7 @@ architecture arch of puzzlefw_top is
signal s_axi_rready: std_logic;
-- Interrupts.
signal s_irq_pending: std_logic_vector(0 downto 0);
signal s_irq_pending: std_logic_vector(1 downto 0);
signal s_irq_f2p: std_logic_vector(7 downto 0);
-- Registers.
@ -140,19 +140,24 @@ architecture arch of puzzlefw_top is
signal s_reg_status: registers_status;
-- DMA write channel control.
signal s_dma_write_cmd_addr: dma_address_array(0 downto 0);
signal s_dma_write_cmd_length: dma_burst_length_array(0 to 0);
signal s_dma_write_cmd_valid: std_logic_vector(0 downto 0);
signal s_dma_write_cmd_ready: std_logic_vector(0 downto 0);
signal s_dma_write_data: dma_data_array(0 downto 0);
signal s_dma_write_data_ready: std_logic_vector(0 downto 0);
signal s_dma_write_finished: std_logic_vector(0 downto 0);
signal s_dma_write_cmd_addr: dma_address_array(0 to 1);
signal s_dma_write_cmd_length: dma_burst_length_array(0 to 1);
signal s_dma_write_cmd_valid: std_logic_vector(1 downto 0);
signal s_dma_write_cmd_ready: std_logic_vector(1 downto 0);
signal s_dma_write_data: dma_data_array(0 to 1);
signal s_dma_write_data_ready: std_logic_vector(1 downto 0);
signal s_dma_write_finished: std_logic_vector(1 downto 0);
signal s_acq_dma_valid: std_logic;
signal s_acq_dma_ready: std_logic;
signal s_acq_dma_empty: std_logic;
signal s_acq_dma_data: dma_data_type;
signal s_tt_dma_valid: std_logic;
signal s_tt_dma_ready: std_logic;
signal s_tt_dma_empty: std_logic;
signal s_tt_dma_data: dma_data_type;
signal s_timestamp: std_logic_vector(timestamp_bits - 1 downto 0);
signal s_adc_data: adc_data_array(0 to 1);
signal s_adc_sample: adc_data_array(0 to 1);
@ -161,13 +166,20 @@ architecture arch of puzzlefw_top is
signal s_dig_deglitch: std_logic_vector(3 downto 0);
signal s_dig_sample: std_logic_vector(3 downto 0);
-- TODO
signal r_mhz_cnt: unsigned(7 downto 0);
signal r_khz_cnt: unsigned(9 downto 0);
signal r_blink_mhz: std_logic;
signal r_blink_mhz_d: std_logic;
signal r_blink_khz: std_logic;
begin
-- Drive LEDs.
led_o(0) <= r_adcclk_led; -- blinking LED, 1 Hz
led_o(1) <= s_reg_control.acquisition_en; -- acquisition enabled
led_o(2) <= s_reg_status.trig_waiting; -- waiting for trigger
-- TODO: led_o(3) <= timetagger_en
led_o(3) <= or_reduce(s_reg_control.timetagger_en); -- timetagger enabled
led_o(7 downto 4) <= s_reg_control.led_state(7 downto 4);
-- Enable ADC clock duty cycle stabilizer.
@ -184,10 +196,39 @@ begin
dac_rst_o <= '0';
dac_pwm_o <= (others => 'Z');
-- Use extension I/O pins as inputs.
exp_p_io <= (others => 'Z');
-- Use extension I/O pins as inputs only.
-- TODO -- temporary test pulse generator
exp_p_io <= (2 => r_blink_khz, 3 => r_blink_khz, 4 => r_blink_mhz, 5 => r_blink_mhz, 6 => r_blink_mhz_d, 7 => r_blink_mhz_d, others => 'Z');
exp_n_io <= (others => 'Z');
-- TODO
process (clk_adc) is
begin
if rising_edge(clk_adc) then
if r_mhz_cnt < 124 then
r_mhz_cnt <= r_mhz_cnt + 1;
else
r_mhz_cnt <= (others => '0');
if r_khz_cnt < 999 then
r_khz_cnt <= r_khz_cnt + 1;
else
r_khz_cnt <= (others => '0');
end if;
end if;
if r_mhz_cnt < 62 then
r_blink_mhz <= '0';
else
r_blink_mhz <= '1';
end if;
r_blink_mhz_d <= r_blink_mhz;
if r_khz_cnt < 500 then
r_blink_khz <= '0';
else
r_blink_khz <= '1';
end if;
end if;
end process;
-- Differential clock input for ADC clock.
inst_ibuf_adc_clk: IBUFDS
port map (
@ -300,7 +341,7 @@ begin
inst_axi_master: entity work.dma_axi_master
generic map (
num_read_channels => 0,
num_write_channels => 1 )
num_write_channels => 2 )
port map (
clk => clk_adc,
reset => s_reset,
@ -366,7 +407,7 @@ begin
m_axi_rready => s_axi_rready
);
-- DMA Write Channel
-- DMA write channel for analog acquisition
inst_acq_dma: entity work.dma_write_channel
generic map (
transfer_size_bits => 4,
@ -398,6 +439,38 @@ begin
write_data_ready => s_dma_write_data_ready(0),
write_finished => s_dma_write_finished(0) );
-- DMA write channel for time tagger
inst_tt_dma: entity work.dma_write_channel
generic map (
transfer_size_bits => 4,
queue_size_bits => 10,
idle_timeout => 256 )
port map (
clk => clk_adc,
reset => s_reset,
channel_en => s_reg_control.tt_dma_en,
channel_busy => s_reg_status.tt_dma_busy,
channel_init => s_reg_control.tt_dma_init,
addr_start => s_reg_control.tt_addr_start,
addr_end => s_reg_control.tt_addr_end,
addr_limit => s_reg_control.tt_addr_limit,
addr_interrupt => s_reg_control.tt_addr_intr,
addr_pointer => s_reg_status.tt_addr_ptr,
intr_en => s_reg_control.tt_intr_en,
intr_clear => s_reg_control.tt_intr_clear,
intr_out => s_irq_pending(1),
in_valid => s_tt_dma_valid,
in_ready => s_tt_dma_ready,
in_empty => s_tt_dma_empty,
in_data => s_tt_dma_data,
write_cmd_addr => s_dma_write_cmd_addr(1),
write_cmd_length => s_dma_write_cmd_length(1),
write_cmd_valid => s_dma_write_cmd_valid(1),
write_cmd_ready => s_dma_write_cmd_ready(1),
write_data => s_dma_write_data(1),
write_data_ready => s_dma_write_data_ready(1),
write_finished => s_dma_write_finished(1) );
-- Timestamp generator.
inst_timestamp_gen: entity work.timestamp_gen
port map (
@ -470,7 +543,7 @@ begin
trig_ext_falling => s_reg_control.trig_ext_falling,
timestamp_in => s_timestamp,
adc_data_in => s_adc_sample,
trig_ext_in => "0000", -- TODO
trig_ext_in => s_dig_sample,
trig_waiting => s_reg_status.trig_waiting,
out_valid => s_acq_dma_valid,
out_ready => s_acq_dma_ready,
@ -518,7 +591,19 @@ begin
-- Monitor digital signal state.
s_reg_status.dig_sample <= s_dig_sample;
-- TODO : time tagger
-- Time tagger.
inst_timetagger: entity work.timetagger
port map (
clk => clk_adc,
reset => s_reset,
channel_en => s_reg_control.timetagger_en,
marker => s_reg_control.timetagger_mark,
timestamp_in => s_timestamp,
dig_sample => s_dig_sample,
out_valid => s_tt_dma_valid,
out_ready => s_tt_dma_ready,
out_empty => s_tt_dma_empty,
out_data => s_tt_dma_data );
-- Collect interrupt signals from peripherals and generate interrupt to PS.
s_reg_status.irq_pending <= s_irq_pending;

View File

@ -76,6 +76,9 @@ begin
v.reg_control.acq_intr_clear := '0';
v.reg_control.trig_force := '0';
v.reg_control.adc_range_clear := '0';
v.reg_control.tt_dma_init := '0';
v.reg_control.tt_intr_clear := '0';
v.reg_control.timetagger_mark := '0';
-- Respond to each APB access on the next clock cycle (no wait states).
v.pready := apb_psel and (not apb_penable);
@ -90,7 +93,7 @@ begin
case to_integer(unsigned(apb_paddr and reg_addr_mask)) is
when reg_info => v.prdata := fw_info_word;
when reg_irq_enable => v.prdata(0) := r.reg_control.irq_enable;
when reg_irq_pending => v.prdata(0 downto 0) := reg_status.irq_pending;
when reg_irq_pending => v.prdata(1 downto 0) := reg_status.irq_pending;
when reg_dma_en => v.prdata(0) := r.reg_control.dma_en;
when reg_dma_status =>
v.prdata(0) := reg_status.dma_busy;
@ -105,8 +108,7 @@ begin
when reg_acq_addr_limit => v.prdata(31 downto 7) := r.reg_control.acq_addr_limit;
when reg_acq_addr_intr => v.prdata(31 downto 3) := r.reg_control.acq_addr_intr;
when reg_acq_addr_ptr => v.prdata(31 downto 3) := reg_status.acq_addr_ptr;
when reg_acq_dma_ctrl =>
v.prdata(0) := r.reg_control.acq_dma_en;
when reg_acq_dma_ctrl => v.prdata(0) := r.reg_control.acq_dma_en;
when reg_acq_intr_ctrl => v.prdata(0) := r.reg_control.acq_intr_en;
when reg_acq_dma_status => v.prdata(0) := reg_status.acq_dma_busy;
when reg_acquisition_en => v.prdata(0) := r.reg_control.acquisition_en;
@ -141,6 +143,15 @@ begin
when reg_adc3_minmax =>
v.prdata(adc_data_bits - 1 downto 0) := reg_status.adc_min_value(3);
v.prdata(adc_data_bits + 15 downto 16) := reg_status.adc_max_value(3);
when reg_tt_addr_start => v.prdata(31 downto 7) := r.reg_control.tt_addr_start;
when reg_tt_addr_end => v.prdata(31 downto 7) := r.reg_control.tt_addr_end;
when reg_tt_addr_limit => v.prdata(31 downto 7) := r.reg_control.tt_addr_limit;
when reg_tt_addr_intr => v.prdata(31 downto 3) := r.reg_control.tt_addr_intr;
when reg_tt_addr_ptr => v.prdata(31 downto 3) := reg_status.tt_addr_ptr;
when reg_tt_dma_ctrl => v.prdata(0) := r.reg_control.tt_dma_en;
when reg_tt_intr_ctrl => v.prdata(0) := r.reg_control.tt_intr_en;
when reg_tt_dma_status => v.prdata(0) := reg_status.tt_dma_busy;
when reg_timetagger_en => v.prdata(7 downto 0) := r.reg_control.timetagger_en;
when reg_dig_simulate =>
v.prdata(8) := r.reg_control.dig_simulate;
v.prdata(3 downto 0) := r.reg_control.dig_sim_state;
@ -185,6 +196,18 @@ begin
v.reg_control.trig_force := apb_pwdata(8);
when reg_trigger_delay => v.reg_control.trigger_delay := apb_pwdata(15 downto 0);
when reg_adc_range_clear => v.reg_control.adc_range_clear := apb_pwdata(0);
when reg_tt_addr_start => v.reg_control.tt_addr_start := apb_pwdata(31 downto 7);
when reg_tt_addr_end => v.reg_control.tt_addr_end := apb_pwdata(31 downto 7);
when reg_tt_addr_limit => v.reg_control.tt_addr_limit := apb_pwdata(31 downto 7);
when reg_tt_addr_intr => v.reg_control.tt_addr_intr := apb_pwdata(31 downto 3);
when reg_tt_dma_ctrl =>
v.reg_control.tt_dma_en := apb_pwdata(0);
v.reg_control.tt_dma_init := apb_pwdata(1);
when reg_tt_intr_ctrl =>
v.reg_control.tt_intr_en := apb_pwdata(0);
v.reg_control.tt_intr_clear := apb_pwdata(1);
when reg_timetagger_en => v.reg_control.timetagger_en := apb_pwdata(7 downto 0);
when reg_timetagger_mark => v.reg_control.timetagger_mark := apb_pwdata(0);
when reg_dig_simulate =>
v.reg_control.dig_simulate := apb_pwdata(8);
v.reg_control.dig_sim_state := apb_pwdata(3 downto 0);

View File

@ -21,7 +21,10 @@ entity simple_fifo is
data_width: integer range 1 to 1024;
-- Size of FIFO as 2-log of the number of words.
fifo_depth_bits: integer range 2 to 16
fifo_depth_bits: integer range 2 to 16;
-- True to use block RAM, False to let the synthesizer choose.
block_ram: boolean
);
port (
@ -75,6 +78,16 @@ architecture arch of simple_fifo is
signal s_ram_wen: std_logic;
signal s_ram_ren: std_logic;
-- Determine the value of the MEMORY_PRIMITIVE attribute.
function choose_memory_primitive return string is
begin
if block_ram then
return "block";
else
return "auto";
end if;
end function;
begin
--
@ -92,7 +105,7 @@ begin
MEMORY_INIT_FILE => "none",
MEMORY_INIT_PARAM => "0",
MEMORY_OPTIMIZATION => "true",
MEMORY_PRIMITIVE => "block",
MEMORY_PRIMITIVE => choose_memory_primitive,
MEMORY_SIZE => data_width * 2**fifo_depth_bits,
MESSAGE_CONTROL => 0,
READ_DATA_WIDTH_B => data_width,

293
fpga/rtl/timetagger.vhd Normal file
View File

@ -0,0 +1,293 @@
--
-- Time tagger logic.
--
-- The time tagger assigns timestamps to events on the digital inputs.
-- It produces a stream of event records to the DMA write channel.
--
-- Output record format:
-- bits 63 : 60 = record type
-- bits 59 : 56 = channel index (only for event record)
-- bits 55 : 52 = 0000
-- bits 51 : 48 = digital signal state (or 0 for overflow record)
-- bits 47 : 0 = timestamp (or 0 for overflow record)
--
-- Channel index:
-- Even channel index (2*k) represents a rising edge on channel k.
-- Odd channel index (2*k+1) represents a falling edge on channel k.
--
-- Joris van Rantwijk 2024
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_misc.all;
use ieee.numeric_std.all;
use work.puzzlefw_pkg.all;
entity timetagger is
port (
-- Main clock, active on rising edge.
clk: in std_logic;
-- Reset, active high, synchronous to main clock.
reset: in std_logic;
-- Channel enable mask.
channel_en: in std_logic_vector(7 downto 0);
-- High to emit a marker record.
-- An arbitrary delay may pass between a marker request and actual
-- emitting of the marker record.
marker: in std_logic;
-- Global timestamp counter.
timestamp_in: in std_logic_vector(timestamp_bits - 1 downto 0);
-- Digital input signals.
dig_sample: in std_logic_vector(3 downto 0);
-- 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 timetagger is
type regs_type is record
overflow: std_logic;
marker_pending: std_logic;
marker_holdoff: std_logic;
prev_sample: std_logic_vector(3 downto 0);
pending_events: std_logic_vector(3 downto 0);
fifo_in_valid: std_logic;
fifo_in_evmask: std_logic_vector(3 downto 0);
out_valid: std_logic;
out_data: dma_data_type;
end record;
constant regs_init: regs_type := (
overflow => '0',
marker_pending => '0',
marker_holdoff => '0',
prev_sample => (others => '0'),
pending_events => (others => '0'),
fifo_in_valid => '0',
fifo_in_evmask => (others => '0'),
out_valid => '0',
out_data => (others => '0')
);
signal r: regs_type := regs_init;
signal rnext: regs_type;
signal s_fifo_in_ready: std_logic;
signal s_fifo_in_data: std_logic_vector(55 downto 0);
signal s_fifo_out_valid: std_logic;
signal s_fifo_out_ready: std_logic;
signal s_fifo_out_data: std_logic_vector(55 downto 0);
-- Return the index of the first active channel.
function find_first_active_channel(ev: std_logic_vector; st: std_logic_vector)
return std_logic_vector
is
constant x: std_logic_vector(ev'length - 1 downto 0) := ev;
constant y: std_logic_vector(st'length - 1 downto 0) := st;
variable r: std_logic_vector(3 downto 0) := "0000";
begin
for i in 0 to x'high loop
if x(i) = '1' then
r(3 downto 1) := std_logic_vector(to_unsigned(i, 3));
r(0) := not y(i);
exit;
end if;
end loop;
return r;
end function;
-- Clear the least significant non-zero bit.
function clear_first_nonzero(ev: std_logic_vector)
return std_logic_vector
is
variable r: std_logic_vector(ev'length - 1 downto 0);
begin
r := ev;
for i in 0 to r'high loop
if r(i) = '1' then
r(i) := '0';
exit;
end if;
end loop;
return r;
end function;
begin
-- Small FIFO buffer for events.
-- A single entry in this buffer may expand into multiple event records
-- thus taking up multiple clock cycles.
inst_event_fifo: entity work.simple_fifo
generic map (
data_width => 56,
fifo_depth_bits => 4,
block_ram => false )
port map (
clk => clk,
reset => reset,
in_valid => r.fifo_in_valid,
in_ready => s_fifo_in_ready,
in_data => s_fifo_in_data,
out_valid => s_fifo_out_valid,
out_ready => s_fifo_out_ready,
out_data => s_fifo_out_data,
queue_length => open );
-- Drive data to FIFO.
-- bits 55 : 52 = mask of active channels
-- bits 51 : 48 = new input state after events
-- bits 47 : 0 = timestamp
s_fifo_in_data <= r.fifo_in_evmask & r.prev_sample & timestamp_in;
-- Read from FIFO if we can produce an output record and there
-- are no pending events.
s_fifo_out_ready <= out_ready and (not or_reduce(r.pending_events));
-- 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;
-- Latch marker request.
if marker = '1' then
v.marker_pending := '1';
end if;
-- Hold off marker record emission during the cycle following insertion
-- of an event into the FIFO. This prevents the marker record from
-- cutting in line and appearing before an event record with an
-- earlier timestamp.
v.marker_holdoff := r.fifo_in_valid;
--
-- Detect events and write to FIFO.
---
-- Hold previous input state.
v.prev_sample := dig_sample;
-- Detect events on enabled channels.
for i in 0 to 3 loop
v.fifo_in_evmask(i) :=
(channel_en(2*i) and (not r.prev_sample(i)) and dig_sample(i))
or (channel_en(2*i+1) and r.prev_sample(i) and (not dig_sample(i)));
end loop;
-- Write changes to FIFO.
if (r.overflow = '0') and (or_reduce(v.fifo_in_evmask) = '1') then
v.fifo_in_valid := '1';
else
v.fifo_in_valid := '0';
end if;
-- Detect FIFO overflow.
if (r.fifo_in_valid = '1') and (s_fifo_in_ready = '0') then
v.overflow := '1';
v.fifo_in_valid := '0';
end if;
--
-- Read from FIFO and generate event records.
--
if out_ready = '1' then
-- By default, do not generate a new record.
v.out_valid := '0';
if or_reduce(r.pending_events) = '1' then
-- Emit the next event of a series of simultaneous events.
v.out_valid := '1';
v.out_data(59 downto 56) := find_first_active_channel(
r.pending_events,
r.out_data(51 downto 48));
v.pending_events := clear_first_nonzero(r.pending_events);
elsif s_fifo_out_valid = '1' then
-- Got digital events from FIFO.
-- Emit the first event. Keep any other events.
v.out_valid := '1';
v.out_data(63 downto 60) := msg_timetagger_data;
v.out_data(59 downto 56) := find_first_active_channel(
s_fifo_out_data(55 downto 52),
s_fifo_out_data(51 downto 48));
v.out_data(55 downto 52) := "0000";
v.out_data(51 downto 48) := s_fifo_out_data(51 downto 48);
v.out_data(47 downto 0) := s_fifo_out_data(47 downto 0);
v.pending_events := clear_first_nonzero(s_fifo_out_data(55 downto 52));
elsif r.overflow = '1' then
-- Wait until output buffer empty, then emit overflow record.
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;
elsif (r.marker_pending = '1') and (r.marker_holdoff = '0') then
-- Emit marker record.
v.out_valid := '1';
v.out_data(63 downto 56) := msg_marker;
v.out_data(55 downto 52) := "0000";
v.out_data(51 downto 48) := r.prev_sample;
v.out_data(47 downto 0) := timestamp_in;
v.marker_pending := '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;

View File

@ -37,6 +37,7 @@ read_vhdl -vhdl2008 ../rtl/shift_engine.vhd
read_vhdl -vhdl2008 ../rtl/syncdff.vhd
read_vhdl -vhdl2008 ../rtl/simple_fifo.vhd
read_vhdl -vhdl2008 ../rtl/timestamp_gen.vhd
read_vhdl -vhdl2008 ../rtl/timetagger.vhd
read_vhdl -vhdl2008 ../rtl/trigger_detector.vhd
read_vhdl -vhdl2008 ../rtl/puzzlefw_top.vhd

View File

@ -59,6 +59,16 @@
#define REG_ADC1_MINMAX 0x0294
#define REG_ADC2_MINMAX 0x0298
#define REG_ADC3_MINMAX 0x029c
#define REG_TT_ADDR_START 0x0300
#define REG_TT_ADDR_END 0x0304
#define REG_TT_ADDR_LIMIT 0x0308
#define REG_TT_ADDR_INTR 0x030c
#define REG_TT_ADDR_PTR 0x0310
#define REG_TT_DMA_CTRL 0x0314
#define REG_TT_INTR_CTRL 0x0318
#define REG_TT_DMA_STATUS 0x031c
#define REG_TIMETAGGER_EN 0x0320
#define REG_TIMETAGGER_MARK 0x0324
#define REG_DIG_SIMULATE 0x0330
#define REG_DIG_SAMPLE 0x0338
#define REG_LED_STATE 0x0404
@ -635,6 +645,33 @@ static void show_status(struct puzzlefw_context *ctx)
v = puzzlefw_read_reg(ctx, REG_DIG_SAMPLE);
printf(" digital input = %d %d %d %d\n",
(v >> 3) & 1, (v >> 2) & 1, (v >> 1) & 1, v & 1);
v = puzzlefw_read_reg(ctx, REG_TT_ADDR_START);
printf(" tt_addr_start = 0x%08x\n", v);
v = puzzlefw_read_reg(ctx, REG_TT_ADDR_END);
printf(" tt_addr_end = 0x%08x\n", v);
v = puzzlefw_read_reg(ctx, REG_TT_ADDR_LIMIT);
printf(" tt_addr_limit = 0x%08x\n", v);
v = puzzlefw_read_reg(ctx, REG_TT_ADDR_INTR);
printf(" tt_addr_intr = 0x%08x\n", v);
v = puzzlefw_read_reg(ctx, REG_TT_ADDR_PTR);
printf(" tt_addr_ptr = 0x%08x\n", v);
v = puzzlefw_read_reg(ctx, REG_TT_DMA_CTRL);
printf(" tt_dma_ctrl = 0x%08x\n", v);
v = puzzlefw_read_reg(ctx, REG_TT_DMA_STATUS);
printf(" tt_dma_status = 0x%08x\n", v);
v = puzzlefw_read_reg(ctx, REG_TT_INTR_CTRL);
printf(" tt_intr_ctrl = 0x%08x\n", v);
v = puzzlefw_read_reg(ctx, REG_TIMETAGGER_EN);
printf(" timetagger_en = 0x%08x\n", v);
}
@ -775,10 +812,15 @@ static int send_all(int conn, const void *buf, size_t len)
static int wait_dma_data(
struct puzzlefw_context *ctx,
int conn,
int timetagger,
uint32_t read_pointer,
uint32_t wait_avail,
int timeout_ms)
{
const uint32_t reg_addr_ptr = timetagger ? REG_TT_ADDR_PTR : REG_ACQ_ADDR_PTR;
const uint32_t reg_addr_intr = timetagger ? REG_TT_ADDR_INTR : REG_ACQ_ADDR_INTR;
const uint32_t reg_intr_ctrl = timetagger ? REG_TT_INTR_CTRL : REG_ACQ_INTR_CTRL;
assert(wait_avail > 0);
assert(wait_avail % ctx->dma_transfer_size == 0);
@ -787,20 +829,20 @@ static int wait_dma_data(
if (addr_intr >= ctx->dma_buf_size) {
addr_intr -= ctx->dma_buf_size;
}
puzzlefw_write_reg(ctx, REG_ACQ_ADDR_INTR, addr_intr);
puzzlefw_write_reg(ctx, REG_ACQ_INTR_CTRL, 3);
puzzlefw_write_reg(ctx, reg_addr_intr, addr_intr);
puzzlefw_write_reg(ctx, reg_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_ACQ_ADDR_PTR);
uint32_t write_pointer = puzzlefw_read_reg(ctx, reg_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_ACQ_INTR_CTRL, 2);
puzzlefw_write_reg(ctx, reg_intr_ctrl, 2);
return 0;
}
@ -821,7 +863,7 @@ static int wait_dma_data(
}
// Disable DMA writer interrupt and clear pending interrupt.
puzzlefw_write_reg(ctx, REG_ACQ_INTR_CTRL, 2);
puzzlefw_write_reg(ctx, reg_intr_ctrl, 2);
if ((fds[0].revents & POLLIN) != 0) {
// Interrupt occurred.
@ -847,7 +889,7 @@ static int wait_dma_data(
*
* Keep running until the TCP connection is closed.
*/
int transmit_dma_data(struct puzzlefw_context *ctx, int conn)
int transmit_dma_data(struct puzzlefw_context *ctx, int conn, int timetagger)
{
// Maximum block size per TCP send() call.
const uint32_t send_max_block = 65536;
@ -861,6 +903,15 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn)
// Reserve this number of bytes in the buffer to avoid ambiguous pointers.
const uint32_t pointer_margin = 4096;
const uint32_t reg_dma_ctrl = timetagger ? REG_TT_DMA_CTRL : REG_ACQ_DMA_CTRL;
const uint32_t reg_dma_status = timetagger ? REG_TT_DMA_STATUS : REG_ACQ_DMA_STATUS;
const uint32_t reg_addr_start = timetagger ? REG_TT_ADDR_START : REG_ACQ_ADDR_START;
const uint32_t reg_addr_end = timetagger ? REG_TT_ADDR_END : REG_ACQ_ADDR_END;
const uint32_t reg_addr_limit = timetagger ? REG_TT_ADDR_LIMIT : REG_ACQ_ADDR_LIMIT;
const uint32_t reg_addr_ptr = timetagger ? REG_TT_ADDR_PTR : REG_ACQ_ADDR_PTR;
const uint32_t reg_intr_ctrl = timetagger ? REG_TT_INTR_CTRL : REG_ACQ_INTR_CTRL;
assert(ctx->dma_buf_size >= 2 * wait_block_size);
assert(send_max_block % ctx->dma_transfer_size == 0);
assert(wait_block_size % ctx->dma_transfer_size == 0);
@ -870,16 +921,15 @@ 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_ACQ_DMA_CTRL, 0);
puzzlefw_write_reg(ctx, reg_dma_ctrl, 0);
// Initialize DMA write buffer.
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);
puzzlefw_write_reg(ctx, reg_addr_start, 0);
puzzlefw_write_reg(ctx, reg_addr_end, ctx->dma_buf_size);
puzzlefw_write_reg(ctx, reg_addr_limit, ctx->dma_buf_size - pointer_margin);
// Disable DMA writer interrupts; clear interrupt status.
puzzlefw_write_reg(ctx, REG_ACQ_INTR_CTRL, 2);
puzzlefw_write_reg(ctx, reg_intr_ctrl, 2);
// Clear AXI DMA state.
puzzlefw_write_reg(ctx, REG_DMA_CLEAR, 1);
@ -888,10 +938,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_ACQ_DMA_CTRL, 2);
puzzlefw_write_reg(ctx, reg_dma_ctrl, 2);
// Enable DMA writer.
puzzlefw_write_reg(ctx, REG_ACQ_DMA_CTRL, 1);
puzzlefw_write_reg(ctx, reg_dma_ctrl, 1);
uint32_t read_pointer = 0;
int ret;
@ -899,7 +949,7 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn)
while (1) {
// Check DMA status.
uint32_t status = puzzlefw_read_reg(ctx, REG_DMA_STATUS);
uint32_t status = puzzlefw_read_reg(ctx, reg_dma_status);
if ((status & 0x1e) != 0) {
// DMA error.
fprintf(stderr, "ERROR: DMA error, status=0x%08x\n", status);
@ -908,7 +958,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_ACQ_ADDR_PTR);
uint32_t write_pointer = puzzlefw_read_reg(ctx, reg_addr_ptr);
uint32_t navail = (write_pointer >= read_pointer) ?
(write_pointer - read_pointer) :
(ctx->dma_buf_size + write_pointer - read_pointer);
@ -916,6 +966,7 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn)
// Wait for enough data in the buffer, or timeout.
if (navail < wait_block_size) {
ret = wait_dma_data(ctx, conn,
timetagger,
read_pointer,
wait_block_size,
timeout_ms);
@ -928,7 +979,7 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn)
break;
}
write_pointer = puzzlefw_read_reg(ctx, REG_ACQ_ADDR_PTR);
write_pointer = puzzlefw_read_reg(ctx, reg_addr_ptr);
}
// Determine number of bytes available.
@ -958,8 +1009,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_ACQ_ADDR_LIMIT,
read_pointer - pointer_margin);
puzzlefw_write_reg(ctx, reg_addr_limit, read_pointer - pointer_margin);
}
navail -= block_size;
@ -985,10 +1035,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_ACQ_DMA_CTRL, 0);
puzzlefw_write_reg(ctx, reg_dma_ctrl, 0);
// Disable DMA writer interrupts; clear interrupt status.
puzzlefw_write_reg(ctx, REG_ACQ_INTR_CTRL, 2);
puzzlefw_write_reg(ctx, reg_intr_ctrl, 2);
return ret;
}
@ -997,9 +1047,9 @@ int transmit_dma_data(struct puzzlefw_context *ctx, int conn)
/*
* Run TCP server.
*/
int run_server(struct puzzlefw_context *ctx)
int run_server(struct puzzlefw_context *ctx, int timetagger)
{
const int tcp_port = 5001;
const int tcp_port = timetagger ? 5002 : 5001;
// Create server socket.
int srv_sock = socket(AF_INET, SOCK_STREAM, 0);
@ -1038,7 +1088,7 @@ int run_server(struct puzzlefw_context *ctx)
close(srv_sock);
int ret = transmit_dma_data(ctx, conn);
int ret = transmit_dma_data(ctx, conn, timetagger);
close(conn);
@ -1064,7 +1114,9 @@ int main(int argc, char **argv)
int acqoff = 0;
int trigger = 0;
int trigauto = 0;
int trigext = 0;
int trignone = 0;
int trigfall = 0, trigrise = 0;
int set_trigdelay = 0, trigdelay = 0;
int set_reclen = 0, reclen = 0;
int set_decimate = 0, decimate = 0;
@ -1073,6 +1125,9 @@ int main(int argc, char **argv)
int simon = 0, simoff = 0;
int rangeclear = 0;
int set_digsim = 0, digsim = 0;
int set_timetagger = 0, timetagger_en = 0;
int marker = 0;
int ttserver = 0;
if (argc == 2 && strcmp(argv[1], "show") == 0) {
show = 1;
@ -1098,8 +1153,14 @@ int main(int argc, char **argv)
trigger = 1;
} else if (argc == 2 && strcmp(argv[1], "trigauto") == 0) {
trigauto = 1;
} else if (argc == 2 && strcmp(argv[1], "trigext") == 0) {
trigext = 1;
} else if (argc == 2 && strcmp(argv[1], "trignone") == 0) {
trignone = 1;
} else if (argc == 2 && strcmp(argv[1], "trigfall") == 0) {
trigfall = 1;
} else if (argc == 2 && strcmp(argv[1], "trigrise") == 0) {
trigrise = 1;
} else if (argc == 3 && strcmp(argv[1], "trigdelay") == 0) {
set_trigdelay = 1;
trigdelay = atoi(argv[2]);
@ -1129,8 +1190,15 @@ int main(int argc, char **argv)
} else {
digsim = atoi(argv[2]);
}
} else if (argc == 3 && strcmp(argv[1], "timetagger") == 0) {
set_timetagger = 1;
timetagger_en = atoi(argv[2]);
} else if (argc == 2 && strcmp(argv[1], "marker") == 0) {
marker = 1;
} else if (argc == 2 && strcmp(argv[1], "server") == 0) {
server = 1;
} else if (argc == 2 && strcmp(argv[1], "ttserver") == 0) {
ttserver = 1;
} else {
printf(
"Usage:\n"
@ -1170,9 +1238,18 @@ int main(int argc, char **argv)
" testje trigauto\n"
" Enable continuous triggering.\n"
"\n"
" testje trigext\n"
" Enable external trigger.\n"
"\n"
" testje trignone\n"
" Disable triggering.\n"
"\n"
" testje trigrise\n"
" Trigger on rising edge.\n"
"\n"
" testje trigfall\n"
" Trigger on falling edge.\n"
"\n"
" testje trigdelay N\n"
" Set trigger delay N cycles.\n"
"\n"
@ -1200,11 +1277,20 @@ int main(int argc, char **argv)
" testje rangeclear\n"
" Clear min/max ADC sample monitor.\n"
"\n"
" testje digsim N/'off'\n"
" testje digsim N|'off'\n"
" Set simulated digital input.\n"
"\n"
" testje timetagger MASK\n"
" Specify set of enabled time tagger channels.\n"
"\n"
" testje marker\n"
" Emit a marker in the time tagger stream.\n"
"\n"
" testje server\n"
" Open TCP port 5001 to stream DMA data.\n"
" Open TCP port 5001 to stream ADC data.\n"
"\n"
" testje ttserver\n"
" Open TCP port 5002 to stream time tagger data.\n"
"\n");
if (argc > 1) {
fprintf(stderr, "ERROR: Invalid command\n");
@ -1266,10 +1352,22 @@ int main(int argc, char **argv)
puzzlefw_set_trigger_mode(&ctx, TRIG_AUTO);
}
if (trigext) {
puzzlefw_set_trigger_mode(&ctx, TRIG_EXTERNAL);
}
if (trignone) {
puzzlefw_set_trigger_mode(&ctx, TRIG_NONE);
}
if (trigrise) {
puzzlefw_set_trigger_ext_falling(&ctx, 0);
}
if (trigfall) {
puzzlefw_set_trigger_ext_falling(&ctx, 1);
}
if (set_trigdelay) {
puzzlefw_write_reg(&ctx, REG_TRIGGER_DELAY, trigdelay);
}
@ -1314,8 +1412,20 @@ int main(int argc, char **argv)
}
}
if (set_timetagger) {
puzzlefw_write_reg(&ctx, REG_TIMETAGGER_EN, timetagger_en);
}
if (marker) {
puzzlefw_write_reg(&ctx, REG_TIMETAGGER_MARK, 1);
}
if (server) {
run_server(&ctx);
run_server(&ctx, 0);
}
if (ttserver) {
run_server(&ctx, 1);
}
puzzlefw_close(&ctx);