# Lab1: RV32I Simulator ## Ripes: RISC-V Instruction Simulator and Integrated Development Environment ![](https://hackmd.io/_uploads/BJIb0Z8ya.png) [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). ![image](https://hackmd.io/_uploads/B1yt5M72xg.png) 2. Instantiate the **LED Matrix** device, designated as **LED Matrix 0**, with a base address of `0xf0000000` ![image](https://hackmd.io/_uploads/S1L6czm2xe.png) > 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). ![image](https://hackmd.io/_uploads/B127ofQhgl.png) 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). ![image](https://hackmd.io/_uploads/r1KvofXnlg.png) Finally, it works. ![LED animation](https://hackmd.io/_uploads/B1U37yXngx.png) 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.