Compare commits

...
57 Commits
Author SHA1 Message Date
Luke Usher 443aabb89b bump version number to v129 2022-07-13 17:53:31 +01:00
Luke Usher 93275dba2c a26: preliminary atari 2600 emulation
This commit introduces preliminary Atari 2600 emulation.

In the current state, many games are working but with the following caveats

1. Only a small subset of mappers are supported
2. The TIA isn't cycle accurate yet; many games have rendering and/or collision issues.

There is currently little-to-no reason to use this over other Atari 2600 emulators at this time,
but the goal is to eventually have cycle-accurate atari emulation.
2022-07-13 16:56:52 +01:00
Giovanni Bajo 3767568cdb n64: fix RSP emulation on ARM
ARM computers fall back to the standard SISD code, where there were some
bugs. Fixed through n64-systemtest (which is now a clean pass on
Apple M1)
2022-07-07 16:46:23 +01:00
Giovanni Bajo 7cfb150be8 n64: improve freeze bit emulation
It looks like the DMA register latches are still working while RDP freeze
is set, so just the DMA itself is frozen.

Fix some tests in n64-systemtest.
2022-06-28 23:55:45 +01:00
Giovanni Bajo 049211abae n64: implement basic behavior of freeze bit
The exact behavior of the freeze bit hasn't been investigate yet, but
at the very least it stops the RDP command engine. This PR does a
basic approximation of it, pending more in-depth tests.

Fixes Banjo-Tooie.
2022-06-22 09:13:03 +01:00
Giovanni Bajo a7ce0c22fa n64: add region check to TLBP
This was missed in the 64-bit TLB series (the region check only makes
sense for 64-bit TLBs).

Fix one test in n64-systemtest
2022-06-19 22:45:51 +01:00
Giovanni Bajo 7205a39f23 n64: fix 64-bit TLB vaddr match
Fix several tests in n64-systemtest.
2022-06-16 16:40:24 +01:00
Giovanni Bajo 99b759b630 n64: improve 64-bit TLB exceptions
It looks like address exceptions and TLB exceptions always set the
same registers (though some are marked as undefined in the manual), so
we unify the two implementations.

Fix several tests in n64-systemtest.
2022-06-16 16:40:24 +01:00
Giovanni Bajo a9ced1175b n64: rename address variables to vaddr when they refer to a VA
There's a big confusion in the CPU core between virtual and physical
addresses, and we have been fixing many bugs around this. Now that the
situation seems stable, do a big sweep and rename all "address" variables
to "vaddr" when they refer to a virtual address, to simplify reading
the code.

"address" is kept for physical addresses, which is consistent to the
rest of the code outside the CPU that only talks about physical
addresses anyway.

No functional changes.
2022-06-16 16:40:24 +01:00
Giovanni Bajo aeb5bc4597 n64: fix 64-bit addressing
This commit do further fixes 64-bit addressing. In 64-bit mode, the bus
addressable range is 2Gb (instead of 512Mb), so we need to mask
appropriately all physical addresses.

Fix several tests in n64-systemtest.
2022-06-16 16:40:24 +01:00
Giovanni Bajo f031407a14 n64: more accurate RDP status bit management
This begins improving the RDP status bits to more accurately reflect
what it can be seen on real hardware. It also undoes a hack for RDP
freeze that was allowing Banjo-Tooie to boot. Being a game-specific
hack, I think it is better to simply disable and wait until we find
out how to properly fix the game.

Fix a few new tests in n64-systemtest.
2022-06-14 13:26:13 +01:00
Giovanni Bajo 3c42e12b75 n64: implement invalid COP0 register access
Fix a few tests in n64-systemtest
2022-06-13 09:22:53 +01:00
TascoDLX 2ce9d210af md: handle controller port disconnect 2022-06-11 14:41:02 +01:00
TascoDLX a038b2cf61 desktop-ui: omit null allocation call when disconnecting port device 2022-06-11 14:41:02 +01:00
TascoDLX cee2ea332f mcd: force workaround for Corpse Killer (CD) gamepad bug 2022-06-11 14:41:02 +01:00
TascoDLX b1e3d90790 mia: extract serial number from MCD header to id disc 2022-06-11 14:41:02 +01:00
TascoDLX f306d1223c md: enable controller port disconnect via deallocation 2022-06-11 14:41:02 +01:00
TascoDLX e90e6ab846 desktop-ui: allow disconnection from ports 2022-06-11 14:41:02 +01:00
Luke Usher 6a103ed073 md: add destructor to fighting pad 2022-06-11 14:39:08 +01:00
Luke Usher 96663687f2 thread: prevent switching to threads that were destroyed while a call to Thread::synchronize() was in progress 2022-06-11 14:32:06 +01:00
remutro 8d17ae1697 validate we found correct number of bios files for NeoGeo 2022-06-07 07:42:37 +01:00
Giovanni Bajo a2e4807810 n64: fix RSP opcode STV
Fix one test in n64-systemtest
2022-06-04 00:23:15 +01:00
Giovanni Bajo 803daf7642 n64: fix RSP opcode SFV
This one is the weirdest. The current implementation is unrolled, but
I can't find a pattern that works. Surely there must be one as the
hardware doesn't surely have a lookup table. Anyway for now this
implementation is completely accurate, albeit verbose.

Fix one test in n64-systemtest
2022-06-04 00:23:15 +01:00
remutro c88fb1155b Fix identifier for WCW Mayhem 2022-06-04 00:05:58 +01:00
Giovanni Bajo 246a5eee93 n64: fix LWL/LWR accesses over RCP ranges
On real hardware, LWL/LWR execute a SysAD 32-bit masked read. Ares
was emulating them with one or multiple shorter accesses, which is
not the correct behavior wrt RCP hardware registers and PI bus area,
where the RCP incorrectly ignores the access size causing several bugs.

After this commit, LWL/LWR always do a 32-bit access on the bus, which
makes sure that we replicate the exact behavior.

SWL/SWR have a similar problem but harder to fix, because the SysAD
bus can do 24-bit writes, which is not something that Ares memory
functions support. So that's for another day.
2022-06-04 00:05:48 +01:00
remutro 4607b40e23 Fix incorrect identifier for South Park Rally (USA) 2022-06-03 21:43:36 +01:00
Giovanni Bajo d0a963ab42 n64: fix RDP scissoring in copy/fill mode
This fixes a bug in mame RDP with respect to scissoring: the upper X
bound is inclusive in copy and fill modes (and exclusives in the others).

This seems another bug in Mame's refactorings that doesn't appear
in upstream angrylion, given the fact that both cen64 and parallel-RDP
emulates this correctly.
2022-06-03 21:43:30 +01:00
Luke Usher bb58366efd desktop-ui: support choosing which system to emulate from command line
examples:

`ares --system "PlayStation" game.cue`
`ares --system "Super Famicom" game.sfc`
2022-06-02 15:54:52 +01:00
CasualPokePlayer 0891a102e4 Update instruction.hpp 2022-06-01 13:36:51 +01:00
CasualPokePlayer 0e74d5c8e3 avoid u128 usage
Also adding in protection against another bad shift case. Can't use the same code here though... could avoid the branching if it can be safely assumed _addressBits is always at least 3
2022-06-01 13:36:51 +01:00
CasualPokePlayer 672940cf06 debugger: fix address masking in instruction tracer (for 64 bit addresses)
This is similar to https://github.com/ares-emulator/ares/commit/822186413c9274295e49b922297f0cc744921eda, but fixes masking for N64 as it has 64 bit addresses.
2022-06-01 13:36:51 +01:00
Luke Usher 9ecfbdbe76 gba: prevent window from blocking translucent sprite blending
fixes Pokemon fog effect.
2022-05-31 21:05:40 +01:00
Luke Usher 679dbaccc9 sh2: implement watchdog in interval timer mode 2022-05-31 11:43:04 +01:00
Giovanni Bajo 57b4d68298 n64: fix PI DMA timing calculation
329a8c9 had a bug: it moved the queue.insert line *after* dmaWrite;
but dmaWrite updates the io.writeLength to its post-transfer status,
so the calculated timing was incorrect.

Fix the regression in Cruisin' Exotica and possibly others.
2022-05-31 09:01:26 +01:00
Luke Usher 5d55f2b8e3 ps1: clear param fifo after command execution 2022-05-30 21:59:27 +01:00
Luke Usher f094529d6d ps1: stub 'motor-on' 2022-05-30 21:59:27 +01:00
Giovanni Bajo c2735c5c1c n64: use a stronger hash in RSP recompiler to avoid collisions
Luckily enough, n64-systemtest triggered an interesting bug in our
RSP recompiler: a hash collision of the IMEM contents. The used hash
is objectively very poor so a collision is well possible.

Switch to CRC32 that should be enough to our scope.

The idea of doing a full IMEM hash is probably a bit weak to begin with,
but that's for another time.

Fix a new test in n64-systemtest (that only failed because of this).
2022-05-27 13:13:03 +01:00
Giovanni Bajo e1f5ab8333 n64: fix marginal cases of SHV and SWV
Fix two new tests in n64-systemtest
2022-05-27 13:13:03 +01:00
Luke Usher cf0ef61c1f ps1: implement SCEX test commands used for anti-mod detection 2022-05-27 08:12:46 +01:00
Luke Usher 2b9907c8d0 ps1: implement ReadToc command 2022-05-27 08:12:46 +01:00
Luke Usher 2abe2286b2 ps1: data reads should stop cd-da reads 2022-05-27 08:12:46 +01:00
Luke Usher b6d8be087f ps1: seek commands are two-stage, not one-stage 2022-05-27 08:12:46 +01:00
Giovanni Bajo 49892159d5 n64: fix serialization
I broke it in 8236464.
2022-05-26 22:17:05 +01:00
Luke Usher e88e2cf032 sfc: lazily sync co-processors
Significant performance improvement for some users, especially on low-power machines such as laptops/tablets.
2022-05-26 22:16:22 +01:00
Giovanni Bajo bcf9cab22d n64: rework RSP DMA double buffering
This change implements RSP DMA double buffering in a more faithful
way, so that reads from the registers always return the current values
(ongoing transfer or finished transfer) rather than the pending ones.

Moreover, this also makes Ares transfer bytes in background a row at
a time, rather than all of them at the end. It is a bit more accurate,
and it was also easier to implement in the new architecture.

This also fixes several tests in n64-systemtest.
2022-05-26 12:57:23 +01:00
Giovanni Bajo 2973fa71e5 n64: reactivate 64-bit memory map
Ares used to have full 64-bit TLB support but it was removed at some
point because of some wrong testing that apparently made us believe
that the CPU didn't support that mode. It will have to be restored from
the git history in the future, though it's not very urgent because
nobody is using 64-bit pointers on N64 (there is no toolchain with
64-bit pointers, nor it seems useful).

Luckily, at least the segment parsing was left in and just commented out.
This allows to pass the last two remaining tests in n64-systemtest.
2022-05-25 09:04:55 +01:00
Giovanni Bajo 16f86afd9f n64: improve RSP DMA timing
RSP DMA runs at 8 bytes per RSP cycle, and each RSP cycle is 3 "ares cycles".
2022-05-24 07:49:28 +01:00
Giovanni Bajo 329a8c958b n64: improve PI DMA timings
PI DMA (at default PI configuration) is roughly 9 COP0 cycles per byte,
which is 18 CPU cycles, which is 36 "ares cycles".
2022-05-24 07:49:28 +01:00
Giovanni Bajo 91f3dfaf3d n64: run PI DMA immediately, and delay only the interrupt
This is less likely to create troubles to games, and matches what it
is being done for RSP DMA.

This makes DK64 boot.
2022-05-24 07:49:28 +01:00
Giovanni Bajo daa8d82f7b n64: implement more null opcodes in RSP
Fix several tests in n64-systemtest
2022-05-24 07:48:57 +01:00
Giovanni Bajo fb98870d56 n64: fix VMACQ and fix+simplify VMOV
Fix two tests in n64-systemtest.
2022-05-24 07:48:57 +01:00
Giovanni Bajo 07183d56f4 n64: fix clamping of RSP vrndn/vrndp opcodes
Fix some tests in n64-systemtest
2022-05-23 16:39:46 +01:00
LuigiBlood 232cb6fcf6 n64: remove readId from controller 2022-05-23 16:39:28 +01:00
LuigiBlood 320779a845 n64: implement per controller joybus pif communication 2022-05-23 16:39:28 +01:00
LuigiBlood 86e962bc13 n64: add n64 mouse emulation 2022-05-23 16:39:28 +01:00
Luke Usher 0cafaad19b sfc: alternate fix for voxel demo; restores SuperFX compatibility 2022-05-21 13:32:36 +01:00
Giovanni Bajo 043b4ffd37 n64: implement AI DMA hardware bug
Fix audio in Twisted Edge
2022-05-19 07:25:09 +01:00
119 changed files with 3317 additions and 710 deletions

No files matched your search

