SubSky

A simple, minimalist, and easy-to-use RISC ISA intending to strike a reasonable balance between elegant simplicity and practical power.

Active Pure Nim score 65/100 · last commit 2026-08-27 · 1 stars · tests present · no docs generated

Summary

Latest Version Unknown
License Unknown
CI Status Failing
Stars 1
Forks 0
Open Issues 0
Last Commit 2026-08-27
Downloads 0
Last Indexed 2026-09-06 07:35

Installation

nimble install SubSky
choosenim install SubSky
git clone https://gitlab.com/golemwire/subsky

OS Compatibility

Platform Linux macOS Windows FreeBSD OpenBSD NetBSD Android iOS WASM Embedded
SubSky - - - - - - -

Source

Repository https://gitlab.com/golemwire/subsky
Homepage https://gitlab.com/golemwire/subsky
Registry Source gitlab

README

SubSky

SubSky is a simple, minimalist, and easy-to-use RISC ISA and computing environment. It strikes a reasonable balance with elegant simplicity and practical 32-bit range, intending to be the most amount of power you can get from the least amount of computer.

Project state: alpha

  • RISC.
  • 16 operations ("instructions").
    • Little to no overlap in function among operations.
  • 32-bit.
  • Up to 4GB of RAM.
  • Simple instruction encoding of four nybbles, allowing one to read machine code hexdumps with (relative!) ease.
  • No (architecturally-visible) status registers or flags.
  • Homogenous "Parentcalls" system for syscalls and I/O.
  • "Runpoints" callback system for simple reacting to externally-triggered events.
  • Hint-based "Dispatches" static ILP system.
  • Relative ease of implementing (for the CPU).
    • The line is drawn at the complexity level of having a signed integer divide instruction.
    • No floating-point (though the standard library provides Q16.8 fixed-point support).

There is no memory protection, no support for interrupts, and no multi-core -- yet.

SubSky, and every file in this repository, are licensed under the GNU LGPLv3 (see COPYING).




Who This is For

  • Software engineers who hold extreme simplicity as one of the highest ideals of software, all the way up the stack.
  • Computer scientists who want a computing system they can learn the workings of in its entirety in a reasonable amount of time.
  • Coders who love the low-level.
  • Power users who want a portable environment they can own, keep, and take to any future system.
  • Platform-decay-aware programmers who want a clean slate.
  • Permacomputing proponents who value reusability but need a larger word size & address space than most permacomputing VMs provide.
  • Adventurous programmers who want to try a novel system.

Where This is Going

The idea is to, in the end, have a complete, standalone computing environment, complete with the processor, interfaces, operating system, and userspace.
Present status:

  • Processor: fully functional in the emulator (Sbse).
    The hardware implementation is to be announced.
  • Interfaces: the primary HIDs are designed, and are mostly functional in the emulator.
  • Operating system: not done.
  • Userspace: not done. To program something for SubSky, you currently have to do so with an external toolchain; for this, the compiler/doctool Sbsc is provided, along with the minimal debugger in the emulator.

SubSky Chat Group

Join the SubSky Fluxer group! Fluxer is an open-source, self-hostable* Discord alternative. Group link: fluxer.gg/I2P03SBv. *Self-hostability is a WIP




Installing and Building

Currently only tested on Linux.

In the same way as other git repositories, you can run \$ git clone https://gitlab.com/golemwire/subsky.git to download SubSky, and \$ git pull (in the downloaded folder) to update it.

Emulator

\$ ./run.sh
The emulator should be able to be built on any platform with a C compiler, libSDL3, and virtual memory (which is all mainstream platforms; though the usage of mmap in this program is Linux-oriented).

Compiler

\$ cd sbsc
\$ ./run-test.sh
The compiler should be able to be built on any system with a Golang toolchain (which is all mainstream platforms).




Architecture Summary

See the SubSky file for the spec.

Instruction Format

From the SubSky spec:

lsb                               msb
0 1 2 3   4 5 6 7   8 9 A B   C D E F
Op        Q         A         B
  • Op: the operation of the instruction (i.e. "opcode").
  • Q: the destination operand; for some instructions, this is the third source operand.
  • A: the first source operand.
  • B: the second source operand.

Operations and Operands

From the emulator's ? debugging command. The left 4x4 has the operations, and the right 4x4 has the operands:

  +0  +1  +2  +3   +0 +1 +2 +3
