Zynq. Datenübertragung zwischen Prozessormodul und programmierbarer Logik

Lassen Sie uns, wie im vorherigen Artikel versprochen ( Was ist Zynq? Ein kurzer Überblick ), über die Datenübertragung zwischen dem Prozessormodul und der programmierbaren Logik sprechen. Im vorherigen Artikel wurden vier Möglichkeiten zum Übertragen von Daten erwähnt. In diesem Artikel werden zwei Methoden erläutert, die eine größere Anwendung gefunden haben. Details unter dem Schnitt. Vorsicht, viele Bilder!



Inhalt



1 Allgemeine Informationen

2 Datenübertragung im PIO-Modus

2.1 Hardware

2.2 Software

2.3 Ergebnisse

3 Datenübertragung in DMA

3.1 Hardware

3.2 Software

3.3 Ergebnisse

4 Schlussfolgerung

5 Verwendete Quellen



1. Allgemeines



Im Allgemeinen ist die Datenübertragung zwischen dem Prozessormodul und der programmierbaren Logik in zwei Modi möglich:



  • PIO über den GP-Port.
  • DMA , HP-Port wird verwendet.


2 Datenübertragung im PIO-Modus



Im PIO-Modus arbeitet das Prozessormodul mit programmierbarer Logik als Registersatz. Um eine bestimmte Datenmenge schreiben oder lesen zu können, muss das Prozessormodul ständig beteiligt sein. Im PIO-Modus werden alle Transaktionen vom Prozessormodul initiiert. Zum Anschließen der programmierbaren Logik wird der GP-Port verwendet, wobei Master ein Prozessormodul und Slave eine programmierbare Logik ist. Projektstruktur bei Verwendung von PIO









