Al aprender VHDL o cualquier otro lenguaje de descripción de hardware suele empezarse por los habituales sumadores y bloques combinacionales, para posteriormente entrar en los sistemas con elementos de memoria, llamados también secuenciales y dentro de ellos las reinas son las máquinas de estados o FSM (Finite State Machine) en ingles.

Antes de empezar, si no os apetece mucho leeros el artículo que hemos preparado, os dejamos un video donde hemos explicado todo paso a paso.

Dentro de los sistemas secuenciales las maquinas de estado son probablemente el modulo más utilizado, y una inagotable fuente de dudas entre los principiantes en VHDL.

En este artículo vamos a estudiar la estructura de una máquina de estados en VHDL y resolveremos algunas dudas típicas que surgen al diseñarlas.

Lo primero, como siempre es describir que es una máquina de estados. Según la Wikipedia:

Las máquinas de estados se definen como un conjunto de estados que sirve de intermediario en esta relación de entradas y salidas, haciendo que el historial de señales de entrada determine, para cada instante, un estado para la máquina, de forma tal que la salida depende únicamente del estado y las entradas actuales.

Es decir que vamos a tener un conjunto de estados, unas entradas y unas salidas. Según la entrada que tengamos iremos recorriendo los estados y asignando las salidas, dependiendo estas de la secuencia de entradas que haya habido en el sistema.

Máquina de estados de Moore y Mealy

A la hora de representar una máquina de estados es habitual utilizar un diagrama de estados, que muestra de forma condensada toda esta información.

maquina de estados de Moore
Ejemplo de diagrama de una máquina de Moore
Maquina de estados de Mealy
Ejemplo de diagrama de estados de Mealy

En los ejemplos de la imágenes tenemos una máquina de estados con 1 bit de entrada y otro de salida, así como varios estados posibles. La máquina de Mealy al arrancar está en el estado S0, en tal estado cuando llega un 1 por la entrada se queda en el estado S0, pero si la entrada vale 0 pasa al estado S1. En función de dichas entradas se establece también el valor de las salidas. Estando en S0 si la entrada vale 1 la salida es 0, ya que en el diagrama tenemos en dicha transición 1/0.

Si nos fijamos en la máquina de Moore, la salida está asociada al estado, no a las entradas. En el estado q0 la salida siempre es 0 independientemente del valor de la entrada. Por tanto la principal diferencia entre ambas representaciones es que en la máquina de Moore la salida solo depende del estado y en la de Mealy depende del estado y la entrada.

Al la hora de diseñar la maquina de estados en VHDL la separación teórica entre máquina de Mealy y de Moore no es relevante, ya que ambas se describen de la misma forma, pero es interesante al menos mencionarlas, ya que suele haber muchas confusiones al respecto.

Descripción funcional de una FSM en VHDL

Una de las características de VHDL es que es posible describir circuitos digitales a diferentes niveles de abstracción. Esto quiere decir que podemos conectar componentes para crear el circuito que representa la máquina de estados, o que podemos simplemente describir su funcionamiento usando el lenguaje. La principal ventaja de usar una descripción funcional es que será la herramienta de síntesis que utilicemos la que a partir de esta descripción generará el circuito digital resultante, ahorrando muchísimo tiempo al diseñador.

Un proceso o dos procesos para la máquina de estados

A la hora de diseñar una FSM en VHDL, siempre surge una duda, ¿vamos a utilizar dos procesos o uno solo?.

La respuesta es que en la mayoría de los casos dará igual.

  • Si utilizamos dos procesos, las salidas se asignarán desde el proceso combinacional, por tanto tendrán glitches (oscilaciones) y el camino crítico del la FSM se unirá al camino crítico del módulo al que este conectada la salida de la máquina. Si necesitamos evitar esto, tendremos que registrar las salidas de la FSM.
  • Si usamos una estructura con un solo proceso, las salidas estarán registradas, es decir se almacenarán en registros, esto ocupará más lógica, pero las señales serán estables y los caminos críticos estarán separados. Por contra, la salida llevará un ciclo de reloj de retraso frente a una máquina descrita en dos procesos.

Como se puede ver, si registramos las salidas del proceso combinacional, el comportamiento de ambas máquinas será exactamente igual. No obstante habrá ocasiones en que el comportamiento de la máquina de estados de dos procesos será el que busquemos. Será el diseñador el que tenga que decidir qué necesita en cada momento.

