diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..cca581a2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,235 @@ +## What's New + +* **02-Jan-2024**: + + - Added a new 'web api' (a set of Javascript functions which allow controlling + the integrated debugger) for the emualators: + - KC85/2, /3 and /4 + - C64 + - CPC + - Debugger: added a 'stopwatch' window to count cycles between two breakpoints + - am40010.h: video ram data is now loaded into an internal 2-byte latch + in two steps at a 2 MHz interval + + Also check out this new VSCode extension: https://marketplace.visualstudio.com/items?itemName=floooh.vscode-kcide + +* **10-Jan-2023**: A big code cleanup session affecting all emulators, + and some minor emulation improvements: + + - C11 is now required to compile the headers (because of stdalign.h usage) + - The KC85/4 emulation now implements the mysterious 'trück signal' + (which is used to synchronize the two audio flip-flops). Clearing + bit #0 on PIO port B forces both audio flip-flops to low state which + allows to put the audio flip-flops into a known state (the intended + function was to avoid that the two audio channels cancel each other + out, but more recently the feature has been used to play sampled audio). + Many thanks to dOc.K of the Moods Plateau demo group for the rubber ducking + support and sample player code for testing, this was extremely helpful! + - Another audio related problem in the KC85 emulation has been fixed + (or rather, the Beeper emulaton used by the KC85 emulators): Forcing + the beeper to a high state, and modulating the output waveform through + the volume didn't work and resulted in silence. This bug wouldn't + manifest itself with regular 'oscillating' audio output, but it broke + sampled audio playback. + - The video decoding on some systems has been slightly optimized + (ZX Spectrum, KC85/2..4, Z9001 and Z1013) + - All emulators now have a snapshot feature, mainly useful for debugging. + Snapshot files are *not* intended as interchange file formats, since even + small changes to the emulator implementation will make old snapshot files + invalid. The feature simply works by taking a copy of the emulator's state + in host memory, which is quite trivial since the entire emulator state + lives in a single nested data structure, but this is also the reason why + snapshot files are not stable, so use the feature at your own risk. + - There's now a new header 'chips/chips_common.h' with shared structs and + functions, this header now needs to be included before any other chip + or system header. + - pointer/size pairs have been replaced now with the common ```chips_range_t``` + struct + - The video decoding has changed drastically: + - The host framebuffer is now part of the emulator state and no longer provided + as a ptr/size pair from the outside. That way, snapshots automatically contain + a screenshot of the current emulator state. + - The (now internal) host framebuffer is now generally 2^N bytes wide which + simplifies address computation in the emulator's video decoding. The host + framebuffer rendering code is expected to only render a visible sub-area + out of the host framebuffer. + - The various getter functions for quering information about the display + have been replaced with a single function which returns a ```chips_display_info_t``` + struct, containing all information needed to render the emulator's + framebuffer (framebuffer dimension, bytes per pixel, pointer to the internal + framebuffer, the visible 'screen rectangle', an optional color palette, + and a boolean whether the display is in portrait mode) + - For most emulators (all except Bombjack), the host framebuffers pixel + format has changed from RGBA8 (4 bytes per pixel) to R8 (1 byte per + pixel) where each 8-bit pixel is an index into a static color palette + which is provided separately. This reduced per-tick work in the emulator + (by getting rid of 8 to 16 color palette lookups and writing less data + per tick) and allows to move part of the video decoding (the color palette + resolution) into a pixel shader running on the GPU. The only exception + is Bombjack. This still generates an RGBA8 host framebuffer because the 'hardware + palette' of the Bombjack arcade machine is 12-bits wide (4096 colors) + and thus doesn't fit into a 256-entry color palette. + - A potential buffer overflow in Bombjack has been fixed (under rare circumstances, + the sprite rendering routine could write outside of the host framebuffer - this + has been fixed, and in debug mode an assert has been added in the inner sprite + decoder loop to catch any regressions) - in the previous 'host system wrapper' + of Bombjack this problem wouldn't have resulted in a segfault, because the + externally provided framebuffer memory block had plenty of 'scribble space' + past the end of the emulator's framebuffer area. + - A somewhat annoying input problem has been fixed in Bombjack: when the debugging + UI was active, the coin input trigger would become unresponsive on host machines + with a display refresh rate >60Hz (such as M1 Macs). The reason for this was + pretty dumb and embarrassing, and thus doesn't require a detailed explanation ;P + +* **16-Dec-2021**: An entirely new 'cycle-stepped' Z80 emulator, along with + various improvements mainly in the Amstrad CPC emulation. Accompanying + blog post upcoming. A snapshot of the old emulator is under the git tag + **pre-cycle-stepped-z80** both in the [chip](https://github.com/floooh/chips/tags) + and [chips-test](https://github.com/floooh/chips-test/tags) repo. PS: blog post is here: + https://floooh.github.io/2021/12/17/cycle-stepped-z80.html + +* **14-May-2020**: A small breaking change in kbd.h: the function ```kbd_update()``` + now takes a new argument ```uint32_t frame_time_us``` which is the + current frame time (duration) in microseconds. This is necessary to + make the sticky-key handling frame rate independent. The ```sticky_frames``` + initialization parameter in ```kbd_init()``` remains unchanged. This + is the number of **60Hz frames** a key press should remain sticky. + +* **20-Jan-2020**: The i8255 and MC6847 chips emulations have been changed + to a 'tick-only API', continuing the 'API streamlining' that started + with the 6522 VIA chip. The Atom system emulation has been updated + accordingly, and the minimal necessary changes to the CPC emulation + have been added (but only as a quick hack, the other CPC support + chips haven't had their API updated yet). + +* **15-Jan-2020**: The CIA (m6526.h), VIC-II (m6569.h), SID (m6581.h) chip + emulators have merged their *_iorq() functions for reading + and writing chip registers into the regular *_tick() functions, + and the C64 emulation in systems/c64.h has been updated accordingly + (this API change started with the chips in the vic20.h and will continue for + the other chip emulators). + +* **03-Jan-2020**: Another VIA- and VIC-20 related update: + - The VIA (m6522.h) API has been simplified: the separate m6522_iorq() + function to read and write chip registers has been merged into + the m6522_tick() function, and all IO callbacks have been removed. + Instead of handling the VIA IO ports through callbacks, the port input + pins are now set before calling m6522_tick(), + and the port outputs are inspected in the pin mask returned by + m6522_tick(). Similar, if chip registers should be read or written, + the chip-select and RW pin must be set on the input pin mask + for m6522_tick(). This 'API streamlining' makes writing system + tick functions more straightforward. System schematics now translate + more directly into code which sets and inspects pin + bit masks, instead of handling the address decoding, register + reads/write, and IO through completely different code (such as + IO callbacks). + - The same API change (merging the iorq function into the tick function + has been implemented for the VIC emulation (m6561.h). In the future + the other chip emulations will follow too. See the ```_vic20_tick()``` + function in the ```systems/vic20.h``` header for a code example of how + the new VIA and VIC APIs are used in a system's tick function. + - The VIA emulation is now more feature complete and accurate, but + not yet complete: + - the entire shift-register functionality is not implemented + - timers and interrupts are not cycle accurate in some situations + - The VIC-20 emulation is now good enough to support TAP-file loading + through c1530.h datassette emulation (this depends mostly on somewhat + accurate VIA timers and interrupts). Also, the first modern demo-scene + demos are now running in the VIC-20 emulation, although with glitches + here and there. + +* **24-Dec-2019**: A small "inbetween merge" because the feature branch I + was working on became too unfocused: The plan was to add 1541 floppy + support to the C64 emulation, but I soon realized that the VIA emulation + would essentially need to be rewritten for this. To help getting the + VIA emulation right, I started with a VIC-20 system emulation. Here's + what's new: + - a new VIC-20 system emulation, still quite WIP + - started to rewrite the 6522 VIA emulation from scratch, it's still + very WIP, but works "at least as good" as the previous implementation + - a new VIC-I PAL (MOS 6561) emulation (used by the VIC-20) + - moved the datasette emulation out of c64.h into its own header + (c1530.h), for attaching peripheral devices the c64.h emulation + now emulates the interface ports (IEC and CASPORT) instead, which + the peripheral device emulations "connect to" + - started with a 1541 floppy drive emulation in the header c1541.h, + not functional yet + - various optimizations in kbd.h with the goal to make the + frequently called keyboard matrix scanning functions so cheap that + they can be called in each emulated tick + - various other minor cleanups and optimizations in m6526.h (CIA) + and m6569.h (VIC-II) + + I will experiment next with more radical changes to the VIA emulation, idea + is to merge the currently separate m6522_iorq() function (which handles + reads and writes to the VIA registers) into the regular m6522_tick() function. + If those experiments are successful, other chips will use the same + model in the future, starting with the IO/timer chips (m6526, i8255, + z80pio and z80ctc). + +* **13-Dec-2019**: The new 'cycle-stepped' 6502/6510 emulator has been merged + to master. The new emulator has a slightly different programming model, + please see the updated [header + documentation](https://github.com/floooh/chips/blob/master/chips/m6502.h) and + [this blog + post](https://floooh.github.io/2019/12/13/cycle-stepped-6502.html). I have + created a [git tag](https://github.com/floooh/chips/tree/old-m6502) which + preserves the previous emulator. All the 6502-based system emulators on the + [Tiny Emulators page](https://floooh.github.io/tiny8bit/) have been updated + with the new cycle-stepped 6502. + +* **14-Oct-2019**: Improvements to the 6502 and C64 emulation: + - All tests of the Wolfgang Lorenz test suite are passing now, except: + - irq and nmi: the timing for interrupt requests is slightly off, + this is most likely because the 6502 emulation currently doesn't + delay interrupt handling to the end of the next instruction under + some circumstances + - cia1ta, cia1tb, cia2ta, cia2tb: these are marked as "under + construction" in the test suite's readme, so I assume it's normal + that they are failing(?) + - With the remaining tests all passing, this means: + - all unintended and unstable 6502 instructions are now supported as + tested by the Wolfgang Lorenz test suite + - all instruction clock cycles are now correct (previously two + unintended NOP instructions were one clock tick off because they used + the wrong addressing mode) + - Some code cleanup and (very minor) optimizations in the 6569 (VIC-II) + emulation, and improved the raster interrupt timing which was slightly + off. + +* **05-Aug-2019**: + - The Z80 and 6502 CPU emulators are now each in a single header instead + of being split into a manually written "outer header" which includes + another code-generated header with the instruction decoder. + No functional changes (I tried a variation of the Z80 emulator which goes + back to separate byte registers in a struct instead of merging the + registers into 64-bit integers, this saved a couple KBytes code size in + WASM but was about 10% slower so I discarded that experiment) + +* **31-Dec-2018**: + - A complete set of debugging UI headers using Dear ImGui has been added, + each chip emulator has a window which visualizes the pin- and + internal-state, and there are helper windows which implement a memory + editor, memory "heatmap" (visualize read/write/execute operations), + disassembler and CPU step debugger. Finally there are 'integration + headers' which implement an entire UI for an emulated system. Note that + the implementation part of the UI headers needs to be compiled as C++, + the 'public API' of the headers are callable from C though. + - The CPU emulators (z80.h and m6502.h) have new trap handling. Instead + of predefined "slots", a trap evaluation callback is now installed, which + is called at the end of each CPU instruction. This is used extensively by + the new debugging UIs to keep track of CPU operations and breakpoint + support. + - The Amstrad CPC emulation has gained floppy disc loading support, and + the video system precision has been improved (many modern graphics demos + at least work now instead of having completely broken rendering, but + there's still more to be done). + - Loading local files via drag'n'drop has been improved in the + WebAssembly version, all emulators can now properly detect and load all + supported file formats via drag'n'drop. + +* **23-Jul-2018**: all chip emulators with callbacks now have an extra +```user_data``` argument in the callbacks which is provided in the init +function, this makes the chip emulators a bit more flexible when more than +one emulator of the same type is used in a program diff --git a/README.md b/README.md index bfda7173..4d0a59ae 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://github.com/floooh/chips/workflows/build_and_test/badge.svg)](https://github.com/floooh/chips/actions) -A toolbox of 8-bit chip-emulators, helper code and complete embeddable +A toolbox of 8-bit chip-emulators, helper code and complete embeddable system emulators in dependency-free C headers (a subset of C99 that compiles on gcc, clang and cl.exe). @@ -12,157 +12,16 @@ The example emulators, compiled to WebAssembly: https://floooh.github.io/tiny8bi For schematics, manuals and research material, see: https://github.com/floooh/emu-info -## What's New +The USP of the chip emulators is that they communicate with the outside world through +a 'pin bit mask': A 'tick' function takes an uint64_t as input where the bits +represent the chip's in/out pins, the tick function inspects the pin +bits, computes one tick, and returns a (potentially modified) pin bit mask. -* **16-Dec-2021**: An entirely new 'cycle-stepped' Z80 emulator, along with - various improvements mainly in the Amstrad CPC emulation. Accompanying - blog post upcoming. A snapshot of the old emulator is under the git tag - **pre-cycle-stepped-z80** both in the [chip](https://github.com/floooh/chips/tags) - and [chips-test](https://github.com/floooh/chips-test/tags) repo. PS: blog post is here: - https://floooh.github.io/2021/12/17/cycle-stepped-z80.html +A complete emulated computer then more or less just wires those chip emulators +together just like on a breadboard. -* **14-May-2020**: A small breaking change in kbd.h: the function ```kbd_update()``` - now takes a new argument ```uint32_t frame_time_us``` which is the - current frame time (duration) in microseconds. This is necessary to - make the sticky-key handling frame rate independent. The ```sticky_frames``` - initialization parameter in ```kbd_init()``` remains unchanged. This - is the number of **60Hz frames** a key press should remain sticky. - -* **20-Jan-2020**: The i8255 and MC6847 chips emulations have been changed - to a 'tick-only API', continuing the 'API streamlining' that started - with the 6522 VIA chip. The Atom system emulation has been updated - accordingly, and the minimal necessary changes to the CPC emulation - have been added (but only as a quick hack, the other CPC support - chips haven't had their API updated yet). - -* **15-Jan-2020**: The CIA (m6526.h), VIC-II (m6569.h), SID (m6581.h) chip - emulators have merged their *_iorq() functions for reading - and writing chip registers into the regular *_tick() functions, - and the C64 emulation in systems/c64.h has been updated accordingly - (this API change started with the chips in the vic20.h and will continue for - the other chip emulators). - -* **03-Jan-2020**: Another VIA- and VIC-20 related update: - - The VIA (m6522.h) API has been simplified: the separate m6522_iorq() - function to read and write chip registers has been merged into - the m6522_tick() function, and all IO callbacks have been removed. - Instead of handling the VIA IO ports through callbacks, the port input - pins are now set before calling m6522_tick(), - and the port outputs are inspected in the pin mask returned by - m6522_tick(). Similar, if chip registers should be read or written, - the chip-select and RW pin must be set on the input pin mask - for m6522_tick(). This 'API streamlining' makes writing system - tick functions more straightforward. System schematics now translate - more directly into code which sets and inspects pin - bit masks, instead of handling the address decoding, register - reads/write, and IO through completely different code (such as - IO callbacks). - - The same API change (merging the iorq function into the tick function - has been implemented for the VIC emulation (m6561.h). In the future - the other chip emulations will follow too. See the ```_vic20_tick()``` - function in the ```systems/vic20.h``` header for a code example of how - the new VIA and VIC APIs are used in a system's tick function. - - The VIA emulation is now more feature complete and accurate, but - not yet complete: - - the entire shift-register functionality is not implemented - - timers and interrupts are not cycle accurate in some situations - - The VIC-20 emulation is now good enough to support TAP-file loading - through c1530.h datassette emulation (this depends mostly on somewhat - accurate VIA timers and interrupts). Also, the first modern demo-scene - demos are now running in the VIC-20 emulation, although with glitches - here and there. - -* **24-Dec-2019**: A small "inbetween merge" because the feature branch I - was working on became too unfocused: The plan was to add 1541 floppy - support to the C64 emulation, but I soon realized that the VIA emulation - would essentially need to be rewritten for this. To help getting the - VIA emulation right, I started with a VIC-20 system emulation. Here's - what's new: - - a new VIC-20 system emulation, still quite WIP - - started to rewrite the 6522 VIA emulation from scratch, it's still - very WIP, but works "at least as good" as the previous implementation - - a new VIC-I PAL (MOS 6561) emulation (used by the VIC-20) - - moved the datasette emulation out of c64.h into its own header - (c1530.h), for attaching peripheral devices the c64.h emulation - now emulates the interface ports (IEC and CASPORT) instead, which - the peripheral device emulations "connect to" - - started with a 1541 floppy drive emulation in the header c1541.h, - not functional yet - - various optimizations in kbd.h with the goal to make the - frequently called keyboard matrix scanning functions so cheap that - they can be called in each emulated tick - - various other minor cleanups and optimizations in m6526.h (CIA) - and m6569.h (VIC-II) - - I will experiment next with more radical changes to the VIA emulation, idea - is to merge the currently separate m6522_iorq() function (which handles - reads and writes to the VIA registers) into the regular m6522_tick() function. - If those experiments are successful, other chips will use the same - model in the future, starting with the IO/timer chips (m6526, i8255, - z80pio and z80ctc). - -* **13-Dec-2019**: The new 'cycle-stepped' 6502/6510 emulator has been merged - to master. The new emulator has a slightly different programming model, - please see the updated [header - documentation](https://github.com/floooh/chips/blob/master/chips/m6502.h) and - [this blog - post](https://floooh.github.io/2019/12/13/cycle-stepped-6502.html). I have - created a [git tag](https://github.com/floooh/chips/tree/old-m6502) which - preserves the previous emulator. All the 6502-based system emulators on the - [Tiny Emulators page](https://floooh.github.io/tiny8bit/) have been updated - with the new cycle-stepped 6502. - -* **14-Oct-2019**: Improvements to the 6502 and C64 emulation: - - All tests of the Wolfgang Lorenz test suite are passing now, except: - - irq and nmi: the timing for interrupt requests is slightly off, - this is most likely because the 6502 emulation currently doesn't - delay interrupt handling to the end of the next instruction under - some circumstances - - cia1ta, cia1tb, cia2ta, cia2tb: these are marked as "under - construction" in the test suite's readme, so I assume it's normal - that they are failing(?) - - With the remaining tests all passing, this means: - - all unintended and unstable 6502 instructions are now supported as - tested by the Wolfgang Lorenz test suite - - all instruction clock cycles are now correct (previously two - unintended NOP instructions were one clock tick off because they used - the wrong addressing mode) - - Some code cleanup and (very minor) optimizations in the 6569 (VIC-II) - emulation, and improved the raster interrupt timing which was slightly - off. - -* **05-Aug-2019**: - - The Z80 and 6502 CPU emulators are now each in a single header instead - of being split into a manually written "outer header" which includes - another code-generated header with the instruction decoder. - No functional changes (I tried a variation of the Z80 emulator which goes - back to separate byte registers in a struct instead of merging the - registers into 64-bit integers, this saved a couple KBytes code size in - WASM but was about 10% slower so I discarded that experiment) - -* **31-Dec-2018**: - - A complete set of debugging UI headers using Dear ImGui has been added, - each chip emulator has a window which visualizes the pin- and - internal-state, and there are helper windows which implement a memory - editor, memory "heatmap" (visualize read/write/execute operations), - disassembler and CPU step debugger. Finally there are 'integration - headers' which implement an entire UI for an emulated system. Note that - the implementation part of the UI headers needs to be compiled as C++, - the 'public API' of the headers are callable from C though. - - The CPU emulators (z80.h and m6502.h) have new trap handling. Instead - of predefined "slots", a trap evaluation callback is now installed, which - is called at the end of each CPU instruction. This is used extensively by - the new debugging UIs to keep track of CPU operations and breakpoint - support. - - The Amstrad CPC emulation has gained floppy disc loading support, and - the video system precision has been improved (many modern graphics demos - at least work now instead of having completely broken rendering, but - there's still more to be done). - - Loading local files via drag'n'drop has been improved in the - WebAssembly version, all emulators can now properly detect and load all - supported file formats via drag'n'drop. - -* **23-Jul-2018**: all chip emulators with callbacks now have an extra -```user_data``` argument in the callbacks which is provided in the init -function, this makes the chip emulators a bit more flexible when more than -one emulator of the same type is used in a program +In reality, most emulators are not quite as 'pure' (as this would affect performance +too much or complicate the emulation): some chip emulators have a small number +of callback functions and the adress decoding in the system emulators often +take shortcuts instead of simulating the actual address decoding chips +(with one exception: the lc80 emulator). diff --git a/chips/am40010.h b/chips/am40010.h index d0cc4346..71dbc1bf 100644 --- a/chips/am40010.h +++ b/chips/am40010.h @@ -8,14 +8,18 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - ~~~C + ~~~C CHIPS_ASSERT(c) ~~~ - + + Include the following files before am40010.h: + + chips/chips_common.h + ## Emulated Pins ************************************ * @@ -44,7 +48,7 @@ (TODO) ## Links - + TODO ## zlib/libpng license @@ -63,7 +67,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -75,60 +79,106 @@ extern "C" { #define AM40010_DISPLAY_WIDTH (768) #define AM40010_DISPLAY_HEIGHT (272) -#define AM40010_DBG_DISPLAY_WIDTH (1024) -#define AM40010_DBG_DISPLAY_HEIGHT (312) +#define AM40010_FRAMEBUFFER_WIDTH (1024) +#define AM40010_FRAMEBUFFER_HEIGHT (312) +#define AM40010_FRAMEBUFFER_SIZE_BYTES (AM40010_FRAMEBUFFER_WIDTH * AM40010_FRAMEBUFFER_HEIGHT) +#define AM40010_NUM_HWCOLORS (32 + 32) // 32 colors plus pure black plus debug visualization colors // Z80-compatible pins -#define AM40010_A13 (1ULL<<13) -#define AM40010_A14 (1ULL<<14) -#define AM40010_A15 (1ULL<<15) - -#define AM40010_D0 (1ULL<<16) -#define AM40010_D1 (1ULL<<17) -#define AM40010_D2 (1ULL<<18) -#define AM40010_D3 (1ULL<<19) -#define AM40010_D4 (1ULL<<20) -#define AM40010_D5 (1ULL<<21) -#define AM40010_D6 (1ULL<<22) -#define AM40010_D7 (1ULL<<23) - -#define AM40010_M1 (1ULL<<24) -#define AM40010_MREQ (1ULL<<25) -#define AM40010_IORQ (1ULL<<26) -#define AM40010_RD (1ULL<<27) -#define AM40010_WR (1ULL<<28) -#define AM40010_INT (1ULL<<30) -#define AM40010_WAIT (1ULL<<33) -#define AM40010_READY (AM40010_WAIT) +#define AM40010_PIN_A13 (13) +#define AM40010_PIN_A14 (14) +#define AM40010_PIN_A15 (15) + +#define AM40010_PIN_D0 (16) +#define AM40010_PIN_D1 (17) +#define AM40010_PIN_D2 (18) +#define AM40010_PIN_D3 (19) +#define AM40010_PIN_D4 (20) +#define AM40010_PIN_D5 (21) +#define AM40010_PIN_D6 (22) +#define AM40010_PIN_D7 (23) + +#define AM40010_PIN_M1 (24) +#define AM40010_PIN_MREQ (25) +#define AM40010_PIN_IORQ (26) +#define AM40010_PIN_RD (27) +#define AM40010_PIN_WR (28) +#define AM40010_PIN_INT (30) +#define AM40010_PIN_WAIT (33) +#define AM40010_PIN_READY (AM40010_PIN_WAIT) // MC6845 compatible pins -#define AM40010_MA0 (1ULL<<0) -#define AM40010_MA1 (1ULL<<1) -#define AM40010_MA2 (1ULL<<2) -#define AM40010_MA3 (1ULL<<3) -#define AM40010_MA4 (1ULL<<4) -#define AM40010_MA5 (1ULL<<5) -#define AM40010_MA6 (1ULL<<6) -#define AM40010_MA7 (1ULL<<7) -#define AM40010_MA8 (1ULL<<8) -#define AM40010_MA9 (1ULL<<9) -#define AM40010_MA10 (1ULL<<10) -#define AM40010_MA11 (1ULL<<11) -#define AM40010_MA12 (1ULL<<12) -#define AM40010_MA13 (1ULL<<13) - -#define AM40010_DE (1ULL<<44) -#define AM40010_VS (1ULL<<45) -#define AM40010_HS (1ULL<<46) - -#define AM40010_RA0 (1ULL<<48) -#define AM40010_RA1 (1ULL<<49) -#define AM40010_RA2 (1ULL<<50) -#define AM40010_RA3 (1ULL<<51) -#define AM40010_RA4 (1ULL<<52) +#define AM40010_PIN_MA0 (0) +#define AM40010_PIN_MA1 (1) +#define AM40010_PIN_MA2 (2) +#define AM40010_PIN_MA3 (3) +#define AM40010_PIN_MA4 (4) +#define AM40010_PIN_MA5 (5) +#define AM40010_PIN_MA6 (6) +#define AM40010_PIN_MA7 (7) +#define AM40010_PIN_MA8 (8) +#define AM40010_PIN_MA9 (9) +#define AM40010_PIN_MA10 (10) +#define AM40010_PIN_MA11 (11) +#define AM40010_PIN_MA12 (12) +#define AM40010_PIN_MA13 (13) + +#define AM40010_PIN_DE (44) +#define AM40010_PIN_VS (45) +#define AM40010_PIN_HS (46) + +#define AM40010_PIN_RA0 (48) +#define AM40010_PIN_RA1 (49) +#define AM40010_PIN_RA2 (50) +#define AM40010_PIN_RA3 (51) +#define AM40010_PIN_RA4 (52) // AM40010 specific pins (starting at pin 40) -#define AM40010_SYNC (1ULL<<41) +#define AM40010_PIN_SYNC (41) + +// pin masks +#define AM40010_A13 (1ULL<= 64 KBytes - uint32_t* rgba8_buffer; // pointer to the RGBA8 output framebuffer - size_t rgba8_buffer_size; // must be at least 1024*312*4 bytes + chips_range_t ram; // direct pointer to the gate-array-visible 4*16 KByte RAM banks + chips_range_t framebuffer; // pointer to framebuffer (at least 1024 * 312 bytes) void* user_data; // optional userdata for callbacks } am40010_desc_t; @@ -167,18 +215,10 @@ typedef struct am40010_desc_t { typedef struct am40010_registers_t { uint8_t inksel; // 5 bits uint8_t config; // bit4: IRQ reset, bit3: HROMEN, bit2: LROMEN, bit1+0: mode - uint8_t border; // 6 bits, see also border_rgba8 - uint8_t ink[16]; // 5 bits, see also ink_rgba8 + uint8_t border; // 5 bits + uint8_t ink[16]; // 5 bits } am40010_registers_t; -// decoded RGBA8 colors -typedef struct am40010_colors_t { - bool dirty; - uint32_t ink_rgba8[16]; // the current ink colors as RGBA8 - uint32_t border_rgba8; // the current border color as RGBA8 - uint32_t hw_rgba8[32]; // the hardware color RGBA8 values -} am40010_colors_t; - // vsync/video/irq generation typedef struct am40010_video_t { int hscount; // 5-bit counter updated at HSYNC falling edge @@ -187,6 +227,7 @@ typedef struct am40010_video_t { uint8_t mode; // currently active mode updated at hsync bool sync; // state of the sync output pin bool intr; // interrupt flip-flop + uint8_t latch[2]; // store video ram bytes read at 2 MHz } am40010_video_t; // CRT beam tracking @@ -214,17 +255,18 @@ typedef struct am40010_t { am40010_registers_t regs; am40010_video_t video; am40010_crt_t crt; - am40010_colors_t colors; am40010_bankswitch_t bankswitch_cb; am40010_cclk_t cclk_cb; const uint8_t* ram; - uint32_t* rgba8_buffer; void* user_data; uint64_t pins; // only for debug inspection + uint8_t* fb; // decoded framebuffer pixels as hw palette indices + uint32_t hw_colors[AM40010_NUM_HWCOLORS]; // hardware colors (different for CPC and KCC) } am40010_t; void am40010_init(am40010_t* ga, const am40010_desc_t* desc); void am40010_reset(am40010_t* ga); + /* Call the iorq function once per Z80 machine cycle when the IORQ and either RD or WR pins are set. This may call the @@ -232,8 +274,6 @@ void am40010_reset(am40010_t* ga); */ void am40010_iorq(am40010_t* ga, uint64_t cpu_pins); /* - FIXME FIXME FIXME - Call the tick function once per Z80 tcycle with the CPU pins. The am40010_tick function will call the CCLK callback as needed (at 1 MHz frequency), tick the MC6845 and AY-3-8910 from this callback. @@ -241,11 +281,16 @@ void am40010_iorq(am40010_t* ga, uint64_t cpu_pins); am40010_tick() will return a new Z80 CPU pin mask with the following pins updated: - Z80_WAIT0..2 - the right number of wait states for this machine cycle - Z80_INT - interrupt request from the gate array was triggered + AM40010_READY/Z80_WAIT - gate array wants the Z80 to wait + AM40010_INT/Z80_INT - interrupt request from the gate array was triggered */ uint64_t am40010_tick(am40010_t* ga, uint64_t cpu_pins); +// prepare am40010_t snapshot before saving +void am40010_snapshot_onsave(am40010_t* snapshot); +// fixup am40010_t snapshot after loading +void am40010_snapshot_onload(am40010_t* snapshot, am40010_t* sys); + #ifdef __cplusplus } // extern "C" #endif @@ -335,17 +380,16 @@ static void _am40010_init_crt(am40010_t* ga) { memset(&ga->crt, 0, sizeof(ga->crt)); } -// initialize the RGBA8 color caches, assumes already zero-initialized -static void _am40010_init_colors(am40010_t* ga) { +// initialize the hardware color palette +static void _am40010_init_hwcolors(am40010_t* ga) { if (ga->cpc_type != AM40010_CPC_TYPE_KCCOMPACT) { // Amstrad CPC colors - for (int i = 0; i < 32; i++) { - ga->colors.hw_rgba8[i] = _am40010_cpc_colors[i]; + for (size_t i = 0; i < 32; i++) { + ga->hw_colors[i] = _am40010_cpc_colors[i]; } - } - else { + } else { // KC Compact colors - for (int i = 0; i < 32; i++) { + for (size_t i = 0; i < 32; i++) { uint32_t c = 0xFF000000; const uint8_t val = _am40010_kcc_color_rom[i]; // color bits: xx|gg|rr|bb @@ -358,28 +402,56 @@ static void _am40010_init_colors(am40010_t* ga) { else if (g != 0) c |= 0x00007F00; // half green if (r == 0x03) c |= 0x000000FF; // full red else if (r != 0) c |= 0x0000007F; // half red - ga->colors.hw_rgba8[i] = c; + ga->hw_colors[i] = c; } } + + // debug visualization colors + for (size_t i = 0x20; i < 0x3F; i++) { + uint8_t r = 0x22, g = 0x22, b = 0x22; + if (i >= 0x30) { + // current ray position + r = 0xFF; + g = 0xFF; + b = 0xFF; + } else { + if (i & 1) { + r = 0x55; // HS set + } + if (i & 2) { + g = 0x55; // VS set + } + if (i & 4) { + r = 0xFF; // SYNC set + } + if (i & 8) { + g = 0xFF; // INTR set + } + } + ga->hw_colors[i] = 0xFF000000 | (b << 16) | (g << 8) | r; + } + + // pure black (for area outside border) + ga->hw_colors[0x3F] = 0xFF000000; } // initialize am40010_t instance void am40010_init(am40010_t* ga, const am40010_desc_t* desc) { CHIPS_ASSERT(ga && desc); CHIPS_ASSERT(desc->bankswitch_cb && desc->cclk_cb); - CHIPS_ASSERT(desc->rgba8_buffer && (desc->rgba8_buffer_size >= _AM40010_MAX_FB_SIZE)); - CHIPS_ASSERT(desc->ram && (desc->ram_size >= (64*1024))); + CHIPS_ASSERT(desc->framebuffer.ptr && (desc->framebuffer.size >= AM40010_FRAMEBUFFER_SIZE_BYTES)); + CHIPS_ASSERT(desc->ram.ptr && (desc->ram.size >= (64*1024))); memset(ga, 0, sizeof(am40010_t)); ga->cpc_type = desc->cpc_type; ga->bankswitch_cb = desc->bankswitch_cb; ga->cclk_cb = desc->cclk_cb; - ga->ram = desc->ram; - ga->rgba8_buffer = desc->rgba8_buffer; + ga->ram = desc->ram.ptr; + ga->fb = desc->framebuffer.ptr; ga->user_data = desc->user_data; _am40010_init_regs(ga); _am40010_init_video(ga); _am40010_init_crt(ga); - _am40010_init_colors(ga); + _am40010_init_hwcolors(ga); ga->bankswitch_cb(ga->ram_config, ga->regs.config, ga->rom_select, ga->user_data); } @@ -419,11 +491,9 @@ void am40010_iorq(am40010_t* ga, uint64_t pins) { will only be made visible during the tick() function */ case (1<<6): - ga->colors.dirty = true; if (ga->regs.inksel & (1<<4)) { ga->regs.border = data & 0x1F; - } - else { + } else { ga->regs.ink[ga->regs.inksel] = data & 0x1F; } break; @@ -513,8 +583,7 @@ static void _am40010_crt_tick(am40010_t* ga, bool sync) { crt->h_pos++; if (crt->h_pos == _AM40010_CRT_H_DISPLAY_START) { crt->h_blank = false; - } - else if (crt->h_pos == 64) { + } else if (crt->h_pos == 64) { // no hsync on this line new_line = true; } @@ -530,8 +599,7 @@ static void _am40010_crt_tick(am40010_t* ga, bool sync) { crt->v_pos++; if (crt->v_pos == _AM40010_CRT_V_DISPLAY_START) { crt->v_blank = false; - } - else if (crt->v_pos == 312) { + } else if (crt->v_pos == 312) { // no vsync on this frame new_frame = true; } @@ -553,8 +621,7 @@ static void _am40010_crt_tick(am40010_t* ga, bool sync) { crt->visible = true; crt->pos_x = crt->h_pos - _AM40010_CRT_VIS_X0; crt->pos_y = crt->v_pos - _AM40010_CRT_VIS_Y0; - } - else { + } else { crt->visible = false; } } @@ -604,7 +671,7 @@ static bool _am40010_sync_irq(am40010_t* ga, uint64_t crtc_pins) { ga->video.intcnt = 0; } } - + // SYNC and MODESYNC via 1 MHz 4-bit CLKCNT uint8_t clkcnt = ga->video.clkcnt; if (clkcnt == 7) { @@ -614,9 +681,8 @@ static bool _am40010_sync_irq(am40010_t* ga, uint64_t crtc_pins) { // if HSYNC is off, force the clkcnt counter to 0 if (0 == (crtc_pins & AM40010_HS)) { clkcnt = 0; - } - // FIXME: figure out why this "< 8" is needed (otherwise purple left column in Demoizart) - else if (clkcnt < 8) { + } else if (clkcnt < 8) { + // FIXME: figure out why this "< 8" is needed (otherwise purple left column in Demoizart) clkcnt++; } // v_sync is on as long as hscount is < 4 @@ -630,23 +696,26 @@ static bool _am40010_sync_irq(am40010_t* ga, uint64_t crtc_pins) { return ga->video.sync; } -static void _am40010_decode_pixels(am40010_t* ga, uint32_t* dst, uint64_t crtc_pins) { +static uint8_t _am40010_vid_read(am40010_t* ga, uint64_t crtc_pins, uint16_t cclk) { /* compute the source address from current CRTC ma (memory address) and ra (raster address) like this: - - |ma13|ma12|ra2|ra1|ra0|ma9|ma8|ma7|ma6|ma5|ma4|ma3|ma2|ma1|ma0|0| - + + |ma13|ma12|ra2|ra1|ra0|ma9|ma8|ma7|ma6|ma5|ma4|ma3|ma2|ma1|ma0|cclk| + Bits ma13 and m12 point to the 16 KByte page, and all other bits are the index into that page. */ const uint16_t addr = ((crtc_pins & 0x3000) << 2) | // MA13,MA12 ((crtc_pins & 0x3FF) << 1) | // MA9..MA0 - (((crtc_pins>>48) & 7) << 11); // RA0..RA2 - const uint8_t* src = &(ga->ram[addr]); + (((crtc_pins>>48) & 7) << 11) | // RA0..RA2 + cclk; + return ga->ram[addr]; +} + +static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst) { uint8_t c; - uint32_t p; - const uint32_t* ink = ga->colors.ink_rgba8; + uint8_t p; switch (ga->video.mode) { case 0: /* @@ -655,11 +724,11 @@ static void _am40010_decode_pixels(am40010_t* ga, uint32_t* dst, uint64_t crtc_p 0: |1|5|3|7| 1: |0|4|2|6| */ - for (int i = 0; i < 2; i++) { - c = *src++; - p = ink[((c>>7)&0x1)|((c>>2)&0x2)|((c>>3)&0x4)|((c<<2)&0x8)]; + for (size_t i = 0; i < 2; i++) { + c = ga->video.latch[i]; + p = ga->regs.ink[((c>>7)&0x1)|((c>>2)&0x2)|((c>>3)&0x4)|((c<<2)&0x8)]; *dst++ = p; *dst++ = p; *dst++ = p; *dst++ = p; - p = ink[((c>>6)&0x1)|((c>>1)&0x2)|((c>>2)&0x4)|((c<<3)&0x8)]; + p = ga->regs.ink[((c>>6)&0x1)|((c>>1)&0x2)|((c>>2)&0x4)|((c<<3)&0x8)]; *dst++ = p; *dst++ = p; *dst++ = p; *dst++ = p; } break; @@ -672,25 +741,30 @@ static void _am40010_decode_pixels(am40010_t* ga, uint32_t* dst, uint64_t crtc_p 2: |1|5| 3: |0|4| */ - for (int i = 0; i < 2; i++) { - c = *src++; - p = ink[((c>>2)&2)|((c>>7)&1)]; + for (size_t i = 0; i < 2; i++) { + c = ga->video.latch[i]; + p = ga->regs.ink[((c>>2)&2)|((c>>7)&1)]; *dst++ = p; *dst++ = p; - p = ink[((c>>1)&2)|((c>>6)&1)]; + p = ga->regs.ink[((c>>1)&2)|((c>>6)&1)]; *dst++ = p; *dst++ = p; - p = ink[((c>>0)&2)|((c>>5)&1)]; + p = ga->regs.ink[((c>>0)&2)|((c>>5)&1)]; *dst++ = p; *dst++ = p; - p = ink[((c<<1)&2)|((c>>4)&1)]; + p = ga->regs.ink[((c<<1)&2)|((c>>4)&1)]; *dst++ = p; *dst++ = p; } break; case 2: // 640x200 @ 2 colors (8 pixels per byte) - for (int i = 0; i < 2; i++) { - c = *src++; - for (int j = 7; j >= 0; j--) { - *dst++ = ink[(c>>j)&1]; - } + for (size_t i = 0; i < 2; i++) { + c = ga->video.latch[i]; + *dst++ = ga->regs.ink[(c>>7)&1]; + *dst++ = ga->regs.ink[(c>>6)&1]; + *dst++ = ga->regs.ink[(c>>5)&1]; + *dst++ = ga->regs.ink[(c>>4)&1]; + *dst++ = ga->regs.ink[(c>>3)&1]; + *dst++ = ga->regs.ink[(c>>2)&1]; + *dst++ = ga->regs.ink[(c>>1)&1]; + *dst++ = ga->regs.ink[(c>>0)&1]; } break; case 3: @@ -700,11 +774,11 @@ static void _am40010_decode_pixels(am40010_t* ga, uint32_t* dst, uint64_t crtc_p 0: |x|x|3|7| 1: |x|x|2|6| */ - for (int i = 0; i < 2; i++) { - c = *src++; - p = ink[((c>>7)&0x1)|((c>>2)&0x2)]; + for (size_t i = 0; i < 2; i++) { + c = ga->video.latch[i]; + p = ga->regs.ink[((c>>7)&0x1)|((c>>2)&0x2)]; *dst++ = p; *dst++ = p; *dst++ = p; *dst++ = p; - p = ink[((c>>6)&0x1)|((c>>1)&0x2)]; + p = ga->regs.ink[((c>>6)&0x1)|((c>>1)&0x2)]; *dst++ = p; *dst++ = p; *dst++ = p; *dst++ = p; } break; @@ -715,78 +789,67 @@ static void _am40010_decode_pixels(am40010_t* ga, uint32_t* dst, uint64_t crtc_p // video signal generator, call this at 1 MHz frequency static void _am40010_decode_video(am40010_t* ga, uint64_t crtc_pins) { if (ga->dbg_vis) { - int dst_x = ga->crt.h_pos * 16; - int dst_y = ga->crt.v_pos; - if ((dst_x <= (AM40010_DBG_DISPLAY_WIDTH-16)) && (dst_y < AM40010_DBG_DISPLAY_HEIGHT)) { - uint32_t* dst = &(ga->rgba8_buffer[dst_x + dst_y * AM40010_DBG_DISPLAY_WIDTH]); - uint8_t r = 0x22, g = 0x22, b = 0x22; + size_t dst_x = ga->crt.h_pos * 16; + size_t dst_y = ga->crt.v_pos; + if ((dst_x <= (AM40010_FRAMEBUFFER_WIDTH-16)) && (dst_y < AM40010_FRAMEBUFFER_HEIGHT)) { + uint8_t* dst = &(ga->fb[dst_x + dst_y * AM40010_FRAMEBUFFER_WIDTH]); + uint8_t* prev_dst; + if (dst == ga->fb) { + prev_dst = dst; + } else { + prev_dst = dst - 16; + } + uint8_t c = 0x20; if (crtc_pins & AM40010_HS) { - r = 0x55; + c |= 0x01; } if (crtc_pins & AM40010_VS) { - g = 0x55; + c |= 0x02; } if (ga->video.sync) { - r = 0xFF; + c |= 0x04; } if (ga->video.intr) { - g = 0xFF; + c |= 0x08; } - uint32_t c = (0xFF<<24) | (b<<16) | (g<<8) | r; if (crtc_pins & AM40010_DE) { - _am40010_decode_pixels(ga, dst, crtc_pins); - for (int i = 0; i < 16; i++) { - dst[i] = (i & 1) ? dst[i] : c; + _am40010_decode_pixels(ga, dst); + for (size_t i = 0; i < 16; i++) { + if (0 == (i & 2)) { + dst[i] = c ^ 0x10; + prev_dst[i] ^= 0x10; + } } - } - else { - for (int i = 0; i < 16; i++) { - *dst++ = (i == 0) ? 0xFF000000 : c; + } else { + for (size_t i = 0; i < 16; i++) { + if (0 == (i & 2)) { + dst[i] = c ^ 0x10; + prev_dst[i] ^= 0x10; + } else { + dst[i] = 63; + } } } } - } - else if (ga->crt.visible) { - int dst_x = ga->crt.pos_x * 16; - int dst_y = ga->crt.pos_y; + } else if (ga->crt.visible) { + size_t dst_x = ga->crt.pos_x * 16; + size_t dst_y = ga->crt.pos_y; bool black = ga->video.sync; - uint32_t* dst = &ga->rgba8_buffer[dst_x + dst_y * AM40010_DISPLAY_WIDTH]; + uint8_t* dst = &ga->fb[dst_x + dst_y * AM40010_FRAMEBUFFER_WIDTH]; if (crtc_pins & AM40010_DE) { - _am40010_decode_pixels(ga, dst, crtc_pins); - } - else if (black) { + _am40010_decode_pixels(ga, dst); + } else if (black) { for (int i = 0; i < 16; i++) { - *dst++ = 0xFF000000; + *dst++ = 63; // special 'pure black' hw color } - } - else { - uint32_t brd = ga->colors.border_rgba8; + } else { for (int i = 0; i < 16; i++) { - *dst++ = brd; + *dst++ = ga->regs.border; } } } } -// make the selected colors visible by updating the color cache -static inline void _am40010_update_colors(am40010_t* ga) { - if (ga->colors.dirty) { - ga->colors.dirty = false; - ga->colors.border_rgba8 = ga->colors.hw_rgba8[ga->regs.border]; - for (int i = 0; i < 16; i++) { - ga->colors.ink_rgba8[i] = ga->colors.hw_rgba8[ga->regs.ink[i]]; - } - } -} - -// the actions which need to happen on CCLK (1 MHz frequency) -static inline void _am40010_do_cclk(am40010_t* ga, uint64_t crtc_pins) { - _am40010_update_colors(ga); - bool sync = _am40010_sync_irq(ga, crtc_pins); - _am40010_crt_tick(ga, sync); - _am40010_decode_video(ga, crtc_pins); -} - // the tick function must be called at 4 MHz uint64_t am40010_tick(am40010_t* ga, uint64_t pins) { /* The hardware has a 'main sequencer' with a rotating bit @@ -808,24 +871,32 @@ uint64_t am40010_tick(am40010_t* ga, uint64_t pins) { the actions that need to happen at the CCLK tick NOTE: the actual position where RDY and CCLK happens is important, experiment with 0, 1, 2, 3. - + NOTE: Logon's Run crashes on rdy:1 and rdy:2 */ - const bool rdy = 0 != (ga->seq_tick_count & 3); - const bool cclk = 1 == (ga->seq_tick_count & 3); + const bool rdy = 0 != (ga->seq_tick_count & 3); + const bool cclk1 = 1 == (ga->seq_tick_count & 3); + const bool cclk0 = 3 == (ga->seq_tick_count & 3); if (rdy) { // READY is connected to Z80 WAIT, this sets the WAIT pin // in 3 out of 4 CPU clock cycles pins |= AM40010_READY; - } - else { + } else { pins &= ~AM40010_READY; } - if (cclk) { + if (cclk0) { + // read first video ram byte uint64_t crtc_pins = ga->cclk_cb(ga->user_data); - _am40010_do_cclk(ga, crtc_pins); + ga->video.latch[0] = _am40010_vid_read(ga, crtc_pins, 0); + bool sync = _am40010_sync_irq(ga, crtc_pins); + _am40010_crt_tick(ga, sync); ga->crtc_pins = crtc_pins; } + if (cclk1) { + // read second video ram byte + ga->video.latch[1] = _am40010_vid_read(ga, ga->crtc_pins, 1); + _am40010_decode_video(ga, ga->crtc_pins); + } // perform the per-4Mhz-tick actions, the AM40010_READY pin is also the Z80_WAIT pin if ((ga->regs.config & AM40010_CONFIG_IRQRESET) != 0) { @@ -850,4 +921,23 @@ uint64_t am40010_tick(am40010_t* ga, uint64_t pins) { ga->pins = pins | ((AM40010_DE|AM40010_HS|AM40010_VS) & ga->crtc_pins); return pins; } + +void am40010_snapshot_onsave(am40010_t* snapshot) { + CHIPS_ASSERT(snapshot); + snapshot->bankswitch_cb = 0; + snapshot->cclk_cb = 0; + snapshot->user_data = 0; + snapshot->ram = 0; + snapshot->fb = 0; +} + +void am40010_snapshot_onload(am40010_t* snapshot, am40010_t* sys) { + CHIPS_ASSERT(snapshot && sys); + snapshot->bankswitch_cb = sys->bankswitch_cb; + snapshot->cclk_cb = sys->cclk_cb; + snapshot->user_data = sys->user_data; + snapshot->ram = sys->ram; + snapshot->fb = sys->fb; +} + #endif // CHIPS_IMPL diff --git a/chips/ay38910.h b/chips/ay38910.h index 05a7296c..cedb5ab0 100644 --- a/chips/ay38910.h +++ b/chips/ay38910.h @@ -4,11 +4,11 @@ Do this: #define CHIPS_IMPL - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provde the following macros with your own implementation - + CHIPS_ASSERT(c) -- your own assert macro (default: assert(c)) EMULATED PINS: @@ -49,7 +49,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. */ #include #include @@ -74,43 +74,73 @@ extern "C" { */ // 8 bits data/address bus shared with CPU data bus -#define AY38910_DA0 (1ULL<<16) -#define AY38910_DA1 (1ULL<<17) -#define AY38910_DA2 (1ULL<<18) -#define AY38910_DA3 (1ULL<<19) -#define AY38910_DA4 (1ULL<<20) -#define AY38910_DA5 (1ULL<<21) -#define AY38910_DA6 (1ULL<<22) -#define AY38910_DA7 (1ULL<<23) +#define AY38910_PIN_DA0 (16) +#define AY38910_PIN_DA1 (17) +#define AY38910_PIN_DA2 (18) +#define AY38910_PIN_DA3 (19) +#define AY38910_PIN_DA4 (20) +#define AY38910_PIN_DA5 (21) +#define AY38910_PIN_DA6 (22) +#define AY38910_PIN_DA7 (23) // reset pin shared with CPU -#define AY38910_RESET (1ULL<<34) +#define AY38910_PIN_RESET (34) // chip-specific pins start at position 40 -#define AY38910_BDIR (1ULL<<40) -#define AY38910_BC1 (1ULL<<41) +#define AY38910_PIN_BDIR (40) +#define AY38910_PIN_BC1 (41) // virtual 'audio sample ready' pin -#define AY38910_SAMPLE (1ULL<<42) +#define AY38910_PIN_SAMPLE (42) // IO port pins -#define AY38910_IOA0 (1ULL<<48) -#define AY38910_IOA1 (1ULL<<49) -#define AY38910_IOA2 (1ULL<<50) -#define AY38910_IOA3 (1ULL<<51) -#define AY38910_IOA4 (1ULL<<52) -#define AY38910_IOA5 (1ULL<<53) -#define AY38910_IOA6 (1ULL<<54) -#define AY38910_IOA7 (1ULL<<55) - -#define AY38910_IOB0 (1ULL<<56) -#define AY38910_IOB1 (1ULL<<57) -#define AY38910_IOB2 (1ULL<<58) -#define AY38910_IOB3 (1ULL<<59) -#define AY38910_IOB4 (1ULL<<60) -#define AY38910_IOB5 (1ULL<<61) -#define AY38910_IOB6 (1ULL<<62) -#define AY38910_IOB7 (1ULL<<63) +#define AY38910_PIN_IOA0 (48) +#define AY38910_PIN_IOA1 (49) +#define AY38910_PIN_IOA2 (50) +#define AY38910_PIN_IOA3 (51) +#define AY38910_PIN_IOA4 (52) +#define AY38910_PIN_IOA5 (53) +#define AY38910_PIN_IOA6 (54) +#define AY38910_PIN_IOA7 (55) + +#define AY38910_PIN_IOB0 (56) +#define AY38910_PIN_IOB1 (57) +#define AY38910_PIN_IOB2 (58) +#define AY38910_PIN_IOB3 (59) +#define AY38910_PIN_IOB4 (60) +#define AY38910_PIN_IOB5 (61) +#define AY38910_PIN_IOB6 (62) +#define AY38910_PIN_IOB7 (63) + +// pin bit masks +#define AY38910_DA0 (1ULL<addr = addr; } +void ay38910_snapshot_onsave(ay38910_t* snapshot) { + CHIPS_ASSERT(snapshot); + snapshot->in_cb = 0; + snapshot->out_cb = 0; + snapshot->user_data = 0; +} + +void ay38910_snapshot_onload(ay38910_t* snapshot, ay38910_t* sys) { + CHIPS_ASSERT(snapshot && sys); + snapshot->in_cb = sys->in_cb; + snapshot->out_cb = sys->out_cb; + snapshot->user_data = sys->user_data; +} #endif /* CHIPS_IMPL */ diff --git a/chips/chips_common.h b/chips/chips_common.h new file mode 100644 index 00000000..340b0da2 --- /dev/null +++ b/chips/chips_common.h @@ -0,0 +1,116 @@ +#pragma once +/* + sys_common.h + + Common data types for chips system headers. + + ## zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + void* ptr; + size_t size; +} chips_range_t; + +typedef struct { + int width, height; +} chips_dim_t; + +typedef struct { + int x, y, width, height; +} chips_rect_t; + +typedef struct { + struct { + chips_dim_t dim; // framebuffer dimensions in pixels + chips_range_t buffer; + size_t bytes_per_pixel; // 1 or 4 + } frame; + chips_rect_t screen; + chips_range_t palette; + bool portrait; +} chips_display_info_t; + +typedef struct { + void (*func)(const float* samples, int num_samples, void* user_data); + void* user_data; +} chips_audio_callback_t; + +typedef void (*chips_debug_func_t)(void* user_data, uint64_t pins); +typedef struct { + struct { + chips_debug_func_t func; + void* user_data; + } callback; + bool* stopped; +} chips_debug_t; + +typedef struct { + chips_audio_callback_t callback; + int num_samples; + int sample_rate; + float volume; +} chips_audio_desc_t; + +// prepare chips_audio_t snapshot for saving +void chips_audio_callback_snapshot_onsave(chips_audio_callback_t* snapshot); +// fixup chips_audio_t snapshot after loading +void chips_audio_callback_snapshot_onload(chips_audio_callback_t* snapshot, chips_audio_callback_t* sys); +// prepare chips_debut_t snapshot for saving +void chips_debug_snapshot_onsave(chips_debug_t* snapshot); +// fixup chips_debug_t snapshot after loading +void chips_debug_snapshot_onload(chips_debug_t* snapshot, chips_debug_t* sys); + +#ifdef __cplusplus +} // extern "C" +#endif + +/*--- IMPLEMENTATION ---------------------------------------------------------*/ +#ifdef CHIPS_IMPL + +void chips_audio_callback_snapshot_onsave(chips_audio_callback_t* snapshot) { + snapshot->func = 0; + snapshot->user_data = 0; +} + +void chips_audio_callback_snapshot_onload(chips_audio_callback_t* snapshot, chips_audio_callback_t* sys) { + snapshot->func = sys->func; + snapshot->user_data = sys->user_data; +} + +void chips_debug_snapshot_onsave(chips_debug_t* snapshot) { + snapshot->callback.func = 0; + snapshot->callback.user_data = 0; + snapshot->stopped = 0; +} + +void chips_debug_snapshot_onload(chips_debug_t* snapshot, chips_debug_t* sys) { + snapshot->callback.func = sys->callback.func; + snapshot->callback.user_data = sys->callback.user_data; + snapshot->stopped = sys->stopped; +} + +#endif // CHIPS_IMPL diff --git a/chips/fdd_cpc.h b/chips/fdd_cpc.h index 9ba17af9..1fd5a72c 100644 --- a/chips/fdd_cpc.h +++ b/chips/fdd_cpc.h @@ -35,14 +35,14 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #ifdef __cplusplus extern "C" { #endif /* load Amstrad CPC .dsk file format */ -bool fdd_cpc_insert_dsk(fdd_t* fdd, const uint8_t* data, int data_size); +bool fdd_cpc_insert_dsk(fdd_t* fdd, chips_range_t data); #ifdef __cplusplus } /* extern "C" */ @@ -89,9 +89,9 @@ typedef struct { } _fdd_cpc_dsk_sector_info; /* parse a standard .dsk image */ -static bool _fdd_cpc_parse_dsk(fdd_t* fdd, bool ext, const uint8_t* data, int data_size) { +static bool _fdd_cpc_parse_dsk(fdd_t* fdd, bool ext, chips_range_t data) { CHIPS_ASSERT(fdd); - const _fdd_cpc_dsk_header* hdr = (_fdd_cpc_dsk_header*) data; + const _fdd_cpc_dsk_header* hdr = (_fdd_cpc_dsk_header*)data.ptr; if (hdr->num_sides > 2) { return false; } @@ -100,16 +100,16 @@ static bool _fdd_cpc_parse_dsk(fdd_t* fdd, bool ext, const uint8_t* data, int da } /* copy the data blob to the local buffer */ - CHIPS_ASSERT(data_size <= FDD_MAX_DISC_SIZE); - fdd->data_size = data_size; - memcpy(fdd->data, data, fdd->data_size); + CHIPS_ASSERT(data.size <= FDD_MAX_DISC_SIZE); + fdd->data_size = data.size; + memcpy(fdd->data, data.ptr, fdd->data_size); /* setup the disc structure */ fdd_disc_t* disc = &fdd->disc; disc->formatted = true; disc->num_sides = hdr->num_sides; disc->num_tracks = hdr->num_tracks; - int data_offset = sizeof(_fdd_cpc_dsk_header); + size_t data_offset = sizeof(_fdd_cpc_dsk_header); for (int track_index = 0; track_index < disc->num_tracks; track_index++) { for (int side_index = 0; side_index < disc->num_sides; side_index++) { fdd_track_t* track = &disc->tracks[side_index][track_index]; @@ -126,13 +126,13 @@ static bool _fdd_cpc_parse_dsk(fdd_t* fdd, bool ext, const uint8_t* data, int da if (0 != memcmp("Track-Info", track_info->magic, 10)) { return false; } - if ((data_offset + track_size) > data_size) { + if ((data_offset + track_size) > data.size) { return false; } track->data_offset = data_offset; track->data_size = track_size; track->num_sectors = track_info->num_sectors; - int sector_data_offset = data_offset + 0x100; + size_t sector_data_offset = data_offset + 0x100; const _fdd_cpc_dsk_sector_info* sector_infos = (const _fdd_cpc_dsk_sector_info*) (track_info+1); for (int sector_index = 0; sector_index < track->num_sectors; sector_index++) { fdd_sector_t* sector = &track->sectors[sector_index]; @@ -169,24 +169,24 @@ static bool _fdd_cpc_parse_dsk(fdd_t* fdd, bool ext, const uint8_t* data, int da return true; } -bool fdd_cpc_insert_dsk(fdd_t* fdd, const uint8_t* data, int data_size) { +bool fdd_cpc_insert_dsk(fdd_t* fdd, chips_range_t data) { CHIPS_ASSERT(fdd); CHIPS_ASSERT(sizeof(_fdd_cpc_dsk_header) == 256); CHIPS_ASSERT(sizeof(_fdd_cpc_dsk_track_info) == 24); CHIPS_ASSERT(sizeof(_fdd_cpc_dsk_sector_info) == 8); - CHIPS_ASSERT(data && (data_size > 0)); + CHIPS_ASSERT(data.ptr && (data.size > 0)); if (fdd->has_disc) { fdd_eject_disc(fdd); } /* check if the header is valid */ - if (data_size > FDD_MAX_DISC_SIZE) { + if (data.size > FDD_MAX_DISC_SIZE) { return false; } - if (data_size <= (int)sizeof(_fdd_cpc_dsk_header)) { + if (data.size <= sizeof(_fdd_cpc_dsk_header)) { return false; } - const _fdd_cpc_dsk_header* hdr = (_fdd_cpc_dsk_header*) data; + const _fdd_cpc_dsk_header* hdr = (_fdd_cpc_dsk_header*) data.ptr; bool ext = false; bool valid = false; if (0 == memcmp(hdr->magic, "MV - CPC", 8)) { @@ -197,7 +197,7 @@ bool fdd_cpc_insert_dsk(fdd_t* fdd, const uint8_t* data, int data_size) { ext = true; } if (valid) { - if (!_fdd_cpc_parse_dsk(fdd, ext, data, data_size)) { + if (!_fdd_cpc_parse_dsk(fdd, ext, data)) { fdd_eject_disc(fdd); return false; } diff --git a/chips/i8255.h b/chips/i8255.h index a25895d4..daae224c 100644 --- a/chips/i8255.h +++ b/chips/i8255.h @@ -4,11 +4,11 @@ Do this: #define CHIPS_IMPL - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + CHIPS_ASSERT(c) -- your own assert macro (default: assert(c)) EMULATED PINS: @@ -32,7 +32,7 @@ - mode 2 (bi-directional bus) - interrupts - input latches - + FIXME: documentation ## zlib/libpng license @@ -51,7 +51,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. */ #include #include @@ -82,63 +82,101 @@ extern "C" { Invoke those operations via to i8255_iorq() function. */ -/* control pins shared with Z80 */ -#define I8255_RD (1ULL<<27) /* read from PPI, shared with Z80_RD! */ -#define I8255_WR (1ULL<<28) /* write to PPI, shared with Z80_WR! */ - -/* i8255 control pins */ -#define I8255_CS (1ULL<<40) /* chip-select, i8255 responds to RW/WR when this is active */ - -/* register addressing pins same as address bus pins A0 and A1 */ -#define I8255_A0 (1ULL<<0) /* address bit 0 */ -#define I8255_A1 (1ULL<<1) /* address bit 1 */ - -/* data bus pins shared with CPU */ -#define I8255_D0 (1ULL<<16) -#define I8255_D1 (1ULL<<17) -#define I8255_D2 (1ULL<<18) -#define I8255_D3 (1ULL<<19) -#define I8255_D4 (1ULL<<20) -#define I8255_D5 (1ULL<<21) -#define I8255_D6 (1ULL<<22) -#define I8255_D7 (1ULL<<23) - -/* port A pins */ -#define I8255_PA0 (1ULL<<48) -#define I8255_PA1 (1ULL<<49) -#define I8255_PA2 (1ULL<<50) -#define I8255_PA3 (1ULL<<51) -#define I8255_PA4 (1ULL<<52) -#define I8255_PA5 (1ULL<<53) -#define I8255_PA6 (1ULL<<54) -#define I8255_PA7 (1ULL<<55) -#define I8255_PA_PINS (I8255_PA0|I8255_PA1|I8255_PA2|I8255_PA3|I8255_PA4|I8255_PA5|I8255_PA6|I8255_PA7) - -/* port B pins */ -#define I8255_PB0 (1ULL<<56) -#define I8255_PB1 (1ULL<<57) -#define I8255_PB2 (1ULL<<58) -#define I8255_PB3 (1ULL<<59) -#define I8255_PB4 (1ULL<<60) -#define I8255_PB5 (1ULL<<61) -#define I8255_PB6 (1ULL<<62) -#define I8255_PB7 (1ULL<<63) -#define I8255_PB_PINS (I8255_PB0|I8255_PB1|I8255_PB2|I8255_PB3|I8255_PB4|I8255_PB5|I8255_PB6|I8255_PB7) +// control pins shared with Z80 +#define I8255_PIN_RD (27) // read from PPI, shared with Z80_RD! +#define I8255_PIN_WR (28) // write to PPI, shared with Z80_WR! + +// i8255 control pins +#define I8255_PIN_CS (40) // chip-select, i8255 responds to RW/WR when this is active + +// register addressing pins same as address bus pins A0 and A1 +#define I8255_PIN_A0 (0) +#define I8255_PIN_A1 (1) + +// data bus pins shared with CPU +#define I8255_PIN_D0 (16) +#define I8255_PIN_D1 (17) +#define I8255_PIN_D2 (18) +#define I8255_PIN_D3 (19) +#define I8255_PIN_D4 (20) +#define I8255_PIN_D5 (21) +#define I8255_PIN_D6 (22) +#define I8255_PIN_D7 (23) + +// port A pins +#define I8255_PIN_PA0 (48) +#define I8255_PIN_PA1 (49) +#define I8255_PIN_PA2 (50) +#define I8255_PIN_PA3 (51) +#define I8255_PIN_PA4 (52) +#define I8255_PIN_PA5 (53) +#define I8255_PIN_PA6 (54) +#define I8255_PIN_PA7 (55) + +// port B pins +#define I8255_PIN_PB0 (56) +#define I8255_PIN_PB1 (57) +#define I8255_PIN_PB2 (58) +#define I8255_PIN_PB3 (59) +#define I8255_PIN_PB4 (60) +#define I8255_PIN_PB5 (61) +#define I8255_PIN_PB6 (62) +#define I8255_PIN_PB7 (63) /* port C pins, NOTE: these overlap with address bus pins A8..A15! don't forget to clear upper 8 address bus pins when reusing the CPU pin mask for the i8255! */ -#define I8255_PC0 (1ULL<<8) -#define I8255_PC1 (1ULL<<9) -#define I8255_PC2 (1ULL<<10) -#define I8255_PC3 (1ULL<<11) -#define I8255_PC4 (1ULL<<12) -#define I8255_PC5 (1ULL<<13) -#define I8255_PC6 (1ULL<<14) -#define I8255_PC7 (1ULL<<15) +#define I8255_PIN_PC0 (8) +#define I8255_PIN_PC1 (9) +#define I8255_PIN_PC2 (10) +#define I8255_PIN_PC3 (11) +#define I8255_PIN_PC4 (12) +#define I8255_PIN_PC5 (13) +#define I8255_PIN_PC6 (14) +#define I8255_PIN_PC7 (15) + +// pin bit masks +#define I8255_RD (1ULL< #include @@ -207,50 +207,89 @@ extern "C" { #endif -/* address bus pins */ -#define M6502_A0 (1ULL<<0) -#define M6502_A1 (1ULL<<1) -#define M6502_A2 (1ULL<<2) -#define M6502_A3 (1ULL<<3) -#define M6502_A4 (1ULL<<4) -#define M6502_A5 (1ULL<<5) -#define M6502_A6 (1ULL<<6) -#define M6502_A7 (1ULL<<7) -#define M6502_A8 (1ULL<<8) -#define M6502_A9 (1ULL<<9) -#define M6502_A10 (1ULL<<10) -#define M6502_A11 (1ULL<<11) -#define M6502_A12 (1ULL<<12) -#define M6502_A13 (1ULL<<13) -#define M6502_A14 (1ULL<<14) -#define M6502_A15 (1ULL<<15) - -/* data bus pins */ -#define M6502_D0 (1ULL<<16) -#define M6502_D1 (1ULL<<17) -#define M6502_D2 (1ULL<<18) -#define M6502_D3 (1ULL<<19) -#define M6502_D4 (1ULL<<20) -#define M6502_D5 (1ULL<<21) -#define M6502_D6 (1ULL<<22) -#define M6502_D7 (1ULL<<23) - -/* control pins */ -#define M6502_RW (1ULL<<24) /* out: memory read or write access */ -#define M6502_SYNC (1ULL<<25) /* out: start of a new instruction */ -#define M6502_IRQ (1ULL<<26) /* in: maskable interrupt requested */ -#define M6502_NMI (1ULL<<27) /* in: non-maskable interrupt requested */ -#define M6502_RDY (1ULL<<28) /* in: freeze execution at next read cycle */ -#define M6510_AEC (1ULL<<29) /* in, m6510 only, put bus lines into tristate mode, not implemented */ -#define M6502_RES (1ULL<<30) /* request RESET */ - -/* m6510 IO port pins */ -#define M6510_P0 (1ULL<<32) -#define M6510_P1 (1ULL<<33) -#define M6510_P2 (1ULL<<34) -#define M6510_P3 (1ULL<<35) -#define M6510_P4 (1ULL<<36) -#define M6510_P5 (1ULL<<37) +// address bus pins +#define M6502_PIN_A0 (0) +#define M6502_PIN_A1 (1) +#define M6502_PIN_A2 (2) +#define M6502_PIN_A3 (3) +#define M6502_PIN_A4 (4) +#define M6502_PIN_A5 (5) +#define M6502_PIN_A6 (6) +#define M6502_PIN_A7 (7) +#define M6502_PIN_A8 (8) +#define M6502_PIN_A9 (9) +#define M6502_PIN_A10 (10) +#define M6502_PIN_A11 (11) +#define M6502_PIN_A12 (12) +#define M6502_PIN_A13 (13) +#define M6502_PIN_A14 (14) +#define M6502_PIN_A15 (15) + +// data bus pins +#define M6502_PIN_D0 (16) +#define M6502_PIN_D1 (17) +#define M6502_PIN_D2 (18) +#define M6502_PIN_D3 (19) +#define M6502_PIN_D4 (20) +#define M6502_PIN_D5 (21) +#define M6502_PIN_D6 (22) +#define M6502_PIN_D7 (23) + +// control pins +#define M6502_PIN_RW (24) // out: memory read or write access +#define M6502_PIN_SYNC (25) // out: start of a new instruction +#define M6502_PIN_IRQ (26) // in: maskable interrupt requested +#define M6502_PIN_NMI (27) // in: non-maskable interrupt requested +#define M6502_PIN_RDY (28) // in: freeze execution at next read cycle +#define M6510_PIN_AEC (29) // in, m6510 only, put bus lines into tristate mode, not implemented +#define M6502_PIN_RES (30) // request RESET + +// m6510 IO port pins +#define M6510_PIN_P0 (32) +#define M6510_PIN_P1 (33) +#define M6510_PIN_P2 (34) +#define M6510_PIN_P3 (35) +#define M6510_PIN_P4 (36) +#define M6510_PIN_P5 (37) + +// pin bit masks +#define M6502_A0 (1ULL<P |= M6502_CF; } cpu->A = sum & 0xFF; - } + } } static inline void _m6502_sbc(m6502_t* cpu, uint8_t val) { @@ -555,7 +598,7 @@ static inline void _m6502_arr(m6502_t* cpu) { } } -/* undocumented SBX instruction: +/* undocumented SBX instruction: AND X register with accumulator and store result in X register, then subtract byte from X register (without borrow) where the subtract works like a CMP instruction @@ -620,6 +663,20 @@ uint64_t m6510_iorq(m6502_t* c, uint64_t pins) { return pins; } +void m6502_snapshot_onsave(m6502_t* snapshot) { + CHIPS_ASSERT(snapshot); + snapshot->in_cb = 0; + snapshot->out_cb = 0; + snapshot->user_data = 0; +} + +void m6502_snapshot_onload(m6502_t* snapshot, m6502_t* sys) { + CHIPS_ASSERT(snapshot && sys); + snapshot->in_cb = sys->in_cb; + snapshot->out_cb = sys->out_cb; + snapshot->user_data = sys->user_data; +} + /* set 16-bit address in 64-bit pin mask */ #define _SA(addr) pins=(pins&~0xFFFF)|((addr)&0xFFFFULL) /* extract 16-bit addess from pin mask */ @@ -651,7 +708,7 @@ uint64_t m6510_iorq(m6502_t* c, uint64_t pins) { uint64_t m6502_tick(m6502_t* c, uint64_t pins) { if (pins & (M6502_SYNC|M6502_IRQ|M6502_NMI|M6502_RDY|M6502_RES)) { // interrupt detection also works in RDY phases, but only NMI is "sticky" - + // NMI is edge-triggered if (0 != ((pins & (pins ^ c->PINS)) & M6502_NMI)) { c->nmi_pip |= 0x100; @@ -660,7 +717,7 @@ uint64_t m6502_tick(m6502_t* c, uint64_t pins) { if ((pins & M6502_IRQ) && (0 == (c->P & M6502_IF))) { c->irq_pip |= 0x100; } - + // RDY pin is only checked during read cycles if ((pins & (M6502_RW|M6502_RDY)) == (M6502_RW|M6502_RDY)) { M6510_SET_PORT(pins, c->io_pins); @@ -672,7 +729,7 @@ uint64_t m6502_tick(m6502_t* c, uint64_t pins) { // load new instruction into 'instruction register' and restart tick counter c->IR = _GD()<<3; _OFF(M6502_SYNC); - + // check IRQ, NMI and RES state // - IRQ is level-triggered and must be active in the full cycle // before SYNC diff --git a/chips/m6522.h b/chips/m6522.h index 95c233a8..861d4f26 100644 --- a/chips/m6522.h +++ b/chips/m6522.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -38,7 +38,7 @@ * | | * * +-----------+ * ************************************* - + ## How to use Call m6522_init() to initialize a new m6522_t instance (note that @@ -108,7 +108,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -117,62 +117,100 @@ extern "C" { #endif -/* register select same as lower 4 shared address bus bits */ -#define M6522_RS0 (1ULL<<0) -#define M6522_RS1 (1ULL<<1) -#define M6522_RS2 (1ULL<<2) -#define M6522_RS3 (1ULL<<3) +// register select same as lower 4 shared address bus bits +#define M6522_PIN_RS0 (0) +#define M6522_PIN_RS1 (1) +#define M6522_PIN_RS2 (2) +#define M6522_PIN_RS3 (3) + +// data bus pins shared with CPU +#define M6522_PIN_D0 (16) +#define M6522_PIN_D1 (17) +#define M6522_PIN_D2 (18) +#define M6522_PIN_D3 (19) +#define M6522_PIN_D4 (20) +#define M6522_PIN_D5 (21) +#define M6522_PIN_D6 (22) +#define M6522_PIN_D7 (23) + +// control pins shared with CPU +#define M6522_PIN_RW (24) // same as M6502_RW +#define M6522_PIN_IRQ (26) // same as M6502_IRQ + +// control pins +#define M6522_PIN_CS1 (40) // chip-select 1, to select: CS1 high, CS2 low +#define M6522_PIN_CS2 (41) // chip-select 2 + +#define M6522_PIN_CA1 (42) // peripheral A control 1 +#define M6522_PIN_CA2 (43) // peripheral A control 2 +#define M6522_PIN_CB1 (44) // peripheral B control 1 +#define M6522_PIN_CB2 (45) // peripheral B control 2 + +// peripheral A port +#define M6522_PIN_PA0 (48) +#define M6522_PIN_PA1 (49) +#define M6522_PIN_PA2 (50) +#define M6522_PIN_PA3 (51) +#define M6522_PIN_PA4 (52) +#define M6522_PIN_PA5 (53) +#define M6522_PIN_PA6 (54) +#define M6522_PIN_PA7 (55) + +// peripheral B port +#define M6522_PIN_PB0 (56) +#define M6522_PIN_PB1 (57) +#define M6522_PIN_PB2 (58) +#define M6522_PIN_PB3 (59) +#define M6522_PIN_PB4 (60) +#define M6522_PIN_PB5 (61) +#define M6522_PIN_PB6 (62) +#define M6522_PIN_PB7 (63) + +// pin bit masks +#define M6522_RS0 (1ULL<pcr & 0x01) #define M6522_PCR_CA1_HIGH_TO_LOW(c) (!(c->pcr & 0x01)) #define M6522_PCR_CB1_LOW_TO_HIGH(c) (c->pcr & 0x10) @@ -217,7 +255,7 @@ extern "C" { #define M6522_PCR_CB2_FIX_OUTPUT(c) ((c->pcr & 0xc0) == 0xc0) #define M6522_PCR_CB2_OUTPUT_LEVEL(c) ((c->pcr & 0x20) >> 5) -/* ACR test macros (MAME naming) */ +// ACR test macros (MAME naming) #define M6522_ACR_PA_LATCH_ENABLE(c) (c->acr & 0x01) #define M6522_ACR_PB_LATCH_ENABLE(c) (c->acr & 0x02) #define M6522_ACR_SR_DISABLED(c) (!(c->acr & 0x1c)) @@ -232,7 +270,7 @@ extern "C" { #define M6522_ACR_T1_CONTINUOUS(c) (c->acr & 0x40) #define M6522_ACR_T2_COUNT_PB6(c) (c->acr & 0x20) -/* interrupt bits */ +// interrupt bits #define M6522_IRQ_CA2 (1<<0) #define M6522_IRQ_CA1 (1<<1) #define M6522_IRQ_SR (1<<2) @@ -242,12 +280,12 @@ extern "C" { #define M6522_IRQ_T1 (1<<6) #define M6522_IRQ_ANY (1<<7) -/* delay-pipeline bit offsets */ +// delay-pipeline bit offsets #define M6522_PIP_TIMER_COUNT (0) #define M6522_PIP_TIMER_LOAD (8) #define M6522_PIP_IRQ (0) -/* I/O port state */ +// I/O port state typedef struct { uint8_t inpr; uint8_t outr; @@ -261,7 +299,7 @@ typedef struct { bool c2_triggered; } m6522_port_t; -/* timer state */ +// timer state typedef struct { uint16_t latch; /* 16-bit initial value latch, NOTE: T2 only has an 8-bit latch */ uint16_t counter; /* 16-bit counter */ @@ -274,14 +312,14 @@ typedef struct { uint16_t pip; } m6522_timer_t; -/* interrupt state (same as m6522_int_t) */ +// interrupt state (same as m6522_int_t) typedef struct { uint8_t ier; /* interrupt enable register */ uint8_t ifr; /* interrupt flag register */ uint16_t pip; } m6522_int_t; -/* m6522 state */ +// m6522 state typedef struct { m6522_port_t pa; m6522_port_t pb; @@ -293,30 +331,30 @@ typedef struct { uint64_t pins; } m6522_t; -/* extract 8-bit data bus from 64-bit pins */ +// extract 8-bit data bus from 64-bit pins #define M6522_GET_DATA(p) ((uint8_t)((p)>>16)) -/* merge 8-bit data bus value into 64-bit pins */ +// merge 8-bit data bus value into 64-bit pins #define M6522_SET_DATA(p,d) {p=(((p)&~0xFF0000ULL)|(((d)<<16)&0xFF0000ULL));} -/* extract port A pins */ +// extract port A pins #define M6522_GET_PA(p) ((uint8_t)((p)>>48)) -/* extract port B pins */ +// extract port B pins #define M6522_GET_PB(p) ((uint8_t)((p)>>56)) -/* merge port A pins into pin mask */ +// merge port A pins into pin mask #define M6522_SET_PA(p,a) {p=((p)&0xFF00FFFFFFFFFFFFULL)|(((a)&0xFFULL)<<48);} -/* merge port B pins into pin mask */ +// merge port B pins into pin mask #define M6522_SET_PB(p,b) {p=((p)&0x00FFFFFFFFFFFFFFULL)|(((b)&0xFFULL)<<56);} -/* merge port A and B pins into pin mask */ +// merge port A and B pins into pin mask #define M6522_SET_PAB(p,a,b) {p=((p)&0x0000FFFFFFFFFFFFULL)|(((a)&0xFFULL)<<48)|(((b)&0xFFULL)<<56);} -/* initialize a new 6522 instance */ +// initialize a new 6522 instance void m6522_init(m6522_t* m6522); -/* reset an existing 6522 instance */ +// reset an existing 6522 instance void m6522_reset(m6522_t* m6522); -/* tick the m6522 */ +// tick the m6522 uint64_t m6522_tick(m6522_t* m6522, uint64_t pins); #ifdef __cplusplus -} /* extern "C" */ +} // extern "C" #endif /*-- IMPLEMENTATION ----------------------------------------------------------*/ @@ -372,9 +410,9 @@ void m6522_init(m6522_t* c) { } /* - "The RESET input clears all internal registers to logic 0, + "The RESET input clears all internal registers to logic 0, (except T1, T2 and SR). This places all peripheral interface lines - in the input state, disables the timers, shift registers etc. and + in the input state, disables the timers, shift registers etc. and disables interrupting from the chip" */ void m6522_reset(m6522_t* c) { @@ -670,11 +708,11 @@ static uint8_t _m6522_read(m6522_t* c, uint8_t addr) { /* FIXME: pulse output delay pipeline */ } break; - + case M6522_REG_DDRB: data = c->pb.ddr; break; - + case M6522_REG_DDRA: data = c->pa.ddr; break; @@ -747,7 +785,7 @@ static void _m6522_write(m6522_t* c, uint8_t addr, uint8_t data) { c->pb.c2_out = false; } break; - + case M6522_REG_RA: c->pa.outr = data; _m6522_clear_pa_intr(c); @@ -758,11 +796,11 @@ static void _m6522_write(m6522_t* c, uint8_t addr, uint8_t data) { /* FIXME: pulse output delay pipeline */ } break; - + case M6522_REG_DDRB: c->pb.ddr = data; break; - + case M6522_REG_DDRA: c->pa.ddr = data; break; diff --git a/chips/m6526.h b/chips/m6526.h index 5578b7de..858e49a7 100644 --- a/chips/m6526.h +++ b/chips/m6526.h @@ -8,14 +8,14 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - ~~~C + ~~~C CHIPS_ASSERT(c) ~~~ - + ## Emulated Pins ************************************ @@ -49,7 +49,7 @@ - https://ist.uwaterloo.ca/~schepers/MJK/cia6526.html TODO: Documentation - + ## zlib/libpng license Copyright (c) 2018 Andre Weissflog @@ -66,7 +66,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -75,76 +75,114 @@ extern "C" { #endif -/* register select same as lower 4 shared address bus bits */ -#define M6526_RS0 (1ULL<<0) -#define M6526_RS1 (1ULL<<1) -#define M6526_RS2 (1ULL<<2) -#define M6526_RS3 (1ULL<<3) +// register select same as lower 4 shared address bus bits +#define M6526_PIN_RS0 (0) +#define M6526_PIN_RS1 (1) +#define M6526_PIN_RS2 (2) +#define M6526_PIN_RS3 (3) + +// data bus pins shared with CPU +#define M6526_PIN_D0 (16) +#define M6526_PIN_D1 (17) +#define M6526_PIN_D2 (18) +#define M6526_PIN_D3 (19) +#define M6526_PIN_D4 (20) +#define M6526_PIN_D5 (21) +#define M6526_PIN_D6 (22) +#define M6526_PIN_D7 (23) + +// control pins shared with CPU +#define M6526_PIN_RW (24) // same as M6502_RW +#define M6526_PIN_IRQ (26) // same as M6502_IRQ + +// chip-specific control pins +#define M6526_PIN_CS (40) +#define M6526_PIN_FLAG (41) +#define M6526_PIN_PC (42) +#define M6526_PIN_SP (43) +#define M6526_PIN_TOD (44) +#define M6526_PIN_CNT (45) + +// port I/O pins +#define M6526_PIN_PA0 (48) +#define M6526_PIN_PA1 (49) +#define M6526_PIN_PA2 (50) +#define M6526_PIN_PA3 (51) +#define M6526_PIN_PA4 (52) +#define M6526_PIN_PA5 (53) +#define M6526_PIN_PA6 (54) +#define M6526_PIN_PA7 (55) + +#define M6526_PIN_PB0 (56) +#define M6526_PIN_PB1 (57) +#define M6526_PIN_PB2 (58) +#define M6526_PIN_PB3 (59) +#define M6526_PIN_PB4 (60) +#define M6526_PIN_PB5 (61) +#define M6526_PIN_PB6 (62) +#define M6526_PIN_PB7 (63) + +// pin bit masks +#define M6526_RS0 (1ULL<>16)) -/* merge 8-bit data bus value into 64-bit pins */ +// merge 8-bit data bus value into 64-bit pins #define M6526_SET_DATA(p,d) {p=(((p)&~0xFF0000ULL)|(((d)<<16)&0xFF0000ULL));} -/* extract port A pins */ +// extract port A pins #define M6526_GET_PA(p) ((uint8_t)((p)>>48)) -/* extract port B pins */ +// extract port B pins #define M6526_GET_PB(p) ((uint8_t)((p)>>56)) -/* merge port A pins into pin mask */ +// merge port A pins into pin mask #define M6526_SET_PA(p,a) {p=((p)&0xFF00FFFFFFFFFFFFULL)|(((a)&0xFFULL)<<48);} -/* merge port B pins into pin mask */ +// merge port B pins into pin mask #define M6526_SET_PB(p,b) {p=((p)&0x00FFFFFFFFFFFFFFULL)|(((b)&0xFFULL)<<56);} -/* merge port A and B pins into pin mask */ +// merge port A and B pins into pin mask #define M6526_SET_PAB(p,a,b) {p=((p)&0x0000FFFFFFFFFFFFULL)|(((a)&0xFFULL)<<48)|(((b)&0xFFULL)<<56);} -/* initialize a new m6526_t instance */ +// initialize a new m6526_t instance void m6526_init(m6526_t* c); -/* reset an existing m6526_t instance */ +// reset an existing m6526_t instance void m6526_reset(m6526_t* c); -/* tick the m6526_t instance */ +// tick the m6526_t instance uint64_t m6526_tick(m6526_t* c, uint64_t pins); #ifdef __cplusplus -} /* extern "C" */ +} // extern "C" #endif /*-- IMPLEMENTATION ----------------------------------------------------------*/ @@ -415,7 +453,7 @@ static uint64_t _m6526_update_irq(m6526_t* c, uint64_t pins) { if (_M6526_PIP_TEST(c->intr.pip, M6526_PIP_IRQ, 0)) { c->intr.icr |= (1<<7); } - + /* update IRQ pin */ if (0 != (c->intr.icr & (1<<7))) { pins |= M6526_IRQ; @@ -507,7 +545,7 @@ static void _m6526_tick_pipeline(m6526_t* c) { /* timer B oneshot pipeline */ if (M6526_RUNMODE_ONESHOT(c->tb.cr)) { _M6526_PIP_SET(c->tb.pip, M6526_PIP_TIMER_ONESHOT, 1); - } + } /* interrupt pipeline */ if (c->intr.icr & c->intr.imr) { diff --git a/chips/m6561.h b/chips/m6561.h index 8ef9c1ec..2a5f9a43 100644 --- a/chips/m6561.h +++ b/chips/m6561.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - ~~~C + ~~~C CHIPS_ASSERT(c) ~~~ @@ -42,7 +42,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -52,7 +52,11 @@ extern "C" { #endif -/* address pins (see control pins for chip-select condition) */ +#define M6561_FRAMEBUFFER_WIDTH (512) +#define M6561_FRAMEBUFFER_HEIGHT (320) +#define M6561_FRAMEBUFFER_SIZE_BYTES (M6561_FRAMEBUFFER_WIDTH * M6561_FRAMEBUFFER_HEIGHT) + +// address pins (see control pins for chip-select condition) #define M6561_A0 (1ULL<<0) #define M6561_A1 (1ULL<<1) #define M6561_A2 (1ULL<<2) @@ -68,7 +72,7 @@ extern "C" { #define M6561_A12 (1ULL<<12) #define M6561_A13 (1ULL<<13) -/* data pins (the 4 additional color data pins are not emulated as pins) */ +// data pins (the 4 additional color data pins are not emulated as pins) #define M6561_D0 (1ULL<<16) #define M6561_D1 (1ULL<<17) #define M6561_D2 (1ULL<<18) @@ -78,7 +82,7 @@ extern "C" { #define M6561_D6 (1ULL<<22) #define M6561_D7 (1ULL<<23) -/* control pins shared with CPU +/* control pins shared with CPU NOTE that a real VIC has no dedicated chip-select pin, instead registers are read and written during the CPU's 'shared bus half-cycle', and the following address bus pin mask is @@ -99,97 +103,95 @@ extern "C" { vic_pins |= M6561_CS; } */ -#define M6561_RW (1ULL<<24) /* same as M6502_RW */ -#define M6561_CS (1ULL<<40) /* virtual chip-select pin */ -#define M6561_SAMPLE (1ULL<<41) /* virtual 'audio sample ready' pin */ +#define M6561_RW (1ULL<<24) // same as M6502_RW +#define M6561_CS (1ULL<<40) // virtual chip-select pin +#define M6561_SAMPLE (1ULL<<41) // virtual 'audio sample ready' pin -/* use this macro to check whether the address bus pins are in the right chip-select state */ +// use this macro to check whether the address bus pins are in the right chip-select state #define M6561_SELECTED_ADDR(pins) ((pins&(M6561_A13|M6561_A12|M6561_A11|M6561_A10|M6561_A9|M6561_A8))==M6561_A12) #define M6561_NUM_REGS (16) #define M6561_REG_MASK (M6561_NUM_REGS-1) -/* sound DC adjustment buffer length */ +// sound DC adjustment buffer length #define M6561_DCADJ_BUFLEN (512) -/* extract 8-bit data bus from 64-bit pins */ +// extract 8-bit data bus from 64-bit pins #define M6561_GET_DATA(p) ((uint8_t)(((p)&0xFF0000ULL)>>16)) -/* merge 8-bit data bus value into 64-bit pins */ +// merge 8-bit data bus value into 64-bit pins #define M6561_SET_DATA(p,d) {p=(((p)&~0xFF0000ULL)|(((d)<<16)&0xFF0000ULL));} -/* memory fetch callback, used to feed pixel- and color-data into the m6561 */ +// memory fetch callback, used to feed pixel- and color-data into the m6561 typedef uint16_t (*m6561_fetch_t)(uint16_t addr, void* user_data); -/* setup parameters for m6561_init() function */ +// setup parameters for m6561_init() function typedef struct { - /* pointer to RGBA8 framebuffer for generated image (optional) */ - uint32_t* rgba8_buffer; - /* size of the RGBA framebuffer (optional) */ - size_t rgba8_buffer_size; - /* visible CRT area blitted to rgba8_buffer (in pixels) */ - uint16_t vis_x, vis_y, vis_w, vis_h; - /* the memory-fetch callback */ + // pointer and size of external framebuffer + chips_range_t framebuffer; + // visible CRT area decoded into framebuffer (in pixels) + chips_rect_t screen; + // the memory-fetch callback m6561_fetch_t fetch_cb; - /* optional user-data for fetch callback */ + // optional user-data for fetch callback void* user_data; - /* frequency at which the tick function is called (for audio generation) */ + // frequency at which the tick function is called (for audio generation) int tick_hz; - /* sound sample frequency */ + // sound sample frequency int sound_hz; - /* sound sample magnitude/volume (0.0..1.0)*/ + // sound sample magnitude/volume (0.0..1.0) float sound_magnitude; } m6561_desc_t; -/* raster unit state */ +// raster unit state typedef struct { - uint8_t h_count; /* horizontal tick counter */ - uint16_t v_count; /* line counter */ - uint16_t vc; /* video matrix counter */ - uint16_t vc_base; /* vc reload value at start of line */ - uint8_t vc_disabled; /* if != 0, video counter inactive */ - uint8_t rc; /* 4-bit raster counter (0..7 or 0..15) */ - uint8_t row_height; /* either 8 or 16 */ - uint8_t row_count; /* character row count */ + uint8_t h_count; // horizontal tick counter + uint16_t v_count; // line counter + uint16_t vc; // video matrix counter + uint16_t vc_base; // vc reload value at start of line + uint8_t vc_disabled; // if != 0, video counter inactive + uint8_t rc; // 4-bit raster counter (0..7 or 0..15) + uint8_t row_height; // either 8 or 16 + uint8_t row_count; // character row count } m6561_raster_unit_t; -/* memory unit state */ +// memory unit state typedef struct { - uint16_t c_addr_base; /* character access base address */ - uint16_t g_addr_base; /* graphics access base address */ - uint16_t c_value; /* last fetched character access value */ + uint16_t c_addr_base; // character access base address + uint16_t g_addr_base; // graphics access base address + uint16_t c_value; // last fetched character access value } m6561_memory_unit_t; -/* graphics unit state */ +// graphics unit state typedef struct { - uint8_t shift; /* current pixel shifter */ - uint8_t color; /* last fetched color value */ - bool inv_color; /* true when bit 3 of CRF is clear */ - uint32_t bg_color; /* current background color RGBA */ - uint32_t brd_color; /* border color RGBA */ - uint32_t aux_color; /* auxiliary color RGBA */ + uint8_t shift; // current pixel shifter + uint8_t color; // last fetched color value + bool inv_color; // true when bit 3 of CRF is clear + uint8_t bg_color; // current background color RGBA + uint8_t brd_color; // border color RGBA + uint8_t aux_color; // auxiliary color RGBA } m6561_graphics_unit_t; -/* border unit state */ +// border unit state typedef struct { uint8_t left, right; uint16_t top, bottom; - uint8_t enabled; /* if != 0, in border area */ + uint8_t enabled; // if != 0, in border area } m6561_border_unit_t; -/* CRT state tracking */ +// CRT state tracking typedef struct { - uint16_t x, y; /* beam pos */ - uint16_t vis_x0, vis_y0, vis_x1, vis_y1; /* the visible area */ - uint16_t vis_w, vis_h; /* width of visible area */ - uint32_t* rgba8_buffer; + uint16_t x, y; // beam pos + uint16_t vis_x0, vis_y0, vis_x1, vis_y1; // the visible area + uint16_t vis_w, vis_h; // width of visible area + uint8_t* fb; } m6561_crt_t; -/* sound generator state */ +// sound generator state typedef struct { - uint16_t count; /* count down with clock frequency */ - uint16_t period; /* counter reload value */ - uint8_t bit; /* toggled between 0 and 1 */ - uint8_t enabled; /* 1 if enabled */ + uint16_t count; // count down with clock frequency + uint16_t period; // counter reload value + uint8_t bit; // toggled between 0 and 1 + uint8_t enabled; // 1 if enabled } m6561_voice_t; typedef struct { @@ -215,11 +217,11 @@ typedef struct { float dcadj_buf[M6561_DCADJ_BUFLEN]; } m6561_sound_t; -/* the m6561_t state struct */ +// the m6561_t state struct typedef struct { uint64_t pins; - m6561_fetch_t fetch_cb; /* memory fetch callback */ - void* user_data; /* memory fetch callback user data */ + m6561_fetch_t fetch_cb; // memory fetch callback + void* user_data; // memory fetch callback user data bool debug_vis; uint8_t regs[M6561_NUM_REGS]; m6561_raster_unit_t rs; @@ -230,21 +232,25 @@ typedef struct { m6561_sound_t sound; } m6561_t; -/* initialize a new m6561_t instance */ +// initialize a new m6561_t instance void m6561_init(m6561_t* vic, const m6561_desc_t* desc); -/* reset a m6561_t instance */ +// reset a m6561_t instance void m6561_reset(m6561_t* vic); -/* tick the m6561_t instance */ +// tick the m6561_t instance uint64_t m6561_tick(m6561_t* vic, uint64_t pins); -/* get the visible display width in pixels */ -int m6561_display_width(m6561_t* vic); -/* get the visible display height in pixels */ -int m6561_display_height(m6561_t* vic); -/* get 32-bit RGBA8 value from color index (0..15) */ -uint32_t m6561_color(int i); +// get the visible screen rect in pixels +chips_rect_t m6561_screen(m6561_t* vic); +// get the color palette +chips_range_t m6561_palette(void); +// get 32-bit RGBA8 value from color index (0..15) +uint32_t m6561_color(size_t i); +// prepare m6561_t snapshot for saving +void m6561_snapshot_onsave(m6561_t* snapshot); +// fixup m6561_t snapshot after loading +void m6561_snapshot_onload(m6561_t* snapshot, m6561_t* sys); #ifdef __cplusplus -} /* extern "C" */ +} // extern "C" #endif /*--- IMPLEMENTATION ---------------------------------------------------------*/ @@ -255,7 +261,7 @@ uint32_t m6561_color(int i); #define CHIPS_ASSERT(c) assert(c) #endif -/* internal constants */ +// internal constants #define _M6561_HTOTAL (71) #define _M6561_VTOTAL (312) #define _M6561_VRETRACEPOS (303) @@ -266,12 +272,12 @@ uint32_t m6561_color(int i); #define _M6561_HVC_DISABLE (1<<0) #define _M6561_VVC_DISABLE (1<<1) -/* fixed point precision for audio sample period */ +// fixed point precision for audio sample period #define _M6561_FIXEDPOINT_SCALE (16) #define _M6561_RGBA8(r,g,b) (0xFF000000|(b<<16)|(g<<8)|(r)) -/* see VICE sources under vice/data/vic20/mike-pal.vpl */ +// see VICE sources under vice/data/vic20/mike-pal.vpl static const uint32_t _m6561_colors[16] = { _M6561_RGBA8(0x00, 0x00, 0x00), /* black */ _M6561_RGBA8(0xFF, 0xFF, 0xFF), /* white */ @@ -292,21 +298,21 @@ static const uint32_t _m6561_colors[16] = { }; static void _m6561_init_crt(m6561_crt_t* crt, const m6561_desc_t* desc) { - /* vis area horizontal coords must be multiple of 8 */ - CHIPS_ASSERT((desc->vis_x & 7) == 0); - CHIPS_ASSERT((desc->vis_w & 7) == 0); - crt->rgba8_buffer = desc->rgba8_buffer; - crt->vis_x0 = desc->vis_x/_M6561_PIXELS_PER_TICK; - crt->vis_y0 = desc->vis_y; - crt->vis_w = desc->vis_w/_M6561_PIXELS_PER_TICK; - crt->vis_h = desc->vis_h; + // vis area horizontal coords must be multiple of 8 + CHIPS_ASSERT((desc->screen.x & 7) == 0); + CHIPS_ASSERT((desc->screen.width & 7) == 0); + crt->fb = desc->framebuffer.ptr; + crt->vis_x0 = desc->screen.x / _M6561_PIXELS_PER_TICK; + crt->vis_y0 = desc->screen.y; + crt->vis_w = desc->screen.width / _M6561_PIXELS_PER_TICK; + crt->vis_h = desc->screen.height; crt->vis_x1 = crt->vis_x0 + crt->vis_w; crt->vis_y1 = crt->vis_y0 + crt->vis_h; } void m6561_init(m6561_t* vic, const m6561_desc_t* desc) { CHIPS_ASSERT(vic && desc && desc->fetch_cb); - CHIPS_ASSERT((0 == desc->rgba8_buffer) || (desc->rgba8_buffer_size >= (_M6561_HTOTAL*8*_M6561_VTOTAL*sizeof(uint32_t)))); + CHIPS_ASSERT(desc->framebuffer.ptr && (desc->framebuffer.size >= M6561_FRAMEBUFFER_SIZE_BYTES)); memset(vic, 0, sizeof(*vic)); _m6561_init_crt(&vic->crt, desc); vic->border.enabled = _M6561_HBORDER|_M6561_VBORDER; @@ -359,33 +365,40 @@ void m6561_reset(m6561_t* vic) { _m6561_reset_audio(vic); } -int m6561_display_width(m6561_t* vic) { +chips_rect_t m6561_screen(m6561_t* vic) { CHIPS_ASSERT(vic); - return _M6561_PIXELS_PER_TICK * (vic->debug_vis ? _M6561_HTOTAL : vic->crt.vis_w); + return (chips_rect_t){ + .x = 0, + .y = 0, + .width = _M6561_PIXELS_PER_TICK * (vic->debug_vis ? _M6561_HTOTAL : vic->crt.vis_w), + .height = vic->debug_vis ? _M6561_VTOTAL : vic->crt.vis_h, + }; } -int m6561_display_height(m6561_t* vic) { - CHIPS_ASSERT(vic); - return vic->debug_vis ? _M6561_VTOTAL : vic->crt.vis_h; +uint32_t m6561_color(size_t i) { + CHIPS_ASSERT(i < 16); + return _m6561_colors[i]; } -uint32_t m6561_color(int i) { - CHIPS_ASSERT((i >= 0) && (i < 16)); - return _m6561_colors[i]; +chips_range_t m6561_palette(void) { + return (chips_range_t){ + .ptr = (void*)_m6561_colors, + .size = sizeof(_m6561_colors) + }; } -/* update precomputed values when disp-related registers changed */ +// update precomputed values when disp-related registers changed static void _m6561_regs_dirty(m6561_t* vic) { - /* each column is 2 ticks */ + // each column is 2 ticks vic->rs.row_height = (vic->regs[3] & 1) ? 16 : 8; vic->border.left = vic->regs[0] & 0x7F; vic->border.right = vic->border.left + (vic->regs[2] & 0x7F) * 2; vic->border.top = vic->regs[1]; vic->border.bottom = vic->border.top + ((vic->regs[3]>>1) & 0x3F) * vic->rs.row_height; vic->gunit.inv_color = (vic->regs[15] & 8) == 0; - vic->gunit.bg_color = _m6561_colors[(vic->regs[15]>>4) & 0xF]; - vic->gunit.brd_color = _m6561_colors[vic->regs[15] & 7]; - vic->gunit.aux_color = _m6561_colors[(vic->regs[14]>>4) & 0xF]; + vic->gunit.bg_color = (vic->regs[15]>>4) & 0xF; + vic->gunit.brd_color = vic->regs[15] & 7; + vic->gunit.aux_color = (vic->regs[14]>>4) & 0xF; vic->mem.g_addr_base = ((vic->regs[5] & 0xF)<<10); // A13..A10 vic->mem.c_addr_base = (((vic->regs[5]>>4)&0xF)<<10) | // A13..A10 (((vic->regs[2]>>7)&1)<<9); // A9 @@ -396,45 +409,44 @@ static void _m6561_regs_dirty(m6561_t* vic) { vic->sound.voice[2].enabled = 0 != (vic->regs[12] & 0x80); vic->sound.voice[2].period = 0x20 * (0x80 - (vic->regs[12] & 0x7F)); vic->sound.noise.enabled = 0 != (vic->regs[13] & 0x80); - /* 0x40 factor is not a bug, tweaked to 'sound right' */ + // 0x40 factor is not a bug, tweaked to 'sound right' vic->sound.noise.period = 0x40 * (0x80 - (vic->regs[13] & 0x7F)); vic->sound.volume = vic->regs[14] & 0xF; } -static inline void _m6561_decode_pixels(m6561_t* vic, uint32_t* dst) { +static inline void _m6561_decode_4pixels(m6561_t* vic, uint8_t* dst) { if (vic->border.enabled) { - for (int i = 0; i < 4; i++) { + for (size_t i = 0; i < 4; i++) { *dst++ = vic->gunit.brd_color; } } else { uint8_t p = vic->gunit.shift; if (vic->gunit.color & 8) { - /* multi-color mode */ - uint32_t fg_color = _m6561_colors[vic->gunit.color & 7]; + // multi-color mode switch (p & 0xC0) { case 0x00: dst[0] = dst[1] = vic->gunit.bg_color; break; case 0x40: dst[0] = dst[1] = vic->gunit.brd_color; break; - case 0x80: dst[0] = dst[1] = fg_color; break; + case 0x80: dst[0] = dst[1] = vic->gunit.color & 7; break; case 0xC0: dst[0] = dst[1] = vic->gunit.aux_color; break; } switch (p & 0x30) { case 0x00: dst[2] = dst[3] = vic->gunit.bg_color; break; case 0x10: dst[2] = dst[3] = vic->gunit.brd_color; break; - case 0x20: dst[2] = dst[3] = fg_color; break; + case 0x20: dst[2] = dst[3] = vic->gunit.color & 7; break; case 0x30: dst[2] = dst[3] = vic->gunit.aux_color; break; } } else { - /* hires mode */ - uint32_t bg, fg; + // hires mode + uint8_t bg, fg; if (vic->gunit.inv_color) { - bg = _m6561_colors[vic->gunit.color & 7]; + bg = vic->gunit.color & 7; fg = vic->gunit.bg_color; } else { bg = vic->gunit.bg_color; - fg = _m6561_colors[vic->gunit.color & 7]; + fg = vic->gunit.color & 7; } dst[0] = (p & (1<<7)) ? fg : bg; dst[1] = (p & (1<<6)) ? fg : bg; @@ -445,49 +457,44 @@ static inline void _m6561_decode_pixels(m6561_t* vic, uint32_t* dst) { } } -/* tick function for video output */ +// tick function for video output static void _m6561_tick_video(m6561_t* vic) { - /* decode pixels, each tick is 4 pixels */ - if (vic->crt.rgba8_buffer) { - int x, y, w; - if (vic->debug_vis) { - x = vic->rs.h_count; - y = vic->rs.v_count; - w = _M6561_HTOTAL; - uint32_t* dst = vic->crt.rgba8_buffer + (y * w + x) * _M6561_PIXELS_PER_TICK; - _m6561_decode_pixels(vic, dst); - } - else if ((vic->crt.x >= vic->crt.vis_x0) && (vic->crt.x < vic->crt.vis_x1) && - (vic->crt.y >= vic->crt.vis_y0) && (vic->crt.y < vic->crt.vis_y1)) - { - const int x = vic->crt.x - vic->crt.vis_x0; - const int y = vic->crt.y - vic->crt.vis_y0; - const int w = vic->crt.vis_w; - uint32_t* dst = vic->crt.rgba8_buffer + (y * w + x) * _M6561_PIXELS_PER_TICK; - _m6561_decode_pixels(vic, dst); - } + // decode pixels, each tick is 4 pixels + if (vic->debug_vis) { + const size_t x = vic->rs.h_count; + const size_t y = vic->rs.v_count; + uint8_t* dst = vic->crt.fb + (y * M6561_FRAMEBUFFER_WIDTH) + (x * _M6561_PIXELS_PER_TICK); + _m6561_decode_4pixels(vic, dst); + } + else if ((vic->crt.x >= vic->crt.vis_x0) && (vic->crt.x < vic->crt.vis_x1) && + (vic->crt.y >= vic->crt.vis_y0) && (vic->crt.y < vic->crt.vis_y1)) + { + const size_t x = vic->crt.x - vic->crt.vis_x0; + const size_t y = vic->crt.y - vic->crt.vis_y0; + uint8_t* dst = vic->crt.fb + (y * M6561_FRAMEBUFFER_WIDTH) + (x * _M6561_PIXELS_PER_TICK); + _m6561_decode_4pixels(vic, dst); } - /* display-enabled area? */ + // display-enabled area? if (vic->rs.h_count == vic->border.left) { - /* enable fetching, but border still active for 1 tick */ + // enable fetching, but border still active for 1 tick vic->rs.vc_disabled &= ~_M6561_HVC_DISABLE; vic->rs.vc = vic->rs.vc_base; } if (vic->rs.h_count == (vic->border.left+1)) { - /* switch off horizontal border */ + // switch off horizontal border vic->border.enabled &= ~_M6561_HBORDER; } if (vic->rs.h_count == (vic->border.right+1)) { - /* switch on horizontal border */ + // switch on horizontal border vic->border.enabled |= _M6561_HBORDER; vic->rs.vc_disabled |= _M6561_HVC_DISABLE; } - /* fetch data */ + // fetch data if (vic->rs.vc & 1) { - /* a g-access (graphics data) into pixel shifter */ + // a g-access (graphics data) into pixel shifter uint16_t addr = vic->mem.g_addr_base + ((vic->mem.c_value & 0xFF) * vic->rs.row_height) + vic->rs.rc; @@ -495,7 +502,7 @@ static void _m6561_tick_video(m6561_t* vic) { vic->gunit.color = (vic->mem.c_value>>8) & 0xF; } else { - /* a c-access (character code and color) */ + // a c-access (character code and color) uint16_t addr = vic->mem.c_addr_base + (vic->rs.vc>>1); vic->mem.c_value = vic->fetch_cb(addr, vic->user_data); } @@ -503,7 +510,7 @@ static void _m6561_tick_video(m6561_t* vic) { vic->rs.vc = (vic->rs.vc + 1) & ((1<<11)-1); } - /* tick horizontal and vertical counters */ + // tick horizontal and vertical counters vic->rs.h_count++; vic->crt.x++; if (vic->rs.h_count == _M6561_HTOTAL) { @@ -658,5 +665,18 @@ uint64_t m6561_tick(m6561_t* vic, uint64_t pins) { return pins; } +void m6561_snapshot_onsave(m6561_t* snapshot) { + CHIPS_ASSERT(snapshot); + snapshot->fetch_cb = 0; + snapshot->user_data = 0; + snapshot->crt.fb = 0; +} + +void m6561_snapshot_onload(m6561_t* snapshot, m6561_t* sys) { + CHIPS_ASSERT(snapshot && sys); + snapshot->fetch_cb = sys->fetch_cb; + snapshot->user_data = sys->user_data; + snapshot->crt.fb = sys->crt.fb; +} #endif diff --git a/chips/m6569.h b/chips/m6569.h index 83b9e947..dd5a56c5 100644 --- a/chips/m6569.h +++ b/chips/m6569.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - ~~~C + ~~~C CHIPS_ASSERT(c) ~~~ @@ -55,7 +55,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -65,101 +65,135 @@ extern "C" { #endif -/* address bus lines (with CS active A0..A6 is register address) */ -#define M6569_A0 (1ULL<<0) -#define M6569_A1 (1ULL<<1) -#define M6569_A2 (1ULL<<2) -#define M6569_A3 (1ULL<<3) -#define M6569_A4 (1ULL<<4) -#define M6569_A5 (1ULL<<5) -#define M6569_A6 (1ULL<<6) -#define M6569_A7 (1ULL<<7) -#define M6569_A8 (1ULL<<8) -#define M6569_A9 (1ULL<<9) -#define M6569_A10 (1ULL<<10) -#define M6569_A11 (1ULL<<11) -#define M6569_A12 (1ULL<<12) -#define M6569_A13 (1ULL<<13) - -/* data bus pins D0..D7 */ -#define M6569_D0 (1ULL<<16) -#define M6569_D1 (1ULL<<17) -#define M6569_D2 (1ULL<<18) -#define M6569_D3 (1ULL<<19) -#define M6569_D4 (1ULL<<20) -#define M6569_D5 (1ULL<<21) -#define M6569_D6 (1ULL<<22) -#define M6569_D7 (1ULL<<23) - -/* shared control pins */ -#define M6569_RW (1ULL<<24) /* shared with m6502 CPU */ -#define M6569_IRQ (1ULL<<26) /* shared with m6502 CPU */ -#define M6569_BA (1ULL<<28) /* shared with m6502 RDY */ -#define M6569_AEC (1ULL<<29) /* shared with m6510 AEC */ - -/* chip-specific control pins */ -#define M6569_CS (1ULL<<40) - -/* number of registers */ +#define M6569_PIXELS_PER_TICK (8) +#define M6569_HTOTAL (63) // 63 cycles per line (PAL) +#define M6569_VTOTAL (312) // 312 lines total (PAL) +#define M6569_FRAMEBUFFER_WIDTH (M6569_HTOTAL * M6569_PIXELS_PER_TICK) +#define M6569_FRAMEBUFFER_HEIGHT (M6569_VTOTAL) +#define M6569_FRAMEBUFFER_SIZE_BYTES (M6569_FRAMEBUFFER_WIDTH * M6569_FRAMEBUFFER_HEIGHT) + +// address bus lines (with CS active A0..A6 is register address) +#define M6569_PIN_A0 (0) +#define M6569_PIN_A1 (1) +#define M6569_PIN_A2 (2) +#define M6569_PIN_A3 (3) +#define M6569_PIN_A4 (4) +#define M6569_PIN_A5 (5) +#define M6569_PIN_A6 (6) +#define M6569_PIN_A7 (7) +#define M6569_PIN_A8 (8) +#define M6569_PIN_A9 (9) +#define M6569_PIN_A10 (10) +#define M6569_PIN_A11 (11) +#define M6569_PIN_A12 (12) +#define M6569_PIN_A13 (13) + +// data bus pins D0..D7 +#define M6569_PIN_D0 (16) +#define M6569_PIN_D1 (17) +#define M6569_PIN_D2 (18) +#define M6569_PIN_D3 (19) +#define M6569_PIN_D4 (20) +#define M6569_PIN_D5 (21) +#define M6569_PIN_D6 (22) +#define M6569_PIN_D7 (23) + +// shared control pins +#define M6569_PIN_RW (24) // shared with m6502 CPU +#define M6569_PIN_IRQ (26) // shared with m6502 CPU +#define M6569_PIN_BA (28) // shared with m6502 RDY +#define M6569_PIN_AEC (29) // shared with m6510 AEC + +// chip-specific control pins +#define M6569_PIN_CS (40) + +// pin bit masks +#define M6569_A0 (1ULL<>16)) -/* merge 8-bit data bus value into 64-bit pins */ +// merge 8-bit data bus value into 64-bit pins #define M6569_SET_DATA(p,d) {p=(((p)&~0xFF0000ULL)|(((d)<<16)&0xFF0000ULL));} -/* memory fetch callback, used to feed pixel- and color-data into the m6569 */ +// memory fetch callback, used to feed pixel- and color-data into the m6569 typedef uint16_t (*m6569_fetch_t)(uint16_t addr, void* user_data); -/* setup parameters for m6569_init() function */ +// setup parameters for m6569_init() function typedef struct { - /* pointer to RGBA8 framebuffer for generated image (optional) */ - uint32_t* rgba8_buffer; - /* size of the RGBA framebuffer (must be at least 512x312, optional) */ - size_t rgba8_buffer_size; - /* visible CRT area blitted to rgba8_buffer (in pixels) */ - uint16_t vis_x, vis_y, vis_w, vis_h; - /* the memory-fetch callback */ + // pointer and size of external framebuffer (at least M6569_FRAMEBUFFER_SIZE_BYTES big) + chips_range_t framebuffer; + // visible CRT area decoded into framebuffer (in pixels) + chips_rect_t screen; + // the memory-fetch callback m6569_fetch_t fetch_cb; - /* optional user-data for fetch callback */ + // optional user-data for fetch callback void* user_data; } m6569_desc_t; -/* register bank */ +// register bank typedef struct { union { uint8_t regs[M6569_NUM_REGS]; struct { - uint8_t mxy[M6569_NUM_MOBS][2]; /* sprite X/Y coords */ - uint8_t mx8; /* x coordinate MSBs */ - uint8_t ctrl_1; /* control register 1 */ - uint8_t raster; /* raster counter */ - uint8_t lightpen_xy[2]; /* lightpen coords */ - uint8_t me; /* sprite enabled bits */ - uint8_t ctrl_2; /* control register 2 */ - uint8_t mye; /* sprite Y expansion */ - uint8_t mem_ptrs; /* memory pointers */ - uint8_t int_latch; /* interrupt latch */ - uint8_t int_mask; /* interrupt-enabled mask */ - uint8_t mdp; /* sprite data priority bits */ - uint8_t mmc; /* sprite multicolor bits */ - uint8_t mxe; /* sprite X expansion */ - uint8_t mcm; /* sprite-sprite collision bits */ - uint8_t mcd; /* sprite-data collision bits */ - uint8_t ec; /* border color */ - uint8_t bc[4]; /* background colors */ - uint8_t mm[2]; /* sprite multicolor 0 */ - uint8_t mc[8]; /* sprite colors */ - uint8_t unused[17]; /* not writable, return 0xFF on read */ + uint8_t mxy[M6569_NUM_MOBS][2]; // sprite X/Y coords + uint8_t mx8; // x coordinate MSBs + uint8_t ctrl_1; // control register 1 + uint8_t raster; // raster counter + uint8_t lightpen_xy[2]; // lightpen coords + uint8_t me; // sprite enabled bits + uint8_t ctrl_2; // control register 2 + uint8_t mye; // sprite Y expansion + uint8_t mem_ptrs; // memory pointers + uint8_t int_latch; // interrupt latch + uint8_t int_mask; // interrupt-enabled mask + uint8_t mdp; // sprite data priority bits + uint8_t mmc; // sprite multicolor bits + uint8_t mxe; // sprite X expansion + uint8_t mcm; // sprite-sprite collision bits + uint8_t mcd; // sprite-data collision bits + uint8_t ec; // border color + uint8_t bc[4]; // background colors + uint8_t mm[2]; // sprite multicolor 0 + uint8_t mc[8]; // sprite colors + uint8_t unused[17]; // not writable, return 0xFF on read }; }; } m6569_registers_t; -/* control- and interrupt-register bits */ +// control- and interrupt-register bits #define M6569_CTRL1_RST8 (1<<7) #define M6569_CTRL1_ECM (1<<6) #define M6569_CTRL1_BMM (1<<5) @@ -170,104 +204,99 @@ typedef struct { #define M6569_CTRL2_MCM (1<<4) #define M6569_CTRL2_CSEL (1<<3) #define M6569_CTRL2_XSCROLL ((1<<2)|(1<<1)|(1<<0)) -#define M6569_INT_IRQ (1<<7) /* int_latch: interrupt requested */ -#define M6569_INT_ILP (1<<3) /* int_latch: lightpen interrupt */ -#define M6569_INT_IMMC (1<<2) /* int_latch: mob/mob collision interrupt */ -#define M6569_INT_IMBC (1<<1) /* int_latch: mob/bitmap collision interrupt */ -#define M6569_INT_IRST (1<<0) /* int_latch: raster interrupt */ -#define M6569_INT_ELP (1<<3) /* int_mask: lightpen interrupt enabled */ -#define M6569_INT_EMMC (1<<2) /* int_mask: mob/mob collision interrupt enabled */ -#define M6569_INT_EMBC (1<<1) /* int_mask: mob/bitmap collision interrupt enabled */ -#define M6569_INT_ERST (1<<0) /* int_mask: raster interrupt enabled */ - -/* raster unit state */ +#define M6569_INT_IRQ (1<<7) // int_latch: interrupt requested +#define M6569_INT_ILP (1<<3) // int_latch: lightpen interrupt +#define M6569_INT_IMMC (1<<2) // int_latch: mob/mob collision interrupt +#define M6569_INT_IMBC (1<<1) // int_latch: mob/bitmap collision interrupt +#define M6569_INT_IRST (1<<0) // int_latch: raster interrupt +#define M6569_INT_ELP (1<<3) // int_mask: lightpen interrupt enabled +#define M6569_INT_EMMC (1<<2) // int_mask: mob/mob collision interrupt enabled +#define M6569_INT_EMBC (1<<1) // int_mask: mob/bitmap collision interrupt enabled +#define M6569_INT_ERST (1<<0) // int_mask: raster interrupt enabled + +// raster unit state typedef struct { uint8_t h_count; uint16_t v_count; - uint16_t v_irqline; /* raster interrupt line, updated when ctrl_1 or raster is written */ - uint16_t vc; /* 10-bit video counter */ - uint16_t vc_base; /* 10-bit video counter base */ - uint8_t rc; /* 3-bit raster counter */ - bool display_state; /* true: in display state, false: in idle state */ - bool badline; /* true when the badline state is active */ - bool frame_badlines_enabled; /* true when badlines are enabled in frame */ + uint16_t v_irqline; // raster interrupt line, updated when ctrl_1 or raster is written + uint16_t vc; // 10-bit video counter + uint16_t vc_base; // 10-bit video counter base + uint8_t rc; // 3-bit raster counter + bool display_state; // true: in display state, false: in idle state + bool badline; // true when the badline state is active + bool frame_badlines_enabled; // true when badlines are enabled in frame } m6569_raster_unit_t; -/* address generator / memory interface state */ +// address generator / memory interface state typedef struct { - uint16_t c_addr_or; /* OR-mask for c-accesses, computed from mem_ptrs */ - uint16_t g_addr_and; /* AND-mask for g-accesses, computed from mem_ptrs */ - uint16_t g_addr_or; /* OR-mask for g-accesses, computed from ECM bit */ - uint16_t i_addr; /* address for i-accesses, 0x3FFF or 0x39FF (if ECM bit set) */ - uint16_t p_addr_or; /* OR-mask for p-accesses */ - m6569_fetch_t fetch_cb; /* memory-fetch callback */ - void* user_data; /* optional user-data for fetch callback */ + uint16_t c_addr_or; // OR-mask for c-accesses, computed from mem_ptrs + uint16_t g_addr_and; // AND-mask for g-accesses, computed from mem_ptrs + uint16_t g_addr_or; // OR-mask for g-accesses, computed from ECM bit + uint16_t i_addr; // address for i-accesses, 0x3FFF or 0x39FF (if ECM bit set) + uint16_t p_addr_or; // OR-mask for p-accesses + m6569_fetch_t fetch_cb; // memory-fetch callback + void* user_data; // optional user-data for fetch callback } m6569_memory_unit_t; -/* video matrix state */ +// video matrix state typedef struct { - uint8_t vmli; /* 6-bit 'vmli' video-matrix line buffer index */ + uint8_t vmli; // 6-bit 'vmli' video-matrix line buffer index uint8_t next_vmli; - uint16_t line[64]; /* 40x 8+4 bits line buffer (64 items because vmli is a 6-bit ctr) */ + uint16_t line[64]; // 40x 8+4 bits line buffer (64 items because vmli is a 6-bit ctr) } m6569_video_matrix_t; -/* border unit state */ +// border unit state typedef struct { uint16_t left, right, top, bottom; - bool main; /* main border flip-flop */ - bool vert; /* vertical border flip flop */ - uint8_t bc_index; /* border color as palette index (not used, but may be useful for outside code) */ - uint32_t bc_rgba8; /* border color as RGBA8, updated when border color register is updated */ + bool main; // main border flip-flop + bool vert; // vertical border flip flop + uint8_t bc; // border color } m6569_border_unit_t; -/* CRT state tracking */ +// CRT state tracking typedef struct { - uint16_t x, y; /* beam pos reset on crt_retrace_h/crt_retrace_v zero */ - uint16_t vis_x0, vis_y0, vis_x1, vis_y1; /* the visible area */ - uint16_t vis_w, vis_h; /* width of visible area */ - uint32_t* rgba8_buffer; + uint16_t x, y; // beam pos reset on crt_retrace_h/crt_retrace_v zero + uint16_t vis_x0, vis_y0, vis_x1, vis_y1; // the visible area + uint16_t vis_w, vis_h; // width of visible area + uint8_t* fb; // pointer to host framebuffer start } m6569_crt_t; -/* graphics sequencer state */ +// graphics sequencer state typedef struct { - bool enabled; /* true while g_accesses are happening */ - uint8_t mode; /* display mode 0..7 precomputed from ECM/BMM/MCM bits */ - uint8_t count; /* counts from 0..8 */ - uint8_t shift; /* current pixel shifter */ - uint8_t outp; /* current output byte (bit 7) */ - uint8_t outp2; /* current output byte at half frequency (bits 7 and 6) */ - uint16_t c_data; /* loaded from video matrix line buffer */ - uint8_t bg_index[4]; /* background color as palette index (not used, but may be useful for outside code) */ - uint32_t bg_rgba8[4]; /* background colors as RGBA8 */ + bool enabled; // true while g_accesses are happening + uint8_t mode; // display mode 0..7 precomputed from ECM/BMM/MCM bits + uint8_t count; // counts from 0..8 + uint8_t shift; // current pixel shifter + uint8_t outp; // current output byte (bit 7) + uint8_t outp2; // current output byte at half frequency (bits 7 and 6) + uint16_t c_data; // loaded from video matrix line buffer + uint16_t bg[4]; // background colors with top 8 bits cleared or set as color multiplexer masks } m6569_graphics_unit_t; -/* sprite sequencer state */ +// sprite sequencer state typedef struct { - /* updated when setting sprite position registers */ - uint8_t h_first[8]; /* first horizontal visible tick */ - uint8_t h_last[8]; /* last horizontal visible tick */ - uint8_t h_offset[8]; /* x-offset within 8-pixel raster */ - uint8_t p_data[8]; /* the byte read by p_access memory fetch */ - bool dma_enabled[8]; /* sprite dma is enabled */ - bool disp_enabled[8]; /* sprite display is enabled */ - bool expand[8]; /* expand flip-flop */ - uint8_t mc[8]; /* 6-bit mob-data-counter */ - uint8_t mc_base[8]; /* 6-bit mob-data-counter base */ - uint8_t delay_count[8]; /* 0..7 delay pixels */ - uint8_t outp2_count[8]; /* outp2 is updated when bit 0 is on */ - uint8_t xexp_count[8]; /* if x stretched, only shift every second pixel tick */ - uint32_t shift[8]; /* 24-bit shift register */ - uint32_t outp[8]; /* current shifter output (bit 31) */ - uint32_t outp2[8]; /* current shifter output at half frequency (bits 31 and 30) */ - uint32_t colors[8][4]; /* 0: unused, 1: multicolor0, 2: main color, 3: multicolor1 - the alpha channel is cleared and used as bitmask for sprites - which produced a color - */ + // updated when setting sprite position registers + uint8_t h_first[8]; // first horizontal visible tick + uint8_t h_last[8]; // last horizontal visible tick + uint8_t h_offset[8]; // x-offset within 8-pixel raster + uint8_t p_data[8]; // the byte read by p_access memory fetch + bool dma_enabled[8]; // sprite dma is enabled + bool disp_enabled[8]; // sprite display is enabled + bool expand[8]; // expand flip-flop + uint8_t mc[8]; // 6-bit mob-data-counter + uint8_t mc_base[8]; // 6-bit mob-data-counter base + uint8_t delay_count[8]; // 0..7 delay pixels + uint8_t outp2_count[8]; // outp2 is updated when bit 0 is on + uint8_t xexp_count[8]; // if x stretched, only shift every second pixel tick + uint32_t shift[8]; // 24-bit shift register + uint32_t outp[8]; // current shifter output (bit 31) + uint32_t outp2[8]; // current shifter output at half frequency (bits 31 and 30) + uint8_t colors[8][4]; // 0: unused, 1: multicolor0, 2: main color, 3: multicolor } m6569_sprite_unit_t; -/* the m6569 state structure */ +// the m6569 state structure typedef struct { - bool debug_vis; /* toggle this to switch debug visualization on/off */ + bool debug_vis; // toggle this to switch debug visualization on/off m6569_registers_t reg; m6569_crt_t crt; m6569_border_unit_t brd; @@ -279,21 +308,25 @@ typedef struct { uint64_t pins; } m6569_t; -/* initialize a new m6569_t instance */ +// initialize a new m6569_t instance void m6569_init(m6569_t* vic, const m6569_desc_t* desc); -/* reset a m6569_t instance */ +// reset a m6569_t instance void m6569_reset(m6569_t* vic); -/* tick the m6569 instance */ +// tick the m6569 instance uint64_t m6569_tick(m6569_t* vic, uint64_t pins); -/* get the visible display width in pixels */ -int m6569_display_width(m6569_t* vic); -/* get the visible display height in pixels */ -int m6569_display_height(m6569_t* vic); -/* get 32-bit RGBA8 value from color index (0..15) */ -uint32_t m6569_color(int i); +// get the visible screen rect in pixels +chips_rect_t m6569_screen(m6569_t* vic); +// get the color palette +chips_range_t m6569_palette(void); +// get 32-bit RGBA8 value from color index (0..15) +uint32_t m6569_color(size_t i); +// prepare m6569_t snapshot for saving +void m6569_snapshot_onsave(m6569_t* snapshot); +// fixup m6569_t snapshot after loading +void m6569_snapshot_onload(m6569_t* snapshot, m6569_t* sys); #ifdef __cplusplus -} /* extern "C" */ +} // extern "C" #endif /*--- IMPLEMENTATION ---------------------------------------------------------*/ @@ -304,103 +337,57 @@ uint32_t m6569_color(int i); #define CHIPS_ASSERT(c) assert(c) #endif -#define _M6569_RGBA8(r,g,b) (0xFF000000|(b<<16)|(g<<8)|(r)) - -/* -http://unusedino.de/ec64/technical/misc/vic656x/colors/ -static const uint32_t _m6569_colors[16] = { - _M6569_RGBA8(0x00,0x00,0x00), - _M6569_RGBA8(0xFF,0xFF,0xFF), - _M6569_RGBA8(0x68,0x37,0x2B), - _M6569_RGBA8(0x70,0xA4,0xB2), - _M6569_RGBA8(0x6F,0x3D,0x86), - _M6569_RGBA8(0x58,0x8D,0x43), - _M6569_RGBA8(0x35,0x28,0x79), - _M6569_RGBA8(0xB8,0xC7,0x6F), - _M6569_RGBA8(0x6F,0x4F,0x25), - _M6569_RGBA8(0x43,0x39,0x00), - _M6569_RGBA8(0x9A,0x67,0x59), - _M6569_RGBA8(0x44,0x44,0x44), - _M6569_RGBA8(0x6C,0x6C,0x6C), - _M6569_RGBA8(0x9A,0xD2,0x84), - _M6569_RGBA8(0x6C,0x5E,0xB5), - _M6569_RGBA8(0x95,0x95,0x95) -}; -*/ - -/* https://www.pepto.de/projects/colorvic/ */ -static const uint32_t _m6569_colors[16] = { - _M6569_RGBA8(0x00,0x00,0x00), - _M6569_RGBA8(0xff,0xff,0xff), - _M6569_RGBA8(0x81,0x33,0x38), - _M6569_RGBA8(0x75,0xce,0xc8), - _M6569_RGBA8(0x8e,0x3c,0x97), - _M6569_RGBA8(0x56,0xac,0x4d), - _M6569_RGBA8(0x2e,0x2c,0x9b), - _M6569_RGBA8(0xed,0xf1,0x71), - _M6569_RGBA8(0x8e,0x50,0x29), - _M6569_RGBA8(0x55,0x38,0x00), - _M6569_RGBA8(0xc4,0x6c,0x71), - _M6569_RGBA8(0x4a,0x4a,0x4a), - _M6569_RGBA8(0x7b,0x7b,0x7b), - _M6569_RGBA8(0xa9,0xff,0x9f), - _M6569_RGBA8(0x70,0x6d,0xeb), - _M6569_RGBA8(0xb2,0xb2,0xb2), -}; - -/* valid register bits */ +// valid register bits static const uint8_t _m6569_reg_mask[M6569_NUM_REGS] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* mob 0..3 xy */ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* mob 4..7 xy */ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* msbx, ctrl1, raster, lpx, lpy, mob enabled */ - 0x3F, /* ctrl2 */ - 0xFF, /* mob y expansion */ - 0xFE, /* memory pointers */ - 0x8F, /* interrupt latch */ - 0x0F, /* interrupt enabled mask */ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* mob data pri, multicolor, x expansion, mob-mob coll, mob-data coll */ - 0x0F, /* border color */ - 0x0F, 0x0F, 0x0F, 0x0F, /* background colors */ - 0x0F, 0x0F, /* sprite multicolor 0,1 */ - 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, /* sprite colors */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* unused 0..7 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* unused 8..15 */ - 0x00 /* unused 16 */ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // mob 0..3 xy + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // mob 4..7 xy + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // msbx, ctrl1, raster, lpx, lpy, mob enabled + 0x3F, // ctrl2 + 0xFF, // mob y expansion + 0xFE, // memory pointers + 0x8F, // interrupt latch + 0x0F, // interrupt enabled mask + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // mob data pri, multicolor, x expansion, mob-mob coll, mob-data coll + 0x0F, // border color + 0x0F, 0x0F, 0x0F, 0x0F, // background colors + 0x0F, 0x0F, // sprite multicolor 0,1 + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, // sprite colors + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // unused 0..7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // unused 8..15 + 0x00 // unused 16 }; -/* internal implementation constants */ -#define _M6569_HTOTAL (63) /* 63 cycles per line (PAL) */ -#define _M6569_VTOTAL (312) /* 312 lines total (PAL) */ -#define _M6569_VRETRACEPOS (303) /* start of vertical beam retrace */ -#define _M6569_RSEL1_BORDER_TOP (51) /* top border when RSEL=1 (25 rows) */ -#define _M6569_RSEL1_BORDER_BOTTOM (251) /* bottom border when RSEL=1 */ -#define _M6569_RSEL0_BORDER_TOP (55) /* top border when RSEL=0 (24 rows) */ -#define _M6569_RSEL0_BORDER_BOTTOM (247) /* bottom border when RSEL=0 (24 rows) */ -#define _M6569_CSEL1_BORDER_LEFT (16) /* left border when CSEL=1 (40 columns) */ -#define _M6569_CSEL1_BORDER_RIGHT (56) /* right border when CSEL=1 */ -#define _M6569_CSEL0_BORDER_LEFT (17) /* left border when CSEL=0 (38 columns) */ -#define _M6569_CSEL0_BORDER_RIGHT (55) /* right border when CSEL=0 */ +// internal implementation constants +#define _M6569_VRETRACEPOS (303) // start of vertical beam retrace +#define _M6569_RSEL1_BORDER_TOP (51) // top border when RSEL=1 (25 rows) +#define _M6569_RSEL1_BORDER_BOTTOM (251) // bottom border when RSEL=1 +#define _M6569_RSEL0_BORDER_TOP (55) // top border when RSEL=0 (24 rows) +#define _M6569_RSEL0_BORDER_BOTTOM (247) // bottom border when RSEL=0 (24 rows) +#define _M6569_CSEL1_BORDER_LEFT (16) // left border when CSEL=1 (40 columns) +#define _M6569_CSEL1_BORDER_RIGHT (56) // right border when CSEL=1 +#define _M6569_CSEL0_BORDER_LEFT (17) // left border when CSEL=0 (38 columns) +#define _M6569_CSEL0_BORDER_RIGHT (55) // right border when CSEL=0 #define _M6569_RAST(r) (vic->rs.v_count == (r)) #define _M6569_RAST_RANGE(r0,r1) ((vic->rs.v_count >= (r0)) && (vic->rs.v_count <= (r1))) /*--- init -------------------------------------------------------------------*/ static void _m6569_init_crt(m6569_crt_t* crt, const m6569_desc_t* desc) { - /* vis area horizontal coords must be multiple of 8 */ - CHIPS_ASSERT((desc->vis_x & 7) == 0); - CHIPS_ASSERT((desc->vis_w & 7) == 0); - crt->rgba8_buffer = desc->rgba8_buffer; - crt->vis_x0 = desc->vis_x/8; - crt->vis_y0 = desc->vis_y; - crt->vis_w = desc->vis_w/8; - crt->vis_h = desc->vis_h; + // vis area horizontal coords must be multiple of 8 + CHIPS_ASSERT((desc->screen.x & 7) == 0); + CHIPS_ASSERT((desc->screen.width & 7) == 0); + crt->fb = desc->framebuffer.ptr; + crt->vis_x0 = desc->screen.x / M6569_PIXELS_PER_TICK; + crt->vis_y0 = desc->screen.y; + crt->vis_w = desc->screen.width / M6569_PIXELS_PER_TICK; + crt->vis_h = desc->screen.height; crt->vis_x1 = crt->vis_x0 + crt->vis_w; crt->vis_y1 = crt->vis_y0 + crt->vis_h; } void m6569_init(m6569_t* vic, const m6569_desc_t* desc) { CHIPS_ASSERT(vic && desc); - CHIPS_ASSERT((0 == desc->rgba8_buffer) || (desc->rgba8_buffer_size >= (_M6569_HTOTAL*8*_M6569_VTOTAL*sizeof(uint32_t)))); + CHIPS_ASSERT(desc->framebuffer.ptr && (desc->framebuffer.size >= M6569_FRAMEBUFFER_SIZE_BYTES)); memset(vic, 0, sizeof(*vic)); _m6569_init_crt(&vic->crt, desc); vic->mem.fetch_cb = desc->fetch_cb; @@ -465,20 +452,20 @@ void m6569_reset(m6569_t* vic) { /*--- register read/writes ---------------------------------------------------*/ -/* update the raster-interrupt line from ctrl_1 and raster register updates */ +// update the raster-interrupt line from ctrl_1 and raster register updates static inline void _m6569_io_update_irq_line(m6569_raster_unit_t* rs, uint8_t ctrl_1, uint8_t rast) { rs->v_irqline = ((ctrl_1 & M6569_CTRL1_RST8)<<1) | rast; } -/* update memory unit values after update mem_ptrs or ctrl_1 registers */ +// update memory unit values after update mem_ptrs or ctrl_1 registers static inline void _m6569_io_update_memory_unit(m6569_memory_unit_t* m, uint8_t mem_ptrs, uint8_t ctrl_1) { - /* c-access: addr=|VM13|VM12|VM11|VM10|VC9|VC8|VC7|VC6|VC5|VC4|VC3|VC2|VC1|VC0| */ + // c-access: addr=|VM13|VM12|VM11|VM10|VC9|VC8|VC7|VC6|VC5|VC4|VC3|VC2|VC1|VC0| m->c_addr_or = (mem_ptrs & 0xF0)<<6; - /* g-access: addr=|CB13|CB12|CB11|D7|D6|D5|D4|D3|D2|D1|D0|RC2|RC1|RC0| */ + // g-access: addr=|CB13|CB12|CB11|D7|D6|D5|D4|D3|D2|D1|D0|RC2|RC1|RC0| m->g_addr_or = (mem_ptrs & 0x0E)<<10; m->g_addr_and = 0xFFFF; m->i_addr = 0x3FFF; - /* p-access: addr=|VM13|VM12|VM11|VM10|1|1|1|1|1|1|1|2..0:sprite-num| */ + // p-access: addr=|VM13|VM12|VM11|VM10|1|1|1|1|1|1|1|2..0:sprite-num| m->p_addr_or = ((mem_ptrs & 0xF0)<<6) | 0x3F8; /* "...If the ECM bit is set, the address generator always holds the @@ -491,44 +478,44 @@ static inline void _m6569_io_update_memory_unit(m6569_memory_unit_t* m, uint8_t } } -/* update the border top/bottom position when updating csel */ +// update the border top/bottom position when updating csel static inline void _m6569_io_update_border_rsel(m6569_border_unit_t* b, uint8_t ctrl_1) { if (ctrl_1 & M6569_CTRL1_RSEL) { - /* RSEL 1: 25 rows */ + // RSEL 1: 25 rows b->top = _M6569_RSEL1_BORDER_TOP; b->bottom = _M6569_RSEL1_BORDER_BOTTOM; } else { - /* RSEL 0: 24 rows */ + // RSEL 0: 24 rows b->top = _M6569_RSEL0_BORDER_TOP; b->bottom = _M6569_RSEL0_BORDER_BOTTOM; } } -/* update the border left/right position when updating csel */ +// update the border left/right position when updating csel static inline void _m6569_io_update_border_csel(m6569_border_unit_t* b, uint8_t ctrl_2) { if (ctrl_2 & M6569_CTRL2_CSEL) { - /* CSEL 1: 40 columns */ + // CSEL 1: 40 columns b->left = _M6569_CSEL1_BORDER_LEFT; b->right = _M6569_CSEL1_BORDER_RIGHT; } else { - /* CSEL 0: 38 columns */ + // CSEL 0: 38 columns b->left = _M6569_CSEL0_BORDER_LEFT; b->right = _M6569_CSEL0_BORDER_RIGHT; } } -/* updates the graphics sequencer display mode (0..7) from the ECM/BMM/MCM bits */ +// updates the graphics sequencer display mode (0..7) from the ECM/BMM/MCM bits static inline void _m6569_io_update_gunit_mode(m6569_graphics_unit_t* gu, uint8_t ctrl_1, uint8_t ctrl_2) { gu->mode = ((ctrl_1&(M6569_CTRL1_ECM|M6569_CTRL1_BMM))|(ctrl_2&M6569_CTRL2_MCM))>>4; } -/* update sprite unit positions and sizes when updating registers */ -static void _m6569_io_update_sunit(m6569_t* vic, int i, uint8_t mx, uint8_t my, uint8_t mx8, uint8_t mxe, uint8_t mye) { +// update sprite unit positions and sizes when updating registers +static void _m6569_io_update_sunit(m6569_t* vic, size_t i, uint8_t mx, uint8_t my, uint8_t mx8, uint8_t mxe, uint8_t mye) { (void)my; // FIXME: my is really unused? m6569_sprite_unit_t* su = &vic->sunit; - /* mxb: MSB for each xpos */ + // mxb: MSB for each xpos uint16_t xpos = ((mx8 & (1<h_first[i] = (xpos / 8) + 13; su->h_offset[i] = (xpos & 7); @@ -542,7 +529,7 @@ static void _m6569_io_update_sunit(m6569_t* vic, int i, uint8_t mx, uint8_t my, } } -/* read chip registers */ +// read chip registers static uint64_t _m6569_read(m6569_t* vic, uint64_t pins) { m6569_registers_t* r = &vic->reg; const m6569_raster_unit_t* rs = &vic->rs; @@ -550,21 +537,21 @@ static uint64_t _m6569_read(m6569_t* vic, uint64_t pins) { uint8_t data; switch (r_addr) { case 0x11: - /* bit 7 of 0x11 is bit 8 of the current raster counter */ + // bit 7 of 0x11 is bit 8 of the current raster counter data = (r->ctrl_1 & 0x7F) | ((rs->v_count & 0x100)>>1); break; case 0x12: - /* reading 0x12 returns bits 0..7 of current raster position */ + // reading 0x12 returns bits 0..7 of current raster position data = (uint8_t)rs->v_count; break; case 0x1E: case 0x1F: - /* registers 0x1E and 0x1F (mob collisions) are cleared on reading */ + // registers 0x1E and 0x1F (mob collisions) are cleared on reading data = r->regs[r_addr]; r->regs[r_addr] = 0; break; default: - /* unconnected bits are returned as 1 */ + // unconnected bits are returned as 1 data = r->regs[r_addr] | ~_m6569_reg_mask[r_addr]; break; } @@ -572,93 +559,93 @@ static uint64_t _m6569_read(m6569_t* vic, uint64_t pins) { return pins; } -/* write chip registers */ +// write chip registers static void _m6569_write(m6569_t* vic, uint64_t pins) { m6569_registers_t* r = &vic->reg; uint8_t r_addr = pins & M6569_REG_MASK; const uint8_t data = M6569_GET_DATA(pins) & _m6569_reg_mask[r_addr]; bool write = true; switch (r_addr) { - case 0x00: /* m0x */ + case 0x00: // m0x _m6569_io_update_sunit(vic, 0, data, r->mxy[0][1], r->mx8, r->mxe, r->mye); break; - case 0x01: /* m0y */ + case 0x01: // m0y _m6569_io_update_sunit(vic, 0, r->mxy[0][0], data, r->mx8, r->mxe, r->mye); break; - case 0x02: /* m1x */ + case 0x02: // m1x _m6569_io_update_sunit(vic, 1, data, r->mxy[1][1], r->mx8, r->mxe, r->mye); break; - case 0x03: /* m1y */ + case 0x03: // m1y _m6569_io_update_sunit(vic, 1, r->mxy[1][0], data, r->mx8, r->mxe, r->mye); break; - case 0x04: /* m2x */ + case 0x04: // m2x _m6569_io_update_sunit(vic, 2, data, r->mxy[2][1], r->mx8, r->mxe, r->mye); break; - case 0x05: /* m2y */ + case 0x05: // m2y _m6569_io_update_sunit(vic, 2, r->mxy[2][0], data, r->mx8, r->mxe, r->mye); break; - case 0x06: /* m3x */ + case 0x06: // m3x _m6569_io_update_sunit(vic, 3, data, r->mxy[3][1], r->mx8, r->mxe, r->mye); break; - case 0x07: /* m3y */ + case 0x07: // m3y _m6569_io_update_sunit(vic, 3, r->mxy[3][0], data, r->mx8, r->mxe, r->mye); break; - case 0x08: /* m4x */ + case 0x08: // m4x _m6569_io_update_sunit(vic, 4, data, r->mxy[4][1], r->mx8, r->mxe, r->mye); break; - case 0x09: /* m4y */ + case 0x09: // m4y _m6569_io_update_sunit(vic, 4, r->mxy[4][0], data, r->mx8, r->mxe, r->mye); break; - case 0x0A: /* m5x */ + case 0x0A: // m5x _m6569_io_update_sunit(vic, 5, data, r->mxy[5][1], r->mx8, r->mxe, r->mye); break; - case 0x0B: /* m5y */ + case 0x0B: // m5y _m6569_io_update_sunit(vic, 5, r->mxy[5][0], data, r->mx8, r->mxe, r->mye); break; - case 0x0C: /* m6x */ + case 0x0C: // m6x _m6569_io_update_sunit(vic, 6, data, r->mxy[6][1], r->mx8, r->mxe, r->mye); break; - case 0x0D: /* m6y */ + case 0x0D: // m6y _m6569_io_update_sunit(vic, 6, r->mxy[6][0], data, r->mx8, r->mxe, r->mye); break; - case 0x0E: /* m7x */ + case 0x0E: // m7x _m6569_io_update_sunit(vic, 7, data, r->mxy[7][1], r->mx8, r->mxe, r->mye); break; - case 0x0F: /* m7y */ + case 0x0F: // m7y _m6569_io_update_sunit(vic, 7, r->mxy[7][0], data, r->mx8, r->mxe, r->mye); break; - case 0x10: /* mx8 */ - for (int i = 0; i < 8; i++) { + case 0x10: // mx8 + for (size_t i = 0; i < 8; i++) { _m6569_io_update_sunit(vic, i, r->mxy[i][0], r->mxy[i][1], data, r->mxe, r->mye); } break; - case 0x11: /* ctrl_1 */ - /* update raster interrupt line */ + case 0x11: // ctrl_1 + // update raster interrupt line _m6569_io_update_irq_line(&vic->rs, data, r->raster); - /* update border top/bottom from RSEL flag */ + // update border top/bottom from RSEL flag _m6569_io_update_border_rsel(&vic->brd, data); - /* ECM bit updates the precomputed fetch address masks */ + // ECM bit updates the precomputed fetch address masks _m6569_io_update_memory_unit(&vic->mem, r->mem_ptrs, data); - /* update the graphics mode */ + // update the graphics mode _m6569_io_update_gunit_mode(&vic->gunit, data, r->ctrl_2); break; case 0x12: - /* raster irq value lower 8 bits */ + // raster irq value lower 8 bits _m6569_io_update_irq_line(&vic->rs, r->ctrl_1, data); break; - case 0x16: /* ctrl_2 */ - /* update border left/right from CSEL flag */ + case 0x16: // ctrl_2 + // update border left/right from CSEL flag _m6569_io_update_border_csel(&vic->brd, data); - /* update the graphics mode */ + // update the graphics mode _m6569_io_update_gunit_mode(&vic->gunit, r->ctrl_1, data); break; - case 0x17: /* mye */ - for (int i = 0; i < 8; i++) { + case 0x17: // mye + for (size_t i = 0; i < 8; i++) { _m6569_io_update_sunit(vic, i, r->mxy[i][0], r->mxy[i][1], r->mx8, r->mxe, data); } break; case 0x18: - /* memory-ptrs register , update precomputed fetch address masks */ + // memory-ptrs register , update precomputed fetch address masks _m6569_io_update_memory_unit(&vic->mem, data, r->ctrl_1); break; case 0x19: @@ -668,46 +655,43 @@ static void _m6569_write(m6569_t* vic, uint64_t pins) { r->int_latch = (r->int_latch & ~data) & _m6569_reg_mask[0x19]; write = false; break; - case 0x1D: /* mxe */ - for (int i = 0; i < 8; i++) { + case 0x1D: // mxe + for (size_t i = 0; i < 8; i++) { _m6569_io_update_sunit(vic, i, r->mxy[i][0], r->mxy[i][1], r->mx8, data, r->mye); } break; case 0x1E: case 0x1F: - /* mob collision registers cannot be written */ + // mob collision registers cannot be written write = false; break; case 0x20: - /* border color */ - vic->brd.bc_index = data & 0xF; - vic->brd.bc_rgba8 = _m6569_colors[data & 0xF]; + // border color + vic->brd.bc = data & 0xF; break; case 0x21: case 0x22: - /* background colors (alpha bits 0 because these count as MCM BG colors) */ - vic->gunit.bg_index[r_addr-0x21] = data & 0xF; - vic->gunit.bg_rgba8[r_addr-0x21] = _m6569_colors[data & 0xF] & 0x00FFFFFF; + // background colors (top 8 bits 0 because these count as MCM BG colors) + vic->gunit.bg[r_addr-0x21] = data & 0xF; break; case 0x23: case 0x24: - /* background colors (alpha bits 1 because these count as MCM FG colors) */ - vic->gunit.bg_index[r_addr-0x21] = data & 0xF; - vic->gunit.bg_rgba8[r_addr-0x21] = _m6569_colors[data & 0xF]; + // background colors (top 8 bits 1 because these count as MCM FG colors) + vic->gunit.bg[r_addr-0x21] = 0xFF00 | (data & 0xF); break; case 0x25: - /* sprite multicolor 0 */ - for (int i = 0; i < 8; i++) { - vic->sunit.colors[i][1] = _m6569_colors[data & 0xF] & 0x00FFFFFF; + // sprite multicolor 0 + for (size_t i = 0; i < 8; i++) { + vic->sunit.colors[i][1] = data & 0xF; } break; case 0x26: - /* sprite multicolor 1*/ - for (int i = 0; i < 8; i++) { - vic->sunit.colors[i][3] = _m6569_colors[data & 0xF] & 0x00FFFFFF; + // sprite multicolor 1 + for (size_t i = 0; i < 8; i++) { + vic->sunit.colors[i][3] = data & 0xF; } break; case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: - /* sprite main color */ - vic->sunit.colors[r_addr-0x27][2] = _m6569_colors[data & 0xF] & 0x00FFFFFF; + // sprite main color + vic->sunit.colors[r_addr-0x27][2] = data & 0xF; break; } if (write) { @@ -749,30 +733,32 @@ static inline void _m6569_gunit_tick(m6569_t* vic, uint8_t g_data) { vic->gunit.shift <<= 1; } -/* +/* graphics sequencer decoding functions for 1 pixel - NOTE: the graphics sequencer returns alpha bits = 0 for background - colors, and alpha bits = 0xFF for foregreound colors, this is important - for the color multiplexer which selectes between the color - produced by the graphics- and sprite-units + NOTE: the graphics sequencer decoding functions return a 16 bit value + with the color in the lower 8 bit values (actually: low 4 bit), and + a mask (either 0xFF for foreground colors or 0 for background colors) + in the top 8 bits. This mask is used in the color multiplexer to select + between the color produced by the graphics or sprite units, and for + sprite collision detection. */ -static inline uint32_t _m6569_gunit_decode_mode0(m6569_t* vic) { +static inline uint16_t _m6569_gunit_decode_mode0(m6569_t* vic) { if (vic->gunit.outp & 0x80) { - /* foreground color (alpha bits set) */ - return _m6569_colors[(vic->gunit.c_data>>8)&0xF]; + // foreground color (top bits set) + return 0xFF00 | ((vic->gunit.c_data>>8) & 0xF); } else { - /* background color (alpha bits clear) */ - return vic->gunit.bg_rgba8[0]; + // background color (top bits clear) + return vic->gunit.bg[0]; } } -static inline uint32_t _m6569_gunit_decode_mode1(m6569_t* vic) { - /* only seven colors in multicolor mode */ - const uint32_t fg = _m6569_colors[(vic->gunit.c_data>>8) & 0x7]; +static inline uint16_t _m6569_gunit_decode_mode1(m6569_t* vic) { + // only seven colors in multicolor mode + const uint8_t fg = (vic->gunit.c_data>>8) & 0x7; if (vic->gunit.c_data & (1<<11)) { - /* outp2 is only updated every 2 ticks */ + // outp2 is only updated every 2 ticks uint8_t bits = ((vic->gunit.outp2)>>6) & 3; /* half resolution multicolor char need 2 bits from the pixel sequencer @@ -782,68 +768,68 @@ static inline uint32_t _m6569_gunit_decode_mode1(m6569_t* vic) { "11": Color from bits 8-10 of c-data (alpha bits set) */ if (bits == 3) { - /* special case '11' */ - return fg; + // special case '11' + return 0xFF00 | fg; } else { - /* one of the 3 background colors */ - return vic->gunit.bg_rgba8[bits]; + // one of the 3 background colors (top bits already pre-set) + return vic->gunit.bg[bits]; } } else { - /* standard text mode char, but with only 7 foreground colors */ + // standard text mode char, but with only 7 foreground colors if (vic->gunit.outp & 0x80) { - /* foreground color (alpha bits set) */ - return fg; + // foreground color (top bits set) + return 0xFF00 | fg; } else { - /* background color (alpha bits clear) */ - return vic->gunit.bg_rgba8[0]; + // background color (top bits already pre-set to 0) + return vic->gunit.bg[0]; } } } -static inline uint32_t _m6569_gunit_decode_mode2(m6569_t* vic) { +static inline uint16_t _m6569_gunit_decode_mode2(m6569_t* vic) { if (vic->gunit.outp & 0x80) { - /* foreground pixel */ - return _m6569_colors[(vic->gunit.c_data >> 4) & 0xF]; + // foreground pixel (top 8 bits set) + return 0xFF00 | ((vic->gunit.c_data >> 4) & 0xF); } else { - /* background pixel (alpha bits must be clear for multiplexer) */ - return _m6569_colors[vic->gunit.c_data & 0xF] & 0x00FFFFFF; + // background pixel (top bits clear for multiplexer) + return vic->gunit.c_data & 0xF; } } -static inline uint32_t _m6569_gunit_decode_mode3(m6569_t* vic) { - /* shift 2 is only updated every 2 ticks */ +static inline uint16_t _m6569_gunit_decode_mode3(m6569_t* vic) { + // shift 2 is only updated every 2 ticks uint8_t bits = vic->gunit.outp2; /* half resolution multicolor char need 2 bits from the pixel sequencer - "00": Background color 0 ($d021) (alpha bits clear) - "01": Color from bits 4-7 of c-data (alpha bits clear) - "10": Color from bits 0-3 of c-data (alpha bits set) - "11": Color from bits 8-11 of c-data (alpha bits set) + "00": Background color 0 ($d021) (top bits clear) + "01": Color from bits 4-7 of c-data (top bits clear) + "10": Color from bits 0-3 of c-data (top bits set) + "11": Color from bits 8-11 of c-data (top bits set) */ switch ((bits>>6)&3) { - case 0: return vic->gunit.bg_rgba8[0]; break; - case 1: return _m6569_colors[(vic->gunit.c_data>>4) & 0xF] & 0x00FFFFFF; break; - case 2: return _m6569_colors[vic->gunit.c_data & 0xF]; break; - default: return _m6569_colors[(vic->gunit.c_data>>8) & 0xF]; break; + case 0: return vic->gunit.bg[0]; break; + case 1: return (vic->gunit.c_data>>4) & 0xF; break; + case 2: return 0xFF00 | (vic->gunit.c_data & 0xF); break; + default: return 0xFF00 | ((vic->gunit.c_data>>8) & 0xF); break; } } -static inline uint32_t _m6569_gunit_decode_mode4(m6569_t* vic) { +static inline uint16_t _m6569_gunit_decode_mode4(m6569_t* vic) { if (vic->gunit.outp & 0x80) { - /* foreground color as usual bits 8..11 of c_data */ - return _m6569_colors[(vic->gunit.c_data>>8) & 0xF]; + // foreground color as usual bits 8..11 of c_data + return 0xFF00 | ((vic->gunit.c_data>>8) & 0xF); } else { - /* bg color selected by bits 6 and 7 of c_data */ - /* FIXME: is the foreground/background selection right? + // bg color selected by bits 6 and 7 of c_data + /* FIXME: is the foreground/background selection right? values 00 and 01 would return as background color, and 10 and 11 as foreground color? */ - return vic->gunit.bg_rgba8[(vic->gunit.c_data>>6) & 3]; + return vic->gunit.bg[(vic->gunit.c_data>>6) & 3]; } } @@ -871,7 +857,7 @@ static inline void _m6569_sunit_start(m6569_t* vic) { const uint8_t me = vic->reg.me; const uint8_t mye = vic->reg.mye; m6569_sprite_unit_t* su = &vic->sunit; - for (int i = 0; i < 8; i++) { + for (size_t i = 0; i < 8; i++) { const uint8_t mask = (1<expand[i] = !su->expand[i]; @@ -885,7 +871,7 @@ static inline void _m6569_sunit_start(m6569_t* vic) { } } } - /* NOTE: the following behaviour differes from the recipe */ + // NOTE: the following behaviour differes from the recipe if (!su->dma_enabled[i]) { su->disp_enabled[i] = false; } @@ -900,7 +886,7 @@ static inline void _m6569_sunit_update_mc_disp_enable(m6569_t* vic) { turned on. */ m6569_sprite_unit_t* su = &vic->sunit; - for (int i = 0; i < 8; i++) { + for (size_t i = 0; i < 8; i++) { su->mc[i] = su->mc_base[i]; if (su->dma_enabled[i] && ((vic->rs.v_count & 0xFF) == vic->reg.mxy[i][1])) { su->disp_enabled[i] = true; @@ -914,7 +900,7 @@ static inline void _m6569_sunit_update_mc_disp_enable(m6569_t* vic) { */ static inline void _m6569_sunit_update_mcbase(m6569_t* vic) { m6569_sprite_unit_t* su = &vic->sunit; - for (int i = 0; i < 8; i++) { + for (size_t i = 0; i < 8; i++) { if (su->expand[i]) { su->mc_base[i] = (su->mc_base[i] + 2) & 0x3F; } @@ -932,7 +918,7 @@ static inline void _m6569_sunit_update_mcbase(m6569_t* vic) { be displayed). Otherwise the last line won't be displayed. This is different behaviour than described in the recipe!. - + BUT: https://sourceforge.net/p/vice-emu/code/HEAD/tree/techdocs/VICII/VIC-Addendum.txt mentions this: @@ -944,7 +930,7 @@ static inline void _m6569_sunit_update_mcbase(m6569_t* vic) { */ static inline void _m6569_sunit_dma_disp_disable(m6569_t* vic) { m6569_sprite_unit_t* su = &vic->sunit; - for (int i = 0; i < 8; i++) { + for (size_t i = 0; i < 8; i++) { if (su->expand[i]) { su->mc_base[i] = (su->mc_base[i] + 1) & 0x3F; } @@ -954,7 +940,7 @@ static inline void _m6569_sunit_dma_disp_disable(m6569_t* vic) { } } -/* set the BA pin if a sprite's DMA is enabled */ +// set the BA pin if a sprite's DMA is enabled static inline uint64_t _m6569_sunit_dma_ba(m6569_t* vic, uint32_t s_index, uint64_t pins) { if (vic->sunit.dma_enabled[s_index]) { pins |= M6569_BA; @@ -962,7 +948,7 @@ static inline uint64_t _m6569_sunit_dma_ba(m6569_t* vic, uint32_t s_index, uint6 return pins; } -/* set the AEC pin if a sprite's DMA is enabled */ +// set the AEC pin if a sprite's DMA is enabled static inline uint64_t _m6569_sunit_dma_aec(m6569_t* vic, uint32_t s_index, uint64_t pins) { if (vic->sunit.dma_enabled[s_index]) { pins |= M6569_AEC; @@ -970,44 +956,44 @@ static inline uint64_t _m6569_sunit_dma_aec(m6569_t* vic, uint32_t s_index, uint return pins; } -static inline uint32_t _m6569_sunit_decode(m6569_t* vic, uint8_t hpos) { +static inline uint16_t _m6569_sunit_decode(m6569_t* vic, uint8_t hpos) { /* this will tick all the sprite units and return the color - of the highest-priority sprite color for the current pixel, - or 0 if the sprite units didn't produce a color + of the highest-priority sprite color for the current pixel in + the lower 8 bits of the result, the high 8 bits of the result + have one bit set for each sprite unit that produced a color + (used for collision detection) - NOTE: The alpha channel bits of the color are cleared, and - instead a bit is set for the highest-priority sprite color - which produced the color! + The function returns 0 if the sprite units didn't produce a color. */ - uint32_t c = 0; + uint16_t c = 0; bool collision = false; m6569_sprite_unit_t* su = &vic->sunit; uint8_t mxe = vic->reg.mxe; uint8_t mmc = vic->reg.mmc; - for (int i = 0; i < 8; i++) { + for (size_t i = 0; i < 8; i++) { if (su->disp_enabled[i] && (hpos >= su->h_first[i]) && (hpos <= su->h_last[i])) { if (su->delay_count[i] == 0) { if ((0 == (su->xexp_count[i]++ & 1)) || (0 == (mxe & (1<outp[i] = su->shift[i]; - /* bits 31 and 30 of outp is half-frequency shifter output */ + // bits 31 and 30 of outp is half-frequency shifter output if (0 == (su->outp2_count[i]++ & 1)) { su->outp2[i] = su->shift[i]; } su->shift[i] <<= 1; } if (mmc & (1<outp2[i] & ((1<<31)|(1<<30)))>>30; if (ci != 0) { - /* don't overwrite higher-priority colors */ + // don't overwrite higher-priority colors if (0 == c) { c = su->colors[i][ci]; } else { collision = true; } - c |= (1<<(24+i)); + c |= (1<<(8+i)); } } else { @@ -1020,7 +1006,7 @@ static inline uint32_t _m6569_sunit_decode(m6569_t* vic, uint8_t hpos) { else { collision = true; } - c |= (1<<(24+i)); + c |= (1<<(8+i)); } } } @@ -1030,7 +1016,7 @@ static inline uint32_t _m6569_sunit_decode(m6569_t* vic, uint8_t hpos) { } } if (collision) { - vic->reg.mcm |= (c>>24); + vic->reg.mcm |= (c>>8); vic->reg.int_latch |= M6569_INT_IMMC; } return c; @@ -1039,56 +1025,56 @@ static inline uint32_t _m6569_sunit_decode(m6569_t* vic, uint8_t hpos) { /* Check for mob-data collision. - Takes the bitmap color bmc (alpha bits 0 if background color) - the sprite color sc (alpha bits set for each sprite which + Takes the bitmap color bmc (top 8 bits are 0 if background color) + the sprite color sc (top 8 bits set for each sprite which produced a non-transparant color), and sets the md collision bitmask, and the IMBC interrupt bit. */ -static inline void _m6569_test_mob_data_col(m6569_t* vic, uint32_t bmc, uint32_t sc) { - if ((sc & bmc & 0xFF000000) != 0) { - vic->reg.mcd |= (sc>>24); +static inline void _m6569_test_mob_data_col(m6569_t* vic, uint16_t bmc, uint16_t sc) { + if ((sc & bmc & 0xFF00) != 0) { + vic->reg.mcd |= (sc>>8); vic->reg.int_latch |= M6569_INT_IMBC; } } -/* +/* The graphics/sprite color priority multiplexer. - the sprite color is 0 if the sprite unit didn't produce a color - - the alpha channel bits of the sprite color have a bit set for the - sprite unit which produced the color - - the alpha channel bits of the bitmap color are 0x00 for a background + - the top 8 bits of the sprite color have a bit set for each + sprite unit which produced a color + - the top 8 bits of the bitmap color are 0 for a background color, or 0xFF for a foreground color */ -static inline uint32_t _m6569_color_multiplex(uint32_t bmc, uint32_t sc, uint8_t mdp) { - uint32_t c; +static inline uint8_t _m6569_color_multiplex(uint16_t bmc, uint16_t sc, uint8_t mdp) { + uint8_t c; if (sc == 0) { - /* sprite unit didn't produce a color, use the bitmap color */ + // sprite unit didn't produce a color, use the bitmap color c = bmc; } - else if ((sc>>24) & mdp) { - /* data priority bit is set, sprite color is behind bitmap foreground color */ - if ((bmc & 0xFF000000) == 0) { - /* bitmap color is background, use sprite color */ + else if ((sc>>8) & mdp) { + // data priority bit is set, sprite color is behind bitmap foreground color + if ((bmc & 0xFF00) == 0) { + // bitmap color is background, use sprite color c = sc; } else { - /* bitmap color is foreground */ + // bitmap color is foreground c = bmc; } } else { - /* sprite color is in front of bitmap color */ + // sprite color is in front of bitmap color c = sc; } - return c | 0xFF000000; + return c; } -/* decode the next 8 pixels */ -static inline void _m6569_decode_pixels(m6569_t* vic, uint8_t g_data, uint32_t* dst, uint8_t hpos) { +// decode the next 8 pixels +static inline void _m6569_decode_pixels(m6569_t* vic, uint8_t g_data, uint8_t* dst, uint8_t hpos) { m6569_sprite_unit_t* su = &vic->sunit; - for (int i = 0; i < 8; i++) { + for (size_t i = 0; i < 8; i++) { if (su->disp_enabled[i] && (hpos == su->h_first[i])) { su->delay_count[i] = su->h_offset[i]; su->outp2_count[i] = 0; @@ -1109,7 +1095,7 @@ static inline void _m6569_decode_pixels(m6569_t* vic, uint8_t g_data, uint32_t* produced the color, this is used to check the priority bit for this sprite - likewise the alpha channel bits for the graphics sequencer - are used to communicate whether the returned color is a + are used to communicate whether the returned color is a background or foreground colors (fg: alpha == 0xFF, bg: alpha == 0) "The main border flip flop controls the border display. If it is set, the @@ -1121,13 +1107,15 @@ static inline void _m6569_decode_pixels(m6569_t* vic, uint8_t g_data, uint32_t* ...otherwise it displays the background color */ bool brd = vic->brd.vert | vic->brd.main; - uint32_t brd_color = vic->brd.main ? vic->brd.bc_rgba8 : vic->gunit.bg_rgba8[0]; + uint8_t brd_color = vic->brd.main ? vic->brd.bc : vic->gunit.bg[0]; const uint8_t mdp = vic->reg.mdp; const uint8_t mode = vic->gunit.mode; - uint32_t bmc = 0; - for (int i = 0; i < 8; i++) { - uint32_t sc = _m6569_sunit_decode(vic, hpos); + uint16_t bmc = 0; + for (size_t i = 0; i < 8; i++) { + // lower 8 bit sprite color, top 8 bit 'coverage mask' + uint16_t sc = _m6569_sunit_decode(vic, hpos); _m6569_gunit_tick(vic, g_data); + // bmc: lower 8 bit color, top 8 bit set (foregreound) or cleared (background) switch (mode) { case 0: bmc = _m6569_gunit_decode_mode0(vic); break; case 1: bmc = _m6569_gunit_decode_mode1(vic); break; @@ -1141,33 +1129,45 @@ static inline void _m6569_decode_pixels(m6569_t* vic, uint8_t g_data, uint32_t* } /* decode the next 8 pixels as debug visualization */ -static void _m6569_decode_pixels_debug(m6569_t* vic, uint8_t g_data, bool ba_pin, uint32_t* dst, uint8_t hpos) { +static void _m6569_decode_pixels_debug(m6569_t* vic, uint8_t g_data, bool ba_pin, uint8_t* dst, uint8_t hpos) { _m6569_decode_pixels(vic, g_data, dst, hpos); - dst[0] = (dst[0] & 0xFF000000) | 0x00222222; - uint32_t mask = 0x00000000; + uint8_t c = 0; if (vic->rs.badline) { - mask = 0x00FF0000; + c |= 0x10; } if (ba_pin) { - mask = 0x000000FF; + c |= 0x20; } - /* sprites */ + // sprites const m6569_sprite_unit_t* su = &vic->sunit; - for (int si = 0; si < 8; si++) { + for (size_t si = 0; si < 8; si++) { if (su->disp_enabled[si]) { if ((hpos >= su->h_first[si]) && (hpos <= su->h_last[si])) { - mask |= 0x00880088; + c |= 0x40; } } } - /* main interrupt bit */ + // main interrupt bit if (vic->reg.int_latch & (1<<7)) { - mask |= 0x0000FF00; + c |= 0x80; + } + // previous raster position for xor-rendering current raster pos + uint8_t* prev_dst; + if (dst == vic->crt.fb) { + prev_dst = dst; + } + else { + prev_dst = dst - M6569_PIXELS_PER_TICK; } - if (mask != 0) { - for (int i = 0; i < 8; i++) { - dst[i] = (dst[i] & 0xFF888888) | mask; + for (size_t i = 0; i < M6569_PIXELS_PER_TICK; i++) { + if (i == 0) { + dst[i] = 0; + } + else { + dst[i] |= c; } + dst[i] ^= 0xF0; + prev_dst[i] ^= 0xF0; } } @@ -1180,8 +1180,8 @@ static void _m6569_decode_pixels_debug(m6569_t* vic, uint8_t g_data, bool ba_pin */ static inline void _m6569_rs_next_rasterline(m6569_t* vic) { vic->rs.h_count = 0; - /* new scanline */ - if (vic->rs.v_count == (_M6569_VTOTAL-1)) { + // new scanline + if (vic->rs.v_count == (M6569_VTOTAL-1)) { vic->rs.v_count = 0; vic->rs.vc_base = 0; } @@ -1216,7 +1216,7 @@ static inline void _m6569_rs_check_irq(m6569_t* vic) { */ static inline void _m6569_rs_update_badline(m6569_t* vic) { if (_M6569_RAST_RANGE(48, 247)) { - /* DEN bit must have been set in raster line $30 */ + // DEN bit must have been set in raster line $30 if (_M6569_RAST(48) && (vic->reg.ctrl_1 & M6569_CTRL1_DEN)) { vic->rs.frame_badlines_enabled = true; } @@ -1286,7 +1286,7 @@ static inline void _m6569_crt_next_crtline(m6569_t* vic) { } } -/* border unit functions */ +// border unit functions static inline void _m6569_bunit_left(m6569_t* vic, uint32_t hpos) { if (hpos == vic->brd.left) { /* 4. If the X coordinate reaches the left comparison value and the Y @@ -1347,21 +1347,21 @@ static inline uint8_t _m6569_i_access(m6569_t* vic) { } static inline uint8_t _m6569_g_i_access(m6569_t* vic) { - /* perform a g-access or i access */ + // perform a g-access or i access if (vic->rs.display_state) { uint16_t addr; if (vic->reg.ctrl_1 & M6569_CTRL1_BMM) { - /* bitmap mode: addr=|CB13|VC9|VC8|VC7|VC6|VC5|VC4|VC3|VC2|VC1|VC0|RC2|RC1|RC0| */ + // bitmap mode: addr=|CB13|VC9|VC8|VC7|VC6|VC5|VC4|VC3|VC2|VC1|VC0|RC2|RC1|RC0| addr = vic->rs.vc<<3 | vic->rs.rc; addr = (addr | (vic->mem.g_addr_or & (1<<13))) & vic->mem.g_addr_and; } else { - /* text mode: addr=|CB13|CB12|CB11|D7|D6|D5|D4|D3|D2|D1|D0|RC2|RC1|RC0| */ + // text mode: addr=|CB13|CB12|CB11|D7|D6|D5|D4|D3|D2|D1|D0|RC2|RC1|RC0| addr = ((vic->vm.line[vic->vm.vmli]&0xFF)<<3) | vic->rs.rc; addr = (addr | vic->mem.g_addr_or) & vic->mem.g_addr_and; } - vic->rs.vc = (vic->rs.vc + 1) & 0x3FF; /* VC is a 10-bit counter */ - vic->vm.next_vmli = (vic->vm.vmli + 1) & 0x3F; /* VMLI is a 6-bit counter */ + vic->rs.vc = (vic->rs.vc + 1) & 0x3FF; // VC is a 10-bit counter + vic->vm.next_vmli = (vic->vm.vmli + 1) & 0x3F; // VMLI is a 6-bit counter return (uint8_t) vic->mem.fetch_cb(addr, vic->mem.user_data); } else { @@ -1375,7 +1375,7 @@ static inline void _m6569_p_access(m6569_t* vic, uint32_t p_index) { } static inline void _m6569_s_access(m6569_t* vic, uint32_t s_index) { - /* sprite s-access: |MP7|MP6|MP5|MP4|MP3|MP2|MP1|MP0|MC5|MC4|MC3|MC2|MC1|MC0| */ + // sprite s-access: |MP7|MP6|MP5|MP4|MP3|MP2|MP1|MP0|MC5|MC4|MC3|MC2|MC1|MC0| m6569_sprite_unit_t* su = &vic->sunit; if (su->dma_enabled[s_index]) { uint16_t addr = (su->p_data[s_index]<<6) | su->mc[s_index]; @@ -1386,7 +1386,7 @@ static inline void _m6569_s_access(m6569_t* vic, uint32_t s_index) { } static inline uint8_t _m6569_s_i_access(m6569_t* vic, uint32_t s_index) { - /* perform an s-access if dma is enabled, otherwise an i-access */ + // perform an s-access if dma is enabled, otherwise an i-access m6569_sprite_unit_t* su = &vic->sunit; if (su->dma_enabled[s_index]) { uint16_t addr = (su->p_data[s_index]<<6) | su->mc[s_index]; @@ -1400,7 +1400,7 @@ static inline uint8_t _m6569_s_i_access(m6569_t* vic, uint32_t s_index) { } } -/* set BA pin if badline flag is set */ +// set BA pin if badline flag is set static inline uint64_t _m6569_ba(m6569_t* vic, uint64_t pins) { if (vic->rs.badline) { pins |= M6569_BA; @@ -1408,18 +1408,18 @@ static inline uint64_t _m6569_ba(m6569_t* vic, uint64_t pins) { return pins; } -/* set AEC pin */ +// set AEC pin static inline uint64_t _m6569_aec(uint64_t pins) { return pins | M6569_AEC; } -/* internal tick function */ +// internal tick function static uint64_t _m6569_tick(m6569_t* vic, uint64_t pins) { pins &= ~M6569_BA; uint8_t g_data = 0x00; _m6569_rs_update_badline(vic); - /* a raster line is 63 ticks, and each line goes through a fixed 'program' */ + // a raster line is 63 ticks, and each line goes through a fixed 'program' vic->rs.h_count++; vic->crt.x++; switch (vic->rs.h_count) { @@ -1608,7 +1608,7 @@ static uint64_t _m6569_tick(m6569_t* vic, uint64_t pins) { _m6569_bunit_end(vic); break; } - /*-- main interrupt bit --*/ + //-- main interrupt bit if (vic->reg.int_latch & vic->reg.int_mask & 0x0F) { vic->reg.int_latch |= M6569_INT_IRQ; } @@ -1619,36 +1619,31 @@ static uint64_t _m6569_tick(m6569_t* vic, uint64_t pins) { pins |= M6569_IRQ; } - /*--- decode pixels into framebuffer -------------------------------------*/ - if (vic->crt.rgba8_buffer) { - int x, y, w; - if (vic->debug_vis) { - x = vic->rs.h_count; - y = vic->rs.v_count; - w = _M6569_HTOTAL; - uint32_t* dst = vic->crt.rgba8_buffer + (y * w + x) * 8; - _m6569_decode_pixels_debug(vic, g_data, 0 != (pins & M6569_BA), dst, vic->rs.h_count); - } - else if ((vic->crt.x >= vic->crt.vis_x0) && (vic->crt.x < vic->crt.vis_x1) && - (vic->crt.y >= vic->crt.vis_y0) && (vic->crt.y < vic->crt.vis_y1)) - { - const int x = vic->crt.x - vic->crt.vis_x0; - const int y = vic->crt.y - vic->crt.vis_y0; - const int w = vic->crt.vis_w; - uint32_t* dst = vic->crt.rgba8_buffer + (y * w + x) * 8; - _m6569_decode_pixels(vic, g_data, dst, vic->rs.h_count); - } + //--- decode pixels into framebuffer + if (vic->debug_vis) { + const size_t x = vic->rs.h_count; + const size_t y = vic->rs.v_count; + uint8_t* dst = vic->crt.fb + (y * M6569_FRAMEBUFFER_WIDTH) + (x * M6569_PIXELS_PER_TICK); + _m6569_decode_pixels_debug(vic, g_data, 0 != (pins & M6569_BA), dst, vic->rs.h_count); + } + else if ((vic->crt.x >= vic->crt.vis_x0) && (vic->crt.x < vic->crt.vis_x1) && + (vic->crt.y >= vic->crt.vis_y0) && (vic->crt.y < vic->crt.vis_y1)) + { + const size_t x = vic->crt.x - vic->crt.vis_x0; + const size_t y = vic->crt.y - vic->crt.vis_y0; + uint8_t* dst = vic->crt.fb + (y * M6569_FRAMEBUFFER_WIDTH) + (x * M6569_PIXELS_PER_TICK); + _m6569_decode_pixels(vic, g_data, dst, vic->rs.h_count); } vic->vm.vmli = vic->vm.next_vmli; return pins; } -/* all-in-one tick function */ +// all-in-one tick function uint64_t m6569_tick(m6569_t* vic, uint64_t pins) { - /* per-tick actions */ + // per-tick actions pins = _m6569_tick(vic, pins); - /* register read/writes */ + // register read/writes if (pins & M6569_CS) { if (pins & M6569_RW) { pins = _m6569_read(vic, pins); @@ -1661,18 +1656,115 @@ uint64_t m6569_tick(m6569_t* vic, uint64_t pins) { return pins; } -int m6569_display_width(m6569_t* vic) { +chips_rect_t m6569_screen(m6569_t* vic) { CHIPS_ASSERT(vic); - return 8 * (vic->debug_vis ? _M6569_HTOTAL : vic->crt.vis_w); + return (chips_rect_t){ + .x = 0, + .y = 0, + .width = M6569_PIXELS_PER_TICK * (vic->debug_vis ? M6569_HTOTAL : vic->crt.vis_w), + .height = vic->debug_vis ? M6569_VTOTAL : vic->crt.vis_h, + }; } -int m6569_display_height(m6569_t* vic) { - CHIPS_ASSERT(vic); - return vic->debug_vis ? _M6569_VTOTAL : vic->crt.vis_h; +/* +http://unusedino.de/ec64/technical/misc/vic656x/colors/ +static const uint32_t _m6569_colors[16] = { + _M6569_RGBA8(0x00,0x00,0x00), + _M6569_RGBA8(0xFF,0xFF,0xFF), + _M6569_RGBA8(0x68,0x37,0x2B), + _M6569_RGBA8(0x70,0xA4,0xB2), + _M6569_RGBA8(0x6F,0x3D,0x86), + _M6569_RGBA8(0x58,0x8D,0x43), + _M6569_RGBA8(0x35,0x28,0x79), + _M6569_RGBA8(0xB8,0xC7,0x6F), + _M6569_RGBA8(0x6F,0x4F,0x25), + _M6569_RGBA8(0x43,0x39,0x00), + _M6569_RGBA8(0x9A,0x67,0x59), + _M6569_RGBA8(0x44,0x44,0x44), + _M6569_RGBA8(0x6C,0x6C,0x6C), + _M6569_RGBA8(0x9A,0xD2,0x84), + _M6569_RGBA8(0x6C,0x5E,0xB5), + _M6569_RGBA8(0x95,0x95,0x95) +}; +*/ +#define _M6569_RGBA8(r,g,b) (0xFF000000|(b<<16)|(g<<8)|(r)) + +/* https://www.pepto.de/projects/colorvic/ */ +static const uint32_t _m6569_colors[16] = { + _M6569_RGBA8(0x00,0x00,0x00), + _M6569_RGBA8(0xff,0xff,0xff), + _M6569_RGBA8(0x81,0x33,0x38), + _M6569_RGBA8(0x75,0xce,0xc8), + _M6569_RGBA8(0x8e,0x3c,0x97), + _M6569_RGBA8(0x56,0xac,0x4d), + _M6569_RGBA8(0x2e,0x2c,0x9b), + _M6569_RGBA8(0xed,0xf1,0x71), + _M6569_RGBA8(0x8e,0x50,0x29), + _M6569_RGBA8(0x55,0x38,0x00), + _M6569_RGBA8(0xc4,0x6c,0x71), + _M6569_RGBA8(0x4a,0x4a,0x4a), + _M6569_RGBA8(0x7b,0x7b,0x7b), + _M6569_RGBA8(0xa9,0xff,0x9f), + _M6569_RGBA8(0x70,0x6d,0xeb), + _M6569_RGBA8(0xb2,0xb2,0xb2), +}; + +chips_range_t m6569_palette(void) { + return (chips_range_t){ + .ptr = (void*)_m6569_colors, + .size = sizeof(_m6569_colors) + }; +} + +chips_range_t m6569_dbg_palette(void) { + static uint32_t dbg_palette[256]; + size_t i = 0; + for (; i < 16; i++) { + dbg_palette[i] = _m6569_colors[i]; + } + for (;i < 256; i++) { + uint32_t c = ((_m6569_colors[i&0xF] >> 2) & 0xFF3F3F3F) | 0xFF000000; + // bad line + if (i & 0x10) { + c |= 0x00FF0000; + } + // BA pin active + if (i & 0x20) { + c |= 0x000000FF; + } + // sprite active + if (i & 0x40) { + c |= 0x00880088; + } + // interrupt active + if (i & 0x80) { + c |= 0x0000FF00; + } + dbg_palette[i] = c; + } + return (chips_range_t){ + .ptr = dbg_palette, + .size = sizeof(dbg_palette) + }; } -uint32_t m6569_color(int i) { - CHIPS_ASSERT((i >= 0) && (i < 16)); +uint32_t m6569_color(size_t i) { + CHIPS_ASSERT(i < 16); return _m6569_colors[i]; } -#endif /* CHIPS_IMPL */ + +void m6569_snapshot_onsave(m6569_t* snapshot) { + CHIPS_ASSERT(snapshot); + snapshot->mem.fetch_cb = 0; + snapshot->mem.user_data = 0; + snapshot->crt.fb = 0; +} + +void m6569_snapshot_onload(m6569_t* snapshot, m6569_t* sys) { + CHIPS_ASSERT(snapshot && sys); + snapshot->mem.fetch_cb = sys->mem.fetch_cb; + snapshot->mem.user_data = sys->mem.user_data; + snapshot->crt.fb = sys->crt.fb; +} + +#endif // CHIPS_IMPL diff --git a/chips/m6581.h b/chips/m6581.h index e1f4b29d..5f1b7a3a 100644 --- a/chips/m6581.h +++ b/chips/m6581.h @@ -10,11 +10,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - ~~~C + ~~~C CHIPS_ASSERT(c) ~~~ @@ -56,7 +56,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -65,32 +65,50 @@ extern "C" { #endif -/* address bus pins A0..A4 */ -#define M6581_A0 (1ULL<<0) -#define M6581_A1 (1ULL<<1) -#define M6581_A2 (1ULL<<2) -#define M6581_A3 (1ULL<<3) -#define M6581_A4 (1ULL<<4) +// address bus pins A0..A4 +#define M6581_PIN_A0 (0) +#define M6581_PIN_A1 (1) +#define M6581_PIN_A2 (2) +#define M6581_PIN_A3 (3) +#define M6581_PIN_A4 (4) + +// data bus pins D0..D7 +#define M6581_PIN_D0 (16) +#define M6581_PIN_D1 (17) +#define M6581_PIN_D2 (18) +#define M6581_PIN_D3 (19) +#define M6581_PIN_D4 (20) +#define M6581_PIN_D5 (21) +#define M6581_PIN_D6 (22) +#define M6581_PIN_D7 (23) + +// shared control pins +#define M6581_PIN_RW (24) /* same as M6502_RW */ + +// chip-specific pins +#define M6581_PIN_CS (40) /* chip-select */ +#define M6581_PIN_SAMPLE (41) /* virtual "audio sample ready" pin */ + +// pin bit masks +#define M6581_A0 (1ULL< #include @@ -99,48 +99,84 @@ extern "C" { /* the address output pins share the same pin locations as the system address bus but they are only set in the output pins mask of mc6845_tick() */ -#define MC6845_MA0 (1ULL<<0) -#define MC6845_MA1 (1ULL<<1) -#define MC6845_MA2 (1ULL<<2) -#define MC6845_MA3 (1ULL<<3) -#define MC6845_MA4 (1ULL<<4) -#define MC6845_MA5 (1ULL<<5) -#define MC6845_MA6 (1ULL<<6) -#define MC6845_MA7 (1ULL<<7) -#define MC6845_MA8 (1ULL<<8) -#define MC6845_MA9 (1ULL<<9) -#define MC6845_MA10 (1ULL<<10) -#define MC6845_MA11 (1ULL<<11) -#define MC6845_MA12 (1ULL<<12) -#define MC6845_MA13 (1ULL<<13) - -/* data bus pins */ -#define MC6845_D0 (1ULL<<16) -#define MC6845_D1 (1ULL<<17) -#define MC6845_D2 (1ULL<<18) -#define MC6845_D3 (1ULL<<19) -#define MC6845_D4 (1ULL<<20) -#define MC6845_D5 (1ULL<<21) -#define MC6845_D6 (1ULL<<22) -#define MC6845_D7 (1ULL<<23) - -/* control pins */ -#define MC6845_CS (1ULL<<40) /* chip select */ -#define MC6845_RS (1ULL<<41) /* register select (active: data register, inactive: address register) */ -#define MC6845_RW (1ULL<<42) /* read/write (active: write, inactive: read) */ -#define MC6845_LPSTB (1ULL<<43) /* light pen strobe */ - -/* display status pins */ -#define MC6845_DE (1ULL<<44) /* display enable */ -#define MC6845_VS (1ULL<<45) /* vsync active */ -#define MC6845_HS (1ULL<<46) /* hsync active */ - -/* row-address output pins */ -#define MC6845_RA0 (1ULL<<48) -#define MC6845_RA1 (1ULL<<49) -#define MC6845_RA2 (1ULL<<50) -#define MC6845_RA3 (1ULL<<51) -#define MC6845_RA4 (1ULL<<52) +#define MC6845_PIN_MA0 (0) +#define MC6845_PIN_MA1 (1) +#define MC6845_PIN_MA2 (2) +#define MC6845_PIN_MA3 (3) +#define MC6845_PIN_MA4 (4) +#define MC6845_PIN_MA5 (5) +#define MC6845_PIN_MA6 (6) +#define MC6845_PIN_MA7 (7) +#define MC6845_PIN_MA8 (8) +#define MC6845_PIN_MA9 (9) +#define MC6845_PIN_MA10 (10) +#define MC6845_PIN_MA11 (11) +#define MC6845_PIN_MA12 (12) +#define MC6845_PIN_MA13 (13) + +// data bus pins +#define MC6845_PIN_D0 (16) +#define MC6845_PIN_D1 (17) +#define MC6845_PIN_D2 (18) +#define MC6845_PIN_D3 (19) +#define MC6845_PIN_D4 (20) +#define MC6845_PIN_D5 (21) +#define MC6845_PIN_D6 (22) +#define MC6845_PIN_D7 (23) + +// control pins +#define MC6845_PIN_CS (40) // chip select +#define MC6845_PIN_RS (41) // register select (active: data register, inactive: address register) +#define MC6845_PIN_RW (42) // read/write (active: write, inactive: read) +#define MC6845_PIN_LPSTB (43) // light pen strobe + +// display status pins +#define MC6845_PIN_DE (44) // display enable +#define MC6845_PIN_VS (45) // vsync active +#define MC6845_PIN_HS (46) // hsync active + +// row-address output pins +#define MC6845_PIN_RA0 (48) +#define MC6845_PIN_RA1 (49) +#define MC6845_PIN_RA2 (50) +#define MC6845_PIN_RA3 (51) +#define MC6845_PIN_RA4 (52) + +// pin bit masks +#define MC6845_MA0 (1ULL<ma = 0; @@ -458,7 +494,7 @@ static inline void _mc6845_co_cmp_raster(mc6845_t* c) { } if (c->r_ctr >= (max_scanline + 1)) { c->co_raster = true; - } + } } uint64_t mc6845_iorq(mc6845_t* c, uint64_t pins) { @@ -582,7 +618,7 @@ static inline void _mc6845_scanline(mc6845_t* c) { /* special case TYPE 0, reload ma_row_start at each scanline of row 0 */ if ((c->type == MC6845_TYPE_UM6845R) && (c->v_ctr == 0)) { c->ma_store = (c->start_addr_hi<<8) | c->start_addr_lo; - c->ma_row_start = c->ma_store; + c->ma_row_start = c->ma_store; } if (c->vs) { _mc6845_co_cmp_vswidth(c); diff --git a/chips/mc6847.h b/chips/mc6847.h index 891a27f9..2dd57f8e 100644 --- a/chips/mc6847.h +++ b/chips/mc6847.h @@ -4,11 +4,11 @@ Do this: #define CHIPS_IMPL - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + CHIPS_ASSERT(c) -- your own assert macro (default: assert(c)) EMULATED PINS: @@ -46,7 +46,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. */ #include #include @@ -112,145 +112,195 @@ extern "C" { The CSS pins select between 2 possible color sets. */ -/* address bus pins */ -#define MC6847_A0 (1ULL<<0) -#define MC6847_A1 (1ULL<<1) -#define MC6847_A2 (1ULL<<2) -#define MC6847_A3 (1ULL<<3) -#define MC6847_A4 (1ULL<<4) -#define MC6847_A5 (1ULL<<5) -#define MC6847_A6 (1ULL<<6) -#define MC6847_A7 (1ULL<<7) -#define MC6847_A8 (1ULL<<8) -#define MC6847_A9 (1ULL<<9) -#define MC6847_A10 (1ULL<<10) -#define MC6847_A11 (1ULL<<11) -#define MC6847_A12 (1ULL<<12) - -/* data bus pins */ -#define MC6847_D0 (1ULL<<16) -#define MC6847_D1 (1ULL<<17) -#define MC6847_D2 (1ULL<<18) -#define MC6847_D3 (1ULL<<19) -#define MC6847_D4 (1ULL<<20) -#define MC6847_D5 (1ULL<<21) -#define MC6847_D6 (1ULL<<22) -#define MC6847_D7 (1ULL<<23) - -/* synchronization output pins */ -#define MC6847_FS (1ULL<<40) /* field sync */ -#define MC6847_HS (1ULL<<41) /* horizontal sync */ -#define MC6847_RP (1ULL<<42) /* row preset (not emulated) */ - -/* mode-select input pins */ -#define MC6847_AG (1ULL<<43) /* graphics mode enable */ -#define MC6847_AS (1ULL<<44) /* semi-graphics mode enable */ -#define MC6847_INTEXT (1ULL<<45) /* internal/external select */ -#define MC6847_INV (1ULL<<46) /* invert enable */ -#define MC6847_GM0 (1ULL<<47) /* graphics mode select 0 */ -#define MC6847_GM1 (1ULL<<48) /* graphics mode select 1 */ -#define MC6847_GM2 (1ULL<<49) /* graphics mode select 2 */ -#define MC6847_CSS (1ULL<<50) /* color select pin */ +// address bus pins +#define MC6847_PIN_A0 (0) +#define MC6847_PIN_A1 (1) +#define MC6847_PIN_A2 (2) +#define MC6847_PIN_A3 (3) +#define MC6847_PIN_A4 (4) +#define MC6847_PIN_A5 (5) +#define MC6847_PIN_A6 (6) +#define MC6847_PIN_A7 (7) +#define MC6847_PIN_A8 (8) +#define MC6847_PIN_A9 (9) +#define MC6847_PIN_A10 (10) +#define MC6847_PIN_A11 (11) +#define MC6847_PIN_A12 (12) + +// data bus pins +#define MC6847_PIN_D0 (16) +#define MC6847_PIN_D1 (17) +#define MC6847_PIN_D2 (18) +#define MC6847_PIN_D3 (19) +#define MC6847_PIN_D4 (20) +#define MC6847_PIN_D5 (21) +#define MC6847_PIN_D6 (22) +#define MC6847_PIN_D7 (23) + +// synchronization output pins +#define MC6847_PIN_FS (40) // field sync +#define MC6847_PIN_HS (41) // horizontal sync +#define MC6847_PIN_RP (42) // row preset (not emulated) + +// mode-select input pins +#define MC6847_PIN_AG (43) // graphics mode enable +#define MC6847_PIN_AS (44) // semi-graphics mode enable +#define MC6847_PIN_INTEXT (45) // internal/external select +#define MC6847_PIN_INV (46) // invert enable +#define MC6847_PIN_GM0 (47) // graphics mode select 0 +#define MC6847_PIN_GM1 (48) // graphics mode select 1 +#define MC6847_PIN_GM2 (49) // graphics mode select 2 +#define MC6847_PIN_CSS (50) // color select pin + +// pin bit masks +#define MC6847_A0 (1ULL<>16)) -/* merge 8-bit data bus value into 64-bit pins */ +// merge 8-bit data bus value into 64-bit pins #define MC6847_SET_DATA(p,d) {p=(((p)&~0xFF0000ULL)|(((d)<<16)&0xFF0000ULL));} -/* public constants */ -#define MC6847_VBLANK_LINES (13) /* 13 lines vblank at top of screen */ -#define MC6847_TOP_BORDER_LINES (25) /* 25 lines top border */ -#define MC6847_DISPLAY_LINES (192) /* 192 lines visible display area */ -#define MC6847_BOTTOM_BORDER_LINES (26) /* 26 lines bottom border */ -#define MC6847_VRETRACE_LINES (6) /* 6 'lines' for vertical retrace */ -#define MC6847_ALL_LINES (262) /* all of the above */ +// public constants +#define MC6847_VBLANK_LINES (13) // 13 lines vblank at top of screen +#define MC6847_TOP_BORDER_LINES (25) // 25 lines top border +#define MC6847_DISPLAY_LINES (192) // 192 lines visible display area +#define MC6847_BOTTOM_BORDER_LINES (26) // 26 lines bottom border +#define MC6847_VRETRACE_LINES (6) // 6 'lines' for vertical retrace +#define MC6847_ALL_LINES (262) // all of the above #define MC6847_DISPLAY_START (MC6847_VBLANK_LINES+MC6847_TOP_BORDER_LINES) #define MC6847_DISPLAY_END (MC6847_DISPLAY_START+MC6847_DISPLAY_LINES) #define MC6847_BOTTOM_BORDER_END (MC6847_DISPLAY_END+MC6847_BOTTOM_BORDER_LINES) #define MC6847_FSYNC_START (MC6847_DISPLAY_END) -/* pixel width and height of entire visible area, including border */ +// hardware color indices +#define MC6847_HWCOLOR_GFX_GREEN (0) +#define MC6847_HWCOLOR_GFX_YELLOW (1) +#define MC6847_HWCOLOR_GFX_BLUE (2) +#define MC6847_HWCOLOR_GFX_RED (3) +#define MC6847_HWCOLOR_GFX_BUFF (4) +#define MC6847_HWCOLOR_GFX_CYAN (5) +#define MC6847_HWCOLOR_GFX_MAGENTA (6) +#define MC6847_HWCOLOR_GFX_ORANGE (7) +#define MC6847_HWCOLOR_ALNUM_GREEN (8) +#define MC6847_HWCOLOR_ALNUM_DARK_GREEN (9) +#define MC6847_HWCOLOR_ALNUM_ORANGE (10) +#define MC6847_HWCOLOR_ALNUM_DARK_ORANGE (11) +#define MC6847_HWCOLOR_BLACK (12) +#define MC6847_HWCOLOR_NUM (13) + +// pixel width and height of entire visible area, including border #define MC6847_DISPLAY_WIDTH (320) #define MC6847_DISPLAY_HEIGHT (MC6847_TOP_BORDER_LINES+MC6847_DISPLAY_LINES+MC6847_BOTTOM_BORDER_LINES) -/* pixel width and height of only the image area, without border */ +// framebuffer width and height +#define MC6847_FRAMEBUFFER_WIDTH (512) +#define MC6847_FRAMEBUFFER_HEIGHT (MC6847_DISPLAY_HEIGHT) +#define MC6847_FRAMEBUFFER_SIZE_BYTES (MC6847_FRAMEBUFFER_WIDTH * MC6847_FRAMEBUFFER_HEIGHT) + +// pixel width and height of only the image area, without border #define MC6847_IMAGE_WIDTH (256) #define MC6847_IMAGE_HEIGHT (192) -/* horizontal border width */ +// horizontal border width #define MC6847_BORDER_PIXELS ((MC6847_DISPLAY_WIDTH-MC6847_IMAGE_WIDTH)/2) -/* the MC6847 is always clocked at 3.579 MHz */ +// the MC6847 is always clocked at 3.579 MHz #define MC6847_TICK_HZ (3579545) -/* fixed point precision for more precise error accumulation */ +// fixed point precision for more precise error accumulation #define MC6847_FIXEDPOINT_SCALE (16) -/* a memory-fetch callback, used to read video memory bytes into the MC6847 */ +// a memory-fetch callback, used to read video memory bytes into the MC6847 typedef uint64_t (*mc6847_fetch_t)(uint64_t pins, void* user_data); -/* the mc6847 setup parameters */ +// the mc6847 setup parameters typedef struct { - /* the CPU tick rate in hz */ + // the CPU tick rate in hz int tick_hz; - /* pointer to an RGBA8 framebuffer where video image is written to */ - uint32_t* rgba8_buffer; - /* size of rgba8_buffer in bytes (must be at least 320*244*4=312320 bytes) */ - size_t rgba8_buffer_size; - /* memory-fetch callback */ + // pointer to an uint8_t framebuffer where video image is written to (must be at least 512*244 bytes) + chips_range_t framebuffer; + // memory-fetch callback mc6847_fetch_t fetch_cb; - /* optional user-data for the fetch callback */ + // optional user-data for the fetch callback void* user_data; } mc6847_desc_t; -/* the mc6847 state struct */ +// the mc6847 state struct typedef struct { - /* last pin state */ + // last pin state uint64_t pins; - /* the graphics mode color palette (RGBA8) */ - uint32_t palette[8]; - /* the black color as RGBA8 */ - uint32_t black; - /* the alpha-numeric mode bright and dark green and orange colors */ - uint32_t alnum_green; - uint32_t alnum_dark_green; - uint32_t alnum_orange; - uint32_t alnum_dark_orange; - - /* internal counters */ + + // internal counters int h_count; int h_sync_start; int h_sync_end; int h_period; int l_count; - /* true during field-sync */ + // true during field-sync bool fs; - /* the fetch callback function */ + // the fetch callback function mc6847_fetch_t fetch_cb; - /* optional user-data for the fetch-callback */ + // optional user-data for the fetch-callback void* user_data; - /* pointer to RGBA8 buffer where decoded video image is written too */ - uint32_t* rgba8_buffer; + // pointer to uint8_t buffer where decoded video image is written too + uint8_t* fb; + // hardware colors + uint32_t hwcolors[MC6847_HWCOLOR_NUM]; } mc6847_t; -/* initialize a new mc6847_t instance */ +// initialize a new mc6847_t instance void mc6847_init(mc6847_t* vdg, const mc6847_desc_t* desc); -/* reset a mc6847_t instance */ +// reset a mc6847_t instance void mc6847_reset(mc6847_t* vdg); -/* tick the mc6847_t instance, this will call the fetch_cb and generate the image */ +// tick the mc6847_t instance, this will call the fetch_cb and generate the image uint64_t mc6847_tick(mc6847_t* vdg, uint64_t pins); +// prepare mc6847_t snapshot for saving +void mc6847_snapshot_onsave(mc6847_t* snapshot); +// fixup mc6847_t snapshot after loading +void mc6847_snapshot_onload(mc6847_t* snapshot, mc6847_t* sys); #ifdef __cplusplus -} /* extern "C" */ +} // extern "C" #endif /*--- IMPLEMENTATION ---------------------------------------------------------*/ @@ -266,28 +316,27 @@ uint64_t mc6847_tick(mc6847_t* vdg, uint64_t pins); void mc6847_init(mc6847_t* vdg, const mc6847_desc_t* desc) { CHIPS_ASSERT(vdg && desc); - CHIPS_ASSERT(desc->rgba8_buffer); - CHIPS_ASSERT(desc->rgba8_buffer_size >= (MC6847_DISPLAY_WIDTH*MC6847_DISPLAY_HEIGHT*sizeof(uint32_t))); + CHIPS_ASSERT(desc->framebuffer.ptr && (desc->framebuffer.size <= MC6847_FRAMEBUFFER_SIZE_BYTES)); CHIPS_ASSERT(desc->fetch_cb); CHIPS_ASSERT((desc->tick_hz > 0) && (desc->tick_hz < MC6847_TICK_HZ)); memset(vdg, 0, sizeof(*vdg)); - vdg->rgba8_buffer = desc->rgba8_buffer; + vdg->fb = desc->framebuffer.ptr; vdg->fetch_cb = desc->fetch_cb; vdg->user_data = desc->user_data; /* compute counter periods, the MC6847 is always clocked at 3.579 MHz, - and the frequency of how the tick function is called must be + and the frequency of how the tick function is called must be communicated to the init function one scanline is 228 3.5 MC6847 ticks */ int64_t tmp = (228LL * desc->tick_hz * MC6847_FIXEDPOINT_SCALE) / MC6847_TICK_HZ; vdg->h_period = (int) tmp; - /* hsync starts at tick 10 of a scanline */ + // hsync starts at tick 10 of a scanline tmp = (10LL * desc->tick_hz * MC6847_FIXEDPOINT_SCALE) / MC6847_TICK_HZ; vdg->h_sync_start = (int) tmp; - /* hsync is 16 ticks long */ + // hsync is 16 ticks long tmp = (26LL * desc->tick_hz * MC6847_FIXEDPOINT_SCALE) / MC6847_TICK_HZ; vdg->h_sync_end = (int) tmp; @@ -313,21 +362,19 @@ void mc6847_init(mc6847_t* vdg, const mc6847_desc_t* desc) { color intensities are slightly boosted */ - vdg->palette[0] = _MC6847_RGBA(19, 146, 11); /* green */ - vdg->palette[1] = _MC6847_RGBA(155, 150, 10); /* yellow */ - vdg->palette[2] = _MC6847_RGBA(2, 22, 175); /* blue */ - vdg->palette[3] = _MC6847_RGBA(155, 22, 7); /* red */ - vdg->palette[4] = _MC6847_RGBA(141, 150, 154); /* buff */ - vdg->palette[5] = _MC6847_RGBA(15, 143, 155); /* cyan */ - vdg->palette[6] = _MC6847_RGBA(139, 39, 155); /* cyan */ - vdg->palette[7] = _MC6847_RGBA(140, 31, 11); /* orange */ - - /* black level color, and alpha-numeric display mode colors */ - vdg->black = 0xFF111111; - vdg->alnum_green = _MC6847_RGBA(19, 146, 11); - vdg->alnum_dark_green = 0xFF002400; - vdg->alnum_orange = _MC6847_RGBA(140, 31, 11); - vdg->alnum_dark_orange = 0xFF000E22; + vdg->hwcolors[MC6847_HWCOLOR_GFX_GREEN] = _MC6847_RGBA(19, 146, 11); + vdg->hwcolors[MC6847_HWCOLOR_GFX_YELLOW] = _MC6847_RGBA(155, 150, 10); + vdg->hwcolors[MC6847_HWCOLOR_GFX_BLUE] = _MC6847_RGBA(2, 22, 175); + vdg->hwcolors[MC6847_HWCOLOR_GFX_RED] = _MC6847_RGBA(155, 22, 7); + vdg->hwcolors[MC6847_HWCOLOR_GFX_BUFF] = _MC6847_RGBA(141, 150, 154); + vdg->hwcolors[MC6847_HWCOLOR_GFX_CYAN] = _MC6847_RGBA(15, 143, 155); + vdg->hwcolors[MC6847_HWCOLOR_GFX_MAGENTA] = _MC6847_RGBA(139, 39, 155); + vdg->hwcolors[MC6847_HWCOLOR_GFX_ORANGE] = _MC6847_RGBA(140, 31, 11); + vdg->hwcolors[MC6847_HWCOLOR_ALNUM_GREEN] = _MC6847_RGBA(19, 146, 11); + vdg->hwcolors[MC6847_HWCOLOR_ALNUM_DARK_GREEN] = 0xFF002400; + vdg->hwcolors[MC6847_HWCOLOR_ALNUM_ORANGE] = _MC6847_RGBA(140, 31, 11); + vdg->hwcolors[MC6847_HWCOLOR_ALNUM_DARK_ORANGE] = 0xFF000E22; + vdg->hwcolors[MC6847_HWCOLOR_BLACK] = 0xFF111111; } void mc6847_reset(mc6847_t* vdg) { @@ -408,39 +455,39 @@ static const uint8_t _mc6847_font[64 * 12] = { }; -static inline uint32_t _mc6847_border_color(mc6847_t* vdg, uint64_t pins) { +static inline uint8_t _mc6847_border_color(uint64_t pins) { if (pins & MC6847_AG) { - /* a graphics mode, either green or buff, depending on CSS pin */ - return (pins & MC6847_CSS) ? vdg->palette[4] : vdg->palette[0]; + // a graphics mode, either green or buff, depending on CSS pin + return (pins & MC6847_CSS) ? MC6847_HWCOLOR_GFX_BUFF : MC6847_HWCOLOR_GFX_GREEN; } else { - /* alphanumeric or semigraphics mode, always black */ - return vdg->black; + // alphanumeric or semigraphics mode, always black + return MC6847_HWCOLOR_BLACK; } } -static void _mc6847_decode_border(mc6847_t* vdg, uint64_t pins, int y) { - uint32_t* dst = &(vdg->rgba8_buffer[y * MC6847_DISPLAY_WIDTH]); - uint32_t c = _mc6847_border_color(vdg, pins); - for (int x = 0; x < MC6847_DISPLAY_WIDTH; x++) { +static void _mc6847_decode_border(mc6847_t* vdg, uint64_t pins, size_t y) { + uint8_t* dst = &(vdg->fb[y * MC6847_FRAMEBUFFER_WIDTH]); + uint8_t c = _mc6847_border_color(pins); + for (size_t x = 0; x < MC6847_DISPLAY_WIDTH; x++) { *dst++ = c; } } -static uint64_t _mc6847_decode_scanline(mc6847_t* vdg, uint64_t pins, int y) { - uint32_t* dst = &(vdg->rgba8_buffer[(y+MC6847_TOP_BORDER_LINES) * MC6847_DISPLAY_WIDTH]); - uint32_t bc = _mc6847_border_color(vdg, pins); +static uint64_t _mc6847_decode_scanline(mc6847_t* vdg, uint64_t pins, size_t y) { + uint8_t* dst = &(vdg->fb[(y + MC6847_TOP_BORDER_LINES) * MC6847_FRAMEBUFFER_WIDTH]); + uint8_t bc = _mc6847_border_color(pins); void* ud = vdg->user_data; - /* left border */ - for (int i = 0; i < MC6847_BORDER_PIXELS; i++) { + // left border + for (size_t i = 0; i < MC6847_BORDER_PIXELS; i++) { *dst++ = bc; } - /* visible scanline */ + // visible scanline if (pins & MC6847_AG) { - /* one of the 8 graphics modes */ - uint8_t sub_mode = (uint8_t) ((pins & (MC6847_GM2|MC6847_GM1)) / MC6847_GM1); + // one of the 8 graphics modes + size_t sub_mode = (uint8_t) ((pins & (MC6847_GM2|MC6847_GM1)) / MC6847_GM1); if (pins & MC6847_GM0) { /* one of the 'resolution modes' (1 bit == 1 pixel block) GM2|GM1: @@ -449,18 +496,18 @@ static uint64_t _mc6847_decode_scanline(mc6847_t* vdg, uint64_t pins, int y) { 10: RG3, 128x192, 16 bytes per row 11: RG6, 256x192, 32 bytes per row */ - int dots_per_bit = (sub_mode < 3) ? 2 : 1; - int bytes_per_row = (sub_mode < 3) ? 16 : 32; - int row_height = (pins & MC6847_GM2) ? 1 : (pins & MC6847_GM1) ? 2 : 3; + size_t dots_per_bit = (sub_mode < 3) ? 2 : 1; + size_t bytes_per_row = (sub_mode < 3) ? 16 : 32; + size_t row_height = (pins & MC6847_GM2) ? 1 : (pins & MC6847_GM1) ? 2 : 3; uint16_t addr = (y / row_height) * bytes_per_row; - uint32_t fg_color = (pins & MC6847_CSS) ? vdg->palette[4] : vdg->palette[0]; - for (int x = 0; x < bytes_per_row; x++) { + uint8_t fg_color = (pins & MC6847_CSS) ? MC6847_HWCOLOR_GFX_BUFF : MC6847_HWCOLOR_GFX_GREEN; + for (size_t x = 0; x < bytes_per_row; x++) { MC6847_SET_ADDR(pins, addr++); pins = vdg->fetch_cb(pins, ud); uint8_t m = MC6847_GET_DATA(pins); for (int p = 7; p >= 0; p--) { - uint32_t c = ((m>>p) & 1) ? fg_color : vdg->black; - for (int d = 0; d < dots_per_bit; d++) { + uint8_t c = ((m>>p) & 1) ? fg_color : MC6847_HWCOLOR_BLACK; + for (size_t d = 0; d < dots_per_bit; d++) { *dst++ = c; } } @@ -475,18 +522,18 @@ static uint64_t _mc6847_decode_scanline(mc6847_t* vdg, uint64_t pins, int y) { 10: CG3, 128x96, 32 bytes per row 11: CG6, 128x192, 32 bytes per row */ - uint32_t pal_offset = (pins & MC6847_CSS) ? 4 : 0; - int dots_per_2bit = (sub_mode == 0) ? 4 : 2; - int bytes_per_row = (sub_mode == 0) ? 16 : 32; - int row_height = (pins & MC6847_GM2) ? ((pins & MC6847_GM1) ? 1 : 2) : 3; + uint8_t color_offset = (pins & MC6847_CSS) ? 4 : 0; + size_t dots_per_2bit = (sub_mode == 0) ? 4 : 2; + size_t bytes_per_row = (sub_mode == 0) ? 16 : 32; + size_t row_height = (pins & MC6847_GM2) ? ((pins & MC6847_GM1) ? 1 : 2) : 3; uint16_t addr = (y / row_height) * bytes_per_row; - for (int x = 0; x < bytes_per_row; x++) { + for (size_t x = 0; x < bytes_per_row; x++) { MC6847_SET_ADDR(pins, addr++); pins = vdg->fetch_cb(pins, ud); uint8_t m = MC6847_GET_DATA(pins); for (int p = 6; p >= 0; p -= 2) { - const uint32_t c = vdg->palette[((m>>p) & 3) + pal_offset]; - for (int d = 0; d < dots_per_2bit; d++) { + const uint8_t c = ((m>>p) & 3) + color_offset; + for (size_t d = 0; d < dots_per_2bit; d++) { *dst++ = c; } } @@ -494,28 +541,28 @@ static uint64_t _mc6847_decode_scanline(mc6847_t* vdg, uint64_t pins, int y) { } } else { - /* we're in alphanumeric/semigraphics mode, one cell is 8x12 pixels */ + // we're in alphanumeric/semigraphics mode, one cell is 8x12 pixels - /* the vidmem src address and offset into the font data */ + // the vidmem src address and offset into the font data uint16_t addr = (y / 12) * 32; - uint8_t m; /* the pixel bitmask */ - int chr_y = y % 12; - /* bit shifters to extract a 2x2 or 2x3 semigraphics 2-bit stack */ - int shift_2x2 = (1 - (chr_y / 6))*2; - int shift_2x3 = (2 - (chr_y / 4))*2; - uint32_t alnum_fg = (pins & MC6847_CSS) ? vdg->alnum_orange : vdg->alnum_green; - uint32_t alnum_bg = (pins & MC6847_CSS) ? vdg->alnum_dark_orange : vdg->alnum_dark_green; - for (int x = 0; x < 32; x++) { + uint8_t m; // the pixel bitmask + size_t chr_y = y % 12; + // bit shifters to extract a 2x2 or 2x3 semigraphics 2-bit stack + size_t shift_2x2 = (1 - (chr_y / 6))*2; + size_t shift_2x3 = (2 - (chr_y / 4))*2; + uint8_t alnum_fg = (pins & MC6847_CSS) ? MC6847_HWCOLOR_ALNUM_ORANGE : MC6847_HWCOLOR_ALNUM_GREEN; + uint8_t alnum_bg = (pins & MC6847_CSS) ? MC6847_HWCOLOR_ALNUM_DARK_ORANGE : MC6847_HWCOLOR_ALNUM_DARK_GREEN; + for (size_t x = 0; x < 32; x++) { MC6847_SET_ADDR(pins, addr++); pins = vdg->fetch_cb(pins, ud); uint8_t chr = MC6847_GET_DATA(pins); if (pins & MC6847_AS) { - /* semigraphics mode */ - uint32_t fg_color; + // semigraphics mode + uint8_t fg_color; if (pins & MC6847_INTEXT) { /* 2x3 semigraphics, 2 color sets at 4 colors (selected by CSS pin) |C1|C0|L5|L4|L3|L2|L1|L0| - + +--+--+ |L5|L4| +--+--+ @@ -525,15 +572,15 @@ static uint64_t _mc6847_decode_scanline(mc6847_t* vdg, uint64_t pins, int y) { +--+--+ */ - /* extract the 2 horizontal bits from one of the 3 stacks */ + // extract the 2 horizontal bits from one of the 3 stacks m = (chr>>shift_2x3) & 3; - /* 2 bits of color, CSS bit selects upper or lower half of color palette */ - fg_color = vdg->palette[((chr>>6)&3) + ((pins&MC6847_CSS)?4:0)]; + // 2 bits of color, CSS bit selects upper or lower half of color palette + fg_color = ((chr>>6)&3) + ((pins & MC6847_CSS) ? 4:0); } else { /* 2x2 semigraphics, 8 colors + black |xx|C2|C1|C0|L3|L2|L1|L0| - + +--+--+ |L3|L2| +--+--+ @@ -541,14 +588,14 @@ static uint64_t _mc6847_decode_scanline(mc6847_t* vdg, uint64_t pins, int y) { +--+--+ */ - /* extract the 2 horizontal bits from the upper or lower stack */ + // extract the 2 horizontal bits from the upper or lower stack m = (chr>>shift_2x2) & 3; - /* 3 color bits directly point into the color palette */ - fg_color = vdg->palette[(chr>>4) & 7]; + // 3 color bits directly point into the color palette + fg_color = (chr>>4) & 7; } - /* write the horizontal pixel blocks (2 blocks @ 4 pixel each) */ + // write the horizontal pixel blocks (2 blocks @ 4 pixel each) for (int p = 1; p>=0; p--) { - uint32_t c = (m & (1<black; + uint8_t c = (m & (1<= 0; p--) { *dst++ = m & (1<h_count += MC6847_FIXEDPOINT_SCALE; if ((vdg->h_count >= vdg->h_sync_start) && (vdg->h_count < vdg->h_sync_end)) { - /* horizontal sync on */ + // horizontal sync on pins |= MC6847_HS; if (vdg->l_count == MC6847_FSYNC_START) { - /* switch field sync on */ + // switch field sync on vdg->fs = true; } } @@ -594,31 +641,31 @@ uint64_t mc6847_tick(mc6847_t* vdg, uint64_t pins) { pins |= MC6847_FS; } - /* rewind horizontal counter? */ + // rewind horizontal counter? if (vdg->h_count >= vdg->h_period) { vdg->h_count -= vdg->h_period; vdg->l_count++; if (vdg->l_count >= MC6847_ALL_LINES) { - /* rewind line counter, field sync off */ + // rewind line counter, field sync off vdg->l_count = 0; vdg->fs = false; } if (vdg->l_count < MC6847_VBLANK_LINES) { - /* inside vblank area, nothing to do */ + // inside vblank area, nothing to do } else if (vdg->l_count < MC6847_DISPLAY_START) { - /* top border */ - int y = vdg->l_count - MC6847_VBLANK_LINES; + // top border + size_t y = (size_t) (vdg->l_count - MC6847_VBLANK_LINES); _mc6847_decode_border(vdg, pins, y); } else if (vdg->l_count < MC6847_DISPLAY_END) { - /* visible area */ - int y = vdg->l_count - MC6847_DISPLAY_START; + // visible area + size_t y = (size_t) (vdg->l_count - MC6847_DISPLAY_START); pins = _mc6847_decode_scanline(vdg, pins, y); } else if (vdg->l_count < MC6847_BOTTOM_BORDER_END) { - /* bottom border */ - int y = vdg->l_count - MC6847_VBLANK_LINES; + // bottom border + size_t y = (size_t) (vdg->l_count - MC6847_VBLANK_LINES); _mc6847_decode_border(vdg, pins, y); } } @@ -626,4 +673,18 @@ uint64_t mc6847_tick(mc6847_t* vdg, uint64_t pins) { return pins; } -# endif /* CHIPS_IMPL */ +void mc6847_snapshot_onsave(mc6847_t* snapshot) { + CHIPS_ASSERT(snapshot); + snapshot->fetch_cb = 0; + snapshot->user_data = 0; + snapshot->fb = 0; +} + +void mc6847_snapshot_onload(mc6847_t* snapshot, mc6847_t* sys) { + CHIPS_ASSERT(snapshot && sys); + snapshot->fetch_cb = sys->fetch_cb; + snapshot->user_data = sys->user_data; + snapshot->fb = sys->fb; +} + +# endif // CHIPS_IMPL diff --git a/chips/mem.h b/chips/mem.h index e3aff258..07a721a7 100644 --- a/chips/mem.h +++ b/chips/mem.h @@ -8,12 +8,12 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macro with your own implementation (default: assert(c)) - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -61,7 +61,7 @@ Each layer is an array of 64 page items (one page item covers 1 KByte of memory). - The CPU sees the highest priority valid page items (where layer 0 is + The CPU sees the highest priority valid page items (where layer 0 is highest priority and layer 3 is lowest priority). Each page item consists of two host system pointers, one for read access, @@ -82,81 +82,6 @@ - **unmapped page**: the read-pointer points to the internal junk-read-page, and the write-pointer to the internal junk-write-page - ## Functions - ~~~C - void mem_init(mem_t* mem); - ~~~ - Initialize a new mem_t instance. - - ~~~C - void mem_map_ram(mem_t* mem, int layer, uint16_t addr, uint32_t size, uint8_t* ptr) - ~~~ - Map a range of host memory to a 16-bit address for RAM access in a - given layer (0..3, 0 being the highest priority layer). Size is in bytes, - must be a multiple of 0x0400 (decimal: 1024), and must be <= 0x10000 (decimal: 65536) - - ~~~C - void mem_map_rom(mem_t* mem, int layer, uint16_t addr, uint32_t size, const uint8_t* ptr) - ~~~ - Map a range of host memory to a 16-bit address for ROM access. - See mem_map_ram() for more details. - - ~~~C - void mem_map_rw(mem_t* mem, int layer, uint16_t addr, uint32_t size, const uint8_t* read_ptr, uint8_t* write_ptr) - ~~~ - Map two host memory ranges to a 16-bit address for RAM-behind-ROM access. - Read accesses will come from _read_ptr_, and write accesses will go - to _write_ptr_. See mem_map_ram() for more details. - - ~~~C - void mem_unmap_layer(mem_t* mem, int layer) - ~~~ - Unmap all memory pages in a layer. - - ~~~C - void mem_unmap_all(mem_t* mem) - ~~~ - Unmap all memory pages in all layers. - - ~~~C - uint8_t mem_rd(mem_t* mem, uint16_t addr) - ~~~ - Read a byte from a 16-bit memory address from the CPU-visible - memory page at that location. If the location is unmapped the read will - come from the internal read-junk-page and 0xFF will be returned. - - ~~~C - void mem_wr(mem_t* mem, uint16_t addr, uint8_t data) - ~~~ - Write a byte to a 16-bit memory address to the CPU-visible memory - page at that location. If the location is unmapped or ROM, the write - will go the internal write-junk-page. - - ~~~C - uint8_t* mem_readptr(mem_t* mem, uint16_t addr) - ~~~ - A helper-function which returns the host-memory location of a 16-bit - address for a read-access. Careful, this will return a pointer into the - internal read-junk-page if the page item is unmapped. - - ~~~C - void mem_write_range(mem_t* mem, uint16_t addr, const uint8_t* src, int num_bytes) - ~~~ - A helper function to copy a range of bytes from host memory to a 16-bit - address range. This will do a series of mem_wr() calls. - - ~~~C - void mem_wr16(mem_t* mem, uint16_t addr, uint16_t data) - ~~~ - A helper function to write a 16-bit value in little-endian format. - This will do 2 calls to mem_wr(). - - ~~~C - uint16_t mem_rd16(mem_t* mem, uint16_t addr) - ~~~ - A helper function to read a 16-bit value in little-endian format. - This will do 2 calls to mem_rd(). - ## zlib/libpng license Copyright (c) 2018 Andre Weissflog @@ -173,61 +98,58 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include +#include #ifdef __cplusplus extern "C" { #endif /* address range (64 KByte) */ -#define MEM_ADDR_RANGE (1<<16) +#define MEM_ADDR_RANGE (1U<<16) #define MEM_ADDR_MASK (MEM_ADDR_RANGE-1) /* page size (1 KByte) */ -#define MEM_PAGE_SHIFT (10) -#define MEM_PAGE_SIZE (1<unmapped_page, 0xFF, sizeof(m->unmapped_page)); + memset(_mem_unmapped_page, 0xFF, sizeof(_mem_unmapped_page)); mem_unmap_all(m); } /* this sets the CPU-visible mapping of a page in the page-table */ -static void _mem_update_page_table(mem_t* m, int page_index) { +static void _mem_update_page_table(mem_t* m, size_t page_index) { /* find highest priority layer which maps this memory page */ - int layer_index; + size_t layer_index; for (layer_index = 0; layer_index < MEM_NUM_LAYERS; layer_index++) { if (m->layers[layer_index][page_index].read_ptr) { /* found highest priority layer with valid mapping */ break; } } - if (layer_index != MEM_NUM_LAYERS) { + if (layer_index != MEM_NUM_LAYERS) { /* found a valid mapping */ - m->page_table[page_index] = m->layers[layer_index][page_index]; + + /* + FIXME FIXME FIXME + + The following lines triggers a code generation problem in Xcode 14.2 + (also 14.0 and 14.1) resulting in a nullptr access because this line + doesn't seem to be executed resulting in all read_ptr/write_ptr to be + zero. Happens when called from within _kc85_update_memory_map() with + at least -O2. After 2 days of investigation it's still unclear whether + this is a bug in Clang or actual UB. A few facts from the investigation: + + - compiling with -fwrapv fixes the problem (yet no case of + signed integer overflow could be found going up the callstack, + UBSAN is also clean) - frwrapv seems to disable any loop unrolling + optimizations + - replacing the struct assignment below with memcpy fixes the problem + - __attribute__((noinline)) fixes the problem + - compiling with UBSAN fixes the problem(!!!) (but neither UBSAN nor + ASAN trigger anything) + - compiling with more recent clang versions (e.g. to WASM with Emscripten + or 'zig cc' also fixes the problem (but in the case of 'zig cc' it might + also be because zig uses different default compilation options? + + TODO: check again once Xcode 15 is released (assuming this finally updated + clang) + */ + // m->page_table[page_index] = m->layers[layer_index][page_index]; + + m->page_table[page_index].read_ptr = m->layers[layer_index][page_index].read_ptr; + m->page_table[page_index].write_ptr = m->layers[layer_index][page_index].write_ptr; } else { /* no mapping exists for this page, set to special 'unmapped page' */ - m->page_table[page_index].read_ptr = m->unmapped_page; - m->page_table[page_index].write_ptr = m->junk_page; + m->page_table[page_index].read_ptr = _mem_unmapped_page; + m->page_table[page_index].write_ptr = _mem_junk_page; } } -static void _mem_map(mem_t* m, int layer, uint16_t addr, uint32_t size, const uint8_t* read_ptr, uint8_t* write_ptr) { +static void _mem_map(mem_t* m, size_t layer, uint16_t addr, uint32_t size, const uint8_t* read_ptr, uint8_t* write_ptr) { CHIPS_ASSERT(m); - CHIPS_ASSERT((layer >= 0) && (layer < MEM_NUM_LAYERS)); + CHIPS_ASSERT(layer < MEM_NUM_LAYERS); CHIPS_ASSERT((addr & MEM_PAGE_MASK) == 0); CHIPS_ASSERT((size & MEM_PAGE_MASK) == 0); CHIPS_ASSERT(size <= MEM_ADDR_RANGE); - const int num = size>>MEM_PAGE_SHIFT; + const size_t num = size>>MEM_PAGE_SHIFT; CHIPS_ASSERT(num <= MEM_NUM_PAGES); - for (int i = 0; i < num; i++) { + for (size_t i = 0; i < num; i++) { const uint16_t offset = i * MEM_PAGE_SIZE; - /* the page_index will wrap-around */ + // the page_index will wrap-around const uint16_t page_index = ((addr+offset) & MEM_ADDR_MASK) >> MEM_PAGE_SHIFT; CHIPS_ASSERT(page_index <= MEM_NUM_PAGES); mem_page_t* page = &m->layers[layer][page_index]; - page->read_ptr = read_ptr + offset; + page->read_ptr = (uint8_t*)read_ptr + offset; if (0 != write_ptr) { page->write_ptr = write_ptr + offset; } else { - page->write_ptr = m->junk_page; + page->write_ptr = _mem_junk_page; } _mem_update_page_table(m, page_index); } } -void mem_map_ram(mem_t* m, int layer, uint16_t addr, uint32_t size, uint8_t* ptr) { +void mem_map_ram(mem_t* m, size_t layer, uint16_t addr, uint32_t size, uint8_t* ptr) { CHIPS_ASSERT(ptr); _mem_map(m, layer, addr, size, ptr, ptr); } -void mem_map_rom(mem_t* m, int layer, uint16_t addr, uint32_t size, const uint8_t* ptr) { +void mem_map_rom(mem_t* m, size_t layer, uint16_t addr, uint32_t size, const uint8_t* ptr) { CHIPS_ASSERT(ptr); _mem_map(m, layer, addr, size, ptr, 0); } -void mem_map_rw(mem_t* m, int layer, uint16_t addr, uint32_t size, const uint8_t* read_ptr, uint8_t* write_ptr) { +void mem_map_rw(mem_t* m, size_t layer, uint16_t addr, uint32_t size, const uint8_t* read_ptr, uint8_t* write_ptr) { CHIPS_ASSERT(read_ptr && write_ptr); _mem_map(m, layer, addr, size, read_ptr, write_ptr); } -void mem_unmap_layer(mem_t* m, int layer) { +void mem_unmap_layer(mem_t* m, size_t layer) { CHIPS_ASSERT(m); - CHIPS_ASSERT((layer >= 0) && (layer < MEM_NUM_LAYERS)); - for (int page_index = 0; page_index < MEM_NUM_PAGES; page_index++) { + CHIPS_ASSERT(layer < MEM_NUM_LAYERS); + for (size_t page_index = 0; page_index < MEM_NUM_PAGES; page_index++) { mem_page_t* page = &m->layers[layer][page_index]; - page->read_ptr = 0; + page->read_ptr = 0; page->write_ptr = 0; _mem_update_page_table(m, page_index); } } void mem_unmap_all(mem_t* m) { - for (int layer_index = 0; layer_index < MEM_NUM_LAYERS; layer_index++) { - for (int page_index = 0; page_index < MEM_NUM_PAGES; page_index++) { + for (size_t layer_index = 0; layer_index < MEM_NUM_LAYERS; layer_index++) { + for (size_t page_index = 0; page_index < MEM_NUM_PAGES; page_index++) { mem_page_t* page = &m->layers[layer_index][page_index]; - page->read_ptr = 0; + page->read_ptr = 0; page->write_ptr = 0; } } - for (int page_index = 0; page_index < MEM_NUM_PAGES; page_index++) { + for (size_t page_index = 0; page_index < MEM_NUM_PAGES; page_index++) { _mem_update_page_table(m, page_index); } } @@ -361,16 +322,16 @@ void mem_unmap_all(mem_t* m) { uint8_t* mem_readptr(mem_t* m, uint16_t addr) { CHIPS_ASSERT(m); return (uint8_t*) &(m->page_table[addr>>MEM_PAGE_SHIFT].read_ptr[addr&MEM_PAGE_MASK]); -} +} -void mem_write_range(mem_t* m, uint16_t addr, const uint8_t* src, int num_bytes) { - for (int i = 0; i < num_bytes; i++) { +void mem_write_range(mem_t* m, uint16_t addr, const uint8_t* src, uint32_t num_bytes) { + for (size_t i = 0; i < num_bytes; i++) { mem_wr(m, addr++, src[i]); } } -uint8_t mem_layer_rd(mem_t* mem, int layer, uint16_t addr) { - CHIPS_ASSERT((layer >= 0) && (layer < MEM_NUM_LAYERS)); +uint8_t mem_layer_rd(mem_t* mem, size_t layer, uint16_t addr) { + CHIPS_ASSERT(layer < MEM_NUM_LAYERS); if (mem->layers[layer][addr>>MEM_PAGE_SHIFT].read_ptr) { return mem->layers[layer][addr>>MEM_PAGE_SHIFT].read_ptr[addr&MEM_PAGE_MASK]; } @@ -379,11 +340,78 @@ uint8_t mem_layer_rd(mem_t* mem, int layer, uint16_t addr) { } } -void mem_layer_wr(mem_t* mem, int layer, uint16_t addr, uint8_t data) { - CHIPS_ASSERT((layer >= 0) && (layer < MEM_NUM_LAYERS)); +void mem_layer_wr(mem_t* mem, size_t layer, uint16_t addr, uint8_t data) { + CHIPS_ASSERT(layer < MEM_NUM_LAYERS); if (mem->layers[layer][addr>>MEM_PAGE_SHIFT].write_ptr) { mem->layers[layer][addr>>MEM_PAGE_SHIFT].write_ptr[addr&MEM_PAGE_MASK] = data; } } +#define MEM_SPECIAL_OFFSET_NULLPTR (-1) +#define MEM_SPECIAL_OFFSET_UNMAPPED_PAGE (-2) +#define MEM_SPECIAL_OFFSET_JUNK_PAGE (-3) + +static void mem_ptr_to_offset(uint8_t** ptr_ptr, uint8_t* base) { + uint8_t* ptr = *ptr_ptr; + if (ptr == 0) { + *ptr_ptr = (uint8_t*)(intptr_t)MEM_SPECIAL_OFFSET_NULLPTR; + } + else if (ptr == _mem_unmapped_page) { + *ptr_ptr = (uint8_t*)(intptr_t)MEM_SPECIAL_OFFSET_UNMAPPED_PAGE; + } + else if (ptr == _mem_junk_page) { + *ptr_ptr = (uint8_t*)(intptr_t)MEM_SPECIAL_OFFSET_JUNK_PAGE; + } + else { + CHIPS_ASSERT(base <= *ptr_ptr); + *ptr_ptr = (uint8_t*) (*ptr_ptr - base); + } +} + +static void mem_offset_to_ptr(uint8_t** ptr_ptr, uint8_t* base) { + intptr_t offset = (intptr_t)*ptr_ptr; + switch (offset) { + case MEM_SPECIAL_OFFSET_NULLPTR: + *ptr_ptr = 0; + break; + case MEM_SPECIAL_OFFSET_UNMAPPED_PAGE: + *ptr_ptr = _mem_unmapped_page; + break; + case MEM_SPECIAL_OFFSET_JUNK_PAGE: + *ptr_ptr = _mem_junk_page; + break; + default: + *ptr_ptr = (base + offset); + break; + } +} + +void mem_snapshot_onsave(mem_t* snapshot, void* base) { + uint8_t* base8 = (uint8_t*)base; + for (size_t page = 0; page < MEM_NUM_PAGES; page++) { + mem_ptr_to_offset(&snapshot->page_table[page].read_ptr, base8); + mem_ptr_to_offset(&snapshot->page_table[page].write_ptr, base8); + } + for (size_t layer = 0; layer < MEM_NUM_LAYERS; layer++) { + for (size_t page = 0; page < MEM_NUM_PAGES; page++) { + mem_ptr_to_offset(&snapshot->layers[layer][page].read_ptr, base8); + mem_ptr_to_offset(&snapshot->layers[layer][page].write_ptr, base8); + } + } +} + +void mem_snapshot_onload(mem_t* snapshot, void* base) { + uint8_t* base8 = (uint8_t*)base; + for (size_t page = 0; page < MEM_NUM_PAGES; page++) { + mem_offset_to_ptr(&snapshot->page_table[page].read_ptr, base8); + mem_offset_to_ptr(&snapshot->page_table[page].write_ptr, base8); + } + for (size_t layer = 0; layer < MEM_NUM_LAYERS; layer++) { + for (size_t page = 0; page < MEM_NUM_PAGES; page++) { + mem_offset_to_ptr(&snapshot->layers[layer][page].read_ptr, base8); + mem_offset_to_ptr(&snapshot->layers[layer][page].write_ptr, base8); + } + } +} + #endif /* CHIPS_IMPL */ diff --git a/chips/upd765.h b/chips/upd765.h index 5c1c63fd..778154d8 100644 --- a/chips/upd765.h +++ b/chips/upd765.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -46,7 +46,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -55,25 +55,39 @@ extern "C" { #endif -/* the A0 pin is usually connected to the address bus A0 pin */ -#define UPD765_A0 (1ULL<<0) /* in: data/status register select */ - -/* data bus pins (in/out) */ -#define UPD765_D0 (1ULL<<16) -#define UPD765_D1 (1ULL<<17) -#define UPD765_D2 (1ULL<<18) -#define UPD765_D3 (1ULL<<19) -#define UPD765_D4 (1ULL<<20) -#define UPD765_D5 (1ULL<<21) -#define UPD765_D6 (1ULL<<22) -#define UPD765_D7 (1ULL<<23) - -/* control pins shared with CPU */ -#define UPD765_RD (1ULL<<27) /* in: read data from controller */ -#define UPD765_WR (1ULL<<28) /* in: write data to controller */ - -/* control pins */ -#define UPD765_CS (1ULL<<40) /* in: chip select */ +// the A0 pin is usually connected to the address bus A0 pin +#define UPD765_PIN_A0 (0) /* in: data/status register select */ + +// data bus pins (in/out) +#define UPD765_PIN_D0 (16) +#define UPD765_PIN_D1 (17) +#define UPD765_PIN_D2 (18) +#define UPD765_PIN_D3 (19) +#define UPD765_PIN_D4 (20) +#define UPD765_PIN_D5 (21) +#define UPD765_PIN_D6 (22) +#define UPD765_PIN_D7 (23) + +// control pins shared with CPU +#define UPD765_PIN_RD (27) // in: read data from controller +#define UPD765_PIN_WR (28) // in: write data to controller + +// control pins +#define UPD765_PIN_CS (40) // in: chip select + +// pin bit masks +#define UPD765_A0 (1ULL<>16)) @@ -243,6 +257,10 @@ void upd765_init(upd765_t* upd, const upd765_desc_t* desc); void upd765_reset(upd765_t* upd); /* perform an IO request on the upd765 */ uint64_t upd765_iorq(upd765_t* upd, uint64_t pins); +// prepare upd765_t snapshot for saving +void upd765_snapshot_onsave(upd765_t* snapshot); +// fixup upd765_t snapshot after loading +void upd765_snapshot_onload(upd765_t* snapshot, upd765_t* sys); #ifdef __cplusplus } /* extern "C" */ @@ -673,4 +691,24 @@ uint64_t upd765_iorq(upd765_t* upd, uint64_t pins) { } return pins; } + +void upd765_snapshot_onsave(upd765_t* snapshot) { + CHIPS_ASSERT(snapshot); + snapshot->seektrack_cb = 0; + snapshot->seeksector_cb = 0; + snapshot->read_cb = 0; + snapshot->trackinfo_cb = 0; + snapshot->driveinfo_cb = 0; + snapshot->user_data = 0; +} + +void upd765_snapshot_onload(upd765_t* snapshot, upd765_t* sys) { + CHIPS_ASSERT(snapshot && sys); + snapshot->seektrack_cb = sys->seektrack_cb; + snapshot->seeksector_cb = sys->seeksector_cb; + snapshot->read_cb = sys->read_cb; + snapshot->trackinfo_cb = sys->trackinfo_cb; + snapshot->driveinfo_cb = sys->driveinfo_cb; + snapshot->user_data = sys->user_data; +} #endif /* CHIPS_IMPL */ diff --git a/chips/z80.h b/chips/z80.h index 309b3f37..e9b76b4a 100644 --- a/chips/z80.h +++ b/chips/z80.h @@ -8,7 +8,7 @@ ~~~~C #define CHIPS_IMPL ~~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide @@ -30,7 +30,7 @@ * NMI --->| |<--> ... * * RFSH <---| |<--> D7 * * +-----------+ * - *********************************** + *********************************** ## Functions @@ -43,13 +43,13 @@ ~~~C uint64_t z80_reset(z80_t* cpu) ~~~ - Resets a z80_t instance, returns pin mask to start execution at + Resets a z80_t instance, returns pin mask to start execution at address 0. ~~~C uint64_t z80_tick(z80_t* cpu, uint64_t pins) ~~~ - Step the z80_t instance for one clock cycle. + Step the z80_t instance for one clock cycle. ~~~C uint64_t z80_prefetch(z80_t* cpu, uint16_t new_pc) @@ -97,11 +97,11 @@ } } ~~~ - The CPU will now run through the whole address space executing NOPs (because the memory is + The CPU will now run through the whole address space executing NOPs (because the memory is filled with 0s instead of a valid program). If there would be a valid Z80 program at memory address 0, this would be executed instead. - IO requests are handled the same as memory requests, but instead of the MREQ pin, the + IO requests are handled the same as memory requests, but instead of the MREQ pin, the IORQ pin must be checked: ~~~C uint8_t mem[(1<<16)] = {0}; @@ -219,7 +219,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. */ #include #include @@ -229,49 +229,88 @@ extern "C" { #endif // address pins -#define Z80_A0 (1ULL<<0) -#define Z80_A1 (1ULL<<1) -#define Z80_A2 (1ULL<<2) -#define Z80_A3 (1ULL<<3) -#define Z80_A4 (1ULL<<4) -#define Z80_A5 (1ULL<<5) -#define Z80_A6 (1ULL<<6) -#define Z80_A7 (1ULL<<7) -#define Z80_A8 (1ULL<<8) -#define Z80_A9 (1ULL<<9) -#define Z80_A10 (1ULL<<10) -#define Z80_A11 (1ULL<<11) -#define Z80_A12 (1ULL<<12) -#define Z80_A13 (1ULL<<13) -#define Z80_A14 (1ULL<<14) -#define Z80_A15 (1ULL<<15) +#define Z80_PIN_A0 (0) +#define Z80_PIN_A1 (1) +#define Z80_PIN_A2 (2) +#define Z80_PIN_A3 (3) +#define Z80_PIN_A4 (4) +#define Z80_PIN_A5 (5) +#define Z80_PIN_A6 (6) +#define Z80_PIN_A7 (7) +#define Z80_PIN_A8 (8) +#define Z80_PIN_A9 (9) +#define Z80_PIN_A10 (10) +#define Z80_PIN_A11 (11) +#define Z80_PIN_A12 (12) +#define Z80_PIN_A13 (13) +#define Z80_PIN_A14 (14) +#define Z80_PIN_A15 (15) // data pins -#define Z80_D0 (1ULL<<16) -#define Z80_D1 (1ULL<<17) -#define Z80_D2 (1ULL<<18) -#define Z80_D3 (1ULL<<19) -#define Z80_D4 (1ULL<<20) -#define Z80_D5 (1ULL<<21) -#define Z80_D6 (1ULL<<22) -#define Z80_D7 (1ULL<<23) +#define Z80_PIN_D0 (16) +#define Z80_PIN_D1 (17) +#define Z80_PIN_D2 (18) +#define Z80_PIN_D3 (19) +#define Z80_PIN_D4 (20) +#define Z80_PIN_D5 (21) +#define Z80_PIN_D6 (22) +#define Z80_PIN_D7 (23) // control pins -#define Z80_M1 (1ULL<<24) // machine cycle 1 -#define Z80_MREQ (1ULL<<25) // memory request -#define Z80_IORQ (1ULL<<26) // input/output request -#define Z80_RD (1ULL<<27) // read -#define Z80_WR (1ULL<<28) // write -#define Z80_HALT (1ULL<<29) // halt state -#define Z80_INT (1ULL<<30) // interrupt request -#define Z80_RES (1ULL<<31) // reset requested -#define Z80_NMI (1ULL<<32) // non-maskable interrupt -#define Z80_WAIT (1ULL<<33) // wait requested -#define Z80_RFSH (1ULL<<34) // refresh +#define Z80_PIN_M1 (24) // machine cycle 1 +#define Z80_PIN_MREQ (25) // memory request +#define Z80_PIN_IORQ (26) // input/output request +#define Z80_PIN_RD (27) // read +#define Z80_PIN_WR (28) // write +#define Z80_PIN_HALT (29) // halt state +#define Z80_PIN_INT (30) // interrupt request +#define Z80_PIN_RES (31) // reset requested +#define Z80_PIN_NMI (32) // non-maskable interrupt +#define Z80_PIN_WAIT (33) // wait requested +#define Z80_PIN_RFSH (34) // refresh // virtual pins (for interrupt daisy chain protocol) -#define Z80_IEIO (1ULL<<37) // unified daisy chain 'Interrupt Enable In+Out' -#define Z80_RETI (1ULL<<38) // cpu has decoded a RETI instruction +#define Z80_PIN_IEIO (37) // unified daisy chain 'Interrupt Enable In+Out' +#define Z80_PIN_RETI (38) // cpu has decoded a RETI instruction + +// pin bit masks +#define Z80_A0 (1ULL<> 8) & Z80_CF) | ((acc ^ val ^ res) & Z80_HF) | - ((((val ^ acc) & (res ^ acc)) >> 5) & Z80_VF); + ((((val ^ acc) & (res ^ acc)) >> 5) & Z80_VF); } static inline uint8_t _z80_sziff2_flags(z80_t* cpu, uint8_t val) { @@ -601,7 +640,7 @@ static inline void _z80_add16(z80_t* cpu, uint16_t val) { const uint32_t res = acc + val; cpu->hlx[cpu->hlx_idx].hl = res; cpu->f = (cpu->f & (Z80_SF|Z80_ZF|Z80_VF)) | - (((acc ^ res ^ val)>>8)&Z80_HF) | + (((acc ^ res ^ val)>>8)&Z80_HF) | ((res >> 16) & Z80_CF) | ((res >> 8) & (Z80_YF|Z80_XF)); } @@ -625,7 +664,7 @@ static inline void _z80_sbc16(z80_t* cpu, uint16_t val) { cpu->wz = acc + 1; const uint32_t res = acc - val - (cpu->f & Z80_CF); cpu->hl = res; - cpu->f = (Z80_NF | (((val ^ acc) & (acc ^ res) & 0x8000) >> 13)) | + cpu->f = (Z80_NF | (((val ^ acc) & (acc ^ res) & 0x8000) >> 13)) | (((acc ^ res ^ val) >> 8) & Z80_HF) | ((res >> 16) & Z80_CF) | ((res >> 8) & (Z80_SF|Z80_YF|Z80_XF)) | @@ -1703,7 +1742,7 @@ static inline uint64_t _z80_fetch_cb(z80_t* cpu, uint64_t pins) { if (cpu->hlx_idx > 0) { // this is a DD+CB / FD+CB instruction, continue // execution on the special DDCB/FDCB decoder block which - // loads the d-offset first and then the opcode in a + // loads the d-offset first and then the opcode in a // regular memory read machine cycle cpu->step = _z80_special_optable[_Z80_OPSTATE_SLOT_DDFDCB]; } diff --git a/chips/z80ctc.h b/chips/z80ctc.h index a1982c06..ca54172d 100644 --- a/chips/z80ctc.h +++ b/chips/z80ctc.h @@ -11,7 +11,7 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation @@ -64,10 +64,10 @@ ~~~ Perform a tick on the CTC, this function must be called for every CPU tick. Depending on the input pin mask and the current - CTC chip state, the CTC will perform IO requests from the CPU, check + CTC chip state, the CTC will perform IO requests from the CPU, check for external trigger events, perform the interrupt daisychain protocol, and return a potentially modified pin mask. - + The CTC reads the following pins when an IO request should be performed: - **Z80CTC_CE|Z80CTC_IORQ**: performs an IO request @@ -87,7 +87,7 @@ handling in "downstream chips" - **Z80CTC_RETI**: virtual pin which is set when the CPU has decoded a RETI/RETN instruction - + The following output pins are potentially modified: - **Z80CTC_D0..Z80CTC_D7**: the resulting data byte of IO read requests @@ -112,7 +112,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. */ #include #include @@ -122,27 +122,45 @@ extern "C" { #endif // control pins directly shared with CPU -#define Z80CTC_M1 (1ULL<<24) // CPU Machine Cycle One (same as Z80_M1) */ -#define Z80CTC_IORQ (1ULL<<26) // CPU IO Request (same as Z80_IORQ) */ -#define Z80CTC_RD (1ULL<<27) // CPU Read Cycle Status (same as Z80_RD) */ -#define Z80CTC_INT (1ULL<<30) // Interrupt Request (same as Z80_INT) */ -#define Z80CTC_RESET (1ULL<<31) // put CTC into reset state (same as Z80_RESET) */ +#define Z80CTC_BIT_M1 (24) // CPU Machine Cycle One (same as Z80_M1) */ +#define Z80CTC_BIT_IORQ (26) // CPU IO Request (same as Z80_IORQ) */ +#define Z80CTC_BIT_RD (27) // CPU Read Cycle Status (same as Z80_RD) */ +#define Z80CTC_BIT_INT (30) // Interrupt Request (same as Z80_INT) */ +#define Z80CTC_BIT_RESET (31) // put CTC into reset state (same as Z80_RESET) */ // Z80 interrupt daisy chain shared pins -#define Z80CTC_IEIO (1ULL<<37) // combined Interrupt Enable In/Out (same as Z80_IEIO) */ -#define Z80CTC_RETI (1ULL<<38) // CPU has decoded a RETI instruction (same as Z80_RETI) */ +#define Z80CTC_BIT_IEIO (37) // combined Interrupt Enable In/Out (same as Z80_IEIO) */ +#define Z80CTC_BIT_RETI (38) // CPU has decoded a RETI instruction (same as Z80_RETI) */ // CTC specific pins starting at bit 40 -#define Z80CTC_CE (1ULL<<40) // Chip Enable -#define Z80CTC_CS0 (1ULL<<41) // Channel Select Bit 0 -#define Z80CTC_CS1 (1ULL<<42) // Channel Select Bit 1 -#define Z80CTC_CLKTRG0 (1ULL<<43) // Clock/Timer Trigger 0 -#define Z80CTC_CLKTRG1 (1ULL<<44) // Clock/Timer Trigger 1 -#define Z80CTC_CLKTRG2 (1ULL<<45) // Clock/Timer Trigger 2 -#define Z80CTC_CLKTRG3 (1ULL<<46) // Clock/Timer Trigger 3 -#define Z80CTC_ZCTO0 (1ULL<<47) // Zero Count/Timeout 0 -#define Z80CTC_ZCTO1 (1ULL<<48) // Zero Count/Timeout 1 -#define Z80CTC_ZCTO2 (1ULL<<49) // Zero Count/Timeout 2 +#define Z80CTC_BIT_CE (40) // Chip Enable +#define Z80CTC_BIT_CS0 (41) // Channel Select Bit 0 +#define Z80CTC_BIT_CS1 (42) // Channel Select Bit 1 +#define Z80CTC_BIT_CLKTRG0 (43) // Clock/Timer Trigger 0 +#define Z80CTC_BIT_CLKTRG1 (44) // Clock/Timer Trigger 1 +#define Z80CTC_BIT_CLKTRG2 (45) // Clock/Timer Trigger 2 +#define Z80CTC_BIT_CLKTRG3 (46) // Clock/Timer Trigger 3 +#define Z80CTC_BIT_ZCTO0 (47) // Zero Count/Timeout 0 +#define Z80CTC_BIT_ZCTO1 (48) // Zero Count/Timeout 1 +#define Z80CTC_BIT_ZCTO2 (49) // Zero Count/Timeout 2 + +#define Z80CTC_M1 (1ULL< #include @@ -116,8 +116,8 @@ extern "C" { #endif /* - Pin definitions. - + Pin definitions. + All pin locations from 0 to 39 are shared with the CPU. Chip-type specific pins start at position 40. This enables efficient bus-sharing with the CPU and other Z80-family chips. @@ -132,42 +132,73 @@ extern "C" { */ /* control pins directly shared with CPU */ -#define Z80PIO_M1 (1ULL<<24) /* CPU Machine Cycle One, same as Z80_M1 */ -#define Z80PIO_IORQ (1ULL<<26) /* IO Request from CPU, same as Z80_IORQ */ -#define Z80PIO_RD (1ULL<<27) /* Read Cycle Status from CPU, same as Z80_RD */ -#define Z80PIO_INT (1ULL<<30) /* Interrupt Request, same as Z80_INT */ +#define Z80PIO_PIN_M1 (24) /* CPU Machine Cycle One, same as Z80_M1 */ +#define Z80PIO_PIN_IORQ (26) /* IO Request from CPU, same as Z80_IORQ */ +#define Z80PIO_PIN_RD (27) /* Read Cycle Status from CPU, same as Z80_RD */ +#define Z80PIO_PIN_INT (30) /* Interrupt Request, same as Z80_INT */ /* Z80 interrupt daisy chain shared pins */ -#define Z80PIO_IEIO (1ULL<<37) /* combined Interrupt Enable In/Out (same as Z80_IEIO) */ -#define Z80PIO_RETI (1ULL<<38) /* CPU has decoded a RETI instruction (same as Z80_RETI) */ +#define Z80PIO_PIN_IEIO (37) /* combined Interrupt Enable In/Out (same as Z80_IEIO) */ +#define Z80PIO_PIN_RETI (38) /* CPU has decoded a RETI instruction (same as Z80_RETI) */ /* PIO specific pins start at bit 40 */ -#define Z80PIO_CE (1ULL<<40) /* Chip Enable */ -#define Z80PIO_BASEL (1ULL<<41) /* Port A/B Select (inactive: A, active: B) */ -#define Z80PIO_CDSEL (1ULL<<42) /* Control/Data Select (inactive: data, active: control) */ -#define Z80PIO_ARDY (1ULL<<43) /* Port A Ready */ -#define Z80PIO_BRDY (1ULL<<44) /* Port B Ready */ -#define Z80PIO_ASTB (1ULL<<45) /* Port A Strobe */ -#define Z80PIO_BSTB (1ULL<<46) /* Port B Strobe */ +#define Z80PIO_PIN_CE (40) /* Chip Enable */ +#define Z80PIO_PIN_BASEL (41) /* Port A/B Select (inactive: A, active: B) */ +#define Z80PIO_PIN_CDSEL (42) /* Control/Data Select (inactive: data, active: control) */ +#define Z80PIO_PIN_ARDY (43) /* Port A Ready */ +#define Z80PIO_PIN_BRDY (44) /* Port B Ready */ +#define Z80PIO_PIN_ASTB (45) /* Port A Strobe */ +#define Z80PIO_PIN_BSTB (46) /* Port B Strobe */ /* A/B 8-bit port pins */ -#define Z80PIO_PA0 (1ULL<<48) -#define Z80PIO_PA1 (1ULL<<49) -#define Z80PIO_PA2 (1ULL<<50) -#define Z80PIO_PA3 (1ULL<<51) -#define Z80PIO_PA4 (1ULL<<52) -#define Z80PIO_PA5 (1ULL<<53) -#define Z80PIO_PA6 (1ULL<<54) -#define Z80PIO_PA7 (1ULL<<55) - -#define Z80PIO_PB0 (1ULL<<56) -#define Z80PIO_PB1 (1ULL<<57) -#define Z80PIO_PB2 (1ULL<<58) -#define Z80PIO_PB3 (1ULL<<59) -#define Z80PIO_PB4 (1ULL<<60) -#define Z80PIO_PB5 (1ULL<<61) -#define Z80PIO_PB6 (1ULL<<62) -#define Z80PIO_PB7 (1ULL<<63) +#define Z80PIO_PIN_PA0 (48) +#define Z80PIO_PIN_PA1 (49) +#define Z80PIO_PIN_PA2 (50) +#define Z80PIO_PIN_PA3 (51) +#define Z80PIO_PIN_PA4 (52) +#define Z80PIO_PIN_PA5 (53) +#define Z80PIO_PIN_PA6 (54) +#define Z80PIO_PIN_PA7 (55) + +#define Z80PIO_PIN_PB0 (56) +#define Z80PIO_PIN_PB1 (57) +#define Z80PIO_PIN_PB2 (58) +#define Z80PIO_PIN_PB3 (59) +#define Z80PIO_PIN_PB4 (60) +#define Z80PIO_PIN_PB5 (61) +#define Z80PIO_PIN_PB6 (62) +#define Z80PIO_PIN_PB7 (63) + +// pin bit masks +#define Z80PIO_M1 (1ULL<io_select = data; p->int_enabled = (p->int_control & Z80PIO_INTCTRL_EI) ? true:false; p->expect_io_select = false; - } + } else if (p->expect_int_mask) { /* followup interrupt mask */ p->int_mask = data; diff --git a/codegen/README.txt b/codegen/README.txt index 584793be..e677b16e 100644 --- a/codegen/README.txt +++ b/codegen/README.txt @@ -1,10 +1,14 @@ This directory contains code-generation python scripts which will generate the z80.h and m6502.h headers. -Run: +First install pyyaml: -> python z80_gen.py -> python m6502_gen.py +pip3 install pyyaml + +Then run: + +> python3 z80_gen.py +> python3 m6502_gen.py To generate the respective decoder source files in the '../chips' directory. diff --git a/codegen/m6502.template.h b/codegen/m6502.template.h index dd46ff57..aaee7ad2 100644 --- a/codegen/m6502.template.h +++ b/codegen/m6502.template.h @@ -5,7 +5,7 @@ MOS Technology 6502 / 6510 CPU emulator. Project repo: https://github.com/floooh/chips/ - + NOTE: this file is code-generated from m6502.template.h and m6502_gen.py in the 'codegen' directory. @@ -13,11 +13,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - ~~~C + ~~~C CHIPS_ASSERT(c) ~~~ @@ -50,7 +50,7 @@ of full instructions. To initialize the emulator, fill out a m6502_desc_t structure with - initialization parameters and then call m6502_init(). + initialization parameters and then call m6502_init(). ~~~C typedef struct { @@ -115,7 +115,7 @@ mask and continue calling the tick function. The interrupt sequence will be initiated at the end of the current or next instruction (depending on the exact cycle the interrupt pin has been set). - + Unlike the M6502_RES pin, you are also responsible for clearing the interrupt pins (typically, the interrupt lines are cleared by the chip which requested the interrupt once the CPU reads a chip's interrupt @@ -198,7 +198,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -207,50 +207,89 @@ extern "C" { #endif -/* address bus pins */ -#define M6502_A0 (1ULL<<0) -#define M6502_A1 (1ULL<<1) -#define M6502_A2 (1ULL<<2) -#define M6502_A3 (1ULL<<3) -#define M6502_A4 (1ULL<<4) -#define M6502_A5 (1ULL<<5) -#define M6502_A6 (1ULL<<6) -#define M6502_A7 (1ULL<<7) -#define M6502_A8 (1ULL<<8) -#define M6502_A9 (1ULL<<9) -#define M6502_A10 (1ULL<<10) -#define M6502_A11 (1ULL<<11) -#define M6502_A12 (1ULL<<12) -#define M6502_A13 (1ULL<<13) -#define M6502_A14 (1ULL<<14) -#define M6502_A15 (1ULL<<15) - -/* data bus pins */ -#define M6502_D0 (1ULL<<16) -#define M6502_D1 (1ULL<<17) -#define M6502_D2 (1ULL<<18) -#define M6502_D3 (1ULL<<19) -#define M6502_D4 (1ULL<<20) -#define M6502_D5 (1ULL<<21) -#define M6502_D6 (1ULL<<22) -#define M6502_D7 (1ULL<<23) - -/* control pins */ -#define M6502_RW (1ULL<<24) /* out: memory read or write access */ -#define M6502_SYNC (1ULL<<25) /* out: start of a new instruction */ -#define M6502_IRQ (1ULL<<26) /* in: maskable interrupt requested */ -#define M6502_NMI (1ULL<<27) /* in: non-maskable interrupt requested */ -#define M6502_RDY (1ULL<<28) /* in: freeze execution at next read cycle */ -#define M6510_AEC (1ULL<<29) /* in, m6510 only, put bus lines into tristate mode, not implemented */ -#define M6502_RES (1ULL<<30) /* request RESET */ - -/* m6510 IO port pins */ -#define M6510_P0 (1ULL<<32) -#define M6510_P1 (1ULL<<33) -#define M6510_P2 (1ULL<<34) -#define M6510_P3 (1ULL<<35) -#define M6510_P4 (1ULL<<36) -#define M6510_P5 (1ULL<<37) +// address bus pins +#define M6502_PIN_A0 (0) +#define M6502_PIN_A1 (1) +#define M6502_PIN_A2 (2) +#define M6502_PIN_A3 (3) +#define M6502_PIN_A4 (4) +#define M6502_PIN_A5 (5) +#define M6502_PIN_A6 (6) +#define M6502_PIN_A7 (7) +#define M6502_PIN_A8 (8) +#define M6502_PIN_A9 (9) +#define M6502_PIN_A10 (10) +#define M6502_PIN_A11 (11) +#define M6502_PIN_A12 (12) +#define M6502_PIN_A13 (13) +#define M6502_PIN_A14 (14) +#define M6502_PIN_A15 (15) + +// data bus pins +#define M6502_PIN_D0 (16) +#define M6502_PIN_D1 (17) +#define M6502_PIN_D2 (18) +#define M6502_PIN_D3 (19) +#define M6502_PIN_D4 (20) +#define M6502_PIN_D5 (21) +#define M6502_PIN_D6 (22) +#define M6502_PIN_D7 (23) + +// control pins +#define M6502_PIN_RW (24) // out: memory read or write access +#define M6502_PIN_SYNC (25) // out: start of a new instruction +#define M6502_PIN_IRQ (26) // in: maskable interrupt requested +#define M6502_PIN_NMI (27) // in: non-maskable interrupt requested +#define M6502_PIN_RDY (28) // in: freeze execution at next read cycle +#define M6510_PIN_AEC (29) // in, m6510 only, put bus lines into tristate mode, not implemented +#define M6502_PIN_RES (30) // request RESET + +// m6510 IO port pins +#define M6510_PIN_P0 (32) +#define M6510_PIN_P1 (33) +#define M6510_PIN_P2 (34) +#define M6510_PIN_P3 (35) +#define M6510_PIN_P4 (36) +#define M6510_PIN_P5 (37) + +// pin bit masks +#define M6502_A0 (1ULL<P |= M6502_CF; } cpu->A = sum & 0xFF; - } + } } static inline void _m6502_sbc(m6502_t* cpu, uint8_t val) { @@ -555,7 +598,7 @@ static inline void _m6502_arr(m6502_t* cpu) { } } -/* undocumented SBX instruction: +/* undocumented SBX instruction: AND X register with accumulator and store result in X register, then subtract byte from X register (without borrow) where the subtract works like a CMP instruction @@ -620,6 +663,20 @@ uint64_t m6510_iorq(m6502_t* c, uint64_t pins) { return pins; } +void m6502_snapshot_onsave(m6502_t* snapshot) { + CHIPS_ASSERT(snapshot); + snapshot->in_cb = 0; + snapshot->out_cb = 0; + snapshot->user_data = 0; +} + +void m6502_snapshot_onload(m6502_t* snapshot, m6502_t* sys) { + CHIPS_ASSERT(snapshot && sys); + snapshot->in_cb = sys->in_cb; + snapshot->out_cb = sys->out_cb; + snapshot->user_data = sys->user_data; +} + /* set 16-bit address in 64-bit pin mask */ #define _SA(addr) pins=(pins&~0xFFFF)|((addr)&0xFFFFULL) /* extract 16-bit addess from pin mask */ @@ -651,7 +708,7 @@ uint64_t m6510_iorq(m6502_t* c, uint64_t pins) { uint64_t m6502_tick(m6502_t* c, uint64_t pins) { if (pins & (M6502_SYNC|M6502_IRQ|M6502_NMI|M6502_RDY|M6502_RES)) { // interrupt detection also works in RDY phases, but only NMI is "sticky" - + // NMI is edge-triggered if (0 != ((pins & (pins ^ c->PINS)) & M6502_NMI)) { c->nmi_pip |= 0x100; @@ -660,7 +717,7 @@ uint64_t m6502_tick(m6502_t* c, uint64_t pins) { if ((pins & M6502_IRQ) && (0 == (c->P & M6502_IF))) { c->irq_pip |= 0x100; } - + // RDY pin is only checked during read cycles if ((pins & (M6502_RW|M6502_RDY)) == (M6502_RW|M6502_RDY)) { M6510_SET_PORT(pins, c->io_pins); @@ -672,7 +729,7 @@ uint64_t m6502_tick(m6502_t* c, uint64_t pins) { // load new instruction into 'instruction register' and restart tick counter c->IR = _GD()<<3; _OFF(M6502_SYNC); - + // check IRQ, NMI and RES state // - IRQ is level-triggered and must be active in the full cycle // before SYNC diff --git a/codegen/z80.template.h b/codegen/z80.template.h index a9305eff..417e8660 100644 --- a/codegen/z80.template.h +++ b/codegen/z80.template.h @@ -8,7 +8,7 @@ ~~~~C #define CHIPS_IMPL ~~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide @@ -30,7 +30,7 @@ * NMI --->| |<--> ... * * RFSH <---| |<--> D7 * * +-----------+ * - *********************************** + *********************************** ## Functions @@ -43,13 +43,13 @@ ~~~C uint64_t z80_reset(z80_t* cpu) ~~~ - Resets a z80_t instance, returns pin mask to start execution at + Resets a z80_t instance, returns pin mask to start execution at address 0. ~~~C uint64_t z80_tick(z80_t* cpu, uint64_t pins) ~~~ - Step the z80_t instance for one clock cycle. + Step the z80_t instance for one clock cycle. ~~~C uint64_t z80_prefetch(z80_t* cpu, uint16_t new_pc) @@ -97,11 +97,11 @@ } } ~~~ - The CPU will now run through the whole address space executing NOPs (because the memory is + The CPU will now run through the whole address space executing NOPs (because the memory is filled with 0s instead of a valid program). If there would be a valid Z80 program at memory address 0, this would be executed instead. - IO requests are handled the same as memory requests, but instead of the MREQ pin, the + IO requests are handled the same as memory requests, but instead of the MREQ pin, the IORQ pin must be checked: ~~~C uint8_t mem[(1<<16)] = {0}; @@ -219,7 +219,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. */ #include #include @@ -229,49 +229,88 @@ extern "C" { #endif // address pins -#define Z80_A0 (1ULL<<0) -#define Z80_A1 (1ULL<<1) -#define Z80_A2 (1ULL<<2) -#define Z80_A3 (1ULL<<3) -#define Z80_A4 (1ULL<<4) -#define Z80_A5 (1ULL<<5) -#define Z80_A6 (1ULL<<6) -#define Z80_A7 (1ULL<<7) -#define Z80_A8 (1ULL<<8) -#define Z80_A9 (1ULL<<9) -#define Z80_A10 (1ULL<<10) -#define Z80_A11 (1ULL<<11) -#define Z80_A12 (1ULL<<12) -#define Z80_A13 (1ULL<<13) -#define Z80_A14 (1ULL<<14) -#define Z80_A15 (1ULL<<15) +#define Z80_PIN_A0 (0) +#define Z80_PIN_A1 (1) +#define Z80_PIN_A2 (2) +#define Z80_PIN_A3 (3) +#define Z80_PIN_A4 (4) +#define Z80_PIN_A5 (5) +#define Z80_PIN_A6 (6) +#define Z80_PIN_A7 (7) +#define Z80_PIN_A8 (8) +#define Z80_PIN_A9 (9) +#define Z80_PIN_A10 (10) +#define Z80_PIN_A11 (11) +#define Z80_PIN_A12 (12) +#define Z80_PIN_A13 (13) +#define Z80_PIN_A14 (14) +#define Z80_PIN_A15 (15) // data pins -#define Z80_D0 (1ULL<<16) -#define Z80_D1 (1ULL<<17) -#define Z80_D2 (1ULL<<18) -#define Z80_D3 (1ULL<<19) -#define Z80_D4 (1ULL<<20) -#define Z80_D5 (1ULL<<21) -#define Z80_D6 (1ULL<<22) -#define Z80_D7 (1ULL<<23) +#define Z80_PIN_D0 (16) +#define Z80_PIN_D1 (17) +#define Z80_PIN_D2 (18) +#define Z80_PIN_D3 (19) +#define Z80_PIN_D4 (20) +#define Z80_PIN_D5 (21) +#define Z80_PIN_D6 (22) +#define Z80_PIN_D7 (23) // control pins -#define Z80_M1 (1ULL<<24) // machine cycle 1 -#define Z80_MREQ (1ULL<<25) // memory request -#define Z80_IORQ (1ULL<<26) // input/output request -#define Z80_RD (1ULL<<27) // read -#define Z80_WR (1ULL<<28) // write -#define Z80_HALT (1ULL<<29) // halt state -#define Z80_INT (1ULL<<30) // interrupt request -#define Z80_RES (1ULL<<31) // reset requested -#define Z80_NMI (1ULL<<32) // non-maskable interrupt -#define Z80_WAIT (1ULL<<33) // wait requested -#define Z80_RFSH (1ULL<<34) // refresh +#define Z80_PIN_M1 (24) // machine cycle 1 +#define Z80_PIN_MREQ (25) // memory request +#define Z80_PIN_IORQ (26) // input/output request +#define Z80_PIN_RD (27) // read +#define Z80_PIN_WR (28) // write +#define Z80_PIN_HALT (29) // halt state +#define Z80_PIN_INT (30) // interrupt request +#define Z80_PIN_RES (31) // reset requested +#define Z80_PIN_NMI (32) // non-maskable interrupt +#define Z80_PIN_WAIT (33) // wait requested +#define Z80_PIN_RFSH (34) // refresh // virtual pins (for interrupt daisy chain protocol) -#define Z80_IEIO (1ULL<<37) // unified daisy chain 'Interrupt Enable In+Out' -#define Z80_RETI (1ULL<<38) // cpu has decoded a RETI instruction +#define Z80_PIN_IEIO (37) // unified daisy chain 'Interrupt Enable In+Out' +#define Z80_PIN_RETI (38) // cpu has decoded a RETI instruction + +// pin bit masks +#define Z80_A0 (1ULL<> 8) & Z80_CF) | ((acc ^ val ^ res) & Z80_HF) | - ((((val ^ acc) & (res ^ acc)) >> 5) & Z80_VF); + ((((val ^ acc) & (res ^ acc)) >> 5) & Z80_VF); } static inline uint8_t _z80_sziff2_flags(z80_t* cpu, uint8_t val) { @@ -601,7 +640,7 @@ static inline void _z80_add16(z80_t* cpu, uint16_t val) { const uint32_t res = acc + val; cpu->hlx[cpu->hlx_idx].hl = res; cpu->f = (cpu->f & (Z80_SF|Z80_ZF|Z80_VF)) | - (((acc ^ res ^ val)>>8)&Z80_HF) | + (((acc ^ res ^ val)>>8)&Z80_HF) | ((res >> 16) & Z80_CF) | ((res >> 8) & (Z80_YF|Z80_XF)); } @@ -625,7 +664,7 @@ static inline void _z80_sbc16(z80_t* cpu, uint16_t val) { cpu->wz = acc + 1; const uint32_t res = acc - val - (cpu->f & Z80_CF); cpu->hl = res; - cpu->f = (Z80_NF | (((val ^ acc) & (acc ^ res) & 0x8000) >> 13)) | + cpu->f = (Z80_NF | (((val ^ acc) & (acc ^ res) & 0x8000) >> 13)) | (((acc ^ res ^ val) >> 8) & Z80_HF) | ((res >> 16) & Z80_CF) | ((res >> 8) & (Z80_SF|Z80_YF|Z80_XF)) | @@ -928,7 +967,7 @@ static inline uint64_t _z80_fetch_cb(z80_t* cpu, uint64_t pins) { if (cpu->hlx_idx > 0) { // this is a DD+CB / FD+CB instruction, continue // execution on the special DDCB/FDCB decoder block which - // loads the d-offset first and then the opcode in a + // loads the d-offset first and then the opcode in a // regular memory read machine cycle cpu->step = _z80_special_optable[_Z80_OPSTATE_SLOT_DDFDCB]; } diff --git a/codegen/z80_desc.yml b/codegen/z80_desc.yml index a277fffd..6f9c2ddb 100644 --- a/codegen/z80_desc.yml +++ b/codegen/z80_desc.yml @@ -83,13 +83,11 @@ ADD HL,$RP: LD (BC),A: cond: (x == 0) and (z == 2) and (q == 0) and (p == 0) mcycles: - # NOTE: WZH can also be 0 on Z80 clones (the VisualZ80 netlist also sets WZH to zero) - { type: mwrite, ab: $BC, db: $A, action: "$WZL=$C+1;$WZH=$A" } LD (DE),A: cond: (x == 0) and (z == 2) and (q == 0) and (p == 1) mcycles: - # NOTE: WZH can also be 0 on Z80 clones (the VisualZ80 netlist also sets WZH to zero) - { type: mwrite, ab: $DE, db: $A, action: "$WZL=$E+1;$WZH=$A" } LD (nn),HL: @@ -105,7 +103,6 @@ LD (nn),A: mcycles: - { type: mread, ab: $PC++, dst: $WZL } - { type: mread, ab: $PC++, dst: $WZH } - # NOTE: WZH can also be 0 on Z80 clones (the VisualZ80 netlist also sets WZH to zero) - { type: mwrite, ab: $WZ++, db: $A, action: "$WZH=$A" } LD A,(BC): @@ -288,6 +285,7 @@ OUT (n),A: - { type: mread, ab: $PC++, dst: $WZL, action: $WZH=$A } - { type: iowrite, ab: $WZ, db: $A, action: $WZL++ } +# FIXME: ioread can go directly into A IN A,(n): cond: (x == 3) and (y == 3) and (z == 3) mcycles: @@ -385,6 +383,7 @@ ED NOP: cond: (x == 0) or (x == 3) or ((x == 1) and (z == 7) and (y >= 6)) or ((x == 2) and ((z > 3) or (y < 4))) mcycles: [] +# FIXME: should be RRY (because H/L can't be IXH/IXL etc...) IN $RY,(C): prefix: ed cond: (x == 1) and (y != 6) and (z == 0) @@ -400,6 +399,7 @@ IN (C): # discard result, only set flags - { type: overlapped, action: "_z80_in(cpu,$DLATCH)" } +# FIXME: should be RRY (because H/L can't be IXH/IXL etc...) OUT (C),$RY: prefix: ed cond: (x == 1) and (y != 6) and (z == 1) @@ -432,7 +432,7 @@ LD (nn),$RP: - { type: mread, ab: $PC++, dst: $WZH } - { type: mwrite, ab: $WZ++, db: $RRPL } - { type: mwrite, ab: $WZ, db: $RRPH } - + LD $RP,(nn): prefix: ed cond: (x == 1) and (z == 3) and (q == 1) @@ -586,7 +586,7 @@ CPDR: INI: prefix: ed cond: (x == 2) and (y == 4) and (z == 2) - mcycles: + mcycles: - { type: generic, tcycles: 1 } - { type: ioread, ab: $BC, dst: $DLATCH, action: "$WZ=$BC+1;$B--;" } - { type: mwrite, ab: "cpu->hl++", db: $DLATCH, action: "_z80_ini_ind(cpu,$DLATCH,$C+1)" } @@ -679,6 +679,7 @@ ddfdcb: #== INTERRUPT HANDLING ========================================================= +# FIXME: last tcycle also needs "cpu->addr = cpu->hl" int_im0: flags: { special: true } mcycles: diff --git a/systems/atom.h b/systems/atom.h index 205d171a..16bae7df 100644 --- a/systems/atom.h +++ b/systems/atom.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -20,6 +20,7 @@ You need to include the following headers before including atom.h: + - chips/chips_common.h - chips/m6502.h - chips/mc6847.h - chips/i8255.h @@ -54,16 +55,20 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. */ #include #include #include +#include #ifdef __cplusplus extern "C" { #endif +// bump snapshot version when memory layout of atom_t changes +#define ATOM_SNAPSHOT_VERSION (1) + #define ATOM_FREQUENCY (1000000) #define ATOM_MAX_AUDIO_SAMPLES (1024) // max number of audio samples in internal sample buffer #define ATOM_DEFAULT_AUDIO_SAMPLES (128) // default number of samples in internal sample buffer @@ -82,51 +87,15 @@ typedef enum { #define ATOM_JOYSTICK_UP (1<<3) #define ATOM_JOYSTICK_BTN (1<<4) -// audio sample data callback -typedef struct { - void (*func)(const float* samples, int num_samples, void* user_data); - void* user_data; -} atom_audio_callback_t; - -// debugging hook definitions -typedef void (*atom_debug_func_t)(void* user_data, uint64_t pins); -typedef struct { - struct { - atom_debug_func_t func; - void* user_data; - } callback; - bool* stopped; -} atom_debug_t; - -typedef struct { - const void* ptr; - size_t size; -} atom_rom_image_t; - // configuration parameters for atom_init() typedef struct { atom_joystick_type_t joystick_type; // what joystick type to emulate, default is ATOM_JOYSTICK_NONE - atom_debug_t debug; // optional debug callback and userdata ptr - - // video output config + chips_debug_t debug; + chips_audio_desc_t audio; struct { - void* ptr; // pointer to a linear RGBA8 pixel buffer, at least 320*256*4 bytes - size_t size; // size of the pixel buffer in bytes - } pixel_buffer; - - // audio output config - struct { - atom_audio_callback_t callback; // called when audio_num_samples are ready - int num_samples; // output sample buffer size, default is ATOM_DEFAULT_AUDIO_SAMPLES - int sample_rate; // playback sample rate, default is 44100 - float volume; // audio volume: 0.0..1.0, default is 0.25 - } audio; - - // ROM images - struct { - atom_rom_image_t abasic; - atom_rom_image_t afloat; - atom_rom_image_t dosrom; + chips_range_t abasic; + chips_range_t afloat; + chips_range_t dosrom; } roms; } atom_desc_t; @@ -137,7 +106,7 @@ typedef struct { i8255_t ppi; m6522_t via; beeper_t beeper; - atom_debug_t debug; + chips_debug_t debug; uint64_t pins; bool valid; int counter_2_4khz; @@ -151,7 +120,7 @@ typedef struct { mem_t mem; kbd_t kbd; struct { - atom_audio_callback_t callback; + chips_audio_callback_t callback; int num_samples; int sample_pos; float sample_buffer[ATOM_MAX_AUDIO_SAMPLES]; @@ -160,6 +129,7 @@ typedef struct { uint8_t rom_abasic[0x2000]; uint8_t rom_afloat[0x1000]; uint8_t rom_dosrom[0x1000]; + alignas(64) uint8_t fb[MC6847_FRAMEBUFFER_SIZE_BYTES]; // tape loading struct { int size; // tape_size is > 0 if a tape is inserted @@ -174,16 +144,10 @@ void atom_init(atom_t* sys, const atom_desc_t* desc); void atom_discard(atom_t* sys); // reset Atom instance void atom_reset(atom_t* sys); +// query display information, can be called with nullptr +chips_display_info_t atom_display_info(atom_t* sys); // run Atom instance for a number of microseconds uint32_t atom_exec(atom_t* sys, uint32_t micro_seconds); -// get the standard framebuffer width and height in pixels -int atom_std_display_width(void); -int atom_std_display_height(void); -// get the maximum framebuffer size in number of bytes -size_t atom_max_display_size(void); -// get the current framebuffer width and height in pixels -int atom_display_width(atom_t* sys); -int atom_display_height(atom_t* sys); // send a key down event void atom_key_down(atom_t* sys, int key_code); // send a key up event @@ -195,9 +159,13 @@ atom_joystick_type_t atom_joystick_type(atom_t* sys); // set joystick mask (combination of ATOM_JOYSTICK_*) void atom_joystick(atom_t* sys, uint8_t mask); // insert a tape for loading (must be an Atom TAP file), data will be copied -bool atom_insert_tape(atom_t* sys, const uint8_t* ptr, int num_bytes); +bool atom_insert_tape(atom_t* sys, chips_range_t data); // remove tape void atom_remove_tape(atom_t* sys); +// take snapshot, patches pointers to zero, returns snapshot version +uint32_t atom_save_snapshot(atom_t* sys, atom_t* dst); +// load snapshot, returns false if snapshot version doesn't match +bool atom_load_snapshot(atom_t* sys, uint32_t version, atom_t* src); #ifdef __cplusplus } // extern "C" @@ -222,10 +190,7 @@ static uint64_t _atom_osload(atom_t* sys, uint64_t pins); void atom_init(atom_t* sys, const atom_desc_t* desc) { CHIPS_ASSERT(sys && desc); - CHIPS_ASSERT(desc->pixel_buffer.ptr && (desc->pixel_buffer.size >= atom_max_display_size())); - if (desc->debug.callback.func) { - CHIPS_ASSERT(desc->debug.stopped); - } + if (desc->debug.callback.func) { CHIPS_ASSERT(desc->debug.stopped); } memset(sys, 0, sizeof(atom_t)); sys->valid = true; sys->joystick_type = desc->joystick_type; @@ -243,12 +208,14 @@ void atom_init(atom_t* sys, const atom_desc_t* desc) { CHIPS_ASSERT(desc->roms.dosrom.ptr && (desc->roms.dosrom.size == sizeof(sys->rom_dosrom))); memcpy(&sys->rom_dosrom, desc->roms.dosrom.ptr, sizeof(sys->rom_dosrom)); - /* initialize the hardware */ + // initialize the hardware sys->pins = m6502_init(&sys->cpu, &(m6502_desc_t){0}); mc6847_init(&sys->vdg, &(mc6847_desc_t){ .tick_hz = ATOM_FREQUENCY, - .rgba8_buffer = (uint32_t*) desc->pixel_buffer.ptr, - .rgba8_buffer_size = desc->pixel_buffer.size, + .framebuffer = { + .ptr = &sys->fb, + .size = sizeof(sys->fb), + }, .fetch_cb = _atom_vdg_fetch, .user_data = sys, }); @@ -261,7 +228,7 @@ void atom_init(atom_t* sys, const atom_desc_t* desc) { .base_volume = _ATOM_DEFAULT(desc->audio.volume, 0.5f), }); - /* setup memory map and keyboard matrix */ + // setup memory map and keyboard matrix _atom_init_memorymap(sys); _atom_init_keymap(sys); } @@ -282,19 +249,19 @@ void atom_reset(atom_t* sys) { } uint64_t _atom_tick(atom_t* sys, uint64_t cpu_pins) { - /* tick the CPU */ + // tick the CPU cpu_pins = m6502_tick(&sys->cpu, cpu_pins); - /* tick the 2.4khz counter */ + // tick the 2.4khz counter sys->counter_2_4khz++; if (sys->counter_2_4khz >= sys->period_2_4khz) { sys->state_2_4khz = !sys->state_2_4khz; sys->counter_2_4khz -= sys->period_2_4khz; } - /* update beeper */ + // update beeper if (beeper_tick(&sys->beeper)) { - /* new audio sample ready */ + // new audio sample ready sys->audio.sample_buffer[sys->audio.sample_pos++] = sys->beeper.sample; if (sys->audio.sample_pos == sys->audio.num_samples) { if (sys->audio.callback.func) { @@ -304,13 +271,13 @@ uint64_t _atom_tick(atom_t* sys, uint64_t cpu_pins) { } } - /* address decoding */ + // address decoding const uint16_t addr = M6502_GET_ADDR(cpu_pins); uint64_t via_pins = cpu_pins & M6502_PIN_MASK; uint64_t ppi_pins = cpu_pins & M6502_PIN_MASK & ~(I8255_RD|I8255_WR|I8255_PC_PINS); uint64_t vdg_pins = 0; if ((addr >= 0xB000) && (addr < 0xC000)) { - /* memory-mapped IO area */ + // memory-mapped IO area if ((addr >= 0xB000) && (addr < 0xB400)) { ppi_pins |= I8255_CS; } @@ -320,7 +287,7 @@ uint64_t _atom_tick(atom_t* sys, uint64_t cpu_pins) { a quick'n'dirty hack for joystick input */ if (cpu_pins & M6502_RW) { - /* read from MMC extension */ + // read from MMC extension if (addr == 0xB400) { /* reading from 0xB400 returns a status/error code, the important ones are STATUS_OK=0x3F, and STATUS_BUSY=0x80, STATUS_COMPLETE @@ -329,36 +296,36 @@ uint64_t _atom_tick(atom_t* sys, uint64_t cpu_pins) { M6502_SET_DATA(cpu_pins, 0x3F); } else if ((addr == 0xB401) && (sys->mmc_cmd == 0xA2)) { - /* read MMC joystick */ - M6502_SET_DATA(cpu_pins, ~(sys->kbd_joymask | sys->joy_joymask)); + // read MMC joystick (NOTE: the unusual cast fixes an UBSAN warning) + M6502_SET_DATA(cpu_pins, ((uint64_t)~(sys->kbd_joymask | sys->joy_joymask))); } } else { - /* write to MMC extension */ + // write to MMC extension if (addr == 0xB400) { sys->mmc_cmd = M6502_GET_DATA(cpu_pins); } } } else if ((addr >= 0xB800) && (addr < 0xBC00)) { - /* 6522 VIA: http://www.acornatom.nl/sites/fpga/www.howell1964.freeserve.co.uk/acorn/atom/amb/amb_6522.htm */ + // 6522 VIA: http://www.acornatom.nl/sites/fpga/www.howell1964.freeserve.co.uk/acorn/atom/amb/amb_6522.htm via_pins |= M6522_CS1; } else { - /* remaining IO space is for expansion devices */ + // remaining IO space is for expansion devices if (cpu_pins & M6502_RW) { M6502_SET_DATA(cpu_pins, 0x00); } } } else { - /* regular memory access */ + // regular memory access if (cpu_pins & M6502_RW) { - /* memory read */ + // memory read M6502_SET_DATA(cpu_pins, mem_rd(&sys->mem, addr)); } else { - /* memory access */ + // memory access mem_wr(&sys->mem, addr, M6502_GET_DATA(cpu_pins)); } } @@ -411,7 +378,7 @@ uint64_t _atom_tick(atom_t* sys, uint64_t cpu_pins) { } } - /* tick the VIA */ + // tick the VIA { via_pins = m6522_tick(&sys->via, via_pins); if ((via_pins & (M6522_RW|M6522_CS1)) == (M6522_RW|M6522_CS1)) { @@ -533,30 +500,6 @@ void atom_joystick(atom_t* sys, uint8_t mask) { sys->joy_joymask = mask; } -int atom_std_display_width(void) { - return MC6847_DISPLAY_WIDTH; -} - -int atom_std_display_height(void) { - return MC6847_DISPLAY_HEIGHT; -} - -size_t atom_max_display_size(void) { - return MC6847_DISPLAY_WIDTH * MC6847_DISPLAY_HEIGHT * 4; -} - -int atom_display_width(atom_t* sys) { - CHIPS_ASSERT(sys && sys->valid); - (void)sys; - return MC6847_DISPLAY_WIDTH; -} - -int atom_display_height(atom_t* sys) { - CHIPS_ASSERT(sys && sys->valid); - (void)sys; - return MC6847_DISPLAY_HEIGHT; -} - static void _atom_init_keymap(atom_t* sys) { /* setup the keyboard matrix the Atom has a 10x8 keyboard matrix, where the @@ -569,7 +512,7 @@ static void _atom_init_keymap(atom_t* sys) { /* ctrl key is entire line 6 */ const int ctrl = (1<<1); kbd_register_modifier_line(&sys->kbd, 1, 6); /* alpha-numeric keys */ - const char* keymap = + const char* keymap = /* no shift */ " ^]\\[ "/**/"3210 "/* */"-,;:987654"/**/"GFEDCBA@/."/**/"QPONMLKJIH"/**/" ZYXWVUTSR" /* shift */ @@ -640,17 +583,17 @@ typedef struct { uint16_t length; } _atom_tap_header; -bool atom_insert_tape(atom_t* sys, const uint8_t* ptr, int num_bytes) { +bool atom_insert_tape(atom_t* sys, chips_range_t data) { CHIPS_ASSERT(sys && sys->valid); - CHIPS_ASSERT(ptr); + CHIPS_ASSERT(data.ptr); atom_remove_tape(sys); - /* check for valid size */ - if ((num_bytes < (int)sizeof(_atom_tap_header)) || (num_bytes > ATOM_MAX_TAPE_SIZE)) { + // check for valid size + if ((data.size < sizeof(_atom_tap_header)) || (data.size > ATOM_MAX_TAPE_SIZE)) { return false; } - memcpy(sys->tape.buf, ptr, num_bytes); + memcpy(sys->tape.buf, data.ptr, data.size); sys->tape.pos = 0; - sys->tape.size = num_bytes; + sys->tape.size = data.size; return true; } @@ -754,4 +697,60 @@ uint64_t _atom_osload(atom_t* sys, uint64_t pins) { return pins; } +chips_display_info_t atom_display_info(atom_t* sys) { + const chips_display_info_t res = { + .frame = { + .dim = { + .width = MC6847_FRAMEBUFFER_WIDTH, + .height = MC6847_FRAMEBUFFER_HEIGHT, + }, + .bytes_per_pixel = 1, + .buffer = { + .ptr = sys ? sys->fb : 0, + .size = MC6847_FRAMEBUFFER_SIZE_BYTES, + } + }, + .screen = { + .x = 0, + .y = 0, + .width = MC6847_DISPLAY_WIDTH, + .height = MC6847_DISPLAY_HEIGHT, + }, + .palette = { + .ptr = sys ? sys->vdg.hwcolors : 0, + .size = MC6847_HWCOLOR_NUM * sizeof(uint32_t) + } + }; + CHIPS_ASSERT(((sys == 0) && (res.frame.buffer.ptr == 0)) || ((sys != 0) && (res.frame.buffer.ptr != 0))); + CHIPS_ASSERT(((sys == 0) && (res.palette.ptr == 0)) || ((sys != 0) && (res.palette.ptr != 0))); + return res; +} + +uint32_t atom_save_snapshot(atom_t* sys, atom_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->debug); + chips_audio_callback_snapshot_onsave(&dst->audio.callback); + m6502_snapshot_onsave(&dst->cpu); + mc6847_snapshot_onsave(&dst->vdg); + mem_snapshot_onsave(&dst->mem, sys); + return ATOM_SNAPSHOT_VERSION; +} + +bool atom_load_snapshot(atom_t* sys, uint32_t version, atom_t* src) { + CHIPS_ASSERT(sys && src); + if (version != ATOM_SNAPSHOT_VERSION) { + return false; + } + static atom_t im; + im = *src; + chips_debug_snapshot_onload(&im.debug, &sys->debug); + chips_audio_callback_snapshot_onload(&im.audio.callback, &sys->audio.callback); + m6502_snapshot_onload(&im.cpu, &sys->cpu); + mc6847_snapshot_onload(&im.vdg, &sys->vdg); + mem_snapshot_onload(&im.mem, sys); + *sys = im; + return true; +} + #endif /* CHIPS_IMPL */ diff --git a/systems/bombjack.h b/systems/bombjack.h index 4f1d1412..f3a7b2e8 100644 --- a/systems/bombjack.h +++ b/systems/bombjack.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -20,6 +20,7 @@ You need to include the following headers before including bombjack.h: + - chips/chips_common.h - chips/z80.h - chips/ay38910.h - chips/clk.h @@ -27,11 +28,11 @@ ## The Bomb Jack Arcade Machine - See: - + See: + - https://floooh.github.io/2018/10/06/bombjack.html - https://github.com/floooh/emu-info/blob/master/misc/bombjack-schematics.pdf - + ## zlib/libpng license Copyright (c) 2018 Andre Weissflog @@ -48,18 +49,27 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include #include +#include #ifdef __cplusplus extern "C" { #endif +// increase when bombjack_t memory layout changes +#define BOMBJACK_SNAPSHOT_VERSION (2) + #define BOMBJACK_MAX_AUDIO_SAMPLES (1024) #define BOMBJACK_DEFAULT_AUDIO_SAMPLES (128) +#define BOMBJACK_FRAMEBUFFER_WIDTH (256) +#define BOMBJACK_FRAMEBUFFER_HEIGHT (288) // save space for sprites +#define BOMBJACK_FRAMEBUFFER_SIZE_BYTES (BOMBJACK_FRAMEBUFFER_WIDTH * BOMBJACK_FRAMEBUFFER_HEIGHT * 4) +#define BOMBJACK_DISPLAY_WIDTH (256) +#define BOMBJACK_DISPLAY_HEIGHT (256) // joystick mask bits #define BOMBJACK_JOYSTICK_RIGHT (1<<0) @@ -122,73 +132,33 @@ extern "C" { #define BOMBJACK_DSW1_DEFAULT (BOMBJACK_DSW1_CABINET_UPRIGHT|BOMBJACK_DSW1_DEMOSOUND_ON) #define BOMBJACK_DSW2_DEFAULT (BOMBJACK_DSW2_DIFFICULTY_EASY) -// audio callback -typedef struct { - void (*func)(const float* samples, int num_samples, void* user_data); - void* user_data; -} bombjack_audio_callback_t; - // debugging hook definitions -typedef void (*bombjack_debug_func_t)(void* user_data, uint64_t pins); typedef struct { - struct { - struct { - bombjack_debug_func_t func; - void* user_data; - } callback; - bool* stopped; - } mainboard; - struct { - struct { - bombjack_debug_func_t func; - void* user_data; - } callback; - bool* stopped; - } soundboard; + chips_debug_t mainboard; + chips_debug_t soundboard; } bombjack_debug_t; -typedef struct { - const void* ptr; - size_t size; -} bombjack_rom_image_t; - // configuration parameters for bombjack_init() typedef struct { - // optional debugging hook bombjack_debug_t debug; - - // video output config - struct { - void* ptr; // pointer to a linear RGBA8 pixel buffer, at least 256*256*4 bytes - size_t size; // size of the pixel buffer in bytes - } pixel_buffer; - - // audio output config (if you don't want audio, set audio.callback.func to zero) - struct { - bombjack_audio_callback_t callback; // called when audio_num_samples are ready - int num_samples; // default is BOMBJACK_DEFAULT_AUDIO_SAMPLES - int sample_rate; // playback sample rate, default is 44100 - float volume; // audio volume, 0.0..1.0, default is 1.0 - } audio; - - // ROM images + chips_audio_desc_t audio; struct { - bombjack_rom_image_t main_0000_1FFF; // main-board ROM 0x0000..0x1FFF - bombjack_rom_image_t main_2000_3FFF; // main-board ROM 0x2000..0x3FFF - bombjack_rom_image_t main_4000_5FFF; // main-board ROM 0x4000..0x5FFF - bombjack_rom_image_t main_6000_7FFF; // main-board ROM 0x6000..0x7FFF - bombjack_rom_image_t main_C000_DFFF; // main-board ROM 0xC000..0xDFFF - bombjack_rom_image_t sound_0000_1FFF; // sound-board ROM 0x0000..0x2000 - bombjack_rom_image_t chars_0000_0FFF; // char ROM 0x0000..0x0FFF - bombjack_rom_image_t chars_1000_1FFF; // char ROM 0x1000..0x1FFF - bombjack_rom_image_t chars_2000_2FFF; // char ROM 0x2000..0x2FFF - bombjack_rom_image_t tiles_0000_1FFF; // tile ROM 0x0000..0x1FFF - bombjack_rom_image_t tiles_2000_3FFF; // tile ROM 0x2000..0x3FFF - bombjack_rom_image_t tiles_4000_5FFF; // tile ROM 0x4000..0x5FFF - bombjack_rom_image_t sprites_0000_1FFF; // sprite ROM 0x0000..0x1FFF - bombjack_rom_image_t sprites_2000_3FFF; // sprite ROM 0x2000..0x3FFF - bombjack_rom_image_t sprites_4000_5FFF; // sprite ROM 0x4000..0x5FFF - bombjack_rom_image_t maps_0000_0FFF; // map ROM 0x0000..0x0FFF + chips_range_t main_0000_1FFF; // main-board ROM 0x0000..0x1FFF + chips_range_t main_2000_3FFF; // main-board ROM 0x2000..0x3FFF + chips_range_t main_4000_5FFF; // main-board ROM 0x4000..0x5FFF + chips_range_t main_6000_7FFF; // main-board ROM 0x6000..0x7FFF + chips_range_t main_C000_DFFF; // main-board ROM 0xC000..0xDFFF + chips_range_t sound_0000_1FFF; // sound-board ROM 0x0000..0x2000 + chips_range_t chars_0000_0FFF; // char ROM 0x0000..0x0FFF + chips_range_t chars_1000_1FFF; // char ROM 0x1000..0x1FFF + chips_range_t chars_2000_2FFF; // char ROM 0x2000..0x2FFF + chips_range_t tiles_0000_1FFF; // tile ROM 0x0000..0x1FFF + chips_range_t tiles_2000_3FFF; // tile ROM 0x2000..0x3FFF + chips_range_t tiles_4000_5FFF; // tile ROM 0x4000..0x5FFF + chips_range_t sprites_0000_1FFF; // sprite ROM 0x0000..0x1FFF + chips_range_t sprites_2000_3FFF; // sprite ROM 0x2000..0x3FFF + chips_range_t sprites_4000_5FFF; // sprite ROM 0x4000..0x5FFF + chips_range_t maps_0000_0FFF; // map ROM 0x0000..0x0FFF } roms; } bombjack_desc_t; @@ -231,13 +201,13 @@ typedef struct { uint8_t rom_maps[1][0x1000]; struct { - bombjack_audio_callback_t callback; + chips_audio_callback_t callback; int num_samples; int sample_pos; float volume; float sample_buffer[BOMBJACK_MAX_AUDIO_SAMPLES]; } audio; - uint32_t* pixel_buffer; + struct { bombjack_debug_t debug; bool draw_background_layer; @@ -245,6 +215,8 @@ typedef struct { bool draw_sprite_layer; bool clear_background_layer; } dbg; + + alignas(64) uint32_t fb[BOMBJACK_FRAMEBUFFER_WIDTH * BOMBJACK_FRAMEBUFFER_HEIGHT]; } bombjack_t; // initialize a new bombjack instance @@ -253,17 +225,17 @@ void bombjack_init(bombjack_t* sys, const bombjack_desc_t* desc); void bombjack_discard(bombjack_t* sys); // reset a bombjack instance void bombjack_reset(bombjack_t* sys); +// query display attributes and framebuffer content (can be called with nullptr) +chips_display_info_t bombjack_display_info(bombjack_t* sys); // run bombjack instance for given amount of microseconds uint32_t bombjack_exec(bombjack_t* sys, uint32_t micro_seconds); -// get the standard framebuffer width and height in pixels -int bombjack_std_display_width(void); -int bombjack_std_display_height(void); -// get the current framebuffer width and height in pixels -int bombjack_display_width(bombjack_t* sys); -int bombjack_display_height(bombjack_t* sys); +// take a snapshot, patches any pointers to zero, returns a snapshot version +uint32_t bombjack_save_snapshot(bombjack_t* sys, bombjack_t* dst); +// load a snapshot, returns false if snapshot version doesn't match +bool bombjack_load_snapshot(bombjack_t* sys, uint32_t version, bombjack_t* src); #ifdef __cplusplus -} /* extern "C" */ +} // extern "C" #endif /*-- IMPLEMENTATION ----------------------------------------------------------*/ @@ -279,21 +251,16 @@ int bombjack_display_height(bombjack_t* sys); #define _BOMBJACK_VSYNC_PERIOD_4MHZ (4000000/60) #define _BOMBJACK_VBLANK_DURATION_4MHZ (((4000000/60)/525)*(525-483)) #define _BOMBJACK_VSYNC_PERIOD_3MHZ (3000000/60) -#define _BOMBJACK_DISPLAY_WIDTH (256) -#define _BOMBJACK_DISPLAY_HEIGHT (256) -#define _BOMBJACK_DISPLAY_SIZE (_BOMBJACK_DISPLAY_WIDTH*_BOMBJACK_DISPLAY_HEIGHT*4) #define _bombjack_def(val, def) (val == 0 ? def : val) void bombjack_init(bombjack_t* sys, const bombjack_desc_t* desc) { CHIPS_ASSERT(sys && desc); - CHIPS_ASSERT((0 == desc->pixel_buffer.ptr) || (desc->pixel_buffer.ptr && (desc->pixel_buffer.size >= _BOMBJACK_DISPLAY_SIZE))); if (desc->debug.mainboard.callback.func) { CHIPS_ASSERT(desc->debug.mainboard.stopped); } if (desc->debug.soundboard.callback.func) { CHIPS_ASSERT(desc->debug.soundboard.stopped); } memset(sys, 0, sizeof(bombjack_t)); sys->valid = true; - sys->pixel_buffer = (uint32_t*) desc->pixel_buffer.ptr; sys->dbg.debug = desc->debug; sys->dbg.draw_background_layer = true; sys->dbg.draw_foreground_layer = true; @@ -362,7 +329,7 @@ void bombjack_init(bombjack_t* sys, const bombjack_desc_t* desc) { .sound_hz = _bombjack_def(desc->audio.sample_rate, 44100), .magnitude = 0.2f, }; - for (int i = 0; i < 3; i++) { + for (size_t i = 0; i < 3; i++) { ay38910_init(&sys->soundboard.psg[i], &psg_desc); } @@ -388,7 +355,7 @@ void bombjack_init(bombjack_t* sys, const bombjack_desc_t* desc) { C000..DFFF: ROM palette RAM is 128 entries with 16-bit per entry (xxxxBBBBGGGGRRRR). - + NOTE that ROM data that's not accessible by CPU isn't accessed through a memory mapper. */ @@ -421,7 +388,7 @@ void bombjack_reset(bombjack_t* sys) { CHIPS_ASSERT(sys && sys->valid); z80_reset(&sys->mainboard.cpu); z80_reset(&sys->soundboard.cpu); - for (int i = 0; i < 3; i++) { + for (size_t i = 0; i < 3; i++) { ay38910_reset(&sys->soundboard.psg[i]); } } @@ -763,24 +730,23 @@ static uint64_t _bombjack_tick_soundboard(bombjack_t* sys, uint64_t pins) { ((uint16_t)rom[0+off]<<8)|((uint16_t)rom[8+off]) static void _bombjack_decode_background(bombjack_t* sys) { - CHIPS_ASSERT(sys && sys->pixel_buffer); - uint32_t* ptr = sys->pixel_buffer; - int img_base_addr = (sys->mainboard.bg_image & 7) * 0x0200; + uint32_t* ptr = sys->fb; + uint16_t img_base_addr = (sys->mainboard.bg_image & 7) * 0x0200; bool img_valid = (sys->mainboard.bg_image & 0x10) != 0; - for (int y = 0; y < 16; y++) { - for (int x = 0; x < 16; x++) { - int addr = img_base_addr + (y * 16 + x); + for (size_t y = 0; y < 16; y++) { + for (size_t x = 0; x < 16; x++) { + size_t addr = img_base_addr + (y * 16 + x); // 256 tiles uint8_t tile_code = img_valid ? sys->rom_maps[0][addr] : 0; uint8_t attr = sys->rom_maps[0][addr + 0x0100]; uint8_t color_block = (attr & 0x0F)<<3; bool flip_y = (attr & 0x80) != 0; if (flip_y) { - ptr += 15*256; + ptr += 15*BOMBJACK_FRAMEBUFFER_WIDTH; } // every tile is 32 bytes - int off = tile_code * 32; - for (int yy = 0; yy < 16; yy++) { + size_t off = tile_code * 32; + for (size_t yy = 0; yy < 16; yy++) { uint16_t bm0 = BOMBJACK_GATHER16(sys->rom_tiles[0], off); uint16_t bm1 = BOMBJACK_GATHER16(sys->rom_tiles[1], off); uint16_t bm2 = BOMBJACK_GATHER16(sys->rom_tiles[2], off); @@ -795,15 +761,15 @@ static void _bombjack_decode_background(bombjack_t* sys) { ptr += flip_y ? -272 : 240; } if (flip_y) { - ptr += 256 + 16; + ptr += BOMBJACK_FRAMEBUFFER_WIDTH + 16; } else { - ptr -= (16 * 256) - 16; + ptr -= (16 * BOMBJACK_FRAMEBUFFER_WIDTH) - 16; } } - ptr += (15 * 256); + ptr += (15 * BOMBJACK_FRAMEBUFFER_WIDTH); } - CHIPS_ASSERT(ptr == sys->pixel_buffer+256*256); + CHIPS_ASSERT(ptr == &sys->fb[BOMBJACK_FRAMEBUFFER_WIDTH * BOMBJACK_DISPLAY_HEIGHT]); } /* render foreground tiles @@ -825,22 +791,21 @@ static void _bombjack_decode_background(bombjack_t* sys) { pixel. */ static void _bombjack_decode_foreground(bombjack_t* sys) { - CHIPS_ASSERT(sys && sys->pixel_buffer); - uint32_t* ptr = sys->pixel_buffer; + uint32_t* ptr = sys->fb; // 32x32 tiles, each 8x8 - for (int y = 0; y < 32; y++) { - for (int x = 0; x < 32; x++) { - int addr = y * 32 + x; + for (size_t y = 0; y < 32; y++) { + for (size_t x = 0; x < 32; x++) { + size_t addr = y * 32 + x; // char codes are at 0x9000, color codes at 0x9400, RAM starts at 0x8000 uint8_t chr = sys->main_ram[(0x9000-0x8000) + addr]; uint8_t clr = sys->main_ram[(0x9400-0x8000) + addr]; // 512 foreground tiles, take 9th bit from color code - int tile_code = chr | ((clr & 0x10)<<4); + size_t tile_code = chr | ((clr & 0x10)<<4); // 16 color blocks a 8 colors - int color_block = (clr & 0x0F)<<3; + size_t color_block = (clr & 0x0F)<<3; // 8 bytes per char bitmap - int off = tile_code * 8; - for (int yy = 0; yy < 8; yy++) { + size_t off = tile_code * 8; + for (size_t yy = 0; yy < 8; yy++) { /* 3 bit planes per char (8 colors per pixel within the palette color block of the char */ @@ -857,11 +822,11 @@ static void _bombjack_decode_foreground(bombjack_t* sys) { } ptr += 248; } - ptr -= (8 * 256) - 8; + ptr -= (8 * BOMBJACK_FRAMEBUFFER_WIDTH) - 8; } - ptr += (7 * 256); + ptr += (7 * BOMBJACK_FRAMEBUFFER_WIDTH); } - CHIPS_ASSERT(ptr == sys->pixel_buffer+256*256); + CHIPS_ASSERT(ptr == &sys->fb[BOMBJACK_FRAMEBUFFER_WIDTH * BOMBJACK_DISPLAY_HEIGHT]); } /* render sprites @@ -888,12 +853,11 @@ static void _bombjack_decode_foreground(bombjack_t* sys) { ((uint32_t)rom[40+off]) static void _bombjack_decode_sprites(bombjack_t* sys) { - CHIPS_ASSERT(sys && sys->pixel_buffer); - uint32_t* dst = sys->pixel_buffer; + uint32_t* dst = sys->fb; // 24 hardware sprites, sprite 0 has highest priority for (int sprite_nr = 23; sprite_nr >= 0; sprite_nr--) { // sprite RAM starts at 0x9820, RAM starts at 0x8000 - int addr = (0x9820 - 0x8000) + sprite_nr*4; + size_t addr = (0x9820 - 0x8000) + sprite_nr*4; uint8_t b0 = sys->main_ram[addr + 0]; uint8_t b1 = sys->main_ram[addr + 1]; uint8_t b2 = sys->main_ram[addr + 2]; @@ -901,15 +865,15 @@ static void _bombjack_decode_sprites(bombjack_t* sys) { uint8_t color_block = (b1 & 0x0F)<<3; // screen is 90 degree rotated, so x and y are switched - int px = b3; - int sprite_code = b0 & 0x7F; + uint8_t px = b3; + uint8_t sprite_code = b0 & 0x7F; if (b0 & 0x80) { // 32x32 'large' sprites (no flip-x/y needed) - int py = 225 - b2; - uint32_t* ptr = dst + py*256 + px; + uint8_t py = 225 - b2; + uint32_t* ptr = dst + py*BOMBJACK_FRAMEBUFFER_WIDTH + px; // offset into sprite ROM to gather sprite bitmap pixels - int off = sprite_code * 128; - for (int y = 0; y < 32; y++) { + size_t off = sprite_code * 128; + for (size_t y = 0; y < 32; y++) { uint32_t bm0 = BOMBJACK_GATHER32(sys->rom_sprites[0], off); uint32_t bm1 = BOMBJACK_GATHER32(sys->rom_sprites[1], off); uint32_t bm2 = BOMBJACK_GATHER32(sys->rom_sprites[2], off); @@ -923,6 +887,7 @@ static void _bombjack_decode_sprites(bombjack_t* sys) { for (int x = 31; x >= 0; x--) { uint8_t pen = ((bm2>>x)&1) | (((bm1>>x)&1)<<1) | (((bm0>>x)&1)<<2); if (0 != pen) { + CHIPS_ASSERT((ptr >= &sys->fb[0]) && (ptr < &sys->fb[BOMBJACK_FRAMEBUFFER_WIDTH*BOMBJACK_FRAMEBUFFER_HEIGHT])); *ptr = sys->mainboard.palette[color_block | pen]; } ptr++; @@ -932,16 +897,16 @@ static void _bombjack_decode_sprites(bombjack_t* sys) { } else { // 16*16 sprites are decoded like 16x16 background tiles - int py = 241 - b2; - uint32_t* ptr = dst + py*256 + px; + uint8_t py = 241 - b2; + uint32_t* ptr = dst + py*BOMBJACK_FRAMEBUFFER_WIDTH + px; bool flip_x = (b1 & 0x80) != 0; bool flip_y = (b1 & 0x40) != 0; if (flip_x) { - ptr += 16*256; + ptr += 16*BOMBJACK_FRAMEBUFFER_WIDTH; } // offset into sprite ROM to gather sprite bitmap pixels - int off = sprite_code * 32; - for (int y = 0; y < 16; y++) { + size_t off = sprite_code * 32; + for (size_t y = 0; y < 16; y++) { uint16_t bm0 = BOMBJACK_GATHER16(sys->rom_sprites[0], off); uint16_t bm1 = BOMBJACK_GATHER16(sys->rom_sprites[1], off); uint16_t bm2 = BOMBJACK_GATHER16(sys->rom_sprites[2], off); @@ -950,9 +915,10 @@ static void _bombjack_decode_sprites(bombjack_t* sys) { off += 8; } if (flip_y) { - for (int x=0; x<=15; x++) { + for (size_t x=0; x<=15; x++) { uint8_t pen = ((bm2>>x)&1) | (((bm1>>x)&1)<<1) | (((bm0>>x)&1)<<2); if (0 != pen) { + CHIPS_ASSERT((ptr >= &sys->fb[0]) && (ptr < &sys->fb[BOMBJACK_FRAMEBUFFER_WIDTH*BOMBJACK_FRAMEBUFFER_HEIGHT])); *ptr = sys->mainboard.palette[color_block | pen]; } ptr++; @@ -962,6 +928,7 @@ static void _bombjack_decode_sprites(bombjack_t* sys) { for (int x=15; x>=0; x--) { uint8_t pen = ((bm2>>x)&1) | (((bm1>>x)&1)<<1) | (((bm0>>x)&1)<<2); if (0 != pen) { + CHIPS_ASSERT((ptr >= &sys->fb[0]) && (ptr < &sys->fb[BOMBJACK_FRAMEBUFFER_WIDTH*BOMBJACK_FRAMEBUFFER_HEIGHT])); *ptr = sys->mainboard.palette[color_block | pen]; } ptr++; @@ -974,23 +941,21 @@ static void _bombjack_decode_sprites(bombjack_t* sys) { } static void _bombjack_decode_video(bombjack_t* sys) { - if (sys->pixel_buffer) { - if (sys->dbg.draw_background_layer) { - _bombjack_decode_background(sys); - } - else { - if (sys->dbg.clear_background_layer) { - for (int i = 0; i < _BOMBJACK_DISPLAY_WIDTH*_BOMBJACK_DISPLAY_HEIGHT; i++) { - sys->pixel_buffer[i] = 0xFF000000; - } + if (sys->dbg.draw_background_layer) { + _bombjack_decode_background(sys); + } + else { + if (sys->dbg.clear_background_layer) { + for (size_t i = 0; i < BOMBJACK_FRAMEBUFFER_WIDTH*BOMBJACK_DISPLAY_HEIGHT; i++) { + sys->fb[i] = 0xFF000000; } } - if (sys->dbg.draw_foreground_layer) { - _bombjack_decode_foreground(sys); - } - if (sys->dbg.draw_sprite_layer) { - _bombjack_decode_sprites(sys); - } + } + if (sys->dbg.draw_foreground_layer) { + _bombjack_decode_foreground(sys); + } + if (sys->dbg.draw_sprite_layer) { + _bombjack_decode_sprites(sys); } } @@ -1016,7 +981,7 @@ uint32_t bombjack_exec(bombjack_t* sys, uint32_t micro_seconds) { const uint32_t slice_us = micro_seconds/2; const uint32_t mb_num_ticks = clk_us_to_ticks(_BOMBJACK_MAINBOARD_FREQUENCY, slice_us); const uint32_t sb_num_ticks = clk_us_to_ticks(_BOMBJACK_SOUNDBOARD_FREQUENCY, slice_us); - for (int i = 0; i < 2; i++) { + for (size_t i = 0; i < 2; i++) { // tick the main board for one half frame { uint64_t pins = sys->mainboard.pins; @@ -1027,7 +992,7 @@ uint32_t bombjack_exec(bombjack_t* sys, uint32_t micro_seconds) { } } else { - // run without debug callback + // run with debug callback for (uint32_t tick = 0; (tick < mb_num_ticks) && !(*sys->dbg.debug.mainboard.stopped); tick++) { pins = _bombjack_tick_mainboard(sys, pins); sys->dbg.debug.mainboard.callback.func(sys->dbg.debug.mainboard.callback.user_data, pins); @@ -1058,26 +1023,62 @@ uint32_t bombjack_exec(bombjack_t* sys, uint32_t micro_seconds) { return 2 * (mb_num_ticks + sb_num_ticks); } -int bombjack_std_display_width(void) { - return _BOMBJACK_DISPLAY_WIDTH; -} - -int bombjack_std_display_height(void) { - return _BOMBJACK_DISPLAY_HEIGHT; -} - -int bombjack_display_size(void) { - return _BOMBJACK_DISPLAY_SIZE; +chips_display_info_t bombjack_display_info(bombjack_t* sys) { + const chips_display_info_t res = { + .frame = { + .dim = { + .width = BOMBJACK_FRAMEBUFFER_WIDTH, + .height = BOMBJACK_FRAMEBUFFER_HEIGHT, + }, + .bytes_per_pixel = 4, + .buffer = { + .ptr = sys ? sys->fb : 0, + .size = BOMBJACK_FRAMEBUFFER_SIZE_BYTES, + } + }, + .screen = { + .x = 0, + .y = 0, + .width = BOMBJACK_DISPLAY_WIDTH, + .height = BOMBJACK_DISPLAY_HEIGHT, + }, + .portrait = true + }; + CHIPS_ASSERT(((sys == 0) && (res.frame.buffer.ptr == 0)) || ((sys != 0) && (res.frame.buffer.ptr != 0))); + return res; } -int bombjack_display_width(bombjack_t* sys) { - (void)sys; - return _BOMBJACK_DISPLAY_WIDTH; +uint32_t bombjack_save_snapshot(bombjack_t* sys, bombjack_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->dbg.debug.mainboard); + chips_debug_snapshot_onsave(&dst->dbg.debug.soundboard); + chips_audio_callback_snapshot_onsave(&dst->audio.callback); + for (size_t i = 0; i < 3; i++) { + ay38910_snapshot_onsave(&dst->soundboard.psg[i]); + } + mem_snapshot_onsave(&dst->mainboard.mem, sys); + mem_snapshot_onsave(&dst->soundboard.mem, sys); + return BOMBJACK_SNAPSHOT_VERSION; } -int bombjack_display_height(bombjack_t* sys) { - (void)sys; - return _BOMBJACK_DISPLAY_HEIGHT; +bool bombjack_load_snapshot(bombjack_t* sys, uint32_t version, bombjack_t* src) { + CHIPS_ASSERT(sys && src); + if (version != BOMBJACK_SNAPSHOT_VERSION) { + return false; + } + static bombjack_t im; + im = *src; + chips_debug_snapshot_onload(&im.dbg.debug.mainboard, &sys->dbg.debug.mainboard); + chips_debug_snapshot_onload(&im.dbg.debug.soundboard, &sys->dbg.debug.soundboard); + chips_audio_callback_snapshot_onload(&im.audio.callback, &sys->audio.callback); + for (size_t i = 0; i < 3; i++) { + ay38910_snapshot_onload(&im.soundboard.psg[i], &sys->soundboard.psg[i]); + } + mem_snapshot_onload(&im.mainboard.mem, sys); + mem_snapshot_onload(&im.soundboard.mem, sys); + *sys = im; + return true; } #endif // CHIPS_IMPL diff --git a/systems/c1530.h b/systems/c1530.h index be2dd5a3..897f36fd 100644 --- a/systems/c1530.h +++ b/systems/c1530.h @@ -8,11 +8,10 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - ~~~C CHIPS_ASSERT(c) ~~~ @@ -64,7 +63,7 @@ void c1530_stop(c1530_t* sys); bool c1530_is_motor_on(c1530_t* sys); ~~~ - + The motor may also be switched on/off by the computer system through the cassette port's MOTOR pin. @@ -84,10 +83,11 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include +#include #ifdef __cplusplus extern "C" { @@ -127,7 +127,7 @@ void c1530_reset(c1530_t* sys); /* tick the tape drive */ void c1530_tick(c1530_t* sys); /* insert a tape file */ -bool c1530_insert_tape(c1530_t* sys, const uint8_t* ptr, int num_bytes); +bool c1530_insert_tape(c1530_t* sys, chips_range_t data); /* remove tape file */ void c1530_remove_tape(c1530_t* sys); /* return true if a tape is currently inserted */ @@ -138,6 +138,10 @@ void c1530_play(c1530_t* sys); void c1530_stop(c1530_t* sys); /* return true if tape motor is on */ bool c1530_is_motor_on(c1530_t* sys); +// prepare c1530_t snapshot for saving +void c1530_snapshot_onsave(c1530_t* snapshot); +// fixup c1530_t snapshot after loading +void c1530_snapshot_onload(c1530_t* snapshot, c1530_t* sys); #ifdef __cplusplus } /* extern "C" */ @@ -180,16 +184,17 @@ typedef struct { uint32_t size; /* size of the following data */ } _c1530_tap_header; -bool c1530_insert_tape(c1530_t* sys, const uint8_t* ptr, int num_bytes) { - CHIPS_ASSERT(sys && sys->valid && ptr); +bool c1530_insert_tape(c1530_t* sys, chips_range_t data) { + CHIPS_ASSERT(sys && sys->valid && data.ptr && (data.size > 0)); c1530_remove_tape(sys); - if (num_bytes <= (int) sizeof(_c1530_tap_header)) { + if (data.size <= sizeof(_c1530_tap_header)) { return false; } + const uint8_t* ptr = (uint8_t*) data.ptr; const _c1530_tap_header* hdr = (const _c1530_tap_header*) ptr; ptr += sizeof(_c1530_tap_header); const uint8_t sig[12] = { 'C','6','4','-','T','A','P','E','-','R','A','W'}; - for (int i = 0; i < 12; i++) { + for (size_t i = 0; i < 12; i++) { if (sig[i] != hdr->signature[i]) { return false; } @@ -197,10 +202,10 @@ bool c1530_insert_tape(c1530_t* sys, const uint8_t* ptr, int num_bytes) { if (1 != hdr->version) { return false; } - if (num_bytes < (int)(hdr->size + sizeof(_c1530_tap_header))) { + if (data.size < (hdr->size + sizeof(_c1530_tap_header))) { return false; } - if (num_bytes > (int)sizeof(sys->buf)) { + if (data.size > sizeof(sys->buf)) { return false; } memcpy(sys->buf, ptr, hdr->size); @@ -264,4 +269,14 @@ void c1530_tick(c1530_t* sys) { } } +void c1530_snapshot_onsave(c1530_t* snapshot) { + CHIPS_ASSERT(snapshot); + snapshot->cas_port = 0; +} + +void c1530_snapshot_onload(c1530_t* snapshot, c1530_t* sys) { + CHIPS_ASSERT(snapshot && sys); + snapshot->cas_port = sys->cas_port; +} + #endif /* CHIPS_IMPL */ diff --git a/systems/c1541.h b/systems/c1541.h index 7f556062..dee53c31 100644 --- a/systems/c1541.h +++ b/systems/c1541.h @@ -8,7 +8,7 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation @@ -20,6 +20,7 @@ You need to include the following headers before including c64.h: + - chips/chips_common.h - chips/m6502.h - chips/m6522.h - chips/mem.h @@ -40,7 +41,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -59,19 +60,14 @@ extern "C" { #define C1541_FREQUENCY (1000000) -typedef struct { - void* ptr; - size_t size; -} c1541_rom_image_t; - // config params for c1541_init() typedef struct { // pointer to a shared byte with IEC serial bus line state uint8_t* iec_port; // rom images struct { - c1541_rom_image_t c000_dfff; - c1541_rom_image_t e000_ffff; + chips_range_t c000_dfff; + chips_range_t e000_ffff; } roms; } c1541_desc_t; @@ -97,9 +93,13 @@ void c1541_reset(c1541_t* sys); // tick a c1541_t instance forward void c1541_tick(c1541_t* sys); // insert a disc image file (.d64) -void c1541_insert_disc(c1541_t* sys, const uint8_t* ptr, int num_bytes); +void c1541_insert_disc(c1541_t* sys, chips_range_t data); // remove current disc void c1541_remove_disc(c1541_t* sys); +// prepare a c1541_t snapshot for saving +void c1541_snapshot_onsave(c1541_t* snapshot, void* base); +// prepare a c1541_t snapshot for loading +void c1541_snapshot_onload(c1541_t* snapshot, c1541_t* sys, void* base); #ifdef __cplusplus } // extern "C" @@ -166,11 +166,10 @@ void c1541_tick(c1541_t* sys) { sys->pins = pins; } -void c1541_insert_disc(c1541_t* sys, const uint8_t* ptr, int num_bytes) { +void c1541_insert_disc(c1541_t* sys, chips_range_t data) { // FIXME (void)sys; - (void)ptr; - (void)num_bytes; + (void)data; } void c1541_remove_disc(c1541_t* sys) { @@ -178,4 +177,18 @@ void c1541_remove_disc(c1541_t* sys) { (void)sys; } +void c1541_snapshot_onsave(c1541_t* snapshot, void* base) { + CHIPS_ASSERT(snapshot && base); + snapshot->iec = 0; + m6502_snapshot_onsave(&snapshot->cpu); + mem_snapshot_onsave(&snapshot->mem, base); +} + +void c1541_snapshot_onload(c1541_t* snapshot, c1541_t* sys, void* base) { + CHIPS_ASSERT(snapshot && sys && base); + snapshot->iec = sys->iec; + m6502_snapshot_onload(&snapshot->cpu, &sys->cpu); + mem_snapshot_onload(&snapshot->mem, base); +} + #endif // CHIPS_IMPL diff --git a/systems/c64.h b/systems/c64.h index 0ab7fac0..15e189d5 100644 --- a/systems/c64.h +++ b/systems/c64.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -20,6 +20,7 @@ You need to include the following headers before including c64.h: + - chips/chips_common.h - chips/m6502.h - chips/m6526.h - chips/m6569.h @@ -40,7 +41,7 @@ - floppy disc support ## Tests Status - + In chips-test/tests/testsuite-2.15/bin branchwrap: ok @@ -70,7 +71,7 @@ trap1..17: ok In chips-test/tests/vice-tests/CIA: - + ciavarious: - all green, expect cia15.prg, which tests the CIA TOD clock, which isn't implemented @@ -206,7 +207,7 @@ phi1timing: FAIL rasterirq: FAIL (reference image doesn't match) - + screenpos: FAIL (reference image doesn't match) split-tests: @@ -239,16 +240,20 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include #include +#include #ifdef __cplusplus extern "C" { #endif +// bump snapshot version when c64_t memory layout changes +#define C64_SNAPSHOT_VERSION (1) + #define C64_FREQUENCY (985248) // clock frequency in Hz #define C64_MAX_AUDIO_SAMPLES (1024) // max number of audio samples in internal sample buffer #define C64_DEFAULT_AUDIO_SAMPLES (128) // default number of samples in internal sample buffer @@ -314,57 +319,22 @@ typedef enum { #define C64_KEY_F7 (0xF7) // F7 #define C64_KEY_F8 (0xF8) // F8 -// audio sample callback -typedef struct { - void (*func)(const float* samples, int num_samples, void* user_data); - void* user_data; -} c64_audio_callback_t; - -// debugging hook definitions -typedef void (*c64_debug_func_t)(void* user_data, uint64_t pins); -typedef struct { - struct { - c64_debug_func_t func; - void* user_data; - } callback; - bool* stopped; -} c64_debug_t; - -typedef struct { - const void* ptr; - size_t size; -} c64_rom_image_t; - // config parameters for c64_init() typedef struct { bool c1530_enabled; // true to enable the C1530 datassette emulation bool c1541_enabled; // true to enable the C1541 floppy drive emulation c64_joystick_type_t joystick_type; // default is C64_JOYSTICK_NONE - c64_debug_t debug; // optional debugging hook - - // video output config (if you don't want video decoding, set these to 0) - struct { - void* ptr; // pointer to a linear RGBA8 pixel buffer, at least 512*312*4 bytes, or ask c64_max_display_size() - size_t size; // size of pixel buffer in bytes - } pixel_buffer; - - // audio output config (to disable audio output, set audio.callback.func to zero) - struct { - c64_audio_callback_t callback; // called when audio_num_samples are ready - int num_samples; // output sample buffer size, default is C64_DEFAULT_AUDIO_SAMPLES - int sample_rate; // playback sample rate, default is 44100 - float volume; // audio volume: 0.0..1.0, default is 1.0 - } audio; - + chips_debug_t debug; // optional debugging hook + chips_audio_desc_t audio; // audio output options // ROM images struct { - c64_rom_image_t chars; // 4 KByte character ROM dump - c64_rom_image_t basic; // 8 KByte BASIC dump - c64_rom_image_t kernal; // 8 KByte KERNAL dump + chips_range_t chars; // 4 KByte character ROM dump + chips_range_t basic; // 8 KByte BASIC dump + chips_range_t kernal; // 8 KByte KERNAL dump // optional C1514 ROM images struct { - c1541_rom_image_t c000_dfff; - c1541_rom_image_t e000_ffff; + chips_range_t c000_dfff; + chips_range_t e000_ffff; } c1541; } roms; } c64_desc_t; @@ -393,11 +363,10 @@ typedef struct { mem_t mem_cpu; // CPU-visible memory mapping mem_t mem_vic; // VIC-visible memory mapping bool valid; - c64_debug_t debug; + chips_debug_t debug; - uint32_t* pixel_buffer; struct { - c64_audio_callback_t callback; + chips_audio_callback_t callback; int num_samples; int sample_pos; float sample_buffer[C64_MAX_AUDIO_SAMPLES]; @@ -408,6 +377,7 @@ typedef struct { uint8_t rom_char[0x1000]; // 4 KB character ROM image uint8_t rom_basic[0x2000]; // 8 KB BASIC ROM image uint8_t rom_kernal[0x2000]; // 8 KB KERNAL V3 ROM image + alignas(64) uint8_t fb[M6569_FRAMEBUFFER_SIZE_BYTES]; c1530_t c1530; // optional datassette c1541_t c1541; // optional floppy drive @@ -419,6 +389,8 @@ void c64_init(c64_t* sys, const c64_desc_t* desc); void c64_discard(c64_t* sys); // reset a C64 instance void c64_reset(c64_t* sys); +// get framebuffer and display attributes +chips_display_info_t c64_display_info(c64_t* sys); // tick C64 instance for a given number of microseconds, return number of ticks executed uint32_t c64_exec(c64_t* sys, uint32_t micro_seconds); // send a key-down event to the C64 @@ -432,9 +404,9 @@ c64_joystick_type_t c64_joystick_type(c64_t* sys); // set joystick mask (combination of C64_JOYSTICK_*) void c64_joystick(c64_t* sys, uint8_t joy1_mask, uint8_t joy2_mask); // quickload a .bin/.prg file -bool c64_quickload(c64_t* sys, const uint8_t* ptr, int num_bytes); +bool c64_quickload(c64_t* sys, chips_range_t data); // insert tape as .TAP file (c1530 must be enabled) -bool c64_insert_tape(c64_t* sys, const uint8_t* ptr, int num_bytes); +bool c64_insert_tape(c64_t* sys, chips_range_t data); // remove tape file void c64_remove_tape(c64_t* sys); // return true if a tape is currently inserted @@ -445,14 +417,18 @@ void c64_tape_play(c64_t* sys); void c64_tape_stop(c64_t* sys); // return true if tape motor is on bool c64_is_tape_motor_on(c64_t* sys); -// get the standard framebuffer width and height in pixels -int c64_std_display_width(void); -int c64_std_display_height(void); -// get the maximum framebuffer size in number of bytes -size_t c64_max_display_size(void); -// get the current framebuffer width and height in pixels -int c64_display_width(c64_t* sys); -int c64_display_height(c64_t* sys); +// save a snapshot, patches pointers to zero and offsets, returns snapshot version +uint32_t c64_save_snapshot(c64_t* sys, c64_t* dst); +// load a snapshot, returns false if snapshot versions don't match +bool c64_load_snapshot(c64_t* sys, uint32_t version, c64_t* src); +// perform a RUN BASIC call +void c64_basic_run(c64_t* sys); +// perform a LOAD BASIC call +void c64_basic_load(c64_t* sys); +// perform a SYS xxxx BASIC call via the BASIC input buffer +void c64_basic_syscall(c64_t* sys, uint16_t addr); +// returns the SYS call return address (can be used to set a breakpoint) +uint16_t c64_syscall_return_addr(void); #ifdef __cplusplus } // extern "C" @@ -466,13 +442,10 @@ int c64_display_height(c64_t* sys); #define CHIPS_ASSERT(c) assert(c) #endif -#define _C64_STD_DISPLAY_WIDTH (392) -#define _C64_STD_DISPLAY_HEIGHT (272) -#define _C64_DBG_DISPLAY_WIDTH ((_M6569_HTOTAL+1)*8) -#define _C64_DBG_DISPLAY_HEIGHT (_M6569_VTOTAL+1) -#define _C64_DISPLAY_SIZE (_C64_DBG_DISPLAY_WIDTH*_C64_DBG_DISPLAY_HEIGHT*4) -#define _C64_DISPLAY_X (64) -#define _C64_DISPLAY_Y (24) +#define _C64_SCREEN_WIDTH (392) +#define _C64_SCREEN_HEIGHT (272) +#define _C64_SCREEN_X (64) +#define _C64_SCREEN_Y (24) static uint8_t _c64_cpu_port_in(void* user_data); static void _c64_cpu_port_out(uint8_t data, void* user_data); @@ -485,12 +458,7 @@ static void _c64_init_memory_map(c64_t* sys); void c64_init(c64_t* sys, const c64_desc_t* desc) { CHIPS_ASSERT(sys && desc); - if (desc->pixel_buffer.ptr) { - CHIPS_ASSERT(desc->pixel_buffer.size >= c64_max_display_size()); - } - if (desc->debug.callback.func) { - CHIPS_ASSERT(desc->debug.stopped); - } + if (desc->debug.callback.func) { CHIPS_ASSERT(desc->debug.stopped); } memset(sys, 0, sizeof(c64_t)); sys->valid = true; @@ -522,12 +490,16 @@ void c64_init(c64_t* sys, const c64_desc_t* desc) { m6526_init(&sys->cia_2); m6569_init(&sys->vic, &(m6569_desc_t){ .fetch_cb = _c64_vic_fetch, - .rgba8_buffer = (uint32_t*) desc->pixel_buffer.ptr, - .rgba8_buffer_size = desc->pixel_buffer.size, - .vis_x = _C64_DISPLAY_X, - .vis_y = _C64_DISPLAY_Y, - .vis_w = _C64_STD_DISPLAY_WIDTH, - .vis_h = _C64_STD_DISPLAY_HEIGHT, + .framebuffer = { + .ptr = sys->fb, + .size = sizeof(sys->fb), + }, + .screen = { + .x = _C64_SCREEN_X, + .y = _C64_SCREEN_Y, + .width = _C64_SCREEN_WIDTH, + .height = _C64_SCREEN_HEIGHT, + }, .user_data = sys, }); m6581_init(&sys->sid, &(m6581_desc_t){ @@ -881,12 +853,12 @@ static void _c64_init_memory_map(c64_t* sys) { this is important at least for the value of the 'ghost byte' at 0x3FFF, which is 0xFF */ - int i; + size_t i; for (i = 0; i < (1<<16);) { - for (int j = 0; j < 64; j++, i++) { + for (size_t j = 0; j < 64; j++, i++) { sys->ram[i] = 0x00; } - for (int j = 0; j < 64; j++, i++) { + for (size_t j = 0; j < 64; j++, i++) { sys->ram[i] = 0xFF; } } @@ -1082,14 +1054,15 @@ void c64_joystick(c64_t* sys, uint8_t joy1_mask, uint8_t joy2_mask) { sys->joy_joy2_mask = joy2_mask; } -bool c64_quickload(c64_t* sys, const uint8_t* ptr, int num_bytes) { - CHIPS_ASSERT(sys && sys->valid); - if (num_bytes < 2) { +bool c64_quickload(c64_t* sys, chips_range_t data) { + CHIPS_ASSERT(sys && sys->valid && data.ptr); + if (data.size < 2) { return false; } + const uint8_t* ptr = (uint8_t*)data.ptr; const uint16_t start_addr = ptr[1]<<8 | ptr[0]; ptr += 2; - const uint16_t end_addr = start_addr + (num_bytes - 2); + const uint16_t end_addr = start_addr + (data.size - 2); uint16_t addr = start_addr; while (addr < end_addr) { mem_wr(&sys->mem_cpu, addr++, *ptr++); @@ -1105,9 +1078,9 @@ bool c64_quickload(c64_t* sys, const uint8_t* ptr, int num_bytes) { return true; } -bool c64_insert_tape(c64_t* sys, const uint8_t* ptr, int num_bytes) { +bool c64_insert_tape(c64_t* sys, chips_range_t data) { CHIPS_ASSERT(sys && sys->valid && sys->c1530.valid); - return c1530_insert_tape(&sys->c1530, ptr, num_bytes); + return c1530_insert_tape(&sys->c1530, data); } void c64_remove_tape(c64_t* sys) { @@ -1135,26 +1108,112 @@ bool c64_is_tape_motor_on(c64_t* sys) { return c1530_is_motor_on(&sys->c1530); } -int c64_std_display_width(void) { - return _C64_STD_DISPLAY_WIDTH; +chips_display_info_t c64_display_info(c64_t* sys) { + chips_display_info_t res = { + .frame = { + .dim = { + .width = M6569_FRAMEBUFFER_WIDTH, + .height = M6569_FRAMEBUFFER_HEIGHT, + }, + .bytes_per_pixel = 1, + .buffer = { + .ptr = sys ? sys->fb : 0, + .size = M6569_FRAMEBUFFER_SIZE_BYTES, + } + }, + .palette = m6569_dbg_palette(), + }; + if (sys) { + res.screen = m6569_screen(&sys->vic); + } + else { + res.screen = (chips_rect_t){ + .x = 0, + .y = 0, + .width = _C64_SCREEN_WIDTH, + .height = _C64_SCREEN_HEIGHT + }; + }; + CHIPS_ASSERT(((sys == 0) && (res.frame.buffer.ptr == 0)) || ((sys != 0) && (res.frame.buffer.ptr != 0))); + return res; +} + +uint32_t c64_save_snapshot(c64_t* sys, c64_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->debug); + chips_audio_callback_snapshot_onsave(&dst->audio.callback); + m6502_snapshot_onsave(&dst->cpu); + m6569_snapshot_onsave(&dst->vic); + mem_snapshot_onsave(&dst->mem_cpu, sys); + mem_snapshot_onsave(&dst->mem_vic, sys); + c1530_snapshot_onsave(&dst->c1530); + c1541_snapshot_onsave(&dst->c1541, sys); + return C64_SNAPSHOT_VERSION; } -int c64_std_display_height(void) { - return _C64_STD_DISPLAY_HEIGHT; +bool c64_load_snapshot(c64_t* sys, uint32_t version, c64_t* src) { + CHIPS_ASSERT(sys && src); + if (version != C64_SNAPSHOT_VERSION) { + return false; + } + static c64_t im; + im = *src; + chips_debug_snapshot_onload(&im.debug, &sys->debug); + chips_audio_callback_snapshot_onload(&im.audio.callback, &sys->audio.callback); + m6502_snapshot_onload(&im.cpu, &sys->cpu); + m6569_snapshot_onload(&im.vic, &sys->vic); + mem_snapshot_onload(&im.mem_cpu, sys); + mem_snapshot_onload(&im.mem_vic, sys); + c1530_snapshot_onload(&im.c1530, &sys->c1530); + c1541_snapshot_onload(&im.c1541, &sys->c1541, sys); + *sys = im; + return true; } -size_t c64_max_display_size(void) { - return _C64_DISPLAY_SIZE; +void c64_basic_run(c64_t* sys) { + CHIPS_ASSERT(sys); + // write RUN into the keyboard buffer + uint16_t keybuf = 0x277; + mem_wr(&sys->mem_cpu, keybuf++, 'R'); + mem_wr(&sys->mem_cpu, keybuf++, 'U'); + mem_wr(&sys->mem_cpu, keybuf++, 'N'); + mem_wr(&sys->mem_cpu, keybuf++, 0x0D); + // write number of characters, this kicks off evaluation + mem_wr(&sys->mem_cpu, 0xC6, 4); } -int c64_display_width(c64_t* sys) { - CHIPS_ASSERT(sys && sys->valid); - return m6569_display_width(&sys->vic); +void c64_basic_load(c64_t* sys) { + CHIPS_ASSERT(sys); + // write LOAD + uint16_t keybuf = 0x277; + mem_wr(&sys->mem_cpu, keybuf++, 'L'); + mem_wr(&sys->mem_cpu, keybuf++, 'O'); + mem_wr(&sys->mem_cpu, keybuf++, 'A'); + mem_wr(&sys->mem_cpu, keybuf++, 'D'); + mem_wr(&sys->mem_cpu, keybuf++, 0x0D); + // write number of characters, this kicks off evaluation + mem_wr(&sys->mem_cpu, 0xC6, 5); } -int c64_display_height(c64_t* sys) { - CHIPS_ASSERT(sys && sys->valid); - return m6569_display_height(&sys->vic); +void c64_basic_syscall(c64_t* sys, uint16_t addr) { + CHIPS_ASSERT(sys); + // write SYS xxxx[Return] into the keyboard buffer (up to 10 chars) + uint16_t keybuf = 0x277; + mem_wr(&sys->mem_cpu, keybuf++, 'S'); + mem_wr(&sys->mem_cpu, keybuf++, 'Y'); + mem_wr(&sys->mem_cpu, keybuf++, 'S'); + mem_wr(&sys->mem_cpu, keybuf++, ((addr / 10000) % 10) + '0'); + mem_wr(&sys->mem_cpu, keybuf++, ((addr / 1000) % 10) + '0'); + mem_wr(&sys->mem_cpu, keybuf++, ((addr / 100) % 10) + '0'); + mem_wr(&sys->mem_cpu, keybuf++, ((addr / 10) % 10) + '0'); + mem_wr(&sys->mem_cpu, keybuf++, ((addr / 1) % 10) + '0'); + mem_wr(&sys->mem_cpu, keybuf++, 0x0D); + // write number of characters, this kicks off evaluation + mem_wr(&sys->mem_cpu, 0xC6, 9); } +uint16_t c64_syscall_return_addr(void) { + return 0xA7EA; +} #endif /* CHIPS_IMPL */ diff --git a/systems/cpc.h b/systems/cpc.h index 026e4ce0..efe13916 100644 --- a/systems/cpc.h +++ b/systems/cpc.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -20,6 +20,7 @@ You need to include the following headers before including cpc.h: + - chips/chips_common.h - chips/z80.h - chips/ay38910.h - chips/i8255.h @@ -65,16 +66,20 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include #include +#include #ifdef __cplusplus extern "C" { #endif +// bump when cpc_t memory layout changes +#define CPC_SNAPSHOT_VERSION (0x0001) + #define CPC_MAX_AUDIO_SAMPLES (1024) // max number of audio samples in internal sample buffer #define CPC_DEFAULT_AUDIO_SAMPLES (128) // default number of samples in internal sample buffer #define CPC_MAX_TAPE_SIZE (128*1024) // max size of tape file in bytes @@ -101,64 +106,30 @@ typedef enum { #define CPC_JOYSTICK_BTN0 (1<<4) #define CPC_JOYSTICK_BTN1 (1<<4) -// audio sample callback -typedef struct { - void (*func)(const float* samples, int num_samples, void* user_data); - void* user_data; -} cpc_audio_callback_t; - -// debugging hook -typedef void (*cpc_debug_func_t)(void* user_data, uint64_t pins); -typedef struct { - struct { - cpc_debug_func_t func; - void* user_data; - } callback; - bool* stopped; -} cpc_debug_t; - -typedef struct { - const void* ptr; - size_t size; -} cpc_rom_image_t; - // configuration parameters for cpc_init() typedef struct { cpc_type_t type; // default is the CPC 6128 cpc_joystick_type_t joystick_type; - cpc_debug_t debug; - - // video output config - struct { - void* ptr; // pointer to a linear RGBA8 pixel buffer, at least 1024*312*4 bytes - size_t size; // size of the pixel buffer in bytes - } pixel_buffer; - - // audio output config (if you don't want audio, set callback.func to zero) - struct { - cpc_audio_callback_t callback; // called when audio_num_samples are ready */ - int num_samples; // default is CPC_AUDIO_NUM_SAMPLES - int sample_rate; // playback sample rate, default is 44100 - float volume; // audio volume: 0.0..1.0, default is 0.25 - } audio; + chips_debug_t debug; + chips_audio_desc_t audio; // ROM images struct { // CPC 464 struct { - cpc_rom_image_t os; - cpc_rom_image_t basic; + chips_range_t os; + chips_range_t basic; } cpc464; // CPC 6128 struct { - cpc_rom_image_t os; - cpc_rom_image_t basic; - cpc_rom_image_t amsdos; + chips_range_t os; + chips_range_t basic; + chips_range_t amsdos; } cpc6128; // KC Compact struct { - cpc_rom_image_t os; - cpc_rom_image_t basic; + chips_range_t os; + chips_range_t basic; } kcc; } roms; } cpc_desc_t; @@ -168,26 +139,24 @@ typedef struct { z80_t cpu; ay38910_t psg; mc6845_t crtc; - am40010_t ga; i8255_t ppi; upd765_t fdc; + am40010_t ga; cpc_type_t type; cpc_joystick_type_t joystick_type; uint8_t kbd_joymask; uint8_t joy_joymask; - uint16_t casread_trap; - uint16_t casread_ret; kbd_t kbd; mem_t mem; uint64_t pins; bool valid; - cpc_debug_t debug; + chips_debug_t debug; struct { - cpc_audio_callback_t callback; + chips_audio_callback_t callback; int num_samples; int sample_pos; float sample_buffer[CPC_MAX_AUDIO_SAMPLES]; @@ -196,15 +165,7 @@ typedef struct { uint8_t rom_os[0x4000]; uint8_t rom_basic[0x4000]; uint8_t rom_amsdos[0x4000]; - // tape loading (FIXME: currently not implemented) - /* - struct { - int size; // tape_size is > 0 if a tape is inserted - int pos; - uint8_t buf[CPC_MAX_TAPE_SIZE]; - } tape; - */ - // floppy disc drive + alignas(64) uint8_t fb[AM40010_FRAMEBUFFER_SIZE_BYTES]; fdd_t fdd; } cpc_t; @@ -214,6 +175,8 @@ void cpc_init(cpc_t* cpc, const cpc_desc_t* desc); void cpc_discard(cpc_t* cpc); // reset a CPC instance void cpc_reset(cpc_t* cpc); +// get display requirements and framebuffer content, may be called with nullptr +chips_display_info_t cpc_display_info(cpc_t* cpc); // run CPC instance for given amount of micro_seconds, returns number of ticks executed uint32_t cpc_exec(cpc_t* cpc, uint32_t micro_seconds); // send a key down event @@ -229,13 +192,13 @@ void cpc_joystick(cpc_t* sys, uint8_t mask); // get current joystick bitmask state uint8_t cpc_joystick_mask(cpc_t* sys); // load a snapshot file (.sna or .bin) into the emulator -bool cpc_quickload(cpc_t* cpc, const uint8_t* ptr, int num_bytes); -// insert a tape file (.tap) (FIXME: currently not implemented) -//bool cpc_insert_tape(cpc_t* cpc, const uint8_t* ptr, int num_bytes); -// remove currently inserted tape (FIXME: currently not implemented) -//void cpc_remove_tape(cpc_t* cpc); +bool cpc_quickload(cpc_t* cpc, chips_range_t data, bool start); +// return the exec address of a quickload file (.sna or .bin) +uint16_t cpc_quickload_exec_addr(chips_range_t data); +// return the return-address for a quickloaded file +uint16_t cpc_quickload_return_addr(cpc_t* cpc); // insert a disk image file (.dsk) -bool cpc_insert_disc(cpc_t* cpc, const uint8_t* ptr, int num_bytes); +bool cpc_insert_disc(cpc_t* cpc, chips_range_t data); // remove current disc void cpc_remove_disc(cpc_t* cpc); // return true if a floppy disc is currently inserted @@ -244,14 +207,10 @@ bool cpc_disc_inserted(cpc_t* cpc); void cpc_enable_video_debugging(cpc_t* cpc, bool enabled); // get current display debug visualization enabled/disabled state bool cpc_video_debugging_enabled(cpc_t* cpc); -// get the standard framebuffer width and height in pixels -int cpc_std_display_width(void); -int cpc_std_display_height(void); -// get the maximum framebuffer size in number of bytes -size_t cpc_max_display_size(void); -// get the current framebuffer width and height in pixels -int cpc_display_width(cpc_t* sys); -int cpc_display_height(cpc_t* sys); +// take a snapshot, patches any pointers to zero, returns snapshot version +uint32_t cpc_save_snapshot(cpc_t* sys, cpc_t* dst); +// load a snapshot, returns false if snapshot version doesn't match +bool cpc_load_snapshot(cpc_t* sys, uint32_t version, cpc_t* src); #ifdef __cplusplus } // extern "C" @@ -282,7 +241,6 @@ static void _cpc_fdc_driveinfo(int drive, void* user_data, upd765_driveinfo_t* o void cpc_init(cpc_t* sys, const cpc_desc_t* desc) { CHIPS_ASSERT(sys && desc); - CHIPS_ASSERT((0 == desc->pixel_buffer.ptr) || (desc->pixel_buffer.ptr && (desc->pixel_buffer.size >= cpc_max_display_size()))); if (desc->debug.callback.func) { CHIPS_ASSERT(desc->debug.stopped); } memset(sys, 0, sizeof(cpc_t)); @@ -298,16 +256,14 @@ void cpc_init(cpc_t* sys, const cpc_desc_t* desc) { CHIPS_ASSERT(desc->roms.cpc464.basic.ptr && (desc->roms.cpc464.basic.size == 0x4000)); memcpy(sys->rom_os, desc->roms.cpc464.os.ptr, 0x4000); memcpy(sys->rom_basic, desc->roms.cpc464.basic.ptr, 0x4000); - } - else if (CPC_TYPE_6128 == desc->type) { + } else if (CPC_TYPE_6128 == desc->type) { CHIPS_ASSERT(desc->roms.cpc6128.os.ptr && (desc->roms.cpc6128.os.size == 0x4000)); CHIPS_ASSERT(desc->roms.cpc6128.basic.ptr && (desc->roms.cpc6128.basic.size == 0x4000)); CHIPS_ASSERT(desc->roms.cpc6128.amsdos.ptr && (desc->roms.cpc6128.amsdos.size == 0x4000)); memcpy(sys->rom_os, desc->roms.cpc6128.os.ptr, 0x4000); memcpy(sys->rom_basic, desc->roms.cpc6128.basic.ptr, 0x4000); memcpy(sys->rom_amsdos, desc->roms.cpc6128.amsdos.ptr, 0x4000); - } - else { // KC Compact + } else { // KC Compact CHIPS_ASSERT(desc->roms.kcc.os.ptr && (desc->roms.kcc.os.size == 0x4000)); CHIPS_ASSERT(desc->roms.kcc.basic.ptr && (desc->roms.kcc.basic.size == 0x4000)); memcpy(sys->rom_os, desc->roms.kcc.os.ptr, 0x4000); @@ -332,10 +288,14 @@ void cpc_init(cpc_t* sys, const cpc_desc_t* desc) { .cpc_type = (am40010_cpc_type_t) sys->type, .bankswitch_cb = _cpc_bankswitch, .cclk_cb = _cpc_cclk, - .ram = &sys->ram[0][0], - .ram_size = sizeof(sys->ram), - .rgba8_buffer = (uint32_t*) desc->pixel_buffer.ptr, - .rgba8_buffer_size = desc->pixel_buffer.size, + .ram = { + .ptr = &sys->ram[0][0], + .size = sizeof(sys->ram) + }, + .framebuffer = { + .ptr = &sys->fb[0], + .size = sizeof(sys->fb), + }, .user_data = sys, }); upd765_init(&sys->fdc, &(upd765_desc_t){ @@ -349,18 +309,6 @@ void cpc_init(cpc_t* sys, const cpc_desc_t* desc) { fdd_init(&sys->fdd); _cpc_init_keymap(sys); - - /* cassette tape loading - (http://www.cpcwiki.eu/index.php/Format:TAP_tape_image_file_format) - */ - if (CPC_TYPE_464 == sys->type) { - sys->casread_trap = 0x2836; - sys->casread_ret = 0x2872; - } - else { - sys->casread_trap = 0x29A6; - sys->casread_ret = 0x29E2; - } } void cpc_discard(cpc_t* sys) { @@ -388,12 +336,10 @@ static uint64_t _cpc_tick(cpc_t* sys, uint64_t cpu_pins) { const uint16_t addr = Z80_GET_ADDR(cpu_pins); if (cpu_pins & Z80_RD) { Z80_SET_DATA(cpu_pins, mem_rd(&sys->mem, addr)); - } - else if (cpu_pins & Z80_WR) { + } else if (cpu_pins & Z80_WR) { mem_wr(&sys->mem, addr, Z80_GET_DATA(cpu_pins)); } - } - else if ((cpu_pins & (Z80_M1|Z80_IORQ)) == Z80_IORQ) { + } else if ((cpu_pins & (Z80_M1|Z80_IORQ)) == Z80_IORQ) { /* CPU IO address decoding For address decoding, see the main board schematics! @@ -506,8 +452,7 @@ static uint64_t _cpc_tick(cpc_t* sys, uint64_t cpu_pins) { if (cpu_pins & Z80_WR) { fdd_motor(&sys->fdd, 0 != (Z80_GET_DATA(cpu_pins) & 1)); } - } - else if ((cpu_pins & (Z80_A10|Z80_A8|Z80_A7)) == Z80_A8) { + } else if ((cpu_pins & (Z80_A10|Z80_A8|Z80_A7)) == Z80_A8) { // floppy controller status/data register uint64_t fdc_pins = UPD765_CS | (cpu_pins & Z80_PIN_MASK); cpu_pins = upd765_iorq(&sys->fdc, fdc_pins) & Z80_PIN_MASK; @@ -574,8 +519,7 @@ static uint8_t _cpc_psg_in(int port_id, void* user_data) { data |= (sys->kbd_joymask | sys->joy_joymask); } return ~data; - } - else { + } else { // this shouldn't happen since the AY-3-8912 only has one IO port return 0xFF; } @@ -603,8 +547,7 @@ static void _cpc_bankswitch(uint8_t ram_config, uint8_t rom_enable, uint8_t rom_ ram_config_index = ram_config & 7; rom0_ptr = sys->rom_os; rom1_ptr = (rom_select == 7) ? sys->rom_amsdos : sys->rom_basic; - } - else { + } else { ram_config_index = 0; rom0_ptr = sys->rom_os; rom1_ptr = sys->rom_basic; @@ -618,8 +561,7 @@ static void _cpc_bankswitch(uint8_t ram_config, uint8_t rom_enable, uint8_t rom_ if (rom_enable & AM40010_CONFIG_LROMEN) { // read/write RAM mem_map_ram(&sys->mem, 0, 0x0000, 0x4000, sys->ram[i0]); - } - else { + } else { // RAM-behind-ROM mem_map_rw(&sys->mem, 0, 0x0000, 0x4000, rom0_ptr, sys->ram[i0]); } @@ -631,8 +573,7 @@ static void _cpc_bankswitch(uint8_t ram_config, uint8_t rom_enable, uint8_t rom_ if (rom_enable & AM40010_CONFIG_HROMEN) { // read/write RAM mem_map_ram(&sys->mem, 0, 0xC000, 0x4000, sys->ram[i3]); - } - else { + } else { // RAM-behind-ROM mem_map_rw(&sys->mem, 0, 0xC000, 0x4000, rom1_ptr, sys->ram[i3]); } @@ -647,8 +588,7 @@ uint32_t cpc_exec(cpc_t* sys, uint32_t micro_seconds) { for (uint32_t tick = 0; tick < num_ticks; tick++) { pins = _cpc_tick(sys, pins); } - } - else { + } else { // run with debug hook for (uint32_t tick = 0; (tick < num_ticks) && !(*sys->debug.stopped); tick++) { pins = _cpc_tick(sys, pins); @@ -671,8 +611,7 @@ void cpc_key_down(cpc_t* sys, int key_code) { case 0x0B: sys->kbd_joymask |= CPC_JOYSTICK_UP; break; default: kbd_key_down(&sys->kbd, key_code); break; } - } - else { + } else { kbd_key_down(&sys->kbd, key_code); } } @@ -688,8 +627,7 @@ void cpc_key_up(cpc_t* sys, int key_code) { case 0x0B: sys->kbd_joymask &= ~CPC_JOYSTICK_UP; break; default: kbd_key_up(&sys->kbd, key_code); break; } - } - else { + } else { kbd_key_up(&sys->kbd, key_code); } } @@ -728,7 +666,7 @@ bool cpc_video_debugging_enabled(cpc_t* sys) { static void _cpc_init_keymap(cpc_t* sys) { /* http://cpctech.cpc-live.com/docs/keyboard.html - + CPC has a 10 columns by 8 lines keyboard matrix. The 10 columns are lit up by bits 0..3 of PPI port C connected to a 74LS145 BCD decoder, and the lines are read through port A of the @@ -826,13 +764,13 @@ typedef struct { uint8_t pad1[0x93]; } _cpc_sna_header; -static bool _cpc_is_valid_sna(const uint8_t* ptr, int num_bytes) { - if (num_bytes <= (int)sizeof(_cpc_sna_header)) { +static bool _cpc_is_valid_sna(chips_range_t data) { + if (data.size <= sizeof(_cpc_sna_header)) { return false; } - const _cpc_sna_header* hdr = (const _cpc_sna_header*) ptr; + const _cpc_sna_header* hdr = (const _cpc_sna_header*)data.ptr; static uint8_t magic[8] = { 'M', 'V', 0x20, '-', 0x20, 'S', 'N', 'A' }; - for (int i = 0; i < 8; i++) { + for (size_t i = 0; i < 8; i++) { if (magic[i] != hdr->magic[i]) { return false; } @@ -841,14 +779,15 @@ static bool _cpc_is_valid_sna(const uint8_t* ptr, int num_bytes) { return true; } -static bool _cpc_load_sna(cpc_t* sys, const uint8_t* ptr, int num_bytes) { +static bool _cpc_load_sna(cpc_t* sys, chips_range_t data) { + const uint8_t* ptr = (uint8_t*) data.ptr; const _cpc_sna_header* hdr = (const _cpc_sna_header*) ptr; ptr += sizeof(_cpc_sna_header); - + // copy 64 or 128 KByte memory dump const uint16_t dump_size = hdr->dump_size_h<<8 | hdr->dump_size_l; const uint32_t dump_num_bytes = (dump_size == 64) ? 0x10000 : 0x20000; - if (num_bytes > (int) (sizeof(_cpc_sna_header) + dump_num_bytes)) { + if (data.size > (sizeof(_cpc_sna_header) + dump_num_bytes)) { return false; } if (dump_num_bytes > sizeof(sys->ram)) { @@ -874,7 +813,6 @@ static bool _cpc_load_sna(cpc_t* sys, const uint8_t* ptr, int num_bytes) { sys->cpu.de2 = (hdr->D_<<8) | hdr->E_; sys->cpu.hl2 = (hdr->H_<<8) | hdr->L_; - sys->ga.colors.dirty = true; for (int i = 0; i < 16; i++) { sys->ga.regs.ink[i] = hdr->pens[i] & 0x1F; } @@ -921,14 +859,15 @@ typedef struct { uint8_t pad_5[0x60]; } _cpc_bin_header; -static bool _cpc_is_valid_bin(int num_bytes) { - if (num_bytes <= (int)sizeof(_cpc_bin_header)) { +static bool _cpc_is_valid_bin(chips_range_t data) { + if (data.size <= sizeof(_cpc_bin_header)) { return false; } return true; } -static bool _cpc_load_bin(cpc_t* sys, const uint8_t* ptr) { +static bool _cpc_load_bin(cpc_t* sys, chips_range_t data, bool start) { + const uint8_t* ptr = (uint8_t*) data.ptr; const _cpc_bin_header* hdr = (const _cpc_bin_header*) ptr; ptr += sizeof(_cpc_bin_header); const uint16_t load_addr = (hdr->load_addr_h<<8)|hdr->load_addr_l; @@ -937,96 +876,78 @@ static bool _cpc_load_bin(cpc_t* sys, const uint8_t* ptr) { for (uint16_t i = 0; i < len; i++) { mem_wr(&sys->mem, load_addr+i, *ptr++); } - sys->cpu.iff1 = true; - sys->cpu.iff2 = true; - sys->cpu.c = 0; // FIXME: "ROM select number" - sys->cpu.hl = start_addr; - sys->pins = z80_prefetch(&sys->cpu, 0xBD16); // MC START PROGRAM - return true;} - -bool cpc_quickload(cpc_t* sys, const uint8_t* ptr, int num_bytes) { - CHIPS_ASSERT(sys && sys->valid && ptr); - if (_cpc_is_valid_sna(ptr, num_bytes)) { - return _cpc_load_sna(sys, ptr, num_bytes); - } - else if (_cpc_is_valid_bin(num_bytes)) { - return _cpc_load_bin(sys, ptr); - } - else { - // not a known file type, or not enough data - return false; + if (start) { + // write CALL &xxxx into BASIC line buffer + const char* to_hex = "0123456789ABCDEF"; + uint16_t line_buf; + switch (sys->type) { + case CPC_TYPE_6128: + case CPC_TYPE_KCCOMPACT: + line_buf = 0xAC8A; + break; + default: + line_buf = 0xACA4; + break; + } + mem_wr(&sys->mem, line_buf++, 'C'); + mem_wr(&sys->mem, line_buf++, 'A'); + mem_wr(&sys->mem, line_buf++, 'L'); + mem_wr(&sys->mem, line_buf++, 'L'); + mem_wr(&sys->mem, line_buf++, ' '); + mem_wr(&sys->mem, line_buf++, '&'); + mem_wr(&sys->mem, line_buf++, to_hex[(start_addr >> 12) & 0x0F]); + mem_wr(&sys->mem, line_buf++, to_hex[(start_addr >> 8) & 0x0F]); + mem_wr(&sys->mem, line_buf++, to_hex[(start_addr >> 4) & 0x0F]); + mem_wr(&sys->mem, line_buf++, to_hex[(start_addr >> 0) & 0x0F]); + mem_wr(&sys->mem, line_buf++, 0); + // generate a Return key press + cpc_key_down(sys, 0x0D); + cpc_key_up(sys, 0x0D); } + return true; } -/*=== CASSETTE TAPE FILE LOADING =============================================*/ -/* - FIXME: implement tape loading through the hardware emulation instead of - OS function trapping. - -static int _cpc_trap_cb(uint16_t pc, uint32_t ticks, uint64_t pins, void* user_data) { - // CPU trap handler to check for casread - (void)ticks; - (void)pins; - cpc_t* sys = (cpc_t*) user_data; - return (pc == sys->casread_trap) ? 1 : 0; -} - -bool cpc_insert_tape(cpc_t* sys, const uint8_t* ptr, int num_bytes) { - CHIPS_ASSERT(sys && sys->valid); - CHIPS_ASSERT(ptr); - cpc_remove_tape(sys); - if (num_bytes > CPC_MAX_TAPE_SIZE) { +bool cpc_quickload(cpc_t* sys, chips_range_t data, bool start) { + CHIPS_ASSERT(sys && sys->valid && data.ptr && (data.size > 0)); + if (_cpc_is_valid_sna(data)) { + return _cpc_load_sna(sys, data); + } else if (_cpc_is_valid_bin(data)) { + return _cpc_load_bin(sys, data, start); + } else { + // not a known file type, or not enough data return false; } - memcpy(sys->tape.buf, ptr, num_bytes); - sys->tape.pos = 0; - sys->tape.size = num_bytes; - z80_trap_cb(&sys->cpu, _cpc_trap_cb, sys); - return true; } -void cpc_remove_tape(cpc_t* sys) { - CHIPS_ASSERT(sys && sys->valid); - sys->tape.pos = 0; - sys->tape.size = 0; - z80_trap_cb(&sys->cpu, 0, 0); +uint16_t cpc_quickload_return_addr(cpc_t* sys) { + switch (sys->type) { + case CPC_TYPE_6128: + case CPC_TYPE_KCCOMPACT: + return 0xB9A2; + case CPC_TYPE_464: + return 0xB99A; + } + return 0xB9A2; } -// the trapped OS casread function, reads one tape block into memory -static void _cpc_cas_read(cpc_t* sys) { - bool success = false; - // if no tape is currently inserted, both tape_pos and tape_size is 0 - if ((sys->tape.pos + 3) < sys->tape.size) { - uint8_t len_l = sys->tape.buf[sys->tape.pos++]; - uint8_t len_h = sys->tape.buf[sys->tape.pos++]; - uint16_t len = len_h<<8 | len_l; - if ((sys->tape.pos + len) <= sys->tape.size) { - uint8_t sync = sys->tape.buf[sys->tape.pos++]; - if (sync == sys->cpu.a) { - success = true; - for (uint16_t i = 0; i < (len-1); i++) { - uint8_t val = sys->tape.buf[sys->tape.pos++]; - mem_wr(&sys->mem, sys->cpu.hl++, val); - } - } - } - } - sys->cpu.f = success ? 0x45 : 0x00; - sys->pins = z80_prefetch(&sys->cpu, sys->casread_ret); - if (sys->tape.pos >= sys->tape.size) { - // reached end of tape, remove tape - cpc_remove_tape(sys); +uint16_t cpc_quickload_exec_addr(chips_range_t data) { + uint16_t start_addr = 0xFFFF; + if (_cpc_is_valid_sna(data)) { + const _cpc_sna_header* hdr = (const _cpc_sna_header*)data.ptr; + start_addr = (hdr->PC_h<<8) | hdr->PC_l; + } else if (_cpc_is_valid_bin(data)) { + const _cpc_bin_header* hdr = (const _cpc_bin_header*)data.ptr; + start_addr = (hdr->start_addr_h<<8)|hdr->start_addr_l; } + return start_addr; } -*/ /*=== FLOPPY DISC SUPPORT ====================================================*/ static int _cpc_fdc_seektrack(int drive, int track, void* user_data) { if (0 == drive) { cpc_t* sys = (cpc_t*) user_data; return fdd_seek_track(&sys->fdd, track); - } - else { + } else { return UPD765_RESULT_NOT_READY; } } @@ -1049,8 +970,7 @@ static int _cpc_fdc_seeksector(int drive, int side, upd765_sectorinfo_t* inout_i inout_info->st2 = sector->info.upd765.st2; } return res; - } - else { + } else { return UPD765_RESULT_NOT_READY; } } @@ -1059,8 +979,7 @@ static int _cpc_fdc_read(int drive, int side, void* user_data, uint8_t* out_data if (0 == drive) { cpc_t* sys = (cpc_t*) user_data; return fdd_read(&sys->fdd, side, out_data); - } - else { + } else { return UPD765_RESULT_NOT_READY; } } @@ -1094,8 +1013,7 @@ static void _cpc_fdc_driveinfo(int drive, void* user_data, upd765_driveinfo_t* o out_info->ready = sys->fdd.motor_on; out_info->write_protected = sys->fdd.disc.write_protected; out_info->fault = false; - } - else { + } else { out_info->physical_track = 0; out_info->sides = 1; out_info->head = 0; @@ -1105,9 +1023,9 @@ static void _cpc_fdc_driveinfo(int drive, void* user_data, upd765_driveinfo_t* o } } -bool cpc_insert_disc(cpc_t* sys, const uint8_t* ptr, int num_bytes) { +bool cpc_insert_disc(cpc_t* sys, chips_range_t data) { CHIPS_ASSERT(sys && sys->valid); - return fdd_cpc_insert_dsk(&sys->fdd, ptr, num_bytes); + return fdd_cpc_insert_dsk(&sys->fdd, data); } void cpc_remove_disc(cpc_t* sys) { @@ -1120,27 +1038,62 @@ bool cpc_disc_inserted(cpc_t* sys) { return fdd_disc_inserted(&sys->fdd); } -int cpc_std_display_width(void) { - return AM40010_DISPLAY_WIDTH; -} - -int cpc_std_display_height(void) { - return AM40010_DISPLAY_HEIGHT; -} - -size_t cpc_max_display_size(void) { - // take debugging visualization into account - return AM40010_DBG_DISPLAY_WIDTH * AM40010_DBG_DISPLAY_HEIGHT * 4; +chips_display_info_t cpc_display_info(cpc_t* sys) { + const chips_display_info_t res = { + .frame = { + .dim = { + .width = AM40010_FRAMEBUFFER_WIDTH, + .height = AM40010_FRAMEBUFFER_HEIGHT, + }, + .bytes_per_pixel = 1, + .buffer = { + .ptr = sys ? sys->fb : 0, + .size = AM40010_FRAMEBUFFER_SIZE_BYTES, + } + }, + .screen = { + .x = 0, + .y = 0, + .width = (sys && sys->ga.dbg_vis) ? AM40010_FRAMEBUFFER_WIDTH : AM40010_DISPLAY_WIDTH, + .height = (sys && sys->ga.dbg_vis) ? AM40010_FRAMEBUFFER_HEIGHT : AM40010_DISPLAY_HEIGHT, + }, + .palette = { + .ptr = sys ? sys->ga.hw_colors : 0, + .size = AM40010_NUM_HWCOLORS * sizeof(uint32_t) + } + }; + CHIPS_ASSERT(((sys == 0) && (res.frame.buffer.ptr == 0)) || ((sys != 0) && (res.frame.buffer.ptr != 0))); + CHIPS_ASSERT(((sys == 0) && (res.palette.ptr == 0)) || ((sys != 0) && (res.palette.ptr != 0))); + return res; } -int cpc_display_width(cpc_t* sys) { - CHIPS_ASSERT(sys && sys->valid); - return sys->ga.dbg_vis ? AM40010_DBG_DISPLAY_WIDTH : AM40010_DISPLAY_WIDTH; +uint32_t cpc_save_snapshot(cpc_t* sys, cpc_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->debug); + chips_audio_callback_snapshot_onsave(&dst->audio.callback); + ay38910_snapshot_onsave(&dst->psg); + upd765_snapshot_onsave(&dst->fdc); + am40010_snapshot_onsave(&dst->ga); + mem_snapshot_onsave(&dst->mem, sys); + return CPC_SNAPSHOT_VERSION; } -int cpc_display_height(cpc_t* sys) { - CHIPS_ASSERT(sys && sys->valid); - return sys->ga.dbg_vis ? AM40010_DBG_DISPLAY_HEIGHT : AM40010_DISPLAY_HEIGHT; +bool cpc_load_snapshot(cpc_t* sys, uint32_t version, cpc_t* src) { + CHIPS_ASSERT(sys && src); + if (version != CPC_SNAPSHOT_VERSION) { + return false; + } + static cpc_t im; + im = *src; + chips_debug_snapshot_onload(&im.debug, &sys->debug); + chips_audio_callback_snapshot_onload(&im.audio.callback, &sys->audio.callback); + ay38910_snapshot_onload(&im.psg, &sys->psg); + upd765_snapshot_onload(&im.fdc, &sys->fdc); + am40010_snapshot_onload(&im.ga, &sys->ga); + mem_snapshot_onload(&im.mem, sys); + *sys = im; + return true; } #endif /* CHIPS_IMPL */ diff --git a/systems/kc85.h b/systems/kc85.h index 7a2332c1..ede6a5fc 100644 --- a/systems/kc85.h +++ b/systems/kc85.h @@ -6,7 +6,7 @@ #define CHIPS_IMPL - before you include this file in *one* C file to create the + before you include this file in *one* C file to create the implementation. Define the KC85 type to build before including this header (both the @@ -17,12 +17,13 @@ CHIPS_KC85_TYPE_4 Optionally provide the following macros with your own implementation - + CHIPS_ASSERT(c) your own assert macro (default: assert(c)) You need to include the following headers before including kc85.h: + - chips/chips_common.h - chips/z80.h - chips/z80ctc.h - chips/z80pio.h @@ -79,7 +80,7 @@ accesses to this RAM block are visible as 'display needling' artefacts. (NOTE: the slow video memory access is not emulation, display needling - is emulated, but I haven't verified against real hardware + is emulated, but I haven't verified against real hardware whether it actually looks correct) ### Special Operating System Conditions @@ -87,7 +88,7 @@ - the index register IX is reserved for operating system use and must not be changed while interrupts are enabled - only interrupt mode IM2 is supported - + ### Interrupt Vectors: - 01E4: PIO-A (cassette tape input) - 01E6: PIO-B (keyboard input) @@ -95,10 +96,10 @@ - 01EA: CTC-1 (cassette tape output) - 01EC: CTC-2 (timer interrupt used for sound length) - ## IO Port Map: + ## IO Port Map: - 80: Expansion module control (OUT: write module control byte, - IN: read module id in slot). The upper 8 bits on the - address bus identify the module slot (in the base + IN: read module id in slot). The upper 8 bits on the + address bus identify the module slot (in the base unit the two slot addresses are 08 and 0C). - 88: PIO port A, data - 89: PIO port B, data @@ -108,7 +109,7 @@ - 8D: CTC channel 1 - 8E: CTC channel 2 - 8F: CTC channel 3 - + The PIO port A and B bits are used to control bank switching and other hardware features: @@ -132,11 +133,11 @@ - CTC-1: sound output (right?) - CTC-2: foreground color blink frequency, timer for cassette input - CTC-3: timer for keyboard input - + ## The Module System: The emulator supports the most common RAM- and ROM-modules, - but doesn't emulate special-hardware modules like the V24 or + but doesn't emulate special-hardware modules like the V24 or A/D converter module. The module system works with 4 byte values: @@ -145,7 +146,7 @@ - The **module id**, this is a fixed value that identifies a module type. All 16 KByte ROM application modules had the same id. The module id can be queried by reading from port 80, with the - slot address in the upper 8 bit of the 16-bit port address (so + slot address in the upper 8 bit of the 16-bit port address (so to query what module is in slot C, you would do an IN A,(C), with the value 0C80 in BC). If no module is in the slot, the value FF would be written to A, otherwise the module's id byte. @@ -154,7 +155,7 @@ this essentially clamps a module's address to a 'round' 8- or 16 KByte value (these are the 2 values I've seen in the wild) - The module control byte, this controls whether a module is currently - active (bit 0), write-protected (bit 1), and at what address the + active (bit 0), write-protected (bit 1), and at what address the module is mapped into the 16-bit address space (upper 3 bits) The module system is controlled with the SWITCH command, for instance @@ -174,7 +175,7 @@ ## The KC85/3 The KC85/3 had the same hardware as the KC85/2 but came with a builtin - 8 KByte BASIC ROM at address C000..DFFF, and the OS was bumped to + 8 KByte BASIC ROM at address C000..DFFF, and the OS was bumped to CAOS 3.1, now taking up a full 8 KBytes. Despite being just a minor update to the KC85/2, the KC85/3 was (most likely) the most popular model of the KC85/2 family. @@ -189,14 +190,14 @@ - Improved color attribute resolution (8x1 pixels instead of 8x4) - An additional per-pixel color mode which allowed to assign each individual pixel one of 4 hardwired colors at full 320x256 - resolution, this was realized by using 1 bit from the + resolution, this was realized by using 1 bit from the pixel-bank and the other bit from the color-bank, so setting one pixel required 2 memory accesses and a bank switch. Maybe this was the reason why this mode was hardly used. - Improved '90-degree-rotated' video memory layout, the 320x256 pixel video memory was organized as 40 vertical stacks of 256 bytes, and the entire video memory was linear, this was perfectly suited - to the Z80's 8+8 bit register pairs. The upper 8-bit register + to the Z80's 8+8 bit register pairs. The upper 8-bit register (for instance H) would hold the 'x coordinate' (columns 0 to 39), and the lower 8-bit register (L) the y coordinate (lines 0 to 255). - 64 KByte video memory was organized into 4 16-KByte banks, 2 banks @@ -211,7 +212,7 @@ New bits in PIO port B: - bit 5: enable the 2 stacked RAM banks at address 8000 - - bit 6: write protect RAM bank at address 8000 + - bit 6: write protect RAM bank at address 8000 Output port 84: - bit 0: select the pixel/color bank pair 0 or 1 for display @@ -250,11 +251,12 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include #include +#include #if !(defined(CHIPS_KC85_TYPE_2) || defined(CHIPS_KC85_TYPE_3) || defined(CHIPS_KC85_TYPE_4)) #error "Please define one of CHIPS_KC85_TYPE_2, CHIPS_KC85_TYPE_3 or CHIPS_KC85_TYPE_4 before including kc85.h!" @@ -264,26 +266,50 @@ extern "C" { #endif -#define KC85_MAX_AUDIO_SAMPLES (1024) // max number of audio samples in internal sample buffer +#if defined(CHIPS_KC85_TYPE_2) +#define KC85_TYPE_ID (0x00020000) +#elif defined(CHIPS_KC85_TYPE_3) +#define KC85_TYPE_ID (0x00030000) +#elif defined(CHIPS_KC85_TYPE_4) +#define KC85_TYPE_ID (0x00040000) +#endif + +#if defined(CHIPS_KC85_TYPE_4) +#define KC85_FREQUENCY (1770000) +#define KC85_SCANLINE_TICKS (113) +#else +#define KC85_FREQUENCY (1750000) +#define KC85_SCANLINE_TICKS (112) +#endif +#define KC85_NUM_SCANLINES (312) +#define KC85_IRM0_PAGE (4) + +// bump this whenever the kc85_t struct layout changes +#define KC85_SNAPSHOT_VERSION (KC85_TYPE_ID | 0x0002) + +#define KC85_MAX_AUDIO_SAMPLES (1024U) // max number of audio samples in internal sample buffer #define KC85_DEFAULT_AUDIO_SAMPLES (128) // default number of samples in internal sample buffer -#define KC85_MAX_TAPE_SIZE (64 * 1024) // max size of a snapshot file in bytes -#define KC85_NUM_SLOTS (2) // 2 expansion slots in main unit, each needs one mem_t layer! -#define KC85_EXP_BUFSIZE (KC85_NUM_SLOTS*64*1024) // expansion system buffer size (64 KB per slot) +#define KC85_EXP_NUM_SLOTS (2U) // 2 expansion slots in main unit, each needs one mem_t layer! +#define KC85_EXP_BUFSIZE (KC85_EXP_NUM_SLOTS*64U*1024U) // expansion system buffer size (64 KB per slot) + +#define KC85_FRAMEBUFFER_WIDTH (512) // multiple of 256 +#define KC85_FRAMEBUFFER_HEIGHT (256) // FIXME: allow border? +#define KC85_FRAMEBUFFER_SIZE_BYTES (KC85_FRAMEBUFFER_WIDTH * KC85_FRAMEBUFFER_HEIGHT) +#define KC85_DISPLAY_WIDTH (320) +#define KC85_DISPLAY_HEIGHT (256) // IO bits -#define KC85_PIO_A_CAOS_ROM (1<<0) -#define KC85_PIO_A_RAM (1<<1) -#define KC85_PIO_A_IRM (1<<2) -#define KC85_PIO_A_RAM_RO (1<<3) -#define KC85_PIO_A_NMI (1<<4) // KC84/2,3 only: trigger an NMI -#define KC85_PIO_A_TAPE_LED (1<<5) -#define KC85_PIO_A_TAPE_MOTOR (1<<6) -#define KC85_PIO_A_BASIC_ROM (1<<7) -#define KC85_PIO_B_853_VOLUME_MASK ((1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)) -#define KC85_PIO_B_854_VOLUME_MASK ((1<<1)|(1<<2)|(1<<3)|(1<<4)) -#define KC85_PIO_B_RAM8 (1<<5) // KC85/4 only -#define KC85_PIO_B_RAM8_RO (1<<6) // KC85/4 only -#define KC85_PIO_B_BLINK_ENABLED (1<<7) +#define KC85_PIO_CAOS_ROM Z80PIO_PA0 +#define KC85_PIO_RAM Z80PIO_PA1 +#define KC85_PIO_IRM Z80PIO_PA2 +#define KC85_PIO_RAM_RO Z80PIO_PA3 +#define KC85_PIO_NMI Z80PIO_PA4 // KC84/2,3 only: trigger an NMI +#define KC85_PIO_TAPE_LED Z80PIO_PA5 +#define KC85_PIO_TAPE_MOTOR Z80PIO_PA6 +#define KC85_PIO_BASIC_ROM Z80PIO_PA7 +#define KC85_PIO_RAM8 Z80PIO_PB5 // KC85/4 only +#define KC85_PIO_RAM8_RO Z80PIO_PB6 // KC85/4 only +#define KC85_PIO_BLINK_ENABLED Z80PIO_PB7 // KC85/4 only IO latches #define KC85_IO84_SEL_VIEW_IMG (1<<0) // 0: display img0, 1: display img1 @@ -297,16 +323,14 @@ extern "C" { #define KC85_IO86_CAOS_ROM_C (1<<7) // PIO and IO latch bit masks which affect the memory mapping -#define KC85_PIO_A_MEMORY_BITS (KC85_PIO_A_CAOS_ROM|KC85_PIO_A_RAM|KC85_PIO_A_IRM|KC85_PIO_A_RAM_RO|KC85_PIO_A_BASIC_ROM) -#define KC85_PIO_B_MEMORY_BITS (KC85_PIO_B_RAM8|KC85_PIO_B_RAM8_RO) +#define KC85_PIO_MEMORY_BITS (KC85_PIO_CAOS_ROM|KC85_PIO_RAM|KC85_PIO_IRM|KC85_PIO_RAM_RO|KC85_PIO_BASIC_ROM|KC85_PIO_RAM8|KC85_PIO_RAM8_RO) #define KC85_IO84_MEMORY_BITS (KC85_IO84_SEL_CPU_COLOR|KC85_IO84_SEL_CPU_IMG|KC85_IO84_SEL_RAM8) #define KC85_IO86_MEMORY_BITS (KC85_IO86_RAM4|KC85_IO86_RAM4_RO|KC85_IO86_CAOS_ROM_C) -// audio sample callback -typedef struct { - void (*func)(const float* samples, int num_samples, void* user_data); - void* user_data; -} kc85_audio_callback_t; +// pin mask aliases +#define KC85_FLIPFLOP_BEEPER_1 (Z80CTC_ZCTO0) +#define KC85_FLIPFLOP_BEEPER_2 (Z80CTC_ZCTO1) +#define KC85_FLIPFLOP_BLINK (Z80CTC_ZCTO2) // callback to apply patches after a snapshot is loaded typedef struct { @@ -314,38 +338,10 @@ typedef struct { void* user_data; } kc85_patch_callback_t; -// debugging hook definitions -typedef void (*kc85_debug_func_t)(void* user_data, uint64_t pins); -typedef struct { - struct { - kc85_debug_func_t func; - void* user_data; - } callback; - bool* stopped; -} kc85_debug_t; - -typedef struct { - const void* ptr; - size_t size; -} kc85_rom_image_t; - // config parameters for kc85_init() typedef struct { - kc85_debug_t debug; // optional debugger hook - - // video output config (if you don't need display decoding, set pixel_buffer to 0) - struct { - void* ptr; // pointer to a linear RGBA8 pixel buffer, at least 320*256*4 bytes - size_t size; // size of the pixel buffer in bytes - } pixel_buffer; - - // audio output config (if you don't want audio, set audio_cb to zero) - struct { - kc85_audio_callback_t callback; // called when audio_num_samples are ready - int num_samples; // default is KC85_DEFAULT_AUDIO_SAMPLES - int sample_rate; // playback sample rate, default is 44100 - float volume; // audio volume (0.0 .. 1.0), default is 0.4 - } audio; + chips_debug_t debug; + chips_audio_desc_t audio; // an optional callback to be invoked after a snapshot file is loaded to apply patches kc85_patch_callback_t patch_callback; @@ -353,15 +349,15 @@ typedef struct { // ROM images struct { #if defined(CHIPS_KC85_TYPE_2) - kc85_rom_image_t caos22; // CAOS 2.2 (used in KC85/2) + chips_range_t caos22; // CAOS 2.2 (used in KC85/2) #elif defined(CHIPS_KC85_TYPE_3) - kc85_rom_image_t caos31; // CAOS 3.1 (used in KC85/3) + chips_range_t caos31; // CAOS 3.1 (used in KC85/3) #elif defined(CHIPS_KC85_TYPE_4) - kc85_rom_image_t caos42c; // CAOS 4.2 at 0xC000 (KC85/4) - kc85_rom_image_t caos42e; // CAOS 4.2 at 0xE000 (KC85/4) + chips_range_t caos42c; // CAOS 4.2 at 0xC000 (KC85/4) + chips_range_t caos42e; // CAOS 4.2 at 0xE000 (KC85/4) #endif #if defined(CHIPS_KC85_TYPE_3) || defined(CHIPS_KC85_TYPE_4) - kc85_rom_image_t kcbasic; // same BASIC version for KC85/3 and KC85/4 + chips_range_t kcbasic; // same BASIC version for KC85/3 and KC85/4 #endif } roms; } kc85_desc_t; @@ -385,7 +381,7 @@ typedef struct { uint8_t id; // id of currently inserted module bool writable; // RAM or ROM module uint8_t addr_mask; // the module's address mask - int size; // the module's byte size + uint32_t size; // the module's byte size } kc85_module_t; // KC85 expansion slot @@ -397,45 +393,43 @@ typedef struct { } kc85_slot_t; /* KC85 expansion system state: - NOTE that each expansion slot needs its own memory-mapping layer starting + NOTE that each expansion slot needs its own memory-mapping layer starting at layer 1 (layer 0 is used by the base system) */ typedef struct { - kc85_slot_t slot[KC85_NUM_SLOTS]; // slots 0x08 and 0x0C in KC85 main unit - uint32_t buf_top; // offset of free area in expansion buffer (kc85_t.exp_buf[]) + kc85_slot_t slot[KC85_EXP_NUM_SLOTS]; // slots 0x08 and 0x0C in KC85 main unit + uint32_t buf_top; // offset of free area in expansion buffer (kc85_t.exp_buf[]) } kc85_exp_t; // KC85 emulator state typedef struct { z80_t cpu; - z80ctc_t ctc; - z80pio_t pio; - beeper_t beeper_1; - beeper_t beeper_2; - - uint8_t pio_a; // current PIO-A value, used for bankswitching - uint8_t pio_b; // current PIO-B value, used for bankswitching + mem_t mem; + struct { + uint16_t h_tick; + uint16_t v_count; + } video; + uint64_t pio_pins; #if defined(CHIPS_KC85_TYPE_4) uint8_t io84; // byte latch at port 0x84, only on KC85/4 uint8_t io86; // byte latch at port 0x86, only on KC85/4 #endif - bool blink_flag; // foreground color blinking flag toggled by CTC - - uint32_t h_tick; // video timing generator counter - uint32_t v_count; + z80ctc_t ctc; + uint64_t flip_flops; // audio and blink flip flop bits controlled by CTC + beeper_t beeper_1; + beeper_t beeper_2; + z80pio_t pio; + kc85_exp_t exp; // expansion module system uint64_t pins; uint64_t freq_hz; kbd_t kbd; - mem_t mem; - kc85_exp_t exp; // expansion module system bool valid; - kc85_debug_t debug; + chips_debug_t debug; - uint32_t* pixel_buffer; struct { - kc85_audio_callback_t callback; + chips_audio_callback_t callback; int num_samples; int sample_pos; float sample_buffer[KC85_MAX_AUDIO_SAMPLES]; @@ -451,6 +445,7 @@ typedef struct { #endif uint8_t rom_caos_e[0x2000]; // 8 KByte CAOS ROM at 0xE000 uint8_t exp_buf[KC85_EXP_BUFSIZE]; // expansion system RAM/ROM + alignas(64) uint8_t fb[KC85_FRAMEBUFFER_SIZE_BYTES]; } kc85_t; // initialize a new KC85 instance @@ -459,16 +454,10 @@ void kc85_init(kc85_t* sys, const kc85_desc_t* desc); void kc85_discard(kc85_t* sys); // reset a KC85 instance void kc85_reset(kc85_t* sys); +// query information about display requirements, can be called with nullptr +chips_display_info_t kc85_display_info(kc85_t* sys); // run KC85 emulation for a given number of microseconds, returns number of ticks executed uint32_t kc85_exec(kc85_t* sys, uint32_t micro_seconds); -// get the standard framebuffer width and height in pixels -int kc85_std_display_width(void); -int kc85_std_display_height(void); -// get the maximum framebuffer size in number of bytes -size_t kc85_max_display_size(void); -// get the current framebuffer width and height in pixels -int kc85_display_width(kc85_t* sys); -int kc85_display_height(kc85_t* sys); // send a key-down event void kc85_key_down(kc85_t* sys, int key_code); // send a key-up event @@ -476,7 +465,7 @@ void kc85_key_up(kc85_t* sys, int key_code); // insert a RAM module (slot must be 0x08 or 0x0C) bool kc85_insert_ram_module(kc85_t* sys, uint8_t slot, kc85_module_type_t type); // insert a ROM module (slot must be 0x08 or 0x0C) -bool kc85_insert_rom_module(kc85_t* sys, uint8_t slot, kc85_module_type_t type, const void* rom_ptr, int rom_size); +bool kc85_insert_rom_module(kc85_t* sys, uint8_t slot, kc85_module_type_t type, chips_range_t rom_data); // remove module in slot bool kc85_remove_module(kc85_t* sys, uint8_t slot); // get a descriptive module name by module type @@ -492,15 +481,27 @@ bool kc85_slot_cpu_visible(kc85_t* sys, uint8_t slot_addr); // compute the current CPU address of module in slot (0 if no active module in slot) uint16_t kc85_slot_cpu_addr(kc85_t* sys, uint8_t slot_addr); // get byte-size of module in slot (0 if no module in slot) -int kc85_slot_mod_size(kc85_t* sys, uint8_t slot_addr); +uint32_t kc85_slot_mod_size(kc85_t* sys, uint8_t slot_addr); // get descriptive name of module in slot ("NONE" if no module in slot) const char* kc85_slot_mod_name(kc85_t* sys, uint8_t slot_addr); // get short name of module slot slot const char* kc85_slot_mod_short_name(kc85_t* sys, uint8_t slot_addr); // get a slot's control byte uint8_t kc85_slot_ctrl(kc85_t* sys, uint8_t slot_addr); -// load a .KCC or .TAP snapshot file into the emulator -bool kc85_quickload(kc85_t* sys, const uint8_t* ptr, int num_bytes); +// test if data is a valid TAP file +bool kc85_is_valid_kctap(chips_range_t data); +// test if data is a valid KCC file +bool kc85_is_valid_kcc(chips_range_t data); +// extract the exed address from a KCC file +uint16_t kc85_kcc_exec_addr(chips_range_t data); +// get the return address in ROM which is pushed on the stack for quickloaded code +uint16_t kc85_quickload_return_addr(void); +// load a .KCC or .TAP snapshot file into the emulator and optionally try to start +bool kc85_quickload(kc85_t* sys, chips_range_t data, bool start); +// take snapshot, patches any pointers to zero, returns a snapshot version +uint32_t kc85_save_snapshot(kc85_t* sys, kc85_t* dst); +// load a snapshot, returns false if snapshot version doesn't match +bool kc85_load_snapshot(kc85_t* sys, uint32_t version, const kc85_t* src); #ifdef __cplusplus } // extern "C" @@ -514,15 +515,6 @@ bool kc85_quickload(kc85_t* sys, const uint8_t* ptr, int num_bytes); #define CHIPS_ASSERT(c) assert(c) #endif -#define _KC85_DISPLAY_WIDTH (320) -#define _KC85_DISPLAY_HEIGHT (256) -#define _KC85_DISPLAY_SIZE (_KC85_DISPLAY_WIDTH*_KC85_DISPLAY_HEIGHT*4) -#if defined(CHIPS_KC85_TYPE_4) -#define _KC85_FREQUENCY (1770000) -#else -#define _KC85_FREQUENCY (1750000) -#endif -#define _KC85_IRM0_PAGE (4) /* IO address decoding. @@ -591,13 +583,11 @@ static inline uint32_t _kc85_xorshift32(uint32_t x) { void kc85_init(kc85_t* sys, const kc85_desc_t* desc) { CHIPS_ASSERT(sys && desc); - CHIPS_ASSERT((0 == desc->pixel_buffer.ptr) || (desc->pixel_buffer.ptr && (desc->pixel_buffer.size >= _KC85_DISPLAY_SIZE))); if (desc->debug.callback.func) { CHIPS_ASSERT(desc->debug.stopped); } memset(sys, 0, sizeof(kc85_t)); sys->valid = true; - sys->freq_hz = _KC85_FREQUENCY; - sys->pixel_buffer = (uint32_t*) desc->pixel_buffer.ptr; + sys->freq_hz = KC85_FREQUENCY; sys->patch_callback = desc->patch_callback; sys->debug = desc->debug; @@ -626,7 +616,7 @@ void kc85_init(kc85_t* sys, const kc85_desc_t* desc) { #if !defined(CHIPS_KC85_TYPE_4) uint32_t r = 0x6D98302B; uint8_t* ptr = &sys->ram[0][0]; - for (int i = 0; i < (int)sizeof(sys->ram);) { + for (size_t i = 0; i < sizeof(sys->ram);) { r = _kc85_xorshift32(r); ptr[i++] = r; ptr[i++] = (r>>8); @@ -638,7 +628,6 @@ void kc85_init(kc85_t* sys, const kc85_desc_t* desc) { // initialize the hardware z80_init(&sys->cpu); z80ctc_init(&sys->ctc); - sys->pio_a = KC85_PIO_A_RAM | KC85_PIO_A_RAM_RO | KC85_PIO_A_IRM | KC85_PIO_A_CAOS_ROM; z80pio_init(&sys->pio); sys->audio.callback = desc->audio.callback; @@ -682,53 +671,21 @@ void kc85_reset(kc85_t* sys) { z80pio_reset(&sys->pio); beeper_reset(&sys->beeper_1); beeper_reset(&sys->beeper_2); - sys->pio_a = 0; - sys->pio_b = 0; #if defined(CHIPS_KC85_TYPE_4) sys->io84 = 0; sys->io86 = 0; #endif _kc85_exp_reset(sys); - sys->pio_a = KC85_PIO_A_RAM | KC85_PIO_A_RAM_RO | KC85_PIO_A_IRM | KC85_PIO_A_CAOS_ROM; + sys->pio_pins = KC85_PIO_RAM | KC85_PIO_RAM_RO | KC85_PIO_IRM | KC85_PIO_CAOS_ROM; _kc85_update_memory_map(sys); // execution after reset starts at 0xE000 sys->pins = z80_prefetch(&sys->cpu, 0xE000); } -// hardwired foreground colors -static uint32_t _kc85_fg_pal[16] = { - 0xFF000000, // black - 0xFFFF0000, // blue - 0xFF0000FF, // red - 0xFFFF00FF, // magenta - 0xFF00FF00, // green - 0xFFFFFF00, // cyan - 0xFF00FFFF, // yellow - 0xFFFFFFFF, // white - 0xFF000000, // black #2 - 0xFFFF00A0, // violet - 0xFF00A0FF, // orange - 0xFFA000FF, // purple - 0xFFA0FF00, // blueish green - 0xFFFFA000, // greenish blue - 0xFF00FFA0, // yellow-green - 0xFFFFFFFF, // white #2 -}; - -// background colors -static uint32_t _kc85_bg_pal[8] = { - 0xFF000000, // black - 0xFFA00000, // dark-blue - 0xFF0000A0, // dark-red - 0xFFA000A0, // dark-magenta - 0xFF00A000, // dark-green - 0xFFA0A000, // dark-cyan - 0xFF00A0A0, // dark-yellow - 0xFFA0A0A0, // gray -}; - -static inline void _kc85_decode_8pixels(uint32_t* ptr, uint8_t pixels, uint8_t colors, bool force_bg) { +#define CHIPS_KC85_USE_BIT_TWIDDLING (1) + +static inline void _kc85_decode_8pixels(uint8_t* dst, uint8_t pixels, uint8_t colors) { /* select foreground- and background color: bit 7: blinking @@ -736,149 +693,230 @@ static inline void _kc85_decode_8pixels(uint32_t* ptr, uint8_t pixels, uint8_t c bits 2..0: background color index 0 is background color, index 1 is foreground color + + NOTE: using 'bit twiddling' to compose spread the 8 pixels into an uint64_t + and writing this as a single 64-bit values doesn't appear to result + in a performance advantage, also see: + + https://www.godbolt.org/z/7dsW4v6vr + + (and this bit twiddling code is even still missing reversing the pixel bit order) */ - const uint8_t bg_index = colors & 0x7; - const uint8_t fg_index = (colors>>3)&0xF; - const uint32_t bg = _kc85_bg_pal[bg_index]; - const uint32_t fg = force_bg ? bg : _kc85_fg_pal[fg_index]; - ptr[0] = pixels & 0x80 ? fg : bg; - ptr[1] = pixels & 0x40 ? fg : bg; - ptr[2] = pixels & 0x20 ? fg : bg; - ptr[3] = pixels & 0x10 ? fg : bg; - ptr[4] = pixels & 0x08 ? fg : bg; - ptr[5] = pixels & 0x04 ? fg : bg; - ptr[6] = pixels & 0x02 ? fg : bg; - ptr[7] = pixels & 0x01 ? fg : bg; + #if CHIPS_KC85_USE_BIT_TWIDDLING + // courtesy of ryg: https://mastodon.gamedev.place/@rygorous/109531596140414988 + static const uint32_t lut32[16] = { + 0x00000000, 0xff000000, 0x00ff0000, 0xffff0000, + 0x0000ff00, 0xff00ff00, 0x00ffff00, 0xffffff00, + 0x000000ff, 0xff0000ff, 0x00ff00ff, 0xffff00ff, + 0x0000ffff, 0xff00ffff, 0x00ffffff, 0xffffffff, + }; + const uint32_t colors32 = colors * 0x01010101u; + const uint32_t bg32 = 0x10101010 | (colors32 & 0x07070707); + const uint32_t fg32 = (colors32 >> 3) & 0x0F0F0F0F; + const uint32_t xor32 = bg32 ^ fg32; + uint32_t* dst32 = (uint32_t*)dst; + dst32[0] = bg32 ^ (xor32 & lut32[pixels >> 4]); + dst32[1] = bg32 ^ (xor32 & lut32[pixels & 0xf]); + #else + const uint8_t bg = 0x10 | (colors & 0x7); + const uint8_t fg = (colors>>3) & 0xF; + dst[0] = pixels & 0x80 ? fg : bg; + dst[1] = pixels & 0x40 ? fg : bg; + dst[2] = pixels & 0x20 ? fg : bg; + dst[3] = pixels & 0x10 ? fg : bg; + dst[4] = pixels & 0x08 ? fg : bg; + dst[5] = pixels & 0x04 ? fg : bg; + dst[6] = pixels & 0x02 ? fg : bg; + dst[7] = pixels & 0x01 ? fg : bg; + #endif } -#if defined(CHIPS_KC85_TYPE_2) || defined(CHIPS_KC85_TYPE_3) -static uint64_t _kc85_tick_video(kc85_t* sys, uint64_t pins) { - /* emulate display needling on KC85/2 and /3, this happens when the - CPU accesses video memory, which will force the background color - a short duration - */ - bool cpu_access = false; - if (0 != (pins & Z80_WR)) { - uint16_t addr = Z80_GET_ADDR(pins); - if ((addr >= 0x8000) && (addr < 0xC000)) { - cpu_access = true; +static inline uint64_t _kc85_update_raster_counters(kc85_t* sys, uint64_t pins) { + sys->video.h_tick++; + // feed '_h4' into CTC CLKTRG0 and 1, per scanline: + // 0..31 ticks lo + // 32..63 ticks hi + // 64..95 ticks lo + // remainder: hi + if (sys->video.h_tick & 0x20) { + pins |= (Z80CTC_CLKTRG0 | Z80CTC_CLKTRG1); + } + // vertical blanking interval (/BI) active for the last 56 scanlines + if (sys->video.v_count & 0x100) { + pins |= (Z80CTC_CLKTRG2 | Z80CTC_CLKTRG3); + } + if (sys->video.h_tick == KC85_SCANLINE_TICKS) { + sys->video.h_tick = 0; + sys->video.v_count++; + if (sys->video.v_count == KC85_NUM_SCANLINES) { + sys->video.v_count = 0; } } + return pins; +} + +#if defined(CHIPS_KC85_TYPE_2) || defined(CHIPS_KC85_TYPE_3) +static uint64_t _kc85_tick_video(kc85_t* sys, uint64_t pins) { // every 2 CPU ticks, 8 pixels are decoded - if (sys->h_tick & 1) { - bool blink_bg = sys->blink_flag && (sys->pio_b & KC85_PIO_B_BLINK_ENABLED); - // decode visible 8-pixel group - uint32_t x = sys->h_tick>>1; - uint32_t y = sys->v_count; - if (sys->pixel_buffer && (y < 256) && (x < 40)) { - uint32_t* dst_ptr = &(sys->pixel_buffer[y*_KC85_DISPLAY_WIDTH + x*8]); + if (sys->video.h_tick & 1) { + uint16_t x = sys->video.h_tick>>1; + uint16_t y = sys->video.v_count; + if ((y < 256) && (x < 40)) { uint32_t pixel_offset, color_offset; - if (x < 0x20) { - // left 256x256 area - pixel_offset = x | (((y>>2)&0x3)<<5) | ((y&0x3)<<7) | (((y>>4)&0xF)<<9); - color_offset = x | (((y>>2)&0x3f)<<5); - } - else { + if (x & 0x20) { // right 64x256 area pixel_offset = 0x2000 + ((x&0x7) | (((y>>4)&0x3)<<3) | (((y>>2)&0x3)<<5) | ((y&0x3)<<7) | (((y>>6)&0x3)<<9)); - color_offset = 0x0800 + ((x&0x7) | (((y>>4)&0x3)<<3) | (((y>>2)&0x3)<<5) | (((y>>6)&0x3)<<7)); + color_offset = 0x3000 + ((x&0x7) | (((y>>4)&0x3)<<3) | (((y>>2)&0x3)<<5) | (((y>>6)&0x3)<<7)); } - const uint8_t* pixel_ram = sys->ram[_KC85_IRM0_PAGE]; - const uint8_t* color_ram = sys->ram[_KC85_IRM0_PAGE] + 0x2800; - uint8_t pixel_bits = pixel_ram[pixel_offset]; - uint8_t color_bits = color_ram[color_offset]; - bool force_bg = (blink_bg && (color_bits & 0x80)) | cpu_access; - _kc85_decode_8pixels(dst_ptr, pixel_bits, color_bits, force_bg); - cpu_access = false; - } - } - // scanline and frame update - sys->h_tick++; - if (sys->h_tick >= 112) { - sys->h_tick = 0; - sys->v_count++; - if (sys->v_count == 312) { - sys->v_count = 0; - // vertical sync, trigger CTC CLKTRG2 input for video blinking effect - pins |= Z80CTC_CLKTRG2; + else { + // left 256x256 area + pixel_offset = x | (((y>>2)&0x3)<<5) | ((y&0x3)<<7) | (((y>>4)&0xF)<<9); + color_offset = 0x2800 + (x | (((y>>2)&0x3f)<<5)); + } + // cpu_access: emulate display needling on KC85/2 and /3, this happens when the + // CPU accesses video memory, which will force the background color + // a short duration + // + uint8_t color_bits = sys->ram[KC85_IRM0_PAGE][color_offset]; + bool fg_blank = 0 != (color_bits & (sys->flip_flops>>(Z80CTC_BIT_ZCTO2-7)) & (sys->pio_pins>>(Z80PIO_PIN_PB7-7)) & (1<<7)); + // same as (pins & Z80_WR) && (addr >= 0x8000) && (addr < 0xC000) + bool cpu_access = (pins & (Z80_WR | 0xC000)) == (Z80_WR | 0x8000); + uint8_t pixel_bits = (fg_blank || cpu_access) ? 0 : sys->ram[KC85_IRM0_PAGE][pixel_offset]; + uint8_t* dst = &(sys->fb[y*KC85_FRAMEBUFFER_WIDTH + x*8]); + _kc85_decode_8pixels(dst, pixel_bits, color_bits); } } - return pins; + return _kc85_update_raster_counters(sys, pins); } #endif // KC85/2,/3 #if defined(CHIPS_KC85_TYPE_4) -// the KC85/4 HICOLOR palette -static uint32_t _kc85_hicolor[4] = { - 0xFF000000, // black - 0xFF0000FF, // red - 0xFFFFFF00, // cyan - 0xFFFFFFFF, // white -}; +static inline void _kc85_decode_hicolor_8pixels(uint8_t* dst, uint8_t p0, uint8_t p1) { + /* + KC85/4 "hicolor" mode + Decode 8 pixels for the "HICOLOR" mode with 2-bits per-pixel color. + p0 and p1 are the two bitplanes (taken from the pixel and color RAM + bank). The color palette is hardwired. + + p0: 8 bits from first IRM page + p1: 8 bits from second IRM page + */ + // FIXME: come up with an alternative 'bit-twiddling hack' + dst[0] = 0x20 | ((p0>>7)&1)|((p1>>6)&2); + dst[1] = 0x20 | ((p0>>6)&1)|((p1>>5)&2); + dst[2] = 0x20 | ((p0>>5)&1)|((p1>>4)&2); + dst[3] = 0x20 | ((p0>>4)&1)|((p1>>3)&2); + dst[4] = 0x20 | ((p0>>3)&1)|((p1>>2)&2); + dst[5] = 0x20 | ((p0>>2)&1)|((p1>>1)&2); + dst[6] = 0x20 | ((p0>>1)&1)|((p1>>0)&2); + dst[7] = 0x20 | ((p0>>0)&1)|((p1<<1)&2); +} static uint64_t _kc85_tick_video(kc85_t* sys, uint64_t pins) { // decode 8 pixels every second tick - if (sys->h_tick & 1) { - if (sys->io84 & KC85_IO84_HICOLOR) { - // regular KC85/4 video mode - bool blink_bg = sys->blink_flag && (sys->pio_b & KC85_PIO_B_BLINK_ENABLED); - uint32_t x = sys->h_tick>>1; - uint32_t y = sys->v_count; - if (sys->pixel_buffer && (y < 256) && (x < 40)) { - uint32_t* dst_ptr = &(sys->pixel_buffer[y*_KC85_DISPLAY_WIDTH + x*8]); - uint32_t irm_index = (sys->io84 & 1) * 2; - const uint8_t* pixel_ram = sys->ram[_KC85_IRM0_PAGE + irm_index]; - const uint8_t* color_ram = sys->ram[_KC85_IRM0_PAGE + irm_index + 1]; - uint32_t offset = (x<<8) | y; - uint8_t pixel_bits = pixel_ram[offset]; - uint8_t color_bits = color_ram[offset]; - bool force_bg = blink_bg && (color_bits & 0x80); // no bus contention on KC85/4 - _kc85_decode_8pixels(dst_ptr, pixel_bits, color_bits, force_bg); + if (sys->video.h_tick & 1) { + uint16_t x = sys->video.h_tick>>1; + uint16_t y = sys->video.v_count; + if ((y < 256) && (x < 40)) { + size_t irm_index = (sys->io84 & 1) * 2; + size_t offset = (x<<8) | y; + uint8_t color_bits = sys->ram[KC85_IRM0_PAGE + irm_index + 1][offset]; + uint8_t* dst = &sys->fb[y * KC85_FRAMEBUFFER_WIDTH + x * 8]; + if (sys->io84 & KC85_IO84_HICOLOR) { + // regular KC85/4 video mode + bool fg_blank = 0 != (color_bits & (sys->flip_flops>>(Z80CTC_BIT_ZCTO2-7)) & (sys->pio_pins>>(Z80PIO_PIN_PB7-7)) & (1<<7)); + uint8_t pixel_bits = fg_blank ? 0 : sys->ram[KC85_IRM0_PAGE + irm_index][offset]; + _kc85_decode_8pixels(dst, pixel_bits, color_bits); } - } - else { - // KC85/4 "hicolor" mode - uint32_t x = sys->h_tick>>1; - uint32_t y = sys->v_count; - if (sys->pixel_buffer && (y < 256) && (x < 40)) { - uint32_t* dst = &(sys->pixel_buffer[y*_KC85_DISPLAY_WIDTH + x*8]); - uint32_t irm_index = (sys->io84 & 1) * 2; - const uint8_t* pixel_ram = sys->ram[_KC85_IRM0_PAGE + irm_index]; - const uint8_t* color_ram = sys->ram[_KC85_IRM0_PAGE + irm_index + 1]; - uint32_t offset = (x<<8) | y; - uint8_t p0 = pixel_ram[offset]; - uint8_t p1 = color_ram[offset]; - /* - Decode 8 pixels for the "HICOLOR" mode with 2-bits per-pixel color. - p0 and p1 are the two bitplanes (taken from the pixel and color RAM - bank). The color palette is hardwired. - */ - dst[0] = _kc85_hicolor[((p0>>7)&1)|((p1>>6)&2)]; - dst[1] = _kc85_hicolor[((p0>>6)&1)|((p1>>5)&2)]; - dst[2] = _kc85_hicolor[((p0>>5)&1)|((p1>>4)&2)]; - dst[3] = _kc85_hicolor[((p0>>4)&1)|((p1>>3)&2)]; - dst[4] = _kc85_hicolor[((p0>>3)&1)|((p1>>2)&2)]; - dst[5] = _kc85_hicolor[((p0>>2)&1)|((p1>>1)&2)]; - dst[6] = _kc85_hicolor[((p0>>1)&1)|((p1>>0)&2)]; - dst[7] = _kc85_hicolor[((p0>>0)&1)|((p1<<1)&2)]; + else { + // hicolor mode + uint8_t p0 = sys->ram[KC85_IRM0_PAGE + irm_index][offset]; + uint8_t p1 = color_bits; + _kc85_decode_hicolor_8pixels(dst, p0, p1); } } } + return _kc85_update_raster_counters(sys, pins); +} +#endif + +static void _kc85_update_memory_map(kc85_t* sys) { + mem_unmap_layer(&sys->mem, 0); + const uint64_t pio_pins = sys->pio_pins; - // advance raster counters - sys->h_tick++; - if (sys->h_tick >= 113) { - sys->h_tick = 0; - sys->v_count++; - if (sys->v_count == 312) { - sys->v_count = 0; - pins |= Z80CTC_CLKTRG2; + // all models have 16 KB builtin RAM at 0x0000 and 8 KB ROM at 0xE000 + if (pio_pins & KC85_PIO_RAM) { + if (pio_pins & KC85_PIO_RAM_RO) { + mem_map_ram(&sys->mem, 0, 0x0000, 0x4000, sys->ram[0]); + } + else { + mem_map_rom(&sys->mem, 0, 0x0000, 0x4000, sys->ram[0]); } } - return pins; + if (pio_pins & KC85_PIO_CAOS_ROM) { + mem_map_rom(&sys->mem, 0, 0xE000, 0x2000, sys->rom_caos_e); + } + + // KC85/3 and KC85/4: builtin 8 KB BASIC ROM at 0xC000 + #if !defined(CHIPS_KC85_TYPE_2) + if (pio_pins & KC85_PIO_BASIC_ROM) { + mem_map_rom(&sys->mem, 0, 0xC000, 0x2000, sys->rom_basic); + } + #endif + + #if !defined(CHIPS_KC85_TYPE_4) // KC85/2 and /3 + // 16 KB Video RAM at 0x8000 + if (pio_pins & KC85_PIO_IRM) { + mem_map_ram(&sys->mem, 0, 0x8000, 0x4000, sys->ram[KC85_IRM0_PAGE]); + } + #else // KC84/4 + // 16 KB RAM at 0x4000 + if (sys->io86 & KC85_IO86_RAM4) { + if (sys->io86 & KC85_IO86_RAM4_RO) { + mem_map_ram(&sys->mem, 0, 0x4000, 0x4000, sys->ram[1]); + } + else { + mem_map_rom(&sys->mem, 0, 0x4000, 0x4000, sys->ram[1]); + } + } + // 16 KB RAM at 0x8000 (2 banks) + if (pio_pins & KC85_PIO_RAM8) { + // select one of two RAM banks + uint8_t* ram8_ptr = (sys->io84 & KC85_IO84_SEL_RAM8) ? sys->ram[3] : sys->ram[2]; + if (pio_pins & KC85_PIO_RAM8_RO) { + mem_map_ram(&sys->mem, 0, 0x8000, 0x4000, ram8_ptr); + } + else { + mem_map_rom(&sys->mem, 0, 0x8000, 0x4000, ram8_ptr); + } + } + /* video memory is 4 banks, 2 for pixels, 2 for colors, + the area at 0xA800 to 0xBFFF is always mapped to IRM0! + */ + if (pio_pins & KC85_PIO_IRM) { + uint32_t irm_index = (sys->io84 & 6)>>1; + uint8_t* irm_ptr = sys->ram[KC85_IRM0_PAGE + irm_index]; + /* on the KC85, an access to IRM banks other than the + first is only possible for the first 10 KByte until + A800, memory access to the remaining 6 KBytes + (A800 to BFFF) is always forced to the first IRM bank + by the address decoder hardware (see KC85/4 service manual) + */ + mem_map_ram(&sys->mem, 0, 0x8000, 0x2800, irm_ptr); + + // always force access to 0xA800 and above to the first IRM bank + mem_map_ram(&sys->mem, 0, 0xA800, 0x1800, sys->ram[KC85_IRM0_PAGE] + 0x2800); + } + // 4 KB CAOS-C ROM at 0xC000 (on top of BASIC) + if (sys->io86 & KC85_IO86_CAOS_ROM_C) { + mem_map_rom(&sys->mem, 0, 0xC000, 0x1000, sys->rom_caos_c); + } + #endif // KC85/4 + + // let the module system update it's memory mapping + _kc85_exp_update_memory_mapping(sys); } -#endif static uint64_t _kc85_tick(kc85_t* sys, uint64_t pins) { // tick the CPU @@ -895,10 +933,10 @@ static uint64_t _kc85_tick(kc85_t* sys, uint64_t pins) { } } - // tick the video system, this may return Z80CTC_CLKTRG2 on VSYNC + // tick the video system, may set CLKTRG0..3 pins = _kc85_tick_video(sys, pins); - // tick the CTC, NOTE: Z80CTC_CLKTRG2 may be set from video system + // tick the CTC { // set virtual IEIO pin because CTC is highest priority interrupt device pins |= Z80_IEIO; @@ -908,21 +946,13 @@ static uint64_t _kc85_tick(kc85_t* sys, uint64_t pins) { if (pins & Z80_A0) { pins |= Z80CTC_CS0; } if (pins & Z80_A1) { pins |= Z80CTC_CS1; } pins = z80ctc_tick(&sys->ctc, pins); - // CTC channels 0 and 1 triggers control audio frequencies - if (pins & Z80CTC_ZCTO0) { - beeper_toggle(&sys->beeper_1); - } - if (pins & Z80CTC_ZCTO1) { - beeper_toggle(&sys->beeper_2); - } - // CTC channel 2 trigger controls video blink frequency - if (pins & Z80CTC_ZCTO2) { - sys->blink_flag = !sys->blink_flag; - } + // toggle audio and blink flip flops + sys->flip_flops ^= pins; pins &= Z80_PIN_MASK; } // tick the PIO + bool memory_mapping_dirty = false; { if ((pins & _KC85_PIO_SEL_MASK) == _KC85_PIO_SEL_PINS) { pins |= Z80PIO_CE; @@ -931,41 +961,51 @@ static uint64_t _kc85_tick(kc85_t* sys, uint64_t pins) { if (pins & Z80_A1) { pins |= Z80PIO_CDSEL; } Z80PIO_SET_PAB(pins, 0xFF, 0xFF); pins = z80pio_tick(&sys->pio, pins); - const uint8_t pio_a = Z80PIO_GET_PA(pins); - const uint8_t pio_b = Z80PIO_GET_PB(pins); #if defined(CHIPS_KC85_TYPE_4) - // volume control on KC85/4 - bool volume_dirty = (pio_b ^ sys->pio_b) & KC85_PIO_B_854_VOLUME_MASK; - if (volume_dirty) { - float vol = (((~pio_b) & KC85_PIO_B_854_VOLUME_MASK) >> 1) / 15.0f; + // volume and symmetry-flip-flop control on KC85/4 + if (((pins ^ sys->pio_pins)>>Z80PIO_PIN_PB1) & 0x0F) { + // volume has changed + float vol = ((~pins >> Z80PIO_PIN_PB1) & 0x0F) / 15.0f; beeper_set_volume(&sys->beeper_1, vol); beeper_set_volume(&sys->beeper_2, vol); } + // PIO-B bit 0 cleared forces the audio beeper flip flop to low + if (0 == (pins & Z80PIO_PB0)) { + sys->flip_flops &= ~(KC85_FLIPFLOP_BEEPER_1|KC85_FLIPFLOP_BEEPER_2); + } #else // on KC85/2 and /3, PA4 is connected to CPU NMI pin - if (pio_a & KC85_PIO_A_NMI) { pins &= ~Z80_NMI; } - else { pins |= Z80_NMI; } + if (pins & KC85_PIO_NMI) { pins &= ~Z80_NMI; } + else { pins |= Z80_NMI; } #endif - bool memory_mapping_dirty = ((pio_a ^ sys->pio_a) & KC85_PIO_A_MEMORY_BITS) || - ((pio_b ^ sys->pio_b) & KC85_PIO_B_MEMORY_BITS); - sys->pio_a = pio_a; - sys->pio_b = pio_b; - if (memory_mapping_dirty) { - _kc85_update_memory_map(sys); - } + memory_mapping_dirty |= ((pins^sys->pio_pins) & KC85_PIO_MEMORY_BITS); + sys->pio_pins = pins; pins &= Z80_PIN_MASK; } + // tick the audio beepers + beeper_set(&sys->beeper_1, sys->flip_flops & KC85_FLIPFLOP_BEEPER_1); + beeper_set(&sys->beeper_2, sys->flip_flops & KC85_FLIPFLOP_BEEPER_2); + beeper_tick(&sys->beeper_1); + if (beeper_tick(&sys->beeper_2)) { + // new audio sample ready + sys->audio.sample_buffer[sys->audio.sample_pos++] = sys->beeper_1.sample + sys->beeper_2.sample; + if (sys->audio.sample_pos == sys->audio.num_samples) { + if (sys->audio.callback.func) { + sys->audio.callback.func(sys->audio.sample_buffer, sys->audio.num_samples, sys->audio.callback.user_data); + } + sys->audio.sample_pos = 0; + } + } + // IO port 0x80: expansion module control, high byte of // port address contains module slot address if ((pins & _KC85_EXP_SEL_MASK) == _KC85_EXP_SEL_PINS) { - const uint8_t slot_addr = Z80_GET_ADDR(pins)>>8; + const uint8_t slot_addr = pins>>Z80_PIN_A8; if (pins & Z80_WR) { // write new control byte and update the memory mapping const uint8_t data = Z80_GET_DATA(pins); - if (_kc85_exp_write_ctrl(sys, slot_addr, data)) { - _kc85_update_memory_map(sys); - } + memory_mapping_dirty |= _kc85_exp_write_ctrl(sys, slot_addr, data); } else if (pins & Z80_RD) { // read module id in slot @@ -978,38 +1018,22 @@ static uint64_t _kc85_tick(kc85_t* sys, uint64_t pins) { if ((pins & _KC85_IO84_SEL_MASK) == _KC85_IO84_SEL_PINS) { if (pins & Z80_WR) { const uint8_t data = Z80_GET_DATA(pins); - bool memory_mapping_dirty = (data ^ sys->io84) & KC85_IO84_MEMORY_BITS; + memory_mapping_dirty |= (data ^ sys->io84) & KC85_IO84_MEMORY_BITS; sys->io84 = data; - if (memory_mapping_dirty) { - _kc85_update_memory_map(sys); - } } } if ((pins & _KC85_IO86_SEL_MASK) == _KC85_IO86_SEL_PINS) { if (pins & Z80_WR) { const uint8_t data = Z80_GET_DATA(pins); - bool memory_mapping_dirty = (data ^ sys->io86) & KC85_IO86_MEMORY_BITS; + memory_mapping_dirty |= (data ^ sys->io86) & KC85_IO86_MEMORY_BITS; sys->io86 = data; - if (memory_mapping_dirty) { - _kc85_update_memory_map(sys); - } } } #endif - // tick the audio beepers - beeper_tick(&sys->beeper_1); - if (beeper_tick(&sys->beeper_2)) { - // new audio sample ready - sys->audio.sample_buffer[sys->audio.sample_pos++] = sys->beeper_1.sample + sys->beeper_2.sample; - if (sys->audio.sample_pos == sys->audio.num_samples) { - if (sys->audio.callback.func) { - sys->audio.callback.func(sys->audio.sample_buffer, sys->audio.num_samples, sys->audio.callback.user_data); - } - sys->audio.sample_pos = 0; - } + if (memory_mapping_dirty) { + _kc85_update_memory_map(sys); } - return pins; } @@ -1038,86 +1062,10 @@ uint32_t kc85_exec(kc85_t* sys, uint32_t micro_seconds) { static void _kc85_init_memory_map(kc85_t* sys) { mem_init(&sys->mem); - sys->pio_a = KC85_PIO_A_RAM | KC85_PIO_A_RAM_RO | KC85_PIO_A_IRM | KC85_PIO_A_CAOS_ROM; + sys->pio_pins = KC85_PIO_RAM | KC85_PIO_RAM_RO | KC85_PIO_IRM | KC85_PIO_CAOS_ROM; _kc85_update_memory_map(sys); } -static void _kc85_update_memory_map(kc85_t* sys) { - mem_unmap_layer(&sys->mem, 0); - - // all models have 16 KB builtin RAM at 0x0000 and 8 KB ROM at 0xE000 - if (sys->pio_a & KC85_PIO_A_RAM) { - if (sys->pio_a & KC85_PIO_A_RAM_RO) { - mem_map_ram(&sys->mem, 0, 0x0000, 0x4000, sys->ram[0]); - } - else { - mem_map_rom(&sys->mem, 0, 0x0000, 0x4000, sys->ram[0]); - } - } - if (sys->pio_a & KC85_PIO_A_CAOS_ROM) { - mem_map_rom(&sys->mem, 0, 0xE000, 0x2000, sys->rom_caos_e); - } - - // KC85/3 and KC85/4: builtin 8 KB BASIC ROM at 0xC000 - #if !defined(CHIPS_KC85_TYPE_2) - if (sys->pio_a & KC85_PIO_A_BASIC_ROM) { - mem_map_rom(&sys->mem, 0, 0xC000, 0x2000, sys->rom_basic); - } - #endif - - #if !defined(CHIPS_KC85_TYPE_4) // KC85/2 and /3 - // 16 KB Video RAM at 0x8000 - if (sys->pio_a & KC85_PIO_A_IRM) { - mem_map_ram(&sys->mem, 0, 0x8000, 0x4000, sys->ram[_KC85_IRM0_PAGE]); - } - #else // KC84/4 - // 16 KB RAM at 0x4000 - if (sys->io86 & KC85_IO86_RAM4) { - if (sys->io86 & KC85_IO86_RAM4_RO) { - mem_map_ram(&sys->mem, 0, 0x4000, 0x4000, sys->ram[1]); - } - else { - mem_map_rom(&sys->mem, 0, 0x4000, 0x4000, sys->ram[1]); - } - } - // 16 KB RAM at 0x8000 (2 banks) - if (sys->pio_b & KC85_PIO_B_RAM8) { - // select one of two RAM banks - uint8_t* ram8_ptr = (sys->io84 & KC85_IO84_SEL_RAM8) ? sys->ram[3] : sys->ram[2]; - if (sys->pio_b & KC85_PIO_B_RAM8_RO) { - mem_map_ram(&sys->mem, 0, 0x8000, 0x4000, ram8_ptr); - } - else { - mem_map_rom(&sys->mem, 0, 0x8000, 0x4000, ram8_ptr); - } - } - /* video memory is 4 banks, 2 for pixels, 2 for colors, - the area at 0xA800 to 0xBFFF is always mapped to IRM0! - */ - if (sys->pio_a & KC85_PIO_A_IRM) { - uint32_t irm_index = (sys->io84 & 6)>>1; - uint8_t* irm_ptr = sys->ram[_KC85_IRM0_PAGE + irm_index]; - /* on the KC85, an access to IRM banks other than the - first is only possible for the first 10 KByte until - A800, memory access to the remaining 6 KBytes - (A800 to BFFF) is always forced to the first IRM bank - by the address decoder hardware (see KC85/4 service manual) - */ - mem_map_ram(&sys->mem, 0, 0x8000, 0x2800, irm_ptr); - - // always force access to 0xA800 and above to the first IRM bank - mem_map_ram(&sys->mem, 0, 0xA800, 0x1800, sys->ram[_KC85_IRM0_PAGE] + 0x2800); - } - // 4 KB CAOS-C ROM at 0xC000 (on top of BASIC) - if (sys->io86 & KC85_IO86_CAOS_ROM_C) { - mem_map_rom(&sys->mem, 0, 0xC000, 0x1000, sys->rom_caos_c); - } - #endif // KC85/4 - - // let the module system update it's memory mapping - _kc85_exp_update_memory_mapping(sys); -} - /* KEYBOARD INPUT @@ -1144,7 +1092,7 @@ static void _kc85_handle_keyboard(kc85_t* sys) { // get the first valid key code from the key buffer uint8_t key_code = 0; - for (int i = 0; i < KBD_MAX_PRESSED_KEYS; i++) { + for (size_t i = 0; i < KBD_MAX_PRESSED_KEYS; i++) { if (sys->kbd.key_buffer[i].key != 0) { key_code = sys->kbd.key_buffer[i].key; break; @@ -1200,10 +1148,10 @@ static void _kc85_handle_keyboard(kc85_t* sys) { /*=== EXPANSION MODULE SUBSYSTEM =============================================*/ static void _kc85_exp_init(kc85_t* sys) { // assumes that the entire kc85_t struct was memory cleared already! - CHIPS_ASSERT(2 == KC85_NUM_SLOTS); + CHIPS_ASSERT(2 == KC85_EXP_NUM_SLOTS); sys->exp.slot[0].addr = 0x08; sys->exp.slot[1].addr = 0x0C; - for (int i = 0; i < KC85_NUM_SLOTS; i++) { + for (size_t i = 0; i < KC85_EXP_NUM_SLOTS; i++) { sys->exp.slot[i].mod.id = 0xFF; } } @@ -1241,7 +1189,7 @@ const char* kc85_mod_short_name(kc85_module_type_t mod_type) { kc85_slot_t* kc85_slot_by_addr(kc85_t* sys, uint8_t slot_addr) { CHIPS_ASSERT(sys && sys->valid); - for (int i = 0; i < KC85_NUM_SLOTS; i++) { + for (size_t i = 0; i < KC85_EXP_NUM_SLOTS; i++) { kc85_slot_t* slot = &sys->exp.slot[i]; if (slot_addr == slot->addr) { return slot; @@ -1284,7 +1232,7 @@ uint16_t kc85_slot_cpu_addr(kc85_t* sys, uint8_t slot_addr) { } } -int kc85_slot_mod_size(kc85_t* sys, uint8_t slot_addr) { +uint32_t kc85_slot_mod_size(kc85_t* sys, uint8_t slot_addr) { CHIPS_ASSERT(sys && sys->valid); kc85_slot_t* slot = kc85_slot_by_addr(sys, slot_addr); if (slot) { @@ -1356,7 +1304,7 @@ static void _kc85_exp_free(kc85_t* sys, kc85_slot_t* free_slot) { const uint32_t bytes_to_free = free_slot->mod.size; CHIPS_ASSERT(sys->exp.buf_top >= bytes_to_free); sys->exp.buf_top -= bytes_to_free; - for (int i = 0; i < KC85_NUM_SLOTS; i++) { + for (size_t i = 0; i < KC85_EXP_NUM_SLOTS; i++) { kc85_slot_t* slot = &sys->exp.slot[i]; // no module in slot: nothing to do if (slot->mod.type == KC85_MODULE_NONE) { @@ -1374,7 +1322,7 @@ static void _kc85_exp_free(kc85_t* sys, kc85_slot_t* free_slot) { } } -static bool _kc85_insert_module(kc85_t* sys, uint8_t slot_addr, kc85_module_type_t type, const void* rom_ptr, int rom_size) { +static bool _kc85_insert_module(kc85_t* sys, uint8_t slot_addr, kc85_module_type_t type, chips_range_t rom_data) { kc85_slot_t* slot = kc85_slot_by_addr(sys, slot_addr); if (!slot) { return false; @@ -1422,11 +1370,11 @@ static bool _kc85_insert_module(kc85_t* sys, uint8_t slot_addr, kc85_module_type } // copy optional ROM image, or clear RAM - if (rom_ptr) { - if (rom_size != slot->mod.size) { + if (rom_data.ptr) { + if (rom_data.size != slot->mod.size) { return false; } - memcpy(&sys->exp_buf[slot->buf_offset], rom_ptr, slot->mod.size); + memcpy(&sys->exp_buf[slot->buf_offset], rom_data.ptr, slot->mod.size); } else { memset(&sys->exp_buf[slot->buf_offset], 0, slot->mod.size); @@ -1441,13 +1389,13 @@ static bool _kc85_insert_module(kc85_t* sys, uint8_t slot_addr, kc85_module_type bool kc85_insert_ram_module(kc85_t* sys, uint8_t slot_addr, kc85_module_type_t type) { CHIPS_ASSERT(sys && sys->valid && (type != KC85_MODULE_NONE)); kc85_remove_module(sys, slot_addr); - return _kc85_insert_module(sys, slot_addr, type, 0, 0); + return _kc85_insert_module(sys, slot_addr, type, (chips_range_t){0}); } -bool kc85_insert_rom_module(kc85_t* sys, uint8_t slot_addr, kc85_module_type_t type, const void* rom_ptr, int rom_size) { +bool kc85_insert_rom_module(kc85_t* sys, uint8_t slot_addr, kc85_module_type_t type, chips_range_t rom_data) { CHIPS_ASSERT(sys && sys->valid && (type != KC85_MODULE_NONE)); kc85_remove_module(sys, slot_addr); - return _kc85_insert_module(sys, slot_addr, type, rom_ptr, rom_size); + return _kc85_insert_module(sys, slot_addr, type, rom_data); } bool kc85_remove_module(kc85_t* sys, uint8_t slot_addr) { @@ -1496,7 +1444,7 @@ static uint8_t _kc85_exp_module_id(kc85_t* sys, uint8_t slot_addr) { } static void _kc85_exp_update_memory_mapping(kc85_t* sys) { - for (int i = 0; i < KC85_NUM_SLOTS; i++) { + for (size_t i = 0; i < KC85_EXP_NUM_SLOTS; i++) { const kc85_slot_t* slot = &sys->exp.slot[i]; // nothing to do if no module in slot @@ -1504,10 +1452,10 @@ static void _kc85_exp_update_memory_mapping(kc85_t* sys) { continue; } - /* each slot gets its own memory system mapping layer, - layer 0 is used by computer base unit + /* each slot gets its own memory system mapping layer, + layer 0 is used by computer base unit */ - const int layer = i + 1; + const size_t layer = i + 1; mem_unmap_layer(&sys->mem, layer); // module is only active if bit 0 in control byte is set @@ -1530,6 +1478,19 @@ static void _kc85_exp_update_memory_mapping(kc85_t* sys) { /*=== FILE LOADING ===========================================================*/ +uint16_t kc85_quickload_return_addr(void) { + // FIXME: KC85/2: find proper return address + #if defined(CHIPS_KC85_TYPE_2) + return 0xffff; + #elif defined(CHIPS_KC85_TYPE_3) + return 0xf15c; + #elif defined(CHIPS_KC85_TYPE_4) + return 0xf17e; + #else + #error "unknown KC85 type!" + #endif +} + // common start function for all snapshot file formats static void _kc85_load_start(kc85_t* sys, uint16_t exec_addr) { sys->cpu.a = 0x00; @@ -1546,11 +1507,10 @@ static void _kc85_load_start(kc85_t* sys, uint16_t exec_addr) { mem_wr(&sys->mem, 0xb7a0, 0); #if defined(CHIPS_KC85_TYPE_3) _kc85_tick(sys, Z80_MAKE_PINS(Z80_IORQ|Z80_WR, 0x89, 0x9f)); - mem_wr16(&sys->mem, sys->cpu.sp, 0xf15c); #elif defined(CHIPS_KC85_TYPE_4) _kc85_tick(sys, Z80_MAKE_PINS(Z80_IORQ|Z80_WR, 0x89, 0xff)); - mem_wr16(&sys->mem, sys->cpu.sp, 0xf17e); #endif + mem_wr16(&sys->mem, sys->cpu.sp, kc85_quickload_return_addr()); z80_prefetch(&sys->cpu, exec_addr); } @@ -1578,11 +1538,11 @@ static void _kc85_invoke_patch_callback(kc85_t* sys, const _kc85_kcc_header* hdr } /* KCC files cannot really be identified since they have no magic number */ -static bool _kc85_is_valid_kcc(const uint8_t* ptr, int num_bytes) { - if (num_bytes <= (int) sizeof (_kc85_kcc_header)) { +bool kc85_is_valid_kcc(chips_range_t data) { + if (data.size <= sizeof(_kc85_kcc_header)) { return false; } - const _kc85_kcc_header* hdr = (const _kc85_kcc_header*) ptr; + const _kc85_kcc_header* hdr = (const _kc85_kcc_header*) data.ptr; if (hdr->num_addr > 3) { return false; } @@ -1597,26 +1557,30 @@ static bool _kc85_is_valid_kcc(const uint8_t* ptr, int num_bytes) { return false; } } - int required_len = (end_addr - load_addr) + sizeof(_kc85_kcc_header); - if (required_len > num_bytes) { + size_t required_len = (end_addr - load_addr) + sizeof(_kc85_kcc_header); + if (required_len > data.size) { return false; } /* could be a KCC file */ return true; } -static bool _kc85_load_kcc(kc85_t* sys, const uint8_t* ptr) { - const _kc85_kcc_header* hdr = (_kc85_kcc_header*) ptr; +uint16_t kc85_kcc_exec_addr(chips_range_t data) { + assert(kc85_is_valid_kcc(data)); + const _kc85_kcc_header* hdr = (const _kc85_kcc_header*) data.ptr; + return hdr->exec_addr_h<<8 | hdr->exec_addr_l; +} + +static bool _kc85_load_kcc(kc85_t* sys, chips_range_t data, bool start) { + const _kc85_kcc_header* hdr = (_kc85_kcc_header*) data.ptr; uint16_t addr = hdr->load_addr_h<<8 | hdr->load_addr_l; uint16_t end_addr = hdr->end_addr_h<<8 | hdr->end_addr_l; - ptr += sizeof(_kc85_kcc_header); + const uint8_t* ptr = (const uint8_t*)data.ptr + sizeof(_kc85_kcc_header); while (addr < end_addr) { - /* data is continuous */ mem_wr(&sys->mem, addr++, *ptr++); } _kc85_invoke_patch_callback(sys, hdr); - /* if file has an exec-address, start the program */ - if (hdr->num_addr > 2) { + if (start && (hdr->num_addr > 2)) { _kc85_load_start(sys, hdr->exec_addr_h<<8 | hdr->exec_addr_l); } return true; @@ -1629,13 +1593,13 @@ typedef struct { _kc85_kcc_header kcc; /* from here on identical with KCC */ } _kc85_kctap_header; -static bool _kc85_is_valid_kctap(const uint8_t* ptr, int num_bytes) { - if (num_bytes <= (int) sizeof (_kc85_kctap_header)) { +bool kc85_is_valid_kctap(chips_range_t data) { + if (data.size <= sizeof(_kc85_kctap_header)) { return false; } - const _kc85_kctap_header* hdr = (const _kc85_kctap_header*) ptr; + const _kc85_kctap_header* hdr = (const _kc85_kctap_header*)data.ptr; static uint8_t sig[16] = { 0xC3,'K','C','-','T','A','P','E',0x20,'b','y',0x20,'A','F','.',0x20 }; - for (int i = 0; i < 16; i++) { + for (size_t i = 0; i < 16; i++) { if (sig[i] != hdr->sig[i]) { return false; } @@ -1654,42 +1618,42 @@ static bool _kc85_is_valid_kctap(const uint8_t* ptr, int num_bytes) { return false; } } - int required_len = (end_addr - load_addr) + sizeof(_kc85_kctap_header); - if (required_len > num_bytes) { + size_t required_len = (end_addr - load_addr) + sizeof(_kc85_kctap_header); + if (required_len > data.size) { return false; } /* this appears to be a valid KC TAP file */ return true; } -static bool _kc85_load_kctap(kc85_t* sys, const uint8_t* ptr) { - const _kc85_kctap_header* hdr = (const _kc85_kctap_header*) ptr; +static bool _kc85_load_kctap(kc85_t* sys, chips_range_t data, bool start) { + const _kc85_kctap_header* hdr = (const _kc85_kctap_header*) data.ptr; uint16_t addr = hdr->kcc.load_addr_h<<8 | hdr->kcc.load_addr_l; uint16_t end_addr = hdr->kcc.end_addr_h<<8 | hdr->kcc.end_addr_l; - ptr += sizeof(_kc85_kctap_header); + const uint8_t* ptr = (const uint8_t*)data.ptr + sizeof(_kc85_kctap_header); while (addr < end_addr) { /* each block is 1 lead-byte + 128 bytes data */ ptr++; - for (int i = 0; i < 128; i++) { + for (size_t i = 0; i < 128; i++) { mem_wr(&sys->mem, addr++, *ptr++); } } _kc85_invoke_patch_callback(sys, &hdr->kcc); /* if file has an exec-address, start the program */ - if (hdr->kcc.num_addr > 2) { + if (start && (hdr->kcc.num_addr > 2)) { _kc85_load_start(sys, hdr->kcc.exec_addr_h<<8 | hdr->kcc.exec_addr_l); } return true; } -bool kc85_quickload(kc85_t* sys, const uint8_t* ptr, int num_bytes) { - CHIPS_ASSERT(sys && sys->valid && ptr); +bool kc85_quickload(kc85_t* sys, chips_range_t data, bool start) { + CHIPS_ASSERT(sys && sys->valid && data.ptr); /* first check for KC-TAP format, since this can be properly identified */ - if (_kc85_is_valid_kctap(ptr, num_bytes)) { - return _kc85_load_kctap(sys, ptr); + if (kc85_is_valid_kctap(data)) { + return _kc85_load_kctap(sys, data, start); } - else if (_kc85_is_valid_kcc(ptr, num_bytes)) { - return _kc85_load_kcc(sys, ptr); + else if (kc85_is_valid_kcc(data)) { + return _kc85_load_kcc(sys, data, start); } else { /* not a known file type, or not enough data */ @@ -1707,26 +1671,105 @@ void kc85_key_up(kc85_t* sys, int key_code) { kbd_key_up(&sys->kbd, key_code); } -int kc85_std_display_width(void) { - return _KC85_DISPLAY_WIDTH; -} - -int kc85_std_display_height(void) { - return _KC85_DISPLAY_HEIGHT; -} - -size_t kc85_max_display_size(void) { - return _KC85_DISPLAY_SIZE; +chips_display_info_t kc85_display_info(kc85_t* sys) { + static const uint32_t palette[36] = { + // 16 foreground colors + 0xFF000000, // black + 0xFFFF0000, // blue + 0xFF0000FF, // red + 0xFFFF00FF, // magenta + 0xFF00FF00, // green + 0xFFFFFF00, // cyan + 0xFF00FFFF, // yellow + 0xFFFFFFFF, // white + 0xFF000000, // black #2 + 0xFFFF00A0, // violet + 0xFF00A0FF, // orange + 0xFFA000FF, // purple + 0xFFA0FF00, // blueish green + 0xFFFFA000, // greenish blue + 0xFF00FFA0, // yellow-green + 0xFFFFFFFF, // white #2 + + // 8 background colors + 0xFF000000, // black + 0xFFA00000, // dark-blue + 0xFF0000A0, // dark-red + 0xFFA000A0, // dark-magenta + 0xFF00A000, // dark-green + 0xFFA0A000, // dark-cyan + 0xFF00A0A0, // dark-yellow + 0xFFA0A0A0, // gray + + // padding to get next block at 2^N + 0xFFFF00FF, + 0xFFFF00FF, + 0xFFFF00FF, + 0xFFFF00FF, + 0xFFFF00FF, + 0xFFFF00FF, + 0xFFFF00FF, + 0xFFFF00FF, + + // KC85/4 only: 4 extra HICOLOR colors + 0xFF000000, // black + 0xFF0000FF, // red + 0xFFFFFF00, // cyan + 0xFFFFFFFF, // white + }; + const chips_display_info_t res = { + .frame = { + .dim = { + .width = KC85_FRAMEBUFFER_WIDTH, + .height = KC85_FRAMEBUFFER_HEIGHT, + }, + .bytes_per_pixel = 1, + .buffer = { + .ptr = sys ? sys->fb : 0, + .size = KC85_FRAMEBUFFER_SIZE_BYTES + } + }, + .screen = { + .x = 0, + .y = 0, + .width = KC85_DISPLAY_WIDTH, + .height = KC85_DISPLAY_HEIGHT, + }, + .palette = { + .ptr = (void*)palette, + .size = sizeof(palette) + } + }; + CHIPS_ASSERT(((sys == 0) && (res.frame.buffer.ptr == 0)) || ((sys != 0) && (res.frame.buffer.ptr != 0))); + return res; } -int kc85_display_width(kc85_t* sys) { - (void)sys; - return _KC85_DISPLAY_WIDTH; +uint32_t kc85_save_snapshot(kc85_t* sys, kc85_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->debug); + chips_audio_callback_snapshot_onsave(&dst->audio.callback); + dst->patch_callback.func = 0; + dst->patch_callback.user_data = 0; + mem_snapshot_onsave(&dst->mem, sys); + return KC85_SNAPSHOT_VERSION; } -int kc85_display_height(kc85_t* sys) { - (void)sys; - return _KC85_DISPLAY_HEIGHT; +// load a snapshot, returns false if snapshot version doesn't match +bool kc85_load_snapshot(kc85_t* sys, uint32_t version, const kc85_t* src) { + CHIPS_ASSERT(sys && src); + if (version != KC85_SNAPSHOT_VERSION) { + return false; + } + // intermediate copy + static kc85_t im; + im = *src; + chips_debug_snapshot_onload(&im.debug, &sys->debug); + chips_audio_callback_snapshot_onload(&im.audio.callback, &sys->audio.callback); + im.patch_callback = sys->patch_callback; + mem_snapshot_onload(&im.mem, sys); + *sys = im; + return true; } #endif /* CHIPS_IMPL */ diff --git a/systems/lc80.h b/systems/lc80.h index fdfa0aa3..e8060270 100644 --- a/systems/lc80.h +++ b/systems/lc80.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -20,6 +20,7 @@ You need to include the following headers before including lc80.h: + - chips/chips_common.h - chips/z80.h - chips/z80ctc.h - chips/z80pio.h @@ -48,7 +49,7 @@ - 3x VQE23 2-digits LED display block (equiv ???) TODO: more details about the hardware and emulator - + ## zlib/libpng license Copyright (c) 2019 Andre Weissflog @@ -65,7 +66,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -75,6 +76,9 @@ extern "C" { #endif +// bump this whenever the lc80_t struct layout changes +#define LC80_SNAPSHOT_VERSION (0x0001) + // key codes (for lc80_key(), lc80_key_down(), lc80_key_up() #define LC80_KEY_0 ('0') #define LC80_KEY_1 ('1') @@ -181,41 +185,17 @@ extern "C" { #define LC80_VQE23_K1 (1ULL<<16) #define LC80_VQE23_K2 (1ULL<<17) -typedef struct { - void (*func)(const float* samples, int num_samples, void* user_data); - void* user_data; -} lc80_audio_callback_t; - #define LC80_MAX_AUDIO_SAMPLES (1024) #define LC80_DEFAULT_AUDIO_SAMPLES (128) -// debugging hook definitions -typedef void (*lc80_debug_func_t)(void* user_data, uint64_t pins); -typedef struct { - struct { - lc80_debug_func_t func; - void* user_data; - } callback; - bool* stopped; -} lc80_debug_t; - // config parameters for lc80_init() typedef struct { - lc80_debug_t debug; // optional debugger hook - - // audio output config (if you don't want audio, set audio_cb to zero) - struct { - lc80_audio_callback_t callback; // called when audio_num_samples are ready - int num_samples; // default is LC80_DEFAULT_AUDIO_SAMPLES - int sample_rate; // playback sample rate, default is 44100 - float volume; // audio volume (0.0 .. 1.0), default is 0.4 - } audio; - - // ROM image (must be single 2KByte image) - struct { const void* ptr; size_t size; } rom; + chips_debug_t debug; + chips_audio_desc_t audio; + chips_range_t rom; } lc80_desc_t; -/* LC80 emulator state */ +// LC80 emulator state typedef struct { z80_t cpu; z80ctc_t ctc; @@ -232,13 +212,13 @@ typedef struct { bool valid; uint64_t pins; - lc80_debug_t debug; + chips_debug_t debug; kbd_t kbd; uint32_t freq_hz; struct { - lc80_audio_callback_t callback; + chips_audio_callback_t callback; int num_samples; int sample_pos; float sample_buffer[LC80_MAX_AUDIO_SAMPLES]; @@ -255,6 +235,8 @@ uint32_t lc80_exec(lc80_t* sys, uint32_t micro_seconds); void lc80_key_down(lc80_t* sys, int key_code); void lc80_key_up(lc80_t* sys, int key_code); void lc80_key(lc80_t* sys, int key_code); // down + up +uint32_t lc80_save_snapshot(lc80_t* sys, lc80_t* dst); // capture snapshot, return snapshot layout version +bool lc80_load_snapshot(lc80_t* sys, uint32_t version, lc80_t* src); // load snapshot, return false if version didn't match #ifdef __cplusplus } /* extern "C" */ @@ -273,11 +255,11 @@ void lc80_key(lc80_t* sys, int key_code); // down + up void lc80_init(lc80_t* sys, const lc80_desc_t* desc) { CHIPS_ASSERT(sys && desc); if (desc->debug.callback.func) { CHIPS_ASSERT(desc->debug.stopped); } - + memset(sys, 0, sizeof(lc80_t)); sys->valid = true; sys->debug = desc->debug; - + CHIPS_ASSERT(desc->rom.ptr && (desc->rom.size == sizeof(sys->rom))); memcpy(sys->rom, desc->rom.ptr, sizeof(sys->rom)); @@ -352,7 +334,7 @@ void lc80_reset(lc80_t* sys) { #define _LC80_HI(pins,mask) (0!=(pins&mask)) #define _LC80_LO(pins,mask) (0==(pins&mask)) -// DS8205 (LS138) 3-to-8 decoder +// DS8205 (LS138) 3-to-8 decoder static inline uint32_t _lc80_ds8205_tick(uint32_t inp) { /* enable = G1 && !G2A && !G2B @@ -471,7 +453,7 @@ uint64_t _lc80_tick(lc80_t* sys, uint64_t pins) { sys->u214[0] = pins & (Z80_WR | 0x0F03FF); sys->u214[1] = (pins & (Z80_WR | 0x0003FF)) | ((pins & 0xF00000)>>4); } - + // tick CTC first (because it's the highest priority daisychain device { pins |= Z80_IEIO; @@ -641,4 +623,25 @@ void lc80_key(lc80_t* sys, int key_code) { lc80_key_up(sys, key_code); } +uint32_t lc80_save_snapshot(lc80_t* sys, lc80_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->debug); + chips_audio_callback_snapshot_onsave(&dst->audio.callback); + return LC80_SNAPSHOT_VERSION; +} + +bool lc80_load_snapshot(lc80_t* sys, uint32_t version, lc80_t* src) { + CHIPS_ASSERT(sys && src); + if (version != LC80_SNAPSHOT_VERSION) { + return false; + } + static lc80_t im; + im = *src; + chips_debug_snapshot_onload(&im.debug, &sys->debug); + chips_audio_callback_snapshot_onload(&im.audio.callback, &sys->audio.callback); + *sys = im; + return true; +} + #endif /* CHIPS_IMPL */ diff --git a/systems/namco.h b/systems/namco.h index e5a0f8ac..a0134d14 100644 --- a/systems/namco.h +++ b/systems/namco.h @@ -12,11 +12,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -30,6 +30,7 @@ You need to include the following headers before including namco.h: + - chips/chips_common.h - chips/z80.h - chips/clk.h - chips/mem.h @@ -55,18 +56,27 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include #include +#include #ifdef __cplusplus extern "C" { #endif +// increase when namco_t memory layout changes +#define NAMCO_SNAPSHOT_VERSION (1) + #define NAMCO_MAX_AUDIO_SAMPLES (1024) #define NAMCO_DEFAULT_AUDIO_SAMPLES (128) +#define NAMCO_FRAMEBUFFER_WIDTH (512) +#define NAMCO_FRAMEBUFFER_HEIGHT (224) +#define NAMCO_FRAMEBUFFER_SIZE_BYTES (NAMCO_FRAMEBUFFER_WIDTH * NAMCO_FRAMEBUFFER_HEIGHT) +#define NAMCO_DISPLAY_WIDTH (288) +#define NAMCO_DISPLAY_HEIGHT (224) // input bits (use with namco_input_set() and namco_input_clear()) #define NAMCO_INPUT_P1_UP (1<<0) @@ -84,73 +94,36 @@ extern "C" { #define NAMCO_INPUT_P2_COIN (1<<12) #define NAMCO_INPUT_P2_START (1<<13) -// audio callback -typedef struct { - void (*func)(const float* samples, int num_samples, void* user_data); - void* user_data; -} namco_audio_callback_t; - -// debugging hook definitions -typedef void (*namco_debug_func_t)(void* user_data, uint64_t pins); -typedef struct { - struct { - namco_debug_func_t func; - void* user_data; - } callback; - bool* stopped; -} namco_debug_t; - -typedef struct { - const void* ptr; - size_t size; -} namco_rom_image_t; - // configuration parameters for namco_init() typedef struct { - // optional debugging hook - namco_debug_t debug; - - // video output config - struct { - void* ptr; // pointer to a linear RGBA8 pixel buffer, at least 224*256*4 bytes - size_t size; // size of the pixel buffer in bytes - } pixel_buffer; - - // audio output config (if you don't want audio, set audio.callback.func to zero) - struct { - namco_audio_callback_t callback; // called when audio_num_samples are ready - int num_samples; // default is NAMCO_DEFAULT_AUDIO_SAMPLES - int sample_rate; // playback sample rate, default is 44100 - float volume; // audio volume, 0.0..1.0, default is 1.0 - } audio; - - // ROM images + chips_debug_t debug; + chips_audio_desc_t audio; struct { // common ROM areas for Pacman and Pengo struct { - namco_rom_image_t cpu_0000_0FFF; - namco_rom_image_t cpu_1000_1FFF; - namco_rom_image_t cpu_2000_2FFF; - namco_rom_image_t cpu_3000_3FFF; - namco_rom_image_t prom_0000_001F; - namco_rom_image_t sound_0000_00FF; - namco_rom_image_t sound_0100_01FF; + chips_range_t cpu_0000_0FFF; + chips_range_t cpu_1000_1FFF; + chips_range_t cpu_2000_2FFF; + chips_range_t cpu_3000_3FFF; + chips_range_t prom_0000_001F; + chips_range_t sound_0000_00FF; + chips_range_t sound_0100_01FF; } common; // Pengo specific ROM areas struct { - namco_rom_image_t cpu_4000_4FFF; - namco_rom_image_t cpu_5000_5FFF; - namco_rom_image_t cpu_6000_6FFF; - namco_rom_image_t cpu_7000_7FFF; - namco_rom_image_t gfx_0000_1FFF; - namco_rom_image_t gfx_2000_3FFF; - namco_rom_image_t prom_0020_041F; + chips_range_t cpu_4000_4FFF; + chips_range_t cpu_5000_5FFF; + chips_range_t cpu_6000_6FFF; + chips_range_t cpu_7000_7FFF; + chips_range_t gfx_0000_1FFF; + chips_range_t gfx_2000_3FFF; + chips_range_t prom_0020_041F; } pengo; // Pacman specific ROM areas struct { - namco_rom_image_t gfx_0000_0FFF; - namco_rom_image_t gfx_1000_1FFF; - namco_rom_image_t prom_0020_011F; + chips_range_t gfx_0000_0FFF; + chips_range_t gfx_1000_1FFF; + chips_range_t prom_0020_011F; } pacman; } roms; } namco_desc_t; @@ -172,13 +145,14 @@ typedef struct { uint8_t rom[2][0x0100]; // wave table ROM int num_samples; int sample_pos; - namco_audio_callback_t callback; + chips_audio_callback_t callback; float sample_buffer[NAMCO_MAX_AUDIO_SAMPLES]; } namco_sound_t; // the Namco arcade machine state typedef struct { z80_t cpu; + mem_t mem; uint8_t in0; // inverted bits (active-low) uint8_t in1; // inverted bits (active-low) uint8_t dsw1; // dip-switches as-is (active-high) @@ -193,14 +167,10 @@ typedef struct { uint8_t clut_select; // Pengo only uint8_t tile_select; // Pengo only uint8_t sprite_coords[16]; // 8 sprites, uint8_t x, uint8_t y - mem_t mem; bool valid; - namco_debug_t debug; + chips_debug_t debug; - uint32_t* pixel_buffer; - uint32_t palette_cache[512]; // precomputed RGBA values, Pacman: 256 entries , Pengo: 512 entries - void* user_data; namco_sound_t sound; uint8_t video_ram[0x0400]; uint8_t color_ram[0x0400]; @@ -208,6 +178,9 @@ typedef struct { uint8_t rom_cpu[0x8000]; // program ROM: Pacman: 16 KB, Pengo: 32 KB uint8_t rom_gfx[0x4000]; // tile ROM: Pacman: 8 KB, Pengo: 16 KB uint8_t rom_prom[0x0420]; // palette and color lookup ROM + uint32_t hw_colors[32]; // decoded color palette from palette ROM + uint8_t palette_cache[512]; // palette indirection table, Pacman: 256 entries , Pengo: 512 entries + alignas(64) uint8_t fb[NAMCO_FRAMEBUFFER_SIZE_BYTES]; // indices into palette } namco_t; // initialize a new namco_t instance @@ -216,18 +189,18 @@ void namco_init(namco_t* sys, const namco_desc_t* desc); void namco_discard(namco_t* sys); // reset a namco_t instance void namco_reset(namco_t* sys); +// query display, framebuffer and color palette (note: palette requires a valid sys ptr!) +chips_display_info_t namco_display_info(namco_t* sys); // run namco_t instance for given amount of microseconds, return number of ticks executed uint32_t namco_exec(namco_t* sys, uint32_t micro_seconds); // set input bits void namco_input_set(namco_t* sys, uint32_t mask); // clear input bits void namco_input_clear(namco_t* sys, uint32_t mask); -// get the standard framebuffer width and height in pixels -int namco_std_display_width(void); -int namco_std_display_height(void); -// get the current framebuffer width and height in pixels -int namco_display_width(namco_t* sys); -int namco_display_height(namco_t* sys); +// take a snapshot, patches any pointers to zero, returns a snapshot version +uint32_t namco_save_snapshot(namco_t* sys, namco_t* dst); +// load a snapshot, returns false if snapshot version doesn't match +bool namco_load_snapshot(namco_t* sys, uint32_t version, namco_t* src); #ifdef __cplusplus } // extern "C" @@ -407,9 +380,6 @@ int namco_display_height(namco_t* sys); #define NAMCO_SOUND_OVERSAMPLE (2) #define NAMCO_SAMPLE_SCALE (16) #define NAMCO_VSYNC_PERIOD (NAMCO_CPU_CLOCK / 60) -#define NAMCO_DISPLAY_WIDTH (288) -#define NAMCO_DISPLAY_HEIGHT (224) -#define NAMCO_DISPLAY_SIZE (NAMCO_DISPLAY_WIDTH*NAMCO_DISPLAY_HEIGHT*4) static void _namco_sound_init(namco_t* sys, const namco_desc_t* desc); static void _namco_sound_wr(namco_t* sys, uint16_t addr, uint8_t data); @@ -419,12 +389,10 @@ static void _namco_sound_tick(namco_t* sys); void namco_init(namco_t* sys, const namco_desc_t* desc) { CHIPS_ASSERT(sys && desc); - CHIPS_ASSERT((0 == desc->pixel_buffer.ptr) || (desc->pixel_buffer.ptr && (desc->pixel_buffer.size >= NAMCO_DISPLAY_SIZE))); if (desc->debug.callback.func) { CHIPS_ASSERT(desc->debug.stopped); } memset(sys, 0, sizeof(namco_t)); sys->valid = true; - sys->pixel_buffer = (uint32_t*) desc->pixel_buffer.ptr; sys->debug = desc->debug; sys->vsync_count = NAMCO_VSYNC_PERIOD; _namco_sound_init(sys, desc); @@ -533,8 +501,7 @@ void namco_init(namco_t* sys, const namco_desc_t* desc) { #endif // setup an RGBA palette from the 8-bit RGB values in PROM - uint32_t hw_colors[32]; - for (int i = 0; i < 32; i++) { + for (size_t i = 0; i < 32; i++) { /* Each color ROM entry describes an RGB color in 1 byte: @@ -547,12 +514,12 @@ void namco_init(namco_t* sys, const namco_desc_t* desc) { uint8_t r = ((rgb>>0)&1) * 0x21 + ((rgb>>1)&1) * 0x47 + ((rgb>>2)&1) * 0x97; uint8_t g = ((rgb>>3)&1) * 0x21 + ((rgb>>4)&1) * 0x47 + ((rgb>>5)&1) * 0x97; uint8_t b = ((rgb>>6)&1) * 0x47 + ((rgb>>7)&1) * 0x97; - hw_colors[i] = 0xFF000000 | (b<<16) | (g<<8) | r; + sys->hw_colors[i] = 0xFF000000 | (b<<16) | (g<<8) | r; } - for (int i = 0; i < 256; i++) { + for (size_t i = 0; i < 256; i++) { uint8_t pal_index = sys->rom_prom[i + 0x20] & 0xF; - sys->palette_cache[i] = hw_colors[pal_index]; - sys->palette_cache[256 + i] = hw_colors[0x10 | pal_index]; + sys->palette_cache[i] = pal_index; + sys->palette_cache[256 + i] = 0x10 | pal_index; } } @@ -579,6 +546,7 @@ static uint64_t _namco_tick(namco_t* sys, uint64_t pins) { // tick the sound chip _namco_sound_tick(sys); + // tick the cpu pins = z80_tick(&sys->cpu, pins); // memory requests @@ -687,9 +655,10 @@ static uint16_t _namco_video_offset(uint32_t x, uint32_t y) { // 8x4 video tile decoder (used both for background tiles and sprites) static inline void _namco_8x4( - uint32_t* pixel_base, + uint8_t* fb, uint8_t* tile_base, - uint32_t* palette_base, + uint8_t* pal_base, + uint8_t* pal_rom, uint32_t tile_stride, uint32_t tile_offset, uint32_t px, @@ -707,7 +676,7 @@ static inline void _namco_8x4( if (y >= NAMCO_DISPLAY_HEIGHT) { continue; } - int tile_index = char_code*tile_stride + tile_offset + yy; + int tile_index = char_code * tile_stride + tile_offset + yy; for (uint32_t xx = 0; xx < 4; xx++) { uint32_t x = px + (xx ^ xor_x); if (x >= NAMCO_DISPLAY_WIDTH) { @@ -716,10 +685,9 @@ static inline void _namco_8x4( uint8_t p2_hi = (tile_base[tile_index]>>(7-xx)) & 1; uint8_t p2_lo = (tile_base[tile_index]>>(3-xx)) & 1; uint8_t p2 = (p2_hi<<1)|p2_lo; - uint32_t rgba = palette_base[(color_code<<2)|p2]; - if (opaque || (rgba != 0xFF000000)) { - uint32_t* dst = &pixel_base[y*NAMCO_DISPLAY_WIDTH + x]; - *dst = rgba; + uint8_t hw_color = pal_base[(color_code<<2)|p2]; + if (opaque || (pal_rom[hw_color] != 0)) { + fb[y * NAMCO_FRAMEBUFFER_WIDTH + x] = hw_color; } } } @@ -727,23 +695,21 @@ static inline void _namco_8x4( // decode background tiles static void _namco_decode_chars(namco_t* sys) { - uint32_t* pixel_base = sys->pixel_buffer; - uint32_t* pal_base = &sys->palette_cache[(sys->pal_select<<8)|(sys->clut_select<<7)]; + uint8_t* pal_base = &sys->palette_cache[(sys->pal_select<<8)|(sys->clut_select<<7)]; uint8_t* tile_base = &sys->rom_gfx[0x0000] + (sys->tile_select * 0x2000); for (uint32_t y = 0; y < 28; y++) { for (uint32_t x = 0; x < 36; x++) { uint16_t offset = _namco_video_offset(x, y); uint8_t char_code = sys->video_ram[offset]; uint8_t color_code = sys->color_ram[offset] & 0x1F; - _namco_8x4(pixel_base, tile_base, pal_base, 16, 8, x*8, y*8, char_code, color_code, true, false, false); - _namco_8x4(pixel_base, tile_base, pal_base, 16, 0, x*8+4, y*8, char_code, color_code, true, false, false); + _namco_8x4(sys->fb, tile_base, pal_base, sys->rom_prom, 16, 8, x*8, y*8, char_code, color_code, true, false, false); + _namco_8x4(sys->fb, tile_base, pal_base, sys->rom_prom, 16, 0, x*8+4, y*8, char_code, color_code, true, false, false); } } } static void _namco_decode_sprites(namco_t* sys) { - uint32_t* pixel_base = sys->pixel_buffer; - uint32_t* pal_base = &sys->palette_cache[(sys->pal_select<<8)|(sys->clut_select<<7)]; + uint8_t* pal_base = &sys->palette_cache[(sys->pal_select<<8)|(sys->clut_select<<7)]; uint8_t* tile_base = &sys->rom_gfx[0x1000] + (sys->tile_select * 0x2000); #if defined(NAMCO_PACMAN) const int max_sprite = 6; @@ -766,23 +732,21 @@ static void _namco_decode_sprites(namco_t* sys) { uint32_t fx1 = flip_x ? 8 : 4; uint32_t fx2 = flip_x ? 4 : 8; uint32_t fx3 = flip_x ? 0 :12; - _namco_8x4(pixel_base, tile_base, pal_base, 64, 8, px+fx0, py+fy0, char_code, color_code, false, flip_x, flip_y); - _namco_8x4(pixel_base, tile_base, pal_base, 64, 16, px+fx1, py+fy0, char_code, color_code, false, flip_x, flip_y); - _namco_8x4(pixel_base, tile_base, pal_base, 64, 24, px+fx2, py+fy0, char_code, color_code, false, flip_x, flip_y); - _namco_8x4(pixel_base, tile_base, pal_base, 64, 0, px+fx3, py+fy0, char_code, color_code, false, flip_x, flip_y); - _namco_8x4(pixel_base, tile_base, pal_base, 64, 40, px+fx0, py+fy1, char_code, color_code, false, flip_x, flip_y); - _namco_8x4(pixel_base, tile_base, pal_base, 64, 48, px+fx1, py+fy1, char_code, color_code, false, flip_x, flip_y); - _namco_8x4(pixel_base, tile_base, pal_base, 64, 56, px+fx2, py+fy1, char_code, color_code, false, flip_x, flip_y); - _namco_8x4(pixel_base, tile_base, pal_base, 64, 32, px+fx3, py+fy1, char_code, color_code, false, flip_x, flip_y); + _namco_8x4(sys->fb, tile_base, pal_base, sys->rom_prom, 64, 8, px+fx0, py+fy0, char_code, color_code, false, flip_x, flip_y); + _namco_8x4(sys->fb, tile_base, pal_base, sys->rom_prom, 64, 16, px+fx1, py+fy0, char_code, color_code, false, flip_x, flip_y); + _namco_8x4(sys->fb, tile_base, pal_base, sys->rom_prom, 64, 24, px+fx2, py+fy0, char_code, color_code, false, flip_x, flip_y); + _namco_8x4(sys->fb, tile_base, pal_base, sys->rom_prom, 64, 0, px+fx3, py+fy0, char_code, color_code, false, flip_x, flip_y); + _namco_8x4(sys->fb, tile_base, pal_base, sys->rom_prom, 64, 40, px+fx0, py+fy1, char_code, color_code, false, flip_x, flip_y); + _namco_8x4(sys->fb, tile_base, pal_base, sys->rom_prom, 64, 48, px+fx1, py+fy1, char_code, color_code, false, flip_x, flip_y); + _namco_8x4(sys->fb, tile_base, pal_base, sys->rom_prom, 64, 56, px+fx2, py+fy1, char_code, color_code, false, flip_x, flip_y); + _namco_8x4(sys->fb, tile_base, pal_base, sys->rom_prom, 64, 32, px+fx3, py+fy1, char_code, color_code, false, flip_x, flip_y); } } void _namco_decode_video(namco_t* sys) { CHIPS_ASSERT(sys && sys->valid); - if (sys->pixel_buffer) { - _namco_decode_chars(sys); - _namco_decode_sprites(sys); - } + _namco_decode_chars(sys); + _namco_decode_sprites(sys); } uint32_t namco_exec(namco_t* sys, uint32_t micro_seconds) { @@ -907,28 +871,6 @@ void namco_input_clear(namco_t* sys, uint32_t mask) { } } -int namco_std_display_width(void) { - return NAMCO_DISPLAY_WIDTH; -} - -int namco_display_size(void) { - return NAMCO_DISPLAY_SIZE; -} - -int namco_std_display_height(void) { - return NAMCO_DISPLAY_HEIGHT; -} - -int namco_display_width(namco_t* sys) { - (void)sys; - return NAMCO_DISPLAY_WIDTH; -} - -int namco_display_height(namco_t* sys) { - (void)sys; - return NAMCO_DISPLAY_HEIGHT; -} - static void _namco_sound_init(namco_t* sys, const namco_desc_t* desc) { CHIPS_ASSERT(desc->audio.num_samples <= NAMCO_MAX_AUDIO_SAMPLES); // assume zero-initialized @@ -1029,4 +971,58 @@ static void _namco_sound_tick(namco_t* sys) { } } } + +chips_display_info_t namco_display_info(namco_t* sys) { + const chips_display_info_t res = { + .frame = { + .dim = { + .width = NAMCO_FRAMEBUFFER_WIDTH, + .height = NAMCO_FRAMEBUFFER_HEIGHT, + }, + .bytes_per_pixel = 1, + .buffer = { + .ptr = sys ? sys->fb : 0, + .size = NAMCO_FRAMEBUFFER_SIZE_BYTES, + }, + }, + .screen = { + .x = 0, + .y = 0, + .width = NAMCO_DISPLAY_WIDTH, + .height = NAMCO_DISPLAY_HEIGHT, + }, + .palette = { + .ptr = sys ? sys->hw_colors : 0, + .size = 32 * sizeof(uint32_t) + }, + .portrait = true, + }; + CHIPS_ASSERT(((sys == 0) && (res.frame.buffer.ptr == 0)) || ((sys != 0) && (res.frame.buffer.ptr != 0))); + CHIPS_ASSERT(((sys == 0) && (res.palette.ptr == 0)) || ((sys != 0) && (res.palette.ptr != 0))); + return res; +} + +uint32_t namco_save_snapshot(namco_t* sys, namco_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->debug); + chips_audio_callback_snapshot_onsave(&dst->sound.callback); + mem_snapshot_onsave(&dst->mem, sys); + return NAMCO_SNAPSHOT_VERSION; +} + +bool namco_load_snapshot(namco_t* sys, uint32_t version, namco_t* src) { + CHIPS_ASSERT(sys && src); + if (version != NAMCO_SNAPSHOT_VERSION) { + return false; + } + static namco_t im; + im = *src; + chips_debug_snapshot_onload(&im.debug, &sys->debug); + chips_audio_callback_snapshot_onload(&im.sound.callback, &sys->sound.callback); + mem_snapshot_onload(&im.mem, sys); + *sys = im; + return true; +} + #endif // CHIPS_IMPL diff --git a/systems/vic20.h b/systems/vic20.h index 77d4bad1..b75ffb1e 100644 --- a/systems/vic20.h +++ b/systems/vic20.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -20,6 +20,7 @@ You need to include the following headers before including vic20.h: + - chips/chips_common.h - chips/m6502.h - chips/m6522.h - chips/m6561.h @@ -53,7 +54,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -63,93 +64,60 @@ extern "C" { #endif +// bump snapshot version when vic20_t memory layout changes +#define VIC20_SNAPSHOT_VERSION (1) + #define VIC20_FREQUENCY (1108404) #define VIC20_MAX_AUDIO_SAMPLES (1024) // max number of audio samples in internal sample buffer -#define VIC20_DEFAULT_AUDIO_SAMPLES (128) // default number of samples in internal sample buffer +#define VIC20_DEFAULT_AUDIO_SAMPLES (128) // default number of samples in internal sample buffer -/* VIC-20 joystick types (only one joystick supported) */ +// VIC-20 joystick types (only one joystick supported) typedef enum { VIC20_JOYSTICKTYPE_NONE, VIC20_JOYSTICKTYPE_DIGITAL, } vic20_joystick_type_t; -/* memory configuration (used in vic20_desc_t.mem_config) */ +// memory configuration (used in vic20_desc_t.mem_config) typedef enum { - VIC20_MEMCONFIG_STANDARD, /* unexpanded */ - VIC20_MEMCONFIG_8K, /* Block 1 */ - VIC20_MEMCONFIG_16K, /* Block 1+2 */ - VIC20_MEMCONFIG_24K, /* Block 1+2+3 */ - VIC20_MEMCONFIG_32K, /* Block 1+2+3+5 (note that BASIC can only use blocks 1+2+3) */ - VIC20_MEMCONFIG_MAX /* 32K + 3KB at 0400..0FFF */ + VIC20_MEMCONFIG_STANDARD, // unexpanded + VIC20_MEMCONFIG_8K, // Block 1 + VIC20_MEMCONFIG_16K, // Block 1+2 + VIC20_MEMCONFIG_24K, // Block 1+2+3 + VIC20_MEMCONFIG_32K, // Block 1+2+3+5 (note that BASIC can only use blocks 1+2+3) + VIC20_MEMCONFIG_MAX // 32K + 3KB at 0400..0FFF } vic20_memory_config_t; -/* joystick mask bits */ +// joystick mask bits #define VIC20_JOYSTICK_UP (1<<0) #define VIC20_JOYSTICK_DOWN (1<<1) #define VIC20_JOYSTICK_LEFT (1<<2) #define VIC20_JOYSTICK_RIGHT (1<<3) #define VIC20_JOYSTICK_BTN (1<<4) -/* casette port bits, same as C1530_CASPORT_* */ +// casette port bits, same as C1530_CASPORT_* #define VIC20_CASPORT_MOTOR (1<<0) #define VIC20_CASPORT_READ (1<<1) #define VIC20_CASPORT_WRITE (1<<2) #define VIC20_CASPORT_SENSE (1<<3) -/* IEC port bits, same as C1541_IECPORT_* */ +// IEC port bits, same as C1541_IECPORT_* #define VIC20_IECPORT_RESET (1<<0) #define VIC20_IECPORT_SRQIN (1<<1) #define VIC20_IECPORT_DATA (1<<2) #define VIC20_IECPORT_CLK (1<<3) #define VIC20_IECPORT_ATN (1<<4) -// audio sample callback -typedef struct { - void (*func)(const float* samples, int num_samples, void* user_data); - void* user_data; -} vic20_audio_callback_t; - -// debugging hook definitions -typedef void (*vic20_debug_func_t)(void* user_data, uint64_t pins); -typedef struct { - struct { - vic20_debug_func_t func; - void* user_data; - } callback; - bool* stopped; -} vic20_debug_t; - -typedef struct { - const void* ptr; - size_t size; -} vic20_rom_image_t; - // config parameters for vic20_init() typedef struct { - bool c1530_enabled; // set to true to enable C1530 datassette emulation + bool c1530_enabled; // set to true to enable C1530 datassette emulation vic20_joystick_type_t joystick_type; // default is VIC20_JOYSTICK_NONE vic20_memory_config_t mem_config; // default is VIC20_MEMCONFIG_STANDARD - vic20_debug_t debug; // optional debugging hook - - // video output config (if you don't want video decoding, set these to 0) + chips_debug_t debug; // optional debugging hook + chips_audio_desc_t audio; struct { - void* ptr; // pointer to a linear RGBA8 pixel buffer, query required size via vic20_max_display_size() - size_t size; // size of the pixel buffer in bytes - } pixel_buffer; - - // audio output config (if you don't want audio, set audio.callback.func to zero - struct { - vic20_audio_callback_t callback; // called when audio_num_samples are ready - int num_samples; // default is VIC20_AUDIO_NUM_SAMPLES - int sample_rate; // playback sample rate in Hz, default is 44100 - float volume; // audio volume of the VIC chip (0.0 .. 1.0), default is 1.0 - } audio; - - // ROM images - struct { - vic20_rom_image_t chars; // 4 KByte character ROM dump - vic20_rom_image_t basic; // 8 KByte BASIC dump - vic20_rom_image_t kernal; // 8 KByte KERNAL dump + chips_range_t chars; // 4 KByte character ROM dump + chips_range_t basic; // 8 KByte BASIC dump + chips_range_t kernal; // 8 KByte KERNAL dump } roms; } vic20_desc_t; @@ -174,11 +142,10 @@ typedef struct { mem_t mem_cpu; // CPU-visible memory mapping mem_t mem_vic; // VIC-visible memory mapping bool valid; - vic20_debug_t debug; + chips_debug_t debug; - uint32_t* pixel_buffer; struct { - vic20_audio_callback_t callback; + chips_audio_callback_t callback; int num_samples; int sample_pos; float sample_buffer[VIC20_MAX_AUDIO_SAMPLES]; @@ -192,6 +159,7 @@ typedef struct { uint8_t rom_basic[0x2000]; // 8 KB BASIC ROM image uint8_t rom_kernal[0x2000]; // 8 KB KERNAL V3 ROM image uint8_t ram_exp[4][0x2000]; // optional expansion 8K RAM blocks + uint8_t fb[M6561_FRAMEBUFFER_SIZE_BYTES]; c1530_t c1530; // c1530.valid = true if enabled @@ -204,6 +172,8 @@ void vic20_init(vic20_t* sys, const vic20_desc_t* desc); void vic20_discard(vic20_t* sys); // reset a VIC-20 instance void vic20_reset(vic20_t* sys); +// query display information +chips_display_info_t vic20_display_info(vic20_t* sys); // tick VIC-20 instance for a given number of microseconds, return number of executed ticks uint32_t vic20_exec(vic20_t* sys, uint32_t micro_seconds); // send a key-down event to the VIC-20 @@ -217,13 +187,13 @@ vic20_joystick_type_t vic20_joystick_type(vic20_t* sys); // set joystick mask (combination of VIC20_JOYSTICK_*) void vic20_joystick(vic20_t* sys, uint8_t joy_mask); // quickload a .prg/.bin file -bool vic20_quickload(vic20_t* sys, const uint8_t* ptr, int num_bytes); +bool vic20_quickload(vic20_t* sys, chips_range_t data); // load a .prg/.bin file as ROM cartridge -bool vic20_insert_rom_cartridge(vic20_t* sys, const uint8_t* ptr, int num_bytes); +bool vic20_insert_rom_cartridge(vic20_t* sys, chips_range_t data); // remove current ROM cartridge void vic20_remove_rom_cartridge(vic20_t* sys); // insert tape as .TAP file (c1530 must be enabled) -bool vic20_insert_tape(vic20_t* sys, const uint8_t* ptr, int num_bytes); +bool vic20_insert_tape(vic20_t* sys, chips_range_t data); // remove tape file void vic20_remove_tape(vic20_t* sys); // return true if a tape is currently inserted @@ -234,14 +204,10 @@ void vic20_tape_play(vic20_t* sys); void vic20_tape_stop(vic20_t* sys); // return true if tape motor is on bool vic20_is_tape_motor_on(vic20_t* sys); -// get the standard framebuffer width and height in pixels -int vic20_std_display_width(void); -int vic20_std_display_height(void); -// get the maximum framebuffer size in number of bytes -size_t vic20_max_display_size(void); -// get the current framebuffer width and height in pixels -int vic20_display_width(vic20_t* sys); -int vic20_display_height(vic20_t* sys); +// take a snapshot, patches pointers to zero or offsets, returns snapshot version +uint32_t vic20_save_snapshot(vic20_t* sys, vic20_t* dst); +// load a snapshot, returns false if snapshot version doesn't match +bool vic20_load_snapshot(vic20_t* sys, uint32_t version, vic20_t* src); #ifdef __cplusplus } // extern "C" @@ -255,13 +221,10 @@ int vic20_display_height(vic20_t* sys); #define CHIPS_ASSERT(c) assert(c) #endif -#define _VIC20_STD_DISPLAY_WIDTH (232) /* actually 229, but rounded up to 8x */ -#define _VIC20_STD_DISPLAY_HEIGHT (272) -#define _VIC20_DBG_DISPLAY_WIDTH ((_M6561_HTOTAL+1)*4) -#define _VIC20_DBG_DISPLAY_HEIGHT (_M6561_VTOTAL+1) -#define _VIC20_DISPLAY_SIZE (_VIC20_DBG_DISPLAY_WIDTH*_VIC20_DBG_DISPLAY_HEIGHT*4) -#define _VIC20_DISPLAY_X (32) -#define _VIC20_DISPLAY_Y (8) +#define _VIC20_SCREEN_WIDTH (232) // actually 229, but rounded up to 8x +#define _VIC20_SCREEN_HEIGHT (272) +#define _VIC20_SCREEN_X (32) +#define _VIC20_SCREEN_Y (8) static uint16_t _vic20_vic_fetch(uint16_t addr, void* user_data); static void _vic20_init_key_map(vic20_t* sys); @@ -270,10 +233,7 @@ static void _vic20_init_key_map(vic20_t* sys); void vic20_init(vic20_t* sys, const vic20_desc_t* desc) { CHIPS_ASSERT(sys && desc); - CHIPS_ASSERT(desc->pixel_buffer.ptr && (desc->pixel_buffer.size >= vic20_max_display_size())); - if (desc->debug.callback.func) { - CHIPS_ASSERT(desc->debug.stopped); - } + if (desc->debug.callback.func) { CHIPS_ASSERT(desc->debug.stopped); } memset(sys, 0, sizeof(vic20_t)); sys->valid = true; @@ -300,19 +260,23 @@ void vic20_init(vic20_t* sys, const vic20_desc_t* desc) { m6522_init(&sys->via_2); m6561_init(&sys->vic, &(m6561_desc_t){ .fetch_cb = _vic20_vic_fetch, - .rgba8_buffer = (uint32_t*) desc->pixel_buffer.ptr, - .rgba8_buffer_size = desc->pixel_buffer.size, - .vis_x = _VIC20_DISPLAY_X, - .vis_y = _VIC20_DISPLAY_Y, - .vis_w = _VIC20_STD_DISPLAY_WIDTH, - .vis_h = _VIC20_STD_DISPLAY_HEIGHT, + .framebuffer = { + .ptr = sys->fb, + .size = sizeof(sys->fb) + }, + .screen = { + .x = _VIC20_SCREEN_X, + .y = _VIC20_SCREEN_Y, + .width = _VIC20_SCREEN_WIDTH, + .height = _VIC20_SCREEN_HEIGHT, + }, .user_data = sys, .tick_hz = VIC20_FREQUENCY, .sound_hz = _VIC20_DEFAULT(desc->audio.sample_rate, 44100), .sound_magnitude = _VIC20_DEFAULT(desc->audio.volume, 1.0f), }); _vic20_init_key_map(sys); - + /* VIC-20 CPU memory map: @@ -662,14 +626,15 @@ static void _vic20_init_key_map(vic20_t* sys) { kbd_register_key(&sys->kbd, 0xF8, 7, 7, 1); } -bool vic20_quickload(vic20_t* sys, const uint8_t* ptr, int num_bytes) { - CHIPS_ASSERT(sys && sys->valid && ptr && (num_bytes > 0)); - if (num_bytes < 2) { +bool vic20_quickload(vic20_t* sys, chips_range_t data) { + CHIPS_ASSERT(sys && sys->valid && data.ptr && (data.size > 0)); + if (data.size < 2) { return false; } + const uint8_t* ptr = (uint8_t*)data.ptr; const uint16_t start_addr = ptr[1]<<8 | ptr[0]; ptr += 2; - const uint16_t end_addr = start_addr + (num_bytes - 2); + const uint16_t end_addr = start_addr + (data.size - 2); uint16_t addr = start_addr; while (addr < end_addr) { mem_wr(&sys->mem_cpu, addr++, *ptr++); @@ -677,9 +642,9 @@ bool vic20_quickload(vic20_t* sys, const uint8_t* ptr, int num_bytes) { return true; } -bool vic20_insert_rom_cartridge(vic20_t* sys, const uint8_t* ptr, int num_bytes) { - CHIPS_ASSERT(sys && sys->valid && ptr && (num_bytes > 0)); - if (num_bytes < 2) { +bool vic20_insert_rom_cartridge(vic20_t* sys, chips_range_t data) { + CHIPS_ASSERT(sys && sys->valid && data.ptr && (data.size > 0)); + if (data.size < 2) { return false; } @@ -687,9 +652,10 @@ bool vic20_insert_rom_cartridge(vic20_t* sys, const uint8_t* ptr, int num_bytes) two memory regions with valid data, we cannot scribble over memory in that gap, so use a temporary memory mapping */ + const uint8_t* ptr = (uint8_t*)data.ptr; const uint16_t start_addr = ptr[1]<<8 | ptr[0]; ptr += 2; - const uint16_t end_addr = start_addr + (num_bytes - 2); + const uint16_t end_addr = start_addr + (data.size - 2); uint16_t addr = start_addr; while (addr < end_addr) { mem_wr(&sys->mem_cart, addr++, *ptr++); @@ -717,34 +683,12 @@ void vic20_remove_rom_cartridge(vic20_t* sys) { sys->pins |= M6502_RES; } -int vic20_std_display_width(void) { - return _VIC20_STD_DISPLAY_WIDTH; -} - -int vic20_std_display_height(void) { - return _VIC20_STD_DISPLAY_HEIGHT; -} - -size_t vic20_max_display_size(void) { - return _VIC20_DISPLAY_SIZE; -} - -int vic20_display_width(vic20_t* sys) { - CHIPS_ASSERT(sys && sys->valid); - return m6561_display_width(&sys->vic); -} - -int vic20_display_height(vic20_t* sys) { - CHIPS_ASSERT(sys && sys->valid); - return m6561_display_height(&sys->vic); -} - // generate precomputed VIA-1 and VIA-2 joystick port masks static void _vic20_update_joymasks(vic20_t* sys) { uint8_t jm = sys->kbd_joy_mask | sys->joy_joy_mask; sys->via1_joy_mask = M6522_PA2|M6522_PA3|M6522_PA4|M6522_PA5; sys->via2_joy_mask = M6522_PB7; - if (jm & VIC20_JOYSTICK_LEFT) { + if (jm & VIC20_JOYSTICK_UP) { sys->via1_joy_mask &= ~M6522_PA2; } if (jm & VIC20_JOYSTICK_DOWN) { @@ -825,9 +769,9 @@ void vic20_joystick(vic20_t* sys, uint8_t joy_mask) { _vic20_update_joymasks(sys); } -bool vic20_insert_tape(vic20_t* sys, const uint8_t* ptr, int num_bytes) { +bool vic20_insert_tape(vic20_t* sys, chips_range_t data) { CHIPS_ASSERT(sys && sys->valid && sys->c1530.valid); - return c1530_insert_tape(&sys->c1530, ptr, num_bytes); + return c1530_insert_tape(&sys->c1530, data); } void vic20_remove_tape(vic20_t* sys) { @@ -855,4 +799,67 @@ bool vic20_is_tape_motor_on(vic20_t* sys) { return c1530_is_motor_on(&sys->c1530); } +chips_display_info_t vic20_display_info(vic20_t* sys) { + chips_display_info_t res = { + .frame = { + .dim = { + .width = M6561_FRAMEBUFFER_WIDTH, + .height = M6561_FRAMEBUFFER_HEIGHT, + }, + .bytes_per_pixel = 1, + .buffer = { + .ptr = sys ? sys->fb : 0, + .size = M6561_FRAMEBUFFER_SIZE_BYTES, + } + }, + .palette = m6561_palette(), + }; + if (sys) { + res.screen = m6561_screen(&sys->vic); + } + else { + res.screen = (chips_rect_t){ + .x = 0, + .y = 0, + .width = _VIC20_SCREEN_WIDTH, + .height = _VIC20_SCREEN_HEIGHT, + }; + } + CHIPS_ASSERT(((sys == 0) && (res.frame.buffer.ptr == 0)) || ((sys != 0) && (res.frame.buffer.ptr != 0))); + return res; +} + +uint32_t vic20_save_snapshot(vic20_t* sys, vic20_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->debug); + chips_audio_callback_snapshot_onsave(&dst->audio.callback); + m6502_snapshot_onsave(&dst->cpu); + m6561_snapshot_onsave(&dst->vic); + c1530_snapshot_onsave(&dst->c1530); + mem_snapshot_onsave(&dst->mem_cpu, sys); + mem_snapshot_onsave(&dst->mem_vic, sys); + mem_snapshot_onsave(&dst->mem_cart, sys); + return VIC20_SNAPSHOT_VERSION; +} + +bool vic20_load_snapshot(vic20_t* sys, uint32_t version, vic20_t* src) { + CHIPS_ASSERT(sys && src); + if (version != VIC20_SNAPSHOT_VERSION) { + return false; + } + static vic20_t im; + im = *src; + chips_debug_snapshot_onload(&im.debug, &sys->debug); + chips_audio_callback_snapshot_onload(&im.audio.callback, &sys->audio.callback); + m6502_snapshot_onload(&im.cpu, &sys->cpu); + m6561_snapshot_onload(&im.vic, &sys->vic); + c1530_snapshot_onload(&im.c1530, &sys->c1530); + mem_snapshot_onload(&im.mem_cpu, sys); + mem_snapshot_onload(&im.mem_vic, sys); + mem_snapshot_onload(&im.mem_cart, sys); + *sys = im; + return true; +} + #endif // CHIPS_IMPL diff --git a/systems/z1013.h b/systems/z1013.h index ef85b5c3..01a6a590 100644 --- a/systems/z1013.h +++ b/systems/z1013.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -20,6 +20,7 @@ You need to include the following headers before including z1013.h: + - chips/chips_common.h - chips/z80.h - chips/z80pio.h - chips/mem.h @@ -60,16 +61,26 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include #include +#include #ifdef __cplusplus extern "C" { #endif +// bump this whenever the z1013_t struct layout changes +#define Z1013_SNAPSHOT_VERSION (0x0001) + +#define Z1013_FRAMEBUFFER_WIDTH (256) +#define Z1013_FRAMEBUFFER_HEIGHT (256) +#define Z1013_FRAMEBUFFER_SIZE_BYTES (Z1013_FRAMEBUFFER_WIDTH * Z1013_FRAMEBUFFER_HEIGHT) +#define Z1013_DISPLAY_WIDTH (256) +#define Z1013_DISPLAY_HEIGHT (256) + // Z1013 model types typedef enum { Z1013_TYPE_64, // Z1013.64 (default, latest model with 2 MHz and 64 KB RAM, new ROM) @@ -77,57 +88,36 @@ typedef enum { Z1013_TYPE_01, // Z1013.01 (original model, 1 MHz, 16 KB RAM) } z1013_type_t; -// debugging hook definitions -typedef void (*z1013_debug_func_t)(void* user_data, uint64_t pins); -typedef struct { - struct { - z1013_debug_func_t func; - void* user_data; - } callback; - bool* stopped; -} z1013_debug_t; - -typedef struct { - const void* ptr; - size_t size; -} z1013_rom_image_t; - // configuration parameters for z1013_setup() typedef struct { z1013_type_t type; // default is Z1013_TYPE_64 - z1013_debug_t debug; // optional debug callback and userdata ptr - - // video output config - struct { - void* ptr; // pointer to a linear RGBA8 pixel buffer, at least 256*256*4 bytes - size_t size; // size of the pixel buffer in bytes - } pixel_buffer; + chips_debug_t debug; // optional debug callback and userdata ptr // ROM images struct { - z1013_rom_image_t mon202; - z1013_rom_image_t mon_a2; - z1013_rom_image_t font; + chips_range_t mon202; + chips_range_t mon_a2; + chips_range_t font; } roms; } z1013_desc_t; // Z1013 emulator state typedef struct { z80_t cpu; - z80pio_t pio; mem_t mem; - z1013_debug_t debug; + z80pio_t pio; + chips_debug_t debug; uint64_t pins; z1013_type_t type; bool valid; uint16_t kbd_request_line_mask; int kbd_request_line_hilo_shift; - uint32_t* pixel_buffer; kbd_t kbd; uint64_t freq_hz; uint8_t ram[1<<16]; uint8_t rom_os[2048]; uint8_t rom_font[2048]; + alignas(64) uint8_t fb[Z1013_FRAMEBUFFER_SIZE_BYTES]; } z1013_t; // initialize a new Z1013 instance @@ -136,22 +126,20 @@ void z1013_init(z1013_t* sys, const z1013_desc_t* desc); void z1013_discard(z1013_t* sys); // reset Z1013 instance void z1013_reset(z1013_t* sys); +// query information about display requirements, can be called with nullptr +chips_display_info_t z1013_display_info(z1013_t* sys); // run the Z1013 instance for a given number of microseconds, returns number of executed ticks uint32_t z1013_exec(z1013_t* sys, uint32_t micro_seconds); -// get the standard framebuffer width and height in pixels -int z1013_std_display_width(void); -int z1013_std_display_height(void); -// get the maximum framebuffer size in number of bytes -size_t z1013_max_display_size(void); -// get the current framebuffer width and height in pixels -int z1013_display_width(z1013_t* sys); -int z1013_display_height(z1013_t* sys); // send a key-down event void z1013_key_down(z1013_t* sys, int key_code); // send a key-up event void z1013_key_up(z1013_t* sys, int key_code); // load a "KC .z80" file into the emulator -bool z1013_quickload(z1013_t* sys, const uint8_t* ptr, int num_bytes); +bool z1013_quickload(z1013_t* sys, chips_range_t data); +// take snapshot, patches any pointers to zero, returns a snapshot version +uint32_t z1013_save_snapshot(z1013_t* sys, z1013_t* dst); +// load a snapshot, returns false if snapshot version doesn't match +bool z1013_load_snapshot(z1013_t* sys, uint32_t version, const z1013_t* src); #ifdef __cplusplus } // extern "C" @@ -165,10 +153,6 @@ bool z1013_quickload(z1013_t* sys, const uint8_t* ptr, int num_bytes); #define CHIPS_ASSERT(c) assert(c) #endif -#define _Z1013_DISPLAY_WIDTH (256) -#define _Z1013_DISPLAY_HEIGHT (256) -#define _Z1013_DISPLAY_SIZE (_Z1013_DISPLAY_WIDTH*_Z1013_DISPLAY_HEIGHT*4) - /* IO address decoding. @@ -206,16 +190,13 @@ bool z1013_quickload(z1013_t* sys, const uint8_t* ptr, int num_bytes); void z1013_init(z1013_t* sys, const z1013_desc_t* desc) { CHIPS_ASSERT(sys && desc); - CHIPS_ASSERT(desc->pixel_buffer.ptr && (desc->pixel_buffer.size >= _Z1013_DISPLAY_SIZE)); - if (desc->debug.callback.func) { - CHIPS_ASSERT(desc->debug.stopped); - } + if (desc->debug.callback.func) { CHIPS_ASSERT(desc->debug.stopped); } + memset(sys, 0, sizeof(z1013_t)); - sys->valid = true; sys->type = desc->type; - sys->pixel_buffer = (uint32_t*) desc->pixel_buffer.ptr; - sys->debug = desc->debug; + sys->valid = true; sys->freq_hz = (Z1013_TYPE_01 == desc->type) ? 1000000 : 2000000; + sys->debug = desc->debug; // copy ROM dumps CHIPS_ASSERT(desc->roms.font.ptr && (desc->roms.font.size == sizeof(sys->rom_font))); @@ -259,7 +240,7 @@ void z1013_init(z1013_t* sys, const z1013_desc_t* desc) { kbd_register_modifier(&sys->kbd, 0, 0, 3); kbd_register_modifier(&sys->kbd, 1, 1, 3); kbd_register_modifier(&sys->kbd, 2, 2, 3); - kbd_register_modifier(&sys->kbd, 3, 3, 3); + kbd_register_modifier(&sys->kbd, 3, 3, 3); const char* keymap = // no shift "@ABCDEFG" "HIJKLMNO" "PQRSTUVW" " " @@ -296,7 +277,7 @@ void z1013_init(z1013_t* sys, const z1013_desc_t* desc) { // ctrl key modifier is column 6 line 5 const int ctrl = 1, ctrl_mask = (1<kbd, ctrl, 6, 5); - const char* keymap = + const char* keymap = // no shift "13579- QETUO@ ADGJL* YCBM.^ 24680[ WRZIP] SFHK+\\ XVN,/_ " // shift @@ -341,15 +322,14 @@ void z1013_reset(z1013_t* sys) { static uint64_t _z1013_tick(z1013_t* sys, uint64_t pins) { pins = z80_tick(&sys->cpu, pins) & Z80_PIN_MASK; + + // handle memory requests if (pins & Z80_MREQ) { - // a memory request const uint16_t addr = Z80_GET_ADDR(pins); if (pins & Z80_RD) { - // read memory byte Z80_SET_DATA(pins, mem_rd(&sys->mem, addr)); } else if (pins & Z80_WR) { - // write memory byte mem_wr(&sys->mem, addr, Z80_GET_DATA(pins)); } } @@ -387,17 +367,22 @@ static uint64_t _z1013_tick(z1013_t* sys, uint64_t pins) { we're cheating a bit and decode the entire frame in one go */ static void _z1013_decode_vidmem(z1013_t* sys) { - uint32_t* dst = sys->pixel_buffer; + static const uint32_t lut32[16] = { + 0x00000000, 0x01000000, 0x00010000, 0x01010000, + 0x00000100, 0x01000100, 0x00010100, 0x01010100, + 0x00000001, 0x01000001, 0x00010001, 0x01010001, + 0x00000101, 0x01000101, 0x00010101, 0x01010101, + }; + uint32_t* dst32 = (uint32_t*) sys->fb; const uint8_t* src = &sys->ram[0xEC00]; // the 32x32 framebuffer starts at EC00 const uint8_t* font = sys->rom_font; - for (int y = 0; y < 32; y++) { - for (int py = 0; py < 8; py++) { - for (int x = 0; x < 32; x++) { + for (size_t y = 0; y < 32; y++) { + for (size_t py = 0; py < 8; py++) { + for (size_t x = 0; x < 32; x++) { uint8_t chr = src[(y<<5) + x]; - uint8_t bits = font[(chr<<3)|py]; - for (int px = 7; px >=0; px--) { - *dst++ = bits & (1<> 4]; + *dst32++ = lut32[pixels & 0xF]; } } } @@ -436,30 +421,6 @@ void z1013_key_up(z1013_t* sys, int key_code) { kbd_key_up(&sys->kbd, key_code); } -int z1013_std_display_width(void) { - return _Z1013_DISPLAY_WIDTH; -} - -int z1013_std_display_height(void) { - return _Z1013_DISPLAY_HEIGHT; -} - -size_t z1013_max_display_size(void) { - return _Z1013_DISPLAY_SIZE; -} - -int z1013_display_width(z1013_t* sys) { - (void)sys; - return _Z1013_DISPLAY_WIDTH; -} - -int z1013_display_height(z1013_t* sys) { - (void)sys; - return _Z1013_DISPLAY_HEIGHT; -} - -//=== FILE LOADING ============================================================= - typedef struct { uint8_t load_addr_l; uint8_t load_addr_h; @@ -473,12 +434,13 @@ typedef struct { uint8_t name[16]; } _z1013_kcz80_header; -bool z1013_quickload(z1013_t* sys, const uint8_t* ptr, int num_bytes) { - CHIPS_ASSERT(sys && sys->valid && ptr); - if (num_bytes < (int)sizeof(_z1013_kcz80_header)) { +bool z1013_quickload(z1013_t* sys, chips_range_t data) { + CHIPS_ASSERT(sys && sys->valid && data.ptr); + if (data.size < sizeof(_z1013_kcz80_header)) { return false; } - const _z1013_kcz80_header* hdr = (const _z1013_kcz80_header*) ptr; + const uint8_t* ptr = (uint8_t*)data.ptr; + const _z1013_kcz80_header* hdr = (const _z1013_kcz80_header*)ptr; if ((hdr->d3[0] != 0xD3) || (hdr->d3[1] != 0xD3) || (hdr->d3[2] != 0xD3)) { return false; } @@ -502,4 +464,59 @@ bool z1013_quickload(z1013_t* sys, const uint8_t* ptr, int num_bytes) { z80_prefetch(&sys->cpu, exec_addr); return true; } + +chips_display_info_t z1013_display_info(z1013_t* sys) { + static const uint32_t palette[2] = { + 0xFF000000, // black + 0xFFFFFFFF, // white + }; + const chips_display_info_t res = { + .frame = { + .dim = { + .width = Z1013_FRAMEBUFFER_WIDTH, + .height = Z1013_FRAMEBUFFER_HEIGHT, + }, + .buffer = { + .ptr = sys ? sys->fb : 0, + .size = Z1013_FRAMEBUFFER_SIZE_BYTES, + }, + .bytes_per_pixel = 1, + }, + .screen = { + .x = 0, + .y = 0, + .width = Z1013_DISPLAY_WIDTH, + .height = Z1013_DISPLAY_HEIGHT, + }, + .palette = { + .ptr = (void*)palette, + .size = sizeof(palette) + } + }; + CHIPS_ASSERT(((sys == 0) && (res.frame.buffer.ptr == 0)) || ((sys != 0) && (res.frame.buffer.ptr != 0))); + return res; +} + +uint32_t z1013_save_snapshot(z1013_t* sys, z1013_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->debug); + mem_snapshot_onsave(&dst->mem, sys); + return Z1013_SNAPSHOT_VERSION; +} + +bool z1013_load_snapshot(z1013_t* sys, uint32_t version, const z1013_t* src) { + CHIPS_ASSERT(sys && src); + if (version != Z1013_SNAPSHOT_VERSION) { + return false; + } + // intermediate copy + static z1013_t im; + im = *src; + chips_debug_snapshot_onload(&im.debug, &sys->debug); + mem_snapshot_onload(&im.mem, sys); + *sys = im; + return true; +} + #endif // CHIPS_IMPL diff --git a/systems/z9001.h b/systems/z9001.h index f2adc741..f58eb08f 100644 --- a/systems/z9001.h +++ b/systems/z9001.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -20,6 +20,7 @@ You need to include the following headers before including z9001.h: + - chips/chips_common.h - chips/z80.h - chips/z80pio.h - chips/z80ctc.h @@ -27,7 +28,7 @@ - chips/mem.h - chips/kbd.h - chips/clk.h - + ## The Robotron Z9001 The Z9001 (later retconned to KC85/1) was independently developed @@ -75,18 +76,27 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include #include +#include #ifdef __cplusplus extern "C" { #endif -#define Z9001_MAX_AUDIO_SAMPLES (1024) /* max number of audio samples in internal sample buffer */ -#define Z9001_DEFAULT_AUDIO_SAMPLES (128) /* default number of samples in internal sample buffer */ +// bump this whenever the z9001_t struct layout changes +#define Z9001_SNAPSHOT_VERSION (0x0001) + +#define Z9001_MAX_AUDIO_SAMPLES (1024) // max number of audio samples in internal sample buffer +#define Z9001_DEFAULT_AUDIO_SAMPLES (128) // default number of samples in internal sample buffer +#define Z9001_FRAMEBUFFER_WIDTH (512) +#define Z9001_FRAMEBUFFER_HEIGHT (192) +#define Z9001_FRAMEBUFFER_SIZE_BYTES (Z9001_FRAMEBUFFER_WIDTH * Z9001_FRAMEBUFFER_HEIGHT) +#define Z9001_DISPLAY_WIDTH (320) +#define Z9001_DISPLAY_HEIGHT (192) // Z9001/KC87 model types typedef enum { @@ -94,60 +104,25 @@ typedef enum { Z9001_TYPE_KC87, // the revised KC87 with built-in BASIC and color module } z9001_type_t; -// audio sample callback -typedef struct { - void (*func)(const float* samples, int num_samples, void* user_data); - void* user_data; -} z9001_audio_callback_t; - -// debugging hook definitions -typedef void (*z9001_debug_func_t)(void* user_data, uint64_t pins); -typedef struct { - struct { - z9001_debug_func_t func; - void* user_data; - } callback; - bool* stopped; -} z9001_debug_t; - -typedef struct { - const void* ptr; - size_t size; -} z9001_rom_image_t; - // configuration parameters for z9001_init() typedef struct { z9001_type_t type; // default is Z9001_TYPE_Z9001 - z9001_debug_t debug; // optional debug hook - - // video output config (if you don't need display decoding, set pixel_buffer to 0) - struct { - void* ptr; // pointer to a linear RGBA8 pixel buffer - size_t size; // size of the pixel buffer in bytes - } pixel_buffer; - - // audio output config (if you don't want audio, set audio_cb to zero) - struct { - z9001_audio_callback_t callback; // called when audio_num_samples are ready - int num_samples; // default is Z9001_DEFAULT_AUDIO_SAMPLES - int sample_rate; // playback sample rate, default is 44100 - float volume; // audio volume (0.0 .. 1.0), default is 0.4 - } audio; - + chips_debug_t debug; // optional debug hook + chips_audio_desc_t audio; struct { // Z9001 ROM images struct { - z9001_rom_image_t os_1; - z9001_rom_image_t os_2; - z9001_rom_image_t font; + chips_range_t os_1; + chips_range_t os_2; + chips_range_t font; // optional BASIC module ROM - z9001_rom_image_t basic; + chips_range_t basic; } z9001; // KC85 ROM images struct { - z9001_rom_image_t os; - z9001_rom_image_t basic; - z9001_rom_image_t font; + chips_range_t os; + chips_range_t basic; + chips_range_t font; } kc87; } roms; } z9001_desc_t; @@ -159,29 +134,29 @@ typedef struct { z80pio_t pio2; z80ctc_t ctc; beeper_t beeper; - bool z9001_has_basic_rom; - bool blink_flip_flop; + uint8_t blink_flip_flop; // bit 7 0=>1=>0 z9001_type_t type; uint64_t pins; - uint64_t ctc_zcto2; // pin mask to store state of CTC ZCTO2 + uint64_t ctc_zcto2; // pin mask to store state of CTC ZCTO2 uint32_t blink_counter; // FIXME: uint8_t border_color; mem_t mem; kbd_t kbd; bool valid; - z9001_debug_t debug; + bool z9001_has_basic_rom; + chips_debug_t debug; - uint32_t* pixel_buffer; struct { - z9001_audio_callback_t callback; + chips_audio_callback_t callback; int num_samples; int sample_pos; float sample_buffer[Z9001_MAX_AUDIO_SAMPLES]; } audio; uint8_t ram[1<<16]; uint8_t rom[0x4000]; - uint8_t rom_font[0x0800]; /* 2 KB font ROM (not mapped into CPU address space) */ + uint8_t rom_font[0x0800]; // 2 KB font ROM (not mapped into CPU address space) + alignas(64) uint8_t fb[Z9001_FRAMEBUFFER_SIZE_BYTES]; } z9001_t; // initialize a new Z9001 instance @@ -190,6 +165,8 @@ void z9001_init(z9001_t* sys, const z9001_desc_t* desc); void z9001_discard(z9001_t* sys); // reset Z9001 instance void z9001_reset(z9001_t* sys); +// query information about display requirements, can be called with nullptr +chips_display_info_t z9001_display_info(z9001_t* sys); // run Z9001 instance for a given number of microseconds, return number of executed ticks uint32_t z9001_exec(z9001_t* sys, uint32_t micro_seconds); // send a key-down event @@ -197,15 +174,11 @@ void z9001_key_down(z9001_t* sys, int key_code); // send a key-up event void z9001_key_up(z9001_t* sys, int key_code); // load a KC TAP or KCC file into the emulator -bool z9001_quickload(z9001_t* sys, const uint8_t* ptr, int num_bytes); -// get the standard framebuffer width and height in pixels -int z9001_std_display_width(void); -int z9001_std_display_height(void); -// get the maximum framebuffer size in number of bytes -int z9001_max_display_size(void); -// get the current framebuffer width and height in pixels -int z9001_display_width(z9001_t* sys); -int z9001_display_height(z9001_t* sys); +bool z9001_quickload(z9001_t* sys, chips_range_t data); +// save a snapshot, patches any pointers to zero, returns a snapshot version +uint32_t z9001_save_snapshot(z9001_t* sys, z9001_t* dst); +// load a snapshot, returns false if snapshot version doesn't match +bool z9001_load_snapshot(z9001_t* sys, uint32_t version, const z9001_t* src); #ifdef __cplusplus } /* extern "C" */ @@ -219,11 +192,6 @@ int z9001_display_height(z9001_t* sys); #define CHIPS_ASSERT(c) assert(c) #endif -#define _Z9001_DISPLAY_WIDTH (320) -#define _Z9001_DISPLAY_HEIGHT (192) -#define _Z9001_DISPLAY_SIZE (_Z9001_DISPLAY_WIDTH*_Z9001_DISPLAY_HEIGHT*4) -#define _Z9001_FREQUENCY (2457600) - // xorshift randomness for memory initialization static inline uint32_t _z9001_xorshift32(uint32_t x) { x ^= x<<13; @@ -233,6 +201,7 @@ static inline uint32_t _z9001_xorshift32(uint32_t x) { } #define _Z9001_DEFAULT(val,def) (((val) != 0) ? (val) : (def)) +#define _Z9001_FREQUENCY (2457600) // IO address decoding masks and pins #define _Z9001_IO_SEL_MASK (Z80_IORQ|Z80_M1|Z80_A7|Z80_A6) @@ -249,10 +218,7 @@ static inline uint32_t _z9001_xorshift32(uint32_t x) { void z9001_init(z9001_t* sys, const z9001_desc_t* desc) { CHIPS_ASSERT(sys && desc); - CHIPS_ASSERT((0 == desc->pixel_buffer.ptr) || (desc->pixel_buffer.ptr && (desc->pixel_buffer.size >= _Z9001_DISPLAY_SIZE))); - if (desc->debug.callback.func) { - CHIPS_ASSERT(desc->debug.stopped); - } + if (desc->debug.callback.func) { CHIPS_ASSERT(desc->debug.stopped); } memset(sys, 0, sizeof(z9001_t)); sys->valid = true; @@ -279,8 +245,6 @@ void z9001_init(z9001_t* sys, const z9001_desc_t* desc) { CHIPS_ASSERT(desc->roms.kc87.os.ptr && (desc->roms.kc87.os.size == 0x2000)); memcpy(&sys->rom[0x2000], desc->roms.kc87.os.ptr, 0x2000); } - CHIPS_ASSERT(desc->pixel_buffer.ptr && (desc->pixel_buffer.size >= _Z9001_DISPLAY_SIZE)); - sys->pixel_buffer = (uint32_t*) desc->pixel_buffer.ptr; // initialize the hardware z80_init(&sys->cpu); @@ -296,7 +260,7 @@ void z9001_init(z9001_t* sys, const z9001_desc_t* desc) { .sound_hz = _Z9001_DEFAULT(desc->audio.sample_rate, 44100), .base_volume = _Z9001_DEFAULT(desc->audio.volume, 0.5f), }); - + /* setup memory map: - memory mapping is static and cannot be changed - initial memory state is random @@ -306,7 +270,7 @@ void z9001_init(z9001_t* sys, const z9001_desc_t* desc) { - KC87 has a 1 KB color buffer at 0xEC800 */ uint32_t r = 0x6D98302B; - for (int i = 0; i < (int)sizeof(sys->ram);) { + for (size_t i = 0; i < sizeof(sys->ram);) { r = _z9001_xorshift32(r); sys->ram[i++] = r; sys->ram[i++] = (r>>8); @@ -338,7 +302,7 @@ void z9001_init(z9001_t* sys, const z9001_desc_t* desc) { // 1 KB ASCII video RAM mem_map_ram(&sys->mem, 0, 0xEC00, 0x0400, &sys->ram[0xEC00]); - /* setup the 8x8 keyboard matrix, keep pressed keys sticky for 3 frames + /* setup the 8x8 keyboard matrix, keep pressed keys sticky for 3 frames to give the keyboard scanning code enough time to read the key */ kbd_init(&sys->kbd, 3); @@ -411,8 +375,9 @@ void z9001_reset(z9001_t* sys) { static uint64_t _z9001_tick(z9001_t* sys, uint64_t pins) { pins = z80_tick(&sys->cpu, pins); + + // handle memory requests if (pins & Z80_MREQ) { - // handle memory requests const uint16_t addr = Z80_GET_ADDR(pins); if (pins & Z80_RD) { Z80_SET_DATA(pins, mem_rd(&sys->mem, addr)); @@ -511,50 +476,48 @@ static uint64_t _z9001_tick(z9001_t* sys, uint64_t pins) { */ if (0 >= sys->blink_counter--) { sys->blink_counter = (_Z9001_FREQUENCY * 8) / 25; - sys->blink_flip_flop = !sys->blink_flip_flop; + sys->blink_flip_flop ^= 0x80; } return pins; } -// decode the KC87 40x24 framebuffer to a linear 320x192 RGBA8 buffer -static const uint32_t _z9001_palette[8] = { - 0xFF000000, // black - 0xFF0000FF, // red - 0xFF00FF00, // green - 0xFF00FFFF, // yellow - 0xFFFF0000, // blue - 0xFFFF00FF, // purple - 0xFFFFFF00, // cyan - 0xFFFFFFFF, // white -}; +static inline void _z9001_decode_8pixels(uint8_t* dst, uint8_t pixels, uint8_t colors) { + // courtesy of ryg: https://mastodon.gamedev.place/@rygorous/109531596140414988 + static const uint32_t lut32[16] = { + 0x00000000, 0xff000000, 0x00ff0000, 0xffff0000, + 0x0000ff00, 0xff00ff00, 0x00ffff00, 0xffffff00, + 0x000000ff, 0xff0000ff, 0x00ff00ff, 0xffff00ff, + 0x0000ffff, 0xff00ffff, 0x00ffffff, 0xffffffff, + }; + const uint32_t colors32 = colors * 0x01010101u; + const uint32_t bg32 = colors32 & 0x07070707; + const uint32_t fg32 = (colors32 >> 4) & 0x07070707; + const uint32_t xor32 = bg32 ^ fg32; + uint32_t* dst32 = (uint32_t*)dst; + dst32[0] = bg32 ^ (xor32 & lut32[pixels >> 4]); + dst32[1] = bg32 ^ (xor32 & lut32[pixels & 0xf]); +} + static void _z9001_decode_vidmem(z9001_t* sys) { // FIXME: there's also a 40x20 video mode - uint32_t* dst = sys->pixel_buffer; const uint8_t* vidmem = &sys->ram[0xEC00]; // 1 KB ASCII buffer at EC00 const uint8_t* colmem = &sys->ram[0xE800]; // 1 KB color buffer at E800 - int offset = 0; + size_t offset = 0; if (Z9001_TYPE_KC87 == sys->type) { // KC87 with color module const uint8_t* font = sys->rom_font; - uint32_t fg, bg; - for (int y = 0; y < 24; y++) { - for (int py = 0; py < 8; py++) { - for (int x = 0; x < 40; x++) { + for (size_t y = 0; y < 24; y++) { + for (size_t py = 0; py < 8; py++) { + uint8_t* dst = &sys->fb[(y * 8 + py) * Z9001_FRAMEBUFFER_WIDTH]; + for (size_t x = 0; x < 40; x++, dst += 8) { uint8_t chr = vidmem[offset+x]; uint8_t pixels = font[(chr<<3)|py]; - uint8_t color = colmem[offset+x]; - if ((color & 0x80) && sys->blink_flip_flop) { + uint8_t colors = colmem[offset+x]; + if (colors & sys->blink_flip_flop & 0x80) { // blinking: swap back- and foreground color - fg = _z9001_palette[color&7]; - bg = _z9001_palette[(color>>4)&7]; - } - else { - fg = _z9001_palette[(color>>4)&7]; - bg = _z9001_palette[color&7]; - } - for (int px = 7; px >= 0; px--) { - *dst++ = pixels & (1<> 4) & 7); } + _z9001_decode_8pixels(dst, pixels, colors); } } offset += 40; @@ -563,14 +526,13 @@ static void _z9001_decode_vidmem(z9001_t* sys) { else { // Z9001 monochrome display const uint8_t* font = sys->rom_font; - for (int y = 0; y < 24; y++) { - for (int py = 0; py < 8; py++) { - for (int x = 0; x < 40; x++) { + for (size_t y = 0; y < 24; y++) { + for (size_t py = 0; py < 8; py++) { + uint8_t* dst = &sys->fb[(y * 8 + py) * Z9001_FRAMEBUFFER_WIDTH]; + for (size_t x = 0; x < 40; x++, dst += 8) { uint8_t chr = vidmem[offset + x]; uint8_t pixels = font[(chr<<3)|py]; - for (int px = 7; px >=0; px--) { - *dst++ = pixels & (1<pio2, Z80PIO_PORT_B, ~kbd_scan_lines(&sys->kbd)); } -/*=== FILE LOADING ===========================================================*/ - // common start function for file loading routines static void _z9001_load_start(z9001_t* sys, uint16_t exec_addr) { sys->cpu.a = 0x00; sys->cpu.f = 0x10; @@ -641,11 +601,11 @@ typedef struct { } _z9001_kcc_header; // KCC files cannot really be identified since they have no magic number -static bool _z9001_is_valid_kcc(const uint8_t* ptr, int num_bytes) { - if (num_bytes <= (int) sizeof (_z9001_kcc_header)) { +static bool _z9001_is_valid_kcc(chips_range_t data) { + if (data.size <= sizeof(_z9001_kcc_header)) { return false; } - const _z9001_kcc_header* hdr = (const _z9001_kcc_header*) ptr; + const _z9001_kcc_header* hdr = (const _z9001_kcc_header*)data.ptr; for (int i = 0; i < 16; i++) { if (hdr->name[i] >= 128) { return false; @@ -665,16 +625,17 @@ static bool _z9001_is_valid_kcc(const uint8_t* ptr, int num_bytes) { return false; } } - int required_len = (end_addr - load_addr) + sizeof(_z9001_kcc_header); - if (required_len > num_bytes) { + size_t required_len = (end_addr - load_addr) + sizeof(_z9001_kcc_header); + if (required_len > data.size) { return false; } // could be a KCC file return true; } -static bool _z9001_load_kcc(z9001_t* sys, const uint8_t* ptr) { - const _z9001_kcc_header* hdr = (_z9001_kcc_header*) ptr; +static bool _z9001_load_kcc(z9001_t* sys, chips_range_t data) { + const uint8_t* ptr = data.ptr; + const _z9001_kcc_header* hdr = (_z9001_kcc_header*)ptr; uint16_t addr = hdr->load_addr_h<<8 | hdr->load_addr_l; uint16_t end_addr = hdr->end_addr_h<<8 | hdr->end_addr_l; ptr += sizeof(_z9001_kcc_header); @@ -692,13 +653,13 @@ typedef struct { _z9001_kcc_header kcc; // from here on identical with KCC } _z9001_kctap_header; -static bool _z9001_is_valid_kctap(const uint8_t* ptr, int num_bytes) { - if (num_bytes <= (int) sizeof (_z9001_kctap_header)) { +static bool _z9001_is_valid_kctap(chips_range_t data) { + if (data.size <= sizeof(_z9001_kctap_header)) { return false; } - const _z9001_kctap_header* hdr = (const _z9001_kctap_header*) ptr; - static uint8_t sig[16] = { 0xC3,'K','C','-','T','A','P','E',0x20,'b','y',0x20,'A','F','.',0x20 }; - for (int i = 0; i < 16; i++) { + const _z9001_kctap_header* hdr = (const _z9001_kctap_header*)data.ptr; + static const uint8_t sig[16] = { 0xC3,'K','C','-','T','A','P','E',0x20,'b','y',0x20,'A','F','.',0x20 }; + for (size_t i = 0; i < 16; i++) { if (sig[i] != hdr->sig[i]) { return false; } @@ -717,16 +678,17 @@ static bool _z9001_is_valid_kctap(const uint8_t* ptr, int num_bytes) { return false; } } - int required_len = (end_addr - load_addr) + sizeof(_z9001_kctap_header); - if (required_len > num_bytes) { + size_t required_len = (end_addr - load_addr) + sizeof(_z9001_kctap_header); + if (required_len > data.size) { return false; } // this appears to be a valid KC TAP file return true; } -static bool _z9001_load_kctap(z9001_t* sys, const uint8_t* ptr) { - const _z9001_kctap_header* hdr = (const _z9001_kctap_header*) ptr; +static bool _z9001_load_kctap(z9001_t* sys, chips_range_t data) { + const uint8_t* ptr = data.ptr; + const _z9001_kctap_header* hdr = (const _z9001_kctap_header*)ptr; uint16_t addr = hdr->kcc.load_addr_h<<8 | hdr->kcc.load_addr_l; uint16_t end_addr = hdr->kcc.end_addr_h<<8 | hdr->kcc.end_addr_l; ptr += sizeof(_z9001_kctap_header); @@ -744,41 +706,80 @@ static bool _z9001_load_kctap(z9001_t* sys, const uint8_t* ptr) { return true; } -bool z9001_quickload(z9001_t* sys, const uint8_t* ptr, int num_bytes) { - CHIPS_ASSERT(sys && sys->valid && ptr); +bool z9001_quickload(z9001_t* sys, chips_range_t data) { + CHIPS_ASSERT(sys && sys->valid && data.ptr); // first check for KC TAP, since this can be properly identified - if (_z9001_is_valid_kctap(ptr, num_bytes)) { - return _z9001_load_kctap(sys, ptr); + if (_z9001_is_valid_kctap(data)) { + return _z9001_load_kctap(sys, data); } - else if (_z9001_is_valid_kcc(ptr, num_bytes)) { - return _z9001_load_kcc(sys, ptr); + else if (_z9001_is_valid_kcc(data)) { + return _z9001_load_kcc(sys, data); } else { - /* not a known file type, or not enough data */ + // not a known file type, or not enough data return false; } } -int z9001_std_display_width(void) { - return _Z9001_DISPLAY_WIDTH; -} - -int z9001_std_display_height(void) { - return _Z9001_DISPLAY_HEIGHT; -} - -int z9001_max_display_size(void) { - return _Z9001_DISPLAY_SIZE; +chips_display_info_t z9001_display_info(z9001_t* sys) { + static const uint32_t palette[8] = { + 0xFF000000, // black + 0xFF0000FF, // red + 0xFF00FF00, // green + 0xFF00FFFF, // yellow + 0xFFFF0000, // blue + 0xFFFF00FF, // purple + 0xFFFFFF00, // cyan + 0xFFFFFFFF, // white + }; + const chips_display_info_t res = { + .frame = { + .dim = { + .width = Z9001_FRAMEBUFFER_WIDTH, + .height = Z9001_FRAMEBUFFER_HEIGHT, + }, + .buffer = { + .ptr = sys ? sys->fb : 0, + .size = Z9001_FRAMEBUFFER_SIZE_BYTES, + }, + .bytes_per_pixel = 1, + }, + .screen = { + .x = 0, + .y = 0, + .width = Z9001_DISPLAY_WIDTH, + .height = Z9001_DISPLAY_HEIGHT, + }, + .palette = { + .ptr = (void*)palette, + .size = sizeof(palette) + } + }; + CHIPS_ASSERT(((sys == 0) && (res.frame.buffer.ptr == 0)) || ((sys != 0) && (res.frame.buffer.ptr != 0))); + return res; } -int z9001_display_width(z9001_t* sys) { - (void)sys; - return _Z9001_DISPLAY_WIDTH; +uint32_t z9001_save_snapshot(z9001_t* sys, z9001_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->debug); + chips_audio_callback_snapshot_onsave(&dst->audio.callback); + mem_snapshot_onsave(&dst->mem, sys); + return Z9001_SNAPSHOT_VERSION; } -int z9001_display_height(z9001_t* sys) { - (void)sys; - return _Z9001_DISPLAY_HEIGHT; +bool z9001_load_snapshot(z9001_t* sys, uint32_t version, const z9001_t* src) { + CHIPS_ASSERT(sys && src); + if (version != Z9001_SNAPSHOT_VERSION) { + return false; + } + static z9001_t im; + im = *src; + chips_debug_snapshot_onload(&im.debug, &sys->debug); + chips_audio_callback_snapshot_onload(&im.audio.callback, &sys->audio.callback); + mem_snapshot_onload(&im.mem, sys); + *sys = im; + return true; } #endif // CHIPS_IMPL diff --git a/systems/zx.h b/systems/zx.h index 01cd7ed4..901c03b8 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_IMPL ~~~ - before you include this file in *one* C or C++ file to create the + before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -20,6 +20,7 @@ You need to include the following headers before including zx.h: + - chips/chips_common.h - chips/z80.h - chips/beeper.h - chips/ay38910.h @@ -33,7 +34,7 @@ ## The ZX Spectrum 128 - TODO! + TODO! ## TODO: - 'contended memory' timing and IO port timing @@ -56,18 +57,27 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include #include +#include #ifdef __cplusplus extern "C" { #endif +// bump this whenever the zx_t struct layout changes +#define ZX_SNAPSHOT_VERSION (0x0001) + #define ZX_MAX_AUDIO_SAMPLES (1024) // max number of audio samples in internal sample buffer #define ZX_DEFAULT_AUDIO_SAMPLES (128) // default number of samples in internal sample buffer +#define ZX_FRAMEBUFFER_WIDTH (512) +#define ZX_FRAMEBUFFER_HEIGHT (256) +#define ZX_FRAMEBUFFER_SIZE_BYTES (ZX_FRAMEBUFFER_WIDTH * ZX_FRAMEBUFFER_HEIGHT) +#define ZX_DISPLAY_WIDTH (320) +#define ZX_DISPLAY_HEIGHT (256) // ZX Spectrum models typedef enum { @@ -90,55 +100,25 @@ typedef enum { #define ZX_JOYSTICK_UP (1<<3) #define ZX_JOYSTICK_BTN (1<<4) -// audio sample callback -typedef struct { - void (*func)(const float* samples, int num_samples, void* user_data); - void* user_data; -} zx_audio_callback_t; - -// debugging hook -typedef void (*zx_debug_func_t)(void* user_data, uint64_t pins); -typedef struct { - struct { - zx_debug_func_t func; - void* user_data; - } callback; - bool* stopped; -} zx_debug_t; - -typedef struct { - const void* ptr; - size_t size; -} zx_rom_image_t; - // config parameters for zx_init() typedef struct { zx_type_t type; // default is ZX_TYPE_48K zx_joystick_type_t joystick_type; // what joystick to emulate, default is ZX_JOYSTICK_NONE - zx_debug_t debug; // optional debugger hook - - // video output config - struct { - void* ptr; // pointer to a linear RGBA8 pixel buffer, at least 320*256*4 bytes - size_t size; // size of the pixel buffer in bytes - } pixel_buffer; - - // audio output config (if you don't want audio, set callback.func to zero) + chips_debug_t debug; // optional debugger hook struct { - zx_audio_callback_t callback; // called when audio_num_samples are ready - int num_samples; // default is ZX_AUDIO_NUM_SAMPLES - int sample_rate; // playback sample rate, default is 44100 - float beeper_volume; // volume of the ZX48K beeper: 0.0..1.0, default is 0.25 - float ay_volume; // volume of the ZX128 AY sound chip: 0.0..1.0, default is 0.5 + chips_audio_callback_t callback; + int num_samples; + int sample_rate; + float beeper_volume; + float ay_volume; } audio; - // ROM images struct { // ZX Spectrum 48K - zx_rom_image_t zx48k; + chips_range_t zx48k; // ZX Spectrum 128 - zx_rom_image_t zx128_0; - zx_rom_image_t zx128_1; + chips_range_t zx128_0; + chips_range_t zx128_1; } roms; } zx_desc_t; @@ -156,6 +136,7 @@ typedef struct { uint8_t last_mem_config; // last out to 0x7FFD uint8_t last_fe_out; // last out value to 0xFE port uint8_t blink_counter; // incremented on each vblank + uint8_t border_color; int frame_scan_lines; int top_border_scanlines; int scanline_period; @@ -163,16 +144,14 @@ typedef struct { int scanline_y; int int_counter; uint32_t display_ram_bank; - uint32_t border_color; kbd_t kbd; mem_t mem; uint64_t pins; uint64_t freq_hz; bool valid; - zx_debug_t debug; - uint32_t* pixel_buffer; + chips_debug_t debug; struct { - zx_audio_callback_t callback; + chips_audio_callback_t callback; int num_samples; int sample_pos; float sample_buffer[ZX_MAX_AUDIO_SAMPLES]; @@ -180,6 +159,7 @@ typedef struct { uint8_t ram[8][0x4000]; uint8_t rom[2][0x4000]; uint8_t junk[0x4000]; + alignas(64) uint8_t fb[ZX_FRAMEBUFFER_SIZE_BYTES]; } zx_t; // initialize a new ZX Spectrum instance @@ -188,6 +168,8 @@ void zx_init(zx_t* sys, const zx_desc_t* desc); void zx_discard(zx_t* sys); // reset a ZX Spectrum instance void zx_reset(zx_t* sys); +// query information about display requirements, can be called with nullptr +chips_display_info_t zx_display_info(zx_t* sys); // run ZX Spectrum instance for a given number of microseconds, return number of ticks uint32_t zx_exec(zx_t* sys, uint32_t micro_seconds); // send a key-down event @@ -201,15 +183,11 @@ zx_joystick_type_t zx_joystick_type(zx_t* sys); // set joystick mask (combination of ZX_JOYSTICK_*) void zx_joystick(zx_t* sys, uint8_t mask); // load a ZX Z80 file into the emulator -bool zx_quickload(zx_t* sys, const uint8_t* ptr, int num_bytes); -// get the standard framebuffer width and height in pixels -int zx_std_display_width(void); -int zx_std_display_height(void); -// get the maximum framebuffer size in number of bytes -size_t zx_max_display_size(void); -// get the current framebuffer width and height in pixels -int zx_display_width(zx_t* sys); -int zx_display_height(zx_t* sys); +bool zx_quickload(zx_t* sys, chips_range_t data); +// save a snapshot, patches any pointers to zero, returns a snapshot version +uint32_t zx_save_snapshot(zx_t* sys, zx_t* dst); +// load a snapshot, returns false if snapshot version doesn't match +bool zx_load_snapshot(zx_t* sys, uint32_t version, zx_t* src); #ifdef __cplusplus } // extern "C" @@ -223,20 +201,16 @@ int zx_display_height(zx_t* sys); #define CHIPS_ASSERT(c) assert(c) #endif -#define _ZX_DISPLAY_WIDTH (320) -#define _ZX_DISPLAY_HEIGHT (256) -#define _ZX_DISPLAY_SIZE (_ZX_DISPLAY_WIDTH*_ZX_DISPLAY_HEIGHT*4) -#define _ZX_48K_FREQUENCY (3500000) -#define _ZX_128_FREQUENCY (3546894) - static void _zx_init_memory_map(zx_t* sys); static void _zx_init_keyboard_matrix(zx_t* sys); #define _ZX_DEFAULT(val,def) (((val) != 0) ? (val) : (def)) +#define _ZX_48K_FREQUENCY (3500000) +#define _ZX_128_FREQUENCY (3546894) + void zx_init(zx_t* sys, const zx_desc_t* desc) { CHIPS_ASSERT(sys && desc); - CHIPS_ASSERT((0 == desc->pixel_buffer.ptr) || (desc->pixel_buffer.ptr && (desc->pixel_buffer.size >= _ZX_DISPLAY_SIZE))); if (desc->debug.callback.func) { CHIPS_ASSERT(desc->debug.stopped); } memset(sys, 0, sizeof(zx_t)); @@ -244,14 +218,13 @@ void zx_init(zx_t* sys, const zx_desc_t* desc) { sys->type = desc->type; sys->joystick_type = desc->joystick_type; sys->freq_hz = (sys->type == ZX_TYPE_48K) ? _ZX_48K_FREQUENCY : _ZX_128_FREQUENCY; - sys->pixel_buffer = (uint32_t*) desc->pixel_buffer.ptr; sys->audio.callback = desc->audio.callback; sys->audio.num_samples = _ZX_DEFAULT(desc->audio.num_samples, ZX_DEFAULT_AUDIO_SAMPLES); CHIPS_ASSERT(sys->audio.num_samples <= ZX_MAX_AUDIO_SAMPLES); sys->debug = desc->debug; - /* initalize the hardware */ - sys->border_color = 0xFF000000; + // initalize the hardware + sys->border_color = 0; if (ZX_TYPE_128 == sys->type) { CHIPS_ASSERT(desc->roms.zx128_0.ptr && (desc->roms.zx128_0.size == 0x4000)); CHIPS_ASSERT(desc->roms.zx128_1.ptr && (desc->roms.zx128_1.size == 0x4000)); @@ -320,17 +293,6 @@ void zx_reset(zx_t* sys) { _zx_init_memory_map(sys); } -static const uint32_t _zx_palette[8] = { - 0xFF000000, // black - 0xFFFF0000, // blue - 0xFF0000FF, // red - 0xFFFF00FF, // magenta - 0xFF00FF00, // green - 0xFFFFFF00, // cyan - 0xFF00FFFF, // yellow - 0xFFFFFFFF, // white -}; - static bool _zx_decode_scanline(zx_t* sys) { /* this is called by the timer callback for every PAL line, controlling the vidmem decoding and vblank interrupt @@ -354,13 +316,12 @@ static bool _zx_decode_scanline(zx_t* sys) { const int btm_decode_line = sys->top_border_scanlines + 192 + 32; if ((sys->scanline_y >= top_decode_line) && (sys->scanline_y < btm_decode_line)) { const uint16_t y = sys->scanline_y - top_decode_line; - uint32_t* dst = &sys->pixel_buffer[y * _ZX_DISPLAY_WIDTH]; + uint8_t* dst = &sys->fb[y * ZX_FRAMEBUFFER_WIDTH]; const uint8_t* vidmem_bank = sys->ram[sys->display_ram_bank]; const bool blink = 0 != (sys->blink_counter & 0x10); - uint32_t fg, bg; if ((y < 32) || (y >= 224)) { // upper/lower border - for (int x = 0; x < _ZX_DISPLAY_WIDTH; x++) { + for (int x = 0; x < ZX_DISPLAY_WIDTH; x++) { *dst++ = sys->border_color; } } @@ -388,19 +349,19 @@ static bool _zx_decode_scanline(zx_t* sys) { const uint8_t clr = vidmem_bank[clr_offset]; // foreground and background color + uint8_t fg, bg; if ((clr & (1<<7)) && blink) { - fg = _zx_palette[(clr>>3) & 7]; - bg = _zx_palette[clr & 7]; + fg = (clr>>3) & 7; + bg = clr & 7; } else { - fg = _zx_palette[clr & 7]; - bg = _zx_palette[(clr>>3) & 7]; - } - if (0 == (clr & (1<<6))) { - // standard brightness - fg &= 0xFFD7D7D7; - bg &= 0xFFD7D7D7; + fg = clr & 7; + bg = (clr>>3) & 7; } + // color bit 6: standard vs bright + fg |= (clr & (1<<6)) >> 3; + bg |= (clr & (1<<6)) >> 3; + for (int px = 7; px >=0; px--) { *dst++ = pix & (1<border_color = _zx_palette[data & 7] & 0xFFD7D7D7; + sys->border_color = data & 7; sys->last_fe_out = data; beeper_set(&sys->beeper, 0 != (data & (1<<4))); } @@ -833,8 +794,10 @@ static bool _zx_overflow(const uint8_t* ptr, intptr_t num_bytes, const uint8_t* return (ptr + num_bytes) > end_ptr; } -bool zx_quickload(zx_t* sys, const uint8_t* ptr, int num_bytes) { - const uint8_t* end_ptr = ptr + num_bytes; +bool zx_quickload(zx_t* sys, chips_range_t data) { + CHIPS_ASSERT(data.ptr && (data.size > 0)); + uint8_t* ptr = data.ptr; + const uint8_t* end_ptr = ptr + data.size; if (_zx_overflow(ptr, sizeof(_zx_z80_header), end_ptr)) { return false; } @@ -871,7 +834,7 @@ bool zx_quickload(zx_t* sys, const uint8_t* ptr, int num_bytes) { int page_index = 0; int src_len = 0; if (is_version1) { - src_len = num_bytes - sizeof(_zx_z80_header); + src_len = data.size - sizeof(_zx_z80_header); } else { _zx_z80_page_header* phdr = (_zx_z80_page_header*) ptr; @@ -983,29 +946,79 @@ bool zx_quickload(zx_t* sys, const uint8_t* ptr, int num_bytes) { else { sys->pins = z80_prefetch(&sys->cpu, (hdr->PC_h<<8)|hdr->PC_l); } - sys->border_color = _zx_palette[(hdr->flags0>>1) & 7] & 0xFFD7D7D7; + sys->border_color = (hdr->flags0>>1) & 7; return true; } -int zx_std_display_width(void) { - return _ZX_DISPLAY_WIDTH; -} - -int zx_std_display_height(void) { - return _ZX_DISPLAY_HEIGHT; +chips_display_info_t zx_display_info(zx_t* sys) { + static const uint32_t palette[16] = { + 0xFF000000, // std black + 0xFFD70000, // std blue + 0xFF0000D7, // std red + 0xFFD700D7, // std magenta + 0xFF00D700, // std green + 0xFFD7D700, // std cyan + 0xFF00D7D7, // std yellow + 0xFFD7D7D7, // std white + 0xFF000000, // bright black + 0xFFFF0000, // bright blue + 0xFF0000FF, // bright red + 0xFFFF00FF, // bright magenta + 0xFF00FF00, // bright green + 0xFFFFFF00, // bright cyan + 0xFF00FFFF, // bright yellow + 0xFFFFFFFF, // bright white + }; + const chips_display_info_t res = { + .frame = { + .dim = { + .width = ZX_FRAMEBUFFER_WIDTH, + .height = ZX_FRAMEBUFFER_HEIGHT, + }, + .buffer = { + .ptr = sys ? sys->fb : 0, + .size = ZX_FRAMEBUFFER_SIZE_BYTES, + }, + .bytes_per_pixel = 1, + }, + .screen = { + .x = 0, + .y = 0, + .width = ZX_DISPLAY_WIDTH, + .height = ZX_DISPLAY_HEIGHT, + }, + .palette = { + .ptr = (void*)palette, + .size = sizeof(palette), + } + }; + CHIPS_ASSERT(((sys == 0) && (res.frame.buffer.ptr == 0)) || ((sys != 0) && (res.frame.buffer.ptr != 0))); + return res; } -size_t zx_max_display_size(void) { - return _ZX_DISPLAY_SIZE; +uint32_t zx_save_snapshot(zx_t* sys, zx_t* dst) { + CHIPS_ASSERT(sys && dst); + *dst = *sys; + chips_debug_snapshot_onsave(&dst->debug); + chips_audio_callback_snapshot_onsave(&dst->audio.callback); + ay38910_snapshot_onsave(&dst->ay); + mem_snapshot_onsave(&dst->mem, sys); + return ZX_SNAPSHOT_VERSION; } -int zx_display_width(zx_t* sys) { - (void)sys; - return _ZX_DISPLAY_WIDTH; +bool zx_load_snapshot(zx_t* sys, uint32_t version, zx_t* src) { + CHIPS_ASSERT(sys && src); + if (version != ZX_SNAPSHOT_VERSION) { + return false; + } + static zx_t im; + im = *src; + chips_debug_snapshot_onload(&im.debug, &sys->debug); + chips_audio_callback_snapshot_onload(&im.audio.callback, &sys->audio.callback); + ay38910_snapshot_onload(&im.ay, &sys->ay); + mem_snapshot_onload(&im.mem, sys); + *sys = im; + return true; } -int zx_display_height(zx_t* sys) { - (void)sys; - return _ZX_DISPLAY_HEIGHT; -} #endif // CHIPS_IMPL diff --git a/ui/ui_am40010.h b/ui/ui_am40010.h index c420a91f..beb78815 100644 --- a/ui/ui_am40010.h +++ b/ui/ui_am40010.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -47,7 +47,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -119,12 +119,12 @@ void ui_am40010_discard(ui_am40010_t* win) { } static void _ui_am40010_draw_hw_colors(ui_am40010_t* win) { - am40010_colors_t* c = &win->am40010->colors; ImGui::Text("Hardware Colors:"); + const am40010_t* ga = win->am40010; const ImVec2 size(18,18); for (int i = 0; i < 32; i++) { ImGui::PushID(i); - ImGui::ColorButton("##hw_color", ImColor(c->hw_rgba8[i]), ImGuiColorEditFlags_NoAlpha, size); + ImGui::ColorButton("##hw_color", ImColor(ga->hw_colors[i]), ImGuiColorEditFlags_NoAlpha, size); ImGui::PopID(); if (((i+1) % 8) != 0) { ImGui::SameLine(); @@ -133,12 +133,12 @@ static void _ui_am40010_draw_hw_colors(ui_am40010_t* win) { } static void _ui_am40010_draw_ink_colors(ui_am40010_t* win) { - am40010_colors_t* c = &win->am40010->colors; + am40010_t* ga = win->am40010; ImGui::Text("Ink Colors:"); const ImVec2 size(18,18); for (int i = 0; i < 16; i++) { ImGui::PushID(128 + i); - ImGui::ColorButton("##ink_color", ImColor(c->ink_rgba8[i]), ImGuiColorEditFlags_NoAlpha, size); + ImGui::ColorButton("##ink_color", ImColor(ga->hw_colors[ga->regs.ink[i]]), ImGuiColorEditFlags_NoAlpha, size); ImGui::PopID(); if (((i+1) % 8) != 0) { ImGui::SameLine(); @@ -147,10 +147,10 @@ static void _ui_am40010_draw_ink_colors(ui_am40010_t* win) { } static void _ui_am40010_draw_border_color(ui_am40010_t* win) { - am40010_colors_t* c = &win->am40010->colors; + am40010_t* ga = win->am40010; ImGui::Text("Border Color:"); const ImVec2 size(18,18); - ImGui::ColorButton("##brd_color", ImColor(c->border_rgba8), ImGuiColorEditFlags_NoAlpha, size); + ImGui::ColorButton("##brd_color", ImColor(ga->hw_colors[ga->regs.border]), ImGuiColorEditFlags_NoAlpha, size); } static void _ui_am40010_draw_registers(ui_am40010_t* win) { @@ -195,19 +195,8 @@ static void _ui_am40010_draw_video(ui_am40010_t* win) { ImGui::Text("addr %04X", addr); } -static void _ui_am40010_tint_framebuffer(ui_am40010_t* win) { - const int num = AM40010_DBG_DISPLAY_WIDTH * AM40010_DBG_DISPLAY_HEIGHT; - uint32_t* ptr = win->am40010->rgba8_buffer; - for (int i = 0; i < num; i++) { - ptr[i] = ~ptr[i] | 0xFF0000F0; - } -} - static void _ui_am40010_draw_state(ui_am40010_t* win) { ImGui::Checkbox("Debug Visualization", &win->am40010->dbg_vis); - if (ImGui::Button("Tint Framebuffer")) { - _ui_am40010_tint_framebuffer(win); - } if (ImGui::CollapsingHeader("Colors", ImGuiTreeNodeFlags_DefaultOpen)) { _ui_am40010_draw_hw_colors(win); _ui_am40010_draw_ink_colors(win); diff --git a/ui/ui_atom.h b/ui/ui_atom.h index 9f77d918..af90aa39 100644 --- a/ui/ui_atom.h +++ b/ui/ui_atom.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -52,7 +52,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -67,10 +67,9 @@ typedef void (*ui_atom_boot_cb)(atom_t* sys); typedef struct { atom_t* atom; ui_atom_boot_cb boot_cb; - ui_dbg_create_texture_t create_texture_cb; /* texture creation callback for ui_dbg_t */ - ui_dbg_update_texture_t update_texture_cb; /* texture update callback for ui_dbg_t */ - ui_dbg_destroy_texture_t destroy_texture_cb; /* texture destruction callback for ui_dbg_t */ - ui_dbg_keys_desc_t dbg_keys; /* user-defined hotkeys for ui_dbg_t */ + ui_dbg_texture_callbacks_t dbg_texture; // texture create/update/destroy callbacks + ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; // snapshot ui setup params } ui_atom_desc_t; typedef struct { @@ -86,12 +85,13 @@ typedef struct { ui_memedit_t memedit[4]; ui_dasm_t dasm[4]; ui_dbg_t dbg; + ui_snapshot_t snapshot; } ui_atom_t; void ui_atom_init(ui_atom_t* ui, const ui_atom_desc_t* desc); void ui_atom_discard(ui_atom_t* ui); void ui_atom_draw(ui_atom_t* ui); -atom_debug_t ui_atom_get_debug(ui_atom_t* ui); +chips_debug_t ui_atom_get_debug(ui_atom_t* ui); #ifdef __cplusplus } /* extern "C" */ @@ -116,6 +116,7 @@ static void _ui_atom_draw_menu(ui_atom_t* ui) { CHIPS_ASSERT(ui && ui->atom && ui->boot_cb); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->snapshot); if (ImGui::MenuItem("Reset")) { atom_reset(ui->atom); ui_dbg_reset(&ui->dbg); @@ -148,6 +149,7 @@ static void _ui_atom_draw_menu(ui_atom_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -336,6 +338,7 @@ void ui_atom_init(ui_atom_t* ui, const ui_atom_desc_t* ui_desc) { CHIPS_ASSERT(ui && ui_desc); CHIPS_ASSERT(ui_desc->atom); CHIPS_ASSERT(ui_desc->boot_cb); + ui_snapshot_init(&ui->snapshot, &ui_desc->snapshot); ui->atom = ui_desc->atom; ui->boot_cb = ui_desc->boot_cb; int x = 20, y = 20, dx = 10, dy = 10; @@ -346,9 +349,7 @@ void ui_atom_init(ui_atom_t* ui, const ui_atom_desc_t* ui_desc) { desc.y = y; desc.m6502 = &ui->atom->cpu; desc.read_cb = _ui_atom_mem_read; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; desc.keys = ui_desc->dbg_keys; desc.user_data = ui->atom; ui_dbg_init(&ui->dbg, &desc); @@ -499,10 +500,10 @@ void ui_atom_draw(ui_atom_t* ui) { ui_dbg_draw(&ui->dbg); } -atom_debug_t ui_atom_get_debug(ui_atom_t* ui) { +chips_debug_t ui_atom_get_debug(ui_atom_t* ui) { CHIPS_ASSERT(ui); - atom_debug_t res = {}; - res.callback.func = (atom_debug_func_t)ui_dbg_tick; + chips_debug_t res = {}; + res.callback.func = (chips_debug_func_t)ui_dbg_tick; res.callback.user_data = &ui->dbg; res.stopped = &ui->dbg.dbg.stopped; return res; diff --git a/ui/ui_bombjack.h b/ui/ui_bombjack.h index 3ff946f0..6136274f 100644 --- a/ui/ui_bombjack.h +++ b/ui/ui_bombjack.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -32,6 +32,7 @@ - ui_dbg.h - ui_memedit.h - ui_memmap.h + - ui_snapshot.h ## zlib/libpng license @@ -49,7 +50,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -60,10 +61,9 @@ extern "C" { typedef struct { bombjack_t* sys; - ui_dbg_create_texture_t create_texture_cb; // texture creation callback for ui_dbg_t - ui_dbg_update_texture_t update_texture_cb; // texture update callback for ui_dbg_t - ui_dbg_destroy_texture_t destroy_texture_cb; // texture destruction callback for ui_dbg_t - ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_dbg_texture_callbacks_t dbg_texture; // texture create/update/destroy callbacks + ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; } ui_bombjack_desc_t; typedef struct { @@ -71,9 +71,7 @@ typedef struct { int w, h; bool open; int hovered_palette_column; - ui_dbg_create_texture_t create_texture; - ui_dbg_update_texture_t update_texture; - ui_dbg_destroy_texture_t destroy_texture; + ui_dbg_texture_callbacks_t texture_cbs; void* tex_16x16[24]; void* tex_32x32[24]; uint32_t pixel_buffer[1024]; @@ -81,6 +79,8 @@ typedef struct { typedef struct { bombjack_t* bj; + uint8_t sys_clear_mask; + double sys_clear_modified_age; struct { ui_z80_t cpu; ui_dbg_t dbg; @@ -95,6 +95,7 @@ typedef struct { ui_memedit_t memedit[4]; ui_dasm_t dasm[4]; ui_bombjack_video_t video; + ui_snapshot_t snapshot; } ui_bombjack_t; void ui_bombjack_init(ui_bombjack_t* ui, const ui_bombjack_desc_t* desc); @@ -249,6 +250,7 @@ void ui_bombjack_init(ui_bombjack_t* ui, const ui_bombjack_desc_t* ui_desc) { CHIPS_ASSERT(ui_desc->sys); memset(ui, 0, sizeof(ui_bombjack_t)); ui->bj = ui_desc->sys; + ui_snapshot_init(&ui->snapshot, &ui_desc->snapshot); int x = 20, y = 20, dx = 10, dy = 10; { ui_dbg_desc_t desc = {0}; @@ -258,9 +260,7 @@ void ui_bombjack_init(ui_bombjack_t* ui, const ui_bombjack_desc_t* ui_desc) { desc.z80 = &ui->bj->mainboard.cpu; desc.read_cb = _ui_bombjack_mem_read; desc.read_layer = _UI_BOMBJACK_MEMLAYER_MAIN; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; desc.keys = ui_desc->dbg_keys; desc.user_data = ui; ui_dbg_init(&ui->main.dbg, &desc); @@ -385,19 +385,17 @@ void ui_bombjack_init(ui_bombjack_t* ui, const ui_bombjack_desc_t* ui_desc) { } } { - ui->video.create_texture = ui_desc->create_texture_cb; - ui->video.update_texture = ui_desc->update_texture_cb; - ui->video.destroy_texture = ui_desc->destroy_texture_cb; + ui->video.texture_cbs = ui_desc->dbg_texture; ui->video.x = 10; ui->video.y = 20; ui->video.w = 450; ui->video.h = 568; ui->video.hovered_palette_column = -1; for (int i = 0; i < 24; i++) { - ui->video.tex_16x16[i] = ui->video.create_texture(16, 16); + ui->video.tex_16x16[i] = ui->video.texture_cbs.create_cb(16, 16); } for (int i = 0; i < 24; i++) { - ui->video.tex_32x32[i] = ui->video.create_texture(32, 32); + ui->video.tex_32x32[i] = ui->video.texture_cbs.create_cb(32, 32); } } } @@ -405,8 +403,8 @@ void ui_bombjack_init(ui_bombjack_t* ui, const ui_bombjack_desc_t* ui_desc) { void ui_bombjack_discard(ui_bombjack_t* ui) { CHIPS_ASSERT(ui && ui->bj); for (int i = 0; i < 24; i++) { - ui->video.destroy_texture(ui->video.tex_16x16[i]); - ui->video.destroy_texture(ui->video.tex_32x32[i]); + ui->video.texture_cbs.destroy_cb(ui->video.tex_16x16[i]); + ui->video.texture_cbs.destroy_cb(ui->video.tex_32x32[i]); } ui_dbg_discard(&ui->main.dbg); ui_dbg_discard(&ui->sound.dbg); @@ -423,6 +421,24 @@ void ui_bombjack_discard(ui_bombjack_t* ui) { ui_z80_discard(&ui->sound.cpu); } +static void _ui_bombjack_set_sys_bit(ui_bombjack_t* ui, uint8_t mask) { + ui->bj->mainboard.sys |= mask; + ui->sys_clear_mask |= mask; + ui->sys_clear_modified_age = 0.0; +} + +static void _ui_bombjack_handle_sys_bits(ui_bombjack_t* ui) { + // sys bits need to be set long enough for the emulator to scan + // (=> at least 60Hz?) + if (ui->sys_clear_mask != 0) { + ui->sys_clear_modified_age += ImGui::GetIO().DeltaTime; + if (ui->sys_clear_modified_age > (1.0 / 30.0)) { + ui->bj->mainboard.sys &= ~ui->sys_clear_mask; + ui->sys_clear_mask = 0; + } + } +} + static bool _ui_bombjack_test_bits(uint8_t val, uint8_t mask, uint8_t bits) { return bits == (val & mask); } @@ -459,6 +475,7 @@ static void _ui_bombjack_set_dsw2(const ui_bombjack_t* ui, uint8_t mask, uint8_t static void _ui_bombjack_draw_menu(ui_bombjack_t* ui) { if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->snapshot); if (ImGui::MenuItem("Reboot")) { bombjack_reset(ui->bj); ui_dbg_reboot(&ui->main.dbg); @@ -466,7 +483,7 @@ static void _ui_bombjack_draw_menu(ui_bombjack_t* ui) { } if (ImGui::BeginMenu("Player 1")) { if (ImGui::MenuItem("Insert Coin")) { - ui->bj->mainboard.sys |= BOMBJACK_SYS_P1_COIN; + _ui_bombjack_set_sys_bit(ui, BOMBJACK_SYS_P1_COIN); } if (ImGui::BeginMenu("1 Coin gives:")) { if (ImGui::MenuItem("1 Credit", nullptr, _ui_bombjack_test_dsw1(ui, BOMBJACK_DSW1_P1_MASK, BOMBJACK_DSW1_P1_1COIN_1PLAY))) { @@ -484,13 +501,13 @@ static void _ui_bombjack_draw_menu(ui_bombjack_t* ui) { ImGui::EndMenu(); } if (ImGui::MenuItem("Play Button")) { - ui->bj->mainboard.sys |= BOMBJACK_SYS_P1_START; + _ui_bombjack_set_sys_bit(ui, BOMBJACK_SYS_P1_START); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Player 2")) { if (ImGui::MenuItem("Insert Coin")) { - ui->bj->mainboard.sys |= BOMBJACK_SYS_P2_COIN; + _ui_bombjack_set_sys_bit(ui, BOMBJACK_SYS_P2_COIN); } if (ImGui::BeginMenu("1 Coin gives:")) { if (ImGui::MenuItem("1 Credit", nullptr, _ui_bombjack_test_dsw1(ui, BOMBJACK_DSW1_P2_MASK, BOMBJACK_DSW1_P2_1COIN_1PLAY))) { @@ -508,7 +525,7 @@ static void _ui_bombjack_draw_menu(ui_bombjack_t* ui) { ImGui::EndMenu(); } if (ImGui::MenuItem("Play Button")) { - ui->bj->mainboard.sys |= BOMBJACK_SYS_P2_START; + _ui_bombjack_set_sys_bit(ui, BOMBJACK_SYS_P2_START); } ImGui::EndMenu(); } @@ -595,6 +612,7 @@ static void _ui_bombjack_draw_menu(ui_bombjack_t* ui) { if (ImGui::BeginMenu("Main Board")) { ImGui::MenuItem("CPU Debugger", 0, &ui->main.dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->main.dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->main.dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->main.dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->main.dbg.ui.show_heatmap); ImGui::EndMenu(); @@ -602,6 +620,7 @@ static void _ui_bombjack_draw_menu(ui_bombjack_t* ui) { if (ImGui::BeginMenu("Sound Board")) { ImGui::MenuItem("CPU Debugger", 0, &ui->sound.dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->sound.dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->sound.dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->sound.dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->sound.dbg.ui.show_heatmap); ImGui::EndMenu(); @@ -702,11 +721,11 @@ static void _ui_bombjack_decode_sprites(ui_bombjack_t* ui) { uint8_t b1 = ui->bj->main_ram[addr + 1]; if (b0 & 0x80) { _ui_bombjack_decode_sprite_32x32(ui, b0, b1); - ui->video.update_texture(ui->video.tex_32x32[sprite_nr], ui->video.pixel_buffer, 32*32*sizeof(uint32_t)); + ui->video.texture_cbs.update_cb(ui->video.tex_32x32[sprite_nr], ui->video.pixel_buffer, 32*32*sizeof(uint32_t)); } else { _ui_bombjack_decode_sprite_16x16(ui, b0, b1); - ui->video.update_texture(ui->video.tex_16x16[sprite_nr], ui->video.pixel_buffer, 16*16*sizeof(uint32_t)); + ui->video.texture_cbs.update_cb(ui->video.tex_16x16[sprite_nr], ui->video.pixel_buffer, 16*16*sizeof(uint32_t)); } } } @@ -813,7 +832,7 @@ static void _ui_bombjack_draw_video(ui_bombjack_t* ui) { void ui_bombjack_draw(ui_bombjack_t* ui) { CHIPS_ASSERT(ui && ui->bj); - ui->bj->mainboard.sys = 0; // FIXME? + _ui_bombjack_handle_sys_bits(ui); _ui_bombjack_draw_menu(ui); _ui_bombjack_draw_video(ui); ui_memmap_draw(&ui->memmap); @@ -834,10 +853,10 @@ void ui_bombjack_draw(ui_bombjack_t* ui) { bombjack_debug_t ui_bombjack_get_debug(ui_bombjack_t* ui) { CHIPS_ASSERT(ui); bombjack_debug_t res = {}; - res.mainboard.callback.func = (bombjack_debug_func_t)ui_dbg_tick; + res.mainboard.callback.func = (chips_debug_func_t)ui_dbg_tick; res.mainboard.callback.user_data = &ui->main.dbg; res.mainboard.stopped = &ui->main.dbg.dbg.stopped; - res.soundboard.callback.func = (bombjack_debug_func_t)ui_dbg_tick; + res.soundboard.callback.func = (chips_debug_func_t)ui_dbg_tick; res.soundboard.callback.user_data = &ui->sound.dbg; res.soundboard.stopped = &ui->sound.dbg.dbg.stopped; return res; diff --git a/ui/ui_c64.h b/ui/ui_c64.h index fe6305dd..165e9397 100644 --- a/ui/ui_c64.h +++ b/ui/ui_c64.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -37,6 +37,7 @@ - ui_memedit.h - ui_memmap.h - ui_kbd.h + - ui_snapshot.h ## zlib/libpng license @@ -54,7 +55,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -70,10 +71,10 @@ typedef void (*ui_c64_boot_cb)(c64_t* sys); typedef struct { c64_t* c64; // pointer to c64_t instance to track ui_c64_boot_cb boot_cb; // reboot callback function - ui_dbg_create_texture_t create_texture_cb; // texture creation callback for ui_dbg_t - ui_dbg_update_texture_t update_texture_cb; // texture update callback for ui_dbg_t - ui_dbg_destroy_texture_t destroy_texture_cb; // texture destruction callback for ui_dbg_t - ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_dbg_texture_callbacks_t dbg_texture; // texture create/update/destroy callbacks + ui_dbg_debug_callbacks_t dbg_debug; + ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; // snapshot UI setup params } ui_c64_desc_t; typedef struct { @@ -92,12 +93,13 @@ typedef struct { ui_dasm_t dasm[4]; ui_dbg_t dbg; ui_dbg_t c1541_dbg; + ui_snapshot_t snapshot; } ui_c64_t; void ui_c64_init(ui_c64_t* ui, const ui_c64_desc_t* desc); void ui_c64_discard(ui_c64_t* ui); void ui_c64_draw(ui_c64_t* ui); -c64_debug_t ui_c64_get_debug(ui_c64_t* ui); +chips_debug_t ui_c64_get_debug(ui_c64_t* ui); #ifdef __cplusplus } /* extern "C" */ @@ -122,6 +124,7 @@ static void _ui_c64_draw_menu(ui_c64_t* ui) { CHIPS_ASSERT(ui && ui->c64 && ui->boot_cb); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->snapshot); if (ImGui::MenuItem("Reset")) { c64_reset(ui->c64); ui_dbg_reset(&ui->dbg); @@ -170,6 +173,7 @@ static void _ui_c64_draw_menu(ui_c64_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -189,6 +193,7 @@ static void _ui_c64_draw_menu(ui_c64_t* ui) { if (ui->c64->c1541.valid) { if (ImGui::BeginMenu("VC-1541 (Floppy Drive)")) { ImGui::MenuItem("CPU Debugger", 0, &ui->c1541_dbg.ui.open); + ImGui::MenuItem("Stopwatch", 0, &ui->c1541_dbg.ui.show_stopwatch); ImGui::MenuItem("Breakpoints", 0, &ui->c1541_dbg.ui.show_breakpoints); ImGui::MenuItem("Execution History", 0, &ui->c1541_dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->c1541_dbg.ui.show_heatmap); @@ -539,6 +544,7 @@ void ui_c64_init(ui_c64_t* ui, const ui_c64_desc_t* ui_desc) { CHIPS_ASSERT(ui_desc->boot_cb); ui->c64 = ui_desc->c64; ui->boot_cb = ui_desc->boot_cb; + ui_snapshot_init(&ui->snapshot, &ui_desc->snapshot); int x = 20, y = 20, dx = 10, dy = 10; { ui_dbg_desc_t desc = {0}; @@ -546,11 +552,13 @@ void ui_c64_init(ui_c64_t* ui, const ui_c64_desc_t* ui_desc) { desc.x = x; desc.y = y; desc.m6502 = &ui->c64->cpu; + desc.freq_hz = C64_FREQUENCY; + desc.scanline_ticks = M6569_HTOTAL; + desc.frame_ticks = M6569_HTOTAL * M6569_VTOTAL; desc.read_cb = _ui_c64_mem_read; desc.break_cb = _ui_c64_eval_bp; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; + desc.debug_cbs = ui_desc->dbg_debug; desc.keys = ui_desc->dbg_keys; desc.user_data = ui; /* custom breakpoint types */ @@ -749,10 +757,10 @@ void ui_c64_draw(ui_c64_t* ui) { } } -c64_debug_t ui_c64_get_debug(ui_c64_t* ui) { +chips_debug_t ui_c64_get_debug(ui_c64_t* ui) { CHIPS_ASSERT(ui); - c64_debug_t res = {}; - res.callback.func = (c64_debug_func_t)ui_dbg_tick; + chips_debug_t res = {}; + res.callback.func = (chips_debug_func_t)ui_dbg_tick; res.callback.user_data = &ui->dbg; res.stopped = &ui->dbg.dbg.stopped; return res; diff --git a/ui/ui_cpc.h b/ui/ui_cpc.h index 8368fee3..c6d32cd8 100644 --- a/ui/ui_cpc.h +++ b/ui/ui_cpc.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -56,7 +56,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -65,16 +65,16 @@ extern "C" { #endif -/* general callback type for rebooting to different configs */ +// general callback type for rebooting to different configs typedef void (*ui_cpc_boot_t)(cpc_t* sys, cpc_type_t type); typedef struct { cpc_t* cpc; - ui_cpc_boot_t boot_cb; /* user-provided callback to reboot to different config */ - ui_dbg_create_texture_t create_texture_cb; /* texture creation callback for ui_dbg_t */ - ui_dbg_update_texture_t update_texture_cb; /* texture update callback for ui_dbg_t */ - ui_dbg_destroy_texture_t destroy_texture_cb; /* texture destruction callback for ui_dbg_t */ - ui_dbg_keys_desc_t dbg_keys; /* user-defined hotkeys for ui_dbg_t */ + ui_cpc_boot_t boot_cb; // user-provided callback to reboot to different config + ui_dbg_texture_callbacks_t dbg_texture; // debug texture create/update/destroy callbacks + ui_dbg_debug_callbacks_t dbg_debug; // user-provided debugger callbacks + ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; // snapshot ui setup params } ui_cpc_desc_t; typedef struct { @@ -95,12 +95,13 @@ typedef struct { ui_memedit_t memedit[4]; ui_dasm_t dasm[4]; ui_dbg_t dbg; + ui_snapshot_t snapshot; } ui_cpc_t; void ui_cpc_init(ui_cpc_t* ui, const ui_cpc_desc_t* desc); void ui_cpc_discard(ui_cpc_t* ui); void ui_cpc_draw(ui_cpc_t* ui); -cpc_debug_t ui_cpc_get_debug(ui_cpc_t* ui); +chips_debug_t ui_cpc_get_debug(ui_cpc_t* ui); #ifdef __cplusplus } /* extern "C" */ @@ -125,6 +126,7 @@ static void _ui_cpc_draw_menu(ui_cpc_t* ui) { CHIPS_ASSERT(ui && ui->cpc && ui->boot_cb); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->snapshot); if (ImGui::MenuItem("Reset")) { cpc_reset(ui->cpc); ui_dbg_reset(&ui->dbg); @@ -144,8 +146,7 @@ static void _ui_cpc_draw_menu(ui_cpc_t* ui) { if (ImGui::MenuItem("Joystick", 0, ui->cpc->joystick_type != CPC_JOYSTICK_NONE)) { if (ui->cpc->joystick_type == CPC_JOYSTICK_NONE) { ui->cpc->joystick_type = CPC_JOYSTICK_DIGITAL; - } - else { + } else { ui->cpc->joystick_type = CPC_JOYSTICK_NONE; } } @@ -167,6 +168,7 @@ static void _ui_cpc_draw_menu(ui_cpc_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -240,8 +242,7 @@ static void _ui_cpc_update_memmap(ui_cpc_t* ui) { ui_memmap_region(&ui->memmap, "RAM 1", 0x4000, 0x4000, true); ui_memmap_region(&ui->memmap, "RAM 2", 0x8000, 0x4000, true); ui_memmap_region(&ui->memmap, "RAM 3 (Screen)", 0xC000, 0x4000, true); - } - else { + } else { const uint8_t ram_config_index = cpc->ga.ram_config & 7; const uint8_t rom_select = cpc->ga.rom_select; ui_memmap_layer(&ui->memmap, "ROM Layer 0"); @@ -270,27 +271,21 @@ static uint8_t* _ui_cpc_memptr(cpc_t* cpc, int layer, uint16_t addr) { if (layer == _UI_CPC_MEMLAYER_GA) { uint8_t* ram = &cpc->ram[0][0]; return ram + addr; - } - else if (layer == _UI_CPC_MEMLAYER_ROMS) { + } else if (layer == _UI_CPC_MEMLAYER_ROMS) { if (addr < 0x4000) { return &cpc->rom_os[addr]; - } - else if (addr >= 0xC000) { + } else if (addr >= 0xC000) { return &cpc->rom_basic[addr - 0xC000]; - } - else { + } else { return 0; } - } - else if (layer == _UI_CPC_MEMLAYER_AMSDOS) { + } else if (layer == _UI_CPC_MEMLAYER_AMSDOS) { if ((CPC_TYPE_6128 == cpc->type) && (addr >= 0xC000)) { return &cpc->rom_amsdos[addr - 0xC000]; - } - else { + } else { return 0; } - } - else { + } else { /* one of the 7 RAM layers */ CHIPS_ASSERT((layer >= _UI_CPC_MEMLAYER_RAM0) && (layer <= _UI_CPC_MEMLAYER_RAM7)); const int ram_config_index = (CPC_TYPE_6128 == cpc->type) ? (cpc->ga.ram_config & 7) : 0; @@ -327,13 +322,11 @@ static uint8_t _ui_cpc_mem_read(int layer, uint16_t addr, void* user_data) { if (layer == _UI_CPC_MEMLAYER_CPU) { /* CPU mapped RAM layer */ return mem_rd(&cpc->mem, addr); - } - else { + } else { uint8_t* ptr = _ui_cpc_memptr(cpc, layer, addr); if (ptr) { return *ptr; - } - else { + } else { return 0xFF; } } @@ -345,8 +338,7 @@ static void _ui_cpc_mem_write(int layer, uint16_t addr, uint8_t data, void* user cpc_t* cpc = ui_cpc->cpc; if (layer == _UI_CPC_MEMLAYER_CPU) { mem_wr(&cpc->mem, addr, data); - } - else { + } else { uint8_t* ptr = _ui_cpc_memptr(cpc, layer, addr); if (ptr) { *ptr = data; @@ -587,6 +579,7 @@ void ui_cpc_init(ui_cpc_t* ui, const ui_cpc_desc_t* ui_desc) { CHIPS_ASSERT(ui_desc->boot_cb); ui->cpc = ui_desc->cpc; ui->boot_cb = ui_desc->boot_cb; + ui_snapshot_init(&ui->snapshot, &ui_desc->snapshot); int x = 20, y = 20, dx = 10, dy = 10; { ui_dbg_desc_t desc = {0}; @@ -596,9 +589,8 @@ void ui_cpc_init(ui_cpc_t* ui, const ui_cpc_desc_t* ui_desc) { desc.z80 = &ui->cpc->cpu; desc.read_cb = _ui_cpc_mem_read; desc.break_cb = _ui_cpc_eval_bp; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; + desc.debug_cbs = ui_desc->dbg_debug; desc.keys = ui_desc->dbg_keys; desc.user_data = ui; /* custom breakpoint types */ @@ -786,10 +778,10 @@ void ui_cpc_draw(ui_cpc_t* ui) { ui_dbg_draw(&ui->dbg); } -cpc_debug_t ui_cpc_get_debug(ui_cpc_t* ui) { +chips_debug_t ui_cpc_get_debug(ui_cpc_t* ui) { CHIPS_ASSERT(ui); - cpc_debug_t res = {}; - res.callback.func = (cpc_debug_func_t)ui_dbg_tick; + chips_debug_t res = {}; + res.callback.func = (chips_debug_func_t)ui_dbg_tick; res.callback.user_data = &ui->dbg; res.stopped = &ui->dbg.dbg.stopped; return res; diff --git a/ui/ui_dasm.h b/ui/ui_dasm.h index c1c144c3..762c94c9 100644 --- a/ui/ui_dasm.h +++ b/ui/ui_dasm.h @@ -418,7 +418,7 @@ static void _ui_dasm_draw_stack(ui_dasm_t* win) { win->stack_num = 0; } char buf[5] = { 0 }; - if (ImGui::ListBoxHeader("##stack", ImVec2(-1,-1))) { + if (ImGui::BeginListBox("##stack", ImVec2(-1,-1))) { for (int i = 0; i < win->stack_num; i++) { snprintf(buf, sizeof(buf), "%04X", win->stack[i]); ImGui::PushID(i); @@ -432,7 +432,7 @@ static void _ui_dasm_draw_stack(ui_dasm_t* win) { } ImGui::PopID(); } - ImGui::ListBoxFooter(); + ImGui::EndListBox(); } ImGui::EndChild(); } diff --git a/ui/ui_dbg.h b/ui/ui_dbg.h index 7ae62ef1..5184cadc 100644 --- a/ui/ui_dbg.h +++ b/ui/ui_dbg.h @@ -8,7 +8,7 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Select the supported CPUs with the following macros (define one @@ -18,7 +18,7 @@ UI_DBG_USE_M6502 Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -53,7 +53,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -71,8 +71,6 @@ extern "C" { #define UI_DBG_MAX_USER_BREAKTYPES (8) /* max number of user breakpoint types */ #define UI_DBG_STEP_TRAPID (128) /* special trap id when step-mode active */ #define UI_DBG_BP_BASE_TRAPID (UI_DBG_STEP_TRAPID+1) /* first CPU trap-id used for breakpoints */ -#define UI_DBG_MAX_STRLEN (32) -#define UI_DBG_MAX_BINLEN (16) #define UI_DBG_NUM_LINES (256) #define UI_DBG_NUM_BACKTRACE_LINES (UI_DBG_NUM_LINES/2) #define UI_DBG_NUM_HISTORY_ITEMS (256) @@ -110,6 +108,13 @@ enum { UI_DBG_STEPMODE_TICK, }; +enum { + UI_DBG_STOP_REASON_UNKNOWN = 0, + UI_DBG_STOP_REASON_BREAK = 1, + UI_DBG_STOP_REASON_BREAKPOINT = 2, + UI_DBG_STOP_REASON_STEP = 3, +}; + /* a breakpoint description */ typedef struct ui_dbg_breakpoint_t { int type; /* UI_DBG_BREAKTYPE_* */ @@ -141,6 +146,14 @@ typedef void* (*ui_dbg_create_texture_t)(int w, int h); typedef void (*ui_dbg_update_texture_t)(void* tex_handle, void* data, int data_byte_size); /* callback to destroy a UI texture */ typedef void (*ui_dbg_destroy_texture_t)(void* tex_handle); +/* callback when emulator is being rebootet */ +typedef void (*ui_dbg_reboot_t)(void); +/* callback when emulator is being reset */ +typedef void (*ui_dbg_reset_t)(void); +/* callback when emulator has stopped at an address (stop_reason is UI_DBG_STOP_REASON_XXX) */ +typedef void (*ui_dbg_stopped_t)(int stop_reason, uint16_t addr); +/* callback when emulator has continued after stopped state */ +typedef void (*ui_dbg_continued_t)(void); /* user-defined hotkeys (all strings must be static) */ typedef struct ui_dbg_key_desc_t { @@ -157,36 +170,42 @@ typedef struct ui_dbg_keys_desc_t { ui_dbg_key_desc_t toggle_breakpoint; } ui_dbg_keys_desc_t; +typedef struct ui_dbg_texture_callbacks_t { + ui_dbg_create_texture_t create_cb; // callback to create UI texture + ui_dbg_update_texture_t update_cb; // callback to update UI texture + ui_dbg_destroy_texture_t destroy_cb; // callback to destroy UI texture +} ui_dbg_texture_callbacks_t; + +typedef struct ui_dbg_debug_callbacks_t { + ui_dbg_reboot_t reboot_cb; + ui_dbg_reset_t reset_cb; + ui_dbg_stopped_t stopped_cb; + ui_dbg_continued_t continued_cb; +} ui_dbg_debug_callbacks_t; + typedef struct ui_dbg_desc_t { - const char* title; /* window title */ + const char* title; // window title #if defined(UI_DBG_USE_Z80) - z80_t* z80; /* Z80 CPU to track */ + z80_t* z80; // Z80 CPU to track #elif defined(UI_DBG_USE_M6502) - m6502_t* m6502; /* 6502 CPU to track */ + m6502_t* m6502; // 6502 CPU to track #endif - ui_dbg_read_t read_cb; /* callback to read memory */ - int read_layer; /* layer argument for read_cb */ - ui_dbg_user_break_t break_cb; /* optional user-breakpoint evaluation callback */ - ui_dbg_create_texture_t create_texture_cb; /* callback to create UI texture */ - ui_dbg_update_texture_t update_texture_cb; /* callback to update UI texture */ - ui_dbg_destroy_texture_t destroy_texture_cb; /* callback to destroy UI texture */ - void* user_data; /* user data for callbacks */ - int x, y; /* initial window pos */ - int w, h; /* initial window size, or 0 for default size */ - bool open; /* initial open state */ - ui_dbg_keys_desc_t keys; /* user-defined hotkeys */ + uint32_t freq_hz; // CPU clock frequency in Hz + uint32_t scanline_ticks; // length of a raster line in clock cycles + uint32_t frame_ticks; // length of a frame in clock cycles + ui_dbg_read_t read_cb; // callback to read memory + int read_layer; // layer argument for read_cb + ui_dbg_user_break_t break_cb; // optional user-breakpoint evaluation callback + ui_dbg_texture_callbacks_t texture_cbs; + ui_dbg_debug_callbacks_t debug_cbs; + void* user_data; // user data for callbacks + int x, y; // initial window pos + int w, h; // initial window size, or 0 for default size + bool open; // initial open state + ui_dbg_keys_desc_t keys; // user-defined hotkeys ui_dbg_breaktype_t user_breaktypes[UI_DBG_MAX_USER_BREAKTYPES]; /* user-defined breakpoint types */ } ui_dbg_desc_t; -/* disassembler state */ -typedef struct ui_dbg_dasm_t { - uint16_t cur_addr; - int str_pos; - char str_buf[UI_DBG_MAX_STRLEN]; - int bin_pos; - uint8_t bin_buf[UI_DBG_MAX_BINLEN]; -} ui_dbg_dasm_t; - /* debugger state */ typedef struct ui_dbg_state_t { #if defined(UI_DBG_USE_Z80) @@ -195,8 +214,9 @@ typedef struct ui_dbg_state_t { m6502_t* m6502; #endif bool stopped; + bool external_debugger_connected; int step_mode; - uint64_t last_tick_pins; // cpu pins in last tick + uint64_t last_tick_pins; // cpu pins in last tick uint32_t frame_id; // used in trap callback to detect when a new frame has started uint32_t cur_op_ticks; uint16_t cur_op_pc; // PC of current instruction @@ -226,6 +246,7 @@ typedef struct ui_dbg_uistate_t { bool show_bytes; bool show_ticks; bool show_history; + bool show_stopwatch; bool request_scroll; ui_dbg_keys_desc_t keys; ui_dbg_line_t line_array[UI_DBG_NUM_LINES]; @@ -234,12 +255,15 @@ typedef struct ui_dbg_uistate_t { const char* breaktype_combo_labels[UI_DBG_MAX_BREAKTYPES]; } ui_dbg_uistate_t; +enum { + UI_DBG_HEATMAP_ITEM_OPCODE = (1<<0), + UI_DBG_HEATMAP_ITEM_WRITE = (1<<1), + UI_DBG_HEATMAP_ITEM_READ = (1<<2), +}; + typedef struct ui_dbg_heatmapitem_t { - uint32_t op_count; /* set for first instruction byte */ - uint32_t write_count; - uint32_t read_count; - uint16_t ticks; /* ticks of current instruction */ - uint16_t op_start; /* set for followup instruction bytes */ + uint8_t state; // UI_DBG_HEATMAP_ITEM_xxx + uint8_t ticks; // instruction tick count } ui_dbg_heatmapitem_t; typedef struct ui_dbg_heatmap_t { @@ -262,37 +286,88 @@ typedef struct ui_dbg_history_t { uint16_t pos; } ui_dbg_history_t; +enum { + UI_DBG_DASM_LINE_MAX_BYTES = 8, + UI_DBG_DASM_LINE_MAX_CHARS = 32, +}; + +typedef struct ui_dbg_dasm_line_t { + uint16_t addr; + uint8_t num_bytes; + uint8_t num_chars; + uint8_t bytes[UI_DBG_DASM_LINE_MAX_BYTES]; + char chars[UI_DBG_DASM_LINE_MAX_CHARS]; +} ui_dbg_dasm_line_t; + +typedef struct ui_dbg_dasm_request_t { + uint16_t addr; // base address + int offset_lines; // offset in number of ops/lines, may be negative + int num_lines; // number of lines to disassemble + ui_dbg_dasm_line_t* out_lines; // pointer to output ops, must have at least num_ops entries +} ui_dbg_dasm_request_t; + +enum { + UI_DBG_STOPWATCH_NUM = 8, +}; +typedef struct ui_dbg_stopwatch_t { + uint32_t freq_hz; + uint32_t scanline_ticks; + uint32_t frame_ticks; + uint64_t cur_ticks; + uint64_t start_ticks[UI_DBG_STOPWATCH_NUM]; +} ui_dbg_stopwatch_t; + typedef struct ui_dbg_t { bool valid; ui_dbg_read_t read_cb; int read_layer; ui_dbg_user_break_t break_cb; - ui_dbg_create_texture_t create_texture_cb; - ui_dbg_update_texture_t update_texture_cb; - ui_dbg_destroy_texture_t destroy_texture_cb; + ui_dbg_texture_callbacks_t texture_cbs; + ui_dbg_debug_callbacks_t debug_cbs; void* user_data; - ui_dbg_dasm_t dasm; + ui_dbg_dasm_line_t dasm_line; ui_dbg_state_t dbg; ui_dbg_uistate_t ui; ui_dbg_heatmap_t heatmap; ui_dbg_history_t history; + ui_dbg_stopwatch_t stopwatch; } ui_dbg_t; -/* initialize a new ui_dbg_t instance */ +// initialize a new ui_dbg_t instance void ui_dbg_init(ui_dbg_t* win, ui_dbg_desc_t* desc); -/* discard ui_dbg_t instance */ +// discard ui_dbg_t instance void ui_dbg_discard(ui_dbg_t* win); -/* render the ui_dbg_t UIs */ +// notify ui_dbg that an external debugger has connected (may change some behaviour) +void ui_dbg_external_debugger_connected(ui_dbg_t* win); +// notify ui_dbg that an external debugger has disconnected (clears breakpoints and continues) +void ui_dbg_external_debugger_disconnected(ui_dbg_t* win); +// render the ui_dbg_t UIs void ui_dbg_draw(ui_dbg_t* win); -/* call after ticking the system */ +// call after ticking the system void ui_dbg_tick(ui_dbg_t* win, uint64_t pins); -/* call when resetting the emulated machine (re-initializes some data structures) */ +// call when resetting the emulated machine (re-initializes some data structures) void ui_dbg_reset(ui_dbg_t* win); -/* call when rebooting the emulated machine (re-initializes some data structures) */ +// call when rebooting the emulated machine (re-initializes some data structures) void ui_dbg_reboot(ui_dbg_t* win); +// set an execution breakpoint at address +void ui_dbg_add_breakpoint(ui_dbg_t* win, uint16_t addr); +// clear an execution breakpoint at address +void ui_dbg_remove_breakpoint(ui_dbg_t* win, uint16_t addr); +// pause/stop execution +void ui_dbg_break(ui_dbg_t* win); +// continue execution +void ui_dbg_continue(ui_dbg_t* win, bool invoke_continued_cb); +// return true if the debugger is currently stopped +bool ui_dbg_stopped(ui_dbg_t* win); +// perform a debugger step-next (step over) +void ui_dbg_step_next(ui_dbg_t* win); +// peform a debugger step-into +void ui_dbg_step_into(ui_dbg_t* win); +// request a disassembly at start address +void ui_dbg_disassemble(ui_dbg_t* win, const ui_dbg_dasm_request_t* request); #ifdef __cplusplus -} /* extern "C" */ +} // extern "C" #endif /*-- IMPLEMENTATION ----------------------------------------------------------*/ @@ -307,8 +382,7 @@ void ui_dbg_reboot(ui_dbg_t* win); static inline const char* _ui_dbg_str_or_def(const char* str, const char* def) { if (str) { return str; - } - else { + } else { return def; } } @@ -330,9 +404,9 @@ static inline uint16_t _ui_dbg_get_pc(ui_dbg_t* win) { /* disassembler callback to fetch the next instruction byte */ static uint8_t _ui_dbg_dasm_in_cb(void* user_data) { ui_dbg_t* win = (ui_dbg_t*) user_data; - uint8_t val = _ui_dbg_read_byte(win, win->dasm.cur_addr++); - if (win->dasm.bin_pos < UI_DBG_MAX_BINLEN) { - win->dasm.bin_buf[win->dasm.bin_pos++] = val; + uint8_t val = _ui_dbg_read_byte(win, win->dasm_line.addr++); + if (win->dasm_line.num_bytes < UI_DBG_DASM_LINE_MAX_BYTES) { + win->dasm_line.bytes[win->dasm_line.num_bytes++] = val; } return val; } @@ -340,38 +414,27 @@ static uint8_t _ui_dbg_dasm_in_cb(void* user_data) { /* disassembler callback to output a character */ static void _ui_dbg_dasm_out_cb(char c, void* user_data) { ui_dbg_t* win = (ui_dbg_t*) user_data; - if ((win->dasm.str_pos+1) < UI_DBG_MAX_STRLEN) { - win->dasm.str_buf[win->dasm.str_pos++] = c; - win->dasm.str_buf[win->dasm.str_pos] = 0; + if ((win->dasm_line.num_chars + 1) < UI_DBG_DASM_LINE_MAX_CHARS) { + win->dasm_line.chars[win->dasm_line.num_chars++] = c; + win->dasm_line.chars[win->dasm_line.num_chars] = 0; } } -/* disassemble the next instruction */ -static inline uint16_t _ui_dbg_disasm(ui_dbg_t* win, uint16_t pc) { - win->dasm.cur_addr = pc; - win->dasm.str_pos = 0; - win->dasm.bin_pos = 0; +// disassemble instruction at address +static inline uint16_t _ui_dbg_disasm(ui_dbg_t* win, uint16_t addr) { + memset(&win->dasm_line, 0, sizeof(win->dasm_line)); + win->dasm_line.addr = addr; #if defined(UI_DBG_USE_Z80) - z80dasm_op(pc, _ui_dbg_dasm_in_cb, _ui_dbg_dasm_out_cb, win); + z80dasm_op(addr, _ui_dbg_dasm_in_cb, _ui_dbg_dasm_out_cb, win); #elif defined(UI_DBG_USE_M6502) - m6502dasm_op(pc, _ui_dbg_dasm_in_cb, _ui_dbg_dasm_out_cb, win); + m6502dasm_op(addr, _ui_dbg_dasm_in_cb, _ui_dbg_dasm_out_cb, win); #endif - return win->dasm.cur_addr; + uint16_t next_addr = win->dasm_line.addr; + win->dasm_line.addr = addr; + return next_addr; } /* disassemble the an instruction, but only return the length of the instruction */ -static inline uint16_t _ui_dbg_disasm_len(ui_dbg_t* win, uint16_t pc) { - win->dasm.cur_addr = pc; - win->dasm.str_pos = 0; - win->dasm.bin_pos = 0; - #if defined(UI_DBG_USE_Z80) - uint16_t next_pc = z80dasm_op(pc, _ui_dbg_dasm_in_cb, 0, win); - #elif defined(UI_DBG_USE_M6502) - uint16_t next_pc = m6502dasm_op(pc, _ui_dbg_dasm_in_cb, 0, win); - #endif - return next_pc - pc; -} - /* check if the an instruction is a 'step over' op */ static bool _ui_dbg_is_stepover_op(uint8_t opcode) { #if defined(UI_DBG_USE_Z80) @@ -419,7 +482,7 @@ static bool _ui_dbg_is_controlflow_op(uint8_t opcode0, uint8_t opcode1) { /* HALT */ case 0x76: /* RET */ - case 0xC9: + case 0xC9: /* RET cc */ case 0xC0: case 0xC8: case 0xD0: case 0xD8: case 0xE0: case 0xE8: case 0xF0: case 0xF8: @@ -452,7 +515,7 @@ static bool _ui_dbg_is_controlflow_op(uint8_t opcode0, uint8_t opcode1) { /* JMP ind */ case 0x6C: /* relative branch */ - case 0x10: case 0x30: case 0x50: case 0x70: + case 0x10: case 0x30: case 0x50: case 0x70: case 0x90: case 0xB0: case 0xD0: case 0xF0: return true; /* RTI */ @@ -470,11 +533,17 @@ static void _ui_dbg_break(ui_dbg_t* win) { win->dbg.stopped = true; win->dbg.step_mode = UI_DBG_STEPMODE_NONE; win->ui.request_scroll = true; + if (win->debug_cbs.stopped_cb) { + win->debug_cbs.stopped_cb(UI_DBG_STOP_REASON_BREAK, win->dbg.cur_op_pc); + } } -static void _ui_dbg_continue(ui_dbg_t* win) { +static void _ui_dbg_continue(ui_dbg_t* win, bool invoke_continue_cb) { win->dbg.stopped = false; win->dbg.step_mode = UI_DBG_STEPMODE_NONE; + if (invoke_continue_cb && win->debug_cbs.continued_cb) { + win->debug_cbs.continued_cb(); + } } static void _ui_dbg_step_into(ui_dbg_t* win) { @@ -487,11 +556,10 @@ static void _ui_dbg_step_over(ui_dbg_t* win) { win->dbg.stopped = false; win->ui.request_scroll = true; uint16_t next_pc = _ui_dbg_disasm(win, _ui_dbg_get_pc(win)); - if (_ui_dbg_is_stepover_op(win->dasm.bin_buf[0])) { + if (_ui_dbg_is_stepover_op(win->dasm_line.bytes[0])) { win->dbg.step_mode = UI_DBG_STEPMODE_OVER; win->dbg.stepover_pc = next_pc; - } - else { + } else { win->dbg.step_mode = UI_DBG_STEPMODE_INTO; } } @@ -555,8 +623,7 @@ static void _ui_dbg_history_draw(ui_dbg_t* win) { /* address */ if (0 == line_i) { ImGui::Text("cur> %04X: ", pc); - } - else { + } else { ImGui::Text("%4d %04X: ", -line_i, pc); } ImGui::SameLine(); @@ -566,7 +633,7 @@ static void _ui_dbg_history_draw(ui_dbg_t* win) { if (win->ui.show_bytes) { for (int n = 0; n < num_bytes; n++) { ImGui::SameLine(x + cell_width * n); - uint8_t val = win->dasm.bin_buf[n]; + uint8_t val = win->dasm_line.bytes[n]; ImGui::Text("%02X ", val); } x += cell_width * 4; @@ -575,7 +642,7 @@ static void _ui_dbg_history_draw(ui_dbg_t* win) { /* disassembled instruction */ x += glyph_width * 4; ImGui::SameLine(x); - ImGui::Text("%s", win->dasm.str_buf); + ImGui::Text("%s", win->dasm_line.bytes); /* tick count */ x += glyph_width * 17; @@ -637,8 +704,7 @@ static int _ui_dbg_eval_op_breakpoints(ui_dbg_t* win, int trap_id, uint16_t pc) #endif break; } - } - else { + } else { for (int i = 0; (i < win->dbg.num_breakpoints) && (trap_id == 0); i++) { const ui_dbg_breakpoint_t* bp = &win->dbg.breakpoints[i]; if (bp->enabled) { @@ -666,7 +732,7 @@ static int _ui_dbg_eval_op_breakpoints(ui_dbg_t* win, int trap_id, uint16_t pc) } } break; - + case UI_DBG_BREAKTYPE_WORD: { uint16_t val = (int) _ui_dbg_read_word(win, bp->addr); @@ -744,7 +810,7 @@ static int _ui_dbg_eval_tick_breakpoints(ui_dbg_t* win, int trap_id, uint64_t pi } } } - + // call optional user-breakpoint evaluation callback if ((0 == trap_id) && win->break_cb) { trap_id = win->break_cb(win, trap_id, pins, win->user_data); @@ -762,8 +828,7 @@ static bool _ui_dbg_bp_add_exec(ui_dbg_t* win, bool enabled, uint16_t addr) { bp->val = 0; bp->enabled = enabled; return true; - } - else { + } else { /* no more breakpoint slots */ return false; } @@ -779,8 +844,7 @@ static bool _ui_dbg_bp_add_byte(ui_dbg_t* win, bool enabled, uint16_t addr) { bp->val = _ui_dbg_read_byte(win, addr); bp->enabled = enabled; return true; - } - else { + } else { /* no more breakpoint slots */ return false; } @@ -796,8 +860,7 @@ static bool _ui_dbg_bp_add_word(ui_dbg_t* win, bool enabled, uint16_t addr) { bp->val = _ui_dbg_read_word(win, addr); bp->enabled = enabled; return true; - } - else { + } else { /* no more breakpoint slots */ return false; } @@ -830,8 +893,7 @@ static void _ui_dbg_bp_toggle_exec(ui_dbg_t* win, uint16_t addr) { if (index >= 0) { /* breakpoint already exists, remove */ _ui_dbg_bp_del(win, index); - } - else { + } else { /* breakpoint doesn't exist, add a new one */ _ui_dbg_bp_add_exec(win, true, addr); } @@ -919,7 +981,7 @@ static void _ui_dbg_bp_draw(ui_dbg_t* win) { bool bp_active = (win->dbg.last_trap_id >= UI_DBG_BP_BASE_TRAPID) && ((win->dbg.last_trap_id - UI_DBG_BP_BASE_TRAPID) == i); if (bp_active) { - ImGui::PushStyleColor(ImGuiCol_CheckMark, 0xFF0000FF); + ImGui::PushStyleColor(ImGuiCol_CheckMark, 0xFF0000FF); } ImGui::Checkbox("##enabled", &bp->enabled); ImGui::SameLine(); if (bp_active) { @@ -928,8 +990,7 @@ static void _ui_dbg_bp_draw(ui_dbg_t* win) { if (ImGui::IsItemHovered()) { if (bp->enabled) { ImGui::SetTooltip("Disable Breakpoint"); - } - else { + } else { ImGui::SetTooltip("Enable Breakpoint"); } } @@ -981,8 +1042,7 @@ static void _ui_dbg_bp_draw(ui_dbg_t* win) { if (bt->show_val8) { ImGui::SameLine(); bp->val = (int) ui_util_input_u8("##byte", (uint8_t)bp->val); - } - else { + } else { ImGui::SameLine(); bp->val = (int) ui_util_input_u16("##word", (uint16_t)bp->val); } @@ -1029,14 +1089,14 @@ static void _ui_dbg_heatmap_init(ui_dbg_t* win) { win->heatmap.tex_height = 256; win->heatmap.tex_width_uicombo_state = 4; win->heatmap.next_tex_width = win->heatmap.tex_width; - win->heatmap.texture = win->create_texture_cb(win->heatmap.tex_width, win->heatmap.tex_height); + win->heatmap.texture = win->texture_cbs.create_cb(win->heatmap.tex_width, win->heatmap.tex_height); win->heatmap.show_ops = win->heatmap.show_reads = win->heatmap.show_writes = true; win->heatmap.scale = 1; win->heatmap.autoclear_interval = 0; /* 0 means: no autoclear */ } static void _ui_dbg_heatmap_discard(ui_dbg_t* win) { - win->destroy_texture_cb(win->heatmap.texture); + win->texture_cbs.destroy_cb(win->heatmap.texture); } static void _ui_dbg_heatmap_update_texture_size(ui_dbg_t* win, int new_width) { @@ -1044,8 +1104,8 @@ static void _ui_dbg_heatmap_update_texture_size(ui_dbg_t* win, int new_width) { if (new_width != win->heatmap.tex_width) { win->heatmap.tex_width = new_width; win->heatmap.tex_height = (1<<16) / new_width; - win->destroy_texture_cb(win->heatmap.texture); - win->heatmap.texture = win->create_texture_cb(win->heatmap.tex_width, win->heatmap.tex_height); + win->texture_cbs.destroy_cb(win->heatmap.texture); + win->heatmap.texture = win->texture_cbs.create_cb(win->heatmap.tex_width, win->heatmap.tex_height); } } @@ -1065,19 +1125,13 @@ static void _ui_dbg_heatmap_clear_all(ui_dbg_t* win) { static void _ui_dbg_heatmap_clear_rw(ui_dbg_t* win) { for (int i = 0; i < (1<<16); i++) { - win->heatmap.items[i].read_count = 0; - win->heatmap.items[i].write_count = 0; + win->heatmap.items[i].state &= ~(UI_DBG_HEATMAP_ITEM_READ|UI_DBG_HEATMAP_ITEM_WRITE); } } static void _ui_dbg_heatmap_record_op(ui_dbg_t* win, uint16_t pc) { // record per-op heatmap events - win->heatmap.items[pc].op_count++; - win->heatmap.items[pc].op_start = 0; - int op_len = _ui_dbg_disasm_len(win, pc); - for (int i = 1; i < op_len; i++) { - win->heatmap.items[(pc + i) & 0xFFFF].op_start = pc; - } + win->heatmap.items[pc].state |= UI_DBG_HEATMAP_ITEM_OPCODE; // update last instruction's ticks win->heatmap.items[win->dbg.cur_op_pc].ticks = win->dbg.cur_op_ticks; } @@ -1086,60 +1140,58 @@ static void _ui_dbg_heatmap_record_tick(ui_dbg_t* win, uint64_t pins) { #if defined(UI_DBG_USE_Z80) if ((pins & Z80_CTRL_PIN_MASK) == (Z80_MREQ|Z80_RD)) { const uint16_t addr = Z80_GET_ADDR(pins); - win->heatmap.items[addr].read_count++; - } - else if ((pins & Z80_CTRL_PIN_MASK) == (Z80_MREQ|Z80_WR)) { + win->heatmap.items[addr].state |= UI_DBG_HEATMAP_ITEM_READ; + } else if ((pins & Z80_CTRL_PIN_MASK) == (Z80_MREQ|Z80_WR)) { const uint16_t addr = Z80_GET_ADDR(pins); - win->heatmap.items[addr].write_count++; + win->heatmap.items[addr].state |= UI_DBG_HEATMAP_ITEM_WRITE; } #elif defined(UI_DBG_USE_M6502) const uint16_t addr = M6502_GET_ADDR(pins); if (0 != (pins & M6502_RW)) { - win->heatmap.items[addr].read_count++; - } - else { - win->heatmap.items[addr].write_count++; + win->heatmap.items[addr].state |= UI_DBG_HEATMAP_ITEM_READ; + } else { + win->heatmap.items[addr].state |= UI_DBG_HEATMAP_ITEM_WRITE; } #endif } +static inline bool _ui_dbg_heatmap_is_opcode(ui_dbg_t* win, uint16_t addr) { + return 0 != (win->heatmap.items[addr].state & UI_DBG_HEATMAP_ITEM_OPCODE); +} + +static inline bool _ui_dbg_heatmap_is_read(ui_dbg_t* win, uint16_t addr) { + return 0 != (win->heatmap.items[addr].state & UI_DBG_HEATMAP_ITEM_READ); +} + +static inline bool _ui_dbg_heatmap_is_write(ui_dbg_t* win, uint16_t addr) { + return 0 != (win->heatmap.items[addr].state & UI_DBG_HEATMAP_ITEM_WRITE); +} + static void _ui_dbg_heatmap_update(ui_dbg_t* win) { const int frame_chunk_height = 64; - int y0 = win->heatmap.cur_y; - int y1 = win->heatmap.cur_y + frame_chunk_height; + const int y0 = win->heatmap.cur_y; + const int y1 = win->heatmap.cur_y + frame_chunk_height; win->heatmap.cur_y = (y0 + frame_chunk_height) & 255; for (int y = y0; y < y1; y++) { for (int x = 0; x < 256; x++) { - int i = y * 256 + x; + const int i = y * 256 + x; uint32_t p = 0; if (_ui_dbg_get_pc(win) == i) { p |= 0xFF00FFFF; } - if (win->heatmap.show_ops && (win->heatmap.items[i].op_count > 0)) { - uint32_t r = 0x80 + (win->heatmap.items[i].op_count>>8); - if (r > 0xFF) { r = 0xFF; } - p |= 0xFF000000 | r; + if (win->heatmap.show_ops && _ui_dbg_heatmap_is_opcode(win, (uint16_t)i)) { + p |= 0xFF0000FF; } - if (win->heatmap.show_ops && (win->heatmap.items[i].op_start != 0)) { - /* opcode followup byte */ - uint32_t r = 0x80 + (win->heatmap.items[win->heatmap.items[i].op_start].op_count>>8); - if (r > 0xFF) { r = 0xFF; } - p |= 0xFF000000 | r; + if (win->heatmap.show_writes && _ui_dbg_heatmap_is_write(win, (uint16_t)i)) { + p |= 0xFF008800; } - if (win->heatmap.show_writes && (win->heatmap.items[i].write_count > 0)) { - uint32_t g = 0x80 + (win->heatmap.items[i].write_count>>8); - if (g > 0xFF) { g = 0xFF; } - p |= 0xFF000000 | (g<<8); - } - if (win->heatmap.show_reads && (win->heatmap.items[i].read_count > 0)) { - uint32_t b = 0x80 + (win->heatmap.items[i].read_count>>8); - if (b > 0xFF) { b = 0xFF; } - p |= 0xFF000000 | (b<<16); + if (win->heatmap.show_reads && _ui_dbg_heatmap_is_read(win, (uint16_t)i)) { + p |= 0xFF880000; } win->heatmap.pixels[i] = p; } } - win->update_texture_cb(win->heatmap.texture, win->heatmap.pixels, 256*256*4); + win->texture_cbs.update_cb(win->heatmap.texture, win->heatmap.pixels, 256*256*4); } static void _ui_dbg_heatmap_draw(ui_dbg_t* win) { @@ -1175,9 +1227,9 @@ static void _ui_dbg_heatmap_draw(ui_dbg_t* win) { ImGui::PushStyleColor(ImGuiCol_Text, 0xFF00FF00); ImGui::Checkbox("W", &hm->show_writes); ImGui::PopStyleColor(3); - if (ImGui::Combo("Size", &hm->tex_width_uicombo_state, + if (ImGui::Combo("Size", &hm->tex_width_uicombo_state, "16 x 4096 bytes\0" - "32 x 2048 bytes\0" + "32 x 2048 bytes\0" "64 x 1024 bytes\0" "128 x 512 bytes\0" "256 x 256 bytes\0" @@ -1199,17 +1251,12 @@ static void _ui_dbg_heatmap_draw(ui_dbg_t* win) { int x = (int)((mouse_pos.x - screen_pos.x) / hm->scale); int y = (int)((mouse_pos.y - screen_pos.y) / hm->scale); uint16_t addr = y * hm->tex_width + x; - if (hm->items[addr].op_start != 0) { - /* address is actually an opcode followup byte, reset to start of instruction */ - addr = hm->items[addr].op_start; - } if (ImGui::IsItemHovered()) { - if (hm->items[addr].op_count > 0) { + if (_ui_dbg_heatmap_is_opcode(win, addr)) { _ui_dbg_disasm(win, addr); ImGui::SetTooltip("%04X: %s (ticks: %d)\n(right-click for options)", - addr, win->dasm.str_buf, hm->items[addr].ticks); - } - else { + addr, win->dasm_line.chars, hm->items[addr].ticks); + } else { ImGui::SetTooltip("%04X: %02X %02X %02X %02X\n(right-click for options)", addr, _ui_dbg_read_byte(win, addr), _ui_dbg_read_byte(win, addr+1), @@ -1244,8 +1291,7 @@ static void _ui_dbg_heatmap_draw(ui_dbg_t* win) { } } ImGui::EndPopup(); - } - else { + } else { hm->popup_addr_valid = false; } ImGui::EndChild(); @@ -1253,6 +1299,57 @@ static void _ui_dbg_heatmap_draw(ui_dbg_t* win) { ImGui::End(); } +/*== STOPWATCH WINDOW ========================================================*/ +static void _ui_dbg_stopwatch_init(ui_dbg_t* win, ui_dbg_desc_t* desc) { + memset(&win->stopwatch, 0, sizeof(win->stopwatch)); + win->stopwatch.freq_hz = desc->freq_hz; + win->stopwatch.scanline_ticks = desc->scanline_ticks; + win->stopwatch.frame_ticks = desc->frame_ticks; +} + +static void _ui_dbg_stopwatch_reset(ui_dbg_t* win) { + win->stopwatch.cur_ticks = 0; + for (int i = 0; i < UI_DBG_STOPWATCH_NUM; i++) { + win->stopwatch.start_ticks[i] = 0; + } +} + +static void _ui_dbg_stopwatch_draw(ui_dbg_t* win) { + if (!win->ui.show_stopwatch) { + return; + } + ImGui::SetNextWindowPos(ImVec2(win->ui.init_x + win->ui.init_w, win->ui.init_y), ImGuiCond_Once); + ImGui::SetNextWindowSize(ImVec2(-1, -1), ImGuiCond_Once); + if (ImGui::Begin("Stopwatch", &win->ui.show_stopwatch)) { + for (int i = 0; i < UI_DBG_STOPWATCH_NUM; i++) { + ImGui::PushID(i); + if (ImGui::Button("Reset")) { + win->stopwatch.start_ticks[i] = win->stopwatch.cur_ticks; + } + ImGui::SameLine(); + uint64_t cycle_count = win->stopwatch.cur_ticks - win->stopwatch.start_ticks[i]; + double ms = -1.0; + double raster_lines = -1.0; + double frames = -1.0; + if (win->stopwatch.freq_hz > 0) { + ms = ((double)cycle_count / (double)win->stopwatch.freq_hz) * 1000.0; + } + if (win->stopwatch.scanline_ticks > 0) { + raster_lines = (double)cycle_count / (double)win->stopwatch.scanline_ticks; + } + if (win->stopwatch.frame_ticks > 0) { + frames = (double)cycle_count / (double)win->stopwatch.frame_ticks; + } + ImGui::Text("%llu ticks", cycle_count); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("milliseconds: %.3f\nraster lines: %.3f\nframes: %.3f\n", ms, raster_lines, frames); + } + ImGui::PopID(); + } + } + ImGui::End(); +} + /*== UI HELPERS ==============================================================*/ static void _ui_dbg_uistate_init(ui_dbg_t* win, ui_dbg_desc_t* desc) { ui_dbg_uistate_t* ui = &win->ui; @@ -1311,8 +1408,7 @@ static void _ui_dbg_uistate_init(ui_dbg_t* win, ui_dbg_desc_t* desc) { if (desc->user_breaktypes[j].label) { ui->breaktypes[i] = desc->user_breaktypes[j]; ui->breaktype_combo_labels[i] = ui->breaktypes[i].label; - } - else { + } else { break; } } @@ -1335,7 +1431,7 @@ static void _ui_dbg_draw_menu(ui_dbg_t* win) { _ui_dbg_break(win); } if (ImGui::MenuItem("Continue", win->ui.keys.cont.name, false, win->dbg.stopped)) { - _ui_dbg_continue(win); + _ui_dbg_continue(win, true); } if (ImGui::MenuItem("Step Over", win->ui.keys.step_over.name, false, win->dbg.stopped)) { _ui_dbg_step_over(win); @@ -1372,9 +1468,10 @@ static void _ui_dbg_draw_menu(ui_dbg_t* win) { if (ImGui::BeginMenu("Show")) { ImGui::MenuItem("Memory Heatmap", 0, &win->ui.show_heatmap); ImGui::MenuItem("Execution History", 0, &win->ui.show_history); + ImGui::MenuItem("Breakpoints", 0, &win->ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &win->ui.show_stopwatch); ImGui::MenuItem("Registers", 0, &win->ui.show_regs); ImGui::MenuItem("Button Bar", 0, &win->ui.show_buttons); - ImGui::MenuItem("Breakpoints", 0, &win->ui.show_breakpoints); ImGui::MenuItem("Opcode Bytes", 0, &win->ui.show_bytes); ImGui::MenuItem("Opcode Ticks", 0, &win->ui.show_ticks); ImGui::EndMenu(); @@ -1426,9 +1523,9 @@ void _ui_dbg_draw_regs(ui_dbg_t* win) { char f_str[9] = { (c->f & Z80_SF) ? 'S':'-', (c->f & Z80_ZF) ? 'Z':'-', - (c->f & Z80_YF) ? 'X':'-', + (c->f & Z80_YF) ? 'Y':'-', (c->f & Z80_HF) ? 'H':'-', - (c->f & Z80_XF) ? 'Y':'-', + (c->f & Z80_XF) ? 'X':'-', (c->f & Z80_VF) ? 'V':'-', (c->f & Z80_NF) ? 'N':'-', (c->f & Z80_CF) ? 'C':'-', @@ -1477,32 +1574,31 @@ static void _ui_dbg_handle_input(ui_dbg_t* win) { /* unused hotkeys are defined as 0 and will never be triggered */ if (win->dbg.stopped) { if (0 != win->ui.keys.cont.keycode) { - if (ImGui::IsKeyPressed(win->ui.keys.cont.keycode)) { - _ui_dbg_continue(win); + if (ImGui::IsKeyPressed((ImGuiKey)win->ui.keys.cont.keycode)) { + _ui_dbg_continue(win, true); } } if (0 != win->ui.keys.step_over.keycode) { - if (ImGui::IsKeyPressed(win->ui.keys.step_over.keycode)) { + if (ImGui::IsKeyPressed((ImGuiKey)win->ui.keys.step_over.keycode)) { _ui_dbg_step_over(win); } } if (0 != win->ui.keys.step_into.keycode) { - if (ImGui::IsKeyPressed(win->ui.keys.step_into.keycode)) { + if (ImGui::IsKeyPressed((ImGuiKey)win->ui.keys.step_into.keycode)) { _ui_dbg_step_into(win); } } if (0 != win->ui.keys.step_tick.keycode) { - if (ImGui::IsKeyPressed(win->ui.keys.step_tick.keycode)) { + if (ImGui::IsKeyPressed((ImGuiKey)win->ui.keys.step_tick.keycode)) { _ui_dbg_step_tick(win); } } - } - else { - if (ImGui::IsKeyPressed(win->ui.keys.stop.keycode)) { + } else { + if (ImGui::IsKeyPressed((ImGuiKey)win->ui.keys.stop.keycode)) { _ui_dbg_break(win); } } - if (ImGui::IsKeyPressed(win->ui.keys.toggle_breakpoint.keycode)) { + if (ImGui::IsKeyPressed((ImGuiKey)win->ui.keys.toggle_breakpoint.keycode)) { _ui_dbg_bp_toggle_exec(win, _ui_dbg_get_pc(win)); } } @@ -1515,7 +1611,7 @@ static void _ui_dbg_draw_buttons(ui_dbg_t* win) { if (win->dbg.stopped || (win->dbg.step_mode != UI_DBG_STEPMODE_NONE)) { snprintf(str, sizeof(str), "Continue (%s)", _ui_dbg_str_or_def(win->ui.keys.cont.name, "-")); if (ImGui::Button(str)) { - _ui_dbg_continue(win); + _ui_dbg_continue(win, true); } ImGui::SameLine(); snprintf(str, sizeof(str), "Over (%s)", _ui_dbg_str_or_def(win->ui.keys.step_over.name, "-")); @@ -1532,8 +1628,7 @@ static void _ui_dbg_draw_buttons(ui_dbg_t* win) { if (ImGui::Button(str)) { _ui_dbg_step_tick(win); } - } - else { + } else { snprintf(str, sizeof(str), "Break (%s)", _ui_dbg_str_or_def(win->ui.keys.stop.name, "-")); if (ImGui::Button(str)) { _ui_dbg_break(win); @@ -1542,6 +1637,49 @@ static void _ui_dbg_draw_buttons(ui_dbg_t* win) { ImGui::Separator(); } +/* helper function for backward scanning disassembly, tries to find + a known op in the previous 4 bytes, returns true if a known op + was found + +*/ +typedef struct { + bool is_known_op; + uint16_t addr; +} _ui_dbg_disasm_backscan_result_t; + +static _ui_dbg_disasm_backscan_result_t _ui_dbg_disasm_backscan(ui_dbg_t* win, uint16_t addr) { + bool is_known_op = false; + uint16_t bs_addr = addr - 1; + uint16_t scan_addr = bs_addr; + for (int i = 0; i < 4; i++, scan_addr--) { + if (_ui_dbg_heatmap_is_opcode(win, scan_addr)) { + // Z80: prefixed instruction? + #if defined(UI_DBG_USE_Z80) + uint16_t prev_addr = scan_addr - 1; + if (_ui_dbg_heatmap_is_opcode(win, prev_addr)) { + uint8_t maybe_prefix = _ui_dbg_read_byte(win, prev_addr); + if ((maybe_prefix == 0xCB) || (maybe_prefix == 0xDD) || (maybe_prefix == 0xED) || (maybe_prefix == 0xFD)) { + scan_addr = prev_addr; + // NOTE: we don't need a separate code path to check for double prefix FD/DD CB, since + // in such a case the CB opcode byte will be the regular opcode byte followed by + // a no-op 8-bit immediate value + } + } + #endif + // found an op start, if any unknown bytes had been skipped, ignore the op + // (it will be found again in the next iteration) + _ui_dbg_disasm(win, scan_addr); + if ((int)(win->dasm_line.num_bytes - 1) == i) { + // ok, no gap bytes, break with 'found_op' status + bs_addr = scan_addr; + is_known_op = true; + } + break; + } + } + return { is_known_op, bs_addr }; +} + /* this updates the line array currently visualized by the disassembler listing, this only happens when the PC is outside the visible area or when the memory content 'under' the line array changes @@ -1550,25 +1688,23 @@ static void _ui_dbg_update_line_array(ui_dbg_t* win, uint16_t addr) { /* one half is backtraced from current PC, the other half is 'forward tracked' from current PC */ - uint16_t bt_addr = addr; - int i; - for (i = 0; i < UI_DBG_NUM_BACKTRACE_LINES; i++) { - bt_addr -= 1; - if (win->heatmap.items[bt_addr].op_start) { - bt_addr = win->heatmap.items[bt_addr].op_start; - } - int bt_index = UI_DBG_NUM_BACKTRACE_LINES - i - 1; - win->ui.line_array[bt_index].addr = bt_addr; - win->ui.line_array[bt_index].val = _ui_dbg_read_byte(win, bt_addr); - } - for (; i < UI_DBG_NUM_LINES; i++) { - win->ui.line_array[i].addr = addr; + uint16_t bs_addr = addr; + int line_idx; + for (line_idx = 0; line_idx < UI_DBG_NUM_BACKTRACE_LINES; line_idx++) { + // scan backwards for op start in blocks of 4 bytes (== max length of an instruction) + bs_addr = _ui_dbg_disasm_backscan(win, bs_addr).addr; + const int bs_index = UI_DBG_NUM_BACKTRACE_LINES - line_idx - 1; + win->ui.line_array[bs_index].addr = bs_addr; + win->ui.line_array[bs_index].val = _ui_dbg_read_byte(win, bs_addr); + } + for (; line_idx < UI_DBG_NUM_LINES; line_idx++) { + win->ui.line_array[line_idx].addr = addr; addr = _ui_dbg_disasm(win, addr); - win->ui.line_array[i].val = win->dasm.bin_buf[0]; + win->ui.line_array[line_idx].val = win->dasm_line.bytes[0]; } } -/* check if the address is outside the line_array +/* check if the address is outside the line_array NOTE that all backtraced lines are also considered "outside the line array", this is for the case where the PC jumps back into the backtraced array, @@ -1581,11 +1717,9 @@ static bool _ui_dbg_addr_inside_line_array(ui_dbg_t* win, uint16_t addr) { uint16_t last_addr = win->ui.line_array[UI_DBG_NUM_LINES-16].addr; if (first_addr == last_addr) { return false; - } - else if (first_addr < last_addr) { + } else if (first_addr < last_addr) { return (first_addr <= addr) && (addr <= last_addr); - } - else { + } else { /* address wraps around in line_addrs array */ return (first_addr <= addr) || (addr <= last_addr); } @@ -1640,9 +1774,9 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { bool in_safe_area = (line_i >= (clipper.DisplayStart+safe_lines)) && (line_i <= (clipper.DisplayEnd-safe_lines)); bool is_pc_line = (addr == pc); if (is_pc_line && - (force_scroll || - (!in_safe_area && win->ui.request_scroll) || - (!in_safe_area && !win->dbg.stopped))) + (force_scroll || + (!in_safe_area && win->ui.request_scroll) || + (!in_safe_area && !win->dbg.stopped))) { win->ui.request_scroll = false; int scroll_to_line = line_i - safe_lines - 2; @@ -1661,12 +1795,11 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { bool visible_line = (line_i >= clipper.DisplayStart) && (line_i < clipper.DisplayEnd); uint16_t addr = win->ui.line_array[line_i].addr; bool is_pc_line = (addr == pc); - bool show_dasm = (line_i >= UI_DBG_NUM_BACKTRACE_LINES) || (win->heatmap.items[addr].op_count > 0); + bool show_dasm = (line_i >= UI_DBG_NUM_BACKTRACE_LINES) || _ui_dbg_heatmap_is_opcode(win, addr); const uint16_t start_addr = addr; if (show_dasm) { addr = _ui_dbg_disasm(win, addr); - } - else { + } else { addr++; } const int num_bytes = addr - start_addr; @@ -1677,10 +1810,9 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { } /* show data bytes or potential but not verified instructions as dimmed */ - if ((win->heatmap.items[start_addr].op_count > 0) || (start_addr == pc)) { + if (_ui_dbg_heatmap_is_opcode(win, start_addr) || (start_addr == pc)) { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_Text]); - } - else { + } else { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); } @@ -1702,8 +1834,7 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { ImU32 bp_color = _ui_dbg_bp_enabled(win, bp_index) ? bp_enabled_color : bp_disabled_color; dl->AddCircleFilled(mid, 7, bp_color); dl->AddCircle(mid, 7, brd_color); - } - else if (ImGui::IsItemHovered()) { + } else if (ImGui::IsItemHovered()) { dl->AddCircle(mid, 7, bp_enabled_color); } /* current PC/step cursor */ @@ -1717,11 +1848,9 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { /* draw a separation line (for PC, breakpoint or control-flow ops) */ if (is_pc_line) { dl->AddLine(ImVec2(pos.x+16,base_y), ImVec2(pos.x+2048,base_y), pc_color); - } - else if (bp_index >= 0) { + } else if (bp_index >= 0) { dl->AddLine(ImVec2(pos.x+16,base_y), ImVec2(pos.x+2048,base_y), bp_disabled_color); - } - else if (show_dasm && _ui_dbg_is_controlflow_op(win->dasm.bin_buf[0], win->dasm.bin_buf[1])) { + } else if (show_dasm && _ui_dbg_is_controlflow_op(win->dasm_line.bytes[0], win->dasm_line.bytes[1])) { dl->AddLine(ImVec2(pos.x+16,base_y), ImVec2(pos.x+2048,base_y), ctrlflow_color); } @@ -1737,9 +1866,8 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { ImGui::SameLine(x + cell_width*n); uint8_t val; if (show_dasm) { - val = win->dasm.bin_buf[n]; - } - else { + val = win->dasm_line.bytes[n]; + } else { val = _ui_dbg_read_byte(win, start_addr+n); } ImGui::Text("%02X ", val); @@ -1751,9 +1879,8 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { x += glyph_width * 4; ImGui::SameLine(x); if (show_dasm) { - ImGui::Text("%s", win->dasm.str_buf); - } - else { + ImGui::Text("%s", win->dasm_line.chars); + } else { ImGui::Text(" "); } @@ -1765,20 +1892,16 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { if (ticks > 0) { if (is_pc_line) { ImGui::Text("%2d/%d", win->dbg.cur_op_ticks, ticks); - } - else { + } else { ImGui::Text(" %d", ticks); } - } - else if (show_dasm) { + } else if (show_dasm) { if (is_pc_line) { ImGui::Text("%2d/?", win->dbg.cur_op_ticks); - } - else { + } else { ImGui::Text("?"); } - } - else { + } else { ImGui::Text(" "); } } @@ -1814,19 +1937,19 @@ void ui_dbg_init(ui_dbg_t* win, ui_dbg_desc_t* desc) { CHIPS_ASSERT(win && desc); CHIPS_ASSERT(desc->title); CHIPS_ASSERT(desc->read_cb); - CHIPS_ASSERT(desc->create_texture_cb && desc->update_texture_cb && desc->destroy_texture_cb); + CHIPS_ASSERT(desc->texture_cbs.create_cb && desc->texture_cbs.update_cb && desc->texture_cbs.destroy_cb); memset(win, 0, sizeof(ui_dbg_t)); win->valid = true; win->read_cb = desc->read_cb; win->read_layer = desc->read_layer; win->break_cb = desc->break_cb; - win->create_texture_cb = desc->create_texture_cb; - win->update_texture_cb = desc->update_texture_cb; - win->destroy_texture_cb = desc->destroy_texture_cb; + win->texture_cbs = desc->texture_cbs; + win->debug_cbs = desc->debug_cbs; win->user_data = desc->user_data; _ui_dbg_dbgstate_init(win, desc); _ui_dbg_uistate_init(win, desc); _ui_dbg_heatmap_init(win); + _ui_dbg_stopwatch_init(win, desc); } void ui_dbg_discard(ui_dbg_t* win) { @@ -1841,6 +1964,10 @@ void ui_dbg_reset(ui_dbg_t* win) { _ui_dbg_uistate_reset(win); _ui_dbg_heatmap_reset(win); _ui_dbg_history_reset(win); + _ui_dbg_stopwatch_reset(win); + if (win->debug_cbs.reset_cb) { + win->debug_cbs.reset_cb(); + } } void ui_dbg_reboot(ui_dbg_t* win) { @@ -1849,6 +1976,9 @@ void ui_dbg_reboot(ui_dbg_t* win) { _ui_dbg_uistate_reboot(win); _ui_dbg_heatmap_reboot(win); _ui_dbg_history_reboot(win); + if (win->debug_cbs.reboot_cb) { + win->debug_cbs.reboot_cb(); + } } void ui_dbg_tick(ui_dbg_t* win, uint64_t pins) { @@ -1874,14 +2004,21 @@ void ui_dbg_tick(ui_dbg_t* win, uint64_t pins) { trap_id = _ui_dbg_eval_tick_breakpoints(win, trap_id, pins); } _ui_dbg_heatmap_record_tick(win, pins); + win->stopwatch.cur_ticks++; win->dbg.cur_op_ticks++; win->dbg.last_tick_pins = pins; if (trap_id >= UI_DBG_STEP_TRAPID) { win->dbg.stopped = true; + if (win->debug_cbs.stopped_cb) { + int stop_reason = (win->dbg.step_mode == UI_DBG_STEPMODE_NONE) ? UI_DBG_STOP_REASON_BREAKPOINT : UI_DBG_STOP_REASON_STEP; + win->debug_cbs.stopped_cb(stop_reason, win->dbg.cur_op_pc); + } win->dbg.step_mode = UI_DBG_STEPMODE_NONE; - ImGui::SetWindowFocus(win->ui.title); - win->ui.open = true; + if (!win->dbg.external_debugger_connected) { + ImGui::SetWindowFocus(win->ui.title); + win->ui.open = true; + } } win->dbg.last_trap_id = trap_id; } @@ -1889,12 +2026,118 @@ void ui_dbg_tick(ui_dbg_t* win, uint64_t pins) { void ui_dbg_draw(ui_dbg_t* win) { CHIPS_ASSERT(win && win->valid && win->ui.title); win->dbg.frame_id++; - if (!(win->ui.open || win->ui.show_heatmap || win->ui.show_breakpoints || win->ui.show_history)) { + if (!(win->ui.open || win->ui.show_heatmap || win->ui.show_breakpoints || win->ui.show_history || win->ui.show_stopwatch)) { return; } _ui_dbg_dbgwin_draw(win); _ui_dbg_heatmap_draw(win); _ui_dbg_history_draw(win); _ui_dbg_bp_draw(win); + _ui_dbg_stopwatch_draw(win); +} + +void ui_dbg_external_debugger_connected(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + win->dbg.external_debugger_connected = true; +} + +void ui_dbg_external_debugger_disconnected(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + win->dbg.external_debugger_connected = false; + // delete all breakpoints and continue execution (in case of stopped) + _ui_dbg_bp_delete_all(win); + _ui_dbg_continue(win, false); +} + +void ui_dbg_add_breakpoint(ui_dbg_t* win, uint16_t addr) { + CHIPS_ASSERT(win && win->valid && win->ui.title); + int index = _ui_dbg_bp_find(win, UI_DBG_BREAKTYPE_EXEC, addr); + if (index < 0) { + _ui_dbg_bp_add_exec(win, true, addr); + } +} + +void ui_dbg_remove_breakpoint(ui_dbg_t* win, uint16_t addr) { + CHIPS_ASSERT(win && win->valid && win->ui.title); + int index = _ui_dbg_bp_find(win, UI_DBG_BREAKTYPE_EXEC, addr); + if (index >= 0) { + _ui_dbg_bp_del(win, index); + } +} + +void ui_dbg_break(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + _ui_dbg_break(win); +} + +void ui_dbg_continue(ui_dbg_t* win, bool invoke_continued_cb) { + CHIPS_ASSERT(win && win->valid); + _ui_dbg_continue(win, invoke_continued_cb); +} + +bool ui_dbg_stopped(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + return win->dbg.stopped; +} + +void ui_dbg_step_next(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + _ui_dbg_step_over(win); } + +void ui_dbg_step_into(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + _ui_dbg_step_into(win); +} + +void ui_dbg_disassemble(ui_dbg_t* win, const ui_dbg_dasm_request_t* request) { + CHIPS_ASSERT(win && win->valid); + CHIPS_ASSERT(request); + CHIPS_ASSERT(request->out_lines); + CHIPS_ASSERT(request->num_lines > 0); + + // NOTE: this code uses the same strategy as _ui_dbg_update_line_array() + // it will first scan backward and look for known instructions + // in the heatmap, and then scan forward + uint16_t bs_addr = request->addr; + int line_idx = 0; + // optional backwards scan + if (request->offset_lines < 0) { + const int num_backtrace_lines = -request->offset_lines; + for (; line_idx < num_backtrace_lines; line_idx++) { + // scan backwards for op start in block of 4 bytes (== max length of instruction) + const _ui_dbg_disasm_backscan_result_t res = _ui_dbg_disasm_backscan(win, bs_addr); + bs_addr = res.addr; + const int bs_line_idx = num_backtrace_lines - line_idx - 1; + if (res.is_known_op) { + _ui_dbg_disasm(win, bs_addr); + request->out_lines[bs_line_idx] = win->dasm_line; + } else { + uint8_t byte = _ui_dbg_read_byte(win, bs_addr); + ui_dbg_dasm_line_t* l = &request->out_lines[bs_line_idx]; + memset(l, 0, sizeof(ui_dbg_dasm_line_t)); + l->addr = bs_addr; + l->num_bytes = 1; + l->bytes[0] = byte; + l->num_chars = 3; + l->chars[0] = '?'; + l->chars[1] = '?'; + l->chars[2] = '?'; + } + } + } + + uint16_t fwd_addr = request->addr; + // if the offset is > 0, skip disassembled instructions + if (request->offset_lines > 0) { + for (int i = 0; i < request->offset_lines; i++) { + fwd_addr = _ui_dbg_disasm(win, fwd_addr); + } + } + for (; line_idx < request->num_lines; line_idx++) { + fwd_addr = _ui_dbg_disasm(win, fwd_addr); + request->out_lines[line_idx] = win->dasm_line; + } +} + #endif /* CHIPS_UI_IMPL */ diff --git a/ui/ui_kc85.h b/ui/ui_kc85.h index a57b7bee..d665776b 100644 --- a/ui/ui_kc85.h +++ b/ui/ui_kc85.h @@ -8,7 +8,7 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Define the KC85 type to build before including this header (both the @@ -19,7 +19,7 @@ CHIPS_KC85_TYPE_4 Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -41,7 +41,8 @@ - ui_dbg.h - ui_memedit.h - ui_memmap.h - + - ui_snapshot.h + ## zlib/libpng license Copyright (c) 2018 Andre Weissflog @@ -58,7 +59,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -76,11 +77,11 @@ typedef void (*ui_kc85_boot_t)(kc85_t* sys); typedef struct { kc85_t* kc85; - ui_kc85_boot_t boot_cb; /* user-provided callback to reboot */ - ui_dbg_create_texture_t create_texture_cb; /* texture creation callback for ui_dbg_t */ - ui_dbg_update_texture_t update_texture_cb; /* texture update callback for ui_dbg_t */ - ui_dbg_destroy_texture_t destroy_texture_cb; /* texture destruction callback for ui_dbg_t */ - ui_dbg_keys_desc_t dbg_keys; /* user-defined hotkeys for ui_dbg_t */ + ui_kc85_boot_t boot_cb; // user-provided callback to reboot + ui_dbg_texture_callbacks_t dbg_texture; // user-provided texture create/update/destroy callbacks + ui_dbg_debug_callbacks_t dbg_debug; // user-provided debugger callbacks + ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; // snapshot system creation params } ui_kc85_desc_t; typedef struct { @@ -95,12 +96,13 @@ typedef struct { ui_memedit_t memedit[4]; ui_dasm_t dasm[4]; ui_dbg_t dbg; + ui_snapshot_t snapshot; } ui_kc85_t; void ui_kc85_init(ui_kc85_t* ui, const ui_kc85_desc_t* desc); void ui_kc85_discard(ui_kc85_t* ui); void ui_kc85_draw(ui_kc85_t* ui); -kc85_debug_t ui_kc85_get_debug(ui_kc85_t* ui); +chips_debug_t ui_kc85_get_debug(ui_kc85_t* ui); #ifdef __cplusplus } /* extern "C" */ @@ -111,7 +113,7 @@ kc85_debug_t ui_kc85_get_debug(ui_kc85_t* ui); #ifndef __cplusplus #error "implementation must be compiled as C++" #endif -#include /* memset */ +#include // memset #ifndef CHIPS_ASSERT #include #define CHIPS_ASSERT(c) assert(c) @@ -125,6 +127,7 @@ static void _ui_kc85_draw_menu(ui_kc85_t* ui) { CHIPS_ASSERT(ui && ui->kc85 && ui->boot_cb); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->snapshot); if (ImGui::MenuItem("Reset")) { kc85_reset(ui->kc85); ui_dbg_reset(&ui->dbg); @@ -147,6 +150,7 @@ static void _ui_kc85_draw_menu(ui_kc85_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -173,47 +177,46 @@ static void _ui_kc85_draw_menu(ui_kc85_t* ui) { static void _ui_kc85_update_memmap(ui_kc85_t* ui) { CHIPS_ASSERT(ui && ui->kc85); - const uint8_t pio_a = ui->kc85->pio_a; + const uint64_t pio_pins = ui->kc85->pio_pins; ui_memmap_reset(&ui->memmap); #if defined(CHIPS_KC85_TYPE_2) /* KC85/2 memory map */ ui_memmap_layer(&ui->memmap, "System"); - ui_memmap_region(&ui->memmap, "RAM0", 0x0000, 0x4000, 0 != (pio_a & KC85_PIO_A_RAM)); - ui_memmap_region(&ui->memmap, "IRM", 0x8000, 0x4000, 0 != (pio_a & KC85_PIO_A_IRM)); - ui_memmap_region(&ui->memmap, "CAOS ROM 1", 0xE000, 0x0800, 0 != (pio_a & KC85_PIO_A_CAOS_ROM)); - ui_memmap_region(&ui->memmap, "CAOS ROM 2", 0xF000, 0x0800, 0 != (pio_a & KC85_PIO_A_CAOS_ROM)); + ui_memmap_region(&ui->memmap, "RAM0", 0x0000, 0x4000, 0 != (pio_pins & KC85_PIO_RAM)); + ui_memmap_region(&ui->memmap, "IRM", 0x8000, 0x4000, 0 != (pio_pins & KC85_PIO_IRM)); + ui_memmap_region(&ui->memmap, "CAOS ROM 1", 0xE000, 0x0800, 0 != (pio_pins & KC85_PIO_CAOS_ROM)); + ui_memmap_region(&ui->memmap, "CAOS ROM 2", 0xF000, 0x0800, 0 != (pio_pins & KC85_PIO_CAOS_ROM)); #elif defined(CHIPS_KC85_TYPE_3) /* KC85/3 memory map */ ui_memmap_layer(&ui->memmap, "System"); - ui_memmap_region(&ui->memmap, "RAM0", 0x0000, 0x4000, 0 != (pio_a & KC85_PIO_A_RAM)); - ui_memmap_region(&ui->memmap, "IRM", 0x8000, 0x4000, 0 != (pio_a & KC85_PIO_A_IRM)); - ui_memmap_region(&ui->memmap, "BASIC ROM", 0xC000, 0x2000, 0 != (pio_a & KC85_PIO_A_BASIC_ROM)); - ui_memmap_region(&ui->memmap, "CAOS ROM", 0xE000, 0x2000, 0 != (pio_a & KC85_PIO_A_CAOS_ROM)); + ui_memmap_region(&ui->memmap, "RAM0", 0x0000, 0x4000, 0 != (pio_pins & KC85_PIO_RAM)); + ui_memmap_region(&ui->memmap, "IRM", 0x8000, 0x4000, 0 != (pio_pins & KC85_PIO_IRM)); + ui_memmap_region(&ui->memmap, "BASIC ROM", 0xC000, 0x2000, 0 != (pio_pins & KC85_PIO_BASIC_ROM)); + ui_memmap_region(&ui->memmap, "CAOS ROM", 0xE000, 0x2000, 0 != (pio_pins & KC85_PIO_CAOS_ROM)); #else /* KC85/4 memory map */ - const uint8_t pio_b = ui->kc85->pio_b; const uint8_t io86 = ui->kc85->io86; const uint8_t io84 = ui->kc85->io84; ui_memmap_layer(&ui->memmap, "System 0"); - ui_memmap_region(&ui->memmap, "RAM0", 0x0000, 0x4000, 0 != (pio_a & KC85_PIO_A_RAM)); + ui_memmap_region(&ui->memmap, "RAM0", 0x0000, 0x4000, 0 != (pio_pins & KC85_PIO_RAM)); ui_memmap_region(&ui->memmap, "RAM4", 0x4000, 0x4000, 0 != (io86 & KC85_IO86_RAM4)); - ui_memmap_region(&ui->memmap, "IRM0 PIXELS", 0x8000, 0x2800, (0 != (pio_a & KC85_PIO_A_IRM)) && (0 == (io84 & 6))); - ui_memmap_region(&ui->memmap, "IRM0", 0xA800, 0x1800, 0 != (pio_a & KC85_PIO_A_IRM)); - ui_memmap_region(&ui->memmap, "CAOS ROM E", 0xE000, 0x2000, 0 != (pio_a & KC85_PIO_A_CAOS_ROM)); + ui_memmap_region(&ui->memmap, "IRM0 PIXELS", 0x8000, 0x2800, (0 != (pio_pins & KC85_PIO_IRM)) && (0 == (io84 & 6))); + ui_memmap_region(&ui->memmap, "IRM0", 0xA800, 0x1800, 0 != (pio_pins & KC85_PIO_IRM)); + ui_memmap_region(&ui->memmap, "CAOS ROM E", 0xE000, 0x2000, 0 != (pio_pins & KC85_PIO_CAOS_ROM)); ui_memmap_layer(&ui->memmap, "System 1"); - ui_memmap_region(&ui->memmap, "IRM0 COLORS", 0x8000, 0x2800, (0 != (pio_a & KC85_PIO_A_IRM)) && (2 == (io84 & 6))); + ui_memmap_region(&ui->memmap, "IRM0 COLORS", 0x8000, 0x2800, (0 != (pio_pins & KC85_PIO_IRM)) && (2 == (io84 & 6))); ui_memmap_region(&ui->memmap, "CAOS ROM C", 0xC000, 0x1000, 0 != (io86 & KC85_IO86_CAOS_ROM_C)); ui_memmap_layer(&ui->memmap, "System 2"); - ui_memmap_region(&ui->memmap, "IRM1 PIXELS", 0x8000, 0x2800, (0 != (pio_a & KC85_PIO_A_IRM)) && (4 == (io84 & 6))); - ui_memmap_region(&ui->memmap, "BASIC ROM", 0xC000, 0x2000, 0 != (pio_a & KC85_PIO_A_BASIC_ROM)); + ui_memmap_region(&ui->memmap, "IRM1 PIXELS", 0x8000, 0x2800, (0 != (pio_pins & KC85_PIO_IRM)) && (4 == (io84 & 6))); + ui_memmap_region(&ui->memmap, "BASIC ROM", 0xC000, 0x2000, 0 != (pio_pins & KC85_PIO_BASIC_ROM)); ui_memmap_layer(&ui->memmap, "System 3"); - ui_memmap_region(&ui->memmap, "IRM1 COLORS", 0x8000, 0x2800, (0 != (pio_a & KC85_PIO_A_IRM)) && (6 == (io84 & 6))); + ui_memmap_region(&ui->memmap, "IRM1 COLORS", 0x8000, 0x2800, (0 != (pio_pins & KC85_PIO_IRM)) && (6 == (io84 & 6))); ui_memmap_layer(&ui->memmap, "System 4"); - ui_memmap_region(&ui->memmap, "RAM8 BANK0", 0x8000, 0x4000, (0 != (pio_b & KC85_PIO_B_RAM8)) && (0 == (io84 & KC85_IO84_SEL_RAM8))); + ui_memmap_region(&ui->memmap, "RAM8 BANK0", 0x8000, 0x4000, (0 != (pio_pins & KC85_PIO_RAM8)) && (0 == (io84 & KC85_IO84_SEL_RAM8))); ui_memmap_layer(&ui->memmap, "System 5"); - ui_memmap_region(&ui->memmap, "RAM8 BANK1", 0x8000, 0x4000, (0 != (pio_b & KC85_PIO_B_RAM8)) && (0 != (io84 & KC85_IO84_SEL_RAM8))); + ui_memmap_region(&ui->memmap, "RAM8 BANK1", 0x8000, 0x4000, (0 != (pio_pins & KC85_PIO_RAM8)) && (0 != (io84 & KC85_IO84_SEL_RAM8))); #endif - for (int i = 0; i < KC85_NUM_SLOTS; i++) { + for (size_t i = 0; i < KC85_EXP_NUM_SLOTS; i++) { const uint8_t slot_addr = ui->kc85->exp.slot[i].addr; ui_memmap_layer(&ui->memmap, slot_addr == 0x08 ? "Slot 08" : "Slot 0C"); if (kc85_slot_occupied(ui->kc85, slot_addr)) { @@ -331,8 +334,15 @@ static uint8_t _ui_kc85_mem_read(int layer, uint16_t addr, void* user_data) { kc85_t* kc85 = (kc85_t*) user_data; if (layer == 0) { return mem_rd(&kc85->mem, addr); - } - else { + } else if ((layer >= 4) && (layer < 8)) { + // IRM access + if ((addr >= 0x8000) && (addr < 0xC000)) { + return kc85->ram[KC85_IRM0_PAGE + (layer-4)][addr - 0x8000]; + } else { + return 0xFF; + } + } else { + // Motherboard, SLOT 08, SLOT 0C return mem_layer_rd(&kc85->mem, layer-1, addr); } } @@ -342,8 +352,12 @@ static void _ui_kc85_mem_write(int layer, uint16_t addr, uint8_t data, void* use kc85_t* kc85 = (kc85_t*) user_data; if (layer == 0) { mem_wr(&kc85->mem, addr, data); - } - else { + } else if ((layer >= 4) && (layer < 8)) { + // IRM access + if ((addr >= 0x8000) && (addr < 0xC000)) { + kc85->ram[KC85_IRM0_PAGE + (layer-4)][addr - 0x8000] = data; + } + } else { mem_layer_wr(&kc85->mem, layer-1, addr, data); } } @@ -352,9 +366,9 @@ void ui_kc85_init(ui_kc85_t* ui, const ui_kc85_desc_t* ui_desc) { CHIPS_ASSERT(ui && ui_desc); CHIPS_ASSERT(ui_desc->kc85); CHIPS_ASSERT(ui_desc->boot_cb); - CHIPS_ASSERT(ui_desc->create_texture_cb && ui_desc->update_texture_cb && ui_desc->destroy_texture_cb); ui->kc85 = ui_desc->kc85; ui->boot_cb = ui_desc->boot_cb; + ui_snapshot_init(&ui->snapshot, &ui_desc->snapshot); int x = 20, y = 20, dx = 10, dy = 10; { ui_dbg_desc_t desc = {0}; @@ -362,10 +376,12 @@ void ui_kc85_init(ui_kc85_t* ui, const ui_kc85_desc_t* ui_desc) { desc.x = x; desc.y = y; desc.z80 = &ui->kc85->cpu; + desc.freq_hz = KC85_FREQUENCY; + desc.scanline_ticks = KC85_SCANLINE_TICKS; + desc.frame_ticks = KC85_SCANLINE_TICKS * KC85_NUM_SCANLINES; desc.read_cb = _ui_kc85_mem_read; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; + desc.debug_cbs = ui_desc->dbg_debug; desc.keys = ui_desc->dbg_keys; desc.user_data = ui->kc85; ui_dbg_init(&ui->dbg, &desc); @@ -426,6 +442,12 @@ void ui_kc85_init(ui_kc85_t* ui, const ui_kc85_desc_t* ui_desc) { desc.layers[1] = "Motherboard"; desc.layers[2] = "Slot 08"; desc.layers[3] = "Slot 0C"; + #if defined(CHIPS_KC85_TYPE_4) + desc.layers[4] = "IRM 0 Pixels"; + desc.layers[5] = "IRM 0 Colors"; + desc.layers[6] = "IRM 1 Pixels"; + desc.layers[7] = "IRM 1 Colors"; + #endif desc.read_cb = _ui_kc85_mem_read; desc.write_cb = _ui_kc85_mem_write; desc.user_data = ui->kc85; @@ -450,6 +472,12 @@ void ui_kc85_init(ui_kc85_t* ui, const ui_kc85_desc_t* ui_desc) { desc.layers[1] = "Motherboard"; desc.layers[2] = "Slot 08"; desc.layers[3] = "Slot 0C"; + #if defined(CHIPS_KC85_TYPE_4) + desc.layers[4] = "IRM 0 Pixels"; + desc.layers[5] = "IRM 0 Colors"; + desc.layers[6] = "IRM 1 Pixels"; + desc.layers[7] = "IRM 1 Colors"; + #endif desc.cpu_type = UI_DASM_CPUTYPE_Z80; desc.start_addr = 0xF000; desc.read_cb = _ui_kc85_mem_read; @@ -498,9 +526,9 @@ void ui_kc85_draw(ui_kc85_t* ui) { ui_dbg_draw(&ui->dbg); } -kc85_debug_t ui_kc85_get_debug(ui_kc85_t* ui) { - kc85_debug_t res = {}; - res.callback.func = (kc85_debug_func_t)ui_dbg_tick; +chips_debug_t ui_kc85_get_debug(ui_kc85_t* ui) { + chips_debug_t res = {}; + res.callback.func = (chips_debug_func_t)ui_dbg_tick; res.callback.user_data = &ui->dbg; res.stopped = &ui->dbg.dbg.stopped; return res; diff --git a/ui/ui_kc85sys.h b/ui/ui_kc85sys.h index f852f9e7..b9b3096e 100644 --- a/ui/ui_kc85sys.h +++ b/ui/ui_kc85sys.h @@ -8,7 +8,7 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Define the KC85 type to build before including this header (both the @@ -19,7 +19,7 @@ CHIPS_KC85_TYPE_4 Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -51,7 +51,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -133,45 +133,45 @@ void ui_kc85sys_draw(ui_kc85sys_t* win) { ImGui::SetNextWindowSize(ImVec2(win->init_w, win->init_h), ImGuiCond_Once); if (ImGui::Begin(win->title, &win->open)) { if (ImGui::CollapsingHeader("Port 88h (PIO A)", ImGuiTreeNodeFlags_DefaultOpen)) { - const uint8_t v = win->kc85->pio_a; - ImGui::Text("0: CAOS ROM E %s", (v & KC85_PIO_A_CAOS_ROM) ? "ON":"OFF"); - ImGui::Text("1: RAM0 %s", (v & KC85_PIO_A_RAM) ? "ON":"OFF"); - ImGui::Text("2: IRM %s", (v & KC85_PIO_A_IRM) ? "ON":"OFF"); - ImGui::Text("3: RAM0 %s", (v & KC85_PIO_A_RAM_RO) ? "R/W":"R/O"); + const uint64_t v = win->kc85->pio_pins; + ImGui::Text("0: CAOS ROM E %s", (v & KC85_PIO_CAOS_ROM) ? "ON":"OFF"); + ImGui::Text("1: RAM0 %s", (v & KC85_PIO_RAM) ? "ON":"OFF"); + ImGui::Text("2: IRM %s", (v & KC85_PIO_IRM) ? "ON":"OFF"); + ImGui::Text("3: RAM0 %s", (v & KC85_PIO_RAM_RO) ? "R/W":"R/O"); ImGui::Text("4: unused"); - ImGui::Text("5: Tape LED %s", (v & KC85_PIO_A_TAPE_LED) ? "ON":"OFF"); - ImGui::Text("6: Tape Motor %s", (v & KC85_PIO_A_TAPE_MOTOR) ? "ON":"OFF"); + ImGui::Text("5: Tape LED %s", (v & KC85_PIO_TAPE_LED) ? "ON":"OFF"); + ImGui::Text("6: Tape Motor %s", (v & KC85_PIO_TAPE_MOTOR) ? "ON":"OFF"); #if defined(CHIPS_KC85_TYPE_2) ImGui::Text("7: unused"); #else - ImGui::Text("7: BASIC ROM %s", (v & KC85_PIO_A_BASIC_ROM) ? "ON":"OFF"); + ImGui::Text("7: BASIC ROM %s", (v & KC85_PIO_BASIC_ROM) ? "ON":"OFF"); #endif } if (ImGui::CollapsingHeader("Port 89h (PIO B)", ImGuiTreeNodeFlags_DefaultOpen)) { - const uint8_t v = win->kc85->pio_b; + const uint64_t v = win->kc85->pio_pins; #if defined(CHIPS_KC85_TYPE_4) - ImGui::Text("0: 'trueck' %s", (v & 1) ? "ON":"OFF"); - ImGui::Text("1..4: Volume %02Xh", ((v>>1) & 0x0F)); + ImGui::Text("0: 'trueck' %s", (v & Z80PIO_PB0) ? "ON":"OFF"); + ImGui::Text("1..4: Volume %02Xh", (uint8_t)((v>>Z80PIO_PIN_PB1) & 0x0F)); #else - ImGui::Text("0..4: Volume %02Xh", (v & 0x1F)); + ImGui::Text("0..4: Volume %02Xh", (uint8_t)((v>>Z80PIO_PIN_PB0) & 0x1F)); #endif #if defined(CHIPS_KC85_TYPE_4) - ImGui::Text("5: RAM8 %s", (v & KC85_PIO_B_RAM8) ? "ON":"OFF"); - ImGui::Text("6: RAM8 %s", (v & KC85_PIO_B_RAM8_RO) ? "R/W":"R/O"); + ImGui::Text("5: RAM8 %s", (v & KC85_PIO_RAM8) ? "ON":"OFF"); + ImGui::Text("6: RAM8 %s", (v & KC85_PIO_RAM8_RO) ? "R/W":"R/O"); #else ImGui::Text("5..6: unused"); #endif - ImGui::Text("7: Blinking %s", (v & KC85_PIO_B_BLINK_ENABLED) ? "ON":"OFF"); + ImGui::Text("7: Blinking %s", (v & KC85_PIO_BLINK_ENABLED) ? "ON":"OFF"); } #if defined(CHIPS_KC85_TYPE_4) if (ImGui::CollapsingHeader("Port 84h", ImGuiTreeNodeFlags_DefaultOpen)) { const uint8_t v = win->kc85->io84; - ImGui::Text("0: Show image %d", (v & KC85_IO84_SEL_VIEW_IMG) ? 0:1); - ImGui::Text("1: Access %s", (v & KC85_IO84_SEL_CPU_COLOR) ? "PIXELS":"COLORS"); - ImGui::Text("2: Access image %d", (v & KC85_IO84_SEL_CPU_IMG) ? 0:1); + ImGui::Text("0: Show image %d", (v & KC85_IO84_SEL_VIEW_IMG) ? 1:0); + ImGui::Text("1: Access %s", (v & KC85_IO84_SEL_CPU_COLOR) ? "COLORS":"PIXELS"); + ImGui::Text("2: Access image %d", (v & KC85_IO84_SEL_CPU_IMG) ? 1:0); ImGui::Text("3: Hicolor mode %s", (v & KC85_IO84_HICOLOR) ? "OFF":"ON"); - ImGui::Text("4: RAM8 block %d", (v & KC85_IO84_SEL_RAM8) ? 0:1); - ImGui::Text("5: RAM8 ??? %d", (v & KC85_IO84_BLOCKSEL_RAM8) ? 0:1); + ImGui::Text("4: RAM8 block %d", (v & KC85_IO84_SEL_RAM8) ? 1:0); + ImGui::Text("5: RAM8 ??? %d", (v & KC85_IO84_BLOCKSEL_RAM8) ? 1:0); ImGui::Text("6..7: unused"); } if (ImGui::CollapsingHeader("Port 86h", ImGuiTreeNodeFlags_DefaultOpen)) { @@ -183,8 +183,8 @@ void ui_kc85sys_draw(ui_kc85sys_t* win) { } #endif if (ImGui::CollapsingHeader("Display", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text("Vert Count: %d", win->kc85->v_count); - ImGui::Text("Hori Tick: %d", win->kc85->h_tick); + ImGui::Text("Vert Count: %d", win->kc85->video.v_count); + ImGui::Text("Hori Tick: %d", win->kc85->video.h_tick); } } ImGui::End(); diff --git a/ui/ui_lc80.h b/ui/ui_lc80.h index 68c641f8..b46c34f5 100644 --- a/ui/ui_lc80.h +++ b/ui/ui_lc80.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -50,7 +50,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -64,11 +64,10 @@ typedef void (*ui_lc80_boot_t)(lc80_t* sys); typedef struct { lc80_t* sys; - ui_lc80_boot_t boot_cb; /* user-provided callback to reboot to different config */ - ui_dbg_create_texture_t create_texture_cb; /* texture creation callback for ui_dbg_t */ - ui_dbg_update_texture_t update_texture_cb; /* texture update callback for ui_dbg_t */ - ui_dbg_destroy_texture_t destroy_texture_cb; /* texture destruction callback for ui_dbg_t */ - ui_dbg_keys_desc_t dbg_keys; /* user-defined hotkeys for ui_dbg_t */ + ui_lc80_boot_t boot_cb; // user-provided callback to reboot to different config + ui_dbg_texture_callbacks_t dbg_texture; // texture create/update/destroy callback + ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; // snapshot system creation params } ui_lc80_desc_t; typedef struct { @@ -94,6 +93,7 @@ typedef struct { ui_memedit_t memedit[4]; ui_dasm_t dasm[4]; ui_dbg_t dbg; + ui_snapshot_t snapshot; } win; struct { ui_lc80_chip_t cpu; @@ -113,7 +113,7 @@ typedef struct { void ui_lc80_init(ui_lc80_t* ui, const ui_lc80_desc_t* desc); void ui_lc80_discard(ui_lc80_t* ui); void ui_lc80_draw(ui_lc80_t* ui); -lc80_debug_t ui_lc80_get_debug(ui_lc80_t* ui); +chips_debug_t ui_lc80_get_debug(ui_lc80_t* ui); #ifdef __cplusplus } /* extern "C" */ @@ -705,6 +705,7 @@ static void _ui_lc80_draw_menu(ui_lc80_t* ui) { CHIPS_ASSERT(ui && ui->sys && ui->boot_cb); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->win.snapshot); if (ImGui::MenuItem("Reset")) { lc80_reset(ui->sys); ui_dbg_reset(&ui->win.dbg); @@ -726,6 +727,7 @@ static void _ui_lc80_draw_menu(ui_lc80_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->win.dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->win.dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->win.dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->win.dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->win.dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -1046,6 +1048,7 @@ void _ui_lc80_mem_write(int layer, uint16_t addr, uint8_t data, void* user_data) } static void _ui_lc80_init_windows(ui_lc80_t* ui, const ui_lc80_desc_t* ui_desc) { + ui_snapshot_init(&ui->win.snapshot, &ui_desc->snapshot); int x = 20, y = 20, dx = 10, dy = 10; { ui_dbg_desc_t desc = {0}; @@ -1054,9 +1057,7 @@ static void _ui_lc80_init_windows(ui_lc80_t* ui, const ui_lc80_desc_t* ui_desc) desc.y = y; desc.z80 = &ui->sys->cpu; desc.read_cb = _ui_lc80_mem_read; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; desc.keys = ui_desc->dbg_keys; desc.user_data = ui->sys; ui_dbg_init(&ui->win.dbg, &desc); @@ -1182,9 +1183,9 @@ void ui_lc80_draw(ui_lc80_t* ui) { _ui_lc80_draw_windows(ui); } -lc80_debug_t ui_lc80_get_debug(ui_lc80_t* ui) { - lc80_debug_t res = {}; - res.callback.func = (lc80_debug_func_t)ui_dbg_tick; +chips_debug_t ui_lc80_get_debug(ui_lc80_t* ui) { + chips_debug_t res = {}; + res.callback.func = (chips_debug_func_t)ui_dbg_tick; res.callback.user_data = &ui->win.dbg; res.stopped = &ui->win.dbg.dbg.stopped; return res; diff --git a/ui/ui_m6561.h b/ui/ui_m6561.h index 47f9e8f8..2c2bdc38 100644 --- a/ui/ui_m6561.h +++ b/ui/ui_m6561.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -47,7 +47,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -178,9 +178,9 @@ static void _ui_m6561_draw_graphics_unit(const ui_m6561_t* win) { const m6561_graphics_unit_t* gu = &win->vic->gunit; ImGui::Text("shifter: %02X", gu->shift); ImGui::Text("color: %02X", gu->color); - _ui_m6561_draw_rgb("bg_color:", gu->bg_color); - _ui_m6561_draw_rgb("brd_color", gu->brd_color); - _ui_m6561_draw_rgb("aux_color", gu->aux_color); + _ui_m6561_draw_rgb("bg_color:", m6561_color(gu->bg_color)); + _ui_m6561_draw_rgb("brd_color", m6561_color(gu->brd_color)); + _ui_m6561_draw_rgb("aux_color", m6561_color(gu->aux_color)); } } @@ -234,16 +234,6 @@ static void _ui_m6561_draw_sound(const ui_m6561_t* win) { } } -static void _ui_m6561_tint_framebuffer(ui_m6561_t* win) { - uint32_t* ptr = win->vic->crt.rgba8_buffer; - if (ptr) { - const int num = m6561_display_width(win->vic) * m6561_display_height(win->vic); - for (int i = 0; i < num; i++) { - ptr[i] = ~ptr[i] | 0xFF0000F0; - } - } -} - void ui_m6561_draw(ui_m6561_t* win) { CHIPS_ASSERT(win && win->valid); if (!win->open) { @@ -258,9 +248,6 @@ void ui_m6561_draw(ui_m6561_t* win) { ImGui::SameLine(); ImGui::BeginChild("##m6561_state", ImVec2(0, 0), true); ImGui::Checkbox("Debug Visualization", &win->vic->debug_vis); - if (ImGui::Button("Tint Framebuffer")) { - _ui_m6561_tint_framebuffer(win); - } _ui_m6561_draw_hwcolors(); _ui_m6561_draw_registers(win); _ui_m6561_draw_raster_unit(win); diff --git a/ui/ui_m6569.h b/ui/ui_m6569.h index e9496499..af2a6457 100644 --- a/ui/ui_m6569.h +++ b/ui/ui_m6569.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -47,7 +47,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -243,7 +243,7 @@ static void _ui_m6569_draw_border_unit(const ui_m6569_t* win) { ImGui::Text("left:%04X right:%04X", brd->left, brd->right); ImGui::Text("top:%04X bottom:%04X", brd->top, brd->bottom); ImGui::Text("main:%s vert:%s", brd->main?"ON ":"OFF", brd->vert?"ON ":"OFF"); - _ui_m6569_draw_color("border color: ", brd->bc_index); + _ui_m6569_draw_color("border color: ", brd->bc); } } @@ -257,10 +257,10 @@ static void _ui_m6569_draw_graphics_unit(const ui_m6569_t* win) { ui_util_b8("shift: ", gu->shift); ui_util_b8("outp: ", gu->outp); ui_util_b8("outp2: ", gu->outp2); - _ui_m6569_draw_color("bg0:", gu->bg_index[0]); ImGui::SameLine(); - _ui_m6569_draw_color("bg1:", gu->bg_index[1]); ImGui::SameLine(); - _ui_m6569_draw_color("bg2:", gu->bg_index[2]); ImGui::SameLine(); - _ui_m6569_draw_color("bg3:", gu->bg_index[3]); + _ui_m6569_draw_color("bg0:", gu->bg[0] & 0xFF); ImGui::SameLine(); + _ui_m6569_draw_color("bg1:", gu->bg[1] & 0xFF); ImGui::SameLine(); + _ui_m6569_draw_color("bg2:", gu->bg[2] & 0xFF); ImGui::SameLine(); + _ui_m6569_draw_color("bg3:", gu->bg[3] & 0xFF); } } @@ -288,16 +288,6 @@ static void _ui_m6569_draw_sprite_units(const ui_m6569_t* win) { } } -static void _ui_m6569_tint_framebuffer(ui_m6569_t* win) { - uint32_t* ptr = win->vic->crt.rgba8_buffer; - if (ptr) { - const int num = m6569_display_width(win->vic) * m6569_display_height(win->vic); - for (int i = 0; i < num; i++) { - ptr[i] = ~ptr[i] | 0xFF0000F0; - } - } -} - void ui_m6569_draw(ui_m6569_t* win) { CHIPS_ASSERT(win && win->valid); if (!win->open) { @@ -312,9 +302,6 @@ void ui_m6569_draw(ui_m6569_t* win) { ImGui::SameLine(); ImGui::BeginChild("##m6569_state", ImVec2(0, 0), true); ImGui::Checkbox("Debug Visualization", &win->vic->debug_vis); - if (ImGui::Button("Tint Framebuffer")) { - _ui_m6569_tint_framebuffer(win); - } _ui_m6569_draw_hwcolors(); _ui_m6569_draw_registers(win); _ui_m6569_draw_raster_unit(win); diff --git a/ui/ui_mc6847.h b/ui/ui_mc6847.h index dfef03f4..3edd9c2e 100644 --- a/ui/ui_mc6847.h +++ b/ui/ui_mc6847.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -47,7 +47,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -123,7 +123,7 @@ static void _ui_mc6847_draw_hwcolors(ui_mc6847_t* win) { ImVec4 c; const ImVec2 size(18,18); for (int i = 0; i < 8; i++) { - c = ImColor(win->mc6847->palette[i]); + c = ImColor(win->mc6847->hwcolors[i]); ImGui::PushID(i); ImGui::ColorButton("##gm_color", c, ImGuiColorEditFlags_NoAlpha, size); ImGui::PopID(); @@ -133,10 +133,10 @@ static void _ui_mc6847_draw_hwcolors(ui_mc6847_t* win) { } ImGui::Separator(); ImGui::Text("Text Mode Colors:"); - ImVec4 tm_green = ImColor(win->mc6847->alnum_green); - ImVec4 tm_dark_green = ImColor(win->mc6847->alnum_dark_green); - ImVec4 tm_orange = ImColor(win->mc6847->alnum_orange); - ImVec4 tm_dark_orange = ImColor(win->mc6847->alnum_dark_orange); + ImVec4 tm_green = ImColor(win->mc6847->hwcolors[MC6847_HWCOLOR_ALNUM_GREEN]); + ImVec4 tm_dark_green = ImColor(win->mc6847->hwcolors[MC6847_HWCOLOR_ALNUM_DARK_GREEN]); + ImVec4 tm_orange = ImColor(win->mc6847->hwcolors[MC6847_HWCOLOR_ALNUM_ORANGE]); + ImVec4 tm_dark_orange = ImColor(win->mc6847->hwcolors[MC6847_HWCOLOR_ALNUM_DARK_ORANGE]); ImGui::ColorButton("##tm_green", tm_green, ImGuiColorEditFlags_NoAlpha, size); ImGui::SameLine(); ImGui::ColorButton("##tm_dark_green", tm_dark_green, ImGuiColorEditFlags_NoAlpha, size); ImGui::SameLine(); ImGui::ColorButton("##tm_orange", tm_orange, ImGuiColorEditFlags_NoAlpha, size); ImGui::SameLine(); diff --git a/ui/ui_memedit.h b/ui/ui_memedit.h index 2e6f1c4b..354af983 100644 --- a/ui/ui_memedit.h +++ b/ui/ui_memedit.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -46,7 +46,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -76,7 +76,8 @@ typedef struct { const char* layers[UI_MEMEDIT_MAX_LAYERS]; /* memory system layer names */ ui_memedit_read_t read_cb; ui_memedit_write_t write_cb; - int num_rows; /* initial number of rows, default is 16 */ + size_t max_addr; + int num_cols; /* initial number of cols, default is 16 */ bool hide_ascii; /* initially hide the ASCII column */ bool hide_options; /* hide the Options dropdown */ bool hide_addr_input; /* hide the address input field */ @@ -93,6 +94,7 @@ typedef struct { void* user_data; float init_x, init_y; float init_w, init_h; + size_t max_addr; MemoryEditor* ed; bool open; bool valid; @@ -163,69 +165,94 @@ void ui_memedit_draw(ui_memedit_t* win); // Todo/Bugs: // - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame. +#ifdef _MSC_VER +#define _PRISizeT "I" +#define ImSnprintf _snprintf +#else +#define _PRISizeT "z" +#define ImSnprintf snprintf +#endif + struct MemoryEditor { - typedef unsigned char u8; - - // Settings - bool Open; // = true // set to false when DrawWindow() was closed. ignore if not using DrawWindow - bool ReadOnly; // = false // set to true to disable any editing - int Rows; // = 16 // - bool Options; // = true - bool AddrInput; // = true - bool OptShowAscii; // = true // - bool OptShowHexII; // = false // - bool OptGreyOutZeroes; // = true // - int OptMidRowsCount; // = 8 // set to 0 to disable extra spacing between every mid-rows - int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr) - ImU32 HighlightColor; // // color of highlight - u8 (*ReadFn)(u8* data, size_t off); // = NULL // optional handler to read bytes - void (*WriteFn)(u8* data, size_t off, u8 d); // = NULL // optional handler to write bytes - bool (*HighlightFn)(u8* data, size_t off); // = NULL // optional handler to return Highlight property (to support non-contiguous highlighting) + enum DataFormat + { + DataFormat_Bin = 0, + DataFormat_Dec = 1, + DataFormat_Hex = 2, + DataFormat_COUNT + }; /*--- BEGIN ui_memedit.h changes ---*/ int NumLayers; int CurLayer; const char* Layers[UI_MEMEDIT_MAX_LAYERS]; + bool OptShowAddrInput; // = true /*--- END ui_memedit.h changes ---*/ - // State/Internals + // Settings + bool Open; // = true // set to false when DrawWindow() was closed. ignore if not using DrawWindow(). + bool ReadOnly; // = false // disable any editing. + int Cols; // = 16 // number of columns to display. + bool OptShowOptions; // = true // display options button/context menu. when disabled, options will be locked unless you provide your own UI for them. + bool OptShowDataPreview; // = false // display a footer previewing the decimal/binary/hex/float representation of the currently selected bytes. + bool OptShowHexII; // = false // display values in HexII representation instead of regular hexadecimal: hide null/zero bytes, ascii values as ".X". + bool OptShowAscii; // = true // display ASCII representation on the right side. + bool OptGreyOutZeroes; // = true // display null/zero bytes using the TextDisabled color. + bool OptUpperCaseHex; // = true // display hexadecimal values as "FF" instead of "ff". + int OptMidColsCount; // = 8 // set to 0 to disable extra spacing between every mid-cols. + int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr). + float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets + ImU32 HighlightColor; // // background color of highlighted bytes. + ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes. + void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes. + bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting). + + // [Internal State] bool ContentsWidthChanged; + size_t DataPreviewAddr; size_t DataEditingAddr; bool DataEditingTakeFocus; char DataInputBuf[32]; char AddrInputBuf[32]; size_t GotoAddr; size_t HighlightMin, HighlightMax; + int PreviewEndianess; + ImGuiDataType PreviewDataType; MemoryEditor() { // Settings Open = true; ReadOnly = false; - Rows = 16; - Options = true; - AddrInput = true; - OptShowAscii = true; + Cols = 16; + OptShowOptions = true; + OptShowDataPreview = false; OptShowHexII = false; + OptShowAscii = true; OptGreyOutZeroes = true; - OptMidRowsCount = 8; + OptUpperCaseHex = true; + OptMidColsCount = 8; OptAddrDigitsCount = 0; - HighlightColor = IM_COL32(255, 255, 255, 40); + OptFooterExtraHeight = 0.0f; + HighlightColor = IM_COL32(255, 255, 255, 50); ReadFn = NULL; WriteFn = NULL; HighlightFn = NULL; // State/Internals ContentsWidthChanged = false; - DataEditingAddr = (size_t)-1; + DataPreviewAddr = DataEditingAddr = (size_t)-1; DataEditingTakeFocus = false; memset(DataInputBuf, 0, sizeof(DataInputBuf)); memset(AddrInputBuf, 0, sizeof(AddrInputBuf)); GotoAddr = (size_t)-1; HighlightMin = HighlightMax = (size_t)-1; + PreviewEndianess = 0; + PreviewDataType = ImGuiDataType_S32; /*--- BEGIN ui_memedit.h changes ---*/ + OptShowAddrInput = true; NumLayers = 0; CurLayer = 0; for (int i = 0; i < UI_MEMEDIT_MAX_LAYERS; i++) { @@ -247,12 +274,14 @@ struct MemoryEditor float LineHeight; float GlyphWidth; float HexCellWidth; - float SpacingBetweenMidRows; + float SpacingBetweenMidCols; float PosHexStart; float PosHexEnd; float PosAsciiStart; float PosAsciiEnd; float WindowWidth; + + Sizes() { memset(this, 0, sizeof(*this)); } }; void CalcSizes(Sizes& s, size_t mem_size, size_t base_display_addr) @@ -265,37 +294,32 @@ struct MemoryEditor s.LineHeight = ImGui::GetTextLineHeight(); s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere - s.SpacingBetweenMidRows = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidRowsCount columns we add a bit of extra spacing + s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth; - s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Rows); + s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols); s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd; if (OptShowAscii) { s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1; - if (OptMidRowsCount > 0) - s.PosAsciiStart += ((Rows + OptMidRowsCount - 1) / OptMidRowsCount) * s.SpacingBetweenMidRows; - s.PosAsciiEnd = s.PosAsciiStart + Rows * s.GlyphWidth; + if (OptMidColsCount > 0) + s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; + s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth; } s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth; } -#ifdef _MSC_VER -#define _PRISizeT "IX" -#else -#define _PRISizeT "zX" -#endif - // Standalone Memory Editor window - void DrawWindow(const char* title, u8* mem_data, size_t mem_size, size_t base_display_addr = 0x0000) + void DrawWindow(const char* title, void* mem_data, size_t mem_size, size_t base_display_addr = 0x0000) { Sizes s; CalcSizes(s, mem_size, base_display_addr); + ImGui::SetNextWindowSize(ImVec2(s.WindowWidth, s.WindowWidth * 0.60f), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX)); Open = true; if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar)) { - if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseClicked(1)) + if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) ImGui::OpenPopup("context"); DrawContents(mem_data, mem_size, base_display_addr); if (ContentsWidthChanged) @@ -308,54 +332,52 @@ struct MemoryEditor } // Memory Editor contents only - void DrawContents(u8* mem_data, size_t mem_size, size_t base_display_addr = 0x0000) + void DrawContents(void* mem_data_void, size_t mem_size, size_t base_display_addr = 0x0000) { + if (Cols < 1) + Cols = 1; + + ImU8* mem_data = (ImU8*)mem_data_void; Sizes s; CalcSizes(s, mem_size, base_display_addr); - if (0 == AddrInputBuf[0]) { - sprintf(AddrInputBuf, "%0*" _PRISizeT, s.AddrDigitsCount, base_display_addr); - } ImGuiStyle& style = ImGui::GetStyle(); - float footer_height_to_reserve = 0.0f; - if (Options || AddrInput || (NumLayers > 1)) { - footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text - } - ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height_to_reserve)); + // We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window. + // This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move. + const float height_separator = style.ItemSpacing.y; + float footer_height = OptFooterExtraHeight; + if (OptShowOptions) + footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1; + if (OptShowDataPreview) + footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3; + ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav); ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - const int line_total_count = (int)((mem_size + Rows - 1) / Rows); + // We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function. + const int line_total_count = (int)((mem_size + Cols - 1) / Cols); ImGuiListClipper clipper; clipper.Begin(line_total_count, s.LineHeight); - clipper.Step(); - const size_t visible_start_addr = clipper.DisplayStart * Rows; - const size_t visible_end_addr = clipper.DisplayEnd * Rows; bool data_next = false; if (ReadOnly || DataEditingAddr >= mem_size) DataEditingAddr = (size_t)-1; + if (DataPreviewAddr >= mem_size) + DataPreviewAddr = (size_t)-1; + + size_t preview_data_type_size = OptShowDataPreview ? DataTypeGetSize(PreviewDataType) : 0; - size_t data_editing_addr_backup = DataEditingAddr; size_t data_editing_addr_next = (size_t)-1; if (DataEditingAddr != (size_t)-1) { // Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered) - if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && DataEditingAddr >= (size_t)Rows) { data_editing_addr_next = DataEditingAddr - Rows; DataEditingTakeFocus = true; } - else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && DataEditingAddr < mem_size - Rows) { data_editing_addr_next = DataEditingAddr + Rows; DataEditingTakeFocus = true; } - else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && DataEditingAddr > 0) { data_editing_addr_next = DataEditingAddr - 1; DataEditingTakeFocus = true; } - else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && DataEditingAddr < mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; DataEditingTakeFocus = true; } - } - if (data_editing_addr_next != (size_t)-1 && (data_editing_addr_next / Rows) != (data_editing_addr_backup / Rows)) - { - // Track cursor movements - const int scroll_offset = ((int)(data_editing_addr_next / Rows) - (int)(data_editing_addr_backup / Rows)); - const bool scroll_desired = (scroll_offset < 0 && data_editing_addr_next < visible_start_addr + Rows * 2) || (scroll_offset > 0 && data_editing_addr_next > visible_end_addr - Rows * 2); - if (scroll_desired) - ImGui::SetScrollY(ImGui::GetScrollY() + scroll_offset * s.LineHeight); + if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; } + else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; } + else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; } + else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; } } // Draw vertical separator @@ -366,202 +388,240 @@ struct MemoryEditor const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text); const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text; - for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines - { - size_t addr = (size_t)(line_i * Rows); - ImGui::Text("%0*" _PRISizeT ": ", s.AddrDigitsCount, base_display_addr + addr); + const char* format_address = OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: "; + const char* format_data = OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x"; + const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x"; + const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x "; - // Draw Hexadecimal - for (int n = 0; n < Rows && addr < mem_size; n++, addr++) + while (clipper.Step()) + for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines { - float byte_pos_x = s.PosHexStart + s.HexCellWidth * n; - if (OptMidRowsCount > 0) - byte_pos_x += (n / OptMidRowsCount) * s.SpacingBetweenMidRows; - ImGui::SameLine(byte_pos_x); + size_t addr = (size_t)(line_i * Cols); + ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr); - // Draw highlight - if ((addr >= HighlightMin && addr < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr))) + // Draw Hexadecimal + for (int n = 0; n < Cols && addr < mem_size; n++, addr++) { - ImVec2 pos = ImGui::GetCursorScreenPos(); - float highlight_width = s.GlyphWidth * 2; - bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1))); - if (is_next_byte_highlighted || (n + 1 == Rows)) + float byte_pos_x = s.PosHexStart + s.HexCellWidth * n; + if (OptMidColsCount > 0) + byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols; + ImGui::SameLine(byte_pos_x); + + // Draw highlight + bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); + bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr)); + bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size); + if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) { - highlight_width = s.HexCellWidth; - if (OptMidRowsCount > 0 && n > 0 && (n + 1) < Rows && ((n + 1) % OptMidRowsCount) == 0) - highlight_width += s.SpacingBetweenMidRows; + ImVec2 pos = ImGui::GetCursorScreenPos(); + float highlight_width = s.GlyphWidth * 2; + bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1))); + if (is_next_byte_highlighted || (n + 1 == Cols)) + { + highlight_width = s.HexCellWidth; + if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0) + highlight_width += s.SpacingBetweenMidCols; + } + draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor); } - draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor); - } - if (DataEditingAddr == addr) - { - // Display text input on current byte - bool data_write = false; - ImGui::PushID((void*)addr); - if (DataEditingTakeFocus) - { - ImGui::SetKeyboardFocusHere(); - ImGui::CaptureKeyboardFromApp(true); - sprintf(AddrInputBuf, "%0*" _PRISizeT, s.AddrDigitsCount, base_display_addr + addr); - sprintf(DataInputBuf, "%02X", ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); - } - ImGui::PushItemWidth(s.GlyphWidth * 2); - struct UserData + if (DataEditingAddr == addr) { - // FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here. - static int Callback(ImGuiInputTextCallbackData* data) + // Display text input on current byte + bool data_write = false; + ImGui::PushID((void*)addr); + if (DataEditingTakeFocus) + { + ImGui::SetKeyboardFocusHere(0); + ImSnprintf(AddrInputBuf, sizeof(AddrInputBuf), format_data, s.AddrDigitsCount, base_display_addr + addr); + ImSnprintf(DataInputBuf, sizeof(DataInputBuf), format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); + } + struct UserData { - UserData* user_data = (UserData*)data->UserData; - if (!data->HasSelection()) - user_data->CursorPos = data->CursorPos; - if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) + // FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here. + static int Callback(ImGuiInputTextCallbackData* data) { - // When not editing a byte, always rewrite its content (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there) - data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, user_data->CurrentBufOverwrite); - data->SelectionStart = 0; - data->SelectionEnd = data->CursorPos = 2; + UserData* user_data = (UserData*)data->UserData; + if (!data->HasSelection()) + user_data->CursorPos = data->CursorPos; + if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) + { + // When not editing a byte, always refresh its InputText content pulled from underlying memory data + // (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there) + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, user_data->CurrentBufOverwrite); + data->SelectionStart = 0; + data->SelectionEnd = 2; + data->CursorPos = 0; + } + return 0; } - return 0; + char CurrentBufOverwrite[3]; // Input + int CursorPos; // Output + }; + UserData user_data; + user_data.CursorPos = -1; + ImSnprintf(user_data.CurrentBufOverwrite, sizeof(user_data.CurrentBufOverwrite), format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); + ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways; +#if IMGUI_VERSION_NUM >= 18104 + flags |= ImGuiInputTextFlags_AlwaysOverwrite; +#else + flags |= ImGuiInputTextFlags_AlwaysInsertMode; +#endif + ImGui::SetNextItemWidth(s.GlyphWidth * 2); + if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, UserData::Callback, &user_data)) + data_write = data_next = true; + else if (!DataEditingTakeFocus && !ImGui::IsItemActive()) + DataEditingAddr = data_editing_addr_next = (size_t)-1; + DataEditingTakeFocus = false; + if (user_data.CursorPos >= 2) + data_write = data_next = true; + if (data_editing_addr_next != (size_t)-1) + data_write = data_next = false; + unsigned int data_input_value = 0; + if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1) + { + if (WriteFn) + WriteFn(mem_data, addr, (ImU8)data_input_value); + else + mem_data[addr] = (ImU8)data_input_value; } - char CurrentBufOverwrite[3]; // Input - int CursorPos; // Output - }; - UserData user_data; - user_data.CursorPos = -1; - sprintf(user_data.CurrentBufOverwrite, "%02X", ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); - ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_AlwaysInsertMode | ImGuiInputTextFlags_CallbackAlways; - if (ImGui::InputText("##data", DataInputBuf, 32, flags, UserData::Callback, &user_data)) - data_write = data_next = true; - else if (!DataEditingTakeFocus && !ImGui::IsItemActive()) - DataEditingAddr = data_editing_addr_next = (size_t)-1; - DataEditingTakeFocus = false; - ImGui::PopItemWidth(); - if (user_data.CursorPos >= 2) - data_write = data_next = true; - if (data_editing_addr_next != (size_t)-1) - data_write = data_next = false; - int data_input_value; - if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1) - { - if (WriteFn) - WriteFn(mem_data, addr, (u8)data_input_value); - else - mem_data[addr] = (u8)data_input_value; - } - ImGui::PopID(); - } - else - { - // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. - u8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; - - if (OptShowHexII) - { - if ((b >= 32 && b < 128)) - ImGui::Text(".%c ", b); - else if (b == 0xFF && OptGreyOutZeroes) - ImGui::TextDisabled("## "); - else if (b == 0x00) - ImGui::Text(" "); - else - ImGui::Text("%02X ", b); + ImGui::PopID(); } else { - if (b == 0 && OptGreyOutZeroes) - ImGui::TextDisabled("00 "); + // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. + ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; + + if (OptShowHexII) + { + if ((b >= 32 && b < 128)) + ImGui::Text(".%c ", b); + else if (b == 0xFF && OptGreyOutZeroes) + ImGui::TextDisabled("## "); + else if (b == 0x00) + ImGui::Text(" "); + else + ImGui::Text(format_byte_space, b); + } else - ImGui::Text("%02X ", b); - } - if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseReleased(0)) - { - DataEditingTakeFocus = true; - data_editing_addr_next = addr; + { + if (b == 0 && OptGreyOutZeroes) + ImGui::TextDisabled("00 "); + else + ImGui::Text(format_byte_space, b); + } + if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) + { + DataEditingTakeFocus = true; + data_editing_addr_next = addr; + } } } - } - if (OptShowAscii) - { - // Draw ASCII values - ImGui::SameLine(s.PosAsciiStart); - ImVec2 pos = ImGui::GetCursorScreenPos(); - addr = line_i * Rows; - ImGui::PushID(line_i); - if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight))) - { - DataEditingAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth); - DataEditingTakeFocus = true; - } - ImGui::PopID(); - for (int n = 0; n < Rows && addr < mem_size; n++, addr++) + if (OptShowAscii) { - if (addr == DataEditingAddr) + // Draw ASCII values + ImGui::SameLine(s.PosAsciiStart); + ImVec2 pos = ImGui::GetCursorScreenPos(); + addr = line_i * Cols; + ImGui::PushID(line_i); + if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight))) { - draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); - draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); + DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth); + DataEditingTakeFocus = true; + } + ImGui::PopID(); + for (int n = 0; n < Cols && addr < mem_size; n++, addr++) + { + if (addr == DataEditingAddr) + { + draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); + draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); + } + unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; + char display_c = (c < 32 || c >= 128) ? '.' : c; + draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1); + pos.x += s.GlyphWidth; } - unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; - char display_c = (c < 32 || c >= 128) ? '.' : c; - draw_list->AddText(pos, (display_c == '.') ? color_disabled : color_text, &display_c, &display_c + 1); - pos.x += s.GlyphWidth; } } - } - clipper.End(); ImGui::PopStyleVar(2); ImGui::EndChild(); - if (data_next && DataEditingAddr < mem_size) + // Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child) + ImGui::SetCursorPosX(s.WindowWidth); + + if (data_next && DataEditingAddr + 1 < mem_size) { - DataEditingAddr = DataEditingAddr + 1; + DataEditingAddr = DataPreviewAddr = DataEditingAddr + 1; DataEditingTakeFocus = true; } else if (data_editing_addr_next != (size_t)-1) { - DataEditingAddr = data_editing_addr_next; + DataEditingAddr = DataPreviewAddr = data_editing_addr_next; + DataEditingTakeFocus = true; } - if (Options || AddrInput || (NumLayers > 1)) { + const bool lock_show_data_preview = OptShowDataPreview; + if (OptShowOptions) + { ImGui::Separator(); + DrawOptionsLine(s, mem_data, mem_size, base_display_addr); } + if (lock_show_data_preview) + { + ImGui::Separator(); + DrawPreviewLine(s, mem_data, mem_size, base_display_addr); + } + } + + void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr) + { + IM_UNUSED(mem_data); + ImGuiStyle& style = ImGui::GetStyle(); + const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x"; + // Options menu - if (Options) { - if (ImGui::Button("Options")) - ImGui::OpenPopup("context"); - if (ImGui::BeginPopup("context")) - { - ImGui::PushItemWidth(56); - if (ImGui::DragInt("##rows", &Rows, 0.2f, 4, 32, "%.0f rows")) ContentsWidthChanged = true; - ImGui::PopItemWidth(); - ImGui::Checkbox("Show HexII", &OptShowHexII); - if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) ContentsWidthChanged = true; - ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes); - ImGui::EndPopup(); - } - ImGui::SameLine(); + if (ImGui::Button("Options")) + ImGui::OpenPopup("context"); + if (ImGui::BeginPopup("context")) + { + ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f); + if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; } + ImGui::Checkbox("Show Data Preview", &OptShowDataPreview); + ImGui::Checkbox("Show HexII", &OptShowHexII); + if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) { ContentsWidthChanged = true; } + ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes); + ImGui::Checkbox("Uppercase Hex", &OptUpperCaseHex); + + ImGui::EndPopup(); } - if (AddrInput) { - ImGui::PushItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth + style.FramePadding.x * 2.0f); - if (ImGui::InputText("##addr", AddrInputBuf, 32, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) + /*--- BEGIN ui_memedit.h changes ---*/ + if (OptShowAddrInput) { + /*--- END ui_memedit.h changes ---*/ + ImGui::SameLine(); + ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1); + ImGui::SameLine(); + ImGui::SetNextItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth + style.FramePadding.x * 2.0f); + if (ImGui::InputText("##addr", AddrInputBuf, IM_ARRAYSIZE(AddrInputBuf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) { size_t goto_addr; - if (sscanf(AddrInputBuf, "%" _PRISizeT, &goto_addr) == 1) + if (sscanf(AddrInputBuf, "%" _PRISizeT "X", &goto_addr) == 1) { GotoAddr = goto_addr - base_display_addr; HighlightMin = HighlightMax = (size_t)-1; } } - ImGui::PopItemWidth(); - ImGui::SameLine(); + /*--- BEGIN ui_memedit.h changes ---*/ } + /*--- END ui_memedit.h changes ---*/ /*--- BEGIN ui_memedit.h changes */ if (NumLayers > 1) { + ImGui::SameLine(); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); ImGui::Combo("##layer", &CurLayer, Layers, NumLayers); ImGui::PopItemWidth(); @@ -573,23 +633,250 @@ struct MemoryEditor if (GotoAddr < mem_size) { ImGui::BeginChild("##scrolling"); - ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Rows) * ImGui::GetTextLineHeight()); + ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight()); ImGui::EndChild(); - DataEditingAddr = GotoAddr; + DataEditingAddr = DataPreviewAddr = GotoAddr; DataEditingTakeFocus = true; } GotoAddr = (size_t)-1; } + } - // Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child) - ImGui::SetCursorPosX(s.WindowWidth); + void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr) + { + IM_UNUSED(base_display_addr); + ImU8* mem_data = (ImU8*)mem_data_void; + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Preview as:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); + if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest)) + { + for (int n = 0; n < ImGuiDataType_COUNT; n++) + if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n)) + PreviewDataType = (ImGuiDataType)n; + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); + ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0"); + + char buf[128] = ""; + float x = s.GlyphWidth * 6.0f; + bool has_value = DataPreviewAddr != (size_t)-1; + if (has_value) + DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf)); + ImGui::Text("Dec"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); + if (has_value) + DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf)); + ImGui::Text("Hex"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); + if (has_value) + DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf)); + buf[IM_ARRAYSIZE(buf) - 1] = 0; + ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); + } + + // Utilities for Data Preview + const char* DataTypeGetDesc(ImGuiDataType data_type) const + { + const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" }; + IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); + return descs[data_type]; + } + + size_t DataTypeGetSize(ImGuiDataType data_type) const + { + const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) }; + IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); + return sizes[data_type]; + } + + const char* DataFormatGetDesc(DataFormat data_format) const + { + const char* descs[] = { "Bin", "Dec", "Hex" }; + IM_ASSERT(data_format >= 0 && data_format < DataFormat_COUNT); + return descs[data_format]; + } + + bool IsBigEndian() const + { + uint16_t x = 1; + char c[2]; + memcpy(c, &x, 2); + return c[0] != 0; + } + + static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian) + { + if (is_little_endian) + { + uint8_t* dst = (uint8_t*)_dst; + uint8_t* src = (uint8_t*)_src + s - 1; + for (int i = 0, n = (int)s; i < n; ++i) + memcpy(dst++, src--, 1); + return _dst; + } + else + { + return memcpy(_dst, _src, s); + } + } + + static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian) + { + if (is_little_endian) + { + return memcpy(_dst, _src, s); + } + else + { + uint8_t* dst = (uint8_t*)_dst; + uint8_t* src = (uint8_t*)_src + s - 1; + for (int i = 0, n = (int)s; i < n; ++i) + memcpy(dst++, src--, 1); + return _dst; + } + } + + void* EndianessCopy(void* dst, void* src, size_t size) const + { + static void* (*fp)(void*, void*, size_t, int) = NULL; + if (fp == NULL) + fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian; + return fp(dst, src, size, PreviewEndianess); + } + + const char* FormatBinary(const uint8_t* buf, int width) const + { + IM_ASSERT(width <= 64); + size_t out_n = 0; + static char out_buf[64 + 8 + 1]; + int n = width / 8; + for (int j = n - 1; j >= 0; --j) + { + for (int i = 0; i < 8; ++i) + out_buf[out_n++] = (buf[j] & (1 << (7 - i))) ? '1' : '0'; + out_buf[out_n++] = ' '; + } + IM_ASSERT(out_n < IM_ARRAYSIZE(out_buf)); + out_buf[out_n] = 0; + return out_buf; + } + + // [Internal] + void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const + { + uint8_t buf[8]; + size_t elem_size = DataTypeGetSize(data_type); + size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size; + if (ReadFn) + for (int i = 0, n = (int)size; i < n; ++i) + buf[i] = ReadFn(mem_data, addr + i); + else + memcpy(buf, mem_data + addr, size); + + if (data_format == DataFormat_Bin) + { + uint8_t binbuf[8]; + EndianessCopy(binbuf, buf, size); + ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8)); + return; + } + + out_buf[0] = 0; + switch (data_type) + { + case ImGuiDataType_S8: + { + int8_t int8 = 0; + EndianessCopy(&int8, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; } + break; + } + case ImGuiDataType_U8: + { + uint8_t uint8 = 0; + EndianessCopy(&uint8, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; } + break; + } + case ImGuiDataType_S16: + { + int16_t int16 = 0; + EndianessCopy(&int16, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; } + break; + } + case ImGuiDataType_U16: + { + uint16_t uint16 = 0; + EndianessCopy(&uint16, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; } + break; + } + case ImGuiDataType_S32: + { + int32_t int32 = 0; + EndianessCopy(&int32, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; } + break; + } + case ImGuiDataType_U32: + { + uint32_t uint32 = 0; + EndianessCopy(&uint32, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; } + break; + } + case ImGuiDataType_S64: + { + int64_t int64 = 0; + EndianessCopy(&int64, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; } + break; + } + case ImGuiDataType_U64: + { + uint64_t uint64 = 0; + EndianessCopy(&uint64, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; } + break; + } + case ImGuiDataType_Float: + { + float float32 = 0.0f; + EndianessCopy(&float32, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; } + break; + } + case ImGuiDataType_Double: + { + double float64 = 0.0; + EndianessCopy(&float64, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; } + break; + } + case ImGuiDataType_COUNT: + break; + } // Switch + IM_ASSERT(0); // Shouldn't reach } }; -#undef _PRISizeT /*== end of imgui_memory_editor.h ============================================*/ /* ImGui MemoryEditor read/write callbacks */ -static uint8_t _ui_memedit_readfn(uint8_t* ptr, size_t off) { +static uint8_t _ui_memedit_readfn(const uint8_t* ptr, size_t off) { /* we'll treat the "data ptr" as "user data" */ const ui_memedit_t* win = (ui_memedit_t*) ptr; CHIPS_ASSERT(win && win->ed); @@ -623,11 +910,12 @@ void ui_memedit_init(ui_memedit_t* win, const ui_memedit_desc_t* desc) { win->init_y = (float) desc->y; win->init_w = (float) ((desc->w == 0) ? 512 : desc->w); win->init_h = (float) ((desc->h == 0) ? 120 : desc->h); + win->max_addr = (desc->max_addr == 0) ? (1<<16) : desc->max_addr; win->open = desc->open; win->ed = new MemoryEditor; - win->ed->Rows = (desc->num_rows == 0) ? win->ed->Rows : desc->num_rows; - win->ed->Options = !desc->hide_options; - win->ed->AddrInput = !desc->hide_addr_input; + win->ed->Cols = (desc->num_cols == 0) ? win->ed->Cols : desc->num_cols; + win->ed->OptShowOptions = !desc->hide_options; + win->ed->OptShowAddrInput = !desc->hide_addr_input; win->ed->OptShowAscii = !desc->hide_ascii; win->ed->Open = win->open; win->ed->ReadFn = _ui_memedit_readfn; @@ -659,13 +947,13 @@ void ui_memedit_draw(ui_memedit_t* win) { } ImGui::SetNextWindowPos(ImVec2(win->init_x, win->init_y), ImGuiCond_Once); ImGui::SetNextWindowSize(ImVec2(win->init_w, win->init_h), ImGuiCond_Once); - win->ed->DrawWindow(win->title, (uint8_t*)win, (1<<16)); + win->ed->DrawWindow(win->title, (uint8_t*)win, win->max_addr); win->open = win->ed->Open; } void ui_memedit_draw_content(ui_memedit_t* win) { CHIPS_ASSERT(win && win->valid); - win->ed->DrawContents((uint8_t*)win, (1<<16)); + win->ed->DrawContents((uint8_t*)win, win->max_addr); } #ifdef _MSC_VER #pragma warning(pop) diff --git a/ui/ui_namco.h b/ui/ui_namco.h index 85a8395e..9725403b 100644 --- a/ui/ui_namco.h +++ b/ui/ui_namco.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -34,6 +34,7 @@ - ui_dbg.h - ui_memedit.h - ui_memmap.h + - ui_snapshot.h ## zlib/libpng license @@ -51,7 +52,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -62,10 +63,9 @@ extern "C" { typedef struct { namco_t* sys; - ui_dbg_create_texture_t create_texture_cb; // texture creation callback for ui_dbg_t - ui_dbg_update_texture_t update_texture_cb; // texture update callback for ui_dbg_t - ui_dbg_destroy_texture_t destroy_texture_cb; // texture destruction callback for ui_dbg_t + ui_dbg_texture_callbacks_t dbg_texture; // user-provided texture create/update/destroy callbacks ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; // snapshot system creation params } ui_namco_desc_t; typedef struct { @@ -76,12 +76,13 @@ typedef struct { ui_memmap_t memmap; ui_memedit_t memedit[4]; ui_dasm_t dasm[4]; + ui_snapshot_t snapshot; } ui_namco_t; void ui_namco_init(ui_namco_t* ui, const ui_namco_desc_t* desc); void ui_namco_discard(ui_namco_t* ui); void ui_namco_draw(ui_namco_t* ui); -namco_debug_t ui_namco_get_debug(ui_namco_t* ui); +chips_debug_t ui_namco_get_debug(ui_namco_t* ui); #ifdef __cplusplus } /* extern "C" */ @@ -200,6 +201,7 @@ void ui_namco_init(ui_namco_t* ui, const ui_namco_desc_t* ui_desc) { CHIPS_ASSERT(ui_desc->sys); memset(ui, 0, sizeof(ui_namco_t)); ui->sys = ui_desc->sys; + ui_snapshot_init(&ui->snapshot, &ui_desc->snapshot); int x = 20, y = 20, dx = 10, dy = 10; { ui_dbg_desc_t desc = {0}; @@ -209,9 +211,7 @@ void ui_namco_init(ui_namco_t* ui, const ui_namco_desc_t* ui_desc) { desc.z80 = &ui->sys->cpu; desc.read_cb = _ui_namco_mem_read; desc.read_layer = _UI_NAMCO_MEMLAYER_MAIN; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; desc.keys = ui_desc->dbg_keys; desc.user_data = ui; ui_dbg_init(&ui->dbg, &desc); @@ -301,6 +301,7 @@ void ui_namco_discard(ui_namco_t* ui) { static void _ui_namco_draw_menu(ui_namco_t* ui) { if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->snapshot); if (ImGui::MenuItem("Reboot")) { namco_reset(ui->sys); ui_dbg_reboot(&ui->dbg); @@ -316,6 +317,7 @@ static void _ui_namco_draw_menu(ui_namco_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -352,9 +354,9 @@ void ui_namco_draw(ui_namco_t* ui) { } } -namco_debug_t ui_namco_get_debug(ui_namco_t* ui) { - namco_debug_t res = {}; - res.callback.func = (namco_debug_func_t)ui_dbg_tick; +chips_debug_t ui_namco_get_debug(ui_namco_t* ui) { + chips_debug_t res = {}; + res.callback.func = (chips_debug_func_t)ui_dbg_tick; res.callback.user_data = &ui->dbg; res.stopped = &ui->dbg.dbg.stopped; return res; diff --git a/ui/ui_snapshot.h b/ui/ui_snapshot.h new file mode 100644 index 00000000..d409b256 --- /dev/null +++ b/ui/ui_snapshot.h @@ -0,0 +1,207 @@ +#pragma once +/*# + # ui_snapshots.h + + Snapshot UI helpers. + + Do this: + ~~~C + #define CHIPS_UI_IMPL + ~~~ + before you include this file in *one* C++ file to create the + implementation. + + Optionally provide the following macros with your own implementation + + ~~~C + CHIPS_ASSERT(c) + ~~~ + your own assert macro (default: assert(c)) + + You need to include the following headers before including the + *implementation*: + + - imgui.h + + ## zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. +#*/ +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_SNAPSHOT_MAX_SLOTS (8) + +// callback function to save snapshot to a numbered slot +typedef void (*ui_snapshot_save_t)(size_t slot_index); +// callback function to load snapshot from numbered slot +typedef bool (*ui_snapshot_load_t)(size_t slot_index); + +// a snapshot screenshot wrapper struct +typedef struct { + void* texture; + bool portrait; +} ui_snapshot_screenshot_t; + +// a snapshot slot +typedef struct { + bool valid; + ui_snapshot_screenshot_t screenshot; +} ui_snapshot_slot_t; + +// initialization parameters +typedef struct { + ui_snapshot_save_t save_cb; + ui_snapshot_load_t load_cb; + ui_snapshot_screenshot_t empty_slot_screenshot; +} ui_snapshot_desc_t; + +// snapshot system state +typedef struct { + ui_snapshot_save_t save_cb; + ui_snapshot_load_t load_cb; + ui_snapshot_slot_t slots[UI_SNAPSHOT_MAX_SLOTS]; +} ui_snapshot_t; + +// initialize the snapshot instance +void ui_snapshot_init(ui_snapshot_t* state, const ui_snapshot_desc_t* desc); +// inject snap menu UI +void ui_snapshot_menus(ui_snapshot_t* state); +// called from UI when a snapshot should be saved +void ui_snapshot_save_slot(ui_snapshot_t* state, size_t slot_index); +// called from UI when a snapshot should be loaded +bool ui_snapshot_load_slot(ui_snapshot_t* state, size_t slot_index); +// update snapshot info, returns previous slot info (usually called from within save callback) +ui_snapshot_screenshot_t ui_snapshot_set_screenshot(ui_snapshot_t* state, size_t slot_index, ui_snapshot_screenshot_t screenshot); + +#ifdef __cplusplus +} // extern "C" +#endif + +//-- IMPLEMENTATION ------------------------------------------------------------ +#ifdef CHIPS_UI_IMPL +#include +#ifndef CHIPS_ASSERT + #include + #define CHIPS_ASSERT(c) assert(c) +#endif + +void ui_snapshot_init(ui_snapshot_t* state, const ui_snapshot_desc_t* desc) { + CHIPS_ASSERT(state && desc); + CHIPS_ASSERT(desc->save_cb && desc->load_cb); + memset(state, 0, sizeof(ui_snapshot_t)); + state->save_cb = desc->save_cb; + state->load_cb = desc->load_cb; + for (size_t i = 0; i < UI_SNAPSHOT_MAX_SLOTS; i++) { + state->slots[i].screenshot = desc->empty_slot_screenshot; + } +} + +static bool ui_snapshot_draw_menu_slot(const char* sel_id, ui_snapshot_screenshot_t screenshot) { + const ImVec2 pos = ImGui::GetCursorPos(); + const ImVec2 size = screenshot.portrait ? ImVec2{ 96.0f, 128.0f } : ImVec2{ 128.0f, 96.0f }; + bool pressed = ImGui::Selectable(sel_id, false, 0, size); + ImGui::SetCursorPos(pos); + ImGui::Image(screenshot.texture, size); + return pressed; +} + +void ui_snapshot_menus(ui_snapshot_t* state) { + CHIPS_ASSERT(state); + if (ImGui::BeginMenu("Save Snapshot")) { + for (size_t slot_index = 0; slot_index < UI_SNAPSHOT_MAX_SLOTS; slot_index++) { + const ui_snapshot_screenshot_t screenshot = state->slots[slot_index].screenshot; + ImGui::PushID((int)slot_index); + if (screenshot.texture) { + if (ui_snapshot_draw_menu_slot("##savesnapshot", screenshot)) { + ui_snapshot_save_slot(state, slot_index); + } + if ((slot_index + 1) & 1) { + ImGui::SameLine(); + } + } + else { + char buf[128]; + snprintf(buf, sizeof(buf), "%sSlot %d\n", state->slots[slot_index].valid ? "* ":"", (int)slot_index); + if (ImGui::MenuItem(buf)) { + ui_snapshot_save_slot(state, slot_index); + } + } + ImGui::PopID(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Load Snapshot")) { + for (size_t slot_index = 0; slot_index < UI_SNAPSHOT_MAX_SLOTS; slot_index++) { + if (state->slots[slot_index].valid) { + const ui_snapshot_screenshot_t screenshot = state->slots[slot_index].screenshot; + ImGui::PushID((int)slot_index); + if (screenshot.texture) { + if (ui_snapshot_draw_menu_slot("##loadsnapshot", screenshot)) { + ui_snapshot_load_slot(state, slot_index); + } + if ((slot_index + 1) & 1) { + ImGui::SameLine(); + } + } + else { + char buf[128]; + snprintf(buf, sizeof(buf), "Slot %d\n", (int)slot_index); + if (ImGui::MenuItem(buf)) { + ui_snapshot_load_slot(state, slot_index); + } + } + ImGui::PopID(); + } + } + ImGui::EndMenu(); + } +} + +void ui_snapshot_save_slot(ui_snapshot_t* state, size_t slot_index) { + CHIPS_ASSERT(slot_index < UI_SNAPSHOT_MAX_SLOTS); + state->save_cb(slot_index); +} + +bool ui_snapshot_load_slot(ui_snapshot_t* state, size_t slot_index) { + CHIPS_ASSERT(state); + CHIPS_ASSERT(slot_index < UI_SNAPSHOT_MAX_SLOTS); + if (state->slots[slot_index].valid) { + return state->load_cb(slot_index); + } + else { + return false; + } +} + +ui_snapshot_screenshot_t ui_snapshot_set_screenshot(ui_snapshot_t* state, size_t slot_index, ui_snapshot_screenshot_t screenshot) { + CHIPS_ASSERT(state && screenshot.texture); + CHIPS_ASSERT(slot_index < UI_SNAPSHOT_MAX_SLOTS); + ui_snapshot_screenshot_t prev_screenshot = {}; + if (state->slots[slot_index].valid) { + prev_screenshot = state->slots[slot_index].screenshot; + } + state->slots[slot_index].valid = true; + state->slots[slot_index].screenshot = screenshot; + return prev_screenshot; +} +#endif diff --git a/ui/ui_vic20.h b/ui/ui_vic20.h index 94e67d31..3bd8137f 100644 --- a/ui/ui_vic20.h +++ b/ui/ui_vic20.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -21,6 +21,7 @@ Include the following headers (and their depenencies) before including ui_c64.h both for the declaration and implementation. + - chips_common.h - vic20.h - c1530.h - mem.h @@ -36,6 +37,7 @@ - ui_memedit.h - ui_memmap.h - ui_kbd.h + - ui_snapshot.h ## zlib/libpng license @@ -53,7 +55,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -69,10 +71,9 @@ typedef void (*ui_vic20_boot_cb)(vic20_t* sys); typedef struct { vic20_t* vic20; // pointer to vic20_t instance to track ui_vic20_boot_cb boot_cb; // reboot callback function - ui_dbg_create_texture_t create_texture_cb; // texture creation callback for ui_dbg_t - ui_dbg_update_texture_t update_texture_cb; // texture update callback for ui_dbg_t - ui_dbg_destroy_texture_t destroy_texture_cb; // texture destruction callback for ui_dbg_t + ui_dbg_texture_callbacks_t dbg_texture; // user-provided texture create/update/destroy callbacks ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; // snapshot ui setup params } ui_vic20_desc_t; typedef struct { @@ -89,13 +90,14 @@ typedef struct { ui_memedit_t memedit[4]; ui_dasm_t dasm[4]; ui_dbg_t dbg; + ui_snapshot_t snapshot; bool system_window_open; } ui_vic20_t; void ui_vic20_init(ui_vic20_t* ui, const ui_vic20_desc_t* desc); void ui_vic20_discard(ui_vic20_t* ui); void ui_vic20_draw(ui_vic20_t* ui); -vic20_debug_t ui_vic20_get_debug(ui_vic20_t* ui); +chips_debug_t ui_vic20_get_debug(ui_vic20_t* ui); #ifdef __cplusplus } // extern "C" @@ -120,6 +122,7 @@ static void _ui_vic20_draw_menu(ui_vic20_t* ui) { CHIPS_ASSERT(ui && ui->vic20 && ui->boot_cb); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->snapshot); if (ImGui::MenuItem("Reset")) { vic20_reset(ui->vic20); ui_dbg_reset(&ui->dbg); @@ -160,6 +163,7 @@ static void _ui_vic20_draw_menu(ui_vic20_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -389,6 +393,7 @@ void ui_vic20_init(ui_vic20_t* ui, const ui_vic20_desc_t* ui_desc) { CHIPS_ASSERT(ui_desc->boot_cb); ui->vic20 = ui_desc->vic20; ui->boot_cb = ui_desc->boot_cb; + ui_snapshot_init(&ui->snapshot, &ui_desc->snapshot); int x = 20, y = 20, dx = 10, dy = 10; { ui_dbg_desc_t desc = {0}; @@ -398,9 +403,7 @@ void ui_vic20_init(ui_vic20_t* ui, const ui_vic20_desc_t* ui_desc) { desc.m6502 = &ui->vic20->cpu; desc.read_cb = _ui_vic20_mem_read; desc.break_cb = _ui_vic20_eval_bp; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; desc.keys = ui_desc->dbg_keys; desc.user_data = ui; /* custom breakpoint types */ @@ -602,9 +605,9 @@ void ui_vic20_draw(ui_vic20_t* ui) { ui_dbg_draw(&ui->dbg); } -vic20_debug_t ui_vic20_get_debug(ui_vic20_t* ui) { - vic20_debug_t res = {}; - res.callback.func = (vic20_debug_func_t)ui_dbg_tick; +chips_debug_t ui_vic20_get_debug(ui_vic20_t* ui) { + chips_debug_t res = {}; + res.callback.func = (chips_debug_func_t)ui_dbg_tick; res.callback.user_data = &ui->dbg; res.stopped = &ui->dbg.dbg.stopped; return res; @@ -614,6 +617,3 @@ vic20_debug_t ui_vic20_get_debug(ui_vic20_t* ui) { #pragma clang diagnostic pop #endif #endif /* CHIPS_UI_IMPL */ - - - diff --git a/ui/ui_z1013.h b/ui/ui_z1013.h index b818b389..28991ad5 100644 --- a/ui/ui_z1013.h +++ b/ui/ui_z1013.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -31,6 +31,7 @@ - ui_dasm.h - ui_memedit.h - ui_memmap.h + - ui_snapshot.h ## zlib/libpng license @@ -48,7 +49,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -57,16 +58,15 @@ extern "C" { #endif -/* general callback type for rebooting to different configs */ +// general callback type for rebooting to different configs typedef void (*ui_z1013_boot_t)(z1013_t* sys, z1013_type_t type); typedef struct { z1013_t* z1013; - ui_z1013_boot_t boot_cb; /* user-provided callback to reboot to different config */ - ui_dbg_create_texture_t create_texture_cb; /* texture creation callback for ui_dbg_t */ - ui_dbg_update_texture_t update_texture_cb; /* texture update callback for ui_dbg_t */ - ui_dbg_destroy_texture_t destroy_texture_cb; /* texture destruction callback for ui_dbg_t */ - ui_dbg_keys_desc_t dbg_keys; /* user-defined hotkeys for ui_dbg_t */ + ui_z1013_boot_t boot_cb; // user-provided callback to reboot to different config + ui_dbg_texture_callbacks_t dbg_texture; // user-provided texture create/update/destroy callbacks + ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; // snapshot system creation params } ui_z1013_desc_t; typedef struct { @@ -78,12 +78,13 @@ typedef struct { ui_memedit_t memedit[4]; ui_dasm_t dasm[4]; ui_dbg_t dbg; + ui_snapshot_t snapshot; } ui_z1013_t; void ui_z1013_init(ui_z1013_t* ui, const ui_z1013_desc_t* desc); void ui_z1013_discard(ui_z1013_t* ui); void ui_z1013_draw(ui_z1013_t* ui); -z1013_debug_t ui_z1013_get_debug(ui_z1013_t* ui); +chips_debug_t ui_z1013_get_debug(ui_z1013_t* ui); #ifdef __cplusplus } /* extern "C" */ @@ -108,6 +109,7 @@ static void _ui_z1013_draw_menu(ui_z1013_t* ui) { CHIPS_ASSERT(ui && ui->z1013 && ui->boot_cb); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->snapshot); if (ImGui::MenuItem("Reset")) { z1013_reset(ui->z1013); ui_dbg_reset(&ui->dbg); @@ -135,6 +137,7 @@ static void _ui_z1013_draw_menu(ui_z1013_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -270,9 +273,9 @@ void ui_z1013_init(ui_z1013_t* ui, const ui_z1013_desc_t* ui_desc) { CHIPS_ASSERT(ui && ui_desc); CHIPS_ASSERT(ui_desc->z1013); CHIPS_ASSERT(ui_desc->boot_cb); - CHIPS_ASSERT(ui_desc->create_texture_cb && ui_desc->update_texture_cb && ui_desc->destroy_texture_cb); ui->z1013 = ui_desc->z1013; ui->boot_cb = ui_desc->boot_cb; + ui_snapshot_init(&ui->snapshot, &ui_desc->snapshot); int x = 20, y = 20, dx = 10, dy = 10; { ui_dbg_desc_t desc = {0}; @@ -281,9 +284,7 @@ void ui_z1013_init(ui_z1013_t* ui, const ui_z1013_desc_t* ui_desc) { desc.y = y; desc.z80 = &ui->z1013->cpu; desc.read_cb = _ui_z1013_mem_read; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; desc.keys = ui_desc->dbg_keys; desc.user_data = ui->z1013; ui_dbg_init(&ui->dbg, &desc); @@ -374,9 +375,9 @@ void ui_z1013_draw(ui_z1013_t* ui) { ui_dbg_draw(&ui->dbg); } -z1013_debug_t ui_z1013_get_debug(ui_z1013_t* ui) { - z1013_debug_t res = {}; - res.callback.func = (z1013_debug_func_t)ui_dbg_tick; +chips_debug_t ui_z1013_get_debug(ui_z1013_t* ui) { + chips_debug_t res = {}; + res.callback.func = (chips_debug_func_t)ui_dbg_tick; res.callback.user_data = &ui->dbg; res.stopped = &ui->dbg.dbg.stopped; return res; diff --git a/ui/ui_z80.h b/ui/ui_z80.h index d24e28c2..ab8787d1 100644 --- a/ui/ui_z80.h +++ b/ui/ui_z80.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -47,7 +47,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -134,9 +134,9 @@ static void _ui_z80_regs(ui_z80_t* win) { char f_str[9] = { (f & Z80_SF) ? 'S':'-', (f & Z80_ZF) ? 'Z':'-', - (f & Z80_YF) ? 'X':'-', + (f & Z80_YF) ? 'Y':'-', (f & Z80_HF) ? 'H':'-', - (f & Z80_XF) ? 'Y':'-', + (f & Z80_XF) ? 'X':'-', (f & Z80_VF) ? 'V':'-', (f & Z80_NF) ? 'N':'-', (f & Z80_CF) ? 'C':'-', diff --git a/ui/ui_z9001.h b/ui/ui_z9001.h index 9f09f466..c339323f 100644 --- a/ui/ui_z9001.h +++ b/ui/ui_z9001.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -34,6 +34,7 @@ - ui_memedit.h - ui_memmap.h - ui_kbd.h + - ui_snapshot.h ## zlib/libpng license @@ -51,7 +52,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -65,11 +66,10 @@ typedef void (*ui_z9001_boot_t)(z9001_t* sys, z9001_type_t type); typedef struct { z9001_t* z9001; - ui_z9001_boot_t boot_cb; /* user-provided callback to reboot to different config */ - ui_dbg_create_texture_t create_texture_cb; /* texture creation callback for ui_dbg_t */ - ui_dbg_update_texture_t update_texture_cb; /* texture update callback for ui_dbg_t */ - ui_dbg_destroy_texture_t destroy_texture_cb; /* texture destruction callback for ui_dbg_t */ - ui_dbg_keys_desc_t dbg_keys; /* user-defined hotkeys for ui_dbg_t */ + ui_z9001_boot_t boot_cb; // user-provided callback to reboot to different config + ui_dbg_texture_callbacks_t dbg_texture; // user-provided texture create/update/destroy callbacks + ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; // snapshot system creation params } ui_z9001_desc_t; typedef struct { @@ -84,12 +84,13 @@ typedef struct { ui_memedit_t memedit[4]; ui_dasm_t dasm[4]; ui_dbg_t dbg; + ui_snapshot_t snapshot; } ui_z9001_t; void ui_z9001_init(ui_z9001_t* ui, const ui_z9001_desc_t* desc); void ui_z9001_discard(ui_z9001_t* ui); void ui_z9001_draw(ui_z9001_t* ui); -z9001_debug_t ui_z9001_get_debug(ui_z9001_t* ui); +chips_debug_t ui_z9001_get_debug(ui_z9001_t* ui); #ifdef __cplusplus } /* extern "C" */ @@ -114,6 +115,7 @@ static void _ui_z9001_draw_menu(ui_z9001_t* ui) { CHIPS_ASSERT(ui && ui->z9001 && ui->boot_cb); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->snapshot); if (ImGui::MenuItem("Reset")) { z9001_reset(ui->z9001); ui_dbg_reset(&ui->dbg); @@ -141,6 +143,7 @@ static void _ui_z9001_draw_menu(ui_z9001_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -310,6 +313,7 @@ void ui_z9001_init(ui_z9001_t* ui, const ui_z9001_desc_t* ui_desc) { CHIPS_ASSERT(ui_desc->boot_cb); ui->z9001 = ui_desc->z9001; ui->boot_cb = ui_desc->boot_cb; + ui_snapshot_init(&ui->snapshot, &ui_desc->snapshot); int x = 20, y = 20, dx = 10, dy = 10; { ui_dbg_desc_t desc = {0}; @@ -318,9 +322,7 @@ void ui_z9001_init(ui_z9001_t* ui, const ui_z9001_desc_t* ui_desc) { desc.y = y; desc.z80 = &ui->z9001->cpu; desc.read_cb = _ui_z9001_mem_read; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; desc.keys = ui_desc->dbg_keys; desc.user_data = ui->z9001; ui_dbg_init(&ui->dbg, &desc); @@ -455,9 +457,9 @@ void ui_z9001_draw(ui_z9001_t* ui) { ui_dbg_draw(&ui->dbg); } -z9001_debug_t ui_z9001_get_debug(ui_z9001_t* ui) { - z9001_debug_t res; - res.callback.func = (z9001_debug_func_t)ui_dbg_tick; +chips_debug_t ui_z9001_get_debug(ui_z9001_t* ui) { + chips_debug_t res = {}; + res.callback.func = (chips_debug_func_t)ui_dbg_tick; res.callback.user_data = &ui->dbg; res.stopped = &ui->dbg.dbg.stopped; return res; diff --git a/ui/ui_zx.h b/ui/ui_zx.h index 6a993113..e19bb590 100644 --- a/ui/ui_zx.h +++ b/ui/ui_zx.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -50,7 +50,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -59,16 +59,15 @@ extern "C" { #endif -/* general callback type for rebooting to different configs */ +// general callback type for rebooting to different configs typedef void (*ui_zx_boot_t)(zx_t* sys, zx_type_t type); typedef struct { zx_t* zx; - ui_zx_boot_t boot_cb; /* user-provided callback to reboot to different config */ - ui_dbg_create_texture_t create_texture_cb; /* texture creation callback for ui_dbg_t */ - ui_dbg_update_texture_t update_texture_cb; /* texture update callback for ui_dbg_t */ - ui_dbg_destroy_texture_t destroy_texture_cb; /* texture destruction callback for ui_dbg_t */ - ui_dbg_keys_desc_t dbg_keys; /* user-defined hotkeys for ui_dbg_t */ + ui_zx_boot_t boot_cb; // user-provided callback to reboot to different config + ui_dbg_texture_callbacks_t dbg_texture; // user-provided texture create/update/destroy callbacks + ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t + ui_snapshot_desc_t snapshot; // snapshot system creation params } ui_zx_desc_t; typedef struct { @@ -82,12 +81,13 @@ typedef struct { ui_memedit_t memedit[4]; ui_dasm_t dasm[4]; ui_dbg_t dbg; + ui_snapshot_t snapshot; } ui_zx_t; void ui_zx_init(ui_zx_t* ui, const ui_zx_desc_t* desc); void ui_zx_discard(ui_zx_t* ui); void ui_zx_draw(ui_zx_t* ui); -zx_debug_t ui_zx_get_debug(ui_zx_t* ui); +chips_debug_t ui_zx_get_debug(ui_zx_t* ui); #ifdef __cplusplus } /* extern "C" */ @@ -112,6 +112,7 @@ static void _ui_zx_draw_menu(ui_zx_t* ui) { CHIPS_ASSERT(ui && ui->zx && ui->boot_cb); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("System")) { + ui_snapshot_menus(&ui->snapshot); if (ImGui::MenuItem("Reset")) { zx_reset(ui->zx); ui_dbg_reset(&ui->dbg); @@ -157,6 +158,7 @@ static void _ui_zx_draw_menu(ui_zx_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -178,7 +180,7 @@ static void _ui_zx_draw_menu(ui_zx_t* ui) { ui_util_options_menu(); ImGui::EndMainMenuBar(); } - + } static void _ui_zx_update_memmap(ui_zx_t* ui) { @@ -344,6 +346,7 @@ void ui_zx_init(ui_zx_t* ui, const ui_zx_desc_t* ui_desc) { CHIPS_ASSERT(ui_desc->boot_cb); ui->zx = ui_desc->zx; ui->boot_cb = ui_desc->boot_cb; + ui_snapshot_init(&ui->snapshot, &ui_desc->snapshot); int x = 20, y = 20, dx = 10, dy = 10; { ui_dbg_desc_t desc = {0}; @@ -352,9 +355,7 @@ void ui_zx_init(ui_zx_t* ui, const ui_zx_desc_t* ui_desc) { desc.y = y; desc.z80 = &ui->zx->cpu; desc.read_cb = _ui_zx_mem_read; - desc.create_texture_cb = ui_desc->create_texture_cb; - desc.update_texture_cb = ui_desc->update_texture_cb; - desc.destroy_texture_cb = ui_desc->destroy_texture_cb; + desc.texture_cbs = ui_desc->dbg_texture; desc.keys = ui_desc->dbg_keys; desc.user_data = ui->zx; ui_dbg_init(&ui->dbg, &desc); @@ -486,9 +487,9 @@ void ui_zx_draw(ui_zx_t* ui) { ui_dbg_draw(&ui->dbg); } -zx_debug_t ui_zx_get_debug(ui_zx_t* ui) { - zx_debug_t res = {}; - res.callback.func = (zx_debug_func_t)ui_dbg_tick; +chips_debug_t ui_zx_get_debug(ui_zx_t* ui) { + chips_debug_t res = {}; + res.callback.func = (chips_debug_func_t)ui_dbg_tick; res.callback.user_data = &ui->dbg; res.stopped = &ui->dbg.dbg.stopped; return res;