Mostrando entradas con la etiqueta Synplify. Mostrar todas las entradas
Mostrando entradas con la etiqueta Synplify. Mostrar todas las entradas

domingo, 28 de octubre de 2012

Replicación de Lógica - Inserción de Buffer - Alta Cargabilidad (High Fan-Out)

Introducción

Entre las tantas tareas que llave a cabo la herramienta de síntesis, hay una que se encarga de revisar la cargabilidad (fan-out) de cada señal lógica.Por defecto existe un número límite máximo de cargabilidad permitido. Este numero básicamente depende de la familia del FPGA con que se este trabajando. Por supuesto existe la opción de incrementar ese número, aunque no es lo mas aconsejable. La mejor solución para casos en que la cargabilidad de una señal sea mayor a la permitida, es lo que técnicamente se llama replicación de la lógica que genera la señal de tan alta cargabilidad. La cargablidad de cada registro, compuerta lógica y cualquier otro elemento lógico es analizado por el compilador de la herramienta de síntesis. Si el numero de cargabilidad encontrado supera al establecido por el diseñador, el compilar replica el nodo fuente de la señal hasta que el número de cada copia esté por debajo del número máximo de cargabilidad permitido. 

Cargabilidad (Fan-out)

Fan-out es definido como el número máximo de entradas de componentes lógicos que pueden ser conectadas con la salida de otro componente lógico. De acuerdo al diseño o circuito que se implementa algunas señales internas del mismo (salidas) se conectan a las entradas de otros componentes. Esta cantidad de conexiones puede ser un número desde 1 hasta un numero muy alto (por ej. 10000), este es el números que se conoce como fan-out. No hace falta que uno sea el que lleva la cuenta de la cargabilidad de cada señal, todas las herramientas de síntesis detallan en su reporte las señales que tienen un alto fan-out. 
Por qué existe un número máximo de fan-out? El número conexiones máximas están limitadas,  por un lado, por la degradación que puede tener la señal de del alta cargabilidad debido a que la principal carga que ve la salida es la capacitancia de entrada de los componentes a los que está conectada; así mientras mas conexiones tiene la salida, más capacitancia, más tarda en subir (rise-time) y en bajar (fall-time) la señal. Al tardar mas en subir y en bajar, la señal sufre un retardo generado solo por la cargabilidad, de este modo el rendimiento del sistema puede verse reducido. Finalmente, en el caso de los FPGAs, otro problema de una señal con un número alto de fan-out es que crea dificultades para rutear la señal dentro del FPGA, a no ser que se use una ruta dedicada para la señal de alta cargabilidad. 
Normalmente las herramientas de síntesis de los distintos fabricantes tienen un valor de fan-out para todas las señal (valor global) configurado por defecto. Sin embargo, este valor puede ser sobrescrito usando ya sea restricciones en el archivo correspondiente, (.sdc para Altera; .ucf para Xilinx), o usando atributos de síntesis, cuyo nombre depende de la herramienta de síntesis que se use.  


En el caso de Synplify, la herramienta permite configurar el valor de fanout máximo desde una ventana de configuración: 
Project -> Implementation Options -> Device

en la opción Fanout Guide se muestra el numero máximo de fanout.




El valor configurado para fanout, se aplica a TODAS las señales del diseño, se por ello se lo denomina fanout global. Tal como se explico anteriormente este valor no puede ser demasiado alto, pero tampoco demasiado bajo. En este último caso se corre el riesgo que la herramienta de sintesis duplique lógica o inserte buffers sin realmente necesitarse. Ambos extremos afectan el ruteo y el rendimiento del diseño. 
Para sobrescribir el fnaout global e imponer un fanout distinto en un mas bajo nivel se puede usar el atributo:

syn_maxfan 

Este atributo se aplica a distintos niveles de jerarquía del diseño, descripto en el siguiente cuadro: 


Cuál es la diferencia entre límite duro y blando? 
En el limite blando la herramienta puede o no puede replicar la logica o introducir un buffer, una vez superado el valor límite. En el caso de aplicar syn_maxfan a un registro por ejemplo, si se supera el valor de fanout configurado por el atributo SI o SI la lógica es replicada o se agrega un buffer a la señal.  
A continuación se presenta un ejemplo de un valor alto de fanout que afecta el rendimiento máximo del sistema. Como puede apreciarse el retardo de ruteo es excesivamente largo, principalmente debido a un muy alto valor de fan-ou (seguramente también esta señal no esta usando un ruteo dedicado). 


Esta señal, sig, es un típico caso en el que un alto fanout está afectando la frecuencia máxima de funcionamiento del sistema, por lo que se debería replicar la lógica generada de esa señal.

Quartus Configuración y Atributo

El atributo que controla el valor de fanout máximo en la herramienta de síntesis del Quartus II maxfan
El valor global de fanout se configura en en el Assignment Editor

XST Configuración y Atributo

El atributo que controla el valor de fanout máximo en la herramienta de síntesis XST del ISE de Xilinx se denomina max_fanout
El valor global de fanout se configura en: 
Process -> Properties -> Xilinx Specific Options -> Max Fanout

Detalles Lógicos

La herramienta de síntesis a fin de cumplir con los requisitos del valor máximo de fanout, puede hacer dos cosas: 
- replicación de lógica
- introducción de un buffer

 

Replicacion de Lógica

Cuando el fanout de un registro o una lógica combinacional supera el valor configurado, la herramienta de síntesis reduce el fanout al replicar la lógica de salida, ya sea secuencial o combinacional.

 

Introducción de Buffer

Un buffer es introducido por la herramienta de síntesis cuando una señal de entrada al FPGA tiene un alto fanout. 

 

Replicación Manual

Teniendo en cuenta que no todas las señales con alta cargabilidad originan problemas de bajo rendimiento, se puede proceder a una replicación manual del componente con una alta cargabilidad. Para ello en el código VHDL o Verilog se describe el componente las veces que sea necesario. Un punto muy importante en estos casos, es el uso de atributos de síntesis para prevenir que la lógica replicada sea optimizada por la herramienta de síntesis. Para ello se usan atributos tales como syn_preserve, syn_keep (en el caso de usar Synplify).


Excepciones

Como siempre ocurre hay excepciones a estas reglas y algunas son las siguientes:
  • Se puede 'exigir' a la herramienta de síntesis que use un 'buffer' en lugar de replicar la lógica. En el caso de Synplify esto se logra configurando el atributo syn_replicate a valor lógico '0', ya sea globalmente, o sobre el modulo o sobre un registro en particular. Este atributo previene la replicación, por lo que el software usará buffers para evitar los problemas relacionados con un fanout muy elevado
  • Para especificar que un puerto de entrada, por ejemplo una entrada de reloj, no sea usada con un buffer, se puede usar el atributo syn_noclockbuf, sobre esa entrada. Esto se usa comúnmente cuando se esta muy al limite con el numero de buffers globales y se necesita el buffer para una senal que no tiene un alto fanout pero que tiene una restricción de tiempo muy critica. 
  • Para casos en que el rendimiento del sistema no se muy crítico, es decir cuando la frecuencia de trabajo del sistema es baja o muy baja, se puede directamente evitar cualquier replicación de lógica o introducción de buffer al configurar el valor de fanout a un numero muy alto.  
  • Otros atributos que se pueden usar para que la lógica no sea replicada y en su lugar se introduzca un buffer, son los atributos syn_keep y syn_preserve. syn_keep es usado cuando se quiere mantener una señal, mientras que syn_preserve se utiliza para preservar un registro. En ambos casos si se supera el numero máximo de fanout, un buffer sera introducido en lugar de replicar la lógica.
  •  

Conclusión 

Es importante resaltar que las señales con alta cargabilidad en un diseño  no deberían ser tocadas a menos que esté en riesgo el rendimiento del diseño, es decir que la técnicas detalladas mas arriba se deberían usar en caso que la señal que tiene alta cargabilidad este afectando la frecuencia máxima a la que se desea funciones el diseño
La cargabilidad de las señales es detallada en los distintos reportes presentados por la herramientas de síntesis, ya sea Synplify, Quartus, XST, etc. En función de ese número y de que si se trata de una señal que es crítica para el máximo rendimiento del diseño, es que se deben aplicar las técnicas presentadas en este blog, para sea replicar la lógica o insertar un buffer.

viernes, 3 de junio de 2011

Memorias ROM: FPGA-VHDL, Cómo? ? ? ?

