Esta página pertenece a una serie de páginas sobre temporización (timing). Las páginas anteriores explicaron la teoría de los cálculos de temporización, mostraron cómo escribir varias restricciones de temporización (timing constraints) y analizaron los principios del cierre de temporización (timing closure). Esta página explica cómo definir las restricciones de temporización relacionadas con los cruces de dominios de reloj (clock domain crossing).
Introducción
Con el conocimiento ya adquirido sobre cómo encontrar elementos lógicos específicos y cómo definir caminos (paths), vamos a comenzar a examinar restricciones de temporización que hacen uso de ese conocimiento.
Lo primero que veremos es la relación entre las restricciones de temporización y los dominios de reloj (clock domains). Estos dos temas están estrechamente relacionados, y es imposible hablar de uno sin mencionar el otro. Por tanto, te sugiero que leas esta serie de páginas sobre dominios de reloj si el tema te resulta nuevo. Entre otras cosas, esto es necesario para entender la terminología que usaré a continuación.
La lógica define la relación entre los relojes
Veamos este ejemplo de código Verilog:
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
Esto es casi lo mismo que el ejemplo con una PLL que vimos antes. La diferencia es que hay un nuevo cruce de dominios de reloj: el valor de @bar se copia en @baz a través de una protección contra la metaestabilidad (metastability guard).
Así pues, en este ejemplo hay dos tipos de cruces de dominios de reloj:
- De @foo_reg a @bar, entre dos relojes relacionados (related clocks) (no hay protección contra la metaestabilidad).
- De @bar a @baz, entre dos relojes no relacionados (unrelated clocks) (@baz_metaguard es una protección contra la metaestabilidad).
Solo he tenido en cuenta un criterio para determinar el tipo de cruce de dominios de reloj: la existencia o ausencia de una protección contra la metaestabilidad. Si la lógica espera relojes no relacionados, utiliza los mecanismos de seguridad necesarios. Por tanto, nada más importa: aunque fuera posible garantizar los requisitos de temporización en los caminos entre esos relojes, no hay razón para hacerlo.
Y viceversa: si la lógica espera relojes relacionados, no hay protección frente a violaciones de temporización. Por tanto, los requisitos de temporización deben cumplirse en todos los caminos entre esos relojes.
Así pues, la lógica siempre hace suposiciones sobre qué relojes son relacionados y cuáles no. Estas suposiciones se reflejan en la existencia o ausencia de mecanismos de protección. Pero son las herramientas de FPGA las que se aseguran de que esas suposiciones se hagan realidad. En particular, las herramientas garantizan que se cumplen los requisitos de temporización en los caminos entre relojes relacionados.
Por tanto, debemos asegurarnos de que las herramientas conozcan las suposiciones que la lógica hace sobre los relojes.
Observa que, en el ejemplo anterior, las herramientas no tienen forma de saber qué espera la lógica respecto a @pll_clk_6 y @clk: la lógica podría suponer que son relojes relacionados o podría suponer lo contrario. De hecho, ya usé un ejemplo parecido antes (ver «Idea n.º 9») para demostrar un camino entre relojes relacionados. En el ejemplo de arriba, estos dos relojes se tratan como no relacionados.
Quizá las herramientas podrían hacer una conjetura inteligente a partir del nombre de @baz_metaguard. Quizá la estructura típica de una protección contra la metaestabilidad podría dar una pista. Pero eso no basta para tomar una decisión sobre un asunto tan importante.
Informar a las herramientas sobre los relojes
En los ejemplos hasta ahora había una única restricción de temporización:
create_clock -period 4.000 -name clk [get_ports clk]
La primera pregunta que viene a la mente es cómo tratan las herramientas, por defecto, el cruce de dominios de reloj entre @pll_clk_6 y @clk. Si esta es la única restricción de temporización, ¿impondrán las herramientas algo sobre el camino entre @bar y @baz_metaguard?
La respuesta es que depende de la herramienta de FPGA utilizada. Aunque prácticamente todas las herramientas considerarían @pll_clk_6 y @pll_clk_8 como relojes relacionados, no es evidente qué ocurre con otros pares de relojes. Por defecto, las herramientas suelen tratar todos los relojes como si fueran relacionados, pero no es seguro fiarse de eso.
Muchas herramientas de FPGA soportan el comando set_clock_groups. Es la mejor opción para definir la relación entre relojes. Dada la importancia de este comando, es buena idea consultar la documentación de la herramienta antes de usarlo en un diseño.
Para el ejemplo anterior, este comando podría usarse así en Vivado (o de forma similar en otras herramientas):
set_clock_groups -asynchronous \
-group [list \
[get_clocks -of_objects [get_pins pll_i/clk_out1] ] \
[get_clocks -of_objects [get_pins pll_i/clk_out2] ] ] \
-group [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]
Este uso de get_clocks y get_pins ya se explicó antes: cada comando get_clocks encuentra un objeto de reloj según el nombre del puerto correspondiente de clk_wiz_1 (observa que el nombre de la instanciación de clk_wiz_1 es pll_i). Por ejemplo, el último comando get_clocks obtiene el objeto de reloj de @clk.
Este ejemplo muestra cómo set_clock_groups define grupos de relojes. En este caso, un grupo está formado por @pll_clk_6 y @pll_clk_8, y el segundo grupo solo por @clk. Esta restricción indica a las herramientas que todos los relojes del mismo grupo son relojes relacionados. Del mismo modo, si dos relojes pertenecen a grupos distintos, las herramientas los consideran no relacionados.
En otras palabras, las herramientas imponen restricciones de temporización sobre un camino si y solo si los relojes a ambos lados del camino pertenecen al mismo grupo.
En un diseño se permite usar varios comandos set_clock_groups. Pero lo mejor es usar un único comando set_clock_groups para dividir todos los relojes en grupos. Eso evita contradicciones y también ayuda a prevenir confusiones: la principal ventaja de este comando es que describe de forma concisa las relaciones entre los relojes. Corto, conciso y matemático. Así es como lo queremos.
Observa que set_clock_groups debe usarse después de todos los comandos create_clock, porque los objetos de reloj mencionados deben existir ya.
Relaciones incoherentes entre relojes
Si en algunas partes del diseño los relojes se consideran relacionados, y en otras partes se consideran no relacionados, no es posible usar set_clock_groups.
Por ejemplo, @clk y @pll_clk_6 se tratan como relojes no relacionados en el código Verilog anterior. Pero en teoría podría haber lógica adicional que los tratara como relacionados. Aunque no es buena idea, es posible imponer restricciones de temporización entre @pll_clk_6 y @clk para esa lógica adicional.
Si set_clock_groups no puede usarse por esta razón, pregúntate primero si no quieres cambiar la lógica para poder usar set_clock_groups. No solo porque este comando sea tan bueno: sin una regla sencilla que diga qué relojes son relacionados y cuáles no, es fácil cometer errores con los cruces de dominios de reloj. Si aun así no quieres hacer esos cambios en la lógica, la solución es definir caminos falsos (false paths).
Brevemente sobre el comando set_false_path
Hay dos comandos principales para declarar caminos falsos: set_clock_groups y set_false_path.
El comando set_clock_groups se presentó antes: cualquier camino entre dos relojes que pertenecen a grupos distintos se considera un camino falso. Pero set_clock_groups no siempre puede usarse para todos los caminos que deban declararse como falsos. En algunos casos, la selección de caminos debe ser más específica que definir grupos de relojes. El comando set_false_path resuelve este problema permitiendo una selección concreta de caminos.
Por ejemplo, sustituyamos el comando set_clock_groups anterior por un comando set_false_path. Lo único que hay que corregir es el camino de @bar a @baz_metaguard. Los requisitos de temporización de todos los demás caminos ya se imponen correctamente. Así que esta es una forma de escribir el comando set_false_path para ese camino. Pero no aprendas de este ejemplo:
set_false_path -from [get_cells bar_reg__0] -to [get_cells baz_metaguard_reg]
Este comando usa get_cells para seleccionar el inicio y el final del camino. Eso exige conocer los nombres de los objetos celda a ambos lados. En este caso tenemos el nombre "bar_reg__0", que demuestra el problema de fiarse de los nombres: ese nombre debería haber sido "bar_reg", pero como ya se explicó, acabó siendo "bar_reg__0" por una coincidencia.
Así que este ejemplo muestra uno de los problemas de set_false_path: la selección detallada de elementos lógicos del diseño a menudo obliga a depender de los nombres de los objetos. Este problema y sus posibles soluciones ya se han comentado antes.
Este comando puede simplificarse: como @baz_metaguard es una protección contra la metaestabilidad, no importa de dónde provengan los caminos hacia este registro. Entonces, ¿por qué no ignorar todos los caminos hacia este registro?
set_false_path -to [get_cells baz_metaguard_reg]
El efecto de este comando es exactamente el mismo.
set_false_path en lugar de set_clock_groups
Otra posibilidad es definir los caminos según los relojes. De hecho, el comando set_clock_groups anterior puede sustituirse por estas restricciones:
set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out1]] \
-to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]
set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out2] ] \
-to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]
set_false_path -from [get_clocks -of_objects [ get_pins pll_i/clk_in1] ] \
-to [get_clocks -of_objects [get_pins pll_i/clk_out1] ]
set_false_path -from [get_clocks -of_objects [ get_pins pll_i/clk_in1] ] \
-to [get_clocks -of_objects [get_pins pll_i/clk_out2] ]
Esta es la forma larga y detallada de crear los mismos caminos falsos. En lugar de dividir los relojes en grupos, hay dos comandos set_false_path por cada par de relojes no relacionados: uno para cada dirección. Resulta evidente lo fácil que es cometer un error con tantas restricciones. Y esto es un ejemplo sencillo con tres relojes.
No obstante, set_false_path se usa a menudo de esta manera en lugar de set_clock_groups. Lo más probable es que alguien copiara las restricciones de algún sitio.
Ejemplo de cómo puede ocurrir un error
Veamos un ejemplo sencillo de un posible error: por la discusión anterior, queda claro que solo hay un camino que necesita declararse como falso: de @bar a @baz_metaguard. Por tanto, de los cuatro comandos set_false_path del último ejemplo, solo este tiene importancia:
set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out2] ] \
-to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]
Los otros tres comandos set_false_path no cubren ningún camino. Pero, espera, ¿y si simplificamos aún más la restricción? ¿Quizá todos los caminos que terminan en @clk deberían ser falsos? ¿Qué tal esto?
set_false_path -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]
En realidad, como el comando create_clock que define "clk" está justo encima del comando set_false_path, esto podría escribirse así:
set_false_path -to [get_clocks clk]
Esta restricción es corta y elegante. Desgraciadamente, es horriblemente incorrecta: he sugerido que todos los caminos que terminan en @clk deberían ser falsos. Pero ¿qué pasa con el camino de @baz_metaguard a @baz? Ese es un camino de @clk a @clk. Ese camino, por supuesto, no debería ser falso. Pero los dos últimos comandos set_false_path incluyen todos los caminos que terminan en @clk, incluso si el camino empieza en @clk.
Es fácil cometer errores de este tipo, sobre todo si las restricciones de camino falso se escriben sin la precisión de una mentalidad matemática. Puede ayudar crear informes de temporización especiales, que revelen qué caminos son falsos y cuáles no.
Con esto concluye la discusión práctica sobre las restricciones de temporización para los caminos internos de la FPGA. La página siguiente explica los caminos multicycle (multicycle paths), que también pertenecen a este tema. Sin embargo, las restricciones de caminos multicycle no suelen recomendarse. Por tanto, puede saltarse a la introducción a las restricciones de E/S si lo prefieres.