Contenido
1 Información general
2 Transferencia de datos en modo PIO
2.1 Hardware
2.2 Software
2.3 Resultados
3 Transferencia de datos en DMA
3.1 Hardware
3.2 Software
3.3 Resultados
4 Conclusión
5 Fuentes utilizadas
1. General
En general, la transferencia de datos entre el módulo procesador y la lógica programable es posible en dos modos:
- PIO , usando el puerto GP.
- Se utiliza DMA , puerto HP.
2 Transferencia de datos en modo PIO
En el modo PIO, el módulo procesador funciona con lógica programable como un conjunto de registros. Para escribir o leer una cierta cantidad de datos, necesita la participación constante del módulo procesador. En el modo PIO, todas las transacciones son iniciadas por el módulo procesador. La conexión de la lógica programable implica el uso del puerto GP, donde Master es un módulo de procesador, Slave es una lógica programable. Estructura del proyecto al usar PIO
2.1 Hardware
- Creamos un proyecto para Zybo en Vivado, tipo de chip xc7z010clg400-1.
- Crea un diseño de bloque. En el Flow Navigator => Crear diseño de bloque => el nombre "ProcessingSystem" => Aceptar.
- Usando el botón "+" en el campo o los atajos de teclado Ctrl + I, agregue el núcleo del procesador.

