Rework DMA to support single-beat transfers
This commit is contained in:
		
							parent
							
								
									c50dd84011
								
							
						
					
					
						commit
						4abc2ee165
					
				|  | @ -1,6 +1,13 @@ | |||
| -- | ||||
| --  AXI3 master for multi-channel DMA controller. | ||||
| -- | ||||
| --  Note: DMA transfers may not cross a 4 kB address boundary. | ||||
| -- | ||||
| --  It is recommended to use only so-called regular transactions. | ||||
| --  A transaction is regular if its length is 1, 2, 4, 8 or 16 beats, | ||||
| --  and its address is aligned to the burst length. | ||||
| --  Regular transactions never cross a 4 kB boundary. | ||||
| -- | ||||
| --  Joris van Rantwijk 2024 | ||||
| -- | ||||
| 
 | ||||
|  | @ -14,9 +21,6 @@ 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; | ||||
| 
 | ||||
|  | @ -57,10 +61,11 @@ entity dma_axi_master is | |||
| 
 | ||||
|         -- 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. | ||||
|         -- Some time later, the controller pushes (read_cmd_length + 1) 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_length:    in  dma_burst_length_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); | ||||
|  | @ -68,11 +73,12 @@ entity dma_axi_master is | |||
| 
 | ||||
|         -- 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. | ||||
|         -- Some time later, the controller pulls (write_cmd_length + 1) 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_length:   in  dma_burst_length_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); | ||||
|  | @ -131,11 +137,13 @@ architecture arch of dma_axi_master is | |||
| 
 | ||||
|         -- Registered output signals to AXI bus. | ||||
|         awaddr:             std_logic_vector(31 downto 3); | ||||
|         awlen:              std_logic_vector(3 downto 0); | ||||
|         awvalid:            std_logic; | ||||
|         wdata:              std_logic_vector(63 downto 0); | ||||
|         wlast:              std_logic; | ||||
|         wvalid:             std_logic; | ||||
|         araddr:             std_logic_vector(31 downto 3); | ||||
|         arlen:              std_logic_vector(3 downto 0); | ||||
|         arvalid:            std_logic; | ||||
| 
 | ||||
|         -- Registered output signals to read channels. | ||||
|  | @ -171,11 +179,13 @@ architecture arch of dma_axi_master is | |||
| 
 | ||||
|     constant regs_init: regs_type := ( | ||||
|         awaddr          => (others => '0'), | ||||
|         awlen           => (others => '0'), | ||||
|         awvalid         => '0', | ||||
|         wdata           => (others => '0'), | ||||
|         wlast           => '0', | ||||
|         wvalid          => '0', | ||||
|         araddr          => (others => '0'), | ||||
|         arlen           => (others => '0'), | ||||
|         arvalid         => '0', | ||||
|         read_cmd_ready  => (others => '0'), | ||||
|         read_data       => (others => '0'), | ||||
|  | @ -204,11 +214,12 @@ architecture arch of dma_axi_master is | |||
| 
 | ||||
|     -- 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); | ||||
|                                   len: std_logic_vector(3 downto 0); | ||||
|                                   limit: std_logic_vector(31 downto 12)) | ||||
|         return boolean | ||||
|     is begin | ||||
|         return (unsigned(limit) /= 0) | ||||
|                 and (unsigned(addr) <= shift_left(resize(unsigned(limit), 29), 9) - transfer_size); | ||||
|                 and (unsigned(addr) < shift_left(resize(unsigned(limit), 29), 9) - unsigned(len)); | ||||
|     end function; | ||||
| 
 | ||||
|     -- Calculate tha AXI address for a DMA transfer by adding the address offset from the client | ||||
|  | @ -224,8 +235,6 @@ architecture arch of dma_axi_master is | |||
| 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 | ||||
|  | @ -245,6 +254,7 @@ begin | |||
|     -- 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_awlen     <= r.awlen; | ||||
|     m_axi_awvalid   <= r.awvalid; | ||||
|     m_axi_wid       <= std_logic_vector(to_unsigned(r.write_channel, 6)); | ||||
|     m_axi_wdata     <= r.wdata; | ||||
|  | @ -252,6 +262,7 @@ begin | |||
|     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_arlen     <= r.arlen; | ||||
|     m_axi_arvalid   <= r.arvalid; | ||||
| 
 | ||||
