GateMate RISC-V
2025-12-17 17:58Смотрите какая симпатичная штука: компьютер с процессором RISC-V и видеопортом VGA на немецком чипе FPGA. Плата GateMateA1-EVB от фирмы Olimex. Подробности спрашивайте у
ymz5 .

Скомпилируем и запустим на симуляторе. Предварительно надо в файле run.sh настроить пути к clang и llvm-objcopy.$ git clone https://github.com/nuta/operating-system-in-1000-lines.git
$ cd operating-system-in-1000-lines
$ wc *.c *.h
96 252 2321 common.c
576 1985 17559 kernel.c
38 113 1092 shell.c
47 134 1160 user.c
33 120 994 common.h
178 459 4745 kernel.h
14 39 294 user.h
982 3102 28165 total
Ядро стартовало, и мы видим приглашение для ввода команды. Примитивный шелл распознаёт только hello, readfile, writefile и exit.$ brew install llvm lld qemu
$ ./run.sh
+ QEMU=qemu-system-riscv32
+ CC=/usr/local/Cellar/llvm/19.1.6/bin/clang
+ OBJCOPY=/usr/local/Cellar/llvm/19.1.6/bin/llvm-objcopy
+ CFLAGS='-std=c11 -O2 -g3 -Wall -Wextra --target=riscv32 -ffreestanding -nostdlib'
+ /usr/local/Cellar/llvm/19.1.6/bin/clang -std=c11 -O2 -g3 -Wall -Wextra --target=riscv32 -ffreestanding -nostdlib -Wl,-Tuser.ld -Wl,-Map=shell.map -o shell.elf shell.c user.c common.c
+ /usr/local/Cellar/llvm/19.1.6/bin/llvm-objcopy --set-section-flags .bss=alloc,contents -O binary shell.elf shell.bin
+ /usr/local/Cellar/llvm/19.1.6/bin/llvm-objcopy -Ibinary -Oelf32-littleriscv shell.bin shell.bin.o
+ /usr/local/Cellar/llvm/19.1.6/bin/clang -std=c11 -O2 -g3 -Wall -Wextra --target=riscv32 -ffreestanding -nostdlib -Wl,-Tkernel.ld -Wl,-Map=kernel.map -o kernel.elf kernel.c common.c shell.bin.o
+ cd disk
+ tar cf ../disk.tar --format=ustar hello.txt meow.txt
+ qemu-system-riscv32 -machine virt -bios default -nographic -serial mon:stdio --no-reboot -d unimp,guest_errors,int,cpu_reset -D qemu.log -drive id=drive0,file=disk.tar,format=raw,if=none -device virtio-blk-device,drive=drive0,bus=virtio-mmio-bus.0 -kernel kernel.elf
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 : uart8250
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), 10 KB (used), 22 KB (free)
Firmware Scratch Size : 4096 B (total), 244 B (used), 3852 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 : 0x10000000-0x10000fff M: (I,R,W) S/U: (R,W)
Domain0 Region02 : 0x02000000-0x0200ffff M: (I,R,W) S/U: ()
Domain0 Region03 : 0x80040000-0x8004ffff M: (R,W) S/U: ()
Domain0 Region04 : 0x80000000-0x8003ffff M: (R,X) S/U: ()
Domain0 Region05 : 0x0c400000-0x0c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06 : 0x0c000000-0x0c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07 : 0x00000000-0xffffffff M: () S/U: (R,W,X)
Domain0 Next Address : 0x80200000
Domain0 Next Arg1 : 0x87e00000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes
Domain0 SysSuspend : yes
Boot HART ID : 0
Boot HART Domain : root
Boot HART Priv Version : v1.12
Boot HART Base ISA : rv32imafdch
Boot HART ISA Extensions : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu
Boot HART PMP Count : 16
Boot HART PMP Granularity : 2 bits
Boot HART PMP Address Bits: 32
Boot HART MHPM Info : 16 (0x0007fff8)
Boot HART Debug Triggers : 2 triggers
Boot HART MIDELEG : 0x00001666
Boot HART MEDELEG : 0x00f0b509
virtio-blk: capacity is 3072 bytes
file: hello.txt, size=83
file: meow.txt, size=6
> _
Ничо так, прикольно. Полная история на английском здесь: Operating System in 1,000 Lines. На самом деле чувак просто научился переключаться между режимом ядра и режимом юзера.> hello
Hello world from shell!
> readfile
Can you see me? Ah, there you are! You've unlocked the achievement "Virtio Newbie!"
> writefile
wrote 2560 bytes to disk
> readfile
Hello from shell!
> exit
process 2 exited
PANIC: kernel.c:564: switched to idle process


