Lua, TIC-80, LÖVE,etc: Introduction to particles systems and games

Version en français ici
default code in TIC-80

A good language for easily developing games, interactive content and procedural art is Lua scripting language. This is a simple functional language with some limited oriented object language features. It is, due to its simplicity and bytecode compiling at start by default, one of the lightest and fastest script language. Some integrations like LÖVE media/games engine and API (very powerful), and TIC-80, that is more limited fantasy-computer inspired first by PICO-8 (also use Lua). They allow quick prototyping, for a final product in the same language or a later port in another language. Lua is also used as plug-in systems language, in lot of games, and tools, including desktop applications (like Blender), web application (like MediaWiki behind Wikimedia) or embedded world. In this domain, popular and open-source drone board control BetaFlight or Open-TX open source radio-command for commands like Taranis one. There is a complete online documentation of Lua on the official website. It is possible to embbed C libs/function in Lua programs with libffi. It as been created first for Python, CFFI, and there is FFI support for PHP too now. It also possible to embed Lua scripts in C program. I also just discovered when I wrote this article (thanks to the author of TIC-80, that there is also PicoC, a simple C language interpreter that so allow to control more finely/low level data structures. The binary size is about the same than Lua interpreter.

An interactive demo of how trigonometric functions workSo, after few years of looking time to time this language and tools, I started to play a bit more in end of 2020, and in few month I can said I made lot of progress in real-time programming. Doing and even finishing light games. I Needed to study again basic trigonometry (follow link for a simple interactive explanation), basic vector algebra and few other mathematics fun things, that I personally consider like puzzles games.

Banner of Falacy Gorx, pseudo 3d game, using lot of tables
I also wrote a short making-off article on itch.io in march, 2021, during a game jam competition (instead of coding ^^). All of this motive me to write more articles tutorials about real-time/vector and procedural generation programming. I will try to write a series explaining methods I used. I will try to keep it as simple as possible to everyone, but some basic knowledge in general programming and mathematics could help a lot in this field as in life in general.

So I start with a short introduction about what I use the most in my development, arrays of elements and some randomness:
* Tables and their common basic management related to animation logic
* Creating a table
* Procedural generation of the content of a table
* A simple example for cleaning a table
* Variation and cleaning of table depending on tests
* Compacting code a bit
* Procedural generation of the content of a table
* Simple graphic particle system example

Tables and their common basic management related to animation logic

Some general table usage in interactive applications

Most things are managed, for scalability, in tables, main actors (like players characters), objects, particles, active agents, etc. Object themselves, also often contains tables of sub-elements, thing as example about the parts of a caterpillar body.

So, most structures have generally 4 vital logic functions that can be managed different ways:
* Cleaning of the table, used when tables elements are no more used, but it is also wise to use it to initialise new general state, like changing from main menu to game for example.
* Initialisation of table (generally including cleaning if needed) and adding 1 to n elements
* Update of the table, states of elements, including their mechanical & physical relationship, positions, their state of drawable, interacting with other objects, and finally removing or creating a new element in the table if needed. All these parameters depend on lot of various critters.
* Output of the content of the tables (display, sound, etc).

You can test these examples in Lua command line interpreter by typing lua in a shell, and then copy-paste them, or by putting them in a file.lua, and calling it by:

lua file.lua

The last examples use TIC-80, that can be used in web browser and natively on several systems, including, Linux, Mac, Windows, Android, Rpi, etc. There is a Pro version, but I never used its functionalities, it’s good to help the developer that is very nice and worked a lot of it. The examples can also easily be ported to other environments.

Creating a table

We will use here an objs[] table (a table with name objs, short of objects). In Lua, by default, table start at 1 when you fill it by objs={elt1,elt2}, but it is still possible to have a 0 indexed element by using objs[0]=elt0. The table can be (re-)initialized empty with objs={}. If it wasn’t empty, Lua garbage collector will clean it. As I coded a lot in low level languages, such as C/C++ and assembly, I’m not a big fan of garbage collectors, but that’s really convenient for quick prototyping.

Content of the table can be simply printed with a classical for loop. For the table called obs, #objs allow to know the number elements currently availablee in the table.

objs={"ball","cube","player"}
for i=1,#objs do
 print(objs[i])
end

Procedural generation of the content of a table

particules systems for clouds, mountains, characters, table of elemets for dragonIn procedural generation, the best friend of Chaos also called Nature, is random function, that generate randomly numbers. In Lua core, math.random is dedicated to this. it accept a range as parameters, limited to integer and with crescent values only. We can as example, decide to generate a number between 1 to 6 included, as the total number of elements we want. If you try several times this function, it will display random value included between 1 and 6 each time. Like if you launched a classical 6 faces cubic dice.

print(math.random(1,6))

In an interactive or animated program, we generally want to create random object, and then varying a more managed way during a period of time. So we fill a table with random values, that can be reused later. We need to first (re-)initialize objs to an empty array, so it can be filled by the for loop.

objs={}
for i=1,math.random(1,6) do
 objs[i]={lt=math.random(1,50)}
end

We so generated a 1 to 6 random number of random 1 to 50 numbered assigned to a parameter we called lt parameter instead of the table directly. This kind of element can be accessed 2 ways in Lua, objs[i].lt or objs[i]["lt"], the second one can be useful in some situations, but we will see this later in another tutorial.
so now the table is filled, we can print values several times, they will be kept the same:

for i=1,#objs do
 print("objs["..i.."]="..objs[i].lt)
end

We use the Lua string concatenation here (symbol ..) to have more information about which element we see printed.

A simple example for cleaning a table

It is important for cleaning a table or removing elements from it to do the loop from last to first element index, as when an element is deleted, all following items in the table will have decremented index. We could then stride over some elements and remove elements we don’t want to.

for i=#objs,1,-1 do
 table.remove(objs,i)
end

In the table.remove() standard Lua function, arguments are the name of the table and the index of the element to remove.

Variation and cleaning of table depending on tests

It can be a good habit to use a short local pointer variable to the element parsed to have a shorter code inside the loop, as it will il most case be accessed a lot. We remove here an element when lt (short for lifetime) is gone to 0, else we decrement it.

for i=#objs,1,-1 do
 local o=objs[i]
 if o.lt<=0 then
  table.remove(objs,i)
 else
  o.lt=o.lt-1
 end
end

The o variable can’t be passed to table.remove(), as it is used as a pointer to an element of the table, not as the name of the table, and the second argument is an index of the table, not either a pointer to the element as o is.

We have now the general base of a particles system.

All particle systems work with generation, often using at least a bit of randomness, and lifetime of each particles as a base, other criteria can changes depending on kind of particles.

Compacting code a bit

I generally add the local variable on the for...do line to have a more readable/compact code and changes behind if...then or else keywords to have a more compact and still readable code:

for i=#objs,1,-1 do local o=objs[i]
 if o.lt<=0 then table.remove(objs,i)
 else o.lt=o.lt-1 end
end

And Lua allow to assign a function pointer to a variable, so I generally use the following trick to have a more compact and clear code :

m=math rnd=m.random

So m is assigned to math and then rnd to m(ath).random.

Warning: on constraint is to not use m variable in the same variable scope than m.* assignation. It’s so better to made this assignation at the beginning, so m can be used freely after.

Simple graphic particle system example

In this example we will simply add random x (horizontal axis) and y (vertical axis) position around a starting central point 120,70 we choose a variation of -15 to +15 pixels in each direction:

objs[i]={lt=rnd(10,30),x=120+rnd(-15,15),y=70+rnd(-15,15)}

Basic particle systemand made them randomly move of a length from 0 to 1 pixel and in direction left/right and up/down. So, we have:
For x-axis
* -1 = left
* 0 don’t move
* +1 = right
For y-axis
* -1 = up
* 0 don’t move
* +1 down

o.x = o.x+rnd(-1,1) o.y = o.y+rnd(-1,1)

In Lua functions are defined by function function_name(arg1,arg2,...) and finish by end.
In the case of TIC-80 for example, that has the advantage to have all embedded in one executable, the function TIC() is a function called periodically, at each new frame, allowing to made time related content (so animation) easily. The function cls(color) is used to clean the screen with color at index “color” as TIC-80 use a 16 colors palettes.

The place of the particles are drawn by circ() (circle) function.

The definition of the function is:

circ(X center, Y center, radius, colour)

We place the center of the circle at the coordinates of the object, it has here a radius of 1 and use the colour index 0, that is black by default with TIC-80.

circ((o.x, o.y, 1, 0)

The current lifetime that the particle stille have is printed just at the right of the particle here for the demonstration. In TIC-80 print(), is used to place screen on graphic screen, and trace() on console.

The part of the print arguments we use are defined the following way:

print(text, X start, Y start, color, fixed font, scale, small font)

So we place as text, the current lifetime of the particle, at the center position of the object (o.x, o.y) with an horizontal variation of +2 pixel (so right) and horizontal of -2 pixel (so top). We choose a fixed size (true), scale of 1, and use small font (true):

print(o.lt, o.x+2, o.y-2, 2, true, 1, true)

In lua we can define several variables in one line, by crossing names of variables and assignations. For example, here, x=5 y=4 can be wrote x,y=5,4.

m=math rnd=m.random
t=0
objs={}
for i=1,rnd(5,8) do
 objs[i]={lt=rnd(10,30),x=120+rnd(-15,15),y=70+rnd(-15,15)}
end
function TIC()
 cls(12)
 for i=#objs,1,-1 do local o=objs[i]
  if o.lt<=0 then table.remove(objs,i)
  else o.lt,o.x,o.y=o.lt-1,o.x+rnd(-1,1),o.y+rnd(-1,1)
   circ(o.x,o.y,1,0)
   print(o.lt,o.x+2,o.y-2,2,true,1,true)
  end
 end
 t=t+1
end

In the Exemple to download we put the generation in a function generate() and call it when table content is null (#objs==0) as a simple loop generation.

function generate()
 for i=1,rnd(5,8) do
  objs[i]={lt=rnd(10,30),x=120+rnd(-15,15),y=70+rnd(-15,15)}
 end
end

function TIC()
 ...
 if #objs==0 then generate()end
 t=t+1
end

If you want to see the particles without their numbers, you can simply comment the print line. To comment code in Lua, simply add two dash at the beginning of the comment.

   -- print(o.lt,o.x+2,o.y-2,2,true,1,true)

Android without Google and GNU/Linux with APK

Android will move from Linux kernel and open specs APK format to a more closed package format and to their closed source Fuschia kernel going away from Free Open Source software (FOSS) that make Google, Android, ChromeOS grow, but also that are used on most of the internet servers, on most of supercomputer in the world, on most spacecrafts, on most internet boxes, TVboxes, etc…

GNU/Linux on your phone now

At the same time GNU/Linux distributions for mobile grow a lot the 2 recent years, thanks to availability of PinePhone for developers for about 2 years. There are now at least 15 Linux distributions for phone and applications maturated a lot, including since few month MegaPixels hardware accelerted camera application, that use GL and other processor to accelerate rendering/computing of what camera receive.

PostMarketOS is a GNU/Linux distribution available for a bunch of available market phones, but not everything is managed an all devices.

GNU/Linux with Android APK compatibility layer

Just like there is Wine to run Windows application on GNU/Linux, there are also several systems allowing to run Android APK on your phone if needed. Jusr as Wine, beside the possibility of running Android in a qemu VM, you can also run these application inside your the standard GNU system.

AnBox allow to run APK/Android application inside a snap packaged container.

Google goes away from FOSS, afraid by the lost of market share, and they probably loose a lot from this, as Microsoft does by the past. Chinese company, thanks to Trump restrictions, embrace more FOSS.

Huawei the maker of the probably highest quality phones, that was going in first seller position just ahead Samsung, when Trump blocked it, already very active in Linux kernel and community for years, made HarmonyOS for their devices, a system, that continue tu uses Linux kernel for their power devices, and LiteOS for their low end/old devices. LiteOS is a FOSS embedded devices oriented light kernel. They keep the EMUI User Interface they used previously on Android, moving step by step away from it, and at the same time keep a compatibility layer with current APK open-source format.

Another Chinese company, JingOS, made high quality tablets, based on GNU/Linux distribution, that derivates from Ubuntu, with KDE tools and Android APK application compatibility using Anbox. And they are active with KDE and open source communities. Few demos here and here.

Removing closed source spywares from your current Android system as a transition phase

Beside long time F-droid Free software apk app repository (I use it for year as main source of APK).

You can remove Android pre-installed (memory+cpu+bandwidth thieves) applications, including NSA compliant one like Google, Amazon, FaceBook, Microsoft ones using from a computer with adb. This allowed me to have less bloated phone and to gain few more days of on-battery usage.

List packages:

adb shell pm list packages

See which one are related to facebook:

adb shell pm list packages facebook

For easier copy paste and have only the package name without package: prefix

adb shell pm list packages | cut -d: -f 2

The corresponding application, can be checked by using in a web browser, this does not work for some system applications, for example, for com.google.android.videos:

https://play.google.com/store/apps/details?id=com.google.android.videos

Remove never at least first seen, never used application:

# FaceBook spyware

adb shell pm uninstall --user 0 com.facebook.katana
adb shell pm uninstall --user 0 com.facebook.system
adb shell pm uninstall --user 0 com.facebook.appmanager

# Microsoft stuff
adb shell pm uninstall --user 0 com.microsoft.office.excel
adb shell pm uninstall --user 0 com.microsoft.office.onenote
adb shell pm uninstall --user 0 com.microsoft.office.powerpoint
adb shell pm uninstall --user 0 com.microsoft.office.word
adb shell pm uninstall --user 0 com.microsoft.skydrive # data thief

adb shell pm uninstall --user 0 com.google.android.apps.docs # Google Drive (data thief)
adb shell pm uninstall --user 0 com.google.android.apps.photos # Google photos (data thief)
adb shell pm uninstall --user 0 com.google.android.googlequicksearchbox # Google Search box (behavior thief)
adb shell pm uninstall --user 0 com.google.android.gm # GMail
adb shell pm uninstall --user 0 com.google.android.talk # Hangouts
adb shell pm uninstall --user 0 com.google.android.music # Google Play Music
adb shell pm uninstall --user 0 com.google.android.videos # Google Play Film & Series
adb shell pm uninstall --user 0 com.google.android.youtube # Youtube application

To be checked

adb shell pm uninstall --user 0 com.google.android.gms # Services Google Play

adb shell Dumpsys a dumper and debugger

adb shell dumpsys allow to dump application before removing them and reinstalling it after that.

I still didn’t tried that, will update it after my tests

adb shell dumpsys package package.name # dump package state
adb pm enable —user 0 nom_de_paquet

Linux syscall and RISC-V assembly

Sample of RISC-V assembly code

Syscall in Linux kernel, is an interface to access to kernel basic functions. They are described in section 2 of man pages. The introduction is in man 2 syscall (indirect system call), and the list of functions are described in man 2 syscalls. ”’Update:”’ System Calls in lectures of official Linux kernel documentation including “Linux system calls implementation”, “VDSO and virtual syscalls” and “Accessing user space from system calls”

This article follow previous one about RISC-V overall progress and available tools to play with, I will try to make a short article here about Linux syscall usage and the RISC-V assembly case.

Table of Content

* Description section of the man page
* Getting the list of function and how to access them
* Passing parameters
* Function number and registers of return values
* Return values and error code
* Compiling and executing on virtual environment
* Update: Bronzebeard assembler and its baremetal environment for real hardware

Description section of the man page

* syscall() is a small library function that invokes the system call whose assembly language interface has the specified number with the specified arguments. Employing
* syscall() is useful, for example, when invoking a system call that has no wrapper function in the C library.
* syscall() saves CPU registers before making the system call, restores the registers upon return from the system call, and stores any error returned by the system call in errno(3).
* Symbolic constants for system call numbers can be found in the header file .

You can find here function, like access to files open/close/read/write/flush, access to sockets, ioctl, uid, gid, pid, messages, ptrace, restart system, etc…

Getting the list of function and how to access them

As far I know, now only a part of syscall functions are accessible easily in assembly, they are defined in /usr/include/unistd.h, and function numbers assigned in ABI are defined in /usr/include/asm-generic/unistd.h.

The more practical match I found is using /usr/include/asm-generic/unistd.h to see which function are available and there respective manpage for the function header definition. For example:
* asm-generic: #define __NR_read 63
* man 2 read: ssize_t read(int fd, void *buf, size_t count);

The ABI with RISC_V as defined in man 2 syscall in section Architecture calling conventions use the registers following 2 tables rules.

Passing parameters

The second table in this section of the man page shows the registers used to pass the system call arguments.

Arch/ABI      arg1  arg2  arg3  arg4  arg5  arg6  arg7  Notes
──────────────────────────────────────────────────────────────
riscv         a0    a1    a2    a3    a4    a5    -

Here are the arguments in the order of the function definition, for example, in read (63) function:

ssize_t      read  ( int fd, void *buf, size_t count );
a0(result) = a7(63)( a0(fd),  a1(*buf),    a2(count) )

For remember, 3 standard I/O file descriptors are STDIN=0, STDOUT=1, STDERR=2, the other are used when opened a file with open and closed by close.

So we set the arguments as this. x0 is the always 0 register:

        addi  a0, x0, 0       # Set STDIN as sources
        la    a1, buffer_addr # load address of helloworld
        addi  a2, x0, 3       # reaad 3 bytes

Function number and registers of return values

And the first one give the register in which put the function number, that will receive return value and errno (error value)

Arch/ABI    Instruction           System  Ret  Ret  Error    Notes
                                  call #  val  val2
───────────────────────────────────────────────────────────────────
riscv       ecall                 a7      a0   a1   -

So for the read function, we need to put 63 (as found in /usr/include/asm-generic/unistd.h in register a7, and registers and following registers will receive the return values, a0 will receive system call result, and a1 an error message (the errno value).

ssize_t read(int fd, void *buf, size_t count);
a0    =  63 (    a0,       a1,         a2)

So can set them as this:

        addi  a7, x0, 63     # set called function as read()
        ecall                # call the function

Return values and error code

After the man page of read(2):
* On success, the number of bytes read is returned
* On error, -1 is returned, and errno is set to indicate the error.

So we can test first the return value of a0 and if a0 < 0 then we jump to part for display the error message, else we can simply display a OK message. So for the branching part, RISC-V in its super reduced set only have < (lt) and <= (le) comparators, you just need to swap registers to compute > (gt) and >= (ge), but this avoid lots more of transistors.

    addi a3,x0,0           # x3=0
    blt  a1,a3, error_seq  # if x1<0 branch to error_seq

We so use here the syscall write function (64) defined as:
* asm-generic: #define __NR_write 64
* man 2 write: ssize_t write(int fd, const void *buf, size_t count);
* So, registers: a0=1 (STDOUT), a1=*buf, a2=count, a7=64 (function number)

And we will finish with exit() syscall, defined as:
* asm-generic: #define __NR_exit 93
* man 2 exit: noreturn void _exit(int status);
* So, registers: a0=return code, a7=93 (function number)

    la    a1, ok           # load address (pseudo code) of ok string
    addi  a2, x0, 3        # set length of text to 3 (O + K + \n)
    addi  a7, x0, 64       # set ecall to write function
    ecall                  # Call the function

    addi  a0, x0, 0        # set return code to 0 (OK) for exit (93) function
    j     end              # unconditional jump to end before quit

error_seq:
    la    a1, error        # load address (pseudo code) of error string
    addi  a2, a2, 0x30     # add 0x30 (0 ASCII code) to the error code
    sb    a2, 7(a1)        # put the (byte) value at position 7 of Error string (before \n)
    addi  a2, x0, 10       # set now length of our string
    addi  a7, x0, 64       # set ecall to write function
    ecall                  # Call the function

    addi  a0, x0, -1       # set return code to -1 (error) for exit (93) function
end:
    addi    a7, x0, 93     # set ecall to exit (93) funciton
    ecall                  # Call linux to terminate the program

.data:
ok:     .ascii "OK\n"
error:  .ascii "Error:  \n"

RISC-V Longan nano

Compiling and executing on virtual environment

If you don't have a RISC-V hardware (can be found as low as 3€ now), you need to have a cross compiler and qemu for emulating instructions, or a whole system installed.

Packages needed for compiling on ArchLinux x86 or ARM for example.

sudo pacman -S riscv64-linux-gnu-gcc  riscv64-linux-gnu-glibc riscv64-elf-binutils riscv64-elf-binutils riscv64-elf-gcc riscv64-elf-gdb

Newlib is a lightweight RV32 (RISC-V 32bits) lightweight library for bare metal that can be used instead of a whole GNU system on embedded devices with low memory capacity (as Longan nano, less than 8€ with screen, see picture below, or 3€ Sipeed RV): riscv32-elf-newlib.

I made a simple shell script to don't have to remember the commands to assemble the code from an x86 platform (work also on ARM or RISC-V one) that take the .s as argument:

name=$1
riscv64-linux-gnu-as -march=rv64imac -o ${name}.o ${name}.s
riscv64-linux-gnu-ld -o ${name} ${name}.o

You can add a strip but better to avoid it if you need to debug it:

riscv64-elf-strip --strip-all ${name}

And it can be executed on non RISC-V platforms by using qemu-riscv64, if it doesn't depend on libraries or if you have them installed, it allow you to test it without having a full RISC-V system installed, qemu is so fantastic. On ArchLinux it is available in package qemu-arch-extra:

qemu-riscv64 ${name}

And can be disassembled (will probably use different instruction than your assembly code, due to RISC-V assembly pseudo-instructions:

riscv64-linux-gnu-objdump -d ${name}

Bronzebeard assembler and its baremetal environment for real hardware

RISC-V Longan nano

Update: Bronzebeard is an assembler with light baremetal environment builder for RISC-V, GD32VF103 as Longan Nano (about 8€ with a screen, as pictured on this article pictures) and Wio (similar board with an added ESP8266 SoC). I made an AUR package of Bronzebeard, and someone made a Mandelbrot set demo in 918 bytes pure RISC-V assembly. You can find some other example in the source of Bronzebeard. gd32vf103inator is a set of tools for GD32V, to manage from a simple random text editor.

RISC-V overall progress

WordPress is so cumbersome (brut long text in SQL DB and other misconception (WTF?), buggy (especially to multilingual content missing) and hard to maintain on long term that I didn’t posted for long time I want to migrate. You can test the new log engine (here specialised in TIC-80 256 bytes code on https://256b.popolon.org/.

I continued my travel toward RISC-V I started as said in a previous post in may 2018.

RISC-V Benefits

Among the benefit of RISC-V beside other implementations:
* Open source and without license fee availability, allowing everyone to participate, implement and have full specifications
* Highest modularity for a processor in specifications, You can reduce the core to only the set of functions you want for a specific tasks, allowing less transistor/more compact specialized cores, and then multiply cores with some specialized in some kind of tasks.
* Vector extension

As example of specific task oriented cores:
* You don’t need a SIMD if you have a more powerful DSP unit, and you can implement this DSP unit using a RISC-V core with Vector extension, that is more efficient that a simple SIMD for this kind of task.
* JIT extension could be useful for some dynamic web server using scripting language (as PHP, Ruby, JS), but less useful for static web server (PCRE-jit computation in servers like nginx is marginal a core could be dedicated on a processor with many cores without this extension). Alibaba Group (Chinese Amazon) developed it’s own RISC-V implementation for its web servers).
* The European processor Initiative started in 2018, with first goal to uses United Kingdom made ARM core for main processor (at this time ARM had solid build/compilation/system ecosystem) with several kind of computing accelerators based on RISC-V, called EPAC (European Processor Accelerators)(a.is). It is developed at Barcelone with several partners inside European Union(a.is), Sadly as most European union project it will help USA industry for manufacturing, (GlobalFoundries) instead of maintain, sustain and develop local knowledge, experience and workforce for independence, employment and smaller carbon footprint shipments (as China and United-States do). The first test version is a 22nm FPGA, the production one will be 12nm ASIC, and among the objectives, there will be to highest throughput while not going above the 1GHz top barrier, to achieve higher energy efficiency.

Operating System working on RISC-V in June 2021

First efforts with toward embedded operating systems as first available RISC-V chips was in this domain. As the RISC-V embedded market is mature, more effort are made toward mobile and desktop Operating Systems.

Several Linux distribution already work on RISC-V now. Next Debian Bulleye (release August 14 2021, already in beta), Fedora have a RISC-V flavor already working on the BeagleV Starlight open-source board and its StarFive JH7100 processor, and there is a port of Yocto.

For reference, here is instructions about making the RISCV64 virtual machine on Debian Wiki, and a more general guide to make Linux run on Qemu, RISC-V.

You can find the Debian RISC-V system image on this page, just click on to download (direct link). This image is made with Debian Quick Image Baker (dqib)

To run it, you also need OpenSBI (Open Source Supervisor Binary Interface) needed to load the bootloader (Documentation, Prebuild deb packages) I used deb package on Archlinux:

ar x ../opensbi_0.9-1_all.deb
SBIPATH=`pwd`
(cd /; sudo tar xf "$SBIPATH/data.tar.xz")

And the Uboot for Qemu. I personally just extract and the directory that also contain, the “artifacts” Debian image.

By default image is in Qcow format, that is more compact for unused blocks, but also really slower.
You can convert it easily by:

qemu-img convert -f qcow2 -O raw image.qcow2 image.raw

Here is the boot.sh to launch it (you can replace image.raw by image.qcow2, if you have a verypowerfull system and only place count: I use 2 cores and 512M of RAM. For huge compilation I restart it with more mem (1536M is generally enough, but more means more cache and faster compilation).

cores=2
mem=512M
qemu-system-riscv64 -machine virt -cpu rv64 -smp ${cores} -m ${mem} \
  -device virtio-blk-device,drive=hd -drive file=image.raw,if=none,id=hd \
  -device virtio-net-device,netdev=net -netdev user,id=net,hostfwd=tcp::2222-:22 \
  -bios /usr/riscv64-linux-gnu/lib/opensbi/generic/fw_jump.elf \
  -kernel ../qemu-riscv64_smode/uboot.elf \
  -object rng-random,filename=/dev/urandom,id=rng -device virtio-rng-device,rng=rng \
  -nographic -append "root=LABEL=rootfs console=ttyS0"

The login/pass are root/root.

you can connect by ssh:

ssh -p 2222 root@127.0.0.1

and rsync files via:

rsync -a --rsh="ssh -p 2222" root@127.0.0.1:path/to/rsync .

I already managed to compile TIC-80 on it and run it on Qemu:

.

There is also BareDOOM a port of DOOM that run on RISC-V emulated in TinyEmu (source code in the same page, there is also a fork with additional features on GitHub, a system emulator for the RISC-V and x86 architectures), JSLinux allow to run it in a browser (There is also a Quake port on RISC-V K210 microcontroller based Sipeed Maix Bit board). There is also a patched version of buildroot for K210 (no-mmu), for building Linux on this platform.

There is an important Note in system images from Fabrice Bellard site to make binary working with TinyEMU:


- TinyEMU only supports raw boot loader and kernel images. So after
  building riscv-pk or the Linux kernel, you must convert the ELF
  image to a raw image with:

  riscv64-unknown-linux-gnu-objcopy -O binary bbl bbl.bin
  
  riscv64-unknown-linux-gnu-objcopy -O binary vmlinux kernel.bin

A port of Haiku has already well progressed, also use TinyEMU for the port.

Update 2 July 2021: Another RISC-V (RV32 only) emulator is Nemu (github) it is a part of XiangShan (香山) RISC-V open processor from Chinese Academy of Sciences (OpenXiangShan (Gitee)) made by Chinese Academy of Sciences, the processors should have the performances of an ARM Cortex-A72/A73 in july 2021 with a 28nm process in first batch (Yanqi hu / Yanqi Lake / 雁栖湖), and performances of an Intel i9-10900K in second batch ( Nanhu / South Lake / 南湖) in fall 2021, with 14nm process, both manufactured by TSMC, and it already run GNU/Linux distribution Debian. See also(中文).

Another Virtual Machine emulator for RISC-V is JuiceVM supporting RV64IMASU. It is lightweight, support small UART serial I/O and MMU

Available RISC-V Hardware in June 2021

Several developers have received a first beta version of BeagleV RISC-V board. Here are demos (on youtube sadly) of first tries of different devs at beginning of may "BeagleV RISC-V Computer (Beta)" (unboxing), "BeagleV from BeagleBoard.org Fedora image Live Booting - 1" (booting), now BeagleV give a prepared Fedora with XFCE working on HDMI.


I have now 2 embedded microcontroller oriented RISC-V Board from Sipeed, one 32bits and one MaixDuino SipeedM1 (microPython (by default), dual-core 64bits+AI module) of the young hackers, Shenzhen based, SiPeed company. The last one support lot of embedded systems including default MicroPython based MaixDuino system (and cool plateforrm.io interface, Arduino, PlatformIO, microPython, OpenMV, FreeRTOS, et TensorFlow, etc..

The Nezha board of SiPeed, is a bit more expansive and with lower specs in first version, but very promising for the future Nezha: Your first RV64 Linux SBC for IoT~ (video Youtube).

Huawei Hi3861 is oriented toward their LiteOS kernel that is used in their new Android compatible HarmonyOS.

The BL602/BL604 de Bouffalo Lab (RISC-V WiFi & Bluetooth 5.0 LE) based on ESP32-C (RISC-V variant of ESP32, pin2pin compatible) is a 100 % open source BootLoader & driver (no blob needed), thanks to the work of Lup Yuen Lee (李立源). He wrote lot of interesting articles about the process of making the driver and a book about this chip, and several article around Ecosystem, including using Rust, How to simulate it in WebAssembly, etc.

China Science Academy is currently working on optimizing Firefox (SpiderMonkey) and Chrome/ium (Webkit) engines for RISC-V 2000 laptops with equivalent power of a Cartex-A78 should be available at then end of 2022

At the same time the port of true GNU/Linux on embedded devices like phones progress fast, on ARM architecture, with specialized distributions like PostMarket OS (and it's derivative Alpine Linux), or more general distributions, including Debian, Manjaro (ArchLinux derivative) etc. Port of KDE Plasma and GTK4/GNOME40 (especially GTK4.1+ with very efficient OpenGL acceleration, are of great help in this area. PinePhone from Pine64.org community is really an active and efficient hub for these ports. This phone (developer oriented in current state) is at the same time cheap (<200€) have hard switch for all network features, and every part can be easily changed. Those project are all ready to be ported on RISC-V versions of this kind of devices. Pine64 is already working on RISC-V opensource bl602 driver (see above) and a RISC-V (GD32VF103TB) running a smart/programmable iron solderer, and a future RISC-V based mother board to replace the current ARM one on the PinePhone could be totally realistic. See also LINux on MOBile for news on the port of Linux on Mobile devices.

An interesting post (and blog) about RISC-V assembly programming from Stephen Marz, another blog about RISC-V assembly from Daniel Mangun and one from ieee.org Build a RISC-V CPU From Scratch. A simple HelloWorld example, with deep explanations, including Linux RISC-V Application Binary Interface (ABI), to start with RISC-V assembly, based on GNU Assembler (support RV32 & RV64). There is also a Python made, limited, RISC-V assembler. A complete tutorial in French for starting with the Sipeed Longan Nano, (available for ~5 € with LCD screen).

HTML5 RISC-V Interpreter/simulator. There is also RARS a really slow/unusable Java RISC-V simulator (pacaur -S rars), jupiter (with link to other simulators) (pacaur -S jupiter) is another one, and spike (pacman -S spike) is a c made simulator. Ripes (pacaur -S ripes-git) is a very advanced one, with multiple level of pipe, data/instruction cache states, etc... Venus with online simulator in Kotlin language.

Tutorial serie about designing RISC-V processor in VHDL.

I hope that with progress I made in real-time programming and different new programming languages in last few years (Lua, some bit of RISC-V assembly, JavaScript), I will make quickly some RISC-V demo on embedded board.

Tester un système Linux RISC-V avec qemu sur ARM et ArchLinuxARM

There is also an english tutorial with more informations here

Pour cela, on peut utiliser la distribution Busybear-linux, très limitée, mais déjà bien pour un premier test de Qemu avec RISC-V (Site officiel, le CPU à sources ouvertes et sous license libre). il a l’avantage de fournir un système pré-compilé, prêt à l’emploi ici : github.com/michaeljclark/busybear-linux/releases

Les deux fichiers indispensables sont :
* bbl.bz2 = boot
* busybear.bin.bz2 = système

Récupérer les fichiers de busybear et les décompresser :

ver=1.0
wget https://github.com/michaeljclark/busybear-linux/releases/download/v$ver/bbl.bz2
wget https://github.com/michaeljclark/busybear-linux/releases/download/v$ver/busybear.bin.bz2
bzcat bbl.bz2 >bbl
bzcat busybear.bin.bz2 >busybear.bin

Les explications que j’ai récupéré viennent du le wiki d’arch pour le périphérique réseau : //wiki.archlinux.fr/Qemu car BusyBear donne des instruction pour des anciens systèmes Debian/Ubuntu (avant le passage à Netplan).

Sur archlinux(ARM) nous avons donc besoin d’uml_utilities qui se trouve dans la grande yaourtière.

wget https://aur.archlinux.org/cgit/aur.git/snapshot/uml_utilities.tar.gz
tar xf uml_utilities.tar.gz
cd uml_utilities
makepkg -A # ignore l'architecture définie dans le PKGBUILD

Il faut ensuite passer en root (ou avoir mis des droits sudo globaux à l’utilisateur pour installer ce paquet :

su - root
cd dossier_de_l'archive
pacman -U uml_utilities*.pkg.tar.xz

Profiter d’être en root pour préparer l’interface réseau pour qemu (remplacer alarm par l’utilisateur de votre choix :

tunctl -u alarm -t tap0
ifconfig tap0 192.168.0.1 netmask 255.255.255.0 up

Puis se deloguer de root par logout ou exit.

Dans le dossier où sont placer les deux fichiers préalablement décompressés :

qemu-system-riscv64 \
  -nographic -machine virt -kernel bbl \
  -append "root=/dev/vda ro console=ttyS0" \
  -drive file=busybear.bin,format=raw,id=hd0 \
  -device virtio-blk-device,drive=hd0 \
  -net nic -net tap,ifname=tap0,script=no

Le boot sur busybear/RiscV va alors commencer. Il se bloc pendant quelques secondes sur le logo RISC-V en ASCII-art :

qemu-system-riscv64: warning: hub port hub0port0 has no peer
qemu-system-riscv64: warning: vlan 0 with no nics
qemu-system-riscv64: warning: netdev hub0port0 has no peer
qemu-system-riscv64: warning: requested NIC (anonymous, model unspecified) was not created (not supported by this machine?)
              vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
                  vvvvvvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrr       vvvvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrr      vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr    vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr    vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr    vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrr      vvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrr       vvvvvvvvvvvvvvvvvvvvvv
rr                vvvvvvvvvvvvvvvvvvvvvv
rr            vvvvvvvvvvvvvvvvvvvvvvvv      rr
rrrr      vvvvvvvvvvvvvvvvvvvvvvvvvv      rrrr
rrrrrr      vvvvvvvvvvvvvvvvvvvvvv      rrrrrr
rrrrrrrr      vvvvvvvvvvvvvvvvvv      rrrrrrrr
rrrrrrrrrr      vvvvvvvvvvvvvv      rrrrrrrrrr
rrrrrrrrrrrr      vvvvvvvvvv      rrrrrrrrrrrr
rrrrrrrrrrrrrr      vvvvvv      rrrrrrrrrrrrrr
rrrrrrrrrrrrrrrr      vv      rrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrr          rrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrr      rrrrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrrrr  rrrrrrrrrrrrrrrrrrrrrr

       INSTRUCTION SETS WANT TO BE FREE

On voit aux message du kernel que c’est encore un noyau assez simple :

[...]
[   10.190000] This architecture does not have kernel memory protection.
[...]

On arrive enfin au prompt où pour se connecter il faut utiliser :
* Identifiant : root
* Mot de passe : busybear.

Et voilà !

ucbvax login: root
Password:
    ____                   ____                     __    _
   / __ )__  _________  __/ __ )___  ____ ______   / /   (_)___  __  ___  __
  / __  / / / / ___/ / / / __  / _ \/ __ `/ ___/  / /   / / __ \/ / / / |/_/
 / /_/ / /_/ (__  ) /_/ / /_/ /  __/ /_/ / /     / /___/ / / / / /_/ />  <
/_____/\__,_/____/\__, /_____/\___/\__,_/_/     /_____/_/_/ /_/\__,_/_/|_|
                 /____/
root@ucbvax:~# cat /proc/cpuinfo
hart    : 0
isa     : rv64imafdcsu
mmu     : sv48

Pour configurer le réseau :

ifconfig sit0 192.168.0.10 netmask 255.255.255.0 up

On peut aussi le modifier dans /etc/hosts/interfaces pour qu'il soit pris en compte au reboot :

auto lo
iface lo inet loopback

auto sit0
iface sit0 inet static
  address 192.168.0.10
  netmask 255.255.255.0
  broadcast 192.168.0.255
  gateway 192.168.0.1

!!! Attention, les cache/resolever DNS par défaut dans /etc/resolv.conf sont ceux de Google !!! Changer par un de votre réseau ou de votre fournisseur d'accès. Ceux de Google :

root@ucbvax:~# cat /etc/resolv.conf 
nameserver 8.8.8.8
nameserver 8.8.4.4