|     -- Drive output signals to read channels. | ||||
|  | @ -340,23 +351,26 @@ begin | |||
|                 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); | ||||
|                     v.awlen := write_cmd_length(r.write_channel); | ||||
| 
 | ||||
|                     -- Setup first data word. | ||||
|                     v.wdata := write_data(r.write_channel); | ||||
|                     v.cnt_write_beat := (others => '0'); | ||||
|                     if transfer_size = 1 then | ||||
|                     v.cnt_write_beat := unsigned(write_cmd_length(r.write_channel)); | ||||
|                     if unsigned(write_cmd_length(r.write_channel)) = 0 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 | ||||
|                     if is_valid_dma_address(write_cmd_addr(r.write_channel), | ||||
|                                             write_cmd_length(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 | ||||
|                         if unsigned(write_cmd_length(r.write_channel)) = 0 then | ||||
|                             v.write_state := WRITE_STATE_WAIT; | ||||
|                         else | ||||
|                             v.write_state := WRITE_STATE_FLOW; | ||||
|  | @ -385,14 +399,14 @@ begin | |||
|                     -- 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 | ||||
|                         if r.cnt_write_beat = 1 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; | ||||
|                         v.cnt_write_beat := r.cnt_write_beat - 1; | ||||
|                     end if; | ||||
| 
 | ||||
|                 when WRITE_STATE_WAIT => | ||||
|  | @ -470,9 +484,12 @@ begin | |||
| 
 | ||||
|                     -- 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); | ||||
|                     v.arlen := read_cmd_length(r.read_channel); | ||||
| 
 | ||||
|                     -- Check address. | ||||
|                     if is_valid_dma_address(read_cmd_addr(r.read_channel), window_size) then | ||||
|                     if is_valid_dma_address(read_cmd_addr(r.read_channel), | ||||
|                                             read_cmd_length(r.read_channel), | ||||
|                                             window_size) then | ||||
|                         -- Setup AXI read burst. | ||||
|                         v.arvalid := '1'; | ||||
|                         v.read_state := READ_STATE_WAIT; | ||||
|  |  | |||
|  | @ -1,8 +1,6 @@ | |||
| -- | ||||
| --  Management of DMA transfers from FPGA to memory. | ||||
| -- | ||||
| --  The AXI master MUST be configured for 16 beats per transfer. | ||||
| -- | ||||
| --  Joris van Rantwijk 2024 | ||||
| -- | ||||
| 
 | ||||
|  | @ -16,8 +14,16 @@ use work.puzzlefw_pkg.all; | |||
| entity dma_write_channel is | ||||
| 
 | ||||
|     generic ( | ||||
|         -- Preferred DMA burst length as the 2-log of the number of beats. | ||||
|         -- Transactions will use the preferred burst length unless there | ||||
|         -- are fewer words available in the buffer. | ||||
|         transfer_size_bits: integer range 0 to 4 := 4; | ||||
| 
 | ||||
|         -- Size of the input data queue as 2-log of the number of words. | ||||
|         queue_size_bits:    integer range 5 to 16 := 10 | ||||
|         queue_size_bits:    integer range 5 to 16 := 10; | ||||
| 
 | ||||
|         -- Number of clock cycles to wait before starting a small DMA burst. | ||||
|         idle_timeout:       natural := 256 | ||||
|     ); | ||||
| 
 | ||||
|     port ( | ||||
|  | @ -32,6 +38,10 @@ entity dma_write_channel is | |||
|         -- no new transfer will be started. | ||||
|         channel_en:         in  std_logic; | ||||
| 
 | ||||
|         -- High if there uncompleted DMA transfers are in progress. | ||||
|         -- If a DMA error occurs, the channel may get stuck in a busy state. | ||||
|         channel_busy:       out std_logic; | ||||
| 
 | ||||
|         -- High to initialize the channel. | ||||
|         -- | ||||
|         -- This resets the write pointer to the start of the buffer and deletes | ||||
|  | @ -39,7 +49,7 @@ entity dma_write_channel is | |||
|         -- | ||||
|         -- 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. | ||||
|         -- are in progress, except to recover from an error. | ||||
|         channel_init:       in  std_logic; | ||||
| 
 | ||||
|         -- Start and end address of the DMA buffer. | ||||
|  | @ -55,10 +65,10 @@ entity dma_write_channel is | |||
| 
 | ||||
|         -- An interrupt is triggered when the write pointer equals the | ||||
|         -- interrupt address. | ||||
|         addr_interrupt:     in  std_logic_vector(31 downto 7); | ||||
|         addr_interrupt:     in  std_logic_vector(31 downto 3); | ||||
| 
 | ||||
|         -- Write pointer. | ||||
|         addr_pointer:       out std_logic_vector(31 downto 7); | ||||
|         addr_pointer:       out std_logic_vector(31 downto 3); | ||||
| 
 | ||||
|         -- High to enable interrupt on reaching a configured address. | ||||
|         intr_en:            in  std_logic; | ||||
|  | @ -77,6 +87,7 @@ entity dma_write_channel is | |||
| 
 | ||||
|         -- Signals to AXI master. | ||||
|         write_cmd_addr:     out dma_address_type; | ||||
|         write_cmd_length:   out dma_burst_length_type; | ||||
|         write_cmd_valid:    out std_logic; | ||||
|         write_cmd_ready:    in  std_logic; | ||||
|         write_data:         out dma_data_type; | ||||
|  | @ -88,37 +99,56 @@ 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; | ||||
|     constant transfer_size: integer range 1 to 16 := 2**transfer_size_bits; | ||||
| 
 | ||||
|     type state_type is (STATE_IDLE, STATE_START, STATE_FLOW); | ||||
|     type state_type is (STATE_SINGLE_IDLE, STATE_SINGLE_START, STATE_SINGLE_DATA, | ||||
|                         STATE_BURST_PREPARE, | ||||
|                         STATE_BURST_IDLE, STATE_BURST_START, STATE_BURST_DATA); | ||||
| 
 | ||||
|     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); | ||||
|         cmd_addr:           std_logic_vector(31 downto 3); | ||||
|         cmd_full_burst:     std_logic; | ||||
|         addr_pointer:       std_logic_vector(31 downto 3); | ||||
|         intr_out:           std_logic; | ||||
|         channel_busy:       std_logic; | ||||
|         state:              state_type; | ||||
|         pending_beats:      unsigned(4 downto 0); | ||||
|         pending_beats:      unsigned(3 downto 0); | ||||
|         pending_transfers:  unsigned(3 downto 0); | ||||
|         idle_timer:         integer range 0 to idle_timeout - 1; | ||||
|     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') | ||||
|         cmd_valid           => '0', | ||||
|         cmd_addr            => (others => '0'), | ||||
|         cmd_full_burst      => '0', | ||||
|         addr_pointer        => (others => '0'), | ||||
|         intr_out            => '0', | ||||
|         channel_busy        => '0', | ||||
|         state               => STATE_SINGLE_IDLE, | ||||
|         pending_beats       => (others => '0'), | ||||
|         pending_transfers   => (others => '0'), | ||||
|         idle_timer          => 0 | ||||
|     ); | ||||
| 
 | ||||
