domingo, 13 de mayo de 2012

Generación de Estímulos y Verificación de Respuestas en Test Bench

Introducción

En este blog describiré lo relacionado a generación de estímulos y verificación de datos en un test bench. NO se describirá lo que es en sí un test bench ni su forma de escribirlo, ya hay mucho de esto en internet, sin embargo hay poca información de cómo generar los datos/relojes/resets para estimular el dispositivo bajo test (usualmente conocido como DUT, device under test).
Básicamente un test bench sirve de estímulo y verificación de datos del componente o dispositivo que se desea verificar. El comportamiento del componente bajo el estímulo definido en el test bench puede verse en una pantalla del simulador usado a tal fin o puede verificarse con el comportamiento esperado usando instrucciones a tal fin en el mismo test bench o también puede guardar los datos resultados en archivos (este último caso no será detallado en este blog). Un diagrama ilustrativo de lo dicho se aprecia en la siguiente figura.



Dentro del código VHDL del test bench podemos encontrar como mínimo los siguientes procesos:
  • Generación de datos de estímulo para el DUT
  • Generación de reloj/es de estímulo para el DUT
  • Generación de señales de inicialización para el DUT
  • Verificación de datos proveniente del DUT bajo un estímulo determinado

Generación de Datos de Estímulo para el DUT

En cualquier sistema que se desee verificar se va necesitar generar datos que simulen el comportamiento de las entradas al DUT. De este modo se deberá, de algún modo, describir en VHDL el comportamiento de estas entradas. Este comportamiento debe estar basado en las especificaciones del sistema que se desea verificar; es decir hay que leer bien las especificaciones del diseño para saber de antemano cual es el comportamiento esperado de las entradas, para luego poder describir ese comportamiento en VHDL/Verilog. Por supuesto, y esto es importante, TAMBIÉN se debe verificar el comportamiento del DUT ante comportamiento no esperado de las entradas.
Se describen a continuación diferentes modos de generar distintos valores para ser asignados a las entradas del DUT.

Generación de Datos I
El siguiente código va cambiando los valores de entrada en el flanco de bajada de reloj. Los valores X1, y X2 son asignados a DATA1 y DATA2 en el flanco negativo del reloj. 


Generación de Datos II
Se pueden generar datos en función del tiempo de simulación. Existen dos modos de hacerlo uno es la generación usando tiempo relativos y la otra es la generación usando tiempos absolutos.
Tiempo Relativo: se van asignando valores a las señales en los tiempos de simulación con respecto al tiempo previo, de una manera acumulada.


Tiempo Absoluto: se asignan valores a las señales en los tiempos absolutos de simulación con respecto al momento del comienzo de la simulación.

  
Generación de Datos Usando Arreglos
Cuando se desea usar como estímulo ciertos valores de datos, los cuales van a ir cambiando en determinada secuencia de tiempo, el uso de arreglos de datos es bastante común, tal como se detalla en el siguiente ejemplo.


 1 architecture array_usage of in_test_benches is
 2
 3    signal Add_Bus : std_logic_vector(7 downto 0);
 4    type stimulus is array (0 to 4) of std_logic_vector (7 downto 0);
 5     
 6    constant DATA : stimulus :=
 7     ("00000000",    -- declara el estimulo
 8      "00000001",    -- como un array.
 9      "00000010",    -- Estos datos seran
10      "00000011",    -- usados para estimular
11      "00000100");   -- las entradas
12
13 begin
14      stim_proc: process
15      begin
16        for i in 0 to 4 loop     -- for-loop que asigna  
17          Add_BUS <= DATA(i); -- a ‘Add_Bus’ un nuevo valor
18           wait for 250 ns;    -- de estimulo cada 10ns
19        end loop;
20      wait;  
21   end process stim_proc;
22 . . .
23 end array_usage;


En el caso descrito en el código arriba, cada dato del arreglo DATA, definido como un arreglo de 5 datos de 8 bits cada uno, es asignado a la señal Add_BUS cada 10 nanosegundos. Este tipo de asignación es totalmente asincrónica, no señal de reloj es usada para nada.
Para una generación de estímulos sincrónica, usando el reloj del sistema bajo test, se puede usar el siguiente código.


 1 architecture array_usage of in_test_benches is
 2   
 3 -- mismas declaracion que el ejemplo anterior
 4
 5 begin
 6   stim_proc: process
 7   begin
 8      for i in 0 to 4 loop
 9        Add_BUS <= DATA(i);
10          for k in 1 to 7 loop
11             wait until rising_edge(clk);
12          end loop;
13        wait until falling_edge(clk);
14      end loop;
15      wait;
16   end process
stim_proc;
17 end array_usage;


En este caso, cada dato del arreglo es asignado a Add_BUS en el flanco de bajada del reloj cada siete flancos de subida del reloj. 

Generación de Reset
La generación de la señal de reset en bastante sencilla, ya que no es una senal que este cambiando valores bastante seguido durante el tiempo de simulación. Un ejemplo se muestra en las siguientes líneas de código.


 1 -- reset totalmente asincronico
 2 reset_proc: process
 3 begin
 4   rst <= '1';
 5   wait for 23 ns;
 6   rst <= '0';
 7   wait for 142 ns;
 8   rst <= '1';
 9   wait for 54 ns;
10   rst <= '0';
11   wait;
12 end process reset_proc;


Tal como se detalla en el código, en este caso se genera un reset asincrónico en la activación (assert) y en la desactivación (desassert). También se destaca la activación del reset en el ‘medio’ de la simulación, no olvidarse de este pequeño detalle que puede hacernos ver algún problema de inicialización cuando el sistema está en funcionamiento normal.

Como ya expliqué en un blog anterior, en realidad es conveniente que un sistema tenga un reset que se active asincrónicamente y se desactive sincrónicamente. Aquí el código del mismo. 



1 -- desactivacion sincronica de reset
2 sreset_proc: process
3 begin
4   rst <= ’1;
5   for i in 1 to 5 loop
6     wait until clk = ‘1;
7   end loop;
8   rst <= ’0;
9 end process sreset_proc;




Verificación de Datos

A fin de contrastar el valor obtenido como salida del DUT ante un cierto estímulo, se usa comúnmente la instrucción assert. De este modo se compara el valor esperado con el obtenido, y dependiendo del resultado de la comparación se lleva a cabo un reporte y se detiene o no la simulación.
 En los test bench la instrucción assert también es usada para comprobar restricciones de tiempo del diseño, pero ese es otro tema y en nuestro caso solo la usaremos para verificación de datos.

La sintáxis de la instrucción assert es bastante sencilla: si la expresión booleana de la instrucción assert es falsa se ejecuta lo que está dentro del assert; normalmente es un cierto reporte, que debería detallar el motivo de la ejecución del assert, y un cierto grado de severidad que indica cuán importante es el error detectado con el assert. Un ejemplo de uso es el siguiente: 



1 assert (out1 = "0110")
2     report"Valor esperad de salida out1 no es igual a 0110"
3       severity ERROR;


En este caso se desea saber si en cierto instante de la simulación la señal out1 tiene el valor “0110”. Si la igualdad es falsa, es decir out1 no es igual a “0110”, se ejecuta el assert reportando el mensaje “El valor de out no es igual a 0110”, y el nivel de severidad asociado es ERROR. En caso de que el problema detectado sea muy grave se puede detener directamente la simulación usando como grado de severidad, severity, FAILURE

Un importante punto para usar el assert con una condición para saber si es correcto el resultado del DUT ante determinado estimulo, es saber en que momento de la simulación, o mejor en que tiempo de simulación se está preguntando por la condición deseada. Para ello se debería hacer un detallado diagrama de tiempo de los estímulos, y en función del tiempo de simulación de determinado estimulo hacer la pregunta del assert en ese preciso tiempo. Esto puede describirse en VHDL usando por ejemplo wait for nnn ms;   hasta llegar al tiempo de simulación deseado y luego usar el assert respectivo. Otro modo de 'esperar' hasta un cierto valor del estimulo es usar un wait until tal como se usa en los test bench ejemplos descriptos a continuación. 
El uso de verificación de datos dentro del test bench es una técnica que tiene muchas ventajas a pesar de ser una tarea complicada la escritura del mismo. Pero la automatización de la verificación ayuda mucho en casos que se tengan diferentes test benches, y se quiera verificar el comportamiento del DUT de manera mas rápida. Este procedimiento se usa mucho en lo que se llama "Regression Tests", en los cuales se corren diferentes test benches en modo batch y la respuestas de cada uno se da a conocer por medio de los resultados de los asserts.  
Más detalle del uso de assert puedes encontrarlo en mi previo blog.

Conclusión


A modo de conclusión de este blog, a continuación se describen tres distintos test bench de un mismo componente (un decodificador 2:4). Cada TB tiene un nivel de complejidad mayor. 

Primer Caso: Test Bench simple



 1 -- Test Bench para verificar
 2 -- el comportamiento de un deco 2:4
 3 entity tb2_decode is
 4 end tb2_decode;
 5
 6 architecture test_bench of tb2_decode is
 7
 8 type input_array is array(0 to 3) of std_logic_vector(1 downto 0);
 9 constant input_vectors: input_array :=   ("00", "01", "10", "11");
10
11 signal in1  : std_logic_vector (1 downto 0);
12 signal out1 : std_logic_vector (3 downto 0);
13
14 component decode
15   port (
16     in1 : in  std_logic_vector(1 downto 0);
17     out1: out std_logic_vector(3 downto 0));
18 end component;
19 begin
20 decode_1: decode port map(
21         in1  => in1,
22         out1 => out1);
23
24 -- generacion de los estimulos
25 apply_inputs: process
26 begin
27   for j in input_vectors‘range loop
28     in1 <= input_vectors(j);
29     wait for 50 ns;
30   end loop;
31   wait;
32 end process apply_inputs;
33
34 -- verificacion de datos
35 test_outputs: process
36 begin
37   wait until (in1 = "01");
38   wait for 25 ns;
39   assert (out1 = "0110")
40     report"Output not equal to 0110"
41       severity ERROR
42   -- check the other outputs
43   wait;
44 end process test_outputs;
45 -- ...
46 end test_bench;



Segundo Caso: Test Bench Elaborado I


 1 -- Test Bench elaborado para verificar  
 2 -- el comportamiento de un deco 2:4
 3 entity tb3_decode is
 4 end tb3_decode;
 5
 6 architecture test_bench of tb3_decode is
 7
 8 type decoder_test is record
 9   in1: std_logic_vector(1 downto 0);
10   out: std_logic_vector(3 downto 0);
11 end record;
12
13 type test_array is array(natural range <>) of decoder_test;
14
15 constant test_data: test_array :=
16    ("00", "0000",
17     "01", "0010",
18     "10", "0100",
19     "11", "1000");
20
21 component decode
22   port (
23     in1 : in  std_logic_vector(1 downto 0);
24     out1: out std_logic_vector(3 downto 0));
25 end component;
26
27 begin
28 decode_1: decode port map(
29         in1  => in1,
30         out1 => out1);
31
32 apply_inputs_test_outputs: process
33 begin
34   for j in test_data’range loop
35     in1 <= test_data(j).in1);
36     wait for 50 ns;
37        assert (out1 = test_data(j).out1)
38         report "Output not equal to the expected value"
39       severity ERROR;
40   end loop;
41 end process apply_inputs_test_outputs;
42
43 end test_bench;



En este caso, el arreglo es un poco más complicado, ya que se tienen dos columnas de datos de diferente tamaño cada una. La primer columna representa el estimulo, mientras que la segunda columna representa el valor esperado como respuesta del sistema. Al describir el estímulo de esta manera de puede realizar la comparación, usando assert, dentro del mismo for-loop usado para la generación de estímulos tal como se detalla en el código ejemplo de arriba.
Otro punto a destacar de este ejemplo es el hecho que el rango de test_array no ha sido definido en su declaración. Sin embargo cuando se declara la constante test_data el rango del arreglo es definido, de este modo el analizador de VHDL no tendrá problemas en asignarle el rango a la senal test_array durante el proceso de análisis. De esta manera se facilita el cambio del tamaño de la constante sin tener que cambiar la declaración del arreglo. Del mismo modo se explica el uso de ‘range’ en el for-loop, así el for-loop se ajustara al tamaño del arreglo, sin tener que modificarlo cada vez que se cambie el tamaño del arreglo.

Tercar Caso: Test Bench sincrónico
En este caso se debe tener en cuenta el reloj, por ello para se describe un proceso un poco complicado en el cual se genera el reloj, se generan los datos estímulos y también se verifica el resultado. A mi particularmente no me gusta mucho mezclar tanto, sino mas bien tener un proceso para cada tipo de datos, y uno o más para la verificación de datos tal como se detalla mas arriba, pero a modo de ejemplo para tener más idea de lo que se puede hacer, el código que se detalla es un buen ejemplo.


 1 -- ...
 2 begin
 3 decode_1: decode port map( in1 => in1, out => out1);
 4 apply_inputs: process
 5 begin
 6   for j in test_data’range loop
 7     in1 <= test_data(j).in1);
 8     clk <= ‘0;
 9     wait for 5 ns;
10     clk <= ‘1;
11     wait for 5 ns;
12        assert (out1 = test_data(j).out1)
13         report "Output not equal to the expected value"
14       severity ERROR;
15   end loop;
16 wait;
17 end process apply_inputs;
18 -- ...

Espero q este blog sea útil ¡ ¡ ¡ Para la version .pdf click acá :)

jueves, 5 de abril de 2012

Modelacion Funcional de Bus (Bus Functional Model)


Introducción

BFM es una sigla muy usada en el ámbito de diseño de HDL-FPGA, sin embargo no todos saben que es ni a que se refiere, otros diseñadores se dedican a escribir BFMs por lo que saben muy bien lo que es. Bueno, este blog es para los que pertenecen al grupo que dice “Qué es eso?” cuando escucha ‘BFM’.

Básicamente como su nombre lo indica, BFM es un modelo de simulación simplificado que describe correctamente la funcionalidad de I/O bus de un sistema normalmente complejo, sin inmiscuirse con el comportamiento interno de ese componente.

Este modelo se usa dentro del test bench como estimulo (stimulus) del sistema que se quiere testear (DUT), permitiendo en forma directa una comunicación entre el DUT y el sistema externo con el que se quiere interactuar, evitando de esta manera tener que escribir un muy complejo test bench. La siguiente figura muestra el sistema de simulación cuando se usa un BFM.


Figura 1 – Esquema de simulación usando BFM

Explico ahora los distintos componentes en la Figura 1:
Test Code: es el código del test bench en sí, escrito en VHDL. En este parte del test bench se escribe el código VHDL para estimular el DUT, principalmente haciendo uso de los procedimientos que modelan algún ciclo de I/O del componente BFM instanciado en el test bench. 
BFM: es el código BFM del sistema que interactúa con el DUT. Este código es provisto por el fabricante del sistema/memoria o escrito por alguno de nosotros. Dentro del test bench se crea una declaración de este componente y su respectiva instanciación. Actúa como generación de estimulo para el DUT al proveer un preciso estimulo del protocolo de comunicación, ciclo por ciclo en funcionalidad y tiempo. Al mismo tiempo el BFM acepta comandos para seleccionar la funcionalidad a ser modelada. Estos comandos son normalmente asignados a alguna señal en el test bench, declarada en el  paquete, y ejecutados por el procedimiento respectivo del BFM. Este bloque básicamente se codifica como una simple maquina de estados, cuyos estados por ejemplo pueden ser read, write, idle. Y de acuerdo a lo que el test code ‘solicita’ la máquina de estados pasa al respectivo estado y activa las I/O necesarias del sistema modelado (BFM).
DUT: es el device under test que queremos depurar.
Package: es la unidad de diseño que contiene todas las declaraciones y descripciones de las señales, funciones, procedimientos relacionadas al test bench y al BFM. Actúa como medio de comunicación entre las señales del test bench y los procedimientos/funciones del BFM.
El símbolo * lo uso para detallar que el código de test y el BFM ‘pueden’ (no es siempre el caso) proveer no solo el estimulo al DUT, sino también chequear la respuesta del DUT a cierto estimulo. Esta funcionalidad es opcional, sin embargo es altamente aconsejada para automatizar el test y proveer un rápido resultado del comportamiento del DUT bajo determinado estimulo.

Quién escribe un BFM?

Normalmente para componentes complejos, por ejemplo memorias DDR, Flash, dispositivos PCI, PCIe, etc; el fabricante ofrece un modelo BFM para que el diseñador pueda verificar que el protocolo que ha diseñado interactúa correctamente con el componente con el que quiere comunicarse.
Ahora, bien el BFM, es en sí un modelo escrito en VHDL por un ingeniero de la empresa fabricante, por lo que el modela será tan bueno como el ingeniero que lo escriba.
Debo aclarar que hay algunas empresas que escriben sus propios BFMs a fin de facilitar la simulación de componentes, normalmente varios, que tienen protocolos de comunicación complejos. En algunas de estas empresas tuve la oportunidad de escribir y usar algunos BFModels, tarea que al principio era linda, pero luego se transformó en tediosa… :) 

Porqué usar BFM?

El proceso de prueba (simulación) a veces es una tarea compleja que involucra muchas horas de escritura del código (test bench) y otras más de correr la simulación. Normalmente más de la mitad del tiempo de un proyecto involucra la simulación del mismo, y más se incrementa este tiempo cuando la complejidad del proyecto crece. Por ello es necesario usar métodos de prueba (test) más eficientes y que sigan asegurando un diseño libre de errores.
El uso de BFM habilita el test de complejos protocolos y funciones con un gran nivel de abstracción de la funcionalidad interna del componente simulado, menos instrucciones a simular, preciso timing de la interface, todo lo que resulta en un test más robusto, completo y sobre todo más rápido de ejecutar. Esta última es una característica que se destaca cuando se comparan los tiempos de simulación de sistemas complejos, IP cores, etc., que usualmente toman un largo tiempo en simular.

Características de un BFM

  • Provee un medio para modelar la interface del componente que se está modelando, SIN importar la estructura interna del componente o si el componente en si tiene un microprocesador, una o varias FSMs, etc.
  • Provee información de todos los tiempos del protocolo de comunicación de I/Os.
  • Provee chequeo funcional para verificar el correcto funcionamiento del protocolo (handshaking).
  • Normalmente son escritas usando las instrucciones típicamente usadas en Test Bench. Paquetes, funciones, procedimientos, registros y tipos son comúnmente encontradas en BFMs.
  • El alto nivel de abstracción que VHDL ofrece permite modelar complejos sistemas, en funcionalidad y tiempo, abstrayendo al diseñador de la complejidad interna del sistema modelado. 

Ejemplo Flujo de Uso de BFM

La siguiente figura detalla el procedimiento que se debería seguir cuando se usa un BFM.

 
Figura 2 - Pasos a seguir para usar los procedimientos del BFM

Paso 1: Dependiendo de la funcionalidad del BFM que se desea invocar, en el código del test bench se genera un llamado al procedimiento respectivo asignando los valores necesarios. Por ejemplo para comenzar un ciclo de escritura (Wr), habrá que asignar a la senal respectiva un ‘valor’ que entienda el procedimiento que se desea invocar.
Paso 2: El procedimiento invocado seguramente necesita generar algún dato o señal antes de invocar al procedimiento principal.
Paso 3: El procedimiento principal primero solicita la disponibilidad del bus de I/O, normalmente por medio de alguna señal de pedido (request) de bus. En caso que el bus no este disponible, por ejemplo porque todavía se está ejecutando algún otro procedimiento, deberá esperar hasta que el bus esté disponible.
Paso 4: Las I/O del BFM son controladas por el procedimiento principal con el comportamiento deseado (Wr/Rd, etc.) y con el respectivo timing. Una vez finalizado el ciclo, el procedimiento avisa por medio de la misma senal de ocupado (busy)  desacertándola. Permitiendo así el recomienzo del ciclo.

Escritura de BFMs

Uno de los principales usos de BFMs es por ejemplo para representar ciclos de Wr/Rd del bus del componente que se desea modelar. Estos ciclos normalmente se describen mediante el uso de funciones y procedimientos definidos en un paquete para permitir la accesibilidad no solo del BFM pero también del test bench y del código VHDL  en sí.
Los ciclos de Wr/Rd son fácilmente descriptos mediante el uso de procedimientos (procedures) que modelan perfectamente el comportamiento de los ciclos de Wr/Rd ya sea sobre memoria o sobre I/O, no solo funcionalmente sino también con los tiempos respectivos. La BFM debe ser capaz de leer un comando, por ejemplo, y devolver la respuesta a ese comando, de ese modo se prueba tanto la escritura como la lectura de mi componente sobre el componente modelado con BFM. Al usar procedimientos y ser un modelo de un sistema toda la potencia del lenguaje VHDL puede ser usada al escribir el BFM.
La entidad/arquitectura del BFM debe ser instanciada junto con nuestro componente (DUT) dentro del test bench. Mínima configuración del BFM a veces es necesaria al comienzo del TB, hasta que pueda comenzar el handshaking entre mi componente y el componente BFM.
Ejemplo del código escrito para una BFM.
Declaraciones en el paquete:

 1 -- declaraciones de type y señales --    
 2 type cycle_op is (idle, nop, read, write);
 3
 4 type instruction_op is record
 5    bus_cycle   : cycle_op;
 6    addr_bus    : std_logic_vector(15 downto 0);
 7    data_bus    : std_logic_vector(15 downto 0);
 8    rd_data     : std_logic_vector(15 downto 0);
 9    bus_request : std_logic;
10    busy        : std_logic;
11 end record;
12
13 signal cmd     : instruction_op :=  (idle,
14                                     (others => '0'),
15                                     (others => '0'),
16                                     (others => 'Z'),
17                                     '0',
18                                     'Z');
 
En este ejemplo el registro (record) declara las señales a ser usadas para generar el estímulo de las I/O, como así también información a pasar al procedimiento que debe ejecutar el ciclo seleccionado. Es decir se declara:
  • Tipo del ciclo (idle, nop, read, write)
  • Dirección a ser utilizada en el bus de direcciones
  • Dato a ser escrito si es un ciclo de escritura, o dato que se espera leer si es un ciclo de lectura
  • Data leído
  • Señal de pedido de bus (bus_request). Se activa para solicitar comienzo de un ciclo.
  • Señal de bus ocupado (busy). Se activa durante el proceso de ejecución de algún ciclo. 
Un procedimiento de un ciclo de escritura puede ser escrito tal como se detalla a continuación:

1 -- declaracion del procedimiento wr_cycle
2 procedure wr_cycle (
3    constant p_addr_bus : in std_logic_vector(15 downto 0);
4    constant p_data_bus : in std_logic_vector(15 downto 0);
5    signal   p_cmd_op   : inout instruction_op
6    ) is
7 begin
8     main_cycle(write,p_addr_bus, p_data_bus,p_cmd_op);
9 end wr_cycle;

 

Donde el procedimiento main_cycle es el siguiente:

 1 -- declaracion procedimiento main_cycle
 2 procedure main_cycle(
 3    constant p_cycle    : in cycle_op;
 4    constant p_addr_bus : in std_logic_vector(15 downto 0);
 5    constant p_data_bus : in std_logic_vector(15 downto 0);
 6    signal   p_cmd_op   : inout instruction_op
 7    ) is
 8 begin
 9   p_cmd_op.request <= '0';
10   if p_cmd_op.busy /= '0' then
11       wait on p_cmd_op.busy until p_cmd_op.busy = '0';
12   end if;
13   p_cmd_op.request   <= '1';
14   p_cmd_op.bus_cycle <= p_cycle;
15   p_cmd_op.addr      <= p_addr;
16   p_cmd_op.data      <= p_data;
17   if (p_cmd_op.busy = '0') then
18      wait on p_cmd_op.busy until p_cmd_op.busy = '1';
19   end if;
20   p_cmd_op.request    <= '0';
21 end;

Este procedimiento lleva a cabo las tareas de ‘handshaking’, usando las señales busy y request, y de comunicación entre el test code y el BFM.
Ahora, como invoco las distintas funciones del BFM dentro del test bench? La siguiente línea de código, escrita en el test bench, muestra como se puede invocar el procedimiento del ciclo de escritura:  

1 wr_cycle(x"0000",x"05ab",cmd_op);

Entonces lo que pasa es lo siguiente:
  • El procedimiento wr_cycle esta declarado en un paquete (ver figura 1)
  • El procedimiento wr_cycle le pasa los parámetros necesarios al procedimiento main_cycle
  • El procedimiento main_cycle lleva a cabo el handshaking, y pasa el comando (cmd_op) a la máquina de estados (FSM, Finite State Machine) de la BFM.
  • Al ser un comando de escritura la máquina de estado pasa al estado escritura donde procederá a activar las señales correspondientes del ciclo de escritura, y habilitar las de espera que el proceso se cumpla (ready). Este último paso es el que realmente ‘ve’ el DUT.
La siguiente figura detalla la modificación de la primer figura al ejecutarse un ciclo de escritura, mostrando gráficamente los pasos recién enumerados.


Figura 3 - Detalle de los procedimientos y código ejecutados durante un ciclo de escritura del BFM

Unos puntos a considerar de la Figura 3:
  • La línea punteada entre el bloque Test Code y Package se debe a que en realidad el procedimiento wr_cycle está definido en el paquete pero, como bien sabemos, cuando se compila el código de Test Code, el procedimiento definido en el paquete es ‘insertado’ en el lugar en que éste es invocado
  • Lo mismo sucede con el procedimiento main_cycle. El cual es también ejecutado desde Test Code
  • Tal como se puede deducir del código de los procedimientos detallados anteriormente (wr_cycle, main_cycle), la señal cmd_op, definida como un registro, contiene toda la información necesaria para que la FSM del componente BFM genere el correspondiente ciclo de escritura en las líneas de I/O de este bloque (recordar que el componente BFM está instanciado dentro del test bench).

Espero que este artículo sirva para, por lo menos, tener una idea de la pregunta: Qué es un BFM?

martes, 13 de marzo de 2012

VHDL 2008: Declaraciones de Contexto (Context Declarations)

En VHDL 2008 hay una nueva unidad de diseño (desgin unit) llamada Context Declaration, o Declaración de Contexto (DC). 

Que es y que hace la DC? 
En primer lugar el estándar VHDL-2008 pre-define una declaración de contexto de la siguiente manera para usar el paquete numeric_std:

1 context ieee_std_conext is
2   library ieee;
3   use ieee.std_logic_1164.all;
4   use ieee.numeric_std.all;
5 end;


 De este modo en mi proyecto puedo directamente escribir: 

1 context ieee.ieee_std_conext;
2
3 entity ...

Pero, así como está pre-definido el contexto arriba mencionado, VHDL2008 permite definir 'user defined' contextos, permitiendo de este modo juntar en un solo archivo TODOS los paquetes que se usan para un proyecto específico. Por ejemplo puedo definir:

1 context mis_paquetes is
2  library ieee;
3  use ieee.std_logic_1164.all;
4  use ieee.numeric_std.all;
5  use my_library.all;
6  use ieee.fixed_pkg.all;
7  .....
8 end;

Donde se escribe una DC?
Ahora bien, como ya dije al comienzo una DC es una design unit, tal como lo es una entidad, un paquete, una configuración, por lo que una DC DEBE escribirse en un archivo aparte, por ejemplo mis_paquetes_context.vhd, e incluirlo dentro del proyecto como cualquier otro desing unit (del mismo modo que se incluye un paquete por ejemplo, puedes ver el blog al respecto). 

Uso del DC?
Una vez definida, se debe incluir en el proyecto la design unit, para usarla en los diferentes componentes del proyecto. 
De este modo en cada otra design unit se debería escribir lo siguiente:

1 context work.mis_paquetes;
2
3 entity . . . .

El uso de 'work.' se debe a que por defecto la mayoría de las herramientas compilan los unidades de diseño en esa librería. Esto puede cambiarse a gusto por el diseñador.
 
Cuidado/Advertencia -> Compatibilidad
Antes de hacer uso de esta característica de VHDL 2008 deberías verificar que tu herramienta de síntesis soporte VHDL 2008. Debido q esto depende de q herramienta de síntesis estés usando, XST, Quartus, Synplify, Precision, etc; no te puedo guiar al respecto, pero normalmente es una tarea sencilla. 
El otro problema q uede presentarse es que la herramienta de síntesis soporte VHDL 2008, pero la de simulación no ! :) ... Por lo que directamente aconsejo verificar ambas herramientas antes de comenzar a trabajar con CD. 

Hasta pronto ! 

jueves, 19 de enero de 2012

Problemas con System Generator / Simulink - 2

Problema 2 - Importando Múltiples Sistemas generados por SysGen en un modulo top en ISE


Cuando se trabaja con sistemas complejos puede suceder que se genere el sistema Simulink-SysGen para cada uno de ellos, y luego en el ambiente ISE escribir el modulo (entity-architectura) que interconecta los diferentes sistemas. Teóricamente este proceso debería hacerse sin problemas, sin embargo HAY PROBLEMAS no documentados, q llevaron largas horas de debug  hasta encontrar primero la fuente del problema y luego la solución. 
El siguiente gráfico detalla los distintos archivos y flujo de síntesis en el caso de que múltiples sistemas generados por separado por System Generator desean ser integrados en un solo modulo de mas alto nivel. 




