Buscar..


Observaciones

VHDL es un acrónimo compuesto de VHSIC (circuito integrado de muy alta velocidad) HDL (lenguaje de descripción de hardware). Como lenguaje de descripción de hardware, se utiliza principalmente para describir o modelar circuitos. VHDL es un lenguaje ideal para describir circuitos, ya que ofrece construcciones de lenguaje que describen fácilmente el comportamiento concurrente y secuencial junto con un modelo de ejecución que elimina la ambigüedad introducida al modelar el comportamiento concurrente.

VHDL se interpreta normalmente en dos contextos diferentes: para simulación y para síntesis. Cuando se interpreta para la síntesis, el código se convierte (sintetiza) a los elementos de hardware equivalentes que se modelan. Normalmente, solo un subconjunto de VHDL está disponible para su uso durante la síntesis, y las construcciones de lenguaje compatibles no están estandarizadas; Es una función del motor de síntesis utilizado y del dispositivo de hardware de destino. Cuando se interpreta VHDL para simulación, todas las construcciones de lenguaje están disponibles para modelar el comportamiento del hardware.

Versiones

Versión Fecha de lanzamiento
IEEE 1076-1987 1988-03-31
IEEE 1076-1993 1994-06-06
IEEE 1076-2000 2000-01-30
IEEE 1076-2002 2002-05-17
IEEE 1076c-2007 2007-09-05
IEEE 1076-2008 2009-01-26

Instalación o configuración

Un programa VHDL puede ser simulado o sintetizado. La simulación es lo que más se parece a la ejecución en otros lenguajes de programación. La síntesis convierte un programa VHDL en una red de puertas lógicas. Muchas herramientas de simulación y síntesis de VHDL forman parte de las suites comerciales de Electronic Design Automation (EDA). Con frecuencia también manejan otros lenguajes de descripción de hardware (HDL), como Verilog, SystemVerilog o SystemC. Existen algunas aplicaciones gratuitas y de código abierto.

Simulación VHDL

GHDL es probablemente el simulador VHDL libre y de código abierto más maduro. Se presenta en tres sabores diferentes según el backend utilizado: gcc , llvm o mcode . Los siguientes ejemplos muestran cómo usar GHDL (versión mcode ) y Modelsim, el simulador comercial HDL de Mentor Graphics, bajo un sistema operativo GNU / Linux. Las cosas serían muy similares con otras herramientas y otros sistemas operativos.

Hola Mundo

Crea un archivo hello_world.vhd contenga:

-- File hello_world.vhd
entity hello_world is
end entity hello_world;

architecture arc of hello_world is
begin
  assert false report "Hello world!" severity note;
end architecture arc;

Una unidad de compilación VHDL es un programa completo de VHDL que se puede compilar solo. Las entidades son unidades de compilación VHDL que se utilizan para describir la interfaz externa de un circuito digital, es decir, sus puertos de entrada y salida. En nuestro ejemplo, la entity se llama hello_world y está vacía. El circuito que estamos modelando es una caja negra, no tiene entradas ni salidas. Las arquitecturas son otro tipo de unidad compiladora. Siempre están asociados a una entity y se utilizan para describir el comportamiento del circuito digital. Una entidad puede tener una o más arquitecturas para describir el comportamiento de la entidad. En nuestro ejemplo, la entidad está asociada solo a una arquitectura denominada arc que contiene solo una declaración VHDL:

  assert false report "Hello world!" severity note;

La declaración se ejecutará al comienzo de la simulación e imprimirá el Hello world! Mensaje en la salida estándar. La simulación terminará porque no hay nada más que hacer. El archivo fuente VHDL que escribimos contiene dos unidades de compilación. Podríamos haberlos separado en dos archivos diferentes, pero no podríamos haberlos dividido en archivos diferentes: una unidad de compilación debe estar completamente contenida en un archivo fuente. Tenga en cuenta que esta arquitectura no se puede sintetizar porque no describe una función que se pueda traducir directamente a puertas lógicas.

Analiza y ejecuta el programa con GHDL:

$ mkdir gh_work
$ ghdl -a --workdir=gh_work hello_world.vhd
$ ghdl -r --workdir=gh_work hello_world
hello_world.vhd:6:8:@0ms:(assertion note): Hello world!

El directorio gh_work es donde GHDL almacena los archivos que genera. Esto es lo que dice la opción --workdir=gh_work . La fase de análisis verifica la corrección de la sintaxis y produce un archivo de texto que describe las unidades de compilación encontradas en el archivo fuente. La fase de ejecución en realidad compila, vincula y ejecuta el programa. Tenga en cuenta que, en el mcode versión de GHDL, no se generan archivos binarios. El programa se recompila cada vez que lo simulamos. Las versiones gcc o llvm comportan de manera diferente. Tenga en cuenta también que ghdl -r no toma el nombre de un archivo fuente VHDL, como lo hace ghdl -a , sino el nombre de una unidad de compilación. En nuestro caso le pasamos el nombre de la entity . Como solo tiene una architecture asociada, no es necesario especificar cuál simular.

