Modelo de desarrollo utilizando CPU basada en pila como ejemplo

¿Alguna vez ha tenido la pregunta "cómo funciona el procesador?". Sí, sí, exactamente el que está en su PC / computadora portátil / teléfono inteligente. En este artículo quiero dar un ejemplo de un procesador auto-inventado con un diseño en Verilog. Verilog no es exactamente el lenguaje de programación que parece. Este es el lenguaje de descripción de hardware. El código escrito no es ejecutado por nada (a menos que lo ejecute en el simulador, por supuesto), sino que se convierte en el diseño del circuito físico, o en la forma percibida por FPGA (Field Programmable Gate Array).


Descargo de responsabilidad: este artículo es el resultado del trabajo en un proyecto en la universidad, por lo que el tiempo de trabajo fue limitado y muchas partes del proyecto todavía están solo en la etapa inicial de desarrollo.


Tenga en cuenta que el procesador creado en este artículo tiene poco en común con los procesadores modernos y extendidos, pero traté de lograr un objetivo ligeramente diferente con su creación.


Para comprender realmente el proceso de programación, debe imaginar cómo funciona cada una de las herramientas utilizadas: un compilador / intérprete del lenguaje, una máquina virtual, si la hay, código intermedio y, por supuesto, el propio procesador. Muy a menudo, las personas que estudian programación están en la primera etapa durante mucho tiempo; solo piensan en cómo funcionan el lenguaje y su compilador. Esto a menudo conduce a errores cuyas soluciones son desconocidas para el programador novato, porque no tiene idea de dónde provienen las raíces de estos problemas. Yo mismo vi varios ejemplos en vivo donde la situación era algo similar a la descripción anterior, así que decidí tratar de solucionar esta situación y crear un conjunto de cosas que ayudarán a los principiantes a comprender todos los pasos.


Este kit consta de:


  • Lenguaje inventado
  • Plugin de resaltado para código VS
  • Compilador
  • Conjunto de instrucciones
  • Un procesador simple capaz de ejecutar este conjunto de instrucciones (escrito en Verilog)

Le recuerdo una vez más que este artículo NO DESCRIBE NADA SIMILAR A UN PROCESADOR REAL MODERNO, describe un modelo que es fácil de entender sin entrar en detalles.


Cosas que necesitará si quiere hacerlo usted mismo:


Para ejecutar una simulación de CPU, necesita ModelSim, que puede descargar del sitio web de Intel.


Para ejecutar el compilador OurLang, necesita la versión Java> = 8.


Enlaces a proyectos:
https://github.com/IamMaxim/OurCPU
https://github.com/IamMaxim/OurLang


Extensión:
https://github.com/IamMaxim/ourlang-vscode


Para construir la parte Verilog, generalmente uso un script bash:


#/bin/bash vlib work vlog *.v vsim -c testbench_1 -do "run; exit" 

Pero esto se puede repetir a través de la GUI.


Es conveniente utilizar Intellij IDEA para trabajar con el compilador. Lo principal es hacer un seguimiento de qué módulos tiene el módulo que necesita en las dependencias. No publiqué el .jar listo para abrir el acceso, porque espero que el lector lea el código fuente del compilador.


Los módulos lanzados son Compilador e Intérprete. Todo está claro con el compilador, Interpreter es solo un simulador de OurCPU en Java, pero no lo consideraremos en este artículo.


Conjunto de instrucciones


Creo que es mejor comenzar con el conjunto de instrucciones.


Hay varias arquitecturas de conjunto de instrucciones:


  • Basado en pila es lo que se describe en el artículo. Una característica distintiva es que todos los operandos se insertan en la pila y se extraen de la pila, lo que excluye inmediatamente la posibilidad de paralelizar la ejecución, pero es uno de los enfoques más simples para trabajar con datos.
  • Basado en el acumulador: la conclusión es que solo hay un registro que almacena un valor que se modifica mediante instrucciones.
  • El uso de registros es lo que se usa en los procesadores modernos porque le permite lograr el máximo rendimiento mediante el uso de varias optimizaciones, incluida la paralelización de la ejecución, la canalización, etc.

Nuestro conjunto de instrucciones del procesador contiene 30 instrucciones


A continuación, propongo echar un vistazo a la implementación del procesador:


El código consta de varios módulos:


  • CPU
  • RAM
  • Módulos para cada instrucción.

RAM es un módulo que contiene directamente la memoria en sí misma, así como una forma de acceder a los datos que contiene.


CPU: un módulo que controla directamente el progreso del programa: lee instrucciones, transfiere el control a la instrucción deseada, almacena los registros necesarios (puntero a la instrucción actual, etc.).


Casi todas las instrucciones funcionan solo con la pila, así que solo síguelas. Algunos (por ejemplo, putw, putb, jmp y jif) tienen un argumento adicional en la instrucción misma. Necesitan pasar toda la instrucción para poder leer los datos necesarios.


Aquí hay un resumen general de cómo funciona el procesador:



Principios generales del diseño del dispositivo a nivel de instrucción.


Creo que es hora de familiarizarse con el dispositivo directamente de los propios programas. Como puede ver en el diagrama anterior, después de cada instrucción, la dirección se mueve a la siguiente. Esto le da un curso lineal al programa. Cuando se hace necesario romper esta linealidad (condición, bucle, etc.), se utilizan instrucciones de bifurcación (en nuestro conjunto de instrucciones son jmp y jif).


Cuando llamamos a funciones, necesitamos guardar el estado actual de todo, y para esto hay registros de activación, registros que almacenan esta información. No están vinculados al procesador ni a las instrucciones de ninguna manera, es solo un concepto que el compilador utiliza al generar código. El registro de activación en OurLang tiene la siguiente estructura:



Como se puede ver en este diagrama, las variables locales también se almacenan en el registro de activación, lo que le permite calcular la dirección de la variable en la memoria en tiempo de compilación, en lugar de en tiempo de ejecución, y así acelera la ejecución del programa.


Para las llamadas a funciones, nuestro conjunto de instrucciones proporciona métodos para trabajar con dos registros contenidos en el módulo de la CPU (puntero de operación y puntero de dirección de activación): putopa / popopa, putara / popara.


Compilador


Ahora echemos un vistazo a la parte más cercana al programador final: el compilador. En general, el compilador como programa consta de 3 partes:


  • Lexer
  • Analizador
  • Compilador

El lexer es responsable de traducir el código fuente del programa en unidades léxicas que el analizador entiende.


El analizador construye un árbol de sintaxis abstracta a partir de estas unidades léxicas.


El compilador recorre este árbol y genera algún tipo de código que consiste en instrucciones de bajo nivel. Esto puede ser un código de bytes o un código binario listo para ser ejecutado por el procesador.


En el compilador OurLang, estas partes están representadas respectivamente por clases


  • Lexer.java
  • Parser.java
  • Compiler.java

Idioma


OurLang está en su infancia, es decir, funciona, pero hasta ahora no hay muchas cosas en él e incluso la parte central del lenguaje no se ha finalizado. Pero para comprender la esencia del compilador, el estado actual es suficiente.


Como ejemplo de un programa para comprender la sintaxis, se propone este fragmento de código (también se usa para probar la funcionalidad):


 // single-line comments /* * Multi-line comments */ function print(int arg) { instr(putara, 0); instr(putw, 4); instr(add, 0); instr(lw, 0); instr(printword, 0); } function func1(int arg1, int arg2): int { print(arg1); print(arg2); if (arg1 == 0) { return arg2; } else { return func1(arg1 - 1, arg2); }; } function main() { var i: int; i = func1(1, 10); if (i == 0) { i = 1; } else { i = 2; }; print(i); } 

No me enfocaré en el idioma, lo dejaré para su estudio. A través del código del compilador, por supuesto;).


Al escribirlo, traté de hacer un código autoexplicativo que sea claro sin comentarios, por lo que no debería haber ningún problema para comprender el código del compilador.


Bueno, por supuesto, lo más interesante es escribir código y luego ver en qué se convierte. Afortunadamente, el compilador OurLang genera código similar al ensamblado con comentarios,
lo que ayuda a no confundirse con lo que está sucediendo dentro.


También recomiendo instalar la extensión para Visual Studio Code, facilitará el trabajo con el lenguaje.


¡Buena suerte aprendiendo el proyecto!

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


All Articles