2.1 Hardware



  1. Wir erstellen ein Projekt für Zybo in Vivado, Chip-Typ xc7z010clg400-1.
  2. Blockdesign erstellen. Im Flow Navigator => Blockdesign erstellen => den Namen "ProcessingSystem" => OK.
  3. Fügen Sie mit der Taste "+" auf dem Feld oder den Tastenkombinationen Strg + I den Prozessorkern hinzu.



  4. Verbinden Sie die erforderlichen Pins, indem Sie auf die Schaltfläche Run Block Automation klicken => OK.
  5. . Zynq7 Processing System => Import XPS Setting => => OK => OK.
  6. , . Tools => Create and Package New IP => Next => Create a new AXI4 peripheral => Next => , «PIO_registers» => Next => (4 ), , Lite => Next => Add IP to the repository => Finish.



  7. , IP . , Flow Navigator => IP Catalog.



  8. . Ctrl + I => PIO_registers.



  9. , . PIO_registers => Edit in IP Packager => OK. Vivado .
  10. PIO_registers_v1_0.vhd :



    iSwitches	: in	std_logic_vector( 3 downto 0);
    oLeds		: out	std_logic_vector( 3 downto 0);
    ...
    iSwitches	=> iSwitches,
    oLeds		=> oLeds,
    
          
          





  11. PIO_registers_v1_0_S_AXI.vhd :



    iSwitches	: in	std_logic_vector( 3 downto 0);
    oLeds		: out	std_logic_vector( 3 downto 0);
    
          
          





  12. :



    signal	SwitchesReg	: std_logic_vector(31 downto 0);
    ...
    process (SwitchesReg, slv_reg1, slv_reg2, slv_reg3, axi_araddr, S_AXI_ARESETN, slv_reg_rden)
    variable loc_addr :std_logic_vector(OPT_MEM_ADDR_BITS downto 0);
    begin
        -- Address decoding for reading registers
        loc_addr := axi_araddr(ADDR_LSB + OPT_MEM_ADDR_BITS downto ADDR_LSB);
        case loc_addr is
          when b"00" =>
            reg_data_out <= SwitchesReg;
          when b"01" =>
            reg_data_out <= slv_reg1;
          when b"10" =>
            reg_data_out <= slv_reg2;
          when b"11" =>
            reg_data_out <= slv_reg3;
          when others =>
            reg_data_out  <= (others => '0');
        end case;
    end process;
    
    process (S_AXI_ACLK) begin
    	if (rising_edge(S_AXI_ACLK)) then
    		if (S_AXI_ARESETN = '0') then
    			SwitchesReg <= (others => '0');
    		else
    			SwitchesReg( 3 downto 0) <= iSwitches;
    		end if;
    	end if;
    end process;
    	
    process (S_AXI_ACLK) begin
    	if (rising_edge(S_AXI_ACLK)) then
    		if (S_AXI_ARESETN = '0') then
    			oLeds <= (others => '0');
    		else
    			oLeds <= slv_reg1( 3 downto 0);
    		end if;
    	end if;
    end process;
    
          
          





  13. vhd , Package IP – PIO_registers. . Compatibility Life Cycle Production. File Groups => Merge changes from File Group Wizard. Customization Parameters => Merge changes from Customization Parameters Wizard. Review and Package => Re-Package IP => Yes. Vivado .
  14. Block Design Report IP Status, Upgrade Selected => OK => Skip => OK.



  15. . Run Connection Automation => OK.



  16. block design’a. , => Make External.



  17. iSwitches_0 => iSwitches. oLeds_0 => oLeds.



  18. => Tools => Validate Design => Ok.
  19. File => Save Block Design.
  20. block design , Flow Navigator => Project Manager.
  21. , block design’a. ProcessingSystem.bd, => View Instantiation Template.



  22. vhd top- block design. File => Add Sources => Add or create design sources => Next => Create File => => OK => Finish => OK => Yes.
  23. :



    entity PioTransfer is
    port	(	DDR_addr		: inout std_logic_vector(14 downto 0 );
    		DDR_ba			: inout std_logic_vector( 2 downto 0 );
    		DDR_cas_n		: inout std_logic;
    		DDR_ck_n		: inout std_logic;
    		DDR_ck_p		: inout std_logic;
    		DDR_cke			: inout std_logic;
    		DDR_cs_n		: inout std_logic;
    		DDR_dm			: inout std_logic_vector( 3 downto 0 );
    		DDR_dq			: inout std_logic_vector(31 downto 0 );
    		DDR_dqs_n		: inout std_logic_vector( 3 downto 0 );
    		DDR_dqs_p		: inout std_logic_vector( 3 downto 0 );
    		DDR_odt			: inout std_logic;
    		DDR_ras_n		: inout std_logic;
    		DDR_reset_n		: inout std_logic;
    		DDR_we_n		: inout std_logic;
    		FIXED_IO_ddr_vrn	: inout std_logic;
    		FIXED_IO_ddr_vrp	: inout std_logic;
    		FIXED_IO_mio		: inout std_logic_vector( 53 downto 0 );
    		FIXED_IO_ps_clk		: inout std_logic;
    		FIXED_IO_ps_porb	: inout std_logic;
    		FIXED_IO_ps_srstb	: inout std_logic;
    		-- Control
    		iSwitches		: in	std_logic_vector( 3 downto 0 );
    		oLeds			: out	std_logic_vector( 3 downto 0 ) );
    end PioTransfer;
    
    architecture Behavioral of PioTransfer is
    
    begin
    
    PS : entity WORK.ProcessingSystem
    port map	(	DDR_addr		=> DDR_addr,
    			DDR_ba			=> DDR_ba,
    			DDR_cas_n		=> DDR_cas_n,
    			DDR_ck_n		=> DDR_ck_n,
    			DDR_ck_p		=> DDR_ck_p,
    			DDR_cke			=> DDR_cke,
    			DDR_cs_n		=> DDR_cs_n,
    			DDR_dm			=> DDR_dm,
    			DDR_dq			=> DDR_dq,
    			DDR_dqs_n		=> DDR_dqs_n,
    			DDR_dqs_p		=> DDR_dqs_p,
    			DDR_odt			=> DDR_odt,
    			DDR_ras_n		=> DDR_ras_n,
    			DDR_reset_n		=> DDR_reset_n,
    			DDR_we_n		=> DDR_we_n,
    			FIXED_IO_ddr_vrn	=> FIXED_IO_ddr_vrn,
    			FIXED_IO_ddr_vrp	=> FIXED_IO_ddr_vrp,
    			FIXED_IO_mio		=> FIXED_IO_mio,
    			FIXED_IO_ps_clk		=> FIXED_IO_ps_clk,
    			FIXED_IO_ps_porb	=> FIXED_IO_ps_porb,
    			FIXED_IO_ps_srstb	=> FIXED_IO_ps_srstb,
    			-- Control
    			iSwitches		=> iSwitches,
    			oLeds			=> oLeds );
    end Behavioral;
          
          





  24. . File => Add sources => Add or create constrains => Next => Create File => => OK => Finish.



  25. :



    #Switches
    set_property PACKAGE_PIN G15 [get_ports {iSwitches[0]}]
    set_property PACKAGE_PIN P15 [get_ports {iSwitches[1]}]
    set_property PACKAGE_PIN W13 [get_ports {iSwitches[2]}]
    set_property PACKAGE_PIN T16 [get_ports {iSwitches[3]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {iSwitches[*]}]
    
    #LEDs
    #IO_L23P_T3_35
    set_property PACKAGE_PIN M14 [get_ports {oLeds[0]}]
    set_property PACKAGE_PIN M15 [get_ports {oLeds[1]}]
    set_property PACKAGE_PIN G14 [get_ports {oLeds[2]}]
    set_property PACKAGE_PIN D18 [get_ports {oLeds[3]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {oLeds[*]}] 
          
          





  26. . Flow Navigator => Generate Bitstream => OK. , , .
  27. . File => Export => Export Hardware => => OK. .xsa





2.2



Jetzt müssen Sie eine Anwendung schreiben, die auf dem Prozessormodul ausgeführt wird und Daten aus der programmierbaren Logik liest und Daten in die programmierbare Logik schreibt. Sie müssen die Vitis-Entwicklungsumgebung starten und eine Anwendung mit der Hello World-Vorlage erstellen. Ein Beispiel hierfür finden Sie im vorherigen Artikel [1].



Die Adresse des erstellten Kernels für den Zugriff vom Prozessormodul kann in Vivado angezeigt werden. In Flow Navigator => Blockdesign öffnen => Registerkarte Adresseditor. In diesem Fall lautet die Adresse 0x43C0_0000. An dieser Adresse befindet sich ein Register, in dem das Attribut gespeichert ist und in welchem ​​Zustand sich die Schalter befinden. Dementsprechend befindet sich an der Adresse 0x43C0_0004 ein Register, das mit den LEDs verbunden ist.



Öffnen Sie in Vitis die Datei helloworld.c und geben Sie Folgendes ein:



int main()
{
	init_platform();

	u32 Status = 0x00;
	u32 Command = 0x00;

	xil_printf("Hello World\n\r");

	while (1)
	{

		Status = Xil_In32(0x43C00000);

		xil_printf("Status %x\n\r", Status);

		if (Status == 0x01 || Status == 0x02 || Status == 0x04 || Status == 0x08)
		{
			Command = 0x01;
		}
		else if (Status == 0x03 || Status == 0x5 || Status == 0x06 || Status == 0x9 || Status == 0xA || Status == 0x0C)
		{
			Command = 0x03;
		}
		else if (Status == 0x7 || Status ==  0x0B || Status == 0x0D || Status == 0x0E)
		{
			Command = 0x7;
		}
		else if (Status == 0x0F)
		{
			Command = 0x0F;
		}
		else
		{
			Command = 0x00;
		}

		xil_printf("Command %x\n\r", Command);

		Xil_Out32(0x43C00004, Command);

		usleep(1000000);
	}

	cleanup_platform();
	return 0;
} 
      
      





Wobei die Funktion Xil_In32 verwendet wird, um 4 Datenbytes aus der programmierbaren Logik zu lesen, und Xil_Out32, um 4 Datenbytes in die programmierbare Logik zu schreiben.



2.3 Ergebnisse



Erstellen Sie die Anwendung, erstellen Sie eine Firmware-Datei und laden Sie sie auf die Karte hoch. Beschrieben im vorherigen Artikel [1].



Starten Sie, schauen Sie in den Com-Port-Monitor:



Xilinx First Stage Boot Loader 
Release 2019.2	Dec  9 2020-15:16:52
Silicon Version 3.1
Boot mode is QSPI
SUCCESSFUL_HANDOFF
FSBL Status = 0x1
Hello World
Status 0
Command 0
Status 8
Command 1
Status C
Command 3
Status D
Command 7
Status F
Command F

      
      





Alles funktioniert richtig.



Um im PIO-Modus auf programmierbare Logik zuzugreifen, ist es daher erforderlich, eine der Kommunikationsschnittstellen mit dem Prozessormodul in der programmierbaren Logik zu implementieren, wobei das Prozessormodul der Initiator ist. Diese Schnittstelle wird nur durch den GP-Port dargestellt.



Mal sehen, wie schnell Anforderungen an programmierbare Logik über den GP-Port verarbeitet werden. Fügen Sie dazu in einer Anwendung, die auf einem Prozessormodul ausgeführt wird, mehrere Einträge hintereinander zum Register in der programmierbaren Logik hinzu und messen Sie die Zeit zwischen Transaktionen in der programmierbaren Logik anhand der in den Debugger gezogenen Bussignale.



Wenn der Axi-Lite-Bus mit 100 MHz läuft, beträgt die Pause zwischen den Anforderungen durchschnittlich 23 Taktzyklen. Ändern wir die Busfrequenz auf 200 MHz. Die Pause zwischen den Anforderungen beträgt durchschnittlich 33 Zyklen.



Insgesamt werden 4 Datenbytes für 100 Taktzyklen mit 100 MHz übertragen. Die Geschwindigkeit beträgt: 32 / (23 * 10 ns) = 139 130 434 Bit / s ≈ 135 869 Kbps ≈ 132 Mbit / s ≈ 16 MB / s.

Insgesamt werden 4 Datenbytes mit 200 MHz für 33 Takte übertragen. Die Geschwindigkeit beträgt 32 / (33 * 5 ns) = 193 939 393 Bit / s ≈ 189 393 Kbit / s ≈ 184 Mbit / s ≈ 23 MB / s.

So können Sie eine Geschwindigkeit von 23 MB / s erreichen, jedoch unter ständiger Beteiligung des Prozessormoduls.



Projekt: github.com/Finnetrib/PioTransfer



3 Datenübertragung im DMA-Modus



Die Datenübertragung im DMA-Modus impliziert, dass das Prozessormodul die Datenaustauschparameter konfiguriert und nicht direkt am Austausch teilnimmt. Damit werden zwei Ziele erreicht: Reduzierung der Belastung des Prozessormoduls und Erhöhung der Datenverarbeitungsgeschwindigkeit. Der Preis dafür ist die Komplikation der Hardware.



In Zynq können mehrere IP-Cores verwendet werden, die DMA-Funktionen implementieren. Dieser Artikel befasst sich mit dem Kern von AXI DMA [2].



AXI DMA hat zwei Kanäle MM2S und S2MM. Der Kanal MM2S (Memory-Mapping to Stream) wird verwendet, um Daten vom Prozessormodul zur programmierbaren Logik zu übertragen. Der S2MM-Kanal (Stream to Memory Mapped) wird verwendet, um Daten von der programmierbaren Logik zum Prozessormodul zu übertragen. Die Kanäle arbeiten unabhängig voneinander.



AXI DMA hat zwei Anwendungsfälle:



  • Direktregistrierungsmodus
  • Streu- / Sammelmodus


Der Direktregistermodus verwendet einen Registersatz, mit dem ein Puffer von der programmierbaren Logik zum Prozessormodul und umgekehrt übertragen werden kann. Um beispielsweise einen Datenpuffer von der programmierbaren Logik zu einem Prozessormodul zu übertragen, müssen Sie die Felder Adresse und Puffergröße ausfüllen und DMA starten. Infolgedessen füllt DMA einen Puffer in der Prozessoreinheit und stoppt.



Der Scatter / Gather-Modus verwendet eine Liste von Deskriptoren. DMA verarbeitet den im Deskriptor beschriebenen Puffer und fährt mit der Verarbeitung des im nächsten Deskriptor beschriebenen Puffers fort.



3.1 Hardware





Projektstruktur bei Verwendung von DMA



Betrachten wir die Option, wenn die Liste der Deskriptoren in programmierbarer Logik gespeichert ist. Der DMA-Block verfügt über einen Steuerport, der mit dem GP-Port der Prozessoreinheit verbunden ist. Es gibt auch einen HP-Anschluss für den Zugriff auf den Prozessor-RAM. Die Deskriptorliste wird im Deskriptorspeicher gespeichert. Auf den Deskriptorspeicher kann sowohl vom DMA als auch von der Prozessoreinheit aus zugegriffen werden. Das Prozessormodul füllt die Deskriptoren aus, der DMA liest die Deskriptoren aus.



  1. Blockdesign erstellen. Im Flow Navigator => Blockdesign erstellen => den Namen "ProcessingSystem" => OK.
  2. Fügen Sie mit der Taste "+" auf dem Feld oder den Tastenkombinationen Strg + I den Prozessorkern hinzu.
  3. Verbinden Sie die erforderlichen Pins, indem Sie auf die Schaltfläche Run Block Automation klicken => OK.
  4. . Zynq7 Processing System => Import XPS Setting => => OK => OK
  5. AXI Direct Memory Access, AXI BRAM Controller, Block Memory Generator.



  6. AXI Direct Memory Access, . «Enable Scatter Gather Engine» , . «Enable Control / Status Stream» AXI Ethernet, . «With of Buffer Length Register» , . 20, 2^20 = 1 048 576 . «Address With» . 32 . «Enable Read Channel» «Enable Write Channel» . «Enable Single AXI4 Data interface» , . «OK» .



  7. AXI BRAM Controller. «Number of BRAM Interfaces» 1. «OK» .



  8. AXI BRAM Controller.
  9. Block Memory Generator. «Memory Type» «True Dual Port RAM». «OK» .



  10. . «Run Connection Automation» => axi_bram_ctrl_0 BRAM_PORTA => axi_bram_ctrl_1 BRAM_PORTA => OK.



  11. . «Run Connection Automation» => axi_bram_ctrl_0 S_AXI => Master Interface /processing_system7_0/M_AXI_GP0 => OK. , .



  12. DMA. «Run Connection Automation» => axi_bram_ctrl_1 S_AXI => Master Interface /axi_dma_0/M_AXI_SG => OK. , DMA .



  13. DMA . «Run Connection Automation» => axi_dma_0 S_AXI_LITE => OK.



  14. – HP . Zynq7 Processing System => PS-PL Configuration => HP Slave AXI Interface => S AXI HP0 Interface.





    Interrupts => Fabric Interrupts => PL-PS Interrupts Ports => Fabric Interrupts => IRQ_F2P => OK.



  15. DMA . «Run Connection Automation» => processing_system7_0 S_AXI_HP0 => Master Interface /axi_dma_0/M_AXI => OK.



  16. DMA . Concat + Ctrl + I.
  17. mm2s_introut DMA, . mm2s_introut In0 Concat. , , .



  18. s2mm_introut, In1 Concat.
  19. dout Concat IRQ_F2P Zynq7 Processing System.
  20. DMA . DMA . Block Design, . Create Port Ctrl + K. , => OK.



  21. FCLK_CLK0 Zynq7 Processing System.
  22. . peripheral_reset Processor System Reset => => Make External.
  23. , , .



  24. DMA. S_AXIS_S2MM AXI Direct Memory Access => => Make External.
  25. , , .



  26. DMA. M_AXIS_MM2S AXI Direct Memory Access => => Make External.
  27. , , .



  28. S_AXIS_S2MM M_AXIS_MM2S AXI Direct Memory Access. «Run Connection Automation» => m_axi_mm2s_aclk m_axi_s2mm_aclk => OK
  29. , DMA . . Address Editor => processing_system7_0 / Data / axi_bram_ctrl_0 => Offset Address 0x4000_0000 => Range 32K. axi_dma_0 / Data_SG / axi_bram_ctrl_1 => Offset Address 0x4000_0000 => Range 32K.



  30. Tools => Validate Design => OK. :



  31. File => Save Block Design.
  32. block design , Flow Navigator => Project Manager.
  33. , block design’a. ProcessingSystem.bd, => View Instantiation Template.
  34. vhd top- block design. File => Add Sources => Add or create design sources => Next => Create File => => OK => Finish => OK => Yes.





  35. :

    entity DmaTransfer is
    port	(	DDR_addr		: inout std_logic_vector(14 downto 0);
    		DDR_ba			: inout std_logic_vector( 2 downto 0);
    		DDR_cas_n		: inout std_logic;
    		DDR_ck_n		: inout std_logic;
    		DDR_ck_p		: inout std_logic;
    		DDR_cke			: inout std_logic;
    		DDR_cs_n		: inout std_logic;
    		DDR_dm			: inout std_logic_vector( 3 downto 0);
    		DDR_dq			: inout std_logic_vector(31 downto 0);
    		DDR_dqs_n		: inout std_logic_vector( 3 downto 0);
    		DDR_dqs_p		: inout std_logic_vector( 3 downto 0);
    		DDR_odt			: inout std_logic;
    		DDR_ras_n		: inout std_logic;
    		DDR_reset_n		: inout std_logic;
    		DDR_we_n		: inout std_logic;
    		FIXED_IO_ddr_vrn	: inout std_logic;
    		FIXED_IO_ddr_vrp	: inout std_logic;
    		FIXED_IO_mio		: inout std_logic_vector(53 downto 0);
    		FIXED_IO_ps_clk		: inout std_logic;
    		FIXED_IO_ps_porb	: inout std_logic;
    		FIXED_IO_ps_srstb	: inout std_logic );
    end DmaTransfer;
    
    architecture Behavioral of DmaTransfer is
    
    	signal	RxData			: std_logic_vector(31 downto 0);
    	signal	RxKeep			: std_logic_vector( 3 downto 0);
    	signal	RxLast			: std_logic;
    	signal	RxValid			: std_logic;
    	signal	RxReady			: std_logic;
    	signal	TxData			: std_logic_vector(31 downto 0);
    	signal	TxKeep			: std_logic_vector( 3 downto 0);
    	signal	TxLast			: std_logic;
    	signal	TxValid			: std_logic;
    	signal	TxReady			: std_logic;
    	signal	clk			: std_logic;
    	signal	rst			: std_logic;
    	signal	FifoDataW		: std_logic_vector(36 downto 0);
    	signal	FifoWrite		: std_logic;
    	signal	FifoRead		: std_logic;
    	signal	FifoDataR		: std_logic_vector(36 downto 0);
    	signal	FifoEmpty		: std_logic;
    	signal	FifoFull		: std_logic;
    
    begin
    
    	PS : entity WORK.ProcessingSystem
    	port map	(	DDR_addr		=> DDR_addr,
    				DDR_ba			=> DDR_ba,
    				DDR_cas_n		=> DDR_cas_n,
    				DDR_ck_n		=> DDR_ck_n,
    				DDR_ck_p		=> DDR_ck_p,
    				DDR_cke			=> DDR_cke,
    				DDR_cs_n		=> DDR_cs_n,
    				DDR_dm			=> DDR_dm,
    				DDR_dq			=> DDR_dq,
    				DDR_dqs_n		=> DDR_dqs_n,
    				DDR_dqs_p		=> DDR_dqs_p,
    				DDR_odt			=> DDR_odt,
    				DDR_ras_n		=> DDR_ras_n,
    				DDR_reset_n		=> DDR_reset_n,
    				DDR_we_n		=> DDR_we_n,
    				FIXED_IO_ddr_vrn	=> FIXED_IO_ddr_vrn,
    				FIXED_IO_ddr_vrp	=> FIXED_IO_ddr_vrp,
    				FIXED_IO_mio		=> FIXED_IO_mio,
    				FIXED_IO_ps_clk		=> FIXED_IO_ps_clk,
    				FIXED_IO_ps_porb	=> FIXED_IO_ps_porb,
    				FIXED_IO_ps_srstb	=> FIXED_IO_ps_srstb,
    				-- Dma Channel
    				iDmaRx_tdata		=> RxData,
    				iDmaRx_tkeep		=> RxKeep,
    				iDmaRx_tlast		=> RxLast,
    				iDmaRx_tready		=> RxReady,
    				iDmaRx_tvalid		=> RxValid,
    				oDmaTx_tdata		=> TxData,
    				oDmaTx_tkeep		=> TxKeep,
    				oDmaTx_tlast		=> TxLast,
    				oDmaTx_tready		=> TxReady,
    				oDmaTx_tvalid		=> TxValid,
    				-- System
    				oZynqClk		=> clk,
    				oZynqRst(0)		=> rst );
    	
    	FifoDataW(31 downto  0) <= not TxData;
    	FifoDataW(35 downto 32) <= TxKeep;
    	FifoDataW(	    36) <= TxLast;
    	
    	FifoWrite <= TxValid and not FifoFull;
    	
    	TxReady <= not FifoFull;
    	
    	EchFifo : entity WORK.SyncFifoBram37x1024
    	port map	(	clk		=> clk,
    				srst		=> rst,
    				din		=> FifoDataW,
    				wr_en		=> FifoWrite,
    				rd_en		=> FifoRead,
    				dout		=> FifoDataR,
    				full		=> open,
    				empty		=> FifoEmpty,
    				prog_full	=> FifoFull );
    
    	RxData <= FifoDataR(31 downto  0);
    	RxKeep <= FifoDataR(35 downto 32);
    	RxLast <= FifoDataR(36);
    	
    	RxValid <= not FifoEmpty;
    	
    	FifoRead <= RxReady;
    
    end Behavioral; 
          
          



  36. . Flow Navigator => Generate Bitstream => OK. , , .
  37. . File => Export => Export Hardware => => OK. .xsa







3.2



Jetzt müssen Sie eine Anwendung schreiben, die auf dem Prozessormodul ausgeführt wird. Sie müssen die Vitis-Entwicklungsumgebung starten und eine Anwendung mithilfe der Hello World-Vorlage erstellen. Ein Beispiel hierfür finden Sie im vorherigen Artikel.



Das Format der Deskriptoren für Axi DMA ist im Kerneldokument [2] beschrieben. Der Deskriptor ist 52 Byte groß, die Adresse, an der sich der Deskriptor befindet, muss jedoch 64 Byte lang ausgerichtet sein.



Kurz zum Format des Deskriptors:



  • NXTDESC - Adresse des nächsten Deskriptors;
  • NXTDESC_MSB - hohe 32 Bits der nächsten Deskriptoradresse;
  • BUFFER_ADDRESS - Pufferadresse;
  • BUFFER_ADDRESS_MSB - hohe 32 Bits der Pufferadresse;
  • RESERVIERT - nicht verwendet;
  • RESERVIERT - nicht verwendet;
  • CONTROL - Legt die Puffergröße sowie die Vorzeichen für den Anfang und das Ende des Pakets fest.
  • STATUS - zeigt an, wie viele Bytes empfangen / gesendet, verarbeitet / nicht verarbeitet wurden;
  • APP0 - wird verwendet, um mit dem Control / Status Stream-Kanal zu arbeiten;
  • APP1 - wird verwendet, um mit dem Control / Status Stream-Kanal zu arbeiten;
  • APP2 - wird verwendet, um mit dem Control / Status Stream-Kanal zu arbeiten;
  • APP3 - wird verwendet, um mit dem Control / Status Stream-Kanal zu arbeiten;
  • APP4 - wird verwendet, um mit dem Kanal "Control / Status Stream" zu arbeiten.


Adressen in programmierbarer Logik für den Zugriff vom Prozessormodul können in Vivado angezeigt werden. In Flow Navigator => Blockdesign öffnen => Registerkarte Adresseditor. In diesem Fall lautet die DMA-Adresse 0x4040_0000. Die Adresse am Anfang des Speicherbereichs für Deskriptoren lautet 0x4000_0000.



  1. Öffnen Sie in Vitis die Datei helloworld.c und fügen Sie die folgenden Bibliotheken hinzu



    #include <xil_io.h>
    #include "sleep.h"
    #include "xil_cache.h"
    #include "xil_mem.h"
    
          
          



  2. , 64 . , 32 32 768 / 64 = 512 . 256 256 .



    #define DESC_COUNT 256
    ...
     /** Descriptors for receive */
    struct SGDesc RxDesc[DESC_COUNT];
    
    /** Descriptors for transmit */
    struct SGDesc TxDesc[DESC_COUNT];
    
          
          



  3. , , .



    /** Flush Cache */
    Xil_DCacheFlush();
    
    /** Disable Cache */
    Xil_DCacheDisable();
    
          
          





  4. , .



    for (u16 desc = 0; desc < DESC_COUNT; desc++)
    {
    	for (u32 i = 0; i < BUFFER_SIZE; i++)
    	{
    		TxBuffer[desc][i] = desc + i;
    	}
    }
    
          
          



  5. .



    for (u16 i = 0; i < DESC_COUNT; i++)
    {
    	TxDesc[i].NXTDESC = &TxDesc[i];
    	TxDesc[i].NXTDESC_MSB = 0x0;
    	TxDesc[i].BUFFER_ADDRESS = &TxBuffer[i][0];
    	TxDesc[i].BUFFER_ADDRESS_MSB = 0x0;
    	TxDesc[i].RESERVED0 = 0x0;
    	TxDesc[i].RESERVED1 = 0x0;
    	TxDesc[i].CONTROL = 0xC000000 + sizeof(TxBuffer[i]);
    	TxDesc[i].STATUS = 0x0;
    	TxDesc[i].APP0 = 0x0;
    	TxDesc[i].APP1 = 0x0;
    	TxDesc[i].APP2 = 0x0;
    	TxDesc[i].APP3 = 0x0;
    	TxDesc[i].APP4 = 0x0;
    }
    
          
          



  6. , .



    DescAddr = 0x40000000;
    for (u16 i = 0; i < DESC_COUNT; i++)
    {
    	Xil_MemCpy(DescAddr, &TxDesc[i], sizeof(TxDesc[i]));
    	DescAddr += 0x40;
    }
    
          
          



  7. .

    /** Write pointer to next pointer */
    DescAddr = 0x40000000;
    for (u16 i = 0; i < DESC_COUNT - 1; i++)
    {
    	Xil_Out32(DescAddr, DescAddr + 0x40);
    	DescAddr += 0x40;
    }
    
    /** Write pointer for last descriptor */
    Xil_Out32(DescAddr, DescAddr);
    
          
          



  8. .



    /** Fill descriptor to receive */
    for (u16 i = 0; i < DESC_COUNT; i++)
    {
    	RxDesc[i].NXTDESC = &RxDesc[i];
    	RxDesc[i].NXTDESC_MSB = 0x0;
    	RxDesc[i].BUFFER_ADDRESS = &RxBuffer[i][0];
    	RxDesc[i].BUFFER_ADDRESS_MSB = 0x0;
    	RxDesc[i].RESERVED0 = 0x0;
    	RxDesc[i].RESERVED1 = 0x0;
    	RxDesc[i].CONTROL = sizeof(RxBuffer[i]);
    	RxDesc[i].STATUS = 0x0;
    	RxDesc[i].APP0 = 0x0;
    	RxDesc[i].APP1 = 0x0;
    	RxDesc[i].APP2 = 0x0;
    	RxDesc[i].APP3 = 0x0;
    	RxDesc[i].APP4 = 0x0;
    }
    
    /** Copy receive descriptor for memory of descriptors */
    DescAddr = 0x40000000 + 0x4000;
    for (u16 i = 0; i < DESC_COUNT; i++)
    {
    	Xil_MemCpy(DescAddr, &RxDesc[i], sizeof(RxDesc[i]));
    	DescAddr += 0x40;
    }
    
    /** Write pointer to next pointer */
    DescAddr = 0x40000000 + 0x4000;
    for (u16 i = 0; i < DESC_COUNT - 1; i++)
    {
    	Xil_Out32(DescAddr, DescAddr + 0x40);
    	DescAddr += 0x40;
    }
    
    /** Write pointer for last descriptor */
    Xil_Out32(DescAddr, DescAddr); 
    
          
          



  9. DMA . DMA .



    /** Reset DMA and setup */
    /** MM2S */
    Xil_Out32(0x40400000, 0x0001dfe6);
    Xil_Out32(0x40400000, 0x0001dfe2);
    
    /** S2MM */
    Xil_Out32(0x40400030, 0x0001dfe6);
    Xil_Out32(0x40400030, 0x0001dfe2);
    
    /** PL => PS */
    Xil_Out32(0x4040003c, 0x00000000);
    Xil_Out32(0x40400038, 0x40004000);
    Xil_Out32(0x40400030, 0x0001dfe3);
    Xil_Out32(0x40400044, 0x00000000);
    Xil_Out32(0x40400040, 0x40007FC0);
    
    /** PS => PL */
    Xil_Out32(0x4040000C, 0x00000000);
    Xil_Out32(0x40400008, 0x40000000);
    Xil_Out32(0x40400000, 0x0001dfe3);
    Xil_Out32(0x40400014, 0x00000000);
    Xil_Out32(0x40400010, 0x40003FC0); 
    
          
          



  10. , . , , .



    /** Wait ready in last descriptor */
    while (1)
    {
    	status = Xil_In32(0x40003FDC);
    	if ((status & 0x80000000) == 0x80000000)
    	{
    		break;
    	}
    	else
    	{
    		countWait++;
    		usleep(100);
    	}
    }
    
    xil_printf("Time %x \n\r", countWait);
    
          
          





3.3



Erstellen Sie die Anwendung, erstellen Sie eine Firmware-Datei und laden Sie sie auf die Karte hoch. Beschrieben im vorherigen Artikel [1].



Starten Sie, schauen Sie in den Com-Port-Monitor:



Xilinx First Stage Boot Loader
Release 2019.2  Dec 16 2020-15:11:44
Silicon Version 3.1
Boot mode is QSPI
SUCCESSFUL_HANDOFF
FSBL Status = 0x1
Hello World
Time 10F

      
      





Für den Datenaustausch zwischen dem Prozessormodul und der programmierbaren Logik muss daher eine der Kommunikationsschnittstellen mit dem Prozessormodul in der programmierbaren Logik implementiert werden, wobei der Initiator die programmierbare Logik ist. Solche Schnittstellen werden durch die Ports GP, HP, ACP dargestellt. Im vorherigen Artikel [1] wurden sie alle berücksichtigt.



Berechnen wir die Datenübertragungsrate: (256 mal * 102400 Bytes) / (271 * 100 μs) ≈ 967 321 033 Bytes / s ≈ 944 649 KB / s ≈ 922 MB / s.

Bitrate 7.738.568.264 Bit / s.

Die theoretische Geschwindigkeit beträgt 32 Bit * 250 MHz = 8.000.000.000 Bit / s.



Es ist auch möglich, Deskriptoren nicht im speicherprogrammierbaren Logikspeicher, sondern im Direktzugriffsspeicher zu speichern, der mit dem Prozessormodul verbunden ist. In diesem Fall wird der M_AXI_SG-Port mit dem HP Zynq-Port verbunden.



Betrachten wir die erste Option, wenn verschiedene HP Ports für den DMA-Zugriff auf Daten und Deskriptoren im RAM des Prozessors verwendet werden. Lassen Sie uns die Firmware in der programmierbaren Logik so ändern, dass wir das folgende Schema erhalten: Zugriff auf Daten und Deskriptoren über verschiedene Ports Wir werden den Quellcode der Anwendung nicht bereitstellen. Der einzige Unterschied besteht darin, dass die Deskriptoren nicht in den Speicher der programmierbaren Logik kopiert werden müssen. Es ist jedoch erforderlich, die Bedingung zu berücksichtigen, dass die Adresse jedes Deskriptors 64-Byte-ausgerichtet ist.













Nach dem Start der Anwendung sehen wir im Com-Port-Monitor, dass sich die Ausführungszeit zum Kopieren des Datenpuffers nicht geändert hat, ebenfalls 271 * 100 μs.



Betrachten Sie die zweite Option, wenn derselbe Port für den Zugriff auf DMA und Deskriptoren im RAM des Prozessors verwendet wird. Lassen Sie uns die Firmware in der programmierbaren Logik so ändern, dass wir das folgende Schema erhalten: Zugriff auf Daten und Deskriptoren über denselben Port Der Quellcode der Anwendung hat sich gegenüber der vorherigen Version nicht geändert. Nach dem Starten der Anwendung sehen wir auf dem Monitor des COM-Ports die neue Ausführungszeit des Pufferkopiervorgangs: 398 * 100 μs.















Infolgedessen beträgt die Verarbeitungsgeschwindigkeit: (256 mal * 102400 Bytes) / (398 * 100 μs) ≤ 658 653 266 Bytes / s ≤ 643 216 KB / s ≤ 628 MB / s.

Bitrate 5 269 226 128 Bit / s.



Projekt: github.com/Finnetrib/DmaTransfer



4. Fazit



In diesem Artikel haben wir zwei Implementierungen des Datenaustauschs zwischen dem Prozessormodul und der programmierbaren Logik besprochen. Der PIO-Modus ist einfach zu implementieren und ermöglicht es Ihnen, die Geschwindigkeit auf bis zu 23 MB / s zu bringen. Der DMA-Modus ist etwas komplizierter, aber die Geschwindigkeit ist auch höher - bis zu 628 MB / s.



5 Verwendete Quellen



  1. habr.com/ru/post/508292
  2. www.xilinx.com/support/documentation/ip_documentation/axi_dma/v7_1/pg021_axi_dma.pdf



All Articles