π₯ Check out this insightful post from Hacker News π
π Category:
π Key idea:

Tiny8 is a lightweight toolkit that allows you to explore how computers work at their core through small-scale memory models, handcrafted assembly, and lightweight in-memory data structures.
Designed for rapid experimentation, Tiny8 embraces minimalism with zero unnecessary dependencies, a clean design, and intuitive visualization tools that make learning, debugging, and tinkering enjoyable.
Why Tiny8?
- Lightweight: tiny install footprint and no heavy runtime dependencies.
- Educational: clear primitives and examples that demonstrate CPU concepts, memory layout, and algorithms.
- Fast feedback loop: assemble, run, and visualize within seconds to iterate on ideas.
- Extensible: meant for experiments, teaching, demos, and small tools that benefit from a predictable, tiny VM.
Who should use it?
- Students learning low-level programming, assembly, or computer architecture who want hands-on examples.
- Educators building demos and interactive lessons that visualize how registers and memory change.
- Hobbyists and hackers experimenting with toy CPUs, compact data layouts, or custom instruction ideas.
- Developers who want a tiny, readable simulator to prototype algorithms that manipulate memory directly.
Get started
- Follow the Installation section below to install from PyPI or set up a development environment.
- See the Examples section (like the bubble sort demo) to run real programs and watch the visualizer in action.
- Dive into the API Reference for details on the CPU, assembler, and visualization helpers.
Tiny8 supports Python 3.11 and newer. It has no heavy external dependencies and is suitable for inclusion in virtual environments. Follow the steps below to prepare your environment and install from source or PyPI.
- Python 3.11+
- Git (for installing from the repository)
- Recommended: create and use a virtual environment
From source (development)
git clone https://github.com/sql-hkr/tiny8.git
cd tiny8
uv venv
source .venv/bin/activate
uv sync
Tip
uv is an extremely fast Python package and project manager, written in Rust. To install it, run:
# On macOS and Linux.
curl -LsSf https://astral.sh/uv/install.sh | sh
# On Windows.
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
This flow sets up a development virtual environment, installs development requirements, and prepares the project for local editing and testing.
This example demonstrates a simple bubble sort algorithm implemented in assembly language for the Tiny8 CPU. The program first fills a section of RAM with pseudo-random bytes, then sorts those bytes using the bubble sort algorithm. Finally, a Python script runs the assembly program and visualizes the sorting process.
bubblesort.asm:
; Bubble sort using RAM (addresses 100..131) - 32 elements
; Purpose: fill RAM[100..131] with pseudo-random bytes and sort them
; Registers (use R16..R31 for LDI immediates):
; R16 - base address (start = 100)
; R17 - index / loop counter for initialization
; R18 - PRNG state (seed)
; R19..R24 - temporary registers used in loops and swaps
; R25 - PRNG multiplier (kept aside to avoid clobber in MUL)
;
; The code below is split into two phases:
; 1) init_loop: generate and store 32 pseudo-random bytes at RAM[100..131]
; 2) outer/inner loops: perform a simple bubble sort over those 32 bytes
; initialize pointers and PRNG
ldi r16, 100 ; base address
ldi r17, 0 ; index = 0
ldi r18, 123 ; PRNG seed
ldi r25, 75 ; PRNG multiplier (kept in r25 so mul doesn't clobber it)
init_loop:
; PRNG step: r2 := lowbyte(r2 * 75), then tweak
mul r18, r25 ; r18 = low byte of (r18 * 75)
inc r18 ; small increment to avoid repeating patterns
; store generated byte into memory at base + index
st r16, r18 ; RAM[base] = r18
inc r16 ; advance base pointer
inc r17 ; increment index
ldi r23, 32
cp r17, r23
brne init_loop
; Bubble sort for 32 elements (perform passes until i == 31)
ldi r18, 0 ; i = 0 (outer loop counter)
outer_loop:
ldi r19, 0 ; j = 0 (inner loop counter)
inner_loop:
; compute address of element A = base + j
ldi r20, 100
add r20, r19
ld r21, r20 ; r21 = A
; compute address of element B = base + j + 1
ldi r22, 100
add r22, r19
ldi r23, 1
add r22, r23
ld r24, r22 ; r24 = B
; compare A and B (we'll swap if A < B)
cp r21, r24 ; sets carry if r21 < r24
brcs no_swap
; swap A and B: store B into A's address, A into B's address
st r20, r24
st r22, r21
no_swap:
inc r19
ldi r23, 31
cp r19, r23
breq end_inner
jmp inner_loop
end_inner:
inc r18
ldi r23, 31
cp r18, r23
breq done
jmp outer_loop
done:
jmp done
Python Code:
from tiny8 import CPU, Visualizer, assemble_file
prog, labels = assemble_file("examples/bubblesort.asm")
cpu = CPU()
cpu.load_program(prog, labels)
cpu.run(max_cycles=15000)
print([cpu.read_ram(i) for i in range(100, 132)])
viz = Visualizer(cpu)
base = 100
viz.animate_combined(
interval=1,
mem_addr_start=base,
mem_addr_end=base + 31,
plot_every=100,
# filename="bubblesort.gif",
# fps=60,
)
Example Output:
[247, 243, 239, 238, 227, 211, 210, 195, 190, 187, 186, 171, 167, 159, 155, 150, 142, 139, 135, 130, 127, 106, 102, 94, 54, 50, 34, 26, 23, 15, 10, 6]
Below is a concise, categorized summary of the Tiny8 instruction set (mnemonics are case-insensitive). This is a quick reference β for implementation details see src/tiny8/cpu.py.
-
Data transfer
- LDI Rd, K β load immediate into register
- MOV Rd, Rr β copy register
- LD Rd, Rr_addr β load from RAM at address in register
- ST Rr_addr, Rr β store register into RAM at address in register
- IN Rd, port β read byte from RAM/IO into register
- OUT port, Rr β write register to RAM/IO
- PUSH Rr / POP Rd β stack push/pop
-
Arithmetic
- ADD Rd, Rr β add registers
- ADC Rd, Rr β add with carry
- SUB Rd, Rr / SUBI Rd, K β subtraction
- SBC Rd, Rr / SBCI Rd, K β subtract with carry/borrow
- INC Rd / DEC Rd β increment / decrement
- MUL Rd, Rr β 8×8 -> 16 multiply (low/high in Rd/Rd+1)
- DIV Rd, Rr β unsigned divide (quotient->Rd, remainder->Rd+1)
- NEG Rd β two’s complement negate
- CLR Rd / SER Rd β clear or set register to all ones
-
Logical and bit ops
- AND Rd, Rr / ANDI Rd, K β bitwise AND
- OR Rd, Rr / ORI Rd, K β bitwise OR
- EOR Rd, Rr / EORI Rd, K β exclusive OR
- COM Rd β one’s complement
- SWAP Rd β swap nibbles
- TST Rd β test for zero or minus
- SBI/CBI / SBIS/SBIC / SBRS/SBRC β set/clear/test single bits and conditional skips
-
Shifts & rotates
- LSL Rd / LSR Rd β logical shift left/right
- ROL Rd / ROR Rd β rotate through carry
-
Word (16-bit) ops
- SBIW / ADIW β simplified word add/subtract helpers for register pairs
-
Control flow
- JMP label / RJMP offset β unconditional jump
- CALL label / RCALL offset β call subroutine (push return address)
- RET / RETI β return from subroutine / return from interrupt (sets I)
- BRNE / BREQ / BRCS / BRCC β conditional branches based on flags
- CP Rd, Rr / CPI Rd, K β compare (sets flags)
- CPSE Rd, Rr β compare and skip if equal
Use the assembler in src/tiny8/assembler.py (or parse_asm) to write programs β register operands are specified as R0..R31 and immediates accept decimal, $hex, 0x, or 0b binary notation.
The API section documents the public modules, classes, functions, and configuration options. See:
Tiny8 is licensed under the MIT License. See LICENSE for details.
Contributions, bug reports, and pull requests are welcome; please follow the repository’s CONTRIBUTING guidelines.
π₯ What do you think?
#οΈβ£ #sqlhkrtiny8 #tiny #CPU #simulator #written #Python
π Posted on 1761315885
