Skip to content
This repository was archived by the owner on Aug 5, 2026. It is now read-only.

Repository files navigation

Bitcraft (Slow Hobby Project)

A Python-Controlled Gate-Level Arithmetic Engine

Concept

A Python library that exposes a complete 16-bit CPU data paths (ALU, memory, bus) implemented in C,
but controlled entirely from Python.

Assembly Language Concept (DSL)

See example at DSL readme.

Opcode scheme (Cheat Sheet)

C-Runtime Operations (ALU & Logic)

All 1-word instructions. Executed directly in C.
Format: [15:12]=opcode, [11:8]=dest, [7:4]=src1, [3:0]=src2

MnemonicParametersDescriptionNotes
ADDdest, src1, src2dest = src1 + src2Updates Z, C, O flags
SUBdest, src1, src2dest = src1 - src2Updates Z, C, O flags
CMPdest, src1, src2dest = src1 - src2 (result discarded)Updates Z, C, O flags only
ANDdest, src1, src2dest = src1 & src2Updates Z flag
ORdest, src1, src2dest = src1 | src2Updates Z flag
XORdest, src1, src2dest = src1 ^ src2Updates Z flag
NANDdest, src1, src2dest = ~(src1 & src2)Updates Z flag
NORdest, src1, src2dest = ~(src1 | src2)Updates Z flag
NOT_Adest, src1, src2dest = ~src1src2 ignored; updates Z flag
PASS_Adest, src1, src2dest = src1src2 ignored
PASS_Bdest, src1, src2dest = src2src1 ignored
SHLdest, src1, src2dest = src1 << src2Updates Z flag
SHRdest, src1, src2dest = src1 >> src2 (logical)Updates Z flag
ROLdest, src1, src2dest = src1 rotate-left src2Updates Z flag
RORdest, src1, src2dest = src1 rotate-right src2Updates Z flag

Python Extended Operations (SYS Subtypes)

All use opcode 0xF (SYS). Format: [15:12]=0xF, [11:8]=subtype, [7:4]=src1, [3:0]=src2

Memory Access

MnemonicSubtypeWordsParametersDescriptionNotes
LD160x12dest, addressR[dest] = mem[address]address is 16-bit immediate in word 2
ST160x22address, src1mem[address] = R[src1]Bypasses STDIO ports
LDI160x32dest, immediateR[dest] = immediateimmediate is 16-bit in word 2
STIND0x91src1, destmem[R[dest]] = R[src1]Routes through STDIO at 0xFFFE
LDIND0xA1dest, src1R[dest] = mem[R[src1]]Routes through STDIO at 0xFFFD

Control Flow

MnemonicSubtypeWordsParametersDescriptionNotes
JMP160x42addressPC = addressUnconditional jump
CALL160x52addressPush (PC+2), then PC = addressRaises CallStackOverflowError if depth > 256
RET0x61NonePC = pop()Raises CallStackUnderflowError if stack empty
JZ0xB2addressif (Z) PC = addressJump if zero flag set
JNZ0xC2addressif (!Z) PC = addressJump if zero flag not set
JC0xD2addressif (C) PC = addressJump if carry flag set

Stack Operations

MnemonicSubtypeWordsParametersDescriptionNotes
PUSH0x71src1SP--; mem[SP] = R[src1]Raises StackOverflowError if SP ≤ 0x1000
POP0x81src1R[src1] = mem[SP]; SP++Raises StackUnderflowError if SP = 0xFF00

System Control

MnemonicSubtypeWordsParametersDescriptionNotes
HALT0x01NoneStop executionSets halted flag

Reserved Subtypes

SubtypeStatus
0xE, 0xFReserved; raises InvalidInstructionError if decoded

STDIO Memory-Mapped Ports

AddressDirectionDescription
0xFFFDInputRead returns character from stdin (blocking, line-buffered)
0xFFFEOutputWrite prints character to stdout

Note: STDIO ports are only accessible via indirect operations (STIND/LDIND) or direct Python cpu[addr] access. Direct ST16/LD16 bypass STDIO interception.

Runs the Essentials

Example of a sum r2 = 42 + 16:

