Colocando parafusos com martelo

Fazer simples é um dos mais complexos problemas da engenharia. Atingir uma solução elegante, ou seja, simples, eficiente e por sua vez robusta para um problema exige estudo, esforço e experiência. Parte do problema está infelizmente mascarado por trás das nossas ferramentas. Se aprendermos a colocar parafusos com um martelo possivelmente não usaremos chaves de fenda, é mais rápido usando um martelo.

As ferramentas têm uma incidência maquiavélica em nosso raciocino e com isso no resultado do nosso trabalho. Quem costuma pensar de uma forma, rapidamente esquece correr atrás de outras soluções.

O projetista de circuitos digitais não é a exceção. Infelizmente o mercado puxa para soluções rápidas e geralmente não geridas pela engenharia mas pelos prazos, que paradoxalmente atrasa o projeto resultado a mais em projetos de baixia qualidade.

O padrão de fato para projeto de lógica programável vem sendo faz algum tempo a descrição formal das funcionalidades. A linguagem, nossa ferramenta, usada na descrição do hardware deveria acompanhar nossa metodologia de projeto e conduzir-nos por sendeiros seguros desalentando-nos de usar construções potencialmente inseguras, perigosas ou desnecessariamente complexas. Alentando ao mesmo tempo ao projetista em rigorosas boas praticas evitando a infelizmente generalizada pratica: codificar rápido e contornar na verificação.

Para apresentar um exemplo de raciocino vamos implementar em lógica programável um componente bem conhecido, um porto de comunicação serial , só Tx para simplificar. A funcionalidade de um porto de comunicação serial do tipo encontrado nos antigos ACIA ou atuais UART é essencialmente simples. Um bloco com entrada em paralelo e saída em serial. A cada escrita na entrada paralelo os dados são encaminhados na saída serial numa taxa de bits predeterminada.

Se adotarmos o padrão semelhante das portas RS232, encaminhar-nos um bit de início a seguir oito bit de dados e para finalizar dois bit de parada.

Simulacion 1

Alguns sinais são implícitos na descrição, um sinal para marcar as escritas e um outro para marcar o passo da taxa de bits. A mais temos que contar os bits para inserir o início e a parada no momento correto. Alias, por metodologia faremos apenas projetos síncronos; para tanto adicionaremos um relógio (clock). Do ponto de vista de um sistema o UART não funcionará isolado, tem que se comunicar com outros blocos e esses blocos precisam saber se o UART estaria disponível, vamos adicionar mais um sinal, uma bandeira, para indicar se estiver ocupado. Se estiver ocupado não poderemos colocar mais dados nele.

Assim que a descrição de funcionalidade ficar pronta, vamos traduzi-la para uma linguagem que possa ser processada facilmente pelas ferramentas de verificação e implementação, no nosso casso será VHDL.

Vamos Iniciar com o bloco entrada paralelo e saída serial

ENTITY Tx IS
   PORT(
      DI : IN STD_LOGIC_VECTOR(7 DOWNTO 0); – entrada paralelo
      D : OUT STD_LOGIC ); – saída serial
END Tx;
         

A seguir vamos adicionar as sinas implícitas já comentadas

ENTITY Tx IS
   PORT(
      TICK : IN STD_LOGIC; – Marcação da taxa de saída
        WR : IN STD_LOGIC; – Marcação da escrita de dados
        DI : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
         D : OUT STD_LOGIC );
END Tx;
         

Faremos síncrono, vamos adicionar o relógio.

ENTITY Tx IS
   PORT(
      CLOCK : IN STD_LOGIC; – Relojo do relógio
       TICK : IN STD_LOGIC;
         WR : IN STD_LOGIC;
         DI : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
          D : OUT STD_LOGIC );
END Tx;
         

Para conversar com o resto do sistema, vamos adicionar a bandeira de ocupado

ENTITY Tx IS
   PORT(
      CLOCK : IN STD_LOGIC;
       TICK : IN STD_LOGIC;
         WR : IN STD_LOGIC;
         DI : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
       BUSY : OUT STD_LOGIC ; – Bandeira de ocupado
          D : OUT STD_LOGIC );
END Tx;
         

