Desarrollo del "firmware" más simple para FPGAs instalados en Redd. Parte 2. Código del programa

Entonces, en el último artículo, desarrollamos el sistema de procesador más simple, con la ayuda de la cual planeamos probar el chip RAM conectado al FPGA del complejo Redd. Hoy, crearemos un programa C ++ para este entorno de hardware, y también descubriremos cómo inyectar este programa y, lo más importante, depurarlo.



El programa se desarrolla en el entorno Eclipse. Para iniciarlo, seleccione el elemento de menú Herramientas-> Nios II Software Build Tools para Eclipse .



Necesitamos crear un proyecto y un BSP para ello. Para hacer esto, haga clic con el botón derecho del mouse en el área del Explorador de proyectos y seleccione el elemento de menú Nuevo-> Aplicación Nios II y BSP de la plantilla .



La plantilla básica sobre la cual se creará el proyecto se creó durante la generación del sistema procesador. Por lo tanto, encontramos el archivo que lo contiene.



También le daremos un nombre al proyecto (lo tengo SDRAMtest ) y seleccionaremos el tipo de proyecto. Elegí Hello World Small . Me gustaría elegir la Prueba de memoria , estamos haciendo una prueba de memoria, pero ahora estamos considerando una forma general de crear aplicaciones. Por lo tanto, elegimos la opción general.



Hemos creado dos proyectos. El primero es nuestro proyecto, el segundo es BSP (Board Support Package, en términos generales, bibliotecas para trabajar con equipos).



Lo primero que suelo hacer es editar la configuración de BSP. Para hacer esto, selecciono el segundo de los proyectos creados, presiono el botón derecho del mouse y selecciono el elemento del menú Nios II -> BSP Editor .



En el editor, todo se divide en grupos:



Pero para no correr a lo largo de ellos, seleccionaré la raíz del árbol, el elemento Configuración y consideraré todo linealmente. Voy a enumerar a qué debe prestar atención. Pantalla de configuración inicial:



El soporte para salir y el soporte para eliminar cuando se sale está deshabilitado, lo que arroja piezas innecesarias de gran tamaño del código, pero inicialmente están deshabilitadas. Estas grajillas se desactivaron, ya que elegí la opción mínima Hola, Mundo . Al elegir otros tipos de código, también es mejor eliminar estos daws. No puedo resistirme y activar el soporte de C ++. También es necesario eliminar la verificación SysID , de lo contrario, nada funcionará. El hecho es que al hacer el sistema de hardware, no agregué el bloque correspondiente. Los ajustes restantes se explican por sí mismos y no se han cambiado. En realidad, tampoco he cambiado otras configuraciones. Un poco más tarde volveremos aquí, pero por ahora presionamos el botón Generar. Estamos creando una nueva versión de BSP basada en la configuración realizada. Al final, haz clic en Salir.

Ahora comienza la diversión. Cómo armar un proyecto. Presione el Ctrl + B habitual - obtenemos un error. Error de decodificación no:



Nada razonable en la consola tampoco:



Seleccione el Proyecto de construcción para el proyecto SDRAMtest , obtenemos una explicación razonable:



De hecho, las plantillas que no funcionan son la identidad corporativa de Quartus. Esta es otra razón por la que no elegí Prueba de memoria . Cada vez hay más carreras. Aquí está claro cuál es el problema.

Esta función del archivo alt_putstr.c falla :

/* * Uses the ALT_DRIVER_WRITE() macro to call directly to driver if available. * Otherwise, uses newlib provided fputs() routine. */ int alt_putstr(const char* str) { #ifdef ALT_SEMIHOSTING return write(STDOUT_FILENO,str,strlen(str)); #else #ifdef ALT_USE_DIRECT_DRIVERS ALT_DRIVER_WRITE_EXTERNS(ALT_STDOUT_DEV); return ALT_DRIVER_WRITE(ALT_STDOUT_DEV, str, strlen(str), 0); #else return fputs(str, stdout); #endif #endif } 

Lo arreglaremos de alguna manera más tarde (para esto necesitamos complicar el sistema de hardware). Hoy simplemente no lo necesitamos. Reemplace su cuerpo con retorno 0 . El proyecto comienza a ensamblarse.

Genial Ya tenemos un archivo elf listo (incluso si no realiza ninguna función útil) y tenemos un equipo en el que puede cargar el archivo hexadecimal (¿recuerda cómo le dijimos al compilador Quartus que los datos de RAM deberían cargarse configurando Onchip RAM?). ¿Cómo convertimos el duende a hexadecimal ? Muy simple Nos ponemos en marcha en el proyecto de trabajo, presionamos el botón derecho del mouse, seleccionamos el elemento del menú Crear objetivos-> Crear :



En la ventana que aparece, primero intente seleccionar la primera opción ( mem_init_install ):



Nada va a pasar Pero del mensaje de error que se nos da, aprendemos cómo finalizar el proyecto para Quartus:

 ../SDRAMtest_bsp//mem_init.mk:230: *** Deprecated Makefile Target: 'mem_init_install'. Use target 'mem_init_generate' and then add mem_init/meminit.qip to your Quartus II Project. Stop. 

Seleccionaremos la compilación con el propósito de mem_init_generate , después de lo cual (justo después) agregaremos el archivo qip especificado al proyecto de hardware (ya aprendimos cómo agregar archivos cuando agregamos un sistema de procesador al proyecto).



Bueno, el archivo hexadecimal en sí también se puede sentir con las manos. Aquí esta:



Genial Tenemos todo para comenzar a llenar el programa con una funcionalidad real. Vamos al archivo hello_world_small.c . Honestamente, me ofende un poco trabajar con C. puro Por lo tanto, lo renombraré cpp. Y para que nada salga mal, agregaré un hechizo mágico al texto existente:



Mismo texto:
 extern "C" { #include "sys/alt_stdio.h" } int main() { alt_putstr("Hello from Nios II!\n"); /* Event loop never exits. */ while (1); return 0; } 


Es importante realizar la operación Clean Project después de cambiar el tipo de archivo; de lo contrario, el compilador mostrará mensajes de error de que el archivo * .c no se encontró en función de cierta información almacenada en caché.

Haremos que la prueba de memoria sea simple. No tengo la tarea de enseñarle al lector a probar el chip RAM correctamente. Solo nos aseguramos superficialmente de que la dirección y los buses de datos no sean pegajosos y no tengan interrupciones. Es decir, escribimos en cada celda todos los ceros y la dirección de la celda. Nuestra tarea es crear un código de trabajo, y todo lo demás (otras constantes de relleno y retraso para verificar que los datos se están regenerando) son detalles de implementación que complican el texto pero que no cambian su esencia.

Comencemos con una pregunta simple y natural: "¿Dónde se encuentra la SDRAM en el espacio de direcciones?" Recuerdo que llamamos a la función de asignación automática de direcciones, pero ni siquiera miramos qué direcciones se asignaron realmente. De hecho, incluso ahora no miraremos allí. Toda la información necesaria está en el archivo:
... \ SDRAMtest_bsp \ system.h .

Como resultado, obtenemos el siguiente código:
 extern "C" { #include "sys/alt_stdio.h" #include <stdint.h> #include "../SDRAMtest_bsp/system.h" #include <altera_avalon_pio_regs.h> } int main() { bool bRes = true; volatile static uint32_t* const pSDRAM = (uint32_t*)NEW_SDRAM_CONTROLLER_0_BASE; static const int sizeInDwords = NEW_SDRAM_CONTROLLER_0_SPAN / sizeof (uint32_t); //  for (int i=0;i<sizeInDwords;i++) { pSDRAM [i] = 0; } //   for (int i=0;i<sizeInDwords;i++) { if (pSDRAM [i] != 0) { bRes = false; } } //   for (int i=0;i<sizeInDwords;i++) { pSDRAM [i] = i; } //    for (int i=0;i<sizeInDwords;i++) { if (pSDRAM [i] != i) { bRes = false; } } if (bRes) { IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x01); } else { IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x02); } /* Event loop never exits. */ while (1); return 0; } 


Recopilamos el archivo hexadecimal (te lo recuerdo a través de este cuadro de diálogo):



Después de eso compilamos el equipo en Quartus e ingresamos al programador para cargar el "firmware" resultante con el código de programa inicializado resultante en el chip.



Vierta el "firmware" y reciba un mensaje de que el sistema vivirá solo mientras esté conectado a JTAG (características de la licencia del kernel SDRAM en el entorno de desarrollo gratuito). En realidad, para el caso de Redd, esto no es crítico. Este JTAG está ahí todo el tiempo.



Los resultados de la prueba de RAM se pueden ver en el conector, los contactos C21 (exitoso) o B21 (error). Nos conectamos a ellos con un osciloscopio.



Ambas salidas están en cero. Algo está mal aquí. Queda por entender exactamente qué. De hecho, un programa inoperativo es genial, porque ahora comenzaremos a aprender la depuración de JTAG. Apuntamos al proyecto, seleccione Debug As-> Nios II Hardware .



La primera vez que el sistema no encuentra el hardware. Se ve así (tenga en cuenta la cruz roja en el encabezado de la pestaña resaltada):



Cambie a la pestaña Conexión de destino y seleccione la casilla de verificación Ignorar ID de sistema que no coincida . Durante los experimentos posteriores, a veces también tuve que configurar la marca de tiempo Ignorar sistema no coincidente . Por si acaso, lo resaltaré en la imagen no con un rojo, sino con un marco amarillo para enfatizar que no es necesario instalarlo ahora, pero si el botón Depurar no está activado, puede ser el momento de instalarlo.



Aplicar ( Aplicar ), luego haga clic en Actualizar conexiones (este botón está oculto, debe desplazarse):



Aparece un depurador en la lista, puede hacer clic en Depurar ...



Nos detuvimos en la función main () . Si lo desea, puede poner un punto de interrupción al final del algoritmo y verificar si el programa lo alcanza:



Ejecute el programa:



El punto de interrupción no funcionó. Detengamos el programa y veamos dónde se ejecuta.



Todo es malo El programa claramente se estrelló. Organizando sucesivamente puntos de interrupción, luego deteniéndonos (con el botón rojo "Detener") y reiniciando el programa (usando el botón de error), encontramos el área del problema.

Todo muere cuando se ejecuta esta línea para el primer elemento:


Mismo texto:
  for (int i=0;i<sizeInDwords;i++) { if (pSDRAM [i] != 0) { bRes = false; } } 


Desde el punto de vista de C ++, todo está limpio aquí. Pero si abre el código desmontado, puede ver que hay demasiados comandos uniformes allí. Parece que el código se ha borrado a sí mismo. Y podría hacer esto si el enlazador lo pone en SDRAM (cuyo contenido sobrescribe este código).



Dejamos de depurar, cerramos la perspectiva de depuración.



Vamos al editor BSP, en el que estábamos al principio de este artículo, pero a la pestaña Linker Script . Si corrigí esta pestaña al principio, no podría mostrar la técnica de ingresar la depuración JTAG (y enfatizar todo su poder en comparación con la salida de depuración simple, porque el hecho del código atascado durante la depuración JTAG me llamó la atención). Así es Un buen enlazador pone todo en la memoria, cuyo tamaño es mayor.



Redirigimos todo a onchip_memory ... Ahora tenemos SDRAM, es solo una pieza de memoria, cuyo rendimiento aún no hemos sido garantizados. No puede cederlo a ninguna acción compiladora automatizada.



Reconstruimos bsp , reconstruimos el proyecto. ¿Necesito volver a crear la imagen de memoria y sobrecargar el FPGA? Entonces, para el trabajo semiautónomo, será necesario, pero mientras la depuración está en progreso, no. Una nueva versión del programa se cargará inmediatamente en la RAM cuando comience una nueva sesión de depuración. Pero para que en el próximo arranque del FPGA no sea necesario iniciar el depurador, hacer un nuevo archivo HEX al final de la depuración y es deseable ensamblar el "firmware" del FPGA con él.

Para el nuevo código, se ha alcanzado un punto de interrupción, el resultado de la prueba es verdadero :



El osciloscopio se ha divertido: el rayo amarillo ha volado en la unidad.



Prueba aprobada Vayamos un poco complicado y verifiquemos simultáneamente el rendimiento del sistema. Hagamos la final así:

  //    GPIO if (bRes) { while (true) { IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x01); IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x00); } } else { while (true) { IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x02); IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x00); } } 

Como resultado, tenemos un meandro en el osciloscopio (todo estaba conectado a través de un bucle largo, por lo que apareció una captación capacitiva en la segunda línea, no le prestes atención):



El resultado es una frecuencia de aproximadamente 8,3 MHz a una frecuencia de reloj de 50 MHz sin ajustar el núcleo del procesador y optimizar el código del programa. Es decir, el acceso a los puertos va a una frecuencia ligeramente superior a 16 MHz, que corresponde a un tercio de la frecuencia del sistema. No es que fuera completamente diferente, pero mejor que 4 MHz a una frecuencia de reloj de 925 MHz para Cyclone V SoC ... No, el procesador NIOS II en sí es varias veces más lento que el quinto núcleo Cyclone ARM, pero el procesador, como dije , tenemos x64 en el sistema, aquí necesitamos más el núcleo, que proporciona la lógica del hierro. Y esta lógica se proporciona precisamente a través del trabajo con puertos. Si trabajar con puertos es lento, entonces todo lo demás estará inactivo regularmente, esperando que el bus termine de funcionar. Las características reveladas son el límite de acceso del procesador a los puertos, pero no el hardware en su conjunto. Sin embargo, cómo implementar el trabajo como un todo, lo consideraremos en el próximo artículo.

Conclusión


El artículo muestra cómo crear y configurar un proyecto en C ++ para el entorno de procesador más simple desarrollado para el complejo Redd. Se muestran los métodos de acceso al equipo, así como la técnica de depuración JTAG. Descargue el kit de hardware / software obtenido al escribir el artículo aquí .

Source: https://habr.com/ru/post/453682/


All Articles