Desenvolvimento de placas de interface no solo Xilinx Zynq 7000 para gravação de voz em formato analógico e digital



Neste artigo, compartilharemos nossa experiência no desenvolvimento de placas de interface da unidade de interface com base no SoC ARM + FPGA Xilinx Zynq 7000. As placas foram projetadas para gravar sinais de fala no formato PRI / BRI analógico e digital (ISDN, E1 / T1). O dispositivo final em si será usado para registrar negociações na aviação civil.

Ferro: seleção de plataforma de hardware do dispositivo


A escolha da plataforma de hardware foi determinada pelo suporte dos protocolos PRI / BRI, que podem ser implementados apenas no lado do FPGA. Microcontroladores (MCUs) e microprocessadores (MPUs) não se encaixavam.

Pode-se escolher duas soluções para esse problema:

  1. Síntese do núcleo de microblaze IP
  2. SoC Zynq-7000.

Optamos por um sistema em um chip Zynq 7000 (SoC), porque é mais fácil escrever aplicativos de software e fornece mais funcionalidade para tarefas atuais e futuras.

No total, a seguinte lista de ferro foi coletada no projeto:

1.Xilinx Zynq 7020 ( Mars-ZX3 e Mars EB1 )

Equipamento Mars ZX3 SOM

Rodapé Enclustra Mars EB1

2. TI TLV320AIC34 ( tlv320aic34evm-k e placa-mãe USB).


Placa de depuração para tlv320aic34 (tlv320aic34evm-k)


Placa de expansão USB-MODEVM para tlv320aic34evm-k

3. Microcircuitos IDT82P2288 - PRI, XHFC-4SU - BRI, não havia kits de depuração; portanto, apenas estabelecemos as bases como um núcleo de ip para testes, e o batismo de fogo ocorreu logo no processo, após a fabricação de placas protótipo.

Trabalhar com o sistema no chip Xilinx Zynq 7000




A estrutura interna do SoC Xilinx Zynq 7000


Etapas para gerar arquivos de inicialização para o Xilinx Zynq

Os arquivos executáveis ​​intermitentes / de download para o Zynq são diferentes do download usual para o MPU. O trabalho usual com os processadores Cortex-A é carregar u-boot, kernel linux, rootfs. E no Zynq, o bitstream aparece, o arquivo de firmware para FPGAs. O fluxo de bits contém uma descrição dos blocos de hardware no FPGA e comunicação interna com o processador. Este arquivo é carregado na inicialização do sistema. Também no lado do linux, existe um mecanismo que permite exibir a parte PL imediatamente durante a operação, um dispositivo chamado xdevcfg ( gerenciador do ZYNQ FPGA desde 2018.1 ).

Interfaces PRI / BRI



Recursos de redes digitais PRI / BRI

A Primary Rate Interface (PRI) é uma interface de rede ISDN padrão que define a disciplina de conectar estações ISDN a troncos de banda larga que conectam centrais locais ou centrais ou comutadores de rede.


Tipo de quadro transmitido para PRI


Vista do quadro transmitido para o BRI


A estrutura interna da física do PRI - IDT82P2288


A estrutura interna da física BRI - XHFC-4SU

Codec de áudio TLV320AIC34


O codec de áudio TLV320AIC34 de quatro canais e baixa potência para áudio e telefonia portátil é uma boa solução para uso em telefonia analógica.


Tlv320aic34 Em parte, o codec de áudio contém dois desses blocos de funções

Os dados podem ser transmitidos através da interface I2S, bem como através de DSP, PCM, TDM.

O I2S é um padrão de interface de barramento serial, é usado para conectar dispositivos de áudio digital e representa eletricamente 3 condutores que passam de um dispositivo ativo para um passivo, além de 4 sinais que correspondem a eles da seguinte maneira:

  1. Relógio de bits (BCLK).
  2. Sincronização do quadro de sinal do relógio (de acordo com as palavras) (WCLK).
  3. Sinal de dados que pode transmitir ou receber 2 canais divididos no tempo (DIN / DOUT).

