Fluxo de projeto para uma placa personalizada FPGA em Vivado e PetaLinux

Whitney Knitter Segue 15 de jul · 17 min ler

Um pouco atrás, uma placa FPGA de fator de forma Raspberry Pi chamada Zynqberry chamou minha atenção e eu passei algum tempo com ela para trazê-la como uma ferramenta pronta para uso em meu arsenal de placas de desenvolvimento. Eu rapidamente descobri que precisava de uma imagem Linux incorporada para utilizar a porta Ethernet e quatro portas USB que levaram ao post anterior aqui . Eu tenho sido feliz com isso desde então, mas com todo o hype do novo Raspberry Pi 4 eu fui lembrado que eu precisava pegá-lo de volta e terminar a construção do dispositivo para o resto dos periféricos, como a porta HDMI, Isso me levou a criar um esboço do meu fluxo de projeto para o desenvolvimento de FPGA do início ao fim, tudo em um só lugar. Agora, enquanto esse fluxo de design específico é baseado em Xilinx, descobri que as ideias principais podem ser aplicadas a outros chipsets em outros IDEs para ajudar a ajustar o novo ambiente mais rapidamente (por exemplo – veja minha primeira tentativa de usar o FPGA da Lattice Semiconductor) e IDE pela primeira vez ).

Passo 1 – Crie um projeto base com qualquer IP pré-construído e processador desejado (opcional se o design for puramente HDL personalizado)

