Escribir un procesador simple y un entorno para ello

Hola En este artículo, te diré qué pasos debes seguir para crear un procesador y un entorno simples para él.


Arquitectura de conjunto de comandos (ISA)


Primero debes decidir cuál será el procesador. Los siguientes parámetros son importantes:


  • El tamaño de la palabra máquina y los registros (bit / "bit" del procesador)
  • Instrucciones de la máquina (instrucciones) y su tamaño.

Las arquitecturas de procesador se pueden dividir en 2 tipos según el tamaño de las instrucciones (de hecho, hay más de ellas, pero otras opciones son menos populares):



Su principal diferencia es que los procesadores RISC tienen el mismo tamaño de instrucción. Sus instrucciones son simples y se ejecutan relativamente rápido, mientras que los procesadores CISC pueden tener diferentes tamaños de instrucciones, algunas de las cuales pueden tomar bastante tiempo.


Decidí hacer un procesador RISC muy parecido a MIPS .


Hice esto por varias razones:


  • Es bastante simple crear un prototipo de tal procesador.
  • Toda la complejidad de este tipo de procesador se transfiere a programas tales como ensamblador y / o compilador.

Estas son las características principales de mi procesador:


  • Palabra de máquina y tamaño de registro: 32 bits
  • 64 registros (incluido el contador de comandos )
  • 2 tipos de instrucciones

El tipo de registro (en adelante tipo de registro) se ve así:


rtype


La peculiaridad de tales instrucciones es que operan con tres registros.


Tipo inmediato :


itype


Las instrucciones de este tipo funcionan con dos registros y un número.


OP es el número de la instrucción que se ejecutará (o para indicar que esta instrucción de tipo Registro ).


R0 , R1 , R2 son números de registro que sirven como operandos para la instrucción.


Func es un campo adicional utilizado para indicar el tipo de instrucciones de tipo de registro .


Imm es el campo donde se escribe el valor, que queremos proporcionar explícitamente instrucciones como un operando.


  • Solo 28 instrucciones

Se puede encontrar una lista completa de instrucciones en el repositorio de github .


Aquí hay solo un par de ellos:


nor r0, r1, r2 

NOR es una instrucción de tipo Registro que realiza un OR lógico O NO en los registros r1 y r2, después de lo cual escribe el resultado en el registro r0.


Para utilizar esta instrucción, debe cambiar el campo OP a 0000 y el campo Func a 0000000111 en el sistema de números binarios.


 lw r0, n(r1) 

LW es una instrucción de tipo Inmediato que carga un valor de memoria en la dirección r1 + n en el registro r0.


Para utilizar esta instrucción, a su vez, debe cambiar el campo OP a 0111 y escribir el número n en el campo IMM .


Escribir código de procesador


Después de crear el ISA, puede comenzar a escribir el procesador.


Para esto, necesitamos conocimiento de algún tipo de lenguaje de descripción de equipos. Aquí hay algunos de ellos:


  • Verilog
  • VHDL (¡no debe confundirse con el anterior!)

Elegí Verilog, porque programarlo fue parte de mi curso universitario.


Para escribir un procesador, debe comprender la lógica de su funcionamiento:


  1. Obteniendo instrucciones en Command Counter (PC)
  2. Instrucciones de decodificación
  3. Ejecución de la instrucción
  4. Agregar al contador el tamaño del comando de la instrucción ejecutada

Y así sucesivamente hasta el infinito.


Resulta que necesitas crear varios módulos:



Analizaremos cada módulo individualmente.


Archivo de registro


Un archivo de registro proporciona acceso a los registros. Con él, debe obtener los valores de algunos registros o cambiarlos.


En mi caso, tengo 64 registros. En uno de los registros se escribe el resultado de la operación en los otros dos, por lo que necesito brindar la oportunidad de cambiar solo uno y obtener los valores de los otros dos.


Decodificador


Un decodificador es esa unidad que se encarga de decodificar las instrucciones. Indica qué operaciones deben ser realizadas por ALU y otras unidades.


Por ejemplo, la instrucción addi debería agregar el valor del registro $ cero (siempre almacena 0 ) y 20 y colocar el resultado en el registro $ t0.


 addi $t0, $zero, 20 

En este punto, el decodificador determina que esta instrucción:


  • Tipo inmediato
  • Debe escribir el resultado en el registro

Y transfiere esta información a los siguientes bloques.


ALU


Después de que el control pasa a ALU. Por lo general, realiza todas las operaciones matemáticas, lógicas, así como operaciones de comparación de números.


Es decir, si consideramos la misma instrucción adicional, en esta etapa se produce la adición de 0 y 20 .


Otros


Además de los bloques anteriores, el procesador debería poder:


  • Obtener y cambiar valores en la memoria
  • Realizar saltos condicionales

Aquí y allá puedes ver cómo se ve en el código.


Ensamblador


Después de escribir el procesador, necesitamos un programa que convierta los comandos de texto en código de máquina para no hacerlo manualmente. Por lo tanto, necesita escribir ensamblador.


Decidí implementarlo en el lenguaje de programación C.


Como mi procesador tiene una arquitectura RISC , para simplificar mi vida, decidí diseñar el ensamblador para poder agregar fácilmente mis propias pseudoinstrucciones (combinaciones de varias instrucciones elementales u otras pseudoinstrucciones).


Puede implementar esto utilizando una estructura de datos que almacena el tipo de instrucción, su formato, un puntero a una función que devuelve códigos de instrucción de máquina y su nombre.


Un programa regular comienza con una declaración de segmento.


Dos segmentos .text son suficientes para nosotros, en los que se almacenará el código fuente de nuestros programas, y .data , en el que se almacenarán nuestros datos y constantes.


Una instrucción podría verse así:


 .text jie $zero, $zero, $zero #  addi $t1, $zero, 2 # $t1 = $zero + 2 lw $t1, 5($t2) # $t1 = *($t2 + 5) syscall 0, $zero, $zero # syscall(0, 0, 0) la $t1, label# $t1 = label 

Primero, se indica el nombre de la instrucción, luego los operandos.


En .data , se indican las declaraciones de datos.


 .data .byte 23 #   1  .half 1337 #   2  .word 69000, 25000 #   4  .asciiz "Hello World!" #     ( ) .ascii "12312009" #   ( ) .space 45 #  45  

Un anuncio debe comenzar con un punto y un nombre de tipo de datos, seguido de constantes o argumentos.


Es conveniente analizar (escanear) el archivo ensamblador de la siguiente manera:


  1. Primero, escanea el segmento
  2. Si es un segmento .data , entonces analizamos diferentes tipos de datos o un segmento .text
  3. Si es un segmento .text , entonces analizamos comandos o un segmento .data

Para trabajar, el ensamblador debe pasar por el archivo fuente 2 veces. La primera vez que considera en qué compensaciones están los enlaces (sirven), generalmente se ven así:


  la $s4, loop #   loop  s4 loop: # ! mul $s2, $s2, $s1 # s2 = s2 * s1 addi $s1, $s1, -1 # s1 = s1 - 1 jil $s3, $s1, $s4 #  s3 < s1     

Y en el segundo paso, ya puede generar un archivo.


Resumen


En el futuro, puede ejecutar el archivo de salida desde el ensamblador en nuestro procesador y evaluar el resultado.


Además, el ensamblador listo para usar se puede usar en el compilador de C. Pero esto es más tarde.


Referencias


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


All Articles