From 5632ffc6b23caf2e5a7d3fab6e9aabda13a3bf84 Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Fri, 9 Aug 2024 20:16:53 +0200 Subject: [PATCH] Add VHDL for DMA write channel --- fpga/rtl/dma_axi_master.vhd | 3 +- fpga/rtl/dma_write_channel.vhd | 250 +++++++++++++++++++++++++++++++++ fpga/rtl/puzzlefw_pkg.vhd | 62 +++++--- fpga/rtl/puzzlefw_top.vhd | 151 ++++++++------------ fpga/rtl/registers.vhd | 29 ++-- fpga/rtl/simple_fifo.vhd | 208 +++++++++++++++++++++++++++ fpga/vivado/nonproject.tcl | 2 + 7 files changed, 583 insertions(+), 122 deletions(-) create mode 100644 fpga/rtl/dma_write_channel.vhd create mode 100644 fpga/rtl/simple_fifo.vhd diff --git a/fpga/rtl/dma_axi_master.vhd b/fpga/rtl/dma_axi_master.vhd index bf20fdb..22ebab1 100644 --- a/fpga/rtl/dma_axi_master.vhd +++ b/fpga/rtl/dma_axi_master.vhd @@ -207,7 +207,8 @@ architecture arch of dma_axi_master is limit: std_logic_vector(31 downto 12)) return boolean is begin - return (unsigned(limit) /= 0) and (unsigned(addr) <= shift_left(unsigned(limit), 9) - transfer_size); + return (unsigned(limit) /= 0) + and (unsigned(addr) <= shift_left(resize(unsigned(limit), 29), 9) - transfer_size); end function; -- Calculate tha AXI address for a DMA transfer by adding the address offset from the client diff --git a/fpga/rtl/dma_write_channel.vhd b/fpga/rtl/dma_write_channel.vhd new file mode 100644 index 0000000..7a80db0 --- /dev/null +++ b/fpga/rtl/dma_write_channel.vhd @@ -0,0 +1,250 @@ +-- +-- Management of DMA transfers from FPGA to memory. +-- +-- The AXI master MUST be configured for 16 beats per transfer. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.puzzlefw_pkg.all; + + +entity dma_write_channel is + + generic ( + -- Size of the input data queue as 2-log of the number of words. + queue_size_bits: integer range 5 to 16 := 10 + ); + + 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 the channel, low to pause the channel. + -- When channel_en is low, any ongoing transfer will be completed but + -- no new transfer will be started. + channel_en: in std_logic; + + -- High to initialize the channel. + -- + -- This resets the write pointer to the start of the buffer and deletes + -- all data from the queue. + -- + -- This function must be used whenever the buffer address range is changed. + -- This function must not be used while uncompleted DMA transfers + -- are in progress. + channel_init: in std_logic; + + -- Start and end address of the DMA buffer. + -- Both addresses are relative to the DMA window base address. + -- The write pointer wraps to the start address when it would + -- have reached the end address. + addr_start: in std_logic_vector(31 downto 7); + addr_end: in std_logic_vector(31 downto 7); + + -- When the write pointer reaches the limit address, DMA transfers + -- are paused until software updates the limit. + addr_limit: in std_logic_vector(31 downto 7); + + -- An interrupt is triggered when the write pointer equals the + -- interrupt address. + addr_interrupt: in std_logic_vector(31 downto 7); + + -- Write pointer. + addr_pointer: out std_logic_vector(31 downto 7); + + -- High to enable interrupt on reaching a configured address. + intr_en: in std_logic; + + -- Pulsed high to clear a previous interrupt condition. + intr_clear: in std_logic; + + -- High if an enabled interrupt condition occurred and the interrupt + -- has not been cleared yet. + intr_out: out std_logic; + + -- Input data stream to the channel. + in_valid: in std_logic; + in_ready: out std_logic; + in_data: in dma_data_type; + + -- Signals to AXI master. + write_cmd_addr: out dma_address_type; + write_cmd_valid: out std_logic; + write_cmd_ready: in std_logic; + write_data: out dma_data_type; + write_data_ready: in std_logic; + write_finished: in std_logic + ); + +end entity; + +architecture arch of dma_write_channel is + + -- Number of beats per DMA transfer. + -- The AXI master must use the same transfer size. + -- Address alignments must match the transfer size. + constant transfer_size: integer := 16; + + type state_type is (STATE_IDLE, STATE_START, STATE_FLOW); + + type regs_type is record + cmd_valid: std_logic; + cmd_addr: std_logic_vector(31 downto 7); + addr_pointer: std_logic_vector(31 downto 7); + intr_out: std_logic; + state: state_type; + pending_beats: unsigned(4 downto 0); + end record; + + constant regs_init: regs_type := ( + cmd_valid => '0', + cmd_addr => (others => '0'), + addr_pointer => (others => '0'), + intr_out => '0', + state => STATE_IDLE, + pending_beats => (others => '0') + ); + + signal r: regs_type := regs_init; + signal rnext: regs_type; + + signal s_fifo_reset: std_logic; + signal s_fifo_length: unsigned(queue_size_bits - 1 downto 0); + +begin + + -- + -- Data FIFO. + -- + inst_fifo: entity work.simple_fifo + generic map ( + data_width => 64, + fifo_depth_bits => queue_size_bits ) + port map ( + clk => clk, + reset => s_fifo_reset, + in_valid => in_valid, + in_ready => in_ready, + in_data => in_data, + out_valid => open, + out_ready => write_data_ready, + out_data => write_data, + queue_length => s_fifo_length ); + + -- Drive output ports. + addr_pointer <= r.addr_pointer; + intr_out <= r.intr_out; + write_cmd_addr <= r.cmd_addr & "0000"; + write_cmd_valid <= r.cmd_valid; + + -- Drive reset signal to FIFO. + s_fifo_reset <= reset or channel_init; + + -- + -- Combinatorial process. + -- + process (all) is + variable v: regs_type; + begin + -- Load current register values. + v := r; + + -- State machine. + case r.state is + + when STATE_IDLE => + -- Issue a new transfer when possible. + -- Data for the whole transfer must be ready in the FIFO. + if (channel_en = '1') + and (s_fifo_length >= transfer_size) + and (r.cmd_addr /= addr_limit) then + v.cmd_valid := '1'; + v.state := STATE_START; + v.pending_beats := to_unsigned(transfer_size, v.pending_beats'length); + end if; + + when STATE_START => + -- Wait until start of transfer. + if write_cmd_ready = '1' then + v.cmd_valid := '0'; + + -- Update command address. + v.cmd_addr := std_logic_vector(unsigned(r.cmd_addr) + 1); + if v.cmd_addr = addr_end then + -- Wrap at end of buffer. + v.cmd_addr := addr_start; + end if; + + v.state := STATE_FLOW; + end if; + + when STATE_FLOW => + -- Wait until last beat. + if (write_data_ready = '1') and (r.pending_beats = 1) then + v.state := STATE_IDLE; + end if; + + end case; + + -- Count write beats to find end of transfer. + if write_data_ready = '1' then + v.pending_beats := r.pending_beats - 1; + end if; + + -- Update pointer on write completion. + if write_finished = '1' then + v.addr_pointer := std_logic_vector(unsigned(r.addr_pointer) + 1); + if v.addr_pointer = addr_end then + -- Wrap at end of buffer. + v.addr_pointer := addr_start; + end if; + end if; + + -- Clear interrupt. + if intr_clear = '1' then + v.intr_out := '0'; + end if; + + -- Raise interrupt when pointer equals threshold. + if (intr_en = '1') and (r.addr_pointer = addr_interrupt) then + v.intr_out := '1'; + end if; + + -- Initialize channel. + if channel_init = '1' then + v.cmd_valid := '0'; + v.cmd_addr := addr_start; + v.addr_pointer := addr_start; + v.intr_out := '0'; + v.state := STATE_IDLE; + end if; + + -- Synchronous reset. + if reset = '1' then + v := regs_init; + end if; + + -- Drive new register values to synchronous process. + rnext <= v; + + end process; + + -- + -- Synchronous process. + -- + process (clk) is + begin + if rising_edge(clk) then + r <= rnext; + end if; + end process; + +end architecture; diff --git a/fpga/rtl/puzzlefw_pkg.vhd b/fpga/rtl/puzzlefw_pkg.vhd index d407e13..28c88b8 100644 --- a/fpga/rtl/puzzlefw_pkg.vhd +++ b/fpga/rtl/puzzlefw_pkg.vhd @@ -20,25 +20,29 @@ package puzzlefw_pkg is type dma_data_array is array(natural range <>) of dma_data_type; -- Register addresses. - constant reg_addr_mask: std_logic_vector(31 downto 0) := x"0010fffc"; - constant reg_info: natural := 16#000000#; - constant reg_irq_enable: natural := 16#000010#; - constant reg_dma_en: natural := 16#000100#; - constant reg_dma_status: natural := 16#000104#; - constant reg_dma_clear: natural := 16#000108#; - constant reg_rcnt: natural := 16#000200#; - constant reg_wcnt: natural := 16#000204#; - constant reg_start: natural := 16#000208#; - constant reg_test_irq: natural := 16#000400#; - constant reg_test_led: natural := 16#000404#; - constant reg_dma_buf_addr: natural := 16#100000#; - constant reg_dma_buf_size: natural := 16#100004#; + constant reg_addr_mask: std_logic_vector(31 downto 0) := x"0010fffc"; + constant reg_info: natural := 16#000000#; + constant reg_irq_enable: natural := 16#000010#; + constant reg_irq_pending: natural := 16#000014#; + constant reg_dma_en: natural := 16#000100#; + constant reg_dma_status: natural := 16#000104#; + constant reg_dma_clear: natural := 16#000108#; + constant reg_acq_addr_start: natural := 16#000200#; + constant reg_acq_addr_end: natural := 16#000204#; + constant reg_acq_addr_limit: natural := 16#000208#; + constant reg_acq_addr_intr: natural := 16#00020c#; + 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_test_led: natural := 16#000404#; + constant reg_dma_buf_addr: natural := 16#100000#; + constant reg_dma_buf_size: natural := 16#100004#; -- Firmware info word. - constant fw_api_version: natural := 1; - constant fw_version_major: natural := 0; - constant fw_version_minor: natural := 2; - constant fw_info_word: std_logic_vector(31 downto 0) := + constant fw_api_version: natural := 1; + constant fw_version_major: natural := 0; + constant fw_version_minor: natural := 3; + constant fw_info_word: std_logic_vector(31 downto 0) := x"4a" & std_logic_vector(to_unsigned(fw_api_version, 8)) & std_logic_vector(to_unsigned(fw_version_major, 8)) @@ -50,42 +54,54 @@ package puzzlefw_pkg is -- Control registers: read/write access by processor, output signals to FPGA. type registers_control is record irq_enable: std_logic; - test_irq: std_logic_vector(7 downto 0); test_led: std_logic_vector(7 downto 0); dma_en: std_logic; + 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 7); + acq_channel_en: std_logic; + acq_intr_en: std_logic; dma_buf_addr: std_logic_vector(31 downto 12); dma_buf_size: std_logic_vector(31 downto 12); end record; -- Status registers: input signals from FPGA, read-only access by processor. type registers_status is record + irq_pending: std_logic_vector(0 downto 0); dma_busy: std_logic; dma_err_read: std_logic; dma_err_write: std_logic; dma_err_address: std_logic; dma_err_any: std_logic; - rcnt: unsigned(31 downto 0); - wcnt: unsigned(31 downto 0); + acq_addr_ptr: std_logic_vector(31 downto 7); end record; -- Trigger registers: write-only access by processor, single-cycle pulse signals to FPGA. type registers_trigger is record dma_clear: std_logic; - start: std_logic; + acq_channel_init: std_logic; + acq_intr_clear: std_logic; end record; constant registers_control_init: registers_control := ( irq_enable => '0', - test_irq => (others => '0'), test_led => (others => '0'), dma_en => '0', + acq_addr_start => (others => '0'), + acq_addr_end => (others => '0'), + acq_addr_limit => (others => '0'), + acq_addr_intr => (others => '0'), + acq_channel_en => '0', + acq_intr_en => '0', dma_buf_addr => (others => '0'), dma_buf_size => (others => '0') ); constant registers_trigger_init: registers_trigger := ( dma_clear => '0', - start => '0' + acq_channel_init => '0', + acq_intr_clear => '0' ); end package; diff --git a/fpga/rtl/puzzlefw_top.vhd b/fpga/rtl/puzzlefw_top.vhd index 96811ab..7050045 100644 --- a/fpga/rtl/puzzlefw_top.vhd +++ b/fpga/rtl/puzzlefw_top.vhd @@ -6,6 +6,7 @@ library ieee; use ieee.std_logic_1164.all; +use ieee.std_logic_misc.all; use ieee.numeric_std.all; library unisim; @@ -127,23 +128,24 @@ architecture arch of puzzlefw_top is signal s_axi_rvalid: std_logic; signal s_axi_rready: std_logic; - signal s_irq: std_logic_vector(7 downto 0); + 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_read_cmd_addr: dma_address_type; - signal s_dma_read_cmd_valid: std_logic; - signal s_dma_read_cmd_ready: std_logic; - signal s_dma_read_data: dma_data_type; - signal s_dma_read_data_valid: std_logic; - signal s_dma_write_cmd_addr: dma_address_type; - signal s_dma_write_cmd_valid: std_logic; - signal s_dma_write_cmd_ready: std_logic; - signal s_dma_write_data: dma_data_type; - signal s_dma_write_data_ready: std_logic; - signal s_dma_write_finished: std_logic; + signal s_dma_write_cmd_addr: dma_address_array(0 downto 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_in_valid: std_logic; + signal s_dma_in_ready: std_logic; + signal s_dma_in_data: std_logic_vector(63 downto 0); signal r_test_prefetch: std_logic; signal r_test_raddr: std_logic_vector(9 downto 0); @@ -153,8 +155,6 @@ architecture arch of puzzlefw_top is begin - s_irq(7 downto 1) <= s_reg_control.test_irq(7 downto 1); - s_irq(0) <= s_reg_control.test_irq(0) and s_reg_control.irq_enable; led_o(7 downto 2) <= s_reg_control.test_led(7 downto 2); -- Differential clock input for ADC clock. @@ -219,7 +219,7 @@ begin APB_M_0_pslverr(0) => s_apb_pslverr, APB_M_0_pwdata => s_apb_pwdata, APB_M_0_pwrite => s_apb_pwrite, - IRQ_F2P => s_irq, + IRQ_F2P => s_irq_f2p, S_AXI_HP0_0_araddr => s_axi_araddr, S_AXI_HP0_0_arburst => s_axi_arburst, S_AXI_HP0_0_arcache => s_axi_arcache, @@ -282,7 +282,7 @@ begin inst_axi_master: entity work.dma_axi_master generic map ( transfer_size => 16, - num_read_channels => 1, + num_read_channels => 0, num_write_channels => 1 ) port map ( clk => clk_adc, @@ -296,17 +296,17 @@ begin err_address => s_reg_status.dma_err_address, err_any => s_reg_status.dma_err_any, clear_errors => s_reg_trigger.dma_clear, - read_cmd_addr(0) => s_dma_read_cmd_addr, - read_cmd_valid(0) => s_dma_read_cmd_valid, - read_cmd_ready(0) => s_dma_read_cmd_ready, - read_data(0) => s_dma_read_data, - read_data_valid(0) => s_dma_read_data_valid, - write_cmd_addr(0) => s_dma_write_cmd_addr, - write_cmd_valid(0) => s_dma_write_cmd_valid, - write_cmd_ready(0) => s_dma_write_cmd_ready, - write_data(0) => s_dma_write_data, - write_data_ready(0) => s_dma_write_data_ready, - write_finished(0) => s_dma_write_finished, + read_cmd_addr => (others => (others => '0')), + read_cmd_valid => (others => '0'), + read_cmd_ready => open, + read_data => open, + read_data_valid => open, + write_cmd_addr => s_dma_write_cmd_addr, + write_cmd_valid => s_dma_write_cmd_valid, + write_cmd_ready => s_dma_write_cmd_ready, + write_data => s_dma_write_data, + write_data_ready => s_dma_write_data_ready, + write_finished => s_dma_write_finished, m_axi_awid => s_axi_awid, m_axi_awaddr => s_axi_awaddr, m_axi_awlen => s_axi_awlen, @@ -347,76 +347,47 @@ begin m_axi_rready => s_axi_rready ); --- little test machine for DMA - inst_mem: xpm_memory_sdpram + -- DMA Write Channel + inst_write_channel: entity work.dma_write_channel generic map ( - CLOCKING_MODE => "common_clock", - MEMORY_PRIMITIVE => "block", - MEMORY_SIZE => 1024 * 64, - ADDR_WIDTH_A => 10, - ADDR_WIDTH_B => 10, - WRITE_DATA_WIDTH_A => 64, - BYTE_WRITE_WIDTH_A => 64, - READ_DATA_WIDTH_B => 64, - READ_LATENCY_B => 1 ) + queue_size_bits => 10 ) port map ( - clka => clk_adc, - clkb => clk_adc, - rstb => s_reset, - addra => r_test_waddr, - addrb => r_test_raddr, - dina => s_dma_read_data, - doutb => s_test_dout, - ena => s_dma_read_data_valid, - enb => s_test_ren, - wea => "1", - regceb => '1', - injectdbiterra => '0', - injectsbiterra => '0', - sleep => '0' ); + clk => clk_adc, + reset => s_reset, + channel_en => s_reg_control.acq_channel_en, + channel_init => s_reg_trigger.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, + write_cmd_addr => s_dma_write_cmd_addr(0), + write_cmd_valid => s_dma_write_cmd_valid(0), + write_cmd_ready => s_dma_write_cmd_ready(0), + write_data => s_dma_write_data(0), + write_data_ready => s_dma_write_data_ready(0), + write_finished => s_dma_write_finished(0) ); - s_dma_write_data <= not s_test_dout; - s_test_ren <= r_test_prefetch or s_dma_write_data_ready; + -- 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 - r_test_prefetch <= '0'; - if s_dma_read_cmd_ready = '1' and s_dma_read_cmd_valid = '1' then - if unsigned(s_dma_read_cmd_addr) < 1008 then - s_dma_read_cmd_addr <= std_logic_vector(unsigned(s_dma_read_cmd_addr) + 16); - else - s_dma_read_cmd_valid <= '0'; - end if; - end if; - if s_dma_read_data_valid = '1' then - s_reg_status.rcnt <= s_reg_status.rcnt + 1; - r_test_waddr <= std_logic_vector(unsigned(r_test_waddr) + 1); - if unsigned(r_test_waddr) = 1023 then - s_dma_write_cmd_valid <= '1'; - r_test_prefetch <= '1'; - end if; - end if; - if s_dma_write_cmd_ready = '1' and s_dma_write_cmd_valid = '1' then - if unsigned(s_dma_write_cmd_addr) < 1008 then - s_dma_write_cmd_addr <= std_logic_vector(unsigned(s_dma_write_cmd_addr) + 16); - else - s_dma_write_cmd_valid <= '0'; - end if; - end if; - if (r_test_prefetch = '1') or (s_dma_write_data_ready = '1') then - r_test_raddr <= std_logic_vector(unsigned(r_test_raddr) + 1); - end if; - if s_dma_write_finished = '1' then - s_reg_status.wcnt <= s_reg_status.wcnt + 1; - end if; - if s_reg_trigger.start = '1' then - r_test_waddr <= (others => '0'); - r_test_raddr <= (others => '0'); - s_dma_read_cmd_addr <= (others => '0'); - s_dma_write_cmd_addr <= (others => '0'); - s_dma_read_cmd_valid <= '1'; - s_dma_write_cmd_valid <= '0'; + s_dma_in_valid <= '1'; + if s_dma_in_ready = '1' then + s_dma_in_data(15 downto 0) <= std_logic_vector(unsigned(s_dma_in_data(15 downto 0)) + 1); + s_dma_in_data(63 downto 16) <= std_logic_vector(unsigned(s_dma_in_data(63 downto 16)) - 1); end if; end if; end process; diff --git a/fpga/rtl/registers.vhd b/fpga/rtl/registers.vhd index 57d7db5..862083b 100644 --- a/fpga/rtl/registers.vhd +++ b/fpga/rtl/registers.vhd @@ -90,6 +90,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_dma_en => v.prdata(0) := r.reg_control.dma_en; when reg_dma_status => v.prdata(0) := reg_status.dma_busy; @@ -97,12 +98,16 @@ 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_rcnt => v.prdata := std_logic_vector(reg_status.rcnt); - when reg_wcnt => v.prdata := std_logic_vector(reg_status.wcnt); + 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; + when reg_acq_addr_intr => v.prdata(31 downto 7) := r.reg_control.acq_addr_intr; + when reg_acq_addr_ptr => v.prdata(31 downto 7) := reg_status.acq_addr_ptr; + when reg_acq_channel_ctrl => v.prdata(0) := r.reg_control.acq_channel_en; + when reg_acq_intr_ctrl => v.prdata(0) := r.reg_control.acq_intr_en; + when reg_test_led => v.prdata(7 downto 0) := r.reg_control.test_led; when reg_dma_buf_addr => v.prdata(31 downto 12) := r.reg_control.dma_buf_addr; when reg_dma_buf_size => v.prdata(31 downto 12) := r.reg_control.dma_buf_size; - when reg_test_irq => v.prdata(7 downto 0) := r.reg_control.test_irq; - when reg_test_led => v.prdata(7 downto 0) := r.reg_control.test_led; when others => null; end case; @@ -114,15 +119,23 @@ begin 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_start => v.reg_trigger.start := 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 7); + when reg_acq_channel_ctrl => + v.reg_control.acq_channel_en := apb_pwdata(0); + v.reg_trigger.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); + when reg_test_led => v.reg_control.test_led := apb_pwdata(7 downto 0); when reg_dma_buf_addr => v.reg_control.dma_buf_addr := apb_pwdata(31 downto 12); when reg_dma_buf_size => v.reg_control.dma_buf_size := apb_pwdata(31 downto 12); - when reg_test_irq => v.reg_control.test_irq := apb_pwdata(7 downto 0); - when reg_test_led => v.reg_control.test_led := apb_pwdata(7 downto 0); when others => null; end case; end if; - + -- Synchronous reset. if reset = '1' then v := regs_init; diff --git a/fpga/rtl/simple_fifo.vhd b/fpga/rtl/simple_fifo.vhd new file mode 100644 index 0000000..7066326 --- /dev/null +++ b/fpga/rtl/simple_fifo.vhd @@ -0,0 +1,208 @@ +-- +-- Simple FIFO with single clock. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library xpm; +use xpm.vcomponents.all; + +use work.puzzlefw_pkg.all; + + +entity simple_fifo is + + generic ( + -- Word width. + 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 + ); + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Reset, active high, synchronous to main clock. + -- After reset, the FIFO is empty. + reset: in std_logic; + + -- Data input stream. + in_valid: in std_logic; + in_ready: out std_logic; + in_data: in std_logic_vector(data_width - 1 downto 0); + + -- Data output stream. + out_valid: out std_logic; + out_ready: in std_logic; + out_data: out std_logic_vector(data_width - 1 downto 0); + + -- Number of words currently in the FIFO. + -- This excludes the word currently expressed on "out_ready" when "out_valid" is high. + queue_length: out unsigned(fifo_depth_bits - 1 downto 0) + ); + +end entity; + +architecture arch of simple_fifo is + + type regs_type is record + in_ready: std_logic; + out_valid: std_logic; + nonempty: std_logic; + waddr: unsigned(fifo_depth_bits - 1 downto 0); + raddr: unsigned(fifo_depth_bits - 1 downto 0); + qlen: unsigned(fifo_depth_bits - 1 downto 0); + end record; + + constant regs_init: regs_type := ( + in_ready => '0', + out_valid => '0', + nonempty => '0', + waddr => (others => '0'), + raddr => (others => '0'), + qlen => (others => '0') + ); + + signal r: regs_type := regs_init; + signal rnext: regs_type; + + signal s_ram_wen: std_logic; + signal s_ram_ren: std_logic; + +begin + + -- + -- Xilinx Simple Dual Port RAM. + -- + ram_inst: xpm_memory_sdpram + generic map ( + ADDR_WIDTH_A => fifo_depth_bits, + ADDR_WIDTH_B => fifo_depth_bits, + AUTO_SLEEP_TIME => 0, + BYTE_WRITE_WIDTH_A => data_width, + CASCADE_HEIGHT => 0, + CLOCKING_MODE => "common_clock", + ECC_MODE => "no_ecc", + MEMORY_INIT_FILE => "none", + MEMORY_INIT_PARAM => "0", + MEMORY_OPTIMIZATION => "true", + MEMORY_PRIMITIVE => "block", + MEMORY_SIZE => data_width * 2**fifo_depth_bits, + MESSAGE_CONTROL => 0, + READ_DATA_WIDTH_B => data_width, + READ_LATENCY_B => 1, + READ_RESET_VALUE_B => "0", + RST_MODE_A => "SYNC", + RST_MODE_B => "SYNC", + SIM_ASSERT_CHK => 0, + USE_EMBEDDED_CONSTRAINT => 0, + USE_MEM_INIT => 0, + WAKEUP_TIME => "disable_sleep", + WRITE_DATA_WIDTH_A => data_width, + WRITE_MODE_B => "no_change" ) + port map ( + addra => std_logic_vector(r.waddr), + addrb => std_logic_vector(r.raddr), + clka => clk, + clkb => clk, + dbiterrb => open, + dina => in_data, + doutb => out_data, + ena => s_ram_wen, + enb => s_ram_ren, + injectdbiterra => '0', + injectsbiterra => '0', + regceb => '1', + rstb => reset, + sbiterrb => open, + sleep => '0', + wea => "1" ); + + -- + -- Drive output ports. + -- + in_ready <= r.in_ready; + out_valid <= r.out_valid; + queue_length <= r.qlen; + + -- + -- Drive control signals to RAM. + -- + s_ram_wen <= in_valid and r.in_ready; + s_ram_ren <= r.nonempty and (out_ready or (not r.out_valid)); + + -- + -- Combinatorial process. + -- + process (all) is + variable v: regs_type; + begin + -- Load current register values. + v := r; + + -- Update write pointer on write. + if (in_valid = '1') and (r.in_ready = '1') then + v.waddr := r.waddr + 1; + end if; + + -- Update input ready status. + -- Note this uses the NEW value of the write pointer. + if v.waddr + 1 = r.raddr then + v.in_ready := '0'; + else + v.in_ready := '1'; + end if; + + -- On read from RAM, update read pointer. + if (r.nonempty = '1') and (out_ready = '1' or r.out_valid = '0') then + v.raddr := r.raddr + 1; + end if; + + -- Determine whether output word is valid in the next cycle. + if r.nonempty = '1' then + -- Keep output word or load a new word during this cycle. + v.out_valid := '1'; + elsif out_ready = '1' then + -- Consume output word without refreshing it. + v.out_valid := '0'; + end if; + + -- Determine whether RAM will contain data on the next cycle. + -- Note this uses the NEW values of the read and write pointers. + if v.waddr = v.raddr then + v.nonempty := '0'; + else + v.nonempty := '1'; + end if; + + -- Calculate queue length. + -- Note this uses the NEW values of "waddr" and "raddr". + v.qlen := v.waddr - v.raddr; + + -- 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 1528a5f..395d769 100644 --- a/fpga/vivado/nonproject.tcl +++ b/fpga/vivado/nonproject.tcl @@ -19,7 +19,9 @@ 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/dma_axi_master.vhd +read_vhdl -vhdl2008 ../rtl/dma_write_channel.vhd read_vhdl -vhdl2008 ../rtl/registers.vhd read_vhdl -vhdl2008 ../rtl/puzzlefw_top.vhd