Máquina de estados con dos procesos

A continuación esta el código VHDL de la máquina de estados de Moore de la imagen anterior descrita con dos procesos, junto al testbench para poder simularla.

El código es simple de entender, utilizamos un tipo enumerado para definir los estados y luego existe un proceso combinacional que calcula las salidas y el próximo estado a partir del valor de la entrada y del estado actual.

El segundo proceso, cada vez que hay un flanco de subida del reloj actualiza el registro que contiene el estado con el valor que hemos calculado en el primer proceso.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
 
entity fsm is
    port (        
        clk : in  std_logic;
        reset : in std_logic;
        x : in std_logic;
        z : out std_logic
    );
end fsm;
 
architecture Behavioral of fsm is
  type estados_t is (q0,q1,q2,q3,q4);
  signal estado, estado_siguiente : estados_t; 

begin

  process(x,estado)
  begin
    z <= '0';
    estado_siguiente <= estado;

    case(estado) is
      when q0 =>
        z <= '0';
        if(x='0') then estado_siguiente <= q1;
        else estado_siguiente <= q2;
        end if;
      when q1 =>
        z <= '0';
        if(x='0') then estado_siguiente <= q1;
        else estado_siguiente <= q3;
        end if;
      when q2 =>
        z <= '0';
        if(x='0') then estado_siguiente <= q4;
        else estado_siguiente <= q2;
        end if;
      when q3 =>
        z <= '1';
        if(x='0') then estado_siguiente <= q4;
        else estado_siguiente <= q2;
        end if;
      when q4 =>
        z <= '1';
        if(x='0') then estado_siguiente <= q1;
        else estado_siguiente <= q3;
        end if;
      when others => null;
    end case;
  end process;

  process (clk,reset) begin
    if(reset='1') then
      estado <= q0;
    elsif rising_edge(clk) then
      estado <= estado_siguiente; 
    end if;
  end process;
end Behavioral;

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
 
entity test is
end test;

architecture funcional of test is

  signal clk : std_logic:='0';
  signal reset : std_logic;
  signal x : std_logic;
  signal z : std_logic;
   
begin

  clk <= not(clk) after 10 ns;
  reset <= '1', '0' after 10 ns;
  x <= '0', '1' after 50 ns,'0' after 110 ns;

  fsm0 : entity work.fsm(Behavioral) port map(clk,reset,x,z);

end architecture;

En la simulación podemos ver el comportamiento del circuito, donde el estado cambia cuando hay un flanco de reloj, tal y como hemos descrito en el código. También se ve como en cuanto entramos en el estado q3 o q4 la salida se pone a 1. En el ejemplo con un solo proceso veremos cómo esto no es así y la salida no se pone a 1 hasta el ciclo siguiente.

Simulación máquina con dos procesos

Máquina de estados con un proceso

La máquina de estados descrita con un solo proceso es muy similar, pero utiliza únicamente un proceso para modelar el sistema. Os dejamos el código a continuación. El test que usaremos es el mismo que en el ejemplo anterior.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
 
entity fsm is
    port (        
        clk : in  std_logic;
        reset : in std_logic;
        x : in std_logic;
        z : out std_logic
    );
end fsm;
 
architecture Behavioral of fsm is
  type estados_t is (q0,q1,q2,q3,q4);
  signal estado : estados_t; 

begin

   process (clk,reset) begin
    if(reset='1') then
      estado <= q0;
      z <= '0';
    elsif rising_edge(clk) then
      case(estado) is
        when q0 =>
          z <= '0';
          if(x='0') then estado <= q1;
          else estado <= q2;
          end if;
        when q1 =>
          z <= '0';
          if(x='0') then estado <= q1;
          else estado <= q3;
          end if;
        when q2 => 
          z <= '0';
          if(x='0') then estado <= q4;
          else estado <= q2; 
          end if;
        when q3 =>
          z <= '1'; 
          if(x='0') then estado <= q4;
          else estado <= q2; 
          end if;
        when q4 =>
          z <= '1';
          if(x='0') then estado <= q1;
          else estado <= q3;
          end if;
        when others => null;
      end case;
    end if;
  end process;

En la simulación podemos observar lo que comentábamos anteriormente. el comportamiento es muy similar, pero al estar las salidas registradas, ya que se asignan dentro de un proceso secuencial, estas se activan con un ciclo de retraso frente al ejemplo con dos procesos.