diff --git a/fpga/rtl/dma_axi_master.vhd b/fpga/rtl/dma_axi_master.vhd new file mode 100644 index 0000000..bf20fdb --- /dev/null +++ b/fpga/rtl/dma_axi_master.vhd @@ -0,0 +1,556 @@ +-- +-- AXI3 master for multi-channel DMA controller. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.puzzlefw_pkg.all; + + +entity dma_axi_master is + + generic ( + -- Number of beats per transfer. + transfer_size: integer range 1 to 16 := 16; + + -- Number of read channels. + num_read_channels: integer range 0 to 16; + + -- Number of write channels. + num_write_channels: integer range 0 to 16 + ); + + 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 DMA, low to pause DMA. + -- When dma_en is low, any ongoing transfer will be completed but no new transfer will be started. + dma_en: in std_logic; + + -- High while DMA transactions are in progress. + dma_busy: out std_logic; + + -- Address window in which DMA transaction can occur. + -- The base address is added to the addresses requested by the channels. + window_base_addr: in std_logic_vector(31 downto 12); + window_size: in std_logic_vector(31 downto 12); + + -- Error notifications. + -- If an error occurs, the corresponding error signal will go high and stay high until cleared. + -- After error, further DMA transactions will be halted until the error is cleared. + -- Note that DMA errors will typically cause misalignment of the data flow in client channels. + err_read: out std_logic; -- AXI slave reported error from read transaction + err_write: out std_logic; -- AXI slave reported error from write transaction + err_address: out std_logic; -- Channel requested address outside window + err_any: out std_logic; -- Logical OR of all error signals + + -- High to clear error notifications. + clear_errors: in std_logic; + + -- Read channels. + -- The client passes a read command for a specified address via valid/ready handshake. + -- Some time later, the controller pushes (transfer_size) data words out to the channel. + -- The client must be ready to accept all data words. + -- Multiple transfers can be in flight for the channel. + read_cmd_addr: in dma_address_array(0 to num_read_channels-1); + read_cmd_valid: in std_logic_vector(num_read_channels-1 downto 0); + read_cmd_ready: out std_logic_vector(num_read_channels-1 downto 0); + read_data: out dma_data_array(0 to num_read_channels-1); + read_data_valid: out std_logic_vector(num_read_channels-1 downto 0); + + -- Write channels. + -- The client passes a write command for a specified address via valid/ready handshake. + -- Some time later, the controller pulls (transfer_size) data words from the channel. + -- The client must supply these data words promptly. + -- Some more time later, the controller pulses write_finished to indicate that the write has finished. + -- Multiple transfers can be in flight for the channel. + write_cmd_addr: in dma_address_array(0 to num_write_channels-1); + write_cmd_valid: in std_logic_vector(num_write_channels-1 downto 0); + write_cmd_ready: out std_logic_vector(num_write_channels-1 downto 0); + write_data: in dma_data_array(0 to num_write_channels-1); + write_data_ready: out std_logic_vector(num_write_channels-1 downto 0); + write_finished: out std_logic_vector(num_write_channels-1 downto 0); + + -- AXI3 master. + m_axi_awid: out std_logic_vector(5 downto 0); + m_axi_awaddr: out std_logic_vector(31 downto 0); + m_axi_awlen: out std_logic_vector(3 downto 0); + m_axi_awsize: out std_logic_vector(2 downto 0); + m_axi_awburst: out std_logic_vector(1 downto 0); + m_axi_awlock: out std_logic_vector(1 downto 0); + m_axi_awcache: out std_logic_vector(3 downto 0); + m_axi_awprot: out std_logic_vector(2 downto 0); + m_axi_awqos: out std_logic_vector(3 downto 0); + m_axi_awvalid: out std_logic; + m_axi_awready: in std_logic; + m_axi_wid: out std_logic_vector(5 downto 0); + m_axi_wdata: out std_logic_vector(63 downto 0); + m_axi_wstrb: out std_logic_vector(7 downto 0); + m_axi_wlast: out std_logic; + m_axi_wvalid: out std_logic; + m_axi_wready: in std_logic; + m_axi_bid: in std_logic_vector(5 downto 0); + m_axi_bresp: in std_logic_vector(1 downto 0); + m_axi_bvalid: in std_logic; + m_axi_bready: out std_logic; + m_axi_arid: out std_logic_vector(5 downto 0); + m_axi_araddr: out std_logic_vector(31 downto 0); + m_axi_arlen: out std_logic_vector(3 downto 0); + m_axi_arsize: out std_logic_vector(2 downto 0); + m_axi_arburst: out std_logic_vector(1 downto 0); + m_axi_arlock: out std_logic_vector(1 downto 0); + m_axi_arcache: out std_logic_vector(3 downto 0); + m_axi_arprot: out std_logic_vector(2 downto 0); + m_axi_arqos: out std_logic_vector(3 downto 0); + m_axi_arvalid: out std_logic; + m_axi_arready: in std_logic; + m_axi_rid: in std_logic_vector(5 downto 0); + m_axi_rdata: in std_logic_vector(63 downto 0); + m_axi_rresp: in std_logic_vector(1 downto 0); + m_axi_rlast: in std_logic; + m_axi_rvalid: in std_logic; + m_axi_rready: out std_logic + ); + +end entity; + +architecture arch of dma_axi_master is + + type write_state_type is (WRITE_STATE_IDLE, WRITE_STATE_START, WRITE_STATE_FLOW, WRITE_STATE_WAIT); + type read_state_type is (READ_STATE_IDLE, READ_STATE_START, READ_STATE_WAIT); + + type regs_type is record + + -- Registered output signals to AXI bus. + awaddr: std_logic_vector(31 downto 3); + awvalid: std_logic; + wdata: std_logic_vector(63 downto 0); + wlast: std_logic; + wvalid: std_logic; + araddr: std_logic_vector(31 downto 3); + arvalid: std_logic; + + -- Registered output signals to read channels. + read_cmd_ready: std_logic_vector(num_read_channels-1 downto 0); + read_data: std_logic_vector(63 downto 0); + read_data_valid: std_logic_vector(num_read_channels-1 downto 0); + + -- Registered output signals to write channels. + write_cmd_ready: std_logic_vector(num_write_channels-1 downto 0); + write_finished: std_logic_vector(num_write_channels-1 downto 0); + + -- Registered status output signals. + dma_busy: std_logic; + err_read: std_logic; + err_write: std_logic; + err_address: std_logic; + err_any: std_logic; + + -- Write state machine. + write_state: write_state_type; + write_channel: integer range 0 to maximum(0, num_write_channels - 1); + write_channel_mask: std_logic_vector(num_write_channels-1 downto 0); + cnt_write_beat: unsigned(3 downto 0); + cnt_write_start: unsigned(5 downto 0); + cnt_write_end: unsigned(5 downto 0); + + -- Read command state machine. + read_state: read_state_type; + read_channel: integer range 0 to maximum(0, num_read_channels - 1); + cnt_read_start: unsigned(5 downto 0); + cnt_read_end: unsigned(5 downto 0); + end record; + + constant regs_init: regs_type := ( + awaddr => (others => '0'), + awvalid => '0', + wdata => (others => '0'), + wlast => '0', + wvalid => '0', + araddr => (others => '0'), + arvalid => '0', + read_cmd_ready => (others => '0'), + read_data => (others => '0'), + read_data_valid => (others => '0'), + write_cmd_ready => (others => '0'), + write_finished => (others => '0'), + dma_busy => '0', + err_read => '0', + err_write => '0', + err_address => '0', + err_any => '0', + write_state => WRITE_STATE_IDLE, + write_channel => 0, + write_channel_mask => (others => '0'), + cnt_write_beat => (others => '0'), + cnt_write_start => (others => '0'), + cnt_write_end => (others => '0'), + read_state => READ_STATE_IDLE, + read_channel => 0, + cnt_read_start => (others => '0'), + cnt_read_end => (others => '0') + ); + + signal r: regs_type := regs_init; + signal rnext: regs_type; + + -- Check that the DMA transfer fits inside the address window if it starts at the specified address offset + function is_valid_dma_address(addr: std_logic_vector(31 downto 3); + 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); + end function; + + -- Calculate tha AXI address for a DMA transfer by adding the address offset from the client + -- to the base address of the DMA window. + -- Returns the 29 most significant address bits; the 3 least significant bits are presumed to be 0. + function calc_dma_address(addr: std_logic_vector(31 downto 3); + base_addr: std_logic_vector(31 downto 12)) + return std_logic_vector + is begin + return std_logic_vector(unsigned(addr) + shift_left(resize(unsigned(base_addr), 29), 9)); + end function; + +begin + + -- Drive fixed output signals to AXI bus. + m_axi_awlen <= std_logic_vector(to_unsigned(transfer_size - 1, 4)); -- use fixed burst length + m_axi_arlen <= std_logic_vector(to_unsigned(transfer_size - 1, 4)); -- use fixed burst length + m_axi_awsize <= "011"; -- always use 64-bit transfers + m_axi_arsize <= "011"; -- always use 64-bit transfers + m_axi_awburst <= "01"; -- always use incrementing burst + m_axi_arburst <= "01"; -- always use incrementing burst + m_axi_awlock <= "00"; -- normal access + m_axi_arlock <= "00"; -- normal access + m_axi_awcache <= "0010"; -- normal memory, non-cacheable, non-bufferable + m_axi_arcache <= "0010"; -- normal memory, non-cacheable, non-bufferable + m_axi_awprot <= "000"; -- data access, secure, unprivileged + m_axi_arprot <= "000"; -- data access, secure, unprivileged + m_axi_awqos <= "0000"; -- no QoS + m_axi_arqos <= "0000"; -- no QoS + m_axi_wstrb <= "11111111"; -- always write all byte lanes + m_axi_bready <= '1'; -- always ready for write response + m_axi_rready <= '1'; -- always ready for read response + + -- Drive variable output signals to AXI bus. + m_axi_awid <= std_logic_vector(to_unsigned(r.write_channel, 6)); + m_axi_awaddr <= r.awaddr & "000"; -- addresses are 8-byte aligned + m_axi_awvalid <= r.awvalid; + m_axi_wid <= std_logic_vector(to_unsigned(r.write_channel, 6)); + m_axi_wdata <= r.wdata; + m_axi_wlast <= r.wlast; + m_axi_wvalid <= r.wvalid; + m_axi_arid <= std_logic_vector(to_unsigned(r.read_channel, 6)); + m_axi_araddr <= r.araddr & "000"; -- addresses are 8-byte aligned + m_axi_arvalid <= r.arvalid; + + -- Drive output signals to read channels. + read_cmd_ready <= r.read_cmd_ready; + read_data_valid <= r.read_data_valid; + gen_rdata: for ch in 0 to num_read_channels - 1 generate + read_data(ch) <= r.read_data; + end generate; + + -- Drive output signals to write channels. + write_cmd_ready <= r.write_cmd_ready; + write_finished <= r.write_finished; + + -- Write data ready signalling is complicated: + -- - During WRITE_STATE_START, one write_data_ready cycle is sent to the selected channel. + -- - During WRITE_STATE_FLOW, write_data_ready to the selected channel depends asynchronously on AXI wready. + write_data_ready <= + r.write_channel_mask + when ((r.write_state = WRITE_STATE_START) or (r.write_state = WRITE_STATE_FLOW and m_axi_wready = '1')) + else (others => '0'); + + -- Drive output signals. + dma_busy <= r.dma_busy; + err_read <= r.err_read; + err_write <= r.err_write; + err_address <= r.err_address; + err_any <= r.err_any; + + -- + -- Combinatorial process. + -- + process (all) is + variable v: regs_type; + begin + -- Load current register values. + v := r; + + -- Report DMA busy/idle. + if (r.cnt_write_start = r.cnt_write_end) and (r.cnt_read_start = r.cnt_read_end) then + v.dma_busy := '0'; + else + v.dma_busy := '1'; + end if; + + -- Clear pending errors. + if clear_errors = '1' then + v.err_read := '0'; + v.err_write := '0'; + v.err_address := '0'; + v.err_any := '0'; + end if; + + -- + -- Write state machine. + -- + + if num_write_channels > 0 then + + -- By default, do not accept write commands from any channel. + v.write_cmd_ready := (others => '0'); + + -- Maintain one-hot write channel mask for the selected write channel. + -- This register is needed during WRITE_STATE_START and WRITE_STATE_FLOW. + v.write_channel_mask := (others => '0'); + v.write_channel_mask(r.write_channel) := '1'; + + case r.write_state is + when WRITE_STATE_IDLE => + -- Cycle through write channels until we find a channel that wants to write. + if (dma_en = '1') and (r.err_any = '0') then + if write_cmd_valid(r.write_channel) = '1' then + -- This channel wants to read. Let's do it. + v.write_state := WRITE_STATE_START; + v.write_cmd_ready(r.write_channel) := '1'; + v.dma_busy := '1'; + else + -- Move on to the next channel. + if r.write_channel >= num_write_channels - 1 then + v.write_channel := 0; + else + v.write_channel := r.write_channel + 1; + end if; + end if; + end if; + + when WRITE_STATE_START => + -- Calculate the AXI address by adding the client address offset to the window base address. + v.awaddr := calc_dma_address(write_cmd_addr(r.write_channel), window_base_addr); + + -- Setup first data word. + v.wdata := write_data(r.write_channel); + v.cnt_write_beat := (others => '0'); + if transfer_size = 1 then + v.wlast := '1'; + else + v.wlast := '0'; + end if; + + -- Check address. + if is_valid_dma_address(write_cmd_addr(r.write_channel), window_size) then + -- Setup AXI write burst. + v.awvalid := '1'; + v.wvalid := '1'; + v.cnt_write_start := r.cnt_write_start + 1; + if transfer_size = 1 then + v.write_state := WRITE_STATE_WAIT; + else + v.write_state := WRITE_STATE_FLOW; + end if; + else + -- Report invalid address. + -- At this point, the client channel will be stuck with misaligned write command + -- and data channels, and this write transfer will never be confirmed. + -- To recover, some type of reset of the client channel will be necessary. + v.err_address := '1'; + v.err_any := '1'; + v.write_state := WRITE_STATE_IDLE; + end if; + + -- Mark DMA busy. + v.dma_busy := '1'; + + when WRITE_STATE_FLOW => + -- Push subsequent data words to the interconnect. + + -- Drop write command when accepted by interconnect. + if m_axi_awready = '1' then + v.awvalid := '0'; + end if; + + -- Push data words to interconnect. + if m_axi_wready = '1' then + v.wdata := write_data(r.write_channel); + if r.cnt_write_beat = transfer_size - 2 then + -- This will be the last beat of the transfer. + v.wlast := '1'; + v.write_state := WRITE_STATE_WAIT; + else + v.wlast := '0'; + end if; + v.cnt_write_beat := r.cnt_write_beat + 1; + end if; + + when WRITE_STATE_WAIT => + -- Wait until interconnect accepts the last beat. + if m_axi_awready = '1' then + v.awvalid := '0'; + end if; + if m_axi_wready = '1' then + v.wvalid := '0'; + end if; + + if (r.awvalid = '0' or m_axi_awready = '1') and (r.wvalid = '0' or m_axi_wready = '1') then + v.write_state := WRITE_STATE_IDLE; + end if; + + end case; + + end if; + + -- + -- Handle write completion. + -- + + -- Report write completion to the channel client. + -- Only successful write bursts are reported. + -- Note that a write error will cause misalignment between command and data flow in the channel. + for i in 0 to num_write_channels - 1 loop + if (m_axi_bvalid = '1') and (m_axi_bresp(1) = '0') and (unsigned(m_axi_bid(3 downto 0)) = i) then + v.write_finished(i) := '1'; + else + v.write_finished(i) := '0'; + end if; + end loop; + + -- Detect write errors. + if (m_axi_bvalid = '1') and (m_axi_bresp(1) = '1') then + v.err_write := '1'; + v.err_any := '1'; + end if; + + -- Count write burst completed. + if m_axi_bvalid = '1' then + v.cnt_write_end := r.cnt_write_end + 1; + end if; + + -- + -- Read command state machine. + -- + + if num_read_channels > 0 then + + -- By default, do not accept read commands from any channel. + v.read_cmd_ready := (others => '0'); + + case r.read_state is + when READ_STATE_IDLE => + -- Cycle through read channels until we find a channel that wants to read. + if (dma_en = '1') and (r.err_any = '0') then + if read_cmd_valid(r.read_channel) = '1' then + -- This channel wants to read. Let's do it. + v.read_state := READ_STATE_START; + v.read_cmd_ready(r.read_channel) := '1'; + v.dma_busy := '1'; + else + -- Move on to the next channel. + if r.read_channel >= num_read_channels - 1 then + v.read_channel := 0; + else + v.read_channel := r.read_channel + 1; + end if; + end if; + end if; + + when READ_STATE_START => + + -- Calculate the AXI address by adding the client address offset to the window base address. + v.araddr := calc_dma_address(read_cmd_addr(r.read_channel), window_base_addr); + + -- Check address. + if is_valid_dma_address(read_cmd_addr(r.read_channel), window_size) then + -- Setup AXI read burst. + v.arvalid := '1'; + v.read_state := READ_STATE_WAIT; + else + -- Report invalid address. + -- At this point, the client channel will be stuck waiting for results + -- that are never going to arrive. + -- To recover, some type of reset of the client channel will be necessary. + v.err_address := '1'; + v.err_any := '1'; + v.read_state := READ_STATE_IDLE; + end if; + + -- Mark DMA busy. + v.dma_busy := '1'; + + when READ_STATE_WAIT => + -- Wait until interconnect accepts our read burst. + v.dma_busy := '1'; + if m_axi_arready = '1' then + -- Read burst accepted. + v.arvalid := '0'; + v.cnt_read_start := r.cnt_read_start + 1; + v.read_state := READ_STATE_IDLE; + end if; + + end case; + + end if; + + -- + -- Handle read result. + -- + + -- Latch read result in a register. + if m_axi_rvalid = '1' then + v.read_data := m_axi_rdata; + end if; + + -- Report read data to the channel client. + -- Only successful read results are reported. + -- Note than a read error will cause misalignment between command and data flow in the channel. + for i in 0 to num_read_channels - 1 loop + if (m_axi_rvalid = '1') and (m_axi_rresp(1) = '0') and (unsigned(m_axi_rid(3 downto 0)) = i) then + v.read_data_valid(i) := '1'; + else + v.read_data_valid(i) := '0'; + end if; + end loop; + + -- Detect read errors. + if (m_axi_rvalid = '1') and (m_axi_rresp(1) = '1') then + v.err_read := '1'; + v.err_any := '1'; + end if; + + -- Count read burst completed. + if m_axi_rvalid = '1' and m_axi_rlast = '1' then + v.cnt_read_end := r.cnt_read_end + 1; + 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 new file mode 100644 index 0000000..a9b357f --- /dev/null +++ b/fpga/rtl/puzzlefw_pkg.vhd @@ -0,0 +1,91 @@ +-- +-- Global definitions for Red Pitaya PuzzleFW firmware. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + + +package puzzlefw_pkg is + + -- 32-bit address for DMA on AXI bus, aligned to 8-byte multiple. + subtype dma_address_type is std_logic_vector(31 downto 3); + type dma_address_array is array(natural range <>) of dma_address_type; + + -- 64-bit data for DMA on AXI bus. + subtype dma_data_type is std_logic_vector(63 downto 0); + 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_dma_buf_addr: natural := 16#100000#; + constant reg_dma_buf_size: natural := 16#100004#; + constant reg_test_irq: natural := 16#100100#; + constant reg_test_led: natural := 16#100104#; + + -- 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) := + x"4a" + & std_logic_vector(to_unsigned(fw_api_version, 8)) + & std_logic_vector(to_unsigned(fw_version_major, 8)) + & std_logic_vector(to_unsigned(fw_version_minor, 8)); + + -- ADC input port type. + type adc_data_input_type is array(0 to 1) of std_logic_vector(15 downto 0); + + -- Control registers: read/write access by processor, output signals to FPGA. + type registers_control is record + irq_enable: std_logic; + test_irq: std_logic_vector(7 downto 0); + test_led: std_logic_vector(7 downto 0); + dma_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 + 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); + 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; + end record; + + constant registers_control_init: registers_control := ( + irq_enable => '0', + test_irq => (others => '0'), + test_led => (others => '0'), + dma_en => '0', + dma_buf_addr => (others => '0'), + dma_buf_size => (others => '0') + ); + + constant registers_trigger_init: registers_trigger := ( + dma_clear => '0', + start => '0' + ); + +end package; diff --git a/fpga/rtl/puzzlefw_top.vhd b/fpga/rtl/puzzlefw_top.vhd new file mode 100644 index 0000000..771992c --- /dev/null +++ b/fpga/rtl/puzzlefw_top.vhd @@ -0,0 +1,440 @@ +-- +-- Top-level FPGA design for Red Pitaya PuzzleFW firmware. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library unisim; +use unisim.vcomponents.all; + +library xpm; +use xpm.vcomponents.all; + +use work.puzzlefw_pkg.all; + + +entity puzzlefw_top is + + port ( + + -- Ports directly connected to ARM/PS. + DDR_0_addr: inout std_logic_vector(14 downto 0); + DDR_0_ba: inout std_logic_vector(2 downto 0); + DDR_0_cas_n: inout std_logic; + DDR_0_ck_n: inout std_logic; + DDR_0_ck_p: inout std_logic; + DDR_0_cke: inout std_logic; + DDR_0_cs_n: inout std_logic; + DDR_0_dm: inout std_logic_vector(3 downto 0); + DDR_0_dq: inout std_logic_vector(31 downto 0); + DDR_0_dqs_n: inout std_logic_vector(3 downto 0); + DDR_0_dqs_p: inout std_logic_vector(3 downto 0); + DDR_0_odt: inout std_logic; + DDR_0_ras_n: inout std_logic; + DDR_0_reset_n: inout std_logic; + DDR_0_we_n: inout std_logic; + FIXED_IO_0_ddr_vrn: inout std_logic; + FIXED_IO_0_ddr_vrp: inout std_logic; + FIXED_IO_0_mio: inout std_logic_vector(53 downto 0); + FIXED_IO_0_ps_clk: inout std_logic; + FIXED_IO_0_ps_porb: inout std_logic; + FIXED_IO_0_ps_srstb: inout std_logic; + + -- Ports controlled by FPGA. + adc_dat_i: in adc_data_input_type; -- ADC data + adc_clk_i: in std_logic_vector(1 downto 0); -- ADC clock 1=pos, 0=neg + adc_clk_o: out std_logic_vector(1 downto 0); -- optional clock output for ADC + adc_cdcs_o: out std_logic; -- ADC clock duty cycle stabilizer + dac_dat_o: out std_logic_vector(13 downto 0); -- DAC data + dac_wrt_o: out std_logic; -- DAC write control + dac_sel_o: out std_logic; -- DAC channel select + dac_clk_o: out std_logic; -- DAC clock + dac_rst_o: out std_logic; -- DAC reset + 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 + ); + +end puzzlefw_top; + +architecture arch of puzzlefw_top is + + -- Main 125 MHz clock, derived from ADC clock input port. + signal clk_adc: std_logic; + + -- Auxiliary clock from FCLK0. + signal clk_fclk: std_logic; + + -- Main reset signal, derived from FCLK_RESET0, active high, synchronous to clk_adc. + signal s_reset: std_logic; + + signal s_adc_clk_ibuf: std_logic; + signal r_fclk_cnt: unsigned(26 downto 0); + signal r_fclk_led: std_logic; + signal r_adcclk_cnt: unsigned(26 downto 0); + signal r_adcclk_led: std_logic; + + signal s_apb_paddr: std_logic_vector(31 downto 0); + signal s_apb_penable: std_logic; + signal s_apb_prdata: std_logic_vector(31 downto 0); + signal s_apb_pready: std_logic; + signal s_apb_psel: std_logic; + signal s_apb_pslverr: std_logic; + signal s_apb_pwdata: std_logic_vector(31 downto 0); + signal s_apb_pwrite: std_logic; + + signal s_axi_awid: std_logic_vector(5 downto 0); + signal s_axi_awaddr: std_logic_vector(31 downto 0); + signal s_axi_awlen: std_logic_vector(3 downto 0); + signal s_axi_awsize: std_logic_vector(2 downto 0); + signal s_axi_awburst: std_logic_vector(1 downto 0); + signal s_axi_awlock: std_logic_vector(1 downto 0); + signal s_axi_awcache: std_logic_vector(3 downto 0); + signal s_axi_awprot: std_logic_vector(2 downto 0); + signal s_axi_awqos: std_logic_vector(3 downto 0); + signal s_axi_awvalid: std_logic; + signal s_axi_awready: std_logic; + signal s_axi_wid: std_logic_vector(5 downto 0); + signal s_axi_wdata: std_logic_vector(63 downto 0); + signal s_axi_wstrb: std_logic_vector(7 downto 0); + signal s_axi_wlast: std_logic; + signal s_axi_wvalid: std_logic; + signal s_axi_wready: std_logic; + signal s_axi_bid: std_logic_vector(5 downto 0); + signal s_axi_bresp: std_logic_vector(1 downto 0); + signal s_axi_bvalid: std_logic; + signal s_axi_bready: std_logic; + signal s_axi_arid: std_logic_vector(5 downto 0); + signal s_axi_araddr: std_logic_vector(31 downto 0); + signal s_axi_arlen: std_logic_vector(3 downto 0); + signal s_axi_arsize: std_logic_vector(2 downto 0); + signal s_axi_arburst: std_logic_vector(1 downto 0); + signal s_axi_arlock: std_logic_vector(1 downto 0); + signal s_axi_arcache: std_logic_vector(3 downto 0); + signal s_axi_arprot: std_logic_vector(2 downto 0); + signal s_axi_arqos: std_logic_vector(3 downto 0); + signal s_axi_arvalid: std_logic; + signal s_axi_arready: std_logic; + signal s_axi_rid: std_logic_vector(5 downto 0); + signal s_axi_rdata: std_logic_vector(63 downto 0); + signal s_axi_rresp: std_logic_vector(1 downto 0); + signal s_axi_rlast: std_logic; + signal s_axi_rvalid: std_logic; + signal s_axi_rready: std_logic; + + signal s_irq: 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 r_test_prefetch: std_logic; + signal r_test_raddr: std_logic_vector(9 downto 0); + signal r_test_waddr: std_logic_vector(9 downto 0); + signal s_test_dout: std_logic_vector(63 downto 0); + signal s_test_ren: std_logic; + +begin + + s_irq <= s_reg_control.test_irq; + led_o(7 downto 2) <= s_reg_control.test_led(7 downto 2); + + -- Differential clock input for ADC clock. + inst_ibuf_adc_clk: IBUFDS + port map ( + O => s_adc_clk_ibuf, + I => adc_clk_i(1), + IB => adc_clk_i(0) + ); + + -- Clock buffer for ADC clock. + inst_bufg_adc_clk: BUFG + port map ( + I => s_adc_clk_ibuf, + O => clk_adc + ); + + inst_obuf_led0: OBUF + port map ( + I => r_fclk_led, + O => led_o(0) + ); + + inst_obuf_led1: OBUF + port map ( + I => r_adcclk_led, + O => led_o(1) + ); + + -- ARM/PS block design. + inst_blockdesign: entity work.puzzlefw_wrapper + port map ( + sys_clk => clk_adc, + ps_fclk => clk_fclk, + peripheral_reset_0(0) => s_reset, + DDR_0_addr => DDR_0_addr, + DDR_0_ba => DDR_0_ba, + DDR_0_cas_n => DDR_0_cas_n, + DDR_0_ck_n => DDR_0_ck_n, + DDR_0_ck_p => DDR_0_ck_p, + DDR_0_cke => DDR_0_cke, + DDR_0_cs_n => DDR_0_cs_n, + DDR_0_dm => DDR_0_dm, + DDR_0_dq => DDR_0_dq, + DDR_0_dqs_n => DDR_0_dqs_n, + DDR_0_dqs_p => DDR_0_dqs_p, + DDR_0_odt => DDR_0_odt, + DDR_0_ras_n => DDR_0_ras_n, + DDR_0_reset_n => DDR_0_reset_n, + DDR_0_we_n => DDR_0_we_n, + FIXED_IO_0_ddr_vrn => FIXED_IO_0_ddr_vrn, + FIXED_IO_0_ddr_vrp => FIXED_IO_0_ddr_vrp, + FIXED_IO_0_mio => FIXED_IO_0_mio, + FIXED_IO_0_ps_clk => FIXED_IO_0_ps_clk, + FIXED_IO_0_ps_porb => FIXED_IO_0_ps_porb, + FIXED_IO_0_ps_srstb => FIXED_IO_0_ps_srstb, + APB_M_0_paddr => s_apb_paddr, + APB_M_0_penable => s_apb_penable, + APB_M_0_prdata => s_apb_prdata, + APB_M_0_pready(0) => s_apb_pready, + APB_M_0_psel(0) => s_apb_psel, + 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, + S_AXI_HP0_0_araddr => s_axi_araddr, + S_AXI_HP0_0_arburst => s_axi_arburst, + S_AXI_HP0_0_arcache => s_axi_arcache, + S_AXI_HP0_0_arid => s_axi_arid, + S_AXI_HP0_0_arlen => s_axi_arlen, + S_AXI_HP0_0_arlock => s_axi_arlock, + S_AXI_HP0_0_arprot => s_axi_arprot, + S_AXI_HP0_0_arqos => s_axi_arqos, + S_AXI_HP0_0_arready => s_axi_arready, + S_AXI_HP0_0_arsize => s_axi_arsize, + S_AXI_HP0_0_arvalid => s_axi_arvalid, + S_AXI_HP0_0_awaddr => s_axi_awaddr, + S_AXI_HP0_0_awburst => s_axi_awburst, + S_AXI_HP0_0_awcache => s_axi_awcache, + S_AXI_HP0_0_awid => s_axi_awid, + S_AXI_HP0_0_awlen => s_axi_awlen, + S_AXI_HP0_0_awlock => s_axi_awlock, + S_AXI_HP0_0_awprot => s_axi_awprot, + S_AXI_HP0_0_awqos => s_axi_awqos, + S_AXI_HP0_0_awready => s_axi_awready, + S_AXI_HP0_0_awsize => s_axi_awsize, + S_AXI_HP0_0_awvalid => s_axi_awvalid, + S_AXI_HP0_0_bid => s_axi_bid, + S_AXI_HP0_0_bready => s_axi_bready, + S_AXI_HP0_0_bresp => s_axi_bresp, + S_AXI_HP0_0_bvalid => s_axi_bvalid, + S_AXI_HP0_0_rdata => s_axi_rdata, + S_AXI_HP0_0_rid => s_axi_rid, + S_AXI_HP0_0_rlast => s_axi_rlast, + S_AXI_HP0_0_rready => s_axi_rready, + S_AXI_HP0_0_rresp => s_axi_rresp, + S_AXI_HP0_0_rvalid => s_axi_rvalid, + S_AXI_HP0_0_wdata => s_axi_wdata, + S_AXI_HP0_0_wid => s_axi_wid, + S_AXI_HP0_0_wlast => s_axi_wlast, + S_AXI_HP0_0_wready => s_axi_wready, + S_AXI_HP0_0_wstrb => s_axi_wstrb, + S_AXI_HP0_0_wvalid => s_axi_wvalid + ); + + -- Memory-mapped registers. + inst_registers: entity work.registers + port map ( + clk => clk_adc, + reset => s_reset, + apb_psel => s_apb_psel, + apb_penable => s_apb_penable, + apb_pwrite => s_apb_pwrite, + apb_paddr => s_apb_paddr, + apb_pwdata => s_apb_pwdata, + apb_pready => s_apb_pready, + apb_pslverr => s_apb_pslverr, + apb_prdata => s_apb_prdata, + reg_control => s_reg_control, + reg_status => s_reg_status, + reg_trigger => s_reg_trigger + ); + + -- AXI master. + inst_axi_master: entity work.dma_axi_master + generic map ( + transfer_size => 16, + num_read_channels => 1, + num_write_channels => 1 ) + port map ( + clk => clk_adc, + reset => s_reset, + dma_en => s_reg_control.dma_en, + dma_busy => s_reg_status.dma_busy, + window_base_addr => s_reg_control.dma_buf_addr, + window_size => s_reg_control.dma_buf_size, + err_read => s_reg_status.dma_err_read, + err_write => s_reg_status.dma_err_write, + err_address => s_reg_status.dma_err_address, + err_any => s_reg_status.dma_err_any, + clear_errors => s_reg_trigger.dma_clear, + 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, + m_axi_awid => s_axi_awid, + m_axi_awaddr => s_axi_awaddr, + m_axi_awlen => s_axi_awlen, + m_axi_awsize => s_axi_awsize, + m_axi_awburst => s_axi_awburst, + m_axi_awlock => s_axi_awlock, + m_axi_awcache => s_axi_awcache, + m_axi_awprot => s_axi_awprot, + m_axi_awqos => s_axi_awqos, + m_axi_awvalid => s_axi_awvalid, + m_axi_awready => s_axi_awready, + m_axi_wid => s_axi_wid, + m_axi_wdata => s_axi_wdata, + m_axi_wstrb => s_axi_wstrb, + m_axi_wlast => s_axi_wlast, + m_axi_wvalid => s_axi_wvalid, + m_axi_wready => s_axi_wready, + m_axi_bid => s_axi_bid, + m_axi_bresp => s_axi_bresp, + m_axi_bvalid => s_axi_bvalid, + m_axi_bready => s_axi_bready, + m_axi_arid => s_axi_arid, + m_axi_araddr => s_axi_araddr, + m_axi_arlen => s_axi_arlen, + m_axi_arsize => s_axi_arsize, + m_axi_arburst => s_axi_arburst, + m_axi_arlock => s_axi_arlock, + m_axi_arcache => s_axi_arcache, + m_axi_arprot => s_axi_arprot, + m_axi_arqos => s_axi_arqos, + m_axi_arvalid => s_axi_arvalid, + m_axi_arready => s_axi_arready, + m_axi_rid => s_axi_rid, + m_axi_rdata => s_axi_rdata, + m_axi_rresp => s_axi_rresp, + m_axi_rlast => s_axi_rlast, + m_axi_rvalid => s_axi_rvalid, + m_axi_rready => s_axi_rready + ); + +-- little test machine for DMA + inst_mem: xpm_memory_sdpram + 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 ) + 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' ); + + s_dma_write_data <= not s_test_dout; + s_test_ren <= r_test_prefetch or s_dma_write_data_ready; + + 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'; + end if; + end if; + end process; +-- END test + + process (clk_fclk) is + begin + if rising_edge(clk_fclk) then + r_fclk_cnt <= r_fclk_cnt + 1; + r_fclk_led <= r_fclk_cnt(r_fclk_cnt'high); + end if; + end process; + + process (clk_adc) is + begin + if rising_edge(clk_adc) then + r_adcclk_cnt <= r_adcclk_cnt + 1; + r_adcclk_led <= r_adcclk_cnt(r_adcclk_cnt'high); + end if; + end process; + +end architecture; diff --git a/fpga/rtl/registers.vhd b/fpga/rtl/registers.vhd new file mode 100644 index 0000000..57d7db5 --- /dev/null +++ b/fpga/rtl/registers.vhd @@ -0,0 +1,144 @@ +-- +-- Memory-mapped registers for Red Pitaya PuzzleFW firmware. +-- +-- Joris van Rantwijk 2024 +-- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.puzzlefw_pkg.all; + + +entity registers is + + port ( + -- Main clock, active on rising edge. + clk: in std_logic; + + -- Reset, active high, synchronous to main clock. + reset: in std_logic; + + -- APB bus interface. + apb_psel: in std_logic; + apb_penable: in std_logic; + apb_pwrite: in std_logic; + apb_paddr: in std_logic_vector(31 downto 0); + apb_pwdata: in std_logic_vector(31 downto 0); + apb_pready: out std_logic; + apb_pslverr: out std_logic; + apb_prdata: out std_logic_vector(31 downto 0); + + -- FPGA interface. + reg_control: out registers_control; + reg_status: in registers_status; + reg_trigger: out registers_trigger + ); + +end entity; + +architecture arch of registers is + + type regs_type is record + pready: std_logic; + prdata: std_logic_vector(31 downto 0); + reg_control: registers_control; + reg_trigger: registers_trigger; + end record; + + constant regs_init: regs_type := ( + pready => '0', + prdata => (others => '0'), + reg_control => registers_control_init, + reg_trigger => registers_trigger_init + ); + + signal r: regs_type := regs_init; + signal rnext: regs_type; + +begin + + -- Drive output signals. + apb_pready <= r.pready; + apb_pslverr <= '0'; -- never signal errors + apb_prdata <= r.prdata; + + reg_control <= r.reg_control; + reg_trigger <= r.reg_trigger; + + -- Combinatorial process. + process (all) is + variable v: regs_type; + begin + -- Load current register values. + v := r; + + -- Clear single-cycle trigger pulses. + v.reg_trigger := registers_trigger_init; + + -- Respond to each APB access on the next clock cycle (no wait states). + v.pready := apb_psel and (not apb_penable); + + -- Handle APB read transfer. + if apb_psel = '1' and apb_penable = '0' and apb_pwrite = '0' then + + -- Initialize result to all-zero bits. + v.prdata := (others => '0'); + + -- Determine read result as function of address. + 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_dma_en => v.prdata(0) := r.reg_control.dma_en; + when reg_dma_status => + v.prdata(0) := reg_status.dma_busy; + v.prdata(1) := reg_status.dma_err_read; + 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_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; + + end if; + + -- Handle APB write transfer. + if apb_psel = '1' and apb_penable = '0' and apb_pwrite = '1' then + case to_integer(unsigned(apb_paddr and reg_addr_mask)) is + when reg_irq_enable => v.reg_control.irq_enable := apb_pwdata(0); + when reg_dma_en => v.reg_control.dma_en := apb_pwdata(0); + when reg_dma_clear => v.reg_trigger.dma_clear := apb_pwdata(0); + when reg_start => v.reg_trigger.start := apb_pwdata(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; + 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;