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

- Verbinden Sie die erforderlichen Pins, indem Sie auf die Schaltfläche Run Block Automation klicken => OK.
- . Zynq7 Processing System => Import XPS Setting => => OK => OK.
- , . 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.

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

- . Ctrl + I => PIO_registers.

- , . PIO_registers => Edit in IP Packager => OK. Vivado .
- 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,
- PIO_registers_v1_0_S_AXI.vhd :
iSwitches : in std_logic_vector( 3 downto 0); oLeds : out std_logic_vector( 3 downto 0);
- :
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;
- 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 .
- Block Design Report IP Status, Upgrade Selected => OK => Skip => OK.

- . Run Connection Automation => OK.

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

- iSwitches_0 => iSwitches. oLeds_0 => oLeds.

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

- vhd top- block design. File => Add Sources => Add or create design sources => Next => Create File => => OK => Finish => OK => Yes.
- :
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;
- . File => Add sources => Add or create constrains => Next => Create File => => OK => Finish.

- :
#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[*]}]
- . Flow Navigator => Generate Bitstream => OK. , , .
- . 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.
- Blockdesign erstellen. Im Flow Navigator => Blockdesign erstellen => den Namen "ProcessingSystem" => OK.
- Fügen Sie mit der Taste "+" auf dem Feld oder den Tastenkombinationen Strg + I den Prozessorkern hinzu.
- Verbinden Sie die erforderlichen Pins, indem Sie auf die Schaltfläche Run Block Automation klicken => OK.
- . Zynq7 Processing System => Import XPS Setting => => OK => OK
- AXI Direct Memory Access, AXI BRAM Controller, Block Memory Generator.

- 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» .

- AXI BRAM Controller. «Number of BRAM Interfaces» 1. «OK» .
- AXI BRAM Controller.
- Block Memory Generator. «Memory Type» «True Dual Port RAM». «OK» .

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

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

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

- – 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.
- DMA . «Run Connection Automation» => processing_system7_0 S_AXI_HP0 => Master Interface /axi_dma_0/M_AXI => OK.
- DMA . Concat + Ctrl + I.
- mm2s_introut DMA, . mm2s_introut In0 Concat. , , .

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

- FCLK_CLK0 Zynq7 Processing System.
- . peripheral_reset Processor System Reset => => Make External.
- , , .
- DMA. S_AXIS_S2MM AXI Direct Memory Access => => Make External.
- , , .

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

- S_AXIS_S2MM M_AXIS_MM2S AXI Direct Memory Access. «Run Connection Automation» => m_axi_mm2s_aclk m_axi_s2mm_aclk => OK
- , 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.

- Tools => Validate Design => OK. :

- File => Save Block Design.
- block design , Flow Navigator => Project Manager.
- , block design’a. ProcessingSystem.bd, => View Instantiation Template.
- vhd top- block design. File => Add Sources => Add or create design sources => Next => Create File => => OK => Finish => OK => Yes.
- :
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;
- . Flow Navigator => Generate Bitstream => OK. , , .
- . 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.
- Ö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"
- , 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];
- , , .
/** Flush Cache */ Xil_DCacheFlush(); /** Disable Cache */ Xil_DCacheDisable();
- , .
for (u16 desc = 0; desc < DESC_COUNT; desc++) { for (u32 i = 0; i < BUFFER_SIZE; i++) { TxBuffer[desc][i] = desc + i; } }
- .
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; }
- , .
DescAddr = 0x40000000; for (u16 i = 0; i < DESC_COUNT; i++) { Xil_MemCpy(DescAddr, &TxDesc[i], sizeof(TxDesc[i])); DescAddr += 0x40; }
- .
/** 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);
- .
/** 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);
- 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);
- , . , , .
/** 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.