Skip to content

JooFeBF/assembler-js

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Review Assignment Due Date

Ensamblador RISC-V

Introducción

Este trabajo está diseñado para ser realizado en parejas. Es decir, grupos de máximo dos estudiantes. Al respecto es necesario aclarar que en cualquier caso se puede requerir una sustentación. En esas circunstancias la nota del trabajo será la nota de la sustentación. Por tratarse de un trabajo en grupos ambos integrantes deben estar en total capacidad de llevar a cabo la sustentación e igual la nota será grupal.

La idea de este trabajo es poner en práctica los conceptos sobre expresiones regulares adquiridos en el curso. Para dar un objetivo extra y adquirir conocimiento en otras áreas de la carrera implementará un ensamblador para el conjunto de instrucciones de RISC-V, en específico parar RISCVI32.

Un ensamblador es un traductor muy simple que toma un archivo escrito en lenguaje de bajo nivel y produce un archivo binario que el procesador puede ejecutar. Así pues, su programa escrito en Javascript será invocado de la siguiente manera:

node index.js -i input.asm -o output.hex

donde assembler.py es su programa (o por lo menos el módulo principal), input.asm es el archivo en lenguaje ensamblador y output.hex es un archivo de texto que contiene la codificación de las instrucciones en el archivo de entrada.

Expresiones regulares en Python

Las expresiones regulares pueden ser utilizadas para extraer información de un texto. El texto puede estar almacenado, por ejemplo, en una cadena de caracteres. La expresión regular es también un texto escrito en un lenguaje particular. En el caso de Python, este es un muy buen punto de partida para iniciar.

A continuación se darán unos ejemplos que tienen como propósito familiarizarlo con el uso de este módulo pero, en ningún momento deben ser entendidos como suficientes para realizar el trabajo.

En el siguiente programa, pattern contiene la expresión regular a buscar en la cadena str. En este caso el carácter '^' indica que el texto debe comenzar en el inicio de una línea. '.' indica que cualquier símbolo satisface esta parte de la expresión. '*' es la cerradura de Kleene, lo que hace que se aplique al símbolo que la precede. Finalmente '$' indica el final de la palabra. En conclusión, pattern acepta todas las palabras que contengan cero o más repeticiones de cualquier símbolo. Pruebe el siguiente programa y examine su salida.

# %%
import re

str = '''5.1,3.5,1.4,.2,"Setosa"'''

pattern = '^.*$'
result = re.findall(pattern, str)
print(result)

Es posible también utilizar expresiones regulares para partir cadenas:

# %%
import re

str = '''5.1,3.5,1.4,.2,"Setosa"'''

pattern = ','
re.split(pattern, str)

Estos son ejemplos muy básicos del uso de expresiones regulares. Sin embargo se espera de que usted utilice su potencial completo en este trabajo.

Ensamblador para RISCV

El trabajo consiste en la implementación de un ensamblador para el conjunto de instrucciones de RISCV32IM. Por ejemplo, el siguiente es un programa de entrada:

main:
    addi x4, zero, 50
    addi x5, zero, 50
    beq x4, x5, label1
    addi x6, zero, 80
    beq zero, zero, label2
    
label1:
    addi x6, zero, 100

label2:
    add zero, zero, zero

y su correspondiente salida después de ser procesado:

03 20 02 13 03 20 02 93 00 52 06 63 05 00 03 13 
00 00 04 63 06 40 03 13 00 00 00 33 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

Es de anotar que la salida no es exactamente esa, fue recortada para que no ocupara tanto espacio en el documento.

Al programa de arriba lo llamaremos el programa de entrada y al de abajo lo llamaremos el programa de salida para los efectos de éste trabajo.

Lo único que queda entonces es establecer es cómo cada línea (instrucción) del programa de arriba debe quedar representada en el archivo de salida. Para que el desarrollo de este ensamblador no luzca en exceso complicado, será descrito por medio de ejercicios. Estos irán incorporando cambios sobre su programa de manera incremental hasta terminar en un ensamblador completo.

Ejercicio 1

En esta primera parte usted va a realizar un programa en C/C++ que lea un archivo de entrada y escriba en un archivo de salida de la siguiente forma:

Cada línea no vacía de la entrada puede ser de una de las siguientes entidades:

  • Etiquetas: marcan el inicio de una sección dentro del programa. label1 y label2 son ejemplos de etiquetas.
  • Instrucciones: son las que finalmente serán traducidas.
  • Directivas: son opciones que no pertenecen al lenguaje de programación pero que afectan la forma como quedará el archivo de salida. Las directivas comienzan con el símbolo de '.' y utilizan una línea completa.
  • Comentarios: son anotaciones de código que no tienen relevancia durante la ejecución. Comienzan con cualquiera de los símbolos '#' o ';'.

Como la finalidad es hacer uso de las expresiones regulares, usted va a procesar el archivo utilizando el módulo re de Python. Para esto debe definir expresiones regulares para: comentarios, directivas e instrucciones. Con estas usted debe:

  1. Ignorar todos los comentarios ya que no aportan información.
  2. Ignorar las directivas por el momento.
  3. Por cada instrucción debe aparecer en el archivo de salida una línea de 32 bits (binario) con solamente ceros. Exceptuando la parte del código de operación, el cual si debe coincidir con los códigos presentados en esta sección

Ejercicio 2

Para este momento usted ya debe contar con una implementación que, a partir de un archivo de entrada como el siguiente:

main:
    addi x4, zero, 520
    addi x5, zero, 1550
    beq x4, x5, label1
    addi x6, zero, 80
    beq zero, zero, label2
    

label1:
    addi x6, zero, 100

label2:
    add zero, zero, zero

produce lo siguiente en el archivo de salida.

00 00 00 00 00 00 00 00 00 00 00 00 0  0010011    
00 00 00 00 00 00 00 00 00 00 00 00 0  0010011    
00 00 00 00 00 00 00 00 00 00 00 00 0  1100011    
00 00 00 00 00 00 00 00 00 00 00 00 0  0010011    
00 00 00 00 00 00 00 00 00 00 00 00 0  1100011    
00 00 00 00 00 00 00 00 00 00 00 00 0  0010011    
00 00 00 00 00 00 00 00 00 00 00 00 0  0110011    

Note que hay una separación con los últimos 7 valores en cada instrucción. Esto es simplemente para facilitar la lectura pero tal separación no va en el archivo de salida. Los datos de la última "columna" (por llamarla de alguna forma) corresponden al código de operación de cada instrucción del archivo de entrada. Note también que las etiquetas no están presentes. De esas se encargará más adelante.

Ahora lo que debe hacer es modificar su programa para adicionar la siguiente funcionalidad. Casi todas las instrucciones tienen un registro destino, esto se puede apreciar en la siguiente figura.

Como se puede apreciar en la figura anterior, a excepción de las instrucciones del tipo S y B, las demás tienen un campo rd. En esta parte usted adicionará la codificación de este campo en la salida. Es importante ir realizando algunas validaciones usando las expresiones regulares. Por ejemplo, el campo rd tiene 5 bits y esos deben ser producidos solamente por nombres válidos de registros estos se encuentran en esta sección y corresponden a las columnas Register y ABI Name.

El resultado de este ejercicio es entonces la codificación del registro rd de cada una de las instrucciones en el archivo de entrada. También puede validar y codificar los demás componentes de las instrucciones que hacen referencia a nombres de registros. Es decir, cualquier campo en la figura anterior que tenga rd, rs1 o rs2.

Ejercicio 3

En este ejercicio va a terminar la codificación completa de las instrucciones tipo R. Tenga en cuenta que estas instrucciones tienen nombres específicos, y usted debe utilizar expresiones regulares para validarlos.

Hay instrucciones que tienen constantes o, como también las llamaremos, valores inmediatos. Por ejemplo las instrucciones tipo I son de ese grupo. En el caso de estas instrucciones es necesario codificar las constantes. Este proceso es simple pero requiere de algunas consideraciones. Las constantes de este grupo deben ser representables en 12 bits y pueden estar signadas.

Note que la única diferencia entre los dos tipos de instrucciones es la constante o valor inmediato. Para codificar esta parte debe tener en cuenta dos aspectos:

  1. La constante ser representable en 12 bits y de no ser el caso su programa debe fallar reportando el error. Use expresiones regulares para esto.

  2. La constante puede ser negativa. En este caso la forma de codificarla es utilizando el complemento a 2 en su representación binaria.

Con la culminación de este ejercicio ya lleva un 40 porciento completado. Sin embargo, lo más importante es que ya tiene un gran conocimiento sobre su programa.

Ejercicio 4

Hasta este momento falta poco para terminar con la codificación completa de las instrucciones de tipo R y tipo I. Para completar las tipo R es cuestión de codificar los campos funct3 y funct7 tal y como lo estipula la tabla para cada operación. Es decir, para cada instrucción tipo R.

Si tenemos en cuenta la siguiente figura y la instrucción add x2, x5, x7:

usted ya debe tener la siguiente codificación:

Ahora queda la codificación del campo funct3 y funct7 que dependen de la operación específica que debe terminar en este momento.

Para completar la codificación de las instrucciones tipo I (aritméticas y de corrimientos) no es mucho lo que falta.

Los campos opcode, rd, rs1 y rs2, ya deben estar codificados. El funct3 debe codificarlo de la misma forma que codificó el de las tipo R (tenga en cuenta que es diferente por cada operación). La constante de estas operaciones está en la parte donde estaban el rs2 y el funct7 para las tipo R:

todas las operaciones tipo I deben llevar la constante codificada en esta parte.

Para completar este ejercicio, lo único que falta es verificar que para las instrucciones: slli, srli, srai, la constante pueda ser codificada en 5 bits únicamente como lo muestra la siguiente figura. Los demás valores deben ser 0 excepto para la operación srai que por tener el mismo funct3 de srli se diferencia de ella en el bit 30 que para srai debe ser 1. En caso de no ser así su ensamblador emitirá un error e indicará la línea en el archivo fuente donde este ocurre.

Ejercicio 5

Este ejercicio va dirigido a la codificación de dos grupos nuevos de instrucciones las tipo I para carga de información desde la memoria y las tipo S para almacenamiento de información.

A continuación se muestra un ejemplo de la forma en que estas instrucciones se presentan en los programas:

lw x10, -12(x13)
add x10, x12, x10
sw x10, 40(x13)

Es claro que aquí las únicas instrucciones del grupo que nos concierne son lw y sw. Solo se muestran estas como representativas de los dos grupos que nos interesan para esta parte.

Note que el tipo de instrucciones de estos dos grupos son escritas de manera diferente. La instrucción tiene tres partes: el nombre, un registro y luego una expresión compuesta por una constante y el nombre de un registro entre paréntesis. Usted debe tener esta estructura en cuenta a la hora de procesarla con la expresión regular. De todos modos la traducción funciona de manera muy similar a la de las instrucciones anteriores. En particular, las del primer grupo (lw por ejemplo) son del tipo I. Esto es importante porque ya se definió como la constante debe ser codificada: 12 bits y en complemento a dos.

La parte de la constante en las instrucciones del segundo grupo deben ser codificadas así:

Note que en este caso la constante debe quedar codificada en dos partes separadas de la instrucción.

En la parte menos significativa está el opcode. Luego van los 5 bits menos significativos de la constante (cuya representación es en 12 bits), al final, en la parte más significativa van los restantes 7 bits de la constante.

Su meta con este ejercicio es completar la implementación de este grupo de instrucciones. Ya con esto debe estar completo alrededor del 65% de su trabajo.

Ejercicio 6

Este es el siguiente grupo de instrucciones que se deberá codificar las instrucciones tipo B.

Lo único relevante aquí ( lo nuevo a lo que se enfrenta ) es el cálculo de la constante y su almacenamiento. Para calcular la constante es necesario tener en cuenta la posición en la memoria de la instrucción que se está codificando y la posición en la memoria señalada por la etiqueta referenciada. Por ejemplo, considere el siguiente programa (prestando atención a los comentarios!):

main:                       ; primera etiqueta
    addi x4, zero, 520      ; primera instrucción
    addi x5, zero, 1550     ; segunda instrucción
    beq x4, x5, label1      ; tercera instrucción
    addi x6, zero, 80       ; cuarta instrucción
    beq zero, zero, label2  ; quinta instrucción
    

label1:                     ; segunda etiqueta
    addi x6, zero, 100      ; sexta instrucción

label2:                     ; tercera etiqueta
    add zero, zero, zero    ; séptima instrucción

Ese programa quedará en la memoria de la siguiente forma:

Para calcular como se debe codificar una etiqueta hay que tener en cuenta dos partes: la parte en la que la etiqueta es definida y la parte en la que la etiqueta es utilizada. En nuestro caso, la etiqueta label1 es definida para almacenar la dirección de la sexta instrucción. Esto quiere decir que la etiqueta va a "representar" el lugar en la memoria donde quedará la codificación de la sexta instrucción que es 20 en este caso. En este momento es donde usted debe revisar la codificación de las etiquetas.

De otro lado, la etiqueta label1 es utilizada en la tercera instrucción que está codificada en la dirección 8. Con estos dos valores es ahora posible calcular la constante con la que se codificará la etiqueta para la instrucción. El cálculo se calcula como $i = t - p$, donde $t$ es 20 y $p$ es 8. Esto da como resultado $i=12$ para el valor de la constante.

Cada vez que una etiqueta sea usada en una de las instrucciones de este grupo, su codificación será diferente y dependerá de el valor de $p$ que depende de la instrucción que está siendo codificada.

Note la sustitución que se realizó en las instrucciones en las que cada etiqueta estaba representada.

El objetivo de este ejercicio es entonces que implemente la parte que calcula cada una de las etiquetas de su programa. Esto también comprende controlar el tipo de errores que pueden aparecer aquí. Por ejemplo, saltar a etiquetas que no están definidas en el programa debe causar un error. En el siguiente veremos como esa información es codificada.

Ejercicio 7

Después de terminado el ejercicio anterior y ya habiendo calculado la constante del salto de cada instrucción procederemos a codificarla. Es de resaltar que la misma instrucción de salto será codificada de manera diferente a lo largo del programa. Es decir, La dirección beq x4, x5, label1 puede aparecer exactamente igual en más de una posición en el programa. Como la constante a calcular es relativa a la posición de esta instrucción, su codificación no siempre será la misma.

La siguiente imagen muestra la distribución de la información de una instrucción tipo B:

Como es posible evidenciar es muy parecida a la distribución de la información de las instrucciones tipo S que usted ya realizó en el ejercicio 5. Es decir, el inmediato o constante está separado en dos partes. La primera está comprendida por los bits del 7 al 11 (parte menos significativa) y la segunda en los bits del 25 al 31. Sigue siendo una constante de 12 bits pero está distribuida de manera diferente en la codificación. Para facilitar la codificación suponga la siguiente representación de una constante en 12 bits:

Esta constante tiene 12 bits porque el bit en la posición 0 no es utilizado. La forma como queda codificada la constante dentro de la instrucción se muestra en la siguiente figura:

Note la correspondencia de cada bit de la constante (representado con una letra) en la codificación de la constante.

Su trabajo en este ejercicio consiste entonces en terminar la codificación de las instrucciones tipo B que comenzó en el ejercicio anterior.

Ejercicio 8

Hasta este punto ya deben estar codificadas la mayoría de las instrucciones, solo quedan faltando 6 de las cuales 2 son realmente básicas así que sería más preciso hablar de 4 aunque de esos 4 hay una instrucción que es de tipo I en la cual usted ya tiene experiencia así que se puede reducir a 3 instrucciones.

La instrucción jalr, que pertenece al grupo de instrucciones Tipo J es codificada de la siguiente forma:

En este caso lo único complicado es que se trata de una constante de 21 bits. Las otras dos partes son simplemente dos valores, un opcode de 7 bits y un registro de 5. La codificación de la constante dentro de la instrucción se realiza de la siguiente manera:


Cod b31 b30 b29 b28 b27 b26 b25 b24 b23 b22 b21 b20
Imm b20 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b11
Cod b19 b18 b17 b16 b15 b14 b13 b12
Imm b19 b18 b17 b16 b15 b14 b13 b12

Por razones de espacio la codificación se separa en dos partes. La primera parte presenta la correspondencia entre bits de la constante con bits de la instrucción hasta los bits 20 y 11 respectivamente. En la segunda parte se muestra el resto. Nuevamente note que no existe bit 0 en la constante. Esto se debe a lo mismo del ejercicio anterior. La codificación de esta instrucción siempre tendrá un 0 como bit menos significativo.

Su trabajo en este ejercicio es codificar la instrucción jaly probar que la codificación de jalresté funcionando bien. Esta última es de tipo I así que usted ya debe estar en capacidad de entenderla.

Ejercicio 9

Las dos instrucciones que usted codificará en este ejercicio son las del tipo U. Son realmente sencillas, más que las del punto anterior. Tienen el siguiente formato:

En este caso la constante es de 20 bits y será asignada a la codificación de la siguiente manera:

Cod b31 b30 b29 b28 b27 b26 b25 b24 b23 b22 b21 b20
Imm b19 b18 b17 b16 b15 b14 b13 b12 b11 b10 b9 b8
Cod b19 18 17 16 15 14 13 12
Imm b7 b6 b5 b4 b3 b2 b1 b0

Ejercicio 10

En este ejercicio usted finalmente probará todo. Si necesita programas de ejemplos los puede construir usted mismo en esta página web. Usted escribe un programa en C en el editor de la izquierda y automáticamente se compila y se emite el código en ensamblador. Tenga en cuenta las siguientes opciones a la hora de generar desde esa herramienta:

alt text

La opción -O0 es una letra "o" en mayúscula seguida por el número cero.

Recursos

En el repositorio encontrará los siguientes archivos:

rvalp.pdf

rvalp.pdf es un documento donde encontrará información detallada de cómo son escritas las instrucciones en el ensamblador. En particular le recomiendo echar un vistazo al capítulo 5. La sección 5.3 es muy importante porque allí encuentra el formato en el que cada instrucción debe quedar en la salida.

  • RISCV_CARD.pdf: Es el documento que contiene todo el conjunto de instrucciones. En especial le recomiendo ver la primera página.

Adicionalmente a los archivos, los siguientes enlaces pueden resultar de utilidad.

  • En este enlace se define lo que es el complemento a dos de la representación binaria de un número.

  • En este otro enlace encontrarán una herramienta para comprobar que su codificación esté quedando bien hecha. Básicamente ustedes le dan una instrucción y les sale la codificación. Aquí ca un ejemplo relacionado con el código del ejercicio 6:

    alt text

    en este caso se utilizó la página para extraer la instrucción original a partir de la codificación 00520663. Para hacerlo en el otro sentido es solamente cuestión de escribir la instrucción y saldrá la codificación respectiva.

Resumen de registros e instrucciones

Registros

Register ABI Name Description Saver
x0 zero Zero constant
x1 ra Return address Callee
x2 sp Stack pointer Callee
x3 gp Global pointer
x4 tp Thread pointer
x5-x7 t0-t2 Temporaries Caller
x8 s0 / fp Saved / frame pointer Callee
x9 s1 Saved register Callee
x10-x11 a0-a1 Fn args/return values Caller
x12-x17 a2-a7 Fn args Caller
x18-x27 s2-s11 Saved registers Callee
x28-x31 t3-t6 Temporaries Caller

Tipo R

Funct7 RS2 RS1 Funct3 RD Opcode Name
0000000 rs2 rs1 000 rd 0110011 ADD
0100000 rs2 rs1 000 rd 0110011 SUB
0000000 rs2 rs1 001 rd 0110011 SLL
0000000 rs2 rs1 010 rd 0110011 SLT
0000000 rs2 rs1 011 rd 0110011 SLTU
0000000 rs2 rs1 100 rd 0110011 XOR
0000000 rs2 rs1 101 rd 0110011 SRL
0100000 rs2 rs1 101 rd 0110011 SRA
0000000 rs2 rs1 110 rd 0110011 OR
0000000 rs2 rs1 111 rd 0110011 AND

Tipo I

Aritméticas y lógicas

Imm12 RS1 Funct3 RD Opcode Name
imm[11:0] rs1 000 rd 0010011 ADDI
imm[11:0] rs1 010 rd 0010011 SLTI
imm[11:0] rs1 011 rd 0010011 SLTIU
imm[11:0] rs1 100 rd 0010011 XORI
imm[11:0] rs1 110 rd 0010011 ORI
imm[11:0] rs1 111 rd 0010011 ANDI

Corrimientos

Imm7 Imm5 RS1 Funct3 RD Opcode Name
0000000 shamt rs1 001 rd 0010011 SLLI
0000000 shamt rs1 101 rd 0010011 SRLI
0100000 shamt rs1 101 rd 0010011 SRAI

Carga en memoria

Imm12 RS1 Funct3 RD Opcode Name
imm[11:0] rs1 000 rd 0000011 LB
imm[11:0] rs1 001 rd 0000011 LH
imm[11:0] rs1 010 rd 0000011 LW
imm[11:0] rs1 100 rd 0000011 LBU
imm[11:0] rs1 101 rd 0000011 LHU

Saltos incondicionales

Imm12 RS1 Funct3 RD Opcode Name
imm[11:0] rs1 000 rd 1100111 JALR

Interrupciones

Imm12 RS1 Funct3 RD Opcode Name
000000000000 00000 000 00000 1110011 ECALL
000000000001 00000 000 00000 1110011 EBREAK

Tipo S

Imm7 RS2 RS1 Funct3 Imm5 Opcode Name
imm[11:5] rs2 rs1 000 imm[4:0] 0100011 SB
imm[11:5] rs2 rs1 001 imm[4:0] 0100011 SH
imm[11:5] rs2 rs1 010 imm[4:0] 0100011 SW

Tipo B

Imm7 RS2 RS1 Funct3 Imm5 Opcode Name
imm[12 | 10:5] rs2 rs1 000 imm[4:1 | 11] 1100011 BEQ
imm[12 | 10:5] rs2 rs1 001 imm[4:1 | 11] 1100011 BNE
imm[12 | 10:5] rs2 rs1 100 imm[4:1 | 11] 1100011 BLT
imm[12 | 10:5] rs2 rs1 101 imm[4:1 | 11] 1100011 BGE
imm[12 | 10:5] rs2 rs1 110 imm[4:1 | 11] 1100011 BLTU
imm[12 | 10:5] rs2 rs1 111 imm[4:1 | 11] 1100011 BGEU

Tipo J

Imm20 RD Opcode Name
imm[20 | 10:1 | 11 | 19:12] rd 1101111 JAL

Tipo U

U Type U Immediate

Imm20 RD Opcode Name
imm[31:12] rd 0110111 LUI
imm[31:12] rd 0010111 AUIPC

About

Assembler for RISCV

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 97.9%
  • Assembly 2.1%