01signal.com

Actualización remota con reconfiguración parcial en Vivado

Esta es la última entrada de una serie de cuatro sobre la reconfiguración parcial (partial reconfiguration), también llamada Dynamic Function eXchange (DFX), con Vivado de Xilinx. Está pensada principalmente para quienes quieran usar la reconfiguración parcial en un escenario de actualización remota (Remote Update). Esta entrada presupone que ya has leído las tres anteriores.

Introducción

Después de hablar de la reconfiguración parcial en general y del procedimiento habitual de Vivado para este fin, esta entrada sienta las bases para usar esta técnica en la actualización remota de la lógica de la FPGA.

El principal problema de este escenario de uso es que el flujo de bits parcial (partial bitstream) debe ser compatible con el flujo de bits inicial, que posiblemente se generó años antes. Por tanto, la implementación padre original debe estar disponible durante la implementación de la lógica reconfigurable, o debe regenerarse para que produzca exactamente el mismo resultado, es decir, con exactamente la misma colocación y encaminamiento (place and route).

Puede que sea posible mantener intacto todo el proyecto de Vivado para no tener que volver a ejecutar la implementación padre. También es posible que, si se repite esa implementación, se obtenga exactamente el mismo resultado. Sin embargo, es difícil garantizar de forma fiable la posibilidad de publicar flujos de bits parciales en el futuro basándose en cualquiera de estos dos métodos.

Existe una solución fiable para este problema, pero para entender cómo funciona es necesario conocer antes los DCP y los OOC. A continuación se presenta una breve introducción a ambos temas.

El punto de control de diseño (DCP)

Recuerda que Vivado realiza la implementación de un diseño de FPGA mediante varias ejecuciones de diseño (Design Runs), que normalmente se llaman synth_1, impl_1, además de otras ejecuciones categorizadas como ejecuciones de módulo fuera de contexto (Out-of-Context, OOC).

Cada una de estas ejecuciones consiste en ejecutar un script Tcl que genera un «proyecto en memoria» (in-memory project) temporal, carga los archivos de diseño, establece propiedades y atributos, y llama a funciones Tcl que realizan la síntesis, la colocación, el encaminamiento, la generación de flujos de bits y otras tareas.

Este proyecto en memoria no crea archivos en el disco y no tiene nada que ver con el proyecto de Vivado visible en la GUI. Es un objeto en memoria que permite realizar muchas operaciones secuenciales con comandos Tcl.

Según avanza la implementación, se escriben en el disco archivos de puntos de control de diseño (Design Checkpoints, DCP) mediante el comando write_checkpoint de Tcl. El contenido de un archivo DCP es una instantánea del proyecto en memoria. En otras palabras, es una base de datos que refleja los archivos de diseño cargados y las operaciones realizadas sobre el proyecto desde que se cargaron.

Por ejemplo, la ejecución de síntesis (normalmente llamada synth_1) puede cargar todos los archivos HDL, los archivos de restricciones y los archivos de IP, y luego usar un comando Tcl llamado synth_design para realizar la síntesis de los archivos HDL. El resultado es una gran lista de conexiones (netlist) a partir de todas estas fuentes (incluidas las IP). Ten en cuenta que la netlist se guarda en memoria, como parte del proyecto en memoria. Por consiguiente, el script Tcl de la ejecución de síntesis hace una llamada a write_checkpoint para crear un archivo DCP, que es el producto de la síntesis. Con esto concluye la ejecución synth_1.

De hecho, el DCP generado por synth_1 se denomina DCP de netlist, aunque normalmente también contiene restricciones de los archivos XDC.

La ejecución de implementación que viene después, normalmente llamada impl_1, crea un nuevo proyecto en memoria y lee este DCP de netlist (entre otros) como punto de partida para las operaciones siguientes. Esta ejecución escribe varios archivos DCP, cada uno de los cuales es una instantánea del proyecto después de una etapa de procesamiento (por ejemplo, optimización del diseño, colocación, optimización física, encaminamiento, etc.).

Todas las ejecuciones, incluida synth_1, pueden cargar DCP en su proyecto en memoria. Es lo que hacen normalmente.

Ejecuciones de módulo fuera de contexto (OOC)