Con Modelsim:

$ vlib ms_work
$ vmap work ms_work
$ vcom hello_world.vhd
$ vsim -c hello_world -do 'run -all; quit'
...
# ** Note: Hello world!
#    Time: 0 ns  Iteration: 0  Instance: /hello_world
...

vlib , vmap , vcom y vsim son cuatro comandos que proporciona Modelsim. vlib crea un directorio ( ms_work ) donde se almacenarán los archivos generados. vmap asocia un directorio creado por vlib con un nombre lógico ( work ). vcom compila un archivo fuente VHDL y, de forma predeterminada, almacena el resultado en el directorio asociado al nombre lógico de work . Finalmente, vsim simula el programa y produce el mismo tipo de salida que GHDL. Tenga en cuenta una vez más que lo que pide vsim no es un archivo de origen, sino el nombre de una unidad de compilación ya compilada. La opción -c le dice al simulador que se ejecute en el modo de línea de comandos en lugar del modo predeterminado de interfaz gráfica de usuario (GUI). La opción -do se utiliza para pasar una secuencia de comandos TCL para ejecutar después de cargar el diseño. TCL es un lenguaje de script muy utilizado en las herramientas EDA. El valor de la opción -do puede ser el nombre de un archivo o, como en nuestro ejemplo, una cadena de comandos TCL. run -all; quit instruye al simulador para que ejecute la simulación hasta que finalice de forma natural, o para siempre si dura para siempre, y luego abandone.

Contador sincrónico

-- File counter.vhd
-- The entity is the interface part. It has a name and a set of input / output
-- ports. Ports have a name, a direction and a type. The bit type has only two
-- values: '0' and '1'. It is one of the standard types.
entity counter is
  port(
    clock: in  bit;    -- We are using the rising edge of CLOCK
    reset: in  bit;    -- Synchronous and active HIGH
    data:  out natural -- The current value of the counter
  );
end entity counter;

-- The architecture describes the internals. It is always associated
-- to an entity.
architecture sync of counter is
  -- The internal signals we use to count. Natural is another standard
  -- type. VHDL is not case sensitive.
  signal current_value: natural;
  signal NEXT_VALUE:    natural;
begin
  -- A process is a concurrent statement. It is an infinite loop.
  process
  begin
    -- The wait statement is a synchronization instruction. We wait
    -- until clock changes and its new value is '1' (a rising edge).
    wait until clock = '1';
    -- Our reset is synchronous: we consider it only at rising edges
    -- of our clock.
    if reset = '1' then
      -- <= is the signal assignment operator.
      current_value <= 0;
    else
      current_value <= next_value;
    end if;
  end process;

  -- Another process. The sensitivity list is another way to express
  -- synchronization constraints. It (approximately) means: wait until
  -- one of the signals in the list changes and then execute the process
  -- body. Sensitivity list and wait statements cannot be used together 
  -- in the same process.
  process(current_value)
  begin
    next_value <= current_value + 1;
  end process;

  -- A concurrent signal assignment, which is just a shorthand for the
  -- (trivial) equivalent process.
  data <= current_value;
end architecture sync;

Hola Mundo

Hay muchas formas de imprimir el clásico "¡Hola mundo!" mensaje en VHDL. El más simple de todos es probablemente algo como:

-- File hello_world.vhd
entity hello_world is
end entity hello_world;

architecture arc of hello_world is
begin
  assert false report "Hello world!" severity note;
end architecture arc;

Un entorno de simulación para el contador síncrono.

Entornos de simulación

Un entorno de simulación para un diseño VHDL (el diseño bajo prueba o DUT) es otro diseño VHDL que, como mínimo:

  • Declara señales correspondientes a los puertos de entrada y salida del DUT.
  • Crea una instancia del DUT y conecta sus puertos a las señales declaradas.
  • Crea una instancia de los procesos que conducen las señales conectadas a los puertos de entrada del DUT.

Opcionalmente, un entorno de simulación puede instanciar otros diseños que el DUT, como, por ejemplo, generadores de tráfico en interfaces, monitores para verificar protocolos de comunicación, verificadores automáticos de las salidas del DUT ...

El entorno de simulación es analizado, elaborado y ejecutado. La mayoría de los simuladores ofrecen la posibilidad de seleccionar un conjunto de señales para observar, trazar sus formas de onda gráficas, poner puntos de interrupción en el código fuente, paso en el código fuente ...

Idealmente, un entorno de simulación debería ser utilizable como una prueba robusta de no regresión, es decir, debería detectar automáticamente violaciones de las especificaciones del DUT, reportar mensajes de error útiles y garantizar una cobertura razonable de las funciones del DUT. Cuando dichos entornos de simulación están disponibles, se pueden volver a ejecutar en cada cambio del DUT para verificar que aún es funcionalmente correcto, sin la necesidad de inspecciones visuales tediosas y propensas a errores de las trazas de simulación.