importctypes# for cpu._machine.lib.* functionsfrommachineimportCPU, ALUOp, SysExt, Modedeftest_ALU_operations():
cpu=CPU()
# Pythonic memory accesscpu[0] =42# R0 = 42cpu[1] =16# R1 = 16# Direct-access ALU operation via the C library# ADD: r2 = r0 + r1cpu._machine.lib.machine_alu_op(
ctypes.byref(cpu._machine.state),
0, 1, 2,
int(ALUOp.ADD)
)
print(f"R2 = {cpu[2]}")
print(f"Flags: {cpu.flags}")
print(f"Registers: {cpu.registers}")

Hello, World!:

frommachineimportCPUdefhello_world():
cpu=CPU()
# Register allocation# R0 = string pointer (advances through the string)# R1 = output port address (constant 0xFFFE)# R2 = current character (loaded via LDIND)# R3 = increment constant (value 1)# R6 = zero constant (for CMP against null terminator)# Allocation & ConfigurationPROGRAM_BASE=0x0200STRING_ADDR=0x4000# string addressOUTPUT_PORT=0xFFFE# Memory-mapped output (stdout port)# Load string data into memory using# CPU.load_string(addr: int, text: str, null_terminate: bool) -> int:cpu.load_string(STRING_ADDR, "Hello, World!\n")
init=cpu.assemble_program([
("LDI16", 0, STRING_ADDR), # R0 = string pointer
("LDI16", 1, OUTPUT_PORT), # R1 = output port address
("LDI16", 3, 1), # R3 = 1 (for pointer increment)
("LDI16", 6, 0), # R6 = 0 (zero constant for CMP)
])
# Logic# LDIND(1) + CMP(1) + JZ(2) + STIND(1) + ADD(1) + JMP16(2) + HALT(1) = 9 wordsstart=PROGRAM_BASE+len(init) # Calculate addresses for branch targetshalt=start+8# HALT sits at the end of the looploop=cpu.assemble_program([
("LDIND", 2, 0), # R2 = mem[R0] (load next char)
("CMP", 6, 2, 6), # Compare R2 with 0 → sets Z if null
("JZ", halt), # If null terminator, jump to HALT
("STIND", 2, 1), # mem[R1] = R2 (write char to 0xFFFE)
("ADD", 0, 0, 3), # R0 = R0 + 1 (advance pointer)
("JMP16", start), # Loop back to start
("HALT",), # End of program (JZ target)
])
# Assemble and loadprogram=init+loopcpu.load_program(program, start=PROGRAM_BASE)
cpu.pc=PROGRAM_BASE# Runcycles=cpu.run(max_cycles=1000)
if__name__=="__main__":
hello_world()

Fibonacci Sequencer:

frommachineimportCPUdeffibonacci():
print(f"Loading CPU...")
cpu=CPU()
# Registers:# R6 = 0 (zero constant)# R0 = scratch# R1 = loop counter (remaining iterations)# R2 = output pointer# R3 = F(k-2)# R4 = F(k-1)# R5 = F(k)# Block 1init=cpu.assemble_program([
("LDI16", 6, 0), # R6 = 0 (zero constant for life of program)
("LDI16", 1, 8), # R1 = 8 more iterations
("LDI16", 2, 0x3002), # R2 = output pointer
("LDI16", 3, 0), # R3 = F(0) = 0
("LDI16", 4, 1), # R4 = F(1) = 1
("ST16", 0x3000, 3), # mem[0x3000] = 0
("ST16", 0x3001, 4), # mem[0x3001] = 1
])
# Segment init + offsetinit_size=len(init)
loop_addr=0x0200+init_size# :loop_bodyloop=cpu.assemble_program([
# Loop next number
("ADD", 5, 3, 4), # R5 = F(k) = F(k-2) + F(k-1)
("STIND", 5, 2), # mem[R2] = R5 (indirect store)# Shift: R3 = R4, R4 = R5 (using R6 as zero)
("ADD", 3, 4, 6), # R3 = R4 + 0 = R4
("ADD", 4, 5, 6), # R4 = R5 + 0 = R5# Advance pointer: R2++
("LDI16", 0, 1), # R0 = 1
("ADD", 2, 2, 0), # R2 = R2 + 1# Decrement counter
("LDI16", 0, 1), # R0 = 1
("SUB", 1, 1, 0), # R1--# Compare and loop
("CMP", 6, 1, 6), # R1 - 0 (sets zero flag if R1==0)
("JNZ", loop_addr), # If not zero, jump back to loop start
("HALT",)
])
# prep start vectorsprogram=init+loopcpu.load_program(program, start=0x0200)
cpu.pc=0x0200# commitcycles=cpu.run(max_cycles=500)
# Resultsprint(f"Executed {cycles} cycles")
print(f"Loop address: 0x{loop_addr:04X}")
print()
print("Fibonacci:")
foriinrange(10):
val=cpu[0x3000+i]
print(f" F({i:2d}) = {val:5d}")
print(f"\nRegisters: {cpu.registers}")
print(f"Zero flag: {cpu.zero}")
# Show instruction traceprint(f"\nInstruction trace ({len(cpu.get_history())} instructions):")
fori, instrinenumerate(cpu.get_history()):
print(f" {i:2d}: {instr}")
if__name__=="__main__":
fibonacci()