Os canais para receber e transmitir dados são divididos, ou seja, existe um canal separado para receber dados e um canal para transmissão. O controlador recebe os dados transmitidos pelo codec de áudio, mas o inverso também é possível.


Quadro I2S, recursos da interface I2S

Após selecionar todos os componentes de hardware, resolvemos o problema de conexão do codec de áudio e do Xilinx Zynq 7020.

Procure núcleos I2S


Provavelmente, o momento mais difícil ao trabalhar com o fluxo de áudio no Xilinx Zynq 7020 foi que, na parte do processador deste sistema, não havia basicamente nenhum barramento I2S no chip, então tive que encontrar o núcleo do I2S. Essa tarefa foi complicada pela condição de que o núcleo do ip deveria estar livre.

Nós estabelecemos vários núcleos de ip. Encontrado para o núcleo bare metal I2S Digilent . Encontramos vários núcleos de ip em opencores e, provavelmente, a melhor opção para nós é o núcleo de ip dos dispositivos analógicos . Eles produzem núcleos de ip para seus equipamentos, para interação FPGA / FPGA.

Estamos interessados ​​no ip-core chamado AXI-I2S-ADI. A própria Analog Devices está promovendo esses núcleos de ip para suas plataformas de hardware.

Lista total de casos de uso:

  1. Bare metal - núcleo IP para I2S (áudio Digilent ZYBO)
  2. opencores.org
  3. Controlador AXI-I2S-ADI (Analog Devices)

Núcleo IP AXI-I2S-ADI


O próprio núcleo do ip se parece com o seguinte: possui as linhas bclk, wclk, din, dout. Ele se conecta ao DMA Xilinx Zynq 7000, em nosso exemplo, a parte DMA PS é usada. Toda troca de dados ocorre através do DMA. O DMA pode ser uma unidade independente ou parte integrante do PS SoC.

Ao configurar esse ip-kernel, é importante não esquecer de enviar a freqüência principal do mclk para o próprio tlv320aic34, como uma opção ao usar o kit de depuração para o tlv320aic34 - enviar uma freqüência principal externa.


Bloco de funções com axi-i2s-adi conectado

Após o procedimento de configuração, a tarefa era iniciar a funcionalidade no sistema operacional Linux.

Iniciar e configurar a árvore de dispositivos para tlv320aic34


Configurando o i2c (tlv320aic34 está configurado nesta interface):

