As primeiras experiências usando o protocolo de streaming no exemplo de comunicação de CPU e processador no FPGA Redd



Nos artigos anteriores, já nos encontramos com o barramento Avalon-MM , onde MM significa Memory Mapped, ou seja, projetado na memória. Este pneu é bastante versátil. Vários dispositivos mestre (mestre) e vários dispositivos escravos podem ser conectados a ele. Já conectamos dois dispositivos principais ao mesmo tempo (Instruction Master e Data Master), porque o processador NIOS II possui uma arquitetura Harvard, portanto os barramentos de comando e de dados são diferentes, mas muitos autores os conectam ao mesmo dispositivo comum para simplificar o desenvolvimento de software externo. para o ônibus.

Se um bloco no barramento tiver a funcionalidade de acesso direto à memória (DMA), ele também conterá um mestre para o barramento.

Na verdade, a principal inconveniência deste pneu é baseada nesse fato (muitos líderes, muitos seguidores). Quando projetamos nosso escravo, tivemos que decodificar o endereço. Quando fiz meu líder, houve muito mais barulho na arbitragem. Mas um fio vermelho em toda a série de artigos é a afirmação de que o desenvolvimento sob Redd é uma parte auxiliar do projeto, não deve exigir muito trabalho. E se pudermos nos libertar da rotina, devemos nos libertar dela.



Todos os artigos do ciclo:

  1. Desenvolvimento do “firmware” mais simples para FPGAs instalados no Redd e depuração usando o teste de memória como exemplo
  2. Desenvolvimento do “firmware” mais simples para FPGAs instalados em Redd. Parte 2. Código do Programa
  3. Desenvolvimento de núcleo próprio para incorporação em um sistema de processador baseado em FPGA
  4. Desenvolvimento de programas para o processador central Redd no exemplo de acesso ao FPGA

O documento Avalon Interface Specifications já nos é conhecido (em geral, eu não dou links diretos, pois eles sempre mudam, para que toda a rede esteja repleta de artigos com links mortos, é mais fácil encontrar a posição atual inserindo o nome no mecanismo de pesquisa) informa que, além do ônibus Avalon-MM , também há um ônibus Avalon-ST , onde ST significa Stream, ou seja, streaming. O fato é que muitas vezes os dados transmitidos têm uma estrutura de fluxo. Sim, mesmo o setor clássico do disco rígido. Tem um tamanho fixo. Deve ser passado do começo ao fim. Mesmo se considerarmos na área endereçável, os endereços aumentarão linearmente. E se você usar o bloco FIFO para armazenamento, os endereços dentro dele estarão completamente ocultos de nós. Eles são, mas trabalhar com eles não é nossa preocupação.

O mesmo se aplica a muitos outros dados de streaming: eles sempre vão do começo ao fim, sendo colocados em repositórios sequencialmente. É exatamente isso que protocolos de streaming são usados ​​para transferir esses dados. Além da falta de endereçamento explícito, o barramento Avalon-ST é interessante, pois sempre conecta dois dispositivos: uma fonte e um receptor. Sempre existem dois deles. Um dispositivo é sempre a fonte, o segundo é sempre o receptor. Portanto, problemas com a arbitragem deste barramento não se preocupam. É assim que são os pares típicos de dispositivos conectados a esse barramento:



E aqui estão os sinais típicos deste barramento:



Além disso, as linhas de erro são opcionais, elas transmitem códigos de erro binários atribuídos por nós e podemos dizer que não há códigos de erro. E as linhas numéricas do canal, como vimos acima, são necessárias apenas se a desmultiplexação for realizada ainda mais. Caso contrário, o número do canal não é necessário. Vamos ficar sem ele por enquanto. Três linhas permanecem: de fato, dados, um sinal de pronto e um sinal de confirmação de dados (strobe). Bem, outro sinal de relógio, já que o ônibus é síncrono.

Da documentação, também se segue que são possíveis mais três sinais, adicionando ao barramento as propriedades de transmissão de pacotes claramente definidos:



Em geral, o pneu é muito interessante e hoje começaremos experimentos com ele. Como já sabemos, o FPGA é conectado ao barramento USB do complexo Redd através da ponte FT2232H , operando no modo FT245-SYNC . A rigor, os dados que passam por essa interface são bastante fluidos. Hoje vamos aprender como transferir esses dados para o nosso sistema de processador baseado no NIOS II. É uma pena que o protocolo FT245-SYNC , embora seja transmitido por streaming, não esteja totalmente em conformidade com o barramento Avalon-ST . Para salvar as pernas do chip, ele possui um barramento de dados bidirecional e o barramento Avalon-ST é unidirecional. Portanto, temos que fazer um bloco que coordene protocolos próximos, mas não correspondentes.

