├── LICENSE ├── README.md ├── index.html ├── js ├── wee.d.ts ├── wee.js └── wee.js.map ├── src ├── Assembler.ts ├── Diagnostic.ts ├── Lexer.ts ├── Tests.ts └── VirtualMachine.ts ├── tests.html └── tsconfig.json /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mario Zechner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wee 2 | Wee is a tiny educational programming environment. Wee is composed of: 3 | 4 | 1. An instruction set architecture (ISA) with a corresponding virtual machine (**Wee Machine**). 5 | 2. An assembly language plus assembler (**Wee Assembly**) 6 | 3. A higher level language (**Wee Lang**) that is an extension of the assembly language 7 | making common tasks, like defining types, functions, locals, etc., more comfortable. 8 | 4. A browser-based development environment with code editing, compilation and debugging tools (**Wee DE**). 9 | 10 | ## Wee Machine 11 | The Wee Machine resembles the architecture of real-world 32-bit machines like x86 and ARM. The Wee Machine is composed of the following components: 12 | 13 | 1. The **processor** with 14 | 1. 32-bit integer & float ALU 15 | 2. 14 general purpose registers (r0-r13) 16 | 3. A program counter register (pc) 17 | 4. A stack pointer register (sp) 18 | 2. Byte-addressable **random access memory** 19 | 3. Memory mapped **video memory** (32-bit RGBA, 320x200) 20 | 4. **Ports** to communicate with peripherals like 21 | 1. Keyboard 22 | 2. Mouse 23 | 3. Graphics card 24 | 5. Sound card 25 | 6. Networking card 26 | 7. Random number generator 27 | 8. High precision timer 28 | 5. A **Basic Input/Output System (BIOS)** 29 | 30 | Wee Machine implements the Von Neumann architecture: code & data are stored to and read from the same memory. This allows self-modifying code. Wee Machine uses little endian, the word size is 32-bit. 31 | 32 | ### Instruction Set 33 | Instructions are 32-bit in size, with 0-3 operands encoded in the instruction. Memory access instructions allow the specification of byte-offsets. The opcode of an instruction is always encoded in the first 6 bits. Some instruction may be made up of an additional 32-bit value encoding data or an address. 34 | 35 | #### Register encoding 36 | Wee Machine has 16 registers, `r0-r13`, `pc` and `sp`. When encoding a register in an instruction, the register is referred to by an index. Registers `r0` to `r13` are indexed from 0 to 13. Register `pc` has index 14, and register `sp` has index 15. 37 | 38 | #### Halting 39 | Wee Machine can be halted via the instruction `0x00000000` or `halt` in assembly. 40 | 41 | #### Arithmetic Operations 42 | Wee Machine supports 32-bit integer and floating point arithmetic. All arithmetic instructions operate only on registers and have the following format: 43 | 44 | | bits 0-5 | bits 6-9 | bits 10-12 | bits 14-17 | bits 18-31 | 45 | | -------- | -------- | -------- | ---------- | ---------- | 46 | | Opcode | Register (op1) | Register (op2) | Register (op3) | Unused | 47 | 48 | These instructions are supported: 49 | 50 | | Opcode | Assembly | Semantics | 51 | | ------ | -------- | --------- | 52 | | 0x01 | `add op1, op2, op3` | Adds the 32-bit integers in `op1` and `op2` and stores the result in `op3`. | 53 | | 0x02 | `sub op1, op2, op3` | Subtracts the 32-bit integers in `op1` and `op2` and stores the result in `op3`. | 54 | | 0x03 | `mul op1, op2, op3` | Multiplies the 32-bit integers in `op1` and `op2` and stores the result in `op3`. | 55 | | 0x04 | `div op1, op2, op3` | Divides the 32-bit signed integer in `op1` by `op2` and stores the result in `op3`. | 56 | | 0x05 | `div_unsigned op1, op2, op3` | Divides the 32-bit unsigned integer in `op1` by `op2` and stores the result in `op3`. | 57 | | 0x06 | `remainder op1, op2, op3` | Divides the 32-bit signed integer in `op1` by `op2` and stores the remainder in `op3`. The remainder has the sign of the dividend `op1` | 58 | | 0x07 | `remainder_unsigned op1, op2, op3` | Divides the 32-bit unsigned integer in `op1` by `op2` and stores the remainder in `op3`. The remainder has the sign of the dividend `op1` | 59 | | 0x08 | `add_float op1, op2, op3` | Adds the 32-bit floats in `op1` and `op2` and stores the result in `op3`. | 60 | | 0x09 | `sub_float op1, op2, op3` | Subtracts the 32-bit floats in `op1` and `op2` and stores the result in `op3`. | 61 | | 0x0a | `mul_float op1, op2, op3` | Multiplies the 32-bit floats in `op1` and `op2` and stores the result in `op3`. | 62 | | 0x0b | `div_float op1, op2, op3` | Divides the 32-bit float in `op1` by `op2` and stores the result in `op3`. | 63 | | 0x0c | `cos_float op1, op2` | Calculates the cosine of the angle given in radians in `op1` and stores the result in `op2` | 64 | | 0x0d | `sin_float op1, op2` | Calculates the sine of the angle given in radians in `op1` and stores the result in `op2` | 65 | | 0x0e | `atan2_float op1, op2, op2` | Calculates the angle of the vector (x, y) stored in `op1`, `op2` relative to the x-axis and stores the result in `op3` | 66 | | 0x0f | `sqrt_float op1, op2` | Calculates the square root of the float in `op1` and stores the result in `op2` | 67 | | 0x10 | `pow_float op1, op2` | Calculates the value of `op1` raised by the exponent in `op2` and stores the result in r3 | 68 | | 0x11 | `convert_int_float op1, op2` | Converts the 32-bit signed integer in `op1` to float and stores the result in `op2` | 69 | | 0x12 | `convert_float_int op1, op2` | Converts the 32-bit float in `op1` to a 32-bit signed integer, truncating the decimal portion, and stores the result in `op2` | 70 | | 0x13 | `cmp op1, op2, op3` | Compares the 32-bit signed integer in `op1` to `op2` and stores the result in `op3`. The result will be 0 if `op1` == `op2`, 1 if `op1` > `op2` and -1 (`0xffffffff`) if `op1` < `op2`. The result can be used with the jump instructions. | 71 | | 0x14 | `cmp_unsigned op1, op2, op3` | Compares the 32-bit unsigned integer in `op1` to `op2` and stores the result in `op3`. The result will be 0 if `op1` == `op2`, 1 if `op1` > `op2` and -1 (`0xffffffff`) if `op1` < `op2`. The result can be used with the jump instructions. | 72 | | 0x15 | `fcmp op1, op2, op3` | Compares the 32-bit float in `op1` to `op2` and stores the result in `op3`. The result will be 0 if `op1` == `op2`, 1 if `op1` > `op2` and -1 (`0xffffffff`) if `op1` < `op2`. The result can be used with the jump instructions. | 73 | 74 | 75 | *Note*: Underflow, overflow and division by zero are not reported. Floating point operations should follow the IEEE 754 standard. Conversions between float and int may result in undefined values if the precision is exceeded. All the nastiness of `NaN`s and infinities apply. Rounding modes TBD. 76 | 77 | #### Bit-wise Operations 78 | Wee Machine supports a standard set of bit-wise operations. All bit-wise operation instructions operate only on registers and have the following format: 79 | 80 | | bits 0-5 | bits 6-9 | bits 10-12 | bits 14-17 | bits 18-31 | 81 | | -------- | -------- | -------- | ---------- | ---------- | 82 | | Opcode | Register (op1) | Register (op2) | Register (op3) | Unused | 83 | 84 | The following instructions are supported: 85 | 86 | | Opcode | Assembly | Semantics | 87 | | ------ | -------- | --------- | 88 | | 0x16 | `not op1, op2` | Inverts the bits in `op1` and stores the result in `op2` | 89 | | 0x17 | `and op1, op2, op3` | Performs a bit-wise and of `op1` and `op2` and stores the result in `op3` | 90 | | 0x18 | `or op1, op2, op3` | Performs a bit-wise or of `op1` and `op2` and stores the result in `op3` | 91 | | 0x19 | `xor op1, op2, op3` | Performs a bit-wise exclusive or of `op1` and `op2` and stores the result in `op3` | 92 | | 0x1a | `shift_left op1, op2, op3` | Shifts the bits in `op1` to the left by the number of bits specified in `op2` and stores the result in `op3` | 93 | | 0x1b | `shift_right op1, op2, op3` | Shifts the bits in `op1` to the right by number of bits specified in `op2` and stores the result in `op3` | 94 | 95 | #### Jumps & Branching 96 | Wee Machine supports a variety of jumps, either directly or based on the result of a `cmp` or `fcmp` 97 | instruction. All jump target addresses are absolute. Jump instructions have the following format: 98 | 99 | | bits 0-5 | bits 6-9 | bits 10-31 | bits 32-64 | 100 | | -------- | -------- | -------- | -------- | 101 | | Opcode | Register (op1) | Unused | word2 102 | 103 | The following instructions are supported: 104 | 105 | | Opcode | Assembly | Semantics | 106 | | ------ | -------- | --------- | 107 | | 0x1c | `jump word2` | Jumps to the specified address in `word2` | 108 | | 0x1d | `jump_equal op1, word2` | Jumps to the specified relative address in `word2` if the operands of the comparison, the result of which is stored in `op1`, were equal | 109 | | 0x1e | `jump_not_equal op1, word2` | Jumps to the specified relative address in `word2` if the operands of the comparison, the result of which is stored in `op1`, were not equal | 110 | | 0x1f | `jump_less op1, word2` | Jumps to the specified relative address in `word2` if the first operand of the comparison, the result of which is stored in `op1`, was less than the second operand | 111 | | 0x20 | `jump_greater op1, word2` | Jumps to the specified relative address in `word2` if the first operand of the comparison, the result of which is stored in `op1`, was greater than the second operand | 112 | | 0x21 | `jump_less_equal op1, word2` | Jumps to the specified relative address in `word2` if the first operand of the comparison, the result of which is stored in `op1`, was less or equal to the second operand | 113 | | 0x22 | `jump_greater_equal op1, word2` | Jumps to the specified relative address in `word2` if the first operand of the comparison, the result of which is stored in `op1`, was greater or equal to the second operand | 114 | 115 | #### Memory operations 116 | Wee Machine has 16 megabytes of byte-addressable memory in which both code and data are stored, plus 16 registers that can hold data and addresses. Wee Machine provides instructions to load and store data from and to registers and memory. 117 | 118 | Memory operations can be 1 or 2 words wide. 2-word memory operations encode a 32-bit value in the second word, which can represent data or an address. The format of the first word is as follows 119 | 120 | | bits 0-5 | bits 6-9 | bits 10-13 | bits 14-31 | bits 32-63 | 121 | | -------- | -------- | -------- | ---------- | ---------- | 122 | | Opcode | Register (op1) | Register (op2) | Offset in bytes (offset) | Value (word) 123 | 124 | The following memory operations are available: 125 | 126 | | Opcode | Assembly | Semantics | 127 | | ------ | -------- | --------- | 128 | | 0x23 | `move op1, op2` | Copies the value in `op1` to `op2` | 129 | | 0x24 | `move word, op2` | Copies the 32-bit value in `word` to `op2` | 130 | | 0x25 | `load word, offset, op2` | Reads the 32-bit value at address `word` + `offset` from memory and stores it in `op2` | 131 | | 0x26 | `load op1, offset, op2` | Reads the 32-bit value at address `op1` + `offset` from memory and stores it in `op2` | 132 | | 0x27 | `store op1, word, offset` | Writes the 32-bit value in `op1` to memory at address `word` + `offset` | 133 | | 0x28 | `store op1, op2, offset` | Writes the 32-bit value in `op1` to memory at address `op2` + `offset` | 134 | | 0x29 | `load_byte word, offset, op2` | Reads the 8-bit value at address `word` + `offset` from memory and stores it in `op1` | 135 | | 0x2a | `load_byte op1, offset, op2` | Reads the 8-bit value at address `op1` + `offset` from memory and stores it in `op2` | 136 | | 0x2b | `store_byte op1, word, offset` | Writes the lowest 8 bits in `op1` to memory at address `word` + `offset` | 137 | | 0x2c | `store_byte op1, op2, offset` | Writes the lowest 8 bits in `op1` to memory at address `op2` + `offset` | 138 | | 0x2d | `load_short word, offset, op2` | Reads the 16-bit value at address `word` + `offset` from memory and stores it in `op1` | 139 | | 0x2e | `load_short op1, offset, op2` | Reads the 16-bit value at address `op1` + `offset` from memory and stores it in `op2` | 140 | | 0x2f | `store_short op1, word, offset` | Writes the lowest 16 bits in `op1` to memory at address `word` + `offset` | 141 | | 0x30 | `store_short op1, op2, offset` | Writes the lowest 16 bits in `op1` to memory at address `op2` + `offset` | 142 | 143 | #### Stack & Call Operations 144 | Wee Machine has a stack at the end of the available memory `0xffffff` which grows "downwards". The register `sp` keeps track of the top of the stack in memory. Wee Machine provides instructions to make working with 145 | the stack easier, e.g. pushing and popping values. 146 | 147 | The stack plays a pivotal role when implementing and calling functions in Wee Machine. A function can use the 148 | stack to store "local" values temporarily while the function is being executed. When calling another function, 149 | the parameters are passed to the function via the stack. Finally, the stack is also used to save the contents 150 | of registers, either because the function can not fit all local data into registers, or because another function is called that will itself modify registers. 151 | 152 | Wee Machine's calling convention works as follows: 153 | 154 | 1. The calling function (caller) saves all registers it uses to the stack, e.g. via `push` 155 | 2. The caller pushes all arguments it wants to pass to the called function (callee) to the stack. The 156 | arguments are pushed to the stack in such an order, that the last argument becomes the top of the stack. All arguments are word sized. 157 | 3. The caller calls the callee, via `call`, which will push the return address (the address of the the next instruction after `call`) onto the stack. 158 | 4. The callee executes its instructions and eventually uses `return `, which will pop the specified number of words from the stack so the stack pointer points at the return address, then pops the return address of the stack, writes it to `pc` and resumes execution. 159 | 5. The caller resumes at the instruction after `call`, with the stack pointer `sp` pointing to the location it pointed to after all arguments were pushed onto the stack. The caller pops the arguments from the stack. The callee may have returned a value in register `r0`. 160 | 161 | Memory operations can be 1 or 2 words wide. 2-word memory operations encode a 32-bit value in the second word, which can represent data or an address. The format of the first word is as follows: 162 | 163 | | bits 0-5 | bits 6-9 | bits 10-31 | bits 32-63 | 164 | | -------- | -------- | -------- | ---------- | 165 | | Opcode | Register (op1) | Number of words | Value (word2) 166 | 167 | The following stack & call operations are available: 168 | 169 | | Opcode | Assembly | Semantics | 170 | | ------ | -------- | --------- | 171 | | 0x31 | `push word2` | Decrease `sp` by 4, then write the 32-bit value `word2` to the stack at address `sp` | 172 | | 0x32 | `push op1` | decrease `sp` by 4, then write the 32-bit value in `op1` to the stack at address `sp` | 173 | | 0x33 | `stackalloc ` | Decrease `sp` by `4 * ` | 174 | | 0x34 | `pop op1` | Reads the 32-bit value at address `sp`, stores it in `op1`, then increases `sp` by 4 | 175 | | 0x35 | `pop ` | Pops the number of words from the stack by increasing `sp` by `4 * `. | 176 | | 0x36 | `call word1` | Pushes the address of the next instruction on the stack, sets `pc` to `word1` which holds the address of the first instruction of the function, and resumes execution | 177 | | 0x37 | `call op1` | Pushes the address of the next instruction on the stack, Sets `pc` to `op1` which holds the address of the first instruction of the function, and resumes execution | 178 | | 0x38 | `return ` | Decreases `sp` by `4 * `, sets `pc` to the value at address `sp`, pops one more word from the stack, and finally resumes execution | 179 | 180 | #### Ports 181 | Wee Machine supports peripherals like keyboard, mouse or graphics card. The processor communicate with these peripherals via ports. Each peripheral is assigned a port number through which the processor can read or write from and to the peripheral. Each peripheral has its own protocol through which it communicates with the processor. 182 | 183 | Port operations are 1 or 2-words wide and have the following format: 184 | 185 | | bits 0-5 | bits 6-9 | bits 10-13 | bits 14-31 | bits 32-63 | 186 | | -------- | -------- | ---------- | ---------- | ---------- | 187 | | Opcode | Register (op1) | Register (op2) | Port number | Value (word2) | 188 | 189 | The following port operations are available: 190 | 191 | | Opcode | Assembly | Semantics | 192 | | ------ | -------- | --------- | 193 | | 0x39 | `port_write op1, ` | Write the 32-bit value in `op1` to port `` | 194 | | 0x3a | `port_write word2, ` | Write the 32-bit value `word2` to port `` | 195 | | 0x3b | `port_write op2, op2` | Write the 32-bit value in `op1` to port `op2` | 196 | | 0x3c | `port_read , op1` | Read the 32-bit value from port `` and store it in `op1`. The operation may block until the peripheral has completed its work. | 197 | | 0x3d | `port_read op1, op2` | Read the 32-bit value from port `op1` and store it in `op2`. The operation may block until the peripheral has completed its work. | 198 | 199 | ### Unused opcodes 200 | All opcodes are encoded in the first 6 bits of an instruction. The instruction set ocupies opcodes `0x00` to `0x3d`, leaving 201 | only opcode `0x3f` unused. This opcode may be used in the future and is reserved. 202 | 203 | ### Peripherals 204 | Wee Machine simulates a system with a keyboard, mouse, graphics card, sound card and networking card. These peripherals are heavily simplified to make working with them simple enough for beginners. The following sections describe the peripheral capabilities and their respective port protocols. 205 | 206 | #### Keyboard 207 | TBD 208 | #### Mouse 209 | TBD 210 | #### Graphics Card 211 | TBD 212 | #### Sound Card 213 | TBD 214 | #### Networking Card 215 | TBD 216 | #### Random number generator 217 | TBD 218 | #### High precision timer 219 | TBD 220 | 221 | ### BIOS 222 | Wee Machine comes with a minimal BIOS that makes interacting with peripherals easier. The BIOS is composed of functions that are co-located in memory with the user code. The assembler knows the addresses of each BIOS function so a programmer can directly reference the function labels in their assembly program. The following sections describe the functions provided by the BIOS. 223 | 224 | TBD 225 | 226 | ### Bootup & Memory Layout 227 | When Wee Machine boots up, it reserves the memory area `0xff0000` to `0xffffff` for the stack, the area `0xfb1800` to `0xfeffff` for the memory mapped video memory (320x200 pixels, 32-bit RGBA), and the area `(0xfb1800 - BIOS code size)` to `0xfb17ff` for the BIOS code. Next, the program is loaded into memory at `0x000000` and executed. The BIOS can manage the unused memory between the program code & data and the BIOS code and provides functions to allocate and deallocate memory in this memory area. Programmers can choose to manage that memory themselves. 228 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /js/wee.d.ts: -------------------------------------------------------------------------------- 1 | declare module wee { 2 | interface StringMap { 3 | [key: string]: V; 4 | } 5 | interface IndexedMap { 6 | [key: number]: V; 7 | } 8 | class ParserResult { 9 | instructions: Array; 10 | labels: StringMap