En Vivado, las IP suelen configurarse mediante una herramienta gráfica. Cuando se hace esto, un script genera los archivos fuente (principalmente archivos HDL y de restricciones) y realiza una síntesis de ellos. El resultado es un DCP de netlist, que se carga después en la ejecución de síntesis y en las de implementación del proyecto principal. Esto reduce el tiempo que tarda la implementación de todo el proyecto, porque elimina la necesidad de regenerar las fuentes de las IP y de sintetizarlas una y otra vez.

La ejecución de Vivado que toma los productos brutos (la información de configuración de una IP, algunos archivos HDL, o lo que haya) y los convierte en un DCP de netlist se denomina ejecución fuera de contexto (Out-of-Context run) en la terminología de Vivado. Este término se usa solo en relación con Vivado y no conozco otros usos. En realidad, es un nombre bastante desafortunado.

Este DCP de netlist lo cargan la ejecución de síntesis y las de implementación, normalmente mediante el comando Tcl read_ip, que en la práctica se reduce a cargar el DCP generado por la ejecución OOC.

Vivado también permite seleccionar un módulo HDL en el proyecto principal y pedir que su síntesis se realice como OOC (haciendo clic con el botón derecho en la fuente en el árbol de fuentes del Project Manager y eligiendo «Set as Out-of-Context for Synthesis…»).

El inconveniente de los OOC es que el sintetizador (synthesizer) puede realizar ciertas optimizaciones a través de las fronteras de los módulos cuando recibe todo el diseño como un único proyecto. Por tanto, la síntesis individual mediante OOC puede reducir el rendimiento y, además, desperdiciar recursos.

OOC y DCP con reconfiguración parcial

Cuando se selecciona un archivo fuente como módulo de nivel superior de un módulo reconfigurable, Vivado crea una ejecución OOC para la síntesis de ese módulo y sus submódulos. Esta ejecución genera un DCP de netlist para usar en la implementación correspondiente. Esto es cierto tanto para la implementación padre como para las implementaciones hijas: el módulo reconfigurable siempre está representado con un DCP independiente.

Sin embargo, este DCP de netlist se usa de forma distinta en la implementación padre y en las hijas: el DCP de netlist asignado a la implementación padre lo cargan synth_1 e impl_1, igual que se hace con una IP en una implementación normal. El hecho de que haya reconfiguración parcial influye en este proceso sobre todo a través de las restricciones de ubicación que impone la planificación física.

La implementación hija, en cambio, no tiene una fase de síntesis propia. Más bien, mezcla el DCP de netlist del módulo reconfigurable con el DCP final de la implementación padre (es decir, el DCP después de la colocación y el encaminamiento). Más concretamente, la implementación hija toma el DCP final de la implementación padre, elimina la parte de lógica reconfigurable e inserta en su lugar el DCP de netlist de su propio módulo reconfigurable. Algo así como quitarle el centro a un calabacín para hacer un calabacín relleno.

Y ahora es el momento de desglosar esto en comandos Tcl.

Los entresijos de las implementaciones padre-hija

El lugar al que hay que mirar para ver cómo funciona la implementación entre bastidores es el archivo Tcl que lleva el nombre del proyecto (con el sufijo .tcl). Este archivo está en el mismo directorio donde se crean los archivos de la implementación.

En particular, es interesante el script que ejecuta la implementación hija. No es casualidad que el capítulo 3 («Vivado Software Flow») de la guía de usuario correspondiente, UG909, muestre y explique ese script, aunque no lo diga directamente.

Como acabo de mencionar, la implementación padre no tiene mucho de especial, salvo que depende de un DCP para la netlist de su módulo reconfigurable y que se aplican restricciones de planificación física. Es más o menos como cualquier diseño jerárquico.

Pero entonces la implementación padre escribe dos flujos de bits en lugar de uno, mediante comandos Tcl como estos:

write_bitstream -force -no_partial_bitfile theproject.bit
write_bitstream -force -cell pr_block_ins pr_block_ins_lpf_partial.bit

y a continuación crea el DCP que usará más tarde la implementación hija:

update_design -cell pr_block_ins -black_box
lock_design -level routing
write_checkpoint -force theproject_postroute_physopt_bb.dcp

Recuerda que mientras esta parte se está ejecutando hay un proyecto en memoria que empezó cargando los DCP de netlist y pasó por la colocación y el encaminamiento y todas las demás optimizaciones. Estas tres líneas Tcl se ejecutan después de escribir el flujo de bits, así que el proyecto en memoria está en la fase realmente final.

Es el momento de hacer el hueco y montar el «calabacín relleno»: el comando update_design convierte el módulo reconfigurable en una caja negra (black box). En otras palabras, se elimina toda su lógica, dejando sitio para que entre otra.

Después se bloquea la colocación y el encaminamiento del diseño mediante el comando lock_design. Tras ello, la instantánea del proyecto se escribe en theproject_postroute_physopt_bb.dcp. «bb» significa «Black Box» (caja negra), por supuesto.

La parte relevante del script de la implementación hija es la siguiente:

create_project -in_memory -part xc7k325tffg900-2
set_property design_mode GateLvl [current_fileset]
add_files -quiet .../impl_1/theproject_postroute_physopt_bb.dcp
add_files -quiet .../two_synth_1/pr_block.dcp
set_property SCOPED_TO_CELLS pr_block_ins [get_files .../two_synth_1/pr_block.dcp]
link_design -top theproject -part xc7k325tffg900-2 -reconfig_partitions pr_block_ins
opt_design
write_checkpoint -force theproject_opt.dcp
[ ... ]

y a partir de ahí continúa con la colocación y el encaminamiento, etc.

Observa que el script de implementación anterior utiliza solo dos fuentes, y ambas son DCP:

Cuando la implementación continúa, el hecho de que el primer DCP estuviera bloqueado garantiza que nada de la lógica estática se mueve. No obstante, la colocación y el encaminamiento del módulo reconfigurable se realizan como siempre, a partir del DCP de netlist.

Como nota al margen, si hay IP XCI que pertenecen al módulo reconfigurable, se añade una línea como la siguiente para cada IP, entre los dos comandos add_files de antes:

read_ip -quiet .../theproject.srcs/sources_1/ip/blkmem/blkmem.xci

Esto no es exclusivo de la reconfiguración parcial: se hace así en cualquier implementación.

Justo antes de escribir los dos flujos de bits, la implementación hija verifica que el diseño encaminado obtenido es compatible con la lógica estática de la implementación padre, en particular en lo que respecta a la colocación y el encaminamiento.

El comando Tcl para ello es algo así:

pr_verify -full_check -initial /path/to/impl_1/theproject_postroute_physopt.dcp -additional /path/to/child_1_impl_1/theproject_routed.dcp -file child_1_impl_1_pr_verify.log

Observa que esto compara dos archivos DCP, al margen del proyecto en memoria. El DCP encaminado de la implementación padre se compara con el DCP final de la implementación hija. La salida de esta comparación va a un archivo llamado *_pr_verify.log.

Esta comparación garantiza que el flujo de bits parcial es compatible, en el sentido de que puede cargarse cuando el flujo de bits de la implementación padre ya está cargado en la FPGA. La comprobación recorre los elementos lógicos de la partición estática y también el encaminamiento.

pr_verify devuelve un estado de error si hay una incompatibilidad. Si ocurre, se impide la creación de flujos de bits en el script de Vivado. Es importante tener presente este paso cuando se escriben scripts de implementación personalizados.

No hay motivo para que esta verificación falle nunca, pero si lo hace, la generación del flujo de bits falla con muchos errores como: «ERROR: [Constraints 18-891] HDPRVerify-08: design check point .../impl_1/theproject_postroute_physopt.dcp places instance ... at site SLICE_X118Y125, yet design check point .../impl_2/theproject_routed.dcp does not. Both check point must have the same static placement result».

Es probable que estos mensajes de error alcancen el límite de 100 y luego se silencien.

Solución para el escenario de actualización remota

Recuerda lo dicho antes: el reto es que el resultado de la implementación padre original debe estar disponible cuando la implementación de la lógica reconfigurable se lleva a cabo como implementación hija.

La solución de fuerza bruta consiste en hacer una copia de todo el directorio del proyecto de Vivado, junto con los archivos de los que pueda depender. Cuando surja la necesidad de generar un nuevo flujo de bits parcial, restaura todos los archivos, fuerza la implementación padre como actualizada y crea un flujo de bits solo para la implementación hija. Técnicamente este método es correcto, pero probablemente acabará resultando molesto. Si eliges este camino, asegúrate de ejecutar manualmente pr_verify contra el archivo DCP de la implementación padre original, porque este método no detectará un cambio no intencionado en la implementación padre.

Hay otras dos alternativas, que se basan en conocer cómo interactúan la implementación padre y las hijas, y esa interacción se produce mediante dos archivos DCP, como se explicó antes. La ventaja evidente de estas dos alternativas es que sabes lo que estás haciendo.

El principio que hay detrás de ambas alternativas es asegurarse de que el flujo de bits parcial se apoye en los dos archivos DCP que se generaron junto con el flujo de bits inicial, a los que me referiré como los DCP de referencia (Golden DCPs).

La primera alternativa es realizar la implementación del flujo de bits parcial como un script Tcl en modo sin proyecto (non-project flow). En esencia, se trata de ejecutar el script que Vivado crea para la implementación hija, pero modificándolo para usar los DCP de referencia. Más concretamente, hay que modificar los argumentos de los comandos link_design y pr_verify para que se apoyen en los DCP de referencia.

El principal inconveniente de esta alternativa es que ejecutar un script Tcl con el flujo sin proyecto no se integra bien con la interfaz gráfica de Vivado, así que resulta considerablemente más difícil procesar los mensajes, abrir el diseño implementado para revisarlo, etc.

El truco de los DCP de referencia

Idealmente, habría sido posible modificar automáticamente el script que Vivado genera para la ejecución de la implementación hija, de modo que se refiriera a los DCP de referencia. Lamentablemente, no parece haber una forma fiable de hacerlo.

Sin embargo, Vivado permite definir scripts Tcl para que se ejecuten antes y después de ciertas fases de la implementación. Esos scripts se ejecutan desde el script de la ejecución de implementación, así que no se pueden usar para cambiar el propio script de la ejecución.

Con todo, esto abre la puerta a un método algo feo, que es la segunda alternativa: la idea es sobrescribir los DCP de la implementación padre con los DCP de referencia. De este modo, la implementación hija se ejecuta con normalidad, pero se apoya en los DCP de referencia, sea cual sea el resultado que la implementación padre haya generado.

La ventaja de este método es que la forma habitual de trabajar con Vivado no cambia: se hacen cambios en la lógica reconfigurable, Vivado vuelve a ejecutar el OOC para la síntesis del módulo reconfigurable y, a continuación, la implementación hija para generar el flujo de bits parcial. Como no se hacen cambios en la lógica estática, Vivado no tiene motivo para lanzar sus ejecuciones relacionadas.

El script Tcl que implementa este método de copia de los DCP de referencia es el siguiente:

if { [catch {
    set parentimpldir "[ file normalize "../impl_1"]"
    set goldendir "[ file normalize "/path/to/golden"]"

    file copy -force "[file normalize "$goldendir/theproject_postroute_physopt_bb.dcp"]" "$parentimpldir/"
    file copy -force "[file normalize "$goldendir/theproject_postroute_physopt.dcp"]" "$parentimpldir/"
} errmsg ] } {
    send_msg_id golden-reconfig-1 error "Failed to copy golden parent reconfiguration file(s): $errmsg"
    return -code error
}

Este script supone que la implementación padre se guarda en el directorio «impl_1», adyacente al directorio de la implementación hija (lo más probable es que así sea), y que los dos DCP de referencia están guardados en el directorio definido en la línea 3 del script.

Haz clic con el botón derecho en la ejecución hija (por ejemplo, child_0_impl_1) en la pestaña Design Runs, elige «Change Run Settings…» y, en el cuadro que se abre, establece tcl.pre para la inicialización del diseño (init_design) con ese script. O, en Tcl, si el script se ha guardado como golden_pr.tcl:

add_files -fileset utils_1 -norecurse /path/to/golden_pr.tcl
set_property STEPS.INIT_DESIGN.TCL.PRE [ get_files /path/to/golden_pr.tcl -of [get_fileset utils_1] ] [get_runs child_0_impl_1]

Lo importante que hay que tener en cuenta al usar este script es ignorar toda la información que presente Vivado sobre la implementación padre, así como cualquier archivo que se genere en su nombre. Vivado puede abrir su GUI de diseño implementado y mostrar sus informes, pero todo eso puede ser completamente irrelevante. Así que esto deja la puerta abierta a cierta confusión.

Una segunda molestia posible es que Vivado ejecute la implementación padre de vez en cuando como respuesta a un cambio en los ajustes del proyecto, o incluso en el archivo de restricciones. Esto se puede evitar haciendo clic con el botón derecho en la fila correspondiente de la pestaña Design Runs y eligiendo «Force Up-to-Date». Este menú solo aparece cuando la ejecución está en estado Out-of-Date, es decir, completada pero Vivado considera que hace falta una actualización. El comando Tcl correspondiente es, por ejemplo:

set_property needs_refresh false [get_runs synth_1]

Qué archivos guardar junto con los DCP de referencia

Está claro que los DCP de referencia deben guardarse en un lugar seguro para conservar la capacidad de generar archivos de flujo de bits compatibles en el futuro. Además, es buena idea comprimir todo el proyecto de Vivado en un archivo .tar.gz o .zip para poder retomarlo rápidamente. Alternativamente, o además de eso, se puede generar un archivo del proyecto con File > Project > Archive… o algo como:

archive_project /path/to/theproject.xpr.zip -force -include_local_ip_cache -include_config_settings

Ten en cuenta, sin embargo, que tanto el archivo del proyecto como otros archivos de un proyecto de Vivado pueden contener rutas absolutas, así que desplegar el proyecto en otro directorio o en otro ordenador puede no funcionar como se espera. Esto también es cierto para el archivo comprimido.

La versión de Vivado utilizada queda escrita en todos los archivos de informe posibles y en el archivo de proyecto .xpr, pero apuntarla no viene mal. Quizás convenga conservar una copia ejecutable de esa versión de Vivado, aunque no haya ninguna razón aparente por la que una actualización del software vaya a importar. Mezclar los DCP de referencia de una versión de Vivado con el DCP de netlist de la lógica reconfigurable de otra versión probablemente funcione bien. Pero no es algo para lo que Vivado esté diseñado.

Resumiendo, el conjunto mínimo de archivos es:

Observa que el uso del flujo de bits de borrado en FPGA Ultrascale está relacionado con la lógica que ya está en la FPGA. Por eso hay que guardar el flujo de bits de borrado correspondiente al flujo de bits inicial. Ten en cuenta también que, si el flujo de bits inicial es el resultado de alguna implementación hija, hay que guardar el flujo de bits de borrado de esa implementación.

En cuanto a guardar las fuentes de la implementación padre, es importante porque determinan las conexiones entre la lógica estática y la reconfigurable. Por ejemplo, si se editan las fuentes, se añade un puerto a la lógica reconfigurable y ese puerto aparece en la instanciación (instantiation), el conjunto de pines de partición cambia. Por tanto, la netlist de la lógica reconfigurable tendrá pines externos que no aparecen en el diseño estático original.

Cuando se produce ese desajuste, la implementación hija falla con un mensaje de error parecido a: «ERROR: [Netlist 29-77] Could not replace (cell 'pr_block_bb', library 'work_pr_block_ins_pr_block_ins_4', file 'NOFILE') with (cell 'pr_block', library 'work', file 'pr_block.edf') because of a port interface mismatch; in strict mode, no extra ports are allowed. 8 ports are missing on the original cell. 5 of the missing ports are: 'thingy[7]' 'thingy[6]' 'thingy[5]' 'thingy[1]' 'thingy[0]'».

Lo que falla realmente en este caso es el comando link_design (ver más arriba), que une la lógica estática y la lógica combinacional (combinational logic).

La forma simple y obvia de evitar este problema es no cambiar la parte estática del diseño, ni tampoco la lista de puertos de la lógica reconfigurable.

Sin embargo, en realidad sí se pueden hacer cambios en la parte estática del proyecto: lo que debe seguir igual es la instanciación del módulo reconfigurable. Mientras la implementación transcurra sin problemas, incluida la verificación final, no hay ningún problema.

Resumen

Aunque no existe una solución totalmente limpia para el uso de la reconfiguración parcial en la actualización remota, hay algunas estrategias disponibles para alcanzar este objetivo de todos modos.

El punto importante es que, en principio, los dos DCP de referencia son todo lo que se necesita para construir y verificar un flujo de bits parcial de un proyecto existente.

Y por muy poco convencionales que puedan parecer las estrategias presentadas aquí, ten en cuenta que la verificación que realiza pr_verify es una comprobación exhaustiva de la compatibilidad entre el flujo de bits parcial y la lógica estática que ya está en su sitio. Siempre que se use el DCP de referencia correcto para esta verificación y la prueba pase, no hay nada más de lo que preocuparse. Además de que la implementación hija cumpla las restricciones de temporización (timing constraints), por supuesto, pero eso es cierto para cualquier implementación de un diseño.

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)