En la práctica, diseñar entornos de simulación ideales o incluso buenos es un desafío. Con frecuencia es tan difícil, o incluso más, que el diseño del propio DUT.

En este ejemplo presentamos un entorno de simulación para el ejemplo del contador síncrono . Mostramos cómo ejecutarlo utilizando GHDL y Modelsim y cómo observar formas de onda gráficas utilizando GTKWave con GHDL y el visor de formas de onda integrado con Modelsim. Luego discutimos un aspecto interesante de las simulaciones: ¿cómo detenerlas?

Un primer entorno de simulación para el contador síncrono.

El contador síncrono tiene dos puertos de entrada y uno puertos de salida. Un entorno de simulación muy simple podría ser:

-- File counter_sim.vhd
-- Entities of simulation environments are frequently black boxes without
-- ports.
entity counter_sim is
end entity counter_sim;

architecture sim of counter_sim is

  -- One signal per port of the DUT. Signals can have the same name as
  -- the corresponding port but they do not need to.
  signal clk:  bit;
  signal rst:  bit;
  signal data: natural;

begin

  -- Instantiation of the DUT
  u0: entity work.counter(sync)
  port map(
    clock => clk,
    reset => rst,
    data  => data
  );

  -- A clock generating process with a 2ns clock period. The process
  -- being an infinite loop, the clock will never stop toggling.
  process
  begin
    clk <= '0';
    wait for 1 ns;
    clk <= '1';
    wait for 1 ns;
  end process;

  -- The process that handles the reset: active from beginning of
  -- simulation until the 5th rising edge of the clock.
  process
  begin
    rst  <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst  <= '0';
    wait; -- Eternal wait. Stops the process forever.
  end process;

end architecture sim;

Simulando con GHDL

Vamos a compilar y simular esto con GHDL:

$ mkdir gh_work
$ ghdl -a --workdir=gh_work counter_sim.vhd
counter_sim.vhd:27:24: unit "counter" not found in 'library "work"'
counter_sim.vhd:50:35: no declaration for "rising_edge"

Entonces los mensajes de error nos dicen dos cosas importantes:

  • El analizador GHDL descubrió que nuestro diseño crea una instancia de una entidad llamada counter pero esta entidad no se encontró en el work biblioteca. Esto es porque no compilamos counter antes de counter_sim . Al compilar diseños VHDL que crean instancias de entidades, los niveles inferiores siempre deben compilarse antes que los niveles superiores (los diseños jerárquicos también pueden compilarse de arriba hacia abajo, pero solo si crean instancias de component , no entidades).
  • La función rising_edge utilizada por nuestro diseño no está definida. Esto se debe al hecho de que esta función se introdujo en VHDL 2008 y no le dijimos a GHDL que usara esta versión del lenguaje (por defecto usa VHDL 1993 con tolerancia de sintaxis de VHDL 1987).

Vamos a corregir los dos errores y lanzar la simulación:

$ ghdl -a --workdir=gh_work --std=08 counter.vhd counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
^C

Tenga en cuenta que la opción --std=08 es necesaria para el análisis y la simulación. Tenga en cuenta también que lanzamos la simulación en la entidad counter_sim , architecture sim , no en un archivo fuente.

Como nuestro entorno de simulación tiene un proceso que nunca termina (el proceso que genera el reloj), la simulación no se detiene y debemos interrumpirlo manualmente. En su lugar, podemos especificar un tiempo de parada con la opción --stop-time :

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns
ghdl:info: simulation stopped by --stop-time

Tal como está, la simulación no nos dice mucho sobre el comportamiento de nuestro DUT. Volcemos los cambios de valor de las señales en un archivo:

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns --vcd=counter_sim.vcd
Vcd.Avhpi_Error!
ghdl:info: simulation stopped by --stop-time

(ignore el mensaje de error, esto es algo que debe solucionarse en GHDL y eso no tiene ninguna consecuencia). Se ha creado un archivo counter_sim.vcd . Contiene en formato VCD (ASCII) todos los cambios de señal durante la simulación. GTKWave puede mostrarnos las formas de onda gráficas correspondientes:

$ gtkwave counter_sim.vcd

Donde podemos ver que el contador funciona como se espera.

Una forma de onda GTKWave

Simulando con Modelsim

El principio es exactamente el mismo con Modelsim:

$ vlib ms_work
...
$ vmap work ms_work
...
$ vcom -nologo -quiet -2008 counter.vhd counter_sim.vhd
$ vsim -voptargs="+acc" 'counter_sim(sim)' -do 'add wave /*; run 60ns'

introduzca la descripción de la imagen aquí

Tenga en cuenta que la -voptargs="+acc" pasó a vsim : impide que el simulador optimice la señal de data y nos permite verla en las formas de onda.

Con gracia terminando simulaciones

