Improve window system for RISC-V
阮祈翰
Main goal
Run Mado on rv32emu by using SDL system call and RV32IM ISA (with B extension)
Research Focus
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.
- Install xpm
- To test if xpm starts
- Install xPack GNU RISC-V Embedded GCC globally
- PATH setup (Make sure to check the version manually in order to set the correct $PATH)
- Quick test
- Write a C code
- Compile and run on rv32emu
Try using SDL in rv32emu
- Run in rv32emu
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Download Mado
- Download needed package then make
- Run demo program with SDL backend
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Analyzing Mado
- When using the command
make
.
-
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.
-
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
-
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.
-
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
.
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 to implement window display.
Implement new graphic backend rv32emu.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];
int mod;
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,
};
typedef struct {
uint32_t keycode;
uint8_t state;
uint16_t mod;
} rv_key_rv_event_t;
typedef struct {
int32_t x, y, xrel, yrel;
} rv_mouse_motion_t;
enum {
RV_MOUSE_BUTTON_LEFT = 1,
};
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
#endif
#endif
- Modify
mado/configs/Kconfig
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
- Set environment variables
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.

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