S10 - myTeachingURJC/Arq-computadores-01 GitHub Wiki

Sesión de Teoría 10: El computador NanoRisc-V

  • Tiempo: 2h
  • Objetivos de la sesión:
    • Descripción del nanoRISC-V
    • Entender el formato de las instrucciones
    • Simulación de programas sencillos

Contenido

Introducción

Para entender el funcionamiento del procesador vamos a estudiar una versión muy simplificada del RISC-V, que llamaremos nanoRISC-V. En esta sesión definiremos nuestro procesador nanoRISC-V, tanto a nivel de instrucciones como a nivel de chip. Con este procesador construimos el computador NanoRISC-V, que ya es capaz de ejecutar las instrucciones almacenadas en la memoria. Estudiaremos qué ocurre a nivel hardware para que este computador ejecute los programas. En las siguientes sesiones estudiaremos los detalles internos del procesador

Procesador nanoRISC-V

El procesador que vamos a implementar es el nanoRISC-V: Un procesador RISC-V simplificado, de 64 bits, que tiene sólo 8 instrucciones. En esta primera implementación todas las instrucciones se ejecutarán en 1 ciclo de reloj (CPI = 1) y la estructura final del circuito se puede modelar como la conexión en cadena de un circuito secuencial más uno combinacional. Serán los retardos de estos componentes los que definan la frecuencia máxima de funcionamiento

  • Arquitectura base RV64I (64-bits)
  • 8 Instrucciones: add, addi, sub, and, or, ld, sd, beq
  • 32 Registros de 64 bits: x0,x1,...,x31
  • CPI = 1. Todas las instrucciones se ejecutan en 1 ciclo de reloj
  • Memoria de código y datos separadas
  • ¿Frecuencia máxima de funcionamiento?

Repertorio de instrucciones

Nuestro nanoRISC-V tiene 8 instrucciones: ld y sd para acceder a memoria (load-store), tres para realizar operaciones aritméticas (add, addi, sub), dos para las operaciones lógicas (and, or) y una para hacer un salto condicional (beq)

  • Instrucciones de acceso a memoria
Inst. Operandos Descripción
LD rd,off(rs1) Load-word. Ejemplo: ld x5, 0(x3). Cargar en el registro destino (rd) el dato de 64-bits situado en la dirección de memoria rs1 + off
SD rs2,off(rs1) Store-word. Ejemplo: sd x1, 16(x2). Almacenar el registro rs2 en memoria, en la dirección rs1 + off.
  • Instrucciones Aritmético-lógicas
Inst. Operandos Descripción
ADD rd,rs1,rs2 Suma. Ejemplo: add x3, x5, x15. Guardar en el registro destino el resultado de sumar los registros rs1 y rs2
ADDi rd,rs1,valor Suma. Ejemplo: add x3, x5, 30. Guardar en el registro destino el resultado de sumar rs1 con el valor inmediato
SUB rd,rs1,rs2 Resta. Ejemplo: sub x7, x6, x13. Guardar en el registro destino el resultado de restar rs1 - rs2
AND rd,rs1,rs2 Operación Y-Lógico. Ejemplo: and x1, x30, x20. Guardar en el registro destino el resultado de hacer una operación AND bit a bit entre los registros rs1 y rs2
OR rd,rs1,rs2 Operación O-Lógico. Ejemplo: or x31, x15, x16. Guardar en el registro destino el resultado de hacer la operación OR bitg a bit entre los registros rs1 y rs2
  • Instrucción de salto condicional
Inst. Operandos Descripción
BEQ rs1,rs2,dest Salto condicional. Ejemplo: beq x1,x0,fin. Realizar un salto a destino si los registros rs1 y rs2 son iguales. En caso contrario se ejecuta la siguiente instrucción. El salto es relativo al PC, en unidades de medias-palabras. Si la condición se cumple, PC = PC + destino*2

Nuestro procesador NanoRISC-V es tan pequeño que no incluye la instrucción ecall. Esto significa que NO TIENE SISTEMA OPERATIVO. No está pensado para realizar llamadas a un sistema operativo. Cualquier función que necesitemos la tenemos que implementar nosotros

Tampoco tenemos la instrucción jal por lo que NO PODEMOS IMPLEMENTAR SUBRUTINAS

Actividades para practicar

  • Actividad 1: Escribe un programa para el NanoRisc-v que implemente la operación de alto nivel f = a + 1. El valor inicial que tiene a es de 170. Recuerda que NO hay sistema operativo, por lo que no uses la instrucción ecall. Así que, de momento, no uses nada para terminar

  • Actividad 2: Escribe un programa para el NanoRISC-V que lea una variable (de 64 bits) de la posición de memoria 0x100 y la guarde en el registro x1. Como estamos a bajo nivel y las direcciones las queremos controlar nosotros, NO uses etiquetas, sino direcciones directamente. También recuerda que en el nanoRV NO tenemos subrutinas, por lo que el registro x1 lo podemos usar como cualqueir otro (x1 es el registro ra, que almacena la dirección de retorno... pero NO en el nanoRV)

  • Actividad 3: Escribe un programa para el NanoRISC-V que almacene el valor 73 (decimal) en la posición de memoria 0x108. No utilices etiquetas, sino direcciones directamente

Formato de las instrucciones

En esta tabla resumen se muestra el formato de las 8 instrucciones. Todas las aritmético-lógicas (menos ADDi) son de tipo R, ya que usan tres registros. Las instrucciones de load y addi son de tipo I, la de store es de tipo S y la de salto condicional de tipo B

El campo opcode nos indica qué instrucción es o de qué tipo. Por ejemplo, las instrucciones add, sub, and y or tiene todas el mismo opcode: 0110011. Esto indica que son de tipo R. Se diferencian entre ellas por los campos func3 y func7

Retos

  • Reto 1: Esta es una instrucción en código máquina del NanoRISC-V: 0x0C000293. ¿Sabrías decodificarla y obtener la instrucción en ensamblador?
  • Reto 2: ¿Y qué instrucción en ensamblador es esta otra 0x00128313?
  • Reto 3: ¿Cuál es el código máquina de la instrucción ld x1, 0x100(x0)?

El chip nanoRISC-V

Dada una arquitectura de un procesador, existen muchas posibles implementaciones diferentes. Unas nos ofrecerán mejor rendimiento, otras serán más baratas, otras consumirán menos, etc. Cada fabricante implementa su propio chip. Pero a nivel de instrucciones, como la arquitectura es la misma, serían compatibles

El objetivo del nanoRISC-V es que sea muy sencillo de implementar. Por eso la memoria principal se divide en dos memorias separadas, que físicamente están en chips distintos:

  • Memoria de instrucciones: Contiene nuestro programa (segmento de código)
  • Memoria de datos: Contiene las variables (Segmento de datos)

Este es el aspecto del nanoRISC-V:

Es un chip que tiene en total 290 patas, cada una de 1 bit. En el dibujo se han agrupado por funcionalidad. En la izquierda están las señales de entrada al chip y en la derecha las señales que salen del chip

  • Señales de salida:

    • PC (64-bits): Contiene la dirección de memoria donde se encuentra almacenada la siguiente instrucción a ejecutar
    • Addr(64-bits): Dirección de acceso a la memoria de datos, para leer o escribir
    • Dout (64-bits): Dato de salida del procesador para ser escrito en la memoria
    • MemWr (1 bit): Señal de control. Indica si el acceso es de escritura (MemWr = 1). En caso contrario será de lectura
  • Señales de entrada:

    • Instr (32-bits): La instrucción a ejecutar, proveniente de la memoria de instrucciones
    • Din (64-bits): El dato recibido de la memoria de datos
    • clk (1-bit): Señal de reloj

El computador nanoRISC-V

El procesador por sí sólo no hace nada. Necesita que exista una memoria donde está almacenado el programa y los datos. Para convertirlo en un computador le añadimos la memoria de datos y la memoria de Instrucciones. Además le conectamos la señal de reloj

Memoria de instrucciones

La memoria de instrucciones almacena el código máquina del programa a ejecutar. Como las instrucciones son de 32 bits, su anchura es de 32-bits. El chip tiene este aspecto:

Tiene una entrada de 64-bits, addr, por donde se recibe la dirección de la instrucción a leer, y una salida de 32-bits, data, por donde sale la instrucción leída. Esta memoria es COMBINACIONAL. En cuanto hay una dirección en sus pines addr la memoria devuelve la instrucción pedida, tras un retardo

Memoria de datos

La memoria de datos contiene todas las variables y constantes que se sitúan en el segmento de datos. Es una memoria de lectura y escritura: el procesador puede leer los datos ahí situados y modificarlos. La memoria tiene 3 señales entrada:

  • Addr: Dirección a la que se quiere acceder, tanto para escritura como lectura
  • Din: Dato de 64 bits a grabar en la memoria, en la dirección indicada por Addr
  • Wr: Señal de control para indicar si el acceso es de lectura o escritura (Wr = 1)

Este es el aspecto del Chip:

La memoria de datos se comporta como un circuito COMBINACIONAL en las lecturas (decimos que las lecturas son combinacionales), pero para la escritura se comporta como un registro: tiene una señal de reloj y la escritura sólo se realiza cuando Wr es 1 y llega un flanco de subida del reloj. Decimos que la escritura es síncrona

La memoria de datos también tiene un retardo, pero es diferente, según que el tipo de acceso. Así habrá un retardo para el caso de la lectura y otro para la escritura

Esquema general

Este es el esquema general del computador nanoRISC-V. Está formado por 3 chips: el procesador nanoRISC-V, un chip de memoria ROM que contiene el programa a ejecutar y un chip de memoria RAM donde se almacenan los datos

Programa de prueba: TEST-1

Antes de entrar en los detalles, vamos a ver cómo funciona nuestro computador nanoRiscV con un programa de 2 instrucciones, que asigna el valor 0xC0 al registro t0, y deposita en t1 su valor incrementado en una unidad: 0xC1. Este sería el programa escrito en el simulador RARs que usamos en el laboratorio

#-- TEST 1: Programa de prueba para el nanoRiscV
#-- Assigna los siguientes valores a los registros
#-- t0 = 0xC0
#-- t1 = t0 + 1 = 0xC1

	.text
	
	li t0, 0xC0     #-- t0 = 0xC0
	addi t1, t0, 1  #-- t1 = t0 + 1

En los programas escritos por humanos usamos pseudoinstrucciones, comentarios, directivas para el ensamblador y los nombres de los registros definidos en la ABI del RISCV.

Conversión a instrucciones y registros básicos

En un primero paso del ensamblado el programa se convierte a instrucciones básicas del RISCV, usando los registros con sus números. El procesador no sabe nada del registro t0 ni t1. Para él estos son los registros x5 y x6 de su banco de registros

Así queda el programa, listo para convertirse a código máquina

addi x5, x0, 0xC0
addi x6, x5, 1

Generación del código máquina

Las instrucciones básicas se traducen ahora a código máquina, utilizando el formato de las instrucciones del RISC-V. Ambas instrucciones son de tipo I. La primera se traduce al ćodigo: 0x0C000293

La segunda se traduce a: 0x00128313

Ya tenemos el programa final, con el código ejecutable

Sólo tiene dos números de 32 bits, que se cargarán en la memoria de instrucción

0x0C000293
0x00128313

Simulando el programa TEST-1

Para que nuestro computador nanoRISC-V ejecute el programa de TEST-1, primero se debe cargar en la memoria de instrucciones. Esto se hace externamente. En nuestro nanoRISC-V NO tenemos sistema operativo, así que lo cargamos a partir de la dirección 0, para que se ejecute nada más arrancar. Como las instrucciones son de 32 bits, ocupan 4 bytes, por lo que la primera está en la dirección 0 y la siguiente en la dirección 4

Dato que el programa TEST-1 NO accede a memoria para leer ningún dato, para simular el funcionamiento sólo incluiremos la memoria de instrucciones. Así es como queda nuestro computador nanoRISC-V en el estado inicial, antes de empezar a funcionar:

En esta animación se muestra una simulación de la ejecución de este programa. Partimos del estado inicial. El valor del PC (0) sale del procesador hacia la memoria. La memoria devuelve el contenido de la posición 0, donde está la primera instrucción. Pero el procesador NO la leerá hasta que no llegue un pulso de reloj (El corazon debe latir. Mientras no lata el procesador no hace cambios en su interior)

Cuando llega el pulso, la instrucción entra en el procesador y la empieza a ejecutar. Determina su formato a partir del opcode y la ejecuta. El resultado de la ejecución es que se guarda el valor 0xC0 en el registro x5, y se incrementa el contador de programa en 4 bytes, para apuntar a la siguiente instrucción. El nuevo valor del PC (4) llega a la memoria de instrucciones y devuelve la siguiente instrucción, que se queda esperando hasta que el procesador la lea

Al llegar otro pulso de reloj se captura la nueva instrucción y se ejecuta: Se incrementa el valor de x5 y el resultado se guarda en x6: 0xC1. Se incrementa el contador de programa en 4 bytes, por lo que ahora vale 8. Este valor sale al exterior hacia la memoria de instrucciones

¡Las dos instrucciones de nuestro programa se han ejecutado!

Deteniendo el computador

El procesador NO se detiene nunca. Siempre está leyendo instrucciones y ejecutándolas. En el ejemplo de la simulación del programa TEST-1, hemos dejado de simular al ejecutarse la segunda instrucción pero... ¡El procesador sigue! Tras ejecutar la segunda instrucción, el contador de programa ha pasado a valer 8, por lo que se ejecutará la instrucción de esa dirección. ¿Y qué es lo que hay? Como no hemos cargado nada, habrá cualquier número

En el laboratorio, para terminar un programa llamamos al servicio exit del sistema operativo. Sin embargo, como estamos construyendo desde cero el computador nanoRISC-V, no tenemos el sistema operativo. ¿Cómo podemos prevenir la ejecución de instrucciones desconocidas?

Lo que hacemos es colocar un bucle infinito en la tercera instrucción. En el nanoRISC-V no tenemos instrucciones de salto incondicional, pero las implementamos muy fácilmente a partir de la instrucción BEQ, usando el registro x0 en los términos de la comparación. Como x0 es igual al x0, el salto se realizará siempre:

# -- Bucle infinito para el procesador
inf:  beq x0, x0, inf

El código máquina de esta instrucción es: 0x00000063

El programa TEST-1 completo queda así:

#-- TEST 1: Programa de prueba para el nanoRiscV
#-- Assigna los siguientes valores a los registros
#-- t0 = 0xC0
#-- t1 = t0 + 1 = 0xC1

	.text
	
	li t0, 0xC0     #-- t0 = 0xC0
	addi t1, t0, 1  #-- t1 = t0 + 1
	
	#-- Detener al procesador para que 
	#-- no siga ejecutando instrucciones de la memoria
stop:   beq x0,x0, stop #-- (Bucle infinito)

Y el código máquina que se carga en la memoria es:

0x0C000293
0x00128313
0x00000063

Al ejecutarse y llegar la última instrucción, el procesador permanece en un estado estable, donde nada cambia:

Programa de prueba: TEST-2

Este programa de prueba utiliza las instrucciones ld y sd para leer un dato de 64 bits situado en la dirección 0x100 y copiarlo a la dirección 0x108. Supondremos que antes de comenzar el programa el dato ya está situado en la memoria (al cargar el programa se han cargado también los datos iniciales)

# -- TEST-2
# -- Ejemplo de load y store
# -- El dato situado en direccion 0x100 se
# -- copia a la dirección 0x108

	.text
	
	#-- Leer dato
	ld x1, 0x100(x0)  #-- x1 = dato1
	
	#-- Copiarlo en destino
	sd x1, 0x108(x0)  #-- dato2 = x1
	
	# -- Detener la ejecución
stop:	beq x0,x0,stop

El programa en código máquina es el siguiente:

0x10003083
0x10103423
0x00000063

En esta figura se muestra con más detalle cómo están codificadas las instrucciones sd y ld:

Al cargar el programa y los datos, nuestro computador se encuentra en este estado:

En la posición 0x100 de la memoria de datos se ha cargado el valor 0xCAFE, y el programa se ha cargado a partir de la dirección 0 de la memoria de instrucciones

La simulación comienza con la ejecución de la instrucción ld, que saca la dirección 0x100 por la salida addr y la memoria de datos devuelve 0xCAFE. Al llegar el flanco de reloj este dato se guarda en el registro x1 y el PC se incrementa en 4 unidades para apuntar a la siguiente instrucción

La siguiente instrucción se lee de memoria, se saca 0x108 por Addr y el registro x1 por Dout. Al llegar el flanco de reloj se hace la escritura y se incrementa PC en 4. Por último se carga la instrucción beq que mantiene el procesador en el mismo estado: nuestro programa se detiene

Este es el estado final

Conclusiones

En esta sesión hemos definido el computador nanoRisc-V, que consta de 3 elementos: El procesador (nanoRisc-V), la memoria de instrucciones y la memoria de datos. Ya sabemos también cómo ejecuta instrucciones y programas básicos. Tenemos todo lo necesario para realizar una primera implementación

Lecturas del libro de referencia

Libro de referencia: "Computer Organization and Design. Hardware/Software interface. Risc-V"

(Las mismas que las de la sesión 6)

  • Apartado 4.1: Introduction (Pag 236-240)
  • Apartado 4.2: Logic Design Conventions (Pag 240-243)

Ejercicios

Ejerccios para practicar y asimilar los conceptos más importantes

Ejercicio 1

Partimos de este circuito:

  • a) Calcula su máxima frecuencia de funcionamiento
  • b) Si inicialmente el registro tiene el valor 0x00400000, indica sus valores en los siguientes 4 ciclos de reloj

A la salida del registro conectamos una memoria ROM que contiene instrucciones, que se almacenan en otro registro en cada ciclo de reloj

  • c) Calcula frecuencia máxima de funcionamiento de este nuevo circuito

Ejercicio 2

Hemos construido un computador nanoRISC-v que sólo tiene memoria de instrucciones:

Los accesos a memoria para la lectura de las instrucciones tardan 10ns. El tiempo que tarda en ejecutarse una instrucción depende del tipo de instrucción. Las instrucciones de tipo R tardan 50ns, las de tipo I 60ns y las de salto condicional 70ns. Las instrucciones se ejecutan en un único ciclo de reloj

  • a) Calcular la frecuencia máxima a la que puede funcionar este computador para que se ejecuten correctamente las instrucciones de tipo R, I y los saltos condicionales (el resto de instrucciones se ignoran en este ejemplo)

  • b) Si estamos usando la frecuencia máxima calculada anteriormente, ¿Cuanto tiempo tarda el procesador en ejecutar estas 6 instrucciones?

      beq x0,x0,next
next: li t0, 1
      addi t1, t0, 1
      add t2, t1, t0
      li t3, 1
      sub t4, t2, t3
      ...

Ejercicio 3

Hemos construido un computador nanoRISC-v que tiene memoria de instrucciones y memoria de datos. El CPI es de 1

Dentro del procesador, todas las instrucciones tardan 20ns. Las instrucciones ld y sd, tienen un retardo adicional determinado por el acceso a la memoria de datos: 30ns en la lectura y 40ns en la escritura

  • a) Calcula el tiempo que tardan las instrucciones de tipo de R en ejecutarse
  • b) Calcula el tiempo que tardan la instrucción beq en ejecutarse
  • c) Calcula el tiempo que tarda la instrucción addi en ejecutar
  • d) Calcula el tiempo que tarda la instrucción ld en ejecutarse
  • e) Calcula el tiempo que tarda la instrucción sd en ejecutarse
  • f) Indica cuál es el camino crítico
  • g) ¿Cual es la frecuencia máxima a la que puede funcionar este procesador?
  • h) Calcula el número de ciclos que tarda el procesador en ejecutar este programa (sin contar la instrucción que está en la etiqueta fin):
       .text
       li t0,0
       li t1,3
bucle: beq t0,t1,fin
       sd t0, 0x100(x0)
       addi t0,t0,1
       beq t0,t0,bucle
fin:   #-- Contar los ciclos hasta aquí

i) Si funciona a su frecuencia máxima, ¿Cuánto tiempo tarda en ejecutar este programa?

Notas para el profesor:

  • Título informal de la clase "La pastilla roja..."
  • Nos sumergimos en el interior de un computador: el computador nano-riscv. En esta primera toma de contacto definimos sus componentes a nivel del chip, y estudiamos cómo funcionan algunos programas sencillos de pocas instrucciones

Autores

Licencia

Créditos

Enlaces