0 Ior Xor And Sft  GT BP IP FP
4 Add Sbf Mul Div  SP SE S1 S2
8 Rwr Rbr Lth Cpt  L1 L2 L3 L4
C Adf Adt Wwr Wbr  L5 L6 L7 L8
  • Ior, Xor, And, Sft: bitwise operations.
  • Add, Sbf, Mul, Div: math operations.
  • Rwr, Rbr, Wwr, Wbr: memory operations.
  • Lth: less-than (actually, DOZ (Difference Or Zero) -- lots of neat tricks with this!).
  • Cpt, Adf, Adt: predicated instructions. Ad{t,f} are useful for conditional branching, like ADF IP <condition> &<destination>.

The last row of operations (Adf through Wbr) can read from all three instruction operands.

  • GT, General Temp: general-purpose register.
  • BP, Begin Pointer: register holding the address of the currently-running program.
  • IP, Instruction Pointer: register holding the address of the instruction to run next.
  • FP, Frame Pointer: register holding the address of the "locals" L1 -- L8.
  • SP, Stack Pointer: register holding the address of the very top of the stack (SP - 4 is the address of the stack top).
  • SE, Stack Element: the stack. Setting SE pushes, and getting SE pops. A lot revolves around this operand.
  • S1 -- S2: stack top and word under stack top. S1 is (SP - 4), and S2 is (SP - 8).
  • L1 -- L8: the "locals", used for most variables. L1 is (FP - 4), L2 is (FP - 8), and so on.

BP through SP, when in the B field, specify to load an immediate to use as B; the B operand code itself indicates how many bytes long the immediate is (BP in the B field means load a 1-byte immediate, IP in the B field means load a 2-byte immediate, etc.).

See the SubSky file for the full spec (about 300 lines, excluding extraneous information).

Parentcalls (syscalls and I/O)

To perform a parentcall, you need zero or more arguments, one command (1 word), and one device ID. Push a return address (IP is set to this when the parentcall returns), push the arguments (if any), push a command, push a device ID, then do RWR IP BP 0.

When IP is 0 at the end of an instruction, I/O is performed. The init runpoint (where execution begins) is at offset 4 in the program (BP + 4) and the program loader sets the first word (at BP) to a value called the PV, Parent Vector. The PV will be set to 0 if the loaded program is the "root" parent (e.g. the an OS kernel), and to the address of a syscall handler otherwise.




Emulator

The emulator is sbse, the SubSky Emulator.

  • Run \$sbse my_program.sbxe to emulate a SubSky system booting the program.
  • Run \$sbse with no options to run the default program, a short 7-byte program that crashes intentionally to bring up the debugger.
    You should see this:
    ERROR: System aborted.
    Reason: division by 0 (as canonical abort)
    Previous IP: #0000000A
    Error code: #FFFFFFFF
    
    Total cycles: 1
    
     // DEBUGGER //
    G | Get operands
    # | Set operand
    ? | Print reference
    M | Dump memory
    R | Execute instr
    @ | Toggle abort state
    Y | Yield
    H | Halt
    Q | Quit debugger
    [IP IS ZERO]
    :
    

Debugging

Type an option in the debugger (see the menu above) and hit enter to choose it.

  • G: Print all the operands that are currently gettable.
  • # (a single hex digit): Set an operand. (Asks for a value.)
  • ?: Print the table of SubSky's operations and operands.
  • M: Dump memory.
  • R: Run an instruction, specified in little-endian digit order. Currently does not work with immediates.
  • @: Toggle the abort state (see the SubSky spec).
  • Y: Yield from the current runpoint immediately (usually inadvisable).
  • H: Halt the CPU (stopping execution).
  • Q: Exit the debugger (resuming execution if possible).

One may insert a breakpoint into a source file by putting a @ mark in the relevant line of code (i.e. insert " @" into the indentation of the line you want to break on). See the section on the programming language.

The compiler has an option, --dbgfile, to generate from a program's source code a dbgfile, which is a list of significant addresses useful for debugging (see this example). In order to use the addresses obtained via the debugger with the dbgfile, one needs to subtract the value of BP from the debugger's addresses, since the program is offset by BP. The debugger does this automatically (look for "Pb:" in the output of the G option).
The compiler also has the option --outline, which can be used to generate an outline of source code. See the section on the compiler.