System Generator crea un archivo de proyecto con la extensión .sgp que puede agregarse como una fuente a Project Navigator en el ISE. Este archivo contiene toda la información necesaria sobre el diseño en System Generator.
Sin embargo, cuando se realiza un diseño jerárquico teniendo dos o más diseños de System Generator como componentes del modulo de mayor jerarquía, se deben renombrar las librerías HDL (autmaticamente generadas por System Generator) para evitar conflictos de nombres y otros comportamientos no deseados durante la simulación. 
System Generator tiene un comando que cambia los nombres de la librería para todos los archivos relacionados en el diseño de un proyecto de System Generator. Además, también hace una copia de seguridad en el caso de que se quiera recuperar el nombre de la biblioteca original. La siguiente es la sintaxis de este comando:

XLSWITCHLIBRARY (target_dir, FROM_LIB, TO_LIB)

Reemplaza todas las bibliotecas HDL referidas como FROM_LIB con TO_LIB en un diseño de System Generator ubicado en la carpeta target_dir.
Importante: este comando se debe ejecutar desde la consola de Matlab.


Modo de Uso 
Suponiendo que se tienen dos proyectos de System Generator cuyos nombres son 'delay_bram' y 'eco_bram', la ejecución del comando XLSWITCHLIBRARY para el cambio del nombre de la librería 'work' (nombre dado por defecto por SysGen) a otro nombre (dado por el usuario) se detalla a continuación: 



Una ejecutado este comando, los sistemas generados por SysGen pueden ser usados como componentes en ISE sin tener problemas.

PD: de nuevo gracias a Layla y Esteban por este aporte. 

miércoles, 30 de noviembre de 2011

Problemas con System Generator / Simulink - 1

Hace poco terminé de dictar uno de mis cursos favoritos basado en el flujo de diseño DSP/FPGA. El curso tuvo mucho éxito, pero como siempre que se implementa algo surgen problemas que solo aparecen cuando uno hace algo.... la teoría es muy linda hasta que  se tiene que implementar.... :) . . . 

Uno de los grupos de alumnos, Layla y Esteban, tuvieron problemas que no estaban documentados por ningún lado, y a fin de que Uds. no gasten tanto tiempo como ellos en encontrar soluciones, decidieron compartir problemas con soluciones. 

Problema 1- Error Debido a la Configuración de Idiomas de la PC
Dentro de un bloque Simulink cuando se le asigna una constante expresada en decimal a un bloque 'CMult' de System Generator, y luego al querer simular el sistema aparece un error relacionado al bloque pero cuyo mensaje es bastante encriptado.... 




Seguramente este mensaje de error es generado porque existe un problema de 'mal entendimiento' en la representación de números decimales: System Generator lo representa de un modo diferente al modo representado por el sistema operativo de la PC. Esto es muy común que nos pase a los que tenemos la PC configurada para el idioma español. Por defecto el número decimal, en todas las opciones del español, se representa con una coma. Mientras que System Generator lo representa con un punto. entonces !, existe una incongruencia que el compilador no sabe que hacer y genera el mensaje de error encriptado. En el ejemplo mostrado en la figura arriba el error se genera por querer asignar un valor de 2,3 a CMult. Si en su lugar se asigna 2.3  no existe ningún error. 

Solución
Cambiar la configuración regional de su PC a Inglés: Panel de Control -> Opciones Regionales y Lenguaje.

viernes, 11 de noviembre de 2011

Error: "Port 'clock' has illegal connections.This port is connected to an input buffer and other componentes"

Este mensaje de error realmente no de mucha idea de cual es el problema, es por ello que deseo aclararlo acá.

Lo que realmente significa este mensaje, es que la entrada de reloj () está siendo ruteada al buffer de entrada y de alli a un DCM, por ejemplo. Por ello no es posible usar esta misma señal de entrada, clock, como señal de reloj en cualquier componente sincrónico. Esto se debe a que cuando uno usa un DCM el ruteo entre el pin de entrada y el DCM es un ruteo dedicado de bajo skew y bajo retardo, y no tiene derivaciones, es decir no puede usarse esa señal en otro lugar porque no tiene ruteo disponible. Así, el unico reloj disponible es la salida del DCM. 

Un solución a este problema es usar otro pin de E/S por el que entra el mismo reloj, pero sin enviarlo a un DCM. En este caso se puede usar un pin E/S como entrada de reloj del DCM, y otro pin E/S, con el mismo reloj, que puede ser usado para los componentes sincronicos que necesiten ser controlados por este reloj. 

La otra solución es usar también dos pines E/S, y dos DCMs, uno para cada frecuencia de trabajo que se necesite.

Greetings.....