+4
View File
@@ -27,6 +27,10 @@ ifeq ($(profile),performance)
flags += -DPROFILE_PERFORMANCE
endif
ifneq ($(filter $(cores),a26),)
include $(ares.path)/a26/GNUmakefile
endif
ifneq ($(filter $(cores),fc),)
include $(ares.path)/fc/GNUmakefile
endif
+15
View File
@@ -0,0 +1,15 @@
ares.components += mos6502
ares.objects += ares-a26-system
ares.objects += ares-a26-controller
ares.objects += ares-a26-cartridge
ares.objects += ares-a26-cpu
ares.objects += ares-a26-tia
ares.objects += ares-a26-riot
$(object.path)/ares-a26-system.o: $(ares.path)/a26/system/system.cpp
$(object.path)/ares-a26-controller.o: $(ares.path)/a26/controller/controller.cpp
$(object.path)/ares-a26-cartridge.o: $(ares.path)/a26/cartridge/cartridge.cpp
$(object.path)/ares-a26-cpu.o: $(ares.path)/a26/cpu/cpu.cpp
$(object.path)/ares-a26-tia.o: $(ares.path)/a26/tia/tia.cpp
$(object.path)/ares-a26-riot.o: $(ares.path)/a26/riot/riot.cpp
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <ares/ares.hpp>
#include <component/processor/mos6502/mos6502.hpp>
namespace ares::Atari2600 {
#include <ares/inline.hpp>
auto enumerate() -> vector<string>;
auto load(Node::System& node, string name) -> bool;
struct Region {
static inline auto NTSC() -> bool;
static inline auto PAL() -> bool;
static inline auto SECAM() -> bool;
};
#include <a26/controller/controller.hpp>
#include <a26/system/system.hpp>
#include <a26/cartridge/cartridge.hpp>
#include <a26/cpu/cpu.hpp>
#include <a26/tia/tia.hpp>
#include <a26/riot/riot.hpp>
}
+58
View File
@@ -0,0 +1,58 @@
struct Atari16k : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
Memory::Writable<n8> ram;
n2 bank;
bool hasRam;
auto load() -> void override {
Interface::load(rom, "program.rom");
}
auto save() -> void override {
}
auto unload() -> void override {
}
auto read(n16 address) -> n8 override {
if(address == 0x1ff6) bank = 0;
if(address == 0x1ff7) bank = 1;
if(address == 0x1ff8) bank = 2;
if(address == 0x1ff9) bank = 3;
if(address.bit(12)) {
if(hasRam && address >= 0x1080 && address <= 0x10ff) return ram.read(address & 0x7f);
return rom.read((bank * 0x1000) + (address & 0xfff));
}
return 0xff;
}
auto write(n16 address, n8 data) -> bool override {
if(address == 0x1ff6) bank = 0;
if(address == 0x1ff7) bank = 1;
if(address == 0x1ff8) bank = 2;
if(address == 0x1ff9) bank = 3;
if(address >= 0x1000 && address <= 0x107f) {
hasRam = true;
ram.write(address & 0x7f, data);
return true;
}
return false;
}
auto power() -> void override {
bank = 0;
hasRam = 0;
ram.allocate(128);
}
auto serialize(serializer& s) -> void override {
s(bank);
s(hasRam);
s(ram);
}
};
+66
View File
@@ -0,0 +1,66 @@
struct Atari32k : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
Memory::Writable<n8> ram;
n3 bank;
bool hasRam;
auto load() -> void override {
Interface::load(rom, "program.rom");
}
auto save() -> void override {
}
auto unload() -> void override {
}
auto read(n16 address) -> n8 override {
if(address == 0x1ff4) bank = 0;
if(address == 0x1ff5) bank = 1;
if(address == 0x1ff6) bank = 2;
if(address == 0x1ff7) bank = 3;
if(address == 0x1ff8) bank = 4;
if(address == 0x1ff9) bank = 5;
if(address == 0x1ffa) bank = 6;
if(address == 0x1ffb) bank = 7;
if(address.bit(12)) {
if(hasRam && address >= 0x1080 && address <= 0x10ff) return ram.read(address & 0x7f);
return rom.read((bank * 0x1000) + (address & 0xfff));
}
return 0xff;
}
auto write(n16 address, n8 data) -> bool override {
if(address == 0x1ff4) bank = 0;
if(address == 0x1ff5) bank = 1;
if(address == 0x1ff6) bank = 2;
if(address == 0x1ff7) bank = 3;
if(address == 0x1ff8) bank = 4;
if(address == 0x1ff9) bank = 5;
if(address == 0x1ffa) bank = 6;
if(address == 0x1ffb) bank = 7;
if(address >= 0x1000 && address <= 0x107f) {
hasRam = true;
ram.write(address & 0x7f, data);
return true;
}
return false;
}
auto power() -> void override {
bank = 0;
hasRam = 0;
ram.allocate(128);
}
auto serialize(serializer& s) -> void override {
s(bank);
s(hasRam);
s(ram);
}
};
+54
View File
@@ -0,0 +1,54 @@
struct Atari8k : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
Memory::Writable<n8> ram;
n1 bank;
bool hasRam;
auto load() -> void override {
Interface::load(rom, "program.rom");
}
auto save() -> void override {
}
auto unload() -> void override {
}
auto read(n16 address) -> n8 override {
if(address == 0x1ff8) bank = 0;
if(address == 0x1ff9) bank = 1;
if(address.bit(12)) {
if(hasRam && address >= 0x1080 && address <= 0x10ff) return ram.read(address & 0x7f);
return rom.read((bank * 0x1000) + (address & 0xfff));
}
return 0xff;
}
auto write(n16 address, n8 data) -> bool override {
if(address == 0x1ff8) bank = 0;
if(address == 0x1ff9) bank = 1;
if(address >= 0x1000 && address <= 0x107f) {
hasRam = true;
ram.write(address & 0x7f, data);
return true;
}
return false;
}
auto power() -> void override {
bank = 0;
hasRam = 0;
ram.allocate(128);
}
auto serialize(serializer& s) -> void override {
s(bank);
s(hasRam);
s(ram);
}
};
+37
View File
@@ -0,0 +1,37 @@
namespace Board {
#include "linear.cpp"
#include "atari8k.cpp"
#include "atari16k.cpp"
#include "atari32k.cpp"
#include "commavid.cpp"
#include "parker-bros.cpp"
#include "tigervision.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 0xff; }
virtual auto write(n16 address, n8 data) -> bool { return false; }
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;
};
}
+41
View File
@@ -0,0 +1,41 @@
struct Commavid : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
Memory::Writable<n8> ram;
auto load() -> void override {
Interface::load(rom, "program.rom");
}
auto save() -> void override {
}
auto unload() -> void override {
}
auto read(n16 address) -> n8 override {
if(address.bit(12)) {
if(address >= 0x1000 && address <= 0x13ff) return ram.read(address & 0x3ff);
return rom.read(address & 0xfff);
}
return 0xff;
}
auto write(n16 address, n8 data) -> bool override {
if(address >= 0x1400 && address <= 0x17ff) {
ram.write(address & 0x3ff, data);
return true;
}
return false;
}
auto power() -> void override {
ram.allocate(1_KiB);
}
auto serialize(serializer& s) -> void override {
s(ram);
}
};
+45
View File
@@ -0,0 +1,45 @@
struct Linear : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
Memory::Writable<n8> ram;
bool hasRam;
auto load() -> void override {
Interface::load(rom, "program.rom");
}
auto save() -> void override {
}
auto unload() -> void override {
}
auto read(n16 address) -> n8 override {
if(address.bit(12)) {
if(hasRam && address >= 0x1080 && address <= 0x10ff) return ram.read(address & 0x7f);
return rom.read(address & 0xfff);
}
return 0xff;
}
auto write(n16 address, n8 data) -> bool override {
if(address >= 0x1000 && address <= 0x107f) {
hasRam = true;
ram.write(address & 0x7f, data);
return true;
}
return false;
}
auto power() -> void override {
hasRam = 0;
ram.allocate(128);
}
auto serialize(serializer& s) -> void override {
s(hasRam);
s(ram);
}
};
+51
View File
@@ -0,0 +1,51 @@
struct ParkerBros : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
n3 banks[3];
auto load() -> void override {
Interface::load(rom, "program.rom");
}
auto save() -> void override {
}
auto unload() -> void override {
}
auto read(n16 address) -> n8 override {
bankswitch(address);
if(address.bit(12)) {
if(address >= 0x1000 && address <= 0x13ff) return rom.read((banks[0] * 0x400) + (address & 0x3ff));
if(address >= 0x1400 && address <= 0x17ff) return rom.read((banks[1] * 0x400) + (address & 0x3ff));
if(address >= 0x1800 && address <= 0x1bff) return rom.read((banks[2] * 0x400) + (address & 0x3ff));
if(address >= 0x1c00 && address <= 0x1fff) return rom.read(( 7 * 0x400) + (address & 0x3ff));
}
return 0xff;
}
auto write(n16 address, n8 data) -> bool override {
bankswitch(address);
return false;
}
auto bankswitch(n16 address) -> void {
if(address >= 0x1fe0 && address <= 0x1fe7) banks[0] = address - 0x1fe0;
if(address >= 0x1fe8 && address <= 0x1fef) banks[1] = address - 0x1fe8;
if(address >= 0x1ff0 && address <= 0x1ff7) banks[2] = address - 0x1ff0;
}
auto power() -> void override {
banks[0] = 0;
banks[1] = 1;
banks[2] = 2;
}
auto serialize(serializer& s) -> void override {
s(banks[0]);
s(banks[1]);
s(banks[2]);
}
};
+42
View File
@@ -0,0 +1,42 @@
struct Tigervision : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
n8 bank;
n8 nextBank;
auto load() -> void override {
Interface::load(rom, "program.rom");
}
auto save() -> void override {
}
auto unload() -> void override {
}
auto read(n16 address) -> n8 override {
if(address.bit(12)) {
if(bank != nextBank) bank = nextBank;
if(address <= 0x17ff) return rom.read((bank * 0x800) + (address & 0x7ff));
return rom.read((rom.size() - 0x800) + (address & 0x7ff));
}
return 0xff;
}
auto write(n16 address, n8 data) -> bool override {
if(address.bit(12) && bank != nextBank) bank = nextBank;
if(address <= 0x3f) nextBank = data;
return false;
}
auto power() -> void override {
bank = 0;
nextBank = 0;
}
auto serialize(serializer& s) -> void override {
s(bank);
s(nextBank);
}
};
+66
View File
@@ -0,0 +1,66 @@
#include <a26/a26.hpp>
namespace ares::Atari2600 {
Cartridge& cartridge = cartridgeSlot.cartridge;
#include "board/board.cpp"
#include "slot.cpp"
#include "serialization.cpp"
auto Cartridge::allocate(Node::Port parent) -> Node::Peripheral {
return node = parent->append<Node::Peripheral>(string{system.name(), " Cartridge"});
}
auto Cartridge::connect() -> void {
if(!node->setPak(pak = platform->pak(node))) return;
information = {};
information.title = pak->attribute("title");
information.region = pak->attribute("region");
information.board = pak->attribute("board");
if(information.board == "Linear") board = new Board::Linear{*this};
if(information.board == "Atari8k") board = new Board::Atari8k{*this};
if(information.board == "Atari16k") board = new Board::Atari16k{*this};
if(information.board == "Atari32k") board = new Board::Atari32k{*this};
if(information.board == "Commavid") board = new Board::Commavid{*this};
if(information.board == "ParkerBros8k") board = new Board::ParkerBros{*this};
if(information.board == "Tigervision") board = new Board::Tigervision{*this};
if(!board) board = new Board::Interface{*this};
board->pak = pak;
board->load();
power();
}
auto Cartridge::disconnect() -> void {
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(!node) return 0xff;
if(board) return board->read(address);
return 0xff;
}
auto Cartridge::write(n16 address, n8 data) -> bool {
if(!node) return false;
if(board) return board->write(address, data);
return false;
}
}
+36
View File
@@ -0,0 +1,36 @@
struct Cartridge;
#include "board/board.hpp"
struct Cartridge {
Node::Peripheral node;
VFS::Pak pak;
auto title() const -> string { return information.title; }
auto region() const -> string { return information.region; }
//cartridge.cpp
auto allocate(Node::Port) -> Node::Peripheral;
auto connect() -> void;
auto disconnect() -> void;
auto save() -> void;
auto power() -> void;
auto read(n16 address) -> n8;
auto write(n16 address, n8 data) -> bool;
//serialization.cpp
auto serialize(serializer&) -> void;
unique_pointer<Board::Interface> board;
//private:
struct Information {
string title;
string region;
string board;
} information;
};
#include "slot.hpp"
extern Cartridge& cartridge;
+3
View File
@@ -0,0 +1,3 @@
auto Cartridge::serialize(serializer& s) -> void {
if(board) s(*board);
}
+18
View File
@@ -0,0 +1,18 @@
CartridgeSlot cartridgeSlot{"Cartridge Slot"};
CartridgeSlot::CartridgeSlot(string name) : name(name) {
}
auto CartridgeSlot::load(Node::Object parent) -> void {
port = parent->append<Node::Port>(name);
port->setFamily(system.name());
port->setType("Cartridge");
port->setAllocate([&](auto name) { return cartridge.allocate(port); });
port->setConnect([&] { return cartridge.connect(); });
port->setDisconnect([&] { return cartridge.disconnect(); });
}
auto CartridgeSlot::unload() -> void {
cartridge.disconnect();
port = {};
}
+13
View File
@@ -0,0 +1,13 @@
struct CartridgeSlot {
Node::Port port;
Cartridge cartridge;
//slot.cpp
CartridgeSlot(string name);
auto load(Node::Object) -> void;
auto unload() -> void;
const string name;
};
extern CartridgeSlot cartridgeSlot;
+8
View File
@@ -0,0 +1,8 @@
#include <a26/a26.hpp>
namespace ares::Atari2600 {
#include "port.cpp"
#include "gamepad/gamepad.cpp"
}
+11
View File
@@ -0,0 +1,11 @@
struct Controller {
Node::Peripheral node;
virtual ~Controller() = default;
virtual auto read() -> n8 { return 0xff; }
virtual auto write(n8 data) -> void {}
};
#include "port.hpp"
#include "gamepad/gamepad.hpp"
+37
View File
@@ -0,0 +1,37 @@
Gamepad::Gamepad(Node::Port parent) {
node = parent->append<Node::Peripheral>("Gamepad");
up = node->append<Node::Input::Button>("Up");
down = node->append<Node::Input::Button>("Down");
left = node->append<Node::Input::Button>("Left");
right = node->append<Node::Input::Button>("Right");
fire = node->append<Node::Input::Button>("Fire");
}
auto Gamepad::read() -> n8 {
platform->input(up);
platform->input(down);
platform->input(left);
platform->input(right);
platform->input(fire);
if(!(up->value() & down->value())) {
yHold = 0, upLatch = up->value(), downLatch = down->value();
} else if(!yHold) {
yHold = 1, swap(upLatch, downLatch);
}
if(!(left->value() & right->value())) {
xHold = 0, leftLatch = left->value(), rightLatch = right->value();
} else if(!xHold) {
xHold = 1, swap(leftLatch, rightLatch);
}
n8 data = 0xff;
data.bit(0) = !upLatch;
data.bit(1) = !downLatch;
data.bit(2) = !leftLatch;
data.bit(3) = !rightLatch;
data.bit(4) = !fire->value();
return data;
}
+19
View File
@@ -0,0 +1,19 @@
struct Gamepad : Controller {
Node::Input::Button up;
Node::Input::Button down;
Node::Input::Button left;
Node::Input::Button right;
Node::Input::Button fire;
Gamepad(Node::Port);
auto read() -> n8 override;
private:
b1 yHold;
b1 upLatch;
b1 downLatch;
b1 xHold;
b1 leftLatch;
b1 rightLatch;
};
+28
View File
@@ -0,0 +1,28 @@
ControllerPort controllerPort1{"Controller Port 1"};
ControllerPort controllerPort2{"Controller Port 2"};
ControllerPort::ControllerPort(string name) : name(name) {
}
auto ControllerPort::load(Node::Object parent) -> void {
port = parent->append<Node::Port>(name);
port->setFamily(system.name());
port->setType("Controller");
port->setHotSwappable(true);
port->setAllocate([&](auto name) { return allocate(name); });
port->setSupported({"Gamepad"});
}
auto ControllerPort::unload() -> void {
device = {};
port = {};
}
auto ControllerPort::allocate(string name) -> Node::Peripheral {
if(name == "Gamepad") device = new Gamepad(port);
if(device) return device->node;
return {};
}
auto ControllerPort::serialize(serializer& s) -> void {
}
+20
View File
@@ -0,0 +1,20 @@
struct ControllerPort {
Node::Port port;
unique_pointer<Controller> device;
//port.cpp
ControllerPort(string name);
auto load(Node::Object) -> void;
auto unload() -> void;
auto allocate(string name) -> Node::Peripheral;
auto read() -> n8 { if(device) return device->read(); return 0xff; }
auto write(n8 data) -> void { if(device) return device->write(data); }
auto serialize(serializer&) -> void;
const string name;
};
extern ControllerPort controllerPort1;
extern ControllerPort controllerPort2;
+42
View File
@@ -0,0 +1,42 @@
#include <a26/a26.hpp>
namespace ares::Atari2600 {
CPU cpu;
#include "memory.cpp"
#include "timing.cpp"
#include "debugger.cpp"
#include "serialization.cpp"
auto CPU::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("CPU");
debugger.load(node);
}
auto CPU::unload() -> void {
debugger = {};
node = {};
}
auto CPU::main() -> void {
debugger.instruction();
instruction();
}
auto CPU::step(u32 clocks) -> void {
Thread::step(clocks);
Thread::synchronize();
}
auto CPU::power(bool reset) -> void {
MOS6502::BCD = 1;
MOS6502::power();
Thread::create(system.frequency() / 3, {&CPU::main, this});
PC.byte(0) = readBus(0xfffc);
PC.byte(1) = readBus(0xfffd);
io = {};
}
}
+48
View File
@@ -0,0 +1,48 @@
struct CPU : MOS6502, Thread {
Node::Object node;
struct Debugger {
//debugger.cpp
auto load(Node::Object) -> void;
auto instruction() -> void;
struct Tracer {
Node::Debugger::Tracer::Instruction instruction;
} tracer;
} debugger;
//cpu.cpp
auto load(Node::Object) -> void;
auto unload() -> void;
auto main() -> void;
auto step(u32 clocks) -> void;
auto power(bool reset) -> void;
//memory.cpp
auto readBus(n16 address) -> n8;
auto writeBus(n16 address, n8 data) -> void;
auto readIO(n16 address) -> n8;
auto writeIO(n16 address, n8 data) -> void;
auto readDebugger(n16 address) -> n8 override;
auto serialize(serializer&) -> void;
//timing.cpp
auto read(n16 address) -> n8 override;
auto write(n16 address, n8 data) -> void override;
auto lastCycle() -> void override;
auto nmi(n16& vector) -> void override;
auto rdyLine(bool) -> void;
//protected:
struct IO {
n1 rdyLine = 1;
} io;
};
extern CPU cpu;
+12
View File
@@ -0,0 +1,12 @@
auto CPU::Debugger::load(Node::Object parent) -> void {
tracer.instruction = parent->append<Node::Debugger::Tracer::Instruction>("Instruction", "CPU");
tracer.instruction->setAddressBits(16);
}
auto CPU::Debugger::instruction() -> void {
if(tracer.instruction->enabled() && tracer.instruction->address(cpu.PC)) {
tracer.instruction->notify(cpu.disassembleInstruction(), cpu.disassembleContext());
}
}
+40
View File
@@ -0,0 +1,40 @@
inline auto CPU::readBus(n16 address) -> n8 {
address &= 0x1fff;
n8 data = cartridge.read(address);
if(address.bit(12) == 0 && address.bit(7) == 0) {
return tia.read(address & 0xf);
}
if(address.bit(12) == 0 && address.bit(9) == 0 && address.bit(7) == 1) {
return riot.readRam(address & 0x7f);
}
if(address.bit(12) == 0 && address.bit(9) == 1 && address.bit(7) == 1) {
return riot.readIo((address - 0x80) & 0x1f);
}
return data;
}
inline auto CPU::writeBus(n16 address, n8 data) -> void {
address &= 0x1fff;
cartridge.write(address, data);
if(address.bit(12) == 0 && address.bit(7) == 0) {
return tia.write(address & 0x3f, data);
}
if(address.bit(12) == 0 && address.bit(9) == 0 && address.bit(7) == 1) {
return riot.writeRam(address & 0x7f, data);
}
if(address.bit(12) == 0 && address.bit(9) == 1 && address.bit(7) == 1) {
return riot.writeIo((address - 0x80) & 0x1f, data);
}
}
auto CPU::readDebugger(n16 address) -> n8 {
return readBus(address);
}
+5
View File
@@ -0,0 +1,5 @@
auto CPU::serialize(serializer& s) -> void {
MOS6502::serialize(s);
Thread::serialize(s);
s(io.rdyLine);
}
+30
View File
@@ -0,0 +1,30 @@
auto CPU::read(n16 address) -> n8 {
if(io.rdyLine == 0) {
while (io.rdyLine == 0) {
step(1);
}
MDR = readBus(address);
return MDR;
}
step(1);
MDR = readBus(address);
return MDR;
}
auto CPU::write(n16 address, n8 data) -> void {
step(1);
writeBus(address, MDR = data);
}
auto CPU::lastCycle() -> void {
}
auto CPU::nmi(n16& vector) -> void {
}
auto CPU::rdyLine(bool line) -> void {
io.rdyLine = line;
}
+87
View File
@@ -0,0 +1,87 @@
auto RIOT::readRam(n8 address) -> n8 {
return ram[address];
}
auto RIOT::writeRam(n8 address, n8 data) -> void {
ram[address] = data;
}
auto RIOT::readIo(n8 address) -> n8 {
switch(address) {
case 0x00: return readPortA();
case 0x01: return port[0].direction;
case 0x02: return readPortB();
case 0x03: return port[1].direction;
case 0x04: return timer.counter;
}
debug(unimplemented, "[RIOT] IO read ", hex(address));
return 0xff;
}
auto RIOT::writeIo(n8 address, n8 data) -> void {
switch(address) {
case 0x00: writePortA(data); return;
case 0x01: port[0].direction = data; return;
case 0x02: writePortB(data); return;
case 0x03: port[1].direction = data; return;
case 0x14: case 0x15: case 0x16: case 0x17: {
const n16 intervals[] = {1, 8, 64, 1024};
timer.counter = data;
timer.interval = intervals[address - 0x14];
timer.reload = timer.interval;
return; }
}
debug(unimplemented, "[RIOT] IO write ",hex(address), " = ", hex(data));
}
auto RIOT::readPortA() -> n8 {
n8 data;
// TODO: Handle when configured as outputs
data.bit(0, 3) = controllerPort2.read().bit(0, 4);
data.bit(4, 7) = controllerPort1.read().bit(0, 4);
return data;
}
auto RIOT::writePortA(n8 data) -> void {
port[0].data = data;
}
auto RIOT::readPortB() -> n8 {
system.controls.poll();
n8 data;
for(auto n : range(8)) if(port[1].direction.bit(n)) { data.bit(n) = port[1].data.bit(n); }
// Atari hard-wires these bits as input
data.bit(0) = !system.controls.reset->value();
data.bit(1) = !system.controls.select->value();
// These are toggle switches; flip the values on the rising edge
if(system.controls.tvType->value() && !tvTypeLatch) {
tvType = !tvType;
}
tvTypeLatch = system.controls.tvType->value();
if(system.controls.leftDifficulty->value() && !leftDifficultyLatch) {
leftDifficulty = !leftDifficulty;
}
leftDifficultyLatch = system.controls.leftDifficulty->value();
if(system.controls.rightDifficulty->value() && !rightDifficultyLatch) {
rightDifficulty = !rightDifficulty;
}
rightDifficultyLatch = system.controls.rightDifficulty->value();
data.bit(3) = tvType;
data.bit(6) = leftDifficulty;
data.bit(7) = rightDifficulty;
return data;
}
auto RIOT::writePortB(n8 data) -> void {
port[1].data = data;
}
+51
View File
@@ -0,0 +1,51 @@
#include <a26/a26.hpp>
namespace ares::Atari2600 {
RIOT riot;
#include "io.cpp"
#include "serialization.cpp"
auto RIOT::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("RIOT");
ram.allocate(128);
}
auto RIOT::unload() -> void {
ram.reset();
node.reset();
}
auto RIOT::main() -> void {
decrementTimer();
step(1);
}
auto RIOT::decrementTimer() -> void {
if(--timer.interval == 0) {
if(--timer.counter == 0xff) {
timer.reload = 1;
}
timer.interval = timer.reload;
}
}
auto RIOT::step(u32 clocks) -> void {
Thread::step(clocks);
Thread::synchronize();
}
auto RIOT::power(bool reset) -> void {
Thread::create(system.frequency() / 3, {&RIOT::main, this});
timer = {};
port[0] = {.data = 0xff};
port[1] = {.data = 0xff};
leftDifficulty = 1;
rightDifficulty = 1;
tvType = 1;
}
}
+48
View File
@@ -0,0 +1,48 @@
struct RIOT : Thread {
Node::Object node;
Memory::Writable<n8> ram;
//riot.cpp
auto load(Node::Object) -> void;
auto unload() -> void;
auto main() -> void;
auto step(u32 clocks) -> void;
auto power(bool reset) -> void;
auto decrementTimer() -> void;
//io.cpp
auto readRam(n8 address) -> n8;
auto writeRam(n8 address, n8 data) -> void;
auto readIo(n8 address) -> n8;
auto writeIo(n8 address, n8 data) -> void;
auto readPortA() -> n8;
auto readPortB() -> n8;
auto writePortA(n8 data) -> void;
auto writePortB(n8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
struct {
u8 counter;
n16 interval;
n16 reload;
} timer;
struct Port {
n8 data;
n8 direction;
} port[2];
n1 leftDifficulty;
n1 leftDifficultyLatch;
n1 rightDifficulty;
n1 rightDifficultyLatch;
n1 tvType;
n1 tvTypeLatch;
};
extern RIOT riot;
+21
View File
@@ -0,0 +1,21 @@
//serialization.cpp
auto RIOT::serialize(serializer& s) -> void {
Thread::serialize(s);
s(ram);
s(timer.counter);
s(timer.interval);
s(timer.reload);
for(auto n : range(2)) {
s(port[n].data);
s(port[n].direction);
}
s(leftDifficulty);
s(leftDifficultyLatch);
s(rightDifficulty);
s(rightDifficultyLatch);
s(tvType);
s(tvTypeLatch);
}
+17
View File
@@ -0,0 +1,17 @@
auto System::Controls::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("Controls");
reset = node->append<Node::Input::Button>("Reset");
select = node->append<Node::Input::Button>("Select");
leftDifficulty = node->append<Node::Input::Button>("Left Difficulty");
rightDifficulty = node->append<Node::Input::Button>("Right Difficulty");
tvType = node->append<Node::Input::Button>("TV Type");
}
auto System::Controls::poll() -> void {
platform->input(reset);
platform->input(select);
platform->input(leftDifficulty);
platform->input(rightDifficulty);
platform->input(tvType);
}
+46
View File
@@ -0,0 +1,46 @@
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
serializer s;
u32 signature = SerializerSignature;
char version[16] = {};
char description[512] = {};
memory::copy(&version, (const char*)SerializerVersion, SerializerVersion.size());
s(signature);
s(synchronize);
s(version);
s(description);
serialize(s, synchronize);
return s;
}
auto System::unserialize(serializer& s) -> bool {
u32 signature = 0;
bool synchronize = true;
char version[16] = {};
char description[512] = {};
s(signature);
s(synchronize);
s(version);
s(description);
if(signature != SerializerSignature) return false;
if(string{version} != SerializerVersion) return false;
if(synchronize) power(/* reset = */ false);
serialize(s, synchronize);
return true;
}
auto System::serialize(serializer& s, bool synchronize) -> void {
scheduler.setSynchronize(synchronize);
s(cartridge);
s(cpu);
s(tia);
s(riot);
s(controllerPort1);
s(controllerPort2);
}
+105
View File
@@ -0,0 +1,105 @@
#include <a26/a26.hpp>
namespace ares::Atari2600 {
auto enumerate() -> vector<string> {
return {
"[Atari] Atari 2600 (NTSC)",
"[Atari] Atari 2600 (PAL)",
"[Atari] Atari 2600 (SECAM)",
};
}
auto load(Node::System& node, string name) -> bool {
if(!enumerate().find(name)) return false;
return system.load(node, name);
}
Random random;
Scheduler scheduler;
System system;
#include "controls.cpp"
#include "serialization.cpp"
auto System::game() -> string {
if(cartridge.node) {
return cartridge.title();
}
return "(no cartridge connected)";
}
auto System::run() -> void {
scheduler.enter();
}
auto System::load(Node::System& root, string name) -> bool {
if(node) unload();
information = {};
if(name.find("NTSC")) {
information.name = "Atari 2600";
information.region = Region::NTSC;
information.frequency = 3579546;
}
if(name.find("PAL")) {
information.name = "Atari 2600";
information.region = Region::PAL;
information.frequency = 3546894;
}
if(name.find("SECAM")) {
information.name = "Atari 2600";
information.region = Region::SECAM;
information.frequency = 3546894;
}
node = Node::System::create(information.name);
node->setGame({&System::game, this});
node->setRun({&System::run, this});
node->setPower({&System::power, this});
node->setSave({&System::save, this});
node->setUnload({&System::unload, this});
node->setSerialize({&System::serialize, this});
node->setUnserialize({&System::unserialize, this});
root = node;
scheduler.reset();
controls.load(node);
riot.load(node);
cpu.load(node);
tia.load(node);
cartridgeSlot.load(node);
controllerPort1.load(node);
controllerPort2.load(node);
return true;
}
auto System::save() -> void {
if(!node) return;
cartridge.save();
}
auto System::unload() -> void {
if(!node) return;
save();
riot.unload();
cpu.unload();
tia.unload();
cartridgeSlot.unload();
controllerPort1.unload();
controllerPort2.unload();
node = {};
}
auto System::power(bool reset) -> void {
for(auto& setting : node->find<Node::Setting::Setting>()) setting->setLatch();
random.entropy(Random::Entropy::Low);
cartridge.power();
riot.power(reset);
cpu.power(reset);
tia.power(reset);
scheduler.power(cpu);
}
}
+53
View File
@@ -0,0 +1,53 @@
extern Random random;
struct System {
Node::System node;
struct Controls {
Node::Object node;
Node::Input::Button reset;
Node::Input::Button select;
Node::Input::Button leftDifficulty;
Node::Input::Button rightDifficulty;
Node::Input::Button tvType;
//controls.cpp
auto load(Node::Object) -> void;
auto poll() -> void;
} controls;
enum class Region : u32 { NTSC, PAL, SECAM };
auto name() const -> string { return information.name; }
auto region() const -> Region { return information.region; }
auto frequency() const -> u32 { return information.frequency; }
//system.cpp
auto game() -> string;
auto run() -> void;
auto load(Node::System& node, string name) -> bool;
auto save() -> void;
auto unload() -> void;
auto power(bool reset) -> void;
//serialization.cpp
auto serialize(bool synchronize) -> serializer;
auto unserialize(serializer&) -> bool;
private:
struct Information {
string name = "Atari 2600";
Region region = Region::NTSC;
u32 frequency = Constants::Colorburst::NTSC;
} information;
//serialization.cpp
auto serialize(serializer&, bool synchronize) -> void;
};
extern System system;
auto Region::NTSC() -> bool { return system.region() == System::Region::NTSC; }
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }
auto Region::SECAM() -> bool { return system.region() == System::Region::SECAM; }
+64
View File
@@ -0,0 +1,64 @@
auto TIA::runAudio() -> void {
if(io.hcounter == 9 || io.hcounter == 81) {
audio[0].phase0();
audio[1].phase0();
}
if(io.hcounter == 37 || io.hcounter == 149) {
auto a = audio[0].phase1();
auto b = audio[1].phase1();
double output = 0;
output += volume[a];
output += volume[b];
stream->frame(output / 2.0);
}
}
auto TIA::AudioChannel::phase0() -> void {
if (enable) {
switch (control.bit(0, 1)) {
case 0x00: case 0x01: pulseCounterPaused = 0; break;
case 0x02: pulseCounterPaused = noiseCounter.bit(1, 4) != 1; break;
case 0x03: pulseCounterPaused = !noiseCounter.bit(0); break;
}
noiseFeedback = (noiseCounter.bit(2) ^ noiseCounter.bit(0)) || noiseCounter == 0;
if(control.bit(0, 2) == 0) {
noiseFeedback = !(control.bit(2, 3)) ||
!(noiseCounter || (pulseCounter != 0x0a)) ||
(pulseCounter.bit(0) ^ noiseCounter.bit(0));
}
}
enable = divCounter == frequency;
if (divCounter == frequency || divCounter == 0x1f) {
divCounter = 0;
return;
}
divCounter++;
}
auto TIA::AudioChannel::phase1() -> u8 {
if(!enable) return pulseCounter.bit(0) * volume;
pulseFeedback = 0;
switch(control.bit(2, 3)) {
case 0x00: pulseFeedback = pulseCounter != 0x0a && control.bit(0, 2) && (pulseCounter.bit(1) ^ pulseCounter.bit(0)); break;
case 0x01: pulseFeedback = !pulseCounter.bit(3); ; break;
case 0x02: pulseFeedback = !noiseCounter.bit(0); ; break;
case 0x03: pulseFeedback = !(pulseCounter.bit(1) || !pulseCounter.bit(1, 3)); ; break;
}
noiseCounter >>= 1;
if (noiseFeedback) noiseCounter.bit(4) = 1;
if(!pulseCounterPaused) {
pulseCounter = ~(pulseCounter >> 1) & 7;
if (pulseFeedback) pulseCounter.bit(3) = 1;
}
return pulseCounter.bit(0) * volume;
}
+42
View File
@@ -0,0 +1,42 @@
auto TIA::color(n32 color) -> n64 {
u64 ntscColors[128] = {
0x0000'0000'0000ull, 0x4040'4040'4040ull, 0x6c6c'6c6c'6c6cull, 0x9090'9090'9090ull, 0xb0b0'b0b0'b0b0ull, 0xc8c8'c8c8'c8c8ull, 0xdcdc'dcdc'dcdcull, 0xecec'ecec'ececull,
0x4444'4444'0000ull, 0x6464'6464'1010ull, 0x8484'8484'2424ull, 0xa0a0'a0a0'3434ull, 0xb8b8'b8b8'4040ull, 0xd0d0'd0d0'5050ull, 0xe8e8'e8e8'5c5cull, 0xfcfc'fcfc'6868ull,
0x7070'2828'0000ull, 0x8484'4444'1414ull, 0x9898'5c5c'2828ull, 0xacac'7878'3c4cull, 0xbcbc'8c8c'4c4cull, 0xcccc'a0a0'5c5cull, 0xdcdc'b4b4'6868ull, 0xecec'c8c8'7878ull,
0x8484'1818'0000ull, 0x9898'3434'1818ull, 0xacac'5050'3030ull, 0xc0c0'6868'4848ull, 0xd0d0'8080'5c5cull, 0xe0e0'9494'7070ull, 0xecec'a8a8'8080ull, 0xfcfc'bcbc'9494ull,
0x8888'0000'0000ull, 0x9c9c'2020'2020ull, 0xb0b0'3c3c'3c3cull, 0xc0c0'5858'5858ull, 0xd0d0'7070'7070ull, 0xe0e0'8888'8888ull, 0xecec'a0a0'a0a0ull, 0xfcfc'b4b4'b4b4ull,
0x7878'0000'5c5cull, 0x8c8c'2020'7474ull, 0xa0a0'3c3c'8888ull, 0xb0b0'5858'9c9cull, 0xc0c0'7070'b0b0ull, 0xd0d0'8484'c0c0ull, 0xdcdc'9c9c'd0d0ull, 0xecec'b0b0'e0e0ull,
0x4848'0000'7878ull, 0x6060'2020'9090ull, 0x7878'3c3c'a4a4ull, 0x8c8c'5858'b8b8ull, 0xa0a0'7070'ccccull, 0xb4b4'8484'dcdcull, 0xc4c4'9c9c'ececull, 0xd4d4'b0b0'fcfcull,
0x1414'0000'8484ull, 0x3030'2020'9898ull, 0x4c4c'3c3c'acacull, 0x6868'5858'c0c0ull, 0x7c7c'7070'd0d0ull, 0x9494'8888'e0e0ull, 0xa8a8'a0a0'ececull, 0xbcbc'b4b4'fcfcull,
0x0000'0000'8888ull, 0x1c1c'2020'9c9cull, 0x3838'4040'b0b0ull, 0x5050'5c5c'c0c0ull, 0x6868'7474'd0d0ull, 0x7c7c'8c8c'e0e0ull, 0x9090'a4a4'ececull, 0xa4a4'b8b8'fcfcull,
0x0000'1818'7c7cull, 0x1c1c'3838'9090ull, 0x3838'5454'a8a8ull, 0x5050'7070'bcbcull, 0x6868'8888'ccccull, 0x7c7c'9c9c'dcdcull, 0x9090'b4b4'ececull, 0xa4a4'c8c8'fcfcull,
0x0000'2c2c'5c5cull, 0x1c1c'4c4c'7878ull, 0x3838'6868'9090ull, 0x5050'8484'acacull, 0x6868'9c9c'c0c0ull, 0x7c7c'b4b4'd4d4ull, 0x9090'cccc'e8e8ull, 0xa4a4'e0e0'fcfcull,
0x0000'3c3c'2c2cull, 0x1c1c'5c5c'4848ull, 0x3838'7c7c'6464ull, 0x5050'9c9c'8080ull, 0x6868'b4b4'9494ull, 0x7c7c'd0d0'acacull, 0x9090'e4e4'c0c0ull, 0xa4a4'fcfc'd4d4ull,
0x0000'3c3c'0000ull, 0x2020'5c5c'2020ull, 0x4040'7c7c'4040ull, 0x5c5c'9c9c'5c5cull, 0x7474'b4b4'7474ull, 0x8c8c'd0d0'8c8cull, 0xa4a4'e4e4'a4a4ull, 0xb8b8'fcfc'b8b8ull,
0x1414'3838'0000ull, 0x3434'5c5c'1c1cull, 0x5050'7c7c'3838ull, 0x6c6c'9898'5050ull, 0x8484'b4b4'6868ull, 0x9c9c'cccc'7c7cull, 0xb4b4'e4e4'9090ull, 0xc8c8'fcfc'a4a4ull,
0x2c2c'3030'0000ull, 0x4c4c'5050'1c1cull, 0x6868'7070'3434ull, 0x8484'8c8c'4c4cull, 0x9c9c'a8a8'6464ull, 0xb4b4'c0c0'7878ull, 0xcccc'd4d4'8888ull, 0xe0e0'ecec'9c9cull,
0x4444'2828'0000ull, 0x6464'4848'1818ull, 0x8484'6868'3030ull, 0xa0a0'8484'4444ull, 0xb8b8'9c9c'5858ull, 0xd0d0'b4b4'6c6cull, 0x8e8c'cccc'7c7cull, 0xfcfc'c0c0'8c8cull,
};
u64 palColors[128] = {
0x0000'0000'0000ull, 0x2828'2828'2828ull, 0x5050'5050'5050ull, 0x7474'7474'7474ull, 0x9494'9494'9494ull, 0xb4b4'b4b4'b4b4ull, 0xd0d0'd0d0'd0d0ull, 0xecec'ecec'ececull,
0x0000'0000'0000ull, 0x2828'2828'2828ull, 0x5050'5050'5050ull, 0x7474'7474'7474ull, 0x9494'9494'9494ull, 0xb4b4'b4b4'b4b4ull, 0xd0d0'd0d0'd0d0ull, 0xecec'ecec'ececull,
0x8080'5858'0000ull, 0x9494'7070'2020ull, 0xa8a8'8484'3c3cull, 0xbcbc'9c9c'5858ull, 0xcccc'acac'7070ull, 0xdcdc'c0c0'8484ull, 0xecec'd0d0'9c9cull, 0xfcfc'e0e0'b0b0ull,
0x4444'5c5c'0000ull, 0x5c5c'7878'2020ull, 0x7474'9090'3c3cull, 0x8c8c'acac'5858ull, 0xa0a0'c0c0'7070ull, 0xb0b0'd4d4'8484ull, 0xc4c4'e8e8'9c9cull, 0xd4d4'fcfc'b0b0ull,
0x7070'3434'0000ull, 0x8888'5050'2020ull, 0xa0a0'6868'3c3cull, 0xb4b4'8484'5858ull, 0xc8c8'9898'7070ull, 0xdcdc'acac'8484ull, 0xecec'c0c0'9c9cull, 0xfcfc'd4d4'b0b0ull,
0x0000'6464'1414ull, 0x2020'8080'3434ull, 0x3c3c'9898'5050ull, 0x5858'b0b0'6c6cull, 0x7070'c4c4'8484ull, 0x8484'd8d8'9c9cull, 0x9c9c'e8e8'b4b4ull, 0xb0b0'fcfc'c8c8ull,
0x7070'0000'1414ull, 0x8888'2020'3434ull, 0xa0a0'3c3c'5050ull, 0xb4b4'5858'6c6cull, 0xc8c8'7070'8484ull, 0xdcdc'8484'9c9cull, 0xecec'9c9c'b4b4ull, 0xfcfc'b0b0'c8c8ull,
0x0000'5c5c'5c5cull, 0x2020'7474'7474ull, 0x3c3c'8c8c'8c8cull, 0x5858'a4a4'a4a4ull, 0x7070'b8b8'b8b8ull, 0x8484'c8c8'c8c8ull, 0x9c9c'dcdc'dcdcull, 0xb0b0'ecec'ececull,
0x7070'0000'5c5cull, 0x8484'2020'7474ull, 0x9494'3c3c'8888ull, 0xa8a8'5858'9c9cull, 0xb4b4'7070'b0b0ull, 0xc4c4'8484'c0c0ull, 0xd0d0'9c9c'd0d0ull, 0xe0e0'b0b0'e0e0ull,
0x0000'3c3c'7070ull, 0x1c1c'5858'8888ull, 0x3838'7474'a0a0ull, 0x5050'8c8c'b4b4ull, 0x6868'a4a4'c8c8ull, 0x7c7c'b8b8'dcdcull, 0x9090'cccc'ececull, 0xa4a4'e0e0'fcfcull,
0x5858'0000'7070ull, 0x6c6c'2020'8888ull, 0x8080'3c3c'a0a0ull, 0x9494'5858'b4b4ull, 0xa4a4'7070'c8c8ull, 0xb4b4'8484'dcdcull, 0xc4c4'9c9c'ececull, 0xd4d4'b0b0'fcfcull,
0x0000'2020'7070ull, 0x1c1c'3c3c'8888ull, 0x3838'5858'a0a0ull, 0x5050'7474'b4b4ull, 0x6868'8888'c8c8ull, 0x7c7c'a0a0'dcdcull, 0x9090'b4b4'ececull, 0xa4a4'c8c8'fcfcull,
0x3c3c'0000'8080ull, 0x5454'2020'9494ull, 0x6c6c'3c3c'a8a8ull, 0x8080'5858'bcbcull, 0x9494'7070'ccccull, 0xa8a8'8484'dcdcull, 0xb8b8'9c9c'ececull, 0xc8c8'b0b0'fcfcull,
0x0000'0000'8888ull, 0x2020'2020'9c9cull, 0x3c3c'3c3c'b0b0ull, 0x5858'5858'c0c0ull, 0x7070'7070'd0d0ull, 0x8484'8484'e0e0ull, 0x9c9c'9c9c'ececull, 0xb0b0'b0b0'fcfcull,
0x0000'0000'0000ull, 0x2828'2828'2828ull, 0x5050'5050'5050ull, 0x7474'7474'7474ull, 0x9494'9494'9494ull, 0xb4b4'b4b4'b4b4ull, 0xd0d0'd0d0'd0d0ull, 0xecec'ecec'ececull,
0x0000'0000'0000ull, 0x2828'2828'2828ull, 0x5050'5050'5050ull, 0x7474'7474'7474ull, 0x9494'9494'9494ull, 0xb4b4'b4b4'b4b4ull, 0xd0d0'd0d0'd0d0ull, 0xecec'ecec'ececull,
};
return Region::PAL() ? palColors[color & 0x7f] : ntscColors[color & 0x7f];
}
+147
View File
@@ -0,0 +1,147 @@
auto TIA::read(n8 address) -> n8 {
n8 value;
switch(address) {
case 0x00: value.bit(7) = collision.M0P1; value.bit(6) = collision.M0P0; return value; // CXM0P
case 0x01: value.bit(7) = collision.M1P0; value.bit(6) = collision.M1P1; return value; // CXM1P
case 0x02: value.bit(7) = collision.P0PF; value.bit(6) = collision.P0BL; return value; // CX0FB
case 0x03: value.bit(7) = collision.P1PF; value.bit(6) = collision.P1BL; return value; // CX1FB
case 0x04: value.bit(7) = collision.M0PF; value.bit(6) = collision.M0BL; return value; // CXM0FB
case 0x05: value.bit(7) = collision.M1PF; value.bit(6) = collision.M1BL; return value; // CXM1FB
case 0x06: value.bit(7) = collision.BLPF; return value; // CXBLPF
case 0x07: value.bit(7) = collision.P0P1; value.bit(6) = collision.M0M1; return value; // CXPPMM
case 0x0c: value.bit(7) = controllerPort1.read().bit(4); return value; // INPT4
case 0x0d: value.bit(7) = controllerPort2.read().bit(4); return value; // INPT5
}
debug(unimplemented, "[TIA] read ", hex(address));
return 0xff;
}
auto TIA::write(n8 address, n8 data) -> void {
switch(address) {
case 0x00: vsync(data.bit(1)); return; // VSYNC
case 0x01: writeQueue.add(address, data, 1); return; // VBLANK
case 0x02: cpu.rdyLine(0); return; // WSYNC
case 0x03: io.hcounter = 0; return; // RSYNC
case 0x04: player[0].size = data.bit(0,2); missile[0].size = data.bit(4, 5); return; // NUSIZ1
case 0x05: player[1].size = data.bit(0,2); missile[1].size = data.bit(4, 5); return; // NUSIZ1
case 0x06: io.p0Color = data.bit(1, 7); return; // COLUP0
case 0x07: io.p1Color = data.bit(1, 7); return; // COLUP1
case 0x08: io.fgColor = data.bit(1, 7); return; // COLUPF
case 0x09: io.bgColor = data.bit(1, 7); return; // COLUBK
case 0x0a: ctrlpf(data); return; // CTRLPF
case 0x0b: writeQueue.add(address, data, 1); return; // REFP0
case 0x0c: writeQueue.add(address, data, 1); return; // REFP1
case 0x0d: writeQueue.add(address, data, 6); return; // PF0
case 0x0e: writeQueue.add(address, data, 6); return; // PF1
case 0x0f: writeQueue.add(address, data, 6); return; // PF2
case 0x10: resp(0); return; // RESP0
case 0x11: resp(1); return; // RESP1
case 0x12: resm(0); return; // RESM0
case 0x13: resm(1); return; // RESM1
case 0x14: resbl(); return; // RESBL
case 0x15: audio[0].control = data; return; // AUDC0
case 0x16: audio[1].control = data; return; // AUDC1
case 0x17: audio[0].frequency = data; return; // AUDF0
case 0x18: audio[1].frequency = data; return; // AUDF1
case 0x19: audio[0].volume = data; return; // AUDV0
case 0x1a: audio[1].volume = data; return; // AUDV1
case 0x1b: writeQueue.add(address, data, 3); return; // GRP0
case 0x1c: writeQueue.add(address, data, 3); return; // GRP1
case 0x1d: writeQueue.add(address, data, 1); return; // ENAM0
case 0x1e: writeQueue.add(address, data, 1); return; // ENAM1
case 0x1f: writeQueue.add(address, data, 1); return; // ENAB1
case 0x20: writeQueue.add(address, data, 2); return; // HMP0
case 0x21: writeQueue.add(address, data, 2); return; // HMP1
case 0x22: writeQueue.add(address, data, 2); return; // HMM0
case 0x23: writeQueue.add(address, data, 2); return; // HMM1
case 0x24: writeQueue.add(address, data, 2); return; // HMBL
case 0x25: player[0].delay = data.bit(0); return; // VDELP0
case 0x26: player[1].delay = data.bit(0); return; // VDELP1
case 0x27: ball.delay = data.bit(0); return; // VDELBL
case 0x28: resmp(0, data); return; // RESMP0
case 0x29: resmp(1, data); return; // RESMP1
case 0x2a: writeQueue.add(address, data, 6); return; // HMOVE
case 0x2b: writeQueue.add(address, data, 2); return; // HMCLR
case 0x2c: cxclr(); return; // CXCLR
}
debug(unimplemented, "[TIA] write ",hex(address), " = ", hex(data));
}
auto TIA::vsync(n1 state) -> void {
if(io.vsync && !state) {
io.vcounter = 0;
frame();
}
io.vsync = state;
}
auto TIA::ctrlpf(n8 data) -> void {
playfield.mirror = data.bit(0);
playfield.scoreMode = data.bit(1);
playfield.priority = data.bit(2);
ball.size = data.bit(4, 5);
}
auto TIA::cxclr() -> void {
collision.M0P0 = 0;
collision.M0P1 = 0;
collision.M1P0 = 0;
collision.M1P1 = 0;
collision.P0PF = 0;
collision.P0BL = 0;
collision.P1PF = 0;
collision.P1BL = 0;
collision.M0PF = 0;
collision.M0BL = 0;
collision.M1PF = 0;
collision.M1BL = 0;
collision.BLPF = 0;
collision.P0P1 = 0;
collision.M0M1 = 0;
}
auto TIA::hmove() -> void {
for(auto& p : player) p.position = (p.position - p.offset + 160) % 160;
for(auto& m : missile) m.position = (m.position - m.offset + 160) % 160;
ball.position = (ball.position - ball.offset + 160) % 160;
io.hmoveTriggered = io.vcounter;
}
auto TIA::hmclr() -> void {
for(auto& p : player) p.offset = 0;
for(auto& m : missile) m.offset = 0;
ball.offset = 0;
}
auto TIA::grp(n1 index, n8 data) -> void {
player[index].graphics[0] = data;
player[!index].graphics[1] = player[!index].graphics[0];
if(index) ball.enable[1] = ball.enable[0];
}
auto TIA::resp(n1 index) -> void {
player[index].position = (io.hcounter < 68 ? 3 : io.hcounter - 68 + 8) % 160;
}
auto TIA::resm(n1 index) -> void {
missile[index].position = (io.hcounter < 68 ? 2 : io.hcounter - 68 + 7) % 160;
}
auto TIA::resmp(n1 index, n8 data) -> void {
missile[index].reset = data.bit(1);
if(missile[index].reset) {
auto offset = 3;
if(player[index].size == 5) offset = 6;
if(player[index].size == 7) offset = 10;
missile[index].position = player[index].position + offset;
}
}
auto TIA::resbl() -> void {
ball.position = (io.hcounter < 68 ? 2 : io.hcounter - 68 + 7);
}
+85
View File
@@ -0,0 +1,85 @@
//serialization.cpp
auto TIA::serialize(serializer& s) -> void {
Thread::serialize(s);
for(auto n : range(writeQueue.maxItems)) {
s(writeQueue.items[n].active);
s(writeQueue.items[n].address);
s(writeQueue.items[n].data);
s(writeQueue.items[n].delay);
}
s(io.vcounter);
s(io.hcounter);
s(io.hmoveTriggered);
s(io.vsync);
s(io.vblank);
s(io.bgColor);
s(io.p0Color);
s(io.p1Color);
s(io.fgColor);
s(playfield.graphics);
s(playfield.mirror);
s(playfield.scoreMode);
s(playfield.priority);
for(auto n : range(2)) {
s(player[n].graphics[0]);
s(player[n].graphics[1]);
s(player[n].reflect);
s(player[n].size);
s(player[n].offset);
s(player[n].position);
s(player[n].delay);
}
for(auto n : range(2)) {
s(missile[n].enable);
s(missile[n].reset);
s(missile[n].size);
s(missile[n].offset);
s(missile[n].position);
}
for(auto n : range(2)) {
s(ball.enable[0]);
s(ball.enable[1]);
s(ball.size);
s(ball.offset);
s(ball.position);
}
s(collision.M0P0);
s(collision.M0P1);
s(collision.M1P0);
s(collision.M1P1);
s(collision.P0PF);
s(collision.P0BL);
s(collision.P1PF);
s(collision.P1BL);
s(collision.M0PF);
s(collision.M0BL);
s(collision.M1PF);
s(collision.M1BL);
s(collision.BLPF);
s(collision.P0P1);
s(collision.M0M1);
for(auto n : range(26)) {
s(volume[n]);
}
for(auto n : range(2)) {
s(audio[n].enable);
s(audio[n].divCounter);
s(audio[n].noiseCounter);
s(audio[n].noiseFeedback);
s(audio[n].pulseCounter);
s(audio[n].pulseCounterPaused);
s(audio[n].pulseFeedback);
s(audio[n].volume);
s(audio[n].control);
s(audio[n].frequency);
}
}
+173
View File
@@ -0,0 +1,173 @@
#include <a26/a26.hpp>
namespace ares::Atari2600 {
TIA tia;
#include "audio.cpp"
#include "color.cpp"
#include "io.cpp"
#include "serialization.cpp"
#include "write-queue.cpp"
auto TIA::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("TIA");
screen = node->append<Node::Video::Screen>("Screen", 160, vlines());
screen->colors(1 << 7, {&TIA::color, this});
screen->setSize(160, vlines());
screen->setScale(2.0, 1.0);
screen->setAspect(1.0, 1.0);
screen->setViewport(0, 0, 160, vlines());
stream = node->append<Node::Audio::Stream>("Audio");
stream->setChannels(1);
stream->setFrequency(31'403);
stream->addHighPassFilter( 20.0, 1);
stream->addLowPassFilter (2840.0, 1);
}
auto TIA::unload() -> void {
screen->quit();
node->remove(screen);
screen.reset();
node.reset();
}
auto TIA::main() -> void {
scanline();
io.vcounter++;
io.hmoveTriggered = 255;
}
auto TIA::scanline() -> void {
cpu.rdyLine(1);
for(io.hcounter = 0; io.hcounter < 228; io.hcounter++) {
auto x = io.hcounter - 68;
if(x >= 0) pixel(x);
writeQueue.step();
runAudio();
step();
}
}
auto TIA::pixel(n8 x) -> void {
auto output = screen->pixels().data() + (io.vcounter * 160);
// 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)) {
output[x] = 0;
return;
}
output[x] = io.bgColor;
auto pf = runPlayfield(x);
auto bl = runBall(x);
auto p0 = runPlayer(x, 0);
auto p1 = runPlayer(x, 1);
auto m0 = runMissile(x, 0);
auto m1 = runMissile(x, 1);
if(!playfield.priority && (pf || bl)) output[x] = io.fgColor;
if(p1 || m1) output[x] = io.p1Color;
if(p0 || m0) output[x] = io.p0Color;
if(playfield.priority && (pf || bl)) output[x] = io.fgColor;
// Update Collision
if(m0 && p1) collision.M0P1 = 1;
if(m0 && p0) collision.M0P0 = 1;
if(m1 && p0) collision.M1P0 = 1;
if(m1 && p1) collision.M1P1 = 1;
if(p0 && pf) collision.P0PF = 1;
if(p0 && bl) collision.P0BL = 1;
if(p1 && pf) collision.P1PF = 1;
if(p1 && bl) collision.P1BL = 1;
if(m0 && pf) collision.M0PF = 1;
if(m0 && bl) collision.M0BL = 1;
if(m1 && pf) collision.M1PF = 1;
if(m1 && bl) collision.M1BL = 1;
if(bl && pf) collision.BLPF = 1;
if(p0 && p1) collision.P0P1 = 1;
if(m0 && m1) collision.M0M1 = 1;
}
auto TIA::runPlayfield(n8 x) -> n1 {
auto pos = x >> 2;
if(!playfield.mirror || pos < 20) return playfield.graphics.bit(pos % 20);
return playfield.graphics.bit(19 - (pos % 20));
}
auto TIA::runPlayer(n8 x, n1 index) -> n1 {
auto& player = this->player[index];
auto position = player.position;
// Handle player stretch capability
auto width = 8;
if(player.size == 5) width = 16;
if(player.size == 7) width = 32;
// Handle repeat capability
auto repeat = 1;
if(player.size == 1 || player.size == 2 || player.size == 4) repeat = 2;
if(player.size == 3 || player.size == 6) repeat = 3;
auto spacing = 8;
if(player.size == 2 || player.size == 6) spacing = 24;
if(player.size == 4 ) spacing = 56;
for(int i = 0; i < repeat; i++) {
if (x >= position && x < position + width) {
auto bit = player.reflect ? (x - position) / (width / 8) : 7 - ((x - position) / (width / 8));
return player.graphics[player.delay].bit(bit);
}
position = (position + (spacing + width)) % 160;
}
return 0;
}
auto TIA::runMissile(n8 x, n1 index) -> n1 {
auto& missile = this->missile[index];
if (!missile.enable || missile.reset) return 0;
const int missileSizes[4] = {1, 2, 4, 8};
return x >= missile.position && x < missile.position + missileSizes[missile.size];
}
auto TIA::runBall(n8 x) -> n1 {
if(!ball.enable[ball.delay]) return 0;
const int ballSizes[4] = {1, 2, 4, 8};
return x >= ball.position && x < ball.position + ballSizes[ball.size];
}
auto TIA::frame() -> void {
screen->frame();
scheduler.exit(Event::Frame);
}
auto TIA::step(u32 clocks) -> void {
Thread::step(clocks);
Thread::synchronize();
}
auto TIA::power(bool reset) -> void {
Thread::create(system.frequency(), {&TIA::main, this});
screen->power();
io = {};
playfield = {};
for(auto& p : player) p = {};
for(auto& m : missile) m = {};
ball = {};
collision = {};
for(auto n : range(writeQueue.maxItems)) writeQueue.items[n] = {};
for(u32 level : range(15)) {
volume[level] = pow(2, level * -2.0 / 6.0);
}
volume[15] = 0;
}
}
+147
View File
@@ -0,0 +1,147 @@
struct TIA : Thread {
Node::Object node;
Node::Video::Screen screen;
Node::Audio::Stream stream;
auto vlines() const -> u32 { return Region::NTSC() ? 262 : 312; }
//tia.cpp
auto load(Node::Object) -> void;
auto unload() -> void;
auto main() -> void;
auto step(u32 clocks = 1) -> void;
auto power(bool reset) -> void;
auto frame() -> void;
auto scanline() -> void;
auto pixel(n8 x) -> void;
auto runPlayfield(n8 x) -> n1;
auto runBall(n8 x) -> n1;
auto runPlayer(n8 x, n1 index) -> n1;
auto runMissile(n8 x, n1 index) -> n1;
auto runCollision() -> void;
//io.cpp
auto read(n8 address) -> n8;
auto write(n8 address, n8 data) -> void;
auto vsync(n1 state) -> void;
auto ctrlpf(n8 data) -> void;
auto cxclr() -> void;
auto hmove() -> void;
auto hmclr() -> void;
auto grp(n1 index, n8 data) -> void;
auto resp(n1 index) -> void;
auto resm(n1 index) -> void;
auto resmp(n1 index, n8 data) -> void;
auto resbl() -> void;
// audio.cpp
auto runAudio() -> void;
//color.cpp
auto color(n32) -> n64;
//serialization.cpp
auto serialize(serializer&) -> void;
// write-queue.cpp
struct WriteQueue {
inline auto add(u8 address, n8 data, i8 delay) -> void;
inline auto step() -> void;
struct QueueItem {
inline auto commit() -> void;
n1 active;
i8 address;
n8 data;
i8 delay;
};
QueueItem items[16];
const int maxItems = 16;
} writeQueue;
struct {
u9 vcounter;
u8 hcounter;
u8 hmoveTriggered;
n1 vsync;
n1 vblank;
n7 bgColor;
n7 p0Color;
n7 p1Color;
n7 fgColor;
} io;
struct {
n20 graphics;
n1 mirror;
n1 scoreMode;
n1 priority;
} playfield;
struct {
n8 graphics[2];
n1 reflect;
n3 size;
i4 offset;
n8 position;
n1 delay;
} player[2];
struct {
n1 enable;
n1 reset;
n2 size;
i4 offset;
n8 position;
} missile[2];
struct {
n1 enable[2];
n2 size;
i4 offset;
n1 delay;
n8 position;
} ball;
struct {
n1 M0P0;
n1 M0P1;
n1 M1P0;
n1 M1P1;
n1 P0PF;
n1 P0BL;
n1 P1PF;
n1 P1BL;
n1 M0PF;
n1 M0BL;
n1 M1PF;
n1 M1BL;
n1 BLPF;
n1 P0P1;
n1 M0M1;
} collision;
struct AudioChannel {
auto phase0() -> void;
auto phase1() -> u8;
n1 enable;
n8 divCounter;
n8 noiseCounter;
n1 noiseFeedback;
n8 pulseCounter;
n1 pulseCounterPaused;
n1 pulseFeedback;
n4 volume;
n4 control;
n5 frequency;
} audio[2];
f64 volume[16];
};
extern TIA tia;
+46
View File
@@ -0,0 +1,46 @@
auto TIA::WriteQueue::add(u8 address, n8 data, i8 delay) -> void {
for(auto n : range(maxItems)) {
if(!items[n].active) {
items[n].address = address;
items[n].data = data;
items[n].delay = delay;
items[n].active = 1;
return;
}
}
}
auto TIA::WriteQueue::step() -> void {
auto& items = tia.writeQueue.items;
for(auto n : range(maxItems)) {
if(items[n].active && --items[n].delay == 0) {
items[n].commit();
items[n].active = 0;
}
}
}
auto TIA::WriteQueue::QueueItem::commit() -> void {
switch(address) {
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
case 0x0e: for(auto n : range(8)) tia.playfield.graphics.bit(4 + n) = data.bit(7 - n); return; // PF1
case 0x0f: tia.playfield.graphics.bit(12, 19) = data; return; // PF2
case 0x1b: tia.grp(0, data); return; // GRP0
case 0x1c: tia.grp(1, data); return; // GRP1
case 0x1d: tia.missile[0].enable = data.bit(1); return; // ENAM0
case 0x1e: tia.missile[1].enable = data.bit(1); return; // ENAM1
case 0x1f: tia.ball.enable[0] = data.bit(1); return; // ENABL
case 0x20: tia.player[0].offset = data.bit(4, 7); return; // HMP0
case 0x21: tia.player[1].offset = data.bit(4, 7); return; // HMP1
case 0x22: tia.missile[0].offset = data.bit(4, 7); return; // HMM0
case 0x23: tia.missile[1].offset = data.bit(4, 7); return; // HMM1
case 0x24: tia.ball.offset = data.bit(4, 7); return; // HMBL
case 0x2a: tia.hmove(); return; // HMOVE
case 0x2b: tia.hmclr(); return; // HMCLR
}
debug(unimplemented, "[TIA] writeQueue: ",hex(address), " = ", hex(data));
}
+2 -2
View File
@@ -38,7 +38,7 @@ using namespace nall;
namespace ares {
static const string Name = "ares";
static const string Version = "128";
static const string Version = "129";
static const string Copyright = "ares team, Near";
static const string License = "ISC";
static const string LicenseURI = "https://opensource.org/licenses/ISC";
@@ -47,7 +47,7 @@ namespace ares {
//incremented only when serialization format changes
static const u32 SerializerSignature = 0x31545342; //"BST1" (little-endian)
static const string SerializerVersion = "128";
static const string SerializerVersion = "129";
namespace VFS {
using Pak = shared_pointer<vfs::directory>;
@@ -28,7 +28,7 @@ struct Instruction : Tracer {
}
auto address(u64 address) -> bool {
address &= (1ull << _addressBits) - 1; //mask upper bits of address
address &= ~0ull >> (64 - _addressBits); //mask upper bits of address
_address = address;
address >>= _addressMask; //clip unneeded alignment bits (to reduce _masks size)
@@ -57,7 +57,7 @@ struct Instruction : Tracer {
//call when writing to executable RAM to support self-modifying code.
auto invalidate(u64 address) -> void {
if(unlikely(_mask && updateMasks())) {
address &= (1ull << _addressBits) - 1;
address &= ~0ull >> (64 - _addressBits);
address >>= _addressMask;
_masks[address >> 3] &= ~(1 << (address & 7));
}
@@ -104,7 +104,7 @@ struct Instruction : Tracer {
protected:
auto updateMasks() -> bool {
auto size = 1ull << _addressBits >> _addressMask >> 3;
auto size = 1ull << (_addressBits - _addressMask - 3);
if(!_mask || !size) return _masks.reset(), false;
if(_masks.size() == size) return true;
_masks.reset();
+2 -1
View File
@@ -84,7 +84,8 @@ inline auto Thread::synchronize() -> void {
template<typename... P>
inline auto Thread::synchronize(Thread& thread, P&&... p) -> void {
//switching to another thread does not guarantee it will catch up before switching back.
while(thread.clock() < clock()) {
//make sure not to switch to threads that were destroyed during synchronization
while(thread.clock() < clock() && thread.handle()) {
//disable synchronization for auxiliary threads during scheduler synchronization.
//synchronization can begin inside of this while loop.
if(scheduler.synchronizing()) break;
+1 -1
View File
@@ -67,7 +67,7 @@ auto SH2::illegalInstruction() -> void {
push(SR);
push(PC);
jump(readLong(VBR + vector * 4) + 4);
debug(unusual, "[SH2] illegal instruction: 0x", hex(busReadWord(PC - 4), 4L));
debug(unusual, "[SH2] illegal instruction: 0x", hex(busReadWord(PC - 4), 4L), " @ 0x", hex(PC - 4));
}
auto SH2::illegalSlotInstruction() -> void {
@@ -133,6 +133,7 @@ auto SH2::WDT::serialize(serializer& s) -> void {
s(rstcsr.rste);
s(rstcsr.wovf);
s(wtcnt);
s(pendingIRQ);
}
auto SH2::UBC::serialize(serializer& s) -> void {
@@ -22,6 +22,13 @@ print("dreq1\n");
}
}
if(self->wdt.pendingIRQ) {
if(self->SR.I < ipra.wdtip) {
self->interrupt(ipra.wdtip, vcrwdt.witv);
self->wdt.pendingIRQ = 0;
}
}
if(self->sci.pendingTransmitEmptyIRQ) {
if(self->SR.I < iprb.sciip) {
self->interrupt(iprb.sciip, vcrb.stxv);
+16 -7
View File
@@ -675,17 +675,26 @@ auto SH2::internalWriteByte(u32 address, n8 data) -> void {
dmac.drcr[1] = data.bit(0,1);
return;
//WTCSR: watchdog timer control/status register
//WTCSR/WTCNT select
case 0xffff'fe80:
wdt.wtcsr.cks = data.bit(0,2);
wdt.wtcsr.tme = data.bit(5);
wdt.wtcsr.wtit = data.bit(6);
wdt.wtcsr.ovf &= data.bit(7);
wdt.select = data.bit(0,7);
return;
//WTCNT: watchdog timer counter
//WTCSR/WTCNT data
case 0xffff'fe81:
wdt.wtcnt = data.bit(0,7);
//WTCSR: watchdog timer control/status register
if(wdt.select == 0xa5) {
wdt.wtcsr.cks = data.bit(0,2);
wdt.wtcsr.tme = data.bit(5);
wdt.wtcsr.wtit = data.bit(6);
wdt.wtcsr.ovf &= data.bit(7);
if(!wdt.wtcsr.tme) wdt.wtcnt = 0;
}
//WTCNT: watchdog timer counter
if(wdt.select == 0x5a) {
wdt.wtcnt = data.bit(0,7);
}
return;
//RSTCSR: reset control/status register
+10 -2
View File
@@ -112,8 +112,8 @@
n7 fovv; //FRT overflow interrupt vector number
} vcrd;
struct VCRWDT { //vector number setting register WDT
n4 bcmv; //BSC compare match interrupt vector number
n4 witv; //WDT interval interrupt vector number
n7 bcmv; //BSC compare match interrupt vector number
n7 witv; //WDT interval interrupt vector number
} vcrwdt;
} intc;
@@ -210,6 +210,9 @@
//watchdog timer
struct WDT {
//timer.cpp
auto run() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
@@ -225,6 +228,11 @@
n1 wovf; //watchdog timer overflow flag
} rstcsr;
n8 wtcnt; //watchdog timer counter
//internal
n32 counter;
n1 pendingIRQ;
n8 select;
} wdt;
//user break controller
@@ -26,3 +26,18 @@ auto SH2::FRT::run() -> void {
}
}
}
auto SH2::WDT::run() -> void {
static constexpr u32 frequencies[8] = {2, 64, 128, 256, 512, 1024, 2096, 8192};
if(!wtcsr.tme) return;
if(wtcsr.wtit) return; // Watchdog mode currently unsupported
if(++counter >= frequencies[wtcsr.cks]) {
counter -= frequencies[wtcsr.cks];
if(++wtcnt == 0) {
wtcsr.ovf = 1;
pendingIRQ = 1;
}
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ auto PPU::DAC::run(u32 x, u32 y) -> n15 {
n15 color = above.color;
//color blending
if(active[SFX]) {
if(active[SFX] || (above.translucent && io.blendBelow[belowLayer])) {
if(above.translucent && io.blendBelow[belowLayer]) {
color = blend(above.color, eva, below.color, evb);
} else if(io.blendMode == 1 && io.blendAbove[aboveLayer] && io.blendBelow[belowLayer]) {
@@ -17,6 +17,10 @@ FightingPad::FightingPad(Node::Port parent) {
Thread::create(1'000'000, {&FightingPad::main, this});
}
FightingPad::~FightingPad() {
Thread::destroy();
}
auto FightingPad::main() -> void {
if(timeout) {
timeout--;
@@ -13,6 +13,7 @@ struct FightingPad : Controller, Thread {
Node::Input::Button start;
FightingPad(Node::Port);
~FightingPad();
auto main() -> void;
auto readData() -> n8 override;
auto writeData(n8 data) -> void override;
+2
View File
@@ -11,6 +11,8 @@ auto ControllerPort::load(Node::Object parent) -> void {
port->setType("Controller");
port->setHotSwappable(true);
port->setAllocate([&](auto name) { return allocate(name); });
port->setDisconnect([&] { return disconnect(); });
port->setSupported({"Control Pad", "Fighting Pad"});
}
+1 -1
View File
@@ -9,7 +9,7 @@ struct ControllerPort {
ControllerPort(string name);
auto connect(Node::Peripheral) -> void;
auto disconnect() -> void;
auto disconnect() -> void { device.reset(); }
auto readControl() -> n8 { return control; }
auto writeControl(n8 data) -> void { control = data; }
+1
View File
@@ -43,6 +43,7 @@ auto M32X::SH7604::main() -> void {
SH2::intc.run();
SH2::dmac.run();
SH2::frt.run();
SH2::wdt.run();
}
auto M32X::SH7604::step(u32 clocks) -> void {
+4 -2
View File
@@ -32,13 +32,15 @@ auto AI::main() -> void {
auto AI::sample() -> void {
if(io.dmaCount == 0) return stream->frame(0.0, 0.0);
io.dmaAddress[0].bit(13,23) += io.dmaAddressCarry;
auto data = rdram.ram.read<Word>(io.dmaAddress[0]);
auto left = s16(data >> 16);
auto right = s16(data >> 0);
stream->frame(left / 32768.0, right / 32768.0);
io.dmaAddress[0] += 4;
io.dmaLength [0] -= 4;
io.dmaAddress[0].bit(0,12) += 4;
io.dmaAddressCarry = io.dmaAddress[0].bit(0,12) == 0;
io.dmaLength[0] -= 4;
if(!io.dmaLength[0]) {
mi.raise(MI::IRQ::AI);
if(--io.dmaCount) {
+1
View File
@@ -36,6 +36,7 @@ struct AI : Thread, Memory::IO<AI> {
struct IO {
n1 dmaEnable;
n24 dmaAddress[2];
n1 dmaAddressCarry;
n18 dmaLength[2];
n2 dmaCount;
n14 dacRate;
+1
View File
@@ -6,6 +6,7 @@ auto AI::serialize(serializer& s) -> void {
s(io.dmaEnable);
s(io.dmaAddress);
s(io.dmaAddressCarry);
s(io.dmaLength);
s(io.dmaCount);
s(io.dacRate);
+1
View File
@@ -4,5 +4,6 @@ namespace ares::Nintendo64 {
#include "port.cpp"
#include "gamepad/gamepad.cpp"
#include "mouse/mouse.cpp"
}
+2
View File
@@ -3,9 +3,11 @@ struct Controller {
virtual ~Controller() = default;
virtual auto save() -> void {}
virtual auto comm(n8 send, n8 recv, n8 input[], n8 output[]) -> n2 { return 1; }
virtual auto read() -> n32 { return 0; }
virtual auto serialize(serializer&) -> void {}
};
#include "port.hpp"
#include "gamepad/gamepad.hpp"
#include "mouse/mouse.hpp"
+88
View File
@@ -83,6 +83,94 @@ auto Gamepad::rumble(bool enable) -> void {
platform->input(motor);
}
auto Gamepad::comm(n8 send, n8 recv, n8 input[], n8 output[]) -> n2 {
b1 valid = 0;
b1 over = 0;
//status
if(input[0] == 0x00 || input[0] == 0xff) {
output[0] = 0x05; //0x05 = gamepad; 0x02 = mouse
output[1] = 0x00;
output[2] = 0x02; //0x02 = nothing present in controller slot
if(ram || motor) {
output[2] = 0x01; //0x01 = pak present
}
valid = 1;
}
//read controller state
if(input[0] == 0x01) {
u32 data = read();
output[0] = data >> 24;
output[1] = data >> 16;
output[2] = data >> 8;
output[3] = data >> 0;
if(recv <= 4) {
over = 0;
} else {
over = 1;
}
valid = 1;
}
//read pak
if(input[0] == 0x02 && send >= 3 && recv >= 1) {
//controller pak
if(ram) {
u32 address = (input[1] << 8 | input[2] << 0) & ~31;
if(pif.addressCRC(address) == (n5)input[2]) {
for(u32 index : range(recv - 1)) {
output[index] = ram.read<Byte>(address++);
}
output[recv - 1] = pif.dataCRC({&output[0], recv - 1});
valid = 1;
}
}
//rumble pak
if(motor) {
u32 address = (input[1] << 8 | input[2] << 0) & ~31;
if(pif.addressCRC(address) == (n5)input[2]) {
for(u32 index : range(recv - 1)) {
output[index] = 0x80;
}
output[recv - 1] = pif.dataCRC({&output[0], recv - 1});
valid = 1;
}
}
}
//write pak
if(input[0] == 0x03 && send >= 3 && recv >= 1) {
//controller pak
if(ram) {
u32 address = (input[1] << 8 | input[2] << 0) & ~31;
if(pif.addressCRC(address) == (n5)input[2]) {
for(u32 index : range(send - 3)) {
ram.write<Byte>(address++, input[3 + index]);
}
output[0] = pif.dataCRC({&input[3], send - 3});
valid = 1;
}
}
//rumble pak
if(motor) {
u32 address = (input[1] << 8 | input[2] << 0) & ~31;
if(pif.addressCRC(address) == (n5)input[2]) {
output[0] = pif.dataCRC({&input[3], send - 3});
valid = 1;
rumble(input[3] & 1);
}
}
}
n2 status = 0;
status.bit(0) = valid;
status.bit(1) = over;
return status;
}
auto Gamepad::read() -> n32 {
platform->input(x);
platform->input(y);
+1
View File
@@ -29,6 +29,7 @@ struct Gamepad : Controller {
auto connect() -> void;
auto disconnect() -> void;
auto rumble(bool enable) -> void;
auto comm(n8 send, n8 recv, n8 input[], n8 output[]) -> n2 override;
auto read() -> n32 override;
auto formatControllerPak() -> void;
auto serialize(serializer&) -> void override;
+63
View File
@@ -0,0 +1,63 @@
Mouse::Mouse(Node::Port parent) {
node = parent->append<Node::Peripheral>("Mouse");
x = node->append<Node::Input::Axis> ("X");
y = node->append<Node::Input::Axis> ("Y");
left = node->append<Node::Input::Button>("Left");
right = node->append<Node::Input::Button>("Right");
}
Mouse::~Mouse() {
}
auto Mouse::comm(n8 send, n8 recv, n8 input[], n8 output[]) -> n2 {
b1 valid = 0;
b1 over = 0;
//status
if(input[0] == 0x00 || input[0] == 0xff) {
output[0] = 0x02; //0x05 = gamepad; 0x02 = mouse
output[1] = 0x00;
output[2] = 0x02; //0x02 = nothing present in controller slot
valid = 1;
}
//read controller state
if(input[0] == 0x01) {
u32 data = read();
output[0] = data >> 24;
output[1] = data >> 16;
output[2] = data >> 8;
output[3] = data >> 0;
if(recv <= 4) {
over = 0;
} else {
over = 1;
}
valid = 1;
}
n2 status = 0;
status.bit(0) = valid;
status.bit(1) = over;
return status;
}
auto Mouse::read() -> n32 {
platform->input(x);
platform->input(y);
platform->input(left);
platform->input(right);
//TODO: Deal with more reasonable values for Mouse axis
auto ax = x->value();
auto ay = -y->value();
n32 data;
data.byte(0) = ay;
data.byte(1) = ax;
data.bit(30) = right->value();
data.bit(31) = left->value();
return data;
}
+12
View File
@@ -0,0 +1,12 @@
struct Mouse : Controller {
Node::Input::Axis x;
Node::Input::Axis y;
Node::Input::Button left;
Node::Input::Button right;
Mouse(Node::Port);
~Mouse();
auto comm(n8 send, n8 recv, n8 input[], n8 output[]) -> n2 override;
auto read() -> n32 override;
};
+2 -1
View File
@@ -12,7 +12,7 @@ auto ControllerPort::load(Node::Object parent) -> void {
port->setType("Controller");
port->setHotSwappable(true);
port->setAllocate([&](auto name) { return allocate(name); });
port->setSupported({"Gamepad"});
port->setSupported({"Gamepad", "Mouse"});
}
auto ControllerPort::unload() -> void {
@@ -26,6 +26,7 @@ auto ControllerPort::save() -> void {
auto ControllerPort::allocate(string name) -> Node::Peripheral {
if(name == "Gamepad") device = new Gamepad(port);
if(name == "Mouse" ) device = new Mouse(port);
if(device) return device->node;
return {};
}
+3 -1
View File
@@ -18,7 +18,8 @@ auto CPU::Context::setMode() -> void {
break;
}
if(bits == 32 || bits == 64) {
if(bits == 32) {
physMask = 0x1fff'ffff;
segment[0] = Segment::Mapped;
segment[1] = Segment::Mapped;
segment[2] = Segment::Mapped;
@@ -47,6 +48,7 @@ auto CPU::Context::setMode() -> void {
}
if(bits == 64) {
physMask = 0x7fff'ffff;
for(auto n : range(8))
switch(mode) {
case Mode::Kernel:
+3 -3
View File
@@ -52,9 +52,9 @@ auto CPU::synchronize() -> void {
queue.step(clocks, [](u32 event) {
switch(event) {
case Queue::RSP_DMA: return rsp.dmaTransfer();
case Queue::PI_DMA_Read: return pi.dmaRead();
case Queue::PI_DMA_Write: return pi.dmaWrite();
case Queue::RSP_DMA: return rsp.dmaTransferStep();
case Queue::PI_DMA_Read: return pi.dmaFinished();
case Queue::PI_DMA_Write: return pi.dmaFinished();
case Queue::PI_BUS_Write: return pi.writeFinished();
case Queue::SI_DMA_Read: return si.dmaRead();
case Queue::SI_DMA_Write: return si.dmaWrite();
+18 -15
View File
@@ -95,6 +95,7 @@ struct CPU : Thread {
auto setMode() -> void;
bool endian;
u64 physMask;
u32 mode;
u32 bits;
u32 segment[8]; //512_MiB chunks
@@ -167,9 +168,8 @@ struct CPU : Thread {
};
//tlb.cpp
auto load(u32 address) -> Match;
auto store(u32 address) -> Match;
auto exception(u32 address) -> void;
auto load(u64 vaddr) -> Match;
auto store(u64 vaddr) -> Match;
struct Entry {
//scc-tlb.cpp
@@ -195,20 +195,20 @@ struct CPU : Thread {
} tlb{*this};
//memory.cpp
auto kernelSegment32(u32 address) const -> Context::Segment;
auto supervisorSegment32(u32 address) const -> Context::Segment;
auto userSegment32(u32 address) const -> Context::Segment;
auto kernelSegment32(u32 vaddr) const -> Context::Segment;
auto supervisorSegment32(u32 vaddr) const -> Context::Segment;
auto userSegment32(u32 vaddr) const -> Context::Segment;
auto kernelSegment64(u64 address) const -> Context::Segment;
auto supervisorSegment64(u64 address) const -> Context::Segment;
auto userSegment64(u64 address) const -> Context::Segment;
auto kernelSegment64(u64 vaddr) const -> Context::Segment;
auto supervisorSegment64(u64 vaddr) const -> Context::Segment;
auto userSegment64(u64 vaddr) const -> Context::Segment;
auto segment(u64 address) -> Context::Segment;
auto devirtualize(u64 address) -> maybe<u64>;
auto fetch(u64 address) -> u32;
template<u32 Size> auto read(u64 address) -> maybe<u64>;
template<u32 Size> auto write(u64 address, u64 data) -> bool;
auto addressException(u64 address) -> void;
auto segment(u64 vaddr) -> Context::Segment;
auto devirtualize(u64 vaddr) -> maybe<u64>;
auto fetch(u64 vaddr) -> u32;
template<u32 Size> auto read(u64 vaddr) -> maybe<u64>;
template<u32 Size> auto write(u64 vaddr, u64 data) -> bool;
auto addressException(u64 vaddr) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
@@ -522,6 +522,9 @@ struct CPU : Thread {
//30: Error Exception Program Counter
n64 epcError;
//other
n64 latch;
} scc;
//interpreter-scc.cpp
+3 -3
View File
@@ -1,12 +1,12 @@
auto CPU::DataCache::Line::hit(u32 address) const -> bool {
return valid && tag == (address & ~0xe000'0fff);
return valid && tag == (address & ~0x0000'0fff);
}
template<u32 Size> auto CPU::DataCache::Line::fill(u32 address, u64 data) -> void {
cpu.step(40);
valid = 1;
dirty = 1;
tag = address & ~0xe000'0fff;
tag = address & ~0x0000'0fff;
//read words according to critical doubleword first scheme
switch(address & 8) {
case 0:
@@ -34,7 +34,7 @@ auto CPU::DataCache::Line::fill(u32 address) -> void {
cpu.step(40);
valid = 1;
dirty = 0;
tag = address & ~0xe000'0fff;
tag = address & ~0x0000'0fff;
//read words according to critical doubleword first scheme
switch(address & 8) {
case 0:
+3 -3
View File
@@ -1,11 +1,11 @@
auto CPU::InstructionCache::Line::hit(u32 address) const -> bool {
return valid && tag == (address & ~0xe000'0fff);
return valid && tag == (address & ~0x0000'0fff);
}
auto CPU::InstructionCache::Line::fill(u32 address) -> void {
cpu.step(48);
valid = 1;
tag = address & ~0xe000'0fff;
tag = address & ~0x0000'0fff;
words[0] = bus.read<Word>(tag | index | 0x00);
words[1] = bus.read<Word>(tag | index | 0x04);
words[2] = bus.read<Word>(tag | index | 0x08);
@@ -42,7 +42,7 @@ auto CPU::InstructionCache::step(u32 address) -> void {
if(!line.hit(address)) {
cpu.step(48);
line.valid = 1;
line.tag = address & ~0xe000'0fff;
line.tag = address & ~0x0000'0fff;
} else {
cpu.step(2);
}
+193 -188
View File
@@ -119,8 +119,8 @@ auto CPU::BREAK() -> void {
}
auto CPU::CACHE(u8 operation, cr64& rs, s16 imm) -> void {
u32 address = rs.u64 + imm;
if (auto phys = devirtualize(address)) address = *phys;
u32 address;
if (auto phys = devirtualize(rs.u64 + imm)) address = *phys;
else return;
switch(operation) {
@@ -394,88 +394,88 @@ auto CPU::LD(r64& rt, cr64& rs, s16 imm) -> void {
auto CPU::LDL(r64& rt, cr64& rs, s16 imm) -> void {
if(!context.kernelMode() && context.bits == 32) return exception.reservedInstruction();
u64 address = rs.u64 + imm;
u64 vaddr = rs.u64 + imm;
u64 data = rt.u64;
if(context.littleEndian())
switch(address & 7) {
switch(vaddr & 7) {
case 0:
data &= 0x00ffffffffffffffull;
if(auto byte = read<Byte>(address & ~7 | 7)) data |= byte() << 56; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 7)) data |= byte() << 56; else return;
break;
case 1:
data &= 0x0000ffffffffffffull;
if(auto half = read<Half>(address & ~7 | 6)) data |= half() << 48; else return;
if(auto half = read<Half>(vaddr & ~7 | 6)) data |= half() << 48; else return;
break;
case 2:
data &= 0x000000ffffffffffull;
if(auto byte = read<Byte>(address & ~7 | 5)) data |= byte() << 56; else return;
if(auto half = read<Half>(address & ~7 | 6)) data |= half() << 40; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 5)) data |= byte() << 56; else return;
if(auto half = read<Half>(vaddr & ~7 | 6)) data |= half() << 40; else return;
break;
case 3:
data &= 0x00000000ffffffffull;
if(auto word = read<Word>(address & ~7 | 4)) data |= word() << 32; else return;
if(auto word = read<Word>(vaddr & ~7 | 4)) data |= word() << 32; else return;
break;
case 4:
data &= 0x0000000000ffffffull;
if(auto byte = read<Byte>(address & ~7 | 3)) data |= byte() << 56; else return;
if(auto word = read<Word>(address & ~7 | 4)) data |= word() << 24; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 3)) data |= byte() << 56; else return;
if(auto word = read<Word>(vaddr & ~7 | 4)) data |= word() << 24; else return;
break;
case 5:
data &= 0x000000000000ffffull;
if(auto half = read<Half>(address & ~7 | 2)) data |= half() << 48; else return;
if(auto word = read<Word>(address & ~7 | 4)) data |= word() << 16; else return;
if(auto half = read<Half>(vaddr & ~7 | 2)) data |= half() << 48; else return;
if(auto word = read<Word>(vaddr & ~7 | 4)) data |= word() << 16; else return;
break;
case 6:
data &= 0x00000000000000ffull;
if(auto byte = read<Byte>(address & ~7 | 1)) data |= byte() << 56; else return;
if(auto half = read<Half>(address & ~7 | 2)) data |= half() << 40; else return;
if(auto word = read<Word>(address & ~7 | 4)) data |= word() << 8; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 1)) data |= byte() << 56; else return;
if(auto half = read<Half>(vaddr & ~7 | 2)) data |= half() << 40; else return;
if(auto word = read<Word>(vaddr & ~7 | 4)) data |= word() << 8; else return;
break;
case 7:
data &= 0x0000000000000000ull;
if(auto dual = read<Dual>(address & ~7 | 0)) data |= dual() << 0; else return;
if(auto dual = read<Dual>(vaddr & ~7 | 0)) data |= dual() << 0; else return;
break;
}
if(context.bigEndian())
switch(address & 7) {
switch(vaddr & 7) {
case 0:
data &= 0x0000000000000000ull;
if(auto dual = read<Dual>(address & ~7 | 0)) data |= dual() << 0; else return;
if(auto dual = read<Dual>(vaddr & ~7 | 0)) data |= dual() << 0; else return;
break;
case 1:
data &= 0x00000000000000ffull;
if(auto byte = read<Byte>(address & ~7 | 1)) data |= byte() << 56; else return;
if(auto half = read<Half>(address & ~7 | 2)) data |= half() << 40; else return;
if(auto word = read<Word>(address & ~7 | 4)) data |= word() << 8; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 1)) data |= byte() << 56; else return;
if(auto half = read<Half>(vaddr & ~7 | 2)) data |= half() << 40; else return;
if(auto word = read<Word>(vaddr & ~7 | 4)) data |= word() << 8; else return;
break;
case 2:
data &= 0x000000000000ffffull;
if(auto half = read<Half>(address & ~7 | 2)) data |= half() << 48; else return;
if(auto word = read<Word>(address & ~7 | 4)) data |= word() << 16; else return;
if(auto half = read<Half>(vaddr & ~7 | 2)) data |= half() << 48; else return;
if(auto word = read<Word>(vaddr & ~7 | 4)) data |= word() << 16; else return;
break;
case 3:
data &= 0x0000000000ffffffull;
if(auto byte = read<Byte>(address & ~7 | 3)) data |= byte() << 56; else return;
if(auto word = read<Word>(address & ~7 | 4)) data |= word() << 24; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 3)) data |= byte() << 56; else return;
if(auto word = read<Word>(vaddr & ~7 | 4)) data |= word() << 24; else return;
break;
case 4:
data &= 0x00000000ffffffffull;
if(auto word = read<Word>(address & ~7 | 4)) data |= word() << 32; else return;
if(auto word = read<Word>(vaddr & ~7 | 4)) data |= word() << 32; else return;
break;
case 5:
data &= 0x000000ffffffffffull;
if(auto byte = read<Byte>(address & ~7 | 5)) data |= byte() << 56; else return;
if(auto half = read<Half>(address & ~7 | 6)) data |= half() << 40; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 5)) data |= byte() << 56; else return;
if(auto half = read<Half>(vaddr & ~7 | 6)) data |= half() << 40; else return;
break;
case 6:
data &= 0x0000ffffffffffffull;
if(auto half = read<Half>(address & ~7 | 6)) data |= half() << 48; else return;
if(auto half = read<Half>(vaddr & ~7 | 6)) data |= half() << 48; else return;
break;
case 7:
data &= 0x00ffffffffffffffull;
if(auto byte = read<Byte>(address & ~7 | 7)) data |= byte() << 56; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 7)) data |= byte() << 56; else return;
break;
}
@@ -484,88 +484,88 @@ auto CPU::LDL(r64& rt, cr64& rs, s16 imm) -> void {
auto CPU::LDR(r64& rt, cr64& rs, s16 imm) -> void {
if(!context.kernelMode() && context.bits == 32) return exception.reservedInstruction();
u64 address = rs.u64 + imm;
u64 vaddr = rs.u64 + imm;
u64 data = rt.u64;
if(context.littleEndian())
switch(address & 7) {
switch(vaddr & 7) {
case 0:
data &= 0x0000000000000000ull;
if(auto dual = read<Dual>(address & ~7 | 0)) data |= dual() << 0; else return;
if(auto dual = read<Dual>(vaddr & ~7 | 0)) data |= dual() << 0; else return;
break;
case 1:
data &= 0xff00000000000000ull;
if(auto word = read<Word>(address & ~7 | 0)) data |= word() << 24; else return;
if(auto half = read<Half>(address & ~7 | 4)) data |= half() << 8; else return;
if(auto byte = read<Byte>(address & ~7 | 6)) data |= byte() << 0; else return;
if(auto word = read<Word>(vaddr & ~7 | 0)) data |= word() << 24; else return;
if(auto half = read<Half>(vaddr & ~7 | 4)) data |= half() << 8; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 6)) data |= byte() << 0; else return;
break;
case 2:
data &= 0xffff000000000000ull;
if(auto word = read<Word>(address & ~7 | 0)) data |= word() << 16; else return;
if(auto half = read<Half>(address & ~7 | 4)) data |= half() << 0; else return;
if(auto word = read<Word>(vaddr & ~7 | 0)) data |= word() << 16; else return;
if(auto half = read<Half>(vaddr & ~7 | 4)) data |= half() << 0; else return;
break;
case 3:
data &= 0xffffff0000000000ull;
if(auto word = read<Word>(address & ~7 | 0)) data |= word() << 8; else return;
if(auto byte = read<Byte>(address & ~7 | 4)) data |= byte() << 0; else return;
if(auto word = read<Word>(vaddr & ~7 | 0)) data |= word() << 8; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 4)) data |= byte() << 0; else return;
break;
case 4:
data &= 0xffffffff00000000ull;
if(auto word = read<Word>(address & ~7 | 0)) data |= word() << 0; else return;
if(auto word = read<Word>(vaddr & ~7 | 0)) data |= word() << 0; else return;
break;
case 5:
data &= 0xffffffffff000000ull;
if(auto half = read<Half>(address & ~7 | 0)) data |= half() << 8; else return;
if(auto byte = read<Byte>(address & ~7 | 2)) data |= byte() << 0; else return;
if(auto half = read<Half>(vaddr & ~7 | 0)) data |= half() << 8; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 2)) data |= byte() << 0; else return;
break;
case 6:
data &= 0xffffffffffff0000ull;
if(auto half = read<Half>(address & ~7 | 0)) data |= half() << 0; else return;
if(auto half = read<Half>(vaddr & ~7 | 0)) data |= half() << 0; else return;
break;
case 7:
data &= 0xffffffffffffff00ull;
if(auto byte = read<Byte>(address & ~7 | 0)) data |= byte() << 0; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 0)) data |= byte() << 0; else return;
break;
}
if(context.bigEndian())
switch(address & 7) {
switch(vaddr & 7) {
case 0:
data &= 0xffffffffffffff00ull;
if(auto byte = read<Byte>(address & ~7 | 0)) data |= byte() << 0; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 0)) data |= byte() << 0; else return;
break;
case 1:
data &= 0xffffffffffff0000ull;
if(auto half = read<Half>(address & ~7 | 0)) data |= half() << 0; else return;
if(auto half = read<Half>(vaddr & ~7 | 0)) data |= half() << 0; else return;
break;
case 2:
data &= 0xffffffffff000000ull;
if(auto half = read<Half>(address & ~7 | 0)) data |= half() << 8; else return;
if(auto byte = read<Byte>(address & ~7 | 2)) data |= byte() << 0; else return;
if(auto half = read<Half>(vaddr & ~7 | 0)) data |= half() << 8; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 2)) data |= byte() << 0; else return;
break;
case 3:
data &= 0xffffffff00000000ull;
if(auto word = read<Word>(address & ~7 | 0)) data |= word() << 0; else return;
if(auto word = read<Word>(vaddr & ~7 | 0)) data |= word() << 0; else return;
break;
case 4:
data &= 0xffffff0000000000ull;
if(auto word = read<Word>(address & ~7 | 0)) data |= word() << 8; else return;
if(auto byte = read<Byte>(address & ~7 | 4)) data |= byte() << 0; else return;
if(auto word = read<Word>(vaddr & ~7 | 0)) data |= word() << 8; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 4)) data |= byte() << 0; else return;
break;
case 5:
data &= 0xffff000000000000ull;
if(auto word = read<Word>(address & ~7 | 0)) data |= word() << 16; else return;
if(auto half = read<Half>(address & ~7 | 4)) data |= half() << 0; else return;
if(auto word = read<Word>(vaddr & ~7 | 0)) data |= word() << 16; else return;
if(auto half = read<Half>(vaddr & ~7 | 4)) data |= half() << 0; else return;
break;
case 6:
data &= 0xff00000000000000ull;
if(auto word = read<Word>(address & ~7 | 0)) data |= word() << 24; else return;
if(auto half = read<Half>(address & ~7 | 4)) data |= half() << 8; else return;
if(auto byte = read<Byte>(address & ~7 | 6)) data |= byte() << 0; else return;
if(auto word = read<Word>(vaddr & ~7 | 0)) data |= word() << 24; else return;
if(auto half = read<Half>(vaddr & ~7 | 4)) data |= half() << 8; else return;
if(auto byte = read<Byte>(vaddr & ~7 | 6)) data |= byte() << 0; else return;
break;
case 7:
data &= 0x0000000000000000ull;
if(auto dual = read<Dual>(address & ~7 | 0)) data |= dual() << 0; else return;
if(auto dual = read<Dual>(vaddr & ~7 | 0)) data |= dual() << 0; else return;
break;
}
@@ -582,9 +582,9 @@ auto CPU::LHU(r64& rt, cr64& rs, s16 imm) -> void {
auto CPU::LL(r64& rt, cr64& rs, s16 imm) -> void {
if(auto address = devirtualize(rs.u64 + imm)) {
if (auto data = read<Word>(*address)) {
if (auto data = read<Word>(rs.u64 + imm)) {
rt.u64 = s32(*data);
scc.ll = (*address & 0x1fff'ffff) >> 4;
scc.ll = *address >> 4;
scc.llbit = 1;
}
}
@@ -593,9 +593,9 @@ auto CPU::LL(r64& rt, cr64& rs, s16 imm) -> void {
auto CPU::LLD(r64& rt, cr64& rs, s16 imm) -> void {
if(!context.kernelMode() && context.bits == 32) return exception.reservedInstruction();
if(auto address = devirtualize(rs.u64 + imm)) {
if (auto data = read<Dual>(*address)) {
if (auto data = read<Dual>(rs.u64 + imm)) {
rt.u64 = *data;
scc.ll = (*address & 0x1fff'ffff) >> 4;
scc.ll = *address >> 4;
scc.llbit = 1;
}
}
@@ -610,110 +610,119 @@ auto CPU::LW(r64& rt, cr64& rs, s16 imm) -> void {
}
auto CPU::LWL(r64& rt, cr64& rs, s16 imm) -> void {
u64 address = rs.u64 + imm;
u64 vaddr = rs.u64 + imm;
u32 data = rt.u32;
auto mem = read<Word>(vaddr & ~3);
if (!mem) return;
if(context.littleEndian())
switch(address & 3) {
switch(vaddr & 3) {
case 0:
data &= 0x00ffffff;
if(auto byte = read<Byte>(address & ~3 | 3)) data |= byte() << 24; else return;
*mem <<= 24;
break;
case 1:
data &= 0x0000ffff;
if(auto half = read<Half>(address & ~3 | 2)) data |= half() << 16; else return;
*mem <<= 16;
break;
case 2:
data &= 0x000000ff;
if(auto byte = read<Byte>(address & ~3 | 1)) data |= byte() << 24; else return;
if(auto half = read<Half>(address & ~3 | 2)) data |= half() << 8; else return;
*mem <<= 8;
break;
case 3:
data &= 0x00000000;
if(auto word = read<Word>(address & ~3 | 0)) data |= word() << 0; else return;
*mem <<= 0;
break;
}
if(context.bigEndian())
switch(address & 3) {
switch(vaddr & 3) {
case 0:
data &= 0x00000000;
if(auto word = read<Word>(address & ~3 | 0)) data |= word() << 0; else return;
*mem <<= 0;
break;
case 1:
data &= 0x000000ff;
if(auto byte = read<Byte>(address & ~3 | 1)) data |= byte() << 24; else return;
if(auto half = read<Half>(address & ~3 | 2)) data |= half() << 8; else return;
*mem <<= 8;
break;
case 2:
data &= 0x0000ffff;
if(auto half = read<Half>(address & ~3 | 2)) data |= half() << 16; else return;
*mem <<= 16;
break;
case 3:
data &= 0x00ffffff;
if(auto byte = read<Byte>(address & ~3 | 3)) data |= byte() << 24; else return;
*mem <<= 24;
break;
}
data |= *mem;
rt.s64 = (s32)data;
}
auto CPU::LWR(r64& rt, cr64& rs, s16 imm) -> void {
u64 address = rs.u64 + imm;
u64 vaddr = rs.u64 + imm;
u32 data = rt.u32;
auto mem = read<Word>(vaddr & ~3);
if (!mem) return;
if(context.littleEndian())
switch(address & 3) {
switch(vaddr & 3) {
case 0:
data &= 0x00000000;
if(auto word = read<Word>(address & ~3 | 0)) data |= word() << 0; else return;
*mem >>= 0;
data |= *mem;
rt.s64 = (s32)data;
break;
case 1:
data &= 0xff000000;
if(auto half = read<Half>(address & ~3 | 0)) data |= half() << 8; else return;
if(auto byte = read<Byte>(address & ~3 | 2)) data |= byte() << 0; else return;
*mem >>= 8;
data |= *mem;
if(context.bits == 32) rt.u32 = data;
if(context.bits == 64) rt.s64 = (s32)data;
break;
case 2:
data &= 0xffff0000;
if(auto half = read<Half>(address & ~3 | 0)) data |= half() << 0; else return;
*mem >>= 16;
data |= *mem;
if(context.bits == 32) rt.u32 = data;
if(context.bits == 64) rt.s64 = (s32)data;
break;
case 3:
data &= 0xffffff00;
if(auto byte = read<Byte>(address & ~3 | 0)) data |= byte() << 0; else return;
*mem >>= 24;
data |= *mem;
if(context.bits == 32) rt.u32 = data;
if(context.bits == 64) rt.s64 = (s32)data;
break;
}
if(context.bigEndian())
switch(address & 3) {
switch(vaddr & 3) {
case 0:
data &= 0xffffff00;
if(auto byte = read<Byte>(address & ~3 | 0)) data |= byte() << 0; else return;
*mem >>= 24;
data |= *mem;
if(context.bits == 32) rt.u32 = data;
if(context.bits == 64) rt.s64 = (s32)data;
break;
case 1:
data &= 0xffff0000;
if(auto half = read<Half>(address & ~3 | 0)) data |= half() << 0; else return;
*mem >>= 16;
data |= *mem;
if(context.bits == 32) rt.u32 = data;
if(context.bits == 64) rt.s64 = (s32)data;
break;
case 2:
data &= 0xff000000;
if(auto half = read<Half>(address & ~3 | 0)) data |= half() << 8; else return;
if(auto byte = read<Byte>(address & ~3 | 2)) data |= byte() << 0; else return;
*mem >>= 8;
data |= *mem;
if(context.bits == 32) rt.u32 = data;
if(context.bits == 64) rt.s64 = (s32)data;
break;
case 3:
data &= 0x00000000;
if(auto word = read<Word>(address & ~3 | 0)) data |= word() << 0; else return;
*mem >>= 0;
data |= *mem;
rt.s64 = (s32)data;
break;
}
@@ -770,25 +779,21 @@ auto CPU::SB(cr64& rt, cr64& rs, s16 imm) -> void {
}
auto CPU::SC(r64& rt, cr64& rs, s16 imm) -> void {
if(auto address = devirtualize(rs.u64 + imm)) {
if(scc.llbit) {
scc.llbit = 0;
rt.u64 = write<Word>(*address, rt.u32);
} else {
rt.u64 = 0;
}
if(scc.llbit) {
scc.llbit = 0;
rt.u64 = write<Word>(rs.u64 + imm, rt.u32);
} else {
rt.u64 = 0;
}
}
auto CPU::SCD(r64& rt, cr64& rs, s16 imm) -> void {
if(!context.kernelMode() && context.bits == 32) return exception.reservedInstruction();
if(auto address = devirtualize(rs.u64 + imm)) {
if(scc.llbit) {
scc.llbit = 0;
rt.u64 = write<Dual>(*address, rt.u64);
} else {
rt.u64 = 0;
}
if(scc.llbit) {
scc.llbit = 0;
rt.u64 = write<Dual>(rs.u64 + imm, rt.u64);
} else {
rt.u64 = 0;
}
}
@@ -799,144 +804,144 @@ auto CPU::SD(cr64& rt, cr64& rs, s16 imm) -> void {
auto CPU::SDL(cr64& rt, cr64& rs, s16 imm) -> void {
if(!context.kernelMode() && context.bits == 32) return exception.reservedInstruction();
u64 address = rs.u64 + imm;
u64 vaddr = rs.u64 + imm;
u64 data = rt.u64;
if(context.littleEndian())
switch(address & 7) {
switch(vaddr & 7) {
case 0:
if(!write<Byte>(address & ~7 | 7, data >> 56)) return;
if(!write<Byte>(vaddr & ~7 | 7, data >> 56)) return;
break;
case 1:
if(!write<Half>(address & ~7 | 6, data >> 48)) return;
if(!write<Half>(vaddr & ~7 | 6, data >> 48)) return;
break;
case 2:
if(!write<Byte>(address & ~7 | 5, data >> 56)) return;
if(!write<Half>(address & ~7 | 6, data >> 40)) return;
if(!write<Byte>(vaddr & ~7 | 5, data >> 56)) return;
if(!write<Half>(vaddr & ~7 | 6, data >> 40)) return;
break;
case 3:
if(!write<Word>(address & ~7 | 4, data >> 32)) return;
if(!write<Word>(vaddr & ~7 | 4, data >> 32)) return;
break;
case 4:
if(!write<Byte>(address & ~7 | 3, data >> 56)) return;
if(!write<Word>(address & ~7 | 4, data >> 24)) return;
if(!write<Byte>(vaddr & ~7 | 3, data >> 56)) return;
if(!write<Word>(vaddr & ~7 | 4, data >> 24)) return;
break;
case 5:
if(!write<Half>(address & ~7 | 2, data >> 48)) return;
if(!write<Word>(address & ~7 | 4, data >> 16)) return;
if(!write<Half>(vaddr & ~7 | 2, data >> 48)) return;
if(!write<Word>(vaddr & ~7 | 4, data >> 16)) return;
break;
case 6:
if(!write<Byte>(address & ~7 | 1, data >> 56)) return;
if(!write<Half>(address & ~7 | 2, data >> 40)) return;
if(!write<Word>(address & ~7 | 4, data >> 8)) return;
if(!write<Byte>(vaddr & ~7 | 1, data >> 56)) return;
if(!write<Half>(vaddr & ~7 | 2, data >> 40)) return;
if(!write<Word>(vaddr & ~7 | 4, data >> 8)) return;
break;
case 7:
if(!write<Dual>(address & ~7 | 0, data >> 0)) return;
if(!write<Dual>(vaddr & ~7 | 0, data >> 0)) return;
break;
}
if(context.bigEndian())
switch(address & 7) {
switch(vaddr & 7) {
case 0:
if(!write<Dual>(address & ~7 | 0, data >> 0)) return;
if(!write<Dual>(vaddr & ~7 | 0, data >> 0)) return;
break;
case 1:
if(!write<Byte>(address & ~7 | 1, data >> 56)) return;
if(!write<Half>(address & ~7 | 2, data >> 40)) return;
if(!write<Word>(address & ~7 | 4, data >> 8)) return;
if(!write<Byte>(vaddr & ~7 | 1, data >> 56)) return;
if(!write<Half>(vaddr & ~7 | 2, data >> 40)) return;
if(!write<Word>(vaddr & ~7 | 4, data >> 8)) return;
break;
case 2:
if(!write<Half>(address & ~7 | 2, data >> 48)) return;
if(!write<Word>(address & ~7 | 4, data >> 16)) return;
if(!write<Half>(vaddr & ~7 | 2, data >> 48)) return;
if(!write<Word>(vaddr & ~7 | 4, data >> 16)) return;
break;
case 3:
if(!write<Byte>(address & ~7 | 3, data >> 56)) return;
if(!write<Word>(address & ~7 | 4, data >> 24)) return;
if(!write<Byte>(vaddr & ~7 | 3, data >> 56)) return;
if(!write<Word>(vaddr & ~7 | 4, data >> 24)) return;
break;
case 4:
if(!write<Word>(address & ~7 | 4, data >> 32)) return;
if(!write<Word>(vaddr & ~7 | 4, data >> 32)) return;
break;
case 5:
if(!write<Byte>(address & ~7 | 5, data >> 56)) return;
if(!write<Half>(address & ~7 | 6, data >> 40)) return;
if(!write<Byte>(vaddr & ~7 | 5, data >> 56)) return;
if(!write<Half>(vaddr & ~7 | 6, data >> 40)) return;
break;
case 6:
if(!write<Half>(address & ~7 | 6, data >> 48)) return;
if(!write<Half>(vaddr & ~7 | 6, data >> 48)) return;
break;
case 7:
if(!write<Byte>(address & ~7 | 7, data >> 56)) return;
if(!write<Byte>(vaddr & ~7 | 7, data >> 56)) return;
break;
}
}
auto CPU::SDR(cr64& rt, cr64& rs, s16 imm) -> void {
if(!context.kernelMode() && context.bits == 32) return exception.reservedInstruction();
u64 address = rs.u64 + imm;
u64 vaddr = rs.u64 + imm;
u64 data = rt.u64;
if(context.littleEndian())
switch(address & 7) {
switch(vaddr & 7) {
case 0:
if(!write<Dual>(address & ~7 | 0, data >> 0)) return;
if(!write<Dual>(vaddr & ~7 | 0, data >> 0)) return;
break;
case 1:
if(!write<Word>(address & ~7 | 0, data >> 24)) return;
if(!write<Half>(address & ~7 | 4, data >> 8)) return;
if(!write<Byte>(address & ~7 | 6, data >> 0)) return;
if(!write<Word>(vaddr & ~7 | 0, data >> 24)) return;
if(!write<Half>(vaddr & ~7 | 4, data >> 8)) return;
if(!write<Byte>(vaddr & ~7 | 6, data >> 0)) return;
break;
case 2:
if(!write<Word>(address & ~7 | 0, data >> 16)) return;
if(!write<Half>(address & ~7 | 4, data >> 0)) return;
if(!write<Word>(vaddr & ~7 | 0, data >> 16)) return;
if(!write<Half>(vaddr & ~7 | 4, data >> 0)) return;
break;
case 3:
if(!write<Word>(address & ~7 | 0, data >> 8)) return;
if(!write<Byte>(address & ~7 | 4, data >> 0)) return;
if(!write<Word>(vaddr & ~7 | 0, data >> 8)) return;
if(!write<Byte>(vaddr & ~7 | 4, data >> 0)) return;
break;
case 4:
if(!write<Word>(address & ~7 | 0, data >> 0)) return;
if(!write<Word>(vaddr & ~7 | 0, data >> 0)) return;
break;
case 5:
if(!write<Half>(address & ~7 | 0, data >> 8)) return;
if(!write<Byte>(address & ~7 | 2, data >> 0)) return;
if(!write<Half>(vaddr & ~7 | 0, data >> 8)) return;
if(!write<Byte>(vaddr & ~7 | 2, data >> 0)) return;
break;
case 6:
if(!write<Half>(address & ~7 | 0, data >> 0)) return;
if(!write<Half>(vaddr & ~7 | 0, data >> 0)) return;
break;
case 7:
if(!write<Byte>(address & ~7 | 0, data >> 0)) return;
if(!write<Byte>(vaddr & ~7 | 0, data >> 0)) return;
break;
}
if(context.bigEndian())
switch(address & 7) {
switch(vaddr & 7) {
case 0:
if(!write<Byte>(address & ~7 | 0, data >> 0)) return;
if(!write<Byte>(vaddr & ~7 | 0, data >> 0)) return;
break;
case 1:
if(!write<Half>(address & ~7 | 0, data >> 0)) return;
if(!write<Half>(vaddr & ~7 | 0, data >> 0)) return;
break;
case 2:
if(!write<Half>(address & ~7 | 0, data >> 8)) return;
if(!write<Byte>(address & ~7 | 2, data >> 0)) return;
if(!write<Half>(vaddr & ~7 | 0, data >> 8)) return;
if(!write<Byte>(vaddr & ~7 | 2, data >> 0)) return;
break;
case 3:
if(!write<Word>(address & ~7 | 0, data >> 0)) return;
if(!write<Word>(vaddr & ~7 | 0, data >> 0)) return;
break;
case 4:
if(!write<Word>(address & ~7 | 0, data >> 8)) return;
if(!write<Byte>(address & ~7 | 4, data >> 0)) return;
if(!write<Word>(vaddr & ~7 | 0, data >> 8)) return;
if(!write<Byte>(vaddr & ~7 | 4, data >> 0)) return;
break;
case 5:
if(!write<Word>(address & ~7 | 0, data >> 16)) return;
if(!write<Half>(address & ~7 | 4, data >> 0)) return;
if(!write<Word>(vaddr & ~7 | 0, data >> 16)) return;
if(!write<Half>(vaddr & ~7 | 4, data >> 0)) return;
break;
case 6:
if(!write<Word>(address & ~7 | 0, data >> 24)) return;
if(!write<Half>(address & ~7 | 4, data >> 8)) return;
if(!write<Byte>(address & ~7 | 6, data >> 0)) return;
if(!write<Word>(vaddr & ~7 | 0, data >> 24)) return;
if(!write<Half>(vaddr & ~7 | 4, data >> 8)) return;
if(!write<Byte>(vaddr & ~7 | 6, data >> 0)) return;
break;
case 7:
if(!write<Dual>(address & ~7 | 0, data >> 0)) return;
if(!write<Dual>(vaddr & ~7 | 0, data >> 0)) return;
break;
}
}
@@ -999,79 +1004,79 @@ auto CPU::SW(cr64& rt, cr64& rs, s16 imm) -> void {
}
auto CPU::SWL(cr64& rt, cr64& rs, s16 imm) -> void {
u64 address = rs.u64 + imm;
u64 vaddr = rs.u64 + imm;
u32 data = rt.u32;
if(context.littleEndian())
switch(address & 3) {
switch(vaddr & 3) {
case 0:
if(!write<Byte>(address & ~3 | 3, data >> 24)) return;
if(!write<Byte>(vaddr & ~3 | 3, data >> 24)) return;
break;
case 1:
if(!write<Half>(address & ~3 | 2, data >> 16)) return;
if(!write<Half>(vaddr & ~3 | 2, data >> 16)) return;
break;
case 2:
if(!write<Byte>(address & ~3 | 1, data >> 24)) return;
if(!write<Half>(address & ~3 | 2, data >> 8)) return;
if(!write<Byte>(vaddr & ~3 | 1, data >> 24)) return;
if(!write<Half>(vaddr & ~3 | 2, data >> 8)) return;
break;
case 3:
if(!write<Word>(address & ~3 | 0, data >> 0)) return;
if(!write<Word>(vaddr & ~3 | 0, data >> 0)) return;
break;
}
if(context.bigEndian())
switch(address & 3) {
switch(vaddr & 3) {
case 0:
if(!write<Word>(address & ~3 | 0, data >> 0)) return;
if(!write<Word>(vaddr & ~3 | 0, data >> 0)) return;
break;
case 1:
if(!write<Byte>(address & ~3 | 1, data >> 24)) return;
if(!write<Half>(address & ~3 | 2, data >> 8)) return;
if(!write<Byte>(vaddr & ~3 | 1, data >> 24)) return;
if(!write<Half>(vaddr & ~3 | 2, data >> 8)) return;
break;
case 2:
if(!write<Half>(address & ~3 | 2, data >> 16)) return;
if(!write<Half>(vaddr & ~3 | 2, data >> 16)) return;
break;
case 3:
if(!write<Byte>(address & ~3 | 3, data >> 24)) return;
if(!write<Byte>(vaddr & ~3 | 3, data >> 24)) return;
break;
}
}
auto CPU::SWR(cr64& rt, cr64& rs, s16 imm) -> void {
u64 address = rs.u64 + imm;
u64 vaddr = rs.u64 + imm;
u32 data = rt.u32;
if(context.littleEndian())
switch(address & 3) {
switch(vaddr & 3) {
case 0:
if(!write<Word>(address & ~3 | 0, data >> 0)) return;
if(!write<Word>(vaddr & ~3 | 0, data >> 0)) return;
break;
case 1:
if(!write<Half>(address & ~3 | 0, data >> 8)) return;
if(!write<Byte>(address & ~3 | 2, data >> 0)) return;
if(!write<Half>(vaddr & ~3 | 0, data >> 8)) return;
if(!write<Byte>(vaddr & ~3 | 2, data >> 0)) return;
break;
case 2:
if(!write<Half>(address & ~3 | 0, data >> 0)) return;
if(!write<Half>(vaddr & ~3 | 0, data >> 0)) return;
break;
case 3:
if(!write<Byte>(address & ~3 | 0, data >> 0)) return;
if(!write<Byte>(vaddr & ~3 | 0, data >> 0)) return;
break;
}
if(context.bigEndian())
switch(address & 3) {
switch(vaddr & 3) {
case 0:
if(!write<Byte>(address & ~3 | 0, data >> 0)) return;
if(!write<Byte>(vaddr & ~3 | 0, data >> 0)) return;
break;
case 1:
if(!write<Half>(address & ~3 | 0, data >> 0)) return;
if(!write<Half>(vaddr & ~3 | 0, data >> 0)) return;
break;
case 2:
if(!write<Half>(address & ~3 | 0, data >> 8)) return;
if(!write<Byte>(address & ~3 | 2, data >> 0)) return;
if(!write<Half>(vaddr & ~3 | 0, data >> 8)) return;
if(!write<Byte>(vaddr & ~3 | 2, data >> 0)) return;
break;
case 3:
if(!write<Word>(address & ~3 | 0, data >> 0)) return;
if(!write<Word>(vaddr & ~3 | 0, data >> 0)) return;
break;
}
}
+5
View File
@@ -126,11 +126,15 @@ auto CPU::getControlRegister(n5 index) -> u64 {
case 30: //error exception program counter
data = scc.epcError;
break;
default:
data = scc.latch;
break;
}
return data;
}
auto CPU::setControlRegister(n5 index, n64 data) -> void {
scc.latch = data;
//read-only variables are defined but commented out for documentation purposes
switch(index) {
case 0: //index
@@ -320,6 +324,7 @@ auto CPU::TLBP() -> void {
auto& entry = tlb.entry[index];
auto mask = ~entry.pageMask & ~0x1fff;
if((entry.virtualAddress & mask) != (scc.tlb.virtualAddress & mask)) continue;
if(entry.region != scc.tlb.region) continue;
if(!entry.global[0] || !entry.global[1]) {
if(entry.addressSpaceID != scc.tlb.addressSpaceID) continue;
}
+106 -104
View File
@@ -1,223 +1,225 @@
//32-bit segments
auto CPU::kernelSegment32(u32 address) const -> Context::Segment {
if(address <= 0x7fff'ffff) return Context::Segment::Mapped; //kuseg
if(address <= 0x9fff'ffff) return Context::Segment::Cached; //kseg0
if(address <= 0xbfff'ffff) return Context::Segment::Direct; //kseg1
if(address <= 0xdfff'ffff) return Context::Segment::Mapped; //ksseg
if(address <= 0xffff'ffff) return Context::Segment::Mapped; //kseg3
auto CPU::kernelSegment32(u32 vaddr) const -> Context::Segment {
if(vaddr <= 0x7fff'ffff) return Context::Segment::Mapped; //kuseg
if(vaddr <= 0x9fff'ffff) return Context::Segment::Cached; //kseg0
if(vaddr <= 0xbfff'ffff) return Context::Segment::Direct; //kseg1
if(vaddr <= 0xdfff'ffff) return Context::Segment::Mapped; //ksseg
if(vaddr <= 0xffff'ffff) return Context::Segment::Mapped; //kseg3
unreachable;
}
auto CPU::supervisorSegment32(u32 address) const -> Context::Segment {
if(address <= 0x7fff'ffff) return Context::Segment::Mapped; //suseg
if(address <= 0xbfff'ffff) return Context::Segment::Unused;
if(address <= 0xdfff'ffff) return Context::Segment::Mapped; //sseg
if(address <= 0xffff'ffff) return Context::Segment::Unused;
auto CPU::supervisorSegment32(u32 vaddr) const -> Context::Segment {
if(vaddr <= 0x7fff'ffff) return Context::Segment::Mapped; //suseg
if(vaddr <= 0xbfff'ffff) return Context::Segment::Unused;
if(vaddr <= 0xdfff'ffff) return Context::Segment::Mapped; //sseg
if(vaddr <= 0xffff'ffff) return Context::Segment::Unused;
unreachable;
}
auto CPU::userSegment32(u32 address) const -> Context::Segment {
if(address <= 0x7fff'ffff) return Context::Segment::Mapped; //useg
if(address <= 0xffff'ffff) return Context::Segment::Unused;
auto CPU::userSegment32(u32 vaddr) const -> Context::Segment {
if(vaddr <= 0x7fff'ffff) return Context::Segment::Mapped; //useg
if(vaddr <= 0xffff'ffff) return Context::Segment::Unused;
unreachable;
}
//64-bit segments
auto CPU::kernelSegment64(u64 address) const -> Context::Segment {
if(address <= 0x0000'00ff'ffff'ffffull) return Context::Segment::Mapped; //xkuseg
if(address <= 0x3fff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0x4000'00ff'ffff'ffffull) return Context::Segment::Mapped; //xksseg
if(address <= 0x7fff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0x8000'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(address <= 0x87ff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0x8800'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(address <= 0x8fff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0x9000'0000'ffff'ffffull) return Context::Segment::Direct; //xkphys*
if(address <= 0x97ff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0x9800'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(address <= 0x9fff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0xa000'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(address <= 0xa7ff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0xa800'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(address <= 0xafff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0xb000'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(address <= 0xb7ff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0xb800'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(address <= 0xbfff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0xc000'00ff'7fff'ffffull) return Context::Segment::Mapped; //xkseg
if(address <= 0xffff'ffff'7fff'ffffull) return Context::Segment::Unused;
if(address <= 0xffff'ffff'9fff'ffffull) return Context::Segment::Cached; //ckseg0
if(address <= 0xffff'ffff'bfff'ffffull) return Context::Segment::Direct; //ckseg1
if(address <= 0xffff'ffff'dfff'ffffull) return Context::Segment::Mapped; //ckseg2
if(address <= 0xffff'ffff'ffff'ffffull) return Context::Segment::Mapped; //ckseg3
auto CPU::kernelSegment64(u64 vaddr) const -> Context::Segment {
if(vaddr <= 0x0000'00ff'ffff'ffffull) return Context::Segment::Mapped; //xkuseg
if(vaddr <= 0x3fff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0x4000'00ff'ffff'ffffull) return Context::Segment::Mapped; //xksseg
if(vaddr <= 0x7fff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0x8000'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(vaddr <= 0x87ff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0x8800'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(vaddr <= 0x8fff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0x9000'0000'ffff'ffffull) return Context::Segment::Direct; //xkphys*
if(vaddr <= 0x97ff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0x9800'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(vaddr <= 0x9fff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0xa000'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(vaddr <= 0xa7ff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0xa800'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(vaddr <= 0xafff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0xb000'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(vaddr <= 0xb7ff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0xb800'0000'ffff'ffffull) return Context::Segment::Cached; //xkphys*
if(vaddr <= 0xbfff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0xc000'00ff'7fff'ffffull) return Context::Segment::Mapped; //xkseg
if(vaddr <= 0xffff'ffff'7fff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0xffff'ffff'9fff'ffffull) return Context::Segment::Cached; //ckseg0
if(vaddr <= 0xffff'ffff'bfff'ffffull) return Context::Segment::Direct; //ckseg1
if(vaddr <= 0xffff'ffff'dfff'ffffull) return Context::Segment::Mapped; //ckseg2
if(vaddr <= 0xffff'ffff'ffff'ffffull) return Context::Segment::Mapped; //ckseg3
unreachable;
}
auto CPU::supervisorSegment64(u64 address) const -> Context::Segment {
if(address <= 0x0000'00ff'ffff'ffffull) return Context::Segment::Mapped; //xsuseg
if(address <= 0x3fff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(address <= 0x4000'00ff'ffff'ffffull) return Context::Segment::Mapped; //xsseg
if(address <= 0xffff'ffff'bfff'ffffull) return Context::Segment::Unused;
if(address <= 0xffff'ffff'dfff'ffffull) return Context::Segment::Mapped; //csseg
if(address <= 0xffff'ffff'ffff'ffffull) return Context::Segment::Unused;
auto CPU::supervisorSegment64(u64 vaddr) const -> Context::Segment {
if(vaddr <= 0x0000'00ff'ffff'ffffull) return Context::Segment::Mapped; //xsuseg
if(vaddr <= 0x3fff'ffff'ffff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0x4000'00ff'ffff'ffffull) return Context::Segment::Mapped; //xsseg
if(vaddr <= 0xffff'ffff'bfff'ffffull) return Context::Segment::Unused;
if(vaddr <= 0xffff'ffff'dfff'ffffull) return Context::Segment::Mapped; //csseg
if(vaddr <= 0xffff'ffff'ffff'ffffull) return Context::Segment::Unused;
unreachable;
}
auto CPU::userSegment64(u64 address) const -> Context::Segment {
if(address <= 0x0000'00ff'ffff'ffffull) return Context::Segment::Mapped; //xuseg
if(address <= 0xffff'ffff'ffff'ffffull) return Context::Segment::Unused;
auto CPU::userSegment64(u64 vaddr) const -> Context::Segment {
if(vaddr <= 0x0000'00ff'ffff'ffffull) return Context::Segment::Mapped; //xuseg
if(vaddr <= 0xffff'ffff'ffff'ffffull) return Context::Segment::Unused;
unreachable;
}
//
auto CPU::segment(u64 address) -> Context::Segment {
auto segment = context.segment[address >> 29 & 7];
//if(likely(context.bits == 32))
return (Context::Segment)segment;
auto CPU::segment(u64 vaddr) -> Context::Segment {
auto segment = context.segment[vaddr >> 29 & 7];
if(likely(context.bits == 32))
return (Context::Segment)segment;
switch(segment) {
case Context::Segment::Kernel64:
return kernelSegment64(address);
return kernelSegment64(vaddr);
case Context::Segment::Supervisor64:
return supervisorSegment64(address);
return supervisorSegment64(vaddr);
case Context::Segment::User64:
return userSegment64(address);
return userSegment64(vaddr);
}
unreachable;
}
auto CPU::devirtualize(u64 address) -> maybe<u64> {
switch(context.segment[address >> 29 & 7]) {
auto CPU::devirtualize(u64 vaddr) -> maybe<u64> {
switch(segment(vaddr)) {
case Context::Segment::Unused:
addressException(address);
addressException(vaddr);
exception.addressLoad();
return nothing;
case Context::Segment::Mapped:
if(auto match = tlb.load(address)) return match.address;
tlb.exception(address);
if(auto match = tlb.load(vaddr)) return match.address & context.physMask;
addressException(vaddr);
return nothing;
case Context::Segment::Cached:
case Context::Segment::Direct:
return address;
return vaddr & context.physMask;
}
unreachable;
}
auto CPU::fetch(u64 address) -> u32 {
switch(segment(address)) {
auto CPU::fetch(u64 vaddr) -> u32 {
switch(segment(vaddr)) {
case Context::Segment::Unused:
step(1);
addressException(address);
addressException(vaddr);
exception.addressLoad();
return 0; //nop
case Context::Segment::Mapped:
if(auto match = tlb.load(address)) {
if(match.cache) return icache.fetch(match.address);
if(auto match = tlb.load(vaddr)) {
if(match.cache) return icache.fetch(match.address & context.physMask);
step(1);
return bus.read<Word>(match.address);
return bus.read<Word>(match.address & context.physMask);
}
step(1);
tlb.exception(address);
addressException(vaddr);
return 0; //nop
case Context::Segment::Cached:
return icache.fetch(address);
return icache.fetch(vaddr & context.physMask);
case Context::Segment::Direct:
step(1);
return bus.read<Word>(address);
return bus.read<Word>(vaddr & context.physMask);
}
unreachable;
}
template<u32 Size>
auto CPU::read(u64 address) -> maybe<u64> {
auto CPU::read(u64 vaddr) -> maybe<u64> {
if constexpr(Accuracy::CPU::AddressErrors) {
if(unlikely(address & Size - 1)) {
if(unlikely(vaddr & Size - 1)) {
step(1);
addressException(address);
addressException(vaddr);
exception.addressLoad();
return nothing;
}
if (context.bits == 32 && unlikely((s32)address != address)) {
if (context.bits == 32 && unlikely((s32)vaddr != vaddr)) {
step(1);
addressException(address);
addressException(vaddr);
exception.addressLoad();
return nothing;
}
}
switch(segment(address)) {
switch(segment(vaddr)) {
case Context::Segment::Unused:
step(1);
addressException(address);
addressException(vaddr);
exception.addressLoad();
return nothing;
case Context::Segment::Mapped:
if(auto match = tlb.load(address)) {
if(match.cache) return dcache.read<Size>(match.address);
if(auto match = tlb.load(vaddr)) {
if(match.cache) return dcache.read<Size>(match.address & context.physMask);
step(1);
return bus.read<Size>(match.address);
return bus.read<Size>(match.address & context.physMask);
}
step(1);
tlb.exception(address);
addressException(vaddr);
return nothing;
case Context::Segment::Cached:
return dcache.read<Size>(address);
return dcache.read<Size>(vaddr & context.physMask);
case Context::Segment::Direct:
step(1);
return bus.read<Size>(address);
return bus.read<Size>(vaddr & context.physMask);
}
unreachable;
}
template<u32 Size>
auto CPU::write(u64 address, u64 data) -> bool {
auto CPU::write(u64 vaddr, u64 data) -> bool {
if constexpr(Accuracy::CPU::AddressErrors) {
if(unlikely(address & Size - 1)) {
if(unlikely(vaddr & Size - 1)) {
step(1);
addressException(address);
addressException(vaddr);
exception.addressStore();
return false;
}
if (context.bits == 32 && unlikely((s32)address != address)) {
if (context.bits == 32 && unlikely((s32)vaddr != vaddr)) {
step(1);
addressException(address);
addressException(vaddr);
exception.addressStore();
return false;
}
}
switch(segment(address)) {
switch(segment(vaddr)) {
case Context::Segment::Unused:
step(1);
addressException(address);
addressException(vaddr);
exception.addressStore();
return false;
case Context::Segment::Mapped:
if(auto match = tlb.store(address)) {
if(match.cache) return dcache.write<Size>(match.address, data), true;
if(auto match = tlb.store(vaddr)) {
if(match.cache) return dcache.write<Size>(match.address & context.physMask, data), true;
step(1);
return bus.write<Size>(match.address, data), true;
return bus.write<Size>(match.address & context.physMask, data), true;
}
step(1);
tlb.exception(address);
addressException(vaddr);
return false;
case Context::Segment::Cached:
return dcache.write<Size>(address, data), true;
return dcache.write<Size>(vaddr & context.physMask, data), true;
case Context::Segment::Direct:
step(1);
return bus.write<Size>(address, data), true;
return bus.write<Size>(vaddr & context.physMask, data), true;
}
unreachable;
}
auto CPU::addressException(u64 address) -> void {
scc.badVirtualAddress = address;
scc.context.badVirtualAddress = address >> 13;
scc.xcontext.badVirtualAddress = address >> 13;
scc.xcontext.region = address >> 62;
auto CPU::addressException(u64 vaddr) -> void {
scc.badVirtualAddress = vaddr;
scc.tlb.virtualAddress.bit(13,39) = vaddr >> 13;
scc.tlb.region = vaddr >> 62;
scc.context.badVirtualAddress = vaddr >> 13;
scc.xcontext.badVirtualAddress = vaddr >> 13;
scc.xcontext.region = vaddr >> 62;
}
+1
View File
@@ -8,6 +8,7 @@ auto CPU::serialize(serializer& s) -> void {
s(branch.state);
s(context.endian);
s(context.physMask);
s(context.mode);
s(context.bits);
s(context.segment);
+22 -29
View File
@@ -1,61 +1,54 @@
//the N64 TLB is 32-bit only: only the 64-bit XTLB exception vector is used.
auto CPU::TLB::load(u32 address) -> Match {
auto CPU::TLB::load(u64 vaddr) -> Match {
for(auto& entry : this->entry) {
if(!entry.globals && entry.addressSpaceID != self.scc.tlb.addressSpaceID) continue;
if((u32)(address & entry.addressMaskHi) != (u32)entry.virtualAddress) continue;
bool lo = address & entry.addressSelect;
if((vaddr & entry.addressMaskHi) != entry.virtualAddress) continue;
if(vaddr >> 62 != entry.region) continue;
bool lo = vaddr & entry.addressSelect;
if(!entry.valid[lo]) {
exception(address);
self.debugger.tlbLoadInvalid(address);
self.addressException(vaddr);
self.debugger.tlbLoadInvalid(vaddr);
self.exception.tlbLoadInvalid();
return {false};
}
physicalAddress = entry.physicalAddress[lo] + (address & entry.addressMaskLo);
self.debugger.tlbLoad(address, physicalAddress);
physicalAddress = entry.physicalAddress[lo] + (vaddr & entry.addressMaskLo);
self.debugger.tlbLoad(vaddr, physicalAddress);
return {true, entry.cacheAlgorithm[lo] != 2, physicalAddress};
}
exception(address);
self.debugger.tlbLoadMiss(address);
self.addressException(vaddr);
self.debugger.tlbLoadMiss(vaddr);
self.exception.tlbLoadMiss();
return {false};
}
auto CPU::TLB::store(u32 address) -> Match {
auto CPU::TLB::store(u64 vaddr) -> Match {
for(auto& entry : this->entry) {
if(!entry.globals && entry.addressSpaceID != self.scc.tlb.addressSpaceID) continue;
if((u32)(address & entry.addressMaskHi) != (u32)entry.virtualAddress) continue;
bool lo = address & entry.addressSelect;
if((vaddr & entry.addressMaskHi) != entry.virtualAddress) continue;
if(vaddr >> 62 != entry.region) continue;
bool lo = vaddr & entry.addressSelect;
if(!entry.valid[lo]) {
exception(address);
self.debugger.tlbStoreInvalid(address);
self.addressException(vaddr);
self.debugger.tlbStoreInvalid(vaddr);
self.exception.tlbStoreInvalid();
return {false};
}
if(!entry.dirty[lo]) {
exception(address);
self.debugger.tlbModification(address);
self.addressException(vaddr);
self.debugger.tlbModification(vaddr);
self.exception.tlbModification();
return {false};
}
physicalAddress = entry.physicalAddress[lo] + (address & entry.addressMaskLo);
self.debugger.tlbStore(address, physicalAddress);
physicalAddress = entry.physicalAddress[lo] + (vaddr & entry.addressMaskLo);
self.debugger.tlbStore(vaddr, physicalAddress);
return {true, entry.cacheAlgorithm[lo] != 2, physicalAddress};
}
exception(address);
self.debugger.tlbStoreMiss(address);
self.addressException(vaddr);
self.debugger.tlbStoreMiss(vaddr);
self.exception.tlbStoreMiss();
return {false};
}
auto CPU::TLB::exception(u32 address) -> void {
self.scc.badVirtualAddress = address;
self.scc.tlb.virtualAddress.bit(13,39) = address >> 13;
self.scc.context.badVirtualAddress = address >> 13;
self.scc.xcontext.badVirtualAddress = address >> 13;
self.scc.xcontext.region = 0;
}
auto CPU::TLB::Entry::synchronize() -> void {
pageMask = pageMask & (0b101010101010 << 13);
pageMask |= pageMask >> 1;
+2 -3
View File
@@ -4,9 +4,6 @@ auto PI::dmaRead() -> void {
u16 data = rdram.ram.read<Half>(io.dramAddress + address);
busWrite<Half>(io.pbusAddress + address, data);
}
io.dmaBusy = 0;
io.interrupt = 1;
mi.raise(MI::IRQ::PI);
}
auto PI::dmaWrite() -> void {
@@ -48,7 +45,9 @@ auto PI::dmaWrite() -> void {
first_block = false;
}
}
auto PI::dmaFinished() -> void {
io.dmaBusy = 0;
io.interrupt = 1;
mi.raise(MI::IRQ::PI);
+4 -2
View File
@@ -98,14 +98,16 @@ auto PI::ioWrite(u32 address, u32 data_) -> void {
//PI_READ_LENGTH
io.readLength = n24(data);
io.dmaBusy = 1;
queue.insert(Queue::PI_DMA_Read, io.readLength * 9);
queue.insert(Queue::PI_DMA_Read, io.readLength * 36);
dmaRead();
}
if(address == 3) {
//PI_WRITE_LENGTH
io.writeLength = n24(data);
io.dmaBusy = 1;
queue.insert(Queue::PI_DMA_Write, io.writeLength * 9);
queue.insert(Queue::PI_DMA_Write, io.writeLength * 36);
dmaWrite();
}
if(address == 4) {
+1
View File
@@ -21,6 +21,7 @@ struct PI : Memory::IO<PI> {
//dma.cpp
auto dmaRead() -> void;
auto dmaWrite() -> void;
auto dmaFinished() -> void;
//io.cpp
auto ioRead(u32 address) -> u32;
+51 -130
View File
@@ -140,155 +140,76 @@ auto PIF::scan() -> void {
}
n8 output[64];
b1 valid = 0;
b1 over = 0;
//status
if(input[0] == 0x00 || input[0] == 0xff) {
//controller
if(channel < 4 && controllers[channel]->device) {
output[0] = 0x05; //0x05 = gamepad; 0x02 = mouse
output[1] = 0x00;
output[2] = 0x02; //0x02 = nothing present in controller slot
if(auto& device = controllers[channel]->device) {
if(auto gamepad = dynamic_cast<Gamepad*>(device.data())) {
if(gamepad->ram || gamepad->motor) {
output[2] = 0x01; //0x01 = pak present
}
}
//controller port communication
if (channel < 4 && controllers[channel]->device) {
n2 status = controllers[channel]->device->comm(send, recv, input, output);
valid = status.bit(0);
over = status.bit(1);
}
if (channel >= 4) {
//status
if(input[0] == 0x00 || input[0] == 0xff) {
//cartridge EEPROM (4kbit)
if(cartridge.eeprom.size == 512) {
output[0] = 0x00;
output[1] = 0x80;
output[2] = 0x00;
valid = 1;
}
//cartridge EEPROM (16kbit)
if(cartridge.eeprom.size == 2048) {
output[0] = 0x00;
output[1] = 0xc0;
output[2] = 0x00;
valid = 1;
}
}
//read EEPROM
if(input[0] == 0x04 && send >= 2) {
u32 address = input[1] * 8;
for(u32 index : range(recv)) {
output[index] = cartridge.eeprom.read<Byte>(address++);
}
valid = 1;
}
//cartridge EEPROM (4kbit)
if(channel >= 4 && cartridge.eeprom.size == 512) {
//write EEPROM
if(input[0] == 0x05 && send >= 2 && recv >= 1) {
u32 address = input[1] * 8;
for(u32 index : range(send - 2)) {
cartridge.eeprom.write<Byte>(address++, input[2 + index]);
}
output[0] = 0x00;
output[1] = 0x80;
output[2] = 0x00;
valid = 1;
}
//cartridge EEPROM (16kbit)
if(channel >= 4 && cartridge.eeprom.size == 2048) {
output[0] = 0x00;
output[1] = 0xc0;
output[2] = 0x00;
valid = 1;
//RTC status
if(input[0] == 0x06) {
debug(unimplemented, "[SI::main] RTC status");
}
}
//read controller state
if(input[0] == 0x01) {
if(channel < 4 && controllers[channel]->device) {
u32 data = controllers[channel]->device->read();
output[0] = data >> 24;
output[1] = data >> 16;
output[2] = data >> 8;
output[3] = data >> 0;
if(recv <= 4) {
ram.write<Byte>(recvOffset, 0x00 | recv & 0x3f);
} else {
ram.write<Byte>(recvOffset, 0x40 | recv & 0x3f);
}
valid = 1;
//RTC read
if(input[0] == 0x07) {
debug(unimplemented, "[SI::main] RTC read");
}
}
//read pak
if(input[0] == 0x02 && send >= 3 && recv >= 1) {
if(auto& device = controllers[channel]->device) {
if(auto gamepad = dynamic_cast<Gamepad*>(device.data())) {
//controller pak
if(auto& ram = gamepad->ram) {
u32 address = (input[1] << 8 | input[2] << 0) & ~31;
if(addressCRC(address) == (n5)input[2]) {
for(u32 index : range(recv - 1)) {
output[index] = ram.read<Byte>(address++);
}
output[recv - 1] = dataCRC({&output[0], recv - 1});
valid = 1;
}
}
//rumble pak
if(gamepad->motor) {
u32 address = (input[1] << 8 | input[2] << 0) & ~31;
if(addressCRC(address) == (n5)input[2]) {
for(u32 index : range(recv - 1)) {
output[index] = 0x80;
}
output[recv - 1] = dataCRC({&output[0], recv - 1});
valid = 1;
}
}
}
//RTC write
if(input[0] == 0x08) {
debug(unimplemented, "[SI::main] RTC write");
}
}
//write pak
if(input[0] == 0x03 && send >= 3 && recv >= 1) {
if(auto& device = controllers[channel]->device) {
if(auto gamepad = dynamic_cast<Gamepad*>(device.data())) {
//controller pak
if(auto& ram = gamepad->ram) {
u32 address = (input[1] << 8 | input[2] << 0) & ~31;
if(addressCRC(address) == (n5)input[2]) {
for(u32 index : range(send - 3)) {
ram.write<Byte>(address++, input[3 + index]);
}
output[0] = dataCRC({&input[3], send - 3});
valid = 1;
}
}
//rumble pak
if(gamepad->motor) {
u32 address = (input[1] << 8 | input[2] << 0) & ~31;
if(addressCRC(address) == (n5)input[2]) {
output[0] = dataCRC({&input[3], send - 3});
valid = 1;
gamepad->rumble(input[3] & 1);
}
}
}
}
}
//read EEPROM
if(input[0] == 0x04 && send >= 2) {
u32 address = input[1] * 8;
for(u32 index : range(recv)) {
output[index] = cartridge.eeprom.read<Byte>(address++);
}
valid = 1;
}
//write EEPROM
if(input[0] == 0x05 && send >= 2 && recv >= 1) {
u32 address = input[1] * 8;
for(u32 index : range(send - 2)) {
cartridge.eeprom.write<Byte>(address++, input[2 + index]);
}
output[0] = 0x00;
valid = 1;
}
//RTC status
if(input[0] == 0x06) {
debug(unimplemented, "[SI::main] RTC status");
}
//RTC read
if(input[0] == 0x07) {
debug(unimplemented, "[SI::main] RTC read");
}
//RTC write
if(input[0] == 0x08) {
debug(unimplemented, "[SI::main] RTC write");
}
if(!valid) {
ram.write<Byte>(recvOffset, 0x80 | recv & 0x3f);
}
if(over) {
ram.write<Byte>(recvOffset, 0x40 | recv & 0x3f);
}
for(u32 index : range(recv)) {
ram.write<Byte>(offset++, output[index]);
}
+19 -11
View File
@@ -22,14 +22,14 @@ auto RDP::readWord(u32 address) -> u32 {
data.bit( 0) = command.source;
data.bit( 1) = command.freeze;
data.bit( 2) = command.flush;
data.bit( 3) = 0; //start gclk?
data.bit( 3) = command.startGclk;
data.bit( 4) = command.tmemBusy > 0;
data.bit( 5) = command.pipeBusy > 0;
data.bit( 6) = command.bufferBusy > 0;
data.bit( 7) = command.ready;
data.bit( 8) = 0; //DMA busy
data.bit( 9) = 0; //end valid
data.bit(10) = 0; //start valid
data.bit( 9) = command.endValid;
data.bit(10) = command.startValid;
}
if(address == 4) {
@@ -62,18 +62,18 @@ auto RDP::writeWord(u32 address, u32 data_) -> void {
if(address == 0) {
//DPC_START
command.start = data.bit(0,23) & ~7;
command.current = command.start;
if(!command.startValid) command.start = data.bit(0,23) & ~7;
command.startValid = 1;
}
if(address == 1) {
//DPC_END
command.end = data.bit(0,23) & ~7;
if(command.end > command.current) {
command.freeze = 0;
render();
command.ready = 1;
if(command.startValid) {
command.current = command.start;
command.startValid = 0;
}
flushCommands();
}
if(address == 2) {
@@ -84,8 +84,8 @@ auto RDP::writeWord(u32 address, u32 data_) -> void {
//DPC_STATUS
if(data.bit(0)) command.source = 0;
if(data.bit(1)) command.source = 1;
if(data.bit(2)) command.freeze = 0;
//if(data.bit(3)) command.freeze = 1;
if(data.bit(2)) command.freeze = 0, flushCommands();
if(data.bit(3)) command.freeze = 1;
if(data.bit(4)) command.flush = 0;
if(data.bit(5)) command.flush = 1;
if(data.bit(6)) command.tmemBusy = 0;
@@ -172,3 +172,11 @@ auto RDP::IO::writeWord(u32 address, u32 data_) -> void {
self.debugger.ioDPS(Write, address, data);
}
auto RDP::flushCommands() -> void {
if(command.freeze) return;
command.pipeBusy = 1;
command.startGclk = 1;
if(command.end > command.current) render();
command.ready = 1;
}
+4
View File
@@ -67,6 +67,7 @@ struct RDP : Thread, Memory::IO<RDP> {
//io.cpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto flushCommands() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
@@ -82,6 +83,9 @@ struct RDP : Thread, Memory::IO<RDP> {
n1 source; //0 = RDRAM, 1 = DMEM
n1 freeze;
n1 flush;
n1 startValid;
n1 endValid;
n1 startGclk;
n1 ready = 1;
} command;
+2
View File
@@ -619,6 +619,8 @@ auto RDP::syncTile() -> void {
//0x29
auto RDP::syncFull() -> void {
mi.raise(MI::IRQ::DP);
command.pipeBusy = 0;
command.startGclk = 0;
}
//0x2a
+37 -21
View File
@@ -1,27 +1,43 @@
auto RSP::dmaTransfer() -> void {
if(dma.requests.empty()) return;
auto request = *dma.requests.read();
auto region = !request.pbusRegion ? 0x0400'0000 : 0x0400'1000;
auto RSP::dmaTransferStart(void) -> void {
if(dma.busy.any()) return;
if(dma.full.any()) {
dma.current = dma.pending;
dma.busy = dma.full;
dma.full = {0,0};
queue.insert(Queue::RSP_DMA, (dma.current.length+8) / 8 * 3);
}
}
auto RSP::dmaTransferStep() -> void {
auto& region = !dma.current.pbusRegion ? dmem : imem;
if(request.type == DMA::Request::Type::Read) {
for(u32 block : range(request.count)) {
for(u32 offset = 0; offset < request.length; offset += 4) {
u32 data = bus.read<Word>(request.dramAddress + offset);
bus.write<Word>(region + (request.pbusAddress + offset & 0xFFF), data);
}
request.pbusAddress += request.length;
request.dramAddress += request.length + request.skip;
if(dma.busy.read) {
if constexpr(Accuracy::RSP::Recompiler) {
if(dma.current.pbusRegion) recompiler.invalidate();
}
for(u32 i = 0; i <= dma.current.length; i += 8) {
u64 data = rdram.ram.read<Dual>(dma.current.dramAddress);
region.write<Dual>(dma.current.pbusAddress, data);
dma.current.dramAddress += 8;
dma.current.pbusAddress += 8;
}
}
if(dma.busy.write) {
for(u32 i = 0; i <= dma.current.length; i += 8) {
u64 data = region.read<Dual>(dma.current.pbusAddress);
rdram.ram.write<Dual>(dma.current.dramAddress, data);
dma.current.dramAddress += 8;
dma.current.pbusAddress += 8;
}
}
if(request.type == DMA::Request::Type::Write) {
for(u32 block : range(request.count)) {
for(u32 offset = 0; offset < request.length; offset += 4) {
u32 data = bus.read<Word>(region + (request.pbusAddress + offset & 0xFFF));
bus.write<Word>(request.dramAddress + offset, data);
}
request.pbusAddress += request.length;
request.dramAddress += request.length + request.skip;
}
if(dma.current.count) {
dma.current.count -= 1;
dma.current.dramAddress += dma.current.skip;
queue.insert(Queue::RSP_DMA, (dma.current.length+8) / 8 * 3);
} else {
dma.busy = {0,0};
dma.current.length = 0xFF8;
dmaTransferStart();
}
}
+95 -51
View File
@@ -301,24 +301,69 @@ auto RSP::SDV(cr128& vt, cr32& rs, s8 imm) -> void {
template<u8 e>
auto RSP::SFV(cr128& vt, cr32& rs, s8 imm) -> void {
auto address = rs.u32 + imm * 16;
auto start = e >> 1;
auto end = start + 4;
auto base = address & 15;
address &= ~15;
for(u32 offset = start; offset < end; offset++) {
dmem.write<Byte>(address + (base & 15), vt.element(offset & 7) >> 7);
base += 4;
auto base = address & 7;
address &= ~7;
switch (e) {
case 0: case 15:
dmem.write<Byte>(address + (base + 0 & 15), vt.element(0) >> 7);
dmem.write<Byte>(address + (base + 4 & 15), vt.element(1) >> 7);
dmem.write<Byte>(address + (base + 8 & 15), vt.element(2) >> 7);
dmem.write<Byte>(address + (base + 12 & 15), vt.element(3) >> 7);
break;
case 1:
dmem.write<Byte>(address + (base + 0 & 15), vt.element(6) >> 7);
dmem.write<Byte>(address + (base + 4 & 15), vt.element(7) >> 7);
dmem.write<Byte>(address + (base + 8 & 15), vt.element(4) >> 7);
dmem.write<Byte>(address + (base + 12 & 15), vt.element(5) >> 7);
break;
case 4:
dmem.write<Byte>(address + (base + 0 & 15), vt.element(1) >> 7);
dmem.write<Byte>(address + (base + 4 & 15), vt.element(2) >> 7);
dmem.write<Byte>(address + (base + 8 & 15), vt.element(3) >> 7);
dmem.write<Byte>(address + (base + 12 & 15), vt.element(0) >> 7);
break;
case 5:
dmem.write<Byte>(address + (base + 0 & 15), vt.element(7) >> 7);
dmem.write<Byte>(address + (base + 4 & 15), vt.element(4) >> 7);
dmem.write<Byte>(address + (base + 8 & 15), vt.element(5) >> 7);
dmem.write<Byte>(address + (base + 12 & 15), vt.element(6) >> 7);
break;
case 8:
dmem.write<Byte>(address + (base + 0 & 15), vt.element(4) >> 7);
dmem.write<Byte>(address + (base + 4 & 15), vt.element(5) >> 7);
dmem.write<Byte>(address + (base + 8 & 15), vt.element(6) >> 7);
dmem.write<Byte>(address + (base + 12 & 15), vt.element(7) >> 7);
break;
case 11:
dmem.write<Byte>(address + (base + 0 & 15), vt.element(3) >> 7);
dmem.write<Byte>(address + (base + 4 & 15), vt.element(0) >> 7);
dmem.write<Byte>(address + (base + 8 & 15), vt.element(1) >> 7);
dmem.write<Byte>(address + (base + 12 & 15), vt.element(2) >> 7);
break;
case 12:
dmem.write<Byte>(address + (base + 0 & 15), vt.element(5) >> 7);
dmem.write<Byte>(address + (base + 4 & 15), vt.element(6) >> 7);
dmem.write<Byte>(address + (base + 8 & 15), vt.element(7) >> 7);
dmem.write<Byte>(address + (base + 12 & 15), vt.element(4) >> 7);
break;
default:
dmem.write<Byte>(address + (base + 0 & 15), 0);
dmem.write<Byte>(address + (base + 4 & 15), 0);
dmem.write<Byte>(address + (base + 8 & 15), 0);
dmem.write<Byte>(address + (base + 12 & 15), 0);
break;
}
}
template<u8 e>
auto RSP::SHV(cr128& vt, cr32& rs, s8 imm) -> void {
auto address = rs.u32 + imm * 16;
auto index = address & 7;
address &= ~7;
for(u32 offset = 0; offset < 8; offset++) {
auto byte = e + offset * 2;
auto value = vt.byte(byte + 0 & 15) << 1 | vt.byte(byte + 1 & 15) >> 7;
dmem.write<Byte>(address, value);
address += 2;
dmem.write<Byte>(address + (index + offset * 2 & 15), value);
}
}
@@ -381,14 +426,14 @@ auto RSP::SSV(cr128& vt, cr32& rs, s8 imm) -> void {
template<u8 e>
auto RSP::STV(u8 vt, cr32& rs, s8 imm) -> void {
auto address = rs.u32 + imm * 16;
auto start = vt;
auto end = min(32, start + 8);
auto element = 8 - (e >> 1);
auto base = (address & 15) + (element << 1);
address &= ~15;
auto start = vt & ~7;
auto end = start + 8;
auto element = 16 - (e & ~1);
auto base = (address & 7) - (e & ~1);
address &= ~7;
for(u32 offset = start; offset < end; offset++) {
dmem.writeUnaligned<Half>(address + (base & 15), vpu.r[offset].element(element++ & 7));
base += 2;
dmem.write<Byte>(address + (base++ & 15), vpu.r[offset].byte(element++ & 15));
dmem.write<Byte>(address + (base++ & 15), vpu.r[offset].byte(element++ & 15));
}
}
@@ -411,8 +456,8 @@ auto RSP::SWV(cr128& vt, cr32& rs, s8 imm) -> void {
auto address = rs.u32 + imm * 16;
auto start = e;
auto end = start + 16;
auto base = address & 15;
address &= ~15;
auto base = address & 7;
address &= ~7;
for(u32 offset = start; offset < end; offset++) {
dmem.write<Byte>(address + (base++ & 15), vt.byte(offset & 15));
}
@@ -424,15 +469,21 @@ auto RSP::VABS(r128& vd, cr128& vs, cr128& vt) -> void {
r128 vte = vt(e);
for(u32 n : range(8)) {
if(vs.s16(n) < 0) {
if(vte.s16(n) == -32768) vte.s16(n) = -32767;
ACCL.s16(n) = -vte.s16(n);
if(vte.s16(n) == -32768) {
ACCL.s16(n) = -32768;
vd.s16(n) = 32767;
} else {
ACCL.s16(n) = -vte.s16(n);
vd.s16(n) = -vte.s16(n);
}
} else if(vs.s16(n) > 0) {
ACCL.s16(n) = +vte.s16(n);
vd.s16(n) = +vte.s16(n);
} else {
ACCL.s16(n) = 0;
vd.s16(n) = 0;
}
}
vd = ACCL;
}
if constexpr(Accuracy::RSP::SIMD) {
@@ -580,10 +631,14 @@ auto RSP::VCL(r128& vd, cr128& vs, cr128& vt) -> void {
if(VCOL.get(n)) {
if(VCOH.get(n)) {
ACCL.u16(n) = VCCL.get(n) ? -vte.u16(n) : vs.u16(n);
} else if(VCE.get(n)) {
ACCL.u16(n) = VCCL.set(n, vs.u16(n) + vte.u16(n) <= 0xffff) ? -vte.u16(n) : vs.u16(n);
} else {
ACCL.u16(n) = VCCL.set(n, vs.u16(n) + vte.u16(n) == 0) ? -vte.u16(n) : vs.u16(n);
u16 sum = vs.u16(n) + vte.u16(n);
bool carry = (vs.u16(n) + vte.u16(n)) != sum;
if(VCE.get(n)) {
ACCL.u16(n) = VCCL.set(n, (!sum || !carry)) ? -vte.u16(n) : vs.u16(n);
} else {
ACCL.u16(n) = VCCL.set(n, (!sum && !carry)) ? -vte.u16(n) : vs.u16(n);
}
}
} else {
if(VCOH.get(n)) {
@@ -678,12 +733,11 @@ auto RSP::VEQ(r128& vd, cr128& vs, cr128& vt) -> void {
if constexpr(Accuracy::RSP::SISD) {
cr128 vte = vt(e);
for(u32 n : range(8)) {
ACCL.u16(n) = VCCL.set(n, !VCE.get(n) && vs.u16(n) == vte.u16(n)) ? vs.u16(n) : vte.u16(n);
ACCL.u16(n) = VCCL.set(n, !VCOH.get(n) && vs.u16(n) == vte.u16(n)) ? vs.u16(n) : vte.u16(n);
}
VCCH = zero; //unverified
VCOL = zero;
VCOH = zero;
VCE = zero;
vd = ACCL;
}
@@ -706,12 +760,11 @@ auto RSP::VGE(r128& vd, cr128& vs, cr128& vt) -> void {
if constexpr(Accuracy::RSP::SISD) {
cr128 vte = vt(e);
for(u32 n : range(8)) {
ACCL.u16(n) = VCCL.set(n, vs.s16(n) > vte.s16(n) || (vs.s16(n) == vte.s16(n) && (!VCOL.get(n) || VCE.get(n)))) ? vs.u16(n) : vte.u16(n);
ACCL.u16(n) = VCCL.set(n, vs.s16(n) > vte.s16(n) || (vs.s16(n) == vte.s16(n) && (!VCOL.get(n) || !VCOH.get(n)))) ? vs.u16(n) : vte.u16(n);
}
VCCH = zero; //unverified
VCOL = zero;
VCOH = zero;
VCE = zero;
vd = ACCL;
}
@@ -737,12 +790,11 @@ auto RSP::VLT(r128& vd, cr128& vs, cr128& vt) -> void {
if constexpr(Accuracy::RSP::SISD) {
cr128 vte = vt(e);
for(u32 n : range(8)) {
ACCL.u16(n) = VCCL.set(n, vs.s16(n) < vte.s16(n) || (vs.s16(n) == vte.s16(n) && VCOL.get(n) && !VCE.get(n))) ? vs.u16(n) : vte.u16(n);
ACCL.u16(n) = VCCL.set(n, vs.s16(n) < vte.s16(n) || (vs.s16(n) == vte.s16(n) && VCOL.get(n) && VCOH.get(n))) ? vs.u16(n) : vte.u16(n);
}
VCCH = zero; //unverified
VCCH = zero;
VCOL = zero;
VCOH = zero;
VCE = zero;
vd = ACCL;
}
@@ -768,7 +820,7 @@ auto RSP::VMACF(r128& vd, cr128& vs, cr128& vt) -> void {
if constexpr(Accuracy::RSP::SISD) {
cr128 vte = vt(e);
for(u32 n : range(8)) {
accumulatorSet(n, accumulatorGet(n) + vs.s16(n) * vte.s16(n) * 2);
accumulatorSet(n, accumulatorGet(n) + (s64)vs.s16(n) * (s64)vte.s16(n) * 2);
if constexpr(U == 0) {
vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff);
}
@@ -823,10 +875,9 @@ auto RSP::VMACQ(r128& vd) -> void {
for(u32 n : range(8)) {
s32 product = ACCH.element(n) << 16 | ACCM.element(n) << 0;
if(product < 0 && !(product & 1 << 5)) product += 32;
else if(product > 0 && !(product & 1 << 5)) product -= 32;
else if(product >= 32 && !(product & 1 << 5)) product -= 32;
ACCH.element(n) = product >> 16;
ACCM.element(n) = product >> 0;
ACCL.element(n) = 0;
vd.element(n) = sclamp<16>(product >> 1) & ~15;
}
}
@@ -974,17 +1025,11 @@ auto RSP::VMADN(r128& vd, cr128& vs, cr128& vt) -> void {
}
}
template<u8 E>
template<u8 e>
auto RSP::VMOV(r128& vd, u8 de, cr128& vt) -> void {
u8 e = E;
switch(e) {
case 0x0 ... 0x1: e = e & 0b000 | de & 0b111; break; //hardware glitch
case 0x2 ... 0x3: e = e & 0b001 | de & 0b110; break; //hardware glitch
case 0x4 ... 0x7: e = e & 0b011 | de & 0b100; break; //hardware glitch
case 0x8 ... 0xf: e = e & 0b111 | de & 0b000; break; //normal behavior
}
vd.u16(de) = vt.u16(e);
ACCL = vt(e);
cr128 vte = vt(e);
vd.u16(de) = vte.u16(de);
ACCL = vte;
}
template<u8 e>
@@ -1105,9 +1150,9 @@ auto RSP::VMULF(r128& vd, cr128& vs, cr128& vt) -> void {
if constexpr(Accuracy::RSP::SISD) {
cr128 vte = vt(e);
for(u32 n : range(8)) {
accumulatorSet(n, vs.s16(n) * vte.s16(n) * 2 + 0x8000);
accumulatorSet(n, (s64)vs.s16(n) * (s64)vte.s16(n) * 2 + 0x8000);
if constexpr(U == 0) {
vd.u16(n) = ACCM.u16(n);
vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff);
}
if constexpr(U == 1) {
vd.u16(n) = ACCH.s16(n) < 0 ? 0x0000 : (ACCH.s16(n) ^ ACCM.s16(n)) < 0 ? 0xffff : ACCM.u16(n);
@@ -1181,12 +1226,11 @@ auto RSP::VNE(r128& vd, cr128& vs, cr128& vt) -> void {
if constexpr(Accuracy::RSP::SISD) {
cr128 vte = vt(e);
for(u32 n : range(8)) {
ACCL.u16(n) = VCCL.set(n, vs.u16(n) != vte.u16(n) || VCE.get(n)) ? vs.u16(n) : vte.u16(n);
ACCL.u16(n) = VCCL.set(n, vs.u16(n) != vte.u16(n) || VCOH.get(n)) ? vs.u16(n) : vte.u16(n);
}
VCCH = zero; //unverified
VCOL = zero;
VCOH = zero;
VCE = zero;
vd = ACCL;
}
@@ -1308,12 +1352,12 @@ auto RSP::VRND(r128& vd, u8 vs, cr128& vt) -> void {
acc |= ACCM.element(n); acc <<= 16;
acc |= ACCL.element(n); acc <<= 16;
acc >>= 16;
if(D == 0 && acc < 0) acc += product;
if(D == 1 && acc >= 0) acc += product;
if(D == 0 && acc < 0) acc = sclip<48>(acc + product);
if(D == 1 && acc >= 0) acc = sclip<48>(acc + product);
ACCH.element(n) = acc >> 32;
ACCM.element(n) = acc >> 16;
ACCL.element(n) = acc >> 0;
vd.element(n) = acc >> 16;
vd.element(n) = sclamp<16>(acc >> 16);
}
}
+5 -5
View File
@@ -291,8 +291,8 @@ auto RSP::decoderVU() -> void {
vu(0x1b, VZERO, VD, VS, VT); //VSAC
vu(0x1c, VZERO, VD, VS, VT); //VSUM
vu(0x1d, VSAR, VD, VS);
op(0x1e, INVALID);
op(0x1f, INVALID);
vu(0x1e, VZERO, VD, VS, VT);
vu(0x1f, VZERO, VD, VS, VT);
vu(0x20, VLT, VD, VS, VT);
vu(0x21, VEQ, VD, VS, VT);
vu(0x22, VNE, VD, VS, VT);
@@ -307,8 +307,8 @@ auto RSP::decoderVU() -> void {
vu(0x2b, VNOR, VD, VS, VT);
vu(0x2c, VXOR, VD, VS, VT);
vu(0x2d, VNXOR, VD, VS, VT);
op(0x2e, INVALID);
op(0x2f, INVALID);
vu(0x2e, VZERO, VD, VS, VT);
vu(0x2f, VZERO, VD, VS, VT);
vu(0x30, VRCP, VD, DE, VT);
vu(0x31, VRCPL, VD, DE, VT);
vu(0x32, VRCPH, VD, DE, VT);
@@ -320,7 +320,7 @@ auto RSP::decoderVU() -> void {
vu(0x38, VZERO, VD, VS, VT); //VEXTT
vu(0x39, VZERO, VD, VS, VT); //VEXTQ
vu(0x3a, VZERO, VD, VS, VT); //VEXTN
op(0x3b, INVALID);
vu(0x3b, VZERO, VD, VS, VT);
vu(0x3c, VZERO, VD, VS, VT); //VINST
vu(0x3d, VZERO, VD, VS, VT); //VINSQ
vu(0x3e, VZERO, VD, VS, VT); //VINSN
+27 -52
View File
@@ -12,35 +12,28 @@ auto RSP::ioRead(u32 address) -> u32 {
if(address == 0) {
//SP_PBUS_ADDRESS
data.bit( 0,11) = dma.pbusAddress;
data.bit(12) = dma.pbusRegion;
data.bit( 0,11) = dma.current.pbusAddress;
data.bit(12) = dma.current.pbusRegion;
}
if(address == 1) {
//SP_DRAM_ADDRESS
data.bit(0,23) = dma.dramAddress;
data.bit(0,23) = dma.current.dramAddress;
}
if(address == 2) {
//SP_READ_LENGTH
data.bit( 0,11) = dma.read.length;
data.bit(12,19) = dma.read.count;
data.bit(20,31) = dma.read.skip;
}
if(address == 3) {
//SP_WRITE_LENGTH
data.bit( 0,11) = dma.write.length;
data.bit(12,19) = dma.write.count;
data.bit(20,31) = dma.write.skip;
if(address == 2 || address == 3) {
//SP_READ_LENGTH or SP_WRITE_LENGTH
data.bit( 0,11) = dma.current.length;
data.bit(12,19) = dma.current.count;
data.bit(20,31) = dma.current.skip;
}
if(address == 4) {
//SP_STATUS
data.bit( 0) = status.halted;
data.bit( 1) = status.broken;
data.bit( 2) = !dma.requests.empty();
data.bit( 3) = dma.requests.full();
data.bit( 2) = dma.busy.any();
data.bit( 3) = dma.full.any();
data.bit( 4) = status.full;
data.bit( 5) = status.singleStep;
data.bit( 6) = status.interruptOnBreak;
@@ -56,12 +49,12 @@ auto RSP::ioRead(u32 address) -> u32 {
if(address == 5) {
//SP_DMA_FULL
data.bit(0) = dma.requests.full();
data.bit(0) = dma.full.any();
}
if(address == 6) {
//SP_DMA_BUSY
data.bit(0) = !dma.requests.empty();
data.bit(0) = dma.busy.any();
}
if(address == 7) {
@@ -89,51 +82,33 @@ auto RSP::ioWrite(u32 address, u32 data_) -> void {
if(address == 0) {
//SP_PBUS_ADDRESS
dma.pbusAddress = data.bit( 0,11);
dma.pbusRegion = data.bit(12);
dma.pending.pbusAddress.bit(3,11) = data.bit( 3,11);
dma.pending.pbusRegion = data.bit(12);
}
if(address == 1) {
//SP_DRAM_ADDRESS
dma.dramAddress = data.bit(0,23);
dma.pending.dramAddress.bit(3,23) = data.bit(3,23);
}
if(address == 2) {
//SP_READ_LENGTH
dma.read.length = data.bit( 0,11);
dma.read.count = data.bit(12,19);
dma.read.skip = data.bit(20,31);
if(!dma.requests.full()) {
DMA::Request request;
request.type = DMA::Request::Type::Read;
request.pbusRegion = dma.pbusRegion;
request.pbusAddress = dma.pbusAddress & ~7;
request.dramAddress = dma.dramAddress & ~7;
request.length = 1 + (dma.read.length | 7);
request.count = 1 + (dma.read.count);
request.skip = dma.read.skip & ~7;
dma.requests.write(request);
queue.insert(Queue::RSP_DMA, request.length * request.count / 4);
}
dma.pending.length.bit(3,11) = data.bit( 3,11);
dma.pending.count = data.bit(12,19);
dma.pending.skip.bit(3,11) = data.bit(23,31);
dma.full.read = 1;
dma.full.write = 0;
dmaTransferStart();
}
if(address == 3) {
//SP_WRITE_LENGTH
dma.write.length = data.bit( 0,11);
dma.write.count = data.bit(12,19);
dma.write.skip = data.bit(20,31);
if(!dma.requests.full()) {
DMA::Request request;
request.type = DMA::Request::Type::Write;
request.pbusRegion = dma.pbusRegion;
request.pbusAddress = dma.pbusAddress & ~7;
request.dramAddress = dma.dramAddress & ~7;
request.length = 1 + (dma.write.length | 7);
request.count = 1 + (dma.write.count);
request.skip = dma.write.skip & ~7;
dma.requests.write(request);
queue.insert(Queue::RSP_DMA, request.length * request.count / 4);
}
dma.pending.length.bit(3,11) = data.bit( 3,11);
dma.pending.count = data.bit(12,19);
dma.pending.skip.bit(3,11) = data.bit(23,31);
dma.full.write = 1;
dma.full.read = 0;
dmaTransferStart();
}
if(address == 4) {
+17 -5
View File
@@ -1,14 +1,14 @@
auto RSP::Recompiler::pool() -> Pool* {
if(context) return context;
u32 hashcode = 0;
nall::Hash::CRC32 hashcode;
for(u32 offset : range(4096)) {
hashcode = (hashcode << 5) + hashcode + self.imem.read<Byte>(offset);
hashcode.input(self.imem.read<Byte>(offset));
}
PoolHashPair pair;
pair.pool = (Pool*)allocator.acquire();
pair.hashcode = hashcode;
pair.hashcode = hashcode.value();
if(auto result = pools.find(pair)) {
return context = result->pool;
}
@@ -857,9 +857,13 @@ auto RSP::Recompiler::emitVU(u32 instruction) -> bool {
return 0;
}
//INVALID
//Invalid opcodes
case 0x1e ... 0x1f: {
return 0;
lea(reg(1), Vd);
lea(reg(2), Vs);
lea(reg(3), Vt);
callvu(&RSP::VZERO);
return 0;
}
//VLT Vd,Vs,Vt(e)
@@ -990,6 +994,10 @@ auto RSP::Recompiler::emitVU(u32 instruction) -> bool {
//INVALID
case 0x2e ... 0x2f: {
lea(reg(1), Vd);
lea(reg(2), Vs);
lea(reg(3), Vt);
callvu(&RSP::VZERO);
return 0;
}
@@ -1073,6 +1081,10 @@ auto RSP::Recompiler::emitVU(u32 instruction) -> bool {
//INVALID
case 0x3b: {
lea(reg(1), Vd);
lea(reg(2), Vs);
lea(reg(3), Vt);
callvu(&RSP::VZERO);
return 0;
}
+16 -21
View File
@@ -43,7 +43,8 @@ struct RSP : Thread, Memory::IO<RSP> {
} pipeline;
//dma.cpp
auto dmaTransfer() -> void;
auto dmaTransferStart() -> void;
auto dmaTransferStep() -> void;
//io.cpp
auto readWord(u32 address) -> u32;
@@ -55,29 +56,23 @@ struct RSP : Thread, Memory::IO<RSP> {
auto serialize(serializer&) -> void;
struct DMA {
n1 pbusRegion;
n12 pbusAddress;
n24 dramAddress;
struct Transfer {
n12 length;
n12 skip;
n8 count;
} read, write;
struct Request {
//serialization.cpp
auto serialize(serializer&) -> void;
enum class Type : u32 { Read, Write } type;
struct Regs {
n1 pbusRegion;
n12 pbusAddress;
n24 dramAddress;
n16 length;
n16 skip;
n16 count;
};
nall::queue<Request[2]> requests;
n12 length;
n12 skip;
n8 count;
auto serialize(serializer&) -> void;
} pending, current;
struct Status {
n1 read;
n1 write;
auto any() -> n1 { return read | write; }
} busy, full;
} dma;
struct Status : Memory::IO<Status> {
+7 -12
View File
@@ -6,16 +6,12 @@ auto RSP::serialize(serializer& s) -> void {
s(pipeline.address);
s(pipeline.instruction);
s(dma.pbusRegion);
s(dma.pbusAddress);
s(dma.dramAddress);
s(dma.read.length);
s(dma.read.skip);
s(dma.read.count);
s(dma.write.length);
s(dma.write.skip);
s(dma.write.count);
s(dma.requests);
s(dma.pending);
s(dma.current);
s(dma.busy.read);
s(dma.busy.write);
s(dma.full.read);
s(dma.full.write);
s(status.semaphore);
s(status.halted);
@@ -49,8 +45,7 @@ auto RSP::serialize(serializer& s) -> void {
}
}
auto RSP::DMA::Request::serialize(serializer& s) -> void {
s((u32&)type);
auto RSP::DMA::Regs::serialize(serializer& s) -> void {
s(pbusRegion);
s(pbusAddress);
s(dramAddress);
+1
View File
@@ -46,6 +46,7 @@ auto System::serialize(serializer& s, bool synchronize) -> void {
s(vi);
s(ai);
s(pi);
s(pif);
s(ri);
s(si);
s(cpu);
+83 -8
View File
@@ -23,6 +23,7 @@ auto Disc::command(u8 operation) -> void {
case 0x04: commandFastForward(); break;
case 0x05: commandRewind(); break;
case 0x06: commandReadWithRetry(); break;
case 0x07: commandMotorOn(); break;
case 0x08: commandStop(); break;
case 0x09: commandPause(); break;
case 0x0a: commandInitialize(); break;
@@ -42,12 +43,14 @@ auto Disc::command(u8 operation) -> void {
case 0x19: commandTest(); break;
case 0x1a: commandGetID(); break;
case 0x1b: commandReadWithoutRetry(); break;
case 0x1e: commandReadToc(); break;
case 0x20 ... 0x4f: commandInvalid(); break;
case 0x50 ... 0x57: commandInvalid(); break; //secret unlock commands
case 0x58 ... 0xff: commandInvalid(); break;
default: commandUnimplemented(operation); break;
}
fifo.parameter.flush();
debugger.commandEpilogue(operation);
}
@@ -58,6 +61,8 @@ auto Disc::commandTest() -> void {
debugger.commandPrologue(operation, suboperation);
switch(suboperation) {
case 0x04: commandTestStartReadSCEX(); break;
case 0x05: commandTestStopReadSCEX(); break;
case 0x20: commandTestControllerDate(); break;
default: commandUnimplemented(operation, suboperation); break;
}
@@ -153,13 +158,34 @@ auto Disc::commandReadWithRetry() -> void {
drive.seeking = 2 << drive.mode.speed;
drive.lba.current = drive.lba.request;
ssr.reading = 1;
ssr.playingCDDA = 0;
fifo.response.write(status());
irq.acknowledge.flag = 1;
irq.poll();
}
//0x07
auto Disc::commandMotorOn() -> void {
if(event.invocation == 0) {
event.invocation = 1;
event.counter = 50'000;
fifo.response.write(status());
irq.acknowledge.flag = 1;
irq.poll();
return;
}
if(event.invocation == 1) {
fifo.response.write(status());
irq.complete.flag = 1;
irq.poll();
return;
}
}
//0x08
auto Disc::commandStop() -> void {
if(event.invocation == 0) {
@@ -398,22 +424,49 @@ auto Disc::commandGetTrackStart() -> void {
//0x15
auto Disc::commandSeekData() -> void {
drive.lba.current = drive.lba.request;
if(event.invocation == 0) {
event.invocation = 1;
event.counter = 50'000; // TODO: Calculate seek time
fifo.response.write(status());
fifo.response.write(status());
irq.complete.flag = 1;
irq.poll();
irq.acknowledge.flag = 1;
irq.poll();
return;
}
if(event.invocation == 1) {
drive.lba.current = drive.lba.request;
ssr.playingCDDA = 0;
fifo.response.write(status());
irq.complete.flag = 1;
irq.poll();
return;
}
}
//0x16
auto Disc::commandSeekCDDA() -> void {
drive.lba.current = drive.lba.request;
ssr.playingCDDA = 0;
// TODO: use subchannel data to seek rather than LBA
commandSeekData();
}
//0x19 0x04
auto Disc::commandTestStartReadSCEX() -> void {
fifo.response.write(status());
irq.acknowledge.flag = 1;
irq.poll();
}
irq.complete.flag = 1;
//0x19 0x05
auto Disc::commandTestStopReadSCEX() -> void {
// Report no SCEX string found to appease mod-chip detection
fifo.response.write(0);
fifo.response.write(0);
irq.acknowledge.flag = 1;
irq.poll();
}
@@ -539,6 +592,28 @@ auto Disc::commandReadWithoutRetry() -> void {
return commandReadWithRetry();
}
//0x1e
auto Disc::commandReadToc() -> void {
if(event.invocation == 0) {
event.invocation = 1;
event.counter = 475'000;
fifo.response.write(status());
irq.acknowledge.flag = 1;
irq.poll();
return;
}
if(event.invocation == 1) {
fifo.response.write(status());
irq.complete.flag = 1;
irq.poll();
return;
}
}
auto Disc::commandUnimplemented(u8 operation, maybe<u8> suboperation) -> void {
if(!suboperation) {
debug(unimplemented, "Disc::command($", hex(operation, 2L), ")");
+4
View File
@@ -109,6 +109,10 @@ auto Disc::Debugger::commandPrologue(u8 operation, maybe<u8> suboperation) -> vo
name = "ReadWithoutRetry";
}
if(operation == 0x1e) {
name = "ReadToc";
}
if(!name) {
if(!suboperation) name = {"$", hex(operation, 2L)};
if( suboperation) name = {"Test$", hex(*suboperation, 2L)};
+4
View File
@@ -60,6 +60,7 @@ struct Disc : Thread, Memory::Interface {
auto commandFastForward() -> void;
auto commandRewind() -> void;
auto commandReadWithRetry() -> void;
auto commandMotorOn() -> void;
auto commandStop() -> void;
auto commandPause() -> void;
auto commandInitialize() -> void;
@@ -74,9 +75,12 @@ struct Disc : Thread, Memory::Interface {
auto commandGetTrackStart() -> void;
auto commandSeekData() -> void;
auto commandSeekCDDA() -> void;
auto commandTestStartReadSCEX() -> void;
auto commandTestStopReadSCEX() -> void;
auto commandTestControllerDate() -> void;
auto commandGetID() -> void;
auto commandReadWithoutRetry() -> void;
auto commandReadToc() -> void;
auto commandUnimplemented(u8, maybe<u8> = nothing) -> void;
//serialization.cpp
+4 -8
View File
@@ -1,32 +1,29 @@
auto SuperFX::read(n24 address, n8 data) -> n8 {
if((address & 0xc00000) == 0x000000) { //$00-3f:0000-7fff,:8000-ffff
address = Bus::mirror(address, rom.size());
while(!regs.scmr.ron) {
step(6);
synchronize(cpu);
if(scheduler.synchronizing()) break;
}
return rom.read((((address & 0x3f0000) >> 1) | (address & 0x7fff)));
return rom.read((((address & 0x3f0000) >> 1) | (address & 0x7fff)) & romMask);
}
if((address & 0xe00000) == 0x400000) { //$40-5f:0000-ffff
address = Bus::mirror(address, rom.size());
while(!regs.scmr.ron) {
step(6);
synchronize(cpu);
if(scheduler.synchronizing()) break;
}
return rom.read(address);
return rom.read(address & romMask);
}
if((address & 0xfe0000) == 0x700000) { //$70-71:0000-ffff
address = Bus::mirror(address, ram.size());
while(!regs.scmr.ran) {
step(6);
synchronize(cpu);
if(scheduler.synchronizing()) break;
}
return ram.read(address);
return ram.read(address & ramMask);
}
return data;
@@ -34,13 +31,12 @@ auto SuperFX::read(n24 address, n8 data) -> n8 {
auto SuperFX::write(n24 address, n8 data) -> void {
if((address & 0xfe0000) == 0x700000) { //$70-71:0000-ffff
address = Bus::mirror(address, ram.size());
while(!regs.scmr.ran) {
step(6);
synchronize(cpu);
if(scheduler.synchronizing()) break;
}
return ram.write(address, data);
return ram.write(address & ramMask, data);
}
}
+15 -1
View File
@@ -44,13 +44,27 @@ auto SuperFX::main() -> void {
}
}
// TODO: find a better place for this routine
auto romSizeRound(u32 romSize) -> u32 {
u32 count = 0;
if (romSize && !(romSize & (romSize - 1))) return romSize;
while(romSize != 0) {
romSize >>= 1;
count += 1;
}
return 1 << count;
}
auto SuperFX::power() -> void {
GSU::power();
Thread::create(Frequency, {&SuperFX::main, this});
cpu.coprocessors.append(this);
romMask = rom.size() - 1;
// SuperFX voxel demo has a non power-of-two rom
// resulting in an incorrect/broken rom mask
// fix this by ensuring romSize is a power of two
romMask = romSizeRound(rom.size()) - 1;
ramMask = ram.size() - 1;
bramMask = bram.size() - 1;
+3
View File
@@ -47,7 +47,10 @@ auto CPU::step(u32 clocks) -> void {
Thread::step(clocks);
for(auto peripheral : peripherals) Thread::synchronize(*peripheral);
#if !defined(PROFILE_PERFORMANCE)
for(auto coprocessor : coprocessors) Thread::synchronize(*coprocessor);
#endif
}
//called by ppu.tick() when Hcounter=0
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff. Show more