initial upload

This commit is contained in:
Luxferre
2026-06-29 20:09:40 +03:00
commit b491b1f100
10 changed files with 4619 additions and 0 deletions
+199
View File
@@ -0,0 +1,199 @@
# ILDEC: a highly portable minimal DEC PDP-8/L emulator
## About
ILDEC (pronounced roughly like _ill-deck_) is a project aimed at creating a working and practical PDP-8 platform emulator in as little amount of code as possible for as many popular programming runtimes as possible.
The name itself doesn't have an official meaning. Common interpretations include but are not limited to:
- "Import, Load, Deposit, Examine, Continue" (5 most commonly used interactive commands in the emulator)
- "Interpreted/Interactive Land of DEC/Loader for DEC"
- "Inexpensive, Lazy DEC"
- "In Lieu of [DeCastro](https://en.wikipedia.org/wiki/Edson_de_Castro)" (PDP-8/L was the last of his designs before leaving for Data General)
- "It Lives through Digital Environmental Collapses" (see the FAQ section)
- "Instruction Logic to Ditch Extra Complexity"
- "Inefficient Luxferre's Decision to Educate Consumers"
- ... and so on.
## Supported PDP-8 implementations
ILDEC is most closely emulating the **unextended** version of PDP-8/L (and, to some extent, PDP-8/S), as it implements the absolute minimum required to run OS-less PDP-8 software packages like FOCAL-69. As a deliberate design decision, it **does not** support extensions like memory bank switching, EAE (Extended Arithmetic Element) with MQ (Multiplier/Quotient) register, or any device controls other than interrupts, teleprinter and keyboard.
See the [ISA documentation](pdp8_isa_ildec.md) for all the details about the supported registers, memory and instruction set. This document may also be very helpful if you want to design your own PDP-8 emulator from scratch or just port ILDEC to a new runtime.
## Implementations
This is the list of all known implementations of the ILDEC specification, both the author's and third-party.
The list is divided into four categories: CLI, GUI (including Web), embedded and library-only implementations. In case an implementation can be used in several niches, it may appear in several categories.
Forks and mods are allowed and even encouraged!
### Official Python implementation: [ildec.py](python/ildec.py)
- Categories: CLI, Embedded, Library
- Compatible with: CPython, MicroPython, CircuitPython (only tested on a LilyGO T-Deck Plus in the [T-DeckARD](https://codeberg.org/luxferre/t-deckard) environment, see below for installation details), Brython (as a library)
## Usage
Depending on the implementation, the way to interact with ILDEC may differ, but generally all implementations that aren't library-only must expose the same set of commands.
### CLI implementations
In case of CLI, the emulator must accept the following optional command-line parameters (in this very order):
- preloaded tape file name (string, default `` (empty)) in the `.oct` format (see below)
- starting PC (program counter) address (4 digits, octal, default `0200`)
- starting SR (switch register) state (4 digits, octal, default `0000`)
Upon starting, the emulator must present an optional welcome banner, then the initial machine state (see below), then a DOS-like prompt (`> `), and accept either a single 4-digit octal number as a command (and update the machine state by changing the SR (switch register) value) or any of the following (case-insensitive) single-character commands:
- `l`: load the SR value into the PC register
- `d`: deposit the value in SR under the memory address which is in PC, then run the examine command (which auto-increments PC)
- `e`: examine (display) the value at the current address in PC, then increment PC
- `c`: continue/start running the program starting at the address in PC
- `s`: step through one instruction at a time (execute it and then increment PC)
- `r`: reset the machine (clear every internal state and register except the memory contents, then restore the initial PC and SR values if they were provided upon emulator start)
- `n`: (new, nuke) fully clear the memory contents, effectively reinitializing the emulator from scratch
- `i`: import a tape in the `.oct` format into the machine memory
- `x`: export current machine memory into the `.oct` format (zero-valued locations are not exported)
- `q`: quit the emulator.
Additionally, you may press Ctrl+D (or Shift+Vol on the T-Deck) to halt execution at any time and return to the emulator command interface.
After running any command and/or halting execution, the emulator must print a new machine state before printing the prompt. The state looks like this:
```
PC: [PC] | L AC: [L] [AC] | SR: [switches]
```
As an example, this is the initial state that gets displayed when starting a CLI implementation of ILDEC with no parameters:
```
PC: 0200 | L AC: 0 0000 | SR: 0000
```
And this is how the state should change after successfully running the bundled [test routine](soft/test.oct) which prints 31 dots when all tests pass:
```
> i
File path: soft/test.oct
Memory imported from soft/test.oct
PC: 0200 | L AC: 0 0000 | SR: 0000
> c
...............................
PC: 1376 | L AC: 0 0000 | SR: 0000
>```
### GUI/Web implementations
While there's no unified way of doing this, any GUI implementation of ILDEC, including Web-based ones, must fully provide functional equivalents of all interactive commands, parameters and displays mentioned in the CLI section.
The designs may vary from just embedding the same CLI into a web page to full graphical simulation with switches and blinkenlights.
### Embedded implementations
In case the embedded target platform provides any sort of serial terminal interface, the emulator must implement the same UI as for the CLI applications, with the exception of preloading the initial state from command-line parameters.
Here's how to install and run ILDEC as an applet on the [T-DeckARD](https://codeberg.org/luxferre/t-deckard) CircuitPython-based environment for LilyGO T-Deck (Plus):
1. Mount your CircuitPython device volume.
2. Create a directory for your PDP-8 tapes in the device volume root, `data/ildec` is the recommended name but you're free to choose any other.
3. Copy `python/ildec.py` from this repo into the `app/` directory on the device.
4. Copy all the `soft/*.oct` files from this repo into the directory you created on step 2 (e.f. `data/ildec`).
5. Unmount and eject the volume.
Now, from the T-DeckARD REPL, you can import the emulator with `from app.ildec import ildec` and then run it by typing `ildec()`, optionally passing the parameters in the form `(filename, PC, SR)`, e.g. `ildec("data/ildec/test.oct", 0o200, 0o1234)` to preload the tape, specify the starting PC and switch register state.
From the interpreter, you can use all the same commands as the usual CLI, including `i` for import and `x` for export.
### Library-only implementations
A non-UI implementation of ILDEC must offer the user a way to inspect and customize the emulator state (memory, PC, AC, L, SR) on the fly and expose methods for continuous running, stepping through and resetting the machine. Also, if possible, a method to load `.oct` tape images is recommended to implement.
## Tape format (`.oct`)
To load (and save) memory images, ILDEC uses an extremely simple `.oct` format that is purely ASCII plain text and resembles old `.lst` listing formats. Each line contains a pair of two 4-digit octal values, `[address]` and `[content]`, separated by a colon and (optional) whitespace: `[address]: [content]`. Everything after the `[content]` value up to the next newline must be ignored by the reader.
Here's a short example of a program starting from the address 0000 in the `.oct` format:
```
0000: 7301
0001: 7001
0002: 7430
0003: 7402
0004: 5001
```
To facilitate converting from popular `.bin` and `.rim` DEC tape formats into ILDEC's `.oct`, the `conv` directory of this repository contains two Python scripts: [bin2oct.py](conv/bin2oct.py) and [rim2oct.py](conv/rim2oct.py). It also contains a script called [oct2rim.py](conv/oct2rim.py) in case you need to export an `.oct` tape into other PDP-8 emulation environments as a `.rim` image.
## Bundled programs
ILDEC repo contains a few examples of PDP-8 software that you can try running on the emulator. For now, the following programs are bundled:
- [Test program](soft/test.oct): the one you should run first. Tests the entire emulation logic. Loads at the (default) address 0200. If the tests are successful, it must print 31 dots and halt at the PC address 1376.
- [Hellorld](soft/hellorld.oct): a program that just prints `HELLORLD!` and then halts. Tribute to [Usagi Electric](https://www.youtube.com/@UsagiElectric) (David Lovett).
- [FOCAL-69 interpreter](soft/focal69.oct): probably the most complete FOCAL programming language version that can run on an unextended PDP-8/L.
## FAQ
### Why bother with PDP-8 emulation in 2026?
Among the mass-produced, non-"esoteric" architectures, PDP-8 was stucturally one of the simplest. For instance, the bit-serial version, PDP-8/S, was made just with 519 logic gates. As a result, 1) this architecture, while being mainstream enough, is one of the easiest to rebuild from scratch in an event of technological collapse, 2) its instruction set (including all the essential IOT and OPR combos) is still smaller than the modern RISC-V (which is considered minimalist on its own). Combined with fixed instruction length (just like in RISC-V, by the way) and clean instruction bit layout (just unlike RISC-V, see e.g. the bit order for its `JAL` instruction), all this makes PDP-8 a perfect target for studying and emulation. ILDEC is a project of mine that serves both of these purposes: studying and emulation.
### What is the rationale behind this project then? Don't we already have [SIMH](https://github.com/simh/simh) / [OpenSIMH](https://opensimh.org/) and other good, accurate and well-documented emulators? Why only emulate the unextended (4K) PDP-8/L with the bare minimum of peripherals?
Most existing PDP-8 emulators are good enough for the purpose of historical software preservation, because they not only support all existing extensions and peripherals to run all the advanced stuff that was ever written for this hardware, but also replicate the full machine logic down to the original clock speeds if you want. And I think that's really cool and something that true retro fans will really appreciate, especially considering that the Open SIMH project is led by a former DEC engineer.
However, all that comes at a cost of increased complexity. SIMH code may be clean but it's just a lot to grasp. One of the main ILDEC's goals is to give you a **practical** (not necessarily historically accurate) recreation of the simplest commercially successful architecture of the past in the smallest possible amount of code while being as portable as possible. For example, the first ILDEC implementation was [written in Python](python/ildec.py) and contains just 191 SLOC, and that includes all the CLI interaction and some compatibility tweaks for MicroPython and CircuitPython + T-DeckARD runtimes. The core CPU logic (all supported instruction groups, indirection, autoincrements, interrupts, I/O etc) is well under 90 SLOC, and the code is very straightforward and understandable. In fact, using any implementation hosted in this repo as a reference, especially in combination with the [ISA document](pdp8_isa_ildec.md), is enough for anyone to recreate their own virtual PDP-8/L from scratch in a very short amount of time in their favorite programming language.
### Why isn't FORTRAN-4K compiler bundled?
While it says FORTRAN-4K, it doesn't make a lot of sense to run it on a 4K system. From the official documentation:
> Compiler and symbol table requires locations 0003-7577 (7574(8) locations)
So, you'll be only left with 0200 (128) words of memory. What will you compile with that?
Meanwhile, in FOCAL-69, if we believe the same official docs, you already have about 700 words free with all math functions enabled, but can unload logarithms, exponents and trigonometry if you don't need them and get up to 1100 words for the (tokenized) program, which is quite a lot.
### Where can I find more software for PDP-8/L?
The main problem with software collections for retro machines is that they don't always specify which exact models any particular program works on. Some trusted sources are:
- Mike Douglas's [DeRamp](https://deramp.com/downloads/mfe_archive/011-Digital%20Equipment%20Corporation/01%20DEC%20PDP-8%20Family%20Software/): contains a lot of relevant software and other useful materials for all PDP-8 generations.
- Vince Slyngstad's [so-much-stuff](https://so-much-stuff.com/pdp8/papertape/papertape.php): download the TAR archive from the link at the bottom, then use `labels.txt` file(s) for navigating through them. A lot of stuff there though either seems to be corrupted or requires more advanced hardware than PDP-8/L.
- [BitSavers](https://bitsavers.org/bits/DEC/pdp8/): not a lot, and most stuff also requires more advanced hardware, but still something.
### How do I write my own software for PDP-8/L?
Since this machine doesn't have any technical means of running an OS like DMS, TSS/8 or OS/8, your most viable options for writing programs for it are either FOCAL-69 (bundled in the repo) on the emulated machine itself, or some kind of PAL-III or MACRO-8 symbolic assemblers on the host side. I recommend two assembler options available nowadays.
The first (and mostly recommended) option is the `pal.c` single-file assembler, which you can download, build and install like this:
```sh
curl https://homepage.divms.uiowa.edu/~jones/pdp8/pal.txt > pal.c
cc -O2 -s -o pal pal.c
sudo cp pal /usr/local/bin/ # optional step
```
This assembler outputs `.lst` and `.bin` files. The latter can be fed into `conv/bin2oct.py` script to produce the `.oct` file loadable by ILDEC. Alternatively, you can generate a `.oct` file directly from the `.lst` file using AWK:
```awk
awk '$2~/^[0-7]+/{printf "%04d: %04d\n",$2,$3}' input.lst > output.oct
```
The second option is [pdpnasm](https://people.csail.mit.edu/ebakke/pdp8/), but it's so minimal that it might not understand some I/O-specific mnemonics and you might have to use direct octal notation for them instead.
To convert pdpnasm's output `.po` files into ILDEC's `.oct` files, you can use a small helper shell script called [po2oct.sh](conv/po2oct.sh) in this repo. Since both formats are plaintext, the conversion is fairly trivial.
### What do I need to do if I want to port ILDEC to a yet unsupported platform?
Simple: first, implement the emulation core according to [the ISA document](pdp8_isa_ildec.md), then implement the UI and `.oct` format support according to the above guidelines. Once your emulator is compatible with ILDEC on the ISA, command and format levels, you may fully call it an ILDEC port.
## Credits
Created by Luxferre in 2026, released into the public domain with no warranties.
In memoriam Edson DeCastro (1938 2024).
Special thanks to [Nino Ivanov](https://github.com/KedalionDaimon) for sharing a complete FOCAL-69 tape image.
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
# Convert PDP-8 .bin format files to ILDEC .oct format
# Created by Luxferre in 2026, released into the public domain
import sys
def print_error(msg):
sys.stderr.write(f"Error: {msg}\n")
def convert_bin_to_oct(input_path, output_path):
# Initialize memory to 0
memory = [0] * 4096
try:
with open(input_path, "rb") as f:
bytes_data = f.read()
except Exception as e:
print_error(f"Failed to read input file '{input_path}': {e}")
return False
current_address, data_frames = 0, []
for b in bytes_data:
if b != 0xFF:
if b & 0x80 and not (b & 0x40) and len(data_frames) > 1:
break
if not (b & 0x80):
data_frames.append(b)
if len(data_frames) < 2:
print_error("No valid binary data frames found in the input file.")
return False
words_loaded = 0
for i in range(0, len(data_frames) - 2, 2):
word = ((data_frames[i] & 0x3F) << 6) | (data_frames[i+1] & 0x3F)
if data_frames[i] & 0x40:
current_address = word
else:
memory[current_address] = word
words_loaded += 1
current_address = (current_address + 1) & 0xFFF
# Output to .oct format
try:
with open(output_path, 'w') as f:
for addr in range(4096):
if memory[addr] != 0:
f.write(f"{addr:04o}: {memory[addr]:04o}\n")
except Exception as e:
print_error(f"Failed to write output file: {e}")
return False
print(f"Successfully converted '{input_path}' to '{output_path}' ({words_loaded} words loaded).")
return True
def main():
args = sys.argv if hasattr(sys, 'argv') else []
if len(args) < 2 or len(args) > 3:
print("Usage: python3 bin2oct.py <input_bin_file> [output_oct_file]")
sys.exit(1)
input_path = args[1]
if len(args) == 3:
output_path = args[2]
else:
# Generate default output name by replacing extension with .oct
dot_idx = input_path.rfind('.')
slash_idx = max(input_path.rfind('/'), input_path.rfind('\\'))
if dot_idx > slash_idx:
output_path = input_path[:dot_idx] + ".oct"
else:
output_path = input_path + ".oct"
success = convert_bin_to_oct(input_path, output_path)
if not success:
sys.exit(1)
if __name__ == '__main__':
main()
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
# Convert ILDEC .oct format files to PDP-8 .rim format
# Created by Luxferre in 2026, released into the public domain
import sys
def print_error(msg):
sys.stderr.write(f"Error: {msg}\n")
def convert_octaddr_to_rim(input_path, output_path):
pairs = []
line_num = 0
try:
with open(input_path, 'r') as f:
for line in f:
line_num += 1
# Strip comments starting with /, #, ;
clean_line = line
for comment_char in ('/', '#', ';'):
if comment_char in clean_line:
clean_line = clean_line.split(comment_char, 1)[0]
clean_line = clean_line.strip()
if not clean_line:
continue
if ':' not in clean_line:
print_error(f"Line {line_num}: Missing colon separator (format is 'addr: data').")
return False
parts = clean_line.split(':', 1)
addr_str = parts[0].strip()
data_str = parts[1].strip()
try:
addr = int(addr_str, 8)
except ValueError:
print_error(f"Line {line_num}: Invalid octal address '{addr_str}'.")
return False
try:
data = int(data_str, 8)
except ValueError:
print_error(f"Line {line_num}: Invalid octal data '{data_str}'.")
return False
if not (0 <= addr <= 0o7777):
print_error(f"Line {line_num}: Address '{addr_str}' ({oct(addr)}) out of 12-bit range (0-7777).")
return False
if not (0 <= data <= 0o7777):
print_error(f"Line {line_num}: Data '{data_str}' ({oct(data)}) out of 12-bit range (0-7777).")
return False
pairs.append((addr, data))
except Exception as e:
print_error(f"Failed to read input file '{input_path}': {e}")
return False
if not pairs:
print_error("No valid address:data pairs found in the input file.")
return False
# Write RIM format
# Leader: 2 bytes of 0xFF
# For each pair:
# addr high: ((addr >> 6) & 0x3F) | 0x40
# addr low: addr & 0x3F
# data high: (data >> 6) & 0x3F
# data low: data & 0x3F
# Trailer: 2 bytes of 0xFF
try:
with open(output_path, 'wb') as f:
# Write leader
f.write(bytes([0xFF] * 2))
for addr, data in pairs:
addr_hi = ((addr >> 6) & 0x3F) | 0x40
addr_lo = addr & 0x3F
data_hi = (data >> 6) & 0x3F
data_lo = data & 0x3F
f.write(bytes([addr_hi, addr_lo, data_hi, data_lo]))
# Write trailer
f.write(bytes([0xFF] * 2))
except Exception as e:
print_error(f"Failed to write output file: {e}")
return False
print(f"Successfully converted '{input_path}' to '{output_path}' ({len(pairs)} words).")
return True
def main():
args = sys.argv if hasattr(sys, 'argv') else []
if len(args) < 2 or len(args) > 3:
print("Usage: python3 oct2rim.py <input_octaddr_file> [output_rim_file]")
sys.exit(1)
input_path = args[1]
if len(args) == 3:
output_path = args[2]
else:
# Generate default output name by replacing extension with .rim
dot_idx = input_path.rfind('.')
slash_idx = max(input_path.rfind('/'), input_path.rfind('\\'))
if dot_idx > slash_idx:
output_path = input_path[:dot_idx] + ".rim"
else:
output_path = input_path + ".rim"
success = convert_octaddr_to_rim(input_path, output_path)
if not success:
sys.exit(1)
if __name__ == '__main__':
main()
Executable
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
# A small shell script (using POSIX AWK) to convert pdpnasm .po files into ILDEC .oct files
# Usage: po2oct.sh input.po output.oct
# Created by Luxferre in 2026, released into the public domain
for l in $(cat "$1"); do
printf '%d\n' "0$l"
done | awk 'BEGIN{a=0}/[0-9]+/{if($1>4096)a=$1%4096;else{if($1)printf("%04o: %04o\n",a,$1);a++}}' > "$2"
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
# Convert PDP-8 .rim format files to ILDEC .oct format
# Created by Luxferre in 2026, released into the public domain
import sys
def print_error(msg):
sys.stderr.write(f"Error: {msg}\n")
def convert_rim_to_oct(input_path, output_path):
# Initialize memory to 0
memory = [0] * 4096
try:
with open(input_path, "rb") as f:
raw_data = f.read()
except Exception as e:
print_error(f"Failed to read input file '{input_path}': {e}")
return False
# Filter out bytes with MSB set (bit 7)
data = [b for b in raw_data if not (b & 0x80)]
i = 0
words_loaded = 0
while i < len(data) - 1:
word = ((data[i] & 0x3F) << 6) | (data[i+1] & 0x3F)
if data[i] & 0x40 and i + 3 < len(data):
addr = word
val = ((data[i+2] & 0x3F) << 6) | (data[i+3] & 0x3F)
memory[addr] = val
words_loaded += 1
i += 4
else:
i += 2
# Output to .oct format
try:
with open(output_path, 'w') as f:
for addr in range(4096):
if memory[addr] != 0:
f.write(f"{addr:04o}: {memory[addr]:04o}\n")
except Exception as e:
print_error(f"Failed to write output file: {e}")
return False
print(f"Successfully converted '{input_path}' to '{output_path}' ({words_loaded} words loaded).")
return True
def main():
args = sys.argv if hasattr(sys, 'argv') else []
if len(args) < 2 or len(args) > 3:
print("Usage: python3 rim2oct.py <input_rim_file> [output_oct_file]")
sys.exit(1)
input_path = args[1]
if len(args) == 3:
output_path = args[2]
else:
# Generate default output name by replacing extension with .oct
dot_idx = input_path.rfind('.')
slash_idx = max(input_path.rfind('/'), input_path.rfind('\\'))
if dot_idx > slash_idx:
output_path = input_path[:dot_idx] + ".oct"
else:
output_path = input_path + ".oct"
success = convert_rim_to_oct(input_path, output_path)
if not success:
sys.exit(1)
if __name__ == '__main__':
main()
+262
View File
@@ -0,0 +1,262 @@
# DEC PDP-8/L Emulation Core Instruction Set Architecture
This document provides a detailed reference of the PDP-8/L instruction set implemented in the ILDEC emulation core.
---
## 1. Architectural Overview & State
The emulator models a simplified 12-bit DEC PDP-8/L processor. CPU registers and state are defined as follows:
* **`memory`**: An array of 4,096 12-bit words (a single 4K memory field).
* **`pc` (Program Counter)**: 12-bit register holding the address offset of the next instruction.
* **`ac` (Accumulator)**: 12-bit register used for arithmetic, logic, and I/O.
* **`link` (Link Register)**: 1-bit register functioning as a carry/overflow indicator, logically positioned to the left of the Accumulator for shifts.
* **sr` (Switch Register)**: 12-bit register representing front panel switches, queried via operate instructions.
* **Program Interrupt Registers**:
* `ie` (Interrupt Enable Flag): 1-bit register controlling the interrupt system.
* `ion_delay` (Interrupt On Delay): Counter managing the 1-instruction latency for enabling interrupts.
* **Peripheral Buffers & Flags**:
* `kbb` (Keyboard Buffer) & `kbf` (Keyboard Flag)
* `chb` (Character Buffer) & `chf` (Character Flag)
---
## 2. Memory Addressing System
Memory is organized into **32 pages of 128 words each** (4,096 words total).
Memory Reference Instructions (MRIs) contain a 9-bit address specification block. The bits are mapped as follows:
```
3 4 5 6 7 8 9 10 11
+---+---+---+---+---+---+---+---+---+
| I | Z | 7-bit Page Offset |
+---+---+---+---+---+---+---+---+---+
```
* **Bit 3 (`I` - Indirect Address Bit)**:
* `0`: Direct addressing.
* `1`: Indirect addressing.
* **Bit 4 (`Z` - Sector/Page Select Bit)**:
* `0`: Page 0 (addresses `0o0000` to `0o0177`).
* `1`: Current Page (the page containing the instruction being executed).
* **Bits 511 (Page Offset)**: 7-bit relative address (`0` to `127`).
### Target Address Resolution Flow
1. **Base Address Offset Calculation**:
* If `Z = 1`: The 5 high-bits of the current `PC` are combined with the 7-bit offset:
$$\text{Base Target} = (\text{PC} \ \& \ \text{0xF80}) \ | \ \text{Offset}$$
* If `Z = 0`: The base target is within Page 0:
$$\text{Base Target} = \text{Offset}$$
2. **Effective Address Calculation**:
* **Direct Addressing (`I = 0`)**:
$$\text{Effective Address} = \text{Base Target}$$
* **Indirect Addressing (`I = 1`)**:
* The pointer address is the base target:
$$\text{Pointer Address} = \text{Base Target}$$
* **Auto-Indexing**: If the base target is in the range `8` to `15` (`0o0010` to `0o0017` in octal), the pointer word is incremented by 1 (modulo 4096) and written back to memory:
$$\text{Memory}[\text{Pointer Address}] = (\text{Memory}[\text{Pointer Address}] + 1) \ \& \ \text{0xFFF}$$
* The 12-bit target pointer offset is read from the pointer address:
$$\text{Effective Address} = \text{Memory}[\text{Pointer Address}]$$
---
## 3. Instruction Set Details
Every instruction is 12 bits wide. The top 3 bits (bits 02) specify the opcode.
```
0 1 2 3 4 5 6 7 8 9 10 11
+---+---+---+---+---+---+---+---+---+---+---+---+
| Opcode | Rest of Instruction |
+---+---+---+---+---+---+---+---+---+---+---+---+
```
---
### 3.1. Memory Reference Instructions (Opcodes 05)
These instructions perform operations involving memory locations.
| Opcode | Octal | Mnemonic | Description | Py-8-S Emulation Core Logic |
| :---: | :---: | :--- | :--- | :--- |
| `0` | `0o0` | **AND** | Logical AND | `self.ac &= self.memory[target_addr]` |
| `1` | `0o1` | **TAD** | Two's Complement Add | `val = self.ac + self.memory[target_addr]`<br>`self.ac = val & 0xFFF`<br>`self.link ^= (val >> 12)` *(Carry toggles Link)* |
| `2` | `0o2` | **ISZ** | Increment and Skip if Zero | `self.memory[target_addr] = (self.memory[target_addr] + 1) & 0xFFF`<br>`if self.memory[target_addr] == 0: PC = (PC + 1) & 0xFFF` |
| `3` | `0o3` | **DCA** | Deposit and Clear AC | `self.memory[target_addr] = self.ac`<br>`self.ac = 0` |
| `4` | `0o4` | **JMS** | Jump to Subroutine | `self.memory[target_addr] = self.pc`<br>`self.pc = (target_addr + 1) & 0xFFF` |
| `5` | `0o5` | **JMP** | Jump | `self.pc = target_addr & 0xFFF` |
---
### 3.2. Input/Output Transfer (IOT) Instructions (Opcode 6)
IOT instructions control and transfer data to/from peripheral devices. The instruction format is:
* **Device Code (`dev`)**: Bits 38 (6 bits, range 063).
* **Sub-operation (`op`)**: Bits 911 (3 bits, range 07).
#### Device 0: Interrupt Control (Program Interrupt System)
Manages the CPU's Program Interrupt (PI) system.
* **`6001` (ION - Interrupt Turn On)**: Enables the program interrupt system. The interrupt system is enabled after the execution of the instruction following ION.
* **`6002` (IOF - Interrupt Turn Off)**: Disables the program interrupt system immediately.
* **`6003` (SRQ - Skip on Interrupt Request)**: Skips the next instruction if a device interrupt request is pending:
`if self.kbf or self.chf: PC = PC + 1`
* **`6004` (GTF - Get Flags)**: Loads various processor flags into the Accumulator:
* AC Bit 0: Link Register (`self.link`)
* AC Bit 2: Interrupt Request (`self.kbf or self.chf`)
* AC Bit 4: Interrupt Enable Flip-Flop (`self.ie`)
* All other bits are cleared to `0`.
* **`6005` (RTF - Restore Flags)**: Restores flags from the Accumulator:
* Link is restored from AC Bit 0: `self.link = (self.ac >> 11) & 1`
* Enables interrupts with a one-instruction delay (equivalent to `ION`).
* **`6007` (CAF - Clear All Flags)**: Resets the processor and I/O status:
* Clears Accumulator and Link: `self.ac = self.link = 0`
* Clears I/O device buffers/flags: `self.kbf = self.kbb = self.chf = self.chb = 0`
* Disables the program interrupt system: `self.ie = self.ion_delay = 0`
#### Device 3: Keyboard / Reader
Interfaces with the standard keyboard/character input.
* **Micro-programmed Keyboard Operations (bits 9, 10, 11)**:
* **Bit 11** (`op & 1`): Skip on Keyboard Flag.
* **Bit 10** (`(op >> 1) & 1`): Clear Accumulator and Keyboard Flag.
* **Bit 9** (`op >> 2`): OR Keyboard Buffer into the Accumulator.
Combined, these bits form the standard Keyboard instructions:
* **`6031` (KSF - Keyboard Skip on Flag)**: `if self.kbf: PC = PC + 1`
* **`6032` (KCC - Keyboard Clear Flag)**: `self.ac = self.kbf = 0`
* **`6034` (KRS - Keyboard Read Static)**: `self.ac |= self.kbb`
* **`6036` (KRB - Keyboard Read Buffer)**: `self.ac = self.kbf = 0` followed by `self.ac |= self.kbb`
#### Device 4: Teleprinter / Punch
Interfaces with the standard teleprinter/character output.
* **`6040` (Custom IOT: Force Teleprinter Flag)**:
Forces the teleprinter flag to high (`chf = 1`).
* **Micro-programmed Teleprinter Operations (bits 9, 10, 11)**:
* **Bit 11** (`op & 1`): Skip on Teleprinter Flag.
* **Bit 10** (`(op >> 1) & 1`): Clear Teleprinter Flag.
* **Bit 9** (`op >> 2`): Load Accumulator into Character Buffer, trigger write callback, and set Teleprinter Flag.
Combined, these bits form the standard Teleprinter instructions:
* **`6041` (TSF - Teleprinter Skip on Flag)**: `if self.chf: PC = PC + 1`
* **`6042` (TCF - Teleprinter Clear Flag)**: `self.chf = 0`
* **`6044` (TPC - Teleprinter Print Character)**: `self.chb = self.ac; self.chf = 1; self.outc(self.ac)`
* **`6046` (TLS - Teleprinter Load Sequence)**: `self.chf = 0; self.chb = self.ac; self.chf = 1; self.outc(self.ac)`
---
### 3.3. Operate Instructions (Opcode 7)
Operate (OPR) instructions are register manipulation commands. They do not reference memory addresses. The lower 9 bits function as micro-programmed instruction controls.
> [!NOTE]
> If the lower 9 bits are `0` (`7000`) or `0o401` (`7401`), the instruction is treated as a `NOP` (No Operation) and returns immediately.
#### Sub-group Determination
* **Bit 3** (MSB of the instruction's lower 9 bits):
* If `0`: **Group 1** (Register manipulation, complements, and shifts).
* If `1`: **Group 2** (Status testing, skips, and halt).
* *Note: Group 3 (EAE/MQ register operations) is not supported.*
---
#### Group 1 Operate Instructions (Bit 3 = 0)
Used for bitwise operations, clearing registers, incrementing, and rotations.
```
3 4 5 6 7 8 9 10 11
+---+---+---+---+---+---+---+---+---+
| 0 |CLA|CLL|CMA|CML|RAR|RAL|BSW|IAC|
+---+---+---+---+---+---+---+---+---+
```
##### Bit Mapping & Function
* **Bit 4 (`CLA`)**: Clear Accumulator.
* **Bit 5 (`CLL` / `b5`)**: Clear Link.
* **Bit 6 (`CMA` / `b6`)**: Complement Accumulator.
* **Bit 7 (`CML` / `b7`)**: Complement Link.
* **Bit 8 (`RAR` / `b8`)**: Rotate Accumulator and Link Right.
* **Bit 9 (`RAL` / `b9`)**: Rotate Accumulator and Link Left.
* **Bit 10 (`BSW` / `b10`)**: Byte Swap / Rotate Command Modifier.
* If `RAR` or `RAL` is active: Specifies double rotation (rotate 2 positions).
* If neither `RAR` nor `RAL` is active: Swaps the high and low 6-bit halves of `AC`:
`self.ac = ((self.ac << 6) | (self.ac >> 6)) & 0xFFF`
* **Bit 11 (`IAC` / `b11`)**: Increment Accumulator.
##### Execution Order
Group 1 micro-operations are executed sequentially in the following order:
1. **Clear**:
* If `CLA` is `1`: `self.ac = 0`
* If `CLL` is `1`: `self.link = 0`
2. **Complement**:
* If `CMA` is `1`: `self.ac ^= 0xFFF`
* If `CML` is `1`: `self.link ^= 1`
3. **Increment**:
* If `IAC` is `1`:
`self.ac, self.link = (self.ac + 1) & 0xFFF, self.link ^ ((self.ac + 1) >> 12)` *(Carry toggles Link)*
4. **Rotation / Swap**:
* If `RAR` is `1`: Circular right rotate `b10 + 1` times of the 13-bit combined register `[Link, AC]`.
* If `RAL` is `1`: Circular left rotate `b10 + 1` times of the 13-bit combined register `[Link, AC]`.
* If `BSW` is `1` and neither `RAR` nor `RAL` is active: Swaps the high and low 6-bit halves of `AC`:
`self.ac = ((self.ac << 6) | (self.ac >> 6)) & 0xFFF`
---
#### Group 2 Operate Instructions (Bit 3 = 1, Bit 11 = 0)
Used for conditional skips, input register integration, and program termination.
```
3 4 5 6 7 8 9 10 11
+---+---+---+---+---+---+---+---+---+
| 1 |CLA|SMA|SZA|SNL|REV|OSR|HLT| 0 |
+---+---+---+---+---+---+---+---+---+
```
##### Bit Mapping & Function
* **Bit 4 (`CLA`)**: Clear Accumulator.
* **Bit 5 (`SMA` / `b5`)**: Skip on Minus Accumulator (`AC & 0x800` is set / `AC > 0x7FF`).
* **Bit 6 (`SZA` / `b6`)**: Skip on Zero Accumulator (`AC == 0`).
* **Bit 7 (`SNL` / `b7`)**: Skip on Non-Zero Link (`Link == 1`).
* **Bit 8 (`REV` / `b8`)**: Reverse skip condition sense (OR group if `0`, AND group if `1`).
* **Bit 9 (`OSR` / `b9`)**: OR AC with Switch Register.
* **Bit 10 (`HLT` / `b10`)**: Halt processor execution.
##### Condition Evaluation & Skip Logic
1. **Or Group (`REV = 0`)**:
Skips the next instruction if *any* of the enabled conditions are true:
$$\text{Skip} \iff (\text{SMA} \land \text{AC} < 0) \lor (\text{SZA} \land \text{AC} == 0) \lor (\text{SNL} \land \text{Link} \ne 0)$$
2. **And Group (`REV = 1`)**:
Skips the next instruction if *none* of the enabled conditions are true. This evaluates to:
$$\text{Skip} \iff (\neg\text{SMA} \lor \text{AC} \ge 0) \land (\neg\text{SZA} \lor \text{AC} \ne 0) \land (\neg\text{SNL} \lor \text{Link} == 0)$$
##### Execution Order
Group 2 micro-operations execute sequentially in the following order:
1. **Skip Condition**: Evaluate conditions and increment `PC` if met:
`if any_cond != b8: self.pc = (self.pc + 1) & 0xFFF`
2. **Clear**:
* If `CLA` is `1`: `self.ac = 0`
3. **OR Switch Register**:
* If `OSR` is `1`: `self.ac |= self.sr`
4. **Halt**:
* If `HLT` is `1`: `self.halted = True`
---
## 4. Emulation & Support Control Mechanisms
### 4.1. Loader format
Octal tape images are loaded with the `load_oct` function. The loader parses files containing lines of `addr: value` configurations, storing values directly in standard memory locations. In this simplified core, all loaded addresses are masked with `0xFFF` to restrict them to the 4K word field limit.
### 4.2. Program Interrupt Mechanism
When the program interrupt system is enabled (`self.ie == 1`) and a device interrupt request is active (either `self.kbf == 1` or `self.chf == 1`), the CPU executes the interrupt sequence at the start of an instruction step:
1. Interrupts are disabled: `self.ie = 0`
2. The current Program Counter (`self.pc`) is saved to memory location `0`.
3. The Program Counter is set to `1` (`self.pc = 1`), branching control to the interrupt handler.
+211
View File
@@ -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)
+3338
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
0200: 7200
0201: 7100
0202: 1220
0203: 3010
0204: 7000
0205: 1410
0206: 7450
0207: 7402
0210: 4212
0211: 5204
0213: 6046
0214: 6041
0215: 5214
0216: 7200
0217: 5612
0220: 0220
0221: 0110
0222: 0105
0223: 0114
0224: 0114
0225: 0117
0226: 0122
0227: 0114
0230: 0104
0231: 0041
+308
View File
@@ -0,0 +1,308 @@
0001: 5003
0003: 7001
0004: 5600
0020: 0000
0021: 7200
0022: 1026
0023: 6046
0024: 7200
0025: 5620
0026: 0056
0031: 7777
0032: 0001
0200: 7200
0201: 1031
0202: 1032
0203: 7001
0204: 4020
0220: 2211
0221: 5223
0222: 7001
0223: 7001
0224: 4020
0011: 7777
0240: 1410
0241: 1410
0242: 7001
0010: 0243
0243: 0001
0244: 0002
0245: 4020
0260: 4265
0261: 7001
0262: 5270
0265: 7200
0266: 7001
0267: 5665
0270: 4020
0271: 5300
0300: 7200
0301: 7041
0302: 7001
0303: 4020
0320: 7200
0321: 7001
0322: 7010
0323: 7001
0324: 4020
0340: 1211
0341: 7001
0011: 0342
0342: 0005
0343: 4020
0360: 5361
0361: 1261
0362: 7001
0361: 0005
0363: 4020
0400: 1211
0401: 7200
0402: 7200
0403: 1211
0404: 7001
0011: 0003
0401: 0005
0405: 4020
0420: 7200
0421: 7440
0422: 7440
0423: 7001
0424: 7001
0425: 4020
0440: 3211
0441: 5242
0442: 7001
0443: 4020
0460: 1211
0461: 7001
0461: 0470
0470: 0007
0462: 4020
0463: 5300
0500: 7200
0501: 2110
0502: 7402
0503: 7001
0504: 4020
0505: 5320
0110: 7777
0520: 7200
0521: 7001
0522: 3730
0523: 7200
0524: 1730
0525: 4020
0526: 5340
0530: 0531
0531: 0000
0540: 7200
0541: 7020
0542: 7100
0543: 7020
0544: 4020
0545: 5360
0560: 7200
0561: 7001
0562: 7004
0563: 7006
0564: 4020
0565: 5766
0566: 0600
0600: 7200
0601: 1610
0602: 7002
0603: 4020
0604: 5220
0610: 0007
0620: 7200
0621: 7020
0622: 7420
0623: 7402
0624: 7100
0625: 7430
0626: 7402
0627: 4020
0630: 5240
0640: 7200
0641: 7040
0642: 7500
0643: 7402
0644: 7200
0645: 7001
0646: 7510
0647: 7402
0650: 4020
0651: 5260
0660: 7200
0661: 7001
0662: 7450
0663: 7402
0664: 4020
0665: 5330
0730: 7200
0731: 7404
0732: 7440
0733: 5336
0734: 5342
0736: 1345
0737: 7440
0740: 7402
0742: 4020
0743: 5746
0745: 6544
0746: 1000
1000: 7200
1001: 6040
1002: 6041
1003: 7402
1004: 6042
1005: 6041
1006: 5210
1007: 7402
1010: 4020
1011: 5240
1040: 7200
1041: 7001
1042: 7001
1043: 7100
1044: 7012
1045: 7420
1046: 7402
1047: 7440
1050: 7402
1051: 4020
1052: 5260
1060: 7200
1061: 7540
1062: 7402
1063: 7040
1064: 7540
1065: 7402
1066: 7200
1067: 7001
1070: 7550
1071: 7402
1072: 7200
1073: 7550
1074: 5276
1075: 7402
1076: 4020
1077: 5700
1100: 1220
1220: 7300
1221: 1237
1222: 7006
1223: 7440
1224: 7402
1225: 7420
1226: 7402
1227: 4020
1230: 5240
1237: 2000
1240: 6042
1241: 7200
1242: 6001
1243: 7000
1244: 6040
1245: 6042
1246: 7001
1247: 1255
1250: 7440
1251: 7402
1252: 6002
1253: 4020
1254: 5260
1255: 7776
1260: 6042
1261: 7200
1262: 6001
1263: 7000
1264: 6002
1265: 6040
1266: 7440
1267: 7402
1270: 4020
1271: 5300
1300: 7300
1301: 7020
1302: 6040
1303: 6004
1304: 1320
1305: 7440
1306: 7402
1307: 4020
1310: 5330
1320: 3000
1330: 7200
1331: 6042
1332: 6003
1333: 5335
1334: 7402
1335: 6040
1336: 6003
1337: 7402
1340: 4020
1341: 5350
1350: 6042
1351: 7200
1352: 1376
1353: 6005
1354: 7000
1355: 6040
1356: 7420
1357: 7402
1360: 4020
1361: 5362
1362: 7300
1363: 7001
1364: 7020
1365: 6040
1366: 6007
1367: 7440
1368: 7402
1369: 7430
1370: 7402
1371: 6003
1372: 5374
1373: 7402
1374: 4020
1375: 7402
1376: 4000