Skip to content

L13: Practica 6‐1

Juan Gonzalez-Gomez edited this page Jan 9, 2026 · 350 revisions

L13: RV32I: Resto de instrucciones

  • Objetivos de la sesión:
    • Completar el estudio de las 37 instrucciones del RV32I
    • Explicar las Instrucciones lui y auipc para el manejo de números de 20 bits
    • Operaciones con números con signo y sin signo
    • Presentar las instrucciones de comparación con asignación
    • Instrucciones de desplazamiento de bits
    • Instrucciones de manipulación de bits

Contenido

Introducción

Comenzamos un nuevo bloque de sesiones de laboratorio donde vamos a seguir aprendiendo sobre el RISC-V. Como ya tenemos los conocimientos básicos, llega el momento de profundizar un poco más...

Las instrucciones que hemos ido aprendiendo se engloban dentro de la arquitectura base RV32I, con la excepción de la instrucción de multiplicación (mul) que usamos en la sesión L8, que pertenece a la extensión M

En esta sesión vamos a presentar el resto de instrucciones que nos faltan, y los usos que tienen

Haremos especial mención a las Instrucciones de desplazamiento y de Manipulación de bits, presentando los casos de uso típicos

RV32I: Lista completa de instrucciones

En la siguiente tabla se resumen TODAS las instrucciones RV32I. En total hay 37. En este enlace hay información detallada sobre ellas: https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html

Instruccion Tipo Funcionamiento
1 lui rd, imm20 U rd = imm << 12
2 auipc rd, imm20 U rd = pc + (imm << 12)
── ────────── ── ──────────────
3 addi rd, rs1, imm12 I rd = rs1 + ext(imm12)
4 slti rd, rs1,imm12 I rd = rs1 < ext(imm12)
5 sltiu rd, rs1, imm12 I rd = rs1 < ext(imm12)
6 xori rd, rs1, imm12 I rd = rs1 ^ ext(imm12)
7 ori rd,rs1,imm I rd = rs1
8 andi rd,rs1,imm I rd = rs1 & ext(imm12)
9 slli rd,rs1,shamt I rd = rs1 << shamt
10 srli rd,rs1,shamt I rd = rs1 >>u shamt
11 srai rd,rs1,shamt I rd = rs1 >>s shamt
── ────────── ── ──────────────
12 add rd,rs1,rs2 R rd = rs1 + rs2
13 sub rd,rs1,rs2 R rd = rs1 - rs2
14 sll rd,rs1,rs2 R rd = rs1 << rs2
15 slt rd,rs1,rs2 R rd = rs1 <s rs2
16 sltu rd,rs1,rs2 R rd = rs1 <u rs2
17 xor rd,rs1,rs2 R rd = rs1 ^ rs2
18 srl rd,rs1,rs2 R rd = rs1 >>u rs2
19 sra rd,rs1,rs2 R rd = rs1 >>s rs2
20 or rd,rs1,rs2 R rd = rs1
21 and rd,rs1,rs2 R rd = rs1 & rs2
── ────────── ── ──────────────
22 lb rd,offset(rs1) L rd = ext(M[rs1 + ext(offset)])
23 lh rd,offset(rs1) L rd = ext(M[rs1 + ext(offset)])
24 lw rd,offset(rs1) L rd = ext(M[rs1 + ext(offset)])
25 lbu rd,offset(rs1) L rd = M[rs1 + ext(offset)])
26 lhu rd,offset(rs1) L rd = M[rs1 + ext(offset)])
── ────────── ── ──────────────
27 sb rs2,offset(rs1) S M[rs1 + ext(offset)] = x[rs2]
28 sh rs2,offset(rs1) S M[rs1 + ext(offset)] = x[rs2]
29 sw rs2,offset(rs1) S M[rs1 + ext(offset)] = x[rs2]
── ────────── ── ──────────────
30 jal rd,offset J rd = pc+4; pc = pc + ext(offset)
31 jalr rd,rs1,offset J rd = pc+4; pc = rs1 + ext(offset)
── ────────── ── ──────────────
32 beq rs1,rs2,offset B if (rs1 == rs2) pc = pc + ext(offset)
33 bne rs1,rs2,offset B if (rs1 != rs2) pc = pc + ext(offset)
34 blt rs1,rs2,offset B if (rs1 < rs2) pc = pc + ext(offset)
35 bge rs1,rs2,offset B if (rs1 >= rs2) pc = pc + ext(offset)
36 bltu rs1,rs2,offset B if (rs1 < rs2) pc = pc + ext(offset)
37 bgeu rs1,rs2,offset B if (rs1 >= rs2) pc = pc + ext(offset)

En estas sesión nos centraremos en las instrucciones que no hemos visto todavía, y entraremos en más detalles con algunas de las que ya conocemos

Asignando números constantes de 32 bits a registros

Supongamos que queremos asignar el valor 0xFFFF07A0 de 32 bits al regitro t0. Esto ya lo sabemos hacer. Basta con ejecutar esta instrucción:

li t0, 0xFFFF07A0

Sin embargo, li es una pseudo-instrucción, y sabemos además, que en este caso se transforma en 2 instrucciones básicas. ¿Cómo podemos hacer esta asignación directamente con instrucciones básicas?

Los números de 32 bits, en el RISCV, se dividen en 2 trozos de diferente tamaño. Por un lado están los 20-bits de mayor peso (5 caracteres hexadecimales) y por otro lado los 12 bits de menor peso (3 caracteres hexadecimales)

Los 12-bits de menor peso se asignan con la instrucción addi, cuyo formato ya conocemos y que se reproduce aquí:

La pregunta es... ¿Cómo asignamos valor a los 20 bits de mayor peso? Usando la instrucción lui

La instrucción LUI

La instrución lui toma un valor de 20-bits, lo desplaza 12-bits hacia la izquierda para situarlos como los 20-bits de mayor peso, y este valor se asigna al registro destino. El desplazamiento hace que los 12-bits de menor peso tengan el valor 0

Al ejecutarse esta instrucción:

lui t0, 0xFFFF0

se asigna el valor 0xFFFF0000 al registro t0

Este es el formato de la instrución lui:

Es similar al de addi, pero el campo inmediato ahora es de 20-bits, y se pierden los campos rs1 y func3, que no se usan en lui

Ahora para obtener el valor 0xFFFF07A0 basta con añadir a t0 el valor 0x7A0 con la instrucción addi. Estas son, por tanto, las dos instrucciones necesarias para meter el valor 0xFFFF07A0 en t0:

lui t0, 0xFFFF0
addi t0, t0, 0x7A0

En esta figura vemos la evolución del registro t0:

Asignación con LI

En este programa asignamos el mismo valor 0xFFFF07A0 a los registros t0 y t1 utilizando lui-addi y li respectivamente. Comprobamos que efectivamente li genera esas mismas dos intrucciones

#-- Ejemplo de asignacion de un valor de 32-bits
#-- a los registros t0 y t1

	#-- Servicios del sistema operativo
	.eqv EXIT 10
	
	.text
	
	#-- Asignar 0xFFFF07A0 directamente
	lui t0, 0xFFFF0     #-- 20-bits de mayor peso
	addi t0, t0, 0x7A0  #-- 12-bits de menor peso
	
	#-- Asignar el valor con LI
	li t1, 0xFFFF07A0
	
	#-- Terminar
	li a7, EXIT
	ecall

Ensamblamos, ejecutamos y comprobamos el resultado:

Obteniendo direcciones a partir de etiquetas

Para acceder a una variable de memoria necesitamos su dirección. Desde nuestros programas en ensamblador usamos la pseudo-instrución la, que se transforma en 2 instrucciones de RISCV. Vamos a comprender este mecanismo. Para ello utilizamos el siguiente ejemplo, en el que queremos obtener la dirección de la primera variable del segmento de datos. Ya sabemos cuál es esta dirección: 0x10010000

	.data
	
	#-- Primera variable del segmento de datos
a:	.word 0xCAFEBACA

	.text

	#-- Obtener la dirección de a
	la t0, a

Cuando se ejecuta la primera instrucción, el contador de programa vale 0x00400000. Si a este valor le sumanos el número constante 0x0fc10000, obtenemos la dirección de a: 0x00400000 + 0x0fc10000 = 0x10010000

En tiempo de ensamblado, el ensamblador conoce la dirección de a y el valor del contador de programa, por lo que puede determinar el valor de este número constante: 0x1001000 - 0x00400000 = 0x0fc10000. Una vez obtenido este número, bastará con tener una instrucción que sume este valor al pc para obtener la dirección de la variable. En ninguna instrucción del RISCV se puede especificar un valor inmediato de 32 bits, porque NO hay espacio. Pero sí se pueden especificar los 20-bits de mayor peso (como en la instrucción lui)

Este es el objetivo de la instrucción auipc: Sumar al pc un offset determinado a partir de sus 20-bits de mayor peso

La instrucción AUIPC

La instrucción auipc define un número de 32-bits a partir de sus 20-bits de mayor peso y se lo suma al contador de programa. El resultado se almacena en un registro destino

Este es el formato de la instrucción: auipc rd, imm

Así por ejemplo, la instrucción auipc t0, 0x12345 obtiene el número 0x12345000, lo suma al pc y devuelve el resultado: rd = pc + 0x12345000

De forma genérica, el comportamiento de esta instrucción queda determinado por esta fórmula: rd = pc + (imm << 12)

Este es el programa para obtener la dirección de la primera variable del segmento de datos. Usamos el valor 0x0FC10 con la instrucción auipc

	.data
	
	#-- Primera variable del segmento de datos
a:	.word 0xCAFEBACA

	.text

	#-- Obtener la dirección de a
	auipc t0, 0x0FC10

Lo ejecutamos para comprobarlo:

Cuando la etiqueta está muy lejos del contador de programa, hay que sumar un valor grande al PC, y la instrucción auipc funciona perfectamente. Sin embargo, si la etiqueta está cerca del PC, la instrucción auipc no nos permite sumar valores pequeños. ¿Cómo lo solucionamos?

Obteniendo el contador del programa

El contador de programa (pc) es un registro especial, al que NO tenemos acceso directamente. Sin embargo sí que lo podemos leer a través de la instrucción auipc. Basta con utilizar un valor inmediato de 0: rd = pc + imm. Si imm es 0, tenemos que rd = pc

En este programa de ejemplo se obtiene en el registro t0 el valor del contador de programa:

	.text

	#-- t0 = Valor del pc
	auipc t0, 0

Lo ejecutamos para comprobarlo:

Obteniendo direcciones relativas al PC pequeñas

Si queremos obtener posiciones de memoria relativas al PC que estén cerca, simplemente hay que obtener el valor del pc, con auipc y sumarle un offset con addi

	.text

	#-- t0 = Valor del pc
	auipc t0, 0
	
	#-- Sumar 8 al PC para obtener la direccion
	#-- de test
	addi t0, t0, 8
	
#-- Etiqueta de test. EStá 8 bytes por delante de la primera
#-- instruccion: PC + 8
test:

La dirección que se obtiene en t0 es 0x00400008, que se corresponde con test. Lo comprobamos:

Entendiendo la pseudo-instrucción la

El ensamblador implementa la mediante las instrucciones auipc y addi. Si la etiqueta está muy lejos, puede ocurrir que no haga falta utilizar addi. Pero el ensamblador en ese caso sí la usa, pero sumando 0. En los casos en que la etiqueta está muy cerca, auipc tiene el valor 0, para obtener el pc, al que luego se le suma el offset

En este ejemplo se utilizan 3 etiquetas, y se comprueban las instrucciones generadas por la para obtener sus direcciones

	.data
a:	.word 0xCAFEBACA

	.text
test1:
	#-- Obtener la direccion de a
	la t0, a
	
	#-- Obtener la direccion de test1
	la t1, test1
	
	#-- Obtener la direccion de test2
	la t2, test2
	
	nop
test2:

Probamos el programa. Observamos que en los registros t0, t1 y t2 tenemos las direcciones correctas. Las 3 pseudoinstrucciones la se han convertido a las instrucciones auipc y addi. La difererencia está en las constantes utilizadas. La dirección de la etiqueta test1 se calcula restando 8 al valor actual del PC (que para esa instrucción vale 0x00400008). La dirección para test2 se calcula sumando 12 (0xC) al valor del PC: 0x00400010 + 0x0c = 0x0040001c

Trabajando con números con signo y sin signo

En este apartado vamos a introducir ideas relacionadas con el tipo de números que podemos usar: números sin signo y números con signo

El problema de la interpretación de los números

Dentro del computador sabemos que TODO ES NÚMERO. Y estos números están en binario. Si en una parte de la memoria nos encontramos con esta secuencia de 8 unos: 11111111. ¿Qué número representa?

La respuesta es... "DEPENDE". Esta secuencia la tenemos que interpretar nosotros como programadores, según lo que queramos. Lo primero es el tamaño. Supongamos que estamos trabajando con bytes (la secuencia de 8 unos podría ser parte de un número de 32-bits). ¿Qué número representa?. También DEPENDE... Puede representar bien el número 255, o bien el número -1. Somos nosotros lo que decidimos cómo interpretar los datos: con signo o sin signo

Si resulta que esa secuencia la interpretamos como un byte con signo, entonces su valor es -1. PERO si estamos usando esa variable como un contador de un bucle, por ejemplo, la interpretamos como un byte sin signo (unsigned). En ese caso su valor es 255

La regla a tener en cuenta es:

Important

Es la persona que programa la que INTERPRETA el significado de los números, decidiendo qué tamaño tienen y qué tipo de números contiene (con signo, sin signo...)

El Bit de signo

En el caso de que queramos utilizar números con signo (y por tanto pueden ser positivos o negativos), la codificación que se utiliza es complemento a 2. En esta codificación el bit de mayor peso es el bit de signo. Si este bit es 1 significa que el número es negativo, pero si es 0 significa que es positivo

PERO el bit de signo sólo tiene sentido si estamos trabajando con números con signo. Si consideramos que se trata de números sin signo, este bit entonces ya pierde su sifnificado. Esta interpretación es la que decidimos nosotros al programar

Aumentando el tamaño de un dato

Muchas veces ocurre que estamos trabajando con datos de un tamaño pequeño, por ejemplo bytes, y queremos realizar una operación con otro dato de un tamaño mayor (por ejemplo una media-palabra, o una palabra). Lo que se hace es convertir el dato de tamaño pequeño al tamaño del grande, y una vez que tienen el mismo tamaño, se hace la operación

Dicho de otra manera. Todas las operaciones que se realizan en la cpu, se aplican a datos del mismo tamaño (que en el RISC que estamos utilizando son de 32-bits). Pero ¿cómo podemos sumar un byte a una palabra?

Como ejemplo, supongamos que queremos sumar la palabra 0x12345678 con el byte 0x80. Lo que tenemos que hacer es convertir ese número 0x80 en uno de igual valor pero de tamaño 32-bits

Esta operación de cambio de tamaño depende de cómo interpretemos este byte: con signo o sin signo

Conversion de un byte sin signo a una palabra

Si estamos trabajando con bytes sin signo, ampliamos el dato añadiendo 24-bits a 0 como bits de mayor peso. De esta forma tenemos el nuevo dato con un tamaño mayor (32-bits) pero su valor no ha cambiado. Sigue siendo 0x80 (128 en decimal)

Extensión del signo

Si estamos trabajando con bytes con signo, esta operación de ampliación del tamaño se denomina extensión del signo. Hay que mirar el bit de signo del byte (el bit de mayor peso) y copiarlo en los bits de mayor peso hasta alcanzar el tamaño grande.

En el ejemplo que hemos puesto, hay que añadir 24-bits a 1, porque 0x80 es un número negativo (su bit de signo es 1). El valor que representa es -128. Al extender el signo, el nuevo número que aparece sigue siendo el -128, aunque ahora tiene más bits

Leyendo bytes y medias-palabras de memoria

En todas las instrucciones del RISCV, incluidas las instrucciones de load, se presupone que los datos tienen signo. Y como los registros son de 32-bits, se supone que el tamaño natural son 32-bits. Cualquier valor que sea menor a 32-bits (bytes y medias-palabras), tiene que ser ampliado a 32 bits antes de ser procesado por la instrucción

Cuando estamos leyendo de memoria una palabra, en realidad nos da igual si consideramos o no su signo. Su valor de 32-bits se transfiere tal cual al registro destino

PERO si estamos leyendo un valor menor a 32-bits, antes de asignárselo al registro destino hay que ampliarlo y por tanto hay que añadir bits de mayor peso. Estos bits dependen de la instrucción de load usada

Leyendo con lb y lh

Las instrucciones lb y lh suponen que el dato leido de memoria TIENE SIGNO, por lo tanto realizan una EXTENSIÓN DEL SIGNO y el resultado lo asignan al registro destino

Vamos a ver algunos ejemplos. Empezamos leyendo los siguientes bytes: 0x80 y 0x7F. El primero es negativo, y por tanto su bit de signo (de mayor peso) es 1, mientras que el segundo es positivo y su bit de signo es 0

Si usamos la instrucción lb para leer ambos bytes, lo que obtenemos en el registro destino son los valores de 32-bits siguientes: 0xFFFFFF80 y 0x0000007F:

Otro ejemplo con medias-palabras. Se leen los valores 0xC012 y 0x4321. El primero es negativo y el segundo positivo. Al utilizar la instrucción lh para leerlos, se extiende el signo y se obtienen los valores 0xFFFFC012 y 0x00004321 respectivamente

En este programa se realizan las lecturas mostradas en los ejemplos, para poder comprobarlo con el RARs:

	.data
	
	#-- Variables a leer
var:	.byte 0x80
	.byte 0x7F
	.half 0xC012
	.half 0x4321

	.text
	
	#-- t0: Puntero a las variables
	la t0, var
	
	#-- Lectura de bytes
	lb t1, 0(t0)
	lb t2, 1(t0)
	
	#-- Lectura de medias-palabras
	lh t3, 2(t0)
	lh t4, 4(t0)

Al ejecutar el programa se pueden ver en los registros t1 y t2 los bytes leidos (con los signos extendidos), y en t3 y t4 las medias palabras (también con los signos extendidos)

Instrucciones lbu y lhu

La instrucción lbu (load byte unsigned) supone que el byte a leer no tiene signo, y por tanto los bits de mayor peso de relleno son siempre 0. De igual modo, la instrucción lhu (load half word unsigned) lee los 16 bits y rellena los 16-bits de mayor peso con 0

Si ahora leemos los bytes 0x80 y 0x7F lo que se obtiene son los valores 0x00000080 y 0x0000007F respectivamente

Si leemos las medias palabras 0xC012 y 0x4321, lo que se obtiene es 0x00C012 y 0x00004321 respectivamente

Comparando números

Las operaciones de comparación que se implementan en el RISC-V son < y >=. A partir de ellas se pueden implementar el resto: <= y >. Estas comparaciones se utilizan para comparar números con signo y números sin signo

Numeros con signo: blt y bge

Las instrucciones de comparación de números blt (menor que) y bge (mayor o igual que) funcionan con números de 32 bits con signo. En el siguiente programa de ejemplo se compara el número 0x70000000 con 0x80000000, 0x60000000 y 0x7F000000 utilizando blt

Nos fijamos primero en esta figura donde se muestra el resultado de la comparación. El número 0x80000000 es negativo, porque su bit de signo está a 1, y por eso es menor que 0x70000000

Este es el programa para realizar la comparación. Se ha creado la rutina cmp_signed para comparar dos números con signos, usando blt, e imprimir el resultado de la comparación

#-------------------------------------
#-- Comparacion de numeros con signo
#-- Ejemplo de uso de blt
#-------------------------------------

	.include "servicios.s"

	.eqv M 0x70000000
	.eqv N1 0x80000000
	.eqv N2 0x60000000
	.eqv N3 0x7F000000

	.data
menor:	.string " < "
mayor:  .string " >= "
msg:    .string "NUMEROS CON SIGNO:\n"

	.text
	
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- Numero principal a comparar con los otros
	li a0, M
	li a1, N1
	
	#-- Comparar M con N1
	jal cmp_signed
	
	#-- Comparar M con N2
	li a0, M
	li a1, N2
	jal cmp_signed
	
	
	#-- Comparar M con N3
	li a0, M
	li a1, N3
	jal cmp_signed
	
	#-- Terminar
	li a7, EXIT
	ecall
	
	
#----------------------------
#-- Funcion de comparacion 
#-- CMP(a, b)
#----------------------------
cmp_signed:
	#-- Guardar argumentos en t0 y t1
	mv t0, a0
	mv t1, a1

	#-- Imprimir primer numero
	mv a0, t0
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Comparar numeros
	blt t0, t1, a_menor_b

	#-- Es mayor o igual
	la a0, mayor
	li a7, PRINT_STRING
	ecall
	
	j cont
			
a_menor_b:
	la a0, menor
	li a7, PRINT_STRING
	ecall

cont:	
	#-- Imprimir N1
	mv a0, t1
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Terminar
	ret

Este es el resultado al ejecutarlo:

Numeros sin signo: bltu y bgeu

Las instrucciones bltu y bgeu realizan las mismas comparaciones que blt y bge, PERO utilizan números sin signo (la u final significa unsigned). Es decir, no tienen en cuenta los bits de signo. Con estas instrucciones estos son los resultados de las comparaciones, utilizando los mismos números del ejemplo anterior:

Ahora el valor 0x70000000 es menor que 0x80000000, ya que el signo no cuenta (ambos valores son positivos)

#-------------------------------------
#-- Comparacion de numeros SIN signo
#-- Ejemplo de uso de bltu
#-------------------------------------

	.include "servicios.s"

	.eqv M 0x70000000
	.eqv N1 0x80000000
	.eqv N2 0x60000000
	.eqv N3 0x7F000000

	.data
menor:	.string " < "
mayor:  .string " >= "
msg:    .string "NUMEROS SIN SIGNO:\n"

	.text
	
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- Numero principal a comparar con los otros
	li a0, M
	li a1, N1
	
	#-- Comparar M con N1
	jal cmp_unsigned
	
	#-- Comparar M con N2
	li a0, M
	li a1, N2
	jal cmp_unsigned
	
	
	#-- Comparar M con N3
	li a0, M
	li a1, N3
	jal cmp_unsigned
	
	#-- Terminar
	li a7, EXIT
	ecall
	
	
#----------------------------
#-- Funcion de comparacion 
#-- CMP(a, b)
#----------------------------
cmp_unsigned:
	#-- Guardar argumentos en t0 y t1
	mv t0, a0
	mv t1, a1

	#-- Imprimir primer numero
	mv a0, t0
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Comparar numeros
	bltu t0, t1, a_menor_b

	#-- Es mayor o igual
	la a0, mayor
	li a7, PRINT_STRING
	ecall
	
	j cont
			
a_menor_b:
	la a0, menor
	li a7, PRINT_STRING
	ecall

cont:	
	#-- Imprimir N1
	mv a0, t1
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Terminar
	ret

Este es el resultado:

Comparación con asignación: Instrucciones slt

Las comparaciones también se pueden realizar con las instrucciones de la familia slt: slt, slti, sltu y sltiu. Tomaremos como representante la instrucción slt

En esta instrucción se comparan dos registros fuente (usando la relación menor que <) y se asigna el valor 1 al registro destino si la relación se cumple, y 0 en caso contrario. Es similar a blt, pero se asigna el valor 0 ó 1 en vez de realizarse el salto condicional

El formato de esta instrucción es: slt rd, rs1, rs2. La acción a realizar la describimos como: rd = (rs1 < rs2)

La instrucción slti es la versión con valor inmediato en vez del registro fuente rs2. Su formato es: slti rd, rs1, imm. La acción a realizar es rd = (rs1 < imm)

Números con signo: Instrucciones slt y slti

Las instrucciones slt y slti funcionan con números con signo, igual que la instrucción blt En este programa se imprimen los resultados de comparar 0x70000000 con 0x80000000, 0x60000000 y 0x7F000000

#----------------------------------
#-- Ejemplo de la instruccion SLT 
#----------------------------------

	.include "servicios.s"

	#-- Valores para comparar
	.eqv M 0x70000000
	.eqv N1 0x80000000
	.eqv N2 0x60000000
	.eqv N3 0x7F000000
	
	.data
msg1:	.string " < "
msg2:   .string " ("
msg3:   .string ")\n"
	
	.text
	
	li a0, M
	li a1, N1
	jal print_slt
	
	li a0, M
	li a1, N2
	jal print_slt
	
	li a0, M
	li a1, N3
	jal print_slt
	
	#-- Terminar
	li a7, EXIT
	ecall	
	
	
#------------------------------------------------
#-- Mostrar el resultado de la operacion SLT
#-- entre dos valores
#-- PRINT_SLT(a,b)
#------------------------------------------------
print_slt:
	#-- Guardar los parametros
	mv t0, a0
	mv t1, a1

	#-- Imprimir el primer numero
	mv a0, t0
	li a7, PRINT_INT_HEX
	ecall

	#-- Imprimir <
	la a0, msg1	
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir segundo numero
	mv a0, t1
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Imprimir (
	la a0, msg2
	li a7, PRINT_STRING
	ecall
	
	#-- Calcular resultado de slt
	slt t2, t0, t1
	
	#-- Imprimir resultado
	mv a0, t2
	li a7, PRINT_INT
	ecall
	
	#-- Imprimir )
	la a0, msg3
	li a7, PRINT_STRING
	ecall
	
	ret

Al ejecutarlo este es el resultado obtenido. Entre paréntesis se dibuja el valor booleano resultado de aplicar la comparación menor que. Como la comparación es con números con signo, el valor 0x70000000 NO es menor a 0x80000000

En este programa se muestra el funcionamiento de slti. Se utilizan los valores inmediatos -1, 5 y 20 para compararse con el registro t0 que vale 10

#----------------------------------
#-- Ejemplo de la instruccion SLTI 
#----------------------------------

	.include "servicios.s"

	#-- Valores para comparar
	.eqv M 10
	.eqv N1 -1
	.eqv N2 5
	.eqv N3 20
	
	.data
msg1:	.string "t0 = "
msg2:   .string "slt t1, t0, "
msg3:   .string " ---> "
	
	.text
	
	li t0, M
	
	la a0, msg1
	li a7, PRINT_STRING
	ecall
	
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	slti t6, t0, N1
	li a0, N1
	mv a1, t6
	jal print_slti
	
	slti t6, t0, N2
	li a0, N2
	mv a1, t6
	jal print_slti
	
	slti t6, t0, N3
	li a0, N3
	mv a1, t6
	jal print_slti
	
	
	#-- Terminar
	li a7, EXIT
	ecall	
	
	
#------------------------------------------------
#-- Mostrar el resultado de la operacion SLTI
#-- entre dos valores
#-- PRINT_SLTI(a,b)
#-- a--> Valor inmediato
#-- b--> Resultado de la operacion
#------------------------------------------------
print_slti:
	#-- Guardar los parametros
	mv t0, a0
	mv t1, a1

	#-- Imprimir slt t1, t0, 
	la a0, msg2
	li a7, PRINT_STRING
	ecall

	#-- Imprimir valor inmediato
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#-- Imprimir -->
	la a0, msg3
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir resultado
	mv a0, t1
	li a7, PRINT_INT
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	ret

Este es el resultado:

Caso de uso 1: Obtener el bit de signo

Uno de los usos de slt es obtener el bit de signo, y determinar si un número es positivo o negativo. Para ello basta con realizar la comparación con zero. Si es menor que zero el registro destino se pone a 1. La instrucción a utilizar es:

  slt t0, t1, zero

En caso de que t1 tenga un número negativo, el registro t0 contendrá el valor 1. En caso contrario tendrá el valor 0

Lo comprobamos en este programa de pruebas, con los valores -1, 10 y 0x80000000

	.include "servicios.s"
	
	.data
msg:	.string " --> Bit de signo: "
	
	.text
	
	#-- Imprimir el signo de los numeros de prueba
	li a0, -1
	jal print_sign_bit
	
	li a0, 10
	jal print_sign_bit
	
	li a0, 0x80000000
	jal print_sign_bit
	
	#-- Terminar
	li a7, EXIT
	ecall
	
	
#----------------------------------
#-- Imprimir el bit de signo
#-- print_sign_bit(a)
#-- a: valor a comprobar el signo
#----------------------------------
print_sign_bit:
	
	#-- t1: Numero a comprobar
	mv t1, a0
	
	#-- t0: Bit de signo
	slt t0, t1, zero
	
	#-- Imprimir el numero
	mv a0, t1
	li a7, PRINT_INT
	ecall
	
	#-- Imprimir mensaje
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir el bit de signo
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	ret

Este es el resultado:

Caso de uso 2: Contador con valor máximo

Supongamos que tenemos un contador que se incrementa de 1 en 1 mientras su valor no haya alcanzado el máximo. Cuando se alcanza este valor máximo, el contador ya no se puede incrementar más

Con la instrucción slt (o slti) podemos calcular fácilmente el incremento a aplicar al contador: 1 (contando) ó 0 (parado)

El equivalente en un lenguaje de alto nivel, como C, sería: if (a < b) c++. Es un incremento condicional

En este programa de ejemplo se incrementa un contador desde -10 hasta 10 (que es su valor máximo). El incremento se calcula con la instrucción slt t1, t0, t6, donde t0 es el valor actual del contador, t6 su valor máximo y ponemos en t1 el incremento. En cada pasada del bucle, el nuevo valor del contador se calcula con add t0, t0, t1. Cuando se ha alcanzado el máximo, t1 vale 0 por lo que ya no se incrementará más

Este es el código:

# ------------------------------------------------------
# -- Ejemplo de la instruccion SLT 
# -- Contador que se incrementa hasta un valor máximo
# -- El programa no termina nunca, se queda en bucle
# -- infinito
#-------------------------------------------------------

	.include "servicios.s"
	
	#-- Valor minimo del contador (donde empieza)
	.eqv MIN -10
	
	#-- Valor máximo del contador
	.eqv MAX 10
	
	.text
	
	#-- Inicializar contador con el valor minimo
	li t0, MIN
	
	#-- t6: Valor maximo
	li t6, MAX
	
inf:
	#-- Imprimir valor actual del contador
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Esperar (en ms)
	li a0, 300
	li a7, SLEEP
	ecall
	
	#-- Calcular incremento del contador
	slt t1, t0, t6
	
	#-- Incrementar contador
	add t0, t0, t1
	
	#-- Bucle infinito
	j inf 

Esto es lo que vemos en la consola del RARs:

En esta animación lo vemos en funcionamiento. Se utiliza el servicio SLEEP del sistema operativo para mostrar periódicamente el valor del contador. Observamos que efectivamente, al llegar a su valor máximo de 10, ya no se incrementa más, y permanece con ese valor indefinidamente

Números sin signo: Instrucciones sltu, sltiu

Las instrucciones sltu y sltiu funcionan con números SIN signo, igual que la instrucción bltu

Estos son sus formatos de instrucción:

En este programa se imprimen los resultados de comparar 0x70000000 con 0x80000000, 0x60000000 y 0x7F000000 (mismo ejemplo que con la instrucción slt), pero ahora sin tener en cuenta el bit de signo

#----------------------------------
#-- Ejemplo de la instruccion SLTU 
#----------------------------------

	.include "servicios.s"

	#-- Valores para comparar
	.eqv M 0x70000000
	.eqv N1 0x80000000
	.eqv N2 0x60000000
	.eqv N3 0x7F000000
	
	.data
msg1:	.string " < "
msg2:   .string " ("
msg3:   .string ")\n"
	
	.text
	
	li a0, M
	li a1, N1
	jal print_sltu
	
	li a0, M
	li a1, N2
	jal print_sltu
	
	li a0, M
	li a1, N3
	jal print_sltu
	
	#-- Terminar
	li a7, EXIT
	ecall	
	
	
#------------------------------------------------
#-- Mostrar el resultado de la operacion SLTU
#-- entre dos valores
#-- PRINT_SLTU(a,b)
#------------------------------------------------
print_sltu:
	#-- Guardar los parametros
	mv t0, a0
	mv t1, a1

	#-- Imprimir el primer numero
	mv a0, t0
	li a7, PRINT_INT_HEX
	ecall

	#-- Imprimir <
	la a0, msg1	
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir segundo numero
	mv a0, t1
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Imprimir (
	la a0, msg2
	li a7, PRINT_STRING
	ecall
	
	#-- Calcular resultado de sltu
	sltu t2, t0, t1
	
	#-- Imprimir resultado
	mv a0, t2
	li a7, PRINT_INT
	ecall
	
	#-- Imprimir )
	la a0, msg3
	li a7, PRINT_STRING
	ecall
	
	ret

Ahora sí que se cumple que 0x70000000 < 0x80000000

Este es el resultado al ejecutar el programa:

Caso de uso 3: Obtención del acarreo

La instrucción sltu es muy útil para calcular el acarreo al sumar números de 32-bits. Supongamos que tenemos en t0 y t1 los dos operandos a sumar. El resultado lo dejamos en t2

Al sumar ambos números, sin signo, puede ocurrir que el resultado exceda los 32-bits. En este caso se genera un bit de acarreo. Con la instrucción sltu se calcula fácilmente. Sabemos que hay acarreo si la suma, que está en t2, es menor que cualquiera de los operandos. Podemos utilizar el operando que queramos, por ejemplo t0

    #-- t2 = t0 + t1: Calcular la suma
    add t2, t0, t1

   #-- Obtener el acarreo en t3: 0 ó 1
   sltu t3, t2, t0

También se podría calcular el acarreo con esta otra instrucción: sltu t3, t2, t1. Es equivalente

Este programa calcula el acarreo de diferentes operaciones

#-------------------------------------------
#-- Ejemplo de la instruccion SLTIU
#-- Calculo del acarreo
#-------------------------------------------

	.include "servicios.s"
	
	.data
	
	#-- Mensajes
msg1:	.string " + "
msg2:   .string " = "
msg3:   .string " ---> Acarreo: "

	#-- Operandos a utilizar
op1:	.word 0xFFFFFFFF, 0xF0000000, 0xFFFFFFFF, 0x70000000, 0xF0000000, 0xAAAAAAAA
op2:	.word 0xFFFFFFFF, 0x10000000, 0x00000001, 0x80000000, 0X0FFFFFFF, 0x55555555
	
	.text
	
	#-- Direccion de acceso a las listas de operandos
	la s1, op1
	la s2, op2
	
	#-- Cantidad de operaciones a realizar
	li s0, 6
	
bucle:
	
	#-- Leer los dos operando
	lw t0, 0(s1)
	lw t1, 0(s2)
	
	#-- t3 = op1 + op2
	add t2, t1, t0
	
	#-- Calcular acarreo
	#-- Opcion 1
	sltu t3, t2, t0
	#-- Opcion 2 (También seria valido)
	#sltu t3, t2, t1
	
	#-- Imprimir la operacion
	mv a0, t0
	mv a1, t1
	mv a2, t2
	mv a3, t3
	jal print_operation
	

	
	#-- Apuntar a los siguientes operandos
	addi s1, s1, 4
	addi s2, s2, 4
	
	#-- Una operacion menos
	addi s0, s0, -1
	
	#-- Comproboar si quedan mas operaciones o no
	bgt s0, zero, bucle
	
	
	#-- Terminar
	li a7, EXIT
	ecall
	

#-----------------------------------
#-- Imprimir la operacion
#-- ENTRADAS:
#--   * a0: Operando 1
#--   * a1: Operando 2
#--   * a2: suma
#--   * a4: Acarreo
#----------------------------------	
print_operation:

	#-- Leer argumentos
	mv t0, a0
	mv t1, a1
	mv t2, a2
	mv t3, a3

	#-- Imprimir operando 1
	mv a0, t0
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Imprimir " + "
	la a0, msg1
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir operando 2
	mv a0, t1
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Imprimir " = "
	la a0, msg2
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir resultado
	mv a0, t2
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Imprimir acarreo
	la a0, msg3
	li a7, PRINT_STRING
	ecall
	
	mv a0, t3
	li a7, PRINT_INT
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	ret

Este es el resultado:

Caso de uso 4: Suma de números de 32-bits

Cuando se suman números de 32-bits, el resultado es de 33-bits. Este bit adicional, el de mayor peso, es precisamente el bit de acarreo. Si en el registro t2 tenemos la suma y en t3 el acarreo, el número resultante es la concatenación t3 t2

Este ejemplo es similar al anterior, pero escribiendo el resultado como dos números de 32-bits

#-------------------------------------------
#-- Ejemplo de la instruccion SLTIU
#-- Calculo de numeros de 32-bits
#-------------------------------------------

	.include "servicios.s"
	
	.data
	
	#-- Mensajes
msg1:	.string " + "
msg2:   .string " = "

	#-- Operandos a utilizar
op1:	.word 0xFFFFFFFF, 0xF0000000, 0xFFFFFFFF, 0x70000000, 0xF0000000, 0xAAAAAAAA
op2:	.word 0xFFFFFFFF, 0x10000000, 0x00000001, 0x80000000, 0X0FFFFFFF, 0x55555555
	
	.text
	
	#-- Direccion de acceso a las listas de operandos
	la s1, op1
	la s2, op2
	
	#-- Cantidad de operaciones a realizar
	li s0, 6
	
bucle:
	
	#-- Leer los dos operando
	lw t0, 0(s1)
	lw t1, 0(s2)
	
	#-- t3 = op1 + op2
	add t2, t1, t0
	
	#-- Calcular acarreo
	#-- Opcion 1
	sltu t3, t2, t0
	#-- Opcion 2 (También seria valido)
	#sltu t3, t2, t1
	
	#-- Imprimir la operacion
	mv a0, t0
	mv a1, t1
	mv a2, t2
	mv a3, t3
	jal print_operation
	

	
	#-- Apuntar a los siguientes operandos
	addi s1, s1, 4
	addi s2, s2, 4
	
	#-- Una operacion menos
	addi s0, s0, -1
	
	#-- Comproboar si quedan mas operaciones o no
	bgt s0, zero, bucle
	
	
	#-- Terminar
	li a7, EXIT
	ecall
	

#-----------------------------------
#-- Imprimir la operacion
#-- ENTRADAS:
#--   * a0: Operando 1
#--   * a1: Operando 2
#--   * a2: suma
#--   * a4: Acarreo
#----------------------------------	
print_operation:

	#-- Leer argumentos
	mv t0, a0
	mv t1, a1
	mv t2, a2
	mv t3, a3

	#-- Imprimir operando 1
	mv a0, t0
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Imprimir " + "
	la a0, msg1
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir operando 2
	mv a0, t1
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Imprimir " = "
	la a0, msg2
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir los 32-bits de mayor peso (acarreo)
	mv a0, t3
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir los 32-bits de menor peso
	mv a0, t2
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	ret

Este es el resultado:

Caso de uso 5: Suma de números de 64-bits

El cálculo del acarreo nos sirve para realizar la suma de números de 64bits, a partir de registros de 32-bits. En esta figura se muestran los registros usados, sus valores, el resultado intermedio y resultado final de la suma de dos valores de 64-bits

Esta es la operación en python, para comprobar el resultado:

hex(0xCAFEBACA_1F0055AA + 0x00BECAF5_F0000000)

Este es el resultado:

>>> hex(0xCAFEBACA_1F0055AA + 0x00BECAF5_F0000000)
'0xcbbd85c00f0055aa'
>>> 

Desplazamiento de bits

Con las operaciones de desplazamiento movemos todos los bits de un registro hacia la izquierda o la derecha, una cantidad determinada de posiciones (de 0 a 31). Estas operaciones reciben el nombre de desplazamientos lógicos

Desplazamiento lógico a la izquierda: sll, slli

El desplazamiento lógico a la izquierda se realiza con las instrucciones sll (Shift left logical) y slli (Shift left logical inmediate). En este desplazamiento, los bits de mayor peso se pierden y por la derecha entran ceros. El resto de bits se mueven hacia la izquierda, aumentando sus pesos

En este ejemplo se muestra el desplazamiento de 2 bits a la izquierda de un valor inicial. El valor final pierde sus dos bits de mayor peso, que son machacados por los bits de pesos 29 y 28. Como bits de menor peso (bits 0 y 1) se añaden ceros

La instrucción sll desplaza hacia la izquierda el valor contenido en rs1 el número de bits indicado en rs2 y el resultado se deja en rd. Este es su formato: sll rd, rs1, rs2 y esta la operación realizada: rd = rs1 << rs2

La instrucción slli es similar, pero en vez del registro rs2 se usa un valor inmediato de 5 bits, que permite realizar desplazamientos desde 0 hasta 31 bits

En este ejemplo se utilizan las instrucciones sll y slli para desplazar dos números de 32-bits hacia la izquierda. Primero se desplaza el número 0xD0003003 2 bits hacia la izquierda y luego el 0x00000001 un bit hacia la izquierda. En la consola se imprimen los valores iniciales y los finales tras el desplazamiento

#----------------------------------------------------
#-- Ejemplo de uso de las instrucciones SLL, SLLI
#----------------------------------------------------
		
	.include "servicios.s"
	
	.text
	
	#-- Numero inicial
	li t0, 0xD0003003
	
	#-- Numero de bits a desplazar
	li t1, 2
	
	#-- Desplazar t0 la cantidad de bits
	#-- indicados en t1. El resultado se guarda en t2
	sll t2, t0, t1
	
	#-- Imprimir resulado
	mv a0, t0
	mv a1, t2
	jal print_shift
	
	#-- Numero inicial
	li t0, 0x01
	
	#-- Desplazar t0 un bit a la izqueida
	#-- El resultado se guarda en t2
	slli t2, t0, 1
	
	#-- Imprimir resultado
	mv a0, t0
	mv a1, t2
	jal print_shift
	
	#-- Terminar
	li a7, EXIT
	ecall
	
	
	
#---------------------------------------
#--  PRINT_SHIFT
#--  ENTRADAS:
#--    * a0: Valor inicial
#--    * a1: Valor desplazado
#---------------------------------------
print_shift:

	mv t0, a0
	mv t1, a1

	#-- Imprimir valor inicial en binario
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir valor final en binario
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	ret

Este es el resultado:

Caso de uso 1: Multiplicación por potencias de 2

Cada vez que se hace un desplazamiento de 1 bit hacia la izquierda, se está multiplicando el número por 2. Si se hacen 2 desplazamientos, se multiplica por 2*2 = 4. De manera genérica, si se hace un desplazamiento de n bits hacia la izquierda, se está multiplicando por $2^n$

Esto es muy interesante si estamos trabajando con el RISCV32I, que NO tiene multiplicaciones por hardware. Con las instrucciones sll y slli podemos multiplicar cualquier número por una potencia de 2

En este ejemplo se muestran las 10 primeras potencias de 2

#----------------------------------------
#-- Mostrar las potencias de 2, mediante
#-- desplazamientos a la izquierda
#----------------------------------------

	.include "servicios.s"
	
	#-- Potencia hasta la que llegar
	.eqv MAX 10	
	
	.data
msg:	.string ":  "
	
	.text
	
	#-- Valor inicial
	li t0, 1
	
	#-- Inicializar contador
	li t1, 0

bucle:	
	#-- Imprimir potencia actual
	mv a0, t1
	li a7, PRINT_INT
	ecall
	
	la a0, msg
	li a7, PRINT_STRING
	ecall

	#-- Imprimir el valor actual
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Desplazar a la izquierda t0 un bit
	#-- (multiplicar por 2)
	slli t0, t0, 1
	
	#-- Incrementar contador
	addi t1, t1, 1
	
	#-- Repetir hasta que se llegue
	#-- al valor maximo
	li t2, MAX
	ble t1, t2, bucle
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

Caso de uso 2: Multiplicación por 10

Aunque no dispongamos de la multiplicación por hardware (instrucción mul), gracias a las instrucciones de desplazamiento podemos multiplicar por otros números aunque no sean potencias de dos, combinándolas con sumas

Vamos a ver un ejemplo: cómo multiplicar por 10. Primero lo descomponemos en sus factores primos: 10 = 5*2. Si logramos obtener el resultado de multiplicar por 5, basta con añadir un desplazamiento de 1 bit a la izquierda para multiplicar por 2

Por tanto lo que tenemos que lograr es multiplicar un número por 5. El número 5 lo descomponemos en potencias de 2: 5 = 4 + 1. Si lo que queremos calcular es 5a, lo podemos descomponer así: 5a = (4 + 1)a = 4a + a. El primer término es una multiplicación por potencia de 2, por tanto lo hacemos con un desplazamiento. A este término le sumamos a, con lo que obtenemos 5a

Si ahora 5a lo desplazamos a la izquierda un bit, obtenemos 10a

También lo podemos hacer directamente, descomponiendo en potencias de 2 el número 10. 10a = (8 + 2)a = 8a + 2a

El código para realizar esta operación es:

.text
	
#-- Valor inicial
li t0, VALOR
	
#-- Calculo a realizar
#-- 10*t0 = (8*t0 + 2*t0)
	
#-- 8*t0 = 2^3*t0
slli t1, t0, 3
	
#-- 2*t0
slli t2, t0, 1
	
#--Resultado final
add t3, t1, t2

En este ejemplo se cuenta de 1 hasta 10, y se multiplica el contador por 10, para comprobar que la operacion es correcta

#---------------------------------------
#-- Multiplicar los numeros de 1 al 10
#-- por 10
#---------------------------------------

	.include "servicios.s"

	#-- Valor maximo del contador
	.eqv MAX 10
	
	.data
msg:	.string " * 10 = "
	
	.text
	
	#-- Inicializar contador
	li t0, 0
	
bucle:
	#-- Incrementar contador
	addi t0, t0, 1
	
	#------ Multiplicar el contador por 10
	
	#-- Calculo a realizar
	#-- 10t0 = (8t0 + 2t0)
	
	#-- 8t0 = 2^3t0
	slli t1, t0, 3
	
	#-- 2t0
	slli t2, t0, 1
	
	#--Resultado final
	add t3, t1, t2
	
	#-- Imprimir contador
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#-- Imprimir cadena
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir el contador * 10
	mv a0, t3
	li a7, PRINT_INT
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Condicion de salida
	li t4, MAX
	blt t0, t4, bucle 
	
	#-- Terminar
	li a7, EXIT
	ecall

Esto es lo que obtenemos al ejecutarlo:

Caso de uso 3: Contador one-hot

La codificación one-hot se usa mucho en electrónica digital. Es un tipo de sistema unario en el que hay un bit que se va desplazando. Lo importante es que en estos números sólo hay un bit activo, estando el resto a 0

Con las instrucciones de desplazamiento a la izquierda es muy sencillo iterar sobre los diferentes valores de la codificación one-hot

En este ejemplo se implementa un contador one-hot, mostrándose los primeros 10 valores

#-----------------------------------
#-- Contador one-hot
#-----------------------------------

	.include "servicios.s"

	#-- Valor maximo del contador
	.eqv MAX 10

	.text
	
	#-- Inicializar contador one-hot
	li t0, 1
	
	#-- Contador decimal
	li t1, 0
	
bucle:
	#-- Incrementar contador decimal
	addi t1, t1, 1
	
	#-- Imprimir contador en binario
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Desplazar un bit a la izquierda
	#-- Incrementar contador one-hot
	slli t0, t0, 1
	
	#-- Repetir
	li t2, MAX
	blt t1, t2, bucle
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado al ejecutarlo:

Caso de uso 4: Contador unario

El Sistema unario es la forma más básica y sencilla de contar. Sólo utiliza un único símbolo, que llamamos marca. Cuando se usa el sistema binario para representar unario, el 1 se usa como marca, y 0 es la ausencia de marca

Para contar en unario hay que inyectar marcas por la derecha (unos), mediante la instrucción addi, tras el desplazamiento hacia la izquierda

En este ejemplo se cuenta en unario de 1 al 10

#-----------------------------------
#-- Contador unario
#-----------------------------------

	.include "servicios.s"

	#-- Valor maximo del contador
	.eqv MAX 10

	.text
	
	#-- Inicializar contador unario
	li t0, 0
	
	#-- Contador decimal
	li t1, 0
	
bucle:
	#-- Incrementar contador decimal
	addi t1, t1, 1
	
	#-- Imprimir contador en binario
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Desplazar un bit a la izquierda
	slli t0, t0, 1
	
	#-- Añadir un '1' como bit de menor peso
	addi t0, t0, 1
	
	#-- Repetir
	li t2, MAX
	ble t1, t2, bucle
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado al ejecutarlo:

Caso de uso 5: Posicionamiento de máscaras

Para extraer campos de una palabra utilizamos un valor que llamamos máscara y realizamos operaciones de manipulación de bits, que veremos en el apartado final de esta sesión

Con la operación de desplazamiento a la izquierda partimos de un valor unario y lo colocamos en la misma posición del campo a extraer

Este valor desplazado es lo que llamamos una máscara

Este es un ejemplo de código donde generamos la máscara:

#-----------------------------
#-- Generacion de una mascara
#-----------------------------	
	.include "servicios.s"

	#-- Posicion del campo
	.eqv POS 8
	
	.text
	
	#-- Valor unario: Tamaño de 5 bits
	li t1, 0x1F
	
	#-- Obtener la mascara mediante desplazamiento
	#-- a la izquierda
	slli t1, t1, POS
	
	#-- Imprimir mascara en binario
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

Desplazamiento lógico a la derecha: srl, srli

El desplazamiento lógico a la derecha se realiza con las instrucciones srl (Shift right logical) y srli (Shift right logical inmediate). En este desplazamiento, los bits de menor peso se pierden y por la izquierda entran ceros. El resto de bits se mueven hacia la derecha, disminuyendo sus pesos

En este ejemplo se muestra el desplazamiento de 2 bits a la derecha de un valor inicial. El valor final pierde sus dos bits de menor peso, que son machacados por los bits de pesos 3 y 2. Como bits de mayor peso (bits 31 y 30) se añaden ceros

La instrucción srl desplaza hacia la derecha el valor contenido en rs1 el número de bits indicado en rs2 y el resultado se deja en rd. Este es su formato: srl rd, rs1, rs2 y esta la operación realizada: rd = rs1 >> rs2

La instrucción srli es similar, pero en vez del registro rs2 se usa un valor inmediato de 5 bits, que permite realizar desplazamientos desde 0 hasta 31 bits

En este ejemplo se utilizan las instrucciones srl y srli para desplazar dos números de 32-bits hacia la derecha. Primero se desplaza el número 0xD0003003 2 bits hacia la derecha y luego el 0x10000000 un bit hacia la derecha. En la consola se imprimen los valores iniciales y los finales tras el desplazamiento

#----------------------------------------------------
#-- Ejemplo de uso de las instrucciones SRL, SRLI
#----------------------------------------------------
		
	.include "servicios.s"
	
	.text
	
	#-- Numero inicial
	li t0, 0xD0003003
	
	#-- Numero de bits a desplazar
	li t1, 2
	
	#-- Desplazar t0 la cantidad de bits
	#-- indicados en t1. El resultado se guarda en t2
	srl t2, t0, t1
	
	#-- Imprimir resulado
	mv a0, t0
	mv a1, t2
	jal print_shift
	
	#-- Numero inicial
	lui t0, 0x80000
	
	#-- Desplazar t0 un bit a la izqueida
	#-- El resultado se guarda en t2
	srli t2, t0, 1
	
	#-- Imprimir resultado
	mv a0, t0
	mv a1, t2
	jal print_shift
	
	#-- Terminar
	li a7, EXIT
	ecall
	
	
	
#---------------------------------------
#--  PRINT_SHIFT
#--  ENTRADAS:
#--    * a0: Valor inicial
#--    * a1: Valor desplazado
#---------------------------------------
print_shift:

	mv t0, a0
	mv t1, a1

	#-- Imprimir valor inicial en binario
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir valor final en binario
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	ret

Este es el resultado:

Caso de uso 1: División por potencias de 2

Cada vez que se hace un desplazamiento de 1 bit hacia la derecha, se está dividiendo el número por 2. Si se hacen 2 desplazamientos, se divide por 2*2 = 4. De manera genérica, si se hace un desplazamiento de n bits hacia la derecha, se está dividiendo por $2^n$

Esto es muy interesante si estamos trabajando con el RISCV32I, que NO tiene divisiones por hardware. Con las instrucciones srl y srli podemos dividir cualquier número por una potencia de 2

En este ejemplo se relizan 10 divisiones entre 2, partiendo del número 1024:

#----------------------------------------
#-- Mostrar la division entre potencias
#-- de 2, mediante desplazamientos a la derecha
#----------------------------------------

	.include "servicios.s"
	
	#-- Numero maximo de divisiones
	#-- entre 2
	.eqv MAX 10	
	
	.data
msg:	.string ":  "
	
	.text
	
	#-- Valor inicial
	li t0, 1024
	
	#-- Inicializar contador
	li t1, 0

bucle:	
	#-- Imprimir numero de iteracion
	mv a0, t1
	li a7, PRINT_INT
	ecall
	
	la a0, msg
	li a7, PRINT_STRING
	ecall

	#-- Imprimir el valor actual
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Desplazar a la derecha t0 un bit
	#-- (dividir por 2)
	srli t0, t0, 1
	
	#-- Incrementar contador
	addi t1, t1, 1
	
	#-- Repetir hasta que se llegue
	#-- al valor maximo
	li t2, MAX
	ble t1, t2, bucle
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

Caso de uso 2: Acceso a campos

Para acceder a un campo de bits situado en una posición determinada dentro de un regitro, utilizamos el desplazamiento a la derecha para llevarlo a las posiciones de menor peso

Para obtener el valor hay que realizar operaciones con los bits, que veremos en el último apartado. Pero el primer paso es aplicar un desplazamiento a la derecha

#-----------------------------
#-- Lectura de un campo
#-----------------------------	
	.include "servicios.s"

	#-- Posicion del campo
	.eqv POS 8
	
	.text
	
	#-- Valor, con el campo a leer
	li t1, 0x000001500
	
	#-- Imprimir valor
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Obtener el campo mediante desplazamiento
	#-- a la derecha
	srli t1, t1, POS
	
	#-- Imprimir el nuevo numero
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

Desplazamiento aritmético a la derecha: sra, srai

El desplazamiento aritmético a la derecha se realiza con las instrucciones sra (Shift right arithmetic) y srai (Shift right arithmetic inmediate). Es un desplazamiento a la derecha que preserva el bit de mayor peso, es decir, mantiene el signo. Así, en vez de rellenarse con 0 por la izquierda, se rellena con el bit de signo. Si es un número positivo se rellena con 0s y si es un número negativo se rellena con 1s

En este ejemplo se muestra el desplazamiento de 1 bit a la derecha de un valor inicial negativo. Al desplazarse a la derecha un bit, el bit de signo se mantiene. El bit de menor peso se pierde, igual que el desplazamiento lógico.

En este ejemplo se realiza el desplazamiento aritmético a la derecha de un bit de un número positivo. Por ello se añade un 0 por la izquierda

La instrucción sra desplaza aritméticamente hacia la derecha el valor contenido en rs1 el número de bits indicado en rs2 y el resultado se deja en rd. Este es su formato: srl rd, rs1, rs2 y esta la operación realizada: rd = rs1 >> rs2 (pero manteniendo el signo)

La instrucción srai es similar, pero en vez del registro rs2 se usa un valor inmediato de 5 bits, que permite realizar desplazamientos desde 0 hasta 31 bits

En este ejemplo se utilizan las instrucciones sra y srai para desplazar dos números de 32-bits hacia la derecha. Primero se desplaza el número 0xC0003003 2 bits hacia la derecha y luego el 0x00000002 un bit hacia la derecha. En la consola se imprimen los valores iniciales y los finales tras el desplazamiento

#----------------------------------------------------
#-- Ejemplo de uso de las instrucciones SRA, SRAI
#----------------------------------------------------
		
	.include "servicios.s"
	
	.text
	
	#-- Numero inicial
	li t0, 0xC0003003
	
	#-- Numero de bits a desplazar
	li t1, 2
	
	#-- Desplazar t0 la cantidad de bits
	#-- indicados en t1. El resultado se guarda en t2
	sra t2, t0, t1
	
	#-- Imprimir resulado
	mv a0, t0
	mv a1, t2
	jal print_shift
	
	#-- Numero inicial
	lui t0, 0x00000002
	
	#-- Desplazar t0 un bit a la izqueida
	#-- El resultado se guarda en t2
	srai t2, t0, 1
	
	#-- Imprimir resultado
	mv a0, t0
	mv a1, t2
	jal print_shift
	
	#-- Terminar
	li a7, EXIT
	ecall
	
	
	
#---------------------------------------
#--  PRINT_SHIFT
#--  ENTRADAS:
#--    * a0: Valor inicial
#--    * a1: Valor desplazado
#---------------------------------------
print_shift:

	mv t0, a0
	mv t1, a1

	#-- Imprimir valor inicial en binario
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir valor final en binario
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	ret

Este es el resultado:

Caso de uso 1: División de números negativos entre potencias de 2

Debido a que el desplazamiento aritmético mantiene el bit de signo, nos permite realizar la división entre potencias de dos de números negativos.

En este ejemplo se relizan 10 divisiones entre 2, partiendo del número -1024:

Instrucciones de manipulación de bits

Las instrucciones de manipulación de bits nos permiten modificar bits individualmente. En el RISCV tenemos 3: XOR, AND y OR. Todas ellas realizan las operaciones bit a bit entre sus operandos: el bit 0 operado con el bit 0, el bit 1 con el 1, etc...

Instrucción XOR

La operación XOR nos permite cambiar de estado los bits indicados. Si aplicamos XOR a un valor a con la máscara m, los bits a 1 de m cambian de estado los correspondientes bits de a. Los bits a 0 de m, no modifican los bits de a

En este ejemplo se cambia de estado los bits 31 y 0, dejándose el resto igual. Si se aplica la operación dos veces, se obtiene el valor original

La instrucción xor realiza la operación xor entre dos registros fuente (rs1 y rs2), y deja el resultado en el registro destino rd. Este es su formato: xor rd, rs1, rs2 y esta es la operación realizada: rd = rs1 ^ rs2

La instrucción xori aplica la operación xor entre un registro fuente (rs1) y un valor inmediato de 12 bits y deja el resultado en el registro destino rd. Este es su formato: xori rd, rs1, imm12. Si el bit de mayor peso de imm12 (El bit 11) está a 1, como se realiza una extensión del signo, los bits 31-12 de la máscara también estarán a 1, por lo que todos esos bits se cambiarán de estado. La operación que se realiza es: rd = rs1 ^ ext(rs2)

En este ejemplo se ponen a 1 los bits 31 y 0, aplicando una máscara con XOR, y luego se vuelve a aplicar la misma máscara para ponerlos a 0

#----------------------------------------------------------------
#-- Ejemplo de la instruccion XOR
#----------------------------------------------------------------
	
	.include "servicios.s"
	
	.data
msg:	.string "XOR:\n"
	
	.text
	
	#-- Valor inicial
	li t0, 0x000FF000
	
	#-- Mascara para el XOR
	li t1, 0x80000001
	
	#-- Aplicar XOR
	xor t2, t0, t1
	
	#-- Volver a aplicar XOR
	xor t3, t2, t1
	
	#------- Mostrar los resultados
	#-- Valor inicial
	mv a0, t0
	li a7, 	PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Mascara
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Mensaje: XOR
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- resultado
	mv a0, t2
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Mascara
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Mensaje: XOR
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- Segundo resultado
	#-- resultado
	mv a0, t3
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall

Esta es la salida del programa:

Caso de uso 1: Bit alternante

Cuando queremos hacer parpadear un LED en un dispositivo físico utilizamos la instrucción XOR para cambiar el estado del BIT que controla el LED. En este ejemplo el bit a cambiar es el bit 0. Por ello utilizamos la instrucción xori t0, t0, 1

#----------------------------------------------------------------
#-- Instruccion XOR: Bit parpadeante
#----------------------------------------------------------------
	
	.include "servicios.s"
	
	.eqv REPETICIONES 5
	
	.text
	
	#-- Contador de repeticiones
	li t1, REPETICIONES
	
	#-- Inicializacion de t0
	mv t0, zero
	
	
bucle:	
	#-- Imprimir registro en decimal
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#-- Cambiar de estado el bit 0
	xori t0, t0, 1
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Una repeticion menos
	addi t1, t1, -1
	
	#-- Terminar al llegar a 0
	bgt t1, zero, bucle
	
	#-- Terminar
	li a7, EXIT
	ecall
  • Resultado:

Caso de uso 2: Negar un valor: Operacion NOT

Una operación muy común es negar todos los bits de un registro, es decir, cambiar de estados TODOS los bits. Esto se conoce también como operación not. Esta operación se realiza muy fácilmente con xori rd, rs1, -1. El número -1 en hexadecimal es 0xFFFFFFFF, es decir, todos los bits a 1. Por ello se cambia de estados todos los bits del registro fuente

En realidad, el valor inmediato que se usa con xori es de 12-bit. El valor inmediato asociado a -1 es 0xFFF (es lo que se almacena en el código máquina de la instrucción). La instrucción xori extiende este valor convirtiéndolo en 0xFFFFFFFF

En este programa de ejemplo se niega el valor 0xFFFF0000, lo que produce el valor 0x0000FFFF

#-------------------------------------
#-- Ejemplo de la instruccion XORI
#-- Negacion de un valor
#-------------------------------------

	.include "servicios.s"

	.text
	
	#-- Valor inicial
	lui t0, 0xFFFF0 
	
	#-- Negar el valor
	xori t1, t0, -1
	
	#---- Imprimir los resultados
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#--  Valor negado
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

Caso de uso 3: Operacion de intercambio (swap)

Una operación utilizada por mucho algoritmos es la de intercambiar el valor entre dos registros. La manera común de hacerlo es utilizar un tercer registro temporal. Así por ejemplo, para intercambiar los registros t0 y t1 podriamos usar este código:

# -- Valores iniciales
li t0, 1
li t1, 2

# -- Intercambiar su valores
mv t2, t0  #-- t2: Registro temporal
mv t0, t1
mv t1, t2

Sin embargo, es posible realizar este mismo intercambio sin utilizar un registro adicional. Es es posible gracias a la magia de la instrucción xor

El operador xor ($\oplus$) tiene las siguientes propiedades:

  1. Elemento inverso: $a \oplus a = 0$ (Todo numero es su propio inverso)
  2. Propiedad asociativa: $( a \oplus b) \oplus c = a \ oplus (b \oplus c)$
  3. Propiedads conmutativa: $a \oplus b = b \oplus a$

El fragmento de código para realizar el intercambio es el siguiente:

#-- Intercambio entre t0 y t1
xor t1, t0, t1   #-- t1 = t0 ^ t1
xor t0, t0, t1   #-- t0 = t0 ^ (t0 ^ t1) = {asoc} = (t0 ^ t0) ^ t1 = {inv} = t1
xor t1, t0, t1   #-- t1 = t1 ^ (t0 ^ t1) = {conmut} = t1 ^ (t1 ^ t0) = {asoc} = (t1 ^ t1) ^ t0 = {inv} = t0

Este es un programa completo que realiza el intercambio con xor, para comprobar que funciona correctamente:

#-----------------------------------------------
#-- Operacion SWAP entre los registros t0 y t1
#-- utilizando solo operaciones xor
#-----------------------------------------------

	.include "servicios.s"
	
	
	.data
msg_t0: .string "t0: "
msg_t1: .string ", t1: "
	
	
	.text
	
	#-- Imprimir los valores iniciales
	li t0, 100
	li t1, 200
	
	mv a0, t0
	mv a1, t1
	jal print_t0_t1
	
	#-- Volvemos a dar los valores iniciales
	li t0, 100
	li t1, 200
	
	#-- Intercambiar t0 con t1
	xor t1, t0, t1
	xor t0, t0, t1
	xor t1, t0, t1
	
	#-- Imprimir valores finales
	mv a0, t0
	mv a1, t1
	jal print_t0_t1
	
	#-- Terminar
	li a7, EXIT
	ecall	

#----------------------------------------
#-- Imprimir valore de los registros
#-- t0 y t1
#-- ENTRADAS:
#--  a0: t0
#--  a1: t1
#----------------------------------------	
print_t0_t1:
	mv t0, a0
	mv t1, a1
	
	#------- Imprimir t0
	la a0, msg_t0
	li a7, PRINT_STRING
	ecall
	
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#------ Imprimir t1
	la a0, msg_t1
	li a7, PRINT_STRING
	ecall
	
	mv a0, t1
	li a7, PRINT_INT
	ecall
	
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	ret

Este es el resultado:

Instrucción AND

La operación AND nos permite preservar los bits indicados en la máscara, y dejar a 0 el resto. Si aplicamos AND a un valor a con la máscara m, los bits a 1 de m dejan en su mismo estado los correspondientes bits de a. Los bits a 0 de m, dejan a 0 los correspondientes bits de a

En este ejemplo se aplica la máscara 0x000000FF al valor 0xCAFEBACA mediante la operación AND. En el valor obtenido todos los bits están a 0 salvo el byte de menor peso (bits de 0 al 7)

La instrucción and realiza la operación AND entre dos registros fuente (rs1 y rs2), y deja el resultado en el registro destino rd. Este es su formato: and rd, rs1, rs2 y esta es la operación realizada: rd = rs1 & rs2

La instrucción andi aplica la operación and entre un registro fuente (rs1) y un valor inmediato de 12 bits y deja el resultado en el registro destino rd. Este es su formato: andi rd, rs1, imm12. Si el bit de mayor peso de imm12 (El bit 11) está a 1, como se realiza una extensión del signo, los bits 31-12 de la máscara también estarán a 1, por lo que todos esos bits se cambiarán de estado. La operación que se realiza es: rd = rs1 & ext(rs2)

Este ejemplo es el mismo mostrado anteriormente: se aplica la máscara 0x000000FF al valor 0xCAFEBACA mediante la operación AND

#----------------------------------------------------------------
#-- Ejemplo de la instruccion AND
#----------------------------------------------------------------
	
	.include "servicios.s"
	
	.data
msg:	.string "AND:\n"
	
	.text
	
	#-- Valor inicial
	li t0, 0xCAFEBACA
	
	#-- Mascara
	li t1, 0x000000FF
	
	#-- Aplicar AND
	and t2, t0, t1
	
	#------- Mostrar los resultados
	#-- Valor inicial
	mv a0, t0
	li a7, 	PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Mascara
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Mensaje: XOR
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- resultado
	mv a0, t2
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

Caso de uso 1: Aislar y obtener un campo de bits

Una de las aplicaciones de la instrucción AND es la de obtener un campo de bits dentro de una palabra. Por ejemplo, en las instrucciones RV de 32bits, el campo registro destino, rd, está codificado en un campo de 5 bits a partir del bit 8. ¿Cómo podemos leer el valor de este campo?

Utilizando instrucciones de desplazamiento hacia la izquierda, vimos en este caso de uso cómo posicionar una máscara para leer este campo. El paso que nos queda es aplicar la instrucción AND para aislar este campo, dejando el resto de bits a 0. Es lo que denominamos el valor filtrado

Si esta valor filtrado lo desplazamos 8 posiciones a la derecha, obtenemos el número del registro destino

En este programa de ejemplo partimos de un valor que representa el código máquina de la instrucción addi x21, x1, 0x7FF, que usa el registro destino x21. Obtenemos el valor de este registro y lo imprimimos:

#------------------------------------------------
#-- Caso de uso de AND:
#-- Obtener el campo rd de una instruccion RV
#------------------------------------------------

	#-- Servicios del sistema operativo
	.include "servicios.s"

	#-- Codigo maquina instruccion: addi x21, x1, 0x7FF
	.eqv MCODE 0x7FF08A93
	
	#-- Máscara para leer el campo rd
	.eqv RD_MASK 0x00000F80
	
	#-- Posicion del campo rd
	.eqv RD_POS 7

	.data
msg:	.string "\nAND:\n"
msg2:   .string "\nCAMPO rd:\n"

	.text

	#-- t0: Meter el codigo maquina
	li t0, MCODE
	
	#-- t1: Máscara a aplicar
	li t1, RD_MASK
	
	#-- t2: Valor filtrado
	and t2, t0, t1
	
	#-- t3: campo rd
	srli t3, t2, RD_POS
	
	#-- Imprimir valor inicial
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir la mascara
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- And
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir valor filtrado
	mv a0, t2
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir mensaje
	la a0, msg2
	li a7, PRINT_STRING
	ecall
	
	#-- Campos rd en binario
	mv a0, t3
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir rd en decimal
	mv a0, t3
	li a7, PRINT_INT
	ecall
	
	#-- Termina
	li a7, EXIT
	ecall

Este es el resultado:

Caso de uso 2: Poner un bit a 0

La instrucción AND se utiliza para poner bits a 0. Muchas de las veces se quiere poner un bit determinado a 0. Como ejemplo, vamos a suponer que queremos poner a 0 el bit 5 del valor 0xCAFEBA76 que se encuentra en el registro t0. Para ello necesitamos aplicar una máscara que tenga TODOS los bits a 1 salvo el bit 5, que tiene que estar a 0. Esta máscara es: 0xFFFFFFDF

Al realizar la operación AND con esta máscara, pasan todos los bits salvo el 5, que se transforma a 0. Lo vemos en esta figura:

Este es el programa que implementa ese ejemplo:

#------------------------------------------------
#-- Caso de uso de AND:
#-- Poner el bit 5 a 0
#------------------------------------------------

	#-- Servicios del sistema operativo
	.include "servicios.s"

	#-- Valor original
	.eqv VALOR_ORIG 0xCAFEBA76
	
	#-- Máscara para poner el bit 5 a 0
	.eqv MASK 0xFFFFFFDF

	.data
msg:	.string "\nAND:\n"
msg2:   .string "\nCAMPO rd:\n"

	.text

	#-- t0: Meter el valor a modificar
	li t0, VALOR_ORIG
	
	#-- t1: Máscara a aplicar
	li t1, MASK
	
	#-- t2: Valor final
	and t2, t0, t1
	
	#-- Imprimir valor inicial
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- Valor original en hexadecimal
	mv a0, t0
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir la mascara
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- mascara en hexadecimal
	mv a0, t1
	li a7, PRINT_INT_HEX
	ecall
	
	#-- And
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir valor final
	mv a0, t2
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- Valor final en hexadecimal
	mv a0, t2
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

La máscara se puede generar automáticamente a partir del número de bit. Esto permite hacer un código genérico, para poner a 0 cualquiera de los bits indicados (0-31)

Estas son las operaciones necesarias para obtener la máscara. La constante BIT representa el número de bit a poner a 0 (de 0 a 31)

  li t0, 1
  slli t0, t0, BIT  #-- Paso 1: (1 << BIT) Desplazar. Colorcar un 1 en el bit a modificar
  xori t0, t0, -1   #-- Paso 2: not. Negar t0 para obtener la máscara (todos los bits a 1 menos el bit a modificar que queda a 0)

En esta figura se muestra la generación de la máscara para el ejemplo de poner el bit 5 a 0

Este es el programa para generarla:

#------------------------------------------------
#-- Caso de uso de AND:
#-- Generacion de una mascara para poner a 0
#-- un bit
#------------------------------------------------

	#-- Servicios del sistema operativo
	.include "servicios.s"

	#-- BIT para el que generar la mascara
	.eqv BIT 5

	.text

	#-- t0: Inicializado a 1 (para luego rotar)
	li t0, 1
	
	#-- t1: Colocar el 1 en BIT
	slli t1, t0, BIT
	
	#-- t2: Obtener la máscara aplicando un not
	xori t2, t1, -1
	
	#-- Imprimir valor inicial
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- Valor original en hexadecimal
	mv a0, t0
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir el segundo valor
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- valor en hexadecimal
	mv a0, t1
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir mascara
	mv a0, t2
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- Mascara en hexadecimal
	mv a0, t2
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

Caso de uso 3: Operación módulo n, donde n es potencia de 2

Para implementar un contador módulo N, donde N es potencia de dos, basta con incrementar el contador y aplicar una operación AND para quedarse con los n bits de menor peso (N = $2^n$ )

Ejemplo: Contador módulo 2
  • N=2¹ ⇒ n=1. Hay que quedarse con el bit 0

Este es el fragmento de programa:

li t0, 0  #-- Contador

#-- Incrementar contador
addi t0, t0, 1

#-- Quedarse con el bit 0
andi t0, t0, 1

En este ejemplo se implementa el contador módulo 2, en bucle infinito. La cuenta se realiza cada medio segundo

#------------------------------------------------
#-- Caso de uso de AND:
#-- Contador módulo 2
#------------------------------------------------

	#-- Servicios del sistema operativo
	.include "servicios.s"

	#-- Valor n en unario
	#-- para modulo 2
	.eqv n 1
	
	#-- Espera en ms
	.eqv WAIT_ms 500

	.text
	
	#-- Inicializar contador
	li t0, 0
	
bucle:
	#-- Obtener el modulo
	andi t0, t0, n

	#-- Imprimir contador actual
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Incrementar contador
	addi t0, t0, 1
	
	#-- Esperar
	li a0, WAIT_ms
	li a7, SLEEP
	ecall
	
	#-- Repetir
	j bucle

En esta animación se muestra el funcionaminto:

Ejemplo: Contador módulo 4
  • N=2² ⇒ n=2. Hay que quedarse con los bits 0 y 1

En este ejemplo se implementa el contador módulo 4, en bucle infinito

#------------------------------------------------
#-- Caso de uso de AND:
#-- Contador módulo 4
#------------------------------------------------

	#-- Servicios del sistema operativo
	.include "servicios.s"

	#-- Valor n en unario
	#-- para modulo 4
	.eqv n 0x3
	
	#-- Espera en ms
	.eqv WAIT_ms 500

	.text
	
	#-- Inicializar contador
	li t0, 0
	
bucle:
	#-- Obtener el modulo
	andi t0, t0, n

	#-- Imprimir contador actual
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Incrementar contador
	addi t0, t0, 1
	
	#-- Esperar
	li a0, WAIT_ms
	li a7, SLEEP
	ecall
	
	#-- Repetir
	j bucle

En esta animación se muestra el funcionaminto:

Ejemplo: Contador módulo 8
  • N=2³ ⇒ n=3. Hay que quedarse con los bits 0, 1 y 2

En este ejemplo se implementa el contador módulo 8, en bucle infinito

#------------------------------------------------
#-- Caso de uso de AND:
#-- Contador módulo 8
#------------------------------------------------

	#-- Servicios del sistema operativo
	.include "servicios.s"

	#-- Valor n en unario
	#-- para modulo 8
	.eqv n 0x7
	
	#-- Espera en ms
	.eqv WAIT_ms 500

	.text
	
	#-- Inicializar contador
	li t0, 0
	
bucle:
	#-- Obtener el modulo
	andi t0, t0, n

	#-- Imprimir contador actual
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Incrementar contador
	addi t0, t0, 1
	
	#-- Esperar
	li a0, WAIT_ms
	li a7, SLEEP
	ecall
	
	#-- Repetir
	j bucle

En esta animación se muestra el funcionaminto:

Caso de uso 4: Rotación hacia la izquierda

La rotación hacia la izquierda es un desplazamiento lógico hacia la izquierda en el que el bit de mayor peso se introduce de nuevo como el bit de menor peso. Es una conexión en anillo, en la que los bits rotan hacia la izquierda

En esta figura se muestra un ejemplo de rotación de un número de 4 bits, donde el valor inicial es 1 (pero podría ser cualquiera)

En este programa de ejemplo se rota el mismo número de 4 bits hacia la izquierda. Se imprime el número completo en binario y cómo el bit vuelve a comenzar desde la parte derecha:

#------------------------------------------------
#-- Caso de uso de AND:
#-- Rotacion hacia la izquierda de un numero
#-- de 4 bits
#------------------------------------------------

	#-- Servicios del sistema operativo
	.include "servicios.s"

	#-- Mascara para obtener bit 3
	#-- (bit de mayor peso)
	.eqv MSB 0x8
	
	#-- Posicion del bit de mayor peso
	.eqv POS 3
	
	#-- Mascara en unario
	.eqv MASK 0xF
	
	#-- Espera en ms
	.eqv WAIT_ms 500

	.text
	
	#-- Valor inicial
	li t0, 1
	
bucle:
	#-- Imprimir
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall

	#-- Obtener bit de mayor peso
	andi t1, t0, MSB
	srli t1, t1, POS
	
	#-- Desplazar a la izquierda: t0 = t0 << t0
	slli t0, t0, 1
	
	#-- Sumar t1: t0 = t0 + t1
	add t0, t0, t1
	
	#-- Quedarse solo con los bits de interes
	andi t0, t0, MASK
	
	#-- Esperar
	li a0, WAIT_ms
	li a7, SLEEP
	ecall
	
	#-- Repetir
	j bucle

En esta animación se muestra el funcionamiento

Instrucción OR

La operación OR nos permite poner a 1 los bits indicados en la máscara, y dejar como están al resto. Si aplicamos OR a un valor a con la máscara m, los bits a 1 de m poner a 1 los correspondientes bits de a. Los bits a 0 de m no modifican los bits correspondientes de a

En este ejemplo se aplica la máscara 0x000000FF al valor 0xCAFEBACA mediante la operación OR. En el valor obtenido todos los bits permanecen igual salvo el byte de menor peso (bits de 0 al 7), que pone todos sus bits a 1. El resultado obtenido es el valor 0xCAFEBAFF

La instrucción or realiza la operación OR entre dos registros fuente (rs1 y rs2), y deja el resultado en el registro destino rd. Este es su formato: or rd, rs1, rs2 y esta es la operación realizada: rd = rs1 | rs2

La instrucción ori aplica la operación and entre un registro fuente (rs1) y un valor inmediato de 12 bits y deja el resultado en el registro destino rd. Este es su formato: ori rd, rs1, imm12. Si el bit de mayor peso de imm12 (El bit 11) está a 1, como se realiza una extensión del signo, los bits 31-12 de la máscara también estarán a 1, por lo que todos esos bits se cambiarán de estado. La operación que se realiza es: rd = rs1 | ext(rs2)

Este ejemplo es el mismo mostrado anteriormente: se aplica la máscara 0x000000FF al valor 0xCAFEBACA mediante la operación OR

#----------------------------------------------------------------
#-- Ejemplo de la instruccion OR
#----------------------------------------------------------------
	
	.include "servicios.s"
	
	.data
msg:	.string "OR:\n"
	
	.text
	
	#-- Valor inicial
	li t0, 0xCAFEBACA
	
	#-- Mascara
	li t1, 0x000000FF
	
	#-- Aplicar OR
	or t2, t0, t1
	
	#------- Mostrar los resultados
	#-- Valor inicial
	mv a0, t0
	li a7, 	PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Mascara
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Mensaje: XOR
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- resultado
	mv a0, t2
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

Caso de uso 1: Poner un bit a 1

La instrucción OR se utiliza para poner bits a 1. Muchas de las veces se quiere poner un bit determinado a 1. Como ejemplo, vamos a suponer que queremos poner a 1 el bit 5 del valor 0xCAFEBA06 que se encuentra en el registro t0. Para ello necesitamos aplicar una máscara que tenga TODOS los bits a 0 salvo el bit 5, que tiene que estar a 1. Esta máscara es: 0x00000020

Al realizar la operación OR con esta máscara, la atraviesan todos los bits sin cambios, salvo el 5, que se transforma a 1. Lo vemos en esta figura:

Este es el programa que implementa ese ejemplo:

#------------------------------------------------
#-- Caso de uso de OR:
#-- Poner el bit 5 a 0
#------------------------------------------------

	#-- Servicios del sistema operativo
	.include "servicios.s"

	#-- Valor original
	.eqv VALOR_ORIG 0xCAFEBA06
	
	#-- Máscara para poner el bit 5 a 0
	.eqv MASK 0x00000020

	.data
msg:	.string "\nOR:\n"

	.text

	#-- t0: Meter el valor a modificar
	li t0, VALOR_ORIG
	
	#-- t1: Máscara a aplicar
	li t1, MASK
	
	#-- t2: Valor final
	or t2, t0, t1
	
	#-- Imprimir valor inicial
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- Valor original en hexadecimal
	mv a0, t0
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir la mascara
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- mascara en hexadecimal
	mv a0, t1
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Or
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir valor final
	mv a0, t2
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- Valor final en hexadecimal
	mv a0, t2
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

La máscara se puede generar automáticamente a partir del número de bit, sin más que realizar un desplazamiento hacia la izquierda. Esto permite hacer un código genérico, para poner a 1 cualquiera de los bits indicados (0-31)

Estas son las operaciones necesarias para obtener la máscara. La constante BIT representa el número de bit a poner a 0 (de 0 a 31)

  li t0, 1
  slli t0, t0, BIT  #-- Paso 1: (1 << BIT) Desplazar. Colorcar un 1 en el bit a modificar

En esta figura se muestra la generación de la máscara para el ejemplo de poner el bit 5 a 0

Este es el programa para generarla:

#------------------------------------------------
#-- Caso de uso de OR:
#-- Generacion de una mascara para poner a 1
#-- un bit
#------------------------------------------------

	#-- Servicios del sistema operativo
	.include "servicios.s"

	#-- BIT para el que generar la mascara
	.eqv BIT 5

	.text

	#-- t0: Inicializado a 1 (para luego rotar)
	li t0, 1
	
	#-- Mascara: t1: Colocar el 1 en BIT
	slli t1, t0, BIT
	
	#-- Imprimir valor inicial
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- Valor original en hexadecimal
	mv a0, t0
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir la mascara
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Imprimir espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- valor en hexadecimal
	mv a0, t1
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall

Este es el resultado:

Caso de uso 2: Combinar campos

Una de las aplicaciones de la instrucción OR es la de combinar campos de bits dentro de una palabra. Por ejemplo, esto permite generar el código máquina de una instrucción a partir del valor de sus campos. Si partimos de una palabra que contiene el opcode, ¿Cómo podemos añadir el campo del registro destino, rd, que está codificado en 5 bits a partir del bit 8?

Esta operación se realiza mediante un desplazamiento a la izquierda para colocar el campo rd en su lugar (esto ya lo hemos visto) y aplicar una operacion OR para fusionar ambos campos

El valor final contiene el opcode así como el campo rd, en sus posiciones

En este programa de ejemplo se construye el código máquina de la instrucción addi x21, x0, 0, partiendo de su opcode y del registro destino (campo rd). El código máquina obtenido es 0x00000a93 (que efectivamente se corresponde con addi x21, x0, 0)

#------------------------------------------------
#-- Caso de uso de OR:
#-- Añadir el registro x21 al opcode addi
#-- Se genera el codigo maquina de addi x21, x0, 0
#-- (0x00000a93)
#------------------------------------------------

	#-- Servicios del sistema operativo
	.include "servicios.s"

	#-- Opcode para addi
	.eqv OPCODE_ADDI 0x00000013
	
	#-- Posicion del campo rd
	.eqv RD_POS 7
	
	#-- Registro a meter en campo rd
	.eqv REG 21

	.data
msg:	.string "\nOR:\n"

	.text

	#-- t0: Instruccion vacia, con opcode
	li t0, OPCODE_ADDI
	
	#-- t1: Campo rd: Registro x21
	li t1, REG
	
	#-- t2: Colocar el campo en su posicion
	slli t2, t1, RD_POS
	
	#-- t3: Instruccion generada
	or t3, t0, t2
	
	#-- Imprimir valor inicial
	mv a0, t0
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir campo rd inicial
	mv a0, t1
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Salto de linea
	li a0, '\n'
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir campo rd en posicion final
	mv a0, t2
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- OR
	la a0, msg
	li a7, PRINT_STRING
	ecall
	
	#-- Imprimir instruccion final en binario
	mv a0, t3
	li a7, PRINT_INT_BINARY
	ecall
	
	#-- Espacio
	li a0, ' '
	li a7, PRINT_CHAR
	ecall
	
	#-- Imprimir instruccion final en hexadecimal
	mv a0, t3
	li a7, PRINT_INT_HEX
	ecall
	
	#-- Termina
	li a7, EXIT
	ecall

Este es el resultado:

Conclusiones

Con esta sesión cerramos el estudio de la arquitectura base RV32I: el núcleo mínimo del que disponen todos los procesadores RISC-V, para implementar aplicaciones a nivel de usuario. En total se dispone de 37 instrucciones, divididas en Operaciones aritméticas, desplazamiento y manipulación de bits, Acceso a memoria (load y store), Comparaciones con asinación, Saltos y Llamadas al sistema

Ejercicios para practicar

🚧 TODO 🚧

Profesorado

Licencia

Enlaces

Página principal


Sesiones de Prácticas

P1: Simulador RARs

L1: El simulador RARs
L2: Calculando expresiones
L3: Práctica 1-3. Variables

P2: E/S mapeada. Llamadas al sistema

L4: Pract 2-1. E/S mapeada
L5: Práctica 2-2: Inst. ecall
L6: Prác 2-3: Cadenas

P3: Bucles y Saltos condicionales

L7: Práct 3-1: Bucles y saltos
L8: Práct 3-2: Cadenas II

P4: Subrutinas

L9: Pract 4-1: Subrut. Nivel-1
L10: Pract 4-2: La pila
L11: Pract 4-3: Recursividad

P5: Memoria Dinámica

L12: Pract 5-1. Heap. Listas

VÍDEO DE DESPEDIDA

SESIONES ADICIONALES

L13: RV32I: Resto de instrucciones
L14: Macros
L15: Entrada/Salida a bajo nivel
L16: Multiplicaciones/divisiones. Unit Tests 🚧
L17: Biblioteca stdio: salida a alto nivel 🚧
Los registros de estado y control (CSR)
Interrupciones y excepciones
Borrador

SOLUCIONES

Soluciones a los ejercicios planteados en cada sesión para practicar:
Sesión L1
Sesión L2
Sesión L3
Sesión L4
Sesión L5
Sesión L6
Sesión L7
Sesión L8
Sesión L9
Sesión L10
Sesión L11
Sesión L12

Ejercicios de examen

Simulacro examen 1
GISAM. Ordinario. 2019-Dic-11
GISAM. Extra. 2020-Jul-03
GISAM. Ordinario. 2021-Ene-21
GISAM. Ordinario. 2022-Ene-10
GISAM. Extra. 2022-Jun-29
GISAM. Parcial 1. 2022-Oct-26
GISAM. Parcial 2. 2022-Nov-30
GISAM. Parcial 3. 2022-Dic-21
GISAM. Parcial 1. 2023-Oct-09
GISAM. Parcial 2. 2023-Nov-11
GISAM. Parcial 3. 2023-Dic-20
GISAM. Extra. 2024-Jun-17
GISAM. Parcial 1. 2024-Oct-14
GISAM. Parcial 2. 2024-Nov-13
GISAM. Parcial 3. 2024-Dic-16
GISAM. Extra. 2025-Jun-17
GISAM. Parcial 1. 2025-Oct-20
GISAM. Parcial 2. 2025-Nov-19
GISAM. Parcial 3. 2026-Enero-9
GISAM. Parcial 1. 2026-Oct-21
TELECO. Ordinario. 2019-Dic-13
TELECO. Extra. 2020-Jul-07
TELECO. Ordinario. 2021-Ene-21
TELECO. Extra. 2021-Jul-02
TELECO. Ordinario. 2022-Ene-10
TELECO. Extra. 2022-Jun-29
TELECO. Ordinario. 2023-Ene-10
TELECO. Extra. 2023-Jun-29
TELECO. Parcial 1. 2023-Oct-20
TELECO. Parcial 2. 2023-Nov-17
TELECO. Parcial 3. 2023-Dic-22
TELECO. Extra. 2024-Jun-17
TELECO. Parcial 1. 2024-Oct-10
TELECO. Parcial 2. 2024-Nov-21
TELECO. Parcial 3. 2024-Dic-19
TELECO. Extra. 2025-Jun-17
TELECO. Parcial 1. 2025-Oct-10
TELECO. Parcial 2. 2025-Nov-13
TELECO. Parcial 3. 2026-Ene-21
TELECO. Extra. 2026-Jun-25
TELECO. Parcial 1. 2026-Oct-09
Robótica. Ordinario. 2020-Jun-1
Robótica. Extra. 2020-Jul-13
Robótica. Ordinario. 2021-Mayo-20
Robótica. Extra. 2021-Junio-16
Robótica. Parcial 1. 2022-Feb-25
Robótica. Parcial 2. 2022-Abril-1
Robótica. Parcial 3. 2022-Mayo-6
Robótica. Parcial 1. 2023-Feb-27
Robótica. Parcial 2. 2023-Mar-27
Robótica. Parcial 3. 2023-May-08
Robótica. Parcial 1. 2024-Feb-26
Robótica. Parcial 2. 2024-Mar-20
Robótica. Parcial 3. 2024-May-06
Robótica. Extra. 2024-Junio-24
Robótica. Parcial 1. 2025-Feb-25
Robótica. Parcial 2. 2025-Mar-31
Robótica. Parcial 3. 2025-May-19
Robótica. Parcial 1. 2026-Feb-23
Robótica. Parcial 2. 2026-Abr-13
Robótica. Parcial 3. 2026-May-11
Datos. Parcial 1. 2023-Oct-09
Datos. Parcial 2. 2023-Nov-15
Datos. Parcial 3. 2023-Dic-20
Datos. Parcial 1. 2024-Oct-09
Datos. Parcial 2. 2024-Nov-13
Datos. Parcial 3. 2025-Ene-17
Datos. Parcial 1. 2025-Oct-15
Datos. Parcial 2. 2025-Nov-19

Clone this wiki locally