Architecture specification

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.