Con ambos simuladores tuvimos que interrumpir la simulación interminable o especificar un tiempo de parada con una opción dedicada. Esto no es muy conveniente. En muchos casos, el tiempo de finalización de una simulación es difícil de anticipar. Sería mucho mejor detener la simulación desde el interior del código VHDL del entorno de simulación, cuando se alcanza una condición particular, como, por ejemplo, cuando el valor actual del contador llega a 20. Esto se puede lograr con una afirmación en el Proceso que maneja el reinicio:

  process
  begin
    rst <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst <= '0';
    loop
      wait until rising_edge(clk);
      assert data /= 20 report "End of simulation" severity failure;
    end loop;
  end process;

Mientras los data sean diferentes de 20, la simulación continúa. Cuando los data llegan a 20, la simulación se bloquea con un mensaje de error:

$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:90:24:@51ns:(assertion failure): End of simulation
ghdl:error: assertion failed
  from: process work.counter_sim(sim2).P1 at counter_sim.vhd:90
ghdl:error: simulation failed

Tenga en cuenta que solo hemos vuelto a compilar el entorno de simulación: es el único diseño que ha cambiado y es el nivel superior. Si hubiéramos modificado solo counter.vhd , habríamos tenido que volver a compilar ambos: counter.vhd porque cambió y counter_sim.vhd porque depende de counter.vhd .

El bloqueo de la simulación con un mensaje de error no es muy elegante. Incluso puede ser un problema cuando se analizan automáticamente los mensajes de simulación para decidir si una prueba automática de no regresión pasó o no. Una solución mejor y mucho más elegante es detener todos los procesos cuando se alcanza una condición. Esto se puede hacer, por ejemplo, agregando una señal boolean fin de simulación ( eof ). Por defecto, se inicializa en false al comienzo de la simulación. Uno de nuestros procesos lo establecerá en true cuando llegue el momento de finalizar la simulación. Todos los demás procesos monitorearán esta señal y se detendrán con una wait eterna cuando se hará true :

  signal eos:  boolean;
...
  process
  begin
    clk <= '0';
    wait for 1 ns;
    clk <= '1';
    wait for 1 ns;
    if eos then
      report "End of simulation";
      wait;
    end if;
  end process;

  process
  begin
    rst <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst <= '0';
    for i in 1 to 20 loop
      wait until rising_edge(clk);
    end loop;
    eos <= true;
    wait;
  end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:120:24:@50ns:(report note): End of simulation

Por último, pero no menos importante, hay una solución aún mejor introducida en VHDL 2008 con el paquete estándar env y los procedimientos de stop y finish que declara:

use std.env.all;
...
  process
  begin
    rst    <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst    <= '0';
    for i in 1 to 20 loop
      wait until rising_edge(clk);
    end loop;
    finish;
  end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
simulation finished @49ns

Señales vs. variables, una breve descripción de la semántica de simulación de VHDL

Este ejemplo trata con uno de los aspectos más fundamentales del lenguaje VHDL: la semántica de simulación. Está dirigido a los principiantes en VHDL y presenta una vista simplificada donde se han omitido muchos detalles (procesos pospuestos, interfaz de procedimiento VHDL, variables compartidas ...) Los lectores interesados ​​en la semántica completa real deben consultar el Manual de referencia de idiomas (LRM).

Señales y variables

La mayoría de los lenguajes de programación imperativos clásicos usan variables. Son contenedores de valor. Un operador de asignación se utiliza para almacenar un valor en una variable:

a = 15;

y el valor actualmente almacenado en una variable se puede leer y usar en otras declaraciones:

if(a == 15) { print "Fifteen" }

VHDL también usa variables y tienen exactamente el mismo rol que en la mayoría de los idiomas imperativos. Pero VHDL también ofrece otro tipo de contenedor de valor: la señal. Las señales también almacenan valores, también se pueden asignar y leer. El tipo de valores que se pueden almacenar en señales es (casi) el mismo que en las variables.

Entonces, ¿por qué tener dos tipos de contenedores de valor? La respuesta a esta pregunta es esencial y está en el corazón de la lengua. Comprender la diferencia entre variables y señales es lo primero que debe hacer antes de intentar programar algo en VHDL.

Ilustremos esta diferencia en un ejemplo concreto: el intercambio.

Nota: todos los siguientes fragmentos de código son parte de procesos. Más adelante veremos qué son los procesos.

    tmp := a;
    a   := b;
    b   := tmp;

Swaps variables a y b . Después de ejecutar estas 3 instrucciones, el nuevo contenido de a es el contenido antiguo de b y viceversa. Como en la mayoría de los lenguajes de programación, se necesita una tercera variable temporal ( tmp ). Si, en lugar de variables, quisiéramos intercambiar señales, escribiríamos:

    r <= s;
    s <= r;

o:

    s <= r;
    r <= s;

¡Con el mismo resultado y sin la necesidad de una tercera señal temporal!