- Conectemos los pines necesarios haciendo clic en el botón Ejecutar automatización de bloques => Aceptar.
- . 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
Ahora necesita escribir una aplicación que se ejecute en el módulo del procesador que leerá datos de la lógica programable y escribirá datos en la lógica programable. Necesita iniciar el entorno de desarrollo de Vitis y crear una aplicación usando la plantilla Hello World, un ejemplo de esto se muestra en el artículo anterior [1].
La dirección del kernel creado para acceder desde el módulo procesador se puede ver en Vivado. En Flow Navigator => Abrir diseño de bloque => Pestaña del editor de direcciones. En este caso, la dirección es 0x43C0_0000. En esta dirección se encuentra un registro, en el que se almacena el atributo, en qué estado se encuentran los interruptores. En consecuencia, en la dirección 0x43C0_0004 hay un registro que está conectado a los LED.
En Vitis, abra el archivo helloworld.c y complete:
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;
}
Donde la función Xil_In32 se usa para leer 4 bytes de datos de la lógica programable, y Xil_Out32, respectivamente, para escribir 4 bytes de datos en la lógica programable.
2.3 Resultados
Cree la aplicación, cree un archivo de firmware y cárguelo en la placa. Descrito en el artículo anterior [1].
Empezamos, miramos en el monitor del puerto com:
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
Todo funciona correctamente.
Así, para acceder a la lógica programable en modo PIO, es necesario implementar una de las interfaces de comunicación con el módulo procesador en la lógica programable, donde el módulo procesador es el iniciador. Esta interfaz está representada únicamente por el puerto GP.
Veamos qué tan rápido se procesan las solicitudes a la lógica programable a través del puerto GP. Para hacer esto, en una aplicación que se ejecuta en un módulo de procesador, agregue varias entradas seguidas al registro en lógica programable y mida el tiempo entre transacciones en lógica programable usando las señales de bus introducidas en el depurador.
Cuando el bus Axi-Lite funciona a 100 MHz, la pausa entre solicitudes es de 23 ciclos de reloj en promedio. Cambiemos la frecuencia del bus a 200 MHz. La pausa entre solicitudes se convierte en un promedio de 33 ciclos.
En total, se transmiten 4 bytes de datos a 100 MHz durante 23 ciclos de reloj. La velocidad es: 32 / (23 * 10ns) = 139130434 bit / s ≈ 135869 Kbps ≈ 132 Mbps ≈ 16 MB / s.
En total, se transmiten 4 bytes de datos a 200 MHz durante 33 relojes. La velocidad es 32 / (33 * 5ns) = 193 939 393 bps ≈ 189 393 Kbps ≈ 184 Mbps ≈ 23 Mbps.
Así, puede alcanzar una velocidad de 23 MB / s, pero con la participación constante del módulo procesador.
Proyecto: github.com/Finnetrib/PioTransfer
3 Transferencia de datos en modo DMA
La transferencia de datos en modo DMA implica que el módulo procesador configura los parámetros de intercambio de datos y no participa directamente en el intercambio. Por lo tanto, se logran dos objetivos: reducir la carga en el módulo del procesador y aumentar la velocidad de procesamiento de datos. El precio de esto es la complicación del hardware.
En Zynq, es posible utilizar varios ip-cores que implementan funciones DMA. Este artículo discutirá el núcleo AXI DMA [2].
AXI DMA tiene dos canales MM2S y S2MM. El canal MM2S (Memory-mapped to stream) se utiliza para transferir datos desde el módulo procesador a la lógica programable. El canal S2MM (Stream to memory-mapped) se utiliza para transferir datos desde la lógica programable al módulo del procesador. Los canales funcionan de forma independiente entre sí.
AXI DMA tiene dos casos de uso:
- Modo de registro directo
- Modo de dispersión / recolección
El modo de registro directo utiliza un conjunto de registros, lo que permite transferir un búfer desde la lógica programable al módulo del procesador y viceversa. Por ejemplo, para transferir un búfer de datos desde la lógica programable a un módulo de procesador, debe completar los campos de dirección y tamaño del búfer e iniciar DMA. Como resultado, DMA llenará un búfer en la unidad procesadora y se detendrá.
El modo Scatter / Gather usa una lista de descriptores. DMA procesa el búfer descrito en el descriptor y procede a procesar el búfer descrito en el siguiente descriptor.
3.1 Hardware
Estructura del proyecto cuando se usa DMA
Consideremos una variante cuando la lista de descriptores se almacena en lógica programable. El bloque DMA tiene un puerto de control que se conecta al puerto GP de la unidad procesadora. También hay un puerto HP que se utiliza para acceder a la RAM del procesador. La lista de descriptores se almacena en la memoria de descriptores. Se puede acceder a la memoria del descriptor tanto desde el DMA como desde la unidad procesadora. El módulo de procesador completa los descriptores, el DMA lee los descriptores.
- Crea un diseño de bloque. En el Flow Navigator => Crear diseño de bloque => el nombre "ProcessingSystem" => Aceptar.
- Usando el botón "+" en el campo o los atajos de teclado Ctrl + I, agregue el núcleo del procesador.
- Conectemos los pines necesarios haciendo clic en el botón Ejecutar automatización de bloques => Aceptar.
- . 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
Ahora necesita escribir una aplicación que se ejecute en el módulo del procesador. Debe iniciar el entorno de desarrollo de Vitis y crear una aplicación utilizando la plantilla Hello World, un ejemplo de esto se muestra en el artículo anterior.
El formato de los descriptores para Axi DMA se describe en el documento del kernel [2]. El descriptor tiene un tamaño de 52 bytes, sin embargo, la dirección en la que se encuentra el descriptor debe estar alineada con 64 bytes.
Brevemente sobre el formato del descriptor:
- NXTDESC - dirección del siguiente descriptor;
- NXTDESC_MSB - 32 bits altos de la siguiente dirección de descriptor;
- BUFFER_ADDRESS - dirección de búfer;
- BUFFER_ADDRESS_MSB - 32 bits altos de la dirección del búfer;
- RESERVADO - no utilizado;
- RESERVADO - no utilizado;
- CONTROL: establece el tamaño del búfer, los signos del comienzo y el final del paquete;
- ESTADO: muestra cuántos bytes se recibieron / transmitieron, procesaron / no procesaron;
- APP0: se utiliza para trabajar con el canal Control / Status Stream;
- APP1: se utiliza para trabajar con el canal Control / Status Stream;
- APP2: se utiliza para trabajar con el canal Control / Status Stream;
- APP3: se utiliza para trabajar con el canal Control / Status Stream;
- APP4: se utiliza para trabajar con el canal "Control / Status Stream".
Las direcciones en lógica programable para el acceso desde el módulo procesador se pueden ver en Vivado. En Flow Navigator => Abrir diseño de bloque => Pestaña del editor de direcciones. En este caso, la dirección DMA es 0x4040_0000. La dirección del comienzo del área de memoria para descriptores es 0x4000_0000.
- En Vitis, abra el archivo helloworld.c e incluya las siguientes bibliotecas
#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
Cree la aplicación, cree un archivo de firmware y cárguelo en la placa. Descrito en el artículo anterior [1].
Empezamos, miramos en el monitor del puerto com:
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
Por lo tanto, para el intercambio de datos entre el módulo procesador y la lógica programable, una de las interfaces de comunicación con el módulo procesador debe implementarse en la lógica programable, donde el iniciador es la lógica programable. Dichas interfaces están representadas por puertos GP, HP, ACP. En el artículo anterior [1] todos fueron considerados.
Calculemos la tasa de transferencia de datos: (256 veces * 102400 bytes) / (271 * 100 μs) ≈ 967321033 bytes / s ≈ 944649 KB / s ≈ 922 MB / s.
Tasa de bits 7,738,568,264 bps.
La velocidad teórica es de 32 bits * 250 MHz = 8.000.000.000 bits / s.
Además, es posible almacenar descriptores no en la memoria lógica programable, sino en la memoria de acceso aleatorio conectada al módulo procesador. En este caso, el puerto M_AXI_SG se conecta al puerto HP Zynq.
Consideremos la primera opción, cuando se utilizan diferentes puertos HP para el acceso DMA a los datos y a los descriptores en la RAM del procesador. Modifiquemos el firmware en la lógica programable para obtener el siguiente esquema: Acceso a datos y descriptores a través de diferentes puertos No proporcionaremos el código fuente de la aplicación. La única diferencia es que los descriptores no necesitan copiarse en la memoria lógica programable. Sin embargo, es necesario tener en cuenta la condición de que la dirección de cada descriptor esté alineada en 64 bytes.
Después de iniciar la aplicación, en el monitor del puerto com, veremos que el tiempo de ejecución para copiar el búfer de datos no ha cambiado, también 271 * 100 μs.
Consideremos la segunda opción, cuando se usa el mismo puerto para acceder a DMA y descriptores en la RAM del procesador. Modifiquemos el firmware en la lógica programable para obtener el siguiente esquema: Acceso a datos y descriptores a través del mismo puerto El código fuente de la aplicación no ha cambiado respecto a la versión anterior. Luego de iniciar la aplicación, en el monitor del com-port veremos el nuevo tiempo de ejecución de la operación de copia en búfer: 398 * 100 μs.
Como resultado, la velocidad de procesamiento será: (256 veces * 102400 bytes) / (398 * 100 μs) ≈ 658653 266 bytes / s ≈ 643216 KB / s ≈ 628 MB / s.
Tasa de bits 5 269 226 128 bit / s.
Proyecto: github.com/Finnetrib/DmaTransfer
4. Conclusión
En este artículo, analizamos dos implementaciones de intercambio de datos entre el módulo del procesador y la lógica programable. El modo PIO es fácil de implementar y le permite obtener la velocidad hasta 23 MB / s, el modo DMA es algo más complicado, pero la velocidad también es mayor, hasta 628 MB / s.