.text
cubic_root:
fmv.d fa3, fa0 # n in register fa3
fld fa2, three, a5 # constant 3.0
fld fa0, one, a5 # x = 1.0, initial guess
loop:
fdiv.d fa5, fa3, fa0 # n / x
fmv.d fa4, fa0 # old x in register fa4
fdiv.d fa5, fa5, fa0 # n / x / x
fadd.d fa5, fa5, fa0 # + x
fadd.d fa5, fa5, fa0 # + x
fdiv.d fa0, fa5, fa2 # divide by 3.0
feq.d a5, fa4, fa0 # nx == x?
beqz a5, loop # otherwise to next iteration
ret
one: .double 1.0
three: .double 3.0На этот раз мне лениво писать на ассемблере печать вещественного числа. Воспользуемся стандартной функцией printf(). Её вариант __printf_chk можно вызывать прямо из ассемблера.
.globl main
main:
fld fs0, input, a5 # input value
fmv.d fa0, fs0 # copy to permanent register
call cubic_root
li a0, 2
lla a1, format
fmv.x.d a2, fs0 # input value
fmv.x.d a3, fa0 # square root
call __printf_chk
li a0, 0 # exit status
call _exit
input: .double 2.0
format: .string "cubic root of %.15g is %.15g\n"
Компилируем, запускаем.Всё оказывается ничуть не сложнее, чем с целочисленными вычислениями.$ cc -o cubic_root cubic_root.s
$ ./cubic_root
cubic root of 2 is 1.25992104989487
.text
factorial:
mv t0, a0 # Устанавливаем начальное значение счётчика
1: addi t0, t0, -1 # Уменьшаем счётчик в регистре t0
beqz t0, 2f # Если получился ноль, идём на выход
mul a0, a0, t0 # Умножаем регистр результата на счётчик
j 1b # На следующую итерацию цикла
2: ret # Возвращаем результат в регистре a0
Попробуем вызвать эту функцию и показать результат... Однако натыкаемся на проблему! Как напечатать целое число из ассемблера? Из Си это сделать нетрудно, есть развитая библиотека libc, а в ней printf(). Ничего подобного не существует для ассемблера. Попытка вызвать Си-шный printf() из ассемблера плохо заканчивается. Потому что библиотека libc не проинициализирована толком. Для этого нужно линковаться с crt0.o и прочей ерундой. Выходит слишком громоздко.Понадобится также процедура печати произвольной текстовой строки.#include <sys/syscall.h>
.text
.globl print_uns
print_uns:
mv a2, sp # end
addi sp, sp, -32 # allocate buf
mv a1, a2 # ptr
li a3, 10 # base
1:
remu a5, a0, a3 # value % base
addi a1, a1, -1 # --ptr
addi a5, a5, 48 # + '0'
sb a5, 0(a1) # *ptr = character
mv a5, a0 # old value
divu a0, a0, a3 # value /= base
bgeu a5, a3, 1b # if (old value >= base) continue
sub a2, a2, a1 # end - ptr
li a0, 1 # stdout
li a7, SYS_write # write() system call
ecall
addi sp, sp, 32
ret
Заметьте, чтобы напечатать строку, требуется сначала посчитать её длину. Делаем функцию strlen().#include <sys/syscall.h>
.text
.globl print_string
print_string:
addi sp, sp, -16 # allocate space in stack
sd ra, 0(sp) # save return address
sd a0, 8(sp) # save string pointer
call strlen
mv a2, a0 # byte count
ld a1, 8(sp) # restore string pointer
li a0, 1 # stdout
li a7, SYS_write # write() system call
ecall
ld ra, 0(sp) # restore return address
addi sp, sp, 16 # free space in stack
ret
.text
.globl strlen
strlen: # a0 = const char *str
addi t1, a0, 1 # ptr + 1
1:
lb t0, 0(a0) # get byte from string
addi a0, a0, 1 # increment pointer
bnez t0, 1b # continue if not end
sub a0, a0, t1 # compute length - 1 for '\0' char
retНу и отдельная процедура для выдачи конца строки.Теперь можем соорудить вызов факториала и показать результат.#include <sys/syscall.h>
.text
.globl print_newline
print_newline:
li a7, SYS_write # write() system call
li a0, 1 # stdout
la a1, newline # string
li a2, 1 # one character
ecall
ret
newline:
.string "\n"
Компилируем, запускаем.#include <sys/syscall.h>
.globl _start
_start:
ld a0, input
call print_uns
la a0, text
call print_string
ld a0, input
call factorial
call print_uns
call print_newline
li a7, SYS_exit # exit the program
li a0, 0 # status code
ecall
.align 3
input: .dword 20
text: .string "! = "
Всё работает как положено. Размер программы 268 байт. Но становится понятно, почему народ перестал программировать на ассемблере. Всякую мелось приходится делать самому: никаких полезных библиотек. Напомню, что всё это происходит под Ubuntu на процессоре PIC64.$ cpp factorial.S | as -o factorial.o -
$ cpp print_uns.S | as -o print_uns.o -
$ cpp print_newline.S | as -o print_newline.o -
$ cpp print_string.S | as -o print_string.o -
$ as -o strlen.o strlen.s
$ ld -o factorial factorial.o print_uns.o print_newline.o print_string.o strlen.o
$ file factorial
factorial: ELF 64-bit LSB executable, UCB RISC-V, double-float ABI, version 1 (SYSV), statically linked, not stripped
$ size factorial
text data bss dec hex filename
268 0 0 268 10c factorial
$ ./factorial
20! = 2432902008176640000
$ cat hello.S
#include <sys/syscall.h>
.section .data
hello: .asciz "Hello, world!\n"
.section .text
.globl _start
_start:
li a7, SYS_write # system call to print a string
li a0, 1 # stdout
la a1, hello # text to print
li a2, 14 # how many bytes
ecall
li a7, SYS_exit # exit the program
li a0, 0 # status code
ecall
Компилируем, запускаем.
$ cpp hello.S | as - -o hello.o
$ ld hello.o -o hello
$ file hello
hello: ELF 64-bit LSB executable, UCB RISC-V, double-float ABI, version 1 (SYSV), statically linked, not stripped
$ size hello
text data bss dec hex filename
36 15 0 51 33 hello
$ ./hello
Hello, world!
Размер бинарного кода: 36 байт команд и 15 байт данных.

Nanoseconds for one run through Dhrystone: 521.2
Million Dhrystones per Second: 1.919
DMIPS: 1092.1Это 14% от Raspberry Pi 4 или 30% от Raspberry Pi 3B.

Nanoseconds for one run through Dhrystone: 151.0
Million Dhrystones per Second: 6.621
DMIPS: 3768.4 Не сказать чтобы супер быстро: всего на 4% шустрее Raspberry Pi 3B. И почти в два раза (48%) медленнее Raspberry Pi 4. Но всё же намного лучше, чем нежавейка. Без радиатора даже на холостом ходу процессор греется до 57ºC градусов. Лучше ставить вентилятор.


Выходит 53% от Raspberry Pi 3B или 25% от Raspberry Pi 4. Нешустро, зато и потребление никакое.$ git clone https://github.com/sergev/dhrystone.git
$ cd dhrystone
$ make
...
$ ./dhrystone
...
Nanoseconds for one run through Dhrystone: 293.9
Million Dhrystones per Second: 3.403
DMIPS: 1936.5
$ git clone https://github.com/cnlohr/mini-rv32ima.git
[...]
$ cd mini-rv32ima
$ make testdlimage
[...]
./mini-rv32ima -f DownloadedImage
[ 0.000000] Linux version 5.18.0 (cnlohr@cnlohr-1520) (riscv32-buildroot-linux-uclibc-gcc.br_real (Buildroot -g91b88fa1) 10.3.0, GNU ld (GNU Binutils) 2.37) #7 Sun Nov 27 07:07:08 EST 2022
[ 0.000000] Machine model: riscv-minimal-nommu,qemu
[ 0.000000] earlycon: uart8250 at MMIO 0x10000000 (options '1000000')
[ 0.000000] printk: bootconsole [uart8250] enabled
[ 0.000000] Zone ranges:
[ 0.000000] Normal [mem 0x0000000080000000-0x0000000083ffefff]
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000080000000-0x0000000083ffefff]
[ 0.000000] Initmem setup node 0 [mem 0x0000000080000000-0x0000000083ffefff]
[ 0.000000] riscv: base ISA extensions aim
[ 0.000000] riscv: ELF capabilities aim
[ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 16255
[ 0.000000] Kernel command line: earlycon=uart8250,mmio,0x10000000,1000000 console=ttyS0
[ 0.000000] Dentry cache hash table entries: 8192 (order: 3, 32768 bytes, linear)
[ 0.000000] Inode-cache hash table entries: 4096 (order: 2, 16384 bytes, linear)
[ 0.000000] Sorting __ex_table...
[ 0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off
[ 0.000000] Memory: 61936K/65532K available (1346K kernel code, 271K rwdata, 149K rodata, 1105K init, 108K bss, 3596K reserved, 0K cma-reserved)
[ 0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
[ 0.000000] riscv-intc: 32 local interrupts mapped
[ 0.000000] clint: clint@11000000: timer running at 1000000 Hz
[ 0.000000] clocksource: clint_clocksource: mask: 0xffffffffffffffff max_cycles: 0x1d854df40, max_idle_ns: 3526361616960 ns
[ 0.000000] sched_clock: 64 bits at 1000kHz, resolution 1000ns, wraps every 2199023255500ns
[ 0.000561] Console: colour dummy device 80x25
[ 0.000788] Calibrating delay loop (skipped), value calculated using timer frequency.. 2.00 BogoMIPS (lpj=4000)
[ 0.001130] pid_max: default: 4096 minimum: 301
[ 0.001424] Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[ 0.001701] Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[ 0.004787] devtmpfs: initialized
[ 0.009819] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
[ 0.010165] futex hash table entries: 16 (order: -5, 192 bytes, linear)
[ 0.024223] clocksource: Switched to clocksource clint_clocksource
[ 0.068572] workingset: timestamp_bits=30 max_order=14 bucket_order=0
[ 0.110245] Serial: 8250/16550 driver, 1 ports, IRQ sharing disabled
[ 0.112745] printk: console [ttyS0] disabled
[ 0.112968] 10000000.uart: ttyS0 at MMIO 0x10000000 (irq = 0, base_baud = 1048576) is a XR16850
[ 0.113313] printk: console [ttyS0] enabled
[ 0.113313] printk: console [ttyS0] enabled
[ 0.113595] printk: bootconsole [uart8250] disabled
[ 0.113595] printk: bootconsole [uart8250] disabled
[ 0.158521] Freeing unused kernel image (initmem) memory: 1104K
[ 0.158744] This architecture does not have kernel memory protection.
[ 0.159003] Run /init as init process
Welcome to Buildroot
buildroot login: root
Jan 1 00:00:02 login[27]: root login on 'console'
~ # uname -a
Linux buildroot 5.18.0 #7 Sun Nov 27 07:07:08 EST 2022 riscv32 GNU/Linux
~ # free
total used free shared buff/cache available
Mem: 63040 2784 58460 0 1796 56960
~ # ps
PID USER TIME COMMAND
1 root 0:00 init
2 root 0:00 [kthreadd]
3 root 0:00 [kworker/0:0-eve]
4 root 0:00 [kworker/0:0H]
5 root 0:00 [kworker/u2:0-ev]
6 root 0:00 [mm_percpu_wq]
7 root 0:00 [ksoftirqd/0]
8 root 0:00 [kdevtmpfs]
9 root 0:00 [writeback]
10 root 0:00 [kworker/u2:1-ev]
11 root 0:00 [kswapd0]
12 root 0:00 [kworker/0:1-eve]
13 root 0:00 [kworker/u2:2-ev]
27 root 0:00 -sh
30 root 0:00 ps
~ # ls -l /
total 4
drwxr-xr-x 2 root root 0 Nov 27 2022 bin
drwxr-xr-x 4 root root 0 Jan 1 00:00 dev
drwxr-xr-x 5 root root 0 Nov 27 2022 etc
-rwxr-xr-x 1 root root 462 Nov 27 2022 init
drwxr-xr-x 2 root root 0 Nov 27 2022 lib
lrwxrwxrwx 1 root root 3 Nov 27 2022 lib32 -> lib
lrwxrwxrwx 1 root root 11 Nov 27 2022 linuxrc -> bin/busybox
drwxr-xr-x 2 root root 0 Nov 27 2022 media
drwxr-xr-x 2 root root 0 Nov 27 2022 mnt
drwxr-xr-x 2 root root 0 Nov 27 2022 opt
dr-xr-xr-x 49 root root 0 Jan 1 00:00 proc
drwx------ 2 root root 0 Jan 1 00:00 root
drwxr-xr-x 3 root root 0 Jan 1 00:00 run
drwxr-xr-x 2 root root 0 Nov 27 2022 sbin
dr-xr-xr-x 10 root root 0 Jan 1 00:00 sys
drwxrwxrwt 2 root root 0 Jan 1 00:00 tmp
drwxr-xr-x 6 root root 0 Nov 27 2022 usr
drwxr-xr-x 4 root root 0 Nov 27 2022 var
~ # _