Nota: el operador de asignación de señal VHDL <= es diferente del operador de asignación de variable := .

Veamos un segundo ejemplo en el que suponemos que el subprograma de print imprime la representación decimal de su parámetro. Si a es una variable entera y su valor actual es 15, ejecutando:

    a := 2 * a;
    a := a - 5;
    a := a / 5;
    print(a);

imprimirá:

5

Si ejecutamos este paso a paso en un depurador, podemos ver el valor de a cambio de los iniciales 15 a 30, 25 y finalmente 5.

Pero si s es una señal entera y su valor actual es 15, ejecutando:

    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);

imprimirá:

15
3

Si ejecutamos este paso a paso en un depurador, no veremos ningún cambio de valor de s hasta después de la instrucción de wait . ¡Además, el valor final de s no será 15, 30, 25 o 5 sino 3!

Este comportamiento aparentemente extraño se debe a la naturaleza fundamentalmente paralela del hardware digital, como veremos en las siguientes secciones.

Paralelismo

VHDL es un lenguaje de descripción de hardware (HDL), es paralelo por naturaleza. Un programa VHDL es una colección de programas secuenciales que se ejecutan en paralelo. Estos programas secuenciales se denominan procesos:

P1: process
begin
  instruction1;
  instruction2;
  ...
  instructionN;
end process P1;

P2: process
begin
  ...
end process P2;

Los procesos, al igual que el hardware que están modelando, nunca terminan: son bucles infinitos. Después de ejecutar la última instrucción, la ejecución continúa con la primera.

Al igual que con cualquier lenguaje de programación que admite una forma u otra de paralelismo, un programador es responsable de decidir qué proceso ejecutar (y cuándo) durante una simulación VHDL. Además, el lenguaje ofrece construcciones específicas para la comunicación y la sincronización entre procesos.

Programación

El programador mantiene una lista de todos los procesos y, para cada uno de ellos, registra su estado actual que se puede running , run-able o suspended . Hay como máximo un proceso en estado de running : el que se ejecuta actualmente. Mientras el proceso actualmente en ejecución no ejecute una instrucción de wait , continúa ejecutándose e impide que se ejecute cualquier otro proceso. El programador de VHDL no es preventivo: es responsabilidad de cada proceso suspenderse y dejar que se ejecuten otros procesos. Este es uno de los problemas que los principiantes en VHDL encuentran con frecuencia: el proceso de ejecución libre.

  P3: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    r <= a;
  end process P3;

Nota: variable de a se declara localmente mientras que las señales s y r son declarados en otro lugar, en un nivel superior. Las variables VHDL son locales al proceso que las declara y no pueden ser vistas por otros procesos. Otro proceso también podría declarar una variable llamada a , no sería la misma variable que la del proceso P3 .

Tan pronto como el programador reanude el proceso P3 , la simulación se atascará, la hora actual de la simulación no progresará más y la única forma de detener esto será matar o interrumpir la simulación. La razón es que P3 no tiene una declaración de wait y, por lo tanto, permanecerá en estado de running para siempre, repitiendo sus 3 instrucciones. Ningún otro proceso tendrá la oportunidad de ejecutarse, incluso si es run-able .

Incluso los procesos que contienen una instrucción de wait pueden causar el mismo problema:

  P4: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    if a = 16 then
      wait on s;
    end if;
    r <= a;
  end process P4;

Nota: el operador de igualdad VHDL es = .

Si el proceso P4 se reanuda mientras el valor de la señal s es 3, se ejecutará para siempre porque la condición a a = 16 nunca será verdadera.

Supongamos que nuestro programa VHDL no contiene tales procesos patológicos. Cuando el proceso en ejecución ejecuta una instrucción de wait , se suspende inmediatamente y el programador la pone en el estado suspended . La instrucción de wait también conlleva la condición de que el proceso pueda volver a run-able . Ejemplo:

    wait on s;

Me significa suspender hasta que el valor de la señal s cambios. Esta condición es registrada por el planificador. El programador luego selecciona otro proceso entre los run-able , lo pone en estado de running y lo ejecuta. Y lo mismo se repite hasta que todos run-able procesos ejecutables se hayan ejecutado y suspendido.

Nota importante: cuando se pueden run-able varios procesos, el estándar VHDL no especifica cómo el programador seleccionará cuál ejecutar. Una consecuencia es que, dependiendo del simulador, la versión del simulador, el sistema operativo o cualquier otra cosa, dos simulaciones del mismo modelo VHDL podrían, en un momento dado, hacer diferentes elecciones y seleccionar un proceso diferente para ejecutar. Si esta elección tuviera un impacto en los resultados de la simulación, podríamos decir que VHDL no es determinista. Como el no determinismo es usualmente indeseable, sería responsabilidad de los programadores evitar situaciones no deterministas. Afortunadamente, VHDL se encarga de esto y es aquí donde las señales entran en escena.

Señales y comunicación entre procesos.