i2c0: i2c@e0004000 { ... tlv320aic3x: tlv320aic3x@18 { #sound-dai-cells = <0>; compatible = "ti,tlv320aic3x"; reg = <0x18>; gpio-reset = <&axi_gpio_0 0 0>; ai3x-gpio-func = <&axi_gpio_0 1 0>, /* AIC3X_GPIO1_FUNC_DISABLED */ <&axi_gpio_0 2 0>; /* AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT */ AVDD-supply = <&vmmc2>; DRVDD-supply = <&vmmc2>; IOVDD-supply = <&vmmc2>; DVDD-supply = <&vmmc2>; ai3x-micbias-vg = <1>; }; ... }; 

Configurando o i2s (os dados de áudio são transmitidos através desta interface):

 i2s_clk: i2s_clk { #clock-cells = <0>; compatible = "fixed-clock"; clock-frequency = <11289600>; clock-output-names = "i2s_clk"; }; axi_i2s_adi_0: axi_i2s_adi@43C00000 { compatible = "adi,axi-i2s-1.00.a"; reg = <0x43C00000 0x1000>; xlnx,bclk-pol = <0x0>; xlnx,dma-type = <0x1>; xlnx,has-rx = <0x1>; xlnx,has-tx = <0x1>; xlnx,lrclk-pol = <0x0>; xlnx,num-ch = <0x1>; xlnx,s-axi-min-size = <0x000001FF>; xlnx,slot-width = <0x18>; }; &axi_i2s_adi_0 { #sound-dai-cells = <0>; compatible = "adi,axi-i2s-1.00.a"; clocks = <&clkc 15>, <&i2s_clk>; clock-names = "axi", "ref"; dmas = <&dmac_s 0 &dmac_s 1>; dma-names = "tx", "rx"; }; 

Configurando a placa de som na árvore de dispositivos (placas de áudio):

  sound { compatible = "simple-audio-card"; simple-audio-card,name = "TLV320AIC34"; simple-audio-card,format = "i2s"; simple-audio-card,bitclock-master = <&dailink0_master>; simple-audio-card,frame-master = <&dailink0_master>; simple-audio-card,widgets = ... simple-audio-card,routing = ... dailink0_master: simple-audio-card,cpu { clocks = <&i2s_clk>; sound-dai = <&axi_i2s_adi_0>; }; simple-audio-card,codec { clocks = <&i2s_clk>; sound-dai = <&tlv320aic3x>; }; }; }; 

Depois de todas as manipulações para configurar e configurar o codec na árvore de dispositivos no Linux, a cobiçada placa de áudio apareceu e pudemos ouvir música (nossa primeira faixa de música foi Highway to Hell, AC / DC).

Aqui está o que tivemos que fazer para isso:

  • Gerou a frequência necessária usando clk_wiz (assistente de relógio)
  • DTS corretamente configurado para tlv320aic34
  • Adicionado suporte para o driver tlv320aic3x
  • Pacotes de áudio adicionados ao buildroot para reproduzir o fluxo de áudio (aplay, madplay, etc.)

No processo de desenvolvimento do dispositivo final, fomos confrontados com a tarefa de conectar 4 microcircuitos tlv320aic34. O chip tlv320aic34 descrito acima contém 2 blocos para trabalhar com o fluxo de áudio, cada bloco possui sua própria linha i2c para configurar e configurar parâmetros de áudio. Um bloco pode ter apenas quatro endereços, respectivamente, é impossível conectar quatro microcircuitos tlv320aic34 a uma interface i2c, você precisa usar duas interfaces i2c (8 blocos de áudio independentes). Para cada bloco, se você iniciar mclk, blck, wclk, din / dout individualmente, precisará adicionar 40 linhas de sinal no total, o que é impossível e irracional do ponto de vista do circuito para o módulo som que escolhemos, porque além desses sinais, você tinha que conectar muitas outras linhas e interfaces.

Como resultado, decidimos mudar a placa de áudio para o modo TDM , no qual todas as linhas mclk, bclk, din e dout são combinadas, o que reduz o número total de linhas de comunicação. Essa decisão afetou a operação do axi-i2s-adi, devido ao fato de o próprio núcleo do ip funcionar no modo mestre. Além disso, essa alteração não nos permitiu usar nosso ip-core no modo TDM e, com uma decisão decidida, tivemos que abandonar o uso do ip-core selecionado. Eu tive que escrever um kernel ip para ouvir o tráfego do i2s e enviá-lo para a dma. Essa solução nos permitiu criar uma interface comum para o recebimento de dados que não dependiam do tipo de cartão para gravação de chamadas (cartões analógicos e digitais).

A arquitetura inicial para receber o fluxo de áudio e seu processamento através da interface i2s:



A arquitetura final para receber o fluxo de áudio e seu processamento através da interface i2s:



Arquitetura de recebimento de um fluxo PRI e seu processamento:



Arquitetura da recepção e processamento do fluxo BRI:



Axi dma


Este é um elemento importante do sistema de sincronização de dados para dma.


Janela de configuração do AXI DMA no Xilinx Vivado

Na tela de impressão, o próprio bloco AXI DMA é apresentado. Tem muitos parâmetros. Você pode configurar o barramento quantos dados transferir. Os dados podem ser alinhados ou em qualquer formato. Uma descrição detalhada da operação e interação com o axi dma é descrita na documentação técnica (de versão para versão, há uma adição e correção de imprecisões na descrição, além de refinamento dos kernels ip).

Verifique a transferência de dados através das opções de teste do AXI DMA, AXI DMA


Ao desenvolver o driver, decidimos encontrar o código aberto e adaptá-lo à nossa tarefa. Como resultado, escolhemos as fontes do projeto github ezdma (trocadilho, lido como fácil dma).

O próximo passo é o desenvolvimento de um driver de teste; foi uma etapa preparatória em antecipação ao momento em que um núcleo de ip com funcionalidade pronta do departamento de desenvolvimento do FPGA chegou até nós (o processo de desenvolvimento descrito foi formado por programadores incorporados). Antes desse momento, decidimos pegar o AXI DMA, o AXI DATA FIFO e fazer um loopback para garantir erros futuros. Como fizemos o loop e o envio e o recebimento de dados, verificamos o resultado do nosso trabalho e o desempenho do nosso driver. Adaptamos um pouco a funcionalidade, adaptamos-a aos nossos desejos na interface de interação e mais uma vez verificamos a operabilidade do driver e o princípio de interação selecionado.


Design de bloco look-back, a primeira maneira de testar o AXI DMA

Um exemplo de uma descrição de DMA e ezdma em uma árvore de dispositivos:

 / { amba_pl: amba_pl { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; ranges ; axi_dma_1: axi_dma { #dma-cells = <1>; compatible = "xlnx,axi-dma-1.00.a"; reg = <0x40400000 0x10000>; clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk"; clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>, <&clkc 15>; interrupt-parent = <&intc>; interrupts = <0 29 4 0 30 4>; xlnx,addrwidth = <0x20>; xlnx,include-sg; dma-channel@40400000 { compatible = "xlnx,axi-dma-mm2s-channel"; dma-channels = <0x1>; interrupts = <0 29 4>; xlnx,datawidth = <0x20>; xlnx,device-id = <0x0>; xlnx,include-dre ; }; dma-channel@40400030 { compatible = "xlnx,axi-dma-s2mm-channel"; dma-channels = <0x1>; interrupts = <0 30 4>; xlnx,datawidth = <0x20>; xlnx,device-id = <0x0>; xlnx,include-dre ; }; }; ezdma0 { compatible = "ezdma"; dmas = <&axi_dma_1 0 &axi_dma_1 1>; dma-names = "loop_tx", "loop_rx"; // used when obtaining reference to above DMA core using dma_request_slave_channel() ezdma,dirs = <2 1>; // direction of DMA channel: 1 = RX (dev->cpu), 2 = TX (cpu->dev) }; ... }; }; 

Você pode gerar facilmente arquivos dts / dtsi usando a ferramenta Device Tree Generator .

A segunda etapa do nosso processo de desenvolvimento é a criação de um ip-kernel de teste para verificar o desempenho do driver, só que desta vez os dados serão significativos, com a transferência via AXIS para o AXI_DMA (como será na versão final do ip-kernel).


Fluxo de trabalho da interface AXIS

Implementamos duas variantes de kernels ip para geração de dados, a primeira versão testada é implementada através do verilog, a segunda - no HLS (nesse contexto, o HLS apareceu sob o slogan “stylish-fashion-youth”).

O gerador de dados verilog (e geralmente nos idiomas da família hdl - verilog, vhdl etc.) é uma solução padrão ao desenvolver núcleos ip desse tipo. Aqui estão alguns trechos de código para o kernel intermediário de ip:

 module GenCnt ( …. assign HandsHake = m_axis_din_tready & m_axis_dout_tvalid; always @(posedge Clk) begin if (Rst) begin smCnt <= sIDLE; end else begin case (smCnt) sIDLE: begin smCnt <= sDATA; end sDATA: begin if (Cnt == cTopCnt - 1) begin smCnt <= sLAST; end end ... endmodule 

Não há necessidade de uma descrição mais detalhada, pois essa é uma tarefa típica de um designer de FPGA.

Um "animal" mais interessante aqui é o HLS. O Vivado HLS (síntese de alto nível) é o novo software CAD da Xilinx para criar dispositivos digitais usando linguagens de alto nível, como OpenCL, C ou C ++.

C / C ++ são as principais linguagens para um engenheiro de software incorporado, portanto, resolver um problema usando essas linguagens é mais interessante em termos de implementação e análise comparativa para projetos futuros.

Aqui estão dois pequenos exemplos de trabalho com o HLS. O primeiro exemplo é um gerador de dados para o AXI_DMA, o segundo exemplo é a troca de dados entre a parte do processador e a lógica programável via interface s_axilite.

A troca de dados por meio da interface s_axilite (o segundo exemplo) foi implementada para que, a qualquer momento no procfs, fosse possível subtrair qual fluxo de bits fosse carregado, e para que fosse possível rastrear a correção do trabalho com a versão para a parte PL do SoC. Aqui um ponto muito interessante aparece com o s_axilite: o Vivado HLS gera um driver para Linux (o driver, por sua vez, adaptamos para trabalhar com procfs para preservar a hereditariedade da escrita). Um exemplo do código gerado para Linux está abaixo (o caminho para a solução de origem1 / impl / ip / drivers / name_xxx / src /).


Etapas da síntese HLS e geração de código rtl

Gerador de dados HLS para verificar a operação com AXI_DMA:

 #include <ap_axi_sdata.h> #include <hls_stream.h> #define SIZE_STREAM 1024 struct axis { int tdata; bool tlast; }; void data_generation(axis outStream[SIZE_STREAM]) { #pragma HLS INTERFACE axis port=outStream int i = 0; do{ outStream[i].tdata = i; outStream[i].tlast = (i == (SIZE_STREAM - 1)) ? 1 : 0; i++; }while( i < SIZE_STREAM); } 

Um exemplo de obtenção de versão e tipo de placa de interface:

 #include <stdio.h> void info( int &aVersion, int &bSubVersion, int &cTypeBoard, int version, int subVersion, int typeBoard ){ #pragma HLS INTERFACE s_axilite port=aVersion #pragma HLS INTERFACE s_axilite port=bSubVersion #pragma HLS INTERFACE s_axilite port=cTypeBoard #pragma HLS INTERFACE ap_ctrl_none port=return aVersion = version; bSubVersion = subVersion; cTypeBoard = typeBoard; } 

Como você notou, para o desenvolvimento em hls é muito importante entender o trabalho e a aplicação de vários pragmas (HLS pragma), pois o processo de síntese está diretamente ligado aos pragmas.

Driver gerado para s_axilite:

 // ============================================================== // File generated by Vivado(TM) HLS - High-Level Synthesis from C, C++ and SystemC // Version: 2016.4 // Copyright (C) 1986-2016 Xilinx, Inc. All Rights Reserved. // // ============================================================== #ifdef __linux__ /***************************** Include Files *********************************/ #include "xinfo.h" /***************** Macros (Inline Functions) Definitions *********************/ #define MAX_UIO_PATH_SIZE 256 #define MAX_UIO_NAME_SIZE 64 #define MAX_UIO_MAPS 5 #define UIO_INVALID_ADDR 0 /**************************** Type Definitions ******************************/ typedef struct { u32 addr; u32 size; } XInfo_uio_map; typedef struct { int uio_fd; int uio_num; char name[ MAX_UIO_NAME_SIZE ]; char version[ MAX_UIO_NAME_SIZE ]; XInfo_uio_map maps[ MAX_UIO_MAPS ]; } XInfo_uio_info; /***************** Variable Definitions **************************************/ static XInfo_uio_info uio_info; /************************** Function Implementation *************************/ static int line_from_file(char* filename, char* linebuf) { char* s; int i; FILE* fp = fopen(filename, "r"); if (!fp) return -1; s = fgets(linebuf, MAX_UIO_NAME_SIZE, fp); fclose(fp); if (!s) return -2; for (i=0; (*s)&&(i<MAX_UIO_NAME_SIZE); i++) { if (*s == '\n') *s = 0; s++; } return 0; } static int uio_info_read_name(XInfo_uio_info* info) { char file[ MAX_UIO_PATH_SIZE ]; sprintf(file, "/sys/class/uio/uio%d/name", info->uio_num); return line_from_file(file, info->name); } static int uio_info_read_version(XInfo_uio_info* info) { char file[ MAX_UIO_PATH_SIZE ]; sprintf(file, "/sys/class/uio/uio%d/version", info->uio_num); return line_from_file(file, info->version); } static int uio_info_read_map_addr(XInfo_uio_info* info, int n) { int ret; char file[ MAX_UIO_PATH_SIZE ]; info->maps[n].addr = UIO_INVALID_ADDR; sprintf(file, "/sys/class/uio/uio%d/maps/map%d/addr", info->uio_num, n); FILE* fp = fopen(file, "r"); if (!fp) return -1; ret = fscanf(fp, "0x%x", &info->maps[n].addr); fclose(fp); if (ret < 0) return -2; return 0; } static int uio_info_read_map_size(XInfo_uio_info* info, int n) { int ret; char file[ MAX_UIO_PATH_SIZE ]; sprintf(file, "/sys/class/uio/uio%d/maps/map%d/size", info->uio_num, n); FILE* fp = fopen(file, "r"); if (!fp) return -1; ret = fscanf(fp, "0x%x", &info->maps[n].size); fclose(fp); if (ret < 0) return -2; return 0; } int XInfo_Initialize(XInfo *InstancePtr, const char* InstanceName) { XInfo_uio_info *InfoPtr = &uio_info; struct dirent **namelist; int i, n; char* s; char file[ MAX_UIO_PATH_SIZE ]; char name[ MAX_UIO_NAME_SIZE ]; int flag = 0; assert(InstancePtr != NULL); n = scandir("/sys/class/uio", &namelist, 0, alphasort); if (n < 0) return XST_DEVICE_NOT_FOUND; for (i = 0; i < n; i++) { strcpy(file, "/sys/class/uio/"); strcat(file, namelist[i]->d_name); strcat(file, "/name"); if ((line_from_file(file, name) == 0) && (strcmp(name, InstanceName) == 0)) { flag = 1; s = namelist[i]->d_name; s += 3; // "uio" InfoPtr->uio_num = atoi(s); break; } } if (flag == 0) return XST_DEVICE_NOT_FOUND; uio_info_read_name(InfoPtr); uio_info_read_version(InfoPtr); for (n = 0; n < MAX_UIO_MAPS; ++n) { uio_info_read_map_addr(InfoPtr, n); uio_info_read_map_size(InfoPtr, n); } sprintf(file, "/dev/uio%d", InfoPtr->uio_num); if ((InfoPtr->uio_fd = open(file, O_RDWR)) < 0) { return XST_OPEN_DEVICE_FAILED; } // NOTE: slave interface 'Axilites' should be mapped to uioX/map0 InstancePtr->Axilites_BaseAddress = (u32)mmap(NULL, InfoPtr->maps[0].size, PROT_READ|PROT_WRITE, MAP_SHARED, InfoPtr->uio_fd, 0 * getpagesize()); assert(InstancePtr->Axilites_BaseAddress); InstancePtr->IsReady = XIL_COMPONENT_IS_READY; return XST_SUCCESS; } int XInfo_Release(XInfo *InstancePtr) { XInfo_uio_info *InfoPtr = &uio_info; assert(InstancePtr != NULL); assert(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); munmap((void*)InstancePtr->Axilites_BaseAddress, InfoPtr->maps[0].size); close(InfoPtr->uio_fd); return XST_SUCCESS; } #endif 

Um arquivo importante que informa a localização das variáveis ​​(registradores) no espaço de endereço é o arquivo x # your_name # _hw.h. Você sempre pode verificar a correção do ip-kernel gravado usando a ferramenta devmem.

O conteúdo deste arquivo:

 // ============================================================== // File generated by Vivado(TM) HLS - High-Level Synthesis from C, C++ and SystemC // Version: 2016.4 // Copyright (C) 1986-2016 Xilinx, Inc. All Rights Reserved. // // ============================================================== // AXILiteS // 0x00 : reserved // 0x04 : reserved // 0x08 : reserved // 0x0c : reserved // 0x10 : Data signal of aVersion // bit 31~0 - aVersion[31:0] (Read) // 0x14 : Control signal of aVersion // bit 0 - aVersion_ap_vld (Read/COR) // others - reserved // 0x18 : Data signal of bSubVersion // bit 31~0 - bSubVersion[31:0] (Read) // 0x1c : Control signal of bSubVersion // bit 0 - bSubVersion_ap_vld (Read/COR) // others - reserved // 0x20 : Data signal of cTypeBoard // bit 31~0 - cTypeBoard[31:0] (Read) // 0x24 : Control signal of cTypeBoard // bit 0 - cTypeBoard_ap_vld (Read/COR) // others - reserved // (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake) #define XINFO_AXILITES_ADDR_AVERSION_DATA 0x10 #define XINFO_AXILITES_BITS_AVERSION_DATA 32 #define XINFO_AXILITES_ADDR_AVERSION_CTRL 0x14 #define XINFO_AXILITES_ADDR_BSUBVERSION_DATA 0x18 #define XINFO_AXILITES_BITS_BSUBVERSION_DATA 32 #define XINFO_AXILITES_ADDR_BSUBVERSION_CTRL 0x1c #define XINFO_AXILITES_ADDR_CTYPEBOARD_DATA 0x20 #define XINFO_AXILITES_BITS_CTYPEBOARD_DATA 32 #define XINFO_AXILITES_ADDR_CTYPEBOARD_CTRL 0x24 

Este arquivo descreve os endereços dos registradores, os registradores correspondem à localização dos argumentos na função. Após a síntese do projeto, você pode ver como o projeto criado será executado em ciclos.


Exemplo de batida do projeto

Trabalhar com hls mostrou que esta ferramenta é adequada para resolver tarefas rapidamente, especialmente se provou para resolver problemas matemáticos de visão computacional, que podem ser facilmente descritos em C ++ ou C, bem como para criar pequenos núcleos de ip para interações e trocas informações com interfaces FPGA padrão.

Ao mesmo tempo, o HLS não é adequado para implementar interfaces de hardware específicas, por exemplo, no caso foi o I2S, e o código rtl gerado ocupa mais espaço no FPGA do que é escrito nas linguagens hdl padrão.

A etapa final no teste do driver é o desenvolvimento de um gerador de tráfego I2S. Esse ip-core repete a funcionalidade dos kernels ip anteriores, exceto que gera dados incrementais (tráfego) que correspondem aos dados reais do I2S no modo TDM.


Projeto de bloco para futuros testes de núcleo I2S personalizados e gerador de tráfego I2S

Como resultado, obtivemos os resultados de hls, axi dma e s_axilite, verificamos o desempenho de nosso software e drivers.

Conclusões


Conseguimos desenvolver os tipos necessários de placas de interface, bem como kernels ip para tdm, pri, bri. Melhoramos significativamente a abordagem atual para o desenvolvimento de tais dispositivos e criamos uma solução abrangente que pode competir com placas de interface semelhantes da Asterick , patton e outras. A vantagem da nossa solução é que o desenvolvedor não precisa de um link intermediário entre o PC e o PCI para a transferência de dados; ele poderá transmitir diretamente as informações recebidas via Ethernet.

Source: https://habr.com/ru/post/pt415435/


All Articles