Feita a interconexão, vamos transladar a funcionalidade do bloco.

Lembrando, a cada ordem de escrita por o WR os dados colocados no DI são encaminhados para a porta serial na taxa de bits marcada por TICK. Enquanto o Tx está encaminhando dados o BUSY ficará valido, valor lógico 1.

Para encaminharmos os dados, vamos carregar os dados no registro data, carregar o número de bits no contador de bit e encaminhar um a um até o contador zerar.

IF WR = '1' THEN
   data <= DI & '0'; bitCnt <= TO_UNSIGNED(11,4); – 1 start + 8 dados + 2 stop = 11 bits
ELSIF TICK = '1' AND NOT(bitCnt=0) THEN
   data <= '1' & data(8 DOWNTO 1); bitCnt <= bitCnt-1;
END IF;
         

Por tanto necessitaremos um registro para os dados (data) e outro para contar os bits (bitCnt).

   SIGNAL data : STD_LOGIC_VECTOR(8 DOWNTO 0);
   SIGNAL bitCnt : UNSIGNED(3 DOWNTO 0);
         

Mas o projeto tem que ser síncrono, tem que ter relógio e detecção da borda do relógio.

   IF rising_edge(CLOCK) THEN
      IF WR = '1' THEN
         data <= DI & '0'; bitCnt <= TO_UNSIGNED(11,4);
      ELSIF TICK = '1' AND NOT(bitCnt=0) THEN
         data <= '1' & data(8 DOWNTO 1); bitCnt <= bitCnt-1;
      END IF;
   END IF;
         

Para criarmos o sinal de estado, o bloco estaria ocupado até encamarinhar todos os bits. Dito de outra forma o bloco estaria ocupado se o contador de bits for diferente de zero.

   BUSY <= '1' WHEN NOT(bitCnt=0) ELSE '0';
         

E finalmente vamos ligar o sinal de saída para o registro de dados, transmitindo o bit menos significativo primeiro, ligando para o bit 0 do registro.

   D <= data(0);
         

Vamos montar os segmentos do código e adicionar as estruturas requeridas do VHDL a mais da biblioteca IEEE, os pacotes IEEE.STD_LOGIC_1164 e IEEE.NUMERIC_STD.


   LIBRARY IEEE;
   USE IEEE.STD_LOGIC_1164.ALL, IEEE.NUMERIC_STD.ALL;

   ENTITY Tx IS
      GENERIC(k : INTEGER := 11); -- 1 Start + 8 Data + 2 Stop
      PORT(
         CLOCK : IN STD_LOGIC;
          TICK : IN STD_LOGIC;
            WR : IN STD_LOGIC;
            DI : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
          BUSY : OUT STD_LOGIC ;
             D : OUT STD_LOGIC );
      END Tx;

   ARCHITECTURE WDG1 OF Tx IS

      SIGNAL data : STD_LOGIC_VECTOR(8 DOWNTO 0) := (OTHERS => '1');
      SIGNAL bitCnt : UNSIGNED(3 DOWNTO 0) := (OTHERS => '0');

   BEGIN

      UARTTx : PROCESS(CLOCK) -- LSB First
      BEGIN
         IF rising_edge(CLOCK) THEN
            IF WR = '1' THEN
               data <= DI & '0'; bitCnt <= TO_UNSIGNED(k,4);
            ELSIF TICK = '1' AND NOT(bitCnt=0) THEN
               data <= '1' & data(8 DOWNTO 1); bitCnt <= bitCnt-1;
            END IF;
         END IF;
      END PROCESS;

      BUSY <= '1' WHEN NOT(bitCnt=0) ELSE '0';

      D <= data(0);

   END WDG1;
         

Vamos verificar o funcionamento.

Simulacion 2

Aparentemente funcionaria, a cada escrita de dados o BUSY passa para valor lógico 1 iniciando o trafego dos bits, finalizando o BUSY retorna para valor lógico 0 e o Tx espera até próximo WR. Porém olhando para as formas de onda em detalhe temos um problema.

Simulacion 3

O bit de início está errado. O banco de teste tenta simular um sistema real; a escrita de dados no bloco não está sincronizada com o marcador de taxa se saída (tick). Isso faz que o tempo do bit de início dependa da diferencia da fase entre o sinal de escrita e o sinal de taxa de bit.

