Initial commits

main
Drew Bednar 5 months ago
commit 320c30dac3

@ -0,0 +1,77 @@
# Hello RISCV
To develop on your x86_64 desktop PC you need QEMU and the GNU riscv compiler toolchain.
```
sudo apt-get install qemu qemu-system-misc gcc-riscv64-linux-gnu sudo apt install gcc-riscv64-unknown-elf
```
## Checking your toolchain
If you are building binaries to target a Linux system:
```
riscv64-linux-gnu-as --version
```
For building standalone Executable and Linkable Format (ELF) binaries that are not tied to any specific operating system use:
```
riscv64-unknown-elf-as --version
```
The output object files are lined against a bare-metall environment or an embedded system that does not rely on an OS. Might use Newlib or another lightweight C lib instead of glibc. Good for embedded systems, bootloaders, or bare metal apps that have no operating system.
### The typical build workflow
You will want to know the following two(three) values for your build target:
-`march=ISA` selects the architecture to target. This controls which instructions and registers are available for the compiler to use. Describes which hardware generated code can run on
-`mabi=ABI` selects the ABI to target. This controls the calling convention (which arguments are passed in which registers) and the layout of data in memory. Describes which software generated code can link against. In order for objects to be linked together, they must follow the same ABI.
- ilp32: int, long, and pointers are all 32bits, long is a 64 bit type, char 8 bit, short 16 bit. No floating point registers.
- lp64: long and pointers are 64 bits long, while int is 32 bit type. The other types remain the same as ilp32
- ilp32f: Same as ipl32 but with 32 bit and smaller floating point arguments are passed in registers. This ABI requires the F extension.
- ilp32d: 64 bit and smaller floating point arguments,are passed in registers. ABI requires the D extension.
- etc.
-`mtune=CODENAME` selects the microarchitecture to target. This informs GCC about the performance of each instruction, allowing it to perform target-specific optimizations. Most times you will not need to use this argument.
See: [The -march, -mabi, and -mtune arguments to RISC-V Compilers](https://www.sifive.com/blog/all-aboard-part-1-compiler-args) for more detail.
Example: If our target has integer, multiplication, atomic memory operations, and compressed instructions extensions then our assembler command would look like:
```
riscv64-linux-gnu-as -march=rv32imac -mabi=ilp32 ...
```
## Using QEMU
Ensure it's installed
```
qemu-system-riscv --version
```
```
qemu-system-riscv32 -machine help
Supported machines are:
none empty machine
opentitan RISC-V Board compatible with OpenTitan
sifive_e RISC-V Board compatible with SiFive E SDK
sifive_u RISC-V Board compatible with SiFive U SDK
spike RISC-V Spike board (default)
virt RISC-V VirtIO board
toor@toor-jammy-jellifish:~/experiments/learn_r
```
We are going to focus on the `virt` machine type.
## Resources
- [RISC-V Instruction Set Manual](https://riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf)
- [Linux Kernel system calls for all architectures](https://gpages.juszkiewicz.com.pl/syscalls-table/syscalls.html)
- [RISC-V ASM Manual](https://github.com/riscv-non-isa/riscv-asm-manual/blob/main/riscv-asm.md)
- [Sample RISC-V ASM Programs](https://marz.utk.edu/my-courses/cosc230/book/example-risc-v-assembly-programs/)
- [RISC-V Assembly Programming](https://riscv-programming.org/) Includes a book, exercises, and a simulator
- [The -march, -mabi, and -mtune arguments to RISC-V Compilers](https://www.sifive.com/blog/all-aboard-part-1-compiler-args)

@ -0,0 +1,9 @@
run: build
# qemu-system-riscv32 -machine virt -m 128M -bios none -device loader,file=./hello -nographic
qemu-system-riscv32 -machine virt -m 128M -bios none -kernel hello -nographic
build:
rm -rf hello
riscv64-unknown-elf-as -march=rv32i -mabi=ilp32 hello.s -o hello.o
riscv64-unknown-elf-ld -m elf32lriscv hello.o -o hello

@ -0,0 +1,4 @@
# Another Hello World
Unlike the Low Level Learning example that used `riscv64-linux-gnu-as` this project is targeting `riscv64-unknown-elf-as` and we will use QEMU to emulate a RISC-V environment to run our ELF executable in.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,17 @@
.global _start
_start:
li a0, 1 # file descriptor = 1 (stdout)
la a1, string # buffer
li a2, 19 # size
li a7, 64 # syscall write (64)
ecall
_end:
li a0, 0 # exit code
li a7, 93 # syscall exit
ecall
string: .asciz "Hello! It works!!!\n"

@ -0,0 +1,14 @@
default: hello
hello: hello.o baremetal.ld
riscv64-unknown-elf-gcc -T baremetal.ld -march=rv32i -mabi=ilp32 -nostdlib -static -o hello hello.o
hello.o: hello.s
riscv64-unknown-elf-as -march=rv32i -mabi=ilp32 hello.s -o hello.o
run: hello
@echo "Ctrl-A C for QEMU console, then quit to exit"
qemu-system-riscv32 -nographic -serial mon:stdio -machine virt -bios hello
clean:
rm hello hello.o

@ -0,0 +1,219 @@
# Baremetal RISC-V
Ok this chuck guy knows his stuff it seems https://www.youtube.com/watch?v=qLzD33xVcRE
`barmetal.ld` contains the linker scripts.
The `SECTIONS` keyword indicates the beginning of the linker script sections where he specifies the memory layout, and the sections of the program.
```
{
. = 0x800000000;
}
```
This sets the location counter to the 0x800000000 memory address. This means that the following sections of the linker script will be placed as if starting from this address in memory. This is important because we need to know where in memory to load our application, so that the cpu boots we know what address will be loaded into the program counter to start executing instructions.
ADD MORE DETAILS
## QEMU
Run this command
```
qemu-system-riscv32 -nographic -serial mon:stdio -machine virt
```
If you encounter
```
qemu-system-riscv32: Unable to load the RISC-V firmware "opensbi-riscv32-generic-fw_dynamic.bin"
```
Well it's going to suck a little. Running `sudo apt install opensbi` only appears to include qemu-system-riscv64 builds. I tried building from source and hit a ton of issues, so instead I untarred a binary release in my home directory
```
wget https://github.com/riscv-software-src/opensbi/releases/download/v1.5.1/opensbi-1.5.1-rv-bin.tar.xz
tar -xf opensbi-1.5.1-rv-bin.tar.xz -C ~/
ls ~/opensbi-1.5.1-rv-bin
```
```
qemu-system-riscv32 -nographic -serial mon:stdio -machine virt -bios ~/opensbi-1.5.1-rv-bin/share/opensbi/ilp32/generic/firmware/fw_dynamic.bin
```
Now we can continue...
We start qemu in `-nographic` mode. We redirect the console input/output to our terminal `-serial mon:stdio`, and we start the `virt` machine type that we will do the simulation under.
```
OpenSBI v1.5.1
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
Platform Name : riscv-virtio,qemu
Platform Features : medeleg
Platform HART Count : 1
Platform IPI Device : aclint-mswi
Platform Timer Device : aclint-mtimer @ 10000000Hz
Platform Console Device : semihosting
Platform HSM Device : ---
Platform PMU Device : ---
Platform Reboot Device : syscon-reboot
Platform Shutdown Device : syscon-poweroff
Platform Suspend Device : ---
Platform CPPC Device : ---
Firmware Base : 0x80000000
Firmware Size : 319 KB
Firmware RW Offset : 0x40000
Firmware RW Size : 63 KB
Firmware Heap Offset : 0x47000
Firmware Heap Size : 35 KB (total), 2 KB (reserved), 9 KB (used), 23 KB (free)
Firmware Scratch Size : 4096 B (total), 240 B (used), 3856 B (free)
Runtime SBI Version : 2.0
Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x00100000-0x00100fff M: (I,R,W) S/U: (R,W)
Domain0 Region01 : 0x02000000-0x0200ffff M: (I,R,W) S/U: ()
Domain0 Region02 : 0x0c200000-0x0c20ffff M: (I,R,W) S/U: (R,W)
Domain0 Region03 : 0x80040000-0x8004ffff M: (R,W) S/U: ()
Domain0 Region04 : 0x80000000-0x8003ffff M: (R,X) S/U: ()
Domain0 Region05 : 0x0c000000-0x0c1fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06 : 0x00000000-0xffffffff M: () S/U: (R,W,X)
Domain0 Next Address : 0x00000000
Domain0 Next Arg1 : 0x87000000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes
Domain0 SysSuspend : yes
Boot HART ID : 0
Boot HART Domain : root
Boot HART Priv Version : v1.10
Boot HART Base ISA : rv32imafdc
Boot HART ISA Extensions : zicntr
Boot HART PMP Count : 16
Boot HART PMP Granularity : 2 bits
Boot HART PMP Address Bits: 32
Boot HART MHPM Info : 0 (0x00000000)
Boot HART Debug Triggers : 0 triggers
Boot HART MIDELEG : 0x00000222
Boot HART MEDELEG : 0x0000b109
```
We see that our `Firmware Base : 0x80000000` is address where the firmware should be loaded from. You can also see that `Domain0 Region04 : 0x80000000-0x8003ffff M: (R,X) S/U: ()` this region is marked as X for executable.
How do we know that the serial UART is at 0x10000000? This UART is defined as a NS16550 Uart in https://www.qemu.org/docs/master/system/riscv/virt.html
Looking at the [UART16550 Core technical manual](https://uart16550.readthedocs.io/en/latest/uart16550doc.html#registers) we see that `Transmitter Holding Register (THR)` is at Address 0. Lastly, we want to enter the qemu console, so `Ctrl-A C` will take us to the qemu console. We want to inspect the `info mtree` output we can see that the `0000000010000000-0000000010000007 (prio 0, i/o): serial` serial device memory location starts at 0x10000000. So since the register the THR is at 0, we know we can use that as our UART target.
```
(qemu) info mtree
(qemu) info mtree
address-space: memory
0000000000000000-ffffffffffffffff (prio 0, i/o): system
0000000000001000-000000000000ffff (prio 0, rom): riscv_virt_board.mrom
0000000000100000-0000000000100fff (prio 0, i/o): riscv.sifive.test
0000000000101000-0000000000101023 (prio 0, i/o): goldfish_rtc
0000000002000000-0000000002003fff (prio 0, i/o): riscv.aclint.swi
0000000002004000-000000000200bfff (prio 0, i/o): riscv.aclint.mtimer
0000000003000000-000000000300ffff (prio 0, i/o): gpex_ioport_window
0000000003000000-000000000300ffff (prio 0, i/o): gpex_ioport
000000000c000000-000000000c20ffff (prio 0, i/o): riscv.sifive.plic
0000000010000000-0000000010000007 (prio 0, i/o): serial
0000000010001000-00000000100011ff (prio 0, i/o): virtio-mmio
0000000010002000-00000000100021ff (prio 0, i/o): virtio-mmio
0000000010003000-00000000100031ff (prio 0, i/o): virtio-mmio
0000000010004000-00000000100041ff (prio 0, i/o): virtio-mmio
0000000010005000-00000000100051ff (prio 0, i/o): virtio-mmio
0000000010006000-00000000100061ff (prio 0, i/o): virtio-mmio
0000000010007000-00000000100071ff (prio 0, i/o): virtio-mmio
0000000010008000-00000000100081ff (prio 0, i/o): virtio-mmio
0000000010100000-0000000010100007 (prio 0, i/o): fwcfg.data
0000000010100008-0000000010100009 (prio 0, i/o): fwcfg.ctl
0000000010100010-0000000010100017 (prio 0, i/o): fwcfg.dma
0000000020000000-0000000021ffffff (prio 0, romd): virt.flash0
0000000022000000-0000000023ffffff (prio 0, romd): virt.flash1
0000000030000000-000000003fffffff (prio 0, i/o): alias pcie-ecam @pcie-mmcfg-mmio 0000000000000000-000000000fffffff
0000000040000000-000000007fffffff (prio 0, i/o): alias pcie-mmio @gpex_mmio_window 0000000040000000-000000007fffffff
0000000080000000-0000000087ffffff (prio 0, ram): riscv_virt_board.ram
0000000300000000-00000003ffffffff (prio 0, i/o): alias pcie-mmio-high @gpex_mmio_window 0000000300000000-00000003ffffffff
address-space: I/O
0000000000000000-000000000000ffff (prio 0, i/o): io
address-space: cpu-memory-0
0000000000000000-ffffffffffffffff (prio 0, i/o): system
0000000000001000-000000000000ffff (prio 0, rom): riscv_virt_board.mrom
0000000000100000-0000000000100fff (prio 0, i/o): riscv.sifive.test
0000000000101000-0000000000101023 (prio 0, i/o): goldfish_rtc
0000000002000000-0000000002003fff (prio 0, i/o): riscv.aclint.swi
0000000002004000-000000000200bfff (prio 0, i/o): riscv.aclint.mtimer
0000000003000000-000000000300ffff (prio 0, i/o): gpex_ioport_window
0000000003000000-000000000300ffff (prio 0, i/o): gpex_ioport
000000000c000000-000000000c20ffff (prio 0, i/o): riscv.sifive.plic
0000000010000000-0000000010000007 (prio 0, i/o): serial
0000000010001000-00000000100011ff (prio 0, i/o): virtio-mmio
0000000010002000-00000000100021ff (prio 0, i/o): virtio-mmio
0000000010003000-00000000100031ff (prio 0, i/o): virtio-mmio
0000000010004000-00000000100041ff (prio 0, i/o): virtio-mmio
0000000010005000-00000000100051ff (prio 0, i/o): virtio-mmio
0000000010006000-00000000100061ff (prio 0, i/o): virtio-mmio
0000000010007000-00000000100071ff (prio 0, i/o): virtio-mmio
0000000010008000-00000000100081ff (prio 0, i/o): virtio-mmio
0000000010100000-0000000010100007 (prio 0, i/o): fwcfg.data
0000000010100008-0000000010100009 (prio 0, i/o): fwcfg.ctl
0000000010100010-0000000010100017 (prio 0, i/o): fwcfg.dma
0000000020000000-0000000021ffffff (prio 0, romd): virt.flash0
0000000022000000-0000000023ffffff (prio 0, romd): virt.flash1
0000000030000000-000000003fffffff (prio 0, i/o): alias pcie-ecam @pcie-mmcfg-mmio 0000000000000000-000000000fffffff
0000000040000000-000000007fffffff (prio 0, i/o): alias pcie-mmio @gpex_mmio_window 0000000040000000-000000007fffffff
0000000080000000-0000000087ffffff (prio 0, ram): riscv_virt_board.ram
0000000300000000-00000003ffffffff (prio 0, i/o): alias pcie-mmio-high @gpex_mmio_window 0000000300000000-00000003ffffffff
address-space: gpex-root
0000000000000000-ffffffffffffffff (prio 0, i/o): bus master container
memory-region: pcie-mmcfg-mmio
0000000000000000-000000001fffffff (prio 0, i/o): pcie-mmcfg-mmio
memory-region: gpex_mmio_window
0000000000000000-ffffffffffffffff (prio 0, i/o): gpex_mmio_window
0000000000000000-ffffffffffffffff (prio 0, i/o): gpex_mmio
memory-region: system
0000000000000000-ffffffffffffffff (prio 0, i/o): system
0000000000001000-000000000000ffff (prio 0, rom): riscv_virt_board.mrom
0000000000100000-0000000000100fff (prio 0, i/o): riscv.sifive.test
0000000000101000-0000000000101023 (prio 0, i/o): goldfish_rtc
0000000002000000-0000000002003fff (prio 0, i/o): riscv.aclint.swi
0000000002004000-000000000200bfff (prio 0, i/o): riscv.aclint.mtimer
0000000003000000-000000000300ffff (prio 0, i/o): gpex_ioport_window
0000000003000000-000000000300ffff (prio 0, i/o): gpex_ioport
000000000c000000-000000000c20ffff (prio 0, i/o): riscv.sifive.plic
0000000010000000-0000000010000007 (prio 0, i/o): serial
0000000010001000-00000000100011ff (prio 0, i/o): virtio-mmio
0000000010002000-00000000100021ff (prio 0, i/o): virtio-mmio
0000000010003000-00000000100031ff (prio 0, i/o): virtio-mmio
0000000010004000-00000000100041ff (prio 0, i/o): virtio-mmio
0000000010005000-00000000100051ff (prio 0, i/o): virtio-mmio
0000000010006000-00000000100061ff (prio 0, i/o): virtio-mmio
0000000010007000-00000000100071ff (prio 0, i/o): virtio-mmio
0000000010008000-00000000100081ff (prio 0, i/o): virtio-mmio
0000000010100000-0000000010100007 (prio 0, i/o): fwcfg.data
0000000010100008-0000000010100009 (prio 0, i/o): fwcfg.ctl
0000000010100010-0000000010100017 (prio 0, i/o): fwcfg.dma
0000000020000000-0000000021ffffff (prio 0, romd): virt.flash0
0000000022000000-0000000023ffffff (prio 0, romd): virt.flash1
0000000030000000-000000003fffffff (prio 0, i/o): alias pcie-ecam @pcie-mmcfg-mmio 0000000000000000-000000000fffffff
0000000040000000-000000007fffffff (prio 0, i/o): alias pcie-mmio @gpex_mmio_window 0000000040000000-000000007fffffff
0000000080000000-0000000087ffffff (prio 0, ram): riscv_virt_board.ram
0000000300000000-00000003ffffffff (prio 0, i/o): alias pcie-mmio-high @gpex_mmio_window 0000000300000000-00000003ffffffff
```

@ -0,0 +1,11 @@
SECTIONS
{
. = 0x80000000;
.text : {
*(.text)
}
. = ALIGN (CONSTANT (COMMONPAGESIZE));
.data : {
*(.data)
}
}

Binary file not shown.

Binary file not shown.

@ -0,0 +1,27 @@
.equ UART_BASE, 0x10000000 # a constant containing the base address for the uart that we will send data.
# This UART is defined as a NS16550 Uart in https://www.qemu.org/docs/master/system/riscv/virt.html
# UART16550 Core technical manual
.section .text
la a0, helloworld # Load address of string
li a1, UART_BASE # Load uart tx base address. This will be used to send characters to the uart
call puts # Print string
loop: j loop # This loop never exits, so the program will just continue to loop.
# This is function
puts:
# a0 - String address
# a1 - UART Base address
1: # While string bytes is no null
lb t0, 0(a0) # Get byte at current string pos
beq zero, t0, 2f # is null?
sb t0, (a1) # Since not null write byte to port
addi a0, a0, 1 # Implement string pointer moving us to the next character in the string.
j 1b # Loop
2: # String byte is null.
ret
.section .data
helloworld: .string "Hello Chuck!\n"

@ -0,0 +1,7 @@
default:
rm -rf hello
riscv64-linux-gnu-as hello.s -o hello.o
riscv64-linux-gnu-gcc -o hello hello.o -nostdlib -static
run:
qemu-system-riscv64 -machine virt -nographic -kernel ./hello -bios none

Binary file not shown.

Binary file not shown.

@ -0,0 +1,30 @@
.global _start
# let's us export an elf file so the linker can see it
_start:
### Invoking the Write syscalls ###
# Check `man 2 write`
# ssize_t write(int fd, const void *buf, size_t count)
# stdout FD =1 by the way in standard linux
# Setup syscall number in a7 to be the write syscall (which is 64)
addi a7, zero, 64
# put the file descriptor value into a0
addi a0, zero, 1
# We need to load into a1 the address of our helloworld label
la a1, helloworld
# Now we need to set the last argument to the string of the string we want to write
addi a2, zero, 15
ecall
### SYSTEM EXIT ###
# Adding into a7 the number 93 which is syscall number for exit
addi a7, zero, 93
# check man exit `void exit(int status)`. We need to put into a0 the argument to exit syscall
addi a0, zero, 13
# invoke the syscall
ecall
# This is a label called helloworld.
# The string is 15 bytes long since this is ascii
helloworld:
.ascii "Hello, RISC V\n"
Loading…
Cancel
Save