dale-8a

CHIP-8 emulator in POSIX AWK
git clone git://git.luxferre.top/dale-8a.git
Log | Files | Refs | README | LICENSE

README.md (9552B)


      1 # DALE-8A: a CHIP-8 platform emulator for POSIX AWK
      2 
      3 This is an advanced port of my previous JS-based CHIP-8 emulator, [DALE-8](https://gitlab.com/suborg/dale-8/), to the AWK programming language in its standard (POSIX) variation. The port was also inspired by [awk-chip8 by patsie75](https://github.com/patsie75/awk-chip8) although not a single piece of code is used from there as that emulator heavily depends on GAWK-specific features and doesn't emulate certain ROM quirks. Compared to the original DALE-8, DALE-8A drops the sound output capability but implements everything else using CLI pseudographics and also is fully compatible with low-res CHIP-8 ROMs developed using the [Octo](https://johnearnest.github.io/Octo/index.html) IDE. All the required interactive input and binary loading functions are provided by my own POSIX-compatible library, `tgl.awk` (The Great Library). As such, DALE-8A externally depends on the `stty` and `od` commands only.
      4 
      5 Since AWK environments can vary in terms of execution speed, DALE-8A performs all necessary profiling before running the main code in order to reduce dependency on external timing utilities like `sleep`. Because this profiling depends on the `$EPOCHREALTIME` environment variable, it's recommended to run DALE-8A from the shell that supports it, like Bash 5.x and above or BusyBox with the corresponding compilation flags. In case this variable is unavailable, a fallback timing method is used which is much less accurate and can make emulation too slow or too fast.
      6 
      7 DALE-8A was created more as an excercise to improve the algorithmic part of CHIP-8 emulation and to practice optimizing portable AWK code. Yet, combined with `busybox awk`, it can be practically used in some embedded environments where deploying any other VM is not easy.
      8 
      9 ## Supported specification
     10 
     11 - Screen resolution: 64x32, 8px wide sprites (no extended screen mode support), 60 Hz refresh rate
     12 - Color palette: monochrome (both foreground and background colors are configurable)
     13 - Memory: 4096 bytes
     14 - 16 operation registers: V0 to VF
     15 - Service registers: address register I, delay timer DT and sound timer ST
     16 - 16-digit CHR ROM (loaded at 0x80)
     17 - 3584-byte PRG ROM (loaded at 0x200)
     18 - Subroutine call stack with the depth of 1792 (theoretically covers all loadable memory)
     19 - All standard 35 opcodes support (RCA-specific `0NNN` calls, except `00E0` and `00EE`, are ignored) - see the list below
     20 - Five optional CPU quirks required for some games are implemented - see below
     21 - Sound timer register is supported but has no effect
     22 
     23 DALE-8A passes all relevant tests from [Timendus' CHIP-8 test suite 4.0](https://github.com/Timendus/chip8-test-suite), as well as some others, which are included in the `testroms` directory of this repo. It is capable of running everything compiled for the bare CHIP-8 in Octo by default, as well as all old games using the `LSQ` and/or `STQ` quirks.
     24 
     25 ## Usage
     26 
     27 ### Running the emulator
     28 
     29 The most convenient way of running CHIP-8 ROMs is using the shell wrapper from this package:
     30 
     31 ```
     32 sh dale8a.sh prog.ch8
     33 ```
     34 
     35 You can also run the AWK file itself directly as follows:
     36 
     37 ```
     38 LANG=C awk -f tgl.awk -f dale8a.awk [params] -- prog.ch8
     39 ```
     40 
     41 If the ROM file has `.l.ch8` extension, additional `LSQ` emulation quirk will be applied (see below). If the file has `.s.ch8` extention, additional `STQ` emulation quirk will be applied (see below). Is the file has `.sl.ch8` or `.ls.ch8` extension, both quirks will be applied.
     42 
     43 The wrapper script tries to use the default `awk` command found on your system. You can also modify the `AWKENGINE` variable assignment or uncomment one of the predefined options below. To achieve better performance, `mawk -W posix` is recommended if available.
     44 
     45 ### Configuration variables
     46 
     47 The DALE-8A script allows to pass a number of configuration variables to the engine using the standard `-v` option of AWK:
     48 
     49 - `CLOCK_FACTOR` - the number of CPU cycles executed per single frame, default 20
     50 - `PXL_COLOR` - set the main screen foreground color (1 to 7), default 2 (green)
     51 - `BG_COLOR` - set the main screen background color (0 to 7), default 0 (black)
     52 - `SBAR_COLOR`- set the statusbar foreground color (1 to 7), default 3 (yellow)
     53 - `SBAR_BG_COLOR` - set the statusbar background color (0 to 7), default 0 (black)
     54 - `EMU_QUIRK_LSQ` - apply `LSQ` quirk (if set with `-v`, overrides the filename-based setting)
     55 - `EMU_QUIRK_STQ` - apply `STQ` quirk (if set with `-v`, overrides the filename-based setting)
     56 - `EMU_QUIRK_VIP` - apply `VIP` quirk
     57 - `EMU_QUIRK_JMP` - apply `JMP` quirk
     58 - `EMU_QUIRK_CRY` - apply `CRY` quirk
     59 
     60 The clock factor variable change can be required by some games that were designed to run under high CPU rate.
     61 
     62 The color values from 0 to 7 correspond to the standard ANSI terminal codes: black, red, green, yellow, blue, magenta, cyan and white respectively. For the foreground values, the "bold" text attribute is also applied where supported, so they are brighter than usual.
     63 
     64 For the emulation quirks description, see the "Supported opcode list" section below. All the quirk-related variables are unset by default.
     65 
     66 ### Status bar
     67 
     68 Above the virtual screen, a status bar is displayed. It contains the current ROM filename and the quirk emulation status in the following order: `LSQ STQ VIP JMP CRY`. If a quirk is off, it won't appear in this list.
     69 
     70 ### Controls
     71 
     72 - **Exiting**: At any point, press Escape to exit the emulator. If running via the `dale8.sh` wrapper, it's also safe to press Ctrl+C.
     73 - **Keyboard mapping** is the same as the default one in the Octo emulator:
     74 
     75 Virtual  |Keyboard
     76 ---------|---------
     77 `1 2 3 C`|`1 2 3 4`
     78 `4 5 6 D`|`q w e r`
     79 `7 8 9 E`|`a s d f`
     80 `A 0 B F`|`z x c v`
     81 
     82 ## Supported opcode list
     83 
     84 These are all the opcodes supported by DALE-8A. The list of mnemonics is taken [from here](http://devernay.free.fr/hacks/chip8/C8TECH10.HTM). All arithmetics is unsigned 8-bit (modulo 256). Arithmetics on the `I` register is unsigned 16-bit.
     85 
     86 Opcode | Assembly instruction | Meaning | Notes
     87 -------|----------------------|---------|------
     88 00E0 | CLS | Clear the screen |
     89 00EE | RET | Return from the subroutine | Does nothing if we're on the top of call stack
     90 0nnn | SYS addr | Machine ROM call at addr | Isn't used in any modern CHIP-8 programs and ignored by DALE-8A
     91 1nnn | JP addr | Unconditional jump to addr |
     92 2nnn | CALL addr | Call the subroutine at addr |
     93 3xkk | SE Vx, byte | Skip next instruction if Vx == byte |
     94 4xkk | SNE Vx, byte | Skip next instruction if Vx != byte |
     95 5xy0 | SE Vx, Vy | Skip next instruction if Vx == Vy |
     96 6xkk | LD Vx, byte | Set Vx = byte |
     97 7xkk | ADD Vx, byte | Set Vx = Vx + byte |
     98 8xy0 | LD Vx, Vy | Set Vx = Vy |
     99 8xy1 | OR Vx, Vy | Set Vx = Vx OR Vy | Bitwise OR. If `VIP` quirk is **on**, also clear the VF register
    100 8xy2 | AND Vx, Vy | Set Vx = Vx AND Vy | Bitwise AND. If `VIP` quirk is **on**, also clear the VF register
    101 8xy3 | XOR Vx, Vy | Set Vx = Vx XOR Vy | Bitwise XOR. If `VIP` quirk is **on**, also clear the VF register
    102 8xy4 | ADD Vx, Vy | Set Vx = Vx + Vy, set VF = carry\* | VF is set to 1 if the result would exceed 255, set to 0 otherwise
    103 8xy5 | SUB Vx, Vy | Set Vx = Vx - Vy, set VF = NOT borrow\* | VF is set to 0 if the result would be less than zero, set to 1 otherwise
    104 8xy6 | SHR Vx {, Vy} | Set Vx = Vy >> 1, VF is set to Vy&1 before the shift\* | If `LSQ` quirk is **on**, the instruction operates on Vx instead of Vy
    105 8xy7 | SUBN Vx, Vy | Set Vx = Vy - Vx, set VF = NOT borrow\* | VF is set to 0 if the result would be less than zero, set to 1 otherwise
    106 8xyE | SHL Vx {, Vy} | Set Vx = Vy << 1, VF is set to Vy&1 before the shift\* | If `LSQ` quirk is **on**, the instruction operates on Vx instead of Vy
    107 9xy0 | SNE Vx, Vy | Skip next instruction if Vx != Vy |
    108 Annn | LD I, addr | Set I = addr |
    109 Bnnn | JP V0, addr | Jump to location addr + V0 | If `JMP` quirk is **on**, V{addr>>8} is used instead of V0
    110 Cxkk | RND Vx, byte | Set Vx = random number AND byte | Vx = rnd(0,255) & byte 
    111 Dxyn | DRW Vx, Vy, n | Display n-byte sprite (XOR with the video memory) starting at memory location I at (Vx, Vy), set VF = collision | VF if set to 1 if **any** existing pixel of the screen was already set to 1 and the sprite overwrote it with 1, making it 0, and VF is set to 0 otherwise. If the sprite is positioned so a part of it is outside of the display width, it wraps around to the opposite side of the screen
    112 Ex9E | SKP Vx | Skip next instruction if key with the value of Vx is pressed |
    113 ExA1 | SKNP Vx | Skip next instruction if key with the value of Vx is not pressed |
    114 Fx07 | LD Vx, DT | Set Vx to the value of delay timer register |
    115 Fx0A | LD Vx, K | Block the execution, wait for keyboard input and store the result digit into Vx |
    116 Fx15 | LD DT, Vx | Set delay timer register to the value of Vx | 
    117 Fx18 | LD ST, Vx | Set sound timer register to the value of Vx |
    118 Fx1E | ADD I, Vx | Set I = I + Vx |
    119 Fx29 | LD F, Vx | Set I = location of sprite for digit stored in Vx |
    120 Fx33 | LD B, Vx | Store BCD representation of Vx in memory locations I, I+1, and I+2 |
    121 Fx55 | LD [I], Vx | Store registers V0 through Vx in memory starting at location I | If `STQ` quirk is **off**, the instruction modifies I to I + x + 1
    122 Fx65 | LD Vx, [I] | Read registers V0 through Vx from memory starting at location I | If `STQ` quirk is **off**, the instruction modifies I to I + x + 1
    123 
    124 \* If `CRY` quirk is on, modify the target register **after** setting the VF register in this operation
    125 
    126 ## Credits
    127 
    128 Created by Luxferre in 2023, released into public domain.
    129 
    130 Made in Ukraine.