I'm currently in the process of building what I'm pretty sure is the most advanced and fastest general-purpose redstone computer out there, using MrTJP's ProjectRed and McJty's RFTools.
Features:
32 bit words and arithmetic
A stack machine architecture, with a 16-depth operand stack which most instructions operate directly on
8 general purpose registers, all holding 32 bits each
A maximum of 65536 bytes (octets) of RAM, same amount as the Commodore 64. Should be possible to bank
more in. Only 1024 bytes of RAM are installed in these screenshots, to reduce FPS lag.
Dedicated instructions for constructing an in-memory stack, which works the same way modern computer stacks work
An 18-depth return stack for holding return addresses after calling into a function
There is no clock (redstone's sequential nature makes a global clock signal somewhat unnecessary) but the average instructions-per-second is around 2. This ends up being a massive leap up from the processing power of any vanilla redstone computers I've seen.
A compiler, written in Rust, that takes a pseudoassembly (essentially just assembly with functions, named locals, and automatic memory layout) and turns it into machine code which can then be flashed to memory using OpenComputers.
I've laid out some overarching design goals, to make this a unique build:
Ease of programming - It should be relatively obvious how to write programs for this build. It should provide high level instructions to do interesting things.
Dead-simple compilation target - Modern programming languages should be able to compile to it and be comfortable to work with. For this purpose, I've designed the stack architecture to be somewhat like WebAssembly.
Aesthetic design - It kinda looks like a microprocessor under a microscope, but in 3D. But sometimes some compactness is sacrificed for satisfying spaghetti.
Interoperable with peripherals and other builds. This should be able to do stupid graphical demos on a piston GPU, as well as be a practical crafting/storage monitor. It should be possible to hook up Railcraft to it and get it to control an extremely overengineered Corned Beef Breakfast factory.
How it works
I'll document more over time, but here is some of the basics.
The core of the computer is the control unit. It holds the program counter - a pointer to the current instruction that the computer is executing. When the run-instruction signal (lightblue) is provided, a gate immediately passes a bus signal, that can come from one of two ICs, to the memory address bus.
If we just want to run the next instruction, there is a bus coming from a 14-bit incrementer circuit, that is always outputting the program counter plus one.
If we want the program to branch (jump), it's a little more complicated. The instruction deciding to perform a jump writes a destination address to an RS latch attached directly to the jump bus (cyan bundle), where it is then stored temporarily. It then sends a signal (cyan) that shuts off the output of the incrementer circuit, so that the jump destination address is the only thing being output to the bus. One tick after the cyan signal, the lightblue signal turns on, which lets this address get sent to the memory address decoders.
A tick after the lightblue signal, we reset everything—making sure the incrementer's signal is passing through again, and making sure the jump bus RS latch is holding no value.
How do we actually decode the instructions? Very simple. We know that 3 ticks after lightblue goes high, the memory unit should have responded with the 32 bit instruction. The first 8 bits can then be decoded by a simple circuit that—when provided a signal—checks if the most significant 8 bits of two numbers are equal, and outputs a pulse if so. In this case, the other number in the comparison is provided by a Bus Input Panel component so we can easily configure each decoder separately in the future.
At the same time that the decoders are activated, the least significant 24 bits of the instruction are latched in for later use.
Once an instruction finishes executing, it pulses lightblue (or cyan) again, restarting the cycle
The instruction set
Some caveats for programming the machine:
In the machine encoding, addresses always address entire words, and are 14 bits long. When pointers are actually used by code (dyn_*, stack_ptr), pointers are multiplied by 4 so they address bytes, and are 16 bits long
The in-memory stack is statically accessed backwards. A stack_set instruction that looks like 0x0a010001 accesses a lower address than 0x0a010000. This is an important consideration when using stack_ptr to treat parts of the stack frame as buffers/arrays - the beginning of your buffer/array has a higher relative address than the end of your buffer/array.
The length of consts in static_const, stack_const, and const are limited to 10 bits, 16 bits, and 24 bits respectively. To load-immediate larger numbers, you'll of course have to use two instructions.
The no-tee bit determines whether the value is consumed by the instruction. In dyn_* instructions, the address is always consumed
0x00 - noop - do absolutely nothing
0x01 - const [24 num] - load a 24-bit constant onto the operand stack
0x02 - rand - generate a random 32 bit number (comes from builtin Java RNG via Bus Randomizer)
0x03 - static_const [14 addr] [10 num] - write a 10 bit constant to an address
0x04 - static_get (10 blank) [14 addr] - read from specified memory position
0x05 - static_set (7 blank) {no-tee} (2 blank) [14 addr] - write to specified memory position
0x06 - stack_start (10 blank) [14 addr] - sets the initial stack pointer
0x07 - stack_end (10 blank) [14 addr] - sets the stack position that will cause the computer to crash if exceeded
0x08 - stack_const [8 reloffset] [16 value] - put a 16 bit constant on stackposition - reloffset
0x09 - stack_get (16 blank) [8 reloffset] - reads from position stackposition - reloffset
0x0a - stack_set (7 blank) {no-tee} (8 blank) [8 reloffset] - writes positon stackposition - reloffset
0x0b - stack_ptr (16 blank) (8 reloffset) - grabs stackposition - reloffset, multiplies it by 4, and puts on operand stack
<!-- these are not imlemented yet
0x0c - dyn_get - read a full word from 16 bit address specified by the top of the operand stack
0x0d - dyn_set (7 blank) {no-tee} - write a full word (from n-1th position on operand stack) to specified 16 bit address
(WARNING: These two instructions will ignore the last 2 bits of the address. Unaligned access is not supported)
0x0e - dyn_get_16 - read a half word from 16 bit address specified by the top of the operand stack
0x0f - dyn_set_16 (7 blank) {no-tee} - write a half word (from n-1th position on operand stack) to specified 16 bit address
(WARNING: These two instructions will ignore the last 1 bit of the address. Unaligned access is not supported)
(WARNING: Writing non-full words is slow, since the computer has to read first)
0x10 - dyn_get_8 - read a byte from 16 bit address specified by the top of the operand stack
0x11 - dyn_set_8 (7 blank) {no-tee} - write a byte (from n-1th position on operand stack) to specified 16 bit address
(WARNING: Writing non-full words is slow, since the computer has to read first)
-->
I may also add some bulk memory operations.
0x20 - jump (10 blank) [14 addr]
0x21 - jump_if_zero (10 blank) [14 addr]
0x22 - jump_if_not_zero (10 blank) [14 addr]
0x23 - return (16 blank) [8 localscount]
0x24 - call [8 locals count] [4 args count] [12 addr]
(WARNING: Jump position for `call` can only be in the first 16384 bytes. Make sure that
the beginning of all your functions are within this space)
0xb0 - register_set [8 register] (15 blank) {no-tee}
0xb1 - register_get [8 register] (16 blank)
0x30 - reset_stack - put the operand stack back into 0th position (useful for cleaning up things after a jump from multiple possible locations)
0x31 - discard - pop the operand stack without reading
0x32 - recover - push the operand stack without writing
0x33 - copy - duplicate the value at the top of the operand stack
0x34 - swap2 - swap the top two values of the operand stack
0x35 - increment - increment number at top of the stack by 1
0x36 - decrement - decrement number at top of stack
0x37 - negate - flip the sign of the number
0x38 - bool_identity - set to 0x00000001 if at least one bit is 1; set to 0x00000000 if all bits are 0
0x39 - bool_not - set to 0x00000001 if all bits are 0; set to 0x00000000 if at least one bit is 1
0x3a - not - invert all bits
0x3b - shift_right1 - divide integer by 2
0x3c - shift_right1_logical - shift all bits to the right
0x3d - shift_left1 - multiply integer by 2
0x3e - bit_population_count - count bits that are 1
0x3f - bit_decode - convert 5 bit number to its corresponding bit mask
<!-- these are not implemented yet
0x60 - is_negative
0x61 - is_negative_or_zero
-->
0x50 - and
0x51 - or
0x52 - xor
0x53 - add
0x54 - subtract
<!-- these are not implemented yet
0x52 - equal
0x53 - not_equal
shift_right_logical
shift_right
shift_left
<!-- multiply - multiply two numbers. Might just put this in OpenComputers, since the circuitry I've made for it is extremely laggy -->
<!-- divmod {signed} - divide and mod. Might just put this in OpenComputers, since I haven't implemented divide yet and it'll probably be laggy -->
<!-- multiply_slow - maybe have a separate multiply instruction for low right arguments -->
<!-- divmod_slow {signed} - maybe have a separate multiply instruction for low left arguments -->
less_than_or_equal {signed}
greater_than_or_equal {signed}
less_than {signed}
greater_than {signed}
-->
0xfa - suspend [24 suspend signal] - wait for input to be placed on operand stack
0xff - halt
A simple program - calculating the 18th fibonacci number in 108 seconds
Shown in the first GIF is a very simple computation. The fibonacci sequence is a sequence of numbers such that each number is the sum of the last two numbers. The 18th fibonacci number is 2584 - let's see if we can compute it using a very simple program
stack_size 64 // currently this is just ignored. Still need to incorporate the in-memory stack into memory layout part of compilation
const 0x10
call fib
static_set 0x90
fn fib(n)
local_const prev 1
local_const cur 1
:loop
local_get cur
local_get prev
add
local_get cur
local_set prev
local_set cur
local_get n
decrement
local_set n tee
jump_if_not_zero :loop
local_get cur
return
After running this through my compiler, you get this machine code (with source maps):
0x000: 00000000
0x001: 06000050
0x002: 07000060
0x003: 01000010 - line 2: const 0x10
0x004: 24031007 - line 3: call fib
0x005: 05010090 - line 4: static_set 0x90
0x006: ff000000
0x007: 08010001 - line 7: local_const prev 1
0x008: 08020001 - line 8: local_const cur 1
0x009: 09000002 - line 12: local_get cur
0x00a: 09000001 - line 13: local_get prev
0x00b: 53000000 - line 15: add
0x00c: 09000002 - line 17: local_get cur
0x00d: 0a010001 - line 18: local_set prev
0x00e: 0a010002 - line 20: local_set cur
0x00f: 09000000 - line 22: local_get n
0x010: 36000000 - line 23: decrement
0x011: 0a000000 - line 24: local_set n tee
0x012: 22000009 - line 26: jump_if_not_zero :loop
0x013: 09000002 - line 28: local_get cur
0x014: 23000003 - line 29: return
0x015: ff000000
After running this, the memory position 0x90 should hold fib(18), which is 2584
I need your help
Some things I'm looking for:
Name ideas. What the hell should I call this thing?
Example programs and test cases. I'll be writing an emulator soon, and will eventually provide a world download. I want to see what you guys can do with this instruction set.
A very good indicator lamp mod that doesn't cause lag. Ideally just a mod with a bunch of solid non-TileEntity blocks that toggle their texture when a redstone signal is provided. If someone could find or make a mod like this, I would be very happy. I'm not in the mood to dive head first into Java game modding myself.
This project is really interesting. I hope you are still working on it. To answer you about a lag-less light indicator, I do not know of a mod that does that, but I'm willing to help out/make a mod for you. Just shoot me a message and we can talk about it if you want.
GIF of it calculating fib(18) to completion: https://i.imgur.com/cTO5ptV.gifv
I'm currently in the process of building what I'm pretty sure is the most advanced and fastest general-purpose redstone computer out there, using MrTJP's ProjectRed and McJty's RFTools.
Features:
I've laid out some overarching design goals, to make this a unique build:
How it works
I'll document more over time, but here is some of the basics.
The core of the computer is the control unit. It holds the program counter - a pointer to the current instruction that the computer is executing. When the run-instruction signal (lightblue) is provided, a gate immediately passes a bus signal, that can come from one of two ICs, to the memory address bus.
If we just want to run the next instruction, there is a bus coming from a 14-bit incrementer circuit, that is always outputting the program counter plus one.
If we want the program to branch (jump), it's a little more complicated. The instruction deciding to perform a jump writes a destination address to an RS latch attached directly to the jump bus (cyan bundle), where it is then stored temporarily. It then sends a signal (cyan) that shuts off the output of the incrementer circuit, so that the jump destination address is the only thing being output to the bus. One tick after the cyan signal, the lightblue signal turns on, which lets this address get sent to the memory address decoders.
A tick after the lightblue signal, we reset everything—making sure the incrementer's signal is passing through again, and making sure the jump bus RS latch is holding no value.
How do we actually decode the instructions? Very simple. We know that 3 ticks after lightblue goes high, the memory unit should have responded with the 32 bit instruction. The first 8 bits can then be decoded by a simple circuit that—when provided a signal—checks if the most significant 8 bits of two numbers are equal, and outputs a pulse if so. In this case, the other number in the comparison is provided by a Bus Input Panel component so we can easily configure each decoder separately in the future.
At the same time that the decoders are activated, the least significant 24 bits of the instruction are latched in for later use.
Once an instruction finishes executing, it pulses lightblue (or cyan) again, restarting the cycle
The instruction set
Some caveats for programming the machine:
A simple program - calculating the 18th fibonacci number in 108 seconds
Shown in the first GIF is a very simple computation. The fibonacci sequence is a sequence of numbers such that each number is the sum of the last two numbers. The 18th fibonacci number is 2584 - let's see if we can compute it using a very simple program
After running this through my compiler, you get this machine code (with source maps):
After running this, the memory position 0x90 should hold fib(18), which is 2584
I need your help
Some things I'm looking for:
Looks interesting. Can you include some notes for us simple folk that have literally no idea what you are doing here?
im interested in what you’re doing but I also literally have no idea what’s going on.
This project is really interesting. I hope you are still working on it. To answer you about a lag-less light indicator, I do not know of a mod that does that, but I'm willing to help out/make a mod for you. Just shoot me a message and we can talk about it if you want.