commit 320c30dac3a66ddc9acb9840444ce52a73d665e6 Author: Drew Bednar Date: Sun Aug 18 21:54:33 2024 -0400 Initial commits diff --git a/README.md b/README.md new file mode 100644 index 0000000..d768097 --- /dev/null +++ b/README.md @@ -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) \ No newline at end of file diff --git a/RISC-V-ALE/hello/Makefile b/RISC-V-ALE/hello/Makefile new file mode 100644 index 0000000..c20d876 --- /dev/null +++ b/RISC-V-ALE/hello/Makefile @@ -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 diff --git a/RISC-V-ALE/hello/README.md b/RISC-V-ALE/hello/README.md new file mode 100644 index 0000000..0157568 --- /dev/null +++ b/RISC-V-ALE/hello/README.md @@ -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. + diff --git a/RISC-V-ALE/hello/hello b/RISC-V-ALE/hello/hello new file mode 100755 index 0000000..7688be9 Binary files /dev/null and b/RISC-V-ALE/hello/hello differ diff --git a/RISC-V-ALE/hello/hello.o b/RISC-V-ALE/hello/hello.o new file mode 100644 index 0000000..1c6fe0f Binary files /dev/null and b/RISC-V-ALE/hello/hello.o differ diff --git a/RISC-V-ALE/hello/hello.s b/RISC-V-ALE/hello/hello.s new file mode 100644 index 0000000..72a4fc0 --- /dev/null +++ b/RISC-V-ALE/hello/hello.s @@ -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" diff --git a/chuckstechtalk/hello_asm/Makefile b/chuckstechtalk/hello_asm/Makefile new file mode 100644 index 0000000..1e36597 --- /dev/null +++ b/chuckstechtalk/hello_asm/Makefile @@ -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 diff --git a/chuckstechtalk/hello_asm/README.md b/chuckstechtalk/hello_asm/README.md new file mode 100644 index 0000000..8b3f755 --- /dev/null +++ b/chuckstechtalk/hello_asm/README.md @@ -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 +``` \ No newline at end of file diff --git a/chuckstechtalk/hello_asm/baremetal.ld b/chuckstechtalk/hello_asm/baremetal.ld new file mode 100644 index 0000000..75d69ab --- /dev/null +++ b/chuckstechtalk/hello_asm/baremetal.ld @@ -0,0 +1,11 @@ +SECTIONS +{ + . = 0x80000000; + .text : { + *(.text) + } + . = ALIGN (CONSTANT (COMMONPAGESIZE)); + .data : { + *(.data) + } +} diff --git a/chuckstechtalk/hello_asm/hello b/chuckstechtalk/hello_asm/hello new file mode 100755 index 0000000..c2a8624 Binary files /dev/null and b/chuckstechtalk/hello_asm/hello differ diff --git a/chuckstechtalk/hello_asm/hello.o b/chuckstechtalk/hello_asm/hello.o new file mode 100644 index 0000000..becc979 Binary files /dev/null and b/chuckstechtalk/hello_asm/hello.o differ diff --git a/chuckstechtalk/hello_asm/hello.s b/chuckstechtalk/hello_asm/hello.s new file mode 100644 index 0000000..ec19693 --- /dev/null +++ b/chuckstechtalk/hello_asm/hello.s @@ -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" + diff --git a/chuckstechtalk/helloc/hello.c b/chuckstechtalk/helloc/hello.c new file mode 100644 index 0000000..e69de29 diff --git a/lowlevellearning/Makefile b/lowlevellearning/Makefile new file mode 100644 index 0000000..4dc70a2 --- /dev/null +++ b/lowlevellearning/Makefile @@ -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 diff --git a/lowlevellearning/hello b/lowlevellearning/hello new file mode 100755 index 0000000..497beb5 Binary files /dev/null and b/lowlevellearning/hello differ diff --git a/lowlevellearning/hello.o b/lowlevellearning/hello.o new file mode 100644 index 0000000..94776ed Binary files /dev/null and b/lowlevellearning/hello.o differ diff --git a/lowlevellearning/hello.s b/lowlevellearning/hello.s new file mode 100644 index 0000000..f447d71 --- /dev/null +++ b/lowlevellearning/hello.s @@ -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"