VHDL evita el no determinismo utilizando dos características específicas:

  1. Los procesos pueden intercambiar información solo a través de señales.
  signal r, s: integer;  -- Common to all processes
...
  P5: process
    variable a: integer; -- Different from variable a of process P6
  begin
    a := s + 1;
    r <= a;
    a := r + 1;
    wait on s;
  end process P5;

  P6: process
    variable a: integer; -- Different from variable a of process P5
  begin
    a := r + 1;
    s <= a;
    wait on r;
  end process P6;

Nota: los comentarios de VHDL se extienden desde -- hasta el final de la línea.

  1. El valor de una señal VHDL no cambia durante la ejecución de procesos

Cada vez que se asigna una señal, el programador registra el valor asignado, pero el valor actual de la señal permanece sin cambios. Esta es otra diferencia importante con las variables que toman su nuevo valor inmediatamente después de ser asignadas.

Veamos una ejecución del proceso P5 anterior y supongamos que a=5 , s=1 r=0 cuando el planificador lo reanuda. Después de ejecutar la instrucción a := s + 1; , el valor de la variable a cambia y se convierte en 2 (1 + 1). Al ejecutar la siguiente instrucción r <= a; es el nuevo valor de a (2) que se asigna a r . Pero siendo r una señal, el valor actual de r sigue siendo 0. Entonces, cuando se ejecuta a := r + 1; , la variable a toma (inmediatamente) el valor 1 (0 + 1), no 3 (2 + 1) como diría la intuición.

¿Cuándo la señal r tomará realmente su nuevo valor? Cuando el programador haya ejecutado todos los procesos ejecutables y todos se suspenderán. Esto también se conoce como: después de un ciclo delta . Solo entonces el programador verá todos los valores que se han asignado a las señales y actualizará realmente los valores de las señales. Una simulación VHDL es una alternancia de fases de ejecución y fases de actualización de señal. Durante las fases de ejecución, el valor de las señales se congela. Simbólicamente, decimos que entre una fase de ejecución y la siguiente fase de actualización de señal transcurrió un delta de tiempo. Esto no es en tiempo real. Un ciclo delta no tiene duración física.

Gracias a este mecanismo de actualización de señal retardada, VHDL es determinista. Los procesos pueden comunicarse solo con señales y las señales no cambian durante la ejecución de los procesos. Por lo tanto, el orden de ejecución de los procesos no importa: su entorno externo (las señales) no cambia durante la ejecución. P5.a=5 esto en el ejemplo anterior con los procesos P5 y P6 , donde el estado inicial es P5.a=5 , P6.a=10 , s=17 , r=0 y donde el programador decide ejecutar P5 primero y P6 continuación . La siguiente tabla muestra el valor de las dos variables, los valores actuales y siguientes de las señales después de ejecutar cada instrucción de cada proceso:

proceso / instrucción P5.a P6.a s.current s.next r.current r.next
Estado inicial 5 10 17 0
P5 / a := s + 1 18 10 17 0
P5 / r <= a 18 10 17 0 18
P5 / a := r + 1 1 10 17 0 18
P5 / wait on s 1 10 17 0 18
P6 / a := r + 1 1 1 17 0 18
P6 / s <= a 1 1 17 1 0 18
P6 / wait on r 1 1 17 1 0 18
Después de la actualización de la señal 1 1 1 18

Con las mismas condiciones iniciales, si el programador decide ejecutar P6 primero y P5 continuación:

proceso / instrucción P5.a P6.a s.current s.next r.current r.next
Estado inicial 5 10 17 0
P6 / a := r + 1 5 1 17 0
P6 / s <= a 5 1 17 1 0
P6 / wait on r 5 1 17 1 0
P5 / a := s + 1 18 1 17 1 0
P5 / r <= a 18 1 17 1 0 18
P5 / a := r + 1 1 1 17 1 0 18
P5 / wait on s 1 1 17 1 0 18
Después de la actualización de la señal 1 1 1 18

Como podemos ver, después de la ejecución de nuestros dos procesos, el resultado es el mismo, independientemente del orden de ejecución.

Esta semántica de asignación de señal contraintuitiva es la causa de un segundo tipo de problemas que los principiantes de VHDL encuentran con frecuencia: la asignación que aparentemente no funciona porque se retrasa un ciclo delta. Cuando se ejecuta el proceso P5 paso a paso en un depurador, después de asignar r 18 y a a r + 1 , se puede esperar que el valor de a sea ​​19, pero el depurador dice obstinadamente que r=0 y a=1 ...

Nota: la misma señal se puede asignar varias veces durante la misma fase de ejecución. En este caso, es la última asignación la que decide el siguiente valor de la señal. Las otras asignaciones no tienen ningún efecto, como si nunca hubieran sido ejecutadas.

Es hora de verificar nuestra comprensión: vuelva a nuestro primer ejemplo de intercambio y trate de entender por qué:

  process
  begin
    ---
    s <= r;
    r <= s;
    ---
  end process;

