Projeto completo de uma UART em MyHDL

Neste artigo vamos implementar uma UART em MyHDL. Assim podemos fazer nosso próprio hardware que se comunica com nosso código ou com outro microcontrolador.
UART em MyHDL
Este post faz parte da série MyHDL. Leia também os outros posts da série:

A UART é um velho conhecido de todo engenheiro de sistemas embarcados, é provavelmente o primeiro protocolo de comunicação que aprendemos na universidade. Neste artigo vamos implementar o nosso próprio circuito serial em MyHDL. Assim podemos fazer nosso próprio hardware que se comunica com nosso código em Java, C#, ou com outro microcontrolador como um Arduino

Conforme vimos em um artigo anterior, MyHDL é uma linguagem de descrição de hardware em Python que possibilita gerar arquivos em VHDL e Verilog. Além é claro de prover todo o potencial do Python para o usuário, como por exemplo, bibliotecas para testes unitários.

UART

Como sabemos, tanto o transmissor quanto o receptor entram em um acordo quanto à taxa de transferência (Baud Rate), o número de bits de dados, o número de stop bits e outros detalhes mais. Neste exemplo vamos implementar uma UART com 8 bits de dados, 2 stop bits e 115200 bps de Baud Rate.

Nossa UART é composta por três módulos:

  • baudrate_gen: Circuito responsável por gerar nossos pulsos de baudrate;
  • serial_tx: Circuito responsável por enviar dados. Possui como entrada uma porta de 8 bits com o dado a ser enviado e uma porta de start. Quando start vai para ‘1’ o dado é enviado pela porta de saída TX;
  • serial_rx: Circuito responsável por receber dados. Possui como entrada a porta RX e como saída uma porta de 8 bits de dados e uma porta indicando quando os dados estão prontos.
arch
Arquitetura da nossa UART em MyHDL

baudrate_gen

Este é o nosso módulo mais simples, com base no clock de entrada é gerada uma saída na taxa especificada. A fórmula é bem simples: Frequência do clock / Frequência do baudrate. Tenho um cristal de 50MHz no meu FPGA e desejo uma taxa 115200 Hz. 50.000.000 / 115.200 da aproximadamente 434. Então, a cada 434 pulsos do meu clock de 50 MHz vou gerar um pulso de baud, descrito como “tick” na nossa arquitetura. 

Como entrada temos o clock (sysclk), um reset (reset_n), a taxa de baud desejada (baud_rate_i) . Como saída temos os ticks: half_baud_rate_o e baud_rate_o. A primeira vai para ‘1’ na metade do tempo, neste caso, a cada 217 pulsos de clock de 50 MHz.

O código fica bem similar a uma descrição em VHDL ou Verilog. O módulo é tratado como uma função em Python, os argumentos das funções são as nossas portas. O MyHDL identifica automaticamente o que é entrada e o que é saída no módulo. Um registrador é inferido  para guardar o número de pulsos, baud_gen_count_reg.

A keyword Signal indica que é um Sinal (equivalente ao VHDL). O tipo intbv se refere a INT BIT VECTOR, é algo semelhante a um STD_LOGIC_VECTOR . Com a diferença de que o MyHDL sabe na hora de gerar VHDL/Verilog, se vai ser um vetor lógico, um vetor com sinal ou vetor sem sinal.

O decorator @always_seq emite automaticamente o nosso PROCESS (VHDL) ou ALWAYS (VERILOG) de forma síncrona e reseta todos os registradores para zero. É sempre necessário ter ao final um return com todos os nossos process/always/decorators.

serial_tx

Este módulo é responsável por enviar os dados via serial. Tem-se uma máquina de estados que aguarda um sinal de (start_i) para iniciar o processo de enviar o byte  (data_i) pela nossa saída de TX (transmit_o).

O código é bem semelhante a uma implementação em VHDL ou Verilog, descrevemos tudo a nível RTL. Temos agora também o decorator @always_comb que é equivalente a um PROCESS/ALWAYS puramente combinacional. Enviamos um bit a cada “tick” do nosso baudrate. Como ficou um pouco mais complexo, optei pelo estilo de RTL dividido em duas máquinas de estados, aonde os registradores são inferidos no decorator sequencial e a lógica combinacional no decorator combinacional.

serial_rx

Este módulo é responsável por transformar os dados que estão vindo via serial em um byte de informação. Aguarda-se o start bit no RX e armazena-se os próximos 8 bits de dados em um registrador de 1 byte. Assim que o dado (data_o) está pronto, o ready_o vai para nível lógico alto.

Testbenches

Vamos testar da forma tradicional o nosso MyHDL, ou seja, criando um testbench e verificando a waveform.

Importe os módulos que acabamos de criar (serial_tx.py, serial_rx,py e baudrate_gen.py) no arquivo tb_serial.py.

Definimos a nossa função bench responsável por parametrizar nosso projeto e também gerar os estímulos. Colocamos como constantes o periodo do clock (1 / 50000000), a frequência do clock (50000000), e calculamos a constante da nossa taxa de baudrate (número de pulsos para tick).

Então declaramos os tipos de dados dos nossos módulos, ao utilizar um ResetSignal para o reset você pode especificar se ele é síncrono ou assincrono e se é ativo em nível lógico alto ou baixo. Isto é necessário para o decorator @always_seq gerar o reset da forma correta. Também instanciamos os nossos módulos e fazemos as conexões, bem semelhante a forma feita em VHDL/Verilog.

Então temos dois decorators, um para gerar o clock e outro para gerar os estímulos do circuito. Como podemos observar, estamos colocando o dado 196 no serial_tx e enviando. Esperamos que o serial_rx nos dê este dado de volta ao emitir o sinal rx_rdy. A keyword yield é equivalente ao wait for em VHDL, ele espera que a condição seja verdadeira e então continua a execução.

A função test_bench executa o bench em MyHDL. O traceSignals é responsável por gerar os nossos waveforms no formato .vcd, iremos rodar a simulação por 1 ms. Para isto basta executar na linha de comando:

>python tb_serial.py

O arquivo bench.vcd foi gerado, podemos abri-lo em qualquer visualizador de waveform. Para abrir no gtkwave (grátis e opensource) basta executar

>gtkwave bench.vcd

Vamos visualizar o nosso waveform:

waveform

E funcionou! O valor 196 foi transmitido via TX (transmit_o), recebido via RX (recieve_i) e quando o ready foi para nível lógico alto lá estava nosso dado de novo!

Teste automatizado

Uma vantagem do MyHDL é a possibilidade de automatizar o nosso teste e não verificar mais waveforms. Para isto vamos alterar o nosso tb_serial.py. Basta adicionar as duas linhas abaixo do start.next = 0:

e rodar o py.test no terminal:

>py.test tb_serial.py

Sem título

Pronto! Automaticamente o MyHDL verifica se o dado de envio e recebimento são os mesmos após a borda de subida do rx_rdy acontecer.

Gerando o VHDL / Verilog

Tudo muito bonito e testado! Mas agora vamos gerar o VHDL/Verilog a partir do nosso MyHDL. Isto é muito simples, basta alterar estas linhas no testbench:

tb_serial.py:

E mais estas:

Agora, além de testar o seu design, os arquivos VHDL também já são gerados automaticamente. A função toVerilog geraria Verilog ao invés de VHDL.

O VHDL/Verilog gerado é completamente legível e bem estruturado, na verdade é basicamente uma tradução do seu RTL em Python. Um RTL ruim em Python gera um arquivo VHDL/Verilog ruim. Como exemplo o nosso serial_tx.vhd gerado:

Testando na placa

Agora é a hora da verdade! Estou usando o kit DE-2 da Terasic e um cabo USB->Serial na porta RS-232.

Layout
Kit DE2

Usei como base o GOLD REFERENCE DESIGN, que já possui todas as portas declaradas e os pinos do FPGA identificados.