Downsides

It's slow!

Measured Speed : ~0.079 MOPS/s
Raw C ALU Baseline : ~1.20 MOPS/s
Python Control Cost : ~15.2x
  • Python control adds 11.82 µs added per cycle overhead
  • Each Python instruction can execute multiple C operations
  • Not suitable for high-performance computing

Is it Managed?

  • Python manages memory safety (no buffer overflows)
  • Python handles instruction decoding
  • Python orchestrates C operations
  • C provides (owns) the chassis and basic functions

Basic ALU Functions

  • Arithmetic: ADD, SUB, CMP
  • Logic: AND, OR, XOR, NAND, NOR, NOT
  • Pass: PASS_A, PASS_B
  • Shift: SHL, SHR, ROL, ROR
  • Control: SYS (control hatch, mode switching)
  • Addressing: Any address (0-65535) for src1, src2, dest

Flags

  • Z: Result is zero
  • C: Carry/borrow occurrence
  • O: Two's complement overflow

Core Model

Python doesn't simulate the CPU, Python IS the CPU

  • C provides the execution units (arithmetic, logic, shifts, memory)
  • Python provides the control logic (instruction fetch, decode, sequencing)
  • The separation mimics real CPU design (datapath vs. control unit)

Architecture

C Layer (ALU+MEMORY)

ComponentDescription
Memory64K x 16-bit unified address space (registers + RAM)
ALU16 operations: ADD, SUB, AND, OR, XOR, NAND, NOR, NOT, PASS_A, PASS_B, SHL, SHR, ROL, ROR, CMP, SYS
Bus16-bit data path with read/write operations
FlagsZero, Carry, Overflow (automatically maintained)
SYSControl hatch, Mode-switching instruction (reconfigures ALU behavior)

Python Layer (CPU)

ComponentDescription
CPU ClassWraps the C state machine
Instruction DecoderPython interprets opcodes from memory
Program CounterPython-managed PC (not in C)
Custom ISAInstruction set defined entirely in Python
Mode ManagerUses SYS to reconfigure C ALU behavior

API

1. Unified Addressing

  • Registers (R0-R7) at addresses 0-7
  • RAM starting at address 0x0200 (512) marker
  • No distinction between register and memory operations

2. Three-Operand Instructions

  • All ALU ops: dest = src1 OP src2
  • Sources and destinations can be any address (registers or RAM)

3. Python as Microcode

  • Each Python "instruction" can execute multiple C operations
  • Complex instructions decomposed into bus transactions
  • Python can implement operations not present in C

4. Runtime Reconfiguration

  • SYS opcode changes ALU behavior without recompilation
  • Modes: saturation, signed arithmetic, rounding, polarity changes

Reference API.H: readme
Reference ALU.H: readme
Reference BUS.H: readme
Reference BINDING.PY: readme

About

A Python-Controlled Gate-Level Arithmetic Engine

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages