pouët.net

Go to bottom

Hello there

category: general [glöplog]
 
I built a 16-bit CPU from scratch — RR16X Mk.8.2

I've just released RR16X Mk.8.2, a custom 16-bit CPU architecture I've been building from scratch.

Custom ISA, assembler, C++ emulator, SystemVerilog implementation and extensible peripheral system.

Windows binaries are included in the first release.

Search GitHub for RR16X_Mk.8_logic- if you're interested.

I'm curious what people here think of programming for unusual/custom hardware.
This is cool as hell!!!
added on the 2026-08-28 06:35:18 by sigflup sigflup
I had a funny feeling you guys would like this.
please, if you have a moment, tell me:
what did you like?
what would you have done differently?
nice project! I liked that you chose a three-operand form for the ISA since that maps well to SSA (static single assignment) which will pay off when you write e.g. a C compiler for it.
added on the 2026-08-28 08:40:20 by bsp bsp
I think it would be a pleasant surprise to find that I've been putting some spare time into actually making a C compiler.
does it fit into 512 bytes like SectorC ? (jk :))

any plans for your CPU ?

e.g. implement it on an FPGA, add a VGA output, run some demo fx ?

would be a cool wild demo !
added on the 2026-08-28 13:14:49 by bsp bsp
plans:
- Finish C compiler (and see what ridiculous footprint I get out of it)
- get my hands on an FPGA to get some actual hardware.
- For my emulator, I'd use raylib for graphics.
- I don't know how to make graphics hardware.

current achievements include BASIC in 6.3 KB of ROM.
I looked up sectorC and by golly it's basically perfect as a starter.
I knew you'd like it (SectorC) :) It's not a full C compiler, though (and it has no optimizer - the hard part). Would advise to write a non-size-optimized, non-optimizing one in C++ first and then iteratively refine it. Remember to separate AST optimizations (constant folding, common sub-expr etc) from assembly level optimizations (dead code, peephole, ..).

Regarding graphics hardware: just stick to software rendering. For the display output use VGA, it's quite simple to implement (once did it in software on an STM32 and a hand-soldered resistor ladder for the D/A conversion :D). 'Modern' monitors are quite picky regarding the timings but it's still easy compared to digital protocols.

So, yeah, sounds like a plan, good luck and looking forward to the "product" !
added on the 2026-08-28 15:32:10 by bsp bsp
I have no intention of implementing optimizing in compilation. unoptimized suffices for me.
I'd say that what's there is open for people to experiment on as-is. But there are things on it worth working on.
That said, if you know your way around C++, you can easily modify/extend it however you like.
Ok, here's another idea: Use LLVM / Clang to compile "C" to the LLVM (ASCII) intermediate representation (.ll IR).

It has an assembly-like syntax that should not be too difficult to parse (see LLVM language reference manual). You don't need to implement all of it, of course.

An .ll file can be created e.g. via "% clang -S -emit-llvm -O0 myfile.c".

For a simple program
Code:int int_multiplyadd(int a, int b, int c) { return a * b + c; } int main(void) { return int_multiplyadd(10, 20, 30); }

the output looks like this:
Code:; Function Attrs: noinline nounwind optnone ssp uwtable(sync) define i32 @int_multiplyadd(i32 noundef %0, i32 noundef %1, i32 noundef %2) #0 { %4 = alloca i32, align 4 %5 = alloca i32, align 4 %6 = alloca i32, align 4 store i32 %0, ptr %4, align 4 store i32 %1, ptr %5, align 4 store i32 %2, ptr %6, align 4 %7 = load i32, ptr %4, align 4 %8 = load i32, ptr %5, align 4 %9 = mul nsw i32 %7, %8 %10 = load i32, ptr %6, align 4 %11 = add nsw i32 %9, %10 ret i32 %11 } ; Function Attrs: noinline nounwind optnone ssp uwtable(sync) define i32 @main() #0 { %1 = alloca i32, align 4 store i32 0, ptr %1, align 4 %2 = call i32 @int_multiplyadd(i32 noundef 10, i32 noundef 20, i32 noundef 30) ret i32 %2 }

(e.g. 'nsw' for example means "no sign wrap". it's just a hint for the code generator / optimizer which you can safely ignore)

There is also a binary / bitstream representation of the LLVM IR but I think it would be easier to work with the ASCII version.

As you can see in the IR, LLVM uses static-single-assignment (SSA), i.e. each instruction produces a new result value (%1..%n).

When the control flow diverges, it inserts a "phi-merge node" at the end. E.g.
Code:int int_max(int a, int b) { return (a > b) ? a : b; } int main(void) { return int_max(10, 20); }

compiles to:
Code:; Function Attrs: noinline nounwind optnone ssp uwtable(sync) define i32 @int_max(i32 noundef %0, i32 noundef %1) #0 { %3 = alloca i32, align 4 %4 = alloca i32, align 4 store i32 %0, ptr %3, align 4 store i32 %1, ptr %4, align 4 %5 = load i32, ptr %3, align 4 %6 = load i32, ptr %4, align 4 %7 = icmp sgt i32 %5, %6 br i1 %7, label %8, label %10 8: ; preds = %2 %9 = load i32, ptr %3, align 4 br label %12 10: ; preds = %2 %11 = load i32, ptr %4, align 4 br label %12 12: ; preds = %10, %8 %13 = phi i32 [ %9, %8 ], [ %11, %10 ] ret i32 %13 } ; Function Attrs: noinline nounwind optnone ssp uwtable(sync) define i32 @main() #0 { %1 = alloca i32, align 4 store i32 0, ptr %1, align 4 %2 = call i32 @int_max(i32 noundef 10, i32 noundef 20) ret i32 %2 }


i.e. "phi [..]" here means that "virtual register" %13 will be equivalent to the value of %9 in block %8, or the value of %11 in block %10, depending on which path is taken (the result of the "icmp" comparison result at runtime), i.e. you can simply insert two "move" instructions in the conditional blocks.

Since all the registers (/values) are "virtual", you need to do hardware register allocation in the end (and spill / restore values to / from the stack if you run out of registers).

If you have done that, you will quickly realize that you want to do at least *a few* optimizations but some are real low hanging fruits (like eliminating branches to the next instruction).

You can skip the more complicated ones, write the actual demo fx in assembly, and use C only for the glue logic / non-time-critical parts (e.g. for precalculating look-up tables).

tl;dr: The advantage of using Clang / LLVM is that you get a full featured C compiler, including a proper preprocessor, for free, which lets you focus on the assembly code generator.
added on the 2026-08-29 22:38:35 by bsp bsp
what

login

Go to top