• For issues you find with the Switch and Console releases, please follow this link and give as much detail as possible. This is the speediest way to get info to Pipeworks and get a hotfix in the works.
  • For issues you find with the Mobile 1.3 update, please follow this link and give as much detail as possible. This is the speediest way to get info to DR Studios to look at your issue. Also, some troubleshooting hints here.

PC [Showcase] TerraByte, the 16 Bit Programmable Computer

Programmatic

Steampunker
Introducing TerraByte, the first fully functioning programmable computer in Terraria:

TerraByte.png


This computer uses a custom-made architecture consisting of 16 different instructions and 16 registers. The computer can run programs consisting of up to 1024 instructions and has 32 kb of RAM

The first instruction of the program is at the top left corner of the Instructions memory bank. Use the teleporter on the left to get there. Use the switches to type in each instruction
Flip the first lever to reset the cycle counter and instruction pointer to 0
Flip the second lever to toggle the clock and resume or pause the program execution
Flip the third lever to advance a single instruction while the program is paused

There are 16 registers available to use:

0000 - constant 0 - Holds a constant value of 0. This register is read only
0001 - constant 1 - Holds a constant value of 1. This register is read only
0010 - accumulator - Holds the result of the last instruction executed, regardless of the output register. This means that outputting an instruction to a different register will also store the result in the accumulator, and attempting to write to a read only register is equivalent to writing to the accumulator. The accumulator will also be set to the second operand partway through a binary instruction, so care should be taken when using it as the first operand
0011-0111 - general purpose - General purpose registers that can be freely read from and written to
1000 - memory - Holds the currently referenced value in RAM. If a single byte is referenced, then it will be sign extended to the remaining 8 bits
1001 - memory pointer - Selects the address in RAM to access. The rightmost 14 bits of the value are used to select an address. The leftmost 2 bits are used to access either the entire 16 bit value, or one of its 2 bytes:

00 - 16 bit value
10 - right byte
11 - left Byte

1010 - output - Write to this register to output the value to the currently referenced line of the output screen. This register is write only. Reading from this register will leave the accumulator unaffected
1011 - output pointer - Selects which of the 16 lines of the output screen should be written to. This register stores a full 16 bit value, but only the rightmost 4 bits are used to select a line
1100 - input - Holds the current value of the user input. This register is read only
1101 - cycle counter - Counts the number of cycles that have passed since the program started. This register is read only
1110 - instruction pointer - Holds the address of the current instruction. Write to this register to jump to a different instruction in the program. The instruction pointer will not advance automatically if a jump occurs
1111 - immediate value - The next line of code will be used as an immediate value. The program will skip this line after the instruction is completed

An instruction consists of 4 groups of 4 bits each, with the following structure:

[Opcode] [Register 1 / Condition] [Register 2] [Output Register / Wait Type]

Opcode - Represents the instruction that should be executed. There are 16 available instructions:

0000 - no operation - Does nothing
0001 - and - Computes the bitwise AND of two inputs
0010 - or - Computes the bitwise OR of two inputs
0011 - xor - Computes the bitwise XOR of two inputs
0100 - add - Computes the sum of two inputs. Sets the carry flag if an overflow occurs
0101 - add with carry - Computes the sum of two inputs and adds 1 if the carry flag is set. Sets the carry flag if an overflow occurs
0110 - subtract - Computes the difference of two inputs. Sets the carry flag if an underflow occurs
0111 - subtract with borrow - Computes the difference of two inputs and subtracts 1 if the carry flag is set. Sets the carry flag if an underflow occurs
1000 - copy - Copies the value of one register to another
1001 - not - Computes the bitwise NOT of an input
1010 - left shift - Shifts the bits of an input 1 bit to the left. Sets the carry flag if the leftmost bit falls off
1011 - left shift with carry - Shifts the bits of an input 1 bit to the left and shifts the carry flag into the rightmost bit. Sets the carry flag if the leftmost bit falls off
1100 - right shift arithmetic - Shifts the bits of an input 1 bit to the right, preserving the sign bit. Sets the carry flag if the rightmost bit falls off
1101 - right shift with carry - Shifts the bits of an input 1 bit to the right and shifts the carry flag into the leftmost bit. Sets the carry flag if the rightmost bit falls off
1110 - right shift logical - Shifts the bits of an input 1 bit to the right. Sets the carry flag if the rightmost bit falls off
1111 - wait - Pauses the program until a certain condition is met. This condition depends of the last 4 bits of the instruction