A correção é simples, após da escrita temos que esperar até tick antes de encaminhar os dados. Vamos precisar mais um sinal para a bandeira de espera, sinc.

O código corrigido ficaria


   UARTTx : PROCESS(CLOCK) -- LSB First
   BEGIN
      IF rising_edge(CLOCK) THEN
         IF WR = '1' THEN
            data <= DI & '0'; bitCnt <= TO_UNSIGNED(11,4); sinc <= '1';
         ELSIF TICK = '1' AND NOT(bitCnt=0) THEN
            IF sinc = '1' THEN
               sinc <= '0';
            ELSE
               data <= '1' & data(8 DOWNTO 1); bitCnt <= bitCnt-1;
            END IF;
         END IF;
      END IF;
   END PROCESS UARTTx;
         

E vamos simular mais uma vez

Simulacion 4

A bandeira sinc fica em um desde a escrita no registro de dados até o primeiro tick a seguir da escrita. Isso atrasa o início do trafego no barramento série sincronizando os bits com o tick conseguindo assim todos os bit da mesma duração.

A captura a seguir apresenta um dos eventos de sincronização, como esperado o evento inicia com a escrita no registro de dados e finaliza na chegada do primeiro TICK após da escrita.

Simulacion 5

A seguir o código completo do transmissor da UART na versão funcional, para conseguir suportar 1 ou 2 bits de parada foi adicionado um parâmetro genérico k para alterar o número de bits encaminhados 10 ou 11.


   -------------------------------------------------------------------------------
   --
   --                                                       walter d. gallegos
   --                                                   www.waltergallegos.com
   --                                             Programable Logic & Software
   --                                                     Consultoria y Diseno
   --
   -- Este archivo y documentacion son propiedad intelectual de Walter D. Gallegos
   --
   -------------------------------------------------------------------------------

   LIBRARY IEEE;
   USE IEEE.STD_LOGIC_1164.ALL, IEEE.NUMERIC_STD.ALL;

   ENTITY Tx IS
      GENERIC(k : INTEGER := 11); -- 1 Start + 8 Data + 2 Stop
      PORT(
         CLOCK : IN STD_LOGIC;
          TICK : IN STD_LOGIC;
            WR : IN STD_LOGIC;
            DI : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
          BUSY : OUT STD_LOGIC ;
             D : OUT STD_LOGIC );
      END Tx;

   ARCHITECTURE WDG2 OF Tx IS
      
      SIGNAL data : STD_LOGIC_VECTOR(8 DOWNTO 0) := (OTHERS => '1');
      SIGNAL bitCnt : UNSIGNED(3 DOWNTO 0) := (OTHERS => '0');
      SIGNAL sinc : STD_LOGIC := '0';

   BEGIN
   
      UARTTx : PROCESS(CLOCK) -- LSB First
      BEGIN
         IF rising_edge(CLOCK) THEN
            IF WR = '1' THEN
               data <= DI & '0'; bitCnt <= TO_UNSIGNED(k,4); sinc <= '1';
            ELSIF TICK = '1' AND NOT(bitCnt=0) THEN
               IF sinc = '1' THEN
                  sinc <= '0';
               ELSE
                  data <= '1' & data(8 DOWNTO 1); bitCnt <= bitCnt-1;
               END IF;
            END IF;
         END IF;
      END PROCESS UARTTx;

      BUSY <= '1' WHEN NOT(bitCnt=0) ELSE '0';

      D <= data(0) OR sinc;

   END WDG2;
         

A solução que adotarmos para um problema está diretamente influenciada por nosso conhecimento e experiência. Nessa experiência as ferramentas que usarmos têm uma participação colateral, maquiavélica, as vezes não valorada, não apenas pelo que conseguirmos fazer com elas mas pelo que elas conseguem influenciar nosso raciocino.

Aplicando uma metodologia de projeto correta com ferramentas que reforcem nosso raciocino, usando linguagens estritas por exemplo, fazermos projetos simples, robustos e eficientes no uso dos recursos.


 
            A sua empresa está necessitando uma revisão de projeto ?