Esta página pertenece a una serie de páginas sobre temporización (timing). Las páginas anteriores explicaron algunos temas básicos: la teoría detrás de los cálculos de temporización y la restricción de temporización del periodo del reloj. También se mostraron y explicaron varios informes de temporización. Ahora toca hablar de uno de los propósitos importantes de este conocimiento: resolver los problemas de temporización.
Introducción
La mayor dificultad de las herramientas de FPGA es cumplir los requisitos de las restricciones de temporización. Con suerte se consigue, pero a veces no. Y cuando no se consigue, somos los humanos quienes tenemos el deber de averiguar por qué ha fallado y de arreglarlo. Esta tarea tiene un nombre: la llamamos cierre de temporización (timing closure). Y no es una tarea fácil.
¿Por qué es difícil el cierre de temporización? La cuestión es que las herramientas tienen un algoritmo de colocación y enrutado (place and route) que intenta usar los recursos de la FPGA de forma óptima. Normalmente, ese algoritmo comienza colocando los elementos lógicos en la FPGA sin esforzarse demasiado. Luego comienza un proceso iterativo: las herramientas recorren todos los caminos (paths) y encuentran los que no cumplen las restricciones de temporización. Para corregir esos fallos, se toman medidas correctivas en esos caminos. Sobre todo, se mueven los elementos lógicos a otras posiciones de la FPGA y se ajusta el enrutado. En cuanto a medidas correctivas más avanzadas, cada herramienta de FPGA tiene sus propios métodos.
Cuando todos los caminos cumplen las restricciones de temporización, la implementación se considera terminada. Pero la implementación también puede terminar porque las herramientas no consiguieron ese objetivo y, en consecuencia, abandonaron el intento. En esa situación, nos quedamos con lo que las herramientas han conseguido cuando cesaron los esfuerzos. Ese resultado no es necesariamente óptimo: puede haber caminos en la implementación que podrían haberse mejorado, pero las herramientas estaban ocupadas arreglando otra cosa. Y cuando esa otra cosa falló, las herramientas se rindieron sin intentar arreglar lo demás. Es como si las herramientas dijeran: «No merece la pena perder el tiempo arreglando una implementación si de todos modos va a fracasar».
Nuestra tarea como diseñadores de FPGA es examinar ese resultado subóptimo y encontrar la razón por la que no se alcanzaron los objetivos de las restricciones de temporización.
Los algoritmos mejoran con el tiempo. Cuando hay una razón habitual para no cumplir las restricciones de temporización, la siguiente versión del software incluirá una solución específica para esa situación. Así que cuando las herramientas fallan, normalmente hay una buena razón.
Miramos lo que han conseguido las herramientas y nos preguntamos: ¿por qué han fallado? ¿Hemos pedido algo imposible? Aún más importante: ¿hemos pedido algo innecesario? Quizá el obstáculo que hizo fallar a las herramientas sea algo que ni siquiera necesitamos. ¿O quizá el algoritmo de optimización no funcionó demasiado bien? A veces es solo cuestión de mala suerte: la colocación inicial de los elementos lógicos puede ser tan mala que los intentos posteriores de mejorar el rendimiento están condenados al fracaso.
Sea cual sea el problema, encontrar la causa del fallo se parece a un detective que investiga la escena de un crimen: los hechos están delante de nosotros, pero la razón suele estar oculta. La mayoría de esos hechos se encuentran en los informes de temporización, pero las pistas no se revelan fácilmente. La pregunta que uno debe hacerse siempre es qué hay de malo, inusual o anómalo en el informe de temporización. Igual que el detective intenta encontrar al culpable, la meta es hallar el detalle que conduce al problema.
Pero para encontrar lo anómalo hay que saber lo que es normal: por ejemplo, ¿cuál es un retardo normal para una red con cierto fan-out? ¿Cuántos niveles lógicos son normales para implementar determinada función lógica? Las respuestas a preguntas de este tipo cambian de una FPGA a otra. Por tanto, es necesario adquirir experiencia leyendo y entendiendo los informes de temporización, incluso cuando todo va bien. Hay que saber qué aspecto tiene un informe de temporización cuando todo está correcto para poder localizar los puntos en los que el informe indica un problema. Por si te preguntabas por qué entré en tanto detalle en las páginas anteriores, esa es una de las razones.
El camino crítico
Cuando las herramientas no consiguen cumplir las restricciones de temporización, significa que hay al menos un camino con margen (slack) negativo. El camino con el margen más negativo se llama camino crítico (critical path). Ese nombre refleja la estrategia habitual para el cierre de temporización: centrarse en el camino crítico suele ser la manera de resolver un problema de temporización. Pero demostraré más abajo que esta estrategia también puede ser una pérdida de tiempo.
Cuando se cumplen las restricciones de temporización, el camino crítico es el camino con el menor margen. Ese camino a menudo no es interesante, porque las herramientas no intentan mejorar los caminos con margen positivo. Así que si el peor camino tiene margen positivo, puede ser una coincidencia que ese camino haya resultado ser el peor.
Pero si el margen es positivo y casi cero (digamos, menos de 0,2 ns), puede indicar que fue difícil conseguir que ese camino cumpliera las restricciones de temporización. Este tipo de caminos críticos pueden considerarse avisos de que esos caminos pueden dar problemas en el futuro (en particular cuando la FPGA se llene con más lógica y los esfuerzos de las herramientas se desvíen hacia otros caminos).
El informe de temporización suele contener un número limitado de caminos críticos para cada reloj. La opción por defecto de la mayoría de las herramientas de FPGA es mostrar unos pocos caminos críticos aunque su margen sea positivo (es decir, cuando se han cumplido las restricciones de temporización). Esta es la configuración recomendada.
Un ejemplo de camino crítico
Empezaré con un ejemplo del análisis de un camino crítico. Para este ejemplo, el código Verilog es el siguiente:
reg [24:0] calc, result;
reg [11:0] x, y, z;
always @(posedge clk)
begin
calc <= x * y + z;
result <= calc;
end
En este ejemplo, la frecuencia de @clk es de 250 MHz y no se usa ninguna PLL para generar ese reloj. Supongamos también que @x, @y y @z son registros síncronos con @clk. El código Verilog que asigna valores a esos registros no se muestra, porque es irrelevante.
Las restricciones de temporización no se cumplieron al intentar este código con Vivado. En el informe de temporización, el camino crítico era este:
Slack (VIOLATED) : -0.239ns (required time - arrival time) Source: x_reg[1]__0_replica_2/C (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Destination: calc_reg[23]/D (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Path Group: clk Path Type: Setup (Max at Slow Process Corner) Requirement: 4.000ns (clk rise@4.000ns - clk rise@0.000ns) Data Path Delay: 4.180ns (logic 1.642ns (39.282%) route 2.538ns (60.718%)) Logic Levels: 7 (CARRY8=4 LUT3=1 LUT4=1 LUT6=1) Clock Path Skew: -0.087ns (DCD - SCD + CPR) Destination Clock Delay (DCD): 3.176ns = ( 7.176 - 4.000 ) Source Clock Delay (SCD): 3.864ns Clock Pessimism Removal (CPR): 0.601ns Clock Uncertainty: 0.035ns ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE Total System Jitter (TSJ): 0.071ns Total Input Jitter (TIJ): 0.000ns Discrete Jitter (DJ): 0.000ns Phase Error (PE): 0.000ns Clock Net Delay (Source): 2.032ns (routing 0.396ns, distribution 1.636ns) Clock Net Delay (Destination): 1.748ns (routing 0.365ns, distribution 1.383ns) Location Delay type Incr(ns) Path(ns) Netlist Resource(s) ------------------------------------------------------------------- ------------------- (clock clk rise edge) 0.000 0.000 r AG12 0.000 0.000 r clk (IN) net (fo=0) 0.000 0.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.738 0.738 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.105 0.843 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.049 0.892 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.839 1.731 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.101 1.832 r clk_IBUF_BUFG_inst/O X2Y0 (CLOCK_ROOT) net (fo=106, routed) 2.032 3.864 clk_IBUF_BUFG SLICE_X54Y54 FDRE r x_reg[1]__0_replica_2/C ------------------------------------------------------------------- ------------------- SLICE_X54Y54 FDRE (Prop_HFF2_SLICEL_C_Q) 0.137 4.001 r x_reg[1]__0_replica_2/Q net (fo=21, routed) 0.371 4.372 x[1]_repN_2 SLICE_X56Y53 LUT6 (Prop_E6LUT_SLICEL_I1_O) 0.219 4.591 r calc[23]_i_101/O net (fo=2, routed) 0.550 5.141 calc[23]_i_101_n_0 SLICE_X54Y57 CARRY8 (Prop_CARRY8_SLICEL_DI[5]_CO[7]) 0.228 5.369 r calc_reg[23]_i_30/CO[7] net (fo=1, routed) 0.030 5.399 calc_reg[23]_i_30_n_0 SLICE_X54Y58 CARRY8 (Prop_CARRY8_SLICEL_CI_O[1]) 0.163 5.562 r calc_reg[23]_i_22/O[1] net (fo=3, routed) 0.351 5.913 calc_reg[23]_i_22_n_14 SLICE_X56Y57 LUT3 (Prop_C6LUT_SLICEL_I1_O) 0.146 6.059 r calc[23]_i_26/O net (fo=3, routed) 0.240 6.299 calc[23]_i_26_n_0 SLICE_X55Y58 LUT4 (Prop_A6LUT_SLICEM_I0_O) 0.089 6.388 r calc[23]_i_7/O net (fo=1, routed) 0.407 6.795 calc[23]_i_7_n_0 SLICE_X53Y57 CARRY8 (Prop_CARRY8_SLICEM_DI[2]_O[4]) 0.308 7.103 r calc_reg[23]_i_2/O[4] net (fo=1, routed) 0.538 7.641 P[20] SLICE_X54Y56 CARRY8 (Prop_CARRY8_SLICEL_S[4]_O[7]) 0.352 7.993 r calc_reg[23]_i_1/O[7] net (fo=1, routed) 0.051 8.044 P0_out[23] SLICE_X54Y56 FDRE r calc_reg[23]/D ------------------------------------------------------------------- ------------------- (clock clk rise edge) 4.000 4.000 r AG12 0.000 4.000 r clk (IN) net (fo=0) 0.000 4.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.515 4.515 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.066 4.581 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.034 4.615 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.722 5.337 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.091 5.428 r clk_IBUF_BUFG_inst/O X2Y0 (CLOCK_ROOT) net (fo=106, routed) 1.748 7.176 clk_IBUF_BUFG SLICE_X54Y56 FDRE r calc_reg[23]/C clock pessimism 0.601 7.777 clock uncertainty -0.035 7.741 SLICE_X54Y56 FDRE (Setup_HFF_SLICEL_C_D) 0.063 7.804 calc_reg[23] ------------------------------------------------------------------- required time 7.804 arrival time -8.044 ------------------------------------------------------------------- slack -0.239
El margen (slack) de este camino era de –0,239 ns, así que es un incumplimiento leve de las restricciones. Lo primero que hay que examinar es el principio y el final del camino: miramos el "Source" y el "Destination" en la cabecera del informe y encontramos x_reg y calc_reg. Así que el origen del problema es claramente esta parte:
calc <= x * y + z;
Esto no debería sorprender, porque es la única operación relevante del código Verilog. En un escenario real, no resulta tan obvio qué parte de la lógica causó el problema.
También es evidente en el informe que hay un número elevado de niveles lógicos: 7. El camino combinacional es demasiado largo. En otras palabras, hay demasiado que hacer entre dos flancos del reloj @clk.
Pero ¿cuál es la razón real del fallo? ¿Tal vez que el enrutado sea responsable del 61% del retardo? Recuerda la regla general de que el retardo de enrutado suele ser el 40% del retardo total. Entonces, ¿quizá convendría intentar que las herramientas de FPGA trabajaran mejor? Sin embargo, es poco probable que esa sea una solución exitosa, porque las herramientas suelen esforzarse al máximo antes de abandonar los intentos de cumplir las restricciones de temporización de un camino.
Intentar cambiar la función lógica entre @x y @calc es igualmente inútil: la multiplicación es imprescindible, así que no hay manera de poner algo más sencillo en su lugar.
En la página siguiente presentaré otros enfoques posibles para resolver un problema como este. Pero repasar una lista de técnicas no ayudará en este caso. Este ejemplo sencillo demuestra que a veces tenemos que pensar como detectives.
No hay sustituto para tu cerebro
La primera pregunta al leer un informe de temporización es qué tiene de anómalo. En este ejemplo, la respuesta es que el camino combinacional consta solo de slices: prácticamente todas las FPGA tienen unidades aritméticas específicas (DSP, ALU; los nombres varían) que se utilizan cuando se pide una multiplicación. De hecho, la multiplicación y la suma es la función más habitual en ese tipo de lógica específica. Así que la solución más sencilla en la mayoría de los casos es hacer que las herramientas usen una unidad aritmética específica. El informe de temporización de esta solución se muestra al final de esta página.
Pero la verdadera pregunta que deberíamos hacernos es por qué se usaron slices en lugar de una unidad aritmética específica. La razón más habitual es que todas las unidades aritméticas disponibles en la FPGA ya las está usando otra parte del diseño. En ese caso, el cambio necesario puede no tener nada que ver con el camino crítico: quizá haga falta liberar unas cuantas unidades aritméticas eliminando algo de lógica del diseño. Otra posibilidad sería indicar a las herramientas que repartan esas unidades aritméticas de otra manera entre las distintas partes del diseño.
En este ejemplo, se usaron slices en lugar de unidades aritméticas porque yo quise que ocurriera así: desactivé deliberadamente el uso de unidades aritméticas (con uno de los parámetros del sintetizador (synthesizer) de Vivado: puse max_dsp a cero). Pero eso no hace artificial este ejemplo. A veces se usan parámetros incorrectos con las herramientas de FPGA, y se llega exactamente a esta situación. De hecho, a veces es correcto no usar unidades aritméticas a propósito, porque se necesitan más en otra parte del diseño.
La solución fácil era, por tanto, usar unidades aritméticas específicas. Pero, ¿y si tenemos que usar slices? Una vez más, la solución es indirecta. Recuerda que la parte problemática era:
calc <= x * y + z;
Pero observa que justo después viene esto:
result <= calc;
Si @calc se usa solo en esa línea y no en ningún otro sitio, es posible dividir el cálculo en dos etapas. Esta técnica suele llamarse segmentación (pipelining). Así que el código Verilog cambia a esto:
reg [24:0] calc, result;
reg [11:0] x, y, z, z_d;
always @(posedge clk)
begin
z_d <= z;
calc <= x * y;
result <= calc + z_d;
end
En esta solución, a @calc se le asigna únicamente el resultado de la multiplicación. El valor de @z se suma a @calc solo en la etapa siguiente. Más concretamente, la operación de suma se hace entre @calc y @z_d, porque esa operación ocurre un ciclo de reloj más tarde. Por tanto, el valor de @result es exactamente el mismo que antes.
Fue fácil resolverlo así porque @result no era más que una copia retardada de @calc en el código Verilog original. En la vida real no solemos tener tanta suerte.
Observa que el camino crítico involucraba solo a @calc y a @x. @z ni siquiera aparecía en el camino. El propósito de esta manipulación es, por tanto, reducir la carga de la operación aritmética. O, más exactamente, reducir el número de niveles lógicos.
Recuerda que el camino crítico es el peor camino después de ejecutar un algoritmo de optimización. Ese algoritmo no pregunta por la causa del problema; más bien intenta mejorar los caminos con margen negativo. Y aunque la solución del problema requiera manipular @z, el camino relacionado con @z no era el camino crítico. Fue una coincidencia, pero ocurre con frecuencia.
El informe de temporización con el camino crítico de esta solución también se muestra al final de esta página. En él se ve que el número de niveles lógicos se redujo de 7 a 6. Como resultado, el retardo del camino de datos disminuyó en 0,715 ns, más que suficiente para cumplir las restricciones de temporización.
La lección de este ejemplo es que el camino crítico no siempre es la razón directa del problema. Sigue siendo correcto preguntarse por qué falló ese camino, pero la solución puede estar en otro sitio. Ten en cuenta que cada herramienta de FPGA tiene sus propias utilidades que ofrecen información capaz de ayudar a encontrar la causa raíz del problema. Merece la pena dedicar tiempo a explorar esas utilidades y a leer su documentación.
Evita el problema al principio en lugar de resolverlo después
Gran parte del trabajo del cierre de temporización puede evitarse si el diseño lógico se hace correctamente desde el principio. Esto exige ser consciente en todo momento de que un diseño lógico no es software. El propósito del código Verilog no es producir el resultado correcto durante la simulación. Lo que realmente importa es la salida que genera el sintetizador (synthesizer) a partir del código Verilog.
Un buen diseño lógico comienza pensando a fondo cómo debería cumplir la lógica su propósito. Eso incluye identificar los obstáculos potenciales relacionados con la temporización.
Los diseñadores de FPGA con menos experiencia suelen desarrollar el código Verilog por ensayo y error. La simulación se usa para ver si la lógica funciona como se espera, y se hacen correcciones poco a poco hasta que la salida de la simulación es correcta. El resultado puede ser una lógica que no se puede usar en el hardware: hay que reescribir el código Verilog por completo para cumplir las restricciones de temporización.
Es importante prever los caminos combinacionales que genera el código Verilog. La idea es mirar cada registro y seguir el camino combinacional hasta su final. Recuerda que un camino combinacional siempre comienza en un registro y termina en un registro.
Veamos este ejemplo:
reg [15:0] a, b;
wire [16:0] x, y;
reg [33:0] z;
assign x = a + 2;
assign y = b + 3;
always @(posedge clk)
z <= x * y;
Respecto a @a: cuando ese registro cambia, el camino combinacional llega a @x como primera etapa. Pero @x no es un registro: @x se actualiza mediante una asignación continua. Por tanto, el camino continúa hasta @z. Así pues, la lógica realiza dos operaciones importantes en este camino combinacional: una suma aritmética y una multiplicación aritmética. ¿Es demasiado? ¿Hace falta dividirlo en dos ciclos de reloj mediante segmentación (pipelining)? Eso depende de la frecuencia del reloj y de la FPGA utilizada.
Otro factor importante es lo difícil que resulta acortar los caminos combinacionales. A veces un camino combinacional largo es inevitable. Pero cuando es fácil mejorar la temporización de un camino, hazlo aunque haya caminos mucho peores en el diseño: si hay unos pocos caminos problemáticos, las herramientas a menudo consiguen cumplir las restricciones concentrando sus esfuerzos en esos caminos. Ayuda mucho que sea fácil cumplir los requisitos de temporización de los demás caminos.
Así que no hay reglas sobre lo que está permitido o prohibido para conseguir un diseño que cumpla las restricciones de temporización. Se necesita experiencia con el diseño de FPGA para tomar decisiones correctas en este terreno. La experiencia con las herramientas de FPGA concretas también es importante. La única regla que siempre es válida es: cuando sea posible mejorar la temporización con un cambio sencillo en el código Verilog, hazlo. No seas perezoso y haz ese cambio desde el principio. Ten siempre presente la temporización.
Escribir lógica rápida
Como ya se ha dicho, el objetivo es conseguir caminos combinacionales cortos entre registros: las funciones lógicas que calculan el siguiente valor de los registros deben ser sencillas. En otras palabras, hace falta un número reducido de niveles lógicos para implementar esas funciones lógicas.
Nuestra tarea como diseñadores de FPGA consiste en mirar el código Verilog y evaluar lo complicadas que serán las funciones lógicas. Para ello hace falta saber cómo traduce el sintetizador (synthesizer) el Verilog a elementos lógicos (LUT y otras primitivas (primitives) lógicas). Ese conocimiento se adquiere con la experiencia, que en parte se gana analizando los informes de temporización. Para complicarlo más, esa traducción cambia de una FPGA a otra. Así que no es tarea fácil escribir código Verilog que se traduzca en lógica rápida.
Si eres novato con FPGA, se recomienda dedicar tiempo a mirar los resultados de la implementación para aprender. El informe de temporización muestra ejemplos de cómo el diseño lógico se descompone en elementos lógicos sencillos. Las herramientas de FPGA también ofrecen otras utilidades para ver los elementos lógicos de bajo nivel.
Además, hay algunas reglas sencillas que pueden ayudar:
- Segmentación (pipelining): sé generoso con los registros. Cuando sea posible, divide las tareas de la lógica en trozos pequeños e inserta un registro después de cada paso. Las FPGA tienen muchos flip-flops (a menudo hay un flip-flop al lado de cada LUT), así que insertar registros no aumentará el nivel de ocupación de la FPGA. La única razón para evitar la segmentación es que complique demasiado el diseño.
- Cuando se usa if-then-else, evita encadenar muchas cláusulas "else". Cuando se pueda usar una sentencia "case", suele ser mejor. Una cláusula "else" suele exigir una función lógica que garantice que todo lo anterior es falso. Por tanto, varias cláusulas "else" pueden necesitar varios niveles lógicos.
- Evita los resets innecesarios. En particular, un reset síncrono (synchronous reset) añade un poco de complejidad a la función lógica. Los dos tipos de reset añaden dificultad al enrutado, porque son señales que deben llegar a muchos elementos lógicos. Hay una serie de páginas aparte sobre este tema.
- No crees máquinas de estados (state machines) enormes. No hay un límite estricto de cuántos estados son aceptables. Pero si el número de estados supera los 20, deberías plantearte reestructurar el diseño. Además, asegúrate de que el sintetizador usa codificación one-hot para las máquinas de estados grandes (por defecto, la mayoría de los sintetizadores lo hacen). Eso ayuda a generar lógica rápida.
- Normalmente es mejor añadir un registro adicional después de una RAM. Veamos este ejemplo de una RAM creada implícitamente:
reg [7:0] array[0:127]; reg [7:0] val; reg [6:0] addr; always @(posedge clk) val <= array[addr];Este código Verilog es correcto, pero observa que @val es la salida síncrona de la RAM. Así, cuando hay un flanco de subida del reloj, comienza la operación de la RAM, y @val solo se actualiza cuando se ha obtenido el valor del array. Por tanto, @val tiene un retardo de reloj a salida (clock-to-output) relativamente grande (en comparación con un flip-flop). En consecuencia, los caminos que comienzan en @val tienen una desventaja inherente. Esto se puede arreglar añadiendo un registro adicional:
reg [7:0] array[0:127]; reg [7:0] val_d, mem_out; reg [6:0] addr; always @(posedge clk) begin mem_out <= array[addr]; val_d <= mem_out; endObserva que esto no es funcionalmente equivalente: @mem_out es la salida de la RAM aquí. Solo un ciclo de reloj más tarde se copia esa salida en @val_d, así que no es un reemplazo exacto de @val. Pero @val_d es un registro real, con un retardo de reloj a salida bajo. En muchas FPGA, ese registro adicional forma parte de las block RAM, así que no se desperdician flip-flops. ¿He mencionado ya que no hay que preocuparse nunca por desperdiciar flip-flops?
Desgraciadamente, añadir ese registro a menudo complica bastante el diseño. Cuando sea así, es mejor no añadirlo y procurar mantener corto el camino combinacional que sale de @val.
Dos informes de temporización adicionales
He prometido dos informes de temporización en la sección «No hay sustituto para tu cerebro», más arriba. Los he puesto aquí, y no donde se mencionan, porque son largos y no son del todo relevantes en ese punto.
Observa que cada uno de estos dos informes es el camino crítico del escenario correspondiente. Por tanto, el camino no empieza y termina en los mismos registros que el camino mostrado antes.
El primer informe corresponde al primer ejemplo de código Verilog. A diferencia del informe anterior, a las herramientas se les permitió usar unidades aritméticas específicas. El resultado es que las restricciones de temporización se cumplieron sin dificultad.
Este informe se generó para una FPGA Kintex Ultrascale. En esta familia de FPGA, la unidad aritmética específica se llama DSP48E2. Observa que el camino comienza y termina en la misma unidad DSP48E2. Por tanto, el retardo lógico es del 100%.
Slack (MET) : 1.406ns (required time - arrival time) Source: calc_reg/DSP_A_B_DATA_INST/CLK (rising edge-triggered cell DSP_A_B_DATA clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Destination: calc_reg/DSP_OUTPUT_INST/ALU_OUT[10] (rising edge-triggered cell DSP_OUTPUT clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Path Group: clk Path Type: Setup (Max at Slow Process Corner) Requirement: 4.000ns (clk rise@4.000ns - clk rise@0.000ns) Data Path Delay: 2.445ns (logic 2.445ns (100.000%) route 0.000ns (0.000%)) Logic Levels: 4 (DSP_ALU=1 DSP_M_DATA=1 DSP_MULTIPLIER=1 DSP_PREADD_DATA=1) Clock Path Skew: -0.010ns (DCD - SCD + CPR) Destination Clock Delay (DCD): 3.392ns = ( 7.392 - 4.000 ) Source Clock Delay (SCD): 4.096ns Clock Pessimism Removal (CPR): 0.694ns Clock Uncertainty: 0.035ns ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE Total System Jitter (TSJ): 0.071ns Total Input Jitter (TIJ): 0.000ns Discrete Jitter (DJ): 0.000ns Phase Error (PE): 0.000ns Clock Net Delay (Source): 2.264ns (routing 0.756ns, distribution 1.508ns) Clock Net Delay (Destination): 1.964ns (routing 0.696ns, distribution 1.268ns) Location Delay type Incr(ns) Path(ns) Netlist Resource(s) ------------------------------------------------------------------- ------------------- (clock clk rise edge) 0.000 0.000 r AG12 0.000 0.000 r clk (IN) net (fo=0) 0.000 0.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.738 0.738 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.105 0.843 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.049 0.892 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.839 1.731 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.101 1.832 r clk_IBUF_BUFG_inst/O X2Y1 (CLOCK_ROOT) net (fo=80, routed) 2.264 4.096 calc_reg/CLK DSP48E2_X11Y34 DSP_A_B_DATA r calc_reg/DSP_A_B_DATA_INST/CLK ------------------------------------------------------------------- ------------------- DSP48E2_X11Y34 DSP_A_B_DATA (Prop_DSP_A_B_DATA_DSP48E2_CLK_A2_DATA[9]) 0.302 4.398 r calc_reg/DSP_A_B_DATA_INST/A2_DATA[9] net (fo=1, routed) 0.000 4.398 calc_reg/DSP_A_B_DATA.A2_DATA<9> DSP48E2_X11Y34 DSP_PREADD_DATA (Prop_DSP_PREADD_DATA_DSP48E2_A2_DATA[9]_A2A1[9]) 0.182 4.580 r calc_reg/DSP_PREADD_DATA_INST/A2A1[9] net (fo=1, routed) 0.000 4.580 calc_reg/DSP_PREADD_DATA.A2A1<9> DSP48E2_X11Y34 DSP_MULTIPLIER (Prop_DSP_MULTIPLIER_DSP48E2_A2A1[9]_U[10]) 0.994 5.574 f calc_reg/DSP_MULTIPLIER_INST/U[10] net (fo=1, routed) 0.000 5.574 calc_reg/DSP_MULTIPLIER.U<10> DSP48E2_X11Y34 DSP_M_DATA (Prop_DSP_M_DATA_DSP48E2_U[10]_U_DATA[10]) 0.164 5.738 r calc_reg/DSP_M_DATA_INST/U_DATA[10] net (fo=1, routed) 0.000 5.738 calc_reg/DSP_M_DATA.U_DATA<10> DSP48E2_X11Y34 DSP_ALU (Prop_DSP_ALU_DSP48E2_U_DATA[10]_ALU_OUT[10]) 0.803 6.541 r calc_reg/DSP_ALU_INST/ALU_OUT[10] net (fo=1, routed) 0.000 6.541 calc_reg/DSP_ALU.ALU_OUT<10> DSP48E2_X11Y34 DSP_OUTPUT r calc_reg/DSP_OUTPUT_INST/ALU_OUT[10] ------------------------------------------------------------------- ------------------- (clock clk rise edge) 4.000 4.000 r AG12 0.000 4.000 r clk (IN) net (fo=0) 0.000 4.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.515 4.515 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.066 4.581 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.034 4.615 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.722 5.337 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.091 5.428 r clk_IBUF_BUFG_inst/O X2Y1 (CLOCK_ROOT) net (fo=80, routed) 1.964 7.392 calc_reg/CLK DSP48E2_X11Y34 DSP_OUTPUT r calc_reg/DSP_OUTPUT_INST/CLK clock pessimism 0.694 8.086 clock uncertainty -0.035 8.050 DSP48E2_X11Y34 DSP_OUTPUT (Setup_DSP_OUTPUT_DSP48E2_CLK_ALU_OUT[10]) -0.104 7.946 calc_reg/DSP_OUTPUT_INST ------------------------------------------------------------------- required time 7.946 arrival time -6.541 ------------------------------------------------------------------- slack 1.406
El segundo informe corresponde al segundo ejemplo de código Verilog. En este ejemplo, la situación ha mejorado gracias a la segmentación (pipelining):
Slack (MET) : 0.433ns (required time - arrival time) Source: y_reg[1]__0/C (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Destination: calc_reg[23]/D (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Path Group: clk Path Type: Setup (Max at Slow Process Corner) Requirement: 4.000ns (clk rise@4.000ns - clk rise@0.000ns) Data Path Delay: 3.465ns (logic 1.653ns (47.706%) route 1.812ns (52.294%)) Logic Levels: 6 (CARRY8=4 LUT4=2) Clock Path Skew: -0.129ns (DCD - SCD + CPR) Destination Clock Delay (DCD): 3.373ns = ( 7.373 - 4.000 ) Source Clock Delay (SCD): 4.040ns Clock Pessimism Removal (CPR): 0.538ns Clock Uncertainty: 0.035ns ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE Total System Jitter (TSJ): 0.071ns Total Input Jitter (TIJ): 0.000ns Discrete Jitter (DJ): 0.000ns Phase Error (PE): 0.000ns Clock Net Delay (Source): 2.208ns (routing 0.756ns, distribution 1.452ns) Clock Net Delay (Destination): 1.945ns (routing 0.696ns, distribution 1.249ns) Location Delay type Incr(ns) Path(ns) Netlist Resource(s) ------------------------------------------------------------------- ------------------- (clock clk rise edge) 0.000 0.000 r AG12 0.000 0.000 r clk (IN) net (fo=0) 0.000 0.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.738 0.738 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.105 0.843 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.049 0.892 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.839 1.731 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.101 1.832 r clk_IBUF_BUFG_inst/O X2Y1 (CLOCK_ROOT) net (fo=121, routed) 2.208 4.040 clk_IBUF_BUFG SLICE_X54Y88 FDRE r y_reg[1]__0/C ------------------------------------------------------------------- ------------------- SLICE_X54Y88 FDRE (Prop_EFF2_SLICEL_C_Q) 0.138 4.178 r y_reg[1]__0/Q net (fo=25, routed) 0.505 4.683 y[1] SLICE_X53Y91 LUT4 (Prop_B6LUT_SLICEM_I0_O) 0.150 4.833 r calc[7]_i_28/O net (fo=1, routed) 0.344 5.177 calc[7]_i_28_n_0 SLICE_X53Y89 CARRY8 (Prop_CARRY8_SLICEM_DI[2]_CO[7]) 0.424 5.601 r calc_reg[7]_i_9/CO[7] net (fo=1, routed) 0.043 5.644 calc_reg[7]_i_9_n_0 SLICE_X53Y90 CARRY8 (Prop_CARRY8_SLICEM_CI_O[0]) 0.122 5.766 r calc_reg[23]_i_30/O[0] net (fo=3, routed) 0.402 6.168 calc_reg[23]_i_30_n_15 SLICE_X51Y88 LUT4 (Prop_C5LUT_SLICEL_I0_O) 0.169 6.337 r calc[15]_i_8/O net (fo=1, routed) 0.437 6.774 calc[15]_i_8_n_0 SLICE_X51Y92 CARRY8 (Prop_CARRY8_SLICEL_DI[1]_CO[7]) 0.422 7.196 r calc_reg[15]_i_1/CO[7] net (fo=1, routed) 0.030 7.226 calc_reg[15]_i_1_n_0 SLICE_X51Y93 CARRY8 (Prop_CARRY8_SLICEL_CI_O[7]) 0.228 7.454 r calc_reg[23]_i_1/O[7] net (fo=1, routed) 0.051 7.505 calc_reg[23]_i_1_n_8 SLICE_X51Y93 FDRE r calc_reg[23]/D ------------------------------------------------------------------- ------------------- (clock clk rise edge) 4.000 4.000 r AG12 0.000 4.000 r clk (IN) net (fo=0) 0.000 4.000 clk_IBUF_inst/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.515 4.515 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.066 4.581 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.034 4.615 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.722 5.337 clk_IBUF BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.091 5.428 r clk_IBUF_BUFG_inst/O X2Y1 (CLOCK_ROOT) net (fo=121, routed) 1.945 7.373 clk_IBUF_BUFG SLICE_X51Y93 FDRE r calc_reg[23]/C clock pessimism 0.538 7.910 clock uncertainty -0.035 7.875 SLICE_X51Y93 FDRE (Setup_HFF_SLICEL_C_D) 0.063 7.938 calc_reg[23] ------------------------------------------------------------------- required time 7.938 arrival time -7.505 ------------------------------------------------------------------- slack 0.433
La diferencia no es tan espectacular como cuando se usó una unidad aritmética específica. Pero aun así es suficiente para cumplir la restricción de temporización.
Con esto concluye la discusión general sobre el cierre de temporización. La página siguiente sugiere varias estrategias prácticas sobre este tema.