en realidad intercambia las señales r y s sin la necesidad de una tercera señal temporal y por qué:

  process
  begin
    ---
    r <= s;
    s <= r;
    ---
  end process;

sería estrictamente equivalente. Trate de entender también por qué, si s es una señal entera y su valor actual es 15, y ejecutamos:

  process
  begin
    ---
    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);
    ---
  end process;

las dos primeras asignaciones de la señal s no tienen efecto, por qué s se asigna finalmente 3 y por qué los dos valores impresos son 15 y 3.

Tiempo fisico

Para modelar hardware es muy útil poder modelar el tiempo físico tomado por alguna operación. Aquí hay un ejemplo de cómo se puede hacer esto en VHDL. El ejemplo modela un contador síncrono y es un código VHDL completo y autónomo que se puede compilar y simular:

-- File counter.vhd
entity counter is
end entity counter;

architecture arc of counter is
  signal clk: bit; -- Type bit has two values: '0' and '1'
  signal c, nc: natural; -- Natural (non-negative) integers
begin
  P1: process
  begin
    clk <= '0';
    wait for 10 ns; -- Ten nano-seconds delay
    clk <= '1';
    wait for 10 ns; -- Ten nano-seconds delay
  end process P1;

  P2: process
  begin
    if clk = '1' and clk'event then
      c <= nc;
    end if;
    wait on clk;
  end process P2;

  P3: process
  begin
    nc <= c + 1 after 5 ns; -- Five nano-seconds delay
    wait on c;
  end process P3;
end architecture arc;

En el proceso P1 la instrucción de wait no se utiliza para esperar hasta que cambie el valor de una señal, como vimos hasta ahora, sino para esperar una duración determinada. Este proceso modela un generador de reloj. Signal clk es el reloj de nuestro sistema, es periódico con un período de 20 ns (50 MHz) y tiene un ciclo de trabajo.

Proceso P2 modelos un registro que, si un borde ascendente de clk acaba de ocurrir, asigna el valor de su entrada nc a su salida c y luego espera para el siguiente cambio de valor de clk .

El proceso P3 modela un incrementador que asigna el valor de su entrada c , incrementado en uno, a su salida nc ... con un retardo físico de 5 ns. Entonces espera hasta que cambie el valor de su entrada c . Esto también es nuevo. Hasta ahora siempre asignamos señales con:

  s <= value;

que, por las razones explicadas en las secciones anteriores, podemos traducir implícitamente a:

  s <= value; -- after delta

Este pequeño sistema de hardware digital podría estar representado por la siguiente figura:

Un contador sincrónico

Con la introducción del tiempo físico, y sabiendo que también tenemos un tiempo simbólico medido en delta , ahora tenemos un tiempo bidimensional que denotaremos T+D donde T es un tiempo físico medido en nano-segundos y D un número de deltas (sin duración física).

La imagen completa

Hay un aspecto importante de la simulación VHDL que aún no discutimos: después de una fase de ejecución, todos los procesos están en estado suspended . Informamos de manera informal que el programador luego actualiza los valores de las señales que se han asignado. Pero, en nuestro ejemplo de un contador síncrono, ¿actualizará las señales clk , c y nc al mismo tiempo? ¿Qué pasa con los retrasos físicos? ¿Y qué sucede después con todos los procesos en estado suspended y ninguno en estado run-able ?

El algoritmo de simulación completo (pero simplificado) es el siguiente:

  1. Inicialización
    • Establezca la hora actual Tc en 0 + 0 (0 ns, 0 delta-ciclo)
    • Inicializa todas las señales.
    • Ejecute cada proceso hasta que se suspenda en una instrucción de wait .
      • Registrar los valores y retrasos de las asignaciones de señales.
      • Registre las condiciones para que se reanude el proceso (retraso o cambio de señal).
    • Calcule la próxima vez Tn como la primera de:
      • El tiempo de reanudación de los procesos suspendidos por una wait for <delay> .
      • La próxima vez que cambie un valor de señal.
  1. Ciclo de simulacion
    • Tc=Tn .
    • Actualizar las señales que deben ser.
    • Ponga en estado de run-able todos los procesos que esperaban un cambio de valor de una de las señales que se ha actualizado.
    • Ponga en estado de run-able todos los procesos que fueron suspendidos por una instrucción de wait for <delay> y cuyo tiempo de reanudación es Tc .
    • Ejecutar todos los procesos ejecutables hasta que se suspendan.
      • Registrar los valores y retrasos de las asignaciones de señales.
      • Registre las condiciones para que se reanude el proceso (retraso o cambio de señal).
    • Calcule la próxima vez Tn como la primera de:
      • El tiempo de reanudación de los procesos suspendidos por una wait for <delay> .
      • La próxima vez que cambie un valor de señal.
    • Si Tn es infinito, detén la simulación. Si no, comienza un nuevo ciclo de simulación.

Simulación manual