|     signal r: regs_type := regs_init; | ||||
|     signal rnext: regs_type; | ||||
| 
 | ||||
|     signal s_fifo_reset:    std_logic; | ||||
|     signal s_fifo_valid:    std_logic; | ||||
|     signal s_fifo_length:   unsigned(queue_size_bits - 1 downto 0); | ||||
| 
 | ||||
|     -- Return true if address is aligned to a full-size burst. | ||||
|     function is_address_aligned(addr: std_logic_vector(31 downto 3)) | ||||
|         return boolean | ||||
|     is begin | ||||
|         if transfer_size = 1 then | ||||
|             return true; | ||||
|         else | ||||
|             return (unsigned(addr(transfer_size_bits + 2 downto 3)) = 0); | ||||
|         end if; | ||||
|     end function; | ||||
| 
 | ||||
| begin | ||||
| 
 | ||||
|     -- | ||||
|  | @ -134,19 +164,22 @@ begin | |||
|             in_valid            => in_valid, | ||||
|             in_ready            => in_ready, | ||||
|             in_data             => in_data, | ||||
|             out_valid           => open, | ||||
|             out_valid           => s_fifo_valid, | ||||
|             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; | ||||
|     channel_busy        <= r.channel_busy; | ||||
|     addr_pointer        <= r.addr_pointer; | ||||
|     intr_out            <= r.intr_out; | ||||
|     write_cmd_addr      <= r.cmd_addr; | ||||
|     write_cmd_length    <= "0000" when (r.cmd_full_burst = '0') else | ||||
|                            std_logic_vector(to_unsigned(transfer_size - 1, 4)); | ||||
|     write_cmd_valid     <= r.cmd_valid; | ||||
| 
 | ||||
