# Improve window system for RISC-V > 阮祈翰 ## Main goal Run [Mado](https://github.com/sysprog21/mado) on [rv32emu](https://github.com/sysprog21/rv32emu) by using [SDL](https://www.libsdl.org/) system call and RV32IM ISA (with B extension) ### Research Focus - Learn how to use [SDL](https://www.libsdl.org/) system call in [rv32emu](https://github.com/sysprog21/rv32emu) - Learn how to transplant [Mado](https://github.com/sysprog21/mado) onto [rv32emu](https://github.com/sysprog21/rv32emu) ## Step by step ### Download xPack GNU RISC-V Embedded GCC - A standalone cross-platform (Windows, macOS, GNU/Linux) binary distribution of GNU RISC-V Embedded GCC, intended for reproducible builds. 1. Install [xpm](https://xpack.github.io/xpm/docs/install/) ```shell $ wget -q -O - https://raw.githubusercontent.com/xpack/assets/master/scripts/install-nvm-node-npm-xpm.sh | ${SHELL} $ export NVM_DIR="$HOME/.nvm" $ source "$NVM_DIR/nvm.sh" $ npm install --location=global xpm@latest ``` 2. To test if [xpm](https://xpack.github.io/xpm/docs/install/) starts ```shell $ which xpm /home/eric/.nvm/versions/node/v22.13.0/bin/xpm $ xpm --version 0.20.5 ``` 3. Install [xPack GNU RISC-V Embedded GCC](https://xpack-dev-tools.github.io/riscv-none-elf-gcc-xpack/) globally ```shell $ xpm install @xpack-dev-tools/riscv-none-elf-gcc@latest --global --verbose ``` 4. PATH setup (Make sure to check the version manually in order to set the correct $PATH) ```shell $ export PATH=$HOME/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/14.2.0-2.1/.content/bin:$PATH ``` 5. Quick test ```shell $ riscv-none-elf-gcc --version ``` ```shell riscv-none-elf-gcc (xPack GNU RISC-V Embedded GCC x86_64) 14.2.0 Copyright (C) 2024 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ``` 6. Write a C code ```c= //hello.c #include <stdio.h> int main(){ printf("hello world!!!\n"); return 0; } ``` 7. Compile and run on rv32emu ```shell riscv-none-elf-gcc -march=rv32gc -mabi=ilp32 -o hello hello.c $ build/rv32emu hello ``` ```shell hello world!!! inferior exit code 0 ``` ### Try using SDL in rv32emu - Write a C code ```c= #include <stdio.h> int main(){ int syscall_num = 0xbeef; int width = 1000; int height = 600; static int buffer[1000 * 600]; int i = 0; for(;i < 1000 * 300;i++){ buffer[i] = 0xFF0000; } for(;i < 1000 * 600;i++){ buffer[i] = 0xFBFF00; } register int* a0 asm("a0") = &buffer[0]; register int a1 asm("a1") = width; register int a2 asm("a2") = height; while(1) { register int a7 asm("a7") = syscall_num; asm volatile ("scall"); } return 0; } ``` - Run in rv32emu ![Screenshot from 2025-01-19 13-47-10](https://hackmd.io/_uploads/SkUffMqw1x.png) ### Download Mado 1. Download needed package then make ```shell $ sudo apt install libjpeg-dev libpng-dev $ sudo apt install libpixman-1-dev $ sudo apt install libsdl2-dev $ make config $ make ``` 2. Run demo program with SDL backend ```shell $ ./demo-sdl ``` ![Screenshot from 2025-01-12 14-14-03](https://hackmd.io/_uploads/S1ph6RlDkl.png) ### Analyzing Mado - When using the command `make`. ```shell $ make DEP .libtwin.a/src/work.c.d DEP .libtwin.a/src/window.c.d DEP .libtwin.a/src/widget.c.d ... CC .demo-sdl/apps/main.c.o LINK .demo-sdl/demo-sdl CP demo-sdl ``` 1. The files in `app` and `src` will implement `Dependency Generation (DEP)` and `Compile (CC)` then write into `.libapps.a/apps` and `.libtwin.a/src` respectively. 2. `AR` stands for "Archive tool". It collects all the `.o` file to `.a` file which become a static library. In this case, we collect files in `.libapps.a/apps` to `.libapps.a/libapps.a` and files in `.libtwin.a/src` to `.libtwin.a/libtwin.a` 3. `RANLIB` stands for "Random Library Index Generator." It creates an index table for `.a` static libraries. This index table contains the symbol information of all `.o` files within the library, allowing for quick location of required functions or variables during the linking process. 4. implement `DEP` and `CC` on `app/main.c` and `LINK` the following files : `.demo-sdl/apps/main.c.o` `libapps.a` `libtwin.a` in order to create executable file `.demo-sdl/demo-sdl`. - Check file description ```shell $ file demo-sdl demo-sdl: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f554f2df4c8710331ec9b5cb8bfb511e763d58cd, for GNU/Linux 3.2.0, not stripped ``` This tell us `demo-sdl` is 64-bit executable file and is built in `x86-64` ISA. The final goal is to turn it to 32-bit which built in `riscv-none-elf-gcc` and run on `rv32emu`. Since `riscv-none-elf-gcc` is not able to access third-party packages like `libsdl2-dev` or `libjpeg-dev`. I have to write a new graphic backend `rv32emu.c` which call the system calls in [rv32emu](https://github.com/sysprog21/rv32emu) to implement window display. - Check `mado/mk/toolchain.mk`, changed the compiler to prebuild [xPack GNU RISC-V Embedded GCC](https://xpack-dev-tools.github.io/riscv-none-elf-gcc-xpack/). ```diff CC_IS_CLANG := CC_IS_GCC := # FIXME: Cross-compilation using Clang is not supported. ifdef CROSS_COMPILE CC := $(CROSS_COMPILE)gcc endif + CC = riscv-none-elf-gcc + CFLAGS += -std=gnu99 + CFLAGS += -march=rv32im + CFLAGS += -mabi=ilp32 override CC := $(shell which $(CC)) ifndef CC $(error "Valid C compiler not found.") endif ``` ### Implement new graphic backend `rv32emu.c` - Code for rv32emu.c ```c #ifndef MADO #define MADO #include <stdint.h> #include <stdlib.h> #include <string.h> #include <time.h> struct mado { const char *title; const int width, height; uint32_t *buf; int keys[256]; /* keys are mostly ASCII, but arrows are 17..20 */ int mod; /* mod is 4 bits mask, ctrl=1, shift=2, alt=4, meta=8 */ int x, y; int mouse; void *event_queue; uint32_t event_count; size_t event_queue_start; }; #ifndef MADO_API #define MADO_API extern #endif MADO_API int mado_open(struct mado *m); MADO_API int mado_loop(struct mado *m); MADO_API void mado_close(struct mado *m); MADO_API void mado_sleep(int64_t ms); MADO_API int64_t mado_time(void); #define mado_pixel(m, x, y) ((m)->buf[((y) * (m)->width) + (x)]) #ifndef MADO_HEADER #define RV_QUEUE_CAPACITY 128 enum { RV_KEYCODE_RETURN = 0x0000000D, RV_KEYCODE_UP = 0x40000052, RV_KEYCODE_DOWN = 0x40000051, RV_KEYCODE_RIGHT = 0x4000004F, RV_KEYCODE_LEFT = 0x40000050, RV_KEYCODE_LCTRL = 0x400000E0, RV_KEYCODE_RCTRL = 0x400000E4, RV_KEYCODE_LSHIFT = 0x400000E1, RV_KEYCODE_RSHIFT = 0x400000E5, RV_KEYCODE_LALT = 0x400000E2, RV_KEYCODE_RALT = 0x400000E6, RV_KEYCODE_LMETA = 0x400000E3, RV_KEYCODE_RMETA = 0x400000E7, }; //KEY_EVENT typedef struct { uint32_t keycode; uint8_t state; uint16_t mod; } rv_key_rv_event_t; //MOUSE_MOTION_EVENT typedef struct { int32_t x, y, xrel, yrel; } rv_mouse_motion_t; enum { RV_MOUSE_BUTTON_LEFT = 1, }; //MOUSE_BUTTON_EVENT typedef struct { uint8_t button; uint8_t state; } rv_mouse_button_t; enum { RV_KEY_EVENT = 0, RV_MOUSE_MOTION_EVENT = 1, RV_MOUSE_BUTTON_EVENT = 2, RV_QUIT_EVENT = 3, }; typedef struct { uint32_t type; union { rv_key_rv_event_t key_event; union { rv_mouse_motion_t motion; rv_mouse_button_t button; } mouse; }; } rv_event_t; enum { RV_RELATIVE_MODE_SUBMISSION = 0, RV_WINDOW_TITLE_SUBMISSION = 1, }; typedef struct { uint8_t enabled; } rv_mouse_rv_submission_t; typedef struct { uint32_t title; uint32_t size; } rv_title_rv_submission_t; typedef struct { uint32_t type; union { rv_mouse_rv_submission_t mouse; rv_title_rv_submission_t title; }; } rv_submission_t; MADO_API int mado_open(struct mado *m) { m->event_queue = malloc((sizeof(rv_event_t) + sizeof(rv_submission_t)) * RV_QUEUE_CAPACITY); m->event_count = 0; register int a0 __asm("a0") = (int) m->event_queue; register int a1 __asm("a1") = RV_QUEUE_CAPACITY; register int a2 __asm("a2") = (int) &m->event_count; register int a7 __asm("a7") = 0xc0de; __asm volatile("scall" : : "r"(a0), "r"(a1), "r"(a2), "r"(a7) : "memory"); m->event_queue_start = 0; rv_submission_t submission = { .type = RV_WINDOW_TITLE_SUBMISSION, .title.title = (uint32_t) m->title, .title.size = strlen(m->title), }; rv_submission_t *submission_queue = (rv_submission_t *) ((rv_event_t *) m->event_queue + RV_QUEUE_CAPACITY); submission_queue[0] = submission; a0 = 1; a7 = 0xfeed; __asm volatile("scall" : : "r"(a0), "r"(a7) : "memory"); return 0; } MADO_API void mado_close(struct mado *m) { free(m->event_queue); } MADO_API int mado_loop(struct mado *m) { for (; m->event_count > 0; m->event_count--) { rv_event_t event = ((rv_event_t *) m->event_queue)[m->event_queue_start]; switch (event.type) { case RV_KEY_EVENT: { uint32_t keycode = event.key_event.keycode; uint8_t state = event.key_event.state; if (keycode == RV_KEYCODE_RETURN) m->keys[10] = state; else if (keycode < 128) m->keys[keycode] = state; if (keycode == RV_KEYCODE_UP) m->keys[17] = state; if (keycode == RV_KEYCODE_DOWN) m->keys[18] = state; if (keycode == RV_KEYCODE_RIGHT) m->keys[19] = state; if (keycode == RV_KEYCODE_LEFT) m->keys[20] = state; if (keycode == RV_KEYCODE_LCTRL || keycode == RV_KEYCODE_RCTRL) m->mod = state ? m->mod | 1 : m->mod & 0b1110; if (keycode == RV_KEYCODE_LSHIFT || keycode == RV_KEYCODE_RSHIFT) m->mod = state ? m->mod | 2 : m->mod & 0b1101; if (keycode == RV_KEYCODE_LALT || keycode == RV_KEYCODE_RALT) m->mod = state ? m->mod | 4 : m->mod & 0b1011; if (keycode == RV_KEYCODE_LMETA || keycode == RV_KEYCODE_RMETA) m->mod = state ? m->mod | 8 : m->mod & 0b0111; break; } case RV_MOUSE_MOTION_EVENT: m->x = event.mouse.motion.x; m->y = event.mouse.motion.y; break; case RV_MOUSE_BUTTON_EVENT: if (event.mouse.button.button == RV_MOUSE_BUTTON_LEFT) m->mouse = event.mouse.button.state; break; case RV_QUIT_EVENT: return -1; } m->event_queue_start = (m->event_queue_start + 1) & (RV_QUEUE_CAPACITY - 1); } register int a0 __asm("a0") = (uintptr_t) m->buf; register int a1 __asm("a1") = m->width; register int a2 __asm("a2") = m->height; register int a7 __asm("a7") = 0xbeef; __asm volatile("scall" : : "r"(a0), "r"(a1), "r"(a2), "r"(a7) : "memory"); return 0; } #undef RV_QUEUE_CAPACITY MADO_API int64_t mado_time(void) { return (int64_t) clock() / (CLOCKS_PER_SEC / 1000.0f); } MADO_API void mado_sleep(int64_t ms) { int64_t start = mado_time(); while (mado_time() - start < ms) ; } #ifdef __cplusplus class Mado { struct mado m; int64_t now; public: Mado(const int w, const int h, const char *title) : m{.title = title, .width = w, .height = h} { this->m.buf = new uint32_t[w * h]; this->now = mado_time(); mado_open(&this->m); } ~Mado() { mado_close(&this->m); delete[] this->m.buf; } bool loop(const int fps) { int64_t t = mado_time(); if (t - this->now < 1000 / fps) { mado_sleep(t - now); } this->now = t; return mado_loop(&this->m) == 0; } inline uint32_t &px(const int x, const int y) { return mado_pixel(&this->m, x, y); } bool key(int c) { return c >= 0 && c < 128 ? this->m.keys[c] : false; } int x() { return this->m.x; } int y() { return this->m.y; } int mouse() { return this->m.mouse; } int mod() { return this->m.mod; } }; #endif /* __cplusplus */ #endif /* !MADO_HEADER */ #endif /* MADO_H */ ``` - Code for main.c ```diff +#include "../backend/rv32emu.c" int main(void) { + enum { W = 640, H = 480 }; + uint32_t buf[W * H]; + struct mado m = {.title = "mado", .width = W, .height = H, .buf = buf}; + mado_open(&m); + uint32_t t = 0; + while(mado_loop(&m) == 0){ + t++; + for (int i = 0; i < 320; i++) { + for (int j = 0; j < 240; j++) + mado_pixel(&m, i, j) = i ^ j ^ t; /* Munching squares */ + } + mado_sleep(100); + } + mado_close(&m); + return 0; - tx = twin_create(WIDTH, HEIGHT); - if (!tx) - return 1; //scroll to bottom - twin_dispatch(tx); ``` - Modify `mado/configs/Kconfig` ```diff choice prompt "Backend Selection" default BACKEND_SDL + config BACKEND_rv32emu + bool "rv32emu output support" config BACKEND_FBDEV bool "Linux framebuffer support" select CURSOR config BACKEND_SDL bool "SDL video output support" config BACKEND_VNC bool "VNC server output support" endchoice ``` - Modify `Makefile` ```diff # Graphical backends BACKEND := none +ifeq ($(CONFIG_BACKEND_rv32emu), y) +BACKEND = rv32emu +libtwin.a_files-y += backend/rv32emu.c +libtwin.a_cflags-y += -I$(HOME)/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/14.2.0-2.1/.content/riscv-none-elf/include +endif ifeq ($(CONFIG_BACKEND_SDL), y) BACKEND = sdl libtwin.a_files-y += backend/sdl.c libtwin.a_cflags-y += $(shell sdl2-config --cflags) TARGET_LIBS += $(shell sdl2-config --libs) endif ``` - Modify `mado/src/draw.c` ```diff -#if defined(__APPLE__) #include <machine/endian.h> -#else -#include <endian.h> -#endif ``` ### Add RISCV version packages Third-party packages like `libjpeg` and `libpng` are prepared for x86-64 projects. So I have to compile those packages to riscv version using the cross-compiler [xPack GNU RISC-V Embedded GCC](https://xpack-dev-tools.github.io/riscv-none-elf-gcc-xpack/) - Set environment variables ```shell $ PACKAGE_PREFIX=$HOME/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/14.2.0-2.1/.content/riscv-none-elf ``` ```shell # build libjpeg and libpng $ sudo tar xzf jpegsrc.v9f $PACKAGE_PREFIX $ cd jpeg-9f $ ./configure –host=riscv-none-elf-gcc –prefix=$PACKAGE_PREFIX $ make $ sudo make install $ sudo tar xzf libpng-1.5.12 $PACKAGE_PREFIX $ cd libpng-1.5.12 $ ./configure –host=riscv-none-elf-gcc –prefix=$PACKAGE_PREFIX $ make $ sudo make install ``` ### Run `demo-rv32emu` Compare to original demo file `demo-sdl`, I took away the jpeg, png and gif file from the makefile, just barely implement system call for display window. ```shell $ build/rv32emu demo-rv32emu ``` ![Screenshot from 2025-01-19 14-30-25](https://hackmd.io/_uploads/SJKG2zcwJl.png) ## Conclusion I implement cross-compile on mado from `x86-64` to `riscv-none-elf-gcc` and run on rv32emu, but I can only show an empty window without those pictures like the `demo-sdl`. Although I didn't finish my goal, I spend a lot time to do the research and actually learn knowledge about cross-compile and how to write Makefile. So it's still a great experience I think. ## Reference - https://github.com/sysprog21/mado - https://github.com/sysprog21/rv32emu - https://github.com/sysprog21/rv32emu/blob/master/tests/fenster.h - https://mortoray.com/android-ndk-cross-compile-setup-libpng-and-freetype/ - https://makefiletutorial.com/