Esse vai deixar muita gente sem entender. Alocação dinâmica e estática em VHDL? Sim, dá para usar estes conceitos. Quase isso, na verdade…
Introdução
Muito pouca gente sabe, mas é possível adotar o conceito de variável estática e dinâmica em VHDL, aproximado-se do conceito de programação de software. Neste artigo, eu vou ensinar uma técnica bastante avançada para o uso de VHDL, que permite sistemas complexos serem criados com facilidade aliviando a complexidade presente na descrição de hardware. Por exemplo, esse paradigma facilita a criação de pipelines em alguns casos, permite pensar mais “algoritmo” e menos hardware em outros e, por fim, acaba aproximando o desenvolvedor de HDL (neste caso, VHDL) ao desenvolvedor de HLS (High Level Synthesis).
A ideia é usar procedures para isso. Mas antes é preciso recordar alguns conceitos, bem simplificados…
Mas se você já vem de HDL…
A ideia é usar a porta inout de uma procedure para gerar amostragem em FlipFlop. Você pode ler de onde veio a ideia caso queira. Se quer só mais uma técnica para o arsenal, pula direto para a implementação do FlipFlop D.
Um pouco sobre Alocação de Variável em Software
Para fazer o pessoal de C ficar nervoso, vou resumir em muito um conceito importantíssimo tomando toda a licença poética ao meu dispor.
Uma variável dinâmica é aquela criada durante a execução de uma função, em geral, alocada na pilha. Durante execução daquela função, uma porção da memória é alocada e utilizada e liberada automaticamente após o fim da execução desta função. Uma variável estática é uma variável que mantém o seu conteúdo mesmo quando a execução da função é finalizada, pois deve manter seu valor enquanto o programa estiver sendo executado. Resumindo mais ainda, em Firmware, ela deve manter o conteúdo até uma próxima chamada da função (ou seja, por toda a vida do programa, que no caso de Firmware, é ele mesmo).
Bom, há muito mais atrás disso, há muito mais a ser explicado. Talvez valha dar uma lida nestes artigos antes de prosseguir:
- https://www.embarcados.com.br/ponteiro-em-c-alocacao-dinamica/
- https://www.embarcados.com.br/tlsf/
- https://www.embarcados.com.br/serie/estruturas-de-controle-de-memoria/
Mas para os propósitos deste artigo, é suficiente. Em VHDL, podemos implementar este raciocínio, mas é preciso fazer um pouco de ginástica mental. Em geral, estático é o que gera hardware de armazenamento (flip-flop ou RAM, mas para os fins deste artigo, pensaremos apenas em flip-flop) e dinâmico é o que é resolvido em tempo de síntese (tempo de síntese é, para os desenvolvedores de HDL, análogo ao tempo de compilação).
- Todos os sinais em uma entidade VHDL, quando utilizados em processo com clock para armazenar valor, são comparáveis com variáveis estáticas globais dentro daquela entidade (sempre alocados em flip-flop durante toda a execução do “programa”);
- Todas as variáveis são locais, sendo que as variáveis continuam apresentando problema: podem ser estáticas ou dinâmicas.
Components, Procedures e Funções
Antes de adentrar nas variáveis em VHDL, precisamos entender os formatos de “subprograma” que o VHDL possui.
- Componentes: componentes (components) são hoje o mais próximo de “objeto” que se possui em VHDL. Um componente é sempre uma entidade VHDL declarada dentro de outra entidade VHDL. Podemos criar componentes que aglomerem funções dentro deles.
- Functions: funções em VHDL servem, dentro do escopo sintetizável, servem para implementar lógica combinacional. Lógica combinacional é o que realmente faz alguma coisa em HDL: fora isso, temos apenas flip-flops ou memórias. Basicamente as funções tem entrada e uma única saída, o retorno.
- Procedures: procedures são subprogramas tal qual as functions e também servem, primariamente, para implementar lógica combinacional. Porém, são mais poderosos pois os procedures podem ter portas de entrada e saída.
NOTA: Leitores mais atentos podem lembrar as construções BLOCK e também dos PROTECTED TYPES. Ambos em geral são desprezados pela ferramenta de síntese.
Variáveis em VHDL
Eu continuo desestimulando o uso de variáveis (variables) em processos, durante a descrição de hardware com VHDL, principalmente para iniciantes. Porém, nunca estendi essa regra para functions e procedures. Até porque é praticamente impossível usá-las sem trabalhar com variáveis e, se o desenvolvedor já está precisando deste tipo de construção, um pouco de variáveis não deve trazer grandes complicações.
Em VHDL, podemos entender que dentro de uma função, o uso de variáveis é sempre dinâmico, ou seja, não é possível armazenar o valor das variáveis internas entre chamadas da função. A única coisa que pode ser armazenada de uma função nem VHDL é o valor de retorno da mesma.
1 2 3 4 5 6 7 8 |
procedure and_f ( input1: std_logic; input2: std_logic ) return std_logic is begin return (input1 and input2); end and_f; ---usage--- signal_c <= and_f(signal_a,signal_b); |
Nas procedures, a coisa não é diferente para as variáveis. Elas são criadas usadas e seu valor, a princípio, não pode ser armazenado. Porém, já mencionei que diferente das funções, uma procedure pode não só ter mais de uma entrada, como também mais de uma saída.
As Procedures Revisitadas
Uma procedure possui portas, tal qual uma entidade. Essas portas podem ser de entrada, saída e o mais interessante, entrada e saída.
1 2 3 4 5 6 7 8 |
procedure and_p ( signal input1: in std_logic; signal input2: in std_logic; signal output: in std_logic ) is begin output <= input1 and input2; end and_p; --usage-- and_p(signal_a,signal_b,signal_c); |
Embora se possa entender as portas de entrada e saída de uma procedure como entrada de “ponteiros” (análogo a possibilidade de poder mudar valores de apontados. Contudo, como o conceito de ponteiro não faz muito sentido em HDL, é melhor entender que as procedures são funções com possibilidade de mais de uma saída, cada uma em uma porta out.
Nota: existe sim uma estrutura de ponteiros usada em simulação, o access. Porém, seu uso é bastante complicado e é possível passar uma vida sem precisar disso. Outro jeito de pensar em ponteiros em VHDL é através do endereçamento de memórias, mas isso é bastante diferente do conceito de ponteiros em C.
Porém, as procedures possuem a porta INOUT. Essa porta permite enviar valor para ser armazenado por um sinal ou variável para ser usado na próxima execução desta procedure (ou seja, no próximo ciclo de clock). Tem-se, portanto, o conceito de que, a cada ciclo de relógio haverá uma chamada da procedure e, portanto, a utilização de valores da chamada anterior.
Um Flip-Flop D
Ok. Bastante explicação. Show me the code! Vamos fazer um Flip-Flop tipo D (FFD). Seja o seguinte record:
1 2 3 4 5 |
type flipflop_t is record d : std_logic; q : std_logic; en : std_logic; end record flipflop_t; |
E seja a seguinte procedure:
1 2 3 4 5 6 7 8 9 |
procedure flipflopd_p ( signal ffd : inout flipflop_t ) is begin if ffd.en = '1' then ffd.q <= ffd.d; else ffd.q <= ffd.q; --note que será preciso sempre manter o status de tudo que for corrente. --Senão, você depende da ferramenta. end if; end flipflopd_p; |
E, em algum lugar da entidade, temos o seguinte sinal sendo declarado, ligações das portas da entidade e o seguinte processo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
signal ffd_s : flipflop_t; --sinal declarado --- ffd_s.d <= ffd_d_i;--input from entity port. ffd_s.en <= ffd_en_i;--input from entity port. ffd_q_o <= ffd_s.q; --output to entity port. process(all) begin if rising_edge(clk) then flipflopd_p(ffd_s); end if; end process; |
Pronto. Acabamos de criar um flip-flop D com esse novo estilo de codificação.
Dicas Finais
Enquanto eu pensava nesse novo estilo de enxergar minhas máquinas digitais, pude antecipar alguns problemas…
Multi Driver
Essa técnica é muito útil, mas há alguns poréns: lembra quando eu afirmei que todos os sinais são variáveis GLOBAIS? Isso quer dizer que um mesmo sinal pode ser utilizado por vários processos, gerando mais de um driver. Em HDL, sempre que um sinal tem mais de um driver, o resultado é ‘U’.
O ideal é que o sinal usado como variável estática seja sempre escrito (ou seja, tenha valores atribuídos a ele) dentro de um mesmo processo (ele pode ser lido por todos os outros, tal qual o uso “normal” dos sinais).
Você pode evitar isso codificando de tal forma que, quando não vai usar um sinal naquele processo, ele assume o valor de ‘Z’, ou seja, alta impedância. Faz anos que os FPGAs não possuem buffers tri-state internos e é bem provável que a ferramenta vá resolver isso com um monte de multiplexador. Mas você estaria a mercê do fabricante (isso até é aguentável), porém, na linha do funciona, mas é feio…
Variáveis continuam sendo um problema.
Sim, elas continuam sendo um problema. A ideia de alocação estática serve também para variáveis, contudo, prefiro usar sinais para isso.
Eu citei as variáveis neste artigo por fazerem parte das descrições em procedures e functions e, de certa forma, para mostrar mais uma vez que pode-se fazer VHDL muito avançado sem elas. Elas vão trazer toda a complicação usual das variáveis: hora vão gerar hardware, ora não.
Contudo, talvez elas possam ajudar… Por causa de temporização, ou seja, quando as coisas acontecem em HDL. Mais sobre isso no próximo item.
O Timing pode ficar complicado.
Toda vez que um sinal recebe um valor, ele só “adquire” aquele valor quando o processo acaba ou encontra um ‘wait until clock’ (ou quando encontra um ‘wait for time‘, mas isso é inválido na síntese).
Caso algo precise adquirir um valor imediato, pode ser necessário usar variáveis nas portas de entrada e saída da procedure (a porta de alocação estática eu recomendo que sempre seja um sinal).
Conclusão e Desafios
Acredito que, sendo uma técnica avançada, se não estiver fazendo muito sentido pode ser importante estudar um pouco mais de VHDL, FPGAs ou HDLs em geral. Talvez esse link ajude:
Ainda vale a pena salientar que é possível criar construções mais complexas do que o FFD. Exemplos:
- Shift-Registers
- Memórias (por limitação dos sintetizadores, talvez acabe implementada com FlipFlop e não com Blockram)
- E também sistemas com pipeline mais complexo (Filtros, Corretores de Erro,…)
Mas por enquanto, que tal pegar os dois exemplos acima e codificar, para ver o que acontece?
obrigado pelo artigo