|     -- Drive reset signal to FIFO. | ||||
|     s_fifo_reset    <= reset or channel_init; | ||||
|     s_fifo_reset        <= reset or channel_init; | ||||
| 
 | ||||
|     -- | ||||
|     -- Combinatorial process. | ||||
|  | @ -160,36 +193,137 @@ begin | |||
|         -- 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); | ||||
|             when STATE_SINGLE_IDLE => | ||||
| 
 | ||||
|                 v.cmd_full_burst := '0'; | ||||
| 
 | ||||
|                 -- Clear busy flag. | ||||
|                 if r.pending_transfers = 0 then | ||||
|                     v.channel_busy := '0'; | ||||
|                 end if; | ||||
| 
 | ||||
|             when STATE_START => | ||||
|                 -- Update idle timer. | ||||
|                 if (s_fifo_valid = '1') and (r.idle_timer /= idle_timeout - 1) then | ||||
|                     v.idle_timer := r.idle_timer + 1; | ||||
|                 end if; | ||||
| 
 | ||||
|                 -- Switch to burst mode if the FIFO has enough data, | ||||
|                 -- and the address is aligned to a full burst. | ||||
|                 if (transfer_size > 1) | ||||
|                         and (s_fifo_length >= transfer_size) | ||||
|                         and is_address_aligned(r.cmd_addr) then | ||||
| 
 | ||||
|                     v.state := STATE_BURST_PREPARE; | ||||
| 
 | ||||
|                 -- Otherwise, start a single-beat transfer when possible. | ||||
|                 elsif (channel_en = '1') | ||||
|                         and (s_fifo_valid = '1') | ||||
|                         and (r.cmd_addr(31 downto 7) /= addr_limit) | ||||
|                         and (r.pending_transfers /= 15) | ||||
|                         and ((transfer_size = 1) | ||||
|                              or (not is_address_aligned(r.cmd_addr)) | ||||
|                              or (r.idle_timer = idle_timeout - 1)) then | ||||
| 
 | ||||
