A Python-Controlled Gate-Level Arithmetic Engine
A Python library that exposes a complete 16-bit CPU data paths (ALU, memory, bus) implemented in C,
but controlled entirely from Python.
See example at DSL readme.
All 1-word instructions. Executed directly in C.
Format: [15:12]=opcode, [11:8]=dest, [7:4]=src1, [3:0]=src2
| Mnemonic | Parameters | Description | Notes |
|---|---|---|---|
| ADD | dest, src1, src2 | dest = src1 + src2 | Updates Z, C, O flags |
| SUB | dest, src1, src2 | dest = src1 - src2 | Updates Z, C, O flags |
| CMP | dest, src1, src2 | dest = src1 - src2 (result discarded) | Updates Z, C, O flags only |
| AND | dest, src1, src2 | dest = src1 & src2 | Updates Z flag |
| OR | dest, src1, src2 | dest = src1 | src2 | Updates Z flag |
| XOR | dest, src1, src2 | dest = src1 ^ src2 | Updates Z flag |
| NAND | dest, src1, src2 | dest = ~(src1 & src2) | Updates Z flag |
| NOR | dest, src1, src2 | dest = ~(src1 | src2) | Updates Z flag |
| NOT_A | dest, src1, src2 | dest = ~src1 | src2 ignored; updates Z flag |
| PASS_A | dest, src1, src2 | dest = src1 | src2 ignored |
| PASS_B | dest, src1, src2 | dest = src2 | src1 ignored |
| SHL | dest, src1, src2 | dest = src1 << src2 | Updates Z flag |
| SHR | dest, src1, src2 | dest = src1 >> src2 (logical) | Updates Z flag |
| ROL | dest, src1, src2 | dest = src1 rotate-left src2 | Updates Z flag |
| ROR | dest, src1, src2 | dest = src1 rotate-right src2 | Updates Z flag |
All use opcode 0xF (SYS). Format: [15:12]=0xF, [11:8]=subtype, [7:4]=src1, [3:0]=src2
| Mnemonic | Subtype | Words | Parameters | Description | Notes |
|---|---|---|---|---|---|
| LD16 | 0x1 | 2 | dest, address | R[dest] = mem[address] | address is 16-bit immediate in word 2 |
| ST16 | 0x2 | 2 | address, src1 | mem[address] = R[src1] | Bypasses STDIO ports |
| LDI16 | 0x3 | 2 | dest, immediate | R[dest] = immediate | immediate is 16-bit in word 2 |
| STIND | 0x9 | 1 | src1, dest | mem[R[dest]] = R[src1] | Routes through STDIO at 0xFFFE |
| LDIND | 0xA | 1 | dest, src1 | R[dest] = mem[R[src1]] | Routes through STDIO at 0xFFFD |
| Mnemonic | Subtype | Words | Parameters | Description | Notes |
|---|---|---|---|---|---|
| JMP16 | 0x4 | 2 | address | PC = address | Unconditional jump |
| CALL16 | 0x5 | 2 | address | Push (PC+2), then PC = address | Raises CallStackOverflowError if depth > 256 |
| RET | 0x6 | 1 | None | PC = pop() | Raises CallStackUnderflowError if stack empty |
| JZ | 0xB | 2 | address | if (Z) PC = address | Jump if zero flag set |
| JNZ | 0xC | 2 | address | if (!Z) PC = address | Jump if zero flag not set |
| JC | 0xD | 2 | address | if (C) PC = address | Jump if carry flag set |
| Mnemonic | Subtype | Words | Parameters | Description | Notes |
|---|---|---|---|---|---|
| PUSH | 0x7 | 1 | src1 | SP--; mem[SP] = R[src1] | Raises StackOverflowError if SP ≤ 0x1000 |
| POP | 0x8 | 1 | src1 | R[src1] = mem[SP]; SP++ | Raises StackUnderflowError if SP = 0xFF00 |
| Mnemonic | Subtype | Words | Parameters | Description | Notes |
|---|---|---|---|---|---|
| HALT | 0x0 | 1 | None | Stop execution | Sets halted flag |
| Subtype | Status |
|---|---|
0xE, 0xF | Reserved; raises InvalidInstructionError if decoded |
| Address | Direction | Description |
|---|---|---|
0xFFFD | Input | Read returns character from stdin (blocking, line-buffered) |
0xFFFE | Output | Write 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.
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()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
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)
| Component | Description |
|---|---|
| Memory | 64K x 16-bit unified address space (registers + RAM) |
| ALU | 16 operations: ADD, SUB, AND, OR, XOR, NAND, NOR, NOT, PASS_A, PASS_B, SHL, SHR, ROL, ROR, CMP, SYS |
| Bus | 16-bit data path with read/write operations |
| Flags | Zero, Carry, Overflow (automatically maintained) |
| SYS | Control hatch, Mode-switching instruction (reconfigures ALU behavior) |
| Component | Description |
|---|---|
| CPU Class | Wraps the C state machine |
| Instruction Decoder | Python interprets opcodes from memory |
| Program Counter | Python-managed PC (not in C) |
| Custom ISA | Instruction set defined entirely in Python |
| Mode Manager | Uses SYS to reconfigure C ALU behavior |
- Registers (R0-R7) at addresses 0-7
- RAM starting at address
0x0200(512) marker - No distinction between register and memory operations
- All ALU ops:
dest = src1 OP src2 - Sources and destinations can be any address (registers or RAM)
- Each Python "instruction" can execute multiple C operations
- Complex instructions decomposed into bus transactions
- Python can implement operations not present in C
SYSopcode 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