initial upload
This commit is contained in:
Executable
+211
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env python3
|
||||
# ILDEC: a minimalistic DEC PDP-8/L emulation core in (Micro)Python
|
||||
# See README.md for the usage guide
|
||||
# Created by Luxferre in 2026, released into the public domain
|
||||
|
||||
import sys
|
||||
try:
|
||||
import select
|
||||
except ImportError:
|
||||
select = None
|
||||
from array import array
|
||||
|
||||
try: # T-DeckARD workaround
|
||||
from deck.input import input
|
||||
from repl.tdeck_repl import Pydos_ui
|
||||
def default_keyboard_check(pdp8):
|
||||
if pdp8.kbd_delay <= 0:
|
||||
pdp8.kbd_delay = 1000
|
||||
if Pydos_ui.serial_bytes_available():
|
||||
c = Pydos_ui.read_keyboard(1)
|
||||
b = ord(c) if c else None
|
||||
if b == 4 or c == "":
|
||||
pdp8.halted = True
|
||||
return
|
||||
if b is not None:
|
||||
pdp8.kbb, pdp8.kbf, pdp8.kbd_delay = (13 if b == 10 else b) | 0x80, 1, 500
|
||||
except:
|
||||
def default_keyboard_check(pdp8):
|
||||
try:
|
||||
if pdp8.kbd_delay <= 0:
|
||||
pdp8.kbd_delay = 1000
|
||||
try:
|
||||
import supervisor
|
||||
has_bytes = supervisor.runtime.serial_bytes_available
|
||||
except (ImportError, AttributeError):
|
||||
has_bytes = pdp8.poller.poll(0) if pdp8.poller else (select.select([sys.stdin], [], [], 0)[0] if select else False)
|
||||
if has_bytes:
|
||||
try:
|
||||
import os; b, c = os.read(0, 1)[0], None
|
||||
except Exception:
|
||||
c = sys.stdin.read(1)
|
||||
b = ord(c) if c else None
|
||||
if b == 4 or c == "":
|
||||
pdp8.halted = True
|
||||
return
|
||||
if b is not None:
|
||||
pdp8.kbb, pdp8.kbf, pdp8.kbd_delay = (13 if b == 10 else b) | 0x80, 1, 500
|
||||
except Exception: pass
|
||||
|
||||
def set_cbreak(enable):
|
||||
try:
|
||||
import termios
|
||||
fd = sys.stdin.fileno()
|
||||
if enable:
|
||||
import tty; tty.setcbreak(fd)
|
||||
else:
|
||||
attr = termios.tcgetattr(fd)
|
||||
attr[3] |= (termios.ICANON | termios.ECHO)
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, attr)
|
||||
except Exception: pass
|
||||
|
||||
def char_output(c):
|
||||
sys.stdout.write(chr(c & 0x7F))
|
||||
if hasattr(sys.stdout, 'flush'): sys.stdout.flush()
|
||||
|
||||
class ILDEC_PDP8:
|
||||
def __init__(self, init_pc=0, init_sr=0, keyboard_check = None, outchar_cb = None):
|
||||
self.memory = array('H', [0] * 4096)
|
||||
self.kbdcheck, self.outc = keyboard_check or default_keyboard_check, outchar_cb or char_output
|
||||
self.reset(init_pc, init_sr)
|
||||
self.poller = None
|
||||
try:
|
||||
import supervisor
|
||||
except ImportError:
|
||||
try:
|
||||
if select:
|
||||
self.poller = select.poll()
|
||||
self.poller.register(sys.stdin, select.POLLIN)
|
||||
except Exception: pass
|
||||
|
||||
def reset(self, pcval=0, srval=0):
|
||||
self.link = self.ac = self.kbb = self.kbf = self.chb = self.chf = self.kbd_delay = 0
|
||||
self.ie = self.ion_delay = 0
|
||||
self.pc, self.sr, self.halted = pcval, srval, False
|
||||
|
||||
def start(self, start_addr=0o200):
|
||||
self.pc, self.halted = start_addr, False
|
||||
set_cbreak(True)
|
||||
try:
|
||||
while not self.halted: self.step()
|
||||
finally: set_cbreak(False)
|
||||
|
||||
def rotate_ac_link(self, dir, times):
|
||||
for _ in range(times):
|
||||
if dir > 0: self.ac, self.link = (self.ac >> 1) | (self.link << 11), self.ac & 1
|
||||
else: self.ac, self.link = ((self.ac << 1) | self.link) & 0xFFF, self.ac >> 11
|
||||
|
||||
def step(self):
|
||||
if self.halted: return
|
||||
if not self.kbf: self.kbdcheck(self)
|
||||
if self.halted: return
|
||||
if self.kbd_delay > 0: self.kbd_delay -= 1
|
||||
if self.ion_delay > 0:
|
||||
self.ion_delay -= 1
|
||||
if self.ion_delay == 0: self.ie = 1
|
||||
if self.ie and (self.kbf or self.chf):
|
||||
self.ie, self.memory[0], self.pc = 0, self.pc, 1
|
||||
instruction, current_pc = self.memory[self.pc], self.pc
|
||||
self.pc = (current_pc + 1) & 0xFFF
|
||||
opcode, rest = (instruction >> 9) & 7, instruction & 511
|
||||
if opcode < 6:
|
||||
target = (current_pc & 0xF80 if rest & 0x80 else 0) | (rest & 0x7F)
|
||||
if rest & 0x100:
|
||||
if 8 <= target <= 15: self.memory[target] = (self.memory[target] + 1) & 0xFFF
|
||||
target = self.memory[target]
|
||||
if opcode == 0: self.ac &= self.memory[target]
|
||||
elif opcode == 1:
|
||||
self.ac, self.link = (self.ac + self.memory[target]) & 0xFFF, self.link ^ ((self.ac + self.memory[target]) >> 12)
|
||||
elif opcode == 2:
|
||||
self.memory[target] = (self.memory[target] + 1) & 0xFFF
|
||||
if not self.memory[target]: self.pc = (self.pc + 1) & 0xFFF
|
||||
elif opcode == 3: self.memory[target], self.ac = self.ac, 0
|
||||
elif opcode == 4: self.memory[target], self.pc = self.pc, (target + 1) & 0xFFF
|
||||
elif opcode == 5: self.pc = target & 0xFFF
|
||||
elif opcode == 6: self.execute_iot(rest)
|
||||
elif opcode == 7: self.execute_opr(rest)
|
||||
|
||||
def execute_opr(self, opr):
|
||||
if opr in (0, 0o401): return
|
||||
group = 1 if not (opr & 0x100) else (3 if (opr & 1) else 2)
|
||||
b10 = (opr >> 1) & 1
|
||||
if group == 1:
|
||||
if opr & 0x80: self.ac = 0
|
||||
if opr & 0x40: self.link = 0
|
||||
if opr & 0x20: self.ac ^= 0xFFF
|
||||
if opr & 0x10: self.link ^= 1
|
||||
if opr & 1: self.ac, self.link = (self.ac + 1) & 0xFFF, self.link ^ ((self.ac + 1) >> 12)
|
||||
if opr & 0x08: self.rotate_ac_link(1, b10 + 1)
|
||||
if opr & 0x04: self.rotate_ac_link(-1, b10 + 1)
|
||||
if b10 and not (opr & 0x0C): self.ac = ((self.ac << 6) | (self.ac >> 6)) & 0xFFF
|
||||
elif group == 2:
|
||||
if bool(((opr & 0x40) and self.ac > 0x7FF) or ((opr & 0x20) and not self.ac) or ((opr & 0x10) and self.link)) != bool(opr & 0x08): self.pc = (self.pc + 1) & 0xFFF
|
||||
if opr & 0x80: self.ac = 0
|
||||
if opr & 0x04: self.ac |= self.sr
|
||||
if b10: self.halted = True
|
||||
|
||||
def execute_iot(self, cmd):
|
||||
dev, op = (cmd >> 3) & 63, cmd & 7
|
||||
if dev == 0:
|
||||
if op == 1: self.ion_delay = 2
|
||||
elif op == 2: self.ie = self.ion_delay = 0
|
||||
elif op == 3 and (self.kbf or self.chf): self.pc = (self.pc + 1) & 0xFFF
|
||||
elif op == 4: self.ac = (self.link << 11) | ((1 if (self.kbf or self.chf) else 0) << 9) | (self.ie << 7)
|
||||
elif op == 5: self.link, self.ion_delay = (self.ac >> 11) & 1, 2
|
||||
elif op == 7:
|
||||
self.ac = self.link = self.kbf = self.kbb = self.chf = self.chb = self.ie = self.ion_delay = 0
|
||||
elif dev == 3 and op != 5:
|
||||
if (op & 1) and self.kbf: self.pc = (self.pc + 1) & 0xFFF
|
||||
if op & 2: self.ac = self.kbf = 0
|
||||
if op & 4: self.ac |= self.kbb
|
||||
elif dev == 4 and op != 5:
|
||||
if op == 0: self.chf = 1
|
||||
else:
|
||||
if (op & 1) and self.chf: self.pc = (self.pc + 1) & 0xFFF
|
||||
if op & 2: self.chf = 0
|
||||
if op & 4: self.chb, self.chf = self.ac, 1; self.outc(self.ac)
|
||||
|
||||
def load_oct(self, filepath):
|
||||
with open(filepath, 'r') as f:
|
||||
for line in f:
|
||||
try:
|
||||
p = line.split(':')
|
||||
self.memory[int(p[0], 8) & 0xFFF] = int(p[1].strip().split()[0], 8)
|
||||
except Exception: pass
|
||||
|
||||
# entry point (for ildec.py to be usable as a module)
|
||||
def ildec(fname='', init_pc=0o200, init_sr=0):
|
||||
print('\n ===== ILDEC PDP-8/L ONLINE =====')
|
||||
emu = ILDEC_PDP8(init_pc, init_sr)
|
||||
if fname: emu.load_oct(fname)
|
||||
while True:
|
||||
print(f"\nPC: {emu.pc:04o} | L AC: {emu.link} {emu.ac:04o} | SR: {emu.sr:04o}\n")
|
||||
cmd = input('> ').strip().lower()
|
||||
if cmd == 'q': break
|
||||
elif cmd == 'r': emu.reset(init_pc, init_sr)
|
||||
elif cmd == 'n': emu.__init__(init_pc, init_sr), print('All memory cleared!')
|
||||
elif cmd == 'c': emu.start(emu.pc)
|
||||
elif cmd == 'l': emu.pc = emu.sr
|
||||
elif cmd == 's': emu.step()
|
||||
elif cmd in ('e', 'd'):
|
||||
if cmd == 'd': emu.memory[emu.pc] = emu.sr
|
||||
print(f"{emu.pc:04o}: {emu.memory[emu.pc]:04o}")
|
||||
emu.pc = (emu.pc + 1) & 0xFFF
|
||||
elif cmd == 'i':
|
||||
fname = input('File path: ').strip()
|
||||
if fname: emu.load_oct(fname); print(f'Memory imported from {fname}')
|
||||
elif cmd == 'x':
|
||||
fname = input('File path: ').strip()
|
||||
if fname:
|
||||
with open(fname, 'w') as f:
|
||||
for i, val in enumerate(emu.memory):
|
||||
if val: f.write(f"{i:04o}: {val:04o}\n")
|
||||
print(f'Memory exported to {fname}')
|
||||
else:
|
||||
try: emu.sr = int(cmd, 8)
|
||||
except Exception: print('Invalid action!')
|
||||
|
||||
# Run it standalone
|
||||
if __name__ == '__main__':
|
||||
a = sys.argv if hasattr(sys, 'argv') else []
|
||||
ildec(a[1] if len(a) > 1 else '', int(a[2], 8) if len(a) > 2 else 0o200, int(a[3], 8) if len(a) > 3 else 0)
|
||||
Reference in New Issue
Block a user