This specification describes the architecture of the Bedrock computer system.
Overview
The Bedrock system contains four components: program memory, two stacks, the device bus, and the processor.
A program is loaded into program memory and executed by the processor, with the stacks storing temporary state and the device bus providing access to external devices.
Definitions
Byte
A byte is an 8-bit value, and is the smallest unit of data that can be operated on by Bedrock.
Double
A double (or ‘double-width value’) is a 16-bit value, and is represented as a pair of bytes in big-endian order.
Undefined behaviour
Undefined behaviour is behaviour that is triggered when performing an unsupported action. This can result in any behaviour at all, including corruption of the system state. All programs must avoid triggering undefined behaviour. All systems should try to handle undefined behaviour safely and predictably.
Program memory
The program memory is a 65536 byte block of writeable memory that holds the current program. The initial value of each byte of program memory is zero.
Systems with fewer than 65536 bytes of program memory are partially supported. The behaviour when an unimplemented memory address is read from or written to is undefined.
Reading from memory
Reading a byte from program memory will return the byte at the given address.
Reading a double from program memory will read the high byte of the double from the given address and the low byte from the following address. The behaviour when a double is read from the highest address is undefined.
Writing to memory
Writing a byte to program memory will write that byte to the given address.
Writing a double to program memory will write the high byte of the double to the given address and the low byte to the following address. The behaviour when a double is written to the highest address is undefined.
Loading a program
Program decode is written into program memory starting from address zero. The program is truncated if it exceeds the available memory.
Stacks
A stack is a 256 byte block of writeable memory with an associated 8-bit stack pointer. The initial value of each byte of stack memory is unspecified, and the initial value of the stack pointer is zero.
Bedrock uses two stacks, called the working stack and the return stack.
Systems with fewer than 256 bytes of stack memory per stack are partially supported. The behaviour when an unimplemented memory address is read from or written to is undefined.
Pushing to a stack
Pushing a byte to a stack will write the byte to the address referenced by the stack pointer, and then will increment the stack pointer by 1. The behaviour when the stack pointer overflows is undefined.
Pushing a double to a stack will push the high byte of the double followed by the low byte.
Popping from a stack
Popping a byte from a stack will decrement the stack pointer by 1, and then will read the byte from the address referenced by the stack pointer. The behaviour when the stack pointer underflows is undefined.
Popping a double from a stack will pop the low byte of the double followed by the high byte.
Device bus
The device bus is a 256 port communications bus for interfacing with sixteen devices. The ports are grouped into blocks of sixteen, one block per device.
Each port is addressed by an 8-bit port number. The upper four bits address a device, and the lower four bits address a port within that device.
Reading from a device
Reading a byte from the device bus will send a read request to the given port, and then the device connected to that port will process the read request and return a byte. There is no limit to the length of time a device can take while processing a read request. If there is no device connected to the given port, the request will return immediately with a value of zero.
Reading a double from the device bus will read the high byte of the double from the given port and then the low byte from the following port. The behaviour when a double is read from the highest port is undefined.
Writing to a device
Writing a byte to the device bus will send a write request containing that byte to the given port, and then the device connected to that port will process the write request and return. There is no limit to the length of time a device can take while processing a write request. If there is no device connected to the given port, the request will return immediately.
Writing a double to the device bus will write the high byte of the double to the given port and then the low byte to the following port. The behaviour when a double is written to the highest port number is undefined.
Processor
The processor executes instructions from program memory.
Program counter
The processor contains a 16-bit program counter. The initial value of the program counter is zero. The behaviour when the program counter overflows is undefined.
Instruction cycle
To execute an instruction, an instruction byte is first read from the program memory address referenced by the program counter, then the program counter is incremented by 1, and then the instruction is executed.
Instructions
Instructions are stored as individual bytes in program memory. The lower five bits of an instruction byte determine the operation to be performed, and the upper three bits are mode flags that modify how the operation will be performed.
Flag | Name |
---|---|
0x80 |
Return mode |
0x40 |
Immediate mode |
0x20 |
Wide mode |
Return mode
When bit 0x80
of the instruction byte is set, the operation will use the return stack in place of the working stack, and the working stack in place of the return stack.
Immediate mode
When bit 0x40
of the instruction byte is set, the first time a value would be popped from either stack that value will instead be read from program memory, from the address referenced by the program counter. The program counter will be incremented by 1 for each byte read.
Wide mode
When bit 0x20
of the instruction byte is set, each value listed as a ‘value’ in the operation description will be a double, otherwise each value will be a byte.
Operations
There are 32 operations grouped into four categories: stack operations, control operations, numeric operations, and bitwise operations.
Oper. | Name | Description |
---|---|---|
0x00 |
HLT | Halt the processor. |
0x01 |
PSH | Move a value between stacks. |
0x02 |
POP | Remove a value from a stack. |
0x03 |
CPY | Copy a value between stacks. |
0x04 |
DUP | Duplicate a value. |
0x05 |
OVR | Duplicate the next value down. |
0x06 |
SWP | Swap the top two values. |
0x07 |
ROT | Rotate the top three values. |
0x08 |
JMP | Jump to an address. |
0x09 |
JMS | Jump to an address, saving the current address. |
0x0A |
JCN | Conditionally jump. |
0x0B |
JCS | Conditionally jump, saving the current address. |
0x0C |
LDA | Load a value from a memory address. |
0x0D |
STA | Store a value to a memory address. |
0x0E |
LDD | Load a value from a device port. |
0x0F |
STD | Store a value to a device port. |
0x10 |
ADD | Add two values. |
0x11 |
SUB | Subtract one value from another. |
0x12 |
INC | Increment a value. |
0x13 |
DEC | Decrement a value. |
0x14 |
LTH | Test if a value is less than another. |
0x15 |
GTH | Test if a value is greater than another. |
0x16 |
EQU | Test if two values are equal. |
0x17 |
NQK | Test if two values are inequal, keeping both. |
0x18 |
SHL | Shift the bits of a value to the left. |
0x19 |
SHR | Shift the bits of a value to the right. |
0x1A |
ROL | Rotate the bits of a value to the left. |
0x1B |
ROR | Rotate the bits of a value to the right. |
0x1C |
IOR | Find the bits that are set in either value. |
0x1D |
XOR | Find the bits that are different in both values. |
0x1E |
AND | Find the bits that are set in both values. |
0x1F |
NOT | Invert the bits in a value. |
HLT
Has no effect. If none of the mode flags are set, the system is halted.
PSH
The value x
is popped from the return stack and is then pushed to the working stack.
POP
The value x
is popped from the working stack.
CPY
The value x
is popped from the return stack, then is pushed to the return stack, and then is pushed to the working stack.
DUP
The value x
is popped from the working stack, then is pushed to the working stack, and then is pushed once more to the working stack.
OVR
The value y
is popped from the working stack, then the value x
is popped from the working stack, then the value x
is pushed to the working stack, then the value y
is pushed to the working stack, and then the value x
is pushed to the working stack.
SWP
The value y
is popped from the working stack, then the value x
is popped from the working stack, then the value y
is pushed to the working stack, and then the value x
is pushed to the working stack.
ROT
The value z
is popped from the working stack, then the value y
is popped from the working stack, then the value x
is popped from the working stack, then the value y
is pushed to the working stack, then the value z
is pushed to the working stack, and then the value x
is pushed to the working stack.
JMP
The double a
is popped from the working stack and is written to the program counter.
JMS
The double a
is popped from the working stack, then the double b
is read from the program counter and written to the return stack, and then the double a
is written to the program counter.
JCN
The double a
is popped from the working stack, then the value t
is popped from the working stack. If t
is not zero, the double a
is written to the program counter.
JCS
The double a
is popped from the working stack, then the value t
is popped from the working stack. If t
is not zero, the double b
is read from the program counter and written to the return stack, and then the double a
is written to the program counter.
LDA
The double a
is popped from the working stack, and then the value v
is read from program memory starting at the memory address referenced by a
and is pushed to the working stack.
STA
The double a
is popped from the working stack, then the value v
is popped from the working stack, and then the value v
is written to program memory starting at the memory address referenced by a
.
LDD
The byte p
is popped from the working stack, then the value v
is read from the device bus starting at the device port referenced by p
and is pushed to the working stack.
STD
The byte p
is popped from the working stack, then the value v
is popped from the working stack and is written to the device bus starting at the device port referenced by p
.
ADD
The value y
is popped from the working stack, then the value x
is popped from the working stack, and then the value r
is calculated as the wrapped addition of y
to x
and is pushed to the working stack.
SUB
The value y
is popped from the working stack, then the value x
is popped from the working stack, and then the value r
is calculated as the wrapped subtraction of y
from x
and is pushed to the working stack.
INC
The value x
is popped from the working stack, and then the value r
is calculated as the wrapped addition of 1 to x
and is pushed to the working stack.
DEC
The value x
is popped from the working stack, and then the value r
is calculated as the wrapped subtraction of 1 from x
and is pushed to the working stack.
LTH
The value y
is popped from the working stack, and then the value x
is popped from the working stack. If x
is less than y
, the byte 0xff
is pushed to the working stack, otherwise the byte 0x00
is pushed to the working stack.
GTH
The value y
is popped from the working stack, and then the value x
is popped from the working stack. If x
is greater than y
, the byte 0xff
is pushed to the working stack, otherwise the byte 0x00
is pushed to the working stack.
EQU
The value y
is popped from the working stack, and then the value x
is popped from the working stack. If x
is equal to y
, the byte 0xff
is pushed to the working stack, otherwise the byte 0x00
is pushed to the working stack.
NQK
The value y
is popped from the working stack, then the value x
is popped from the working stack, then the value x
is pushed to the working stack, and then the value y
is pushed to the working stack. If x
is not equal to y
, the byte 0xff
is pushed to the working stack, otherwise the byte 0x00
is pushed to the working stack.
SHL
The byte y
is popped from the working stack, then the value x
is popped from the working stack. The value r
is calculated by shifting x
leftwards y
times, and then r
is then pushed to the working stack. When a value is shifted leftwards, each bit of the value is set to the original state of the next lower bit, and the lowest bit is unset.
SHR
The byte y
is popped from the working stack, then the value x
is popped from the working stack. The value r
is calculated by shifting x
rightwards y
times, and then r
is then pushed to the working stack. When a value is shifted rightwards, each bit of the value is set to the original state of the next higher bit, and the highest bit is unset.
ROL
The byte y
is popped from the working stack, then the value x
is popped from the working stack. The value r
is calculated by rotating x
leftwards y
times, and then r
is then pushed to the working stack. When a value is rotated leftwards, each bit of the value is set to the original state of the next lower bit, and the lowest bit is set to the original state of the highest bit.
ROR
The byte y
is popped from the working stack, then the value x
is popped from the working stack. The value r
is calculated by rotating the value x
rightwards y
times, and then r
is pushed to the working stack. When a value is rotated rightwards, each bit of the value is set to the original state of the next higher bit, and the highest bit is set to the original state of the lowest bit.
IOR
The value y
is popped from the working stack, and then the value x
is popped from the working stack. The value r
is then calculated bitwise, where each bit of r
is set only if at least one of the corresponding bits of x
and y
is set. The value r
is then pushed to the working stack.
XOR
The value y
is popped from the working stack, and then the value x
is popped from the working stack. The value r
is then calculated bitwise, where each bit of r
is set only if exactly one of the corresponding bits of x
and y
is set. The value r
is then pushed to the working stack.
AND
The value y
is popped from the working stack, and then the value x
is popped from the working stack. The value r
is then calculated bitwise, where each bit of r
is set only if both of the corresponding bits of x
and y
are set. The value r
is then pushed to the working stack.
NOT
The value x
is popped from the working stack. The value r
is then calculated bitwise, where each bit of r
is set only if the corresponding bit of x
is not set. The value r
is then pushed to the working stack.