diff --git a/fpga/rtl/acquisition_stream.vhd b/fpga/rtl/acquisition_stream.vhd index 8511c4c..e07f4e1 100644 --- a/fpga/rtl/acquisition_stream.vhd +++ b/fpga/rtl/acquisition_stream.vhd @@ -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 diff --git a/fpga/rtl/dma_write_channel.vhd b/fpga/rtl/dma_write_channel.vhd index 5f01fa8..3278ec5 100644 --- a/fpga/rtl/dma_write_channel.vhd +++ b/fpga/rtl/dma_write_channel.vhd @@ -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, diff --git a/fpga/rtl/puzzlefw_pkg.vhd b/fpga/rtl/puzzlefw_pkg.vhd index 718a237..ab3e36e 100644 --- a/fpga/rtl/puzzlefw_pkg.vhd +++ b/fpga/rtl/puzzlefw_pkg.vhd @@ -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'), diff --git a/fpga/rtl/puzzlefw_top.vhd b/fpga/rtl/puzzlefw_top.vhd index fad74b1..1777bd6 100644 --- a/fpga/rtl/puzzlefw_top.vhd +++ b/fpga/rtl/puzzlefw_top.vhd @@ -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; diff --git a/fpga/rtl/registers.vhd b/fpga/rtl/registers.vhd index e2a3919..47369d5 100644 --- a/fpga/rtl/registers.vhd +++ b/fpga/rtl/registers.vhd @@ -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); diff --git a/fpga/rtl/simple_fifo.vhd b/fpga/rtl/simple_fifo.vhd index 7066326..9058319 100644 --- a/fpga/rtl/simple_fifo.vhd +++ b/fpga/rtl/simple_fifo.vhd @@ -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, diff --git a/fpga/rtl/timetagger.vhd b/fpga/rtl/timetagger.vhd new file mode 100644 index 0000000..c43a491 --- /dev/null +++ b/fpga/rtl/timetagger.vhd @@ -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; diff --git a/fpga/vivado/nonproject.tcl b/fpga/vivado/nonproject.tcl index b7b75fb..a05009f 100644 --- a/fpga/vivado/nonproject.tcl +++ b/fpga/vivado/nonproject.tcl @@ -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 diff --git a/os/src/userspace/testje.c b/os/src/userspace/testje.c index 2955071..caa33be 100644 --- a/os/src/userspace/testje.c +++ b/os/src/userspace/testje.c @@ -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);