├── .github └── workflows │ └── fox32-unstable-linux.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docs ├── cpu.md ├── encoding.md ├── instructions.md ├── io_bus.md ├── logos │ ├── 32.png │ ├── 32.svg │ ├── fox32-circle.png │ ├── fox32.png │ └── fox32.svg ├── memory.md └── screenshots │ └── fox32os-terminal.png └── src ├── bus.c ├── bus.h ├── cpu.c ├── cpu.h ├── disk.c ├── disk.h ├── framebuffer.c ├── framebuffer.h ├── keyboard.c ├── keyboard.h ├── main.c ├── mmu.c ├── mmu.h ├── mouse.c ├── mouse.h ├── screen.c ├── screen.h ├── serial.c └── serial.h /.github/workflows/fox32-unstable-linux.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | branches: 5 | - main 6 | 7 | name: fox32 Unstable - Linux 8 | 9 | jobs: 10 | fox32-unstable-linux: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Download latest fox32rom artifact 17 | uses: dawidd6/action-download-artifact@v2 18 | with: 19 | repo: fox32-arch/fox32rom 20 | workflow: fox32rom-unstable.yml 21 | workflow_conclusion: success 22 | 23 | - name: Move fox32.rom into the root folder 24 | run: | 25 | mv fox32.rom/ download/ 26 | cp download/fox32.rom ./fox32.rom 27 | 28 | - name: Install libsdl2-dev and vim 29 | run: | 30 | sudo apt update 31 | sudo apt install -y libsdl2-dev vim 32 | 33 | - name: Build 34 | run: make 35 | 36 | - name: Upload Artifact 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: fox32 40 | path: fox32 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.vscode/ 2 | 3 | *.o 4 | 5 | fox32.rom 6 | fox32rom.h 7 | 8 | fox32 9 | 10 | fox32.exe 11 | SDL2.dll 12 | 13 | fox32.html 14 | fox32.wasm 15 | fox32.js 16 | fox32.data 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ryfox/ry755 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = linux 2 | ifeq ($(TARGET),linux) 3 | # default is used for CC 4 | SDL2_CONFIG = sdl2-config 5 | CFLAGS += -g -Ofast -std=c99 -Wall -Wextra `$(SDL2_CONFIG) --cflags` 6 | LDFLAGS += `$(SDL2_CONFIG) --libs` 7 | else 8 | ifeq ($(TARGET),aarch64-darwin) 9 | SDL2_CONFIG = sdl2-config 10 | CFLAGS += -g -Ofast -std=c99 -Wall -Wextra -I`$(SDL2_CONFIG) --prefix`/include -D_THREAD_SAFE 11 | LDFLAGS += `$(SDL2_CONFIG) --libs` 12 | else 13 | ifeq ($(TARGET),mingw) 14 | CC = x86_64-w64-mingw32-gcc 15 | SDL2_CONFIG = /usr/local/x86_64-w64-mingw32/bin/sdl2-config 16 | CFLAGS += -g -Ofast -std=c99 -Wall -Wextra -DWINDOWS -I/usr/local/x86_64-w64-mingw32/include -Dmain=SDL_main 17 | LDFLAGS += -lmingw32 -lSDL2main -lSDL2 -L/usr/local/x86_64-w64-mingw32/lib -lmingw32 -lSDL2main -lSDL2 -mwindows 18 | TARGET_FILE_EXTENSION = .exe 19 | else 20 | ifeq ($(TARGET),wasm) 21 | CC = emcc 22 | CFLAGS += -O3 -std=c99 -Wall -Wextra 23 | LDFLAGS += -s TOTAL_MEMORY=70057984 -sALLOW_MEMORY_GROWTH=1 -sUSE_SDL=2 --preload-file fox32os.img 24 | TARGET_EXTRADEPS = fox32os.img 25 | TARGET_FILE_EXTENSION = .html 26 | else 27 | $(error unknown TARGET) 28 | endif 29 | endif 30 | endif 31 | endif 32 | 33 | CFILES = src/main.c \ 34 | src/bus.c \ 35 | src/cpu.c \ 36 | src/disk.c \ 37 | src/framebuffer.c \ 38 | src/keyboard.c \ 39 | src/mmu.c \ 40 | src/mouse.c \ 41 | src/screen.c \ 42 | src/serial.c 43 | 44 | OBJS = $(addsuffix .o, $(basename $(CFILES))) 45 | 46 | .PHONY: all 47 | all: fox32$(TARGET_FILE_EXTENSION) 48 | 49 | FOX32ROM_IN = fox32.rom 50 | FOX32ROM_OUT = fox32rom.h 51 | 52 | $(FOX32ROM_OUT): $(FOX32ROM_IN) 53 | xxd -i $(FOX32ROM_IN) $(FOX32ROM_OUT) 54 | sed -i -e 's/fox32_rom/fox32rom/' $(FOX32ROM_OUT) 55 | 56 | fox32$(TARGET_FILE_EXTENSION): $(TARGET_EXTRADEPS) $(OBJS) 57 | $(CC) -o $@ $(OBJS) $(LDFLAGS) 58 | 59 | %.o: %.c $(FOX32ROM_OUT) 60 | $(CC) -o $@ -c $< $(CFLAGS) 61 | 62 | clean: 63 | rm -rf fox32 fox32.exe fox32.wasm fox32.html fox32.data fox32.js fox32rom.h $(OBJS) 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fox32 2 | 3 | ![fox32 logo](docs/logos/fox32-circle.png) 4 | (logo by [ZenithNeko](https://zencorner.xyz/contacts.html)) 5 | 6 | This is the reference emulator of the fox32 platform. See the [organization root](https://github.com/fox32-arch) README for general details. 7 | 8 | ![Screenshot of fox32os](docs/screenshots/fox32os-terminal.png) 9 | 10 | ## Getting Started 11 | 12 | Prebuilt Linux binaries of the latest commit are available on the [GitHub Actions page](https://github.com/fox32-arch/fox32/actions). 13 | 14 | Releases available on the [Releases page](https://github.com/fox32-arch/fox32/releases) are **very outdated** at the moment and should not be used. 15 | 16 | ### Building 17 | 18 | Download the latest release or commit of [**fox32rom**](https://github.com/fox32-arch/fox32rom), and place the downloaded `fox32.rom` file into the root directory of this repo. Then simply run `make`. The resulting binary will be saved as `fox32`. Optionally you may build for a different target with `make TARGET=`, see the Makefile for details. 19 | 20 | ### Usage 21 | 22 | The following arguments are valid: 23 | - `--disk `: mount the specified file as a disk 24 | - `--rom `: use the specified file as the boot ROM; if this argument is not specified then the embedded copy of **fox32rom** is used 25 | - `--debug`: print a disassembly of each instruction as it runs 26 | 27 | The most common use case is passing the [**fox32os**](https://github.com/fox32-arch/fox32os) disk image as the first disk: `./fox32 --disk fox32os.img` 28 | 29 | See [encoding.md](docs/encoding.md) and [cpu.md](docs/cpu.md) for information about the instruction set. 30 | 31 | ## License 32 | This project is licensed under the [MIT license](LICENSE). 33 | -------------------------------------------------------------------------------- /docs/cpu.md: -------------------------------------------------------------------------------- 1 | # fox32 CPU 2 | 3 | This document aims to describe the CPU in the the fox32 architecture. 4 | Peripherals such as the disk controller are described in [io_bus.md](./io_bus.md). 5 | 6 | 7 | ## Endianness 8 | 9 | All 16-bit or 32-bit values are stored in memory in little-endian order. 10 | 11 | 12 | ## Registers 13 | 14 | The fox32 CPU has the following registers: 15 | 16 | - **r0-r31**: 32-bit general-purpose registers 17 | - **rsp**: current stack pointer 18 | - **resp**: exception stack pointer 19 | - **rfp**: frame pointer 20 | - **rip**: instruction pointer 21 | - condition flags, which are updated after some operations 22 | - **zero flag** 23 | - **carry flag** 24 | - other flags 25 | - enable use of exception stack pointer (**swap sp**) 26 | - **interrupt flag**, to enable interrupt handling 27 | - MMU state 28 | - **MMU enabled** flag 29 | - **page directory pointer** 30 | 31 | 32 | ## External buses 33 | 34 | There are two kinds of external bus that the fox32 CPU can address: 35 | 36 | - **Memory**: Data is read from and written to memory with the `mov` instruction, 37 | instructions are fetched from memory. 38 | - **I/O bus**: Peripherals are connected to the I/O bus. Peripheral registers 39 | can be read using the `in` instruction and written using the `out` instruction. 40 | 41 | 42 | ## Instruction encoding 43 | 44 | All instructions start with a 16-bit control word, which is optionally followed 45 | by a source{,+offset} operand, or by source{,+offset} and target{,+offset} operands, depending on the opcode. 46 | 47 | 48 | | bits | name | description 49 | |--------|--------|--------------------------------------------------- 50 | | 1:0 | source | source operand type 51 | | 3:2 | target | target operand type 52 | | 6:4 | cond | condition code 53 | | 7 | off | set if 8-bit unsigned offset follows register pointer operand 54 | | 13:8 | opcode | operation code/type, e.g. `mov` or `add` 55 | | 15:14 | size | operation size, e.g. 32 bits 56 | 57 | 58 | NOTE: Although in the instruction encoding the source operand comes first, 59 | followed by the target operand, the order is reversed in the fox32 assembly 60 | language. The following lines are equivalent: 61 | 62 | ``` 63 | cmp r1, r20 64 | data.16 0x8700 data.8 20 data.8 1 65 | ``` 66 | 67 | 68 | ### Operand types 69 | 70 | | value | description | size of operand | what's actually stored? 71 | |-------|-----------------------|------------------|-------------------------- 72 | | 0 | register | 8 bits | register number 73 | | 1 | register pointer | 8 bits | register number 74 | | 2 | immediate | operation size | value 75 | | 3 | immediate pointer | 32 bits | pointer to memory location 76 | 77 | 78 | ### Register numbers 79 | 80 | | value | register 81 | |-------|--------------------------------------------------------------------- 82 | | 0-31 | r0-r31 83 | | 32 | rsp 84 | | 33 | resp 85 | | 34 | rfp 86 | 87 | 88 | 89 | ### Condition codes 90 | 91 | | value | name | description 92 | |-------|-------------|----------------------------------------------------- 93 | | 0 | always | execute unconditionally 94 | | 1 | ifz | execute if zero flag is set 95 | | 2 | ifnz | execute if zero flag is not set 96 | | 3 | ifc/iflt | execute if carry flag is set 97 | | 4 | ifnc/ifgteq | execute if carry flag is not set 98 | | 5 | ifgt | execute if neither zero flag nor carry flag is set 99 | | 6 | iflteq | execute if zero flag or carry flag is set 100 | 101 | 102 | ### Operation codes 103 | 104 | | value | name | operands | op sizes| description 105 | |-------|--------|----------|---------|----------------------------------------------------- 106 | | 0x00 | NOP | none | 8/16/32 | no operation 107 | | 0x01 | ADD | src+tgt | 8/16/32 | add 108 | | 0x02 | MUL | src+tgt | 8/16/32 | multiply (unsigned) 109 | | 0x03 | AND | src+tgt | 8/16/32 | bitwise AND 110 | | 0x04 | SLA | src+tgt | 8/16/32 | shift left 111 | | 0x05 | SRA | src+tgt | 8/16/32 | shift right arithmetic (with sign extension) 112 | | 0x06 | BSE | src+tgt | 8/16/32 | bit set 113 | | 0x07 | CMP | src+tgt | 8/16/32 | unsigned compare 114 | | 0x08 | JMP | src | 32 | absolute jump 115 | | 0x09 | RJMP | src | 8/16/32 | relative jump 116 | | 0x0A | PUSH | src | 8/16/32 | push value to stack 117 | | 0x0B | IN | src+tgt | 32 | get input from I/O bus 118 | | 0x0C | ISE | none | 32 | set interrupt enable flag 119 | | 0x0D | MSE | none | 32 | set MMU enable flag 120 | | 0x10 | HALT | none | 8/16/32 | halt CPU 121 | | 0x11 | INC | src | 8/16/32 | increment (add 1 << tgt in opcode encoding) 122 | | 0x13 | OR | src+tgt | 8/16/32 | bitwise OR 123 | | 0x14 | IMUL | src+tgt | 8/16/32 | multiply (signed) 124 | | 0x15 | SRL | src+tgt | 8/16/32 | shift right logical (with zero extension) 125 | | 0x16 | BCL | src+tgt | 8/16/32 | bit clear 126 | | 0x17 | MOV | src+tgt | 8/16/32 | move value 127 | | 0x18 | CALL | src | 32 | absolute call 128 | | 0x19 | RCALL | src | 8/16/32 | relative call 129 | | 0x1A | POP | src | 8/16/32 | pop value from stack 130 | | 0x1B | OUT | src+tgt | 32 | output on I/O bus 131 | | 0x1C | ICL | none | 32 | clear interrupt enable flag 132 | | 0x1D | MCL | none | 32 | clear MMU enable flag 133 | | 0x20 | BRK | none | 8/16/32 | debug breakpoint 134 | | 0x21 | SUB | src+tgt | 8/16/32 | subtract 135 | | 0x22 | DIV | src+tgt | 8/16/32 | divide (unsigned) 136 | | 0x23 | XOR | src+tgt | 8/16/32 | bitwise XOR 137 | | 0x24 | ROL | src+tgt | 8/16/32 | rotate left 138 | | 0x25 | ROR | src+tgt | 8/16/32 | rotate right 139 | | 0x26 | BTS | src+tgt | 8/16/32 | test if bit set 140 | | 0x27 | MOVZ | src+tgt | 8/16/32 | move value and clear upper bits in target register 141 | | 0x28 | LOOP | src | 32 | absolute loop 142 | | 0x29 | RLOOP | src | 8/16/32 | relative loop 143 | | 0x2A | RET | none | 32 | return from function 144 | | 0x2C | INT | src | 32 | raise interrupt 145 | | 0x2D | TLB | src | 32 | flush TLB and set page directory pointer 146 | | 0x31 | DEC | src | 8/16/32 | decrement (subtract 1 << tgt in opcode encoding) 147 | | 0x32 | REM | src+tgt | 8/16/32 | calculate remainder of division (unsigned) 148 | | 0x33 | NOT | src | 8/16/32 | bitwise NOT 149 | | 0x34 | IDIV | src+tgt | 8/16/32 | divide (signed) 150 | | 0x35 | IREM | src+tgt | 8/16/32 | remainder (signed) 151 | | 0x37 | ICMP | src+tgt | 8/16/32 | signed compare 152 | | 0x39 | RTA | src+tgt | 8/16/32 | calculate address relative to instruction pointer 153 | | 0x3A | RETI | none | 32 | return from interrupt 154 | | 0x3D | FLP | src | 32 | flush page from TLB 155 | 156 | 157 | ### Operation sizes 158 | 159 | | value | description 160 | |-------|-------------------------------------------------------------- 161 | | 0 | byte (8 bits) 162 | | 1 | half (16 bits) 163 | | 2 | word (32 bits) 164 | | 3 | reserved 165 | 166 | For SLA, SRA, SRL, ROR, ROL, BCL, BSE, BTS, src immediates are fixed to 8 bits 167 | 168 | ## Interrupts and Exceptions 169 | 170 | Interrupts indicate asynchronous hardware events (such as VSYNC) or execution 171 | of the `int` instruction, while exceptions indicate various synchronous errors. 172 | 173 | There are 0x100 interrupt vectors and 5 exception vectors. Interrupt vectors 174 | are at 0x000 to 0x3FC, and exception vectors at 0x400 to 0x410. These memory 175 | locations simply store the address of the interrupt/exception handler, 176 | or 0x0 when no handler has been installed. 177 | 178 | (TODO: what should the hardware do when a handler is missing?) 179 | 180 | | type | vector | description 181 | |-----------|----------|------------------------------------------- 182 | | interrupt | 0 - 0xF0 | free for software use 183 | | interrupt | 0xFB | audio channel 3 refill 184 | | interrupt | 0xFC | audio channel 2 refill 185 | | interrupt | 0xFD | audio channel 1 refill 186 | | interrupt | 0xFE | audio channel 0 refill 187 | | interrupt | 0xFF | display VSYNC 188 | | exception | 0x00 | divide by zero 189 | | exception | 0x01 | invalid opcode 190 | | exception | 0x02 | page fault during read 191 | | exception | 0x03 | page fault during write 192 | | exception | 0x04 | breakpoint 193 | 194 | 195 | Upon interrupt/exception entry, the CPU performs the following operations: 196 | 197 | - read handler address from vector 198 | - if *swap sp* is enabled: 199 | - switch to the exception stack pointer, and push the old stack pointer 200 | - push current instruction pointer 201 | - push flags (8 bits) 202 | - push interrupt vector (0x00-0xff) or exception operand 203 | - clear interrupt flag and *swap sp* flag 204 | - jump to handler 205 | 206 | 207 | Interrupt/exception handlers are exited through the `reti` instruction, which 208 | performs the following operations: 209 | 210 | - pop and restore flags 211 | - pop instruction pointer 212 | - if *swap sp* flag is set, pop stack pointer 213 | 214 | 215 | The flags are stored in the following format: 216 | 217 | | bit | description 218 | |--------|----------------------------------------------- 219 | | 0 | zero flag 220 | | 1 | carry flag 221 | | 2 | interrupt flag 222 | | 3 | *swap sp* flag 223 | 224 | 225 | ## MMU 226 | 227 | If the MMU is enabled, two-level page tables are used to translate virtual 228 | addresses to physical addresses. 229 | 230 | The address of the page directory can be set using the `tlb` instruction. 231 | 232 | | virtual address bits | purpose 233 | |-----------------------|-------------------------------------------------- 234 | | 11:0 | lowest 12 bits of physical address 235 | | 21:12 | page table index 236 | | 31:22 | page directory index 237 | 238 | 239 | Page directories and page tables are arrays of 1024 elements of the following format: 240 | 241 | | bits | bit mask | purpose 242 | |-------|---------------|-------------------------------------------------- 243 | | 31:12 | 0xfffff000 | address of page table or page 244 | | 1 | 0x00000002 | page is writable 245 | | 0 | 0x00000001 | page table or page is present 246 | 247 | 248 | A page table walk is performed as follows: 249 | 250 | - read page directory entry at page directory index in page directory 251 | - abort if page table is not present 252 | - read page table entry at page table index in page table 253 | - abort of page is not present 254 | - use physical page address and writability information from page table entry 255 | -------------------------------------------------------------------------------- /docs/encoding.md: -------------------------------------------------------------------------------- 1 | # Encoding 2 | ``` 3 | size instr off cond dest src 4 | xx xxxxxx x xxx xx xx <8,16,32 bits> <8 bits> <8,16,32 bits> <8 bits> 5 | ``` 6 | 7 | 8 | # Size table 9 | If the instruction doesn't allow variable sizes or a size was not specified, set the size bits to Word (0b10) 10 | | | | 11 | | :--: | -------------- | 12 | | 0b00 | byte (8 bits) | 13 | | 0b01 | half (16 bits) | 14 | | 0b10 | word (32 bits) | 15 | 16 | # Instruction table 17 | | 0x | -0 | -1 | -2 | -3 | -4 | -5 | -6 | -7 | -8 | -9 | -A | -B | -C | -D | -E | -F | 18 | | :-: | ---- | ------------- | ------------- | ------------- | -------------- | -------------- | ------------- | -------------- | ---- | --------------- | -------------- | --- | --- | --- | --- | --- | 19 | | 0- | NOP | ADD[.8,16,32] | MUL[.8,16,32] | AND[.8,16,32] | SLA[.8,16,32] | SRA[.8,16,32] | BSE[.8,16,32] | CMP[.8,16,32] | JMP | RJMP[.8,16,32] | PUSH[.8,16,32] | IN | ISE | MSE | | | 20 | | 1- | HALT | INC[.8,16,32] | | OR[.8,16,32] | IMUL[.8,16,32] | SRL[.8,16,32] | BCL[.8,16,32] | MOV[.8,16,32] | CALL | RCALL[.8,16,32] | POP[.8,16,32] | OUT | ICL | MCL | | | 21 | | 2- | BRK | SUB[.8,16,32] | DIV[.8,16,32] | XOR[.8,16,32] | ROL[.8,16,32] | ROR[.8,16,32] | BTS[.8,16,32] | MOVZ[.8,16,32] | LOOP | RLOOP[.8,16,32] | RET | | INT | TLB | | | 22 | | 3- | | DEC[.8,16,32] | REM[.8,16,32] | NOT[.8,16,32] | IDIV[.8,16,32] | IREM[.8,16,32] | | ICMP[.8,16,32] | | RTA[.8,16,32] | RETI | | | FLP | | | 23 | 24 | # Condition table 25 | | | | | 26 | | :---: | ------ | --------------------------------------------- | 27 | | 0b000 | --- | always | 28 | | 0b001 | IFZ | zero | 29 | | 0b010 | IFNZ | not zero | 30 | | 0b011 | IFC | carry | 31 | | 0b011 | IFLT | less than (equivalent to IFC) | 32 | | 0b100 | IFNC | not carry | 33 | | 0b100 | IFGTEQ | greater than or equal to (equivalent to IFNC) | 34 | | 0b101 | IFGT | greater than | 35 | | 0b110 | IFLTEQ | less than or equal to | 36 | 37 | # Source/Destination table 38 | | | | 39 | | :--: | ------------------- | 40 | | 0b00 | register | 41 | | 0b01 | register (pointer) | 42 | | 0b10 | immediate | 43 | | 0b11 | immediate (pointer) | 44 | Using an immediate (0b10) as the destination type is only allowed in a few cases, such as with the `OUT` instruction. Using it with instructions where it doesn't make sense (such as doing `mov 0, 1` for example) will throw an invalid opcode exception. 45 | 46 | # Register Pointer Offset 47 | The off field indicates that each operand of type 0b01 (register pointer) has an 8-bit immediate following the operand. This immediate is added to the value of the register before dereferencing. 48 | -------------------------------------------------------------------------------- /docs/instructions.md: -------------------------------------------------------------------------------- 1 | # Fox32 instructions 2 | 3 | This file describes every fox32 instruction in detail. For a general 4 | description of the fox32 CPU and instruction encoding details, see [cpu.md](./cpu.md). 5 | 6 | 7 | ## Arithmetic instructions 8 | 9 | ### ADD: add 10 | ### SUB: subtract 11 | ### INC: increment (add 1/2/4/8) 12 | ### DEC: decrement (subtract 1/2/4/8) 13 | ### CMP: unsigned compare 14 | ### ICMP: signed compare 15 | ### AND: bitwise AND 16 | ### OR: bitwise OR 17 | ### NOT: bitwise NOT 18 | ### XOR: bitwise XOR 19 | ### BSE: bit set 20 | ### BCL: bit clear 21 | ### BTS: test if bit set 22 | ### SLA: shift left 23 | ### SRA: shift right arithmetic (with sign extension) 24 | ### SRL: shift right logical (with zero extension) 25 | ### ROL: rotate left 26 | ### ROR: rotate right 27 | ### MUL: multiply (unsigned) 28 | ### IMUL: multiply (signed) 29 | ### DIV: divide (unsigned) 30 | ### IDIV: divide (signed) 31 | ### REM: calculate remainder of division (unsigned) 32 | ### IREM: remainder (signed) 33 | ### NOP: no operation 34 | ### MOV: move value 35 | ### MOVZ: move value and clear upper bits in target register 36 | ### RTA: calculate address relative to instruction pointer 37 | 38 | ## Stack instructions 39 | 40 | ### PUSH: push value to stack 41 | ### POP: pop value from stack 42 | 43 | ## Control flow instructions 44 | 45 | ### JMP: absolute jump 46 | ### RJMP: relative jump 47 | ### LOOP: absolute loop 48 | ### RLOOP: relative loop 49 | ### CALL: absolute call 50 | ### RCALL: relative call 51 | ### RET: return from function 52 | ### RETI: return from interrupt 53 | ### ISE: set interrupt enable flag 54 | ### ICL: clear interrupt enable flag 55 | ### BRK: debug breakpoint 56 | ### INT: raise interrupt 57 | ### HALT: halt CPU 58 | 59 | ## MMU instructions 60 | 61 | ### MSE: set MMU enable flag 62 | ### MCL: clear MMU enable flag 63 | ### FLP: flush page from TLB 64 | ### TLB: flush TLB and set page directory pointer 65 | 66 | ## I/O instructions 67 | 68 | ### IN: get input from I/O bus 69 | ### OUT: output on I/O bus 70 | -------------------------------------------------------------------------------- /docs/io_bus.md: -------------------------------------------------------------------------------- 1 | # I/O bus 2 | 3 | The I/O bus is accessed using the `in` and `out` instructions. 4 | Every access is 32 bits wide; there is no byte-addressing on the I/O bus and 5 | addresses don't have to be aligned to multiples of four. 6 | 7 | When a peripheral has multiple instances (e.g. 32 display overlays, 4 disks), 8 | the lowest bits of the address usually indicate the instance number, while 9 | higher bits indicate the function to be performed. 10 | 11 | | start | end | description 12 | |------------|------------|--------------------------------------- 13 | | 0x00000000 | 0x00000000 | serial port 14 | | 0x80000000 | 0x8000031f | display 15 | | 0x80000400 | 0x80000401 | mouse 16 | | 0x80000500 | 0x80000500 | keyboard 17 | | 0x80000600 | 0x80000600 | audio 18 | | 0x80000700 | 0x80000706 | RTC and uptime 19 | | 0x80001000 | 0x80005003 | disk 20 | | 0x80010000 | 0x80010000 | power controller 21 | 22 | 23 | ## 0x00000000: Serial port 24 | 25 | ### 0x00: Data register 26 | 27 | Writing to this register outputs the lowest 8 bits as a byte on the debug 28 | serial port (stdout in case of the fox32 emulator). If necessary, the CPU is 29 | stalled long enough to ensure that the byte is output rather than discarded. 30 | 31 | Reading from this register gets a byte of input from the serial port, if 32 | available, or zero otherwise. 33 | 34 | 35 | ## 0x80000000: Display 36 | 37 | The display controller manages a background framebuffer and 32 overlays, 38 | and composes them into the picture seen on screen. 39 | 40 | The background framebuffer is located at the fixed RAM address 0x2000000, or 32 MiB. 41 | The screen resolution is 640x480 (307200 pixels, 1228800 bytes). 42 | 43 | The pixel format is RGBA. The alpha channel determines transparency in overlays 44 | insofar that a non-zero alpha value causes a pixel to be fully opaque and a 45 | zero alpha value causes it to be fully transparent. 46 | 47 | When multiple overlays are active at a specific pixel position, the highest 48 | numbered overlay that provides an opaque pixel "wins": Its pixel RGB value is 49 | output on screen. 50 | 51 | ### 0x00-0x1f: Overlay position 52 | 53 | bits | description 54 | --------|------------------ 55 | 31:16 | Y (vertical) position 56 | 15:0 | X (horizontal) position 57 | 58 | ### 0x100-0x11f: Overlay size 59 | 60 | bits | description 61 | --------|------------------ 62 | 31:16 | height (vertical size) 63 | 15:0 | width (horizontal size) 64 | 65 | ### 0x200-0x21f: Overlay buffer pointer 66 | 67 | RAM address of the framebuffer for this overlay. 68 | 69 | ### 0x300-0x31f: Overlay enable 70 | 71 | - Write non-zero to enable this overlay, zero to disable 72 | - Read 1 if enabled, 0 of disabled 73 | 74 | 75 | ## 0x80000400: Mouse 76 | 77 | The mouse position and button states can be read and written, but hardware may 78 | change them on incoming mouse events. 79 | 80 | ### 0x00: Button states 81 | 82 | bits | description 83 | --------|------------------ 84 | 2 | mouse button is currently held 85 | 1 | mouse button has been released 86 | 0 | mouse button has been pressed 87 | 88 | ### 0x01: Mouse position 89 | 90 | bits | description 91 | --------|------------------ 92 | 31:16 | Y (vertical) position 93 | 15:0 | X (horizontal) position 94 | 95 | 96 | ## 0x80000500: Keyboard 97 | 98 | ### 0x00: Get key event (read-only) 99 | 100 | Read this register to get a key event from the key event queue, or 0 if none is 101 | currently available. 102 | 103 | bits | description 104 | --------|------------------ 105 | 8 | 0=pressed, 1=released 106 | 7:0 | PC-compatible keyboard scancode 107 | 108 | 109 | ## 0x80000600: Audio 110 | 111 | TODO 112 | 113 | 114 | ## 0x80000700: Real-Time Clock (RTC) and Uptime 115 | 116 | offset | description 117 | --------|------------------ 118 | 0x00 | year (e.g. 2023) 119 | 0x01 | month (1-12) 120 | 0x02 | day (1-31) 121 | 0x03 | hour (0-23) 122 | 0x04 | minute (0-59) 123 | 0x05 | second (0-59) 124 | 0x06 | milliseconds since startup 125 | 0x07 | daylight savings time active (zero or non-zero) 126 | 127 | 128 | ## 0x80001000: Disk 129 | 130 | The disk controller supports four disk ports. Each of them may be connected to 131 | a disk (or not) at any time. 132 | 133 | Disks are organized in sectors of 512 bytes each. 134 | 135 | ### 0x80001000-0x80001003: Disk size (read-only) 136 | 137 | Read this register to get the disk size in bytes. 138 | 139 | ### 0x80002000-0x80002003: Buffer pointer 140 | 141 | Address of RAM buffer to be used for read/write transfers. 142 | 143 | ### 0x80003000-0x80003003: Initiate disk read (write-only) 144 | 145 | Write a sector number to this register to initate a disk read from the 146 | specified sector. The CPU is stalled until the read completes. 147 | 148 | ### 0x80004000-0x80004003: Initiate disk write (write-only) 149 | 150 | Write a sector number to this register to initate a disk write to the specfied 151 | sector. The CPU is stalled until the write completes. 152 | 153 | ### 0x80005000-0x80005003: Remove disk (write-only) 154 | 155 | Write any value to this register to remove or eject the specified disk. 156 | 157 | 158 | ## 0x80010000: Power controller 159 | 160 | ### 0x00: Poweroff 161 | 162 | Write 0 to this register to power the computer off (or terminate the emulator). 163 | 164 | Poweroff does not take effect immediately, so software should do something safe 165 | (such as spinning in a loop) after triggering the poweroff. 166 | -------------------------------------------------------------------------------- /docs/logos/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox32-arch/fox32/8b0cbe37f4fe00efb39e07d65284bdeb5daa61bb/docs/logos/32.png -------------------------------------------------------------------------------- /docs/logos/32.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 28 | 31 | 35 | 40 | 41 | 42 | 65 | 69 | 73 | 77 | 81 | 85 | 89 | 97 | 103 | 107 | 111 | 115 | 119 | 123 | 127 | 131 | 135 | 139 | 32 150 | 151 | -------------------------------------------------------------------------------- /docs/logos/fox32-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox32-arch/fox32/8b0cbe37f4fe00efb39e07d65284bdeb5daa61bb/docs/logos/fox32-circle.png -------------------------------------------------------------------------------- /docs/logos/fox32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox32-arch/fox32/8b0cbe37f4fe00efb39e07d65284bdeb5daa61bb/docs/logos/fox32.png -------------------------------------------------------------------------------- /docs/logos/fox32.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 28 | 31 | 35 | 40 | 41 | 42 | 66 | 70 | 74 | 78 | 82 | 86 | 90 | 98 | 104 | 108 | 112 | 116 | 120 | 124 | 128 | 132 | 136 | 140 | 32 151 | fox 162 | 163 | -------------------------------------------------------------------------------- /docs/memory.md: -------------------------------------------------------------------------------- 1 | # Memory 2 | 3 | The memory in a fox32 system is laid out as follows: 4 | 5 | | start | end | size | description 6 | |------------|------------|---------|--------------------------------------- 7 | | 0x00000000 | 0x04000000 | 64 MiB | RAM 8 | | 0xf0000000 | 0xf0080000 | 512 KiB | ROM 9 | -------------------------------------------------------------------------------- /docs/screenshots/fox32os-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox32-arch/fox32/8b0cbe37f4fe00efb39e07d65284bdeb5daa61bb/docs/screenshots/fox32os-terminal.png -------------------------------------------------------------------------------- /src/bus.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "bus.h" 14 | #include "cpu.h" 15 | #include "disk.h" 16 | #include "framebuffer.h" 17 | #include "keyboard.h" 18 | #include "mouse.h" 19 | #include "serial.h" 20 | 21 | bool bus_requests_exit = false; 22 | 23 | extern time_t rtc_time; 24 | extern uint32_t rtc_uptime; 25 | 26 | extern fox32_vm_t vm; 27 | extern disk_controller_t disk_controller; 28 | extern mouse_t mouse; 29 | 30 | int bus_io_read(void *user, uint32_t *value, uint32_t port) { 31 | (void) user; 32 | switch (port) { 33 | #ifndef WINDOWS 34 | case 0x00000000: { // serial port 35 | *value = serial_get(); 36 | break; 37 | }; 38 | #endif 39 | 40 | case 0x80000000 ... 0x8000031F: { // overlay port 41 | uint8_t overlay_number = port & 0x000000FF; 42 | uint8_t setting = (port & 0x0000FF00) >> 8; 43 | switch (setting) { 44 | case 0x00: { 45 | // overlay position 46 | uint32_t x = overlay_get(overlay_number)->x; 47 | uint32_t y = overlay_get(overlay_number)->y; 48 | *value = (y << 16) | x; 49 | break; 50 | }; 51 | case 0x01: { 52 | // overlay size 53 | uint32_t width = overlay_get(overlay_number)->width; 54 | uint32_t height = overlay_get(overlay_number)->height; 55 | *value = (height << 16) | width; 56 | break; 57 | }; 58 | case 0x02: { 59 | // overlay framebuffer pointer 60 | *value = overlay_get(overlay_number)->pointer; 61 | break; 62 | }; 63 | case 0x03: { 64 | // overlay enable status 65 | *value = overlay_get(overlay_number)->enabled ? 1 : 0; 66 | break; 67 | }; 68 | } 69 | 70 | break; 71 | }; 72 | 73 | case 0x80000400 ... 0x80000401: { // mouse port 74 | uint8_t setting = port & 0x000000FF; 75 | switch (setting) { 76 | case 0x00: { 77 | // button states 78 | if (mouse.clicked) *value |= 0b001; 79 | if (mouse.released) *value |= 0b010; 80 | if (mouse.held) *value |= 0b100; else *value &= ~(0b100); 81 | break; 82 | }; 83 | case 0x01: { 84 | // position 85 | *value = (mouse.y << 16) | mouse.x; 86 | break; 87 | }; 88 | } 89 | 90 | break; 91 | }; 92 | 93 | case 0x80000500: { 94 | *value = (uint32_t) key_take(); 95 | 96 | break; 97 | } 98 | 99 | case 0x80000700 ... 0x80000707: { // RTC port 100 | uint8_t setting = port & 0x000000FF; 101 | struct tm *now = localtime(&rtc_time); 102 | switch (setting) { 103 | case 0x00: { // year 104 | *value = now->tm_year + 1900; 105 | break; 106 | } 107 | case 0x01: { // month 108 | *value = now->tm_mon + 1; 109 | break; 110 | } 111 | case 0x02: { // day 112 | *value = now->tm_mday; 113 | break; 114 | } 115 | case 0x03: { // hour 116 | *value = now->tm_hour; 117 | break; 118 | } 119 | case 0x04: { // minute 120 | *value = now->tm_min; 121 | break; 122 | } 123 | case 0x05: { // second 124 | *value = now->tm_sec; 125 | break; 126 | } 127 | case 0x06: { // ms since startup 128 | *value = rtc_uptime; 129 | break; 130 | } 131 | case 0x07: { // daylight savings time active 132 | *value = now->tm_isdst; 133 | break; 134 | } 135 | } 136 | 137 | break; 138 | } 139 | 140 | case 0x80001000 ... 0x80002003: { // disk controller port 141 | size_t id = port & 0xFF; 142 | uint8_t operation = (port & 0x0000F000) >> 8; 143 | switch (operation) { 144 | case 0x10: { 145 | // current insert state of specified disk id 146 | // size will be zero if disk isn't inserted 147 | *value = get_disk_size(id); 148 | break; 149 | }; 150 | case 0x20: { 151 | // current buffer pointer 152 | *value = disk_controller.buffer_pointer; 153 | break; 154 | }; 155 | } 156 | 157 | break; 158 | }; 159 | } 160 | 161 | return 0; 162 | } 163 | 164 | int bus_io_write(void *user, uint32_t value, uint32_t port) { 165 | (void) user; 166 | switch (port) { 167 | case 0x00000000: { // serial port 168 | serial_put(value); 169 | break; 170 | }; 171 | 172 | case 0x80000000 ... 0x8000031F: { // overlay port 173 | uint8_t overlay_number = (port & 0x000000FF); 174 | uint8_t setting = (port & 0x0000FF00) >> 8; 175 | switch (setting) { 176 | case 0x00: { 177 | // overlay position 178 | uint32_t x = value & 0x0000FFFF; 179 | uint32_t y = (value & 0xFFFF0000) >> 16; 180 | overlay_get(overlay_number)->x = x; 181 | overlay_get(overlay_number)->y = y; 182 | break; 183 | }; 184 | case 0x01: { 185 | // overlay size 186 | uint32_t width = value & 0x0000FFFF; 187 | uint32_t height = (value & 0xFFFF0000) >> 16; 188 | overlay_get(overlay_number)->width = width; 189 | overlay_get(overlay_number)->height = height; 190 | break; 191 | }; 192 | case 0x02: { 193 | // overlay framebuffer pointer 194 | overlay_get(overlay_number)->pointer = value; 195 | break; 196 | }; 197 | case 0x03: { 198 | // overlay enable status 199 | overlay_get(overlay_number)->enabled = value != 0; 200 | break; 201 | }; 202 | } 203 | 204 | break; 205 | }; 206 | 207 | case 0x80000400 ... 0x80000401: { // mouse port 208 | uint8_t setting = port & 0x000000FF; 209 | switch (setting) { 210 | case 0x00: { 211 | // button states 212 | mouse.clicked = value & 0b001; 213 | mouse.released = value & 0b010; 214 | mouse.held = value & 0b100; 215 | break; 216 | }; 217 | case 0x01: { 218 | // position 219 | mouse.x = value & 0x0000FFFF; 220 | mouse.y = (value & 0xFFFF0000) >> 16; 221 | break; 222 | }; 223 | } 224 | 225 | break; 226 | }; 227 | 228 | case 0x80001000 ... 0x80005003: { // disk controller port 229 | size_t id = port & 0xFF; 230 | uint8_t operation = (port & 0x0000F000) >> 8; 231 | switch (operation) { 232 | case 0x10: { 233 | // no-op 234 | break; 235 | }; 236 | case 0x20: { 237 | // set the buffer pointer 238 | disk_controller.buffer_pointer = value; 239 | break; 240 | }; 241 | case 0x30: { 242 | // read specified disk sector into memory 243 | set_disk_sector(id, value); 244 | read_disk_into_memory(id); 245 | break; 246 | }; 247 | case 0x40: { 248 | // write specified disk sector from memory 249 | set_disk_sector(id, value); 250 | write_disk_from_memory(id); 251 | break; 252 | }; 253 | case 0x50: { 254 | // remove specified disk 255 | remove_disk(id); 256 | break; 257 | }; 258 | } 259 | 260 | break; 261 | }; 262 | 263 | case 0x80010000: { // power control port 264 | if (value == 0) { 265 | bus_requests_exit = true; 266 | } 267 | }; 268 | } 269 | 270 | return 0; 271 | } 272 | 273 | void drop_file(char *filename) { 274 | int last_id = 0; 275 | for (int i = 0; i < 4; i++) { 276 | if (disk_controller.disks[i].size != 0) { 277 | last_id++; 278 | } 279 | } 280 | new_disk(filename, last_id); 281 | } 282 | -------------------------------------------------------------------------------- /src/bus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int bus_io_read(void *user, uint32_t *value, uint32_t port); 4 | int bus_io_write(void *user, uint32_t value, uint32_t port); 5 | void drop_file(char *filename); 6 | -------------------------------------------------------------------------------- /src/cpu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "cpu.h" 8 | #include "mmu.h" 9 | 10 | typedef fox32_err_t err_t; 11 | 12 | static const char *const err_messages[] = { 13 | "", 14 | "internal error", 15 | "breakpoint reached", 16 | "fault while reading memory", 17 | "fault while writing memory", 18 | "invalid opcode", 19 | "invalid condition", 20 | "invalid register", 21 | "write to immediate", 22 | "division by zero", 23 | "io read failed", 24 | "io write failed", 25 | "interrupts disabled", 26 | "error is not recoverable" 27 | }; 28 | 29 | static const char *err_tostring(err_t err) { 30 | if (err > 0 && err <= FOX32_ERR_CANTRECOVER) { 31 | return err_messages[err]; 32 | } 33 | return err_messages[FOX32_ERR_OK]; 34 | } 35 | 36 | typedef fox32_io_read_t io_read_t; 37 | typedef fox32_io_write_t io_write_t; 38 | 39 | static int io_read_default_impl(void *user, uint32_t *value, uint32_t port) { 40 | return (void) user, (void) value, (int) port; 41 | } 42 | static int io_write_default_impl(void *user, uint32_t value, uint32_t port) { 43 | if (port == 0) { 44 | putchar((int) value); 45 | fflush(stdout); 46 | } 47 | return (void) user, (int) port; 48 | } 49 | 50 | static io_read_t *const io_read_default = io_read_default_impl; 51 | static io_write_t *const io_write_default = io_write_default_impl; 52 | 53 | enum { 54 | OP_NOP = 0x00, 55 | OP_ADD = 0x01, 56 | OP_MUL = 0x02, 57 | OP_AND = 0x03, 58 | OP_SLA = 0x04, 59 | OP_SRA = 0x05, 60 | OP_BSE = 0x06, 61 | OP_CMP = 0x07, 62 | OP_JMP = 0x08, 63 | OP_RJMP = 0x09, 64 | OP_PUSH = 0x0A, 65 | OP_IN = 0x0B, 66 | OP_ISE = 0x0C, 67 | OP_MSE = 0x0D, 68 | OP_HALT = 0x10, 69 | OP_INC = 0x11, 70 | OP_OR = 0x13, 71 | OP_IMUL = 0x14, 72 | OP_SRL = 0x15, 73 | OP_BCL = 0x16, 74 | OP_MOV = 0x17, 75 | OP_CALL = 0x18, 76 | OP_RCALL = 0x19, 77 | OP_POP = 0x1A, 78 | OP_OUT = 0x1B, 79 | OP_ICL = 0x1C, 80 | OP_MCL = 0x1D, 81 | OP_BRK = 0x20, 82 | OP_SUB = 0x21, 83 | OP_DIV = 0x22, 84 | OP_XOR = 0x23, 85 | OP_ROL = 0x24, 86 | OP_ROR = 0x25, 87 | OP_BTS = 0x26, 88 | OP_MOVZ = 0x27, 89 | OP_LOOP = 0x28, 90 | OP_RLOOP = 0x29, 91 | OP_RET = 0x2A, 92 | OP_INT = 0x2C, 93 | OP_TLB = 0x2D, 94 | OP_DEC = 0x31, 95 | OP_REM = 0x32, 96 | OP_NOT = 0x33, 97 | OP_IDIV = 0x34, 98 | OP_IREM = 0x35, 99 | OP_ICMP = 0x37, 100 | OP_RTA = 0x39, 101 | OP_RETI = 0x3A, 102 | OP_FLP = 0x3D, 103 | }; 104 | 105 | enum { 106 | SZ_BYTE, 107 | SZ_HALF, 108 | SZ_WORD 109 | }; 110 | 111 | #define OP(_size, _optype) (((uint8_t) (_optype)) | (((uint8_t) (_size)) << 6)) 112 | 113 | enum { 114 | CD_ALWAYS, 115 | CD_IFZ, 116 | CD_IFNZ, 117 | CD_IFC, 118 | CD_IFNC, 119 | CD_IFGT, 120 | CD_IFLTEQ 121 | }; 122 | 123 | enum { 124 | TY_REG, 125 | TY_REGPTR, 126 | TY_IMM, 127 | TY_IMMPTR, 128 | TY_NONE 129 | }; 130 | 131 | enum { 132 | EX_DIVZERO = 256 + 0x00, 133 | EX_ILLEGAL = 256 + 0x01, 134 | EX_FAULT_RD = 256 + 0x02, 135 | EX_FAULT_WR = 256 + 0x03, 136 | EX_DEBUGGER = 256 + 0x04, 137 | EX_BUS = 256 + 0x05 138 | }; 139 | 140 | #define SIZE8 1 141 | #define SIZE16 2 142 | #define SIZE32 4 143 | 144 | static uint8_t ptr_get8(const void *ptr) { 145 | return *((const uint8_t *) ptr); 146 | } 147 | static uint16_t ptr_get16(const void *ptr) { 148 | const uint8_t *bytes = ptr; 149 | return 150 | (((uint16_t) bytes[0])) | 151 | (((uint16_t) bytes[1]) << 8); 152 | } 153 | static uint32_t ptr_get32(const void *ptr) { 154 | const uint8_t *bytes = ptr; 155 | return 156 | (((uint32_t) bytes[0])) | 157 | (((uint32_t) bytes[1]) << 8) | 158 | (((uint32_t) bytes[2]) << 16) | 159 | (((uint32_t) bytes[3]) << 24); 160 | } 161 | static void ptr_set8(void *ptr, uint8_t value) { 162 | *((uint8_t *) ptr) = value; 163 | } 164 | static void ptr_set16(void *ptr, uint16_t value) { 165 | uint8_t *bytes = ptr; 166 | bytes[0] = (uint8_t) (value); 167 | bytes[1] = (uint8_t) (value >> 8); 168 | } 169 | static void ptr_set32(void *ptr, uint32_t value) { 170 | uint8_t *bytes = ptr; 171 | bytes[0] = (uint8_t) (value); 172 | bytes[1] = (uint8_t) (value >> 8); 173 | bytes[2] = (uint8_t) (value >> 16); 174 | bytes[3] = (uint8_t) (value >> 24); 175 | } 176 | 177 | typedef struct { 178 | uint8_t opcode; 179 | uint8_t condition; 180 | uint8_t offset; 181 | uint8_t target; 182 | uint8_t source; 183 | uint8_t size; 184 | } asm_instr_t; 185 | 186 | static asm_instr_t asm_instr_from(uint16_t half) { 187 | asm_instr_t instr = { 188 | (half >> 8), 189 | (half >> 4) & 7, 190 | (half >> 7) & 1, 191 | (half >> 2) & 3, 192 | (half ) & 3, 193 | (half >> 14) 194 | }; 195 | return instr; 196 | } 197 | 198 | typedef struct { 199 | const char *const name; 200 | const unsigned int prcount; 201 | } asm_iinfo_t; 202 | 203 | static const asm_iinfo_t asm_iinfo_unknown = { "ERROR", 0 }; 204 | static const asm_iinfo_t asm_iinfos[256] = { 205 | [OP_NOP ] = { "NOP ", 0 }, 206 | [OP_ADD ] = { "ADD ", 2 }, 207 | [OP_MUL ] = { "MUL ", 2 }, 208 | [OP_AND ] = { "AND ", 2 }, 209 | [OP_SLA ] = { "SLA ", 2 }, 210 | [OP_SRA ] = { "SRA ", 2 }, 211 | [OP_BSE ] = { "BSE ", 2 }, 212 | [OP_CMP ] = { "CMP ", 2 }, 213 | [OP_JMP ] = { "JMP ", 1 }, 214 | [OP_RJMP ] = { "RJMP ", 1 }, 215 | [OP_PUSH ] = { "PUSH ", 1 }, 216 | [OP_IN ] = { "IN ", 2 }, 217 | [OP_ISE ] = { "ISE ", 0 }, 218 | [OP_MSE ] = { "MSE ", 0 }, 219 | [OP_HALT ] = { "HALT ", 0 }, 220 | [OP_INC ] = { "INC ", 1 }, 221 | [OP_OR ] = { "OR ", 2 }, 222 | [OP_IMUL ] = { "IMUL ", 2 }, 223 | [OP_SRL ] = { "SRL ", 2 }, 224 | [OP_BCL ] = { "BCL ", 2 }, 225 | [OP_MOV ] = { "MOV ", 2 }, 226 | [OP_CALL ] = { "CALL ", 1 }, 227 | [OP_RCALL] = { "RCALL", 1 }, 228 | [OP_POP ] = { "POP ", 1 }, 229 | [OP_OUT ] = { "OUT ", 2 }, 230 | [OP_ICL ] = { "ICL ", 0 }, 231 | [OP_MCL ] = { "MCL ", 0 }, 232 | [OP_BRK ] = { "BRK ", 0 }, 233 | [OP_SUB ] = { "SUB ", 2 }, 234 | [OP_DIV ] = { "DIV ", 2 }, 235 | [OP_XOR ] = { "XOR ", 2 }, 236 | [OP_ROL ] = { "ROL ", 2 }, 237 | [OP_ROR ] = { "ROR ", 2 }, 238 | [OP_BTS ] = { "BTS ", 2 }, 239 | [OP_MOVZ ] = { "MOVZ ", 2 }, 240 | [OP_LOOP ] = { "LOOP ", 1 }, 241 | [OP_RLOOP] = { "RLOOP", 1 }, 242 | [OP_RET ] = { "RET ", 0 }, 243 | [OP_INT ] = { "INT ", 1 }, 244 | [OP_TLB ] = { "TLB ", 1 }, 245 | [OP_DEC ] = { "DEC ", 1 }, 246 | [OP_REM ] = { "REM ", 2 }, 247 | [OP_NOT ] = { "NOT ", 1 }, 248 | [OP_IDIV ] = { "IDIV ", 2 }, 249 | [OP_IREM ] = { "IREM ", 2 }, 250 | [OP_ICMP ] = { "ICMP ", 2 }, 251 | [OP_RTA ] = { "RTA ", 2 }, 252 | [OP_RETI ] = { "RETI ", 0 }, 253 | [OP_FLP ] = { "FLP ", 1 } 254 | }; 255 | 256 | static const asm_iinfo_t *asm_iinfo_get(uint8_t opcode) { 257 | const asm_iinfo_t *iinfo = &asm_iinfos[opcode & 0x3F]; 258 | if (iinfo->name == NULL) { 259 | iinfo = &asm_iinfo_unknown; 260 | } 261 | return iinfo; 262 | } 263 | 264 | static uint32_t asm_disas_prsize(asm_instr_t instr, uint8_t prtype) { 265 | if (prtype < TY_IMM) return SIZE8; 266 | if (prtype < TY_IMMPTR) return SIZE32; 267 | switch (instr.size) { 268 | case SZ_BYTE: return SIZE8; 269 | case SZ_HALF: return SIZE16; 270 | case SZ_WORD: return SIZE32; 271 | } 272 | return 0; 273 | } 274 | 275 | static uint32_t asm_disas_paramssize(asm_instr_t instr, const asm_iinfo_t *iinfo) { 276 | uint32_t size = 0; 277 | if (iinfo->prcount > 0) size += asm_disas_prsize(instr, instr.source); 278 | if (iinfo->prcount > 1) size += asm_disas_prsize(instr, instr.target); 279 | return size; 280 | } 281 | 282 | static const char *const asm_disas_strssize[4] = { 283 | "BYTE", 284 | "HALF", 285 | "WORD", 286 | "????" 287 | }; 288 | 289 | static const char *const asm_disas_strscondition[8] = { 290 | " ", 291 | "IFZ ", 292 | "IFNZ ", 293 | "IFC ", 294 | "IFNC ", 295 | "IFGT ", 296 | "IFLTEQ", 297 | "??????" 298 | }; 299 | 300 | static const char *const asm_disas_strslocal[36] = { 301 | "R0 ", "R1 ", "R2 ", "R3 ", "R4 ", "R5 ", "R6 ", "R7 ", 302 | "R8 ", "R9 ", "R10", "R11", "R12", "R13", "R14", "R15", 303 | "R16", "R17", "R18", "R19", "R20", "R21", "R22", "R23", 304 | "R24", "R25", "R26", "R27", "R28", "R29", "R30", "R31", 305 | "RSP", "ESP", "RFP", "???" 306 | }; 307 | 308 | static void asm_disas_printparam(asm_instr_t instr, const uint8_t **paramsdata, char **buffer, uint8_t prtype) { 309 | int count = 0; 310 | 311 | if (prtype < TY_IMM) { 312 | uint8_t local = ptr_get8(*paramsdata); 313 | *paramsdata += SIZE8; 314 | 315 | const char* str_local = asm_disas_strslocal[35]; 316 | if (local < 35) { 317 | str_local = asm_disas_strslocal[local]; 318 | } 319 | 320 | count = sprintf(*buffer, "%s %s ", prtype == TY_REG ? "REG " : "REGPTR", str_local); 321 | } else { 322 | uint32_t value = 0; 323 | switch (instr.size) { 324 | case SZ_BYTE: value = (uint32_t) ptr_get8 (*paramsdata); *paramsdata += SIZE8 ; break; 325 | case SZ_HALF: value = (uint32_t) ptr_get16(*paramsdata); *paramsdata += SIZE16; break; 326 | case SZ_WORD: value = ptr_get32(*paramsdata); *paramsdata += SIZE32; break; 327 | } 328 | 329 | count = sprintf(*buffer, "%s %08X", prtype == TY_IMM ? "IMM " : "IMMPTR", value); 330 | } 331 | 332 | if (count > 0) { 333 | *buffer += (size_t) count; 334 | } 335 | } 336 | 337 | static void asm_disas_print(asm_instr_t instr, const asm_iinfo_t *iinfo, const uint8_t *paramsdata, char *buffer) { 338 | const char *str_size = asm_disas_strssize[3]; 339 | if (instr.size < 3) { 340 | str_size = asm_disas_strssize[instr.size]; 341 | } 342 | const char *str_condition = asm_disas_strscondition[7]; 343 | if (instr.condition < 8) { 344 | str_condition = asm_disas_strscondition[instr.condition]; 345 | } 346 | 347 | int count = sprintf(buffer, "%s %s %s", str_condition, str_size, iinfo->name); 348 | if (count > 0) { 349 | buffer += (size_t) count; 350 | } 351 | 352 | if (iinfo->prcount > 0) { 353 | *buffer++ = ' '; 354 | asm_disas_printparam(instr, ¶msdata, &buffer, instr.source); 355 | } 356 | if (iinfo->prcount > 1) { 357 | *buffer++ = ','; 358 | *buffer++ = ' '; 359 | asm_disas_printparam(instr, ¶msdata, &buffer, instr.target); 360 | } 361 | } 362 | 363 | typedef fox32_vm_t vm_t; 364 | 365 | static void vm_init(vm_t *vm) { 366 | memset(vm, 0, sizeof(vm_t)); 367 | vm->pointer_instr = FOX32_POINTER_DEFAULT_INSTR; 368 | vm->pointer_stack = FOX32_POINTER_DEFAULT_STACK; 369 | vm->halted = true; 370 | vm->soft_halted = false; 371 | vm->mmu_enabled = false; 372 | vm->io_user = NULL; 373 | vm->io_read = io_read_default; 374 | vm->io_write = io_write_default; 375 | vm->deferred_interrupt_count = 0; 376 | 377 | for (int i = 0; i < 256; i++) { 378 | vm->pending_vectors[i] = 0; 379 | } 380 | } 381 | 382 | static noreturn void vm_panic(vm_t *vm, err_t err) { 383 | longjmp(vm->panic_jmp, (vm->panic_err = err, 1)); 384 | } 385 | static noreturn void vm_unreachable(vm_t *vm) { 386 | vm_panic(vm, FOX32_ERR_INTERNAL); 387 | } 388 | 389 | static uint32_t vm_io_read(vm_t *vm, uint32_t port) { 390 | uint32_t value = 0; 391 | int status = vm->io_read(vm->io_user, &value, port); 392 | if (status != 0) { 393 | vm_panic(vm, FOX32_ERR_IOREAD); 394 | } 395 | return value; 396 | } 397 | static void vm_io_write(vm_t *vm, uint32_t port, uint32_t value) { 398 | int status = vm->io_write(vm->io_user, value, port); 399 | if (status != 0) { 400 | vm_panic(vm, FOX32_ERR_IOWRITE); 401 | } 402 | } 403 | 404 | static uint8_t vm_flags_get(vm_t *vm) { 405 | return (((uint8_t) vm->flag_swap_sp) << 3) | 406 | (((uint8_t) vm->flag_interrupt) << 2) | 407 | (((uint8_t) vm->flag_carry) << 1) | 408 | ((uint8_t) vm->flag_zero); 409 | } 410 | static void vm_flags_set(vm_t *vm, uint8_t flags) { 411 | vm->flag_zero = (flags & 1) != 0; 412 | vm->flag_carry = (flags & 2) != 0; 413 | vm->flag_interrupt = (flags & 4) != 0; 414 | vm->flag_swap_sp = (flags & 8) != 0; 415 | } 416 | 417 | static uint32_t *vm_findlocal(vm_t *vm, uint8_t local) { 418 | if (local < FOX32_REGISTER_COUNT) { 419 | return &vm->registers[local]; 420 | } 421 | if (local == FOX32_REGISTER_COUNT) { 422 | return &vm->pointer_stack; 423 | } 424 | if (local == FOX32_REGISTER_COUNT + 1) { 425 | return &vm->pointer_exception_stack; 426 | } 427 | if (local == FOX32_REGISTER_COUNT + 2) { 428 | return &vm->pointer_frame; 429 | } 430 | vm_panic(vm, FOX32_ERR_BADREGISTER); 431 | } 432 | 433 | static uint8_t *vm_findmemory_phys(vm_t *vm, uint32_t address, uint32_t size, bool write) { 434 | uint32_t address_end = address + size; 435 | 436 | if (address_end > address) { 437 | if (address_end <= FOX32_MEMORY_RAM) { 438 | return &vm->memory_ram[address]; 439 | } 440 | if ( 441 | !write && 442 | (address >= FOX32_MEMORY_ROM_START) && 443 | (address -= FOX32_MEMORY_ROM_START) + size <= FOX32_MEMORY_ROM 444 | ) { 445 | return &vm->memory_rom[address]; 446 | } 447 | } 448 | if (!write) { 449 | vm->exception_operand = address; 450 | vm_panic(vm, FOX32_ERR_FAULT_RD); 451 | } else { 452 | vm->exception_operand = address; 453 | vm_panic(vm, FOX32_ERR_FAULT_WR); 454 | } 455 | } 456 | 457 | static uint8_t *vm_findmemory(vm_t *vm, uint32_t address, uint32_t size, bool write) { 458 | if (!vm->mmu_enabled) { 459 | return vm_findmemory_phys(vm, address, size, write); 460 | } else { 461 | mmu_page_t *virtual_page = get_present_page(address); 462 | if (virtual_page == NULL) { 463 | if (!write) { 464 | vm->exception_operand = address; 465 | vm_panic(vm, FOX32_ERR_FAULT_RD); 466 | } else { 467 | vm->exception_operand = address; 468 | vm_panic(vm, FOX32_ERR_FAULT_WR); 469 | } 470 | } 471 | if (!virtual_page->rw && write) { 472 | vm->exception_operand = address; 473 | vm_panic(vm, FOX32_ERR_FAULT_WR); 474 | } 475 | uint32_t offset = address & 0x00000FFF; 476 | uint32_t physical_address = virtual_page->physical_address | offset; 477 | 478 | return vm_findmemory_phys(vm, physical_address, size, write); 479 | } 480 | } 481 | 482 | static uint32_t vm_read_across(vm_t *vm, uint32_t address, int size) { 483 | uint32_t result = 0; 484 | 485 | int shift = 0; 486 | 487 | // read the first page 488 | 489 | int bytes = 0x1000 - (address&0xFFF); 490 | uint8_t *ptr = vm_findmemory(vm, address, bytes, false); 491 | 492 | while (bytes) { 493 | result |= (*ptr<>= 8; 536 | 537 | ptr_first++; 538 | bytes_first--; 539 | } 540 | 541 | // write the second page 542 | 543 | while (bytes_second) { 544 | *ptr_second = value & 0xFF; 545 | value >>= 8; 546 | 547 | ptr_second++; 548 | bytes_second--; 549 | } 550 | } 551 | 552 | #define VM_READ_BODY(_ptr_get, _size) \ 553 | if ((address & 0xFFFFF000) == ((address + _size - 1) & 0xFFFFF000)) { \ 554 | return _ptr_get(vm_findmemory(vm, address, _size, false)); \ 555 | } else { \ 556 | return vm_read_across(vm, address, _size); \ 557 | } 558 | 559 | static uint8_t vm_read8(vm_t *vm, uint32_t address) { 560 | VM_READ_BODY(ptr_get8, SIZE8) 561 | } 562 | static uint16_t vm_read16(vm_t *vm, uint32_t address) { 563 | VM_READ_BODY(ptr_get16, SIZE16) 564 | } 565 | static uint32_t vm_read32(vm_t *vm, uint32_t address) { 566 | VM_READ_BODY(ptr_get32, SIZE32) 567 | } 568 | 569 | #define VM_WRITE_BODY(_ptr_set, _size) \ 570 | if ((address & 0xFFFFF000) == ((address + _size - 1) & 0xFFFFF000)) { \ 571 | return _ptr_set(vm_findmemory(vm, address, _size, true), value); \ 572 | } else { \ 573 | return vm_write_across(vm, address, _size, value); \ 574 | } 575 | 576 | 577 | static void vm_write8(vm_t *vm, uint32_t address, uint8_t value) { 578 | VM_WRITE_BODY(ptr_set8, SIZE8) 579 | } 580 | static void vm_write16(vm_t *vm, uint32_t address, uint16_t value) { 581 | VM_WRITE_BODY(ptr_set16, SIZE16) 582 | } 583 | static void vm_write32(vm_t *vm, uint32_t address, uint32_t value) { 584 | VM_WRITE_BODY(ptr_set32, SIZE32) 585 | } 586 | 587 | #define VM_PUSH_BODY(_vm_write, _size) \ 588 | _vm_write(vm, vm->pointer_stack - _size, value); \ 589 | vm->pointer_stack -= _size; 590 | 591 | static void vm_push8(vm_t *vm, uint8_t value) { 592 | VM_PUSH_BODY(vm_write8, SIZE8) 593 | } 594 | static void vm_push16(vm_t *vm, uint16_t value) { 595 | VM_PUSH_BODY(vm_write16, SIZE16) 596 | } 597 | static void vm_push32(vm_t *vm, uint32_t value) { 598 | VM_PUSH_BODY(vm_write32, SIZE32) 599 | } 600 | 601 | #define VM_POP_BODY(_vm_read, _size) \ 602 | uint32_t result = _vm_read(vm, vm->pointer_stack); \ 603 | vm->pointer_stack += _size; \ 604 | return result; 605 | 606 | static uint8_t vm_pop8(vm_t *vm) { 607 | VM_POP_BODY(vm_read8, SIZE8) 608 | } 609 | static uint16_t vm_pop16(vm_t *vm) { 610 | VM_POP_BODY(vm_read16, SIZE16) 611 | } 612 | static uint32_t vm_pop32(vm_t *vm) { 613 | VM_POP_BODY(vm_read32, SIZE32) 614 | } 615 | 616 | #define VM_SOURCE_BODY(_vm_read, _size, _type, _move, _offset) \ 617 | uint32_t pointer_base = vm->pointer_instr_mut; \ 618 | switch (prtype) { \ 619 | case TY_REG: { \ 620 | if (_move) vm->pointer_instr_mut += SIZE8; \ 621 | return (_type) *vm_findlocal(vm, vm_read8(vm, pointer_base)); \ 622 | }; \ 623 | case TY_REGPTR: { \ 624 | if (_move) vm->pointer_instr_mut += SIZE8+_offset; \ 625 | return _vm_read(vm, *vm_findlocal(vm, vm_read8(vm, pointer_base)) \ 626 | +(_offset ? vm_read8(vm, pointer_base + 1) : 0)); \ 627 | }; \ 628 | case TY_IMM: { \ 629 | if (_move) vm->pointer_instr_mut += _size; \ 630 | return _vm_read(vm, pointer_base); \ 631 | }; \ 632 | case TY_IMMPTR: { \ 633 | if (_move) vm->pointer_instr_mut += SIZE32; \ 634 | return _vm_read(vm, vm_read32(vm, pointer_base)); \ 635 | }; \ 636 | } \ 637 | vm_unreachable(vm); 638 | 639 | static uint8_t vm_source8(vm_t *vm, uint8_t prtype, uint8_t offset) { 640 | VM_SOURCE_BODY(vm_read8, SIZE8, uint8_t, true, offset) 641 | } 642 | static uint8_t vm_source8_stay(vm_t *vm, uint8_t prtype, uint8_t offset) { 643 | VM_SOURCE_BODY(vm_read8, SIZE8, uint8_t, false, offset) 644 | } 645 | static uint16_t vm_source16(vm_t *vm, uint8_t prtype, uint8_t offset) { 646 | VM_SOURCE_BODY(vm_read16, SIZE16, uint16_t, true, offset) 647 | } 648 | static uint16_t vm_source16_stay(vm_t *vm, uint8_t prtype, uint8_t offset) { 649 | VM_SOURCE_BODY(vm_read16, SIZE16, uint16_t, false, offset) 650 | } 651 | static uint32_t vm_source32(vm_t *vm, uint8_t prtype, uint8_t offset) { 652 | VM_SOURCE_BODY(vm_read32, SIZE32, uint32_t, true, offset) 653 | } 654 | static uint32_t vm_source32_stay(vm_t *vm, uint8_t prtype, uint8_t offset) { 655 | VM_SOURCE_BODY(vm_read32, SIZE32, uint32_t, false, offset) 656 | } 657 | 658 | #define VM_TARGET_BODY(_vm_write, _localvalue, _offset) \ 659 | uint32_t pointer_base = vm->pointer_instr_mut; \ 660 | switch (prtype) { \ 661 | case TY_REG: { \ 662 | vm->pointer_instr_mut += SIZE8; \ 663 | uint8_t local = vm_read8(vm, pointer_base); \ 664 | *vm_findlocal(vm, local) = _localvalue; \ 665 | return; \ 666 | }; \ 667 | case TY_REGPTR: { \ 668 | vm->pointer_instr_mut += SIZE8+_offset; \ 669 | _vm_write(vm, ( _offset ? vm_read8(vm, pointer_base + 1) : 0) + \ 670 | *vm_findlocal(vm, vm_read8(vm, pointer_base)), value); \ 671 | return; \ 672 | }; \ 673 | case TY_IMM: { \ 674 | vm_panic(vm, FOX32_ERR_BADIMMEDIATE); \ 675 | return; \ 676 | }; \ 677 | case TY_IMMPTR: { \ 678 | vm->pointer_instr_mut += SIZE32; \ 679 | _vm_write(vm, vm_read32(vm, pointer_base), value); \ 680 | return; \ 681 | }; \ 682 | }; \ 683 | vm_unreachable(vm); 684 | 685 | static void vm_target8(vm_t *vm, uint8_t prtype, uint8_t value, uint8_t offset) { 686 | VM_TARGET_BODY(vm_write8, (*vm_findlocal(vm, local) & 0xFFFFFF00) | (uint32_t) value, offset) 687 | } 688 | static void vm_target8_zero(vm_t *vm, uint8_t prtype, uint8_t value, uint8_t offset) { 689 | VM_TARGET_BODY(vm_write32, (uint32_t) value, offset) 690 | } 691 | static void vm_target16(vm_t *vm, uint8_t prtype, uint16_t value, uint8_t offset) { 692 | VM_TARGET_BODY(vm_write16, (*vm_findlocal(vm, local) & 0xFFFF0000) | (uint32_t) value, offset) 693 | } 694 | static void vm_target16_zero(vm_t *vm, uint8_t prtype, uint16_t value, uint8_t offset) { 695 | VM_TARGET_BODY(vm_write32, (uint32_t) value, offset) 696 | } 697 | static void vm_target32(vm_t *vm, uint8_t prtype, uint32_t value, uint8_t offset) { 698 | VM_TARGET_BODY(vm_write32, value, offset) 699 | } 700 | 701 | static bool vm_shouldskip(vm_t *vm, uint8_t condition) { 702 | switch (condition) { 703 | case CD_ALWAYS: { 704 | return false; 705 | }; 706 | case CD_IFZ: { 707 | return vm->flag_zero == false; 708 | }; 709 | case CD_IFNZ: { 710 | return vm->flag_zero == true; 711 | }; 712 | case CD_IFC: { 713 | return vm->flag_carry == false; 714 | }; 715 | case CD_IFNC: { 716 | return vm->flag_carry == true; 717 | }; 718 | case CD_IFGT: { 719 | return (vm->flag_zero == true) || (vm->flag_carry == true); 720 | }; 721 | case CD_IFLTEQ: { 722 | return (vm->flag_zero == false) && (vm->flag_carry == false); 723 | }; 724 | } 725 | vm_panic(vm, FOX32_ERR_BADCONDITION); 726 | } 727 | 728 | static void vm_skipparam(vm_t *vm, uint32_t size, uint8_t prtype, uint8_t offset) { 729 | if (prtype < TY_IMM) { 730 | vm->pointer_instr_mut += SIZE8; 731 | if (offset && prtype==TY_REGPTR) 732 | vm->pointer_instr_mut += SIZE8; 733 | } else if (prtype == TY_IMMPTR) { 734 | vm->pointer_instr_mut += SIZE32; 735 | } else { 736 | vm->pointer_instr_mut += size; 737 | } 738 | } 739 | 740 | #define CHECKED_ADD(_a, _b, _out) __builtin_add_overflow(_a, _b, _out) 741 | #define CHECKED_SUB(_a, _b, _out) __builtin_sub_overflow(_a, _b, _out) 742 | #define CHECKED_MUL(_a, _b, _out) __builtin_mul_overflow(_a, _b, _out) 743 | 744 | #define OPER_DIV(_a, _b) ((_a) / (_b)) 745 | #define OPER_REM(_a, _b) ((_a) % (_b)) 746 | #define OPER_AND(_a, _b) ((_a) & (_b)) 747 | #define OPER_XOR(_a, _b) ((_a) ^ (_b)) 748 | #define OPER_OR(_a, _b) ((_a) | (_b)) 749 | #define OPER_SHIFT_LEFT(_a, _b) ((_a) << (_b)) 750 | #define OPER_SHIFT_RIGHT(_a, _b) ((_a) >> (_b)) 751 | #define OPER_BIT_SET(_a, _b) ((_a) | (1 << (_b))) 752 | #define OPER_BIT_CLEAR(_a, _b) ((_a) & ~(1 << (_b))) 753 | 754 | #define ROTATE_LEFT(_size, _a, _b) (((_a) << (_b)) | ((_a) >> (((_size) * 8) - (_b)))) 755 | #define ROTATE_LEFT8(_a, _b) ROTATE_LEFT(SIZE8, _a, _b) 756 | #define ROTATE_LEFT16(_a, _b) ROTATE_LEFT(SIZE16, _a, _b) 757 | #define ROTATE_LEFT32(_a, _b) ROTATE_LEFT(SIZE32, _a, _b) 758 | #define ROTATE_RIGHT(_size, _a, _b) (((_a) >> (_b)) | ((_a) << (((_size) * 8) - (_b)))) 759 | #define ROTATE_RIGHT8(_a, _b) ROTATE_RIGHT(SIZE8, _a, _b) 760 | #define ROTATE_RIGHT16(_a, _b) ROTATE_RIGHT(SIZE16, _a, _b) 761 | #define ROTATE_RIGHT32(_a, _b) ROTATE_RIGHT(SIZE32, _a, _b) 762 | 763 | #define SOURCEMAP_IDENTITY(x) (x) 764 | #define SOURCEMAP_RELATIVE(x) (instr_base + (x)) 765 | 766 | #define VM_PRELUDE_0() { \ 767 | if (vm_shouldskip(vm, instr.condition)) { \ 768 | break; \ 769 | } \ 770 | } 771 | #define VM_PRELUDE_1(_size) { \ 772 | if (vm_shouldskip(vm, instr.condition)) { \ 773 | vm_skipparam(vm, _size, instr.source, instr.offset); \ 774 | break; \ 775 | } \ 776 | } 777 | #define VM_PRELUDE_2(_size) { \ 778 | if (vm_shouldskip(vm, instr.condition)) { \ 779 | vm_skipparam(vm, _size, instr.target, instr.offset); \ 780 | vm_skipparam(vm, _size, instr.source, instr.offset); \ 781 | break; \ 782 | } \ 783 | } 784 | #define VM_PRELUDE_BIT(_size) { \ 785 | if (vm_shouldskip(vm, instr.condition)) { \ 786 | vm_skipparam(vm, _size, instr.target, instr.offset); \ 787 | vm_skipparam(vm, SIZE8, instr.source, instr.offset); \ 788 | break; \ 789 | } \ 790 | } 791 | 792 | #define VM_IMPL_JMP(_size, _sourcemap) { \ 793 | VM_PRELUDE_1(_size); \ 794 | switch (_size) { \ 795 | case SIZE8: vm->pointer_instr_mut = _sourcemap((int8_t)vm_source8(vm, instr.source, instr.offset)); break; \ 796 | case SIZE16: vm->pointer_instr_mut = _sourcemap((int16_t)vm_source16(vm, instr.source, instr.offset)); break; \ 797 | default: vm->pointer_instr_mut = _sourcemap(vm_source32(vm, instr.source, instr.offset)); break; \ 798 | } \ 799 | break; \ 800 | } 801 | 802 | #define VM_IMPL_LOOP(_size, _sourcemap) { \ 803 | if ( \ 804 | !vm_shouldskip(vm, instr.condition) && \ 805 | (vm->registers[FOX32_REGISTER_LOOP] -= 1) != 0 \ 806 | ) { \ 807 | switch (_size) { \ 808 | case SIZE8: vm->pointer_instr_mut = _sourcemap((int8_t)vm_source8(vm, instr.source, instr.offset)); break; \ 809 | case SIZE16: vm->pointer_instr_mut = _sourcemap((int16_t)vm_source16(vm, instr.source, instr.offset)); break; \ 810 | default: vm->pointer_instr_mut = _sourcemap(vm_source32(vm, instr.source, instr.offset)); break; \ 811 | } \ 812 | } else { \ 813 | vm_skipparam(vm, _size, instr.source, instr.offset); \ 814 | } \ 815 | break; \ 816 | } 817 | 818 | #define VM_IMPL_CALL(_size, _sourcemap) { \ 819 | VM_PRELUDE_1(_size); \ 820 | uint32_t pointer_call; \ 821 | switch (_size) { \ 822 | case SIZE8: pointer_call = (int8_t)vm_source8(vm, instr.source, instr.offset); break; \ 823 | case SIZE16: pointer_call = (int16_t)vm_source16(vm, instr.source, instr.offset); break; \ 824 | default: pointer_call = vm_source32(vm, instr.source, instr.offset); break; \ 825 | } \ 826 | vm_push32(vm, vm->pointer_instr_mut); \ 827 | switch (_size) { \ 828 | case SIZE8: vm->pointer_instr_mut = _sourcemap((int8_t)pointer_call); break; \ 829 | case SIZE16: vm->pointer_instr_mut = _sourcemap((int16_t)pointer_call); break; \ 830 | default: vm->pointer_instr_mut = _sourcemap(pointer_call); break; \ 831 | } \ 832 | break; \ 833 | } 834 | 835 | // make sure NOT to update the stack pointer until the full instruction has 836 | // been read, and the target has been written. otherwise a pagefault halfway 837 | // through could wreak havoc. 838 | 839 | #define VM_IMPL_POP(_size, _vm_target, _vm_pop) { \ 840 | VM_PRELUDE_1(_size); \ 841 | uint32_t oldsp = vm->pointer_stack; \ 842 | uint32_t val = _vm_pop(vm); \ 843 | uint32_t newsp = vm->pointer_stack; \ 844 | vm->pointer_stack = oldsp; \ 845 | _vm_target(vm, instr.source, val, instr.offset); \ 846 | vm->pointer_stack = newsp; \ 847 | break; \ 848 | } 849 | 850 | #define VM_IMPL_PUSH(_size, _vm_source, _vm_push) { \ 851 | VM_PRELUDE_1(_size); \ 852 | _vm_push(vm, _vm_source(vm, instr.source, instr.offset)); \ 853 | break; \ 854 | } 855 | 856 | #define VM_IMPL_MOV(_size, _vm_source, _vm_target) { \ 857 | VM_PRELUDE_2(_size); \ 858 | _vm_target(vm, instr.target, _vm_source(vm, instr.source, instr.offset), instr.offset); \ 859 | break; \ 860 | } 861 | 862 | #define VM_IMPL_NOT(_size, _type, _vm_source_stay, _vm_target) { \ 863 | VM_PRELUDE_1(_size); \ 864 | _type v = _vm_source_stay(vm, instr.source, instr.offset); \ 865 | _type x = ~v; \ 866 | _vm_target(vm, instr.source, x, instr.offset); \ 867 | vm->flag_zero = x == 0; \ 868 | break; \ 869 | } 870 | 871 | #define VM_IMPL_INC(_size, _type, _vm_source_stay, _vm_target, _oper) { \ 872 | VM_PRELUDE_1(_size); \ 873 | _type v = _vm_source_stay(vm, instr.source, instr.offset); \ 874 | _type x; \ 875 | bool carry = _oper(v, 1 << instr.target, &x); \ 876 | _vm_target(vm, instr.source, x, instr.offset); \ 877 | vm->flag_carry = carry; \ 878 | vm->flag_zero = x == 0; \ 879 | break; \ 880 | } 881 | 882 | #define VM_IMPL_ADD(_size, _type, _type_target, _vm_source, _vm_source_stay, _vm_target, _oper) { \ 883 | VM_PRELUDE_2(_size); \ 884 | _type a = (_type) _vm_source(vm, instr.source, instr.offset); \ 885 | _type b = (_type) _vm_source_stay(vm, instr.target, instr.offset); \ 886 | _type x; \ 887 | bool carry = _oper(b, a, &x); \ 888 | _vm_target(vm, instr.target, (_type_target) x, instr.offset); \ 889 | vm->flag_carry = carry; \ 890 | vm->flag_zero = x == 0; \ 891 | break; \ 892 | } 893 | 894 | #define VM_IMPL_AND(_size, _type, _type_target, _vm_source, _vm_source_stay, _vm_target, _oper) { \ 895 | VM_PRELUDE_2(_size); \ 896 | _type a = (_type) _vm_source(vm, instr.source, instr.offset); \ 897 | _type b = (_type) _vm_source_stay(vm, instr.target, instr.offset); \ 898 | _type x = _oper(b, a); \ 899 | _vm_target(vm, instr.target, (_type_target) x, instr.offset); \ 900 | vm->flag_zero = x == 0; \ 901 | break; \ 902 | } 903 | 904 | #define VM_IMPL_SHIFT(_size, _type, _type_target, _vm_source, _vm_source_stay, _vm_target, _oper){\ 905 | VM_PRELUDE_BIT(_size); \ 906 | _type a = (_type) vm_source8(vm, instr.source, instr.offset); \ 907 | _type b = (_type) _vm_source_stay(vm, instr.target, instr.offset); \ 908 | _type x = _oper(b, a); \ 909 | _vm_target(vm, instr.target, (_type_target) x, instr.offset); \ 910 | vm->flag_zero = x == 0; \ 911 | break; \ 912 | } 913 | 914 | #define VM_IMPL_DIV(_size, _type, _type_target, _vm_source, _vm_source_stay, _vm_target, _oper) { \ 915 | VM_PRELUDE_2(_size); \ 916 | _type a = (_type) _vm_source(vm, instr.source, instr.offset); \ 917 | _type b = (_type) _vm_source_stay(vm, instr.target, instr.offset); \ 918 | if (a == 0) { \ 919 | vm_panic(vm, FOX32_ERR_DIVZERO); \ 920 | break; \ 921 | } \ 922 | _type x = _oper(b, a); \ 923 | _vm_target(vm, instr.target, (_type_target) x, instr.offset); \ 924 | vm->flag_zero = x == 0; \ 925 | break; \ 926 | } 927 | 928 | #define VM_IMPL_CMP(_size, _type, _vm_source) { \ 929 | VM_PRELUDE_2(_size); \ 930 | _type a = _vm_source(vm, instr.source, instr.offset); \ 931 | _type b = _vm_source(vm, instr.target, instr.offset); \ 932 | vm->flag_carry = a > b; \ 933 | vm->flag_zero = a == b; \ 934 | break; \ 935 | } 936 | 937 | #define VM_IMPL_BTS(_size, _type, _vm_source) { \ 938 | VM_PRELUDE_BIT(_size); \ 939 | _type a = vm_source8(vm, instr.source, instr.offset); \ 940 | _type b = _vm_source(vm, instr.target, instr.offset); \ 941 | _type x = b & (1 << a); \ 942 | vm->flag_zero = x == 0; \ 943 | break; \ 944 | } 945 | 946 | static void vm_debug(vm_t *vm, asm_instr_t instr, uint32_t ip, uint32_t sp) { 947 | const asm_iinfo_t *iinfo = asm_iinfo_get(instr.opcode); 948 | 949 | uint32_t params_size = asm_disas_paramssize(instr, iinfo); 950 | uint8_t *params_data = NULL; 951 | if (params_size > 0) { 952 | params_data = vm_findmemory(vm, ip + SIZE16, params_size, false); 953 | } 954 | 955 | char buffer[128] = {}; 956 | asm_disas_print(instr, iinfo, params_data, buffer); 957 | 958 | printf("SP=%08X IP=%08X %s\n", sp, ip, buffer); 959 | } 960 | 961 | static void vm_execute(vm_t *vm) { 962 | uint32_t instr_base = vm->pointer_instr; 963 | uint16_t instr_raw = vm_read16(vm, instr_base); 964 | 965 | asm_instr_t instr = asm_instr_from(instr_raw); 966 | 967 | vm->pointer_instr_mut = instr_base + SIZE16; 968 | 969 | if (vm->debug) vm_debug(vm, instr, instr_base, vm->pointer_stack); 970 | 971 | switch (instr.opcode) { 972 | case OP(SZ_BYTE, OP_NOP): 973 | case OP(SZ_HALF, OP_NOP): 974 | case OP(SZ_WORD, OP_NOP): { 975 | break; 976 | }; 977 | 978 | case OP(SZ_BYTE, OP_HALT): 979 | case OP(SZ_HALF, OP_HALT): 980 | case OP(SZ_WORD, OP_HALT): { 981 | VM_PRELUDE_0(); 982 | vm->soft_halted = true; 983 | break; 984 | }; 985 | 986 | case OP(SZ_BYTE, OP_BRK): 987 | case OP(SZ_HALF, OP_BRK): 988 | case OP(SZ_WORD, OP_BRK): { 989 | VM_PRELUDE_0(); 990 | vm->pointer_instr = vm->pointer_instr_mut; 991 | vm_panic(vm, FOX32_ERR_DEBUGGER); 992 | break; 993 | }; 994 | 995 | case OP(SZ_WORD, OP_IN): { 996 | VM_PRELUDE_2(SIZE32); 997 | vm_target32(vm, instr.target, vm_io_read(vm, vm_source32(vm, instr.source, 0)), instr.offset); 998 | break; 999 | }; 1000 | case OP(SZ_WORD, OP_OUT): { 1001 | VM_PRELUDE_2(SIZE32); 1002 | uint32_t value = vm_source32(vm, instr.source, instr.offset); 1003 | uint32_t port = vm_source32(vm, instr.target, instr.offset); 1004 | vm_io_write(vm, port, value); 1005 | break; 1006 | }; 1007 | 1008 | case OP(SZ_BYTE, OP_RTA): { 1009 | VM_PRELUDE_2(SIZE8); 1010 | vm_target32(vm, instr.target, instr_base + (int8_t)vm_source8(vm, instr.source, instr.offset), instr.offset); 1011 | break; 1012 | }; 1013 | case OP(SZ_HALF, OP_RTA): { 1014 | VM_PRELUDE_2(SIZE16); 1015 | vm_target32(vm, instr.target, instr_base + (int16_t)vm_source16(vm, instr.source, instr.offset), instr.offset); 1016 | break; 1017 | }; 1018 | case OP(SZ_WORD, OP_RTA): { 1019 | VM_PRELUDE_2(SIZE32); 1020 | vm_target32(vm, instr.target, instr_base + vm_source32(vm, instr.source, instr.offset), instr.offset); 1021 | break; 1022 | }; 1023 | 1024 | case OP(SZ_WORD, OP_RET): { 1025 | VM_PRELUDE_0(); 1026 | vm->pointer_instr_mut = vm_pop32(vm); 1027 | break; 1028 | }; 1029 | case OP(SZ_WORD, OP_RETI): { 1030 | VM_PRELUDE_0(); 1031 | vm_flags_set(vm, vm_pop8(vm)); 1032 | vm->pointer_instr_mut = vm_pop32(vm); 1033 | if (vm->flag_swap_sp) { 1034 | vm->pointer_stack = vm_pop32(vm); 1035 | } 1036 | if (vm->flag_interrupt && vm->deferred_interrupt_count != 0) { 1037 | for (int i = 0; i < 256; i++) { 1038 | if (vm->pending_vectors[i]) { 1039 | vm->deferred_interrupt_count--; 1040 | vm->pending_vectors[i] = 0; 1041 | vm->pointer_instr = vm->pointer_instr_mut; 1042 | fox32_raise(vm, i); 1043 | vm->pointer_instr_mut = vm->pointer_instr; 1044 | break; 1045 | } 1046 | } 1047 | } 1048 | break; 1049 | }; 1050 | 1051 | case OP(SZ_WORD, OP_ISE): { 1052 | VM_PRELUDE_0(); 1053 | vm->flag_interrupt = true; 1054 | if (vm->deferred_interrupt_count != 0) { 1055 | for (int i = 0; i < 256; i++) { 1056 | if (vm->pending_vectors[i]) { 1057 | vm->deferred_interrupt_count--; 1058 | vm->pending_vectors[i] = 0; 1059 | vm->pointer_instr = vm->pointer_instr_mut; 1060 | fox32_raise(vm, i); 1061 | vm->pointer_instr_mut = vm->pointer_instr; 1062 | break; 1063 | } 1064 | } 1065 | } 1066 | break; 1067 | }; 1068 | case OP(SZ_WORD, OP_ICL): { 1069 | VM_PRELUDE_0(); 1070 | vm->flag_interrupt = false; 1071 | break; 1072 | }; 1073 | 1074 | case OP(SZ_WORD, OP_JMP): VM_IMPL_JMP(SIZE32, SOURCEMAP_IDENTITY); 1075 | case OP(SZ_WORD, OP_CALL): VM_IMPL_CALL(SIZE32, SOURCEMAP_IDENTITY); 1076 | case OP(SZ_WORD, OP_LOOP): VM_IMPL_LOOP(SIZE32, SOURCEMAP_IDENTITY); 1077 | 1078 | case OP(SZ_BYTE, OP_RJMP): VM_IMPL_JMP(SIZE8, SOURCEMAP_RELATIVE); 1079 | case OP(SZ_BYTE, OP_RCALL): VM_IMPL_CALL(SIZE8, SOURCEMAP_RELATIVE); 1080 | case OP(SZ_BYTE, OP_RLOOP): VM_IMPL_LOOP(SIZE8, SOURCEMAP_RELATIVE); 1081 | case OP(SZ_HALF, OP_RJMP): VM_IMPL_JMP(SIZE16, SOURCEMAP_RELATIVE); 1082 | case OP(SZ_HALF, OP_RCALL): VM_IMPL_CALL(SIZE16, SOURCEMAP_RELATIVE); 1083 | case OP(SZ_HALF, OP_RLOOP): VM_IMPL_LOOP(SIZE16, SOURCEMAP_RELATIVE); 1084 | case OP(SZ_WORD, OP_RJMP): VM_IMPL_JMP(SIZE32, SOURCEMAP_RELATIVE); 1085 | case OP(SZ_WORD, OP_RCALL): VM_IMPL_CALL(SIZE32, SOURCEMAP_RELATIVE); 1086 | case OP(SZ_WORD, OP_RLOOP): VM_IMPL_LOOP(SIZE32, SOURCEMAP_RELATIVE); 1087 | 1088 | case OP(SZ_BYTE, OP_POP): VM_IMPL_POP(SIZE8, vm_target8, vm_pop8); 1089 | case OP(SZ_HALF, OP_POP): VM_IMPL_POP(SIZE16, vm_target16, vm_pop16); 1090 | case OP(SZ_WORD, OP_POP): VM_IMPL_POP(SIZE32, vm_target32, vm_pop32); 1091 | 1092 | case OP(SZ_BYTE, OP_PUSH): VM_IMPL_PUSH(SIZE8, vm_source8, vm_push8); 1093 | case OP(SZ_HALF, OP_PUSH): VM_IMPL_PUSH(SIZE16, vm_source16, vm_push16); 1094 | case OP(SZ_WORD, OP_PUSH): VM_IMPL_PUSH(SIZE32, vm_source32, vm_push32); 1095 | 1096 | case OP(SZ_BYTE, OP_MOV): VM_IMPL_MOV(SIZE8, vm_source8, vm_target8); 1097 | case OP(SZ_BYTE, OP_MOVZ): VM_IMPL_MOV(SIZE8, vm_source8, vm_target8_zero); 1098 | case OP(SZ_HALF, OP_MOV): VM_IMPL_MOV(SIZE16, vm_source16, vm_target16); 1099 | case OP(SZ_HALF, OP_MOVZ): VM_IMPL_MOV(SIZE16, vm_source16, vm_target16_zero); 1100 | case OP(SZ_WORD, OP_MOV): 1101 | case OP(SZ_WORD, OP_MOVZ): VM_IMPL_MOV(SIZE32, vm_source32, vm_target32); 1102 | 1103 | case OP(SZ_BYTE, OP_NOT): VM_IMPL_NOT(SIZE8, uint8_t, vm_source8_stay, vm_target8); 1104 | case OP(SZ_HALF, OP_NOT): VM_IMPL_NOT(SIZE16, uint16_t, vm_source16_stay, vm_target16); 1105 | case OP(SZ_WORD, OP_NOT): VM_IMPL_NOT(SIZE32, uint32_t, vm_source32_stay, vm_target32); 1106 | 1107 | case OP(SZ_BYTE, OP_INC): VM_IMPL_INC(SIZE8, uint8_t, vm_source8_stay, vm_target8, CHECKED_ADD); 1108 | case OP(SZ_HALF, OP_INC): VM_IMPL_INC(SIZE16, uint16_t, vm_source16_stay, vm_target16, CHECKED_ADD); 1109 | case OP(SZ_WORD, OP_INC): VM_IMPL_INC(SIZE32, uint32_t, vm_source32_stay, vm_target32, CHECKED_ADD); 1110 | case OP(SZ_BYTE, OP_DEC): VM_IMPL_INC(SIZE8, uint8_t, vm_source8_stay, vm_target8, CHECKED_SUB); 1111 | case OP(SZ_HALF, OP_DEC): VM_IMPL_INC(SIZE16, uint16_t, vm_source16_stay, vm_target16, CHECKED_SUB); 1112 | case OP(SZ_WORD, OP_DEC): VM_IMPL_INC(SIZE32, uint32_t, vm_source32_stay, vm_target32, CHECKED_SUB); 1113 | 1114 | case OP(SZ_BYTE, OP_ADD): VM_IMPL_ADD(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, CHECKED_ADD); 1115 | case OP(SZ_HALF, OP_ADD): VM_IMPL_ADD(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, CHECKED_ADD); 1116 | case OP(SZ_WORD, OP_ADD): VM_IMPL_ADD(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, CHECKED_ADD); 1117 | case OP(SZ_BYTE, OP_SUB): VM_IMPL_ADD(SIZE8, uint8_t, uint8_t ,vm_source8, vm_source8_stay, vm_target8, CHECKED_SUB); 1118 | case OP(SZ_HALF, OP_SUB): VM_IMPL_ADD(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, CHECKED_SUB); 1119 | case OP(SZ_WORD, OP_SUB): VM_IMPL_ADD(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, CHECKED_SUB); 1120 | case OP(SZ_BYTE, OP_MUL): VM_IMPL_ADD(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, CHECKED_MUL); 1121 | case OP(SZ_HALF, OP_MUL): VM_IMPL_ADD(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, CHECKED_MUL); 1122 | case OP(SZ_WORD, OP_MUL): VM_IMPL_ADD(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, CHECKED_MUL); 1123 | case OP(SZ_BYTE, OP_IMUL): VM_IMPL_ADD(SIZE8, int8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, CHECKED_MUL); 1124 | case OP(SZ_HALF, OP_IMUL): VM_IMPL_ADD(SIZE16, int16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, CHECKED_MUL); 1125 | case OP(SZ_WORD, OP_IMUL): VM_IMPL_ADD(SIZE32, int32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, CHECKED_MUL); 1126 | 1127 | case OP(SZ_BYTE, OP_DIV): VM_IMPL_DIV(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_DIV); 1128 | case OP(SZ_HALF, OP_DIV): VM_IMPL_DIV(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_DIV); 1129 | case OP(SZ_WORD, OP_DIV): VM_IMPL_DIV(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_DIV); 1130 | case OP(SZ_BYTE, OP_REM): VM_IMPL_DIV(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_REM); 1131 | case OP(SZ_HALF, OP_REM): VM_IMPL_DIV(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_REM); 1132 | case OP(SZ_WORD, OP_REM): VM_IMPL_DIV(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_REM); 1133 | case OP(SZ_BYTE, OP_IDIV): VM_IMPL_DIV(SIZE8, int8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_DIV); 1134 | case OP(SZ_HALF, OP_IDIV): VM_IMPL_DIV(SIZE16, int16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_DIV); 1135 | case OP(SZ_WORD, OP_IDIV): VM_IMPL_DIV(SIZE32, int32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_DIV); 1136 | case OP(SZ_BYTE, OP_IREM): VM_IMPL_DIV(SIZE8, int8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_REM); 1137 | case OP(SZ_HALF, OP_IREM): VM_IMPL_DIV(SIZE16, int16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_REM); 1138 | case OP(SZ_WORD, OP_IREM): VM_IMPL_DIV(SIZE32, int32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_REM); 1139 | 1140 | case OP(SZ_BYTE, OP_AND): VM_IMPL_AND(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_AND); 1141 | case OP(SZ_HALF, OP_AND): VM_IMPL_AND(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_AND); 1142 | case OP(SZ_WORD, OP_AND): VM_IMPL_AND(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_AND); 1143 | case OP(SZ_BYTE, OP_XOR): VM_IMPL_AND(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_XOR); 1144 | case OP(SZ_HALF, OP_XOR): VM_IMPL_AND(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_XOR); 1145 | case OP(SZ_WORD, OP_XOR): VM_IMPL_AND(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_XOR); 1146 | case OP(SZ_BYTE, OP_OR): VM_IMPL_AND(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_OR); 1147 | case OP(SZ_HALF, OP_OR): VM_IMPL_AND(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_OR); 1148 | case OP(SZ_WORD, OP_OR): VM_IMPL_AND(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_OR); 1149 | 1150 | case OP(SZ_BYTE, OP_SLA): VM_IMPL_SHIFT(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_SHIFT_LEFT); 1151 | case OP(SZ_HALF, OP_SLA): VM_IMPL_SHIFT(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_SHIFT_LEFT); 1152 | case OP(SZ_WORD, OP_SLA): VM_IMPL_SHIFT(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_SHIFT_LEFT); 1153 | case OP(SZ_BYTE, OP_SRL): VM_IMPL_SHIFT(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_SHIFT_RIGHT); 1154 | case OP(SZ_HALF, OP_SRL): VM_IMPL_SHIFT(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_SHIFT_RIGHT); 1155 | case OP(SZ_WORD, OP_SRL): VM_IMPL_SHIFT(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_SHIFT_RIGHT); 1156 | case OP(SZ_BYTE, OP_SRA): VM_IMPL_SHIFT(SIZE8, int8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_SHIFT_RIGHT); 1157 | case OP(SZ_HALF, OP_SRA): VM_IMPL_SHIFT(SIZE16, int16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_SHIFT_RIGHT); 1158 | case OP(SZ_WORD, OP_SRA): VM_IMPL_SHIFT(SIZE32, int32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_SHIFT_RIGHT); 1159 | 1160 | case OP(SZ_BYTE, OP_ROL): VM_IMPL_SHIFT(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, ROTATE_LEFT8); 1161 | case OP(SZ_HALF, OP_ROL): VM_IMPL_SHIFT(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, ROTATE_LEFT16); 1162 | case OP(SZ_WORD, OP_ROL): VM_IMPL_SHIFT(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, ROTATE_LEFT32); 1163 | case OP(SZ_BYTE, OP_ROR): VM_IMPL_SHIFT(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, ROTATE_RIGHT8); 1164 | case OP(SZ_HALF, OP_ROR): VM_IMPL_SHIFT(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, ROTATE_RIGHT16); 1165 | case OP(SZ_WORD, OP_ROR): VM_IMPL_SHIFT(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, ROTATE_RIGHT32); 1166 | 1167 | case OP(SZ_BYTE, OP_BSE): VM_IMPL_SHIFT(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_BIT_SET); 1168 | case OP(SZ_HALF, OP_BSE): VM_IMPL_SHIFT(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_BIT_SET); 1169 | case OP(SZ_WORD, OP_BSE): VM_IMPL_SHIFT(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_BIT_SET); 1170 | case OP(SZ_BYTE, OP_BCL): VM_IMPL_SHIFT(SIZE8, uint8_t, uint8_t, vm_source8, vm_source8_stay, vm_target8, OPER_BIT_CLEAR); 1171 | case OP(SZ_HALF, OP_BCL): VM_IMPL_SHIFT(SIZE16, uint16_t, uint16_t, vm_source16, vm_source16_stay, vm_target16, OPER_BIT_CLEAR); 1172 | case OP(SZ_WORD, OP_BCL): VM_IMPL_SHIFT(SIZE32, uint32_t, uint32_t, vm_source32, vm_source32_stay, vm_target32, OPER_BIT_CLEAR); 1173 | 1174 | case OP(SZ_BYTE, OP_CMP): VM_IMPL_CMP(SIZE8, uint8_t, vm_source8); 1175 | case OP(SZ_HALF, OP_CMP): VM_IMPL_CMP(SIZE16, uint16_t, vm_source16); 1176 | case OP(SZ_WORD, OP_CMP): VM_IMPL_CMP(SIZE32, uint32_t, vm_source32); 1177 | case OP(SZ_BYTE, OP_ICMP): VM_IMPL_CMP(SIZE8, int8_t, vm_source8); 1178 | case OP(SZ_HALF, OP_ICMP): VM_IMPL_CMP(SIZE16, int16_t, vm_source16); 1179 | case OP(SZ_WORD, OP_ICMP): VM_IMPL_CMP(SIZE32, int32_t, vm_source32); 1180 | 1181 | case OP(SZ_BYTE, OP_BTS): VM_IMPL_BTS(SIZE8, uint8_t, vm_source8); 1182 | case OP(SZ_HALF, OP_BTS): VM_IMPL_BTS(SIZE16, uint16_t, vm_source16); 1183 | case OP(SZ_WORD, OP_BTS): VM_IMPL_BTS(SIZE32, uint32_t, vm_source32); 1184 | 1185 | case OP(SZ_WORD, OP_MSE): { 1186 | VM_PRELUDE_0(); 1187 | vm->mmu_enabled = true; 1188 | break; 1189 | }; 1190 | case OP(SZ_WORD, OP_MCL): { 1191 | VM_PRELUDE_0(); 1192 | vm->mmu_enabled = false; 1193 | break; 1194 | }; 1195 | case OP(SZ_WORD, OP_INT): { 1196 | VM_PRELUDE_1(SIZE32); 1197 | uint32_t intr = vm_source32(vm, instr.source, instr.offset); 1198 | vm->pointer_instr = vm->pointer_instr_mut; 1199 | fox32_raise(vm, intr); 1200 | vm->pointer_instr_mut = vm->pointer_instr; 1201 | break; 1202 | }; 1203 | case OP(SZ_WORD, OP_TLB): { 1204 | VM_PRELUDE_1(SIZE32); 1205 | set_and_flush_tlb(vm_source32(vm, instr.source, instr.offset)); 1206 | break; 1207 | }; 1208 | case OP(SZ_WORD, OP_FLP): { 1209 | VM_PRELUDE_1(SIZE32); 1210 | flush_single_page(vm_source32(vm, instr.source, instr.offset)); 1211 | break; 1212 | }; 1213 | 1214 | default: 1215 | vm_panic(vm, FOX32_ERR_BADOPCODE); 1216 | } 1217 | 1218 | vm->pointer_instr = vm->pointer_instr_mut; 1219 | } 1220 | 1221 | static err_t vm_step(vm_t *vm) { 1222 | if (setjmp(vm->panic_jmp) != 0) { 1223 | return vm->halted = true, vm->panic_err; 1224 | } 1225 | vm_execute(vm); 1226 | return FOX32_ERR_OK; 1227 | } 1228 | static err_t vm_resume(vm_t *vm, uint32_t count, uint32_t *executed) { 1229 | if (setjmp(vm->panic_jmp) != 0) { 1230 | return vm->halted = true, vm->panic_err; 1231 | } 1232 | 1233 | vm->halted = false; 1234 | 1235 | uint32_t remaining = count; 1236 | while (!vm->halted && !vm->soft_halted && remaining > 0) { 1237 | vm_execute(vm); 1238 | remaining -= 1; 1239 | *executed += 1; 1240 | } 1241 | 1242 | if (vm->soft_halted) { 1243 | *executed = count; 1244 | } 1245 | 1246 | return FOX32_ERR_OK; 1247 | } 1248 | 1249 | static fox32_err_t vm_raise(vm_t *vm, uint16_t vector) { 1250 | if (!vm->flag_interrupt && vector < 256) { 1251 | if (vm->pending_vectors[vector] == 0) { 1252 | vm->pending_vectors[vector] = 1; 1253 | vm->deferred_interrupt_count++; 1254 | } 1255 | 1256 | return FOX32_ERR_NOINTERRUPTS; 1257 | } 1258 | if (setjmp(vm->panic_jmp) != 0) { 1259 | return vm->panic_err; 1260 | } 1261 | 1262 | uint32_t pointer_handler = 1263 | vm->memory_ram[SIZE32 * (uint32_t) vector] | 1264 | vm->memory_ram[SIZE32 * (uint32_t) vector + 1] << 8 | 1265 | vm->memory_ram[SIZE32 * (uint32_t) vector + 2] << 16 | 1266 | vm->memory_ram[SIZE32 * (uint32_t) vector + 3] << 24; 1267 | 1268 | if (vm->flag_swap_sp) { 1269 | uint32_t old_stack_pointer = vm->pointer_stack; 1270 | vm->pointer_stack = vm->pointer_exception_stack; 1271 | vm_push32(vm, old_stack_pointer); 1272 | vm_push32(vm, vm->pointer_instr); 1273 | vm_push8(vm, vm_flags_get(vm)); 1274 | vm->flag_swap_sp = false; 1275 | } else { 1276 | vm_push32(vm, vm->pointer_instr); 1277 | vm_push8(vm, vm_flags_get(vm)); 1278 | } 1279 | 1280 | if (vector >= 256) { 1281 | // if this is an exception, push the operand 1282 | vm_push32(vm, vm->exception_operand); 1283 | vm->exception_operand = 0; 1284 | } else { 1285 | // if this is an interrupt, push the vector 1286 | vm_push32(vm, (uint32_t) vector); 1287 | } 1288 | 1289 | vm->pointer_instr = pointer_handler; 1290 | vm->halted = true; 1291 | vm->soft_halted = false; 1292 | vm->flag_interrupt = false; 1293 | 1294 | return FOX32_ERR_OK; 1295 | } 1296 | 1297 | static fox32_err_t vm_recover(vm_t *vm, err_t err) { 1298 | switch (err) { 1299 | case FOX32_ERR_DEBUGGER: 1300 | return vm_raise(vm, EX_DEBUGGER); 1301 | case FOX32_ERR_FAULT_RD: 1302 | return vm_raise(vm, EX_FAULT_RD); 1303 | case FOX32_ERR_FAULT_WR: 1304 | return vm_raise(vm, EX_FAULT_WR); 1305 | case FOX32_ERR_BADOPCODE: 1306 | case FOX32_ERR_BADCONDITION: 1307 | case FOX32_ERR_BADREGISTER: 1308 | case FOX32_ERR_BADIMMEDIATE: 1309 | return vm_raise(vm, EX_ILLEGAL); 1310 | case FOX32_ERR_DIVZERO: 1311 | return vm_raise(vm, EX_DIVZERO); 1312 | case FOX32_ERR_IOREAD: 1313 | case FOX32_ERR_IOWRITE: 1314 | return vm_raise(vm, EX_BUS); 1315 | default: 1316 | return FOX32_ERR_CANTRECOVER; 1317 | } 1318 | } 1319 | 1320 | #define VM_SAFEPUSH_BODY(_vm_push) \ 1321 | if (setjmp(vm->panic_jmp) != 0) { \ 1322 | return vm->panic_err; \ 1323 | } \ 1324 | _vm_push(vm, value); \ 1325 | return FOX32_ERR_OK; 1326 | 1327 | static fox32_err_t vm_safepush_byte(vm_t *vm, uint8_t value) { 1328 | VM_SAFEPUSH_BODY(vm_push8) 1329 | } 1330 | static fox32_err_t vm_safepush_half(vm_t *vm, uint16_t value) { 1331 | VM_SAFEPUSH_BODY(vm_push16) 1332 | } 1333 | static fox32_err_t vm_safepush_word(vm_t *vm, uint32_t value) { 1334 | VM_SAFEPUSH_BODY(vm_push32) 1335 | } 1336 | 1337 | #define VM_SAFEPOP_BODY(_vm_pop) \ 1338 | *value = 0; \ 1339 | if (setjmp(vm->panic_jmp) != 0) { \ 1340 | return vm->panic_err; \ 1341 | } \ 1342 | *value = _vm_pop(vm); \ 1343 | return FOX32_ERR_OK; 1344 | 1345 | static fox32_err_t vm_safepop_byte(vm_t *vm, uint8_t *value) { 1346 | VM_SAFEPOP_BODY(vm_pop8) 1347 | } 1348 | static fox32_err_t vm_safepop_half(vm_t *vm, uint16_t *value) { 1349 | VM_SAFEPOP_BODY(vm_pop16) 1350 | } 1351 | static fox32_err_t vm_safepop_word(vm_t *vm, uint32_t *value) { 1352 | VM_SAFEPOP_BODY(vm_pop32) 1353 | } 1354 | 1355 | const char *fox32_strerr(fox32_err_t err) { 1356 | return err_tostring(err); 1357 | } 1358 | void fox32_init(fox32_vm_t *vm) { 1359 | vm_init(vm); 1360 | } 1361 | fox32_err_t fox32_step(fox32_vm_t *vm) { 1362 | return vm_step(vm); 1363 | } 1364 | fox32_err_t fox32_resume(fox32_vm_t *vm, uint32_t count, uint32_t *executed) { 1365 | return vm_resume(vm, count, executed); 1366 | } 1367 | fox32_err_t fox32_raise(fox32_vm_t *vm, uint16_t vector) { 1368 | return vm_raise(vm, vector); 1369 | } 1370 | fox32_err_t fox32_recover(fox32_vm_t *vm, fox32_err_t err) { 1371 | return vm_recover(vm, err); 1372 | } 1373 | fox32_err_t fox32_push_byte(fox32_vm_t *vm, uint8_t value) { 1374 | return vm_safepush_byte(vm, value); 1375 | } 1376 | fox32_err_t fox32_push_half(fox32_vm_t *vm, uint16_t value) { 1377 | return vm_safepush_half(vm, value); 1378 | } 1379 | fox32_err_t fox32_push_word(fox32_vm_t *vm, uint32_t value) { 1380 | return vm_safepush_word(vm, value); 1381 | } 1382 | fox32_err_t fox32_pop_byte(fox32_vm_t *vm, uint8_t *value) { 1383 | return vm_safepop_byte(vm, value); 1384 | } 1385 | fox32_err_t fox32_pop_half(fox32_vm_t *vm, uint16_t *value) { 1386 | return vm_safepop_half(vm, value); 1387 | } 1388 | fox32_err_t fox32_pop_word(fox32_vm_t *vm, uint32_t *value) { 1389 | return vm_safepop_word(vm, value); 1390 | } 1391 | -------------------------------------------------------------------------------- /src/cpu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define FOX32_CPU_HZ 33000000 9 | 10 | #define FOX32_MEMORY_RAM 0x04000000 // 64 MiB 11 | #define FOX32_MEMORY_ROM 0x00080000 // 512 KiB 12 | #define FOX32_MEMORY_ROM_START 0xF0000000 13 | 14 | #define FOX32_POINTER_DEFAULT_INSTR FOX32_MEMORY_ROM_START 15 | #define FOX32_POINTER_DEFAULT_STACK 0x00000000 16 | 17 | #define FOX32_POINTER_INTERRUPTVECS 0x00000000 18 | 19 | #define FOX32_REGISTER_LOOP 31 20 | #define FOX32_REGISTER_COUNT 32 21 | 22 | typedef enum { 23 | FOX32_ERR_OK, 24 | FOX32_ERR_INTERNAL, 25 | FOX32_ERR_DEBUGGER, 26 | FOX32_ERR_FAULT_RD, 27 | FOX32_ERR_FAULT_WR, 28 | FOX32_ERR_BADOPCODE, 29 | FOX32_ERR_BADCONDITION, 30 | FOX32_ERR_BADREGISTER, 31 | FOX32_ERR_BADIMMEDIATE, 32 | FOX32_ERR_DIVZERO, 33 | FOX32_ERR_IOREAD, 34 | FOX32_ERR_IOWRITE, 35 | FOX32_ERR_NOINTERRUPTS, 36 | FOX32_ERR_CANTRECOVER 37 | } fox32_err_t; 38 | 39 | const char *fox32_strerr(fox32_err_t err); 40 | 41 | typedef int fox32_io_read_t(void *user, uint32_t *value, uint32_t port); 42 | typedef int fox32_io_write_t(void *user, uint32_t value, uint32_t port); 43 | 44 | typedef struct { 45 | uint32_t pointer_instr_mut; 46 | uint32_t pointer_instr; 47 | uint32_t pointer_stack; 48 | uint32_t pointer_exception_stack; 49 | uint32_t pointer_frame; 50 | uint32_t pointer_page_directory; 51 | uint32_t registers[FOX32_REGISTER_COUNT]; 52 | 53 | bool flag_zero; 54 | bool flag_carry; 55 | bool flag_interrupt; 56 | bool flag_swap_sp; 57 | 58 | bool halted; 59 | bool soft_halted; 60 | 61 | bool debug; 62 | bool headless; 63 | 64 | bool mmu_enabled; 65 | 66 | jmp_buf panic_jmp; 67 | fox32_err_t panic_err; 68 | 69 | uint32_t exception_operand; 70 | 71 | void *io_user; 72 | fox32_io_read_t *io_read; 73 | fox32_io_write_t *io_write; 74 | 75 | uint16_t deferred_interrupt_count; 76 | 77 | uint8_t pending_vectors[256]; 78 | uint8_t memory_ram[FOX32_MEMORY_RAM]; 79 | uint8_t memory_rom[FOX32_MEMORY_ROM]; 80 | } fox32_vm_t; 81 | 82 | void fox32_init(fox32_vm_t *vm); 83 | 84 | fox32_err_t fox32_step(fox32_vm_t *vm); 85 | fox32_err_t fox32_resume(fox32_vm_t *vm, uint32_t count, uint32_t *executed); 86 | 87 | fox32_err_t fox32_raise(fox32_vm_t *vm, uint16_t vector); 88 | fox32_err_t fox32_recover(fox32_vm_t *vm, fox32_err_t err); 89 | 90 | fox32_err_t fox32_push_byte(fox32_vm_t *vm, uint8_t value); 91 | fox32_err_t fox32_push_half(fox32_vm_t *vm, uint16_t value); 92 | fox32_err_t fox32_push_word(fox32_vm_t *vm, uint32_t value); 93 | fox32_err_t fox32_pop_byte(fox32_vm_t *vm, uint8_t *value); 94 | fox32_err_t fox32_pop_half(fox32_vm_t *vm, uint16_t *value); 95 | fox32_err_t fox32_pop_word(fox32_vm_t *vm, uint32_t *value); 96 | -------------------------------------------------------------------------------- /src/disk.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cpu.h" 12 | #include "disk.h" 13 | 14 | disk_controller_t disk_controller; 15 | 16 | extern fox32_vm_t vm; 17 | 18 | void new_disk(const char *filename, size_t id) { 19 | if (id > 3) { puts("attempting to insert disk with ID > 3"); return; } 20 | printf("inserting %s as disk ID %d\n", filename, (int) id); 21 | disk_controller.disks[id].file = fopen(filename, "r+b"); 22 | if (!disk_controller.disks[id].file) { 23 | fprintf(stderr, "couldn't open disk file\n"); 24 | exit(1); 25 | } 26 | fseek(disk_controller.disks[id].file, 0, SEEK_END); 27 | disk_controller.disks[id].size = ftell(disk_controller.disks[id].file); 28 | rewind(disk_controller.disks[id].file); 29 | } 30 | 31 | void insert_disk(disk_t disk, size_t id) { 32 | if (id > 3) { puts("attempting to insert disk with ID > 3"); return; } 33 | if (disk_controller.disks[id].size > 0) remove_disk(id); 34 | printf("inserting disk ID %d\n", (int) id); 35 | disk_controller.disks[id] = disk; 36 | } 37 | 38 | void remove_disk(size_t id) { 39 | if (id > 3) { puts("attempting to remove disk with ID > 3"); return; } 40 | if (disk_controller.disks[id].file) { 41 | printf("removing disk ID %d\n", (int) id); 42 | fclose(disk_controller.disks[id].file); 43 | disk_controller.disks[id].file = NULL; 44 | disk_controller.disks[id].size = 0; 45 | } 46 | } 47 | 48 | uint64_t get_disk_size(size_t id) { 49 | if (id > 3) { puts("attempting to access disk size with ID > 3"); return 0; } 50 | return disk_controller.disks[id].size; 51 | } 52 | 53 | void set_disk_sector(size_t id, uint64_t sector) { 54 | if (id > 3) { puts("attempting to set disk sector with ID > 3"); return; } 55 | if (disk_controller.disks[id].file) { 56 | fseek(disk_controller.disks[id].file, sector * 512, 0); 57 | } 58 | } 59 | 60 | size_t read_disk_into_memory(size_t id) { 61 | if (id > 3) { puts("attempting to read disk with ID > 3"); return 0; } 62 | if (disk_controller.disks[id].file) { 63 | return fread(&vm.memory_ram[disk_controller.buffer_pointer], 1, 512, disk_controller.disks[id].file); 64 | } else { 65 | return 0; 66 | } 67 | } 68 | 69 | size_t write_disk_from_memory(size_t id) { 70 | if (id > 3) { puts("attempting to write disk with ID > 3"); return 0; } 71 | if (disk_controller.disks[id].file) { 72 | return fwrite(&vm.memory_ram[disk_controller.buffer_pointer], 1, 512, disk_controller.disks[id].file); 73 | } else { 74 | return 0; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/disk.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct { 4 | FILE *file; 5 | uint64_t size; 6 | } disk_t; 7 | 8 | typedef struct { 9 | disk_t disks[4]; 10 | size_t buffer_pointer; 11 | } disk_controller_t; 12 | 13 | void new_disk(const char *filename, size_t id); 14 | void insert_disk(disk_t disk, size_t id); 15 | void remove_disk(size_t id); 16 | uint64_t get_disk_size(size_t id); 17 | void set_disk_sector(size_t id, uint64_t sector); 18 | size_t read_disk_into_memory(size_t id); 19 | size_t write_disk_from_memory(size_t id); 20 | -------------------------------------------------------------------------------- /src/framebuffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "bus.h" 12 | #include "cpu.h" 13 | #include "framebuffer.h" 14 | #include "screen.h" 15 | 16 | extern fox32_vm_t vm; 17 | 18 | static uint8_t framebuffer[FRAMEBUFFER_WIDTH * FRAMEBUFFER_HEIGHT * 4]; 19 | static overlay_t overlays[32]; 20 | 21 | overlay_t *overlay_get(uint32_t index) { 22 | if (index >= 32) abort(); 23 | return &overlays[index]; 24 | } 25 | 26 | void draw_framebuffer(struct Screen *screen) { 27 | SDL_Texture *texture = ScreenGetTexture(screen); 28 | 29 | memcpy(framebuffer, &vm.memory_ram[0x02000000], FRAMEBUFFER_WIDTH * FRAMEBUFFER_HEIGHT * 4); 30 | 31 | for (size_t i = 0; i < 32; i++) { 32 | overlay_t *overlay = &overlays[i]; 33 | if (!overlay->enabled) continue; 34 | 35 | size_t pointer = overlay->pointer; 36 | 37 | size_t height = overlay->height; 38 | size_t width = overlay->width; 39 | 40 | size_t ymin = overlay->y, ymax = ymin + height; 41 | size_t xmin = overlay->x, xmax = xmin + width; 42 | ymax = ymax < FRAMEBUFFER_HEIGHT ? ymax : FRAMEBUFFER_HEIGHT; 43 | xmax = xmax < FRAMEBUFFER_WIDTH ? xmax : FRAMEBUFFER_WIDTH; 44 | if (ymin >= ymax) continue; 45 | if (xmin >= xmax) continue; 46 | 47 | for (size_t y = ymin; y < ymax; y++) { 48 | for (size_t x = xmin; x < xmax; x++) { 49 | size_t index_dst = (x + y * FRAMEBUFFER_WIDTH) * 4; 50 | size_t index_src = ((x - xmin) + (y - ymin) * width) * 4 + pointer; 51 | if (vm.memory_ram[index_src + 3] > 0) { 52 | memcpy(&framebuffer[index_dst], &vm.memory_ram[index_src], 4); 53 | } 54 | } 55 | } 56 | } 57 | 58 | SDL_UpdateTexture(texture, NULL, framebuffer, FRAMEBUFFER_WIDTH * 4); 59 | } 60 | -------------------------------------------------------------------------------- /src/framebuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "screen.h" 4 | 5 | #define FRAMEBUFFER_WIDTH 640 6 | #define FRAMEBUFFER_HEIGHT 480 7 | 8 | #define VSYNC_INTERRUPT_VECTOR 0xFF 9 | 10 | void draw_framebuffer(struct Screen *screen); 11 | 12 | typedef struct { 13 | uint32_t pointer; 14 | uint32_t x, y; 15 | uint32_t width, height; 16 | bool enabled; 17 | } overlay_t; 18 | 19 | overlay_t *overlay_get(uint32_t index); 20 | -------------------------------------------------------------------------------- /src/keyboard.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cpu.h" 12 | #include "keyboard.h" 13 | 14 | extern fox32_vm_t vm; 15 | 16 | typedef struct node_s { 17 | struct node_s *prev; 18 | struct node_s *next; 19 | keycode_t code; 20 | } node_t; 21 | 22 | static node_t *head = NULL; 23 | static node_t *tail = NULL; 24 | 25 | keycode_t key_take(void) { 26 | node_t *node = head; 27 | 28 | if (node == NULL) { 29 | return 0; 30 | } 31 | 32 | if (node == tail) { 33 | head = NULL; 34 | tail = NULL; 35 | } else { 36 | head = node->next; 37 | head->prev = NULL; 38 | } 39 | 40 | keycode_t code = node->code; 41 | return free(node), code; 42 | } 43 | 44 | void key_put(keycode_t code) { 45 | if (code == 0) abort(); 46 | 47 | node_t *node = malloc(sizeof(node_t)); 48 | 49 | node->prev = tail; 50 | node->next = NULL; 51 | node->code = code; 52 | 53 | if (head == NULL) { 54 | head = node; 55 | } else { 56 | tail->next = node; 57 | } 58 | 59 | tail = node; 60 | } 61 | 62 | static const keycode_t key_map[SDL_NUM_SCANCODES] = { 63 | [SDL_SCANCODE_ESCAPE] = 0x01, 64 | [SDL_SCANCODE_1] = 0x02, 65 | [SDL_SCANCODE_KP_1] = 0x02, 66 | [SDL_SCANCODE_2] = 0x03, 67 | [SDL_SCANCODE_KP_2] = 0x03, 68 | [SDL_SCANCODE_3] = 0x04, 69 | [SDL_SCANCODE_KP_3] = 0x04, 70 | [SDL_SCANCODE_4] = 0x05, 71 | [SDL_SCANCODE_KP_4] = 0x05, 72 | [SDL_SCANCODE_5] = 0x06, 73 | [SDL_SCANCODE_KP_5] = 0x06, 74 | [SDL_SCANCODE_6] = 0x07, 75 | [SDL_SCANCODE_KP_6] = 0x07, 76 | [SDL_SCANCODE_7] = 0x08, 77 | [SDL_SCANCODE_KP_7] = 0x08, 78 | [SDL_SCANCODE_8] = 0x09, 79 | [SDL_SCANCODE_KP_8] = 0x09, 80 | [SDL_SCANCODE_9] = 0x0A, 81 | [SDL_SCANCODE_KP_9] = 0x0A, 82 | [SDL_SCANCODE_0] = 0x0B, 83 | [SDL_SCANCODE_KP_0] = 0x0B, 84 | [SDL_SCANCODE_MINUS] = 0x0C, 85 | [SDL_SCANCODE_EQUALS] = 0x0D, 86 | [SDL_SCANCODE_BACKSPACE] = 0x0E, 87 | [SDL_SCANCODE_TAB] = 0x0F, 88 | [SDL_SCANCODE_Q] = 0x10, 89 | [SDL_SCANCODE_W] = 0x11, 90 | [SDL_SCANCODE_E] = 0x12, 91 | [SDL_SCANCODE_R] = 0x13, 92 | [SDL_SCANCODE_T] = 0x14, 93 | [SDL_SCANCODE_Y] = 0x15, 94 | [SDL_SCANCODE_U] = 0x16, 95 | [SDL_SCANCODE_I] = 0x17, 96 | [SDL_SCANCODE_O] = 0x18, 97 | [SDL_SCANCODE_P] = 0x19, 98 | [SDL_SCANCODE_LEFTBRACKET] = 0x1A, 99 | [SDL_SCANCODE_RIGHTBRACKET] = 0x1B, 100 | [SDL_SCANCODE_RETURN] = 0x1C, 101 | [SDL_SCANCODE_KP_ENTER] = 0x1C, 102 | [SDL_SCANCODE_LCTRL] = 0x1D, 103 | [SDL_SCANCODE_A] = 0x1E, 104 | [SDL_SCANCODE_S] = 0x1F, 105 | [SDL_SCANCODE_D] = 0x20, 106 | [SDL_SCANCODE_F] = 0x21, 107 | [SDL_SCANCODE_G] = 0x22, 108 | [SDL_SCANCODE_H] = 0x23, 109 | [SDL_SCANCODE_J] = 0x24, 110 | [SDL_SCANCODE_K] = 0x25, 111 | [SDL_SCANCODE_L] = 0x26, 112 | [SDL_SCANCODE_SEMICOLON] = 0x27, 113 | [SDL_SCANCODE_APOSTROPHE] = 0x28, 114 | [SDL_SCANCODE_GRAVE] = 0x29, 115 | [SDL_SCANCODE_LSHIFT] = 0x2A, 116 | [SDL_SCANCODE_BACKSLASH] = 0x2B, 117 | [SDL_SCANCODE_Z] = 0x2C, 118 | [SDL_SCANCODE_X] = 0x2D, 119 | [SDL_SCANCODE_C] = 0x2E, 120 | [SDL_SCANCODE_V] = 0x2F, 121 | [SDL_SCANCODE_B] = 0x30, 122 | [SDL_SCANCODE_N] = 0x31, 123 | [SDL_SCANCODE_M] = 0x32, 124 | [SDL_SCANCODE_COMMA] = 0x33, 125 | [SDL_SCANCODE_PERIOD] = 0x34, 126 | [SDL_SCANCODE_SLASH] = 0x35, 127 | [SDL_SCANCODE_RSHIFT] = 0x36, 128 | [SDL_SCANCODE_KP_HASH] = 0x37, 129 | [SDL_SCANCODE_LALT] = 0x38, 130 | [SDL_SCANCODE_SPACE] = 0x39, 131 | [SDL_SCANCODE_CAPSLOCK] = 0x3A, 132 | [SDL_SCANCODE_F1] = 0x3B, 133 | [SDL_SCANCODE_F2] = 0x3C, 134 | [SDL_SCANCODE_F3] = 0x3D, 135 | [SDL_SCANCODE_F4] = 0x3E, 136 | [SDL_SCANCODE_F5] = 0x3F, 137 | [SDL_SCANCODE_F6] = 0x40, 138 | [SDL_SCANCODE_F7] = 0x41, 139 | [SDL_SCANCODE_F8] = 0x42, 140 | [SDL_SCANCODE_F9] = 0x43, 141 | [SDL_SCANCODE_F10] = 0x44, 142 | [SDL_SCANCODE_F11] = 0x57, 143 | [SDL_SCANCODE_F12] = 0x58, 144 | [SDL_SCANCODE_UP] = 0x67, 145 | [SDL_SCANCODE_DOWN] = 0x6C, 146 | [SDL_SCANCODE_LEFT] = 0x69, 147 | [SDL_SCANCODE_RIGHT] = 0x6A, 148 | }; 149 | 150 | keycode_t key_convert(int sdlcode) { 151 | if (sdlcode < 0 || sdlcode > SDL_NUM_SCANCODES) return 0; 152 | return key_map[sdlcode]; 153 | } 154 | 155 | void key_pressed(int sdlcode) { 156 | if (sdlcode == SDL_SCANCODE_F11) vm.debug = !vm.debug; 157 | keycode_t code = key_convert(sdlcode); 158 | if (code) key_put(code); 159 | } 160 | 161 | void key_released(int sdlcode) { 162 | keycode_t code = key_convert(sdlcode) | 0x80; 163 | if (code) key_put(code); 164 | } 165 | -------------------------------------------------------------------------------- /src/keyboard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef unsigned char keycode_t; 4 | 5 | keycode_t key_take(void); 6 | void key_put(keycode_t code); 7 | 8 | keycode_t key_convert(int sdlcode); 9 | void key_pressed(int sdlcode); 10 | void key_released(int sdlcode); 11 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #ifdef __EMSCRIPTEN__ 12 | #include 13 | #include 14 | #endif 15 | 16 | #include "bus.h" 17 | #include "cpu.h" 18 | #include "disk.h" 19 | #include "framebuffer.h" 20 | #include "keyboard.h" 21 | #include "mouse.h" 22 | #include "screen.h" 23 | #include "serial.h" 24 | 25 | #include "../fox32rom.h" 26 | 27 | #define FPS 60 28 | #define TPF 1 29 | #define TPS (FPS * TPF) 30 | 31 | fox32_vm_t vm; 32 | 33 | extern bool bus_requests_exit; 34 | extern disk_controller_t disk_controller; 35 | 36 | uint32_t tick_start; 37 | uint32_t tick_end; 38 | int ticks = 0; 39 | bool done = false; 40 | 41 | time_t rtc_time; 42 | uint32_t rtc_uptime; 43 | 44 | void main_loop(void); 45 | void load_rom(const char *filename); 46 | 47 | int main(int argc, char *argv[]) { 48 | fox32_init(&vm); 49 | vm.io_read = bus_io_read; 50 | vm.io_write = bus_io_write; 51 | vm.halted = false; 52 | vm.debug = false; 53 | 54 | memcpy(vm.memory_rom, fox32rom, sizeof(fox32rom)); 55 | 56 | size_t disk_id = 0; 57 | int filtering_mode = 0; 58 | #ifndef __EMSCRIPTEN__ 59 | for (int i = 1; i < argc; i++) { 60 | if (strcmp(argv[i], "--help") == 0) { 61 | fprintf( 62 | stderr, 63 | "Usage: %s [OPTIONS]\n\n" 64 | "Options:\n" 65 | " --help Print this message\n" 66 | " --disk DISK Specify a disk image to use\n" 67 | " --rom ROM Specify a ROM image to use\n" 68 | " --debug Enable debug output\n" 69 | " --headless Headless mode: don't open a window\n" 70 | " --filtering MODE Set scale filtering mode for high DPI displays\n" 71 | " 0 = nearest pixel (default)\n" 72 | " 1 = linear filtering\n", 73 | argv[0] 74 | ); 75 | return 0; 76 | } else if (strcmp(argv[i], "--disk") == 0) { 77 | if (i + 1 < argc) { 78 | new_disk(argv[i + 1], disk_id++); 79 | i++; 80 | } else { 81 | fprintf(stderr, "no disk image specified\n"); 82 | return 1; 83 | } 84 | } else if (strcmp(argv[i], "--rom") == 0) { 85 | if (i + 1 < argc) { 86 | load_rom(argv[i + 1]); 87 | i++; 88 | } else { 89 | fprintf(stderr, "no ROM image specified\n"); 90 | return 1; 91 | } 92 | } else if (strcmp(argv[i], "--debug") == 0) { 93 | vm.debug = true; 94 | } else if (strcmp(argv[i], "--headless") == 0) { 95 | vm.headless = true; 96 | } else if (strcmp(argv[i], "--filtering") == 0) { 97 | if (i + 1 < argc) { 98 | if (strcmp(argv[i + 1], "0") == 0) { 99 | filtering_mode = 0; // nearest pixel filtering 100 | } else if (strcmp(argv[i + 1], "1") == 0) { 101 | filtering_mode = 1; // linear filtering 102 | } else { 103 | fprintf(stderr, "incorrect scale filtering mode specified\n"); 104 | return 1; 105 | } 106 | i++; 107 | } else { 108 | fprintf(stderr, "no scale filtering mode specified\n"); 109 | return 1; 110 | } 111 | } else { 112 | fprintf(stderr, "unrecognized option %s\n", argv[i]); 113 | return 1; 114 | } 115 | } 116 | #else 117 | new_disk("fox32os.img", disk_id++); 118 | #endif 119 | 120 | if (!vm.headless) { 121 | if (SDL_Init(SDL_INIT_VIDEO) != 0) { 122 | fprintf(stderr, "unable to initialize SDL: %s", SDL_GetError()); 123 | return 1; 124 | } 125 | 126 | SDL_ShowCursor(SDL_DISABLE); 127 | 128 | ScreenCreate( 129 | FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, 130 | filtering_mode, 131 | draw_framebuffer, 132 | key_pressed, 133 | key_released, 134 | mouse_pressed, 135 | mouse_released, 136 | mouse_moved, 137 | drop_file 138 | ); 139 | 140 | ScreenInit(); 141 | ScreenDraw(); 142 | } 143 | 144 | #ifndef WINDOWS 145 | serial_init(); 146 | #endif 147 | 148 | tick_start = SDL_GetTicks(); 149 | tick_end = SDL_GetTicks(); 150 | 151 | #ifdef __EMSCRIPTEN__ 152 | emscripten_set_main_loop(main_loop, FPS, 1); 153 | #endif 154 | 155 | while (!done && !bus_requests_exit) { 156 | main_loop(); 157 | 158 | tick_end = SDL_GetTicks(); 159 | int delay = 1000/TPS - (tick_end - tick_start); 160 | if (delay > 0) { 161 | SDL_Delay(delay); 162 | } else { 163 | //printf("time overrun %d\n", delay); 164 | } 165 | } 166 | 167 | return 0; 168 | } 169 | 170 | void main_loop(void) { 171 | #ifdef __EMSCRIPTEN__ 172 | if (done || bus_requests_exit) { 173 | emscripten_cancel_main_loop(); 174 | } 175 | #endif 176 | int dt = SDL_GetTicks() - tick_start; 177 | tick_start = SDL_GetTicks(); 178 | if (!dt) 179 | dt = 1; 180 | 181 | int cycles_per_tick = FOX32_CPU_HZ / TPS / dt; 182 | int extra_cycles = FOX32_CPU_HZ / TPS - (cycles_per_tick * dt); 183 | 184 | fox32_err_t error = FOX32_ERR_OK; 185 | 186 | for (int i = 0; i < dt; i++) { 187 | rtc_uptime += 1; 188 | rtc_time = time(NULL); 189 | 190 | int cycles_left = cycles_per_tick; 191 | 192 | if (i == dt - 1) 193 | cycles_left += extra_cycles; 194 | 195 | while (cycles_left > 0) { 196 | uint32_t executed = 0; 197 | 198 | error = fox32_resume(&vm, cycles_left, &executed); 199 | if (error != FOX32_ERR_OK) { 200 | if (vm.debug) puts(fox32_strerr(error)); 201 | error = fox32_recover(&vm, error); 202 | if (error != FOX32_ERR_OK) 203 | break; 204 | } 205 | 206 | cycles_left -= executed; 207 | } 208 | } 209 | 210 | if ((ticks % TPF) == 0) { 211 | if (!vm.headless) 212 | ScreenDraw(); 213 | fox32_raise(&vm, VSYNC_INTERRUPT_VECTOR); 214 | vm.halted = false; 215 | } 216 | 217 | done = ScreenProcessEvents(); 218 | 219 | ticks++; 220 | } 221 | 222 | void load_rom(const char *filename) { 223 | FILE *rom = fopen(filename, "r"); 224 | 225 | if (!rom) { 226 | fprintf(stderr, "couldn't find ROM file %s\n", filename); 227 | exit(1); 228 | } 229 | printf("using %s as boot ROM\n", filename); 230 | 231 | if (fread(&vm.memory_rom, sizeof(fox32rom), 1, rom) != 1) { 232 | fprintf(stderr, "error reading ROM file %s\n", filename); 233 | if (feof(rom)) { 234 | fprintf(stderr, "ROM file too small, must be %lu bytes\n", sizeof(fox32rom)); 235 | } 236 | fclose(rom); 237 | exit(1); 238 | } 239 | 240 | if (fgetc(rom) != EOF) { 241 | fprintf(stderr, "error reading ROM file %s\n", filename); 242 | fprintf(stderr, "ROM file too large, must be %lu bytes\n", sizeof(fox32rom)); 243 | fclose(rom); 244 | exit(1); 245 | } 246 | 247 | fclose(rom); 248 | } 249 | -------------------------------------------------------------------------------- /src/mmu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cpu.h" 12 | #include "mmu.h" 13 | 14 | mmu_page_t mmu_tlb[64]; 15 | 16 | extern fox32_vm_t vm; 17 | 18 | uint32_t replacement_index = 0; 19 | 20 | static size_t find_free_tlb_entry_index() { 21 | for (size_t i = 0; i < 64; i++) { 22 | if (!mmu_tlb[i].present) { 23 | return i; 24 | } 25 | } 26 | 27 | return (replacement_index++) & 63; 28 | } 29 | 30 | void set_and_flush_tlb(uint32_t virtual_address) { 31 | vm.pointer_page_directory = virtual_address; 32 | for (size_t i = 0; i < 64; i++) { 33 | mmu_tlb[i] = (mmu_page_t) { 34 | .physical_address = 0, 35 | // impossible to match with this entry because the low 12 bits are masked off first 36 | .virtual_page = 0xFFFFFFFF, 37 | .present = false, 38 | .rw = false 39 | }; 40 | } 41 | //printf("flushed TLB and set page directory pointer to %X\n", virtual_address); 42 | } 43 | 44 | void flush_single_page(uint32_t virtual_address) { 45 | uint32_t virtual_page = virtual_address & 0xFFFFF000; 46 | //printf("flushing single page %X\n", virtual_page); 47 | for (size_t i = 0; i < 64; i++) { 48 | if (mmu_tlb[i].virtual_page == virtual_page) { 49 | mmu_tlb[i].physical_address = 0; 50 | // impossible to match with this entry because the low 12 bits are masked off first 51 | mmu_tlb[i].virtual_page = 0xFFFFFFFF; 52 | mmu_tlb[i].present = false; 53 | mmu_tlb[i].rw = false; 54 | //printf("flushed\n"); 55 | break; 56 | } 57 | } 58 | } 59 | 60 | mmu_page_t *get_present_page(uint32_t virtual_address) { 61 | uint32_t virtual_page = virtual_address & 0xFFFFF000; 62 | //printf("attempting to fetch physical address for virtual address %X (page %X)\n", virtual_address, virtual_page); 63 | mmu_page_t *physical_page = NULL; 64 | for (size_t i = 0; i < 64; i++) { 65 | if (mmu_tlb[i].virtual_page == virtual_page) { 66 | physical_page = &mmu_tlb[i]; 67 | break; 68 | } 69 | } 70 | if (physical_page == NULL) { 71 | // we didn't find an entry for this page in the TLB, try to insert it from the tables in memory 72 | //printf("couldn't find an entry for this virtual page in the TLB, attempting to cache from tables\n"); 73 | uint32_t page_directory_index = virtual_address >> 22; 74 | uint32_t page_table_index = (virtual_address >> 12) & 0x03FF; 75 | bool directory_present = insert_tlb_entry_from_tables(page_directory_index, page_table_index); 76 | if (!directory_present) return NULL; 77 | // try again after possibly inserting the TLB entry 78 | for (size_t i = 0; i < 64; i++) { 79 | if (mmu_tlb[i].virtual_page == virtual_page) { 80 | physical_page = &mmu_tlb[i]; 81 | break; 82 | } 83 | } 84 | // if we still can't find the page, return NULL 85 | if (physical_page == NULL) return NULL; 86 | } 87 | // if the page is present, return it, otherwise return NULL 88 | if (physical_page->present) { 89 | //printf("found physical address: %X\n", physical_page->physical_address); 90 | return physical_page; 91 | } else { 92 | return NULL; 93 | } 94 | } 95 | 96 | bool insert_tlb_entry_from_tables(uint32_t page_directory_index, uint32_t page_table_index) { 97 | uint32_t directory = 98 | vm.memory_ram[vm.pointer_page_directory + (page_directory_index * 4)] | 99 | vm.memory_ram[vm.pointer_page_directory + (page_directory_index * 4) + 1] << 8 | 100 | vm.memory_ram[vm.pointer_page_directory + (page_directory_index * 4) + 2] << 16 | 101 | vm.memory_ram[vm.pointer_page_directory + (page_directory_index * 4) + 3] << 24; 102 | //printf("operating on directory at %X\n", vm.pointer_page_directory + (page_directory_index * 4)); 103 | bool directory_present = (directory & 0b1) != 0; 104 | uint32_t directory_address = directory & 0xFFFFF000; 105 | //printf("directory_present: %X, directory_address: %X\n", directory_present, directory_address); 106 | if (directory_present) { 107 | uint32_t table = 108 | vm.memory_ram[directory_address + (page_table_index * 4)] | 109 | vm.memory_ram[directory_address + (page_table_index * 4) + 1] << 8 | 110 | vm.memory_ram[directory_address + (page_table_index * 4) + 2] << 16 | 111 | vm.memory_ram[directory_address + (page_table_index * 4) + 3] << 24; 112 | bool table_present = (table & 0b01) != 0; 113 | bool table_rw = (table & 0b10) != 0; 114 | uint32_t table_address = table & 0xFFFFF000; 115 | if (table_present) { 116 | mmu_page_t entry = { 117 | .physical_address = table_address, 118 | .virtual_page = (page_directory_index << 22) | (page_table_index << 12), 119 | .present = table_present, 120 | .rw = table_rw 121 | }; 122 | size_t entry_index = find_free_tlb_entry_index(); 123 | mmu_tlb[entry_index] = entry; 124 | //printf("inserting virtual page %X into the TLB\n", (page_directory_index << 22) | (page_table_index << 12)); 125 | } 126 | } 127 | return directory_present; 128 | } 129 | -------------------------------------------------------------------------------- /src/mmu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct { 4 | uint32_t virtual_page; 5 | uint32_t physical_address; 6 | bool present; 7 | bool rw; 8 | } mmu_page_t; 9 | 10 | void set_and_flush_tlb(uint32_t virtual_address); 11 | void flush_single_page(uint32_t virtual_address); 12 | mmu_page_t *get_present_page(uint32_t virtual_address); 13 | bool insert_tlb_entry_from_tables(uint32_t page_directory_index, uint32_t page_table_index); 14 | -------------------------------------------------------------------------------- /src/mouse.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "mouse.h" 12 | #include "screen.h" 13 | 14 | mouse_t mouse; 15 | 16 | void mouse_moved(int x, int y) { 17 | mouse.x = x; 18 | mouse.y = y; 19 | 20 | if (mouse.x > 0x8000) mouse.x = 0; 21 | if (mouse.x > 640) mouse.x = 640; 22 | if (mouse.y > 0x8000) mouse.y = 0; 23 | if (mouse.y > 480) mouse.y = 480; 24 | } 25 | 26 | void mouse_pressed(int button) { 27 | (void) button; // TODO: check which button was clicked 28 | mouse.clicked = true; 29 | mouse.held = true; 30 | } 31 | 32 | void mouse_released(int button) { 33 | (void) button; // TODO: check which button was released 34 | mouse.released = true; 35 | mouse.held = false; 36 | } 37 | -------------------------------------------------------------------------------- /src/mouse.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct { 4 | uint16_t x, y; 5 | bool clicked; 6 | bool released; 7 | bool held; 8 | } mouse_t; 9 | 10 | void mouse_moved(int dx, int dy); 11 | void mouse_pressed(int button); 12 | void mouse_released(int button); 13 | -------------------------------------------------------------------------------- /src/screen.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "screen.h" 11 | 12 | #ifndef SCREEN_ZOOM 13 | #define SCREEN_ZOOM 1 14 | #endif 15 | 16 | struct Screen MainScreen; 17 | 18 | int WindowWidth = 0; 19 | int WindowHeight = 0; 20 | 21 | bool ScreenFirstDraw = true; 22 | 23 | SDL_Window *ScreenWindow; 24 | SDL_Renderer *ScreenRenderer; 25 | 26 | SDL_Rect WindowRect; 27 | 28 | void ScreenInit() { 29 | ScreenWindow = SDL_CreateWindow( 30 | "fox32 emulator", 31 | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 32 | (int)(WindowWidth * SCREEN_ZOOM), 33 | (int)(WindowHeight * SCREEN_ZOOM), 34 | SDL_WINDOW_HIDDEN | SDL_WINDOW_ALLOW_HIGHDPI 35 | ); 36 | 37 | if (!ScreenWindow) { 38 | fprintf(stderr, "failed to create window\n"); 39 | exit(1); 40 | } 41 | 42 | SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitor"); 43 | SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "1"); 44 | 45 | // set scale filtering mode 46 | char filtering[2]; 47 | sprintf(filtering, "%d", MainScreen.ScaleFiltering); 48 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, filtering); // 0: point, 1 = linear 49 | 50 | ScreenRenderer = SDL_CreateRenderer(ScreenWindow, -1, 0); 51 | 52 | if (!ScreenRenderer) { 53 | fprintf(stderr, "failed to create renderer\n"); 54 | exit(1); 55 | } 56 | 57 | SDL_RenderSetLogicalSize(ScreenRenderer, WindowWidth, WindowHeight); 58 | 59 | WindowRect = (SDL_Rect) { 60 | .w = WindowWidth, 61 | .h = WindowHeight 62 | }; 63 | } 64 | 65 | void ScreenDraw() { 66 | MainScreen.Draw(&MainScreen); 67 | 68 | SDL_Rect screenrect = { 69 | .w = MainScreen.Width, 70 | .h = MainScreen.Height, 71 | }; 72 | 73 | SDL_Rect winrect = { 74 | .w = MainScreen.Width, 75 | .h = MainScreen.Height, 76 | .x = 0, 77 | .y = 0 78 | }; 79 | 80 | if ((WindowRect.w != screenrect.w) || (WindowRect.h != screenrect.h)) { 81 | int oldx; 82 | int oldy; 83 | 84 | SDL_GetWindowPosition(ScreenWindow, &oldx, &oldy); 85 | 86 | oldx += (WindowRect.w - screenrect.w)/2; 87 | oldy += (WindowRect.h - screenrect.h)/2; 88 | 89 | SDL_SetWindowSize(ScreenWindow, screenrect.w, screenrect.h); 90 | SDL_SetWindowPosition(ScreenWindow, oldx, oldy); 91 | 92 | WindowRect.w = screenrect.w; 93 | WindowRect.h = screenrect.h; 94 | } 95 | 96 | SDL_RenderClear(ScreenRenderer); 97 | SDL_RenderCopy(ScreenRenderer, MainScreen.Texture, &screenrect, &winrect); 98 | SDL_RenderPresent(ScreenRenderer); 99 | 100 | if (ScreenFirstDraw) { 101 | SDL_ShowWindow(ScreenWindow); 102 | ScreenFirstDraw = false; 103 | } 104 | } 105 | 106 | int ScreenProcessEvents() { 107 | SDL_Event event; 108 | while (SDL_PollEvent(&event)) { 109 | switch (event.type) { 110 | case SDL_QUIT: { 111 | return 1; 112 | } 113 | 114 | case SDL_WINDOWEVENT: { 115 | break; 116 | } 117 | 118 | case SDL_MOUSEMOTION: { 119 | if (MainScreen.MouseMoved) 120 | MainScreen.MouseMoved(event.motion.x, event.motion.y); 121 | break; 122 | } 123 | 124 | case SDL_MOUSEBUTTONDOWN: { 125 | if (MainScreen.MousePressed) 126 | MainScreen.MousePressed(event.button.button); 127 | break; 128 | } 129 | 130 | case SDL_MOUSEBUTTONUP: { 131 | if (MainScreen.MouseReleased) 132 | MainScreen.MouseReleased(event.button.button); 133 | break; 134 | } 135 | 136 | case SDL_KEYDOWN: 137 | if (MainScreen.KeyPressed) 138 | MainScreen.KeyPressed(event.key.keysym.scancode); 139 | break; 140 | 141 | case SDL_KEYUP: 142 | if (MainScreen.KeyReleased) 143 | MainScreen.KeyReleased(event.key.keysym.scancode); 144 | break; 145 | 146 | case SDL_DROPFILE: 147 | if (MainScreen.DropFile) { 148 | char *file = event.drop.file; 149 | MainScreen.DropFile(file); 150 | SDL_free(file); 151 | } 152 | break; 153 | } 154 | } 155 | 156 | return 0; 157 | } 158 | 159 | struct SDL_Texture *ScreenGetTexture(struct Screen *screen) { 160 | if (screen->Texture) { 161 | return screen->Texture; 162 | } 163 | 164 | screen->Texture = SDL_CreateTexture( 165 | ScreenRenderer, 166 | SDL_PIXELFORMAT_ABGR8888, 167 | SDL_TEXTUREACCESS_STREAMING, 168 | screen->Width, 169 | screen->Height 170 | ); 171 | 172 | return screen->Texture; 173 | } 174 | 175 | void ScreenCreate( 176 | int w, int h, 177 | int filtering, 178 | ScreenDrawF draw, 179 | ScreenKeyPressedF keypressed, 180 | ScreenKeyReleasedF keyreleased, 181 | ScreenMousePressedF mousepressed, 182 | ScreenMouseReleasedF mousereleased, 183 | ScreenMouseMovedF mousemoved, 184 | ScreenDropFileF dropfile 185 | ) { 186 | 187 | if (w > WindowWidth) 188 | WindowWidth = w; 189 | 190 | if (h > WindowHeight) 191 | WindowHeight = h; 192 | 193 | MainScreen.Width = w; 194 | MainScreen.Height = h; 195 | MainScreen.ScaleFiltering = filtering; 196 | MainScreen.Draw = draw; 197 | MainScreen.KeyPressed = keypressed; 198 | MainScreen.KeyReleased = keyreleased; 199 | MainScreen.MousePressed = mousepressed; 200 | MainScreen.MouseReleased = mousereleased; 201 | MainScreen.MouseMoved = mousemoved; 202 | MainScreen.DropFile = dropfile; 203 | } 204 | -------------------------------------------------------------------------------- /src/screen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Screen; 6 | 7 | typedef void (*ScreenDrawF)(struct Screen *screen); 8 | typedef void (*ScreenKeyPressedF)(int sdl_scancode); 9 | typedef void (*ScreenKeyReleasedF)(int sdl_scancode); 10 | typedef void (*ScreenMousePressedF)(int button); 11 | typedef void (*ScreenMouseReleasedF)(int button); 12 | typedef void (*ScreenMouseMovedF)(int dx, int dy); 13 | typedef void (*ScreenDropFileF)(char *filename); 14 | 15 | struct Screen { 16 | int Width; 17 | int Height; 18 | int ScaleFiltering; 19 | SDL_Texture *Texture; 20 | ScreenDrawF Draw; 21 | ScreenKeyPressedF KeyPressed; 22 | ScreenKeyReleasedF KeyReleased; 23 | ScreenMousePressedF MousePressed; 24 | ScreenMouseReleasedF MouseReleased; 25 | ScreenMouseMovedF MouseMoved; 26 | ScreenDropFileF DropFile; 27 | }; 28 | 29 | void ScreenInit(); 30 | 31 | void ScreenDraw(); 32 | 33 | int ScreenProcessEvents(); 34 | 35 | struct SDL_Texture *ScreenGetTexture(struct Screen *screen); 36 | 37 | void ScreenCreate( 38 | int w, int h, 39 | int filtering, 40 | ScreenDrawF draw, 41 | ScreenKeyPressedF keypressed, 42 | ScreenKeyReleasedF keyreleased, 43 | ScreenMousePressedF mousepressed, 44 | ScreenMouseReleasedF mousereleased, 45 | ScreenMouseMovedF mousemoved, 46 | ScreenDropFileF dropfile 47 | ); 48 | -------------------------------------------------------------------------------- /src/serial.c: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef WINDOWS 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static struct termios saved_tios; 10 | static bool is_terminal = false; 11 | 12 | static void exit_handler(void) { 13 | if (is_terminal) 14 | tcsetattr(0, TCSANOW, &saved_tios); 15 | } 16 | 17 | void serial_init(void) { 18 | struct termios tios; 19 | 20 | if (tcgetattr(0, &tios) != -1) { 21 | is_terminal = 1; 22 | saved_tios = tios; 23 | 24 | atexit(exit_handler); 25 | } 26 | 27 | if (is_terminal) { 28 | tios.c_lflag &= ~(ICANON|ECHO); 29 | tcsetattr(0, TCSANOW, &tios); 30 | } 31 | } 32 | 33 | int serial_get(void) { 34 | fd_set readfds; 35 | int fd = STDIN_FILENO; 36 | struct timeval tm = { 0, 0 }; 37 | 38 | FD_ZERO(&readfds); 39 | FD_SET(fd, &readfds); 40 | 41 | int ready = select(fd + 1, &readfds, NULL, NULL, &tm); 42 | 43 | if (ready == 1 && FD_ISSET(fd, &readfds)) { 44 | char c; 45 | int ret = read(fd, &c, 1); 46 | 47 | if (ret == 1) 48 | return c; 49 | } 50 | 51 | return 0; 52 | } 53 | 54 | #endif 55 | 56 | void serial_put(int value) { 57 | putchar((int) value); 58 | fflush(stdout); 59 | } 60 | -------------------------------------------------------------------------------- /src/serial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void serial_init(void); 4 | int serial_get(void); 5 | void serial_put(int value); 6 | --------------------------------------------------------------------------------