Instanciei meus três componentes, conectei a condição de START do Serial_TX ao botão KEY[0], conectei o RX vindo direto do RS232 no meu recieve_i e o TX direto do cabo no meu transmit_o.

Também conectei como loopback o dado a enviar no TX e o dado recebido no RX. A conexão está exatamente igual à indicada na arquitetura lá no começo do arquivo.

instancia

E não é que funciona? Basta conectarmos o cabo no FPGA e no computador e configurar o seu software de preferência que se comunique via serial.

Eu utilizei o Hyper Terminal, configurei a taxa de 115200 Hz, 8 bits de dados, sem paridade e 2 bits de stop (115200-8-n-2) e voilá… Tudo que você digita da Eco enquanto o botão estiver pressionado.

(Salve seu trabalho antes!!! Como esta é uma serial simplificada, não foram observados sinais de controle como CLEAR TO SEND e READY TO SEND. Isto me ocasionou uma tela azul no Windows 7 durante os testes).

O start do tx está mapeado em um botão sem debounce e não registrado. Ao apertar o botão para enviar um dado o sinal de “start” vai para ‘1’ diversas vezes.

loopback_serial
Loopback na serial

Conclusão

Com este projeto podemos observar que o MyHDL funciona e muito bem, gerando um bom VHDL e funcionando na placa. O ciclo de desenvolvimento fica muito mais rápido em Python e você possui ferramentas a mais para testar seu módulo antes de gerar o VHDL, é possível realizar diversos asserts e testar o seu projeto inteiro com um simples py.test <tb_top.py>.

Esta implementação de UART é mais didática, a intenção não é desenvolver uma UART para ser utilizada em um projeto da vida real.

O código do projeto está disponível no github.

Outros artigos da série

<< MyHDL – Descrevendo hardware em Python

Engenheiro de computação com experiência no desenvolvimento de software e hardware para sistemas embarcados.

Principalmente em projetos para a indústria envolvendo FPGA.

8 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Marcelo Rodrigo Dos Santos Andriolli
Marcelo Andriolli
26/08/2014 23:20

Parabéns André! Muito bacana o artigo e o projeto! Que tal um SPI com MyHDL? hehehe hehehehe

André Castelan
Reply to  Marcelo Andriolli
03/09/2014 08:46

Valeu Marcelo! Ótima sugestão, vou fazer 🙂

abs

Ronaldo Borges
ronaldo
25/08/2014 20:54

Funcionou bem!
Apenas nao consegui rodar o código abaixo:

yield rx_rdy.posedge
assert tx_data == rx_data

André Castelan
Reply to  ronaldo
25/08/2014 22:14

Olá Ronaldo. Qual erro aconteceu? Você comentou a parte do test bench necessária ? (TraceSignals)

Abraço

Ronaldo Borges
ronaldo
Reply to  André Castelan
25/08/2014 22:26

Segui a instrução: Para isto vamos alterar o nosso tb_serial.py. Basta adicionar as duas linhas abaixo do start.next = 0:
yield rx_rdy.posedge
assert tx_data == rx_data

E apresentou o erro:

File “tb_serial.py”, line 41
yield rx_rdy.posedge
^
IndentationError: unexpected indent

As ações antes desta alteração funcionaram perfeitamente

André Castelan
Reply to  ronaldo
25/08/2014 22:58

Ola Ronaldo é um erro de identação, verifique se você está usando quatro espaços de identação. Abraço

Ronaldo Borges
ronaldo
Reply to  André Castelan
25/08/2014 23:06

Pode cre! valeu!

trackback
25/08/2014 18:05

[…] Utilizando o Python para gerar VHDL e Verilog com o MyHDL! O melhor de tudo, com testes automatizados. Link para o artigo completo no Embarcados: https://www.embarcados.com.br/uart-myhdl-vhdl-verilog-fpga/ […]

WEBINAR

Imagens de Ultrassom: Princípios e Aplicações

DATA: 26/10 ÀS 19:30 H