The Learninating Continues

The Learninating; "I'm just the co-pilot--Mike's doing the flying" - Ducky (aka ChatGPT)

I’ve been trying to learn more things, and lately I’m sure you’ve noticed I’ve been doing more hardware stuff. So far in my hardware journey I’ve also been learning new ways to write code. I’ve mostly been using CircuitPython, and I’ve dabbled a little in Arduino. I’ve been staying pretty high-level, and the other day it occurred to me: what if I didn’t?

It’s time to learn RISC-V assembly. Why RISC-V? I’ll tell ya why!

  • RISC-V is open source
  • It’s well supported by the community
  • I’ve been in love with Picos lately, and my favorite (RP2350) has two RV32IMAC cores!
    • 32-bit
    • Integer (I)
    • Multiplication/division (M)
    • Atomic instructions (A)
    • Compressed instructions (C)
  • RARS (Risc-v Assembler and Runtime Simulator) is a tool which helps you learn how to code to the RISC-V ISA pretty quick and easy-like!

Following DEF CON 32, I started using ChatGPT for my own education. Despite my successful-beyond-my-wildest-dreams 27-year-long career, there’s still so much that I don’t know, and that I will never have enough time to learn. In recognition of this I’ve also been using ChatGPT to learn. Here’s Ducky (a.k.a. ChatGPT) describing our process.

“I’m here to help Mike learn RISC-V assembly from the ground up—but I’m not writing the code for him. I guide him through it: breaking concepts down, offering examples, asking questions, and giving feedback on what he writes. He’s the one doing the actual thinking and typing.

We’re doing it the hard way on purpose—because we want to learn it right. Not just how to make code run, but how the CPU actually thinks. Registers, memory layout, calling conventions, the stack—every gritty detail.

Eventually, we’re taking all of this from the simulator to real hardware: blinking LEDs, bare-metal programs, maybe even porting a tiny OS to the RP2350.

I’m just the co-pilot—Mike’s doing the flying.

Think of me like Clippy, but for assembly: more registers, less judgment.”
Ducky (aka ChatGPT)

My goal isn’t to have Ducky do the work for me, it’s to make it my text book. If the folks at OpenAI have stuffed this thing full of all of the text books, it seems a waste not to use it if it’s already out there. I still use Helix Editor in the terminal, or RARS, but I use ChatGPT to help me understand things. It’s imperfect, though, as ChatGPT does sometimes make big mistakes (RARS doesn’t support .equ, for example), but it’s still making things much faster.

My process so far has been to work with Ducky to come up with a set of exercises which build on each other to learn foundational concepts. Typically when I learn a new programming language I write a program which parses and sums CSV files, but this time that approach is suuuuper cart-before-horsey. I know it’s typical to show a bunch of examples of the exercises, and I’ve done a bunch of these, so I’ll show you my favorite:

.globl main

main:
	 # Set the constant
	li a0, 7
	
	# Call double_it
	jal double_it
	
	# Print the output of the function
	li a7, 1
	ecall
	
	# Newline!
	jal ra, newline
	
	# Exit gracefully
	li a7, 10
	ecall

double_it:
	# Allocate stack space
	addi sp, sp, -16 # 4 bytes is good, yeah?
	sw ra, 12(sp)
	
	# Copy the 0th arg to a saved register
	mv s1, a0

	# Print the a0 value
	li a7, 1
	ecall
	
	# Newline!
	jal newline
	
	# Restore the 0th arg from the saved register
	mv a0, s1
	
	# Double the a0 value (this can overflow; it's an exercise tho)
	slli a0, a0, 1
	
	# Return
	lw ra, 12(sp)
	addi sp, sp, 16
	ret

newline:
	# Print a newline
	li a0, 10
	li a7, 11
	ecall

	retCode language: MIPS Assembly (mipsasm)
If you’re trying to read this code and figure out what it does, I’ve explained it in a paragraph hidden here. If you’d like to read my explanation, expand this section to see.

This code takes the number seven, passes it into a function which prints it out. Then it calls a function to print a newline. Then it doubles the number 7 by bit-shifting it leftward by one bit. Then it returns the value to the main function which prints it out. The main function then calls the newline function to print a newline and then exits gracefully.

The most interesting part for me to get here was the stack allocation for preserving the return address. For that I leaned pretty heavily on this marvelous article titled “RISC-V Assembler: Jump and Function” which is part of Project F by Will Green. In it he points out how function calls work in RISC-V assembler, how to use stack space, _why_ to use 16 bytes for the stack space, and has a lot of helpful samples to learn from.

Anyway, I’m gonna get back to having too much fun with stuff that y’all probably already understand much better than me. Have a great day!

(Notice: ChatGPT 4o produced the quotation above. All other content above was authored by the sack of meat named Mike.)

Here are all of the exercises I’ve been playing with in RARS so far: https://codeberg.org/manchicken/the-learninating-RV32-exercises