Já nos familiarizamos com o protocolo FT245-SYNC em um dos artigos anteriores . Deixe-me lembrá-lo de que sua descrição pode ser encontrada no documento AN_130 FT2232H Usado no modo FIFO síncrono no estilo FT245 . Aqui está um diagrama de tempo típico da transmissão de uma ponte para um FPGA



Em geral, como programador, estou muito interessado no fato de que o pacote transmitido teria claramente marcado início e fim. Bem, para tornar mais lógico na lógica do protocolo UDP, já que se a transmissão for no estilo TCP, você precisará adicionar dados de referência especiais ao fluxo, que serão gastos na minha programação, esforços e ciclos de processador ... Parece a linha RXF pode nos ajudar com isso. Verificamos ... Preenchemos o “firmware” no FPGA para medir o desempenho, feito no artigo anterior , e conectamos a sonda do osciloscópio à linha RXF. Como um programa de teste para o processador central Redd, usamos a base, também usada para medir o desempenho. Em vez de enviar grandes quantidades de dados, enviamos um bloco monolítico de 0x400 bytes.

uint8_t temp [maxBlockSize]; memset (temp,0,sizeof (temp)); uint32_t dwWritten; FT_Write(ftHandle0, temp, 0x400, &dwWritten); 

Temos a seguinte imagem na linha RXF:



É claro que o microcircuito recebe 0x200 bytes de buffer (ou seja, quanto pode receber em um pacote USB2.0 HS), depois os envia para o canal. Em geral, isso é estranho, uma vez que a documentação afirma que dois buffers são usados ​​em cada direção. Durante a transmissão, o segundo buffer deveria ter tido tempo para encher. Infelizmente. O final de seu preenchimento está claramente atrasado. Na verdade, isso mostra por que o desempenho não atinge os 52 megabytes teóricos por segundo: uma grande porcentagem do tempo (embora não 50%) simplesmente não é transmitida.

Mas de uma maneira ou de outra, e descobrimos que é possível detectar o início de um pacote em uma borda RXF negativa somente se o tamanho do pacote não exceder 0x200 bytes. Se enviarmos apenas comandos com uma pequena quantidade de dados para o dispositivo, isso é bastante possível. Mas se enviarmos grandes fluxos de dados, teremos que usar um canal contínuo, semelhante em sua lógica ao UART (ou, digamos, ao canal TCP), destacando os limites de pacotes de maneira puramente programática.

Em geral, para simplificar a apresentação, tomamos a versão de streaming como base. Hoje não consideraremos pacotes. Bem, qual versão do ônibus Avalon-ST que tomamos como base é clara. Começamos a projetar nosso bloco. Como observado acima, temos que fazer não apenas uma ponte, mas um comutador, porque o barramento FT245FIFO é bidirecional e o barramento Avalon-ST é unidirecional. Ou seja, é necessário fazer dois barramentos Avalon-ST ao mesmo tempo: saída e entrada.



Estamos começando a desenvolver lentamente um autômato que implementará a lógica de que precisamos. Obviamente, no artigo, essa lógica será simplificada ao máximo. Vamos começar com a transferência de dados do FPGA para o PC, pois esse processo é um pouco mais simples (você não precisa mudar o estado da linha do OE, sobre o qual falamos no último artigo ). Ou seja, estamos implementando a porta Sink.

Do lado do barramento Avalon-ST , escolhi o seguinte modo de operação (existem muitos no documento, mas para a interface com o FT245-SYNC, este é o mais próximo):



Deixe-me lembrá-lo da direção dos sinais:



Ou seja, basta aguardar a confirmação no ônibus ( válido ), clicar nos dados e fechar esse fato com a linha pronta .

Do lado do FT245_FIFO, o protocolo fica assim:



Acontece que devemos aguardar o sinal TXE e bloquear os dados com o sinal WR # (a polaridade é inversa para ambos os sinais).

TXE # é muito semelhante em funcionalidade a pronto , e WR # é válido . Os detalhes são um pouco diferentes, mas a lógica é semelhante.

