Compare commits

...
84 Commits
Author SHA1 Message Date
Luke Usher 39025af1fe release ares v136 2024-02-23 17:02:48 +00:00
Luke Usher 5cdefb088d desktop-ui: cleanup resource search paths
As well as simplifying the code a little, this also  fixes an oversight where users could not override application resources on macOS.
2024-02-21 17:49:12 +00:00
Giovanni Bajo d0253599a6 n64: fix corner case in FPU exception handling (#1400)
Ares checked each of the two input arguments to three-ops functions
separately, but this is not correct. Conditions causing unimplemented
operation on either input (sNAN, subnormal) take precedence on
conditions causing invalid operation on either input (qNAN).

After this fix, Ares passes n64-systemtest COP1 stress test.
2024-02-20 23:34:38 +00:00
Luke Usher 1e83da0241 md: implement subchannel processing for mcd (CD+G) 2024-02-19 14:19:56 +00:00
Luke Usher ed0a1c8852 cheats: only create a cheat file when cheats are added
This also cleans up empty cheat files on unload.
2024-02-18 08:41:56 +00:00
Luke Usher dd9c728a12 ares: preliminary cheat support
Adds a cheat editor to Tools->Cheats when a game is loaded.

The cheat editor should work for all systems except N64 and PS1: support for these will come later.

The code has currently only been tested for MS, MD and SFC but it is assumed all the other systems are also functioning; the code paths are the same.

Code format is standard patch codes in the format X:Y where X is a memory address and Y is a value to place there; most emulators also support this format.

You can use multiple codes in a single cheat by separating codes with a + sign.

Cheats that require memory comparisons are not currently supported, only replacements

Cheats are saved alongside the roms, eg "Game.sfc" will create "Game.cheats.bml".
2024-02-16 19:01:57 +00:00
Luke Usher 7ec1e1430c fc: reduce overscan cropping similar to ms 2024-02-15 17:35:47 +00:00
Luke Usher 16379aabff ruby: use PRESENTINTERVAL_ONE rather than GetRasterStatus for VBlank with d3d9 2024-02-15 17:34:30 +00:00
Luke Usher 11f24b47c3 ms: adjust overscan cropping, it was a little too agressive 2024-02-15 17:33:51 +00:00
Luke Usher 32f205975c md: add CD PCM RAM to debug memory viewer/editor 2024-02-14 17:45:50 +00:00
png183 21ca9cf76e gba: timer 0 count-up bit cannot be set (#1392)
The count-up bit for timer 0 cannot be set on hardware ([test
ROM](https://github.com/png183/gba-tests/blob/master/timer/timer_check_count_up_presence.gba)).
This PR implements said behaviour in ares, preventing timer 0 from
incorrectly halting if this bit is written.
2024-02-14 10:14:39 +00:00
Giovanni Bajo d32b3ffe45 n64: revert previous commit (add HLE RDRAM initialization) (#1391)
This is actually still required as we still skip RDRAM initialization
anyway and proprietary IPL3 doesn't initialize the RAM size in this
case.
2024-02-14 10:14:20 +00:00
Luke Usher 5864382c67 ps1: various fixes to GPUSTAT register behavior 2024-02-13 22:34:03 +00:00
Luke Usher f392e4f428 libchdr: disable asm optimisation for zstd
Should fix the Ubuntu build.
2024-02-11 19:51:28 +00:00
Luke Usher d0075978f8 libchdr: rollback lzma to 19.00
This version still exists in the repo for libchdr
22.01 fails compilation on many platforms right now
2024-02-11 19:37:46 +00:00
Luke Usher c1e5777dc1 thirdparty: fix compilation after libchdr update 2024-02-11 19:16:41 +00:00
Luke Usher ec6c9fc330 Merge commit '9bc95db1f0c5591dfbcec23c8e326aa6c2bb3b29' 2024-02-11 19:16:32 +00:00
Luke Usher 9bc95db1f0 Squashed 'thirdparty/libchdr/' changes from 9108f34a8..2648c221c
2648c221c Merge pull request #108 from herbymachine/master
f7729588b Update libchdr_huffman.c
26d27ca49 Merge pull request #106 from zakk4223/zstd
d6f59e748 Add zstd codec

git-subtree-dir: thirdparty/libchdr
git-subtree-split: 2648c221c9ce6b49600ed7d2eb45c9e6e593790f
2024-02-11 19:16:32 +00:00
Luke Usher 6d80a8e940 Merge commit '2a0f9651b976a5431510222e04077fd360415d69' 2024-02-11 19:16:30 +00:00
Luke Usher 2a0f9651b9 Squashed 'thirdparty/sljit/' changes from c1cbaf09e..607158516
607158516 Improve serialize interface
dd73cb873 Implement serialization interface
5d2d7cd59 Improve PPC branch support
8b8b6b442 Improve ARM32 branch support
2cfed9ce4 Improve x86, ARM-T2 branch support
6c75e773c Improve riscv branch support
dfeb8cf68 Add extraction macros for operands
dc52b334e Fix typo (#229)
7a821cd71 Add LoongArch float to integer conversion special cases (#226)
cbde0bea6 regex: allow building with a C++ compiler (#224)
40680870e Fix masked shift on arm32
a0643952a Fix sljit_get_register_index on x86-32
80bdff333 Support temporary registers in sljit_get_register_index
4629814f8 riscv: fix getput_arg and load_addr_to_reg (#223)
a26427a6e define float to integer conversion special cases
42128180d build: make code mostly ANSI C89 compatible (#221)
57c212f58 loongarch: fix FTBFS when building pcre2test as part of PCRE2 (#220)
3faacd431 Optimize certain fop1 operations on x86
4f33321c1 Move float/call/simd tests to separate files
230429241 Add another temp register on s390x
fbf9c47f3 Support access to temporary registers
8c5c50463 Implement simd on s390x
02e76fb14 s390x: fix FTBFS with clang (#219)
0bd5147fc Implement simd2 operations

git-subtree-dir: thirdparty/sljit
git-subtree-split: 6071585164d9a38a8890cfd2722ca9d8492528bb
2024-02-11 19:16:29 +00:00
Giovanni Bajo 51a10b0bd0 n64: avoid HLE-ish memory init (#1388)
Not required anymore, possibly for a long time. IPL3 does that, plus
this creates correctness issues with open source IPL3 which *doesn't*
initialize those memory locations.
2024-02-11 11:09:07 +00:00
png183 7e2f490fd0 gba: improve horizontal sprite mosaic (#1387)
Addresses a few cases where the current horizontal sprite mosaic
implementation is incorrect:

- Transparent pixels should be affected by horizontal sprite mosaic
effects
- If the latched pixel does not have the sprite mosaic effect enabled,
the next pixel should always be latched
- Mosaic offset should not be reset to 0 at the start of a new
horizontal mosaic effect

Accounting for these cases improves visual accuracy of sprite mosaic
effects. This is easily visible in test cases such as
[sprite-hmosaic.gba](https://github.com/nba-emu/hw-test/tree/master/ppu/sprite-hmosaic),
and [Tonc's mosaic demo](https://www.coranac.com/projects/#tonc)
(mos_demo.gba, from tonc-bin.zip v1.4.2).
2024-02-11 11:08:41 +00:00
Luke Usher 2063393f20 sfc: fix SGB2 clocking issue
This fixes an issue where the SGB2 would reconfigure to SGB1 clock speeds when writing to the control port.
2024-02-06 00:15:49 +00:00
Giovanni Bajo 4c15ed753e n64: fix PI DMA implementation to handle a newly discovered behavior (#1385)
I've lately discovered that misaligned PI DMA is also affected by RDRAM
writes that must cross RDRAM row boundaries (2048 bytes). It is obvious
in retrospect that the DMA implementation cannot generate a write burst
across different rows, so its internal buffering must be segmented on
RDRAM row boundaries.

The PI DMA testsuite has been updated to cover this edge case as well:
https://github.com/rasky/n64_pi_dma_test

Ares now passes the updated testsuite in full (timing aside, which is
close but not perfect).
2024-02-05 16:14:07 +00:00
Luke Usher 704fc91b9c desktop-ui: 'hide until ready' for Windows only 2024-02-01 18:58:50 +00:00
Luke Usher 757d74bac0 desktop-ui: hide window until startup completes
This also avoids calling inode::exists on every recent game at load time, reducing initial wait time.
2024-02-01 18:40:45 +00:00
Luke Usher ff36889d47 desktop-ui: exclude rumble inputs from poll timeout
Fixes rumble issues in many games, notably, OOT
2024-01-29 13:12:06 +00:00
Luke Usher 3d10cd34d6 ruby: differentiate devices in SDL2 driver 2024-01-28 21:00:36 +00:00
Luke Usher acaa1cabb5 ares: border and overscan overhaul
This updates most pre-32bit systems to have hardware accurate borders and
aspect ratio where known, reasonable estimates are used where not known.

This also means that PAL games will use the correct PAL aspect rather than
using the NTSC pixel aspect ratio.

All TMS9918 VDP based consoles are thought to be be correct, as are Master
System, Mega Drive, and NES, while SNES and PC-Engine are (educated)
estimates.

The overscan option has been reworked to crop all non-desirable edges of the
screen, including borders and areas that often contain garbage data, previously
this option only cropped the bottom 16 lines of active display.

The reworked overscan is not yet implemented for Atari 2600, PC-Engine, N64 or
PlayStation.
2024-01-28 15:38:47 +00:00
png183 e8f38d15f7 gba: reset prefetch buffer on ROM accesses from CPU (#1377)
According to NanoBoyAdvance author fleroviux, the prefetch buffer should
be reset on any ROM accesses from the CPU or DMA. This PR implements
this behaviour for CPU ROM accesses. This allows ares to pass 1760/2020
of the timing tests in the mGBA test suite (up from 1608/2020).

Allowing DMA ROM accesses to clear the prefetch buffer seems to worsen
timings at the moment, possibly due to some other timing issues that
also come into effect in these situations. I've left comments to make a
note of the issue, so that resetting the prefetch buffer on ROM accesses
from DMA can be implemented once it becomes beneficial to do so.
2024-01-27 11:47:44 +00:00
Luke Usher 527aaefef1 ruby: use wglSwapInterval rather than DwmFlush
Despite GFWL, and other sources recommending the DwmFlush approach, actual users are setting better results the old way.
2024-01-25 16:11:57 +00:00
Dragorn421 a938ef8aa8 Fixup *nix SDL info and makefile indentation (#1376)
The readme references `libsdl2-0.0` which does not exist:
https://packages.ubuntu.com/search?suite=default&section=all&arch=any&keywords=libsdl2-0.0&searchon=names

I'm not sure it's even useful mentioning `libsdl2-2.0-0` since
`libsdl2-dev` depends on it. Maybe for distributing ares you would then
know people have to install `libsdl2-2.0-0` I guess

Also added a note that SDL2 input allows using a controller on *nix as I
haven't seen that documented anywhere

And I fixed a random makefile indentation issue that messed with my
head.
2024-01-25 13:37:32 +00:00
Luke Usher 91658b989a ares: bump version to v135 2024-01-23 10:47:43 +00:00
Steveice10 801eaa4088 desktop-ui: Fix broken firmware path setting. (#1374)
* Fix the firmware search path not being saved or loaded from the
settings file.
* Fix `FirmwareSettings::findFirmware` not using the right casing when
locating the firmware directory.
2024-01-22 14:20:27 +00:00
Steveice10 d6b2ca53e2 nall: Account for macOS app bundles in program path. (#1373)
Contains three main changes:
* In `Path::program()`, account for the macOS app bundle, so that
portable config support works with the app bundle.
* Create a new function `Path::resources()` for the resources path,
since `Path::program()` can no longer trivially be used to construct the
bundle resources path (no guarantee what the .app is named). On macOS
this returns the path to "ares.app/Contents/Resources", on other
platforms (in case they use it in the future) it just returns the same
as `Path::program()`.
* Fix an issue where, if Ares is stored in a sub-directory of the
desktop, the path settings UI will display paths next to the program as
relative to the desktop instead of relative to the program directory.
2024-01-22 14:20:14 +00:00
png183 d823641ccc gba: improve ROM to ROM DMA timings (#1372)
When performing a DMA with source and destination addresses both in
cartridge memory, the first write should use sequential access timings.
Implementing this in ares gives more accurate timings for ROM to ROM
DMAs than the previous approach, which instead removed the 2-cycle DMA
starting delay in these cases.
2024-01-22 14:19:45 +00:00
Giovanni Bajo dec675896d n64: reenable isviewer even outside of homebrew mode (#1370)
ISViewer is currently gated by the homebrew mode flag. It seems like
this is not ideal for rom hackers because they would still need a way to
log but homebrew mode is too verbose for them and hide the ISViewer
messages in streams of actual problems in game code that they don't
intend to fix. So restore ISViewer functionality without homebrew mode.
2024-01-22 14:18:53 +00:00
Giovanni Bajo e2caf175d6 nall: make gdb server accept only one connection at a time. (#1369)
This closes the socket server after each successful connection so that
new connections are immediately refused. Before, given that the server
socket was always in listen mode, one additional connection could always
be estabilished at the TCP level, even though the server wouldn't accept
it right away.
2024-01-22 14:18:42 +00:00
Giovanni Bajo 322c99ca75 nall: correctly handle TCP peer disconnections in tcp-socket.cpp (#1365)
Currently, when a client disconnects, the server in Ares fails to notice
and to call the onDisconnect hook, which in turns cause the server to
stop responding (it won't accept a new client, and will never receive
more data from the disconnected one).

This commit fixes it by correctly handling the recv() return code to
detect peer disconnections (return value 0) and any other kind of error
(return value < 0).
2024-01-18 09:27:23 +00:00
png183 b28bb3dbd7 gba: improve save type detection for homebrew (#1367)
Commercial GBA games contain strings in the ROM that correspond to a
save type (e.g. SRAM, flash memory, EEPROM), followed by 3 digits. These
strings are used by ares to detect what save type to use. However, when
running [jsmolka's SRAM and flash memory save
tests](https://github.com/jsmolka/gba-tests/tree/master/save), ares does
not detect a save type, since the programs lack the 3 digits at the end
of the save type string. This PR ignores the trailing digits during save
type detection, since it's likely that many other homebrew programs are
affected by the same issue ([GBATEK even advises developers to not add
digits when including save type
strings](https://problemkaputt.de/gbatek-gba-cart-backup-ids.htm)).
2024-01-18 09:27:02 +00:00
Giovanni Bajo 3f4334e5ff n64: implement undocumented accesses in FPU half-mode (#1364)
Fixes most of the remaining failures in n64-systemtest
2024-01-16 11:59:04 +00:00
png183 99a40e43ef arm7tdmi: fix additional instructions that require offset when reading PC (#1363)
Depending on which cycle they read values from registers, instructions
will read a different PC value. This is handled in ares by adding an
offset to the program counter (r15) value in affected cases. This PR
applies the offset in a few instructions that are affected by this
behaviour, but were previously not covered.
2024-01-13 19:00:42 +00:00
png183 9bbe36d73c arm7tdmi: Improved ldmia and stmia timings in Thumb mode (#1362)
ARM and Thumb mode variants of the ldmia and stmia instructions use a
shared pattern of sequential and nonsequential memory accesses. The
correct pattern was already implemented in ARM mode, and this PR applies
the same pattern to Thumb mode, making the timings of these instructions
more accurate.
2024-01-08 10:46:27 +00:00
Luke Usher 4bae7965f9 ps1: fix broken controller when sideloading ps-exe 2024-01-03 13:46:52 +00:00
jcmandjcm 9e4b3795f1 build: add macOS SDL to thirdparty, add .dylib to bundle (#1345)
### Description

Adds SDL (properly) to Mac builds.

This time, it has been added as a fully fledged optional dependency in
`thirdparty`, à la MoltenVK, and built from source before ares. We build
from source because brew installations are both single-architecture and
not fully portable.

The .dylib is copied inside the app bundle when linking, at the same
stage as MoltenVK.

The initialization of `ares.dylibs` has been moved from ares's Makefile
to the `desktop-ui` Makefile so that it's initialized when building
ruby, which it wasn't previously.

The SDL build is also added to CI, with the same caching methods as used
for MVK.

### Relevant Details

`HEAD` points at SDL 2.28.5 currently, which is the most recent stable
release. This should probably be periodically incremented as
appropriate.

~~SDL is currently built with the macOS deployment target of 10.13.
While SDL's minimum supported macOS version is 10.11, ares CI is using
Xcode 14.2, which has a minimum deployment target of 10.13. I am
currently testing to see if I can get the deployment target lower
without breaking other parts of ares's CI.~~ resolved, per below; SDL
supporting 10.11 is built + bundled.

### Testing

This has been tested to build properly locally and via GitHub CI. The
.dylib is packaged inside the bundle correctly and SDL functions
correctly in my testing, even if the user does not have SDL installed
elsewhere on their system.

This is seeking tests from other Mac users, especially on older
operating systems, to verify that SDL works correctly there.

Co-authored-by: jcm <[email protected]>
2023-12-28 09:24:04 +00:00
shinra-electric a82913c1f9 macOS: Add minimum macOS version and copyright to ares.plist (#1350)
Added the minimum macOS version key, but kept it at 10.9 which is what
macOS currently reports it as.
Now that the key is here, it will be easier to update it in the future. 

Also added the copyright line attributing the ares team and
contributors.
2023-12-28 09:19:47 +00:00
Jasmine Minter b7db1c074b macOS: various UI fixes (#1355)
Some issues I noticed while playing with this on macOS. Took a bit of a
deep dive and attempted to fix them up:
- changing the tracer settings did not seem to change what was logged.
seems to be the row isn't selected when the `onToggle` callback is
called. solution was to instead use the toggled `cell` to get the row
its in. this caused some C++ template issues but removing the `auto`
fixes them all (the alternative being adding `template` to a few method
calls, I should add I'm not a C++ programmer so from my limited
understanding this was the best solution)
- text in text fields were invisible. this was due to
`setForegroundColor` in the macOS backend not properly handling a
0,0,0,0 color. changed to perform the ternary on the `Color` rather than
the `NSColor` (`SystemColor` did not support the ternary)

merry christmas :)
2023-12-28 09:19:24 +00:00
Giovanni Bajo 1478637209 n64: support ROMs bigger than 63.9375 MiB (#1354)
Currently, the n64 core is limited to ROMs whose size is 63.9375 MiB
(also known as 64 MiB - 64 KiB). The reason is twofold: first, only 64
MiB are actually mapped in the PI bus; second, the last 64 KiB are
shadowed by the ISViewer debugging device.

ISViewer addresses sit in the last 64 KiB of the 64th MiB, so when the
ROM is larger than that (for instance, it is exactly 64 MiB), we should
simply disable ISViewer. In the long run, there will be a better
debugging solution which doesn't sit in the PI address space anyway.

Moreover, N64 supports basically ROM of unlimited size, there is no 64
MiB limit. This commit also allows that by simply mapping the whole ROM
into the PI address bus, whatever the actual size is.
2023-12-28 09:17:52 +00:00
Giovanni Bajo 432c4e3b2a z80: add newly discovered behavior of inir/indr/otir/otdr (#1353)
See https://spectrumcomputing.co.uk/forums/viewtopic.php?t=10555
2023-12-21 15:30:24 +00:00
Giovanni Bajo 18c84a83e4 n64: improve PIF HLE emulation by reducing latency (#1351)
When doing PIF HLE emulation, we run the HLE state machine just once per
frame. This is OK in general, but there can be a few situations where
this can cause PIF to miss one command from CPU. For instance, a ROM
might send the PIF boot termination command (0x8) and then shortly after
issue a SI DMA to identify controllers. If the two happen within the
same frame, our HLE emulation would miss the boot termination command.

This commit updates the HLE state machine also after each write to PIF
RAM.
2023-12-17 14:27:57 +00:00
remutro c4bc588082 Correct prefetch order for M68000 ASR instruction (#1347)
Originally found by vmicka72, so credits to them. Documented as issue:
https://github.com/ares-emulator/ares/issues/1275 and confirmed by
TascoDLX.

Since it is easy and straightforward, no sense in letting it hang
around.
2023-12-16 08:21:04 +00:00
Giovanni Bajo ed3083db8a Implement N64 register mirroring (#1344)
Also slightly improve PI latch emulation.
2023-12-14 11:13:55 +00:00
invertego 6d5ef628e8 ci: disable sdl2 on macOS to unbreak the build (#1343)
More work is required to support building universal binaries.
2023-12-12 23:38:21 +00:00
Luke Usher 6e8f4f224a cv: fix the build 2023-12-12 13:47:08 +00:00
png183 4bdeb3727f gba: unmapped i/o fixes (#1342)
Some unmapped I/O registers on the GBA always return 0 when read. This
pull request implements the required behaviour on six registers that
previously used open bus behaviour instead, allowing ares to pass all
remaining I/O read tests in the mGBA test suite.
2023-12-12 12:30:51 +00:00
NoxyMeandLuke Usher 20a3aac83b cv: add "XIn1" and "MegaCart" mappers (#1327)
Added "Xin1" mapper for the ColecoVision, this mapper supports:
- 31-in-1 (v1.1) ( 2013 )
- 63-in-1 (v1.0) ( 2013/0 1)
Both carts are by "Buzz and ZX-81", and i tested all games for both
multis and they all at least boot perfectly fine


And the "MegaCart" mapper, this mapper automaticly triggers for any rom
over 32k that isnt known to be another mapper (like the "xin1" mapper).

it all should be well with all homebrew games that requite the MegaCart
hardware.


If anyone knows of any other ColecoVision carts like these let me know
and I'll try to add them.

---------

Co-authored-by: Luke Usher <[email protected]>
2023-12-11 17:44:19 +00:00
invertego 2950d3fe44 md: various i/o port improvements (#1341)
General fixes:
- model data lines separately from output data latches
- update data lines after control register writes
- distinguish between driven and floating data lines
- remove redundant data latch state from devices
- separate host input polling from data line synthesis

6 button/fighting pad fixes:
- reset timeout only on TH 0->1 transitions
- remain in last phase until timeout (don't wrap)
- read back C/B button state along with M/X/Y/Z

Mega mouse:
- remove erroneous thread synchronize on attach which could crash ares

Affected titles:
- Decap Attack (MD)
- Trouble Shooter/Battle Mania (MD)
- WWF Raw (32X)
2023-12-11 08:28:36 +00:00
jcmandjcm b55c5010f3 ruby: add SDL to macOS (#1313)
### Description
Up to this point, SDL as an input library has not been present in macOS
builds. The plumbing was already in place for macOS to make use of SDL,
but SDL was not actually linked. This PR rectifies that. Seemingly
unnecessary references to the IOKit joypad are also removed from
`sdl.cpp`.

To facilitate adding SDL to ruby for macOS, a stubbed out macOS mouse
driver, `gcmouse.cpp`, has been added, to avoid needing to make numerous
modifications to `sdl.cpp` to work around mouse-related code.
### Motivation
The legacy macOS input uses raw values from IOKit, which are not
available for all supported controllers, and where present, are
uncalibrated and generally not ideal. SDL's input support is much more
advanced comparatively, properly handling and interpolating input from a
wide variety of controllers where raw IOKit input values aren't
sufficient. This should resolve a number of situations where ares won't
recognize input from controllers connected to macOS machines, among
other macOS input-related issues.

### Discussion
I am not super well-acquainted with ares's code and build system, and
it's entirely probable that I missed something here or went about this
not-quite-correctly; I did so as straightforwardly as possible and may
have missed something, for example fallbacks if SDL is not present at
build-time; I am not sure if this is currently handled. I encourage any
and all edits from maintainers if I broke something or went about
something incorrectly.

### Testing?
Tested locally on my Apple Silicon machine on macOS Sonoma. SDL can now
be selected and used successfully as an input driver for all supported
connected controllers.

Co-authored-by: jcm <[email protected]>
2023-12-09 22:03:16 +00:00
invertego f893afca89 nall: ensure utf-8 string encoding in cl builds (#1339)
As a bonus, use the Unicode ellipsis character in the ares UI on all
platforms instead of just macOS. In addition to looking nicer, it will
make regressions in this area very obvious.
2023-12-09 21:46:17 +00:00
invertego 0d131bf4d8 build: improve pkg-config support on windows (#1337)
Allow ares to be more easily built against external dependencies on
Windows outside of MSYS2 and using the MSVC ABI.

For example, dependencies can now acquired via vcpkg by installing the
relevant packages (pkgconf, gtk3, qt5, sdl2) and setting up the
environment appropriately before building.

```
CMD:
set PKG_CONFIG=C:\src\vcpkg\installed\x64-windows\tools\pkgconf\pkgconf.exe
set PKG_CONFIG_PATH=C:\src\vcpkg\installed\x64-windows\lib\pkgconfig

Bash:
export PKG_CONFIG=/c/src/vcpkg/installed/x64-windows/tools/pkgconf/pkgconf.exe
export PKG_CONFIG_PATH=/c/src/vcpkg/installed/x64-windows/lib/pkgconfig
```

Changes:
- hiro: fix building against gtk/qt with cl
- build: improve pkg-config detection and use with cl
- nall: support dynamic crt with clang-msvc/cl
2023-12-08 11:55:43 +00:00
remutro 7e99155f8e Mega Mouse support for 32X + CD (#1336)
Doom Resurrection patch version 3.0 (currently at version 3.1) added
support for the Mega Mouse, as well as making use of Mega CD hardware if
available. When I added mouse support for the Mega CD, the patch for
Doom Resurrection was at version 2.2, and since there was no licensed
32X software that supported the Mega Mouse I declined to add support at
the time. But leave it to homebrew, especially one as comprehensive as
this patch to add support.

This adds Mega Mouse support for both the 32X and 32X/CD combo, so it is
now available for all Mega * consoles.
2023-12-08 11:55:16 +00:00
Giovanni Bajo c95dc1bad7 n64: improve and extend cache coherency checks (#1314)
To make sure to intercept all possible errors, the check are now
performed by the RDRAM module, whenever a RDRAM read/write behind a
cacheline happens.

CPU writes to cache is now tracking dirtyness at the byte level rather
than whole cacheline level, so that hardware accessing memory does not
trigger a false positive for false-shared variables. An initial round of
testing has shown that the check would trigger far too much otherwise.
Error reporting is also much improved to provide more context to analyze
the issue, including tracking the PC at which the hardware DMA was
triggered.

For RSP DMA, we do even more: instead of reporting an issue when area of
a cached memory is DMA into RSP DMEM/IMEM, we actually just mark those
memory locations as tainted, and emit the warning only if RSP later
reads them. This is necessary because it is extremely common for RSP
ucode to read data beyond the actual buffers it cares about, even though
that data is then never accesses.

Some of the warnings issues by Ares have been analyzed on Super Mario 64
and Zelda OOT and in both cases they have been confirmed to as real bugs
in cache management made by the game.

For instance, in Mario 64, the programmers forgot to invalidate the
cache before loading data for the initial Mario head animation, as
reported by Ares now:
```
[unusual] PI DMA writing to RDRAM address 0x390650 which is cached (missing cache invalidation?)
	Cacheline was loaded at CPU PC: 0xffffffff80183b38
	PI DMA started at CPU PC: 0xffffffff80328558
```

The game just happens to work because after loading, the game does
something else and manages to get the cache invalidated by touching
other locations, but otherwise it would be a real bug.

As another example, in Zelda OOT, we get these warnings at boot:

```
[unusual] AI reading from RDRAM address 190820 which is modified in the cache (missing cache writeback?)
    Cacheline was loaded at CPU PC: ffffffff800b54fc
    Cacheline was last written at CPU PC: ffffffff800b5528
    
[unusual] RSP reading from DMEM address fe0 which contains a value which is not cache coherent
    Current RSP PC: d10
    The value read was previously written by RSP DMA from RDRAM address 00199e40
    RSP DMA started at RSP PC: abc
    The relative CPU cacheline was dirty (missing cache writeback?)
    Cacheline was loaded at CPU PC: ffffffff800b5244
    Cacheline was last written at CPU PC: ffffffff800b5264
```

These warnings appear to be real bugs in the audio library. Quoting
Thar0:
```
* AudioHeap_ClearCurrentAiBuffer/AudioHeap_ResetStep wipe the AI buffers with CPU writes and 
  then don't write back the cache, and I guess an AI DMA is either in progress or starts before it can
  be properly flushed. On boot this shouldn't matter as gAudioHeap is BSS and is already zero, but
  if the driver is reset later it might cause some minor issues?
* The RSP cache coherency problems is from loading filters in AudioHeap_LoadLowPassFilter.
  The heap allocator returns a pointer to which the CPU writes the filter data.. but the allocator
  writes back the cache before the CPU writes the data 😂
```

Moreover, this PR also implements all the known SysAD-related CPU
freezes that cause the console to crash. We now pass the
[n64-systemcrash](https://github.com/rasky/n64-systemcrash) testsuite in
full.
2023-12-07 17:14:18 +00:00
invertego b85395fe7b hiro: make gtk2/gtk3/qt5 backends build (again) on windows (#1335) 2023-12-07 16:50:49 +00:00
encoded-byte 91c46dbad4 fc: improve fds timer irq generation (#1333)
Improvements to fds irq generation logic, to match description at
https://www.nesdev.org/wiki/Family_Computer_Disk_System#Timer_IRQ_control_($4022)

- Fixed "i/o enable" register address
- Correctly acknowledge interrupts in writes to $4022 / $4023
- Improve irq generation logic

The changes are based on the discusion on this thread:
https://forums.nesdev.org/viewtopic.php?t=16507

With this pr ares passes all 21 tests on FdsIrqTestsV7 (many failed
before), which can be downloaded at
https://forums.nesdev.org/download/file.php?id=10240

In addition to passing the tests this pr also fixes Kaettekita Mario
Bros. (Japan): this game has 3 random opening animations before
requesting to switch to side B, two of these openings will display a
glitchy screen with old irq logic. This can be tested by just resetting
if the game shows the animation that works.
2023-12-07 16:23:15 +00:00
invertego 6a253b17af nall: define new type traits (#1334)
Instead of directly specializing std traits classes (which would result
in undefined behavior) define our own traits classes that inherit from
the std types.

This allows us to remove a hack in the serializer class and paves the
way for building ares with Qt on Windows.
2023-12-07 16:19:40 +00:00
invertego 4116855924 build: take care of some macOS build warnings (#1332)
- remove -stdlib=libc++ as it caused warnings when applied to C files
and has been the default on macOS for something like a decade
- add -Wno-deprecated-declarations to the command line to silence
warnings about things like sprintf in thirdparty
2023-12-07 16:17:03 +00:00
invertego 1146c0def3 build: remove -fomit-frame-pointer (#1331)
This option is usually enabled by default by GCC/Clang with -O1 and
above. This is not true on some targets like macOS/arm64, and there we
are better off deferring to the compiler anyway.

This became an issue because Xcode 15 shipped with a new linker that, at
least when LTO is enabled, appears to mess up metadata that is used to
unwind the stack during exception handling. Without a frame pointer to
fall back on, unwinding fails completely and causes ares to terminate.
2023-12-06 10:33:34 +00:00
invertego e176d8b3ee build: always pass -arch on macOS (#1325)
Use the arch derived from -dumpmachine if none was specified to make.
Passing -arch seems to be required to get full debug information on
arm64.
2023-12-04 18:44:35 +00:00
Giovanni Bajo 9b5e339cc1 n64: improve SI emulation (#1326)
In addition to approximating the correct values in SI STATUS fields,
this commit also fixes a bug: I/O writes to PIF RAM do trigger an
interrupt just like DMA does.
2023-12-04 18:44:16 +00:00
invertego 8f626e304a ci: build resiliency tweaks (#1322)
Disable "fast fail" so jobs will run to completion even if others fail.
The workflow as a whole will still fail, but the jobs that succeed will
produce downloadable artifacts.

Secondly, allow the "macOS: Import Certificate" step to fail on forks.
This allows CI to function on forks without any other setup, though the
produced macOS builds won't run actually run unless you re-sign them.
2023-12-04 12:02:26 +00:00
remutro bbe9a9b0a4 Make it more obvious that the apply button for drivers needs to be used (#1320)
We frequently get issue reports in GitHub and Discord about driver
settings not being saved. This is due to the fact that changes to
drivers requires using the Apply (formerly Reload) button for changes to
stick, which is different than other options in the settings window.

With this change, the Apply button is grayed out until a different
driver is selected, hopefully making it more obvious that the buttons
need to be used. A small note was also added at the bottom explicitly
stating that they Apply buttons must be used in order to switch/change
updates to driver selection.
2023-12-04 12:02:05 +00:00
invertego c00e402eeb build: enable strict standards conformance with cl (msvc) (#1317)
From the MSVC docs: "You can use the /permissive- compiler option to
specify standards-conforming compiler behavior. This option disables
permissive behaviors, and sets the /Zc compiler options for strict
conformance."

While generally a positive thing, this does make MSVC stricter than
GCC/Clang in some cases. A couple of things popped out:

- nall::multiFactorImage had a busted constructor. I fixed this and did
some other cleanup while I was at it.
- nall::string has an implicit conversion operator to const char*. This
is honestly really ugly and creates ambiguity and allows for all kinds
of surprising silent conversions. Unfortunately, fixing that (by making
it explicit) will require touching hundreds of lines of code so I opted
not to deal with it now. Instead I fixed the much smaller number of
ambigous cases relating to string literals.
2023-12-04 12:01:50 +00:00
invertego 5e6afeaa5e hiro: prevent exception originating from appkit (#1324)
As noted in the Chromium source, the value for
NSTreatUnknownArgumentsAsOpen needs to be of type string despite being a
boolean setting.

If the type is boolean, AppKit throws an exception (which is caught),
but if unwinding fails (observed on some arm64 systems in certain build
configurations when ares is compiled with LTO and -fomit-frame-pointer)
it terminates the whole application.

I've confirmed the behavior the original commit was designed to correct
is still fixed with this change.
2023-12-04 12:01:37 +00:00
Luke Usher a3222abe79 md: remove 32x dreq debug printing 2023-12-04 11:21:32 +00:00
Tomás Domínguez Bolaño b0ee66d4ad fc: use hkrom for mmc6 (#1316)
MMC3 and MMC6 share the same mapper in the iNES format. To disambiguate
MMC6 from the MMC3 mapper the NES 2.0 submapper 004:1 should be used:

https://www.nesdev.org/wiki/MMC6

Code for using the MMC6 was actually commented in mia/medium/famicom.cpp
up to commit bed4ce3659, where it was deleted. This commit
reintroduces this code to use it with the NES 2.0 submapper 004:1. This
adds compatibility with the games "StarTropics" and "Zoda's Revenge:
StarTropics II".
2023-12-02 17:54:09 +00:00
invertego 6567d9dcf0 desktop-ui: destroy timers on system unload (#1315)
Fix two issues caused by disc swap timers:
1. Crash on exit in certain build configurations
2. Crash on system unload shortly after disc swap

Because these timers were simply members of Emulator derived classes
(which are in turn owned by a global variable), they were being
destructed after main() along with other static lifetime objects. This
was problematic because hiro also has global state that the timer
implementation depends on, at least on Windows. Whether this manifested
in a crash depended on a few factors, object destruction order among
them (which is at the mercy of the linker).

The second crash would reliably reproduce simply by initiating a disc
swap and unloading the emulated system (such as by closing the emulator)
within the next 3 seconds.
2023-12-02 14:11:26 +00:00
invertego 9a8e7f8b6e recompiler: allow jitprotect() calls to be nested (#1306)
The following applies only to macOS when JIT page protection is enforced
(namely on Apple silicon).

A sequence of jitprotect(false) calls must now be followed by an equal
number of jitprotect(true) calls to restore executable protection. A
further assumption is that nesting always starts with (false), which is
only logical because you have to write something before you have
anything to execute. This is explicitly checked in debug builds.

The N64 and PS1 recompilers have needed this since commit 8ed4af8 in
order to handle the path taken on recompiler cache flushes. It's subtle,
but emit() will evict all pools and so the second call to pool() from
block() will reallocate the pool and unexpectedly toggle the protection
out from under it.

Technically the SH2 recompiler has needed this since commit 241f0e9
(from 8 months ago!) but that same commit made cache flushes practically
nonexistent, so it's likely not impacting the retail title library.
2023-11-30 09:38:32 +00:00
Giovanni Bajo 1c9d38895d n64: simulate SysAD freezes when accessing non-RDRAM areas via cache (#1299)
The CPU will freeze until power-off if a cached access is performed to a
non RDRAM area. This obviously never happens with games, but it is a not
so uncommon error when doing very low level development on the system.

This patch emulates the freeze for the data cache, and for the
instruction cache when using the interpreter. Implementation for
instruction cache and recompiler is left as a followup.
2023-11-29 21:43:52 +00:00
invertego cd11421d72 recompiler: enable instruction tracing w/recompilers (#1301)
To prevent undue performance losses, the extra codegen only occurs when
tracing is enabled. To support toggling tracing on/off during execution,
the recompiler cache is flushed on setting changes.

Flushing the recompiler cache has its own side effects, but that's
already an issue, and it seemed the least bad way to isolate the impact
of this feature.
2023-11-29 21:43:30 +00:00
remutro 079295e2ca Enable/Disable N64 Expansion Pak (#1302)
Add ability to enable/disable N64 4MB expansion pak. 

Similarly to the video settings window, I've added a Nintendo 64
specific section in the options window, which was mostly empty. Existing
options fall under a generic "Emulator Options" section. Seems like a
reasonable place to add a few console specific options.

Addresses issue: https://github.com/ares-emulator/ares/issues/939
2023-11-29 21:43:20 +00:00
Luke Usher 2a819403db ci: pass local=false and build=optimised to macos builds
Fixes an issue where release builds for macOS were optimised for the compiling machine rather than for distribution.
2023-11-29 16:23:36 +00:00
Luke Usher df031a7d4c desktop-ui: rename FPS to VPS (VBlanks Per Second) to better represent what it is really showing 2023-11-28 21:52:55 +00:00
Luke Usher 0dd3edebe0 desktop-ui: fix an issue where "Deep Black Boost" did not save 2023-11-28 21:25:43 +00:00
Luke Usher f9d38f5daf desktop-ui: rename "Reload" to "Apply" in driver settings to clarify it's function 2023-11-28 21:24:09 +00:00
886 changed files with 184133 additions and 9066 deletions

No files matched your search

+17 -3
View File
@@ -8,6 +8,7 @@ on:
jobs:
build:
strategy:
fail-fast: false
matrix:
program:
- ares
@@ -77,6 +78,7 @@ jobs:
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 }}
@@ -85,7 +87,7 @@ jobs:
keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
- name: Checkout source code
uses: actions/checkout@v2
- name: "macOS: recover cache"
- name: "macOS: recover MoltenVK cache"
if: runner.os == 'macOS'
uses: actions/cache@v3
env:
@@ -93,12 +95,23 @@ jobs:
with:
path: thirdparty/MoltenVK/MoltenVK
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('thirdparty/MoltenVK/HEAD') }}
- name: "macOS: recover SDL cache"
if: runner.os == 'macOS'
uses: actions/cache@v3
env:
cache-name: sdl
with:
path: thirdparty/SDL/SDL
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('thirdparty/SDL/HEAD') }}
- name: Install macOS Dependencies
if: runner.os == 'macOS'
run: |
brew install make ninja sdl2
brew install make ninja cmake
pip install --no-input setuptools
echo "MAKE=gmake" >> $GITHUB_ENV
pushd thirdparty/SDL
./build-SDL.sh
popd
pushd thirdparty/MoltenVK
./build-moltenvk.sh
popd
@@ -112,7 +125,7 @@ jobs:
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
run: scripts/macos-make-universal.sh build=optimized local=false
env:
MAKEFLAGS: -j3
MACOS_CERTIFICATE_NAME: ${{ secrets.MACOS_CERTIFICATE_NAME }}
@@ -130,6 +143,7 @@ jobs:
timestampUrl: 'http://timestamp.digicert.com'
- name: "macOS: notarize"
if: runner.os == 'macOS' && github.event_name != 'pull_request'
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
+2 -1
View File
@@ -34,7 +34,8 @@ ares supports additional audio drivers besides the ALSA drivers included above.
By default, GTK3 is used, but support for GTK2 is available. You will need to install the additional package `libgtk2.0-dev` as well as specifying the command line option `hiro=gtk2` at compile time.
###### SDL2 for input
If you would like to use SDL for input, you will need to install the following the `libsdl2-dev` and `libsdl2-0.0` packages and perform a clean build of ares. You should then be able to select SDL for input in the Settings > Drivers menu.
If you would like to use SDL for input (e.g. for using a controller), you will need to install the `libsdl2-dev` and `libsdl2-2.0-0` packages and perform a clean build of ares.
You should then be able to select SDL for input in the Settings > Drivers menu.
##### Building with clang
-4
View File
@@ -13,10 +13,6 @@ 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 ($(platform),macos)
ares.dylibs :=
endif
ifeq ($(vulkan),true)
flags += -DVULKAN
endif
+2
View File
@@ -1,5 +1,7 @@
inline auto CPU::readBus(n16 address) -> n8 {
address &= 0x1fff;
if(auto result = platform->cheat(address)) return *result;
n8 data = cartridge.read(address);
if(address.bit(12) == 0 && address.bit(7) == 0) {
+2 -1
View File
@@ -72,8 +72,9 @@ auto TIA::write(n8 address, n8 data) -> void {
auto TIA::vsync(n1 state) -> void {
if(io.vsync && !state) {
if(!io.vsync && state) {
frame();
tia.io.vcounter = 0;
}
io.vsync = state;
+10 -15
View File
@@ -13,12 +13,12 @@ TIA tia;
auto TIA::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("TIA");
screen = node->append<Node::Video::Screen>("Screen", 160, vlines());
screen = node->append<Node::Video::Screen>("Screen", 180, displayHeight());
screen->colors(1 << 7, {&TIA::color, this});
screen->setSize(160, displayHeight());
screen->setScale(1.0, 1.0);
screen->setAspect(12.0, 7.0);
screen->setViewport(0, 0, 160, displayHeight());
screen->setSize(180, displayHeight());
screen->setScale(2.0, 1.0);
Region::PAL() ? screen->setAspect(38.0, 45.0) : screen->setAspect(4.0, 5.0);
screen->setViewport(0, 0, 180, displayHeight());
stream = node->append<Node::Audio::Stream>("Audio");
stream->setChannels(1);
@@ -46,7 +46,8 @@ auto TIA::scanline() -> void {
for(io.hcounter = 0; io.hcounter < 228; io.hcounter++) {
writeQueue.step();
auto x = io.hcounter - 68;
if(x >= 0 && io.vcounter < vlines()) pixel(x);
auto y = io.vcounter - voffset();
if(x >= 0 && y > 0 && y < vlines()) pixel(x);
runAudio();
step();
if(io.hcounter == 0) cpu.io.rdyLine = 1;
@@ -60,7 +61,9 @@ auto TIA::scanline() -> void {
}
auto TIA::pixel(n8 x) -> void {
auto output = screen->pixels().data() + (io.vcounter * 160);
auto y = io.vcounter - voffset();
auto output = screen->pixels().data() + (y * 180) + 10;
if(y >= displayHeight()) return;
// Output only black during vblank, or for the first 8px of a scanline where hmove was triggered
if (io.vblank || (io.hmoveTriggered == io.vcounter && x < 8)) {
@@ -190,14 +193,6 @@ auto TIA::runBall(n8 x) -> n1 {
}
auto TIA::frame() -> void {
auto height = io.vcounter;
if(height <= 0) {
debug(unusual, "invalid screen height");
height = vlines();
}
screen->setViewport(0, 0, 160, height);
screen->frame();
scheduler.exit(Event::Frame);
}
+2 -1
View File
@@ -4,7 +4,8 @@ struct TIA : Thread {
Node::Audio::Stream stream;
auto vlines() const -> u32 { return Region::NTSC() ? 262 : 312; }
auto displayHeight() const -> u32 { return Region::NTSC() ? 192 : 228; }
auto displayHeight() const -> u32 { return Region::PAL() ? 243 : 228; }
auto voffset() const -> s32 { return Region::PAL() ? 40 : 20; }
//tia.cpp
auto load(Node::Object) -> void;
+1 -1
View File
@@ -23,7 +23,7 @@ auto TIA::WriteQueue::step() -> void {
auto TIA::WriteQueue::QueueItem::commit() -> void {
switch(address) {
case 0x01: tia.io.vblank = data.bit(1); if(tia.io.vblank == 0) { tia.io.vcounter = 0; } return; // VBLANK
case 0x01: tia.io.vblank = data.bit(1); return; // VBLANK
case 0x0b: tia.player[0].reflect = data.bit(3); return; // REFP0
case 0x0c: tia.player[1].reflect = data.bit(3); return; // REFP1
case 0x0d: tia.playfield.graphics.bit(0, 3) = data.bit(4, 7); return; // PF0
+1 -1
View File
@@ -10,7 +10,7 @@ Platform* platform = nullptr;
bool _runAhead = false;
const string Name = "ares";
const string Version = "134";
const string Version = "136";
const string Copyright = "ares team, Near";
const string License = "ISC";
const string LicenseURI = "https://opensource.org/licenses/ISC";
+4 -2
View File
@@ -12,10 +12,11 @@ struct Tracer : Debugger {
auto file() const -> bool { return _file; }
auto autoLineBreak() const -> bool { return _autoLineBreak; }
auto setToggle(function<void ()> toggle) -> void { _toggle = toggle; }
auto setComponent(string component) -> void { _component = component; }
auto setPrefix(bool prefix) -> void { _prefix = prefix; }
auto setTerminal(bool terminal) -> void { _terminal = terminal; }
auto setFile(bool file) -> void { _file = file; }
auto setTerminal(bool terminal) -> void { _terminal = terminal; if(_toggle) _toggle(); }
auto setFile(bool file) -> void { _file = file; if(_toggle) _toggle(); }
auto setAutoLineBreak(bool autoLineBreak) -> void { _autoLineBreak = autoLineBreak; }
auto serialize(string& output, string depth) -> void override {
@@ -35,6 +36,7 @@ struct Tracer : Debugger {
}
protected:
function<void ()> _toggle;
string _component;
bool _prefix = false;
bool _terminal = false;
+5
View File
@@ -76,6 +76,11 @@ auto Screen::setViewport(u32 x, u32 y, u32 width, u32 height) -> void {
_viewportHeight = height;
}
auto Screen::setOverscan(bool overscan) -> void {
lock_guard<recursive_mutex> lock(_mutex);
_overscan = overscan;
}
auto Screen::setSize(u32 width, u32 height) -> void {
lock_guard<recursive_mutex> lock(_mutex);
_width = width;
+3
View File
@@ -16,6 +16,7 @@ struct Screen : Video {
auto scaleY() const -> f64 { return _scaleY; }
auto aspectX() const -> f64 { return _aspectX; }
auto aspectY() const -> f64 { return _aspectY; }
auto overscan() const -> bool { return _overscan; }
auto colors() const -> u32 { return _colors; }
auto pixels(bool frame = 0) -> array_span<u32>;
@@ -37,6 +38,7 @@ struct Screen : Video {
auto setSize(u32 width, u32 height) -> void;
auto setScale(f64 scaleX, f64 scaleY) -> void;
auto setAspect(f64 aspectX, f64 aspectY) -> void;
auto setOverscan(bool overscan) -> void;
auto setSaturation(f64 saturation) -> void;
auto setGamma(f64 gamma) -> void;
@@ -81,6 +83,7 @@ protected:
bool _colorBleed = false;
bool _colorBleedWidth = 1;
bool _interframeBlending = false;
bool _overscan = true;
u32 _rotation = 0; //counter-clockwise (90 = left, 270 = right)
function<n64 (n32)> _color;
+1
View File
@@ -20,6 +20,7 @@ struct Platform {
virtual auto video(Node::Video::Screen, const u32* data, u32 pitch, u32 width, u32 height) -> void {}
virtual auto audio(Node::Audio::Stream) -> void {}
virtual auto input(Node::Input::Input) -> void {}
virtual auto cheat(u32 addr) -> maybe<u32> { return nothing; }
};
extern Platform* platform;
@@ -62,7 +62,7 @@ struct ARM7TDMI {
auto thumbInitialize() -> void;
//instructions-arm.cpp
auto armALU(n4 mode, n4 target, n4 source, n32 data) -> void;
auto armALU(n4 mode, n4 target, n32 source, n32 data) -> void;
auto armMoveToStatus(n4 field, n1 source, n32 data) -> void;
auto armInstructionBranch(i24, n1) -> void;
@@ -1,6 +1,4 @@
auto ARM7TDMI::armALU(n4 mode, n4 d, n4 n, n32 rm) -> void {
n32 rn = r(n);
auto ARM7TDMI::armALU(n4 mode, n4 d, n32 rn, n32 rm) -> void {
switch(mode) {
case 0: r(d) = BIT(rn & rm); break; //AND
case 1: r(d) = BIT(rn ^ rm); break; //EOR
@@ -64,14 +62,16 @@ auto ARM7TDMI::armInstructionBranchExchangeRegister
auto ARM7TDMI::armInstructionDataImmediate
(n8 immediate, n4 shift, n4 d, n4 n, n1 save, n4 mode) -> void {
n32 rn = r(n);
n32 data = immediate;
carry = cpsr().c;
if(shift) data = ROR(data, shift << 1);
armALU(mode, d, n, data);
armALU(mode, d, rn, data);
}
auto ARM7TDMI::armInstructionDataImmediateShift
(n4 m, n2 type, n5 shift, n4 d, n4 n, n1 save, n4 mode) -> void {
n32 rn = r(n);
n32 rm = r(m);
carry = cpsr().c;
@@ -82,12 +82,13 @@ auto ARM7TDMI::armInstructionDataImmediateShift
case 3: rm = shift ? ROR(rm, shift) : RRX(rm); break;
}
armALU(mode, d, n, rm);
armALU(mode, d, rn, rm);
}
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);
n32 rn = r(n) + (n == 15 ? 4 : 0);
n32 rm = r(m) + (m == 15 ? 4 : 0);
carry = cpsr().c;
@@ -98,7 +99,7 @@ auto ARM7TDMI::armInstructionDataRegisterShift
case 3: if(rs) rm = ROR(rm, rs & 31 ? u32(rs & 31) : 32); break;
}
armALU(mode, d, n, rm);
armALU(mode, d, rn, rm);
}
auto ARM7TDMI::armInstructionLoadImmediate
@@ -167,7 +168,7 @@ auto ARM7TDMI::armInstructionMoveHalfRegister
auto ARM7TDMI::armInstructionMoveImmediateOffset
(n12 immediate, n4 d, n4 n, n1 mode, n1 writeback, n1 byte, 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((byte ? Byte : Word) | Nonsequential, rn);
@@ -201,7 +202,7 @@ auto ARM7TDMI::armInstructionMoveMultiple
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));
if(mode == 0) write(Word | sequential, rn, r(m) + (m == 15 ? 4 : 0));
rn += 4;
sequential = Sequential;
}
@@ -126,17 +126,23 @@ auto ARM7TDMI::thumbInstructionMoveMultiple
(n8 list, n3 n, n1 mode) -> void {
n32 rn = r(n);
u32 sequential = Nonsequential;
for(u32 m : range(8)) {
if(!list.bit(m)) continue;
switch(mode) {
case 0: write(Word | Nonsequential, rn, r(m)); break; //STMIA
case 1: r(m) = read(Word | Nonsequential, rn); break; //LDMIA
case 0: write(Word | sequential, rn, r(m)); break; //STMIA
case 1: r(m) = read(Word | sequential, rn); break; //LDMIA
}
rn += 4;
sequential = Sequential;
}
if(mode == 0 || !list.bit(n)) r(n) = rn;
if(mode == 1) idle();
if(mode == 1) {
idle();
} else {
pipeline.nonsequential = true;
}
}
auto ARM7TDMI::thumbInstructionMoveRegisterOffset
@@ -197,9 +197,9 @@ template<u32 Size> auto M68000::instructionASR(n4 count, DataRegister with) -> v
template<u32 Size> auto M68000::instructionASR(DataRegister from, DataRegister with) -> void {
auto count = read<Long>(from) & 63;
prefetch();
idle((Size != Long ? 2 : 4) + count * 2);
auto result = ASR<Size>(read<Size>(with), count);
prefetch();
write<Size>(with, result);
}
@@ -5,7 +5,7 @@ auto SH2::hint(P&&... p) const -> string {
return {std::forward<P>(p)...};
}
auto SH2::disassembleInstruction() -> string {
auto SH2::disassembleInstruction(u16 opcode) -> string {
vector<string> s;
auto registerName = [&](u32 r) -> string {
@@ -48,8 +48,6 @@ auto SH2::disassembleInstruction() -> string {
return {"0x", hex(PC + (i12)d * 2, 8L)};
};
u16 opcode = readWord(PC - 4);
#define n (opcode >> 8 & 0x00f)
#define m (opcode >> 4 & 0x00f)
#define i (opcode >> 0 & 0x0ff)
@@ -23,6 +23,7 @@ auto SH2::instruction() -> void {
}
ID = 0;
u16 opcode = readWord(PC - 4);
instructionPrologue(opcode);
execute(opcode);
instructionEpilogue();
}
@@ -129,6 +129,10 @@ auto SH2::Recompiler::emit(u32 address) -> Block* {
inDelaySlot = 1; //force runtime check on first instruction
while(true) {
u16 instruction = instructions[index++];
if(callInstructionPrologue) {
mov32(reg(1), imm(instruction));
call(&SH2::instructionPrologue);
}
auto branch = emitInstruction(instruction);
inDelaySlot = branch == Branch::Slot;
add64(CCR, CCR, imm(1));
+3 -1
View File
@@ -7,6 +7,7 @@
namespace ares {
struct SH2 {
virtual auto instructionPrologue(u16 instruction) -> void = 0;
virtual auto step(u32 clocks) -> void = 0;
virtual auto busReadByte(u32 address) -> u32 = 0;
virtual auto busReadWord(u32 address) -> u32 = 0;
@@ -189,7 +190,7 @@ struct SH2 {
//disassembler.cpp
template<typename... P> auto hint(P&&...) const -> string;
auto disassembleInstruction() -> string;
auto disassembleInstruction(u16 opcode) -> string;
auto disassembleContext() -> string;
static constexpr u32 undefined = 0;
@@ -299,6 +300,7 @@ struct SH2 {
static auto mask(u8 address, u8 size) -> u64;
bool callInstructionPrologue = false;
bool inDelaySlot;
u32 generation;
bump_allocator allocator;
@@ -13,11 +13,9 @@ auto SH2::INTC::run() -> void {
if(self->dmac.pendingIRQ & 1) {
self->interrupt(ipra.dmacip, self->dmac.vcrdma[0] & 0x7f);
self->dmac.pendingIRQ &= ~1;
print("dreq0\n");
} else if(self->dmac.pendingIRQ & 2) {
self->interrupt(ipra.dmacip, self->dmac.vcrdma[1] & 0x7f);
self->dmac.pendingIRQ &= ~2;
print("dreq1\n");
}
}
}
@@ -290,6 +290,7 @@ auto Z80::instructionINDR() -> void { Q = 1;
if(!B) return;
wait(5);
PC -= 2;
WZ = PC + 1;
XF = PC.bit(11);
YF = PC.bit(13);
@@ -327,6 +328,7 @@ auto Z80::instructionINIR() -> void { Q = 1;
if(!B) return;
wait(5);
PC -= 2;
WZ = PC + 1;
XF = PC.bit(11);
YF = PC.bit(13);
@@ -516,6 +518,7 @@ auto Z80::instructionOTDR() -> void { Q = 1;
if(!B) return;
wait(5);
PC -= 2;
WZ = PC + 1;
XF = PC.bit(11);
YF = PC.bit(13);
@@ -537,6 +540,7 @@ auto Z80::instructionOTIR() -> void { Q = 1;
if(!B) return;
wait(5);
PC -= 2;
WZ = PC + 1;
XF = PC.bit(11);
YF = PC.bit(13);
+1 -4
View File
@@ -14,10 +14,7 @@ auto TMS9918::Background::run(n8 hoffset, n8 voffset) -> void {
}
auto TMS9918::Background::text1(n8 hoffset, n8 voffset) -> void {
// Offset by 6px and blank the last 10px to centre the display
i32 x = hoffset - 6;
if (x < 0) return;
if (hoffset > 246) return;
i32 x = hoffset;
n14 nameAddress = (voffset.bit(3,7) * 40) + (x / 6);
nameAddress.bit(10,13) = io.nameTableAddress;
+5 -3
View File
@@ -1,5 +1,7 @@
auto TMS9918::DAC::setup(n8 y) -> void {
output = self.screen->pixels().data() + y * 256;
auto TMS9918::DAC::setup(n9 y) -> void {
y = (y + 27) % 243;
output = self.screen->pixels().data() + y * 284;
for (u32 x : range(284)) output[x] = io.colorBackground;
}
auto TMS9918::DAC::run(n8 x, n8 y) -> void {
@@ -8,7 +10,7 @@ auto TMS9918::DAC::run(n8 x, n8 y) -> void {
if(self.background.output.color) color = self.background.output.color;
if(self.sprite.output.color) color = self.sprite.output.color;
}
output[x] = color;
output[(x + 13) % 284] = color;
}
auto TMS9918::DAC::power() -> void {
+1 -1
View File
@@ -18,11 +18,11 @@ auto TMS9918::unload() -> void {
}
auto TMS9918::main() -> void {
dac.setup(io.vcounter);
if(io.vcounter < 192) {
n8 y = io.vcounter;
background.setup(y);
sprite.setup(y);
dac.setup(y);
for(n8 x : range(256)) {
background.run(x, y);
sprite.run(x, y);
+1 -1
View File
@@ -101,7 +101,7 @@ protected:
TMS9918& self;
//dac.cpp
auto setup(n8 voffset) -> void;
auto setup(n9 voffset) -> void;
auto run(n8 hoffset, n8 voffset) -> void;
auto power() -> void;
+1 -4
View File
@@ -20,10 +20,7 @@ auto V9938::Background::run(n8 x, n8 y) -> void {
}
auto V9938::Background::text1(n8 hoffset, n8 voffset) -> void {
// Offset by 6px and blank the last 10px to centre the display
i32 x = hoffset - 6;
if (x < 0) return;
if (hoffset > 246) return;
i32 x = hoffset;
n14 nameAddress = (voffset.bit(3,7) * 40) + (x / 6);
nameAddress.bit(10,16) = io.nameTableAddress.bit(10,16);
+7 -4
View File
@@ -1,7 +1,10 @@
auto V9938::DAC::setup(n8 voffset) -> void {
n9 vcenter = voffset + (!self.overscan() ? 10 : 0); //centers output within buffer
output = self.screen->pixels().data() + vcenter * 1024;
if(self.interlace() && self.field()) output += 512;
auto V9938::DAC::setup(n9 voffset) -> void {
//TODO: Verify border in overscan mode
voffset = (voffset + (self.latch.overscan ? 13 : 27)) % 243;
output = self.screen->pixels().data() + voffset * 1136;
if(self.interlace() && self.field()) output += 568;
for(auto n: range(568)) output[n] = self.pram[self.io.colorBackground];
output += 26;
}
auto V9938::DAC::run(n8 hoffset, n8 voffset) -> void {
+1 -1
View File
@@ -19,12 +19,12 @@ auto V9938::unload() -> void {
}
auto V9938::main() -> void {
dac.setup(io.vcounter);
if(io.vcounter < vlines()) {
n9 y = io.vcounter;
latch.vscroll = io.vscroll;
background.setup(y);
sprite.setup(y + latch.vscroll);
dac.setup(y);
while(io.hcounter < 256) {
n9 x = io.hcounter;
+1 -1
View File
@@ -154,7 +154,7 @@ protected:
V9938& self;
//dac.cpp
auto setup(n8 voffset) -> void;
auto setup(n9 voffset) -> void;
auto run(n8 hoffset, n8 voffset) -> void;
auto power() -> void;
+33
View File
@@ -0,0 +1,33 @@
namespace Board {
#include "coleco.cpp"
#include "xin1.cpp"
#include "megacart.cpp"
auto Interface::load(Memory::Readable<n8>& memory, string name) -> bool {
if(auto fp = pak->read(name)) {
memory.allocate(fp->size());
memory.load(fp);
return true;
}
return false;
}
auto Interface::load(Memory::Writable<n8>& memory, string name) -> bool {
if(auto fp = pak->read(name)) {
memory.allocate(fp->size());
memory.load(fp);
return true;
}
return false;
}
auto Interface::save(Memory::Writable<n8>& memory, string name) -> bool {
if(auto fp = pak->write(name)) {
memory.save(fp);
return true;
}
return false;
}
}
+23
View File
@@ -0,0 +1,23 @@
namespace Board {
struct Interface {
VFS::Pak pak;
Interface(Cartridge& cartridge) : cartridge(cartridge) {}
virtual ~Interface() = default;
virtual auto load() -> void {}
virtual auto save() -> void {}
virtual auto unload() -> void {}
virtual auto read(n16 address) -> n8 { return 0; }
virtual auto write(n16 address, n8 data) -> void {}
virtual auto power() -> void {}
virtual auto serialize(serializer&) -> void {}
auto load(Memory::Readable<n8>&, string name) -> bool;
auto load(Memory::Writable<n8>&, string name) -> bool;
auto save(Memory::Writable<n8>&, string name) -> bool;
Cartridge& cartridge;
};
}
+25
View File
@@ -0,0 +1,25 @@
struct coleco : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
auto load() -> void override {
Interface::load(rom, "program.rom");
}
auto save() -> void override {}
auto unload() -> void override {}
auto read(n16 address) -> n8 override {
return rom.read(address);
}
auto write(n16 address, n8 data) -> void override {
return;
}
auto power() -> void override {}
auto serialize(serializer& s) -> void override {}
};
+35
View File
@@ -0,0 +1,35 @@
struct megacart : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
auto load() -> void override {
Interface::load(rom, "program.rom");
bankCount = rom.size() >> 14;
}
auto save() -> void override {}
auto unload() -> void override {}
auto read(n16 address) -> n8 override {
if(address >= 0x7fc0) return bank = address & (bankCount - 1);
if(address >= 0x4000) return rom.read((bank << 14) + (address - 0x4000));
return rom.read((bankCount << 14) + (address - 0x4000));
}
auto write(n16 address, n8 data) -> void override {
return;
}
auto power() -> void override {
bank = 0;
}
auto serialize(serializer& s) -> void override {
s(bank);
s(bankCount);
}
n16 bank;
n16 bankCount;
};
+34
View File
@@ -0,0 +1,34 @@
struct xin1 : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
auto load() -> void override {
Interface::load(rom, "program.rom");
bankCount = rom.size() / 0x8000;
}
auto save() -> void override {}
auto unload() -> void override {}
auto read(n16 address) -> n8 override {
if(address >= 0x7fc0) return bank = (address - 0x7fc0) % bankCount;
return rom.read(address + (bank * 0x8000));
}
auto write(n16 address, n8 data) -> void override {
return;
}
auto power() -> void override {
bank = bankCount - 1;
}
auto serialize(serializer& s) -> void override {
s(bank);
s(bankCount);
}
n16 bank;
n16 bankCount;
};
+23 -10
View File
@@ -3,6 +3,7 @@
namespace ares::ColecoVision {
Cartridge& cartridge = cartridgeSlot.cartridge;
#include "board/board.cpp"
#include "slot.cpp"
#include "serialization.cpp"
@@ -16,31 +17,43 @@ auto Cartridge::connect() -> void {
information = {};
information.title = pak->attribute("title");
information.region = pak->attribute("region");
information.board = pak->attribute("board");
if(auto fp = pak->read("program.rom")) {
rom.allocate(fp->size());
rom.load(fp);
}
if(information.board == "xin1") board = new Board::xin1{*this};
if(information.board == "coleco") board = new Board::coleco{*this};
if(information.board == "megacart") board = new Board::megacart{*this};
if(!board) board = new Board::Interface{*this};
board->pak = pak;
board->load();
power();
}
auto Cartridge::disconnect() -> void {
if(!node) return;
rom.reset();
pak.reset();
if(!node || !board) return;
board->unload();
board->pak.reset();
board.reset();
node.reset();
}
auto Cartridge::save() -> void {
if(!node) return;
if(board) board->save();
}
auto Cartridge::power() -> void {
if(board) board->power();
}
auto Cartridge::read(n16 address) -> n8 {
if(address >= rom.size()) return 0xff;
return rom.read(address);
if(board) return board->read(address);
return 0;
}
auto Cartridge::write(n16 address, n8 data) -> void {
if(board) return board->write(address, data);
}
}
+8 -3
View File
@@ -1,3 +1,6 @@
struct Cartridge;
#include "board/board.hpp"
struct Cartridge {
Node::Peripheral node;
VFS::Pak pak;
@@ -14,17 +17,19 @@ struct Cartridge {
auto power() -> void;
auto read(n16 address) -> n8;
auto write(n16 address, n8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private:
unique_pointer<Board::Interface> board;
//private:
struct Information {
string title;
string region;
string board;
} information;
Memory::Readable<n8> rom;
};
#include "slot.hpp"
+1
View File
@@ -1,5 +1,6 @@
auto CPU::read(n16 address) -> n8 {
n8 data = 0xff;
if(auto result = platform->cheat(address)) return *result;
if(address >= 0x0000 && address <= 0x1fff && io.replaceBIOS) return expansion.read(address);
if(address >= 0x2000 && address <= 0x7fff && io.replaceRAM ) return expansion.read(address);
if(address >= 0x0000 && address <= 0x1fff) return system.bios[address & 0x1fff];
+16 -3
View File
@@ -11,12 +11,12 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 256, 192);
screen = node->append<Node::Video::Screen>("Screen", 284, 243);
screen->colors(1 << 4, {&VDP::color, this});
screen->setSize(256, 192);
screen->setSize(284, 243);
screen->setScale(1.0, 1.0);
screen->setAspect(8.0, 7.0);
screen->setViewport(0, 0, 256, 192);
screen->setViewport(0, 0, 284, 243);
TMS9918::load(screen);
}
@@ -39,6 +39,19 @@ auto VDP::irq(bool line) -> void {
}
auto VDP::frame() -> void {
if(screen->overscan()) {
screen->setSize(284, 243);
screen->setViewport(0, 0, 284, 243);
} else {
int x = 16;
int y = 16;
int width = 284 - 32;
int height = 243 - 32;
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+1
View File
@@ -7,6 +7,7 @@
//$4018-ffff = Cartridge
inline auto CPU::readBus(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
n8 data = cartridge.readPRG(address, MDR);
if(address <= 0x1fff) return ram.read(address);
if(address <= 0x3fff) return ppu.readIO(address);
+11 -3
View File
@@ -1,9 +1,13 @@
auto FDSTimer::clock() -> void {
if(enable && !--counter) {
if(!enable || !irq) return;
if(!counter) {
pending = 1;
fds.poll();
if(repeat) counter = period;
irq &= repeat;
} else {
counter--;
}
}
@@ -40,12 +44,16 @@ auto FDSTimer::write(n16 address, n8 data) -> void {
counter = period;
} else {
pending = 0;
fds.poll();
}
return;
case 0x4025:
case 0x4023:
enable = data.bit(0);
if(!enable) pending = 0;
if(!enable) {
pending = 0;
fds.poll();
}
return;
}
+19 -12
View File
@@ -16,17 +16,11 @@ auto PPU::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("PPU");
screen = node->append<Node::Video::Screen>("Screen", 256, 240);
screen = node->append<Node::Video::Screen>("Screen", 282, displayHeight());
screen->colors(1 << 9, {&PPU::color, this});
screen->setSize(256, 240);
screen->setSize(282, displayHeight());
screen->setScale(1.0, 1.0);
screen->setAspect(8.0, 7.0);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(256, 224);
if(value == 1) screen->setSize(256, 240);
});
overscan->setDynamic(true);
Region::PAL() ? screen->setAspect(55.0, 43.0) :screen->setAspect(8.0, 7.0);
debugger.load(node);
}
@@ -35,7 +29,6 @@ auto PPU::unload() -> void {
screen->quit();
node->remove(screen);
debugger.unload();
overscan.reset();
screen.reset();
node.reset();
ciram.reset();
@@ -80,8 +73,22 @@ auto PPU::scanline() -> void {
auto PPU::frame() -> void {
io.field++;
if(overscan->value() == 0) screen->setViewport(0, 8, 256, 224);
if(overscan->value() == 1) screen->setViewport(0, 0, 256, 240);
if(screen->overscan()) {
screen->setSize(282, displayHeight());
screen->setViewport(0, 0, 282, displayHeight());
} else {
int x = 16;
int y = 8;
int width = 282 - 32;
int height = displayHeight() - 16;
if(Region::PAL()) height -= 48;
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+3 -1
View File
@@ -1,7 +1,6 @@
struct PPU : Thread {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
Memory::Writable<n8> ciram;
Memory::Writable<n8> cgram;
Memory::Writable<n8> oam;
@@ -20,6 +19,7 @@ struct PPU : Thread {
auto rate() const -> u32 { return Region::PAL() ? 5 : 4; }
auto vlines() const -> u32 { return Region::PAL() ? 312 : 262; }
auto displayHeight() const -> u32 { return Region::PAL() ? 288 : 242; }
//ppu.cpp
auto load(Node::Object) -> void;
@@ -133,6 +133,8 @@ struct PPU : Thread {
OAM oam[8]; //primary
OAM soam[8]; //secondary
} latch;
u32* output;
};
extern PPU ppu;
+15 -3
View File
@@ -7,9 +7,7 @@ auto PPU::loadCHR(n16 address) -> n8 {
}
auto PPU::renderPixel() -> void {
//todo: renderPixel() is called when Y=261 ... why?
if(io.ly >= screen->canvasHeight()) return;
auto output = screen->pixels().data() + io.ly * 256;
u32 x = io.lx - 1;
u32 mask = 0x8000 >> (io.v.fineX + (x & 7));
@@ -17,6 +15,9 @@ auto PPU::renderPixel() -> void {
u32 objectPalette = 0;
bool objectPriority = 0;
//PAL systems blank the topmost scanline and the first and last 2px of active display
if(Region::PAL()) if(io.ly == 0 || x < 2 || x > 254) return;
palette |= latch.tiledataLo & mask ? 1 : 0;
palette |= latch.tiledataHi & mask ? 2 : 0;
if(palette) {
@@ -55,7 +56,13 @@ auto PPU::renderPixel() -> void {
}
if(!enable()) palette = 0;
output[x] = io.emphasis << 6 | readCGRAM(palette);
if(Region::PAL()) {
output[(x + 18) % 282] = io.emphasis << 6 | readCGRAM(palette);
return;
}
output[(x + 15) % 282] = io.emphasis << 6 | readCGRAM(palette);
}
auto PPU::renderSprite() -> void {
@@ -80,6 +87,11 @@ auto PPU::renderSprite() -> void {
}
auto PPU::renderScanline() -> void {
if(io.ly < screen->canvasHeight()) {
output = screen->pixels().data() + io.ly * 282;
for(auto n : range(282)) output[n] = Region::PAL() ? 0x3f : (io.emphasis << 6 | readCGRAM(0));
}
//Vblank
if(io.ly >= 240 && io.ly <= vlines() - 2) return step(341), scanline();
+1
View File
@@ -5,6 +5,7 @@ namespace ares::GameBoy {
Bus bus;
auto Bus::read(u32 cycle, n16 address, n8 data) -> n8 {
if(auto result = platform->cheat(address)) return *result;
data &= cpu.readIO(cycle, address, data);
data &= apu.readIO(cycle, address, data);
data &= ppu.readIO(cycle, address, data);
+4
View File
@@ -21,6 +21,10 @@ auto APU::readIO(n32 address) -> n8 {
case 0x0400'0068: return square2.read(1);
case 0x0400'0069: return square2.read(2);
//zero
case 0x0400'006a: return 0x00;
case 0x0400'006b: return 0x00;
//NR23, NR24
case 0x0400'006c: return square2.read(3);
case 0x0400'006d: return square2.read(4);
+3 -2
View File
@@ -15,7 +15,7 @@ auto CPU::get(u32 mode, n32 address) -> n32 {
word = prefetchRead();
if(mode & Word) word |= prefetchRead() << 16;
} else {
if(!context.dmaActive) prefetchWait();
if(!context.dmaActive) prefetchReset(); //todo: Prefetch buffer should also be reset when DMA accesses ROM
step(clocks - 1);
word = cartridge.read(mode, address);
step(1);
@@ -23,6 +23,7 @@ auto CPU::get(u32 mode, n32 address) -> n32 {
} else {
if(memory.biosSwap && address < 0x0400'0000) address ^= 0x0200'0000;
prefetchStep(clocks);
if(auto result = platform->cheat(address)) return *result;
if(address < 0x0200'0000) word = bios.read(mode, address);
else if(address < 0x0300'0000) word = readEWRAM(mode, address);
else if(address < 0x0400'0000) word = readIWRAM(mode, address);
@@ -42,7 +43,7 @@ auto CPU::set(u32 mode, n32 address, n32 word) -> void {
if(address >= 0x1000'0000) {
prefetchStep(clocks);
} else if(address & 0x0800'0000) {
if(!context.dmaActive) prefetchWait();
if(!context.dmaActive) prefetchReset(); //todo: Prefetch buffer should also be reset when DMA accesses ROM
step(clocks);
cartridge.write(mode, address, word);
} else {
+3 -3
View File
@@ -126,9 +126,9 @@ auto CPU::power() -> void {
for(u32 n = 0x100; n <= 0x10f; n++) bus.io[n] = this; //Timers
for(u32 n = 0x120; n <= 0x12b; n++) bus.io[n] = this; //Serial
for(u32 n = 0x130; n <= 0x133; n++) bus.io[n] = this; //Keypad
for(u32 n = 0x134; n <= 0x159; n++) bus.io[n] = this; //Serial
for(u32 n = 0x200; n <= 0x209; n++) bus.io[n] = this; //System
for(u32 n = 0x300; n <= 0x301; n++) bus.io[n] = this; //System
for(u32 n = 0x134; n <= 0x15b; n++) bus.io[n] = this; //Serial
for(u32 n = 0x200; n <= 0x20b; n++) bus.io[n] = this; //System
for(u32 n = 0x300; n <= 0x303; n++) bus.io[n] = this; //System
//0x080-0x083 mirrored via gba/memory/memory.cpp //System
}
+1 -1
View File
@@ -52,7 +52,7 @@ struct CPU : ARM7TDMI, Thread, IO {
//prefetch.cpp
auto prefetchSync(n32 address) -> void;
auto prefetchStep(u32 clocks) -> void;
auto prefetchWait() -> void;
auto prefetchReset() -> void;
auto prefetchRead() -> n16;
//bus.cpp
+10 -6
View File
@@ -13,12 +13,8 @@ auto CPU::DMA::transfer() -> void {
mode |= latch.length() == length() ? Nonsequential : Sequential;
if(mode & Nonsequential) {
if((source() & 0x0800'0000) && (target() & 0x0800'0000)) {
//ROM -> ROM transfer
} else {
cpu.idle();
cpu.idle();
}
cpu.idle();
cpu.idle();
}
if(latch.source() < 0x0200'0000) {
@@ -31,6 +27,14 @@ auto CPU::DMA::transfer() -> void {
if(mode & Half) cpu.dmabus.data |= cpu.dmabus.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 {
+18 -1
View File
@@ -144,6 +144,10 @@ auto CPU::readIO(n32 address) -> n8 {
joybus.siIRQEnable << 0
| joybus.mode << 6
);
//zero
case 0x0400'0136: return 0;
case 0x0400'0137: return 0;
//JOYCNT
case 0x0400'0140: return (
@@ -201,14 +205,26 @@ auto CPU::readIO(n32 address) -> n8 {
| wait.prefetch << 6
| wait.gameType << 7
);
//zero
case 0x0400'0206: return 0;
case 0x0400'0207: return 0;
//IME
case 0x0400'0208: return irq.ime;
case 0x0400'0209: return 0;
//zero
case 0x0400'020a: return 0;
case 0x0400'020b: return 0;
//POSTFLG + HALTCNT
case 0x0400'0300: return context.booted;
case 0x0400'0301: return 0;
//zero
case 0x0400'0302: return 0;
case 0x0400'0303: return 0;
//MEMCNT_L
case 0x0400'0800: return (
@@ -293,10 +309,11 @@ auto CPU::writeIO(n32 address, n8 data) -> void {
bool enable = timer().enable;
timer().frequency = data.bit(0,1);
timer().cascade = data.bit(2);
timer().irq = data.bit(6);
timer().enable = data.bit(7);
if(address != 0x0400'0102) timer().cascade = data.bit(2);
if(!enable && timer().enable) { //0->1 transition
timer().pending = true;
}
+3 -5
View File
@@ -18,11 +18,9 @@ auto CPU::prefetchStep(u32 clocks) -> void {
}
}
auto CPU::prefetchWait() -> void {
if(!wait.prefetch || context.dmaActive || prefetch.full()) return;
prefetchStep(prefetch.wait);
prefetch.wait = _wait(Half | Nonsequential, prefetch.load);
auto CPU::prefetchReset() -> void {
prefetch.addr = 0;
prefetch.load = 0;
}
auto CPU::prefetchRead() -> n16 {
+6 -3
View File
@@ -56,12 +56,12 @@ auto PPU::Objects::scanline(u32 y) -> void {
}
} else if(!buffer[bx].enable || object.priority < buffer[bx].priority) {
buffer[bx].priority = object.priority; //updated regardless of transparency
buffer[bx].mosaic = object.mosaic; //updated regardless of transparency
if(color) {
if(object.colors == 0) color = object.palette * 16 + color;
buffer[bx].enable = true;
buffer[bx].color = ppu.pram[256 + color];
buffer[bx].translucent = object.mode == 1;
buffer[bx].mosaic = object.mosaic;
}
}
}
@@ -80,12 +80,15 @@ auto PPU::Objects::run(u32 x, u32 y) -> void {
}
output = buffer[x];
//horizontal mosaic
if(!output.mosaic || ++mosaicOffset >= 1 + io.mosaicWidth) {
if(mosaicOffset >= 1 + io.mosaicWidth) {
mosaicOffset = 0;
mosaic = output;
} else if(!mosaic.mosaic || !output.mosaic) {
mosaic = output;
}
mosaicOffset++;
}
auto PPU::Objects::power() -> void {
+1
View File
@@ -1,4 +1,5 @@
alwaysinline auto Bus::read(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
if(auto result = platform->cheat(address)) return *result;
if(address >= 0x000000 && address <= 0x3fffff) {
waitRefreshExternal();
if(!cpu.io.romEnable) {
@@ -11,9 +11,7 @@ ControlPad::ControlPad(Node::Port parent) {
start = node->append<Node::Input::Button>("Start");
}
auto ControlPad::readData() -> n8 {
n6 data;
auto ControlPad::poll() -> void {
platform->input(up);
platform->input(down);
platform->input(left);
@@ -34,6 +32,10 @@ auto ControlPad::readData() -> n8 {
} else if(!xHold) {
xHold = 1, swap(leftLatch, rightLatch);
}
}
auto ControlPad::readData() -> Data {
n6 data;
if(select == 0) {
data.bit(0) = upLatch;
@@ -51,10 +53,9 @@ auto ControlPad::readData() -> n8 {
}
data = ~data;
return latch << 7 | select << 6 | data;
return {data, 0x3f};
}
auto ControlPad::writeData(n8 data) -> void {
select = data.bit(6);
latch = data.bit(7);
}
@@ -10,12 +10,12 @@ struct ControlPad : Controller {
ControlPad(Node::Port);
auto readData() -> n8 override;
auto poll() -> void override;
auto readData() -> Data override;
auto writeData(n8 data) -> void override;
private:
n1 select = 1;
n1 latch;
b1 yHold;
b1 upLatch;
+7 -1
View File
@@ -17,9 +17,15 @@
struct Controller {
Node::Peripheral node;
struct Data {
n8 value;
n8 mask;
};
virtual ~Controller() = default;
virtual auto readData() -> n8 { return 0xff; }
virtual auto poll() -> void {}
virtual auto readData() -> Data { return {0x7f, 0x7f}; }
virtual auto writeData(n8 data) -> void {}
};
@@ -23,15 +23,13 @@ FightingPad::~FightingPad() {
auto FightingPad::main() -> void {
if(timeout) {
timeout--;
} else {
counter = 0;
if(!--timeout) counter = 0;
}
Thread::step(1);
Thread::synchronize(cpu);
}
auto FightingPad::readData() -> n8 {
auto FightingPad::poll() -> void {
platform->input(up);
platform->input(down);
platform->input(left);
@@ -56,7 +54,9 @@ auto FightingPad::readData() -> n8 {
} else if(!xHold) {
xHold = 1, swap(leftLatch, rightLatch);
}
}
auto FightingPad::readData() -> Data {
n6 data;
if(select == 0) {
@@ -82,8 +82,6 @@ auto FightingPad::readData() -> n8 {
data.bit(1) = downLatch;
data.bit(2) = leftLatch;
data.bit(3) = rightLatch;
data.bit(4) = b->value();
data.bit(5) = c->value();
}
if(counter == 3) {
@@ -91,20 +89,21 @@ auto FightingPad::readData() -> n8 {
data.bit(1) = y->value();
data.bit(2) = x->value();
data.bit(3) = mode->value();
data.bit(4,5) = 0;
}
data.bit(4) = b->value();
data.bit(5) = c->value();
}
data = ~data;
return latch << 7 | select << 6 | data;
return {data, 0x3f};
}
auto FightingPad::writeData(n8 data) -> void {
if(!select && data.bit(6)) { //0->1 transition
if(++counter == 5) counter = 0;
if(counter < 4) ++counter;
timeout = 1600; //~1.6ms
}
select = data.bit(6);
latch = data.bit(7);
timeout = 1600; //~1.6ms
}
@@ -15,12 +15,12 @@ struct FightingPad : Controller, Thread {
FightingPad(Node::Port);
~FightingPad();
auto main() -> void;
auto readData() -> n8 override;
auto poll() -> void override;
auto readData() -> Data override;
auto writeData(n8 data) -> void override;
private:
n1 select = 1;
n1 latch;
n3 counter;
n32 timeout;
+6 -7
View File
@@ -38,7 +38,6 @@ MegaMouse::MegaMouse(Node::Port parent) {
t_data = 10 * timerfreq / 1000000;
Thread::create(timerfreq, {&MegaMouse::main, this});
Thread::synchronize(cpu);
}
MegaMouse::~MegaMouse() {
@@ -68,7 +67,7 @@ auto MegaMouse::main() -> void {
Thread::synchronize(cpu);
}
auto MegaMouse::readData() -> n8 {
auto MegaMouse::readData() -> Data {
n8 data;
if (th) {
@@ -81,14 +80,15 @@ auto MegaMouse::readData() -> n8 {
}
data.bit(4) = tl;
data.bit(5) = tr;
data.bit(6) = th;
data.bit(7) = latch;
return data;
return {data, 0x1f};
}
auto MegaMouse::writeData(n8 data) -> void {
//todo: this check wouldn't be necessary if the logic below was correct.
//this function should only respond to level changes.
if(tr == data.bit(5) && th == data.bit(6)) return;
// Falling TH
if (!data.bit(6) && th) {
// When TH falls low, make sure the second nibble is driven. The
@@ -142,6 +142,5 @@ auto MegaMouse::writeData(n8 data) -> void {
timeout = t_handshake;
}
latch = data.bit(7);
th = data.bit(6);
}
+1 -2
View File
@@ -10,14 +10,13 @@ struct MegaMouse : Controller, Thread {
~MegaMouse();
auto main() -> void;
auto readData() -> n8 override;
auto readData() -> Data override;
auto writeData(n8 data) -> void override;
private:
n1 th = 1;
n1 tr = 1;
n1 tl = 1;
n1 latch;
n8 index = 0;
n4 status[10];
s16 maxspeed = 255;
+12
View File
@@ -29,10 +29,21 @@ auto ControllerPort::allocate(string name) -> Node::Peripheral {
return {};
}
auto ControllerPort::update() -> void {
auto [inputData, inputMask] = device ? device->readData() : Controller::Data{0x7f, 0x7f};
n8 outputMask = 0x80 | control;
n8 prevLines = dataLines;
dataLines = inputData & inputMask | dataLines & ~inputMask;
dataLines = dataLatch & outputMask | dataLines & ~outputMask;
//todo: gradually pull up floating lines
if(device && prevLines != dataLines) return device->writeData(dataLines);
}
auto ControllerPort::power(bool reset) -> void {
if(!reset) {
control = 0x00;
dataLatch = 0x7f;
dataLines = 0x7f;
serialControl = 0x00;
serialTxBuffer = 0xff;
serialRxBuffer = 0x00;
@@ -42,6 +53,7 @@ auto ControllerPort::power(bool reset) -> void {
auto ControllerPort::serialize(serializer& s) -> void {
s(control);
s(dataLatch);
s(dataLines);
s(serialControl);
s(serialTxBuffer);
s(serialRxBuffer);
+8 -7
View File
@@ -11,17 +11,17 @@ struct ControllerPort {
auto connect(Node::Peripheral) -> void;
auto disconnect() -> void { device.reset(); }
auto update() -> void;
auto readControl() -> n8 { return control; }
auto writeControl(n8 data) -> void { control = data; }
auto writeControl(n8 data) -> void { control = data; update(); }
auto readData() -> n8 {
n8 data = device ? device->readData() : n8(~0);
return dataLatch = dataLatch & (0x80 | control) | data & ~(0x80 | control);
}
auto writeData(n8 data) -> void {
dataLatch = dataLatch & ~(0x80 | control) | data & (0x80 | control);
if(device) return device->writeData(dataLatch);
if(device) device->poll();
update();
return dataLines;
}
auto writeData(n8 data) -> void { dataLatch = data; update(); }
// TODO: Implement working serial transfers. This code is mostly placeholder.
@@ -57,6 +57,7 @@ protected:
const string name;
n8 control; //d0-d6 = PC0-PC6 (0 = input; 1 = output); d7 = TH-INT enable
n8 dataLatch;
n8 dataLines;
n8 serialControl;
n8 serialTxBuffer;
n8 serialRxBuffer;
+8 -2
View File
@@ -13,13 +13,19 @@ auto M32X::SH7604::Debugger::load(Node::Object parent) -> void {
tracer.instruction = parent->append<Node::Debugger::Tracer::Instruction>("Instruction", parent->name());
tracer.instruction->setAddressBits(32, 1);
tracer.instruction->setDepth(16);
if constexpr(SH2::Accuracy::Recompiler) {
tracer.instruction->setToggle([&] {
self->recompiler.reset();
self->recompiler.callInstructionPrologue = tracer.instruction->enabled();
});
}
tracer.interrupt = parent->append<Node::Debugger::Tracer::Notification>("Interrupt", parent->name());
}
auto M32X::SH7604::Debugger::instruction() -> void {
auto M32X::SH7604::Debugger::instruction(u16 opcode) -> void {
if(tracer.instruction->enabled() && tracer.instruction->address(self->regs.PC - 4)) {
tracer.instruction->notify(self->disassembleInstruction(), self->disassembleContext());
tracer.instruction->notify(self->disassembleInstruction(opcode), self->disassembleContext());
}
}
+2 -1
View File
@@ -23,7 +23,7 @@ struct M32X {
//debugger.cpp
auto load(Node::Object) -> void;
auto instruction() -> void;
auto instruction(u16 opcode) -> void;
auto interrupt(string_view) -> void;
struct Tracer {
@@ -37,6 +37,7 @@ struct M32X {
auto unload() -> void;
auto main() -> void;
auto instructionPrologue(u16 instruction) -> void override;
auto step(u32 clocks) -> void override;
auto power(bool reset) -> void;
auto restart() -> void;
+4 -1
View File
@@ -38,7 +38,6 @@ auto M32X::SH7604::main() -> void {
#undef raise
}
debugger.instruction();
SH2::instruction();
SH2::intc.run();
SH2::dmac.run();
@@ -46,6 +45,10 @@ auto M32X::SH7604::main() -> void {
if(m32x.shs.active()) m32x.shs.dmac.dreq[1] = 0;
}
auto M32X::SH7604::instructionPrologue(u16 instruction) -> void {
debugger.instruction(instruction);
}
auto M32X::SH7604::step(u32 clocks) -> void {
if(clocks > 0) {
auto cycles = clocks;
+29
View File
@@ -60,6 +60,7 @@ auto MCD::CDD::advance() -> void {
io.track = track();
io.sector++;
io.sample = 0;
readSubcode();
return;
}
@@ -93,6 +94,34 @@ auto MCD::CDD::position(s32 sector) -> double {
return sqrt(sector / sectors * (outerRadius - innerRadius) + innerRadius) / radius;
}
auto MCD::CDD::readSubcode() -> void {
if(!mcd.fd) return;
mcd.fd->seek(((abs(mcd.cdd.session.leadIn.lba) + io.sector) * 2448) + 2352);
vector<u8> subchannel;
subchannel.resize(96);
mcd.fd->read({subchannel.data(), 96});
io.subcodePosition += 49;
n6 index = io.subcodePosition;
//subchannel data is stored by ares as 12x8 for each channel (PQRSTUVW)
//we need to convert this back to the raw subchannel encoding as per EMCA-130
for(int i = 0; i < 96; i+= 2) {
n16 data = 0;
for(auto channel : range(8)) {
n8 input = subchannel[(channel * 12) + (i / 8)];
n2 bits = input >> (6 - (i & 6));
data.bit( 7 - channel) = bits.bit(0);
data.bit(15 - channel) = bits.bit(1);
}
subcode[index++] = data;
}
mcd.irq.subcode.raise();
}
auto MCD::CDD::process() -> void {
//if(command[0]) print("CDD ", command[0], ":", command[3], "\n");
+9
View File
@@ -26,6 +26,15 @@ auto MCD::Debugger::load(Node::Object parent) -> void {
mcd.bram[address] = data;
});
memory.pcmRam = parent->append<Node::Debugger::Memory>("CD PCM RAM");
memory.pcmRam->setSize(64_KiB);
memory.pcmRam->setRead([&](u32 address) -> u8 {
return mcd.pcm.ram[address];
});
memory.bram->setWrite([&](u32 address, u8 data) -> void {
mcd.pcm.ram[address] = data;
});
tracer.instruction = parent->append<Node::Debugger::Tracer::Instruction>("Instruction", "MCD");
tracer.instruction->setAddressBits(24);
tracer.instruction->setDepth(16);
+8 -1
View File
@@ -176,8 +176,15 @@ auto MCD::readIO(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
data.bit(1,15) = gpu.vector.base.bit(3,17);
}
if(address == 0xff8068) {
data.bit(0) = Unmapped;
data.bit(1,7) = cdd.io.subcodePosition.bit(0,6);
data.bit(8,15) = Unmapped;
}
if(address >= 0xff8100 && address <= 0xff81ff) {
debug(unusual, "[MCD::readIO] address=0x", hex(address, 6L));
n6 index = address - 0xff8100 >> 1;
data = cdd.subcode[index];
}
return data;
+4
View File
@@ -21,6 +21,7 @@ struct MCD : M68000, Thread {
Node::Debugger::Memory pram;
Node::Debugger::Memory wram;
Node::Debugger::Memory bram;
Node::Debugger::Memory pcmRam;
} memory;
struct Tracer {
@@ -306,6 +307,7 @@ struct MCD : M68000, Thread {
auto sample() -> void;
auto position(s32 sector) -> double;
auto process() -> void;
auto readSubcode() -> void;
auto valid() -> bool;
auto checksum() -> void;
auto insert() -> void;
@@ -344,12 +346,14 @@ struct MCD : M68000, Thread {
n16 sample; //current audio sample# within current frame
n7 track; //current track#
n1 tocRead;
n6 subcodePosition;
} io;
n1 hostClockEnable;
n1 statusPending;
n4 status [10];
n4 command[10];
n16 subcode[64];
} cdd;
struct Timer {
+2
View File
@@ -148,11 +148,13 @@ auto MCD::CDD::serialize(serializer& s) -> void {
s(io.sample);
s(io.track);
s(io.tocRead);
s(io.subcodePosition);
s(hostClockEnable);
s(statusPending);
s(status);
s(command);
s(subcode);
}
auto MCD::Timer::serialize(serializer& s) -> void {
+1 -1
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v133.1";
static const string SerializerVersion = "v135";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
+3 -11
View File
@@ -1,19 +1,11 @@
auto VDP::pixels() -> u32* {
u32* output = nullptr;
if(overscan->value() == 0 && io.overscan == 0) {
if(state.vcounter >= 224) return nullptr;
output = screen->pixels().data() + (state.vcounter - 0) * 2 * 1280;
}
if(overscan->value() == 0 && io.overscan == 1) {
if(state.vcounter <= 7) return nullptr;
if(state.vcounter >= 232) return nullptr;
output = screen->pixels().data() + (state.vcounter - 8) * 2 * 1280;
}
if(overscan->value() == 1 && io.overscan == 0) {
if(io.overscan == 0) {
if(state.vcounter >= 232) return nullptr;
output = screen->pixels().data() + (state.vcounter + 8) * 2 * 1280;
}
if(overscan->value() == 1 && io.overscan == 1) {
if(io.overscan == 1) {
output = screen->pixels().data() + (state.vcounter + 0) * 2 * 1280;
}
if(latch.interlace) output += state.field * 1280;
-7
View File
@@ -26,12 +26,6 @@ auto VDP::load(Node::Object parent) -> void {
screen->setScale(0.25, 0.5);
screen->setAspect(32, 35);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(1280, 448);
if(value == 1) screen->setSize(1280, 480);
});
overscan->setDynamic(true);
psg.load(node);
debugger.load(node);
}
@@ -39,7 +33,6 @@ auto VDP::load(Node::Object parent) -> void {
auto VDP::unload() -> void {
debugger = {};
psg.unload();
overscan.reset();
screen->quit();
node->remove(screen);
screen.reset();
-1
View File
@@ -2,7 +2,6 @@
struct VDP : Thread {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
struct Debugger {
//debugger.cpp
+51 -35
View File
@@ -34,17 +34,11 @@ VDP vdp;
auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 1280, 480);
screen = node->append<Node::Video::Screen>("Screen", 1388, visibleHeight() * 2);
screen->colors(1 << 16, {&VDP::color, this});
screen->setSize(1280, 480);
screen->setSize(1388, visibleHeight() * 2);
screen->setScale(0.25, 0.5);
screen->setAspect(32, 35);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(1280, 448);
if(value == 1) screen->setSize(1280, 480);
});
overscan->setDynamic(true);
Region::PAL() ? screen->setAspect(111.0, 100.0) : screen->setAspect(32.0, 35.0);
psg.load(node);
debugger.load(node);
@@ -55,7 +49,6 @@ auto VDP::load(Node::Object parent) -> void {
auto VDP::unload() -> void {
debugger.unload();
psg.unload();
overscan.reset();
screen->quit();
node->remove(screen);
screen.reset();
@@ -63,37 +56,60 @@ auto VDP::unload() -> void {
}
auto VDP::pixels() -> u32* {
//TODO: vcounter values of top border may not be correct here
u32* output = nullptr;
if(overscan->value() == 0 && latch.overscan == 0) {
if(vcounter() >= 224) return nullptr;
output = screen->pixels().data() + (vcounter() - 0) * 2 * 1280;
}
if(overscan->value() == 0 && latch.overscan == 1) {
if(vcounter() <= 7) return nullptr;
if(vcounter() >= 232) return nullptr;
output = screen->pixels().data() + (vcounter() - 8) * 2 * 1280;
}
if(overscan->value() == 1 && latch.overscan == 0) {
if(vcounter() >= 0x1f8) { // top border
output = screen->pixels().data() + (vcounter() - 0x1f8) * 2 * 1280;
} else if(vcounter() >= 232) {
return nullptr;
} else {
output = screen->pixels().data() + (vcounter() + 8) * 2 * 1280;
}
}
if(overscan->value() == 1 && latch.overscan == 1) {
if(vcounter() >= 240) return nullptr;
output = screen->pixels().data() + (vcounter() + 0) * 2 * 1280;
}
if(latch.interlace) output += field() * 1280;
return output;
if(Region::NTSC() && vcounter() >= 0x1ed) return nullptr;
if(Region::PAL() && vcounter() >= 0x1f0) return nullptr;
//account for vcounter jumps during blanking periods
n9 y = vcounter();
if(Region::NTSC() && v28() && vcounter() >= 0x1e5) y -= 250;
if(Region::PAL() && v28() && vcounter() >= 0x1ca) y -= 201;
if(v30() && Region::PAL() && vcounter() >= 0x1d2) y -= 201;
auto offset = Region::PAL() ? 38 : 11;
if(latch.overscan) offset -= 8;
y = (y + offset) % visibleHeight();
output = screen->pixels().data() + y * 2 * 1388;
if(latch.interlace) output += field() * 1388;
//TODO: this should probably be handled in DAC
n32 bg = 1 << 11 | 1 << 9 | cram.color(io.backgroundColor);
for(auto n: range(1388)) output[n] = bg;
return output + 52;
}
auto VDP::frame() -> void {
if(latch.interlace == 0) screen->setProgressive(1);
if(latch.interlace == 1) screen->setInterlace(field());
screen->setViewport(0, 0, screen->width(), screen->height());
if(screen->overscan()) {
screen->setSize(1388, visibleHeight() * 2);
screen->setViewport(0, 0, screen->width(), screen->height());
} else {
int x = 14 * 4;
int y = 12 * 2;
int width = 1388 - (28 * 4);
int height = (visibleHeight() * 2) - (24 * 2);
if(Region::PAL()) {
y += 28 * 2;
height -= 48 * 2;
if(v30()) {
y -= 8 * 2;
height += 16 * 2;
}
}
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+1 -1
View File
@@ -5,7 +5,6 @@
struct VDP : Thread {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
struct Debugger {
VDP& self;
@@ -53,6 +52,7 @@ struct VDP : Thread {
auto screenWidth() const -> u32 { return latch.displayWidth ? 320 : 256; }
auto screenHeight() const -> u32 { return io.overscan ? 240 : 224; }
auto frameHeight() const -> u32 { return Region::PAL() ? 312 : 262; }
auto visibleHeight() const -> u32 { return Region::PAL() ? 294 : 243; }
//vdp.cpp
auto load(Node::Object) -> void;
+1
View File
@@ -3,6 +3,7 @@ auto CPU::mdr() const -> n8 {
}
auto CPU::read(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
n8 data = mdr();
if(address >= 0xc000 && bus.ramEnable) data = ram.read(address);
if(Device::MasterSystem() && bus.biosEnable) data = bios.read(address, data);
+7 -4
View File
@@ -1,6 +1,9 @@
auto VDP::DAC::setup(n8 y) -> void {
self.screen->setFillColor(palette(16 | io.backdropColor));
output = self.screen->pixels().data() + ((240-self.vlines())/2 + y) * 256;
auto VDP::DAC::setup(n9 y) -> void {
auto offset = Region::PAL() ? 48 : 27;
y = (y + offset) % vdp.screenHeight();
output = self.screen->pixels().data() + y * 284;
for (u32 x : range(284)) output[x] = palette(16 | io.backdropColor);
}
auto VDP::DAC::run(n8 x, n8 y) -> void {
@@ -15,7 +18,7 @@ auto VDP::DAC::run(n8 x, n8 y) -> void {
}
}
output[x] = color;
output[(x + 13) % 284] = color;
}
auto VDP::DAC::palette(n5 index) -> n12 {
+25 -18
View File
@@ -18,7 +18,7 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 256, 240);
screen = node->append<Node::Video::Screen>("Screen", 284, screenHeight());
u32 defaultRevision = 2;
if((Model::MarkIII() || Model::MasterSystemI()) && Region::NTSCJ()) defaultRevision = 1;
@@ -27,15 +27,9 @@ auto VDP::load(Node::Object parent) -> void {
if(Display::CRT()) {
screen->colors(1 << 6, {&VDP::colorMasterSystem, this});
screen->setSize(256, 240);
screen->setSize(284, screenHeight());
screen->setScale(1.0, 1.0);
screen->setAspect(8.0, 7.0);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(256, vlines());
if(value == 1) screen->setSize(256, 240);
});
overscan->setDynamic(true);
Region::PAL() ? screen->setAspect(19.0, 14.0) : screen->setAspect(8.0, 7.0);
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
@@ -48,7 +42,7 @@ auto VDP::load(Node::Object parent) -> void {
screen->setSize(160, 144);
screen->setScale(1.0, 1.0);
screen->setAspect(6.0, 5.0);
screen->setViewport(0, 0, 256, 240);
screen->setViewport(0, 0, 284, screenHeight());
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
screen->setInterframeBlending(value);
@@ -65,7 +59,6 @@ auto VDP::unload() -> void {
revision.reset();
screen->quit();
node->remove(screen);
overscan.reset();
screen.reset();
node.reset();
vram.reset();
@@ -89,11 +82,11 @@ auto VDP::main() -> void {
}
//684 clocks/scanline
dac.setup(io.vcounter);
if(io.vcounter < vlines()) {
u8 y = io.vcounter;
background.setup(y);
sprite.setup(y);
dac.setup(y);
for(u8 x : range(256)) {
background.run(x, y);
sprite.run(x, y);
@@ -108,13 +101,28 @@ auto VDP::main() -> void {
if(io.vcounter == 240) {
if(Display::CRT()) {
if(overscan->value() == 0) screen->setViewport(0, (240-vlines())/2, 256, vlines());
if(overscan->value() == 1) screen->setViewport(0, 0, 256, 240);
if(screen->overscan()) {
screen->setSize(284, screenHeight());
screen->setViewport(0, 0, 284, screenHeight());
} else {
int x = 16;
int y = 24;
int width = 284 - 32;
int height = screenHeight() - 48;
if(Region::PAL()) {
y += 24;
height -= 48;
}
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
} else if (Mode::MasterSystem()) {
screen->setViewport(0, (240-vlines())/2, 256, vlines());
screen->setViewport(21, 27, 248, 200);
}
if(Mode::GameGear()) {
screen->setViewport(48, 48, 160, 144);
screen->setViewport(61, 51, 160, 144);
}
screen->frame();
scheduler.exit(Event::Frame);
@@ -142,8 +150,7 @@ auto VDP::step(u32 clocks) -> void {
auto VDP::updateScreenSize() -> void {
if(Display::CRT()) {
if(overscan->value() == 0) screen->setSize(256, vlines());
if(overscan->value() == 1) screen->setSize(256, 240);
screen->setSize(284, screenHeight());
}
//if(Display::LCD()) {
// screen->setSize(160, 144);
+2 -2
View File
@@ -5,7 +5,6 @@
struct VDP : Thread {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
Node::Setting::Boolean colorEmulation;
Node::Setting::Natural revision;
Node::Setting::Boolean interframeBlending; //Game Gear only
@@ -40,6 +39,7 @@ struct VDP : Thread {
auto step(u32 clocks) -> void;
auto updateScreenSize() -> void;
auto screenHeight() const -> u32 { return Region::PAL() ? 288 : 243; }
auto vlines() -> u32;
auto vblank() -> bool;
@@ -137,7 +137,7 @@ struct VDP : Thread {
VDP& self;
//dac.cpp
auto setup(n8 voffset) -> void;
auto setup(n9 voffset) -> void;
auto run(n8 hoffset, n8 voffset) -> void;
auto palette(n5 index) -> n12;
auto power() -> void;
+1
View File
@@ -1,4 +1,5 @@
auto CPU::read(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
n2 page = address.bit(14,15);
n2 primary = slot[page].primary;
+22 -7
View File
@@ -10,9 +10,9 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
if(Model::MSX()) {
screen = node->append<Node::Video::Screen>("Screen", 256, 192);
screen = node->append<Node::Video::Screen>("Screen", 284, 243);
screen->colors(1 << 4, {&VDP::colorMSX, this});
screen->setSize(256, 192);
screen->setSize(284, 243);
screen->setScale(1.0, 1.0);
screen->setAspect(8.0, 7.0);
TMS9918::vram.allocate(16_KiB, 0x00);
@@ -20,9 +20,9 @@ auto VDP::load(Node::Object parent) -> void {
}
if(Model::MSX2()) {
screen = node->append<Node::Video::Screen>("Screen", 512, 424);
screen = node->append<Node::Video::Screen>("Screen", 568, 486);
screen->colors(1 << 9, {&VDP::colorMSX2, this});
screen->setSize(512, 424);
screen->setSize(568, 486);
screen->setScale(0.5, 0.5);
screen->setAspect(8.0, 7.0);
V9938::vram.allocate(128_KiB, 0x00);
@@ -51,16 +51,33 @@ auto VDP::irq(bool line) -> void {
}
auto VDP::frame() -> void {
int screenScale = Model::MSX2() ? 2 : 1;
if(Model::MSX2()) {
if(V9938::interlace() == 0) screen->setProgressive(1);
if(V9938::interlace() == 1) screen->setInterlace(V9938::field());
}
screen->setViewport(0, 0, screen->width(), screen->height());
if(screen->overscan()) {
screen->setSize(284 * screenScale, 243 * screenScale);
screen->setViewport(0, 0, 284 * screenScale, 243 * screenScale);
} else {
int x = 16;
int y = 16;
int width = 284 - 32;
int height = 243 - 32;
screen->setSize(width * screenScale, height * screenScale);
screen->setViewport(x * screenScale, y * screenScale, width * screenScale, height * screenScale);
}
screen->frame();
scheduler.exit(Event::Frame);
}
auto VDP::power() -> void {
screen->power();
if(Model::MSX()) {
TMS9918::power();
Thread::create(system.colorburst() * 2, [&] { TMS9918::main(); });
@@ -70,8 +87,6 @@ auto VDP::power() -> void {
V9938::power();
Thread::create(system.colorburst() * 2, [&] { V9938::main(); });
}
screen->power();
}
/* Z80 I/O ports 0x98 - 0x9b */
+1
View File
@@ -1,4 +1,5 @@
auto CPU::read(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
// VIDEO
if (address == 0xe000) return vdp.data();
+17 -4
View File
@@ -11,12 +11,12 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 256, 192);
screen = node->append<Node::Video::Screen>("Screen", 284, 243);
screen->colors(1 << 4, {&VDP::color, this});
screen->setSize(256, 192);
screen->setViewport(0, 0, 256, 192);
screen->setSize(284, 243);
screen->setViewport(0, 0, 284, 243);
screen->setScale(1.0, 1.0);
screen->setAspect(1.0, 1.0);
screen->setAspect(8.0, 7.0);
TMS9918::load(screen);
}
@@ -39,6 +39,19 @@ auto VDP::irq(bool line) -> void {
}
auto VDP::frame() -> void {
if(screen->overscan()) {
screen->setSize(284, 243);
screen->setViewport(0, 0, 284, 243);
} else {
int x = 16;
int y = 16;
int width = 284 - 32;
int height = 243 - 32;
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+4 -3
View File
@@ -38,7 +38,7 @@ auto AI::sample(f64& left, f64& right) -> void {
if(io.dmaLength[0] && io.dmaEnable) {
io.dmaAddress[0].bit(13,23) += io.dmaAddressCarry;
auto data = rdram.ram.read<Word>(io.dmaAddress[0]);
auto data = rdram.ram.read<Word>(io.dmaAddress[0], "AI");
auto l = s16(data >> 16);
auto r = s16(data >> 0);
left = l / 32768.0;
@@ -50,8 +50,9 @@ auto AI::sample(f64& left, f64& right) -> void {
}
if(!io.dmaLength[0]) {
if(--io.dmaCount) {
io.dmaAddress[0] = io.dmaAddress[1];
io.dmaLength [0] = io.dmaLength [1];
io.dmaAddress[0] = io.dmaAddress[1];
io.dmaLength [0] = io.dmaLength [1];
io.dmaOriginPc[0] = io.dmaOriginPc[1];
mi.raise(MI::IRQ::AI);
}
}
+1
View File
@@ -38,6 +38,7 @@ struct AI : Thread, Memory::RCP<AI> {
n1 dmaAddressCarry;
n18 dmaLength[2];
n2 dmaCount;
u64 dmaOriginPc[2];
n14 dacRate;
n4 bitRate;
} io;
+3 -2
View File
@@ -1,5 +1,5 @@
auto AI::readWord(u32 address, Thread& thread) -> u32 {
address = (address & 0xfffff) >> 2;
address = (address & 0x1f) >> 2;
n32 data;
if(address != 3) {
@@ -22,7 +22,7 @@ auto AI::readWord(u32 address, Thread& thread) -> u32 {
}
auto AI::writeWord(u32 address, u32 data_, Thread& thread) -> void {
address = (address & 0xfffff) >> 2;
address = (address & 0x1f) >> 2;
n32 data = data_;
if(address == 0) {
@@ -38,6 +38,7 @@ auto AI::writeWord(u32 address, u32 data_, Thread& thread) -> void {
if(io.dmaCount < 2) {
if(io.dmaCount == 0) mi.raise(MI::IRQ::AI);
io.dmaLength[io.dmaCount] = length;
io.dmaOriginPc[io.dmaCount] = cpu.ipu.pc;
io.dmaCount++;
}
}
+6 -4
View File
@@ -47,10 +47,12 @@ auto Cartridge::connect() -> void {
rtc.load();
isviewer.ram.allocate(64_KiB);
isviewer.tracer = node->append<Node::Debugger::Tracer::Notification>("ISViewer", "Cartridge");
isviewer.tracer->setAutoLineBreak(false);
isviewer.tracer->setTerminal(true);
if(rom.size <= 0x03fe'ffff) {
isviewer.ram.allocate(64_KiB);
isviewer.tracer = node->append<Node::Debugger::Tracer::Notification>("ISViewer", "Cartridge");
isviewer.tracer->setAutoLineBreak(false);
isviewer.tracer->setTerminal(true);
}
debugger.load(node);
+2
View File
@@ -42,6 +42,8 @@ struct Cartridge {
Memory::Writable ram; //unserialized
Node::Debugger::Tracer::Notification tracer;
auto enabled() -> bool { return ram.size; }
//isviewer.cpp
auto messageChar(char c) -> void;
auto readHalf(u32 address) -> u16;
+2 -1
View File
@@ -85,7 +85,8 @@ auto Cartridge::Flash::writeWord(u32 address, u64 data) -> void {
}
if(mode == Mode::Write) {
for(u32 index = 0; index < 128; index += 2) {
u16 half = rdram.ram.read<Half>(source + index);
// FIXME: this is obviously wrong, the flash can't access RDRAM
u16 half = rdram.ram.read<Half>(source + index, "Flash");
Memory::Writable::write<Half>(offset + index, half);
}
}
+11 -3
View File
@@ -91,6 +91,10 @@ auto CPU::instruction() -> void {
step(1 * 2);
return exception.nmi();
}
if (scc.sysadFrozen) {
step(1 * 2);
return;
}
if constexpr(Accuracy::CPU::Recompiler) {
// Fast path: attempt to lookup previously compiled blocks with devirtualizeFast
@@ -111,16 +115,20 @@ auto CPU::instruction() -> void {
}
if constexpr(Accuracy::CPU::Interpreter) {
pipeline.address = ipu.pc;
auto data = fetch(ipu.pc);
if (!data) return;
pipeline.instruction = *data;
debugger.instruction();
instructionPrologue(*data);
decoderEXECUTE();
instructionEpilogue();
}
}
auto CPU::instructionPrologue(u32 instruction) -> void {
pipeline.address = ipu.pc;
pipeline.instruction = instruction;
debugger.instruction();
}
auto CPU::instructionEpilogue() -> s32 {
if constexpr(Accuracy::CPU::Recompiler) {
//simulates timings without performing actual icache loads
+18 -22
View File
@@ -36,6 +36,7 @@ struct CPU : Thread {
auto synchronize() -> void;
auto instruction() -> void;
auto instructionPrologue(u32 instruction) -> void;
auto instructionEpilogue() -> s32;
auto power(bool reset) -> void;
@@ -147,26 +148,12 @@ struct CPU : Thread {
cpu.step(48 * 2);
valid = 1;
tag = address & ~0x0000'0fff;
words[0] = cpu.busRead<Word>(tag | index | 0x00);
words[1] = cpu.busRead<Word>(tag | index | 0x04);
words[2] = cpu.busRead<Word>(tag | index | 0x08);
words[3] = cpu.busRead<Word>(tag | index | 0x0c);
words[4] = cpu.busRead<Word>(tag | index | 0x10);
words[5] = cpu.busRead<Word>(tag | index | 0x14);
words[6] = cpu.busRead<Word>(tag | index | 0x18);
words[7] = cpu.busRead<Word>(tag | index | 0x1c);
cpu.busReadBurst<ICache>(tag | index, words);
}
auto writeBack(CPU& cpu) -> void {
cpu.step(48 * 2);
cpu.busWrite<Word>(tag | index | 0x00, words[0]);
cpu.busWrite<Word>(tag | index | 0x04, words[1]);
cpu.busWrite<Word>(tag | index | 0x08, words[2]);
cpu.busWrite<Word>(tag | index | 0x0c, words[3]);
cpu.busWrite<Word>(tag | index | 0x10, words[4]);
cpu.busWrite<Word>(tag | index | 0x14, words[5]);
cpu.busWrite<Word>(tag | index | 0x18, words[6]);
cpu.busWrite<Word>(tag | index | 0x1c, words[7]);
cpu.busWriteBurst<ICache>(tag | index, words);
}
auto read(u32 address) const -> u32 { return words[address >> 2 & 7]; }
@@ -191,16 +178,17 @@ struct CPU : Thread {
//8KB
struct Line {
auto hit(u32 address) const -> bool;
template<u32 Size> auto fill(u32 address, u64 data) -> void;
auto fill(u32 address) -> void;
auto writeBack() -> void;
template<u32 Size> auto read(u32 address) const -> u64;
template<u32 Size> auto write(u32 address, u64 data) -> void;
bool valid;
bool dirty;
u16 dirty;
u32 tag;
u16 index;
u64 fillPc;
u64 dirtyPc;
union {
u8 bytes[16];
u16 halfs[8];
@@ -300,6 +288,8 @@ struct CPU : Thread {
auto fetch(u64 vaddr) -> maybe<u32>;
template<u32 Size> auto busWrite(u32 address, u64 data) -> void;
template<u32 Size> auto busRead(u32 address) -> u64;
template<u32 Size> auto busWriteBurst(u32 address, u32 *data) -> void;
template<u32 Size> auto busReadBurst(u32 address, u32 *data) -> void;
template<u32 Size> auto read(u64 vaddr) -> maybe<u64>;
template<u32 Size> auto write(u64 vaddr, u64 data, bool alignedError=true) -> bool;
template<u32 Size> auto vaddrAlignedError(u64 vaddr, bool write) -> bool;
@@ -637,6 +627,7 @@ struct CPU : Thread {
//other
n64 latch;
n1 nmiPending;
n1 sysadFrozen;
} scc;
//interpreter-scc.cpp
@@ -661,7 +652,7 @@ struct CPU : Thread {
struct Coprocessor {
static constexpr u8 revision = 0x00;
static constexpr u8 implementation = 0x0b;
static constexpr u8 implementation = 0x0a;
} coprocessor;
struct ControlStatus {
@@ -696,7 +687,9 @@ struct CPU : Thread {
//interpreter-fpu.cpp
float_env fenv;
template<typename T> auto fgr(u32) -> T&;
template<typename T> auto fgr_t(u32) -> T&;
template<typename T> auto fgr_s(u32) -> T&;
template<typename T> auto fgr_d(u32) -> T&;
auto getControlRegisterFPU(n5) -> u32;
auto setControlRegisterFPU(n5, n32) -> void;
template<bool CVT> auto checkFPUExceptions() -> bool;
@@ -707,8 +700,10 @@ struct CPU : Thread {
auto fpeInvalidOperation() -> bool;
auto fpeUnimplemented() -> bool;
auto fpuCheckStart() -> bool;
auto fpuCheckInput(f32& f) -> bool;
auto fpuCheckInput(f64& f) -> bool;
template <typename T>
auto fpuCheckInput(T& f) -> bool;
template <typename T>
auto fpuCheckInputs(T& f1, T& f2) -> bool;
auto fpuCheckOutput(f32& f) -> bool;
auto fpuCheckOutput(f64& f) -> bool;
auto fpuClearCause() -> void;
@@ -911,6 +906,7 @@ struct CPU : Thread {
auto emitFPU(u32 instruction) -> bool;
auto emitCOP2(u32 instruction) -> bool;
bool callInstructionPrologue = false;
bump_allocator allocator;
Pool* pools[1 << 21]; //2_MiB * sizeof(void*) == 16_MiB
} recompiler{*this};
+10 -53
View File
@@ -2,63 +2,19 @@ auto CPU::DataCache::Line::hit(u32 address) const -> bool {
return valid && tag == (address & ~0x0000'0fff);
}
template<u32 Size> auto CPU::DataCache::Line::fill(u32 address, u64 data) -> void {
cpu.step(40 * 2);
valid = 1;
dirty = 1;
tag = address & ~0x0000'0fff;
//read words according to critical doubleword first scheme
switch(address & 8) {
case 0:
if constexpr(Size != Dual) {
words[0] = cpu.busRead<Word>(tag | index | 0x0);
words[1] = cpu.busRead<Word>(tag | index | 0x4);
}
write<Size>(address, data);
words[2] = cpu.busRead<Word>(tag | index | 0x8);
words[3] = cpu.busRead<Word>(tag | index | 0xc);
break;
case 8:
if constexpr(Size != Dual) {
words[2] = cpu.busRead<Word>(tag | index | 0x8);
words[3] = cpu.busRead<Word>(tag | index | 0xc);
}
write<Size>(address, data);
words[0] = cpu.busRead<Word>(tag | index | 0x0);
words[1] = cpu.busRead<Word>(tag | index | 0x4);
break;
}
}
auto CPU::DataCache::Line::fill(u32 address) -> void {
cpu.step(40 * 2);
valid = 1;
dirty = 0;
tag = address & ~0x0000'0fff;
//read words according to critical doubleword first scheme
switch(address & 8) {
case 0:
words[0] = cpu.busRead<Word>(tag | index | 0x0);
words[1] = cpu.busRead<Word>(tag | index | 0x4);
words[2] = cpu.busRead<Word>(tag | index | 0x8);
words[3] = cpu.busRead<Word>(tag | index | 0xc);
break;
case 8:
words[2] = cpu.busRead<Word>(tag | index | 0x8);
words[3] = cpu.busRead<Word>(tag | index | 0xc);
words[0] = cpu.busRead<Word>(tag | index | 0x0);
words[1] = cpu.busRead<Word>(tag | index | 0x4);
break;
}
valid = 1;
dirty = 0;
tag = address & ~0x0000'0fff;
fillPc = cpu.ipu.pc;
cpu.busReadBurst<DCache>(tag | index, words);
}
auto CPU::DataCache::Line::writeBack() -> void {
cpu.step(40 * 2);
dirty = 0;
cpu.busWrite<Word>(tag | index | 0x0, words[0]);
cpu.busWrite<Word>(tag | index | 0x4, words[1]);
cpu.busWrite<Word>(tag | index | 0x8, words[2]);
cpu.busWrite<Word>(tag | index | 0xc, words[3]);
cpu.busWriteBurst<DCache>(tag | index, words);
}
auto CPU::DataCache::line(u32 vaddr) -> Line& {
@@ -86,7 +42,8 @@ auto CPU::DataCache::Line::write(u32 address, u64 data) -> void {
words[address >> 2 & 2 | 0] = data >> 32;
words[address >> 2 & 2 | 1] = data >> 0;
}
dirty = 1;
dirty |= ((1 << Size) - 1) << (address & 0xF);
dirtyPc = cpu.ipu.pc;
}
template<u32 Size>
@@ -105,7 +62,7 @@ auto CPU::DataCache::readDebug(u32 vaddr, u32 address) -> u8 {
auto& line = this->line(vaddr);
if(!line.hit(address)) {
Thread dummyThread{};
return bus.read<Byte>(address, dummyThread);
return bus.read<Byte>(address, dummyThread, "Ares Debugger");
}
return line.read<Byte>(address);
}
@@ -115,7 +72,7 @@ auto CPU::DataCache::write(u32 vaddr, u32 address, u64 data) -> void {
auto& line = this->line(vaddr);
if(!line.hit(address)) {
if(line.valid && line.dirty) line.writeBack();
return line.fill<Size>(address, data);
line.fill(address);
} else {
cpu.step(1 * 2);
}
+6
View File
@@ -2,6 +2,12 @@ auto CPU::Debugger::load(Node::Object parent) -> void {
tracer.instruction = parent->append<Node::Debugger::Tracer::Instruction>("Instruction", "CPU");
tracer.instruction->setAddressBits(64, 2);
tracer.instruction->setDepth(64);
if constexpr(Accuracy::CPU::Recompiler) {
tracer.instruction->setToggle([&] {
cpu.recompiler.reset();
cpu.recompiler.callInstructionPrologue = tracer.instruction->enabled();
});
}
tracer.exception = parent->append<Node::Debugger::Tracer::Notification>("Exception", "CPU");
tracer.interrupt = parent->append<Node::Debugger::Tracer::Notification>("Interrupt", "CPU");
+84 -46
View File
@@ -7,7 +7,7 @@ auto CPU::FPU::setFloatingPointMode(bool mode) -> void {
}
}
template<> auto CPU::fgr<s32>(u32 index) -> s32& {
template<> auto CPU::fgr_t<s32>(u32 index) -> s32& {
if(scc.status.floatingPointMode) {
return fpu.r[index].s32;
} else if(index & 1) {
@@ -17,21 +17,41 @@ template<> auto CPU::fgr<s32>(u32 index) -> s32& {
}
}
template<> auto CPU::fgr<u32>(u32 index) -> u32& {
return (u32&)fgr<s32>(index);
template<> auto CPU::fgr_s<s32>(u32 index) -> s32& {
if(scc.status.floatingPointMode) {
return fpu.r[index].s32;
} else {
return fpu.r[index & ~1].s32;
}
}
template<> auto CPU::fgr<f32>(u32 index) -> f32& {
template<> auto CPU::fgr_d<s32>(u32 index) -> s32& {
fpu.r[index].s32h = 0;
return fpu.r[index].s32;
}
template<> auto CPU::fgr_t<u32>(u32 index) -> u32& {
return (u32&)fgr_t<s32>(index);
}
template<> auto CPU::fgr_t<f32>(u32 index) -> f32& {
return fpu.r[index].f32;
}
template<> auto CPU::fgr_d<f32>(u32 index) -> f32& {
fpu.r[index].f32h = 0;
return fpu.r[index].f32;
}
template<> auto CPU::fgr_s<f32>(u32 index) -> f32& {
if(scc.status.floatingPointMode) {
return fpu.r[index].f32;
} else if(index & 1) {
return fpu.r[index & ~1].f32h;
} else {
return fpu.r[index & ~1].f32;
}
}
template<> auto CPU::fgr<s64>(u32 index) -> s64& {
template<> auto CPU::fgr_t<s64>(u32 index) -> s64& {
if(scc.status.floatingPointMode) {
return fpu.r[index].s64;
} else {
@@ -39,11 +59,31 @@ template<> auto CPU::fgr<s64>(u32 index) -> s64& {
}
}
template<> auto CPU::fgr<u64>(u32 index) -> u64& {
return (u64&)fgr<s64>(index);
template<> auto CPU::fgr_d<s64>(u32 index) -> s64& {
return fpu.r[index].s64;
}
template<> auto CPU::fgr<f64>(u32 index) -> f64& {
template<> auto CPU::fgr_s<s64>(u32 index) -> s64& {
return fgr_t<s64>(index);
}
template<> auto CPU::fgr_t<u64>(u32 index) -> u64& {
return (u64&)fgr_t<s64>(index);
}
template<> auto CPU::fgr_s<u64>(u32 index) -> u64& {
return fgr_t<u64>(index);
}
template<> auto CPU::fgr_t<f64>(u32 index) -> f64& {
return fpu.r[index].f64;
}
template<> auto CPU::fgr_d<f64>(u32 index) -> f64& {
return fgr_t<f64>(index);
}
template<> auto CPU::fgr_s<f64>(u32 index) -> f64& {
if(scc.status.floatingPointMode) {
return fpu.r[index].f64;
} else {
@@ -210,8 +250,9 @@ auto CPU::checkFPUExceptions() -> bool {
#define CHECK_FPE_IMPL(type, res, operation, convert) \
fenv.clearExcept(); \
type res = [&]() noinline -> type { return operation; }(); \
if (checkFPUExceptions<convert>()) return;
volatile type v##res = [&]() noinline -> type { return operation; }(); \
if (checkFPUExceptions<convert>()) return; \
type res = v##res;
#define CHECK_FPE(type, res, operation) CHECK_FPE_IMPL(type, res, operation, false)
#define CHECK_FPE_CONV(type, res, operation) CHECK_FPE_IMPL(type, res, operation, true)
@@ -240,7 +281,9 @@ auto CPU::fpuCheckStart() -> bool {
return true;
}
auto CPU::fpuCheckInput(f32& f) -> bool {
template <typename T>
auto CPU::fpuCheckInput(T& f) -> bool {
static_assert(std::is_same_v<T, f32> || std::is_same_v<T, f64>);
switch (fpclassify(f)) {
case FP_SUBNORMAL:
if(fpeUnimplemented()) return exception.floatingPoint(), false;
@@ -253,19 +296,23 @@ auto CPU::fpuCheckInput(f32& f) -> bool {
return true;
}
auto CPU::fpuCheckInput(f64& f) -> bool {
switch (fpclassify(f)) {
case FP_SUBNORMAL:
template <typename T>
auto CPU::fpuCheckInputs(T& f1, T& f2) -> bool {
static_assert(std::is_same_v<T, f32> || std::is_same_v<T, f64>);
int cl1 = fpclassify(f1), cl2 = fpclassify(f2);
if((cl1 == FP_NAN && !qnan(f1)) || (cl2 == FP_NAN && !qnan(f2))) {
if(fpeUnimplemented()) return exception.floatingPoint(), false;
return true;
case FP_NAN:
if(qnan(f) ? fpeInvalidOperation() : fpeUnimplemented())
return exception.floatingPoint(), false;
return true;
}
if(cl1 == FP_SUBNORMAL || cl2 == FP_SUBNORMAL) {
if(fpeUnimplemented()) return exception.floatingPoint(), false;
}
if((cl1 == FP_NAN && qnan(f1)) || (cl2 == FP_NAN && qnan(f2))) {
if(fpeInvalidOperation()) return exception.floatingPoint(), false;
}
return true;
}
template<typename T>
auto fpuFlushResult(T f, u32 roundMode) -> T
{
@@ -364,9 +411,9 @@ auto CPU::fpuCheckInputConv<s64>(f64& f) -> bool {
}
#define CF fpu.csr.compare
#define FD(type) fgr<type>(fd)
#define FS(type) fgr<type>(fs)
#define FT(type) fgr<type>(ft)
#define FD(type) fgr_d<type>(fd)
#define FS(type) fgr_s<type>(fs)
#define FT(type) fgr_t<type>(ft)
auto CPU::BC1(bool value, bool likely, s16 imm) -> void {
if(!fpuCheckStart()) return;
@@ -416,8 +463,7 @@ auto CPU::FABS_D(u8 fd, u8 fs) -> void {
auto CPU::FADD_S(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
f32 ffs = FS(f32), fft = FT(f32);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f32, ffd, FS(f32) + FT(f32));
if(!fpuCheckOutput(ffd)) return;
FD(f32) = ffd;
@@ -427,8 +473,7 @@ auto CPU::FADD_S(u8 fd, u8 fs, u8 ft) -> void {
auto CPU::FADD_D(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f64), fft = FT(f64);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f64, ffd, ffs + fft);
if(!fpuCheckOutput(ffd)) return;
FD(f64) = ffd;
@@ -758,8 +803,7 @@ auto CPU::FCVT_W_D(u8 fd, u8 fs) -> void {
auto CPU::FDIV_S(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f32), fft = FT(f32);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f32, ffd, ffs / fft);
if(!fpuCheckOutput(ffd)) return;
FD(f32) = ffd;
@@ -769,8 +813,7 @@ auto CPU::FDIV_S(u8 fd, u8 fs, u8 ft) -> void {
auto CPU::FDIV_D(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f64), fft = FT(f64);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f64, ffd, ffs / fft);
if(!fpuCheckOutput(ffd)) return;
FD(f64) = ffd;
@@ -814,8 +857,7 @@ auto CPU::FFLOOR_W_D(u8 fd, u8 fs) -> void {
}
auto CPU::FMOV_S(u8 fd, u8 fs) -> void {
if(!scc.status.enable.coprocessor1) return exception.coprocessor1();
FD(f32) = FS(f32);
return FMOV_D(fd, fs);
}
auto CPU::FMOV_D(u8 fd, u8 fs) -> void {
@@ -826,8 +868,7 @@ auto CPU::FMOV_D(u8 fd, u8 fs) -> void {
auto CPU::FMUL_S(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f32), fft = FT(f32);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f32, ffd, ffs * fft);
if(!fpuCheckOutput(ffd)) return;
FD(f32) = ffd;
@@ -837,8 +878,7 @@ auto CPU::FMUL_S(u8 fd, u8 fs, u8 ft) -> void {
auto CPU::FMUL_D(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f64), fft = FT(f64);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f64, ffd, ffs * fft);
if(!fpuCheckOutput(ffd)) return;
FD(f64) = ffd;
@@ -926,8 +966,7 @@ auto CPU::FSQRT_D(u8 fd, u8 fs) -> void {
auto CPU::FSUB_S(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f32), fft = FT(f32);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f32, ffd, ffs - fft);
if(!fpuCheckOutput(ffd)) return;
FD(f32) = ffd;
@@ -937,8 +976,7 @@ auto CPU::FSUB_S(u8 fd, u8 fs, u8 ft) -> void {
auto CPU::FSUB_D(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f64), fft = FT(f64);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f64, ffd, ffs - fft);
if(!fpuCheckOutput(ffd)) return;
FD(f64) = ffd;
@@ -995,14 +1033,14 @@ auto CPU::LWC1(u8 ft, cr64& rs, s16 imm) -> void {
if(auto data = read<Word>(rs.u64 + imm)) FT(u32) = *data;
}
auto CPU::MFC1(r64& rt, u8 fs) -> void {
auto CPU::MFC1(r64& rt, u8 ft) -> void {
if(!scc.status.enable.coprocessor1) return exception.coprocessor1();
rt.u64 = FS(s32);
rt.u64 = FT(s32);
}
auto CPU::MTC1(cr64& rt, u8 fs) -> void {
auto CPU::MTC1(cr64& rt, u8 ft) -> void {
if(!scc.status.enable.coprocessor1) return exception.coprocessor1();
FS(s32) = rt.u32;
FT(s32) = rt.u32;
}
auto CPU::SDC1(u8 ft, cr64& rs, s16 imm) -> void {
+15 -5
View File
@@ -146,12 +146,22 @@ auto CPU::devirtualizeDebug(u64 vaddr) -> u64 {
template<u32 Size>
inline auto CPU::busWrite(u32 address, u64 data) -> void {
bus.write<Size>(address, data, *this);
bus.write<Size>(address, data, *this, "CPU");
}
template<u32 Size>
inline auto CPU::busWriteBurst(u32 address, u32 *data) -> void {
bus.writeBurst<Size>(address, data, *this);
}
template<u32 Size>
inline auto CPU::busRead(u32 address) -> u64 {
return bus.read<Size>(address, *this);
return bus.read<Size>(address, *this, "CPU");
}
template<u32 Size>
inline auto CPU::busReadBurst(u32 address, u32 *data) -> void {
return bus.readBurst<Size>(address, data, *this);
}
auto CPU::fetch(u64 vaddr) -> maybe<u32> {
@@ -229,7 +239,7 @@ auto CPU::readDebug(u64 vaddr) -> u8 {
case Context::Segment::Mapped:
if(auto match = tlb.load(vaddr, true)) {
if(match.cache) return dcache.readDebug(vaddr, match.address & context.physMask);
return bus.read<Byte>(match.address & context.physMask, dummyThread);
return bus.read<Byte>(match.address & context.physMask, dummyThread, "Ares Debugger");
}
return 0;
case Context::Segment::Cached:
@@ -237,9 +247,9 @@ auto CPU::readDebug(u64 vaddr) -> u8 {
case Context::Segment::Cached32:
return dcache.readDebug(vaddr, vaddr & 0xffff'ffff);
case Context::Segment::Direct:
return bus.read<Byte>(vaddr & 0x1fff'ffff, dummyThread);
return bus.read<Byte>(vaddr & 0x1fff'ffff, dummyThread, "Ares Debugger");
case Context::Segment::Direct32:
return bus.read<Byte>(vaddr & 0xffff'ffff, dummyThread);
return bus.read<Byte>(vaddr & 0xffff'ffff, dummyThread, "Ares Debugger");
}
unreachable;
+5 -1
View File
@@ -36,7 +36,11 @@ auto CPU::Recompiler::emit(u32 vaddr, u32 address, bool singleInstruction) -> Bl
Thread thread;
bool hasBranched = 0;
while(true) {
u32 instruction = bus.read<Word>(address, thread);
u32 instruction = bus.read<Word>(address, thread, "Ares Recompiler");
if(callInstructionPrologue) {
mov32(reg(1), imm(instruction));
call(&CPU::instructionPrologue);
}
bool branched = emitEXECUTE(instruction);
if(unlikely(instruction == 0x1000'ffff //beq 0,0,<pc>
|| instruction == (2 << 26 | vaddr >> 2 & 0x3ff'ffff))) { //j <pc>
+1
View File
@@ -117,6 +117,7 @@ auto CPU::serialize(serializer& s) -> void {
s(scc.epcError);
s(scc.latch);
s(scc.nmiPending);
s(scc.sysadFrozen);
for(auto& r : fpu.r) s(r.u64);
s(fpu.csr.roundMode);
Loaded 100 of 886 files, more files were not shown because too many files have changed in this diff. Show more