No Vivado, há uma tonelada de blocos IP (propriedade intelectual) pré-embalados para cobrir uma tonelada de funcionalidades básicas para você utilizar, de modo que você possa se concentrar mais nas partes personalizadas de seu design, em vez de reinventar a roda sobre e de novo em coisas como drivers UART, interfaces SPI, etc. O design do bloco também é onde você irá instanciar seus processadores flexíveis ou hard, como o MicroBlaze (processador soft proprietário da Xilinx) ou processadores ARM (se você estiver usando um dos Xilinx's Chips Zynq). Agora, esta etapa é opcional se seu design for puramente seu próprio HDL personalizado. O repositório IP interno da Xilinx também está disponível no nível HDL, mas eu realmente recomendo usá-lo no projeto de bloco se você for usá-lo, já que o editor de design de bloco do Vivado oferece assistência de design automatizada em blocos de fiação juntos quando você os solta em.

Para criar um design de bloco em um projeto, basta selecionar a opção 'Criar Design de Bloco' na guia IP Integrator no menu Project Manager. Depois disso, é arrastar e soltar!

Outra coisa interessante sobre o design do bloco no Vivado é que você pode empacotar um projeto inteiro em seu próprio bloco de IP e colocá-lo em um repositório local para usar em outros projetos. Depois de examinar a página de suporte da Trenz Electronic para o Zynqberry , descobri que eles haviam criado um pouco de seu IP personalizado para o Zynqberry. Depois de baixar a biblioteca IP do site da Trenz, adicionei-a ao meu projeto no Gerenciador de Projetos em 'Configurações' ? 'IP' ? 'Repositório' ? 'Adicionar Repo'. Isso agora me permite executar o script TCL de Trenz para recriar o design do bloco do Zynqberry contendo esses blocos IP personalizados.

Para recriar um design de bloco de um script TCL como este, não crie manualmente um novo design de bloco. Em vez disso, direto do console do TCL em um projeto vazio, execute o seguinte comando:

source <caminho do arquivo para o script TCL> /yourBlockDesign.tcl

Quando o design do bloco estiver concluído, seja criado manualmente ou recriado de um script TCL, eu executo a validação no design clicando no botão "verificar" na barra de menu superior. Depois que o design passar pela validação, salve e feche o design do bloco.

Etapa 2 – Adicionar HDL personalizado e instanciar no design base

Para que o design do bloco e o HDL interfiram, é necessário um invólucro HDL de nível superior. Este wrapper de nível superior irá instanciar uma instância do design do bloco que, em seguida, o torna disponível para qualquer outra instância dos módulos HDL. É importante lembrar que os únicos sinais que estarão diretamente disponíveis no wrapper a partir do design do bloco são aqueles sinais que foram feitos externamente, trazendo-os para um pino no design do bloco.

Para criar um wrapper de nível superior, clique com o botão direito do mouse no design do bloco na guia Origens e selecione a opção 'Criar HDL Wrapper…'.

Há duas opções ao criar um novo wrapper HDL: permitir que o Vivado o gerencie e atualize automaticamente ou configure-o manualmente conforme desejado. Esta opção é relevante para se / quando o design do bloco precisar ser atualizado mais tarde. O método mais à prova de erro que eu pessoalmente encontrei para fazer isto é que eu selecionei a opção para permitir que o Vivado gerencie o wrapper HDL, então crie meu próprio módulo no Project Manager selecionando 'Add Sources' ? 'Add or Create Design' Source 'e eu simplesmente copio + colo a instanciação do arquivo wrapper gerado automaticamente no meu. Em seguida, defino meu próprio arquivo como o novo arquivo de nível superior para o design e desabilito (NÃO elimino) o wrapper criado automaticamente pelo Vivado. Se eu precisar atualizar o design do bloco, eu reativo o wrapper HDL criado automaticamente e o coloco de volta como o topo antes de entrar no design do bloco para modificá-lo. Essa maneira super complicada e indireta de fazer isso é hiperespecífica para o IDE do Vivado. Há tanta coisa acontecendo em segundo plano com cada ação na GUI, eu aprendi que é melhor minimizar as interrupções para esses processos, tanto quanto possível e esta é a melhor maneira que eu encontrei para conseguir isso para o design do bloco.

Uma vez que o wrapper HDL de nível superior está no lugar, eu escrevo meu Verilog / VHDL como normal e instanciando-o conforme necessário no meu design. Para este projeto no Zynqberry, deixei o wrapper criado automaticamente como o módulo de nível superior, já que não escrevi nenhum HDL personalizado para ele. Todo o design está contido no design do bloco.

Etapa 3 – Criar arquivo de restrições

Existem algumas maneiras diferentes de fazer isso, dependendo se estou criando um novo arquivo de restrições ou importando um arquivo de restrições existente. Se estou criando novas restrições, executo a síntese e uso a GUI do IO Planning no design sintetizado para rotear meus sinais para os pinos. Se eu estou importando restrições existentes como eu sou para o Zynqberry, eu vou para o Project Manager, selecione 'Add Sources' ? 'Add ou Create Constraints' e importe o arquivo existente. No Vivado, ele trata tudo o que é importado para a pasta 'Constraints' como um todo. Isso significa que você não precisa colocar tudo em um único arquivo, já que as restrições podem ficar muito longas para alguns projetos. Eu pessoalmente gosto de dividi-lo por ter um arquivo para minhas criações de tempo / restrições de tempo, então eu coloquei todos os meus pinouts em um segundo arquivo. Quando você tem vários arquivos em seu conjunto de restrições, é necessário especificar um como seu arquivo de restrições de 'destino' (se houver apenas um, o Vivado o define como o destino por padrão). Eu configurei meu arquivo de restrições de criação de tempo / relógio como meu destino (clique com o botão direito do mouse no arquivo ? 'Definir como Arquivo de Restrições de Destino').

Quando baixei as restrições definidas para o Zynqberry da Trenz , notei que elas tinham feito algo semelhante e também tinham feito um arquivo separado para cada periférico. Eu poderia começar a fazer o mesmo no futuro para designs maiores, pois isso tornava tudo muito legível.

Etapa 4 – Executar síntese / implementação

Uma vez que as restrições tenham sido definidas, a síntese e a implementação precisam ser executadas para construir o projeto e rotea-lo através do chip alvo. Este passo é muito de assistir e esperar. Se houver algum erro ou aviso crítico, o código de erro de saída resultante e o Google são seus melhores amigos.

Passo 5 – Ajuste o tempo do projeto, se necessário

Depois que o design é roteado na implementação, a configuração e o tempo de espera de todos os caminhos dentro da malha do FPGA são calculados. Se o projeto não atender às restrições de tempo adequadas, um aviso crítico será gerado após a conclusão da implementação. Há uma infinidade de técnicas para corrigir problemas de tempo, dependendo da causa raiz. Em Vivado especificamente, você pode encontrar uma tonelada de recursos para saber como corrigir problemas de tempo no DocNav se você pesquisar ' Timing Closure & Design Analysis '.

Etapa 6 – Gerar um fluxo de bits

Depois que os erros e avisos críticos forem resolvidos, o design estará pronto para ser empacotado em um fluxo de bits para exportar para o SDK. Se você encontrar algum erro aqui, isso geralmente ocorre devido a especificadores de nível de voltagem incorretos ou a alguma outra incompatibilidade nas suas restrições de pinagem. Não ignore nenhum aviso crítico, especialmente aqui, porque ele voltará a te morder no comportamento esperado mais tarde.

Etapa 7 – Exportar para o SDK

Agora que o design do hardware está completo e verificado, a próxima etapa é exportá-lo para o SDK, onde o software incorporado apropriado pode ser adicionado ao design. Para fazer isso vá em Arquivo ? Exportar Hardware ? marque a caixa para incluir o fluxo de bits e deixe o local como <Local para Projeto>. Tecnicamente, você pode exportar sua definição de hardware e criar o espaço de trabalho do SDK onde quiser, mas como mencionei anteriormente, a maneira mais à prova de erros é permitir que o Vivado coloque as coisas onde quiser. Para iniciar o SDK após exportar o design de hardware, vá para Arquivo ? Iniciar SDK ? e, novamente, deixe as opções de local como <Local para Projeto>.

Etapa 8 – Crie aplicativos bare metal conforme necessário

Embora eu esteja finalmente usando o PetaLinux para criar uma imagem Linux incorporada para o Zynqberry, o primeiro estágio de bootloader para iniciar o kernel Linux é um aplicativo bare-metal que é criado no SDK. O Zynqberry precisa de dois bootloaders de primeiro estágio: o FSBL normal para armazenar em memória flash e lançar o kernel Linux localizado no cartão SD, e um segundo ZSBL especial para trazer o Zynqberry via JTAG para nos permitir programar o FSBL normal memória flash. Esse JTAG FSBL também é usado pelo depurador do sistema no SDK se estiver depurando um aplicativo bare-metal (que é feito via JTAG). Eu cobri os detalhes para criar estes dois FSBLs para o Zynqberry no passado aqui .

Etapa 9 – Criar projeto PetaLinux e importar definição de hardware de design

Ao criar um novo projeto PetaLinux, eu pessoalmente gostaria de criá-lo na mesma pasta que o meu projeto Vivado, então vou mudar os diretórios para essa pasta antes de executar o seguinte comando para criar o projeto.

 petalinux-create --tipo de projeto --template zynq --name <nome do projeto> 

O PetaLinux irá criar uma pasta de nível superior com o mesmo nome que o nome do projeto que você passa com a opção '-name' para colocar o projeto. Mude os diretórios para esta pasta do projeto antes de executar qualquer um dos comandos de configuração do projeto.

 cd ./< nome do projeto> 

Etapa 10 – Definir configurações de hardware, kernel do Linux e sistema de arquivos raiz

As configurações de hardware são onde o kernel é configurado para inicializar a partir do cartão SD para este design. O comando PetaLinux a seguir importa a descrição de hardware do design do SDK e inicia uma GUI para definir as configurações de hardware. Todas essas configurações são as mesmas entre este build completo do Zynqberry e meu post anterior, onde eu estava focado apenas na interface com a porta Ethernet, que você pode encontrar aqui na etapa 2.

 petalinux-config --get-hw-description <caminho para o arquivo .hdf no SDK> 

Uma vez que as configurações de hardware estão configuradas (estas configurações estão apenas fazendo PetaLinux ciente do hardware que você já escolheu, por isso certifique-se de suas configurações fazem sentido com o que você projetou no Vivado) a próxima coisa a fazer é configurar o kernel. O kernel do Linux é responsável por iniciar e gerenciar os processos de recursos e aplicativos de periféricos que o sistema operacional está usando. De certo modo, o kernel é basicamente a cola entre o sistema operacional e o hardware.

 kernel do petalinux-config -c 

O kernel agora precisa saber qual hardware está disponível para ele, como os PHYs USB e Ethernet, o driver de som (ALSA), os gráficos para a porta HDMI e os drivers I2C para cada um deles. Novamente, o comando de configuração PetaLinux acima iniciará uma GUI para fazer essas seleções. O Zynqberry requer a seguinte configuração:

Finalmente, o sistema de arquivos raiz para o Zynqberry precisa apenas de algumas coisas ativadas especificamente em torno da utilização da Arquitetura Avançada de Som do Linux (ALSA) e da biblioteca de pacotes I2C.

 petalinux-config -c rootfs 

Etapa 11 – Configurar a árvore de dispositivos personalizados

Ao observar esta árvore de dispositivos e os comentários correspondentes, você pode começar a ter uma ideia de como funciona uma árvore de dispositivos. Cada periférico na placa tem seu próprio nó definindo seus endereços de registro e, em alguns casos, qual driver irá vinculá-lo ao sistema de arquivos raiz.

Passo 12 – Construir

Com tudo configurado e a árvore de dispositivos construída, o projeto precisa ser construído, o que chamará todos os vários compiladores necessários para criar os arquivos de saída finais para a imagem do kernel e o sistema de arquivos raiz.

 petalinux-build 

Passo 13 – Criar arquivo de imagem de inicialização

Como o Zynqberry será inicializado a partir de um cartão SD, as únicas coisas que precisam ser empacotadas no arquivo de imagem de inicialização são o carregador de inicialização normal do primeiro estágio (não o JTAG), bitstream FPGA e u-boot. Se a placa estivesse sendo inicializada a partir de algum tipo de memória on-board, esse arquivo de imagem de inicialização também incluiria o kernel, a árvore de dispositivos e o sistema de arquivos raiz.

 petalinux-package --boot --fsbl <caminho para o normal fsbl.elf> --fpga <caminho para o arquivo bitstream> --u-boot 

Etapa 14 – Carregar o arquivo de imagem de inicialização na memória flash com o SDK

O arquivo de imagem de inicialização ficará na memória flash on-board do Zynqberry, que pode ser carregado usando o SDK usando a memória flash de programa . É aqui que o JTAG FSBL especial entra para trazer o Zynqberry inicialmente para poder programar a memória flash QSPI.

O comando de empacotamento da etapa anterior será enviado para o BOOT.bin para <project path> / images / linux .

Passo 14 – Prepare e carregue o cartão SD com o kernel Linux e o sistema de arquivos raiz

Agora que o arquivo de imagem de inicialização é carregado na memória flash do FPGA para apontar para o restante do processo de inicialização para o cartão SD, o cartão SD precisa ser preparado de acordo. Os arquivos que viverão no cartão SD exigem dois formatos diferentes de sistema de arquivos. A árvore de dispositivos e a imagem do kernel funcionam melhor em um formato fat32, já que o fat32 é tão amplamente compatível com vários sistemas operacionais. O sistema de arquivos raiz, no entanto, precisa do melhor desempenho e confiabilidade de um formato como o mais recente e melhor sistema de arquivos estendido, o ext4.

Eu pessoalmente gosto de usar o GParted para formatar meus cartões SD, e também gosto de deixar alguns MB de espaço não alocado antes da primeira partição. Isso aconteceu nos muitos Raspberry Pis e incontáveis imagens Pi que eu mostrei, quando eu estava tendo problemas com a imagem corrompida rapidamente e precisando reformatar / reflash o cartão SD regularmente. Depois de muita pesquisa no Google, e trilha e erro, descobri que sair de 2 MB – 8 MB de espaço não alocado à frente da primeira partição em um cartão SD levava a uma estabilidade muito melhor. Eu encontrei uma defesa doce e doce disso quando encontrei na UG1144 (a documentação oficial da Xilinx para a PetaLiunx) que a Xilinx recomenda deixar 4 MB de espaço não alocado antes da primeira partição em um cartão SD.

A primeira partição é a fat32 onde a imagem do kernel e a árvore de dispositivos que ela fará referência viverão (o UG1144 recomenda um mínimo de 60MB), e a segunda partição é a ext4 para o sistema de arquivos raiz. Pelo menos 4 GB são recomendados para o sistema de arquivos raiz básico que o PetaLinux irá gerar, mas é importante ter em mente qual será o uso final aqui. Dependendo do número e do tipo de aplicativos personalizados a serem desenvolvidos, a memória geral necessária aumentará.

Depois que o cartão SD tiver sido formatado, crie um diretório para montá-lo e montá-lo.

 sudo mkdir / media / BOOT 
sudo mkdir / media / rootfs
sudo mount / dev / <nome do dispositivo da partição 1> / media / BOOT
sudo mount / dev / <nome do dispositivo da partição 2> / media / rootfs

Copie a imagem do kernel e a árvore de dispositivos para o diretório BOOT:

 sudo cp <caminho para o projeto> /images/linux/image.ub / media / BOOT 
sudo cp <caminho para o projeto> /images/linux/system.dtb / media / BOOT

Exatamente o sistema de arquivos raiz no diretório rootfs:

 sudo tar xvf <caminho para o projeto> /images/linux/rootfs.tar.gz -C / media / rootfs 

E finalmente desmontar o cartão SD:

 sudo umount / media / BOOT 
sudo umount / media / rootfs

Etapa 15 – Configurar o u-boot

O Universal Boot Loader, u-boot, é o principal gerenciador de inicialização para Linux que o Zynq FSBL (o aplicativo bare-metal criado no SDK) inicia na seqüência geral de inicialização. Para este design em particular, uma vez que o Zynqberry está sendo inicializado a partir do cartão SD, o u-boot precisa ter o número do bloco do cartão SD configurado para onde carregar a árvore de dispositivos e o kernel. Em seguida, também informaremos ao u-boot para carregar primeiro o kernel, seguido pela árvore de dispositivos do kernel para indexar o hardware e continuar com a inicialização.

Para entrar no ambiente do editor de inicialização, você tem que pegá-lo no momento certo na seqüência de inicialização, pois você só pode entrar no editor de inicialização durante o tempo em que o u-boot é o componente ativo na seqüência de inicialização. . No Zynqberry, descobri que isso acontecia depois que o LED de status vermelho passava da intermitência rápida após a alimentação inicial até piscar mais lentamente, mas mesmo assim ainda há apenas uma janela limitada antes que o kernel assuma e conclua a sequência de inicialização. Isso levou muita tentativa e erro para eu descobrir, e foi de longe onde eu fiquei mais tempo na primeira vez que fiz isso.

O ambiente do editor u-boot é acessado através de uma porta COM, então é a primeira coisa que cria uma porta COM na seqüência de inicialização. Durante meu teste e erro no Zynqberry, eu consegui correlacionar a inicialização do u-boot com a taxa de intermitência do LED de status vermelho com base em quão cedo consegui fazer com que Putty se conectasse à porta COM do Zynqberry após aplicar energia.

Uma vez no editor de inicialização, o comando setenv é usado para modificar as configurações necessárias. Então eu ecoo de volta todas as configurações atuais com printenv para verificar tudo antes de salvar com saveenv .

 console de setenv bootargs = ttyPS0,115200 raiz de earlyprintk = / dev / mmcblk0p2 rootfstype = ext4 ru rootwait ' setenv cp_dtb2ram' carga de gordura mmc 0 $ {dtbnetstart} $ {dtb_img} ' 
setenv cp_kernel2ram 'fatload mmc 0 $ {netstart} $ {kernel_img}'
setenv default_bootcmd 'execute cp_kernel2ram && cp_dtb2ram && bootm $ {netstart} - $ {dtbnetstart}'
printenv saveenv

Antes de editar essas configurações, o número do bloco do dispositivo (cartão SD) precisa ser verificado. O comando 'mmclist' listará todos os dispositivos disponíveis que o u-boot visualizar. Os comandos do ambiente set acima de tudo assumem um nó de dispositivo de 0, mas se o número do nó do cartão SD for diferente, os argumentos de inicialização do u-boot e o comando boot precisarão ser alterados para refletir isso, assim como o kernel também precisará ser reconfigurado para procurar o número de nó do dispositivo correto.

Etapa 16 – Inicialização Final

Com tudo configurado e pronto para ser usado, a seqüência de inicialização pode ser comandada para continuar no ambiente do editor de inicialização:

 bota 

Se tudo correr bem, será apresentado um prompt de login. Por padrão, o nome de usuário e senha nas imagens PetaLinux são ambos 'root'. E este Zynqberry está pronto para ir!

Como sempre, criei um repositório do GitHub com os arquivos de design específicos para o meu projeto que você pode encontrar aqui !