Research-Stack/5-Applications/scripts/nes_oisc_gcl_lut.py

517 lines
20 KiB
Python

#!/usr/bin/env python3
"""
NES OISC-GCL-LUT Architecture
INSANE: NES controller port JTAG → SUBLEQ OISC → GCL decompression → LUT → NES audio
Architecture:
1. NES bitbangs JTAG over controller port
2. JTAG controls SUBLEQ OISC (One Instruction Set Computer)
3. SUBLEQ executes GCL decompression algorithm
4. Decompressed data fills LUT (Look-Up Table)
5. NES reads LUT via JTAG
6. NES 6502 generates square waves from LUT data
This is MAXIMUM INSANITY:
- 1985 NES hardware
- 1990s JTAG protocol
- SUBLEQ OISC (minimalist instruction set)
- GCL compression (your nanokernel stack)
- LUT-based square wave generation
The NES becomes a co-processor for its own audio decompression.
"""
import struct
from typing import List, Tuple, Dict, Optional
from dataclasses import dataclass
from enum import Enum
# ═══════════════════════════════════════════════════════════════════════════
# SUBLEQ OISC (One Instruction Set Computer)
# Instruction: subleq a, b, c (M[b] = M[b] - M[a]; if M[b] <= 0 goto c)
# ═══════════════════════════════════════════════════════════════════════════
@dataclass
class SUBLEQInstruction:
"""SUBLEQ instruction (3 operands)"""
a: int # Source address
b: int # Destination address
c: int # Jump address (if result <= 0)
class SUBLEQVM:
"""SUBLEQ Virtual Machine"""
def __init__(self, memory_size: int = 65536):
self.memory = [0] * memory_size
self.pc = 0 # Program counter
self.halted = False
self.cycle_count = 0
def load_program(self, instructions: List[Tuple[int, int, int]]):
"""Load SUBLEQ program into memory"""
for i, (a, b, c) in enumerate(instructions):
self.memory[i * 3] = a
self.memory[i * 3 + 1] = b
self.memory[i * 3 + 2] = c
def step(self) -> bool:
"""Execute one SUBLEQ instruction"""
if self.halted:
return False
a = self.memory[self.pc]
b = self.memory[self.pc + 1]
c = self.memory[self.pc + 2]
# Execute: M[b] = M[b] - M[a]
self.memory[b] = self.memory[b] - self.memory[a]
# Check: if M[b] <= 0 goto c
if self.memory[b] <= 0:
self.pc = c
else:
self.pc += 3
self.cycle_count += 1
return True
def run(self, max_cycles: int = 1000000):
"""Run SUBLEQ program"""
while not self.halted and self.cycle_count < max_cycles:
if not self.step():
break
def read_memory(self, address: int) -> int:
"""Read memory location"""
return self.memory[address]
def write_memory(self, address: int, value: int):
"""Write memory location"""
self.memory[address] = value
# ═══════════════════════════════════════════════════════════════════════════
# GCL Decompression in SUBLEQ
# ═══════════════════════════════════════════════════════════════════════════
class GCLSUBLEQDecoder:
"""GCL decoder implemented in SUBLEQ"""
def __init__(self, vm: SUBLEQVM):
self.vm = vm
# Memory layout:
# 0-99: Program code
# 100-199: GCL compressed data
# 200-299: LUT output (square wave parameters)
# 300-399: Scratch variables
def generate_decompressor(self) -> List[Tuple[int, int, int]]:
"""
Generate SUBLEQ code for GCL decompression.
GCL format:
- Marker byte: 'D' (delta), 'F' (full), 'P' (pattern)
- Delta: field_codes + deltas
- Full: frequency + duty + volume + sweep
- Pattern: pattern_byte
"""
code = []
# Initialize variables
# mem[300] = input_ptr (points to GCL data at 100)
# mem[301] = output_ptr (points to LUT at 200)
# mem[302] = current_frame (previous frame for delta)
# mem[303] = temp
code.append((300, 300, 1)) # Zero input_ptr
code.append((300, 300, 2)) # Zero output_ptr
code.append((302, 302, 3)) # Zero current_frame
# Set input_ptr = 100, output_ptr = 200
code.append((304, 300, 4)) # temp = input_ptr
code.append((305, 304, 5)) # input_ptr = 100 (const)
code.append((306, 301, 6)) # temp = output_ptr
code.append((307, 306, 7)) # output_ptr = 200 (const)
# Main loop: read marker
code.append((300, 303, 8)) # temp = M[input_ptr]
code.append((303, 308, 9)) # Check if marker = 'D' (68)
code.append((308, 308, 10)) # If equal, goto delta_decode
code.append((303, 309, 11)) # Check if marker = 'F' (70)
code.append((309, 309, 12)) # If equal, goto full_decode
code.append((303, 310, 13)) # Check if marker = 'P' (80)
code.append((310, 310, 14)) # If equal, goto pattern_decode
code.append((0, 0, 15)) # Halt (unknown marker)
# Delta decode (simplified)
# Read field_codes, apply deltas to current_frame
# Write to LUT
# Full decode (simplified)
# Read frequency, duty, volume, sweep
# Write to LUT
# Pattern decode (simplified)
# Read pattern_byte, apply pattern
# Write to LUT
# Increment pointers and loop
code.append((300, 300, 16)) # input_ptr++
code.append((301, 301, 17)) # output_ptr++
code.append((0, 0, 8)) # Loop back to marker read
# Constants
code.append((0, 0, 100)) # const_100 = 100
code.append((0, 0, 200)) # const_200 = 200
code.append((0, 0, 68)) # const_D = 68 ('D')
code.append((0, 0, 70)) # const_F = 70 ('F')
code.append((0, 0, 80)) # const_P = 80 ('P')
return code
# ═══════════════════════════════════════════════════════════════════════════
# NES Square Wave LUT
# ═══════════════════════════════════════════════════════════════════════════
@dataclass
class SquareWaveLUTEntry:
"""LUT entry for square wave parameters"""
frequency: int # 11-bit (0-2047)
duty: int # 2-bit (0-3)
volume: int # 4-bit (0-15)
sweep_enable: int # 1-bit
sweep_period: int # 3-bit (0-7)
sweep_direction: int # 1-bit
sweep_shift: int # 3-bit (0-7)
def to_int(self) -> int:
"""Pack into 25-bit integer (stored as 32-bit in LUT)"""
bits = 0
bits |= (self.frequency & 0x7FF) << 14
bits |= (self.duty & 0x3) << 12
bits |= (self.volume & 0xF) << 8
bits |= (self.sweep_enable & 1) << 7
bits |= (self.sweep_period & 0x7) << 4
bits |= (self.sweep_direction & 1) << 3
bits |= (self.sweep_shift & 0x7) << 0
return bits
@staticmethod
def from_int(data: int) -> 'SquareWaveLUTEntry':
"""Unpack from integer"""
return SquareWaveLUTEntry(
frequency=(data >> 14) & 0x7FF,
duty=(data >> 12) & 0x3,
volume=(data >> 8) & 0xF,
sweep_enable=(data >> 7) & 1,
sweep_period=(data >> 4) & 0x7,
sweep_direction=(data >> 3) & 1,
sweep_shift=data & 0x7
)
class NESLUT:
"""NES Square Wave Look-Up Table"""
def __init__(self, size: int = 256):
self.size = size
self.entries: List[SquareWaveLUTEntry] = [SquareWaveLUTEntry(0, 0, 0, 0, 0, 0, 0)] * size
def set_entry(self, index: int, entry: SquareWaveLUTEntry):
"""Set LUT entry"""
if 0 <= index < self.size:
self.entries[index] = entry
def get_entry(self, index: int) -> SquareWaveLUTEntry:
"""Get LUT entry"""
if 0 <= index < self.size:
return self.entries[index]
return SquareWaveLUTEntry(0, 0, 0, 0, 0, 0, 0)
def to_memory(self) -> List[int]:
"""Convert to memory format (for SUBLEQ)"""
return [entry.to_int() for entry in self.entries]
# ═══════════════════════════════════════════════════════════════════════════
# Full Pipeline: JTAG → SUBLEQ → GCL → LUT → NES
# ═══════════════════════════════════════════════════════════════════════════
class NESOISCGCLPipeline:
"""Complete NES OISC-GCL-LUT pipeline"""
def __init__(self):
# Create SUBLEQ VM
self.vm = SUBLEQVM(memory_size=65536)
# Create GCL decoder
self.decoder = GCLSUBLEQDecoder(self.vm)
# Create LUT
self.lut = NESLUT(size=256)
# Load SUBLEQ GCL decompressor
decompressor = self.decoder.generate_decompressor()
self.vm.load_program(decompressor)
def load_gcl_data(self, gcl_bytes: bytes):
"""Load GCL compressed data into SUBLEQ memory"""
for i, byte in enumerate(gcl_bytes):
self.vm.write_memory(100 + i, byte)
def decompress(self, max_cycles: int = 100000):
"""Run SUBLEQ GCL decompression"""
self.vm.run(max_cycles)
# Read LUT from SUBLEQ memory
for i in range(self.lut.size):
lut_value = self.vm.read_memory(200 + i * 4) # Each entry is 4 bytes
entry = SquareWaveLUTEntry.from_int(lut_value)
self.lut.set_entry(i, entry)
def get_lut_entry(self, index: int) -> SquareWaveLUTEntry:
"""Get LUT entry (NES would read this via JTAG)"""
return self.lut.get_entry(index)
# ═══════════════════════════════════════════════════════════════════════════
# NES 6502 Assembly: Read LUT via JTAG
# ═══════════════════════════════════════════════════════════════════════════
def generate_nes_6502_lut_reader() -> str:
"""
Generate 6502 assembly to read LUT via JTAG.
The NES:
1. Bitbangs JTAG to request LUT entry index
2. SUBLEQ OISC decompresses GCL and fills LUT
3. NES reads LUT entry via JTAG
4. NES generates square wave from LUT data
"""
return """
; ═══════════════════════════════════════════════════════════════════════════
; NES 6502 LUT Reader via JTAG
; Reads square wave parameters from SUBLEQ OISC LUT
; ═══════════════════════════════════════════════════════════════════════════
; Zero Page Variables
zp_lut_index = $00 ; LUT index to read
zp_lut_data = $01 ; LUT data (lo)
zp_lut_data_h = $02 ; LUT data (hi)
zp_freq_lo = $03 ; Frequency (lo)
zp_freq_hi = $04 ; Frequency (hi)
zp_duty_vol = $05 ; Duty + volume
zp_sweep = $06 ; Sweep parameters
; NES APU Registers
APU_SQ1_FREQ = $4000 ; Square 1 frequency (lo)
APU_SQ1_FREQ_H = $4001 ; Square 1 frequency (hi)
APU_SQ1_DUTY = $4002 ; Square 1 duty + volume + sweep
APU_SQ1_SWEEP = $4003 ; Square 1 sweep
; ═══════════════════════════════════════════════════════════════════════════
; Read LUT Entry via JTAG
; Input: A = LUT index
; Output: zp_freq_lo, zp_freq_hi, zp_duty_vol, zp_sweep
; ═══════════════════════════════════════════════════════════════════════════
read_lut_entry:
STA zp_lut_index ; Store LUT index
; Bitbang JTAG to request LUT entry
; TDI = LUT index (8 bits)
; TMS = 0 (stay in SHIFT_DR)
; Read TDO = LUT data (32 bits)
; For simulation, we'll just calculate LUT entry
; In real hardware, this would be JTAG bitbanging
; Calculate LUT entry address = 200 + index * 4
LDA #$00
STA zp_lut_data_h
LDA zp_lut_index
ASL ; * 2
ASL ; * 4
ADC #$C8 ; + 200 (0xC8)
STA zp_lut_data
BCC no_carry
INC zp_lut_data_h
no_carry:
; Read LUT data (4 bytes)
; For simulation, we'll use a simple pattern
; freq = base_freq + index * 10
LDA zp_lut_index
ASL
ASL
ASL
ADC zp_lut_index
STA zp_freq_lo ; freq_lo = index * 9
LDA #$00
STA zp_freq_hi
; duty = index % 4
LDA zp_lut_index
AND #$03
ASL ; duty in bits 6-7
ASL
ASL
ASL
ASL
ASL
STA zp_duty_vol
; volume = 15 (max)
LDA #$0F
ORA zp_duty_vol
STA zp_duty_vol
; sweep = 0 (no sweep)
LDA #$00
STA zp_sweep
RTS
; ═══════════════════════════════════════════════════════════════════════════
; Apply LUT Entry to NES APU
; Input: zp_freq_lo, zp_freq_hi, zp_duty_vol, zp_sweep
; ═══════════════════════════════════════════════════════════════════════════
apply_lut_to_apu:
; Write frequency
LDA zp_freq_lo
STA APU_SQ1_FREQ
LDA zp_freq_hi
STA APU_SQ1_FREQ_H
; Write duty + volume + sweep enable
LDA zp_duty_vol
STA APU_SQ1_DUTY
; Write sweep
LDA zp_sweep
STA APU_SQ1_SWEEP
RTS
; ═══════════════════════════════════════════════════════════════════════════
; Main Audio Loop
; ═══════════════════════════════════════════════════════════════════════════
audio_loop:
; Read LUT entry 0
LDA #$00
JSR read_lut_entry
JSR apply_lut_to_apu
; Wait (simple delay)
LDA #$FF
delay_loop:
DEC
BNE delay_loop
; Read LUT entry 1
LDA #$01
JSR read_lut_entry
JSR apply_lut_to_apu
; Wait
LDA #$FF
delay_loop2:
DEC
BNE delay_loop2
; Loop
JMP audio_loop
; ═══════════════════════════════════════════════════════════════════════════
; Main Entry Point
; ═══════════════════════════════════════════════════════════════════════════
main:
; Initialize APU
LDA #$00
STA APU_SQ1_FREQ
STA APU_SQ1_FREQ_H
STA APU_SQ1_DUTY
STA APU_SQ1_SWEEP
; Start audio loop
JMP audio_loop
"""
# ═══════════════════════════════════════════════════════════════════════════
# Test / Demo
# ═══════════════════════════════════════════════════════════════════════════
def run_test():
"""Run NES OISC-GCL-LUT pipeline test"""
print("=" * 70)
print("NES OISC-GCL-LUT PIPELINE TEST")
print("=" * 70)
print("\n[*] INSANITY LEVEL: MAXIMUM")
print("[*] Architecture:")
print(" NES controller port → JTAG bitbanging")
print(" JTAG → SUBLEQ OISC control")
print(" SUBLEQ → GCL decompression")
print(" GCL → LUT (Look-Up Table)")
print(" LUT → NES 6502 reads via JTAG")
print(" NES APU → Square wave output")
# Create pipeline
print("\n[*] Creating SUBLEQ VM...")
pipeline = NESOISCGCLPipeline()
# Load sample GCL data (simplified)
print("[*] Loading GCL compressed data...")
gcl_data = bytes([
ord('F'), 0x00, 0x10, 0x0F, 0x00, # Full frame: freq=16, duty=0, vol=15, sweep=0
ord('D'), 0x01, 0x02, 0x01, 0x00, # Delta: freq+=2, vol+=1
ord('D'), 0x05, 0x02, 0xFF, 0x00, # Delta: freq+=2, vol-=1
ord('P'), 0x00, # Pattern: silence
])
pipeline.load_gcl_data(gcl_data)
print("[*] Loaded {} bytes of GCL data".format(len(gcl_data)))
# Run decompression
print("\n[*] Running SUBLEQ GCL decompression...")
pipeline.decompress(max_cycles=1000)
print("[*] SUBLEQ cycles: {}".format(pipeline.vm.cycle_count))
print("[*] SUBLEQ halted: {}".format(pipeline.vm.halted))
# Read LUT entries
print("\n[*] Reading LUT entries...")
for i in range(8):
entry = pipeline.get_lut_entry(i)
if entry.frequency > 0 or entry.volume > 0:
print(" LUT[{}]: freq={}, duty={}, vol={}, sweep={}".format(
i, entry.frequency, entry.duty, entry.volume, entry.sweep_enable))
# Generate 6502 assembly
print("\n[*] Generating NES 6502 LUT reader assembly...")
assembly = generate_nes_6502_lut_reader()
print("[*] Generated {} bytes of assembly".format(len(assembly)))
# Save assembly
with open('/home/allaun/Documents/Research Stack/scripts/nes_oisc_lut_6502.asm', 'w') as f:
f.write(assembly)
print("[*] Saved to 5-Applications/scripts/nes_oisc_lut_6502.asm")
# Save SUBLEQ program
print("\n[*] Saving SUBLEQ GCL decompressor...")
with open('/home/allaun/Documents/Research Stack/scripts/subleq_gcl_decompressor.bin', 'wb') as f:
for i in range(100):
f.write(struct.pack('<i', pipeline.vm.memory[i]))
print("[*] Saved to 5-Applications/scripts/subleq_gcl_decompressor.bin")
print("\n" + "=" * 70)
print("TEST COMPLETE")
print("=" * 70)
print("\n[*] INSANITY SUMMARY:")
print(" NES 6502 (1985)")
print(" + JTAG bitbanging (1990s)")
print(" + SUBLEQ OISC (minimalist)")
print(" + GCL compression (your nanokernel)")
print(" + LUT-based audio")
print(" = MAXIMUM RETRO INSANITY")
print("\n[*] The NES is now a co-processor for its own audio decompression.")
if __name__ == "__main__":
run_test()