├── .gitignore ├── LICENSE ├── README.md ├── hello.s ├── shell.nix ├── sifive_e ├── cfg.inc └── hello.ld └── sifive_u ├── cfg.inc └── hello.ld /.gitignore: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright: (c) Vo Minh Thu, 2019-2021. 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 3. Neither the name of the author nor the names of his contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bare metal RISC-V assembly hello world 2 | 3 | This is a bare metal 64-bit RISC-V assembly program outputing `Hello.`. It is 4 | compiled with the riscv-gnu-toolchain and can be run with the QEMU `sifive_u` 5 | and `sifive_e` machines. 6 | 7 | I searched for such a program on the Internet but the only examples I found 8 | were either bare metal C, or assembly but relying on an OS. Eventually I took 9 | the bare metal hello program from the 10 | [riscv-probe](https://github.com/michaeljclark/riscv-probe) repository and 11 | stripped everything I could. [The 12 | result](https://github.com/noteed/riscv-hello-c) can be disassembled and serve 13 | as a guide to adapt other hello world examples. 14 | 15 | 16 | ## Toolchain 17 | 18 | I'm using Nix and use a toolchain provided by Nixpkgs's [cross-compiling 19 | infrastructure](https://nixos.wiki/wiki/Cross_Compiling). This repository 20 | contains a `shell.nix` file, and the example commands in this README can be 21 | followed with either `nix-shell --attr riscv64` for the `sifive_u` case, or 22 | `nix-shell --attr riscv32` for the `sifive_e` case. 23 | 24 | Someone seems to have success with the SiFive binaries as seen in [the first 25 | issue](https://github.com/noteed/riscv-hello-asm/issues/1). 26 | 27 | 28 | ## Building for the `sifive_u` machine 29 | 30 | Assuming the toolchain is in the `$PATH`, running the following produces our 31 | `hello` program. 32 | 33 | ``` 34 | $ riscv64-unknown-linux-gnu-gcc -march=rv64g -mabi=lp64 -static -mcmodel=medany \ 35 | -fvisibility=hidden -nostdlib -nostartfiles -Tsifive_u/hello.ld -Isifive_u \ 36 | hello.s -o hello 37 | ``` 38 | 39 | The result is a 64-bit RISC-V binary compatible with QEMU `sifive_u` machine. 40 | 41 | ``` 42 | $ file hello 43 | hello: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically 44 | linked, not stripped 45 | ``` 46 | 47 | Run it with: 48 | 49 | ``` 50 | $ qemu-system-riscv64 -nographic -machine sifive_u -bios none -kernel hello 51 | Hello. 52 | QEMU: Terminated 53 | ``` 54 | 55 | Note: the program enters an infinite loop after producing the `Hello.` text. 56 | Type `ctrl-a x` to stop QEMU. 57 | 58 | 59 | ## Building for the `sifive_e` machine 60 | 61 | This program can be compiled for more resticted machines like `sifive_e` 62 | that support 32-bit RISC-V, have small amount of RAM and require executable 63 | code to be placed in ROM with different start address. 64 | 65 | Assuming the toolchain is in the `$PATH`, running the following produces our 66 | `hello` program, but now ready for `sifive_e`. 67 | 68 | ``` 69 | $ riscv32-none-elf-gcc -march=rv32g -mabi=ilp32 -static -mcmodel=medany \ 70 | -fvisibility=hidden -nostdlib -nostartfiles -Tsifive_e/hello.ld -Isifive_e \ 71 | hello.s -o hello 72 | ``` 73 | 74 | Note: using either `riscv32-none-elf-gcc` or `riscv64-unknown-linux-gnu-gcc` 75 | works. 76 | 77 | Run it with: 78 | 79 | ``` 80 | $ qemu-system-riscv32 -nographic -machine sifive_e -bios none -kernel hello 81 | Hello. 82 | QEMU: Terminated 83 | ``` 84 | 85 | Note: the program enters an infinite loop after producing the `Hello.` text. 86 | Type `ctrl-a x` to stop QEMU. 87 | 88 | 89 | ## Assembly 90 | 91 | To disassemble the program (here the one for the `sifive_u` machine): 92 | 93 | 94 | ``` 95 | $ riscv64-unknown-elf-objdump -d hello 96 | hello: file format elf64-littleriscv 97 | 98 | 99 | Disassembly of section .text: 100 | 101 | 0000000080000000 <_start>: 102 | 80000000: f14022f3 csrr t0,mhartid 103 | 80000004: 00029c63 bnez t0,8000001c 104 | 80000008: 00008117 auipc sp,0x8 105 | 8000000c: 04410113 addi sp,sp,68 # 8000804c <_end> 106 | 80000010: 00000517 auipc a0,0x0 107 | 80000014: 03450513 addi a0,a0,52 # 80000044 108 | 80000018: 008000ef jal ra,80000020 109 | 110 | 000000008000001c : 111 | 8000001c: 0000006f j 8000001c 112 | 113 | 0000000080000020 : 114 | 80000020: 100102b7 lui t0,0x10010 115 | 80000024: 00054303 lbu t1,0(a0) 116 | 80000028: 00030c63 beqz t1,80000040 117 | 8000002c: 0002a383 lw t2,0(t0) # 10010000 118 | 80000030: fe03cee3 bltz t2,8000002c 119 | 80000034: 0062a023 sw t1,0(t0) 120 | 80000038: 00150513 addi a0,a0,1 121 | 8000003c: fe9ff06f j 80000024 122 | 80000040: 00008067 ret 123 | ``` 124 | 125 | 126 | ## Elsewhere 127 | 128 | Here is a link to another repository that links back to this repository, and 129 | that may be worth checking out. 130 | 131 | - [Bare metal RISC-V assembly in QEMU](https://github.com/rtfb/riscv64-in-qemu) 132 | -------------------------------------------------------------------------------- /hello.s: -------------------------------------------------------------------------------- 1 | .align 2 2 | .include "cfg.inc" 3 | .equ UART_REG_TXFIFO, 0 4 | 5 | .section .text 6 | .globl _start 7 | 8 | _start: 9 | csrr t0, mhartid # read hardware thread id (`hart` stands for `hardware thread`) 10 | bnez t0, halt # run only on the first hardware thread (hartid == 0), halt all the other threads 11 | 12 | la sp, stack_top # setup stack pointer 13 | 14 | la a0, msg # load address of `msg` to a0 argument register 15 | jal puts # jump to `puts` subroutine, return address is stored in ra regster 16 | 17 | halt: j halt # enter the infinite loop 18 | 19 | puts: # `puts` subroutine writes null-terminated string to UART (serial communication port) 20 | # input: a0 register specifies the starting address of a null-terminated string 21 | # clobbers: t0, t1, t2 temporary registers 22 | 23 | li t0, UART_BASE # t0 = UART_BASE 24 | 1: lbu t1, (a0) # t1 = load unsigned byte from memory address specified by a0 register 25 | beqz t1, 3f # break the loop, if loaded byte was null 26 | 27 | # wait until UART is ready 28 | 2: lw t2, UART_REG_TXFIFO(t0) # t2 = uart[UART_REG_TXFIFO] 29 | bltz t2, 2b # t2 becomes positive once UART is ready for transmission 30 | sw t1, UART_REG_TXFIFO(t0) # send byte, uart[UART_REG_TXFIFO] = t1 31 | 32 | addi a0, a0, 1 # increment a0 address by 1 byte 33 | j 1b 34 | 35 | 3: ret 36 | 37 | .section .rodata 38 | msg: 39 | .string "Hello.\n" 40 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | # This file defines two Nix shells, one with a RISC-V 32-bit toolchain, the 2 | # other is 64-bit. The approach is outlined at 3 | # https://nixos.wiki/wiki/Cross_Compiling. 4 | # 5 | # Usage: 6 | # 7 | # nix-shell --attr riscv32 8 | # 9 | # then use `riscv32-none-elf-gcc` as compiler, or 10 | # 11 | # nix-shell --attr riscv64 12 | # 13 | # then use `riscv64-unknown-linux-gnu-gcc` as compiler. 14 | 15 | { pkgs ? import {} }: 16 | 17 | let 18 | riscv32-pkgs = import { 19 | crossSystem = (import ).systems.examples.riscv32-embedded; 20 | }; 21 | 22 | riscv64-pkgs = import { 23 | crossSystem = (import ).systems.examples.riscv64; 24 | }; 25 | 26 | in 27 | { 28 | riscv32 = riscv32-pkgs.mkShell { 29 | buildInputs = [ 30 | ]; 31 | }; 32 | 33 | riscv64 = riscv64-pkgs.mkShell { 34 | buildInputs = [ 35 | ]; 36 | nativeBuildInputs = [ 37 | # Uncomment to also bring QEMU, if you don't have it system-wide. 38 | # riscv64-pkgs.buildPackages.buildPackages.qemu 39 | riscv64-pkgs.buildPackages.gdb 40 | ]; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /sifive_e/cfg.inc: -------------------------------------------------------------------------------- 1 | .equ UART_BASE, 0x10013000 2 | -------------------------------------------------------------------------------- /sifive_e/hello.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | OUTPUT_FORMAT("elf32-littleriscv") 3 | ENTRY( _start ) 4 | SECTIONS 5 | { 6 | /* text: test code section */ 7 | . = 0x20400000; 8 | .text : { *(.text) } 9 | /* gnu_build_id: readonly build identifier */ 10 | .gnu_build_id : { *(.note.gnu.build-id) } 11 | /* rodata: readonly data segment */ 12 | .rodata : { *(.rodata) } 13 | 14 | /* data: Initialized data segment */ 15 | . = 0x80000000; 16 | .data : { *(.data) } 17 | .sdata : { *(.sdata) } 18 | .debug : { *(.debug) } 19 | . += 0x1000; 20 | stack_top = .; 21 | 22 | /* End of uninitalized data segement */ 23 | _end = .; 24 | } 25 | -------------------------------------------------------------------------------- /sifive_u/cfg.inc: -------------------------------------------------------------------------------- 1 | .equ UART_BASE, 0x10010000 2 | -------------------------------------------------------------------------------- /sifive_u/hello.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | OUTPUT_FORMAT("elf64-littleriscv") 3 | ENTRY( _start ) 4 | SECTIONS 5 | { 6 | /* text: test code section */ 7 | . = 0x80000000; 8 | .text : { *(.text) } 9 | /* data: Initialized data segment */ 10 | .gnu_build_id : { *(.note.gnu.build-id) } 11 | .data : { *(.data) } 12 | .rodata : { *(.rodata) } 13 | .sdata : { *(.sdata) } 14 | .debug : { *(.debug) } 15 | . += 0x8000; 16 | stack_top = .; 17 | 18 | /* End of uninitalized data segement */ 19 | _end = .; 20 | } 21 | --------------------------------------------------------------------------------