Para concluir, ahora ejerzamos manualmente el algoritmo de simulación simplificado en el contador síncrono presentado anteriormente. Decidimos arbitrariamente que, cuando se ejecutan varios procesos, el orden será P3 > P2 > P1 . Las siguientes tablas representan la evolución del estado del sistema durante la inicialización y los primeros ciclos de simulación. Cada señal tiene su propia columna en la que se indica el valor actual. Cuando se ejecuta una asignación de señal, el valor programado se agrega al valor actual, por ejemplo, a/b@T+D si el valor actual es a y el siguiente valor será b en el tiempo T+D (tiempo físico más ciclos delta) . Las 3 últimas columnas indican la condición para reanudar los procesos suspendidos (nombre de las señales que deben cambiar o la hora a la que se debe reanudar el proceso).

Fase de inicialización:

Operaciones Tc Tn clk c nc P1 P2 P3
Establecer hora actual 0 + 0
Inicializar todas las señales 0 + 0 '0' 0 0
P3/nc<=c+1 after 5 ns 0 + 0 '0' 0 0/1 @ 5 + 0
P3/wait on c 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/if clk='1'... 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/end if 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/wait on clk 0 + 0 '0' 0 0/1 @ 5 + 0 clk c
P1/clk<='0' 0 + 0 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 clk c
P1/wait for 10 ns 0 + 0 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c
Calcular la próxima vez 0 + 0 0 + 1 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c

Ciclo de simulación # 1

Operaciones Tc Tn clk c nc P1 P2 P3
Establecer hora actual 0 + 1 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c
Actualizar señales 0 + 1 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
Calcular la próxima vez 0 + 1 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c

Nota: durante el primer ciclo de simulación no hay fase de ejecución porque ninguno de nuestros 3 procesos cumple con su condición de reanudación. P2 está esperando un cambio de valor de clk y ha habido una transacción en clk , pero como los valores antiguos y nuevos son los mismos, esto no es un cambio de valor.

Ciclo de simulación # 2

Operaciones Tc Tn clk c nc P1 P2 P3
Establecer hora actual 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
Actualizar señales 5 + 0 '0' 0 1 10 + 0 clk c
Calcular la próxima vez 5 + 0 10 + 0 '0' 0 1 10 + 0 clk c

Nota: de nuevo, no hay fase de ejecución. nc cambió pero ningún proceso está esperando en nc .

Ciclo de simulación # 3

Operaciones Tc Tn clk c nc P1 P2 P3
Establecer hora actual 10 + 0 '0' 0 1 10 + 0 clk c
Actualizar señales 10 + 0 '0' 0 1 10 + 0 clk c
P1/clk<='1' 10 + 0 '0' / '1' @ 10 + 1 0 1 clk c
P1/wait for 10 ns 10 + 0 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c
Calcular la próxima vez 10 + 0 10 + 1 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c

Ciclo de simulación # 4

Operaciones Tc Tn clk c nc P1 P2 P3
Establecer hora actual 10 + 1 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c
Actualizar señales 10 + 1 '1' 0 1 20 + 0 clk c
P2/if clk='1'... 10 + 1 '1' 0 1 20 + 0 c
P2/c<=nc 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 c
P2/end if 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 c
P2/wait on clk 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
Calcular la próxima vez 10 + 1 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c

Ciclo de simulación # 5

Operaciones Tc Tn clk c nc P1 P2 P3
Establecer hora actual 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
Actualizar señales 10 + 2 '1' 1 1 20 + 0 clk c
P3/nc<=c+1 after 5 ns 10 + 2 '1' 1 1/2 @ 15 + 0 20 + 0 clk
P3/wait on c 10 + 2 '1' 1 1/2 @ 15 + 0 20 + 0 clk c
Calcular la próxima vez 10 + 2 15 + 0 '1' 1 1/2 @ 15 + 0 20 + 0 clk c

Nota: se podría pensar que la actualización de nc se programaría en 15+2 , mientras que la programamos en 15+0 . Cuando se agrega un retardo físico distinto de cero (aquí 5 ns ) a una hora actual ( 10+2 ), los ciclos delta se desvanecen. De hecho, los ciclos delta son útiles solo para distinguir diferentes tiempos de simulación T+0 , T+1 ... con el mismo tiempo físico T Tan pronto como el tiempo físico cambie, los ciclos delta se pueden restablecer.

Ciclo de simulación # 6

Operaciones Tc Tn clk c nc P1 P2 P3
Establecer hora actual 15 + 0 '1' 1 1/2 @ 15 + 0 20 + 0 clk c
Actualizar señales 15 + 0 '1' 1 2 20 + 0 clk c
Calcular la próxima vez 15 + 0 20 + 0 '1' 1 2 20 + 0 clk c

Nota: de nuevo, no hay fase de ejecución. nc cambió pero ningún proceso está esperando en nc .

Simulación ... cambia a inglés para seguir leyendo.

Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow