01signal.com

Uso de comandos Tcl para seleccionar elementos lógicos

Esta página pertenece a una serie de páginas sobre temporización (timing). Las páginas anteriores explicaron la teoría que hay detrás de los cálculos de temporización, analizaron la restricción del periodo de reloj, mostraron los principios del cierre de temporización (timing closure) y comenzaron a examinar el entorno Tcl. Esta página explica los comandos que permiten que las restricciones de temporización (timing constraints) sean específicas.

Visión general

Recuerda que el propósito de las restricciones de temporización (timing constraints) es garantizar que se cumplen los requisitos de temporización de todos los caminos (paths) del diseño. Los distintos caminos pueden tener requisitos diferentes, así que cada restricción debe dirigirse al grupo de caminos pertinente y no incluir nada más.

La única manera de definir caminos es refiriéndose a los elementos lógicos relacionados con esos caminos. Por tanto, es importante saber escribir expresiones precisas que seleccionen los grupos correctos de elementos lógicos. En otras palabras, es necesario usar correctamente comandos como get_clocks, get_ports y get_cells, de modo que sus resultados sean exactamente lo necesario.

Esta página recorre las técnicas fundamentales para describir grupos de elementos lógicos. Casi todo lo que hay aquí es específico de la sintaxis SDC. También voy a suponer que hay disponible una interfaz de línea de comandos Tcl. Así ocurre en Vivado y Quartus, así como en la mayoría de las herramientas de FPGA actuales.

Algunos fabricantes de FPGA declaran abiertamente que el software de Synopsys está integrado en sus herramientas, así que naturalmente esas herramientas usan los mismos comandos Tcl. Vivado, por su parte, no se considera derivado de Synopsys. Y sin embargo, hay algunas similitudes llamativas entre ambos, sobre todo en lo que respecta a la interfaz Tcl. Quartus parece haberse desarrollado de forma independiente, por lo que su interfaz Tcl es algo distinta.

A primera vista, puede parecer que esta página entra demasiado en los detalles del guion Tcl (script). La verdad es lo contrario: la precisión es extremadamente importante, y eso solo puede lograrse entendiendo exactamente cómo se interpretan los comandos. De hecho, esta página solo explica los principios básicos. No hay sustituto para leer la documentación.

Obtener ayuda de las herramientas de FPGA

A menudo es difícil empezar a escribir restricciones de temporización, porque hay muchísimos detalles que cuidar. Las herramientas de FPGA pueden ayudar en esto.

El comando "help" puede usarse en la consola Tcl para ver la documentación de un comando Tcl. Suele ser exactamente la misma información que aparece en los documentos oficiales (por ejemplo, en formato pdf).

La mayoría de las herramientas de FPGA tienen una interfaz gráfica (GUI) para crear restricciones de temporización automáticamente. Este método se usa habitualmente para elegir el tipo de restricción de temporización que se necesita. El siguiente paso es seleccionar los elementos lógicos relevantes de una lista. El resultado es una o varias restricciones que se añaden al archivo SDC.

Usar un asistente de este tipo es útil para obtener pistas sobre la sintaxis Tcl. En ocasiones, las restricciones creadas automáticamente también pueden usarse tal cual. Sin embargo, en la mayoría de los casos conviene resistir la tentación de usar la salida del asistente y, en su lugar, pensar detenidamente cuál es la mejor manera de lograr el objetivo de la restricción. También es importante tener en cuenta cómo se espera que evolucione el proyecto y asegurarse de que las restricciones sigan siendo correctas con el tiempo. Una sesión rápida con un asistente gráfico difícilmente conseguirá eso.

Algunas herramientas de FPGA también tienen una interfaz gráfica para buscar elementos lógicos en el diseño. Cuando se usa esa interfaz, a menudo se muestra el comando Tcl correspondiente a la búsqueda solicitada. Es un método cómodo para obtener expresiones Tcl que encuentren elementos lógicos concretos. Una vez más, esas expresiones deben tratarse como punto de partida para seguir trabajando.

Una tercera funcionalidad de la mayoría de las herramientas de FPGA es una consola de línea de comandos Tcl que permite escribir comandos (o usar copiar y pegar) y ver los resultados en pantalla. Esto permite probar los comandos y sus patrones de búsqueda, y ver qué objetos se encuentran. Así se puede comprobar que el patrón de búsqueda es correcto.

En conclusión, las herramientas de FPGA pueden ayudar a crear el comando Tcl. Pero, como ya se ha dicho, los comandos que crean las herramientas deben servir solo como base para escribir patrones de búsqueda precisos cuya corrección esté garantizada con el tiempo.

Ahora que ya conocemos la forma perezosa de crear restricciones de temporización, ha llegado el momento de aprender a hacerlo bien.

La netlist: un breve recordatorio

Antes de entrar en más detalles sobre el entorno Tcl, quiero hacer un breve recordatorio sobre las netlists.

El formato de archivo más común para las netlists es EDIF, pero muchas herramientas de FPGA tienen también su propio formato. Normalmente, el sintetizador (synthesizer) crea la netlist, aunque las herramientas también pueden modificarla en fases posteriores. Este archivo describe el diseño lógico en términos de sus componentes básicos y las conexiones entre esos componentes. Es como un esquema de cableado, pero con representación textual en lugar de una imagen gráfica.

Los componentes de una netlist se llaman celdas (cells). La gran mayoría de las celdas son algo así como una LUT, otro pequeño elemento de lógica combinacional (combinatorial logic) o un flip-flop. Además, las instanciaciones (instantiation) de cajas negras en el código Verilog (por ejemplo, IP cores) se representan como una celda en la netlist. Otras celdas son las PLL, las block RAM y los grandes elementos lógicos («hard IPs»): bloques PCIe, transceptores MGT, procesadores, etc.

Cada celda tiene varios pines (pins): estos pines son como los puntos de conexión externos de un componente electrónico físico. Pero no hay que confundirlos con la E/S externa de la FPGA: tanto las celdas como los pines existen dentro de la FPGA.

La interconexión en la netlist consiste en redes (nets). Son como cables físicos. Por ejemplo, cuando en Verilog se define una señal con "wire", el resultado es una red (net). Una red conecta dos o más pines y, al hacerlo, garantiza que esos pines tengan siempre el mismo nivel lógico.

La representación de los elementos lógicos como objetos

Cuando las herramientas de FPGA ejecutan una implementación de un proyecto, lo que ocurre en realidad es que se ejecutan guiones Tcl (scripts). Esto es así en Vivado, Quartus y varias otras herramientas de FPGA. Incluso con software que funciona de otra manera, es correcto hacer esta suposición: la ilusión de que todo es un gran guion Tcl la crea la API destinada a las restricciones y a otros archivos de guion.

En el entorno de ese guion Tcl (sea imaginario o no), todos los elementos lógicos se representan como objetos creados a partir de distintas clases. Las restricciones de temporización de un archivo SDC (o XDC, en el caso de Xilinx) pueden acceder a esos objetos. Del mismo modo, los comandos Tcl de la consola de línea de comandos y los guiones Tcl tienen acceso a ellos.

Hay cinco comandos Tcl que soportan todas las herramientas de FPGA que trabajan con sintaxis SDC. Estos comandos se usan para encontrar objetos de distintos tipos (es decir, clases distintas). Ya los he utilizado en los ejemplos anteriores de restricciones de temporización. De hecho, es casi imposible escribir restricciones de temporización con sentido sin estos comandos.

Sin ningún argumento, estos comandos encuentran todos los objetos del tipo correspondiente. Más adelante veremos cómo afinar la búsqueda.

Aparte de estos cinco comandos, cada herramienta de FPGA tiene sus propios comandos y objetos adicionales. Por ejemplo, Vivado tiene comandos adicionales como all_ffs, all_registers, all_inputs, all_outputs, all_rams y varios más de ese estilo. Quartus soporta algunos de ellos, y también tiene get_registers, get_keepers, get_nodes, get_fanins y get_fanouts, etc.

La importancia de estos objetos es que se usan en las restricciones de temporización para referirse a elementos lógicos del diseño. También pueden usarse en guiones Tcl para obtener información, por ejemplo la frecuencia de un reloj. Cada herramienta de FPGA tiene su propia API para acceder a esos objetos desde guiones Tcl. Por ejemplo, en Vivado se puede usar este comando Tcl para obtener un periodo de reloj:

> get_property PERIOD [get_clocks clk]
4.000

Y lo mismo en Quartus:

> get_clock_info -period [get_clocks clk]
4.000

Pese a estas diferencias, la mayoría de las herramientas de FPGA coinciden en la sintaxis y el significado de las restricciones de temporización en formato SDC. La información sobre la API que soporta cada herramienta suele encontrarse en guías de usuario con títulos relacionados con guiones Tcl o con cierre de temporización.

Los ejemplos de esta página se basan en Vivado, salvo que se indique lo contrario.

Algunas notas sobre Tcl

Tcl es un lenguaje anticuado; sin embargo, como está muy asentado en el campo del diseño lógico, no se espera que desaparezca pronto. Por suerte, es posible hacer cosas útiles con Tcl sin conocerlo demasiado bien.

Lo primero, que ya he mencionado brevemente, son los corchetes («[» y «]»). En Tcl, eso significa ejecutar el comando que hay entre corchetes y poner el resultado en su posición. Para quienes estén familiarizados con guiones de shell o con Perl, es lo mismo que las comillas invertidas. Por ejemplo, en este comando, get_port se sustituye por el objeto puerto llamado "clk":

create_clock -period 4.000 -name clk [get_ports clk]

Del mismo modo, esto podría haberse escrito así:

set the_clk_port [get_ports clk]
create_clock -period 4.000 -name clk $the_clk_port

Como se muestra en este segundo ejemplo, las variables se definen y se les asigna un valor con el comando "set". Para acceder al valor de una variable se usa el signo del dólar ($). Una vez más, igual que en los guiones de shell y Perl.

En cuanto a las llaves («{» y «}»), es otra historia: como en varios otros lenguajes, su significado depende mucho del contexto. En Tcl, uno de los significados menos esperados de las llaves es que la cadena encerrada debe permanecer intacta. En otras palabras, no debe haber sustituciones y los espacios en blanco deben tratarse como cualquier otro carácter. Por ejemplo, la misma restricción podría haberse escrito así:

create_clock -period {4.000} -name {clk} [get_ports {clk}]

En este ejemplo, las llaves son completamente innecesarias, y el comando significa exactamente lo mismo que antes. Las llaves innecesarias son por desgracia habituales, y a menudo no significan nada, exactamente como en este ejemplo.

Un consejo para usar la consola Tcl

A menudo ocurre que el número de objetos encontrados es grande, lo que dificulta leer la salida del comando de búsqueda. Esto puede resolverse con comandos Tcl sencillos. Cómo hacerlo exactamente depende de la herramienta utilizada. Con Vivado, este comando imprime todas las celdas del diseño, cada una en una línea separada, de modo que aunque haya muchas celdas la salida sigue siendo legible:

> join [get_cells -hierarchical] \n
GND
VCC
bar__0_i_1
bar_reg_OBUF_inst
bar_reg__0
[ ... ]

El comando "join" introduce un salto de línea entre cada elemento de la lista que produce get_cells.

Con Quartus, se consigue el mismo resultado con:

join [query_collection -all [get_cells -hierarchical] ] \n

O usando query_collection de forma más sensata:

query_collection -all -report_format [get_cells -hierarchical]

Encontrar elementos específicos

Después de una larga introducción, por fin ha llegado el momento de hablar de lo que es realmente interesante. Para los ejemplos siguientes, consulta el siguiente código Verilog, que también se utiliza más adelante:

module top(
    input clk,
    input foo,
    output reg bar_reg,
    output reg baz
);
    reg foo_reg;
    reg bar;
    reg baz_metaguard;
    wire pll_clk_8, pll_clk_6;

   clk_wiz_1 pll_i
   (.clk_in1(clk),
    .clk_out1(pll_clk_8),
    .clk_out2(pll_clk_6));

always @(posedge pll_clk_8)
  foo_reg <= foo;

always @(posedge pll_clk_6)
  begin
    bar <= !foo_reg;
    bar_reg <= bar;
  end

always @(posedge clk)
  begin
    baz_metaguard <= bar;
    baz <= baz_metaguard;
  end

Como se ha mencionado al principio de esta página, la precisión de las restricciones de temporización depende de seleccionar los grupos correctos de elementos lógicos. Por tanto, es posible y necesario reducir los resultados de búsqueda de los cinco comandos get_* mencionados antes. Hay varias formas de hacerlo; sin embargo, la más habitual se basa en el nombre del objeto. El patrón más sencillo es encontrar un solo objeto cuyo nombre sea exactamente el que buscamos. Por ejemplo, en la consola Tcl de Vivado:

> get_ports clk
clk

Del mismo modo, es posible encontrar todos los objetos cuyos nombres coincidan con un patrón concreto:

> get_pins pll_i/*
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2

Observa que la salida en ambos ejemplos son objetos. En la consola Tcl de Vivado, los nombres de esos objetos se imprimen por comodidad.

Aún más importante: ten en cuenta que cada herramienta de FPGA se comporta de forma ligeramente distinta con estos patrones de búsqueda. Aquí se usa Vivado en los ejemplos. Los mismos principios se aplican a otras herramientas.

El asterisco («*») es un comodín que sustituye a cualquier número de caracteres. El signo de interrogación («?») sustituye a un solo carácter. Funciona igual que los comodines con nombres de archivo.

Observa que, igual que con los nombres de archivo, los comodines no coinciden con el separador de jerarquía (por ejemplo, «/» en Vivado y «|» en Quartus).

También hay una similitud entre la ruta jerárquica y los directorios de archivos: la búsqueda se hace en relación con la jerarquía de nivel superior, que es similar al directorio raíz de un sistema de archivos. Así, por ejemplo:

> get_pins */clk_*
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2
> get_pins pll_i/clk_out?
pll_i/clk_out1 pll_i/clk_out2

La necesidad de indicar la posición exacta de cada elemento lógico en la jerarquía es a menudo un inconveniente importante: los elementos que queremos encontrar suelen estar en posiciones distintas. Esto se resuelve con "-hierarchical": cuando está presente este indicador, la búsqueda del patrón se hace en todas las posiciones de la jerarquía.

En general, no se recomienda usar comodines para encontrar elementos lógicos. Las únicas excepciones son cuando el patrón es muy sencillo o cuando no hay más remedio. Hay una página aparte que explica cómo usar comodines y "-hierarchical", y muestra las limitaciones de este método.

Usar -filter

Usar patrones de búsqueda basados en comodines tiene varios inconvenientes. El más significativo es que hay que definir la jerarquía con precisión o no definirla en absoluto. Un problema es que a menudo se desea limitar la búsqueda a objetos de una subjerarquía concreta. Sin embargo, esto no es posible con comodines, y la opción "-hierarchical" no resuelve ese problema.

Por esta razón y por otras, la forma preferida de definir patrones de búsqueda es con la opción -filter. Esta opción va acompañada de una expresión booleana. Cuando se usa, solo permanecen en el resultado los objetos para los que esa expresión es verdadera.

Por ejemplo,

> get_pins */clk_*
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2
> get_pins */clk_* -filter {name =~ *2*}
pll_i/clk_out2

En este ejemplo, se examinó la propiedad llamada "name" en cada uno de los objetos encontrados mediante el comodín. El objeto permanecía en el resultado solo si esa propiedad coincidía con "*2*". En otras palabras, solo si el nombre del objeto contenía un «2».

Este ejemplo no es interesante desde un punto de vista práctico. Es mucho más interesante cuando se usa "-hierarchical": sin ningún patrón de búsqueda, se encuentran todos los objetos. Es decir, este comando encuentra todos los pines de todas las jerarquías:

get_pins -hierarchical

A partir de ahí, es posible reducir los resultados con -filter:

> get_pins -hierarchical -filter {name =~ pll_i/*/*out1 }
pll_i/inst/clk_out1
> get_pins -hierarchical -filter {name =~ pll_i/*out1 }
pll_i/clk_out1 pll_i/inst/clk_out1
> get_pins -hierarchical -filter {name =~ *out1 }
pll_i/clk_out1 pll_i/inst/clk_out1

Observa que el significado de las llaves («{» y «}») es simplemente que la parte encerrada no debe ser modificada por el intérprete de Tcl.

También hay que notar que el patrón de búsqueda pertenece a la opción -filter y se comporta de forma distinta: trata el "name" como una propiedad de un objeto. Por tanto, todos los caracteres se tratan por igual: «/» no tiene ningún significado especial. No importa que «/» sea el separador de jerarquía. Todos los caracteres, incluido «/», pueden coincidir con el comodín («*»). Del mismo modo, todos los caracteres, incluido «/», pueden usarse en el patrón. Esto no es así sin -filter.

El hecho de que cualquier carácter pueda coincidir con «*» hace de -filter una herramienta más potente, pero esa ventaja también es una posibilidad de cometer errores: es fácil olvidar que un «*» inocente puede coincidir accidentalmente tanto con «pll_i/clk_» como con «pll_i/inst/clk_», como se muestra en el ejemplo anterior.

Un uso correcto de esta funcionalidad es encontrar un elemento lógico cuyo nombre se conoce en algún lugar de la jerarquía:

> get_pins -hierarchical -filter {name =~ */clkout2_buf/O }
pll_i/inst/clkout2_buf/O

Esto es correcto si estamos seguros de que en el diseño solo hay una celda llamada "clkout2_buf". Con este comando, el pin de salida de esa celda se encuentra siempre, aunque el módulo que contiene ese elemento lógico se mueva dentro de la jerarquía del proyecto. En un escenario real, es mejor elegir un nombre más exclusivo que "clkout2_buf".

El mismo método funciona con get_cells y get_nets, por ejemplo:

> get_cells -hierarchical -filter {name =~ */clk*_buf}
pll_i/inst/clkf_buf pll_i/inst/clkout1_buf pll_i/inst/clkout2_buf

Pero -filter funciona con todas las propiedades, no solo con el nombre. Así que este comando encuentra todos los registros que tienen «bar» en su nombre:

get_cells -hier -filter {primitive_type =~ register.*.* && name =~ *bar*}

Observa el operador «&&», que significa Y lógico (igual que en Verilog y en C).

En este comando, "primitive_type" y "name" son solo nombres de propiedades. El operador «=~» hace una comparación y admite comodines.

Recuerda que las propiedades de un objeto pueden enumerarse con el comando "report_property" (en Vivado).

Así que -filter es flexible para trabajar. Desgraciadamente, no está disponible en todas las herramientas de FPGA.

Observa que Vivado tiene un comando llamado filter, que hace la misma operación que -filter. Así que los dos comandos siguientes son equivalentes:

set result [get_cells -hierarchical -filter {name =~ *_reg}]
set result [filter [get_cells -hierarchical] {name =~ *_reg}]

El segundo formato es útil cuando una lista de objetos está guardada en una variable. Así que los dos comandos anteriores también son equivalentes a esto:

set all_cells [get_cells -hierarchical]
set result [filter $all_cells {name =~ *_reg}]

Usar -regex

Quienes estén familiarizados con las expresiones regulares puede que quieran usar esta opción. Normalmente no es buena idea, sobre todo porque hace que las restricciones de temporización sean más difíciles de entender para otras personas. Las herramientas de FPGA que soportan expresiones regulares suelen soportar también -filter, así que casi siempre es mejor usar -filter.

Recuerda que el principal problema con el patrón de búsqueda habitual es que el separador de jerarquía no coincide con el comodín.

Así que -regexp puede resolver esto, como se muestra en este par de ejemplos:

> get_pins -hierarchical -regexp {.+/clk_out[123]}
pll_i/clk_out1 pll_i/clk_out2 pll_i/inst/clk_out1 pll_i/inst/clk_out2
> get_pins {pll_i/[^/]+/clk[^/]+} -hierarchical -regexp
pll_i/inst/clk_in1 pll_i/inst/clk_out1 pll_i/inst/clk_out2

El primer comando demuestra que «.» coincide con cualquier carácter. Eso incluye «/» (el separador de jerarquía).

El segundo comando demuestra cómo se usa «[^/]+» para que coincida con cualquier cosa excepto un separador de jerarquía. Eso permite controlar la profundidad exacta en la jerarquía de los resultados. Este comando también demuestra que el patrón de búsqueda no es un argumento de -regexp, sino que -regexp cambia la sintaxis del patrón.

Observa que la expresión regular debe coincidir con el nombre completo del objeto. En otras palabras, las herramientas añaden implícitamente un «^» al principio de la expresión regular y un «$» al final.

Pero no uses -regex si hay otra forma de conseguir el mismo resultado. La mayoría de los otros diseñadores de FPGA no podrán entender el patrón.

Usar -of_objects

En lugar de encontrar objetos según sus nombres, -of_objects permite encontrar objetos según su relación con otros objetos. En la mayoría de los casos, "-of_objects" significa aproximadamente «está conectado a».

Por ejemplo, para encontrar todos los pines conectados a una red:

> get_pins -of_objects [get_nets bar]
bar_reg_reg/D baz_metaguard_reg/D bar_reg__0/Q

O todos los pines de una celda:

> get_pins -of_objects [get_cells bar_reg_reg]
bar_reg_reg/Q bar_reg_reg/C bar_reg_reg/CE bar_reg_reg/D bar_reg_reg/R

O el pin que es el origen de un reloj:

> get_pins -of_objects [get_clocks clk_out1_clk_wiz_1]
pll_i/inst/mmcme3_adv_inst/CLKOUT0

El mismo método sirve para encontrar redes. Por ejemplo, ¿qué redes están conectadas a un pin concreto?

> get_nets -of_objects [get_pins bar_reg_reg/C]
pll_clk_6

O, ¿qué redes están conectadas a la celda correspondiente?

> get_nets -of_objects [get_cells bar_reg_reg]
bar_reg_OBUF pll_clk_6 <const1> bar <const0>

Del mismo modo, es posible buscar celdas de esta manera. Por ejemplo, ¿qué celdas están conectadas a @pll_clk_6?

> get_cells -of_objects [get_nets pll_clk_6]
bar_reg__0 bar_reg_reg pll_i

Observa que "pll_i" es el nombre de la instanciación del IP del Clock Wizard. Probablemente no sea un resultado deseado. Entonces, ¿quizá limitar el resultado a solo flip-flops?

> get_cells -of_objects [get_nets pll_clk_6] -filter {primitive_type =~ register.*.*}
bar_reg__0 bar_reg_reg

Hasta ahora, los ejemplos con -of_objects han mostrado cómo pines, redes y celdas pueden referirse entre sí. Pero también es posible encontrar objetos de reloj con esta opción:

> get_clocks -of_objects [get_nets pll_clk_6]
clk_out2_clk_wiz_1
> get_clocks -of_objects [get_cells bar_reg_reg]
clk_out2_clk_wiz_1
> get_clocks -of_objects [get_pins bar_reg_reg/C]
clk_out2_clk_wiz_1

Estos tres comandos muestran cómo se encuentra el reloj según el elemento lógico conectado a él. Observa que cuando ese elemento es una celda, puede haber más de un resultado. Por ejemplo:

> get_clocks -of_objects [get_cells pll_i]
clk clk_out1_clk_wiz_1 clk_out2_clk_wiz_1

Recuerda que "pll_i" es una IP, así que sus pines son los puertos de ese módulo:

> get_pins -of_objects [get_cells pll_i]
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2

Así pues, para encontrar un reloj concreto, get_pins es más seguro:

> get_clocks -of_objects [get_pins pll_i/clk_out2]
clk_out2_clk_wiz_1

Pero, por supuesto, el objeto de reloj de un puerto externo de E/S se encuentra mejor con:

> get_clocks -of_objects [get_ports clk]
clk

O, con el comando más corto, que es equivalente:

> get_clocks [get_ports clk]
clk

Hablando de get_ports, también funciona con -of_objects. Consulta la documentación sobre las posibilidades, que son específicas de get_ports. Las posibilidades similares a las de otros comandos son bastante inútiles, por ejemplo:

> get_ports -of_objects [get_nets clk]
clk

En resumen, -of_objects es un método excelente para seleccionar elementos lógicos concretos. Esto es especialmente cierto por las dificultades que plantea buscar según el nombre del objeto, que es el siguiente tema.

Desgraciadamente, muchas herramientas de FPGA no soportan -of_objects, lo que no deja más remedio que apoyarse en métodos menos fiables.

El problema de buscar por nombre

