Projeto completo de uma UART em MyHDL

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
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Hardware, Sistemas Digitais
, , ,
Comentários:
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/ […]

Talvez você goste:

Séries



Outros da Série

Menu

EVENTO ONLINE

Simplificando seus projetos de Internet das coisas com o iMCP HT32SX Sigfox

DATA: 18/05 às 15:00h