# Lab1: RV32I Simulator
## Ripes: RISC-V Instruction Simulator and Integrated Development Environment

[Ripes](https://github.com/mortbopet/Ripes) is a graphical RISC-V pipeline simulator and assembly editor. It provides a visual representation of how assembly level code is executed on various microarchitectures.
* Video: [Ripes: Teaching Computer Architecture Through Visual and Interactive Simulators](https://youtu.be/yEw-S8J-LkI)
* Tutortial: [Ripes RISC-V Simulator](https://www.hackster.io/patrick-fitzgerald2/ripes-risc-v-simulator-9c8a51)
* Download: <https://github.com/mortbopet/Ripes/releases>
* ℹ️ You can download Ripes in the [AppImage](https://appimage.org/) format, which allows for instant execution on various Linux distributions without requiring superuser permissions.
* You can also use Ripes directly in your [web browser](https://ripes.me/).
* ⚠️ On Apple Silicon, Ripes must be built from the source. Please refer to the instructions for details.
---
## Example: Factorial Calculation
The following example demonstrates an implementation of the mathematical factorial function (`!`) to find the factorial value of $7! = 5040$.
The factorial function can be expressed as a recursive function. Recall that the factorial of a non-negative integer $n$, denoted by $n!$, is the product of all positive integers less than or equal to $n$.
$fact(n) = n \times (n – 1) \times (n – 2) \times \dots \times 2 \times 1$
The `fact` function can be rewritten recursively as:
$fact(n) = n \times fact(n – 1)$
```c
.data
argument: .word 7 # Store the initial value (7) for which we want to compute the factorial
str1: .string "Factorial value of " # First part of the output message
str2: .string " is " # Second part of the output message
.text
main:
lw a0, argument # Load the argument (7) into register a0
jal ra, fact # Jump-and-link to the 'fact' function to compute factorial
# Prepare to print the result
mv a1, a0 # Move the result (factorial) from a0 to a1 for printing
lw a0, argument # Reload the original argument into a0 to print it
# Call the function to print the result
jal ra, printResult
# Exit the program
li a7, 10 # System call code for exiting the program
ecall # Make the exit system call
# Recursive function to compute factorial
# a0: Input argument (number for which factorial is to be calculated)
fact:
addi sp, sp, -16 # Allocate stack space for local variables (ra and a0)
sw ra, 8(sp) # Save return address (ra) on the stack
sw a0, 0(sp) # Save input argument (a0) on the stack
addi t0, a0, -1 # Check if a0 > 0 by subtracting 1
bge t0, zero, nfact # If a0 >= 1, jump to 'nfact' to continue recursion
# Base case: factorial(0) = 1
addi a0, zero, 1 # Set a0 = 1 as factorial(0) = 1
addi sp, sp, 16 # Restore stack
jr x1 # Return to the caller
nfact:
addi a0, a0, -1 # Decrement a0 (input) by 1 for recursive call
jal ra, fact # Recursive call to 'fact' function
addi t1, a0, 0 # Store the result of factorial(n-1) in t1
# Restore the previous state before returning
lw a0, 0(sp) # Load the original value of a0 (n) from the stack
lw ra, 8(sp) # Restore return address (ra)
addi sp, sp, 16 # Deallocate stack space
# Multiply the current value of n with factorial(n-1)
mul a0, a0, t1 # a0 = a0 * t1, where t1 contains factorial(n-1)
ret # Return to the caller with factorial(n)
# This function prints the factorial result in the format:
# "Factorial value of X is Y", where X is the original number and Y is the computed factorial
# a0: The original input value (X)
# a1: The computed factorial result (Y)
printResult:
mv t0, a0 # Save original input value (X) in temporary register t0
mv t1, a1 # Save factorial result (Y) in temporary register t1
la a0, str1 # Load the address of the first string ("Factorial value of ")
li a7, 4 # System call code for printing a string
ecall # Print the string
mv a0, t0 # Move the original input value (X) to a0 for printing
li a7, 1 # System call code for printing an integer
ecall # Print the integer (X)
la a0, str2 # Load the address of the second string (" is ")
li a7, 4 # System call code for printing a string
ecall # Print the string
mv a0, t1 # Move the factorial result (Y) to a0 for printing
li a7, 1 # System call code for printing an integer
ecall # Print the integer (Y)
ret # Return to the caller
```
The console will output:
```
Factorial value of 7 is 5040
```
## Example: Ripes LED Matrix
Ripes includes several memory-mapped I/O (MMIO) devices—such as an LED Matrix, so you can draw directly to a simulated display. See [Memory-mapped I/O devices](https://github.com/mortbopet/Ripes/blob/master/docs/mmio.md) for details.
### Set up devices
1. Open the **I/O** tab: On the left side of Ripes, you’ll find a vertical grey bar containing several tabs. When selected, a tab is highlighted in a lighter shade of grey. The Editor tab displays the program currently loaded into Ripes, while both the Editor and Processor tabs also include a console where you can enter input and view output. To proceed, locate the I/O tab on the left. Its button looks like this (see image below).

2. Instantiate the **LED Matrix** device, designated as **LED Matrix 0**, with a base address of `0xf0000000`

> As you change parameters, notice how the **register map** and **exported symbols** for the LED Matrix update automatically.
### Animated emoji demo (35×25 display)
The following example animates a 4×4 pixel emoji that moves diagonally across a **35×25** LED Matrix.
**Visual representation**
**Initial state** (blue background, yellow emoji at position 10,10):
```
🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟨🟨🟨🟨🟦🟦...
🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟨⚫⚫🟨🟦🟦...
🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟨🟨🟨🟨🟦🟦...
🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟨🔴🔴🟨🟦🟦...
```
**After animation** (moves down-right):
```
(10,10) → (12,11) → (14,12) → …
```
### How it works
1. **Initialization**
* Base address for LED Matrix: `0xF0000000`
* Buffer size: `35 × 25 × 4 = 3500` bytes (RGBA or 32-bit color)
* Clear screen to blue: `0x0000FF`
2. **Sprite definition (4×4)**
* Face (yellow): `0xFFFF00`
* Eyes (black): `0x000000`
* Mouth (red): `0xFF0000`
3. **Animation loop (5 iterations)**
* Draw sprite at `(x, y)`
* Brief delay (software loop)
* Clear sprite area back to blue
* Update position: `x += 2`, `y += 1`
* Redraw sprite
4. **Drawing math**
* Address of pixel `(X, Y)`:
```
addr = BASE + (Y * WIDTH + X) * BYTES_PER_PIXEL
```
with `BASE = 0xF0000000`, `WIDTH = 35`, `BYTES_PER_PIXEL = 4`.
* Optimized multiply by 35:
```
n * 35 = (n << 5) + (n << 1) + n // 32n + 2n + n
```
This structure lets you parameterize the display, keep memory math simple, and render small sprites efficiently on Ripes' LED Matrix MMIO device.
RISC-V code is shown below
```c
.data
# 4x4 face sprite (RGB: 0x00RRGGBB)
emoji_face:
.word 0x00FFFF00, 0x00FFFF00, 0x00FFFF00, 0x00FFFF00 # Row 0: Yellow
.word 0x00FFFF00, 0x00000000, 0x00000000, 0x00FFFF00 # Row 1: Eyes (black)
.word 0x00FFFF00, 0x00FFFF00, 0x00FFFF00, 0x00FFFF00 # Row 2: Yellow
.word 0x00FFFF00, 0x00FF0000, 0x00FF0000, 0x00FFFF00 # Row 3: Mouth (red)
.text
.global main
main:
# Initialize LED Matrix (35x25 @ 0xF0000000)
lui s0, 0xF0000 # LED base address
addi s1, x0, 35 # Display width
addi s2, x0, 25 # Display height
# Calculate buffer end: 35*25*4 = 3500 bytes
lui t0, 1
addi t0, t0, -596 # 4096 - 596 = 3500
add s6, s0, t0 # s6 = buffer end address
# Fill screen with blue (0x0000FF)
lui a1, 0xFF # Load 0x000FF000
srli a1, a1, 12 # Shift to 0x000000FF (blue)
mv a0, s0 # Start at base
clear_loop:
sw a1, 0(a0)
addi a0, a0, 4
blt a0, s6, clear_loop
# Initial sprite position
la s7, emoji_face # Sprite data pointer
addi s3, x0, 10 # Initial X = 10
addi s4, x0, 10 # Initial Y = 10
# Draw initial sprite
jal draw_sprite
# Animation loop (5 iterations)
addi s10, x0, 5
animate_loop:
# Simple delay for visibility
lui a0, 2
delay:
addi a0, a0, -1
bnez a0, delay
# Clear current sprite (fill with blue)
addi s8, x0, 0 # Row index
clear_sprite:
addi s9, x0, 0 # Column index
clear_col:
# Calculate pixel address: base + ((y+row)*width + x+col)*4
add t5, s4, s8 # y + row
# Multiply by 35: t5*35 = t5*32 + t5*2 + t5
mv t2, t5 # Save original
slli t0, t5, 5 # t5 * 32
slli t1, t5, 1 # t5 * 2
add t5, t0, t1 # 32 + 2
add t5, t5, t2 # + 1 = *35
add t5, t5, s3 # Add X position
add t5, t5, s9 # Add column
slli t5, t5, 2 # Convert to bytes (*4)
add t5, s0, t5 # Add base address
# Write blue pixel
lui t0, 0xFF # rd = imm << 12
srli t0, t0, 12 # Blue = 0x0000FF
sw t0, 0(t5)
# Next column
addi s9, s9, 1
addi t0, x0, 4
blt s9, t0, clear_col
# Next row
addi s8, s8, 1
addi t0, x0, 4
blt s8, t0, clear_sprite
# Update position (diagonal movement)
addi s3, s3, 2 # Move right by 2
addi s4, s4, 1 # Move down by 1
# Draw sprite at new position
jal draw_sprite
# Continue animation
addi s10, s10, -1
bnez s10, animate_loop
# Exit program
addi a7, x0, 10
ecall
# Function: draw_sprite
# Draws 4x4 sprite at position (s3, s4)
# Uses: s0=base, s3=x, s4=y, s7=sprite
draw_sprite:
addi sp, sp, -4
sw ra, 0(sp) # Save return address
addi s8, x0, 0 # Row index
draw_row:
addi s9, x0, 0 # Column index
draw_pixel:
# Calculate display address
add t5, s4, s8 # y + row
# Multiply by 35
mv t2, t5
slli t0, t5, 5
slli t1, t5, 1
add t5, t0, t1
add t5, t5, t2
add t5, t5, s3 # Add X
add t5, t5, s9 # Add column
slli t5, t5, 2 # To bytes
add t5, s0, t5 # Final address
# Get sprite pixel: sprite[row*4 + col]
slli t6, s8, 2 # row * 4
add t6, t6, s9 # + col
slli t6, t6, 2 # * 4 bytes
add t6, s7, t6 # Sprite address
# Copy pixel
lw t0, 0(t6)
sw t0, 0(t5)
# Next column
addi s9, s9, 1
addi t0, x0, 4
blt s9, t0, draw_pixel
# Next row
addi s8, s8, 1
addi t0, x0, 4
blt s8, t0, draw_row
# Return
lw ra, 0(sp)
addi sp, sp, 4
jr ra
```
Before running the program, return to the I/O tab if you navigated away. Find the pop-out button for the LED matrix you instantiated in the above step. Clicking this button detaches the matrix from its window, allowing it to float on top of the Ripes interface while it remains open (see image below).

Now go back to the Editor tab, where the assembly program instructions are located. Run the program by clicking the `>>` button (see image below with green highlight).

Finally, it works.

See also: [RISCV Assembly Tutorial: Practice with LED and Switch on Simulator](https://youtu.be/rlB8aeXDpc0)
---
## Building Ripes from Source
> for Apple Silicon / macOS
### 1. Install Homebrew
If you do not already have [Homebrew](https://brew.sh/) installed, open Terminal and follow the instructions on the Homebrew website. Homebrew will serve as your package manager for installing dependencies.
### 2. Install Required Packages
Use Homebrew to install the necessary build tools and libraries:
```shell
$ brew install cmake ninja qt
```
### 3. Clone the Ripes Source Code
Fetch the Ripes repository, including its submodules:
```shell
$ git clone --recursive https://github.com/mortbopet/Ripes
```
### 4. Configure and Build
Move into the source tree, set up a build directory, and compile using CMake and Ninja:
```shell
$ cd Ripes
$ mkdir -p build
$ cd build
$ cmake .. -G Ninja -DCMAKE_POLICY_VERSION_MINIMUM=3.5
$ ninja
```
:::info
You can use `make` instead of `ninja`, but since you specified Ninja during configuration, `ninja` is the appropriate choice here.
:::
### 5. Run Ripes
Once the build completes successfully, you can launch Ripes with:
```shell
$ Ripes.app/Contents/MacOS/Ripes
```
This should bring up the main Ripes interface.