Básicamente hay dos formas de que una memoria ROM sea implementada en un FPGA a partir del código VHDL:
- Deduciendo la memoria desde el código VHDL. Comúnmente conocido como Inferir.
- Generando la memoria a partir de la herramienta de generación de IPs ofrecida por el vendedor del FPGA (CoreGen, MegaWizard, etc).
Por portabilidad y code-resuse el primer método, inferir, es el mas aconsejado. Pero por supuesto tambien se puede recurrir a los IPs del FPGA (segundo caso).
A su vez dentro del metodo de inferir hay diferentes opciones para el modo de 'cargar' la ROM:
1- declarando un tipo (type), luego una señal de ese tipo y asignarle a la señal valores constantes (datos de la ROM)
2- declarando los datos de la ROM en un archivo (data file) y usar instrucciones de apertura y lectura de archivos  para leer los datos.
3- usando instrucción 'case'.

Analizando las distintas opciones en gral podemos decir el caso 3 se usa cdo la memoria es pequeña, de lo contrario queda un 'case' tremendamente largo y un poco anti-estético. El caso 2 depende del entorno del proyecto- hay empresas en las que no les gusta o no están de acuerdo con el acceso a disco para leer/escribir datos, otras que directamente lo prohíben y otras que lo estimulan. Por lo dicho entonces, detallaré la metodología de la opción 1 que es la mas generalizada.

El primer paso es la declaración del arreglo bi-dimensional que fija el tamaño de la memoria. Pero, antes de detallar esta declaración es conveniente declarar también dos constantes: una constante relacionada con el ancho del bus de direcciones de la memoria, y otra constante relacionada con el ancho del bus de datos de la memoria. Conviene que estas declaraciones sean realizadas como generics dentro del componente, así es más fácil la modificación del tamaño de la misma.
De acuerdo a lo descrito, la primera parte del código de la memoria ROM quedaría:

1 library ieee;
2 use ieee.std_logic_1164.all;
3 use ieee.numeric_std.all;
4
5 entity sync_rom is
6  generic (data_width  : natural := 16;
7           addr_length : natural := 10);
8  port (
9   clk     :in  std_logic;
10  address :in  std_logic_vector(addr_length-1 downto 0);
11  data_out:out std_logic_vector(data_width-1  downto 0)
12     );
13 end sync_rom;

En al parte declarativa de la arquitectura se hacen las declaraciones de: 
- constante del tamaño de la memoria basado en el generic que declara en ancho del bus de direcciones (línea 2).
- type que declara el tamaño del arreglo bidimensional usando la constante recién declarada y el ancho del bus de datos declarado como generic (líneas 3-4). 
- constante que determina los distintos valores asignados a cada dirección de la memoria ROM (líneas 5-11). 
Por último, en la parte de descripción funcional del componente, se describe el comportamiento de la memoria ROM. Sencillamente, a la señal dato de salida se le asigna el correspondiente valor de la memoria de acuerdo del valor del bus de direcciones. Dos aclaraciones con respecto a la descripción del comportamiento de la memoria: 
1- en este caso se describe en un proceso sincrónico (líneas 13-18). Razón?... esto es debido a que por ejempo para implementar la memoria ROM en un BRAM de los FPGAs de Xilinx, la memoria DEBE SER descripta de modo sincrónico; de lo contrario es implementada en LUTs. Lógicamente que nadie quiere que una gran memoria sea implementada en LUTs teniendo los BRAMs disponibles. En otros casos se puede directamente hacer una descripción combinacional de la memoria, que directamente sería solo la instrucción de asignación detallada en línea 16.
2- la conversión de 'address' primero a 'unsigned' usando cast unsigned, y luego de unsigned a entero (integer) usando la función to_integer. Con estas conversiones lo que se hace es encontrar el índice de valor entero del arreglo definido como constante. De este modo se accede al valor asignado a esa dirección y que a su vez es asignado a la señal en el RHS de la instrucción (Right Hand Side).  
El código de la parte de la arquitectura de la memoria es la siguiente:  

 1 architecture synth of sync_rom

 2 constant mem_size : natural := 2**addr_length;
 3 type mem_type is array (mem_size-1 downto 0) of
 4            std_logic_vector (data_width-1 downto 0);
 5 constant mem : mem_type :=
 6  (0=> x"abcd", 1=> x"beef", 2 => x"5555", 3 => x"1010",
 7   4=> x"5a6b", 5=> x"f0f0", 6 => x"1234", 7 => x"fabc",
 8   8=> x"2345", 9=> x"9876", 10=> x"5432", 11=> x"6666",
 9  12=> x"0101",
10  13=> std_logic_vector(to_unsigned (1234,16)),
11  others => x"4247");

12 begin
13    rom : process (clk)
14    begin
15       if rising_edge(clk) then
16          data_out <=mem(to_integer(unsigned(address)));
17       end if;
18    end process rom;

19 end architecture synth;

Línea 10 ejemplifica otro modo de declarar el valor constante del dato correspondiente a una dirección (se puede usar cdo estamos lazzy y queremos evitar el cálculo de entero a slv :) ).

En caso que el tamaño de la ROM sea muy grande, el tamaño de la declaración de valores del arreglo bidimensional crecerá proporcionalmente y puede llegar a ser de varias hojas de valores,  perdiéndose el código VHDL descriptivo de la memoria. Para estos casos lo mejor es declarar las constantes y el arreglo de la memoria en un paquete. Así, el código VHDL es más compacto y el paquete 'oculta' la longitud de la memoria.

Consideraciones de Síntesis
La implementación de la memoria ROM en el FPGA puede realizarse en:
- LUTs.
- Bloques dedicados de Memoria RAM.
En qué bloque se implementaría la memoria descrita anteriormente????... La implementación depende de varios factores, entre ellos principalmente el tamaño de la memoria (1), pero depende también del 'seteo' de la herramienta de síntesis (2), por último si se usan o no atributos al respecto (3). Veamos:
1- Las herramientas de síntesis tienen un 'umbral de tamaño'. Si el tamaño de la memoria supera ese umbral la memoria implementará en BRAM, y sino supera el umbral se implementará en LUTs. Este umbral depende del tamaño del dispositivo FPGA que se esté usando, de la profundidad de la memoria y del número total de bits de memoria. Por ejemplo en la documentación de Xilinx se puede encontrar la siguiente tabla que muestra los valores de umbral mínimos para implementar memoria en los BRAM:







































Sin embargo, y esto es importante!, se puede 'obligar' a la herramienta de síntesis a forzar la implementación ya sea en LUTs o Bloques dedicados de RAM, dejando sin efecto los valores detallados en la tabla anterior. Para ello se tienen las dos opciones siguientes:
2- Variar el 'seteo' de la herramienta de síntesis. Por defecto el seteo para la implementación de memorias es 'AUTO', esto quiere decir que dependiendo del tamaño será donde se implementará la memoria. Cambiando este valor a BRAM o DISTRIBUITED se logra forzar a la herramienta de síntesis  a implementar la memoria donde uno desee.
3- Usar atributos en el código fuente VHDL o Verilog. Para ello, primero se debe declarar el atributo de síntesis respectivo como tipo 'string'. Para el caso de Xilinx XST, el atributo se denomina 'rom_style', para Altera Quartus 'romstyle', y para Synplify 'syn_romstyle'. Una vez declarado el atributo es necesario asociarlo con la señal o variable que representa la memoria ROM que se desea inferir e implementar. Al hacer la asociación se fija también el tipo de memoria que se desea usar para la implementacion de la misma. Este valor depende de la herramienta de sintesis, asi por ejemplo para Quartus los valores permitidos son: "logic", "M512", "M4K", M9K", M144K", "MLAB" o "M_RAM". Para el caso de XST, los valores permitidos son "auto", "block" o "distributed". Finalmente para Synplify (syn_romstyle), los valores permitidos son 'register' o 'block_rom'. 
A continuación un ejemplo del uso de atributos de síntesis detallado en las siguientes líneas de código:

-- Xilinx XST
attribute rom_style : string;
2 attribute rom_style of data_out:signal is "block";
-- Altera Quartus
attribute romstyle : string;
attribute romstyle of data_out:signal is "M_RAM";
-- Synplify
attribute syn_romstyle : string;
attribute syn_romstyle of data_out:signal is "block_rom";                            


Estas lineas de código deberían ir a continuación de la línea 11 (del ejemplo anterior) en la parte declarativa de la arquitectura. Para el caso de XST en esta lineas se asigna el atributo 'rom_style" a la señal 'data_out' (salida de la ROM) con el valor 'block'. De este modo la ROM se implementará en un BRAM sin importar su tamaño ni el seteo de XST. El otro valor que se le puede asigna al atributo 'rom_style' es 'distributed', el cual hará que la memoria se implemente en lógica distribuida, es decir en LUTs.


Versión pdf?  Hacé click acá :)

sábado, 31 de julio de 2010

Codificación de Máquinas de Estado (FSM) con Synplify y XST

FSM codificación con Synplify:
Cuando se desea una asignación de código de estados específica para la FSM, Synplify permite realizar la asignación usando la siguiente sintaxis:


1 type fsm_type is (idle, setup, txing, stop);
2 attribute syn_enum_encoding: string;
3 attribute syn_enum_encoding of fsm_type: type is "111 101 001 011";


Cuando se desee asignar una codificación tal como Gray, OneHot, Sequential or Binary, se debe usar la siguiente sintaxis:

1 type fsm_type is (idle, setup, txing, stop);
2 attribute syn_encoding: string;
3 signal curr_state, nxt_state: fsm_type;
4 attribute syn_encoding of curr_state: signal is "onehot";

Importante: para que efectivamente Synplify realice las asignaciones específicadas por el atributo syn_encoding o syn_enum_encoding, la opción FSM Compiler en la pantalla principal de Synplify debe estar sin marcar (not checked).
En caso de usar Tcl script, use el siguiente comando para no hacer uso del FSM Compiler::

set_options -symbolic_fsm_compiler 0


Implementacion Segura (Safe Implementation):
Por defecto Synplify generará FSM optimizadas por velocidad y área; sin embargo hay casos en los la prioridad de la FSM es que funcione correctamente ante cualquier circunstancia. En el modo Safe Implementation, Synplify agregará lógica de modo que si la FSM alguna vez cae en un estado invalido (estado no asignado), sera forzada al estado de reset; evitando así que la FSM se 'cuelgue' dejando de funcionar correctamente. Para habilitar esta característica se usa el mismo atributo syn_encoding pero agregándole la opción safe. Sintaxis: 

1 attribute syn_encoding of curr_state: signal is "safe,onehot";

FSM codificación con Xilinx XST:
Se puede usar la siguiente sintaxis para una asignación de código de estados específica:

1 type fsm_type is (idle, setup, txing, stop);
2 attribute enum_encoding: string;
3 attribute enum_encoding of fsm_type: type is "111 101 001 011";


Sin embargo  XST no tendrá en cuenta lo especificado en el atributo a menos que se active el modo FSM en modo 'user' (usuario). El modo FSM 'user' se puede seleccionar de dos modos, usando attribute o mediante Project Manager.
Usando attribute:

4 attribute fsm_encoding: string;
5 signal curr_state, nxt_state: fsm_type;
6 attribute fsm_encoding of curr_state: signal is "user";

GUI:
- Click el botón derecho del mouse sobre la opción Synthesize-XST
- Seleccione Properties
- Seleccione HDL Options
- Seleccione FSM Encoding Algorithm como User


El atributo fsm_encoding también se usa en el caso que se desee una asignación de estados con alguno de los siguientes valores:
- Auto
- OneHot
- Compact
- Sequential
- Gray
- Johnson
- Speed1
Para especificar una codificacion use la siguiente sintaxis:

1 type fsm_type is (idle, setup, txing, stop);
2 attribute fsm_encoding: string;
3 signal curr_state, nxt_state: fsm_type;
4 attribute fsm_encoding of curr_state: signal is "one-hot";

En caso de no especificar ninguna codificacion en particular Auto sera usado por XST por defecto.
Implementacion Segura (Safe Implementation):
En el modo Safe Implementation, XST genera lógica adicional que fuerza la FSM a un estado válido en caso que la FSM entre en un estado inválido. Sintaxis:
 1 type fsm_type is (idle, setup, txing, stop);
2 attribute safe_implementation: string;
3 signal curr_state, nxt_state: fsm_type;
4 attribute safe_implementacion of curr_state: signal is "yes";