Compare commits

...
289 Commits
Author SHA1 Message Date
Christopher Bonhage 41d1b7e57d Fix Controller "Nothing" checked state in portMenu 2025-08-26 09:40:01 +01:00
invertego c28ba81684 Revert "nall: clean up string conversions in cue/mmi decode"
This reverts commit 81dea4cf7e.
2025-08-26 09:39:42 +01:00
invertego 1561a1d0b4 Revert "nall: remove memory ownership from string_view"
This reverts commit 70fa70fff4.
2025-08-26 09:39:42 +01:00
jcm 0a302dcf0e desktop-ui: Create program guard for system menu items 2025-08-23 20:25:26 +01:00
TascoDLX 07051ab738 desktop-ui: avoid overwriting paused status message 2025-08-23 19:39:31 +01:00
jcm 973030ab67 ares: Set up color settings before calling colors() 2025-08-23 19:39:00 +01:00
jcm 67716bcedf hiro/cocoa: Don't autoenable menu items 2025-08-22 12:40:07 +01:00
Giovanni Bajo e06b748516 n64: activate pak menu on all four controllers 2025-08-22 11:16:13 +01:00
remutro 02dac5b4b7 Update controller pak menues for the N64 2025-08-22 08:30:17 +01:00
jcm a4b8571b61 desktop-ui: remove Apply buttons from driver settings 2025-08-21 14:25:51 +01:00
Roger Sanders a3fa22b5fc ld: Revert palette nullptr check 2025-08-21 14:25:45 +01:00
Roger Sanders 9e9b6f8642 ld: Apply a pixel offset patch to fix alignment under H40 mode 2025-08-21 14:25:45 +01:00
Roger Sanders 5f6a0983bd ld: Fixed some stop point resume and image blanking behaviour 2025-08-21 14:25:45 +01:00
Roger Sanders 2fcab5ca5f ld: Fix overscan mode, and address some redraw issues. 2025-08-21 14:25:45 +01:00
jcm 778a32700d screen: consistently refresh palette after reset() 2025-08-20 12:33:43 +01:00
Luke Usher a6577318b6 md: bump save state version 2025-08-19 13:57:24 +01:00
Roger Sanders badd476d3b ld: Fixed a double buffering issue which caused single-frame errors 2025-08-19 13:56:36 +01:00
Roger Sanders 5a3548402b ld: Fixed several aspects of video disable and stop point behaviour 2025-08-18 15:42:20 +01:00
User c1f0307a8d gba: allow running DMA during CPU idle cycles 2025-08-17 22:30:04 +01:00
Roger Sanders 77c0718df4 ld: Convert video composition to use 16.16 fixed point for a 3x speedup 2025-08-17 16:00:42 +01:00
Roger Sanders 30bff8dd06 ld: Cleaned up version of video compositing prior to optimization 2025-08-17 16:00:42 +01:00
Roger Sanders b1cf9bf4e5 ld: Added support for remaining analog audio mixing features 2025-08-17 07:52:41 +01:00
Roger Sanders bafc2b6ef4 ld: Fixed a potential out of bounds array index issue in the previous commit 2025-08-16 06:31:39 +01:00
Roger Sanders 565591568f ld: Fixed a few disc side changing issues 2025-08-16 06:31:39 +01:00
Roger Sanders 9df891606f ld: Removed region blocks for now since titles appear region free 2025-08-16 00:43:31 +01:00
Roger Sanders eea666317b ld: Fixed incorrect alignment of CLV seek operations. Improved media detection. 2025-08-16 00:43:31 +01:00
Luke Usher ceff1d47a6 ld: preliminary support for multi-disc/multi-side titles
Mostly untested, but verified going from "Disc 1 side 1" to "No Disc" to "Disc 1 Side 1" again in the bios works as expected.
2025-08-15 12:58:47 +01:00
invertego 81dea4cf7e nall: clean up string conversions in cue/mmi decode
Now that the usage issues with string_view have been resolved it can be
safely used to wrap a vector<u8>. I'll note that passing a vector<u8>
directly to the string constructor would work as well (thanks to a
stringify overload) but I'm preserving the wishes of the author of the
MegaLD code to keep the type conversions explicit.

I'll also note that we're still constructing a string from the
string_view when we call JSON::unserialize(). Ideally, it would take a
string_view instead of a const string& and we could avoid the copy.
Unfortunately, all of the markup decoders currently require the input to
be null-terminated, which is only guaranteed by the string class.
2025-08-14 08:56:08 +01:00
jcmandkev4cards 17fa3b58a6 desktop-ui: Fix hotkey behavior when unfocused
Co-authored-by: kev4cards <[email protected]>
2025-08-14 07:46:11 +01:00
invertego 70fa70fff4 nall: remove memory ownership from string_view
Previously, string_view had a constuctor that allocated a new string and
forwarded all arguments to the string constructor. Among other things,
this allowed construction of string_view objects using any of the
various stringify overloads. However, there were drawbacks:

1.  The constructor string_view(const char*, u32) shadowed the
    forwarding constructor, leading to very different behavior. In the
    forwarding version, the first parameter would be treated as a
    null-terminated string, and the second parameter would be formatted
    as a string and concatenated with the first.
2.  Having both owning and non-owning modes makes it difficult for
    someone reading the code to reason about lifetimes, as they depend
    on how the object was constructed. It also deviates from the
    behavior of the identically-named standard C++ string_view.
2025-08-14 07:45:14 +01:00
Roger Sanders 137afe5b5b ld: Fix memory issues with MegaLD games after merge. Some additional cleanup. 2025-08-14 07:44:37 +01:00
Luke Usher 545790f048 Revert "nall: in priority-queue, call event callbacks with correct timing"
This reverts commit 6992197fb8.
2025-08-13 17:34:12 +01:00
Luke Usher 807ef03005 Revert "n64: improve PI DMA accuracy with stricter timings"
This reverts commit bea395b24b.
2025-08-13 17:34:12 +01:00
Luke Usher ce89a31749 ld: pass system type through a pak attribute 2025-08-13 17:25:28 +01:00
Luke Usher 3e7675cafb ld: pass location through a pak attribute 2025-08-13 17:25:23 +01:00
Luke Usher 4132375f26 desktop-ui: fix a crash when opening the settings dialog from the "missing firmware" prompt 2025-08-13 17:25:17 +01:00
Luke Usher 5e0bd5b3b8 ld: nall-ize mmi; remove nlohmann/json
Likely still room for improvement; especially if multi-side games exist, but that's a later problem.

mia: minor cleanups/nallisms
2025-08-13 17:25:11 +01:00
Luke Usher d00c84ff1b mia: add laserdisc medium type 2025-08-13 17:25:06 +01:00
Luke Usher 97f3a7a6fb thirdparty: fix qoi/qon build on all platforms 2025-08-13 17:25:00 +01:00
Roger Sanders 8c656cdfd3 mld: Initial MegaLD support based on a skeleton by Luke Usher 2025-08-13 17:24:54 +01:00
Giovanni Bajo bea395b24b n64: improve PI DMA accuracy with stricter timings
This commit splits the PI DMA-Write transfers (that is, loads from ROM)
in multiple blocks, following what happens on real hardware. Moreover,
it tries to more accurately reflect timing that each block takes. This
is easier to be done at the block granularity, otherwise the timing
algorithm would need to figure it out again the whole block splitting
logic.

From the point of view of the running application, now a ROM con see
the two hardware registers PI_CART_ADDR and PI_RDRAM_ADDR increment
one block at a time during the transfer, which more accurately reflect
what happens on real hardware.

This commit is validated with n64_pi_dma_test test ROM
(https://github.com/rasky/n64_pi_dma_test) which we now pass in full,
including timing tests with the default 10% tolerance.
2025-08-12 14:09:16 +01:00
Giovanni Bajo 6992197fb8 nall: in priority-queue, call event callbacks with correct timing
This allows callbacks for repeated events (rescheduled at each callback)
to not have additional latency caused by the latency with which we call
step().
2025-08-12 14:09:16 +01:00
Giovanni Bajo 097698ab63 n64: support configuration of different paks via homebrew header
The homebrew header supports configuring a different controller and pak
for each port, but mia couldn't represent that in the manifest. The
required change is trivial though.

For backward compatibility, if the homebrew header doesn't declare
anything (which is the same as asking for a standard controller in all
four ports with no paks), we install a cpak in port 1 and a rpak in
port 2, which is also what we have been doing forever with commercial
games.
2025-08-12 14:08:59 +01:00
Giovanni Bajo 46abceebf5 n64: fix controller pak formatting
Controller pak formatting had two bugs: checksum calculation was wrong
(didn't really iterate on all bugs), and the FAT backup copy was in the
wrong position.
2025-08-08 09:03:54 +01:00
Giovanni Bajo 06130778b7 n64: fix readback of PI_CART_ADDR after direct I/O 2025-08-06 15:51:22 +01:00
jcm 85b8d6c5a5 nall: Dynamically load SetThreadDescription on Windows 2025-08-06 08:57:28 +01:00
Luke Usher 8436634bc1 desktop-ui: remove "Fixed" and "Pixel Perfect" scale factors
Pixel perfect (1.1 PAR) can still be achieved "Scale:Integer" combined with "Aspect: No correction"; this works better than the 'Pixel Perfect' setting ever did.
2025-07-29 10:12:10 +01:00
Giovanni Bajo 5972b4e1aa Implement aspect correction mode
Currently, Ares has an "Aspect Correction" checkbox that allows to
disable the PAR (pixel aspect ratio) configuration made by each core,
to basically display the framebuffer without any additional scaling,
with host PC aspect ratio.

In addition to this, it is common for people to request an
"anamorphic 16:9" option, to correctly display games the way they would
appear on a 16:9 TV when stretched. Sometimes, homebrew games are even
designed to be viewed like this.

This PR changes the aspect mode from a boolean to a 3-value option.
The standard option is now named "Standard (4:3 TV)", and a new
option called "Anamorphic (16:9 TV)" is added.
2025-07-29 09:45:52 +01:00
jcm 426606bd02 desktop-ui: Defer settings window initialization on startup 2025-07-27 19:04:22 +01:00
Giovanni Bajo f9604e29e6 n64: correctly render full resolution on PAL
Currently, we render PAL games as 640x480 which is wrong because PAL has
more vertical resolution. This causes PAL graphics to be reduced in
quality compared to a real output.

This commit renders PAL at 640x576 which is the commonly used active
display area used by PAL games, and then configure the aspect ratio to
make sure the output is correct.
2025-07-27 19:04:12 +01:00
jcm 7d7b8ea4b2 n64: Reinitialize paraLLEl-RDP on system reset 2025-07-23 15:52:10 +01:00
remutro 2ed529d564 Fix mouse capture on Windows devices 2025-07-23 13:35:56 +01:00
clippy 73148db456 prevent 64 bit access to RCP over gdb 2025-07-23 13:35:37 +01:00
User 2fbc9c391c arm7tdmi: fix cycle order for SWP instruction 2025-07-23 11:29:10 +01:00
png183 12f3d6d89d gba: add comments to clarify how the upper 8 bits of SIOMLT_SEND are used 2025-07-23 11:28:42 +01:00
User 78c13dddef gba: SIOMLT_SEND should be a 16-bit register 2025-07-23 11:28:42 +01:00
Luke Usher be19b58307 sfc: add support for Limited Run Games SNES rumble specification.
See https://github.com/LimitedRunGames-Tech/snes-rumble
2025-07-21 13:09:30 +01:00
jcm bca80dc784 desktop-ui: Make internal program methods private; add docstrings 2025-07-20 19:38:22 +01:00
jcm e6fe07b084 desktop-ui: Fix undefined behavior in exit handler 2025-07-20 19:38:22 +01:00
jcm c28e47cb93 deps: Update dependencies to 2025-07-14 release 2025-07-15 12:38:57 +01:00
jcm c217662211 desktop-ui: Prevent deadlock when exiting 2025-07-14 14:49:40 +01:00
jcm 975a35e7a7 build: Provide librashader header path hint on all OSs 2025-07-14 10:38:40 +01:00
jcm 2e9233e534 CI: Ignore signing errors on Windows 2025-07-14 10:38:21 +01:00
jcm 4c6d151f7f build: Statically link runtime library for Windows CI builds 2025-07-14 10:38:13 +01:00
invertego 383931bde9 build: remove extraneous msvc optimization flags
/Oi, /Ob2, and /Ot are implied by /O2, which CMake adds by default in
Release and RelWithDebInfo builds. In RelWithDebInfo builds CMake also
downgrades inlining by adding in /Ob1, but we already have a workaround
for this. MinSizeRel builds may see a change in behavior, where CMake
passes /O1 instead, but in any case that is the recommended flag for
minimizing binary size.

The removal of /GL is especially important for MSVC builds. CMake will
add it when IPO is enabled, and forcing it on for all non-debug builds
was not simply redundant; it was overriding the user preference
expressed with ENABLE_IPO. ClangCL requires other flags to enable the
equivalent LTO feature, and this is already addressed elsewhere.
2025-07-14 10:37:55 +01:00
TascoDLX 1ac8c602c9 ms: remove obsolete screen resize code 2025-07-13 15:31:15 +01:00
Giovanni Bajo d5135ca35f n64: improve gdb memory hooks
Now support reads and writes of arbitrary lengths in both RDRAM
and RCP ares (using respectively 8-bit and 32-bit accesses).
2025-07-13 15:30:52 +01:00
User 8265726879 gba: prefetcher cannot be read when R15 is misaligned 2025-07-12 13:43:20 +01:00
User 5261757f3c gba: prefetcher should not be marked full if read on the same cycle that it was filled 2025-07-12 13:43:20 +01:00
Luke Usher c099be2b85 pce: force enable Pixel Accuracy VDP
The scanline renderer in this case is too buggy and causes too many game issues at this time.
2025-07-10 19:27:26 +01:00
jcm de8143ef47 CI: Upload source archives with releases 2025-07-10 18:24:30 +01:00
jcm b80f08956b build: Fix ARES_VERSION_OVERRIDE in source archives 2025-07-10 18:24:30 +01:00
kev4cards a3088f6d4c node, n64, ps1: rework ps1 stick into a virtual square, adjust scaling for N64 stick, and move common code into Node::Input::Axis 2025-07-10 10:08:06 +01:00
jcm fdaf0b65a1 build: Run gersemi CMake formatting pass 2025-07-10 10:06:03 +01:00
jcm 71e0ad1f5b build: Improve build code for test harnesses 2025-07-10 10:06:03 +01:00
User c87be1b4f0 gba: more prefetcher timing improvements 2025-07-10 10:05:42 +01:00
jcm 625df94505 n64: Always re-init paraLLEl-RDP from worker thread 2025-07-09 06:48:58 +01:00
jcm 0dc80ee4a2 desktop-ui: Fix data race when loading title on startup 2025-07-08 20:38:47 +01:00
jcm f373fdc165 desktop-ui: Guard changes to emulator video settings 2025-07-07 17:49:49 +01:00
jcm 68f2c94658 desktop-ui: Use Program::Guard to manage program state 2025-07-07 17:49:49 +01:00
jcm 11750b9600 hiro: Adjust GUI run loop frequency 2025-07-07 17:49:49 +01:00
remutro 135cadd32f Use GBC core with N64 transfer pak to allow both GB & GBC saves 2025-07-07 17:49:42 +01:00
invertego f015fffd69 build: #ifdef guard pragmas specific to gcc/clang
This eliminates some warnings compiling with MSVC.
2025-06-30 11:39:31 +01:00
User aa7260a58d s3511a: port alarm handling from WonderSwan core 2025-06-30 11:39:22 +01:00
User cdd551f122 s3511a: add comments for registers with 0-byte payloads 2025-06-30 11:39:22 +01:00
User f3ce7952f6 gba: make S3511A a shared component 2025-06-30 11:39:22 +01:00
User 9b1eb2e2f9 gba: use same variable for input and output of RTC SIO pin 2025-06-30 11:39:22 +01:00
User c337ba258f gba: RTC status bit 7 is cleared on write, not read 2025-06-30 11:39:22 +01:00
User 6f16c5c17e gba: RTC should not be aware of GPIO direction 2025-06-30 11:39:22 +01:00
User b494705ad7 gba: fix RTC power-on state 2025-06-30 11:39:22 +01:00
User 371ba7b136 gba: implement GPIO output buffer 2025-06-30 11:39:22 +01:00
User fb685afa70 gba: initial RTC implementation 2025-06-30 11:39:22 +01:00
jcm f3ff75feb1 desktop-ui: Synchronize access to emulator configuration shown in status bar 2025-06-29 07:48:16 +01:00
jcm 5211e6c56d ares: Use atomic to synchronize run-ahead setting 2025-06-29 07:48:16 +01:00
invertego e3c5cfb44d ps1: make mute bit apply to reverb voices 2025-06-28 08:47:02 +01:00
invertego c95b48d95f ps1: make save states robust across ares launches
The CPU serialization logic would sometimes write pointers to save
states. Needless to say, this was not very compatible with saving and
loading states across separate runs of the ares process.
2025-06-28 08:46:36 +01:00
invertego 136afead52 ps1: represent spu noise as a signed 16-bit quantity 2025-06-27 12:17:26 +01:00
jcm 0865680da1 CI: Pin commit hashes for all third-party actions 2025-06-26 09:59:57 +01:00
jcm 08811ee3cb build: Use CMake build type for default launch configuration in Xcode 2025-06-26 09:59:57 +01:00
jcm b79ed895ff build: Prefer built-in setting for LTO on Xcode 2025-06-26 09:59:57 +01:00
jcm ece74e44e4 Show explicit Ccache status messages 2025-06-26 09:59:57 +01:00
TascoDLX b1c69c9f3f md: add cpu wait for apu bus access 2025-06-25 06:40:35 +01:00
Luke Usher 4c42273a07 ps1: DICR bit 15 is readable 2025-06-24 10:41:53 +01:00
Luke Usher ac58451996 nall: allow the use of VT100 escape sequences on Windows when supported and treat console output as utf8 2025-06-24 10:41:01 +01:00
Luke Usher 325c0ed876 ps1: use physical addresses as early as possible 2025-06-24 10:41:00 +01:00
Luke Usher f89777d8cc ps1: allow loading of executables with ps-exe extension 2025-06-24 10:41:00 +01:00
invertego f0fd4c0bd2 ps1: fix dma interrupt flag calculation
The topmost bit of the DICR (DMA Interrupt) register should not
influenced by the per-channel interrupt enable bits. This flaw was
masked in fast boot mode, as it allows title code to begin execution in
a pristine state unlike what the BIOS leaves behind.

With this change, the titles that were "fixed" by enabling fast boot
mode can now be launched with it disabled.
2025-06-24 07:02:17 +01:00
png183 a3d8db1c2a arm7tdmi: implement R15 misalignment and clean up LDM/STM (#2058)
Allows bit 1 of R15 to be set in ARM mode.

The updated LDM/STM implementations are functionally identical to the
previous versions, but are significantly shorter and require less
special-casing.
2025-06-23 11:21:30 +01:00
Luke Usher 073d7a5375 desktop-ui: status bar overhaul
The status bar now shows the loaded system configuration when there are no messages to display.
When GDB is enabled, debug status will show instead.
2025-06-13 12:13:25 +01:00
Luke Usher 264a1baafa desktop-ui: simplify region detection; fix "NTSC" base region and NTSC-DEV, etc 2025-06-13 11:42:14 +01:00
Luke Usher 645be6ab9b ui: implement order of region preferences= 2025-06-11 18:59:55 +01:00
Luke Usher 67fc2b1073 fc: fix database to allow running PAL/NTSC games in both supported modes 2025-06-11 15:03:16 +01:00
png183 46b1991f8f gba: prefetcher timing improvements (#2051)
Fixed a bug where the prefetcher was restarting too early upon being
emptied, and implemented a timing edge case involving simultaneous
accesses from the CPU and prefetcher.
2025-06-11 10:06:31 +01:00
Luke Usher c9dfe8a1b9 gb: g/hdma takes 4 clocks to setup + 32|64 clocks depending on mode 2025-06-09 16:55:02 +01:00
Luke Usher af07e1f863 gb: hdma should be triggered on the rising edge of the combined hblank & hdmaActive signal 2025-06-09 16:55:02 +01:00
jcm b624fadd75 desktop-ui: Acquire program mutex for hotkeys affecting state (#2048)
Fixes an oversight where hotkeys that affected emulated system state
were not acquiring the program mutex.

Should fix #2045
2025-06-06 17:21:40 +01:00
png183 46209710d7 gba: do not advance IRQs during DMA (#2047)
Improves timings regarding interactions between the IRQ pipeline and
DMA, allowing ares to pass more of Alyosha's new test ROMs.
2025-06-05 07:26:20 +01:00
Luke Usher 90eb0df7d7 gb: HDMA issued during hblank (mode 0) should be processed immediately
Fixes:
Little Mermaid II, The - Pinball Frenzy
3-D Ultra Pinball - Thrillride

Likely others
2025-06-04 11:19:53 +01:00
jcm 64c9751dc5 desktop-ui: Threading fixes (#2046)
* Acquire the program mutex when updating audio sync settings to prevent
a possible crash in some audio drivers
* Pause emulation while showing driver confirmation modals to prevent
interference from the UI thread calling `ruby::audio.clear()` during the
modal state, and also because pausing the application for a modal is a
normal pattern
* Restore a workaround when loading Arcade systems via the game browser
window that prevented a crash
2025-06-03 22:33:37 +01:00
jcm 54e3479708 desktop-ui: Initialize tools window before program (#2044)
When loading a ROM via a startup argument, `Program` requires access to
elements that are initialized as part of the Tools window. Thus, make
sure the Tools window is fully loaded before creating `Program`.

This restores the load order prior to the threading rework. An earlier
version of the threading rework created the program mutex differently,
which is why the load order was modified in the first place
(incidentally rather than intentionally).

Fixes #2043
2025-06-02 17:26:19 +01:00
png183 9186ec0201 arm7tdmi: specify undefined instructions more strictly (#2042)
- Adds instruction encodings for BX with bit 21 clear, and for an
undocumented `mov rd, rn` instruction
- Uses specific encodings for undefined instructions, rather than
binding them to unused encodings, and then asserts that all encodings
have been bound
2025-05-31 18:20:05 +01:00
png183 64196caef8 gba: step CPU 1 cycle at a time while halted (#2041)
Removes a speedup in the GBA core that was detrimental to accuracy.
Testing on Linux, the performance cost is more than offset by the recent
UI threading rework.
2025-05-30 20:34:53 +01:00
jcm 1d0d6177a5 desktop-ui: Threading rework (#1906)
Rearranges ares's main run loop, putting emulation in its own dedicated
thread that is separate from the main/GUI thread. This updates ares to
somewhat more modern application practices. This rework should have an
array of benefits, including increased UI responsiveness, improved
energy efficiency/performance, and increased compatibility with various
modern APIs and frameworks for rendering, input, and audio.

## Details

Generally, this PR implements a fairly simple approach to threading,
using `nall::thread`'s pthread-like semantics and several basic
recursive mutexes. For this initial implementation, we use very coarse
locking on the emulation thread, combined with very limited access to
the emulator state from the main and other ares threads, allowed to
occur between emulator run loop iterations. Synchronization on the
emulator state is managed via `programMutex`.

Since this mutex is held for the duration of the primary work loop,
including while the thread is suspended, the GUI thread should try to
never acquire the program mutex unless absolutely necessary. Doing so
with regularity will incur a severe UI responsiveness penalty. For
destructive updates like driver settings updates, memory writes, and
game loads/unloads, we acquire it. For other cross-thread communication,
we try to synchronize using other methods that do not incur a
significant performance penalty.

Window resizes triggered via the emulation loop synchronize with the
`_needsResize` atomic. Status messages are passed by acquiring the
`messageMutex`. `vBlanksPerSecond` is also now an `atomic<u64>`.

Input continues to poll on the main thread for compatibility with
existing code. Access to the input system is managed via `inputMutex`.

### Known Issues

Draft status as this is still a work in progress, but PRing now for
visibility, feedback, and to consolidate any issue tracking. There are
likely kinks left to work out; testing is appreciated.

~~- Cores are loaded on the main thread before being run on the emulator
thread; this causes issues for paraLLEl-RDP, which expects that the
thread to initialize Vulkan is also the thread that will make RDP
calls.~~ Addressed.

~~- Memory editor currently does not have access to the program mutex;
reads and writes are unsynchronized.~~ Addressed; memory editor
synchronizes on writes but reads occur in a thread-unsafe fashion.

~~- The Clang thread sanitizer still reports a couple data races in
Metal and OpenGL; further testing with other Ruby drivers is needed to
make sure resource access is properly managed, particularly with respect
to the output framebuffers.~~ A larger examination of locking in other
parts of ares is probably necessary, but I think out of scope for the
first iteration of this rework.

- The gdb server seems to still be working fine, but could probably use
more testing.
2025-05-30 09:48:22 +01:00
jcm 421b7abf96 build: Fix build on macOS 10.15 (#2027)
[Build docs
state](https://github.com/ares-emulator/ares/wiki/Build-Instructions-For-macOS)
that we support building on macOS 10.15. A couple things broke since
that was written:


https://github.com/ares-emulator/ares/commit/6cc9f5a416ec9b0077fc0f50912b1af96987cdf5
broke because AppleClang 12.0 thinks that `step` is referring to the
function called `step` rather than the argument provided to the
function.


https://github.com/ares-emulator/ares/commit/c287c1b22501152342746ada4c988049715a1934
broke because the availability macro was not written correctly.


https://github.com/ares-emulator/ares/commit/4fc09438d22b02ea23d70ff96661e24151165ef6
broke because we made an inappropriate replacement when replacing
deprecations: `NSRegularSquareBezelStyle` should have become
`NSBezelStyleRegularSquare`; `NSBezelStyleFlexiblePush` resolves to the
same appearance, but is unavailable in the SDK included with Xcode 12.4.
2025-05-29 18:01:11 +01:00
thekovic 7e9df5858d n64: Improve labels for internal resolution upscaling options (#2026)
The SD/HD/UHD options represent scaling the internal N64 resolution to
1x/2x/4x. Even for a title running at 640x480, this results in 1280x960
and 2560x1920 which doesn't match the meaning of HD/UHD. Games running
at lower resolutions give even more out of whack results. New label
communicates more explicitly what the option actually does.

I didn't change the string used to serialize this option in
`settings.bml`. Feedback welcome on whether I should do that.
2025-05-29 18:01:04 +01:00
Luke Usher 69381f7c54 ms: improve header validation; fixes many NTSC-J titles being detected as international 2025-05-29 12:51:24 +01:00
png183 8f55894d65 arm7tdmi: add support for more undocumented instruction fields (#2039)
Adds unused bits to MUL and MRS, and adds support for undocumented STR
counterparts to LDRSH and LDRSB.
2025-05-29 10:01:44 +01:00
jcm 4e1435be8a ruby/metal: Zero-initialize librashader handles (#2038)
Metal checks whether librashader handles are non-null during
deinitialization to determine whether to call librashader's `free`
functions on them.

If the handles are initialized to contain junk data, they will be
non-null but `free` will fail, causing a crash during the deinit that
occurs prior to any initialization of the video driver.

On Apple Silicon, the system malloc implementation provides something of
a guarantee that heap allocations are zero-initialized, but this proves
to not always be the case in practice. There are also no (or fewer) such
guarantees on x86_64.

On OpenGL, these pointers were already being zero-initialized.
2025-05-29 10:01:14 +01:00
png183 cf0c21e15a arm7tdmi: improved handling of PSR edge cases (#2036)
When the CPSR mode field is set to an invalid mode, SP (R13), LR (R14),
and SPSR are not mapped to any register. Therefore, writes to them
should fail, and reading them should return zero (or 0x00000010 in the
case of SPSR).

Update: Also implemented a new edge case for BX instructions, where
setting bit 22 causes SPSR to be modified rather than CPSR.
2025-05-26 09:16:36 +01:00
png183 e10ce79153 arm7tdmi: add undocumented SWP encodings (#2032)
Instruction bits 23, 21, and 20 do not need to be zero for a SWP
instruction to be successfully decoded.
2025-05-24 19:16:10 +01:00
jcm ad7b83b604 build: Miscellaneous fixes (#2033)
* Fix a regression where the application manifest was not embedding
properly on Windows, leading to various problems including incorrect UI
styling and DPI settings
* Fix an issue where we would fail to parse a canonical version from a
`git describe` string without a suffix
2025-05-24 19:15:39 +01:00
jcm 53f3b6a2a2 Update dependencies to 2025-05-22 release (#2029)
* Update SDL to 3.2.14, containing miscellaneous bugfixes
* Update librashader to 0.8.1, which should address a rare crash with
bad shader caches on Windows
* Update slang-shaders to its master branch contents as of 5-21
(8c630e0d3234d93b6c2bc847371f86aa4e535686)
* Update MoltenVK to version 1.3.0
2025-05-22 19:34:36 +01:00
Alexander Chen b630377ddc hiro/cocoa: Add Edit Menu, refactor menu creation (#2024) 2025-05-18 08:35:00 +01:00
User 24a9c00ae7 arm7tdmi: finish coprocessor interface 2025-05-18 08:34:06 +01:00
User b5488aad82 arm7tdmi: simplify coprocessor interface 2025-05-18 08:34:06 +01:00
User 5f782419ae arm7tdmi: implement CDP instruction 2025-05-18 08:34:06 +01:00
User 7d5c6d2561 arm7tdmi: implement MCR instruction 2025-05-18 08:34:06 +01:00
User 585e43a94c arm7tdmi: implement MRC instruction 2025-05-18 08:34:06 +01:00
Luke Usher 28ab75242f cmake: keyboard.cpp should not have been duplicated as an included source 2025-05-17 13:58:26 +01:00
jcm 4dd7a1f5fe build: Fix up artifact metadata to include version info, copyright 2025-05-17 13:55:29 +01:00
jcm 756ea5b704 build: Do not require tags, add unofficial versioning scheme 2025-05-17 13:55:29 +01:00
jcm 7c051bae0b Update CMake policy version, add old Finder metadata to .gitignore (#2019)
Add explicit support for CMake 4.0 version policies; the only change
required is to remove a superfluous/invalid POST_BUILD designation from
Linux bundling code.

Also add .DS_Store to the .gitignore, since developers may possibly
still be on systems old enough to litter these everywhere.
2025-05-16 19:23:55 +01:00
Luke Usher edfc6b5fb0 sg: SC-3000 keyboard support 2025-05-16 19:19:47 +01:00
Luke Usher cdc0633fdd sg: properly handle software with on-cart *volatile* ram 2025-05-16 18:07:41 +01:00
Luke Usher 71facd2e14 cmake: add missing sc-3000.cpp 2025-05-16 17:15:37 +01:00
Luke Usher af95d2b760 sg: preliminary sg-3000 emulation (no keyboard yet) 2025-05-16 17:11:56 +01:00
Luke Usher f4f48eb4b9 sg: fix that base SG-1000 doesn't use a PPI
The PPI chip is only present in SG-1000A (Arcade Board) and SC-3000
2025-05-16 14:52:22 +01:00
Luke Usher 667827ab00 sg: IO port B bits 3-6 are active low, 7 is active high 2025-05-16 14:44:07 +01:00
Luke Usher 70480452a8 i8255: implement BSR mode 2025-05-16 14:43:22 +01:00
png183 a70acad605 arm7tdmi: fix failed assertion in debug builds (#2016)
Fixes an assertion that was failing because of an oversight in #2007
regarding ARM7TDMI BX instruction decode bits.
2025-05-15 17:58:47 +01:00
png183 dca8745f83 arm7tdmi: implement more undocumented fields in BX and MSR instructions (#2011)
Adds shifter fields to MSR, and a CPSR mask field to BX.
2025-05-14 13:30:22 +01:00
png183 fabbdc8d8f arm7tdmi: branch instruction fixes (#2007)
Fixed an issue where an undefined opcode was being treated as an
unconditional branch in Thumb mode, and added a destination register
field to ARM mode BX instructions.
2025-05-09 07:36:00 +01:00
png183 1a79a0aae0 gba: various APU improvements (#1997)
- Fixes an edge case involving overflowing audio FIFOs
- Slight FIFO channel timing improvements
- Prevents most APU registers from being written while audio is disabled
- Fixes writing to wave RAM while audio is disabled
2025-05-06 10:34:52 +01:00
jcm de066b1ea2 build: Improve version handling (#1995)
ares' build system tries to always have version information available
for debuggability and build tracking. It depends upon the presence of
Git to provide version information, since Git is the version control
system used by the project.

When distributing tarballs of ares, however, no `.git` directory and no
inherent version information is included in-tree, which causes the build
to fail with a vague fatal error after failing to derive a version for
itself.

Since we would like building a tarball to be at least possible with
minimal fuss, we should provide some version information inside a tar
archive served by GitHub. This PR uses Git's
[export-subst](https://git-scm.com/docs/gitattributes#_export_subst)
attribute to automatically bundle version information when `git-archive`
is run against the repository, as is done when the GitHub API serves a
tar archive of a given release, branch or commit. The build system will
check for the presence of this file if it finds that there is no `.git`
directory at the root of the repository.

This PR also improves the version handling in general to better support
semantic version strings of the form `v123.x.y`, in case future releases
of ares require hotfixes or minor versions.
2025-05-05 14:50:07 +01:00
png183 c185fbd66e gba: reduce audio FIFO size from 32 to 28 bytes (#1963)
Closes #1869.
2025-05-01 11:54:09 +01:00
Alexander Chen dae8db9883 Edited insert to fix bug with "result" variable (#1961)
Fixed result such that insert(std::.....) actually updates result as it
is supposed to.

The change is occured in ares/nall/nall/set.hpp, line 83
2025-04-30 16:03:19 +01:00
jcm 4c4b2a5e5a CI: clangarm64 Windows to aux, clang-cl-arm64 to primary (#1960)
The [Windows signing action used by
ares](https://github.com/Dana-Prajea/code-sign-action) fails under
clangarm64 due to having the wrong path for `signtool.exe`.

This PR swaps the clang-cl arm64 Windows build back into the
primary/nightly workflow until we have a fix for clangarm64 codesigning.
2025-04-29 19:32:35 +01:00
png183 510ccf01d7 gba: add option to enable Game Boy Player (#1856)
Re-adds support for Game Boy Player rumble, with a UI toggle to
enable/disable the feature.
2025-04-29 18:44:46 +01:00
Luke Usher aab2fdf9b2 ps1: missed cmakelists in previous commit 2025-04-29 16:31:08 +01:00
Luke Usher e2d7106a6c ps1: remove the buggy recompiler while we improve overall accuracy
It may be replaced in future, but it introduces too many bugs in its current form.
2025-04-29 13:53:37 +01:00
Luke Usher 1598f67506 ps1: fix VRAMtoCPU transfers 2025-04-29 13:53:37 +01:00
Luke Usher 5d3f614c63 ps1: fix boot of discs without Sony copyright string (XPloder, etc) 2025-04-29 13:53:37 +01:00
Luke Usher 8ef16dfbd9 ps1: fix handling of CE bits of COP0.CAUSE
All tests in psxtest_cpu and psxtest_cpx now pass (when using the interpreter)
2025-04-29 13:53:37 +01:00
User 701cfb7b13 gba: fix erroneous EEPROM zero-initialization 2025-04-29 12:27:42 +01:00
jcm ae121d7377 build: Add arm64 runners to CI, fix msys2 clangarm64 build 2025-04-29 12:27:31 +01:00
remutro ed1e34c74e remove errant NALL_NOINLINE macro from SH2 instructions 2025-04-29 12:26:15 +01:00
Tim Hentenaar 728105b28e ps1: gpu: macro -> constexpr 2025-04-23 09:43:14 +01:00
Tim Hentenaar 3d88e24559 ps1: gpu: Ensure hi >= lo when calling std::clamp() 2025-04-22 13:41:06 +01:00
Adrian Siekierka a61ce6449f ws: support 8-bit bus width ROMs 2025-04-22 11:05:26 +01:00
jcm 63aa9ec5e4 hiro: Use GTK native file dialogs for portal compatibility 2025-04-21 09:49:36 +01:00
remutroandjcm 50e42627e2 Update mia/medium/super-famicom.cpp
Explicit cast so we hit the right constructor.

Co-authored-by: jcm <[email protected]>
2025-04-18 19:01:30 +01:00
remutro 3a46652b15 [SNES] Remove unnecessary additional string allocation 2025-04-18 19:01:30 +01:00
remutro 7528feabe8 [SNES] Update special case Sufami Turbo handling for Fuwafuwa Panic 2025-04-18 19:01:30 +01:00
remutro e57a80ea1b [SNES] Fix loading of some games using firmware by removing unnecessary data definition/reference to rom 2025-04-18 19:01:30 +01:00
User 4c0a03b35a gba: approximate PRAM access stalls in scanline renderer 2025-04-18 19:00:16 +01:00
jcm a247271e35 CI: Preserve symlinks in artifacts 2025-04-10 21:18:40 +01:00
jcm d61cfa905a Update ares-deps to 2025-04-09 release
Includes:
* Stop vendoring the librashader header on Linux
* deps.windows: Update librashader to 0.6.3
* deps.macos: Update librashader to 0.6.3
* Update slang-shaders to ref 25311dc
* Pin Rust nightly to version contemporary with librashader version
* Establish macOS deployment targets
* deps.macos: Update SDL to 3.2.10
* deps.windows: Update SDL to 3.2.10
2025-04-10 14:48:52 +01:00
jcm 9987bf18b2 ruby: Update to SDL3 2025-04-10 14:48:49 +01:00
Adrian Siekierka 8a80d32519 ws: PPU palette 4-7, 12-15 bit 0-2 reads as zero 2025-04-08 17:07:12 +01:00
remutro 5d0de5fbb3 Update MD serialization.cpp
Serialization for the MD, specific to the ym2612 audio implementation was changed, but there wasn't a version bump. This corrects this missing contribution.
2025-04-04 07:18:49 +01:00
jsgroth f06168e3f5 ym2612: fix LFO AM enabled bit not functioning 2025-04-04 06:52:02 +01:00
Adrian Siekierka 828f847efe ws: fix RTC cartridge protocol emulation 2025-04-03 18:36:34 +01:00
jcm d8c997ca8a build: Enable CHD support via build option 2025-04-03 10:47:15 +01:00
jcm 93137b76b4 mia: Add generic CD data sector read method 2025-04-03 10:47:15 +01:00
jcm b889aff3d4 desktop-ui: Don't show missing firmware error twice 2025-04-02 10:42:35 +01:00
Giovanni Bajo 70f3bfa00c n64: disable RDP readbacks from super-sampled image
This fixes some tests in the official RDP testsuite
2025-04-01 17:53:16 +01:00
User d942746f74 gb: implement undocumented PCM12 and PCM34 registers 2025-03-31 18:52:41 +01:00
jcm 1c05456bec libco: Define LIBCO_MPROTECT by default on aarch64 2025-03-27 15:36:11 +00:00
User 240363d31a gba: add renderingCycle to scanline renderer 2025-03-27 10:37:54 +00:00
User ebb3bcb37b gba: improve handling of mid-scanline writes to linear background scroll registers 2025-03-27 10:37:54 +00:00
remutro ab14fb1d10 [MS] Update PAL only entries in mia DB 2025-03-26 14:32:33 +00:00
User 7ba94fe7e4 gba: override save type in known problematic cases 2025-03-24 07:28:00 +00:00
jcm 69ea086d95 build: Remove invalid arguments from custom commands 2025-03-24 07:27:40 +00:00
jcm a510ac7e55 build: Don't search for usbhid on non-BSD 2025-03-24 07:27:40 +00:00
jcm ac98fd8d9c build: Fix macOS SDK detection for CMake 4.0 2025-03-24 07:27:40 +00:00
remutro 122820753e Add DB entry for Back to the Future III which was PAL only 2025-03-20 11:59:56 +00:00
remutro e355fbe88c [N64] Fix Id for Rampage 2 - Universal Tour in DB 2025-03-20 11:59:49 +00:00
Adrian Siekierka c1efa05d65 ws: fix regression in APU logic 2025-03-16 17:46:58 +00:00
Adrian Siekierka da071246fe v30mz: make I/O access timing marginally more accurate 2025-03-15 15:49:37 +00:00
Giovanni Bajo cca0ecf202 n64: add IPL2 checksum failure as accuracy flag
As part of the boot security, IPL2 (run by CPU), PIF and CIC collaborate
to verify that the IPL3 (stored at the beginning of the ROM) is valid
and not tampered, by running a checksum. If the checksum is invalid,
the console is halted immediately.

Ares emulates this behavior correctly. Sometimes, though, it might be
useful for development purposes to let some ROM boot even with a failing
checksum. Given that disabling the check was non-obvious, I'm adding
an accuracy compile-time flag for now.
2025-03-15 15:49:28 +00:00
remutro 17aee9071d Fix check for pif rom when loading Aleck64 games 2025-03-15 15:49:14 +00:00
User b92ae8a14c gba: improve linear background VRAM access timings 2025-03-15 07:59:34 +00:00
jcm 4ae42b2d44 desktop-ui: Fix up app icon, macOS asset compilation 2025-03-13 20:01:42 +00:00
Adrian Siekierka 2a11da200d ws: APU accuracy fixes
- the noise LFSR reset bit takes effect immediately
- channel period counters tick only when they're enabled, including
  if channel 2 is in voice mode or channel 4 is in noise mode
- the noise LFSR only ticks if channel 4 is enabled in noise mode
2025-03-09 21:32:21 +00:00
Giovanni Bajo cbf4a939aa n64: allow booting ROMs with non-standard PI DOM1 parms in the header
All commercial ROMs in N64 have the value 0x80371240 in the first 4
bytes. This has long been used by emulators both as a magic number to
"detect a N64 rom", and as a way to detect endianess of the dump.

In reality, the first byte (0x80) is unused/ignored by the console, while
the other 3 (0x371240) are just parameters used to configure cartridge
access (basically, ROM speed). There's absolutely nothing that prevents
a ROM with other values to boot. Ares currently fails to honor this
by refusing booting ROMs with different values, but this is just wrong.

To fix this, we change approach and use the CIC detection algorithm to
also detect the endianess, given that it's based on a checksum match.
If we can't find a match, we tried the two common types of byteswaps,
hoping to find a match. Eventually, we fallback to assume it's a
big-endian dump, and needs the common CIC-6102.

This means that analyze() will now only reject ROMs with size lower
than 4 KiB, but that's correct. Given that 4 KiB are loaded by the
IPL2, we assume that minimum size is required. Besides that, there is
nothing else in the ROM that can be used to "detect" a N64 ROM, so
it's correct to just let it boot and see what happens.
2025-03-09 19:20:31 +00:00
Adrian Siekierka d10a8498e2 ws: clear serial IRQs if serial block is disabled 2025-03-09 19:19:57 +00:00
Giovanni Bajo 2eb6bf6df8 gamepad: bank switch command must be ignored for single-bank paks 2025-03-05 12:21:44 +00:00
Cuyler36 35ede44b80 Support dynamic bank counts for N64 Contorller Paks 2025-03-05 12:21:44 +00:00
Giovanni Bajo 0c011fb2d4 n64: add O.D.T. to gamelist 2025-03-05 12:21:30 +00:00
Giovanni Bajo a006ef97e7 vi: make sure we refresh the screen even when VI is disabled 2025-03-04 20:07:46 +00:00
User 14a4f32997 gba: perform VRAM accesses in bitmap and affine modes even when no pixel is rendered 2025-03-04 20:06:28 +00:00
Adrian Siekierka 25c1e281cc v30mz, ws: fix HLT regression 2025-03-03 22:47:01 +00:00
invertegoandpng183 3ee7556bb7 arm7tdmi: add missing idle cycles
Co-authored-by: png183 <[email protected]>
2025-03-03 13:15:10 +00:00
invertegoandpng183 a5b11b13d0 arm7tdmi: move to status register bit 4 fix
Co-authored-by: png183 <[email protected]>
2025-03-03 13:15:10 +00:00
invertego 01e6cc1443 arm7tdmi: thumb carry flag fixes 2025-03-03 13:15:10 +00:00
invertegoandpng183 f5dace0a0f arm7tdmi: r15 fixes
Co-authored-by: png183 <[email protected]>
2025-03-03 13:15:10 +00:00
invertego 6b5ebb2882 arm7tdmi: fix disassembler mnemonic 2025-03-03 13:15:10 +00:00
Adrian Siekierka 140782662b v30mz: breaks are also delayed by one instruction 2025-03-02 18:13:58 +00:00
Adrian Siekierka 3daec1184a v30mz, ws: fix interrupt triggering edge cases on interrupt flag change 2025-03-02 18:13:58 +00:00
Adrian Siekierka 7e4386c1bf v30mz: fix interrupt triggering incorrectly around segment move instructions 2025-03-02 18:13:58 +00:00
Adrian Siekierka 8eba8ad784 ws: make level interrupts take immediate effect 2025-03-02 18:13:58 +00:00
png183 08377d02e2 gba: update list of game codes that require mirroring 2025-03-02 18:13:11 +00:00
Luke Usher ea4ad6890a md: 32X display is offset by approx 3.25 pixels to the right in H32 mode 2025-02-27 14:07:21 +00:00
Luke Usher 50be2e4e63 fc: add delay when changing FDS disk state 2025-02-27 11:48:05 +00:00
jcm effc5614bc CI: Align MSYS2 git line ending settings with system git 2025-02-23 14:47:32 +00:00
jcm 5f81464857 misc: CMake formatting pass 2025-02-22 08:50:47 +00:00
jcm bd8442605e build: Move Linux librashader path hint to package finder 2025-02-22 08:50:47 +00:00
jcm da312032de build: Prevent macOS deployment target being modified during auto-regeneration 2025-02-22 08:50:47 +00:00
jcm c509dd3964 Remove CRLFs from ares/n64/CMakeLists.txt 2025-02-22 08:50:47 +00:00
jcm 10a4d480de CI: Unset draft status on tagged releases 2025-02-22 08:50:47 +00:00
User 8c3ead7cb0 gba: latch lower ROM address lines during burst transfer 2025-02-22 08:50:32 +00:00
jcm 1b36d47554 ruby: SDL2 input fixups 2025-02-21 08:37:07 +00:00
jcm 2c940b03a1 nall: Retrieve executable path more reliably 2025-02-21 00:16:50 +00:00
User 7a5174a712 gb: tick timer on falling edge 2025-02-19 18:03:06 +00:00
remutro 274d6eb9a5 Allow for trimmed and padded versions 2025-02-19 18:02:56 +00:00
remutro de0cb099cb Update sha256 for Wonder Boy in Monster World (USA, Europe) for proper eeprom assignment 2025-02-19 18:02:56 +00:00
jcm 32fd70f7c7 build: Enable system library semantics for nall in other targets 2025-02-19 18:02:47 +00:00
Luke Usher b2251305c0 md: always sync core MD components regardless of reduced sync
Fixes VDP H/V tests in testpico when running as a 32X rom.
2025-02-18 14:25:38 +00:00
FitzRoyX 25a2602300 Update README.md 2025-02-18 08:35:00 +00:00
FitzRoyX 4480f7adec Update README.md 2025-02-18 08:35:00 +00:00
jcm 4197502402 nall: Resolve all file paths passed on command line 2025-02-18 08:34:42 +00:00
TascoDLX fb96de3eef mcd: fix subcpu reset behavior 2025-02-18 08:31:56 +00:00
Luke Usher 54c898f199 pak: saveName() needs to call name(), in case it was overridden. 2025-02-17 17:57:26 +00:00
TascoDLX e48b208de2 ym2612: fix timer behaviors 2025-02-17 12:07:24 +00:00
TascoDLX a9f9fd90e7 m32x: fix regs & reset state 2025-02-17 12:07:24 +00:00
TascoDLX deb8830c82 md: fix vdp irq timings 2025-02-17 12:07:24 +00:00
Adrian Siekierka 58af8cb64b ws: First pass at "mono" model color emulation. 2025-02-16 13:17:41 +00:00
TascoDLX c9285b5289 m32x: fix pwm serialization 2025-02-12 06:28:23 +00:00
jcm 4fc09438d2 misc: Address macOS deprecation warnings 2025-02-11 21:36:32 +00:00
jcm a0a7a53b32 build: Increment macOS deployment target to 10.15 2025-02-11 21:36:32 +00:00
User 90f1425b8e Simplify prefetcher logic 2025-02-10 20:47:44 +00:00
User 9f4446061d Initial commit 2025-02-10 20:47:44 +00:00
jcm 32e58943f0 build: Adjust CI push workflow to properly run on all branches 2025-02-10 18:03:40 +00:00
jcm bc116e70e1 build: Add legacy zconf header to .gitignore 2025-02-10 18:03:40 +00:00
jcm 992e1c08cb misc: Use shortened copyright string in About dialog 2025-02-10 18:03:40 +00:00
jcm 9dcc1a39d3 build: Statically link libc++ when using GCC on Windows 2025-02-10 18:03:40 +00:00
Luke Usher 9bbcf638dd gb: use "Game Boy" save path for both GB and GBC
Fixes using the same rom file on both GB and GBC hardware without losing save-data between them
Fixes Pokemon Stadium when loading a .gbc rom (the Transfer Pak only used the GB path)

Existing users using a custom save game path will have to manually move their saves from "Game Boy Color" to "Game Boy"
Users using the default "Save alongside rom files" will not have to take any action.
2025-02-10 16:18:42 +00:00
Luke Usher d6c8d36770 md: fix 32x blending when MD is in H32 mode 2025-02-10 12:54:49 +00:00
jcm c19cf47803 hiro/cocoa: Ensure trailing slash in directory picker result 2025-02-10 09:45:15 +00:00
remutro 1d5a5aef83 remove accidentally introduced space in line with no change 2025-02-09 18:46:25 +00:00
remutro 64ec01bd57 Proper 32X region header processing for licensed games 2025-02-09 18:46:25 +00:00
Luke Usher 82b1e88ec1 sfc: fix typos in sub-rom loading
Fixes Super Game Boy
Fixed Sufami Turbo
Fixes Satellaview/BS Memory Pak
2025-02-07 23:25:43 +00:00
Vincent Cunningham a592dc24d2 ci: Ignore nightly tag for versioning 2025-02-07 18:40:12 +00:00
Luke Usher 976b9828b6 md: Fix new emulator hang/crash in SRB2 32X port 2025-02-07 12:09:04 +00:00
Luke Usher 7f625b6348 md: more 32X PEN=0 stalls; cleaner multi-device sync 2025-02-07 11:51:48 +00:00
Luke Usher 23a9d66924 md: stall m68k when accessing 32x palette when PEN=0
Fixes colours in Chaotix
2025-02-07 11:37:52 +00:00
TascoDLX e4e9044097 m32x vdp fixes
- SH2 memory windows have been adjusted. The framebuffer areas must be mirrored up to 0x5ffffff -- fixes Toughman Contest backgrounds.
- VDP FEN & PEN flags are closer to accurate but still missing some finer timing details.
- FEN/PEN status may block vdp memory accesses. The docs seem to indicate that accesses may be held in wait until a status change, but thats not totally clear (nor validated?). For now, these accesses are blocked and logged in debug.
- FM setting (framebufferAccess) only allows vdp access by either 68k or SH2-side, so additional guards have been inserted for cram/framebuffer accesses. VDP reg accesses should be blocked as well according to docs, but this aspect needs to be reviewed.
- VDP reg latching has been implemented. The timing isnt completely accurate, but should be decent enough for now. Fixes Mars Check image test (corrupt lines from mode switching).
- Autofill timing has been implemented according to the docs. For now, this is just signaling via the FEN flag. Fixes MK II glitching et al.
2025-02-07 09:06:57 +00:00
Vincent Cunningham 04a51df859 ci: Update nightly tag to latest commit 2025-02-07 07:27:08 +00:00
Luke Usher 0c80c1e56b Merge remote-tracking branch 'origin/master' 2025-02-06 21:16:47 +00:00
Luke Usher 6f7618dc81 sh7604: log when wdt is used in watchdog mode 2025-02-06 21:16:29 +00:00
Luke Usher fdd7acb76b md: tweak 32X timing numbers
Should fix Brutal and Chaotix
2025-02-06 20:55:14 +00:00
Luke Usher 7ce2ffd010 md: tweak 32X timing numbers
Should fix Brutal and Chaotix
2025-02-06 20:54:38 +00:00
Vincent Cunningham bb22386f5f n64: Don't report interrupt exceptions to GDB
Interrupt exceptions fire multiple times per second and having to ack all of them can cause performance issues while debugging.
2025-02-06 19:04:36 +00:00
Luke Usher b372f65691 md: refactor sync between 32X components
Hopefully we don't see any negative impact from this;
I have tested many known timing-sensitive games with no ill effects locally.
2025-02-06 19:00:27 +00:00
Luke Usher 048d0e331d md: add internalStep function for 32x recompiler
Allows in-block calls to step() to become an increment which to be
actioned in bulk at the end of the recompiler block
2025-02-06 13:01:43 +00:00
jcm 1323a627b3 Update scripts and documentation 2025-02-06 06:41:02 +00:00
jcm b4ef96e4d6 Remove old macOS build script 2025-02-06 06:41:02 +00:00
jcm 8d2f88b745 build: Re-add librashader headers to source tree for Linux builds 2025-02-06 06:41:02 +00:00
jcm ca7eee17a9 build: Remove CMake platform definitions 2025-02-06 06:41:02 +00:00
jcm 9ef81df3cf build: Remove legacy gitignore entries 2025-02-06 06:41:02 +00:00
jcm 68af829f8e build: Remove legacy zconf header 2025-02-06 06:41:02 +00:00
jcm 56862e0026 build: Use configure_file to define version, copyright info 2025-02-06 06:41:02 +00:00
jcm 09d8af1c18 build: Rename configured files 2025-02-06 06:41:02 +00:00
jcm d20bc3c1b0 build: Remove legacy compatibility compile definitions 2025-02-06 06:41:02 +00:00
jcm 97c82119b5 build: Remove resource files 2025-02-06 06:41:02 +00:00
jcm b78228c5c0 build: Remove legacy workflow 2025-02-06 06:41:02 +00:00
jcm 5107ed00cb build: Remove dynamic dependencies from thirdparty 2025-02-06 06:41:02 +00:00
jcm aeff8ec8f9 build: Remove GNUmakefiles 2025-02-06 06:41:02 +00:00
Christopher Bonhage 7640e1598c n64: fix Joybus RTC read/write status byte (#1814)
As documented in the N64brew wiki:
https://n64brew.dev/wiki/Joybus_Protocol#0x07_-_Read_Real-Time_Clock_Block

The final output byte of Joybus RTC read/write commands should be the
RTC status.

This intended behavior has been confirmed with Mateusz Faderewski, the
creator of SummerCart.
2025-02-05 23:58:05 +01:00
4630 changed files with 11523 additions and 493513 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
# When archiving, export a version file containing tag/commit information
cmake/common/.archive-version.json export-subst
+1
View File
@@ -10,6 +10,7 @@ pushd build
# build; pipe output to render warnings neatly
xcodebuild -configuration RelWithDebInfo \
DEBUG_INFORMATION_FORMAT="dwarf-with-dsym" \
-parallelizeTargets \
2>&1 | xcbeautify --renderer github-actions
popd
+1 -1
View File
@@ -13,7 +13,7 @@ cmake --preset $TARGET_PRESET
pushd build
cmake --build . --config RelWithDebInfo
if [ "$CROSS_COMPILE" = true ]; then
if [ "$CROSS_COMPILE" = true ] || [ "$ARES_PLATFORM_NAME" = "windows-arm64" ]; then
cp ../.deps/ares-deps-windows-arm64/lib/*.pdb desktop-ui/rundir/
else
cp ../.deps/ares-deps-windows-x64/lib/*.pdb desktop-ui/rundir/
+8 -4
View File
@@ -21,13 +21,12 @@ do
mkdir ${outdir}-dSYMs
cp -a ${bindir}/ares-${package}-dSYMs/*.dSYM ${outdir}-dSYMs
cp -a ${bindir}/ares-${package}/*.app ${outdir}
zip -r ../ares-${package}.zip ${outdir}
zip -r ../ares-${package}-dSYMs.zip ${outdir}-dSYMs
zip -r -y ../ares-${package}.zip ${outdir}
zip -r -y ../ares-${package}-dSYMs.zip ${outdir}-dSYMs
cd -
done
for package in windows-x64 windows-clang-cl-x64 windows-clang-cl-arm64
for package in windows-x64 windows-clang-cl-arm64
do
mkdir "${package}"
cd "${package}"
@@ -43,3 +42,8 @@ do
zip -r ../ares-${package}.zip ${outdir}
cd -
done
# Package the source archive
pushd src
git archive --format=tar.gz -o ../ares-source.tar.gz HEAD
popd
+112
View File
@@ -0,0 +1,112 @@
name: Build (Auxiliary)
on:
workflow_call
jobs:
build:
strategy:
fail-fast: false
matrix:
program:
- ares
config:
- RelWithDebInfo
platform:
- name: windows-x64-gcc
os: windows-latest
shell: 'msys2 {0}'
msystem: mingw64
install: make git cmake ccache ninja mingw-w64-x86_64-toolchain
target-cmake-preset: windows-ci-mingw-native
- name: windows-clang-cl-x64
os: windows-latest
windres: rc
shell: bash
target-cmake-preset: windows-ci-cl-native
- name: windows-arm64
os: windows-11-arm
compiler: clang++
shell: 'msys2 {0}'
msystem: clangarm64
install: mingw-w64-clang-aarch64-make git mingw-w64-clang-aarch64-cmake mingw-w64-clang-aarch64-ccache mingw-w64-clang-aarch64-ninja mingw-w64-clang-aarch64-clang
target-cmake-preset: windows-ci-mingw-native
- name: windows-msvc-x64
os: windows-latest
windres: rc
shell: bash
target-cmake-preset: windows-ci-msvc-native
- name: windows-msvc-arm64
os: windows-latest
windres: rc
shell: bash
target-cmake-preset: windows-ci-msvc-cross
native-cmake-preset: windows-ci-msvc-native
- name: ubuntu-gcc-x64
os: ubuntu-24.04
shell: bash
target-cmake-preset: ubuntu-ci
- name: ubuntu-gcc-arm64
os: ubuntu-24.04-arm
shell: bash
target-cmake-preset: ubuntu-ci
- name: ubuntu-clang-x64
os: ubuntu-24.04
shell: bash
target-cmake-preset: ubuntu-ci-clang
- name: ubuntu-clang-arm64
os: ubuntu-24.04-arm
shell: bash
target-cmake-preset: ubuntu-ci-clang
name: ${{ matrix.program }}-${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
defaults:
run:
shell: ${{ matrix.platform.shell }}
steps:
- name: Install MSYS2 Dependencies
if: matrix.platform.shell == 'msys2 {0}'
uses: msys2/setup-msys2@40677d36a502eb2cf0fb808cc9dec31bf6152638
with:
msystem: ${{ matrix.platform.msystem }}
install: ${{ matrix.platform.install }}
- name: Checkout source code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-tags: true
fetch-depth: 0
- name: Setup MSYS2 Git Environment
if: matrix.platform.shell == 'msys2 {0}'
run: |
git config core.autocrlf true
- name: Install Linux Dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update -y -qq
sudo apt-get install cmake ccache ninja-build libsdl2-dev libgtk-3-dev libao-dev libopenal-dev
- name: "Build: Linux"
if: runner.os == 'Linux'
run: .github/scripts/build_ubuntu.sh
env:
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
- name: "Build: Windows"
if: runner.os != 'macOS' && runner.os != 'Linux'
run: .github/scripts/build_windows.sh
env:
ARES_PLATFORM_NAME: ${{ matrix.platform.name }}
CROSS_COMPILE: ${{ matrix.platform.native-cmake-preset != '' }}
NATIVE_PRESET: ${{ matrix.platform.native-cmake-preset }}
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
- name: "Compress Build Artifacts (Windows)"
if: runner.os != 'macOS' && runner.os != 'Linux'
run: |
tar -cvJf ares-${{ matrix.platform.name }}.tar.xz -C build/desktop-ui/rundir/ .
- name: Upload Build
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: ${{ matrix.program }}-${{ matrix.platform.name }}
path: ares-${{ matrix.platform.name }}.tar.xz
- name: Upload Debug Symbols (Windows)
if: runner.os != 'macOS' && runner.os != 'Linux'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: ${{ matrix.program }}-${{ matrix.platform.name }}-PDBs
path: build/PDBs/*
+81 -264
View File
@@ -1,10 +1,17 @@
name: Build
name: Build (Primary)
on:
push:
branches: [ master ]
tags: [ 'v*' ]
pull_request:
branches: [ master ]
workflow_call:
inputs:
codesign:
description: Enable codesigning
required: false
default: false
type: boolean
official:
description: Use official version strings
required: true
default: true
type: boolean
jobs:
build:
strategy:
@@ -12,53 +19,28 @@ jobs:
matrix:
program:
- ares
config:
- RelWithDebInfo
platform:
- name: windows
- name: windows-x64
os: windows-latest
compiler: clang++
windres: windres
shell: 'msys2 {0}'
msystem: clang64
install: mingw-w64-clang-x86_64-clang mingw-w64-clang-x86_64-pkgconf mingw-w64-clang-x86_64-SDL2
- name: windows-arm64
install: make git cmake ccache ninja mingw-w64-clang-x86_64-clang
target-cmake-preset: windows-ci-mingw-native
- name: windows-clang-cl-arm64
os: windows-latest
compiler: clang++ --target=aarch64-w64-windows-gnu --sysroot=/clangarm64 -resource-dir=/clangarm64/lib/clang/$(basename "$(clang++ -print-resource-dir)")
windres: windres --target=aarch64-w64-windows-gnu
shell: 'msys2 {0}'
msystem: clang64
install: mingw-w64-clang-x86_64-clang mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-pkgconf mingw-w64-clang-aarch64-SDL2
- name: windows-msvc-x64
os: windows-latest
compiler: clang++
windres: llvm-rc
shell: bash
msvc-arch: x64
- name: windows-msvc-arm64
os: windows-latest
compiler: clang++ --target=aarch64-w64-windows-msvc
windres: llvm-rc
shell: bash
msvc-arch: x64_arm64
- name: windows-cl-x64
os: windows-latest
compiler: cl
windres: rc
shell: bash
msvc-arch: x64
- name: windows-cl-arm64
os: windows-latest
compiler: cl
windres: rc
shell: bash
msvc-arch: x64_arm64
target-cmake-preset: windows-ci-cl-cross
native-cmake-preset: windows-ci-cl-native
- name: macos-universal
os: macos-15
compiler: clang++
shell: sh
- name: ubuntu
os: ubuntu-latest
compiler: g++
shell: sh
install: cmake ccache xcbeautify
target-cmake-preset: macos-ci-universal
name: ${{ matrix.program }}-${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
defaults:
@@ -67,250 +49,85 @@ jobs:
steps:
- name: Install MSYS2 Dependencies
if: matrix.platform.shell == 'msys2 {0}'
uses: msys2/setup-msys2@v2
uses: msys2/setup-msys2@40677d36a502eb2cf0fb808cc9dec31bf6152638
with:
msystem: ${{ matrix.platform.msystem }}
install: make ${{ matrix.platform.install }}
install: ${{ matrix.platform.install }}
- name: "macOS: Import Certificate"
if: runner.os == 'macOS' && inputs.codesign
uses: apple-actions/import-codesign-certs@65cfbc37e1464d733524cb555b724878f278b8c3
continue-on-error: ${{ secrets.MACOS_CERTIFICATE_NAME == '' }}
with:
p12-file-base64: ${{ secrets.MACOS_CERTIFICATE_DATA }}
p12-password: ${{ secrets.MACOS_CERTIFICATE_PASSPHRASE }}
- name: Checkout source code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-tags: true
fetch-depth: 0
- name: Setup MSYS2 Git Environment
if: matrix.platform.shell == 'msys2 {0}'
run: |
git config core.autocrlf true
- name: Install Linux Dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update -y -qq
sudo apt-get install libsdl2-dev libgtk-3-dev libao-dev libopenal-dev
- name: "macOS: Import Certificate"
if: runner.os == 'macOS' && github.event_name != 'pull_request'
continue-on-error: ${{ github.repository != 'ares-emulator/ares' }}
uses: wpilibsuite/import-signing-certificate@2ac4f44d28045073d23153256efbb4c4b2d8aa22 # Don't use rolling branch for security reasons
with:
certificate-data: ${{ secrets.MACOS_CERTIFICATE_DATA }}
certificate-passphrase: ${{ secrets.MACOS_CERTIFICATE_PASSPHRASE }}
keychain-name: ares-macos-keychain
keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
- name: Checkout source code
uses: actions/checkout@v4
- name: Install Windows Dependencies
if: runner.os == 'Windows'
run: |
export PATH="/c/Users/runneradmin/.cargo/bin:$PATH" # correct on windows-latest as of 2024-02-19
if [[ ${{ matrix.platform.name }} == *-arm64 ]]; then
rustup toolchain install nightly
rustup default nightly
rustup target add aarch64-pc-windows-msvc
pushd thirdparty/librashader
./build-librashader.sh aarch64-pc-windows-msvc
popd
else
rustup toolchain install nightly
rustup default nightly
pushd thirdparty/librashader
./build-librashader.sh
popd
fi
- name: "macOS: recover MoltenVK cache"
if: runner.os == 'macOS'
uses: actions/cache@v4
env:
cache-name: moltenvk
with:
path: thirdparty/MoltenVK/MoltenVK
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('thirdparty/MoltenVK/HEAD') }}
sudo apt-get install ${{ matrix.platform.install }}
- name: Install macOS Dependencies
if: runner.os == 'macOS'
run: |
sudo xcode-select -s /Applications/Xcode_16.1.0.app/Contents/Developer
export MACOSX_DEPLOYMENT_TARGET=10.13
brew install ninja cmake
pip install --no-input setuptools
rustup toolchain install nightly
rustup default nightly
rustup target add x86_64-apple-darwin
rustup target add aarch64-apple-darwin
pushd thirdparty/SDL
./build-SDL.sh
popd
pushd thirdparty/MoltenVK
./build-moltenvk.sh
popd
export MACOSX_DEPLOYMENT_TARGET=10.15
pushd thirdparty/librashader
./build-librashader.sh
popd
- name: Set up MSVC environment
if: matrix.platform.msvc-arch != ''
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.platform.msvc-arch }}
- name: Make
if: runner.os != 'macOS'
run: ${MAKE:-make} -j4 -C desktop-ui build=optimized local=false compiler="${{ matrix.platform.compiler }}" windres="${{ matrix.platform.windres }}"
- name: "macOS: Make universal app"
if: runner.os == 'macOS'
run: scripts/macos-make-universal.sh build=optimized local=false
brew install ${{ matrix.platform.install }}
- name: "Build: Windows"
if: runner.os != 'macOS' && runner.os != 'Linux'
run: .github/scripts/build_windows.sh
env:
MAKEFLAGS: -j3
MACOS_CERTIFICATE_NAME: ${{ secrets.MACOS_CERTIFICATE_NAME }}
MACOS_KEYCHAIN_NAME: ares-macos-keychain
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
ARES_BUILD_OFFICIAL: ${{ inputs.official }}
ARES_PLATFORM_NAME: ${{ matrix.platform.name }}
CROSS_COMPILE: ${{ matrix.platform.native-cmake-preset != '' }}
NATIVE_PRESET: ${{ matrix.platform.native-cmake-preset }}
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
- name: "Windows: sign"
if: runner.os == 'Windows' && github.event_name != 'pull_request'
if: runner.os == 'Windows' && inputs.codesign
continue-on-error: true
uses: DanaBear/code-sign-action@98c79121b376beab8d6a9484f445089db4461bca
with:
certificate: '${{ secrets.WIN_CERTIFICATE }}'
password: '${{ secrets.WIN_PASSWORD }}'
certificatesha1: '${{ secrets.WIN_CERTHASH }}'
certificatename: '${{ secrets.WIN_CERTNAME }}'
folder: 'desktop-ui/out'
folder: 'build/desktop-ui/rundir/'
timestampUrl: 'http://timestamp.digicert.com'
- name: "macOS: notarize"
if: false
continue-on-error: ${{ github.repository != 'ares-emulator/ares' }}
run: |
ditto -c -k --keepParent desktop-ui/out/ares.app /tmp/ares.zip
xcrun notarytool submit /tmp/ares.zip --apple-id "$MACOS_NOTARIZATION_USERNAME" --password "$MACOS_NOTARIZATION_PASSWORD" --team-id "$MACOS_NOTARIZATION_TEAMID" --wait
xcrun stapler staple desktop-ui/out/ares.app
- name: "Build: macOS"
if: runner.os == 'macOS'
run: .github/scripts/build_macos.sh
env:
MACOS_NOTARIZATION_USERNAME: ${{ secrets.MACOS_NOTARIZATION_USERNAME }}
MACOS_NOTARIZATION_PASSWORD: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
ARES_BUILD_OFFICIAL: ${{ inputs.official }}
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
MACOS_CERTIFICATE_NAME: ${{ secrets.MACOS_CERTIFICATE_NAME }}
MACOS_NOTARIZATION_TEAMID: ${{ secrets.MACOS_NOTARIZATION_TEAMID }}
- name: Upload
uses: actions/upload-artifact@v4
- name: "Compress Build Artifact (macOS)"
if: runner.os == 'macOS'
run: |
tar -cvJf ares-${{ matrix.platform.name }}.tar.xz -C build/desktop-ui/RelWithDebInfo/ .
- name: "Compress Build Artifacts (Windows)"
if: runner.os != 'macOS' && runner.os != 'Linux'
run: |
tar -cvJf ares-${{ matrix.platform.name }}.tar.xz -C build/desktop-ui/rundir/ .
- name: Upload Build
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: ${{ matrix.program }}-${{ matrix.platform.name }}
path: desktop-ui/out/*
release:
if: false
runs-on: ubuntu-latest
needs:
- build
steps:
- uses: actions/checkout@v4
path: ares-${{ matrix.platform.name }}.tar.xz
- name: Upload Debug Symbols (Windows)
if: runner.os != 'macOS' && runner.os != 'Linux'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
path: 'src'
- name: Download Artifacts
uses: actions/download-artifact@v4
name: ${{ matrix.program }}-${{ matrix.platform.name }}-PDBs
path: build/PDBs/*
- name: Upload Debug Symbols (macOS)
if: runner.os == 'macOS'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
path: 'bin'
- name: Package Artifacts
run: |
set -eu
case ${GITHUB_REF} in
refs/tags/*) suffix="-${GITHUB_REF#refs/tags/}" ;;
refs/heads/master) suffix="-nightly" ;;
*) suffix="" ;;
esac
srcdir="${GITHUB_WORKSPACE}/src"
bindir="${GITHUB_WORKSPACE}/bin"
# Hack: Workaround for GitHub artifacts losing attributes.
chmod +x ${bindir}/ares-macos-universal/ares.app/Contents/MacOS/ares
for package in windows windows-msvc-arm64 macos-universal
do
mkdir "${package}"
cd "${package}"
# Package ares.
outdir=ares${suffix}
mkdir ${outdir}
cp -ar ${bindir}/ares-${package}/* ${outdir}
zip -r ../ares-${package}.zip ${outdir}
cd -
done
- name: Create Release
id: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eu
github_rest()
{
local method="${1}"
local url="https://api.github.com${2}"
shift 2
>&2 echo "${method} ${url}"
curl \
--fail \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${GITHUB_TOKEN}" \
-X "${method}" \
"${url}" \
"$@"
}
github_get_release_id_for_tag()
{
payload=$(github_rest GET "/repos/${GITHUB_REPOSITORY}/releases/tags/${1}") || return
echo "${payload}" | jq .id
}
github_delete_release_by_id()
{
github_rest DELETE "/repos/${GITHUB_REPOSITORY}/releases/${1}"
}
github_create_release()
{
local payload="{
\"tag_name\": \"${1}\",
\"target_commitish\": \"${2}\",
\"name\": \"${3}\",
\"body\": \"${4}\",
\"draft\": ${5},
\"prerelease\": ${6}
}"
github_rest POST "/repos/${GITHUB_REPOSITORY}/releases" -d "${payload}"
}
make_nightly_release()
{
github_create_release \
nightly \
"${GITHUB_SHA}" \
"ares nightly $(date +"%Y-%m-%d")" \
"Auto-generated nightly release on $(date -u +"%Y-%m-%d %T %Z")" \
false \
true
}
make_version_release()
{
github_create_release \
"${1}" \
"${GITHUB_SHA}" \
"ares ${1}" \
"This is ares ${1}, released on $(date +"%Y-%m-%d")." \
false \
false
}
case ${GITHUB_REF} in
refs/tags/*)
# Create a new version release using the current revision.
echo "UPLOAD_URL=$(make_version_release ${GITHUB_REF#refs/tags/} | jq -r .upload_url)" >> $GITHUB_ENV
;;
refs/heads/master)
# Check for an existing nightly release.
{ release_id=$(github_get_release_id_for_tag nightly); status=$?; } || true
# Delete existing nightly release if it exists.
case ${status} in
0) github_delete_release_by_id "${release_id}" ;;
22) >&2 echo "No current nightly release; skipping tag deletion." ;;
*) >&2 echo "API call failed unexpectedly." && exit 1 ;;
esac
# Create a new nightly release using the current revision.
echo "UPLOAD_URL=$(make_nightly_release | jq -r .upload_url)" >> $GITHUB_ENV
;;
esac
- name: Upload ares-windows
uses: actions/upload-release-asset@v1
env: { GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' }
with: { upload_url: '${{ env.UPLOAD_URL }}', asset_path: 'ares-windows.zip', asset_name: 'ares-windows.zip', asset_content_type: 'application/zip' }
- name: Upload ares-windows-msvc-arm64
uses: actions/upload-release-asset@v1
env: { GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' }
with: { upload_url: '${{ env.UPLOAD_URL }}', asset_path: 'ares-windows-msvc-arm64.zip', asset_name: 'ares-windows-msvc-arm64.zip', asset_content_type: 'application/zip' }
- name: Upload ares-macos-universal
uses: actions/upload-release-asset@v1
env: { GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' }
with: { upload_url: '${{ env.UPLOAD_URL }}', asset_path: 'ares-macos-universal.zip', asset_name: 'ares-macos-universal.zip', asset_content_type: 'application/zip' }
# - name: Upload ares-ubuntu
# uses: actions/upload-release-asset@v1
# env: { GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' }
# with: { upload_url: '${{ env.UPLOAD_URL }}', asset_path: 'ares-ubuntu.zip', asset_name: 'ares-ubuntu.zip', asset_content_type: 'application/zip' }
name: ${{ matrix.program }}-${{ matrix.platform.name }}-dSYMs
path: build/dSYMs/*
-153
View File
@@ -1,153 +0,0 @@
name: Build (New)
on:
workflow_call:
inputs:
codesign:
description: Enable codesigning
required: false
default: false
type: boolean
jobs:
build:
strategy:
fail-fast: false
matrix:
program:
- ares
config:
- RelWithDebInfo
platform:
- name: windows-x64
os: windows-latest
compiler: clang++
shell: 'msys2 {0}'
msystem: clang64
install: mingw-w64-clang-x86_64-clang
target-cmake-preset: windows-ci-mingw-native
- name: windows-x64-gcc
os: windows-latest
compiler: g++
shell: 'msys2 {0}'
msystem: mingw64
install: mingw-w64-x86_64-toolchain
target-cmake-preset: windows-ci-mingw-native
- name: windows-clang-cl-x64
os: windows-latest
windres: rc
shell: bash
target-cmake-preset: windows-ci-cl-native
- name: windows-clang-cl-arm64
os: windows-latest
windres: rc
shell: bash
target-cmake-preset: windows-ci-cl-cross
native-cmake-preset: windows-ci-cl-native
- name: windows-msvc-x64
os: windows-latest
windres: rc
shell: bash
target-cmake-preset: windows-ci-msvc-native
- name: windows-msvc-arm64
os: windows-latest
windres: rc
shell: bash
target-cmake-preset: windows-ci-msvc-cross
native-cmake-preset: windows-ci-msvc-native
- name: macos-universal
os: macos-15
compiler: clang++
shell: sh
install: cmake ccache xcbeautify
target-cmake-preset: macos-ci-universal
- name: ubuntu
os: ubuntu-latest
compiler: g++
shell: bash
install: cmake ccache ninja-build libsdl2-dev libgtk-3-dev libao-dev libopenal-dev
target-cmake-preset: ubuntu-ci
name: ${{ matrix.program }}-${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
defaults:
run:
shell: ${{ matrix.platform.shell }}
steps:
- name: Install MSYS2 Dependencies
if: matrix.platform.shell == 'msys2 {0}'
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.platform.msystem }}
install: make git cmake ccache ninja ${{ matrix.platform.install }}
- name: "macOS: Import Certificate"
if: runner.os == 'macOS' && inputs.codesign
uses: apple-actions/import-codesign-certs@v3
continue-on-error: ${{ !inputs.codesign }}
with:
p12-file-base64: ${{ secrets.MACOS_CERTIFICATE_DATA }}
p12-password: ${{ secrets.MACOS_CERTIFICATE_PASSPHRASE }}
- name: Checkout source code
uses: actions/checkout@v4
with:
fetch-tags: true
fetch-depth: 0
- name: Install Linux Dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update -y -qq
sudo apt-get install ${{ matrix.platform.install }}
- name: Install macOS Dependencies
if: runner.os == 'macOS'
run: |
brew install ${{ matrix.platform.install }}
- name: "Build: Linux"
if: runner.os == 'Linux'
run: .github/scripts/build_ubuntu.sh
env:
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
- name: "Build: Windows"
if: runner.os != 'macOS' && runner.os != 'Linux'
run: .github/scripts/build_windows.sh
env:
CROSS_COMPILE: ${{ matrix.platform.native-cmake-preset != '' }}
NATIVE_PRESET: ${{ matrix.platform.native-cmake-preset }}
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
- name: "Windows: sign"
if: runner.os == 'Windows' && inputs.codesign
uses: DanaBear/code-sign-action@98c79121b376beab8d6a9484f445089db4461bca
with:
certificate: '${{ secrets.WIN_CERTIFICATE }}'
password: '${{ secrets.WIN_PASSWORD }}'
certificatesha1: '${{ secrets.WIN_CERTHASH }}'
certificatename: '${{ secrets.WIN_CERTNAME }}'
folder: 'build/desktop-ui/rundir/'
timestampUrl: 'http://timestamp.digicert.com'
- name: "Build: macOS"
if: runner.os == 'macOS'
run: .github/scripts/build_macos.sh
env:
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
MACOS_CERTIFICATE_NAME: ${{ secrets.MACOS_CERTIFICATE_NAME }}
MACOS_NOTARIZATION_TEAMID: ${{ secrets.MACOS_NOTARIZATION_TEAMID }}
- name: Upload Build (Windows)
if: runner.os != 'macOS' && runner.os != 'Linux'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.program }}-${{ matrix.platform.name }}
path: build/desktop-ui/rundir/*
- name: Upload Build (macOS)
if: runner.os == 'macOS'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.program }}-${{ matrix.platform.name }}
path: build/desktop-ui/RelWithDebInfo/
- name: Upload Debug Symbols (Windows)
if: runner.os != 'macOS' && runner.os != 'Linux'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.program }}-${{ matrix.platform.name }}-PDBs
path: build/PDBs/*
- name: Upload Debug Symbols (macOS)
if: runner.os == 'macOS'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.program }}-${{ matrix.platform.name }}-dSYMs
path: build/dSYMs/*
+8 -2
View File
@@ -11,7 +11,13 @@ concurrency:
cancel-in-progress: true
jobs:
build-project:
name: Build
uses: ./.github/workflows/build_new.yml
name: Build ares (primary)
uses: ./.github/workflows/build.yml
permissions:
contents: read
with:
official: false
build-aux:
name: Build ares (auxiliary)
uses: ./.github/workflows/build-aux.yml
secrets: inherit
+8 -5
View File
@@ -1,8 +1,6 @@
name: Push
on:
push:
branches: [ '*' ]
tags: [ 'v*' ]
push
concurrency:
group: '${{ github.workflow }} @ ${{ github.head_ref || github.ref }}'
cancel-in-progress: true
@@ -10,11 +8,16 @@ permissions:
contents: write
jobs:
build:
name: Build ares
uses: ./.github/workflows/build_new.yml
name: Build ares (primary)
uses: ./.github/workflows/build.yml
secrets: inherit
with:
codesign: true
official: ${{ github.ref_name == 'master' || github.ref_type == 'tag' }}
build-aux:
name: Build ares (auxiliary)
uses: ./.github/workflows/build-aux.yml
secrets: inherit
release:
name: Release
needs: build
+20 -6
View File
@@ -12,13 +12,22 @@ jobs:
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/')
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
path: 'src'
- name: Download Artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
path: 'bin'
- name: "Decompress Build Artifacts"
if: runner.os == 'macOS'
run: |
for package in macos-universal windows-x64 windows-clang-cl-arm64; do
pushd bin/ares-${package}
tar -xJf ares-${package}.tar.xz
rm -f ares-${package}.tar.xz
popd
done
- name: "macOS: notarize"
if: inputs.notarize
run: |
@@ -48,21 +57,26 @@ jobs:
description="This is ares ${GITHUB_REF_NAME}, released on ${now}."
echo "description=${description}" >> $GITHUB_OUTPUT
fi
- name: Update Git Tag
if: steps.check.outputs.tag == 'nightly'
id: update-tag
run: |
git -C src tag -f ${{ steps.check.outputs.tag }}
git -C src push -f origin ${{ steps.check.outputs.tag }}
- name: Create Release 🛫
id: create_release
uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191
with:
draft: ${{ github.ref != 'refs/heads/master' }}
prerelease: ${{ steps.check.outputs.tag == 'nightly' }}
tag_name: ${{ steps.check.outputs.tag }}
name: ares ${{ steps.check.outputs.versionName }}
body: ${{ steps.check.outputs.description }}
files: |
${{ github.workspace }}/ares-source.tar.gz
${{ github.workspace }}/ares-macos-universal.zip
${{ github.workspace }}/ares-macos-universal-dSYMs.zip
${{ github.workspace }}/ares-windows-x64.zip
${{ github.workspace }}/ares-windows-x64-PDBs.zip
${{ github.workspace }}/ares-windows-clang-cl-x64.zip
${{ github.workspace }}/ares-windows-clang-cl-x64-PDBs.zip
${{ github.workspace }}/ares-windows-clang-cl-arm64.zip
${{ github.workspace }}/ares-windows-clang-cl-arm64-PDBs.zip
+12 -4
View File
@@ -26,10 +26,10 @@
!CMakeLists.txt
!CMakePresets.json
thirdparty/SDL/SDL
thirdparty/SDL/libSDL2-2.0.0.dylib
desktop-ui/out
desktop-ui/obj
# Exclude old macOS Finder metadata
.DS_Store
# Exclude generated sources that still reside in-tree
ruby/video/metal/shaders.metallib
ruby/video/metal/shaders.ir
ares/ares/resource/resource.cpp
@@ -38,5 +38,13 @@ hiro/resource/resource.cpp
hiro/resource/resource.hpp
desktop-ui/resource/resource.cpp
desktop-ui/resource/resource.hpp
mia/resource/resource.cpp
mia/resource/resource.hpp
# Exclude fetched third-party processor test binaries
tests/arm7tdmi/tests
tests/m68000/tests
# Exclude unused legacy zlib header that is renamed by CMake
thirdparty/libchdr/deps/zlib-*/zconf.h
thirdparty/libchdr/deps/zlib-*/zconf.h.included
+2 -2
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.28...3.30)
cmake_minimum_required(VERSION 3.28...4.0)
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/bootstrap.cmake" NO_POLICY_SCOPE)
@@ -12,7 +12,7 @@ add_subdirectory(cmake)
add_subdirectory(thirdparty)
add_subdirectory(nall)
add_subdirectory(nall/nall)
add_subdirectory(libco)
add_subdirectory(ruby)
add_subdirectory(hiro)
+15 -3
View File
@@ -12,7 +12,8 @@
"cacheVariables": {
"ARES_PROFILE_ACCURACY": false,
"ARES_BUILD_LOCAL": true,
"ENABLE_CCACHE": true
"ENABLE_CCACHE": true,
"ARES_BUILD_OFFICIAL": {"type": "BOOL", "value": "$penv{ARES_BUILD_OFFICIAL}"}
}
},
{
@@ -81,7 +82,7 @@
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_OSX_ARCHITECTURES": {"type": "STRING", "value": "x86_64;arm64"},
"CMAKE_OSX_DEPLOYMENT_TARGET": {"type": "STRING", "value": "10.13"},
"CMAKE_OSX_DEPLOYMENT_TARGET": {"type": "STRING", "value": "10.15"},
"ARES_CODESIGN_IDENTITY": {"type": "STRING", "value": "$penv{MACOS_CERTIFICATE_NAME}"},
"ARES_CODESIGN_TEAM": {"type": "STRING", "value": "$penv{MACOS_NOTARIZATION_TEAMID}"},
"ENABLE_CCACHE": true,
@@ -101,7 +102,8 @@
"ARES_BUILD_LOCAL": false,
"ARES_BUILD_OPTIONAL_TARGETS": true,
"ARES_UNITY_CORES": true,
"ARES_PRECOMPILE_HEADERS": true
"ARES_PRECOMPILE_HEADERS": true,
"CMAKE_MSVC_RUNTIME_LIBRARY": {"type": "STRING", "value": "MultiThreaded"}
}
},
{
@@ -162,6 +164,16 @@
"ARES_BUILD_OPTIONAL_TARGETS": true,
"ARES_PRECOMPILE_HEADERS": false
}
},
{
"name": "ubuntu-ci-clang",
"displayName": "Ubuntu CI (clang)",
"description": "Single-arch binary for building on Github Actions",
"inherits": ["ubuntu-ci"],
"cacheVariables": {
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++"
}
}
],
"buildPresets": [
-6
View File
@@ -1,6 +0,0 @@
targets = all clean install uninstall verbose
$(targets):
@$(MAKE) -C desktop-ui $@
.PHONY: $(targets)
+30 -2
View File
@@ -1,7 +1,7 @@
----------------------------------------------------------------------
ares
Copyright (c) 2004-2024 ares team, Near et al
Copyright (c) 2004-2025 ares team, Near et al
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -828,4 +828,32 @@ You may add additional accurate notices of copyright ownership.
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
----------------------------------------------------------------------
----------------------------------------------------------------------
----------------------------------------------------------------------
qoi/qon
Copyright (c) 2025, Roger Sanders
Revised QOI format, achieving higher compression with equivalent performance.
Based on the "Quite OK Image" format for fast, lossless image compression
Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
----------------------------------------------------------------------
+5 -5
View File
@@ -5,6 +5,8 @@
**ares** is a multi-system emulator that began development on October 14th, 2004.
It is a descendant of [higan](https://github.com/higan-emu/higan) and [bsnes](https://github.com/bsnes-emu/bsnes/), and focuses on accuracy and preservation.
It's worth noting that ares takes some uncommon design approaches that essentially trade speed for code clarity. We avoid state machines and bitmasks (when possible). Most cores end up being half the amount of code, but slower. The code is clearer and less spaghettified, especially for systems with lots of processors. C bitfields being non-portable incurs a speedhit. Windows also has a speedhit over Linux due to its ABI needing more instructions to switch contexts.
Official Releases
-----------------
@@ -14,21 +16,19 @@ Official releases are available from
Nightly Builds
--------------
Automated, untested builds of ares are available for Windows and macOS as a [pre-release](https://github.com/higan-emu/ares/releases/tag/nightly).
Automated, untested builds of ares are available for Windows and macOS as a [pre-release](https://github.com/ares-emulator/ares/releases/tag/nightly).
Only the latest nightly build is kept.
Building ares
-------------
Build instructions are available on the ares wiki. See build instructions for:
ares supports building on Windows, macOS, and various Linux/BSD distributions. See build instructions for:
* [Windows](https://github.com/ares-emulator/ares/wiki/Build-Instructions-For-Windows)
* [macOS](https://github.com/ares-emulator/ares/wiki/Build-Instructions-For-macOS)
* [Linux](https://github.com/ares-emulator/ares/wiki/Build-Instructions-For-Linux)
* [BSD](https://github.com/ares-emulator/ares/wiki/Build-Instructions-For-BSD)
For legacy Makefile build instructions, see [here](https://github.com/ares-emulator/ares/wiki/Legacy-Build-Instructions).
Command-line options
--------------------
@@ -40,7 +40,7 @@ Usage: ./ares [options] game(s)
--help Displays available options and exit
--fullscreen Start in full screen mode
--system system Specify the system name
--shader shader Specify GLSL shader name to load (requires OpenGL driver)
--shader shader Specify a slang shader to load (requires OpenGL or Metal)
--setting name=value Specify a value for a setting
--dump-all-settings Show a list of all existing settings and exit
--no-file-prompt Do not prompt to load (optional) additional roms (eg: 64DD)
+10 -1
View File
@@ -1,5 +1,7 @@
add_library(ares STATIC ares/ares.cpp ares/memory/fixed-allocator.cpp)
configure_file(ares/ares.cpp.in ares/ares.cpp @ONLY)
add_library(ares::ares ALIAS ares)
option(ARES_PROFILE_ACCURACY "Build to prefer emulation accuracy over performance in certain specific areas" OFF)
@@ -29,7 +31,7 @@ endif()
target_sources(
ares
PRIVATE ares/ares.cpp ares/ares.hpp ares/inline.hpp ares/platform.hpp ares/random.hpp ares/types.hpp
PRIVATE ares/ares.cpp ares/ares.cpp.in ares/ares.hpp ares/inline.hpp ares/platform.hpp ares/random.hpp ares/types.hpp
)
target_sources(
@@ -291,6 +293,13 @@ set_source_files_properties(
n64/vulkan/parallel-rdp/vulkan/device.cpp
n64/vulkan/parallel-rdp/vulkan/command_buffer.cpp
n64/vulkan/parallel-rdp/vulkan/render_pass.cpp
n64/vulkan/parallel-rdp/vulkan/context.cpp
n64/vulkan/parallel-rdp/vulkan/memory_allocator.cpp
n64/vulkan/parallel-rdp/vulkan/semaphore.cpp
n64/vulkan/parallel-rdp/util/logging.cpp
n64/vulkan/parallel-rdp/util/environment.cpp
n64/vulkan/parallel-rdp/util/thread_name.cpp
n64/vulkan/parallel-rdp/util/timer.cpp
PROPERTIES SKIP_PRECOMPILE_HEADERS ON
)
-108
View File
@@ -1,108 +0,0 @@
ifeq ($(platform),windows)
options += $(call lib,uuid kernel32 user32 gdi32 comctl32 comdlg32 shell32)
else ifeq ($(platform),macos)
else ifneq ($(filter $(platform),linux bsd),)
options += -Wl,-export-dynamic
options += -lX11 -lXext
else
$(error "ares is a library and cannot be built directly.")
endif
ares.objects := ares ares-fixed-allocator
$(object.path)/ares.o: $(ares.path)/ares/ares.cpp
$(object.path)/ares-fixed-allocator.o: $(ares.path)/ares/memory/fixed-allocator.cpp
ifeq ($(vulkan),true)
flags += -DVULKAN
endif
ifeq ($(profile),accuracy)
flags += -DPROFILE_ACCURACY
endif
ifeq ($(profile),performance)
flags += -DPROFILE_PERFORMANCE
endif
ifneq ($(filter $(cores),a26),)
include $(ares.path)/a26/GNUmakefile
endif
ifneq ($(filter $(cores),fc),)
include $(ares.path)/fc/GNUmakefile
endif
ifneq ($(filter $(cores),sfc),)
include $(ares.path)/sfc/GNUmakefile
endif
ifneq ($(filter $(cores),n64),)
include $(ares.path)/n64/GNUmakefile
endif
ifneq ($(filter $(cores),sg),)
include $(ares.path)/sg/GNUmakefile
endif
ifneq ($(filter $(cores),ms),)
include $(ares.path)/ms/GNUmakefile
endif
ifneq ($(filter $(cores),md),)
include $(ares.path)/md/GNUmakefile
endif
ifneq ($(filter $(cores),saturn),)
include $(ares.path)/saturn/GNUmakefile
endif
ifneq ($(filter $(cores),ps1),)
include $(ares.path)/ps1/GNUmakefile
endif
ifneq ($(filter $(cores),pce),)
include $(ares.path)/pce/GNUmakefile
endif
ifneq ($(filter $(cores),msx),)
include $(ares.path)/msx/GNUmakefile
endif
ifneq ($(filter $(cores),cv),)
include $(ares.path)/cv/GNUmakefile
endif
ifneq ($(filter $(cores),myvision),)
include $(ares.path)/myvision/GNUmakefile
endif
ifneq ($(filter $(cores),gb),)
include $(ares.path)/gb/GNUmakefile
endif
ifneq ($(filter $(cores),gba),)
include $(ares.path)/gba/GNUmakefile
endif
ifneq ($(filter $(cores),ws),)
include $(ares.path)/ws/GNUmakefile
endif
ifneq ($(filter $(cores),ng),)
include $(ares.path)/ng/GNUmakefile
endif
ifneq ($(filter $(cores),ngp),)
include $(ares.path)/ngp/GNUmakefile
endif
ifneq ($(filter $(cores),spec),)
include $(ares.path)/spec/GNUmakefile
endif
include $(ares.path)/component/GNUmakefile
flags += $(foreach c,$(call strupper,$(cores)),-DCORE_$c)
ares.objects := $(ares.objects:%=$(object.path)/%.o)
-15
View File
@@ -1,15 +0,0 @@
ares.components += mos6502
ares.objects += ares-a26-system
ares.objects += ares-a26-controller
ares.objects += ares-a26-cartridge
ares.objects += ares-a26-cpu
ares.objects += ares-a26-tia
ares.objects += ares-a26-riot
$(object.path)/ares-a26-system.o: $(ares.path)/a26/system/system.cpp
$(object.path)/ares-a26-controller.o: $(ares.path)/a26/controller/controller.cpp
$(object.path)/ares-a26-cartridge.o: $(ares.path)/a26/cartridge/cartridge.cpp
$(object.path)/ares-a26-cpu.o: $(ares.path)/a26/cpu/cpu.cpp
$(object.path)/ares-a26-tia.o: $(ares.path)/a26/tia/tia.cpp
$(object.path)/ares-a26-riot.o: $(ares.path)/a26/riot/riot.cpp
+1
View File
@@ -54,6 +54,7 @@ auto System::load(Node::System& root, string name) -> bool {
}
node = Node::System::create(information.name);
node->setAttribute("configuration", name);
node->setGame({&System::game, this});
node->setRun({&System::run, this});
node->setPower({&System::power, this});
+6 -6
View File
@@ -7,15 +7,15 @@
namespace ares {
Platform* platform = nullptr;
bool _runAhead = false;
atomic<bool> _runAhead = false;
const string Name = "ares";
const string Version = "142";
const string Copyright = "ares team, Near";
const string Name = "@ARES_NAME@";
const string Version = "@ARES_VERSION@";
const string Copyright = "@ARES_LEGAL_COPYRIGHT_SHORT@";
const string License = "ISC";
const string LicenseURI = "https://opensource.org/licenses/ISC";
const string Website = "ares-emu.net";
const string WebsiteURI = "https://ares-emu.net/";
const string Website = "@ARES_WEBSITE@";
const string WebsiteURI = "https://@ARES_WEBSITE@/";
const u32 SerializerSignature = 0x31545342; //"BST1" (little-endian)
}
+13 -1
View File
@@ -66,11 +66,23 @@ namespace ares {
}
}
extern bool _runAhead;
extern atomic<bool> _runAhead;
inline auto runAhead() -> bool { return _runAhead; }
inline auto setRunAhead(bool runAhead) -> void { _runAhead = runAhead; }
}
/// ares elects to use the reserved C++ `register` identifier liberally in a few different areas so that it can more
/// freely describe hardware registers within emulated system components. This macro exists to prevent compilers
/// exploding at the sight of the reserved identifier.
///
/// Perhaps unsurprisingly, this macro will conflict with certain system headers. MSVC headers will emit an error
/// forbidding macroizing `register` if the identifier is defined, while the `clangarm64` system headers in MSYS2
/// appear to try to make use of the obsolete keyword meaning of the identifier.
///
/// Defining this macro after all platform headers are included avoids these conflicts... for now. Removing this
/// macro along with all uses of this identifier may inevitably become necessary in the future.
#define register $register
#include <ares/types.hpp>
#include <ares/random.hpp>
#include <ares/debug/debug.hpp>
+51
View File
@@ -5,6 +5,57 @@ struct Axis : Input {
auto value() const -> s64 { return _value; }
auto setValue(s64 value) -> void { _value = value; }
auto setOperatingRange(s16 position, double saturationRadius, double offset) -> double {
return position * saturationRadius / 32767.0 + offset;
}
auto processDeadzoneAndResponseCurve(double position, double innerDeadzone, double cardinalMax, double offset) -> double {
auto lengthAbsolute = abs(position - offset);
if(lengthAbsolute <= innerDeadzone) {
position = offset;
} else {
lengthAbsolute = (lengthAbsolute - innerDeadzone) * (cardinalMax) / (cardinalMax - innerDeadzone) / lengthAbsolute;
position = (position - offset) * lengthAbsolute + offset;
}
return position;
}
auto revisePosition(double position, double length, double saturationRadius, double offset) -> double {
if(length > saturationRadius) {
length = saturationRadius / length;
position = (position - offset) * length + offset;
}
return position;
}
auto applyGateBoundaries(double innerDeadzone, double cardinalMax, double diagonalMax, double positionX, double positionY, double offset, double& boundedPositionX, double& boundedPositionY) -> void {
if(positionX != offset && positionY != offset) {
auto slope = (positionY - offset) / (positionX - offset);
auto edgex = copysign(cardinalMax / (abs(slope) + (cardinalMax - diagonalMax) / diagonalMax), positionX);
auto edgey = copysign(min(abs(edgex * slope), cardinalMax / (1.0 / abs(slope) + (cardinalMax - diagonalMax) / diagonalMax)), positionY);
edgex = edgey / slope;
auto distanceToEdge = hypot(edgex, edgey);
auto length = hypot(positionX - offset, positionY - offset);
if(length > distanceToEdge) {
positionX = edgex + offset;
positionY = edgey + offset;
}
}
boundedPositionX = positionX;
boundedPositionY = positionY;
}
auto clampAxisToNearestBoundary(double position, double offset, double cardinalMax) -> double {
if(abs(position - offset) > cardinalMax) position = copysign(cardinalMax, position - offset) + offset;
return position;
}
auto counteractPrecisionError(double position) -> double {
return copysign(abs(position) + 1e-09, position);
}
protected:
s64 _value = 0;
s64 _minimum = -32768;
+31 -1
View File
@@ -7,6 +7,8 @@ Screen::Screen(string name, u32 width, u32 height) : Video(name) {
_inputB = new u32[width * height]();
_output = new u32[width * height]();
_rotate = new u32[width * height]();
_lineOverrideActive.resize(width * height, false);
_lineOverride.resize(width * height, nullptr);
if constexpr(ares::Video::Threaded) {
_thread = nall::thread::create({&Screen::main, this});
@@ -24,6 +26,7 @@ Screen::~Screen() {
}
auto Screen::main(uintptr_t) -> void {
thread::setName("dev.ares.screen");
while(!_kill) {
unique_lock<mutex> lock(_frameMutex);
@@ -49,6 +52,8 @@ auto Screen::power() -> void {
memory::fill<u32>(_inputB.data(), _canvasWidth * _canvasHeight, _fillColor);
memory::fill<u32>(_output.data(), _canvasWidth * _canvasHeight, _fillColor);
memory::fill<u32>(_rotate.data(), _canvasWidth * _canvasHeight, _fillColor);
memory::fill<n1>(_lineOverrideActive.data(), _canvasWidth * _canvasHeight, false);
memory::fill<const u32*>(_lineOverride.data(), _canvasWidth * _canvasHeight, nullptr);
}
auto Screen::pixels(bool frame) -> array_span<u32> {
@@ -60,6 +65,7 @@ auto Screen::pixels(bool frame) -> array_span<u32> {
auto Screen::resetPalette() -> void {
lock_guard<recursive_mutex> lock(_mutex);
_palette.reset();
refreshPalette();
}
auto Screen::resetSprites() -> void {
@@ -116,18 +122,21 @@ auto Screen::setSaturation(f64 saturation) -> void {
lock_guard<recursive_mutex> lock(_mutex);
_saturation = saturation;
_palette.reset();
refreshPalette();
}
auto Screen::setGamma(f64 gamma) -> void {
lock_guard<recursive_mutex> lock(_mutex);
_gamma = gamma;
_palette.reset();
refreshPalette();
}
auto Screen::setLuminance(f64 luminance) -> void {
lock_guard<recursive_mutex> lock(_mutex);
_luminance = luminance;
_palette.reset();
refreshPalette();
}
auto Screen::setFillColor(u32 fillColor) -> void {
@@ -186,6 +195,7 @@ auto Screen::colors(u32 colors, function<n64 (n32)> color) -> void {
_colors = colors;
_color = color;
_palette.reset();
refreshPalette();
}
auto Screen::frame() -> void {
@@ -225,7 +235,13 @@ auto Screen::refresh() -> void {
auto source = input + y * pitch;
auto target = output + y * width;
if(_interlace) {
if (_lineOverrideActive[y]) {
auto source = _lineOverride[y];
for(u32 x : range(width)) {
auto color = *source++;
*target++ = color;
}
} else if(_interlace) {
if((_interlaceField & 1) == (y & 1)) {
for(u32 x : range(width)) {
auto color = _palette[*source++];
@@ -333,6 +349,20 @@ auto Screen::refresh() -> void {
memory::fill<u32>(_inputB.data(), width * height, _fillColor);
}
auto Screen::lookupPalette(u32 index) -> u32 {
return _palette[index];
}
auto Screen::overrideLineDraw(u32 y, const u32* source) -> void {
_lineOverride[y] = source;
_lineOverrideActive[y] = true;
}
auto Screen::clearOverrideLineDraw(u32 y) -> void {
_lineOverrideActive[y] = false;
_lineOverride[y] = nullptr;
}
auto Screen::refreshPalette() -> void {
lock_guard<recursive_mutex> lock(_mutex);
if(_palette) return;
+5
View File
@@ -61,6 +61,9 @@ struct Screen : Video {
auto colors(u32 colors, function<n64 (n32)> color) -> void;
auto frame() -> void;
auto refresh() -> void;
auto lookupPalette(u32 index) -> u32;
auto overrideLineDraw(u32 y, const u32* source) -> void;
auto clearOverrideLineDraw(u32 y) -> void;
auto serialize(string& output, string depth) -> void override;
auto unserialize(Markup::Node node) -> void override;
@@ -94,6 +97,8 @@ protected:
unique_pointer<u32[]> _output;
unique_pointer<u32[]> _rotate;
unique_pointer<u32[]> _palette;
vector<n1> _lineOverrideActive;
vector<const u32*> _lineOverride;
vector<Node::Video::Sprite> _sprites;
//unserialized:
-10
View File
@@ -1,10 +0,0 @@
build := stable
nall.path := ../../../nall
include $(nall.path)/GNUmakefile
all:
sourcery resource.bml resource.cpp resource.hpp
clean:
$(call delete,resource.cpp)
$(call delete,resource.hpp)
File diff suppressed because it is too large. Load diff
-34
View File
@@ -1,34 +0,0 @@
namespace Resource {
namespace Ares {
extern const unsigned char Icon1x[6562];
extern const unsigned char Icon2x[9662];
extern const unsigned char Logo1x[11533];
extern const unsigned char Logo2x[18991];
}
namespace Sprite {
namespace SuperFamicom {
extern const unsigned char CrosshairBlue[332];
extern const unsigned char CrosshairGreen[329];
extern const unsigned char CrosshairRed[342];
}
namespace WonderSwan {
extern const unsigned char Auxiliary0[117];
extern const unsigned char Auxiliary1[134];
extern const unsigned char Auxiliary2[136];
extern const unsigned char Headphones[167];
extern const unsigned char Initialized[136];
extern const unsigned char LowBattery[144];
extern const unsigned char Orientation0[148];
extern const unsigned char Orientation1[150];
extern const unsigned char PoweredOn[155];
extern const unsigned char Sleeping[154];
extern const unsigned char VolumeA0[129];
extern const unsigned char VolumeA1[142];
extern const unsigned char VolumeA2[146];
extern const unsigned char VolumeB0[125];
extern const unsigned char VolumeB1[137];
extern const unsigned char VolumeB2[148];
extern const unsigned char VolumeB3[153];
}
}
}
+14
View File
@@ -1,5 +1,9 @@
list(REMOVE_DUPLICATES ares.components)
set(ARES_COMPONENTS_LIST "${ares.components}" CACHE LIST "Enabled shared hardware components" FORCE)
mark_as_advanced(ARES_COMPONENTS_LIST)
ares_add_sources(INCLUDED CMakeLists.txt)
macro(component_sources)
@@ -432,6 +436,16 @@ component_sources(
processor/z80/z80.hpp
)
component_sources(
COMPONENT
s3511a
PRIMARY
rtc/s3511a/s3511a.cpp
INCLUDED
rtc/s3511a/s3511a.hpp
rtc/s3511a/serialization.cpp
)
component_sources(
COMPONENT
sst39sf0x0
-76
View File
@@ -1,76 +0,0 @@
ares.components := $(call unique,$(ares.components))
ares.objects += $(if $(findstring ay38910,$(ares.components)),ares-audio-ay38910)
ares.objects += $(if $(findstring msm5205,$(ares.components)),ares-audio-msm5205)
ares.objects += $(if $(findstring sn76489,$(ares.components)),ares-audio-sn76489)
ares.objects += $(if $(findstring t6w28,$(ares.components)),ares-audio-t6w28)
ares.objects += $(if $(findstring ym2149,$(ares.components)),ares-audio-ym2149)
ares.objects += $(if $(findstring ym2413,$(ares.components)),ares-audio-ym2413)
ares.objects += $(if $(findstring ym2610,$(ares.components)),ares-audio-ym2610)
ares.objects += $(if $(findstring ym2612,$(ares.components)),ares-audio-ym2612)
ares.objects += $(if $(findstring m24c,$(ares.components)),ares-eeprom-m24c)
ares.objects += $(if $(findstring m93lcx6,$(ares.components)),ares-eeprom-m93lcx6)
ares.objects += $(if $(findstring i8255,$(ares.components)),ares-io-i8255)
ares.objects += $(if $(findstring arm7tdmi,$(ares.components)),ares-processor-arm7tdmi)
ares.objects += $(if $(findstring gsu,$(ares.components)),ares-processor-gsu)
ares.objects += $(if $(findstring hg51b,$(ares.components)),ares-processor-hg51b)
ares.objects += $(if $(findstring huc6280,$(ares.components)),ares-processor-huc6280)
ares.objects += $(if $(findstring i8080,$(ares.components)),ares-processor-i8080)
ares.objects += $(if $(findstring m68hc05,$(ares.components)),ares-processor-m68hc05)
ares.objects += $(if $(findstring m68000,$(ares.components)),ares-processor-m68000)
ares.objects += $(if $(findstring mos6502,$(ares.components)),ares-processor-mos6502)
ares.objects += $(if $(findstring sh2,$(ares.components)),ares-processor-sh2)
ares.objects += $(if $(findstring sm5k,$(ares.components)),ares-processor-sm5k)
ares.objects += $(if $(findstring sm83,$(ares.components)),ares-processor-sm83)
ares.objects += $(if $(findstring spc700,$(ares.components)),ares-processor-spc700)
ares.objects += $(if $(findstring ssp1601,$(ares.components)),ares-processor-ssp1601)
ares.objects += $(if $(findstring tlcs900h,$(ares.components)),ares-processor-tlcs900h)
ares.objects += $(if $(findstring upd96050,$(ares.components)),ares-processor-upd96050)
ares.objects += $(if $(findstring v30mz,$(ares.components)),ares-processor-v30mz)
ares.objects += $(if $(findstring wdc65816,$(ares.components)),ares-processor-wdc65816)
ares.objects += $(if $(findstring z80,$(ares.components)),ares-processor-z80)
ares.objects += $(if $(findstring sst39sf0x0,$(ares.components)),ares-flash-sst39sf0x0)
ares.objects += $(if $(findstring tms9918,$(ares.components)),ares-video-tms9918)
ares.objects += $(if $(findstring v9938,$(ares.components)),ares-video-v9938)
$(object.path)/ares-audio-ay38910.o: $(ares.path)/component/audio/ay38910/ay38910.cpp
$(object.path)/ares-audio-msm5205.o: $(ares.path)/component/audio/msm5205/msm5205.cpp
$(object.path)/ares-audio-sn76489.o: $(ares.path)/component/audio/sn76489/sn76489.cpp
$(object.path)/ares-audio-t6w28.o: $(ares.path)/component/audio/t6w28/t6w28.cpp
$(object.path)/ares-audio-ym2149.o: $(ares.path)/component/audio/ym2149/ym2149.cpp
$(object.path)/ares-audio-ym2413.o: $(ares.path)/component/audio/ym2413/ym2413.cpp
$(object.path)/ares-audio-ym2612.o: $(ares.path)/component/audio/ym2612/ym2612.cpp
$(object.path)/ares-eeprom-m24c.o: $(ares.path)/component/eeprom/m24c/m24c.cpp
$(object.path)/ares-eeprom-m93lcx6.o: $(ares.path)/component/eeprom/m93lcx6/m93lcx6.cpp
$(object.path)/ares-io-i8255.o: $(ares.path)/component/io/i8255/i8255.cpp
$(object.path)/ares-processor-arm7tdmi.o: $(ares.path)/component/processor/arm7tdmi/arm7tdmi.cpp
$(object.path)/ares-processor-gsu.o: $(ares.path)/component/processor/gsu/gsu.cpp
$(object.path)/ares-processor-hg51b.o: $(ares.path)/component/processor/hg51b/hg51b.cpp
$(object.path)/ares-processor-huc6280.o: $(ares.path)/component/processor/huc6280/huc6280.cpp
$(object.path)/ares-processor-i8080.o: $(ares.path)/component/processor/i8080/i8080.cpp
$(object.path)/ares-processor-m68hc05.o: $(ares.path)/component/processor/m68hc05/m68hc05.cpp
$(object.path)/ares-processor-m68000.o: $(ares.path)/component/processor/m68000/m68000.cpp
$(object.path)/ares-processor-mos6502.o: $(ares.path)/component/processor/mos6502/mos6502.cpp
$(object.path)/ares-processor-sh2.o: $(ares.path)/component/processor/sh2/sh2.cpp
$(object.path)/ares-processor-sm5k.o: $(ares.path)/component/processor/sm5k/sm5k.cpp
$(object.path)/ares-processor-sm83.o: $(ares.path)/component/processor/sm83/sm83.cpp
$(object.path)/ares-processor-spc700.o: $(ares.path)/component/processor/spc700/spc700.cpp
$(object.path)/ares-processor-ssp1601.o: $(ares.path)/component/processor/ssp1601/ssp1601.cpp
$(object.path)/ares-processor-tlcs900h.o: $(ares.path)/component/processor/tlcs900h/tlcs900h.cpp
$(object.path)/ares-processor-upd96050.o: $(ares.path)/component/processor/upd96050/upd96050.cpp
$(object.path)/ares-processor-v30mz.o: $(ares.path)/component/processor/v30mz/v30mz.cpp
$(object.path)/ares-processor-wdc65816.o: $(ares.path)/component/processor/wdc65816/wdc65816.cpp
$(object.path)/ares-processor-z80.o: $(ares.path)/component/processor/z80/z80.cpp
$(object.path)/ares-flash-sst39sf0x0.o: $(ares.path)/component/flash/sst39sf0x0/sst39sf0x0.cpp
$(object.path)/ares-video-tms9918.o: $(ares.path)/component/video/tms9918/tms9918.cpp
$(object.path)/ares-video-v9938.o: $(ares.path)/component/video/v9938/v9938.cpp
+2 -2
View File
@@ -125,7 +125,7 @@ auto YM2612::Channel::Operator::updatePhase() -> void {
auto YM2612::Channel::Operator::updateLevel() -> void {
u32 lfo = ym2612.lfo.clock & 0x40 ? ym2612.lfo.clock & 0x3f : ~ym2612.lfo.clock & 0x3f;
u32 depth = tremolos[channel.tremolo];
u32 depth = tremolos[tremoloEnable * channel.tremolo];
bool invert = ssg.attack != ssg.invert && envelope.state != Release;
n10 value = ssg.enable && invert ? 0x200 - envelope.value : 0 + envelope.value;
@@ -147,7 +147,7 @@ auto YM2612::Channel::power() -> void {
for(auto& op : operators) {
op.keyOn = 0;
op.keyLine = 0;
op.lfoEnable = 0;
op.tremoloEnable = 0;
op.keyScale = 0;
op.detune = 0;
op.multiple = 0;
+5 -9
View File
@@ -36,18 +36,14 @@ auto YM2612::writeData(n8 data) -> void {
//timer B period
case 0x026: {
timerB.period.bit(0,7) = data.bit(0,7);
// Not sure if this is the right place for it, but...
// handle a specific use case to reset the subticks on timer B
if(!timerB.enable) timerB.divider = 0;
break;
}
//timer control
case 0x027: {
//reload period on 0->1 transition
if(!timerA.enable && data.bit(0)) timerA.counter = timerA.period;
if(!timerB.enable && data.bit(1)) {
timerB.counter = timerB.period;
timerB.divider = 0;
}
timerA.enable = data.bit(0);
timerB.enable = data.bit(1);
timerA.irq = data.bit(2);
@@ -125,10 +121,10 @@ auto YM2612::writeData(n8 data) -> void {
break;
}
//LFO enable, decay rate
//LFO AM enable, decay rate
case 0x060: {
op.envelope.decayRate = data.bit(0,4);
op.lfoEnable = data.bit(7);
op.tremoloEnable = data.bit(7);
channel[index].updateEnvelope();
channel[index].updateLevel();
break;
@@ -13,12 +13,14 @@ auto YM2612::serialize(serializer& s) -> void {
s(envelope.divider);
s(timerA.enable);
s(timerA.enableLatch);
s(timerA.irq);
s(timerA.line);
s(timerA.period);
s(timerA.counter);
s(timerB.enable);
s(timerB.enableLatch);
s(timerB.irq);
s(timerB.line);
s(timerB.period);
@@ -43,7 +45,7 @@ auto YM2612::Channel::serialize(serializer& s) -> void {
auto YM2612::Channel::Operator::serialize(serializer& s) -> void {
s(keyOn);
s(keyLine);
s(lfoEnable);
s(tremoloEnable);
s(keyScale);
s(detune);
s(multiple);
+10 -11
View File
@@ -1,16 +1,15 @@
auto YM2612::TimerA::run() -> void {
if(!enable) return;
if(++counter) return;
counter = period;
line |= irq;
if(!++counter)
line |= irq & enableLatch;
if(enableLatch < enable || !counter)
counter = period;
enableLatch = enable;
}
auto YM2612::TimerB::run() -> void {
if(!enable) return;
if(++divider) return;
if(++counter) return;
counter = period;
line |= irq;
if(!++divider && !++counter)
line |= irq & enableLatch;
if(enableLatch < enable || !counter && !divider)
counter = period; // do not reset divider on reenable
enableLatch = enable;
}
+3 -1
View File
@@ -45,6 +45,7 @@ protected:
auto run() -> void;
n1 enable = 0;
n1 enableLatch = 0;
n1 irq = 0;
n1 line = 0;
n10 period = 0;
@@ -56,6 +57,7 @@ protected:
auto run() -> void;
n1 enable = 0;
n1 enableLatch = 0;
n1 irq = 0;
n1 line = 0;
n8 period = 0;
@@ -106,7 +108,7 @@ protected:
n1 keyOn = 0;
n1 keyLine = 0;
n1 lfoEnable = 0;
n1 tremoloEnable = 0;
n5 keyScale = 0;
n3 detune = 0;
n4 multiple = 0;
+7 -4
View File
@@ -9,7 +9,6 @@ namespace ares {
io.portCHigherInput = 1;
io.portAInput = 1;
io.groupAMode = 0;
io.ioMode = 0;
state.portA = 0xff;
state.portB = 0xff;
state.portC = 0xff;
@@ -43,15 +42,20 @@ namespace ares {
}
auto I8255::writeControl(n8 data) -> void {
if(data.bit(7) == 0) {
n3 bitSelect = data.bit(1, 3);
state.portC.bit(bitSelect) = data.bit(0);
writePortC(state.portC);
return;
}
io.portCLowerInput = data.bit(0);
io.portBInput = data.bit(1);
io.groupBMode = data.bit(2);
io.portCHigherInput = data.bit(3);
io.portAInput = data.bit(4);
io.groupAMode = data.bit(5,6);
io.ioMode = data.bit(7);
if(!io.ioMode) debug(unimplemented, "[I8255] BSR Mode");
if(io.groupAMode != 0) debug(unimplemented, "[I8255] Group A Mode != 0");
if(io.groupBMode != 0) debug(unimplemented, "[I8255] Group B Mode != 0");
}
@@ -63,7 +67,6 @@ namespace ares {
s(io.portCHigherInput);
s(io.portAInput);
s(io.groupAMode);
s(io.ioMode);
s(state.portA);
s(state.portB);
-1
View File
@@ -23,7 +23,6 @@ namespace ares {
n1 portCHigherInput;
n1 portAInput;
n2 groupAMode;
n1 ioMode;
} io;
struct State {
@@ -9,6 +9,7 @@ namespace ares {
#include "instruction.cpp"
#include "instructions-arm.cpp"
#include "instructions-thumb.cpp"
#include "coprocessor.cpp"
#include "serialization.cpp"
#include "disassembler.cpp"
@@ -19,7 +20,9 @@ ARM7TDMI::ARM7TDMI() {
auto ARM7TDMI::power() -> void {
processor = {};
processor.r15.modify = [&] { pipeline.reload = true; };
processor.r15.modify = [&] { processor.r15.data &= ~1; pipeline.reload = true; };
processor.rNULL.modify = [&] { processor.rNULL.data = 0; };
processor.spsrNULL.readonly = true;
pipeline = {};
carry = 0;
irq = 0;
+53 -23
View File
@@ -6,15 +6,13 @@ namespace ares {
struct ARM7TDMI {
enum : u32 {
Nonsequential = 1 << 0, //N cycle
Sequential = 1 << 1, //S cycle
Load = 1 << 0, //load operation
Store = 1 << 1, //store operation
Prefetch = 1 << 2, //instruction fetch
Byte = 1 << 3, // 8-bit access
Half = 1 << 4, //16-bit access
Word = 1 << 5, //32-bit access
Load = 1 << 6, //load operation
Store = 1 << 7, //store operation
Signed = 1 << 8, //sign-extend
Signed = 1 << 6, //sign-extend
};
virtual auto step(u32 clocks) -> void = 0;
@@ -44,6 +42,7 @@ struct ARM7TDMI {
auto load(u32 mode, n32 address) -> n32;
auto write(u32 mode, n32 address, n32 word) -> void;
auto store(u32 mode, n32 address, n32 word) -> void;
auto endBurst() -> void { nonsequential = true; return; }
//algorithms.cpp
auto ADD(n32, n32, bool) -> n32;
@@ -70,21 +69,25 @@ struct ARM7TDMI {
auto armMoveToStatus(n4 field, n1 source, n32 data) -> void;
auto armInstructionBranch(i24, n1) -> void;
auto armInstructionBranchExchangeRegister(n4) -> void;
auto armInstructionBranchExchangeRegister(n4, n4, n4, n1) -> void;
auto armInstructionCoprocessorDataProcessing(n4, n3, n4, n4, n4, n4) -> void;
auto armInstructionDataImmediate(n8, n4, n4, n4, n1, n4) -> void;
auto armInstructionDataImmediateShift(n4, n2, n5, n4, n4, n1, n4) -> void;
auto armInstructionDataRegisterShift(n4, n2, n4, n4, n4, n1, n4) -> void;
auto armInstructionLoadImmediate(n8, n1, n4, n4, n1, n1, n1) -> void;
auto armInstructionLoadRegister(n4, n1, n4, n4, n1, n1, n1) -> void;
auto armInstructionMemorySwap(n4, n4, n4, n1) -> void;
auto armInstructionMoveHalfImmediate(n8, n4, n4, n1, n1, n1, n1) -> void;
auto armInstructionMoveHalfRegister(n4, n4, n4, n1, n1, n1, n1) -> void;
auto armInstructionMoveImmediateOffset(n12, n4, n4, n1, n1, n1, n1, n1) -> void;
auto armInstructionMoveMultiple(n16, n4, n1, n1, n1, n1, n1) -> void;
auto armInstructionMoveRegisterOffset(n4, n2, n5, n4, n4, n1, n1, n1, n1, n1) -> void;
auto armInstructionMoveSignedImmediate(n8, n1, n4, n4, n1, n1, n1, n1) -> void;
auto armInstructionMoveSignedRegister(n4, n1, n4, n4, n1, n1, n1, n1) -> void;
auto armInstructionMoveToCoprocessorFromRegister(n4, n3, n4, n4, n4, n3) -> void;
auto armInstructionMoveToRegisterFromCoprocessor(n4, n3, n4, n4, n4, n3) -> void;
auto armInstructionMoveToRegisterFromRegister(n4, n4) -> void;
auto armInstructionMoveToRegisterFromStatus(n4, n1) -> void;
auto armInstructionMoveToStatusFromImmediate(n8, n4, n4, n1) -> void;
auto armInstructionMoveToStatusFromRegister(n4, n4, n1) -> void;
auto armInstructionMoveToStatusFromRegister(n4, n2, n5, n4, n1) -> void;
auto armInstructionMultiply(n4, n4, n4, n4, n1, n1) -> void;
auto armInstructionMultiplyLong(n4, n4, n4, n4, n1, n1, n1) -> void;
auto armInstructionSoftwareInterrupt(n24 immediate) -> void;
@@ -148,21 +151,31 @@ struct ARM7TDMI {
};
operator u32() const {
return m << 0 | t << 5 | f << 6 | i << 7 | v << 28 | c << 29 | z << 30 | n << 31;
return m << 0 | 1 << 4 | t << 5 | f << 6 | i << 7 | v << 28 | c << 29 | z << 30 | n << 31;
}
auto operator=(n32 data) -> PSR& {
m = data.bit(0,4);
t = data.bit(5);
f = data.bit(6);
i = data.bit(7);
v = data.bit(28);
c = data.bit(29);
z = data.bit(30);
n = data.bit(31);
set(0b1111, data);
return *this;
}
auto set(n4 field, n32 data) -> void {
if(readonly) return;
if(field.bit(0)) {
m = data.bit(0,4) | 0x10;
t = data.bit(5);
f = data.bit(6);
i = data.bit(7);
}
if(field.bit(3)) {
v = data.bit(28);
c = data.bit(29);
z = data.bit(30);
n = data.bit(31);
}
}
//serialization.cpp
auto serialize(serializer&) -> void;
@@ -174,6 +187,8 @@ struct ARM7TDMI {
b1 c; //carry
b1 z; //zero
b1 n; //negative
b1 readonly;
};
struct Processor {
@@ -182,6 +197,8 @@ struct ARM7TDMI {
GPR r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15;
PSR cpsr;
GPR rNULL; //read-only, used when no register is mapped to r(13) or r(14)
PSR spsrNULL; //read-only, used when no SPSR is mapped
struct FIQ {
GPR r8, r9, r10, r11, r12, r13, r14;
@@ -221,7 +238,6 @@ struct ARM7TDMI {
};
n1 reload = 1;
n1 nonsequential = 1;
Instruction fetch;
Instruction decode;
Instruction execute;
@@ -230,27 +246,41 @@ struct ARM7TDMI {
n32 opcode;
b1 carry;
b1 irq;
b1 nonsequential;
function<void (n32 opcode)> armInstruction[4096];
function<void ()> thumbInstruction[65536];
//coprocessor.cpp
auto bindCDP(n4 id, function<void (n4 cm, n3 op2, n4 cd, n4 cn, n4 op1)> handler) -> void;
auto bindMCR(n4 id, function<void (n32 data, n4 cm, n3 op2, n4 cn, n3 op1)> handler) -> void;
auto bindMRC(n4 id, function<n32 (n4 cm, n3 op2, n4 cn, n3 op1)> handler) -> void;
function<void (n4 cm, n3 op2, n4 cd, n4 cn, n4 op1)> CDP[16];
function<void (n32 data, n4 cm, n3 op2, n4 cn, n3 op1)> MCR[16];
function<n32 (n4 cm, n3 op2, n4 cn, n3 op1)> MRC[16];
//disassembler.cpp
auto armDisassembleBranch(i24, n1) -> string;
auto armDisassembleBranchExchangeRegister(n4) -> string;
auto armDisassembleBranchExchangeRegister(n4, n4, n4, n1) -> string;
auto armDisassembleCoprocessorDataProcessing(n4, n3, n4, n4, n4, n4) -> string;
auto armDisassembleDataImmediate(n8, n4, n4, n4, n1, n4) -> string;
auto armDisassembleDataImmediateShift(n4, n2, n5, n4, n4, n1, n4) -> string;
auto armDisassembleDataRegisterShift(n4, n2, n4, n4, n4, n1, n4) -> string;
auto armDisassembleLoadImmediate(n8, n1, n4, n4, n1, n1, n1) -> string;
auto armDisassembleLoadRegister(n4, n1, n4, n4, n1, n1, n1) -> string;
auto armDisassembleMemorySwap(n4, n4, n4, n1) -> string;
auto armDisassembleMoveHalfImmediate(n8, n4, n4, n1, n1, n1, n1) -> string;
auto armDisassembleMoveHalfRegister(n4, n4, n4, n1, n1, n1, n1) -> string;
auto armDisassembleMoveImmediateOffset(n12, n4, n4, n1, n1, n1, n1, n1) -> string;
auto armDisassembleMoveMultiple(n16, n4, n1, n1, n1, n1, n1) -> string;
auto armDisassembleMoveRegisterOffset(n4, n2, n5, n4, n4, n1, n1, n1, n1, n1) -> string;
auto armDisassembleMoveSignedImmediate(n8, n1, n4, n4, n1, n1, n1, n1) -> string;
auto armDisassembleMoveSignedRegister(n4, n1, n4, n4, n1, n1, n1, n1) -> string;
auto armDisassembleMoveToCoprocessorFromRegister(n4, n3, n4, n4, n4, n3) -> string;
auto armDisassembleMoveToRegisterFromCoprocessor(n4, n3, n4, n4, n4, n3) -> string;
auto armDisassembleMoveToRegisterFromRegister(n4, n4) -> string;
auto armDisassembleMoveToRegisterFromStatus(n4, n1) -> string;
auto armDisassembleMoveToStatusFromImmediate(n8, n4, n4, n1) -> string;
auto armDisassembleMoveToStatusFromRegister(n4, n4, n1) -> string;
auto armDisassembleMoveToStatusFromRegister(n4, n2, n5, n4, n1) -> string;
auto armDisassembleMultiply(n4, n4, n4, n4, n1, n1) -> string;
auto armDisassembleMultiplyLong(n4, n4, n4, n4, n1, n1, n1) -> string;
auto armDisassembleSoftwareInterrupt(n24) -> string;
@@ -0,0 +1,11 @@
auto ARM7TDMI::bindCDP(n4 id, function<void (n4 cm, n3 op2, n4 cd, n4 cn, n4 op1)> handler) -> void {
CDP[id] = handler;
}
auto ARM7TDMI::bindMCR(n4 id, function<void (n32 data, n4 cm, n3 op2, n4 cn, n3 op1)> handler) -> void {
MCR[id] = handler;
}
auto ARM7TDMI::bindMRC(n4 id, function<n32 (n4 cm, n3 op2, n4 cn, n3 op1)> handler) -> void {
MRC[id] = handler;
}
@@ -17,12 +17,12 @@ auto ARM7TDMI::disassembleInstruction(maybe<n32> pc, maybe<boolean> thumb) -> st
_pc = pc();
if(!thumb()) {
n32 opcode = getDebugger(Word | Nonsequential, _pc & ~3);
n32 opcode = getDebugger(Word, _pc & ~3);
n12 index = (opcode & 0x0ff00000) >> 16 | (opcode & 0x000000f0) >> 4;
_c = _conditions[opcode >> 28];
return pad(armDisassemble[index](opcode), -40);
} else {
n16 opcode = getDebugger(Half | Nonsequential, _pc & ~1);
n16 opcode = getDebugger(Half, _pc & ~1);
return pad(thumbDisassemble[opcode](), -40);
}
}
@@ -64,10 +64,16 @@ auto ARM7TDMI::armDisassembleBranch
}
auto ARM7TDMI::armDisassembleBranchExchangeRegister
(n4 m) -> string {
(n4 m, n4 d, n4 field, n1 mode) -> string {
return {"bx", _c, " ", _r[m]};
}
auto ARM7TDMI::armDisassembleCoprocessorDataProcessing
(n4 cm, n3 op2, n4 cpid, n4 cd, n4 cn, n4 op1) -> string {
return {"cdp", _c, " p", cpid, ", ", op1, ", cr", cd,
", cr", cn, ", cr", cm, ", ", op2};
}
auto ARM7TDMI::armDisassembleDataImmediate
(n8 immediate, n4 shift, n4 d, n4 n, n1 save, n4 mode) -> string {
static const string opcode[] = {
@@ -118,30 +124,6 @@ auto ARM7TDMI::armDisassembleDataRegisterShift
" ", _r[s]};
}
auto ARM7TDMI::armDisassembleLoadImmediate
(n8 immediate, n1 half, n4 d, n4 n, n1 writeback, n1 up, n1 pre) -> string {
string data;
if(n == 15) data = {" =0x", hex(getDebugger((half ? Half : Byte) | Nonsequential,
_pc + 8 + (up ? +immediate : -immediate)), half ? 4L : 2L)};
return {"ldr", _c, half ? "sh" : "sb", " ",
_r[d], ",[", _r[n],
pre == 0 ? "]" : "",
immediate ? string{",", up ? "+" : "-", "0x", hex(immediate, 2L)} : string{},
pre == 1 ? "]" : "",
pre == 0 || writeback ? "!" : "", data};
}
auto ARM7TDMI::armDisassembleLoadRegister
(n4 m, n1 half, n4 d, n4 n, n1 writeback, n1 up, n1 pre) -> string {
return {"ldr", _c, half ? "sh" : "sb", " ",
_r[d], ",[", _r[n],
pre == 0 ? "]" : "",
",", up ? "+" : "-", _r[m],
pre == 1 ? "]" : "",
pre == 0 || writeback ? "!" : ""};
}
auto ARM7TDMI::armDisassembleMemorySwap
(n4 m, n4 d, n4 n, n1 byte) -> string {
return {"swp", _c, byte ? "b" : "", " ", _r[d], ",", _r[m], ",[", _r[n], "]"};
@@ -150,7 +132,7 @@ auto ARM7TDMI::armDisassembleMemorySwap
auto ARM7TDMI::armDisassembleMoveHalfImmediate
(n8 immediate, n4 d, n4 n, n1 mode, n1 writeback, n1 up, n1 pre) -> string {
string data;
if(n == 15) data = {" =0x", hex(getDebugger(Half | Nonsequential, _pc + (up ? +immediate : -immediate)), 4L)};
if(n == 15) data = {" =0x", hex(getDebugger(Half, _pc + (up ? +immediate : -immediate)), 4L)};
return {mode ? "ldr" : "str", _c, "h ",
_r[d], ",[", _r[n],
@@ -173,7 +155,7 @@ auto ARM7TDMI::armDisassembleMoveHalfRegister
auto ARM7TDMI::armDisassembleMoveImmediateOffset
(n12 immediate, n4 d, n4 n, n1 mode, n1 writeback, n1 byte, n1 up, n1 pre) -> string {
string data;
if(n == 15) data = {" =0x", hex(getDebugger((byte ? Byte : Word) | Nonsequential,
if(n == 15) data = {" =0x", hex(getDebugger((byte ? Byte : Word),
_pc + 8 + (up ? +immediate : -immediate)), byte ? 2L : 4L)};
return {mode ? "ldr" : "str", _c, byte ? "b" : "", " ", _r[d], ",[", _r[n],
pre == 0 ? "]" : "",
@@ -212,6 +194,47 @@ auto ARM7TDMI::armDisassembleMoveRegisterOffset
pre == 0 || writeback ? "!" : ""};
}
auto ARM7TDMI::armDisassembleMoveSignedImmediate
(n8 immediate, n1 half, n4 d, n4 n, n1 mode, n1 writeback, n1 up, n1 pre) -> string {
string data;
if(n == 15) data = {" =0x", hex(getDebugger((half ? Half : Byte),
_pc + 8 + (up ? +immediate : -immediate)), half ? 4L : 2L)};
return {mode ? "ldr" : "str", _c, half ? "sh" : "sb", " ",
_r[d], ",[", _r[n],
pre == 0 ? "]" : "",
immediate ? string{",", up ? "+" : "-", "0x", hex(immediate, 2L)} : string{},
pre == 1 ? "]" : "",
pre == 0 || writeback ? "!" : "", data};
}
auto ARM7TDMI::armDisassembleMoveSignedRegister
(n4 m, n1 half, n4 d, n4 n, n1 mode, n1 writeback, n1 up, n1 pre) -> string {
return {mode ? "ldr" : "str", _c, half ? "sh" : "sb", " ",
_r[d], ",[", _r[n],
pre == 0 ? "]" : "",
",", up ? "+" : "-", _r[m],
pre == 1 ? "]" : "",
pre == 0 || writeback ? "!" : ""};
}
auto ARM7TDMI::armDisassembleMoveToCoprocessorFromRegister
(n4 cm, n3 op2, n4 cpid, n4 d, n4 cn, n3 op1) -> string {
return {"mcr", _c, " p", cpid, ", ", op1, ", ", _r[d],
", cr", cn, ", cr", cm, ", ", op2};
}
auto ARM7TDMI::armDisassembleMoveToRegisterFromCoprocessor
(n4 cm, n3 op2, n4 cpid, n4 d, n4 cn, n3 op1) -> string {
return {"mrc", _c, " p", cpid, ", ", op1, ", ", _r[d],
", cr", cn, ", cr", cm, ", ", op2};
}
auto ARM7TDMI::armDisassembleMoveToRegisterFromRegister
(n4 d, n4 n) -> string {
return {"mov", _c, " ", _r[d], ",", _r[n]};
}
auto ARM7TDMI::armDisassembleMoveToRegisterFromStatus
(n4 d, n1 mode) -> string {
return {"mrs", _c, " ", _r[d], ",", mode ? "spsr" : "cpsr"};
@@ -229,13 +252,18 @@ auto ARM7TDMI::armDisassembleMoveToStatusFromImmediate
}
auto ARM7TDMI::armDisassembleMoveToStatusFromRegister
(n4 m, n4 field, n1 mode) -> string {
(n4 m, n2 type, n5 shift, n4 field, n1 mode) -> string {
return {"msr", _c, " ", mode ? "spsr:" : "cpsr:",
field.bit(0) ? "c" : "",
field.bit(1) ? "x" : "",
field.bit(2) ? "s" : "",
field.bit(3) ? "f" : "",
",", _r[m]};
",", _r[m],
type == 0 && shift ? string{" lsl #", shift} : string{},
type == 1 ? string{" lsr #", shift ? (u32)shift : 32} : string{},
type == 2 ? string{" asr #", shift ? (u32)shift : 32} : string{},
type == 3 && shift ? string{" ror #", shift} : string{},
type == 3 && !shift ? " rrx" : ""};
}
auto ARM7TDMI::armDisassembleMultiply
@@ -276,7 +304,7 @@ auto ARM7TDMI::thumbDisassembleALU
auto ARM7TDMI::thumbDisassembleALUExtended
(n4 d, n4 m, n2 mode) -> string {
static const string opcode[] = {"add", "sub", "mov"};
static const string opcode[] = {"add", "cmp", "mov"};
if(d == 8 && m == 8 && mode == 2) return {"nop"};
return {opcode[mode], " ", _r[d], ",", _r[m]};
}
@@ -308,7 +336,7 @@ auto ARM7TDMI::thumbDisassembleBranchExchange
auto ARM7TDMI::thumbDisassembleBranchFarPrefix
(i11 displacementHi) -> string {
n11 displacementLo = getDebugger(Half | Nonsequential, (_pc & ~1) + 2);
n11 displacementLo = getDebugger(Half, (_pc & ~1) + 2);
i22 displacement = displacementHi << 11 | displacementLo << 0;
n32 address = _pc + 4 + displacement * 2;
return {"bl 0x", hex(address, 8L)};
@@ -340,7 +368,7 @@ auto ARM7TDMI::thumbDisassembleImmediate
auto ARM7TDMI::thumbDisassembleLoadLiteral
(n8 displacement, n3 d) -> string {
n32 address = ((_pc + 4) & ~3) + (displacement << 2);
n32 data = getDebugger(Word | Nonsequential, address);
n32 data = getDebugger(Word, address);
return {"ldr ", _r[d], ",[pc,#0x", hex(address, 8L), "] =0x", hex(data, 8L)};
}
+133 -62
View File
@@ -1,11 +1,9 @@
auto ARM7TDMI::reload() -> void {
u32 mask = !cpsr().t ? 3 : 1;
u32 size = !cpsr().t ? Word : Half;
pipeline.reload = false;
r(15).data &= ~mask;
pipeline.fetch.address = r(15) & ~mask;
pipeline.fetch.instruction = read(Prefetch | size | Nonsequential, pipeline.fetch.address);
endBurst();
pipeline.fetch.address = r(15);
pipeline.fetch.instruction = read(Prefetch | size, pipeline.fetch.address);
fetch();
}
@@ -16,18 +14,10 @@ auto ARM7TDMI::fetch() -> void {
pipeline.decode.thumb = cpsr().t;
pipeline.decode.irq = !cpsr().i;
u32 sequential = Sequential;
if(pipeline.nonsequential) {
pipeline.nonsequential = false;
sequential = Nonsequential;
}
u32 mask = !cpsr().t ? 3 : 1;
u32 size = !cpsr().t ? Word : Half;
r(15).data += size >> 3;
pipeline.fetch.address = r(15) & ~mask;
pipeline.fetch.instruction = read(Prefetch | size | sequential, pipeline.fetch.address);
pipeline.fetch.address = r(15);
pipeline.fetch.instruction = read(Prefetch | size, pipeline.fetch.address);
}
auto ARM7TDMI::instruction() -> void {
@@ -85,13 +75,31 @@ auto ARM7TDMI::armInitialize() -> void {
#undef arguments
#define arguments \
opcode.bit( 0, 3) /* m */
{
auto opcode = pattern(".... 0001 0010 ---- ---- ---- 0001 ????");
opcode.bit( 0, 3), /* m */ \
opcode.bit(12,15), /* d */ \
opcode.bit(16,19), /* field */ \
opcode.bit(22) /* mode */
for(n3 _ : range(8))
for(n1 mode : range(2)) {
auto opcode = pattern(".... 0001 0??0 ???? ???? ---- 0??1 ????") | _.bit(0,1) << 5 | _.bit(2) << 21 | mode << 22;
bind(opcode, BranchExchangeRegister);
}
#undef arguments
#define arguments \
opcode.bit( 0, 3), /* cm */ \
opcode.bit( 5, 7), /* op2 */ \
opcode.bit( 8,11), /* cpid */ \
opcode.bit(12,15), /* cd */ \
opcode.bit(16,19), /* cn */ \
opcode.bit(20,23) /* op1 */
for(n3 op2 : range(8))
for(n4 op1 : range(16)) {
auto opcode = pattern(".... 1110 ???? ???? ???? ???? ???0 ????") | op2 << 5 | op1 << 20;
bind(opcode, CoprocessorDataProcessing);
}
#undef arguments
#define arguments \
opcode.bit( 0, 7), /* immediate */ \
opcode.bit( 8,11), /* shift */ \
@@ -143,47 +151,14 @@ auto ARM7TDMI::armInitialize() -> void {
}
#undef arguments
#define arguments \
opcode.bit( 0, 3) << 0 | opcode.bit( 8,11) << 4, /* immediate */ \
opcode.bit( 5), /* half */ \
opcode.bit(12,15), /* d */ \
opcode.bit(16,19), /* n */ \
opcode.bit(21), /* writeback */ \
opcode.bit(23), /* up */ \
opcode.bit(24) /* pre */
for(n1 half : range(2))
for(n1 writeback : range(2))
for(n1 up : range(2))
for(n1 pre : range(2)) {
auto opcode = pattern(".... 000? ?1?1 ???? ???? ???? 11?1 ????") | half << 5 | writeback << 21 | up << 23 | pre << 24;
bind(opcode, LoadImmediate);
}
#undef arguments
#define arguments \
opcode.bit( 0, 3), /* m */ \
opcode.bit( 5), /* half */ \
opcode.bit(12,15), /* d */ \
opcode.bit(16,19), /* n */ \
opcode.bit(21), /* writeback */ \
opcode.bit(23), /* up */ \
opcode.bit(24) /* pre */
for(n1 half : range(2))
for(n1 writeback : range(2))
for(n1 up : range(2))
for(n1 pre : range(2)) {
auto opcode = pattern(".... 000? ?0?1 ???? ???? ---- 11?1 ????") | half << 5 | writeback << 21 | up << 23 | pre << 24;
bind(opcode, LoadRegister);
}
#undef arguments
#define arguments \
opcode.bit( 0, 3), /* m */ \
opcode.bit(12,15), /* d */ \
opcode.bit(16,19), /* n */ \
opcode.bit(22) /* byte */
for(n3 _ : range(8))
for(n1 byte : range(2)) {
auto opcode = pattern(".... 0001 0?00 ???? ???? ---- 1001 ????") | byte << 22;
auto opcode = pattern(".... 0001 ???? ???? ???? ---- 1001 ????") | _.bit(0,1) << 20 | byte << 22 | _.bit(2) << 23;
bind(opcode, MemorySwap);
}
#undef arguments
@@ -287,11 +262,88 @@ auto ARM7TDMI::armInitialize() -> void {
}
#undef arguments
#define arguments \
opcode.bit( 0, 3) << 0 | opcode.bit( 8,11) << 4, /* immediate */ \
opcode.bit( 5), /* half */ \
opcode.bit(12,15), /* d */ \
opcode.bit(16,19), /* n */ \
opcode.bit(20), /* mode */ \
opcode.bit(21), /* writeback */ \
opcode.bit(23), /* up */ \
opcode.bit(24) /* pre */
for(n1 half : range(2))
for(n1 mode : range(2))
for(n1 writeback : range(2))
for(n1 up : range(2))
for(n1 pre : range(2)) {
auto opcode = pattern(".... 000? ?1?? ???? ???? ???? 11?1 ????") | half << 5 | mode << 20 | writeback << 21 | up << 23 | pre << 24;
bind(opcode, MoveSignedImmediate);
}
#undef arguments
#define arguments \
opcode.bit( 0, 3), /* m */ \
opcode.bit( 5), /* half */ \
opcode.bit(12,15), /* d */ \
opcode.bit(16,19), /* n */ \
opcode.bit(20), /* mode */ \
opcode.bit(21), /* writeback */ \
opcode.bit(23), /* up */ \
opcode.bit(24) /* pre */
for(n1 half : range(2))
for(n1 mode : range(2))
for(n1 writeback : range(2))
for(n1 up : range(2))
for(n1 pre : range(2)) {
auto opcode = pattern(".... 000? ?0?? ???? ???? ---- 11?1 ????") | half << 5 | mode << 20 | writeback << 21 | up << 23 | pre << 24;
bind(opcode, MoveSignedRegister);
}
#undef arguments
#define arguments \
opcode.bit( 0, 3), /* cm */ \
opcode.bit( 5, 7), /* op2 */ \
opcode.bit( 8,11), /* cpid */ \
opcode.bit(12,15), /* d */ \
opcode.bit(16,19), /* cn */ \
opcode.bit(21,23) /* op1 */
for(n3 op2 : range(8))
for(n3 op1 : range(8)) {
auto opcode = pattern(".... 1110 ???0 ???? ???? ???? ???1 ????") | op2 << 5 | op1 << 21;
bind(opcode, MoveToCoprocessorFromRegister);
}
#undef arguments
#define arguments \
opcode.bit( 0, 3), /* cm */ \
opcode.bit( 5, 7), /* op2 */ \
opcode.bit( 8,11), /* cpid */ \
opcode.bit(12,15), /* d */ \
opcode.bit(16,19), /* cn */ \
opcode.bit(21,23) /* op1 */
for(n3 op2 : range(8))
for(n3 op1 : range(8)) {
auto opcode = pattern(".... 1110 ???1 ???? ???? ???? ???1 ????") | op2 << 5 | op1 << 21;
bind(opcode, MoveToRegisterFromCoprocessor);
}
#undef arguments
#define arguments \
opcode.bit(12,15), /* d */ \
opcode.bit(16,19) /* n */
for(n5 _ : range(32)) {
//undocumented instruction, equivalent to "mov rd, rn"
auto opcode = pattern(".... 0011 0?00 ???? ???? ---- ???? ----") | _.bit(0,3) << 4 | _.bit(4) << 22;
bind(opcode, MoveToRegisterFromRegister);
}
#undef arguments
#define arguments \
opcode.bit(12,15), /* d */ \
opcode.bit(22) /* mode */
for(n3 _ : range(8))
for(n1 mode : range(2)) {
auto opcode = pattern(".... 0001 0?00 ---- ???? ---- 0000 ----") | mode << 22;
auto opcode = pattern(".... 0001 0?00 ---- ???? ---- ???0 ----") | _ << 5 | mode << 22;
bind(opcode, MoveToRegisterFromStatus);
}
#undef arguments
@@ -310,10 +362,14 @@ auto ARM7TDMI::armInitialize() -> void {
#define arguments \
opcode.bit( 0, 3), /* m */ \
opcode.bit( 5, 6), /* type */ \
opcode.bit( 7,11), /* shift */ \
opcode.bit(16,19), /* field */ \
opcode.bit(22) /* mode */
for(n2 type : range(4))
for(n1 shiftLo : range(2))
for(n1 mode : range(2)) {
auto opcode = pattern(".... 0001 0?10 ???? ---- ---- 0000 ????") | mode << 22;
auto opcode = pattern(".... 0001 0?10 ???? ---- ???? ???0 ????") | type << 5 | shiftLo << 7 | mode << 22;
bind(opcode, MoveToStatusFromRegister);
}
#undef arguments
@@ -326,8 +382,9 @@ auto ARM7TDMI::armInitialize() -> void {
opcode.bit(20), /* save */ \
opcode.bit(21) /* accumulate */
for(n1 save : range(2))
for(n1 accumulate : range(2)) {
auto opcode = pattern(".... 0000 00?? ???? ???? ???? 1001 ????") | save << 20 | accumulate << 21;
for(n1 accumulate : range(2))
for(n1 _ : range(2)) {
auto opcode = pattern(".... 0000 0??? ???? ???? ???? 1001 ????") | save << 20 | accumulate << 21 | _ << 22;
bind(opcode, Multiply);
}
#undef arguments
@@ -358,15 +415,28 @@ auto ARM7TDMI::armInitialize() -> void {
#undef arguments
#define arguments
for(n12 id : range(4096)) {
if(armInstruction[id]) continue;
auto opcode = pattern(".... ???? ???? ---- ---- ---- ???? ----") | id.bit(0,3) << 4 | id.bit(4,11) << 20;
for(n8 _ : range(256)) {
//architecturally undefined
auto opcode = pattern(".... 011? ???? ---- ---- ---- ???1 ----") | _.bit(0,2) << 5 | _.bit(3,7) << 20;
bind(opcode, Undefined);
}
for(n8 _ : range(256)) {
//load to coprocessor
auto opcode = pattern(".... 110? ???1 ---- ---- ---- ???? ----") | _.bit(0,3) << 4 | _.bit(4,7) << 21;
bind(opcode, Undefined);
}
for(n8 _ : range(256)) {
//store from coprocessor
auto opcode = pattern(".... 110? ???0 ---- ---- ---- ???? ----") | _.bit(0,3) << 4 | _.bit(4,7) << 21;
bind(opcode, Undefined);
}
#undef arguments
#undef bind
#undef pattern
//check that all encodings are bound
for(n12 index : range(4096)) assert(armInstruction[index]);
}
auto ARM7TDMI::thumbInitialize() -> void {
@@ -423,9 +493,9 @@ auto ARM7TDMI::thumbInitialize() -> void {
bind(opcode, AdjustStack, immediate, mode);
}
for(n4 d : range(16))
for(n4 _ : range(16))
for(n4 m : range(16)) {
auto opcode = pattern("0100 0111 ???? ?---") | d.bit(0,2) << 0 | m << 3 | d.bit(3) << 7;
auto opcode = pattern("0100 0111 ???? ?---") | _.bit(0,2) << 0 | m << 3 | _.bit(3) << 7;
bind(opcode, BranchExchange, m);
}
@@ -446,6 +516,7 @@ auto ARM7TDMI::thumbInitialize() -> void {
for(n8 displacement : range(256))
for(n4 condition : range(16)) {
if(condition == 14) continue; //BAL
if(condition == 15) continue; //BNV
auto opcode = pattern("1101 ???? ???? ????") | displacement << 0 | condition << 8;
bind(opcode, BranchTest, displacement, condition);
@@ -24,24 +24,12 @@ auto ARM7TDMI::armALU(n4 mode, n4 d, n32 rn, n32 rm) -> void {
}
auto ARM7TDMI::armMoveToStatus(n4 field, n1 mode, n32 data) -> void {
if(mode && (cpsr().m == PSR::USR || cpsr().m == PSR::SYS)) return;
PSR& psr = mode ? spsr() : cpsr();
if(field.bit(0)) {
if(mode || privileged()) {
psr.m = data.bit(0,4);
psr.t = data.bit(5);
psr.f = data.bit(6);
psr.i = data.bit(7);
if(!mode && psr.t) r(15).data += 2;
}
}
if(field.bit(3)) {
psr.v = data.bit(28);
psr.c = data.bit(29);
psr.z = data.bit(30);
psr.n = data.bit(31);
if(mode) {
spsr().set(field, data);
} else {
if(!privileged()) field.bit(0) = 0;
cpsr().set(field, data);
if(cpsr().t) r(15).data += 2;
}
}
@@ -54,10 +42,21 @@ auto ARM7TDMI::armInstructionBranch
}
auto ARM7TDMI::armInstructionBranchExchangeRegister
(n4 m) -> void {
(n4 m, n4 d, n4 field, n1 mode) -> void {
n32 address = r(m);
cpsr().t = address.bit(0);
r(15) = address;
if((field & 0b1001) == 0b1001) {
PSR& psr = mode ? spsr() : cpsr();
psr.t = address.bit(0);
} else {
armMoveToStatus(field, mode, address);
}
r(d) = address;
}
auto ARM7TDMI::armInstructionCoprocessorDataProcessing
(n4 cm, n3 op2, n4 cpid, n4 cd, n4 cn, n4 op1) -> void {
if(!CDP[cpid]) return armInstructionUndefined();
CDP[cpid](cm, op2, cd, cn, op1);
}
auto ARM7TDMI::armInstructionDataImmediate
@@ -87,84 +86,65 @@ auto ARM7TDMI::armInstructionDataImmediateShift
auto ARM7TDMI::armInstructionDataRegisterShift
(n4 m, n2 type, n4 s, n4 d, n4 n, n1 save, n4 mode) -> void {
n8 rs = r(s) + (s == 15 ? 4 : 0);
n8 rs = r(s);
n32 rn = r(n) + (n == 15 ? 4 : 0);
n32 rm = r(m) + (m == 15 ? 4 : 0);
carry = cpsr().c;
idle();
switch(type) {
case 0: rm = LSL(rm, rs < 33 ? rs : (n8)33); break;
case 1: rm = LSR(rm, rs < 33 ? rs : (n8)33); break;
case 2: rm = ASR(rm, rs < 32 ? rs : (n8)32); break;
case 3: if(rs) rm = ROR(rm, rs & 31 ? u32(rs & 31) : 32); break;
case 0: rm = LSL(rm, rs); break;
case 1: rm = LSR(rm, rs); break;
case 2: rm = ASR(rm, rs); break;
case 3: rm = ROR(rm, rs); break;
}
armALU(mode, d, rn, rm);
}
auto ARM7TDMI::armInstructionLoadImmediate
(n8 immediate, n1 half, n4 d, n4 n, n1 writeback, n1 up, n1 pre) -> void {
n32 rn = r(n);
n32 rd = r(d);
if(pre == 1) rn = up ? rn + immediate : rn - immediate;
rd = load((half ? Half : Byte) | Nonsequential | Signed, rn);
if(pre == 0) rn = up ? rn + immediate : rn - immediate;
if(pre == 0 || writeback) r(n) = rn;
r(d) = rd;
}
auto ARM7TDMI::armInstructionLoadRegister
(n4 m, n1 half, n4 d, n4 n, n1 writeback, n1 up, n1 pre) -> void {
n32 rn = r(n);
n32 rm = r(m);
n32 rd = r(d);
if(pre == 1) rn = up ? rn + rm : rn - rm;
rd = load((half ? Half : Byte) | Nonsequential | Signed, rn);
if(pre == 0) rn = up ? rn + rm : rn - rm;
if(pre == 0 || writeback) r(n) = rn;
r(d) = rd;
}
auto ARM7TDMI::armInstructionMemorySwap
(n4 m, n4 d, n4 n, n1 byte) -> void {
lock();
n32 word = load((byte ? Byte : Word) | Nonsequential, r(n));
store((byte ? Byte : Word) | Nonsequential, r(n), r(m));
r(d) = word;
n32 word = load((byte ? Byte : Word), r(n));
store((byte ? Byte : Word), r(n), r(m) + (m == 15 ? 4 : 0));
unlock();
idle();
r(d) = word;
}
auto ARM7TDMI::armInstructionMoveHalfImmediate
(n8 immediate, n4 d, n4 n, n1 mode, n1 writeback, n1 up, n1 pre) -> void {
n32 rn = r(n);
n32 rd = r(d);
n32 rd = r(d) + (d == 15 ? 4 : 0);
if(pre == 1) rn = up ? rn + immediate : rn - immediate;
if(mode == 1) rd = load(Half | Nonsequential, rn);
if(mode == 0) store(Half | Nonsequential, rn, rd);
if(mode == 1) rd = load(Half, rn);
if(mode == 0) store(Half, rn, rd);
if(pre == 0) rn = up ? rn + immediate : rn - immediate;
if(pre == 0 || writeback) r(n) = rn;
if(mode == 1) r(d) = rd;
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveHalfRegister
(n4 m, n4 d, n4 n, n1 mode, n1 writeback, n1 up, n1 pre) -> void {
n32 rn = r(n);
n32 rm = r(m);
n32 rd = r(d);
n32 rd = r(d) + (d == 15 ? 4 : 0);
if(pre == 1) rn = up ? rn + rm : rn - rm;
if(mode == 1) rd = load(Half | Nonsequential, rn);
if(mode == 0) store(Half | Nonsequential, rn, rd);
if(mode == 1) rd = load(Half, rn);
if(mode == 0) store(Half, rn, rd);
if(pre == 0) rn = up ? rn + rm : rn - rm;
if(pre == 0 || writeback) r(n) = rn;
if(mode == 1) r(d) = rd;
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveImmediateOffset
@@ -173,46 +153,47 @@ auto ARM7TDMI::armInstructionMoveImmediateOffset
n32 rd = r(d) + (d == 15 ? 4 : 0);
if(pre == 1) rn = up ? rn + immediate : rn - immediate;
if(mode == 1) rd = load((byte ? Byte : Word) | Nonsequential, rn);
if(mode == 0) store((byte ? Byte : Word) | Nonsequential, rn, rd);
if(mode == 1) rd = load((byte ? Byte : Word), rn);
if(mode == 0) store((byte ? Byte : Word), rn, rd);
if(pre == 0) rn = up ? rn + immediate : rn - immediate;
if(pre == 0 || writeback) r(n) = rn;
if(mode == 1) r(d) = rd;
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveMultiple
(n16 list, n4 n, n1 mode, n1 writeback, n1 type, n1 up, n1 pre) -> void {
n32 rn = r(n);
n32 bitCount = list ? bit::count(list) : 16;
n32 rnEnd;
if(up == 1) rnEnd = r(n) + bitCount * 4; //IA,IB
if(up == 0) rnEnd = r(n) - bitCount * 4; //DA,DB
if(pre == 0 && up == 1) rn = rn + 0; //IA
if(pre == 1 && up == 1) rn = rn + 4; //IB
if(pre == 1 && up == 0) rn = rn - bitCount * 4 + 0; //DB
if(pre == 0 && up == 0) rn = rn - bitCount * 4 + 4; //DA
if(writeback && mode == 1 && !list.bit(n)) {
if(up == 1) r(n) = r(n) + bitCount * 4; //IA,IB
if(up == 0) r(n) = r(n) - bitCount * 4; //DA,DB
}
auto cpsrMode = cpsr().m;
bool usr = false;
if(type && mode == 1 && !list.bit(15)) usr = true;
if(type && mode == 0) usr = true;
if(usr) cpsr().m = PSR::USR;
u32 sequential = Nonsequential;
if(!list) {
if(mode == 1) r(15) = read(Word | sequential, rn);
if(mode == 0) write(Word | sequential, rn, r(15) + 4);
} else {
for(u32 m : range(16)) {
if(!list.bit(m)) continue;
if(mode == 1) r(m) = read(Word | sequential, rn);
if(mode == 0) write(Word | sequential, rn, r(m) + (m == 15 ? 4 : 0));
rn += 4;
sequential = Sequential;
if(writeback && mode == 1 && !list.bit(n)) r(n) = rnEnd;
endBurst();
if(!list) list.bit(15) = 1;
for(u32 m : range(16)) {
if(!list.bit(m)) continue;
if(mode == 1) r(m) = read(Word, rn);
if(mode == 0) {
write(Word, rn, r(m) + (!(n == 15 && writeback) && m == 15 ? 4 : 0));
if(writeback) r(n) = rnEnd; //writeback occurs after first access
}
rn += 4;
}
if(usr) cpsr().m = cpsrMode;
@@ -223,19 +204,14 @@ auto ARM7TDMI::armInstructionMoveMultiple
cpsr() = spsr();
}
} else {
pipeline.nonsequential = true;
}
if(writeback && mode == 0) {
if(up == 1) r(n) = r(n) + bitCount * 4; //IA,IB
if(up == 0) r(n) = r(n) - bitCount * 4; //DA,DB
endBurst();
}
}
auto ARM7TDMI::armInstructionMoveRegisterOffset
(n4 m, n2 type, n5 shift, n4 d, n4 n, n1 mode, n1 writeback, n1 byte, n1 up, n1 pre) -> void {
n32 rm = r(m);
n32 rd = r(d);
n32 rd = r(d) + (d == 15 ? 4 : 0);
n32 rn = r(n);
carry = cpsr().c;
@@ -247,12 +223,67 @@ auto ARM7TDMI::armInstructionMoveRegisterOffset
}
if(pre == 1) rn = up ? rn + rm : rn - rm;
if(mode == 1) rd = load((byte ? Byte : Word) | Nonsequential, rn);
if(mode == 0) store((byte ? Byte : Word) | Nonsequential, rn, rd);
if(mode == 1) rd = load((byte ? Byte : Word), rn);
if(mode == 0) store((byte ? Byte : Word), rn, rd);
if(pre == 0) rn = up ? rn + rm : rn - rm;
if(pre == 0 || writeback) r(n) = rn;
if(mode == 1) r(d) = rd;
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveSignedImmediate
(n8 immediate, n1 half, n4 d, n4 n, n1 mode, n1 writeback, n1 up, n1 pre) -> void {
n32 rn = r(n);
n32 rd = r(d) + (d == 15 ? 4 : 0);
if(pre == 1) rn = up ? rn + immediate : rn - immediate;
if(mode == 1) rd = load((half ? Half : Byte) | Signed, rn);
if(mode == 0) store((half ? Half : Byte) | Signed, rn, rd);
if(pre == 0) rn = up ? rn + immediate : rn - immediate;
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveSignedRegister
(n4 m, n1 half, n4 d, n4 n, n1 mode, n1 writeback, n1 up, n1 pre) -> void {
n32 rn = r(n);
n32 rm = r(m);
n32 rd = r(d) + (d == 15 ? 4 : 0);
if(pre == 1) rn = up ? rn + rm : rn - rm;
if(mode == 1) rd = load((half ? Half : Byte) | Signed, rn);
if(mode == 0) store((half ? Half : Byte) | Signed, rn, rd);
if(pre == 0) rn = up ? rn + rm : rn - rm;
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveToCoprocessorFromRegister
(n4 cm, n3 op2, n4 cpid, n4 d, n4 cn, n3 op1) -> void {
if(!MCR[cpid]) return armInstructionUndefined();
MCR[cpid](r(d), cm, op2, cn, op1);
}
auto ARM7TDMI::armInstructionMoveToRegisterFromCoprocessor
(n4 cm, n3 op2, n4 cpid, n4 d, n4 cn, n3 op1) -> void {
if(!MRC[cpid]) return armInstructionUndefined();
r(d) = MRC[cpid](cm, op2, cn, op1);
}
auto ARM7TDMI::armInstructionMoveToRegisterFromRegister
(n4 d, n4 n) -> void {
r(d) = r(n);
}
auto ARM7TDMI::armInstructionMoveToRegisterFromStatus
@@ -268,14 +299,25 @@ auto ARM7TDMI::armInstructionMoveToStatusFromImmediate
}
auto ARM7TDMI::armInstructionMoveToStatusFromRegister
(n4 m, n4 field, n1 mode) -> void {
armMoveToStatus(field, mode, r(m));
(n4 m, n2 type, n5 shift, n4 field, n1 mode) -> void {
n32 rm = r(m);
carry = cpsr().c;
switch(type) {
case 0: rm = LSL(rm, shift); break;
case 1: rm = LSR(rm, shift ? (u32)shift : 32); break;
case 2: rm = ASR(rm, shift ? (u32)shift : 32); break;
case 3: rm = shift ? ROR(rm, shift) : RRX(rm); break;
}
armMoveToStatus(field, mode, rm);
}
auto ARM7TDMI::armInstructionMultiply
(n4 m, n4 s, n4 n, n4 d, n1 save, n1 accumulate) -> void {
if(accumulate) idle();
r(d) = MUL(accumulate ? r(n) : 0, r(m), r(s));
n32 product = MUL(accumulate ? r(n) : 0, r(m), r(s));
if(d != 15) r(d) = product;
}
auto ARM7TDMI::armInstructionMultiplyLong
@@ -302,8 +344,8 @@ auto ARM7TDMI::armInstructionMultiplyLong
n64 rd = rm * rs;
if(accumulate) rd += (n64)r(h) << 32 | (n64)r(l) << 0;
r(l) = rd >> 0;
r(h) = rd >> 32;
if(l != 15) r(l) = rd >> 0;
if(h != 15) r(h) = rd >> 32;
if(save) {
cpsr().z = rd == 0;
@@ -1,14 +1,15 @@
auto ARM7TDMI::thumbInstructionALU
(n3 d, n3 m, n4 mode) -> void {
carry = cpsr().c;
switch(mode) {
case 0: r(d) = BIT(r(d) & r(m)); break; //AND
case 1: r(d) = BIT(r(d) ^ r(m)); break; //EOR
case 2: r(d) = BIT(LSL(r(d), r(m))); break; //LSL
case 3: r(d) = BIT(LSR(r(d), r(m))); break; //LSR
case 4: r(d) = BIT(ASR(r(d), r(m))); break; //ASR
case 2: r(d) = BIT(LSL(r(d), r(m))); idle(); break; //LSL
case 3: r(d) = BIT(LSR(r(d), r(m))); idle(); break; //LSR
case 4: r(d) = BIT(ASR(r(d), r(m))); idle(); break; //ASR
case 5: r(d) = ADD(r(d), r(m), cpsr().c); break; //ADC
case 6: r(d) = SUB(r(d), r(m), cpsr().c); break; //SBC
case 7: r(d) = BIT(ROR(r(d), r(m))); break; //ROR
case 7: r(d) = BIT(ROR(r(d), r(m))); idle(); break; //ROR
case 8: BIT(r(d) & r(m)); break; //TST
case 9: r(d) = SUB(0, r(m), 1); break; //NEG
case 10: SUB(r(d), r(m), 1); break; //CMP
@@ -24,7 +25,7 @@ auto ARM7TDMI::thumbInstructionALUExtended
(n4 d, n4 m, n2 mode) -> void {
switch(mode) {
case 0: r(d) = r(d) + r(m); break; //ADD
case 1: SUB(r(d), r(m), 1); break; //SUBS
case 1: SUB(r(d), r(m), 1); break; //CMP
case 2: r(d) = r(m); break; //MOV
}
}
@@ -92,6 +93,7 @@ auto ARM7TDMI::thumbInstructionBranchTest
auto ARM7TDMI::thumbInstructionImmediate
(n8 immediate, n3 d, n2 mode) -> void {
carry = cpsr().c;
switch(mode) {
case 0: r(d) = BIT(immediate); break; //MOV
case 1: SUB(r(d), immediate, 1); break; //CMP
@@ -103,81 +105,81 @@ auto ARM7TDMI::thumbInstructionImmediate
auto ARM7TDMI::thumbInstructionLoadLiteral
(n8 displacement, n3 d) -> void {
n32 address = (r(15) & ~3) + (displacement << 2);
r(d) = load(Word | Nonsequential, address);
r(d) = load(Word, address);
idle();
}
auto ARM7TDMI::thumbInstructionMoveByteImmediate
(n3 d, n3 n, n5 offset, n1 mode) -> void {
switch(mode) {
case 0: store(Byte | Nonsequential, r(n) + offset, r(d)); break; //STRB
case 1: r(d) = load(Byte | Nonsequential, r(n) + offset); break; //LDRB
case 0: store(Byte, r(n) + offset, r(d)); break; //STRB
case 1: r(d) = load(Byte, r(n) + offset); idle(); break; //LDRB
}
}
auto ARM7TDMI::thumbInstructionMoveHalfImmediate
(n3 d, n3 n, n5 offset, n1 mode) -> void {
switch(mode) {
case 0: store(Half | Nonsequential, r(n) + offset * 2, r(d)); break; //STRH
case 1: r(d) = load(Half | Nonsequential, r(n) + offset * 2); break; //LDRH
case 0: store(Half, r(n) + offset * 2, r(d)); break; //STRH
case 1: r(d) = load(Half, r(n) + offset * 2); idle(); break; //LDRH
}
}
auto ARM7TDMI::thumbInstructionMoveMultiple
(n8 list, n3 n, n1 mode) -> void {
n32 rn = r(n);
n32 bitCount = list ? bit::count(list) : 16;
n16 rlist = list;
n32 bitCount = rlist ? bit::count(rlist) : 16;
n32 rnEnd = r(n) + bitCount * 4;
if(mode == 1 && !list.bit(n)) r(n) = r(n) + bitCount * 4;
if(mode == 1 && !rlist.bit(n)) r(n) = rnEnd;
u32 sequential = Nonsequential;
if(!list) {
if(mode == 1) r(15) = read(Word | sequential, rn);
if(mode == 0) write(Word | sequential, rn, r(15) + 2);
} else {
for(u32 m : range(8)) {
if(!list.bit(m)) continue;
if(mode == 1) r(m) = read(Word | sequential, rn); //LDMIA
if(mode == 0) write(Word | sequential, rn, r(m)); //STMIA
rn += 4;
sequential = Sequential;
endBurst();
if(!rlist) rlist.bit(15) = 1;
for(u32 m : range(16)) {
if(!rlist.bit(m)) continue;
if(mode == 1) r(m) = read(Word, rn); //LDMIA
if(mode == 0) {
write(Word, rn, r(m) + (m == 15 ? 2 : 0)); //STMIA
r(n) = rnEnd; //writeback occurs after first access
}
rn += 4;
}
if(mode) {
idle();
} else {
pipeline.nonsequential = true;
r(n) = r(n) + bitCount * 4;
endBurst();
}
}
auto ARM7TDMI::thumbInstructionMoveRegisterOffset
(n3 d, n3 n, n3 m, n3 mode) -> void {
switch(mode) {
case 0: store(Word | Nonsequential, r(n) + r(m), r(d)); break; //STR
case 1: store(Half | Nonsequential, r(n) + r(m), r(d)); break; //STRH
case 2: store(Byte | Nonsequential, r(n) + r(m), r(d)); break; //STRB
case 3: r(d) = load(Byte | Nonsequential | Signed, r(n) + r(m)); break; //LDSB
case 4: r(d) = load(Word | Nonsequential, r(n) + r(m)); break; //LDR
case 5: r(d) = load(Half | Nonsequential, r(n) + r(m)); break; //LDRH
case 6: r(d) = load(Byte | Nonsequential, r(n) + r(m)); break; //LDRB
case 7: r(d) = load(Half | Nonsequential | Signed, r(n) + r(m)); break; //LDSH
case 0: store(Word, r(n) + r(m), r(d)); break; //STR
case 1: store(Half, r(n) + r(m), r(d)); break; //STRH
case 2: store(Byte, r(n) + r(m), r(d)); break; //STRB
case 3: r(d) = load(Byte | Signed, r(n) + r(m)); idle(); break; //LDSB
case 4: r(d) = load(Word, r(n) + r(m)); idle(); break; //LDR
case 5: r(d) = load(Half, r(n) + r(m)); idle(); break; //LDRH
case 6: r(d) = load(Byte, r(n) + r(m)); idle(); break; //LDRB
case 7: r(d) = load(Half | Signed, r(n) + r(m)); idle(); break; //LDSH
}
}
auto ARM7TDMI::thumbInstructionMoveStack
(n8 immediate, n3 d, n1 mode) -> void {
switch(mode) {
case 0: store(Word | Nonsequential, r(13) + immediate * 4, r(d)); break; //STR
case 1: r(d) = load(Word | Nonsequential, r(13) + immediate * 4); break; //LDR
case 0: store(Word, r(13) + immediate * 4, r(d)); break; //STR
case 1: r(d) = load(Word, r(13) + immediate * 4); idle(); break; //LDR
}
}
auto ARM7TDMI::thumbInstructionMoveWordImmediate
(n3 d, n3 n, n5 offset, n1 mode) -> void {
switch(mode) {
case 0: store(Word | Nonsequential, r(n) + offset * 4, r(d)); break; //STR
case 1: r(d) = load(Word | Nonsequential, r(n) + offset * 4); break; //LDR
case 0: store(Word, r(n) + offset * 4, r(d)); break; //STR
case 1: r(d) = load(Word, r(n) + offset * 4); idle(); break; //LDR
}
}
@@ -198,33 +200,28 @@ auto ARM7TDMI::thumbInstructionSoftwareInterrupt
auto ARM7TDMI::thumbInstructionStackMultiple
(n8 list, n1 lrpc, n1 mode) -> void {
n32 sp = r(13);
n32 bitCount = (list | lrpc) ? (bit::count(list) + lrpc) : 16;
n16 rlist = list;
if(lrpc) {
if(mode == 1) rlist.bit(15) = 1; //POP
if(mode == 0) rlist.bit(14) = 1; //PUSH
}
n32 bitCount = rlist ? bit::count(rlist) : 16;
if(mode == 1) r(13) = r(13) + bitCount * 4; //POP
if(mode == 0) sp = sp - bitCount * 4 + 0; //PUSH
u32 sequential = Nonsequential;
if(bitCount == 16) {
if(mode == 1) r(15) = read(Word | sequential, sp); //POP
if(mode == 0) write(Word | sequential, sp, r(15) + 2); //PUSH
} else {
for(u32 m : range(8)) {
if(!list.bit(m)) continue;
if(mode == 1) r(m) = read(Word | sequential, sp); //POP
if(mode == 0) write(Word | sequential, sp, r(m)); //PUSH
sp += 4;
sequential = Sequential;
}
}
if(lrpc) {
if(mode == 1) r(15) = read(Word | sequential, sp); //POP
if(mode == 0) write(Word | sequential, sp, r(14)); //PUSH
endBurst();
if(!rlist) rlist.bit(15) = 1;
for(u32 m : range(16)) {
if(!rlist.bit(m)) continue;
if(mode == 1) r(m) = read(Word, sp); //POP
if(mode == 0) write(Word, sp, r(m) + (m == 15 ? 2 : 0)); //PUSH
sp += 4;
}
if(mode) {
idle();
} else {
pipeline.nonsequential = true;
endBurst();
r(13) = r(13) - bitCount * 4; //PUSH
}
}
+9 -7
View File
@@ -1,14 +1,16 @@
auto ARM7TDMI::idle() -> void {
pipeline.nonsequential = true;
endBurst();
sleep();
}
auto ARM7TDMI::read(u32 mode, n32 address) -> n32 {
return get(mode, address);
n32 word = get(mode, address);
nonsequential = false; //allows burst transfer to continue
return word;
}
auto ARM7TDMI::load(u32 mode, n32 address) -> n32 {
pipeline.nonsequential = true;
endBurst();
auto word = get(Load | mode, address);
if(mode & Half) {
address &= 1;
@@ -23,17 +25,17 @@ auto ARM7TDMI::load(u32 mode, n32 address) -> n32 {
} else {
word = ROR(word, address.bit(0,1) << 3);
}
idle();
return word;
}
auto ARM7TDMI::write(u32 mode, n32 address, n32 word) -> void {
pipeline.nonsequential = true;
return set(mode, address, word);
set(mode, address, word);
nonsequential = false; //allows burst transfer to continue
return;
}
auto ARM7TDMI::store(u32 mode, n32 address, n32 word) -> void {
pipeline.nonsequential = true;
endBurst();
if(mode & Half) { word &= 0xffff; word |= word << 16; }
if(mode & Byte) { word &= 0xff; word |= word << 8; word |= word << 16; }
return set(Store | mode, address, word);
@@ -14,20 +14,24 @@ inline auto ARM7TDMI::r(n4 index) -> GPR& {
case 11: return processor.cpsr.m == PSR::FIQ ? processor.fiq.r11 : processor.r11;
case 12: return processor.cpsr.m == PSR::FIQ ? processor.fiq.r12 : processor.r12;
case 13: switch(processor.cpsr.m) {
case PSR::USR: return processor.r13;
case PSR::FIQ: return processor.fiq.r13;
case PSR::IRQ: return processor.irq.r13;
case PSR::SVC: return processor.svc.r13;
case PSR::ABT: return processor.abt.r13;
case PSR::UND: return processor.und.r13;
default: return processor.r13;
case PSR::SYS: return processor.r13;
default: return processor.rNULL;
}
case 14: switch(processor.cpsr.m) {
case PSR::USR: return processor.r14;
case PSR::FIQ: return processor.fiq.r14;
case PSR::IRQ: return processor.irq.r14;
case PSR::SVC: return processor.svc.r14;
case PSR::ABT: return processor.abt.r14;
case PSR::UND: return processor.und.r14;
default: return processor.r14;
case PSR::SYS: return processor.r14;
default: return processor.rNULL;
}
case 15: return processor.r15;
}
@@ -46,7 +50,7 @@ inline auto ARM7TDMI::spsr() -> PSR& {
case PSR::ABT: return processor.abt.spsr;
case PSR::UND: return processor.und.spsr;
}
throw;
return processor.spsrNULL;
}
inline auto ARM7TDMI::privileged() const -> bool {
@@ -3,6 +3,7 @@ auto ARM7TDMI::serialize(serializer& s) -> void {
s(pipeline);
s(carry);
s(irq);
s(nonsequential);
}
auto ARM7TDMI::Processor::serialize(serializer& s) -> void {
@@ -23,6 +24,8 @@ auto ARM7TDMI::Processor::serialize(serializer& s) -> void {
s(r14.data);
s(r15.data);
s(cpsr);
s(rNULL.data);
s(spsrNULL);
s(fiq.r8.data);
s(fiq.r9.data);
s(fiq.r10.data);
@@ -54,11 +57,11 @@ auto ARM7TDMI::PSR::serialize(serializer& s) -> void {
s(c);
s(z);
s(n);
s(readonly);
}
auto ARM7TDMI::Pipeline::serialize(serializer& s) -> void {
s(reload);
s(nonsequential);
s(fetch.address);
s(fetch.instruction);
s(fetch.thumb);
@@ -513,19 +513,19 @@ auto SH2::MOVI(u32 i, u32 n) -> void {
}
//MOV.W @(disp,PC),Rn
NALL_NOINLINE auto SH2::MOVWI(u32 d, u32 n) -> void {
auto SH2::MOVWI(u32 d, u32 n) -> void {
u32 pc = inDelaySlot() ? PPC - 2 : PC;
R[n] = (s16)readWord(pc + d * 2);
}
//MOV.L @(disp,PC),Rn
NALL_NOINLINE auto SH2::MOVLI(u32 d, u32 n) -> void {
auto SH2::MOVLI(u32 d, u32 n) -> void {
u32 pc = inDelaySlot() ? PPC - 2 : PC;
R[n] = readLong((pc & ~3) + d * 4);
}
//MOVA @(disp,PC),R0
NALL_NOINLINE auto SH2::MOVA(u32 d) -> void {
auto SH2::MOVA(u32 d) -> void {
u32 pc = inDelaySlot() ? PPC - 2 : PC;
R[0] = (pc & ~3) + d * 4;
}
@@ -150,6 +150,7 @@ auto SH2::internalReadByte(u32 address, n8 data) -> n8 {
data.bit(5) = wdt.wtcsr.tme;
data.bit(6) = wdt.wtcsr.wtit;
data.bit(7) = wdt.wtcsr.ovf;
if(wdt.wtcsr.wtit) debug(unimplemented, "SH2: wdt.wtit = 1");
return data;
//WTCNT: watchdog timer counter
@@ -42,17 +42,20 @@ auto V30MZ::interrupt(u8 vector, InterruptSource source) -> bool {
auto V30MZ::interrupt(u8 vector) -> bool {
state.halt = false;
if(!PSW.IE) return false;
if(!state.interrupt || !PSW.IE) return false;
return interrupt(vector, InterruptSource::INT);
}
auto V30MZ::nonMaskableInterrupt() -> bool {
return interrupt(2, InterruptSource::NMI);
auto V30MZ::nonMaskableInterrupt(bool value) -> bool {
state.nmi = value;
return true;
}
#define op(id, name, ...) case id: instruction##name(__VA_ARGS__); break;
auto V30MZ::instruction() -> void {
state.brk = PSW.BRK;
state.interrupt = PSW.IE;
state.poll = 1;
state.prefix = 0;
if(state.halt) return wait(1);
@@ -317,7 +320,8 @@ auto V30MZ::instruction() -> void {
}
if(!state.prefix) prefixFlush();
if(PSW.BRK) interrupt(1, InterruptSource::SingleStep);
if(state.poll && state.nmi) interrupt(2, InterruptSource::NMI);
if(state.poll && state.brk) interrupt(1, InterruptSource::SingleStep);
}
#undef op
@@ -105,7 +105,6 @@ auto V30MZ::instructionReturnInt() -> void {
PS = pop();
PSW = pop();
flush();
state.poll = 0;
}
auto V30MZ::instructionInt3() -> void {
@@ -182,7 +181,6 @@ auto V30MZ::instructionPushFlags() -> void {
auto V30MZ::instructionPopFlags() -> void {
wait(2);
PSW = pop();
state.poll = 0;
}
auto V30MZ::instructionPushAll() -> void {
@@ -21,5 +21,4 @@ auto V30MZ::instructionClearFlag(u32 bit) -> void {
auto V30MZ::instructionSetFlag(u32 bit) -> void {
wait(4);
PSW |= 1 << bit;
if(bit == PSW.IE.bit()) state.poll = 0;
}
@@ -43,21 +43,27 @@ auto V30MZ::instructionUndefined1() -> void {
}
template<u32 size> auto V30MZ::instructionIn() -> void {
wait(6);
// TODO: The exact cycle on which I/O access is performed remains unknown.
wait(5);
setAccumulator<size>(in<size>(fetch<Byte>()));
wait(1);
}
template<u32 size> auto V30MZ::instructionOut() -> void {
// TODO: The exact cycle on which I/O access is performed remains unknown.
wait(6);
out<size>(fetch<Byte>(), getAccumulator<size>());
}
template<u32 size> auto V30MZ::instructionInDW() -> void {
wait(5);
// TODO: The exact cycle on which I/O access is performed remains unknown.
wait(4);
setAccumulator<size>(in<size>(DW));
wait(1);
}
template<u32 size> auto V30MZ::instructionOutDW() -> void {
// TODO: The exact cycle on which I/O access is performed remains unknown.
wait(5);
out<size>(DW, getAccumulator<size>());
}
@@ -14,14 +14,14 @@ auto V30MZ::instructionMoveMemSeg() -> void {
wait(1);
modRM();
setMemory<Word>(getSegment());
state.poll = 0;
}
auto V30MZ::instructionMoveSegMem() -> void {
wait(2);
modRM();
setSegment(getMemory<Word>());
if((modrm.reg & 3) == 3) state.poll = 0;
// delay interrupt polling if stack segment changed
if((modrm.reg & 3) == 2) state.poll = 0;
}
template<u32 size> auto V30MZ::instructionMoveAccMem() -> void {
@@ -2,6 +2,9 @@ auto V30MZ::serialize(serializer& s) -> void {
s(state.halt);
s(state.poll);
s(state.prefix);
s(state.interrupt);
s(state.brk);
s(state.nmi);
s(opcode);
+3
View File
@@ -26,6 +26,9 @@ auto V30MZ::power() -> void {
state.halt = 0;
state.poll = 1;
state.prefix = 0;
state.interrupt = 0;
state.brk = 0;
state.nmi = 0;
opcode = 0;
prefixFlush();
+7 -4
View File
@@ -41,7 +41,7 @@ struct V30MZ {
//instruction.cpp
auto prefixFlush() -> void;
auto interrupt(u8 vector) -> bool;
auto nonMaskableInterrupt() -> bool;
auto nonMaskableInterrupt(bool value) -> bool;
auto instruction() -> void;
//registers.cpp
@@ -237,9 +237,12 @@ struct V30MZ {
auto disassembleContext() -> string;
struct State {
bool halt; //set to true for hlt instruction; blocks execution until next interrupt
bool poll; //set to false to suppress interrupt polling between CPU instructions
bool prefix; //set to true for prefix instructions; prevents flushing of Prefix struct
bool halt; //set to true for hlt instruction; blocks execution until next interrupt
bool poll; //set to false to suppress interrupt polling between CPU instructions
bool prefix; //set to true for prefix instructions; prevents flushing of Prefix struct
bool interrupt; //interrupt enable processor status before CPU instruction execution
bool brk; //breakpoint enable processor status before CPU instruction execution
bool nmi; //non-maskable interrupt requested
} state;
u8 opcode;
+286
View File
@@ -0,0 +1,286 @@
#include <ares/ares.hpp>
#include "s3511a.hpp"
namespace ares {
#include "serialization.cpp"
//calculate time between last play of game and current time;
//increment RTC by said amount of seconds
auto S3511A::load() -> void {
n64 timestamp = 0;
for(auto n : range(8)) timestamp.byte(n) = data[8 + n];
if(!timestamp || !(timestamp + 1)) return initRegs(false); //new save file
if(status() & 0x15) {
//these status bits are always 0; reset state on invalid status.
initRegs(false);
return;
}
timestamp = time(0) - timestamp;
//prevent insurmountable slowdown by limiting skips to 5 years
if(timestamp < 60*60*24*365*5) {
while(timestamp--) tickSecond();
}
}
//save time when game is unloaded
auto S3511A::save() -> void {
n64 timestamp = time(0);
for(auto n : range(8)) data[8 + n] = timestamp.byte(n);
}
auto S3511A::power() -> void {
cs = 0;
sio = 0;
sck = 0;
inBuffer = 0;
outBuffer = 0;
shift = 0;
index = 0;
rwSelect = 0;
regSelect = 0;
cmdLatched = 0;
counter = 0;
}
auto S3511A::tickSecond() -> void {
static auto bcdIncrement = [](n8& data) -> n8 {
if ((data & 0x0F) >= 0x09) {
data = (data & 0xF0) + 0x10;
} else {
data++;
}
return data;
};
if(bcdIncrement(second()) < 0x60) return;
second() = 0;
if(bcdIncrement(minute()) < 0x60) return;
minute() = 0;
if(status() & 0x40) {
//24-hour clock
if((bcdIncrement(hour()) & 0x7F) < 0x24) return;
hour().bit(0,5) = 0;
} else {
//12-hour clock
if((bcdIncrement(hour()) & 0x7F) < 0x12) return;
hour().bit(0,5) = 0;
hour().bit(7) ^= 1;
}
weekday() += 1;
weekday() %= 7;
u32 bcdDaysInMonth[12] = {0x31, 0x28, 0x31, 0x30, 0x31, 0x30, 0x31, 0x31, 0x30, 0x31, 0x30, 0x31};
if(year() && !(year() & 3)) bcdDaysInMonth[1]++;
if(bcdIncrement(day()) <= bcdDaysInMonth[month()]) return;
day() = 1;
if(bcdIncrement(month()) <= 0x12) return;
month() = 1;
bcdIncrement(year());
}
auto S3511A::checkAlarm() -> void {
//todo: a lot of this is vaguely informed guesswork.
if(status() & 0x08) {
//per-minute edge/steady
if(counter < 256) irqLevel(1);
if(status() & 0x02 && second() == 0x30 && counter == 0) irqLevel(0);
} else if(status() & 0x02) {
//selected frequency steady
n16 duty = (counter << 1) ^ 0xFFFF;
n16 mask = (alarmHour() | (alarmMinute() << 8));
irqLevel((duty & mask) != 0);
} else if(status() & 0x20) {
//alarm
if(status() & 0x40) {
//24-hour clock
irqLevel((hour() & 0x3F) == (alarmHour() & 0x3F) && (minute() & 0x7F) == (alarmMinute() & 0x7F));
} else {
//12-hour clock
irqLevel((hour() & 0xBF) == (alarmHour() & 0xBF) && (minute() & 0x7F) == (alarmMinute() & 0x7F));
}
} else {
irqLevel(0);
}
}
auto S3511A::readSIO() -> n1 {
return sio | ~rwSelect | ~cs;
}
auto S3511A::writeCS(n1 data) -> void {
n1 csPrev = cs;
cs = data;
//clear command status on falling edge
if(csPrev == 1 && cs == 0) {
inBuffer = 0x00;
shift = 0;
index = 0;
rwSelect = 0;
cmdLatched = 0;
}
}
auto S3511A::writeSIO(n1 data) -> void {
sio = data;
}
auto S3511A::writeSCK(n1 data) -> void {
n1 sckPrev = sck | ~cs;
sck = data;
//only read SIO data if CS is raised
if(!cs) return;
//process command and output data on falling edge of SCK
if(sckPrev == 1 && sck == 0) {
if(!cmdLatched && shift == 0) writeCommand();
if(cmdLatched && rwSelect == 1 && shift == 0) readRegister();
sio = outBuffer.bit(shift);
}
//sample RTC command data on rising edge of SCK
if(sckPrev == 0 && sck == 1) {
//add bit into input buffer
inBuffer.bit(shift) = sio;
shift++;
if(cmdLatched && rwSelect == 0 && shift == 0) writeRegister();
}
}
auto S3511A::writeCommand() -> void {
n4 magic = inBuffer >> 4;
n4 cmd = inBuffer;
if(magic != 0b0110) {
//flip bit order of command
magic = cmd;
cmd.bit(0) = inBuffer.bit(7);
cmd.bit(1) = inBuffer.bit(6);
cmd.bit(2) = inBuffer.bit(5);
cmd.bit(3) = inBuffer.bit(4);
}
if(magic != 0b0110) return; //invalid command
rwSelect = cmd;
regSelect = cmd >> 1;
cmdLatched = 1;
//process any registers that have an effect on write without a payload
if(rwSelect == 1) return;
switch(regSelect) {
case 0:
//RESET
initRegs(true);
break;
default:
break;
}
}
auto S3511A::readRegister() -> void {
switch(regSelect) {
case 1:
//STATUS
outBuffer = status();
break;
case 2: {
//DATETIME
switch(index) {
case 0: outBuffer = year(); index++; break;
case 1: outBuffer = month(); index++; break;
case 2: outBuffer = day(); index++; break;
case 3: outBuffer = weekday(); index++; break;
case 4: outBuffer = hour(); index++; break;
case 5: outBuffer = minute(); index++; break;
default: outBuffer = second(); break;
}
break;
}
case 3: {
//TIME
switch(index) {
case 0: outBuffer = hour(); index++; break;
case 1: outBuffer = minute(); index++; break;
default: outBuffer = second(); break;
}
break;
}
case 4: {
//ALARM
switch(index) {
case 0: outBuffer = alarmHour(); index++; break;
default: outBuffer = alarmMinute(); break;
}
}
default:
outBuffer = 0xff;
break;
}
}
auto S3511A::writeRegister() -> void {
switch(regSelect) {
case 1:
//STATUS
status().bit(1) = inBuffer.bit(1);
status().bit(3) = inBuffer.bit(3);
status().bit(5) = inBuffer.bit(5);
status().bit(6) = inBuffer.bit(6);
status().bit(7) &= inBuffer.bit(7);
break;
case 2: {
//DATETIME
switch(index) {
case 0: year() = inBuffer; index++; break;
case 1: month() = inBuffer; index++; break;
case 2: day() = inBuffer; index++; break;
case 3: weekday() = inBuffer; index++; break;
case 4: hour() = inBuffer; index++; break;
case 5: minute() = inBuffer; index++; break;
default: second().bit(0,6) = inBuffer.bit(0,6); break;
}
break;
}
case 3: {
//TIME
switch(index) {
case 0: hour() = inBuffer; index++; break;
case 1: minute() = inBuffer; index++; break;
default: second().bit(0,6) = inBuffer.bit(0,6); break;
}
break;
}
case 4: {
//ALARM
switch(index) {
case 0: alarmHour() = inBuffer; index++; break;
default: alarmMinute() = inBuffer; break;
}
}
default:
break;
}
}
auto S3511A::initRegs(bool reset) -> void {
year() = 0;
month() = 1;
day() = 1;
weekday() = 0;
hour() = 0;
minute() = 0;
second() = 0;
status() = reset ? 0x00 : 0x82;
alarmHour() = 0x00;
alarmMinute() = reset ? 0x00 : 0x80;
}
}
+64
View File
@@ -0,0 +1,64 @@
#pragma once
namespace ares {
//Seiko S-3511A Real-Time Clock
struct S3511A {
// bytes 0- 6: date/time data, BCD
// bytes 7- 7: status register
// bytes 8-15: last timestamp
// bytes 16-17: alarm register
static constexpr u32 size = 18;
n8 data[size];
virtual auto irqLevel(bool value) -> void = 0;
//s3511a.cpp
auto load() -> void;
auto save() -> void;
auto power() -> void;
auto tickSecond() -> void;
auto checkAlarm() -> void;
auto readSIO() -> n1;
auto writeCS(n1 data) -> void;
auto writeSIO(n1 data) -> void;
auto writeSCK(n1 data) -> void;
auto writeCommand() -> void;
auto readRegister() -> void;
auto writeRegister() -> void;
auto initRegs(bool reset) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
//pin states
n1 cs;
n1 sio;
n1 sck;
//buffered command data
n8 inBuffer;
n8 outBuffer;
n3 shift;
n3 index;
//internal state
n1 rwSelect;
n3 regSelect;
n1 cmdLatched;
n15 counter;
auto year() -> n8& { return data[ 0]; }
auto month() -> n8& { return data[ 1]; }
auto day() -> n8& { return data[ 2]; }
auto weekday() -> n8& { return data[ 3]; }
auto hour() -> n8& { return data[ 4]; }
auto minute() -> n8& { return data[ 5]; }
auto second() -> n8& { return data[ 6]; }
auto status() -> n8& { return data[ 7]; }
auto alarmHour() -> n8& { return data[16]; }
auto alarmMinute() -> n8& { return data[17]; }
};
}
@@ -0,0 +1,15 @@
auto S3511A::serialize(serializer& s) -> void {
s(array_span<u8>{data, size});
s(cs);
s(sio);
s(sck);
s(inBuffer);
s(outBuffer);
s(shift);
s(index);
s(rwSelect);
s(regSelect);
s(cmdLatched);
s(counter);
}
-17
View File
@@ -1,17 +0,0 @@
ares.components += z80
ares.components += tms9918
ares.components += sn76489
ares.objects += ares-cv-cpu
ares.objects += ares-cv-vdp
ares.objects += ares-cv-psg
ares.objects += ares-cv-system
ares.objects += ares-cv-cartridge
ares.objects += ares-cv-controller
$(object.path)/ares-cv-cpu.o: $(ares.path)/cv/cpu/cpu.cpp
$(object.path)/ares-cv-vdp.o: $(ares.path)/cv/vdp/vdp.cpp
$(object.path)/ares-cv-psg.o: $(ares.path)/cv/psg/psg.cpp
$(object.path)/ares-cv-system.o: $(ares.path)/cv/system/system.cpp
$(object.path)/ares-cv-cartridge.o: $(ares.path)/cv/cartridge/cartridge.cpp
$(object.path)/ares-cv-controller.o: $(ares.path)/cv/controller/controller.cpp
+1
View File
@@ -56,6 +56,7 @@ auto System::load(Node::System& root, string name) -> bool {
}
node = Node::System::create(information.name);
node->setAttribute("configuration", name);
node->setGame({&System::game, this});
node->setRun({&System::run, this});
node->setPower({&System::power, this});
-23
View File
@@ -1,23 +0,0 @@
ares.components += mos6502
ares.components += ym2149
ares.components += ym2413
ares.components += m24c
ares.components += sst39sf0x0
ares.objects += ares-fc-system
ares.objects += ares-fc-controller
ares.objects += ares-fc-expansion
ares.objects += ares-fc-cartridge
ares.objects += ares-fc-cpu
ares.objects += ares-fc-apu
ares.objects += ares-fc-ppu
ares.objects += ares-fc-fds
$(object.path)/ares-fc-system.o: $(ares.path)/fc/system/system.cpp
$(object.path)/ares-fc-controller.o: $(ares.path)/fc/controller/controller.cpp
$(object.path)/ares-fc-expansion.o: $(ares.path)/fc/expansion/expansion.cpp
$(object.path)/ares-fc-cartridge.o: $(ares.path)/fc/cartridge/cartridge.cpp
$(object.path)/ares-fc-cpu.o: $(ares.path)/fc/cpu/cpu.cpp
$(object.path)/ares-fc-apu.o: $(ares.path)/fc/apu/apu.cpp
$(object.path)/ares-fc-ppu.o: $(ares.path)/fc/ppu/ppu.cpp
$(object.path)/ares-fc-fds.o: $(ares.path)/fc/fds/fds.cpp
+1
View File
@@ -61,6 +61,7 @@ auto System::load(Node::System& root, string name) -> bool {
}
node = Node::System::create(information.name);
node->setAttribute("configuration", name);
node->setGame({&System::game, this});
node->setRun({&System::run, this});
node->setPower({&System::power, this});
-16
View File
@@ -1,16 +0,0 @@
ares.components += sm83
ares.components += m93lcx6
ares.objects += ares-gb-system
ares.objects += ares-gb-bus
ares.objects += ares-gb-cartridge
ares.objects += ares-gb-cpu
ares.objects += ares-gb-ppu
ares.objects += ares-gb-apu
$(object.path)/ares-gb-system.o: $(ares.path)/gb/system/system.cpp
$(object.path)/ares-gb-cartridge.o: $(ares.path)/gb/cartridge/cartridge.cpp
$(object.path)/ares-gb-bus.o: $(ares.path)/gb/bus/bus.cpp
$(object.path)/ares-gb-cpu.o: $(ares.path)/gb/cpu/cpu.cpp
$(object.path)/ares-gb-ppu.o: $(ares.path)/gb/ppu/ppu.cpp
$(object.path)/ares-gb-apu.o: $(ares.path)/gb/apu/apu.cpp
+4
View File
@@ -44,6 +44,7 @@ struct APU : Thread {
n11 frequency;
bool counter;
n4 sample;
i16 output;
bool dutyOutput;
n3 phase;
@@ -77,6 +78,7 @@ struct APU : Thread {
n11 frequency;
bool counter;
n4 sample;
i16 output;
bool dutyOutput;
n3 phase;
@@ -105,6 +107,7 @@ struct APU : Thread {
bool counter;
n8 pattern[16];
n4 sample;
i16 output;
u32 length;
u32 period;
@@ -135,6 +138,7 @@ struct APU : Thread {
n3 divisor;
bool counter;
n4 sample;
i16 output;
u32 length;
n3 envelopePeriod;
+16
View File
@@ -1,4 +1,20 @@
auto APU::readIO(u32 cycle, n16 address, n8 data) -> n8 {
if(Model::GameBoyColor()) {
//PCM12
if(address == 0xff76 && cycle == 2) {
data.bit(0,3) = square1.sample;
data.bit(4,7) = square2.sample;
return data;
}
//PCM34
if(address == 0xff77 && cycle == 2) {
data.bit(0,3) = wave.sample;
data.bit(4,7) = noise.sample;
return data;
}
}
if(address < 0xff10 || address > 0xff3f) return data;
//NR10
+2 -1
View File
@@ -16,7 +16,7 @@ auto APU::Noise::run() -> void {
}
}
n4 sample = lfsr & 1 ? 0 : (u32)volume;
sample = lfsr & 1 ? 0 : (u32)volume;
if(!enable) sample = 0;
output = sample;
@@ -79,6 +79,7 @@ auto APU::Noise::serialize(serializer& s) -> void {
s(divisor);
s(counter);
s(sample);
s(output);
s(length);
s(envelopePeriod);
+2 -1
View File
@@ -14,7 +14,7 @@ auto APU::Square1::run() -> void {
}
}
n4 sample = dutyOutput ? (u32)volume : 0;
sample = dutyOutput ? (u32)volume : 0;
if(!enable) sample = 0;
output = sample;
@@ -120,6 +120,7 @@ auto APU::Square1::serialize(serializer& s) -> void {
s(frequency);
s(counter);
s(sample);
s(output);
s(dutyOutput);
s(phase);
+2 -1
View File
@@ -14,7 +14,7 @@ auto APU::Square2::run() -> void {
}
}
n4 sample = dutyOutput ? (u32)volume : 0;
sample = dutyOutput ? (u32)volume : 0;
if(!enable) sample = 0;
output = sample;
@@ -77,6 +77,7 @@ auto APU::Square2::serialize(serializer& s) -> void {
s(frequency);
s(counter);
s(sample);
s(output);
s(dutyOutput);
s(phase);
+2 -1
View File
@@ -12,7 +12,7 @@ auto APU::Wave::run() -> void {
}
static const u32 shift[] = {4, 0, 1, 2}; //0%, 100%, 50%, 25%
n4 sample = patternSample >> shift[volume];
sample = patternSample >> shift[volume];
if(!enable) sample = 0;
output = sample;
@@ -95,6 +95,7 @@ auto APU::Wave::serialize(serializer& s) -> void {
s(counter);
s(pattern);
s(sample);
s(output);
s(length);
s(period);
+3 -3
View File
@@ -61,9 +61,9 @@ auto CPU::unload() -> void {
}
auto CPU::main() -> void {
if(status.hblankPending) {
status.hblankPending = 0;
hblankTrigger();
if(ppu.status.ly < 144 && status.hdmaPending) {
performHdma();
status.hdmaPending = 0;
}
//are interrupts enabled?
+8 -7
View File
@@ -66,19 +66,20 @@ struct CPU : SM83, Thread {
//timing.cpp
auto step() -> void;
auto step(u32 clocks) -> void;
auto timer262144hz() -> void;
auto timer65536hz() -> void;
auto timer16384hz() -> void;
auto timerTick() -> void;
auto timer8192hz() -> void;
auto timer4096hz() -> void;
auto timer1024hz() -> void;
auto hblank() -> void;
auto hblankTrigger() -> void;
auto hblankIn() -> void;
auto hblankOut() -> void;
auto hdmaTrigger(n1 hblank, n1 active) -> void;
auto performHdma() -> void;
struct Status {
n22 clock;
n8 interruptLatch;
n1 hblankPending;
n1 hblank;
n1 hdmaPending;
n1 timerLine;
//$ff00 JOYP
n4 joyp;
+3 -11
View File
@@ -139,16 +139,6 @@ auto CPU::readIO(u32 cycle, n16 address, n8 data) -> n8 {
return data;
}
if(Model::GameBoyColor())
if(address == 0xff76 && cycle == 2) { //???
return 0xff;
}
if(Model::GameBoyColor())
if(address == 0xff77 && cycle == 2) { //???
return 0xff;
}
if(address == 0xffff && cycle == 2) { //IE
return status.interruptEnable;
}
@@ -254,15 +244,17 @@ auto CPU::writeIO(u32 cycle, n16 address, n8 data) -> void {
}
status.dmaLength = data.bit(0,6);
hdmaTrigger(status.hblank, 1);
status.hdmaActive = data.bit(7);
//GDMA
if(!data.bit(7)) {
step(4);
do {
for(u32 loop : range(16)) {
writeDMA(status.dmaTarget++, readDMA(status.dmaSource++, 0xff));
}
step(8 << status.speedDouble);
step(2 << status.speedDouble);
} while(status.dmaLength--);
}
return;
+3 -1
View File
@@ -7,7 +7,9 @@ auto CPU::serialize(serializer& s) -> void {
s(status.clock);
s(status.interruptLatch);
s(status.hblankPending);
s(status.hblank);
s(status.hdmaPending);
s(status.timerLine);
s(status.joyp);
s(status.p14);
+39 -50
View File
@@ -7,14 +7,21 @@ auto CPU::step() -> void {
}
auto CPU::step(u32 clocks) -> void {
//determine which bit of DIV is used to check timer state
n32 timerBit = 9;
if(status.timerClock != 0) timerBit = status.timerClock * 2 + 1;
for(auto n : range(clocks)) {
if(!r.stop) status.div++;
if((n4 )status.div == 0) timer262144hz();
if((n6 )status.div == 0) timer65536hz();
if((n8 )status.div == 0) timer16384hz();
if((n9 )status.div == 0) timer8192hz();
if((n10)status.div == 0) timer4096hz();
if((n12)status.div == 0) timer1024hz();
//tick timer on falling edge
n1 timerLineNew = status.div.bit(timerBit) & status.timerEnable;
if(timerLineNew == 0 && status.timerLine == 1) timerTick();
if((n9 )status.div == 0) timer8192hz(); //serial
if((n12)status.div == 0) timer1024hz(); //joypad
status.timerLine = timerLineNew;
Thread::step(1);
Thread::synchronize();
@@ -25,30 +32,10 @@ auto CPU::step(u32 clocks) -> void {
}
}
auto CPU::timer262144hz() -> void {
if(status.timerEnable && status.timerClock == 1) {
if(++status.tima == 0) {
status.tima = status.tma;
raise(Interrupt::Timer);
}
}
}
auto CPU::timer65536hz() -> void {
if(status.timerEnable && status.timerClock == 2) {
if(++status.tima == 0) {
status.tima = status.tma;
raise(Interrupt::Timer);
}
}
}
auto CPU::timer16384hz() -> void {
if(status.timerEnable && status.timerClock == 3) {
if(++status.tima == 0) {
status.tima = status.tma;
raise(Interrupt::Timer);
}
auto CPU::timerTick() -> void {
if(++status.tima == 0) {
status.tima = status.tma;
raise(Interrupt::Timer);
}
}
@@ -63,31 +50,33 @@ auto CPU::timer8192hz() -> void {
}
}
auto CPU::timer4096hz() -> void {
if(status.timerEnable && status.timerClock == 0) {
if(++status.tima == 0) {
status.tima = status.tma;
raise(Interrupt::Timer);
}
}
}
auto CPU::timer1024hz() -> void {
joypPoll();
}
auto CPU::hblank() -> void {
status.hblankPending = 1;
auto CPU::hblankIn() -> void {
hdmaTrigger(1, status.hdmaActive);
status.hblank = 1;
}
auto CPU::hblankTrigger() -> void {
if(status.hdmaActive && ppu.status.ly < 144) {
for(u32 loop : range(16)) {
writeDMA(status.dmaTarget++, readDMA(status.dmaSource++, 0xff));
if(loop & 1) step(1 << status.speedDouble);
}
if(status.dmaLength-- == 0) {
status.hdmaActive = 0;
}
auto CPU::hblankOut() -> void {
status.hblank = 0;
}
auto CPU::hdmaTrigger(n1 hblank, n1 active) -> void {
//HDMA is triggered on rising edge of combined signal
n1 previousState = status.hdmaActive && status.hblank;
n1 newState = active && hblank;
status.hdmaPending = previousState == 0 && newState == 1;
}
auto CPU::performHdma() -> void {
step(4);
for(u32 loop : range(16)) {
writeDMA(status.dmaTarget++, readDMA(status.dmaSource++, 0xff));
step(2 << status.speedDouble);
}
if(status.dmaLength-- == 0) {
status.hdmaActive = 0;
}
}
+9 -8
View File
@@ -30,15 +30,15 @@ auto PPU::load(Node::Object parent) -> void {
screen->refreshRateHint(4 * 1024 * 1024, 456, 154);
if(Model::GameBoy()) {
screen->colors(1 << 2, {&PPU::colorGameBoy, this});
screen->setFillColor(0);
colorEmulationDMG = screen->append<Node::Setting::String>("Color Emulation", "Game Boy", [&](auto value) {
screen->resetPalette();
});
colorEmulationDMG->setAllowedValues({"Game Boy", "Game Boy Pocket", "RGB"});
colorEmulationDMG->setDynamic(true);
screen->colors(1 << 2, {&PPU::colorGameBoy, this});
screen->setFillColor(0);
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
screen->setInterframeBlending(value);
});
@@ -46,14 +46,14 @@ auto PPU::load(Node::Object parent) -> void {
}
if(Model::GameBoyColor()) {
screen->colors(1 << 15, {&PPU::colorGameBoyColor, this});
screen->setFillColor(0x7fff);
colorEmulationCGB = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
});
colorEmulationCGB->setDynamic(true);
screen->colors(1 << 15, {&PPU::colorGameBoyColor, this});
screen->setFillColor(0x7fff);
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
screen->setInterframeBlending(value);
});
@@ -103,7 +103,6 @@ auto PPU::main() -> void {
step(172);
mode(0);
cpu.hblank();
step(456 - 8 - status.lx);
} else if(status.ly <= 143) {
mode(2);
@@ -123,7 +122,6 @@ auto PPU::main() -> void {
step(12);
mode(0);
cpu.hblank();
step(456 - status.lx);
} else {
mode(1);
@@ -146,6 +144,9 @@ auto PPU::main() -> void {
}
auto PPU::mode(n2 mode) -> void {
if(mode == 0) cpu.hblankIn();
if(mode != 0) cpu.hblankOut();
if(status.mode == 0 && mode != 0) {
if(Model::SuperGameBoy()) superGameBoy->ppuHreset();
}
+1 -1
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v131";
static const string SerializerVersion = "v144";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
+1
View File
@@ -58,6 +58,7 @@ auto System::load(Node::System& root, string name) -> bool {
if(information.name == "Game Boy" || information.name == "Game Boy Color") {
node = Node::System::create(information.name);
node->setAttribute("configuration", name);
node->setGame({&System::game, this});
node->setRun({&System::run, this});
node->setPower({&System::power, this});
+4 -1
View File
@@ -1,4 +1,4 @@
ares_components(arm7tdmi)
ares_components(arm7tdmi s3511a)
ares_add_sources(
CORE #
@@ -47,8 +47,10 @@ ares_add_sources(
cartridge/cartridge.hpp
cartridge/eeprom.cpp
cartridge/flash.cpp
cartridge/gpio.cpp
cartridge/memory.hpp
cartridge/mrom.cpp
cartridge/rtc.cpp
cartridge/serialization.cpp
cartridge/slot.cpp
cartridge/slot.hpp
@@ -60,6 +62,7 @@ ares_add_sources(
gba
INCLUDED #
cpu/bus.cpp
cpu/coprocessor.cpp
cpu/cpu.hpp
cpu/debugger.cpp
cpu/dma.cpp
-19
View File
@@ -1,19 +0,0 @@
ares.components += arm7tdmi
ares.objects += ares-gba-memory
ares.objects += ares-gba-system
ares.objects += ares-gba-cartridge
ares.objects += ares-gba-player
ares.objects += ares-gba-cpu
ares.objects += ares-gba-ppu
ares.objects += ares-gba-apu
ares.objects += ares-gba-display
$(object.path)/ares-gba-memory.o: $(ares.path)/gba/memory/memory.cpp
$(object.path)/ares-gba-system.o: $(ares.path)/gba/system/system.cpp
$(object.path)/ares-gba-cartridge.o: $(ares.path)/gba/cartridge/cartridge.cpp
$(object.path)/ares-gba-player.o: $(ares.path)/gba/player/player.cpp
$(object.path)/ares-gba-cpu.o: $(ares.path)/gba/cpu/cpu.cpp
$(object.path)/ares-gba-ppu.o: $(ares.path)/gba/ppu/ppu.cpp
$(object.path)/ares-gba-apu.o: $(ares.path)/gba/apu/apu.cpp
$(object.path)/ares-gba-display.o: $(ares.path)/gba/display/display.cpp
+10 -7
View File
@@ -93,6 +93,7 @@ struct APU : Thread, IO {
auto clockLength() -> void;
auto read(u32 address) const -> n8;
auto write(u32 address, n8 byte) -> void;
auto freeBank() const -> n1;
auto readRAM(u32 address) const -> n8;
auto writeRAM(u32 address, n8 byte) -> void;
auto power() -> void;
@@ -169,17 +170,19 @@ struct APU : Thread, IO {
//fifo.cpp
auto sample() -> void;
auto read() -> void;
auto write(i8 byte) -> void;
auto write(n2 address, n8 byte) -> void;
auto reset() -> void;
auto power() -> void;
auto size() -> n3 { return wroffset - rdoffset; }
i8 samples[32];
i8 active;
i8 output;
n32 buffer;
n32 samples[8];
i8 active;
i8 output;
n5 rdoffset;
n5 wroffset;
n6 size;
n3 rdoffset;
n3 wroffset;
n3 samplesRead;
n1 volume; //0 = 50%, 1 = 100%
n1 lenable;
+13 -9
View File
@@ -3,25 +3,29 @@ auto APU::FIFO::sample() -> void {
}
auto APU::FIFO::read() -> void {
if(size == 0) return;
size--;
active = samples[rdoffset++];
if(samplesRead < 4) active = buffer.byte(samplesRead++);
if(size() > 0) {
if(samplesRead >= 4) {
samplesRead = 0;
buffer = samples[rdoffset++];
}
}
}
auto APU::FIFO::write(i8 byte) -> void {
if(size == 32) rdoffset++;
else size++;
samples[wroffset++] = byte;
auto APU::FIFO::write(n2 address, n8 byte) -> void {
samples[wroffset].byte(address) = byte;
if(address == 3) wroffset++;
}
auto APU::FIFO::reset() -> void {
for(auto& byte : samples) byte = 0;
buffer = 0;
for(auto& word : samples) word = 0;
active = 0;
output = 0;
rdoffset = 0;
wroffset = 0;
size = 0;
samplesRead = 0;
}
auto APU::FIFO::power() -> void {
+3 -2
View File
@@ -146,6 +146,7 @@ auto APU::readIO(n32 address) -> n8 {
}
auto APU::writeIO(n32 address, n8 data) -> void {
if(!sequencer.masterenable && (address < 0x0400'0080 || address >= 0x0400'00a0)) return;
cpu.synchronize(apu);
switch(address) {
@@ -260,12 +261,12 @@ auto APU::writeIO(n32 address, n8 data) -> void {
//FIFO_A_H
case 0x0400'00a0: case 0x0400'00a1:
case 0x0400'00a2: case 0x0400'00a3:
return fifo[0].write(data);
return fifo[0].write(address, data);
//FIFO_B_L
//FIFO_B_H
case 0x0400'00a4: case 0x0400'00a5:
case 0x0400'00a6: case 0x0400'00a7:
return fifo[1].write(data);
return fifo[1].write(address, data);
}
}
+2 -1
View File
@@ -96,12 +96,13 @@ auto APU::serialize(serializer& s) -> void {
s(sequencer.routput);
for(auto& f : fifo) {
s(f.buffer);
s(f.samples);
s(f.active);
s(f.output);
s(f.rdoffset);
s(f.wroffset);
s(f.size);
s(f.samplesRead);
s(f.volume);
s(f.lenable);
s(f.renable);
+8 -4
View File
@@ -65,16 +65,20 @@ auto APU::Wave::write(u32 address, n8 byte) -> void {
}
}
auto APU::Wave::freeBank() const -> n1 {
return bank && apu.sequencer.masterenable;
}
auto APU::Wave::readRAM(u32 address) const -> n8 {
n8 byte = 0;
byte |= pattern[!bank << 5 | address << 1 | 0] << 4;
byte |= pattern[!bank << 5 | address << 1 | 1] << 0;
byte |= pattern[freeBank() << 5 | address << 1 | 0] << 4;
byte |= pattern[freeBank() << 5 | address << 1 | 1] << 0;
return byte;
}
auto APU::Wave::writeRAM(u32 address, n8 byte) -> void {
pattern[!bank << 5 | address << 1 | 0] = byte >> 4;
pattern[!bank << 5 | address << 1 | 1] = byte >> 0;
pattern[freeBank() << 5 | address << 1 | 0] = byte >> 4;
pattern[freeBank() << 5 | address << 1 | 1] = byte >> 0;
}
auto APU::Wave::power() -> void {
+20 -33
View File
@@ -8,17 +8,17 @@ Cartridge& cartridge = cartridgeSlot.cartridge;
#include "sram.cpp"
#include "eeprom.cpp"
#include "flash.cpp"
#include "gpio.cpp"
#include "rtc.cpp"
#include "serialization.cpp"
Cartridge::Cartridge() {
mrom.data = new n8[mrom.size = 32 * 1024 * 1024];
sram.data = new n8[sram.size = 32 * 1024];
eeprom.data = new n8[eeprom.size = 8 * 1024];
flash.data = new n8[flash.size = 128 * 1024];
}
Cartridge::~Cartridge() {
delete[] mrom.data;
delete[] sram.data;
delete[] eeprom.data;
delete[] flash.data;
@@ -37,7 +37,8 @@ auto Cartridge::connect() -> void {
if(auto fp = pak->read("program.rom")) {
mrom.size = min(32_MiB, fp->size());
fp->read({mrom.data, mrom.size});
mrom.data.allocate(mrom.size >> 1);
mrom.data.load(fp);
mrom.mirror = pak->attribute("mirror").boolean();
}
@@ -57,7 +58,7 @@ auto Cartridge::connect() -> void {
eeprom.mask = mrom.size > 16 * 1024 * 1024 ? 0x0fffff00 : 0x0f000000;
eeprom.test = mrom.size > 16 * 1024 * 1024 ? 0x0dffff00 : 0x0d000000;
for(auto n : range(eeprom.size)) eeprom.data[n] = 0xff;
fp->read({eeprom.data, eeprom.size});
if(!fp->end()) fp->read({eeprom.data, eeprom.size}); //only load save file if already present
}
if(auto fp = pak->read("save.flash")) {
@@ -75,15 +76,23 @@ auto Cartridge::connect() -> void {
fp->read({flash.data, flash.size});
}
if(auto fp = pak->read("time.rtc")) {
has.rtc = true;
for(auto n : range(rtc.size)) rtc.data[n] = 0x00;
if(!fp->end()) fp->read({rtc.data, rtc.size}); //only load save file if already present
rtc.load();
}
power();
}
auto Cartridge::disconnect() -> void {
if(!node) return;
memory::fill<u8>(mrom.data, mrom.size);
mrom.data.reset();
memory::fill<u8>(sram.data, sram.size);
memory::fill<u8>(eeprom.data, eeprom.size);
memory::fill<u8>(flash.data, flash.size);
memory::fill<u8>(rtc.data, rtc.size);
has = {};
pak.reset();
node.reset();
@@ -104,39 +113,17 @@ auto Cartridge::save() -> void {
if(auto fp = pak->write("save.flash")) {
fp->write({flash.data, flash.size});
}
if(auto fp = pak->write("time.rtc")) {
rtc.save();
fp->write({rtc.data, rtc.size});
}
}
auto Cartridge::power() -> void {
eeprom.power();
flash.power();
}
#define RAM_ANALYZE
auto Cartridge::read(u32 mode, n32 address) -> n32 {
if(address < 0x0e00'0000) {
if(has.eeprom && (address & eeprom.mask) == eeprom.test) return eeprom.read();
return mrom.read(mode, address);
} else {
n32 word = 0xff;
if(has.sram) word = sram.read(address);
if(has.flash) word = flash.read(address);
word |= word << 8;
word |= word << 16;
return word;
}
}
auto Cartridge::write(u32 mode, n32 address, n32 word) -> void {
if(address < 0x0e00'0000) {
if(has.eeprom && (address & eeprom.mask) == eeprom.test) return eeprom.write(word & 1);
return mrom.write(mode, address, word);
} else {
if(mode & Word) word = word >> (8 * (address & 3));
if(mode & Half) word = word >> (8 * (address & 1));
if(has.sram) return sram.write(address, word);
if(has.flash) return flash.write(address, word);
}
if(has.rtc) rtc.power();
}
}
+61 -2
View File
@@ -17,8 +17,66 @@ struct Cartridge {
auto save() -> void;
auto power() -> void;
auto read(u32 mode, n32 address) -> n32;
auto write(u32 mode, n32 address, n32 word) -> void;
template<bool UseDebugger>
auto readRom(u32 mode, n32 address) -> n32 {
if(mode & Word) {
n32 word = 0;
word |= readRom<UseDebugger>(mode & ~Word | Half, (address & ~3) + 0) << 0;
word |= readRom<UseDebugger>(Sequential | Half, (address & ~3) + 2) << 16;
return word;
}
if(has.eeprom && (address & eeprom.mask) == eeprom.test) return eeprom.read();
if(has.rtc) {
if((address & 0x1fffffe) == 0xc4) return gpio.readData();
if((address & 0x1fffffe) == 0xc6) return gpio.readDirection();
if((address & 0x1fffffe) == 0xc8) return gpio.readControl();
}
n32 romAddr;
if constexpr(!UseDebugger) romAddr = mrom.burstAddr(mode, address);
if constexpr( UseDebugger) romAddr = address & 0x1ff'fffe;
n16 half = mrom.read(mode, romAddr);
if constexpr(!UseDebugger) mrom.pageAddr++;
if(mode & Byte) return half.byte(address & 1);
return half;
}
auto readBackup(u32 mode, n32 address) -> n32 {
n32 word = 0xff;
if(has.sram) word = sram.read(address);
if(has.flash) word = flash.read(address);
word *= 0x01010101;
return word;
}
auto writeRom(u32 mode, n32 address, n32 word) -> void {
if(mode & Word) {
writeRom(mode & ~Word | Half, (address & ~3) + 0, word >> 0);
writeRom(Sequential | Half, (address & ~3) + 2, word >> 16);
}
if(has.eeprom && (address & eeprom.mask) == eeprom.test) return eeprom.write(word & 1);
if(has.rtc) {
if((address & 0x1fffffe) == 0xc4) return gpio.writeData(word);
if((address & 0x1fffffe) == 0xc6) return gpio.writeDirection(word);
if((address & 0x1fffffe) == 0xc8) return gpio.writeControl(word);
}
n32 romAddr = mrom.burstAddr(mode, address);
mrom.write(mode, romAddr, word);
mrom.pageAddr++;
return;
}
auto writeBackup(u32 mode, n32 address, n32 word) -> void {
if(mode & Word) word = word >> (8 * (address & 3));
if(mode & Half) word = word >> (8 * (address & 1));
if(has.sram) return sram.write(address, word);
if(has.flash) return flash.write(address, word);
}
auto serialize(serializer&) -> void;
@@ -31,6 +89,7 @@ private:
n1 sram;
n1 eeprom;
n1 flash;
n1 rtc;
} has;
};
+33
View File
@@ -0,0 +1,33 @@
auto Cartridge::GPIO::readData() -> n4 {
if(!readEnable) return 0;
cpu.synchronize(cartridge.rtc);
n4 data = latch & direction;
if(!direction.bit(1)) data.bit(1) = cartridge.rtc.readSIO();
return data;
}
auto Cartridge::GPIO::readDirection() -> n4 {
if(!readEnable) return 0;
return direction;
}
auto Cartridge::GPIO::readControl() -> n1 {
return readEnable;
}
auto Cartridge::GPIO::writeData(n4 data) -> void {
cpu.synchronize(cartridge.rtc);
latch = data;
if(direction.bit(0)) cartridge.rtc.writeSCK(latch >> 0);
if(direction.bit(2)) cartridge.rtc.writeCS( latch >> 2);
if(direction.bit(1)) cartridge.rtc.writeSIO(latch >> 1);
}
auto Cartridge::GPIO::writeDirection(n4 data) -> void {
direction = data;
}
auto Cartridge::GPIO::writeControl(n1 data) -> void {
readEnable = data;
}
+36 -4
View File
@@ -1,15 +1,17 @@
struct MROM {
//mrom.cpp
auto read(u32 mode, n32 address) -> n32;
auto readBus(u32 mode, n32 address) -> n16;
auto write(u32 mode, n32 address, n32 word) -> void;
auto read(u32 mode, n32 address) -> n16;
auto write(u32 mode, n32 address, n16 half) -> void;
auto burstAddr(u32 mode, n32 address) -> n32;
//serialization.cpp
auto serialize(serializer&) -> void;
n8* data = nullptr;
Memory::Readable<n16> data;
u32 size;
u32 mask;
n16 pageAddr;
n1 burst;
bool mirror;
} mrom;
@@ -76,3 +78,33 @@ struct FLASH {
bool writeselect;
bool bank;
} flash;
struct GPIO {
//gpio.cpp
auto readData() -> n4;
auto readDirection() -> n4;
auto readControl() -> n1;
auto writeData(n4 data) -> void;
auto writeDirection(n4 data) -> void;
auto writeControl(n1 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
n4 latch;
n4 direction;
n1 readEnable;
} gpio;
struct RTC : S3511A, Thread {
//rtc.cpp
auto irqLevel(bool value) -> void override;
auto power() -> void;
auto main() -> void;
auto step(u32 clocks) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
bool irq;
} rtc;
+20 -22
View File
@@ -1,28 +1,26 @@
auto Cartridge::MROM::read(u32 mode, n32 address) -> n32 {
if(mode & Word) {
n32 word = 0;
word |= readBus(mode & ~Word | Half, (address & ~3) + 0) << 0;
word |= readBus(mode & ~Word | Half, (address & ~3) + 2) << 16;
return word;
}
return readBus(mode, address);
}
auto Cartridge::MROM::readBus(u32 mode, n32 address) -> n16 {
address &= 0x01ff'ffff;
auto Cartridge::MROM::read(u32 mode, n32 address) -> n16 {
//out-of-bounds accesses
if(address >= size) {
if(!mirror) return (n16)(address >> 1);
address &= size - 1;
if(!mirror) return pageAddr;
address &= (size - 1);
}
if(mode & Half) address &= ~1;
auto p = data + address;
if(mode & Half) return p[0] << 0 | p[1] << 8;
if(mode & Byte) return p[0] << 0;
return 0; //should never occur
return data[address >> 1];
}
auto Cartridge::MROM::write(u32 mode, n32 address, n32 word) -> void {
auto Cartridge::MROM::write(u32 mode, n32 address, n16 half) -> void {
}
auto Cartridge::MROM::burstAddr(u32 mode, n32 address) -> n32 {
//use latched lower 16 bits of address lines if sequential
n8 page = address >> 17;
if(mode & Nonsequential) {
pageAddr = address >> 1;
burst = true;
}
//end burst transfer if requesting last address on page
if((address & 0x1fffe) == 0x1fffe) burst = false;
return (page << 17) | (pageAddr << 1);
}
+22
View File
@@ -0,0 +1,22 @@
auto Cartridge::RTC::irqLevel(bool value) -> void {
//trigger IRQ on rising edge
bool irqPrev = irq;
irq = value;
if(!irqPrev && irq) cpu.setInterruptFlag(CPU::Interrupt::Cartridge);
}
auto Cartridge::RTC::power() -> void {
S3511A::power();
Thread::create(32'768, {&Cartridge::RTC::main, this});
}
auto Cartridge::RTC::main() -> void {
if(++counter == 0) tickSecond();
checkAlarm();
step(1);
}
auto Cartridge::RTC::step(u32 clocks) -> void {
Thread::step(clocks);
synchronize(cpu);
}
+16
View File
@@ -3,11 +3,15 @@ auto Cartridge::serialize(serializer& s) -> void {
if(has.sram) s(sram);
if(has.eeprom) s(eeprom);
if(has.flash) s(flash);
if(has.rtc) s(gpio);
if(has.rtc) s(rtc);
}
auto Cartridge::MROM::serialize(serializer& s) -> void {
s(size);
s(mask);
s(pageAddr);
s(burst);
}
auto Cartridge::SRAM::serialize(serializer& s) -> void {
@@ -40,3 +44,15 @@ auto Cartridge::FLASH::serialize(serializer& s) -> void {
s(writeselect);
s(bank);
}
auto Cartridge::GPIO::serialize(serializer& s) -> void {
s(latch);
s(direction);
s(readEnable);
}
auto Cartridge::RTC::serialize(serializer& s) -> void {
S3511A::serialize(s);
Thread::serialize(s);
s(irq);
}
+65 -20
View File
@@ -1,7 +1,8 @@
auto CPU::sleep() -> void {
dmaRun();
ARM7TDMI::irq = irq.synchronizer;
prefetchStep(1);
if(!dmac.step()) {
ARM7TDMI::irq = irq.synchronizer;
prefetchStep(1);
}
}
template <bool UseDebugger>
@@ -43,19 +44,35 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
word = ppu.readOAM(mode, address);
break;
case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f:
case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d:
if constexpr(UseDebugger) return cartridge.readRom<true>(mode, address);
mode = cartMode(mode, address);
context.romAccess = true;
if(mode & Prefetch && wait.prefetch) {
prefetchSync(address);
prefetchStep(1);
word = prefetchRead();
if(mode & Word) word |= prefetchRead() << 16;
if(address == prefetch.addr && (!prefetch.empty() || prefetch.ahead)) {
prefetchStepInternal(1);
word = prefetchRead();
if(mode & Word) word |= prefetchRead() << 16;
} else {
if(mode & Word) address &= ~3; //prevents misaligned PC from reading incorrect values
prefetchSync(mode, address);
step(waitCartridge(mode, address));
word = cartridge.readRom<false>(mode, address);
}
} else {
if constexpr(!UseDebugger) prefetchReset();
if constexpr(!UseDebugger) step(waitCartridge(mode, address));
word = cartridge.read(mode, address);
if(mode & DMA) dmac.romBurst = true;
prefetchReset();
step(waitCartridge(mode, address));
word = cartridge.readRom<false>(mode, address);
}
break;
case 0x0e: case 0x0f:
if constexpr(!UseDebugger) prefetchReset();
if constexpr(!UseDebugger) step(waitCartridge(mode, address));
word = cartridge.readBackup(mode, address);
break;
default:
if constexpr(!UseDebugger) prefetchStep(1);
return openBus.get(mode, address);
@@ -69,9 +86,12 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
}
auto CPU::get(u32 mode, n32 address) -> n32 {
dmaRun();
if(!(mode & DMA)) dmac.runPending();
ARM7TDMI::irq = irq.synchronizer;
return getBus<false>(mode, address);
context.romAccess = false;
u32 word = getBus<false>(mode, address);
if(!context.romAccess && !(mode & DMA)) cartridge.mrom.burst = false;
return word;
}
auto CPU::getDebugger(u32 mode, n32 address) -> n32 {
@@ -79,8 +99,9 @@ auto CPU::getDebugger(u32 mode, n32 address) -> n32 {
}
auto CPU::set(u32 mode, n32 address, n32 word) -> void {
dmaRun();
if(!(mode & DMA)) dmac.runPending();
ARM7TDMI::irq = irq.synchronizer;
context.romAccess = false;
if(memory.biosSwap && address < 0x0400'0000) address ^= 0x0200'0000;
@@ -117,10 +138,19 @@ auto CPU::set(u32 mode, n32 address, n32 word) -> void {
ppu.writeOAM(mode, address, word);
break;
case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f:
case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d:
mode = cartMode(mode, address);
context.romAccess = true;
if(mode & DMA) dmac.romBurst = true;
prefetchReset();
step(waitCartridge(mode, address));
cartridge.write(mode, address, word);
cartridge.writeRom(mode, address, word);
break;
case 0x0e: case 0x0f:
prefetchReset();
step(waitCartridge(mode, address));
cartridge.writeBackup(mode, address, word);
break;
default:
@@ -130,10 +160,11 @@ auto CPU::set(u32 mode, n32 address, n32 word) -> void {
}
openBus.set(mode, address, word);
if(!context.romAccess && !(mode & DMA)) cartridge.mrom.burst = false;
}
auto CPU::lock() -> void {
dmaRun();
dmac.runPending();
context.busLocked = true;
}
@@ -158,13 +189,27 @@ auto CPU::waitCartridge(u32 mode, n32 address) -> u32 {
}
bool sequential = (mode & Sequential);
if((address & 0x1fffe) == 0) sequential = false; //N cycle on 16-bit ROM crossing 128KB page boundary (RAM S==N)
u32 clocks = sequential ? s : n;
u32 clocks = (mode & Sequential) ? s : n;
if(mode & Word) clocks += s; //16-bit bus requires two transfers for words
return clocks;
}
auto CPU::cartMode(u32 mode, n32 address) -> u32 {
//if no burst transfer is active, start a new burst transfer
if(cartridge.mrom.burst == false) return mode | Nonsequential;
//determine whether sequential access may be performed
if(mode & DMA) {
u32 sequential = dmac.romBurst ? Sequential : Nonsequential;
mode |= sequential;
} else {
u32 sequential = ARM7TDMI::nonsequential ? Nonsequential : Sequential;
mode |= sequential;
}
return mode;
}
auto CPU::OpenBus::get(u32 mode, n32 address) -> n32 {
if(mode & Word) address &= ~3;
if(mode & Half) address &= ~1;
+16
View File
@@ -0,0 +1,16 @@
auto CPU::Coprocessor::vcCDP() -> void {
static bool warnVC = false;
if(!warnVC) {
warnVC = true;
print("Warning: CP0 instruction executed. This likely signifies a Virtual Console ROM which may not function correctly on real hardware.\n");
}
cpu.armInstructionUndefined();
}
auto CPU::Coprocessor::debugMCR() -> void {
return;
}
auto CPU::Coprocessor::debugMRC() -> n32 {
return cpu.openBus.get(Word, 0);
}
+33 -38
View File
@@ -10,6 +10,7 @@ CPU cpu;
#include "dma.cpp"
#include "timer.cpp"
#include "keypad.cpp"
#include "coprocessor.cpp"
#include "debugger.cpp"
#include "serialization.cpp"
@@ -43,9 +44,9 @@ auto CPU::main() -> void {
}
if(halted()) {
dmaRun();
dmac.runPending();
if(!(irq.enable[0] & irq.flag[0])) {
return step(4);
return step(1);
}
step(2);
context.halted = false;
@@ -55,37 +56,27 @@ auto CPU::main() -> void {
instruction();
}
auto CPU::dmaRun() -> void {
if(!context.dmaActive && !context.busLocked) {
context.dmaActive = true;
while(dma[0].run() | dma[1].run() | dma[2].run() | dma[3].run());
if(context.dmaRan) {
idle();
context.dmaRan = false;
context.dmaRomAccess = false;
}
context.dmaActive = false;
}
}
auto CPU::setInterruptFlag(u32 source) -> void {
irq.flag[1] |= source;
}
inline auto CPU::stepIRQ() -> void {
irq.synchronizer = irq.ime[0] && (irq.enable[0] & irq.flag[0]);
irq.enable[0] = irq.enable[1];
irq.flag[0] = irq.flag[1];
irq.ime[0] = irq.ime[1];
if(!dmac.stallingCPU) {
irq.synchronizer = irq.ime[0] && (irq.enable[0] & irq.flag[0]);
irq.enable[0] = irq.enable[1];
irq.flag[0] = irq.flag[1];
irq.ime[0] = irq.ime[1];
}
}
auto CPU::step(u32 clocks) -> void {
if(!clocks) return;
context.hcounter = (context.hcounter + clocks) % 1232;
dma[0].waiting = max(0, dma[0].waiting - (s32)clocks);
dma[1].waiting = max(0, dma[1].waiting - (s32)clocks);
dma[2].waiting = max(0, dma[2].waiting - (s32)clocks);
dma[3].waiting = max(0, dma[3].waiting - (s32)clocks);
dmac.channel[0].waiting = max(0, dmac.channel[0].waiting - (s32)clocks);
dmac.channel[1].waiting = max(0, dmac.channel[1].waiting - (s32)clocks);
dmac.channel[2].waiting = max(0, dmac.channel[2].waiting - (s32)clocks);
dmac.channel[3].waiting = max(0, dmac.channel[3].waiting - (s32)clocks);
for(auto _ : range(clocks)) {
stepIRQ();
@@ -110,11 +101,11 @@ auto CPU::step(u32 clocks) -> void {
Thread::step(clocks);
Thread::synchronize(display, player);
//occasionally synchronize with PPU and APU in case CPU has not recently interacted with them
//occasionally perform a full sync in case CPU has not recently interacted with some component
static u32 counter = 0;
counter += clocks;
if(counter >= 1024) {
Thread::synchronize(ppu, apu);
Thread::synchronize();
counter = 0;
}
}
@@ -123,10 +114,14 @@ auto CPU::power() -> void {
ARM7TDMI::power();
Thread::create(system.frequency(), {&CPU::main, this});
bindCDP( 0, [&](n4 cm, n3 op2, n4 cd, n4 cn, n4 op1) { return coprocessor.vcCDP(); });
bindMCR(14, [&](n32 data, n4 cm, n3 op2, n4 cn, n3 op1) { return coprocessor.debugMCR(); });
bindMRC(14, [&](n4 cm, n3 op2, n4 cn, n3 op1) { return coprocessor.debugMRC(); });
for(auto& byte : iwram) byte = 0x00;
for(auto& byte : ewram) byte = 0x00;
for(auto n : range(4)) dma[n] = {n};
for(auto n : range(4)) dmac.channel[n] = {n};
for(auto n : range(4)) timer[n] = {n};
serial = {};
keypad = {};
@@ -137,21 +132,21 @@ auto CPU::power() -> void {
prefetch = {};
context = {};
dma[0].source.setBits(27); dma[0].latch.source.setBits(27);
dma[0].target.setBits(27); dma[0].latch.target.setBits(27);
dma[0].length.setBits(14); dma[0].latch.length.setBits(14);
dmac.channel[0].source.setBits(27); dmac.channel[0].latch.source.setBits(27);
dmac.channel[0].target.setBits(27); dmac.channel[0].latch.target.setBits(27);
dmac.channel[0].length.setBits(14); dmac.channel[0].latch.length.setBits(14);
dma[1].source.setBits(28); dma[1].latch.source.setBits(28);
dma[1].target.setBits(27); dma[1].latch.target.setBits(27);
dma[1].length.setBits(14); dma[1].latch.length.setBits(14);
dmac.channel[1].source.setBits(28); dmac.channel[1].latch.source.setBits(28);
dmac.channel[1].target.setBits(27); dmac.channel[1].latch.target.setBits(27);
dmac.channel[1].length.setBits(14); dmac.channel[1].latch.length.setBits(14);
dma[2].source.setBits(28); dma[2].latch.source.setBits(28);
dma[2].target.setBits(27); dma[2].latch.target.setBits(27);
dma[2].length.setBits(14); dma[2].latch.length.setBits(14);
dmac.channel[2].source.setBits(28); dmac.channel[2].latch.source.setBits(28);
dmac.channel[2].target.setBits(27); dmac.channel[2].latch.target.setBits(27);
dmac.channel[2].length.setBits(14); dmac.channel[2].latch.length.setBits(14);
dma[3].source.setBits(28); dma[3].latch.source.setBits(28);
dma[3].target.setBits(28); dma[3].latch.target.setBits(28);
dma[3].length.setBits(16); dma[3].latch.length.setBits(16);
dmac.channel[3].source.setBits(28); dmac.channel[3].latch.source.setBits(28);
dmac.channel[3].target.setBits(28); dmac.channel[3].latch.target.setBits(28);
dmac.channel[3].length.setBits(16); dmac.channel[3].latch.length.setBits(16);
for(u32 n = 0x0b0; n <= 0x0df; n++) bus.io[n] = this; //DMA
for(u32 n = 0x100; n <= 0x10f; n++) bus.io[n] = this; //Timers
+49 -29
View File
@@ -46,14 +46,14 @@ struct CPU : ARM7TDMI, Thread, IO {
auto unload() -> void;
auto main() -> void;
auto dmaRun() -> void;
auto setInterruptFlag(u32 source) -> void;
auto stepIRQ() -> void;
auto step(u32 clocks) -> void override;
auto power() -> void;
//prefetch.cpp
auto prefetchSync(n32 address) -> void;
auto prefetchSync(u32 mode, n32 address) -> void;
auto prefetchStepInternal(u32 clocks) -> void;
auto prefetchStep(u32 clocks) -> void;
auto prefetchReset() -> void;
auto prefetchRead() -> n16;
@@ -68,6 +68,7 @@ struct CPU : ARM7TDMI, Thread, IO {
auto unlock() -> void override;
auto waitEWRAM(u32 mode) -> u32;
auto waitCartridge(u32 mode, n32 address) -> u32;
auto cartMode(u32 mode, n32 address) -> u32;
//io.cpp
auto readIO(n32 address) -> n8 override;
@@ -105,36 +106,48 @@ struct CPU : ARM7TDMI, Thread, IO {
n32 mask;
};
struct DMA {
struct DMAC {
//dma.cpp
auto run() -> bool;
auto transfer() -> void;
auto step() -> bool;
auto runPending() -> void;
n2 id;
n1 romBurst;
n1 active;
n2 activeChannel;
n1 stallingCPU;
n1 writeCycle;
n1 active;
i32 waiting;
struct Channel {
auto ready() -> bool;
auto read() -> void;
auto write() -> void;
n2 targetMode;
n2 sourceMode;
n1 repeat;
n1 size;
n1 drq;
n2 timingMode;
n1 irq;
n1 enable;
n2 id;
uintVN source;
uintVN target;
uintVN length;
n1 active;
i32 waiting;
n2 targetMode;
n2 sourceMode;
n1 repeat;
n1 size;
n1 drq;
n2 timingMode;
n1 irq;
n1 enable;
struct Latch {
uintVN source;
uintVN target;
uintVN length;
u32 data;
} latch;
} dma[4];
struct Latch {
uintVN source;
uintVN target;
uintVN length;
u32 data;
} latch;
} channel[4];
} dmac;
struct Timer {
//timer.cpp
@@ -174,7 +187,7 @@ struct CPU : ARM7TDMI, Thread, IO {
n1 irqEnable;
n16 data[4];
n8 data8;
n16 dataMulti;
} serial;
struct Keypad {
@@ -249,22 +262,29 @@ struct CPU : ARM7TDMI, Thread, IO {
auto size() const { return (load - addr) >> 1; }
n16 slot[8];
n32 addr; //read location of slot buffer
n32 load; //write location of slot buffer
n32 addr; //read location of slot buffer
n32 load; //write location of slot buffer
i32 wait = 1; //number of clocks before next slot load
n1 stopped = 1;
n1 ahead;
} prefetch;
struct Coprocessor {
//coprocessor.cpp
auto vcCDP() -> void;
auto debugMCR() -> void;
auto debugMRC() -> n32;
} coprocessor;
struct Context {
n32 clock;
n1 halted;
n1 stopped;
n1 booted; //set to true by the GBA BIOS
n1 dmaRan;
n1 dmaRomAccess;
n1 dmaActive;
n1 romAccess;
n1 timerLatched;
n1 busLocked;
n32 hcounter;
} context;
};
+75 -39
View File
@@ -1,56 +1,88 @@
inline auto CPU::DMA::run() -> bool {
if(!active || waiting) return false;
//returns true if DMA is currently active
auto CPU::DMAC::step() -> bool {
if(!active) {
//DMA cannot start if bus is locked
if(cpu.context.busLocked) return false;
transfer();
if(irq) cpu.setInterruptFlag(CPU::Interrupt::DMA0 << id);
if(drq && id == 3) cpu.setInterruptFlag(CPU::Interrupt::Cartridge);
return true;
//check if DMA is ready
if(channel[0].ready() || channel[1].ready() || channel[2].ready() || channel[3].ready()) {
//transfer control of bus from CPU to DMA
active = true;
cpu.ARM7TDMI::nonsequential = true;
cpu.prefetchStep(1);
return true;
}
return false;
}
//run write transaction, if read has finished prior
if(writeCycle) { channel[activeChannel].write(); return true; }
//run read transaction
if(channel[0].ready()) { channel[0].read(); return true; }
if(channel[1].ready()) { channel[1].read(); return true; }
if(channel[2].ready()) { channel[2].read(); return true; }
if(channel[3].ready()) { channel[3].read(); return true; }
//DMA has finished - hand bus control back to CPU
cpu.prefetchStep(1);
romBurst = false;
activeChannel = 0; //assign burst to a channel that cannot access ROM
active = false;
return false;
}
auto CPU::DMA::transfer() -> void {
u32 seek = size ? 4 : 2;
u32 mode = size ? Word : Half;
mode |= latch.length() == length() ? Nonsequential : Sequential;
auto CPU::DMAC::runPending() -> void {
stallingCPU = true;
while(step());
stallingCPU = false;
}
if(!cpu.context.dmaRan) {
cpu.context.dmaRan = true;
cpu.idle();
inline auto CPU::DMAC::Channel::ready() -> bool {
return active && !waiting;
}
auto CPU::DMAC::Channel::read() -> void {
u32 seek = size ? 4 : 2;
u32 mode = DMA | (size ? Word : Half );
if(cpu.dmac.activeChannel != id) {
//channel has switched - new burst transfer must be started
cpu.dmac.romBurst = 0;
cpu.dmac.activeChannel = id;
}
if(latch.source() < 0x0200'0000) {
cpu.idle(); //cannot access BIOS
cpu.prefetchStep(1); //cannot access BIOS
} else {
n32 addr = latch.source();
if(mode & Word) addr &= ~3;
if(mode & Half) addr &= ~1;
if(addr & 0x0800'0000) cpu.context.dmaRomAccess = true;
latch.data = cpu.get(mode, addr);
if(mode & Half) latch.data |= latch.data << 16;
}
if(mode & Nonsequential) {
if((source() & 0x0800'0000) && (target() & 0x0800'0000)) {
//ROM -> ROM transfer
mode |= Sequential;
mode ^= Nonsequential;
}
}
if(latch.target() < 0x0200'0000) {
cpu.idle(); //cannot access BIOS
} else {
n32 addr = latch.target();
if(mode & Word) addr &= ~3;
if(mode & Half) addr &= ~1;
if(addr & 0x0800'0000) cpu.context.dmaRomAccess = true;
cpu.set(mode, addr, latch.data >> (addr & 2) * 8);
}
switch(sourceMode) {
case 0: latch.source.data += seek; break;
case 1: latch.source.data -= seek; break;
}
cpu.dmac.writeCycle = true;
}
auto CPU::DMAC::Channel::write() -> void {
u32 seek = size ? 4 : 2;
u32 mode = DMA | (size ? Word : Half );
if(latch.target() < 0x0200'0000) {
cpu.prefetchStep(1); //cannot access BIOS
} else {
n32 addr = latch.target();
if(mode & Word) addr &= ~3;
if(mode & Half) addr &= ~1;
cpu.set(mode, addr, latch.data >> (addr & 2) * 8);
}
switch(targetMode) {
case 0: latch.target.data += seek; break;
case 1: latch.target.data -= seek; break;
@@ -64,21 +96,25 @@ auto CPU::DMA::transfer() -> void {
if(repeat == 1) latch.length = length;
if(repeat == 0) enable = false;
}
cpu.dmac.writeCycle = false;
if(irq) cpu.setInterruptFlag(CPU::Interrupt::DMA0 << id);
if(drq && id == 3) cpu.setInterruptFlag(CPU::Interrupt::Cartridge);
}
auto CPU::dmaVblank() -> void {
for(auto& dma : this->dma) {
if(dma.enable && dma.timingMode == 1) dma.active = true;
for(auto& channel : this->dmac.channel) {
if(channel.enable && channel.timingMode == 1) channel.active = true;
}
}
auto CPU::dmaHblank() -> void {
for(auto& dma : this->dma) {
if(dma.enable && dma.timingMode == 2) dma.active = true;
for(auto& channel : this->dmac.channel) {
if(channel.enable && channel.timingMode == 2) channel.active = true;
}
}
auto CPU::dmaHDMA() -> void {
auto& dma = this->dma[3];
if(dma.enable && dma.timingMode == 3) dma.active = true;
auto& channel = this->dmac.channel[3];
if(channel.enable && channel.timingMode == 3) channel.active = true;
}
+44 -44
View File
@@ -1,5 +1,5 @@
auto CPU::readIO(n32 address) -> n8 {
auto dma = [&]() -> DMA& { return this->dma[address / 12 & 3]; };
auto channel = [&]() -> DMAC::Channel& { return this->dmac.channel[address / 12 & 3]; };
auto timer = [&]() -> Timer& { return this->timer[address.bit(2,3)]; };
switch(address) {
@@ -10,17 +10,17 @@ auto CPU::readIO(n32 address) -> n8 {
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de: return (
dma().targetMode << 5
| dma().sourceMode.bit(0) << 7
channel().targetMode << 5
| channel().sourceMode.bit(0) << 7
);
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: return (
dma().sourceMode.bit(1) << 0
| dma().repeat << 1
| dma().size << 2
| dma().drq << 3
| dma().timingMode << 4
| dma().irq << 6
| dma().enable << 7
channel().sourceMode.bit(1) << 0
| channel().repeat << 1
| channel().size << 2
| channel().drq << 3
| channel().timingMode << 4
| channel().irq << 6
| channel().enable << 7
);
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
@@ -61,8 +61,8 @@ auto CPU::readIO(n32 address) -> n8 {
);
//SIOMLT_SEND (SIODATA8)
case 0x0400'012a: return serial.data8;
case 0x0400'012b: return 0;
case 0x0400'012a: return serial.dataMulti.byte(0);
case 0x0400'012b: return serial.dataMulti.byte(1); //upper 8 bits are always readable, but only used in multiplayer mode
//KEYINPUT
case 0x04000130: {
@@ -248,54 +248,54 @@ auto CPU::readIO(n32 address) -> n8 {
}
auto CPU::writeIO(n32 address, n8 data) -> void {
auto dma = [&]() -> DMA& { return this->dma[address / 12 & 3]; };
auto channel = [&]() -> DMAC::Channel& { return this->dmac.channel[address / 12 & 3]; };
auto timer = [&]() -> Timer& { return this->timer[address.bit(2,3)]; };
switch(address) {
//DMA0SAD, DMA1SAD, DMA2SAD, DMA3SAD
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: dma().source.data.byte(0) = data; return;
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: dma().source.data.byte(1) = data; return;
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: dma().source.data.byte(2) = data; return;
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: dma().source.data.byte(3) = data; return;
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: channel().source.data.byte(0) = data; return;
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: channel().source.data.byte(1) = data; return;
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: channel().source.data.byte(2) = data; return;
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: channel().source.data.byte(3) = data; return;
//DMA0DAD, DMA1DAD, DMA2DAD, DMA3DAD
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: dma().target.data.byte(0) = data; return;
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: dma().target.data.byte(1) = data; return;
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: dma().target.data.byte(2) = data; return;
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: dma().target.data.byte(3) = data; return;
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: channel().target.data.byte(0) = data; return;
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: channel().target.data.byte(1) = data; return;
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: channel().target.data.byte(2) = data; return;
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: channel().target.data.byte(3) = data; return;
//DMA0CNT_L, DMA1CNT_L, DMA2CNT_L, DMA3CNT_L
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: dma().length.data.byte(0) = data; return;
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: dma().length.data.byte(1) = data; return;
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: channel().length.data.byte(0) = data; return;
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: channel().length.data.byte(1) = data; return;
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de:
dma().targetMode = data.bit(5,6);
dma().sourceMode.bit(0) = data.bit(7);
channel().targetMode = data.bit(5,6);
channel().sourceMode.bit(0) = data.bit(7);
return;
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: {
bool enable = dma().enable;
bool enable = channel().enable;
if(address != 0x0400'00df) data.bit(3) = 0; //gamepad DRQ valid for DMA3 only
dma().sourceMode.bit(1) = data.bit(0);
dma().repeat = data.bit(1);
dma().size = data.bit(2);
dma().drq = data.bit(3);
dma().timingMode = data.bit(4,5);
dma().irq = data.bit(6);
dma().enable = data.bit(7);
channel().sourceMode.bit(1) = data.bit(0);
channel().repeat = data.bit(1);
channel().size = data.bit(2);
channel().drq = data.bit(3);
channel().timingMode = data.bit(4,5);
channel().irq = data.bit(6);
channel().enable = data.bit(7);
if(!enable && dma().enable) { //0->1 transition
if(dma().timingMode == 0) {
dma().active = true; //immediate transfer mode
dma().waiting = 2;
if(!enable && channel().enable) { //0->1 transition
if(channel().timingMode == 0) {
channel().active = true; //immediate transfer mode
channel().waiting = 2;
}
dma().latch.source = dma().source;
dma().latch.target = dma().target;
dma().latch.length = dma().length;
} else if(!dma().enable) {
dma().active = false;
channel().latch.source = channel().source;
channel().latch.target = channel().target;
channel().latch.length = channel().length;
} else if(!channel().enable) {
channel().active = false;
}
return;
}
@@ -346,8 +346,8 @@ auto CPU::writeIO(n32 address, n8 data) -> void {
return;
//SIOMLT_SEND (SIODATA8)
case 0x0400'012a: serial.data8 = data; return;
case 0x0400'012b: return;
case 0x0400'012a: serial.dataMulti.byte(0) = data; return;
case 0x0400'012b: serial.dataMulti.byte(1) = data; return; //upper 8 bits are always writable, but only used in multiplayer mode
//KEYCNT
case 0x0400'0132:
+23 -16
View File
@@ -1,47 +1,54 @@
auto CPU::prefetchSync(n32 address) -> void {
if(address == prefetch.addr) return;
auto CPU::prefetchSync(u32 mode, n32 address) -> void {
if(prefetch.wait == 1) step(1);
n32 size = (mode & Word) ? 4 : 2;
prefetch.stopped = false;
prefetch.addr = address;
prefetch.load = address;
prefetch.wait = waitCartridge(Half | Nonsequential, prefetch.load);
prefetch.ahead = false;
prefetch.addr = address + size;
prefetch.load = address + size;
prefetch.wait = waitCartridge(Half | Sequential, prefetch.load);
}
auto CPU::prefetchStep(u32 clocks) -> void {
auto CPU::prefetchStepInternal(u32 clocks) -> void {
step(clocks);
if(!wait.prefetch || context.dmaRomAccess || prefetch.stopped) return;
if(!wait.prefetch || prefetch.stopped) return;
while(!prefetch.full() && clocks--) {
while(clocks--) {
if(prefetch.full()) {
prefetch.stopped = true;
prefetch.ahead = false;
break;
}
if(--prefetch.wait) continue;
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.read(Half, prefetch.load);
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.readRom<false>(Half | Nonsequential, prefetch.load); //todo: make access sequentiality consistent with timing used
prefetch.load += 2;
prefetch.wait = waitCartridge(Half | Sequential, prefetch.load);
}
}
if(prefetch.full()) prefetch.stopped = true;
auto CPU::prefetchStep(u32 clocks) -> void {
if(wait.prefetch && !prefetch.stopped && prefetch.empty()) prefetch.ahead = true;
prefetchStepInternal(clocks);
}
auto CPU::prefetchReset() -> void {
if(prefetch.wait == 1) step(1);
prefetch.stopped = true;
prefetch.ahead = false;
prefetch.wait = 0;
prefetch.addr = 0;
prefetch.load = 0;
}
auto CPU::prefetchRead() -> n16 {
if(prefetch.empty()) prefetchStep(prefetch.wait);
n16 word = prefetch.slot[prefetch.addr >> 1 & 7];
prefetch.addr += 2;
if(prefetch.stopped && prefetch.empty()) {
prefetch.stopped = false;
prefetch.wait = waitCartridge(Half | Nonsequential, prefetch.load);
}
if(prefetch.empty()) prefetchStepInternal(prefetch.wait);
n16 word = prefetch.slot[prefetch.addr >> 1 & 7];
prefetch.addr += 2;
return word;
}
Loaded 100 of 4630 files, more files were not shown because too many files have changed in this diff. Show more