Programming Language Summary

SubSky's programming language is called Slang. See the Slang file for the WIP spec.

Slang is unusual in that the source code merely specifies directly what data literals are to be embedded into the file, in order. The source code can be read by the compiler in one pass. It is a low-level language, somewhere between assembly and C.
Its filename extensions are .s (for code that inserts data into the executable) and .h (for code that doesn't; i.e. definitions). .s was chosen, as Slang is the generic assembly language local to SubSky. That said, it notably allows you to express code in a more natural "cursive" form like GT = foo * bar instead of only a traditional assembly form like MUL GT foo bar or MUL GT L2 L5 (though you may do that too). It provides support for consts, macros (consts with fields), enumerations (auto-generated runs of consts), and struct definitions (which are mostly just field offset enumerations, and are thus consts).

Intro Example

Example program. It prints "Hello", the numbers 1 through 6, then "world". It and its comments provide you with a walkthrough of a little bit of the language.

// SubSky program loaders expect the first four bytes to be a BSS size.
// `:4` means embed 4 bytes, and `0` is the value being embedded:
:4  0

// Program execution begins here, four bytes in. Jump over the imported
// library (see below) to the init routine:
IP = IP + &init
// `&init` is the offset from the end of the instruction to the `init` label.
// Can also be written, equivalently, as `ADD IP IP &init`.

// Include the source code file "lib1.0/ASCII.s". Using `%1` instead of `%`
// will cause the compiler to remember that "lib1.0/ASCII.s" was included,
// and to not include it again if another `%1 lib1.0/ASCII.s` is encountered.
// The ASCII.s library provides the function `print_decimal`.
%1 lib1.0/ASCII.s

// Declare the label `init`, referenced earlier:
[ init ]
// Create a block of code. To do this, you create a "plate comment"
// [[ like this ]], and indent with a tab.
// Plate comments can be extracted by documentation tools, too (e.g.
// `sbsc -outline`).
[[ Init runpoint ]]
    // We are inside the first block of code.

    // Print "Hello,\n":
 $  " Hello,
     "
    // Slang has "marks", tokens you put in the indentation, after 1 space.
    // Here, we use the `$` mark, which is for printing strings.
    // Slang uses tabs for indentation, and a leading space to specify marks.
    // Strings are terminated with ` "` (which is not a mark).

    [[ Print the numbers 0 through 6, for-loop style ]]
    i = 0
        // Writing `name = value` between the plate comment and the first
        // line of the body creates and initializes locals.
        // The code inserted by this pushes 0 then sets FP to SP.
        // `i` is L1 now, and will automatically e.g. become L2 if another
        // local is created inside this block.

        // The for-loop's test:
     v! ( i < ( 6 + 1 ) )
        // If both sides of an expression are literals, then the expression
        // "preprocesses" to a single literal. `( 6 + 1 )` is the same
        // as `7`. One could just type in 7 here, but this is a demonstration.
        // The `v!` mark jumps to where the "v" arrow points, if its
        // expression is false. (`v?` likewise jumps if its expression
        // is true.)
        // Expressions in Slang are the A and B operands of an instruction,
        // with Q implicitly being SE.
        /* Thus, expressions evaluate as SE, and this can be
           written equivalently as:
        SE = i < 7
     v! SE
        */

        // Call the print_decimal function from ASCII.s, with the parameter
        // `i`:
        { i  | print_decimal }
        // { ... } is a statement for functions which don't return a value;
        // ({ ... }) is an expression for functions which do.

        // Print a newline:
     $1 <LF>
        // The `$1` mark prints one byte. `<LF>` is a const from ASCII.h
        // which expands to `#0A`; ASCII.h is imported by ASCII.s.

        // Increment i, then jump to where the `^` mark points:
     ^  i = i + 1

    // Print "world!\n"
 $  " world!
     "

 Y  // The `Y` mark "yields" from the current runpoint (init), returning
    // control to the parent. The symbol represents the parent and this
    // runpoint merging into the parent.
    // Since this program didn't create any additional runpoints, this is
    // the only runpoint left, so this yield terminates the program.

The source code for this program is in the file Examples/INTRO.s.
Compiling and running:

$ sbsc INTRO
Compiled in 538 (#21A) bytes (0.525KiB).

$ sbse INTRO.sbxe
Hello,
0
1
2
3
4
5
6
world!
CPU halted.
Total cycles: 993

Note that the executable contains the ASCII.s library, which has the functions print_decimal_Q (for printing Q16.8 fixed-point values in decimal), and hexprint (for printing a zero-padded unsigned word in hex). While those add a small amount of size to this program, it simplifies the compiler since it can output code as it encounters it without tracking whether a function is ever used (or even knowing what a function is, since it only sees its label). This project deliberately aims to be simple and comprehensible.

For a breakdown of the machine code specified by INTRO.s, see Examples/INTRO.sbxe breakdown.txt. INTRO.{s,sbxe} are readonly to keep this document robust against compiler changes. A more recent compilation is 525 bytes, and runs in the same amount of cycles.

Source with Machine Code Examples

Be sure to check out the Tips file for some programming tips and idioms, and the Slang conventions and help file for some of the standard programming conventions. And, of course, the Examples folder for some interesting examples.
See the Notes on Endianness section.

In this table, ····· indicates a tab.

Intention Source code, normal
"cursive" form
Source code, "traditional
assembly" form
Machine code (hex form)
The data word 0. :4 0 Same 00 00 00 00
Code setting
L1 to L2 + L3.
L1 = L2 + L3 ADD L1 L2 L3 84 A9
Code setting
L1 to L2 + L3 + 4.
L1 = ( L2 + L3 ) + 4 ADD SE L2 L3
ADD L1 SE 4
54 A9 84 15 04
[Same, but written
differently. Note what
the parentheses had
resulted in!]
SE = L2 + L3
L1 = SE + 4
ADD SE L2 L3
ADD L1 SE 4
54 A9 84 15 04
Block of code with a
no-op inside.
[[ ]]
·····GT = GT \| GT
-
IOR GT GT GT 00 00
Block of code with a
local foo initialized
to 0xDEADBEEF, with a
no-op inside.
[[ ]]
foo = #DEADBEEF
·····GT = GT \| GT
-
CPT SE IP #DEADBEEF
XOR FP SP 0
IOR GT GT GT
ADD FP FP 4
ADF GT IP SE
5B 42 EF BE AD DE 31 14
00 00 00 34 13 04 0C 52

[Note: the ADF GT IP SE is an optimized version of XOR SP FP 0 in this case]

You can experiment with Slang → machine code compilation yourself by doing \$sbsc | hd, or if you have the moreutils installed, you can do it with real-time assembly output using \$sbsc | pee "sbsc -disasm" "sponge | hd" [the disassembler needs implemented first].




Compiler

The compiler is sbsc, the SubSky Compiler.

Run \$sbsc --help for the list of the compiler options.

  • To view a program as a hexdump and with points of interest noted (e.g. labels and abort messages): sbsc --dbgfile /dev/stderr < my_program.s | hd
  • To view a source outline of a program (instead of writing it to a file): sbsc --outline /dev/stderr < my_program.s
  • To merely verify a program, showing any errors: sbsc < my_program.s > /dev/null
  • To run on successful compilation: sbsc my_program && sbse my_program.sbxe
  • To grasp at straws, use the --lint flag when compiling.
  • Make sure your working directory is at where the program is located (i.e. via cd), if the compiler complains about a file (e.g. in lib1.0/) not being located.




Notes on Endianness

SubSky uses the little-endian byte order. This is to keep both the memory addresses and byte place value in ascending order, rather than mix ascending and descending order like with the big-endian byte order.

The obvious problem with little-endian is that it is the opposite direction that current cultures write numbers; for example, the integer 𝍸𝍸𝍷𝍷𝍷 is written (in base-10) as "13" (big-endian: start at the highest place), not "31" (little-endian: start at the units place). This means that, for example, the SubSky instruction ADD L1 L2 L3 (op = 0x4, Q = 0x8, A = 0x9, B = 0xA) shows up in a hexdump as 84 A9 (normal big-endian digit order) instead of 48 9A (little-endian digit order). Likewise, the instruction is, as a single hex number, 0xA984 (normal big-endian digit order) instead of 0x489A (little-endian digit order).
Obviously one can't change the order everyone writes numbers, but putting the SubSky instruction fields in the opposite order doesn't fix this hexdump appearance issue anyway: you'd see 9A 48 (though you would see 0x489A when looking at an instruction as a single hex number).

Although little-endian was chosen for internal straightforwardness of design, when displaying data to the user the data is displayed big-endian in the vast majority of cases, since it is solely what users (and most programmers) are trained to work with.
It is also worth noting that the vast majority of CPUs in personal computers are little-endian (e.g. x86 CPUs, Apple's ARM CPUs), or at least support little-endian or are usually little-endian (ARM, RISC-V), so this makes emulating SubSky programs much faster on these CPUs.




Implemented Devices

See the Devices and Extended Devices files for the device specifications.

Sbse, the emulator provided here, implements various devices.
Standard devices:

  • The System device, providing basic controls like halting and yield'ing from runpoints.
  • The Console device, providing an input and an output data stream (i.e. stdin and stdout).

Extended devices (harder to implement):

  • The Screen device, providing 24-bit RGB graphics with transparency and primitive compositing functionality (nearest-neighbor scaling, no rotation), and "touch" input (controlled by the mouse in Sbse).
  • The Controllers device, a generic Human Input Device (HID) interface providing "controllers" with 0D (button), 1D ("lever", scroll-wheel etc.), and 2D (joystick, mouse etc.) inputs. Useful for keyboards, game controllers, and the like. Sbse has basic support for game controllers and keyboards.
    • Sbse registers two virtual keyboards when both the Controllers device and Screen device are initialized: one is a standard SubSky keyboard (providing character codes), and the other is a semi-standard type- -1 keyboard (providing PC scancodes). See the Extended Devices file for info on the Controllers device.




Going Further

Resources

Check out the CPU spec (SubSky), the language spec (Slang), the examples folder (Examples), and the Tips file and Slang conventions and help file for how to use the language effectively.
The Tests/systest.s program contains a system test which tests various properties of a Slang compiler and SubSky CPU.

Helping Out

Feel very free to open an issue on this project. This is my first nontrivial open-source project. Even if I know about the issue, it's good to be sure it is recognized.
Have any questions or want to discuss SubSky? Check out the chat group on Fluxer.

Projects

  • I am developing the Beings of Isness videogame for SubSky. It is an action-adventure story game with platform-fighter mechanics -- a big project. I am working on it with a close friend who can do graphics and music.
  • I own a Commodore 64U. It is FPGA-based, and I would like to create a hardware SubSky implementation for it.
  • Sbse Layer ("Subsea layer"): a port of the Sbse SubSky emulator to UEFI.
    I did this port a while ago, but should do it again with full Screen device support!
    • Potential related project: an OS for SubSky.

SubSky as a networked p-code

It is the wish of the developer that if this project is used as a "bytecode" for making Internet browsing pages interactive (think WASM or Java applets on the Web), "background" pages (i.e. pages which are not focused) run with a limitation of 1,024 SubSky instructions per second. (If that is not enough, then no more than 65,536 per second. I do not wish to contribute to platform decay.) If a site wants a higher limit, it is treated as a browser permission which has to be granted.

Outstanding Missing Features

And any important bugs.

  • Various devices and features of devices have not been implemented in Sbse yet, in particular Stdin support (partially ready; high-priority TODO. The Controllers device does supply a keyboard though, when a window is open).
  • Memory allocation System dev commands (malloc, dealloc, realloc) are not implemented (currently my programs rely on the BSS, stack, and initialized globals, which works very well but will be insufficient with larger programs).
  • Const ref escaping bug (some usages of macros require you to backslash-escape them; for now, the compiler just tells you when to do it).
    The compiler's macros system needs re-written.
  • How objects work is not documented. For now, see Examples/interfaces.s and the object-related stdlib code it uses.
  • The emulator's built-in debugger being non-symbolic, outputting raw addresses.
    It can be used manually with a dbgfile (you can look up the "Error code" in a dbgfile!), but having the emulator be able to load debugging data and apply it would be much more helpful than squinting at operand listings and sifting through RAM.
  • Disassembler not being implemented (will implement, but reading the hex straight is actually relatively easy due to the instruction format).
  • The graphics library is largely incomplete (you can draw rectangles, images, and set pixels).
  • The Machines device, or something like it (see Extended Devices), could provide interrupts, memory protection, and multiprocessing.
  • Dispatch directives are ignored by Sbse (this is allowed by the spec; it doesn't change program output). Supporting it would mean a reference implementation of SubSky would exist supporting dispatches, and Sbse running faster.




SubSky logo, Alpha