0000 - wait for input - Waits until any bit of the user input is changed. If the input is changed before this instruction is reached, the program will continue immediately
0001 - wait for confirm - Waits until the player flips the confirm lever to the right of the input
0010 - wait for cycles - Waits for a number of cycles specified by the value of Register 2
Any other value will halt the program

Register 1 / Condition - For binary operations (opcodes 0001 - 0111), specifies the register to be used as the first operand. For unary operations (opcodes 1000 - 1111), specifies a condition that determines whether or not the instruction should be executed. This condition is based on a combination of flags that are set by certain instructions:

0000 - no condition - Execute the instruction unconditionally
0001 - zero - The zero flag, which is set to 1 when the current value of the accumulator is 0
0010 - sign - The sign flag, which is set to the leftmost bit of the accumulator, representing a negative value
0100 - carry - The carry flag, which is set to 0 or 1 based on the result of an ALU operation. The carry flag can also be used as an unsigned variant of the sign flag
1000 - invert - Inverts the condition, causing the instruction to execute only if the condition fails

Different combinations of these flags can be used to create various other conditions:

0011 - Less than or equal to zero
1010 - Greater than or equal to zero
1011 - Greater than zero

Register 2 - For binary operations, specifies the register to be used as the second operand. For unary operations, specifies the input for the operation

Output Register / Wait Type - For most instructions, specifies the register to write the result of the operation to. For the Wait instruction, specifies the condition to wait for

In addition to TerraByte itself, I have also written a simple assembler to assist with writing programs. This assembler takes a text file as input and outputs a .tbml file containing the corresponding machine code, which must still be plugged into TerraByte by hand

An instruction consists of a single line consisting of up 4 tokens, each separated by a space. All tokens are not case sensitive:

[Instruction Name] [Register 1 / Condition] [Register 2] [Output Register / Wait Type]

Instruction Name - The name of the instruction to execute. Refer to the Instruction Breakdown above to see the function of each instruction:

noop - No operation
and - AND
or - OR
xor - XOR
add - Add
addc - Add with carry
sub - Subtract
subb - Subtract with borrow
copy - Copy
not - NOT
sll - Left shift
slc - Left shift with carry
sra - Right shift arithmetic
src - Right shift with carry
srl - Right shift logical
wait - Wait

Register 1 / Condition - The register to use as the first operand (for binary operations), or a condition (for unary operations)

The names of the different registers are as follows:

0 - Constant 0
1 - Constant 1
acc - Accumulator
gp0-gp4 - General purpose
mem - Memory
mptr - Memory pointer
out - Output
optr - Output pointer
in - Input
cycl - Cycle counter
iptr - Instruction pointer

For an immediate value, enter any valid integer within the range of a signed 16 bit value (-32,768 - 32,767), or prefix the number with 0x or 0b to enter a hexadecimal or binary value, respectively. Attempting to enter two different immediate values in the same instruction will result in an error

A variety of different names can be used for each possible condition. All conditions are prefixed with a ? or !:

?n, !n - No condition
?e, ?z - Equal to zero
?l, !ge - Less than zero
?le, !g - Less than or equal to zero
?c, ?lu, !geu - Carry / Less than zero unsigned
?ce, ?cz, ?leu, !gu - Carry or equal to zero / Less than or equal to zero unsigned
?cl - Carry or less than zero
?cle - Carry or less than or equal to zero
!e, !z - Not equal to zero
?ge, !l- Greater than or equal to zero
?g !le- Greater than zero
!c, ?geu, !lu - Not carry / Greater than or equal to zero unsigned
!ce, !cz, ?gu, !leu - Not carry or equal to zero / Greater than zero unsigned
!cl - Not carry or less than zero
!cle - Not carry or less than or equal to zero
The condition can also be omitted if none is required

Register 2 - The register to use as the second operand (for binary operations), or the input register (for unary operations)

Output Register / Wait Type - The output register (for most instructions), or the wait type (for the Wait instruction). You can optionally omit this token to use Register 2 as the output register

The names of the available wait types are as follows:

in - Wait for input
conf - Wait for confirm
cycl - Wait for cycles
Even for the In and Conf types, Register 2 must still be specified

The assembler also has a few available pseudoinstructions:

def [name] [value] - Creates a new definition, replacing all instances of Name in the program with Value when the assembler is run. Useful for creating constants
data [name] [value 1] [value 2] ... - Creates a definition for the address of a free spot in RAM and adds a set of instructions that copies Value 1 to that address. Additional values can optionally be specified to create an array. Useful for initializing global variables. This pseudoinstruction should be placed before any other instructions, otherwise values in memory may be overwritten
labl [name] - Creates a definition for the address of the next instruction in the program. Useful in conjunction with the Jump pseudoinstruction
jump [condition] [address] - equivalent to "copy [condition] [address] iptr". Condition can be omitted as usual
read [condition] [address] - equivalent to "copy [condition] [address] mptr". Condition can be omitted as usual
inc [register] [amount] - equivalent to "add [register] [amount] [register]". Amount can be omitted to use a constant 1 instead
dec [register] [amount] - equivalent to "sub [register] [amount] [register]". Amount can be omitted to use a constant 1 instead

Inserting a # into a line will exclude the characters to its right from the program, allowing you to add comments

Finally, I have written an emulator that can be used to quickly test programs generated by the assembler before having to plug them into TerraByte

Input the full file path of a .tbml file. The program will begin executing automatically
When a Wait for Input or Wait for Confirm instruction is executed, the console will prompt you to either enter a valid integer value, or just press enter to leave the input unchanged

The TerraByte world file, the assembler, and the emulator are all attached below
I will upload a demonstration video and some sample programs once I'm certain that TerraByte, the assembler, and the emulator all function properly

Made correction about accumulator register functionality and updated the assembler to allow implicitly specifying the output register

Fixed issue with assembler where jumps could not be placed before their respective labels
 

Attachments

Last edited:

Programmatic

Steampunker
Also, since I'm still in the process of testing the computer to make sure everything works, feel free to suggest any simple programs that you want me to try to implement and I'll reply with working assembly and machine code.
 

Shin-Chan

Terrarian
This is really something. I was barely starting to build a CPU myself, but - luckly - I came here and saw this before I lost too much time.

I suppose you have made a couple tests, already. What I would test first are the basics: check every instruction individually (as much "individually" as possible, at least); sum 2 numbers; subtract 2 numbers; sum 2 numbers, but forcing overflow (btw, does it handle floating-point?); substract 2 numbers, but forcing overflow; force stack overflow. From that, supposing it all works accordingly, maybe set a bounty for people to find errors.

Also, congratulations. It's good to see a CS fella implementing in Terraria. I must say, however, that nowadays, with logic gates as a feature in-game, it is far easier: back in the day (v1.1) a friend of mine, xXAndreXx (member of the, now deceased, brazilian Terraria forums) implemented logic gates using birds and doors. I can't deny it was wonderful to see (though slow af).
 

Haku

Skeletron Prime
Wow, that's really impressive! 👀
I was just wondering, how does it hold up in terms of lag? I made a quad-core CPU but it ended up being too powerful to be effective on high clock speeds ingame with frame skip on.
 

Programmatic

Steampunker
Wow, that's really impressive! 👀
I was just wondering, how does it hold up in terms of lag? I made a quad-core CPU but it ended up being too powerful to be effective on high clock speeds ingame with frame skip on.
On the laptop I use, Terraria slows by about half while TerraByte is running, so I get about 30 instructions per second. Good enough for mathematical calculations and turn-based games, but definitely not fast enough for making real-time games and such. Trying to make it multi-core definitely wouldn't help since it would probably just drop the framerate even further and cancel out.
 

Shin-Chan

Terrarian
On the laptop I use, Terraria slows by about half while TerraByte is running, so I get about 30 instructions per second. Good enough for mathematical calculations and turn-based games, but definitely not fast enough for making real-time games and such. Trying to make it multi-core definitely wouldn't help since it would probably just drop the framerate even further and cancel out.
Surely would drop the framerate. A computer such as this, running 100% in-game, can't be much faster than this. What programs have you already tested?
 
Top Bottom