terça-feira, 3 de novembro de 2009

Ataque Cilônio!

No último post, nós vimos como funciona uma FPGA. Agora é hora de aprender a usá-la na prática, e o melhor jeito é fazendo um pequeno projetinho. Como exemplo de projetinho, eu pensei que poderíamos construir um Cilônio a partir do zero:



Para começar a desenvolver para FPGAs, a primeira coisa de que você precisa é, obviamente, uma FPGA. Se você for o tipo de ninja que consegue soldar SMD na mão, pode fazer sua própria placa de desenvolvimento. Eu, como sou inepto com ferro de solda, optei por comprar um kit de desenvolvimento na Digilent. O kit que escolhi foi o S3EBOARD, que tem o menor custo/benefício:



Essa placa custa 150 dólares. Se você comprar do Brasil, somando o frete e impostos, pode ficar meio salgado, mas no final vale a pena. Uma FPGA sozinha não serve pra muita coisa, a coisa só fica divertida quando você a usa pra controlar algum dispositivo. E essa placa da Digilent tem todos os dispositivos que você precisa! Ela tem RAM, conector VGA, PS/2 pra teclado, serial pra mouse, conector Ethernet, DACs pra fazer saída de som, enfim, com ela você pode fazer um computador completo.

Em especial, no cantinho da placa tem uma série de 8 LEDs. Com isso, já podemos escolher por onde vamos começar a fazer o nosso Cilônio. Já que temos LEDs enfileirados, vamos começar pelo visor!



Já temos uma placa com a FPGA, precisamos agora de um compilador para programá-la. Eu uso o Xilinx ISE Webpack, que é free (as in beer), e disponível para Windows e Linux. Compiladores de FPGA são análogos aos compiladores de software, até mesmo na idéia de compilar em etapas. Um compilador típico de C tem estágios de pré-processamento, compilação, otimização e linking. Um compilador para FPGA possui as seguintes etapas:

Síntese: Tudo começa com a sua descrição do projeto, feita em alguma linguagem de descrição de hardware. Para começar o processo, o compilador traduz essa linguagem para uma representação RTL, que é basicamente um sistema de equações booleanas. Essas equações são então simplificadas, de modo a eliminar redundâncias e deixar o circuito final mais rápido.


Circuito original, e circuito após otimização

Mapeamento tecnológico: Mesmo que o otimizador tenha chegado nas equações booleanas mínimas, elas ainda não podem ser usadas na FPGA. Nós sabemos que a FPGA é formada de blocos lógicos (os CLBs), então precisamos de um processo para mapear as equações nos CLBs. Note que a etapa de síntese não depende da arquitetura, mas na etapa de map o compilador precisa saber o modelo exato da FPGA sendo usada, já que cada FPGA tem um tipo de CLB diferente.


Mapeamento tecnológico, cada CLB tem um AND e um OR

Place and Route: Depois de feito o mapeamento tecnológico, o compilador sabe quantas CLBs ele precisa, e como preencher cada uma delas. Mas ele ainda não escolheu quais CLBs serão usadas. Apesar das CLBs serem todas iguais, se você utilizar CLBs muito distantes entre si, o tempo que o sinal leva de uma a outra pode influir na velocidade do seu circuito. Na etapa de place and route, o compilador escolhe a melhor disposição de CLBs para o seu design.


Antes e depois do place and route

Geração da bitstream: Por fim, o compilador gera uma bitstream que é usada pra programar a FPGA, usualmente através de uma interface JTAG. A partir desse ponto, sua FPGA está programada e pronta pra rodar :)



Agora precisamos escolher a linguagem que vamos usar. O Xilinx Webpack suporta VHDL e Verilog. Escolher uma das duas é como escolher vi ou emacs, existem fãs dos dois lados, mas na prática são quase equivalentes. Em geral, para quem está começando, eu recomendo não pensar muito e escolher aquela que seus amigos usam (assim você tem pra quem perguntar quando tiver dúvidas). Eu acabei escolhendo usar VHDL.

A origem da linguagem VHDL é curiosa. O departamento de defesa americano fazia muitos contratos de fabricação de chips, mas a documentação que vinha de cada fabricante era diferente. Eles então bolaram um padrão, o VHDL, que no começo era simplesmente um jeito de documentar circuitos. Em um certo ponto, notaram que a documentação era precisa o suficiente pra permitir simulação, e da simulação partiram para a síntese.

Mas vamos ao nosso projeto. A primeira parte de um design em VHDL, assim como em faríamos em software, são os imports:



O motivo de incluir essas bibliotecas é que sem elas não podemos usar bits em hardware. Bits em software são simples, eles podem ser 0 ou 1. Já em hardware, temos muito mais opções. Além do "0" (pino ligado no GND) e do "1" (pino ligado no Vcc), também temos o "Z" (pino não conectado), "H" (pino ligado no Vcc através de um pull-up), e assim por diante. Em VHDL, bits de hardware são chamados de std_logic.


Alguns estados do std_logic

Em seguida nós precisamos definir a entidade que vamos fazer. Uma entidade em VHDL é como uma interface em Java, ela define como seu código interage com o mundo. Em hardware, isso é equivalente a definir quem serão os pinos de entrada e saída do seu design. No nosso projeto, nós precisamos de duas entradas: um clock e um botão de liga-desliga; e oito saídas: uma para cada LED.



Agora vamos definir a implementação da interface, que em VHDL é o behaviour. No nosso projeto, vamos usar um clock de 50MHz, rápido demais para fazer a animação dos LEDs. Precisamos então dividir essa freqüência, pra isso vamos usar um contador que reseta a cada 5 milhões de ciclos. Cada vez que o contador resetar, nós incrementamos um estado da nossa máquina de estados, e, por fim, vamos associar cada estado a uma combinação de LEDs acesos e apagados.

Se fosse software, precisaríamos de três variáveis pra implementar esse algoritmo. Em hardware, não usamos variáveis, mas sim registradores. Uma parte curiosa do VHDL é que você não define os registradores diretamente; ao invés disso, você define os sinais e ele infere quais registradores você precisa. Como você deve imaginar, num projeto grande isso é fonte de inúmeros bugs! Felizmente, nesse projetinho não vamos ter esse problema.



Note que ao especificar um registrador, você precisa falar o tamanho dele. Nosso contador de clocks, por exemplo, vai de 0 a 5 milhões, e portanto cabe num registro de 23 bits. Vamos aproveitar e implementar esse contador. A diferença mais importante de hardware e software é que no hardware você tem paralelismo real, não é time-sharing e nem tem limite no número de cores. Em VHDL, cada unidade que roda em paralelo é um processo:



Na primeira linha de cada processo você define quem são as entradas (nesse caso, o clock e o valor anterior do contador). O primeiro if checa se aconteceu uma mudança no clock que o levou para o estado 1 (ou seja, o código dentro do if só roda em uma borda de subida). O resto é como em software, se chegar a 5M, volte a zero, senão incremente.

O processo seguinte faz o mesmo com o contador do estado atual:



O contador de estado incrementa na borda de subida, mas só quando o contador de clock é zero. Além disso, nós também zeramos o estado quando a chave SW0 está desligada (implementando assim um botão que liga e desliga).

Agora precisamos associar cada estado a uma combinação de LEDs (o que, em hardware, chamamos de demultiplexação):



Note que além de definir todos os estados possível, ainda temos um else extra. Precisamos sempre lembrar que, em hardware, bits não são apenas 0 ou 1! Se o estado cair em algum dos outros valores, como Z ou H, precisamos do else extra para cuidar dele.

Por fim, precisamos ligar o demultiplexador nos pinos de saída. Ligações simples de sinais nem precisam de um processo:



Agora o VHDL está pronto, mas ainda falta um detalhe para acabar o projeto. O VHDL cuida de tudo que vai acontecer dentro da FPGA, mas ainda precisamos cuidar do que está fora dela. Pra isso, precisamos de um constraint file, que vai indicar em quais pinos da FPGA estão ligados nossos periféricos (os LEDs, o clock, etc). Esses valores nós pegamos diretamente do manual do kit da Digilent. Nossos arquivos ficaram assim, então:

Visor cilônio em VHDL
Visor cilônio (constraint file, no formato UCF)

Com tudo pronto, só precisamos compilar e fazer o upload para a FPGA :)







Agora temos o visor pronto! Só falta terminar o resto do Cilônio, que fica como exercício para o leitor :)

32 comentários:

  1. Vai ter gente querendo o Cilônio inteiro, cê vai ver só! ;-)

    ResponderExcluir
  2. Foi divertido ler o artigo e relembrar das aulas de Eletrônica Digital que recebi do Prof. Furukawa. E relembrar o quão poderoso é o Mapa de Karnaugh

    ResponderExcluir
  3. Legal, ricbit! Como você fez para importar essa belezinha?

    ResponderExcluir
  4. Eu comprei a minha na Califórnia :)

    ResponderExcluir
  5. Caramba Ricbit, sou seu fã.

    Você consegue tornar emocionante
    ficar olhando para oito leds por 48
    segundos. :)

    Tudo que você faz tem um toque de
    capricho extra, é um exemplo,
    parabéns.

    ResponderExcluir
  6. O que é mais fácil, soldar SMD na mão ou terminar o Cilônio?

    ResponderExcluir
  7. Cesar, depende de quantos braços você tem!

    ResponderExcluir
  8. Oi, acho que o desenho que tem a seguinte legenda "Alguns estados do std_logic" ficou invertido a representacao do nivel lógico alte e baixo, não?

    Bom, fora isso estou achando mto bem feito todas as explicações. Bem didáticas inclusive. Ótimo pra aprender e pra incentivar as pessoas a aprender e gerar coisas novas.

    Bom, era isso, abraço!

    ResponderExcluir
  9. Nó, estava trocado mesmo, vou atualizar a figura.

    Obrigado :)

    ResponderExcluir
  10. Cylon.

    Não existe esse tal de Cilônio.

    Cylon: Cybernetic Lifeform Node

    Cilônio: ???????????????????

    Se for traduzir, traduza o significado. Ficando algo mais ou menos: Centro de vida cibernética, Node de vida cibernética, Forma de vida cibernética, etc, etc, etc.

    Mas por favor, tire esse Cilônio da cabeça.

    ResponderExcluir
  11. Cilônio era a tradução usada na versão da década de 70 do Battlestar Galactica.

    Sim, eu sou velho assim :)

    ResponderExcluir
  12. Dava pra ficar mais elegante colocando nas portas da entidade:
    LEDS: out std_logic_vector (7 downto 0);

    e no fim era só colocar:
    LEDS <= led_output;

    Mas aí é de cada um.

    De qualquer maneira, ótimo post.

    ResponderExcluir
  13. Eu não fiz desse jeito porque eu não faço idéia de como fazer um vetor no UCF. Fazendo LED a LED eu sei atribuir aos pinos :)

    ResponderExcluir
  14. Eu uso o Quartus II da Altera pois a placa que uso tem uma FPGA da Altera, d'oh :D

    Lá aparece cada um dos bits do vetor pra atribuir os pinos da placa... por isso sugeri assim. :) hehe

    ResponderExcluir
  15. Pô Thiago, Cilônio é mais simpático que Cylon vai! ;-)

    ResponderExcluir
  16. parabens, placa de teste $150,00, tempo gasto sem namorar, e fazer 8 leds como sequencial super maquina, não tem preço !

    ResponderExcluir
  17. João,
    Ainda bem que ele fez enquanto eu estava no trabalho então! hohoho ;-)

    ResponderExcluir
  18. Sequencial de LEDs é praticamente o Hello World do hardware hehe.

    ResponderExcluir
  19. Me avise quando voce resolver escrever sobre como usar o compilador (ISE WEBPACK), onegaishimasu. :-D

    ResponderExcluir
  20. ricbit, basta fazer:
    NET "LED7<indice>" LOC = ...

    A propósito. Seu post ficou muito bacana de se ler. E se me permite, um tutorial muito bom (embora pequeno) e pequeno é: http://jedizone.wordpress.com/2008/06/20/maquinas-de-estados-e-sintese-de-circuitos-com-vhdl/

    A vantagem desse tutorial é que ele dá uma visão crítica do código, algo que é bastante importante e normalmente os tutoriais ignoram.

    ResponderExcluir
  21. Outra coisa. Embora o tutorial tenha ficado bom, acho que seria interessante ensinar a fazer simulações também. Você pode até sonegar essa parte do fluxo de design para projetos simples, mas, querendo ou não, para projetos complexos ele é imprescindível.

    ResponderExcluir
  22. Ainda tem bastante coisa pra falar, eu vou fazendo posts aos poucos.

    Obrigado pela dica do ucf!

    ResponderExcluir
  23. ótimo post! tava procurando algum blog que falasse sobre fpga! o fpga4fun também é bem em conta!
    vc prefere vhdl a verilog?

    ResponderExcluir
  24. Você já tentou converter o código do One Chip MSX para o Spartan-3E? Seria necessário um FPGA de quantas portas para esse código funcionar?

    ResponderExcluir
  25. Oikawa, eu uso VHDL porque é o mais usado na comunidade de reconstrução de retro hardware.

    Eduardo, eu ainda não tentei, mas com certeza cabe. Eu já rodei o arcade do pacman inteiro nessa placa.

    ResponderExcluir
  26. Estou tentando fazer um oscilador em FPGA com uma quantidade ímpar de inversores em série e em malha fechada. O delay deles seria responsável pela oscilação. Não há clock. Isso é possível de ser feito? Na saída, implementei um contador assíncrono que incrementa a cada subida do oscilador, entretanto não recebo resposta nenhuma... Em tese, não vejo por que não funcionaria, pois usando componentes discretos isso dá certo.

    ResponderExcluir
  27. Tem certeza que o otimizador não tirou fora sua rede de inversores?

    ResponderExcluir
  28. É bem provável que tenha acontecido isso... Como faço para evitar isso?

    Obrigado por responder tão rápido!

    ResponderExcluir
  29. Acho que se você fizer a compilação pela linha de comando, dá pra passar uma flag que desliga a otimização. Mas eu não sei os detalhes porque nunca precisei fazer isso.

    ResponderExcluir
  30. Ok, vou tentar e depois comento o resultado. Obrigado!

    ResponderExcluir
  31. só curiosidade, vc fez eletrônica ou computação?

    ResponderExcluir
  32. Eu fiz eletrônica / telecomunicações na USP.

    ResponderExcluir