Proyecto | Cuarto SoC escrito en VHDL |
---|---|
Autor | Richard James Howe |
Derechos de autor | 2013-2019 Richard Howe |
Licencia | MIT/LGPL |
Correo electrónico | [email protected] |
Este proyecto implementa una pequeña computadora diseñada para ejecutar Forth basada en la CPU J1. El procesador ha sido reescrito en VHDL de Verilog y ligeramente ampliado.
Los objetivos del proyecto son los siguientes:
Los tres han sido completados.
El procesador H2, al igual que el J1, es un procesador basado en pila que ejecuta un conjunto de instrucciones especialmente adecuado para FORTH.
El objetivo actual es la placa Nexys3, con una FPGA Xilinx Spartan-6 XC6LX16-CS324; en el futuro se apuntarán a nuevas placas a medida que esta placa esté llegando al final de su vida útil. El VHDL está escrito de forma genérica, con componentes de hardware inferidos en lugar de instanciados explícitamente, esto debería hacer que el código sea bastante portátil, aunque las interfaces a los componentes de la placa Nexys3 son específicas de los periféricos de esa placa.
Puede ver un vídeo del proyecto en acción, en el hardware, aquí:
El SoC también se puede simular con un simulador escrito en C, como se muestra a continuación:
La arquitectura del sistema es la siguiente:
Las licencias utilizadas por el proyecto son mixtas y se otorgan por archivo. Para mi código utilizo la licencia MIT, así que siéntete libre de utilizarla como desees. Las otras licencias utilizadas son LGPL y Apache 2.0; se limitan a módulos individuales, por lo que podrían eliminarse si tiene cierta aversión al código LGPL.
El único tablero objetivo disponible en este momento es el Nexys3, esto debería cambiar en el futuro ya que el tablero se encuentra actualmente en su fin de vida útil. Las próximas placas que busco admitir son su sucesora, Nexys 4 y myStorm BlackIce (https://mystorm.uk/). El tablero myStorm utiliza una cadena de herramientas de código completamente abierto para síntesis, ubicación y ruta y generación de archivos de bits.
La compilación ha sido probada en Debian Linux, versión 8.
Necesitará:
Hardware:
Xilinx ISE puede (o podría descargarse) de forma gratuita, pero requiere registro. ISE debe estar en tu camino:
PATH=$PATH:/opt/Xilinx/14.7/ISE_DS/ISE/bin/lin64;
PATH=$PATH:/opt/Xilinx/14.7/ISE_DS/ISE/lib/lin64;
Para crear la cadena de herramientas basada en C:
make embed.hex
Para crear un archivo de bits que se pueda enviar al tablero de destino:
make simulation synthesis implementation bitfile
Para cargar el archivo de bits en el tablero de destino:
make upload
Para ver la forma de onda generada por "hacer simulación":
make viewer
El simulador CLI basado en C se puede invocar con:
make run
Lo cual ensamblará el archivo fuente H2 Forth embed.fth y ejecutará el archivo objeto ensamblado en el simulador H2 con el depurador activado. Se puede ejecutar un simulador gráfico con:
make gui-run
Lo que requiere freeglut y un compilador de C.
El proyecto J1 original está disponible en:
Este proyecto apunta al núcleo J1 original y proporciona una implementación eForth (escrita usando Gforth como para metacompilación/compilación cruzada con el núcleo J1). También proporciona un simulador del sistema escrito en C.
El intérprete de eForth en el que se basa el metacompilador se puede encontrar en:
El procesador H2 y los periféricos asociados ahora son bastante estables, sin embargo la fuente es siempre la guía definitiva sobre cómo se comportan las instrucciones y los periféricos, así como el mapa de registros.
Hay algunas modificaciones a la CPU J1 que incluyen:
La CPU H2 se comporta de manera muy similar a la CPU J1 y el PDF J1 se puede leer para comprender mejor este procesador. El procesador es de 16 bits con instrucciones que toman un solo ciclo de reloj. La mayoría de las palabras primitivas Forth también se pueden ejecutar en un solo ciclo, una excepción notable es store ("!"), que se divide en dos instrucciones.
La CPU tiene el siguiente estado dentro de ella:
Carga y almacena en el bloque RAM que contiene el programa H2, descarta el bit más bajo, todas las demás operaciones de memoria utilizan el bit más bajo (como saltos, cargas y almacenamientos en periféricos de entrada/salida). Esto es para que las aplicaciones puedan usar el bit más bajo para operaciones de caracteres al acceder a la RAM del programa.
El conjunto de instrucciones se decodifica de la siguiente manera:
+---------------------------------------------------------------+
| F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---------------------------------------------------------------+
| 1 | LITERAL VALUE |
+---------------------------------------------------------------+
| 0 | 0 | 0 | BRANCH TARGET ADDRESS |
+---------------------------------------------------------------+
| 0 | 0 | 1 | CONDITIONAL BRANCH TARGET ADDRESS |
+---------------------------------------------------------------+
| 0 | 1 | 0 | CALL TARGET ADDRESS |
+---------------------------------------------------------------+
| 0 | 1 | 1 | ALU OPERATION |T2N|T2R|N2A|R2P| RSTACK| DSTACK|
+---------------------------------------------------------------+
| F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---------------------------------------------------------------+
T : Top of data stack
N : Next on data stack
PC : Program Counter
LITERAL VALUES : push a value onto the data stack
CONDITIONAL : BRANCHS pop and test the T
CALLS : PC+1 onto the return stack
T2N : Move T to N
T2R : Move T to top of return stack
N2A : STORE T to memory location addressed by N
R2P : Move top of return stack to PC
RSTACK and DSTACK are signed values (twos compliment) that are
the stack delta (the amount to increment or decrement the stack
by for their respective stacks: return and data)
Todas las operaciones de ALU reemplazan a T:
Valor | Operación | Descripción |
---|---|---|
0 | t | Parte superior de la pila |
1 | norte | Copiar T a N |
2 | T+N | Suma |
3 | T y N | Bit a bit Y |
4 | Rasgado | O bit a bit |
5 | T^N | XOR bit a bit |
6 | ~T | Inversión bit a bit |
7 | T = norte | prueba de igualdad |
8 | norte Comparación firmada | |
9 | norte >> t | Desplazamiento lógico a la derecha |
10 | T-1 | Decremento |
11 | R | Parte superior de la pila de devolución |
12 | [t] | Cargar desde la dirección |
13 | norte << t | Desplazamiento lógico a la izquierda |
14 | profundidad | Profundidad de la pila |
15 | nu < t | Comparación sin firmar |
16 | Establecer el estado de la CPU | Habilitar interrupciones |
17 | Obtener el estado de la CPU | ¿Están activadas las interrupciones? |
18 | profundidad | Profundidad de retorno stk |
19 | 0= | T == 0? |
20 | identificación de la CPU | Identificador de CPU |
21 | LITERAL | Instrucción interna |
Los registros marcados con el prefijo 'o' son registros de salida, aquellos con un prefijo 'i' son registros de entrada. Los registros se dividen en una sección de registros de entrada y salida y las direcciones de los registros de entrada y salida no se corresponden entre sí en todos los casos.
Se han implementado los siguientes periféricos en el SoC VHDL para interactuar con los dispositivos de la placa Nexys3:
El SoC también presenta un conjunto limitado de interrupciones que se pueden habilitar o deshabilitar.
El mapa de registro de salida:
Registro | DIRECCIÓN | Descripción |
---|---|---|
oUarte | 0x4000 | registro UART |
oVT100 | 0x4002 | Escritura del terminal VT100 |
LED | 0x4004 | Salidas LED |
oCtrlTemporizador | 0x4006 | control del temporizador |
oMemDout | 0x4008 | Salida de datos de la memoria |
oMemControl | 0x400A | Control de memoria/alta dirección |
oMemAddrLow | 0x400C | Dirección baja de memoria |
o7SegLED | 0x400E | 4 x pantalla LED de 7 segmentos |
oIrcMáscara | 0x4010 | Máscara de interrupción de CPU |
oUartBaudTx | 0x4012 | Configuración del reloj en baudios UART Tx |
oUartBaudRx | 0x4014 | Configuración del reloj en baudios UART Rx |
Los registros de entrada:
Registro | DIRECCIÓN | Descripción |
---|---|---|
iUarte | 0x4000 | registro UART |
iVT100 | 0x4002 | Estado del terminal y teclado PS/2 |
iInterruptores | 0x4004 | Botones e interruptores |
iTimerDin | 0x4006 | Valor actual del temporizador |
iMemDin | 0x4008 | Entrada de datos de memoria |
La siguiente descripción de los registros debe leerse en orden y describir también cómo funcionan los periféricos.
En el SoC hay un UART con una velocidad de baudios y un formato fijos (115200, 8 bits, 1 bit de parada). El UART tiene un FIFO de profundidad 8 en los canales RX y TX. El control de la UART se divide entre oUart e iUart.
Para escribir un valor en UART, afirme TXWE junto con poner los datos en TXDO. El estado FIFO se puede analizar mirando el registro iUart.
Para leer un valor del UART: se puede verificar iUart para ver si hay datos presentes en el FIFO, si se afirma RXRE en el registro oUart, en el siguiente ciclo de reloj los datos estarán presentes en el registro iUart.
La velocidad en baudios del UART se puede cambiar reconstruyendo el proyecto VHDL, la longitud de bits, los bits de paridad y los bits de parada solo se pueden cambiar con modificaciones en uart.vhd.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X |TXWE| X | X |RXRE| X | X | TXDO |
+-------------------------------------------------------------------------------+
TXWE: UART TX Write Enable
RXRE: UART RX Read Enable
TXDO: UART TX Data Output
El dispositivo VGA Text emula un terminal con el que el usuario puede hablar escribiendo en el registro oVT100. Admite un subconjunto de la funcionalidad del terminal VT100. La interfaz se comporta de manera muy similar a escribir en un UART con las mismas señales de ocupado y control. La entrada se toma desde un teclado PS/2 disponible en la placa, este se comporta como el mecanismo RX del UART.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X |TXWE| X | X |RXRE| X | X | TXDO |
+-------------------------------------------------------------------------------+
TXWE: VT100 TX Write Enable
RXRE: UART RX Read Enable
TXDO: UART TX Data Output
En la placa Nexys3 hay un banco de LED que se encuentran al lado de los interruptores, estos LED se pueden encender (1) o apagar (0) escribiendo en LEDO. Cada LED aquí corresponde al interruptor al que está al lado.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | X | X | X | X | X | LEDO |
+-------------------------------------------------------------------------------+
LEDO: LED Output
El temporizador es controlable mediante el registro oTimerCtrl, es un temporizador de 13 bits que funciona a 100 MHz, opcionalmente puede generar interrupciones y el recuento interno de los temporizadores actuales se puede volver a leer con el registro iTimerDin.
El temporizador cuenta una vez que se afirma el bit TE, una vez que el temporizador alcanza el valor TCMP, se reinicia y, opcionalmente, puede generar una interrupción afirmando INTE. Esto también alterna las líneas Q y NQ que salen del temporizador y se dirigen a los pines del tablero (consulte el archivo de restricciones top.ucf para los pines).
El temporizador se puede restablecer escribiendo en RST.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| TE | RST|INTE| TCMP |
+-------------------------------------------------------------------------------+
TE: Timer Enable
RST: Timer Reset
INTE: Interrupt Enable
TCMP: Timer Compare Value
El núcleo H2 tiene un mecanismo para interrupciones, las interrupciones deben habilitarse o deshabilitarse con una instrucción. Cada interrupción se puede enmascarar con un bit en IMSK para habilitar esa interrupción específica. Un '1' en un bit de IMSK habilita esa interrupción específica, que se entregará a la CPU si se habilitan interrupciones dentro de ella.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | X | X | X | X | X | IMSK |
+-------------------------------------------------------------------------------+
IMSK: Interrupt Mask
Este registro se utiliza para configurar la frecuencia de reloj de muestreo y baudios para transmisión únicamente.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| BTXC |
+-------------------------------------------------------------------------------+
BTXC: Baud Clock Settings
Este registro se utiliza para configurar la frecuencia de reloj de muestreo y baudios para recepción únicamente.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| BRXC |
+-------------------------------------------------------------------------------+
BRXC: Baud Clock Settings
Los datos se enviarán a la dirección seleccionada cuando se emita la habilitación de escritura (WE) en oMemControl.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| Data Ouput |
+-------------------------------------------------------------------------------+
Este registro contiene los registros de control de la memoria integrada en la placa Nexys3. La placa contiene tres dispositivos de memoria, dos dispositivos de memoria no volátil y un dispositivo volátil basado en RAM. Los dos dispositivos accesibles mediante una interfaz SRAM simple (uno volátil M45W8MW16, uno no volátil, un NP8P128A13T1760E) son accesibles, el tercero es un dispositivo de memoria basado en SPI, NP5Q128A13ESFC0E) y actualmente no es accesible.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| OE | WE | RST|WAIT| RCS| FCS| Address Hi |
+-------------------------------------------------------------------------------+
OE: Output Enable - enable reading from current address into iMemDin
WE: Write Enable - enable writing oMemDout into ram at current address
RST: Reset the Flash memory controller
RCS: RAM Chip Select, Enable Volatile Memory
FCS: Flash Chip Select, Enable Non-Volatile Memory
Address Hi: High Bits of RAM address
OE y WE son mutuamente excluyentes; si se configuran ambos, no hay ningún efecto.
El controlador de memoria está en desarrollo activo y su interfaz podría cambiar.
Estos son los bits de dirección inferiores de la RAM.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| Address Lo |
+-------------------------------------------------------------------------------+
En la placa Nexys3 hay un banco de visualizadores de 7 segmentos, con un punto decimal (en realidad, de 8 segmentos), que se pueden utilizar para salida numérica. Los segmentos LED no se pueden direccionar directamente. En cambio, el valor almacenado en L8SD se asigna a un valor de visualización hexadecimal (o un valor BCD, pero esto requiere la regeneración del SoC y la modificación de un genérico en el VHDL).
El valor '0' corresponde a un cero mostrado en el segmento LED, '15' a una 'F', etcétera.
Hay 4 pantallas seguidas.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| L7SD0 | L7SD1 | L7SD2 | L7SD3 |
+-------------------------------------------------------------------------------+
L7SD0: LED 7 Segment Display (leftmost display)
L7SD1: LED 7 Segment Display
L7SD2: LED 7 Segment Display
L7SD3: LED 7 Segment Display (right most display)
El registro iUart funciona junto con el registro oUart. El estado del FIFO que almacena tanto la transmisión como la recepción de bytes está disponible en el registro iUart, así como cualquier byte recibido.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X |TFFL|TFEM| X |RFFL|RFEM| RXDI |
+-------------------------------------------------------------------------------+
TFFL: UART TX FIFO Full
TFEM: UART TX FIFO Empty
RFFL: UART RX FIFO Full
RFEM: UART RX FIFO Empty
RXDI: UART RX Data Input
El registro iVT100 funciona junto con el registro oVT100. El estado del FIFO que almacena tanto la transmisión como la recepción de bytes está disponible en el registro iVT100, así como cualquier byte recibido. Funciona igual que los registros iUart/oUart.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X |TFFL|TFEM| X |RFFL|RFEM| 0 | ACHR |
+-------------------------------------------------------------------------------+
TFFL: VGA VT100 TX FIFO Full
TFEM: VGA VT100 TX FIFO Empty
RFFL: PS2 VT100 RX FIFO Full
RFEM: PS2 VT100 RX FIFO Empty
ACHR: New character available on PS2 Keyboard
Este registro contiene el valor actual del contador de temporizadores.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | TCNT |
+-------------------------------------------------------------------------------+
TCNT: Timer Counter Value
iSwitches contiene líneas de entrada de múltiples fuentes. Los botones (BUP, BDWN, BLFT, BRGH y BCNT) corresponden a un D-Pad en la placa Nexys3. Los interruptores (TSWI) son los mencionados en oLeds, cada uno tiene un LED al lado.
Los interruptores y botones ya cuentan con protección contra rebotes en el hardware, por lo que no es necesario procesarlos una vez leídos de estos registros.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | BUP|BDWN|BLFT|BRGH|BCNT| TSWI |
+-------------------------------------------------------------------------------+
BUP: Button Up
BDWN: Button Down
BLFT: Button Left
BRGH: Button Right
BCNT: Button Center
TSWI: Two Position Switches
Entrada de memoria, ya sea desde la SRAM o Flash, indexada por oMemControl y oMemAddrLow. Al leer desde flash, esto podría ser información de estado o información de la tabla de consulta.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| Data Input |
+-------------------------------------------------------------------------------+
Se definen las siguientes rutinas de servicio de interrupción:
Nombre | Número | Descripción |
---|---|---|
isrNinguno | 0 | No usado |
isrRxFifoNotEmpty | 1 | UART RX FIFO no está vacío |
isrRxFifoFull | 2 | UART RX FIFI está lleno |
isrTxFifoNotEmpty | 3 | UART TX FIFO no está vacío |
isrTxFifoFull | 4 | UART TX FIFO está lleno |
isrKbdNuevo | 5 | Nuevo personaje del teclado PS/2 |
isrTimer | 6 | Contador del temporizador |
botón isrDPad | 7 | Cualquier cambio de estado del botón D-Pad |
Cuando ocurre una interrupción y las interrupciones están habilitadas dentro del procesador, se realiza una llamada a la ubicación en la memoria; la ubicación es la misma que el número ISR. Un ISR con un número '4' realizará una llamada (no un salto) a la ubicación '4' dentro de la memoria, por ejemplo.
Las interrupciones tienen una latencia de al menos 4-5 ciclos antes de que se actúe sobre ellas, hay un retraso de dos a tres ciclos en el controlador de solicitudes de interrupción, luego se debe realizar la llamada a la ubicación ISR en la memoria, luego la llamada al palabra que implementa el propio ISR.
Si ocurren dos interrupciones al mismo tiempo, se procesan desde el número de interrupción más bajo hasta el más alto.
Las interrupciones se pierden cuando ocurre una interrupción con el mismo número que no ha sido procesada.
El simulador basado en C y Disassembler para el H2 está en un solo programa (ver h2.c). Este simulador complementa el banco de pruebas VHDL tb.vhd y no lo reemplaza. El metacompilador se ejecuta sobre un intérprete eForth y está contenido en los archivos embed.c y embed.blk. El metacompilador (en el lenguaje de Forth para un compilador cruzado) es un programa de Forth que se utiliza para crear la imagen de eForth que se ejecuta en el destino.
La cadena de herramientas está actualmente en proceso de cambio, en el futuro es probable que haya una mayor integración entre h2.c y embed.c, además de cambiar la máquina virtual integrada por una que se parezca más a la CPU H2 con el objetivo a largo plazo de crear un alojamiento propio. sistema.
Para compilar ambos, se necesita un compilador de C, el objetivo de compilación "h2" generará el ejecutable, h2 y "embed" generará el metacompilador:
make h2 embed
Y se puede ejecutar en el archivo fuente embed.fth con el destino make:
make run
El archivo make no es necesario:
Linux:
cc -std=c99 h2.c -o h2 # To build the h2 executable
cc -std=c99 embed.c -o embed # To build the embed VM executable
./embed embed.blk embed.hex embed.fth # Create the target eForth image
./h2 -h # For a list of options
./h2 -r embed.hex # Run the assembled file
Windows:
gcc -std=c99 h2.c -o h2.exe # Builds the h2.exe executable
gcc -std=c99 embed.c -o embed.exe # Builds the embed.exe executable
embed.exe embed.blk embed.hex embed.fth # Create the target eForth iamge
h2.exe -h # For a list of options
h2.exe -r embed.hex # Run the assembled file
Una lista de opciones de línea de comando disponibles:
- stop processing options, following arguments are files
-h print a help message and exit
-v increase logging level
-d disassemble input files (default)
-D full disassembly of input files
-T Enter debug mode when running simulation
-r run hex file
-L # load symbol file
-s # number of steps to run simulation (0 = forever)
-n # specify NVRAM block file (default is nvram.blk)
file* file to process
Este programa se publica bajo la licencia MIT, siéntete libre de usarlo y modificarlo como quieras. Con una mínima modificación debería poder ensamblar programas para el núcleo J1 original.
El metacompilador se ejecuta sobre la máquina virtual integrada, es una máquina virtual de 16 bits que originalmente descendió de la CPU H2. El proyecto incluye un esquema de metacompilación que permite que una imagen de eForth genere una nueva imagen de eForth con modificaciones. Ese sistema se ha adaptado para su uso con el H2, que reemplazó al compilador cruzado escrito en C, que permitió crear la primera imagen para el H2.
El metacompilador es un programa Forth normal y está contenido en embed.fth. Luego, el programa metacompilador Forth se utiliza para crear una imagen eForth capaz de ejecutarse en el objetivo H2.
Para obtener más información sobre la metacompilación en Forth, consulte:
El desensamblador toma un archivo de texto que contiene el programa ensamblado, que consta de números hexadecimales de 16 bits. Luego intenta desmontar las instrucciones. También se le puede alimentar un archivo de símbolos que puede ser generado por el ensamblador e intentar encontrar las ubicaciones a las que apuntan los saltos y las llamadas.
El desensamblador es utilizado por un script tcl llamado por GTKwave, convierte el rastro de instrucciones del H2 de una serie de números en las instrucciones y destinos de rama que representan. Esto facilita mucho la depuración del VHDL.
El trazo morado muestra las instrucciones desmontadas.
El simulador en C implementa el núcleo H2 y la mayor parte del SoC. El IO para el simulador no tiene ciclos precisos, pero se puede usar para ejecutar y depurar programas con resultados muy similares a cómo se comporta el hardware. Esto es mucho más rápido que reconstruir el archivo de bits utilizado para actualizar la FPGA.
El simulador también incluye un depurador, que está diseñado para ser similar al programa DEBUG.COM disponible en DOS. El depurador se puede utilizar para desmontar secciones de memoria, inspeccionar el estado de los periféricos y volcar secciones de memoria en la pantalla. También se puede utilizar para establecer puntos de interrupción, realizar un solo paso y ejecutar el código hasta alcanzar un punto de interrupción.
Para ejecutar el depurador se debe proporcionar un archivo hexadecimal o un archivo fuente:
# -T turns debugging mode on
./h2 -T -r file.hex # Run simulator
Ambos modos de operación se pueden ampliar con un archivo de símbolos, que enumera dónde se ubican las variables, etiquetas y funciones con el núcleo ensamblado.
Cuando se proporciona la opción "-T", se ingresará al modo de depuración antes de ejecutar la simulación. Debería aparecer un mensaje y la línea de comando debería verse así:
$ ./h2 -T -R h2.fth
Debugger running, type 'h' for a list of command
debug>
Los puntos de interrupción se pueden establecer simbólicamente o mediante la ubicación del programa; el comando 'b' se usa para establecer puntos de interrupción:
Los números se pueden ingresar en octal (prefijo '0' al número), hexadecimal (prefijo '0x') o en decimal. Como ejemplo, los siguientes tres comandos de depuración establecen un punto de interrupción en la misma ubicación:
debug> b 16
debug> b 0x10
debug> b 020
'k' se puede utilizar para enumerar los puntos de interrupción actuales que están establecidos:
debug> k
0x0010
Esto establece un punto de interrupción cuando la función "key?" se llama:
debug> b key?
Tanto las funciones como las etiquetas se pueden detener, esto requiere que se especifique un archivo de símbolos en la línea de comando o que se ensamble y ejecute para usarse en un archivo fuente, no en un archivo hexadecimal. Los archivos de símbolos se pueden utilizar en archivos fuente o hexadecimales.
Para un solo paso se puede dar el comando 's', aunque no sucederá mucho si el seguimiento está desactivado (el seguimiento está desactivado de forma predeterminada). El seguimiento se puede activar o desactivar con el comando 't':
debug> s
debug> s
debug> t
trace on
debug> s
0001: pc(089a) inst(4889) sp(0) rp(0) tos(0000) r(0000) call 889 init
debug> s
0002: pc(0889) inst(807a) sp(0) rp(1) tos(0000) r(089b) 7a
debug> s
0003: pc(088a) inst(e004) sp(1) rp(1) tos(007a) r(089b) 6004
Es recomendable desactivar el rastreo cuando se ejecuta emitiendo el comando 'c' o continuar.
El '.' El comando se puede utilizar para mostrar el estado interno de los núcleos H2:
debug> .
Return Stack:
0000: 0000 08aa 0883 017b 0000 031b 0000 ffb0 0000 02eb ffb5 0210 0167 0167
0167 0167
0010: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000
Variable Stack:
tos: 0000
0001: 0000 0000 0000 0001 0004 0005 0000 ffb0 0000 0000 0000 0000 0000 0000
0000 0000
0011: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000
pc: 0538
rp: 0001
dp: 0000
ie: false
Y el comando 'p' se puede utilizar para mostrar el estado de los periféricos simulados:
debug> p
LEDS: 00
VGA Cursor: 0005
VGA Control: 007a
Timer Control: 8032
Timer: 001b
IRC Mask: 0000
UART Input: 6c
LED 7seg: 0005
Switches: 00
LFSR: 40ba
Waiting: false
Para obtener una lista completa de comandos, utilice el comando 'h'.
Otras formas de ingresar al modo de depuración incluyen colocar la directiva de ensamblador ".break" en el código fuente (esto solo funciona si el comando ensamblar y ejecutar se usa en archivos fuente, no en archivos hexadecimales) y presionar el carácter de escape cuando el simulador está en funcionamiento. intentando leer datos a través del teclado UART o PS/2 simulado (el escape aún se pasará al simulador, pero también activa el modo de depuración).
Se puede compilar un programa separado y probarlo en Linux y Windows. Esto simula los periféricos de la placa Nexys3 con los que interactúa el SoC, pero proporciona un entorno gráfico, a diferencia de la utilidad de línea de comandos. Es más fácil interactuar con el dispositivo y ver qué está haciendo, pero las sesiones de depuración están menos controladas. Requiere exceso libre.
A continuación se muestra una imagen de una sesión en ejecución en el simulador de GUI:
La construcción se puede hacer con
make gui
Y corriendo:
make gui-run
O:
./gui h2.hex (on Linux)
gui.exe h2.hex (on Windows)
La compilación de Linux debería funcionar cuando el paquete de desarrollo de free glut está instalado en su sistema, la compilación de Windows puede requerir cambios en el sistema de compilación y/o la instalación manual del compilador, las bibliotecas y los encabezados.
El mapa clave actual es:
Up Activate Up D-Pad Button, Release turns off
Down Activate Down D-Pad Button, Release turns off
Left Activate Left D-Pad Button, Release turns off
Right Activate Right D-Pad Button, Release turns off
F1 - F8 Toggle Switch On/Off, F1 is left most, F8 Right Most
F11 Toggle UART/PS2 Keyboard Input
F12 Toggle Debugging Information
Escape Quit simulator
Todas las demás teclas del teclado se redirigen a la entrada del teclado UART o PS/2.
Se puede hacer clic en los botones Switches y D-Pad para encenderlos, los interruptores se encienden con clics izquierdos y se apagan con clics derechos. Los botones D-Pads se encienden con un clic encima de ellos y se apagan soltando una tecla en cualquier parte de la pantalla.
Los componentes VHDL utilizados en este sistema están diseñados para ser reutilizables y portátiles entre diferentes cadenas de herramientas y proveedores. Los componentes de hardware, como el bloque de RAM, se infieren y no se crean instancias explícitas. Los componentes también están diseñados para ser lo más genéricos posible y la mayoría tiene anchos seleccionables. Esto se llevaría al extremo, pero lamentablemente muchos proveedores todavía no soportan el estándar VHDL-2008.
Archivo | Licencia | Autor | Descripción |
---|---|---|---|
util.vhd | MIT | Richard Howe | Una colección de componentes genéricos. |
h2.vhd | MIT | Richard Howe | H2 Cuarto núcleo de CPU |
uart.vhd | MIT | Richard Howe | UART TX/RX (tiempo de ejecución personalizable) |
vga.vhd | LGPL 3.0 | Javier V García | Modo texto Pantalla VGA 80x40 |
Richard Howe | (y emulador de terminal VT100) | ||
kbd.vhd | ??? | Scott Larson | Teclado PS/2 |
El lenguaje pseudo-similar a Forth utilizado como ensamblador se describe anteriormente; la aplicación que realmente se ejecuta en el núcleo de Forth es en sí misma un intérprete de Forth. Esta sección describe el intérprete de Forth que se ejecuta en H2 Core y está contenido en embed.fth.
HACER:
Se utilizan varios lenguajes a lo largo de este proyecto, todos los cuales son radicalmente diferentes entre sí y requieren su propio conjunto de estándares de codificación y guías de estilo.
Nombres de señales comunes:
clk - The system clock
rst - A reset signal for the module
we - Write Enable
re - Read Enable
di - Data In
din - Data In
do - Data Out
dout - Data Out
control - Generally an input to a register, the documentation
for the module will need to be consulted to find out
what each bit means
signal_we - The write enable for 'signal'
signal_i - This is an input signal
signal_o - This is an output signal
Generalmente no se utilizan los sufijos "_i" y "_o", los módulos se mantienen breves y los nombres se eligen de manera que su significado sea obvio. Esta regla podría revisarse una vez que el proyecto crezca.
Los componentes deben:
constant N: positive := 4;
signal a: std_logic_vector(N - 1 downto 0) := (others => '1');
En lugar de:
signal a: std_logic_vector(3 downto 0) := x"F";
Las reglas de estilo son las siguientes:
Un ejemplo de las pautas de formato, describe un registro de ancho arbitrario simple:
-- Lots of comments about what the unit does should go
-- here. Describe the waveforms, states and use ASCII
-- art where possible.
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all; -- numeric_std not std_logic_arith
entity reg is -- generic and port indented one tab, their parameters two
generic (
N: positive); -- Generic parameters make for a generic component
port (
clk: in std_logic; -- standard signal names
rst: in std_logic; --
we: in std_logic;
di: in std_logic_vector(N - 1 downto 0);
do: out std_logic_vector(N - 1 downto 0)); -- note the position of ");
end entity; -- "end entity", not "end reg"
architecture rtl of reg is
signal r_c, r_n: std_logic_vector(N - 1 downto 0) := (others => '0');
begin
do <= r_c;
process(rst, clk)
begin
if rst = '1' then -- asynchronous reset
r_c <= (others => '0');
elsif rising_edge(clk) then -- rising edge, not "clk'event and clk = '1'"
r_c <= r_n;
end if;
end process;
process(r_c, di, we)
begin
r_n <= r_c;
if we = '1' then
r_n <= di;
end if;
end process;
end; -- "end" or "end architecture"
Se utiliza bastante código C en este proyecto, que se utiliza para crear una cadena de herramientas para el núcleo H2 y para simular el sistema.
No hay nada sorprendente en el código C aquí incluido, por lo que algunas de las excepciones deben abordarse.
static const char *alu_op_to_string(uint16_t instruction) {
/* notice also that the 'case' clauses are inline with the
* switch selector */
switch (ALU_OP(instruction)) {
case ALU_OP_T: return "T";
case ALU_OP_N: return "N";
case ALU_OP_T_PLUS_N: return "T+N";
case ALU_OP_T_AND_N: return "T&N";
case ALU_OP_T_OR_N: return "T|N";
case ALU_OP_T_XOR_N: return "T^N";
case ALU_OP_T_INVERT: return "~T";
case ALU_OP_T_EQUAL_N: return "N=T";
case ALU_OP_N_LESS_T: return "T>N";
case ALU_OP_N_RSHIFT_T: return "N>>T";
case ALU_OP_T_DECREMENT: return "T-1";
case ALU_OP_R: return "R";
case ALU_OP_T_LOAD: return "[T]";
case ALU_OP_N_LSHIFT_T: return "N<N";
case ALU_OP_ENABLE_INTERRUPTS: return "seti";
case ALU_OP_INTERRUPTS_ENABLED: return "iset?";
case ALU_OP_RDEPTH: return "rdepth";
case ALU_OP_T_EQUAL_0: return "0=";
case ALU_OP_CPU_ID: return "cpu-id";
default: return "unknown";
}
}
if (foo)
bar();
else
baz();
picocom --omap delbs -b 115200 -e b /dev/ttyUSB1