Skip to content

Macchine a stati, memorie e funzioni in VHDL

Per realizzare una macchina a stati in VHDL, possiamo separare la parte sequenziale e quella combinatoria. In particolare, la prima sarà un registro a molti bit e la seconda verrà divisa in 2 parti, una per il calcolo dello stato futuro e l’altra per le uscite.

Schema a blocchi di una generica macchina a stati

Realizziamo il derivatore, nell’implementazione come macchina di Moore.

library ieee;
use ieee.std_logic_1164.all;

entity derivatore is
    port (
        s, clk, res : in std_logic;
        y : out std_logic;
    );
end;

architecture behavioral of derivatore is
    -- Lo stato è un enum, in modo da non dover specificare subito la codifica.
    -- Non è sempre supportato dai sintetizzatori. Una soluzione più portatile
    -- sarebbe quella di usare costanti esplicite.
    type stato is (a, b, c, d);
    signal present_state, next_state : stato;
begin
    -- Parte sequenziale del derivatore.
    seq: process (res, clk) is
    begin
        -- Segnale di reset asincrono.
        if res = '0' then
            -- a è lo stato iniziale.
            present_state <= a;
        elsif rising_edge(clk) then
            -- Al fronte del clock lo stato futuro diventa il presente.
            present_state <= next_state;
        end;
    end;

    futuro: process (present_state, s) is
    begin
        -- Nei processi si usa case.
        case present_state is
            when a =>
                if s = '0' then
                    next_state <= a;
                else
                    next_state <= b;
                end;
            when b =>
                next_state <= c;
            when c =>
                if s = '0' then
                    next_state <= d;
                else
                    next_state <= c;
                end;
            when d =>
                next_state <= a;

            -- Se ci fossero varianti rimanenti, si userebbe `when others =>`.
        end;
    end;

    uscite: process (present_state) is
    begin
        y <= '0';

        if present_state = b or present_state = d then
            y <= '1';
        end;
    end;
end;

present_state viene sintetizzato come un insieme di flip-flop perché in alcuni casi il processo combinatorio non gli assegna un valore.

Quando si progetta un circuito si possono usare diversi tipi di memoria:

  • ROM: read-only memory;
  • RAM: random-access memory;

Normalmente la memoria non viene sintetizzata (verrebbe un circuito troppo grande). Si implementa manualmente o con specifici generatori automatici.

Può essere utile però esprimere delle memorie in VHDL per eseguire le simulazioni.

Una memoria ROM è semplicemente un array di valori indicizzato da un integer (indirizzo).

library ieee;
use ieee.std_logic_1164.all;

entity ROM is
    port (
        address : in integer range (0 to 15);
        data : out std_logic_vector(7 downto 0);
    );
end;

architecture dataflow of ROM is
    type rom_array is array(0 to 15) of std_logic_vector(7 downto 0);
    constant rom : rom_array := (
        "01010101",
        "10101010",
        -- ...
        "11111111",
        "00110011",
    );
begin
    data <= rom(address) after 10ns;
end;

Con una ROM si può implementare una tabella della verità. Di conseguenza qualsiasi circuito combinatorio può essere implementato come una ROM.

Creiamo una RAM asincrona, con i seguenti segnali:

  • bus dati di tipo inout;
  • cs: chip select, per segnalare che la memoria può usare il bus dati;
  • oe: output enable, per abilitare la lettura dalla RAM;
  • we: write enable, per abilitare la scrittura sulla RAM;
library ieee;
use ieee.std_logic_1164.all;

entity RAM is
    port (
        address : in integer range (0 to 15);
        data : inout std_logic_vector(7 downto 0);
        cs, oe, we : in std_logic;
    );
end;

architecture behavioral of RAM is
begin
    process (address, cs, we, oe, data) is
        type ram_array is array(0 to 15) of std_logic_vector(7 downto 0);
        variable mem : ram_array;
    begin
        data <= (others => 'Z');

        if cs = '0' then
            if oe = '0' then
                data <= mem(address);
            elsif we = '0' then
                mem(address) <= data;
            end;
        end;
    end;
end;

Bisogna fare attenzione a come viene pilotato l’ingresso dati, dato che esso sarà condiviso con altre periferiche.

In VHDL una funzione è uno strumento che serve solo a modulare il codice.

I parametri sono solamente ingressi. È possibile ritornare un valore con la parola chiave return.

Gli statement vengono eseguiti sequenzialmente, come in un processo. A differenza dei processi, viene eseguita solo quando viene chiamata, non quando cambia il valore dei parametri in ingresso.

library ieee;
use ieee.std_logic_1164.all;

entity s is
    port (
        p1, q1 : in std_logic_vector(7 downto 0);
        p2, q2 : in std_logic_vector(3 downto 0);
        y1 : out std_logic_vector(7 downto 0);
        y2 : out std_logic_vector(3 downto 0);
    );
end;

architecture behavioral of s is
    function add(a, b : in std_logic_vector) return std_logic_vector is
        variable s : std_logic_vector(a'range);
        variable carry : std_logic;
    begin
        carry := '0';
        for i in a'range loop
            s(i) := a(i) xor b(i) xor carry;
            carry := (a(i) and b(i)) or (carry and a(i)) or (carry and b(i));
        end;
        return s;
    end;
begin
    y1 <= add(p1, q1);
    y2 <= add(p2, q2);
end;

Le procedure sono simili alle funzioni, ma possono ritornare più di un valore.

I parametri possono essere di tipo in, out o inout.

procedure somma(
    a, b : in std_logic_vector;
    y : out std_logic_vector;
    cout : out std_logic;
) is
    variable carry : std_logic;
begin
    carry := '0';
    for i in a'range loop
        y(i) := a(i) xor b(i) xor carry;
        carry := (a(i) and b(i)) or (carry and a(i)) or (carry and b(i));
    end;
    cout := carry;
end;

Nelle macchine di Moore può essere utile fare in modo che le uscite del circuito corrispondano alle uscite di qualche flip-flop. In questo modo esse si aggiornano subito dopo il fronte del clock e non presentano glitch.

Per evitare ulteriori ritardi, è possibile calcolare il valore delle uscite a partire dallo stato futuro.

Macchina di Moore con uscite registrate