|                     v.cmd_valid := '1'; | ||||
|                     v.channel_busy := '1'; | ||||
|                     v.pending_beats := to_unsigned(0, v.pending_beats'length); | ||||
|                     v.state := STATE_SINGLE_START; | ||||
| 
 | ||||
|                 end if; | ||||
| 
 | ||||
|             when STATE_SINGLE_START => | ||||
|                 -- Wait until start of transfer. | ||||
| 
 | ||||
|                 if write_cmd_ready = '1' then | ||||
|                     v.cmd_valid := '0'; | ||||
| 
 | ||||
|                     -- Update pending transfers. | ||||
|                     v.pending_transfers := r.pending_transfers + 1; | ||||
| 
 | ||||
|                     -- Update command address. | ||||
|                     v.cmd_addr := std_logic_vector(unsigned(r.cmd_addr) + 1); | ||||
|                     if v.cmd_addr(31 downto 7) = addr_end then | ||||
|                         -- Wrap at end of buffer. | ||||
|                         v.cmd_addr := addr_start & "0000"; | ||||
|                     end if; | ||||
| 
 | ||||
|                     if write_data_ready = '1' then | ||||
|                         v.state := STATE_SINGLE_IDLE; | ||||
|                     else | ||||
|                         v.state := STATE_SINGLE_DATA; | ||||
|                     end if; | ||||
|                 end if; | ||||
| 
 | ||||
|                 -- Reset idle counter. | ||||
|                 v.idle_timer := 0; | ||||
| 
 | ||||
|             when STATE_SINGLE_DATA => | ||||
| 
 | ||||
|                 -- Wait until data accepted. | ||||
|                 if (write_data_ready = '1') and (r.pending_beats = 0) then | ||||
|                     v.state := STATE_SINGLE_IDLE; | ||||
|                 end if; | ||||
| 
 | ||||
|             when STATE_BURST_PREPARE => | ||||
| 
 | ||||
|                 -- Wait until pending transfers are completed. | ||||
|                 if r.pending_transfers = 0 then | ||||
|                     v.state := STATE_BURST_IDLE; | ||||
|                 end if; | ||||
| 
 | ||||
|                 -- Reset idle counter. | ||||
|                 v.idle_timer := 0; | ||||
| 
 | ||||
|             when STATE_BURST_IDLE => | ||||
| 
 | ||||
|                 v.cmd_full_burst := '1'; | ||||
| 
 | ||||
|                 -- Clear busy flag. | ||||
|                 if r.pending_transfers = 0 then | ||||
|                     v.channel_busy := '0'; | ||||
|                 end if; | ||||
| 
 | ||||
|                 -- Switch to single transfer mode if FIFO has insufficient data. | ||||
|                 if (s_fifo_length < transfer_size) | ||||
|                         and (r.pending_transfers = 0) then | ||||
|                     v.state := STATE_SINGLE_IDLE; | ||||
|                 end if; | ||||
| 
 | ||||
|                 -- Start a burst transfer if possible. | ||||
|                 if (channel_en = '1') | ||||
|                         and (s_fifo_length >= transfer_size) | ||||
|                         and (r.cmd_addr(31 downto 7) /= addr_limit) | ||||
|                         and (r.pending_transfers /= 15) then | ||||
|                     v.cmd_valid := '1'; | ||||
|                     v.channel_busy := '1'; | ||||
|                     v.pending_beats := to_unsigned(transfer_size - 1, v.pending_beats'length); | ||||
|                     v.state := STATE_BURST_START; | ||||
|                 end if; | ||||
| 
 | ||||
|             when STATE_BURST_START => | ||||
| 
 | ||||
|                 -- Wait until start of transfer. | ||||
|                 if write_cmd_ready = '1' then | ||||
|                     v.cmd_valid := '0'; | ||||
| 
 | ||||
|                     -- Update pending transfers. | ||||
|                     v.pending_transfers := r.pending_transfers + 1; | ||||
| 
 | ||||
|                     -- Update command address. | ||||
|                     v.cmd_addr := std_logic_vector(unsigned(r.cmd_addr) + 1); | ||||
|                     if v.cmd_addr = addr_end then | ||||
|                     v.cmd_addr := std_logic_vector(unsigned(r.cmd_addr) + transfer_size); | ||||
|                     if v.cmd_addr(31 downto 7) = addr_end then | ||||
|                         -- Wrap at end of buffer. | ||||
|                         v.cmd_addr := addr_start; | ||||
|                         v.cmd_addr := addr_start & "0000"; | ||||
|                     end if; | ||||
| 
 | ||||
|                     v.state := STATE_FLOW; | ||||
|                     v.state := STATE_BURST_DATA; | ||||
|                 end if; | ||||
| 
 | ||||
|             when STATE_FLOW => | ||||
|             when STATE_BURST_DATA => | ||||
| 
 | ||||
|                 -- Wait until last beat. | ||||
|                 if (write_data_ready = '1') and (r.pending_beats = 1) then | ||||
|                     v.state := STATE_IDLE; | ||||
|                 if (write_data_ready = '1') and (r.pending_beats = 0) then | ||||
|                     v.state := STATE_BURST_IDLE; | ||||
|                 end if; | ||||
| 
 | ||||
|         end case; | ||||
|  | @ -200,17 +334,29 @@ begin | |||
|         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; | ||||
|         if (write_finished = '1') and (r.pending_transfers /= 0) then | ||||
|             v.pending_transfers := v.pending_transfers - 1; | ||||
| 
 | ||||
|             if r.cmd_full_burst = '1' then | ||||
|                 -- Completion of a full burst. | ||||
|                 v.addr_pointer := std_logic_vector(unsigned(r.addr_pointer) + transfer_size); | ||||
| 
 | ||||
|                 -- Raise interrupt when pointer passes threshold. | ||||
|                 if (intr_en = '1') | ||||
|                     and (r.addr_pointer(31 downto 3 + transfer_size_bits) | ||||
|                          = addr_interrupt(31 downto 3 + transfer_size_bits)) then | ||||
|                     v.intr_out := '1'; | ||||
|                 end if; | ||||
|             else | ||||
|                 -- Completion of single transfer. | ||||
|                 v.addr_pointer := std_logic_vector(unsigned(r.addr_pointer) + 1); | ||||
|             end if; | ||||
| 
 | ||||
|             -- Wrap at end of buffer. | ||||
|             if v.addr_pointer(31 downto 7) = addr_end then | ||||
|                 v.addr_pointer := addr_start & "0000"; | ||||
|             end if; | ||||
| 
 | ||||
|         -- Clear interrupt. | ||||
|         if intr_clear = '1' then | ||||
|             v.intr_out := '0'; | ||||
|         end if; | ||||
| 
 | ||||
|         -- Raise interrupt when pointer equals threshold. | ||||
|  | @ -218,13 +364,18 @@ begin | |||
|             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; | ||||
|         -- Clear interrupt. | ||||
|         if intr_clear = '1' then | ||||
|             v.intr_out := '0'; | ||||
|             v.state := STATE_IDLE; | ||||
|         end if; | ||||
| 
 | ||||
|         -- Initialize pointers. | ||||
|         if channel_init = '1' then | ||||
|             v.cmd_addr := addr_start & "0000"; | ||||
|             v.addr_pointer := addr_start & "0000"; | ||||
|             v.state := STATE_SINGLE_IDLE; | ||||
|             v.pending_transfers := (others => '0'); | ||||
|             v.idle_timer := 0; | ||||
|         end if; | ||||
| 
 | ||||
|         -- Synchronous reset. | ||||
|  |  | |||
|  | @ -15,10 +15,27 @@ package puzzlefw_pkg is | |||
|     subtype dma_address_type is std_logic_vector(31 downto 3); | ||||
|     type dma_address_array is array(natural range <>) of dma_address_type; | ||||
| 
 | ||||
|     -- Burst length for DMA on AXI bus as number of beats minus 1. | ||||
|     subtype dma_burst_length_type is std_logic_vector(3 downto 0); | ||||
|     type dma_burst_length_array is array (natural range <>) of dma_burst_length_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; | ||||
| 
 | ||||
|     -- 14-bit ADC data. | ||||
|     constant adc_data_bits: integer := 14; | ||||
|     subtype adc_data_type is std_logic_vector(adc_data_bits - 1 downto 0); | ||||
|     type adc_data_array is array(natural range <>) of adc_data_type; | ||||
| 
 | ||||
|     -- 24-bit averaged data. | ||||
|     constant sample_data_bits: integer := 24; | ||||
|     subtype sample_data_type is std_logic_vector(sample_data_bits - 1 downto 0); | ||||
|     type sample_data_array is array(natural range <>) of sample_data_type; | ||||
| 
 | ||||
|     -- 48-bit timestamp. | ||||
|     constant timestamp_bits: integer := 48; | ||||
| 
 | ||||
|     -- Register addresses. | ||||
|     constant reg_addr_mask:         std_logic_vector(31 downto 0) := x"0010fffc"; | ||||
|     constant reg_info:              natural := 16#000000#; | ||||
|  | @ -35,6 +52,7 @@ package puzzlefw_pkg is | |||
|     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_test_divider:      natural := 16#000408#; | ||||
|     constant reg_dma_buf_addr:      natural := 16#100000#; | ||||
|     constant reg_dma_buf_size:      natural := 16#100004#; | ||||
| 
 | ||||
|  | @ -48,6 +66,11 @@ package puzzlefw_pkg is | |||
|         & std_logic_vector(to_unsigned(fw_version_major, 8)) | ||||
|         & 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"; | ||||
| 
 | ||||
|     -- ADC input port type. | ||||
|     type adc_data_input_type is array(0 to 1) of std_logic_vector(15 downto 0); | ||||
| 
 | ||||
|  | @ -55,11 +78,12 @@ package puzzlefw_pkg is | |||
|     type registers_control is record | ||||
|         irq_enable:         std_logic; | ||||
|         test_led:           std_logic_vector(7 downto 0); | ||||
|         test_divider:       std_logic_vector(15 downto 0); | ||||
|         dma_en:             std_logic; | ||||
|         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_addr_intr:      std_logic_vector(31 downto 3); | ||||
|         acq_channel_en:     std_logic; | ||||
|         acq_intr_en:        std_logic; | ||||
|         dma_buf_addr:       std_logic_vector(31 downto 12); | ||||
|  | @ -74,7 +98,8 @@ package puzzlefw_pkg is | |||
|         dma_err_write:      std_logic; | ||||
|         dma_err_address:    std_logic; | ||||
|         dma_err_any:        std_logic; | ||||
|         acq_addr_ptr:       std_logic_vector(31 downto 7); | ||||
|         acq_addr_ptr:       std_logic_vector(31 downto 3); | ||||
|         acq_channel_busy:   std_logic; | ||||
|     end record; | ||||
| 
 | ||||
|     -- Trigger registers: write-only access by processor, single-cycle pulse signals to FPGA. | ||||
|  | @ -87,6 +112,7 @@ package puzzlefw_pkg is | |||
|     constant registers_control_init: registers_control := ( | ||||
|         irq_enable      => '0', | ||||
|         test_led        => (others => '0'), | ||||
|         test_divider    => (others => '0'), | ||||
|         dma_en          => '0', | ||||
|         acq_addr_start  => (others => '0'), | ||||
|         acq_addr_end    => (others => '0'), | ||||
|  |  | |||
|  | @ -137,6 +137,7 @@ architecture arch of puzzlefw_top is | |||
|     signal s_reg_trigger:   registers_trigger; | ||||
| 
 | ||||
|     signal s_dma_write_cmd_addr:  dma_address_array(0 downto 0); | ||||
|     signal s_dma_write_cmd_length: dma_burst_length_array(0 to 0); | ||||
|     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); | ||||
|  | @ -150,6 +151,7 @@ architecture arch of puzzlefw_top is | |||
|     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 r_test_cnt:      unsigned(15 downto 0); | ||||
|     signal s_test_dout:     std_logic_vector(63 downto 0); | ||||
|     signal s_test_ren:      std_logic; | ||||
| 
 | ||||
|  | @ -281,7 +283,6 @@ begin | |||
|     -- AXI master. | ||||
|     inst_axi_master: entity work.dma_axi_master | ||||
|         generic map ( | ||||
|             transfer_size       => 16, | ||||
|             num_read_channels   => 0, | ||||
|             num_write_channels  => 1 ) | ||||
|         port map ( | ||||
|  | @ -297,11 +298,13 @@ begin | |||
|             err_any             => s_reg_status.dma_err_any, | ||||
|             clear_errors        => s_reg_trigger.dma_clear, | ||||
|             read_cmd_addr       => (others => (others => '0')), | ||||
|             read_cmd_length     => (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_length    => s_dma_write_cmd_length, | ||||
|             write_cmd_valid     => s_dma_write_cmd_valid, | ||||
|             write_cmd_ready     => s_dma_write_cmd_ready, | ||||
|             write_data          => s_dma_write_data, | ||||
|  | @ -350,11 +353,14 @@ begin | |||
|     -- DMA Write Channel | ||||
|     inst_write_channel: entity work.dma_write_channel | ||||
|         generic map ( | ||||
|             queue_size_bits     => 10 ) | ||||
|             transfer_size_bits  => 4, | ||||
|             queue_size_bits     => 10, | ||||
|             idle_timeout        => 256 ) | ||||
|         port map ( | ||||
|             clk                 => clk_adc, | ||||
|             reset               => s_reset, | ||||
|             channel_en          => s_reg_control.acq_channel_en, | ||||
|             channel_busy        => s_reg_status.acq_channel_busy, | ||||
|             channel_init        => s_reg_trigger.acq_channel_init, | ||||
|             addr_start          => s_reg_control.acq_addr_start, | ||||
|             addr_end            => s_reg_control.acq_addr_end, | ||||
|  | @ -368,6 +374,7 @@ begin | |||
|             in_ready            => s_dma_in_ready, | ||||
|             in_data             => s_dma_in_data, | ||||
|             write_cmd_addr      => s_dma_write_cmd_addr(0), | ||||
|             write_cmd_length    => s_dma_write_cmd_length(0), | ||||
|             write_cmd_valid     => s_dma_write_cmd_valid(0), | ||||
|             write_cmd_ready     => s_dma_write_cmd_ready(0), | ||||
|             write_data          => s_dma_write_data(0), | ||||
|  | @ -384,10 +391,24 @@ begin | |||
|     process (clk_adc) is | ||||
|     begin | ||||
|         if rising_edge(clk_adc) then | ||||
|             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); | ||||
|             if s_reg_control.test_divider = x"ffff" then | ||||
|                 s_dma_in_valid <= '1'; | ||||
|                 if s_dma_in_ready = '1' then | ||||
|                     s_dma_in_data(47 downto 0) <= std_logic_vector(unsigned(s_dma_in_data(47 downto 0)) + 1); | ||||
|                     s_dma_in_data(63 downto 48) <= (others => '0'); | ||||
|                 end if; | ||||
|             else | ||||
|                 if s_dma_in_ready = '1' then | ||||
|                     s_dma_in_valid <= '0'; | ||||
|                 end if; | ||||
|                 if r_test_cnt >= unsigned(s_reg_control.test_divider) then | ||||
|                     r_test_cnt <= (others => '0'); | ||||
|                     s_dma_in_valid <= '1'; | ||||
|                     s_dma_in_data(47 downto 0) <= std_logic_vector(unsigned(s_dma_in_data(47 downto 0)) + 1); | ||||
|                     s_dma_in_data(63 downto 48) <= (others => '0'); | ||||
|                 else | ||||
|                     r_test_cnt <= r_test_cnt + 1; | ||||
|                 end if; | ||||
|             end if; | ||||
|         end if; | ||||
|     end process; | ||||
|  |  | |||
|  | @ -101,11 +101,14 @@ begin | |||
|                 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_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_channel_ctrl => | ||||
|                     v.prdata(0) := r.reg_control.acq_channel_en; | ||||
|                     v.prdata(8) := reg_status.acq_channel_busy; | ||||
|                 when reg_acq_intr_ctrl =>   v.prdata(0) := r.reg_control.acq_intr_en; | ||||
|                 when reg_test_led =>        v.prdata(7 downto 0) := r.reg_control.test_led; | ||||
|                 when reg_test_divider =>    v.prdata(15 downto 0) := r.reg_control.test_divider; | ||||
|                 when reg_dma_buf_addr =>    v.prdata(31 downto 12) := r.reg_control.dma_buf_addr; | ||||
|                 when reg_dma_buf_size =>    v.prdata(31 downto 12) := r.reg_control.dma_buf_size; | ||||
|                 when others =>              null; | ||||
|  | @ -122,7 +125,7 @@ begin | |||
|                 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_addr_intr =>   v.reg_control.acq_addr_intr := apb_pwdata(31 downto 3); | ||||
|                 when reg_acq_channel_ctrl => | ||||
|                     v.reg_control.acq_channel_en := apb_pwdata(0); | ||||
|                     v.reg_trigger.acq_channel_init := apb_pwdata(1); | ||||
|  | @ -130,6 +133,7 @@ begin | |||
|                     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_test_divider =>    v.reg_control.test_divider := apb_pwdata(15 downto 0); | ||||
|                 when reg_dma_buf_addr =>    v.reg_control.dma_buf_addr := apb_pwdata(31 downto 12); | ||||
|                 when reg_dma_buf_size =>    v.reg_control.dma_buf_size := apb_pwdata(31 downto 12); | ||||
|                 when others =>              null; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue