RISC-V Assembly Guidelines For The Turbo V
Rando notes for me
Using .S files:
section_kernelvars cursor_column .space 1 cursor_row .space 1 textcolor .space 1 alignment .space 1 long_var .space 4
vs .s:
.section .data
banner_color: .byte MSG_COLOUR_RED
(but sections can't be arbitrarily named? Confused)
Folks can write assembly however they like, ultimately. That is part of the fun of a bare-metal computer! To make things easier for folks, these are some suggestions for how to make the experience more consistent.
Using the ABI
The system kernel abides by the RISC-V ABI and it is suggested that applications do the same.
| Raw Name | ABI | Meaning | Preserved |
|---|---|---|---|
| x0 | zero | Zero | Immutable |
| x1 | ra | Return Address | No |
| x2 | sp | Stack Pointer | Yes |
| x3 | up (gp) | User "Zero" Page | Immutable |
| x4 | kp (tp) | Kernel "Zero" Page | Immutable |
| x5 - x7 | t0 - t2 | Temporary Registers | No |
| x8 - x9 | s0 - s1 | Callee-Saved Registers | Yes |
| x10 - x17 | a0 - a7 | Argument Registers | No |
| x18 - x27 | s2 - s11 | Callee-saved Registers | Yes |
| x28 - x31 | t3 - t6 | Temporary Registers | No |
Note s0 can be an alias to fp (Frame Pointer) which C programs may use.
zp and kp
zp and kp are set as part of the Turbo V boot sequence and point
to the User Variables and Kernel Variables memory bases respectively.
On the Turbo V, the zp (gp in the official RISCV abi)
register has a more special meaning. It stores the
base memory address for the User Vars ("Zero Page" in 6502 terms) area. This
allows constantly fast access to important user variables, much like how
the zero page works on 6502. The user can have up to 2KB of data here, though
at present 1KB is allocated (though this may change).
Likewise, kp (tp) is the same thing, only for the Kernel Vars. Unlike the 6502,
on the Turbo V the kernel has its own "zero page". This makes interrupts less
expensive (at least those which use kernel vars).
The original use of the tp is a thread pointer which is mostly for multi-threaded
systems, of which Turbo V is not. While future versions might be, that is a big
enough change where it might be a different system entirely, though one where the
function of tp would still be similar to how it will be used today.
Why do all this? The user and kernel variable spaces use fast DPRAM available and
run at CPU speed. So even if a program is running in System RAM (Bus RAM), variables
fetches will be fast. Second, this can result in fewer instructions as the variables
can be fetched quickly using zp or kp as memory offsets. It's all very similar to
the 6502 zeropage.
Using tv.inc
tv.inc contains helper macros and constants to using the system. Things like
names of the kernel calls, the kcall macro (for calling kernel routines), colors,
character names, etc.
This keeps applications a bit more consistent as they use well known common names and macros.
Local Variables
Folks coming from 6502 may be used to overloading variables from the Zeropage.
Things like zp_TMP0 might get renamed in a local routine to FARTS to make
it easier to follow. In RISCV, there are many more registers where it may be
beneficial to rename a register to a local variable. The recommended way
to do that is via .equ and .undef. For example:
cool_routine:
# Define local aliases
.equ cursor_col, t2
.equ cursor_row, t3
lbu cursor_col, 0(t1)
lbu cursor_row, 1(t1)
...
# Clean up aliases to avoid conflicts
.undef cursor_col
.undef cursor_row
Scopes
In gcc/gas scopes as they exist in ca65 are not a thing. They can be somewhat emulated by
using a few things:
Local Labels
Using local labels keeps those labels local to the scope of the file. This is done with the
.L prefix (e.g. .Lloop1).
.local
Using .local keeps the defined routine or variable local to the file.