This is a programmable computer constructed entirely of vanilla combinator logic, with some mods for peripherals (Notably, Pushbuttons and Nixie Tubes everywhere for IO devices, and Color Coding for clarity). The machine has a set of registers which can be used directly in operations, and a shared memory bus for RAM, ROM, and memory-mapped devices. These registers and memories all store circuit-frames, sets of signal/value pairs. There is no system-wide clock, the machine runs by passing a pulse around in cycles - each instruction returns the pulse to the Instruction Fetch block to be sent out again to the next instruction. The machine is programmed in not-quite-lua, or nql, which has a syntax mostly based on Lua, with a few 'borrows' from other languages (mostly C) where required.
Major signals are carried along a 12-pole Main Bus, and labeled with floor concrete. In general pairs will be kept together for single-color poles, but when a pole must carry mixed pairs, the left side is colored for the green wire, and the right side for red wire.
The bus is constructed from the [Bus H](blueprints/Bus H.txt) and [Bus V](blueprints/Bus V.txt) blueprints. The fire hazard concrete should be bottom-right.
Pole Color | Green Wire | Red Wire |
---|---|---|
Red | rKeyboard | |
Orange | Op Pulse | rStatus |
Yellow | Op | To PC |
Green | R1 | Scalars |
Cyan | R2 | Vector Result |
Blue | Scalar Result (signal-grey ) |
rIndex |
Purple | Memory Read Request | Memory Read Response |
Magenta | Memory Write | |
White | NixieTerm | NixieTerm |
Hazard | IO Wire | IO Wire |
FireHazard | IO Wire Register | IO Wire Register |
- Scalars
- signal-grey: R1.s1
- signal-white: R2.s2
- signal-I: rIndex.index
- To PC
- signal-blue: next
- signal-green: rjmp to signal-grey
- signal-red: jump to signal-grey
- signal-pink: execute this frame as an instruction
- signal-yellow: Hold. Prevents execution of instructions while set.
- signal-cyan: Interrupt. Next update (except exec) will save return site in rStatus and jump to interrupt vector.
- rStatus
- signal-blue: PC
- signal-green: Interrupt Return site
- signal-cyan: Interrupt Request
- signal-I: Interrupt Enable
The Scalar Pick & Return mechanism allows operating on individual signals from registers. There are two Pick channels, s1 and s2, and one Return channel sd, selected by the corresponding signals in the current opcode, and operating on the corresponding register selection. Because the signals available will vary depending on what mods are installed, this module is generated by a script, ScalarGen. ScalarGen is executed by pasting it into Foreman's import window as if it were a blueprint string, and will add a blueprint named ScalarGen to your list. It will also produce a file scalarmap.lua
in your script-output directory listing the numeric mappings for the assembly it has generated, which must be used when compiling programs for your computer. The local bus generated by ScalarGen is, from left to right, Blue,Yellow,Cyan,Green.
Registers store an entire circuit network frame, except signal-black
. signal-black
is used internally for various scalar and flag values throughout the machine, and cannot be stored in registers/memory, or expressed correctly in most mechanisms. General Purpose registers start at 1, IO/Special registers start at 101.
ID | Name | Callee Saved | Purpose |
---|---|---|---|
<0 | No | Reserved for future use | |
0 | rNull |
No | No Register selected Returns 0 on every signal |
1 | rGlobalInts |
Yes | Global Integers |
2 | rLocalInts |
Yes | Local Integers |
3 | rScatchInts |
No | Int Temporaries |
4-5 | rFetch[1-2] |
No | Fetch space for non-register inputs |
6-9 | rArg[1-4] |
No | Function Arguments. |
10 | rIntArgs |
No | Int args Return site on signal-0 |
11-15 | rTemp[1-5] |
No | Temporaries |
16-20 | rSTemp[1-5] |
Yes | Saved Temporaries |
21-25 | r21 -r25 |
Yes | Reserved for OS/ISR use |
26-100 | Reserved for future use | ||
101 | rIndex |
Yes* | Indexing regiser Supports auto-indexing memory operations. |
102 | rRed |
No | IO Wire Red data since list transmitted |
103 | rGreen |
No | IO Wire Green data since last transmitted |
104 | rStatus |
No | CPU Status register |
105 | rOp |
No | Current Op data |
106 | rNixie |
No | NixieTerm |
107 | rKeyboard |
No | Keyboard interface Reads a whole string |
108 | rResearch |
No | Research Combinator Reads current research information |
109+ | r109 + |
No | IO Expansion ports Additional devices may be connected to these registers |
Calling Convention:
- Caller:
- Save temporaries if used locally
- Push rTemp1-5
- Push rArgs1-5
- Arrange Function args
- Tables in rArg[1-4], then on stack
- Ints in rIntArgs (own from stack if required)
- Longcall: update pointers
- Push rIndex
- VReplace rIndex {newframe}
- Jump to function / callsite to rIntArgs.signal-0
- Save temporaries if used locally
- Callee:
- Save registers before local use
- Push rLocalInts
- Push rSTemp1-5
- [Function Body]
- Arrange returns
- Ints in rScratchInts
- Tables in rFetch1-2
- Restore registers
- Pop rSTemp5-1
- Pop rLocalInts
- Jump to rIntArgs.signal-0
- Save registers before local use
- Caller (on return):
- LongCall: restore pointers
- Pop rIndex
- Assign Return values
- but regs not restored?
- tables - skip restore and copy these?
- copy to rScratchInts/rFetch1-2
- any avialable rTemp too
- but regs not restored?
- Remove stacked args if any
- Pop rNull or just callstack+=n
- Restore Temporaries
- Pop rArgs5-1
- Pop rTemp5-1
- LongCall: restore pointers
![RAM](screenshots/RAM 300 Cells.png)
The memory is a large array of identical storage cells. To write a cell, send the address on signal-black
combined with the data on the global Memory Write wire. To read a cell, send the address on signal-black
on the Memory Read Request wire, and receive a response the following tick on Memory Read Reply containing data+address.
![Instruction Fetch Screenshot](screenshots/Instruction Fetch W Interrupt.png)
This block recieves the PC Update frames produced by other blocks and acts on them. It performs the following actions:
- Update PC
- Fetch the instruction specified, and post it to the global Op wire
- Wait for signals to stabilize
- Scalar
signal-I
from rIndex, if selected - R1 and R2 selected for operation
- R1.S1 and R2.S2 selected for operation
- Send a pulse to the global Op Pulse wire to trigger an instruction block
In the case of an Exec command, instead of fetching a frame from memory, the command itself will be used.
When interrupts are enabled (using a jmp instruction with signal-cyan
set), this block also handles jumping to the interrupt vector when an interrupt is triggered, signalled by raising signal-cyan
on the To PC wire. When an interrupt is caught, the next value of PC is calculated, stored in rStatus.intreturn, and then execution jumps to rIndex.intvect to handle the interrupt.
NixieTerm is a multi-line Alpha Nixie display. It can be accessed as an array of bit-packed strings in memory starting at 900, or the lowermost row can be accessed as a rNixie
, and shifted upward.
Any signals written in register mode will be added to the sum for the lowermost row. Clearing the register will shift all rows upward and clear the lower row.
Two numeric displays are provided beside each row, showing signal-grey
(top) and signal-white
(bottom) of the corresponding cell.
NixieTerm supports all the characters supported by the Alpha Nixie:
- A-Z0-9 as their correspnding signals
- The symbol signals provided by Nixie Tubes
Color signals may be bit-packed along with characters.
The following signals are used to select registers and signals:
Signal | Purpose |
---|---|
signal-0 | Op |
signal-A | Accumulate |
signal-I | Index/Stack Select |
signal-R | R1 |
signal-S | S1 |
signal-T | R2 |
signal-U | S2 |
signal-V | Rd |
signal-W | Sd |
signal-grey | Imm1 |
signal-white | Imm2 |
If Rd is set, the selected register will be cleared as Op Pulse is triggered unless Accumulate is also set (>0), even if the current operation does not actually assign to it. The whole register will be cleared, even in scalar operations.
For operations which support memory indexing, the base pointers are selected as follows:
I | Signal | Usage |
---|---|---|
1 | signal-red |
Call Stack |
2 | signal-green |
Current Program Constant/Code Frame |
3 | signal-blue |
Current Progarm Data Frame |
4 | signal-yellow |
local usage |
Individual instructions may also define additional signals. Any unused signals should be left unset.
Potential future expansion - more indexes, maybe PC,
- Reserved/Unsupported - <0
- Null - 0
- RAM - 1-600
- slices allocated to programs as needed
- <gap>
- NixieTerm 900+nlines
- <gap>
- ROMS 1000-?
Programs:
- Program symbol [1]
- Program name, size of other sections
- Symbol table [n]
- Symbol name/type/address/size
- (?) Extern links
- Constants [n]
- Code [n]
Any undefined opcode will halt the machine, but Op=0 is specifically reserved for doing so.
mk5alu:
ALU in several blocks
Comparators
Compare modes (??) selected by:
- Signal-1 : =
- Signal-2 : <
- Signal-3 : >
- R1.Each ?? R2.S2 ::> 1 -> Rd.Sd
- R1.Each ?? R2.S2 ::> R1:1 => Rd
- R1.Each ?? R2.S2 ::> R1 => Rd
- R1.Every ?? R2.S2 ::> 1 -> Rd.Sd
- R1.Every ?? R2.S2 ::> R1:1 => Rd
- R1.Every ?? R2.S2 ::> R1 => Rd
- R1.Any ?? R2.S2 ::> 1 -> Rd.Sd
- R1.Any ?? R2.S2 ::> R1:1 => Rd
- R1.Any ?? R2.S2 ::> R1 => Rd
- R1.S1 ?? R2.S2 ::> 1 -> Rd.Sd
- R1.S1 ?? R2.S2 ::> R1:1 => Rd
- R1.S1 ?? R2.S2 ::> R1 => Rd
Each [arith] Scalar => Vector
- R1.Each - R2.S2 => Rd
- R1.Each + R2.S2 => Rd
- R1.Each / R2.S2 => Rd
- R1.Each * R2.S2 => Rd
- R1.Each % R2.S2 => Rd
- R1.Each & R2.S2 => Rd
- R1.Each | R2.S2 => Rd
- R1.Each xor R2.S2 => Rd
- R1.Each pow R2.S2 => Rd
- R1.Each << R2.S2 => Rd
- R1.Each >> R2.S2 => Rd
Scalar [arith] Scalar -> Scalar
- R1.S1 - R2.S2 -> Rd.Sd
- R1.S1 + R2.S2 -> Rd.Sd
- R1.S1 / R2.S2 -> Rd.Sd
- R1.S1 * R2.S2 -> Rd.Sd
- R1.S1 % R2.S2 -> Rd.Sd
- R1.S1 & R2.S2 -> Rd.Sd
- R1.S1 | R2.S2 -> Rd.Sd
- R1.S1 xor R2.S2 -> Rd.Sd
- R1.S1 pow R2.S2 -> Rd.Sd
- R1.S1 << R2.S2 -> Rd.Sd
- R1.S1 >> R2.S2 -> Rd.Sd
This block is generated by MaskGen. The local bus generated by MaskGen is, from left to right, Orange,Cyan,Green.
The Pairwise ALU block is generated by a script to allow working with all signals in the current game. This block performs pairwise Multiply/Divide operations on the operands and returns the result to Vector Result. This block is optional and if not installed Ops 61 and 62 will halt the machine.
- 61: R1.each * R2.each => Rd
- x y = ((x + y)^2 - (x - y)^2)/4
- x y = ((x + y)^2)/4 + ((x - y)^2)/-4
- 62: R1.each / R2.each => Rd
- 63: Pick
- R1.[R2.s2] -> Rd.sd
- exec{0=58,R=R,S=[R2.s2],V=V,W=W,A=A}
- R1.[R2.s2] -> Rd.sd
- 64: Write
- R1.s1 -> Rd.[R2.s2]
- exec{0=58,R=R,S=S,V=V,W=[R2.s2],A=A}
- R1.s1 -> Rd.[R2.s2]
R1 >> R2.s2 => Rd
R1 << R2.s2 => Rd
- positive values in R2 replace the values in R1
- negative values in R2 clear the signal
max(log2(R1)) -> Rd.Sd log2() treats input values as unsigned, so log2(negative)=32
Jump to R1.s1 if signal-green
=0 or PC+R1.s1 if signal-green
=1. Return PC+1 to Rd.Sd. If signal-cyan
is set, enable(1)/disable(-1) interrupts after this jump.
Returns PC+1 to Rd.sd. Compares R1.s1 to R2.s2, and makes the following jumps:
=
PC+rOp.1<
PC+rOp.2>
PC+rOp.3
Execute the contents of R1 as an instruction, at the current PC value.
Write a packet to a two-wire network, and clear the receive registers for a response. To leave either wire untouched, select rNull
for it. rRed
and rGreen
are cleared on the same frame the selected signals are transmitted. Write rNull
to both wires to clear the receive buffer without transmitting anything.
- R1=>Red Wire
- R2=>Green Wire
- 0=>rRed,rGreen
-
81: Write
- Write the contents of R2 to the memory location or memory-mapped device selected by R1.s1. If
signal-I
is set, the memory access is offset from the selected pointer. If Rd is set, the value previously in that memory address will be returned.- [R1.s1+I] -> Rd
- R2 -> [R1.s1+I]
- Write the contents of R2 to the memory location or memory-mapped device selected by R1.s1. If
-
82: Read
- Read the memory location or memory-mapped device selected by R1.s1 into Rd. If
signal-I
is set, the memery access is offset from the selected pointer.- [R1.s1+I] -> Rd
- Read the memory location or memory-mapped device selected by R1.s1 into Rd. If
This block implements the frame pointer value on Scalar signal-I
for use by other instructions.
- 83: Push
- Store a frame to one of the stacks in rIndex, as selected by
signal-I
.- R2 -> [rIndex.stack-1]
- rIndex.stack--
- Store a frame to one of the stacks in rIndex, as selected by
- 84: Pop
- Retrieve a frame to one of the stacks in rIndex, as selected by
signal-I
.- [rIndex.stack] -> Rd
- rIndex.stack++
- As a special case, the stack pointer will not be incremented if Rd is rIndex.
- Retrieve a frame to one of the stacks in rIndex, as selected by
Store a frame to an array in one of the index pointers.
- R2 -> [rIndex.stack++]
This instruction uses my Player Combinator mod to read player information.
If R1.S1 == 0, a playercounts
frame is returned containing the number players total, and online.
If R1.S1 >0, then it returns a playerinfo
frame, with the players name, and their online/admin status.
Issue an order in R1 to a ConMan, with data in R2. If ConMan returns any data on CC2, it will be sent to Rd
Scan the position or area selected by the command in R1, and send result to Rd.