A 16-bit computer architecture i made, emulated in opal.
NOTE: The project is not finished, there will probably be changes in the design and architecture in the future
to run, open or compile Computer.opal
using the opal compiler, and pass it a file to run as a command line argument.
--clock
- Sets the clock pulse duration of the CPU. Default is 0.
- Usage: --clock [duration]
--resolution
- Sets the computer screen resolution. Default is 256x256.
- Usage: --resolution [width]x[height]
--scale
- Sets the computer screen window scale. Default is 1.
- Usage: --scale [scale factor]
--hex-dump
- Shows an hex dump of the data that is written to RAM after compilation.
- Usage --hex-dump
--time
- Shows the time elapsed since the computer started running when the computer stops.
- Usage --time
--mixer-words
- Sets the amount of words dedicated to each mixer code. Default is 1.
- Usage: --mixer-words [word count]
The computer, designed to have a 16 bits CPU, supports a maximum of 8 GB of RAM (around 4 billion addresses), split in 65536 memory banks, each having a maximum size of 128 KB (65536 addresses). By default, the computer uses 2 MB of RAM split in 16 memory banks. The GPU has a dedicated video memory, that can be of a maximum size of 12 GB (when assuming a 65536x65536 screen resolution). The video memory size depends on the resolution set by the user. By default, it can store 196 KB of data using a 256x256 resolution.
The computer contains 5 general purpose registers (X, Y, Z, A, B), 2 of which (A and B) are directly connected to the ALU. It also contains an alphanumeric display (often used as a data register, referred to as "D"), and a stack pointer (referred to as "S").
- Loads a register with a value. * indicates the register letter:
- LIX - 0x01
- LIY - 0x02
- LIZ - 0x03
- LIA - 0x04
- LIB - 0x05
- LID - 0x06
- LIS - 0x71
- Usage: LI* [value]
- Loads a register with a value, directly taken from the low half of the instruction register. * indicates the register letter:
- FLX - 0x07
- FLY - 0x08
- FLZ - 0x09
- FLA - 0x0a
- FLB - 0x0b
- FLD - 0x0c
- Usage: FL* [value]
- Loads a register with a value from the given memory address. * indicates the register letter:
- LOX - 0x0d
- LOY - 0x0e
- LOZ - 0x0f
- LOA - 0x10
- LOB - 0x11
- LOD - 0x12
- LOS - 0x70
- Usage: LO* [address]
- Stores the contents of a register in a given memory address. * indicates the register letter:
- STX - 0x13
- STY - 0x14
- STZ - 0x15
- STA - 0x16
- STB - 0x17
- STD - 0x18
- STS - 0x72
- Usage: ST* [address]
- Adds the contents of registers A and B and saves the result in X:
- Usage: ADD
- Subtracts the contents of registers A and B and saves the result in X:
- Usage: SUB
- Adds two values and saves the result in X:
- Usage: ADI [value1], [value2]
- Subtracts two values and saves the result in X:
- Usage: SBI [value1], [value2]
- Sets the alphanumeric display mode. Available modes are:
- 0: unsigned int;
- 1: signed int;
- 2: char.
- Usage: DPM [mode]
- Displays the contents of the alphanumeric display:
- Usage: DPL
- Displays a value on the alphanumeric display:
- Usage: DLI [value]
- Displays a value on the alphanumeric display, taken from the low half of the instruction register:
- Usage: FDL [value]
- Jumps to a given memory address (NOTE: all jump instructions can't jump to different memory banks):
- Usage: JMP [address]
- Jumps to a given memory address, taken from the low half of the instruction register:
- Usage: JMI [address]
- Jumps to a given memory address if the carry flag is set:
- Usage: JC [address]
- Jumps to a given memory address, taken from the low half of the instruction register, if the carry flag is set:
- Usage: JCI [address]
- Jumps to a given memory address if the zero flag is set:
- Usage: JZ [address]
- Jumps to a given memory address, taken from the low half of the instruction register, if the zero flag is set:
- Usage: JZI [address]
- Increments the value stored in a register. * indicates the register letter:
- INX - 0x27
- INY - 0x28
- INZ - 0x29
- INA - 0x2a
- INB - 0x2b
- IND - 0x2c
- INS - 0x73
- Usage: IN*
- Decrements the value stored in a register. * indicates the register letter:
- DCX - 0x2c
- DCY - 0x2d
- DCZ - 0x2e
- DCA - 0x2f
- DCB - 0x30
- DCD - 0x31
- DCS - 0x74
- Usage: DC*
- Shifts left the value stored in a register a given amount of times. * indicates the register letter:
- SLX - 0x32
- SLY - 0x33
- SLZ - 0x34
- SLA - 0x35
- SLB - 0x36
- SLD - 0x37
- Usage: SL* [amount]
- Shifts right the value stored in a register a given amount of times. * indicates the register letter:
- SRX - 0x38
- SRY - 0x39
- SRZ - 0x3a
- SRA - 0x3b
- SRB - 0x3c
- SRD - 0x3d
- Usage: SR* [amount]
- Rotates left the value stored in a register a given amount of times. * indicates the register letter:
- RLX - 0x3e
- RLY - 0x3f
- RLZ - 0x40
- RLA - 0x41
- RLB - 0x42
- RLD - 0x43
- Usage: RL* [amount]
- Rotates right the value stored in a register a given amount of times. * indicates the register letter:
- RRX - 0x44
- RRY - 0x45
- RRZ - 0x46
- RRA - 0x47
- RRB - 0x48
- RRD - 0x49
- Usage: RR* [amount]
- Copies the contents of a register to another register. ? indicates the source and * indicates the destination:
- MXY - 0x4a
- MXZ - 0x4b
- MXA - 0x4c
- MXB - 0x4d
- MXD - 0x4e
- MYX - 0x4f
- MYZ - 0x50
- MYA - 0x51
- MYB - 0x52
- MYD - 0x53
- MZX - 0x54
- MZY - 0x55
- MZA - 0x56
- MZB - 0x57
- MZD - 0x58
- MAX - 0x59
- MAY - 0x5a
- MAZ - 0x5b
- MAB - 0x5c
- MAD - 0x5d
- MBX - 0x5e
- MBY - 0x5f
- MBZ- 0x60
- MBA - 0x61
- MBD - 0x62
- MDX - 0x63
- MDY - 0x64
- MDZ - 0x65
- MDA - 0x66
- MDB - 0x67
- Usage: M?*
- Loads a value from the address stored in given register, to the same register. * indicates the register letter:
- LAX - 0x68
- LAY - 0x69
- LAZ - 0x6a
- LAA - 0x6b
- LAB - 0x6c
- LAD - 0x6d
- Usage: LA*
- Subtracts the contents of registers A and B without saving the result:
- Usage: CMP
- Subtracts two values without saving the result:
- Usage: CPI [value1], [value2]
- Pushes the contents of a register into the stack. * indicates the register letter:
- PSX - 0x75
- PSY - 0x76
- PSZ - 0x77
- PSA - 0x78
- PSB - 0x79
- PSD - 0x7a
- Usage: PS*
- Pops an element from the stack and saves it in given register. * indicates the register letter:
- PPX - 0x7b
- PPY - 0x7c
- PPZ - 0x7d
- PPA - 0x7e
- PPB - 0x7f
- PPD - 0x80
- Usage: PP*
- Pushes the next address and the current general purpose registers' values to the stack and jumps to a given memory address:
- Usage: JSR [address]
- Jumps to the last memory address saved into the stack and restores registers values from it:
- Usage: RTS
- Negates the value stored in a register. * indicates the register letter:
- NEX - 0x83
- NEY - 0x84
- NEZ - 0x85
- NEA - 0x86
- NEB - 0x87
- NED - 0x88
- Usage: NE*
- Jumps to a given memory address if the odd flag is set:
- Usage: JOD [address]
- Jumps to a given memory address, taken from the low half of the instruction register, if the odd flag is set:
- Usage: JDI [address]
- Generates an interrupt with a given value taken from the low half of the instruction register:
- Usage: INT [value]
- Returns from an interrupt call:
- Usage: RTI
- Loads a GPU pointer register with a value from the given memory address. * indicates the register letter:
- LGX - 0x8d
- LGY - 0x8e
- Usage: LG* [address]
- Loads a GPU pointer register with a value. * indicates the register letter:
- FGX - 0x8f
- FGY - 0x90
- Usage: FG* [value]
- Sets the GPU mode. Available modes are:
- 0 - pixel mode:
- Draws a pixel at the location pointed by the GPU pointers. Expects a full color code as input.
- 1 - text mode:
- Draws a character at the location pointed by the GPU pointers. Expects a background-foreground-character code as input.
- 2 - rectangle mode:
- Draws a rectangle at the location pointed by the GPU pointers. Expects a memory address that points to 3 words of data, respectively containing the rectangle width, height, and color (in full color code format).
- 3 - line mode:
- Draws a line starting from the location pointed by the GPU pointers. Expects a memory address that points to 3 words of data, respectively containing the horizontal and vertical line destination coordinates, and a full color code.
- 4 - black/white mode:
- Draws 16 pixels according to the binary representation of the given value (1 is white, 0 is black)
- 0 - pixel mode:
- Usage: GPM [mode]
- Sends data to the GPU as explained in the GPM instruction section. The data is taken from any general purpose register. * indicates the register letter:
- FSX - 0x92
- FSY - 0x93
- FSZ - 0x94
- FSA - 0x95
- FSB - 0x96
- FSD - 0x97
- Usage: FS*
- Copies the contents of the X register to the X GPU pointer:
- Usage: GXX
- Copies the contents of the Y register to the Y GPU pointer:
- Usage: GYY
- Increments a GPU pointer register. * indicates the register letter:
- IGX - 0x9a
- IGY - 0x9b
- Usage: IG*
- Decrements a GPU pointer register. * indicates the register letter:
- DGX - 0x9c
- DGY - 0x9d
- Usage: DG*
- Stores the value of a register at an address contained in another register. ? indicates the register containing the address and * indicates the register to store:
- AXY - 0x9e
- AXZ - 0x9f
- AXA - 0xa0
- AXB - 0xa1
- AXD - 0xa2
- AYX - 0xa3
- AYZ - 0xa4
- AYA - 0xa5
- AYB - 0xa6
- AYD - 0xa7
- Usage: A?*
- Plays a sound. Expects a memory address pointing to a sound code:
- Usage: SND [address]
- Plays a sound. Expects a memory address pointing to a sound code stored in a general purpose register. * indicates the register letter:
- SNX - 0xa9
- SNY - 0xaa
- SNZ - 0xab
- SNA - 0xac
- SNB - 0xad
- Usage: SN*
- Waits a certain number of milliseconds stored in a general purpose register. * indicates the register letter:
- WTX - 0xae
- WTY - 0xaf
- WTZ - 0xb0
- WTA - 0xb1
- WTB - 0xb2
- Usage: WT*
- The CPU might also switch to a secondary process while waiting.
- Loads the memory bank selector register with a value taken from the low half of the instruction register:
- Usage: FLS [value]
- Copies the contents of a general purpose register into the memory bank selector. * indicates the register letter:
- MXS - 0xb4
- MYS - 0xb5
- MZS - 0xb6
- MAS - 0xb7
- MBS - 0xb8
- Usage: M*S
- Stops the GPU from updating the screen every time it gets loaded:
- Usage: GFXSTOP
- Restores the normal updating behaviour of the GPU:
- Usage: GFXSTART
- Stops the CPU and closes the emulator:
- Usage: HLT
- The compiler will accept as expressions:
- Integers (signed, unsigned, and in hex);
- Characters (indicated with apostrophes, example: 'a');
- Labels;
- Expressions that only require addition and subtraction between the items mentioned above.
- Expressions can be used with no instruction, and they will be stored in RAM at the address that line of code represents.
- If you leave a field that needs and expression as blank, it will be 0 by default.
- Labels are used to identify memory addresses;
- They are created by placing a colon at the beginning of the line, then giving it a name (example:
:loop
); - Label names can only contain letters and numbers.
Code can be commented out using an hash at the beginning of a line.
Some instructions are used to determine computer settings or special actions, and don't get written to RAM. They are easily recognizable because they all start with a dot. Compiler instructions, as opposed to assembly instructions, are case sensitive:
.addr
- Shifts the next code to start at the address specified.
- Usage: .addr [address]
.fill
- Fills RAM with a certain number of empty words.
- Usage: .fill [amount]
.stack
- Locates the stack in RAM and allocates it a certain number of empty words.
- Usage: .stack [size]
.string
- Fills RAM with a string.
- Usage: .string [string]
.interrupt
- Locates code assigned to an interrupt subroutine.
- Usage: .interrupt [interrupt number]
.waiting
- Locates a segment of code that is ran when the CPU is waiting.
- This instruction will reserve 7 words of RAM to store a separate program counter for the waiting code section and register states (so that they can get restored when the CPU executes the waiting section again).
- Usage: .waiting
.endwaiting
- Marks the last line of the waiting segment.
- The CPU will stop calling the waiting section when waiting once this line has been reached. If you plan on making the waiting section loop, this statement should be omitted.
- Usage: .endwaiting
A full color code is a word composed like this:
[ modifier ][ blue ][ green ][ red ]
|-- 1 bit --||- 5 bits -||- 5 bits -||- 5 bits -|
The modifier bit is used to switch palettes by offsetting the red, green, and blue values.
A text color code is a word composed like this:
[bg blue][bg green][ bg red ][ fg blue ][ fg green ][ fg red ][ char ]
| 1 bit || 1 bit || 1 bit || 2 bits || 2 bits || 2 bits || 7 bits |
NOTE: The character set is not complete yet.
A sound code is 2 to 4 words of data, composed like this:
[ amplitude ][ frequency ]
|- 3 bits -||- 13 bits -|
[ 1st mixer word ]
|------- 16 bits --------|
[ 2nd mixer word ]
|------- 16 bits --------|
[ sound duration (ms) ]
|------- 16 bits --------|
Mixer codes are used to create a custom waveform by combining multiple waveforms. When the amount of mixer codes is 0, the sound chip will use square waves for every sound. The first mixer code is composed like this:
Most significant bit
[ sawtooth width ][ sawtooth amplitude ]
|------- 4 bits -------||------- 4 bits --------|
[ square PWM sawtooth width ][ square amplitude ] Least significant bit
|--------- 4 bits ----------||----- 4 bits -----|
- The square amplitude controls the relative amplitude of the square wave;
- The square PWM sawtooth width controls the width of a sawtooth wave on which the square wave will be modulated (0 for no modulation);
- The sawtooth amplitude controls the relative amplitude of the sawtooth wave;
- The sawtooth width controls the "width of the rising ramp as a proportion of the total cycle" (https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.sawtooth.html) of the sawtooth wave.
The second mixer code is composed like this:
[ noise amplitude ][ square duty cycle ][ square amplitude ]
|----- 5 bits ----||------ 6 bits -----||----- 5 bits -----|
- The square amplitude controls the relative amplitude of a second square wave;
- The square duty cycle controls the duty cycle of the second square wave;
- The noise amplitude controls the relative amplitude of a white noise sample.