Como ya se ha comentado, la precisión de las restricciones de temporización depende de la precisión al encontrar elementos lógicos. Al menos parte de esos resultados dependen del nombre de un objeto. Veamos cómo puede causar problemas eso.

Por ejemplo, "get_ports clk" se usa para hacer la conexión entre un objeto de reloj y la señal presente en un pin de E/S concreto. Ese pin de E/S está representado por un objeto puerto cuyo nombre es "clk":

create_clock -period 4.000 -name clk [get_ports clk]

Pero, ¿por qué ese objeto puerto se llama "clk"? La respuesta está relacionada con cómo crea la netlist el sintetizador: el nombre del puerto de nivel superior en el código Verilog se eligió también como nombre del puerto de nivel superior de la netlist. Es una elección obvia, así que cualquier sintetizador que se precie hará lo mismo.

¿Y qué pasa con los nombres de las celdas? Tomemos el registro llamado "foo_reg" en el código Verilog anterior. ¿Cuál es el nombre del objeto celda que representa el flip-flop de ese registro? El sintetizador de Vivado eligió el nombre "foo_reg_reg" para ese objeto. Así que está claro que este sintetizador tiende a añadir el sufijo "_reg" al nombre del código Verilog. Parece una regla en la que se puede confiar. Pero otro sintetizador probablemente haría algo distinto.

¿Y qué pasa con el registro llamado "bar"? El nombre del objeto celda correspondiente debería haber sido "bar_reg", pero el sintetizador tomó otra decisión: "bar_reg__0". Esto se debe a que en el código Verilog hay un registro llamado "bar_reg". Para evitar una colisión de nombres, el sintetizador eligió un nombre ligeramente distinto: añadió "_reg__0" en lugar de solo "_reg". Este ejemplo sencillo demuestra el problema de confiar en los nombres de los objetos.

Para colmo, supongamos que la restricción de temporización se escribió antes de añadir al código Verilog el registro llamado "bar_reg". En ese caso, el objeto celda relacionado con @bar recibe el nombre "bar_reg", como es habitual. Así que la restricción usaría ese nombre. Más tarde se añade al diseño el registro "bar_reg". Como resultado, el nombre del objeto celda solicitado cambia de "bar_reg" a "bar_reg__0". La restricción, que depende del nombre "bar_reg", de repente es incorrecta. Con un poco de suerte, las herramientas emitirán un aviso al respecto.

Hay otras razones posibles por las que usar nombres de objetos puede salir mal. Por ejemplo, las herramientas pueden duplicar un registro automáticamente si su fan-out supera un límite. Cuando eso ocurre, el registro adicional puede no quedar incluido en la restricción, porque el nombre del nuevo registro no coincide con el patrón de búsqueda.

Un problema más serio es cuando elementos lógicos quedan incluidos en las restricciones por accidente. Por ejemplo, esto puede ocurrir con elementos que pertenecen a bloques de IP (IP core). Como no tenemos ningún control sobre los nombres de esos elementos, es posible que esos nombres coincidan accidentalmente con el patrón de una restricción.

La inclusión accidental de elementos lógicos en las restricciones también puede ser resultado de la pereza: las restricciones suelen escribirse al mismo tiempo que se añaden funcionalidades al diseño. Si el patrón se escribe por ensayo y error, puede que no se tengan en cuenta los nombres de futuros elementos lógicos. Así, cuando se añade lógica nueva, algunos de sus nombres pueden coincidir sin querer con las restricciones existentes.

Cómo evitar errores con las restricciones de temporización

La primera y más importante regla es que no basta con probar qué elementos coinciden con el patrón de una restricción. Aunque hagas una lista completa de esos elementos y la revises con cuidado, eso no garantiza nada respecto a la lógica que se añada en el futuro. Tampoco garantiza que las restricciones sigan funcionando si el sintetizador u otra fase de la implementación cambia los nombres de los objetos.

Por tanto, es importante tratar los patrones como expresiones matemáticas: no basta con que funcionen ahora. Y si no funcionan como se espera, no basta con hacer un pequeño arreglo para que funcionen. Más bien, tiene que haber una explicación lógica de por qué el patrón es correcto y de por qué es muy probable que siga siéndolo con el tiempo.

También es importante que los patrones se apoyen en cosas que difícilmente cambien. Por ejemplo, las herramientas no cambian los nombres de las instanciaciones (instantiation) del código Verilog. Esto es válido para las instanciaciones de módulos, IPs y primitivas (primitives). Por tanto, es seguro apoyarse en la ruta jerárquica cuando esta consta solo de nombres de instanciaciones escritos en el código Verilog. No es tan seguro usar nombres creados dentro de un bloque de IP. Para explicarlo, veamos este comando:

get_clocks -of_objects [get_pins pll_i/inst/clkout1_buf/O]

Es un método para obtener un objeto de reloj refiriéndose al pin de salida del buffer global de reloj que distribuye el reloj. La cuestión es si podemos estar seguros de que esto funcionará a largo plazo.

El problema de este método es que "inst" y "clkout1_buf" son nombres de instanciaciones hechas dentro del IP Clocking Wizard. Aunque es poco probable que esos nombres cambien, no hay garantía de que no lo hagan.

Una solución posible es encontrar el código Verilog que implementa la IP e incluir ese Verilog directamente en el proyecto. Eso garantiza que nada cambiará en el futuro.

Una alternativa es mirar la instanciación de la IP en Verilog. Recuerda que era:

 clk_wiz_1 pll_i
   (.clk_in1(clk),
    .clk_out1(pll_clk_8),
    .clk_out2(pll_clk_6));

Como se trata de una instanciación de una caja negra, cada uno de los puertos tiene un pin en la netlist. Los nombres no pueden cambiar, porque así es como se identifican los puertos de la IP. Por tanto, está garantizado que el pin llamado "pll_i/clk_out1" hace la conexión con @pll_clk_8. Así que esta es una forma segura de obtener el objeto de reloj:

get_clocks -of_object [get_pins pll_i/clk_out1]

Observa que probablemente esto no funcionará si clk_wiz_1 es simplemente otro módulo Verilog del proyecto: en ese caso no se crean pines en nombre de la instanciación de ese módulo (porque el sintetizador suele implementar las conexiones de los puertos fusionando redes). Una posible solución sería usar los nombres que aparecen en niveles más bajos de la jerarquía.

Así que hay varios tipos de nombres en los que se puede confiar:

Es mejor fallar en grande

La peor situación es cuando una restricción está casi bien: cuando solo faltan unos pocos caminos (paths). O cuando solo hay unos pocos caminos incluidos en la restricción por error. Errores de este tipo son los más difíciles de encontrar.

Esta es la razón principal por la que son mejores las restricciones cortas, concisas y de estilo matemático. Si hay una restricción para cada pequeño grupo de elementos lógicos, es fácil que un error se cuele en esa larga lista de reglas.

Para demostrar esta idea, volvamos al ejemplo mostrado antes para encontrar un objeto de reloj:

get_clocks -of_objects [get_pins pll_i/inst/clkout1_buf/O]

Como se ha dicho, el problema de esta expresión es que si la instanciación de pll_i se mueve a otra posición de la jerarquía, no se encontrará ningún objeto de reloj. ¿Hasta qué punto será grave eso?

Supongamos que ese objeto de reloj se guarda en una variable Tcl así:

set my_clock [get_clocks -of_objects [get_pins pll_i/inst/clkout1_buf/O]]

Y después $my_clock se usa en muchas restricciones, de modo que hay muchos caminos implicados. En ese caso, no es un gran problema que $my_clock de repente no contenga ningún objeto de reloj a causa de un error: hay muchas posibilidades de que ese error se note pronto, porque muchas cosas irán mal.

Pero si $my_clock se usa en una sola restricción, pensada para resolver un pequeño problema que rara vez tiene efecto, la situación es mala. Lo más probable es que ese error pase desapercibido.

En conclusión: una restricción de temporización bien escrita debe funcionar perfectamente o no funcionar en absoluto.


Esta página ha mostrado cómo seleccionar elementos lógicos con precisión. En la página siguiente, ese conocimiento se usa para definir restricciones de temporización selectivas.

Esta página se ha traducido del inglés mediante traducción automática. En caso de duda, consulta el texto original.
Copyright © 2021-2026. All rights reserved. (dcc38493)