Acontece que podemos destacar um único estado para PC, no qual as comutações mais simples de algumas linhas serão executadas. A condição para entrar nesse estado será a disponibilidade de ambas as partes para a transmissão, ou seja (TXE # == 0) AND (válido == 1). Assim que um pouco da prontidão acabar, voltamos ao modo inativo.

O gráfico de transição do autômato ainda é simples:



E a tabela de comutação é assim (onde os nomes dos sinais são ambíguos, os índices são adicionados a eles, onde os nomes são únicos - não há índices):

SignalStatus do ToPCOutras condições
WR #NÃO (validSink)1
readySinkNÃO (nº TXE)0 0
DATAFT245_FIFODataSinkZ


Passando para uma transferência um pouco mais complexa da Origem para a FT245_FIFO. Como vimos no artigo anterior , a complicação é mudar de direção com o sinal OE #:



Para o barramento Avalon_ST, tudo é o mesmo de antes, portanto as imagens não são mostradas uma segunda vez, mas agora estamos na posição Fonte.

Aqui, a linha RXF # corresponde à linha válida e a linha RD # corresponde à linha pronta . Bem, adicione alguns estados à máquina:



e a seguinte lógica para sinais ativos neste estado:

SignaldropOEfromPCOutras condições
OE #0 00 01
RD #1NÃO (readySource)1
dataSourceQualquer valorDATAFT245_FIFOQualquer valor
fonte válida0 0NÃO (RXF #)0 0

É claro que o esquema não era o mais ideal. Existem várias nuances associadas a transbordamentos ou desobstruções de buffer. Mas não deve haver nenhum tipo de perda de dados, mas quanto à otimização, você precisa começar de algum lugar!

Começamos a transferir a teoria desenvolvida para o código SystemVerilog. É verdade que não podemos usar todos os recursos do SystemVerilog. Foi o caso, escrevi um grande artigo em que testei a capacidade de sintetização prática dos belos recursos dessa linguagem com um ambiente de desenvolvimento real. Aqui apenas pedimos o uso de interfaces, porque o sistema terá duas instâncias do tipo Avalon-ST . Ai e ah. Aqui está o código do teste:
 interface AvalonST #(parameter width=8)(input clk); logic [width-1:0] data; logic ready; logic valid; modport source (input clk, ready, output data,valid); modport sink (input clk, data, valid, output ready); endinterface module FT245toAvalonST ( AvalonST.source source, AvalonST.sink sink ); //assign source.ready = sink.valid; assign sink.ready = source.valid; endmodule 

É perfeitamente sintetizado no compilador principal (uma linha comentada ao excluir um comentário provoca um erro para garantir que o sintetizador interpreta tudo corretamente), mas ao verificar o botão Analyze Synthesis Files para um componente desse código, é gerado um erro que o tipo AvalonST é desconhecido. Ou seja, a análise não existe no SystemVerilog, mas no Verilog puro. Que pena.



Além disso, a linguagem é determinada corretamente, apenas o analisador não entende as interfaces entre as portas.



Em geral, você precisa usar a sintaxe antiga e feia.

Com esta sintaxe, obtemos a seguinte interface de módulo:
 module FT245toAvalonST ( input clk, input reset, inout [7:0] ft245_data, input logic ft245_rxf, input logic ft245_txe, output logic ft245_rd, output logic ft245_wr, output logic ft245_oe, output logic ft245_siwu, input logic source_ready, output logic source_valid, output logic[7:0] source_data, output logic sink_ready, input logic sink_valid, input logic[7:0] sink_data ); 


Rude, vintage, mas o que você pode fazer.

Percebemos o gráfico de transição do autômato sem frescuras:
 //    enum {idle, toPC, dropOE, fromPC} state = idle; //     always_ff @(posedge clk,posedge reset) begin if (reset == 1) begin state <= idle; end else begin case (state) idle: begin if ((ft245_txe == 0) && (sink_valid == 1)) state <= toPC; else if ((ft245_rxf == 0)&&(source_ready == 1)) state <= dropOE; end toPC: begin if (!((ft245_txe == 0) && (sink_valid == 1))) state <= idle; end dropOE: begin state <= fromPC; end fromPC: begin if (!((ft245_rxf == 0)&&(source_ready == 1))) state <= idle; end endcase end end 


O controle das saídas, no entanto, requer alguma explicação.

Parte das instalações é feita “na testa”:
 //    //   ,        , //  -    . always_comb begin ft245_oe <= 1; ft245_rd <= 1; ft245_wr <= 1; source_valid <= 0; sink_ready <= 0; //     , //     assign- case (state) idle: begin end toPC: begin ft245_wr <= !(sink_valid); sink_ready <= !(ft245_txe); end dropOE: begin ft245_oe <= 0; end fromPC: begin ft245_oe <= 0; ft245_rd <= !(source_ready); source_valid <= !(ft245_rxf); end endcase end 


Mas, digamos, para um barramento de dados bidirecional, uma solução típica deve ser aplicada. Como lembramos, é declarado na parte da interface da seguinte maneira:

  inout [7:0] ft245_data, 

e a leitura pode ser feita da maneira usual. No nosso caso, simplesmente agrupamos todos os dados nos dados do barramento Avalon-ST de saída:

 //          assign source_data = ft245_data; 

Mas, em geral, você sempre pode ler do ônibus e da maneira que quiser. Mas você deve escrever usando o multiplexador. Quando gravamos dados no barramento, esses dados devem vir de qualquer outro barramento pré-preparado. Normalmente, uma variável do tipo reg (ou lógica newfangled) é encerrada em um módulo. No nosso caso, esse ônibus já existe. Este é o barramento sink_data . Em outros casos, o estado Z é emitido. Se você estiver familiarizado com os circuitos, estará ciente de um buffer de saída típico. Ele pula qualquer dado de entrada ou entra no estado Z. Em nosso código, esse multiplexador é assim:

 //      inout- assign ft245_data = (state == toPC) ? sink_data : 8'hzz; 

E outro sinal ft245_siwu. Nós nunca o usamos, portanto, de acordo com a documentação do FT2232H, puxe-o para a unidade:

 //   FTDI : // Tie this pin to VCCIO if not used. assign ft245_siwu = 1; 

Na verdade, é tudo.

O módulo inteiro fica assim:
 module FT245toAvalonST ( input clk, input reset, inout [7:0] ft245_data, input logic ft245_rxf, input logic ft245_txe, output logic ft245_rd, output logic ft245_wr, output logic ft245_oe, output logic ft245_siwu, input logic source_ready, output logic source_valid, output logic[7:0] source_data, output logic sink_ready, input logic sink_valid, input logic[7:0] sink_data ); //    enum {idle, toPC, dropOE, fromPC} state = idle; //     always_ff @(posedge clk,posedge reset) begin if (reset == 1) begin state <= idle; end else begin case (state) idle: begin if ((ft245_txe == 0) && (sink_valid == 1)) state <= toPC; else if ((ft245_rxf == 0)&&(source_ready == 1)) state <= dropOE; end toPC: begin if (!((ft245_txe == 0) && (sink_valid == 1))) state <= idle; end dropOE: begin state <= fromPC; end fromPC: begin if (!((ft245_rxf == 0)&&(source_ready == 1))) state <= idle; end endcase end end //    //   ,        , //  -    . always_comb begin ft245_oe <= 1; ft245_rd <= 1; ft245_wr <= 1; source_valid <= 0; sink_ready <= 0; //     , //     assign- case (state) idle: begin end toPC: begin ft245_wr <= !(sink_valid); sink_ready <= !(ft245_txe); end dropOE: begin ft245_oe <= 0; end fromPC: begin ft245_oe <= 0; ft245_rd <= !(source_ready); source_valid <= !(ft245_rxf); end endcase end // -  c  ,   ... //   FTDI : // Tie this pin to VCCIO if not used. assign ft245_siwu = 1; //      inout- assign ft245_data = (state == toPC) ? sink_data : 8'hzz; //          assign source_data = ft245_data; endmodule 


Como incluir o módulo na lista de disponíveis para uso no sistema do processador, examinamos em detalhes em um dos artigos anteriores , então apenas mostro o resultado na figura. Lembro-me de que, para alcançá-lo, tive que adicionar dois barramentos AVALON-ST , um de Conduit , puxar sinais de um barramento AVALON-MM definido erroneamente e, quando não houver um único sinal naquele barramento, basta excluí-lo. Ao longo do caminho, a figura mostra as configurações que selecionei para os barramentos AVALON-ST (8 bits por símbolo, sem erros, o canal máximo é zero, a latência é zero).



Com o desenvolvimento de um módulo para encaixar pneus - é isso. Mas infelizmente, ah. Desenvolver é apenas o começo do trabalho. Implementar é muito mais difícil. Como pode ser visto a partir da posição do rolo na tela, o final do artigo ainda está longe. Então, estamos começando a criar um projeto simples que usa a junção de barramento FT245-SYNC com os barramentos AVALON-ST . É o mais simples. Um projeto sério não se enquadra na estrutura de um único artigo de tamanho razoável. Agora simplificarei após simplificação, simplesmente para que a atenção dos leitores seja suficiente para o restante do texto, para que eles não parem de ler em uma palavra. A primeira simplificação é que os relógios de 60 MHz para o FT245_SYNC são gerados pelo próprio chip FT2232H . Eu poderia adicionar duas linhas de relógio ao sistema, mas assim que todos virem, teremos tantas teias de aranha que minha mãe não sofrerá. Se eu ainda prestar atenção às diferentes linhas do relógio, todos ficaremos confusos. Portanto, eu simplesmente anuncio que hoje nosso sistema de processador funcionará com clock do chip FT2232H , e não de um gerador comum.

Por que você não pode sempre fazer isso? Muito simples: enquanto o FT2232H não estiver no modo 245_SYNC, ele não terá esses pulsos na saída. Ou seja, você deve primeiro executar o programa para o processador central e só depois carregar tudo no FPGA. Se criarmos um sistema para um cliente externo, essa solução criaria muitos problemas. Sei por experiência própria que eles ligavam para nós regularmente e diziam que nada funciona, lembrávamos de bares, mas isso ajudaria por um tempo. Mas estamos fazendo uma coisa interna e a usaremos apenas em condições de laboratório. Ou seja, dentro da estrutura desta tarefa, isso é permitido.

Mas isso traz novos desafios. Temos uma frequência de 60 MHz e o bloco de relógio SDRAM que estamos usando atualmente está intimamente ligado a uma frequência de 50 MHz. Sim, verifiquei, 60 podem ser enviadas, mas vamos fingir que tentamos não ir além dos modos permitidos. Nos artigos subseqüentes, tentarei mostrar como substituir esse bloco rígido, mas hoje dizemos que, como nossa unidade de relógio da SDRAM não pode trabalhar com a frequência usada, a excluímos do sistema do processador SDRAM. O programa e seus dados estarão totalmente localizados na memória interna do FPGA. Foi experimentalmente constatado que, na configuração atual, os FPGAs podem ocupar no máximo 28 kilobytes de RAM para esse negócio. Acontece que você pode pegar volumes e não vários poderes de dois ...

Além disso, usaremos o relógio padrão e a unidade de redefinição. É redefinido um pouco diferente do que usamos para SDRAM. Para não complicar o artigo, aproveitarei o fato de que o sistema em desenvolvimento sempre funcionará sob o controle de um depurador, portanto, iniciarei uma redefinição no subsistema JTAG para depuração.

No total, temos um esboço do sistema do processador básico (a linha de redefinição mais difícil está destacada no momento, o marcador azul está na fonte de sinal):



onde a frequência foi ajustada para o relógio e o bloco de redefinição:



e para RAM - o volume:



Hoje, precisamos exibir o texto no terminal. Portanto, adicionaremos um bloco tão interessante ao sistema:



Com este bloco, poderemos chamar funções semelhantes ao printf. Além do barramento AVALON_MM, ele também deve conectar a saída de solicitação de interrupção.



É isso aí, a compra do sistema do processador está concluída. É hora de incorporar nossa unidade. Para onde ele enviará os dados? Entre os blocos disponíveis, há uma memória FIFO de duas portas muito interessante. Seu charme reside no fato de que uma porta pode ser configurada no modo AVALON-ST e conectá-la à nossa unidade, e a segunda no modo AVALON_MM e trabalhar com ela usando o processador NIOS II. Este maravilhoso bloco está localizado aqui:



Como temos dois ônibus Avalon-ST (um para leitura e outro para escrita), também precisamos de dois blocos FIFO. Agora, examinarei detalhadamente um deles, percorremos alguns quilômetros da Web (e várias telas de texto com imagens) e, no segundo, dizemos que "isso pode ser feito por analogia", indicando apenas diferenças. Portanto, por enquanto, adicionamos apenas um bloco ao sistema e examinamos suas configurações. Existem muitas configurações. Pode-se simplesmente indicar os valores necessários para que todos se refiram ao artigo como referência, mas de repente alguém entra em uma situação que precisa ser configurada, mas não há acesso à rede (e, portanto, ao artigo). Portanto, adicionarei configurações iterativamente. Primeiro óbvio, então - como o sistema exige, execute o diálogo repetidamente. Então todos sentirão o processo e poderão repeti-lo a qualquer momento. Então Por padrão, recebemos as seguintes configurações:



Agora vou fazer o FIFO, que coleta dados do Avalon-ST e os envia para o Avalon-MM . Acontece que a primeira edição será assim:



Eu recebi este aviso interessante:



Acontece que quando pelo menos uma das portas é projetada na memória, a largura do barramento Avalon-ST deve ser estritamente de 32 bits. E nós temos um ônibus de 8 bits. Como concordar com a profundidade dos bits, vou lhe dizer um pouco mais baixo, mas por enquanto estamos fazendo um barramento de 32 bits com um caractere de oito bits aqui. Bem, desative o modo em lote, como foi decidido na parte teórica.



Em seguida é a capacidade. Suponha que eu coloque na fila 256 palavras (ou seja, 1024 bytes):



Agora o status. No começo, eu não atribui nenhuma importância a isso e o programa congela firmemente. Então agora eu sei que o status é necessário. Como trabalharemos com a porta de saída programaticamente, adicionamos o status a ela.



e pegue o erro:



Bem então. Adicione relógio duplo. Basta conectar as duas entradas à mesma linha do relógio, pois temos uma.
Uhhhh Total que temos:



Mas é muito cedo para conectar esta empresa ao sistema comum. Como descobrimos, o barramento Avalon-ST de 8 bits deixa o bloco que desenvolvemos e deve incluir o de 32 bits. Como estamos? Remodelar seu bloco? Não! Tudo foi feito diante de nós. Aqui está o que nos ajudará:



Adicione-o ao sistema. Além disso, como se trata de uma camada puramente de beleza, a colocamos entre o nosso bloco e o FIFO, usando a seta correspondente:



Fazemos as seguintes configurações: na entrada, temos um barramento de 8 bits, na saída de 32 bits. Sinais de pacotes não são usados, sinais prontos e válidos são usados.



É hora de tecer uma web. Primeiro, colocarei as linhas de streaming (na figura elas estão destacadas, os marcadores estão nos receptores de dados):



Ou seja, o sinal da fonte do nosso bloco vai para a entrada do adaptador. E da saída do adaptador para a entrada FIFO. Como eu disse, todas as conexões no protocolo de streaming são feitas ponto a ponto.
Bem, agora suspendemos as linhas de redefinição, as linhas do relógio e também conectamos tudo ao barramento do sistema e às interrupções ...



Bem ... E agora, pelo mesmo princípio, adicionamos FIFO para emitir dados para o FT245SYNC . Somente lá, os dados entram no FIFO do Avalon-MM na forma de 32 bits. Eles passam por um adaptador 32-em-8 e, em seguida, chegam à entrada SINK do nosso bloco, que não está conectado no circuito atual ... Temos o seguinte fragmento do circuito final (a memória acabou com um único relógio):



Outras formalidades que já elaboramos bem nos experimentos descritos em artigos anteriores ( na maioria dos casos - neste ). Atribuímos vetores ao processador. Para o sistema, chamamos a atribuição automática de números e endereços de interrupção. Nós salvamos o sistema ... Todos lembram que o nome do sistema salvo deve corresponder ao nome do projeto para que o sistema esteja no nível superior da hierarquia? Adicione o sistema ao projeto, faça um rascunho do projeto, atribua pernas. Pessoalmente, eu trapacei: copiei as atribuições do arquivo * .qsf do projeto de rascunho para o atual final (e você pode pegar meu projeto e copiar as linhas * .qsf correspondentes às suas, mas você pode simplesmente atribuir todas as pernas por meio da GUI). Presto especial atenção ao fato de que o sinal clk está conectado à perna 23, e não 25, como nos projetos anteriores. Lembro que aqui estamos marcando a saída do FT2232.



Ótimo! O hardware está pronto. Passamos para o software. Por onde começamos? Hoje esta questão não vale a pena. Se começarmos com um programa que roda no processador NIOS II, nada funcionará para nós. Primeiro, devemos colocar o FT2232 no modo 245-SYNC; somente então nosso sistema de processador receberá pulsos de clock. Portanto, começamos com o código do processador central.

Temos algo parecido com isto:
 #include <cstdio> #include <sys/time.h> #include <unistd.h> #include "ftd2xx.h" FT_HANDLE OpenFT2232H() { FT_HANDLE ftHandle0; static FT_DEVICE ftDevice; //      int nDevice = 0; while (true) { //     if (FT_Open(nDevice, &ftHandle0) != FT_OK) { printf("No FT2232 found\n"); //  ,      return 0; } //     ? if (FT_GetDeviceInfo(ftHandle0, &ftDevice, NULL, NULL, NULL, NULL) == FT_OK) { // ,    if (ftDevice == FT_DEVICE_2232H) { // ,     AN130 FT_SetBitMode(ftHandle0, 0xff, 0x00); usleep(1000000); //Sync FIFO mode FT_SetBitMode(ftHandle0, 0xff, 0x40); FT_SetLatencyTimer(ftHandle0, 2); FT_SetUSBParameters(ftHandle0, 0x10000, 0x10000); return ftHandle0; } } //    FT_Close(ftHandle0); //    nDevice += 1; } printf("No FT2232 found\n"); } int main() { FT_HANDLE ftHandle0 = OpenFT2232H(); if (ftHandle0 == 0) { printf("Cannot open device\n"); return -1; } int item; bool bWork = true; while (bWork) { printf("1 - Send 16 bytes\n"); printf("2 - Send 256 bytes\n"); printf("3 - Receive loop\n"); printf("0 - Exit\n"); scanf("%d", &item); switch (item) { case 0: bWork = false; break; case 1: { static const unsigned char data[0x10] = { 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f }; DWORD dwWritten; FT_Write(ftHandle0, (void*)data, sizeof(data), &dwWritten); } break; case 2: { unsigned char data[0x100]; for (size_t i = 0; i < sizeof(data); i++) { data[i] = (unsigned char)i; } DWORD dwWritten; FT_Write(ftHandle0, (void*)data, sizeof(data), &dwWritten); } break; case 3: { DWORD dwRxBytes; DWORD dwRead; DWORD buf[0x100]; while (true) { FT_GetQueueStatus(ftHandle0, &dwRxBytes); if (dwRxBytes != 0) { printf("Received %d bytes (%d DWORDs)\n", dwRxBytes, dwRxBytes / sizeof(buf[0])); if (dwRxBytes > sizeof(buf)) { dwRxBytes = sizeof(buf); } FT_Read(ftHandle0, buf, dwRxBytes, &dwRead); for (DWORD i = 0; i < dwRxBytes / sizeof(buf[0]);i++) { printf("0x%X, ",buf[i]); } printf("\n"); } } } break; } } // ,    FT_Close(ftHandle0); return 0; } 


A função OpenFT2232H () nos é familiar desde o último artigo . É ela quem abre o dispositivo FT2232 e o coloca no modo que precisamos. Imediatamente após o lançamento bem-sucedido do programa, obtemos pulsos de clock e, com eles, a capacidade de depurar o programa para o NIOS II. Bem, a funcionalidade da função principal é tão simples quanto um banquinho. Envie alguns dados (1), envie muitos dados (2), receba dados (3). Observe que todos os dados são enviados em blocos com múltiplos de quatro bytes. Isso é tudo porque temos um adaptador 8 em 32. Na saída, os dados devem ir em duas palavras. Caso contrário, tudo é óbvio.

Ao desenvolver um programa para o NIOS II, você deve primeiro configurar o BSP. Lembro que eu criei o próprio programa de acordo com o modelo Hello World Small. Os campos alterados no BSP estão marcados em vermelho na figura abaixo (como o programa é criado a partir do modelo e como o BSP é corrigido foi discutido em detalhes em um dos artigos anteriores ). Deixe-me lembrá-lo de que seleciono a raiz da árvore, ou seja, o elemento Configurações, para que todas as configurações fiquem imediatamente visíveis à direita.



Em seguida, gere BSP e, em virtude do meu hábito, altero o nome do arquivo hello_world_small.c para hello_world_small.cpp , após o qual limpo o projeto para que não haja erros induzidos nessa renomeação.

Realizo uma verificação do trabalho de maneira bastante superficial (um testador real certamente testaria minuciosamente a transferência de grandes quantidades de dados que excedem o tamanho do FIFO, mas o objetivo do artigo é mostrar os princípios básicos e não garantir que ele seja descartado devido ao seu tamanho insano). E mostrarei os princípios básicos em duas etapas. O primeiro passo é verificar a transferência de dados do processador central para o NIOS II. Para isso, desenvolvi o seguinte código:

 extern "C" { #include "sys/alt_stdio.h" #include <system.h> #include <altera_avalon_fifo_util.h> } #include <stdint.h> int main() { while (1) { int level = IORD_ALTERA_AVALON_FIFO_LEVEL(FIFO_0_OUT_CSR_BASE); if (level != 0) { alt_printf("0x%x words received:\n",level); for (int i=0;i<level;i++) { alt_printf("0x%x,",IORD_ALTERA_AVALON_FIFO_DATA (FIFO_0_OUT_BASE)); } alt_printf("\n"); } } /* Event loop never exits. */ while (1); return 0; } 

Este programa está aguardando a exibição dos dados no FIFO. Se eles aparecerem lá, os exibe.
Chegando ao teste. Primeiro, vou fingir que esqueci de começar o tempo. Portanto, depois de ativar o Redd, carrego o “firmware” do FPGA e tento executar um programa de depuração para o NIOS II. Eu recebo esta mensagem:



Se você tem o mesmo, significa que realmente esqueceu de começar a cronometrar o sistema do processador. Mas agora você sabe como identificá-lo rapidamente. E para eliminar, é necessário e suficiente executar o programa que escrevemos para o processador central. Assim que ele inicia e inicializa a ponte FT2232, os pulsos de clock vão para o nosso processador e será possível repetir o processo de iniciar a depuração. Além disso, o programa para o processador central a essa altura pode ser concluído. Os pulsos do relógio não vão a lugar algum: a ponte já está configurada para o modo FT245-SYNC .

No programa do processador central, pressione 1. Dependendo da situação, aparece no terminal:

0x2 palavras recebidas:
0x3020100,0x7060504,
0x2 palavras recebidas:
0xb0a0908,0xf0e0d0c,


ou:

0x3 palavras recebidas:
0x3020100,0x7060504,0xb0a0908,
0x1 palavras recebidas:
0xf0e0d0c,


Em princípio, pode haver 1, depois 3 palavras duplas, mas isso não me ocorreu. Tudo depende de quantos bytes têm tempo de execução no barramento antes do início da primeira exibição. E, se iniciado, no final, todos os outros bytes terão tempo de execução, pois a transferência de dados via JTAG não é um processo rápido. Se o barramento usasse sinais de pacotes, o programa seria capaz de ver os dados somente após a conclusão da recepção de pacotes. Em alguns casos, é bom (ainda não há pacote, por que devo vê-lo? Especialmente se o pacote é transitório), em alguns é ruim (FIFO é uma caixa preta, para o processamento final, os dados devem ser copiados para a RAM endereçável, e isso é melhor em paralelo com o recebimento de dados )

Os dados transmitidos são colocados em palavras duplas na notação Little Endian. Deixe-me lembrá-lo de que a seguinte matriz é passada:

 static const unsigned char data[0x10] = { 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f }; 

Isso mesmo. Se você selecionar o item 2 do programa para o processador central, uma mensagem será exibida (para facilitar a leitura, as linhas serão formatadas ao preparar o artigo):

 0x3 words received: 0x3020100,0x7060504,0xb0a0908, 0x3d words received: 0xf0e0d0c, 0x13121110,0x17161514,0x1b1a1918,0x1f1e1d1c, 0x23222120,0x27262524,0x2b2a2928,0x2f2e2d2c, 0x33323130,0x37363534,0x3b3a3938,0x3f3e3d3c, 0x43424140,0x47464544,0x4b4a4948,0x4f4e4d4c, 0x53525150,0x57565554,0x5b5a5958,0x5f5e5d5c, 0x63626160,0x67666564,0x6b6a6968,0x6f6e6d6c, 0x73727170,0x77767574,0x7b7a7978,0x7f7e7d7c, 0x83828180,0x87868584,0x8b8a8988,0x8f8e8d8c, 0x93929190,0x97969594,0x9b9a9998,0x9f9e9d9c, 0xa3a2a1a0,0xa7a6a5a4,0xabaaa9a8,0xafaeadac, 0xb3b2b1b0,0xb7b6b5b4,0xbbbab9b8,0xbfbebdbc, 0xc3c2c1c0,0xc7c6c5c4,0xcbcac9c8,0xcfcecdcc, 0xd3d2d1d0,0xd7d6d5d4,0xdbdad9d8,0xdfdedddc, 0xe3e2e1e0,0xe7e6e5e4,0xebeae9e8,0xefeeedec, 0xf3f2f1f0,0xf7f6f5f4,0xfbfaf9f8,0xfffefdfc, 

Tudo também é verdade. Prosseguimos para verificar a marcha à ré. Substituímos o programa do NIOS II por este:

  /*  -  2 */ uint32_t buf[] = {0x11223344,0x55667788,0x99aabbcc,0xddeeff00}; for (uint32_t i=0;i<sizeof(buf)/sizeof(buf[0]);i++) { IOWR_ALTERA_AVALON_FIFO_DATA (FIFO_1_IN_BASE,buf[i]); } 

Selecionamos o ponto 3 do programa para o processador central e executamos esta versão do programa para o NIOS II. Recebemos:

Recebido 16 bytes (4 DWORDs)

0x11223344, 0x55667788, 0x99AABBCC, 0xDDEEFF00,


Ambos os canais são difíceis. E vamos dar uma olhada em outra hora de alguma forma.

Conclusão


Este artigo discute os conceitos básicos do protocolo de streaming de barramento Avalon-ST . Através deste protocolo, a conexão do processador central Redd com o sistema do processador implementado no FPGA é organizada. Os leitores tiveram uma idéia do método mais simples de interação entre os processadores central e auxiliar. Faça o download dos projetos criados durante o processo de desenvolvimento aqui .

No entanto, o conhecimento adquirido sobre protocolos de streaming e seu uso é muito básico. Nos artigos subsequentes, será mostrado como, por meio desses protocolos, salvar com eficiência dados na RAM dinâmica localizada na placa Redd.

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


All Articles