mirror of
https://github.com/ares-emulator/ares.git
synced 2026-11-12 18:06:03 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e6e9fd36e | ||
|
|
8111bf3163 | ||
|
|
c287c1b225 | ||
|
|
863593ed7f | ||
|
|
c20d405006 | ||
|
|
e1e1bedb2d | ||
|
|
9f060c769b | ||
|
|
e7f9af4bbd | ||
|
|
65c34c2ea4 | ||
|
|
9babe6a02b | ||
|
|
7e5292cd16 | ||
|
|
a8274cd127 | ||
|
|
d99b0a3725 | ||
|
|
e43f3f75f7 | ||
|
|
f894a85253 | ||
|
|
09fbcc4e65 | ||
|
|
73d90e6e7c | ||
|
|
38dd6defa9 | ||
|
|
030488aba7 | ||
|
|
b2175473f0 | ||
|
|
bc2a54800f | ||
|
|
6e182d8542 | ||
|
|
38f64fa70c | ||
|
|
7eb1b28671 | ||
|
|
42c16831b7 | ||
|
|
5982fa8ae3 | ||
|
|
5f435345f1 | ||
|
|
8343f8136c | ||
|
|
4dcba42240 | ||
|
|
81d5cdba32 | ||
|
|
5151003fa4 | ||
|
|
c6a838d6ee | ||
|
|
349f1d281c | ||
|
|
cac4aab301 | ||
|
|
5911338cac | ||
|
|
c0af33d4ce | ||
|
|
ea77623238 | ||
|
|
033752679f | ||
|
|
025fc6450a | ||
|
|
d6858ad821 | ||
|
|
0b46609e95 | ||
|
|
b5a8edc5d0 | ||
|
|
757a7dd670 | ||
|
|
950573df14 | ||
|
|
f43da416fd | ||
|
|
18f45ddc85 | ||
|
|
7135646c44 | ||
|
|
4ab8472a53 | ||
|
|
a47e793b57 | ||
|
|
1ffb4f6d5a | ||
|
|
aa350116d4 | ||
|
|
e8a5579cb7 | ||
|
|
f091fcfacd | ||
|
|
fa9d350a66 | ||
|
|
5ff5df0e2e | ||
|
|
efb7f61f04 | ||
|
|
3cd82c8cfe | ||
|
|
ace4115c86 | ||
|
|
31ca4552ea | ||
|
|
873aded84b | ||
|
|
4dc46976ef | ||
|
|
156603ceff | ||
|
|
12f0d04709 | ||
|
|
693447d0e0 | ||
|
|
27719b391b | ||
|
|
3455da48df | ||
|
|
dac6a2069f | ||
|
|
39174a2a79 | ||
|
|
23e322fd57 | ||
|
|
5c0c1fb4aa | ||
|
|
c27a62b624 | ||
|
|
9c93735045 | ||
|
|
9e03af99d9 | ||
|
|
eec5104909 | ||
|
|
00a67f1671 | ||
|
|
a76847dbd6 | ||
|
|
f209d544b0 | ||
|
|
5453fe084a | ||
|
|
426fb79639 | ||
|
|
33c645a441 | ||
|
|
ba9a291784 | ||
|
|
9fb67ba7a7 | ||
|
|
54794f86d9 | ||
|
|
7f43d0658e | ||
|
|
0d24374019 | ||
|
|
78a5e584b5 | ||
|
|
ffe97222a1 | ||
|
|
71e5f89f8b | ||
|
|
cac454a6a9 | ||
|
|
6c8265577c | ||
|
|
401aab6d64 | ||
|
|
c0fb9ba815 | ||
|
|
356208f312 | ||
|
|
fbf10d14fc | ||
|
|
723d8a8cd4 | ||
|
|
6eedb9399b | ||
|
|
36f821f5ac | ||
|
|
3bbfde5e9b | ||
|
|
b8e355b218 | ||
|
|
c34ed82fa3 | ||
|
|
3de728fa3b | ||
|
|
2b0738ec56 | ||
|
|
79e25f30bf | ||
|
|
5bf09dac13 | ||
|
|
b4fafd9028 | ||
|
|
21f730968b | ||
|
|
6cd4470562 | ||
|
|
393ba03358 | ||
|
|
9159438401 | ||
|
|
3372230021 | ||
|
|
8c4af6ac4c | ||
|
|
b5ee4124e1 | ||
|
|
4dbd6fad37 | ||
|
|
a876f4479d | ||
|
|
afa3f08f8c | ||
|
|
371d6eeaa8 | ||
|
|
e1a940cafa | ||
|
|
7c4a36a482 | ||
|
|
bea295a915 | ||
|
|
db1d29ebcf | ||
|
|
54845f6194 | ||
|
|
7c7d2c2155 | ||
|
|
edb88e2e3a | ||
|
|
ac75269342 | ||
|
|
bfc9636403 | ||
|
|
c6ca36e447 | ||
|
|
4e97f23446 | ||
|
|
d7dfae7a7a | ||
|
|
62f1512d44 | ||
|
|
e6481d975e | ||
|
|
61a94f025e | ||
|
|
d76e79076f | ||
|
|
a574410a03 | ||
|
|
dc97c814a3 | ||
|
|
9325b04b23 | ||
|
|
762a2302f7 | ||
|
|
2b6346df6e | ||
|
|
587aa20dcc | ||
|
|
4615a9f4b7 | ||
|
|
fc7cdb57f7 | ||
|
|
0a8f25e7d8 | ||
|
|
f3cdf203c3 | ||
|
|
76615ce847 | ||
|
|
5e04d6d31b | ||
|
|
2d81319a2f | ||
|
|
4b3b8b9f5b | ||
|
|
36df7ab00f | ||
|
|
47d6d25687 | ||
|
|
117f4f7b4e | ||
|
|
c23a3f736d | ||
|
|
dd25ac4f72 | ||
|
|
804e6699a4 | ||
|
|
c2dede6c85 | ||
|
|
6cabb5d118 | ||
|
|
5c92d877c8 | ||
|
|
9db8decbd6 | ||
|
|
0ef0a3e3a7 | ||
|
|
dbb3b85bb3 | ||
|
|
c94794e959 | ||
|
|
2abc056d06 | ||
|
|
9664144d1b | ||
|
|
f31d209883 | ||
|
|
21209edad6 | ||
|
|
8f3261e870 | ||
|
|
a65bf18645 | ||
|
|
a327e9b953 | ||
|
|
672e81572c | ||
|
|
deb1cf6d79 | ||
|
|
70f833957e | ||
|
|
8b5e30dad9 | ||
|
|
9fb9dd59ba | ||
|
|
67fc9ea81b | ||
|
|
4f834bd506 | ||
|
|
785c148768 | ||
|
|
cfda608bc2 | ||
|
|
f94e053767 | ||
|
|
6e8bcd9bda |
No files matched your search
@@ -90,7 +90,7 @@ jobs:
|
||||
keychain-name: ares-macos-keychain
|
||||
keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Windows Dependencies
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
fi
|
||||
- name: "macOS: recover MoltenVK cache"
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: moltenvk
|
||||
with:
|
||||
@@ -122,9 +122,11 @@ jobs:
|
||||
run: |
|
||||
if [ $ImageOS == "macos14" ]; then
|
||||
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.13
|
||||
echo "Set Xcode version to 15.2 for macos-latest build."
|
||||
elif [ $ImageOS == "macos12" ]; then
|
||||
sudo xcode-select -s /Applications/Xcode_13.4.app/Contents/Developer
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.9
|
||||
echo "Set Xcode version to 13.4 for macos-compat build."
|
||||
fi
|
||||
brew install make ninja cmake
|
||||
@@ -140,6 +142,7 @@ jobs:
|
||||
pushd thirdparty/MoltenVK
|
||||
./build-moltenvk.sh
|
||||
popd
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.15
|
||||
pushd thirdparty/librashader
|
||||
./build-librashader.sh
|
||||
popd
|
||||
@@ -181,7 +184,7 @@ jobs:
|
||||
MACOS_NOTARIZATION_PASSWORD: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
|
||||
MACOS_NOTARIZATION_TEAMID: ${{ secrets.MACOS_NOTARIZATION_TEAMID }}
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.program }}-${{ matrix.platform.name }}
|
||||
path: desktop-ui/out/*
|
||||
@@ -193,11 +196,11 @@ jobs:
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: 'src'
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: 'bin'
|
||||
- name: Package Artifacts
|
||||
|
||||
@@ -93,4 +93,4 @@ auto RIOT::readPortB() -> n8 {
|
||||
|
||||
auto RIOT::writePortB(n8 data) -> void {
|
||||
port[1].data = data;
|
||||
}
|
||||
}
|
||||
@@ -44,4 +44,4 @@ auto RIOT::power(bool reset) -> void {
|
||||
tvType = 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -47,4 +47,4 @@ struct RIOT : Thread {
|
||||
n1 tvTypeLatch;
|
||||
};
|
||||
|
||||
extern RIOT riot;
|
||||
extern RIOT riot;
|
||||
@@ -20,4 +20,4 @@ auto RIOT::serialize(serializer& s) -> void {
|
||||
s(rightDifficultyLatch);
|
||||
s(tvType);
|
||||
s(tvTypeLatch);
|
||||
}
|
||||
}
|
||||
@@ -61,4 +61,4 @@ auto TIA::AudioChannel::phase1() -> u8 {
|
||||
}
|
||||
|
||||
return pulseCounter.bit(0) * volume;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -140,4 +140,4 @@ auto TIA::resmp(n1 index, n8 data) -> void {
|
||||
|
||||
auto TIA::resbl() -> void {
|
||||
ball.reset();
|
||||
}
|
||||
}
|
||||
@@ -85,4 +85,4 @@ auto TIA::runMissile(n8 x, n1 index) -> n1 {
|
||||
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
*/
|
||||
@@ -7,4 +7,4 @@ auto TIA::runPlayfield(n8 x) -> n1 {
|
||||
}
|
||||
|
||||
return playfield.pixel;
|
||||
}
|
||||
}
|
||||
@@ -93,4 +93,4 @@ auto TIA::serialize(serializer& s) -> void {
|
||||
s(audio[n].control);
|
||||
s(audio[n].frequency);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,4 +152,4 @@ auto TIA::power(bool reset) -> void {
|
||||
volume[15] = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -172,4 +172,4 @@ struct TIA : Thread {
|
||||
f64 volume[16];
|
||||
};
|
||||
|
||||
extern TIA tia;
|
||||
extern TIA tia;
|
||||
@@ -43,4 +43,4 @@ auto TIA::WriteQueue::QueueItem::commit() -> void {
|
||||
case 0x2b: tia.hmclr(); return; // HMCLR
|
||||
}
|
||||
debug(unimplemented, "[TIA] writeQueue: ",hex(address), " = ", hex(data));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -10,7 +10,7 @@ Platform* platform = nullptr;
|
||||
bool _runAhead = false;
|
||||
|
||||
const string Name = "ares";
|
||||
const string Version = "138";
|
||||
const string Version = "141";
|
||||
const string Copyright = "ares team, Near";
|
||||
const string License = "ISC";
|
||||
const string LicenseURI = "https://opensource.org/licenses/ISC";
|
||||
|
||||
@@ -165,6 +165,8 @@ auto M93LCx6::ShiftRegister::read() -> n1 {
|
||||
}
|
||||
|
||||
auto M93LCx6::ShiftRegister::write(n1 bit) -> void {
|
||||
// wait for start bit
|
||||
if(!bit && !count) return;
|
||||
value <<= 1;
|
||||
value.bit(0) = bit;
|
||||
count++;
|
||||
|
||||
@@ -69,4 +69,4 @@ namespace ares {
|
||||
s(state.portB);
|
||||
s(state.portC);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,4 +32,5 @@ namespace ares {
|
||||
n8 portC;
|
||||
} state;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ struct ARM7TDMI {
|
||||
virtual auto step(u32 clocks) -> void = 0;
|
||||
virtual auto sleep() -> void = 0;
|
||||
virtual auto get(u32 mode, n32 address) -> n32 = 0;
|
||||
virtual auto getDebugger(u32 mode, n32 address) -> n32 { return get(mode, address); }
|
||||
virtual auto set(u32 mode, n32 address, n32 word) -> void = 0;
|
||||
|
||||
//arm7tdmi.cpp
|
||||
|
||||
@@ -17,12 +17,12 @@ auto ARM7TDMI::disassembleInstruction(maybe<n32> pc, maybe<boolean> thumb) -> st
|
||||
|
||||
_pc = pc();
|
||||
if(!thumb()) {
|
||||
n32 opcode = read(Word | Nonsequential, _pc & ~3);
|
||||
n32 opcode = getDebugger(Word | Nonsequential, _pc & ~3);
|
||||
n12 index = (opcode & 0x0ff00000) >> 16 | (opcode & 0x000000f0) >> 4;
|
||||
_c = _conditions[opcode >> 28];
|
||||
return pad(armDisassemble[index](opcode), -40);
|
||||
} else {
|
||||
n16 opcode = read(Half | Nonsequential, _pc & ~1);
|
||||
n16 opcode = getDebugger(Half | Nonsequential, _pc & ~1);
|
||||
return pad(thumbDisassemble[opcode](), -40);
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ auto ARM7TDMI::armDisassembleDataRegisterShift
|
||||
auto ARM7TDMI::armDisassembleLoadImmediate
|
||||
(n8 immediate, n1 half, n4 d, n4 n, n1 writeback, n1 up, n1 pre) -> string {
|
||||
string data;
|
||||
if(n == 15) data = {" =0x", hex(read((half ? Half : Byte) | Nonsequential,
|
||||
if(n == 15) data = {" =0x", hex(getDebugger((half ? Half : Byte) | Nonsequential,
|
||||
_pc + 8 + (up ? +immediate : -immediate)), half ? 4L : 2L)};
|
||||
|
||||
return {"ldr", _c, half ? "sh" : "sb", " ",
|
||||
@@ -150,7 +150,7 @@ auto ARM7TDMI::armDisassembleMemorySwap
|
||||
auto ARM7TDMI::armDisassembleMoveHalfImmediate
|
||||
(n8 immediate, n4 d, n4 n, n1 mode, n1 writeback, n1 up, n1 pre) -> string {
|
||||
string data;
|
||||
if(n == 15) data = {" =0x", hex(read(Half | Nonsequential, _pc + (up ? +immediate : -immediate)), 4L)};
|
||||
if(n == 15) data = {" =0x", hex(getDebugger(Half | Nonsequential, _pc + (up ? +immediate : -immediate)), 4L)};
|
||||
|
||||
return {mode ? "ldr" : "str", _c, "h ",
|
||||
_r[d], ",[", _r[n],
|
||||
@@ -173,7 +173,7 @@ auto ARM7TDMI::armDisassembleMoveHalfRegister
|
||||
auto ARM7TDMI::armDisassembleMoveImmediateOffset
|
||||
(n12 immediate, n4 d, n4 n, n1 mode, n1 writeback, n1 byte, n1 up, n1 pre) -> string {
|
||||
string data;
|
||||
if(n == 15) data = {" =0x", hex(read((byte ? Byte : Word) | Nonsequential,
|
||||
if(n == 15) data = {" =0x", hex(getDebugger((byte ? Byte : Word) | Nonsequential,
|
||||
_pc + 8 + (up ? +immediate : -immediate)), byte ? 2L : 4L)};
|
||||
return {mode ? "ldr" : "str", _c, byte ? "b" : "", " ", _r[d], ",[", _r[n],
|
||||
pre == 0 ? "]" : "",
|
||||
@@ -308,7 +308,7 @@ auto ARM7TDMI::thumbDisassembleBranchExchange
|
||||
|
||||
auto ARM7TDMI::thumbDisassembleBranchFarPrefix
|
||||
(i11 displacementHi) -> string {
|
||||
n11 displacementLo = read(Half | Nonsequential, (_pc & ~1) + 2);
|
||||
n11 displacementLo = getDebugger(Half | Nonsequential, (_pc & ~1) + 2);
|
||||
i22 displacement = displacementHi << 11 | displacementLo << 0;
|
||||
n32 address = _pc + 4 + displacement * 2;
|
||||
return {"bl 0x", hex(address, 8L)};
|
||||
@@ -340,7 +340,7 @@ auto ARM7TDMI::thumbDisassembleImmediate
|
||||
auto ARM7TDMI::thumbDisassembleLoadLiteral
|
||||
(n8 displacement, n3 d) -> string {
|
||||
n32 address = ((_pc + 4) & ~3) + (displacement << 2);
|
||||
n32 data = read(Word | Nonsequential, address);
|
||||
n32 data = getDebugger(Word | Nonsequential, address);
|
||||
return {"ldr ", _r[d], ",[pc,#0x", hex(address, 8L), "] =0x", hex(data, 8L)};
|
||||
}
|
||||
|
||||
|
||||
@@ -182,14 +182,15 @@ auto ARM7TDMI::armInstructionMoveImmediateOffset
|
||||
auto ARM7TDMI::armInstructionMoveMultiple
|
||||
(n16 list, n4 n, n1 mode, n1 writeback, n1 type, n1 up, n1 pre) -> void {
|
||||
n32 rn = r(n);
|
||||
n32 bitCount = list ? bit::count(list) : 16;
|
||||
if(pre == 0 && up == 1) rn = rn + 0; //IA
|
||||
if(pre == 1 && up == 1) rn = rn + 4; //IB
|
||||
if(pre == 1 && up == 0) rn = rn - bit::count(list) * 4 + 0; //DB
|
||||
if(pre == 0 && up == 0) rn = rn - bit::count(list) * 4 + 4; //DA
|
||||
if(pre == 1 && up == 0) rn = rn - bitCount * 4 + 0; //DB
|
||||
if(pre == 0 && up == 0) rn = rn - bitCount * 4 + 4; //DA
|
||||
|
||||
if(writeback && mode == 1) {
|
||||
if(up == 1) r(n) = r(n) + bit::count(list) * 4; //IA,IB
|
||||
if(up == 0) r(n) = r(n) - bit::count(list) * 4; //DA,DB
|
||||
if(writeback && mode == 1 && !list.bit(n)) {
|
||||
if(up == 1) r(n) = r(n) + bitCount * 4; //IA,IB
|
||||
if(up == 0) r(n) = r(n) - bitCount * 4; //DA,DB
|
||||
}
|
||||
|
||||
auto cpsrMode = cpsr().m;
|
||||
@@ -199,12 +200,17 @@ auto ARM7TDMI::armInstructionMoveMultiple
|
||||
if(usr) cpsr().m = PSR::USR;
|
||||
|
||||
u32 sequential = Nonsequential;
|
||||
for(u32 m : range(16)) {
|
||||
if(!list.bit(m)) continue;
|
||||
if(mode == 1) r(m) = read(Word | sequential, rn);
|
||||
if(mode == 0) write(Word | sequential, rn, r(m) + (m == 15 ? 4 : 0));
|
||||
rn += 4;
|
||||
sequential = Sequential;
|
||||
if(!list) {
|
||||
if(mode == 1) r(15) = read(Word | sequential, rn);
|
||||
if(mode == 0) write(Word | sequential, rn, r(15) + 4);
|
||||
} else {
|
||||
for(u32 m : range(16)) {
|
||||
if(!list.bit(m)) continue;
|
||||
if(mode == 1) r(m) = read(Word | sequential, rn);
|
||||
if(mode == 0) write(Word | sequential, rn, r(m) + (m == 15 ? 4 : 0));
|
||||
rn += 4;
|
||||
sequential = Sequential;
|
||||
}
|
||||
}
|
||||
|
||||
if(usr) cpsr().m = cpsrMode;
|
||||
@@ -219,8 +225,8 @@ auto ARM7TDMI::armInstructionMoveMultiple
|
||||
}
|
||||
|
||||
if(writeback && mode == 0) {
|
||||
if(up == 1) r(n) = r(n) + bit::count(list) * 4; //IA,IB
|
||||
if(up == 0) r(n) = r(n) - bit::count(list) * 4; //DA,DB
|
||||
if(up == 1) r(n) = r(n) + bitCount * 4; //IA,IB
|
||||
if(up == 0) r(n) = r(n) - bitCount * 4; //DA,DB
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,8 +255,7 @@ auto ARM7TDMI::armInstructionMoveRegisterOffset
|
||||
|
||||
auto ARM7TDMI::armInstructionMoveToRegisterFromStatus
|
||||
(n4 d, n1 mode) -> void {
|
||||
if(mode && (cpsr().m == PSR::USR || cpsr().m == PSR::SYS)) return;
|
||||
r(d) = mode ? spsr() : cpsr();
|
||||
r(d) = (mode && cpsr().m != PSR::USR && cpsr().m != PSR::SYS) ? spsr() : cpsr();
|
||||
}
|
||||
|
||||
auto ARM7TDMI::armInstructionMoveToStatusFromImmediate
|
||||
|
||||
@@ -125,23 +125,29 @@ auto ARM7TDMI::thumbInstructionMoveHalfImmediate
|
||||
auto ARM7TDMI::thumbInstructionMoveMultiple
|
||||
(n8 list, n3 n, n1 mode) -> void {
|
||||
n32 rn = r(n);
|
||||
n32 bitCount = list ? bit::count(list) : 16;
|
||||
|
||||
if(mode == 1 && !list.bit(n)) r(n) = r(n) + bitCount * 4;
|
||||
|
||||
u32 sequential = Nonsequential;
|
||||
for(u32 m : range(8)) {
|
||||
if(!list.bit(m)) continue;
|
||||
switch(mode) {
|
||||
case 0: write(Word | sequential, rn, r(m)); break; //STMIA
|
||||
case 1: r(m) = read(Word | sequential, rn); break; //LDMIA
|
||||
if(!list) {
|
||||
if(mode == 1) r(15) = read(Word | sequential, rn);
|
||||
if(mode == 0) write(Word | sequential, rn, r(15) + 2);
|
||||
} else {
|
||||
for(u32 m : range(8)) {
|
||||
if(!list.bit(m)) continue;
|
||||
if(mode == 1) r(m) = read(Word | sequential, rn); //LDMIA
|
||||
if(mode == 0) write(Word | sequential, rn, r(m)); //STMIA
|
||||
rn += 4;
|
||||
sequential = Sequential;
|
||||
}
|
||||
rn += 4;
|
||||
sequential = Sequential;
|
||||
}
|
||||
|
||||
if(mode == 0 || !list.bit(n)) r(n) = rn;
|
||||
if(mode == 1) {
|
||||
if(mode) {
|
||||
idle();
|
||||
} else {
|
||||
pipeline.nonsequential = true;
|
||||
r(n) = r(n) + bitCount * 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,37 +197,35 @@ auto ARM7TDMI::thumbInstructionSoftwareInterrupt
|
||||
|
||||
auto ARM7TDMI::thumbInstructionStackMultiple
|
||||
(n8 list, n1 lrpc, n1 mode) -> void {
|
||||
n32 sp;
|
||||
switch(mode) {
|
||||
case 0: sp = r(13) - (bit::count(list) + lrpc) * 4; break; //PUSH
|
||||
case 1: sp = r(13); //POP
|
||||
}
|
||||
n32 sp = r(13);
|
||||
n32 bitCount = (list | lrpc) ? (bit::count(list) + lrpc) : 16;
|
||||
if(mode == 1) r(13) = r(13) + bitCount * 4; //POP
|
||||
if(mode == 0) sp = sp - bitCount * 4 + 0; //PUSH
|
||||
|
||||
u32 sequential = Nonsequential;
|
||||
for(u32 m : range(8)) {
|
||||
if(!list.bit(m)) continue;
|
||||
switch(mode) {
|
||||
case 0: write(Word | sequential, sp, r(m)); break; //PUSH
|
||||
case 1: r(m) = read(Word | sequential, sp); break; //POP
|
||||
if(bitCount == 16) {
|
||||
if(mode == 1) r(15) = read(Word | sequential, sp); //POP
|
||||
if(mode == 0) write(Word | sequential, sp, r(15) + 2); //PUSH
|
||||
} else {
|
||||
for(u32 m : range(8)) {
|
||||
if(!list.bit(m)) continue;
|
||||
if(mode == 1) r(m) = read(Word | sequential, sp); //POP
|
||||
if(mode == 0) write(Word | sequential, sp, r(m)); //PUSH
|
||||
sp += 4;
|
||||
sequential = Sequential;
|
||||
}
|
||||
sp += 4;
|
||||
sequential = Sequential;
|
||||
}
|
||||
|
||||
if(lrpc) {
|
||||
switch(mode) {
|
||||
case 0: write(Word | sequential, sp, r(14)); break; //PUSH
|
||||
case 1: r(15) = read(Word | sequential, sp); break; //POP
|
||||
}
|
||||
if(mode == 1) r(15) = read(Word | sequential, sp); //POP
|
||||
if(mode == 0) write(Word | sequential, sp, r(14)); //PUSH
|
||||
sp += 4;
|
||||
}
|
||||
|
||||
if(mode == 1) {
|
||||
if(mode) {
|
||||
idle();
|
||||
r(13) = r(13) + (bit::count(list) + lrpc) * 4; //POP
|
||||
} else {
|
||||
pipeline.nonsequential = true;
|
||||
r(13) = r(13) - (bit::count(list) + lrpc) * 4; //PUSH
|
||||
r(13) = r(13) - bitCount * 4; //PUSH
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,8 +75,8 @@ struct GSU {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//disassembler.cpp
|
||||
noinline auto disassembleInstruction() -> string;
|
||||
noinline auto disassembleContext() -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction() -> string;
|
||||
NALL_NOINLINE auto disassembleContext() -> string;
|
||||
|
||||
auto disassembleOpcode(char* output) -> void;
|
||||
auto disassembleALT0(char* output) -> void;
|
||||
|
||||
@@ -111,8 +111,8 @@ struct HG51B {
|
||||
auto writeRegister(n7 address, n24 data) -> void;
|
||||
|
||||
//disassembler.cpp
|
||||
noinline auto disassembleInstruction(maybe<n15> pb = {}, maybe<n8> pc = {}) -> string;
|
||||
noinline auto disassembleContext() -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction(maybe<n15> pb = {}, maybe<n8> pc = {}) -> string;
|
||||
NALL_NOINLINE auto disassembleContext() -> string;
|
||||
|
||||
protected:
|
||||
struct Registers {
|
||||
|
||||
@@ -124,8 +124,8 @@ struct I8080 {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//disassembler.cpp
|
||||
noinline auto disassembleInstruction(maybe<n16> pc = {}) -> string;
|
||||
noinline auto disassembleContext() -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction(maybe<n16> pc = {}) -> string;
|
||||
NALL_NOINLINE auto disassembleContext() -> string;
|
||||
|
||||
auto disassemble(n16 pc, n8 prefix, n8 code) -> string;
|
||||
|
||||
|
||||
@@ -156,8 +156,8 @@ struct MOS6502 {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//disassembler.cpp
|
||||
noinline auto disassembleInstruction(maybe<n16> pc = {}) -> string;
|
||||
noinline auto disassembleContext() -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction(maybe<n16> pc = {}) -> string;
|
||||
NALL_NOINLINE auto disassembleContext() -> string;
|
||||
|
||||
struct PR {
|
||||
bool c; //carry
|
||||
|
||||
@@ -14,7 +14,7 @@ auto SH2::delaySlot(u32 pc) -> void {
|
||||
}
|
||||
|
||||
auto SH2::instruction() -> void {
|
||||
if constexpr(Accuracy::Interpreter) {
|
||||
if(Accuracy::Interpreter || !recompiler.enabled) {
|
||||
step(1);
|
||||
exceptionHandler();
|
||||
if constexpr(Accuracy::AddressErrors) {
|
||||
@@ -26,9 +26,7 @@ auto SH2::instruction() -> void {
|
||||
instructionPrologue(opcode);
|
||||
execute(opcode);
|
||||
instructionEpilogue();
|
||||
}
|
||||
|
||||
if constexpr(Accuracy::Recompiler) {
|
||||
} else {
|
||||
exceptionHandler();
|
||||
|
||||
// Recompiled blocks may be very small, negating the impact
|
||||
|
||||
@@ -513,19 +513,19 @@ auto SH2::MOVI(u32 i, u32 n) -> void {
|
||||
}
|
||||
|
||||
//MOV.W @(disp,PC),Rn
|
||||
noinline auto SH2::MOVWI(u32 d, u32 n) -> void {
|
||||
NALL_NOINLINE auto SH2::MOVWI(u32 d, u32 n) -> void {
|
||||
u32 pc = inDelaySlot() ? PPC - 2 : PC;
|
||||
R[n] = (s16)readWord(pc + d * 2);
|
||||
}
|
||||
|
||||
//MOV.L @(disp,PC),Rn
|
||||
noinline auto SH2::MOVLI(u32 d, u32 n) -> void {
|
||||
NALL_NOINLINE auto SH2::MOVLI(u32 d, u32 n) -> void {
|
||||
u32 pc = inDelaySlot() ? PPC - 2 : PC;
|
||||
R[n] = readLong((pc & ~3) + d * 4);
|
||||
}
|
||||
|
||||
//MOVA @(disp,PC),R0
|
||||
noinline auto SH2::MOVA(u32 d) -> void {
|
||||
NALL_NOINLINE auto SH2::MOVA(u32 d) -> void {
|
||||
u32 pc = inDelaySlot() ? PPC - 2 : PC;
|
||||
R[0] = (pc & ~3) + d * 4;
|
||||
}
|
||||
|
||||
@@ -300,6 +300,7 @@ struct SH2 {
|
||||
|
||||
static auto mask(u8 address, u8 size) -> u64;
|
||||
|
||||
bool enabled = false;
|
||||
bool callInstructionPrologue = false;
|
||||
bool inDelaySlot;
|
||||
u32 generation;
|
||||
|
||||
@@ -29,8 +29,8 @@ struct SM83 {
|
||||
|
||||
//disassembler.cpp
|
||||
virtual auto readDebugger(n16 address) -> n8 { return 0; }
|
||||
noinline auto disassembleInstruction(maybe<n16> pc = {}) -> string;
|
||||
noinline auto disassembleContext() -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction(maybe<n16> pc = {}) -> string;
|
||||
NALL_NOINLINE auto disassembleContext() -> string;
|
||||
|
||||
//memory.cpp
|
||||
auto operand() -> n8;
|
||||
|
||||
@@ -117,9 +117,9 @@ struct SPC700 {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//disassembler.cpp
|
||||
noinline auto disassembleInstruction(n16 address, n1 p) -> string;
|
||||
noinline auto disassembleInstruction() -> string;
|
||||
noinline auto disassembleContext() -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction(n16 address, n1 p) -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction() -> string;
|
||||
NALL_NOINLINE auto disassembleContext() -> string;
|
||||
|
||||
struct Flags {
|
||||
bool c; //carry
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
noinline auto TLCS900H::disassembleInstruction() -> string {
|
||||
NALL_NOINLINE auto TLCS900H::disassembleInstruction() -> string {
|
||||
string output;
|
||||
|
||||
auto pc = load(PC);
|
||||
@@ -636,7 +636,7 @@ noinline auto TLCS900H::disassembleInstruction() -> string {
|
||||
return pad(output, -48);
|
||||
}
|
||||
|
||||
noinline auto TLCS900H::disassembleContext() -> string {
|
||||
NALL_NOINLINE auto TLCS900H::disassembleContext() -> string {
|
||||
string output;
|
||||
output.append("XWA:", hex(r.xwa[RFP].l.l0, 8L), " ");
|
||||
output.append("XBC:", hex(r.xbc[RFP].l.l0, 8L), " ");
|
||||
|
||||
@@ -25,8 +25,8 @@ struct uPD96050 {
|
||||
auto writeDP(n12 address, n8 data) -> void;
|
||||
|
||||
//disassembler.cpp
|
||||
noinline auto disassembleInstruction(maybe<n14> ip = {}) -> string;
|
||||
noinline auto disassembleContext() -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction(maybe<n14> ip = {}) -> string;
|
||||
NALL_NOINLINE auto disassembleContext() -> string;
|
||||
|
||||
enum class Revision : u32 { uPD7725, uPD96050 } revision;
|
||||
n24 programROM[16384];
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
auto V30MZ::prefixFlush() -> void {
|
||||
prefix.count = 0;
|
||||
prefix.lock = 0;
|
||||
prefix.repeat = 0;
|
||||
prefix.segment = 0;
|
||||
}
|
||||
|
||||
auto V30MZ::interrupt(u8 vector, InterruptSource source) -> bool {
|
||||
if(source == InterruptSource::INT) wait(32);
|
||||
else if (source == InterruptSource::NMI) wait(26);
|
||||
@@ -5,15 +12,15 @@ auto V30MZ::interrupt(u8 vector, InterruptSource source) -> bool {
|
||||
|
||||
state.halt = 0;
|
||||
state.poll = 1;
|
||||
state.prefix = 0;
|
||||
|
||||
//if an IRQ fires during a rep string instruction;
|
||||
//flush prefix queue and seek back to first prefix.
|
||||
//this allows the transfer to resume after the IRQ.
|
||||
if(repeat()) {
|
||||
PC -= prefixes.size();
|
||||
prefixes.flush();
|
||||
if(state.prefix) {
|
||||
state.prefix = 0;
|
||||
PC -= prefix.count;
|
||||
}
|
||||
prefixFlush();
|
||||
|
||||
auto pc = read<Word>(0x0000, vector * 4 + 0);
|
||||
auto ps = read<Word>(0x0000, vector * 4 + 2);
|
||||
@@ -309,7 +316,7 @@ auto V30MZ::instruction() -> void {
|
||||
op(0xff, Group4MemImm<Word>)
|
||||
}
|
||||
|
||||
if(!state.prefix) prefixes.flush();
|
||||
if(!state.prefix) prefixFlush();
|
||||
if(PSW.BRK) interrupt(1, InterruptSource::SingleStep);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ auto V30MZ::instructionDecimalAdjust(bool negate) -> void {
|
||||
al = AL;
|
||||
AL += negate ? -0x60 : 0x60;
|
||||
PSW.CY = 1;
|
||||
if(negate) PSW.V |= (al ^ 0x06) & (al ^ AL) & 0x80; // undefined
|
||||
if(negate) PSW.V |= (al ^ 0x60) & (al ^ AL) & 0x80; // undefined
|
||||
else PSW.V |= (AL ^ al) & (AL ^ 0x60) & 0x80; // undefined
|
||||
} else {
|
||||
PSW.CY = 0;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
auto V30MZ::instructionSegment(n16 segment) -> void {
|
||||
if(prefixes.full()) prefixes.read(0);
|
||||
prefixes.write(opcode);
|
||||
prefix.segment = opcode;
|
||||
prefix.count++;
|
||||
state.prefix = 1;
|
||||
state.poll = 0;
|
||||
}
|
||||
|
||||
auto V30MZ::instructionRepeat() -> void {
|
||||
if(prefixes.full()) prefixes.read(0);
|
||||
prefixes.write(opcode);
|
||||
prefix.repeat = opcode;
|
||||
prefix.count++;
|
||||
wait(4);
|
||||
state.prefix = 1;
|
||||
state.poll = 0;
|
||||
}
|
||||
|
||||
auto V30MZ::instructionLock() -> void {
|
||||
if(prefixes.full()) prefixes.read(0);
|
||||
prefixes.write(opcode);
|
||||
prefix.lock = opcode;
|
||||
prefix.count++;
|
||||
state.prefix = 1;
|
||||
state.poll = 0;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
template<u32 size> auto V30MZ::instructionInString() -> void {
|
||||
wait(3);
|
||||
if(!repeat() || CW) {
|
||||
if(!prefix.repeat || CW) {
|
||||
auto data = in<size>(DW);
|
||||
write<size>(DS1, IY, data);
|
||||
IY += PSW.DIR ? -size : size;
|
||||
|
||||
if(!repeat() || !--CW) return;
|
||||
if(!prefix.repeat || !--CW) return;
|
||||
|
||||
state.prefix = 1;
|
||||
PC--;
|
||||
@@ -15,12 +15,12 @@ template<u32 size> auto V30MZ::instructionInString() -> void {
|
||||
|
||||
template<u32 size> auto V30MZ::instructionOutString() -> void {
|
||||
wait(3);
|
||||
if(!repeat() || CW) {
|
||||
if(!prefix.repeat || CW) {
|
||||
auto data = read<size>(segment(DS0), IX);
|
||||
out<size>(DW, data);
|
||||
IX += PSW.DIR ? -size : size;
|
||||
|
||||
if(!repeat() || !--CW) return;
|
||||
if(!prefix.repeat || !--CW) return;
|
||||
|
||||
state.prefix = 1;
|
||||
PC--;
|
||||
@@ -30,13 +30,13 @@ template<u32 size> auto V30MZ::instructionOutString() -> void {
|
||||
|
||||
template<u32 size> auto V30MZ::instructionMoveString() -> void {
|
||||
wait(3);
|
||||
if(!repeat() || CW) {
|
||||
if(!prefix.repeat || CW) {
|
||||
auto data = read<size>(segment(DS0), IX);
|
||||
write<size>(DS1, IY, data);
|
||||
IX += PSW.DIR ? -size : size;
|
||||
IY += PSW.DIR ? -size : size;
|
||||
|
||||
if(!repeat() || !--CW) return;
|
||||
if(!prefix.repeat || !--CW) return;
|
||||
|
||||
state.prefix = 1;
|
||||
PC--;
|
||||
@@ -46,16 +46,16 @@ template<u32 size> auto V30MZ::instructionMoveString() -> void {
|
||||
|
||||
template<u32 size> auto V30MZ::instructionCompareString() -> void {
|
||||
wait(4);
|
||||
if(!repeat() || CW) {
|
||||
if(!prefix.repeat || CW) {
|
||||
auto x = read<size>(segment(DS0), IX);
|
||||
auto y = read<size>(DS1, IY);
|
||||
IX += PSW.DIR ? -size : size;
|
||||
IY += PSW.DIR ? -size : size;
|
||||
SUB<size>(x, y);
|
||||
|
||||
if(!repeat() || !--CW) return;
|
||||
if(repeat() == RepeatWhileZeroLo && PSW.Z == 1) return;
|
||||
if(repeat() == RepeatWhileZeroHi && PSW.Z == 0) return;
|
||||
if(!prefix.repeat || !--CW) return;
|
||||
if(prefix.repeat == RepeatWhileZeroLo && PSW.Z == 1) return;
|
||||
if(prefix.repeat == RepeatWhileZeroHi && PSW.Z == 0) return;
|
||||
|
||||
state.prefix = 1;
|
||||
PC--;
|
||||
@@ -65,11 +65,11 @@ template<u32 size> auto V30MZ::instructionCompareString() -> void {
|
||||
|
||||
template<u32 size> auto V30MZ::instructionStoreString() -> void {
|
||||
wait(2);
|
||||
if(!repeat() || CW) {
|
||||
if(!prefix.repeat || CW) {
|
||||
write<size>(DS1, IY, getAccumulator<size>());
|
||||
IY += PSW.DIR ? -size : size;
|
||||
|
||||
if(!repeat() || !--CW) return;
|
||||
if(!prefix.repeat || !--CW) return;
|
||||
|
||||
state.prefix = 1;
|
||||
PC--;
|
||||
@@ -79,11 +79,11 @@ template<u32 size> auto V30MZ::instructionStoreString() -> void {
|
||||
|
||||
template<u32 size> auto V30MZ::instructionLoadString() -> void {
|
||||
wait(2);
|
||||
if(!repeat() || CW) {
|
||||
if(!prefix.repeat || CW) {
|
||||
setAccumulator<size>(read<size>(segment(DS0), IX));
|
||||
IX += PSW.DIR ? -size : size;
|
||||
|
||||
if(!repeat() || !--CW) return;
|
||||
if(!prefix.repeat || !--CW) return;
|
||||
|
||||
state.prefix = 1;
|
||||
PC--;
|
||||
@@ -93,15 +93,15 @@ template<u32 size> auto V30MZ::instructionLoadString() -> void {
|
||||
|
||||
template<u32 size> auto V30MZ::instructionScanString() -> void {
|
||||
wait(3);
|
||||
if(!repeat() || CW) {
|
||||
if(!prefix.repeat || CW) {
|
||||
auto x = getAccumulator<size>();
|
||||
auto y = read<size>(DS1, IY);
|
||||
IY += PSW.DIR ? -size : size;
|
||||
SUB<size>(x, y);
|
||||
|
||||
if(!repeat() || !--CW) return;
|
||||
if(repeat() == RepeatWhileZeroLo && PSW.Z == 1) return;
|
||||
if(repeat() == RepeatWhileZeroHi && PSW.Z == 0) return;
|
||||
if(!prefix.repeat || !--CW) return;
|
||||
if(prefix.repeat == RepeatWhileZeroLo && PSW.Z == 1) return;
|
||||
if(prefix.repeat == RepeatWhileZeroHi && PSW.Z == 0) return;
|
||||
|
||||
state.prefix = 1;
|
||||
PC--;
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
auto V30MZ::repeat() -> u8 {
|
||||
for(auto prefix : prefixes) {
|
||||
if(prefix == RepeatWhileZeroLo) return prefix;
|
||||
if(prefix == RepeatWhileZeroHi) return prefix;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto V30MZ::segment(u16 segment) -> u16 {
|
||||
for(auto prefix : prefixes) {
|
||||
if(prefix == SegmentOverrideDS1) return DS1;
|
||||
if(prefix == SegmentOverridePS ) return PS;
|
||||
if(prefix == SegmentOverrideSS ) return SS;
|
||||
if(prefix == SegmentOverrideDS0) return DS0;
|
||||
}
|
||||
if(prefix.segment == SegmentOverrideDS1) return DS1;
|
||||
if(prefix.segment == SegmentOverridePS ) return PS;
|
||||
if(prefix.segment == SegmentOverrideSS ) return SS;
|
||||
if(prefix.segment == SegmentOverrideDS0) return DS0;
|
||||
return segment;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ auto V30MZ::serialize(serializer& s) -> void {
|
||||
s(state.prefix);
|
||||
|
||||
s(opcode);
|
||||
s(prefixes);
|
||||
|
||||
s(prefix.count);
|
||||
s(prefix.lock);
|
||||
s(prefix.repeat);
|
||||
s(prefix.segment);
|
||||
|
||||
s(modrm.mod);
|
||||
s(modrm.reg);
|
||||
|
||||
@@ -28,7 +28,7 @@ auto V30MZ::power() -> void {
|
||||
state.prefix = 0;
|
||||
|
||||
opcode = 0;
|
||||
prefixes.flush();
|
||||
prefixFlush();
|
||||
modrm.mod = 0;
|
||||
modrm.reg = 0;
|
||||
modrm.mem = 0;
|
||||
|
||||
@@ -39,6 +39,7 @@ struct V30MZ {
|
||||
auto power() -> void;
|
||||
|
||||
//instruction.cpp
|
||||
auto prefixFlush() -> void;
|
||||
auto interrupt(u8 vector) -> bool;
|
||||
auto nonMaskableInterrupt() -> bool;
|
||||
auto instruction() -> void;
|
||||
@@ -242,7 +243,12 @@ struct V30MZ {
|
||||
} state;
|
||||
|
||||
u8 opcode;
|
||||
queue<u8[7]> prefixes;
|
||||
struct Prefix {
|
||||
u16 count;
|
||||
u8 lock;
|
||||
u8 repeat;
|
||||
u8 segment;
|
||||
} prefix;
|
||||
|
||||
struct ModRM {
|
||||
u2 mod;
|
||||
|
||||
@@ -235,9 +235,9 @@ struct WDC65816 {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//disassembler.cpp
|
||||
noinline auto disassembleInstruction(n24 address, bool e, bool m, bool x) -> string;
|
||||
noinline auto disassembleInstruction() -> string;
|
||||
noinline auto disassembleContext(maybe<bool> e = {}) -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction(n24 address, bool e, bool m, bool x) -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction() -> string;
|
||||
NALL_NOINLINE auto disassembleContext(maybe<bool> e = {}) -> string;
|
||||
|
||||
struct f8 {
|
||||
bool c = 0; //carry
|
||||
|
||||
@@ -211,8 +211,8 @@ struct Z80 {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//disassembler.cpp
|
||||
noinline auto disassembleInstruction(maybe<n16> pc = {}) -> string;
|
||||
noinline auto disassembleContext() -> string;
|
||||
NALL_NOINLINE auto disassembleInstruction(maybe<n16> pc = {}) -> string;
|
||||
NALL_NOINLINE auto disassembleContext() -> string;
|
||||
|
||||
auto disassemble(n16 pc, n8 prefix, n8 code) -> string;
|
||||
auto disassembleCB(n16 pc, n8 prefix, n8 code) -> string;
|
||||
|
||||
+3
-1
@@ -148,13 +148,14 @@ struct APU : Thread {
|
||||
auto clock() -> n8;
|
||||
|
||||
auto power(bool reset) -> void;
|
||||
auto setDMABuffer(n8 data) -> void;
|
||||
auto dmaAddress() -> n16 const { return 0x8000 | readAddress; }
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
n16 lengthCounter;
|
||||
n16 periodCounter;
|
||||
n16 dmaDelayCounter;
|
||||
n1 irqPending;
|
||||
n4 period;
|
||||
n1 irqEnable;
|
||||
@@ -168,6 +169,7 @@ struct APU : Thread {
|
||||
n8 dmaBuffer;
|
||||
n1 sampleValid;
|
||||
n8 sample;
|
||||
n8 dmaDelayCounter;
|
||||
} dmc;
|
||||
|
||||
struct FrameCounter {
|
||||
|
||||
+26
-34
@@ -2,44 +2,19 @@ auto APU::DMC::start() -> void {
|
||||
if(lengthCounter == 0) {
|
||||
readAddress = 0x4000 + (addressLatch << 6);
|
||||
lengthCounter = (lengthLatch << 4) + 1;
|
||||
|
||||
if (!dmaBufferValid)
|
||||
dmaDelayCounter = periodCounter & 1 ? 2 : 3;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::DMC::stop() -> void {
|
||||
lengthCounter = 0;
|
||||
dmaDelayCounter = 0;
|
||||
cpu.rdyLine(1);
|
||||
cpu.rdyAddress(false);
|
||||
}
|
||||
|
||||
auto APU::DMC::clock() -> n8 {
|
||||
n8 result = dacLatch;
|
||||
|
||||
if(dmaDelayCounter > 0) {
|
||||
dmaDelayCounter--;
|
||||
|
||||
if(dmaDelayCounter == 1) {
|
||||
cpu.rdyAddress(true, 0x8000 | readAddress);
|
||||
} else if(dmaDelayCounter == 0) {
|
||||
cpu.rdyLine(1);
|
||||
cpu.rdyAddress(false);
|
||||
|
||||
dmaBuffer = cpu.io.openBus;
|
||||
dmaBufferValid = true;
|
||||
lengthCounter--;
|
||||
readAddress++;
|
||||
|
||||
if(lengthCounter == 0) {
|
||||
if(loopMode) {
|
||||
start();
|
||||
} else if(irqEnable) {
|
||||
irqPending = true;
|
||||
apu.setIRQ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(--periodCounter == 0) {
|
||||
if(sampleValid) {
|
||||
s32 delta = (((sample >> bitCounter) & 1) << 2) - 2;
|
||||
@@ -52,6 +27,9 @@ auto APU::DMC::clock() -> n8 {
|
||||
sampleValid = true;
|
||||
sample = dmaBuffer;
|
||||
dmaBufferValid = false;
|
||||
|
||||
if (lengthCounter > 0)
|
||||
cpu.dmcDMAPending();
|
||||
} else {
|
||||
sampleValid = false;
|
||||
}
|
||||
@@ -60,10 +38,8 @@ auto APU::DMC::clock() -> n8 {
|
||||
periodCounter = Region::PAL() ? dmcPeriodTablePAL[period] : dmcPeriodTableNTSC[period];
|
||||
}
|
||||
|
||||
if(lengthCounter > 0 && !dmaBufferValid && dmaDelayCounter == 0) {
|
||||
cpu.rdyLine(0);
|
||||
dmaDelayCounter = 4;
|
||||
}
|
||||
if (dmaDelayCounter > 0 && --dmaDelayCounter == 0)
|
||||
cpu.dmcDMAPending();
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -71,8 +47,7 @@ auto APU::DMC::clock() -> n8 {
|
||||
auto APU::DMC::power(bool reset) -> void {
|
||||
lengthCounter = 0;
|
||||
periodCounter = Region::PAL() ? dmcPeriodTablePAL[0] : dmcPeriodTableNTSC[0];
|
||||
dmaDelayCounter = 0;
|
||||
irqPending = 0;
|
||||
irqPending = false;
|
||||
period = 0;
|
||||
irqEnable = 0;
|
||||
loopMode = 0;
|
||||
@@ -86,3 +61,20 @@ auto APU::DMC::power(bool reset) -> void {
|
||||
sampleValid = 0;
|
||||
sample = 0;
|
||||
}
|
||||
|
||||
auto APU::DMC::setDMABuffer(n8 data) -> void {
|
||||
dmaBuffer = data;
|
||||
dmaBufferValid = true;
|
||||
lengthCounter--;
|
||||
readAddress++;
|
||||
|
||||
if (lengthCounter == 0) {
|
||||
if (loopMode) {
|
||||
readAddress = 0x4000 + (addressLatch << 6);
|
||||
lengthCounter = (lengthLatch << 4) + 1;
|
||||
} else if (irqEnable) {
|
||||
irqPending = true;
|
||||
apu.setIRQ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,8 +59,8 @@ auto APU::FrameCounter::power(bool reset) -> void {
|
||||
counter = getPeriod();
|
||||
|
||||
odd = true;
|
||||
delay = true;
|
||||
delayCounter = 1;
|
||||
delay = false;
|
||||
delayCounter = 0;
|
||||
}
|
||||
|
||||
auto APU::FrameCounter::write(n8 data) -> void {
|
||||
|
||||
@@ -70,7 +70,6 @@ auto APU::Noise::serialize(serializer& s) -> void {
|
||||
auto APU::DMC::serialize(serializer& s) -> void {
|
||||
s(lengthCounter);
|
||||
s(periodCounter);
|
||||
s(dmaDelayCounter);
|
||||
s(irqPending);
|
||||
s(period);
|
||||
s(irqEnable);
|
||||
@@ -84,6 +83,7 @@ auto APU::DMC::serialize(serializer& s) -> void {
|
||||
s(dmaBuffer);
|
||||
s(sampleValid);
|
||||
s(sample);
|
||||
s(dmaDelayCounter);
|
||||
}
|
||||
|
||||
auto APU::FrameCounter::serialize(serializer& s) -> void {
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Board {
|
||||
#include "bandai-karaoke.cpp"
|
||||
#include "bandai-lz93d50.cpp"
|
||||
#include "bandai-oeka.cpp"
|
||||
#include "camerica-bf909x.cpp"
|
||||
#include "colordreams-74x377.cpp"
|
||||
#include "gtrom.cpp"
|
||||
#include "jaleco-jf05.cpp"
|
||||
@@ -54,8 +55,14 @@ namespace Board {
|
||||
#include "taito-tc0690.cpp"
|
||||
#include "taito-x1-005.cpp"
|
||||
#include "taito-x1-017.cpp"
|
||||
#include "tengen-80003x.cpp"
|
||||
#include "unrom-512.cpp"
|
||||
|
||||
#include "unl-bmc.cpp"
|
||||
#include "unl-sachen-74ls374n.cpp"
|
||||
#include "unl-sachen-sa0037.cpp"
|
||||
#include "unl-txc.cpp"
|
||||
|
||||
auto Interface::create(string board) -> Interface* {
|
||||
Interface* p = nullptr;
|
||||
if(!p) p = Action53::create(board);
|
||||
@@ -66,6 +73,7 @@ auto Interface::create(string board) -> Interface* {
|
||||
if(!p) p = BandaiKaraoke::create(board);
|
||||
if(!p) p = BandaiLZ93D50::create(board);
|
||||
if(!p) p = BandaiOeka::create(board);
|
||||
if(!p) p = Camerica_BF909x::create(board);
|
||||
if(!p) p = ColorDreams_74x377::create(board);
|
||||
if(!p) p = GTROM::create(board);
|
||||
if(!p) p = HVC_AxROM::create(board);
|
||||
@@ -112,7 +120,14 @@ auto Interface::create(string board) -> Interface* {
|
||||
if(!p) p = TaitoTC0690::create(board);
|
||||
if(!p) p = TaitoX1005::create(board);
|
||||
if(!p) p = TaitoX1017::create(board);
|
||||
if(!p) p = Tengen80003x::create(board);
|
||||
if(!p) p = UNROM512::create(board);
|
||||
|
||||
if(!p) p = BMC::create(board);
|
||||
if(!p) p = Sachen74LS374N::create(board);
|
||||
if(!p) p = SachenSA0037::create(board);
|
||||
if(!p) p = TXC::create(board);
|
||||
|
||||
if(!p) p = new Interface;
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Board {
|
||||
#include "ic/jv001.hpp"
|
||||
#include "ic/txc05-00002-010.hpp"
|
||||
|
||||
struct Interface {
|
||||
VFS::Pak pak;
|
||||
@@ -24,6 +26,8 @@ struct Interface {
|
||||
|
||||
virtual auto power() -> void {}
|
||||
|
||||
virtual auto ppuAddressBus(n14 address) -> void {}
|
||||
|
||||
virtual auto serialize(serializer&) -> void {}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
struct Camerica_BF909x : Interface {
|
||||
static auto create(string id) -> Interface* {
|
||||
if(id == "CAMERICA-BF9093" ) return new Camerica_BF909x(Revision::CAMERICA_BF9093);
|
||||
if(id == "CAMERICA-BF9096" ) return new Camerica_BF909x(Revision::CAMERICA_BF9096);
|
||||
if(id == "CAMERICA-BF9096A") return new Camerica_BF909x(Revision::CAMERICA_BF9096A);
|
||||
if(id == "CAMERICA-BF9097" ) return new Camerica_BF909x(Revision::CAMERICA_BF9097);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Memory::Readable<n8> programROM;
|
||||
Memory::Writable<n8> characterRAM;
|
||||
|
||||
enum class Revision : u32 {
|
||||
CAMERICA_BF9093,
|
||||
CAMERICA_BF9096,
|
||||
CAMERICA_BF9096A,
|
||||
CAMERICA_BF9097,
|
||||
} revision;
|
||||
|
||||
Camerica_BF909x(Revision revision) : revision(revision) {}
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(programROM, "program.rom");
|
||||
Interface::load(characterRAM, "character.ram");
|
||||
mirror = pak->attribute("mirror") == "vertical";
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
Interface::save(characterRAM, "character.ram");
|
||||
}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if(address < 0x8000) return data;
|
||||
n4 bank;
|
||||
if(address < 0xC000) {
|
||||
bank = programBank;
|
||||
} else {
|
||||
bank = 0x0f;
|
||||
}
|
||||
if(revision == Revision::CAMERICA_BF9096 || revision == Revision::CAMERICA_BF9096A) {
|
||||
bank.bit(2,3) = programOuterBank;
|
||||
}
|
||||
return programROM.read(bank << 14 | (n14)address);
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if(address < 0x8000) return;
|
||||
if(address < 0xC000) {
|
||||
if(revision == Revision::CAMERICA_BF9096) {
|
||||
programOuterBank = data.bit(3,4);
|
||||
} else if(revision == Revision::CAMERICA_BF9096A) {
|
||||
programOuterBank.bit(0) = data.bit(4);
|
||||
programOuterBank.bit(1) = data.bit(3);
|
||||
} else if(address < 0xA000 && revision == Revision::CAMERICA_BF9097) {
|
||||
characterBank = data.bit(4);
|
||||
}
|
||||
} else {
|
||||
programBank = data.bit(0,3);
|
||||
}
|
||||
}
|
||||
|
||||
auto addressCIRAM(n32 address) const -> n32 {
|
||||
if(revision == Revision::CAMERICA_BF9097) {
|
||||
return characterBank << 10 | (n10)address;
|
||||
} else {
|
||||
return address >> !mirror & 0x0400 | (n10)address;
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(n32 address, n8 data) -> n8 override {
|
||||
if(address & 0x2000) {
|
||||
return ppu.readCIRAM(addressCIRAM(address));
|
||||
}
|
||||
return characterRAM.read((n13)address);
|
||||
}
|
||||
|
||||
auto writeCHR(n32 address, n8 data) -> void override {
|
||||
if(address & 0x2000) {
|
||||
return ppu.writeCIRAM(addressCIRAM(address), data);
|
||||
}
|
||||
return characterRAM.write((n13)address, data);
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(characterRAM);
|
||||
s(mirror);
|
||||
s(programBank);
|
||||
s(programOuterBank);
|
||||
s(characterBank);
|
||||
}
|
||||
|
||||
n1 mirror; //0 = horizontal, 1 = vertical
|
||||
n4 programBank;
|
||||
n2 programOuterBank;
|
||||
n1 characterBank;
|
||||
};
|
||||
@@ -7,6 +7,7 @@ struct HVC_CNROM : Interface {
|
||||
}
|
||||
|
||||
Memory::Readable<n8> programROM;
|
||||
Memory::Writable<n8> programRAM;
|
||||
Memory::Readable<n8> characterROM;
|
||||
Memory::Writable<n8> characterRAM;
|
||||
|
||||
@@ -20,6 +21,7 @@ struct HVC_CNROM : Interface {
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(programROM, "program.rom");
|
||||
Interface::load(programRAM, "save.ram");
|
||||
Interface::load(characterROM, "character.rom");
|
||||
Interface::load(characterRAM, "character.ram");
|
||||
mirror = pak->attribute("mirror") == "vertical";
|
||||
@@ -27,16 +29,21 @@ struct HVC_CNROM : Interface {
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
Interface::save(programRAM, "save.ram");
|
||||
Interface::save(characterRAM, "character.ram");
|
||||
}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if(address < 0x8000) return data;
|
||||
if(address < 0x6000) return data;
|
||||
if(address < 0x8000 && !programRAM) return data;
|
||||
if(address < 0x8000) return programRAM.read(address);
|
||||
return programROM.read((n15)address);
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if(address < 0x8000) return;
|
||||
if(address < 0x6000) return;
|
||||
if(address < 0x8000 && !programRAM) return;
|
||||
if(address < 0x8000) return programRAM.write(address, data);
|
||||
characterBank = data & programROM.read((n15)address);
|
||||
characterEnable = (data == key);
|
||||
}
|
||||
@@ -70,6 +77,7 @@ struct HVC_CNROM : Interface {
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(programRAM);
|
||||
s(characterRAM);
|
||||
s(mirror);
|
||||
s(key);
|
||||
|
||||
@@ -59,6 +59,7 @@ struct HVC_NROM : Interface {
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(programRAM);
|
||||
s(characterRAM);
|
||||
s(mirror);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ struct HVC_TxROM : Interface { //MMC3
|
||||
if(id == "HVC-TVROM" ) return new HVC_TxROM(Revision::TVROM);
|
||||
if(id == "NES-QJ" ) return new HVC_TxROM(Revision::NESQJ);
|
||||
if(id == "PAL-ZZ" ) return new HVC_TxROM(Revision::PALZZ);
|
||||
if(id == "MC-ACC" ) return new HVC_TxROM(Revision::MCACC);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -43,6 +44,7 @@ struct HVC_TxROM : Interface { //MMC3
|
||||
TVROM,
|
||||
NESQJ,
|
||||
PALZZ,
|
||||
MCACC,
|
||||
} revision;
|
||||
|
||||
HVC_TxROM(Revision revision) : revision(revision) {}
|
||||
@@ -65,21 +67,6 @@ struct HVC_TxROM : Interface { //MMC3
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irqTest(n16 address) -> void {
|
||||
if(!(characterAddress & 0x1000) && (address & 0x1000)) {
|
||||
if(irqDelay == 0) {
|
||||
if(irqCounter == 0) {
|
||||
irqCounter = irqLatch + 1;
|
||||
}
|
||||
if(--irqCounter == 0) {
|
||||
if(irqEnable) irqLine = 1;
|
||||
}
|
||||
}
|
||||
irqDelay = 6;
|
||||
}
|
||||
characterAddress = address;
|
||||
}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if(address < 0x6000) return data;
|
||||
|
||||
@@ -108,7 +95,6 @@ struct HVC_TxROM : Interface { //MMC3
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if(address == 0x2006 && ++characterLatch) irqTest(data << 8);
|
||||
if(address < 0x6000) return;
|
||||
|
||||
if(address < 0x8000) {
|
||||
@@ -149,6 +135,7 @@ struct HVC_TxROM : Interface { //MMC3
|
||||
break;
|
||||
case 0xc001:
|
||||
irqCounter = 0;
|
||||
mcAccPulseCounter = 0;
|
||||
break;
|
||||
case 0xe000:
|
||||
irqEnable = 0;
|
||||
@@ -197,7 +184,6 @@ struct HVC_TxROM : Interface { //MMC3
|
||||
}
|
||||
|
||||
auto readCHR(n32 address, n8 data) -> n8 override {
|
||||
irqTest(address);
|
||||
if(revision == Revision::TVROM) {
|
||||
if(address < 0x2000) return characterROM.read(addressCHR(address));
|
||||
address.bit(12) = 0;
|
||||
@@ -223,7 +209,6 @@ struct HVC_TxROM : Interface { //MMC3
|
||||
}
|
||||
|
||||
auto writeCHR(n32 address, n8 data) -> void override {
|
||||
irqTest(address);
|
||||
if(revision == Revision::TVROM) {
|
||||
if(address < 0x2000) return;
|
||||
address.bit(12) = 0;
|
||||
@@ -245,6 +230,29 @@ struct HVC_TxROM : Interface { //MMC3
|
||||
ramWritable = 1;
|
||||
}
|
||||
|
||||
auto ppuAddressBus(n14 address) -> void override {
|
||||
if (!(characterAddress & 0x1000) && (address & 0x1000)) {
|
||||
bool clocking = false;
|
||||
|
||||
if (revision == Revision::MCACC) {
|
||||
clocking = ++mcAccPulseCounter == 1;
|
||||
} else {
|
||||
clocking = irqDelay == 0;
|
||||
irqDelay = 6;
|
||||
}
|
||||
|
||||
if (clocking) {
|
||||
if (irqCounter == 0)
|
||||
irqCounter = irqLatch + 1;
|
||||
|
||||
if (--irqCounter == 0) {
|
||||
if (irqEnable) irqLine = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
characterAddress = address;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(programRAM);
|
||||
s(characterRAM);
|
||||
@@ -264,6 +272,8 @@ struct HVC_TxROM : Interface { //MMC3
|
||||
s(irqLine);
|
||||
s(characterLatch);
|
||||
s(characterAddress);
|
||||
if (revision == Revision::MCACC)
|
||||
s(mcAccPulseCounter);
|
||||
}
|
||||
|
||||
n1 characterMode;
|
||||
@@ -282,4 +292,5 @@ struct HVC_TxROM : Interface { //MMC3
|
||||
n1 irqLine;
|
||||
n1 characterLatch;
|
||||
n16 characterAddress;
|
||||
n3 mcAccPulseCounter;
|
||||
};
|
||||
@@ -8,6 +8,7 @@ struct HVC_UxROM : Interface {
|
||||
}
|
||||
|
||||
Memory::Readable<n8> programROM;
|
||||
Memory::Writable<n8> programRAM;
|
||||
Memory::Readable<n8> characterROM;
|
||||
Memory::Writable<n8> characterRAM;
|
||||
|
||||
@@ -22,17 +23,21 @@ struct HVC_UxROM : Interface {
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(programROM, "program.rom");
|
||||
Interface::load(programRAM, "save.ram");
|
||||
Interface::load(characterROM, "character.rom");
|
||||
Interface::load(characterRAM, "character.ram");
|
||||
mirror = pak->attribute("mirror") == "vertical";
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
Interface::save(programRAM, "save.ram");
|
||||
Interface::save(characterRAM, "character.ram");
|
||||
}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if(address < 0x8000) return data;
|
||||
if(address < 0x6000) return data;
|
||||
if(address < 0x8000 && !programRAM) return data;
|
||||
if(address < 0x8000) return programRAM.read(address);
|
||||
n8 bank;
|
||||
switch(address >> 14 & 1) {
|
||||
case 0: bank = (revision == Revision::UNROMA ? (n8)0x00 : programBank); break;
|
||||
@@ -42,7 +47,9 @@ struct HVC_UxROM : Interface {
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if(address < 0x8000) return;
|
||||
if(address < 0x6000) return;
|
||||
if(address < 0x8000 && !programRAM) return;
|
||||
if(address < 0x8000) return programRAM.write(address, data);
|
||||
programBank = data;
|
||||
if(revision == Revision::UN1ROM) programBank >>= 2;
|
||||
}
|
||||
@@ -69,6 +76,7 @@ struct HVC_UxROM : Interface {
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(programRAM);
|
||||
s(characterRAM);
|
||||
s(mirror);
|
||||
s(programBank);
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
// JV001: 136/172 136 172
|
||||
// .--\/--. | .--\/--. | .--\/--.
|
||||
// PPU A13 -> |01 28| -- GND | PPU A13 -> |01 28| -- GND | PPU A13 -> |01 28| -- GND
|
||||
// CPU D5 <> |02 27| <- PPU R/W | CPU D5 <> |02 27| <- NC | CPU D0 <> |02 27| <- PPU R/W
|
||||
// CPU D4 <> |03 26| -> Invert | CPU D4 <> |03 26| -> NC | CPU D1 <> |03 26| -> CIRAM A10
|
||||
// CPU D3 <> |04 25| <- PPU A11 | CPU D3 <> |04 25| <- NC | CPU D2 <> |04 25| <- PPU A11
|
||||
// CPU D2 <> |05 24| <- PPU A10 | CPU D2 <> |05 24| <- NC | CPU D3 <> |05 24| <- PPU A10
|
||||
// CPU D1 <> |06 23| -> O0 | CPU D1 <> |06 23| -> NC | CPU D4 <> |06 23| -> CHR A13
|
||||
// CPU D0 <> |07 22| -> O1 | CPU D0 <> |07 22| -> NC | CPU D5 <> |07 22| -> CHR A14
|
||||
// CPU A0 -> |08 21| -> O2 | CPU A0 -> |08 21| -> NC | CPU A0 -> |08 21| -> NC
|
||||
// CPU A1 -> |09 20| -> O3 | CPU A1 -> |09 20| -> NC | CPU A1 -> |09 20| -> NC
|
||||
// CPU A8 -> |10 19| -> O4 | CPU A8 -> |10 19| -> NC | CPU A8 -> |10 19| -> NC
|
||||
// M2 -> |11 18| -> O5 | M2 -> |11 18| -> NC | M2 -> |11 18| -> NC
|
||||
// /ROMSEL -> |12 17| <- CHR /OE | /ROMSEL -> |12 17| <- CHR /OE | /ROMSEL -> |12 17| <- CHR /OE
|
||||
// CPU R/W -> |13 16| <- CPU A13 | CPU R/W -> |13 16| <- NC | CPU R/W -> |13 16| <- CPU A13
|
||||
// 5V -- |14 15| <- CPU A14 | 5V -- |14 15| <- NC | 5V -- |14 15| <- CPU A14
|
||||
// '------' | '------' | '------'
|
||||
struct JV001 {
|
||||
auto writePRG(n16 address, n8 data) -> void {
|
||||
address &= 0xe103;
|
||||
switch (address) {
|
||||
case 0x4100: if (increment) {
|
||||
++reg.bit(0,3);
|
||||
} else {
|
||||
reg.bit(0,3) = in ^ (invert ? 0x0f : 0);
|
||||
}
|
||||
return;
|
||||
case 0x4101: invert = data.bit(0);
|
||||
return;
|
||||
case 0x4102: reg.bit(4,5) = data.bit(4,5);
|
||||
in = data.bit(0,3);
|
||||
return;
|
||||
case 0x4103: increment = data.bit(0);
|
||||
return;
|
||||
default: out.bit(0,3) = reg.bit(0,3);
|
||||
out.bit(4,5) = reg.bit(4,5) ^ (invert ? 0x03 : 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s(reg);
|
||||
s(invert);
|
||||
s(in);
|
||||
s(increment);
|
||||
s(out);
|
||||
}
|
||||
|
||||
n6 reg;
|
||||
n1 invert;
|
||||
n4 in;
|
||||
n1 increment;
|
||||
n6 out;
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
// TXC 05-00002-010
|
||||
// .--\/--.
|
||||
// Q2 <- |01 24| -> Q3
|
||||
// Q1 <- |02 23| -> Q4
|
||||
// Q0 <- |03 22| -> o3
|
||||
// i1 -> |04 21| <- CPU A13 (rn)
|
||||
// i0 -> |05 20| <- CPU A14 (rn)
|
||||
// io2 <> |06 19| -- GND
|
||||
// 5V -- |07 18| <- CPU R/W (n)
|
||||
// D5 <> |08 17| <- /ROMSEL (rn)
|
||||
// D4 <> |09 16| <- M2 (n)
|
||||
// D2 <> |10 15| <- CPU A8 (rn)
|
||||
// D1 <> |11 14| <- CPU A1 (rn)
|
||||
// D0 <> |12 13| <- CPU A0 (rn)
|
||||
// '------'
|
||||
// 36 132 173
|
||||
// .--\/--. .--\/--. .--\/--.
|
||||
// NC <- |01 24| -> NC (r) PRG A15 <- |01 24| -> NC NC <- |01 24| -> NC
|
||||
// (r) PRG A16 <- |02 23| -> NC (r) CHR A14 <- |02 23| -> NC *(r) CHR A15 <- |02 23| -> NC
|
||||
// (r) PRG A15 <- |03 22| -> NC (r) CHR A13 <- |03 22| -> NC (r) CHR A13 <- |03 22| -> CHR A14
|
||||
// GND -> |04 21| <- CPU A13 (rn) GND -> |04 21| <- CPU A13 (fr) GND -> |04 21| <- CPU A13 (fr)
|
||||
// 5V -> |05 20| <- CPU A14 (rn) 5V -> |05 20| <- CPU A14 (fr) 5V -> |05 20| <- CPU A14 (fr)
|
||||
// NC <> |06 19| -- GND NC <> |06 19| -- GND NC <> |06 19| <- GND
|
||||
// 5V -- |07 18| <- CPU R/W (n) 5V -- |07 18| <- CPU R/W (f) 5V -- |07 18| <- CPU R/W (f)
|
||||
// NC <> |08 17| <- /ROMSEL (rn) GND <> |08 17| <- /ROMSEL (fr) GND <> |08 17| <- /ROMSEL (fr)
|
||||
// NC <> |09 16| <- M2 (n) (fr) CPU D3 <> |09 16| <- M2 (f) (fr) CPU D3 <> |09 16| <- M2 (f)
|
||||
// NC <> |10 15| <- CPU A8 (rn) (fr) CPU D2 <> |10 15| <- CPU A8 (fr) (fr) CPU D2 <> |10 15| <- CPU A8 (fr)
|
||||
// (rn) CPU D5 <> |11 14| <- CPU A1 (rn) (fr) CPU D1 <> |11 14| <- CPU A1 (fr) (fr) CPU D1 <> |11 14| <- CPU A1 (fr)
|
||||
// (rn) CPU D4 <> |12 13| <- CPU A0 (rn) (fr) CPU D0 <> |12 13| <- CPU A0 (fr) (fr) CPU D0 <> |12 13| <- CPU A0 (fr)
|
||||
// '------'` '------' '------'
|
||||
// 173*: TXC pin 2 was routed to CHR ROM pin 1 on a 28-pin package.
|
||||
// On a UVEPROM, this is A15.
|
||||
// However, no games were ever released using more than 32 KiB of CHR.
|
||||
// TXC 05-00002-010: 36/132/173
|
||||
struct TXC05_00002_010 {
|
||||
auto writePRG(n16 address, n8 data) -> void {
|
||||
address &= 0xe103;
|
||||
switch (address) {
|
||||
case 0x4100: if (increment) {
|
||||
++reg.bit(0,3);
|
||||
} else {
|
||||
reg.bit(0,3) = in ^ (invert ? 0x0f : 0);
|
||||
}
|
||||
return;
|
||||
case 0x4101: invert = data.bit(0);
|
||||
if (overdown) {
|
||||
o3 = (invert ? io2 : i0) | data.bit(5);
|
||||
} else {
|
||||
io2 = invert ? i1 : i0;
|
||||
o3 = io2 | data.bit(5);
|
||||
}
|
||||
return;
|
||||
case 0x4102: reg.bit(4,5) = data.bit(4,5);
|
||||
in.bit(0,2) = data.bit(0,2);
|
||||
in.bit(3) = in.bit(3) ^ invert;
|
||||
return;
|
||||
case 0x4103: increment = data.bit(0);
|
||||
return;
|
||||
default: out.bit(0,3) = reg.bit(0,3);
|
||||
out.bit(4) = reg.bit(4) ^ invert;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s(i0);
|
||||
s(i1);
|
||||
s(io2);
|
||||
s(o3);
|
||||
|
||||
s(increment);
|
||||
s(invert);
|
||||
s(overdown);
|
||||
s(reg);
|
||||
s(in);
|
||||
s(out);
|
||||
}
|
||||
|
||||
bool overdown; // io2 is using input or output flag
|
||||
|
||||
// 4 pin
|
||||
n1 i0, i1, io2, o3;
|
||||
|
||||
n1 increment;
|
||||
n1 invert;
|
||||
n6 reg;
|
||||
n4 in;
|
||||
n5 out;
|
||||
};
|
||||
|
||||
@@ -65,6 +65,7 @@ struct Sunsoft1 : Interface {
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(characterRAM);
|
||||
s(characterBank);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ struct Sunsoft2 : Interface {
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(characterRAM);
|
||||
s(programBank);
|
||||
s(characterBank);
|
||||
s(mirror);
|
||||
|
||||
@@ -105,6 +105,8 @@ struct Sunsoft3 : Interface {
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(programRAM);
|
||||
s(characterRAM);
|
||||
s(programBank);
|
||||
s(characterBank);
|
||||
s(mirror);
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
struct Tengen80003x : Interface { //RAMBO-1
|
||||
static auto create(string id) -> Interface* {
|
||||
if(id == "TENGEN-800032") return new Tengen80003x(Revision::TENGEN_800032);
|
||||
if(id == "TENGEN-800037") return new Tengen80003x(Revision::TENGEN_800037);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Memory::Readable<n8> programROM;
|
||||
Memory::Readable<n8> characterROM;
|
||||
|
||||
enum class Revision : u32 {
|
||||
TENGEN_800032,
|
||||
TENGEN_800037,
|
||||
} revision;
|
||||
|
||||
Tengen80003x(Revision revision) : revision(revision) {}
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(programROM, "program.rom");
|
||||
Interface::load(characterROM, "character.rom");
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
}
|
||||
|
||||
auto main() -> void override {
|
||||
if(irqDelay) irqDelay--;
|
||||
if(irqLineDelay && --irqLineDelay == 0) irqLine = 1;
|
||||
cpu.irqLine(irqLine);
|
||||
|
||||
if(++irqCycleCounter == 0) {
|
||||
if(irqCycleMode) irqTest(1);
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irqTest(n8 delay) -> void {
|
||||
if(irqCounter == 0) {
|
||||
irqCounter = irqLatch + 1;
|
||||
}
|
||||
if(--irqCounter == 0) {
|
||||
if(irqEnable) irqLineDelay = delay;
|
||||
}
|
||||
}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if(address < 0x8000) return data;
|
||||
|
||||
n5 bank;
|
||||
switch(address >> 13 & 3) {
|
||||
case 0: bank = programBank[programMode == 0 ? 0 : 2]; break;
|
||||
case 1: bank = programBank[1]; break;
|
||||
case 2: bank = programBank[programMode == 0 ? 2 : 0]; break;
|
||||
case 3: bank = 0x1f; break;
|
||||
}
|
||||
|
||||
address = bank << 13 | (n13)address;
|
||||
return programROM.read(address);
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if(address < 0x8000) return;
|
||||
|
||||
switch(address & 0xe001) {
|
||||
case 0x8000:
|
||||
bankSelect = data.bit(0,3);
|
||||
characterMode = data.bit(5);
|
||||
programMode = data.bit(6);
|
||||
characterInvert = data.bit(7);
|
||||
break;
|
||||
case 0x8001:
|
||||
switch(bankSelect) {
|
||||
case 0: characterBank[0] = data; break;
|
||||
case 1: characterBank[1] = data; break;
|
||||
case 2: characterBank[2] = data; break;
|
||||
case 3: characterBank[3] = data; break;
|
||||
case 4: characterBank[4] = data; break;
|
||||
case 5: characterBank[5] = data; break;
|
||||
case 6: programBank[0] = data; break;
|
||||
case 7: programBank[1] = data; break;
|
||||
case 8: characterBank[6] = data; break;
|
||||
case 9: characterBank[7] = data; break;
|
||||
case 15: programBank[2] = data; break;
|
||||
}
|
||||
break;
|
||||
case 0xa000:
|
||||
mirror = data.bit(0);
|
||||
break;
|
||||
case 0xc000:
|
||||
irqLatch = data;
|
||||
break;
|
||||
case 0xc001:
|
||||
irqCounter = 0;
|
||||
irqCycleCounter = 0;
|
||||
irqCycleMode = data.bit(0);
|
||||
break;
|
||||
case 0xe000:
|
||||
irqEnable = 0;
|
||||
irqLine = 0;
|
||||
break;
|
||||
case 0xe001:
|
||||
irqEnable = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto addressCHR(n32 address) const -> n32 {
|
||||
address.bit(12) ^= characterInvert;
|
||||
if(characterMode == 0) {
|
||||
if(address <= 0x07ff) return (characterBank[0] & ~1) << 10 | (n11)address;
|
||||
if(address <= 0x0fff) return (characterBank[1] & ~1) << 10 | (n11)address;
|
||||
} else {
|
||||
if(address <= 0x03ff) return characterBank[0] << 10 | (n10)address;
|
||||
if(address <= 0x07ff) return characterBank[6] << 10 | (n10)address;
|
||||
if(address <= 0x0bff) return characterBank[1] << 10 | (n10)address;
|
||||
if(address <= 0x0fff) return characterBank[7] << 10 | (n10)address;
|
||||
}
|
||||
if(address <= 0x13ff) return characterBank[2] << 10 | (n10)address;
|
||||
if(address <= 0x17ff) return characterBank[3] << 10 | (n10)address;
|
||||
if(address <= 0x1bff) return characterBank[4] << 10 | (n10)address;
|
||||
if(address <= 0x1fff) return characterBank[5] << 10 | (n10)address;
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto addressCIRAM(n32 address) const -> n32 {
|
||||
if(revision == Revision::TENGEN_800037) {
|
||||
address.bit(12) = characterInvert;
|
||||
if(characterMode == 0) {
|
||||
if(address <= 0x27ff) return characterBank[0].bit(7) << 10 | (n10)address;
|
||||
if(address <= 0x2fff) return characterBank[1].bit(7) << 10 | (n10)address;
|
||||
} else {
|
||||
if(address <= 0x23ff) return characterBank[0].bit(7) << 10 | (n10)address;
|
||||
if(address <= 0x27ff) return characterBank[6].bit(7) << 10 | (n10)address;
|
||||
if(address <= 0x2bff) return characterBank[1].bit(7) << 10 | (n10)address;
|
||||
if(address <= 0x2fff) return characterBank[7].bit(7) << 10 | (n10)address;
|
||||
}
|
||||
if(address <= 0x33ff) return characterBank[2].bit(7) << 10 | (n10)address;
|
||||
if(address <= 0x37ff) return characterBank[3].bit(7) << 10 | (n10)address;
|
||||
if(address <= 0x3bff) return characterBank[4].bit(7) << 10 | (n10)address;
|
||||
if(address <= 0x3fff) return characterBank[5].bit(7) << 10 | (n10)address;
|
||||
unreachable;
|
||||
}
|
||||
return address >> mirror & 0x0400 | (n10)address;
|
||||
}
|
||||
|
||||
auto readCHR(n32 address, n8 data) -> n8 override {
|
||||
if(address & 0x2000) return ppu.readCIRAM(addressCIRAM(address));
|
||||
return characterROM.read(addressCHR(address));
|
||||
}
|
||||
|
||||
auto writeCHR(n32 address, n8 data) -> void override {
|
||||
if(address & 0x2000) return ppu.writeCIRAM(addressCIRAM(address), data);
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
}
|
||||
|
||||
auto ppuAddressBus(n14 address) -> void override {
|
||||
if(!(characterAddress & 0x1000) && (address & 0x1000)) {
|
||||
if(irqDelay == 0) {
|
||||
if(!irqCycleMode) irqTest(3);
|
||||
}
|
||||
irqDelay = 6;
|
||||
}
|
||||
characterAddress = address;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(characterMode);
|
||||
s(characterInvert);
|
||||
s(programMode);
|
||||
s(bankSelect);
|
||||
s(programBank);
|
||||
s(characterBank);
|
||||
s(mirror);
|
||||
s(irqLatch);
|
||||
s(irqCounter);
|
||||
s(irqEnable);
|
||||
s(irqDelay);
|
||||
s(irqLineDelay);
|
||||
s(irqLine);
|
||||
s(irqCycleMode);
|
||||
s(irqCycleCounter);
|
||||
s(characterAddress);
|
||||
}
|
||||
|
||||
n1 characterMode;
|
||||
n1 characterInvert;
|
||||
n1 programMode;
|
||||
n4 bankSelect;
|
||||
n5 programBank[3];
|
||||
n8 characterBank[8];
|
||||
n1 mirror;
|
||||
n8 irqLatch;
|
||||
n8 irqCounter;
|
||||
n1 irqEnable;
|
||||
n8 irqDelay;
|
||||
n8 irqLineDelay;
|
||||
n1 irqLine;
|
||||
n1 irqCycleMode;
|
||||
n2 irqCycleCounter;
|
||||
n16 characterAddress;
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
struct BMC : Interface {
|
||||
static auto create(string id) -> Interface* {
|
||||
if (id == "UNL-BMC-32") return new BMC(Revision::BMC_32);
|
||||
if (id == "UNL-BMC-128") return new BMC(Revision::BMC_128);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Memory::Readable<n8> programROM;
|
||||
Memory::Readable<n8> characterROM;
|
||||
|
||||
enum class Revision : u32 {
|
||||
BMC_32, // 229: max 32 * 16KB PRG-ROM, 32 * 8KB CHR-ROM
|
||||
BMC_128, // 225/255: max 128 * 16KB PRG-ROM, 128 * 8KB CHR-ROM
|
||||
} revision;
|
||||
|
||||
BMC(Revision revision) : revision(revision) {}
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(programROM, "program.rom");
|
||||
Interface::load(characterROM, "character.rom");
|
||||
}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if (address >= 0x5800 && address < 0x6000 && revision == Revision::BMC_128) {
|
||||
return regs[address & 3];
|
||||
}
|
||||
if (address < 0x8000) return data;
|
||||
|
||||
if (mode == 1)
|
||||
address = prgBank << 15 | (n15)address;
|
||||
else
|
||||
address = prgBank << 14 | (n14)address;
|
||||
|
||||
return programROM.read(address);
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if (address >= 0x5800 && address < 0x6000 && revision == Revision::BMC_128) {
|
||||
regs[address & 3] = data;
|
||||
return;
|
||||
}
|
||||
if (address < 0x8000) return;
|
||||
|
||||
update(address);
|
||||
}
|
||||
|
||||
auto readCHR(n32 address, n8 data) -> n8 override {
|
||||
if (address & 0x2000) {
|
||||
address = (address >> mirror & 0x0400) | (n10)address;
|
||||
return ppu.readCIRAM(address);
|
||||
}
|
||||
|
||||
return characterROM.read(chrBank << 13 | (n13)address);
|
||||
}
|
||||
|
||||
auto writeCHR(n32 address, n8 data) -> void override {
|
||||
if (address & 0x2000) {
|
||||
address = (address >> mirror & 0x0400) | (n10)address;
|
||||
return ppu.writeCIRAM(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
update(0x8000);
|
||||
|
||||
if (revision == Revision::BMC_128)
|
||||
for (auto& reg : regs) reg = random();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(latch);
|
||||
update(latch);
|
||||
|
||||
if (revision == Revision::BMC_128) s(regs);
|
||||
}
|
||||
|
||||
auto update(n16 address) -> void {
|
||||
latch = address;
|
||||
|
||||
if (revision == Revision::BMC_32) {
|
||||
mode = address.bit(1, 4) == 0;
|
||||
chrBank = address.bit(0, 4);
|
||||
prgBank = address.bit(0, 4);
|
||||
mirror = address.bit(5);
|
||||
} else {
|
||||
mirror = latch.bit(13);
|
||||
mode = latch.bit(12) == 0;
|
||||
chrBank = latch.bit(0, 5) | latch.bit(14) << 6;
|
||||
prgBank = latch.bit(6, 11) | latch.bit(14) << 6;
|
||||
}
|
||||
|
||||
if (mode == 1)
|
||||
prgBank >>= 1;
|
||||
}
|
||||
|
||||
n16 latch;
|
||||
|
||||
n1 mode;
|
||||
n1 mirror;
|
||||
n7 chrBank;
|
||||
n7 prgBank;
|
||||
|
||||
n4 regs[4];
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
/// 74LS374N SA-020 SA-015
|
||||
/// .---V---. .---V---. .---V---.
|
||||
/// R5.0 <- | 01 20 | -- Vcc PRG A15 <- | 01 20 | -- Vcc PRG A15 <- | 01 20 | -- Vcc
|
||||
/// CIRAM A10 <- | 02 19 | <- CPU A8 CIRAM A10 <- | 02 19 | <- CPU A8 CIRAM A10 <- | 02 19 | <- CPU A8
|
||||
/// PPU A11 -> | 03 18 | <- R/W PPU A11 -> | 03 18 | <- R/W PPU A11 -> | 03 18 | <- R/W
|
||||
/// R5.1 <- | 04 17 | <- M2 PRG A16 <- | 04 17 | <- M2 PRG A16 <- | 04 17 | <- M2
|
||||
/// CPU A14 -> | 05 16 | <- PPU A10 CPU A14 -> | 05 16 | <- PPU A10 CPU A14 -> | 05 16 | <- PPU A10
|
||||
/// CPU A0 -> | 06 15 | <- /ROMSEL CPU A0 -> | 06 15 | <- /ROMSEL CPU A0 -> | 06 15 | <- /ROMSEL
|
||||
/// CPU D0 <> | 07 14 | <> CPU D2 CPU D0 <> | 07 14 | <> CPU D2 CPU D0 <> | 07 14 | <> CPU D2 or Vcc, depending on solder pad setting
|
||||
/// CPU D1 <> | 08 13 | -> R6.1 CPU D1 <> | 08 13 | -> CHR A14 CPU D1 <> | 08 13 | -> CHR A14
|
||||
/// R2.0 <- | 09 12 | -> R6.0 CHR A16 <- | 09 12 | -> CHR A13 N/C <- | 09 12 | -> CHR A13
|
||||
/// GND -- | 10 11 | -> R4.0 GND -- | 10 11 | -> CHR A15 GND -- | 10 11 | -> CHR A15
|
||||
/// '-------' '-------' '-------'
|
||||
///
|
||||
/// SA-020A:
|
||||
/// CHR A16 A15 A14 A13: R2.0 R4.0 R6.1 R6.0
|
||||
/// PRG A16 A15: R5.1 R5.0
|
||||
///
|
||||
/// SA-015:
|
||||
/// CHR A15 A14 A13: R4.0 R6.1 R6.0
|
||||
/// PRG A16 A15: R5.1 R5.0
|
||||
struct Sachen74LS374N : Interface {
|
||||
static auto create(string id) -> Interface* {
|
||||
if (id == "UNL-Sachen-74LS374N") return new Sachen74LS374N(Revision::SACHEN_74LS374N);
|
||||
if (id == "UNL-Sachen-74LS374NA") return new Sachen74LS374N(Revision::SACHEN_74LS374NA);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Memory::Readable<n8> programROM;
|
||||
Memory::Readable<n8> characterROM;
|
||||
|
||||
// The circuit board has a solder pad that selects
|
||||
// whether ASIC pin 14 is connected to CPU D2 or to
|
||||
// Vcc. In the latter setting, the ASIC sees all
|
||||
// writes as being OR'd with $04, while on reads,
|
||||
// D2 is open bus. The pad setting only makes a
|
||||
// difference for the game Shōgi Gakuen, which
|
||||
// writes to R2 and reads from R6 to check whether
|
||||
// it now holds that written value, as would be
|
||||
// the case if the solder pad were in the Vcc
|
||||
// position; depending on the setting, a different
|
||||
// copyright tag ("Sachen" vs. "Sachen and Hacker
|
||||
// International") is displayed.
|
||||
Node::Setting::Boolean solderPad;
|
||||
|
||||
enum class Revision : u32 {
|
||||
SACHEN_74LS374N,
|
||||
SACHEN_74LS374NA,
|
||||
} revision;
|
||||
|
||||
Sachen74LS374N(Revision revision) : revision(revision) {}
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(programROM, "program.rom");
|
||||
Interface::load(characterROM, "character.rom");
|
||||
|
||||
if (revision == Revision::SACHEN_74LS374N) {
|
||||
solderPad = cartridge.node->append<Node::Setting::Boolean>("Solder Pad", false);
|
||||
}
|
||||
}
|
||||
|
||||
auto unload() -> void override {
|
||||
if (revision == Revision::SACHEN_74LS374N) {
|
||||
solderPad.reset();
|
||||
}
|
||||
}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if (address < 0x4100) return data;
|
||||
if (address < 0x8000) {
|
||||
if ((address & 0x4101) == 0x4101)
|
||||
if (revision == Revision::SACHEN_74LS374N && solderPad->value())
|
||||
return (data & ~0x07) | regs[select].bit(0, 1) | 0x04;
|
||||
else
|
||||
return (data & ~0x07) | regs[select].bit(0, 2);
|
||||
return data;
|
||||
}
|
||||
address = prgBank << 15 | (n15)address;
|
||||
data = programROM.read(address);
|
||||
return data;
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if (address < 0x4100) return;
|
||||
if (address < 0x8000) {
|
||||
address &= 0x4101;
|
||||
if (address == 0x4100) {
|
||||
select = data.bit(0, 2);
|
||||
} else {
|
||||
if (revision == Revision::SACHEN_74LS374N && solderPad->value()) {
|
||||
regs[select] = data.bit(0, 1) | 0x04;
|
||||
} else {
|
||||
regs[select] = data.bit(0, 2);
|
||||
}
|
||||
regs[select] = data.bit(0, 2);
|
||||
update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(n32 address, n8 data) -> n8 override {
|
||||
if (address & 0x2000) return ppu.readCIRAM(addressCIRAM(address));
|
||||
return characterROM.read(chrBank << 13 | (n13)address);
|
||||
}
|
||||
|
||||
auto writeCHR(n32 address, n8 data) -> void override {
|
||||
if (address & 0x2000) return ppu.writeCIRAM(addressCIRAM(address), data);
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
for (n8 ® : regs) reg = 0;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(regs);
|
||||
s(select);
|
||||
|
||||
if (s.reading()) update();
|
||||
}
|
||||
|
||||
auto update() -> void {
|
||||
if (revision == Revision::SACHEN_74LS374N) {
|
||||
chrBank = regs[4].bit(0) << 2 | regs[6].bit(0, 1);
|
||||
} else {
|
||||
chrBank = regs[2].bit(0) << 3 | regs[4].bit(0) << 2 | regs[6].bit(0, 1);
|
||||
}
|
||||
prgBank = regs[5].bit(0,1);
|
||||
mirror = regs[7].bit(1,2);
|
||||
}
|
||||
|
||||
auto addressCIRAM(n32 address) -> n32 {
|
||||
switch (mirror) {
|
||||
case 0:
|
||||
// nametable is 0 0 0 1
|
||||
if (address & 0x0c00 != 0x0c00) {
|
||||
address = (n10)address;
|
||||
} else {
|
||||
address = 0x0400 | (n10)address;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// horizontal
|
||||
address = (address >> 1 & 0x0400) | (n10)address;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// vertical
|
||||
address = (address >> 0 & 0x0400) | (n10)address;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// single screen
|
||||
address = (n10)address;
|
||||
break;
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
n8 regs[8];
|
||||
n3 select;
|
||||
|
||||
// 0b00 = 0 0 0 1, 0b01 = horizontal,
|
||||
// 0b10 = vertical, 0b11 = single screen
|
||||
n2 mirror;
|
||||
n2 prgBank;
|
||||
n4 chrBank;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
struct SachenSA0037 : Interface {
|
||||
static auto create(string id) -> Interface* {
|
||||
if(id == "TENGEN-800008") return new SachenSA0037(Revision::TENGEN_800008);
|
||||
if(id == "UNL-SA-0037" ) return new SachenSA0037(Revision::SACHEN_SA0037);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Memory::Readable<n8> programROM;
|
||||
Memory::Readable<n8> characterROM;
|
||||
|
||||
enum class Revision : u32 {
|
||||
SACHEN_SA0037,
|
||||
TENGEN_800008,
|
||||
} revision;
|
||||
|
||||
SachenSA0037(Revision revision) : revision(revision) {}
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(programROM, "program.rom");
|
||||
Interface::load(characterROM, "character.rom");
|
||||
mirror = pak->attribute("mirror") == "vertical";
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if(address < 0x8000) return data;
|
||||
return programROM.read(programBank << 15 | (n15)address);
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if(address < 0x8000) return;
|
||||
characterBank = data.bit(0,2);
|
||||
programBank = data.bit(3);
|
||||
}
|
||||
|
||||
auto readCHR(n32 address, n8 data) -> n8 override {
|
||||
if(address & 0x2000) {
|
||||
address = address >> !mirror & 0x0400 | (n10)address;
|
||||
return ppu.readCIRAM(address);
|
||||
}
|
||||
address = characterBank << 13 | (n13)address;
|
||||
return characterROM.read(address);
|
||||
}
|
||||
|
||||
auto writeCHR(n32 address, n8 data) -> void override {
|
||||
if(address & 0x2000) {
|
||||
address = address >> !mirror & 0x0400 | (n10)address;
|
||||
return ppu.writeCIRAM(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(mirror);
|
||||
s(programBank);
|
||||
s(characterBank);
|
||||
}
|
||||
|
||||
n1 mirror; //0 = horizontal, 1 = vertical
|
||||
n1 programBank;
|
||||
n3 characterBank;
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
// TXC-22211A: Mapper 132
|
||||
// TXC-22211B: Mapper 172
|
||||
|
||||
struct TXC : Interface {
|
||||
static auto create(string id) -> Interface*;
|
||||
|
||||
enum class Revision : u32 {
|
||||
TXC_22211A,
|
||||
TXC_22211B,
|
||||
TXC_22211C,
|
||||
} revision;
|
||||
|
||||
TXC(Revision revision) : revision(revision) {}
|
||||
|
||||
Memory::Readable<n8> programROM;
|
||||
Memory::Readable<n8> characterROM;
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(programROM, "program.rom");
|
||||
Interface::load(characterROM, "character.rom");
|
||||
}
|
||||
};
|
||||
|
||||
struct TXC_22211AC : TXC {
|
||||
TXC_22211AC(Revision revision) : TXC(revision) {}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if ((address & 0xe100) == 0x4100) {
|
||||
data &= 0xf0;
|
||||
data.bit(0,2) = txc.reg.bit(0,2);
|
||||
data.bit(3) = txc.reg.bit(4);
|
||||
return data;
|
||||
}
|
||||
if (address < 0x8000) return data;
|
||||
|
||||
if (revision == Revision::TXC_22211A)
|
||||
address = txc.out.bit(2) << 15 | (n15)address;
|
||||
|
||||
return programROM.read(address);
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if ((address & 0xe100) == 0x4100) {
|
||||
txc.writePRG(address, data.bit(3) << 4 | data.bit(0,2));
|
||||
}
|
||||
if (address < 0x8000) return;
|
||||
|
||||
txc.writePRG(address, data.bit(3) << 4 | data.bit(0,2));
|
||||
}
|
||||
|
||||
auto readCHR(n32 address, n8 data) -> n8 override {
|
||||
if (address & 0x2000) return ppu.readCIRAM((n11)address);
|
||||
|
||||
if (revision == Revision::TXC_22211A)
|
||||
address = txc.out.bit(0,1) << 13 | (n13)address;
|
||||
else
|
||||
address = txc.out.bit(1) << 15 | txc.o3 << 14 | txc.out.bit(0) << 13 | (n13)address;
|
||||
|
||||
return characterROM.read(address);
|
||||
}
|
||||
|
||||
auto writeCHR(n32 address, n8 data) -> void override {
|
||||
if (address & 0x2000) return ppu.writeCIRAM((n11)address, data);
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
txc = {};
|
||||
txc.overdown = false;
|
||||
txc.i0 = 1;
|
||||
txc.i1 = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(txc);
|
||||
}
|
||||
|
||||
TXC05_00002_010 txc;
|
||||
};
|
||||
|
||||
struct TXC_22211B : TXC {
|
||||
TXC_22211B() : TXC(Revision::TXC_22211B) {}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if ((address & 0xe100) == 0x4100) {
|
||||
n8 v = (jv001.reg.bit(4,5) ^ (jv001.invert ? 3 : 0)) << 4;
|
||||
v |= jv001.reg.bit(0,3);
|
||||
return data & 0xc0 | bit::reverse(v) >> 2;
|
||||
}
|
||||
if (address < 0x8000) return data;
|
||||
|
||||
return programROM.read((n15)address);
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if ((address & 0xe100) == 0x4100)
|
||||
return jv001.writePRG(address, bit::reverse(data) >> 2);
|
||||
if (address < 0x8000) return;
|
||||
|
||||
return jv001.writePRG(address, bit::reverse(data) >> 2);
|
||||
}
|
||||
|
||||
auto readCHR(n32 address, n8 data) -> n8 override {
|
||||
if (address & 0x2000) {
|
||||
address = (address >> !jv001.invert & 0x0400) | (n10)address;
|
||||
return ppu.readCIRAM(address);
|
||||
}
|
||||
return characterROM.read(jv001.out.bit(0,1) << 13 | (n13)address);
|
||||
}
|
||||
|
||||
auto writeCHR(n32 address, n8 data) -> void override {
|
||||
if (address & 0x2000) {
|
||||
address = (address >> !jv001.invert & 0x0400) | (n10)address;
|
||||
return ppu.writeCIRAM(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
jv001 = {};
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(jv001);
|
||||
}
|
||||
|
||||
JV001 jv001;
|
||||
};
|
||||
|
||||
auto TXC::create(string id) -> Interface* {
|
||||
if (id == "UNL-22211" ) return new TXC_22211AC(Revision::TXC_22211A);
|
||||
if (id == "TXC-22211A") return new TXC_22211AC(Revision::TXC_22211A);
|
||||
if (id == "TXC-22211B") return new TXC_22211B();
|
||||
if (id == "TXC-22211C") return new TXC_22211AC(Revision::TXC_22211C);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -71,6 +71,10 @@ auto Cartridge::writeCHR(n32 address, n8 data) -> void {
|
||||
return board->writeCHR(address, data);
|
||||
}
|
||||
|
||||
auto Cartridge::ppuAddressBus(n14 address) -> void {
|
||||
return board->ppuAddressBus(address);
|
||||
}
|
||||
|
||||
auto Cartridge::scanline(n32 y) -> void {
|
||||
return board->scanline(y);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ struct Cartridge : Thread {
|
||||
auto readCHR(n32 address, n8 data = 0x00) -> n8;
|
||||
auto writeCHR(n32 address, n8 data) -> void;
|
||||
|
||||
auto ppuAddressBus(n14 address) -> void;
|
||||
|
||||
//scanline() is for debugging purposes only:
|
||||
//boards must detect scanline edges on their own
|
||||
auto scanline(n32 y) -> void;
|
||||
|
||||
@@ -4,5 +4,6 @@ namespace ares::Famicom {
|
||||
|
||||
#include "port.cpp"
|
||||
#include "gamepad/gamepad.cpp"
|
||||
#include "zapper/zapper.cpp"
|
||||
|
||||
}
|
||||
@@ -31,3 +31,4 @@ struct Controller {
|
||||
|
||||
#include "port.hpp"
|
||||
#include "gamepad/gamepad.hpp"
|
||||
#include "zapper/zapper.hpp"
|
||||
@@ -11,7 +11,7 @@ auto ControllerPort::load(Node::Object parent) -> void {
|
||||
port->setHotSwappable(true);
|
||||
port->setAllocate([&](auto name) { return allocate(name); });
|
||||
port->setDisconnect([&] { device.reset(); });
|
||||
port->setSupported({"Gamepad"});
|
||||
port->setSupported({"Gamepad", "Zapper"});
|
||||
}
|
||||
|
||||
auto ControllerPort::unload() -> void {
|
||||
@@ -21,6 +21,7 @@ auto ControllerPort::unload() -> void {
|
||||
|
||||
auto ControllerPort::allocate(string name) -> Node::Peripheral {
|
||||
if(name == "Gamepad") device = new Gamepad(port);
|
||||
if(name == "Zapper") device = new Zapper(port);
|
||||
if(device) return device->node;
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
Zapper::Zapper(Node::Port port) {
|
||||
node = port->append<Node::Peripheral>("Zapper");
|
||||
|
||||
x = node->append<Node::Input::Axis >("X");
|
||||
y = node->append<Node::Input::Axis >("Y");
|
||||
trigger = node->append<Node::Input::Button>("Trigger");
|
||||
|
||||
sprite = node->append<Node::Video::Sprite>("Crosshair");
|
||||
sprite->setImage(Resource::Sprite::SuperFamicom::CrosshairGreen);
|
||||
ppu.screen->attach(sprite);
|
||||
}
|
||||
|
||||
Zapper::~Zapper() {
|
||||
if(ppu.screen) ppu.screen->detach(sprite);
|
||||
}
|
||||
|
||||
auto Zapper::data() -> n3 {
|
||||
u32 next = ppu.io.ly * 283 + ppu.io.lx;
|
||||
n3 result = 0b000;
|
||||
|
||||
platform->input(trigger);
|
||||
if (trigger->value()) result.bit(2) = 1;
|
||||
|
||||
if (next < previous) {
|
||||
platform->input(x); //-n = left, 0 = center, +n = right
|
||||
platform->input(y); //-n = up, 0 = center, +n = down
|
||||
|
||||
if (x->value() != px || y->value() != py) {
|
||||
px = x->value();
|
||||
py = y->value();
|
||||
|
||||
cx = max(-8, min(256 + 8, px + cx));
|
||||
cy = max(-8, min(240 + 8, py + cy));
|
||||
|
||||
sprite->setPosition(cx, cy);
|
||||
sprite->setVisible(true);
|
||||
|
||||
nx = cx + 8;
|
||||
ny = cy + 8;
|
||||
}
|
||||
}
|
||||
previous = next;
|
||||
|
||||
bool offscreen = nx < 0 || ny < 0 || nx >= 256 || ny >= 240;
|
||||
if (offscreen) {
|
||||
result.bit(1) = 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 *target = ppu.screen->pixels().data();
|
||||
u32 count = 0;
|
||||
for(int y = ny - 8; y <= ny + 8; y++) {
|
||||
if (y < 0) continue;
|
||||
if (y <= ppu.io.ly - 18) continue;
|
||||
if (y > ppu.io.ly) break;
|
||||
|
||||
for(int x = nx - 8; x <= nx + 8; x++) {
|
||||
if (x < 0) continue;
|
||||
if (x >= 256) break;
|
||||
|
||||
n32 offset = y * 283 + x;// y * 283 + x + Region::PAL() ? 18 : 16;
|
||||
n64 color = ppu.color(target[offset]);
|
||||
f64 brightness = 0;
|
||||
|
||||
brightness += ((color >> 32) & 0xffff) * 0.299;
|
||||
brightness += ((color >> 16) & 0xffff) * 0.587;
|
||||
brightness += ((color >> 0) & 0xffff) * 0.114;
|
||||
if (brightness > 0x80)
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count < 0x40)
|
||||
result.bit(1) = 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto Zapper::latch(n1 data) -> void {
|
||||
}
|
||||
|
||||
auto Zapper::serialize(serializer& s) -> void {
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
struct Zapper : Controller {
|
||||
Node::Input::Axis x;
|
||||
Node::Input::Axis y;
|
||||
Node::Input::Button trigger;
|
||||
Node::Video::Sprite sprite;
|
||||
|
||||
Zapper(Node::Port);
|
||||
~Zapper();
|
||||
|
||||
auto main() -> void;
|
||||
auto data() -> n3 override;
|
||||
auto latch(n1 data) -> void override;
|
||||
auto serialize(serializer&) -> void override;
|
||||
|
||||
private:
|
||||
i32 cx = 256 / 2; //x-coordinate
|
||||
i32 cy = 240 / 2; //y-coordinate
|
||||
i32 px = 0;
|
||||
i32 py = 0;
|
||||
u32 nx = 256 / 2;
|
||||
u32 ny = 240 / 2;
|
||||
u32 previous = 0;
|
||||
};
|
||||
@@ -34,6 +34,7 @@ auto CPU::main() -> void {
|
||||
|
||||
auto CPU::step(u32 clocks) -> void {
|
||||
assert(clocks == rate());
|
||||
io.oddCycle ^= 1;
|
||||
Thread::step(clocks);
|
||||
Thread::synchronize();
|
||||
}
|
||||
|
||||
+5
-7
@@ -49,15 +49,13 @@ struct CPU : MOS6502, Thread {
|
||||
auto irqPending() -> bool override;
|
||||
auto nmi(n16& vector) -> void override;
|
||||
|
||||
auto oamDMA() -> void;
|
||||
auto dmcDMAPending() -> void;
|
||||
auto dma(n16 address) -> void;
|
||||
|
||||
auto nmiLine(bool) -> void;
|
||||
auto irqLine(bool) -> void;
|
||||
auto apuLine(bool) -> void;
|
||||
|
||||
auto rdyLine(bool) -> void;
|
||||
auto rdyAddress(bool valid, n16 value = 0) -> void;
|
||||
|
||||
//protected:
|
||||
struct IO {
|
||||
n1 interruptPending;
|
||||
@@ -65,9 +63,9 @@ struct CPU : MOS6502, Thread {
|
||||
n1 nmiLine;
|
||||
n1 irqLine;
|
||||
n1 apuLine;
|
||||
n1 rdyLine = 1;
|
||||
n1 rdyAddressValid;
|
||||
n16 rdyAddressValue;
|
||||
n1 oddCycle;
|
||||
n1 dmcDMAPending;
|
||||
n1 dmcDummyRead;
|
||||
n1 oamDMAPending;
|
||||
n8 oamDMAPage;
|
||||
n8 openBus;
|
||||
|
||||
@@ -7,9 +7,9 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(io.nmiLine);
|
||||
s(io.irqLine);
|
||||
s(io.apuLine);
|
||||
s(io.rdyLine);
|
||||
s(io.rdyAddressValid);
|
||||
s(io.rdyAddressValue);
|
||||
s(io.oddCycle);
|
||||
s(io.dmcDMAPending);
|
||||
s(io.dmcDummyRead);
|
||||
s(io.oamDMAPending);
|
||||
s(io.oamDMAPage);
|
||||
s(io.openBus);
|
||||
|
||||
+63
-22
@@ -1,13 +1,6 @@
|
||||
auto CPU::read(n16 address) -> n8 {
|
||||
if(io.oamDMAPending) {
|
||||
io.oamDMAPending = 0;
|
||||
read(address);
|
||||
oamDMA();
|
||||
}
|
||||
|
||||
while(io.rdyLine == 0) {
|
||||
io.openBus = readBus(io.rdyAddressValid ? io.rdyAddressValue : address);
|
||||
step(rate());
|
||||
if(io.oamDMAPending || io.dmcDMAPending) {
|
||||
dma(address);
|
||||
}
|
||||
|
||||
io.openBus = readBus(address);
|
||||
@@ -43,10 +36,67 @@ auto CPU::nmi(n16& vector) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::oamDMA() -> void {
|
||||
for(u32 n : range(256)) {
|
||||
n8 data = read(io.oamDMAPage << 8 | n);
|
||||
write(0x2004, data);
|
||||
auto CPU::dmcDMAPending() -> void {
|
||||
io.dmcDMAPending = 1;
|
||||
io.dmcDummyRead = 0;
|
||||
}
|
||||
|
||||
auto CPU::dma(n16 address) -> void {
|
||||
bool oamWriteReady = false;
|
||||
n8 oamCounter = 0;
|
||||
bool skipDummyReads = (address == 0x4016 || address == 0x4017);
|
||||
|
||||
// halt read
|
||||
io.openBus = readBus(address);
|
||||
step(rate());
|
||||
|
||||
while (io.dmcDMAPending || io.oamDMAPending) {
|
||||
if (io.oddCycle) {
|
||||
// put_cycle
|
||||
if (io.oamDMAPending && oamWriteReady) {
|
||||
// oam write
|
||||
writeBus(0x2004, io.openBus);
|
||||
step(rate());
|
||||
|
||||
oamWriteReady = false;
|
||||
if (++oamCounter == 0)
|
||||
io.oamDMAPending = 0;
|
||||
} else {
|
||||
// oam (re)alignment cycle
|
||||
// dmc alignment cycle
|
||||
io.dmcDummyRead = 1;
|
||||
|
||||
if (!skipDummyReads)
|
||||
io.openBus = readBus(address);
|
||||
step(rate());
|
||||
}
|
||||
} else {
|
||||
// get_cycle
|
||||
if (io.dmcDMAPending && io.dmcDummyRead) {
|
||||
// dmc read
|
||||
io.openBus = readBus(apu.dmc.dmaAddress());
|
||||
step(rate());
|
||||
|
||||
apu.dmc.setDMABuffer(io.openBus);
|
||||
io.dmcDMAPending = 0;
|
||||
} else if (io.oamDMAPending) {
|
||||
if (io.dmcDMAPending)
|
||||
io.dmcDummyRead = 1;
|
||||
|
||||
// oam read
|
||||
io.openBus = readBus(io.oamDMAPage << 8 | oamCounter);
|
||||
step(rate());
|
||||
|
||||
oamWriteReady = true;
|
||||
} else {
|
||||
io.dmcDummyRead = 1;
|
||||
|
||||
// dmc dummy read cycle
|
||||
if (!skipDummyReads)
|
||||
io.openBus = readBus(address);
|
||||
step(rate());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,12 +115,3 @@ auto CPU::apuLine(bool line) -> void {
|
||||
//level-sensitive
|
||||
io.apuLine = line;
|
||||
}
|
||||
|
||||
auto CPU::rdyLine(bool line) -> void {
|
||||
io.rdyLine = line;
|
||||
}
|
||||
|
||||
auto CPU::rdyAddress(bool valid, n16 value) -> void {
|
||||
io.rdyAddressValid = valid;
|
||||
io.rdyAddressValue = value;
|
||||
}
|
||||
@@ -25,6 +25,15 @@ auto PPU::Debugger::load(Node::Object parent) -> void {
|
||||
memory.oam->setWrite([&](u32 address, u8 data) -> void {
|
||||
ppu.oam[address] = data;
|
||||
});
|
||||
|
||||
memory.soam = parent->append<Node::Debugger::Memory>("PPU SOAM");
|
||||
memory.soam->setSize(ppu.soam.size());
|
||||
memory.soam->setRead([&](u32 address) -> u8 {
|
||||
return ppu.soam[address];
|
||||
});
|
||||
memory.soam->setWrite([&](u32 address, u8 data) -> void {
|
||||
ppu.soam[address] = data;
|
||||
});
|
||||
}
|
||||
|
||||
auto PPU::Debugger::unload() -> void {
|
||||
|
||||
+77
-40
@@ -7,15 +7,17 @@ auto PPU::writeCIRAM(n11 address, n8 data) -> void {
|
||||
}
|
||||
|
||||
auto PPU::readCGRAM(n5 address) -> n8 {
|
||||
if((address & 0x13) == 0x10) address &= ~0x10;
|
||||
if((address & 0x3) == 0x0) address &= ~0x10;
|
||||
n8 data = cgram[address];
|
||||
if(io.grayscale) data &= 0x30;
|
||||
return data;
|
||||
}
|
||||
|
||||
auto PPU::writeCGRAM(n5 address, n8 data) -> void {
|
||||
if((address & 0x13) == 0x10) address &= ~0x10;
|
||||
cgram[address] = data;
|
||||
|
||||
if((address & 0x3) == 0x0)
|
||||
cgram[address ^ 0x10] = data;
|
||||
}
|
||||
|
||||
auto PPU::readIO(n16 address) -> n8 {
|
||||
@@ -25,35 +27,47 @@ auto PPU::readIO(n16 address) -> n8 {
|
||||
|
||||
//PPUSTATUS
|
||||
case 2:
|
||||
result.bit(5) = io.spriteOverflow;
|
||||
result.bit(5) = sprite.spriteOverflow;
|
||||
result.bit(6) = io.spriteZeroHit;
|
||||
result.bit(7) = io.nmiFlag;
|
||||
io.v.latch = 0;
|
||||
scroll.latch = 0;
|
||||
io.nmiHold = 0;
|
||||
cpu.nmiLine(io.nmiFlag = 0);
|
||||
break;
|
||||
|
||||
//OAMDATA
|
||||
case 4:
|
||||
result = oam[io.oamAddress];
|
||||
result = oam[sprite.oamAddress];
|
||||
|
||||
if (io.ly < 240 || io.ly == vlines() - 1 ||
|
||||
(Region::PAL() && io.ly >= 264 && io.ly <= vlines() - 2))
|
||||
if (enable())
|
||||
result = sprite.oamData;
|
||||
break;
|
||||
|
||||
//PPUDATA
|
||||
case 7:
|
||||
if(enable() && (io.ly < 240 || io.ly == vlines() - 1)) break;
|
||||
|
||||
address = (n14)io.v.address;
|
||||
if(address <= 0x1fff) {
|
||||
result = io.busData;
|
||||
io.busData = cartridge.readCHR(address);
|
||||
} else if(address <= 0x3eff) {
|
||||
result = io.busData;
|
||||
io.busData = cartridge.readCHR(address);
|
||||
} else if(address <= 0x3fff) {
|
||||
result.bit(0, 5) = readCGRAM(address);
|
||||
io.busData = cartridge.readCHR(address);
|
||||
if (var.blockingRead) {
|
||||
result = io.mdr;
|
||||
break;
|
||||
}
|
||||
io.v.address += io.vramIncrement;
|
||||
|
||||
address = io.busAddress;
|
||||
result = var.latchData;
|
||||
var.latchData = cartridge.readCHR(address);
|
||||
if (address >= 0x3f00)
|
||||
result = readCGRAM(address) | (io.mdr & 0xc0);
|
||||
|
||||
if (rendering()) {
|
||||
incrementVRAMAddressX();
|
||||
incrementVRAMAddressY();
|
||||
} else {
|
||||
var.address += io.vramIncrement;
|
||||
io.busAddress = var.address;
|
||||
cartridge.ppuAddressBus(io.busAddress);
|
||||
}
|
||||
|
||||
var.blockingRead = 6;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -67,7 +81,7 @@ auto PPU::writeIO(n16 address, n8 data) -> void {
|
||||
|
||||
//PPUCTRL
|
||||
case 0:
|
||||
io.t.nametable = data.bit(0,1);
|
||||
scroll.nametable = data.bit(0,1);
|
||||
io.vramIncrement = data.bit(2) ? 32 : 1;
|
||||
io.spriteAddress = data.bit(3) ? 0x1000 : 0x0000;
|
||||
io.bgAddress = data.bit(4) ? 0x1000 : 0x0000;
|
||||
@@ -93,49 +107,72 @@ auto PPU::writeIO(n16 address, n8 data) -> void {
|
||||
|
||||
//OAMADDR
|
||||
case 3:
|
||||
io.oamAddress = data;
|
||||
sprite.oamAddress = data;
|
||||
break;
|
||||
|
||||
//OAMDATA
|
||||
case 4:
|
||||
if(io.oamAddress.bit(0,1) == 2) data.bit(2,4) = 0; //clear non-existent bits (always read back as 0)
|
||||
oam[io.oamAddress++] = data;
|
||||
// Writes to OAMDATA during rendering (on the pre-render
|
||||
// line and the visible lines 0-239, provided either
|
||||
// sprite or background rendering is enabled) do not
|
||||
// modify values in OAM, but do perform a glitchy
|
||||
// increment of OAMADDR, bumping only the high 6 bits
|
||||
if (io.ly < 240 || io.ly == vlines() - 1 ||
|
||||
(Region::PAL() && io.ly >= 264 && io.ly <= vlines() - 2)) {
|
||||
if (enable()) {
|
||||
++sprite.oamMainCounterIndex;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The three unimplemented bits of each sprite's byte 2
|
||||
// do not exist in the PPU and always read back as 0 on
|
||||
// PPU revisions that allow reading PPU OAM through
|
||||
// OAMDATA ($2004)
|
||||
if (sprite.oamMainCounterTiming == 2)
|
||||
data.bit(2,4) = 0;
|
||||
|
||||
oam[sprite.oamAddress] = data;
|
||||
++sprite.oamMainCounter;
|
||||
break;
|
||||
|
||||
//PPUSCROLL
|
||||
case 5:
|
||||
if(io.v.latch++ == 0) {
|
||||
io.v.fineX = data.bit(0,2);
|
||||
io.t.tileX = data.bit(3,7);
|
||||
if(scroll.latch++ == 0) {
|
||||
scroll.fineX = data.bit(0,2);
|
||||
scroll.tileX = data.bit(3,7);
|
||||
} else {
|
||||
io.t.fineY = data.bit(0,2);
|
||||
io.t.tileY = data.bit(3,7);
|
||||
scroll.fineY = data.bit(0,2);
|
||||
scroll.tileY = data.bit(3,7);
|
||||
}
|
||||
break;
|
||||
|
||||
//PPUADDR
|
||||
case 6:
|
||||
if(io.v.latch++ == 0) {
|
||||
io.t.addressHi = data.bit(0,5);
|
||||
if(scroll.latch++ == 0) {
|
||||
scroll.addressHi = data.bit(0,5);
|
||||
} else {
|
||||
io.t.addressLo = data.bit(0,7);
|
||||
io.v.address = io.t.address;
|
||||
scroll.addressLo = data.bit(0,7);
|
||||
scroll.transferDelay = 3;
|
||||
}
|
||||
break;
|
||||
|
||||
//PPUDATA
|
||||
case 7:
|
||||
if(enable() && (io.ly < 240 || io.ly == vlines() - 1)) return;
|
||||
|
||||
address = (n14)io.v.address;
|
||||
if(address <= 0x1fff) {
|
||||
cartridge.writeCHR(address, data);
|
||||
} else if(address <= 0x3eff) {
|
||||
cartridge.writeCHR(address, data);
|
||||
} else if(address <= 0x3fff) {
|
||||
address = io.busAddress;
|
||||
if (address >= 0x3f00)
|
||||
writeCGRAM(address, data);
|
||||
else
|
||||
cartridge.writeCHR(address, data);
|
||||
|
||||
if (rendering()) {
|
||||
incrementVRAMAddressX();
|
||||
incrementVRAMAddressY();
|
||||
} else {
|
||||
var.address += io.vramIncrement;
|
||||
io.busAddress = var.address;
|
||||
cartridge.ppuAddressBus(io.busAddress);
|
||||
}
|
||||
io.v.address += io.vramIncrement;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
+29
-7
@@ -7,18 +7,21 @@ PPU ppu;
|
||||
#include "render.cpp"
|
||||
#include "color.cpp"
|
||||
#include "debugger.cpp"
|
||||
#include "sprite.cpp"
|
||||
#include "scroll.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto PPU::load(Node::Object parent) -> void {
|
||||
ciram.allocate(2048);
|
||||
cgram.allocate(32);
|
||||
oam.allocate(256);
|
||||
soam.allocate(32);
|
||||
|
||||
node = parent->append<Node::Object>("PPU");
|
||||
|
||||
screen = node->append<Node::Video::Screen>("Screen", 282, displayHeight());
|
||||
screen = node->append<Node::Video::Screen>("Screen", 283, displayHeight());
|
||||
screen->colors(1 << 9, {&PPU::color, this});
|
||||
screen->setSize(282, displayHeight());
|
||||
screen->setSize(283, displayHeight());
|
||||
screen->setScale(1.0, 1.0);
|
||||
Region::PAL() ? screen->setAspect(55.0, 43.0) :screen->setAspect(8.0, 7.0);
|
||||
screen->refreshRateHint(system.frequency() / rate(), 341, vlines());
|
||||
@@ -35,6 +38,7 @@ auto PPU::unload() -> void {
|
||||
ciram.reset();
|
||||
cgram.reset();
|
||||
oam.reset();
|
||||
soam.reset();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
@@ -45,11 +49,24 @@ auto PPU::step(u32 clocks) -> void {
|
||||
u32 L = vlines();
|
||||
|
||||
while(clocks--) {
|
||||
if (var.blockingRead) --var.blockingRead;
|
||||
scrollTransferDelay();
|
||||
|
||||
if (enable() && io.ly == L - 1)
|
||||
cyclePrepareSpriteEvaluation();
|
||||
|
||||
if (enable() && (io.ly < 240 || (Region::PAL() && io.ly >= 264 && io.ly <= L - 2)))
|
||||
cycleSpriteEvaluation();
|
||||
|
||||
if (enable() && (io.ly < 240 || io.ly == L - 1))
|
||||
cycleScroll();
|
||||
|
||||
if(io.ly == 240 && io.lx == 1) io.busAddress = var.address, cartridge.ppuAddressBus(io.busAddress);
|
||||
if(io.ly == 240 && io.lx == 340) io.nmiHold = 1;
|
||||
if(io.ly == 241 && io.lx == 0) io.nmiFlag = io.nmiHold;
|
||||
if(io.ly == 241 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
|
||||
|
||||
if(io.ly == L-2 && io.lx == 340) io.spriteZeroHit = 0, io.spriteOverflow = 0;
|
||||
if(io.ly == L-2 && io.lx == 340) io.spriteZeroHit = 0, sprite.spriteOverflow = 0;
|
||||
|
||||
if(io.ly == L-2 && io.lx == 340) io.nmiHold = 0;
|
||||
if(io.ly == L-1 && io.lx == 0) io.nmiFlag = io.nmiHold;
|
||||
@@ -76,12 +93,12 @@ auto PPU::frame() -> void {
|
||||
io.field++;
|
||||
|
||||
if(screen->overscan()) {
|
||||
screen->setSize(282, displayHeight());
|
||||
screen->setViewport(0, 0, 282, displayHeight());
|
||||
screen->setSize(283, displayHeight());
|
||||
screen->setViewport(0, 0, 283, displayHeight());
|
||||
} else {
|
||||
int x = 16;
|
||||
int y = 8;
|
||||
int width = 282 - 32;
|
||||
int width = 283 - 33;
|
||||
int height = displayHeight() - 16;
|
||||
|
||||
if(Region::PAL()) height -= 48;
|
||||
@@ -100,11 +117,16 @@ auto PPU::power(bool reset) -> void {
|
||||
|
||||
if(!reset) {
|
||||
ciram.fill();
|
||||
cgram.fill();
|
||||
oam.fill();
|
||||
soam.fill();
|
||||
memory::copy(cgram.data(), cgramBootValue, 32);
|
||||
}
|
||||
|
||||
scroll = {};
|
||||
var = {};
|
||||
io = {};
|
||||
latch = {};
|
||||
sprite = {};
|
||||
}
|
||||
|
||||
}
|
||||
+90
-25
@@ -4,6 +4,7 @@ struct PPU : Thread {
|
||||
Memory::Writable<n8> ciram;
|
||||
Memory::Writable<n8> cgram;
|
||||
Memory::Writable<n8> oam;
|
||||
Memory::Writable<n8> soam;
|
||||
|
||||
struct Debugger {
|
||||
//debugger.cpp
|
||||
@@ -14,6 +15,7 @@ struct PPU : Thread {
|
||||
Node::Debugger::Memory ciram;
|
||||
Node::Debugger::Memory cgram;
|
||||
Node::Debugger::Memory oam;
|
||||
Node::Debugger::Memory soam;
|
||||
} memory;
|
||||
} debugger;
|
||||
|
||||
@@ -45,10 +47,10 @@ struct PPU : Thread {
|
||||
|
||||
//render.cpp
|
||||
auto enable() const -> bool;
|
||||
auto rendering() const -> bool;
|
||||
auto loadCHR(n16 address) -> n8;
|
||||
|
||||
auto renderPixel() -> void;
|
||||
auto renderSprite() -> void;
|
||||
auto renderScanline() -> void;
|
||||
|
||||
//color.cpp
|
||||
@@ -57,28 +59,64 @@ struct PPU : Thread {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
// scroll.cpp
|
||||
auto incrementVRAMAddressX() -> void;
|
||||
auto incrementVRAMAddressY() -> void;
|
||||
auto transferScrollX() -> void;
|
||||
auto transferScrollY() -> void;
|
||||
auto cycleScroll() -> void;
|
||||
auto scrollTransferDelay() -> void;
|
||||
|
||||
// sprite.cpp
|
||||
auto cycleSpriteEvaluation() -> void;
|
||||
auto cyclePrepareSpriteEvaluation() -> void;
|
||||
|
||||
struct ScrollRegisters {
|
||||
n15 data;
|
||||
|
||||
BitRange<15, 0, 4> tileX {&data};
|
||||
BitRange<15, 5, 9> tileY {&data};
|
||||
BitRange<15,10,10> nametableX{&data};
|
||||
BitRange<15,11,11> nametableY{&data};
|
||||
BitRange<15,12,14> fineY {&data};
|
||||
n1 latch;
|
||||
n3 fineX;
|
||||
|
||||
BitRange<15,10,11> nametable {&data};
|
||||
BitRange<15, 0,14> address {&data};
|
||||
BitRange<15, 0, 7> addressLo {&data};
|
||||
BitRange<15, 8,14> addressHi {&data};
|
||||
|
||||
n8 transferDelay;
|
||||
} scroll;
|
||||
|
||||
struct VRAMAddressRegisters {
|
||||
n15 data;
|
||||
|
||||
BitRange<15, 0, 4> tileX {&data};
|
||||
BitRange<15, 5, 9> tileY {&data};
|
||||
BitRange<15,10,10> nametableX{&data};
|
||||
BitRange<15,11,11> nametableY{&data};
|
||||
BitRange<15,12,14> fineY {&data};
|
||||
|
||||
BitRange<15,10,11> nametable {&data};
|
||||
BitRange<15, 0,14> address {&data};
|
||||
|
||||
BitRange<15, 2, 4> attrX {&data};
|
||||
BitRange<15, 7, 9> attrY {&data};
|
||||
|
||||
n8 latchData;
|
||||
n8 blockingRead;
|
||||
} var;
|
||||
|
||||
struct IO {
|
||||
n14 busAddress;
|
||||
|
||||
//internal
|
||||
n8 mdr;
|
||||
n1 field;
|
||||
n16 lx;
|
||||
n16 ly;
|
||||
n8 busData;
|
||||
|
||||
struct Union {
|
||||
n19 data;
|
||||
BitRange<19, 0, 4> tileX {&data};
|
||||
BitRange<19, 5, 9> tileY {&data};
|
||||
BitRange<19,10,11> nametable {&data};
|
||||
BitRange<19,10,10> nametableX{&data};
|
||||
BitRange<19,11,11> nametableY{&data};
|
||||
BitRange<19,12,14> fineY {&data};
|
||||
BitRange<19, 0,14> address {&data};
|
||||
BitRange<19, 0, 7> addressLo {&data};
|
||||
BitRange<19, 8,14> addressHi {&data};
|
||||
BitRange<19,15,15> latch {&data};
|
||||
BitRange<19,16,18> fineX {&data};
|
||||
} v, t;
|
||||
|
||||
n1 nmiHold;
|
||||
n1 nmiFlag;
|
||||
@@ -100,11 +138,7 @@ struct PPU : Thread {
|
||||
n3 emphasis;
|
||||
|
||||
//$2002
|
||||
n1 spriteOverflow;
|
||||
n1 spriteZeroHit;
|
||||
|
||||
//$2003
|
||||
n8 oamAddress;
|
||||
} io;
|
||||
|
||||
struct OAM {
|
||||
@@ -127,14 +161,45 @@ struct PPU : Thread {
|
||||
n16 tiledataLo;
|
||||
n16 tiledataHi;
|
||||
|
||||
n16 oamIterator;
|
||||
n16 oamCounter;
|
||||
|
||||
n8 oamId[8];
|
||||
OAM oam[8]; //primary
|
||||
OAM soam[8]; //secondary
|
||||
} latch;
|
||||
|
||||
struct SpriteEvaluation {
|
||||
// $2002 bit5
|
||||
n1 spriteOverflow;
|
||||
|
||||
// $2003
|
||||
n8 oamAddress;
|
||||
|
||||
// $2004
|
||||
n8 oamData;
|
||||
|
||||
// main oam counter (oamAddress)
|
||||
BitRange<8,0,7> oamMainCounter{&oamAddress};
|
||||
bool oamMainCounterOverflow;
|
||||
// every sprite has 4 bytes
|
||||
BitRange<8,0,1> oamMainCounterTiming{&oamAddress};
|
||||
// main counter have 64 sprites
|
||||
BitRange<8,2,7> oamMainCounterIndex{&oamAddress};
|
||||
|
||||
// secondary oam counter
|
||||
n5 oamTempCounter;
|
||||
bool oamTempCounterOverflow;
|
||||
// every sprite has 4 bytes
|
||||
BitRange<5,0,1> oamTempCounterTiming{&oamTempCounter};
|
||||
// temp counter have 8 sprites
|
||||
BitRange<5,2,4> oamTempCounterIndex{&oamTempCounter};
|
||||
} sprite;
|
||||
|
||||
u32* output;
|
||||
|
||||
static constexpr u8 cgramBootValue[32] = {
|
||||
0x09, 0x01, 0x00, 0x01, 0x00, 0x02, 0x02, 0x0D,
|
||||
0x08, 0x10, 0x08, 0x24, 0x00, 0x00, 0x04, 0x2C,
|
||||
0x09, 0x01, 0x34, 0x03, 0x00, 0x04, 0x00, 0x14,
|
||||
0x08, 0x3A, 0x00, 0x02, 0x00, 0x20, 0x2C, 0x08,
|
||||
};
|
||||
};
|
||||
|
||||
extern PPU ppu;
|
||||
+66
-89
@@ -2,15 +2,25 @@ auto PPU::enable() const -> bool {
|
||||
return io.bgEnable || io.spriteEnable;
|
||||
}
|
||||
|
||||
auto PPU::rendering() const -> bool {
|
||||
return enable() && (io.ly < 240 || io.ly == vlines() - 1);
|
||||
}
|
||||
|
||||
auto PPU::loadCHR(n16 address) -> n8 {
|
||||
return enable() ? cartridge.readCHR(address) : (n8)0x00;
|
||||
if (enable()) {
|
||||
io.busAddress = (n14)address;
|
||||
cartridge.ppuAddressBus(address);
|
||||
return cartridge.readCHR(address);
|
||||
} else {
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::renderPixel() -> void {
|
||||
if(io.ly >= screen->canvasHeight()) return;
|
||||
|
||||
u32 x = io.lx - 1;
|
||||
u32 mask = 0x8000 >> (io.v.fineX + (x & 7));
|
||||
u32 mask = 0x8000 >> (scroll.fineX + (x & 7));
|
||||
u32 palette = 0;
|
||||
u32 objectPalette = 0;
|
||||
bool objectPriority = 0;
|
||||
@@ -30,99 +40,77 @@ auto PPU::renderPixel() -> void {
|
||||
if(!io.bgEdgeEnable && x < 8) palette = 0;
|
||||
|
||||
if(io.spriteEnable)
|
||||
for(i32 sprite = 7; sprite >= 0; sprite--) {
|
||||
if(!io.spriteEdgeEnable && x < 8) continue;
|
||||
if(latch.oam[sprite].id == 64) continue;
|
||||
for(i32 sprite = 7; sprite >= 0; sprite--) {
|
||||
if(!io.spriteEdgeEnable && x < 8) continue;
|
||||
if(latch.oam[sprite].id == 64) continue;
|
||||
|
||||
u32 spriteX = x - latch.oam[sprite].x;
|
||||
if(spriteX >= 8) continue;
|
||||
u32 spriteX = x - latch.oam[sprite].x;
|
||||
if(spriteX >= 8) continue;
|
||||
|
||||
if(latch.oam[sprite].attr & 0x40) spriteX ^= 7;
|
||||
u32 mask = 0x80 >> spriteX;
|
||||
u32 spritePalette = 0;
|
||||
spritePalette |= latch.oam[sprite].tiledataLo & mask ? 1 : 0;
|
||||
spritePalette |= latch.oam[sprite].tiledataHi & mask ? 2 : 0;
|
||||
if(spritePalette == 0) continue;
|
||||
if(latch.oam[sprite].attr & 0x40) spriteX ^= 7;
|
||||
u32 mask = 0x80 >> spriteX;
|
||||
u32 spritePalette = 0;
|
||||
spritePalette |= latch.oam[sprite].tiledataLo & mask ? 1 : 0;
|
||||
spritePalette |= latch.oam[sprite].tiledataHi & mask ? 2 : 0;
|
||||
if(spritePalette == 0) continue;
|
||||
|
||||
if(latch.oam[sprite].id == 0 && palette && x != 255) io.spriteZeroHit = 1;
|
||||
spritePalette |= (latch.oam[sprite].attr & 3) << 2;
|
||||
if(latch.oam[sprite].id == 0 && palette && x != 255) io.spriteZeroHit = 1;
|
||||
spritePalette |= (latch.oam[sprite].attr & 3) << 2;
|
||||
|
||||
objectPriority = latch.oam[sprite].attr & 0x20;
|
||||
objectPalette = 16 + spritePalette;
|
||||
}
|
||||
objectPriority = latch.oam[sprite].attr & 0x20;
|
||||
objectPalette = 16 + spritePalette;
|
||||
}
|
||||
|
||||
if(objectPalette) {
|
||||
if(palette == 0 || objectPriority == 0) palette = objectPalette;
|
||||
}
|
||||
|
||||
if(!enable()) palette = 0;
|
||||
|
||||
if(Region::PAL()) {
|
||||
output[(x + 18) % 282] = io.emphasis << 6 | readCGRAM(palette);
|
||||
return;
|
||||
u32 color = 0;
|
||||
if (enable() || (n14)var.address < 0x3f00) {
|
||||
color = io.emphasis << 6 | readCGRAM(palette);
|
||||
} else {
|
||||
color = io.emphasis << 6 | readCGRAM((n5)var.address);
|
||||
}
|
||||
|
||||
output[(x + 15) % 282] = io.emphasis << 6 | readCGRAM(palette);
|
||||
}
|
||||
|
||||
auto PPU::renderSprite() -> void {
|
||||
if(!enable()) return;
|
||||
|
||||
u32 n = latch.oamIterator++;
|
||||
s32 ly = io.ly == vlines() - 1 ? -1 : (s32)io.ly;
|
||||
u32 y = ly - oam[n * 4 + 0];
|
||||
|
||||
if(y >= io.spriteHeight) return;
|
||||
if(latch.oamCounter == 8) {
|
||||
io.spriteOverflow = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
auto& o = latch.soam[latch.oamCounter++];
|
||||
o.id = n;
|
||||
o.y = oam[n * 4 + 0];
|
||||
o.tile = oam[n * 4 + 1];
|
||||
o.attr = oam[n * 4 + 2];
|
||||
o.x = oam[n * 4 + 3];
|
||||
if(Region::PAL())
|
||||
output[(x + 18) % 283] = color;
|
||||
else
|
||||
output[(x + 16) % 283] = color;
|
||||
}
|
||||
|
||||
auto PPU::renderScanline() -> void {
|
||||
if(io.ly < screen->canvasHeight()) {
|
||||
output = screen->pixels().data() + io.ly * 282;
|
||||
for(auto n : range(282)) output[n] = Region::PAL() ? 0x3f : (io.emphasis << 6 | readCGRAM(0));
|
||||
output = screen->pixels().data() + io.ly * 283;
|
||||
for (auto n : range(283))
|
||||
output[n] = Region::PAL() ? 0x3f : io.emphasis << 6 | readCGRAM(0);
|
||||
}
|
||||
|
||||
//Vblank
|
||||
if(io.ly >= 240 && io.ly <= vlines() - 2) return step(341), scanline();
|
||||
|
||||
latch.oamIterator = 0;
|
||||
latch.oamCounter = 0;
|
||||
|
||||
for(auto n : range(8)) latch.soam[n] = {};
|
||||
|
||||
// 0
|
||||
step(1);
|
||||
|
||||
// force clear sprite counter at start of each scanline
|
||||
for (auto& id : latch.oamId) id = 64;
|
||||
|
||||
// 1-256
|
||||
for(u32 tile : range(32)) {
|
||||
u32 nametable = loadCHR(0x2000 | (n12)io.v.address);
|
||||
u32 tileaddr = io.bgAddress | nametable << 4 | io.v.fineY;
|
||||
u32 nametable = loadCHR(0x2000 | (n12)var.address);
|
||||
u32 tileaddr = io.bgAddress | nametable << 4 | var.fineY;
|
||||
renderPixel();
|
||||
step(1);
|
||||
|
||||
renderPixel();
|
||||
step(1);
|
||||
|
||||
u32 attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
|
||||
if(io.v.tileY & 2) attribute >>= 4;
|
||||
if(io.v.tileX & 2) attribute >>= 2;
|
||||
u32 attribute = loadCHR(0x23c0 | var.nametable << 10 | var.attrY << 3 | var.attrX);
|
||||
if(var.tileY & 2) attribute >>= 4;
|
||||
if(var.tileX & 2) attribute >>= 2;
|
||||
renderPixel();
|
||||
step(1);
|
||||
|
||||
if(enable() && ++io.v.tileX == 0) io.v.nametableX++;
|
||||
if(enable() && tile == 31 && ++io.v.fineY == 0 && ++io.v.tileY == 30) io.v.nametableY++, io.v.tileY = 0;
|
||||
renderPixel();
|
||||
renderSprite();
|
||||
step(1);
|
||||
|
||||
u32 tiledataLo = loadCHR(tileaddr + 0);
|
||||
@@ -137,7 +125,6 @@ auto PPU::renderScanline() -> void {
|
||||
step(1);
|
||||
|
||||
renderPixel();
|
||||
renderSprite();
|
||||
step(1);
|
||||
|
||||
latch.nametable = latch.nametable << 8 | nametable;
|
||||
@@ -146,22 +133,20 @@ auto PPU::renderScanline() -> void {
|
||||
latch.tiledataHi = latch.tiledataHi << 8 | tiledataHi;
|
||||
}
|
||||
|
||||
for(u32 n : range(8)) latch.oam[n] = latch.soam[n];
|
||||
for(u32 n : range(8)) {
|
||||
latch.oam[n].id = latch.oamId[n];
|
||||
latch.oam[n].y = soam[4 * n + 0];
|
||||
latch.oam[n].tile = soam[4 * n + 1];
|
||||
latch.oam[n].attr = soam[4 * n + 2];
|
||||
latch.oam[n].x = soam[4 * n + 3];
|
||||
}
|
||||
|
||||
//257-320
|
||||
for(u32 sprite : range(8)) {
|
||||
if(enable()) io.oamAddress = 0;
|
||||
u32 nametable = loadCHR(0x2000 | (n12)io.v.address);
|
||||
step(1);
|
||||
u32 nametable = loadCHR(0x2000 | (n12)var.address);
|
||||
step(2);
|
||||
|
||||
if(enable() && sprite == 0) {
|
||||
//258
|
||||
io.v.nametableX = io.t.nametableX;
|
||||
io.v.tileX = io.t.tileX;
|
||||
}
|
||||
step(1);
|
||||
|
||||
u32 attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
|
||||
u32 attribute = loadCHR(0x23c0 | var.nametable << 10 | (var.tileY >> 2) << 3 | var.tileX >> 2);
|
||||
u32 tileaddr = io.spriteHeight == 8
|
||||
? io.spriteAddress + latch.oam[sprite].tile * 16
|
||||
: (latch.oam[sprite].tile & ~1) * 16 + (latch.oam[sprite].tile & 1) * 0x1000;
|
||||
@@ -176,26 +161,18 @@ auto PPU::renderScanline() -> void {
|
||||
|
||||
latch.oam[sprite].tiledataHi = loadCHR(tileaddr + 8);
|
||||
step(2);
|
||||
|
||||
if(enable() && sprite == 6 && io.ly == vlines() - 1) {
|
||||
//305
|
||||
io.v.address = io.t.address;
|
||||
}
|
||||
}
|
||||
|
||||
//321-336
|
||||
for(u32 tile : range(2)) {
|
||||
u32 nametable = loadCHR(0x2000 | (n12)io.v.address);
|
||||
u32 tileaddr = io.bgAddress | nametable << 4 | io.v.fineY;
|
||||
u32 nametable = loadCHR(0x2000 | (n12)var.address);
|
||||
u32 tileaddr = io.bgAddress | nametable << 4 | var.fineY;
|
||||
step(2);
|
||||
|
||||
u32 attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
|
||||
if(io.v.tileY & 2) attribute >>= 4;
|
||||
if(io.v.tileX & 2) attribute >>= 2;
|
||||
step(1);
|
||||
|
||||
if(enable() && ++io.v.tileX == 0) io.v.nametableX++;
|
||||
step(1);
|
||||
u32 attribute = loadCHR(0x23c0 | var.nametable << 10 | (var.tileY >> 2) << 3 | var.tileX >> 2);
|
||||
if(var.tileY & 2) attribute >>= 4;
|
||||
if(var.tileX & 2) attribute >>= 2;
|
||||
step(2);
|
||||
|
||||
u32 tiledataLo = loadCHR(tileaddr + 0);
|
||||
step(2);
|
||||
@@ -210,12 +187,12 @@ auto PPU::renderScanline() -> void {
|
||||
}
|
||||
|
||||
//337-338
|
||||
loadCHR(0x2000 | (n12)io.v.address);
|
||||
loadCHR(0x2000 | (n12)var.address);
|
||||
bool skip = !Region::PAL() && enable() && io.field == 1 && io.ly == vlines() - 1;
|
||||
step(2);
|
||||
|
||||
//339
|
||||
loadCHR(0x2000 | (n12)io.v.address);
|
||||
loadCHR(0x2000 | (n12)var.address);
|
||||
step(1);
|
||||
|
||||
//340
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
auto PPU::incrementVRAMAddressX() -> void {
|
||||
if (++var.tileX == 0)
|
||||
var.nametableX++;
|
||||
}
|
||||
|
||||
auto PPU::incrementVRAMAddressY() -> void {
|
||||
if (++var.fineY == 0 && ++var.tileY == 30)
|
||||
var.nametableY++, var.tileY = 0;
|
||||
}
|
||||
|
||||
auto PPU::transferScrollX() -> void {
|
||||
var.tileX = scroll.tileX;
|
||||
var.nametableX = scroll.nametableX;
|
||||
}
|
||||
|
||||
auto PPU::transferScrollY() -> void {
|
||||
var.tileY = scroll.tileY;
|
||||
var.nametableY = scroll.nametableY;
|
||||
var.fineY = scroll.fineY;
|
||||
}
|
||||
|
||||
auto PPU::cycleScroll() -> void {
|
||||
if (io.lx == 0) return;
|
||||
|
||||
if (io.lx <= 256) {
|
||||
if (io.lx & 7) return;
|
||||
|
||||
incrementVRAMAddressX();
|
||||
if (io.lx == 256) incrementVRAMAddressY();
|
||||
return;
|
||||
}
|
||||
|
||||
if (io.lx == 257) {
|
||||
transferScrollX();
|
||||
return;
|
||||
}
|
||||
|
||||
if (io.ly == vlines() - 1) {
|
||||
if (io.lx >= 280 && io.lx <= 304) {
|
||||
transferScrollY();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (io.lx > 320 && io.lx <= 336) {
|
||||
if (io.lx & 7) return;
|
||||
|
||||
incrementVRAMAddressX();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::scrollTransferDelay() -> void {
|
||||
if (!scroll.transferDelay || --scroll.transferDelay)
|
||||
return;
|
||||
|
||||
bool isRendering = rendering();
|
||||
bool isTransferX = isRendering && io.lx != 0 && (io.lx & 7) == 0 && (io.lx <= 256 || io.lx > 320);
|
||||
bool isTransferY = isRendering && io.lx == 256;
|
||||
|
||||
if (isTransferX) {
|
||||
scroll.tileX = var.tileX & scroll.tileX;
|
||||
scroll.nametableX = var.nametableX & scroll.nametableX;
|
||||
}
|
||||
|
||||
if (isTransferY) {
|
||||
scroll.tileY = var.tileY & scroll.tileY;
|
||||
scroll.nametableY = var.nametableY & scroll.nametableY;
|
||||
scroll.fineY = var.fineY & scroll.fineY;
|
||||
}
|
||||
|
||||
transferScrollX();
|
||||
transferScrollY();
|
||||
|
||||
if (!isRendering) {
|
||||
io.busAddress = var.address;
|
||||
cartridge.ppuAddressBus(io.busAddress);
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,19 @@ auto PPU::serialize(serializer& s) -> void {
|
||||
s(ciram);
|
||||
s(cgram);
|
||||
s(oam);
|
||||
s(soam);
|
||||
s(scroll.data);
|
||||
s(scroll.latch);
|
||||
s(scroll.fineX);
|
||||
s(scroll.transferDelay);
|
||||
s(var.data);
|
||||
s(var.latchData);
|
||||
s(var.blockingRead);
|
||||
s(io.busAddress);
|
||||
s(io.mdr);
|
||||
s(io.field);
|
||||
s(io.lx);
|
||||
s(io.ly);
|
||||
s(io.busData);
|
||||
s(io.v.data);
|
||||
s(io.t.data);
|
||||
s(io.nmiHold);
|
||||
s(io.nmiFlag);
|
||||
s(io.vramIncrement);
|
||||
@@ -24,17 +30,20 @@ auto PPU::serialize(serializer& s) -> void {
|
||||
s(io.bgEnable);
|
||||
s(io.spriteEnable);
|
||||
s(io.emphasis);
|
||||
s(io.spriteOverflow);
|
||||
s(io.spriteZeroHit);
|
||||
s(io.oamAddress);
|
||||
s(latch.nametable);
|
||||
s(latch.attribute);
|
||||
s(latch.tiledataLo);
|
||||
s(latch.tiledataHi);
|
||||
s(latch.oamIterator);
|
||||
s(latch.oamCounter);
|
||||
s(latch.oamId);
|
||||
for(auto& o : latch.oam) s(o);
|
||||
for(auto& o : latch.soam) s(o);
|
||||
|
||||
s(sprite.spriteOverflow);
|
||||
s(sprite.oamAddress);
|
||||
s(sprite.oamData);
|
||||
s(sprite.oamTempCounterOverflow);
|
||||
s(sprite.oamTempCounter);
|
||||
s(sprite.oamTempCounterOverflow);
|
||||
}
|
||||
|
||||
auto PPU::OAM::serialize(serializer& s) -> void {
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
auto PPU::cycleSpriteEvaluation() -> void {
|
||||
if (io.lx == 0) return;
|
||||
|
||||
if (io.lx <= 64) {
|
||||
if ((io.lx & 1) == 1)
|
||||
sprite.oamData = 0xff;
|
||||
else
|
||||
soam[sprite.oamTempCounter++] = sprite.oamData;
|
||||
return;
|
||||
}
|
||||
|
||||
if (io.lx <= 256) {
|
||||
if ((io.lx & 1) == 1) {
|
||||
sprite.oamData = oam[sprite.oamMainCounter];
|
||||
return;
|
||||
}
|
||||
|
||||
if (sprite.oamMainCounterOverflow) {
|
||||
++sprite.oamMainCounterIndex;
|
||||
if (sprite.oamTempCounterOverflow)
|
||||
sprite.oamData = soam[sprite.oamTempCounter];
|
||||
return;
|
||||
}
|
||||
|
||||
if (sprite.oamTempCounterTiming == 0) {
|
||||
s32 ly = io.ly == vlines() - 1 ? -1 : (s32)io.ly;
|
||||
u32 y = ly - sprite.oamData;
|
||||
|
||||
if (sprite.oamTempCounterOverflow) {
|
||||
sprite.oamData = soam[sprite.oamTempCounter];
|
||||
|
||||
if (y >= io.spriteHeight) {
|
||||
if (++sprite.oamMainCounterIndex == 0)
|
||||
sprite.oamMainCounterOverflow = true;
|
||||
++sprite.oamMainCounterTiming;
|
||||
return;
|
||||
}
|
||||
|
||||
sprite.spriteOverflow = 1;
|
||||
++sprite.oamTempCounter;
|
||||
++sprite.oamMainCounterTiming;
|
||||
return;
|
||||
}
|
||||
|
||||
soam[sprite.oamTempCounter] = sprite.oamData;
|
||||
if (y >= io.spriteHeight) {
|
||||
if (++sprite.oamMainCounterIndex == 0)
|
||||
sprite.oamMainCounterOverflow = true;
|
||||
return;
|
||||
}
|
||||
|
||||
latch.oamId[sprite.oamTempCounterIndex] = sprite.oamMainCounterIndex;
|
||||
++sprite.oamTempCounter;
|
||||
++sprite.oamMainCounterTiming;
|
||||
} else {
|
||||
if (sprite.oamTempCounterOverflow) {
|
||||
sprite.oamData = soam[sprite.oamTempCounter];
|
||||
++sprite.oamTempCounter;
|
||||
++sprite.oamMainCounterTiming;
|
||||
return;
|
||||
}
|
||||
|
||||
soam[sprite.oamTempCounter] = sprite.oamData;
|
||||
++sprite.oamTempCounter;
|
||||
++sprite.oamMainCounterTiming;
|
||||
|
||||
if (sprite.oamTempCounterTiming == 0) {
|
||||
if (++sprite.oamMainCounterIndex == 0)
|
||||
sprite.oamMainCounterOverflow = true;
|
||||
|
||||
if (sprite.oamTempCounter == 0)
|
||||
sprite.oamTempCounterOverflow = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (io.lx <= 320) {
|
||||
u32 index = ((io.lx - 257) >> 3) << 2 + min((io.lx - 257) & 7, 3);
|
||||
|
||||
sprite.oamMainCounter = 0;
|
||||
sprite.oamTempCounter = 0;
|
||||
sprite.oamMainCounterOverflow = false;
|
||||
sprite.oamTempCounterOverflow = false;
|
||||
sprite.oamData = soam[index];
|
||||
return;
|
||||
}
|
||||
|
||||
if (io.lx <= 340) {
|
||||
sprite.oamData = soam[0];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::cyclePrepareSpriteEvaluation() -> void {
|
||||
// In https://www.nesdev.org/wiki/PPU_sprite_evaluation,
|
||||
// Sprite evaluation does not happen on the pre-render
|
||||
// scanline. Because evaluation applies to the next
|
||||
// line's sprite rendering, no sprites will be rendered
|
||||
// on the first scanline, and this is why there is a 1
|
||||
// line offset on a sprite's Y coordinate.
|
||||
if (io.lx > 0 && io.lx <= 8) {
|
||||
if (sprite.oamMainCounterIndex >= 2) {
|
||||
oam[io.lx - 1] = oam[(sprite.oamMainCounter & 0xf8) + io.lx - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
static const string SerializerVersion = "v140";
|
||||
static const string SerializerVersion = "v148";
|
||||
|
||||
auto System::serialize(bool synchronize) -> serializer {
|
||||
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
|
||||
|
||||
+4
-1
@@ -1,4 +1,6 @@
|
||||
auto APU::readIO(n32 address) -> n8 {
|
||||
cpu.synchronize(apu);
|
||||
|
||||
switch(address) {
|
||||
|
||||
//NR10
|
||||
@@ -140,11 +142,12 @@ auto APU::readIO(n32 address) -> n8 {
|
||||
|
||||
}
|
||||
|
||||
if(cpu.context.dmaActive) return cpu.dmabus.data.byte(address & 3);
|
||||
return cpu.pipeline.fetch.instruction.byte(address & 1);
|
||||
}
|
||||
|
||||
auto APU::writeIO(n32 address, n8 data) -> void {
|
||||
cpu.synchronize(apu);
|
||||
|
||||
switch(address) {
|
||||
|
||||
//NR10
|
||||
|
||||
+17
-11
@@ -2,28 +2,26 @@ auto CPU::sleep() -> void {
|
||||
prefetchStep(1);
|
||||
}
|
||||
|
||||
auto CPU::get(u32 mode, n32 address) -> n32 {
|
||||
template <bool UseDebugger>
|
||||
inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
|
||||
u32 clocks = _wait(mode, address);
|
||||
u32 word = pipeline.fetch.instruction;
|
||||
if(context.dmaActive) word = dmabus.data;
|
||||
|
||||
if(address >= 0x1000'0000) {
|
||||
prefetchStep(clocks);
|
||||
if constexpr(!UseDebugger) prefetchStep(clocks);
|
||||
} else if(address & 0x0800'0000) {
|
||||
if(mode & Prefetch && wait.prefetch) {
|
||||
dmaRun();
|
||||
prefetchSync(address);
|
||||
prefetchStep(1);
|
||||
word = prefetchRead();
|
||||
if(mode & Word) word |= prefetchRead() << 16;
|
||||
word = prefetchRead(mode);
|
||||
} else {
|
||||
if(!context.dmaActive) prefetchReset(); //todo: Prefetch buffer should also be reset when DMA accesses ROM
|
||||
step(clocks - 1);
|
||||
if constexpr(!UseDebugger) prefetchReset();
|
||||
if constexpr(!UseDebugger) step(clocks);
|
||||
word = cartridge.read(mode, address);
|
||||
step(1);
|
||||
}
|
||||
} else {
|
||||
if(memory.biosSwap && address < 0x0400'0000) address ^= 0x0200'0000;
|
||||
prefetchStep(clocks);
|
||||
if constexpr(!UseDebugger) prefetchStep(clocks);
|
||||
if(auto result = platform->cheat(address)) return *result;
|
||||
if(address < 0x0200'0000) word = bios.read(mode, address);
|
||||
else if(address < 0x0300'0000) word = readEWRAM(mode, address);
|
||||
@@ -38,13 +36,21 @@ auto CPU::get(u32 mode, n32 address) -> n32 {
|
||||
return word;
|
||||
}
|
||||
|
||||
auto CPU::get(u32 mode, n32 address) -> n32 {
|
||||
return getBus<false>(mode, address);
|
||||
}
|
||||
|
||||
auto CPU::getDebugger(u32 mode, n32 address) -> n32 {
|
||||
return getBus<true>(mode, address);
|
||||
}
|
||||
|
||||
auto CPU::set(u32 mode, n32 address, n32 word) -> void {
|
||||
u32 clocks = _wait(mode, address);
|
||||
|
||||
if(address >= 0x1000'0000) {
|
||||
prefetchStep(clocks);
|
||||
} else if(address & 0x0800'0000) {
|
||||
if(!context.dmaActive) prefetchReset(); //todo: Prefetch buffer should also be reset when DMA accesses ROM
|
||||
prefetchReset();
|
||||
step(clocks);
|
||||
cartridge.write(mode, address, word);
|
||||
} else {
|
||||
|
||||
+55
-21
@@ -30,20 +30,25 @@ auto CPU::unload() -> void {
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
ARM7TDMI::irq = irq.ime && (irq.enable & irq.flag);
|
||||
ARM7TDMI::irq = irq.synchronizer[0];
|
||||
|
||||
if(stopped()) {
|
||||
if(!(irq.enable & irq.flag & Interrupt::Keypad)) {
|
||||
Thread::step(16);
|
||||
if(!keypad.conditionMet) {
|
||||
stepIRQ();
|
||||
Thread::step(1);
|
||||
Thread::synchronize();
|
||||
return;
|
||||
}
|
||||
Thread::step(2);
|
||||
Thread::synchronize();
|
||||
context.stopped = false;
|
||||
}
|
||||
|
||||
if(halted()) {
|
||||
if(!(irq.enable & irq.flag)) {
|
||||
return step(16);
|
||||
if(!(irq.enable[0] & irq.flag[0])) {
|
||||
return step(4);
|
||||
}
|
||||
step(2);
|
||||
context.halted = false;
|
||||
}
|
||||
|
||||
@@ -51,14 +56,34 @@ auto CPU::main() -> void {
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::dmaRun() -> void {
|
||||
if(!context.dmaActive && !context.prefetchActive) {
|
||||
context.dmaActive = true;
|
||||
while(dma[0].run() | dma[1].run() | dma[2].run() | dma[3].run());
|
||||
if(context.dmaRan) {
|
||||
idle();
|
||||
context.dmaRan = false;
|
||||
context.dmaRomAccess = false;
|
||||
}
|
||||
context.dmaActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::setInterruptFlag(u32 source) -> void {
|
||||
irq.flag[1] |= source;
|
||||
}
|
||||
|
||||
inline auto CPU::stepIRQ() -> void {
|
||||
irq.synchronizer[0] = irq.synchronizer[1];
|
||||
irq.synchronizer[1] = irq.ime && (irq.enable[0] & irq.flag[0]);
|
||||
irq.enable[0] = irq.enable[1];
|
||||
irq.flag[0] = irq.flag[1];
|
||||
}
|
||||
|
||||
auto CPU::step(u32 clocks) -> void {
|
||||
if(!clocks) return;
|
||||
|
||||
if(!context.dmaActive) {
|
||||
context.dmaActive = true;
|
||||
while(dma[0].run() | dma[1].run() | dma[2].run() | dma[3].run());
|
||||
context.dmaActive = false;
|
||||
}
|
||||
dmaRun();
|
||||
|
||||
dma[0].waiting = max(0, dma[0].waiting - (s32)clocks);
|
||||
dma[1].waiting = max(0, dma[1].waiting - (s32)clocks);
|
||||
@@ -66,24 +91,35 @@ auto CPU::step(u32 clocks) -> void {
|
||||
dma[3].waiting = max(0, dma[3].waiting - (s32)clocks);
|
||||
|
||||
for(auto _ : range(clocks)) {
|
||||
stepIRQ();
|
||||
timer[0].run();
|
||||
timer[1].run();
|
||||
timer[2].run();
|
||||
timer[3].run();
|
||||
timer[0].reloadLatch();
|
||||
timer[1].reloadLatch();
|
||||
timer[2].reloadLatch();
|
||||
timer[3].reloadLatch();
|
||||
if(context.timerLatched) {
|
||||
timer[0].stepLatch();
|
||||
timer[1].stepLatch();
|
||||
timer[2].stepLatch();
|
||||
timer[3].stepLatch();
|
||||
context.timerLatched = 0;
|
||||
}
|
||||
context.clock++;
|
||||
}
|
||||
|
||||
#if defined(PROFILE_PERFORMANCE)
|
||||
//20% speedup by only synchronizing other components every 64 clock cycles
|
||||
Thread::step(clocks);
|
||||
Thread::synchronize(ppu, player);
|
||||
|
||||
//occasionally synchronize with APU in case CPU has not recently interacted with it
|
||||
static u32 counter = 0;
|
||||
counter += clocks;
|
||||
if(counter < 64) return;
|
||||
clocks = counter;
|
||||
counter = 0;
|
||||
#endif
|
||||
|
||||
Thread::step(clocks);
|
||||
Thread::synchronize();
|
||||
if(counter >= 1024) {
|
||||
Thread::synchronize(apu);
|
||||
counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
@@ -104,8 +140,6 @@ auto CPU::power() -> void {
|
||||
prefetch = {};
|
||||
context = {};
|
||||
|
||||
dmabus = {};
|
||||
|
||||
dma[0].source.setBits(27); dma[0].latch.source.setBits(27);
|
||||
dma[0].target.setBits(27); dma[0].latch.target.setBits(27);
|
||||
dma[0].length.setBits(14); dma[0].latch.length.setBits(14);
|
||||
|
||||
+28
-9
@@ -46,6 +46,9 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
auto unload() -> void;
|
||||
|
||||
auto main() -> void;
|
||||
auto dmaRun() -> void;
|
||||
auto setInterruptFlag(u32 source) -> void;
|
||||
auto stepIRQ() -> void;
|
||||
auto step(u32 clocks) -> void override;
|
||||
auto power() -> void;
|
||||
|
||||
@@ -53,11 +56,13 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
auto prefetchSync(n32 address) -> void;
|
||||
auto prefetchStep(u32 clocks) -> void;
|
||||
auto prefetchReset() -> void;
|
||||
auto prefetchRead() -> n16;
|
||||
auto prefetchRead(u32 mode) -> n32;
|
||||
|
||||
//bus.cpp
|
||||
auto sleep() -> void override;
|
||||
template<bool UseDebugger> auto getBus(u32 mode, n32 address) -> n32;
|
||||
auto get(u32 mode, n32 address) -> n32 override;
|
||||
auto getDebugger(u32 mode, n32 address) -> n32 override;
|
||||
auto set(u32 mode, n32 address, n32 word) -> void override;
|
||||
auto _wait(u32 mode, n32 address) -> u32;
|
||||
|
||||
@@ -91,11 +96,6 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n32 mask;
|
||||
};
|
||||
|
||||
//DMA data bus shared between all DMA channels
|
||||
struct DMABus {
|
||||
n32 data;
|
||||
} dmabus;
|
||||
|
||||
struct DMA {
|
||||
//dma.cpp
|
||||
auto run() -> bool;
|
||||
@@ -123,11 +123,14 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
uintVN source;
|
||||
uintVN target;
|
||||
uintVN length;
|
||||
u32 data;
|
||||
} latch;
|
||||
} dma[4];
|
||||
|
||||
struct Timer {
|
||||
//timer.cpp
|
||||
auto stepLatch() -> void;
|
||||
auto reloadLatch() -> void;
|
||||
auto run() -> void;
|
||||
auto step() -> void;
|
||||
|
||||
@@ -142,6 +145,13 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n1 cascade;
|
||||
n1 irq;
|
||||
n1 enable;
|
||||
|
||||
struct Latch {
|
||||
n16 reload;
|
||||
n8 control;
|
||||
n2 reloadFlags;
|
||||
n1 controlFlag;
|
||||
} latch;
|
||||
} timer[4];
|
||||
|
||||
struct Serial {
|
||||
@@ -150,7 +160,8 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n1 transferEnableReceive;
|
||||
n1 transferEnableSend;
|
||||
n1 startBit;
|
||||
n1 transferLength;
|
||||
n4 uartFlags;
|
||||
n2 mode;
|
||||
n1 irqEnable;
|
||||
|
||||
n16 data[4];
|
||||
@@ -164,6 +175,8 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n1 enable;
|
||||
n1 condition;
|
||||
n1 flag[10];
|
||||
|
||||
n1 conditionMet;
|
||||
} keypad;
|
||||
|
||||
struct Joybus {
|
||||
@@ -193,8 +206,9 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
|
||||
struct IRQ {
|
||||
n1 ime;
|
||||
n16 enable;
|
||||
n16 flag;
|
||||
n1 synchronizer[2];
|
||||
n16 enable[2];
|
||||
n16 flag[2];
|
||||
} irq;
|
||||
|
||||
struct Wait {
|
||||
@@ -216,6 +230,7 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
struct {
|
||||
auto empty() const { return addr == load; }
|
||||
auto full() const { return load - addr == 16; }
|
||||
auto size() const { return (load - addr) >> 1; }
|
||||
|
||||
n16 slot[8];
|
||||
n32 addr; //read location of slot buffer
|
||||
@@ -228,7 +243,11 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n1 halted;
|
||||
n1 stopped;
|
||||
n1 booted; //set to true by the GBA BIOS
|
||||
n1 dmaRan;
|
||||
n1 dmaRomAccess;
|
||||
n1 dmaActive;
|
||||
n1 prefetchActive;
|
||||
n1 timerLatched;
|
||||
} context;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ inline auto CPU::DMA::run() -> bool {
|
||||
if(!active || waiting) return false;
|
||||
|
||||
transfer();
|
||||
if(irq) cpu.irq.flag |= CPU::Interrupt::DMA0 << id;
|
||||
if(drq && id == 3) cpu.irq.flag |= CPU::Interrupt::Cartridge;
|
||||
if(irq) cpu.setInterruptFlag(CPU::Interrupt::DMA0 << id);
|
||||
if(drq && id == 3) cpu.setInterruptFlag(CPU::Interrupt::Cartridge);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ auto CPU::DMA::transfer() -> void {
|
||||
u32 mode = size ? Word : Half;
|
||||
mode |= latch.length() == length() ? Nonsequential : Sequential;
|
||||
|
||||
if(mode & Nonsequential) {
|
||||
cpu.idle();
|
||||
if(!cpu.context.dmaRan) {
|
||||
cpu.context.dmaRan = true;
|
||||
cpu.idle();
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ auto CPU::DMA::transfer() -> void {
|
||||
n32 addr = latch.source();
|
||||
if(mode & Word) addr &= ~3;
|
||||
if(mode & Half) addr &= ~1;
|
||||
cpu.dmabus.data = cpu.get(mode, addr);
|
||||
if(mode & Half) cpu.dmabus.data |= cpu.dmabus.data << 16;
|
||||
if(addr & 0x0800'0000) cpu.context.dmaRomAccess = true;
|
||||
latch.data = cpu.get(mode, addr);
|
||||
if(mode & Half) latch.data |= latch.data << 16;
|
||||
}
|
||||
|
||||
if(mode & Nonsequential) {
|
||||
@@ -41,7 +42,8 @@ auto CPU::DMA::transfer() -> void {
|
||||
n32 addr = latch.target();
|
||||
if(mode & Word) addr &= ~3;
|
||||
if(mode & Half) addr &= ~1;
|
||||
cpu.set(mode, addr, cpu.dmabus.data);
|
||||
if(addr & 0x0800'0000) cpu.context.dmaRomAccess = true;
|
||||
cpu.set(mode, addr, latch.data >> (addr & 2) * 8);
|
||||
}
|
||||
|
||||
switch(sourceMode) {
|
||||
|
||||
+40
-40
@@ -55,8 +55,9 @@ auto CPU::readIO(n32 address) -> n8 {
|
||||
| serial.startBit << 7
|
||||
);
|
||||
case 0x0400'0129: return (
|
||||
serial.transferLength << 4
|
||||
| serial.irqEnable << 6
|
||||
serial.uartFlags << 0
|
||||
| serial.mode << 4
|
||||
| serial.irqEnable << 6
|
||||
);
|
||||
|
||||
//SIOMLT_SEND (SIODATA8)
|
||||
@@ -81,10 +82,10 @@ auto CPU::readIO(n32 address) -> n8 {
|
||||
result.bit(7) = !system.controls.downLatch;
|
||||
}
|
||||
if(ppu.rotation->value() == "90°") {
|
||||
result.bit(4) = !system.controls.downLatch;
|
||||
result.bit(5) = !system.controls.upLatch;
|
||||
result.bit(6) = !system.controls.rightLatch;
|
||||
result.bit(7) = !system.controls.leftLatch;
|
||||
result.bit(4) = !system.controls.upLatch;
|
||||
result.bit(5) = !system.controls.downLatch;
|
||||
result.bit(6) = !system.controls.leftLatch;
|
||||
result.bit(7) = !system.controls.rightLatch;
|
||||
}
|
||||
if(ppu.rotation->value() == "180°") {
|
||||
result.bit(4) = !system.controls.leftLatch;
|
||||
@@ -93,10 +94,10 @@ auto CPU::readIO(n32 address) -> n8 {
|
||||
result.bit(7) = !system.controls.upLatch;
|
||||
}
|
||||
if(ppu.rotation->value() == "270°") {
|
||||
result.bit(4) = !system.controls.upLatch;
|
||||
result.bit(5) = !system.controls.downLatch;
|
||||
result.bit(6) = !system.controls.leftLatch;
|
||||
result.bit(7) = !system.controls.rightLatch;
|
||||
result.bit(4) = !system.controls.downLatch;
|
||||
result.bit(5) = !system.controls.upLatch;
|
||||
result.bit(6) = !system.controls.rightLatch;
|
||||
result.bit(7) = !system.controls.leftLatch;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -183,12 +184,12 @@ auto CPU::readIO(n32 address) -> n8 {
|
||||
case 0x0400'015b: return 0;
|
||||
|
||||
//IE
|
||||
case 0x0400'0200: return irq.enable.byte(0);
|
||||
case 0x0400'0201: return irq.enable.byte(1);
|
||||
case 0x0400'0200: return irq.enable[0].byte(0);
|
||||
case 0x0400'0201: return irq.enable[0].byte(1);
|
||||
|
||||
//IF
|
||||
case 0x0400'0202: return irq.flag.byte(0);
|
||||
case 0x0400'0203: return irq.flag.byte(1);
|
||||
case 0x0400'0202: return irq.flag[0].byte(0);
|
||||
case 0x0400'0203: return irq.flag[0].byte(1);
|
||||
|
||||
//WAITCNT
|
||||
case 0x0400'0204: return (
|
||||
@@ -243,7 +244,6 @@ auto CPU::readIO(n32 address) -> n8 {
|
||||
|
||||
}
|
||||
|
||||
if(cpu.context.dmaActive) return cpu.dmabus.data.byte(address & 3);
|
||||
return cpu.pipeline.fetch.instruction.byte(address & 1);
|
||||
}
|
||||
|
||||
@@ -301,24 +301,23 @@ auto CPU::writeIO(n32 address, n8 data) -> void {
|
||||
}
|
||||
|
||||
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
|
||||
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: timer().reload.byte(0) = data; return;
|
||||
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: timer().reload.byte(1) = data; return;
|
||||
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c:
|
||||
timer().latch.reload.byte(0) = data;
|
||||
timer().latch.reloadFlags.bit(0) = 1;
|
||||
context.timerLatched = 1;
|
||||
return;
|
||||
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d:
|
||||
timer().latch.reload.byte(1) = data;
|
||||
timer().latch.reloadFlags.bit(1) = 1;
|
||||
context.timerLatched = 1;
|
||||
return;
|
||||
|
||||
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
|
||||
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: {
|
||||
bool enable = timer().enable;
|
||||
|
||||
timer().frequency = data.bit(0,1);
|
||||
timer().irq = data.bit(6);
|
||||
timer().enable = data.bit(7);
|
||||
|
||||
if(address != 0x0400'0102) timer().cascade = data.bit(2);
|
||||
|
||||
if(!enable && timer().enable) { //0->1 transition
|
||||
timer().pending = true;
|
||||
}
|
||||
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e:
|
||||
timer().latch.control = data;
|
||||
timer().latch.controlFlag = 1;
|
||||
context.timerLatched = 1;
|
||||
return;
|
||||
}
|
||||
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f:
|
||||
return;
|
||||
|
||||
@@ -341,8 +340,9 @@ auto CPU::writeIO(n32 address, n8 data) -> void {
|
||||
serial.startBit = data.bit(7);
|
||||
return;
|
||||
case 0x0400'0129:
|
||||
serial.transferLength = data.bit(4);
|
||||
serial.irqEnable = data.bit(6);
|
||||
serial.uartFlags = data.bit(0,3);
|
||||
serial.mode = data.bit(4,5);
|
||||
serial.irqEnable = data.bit(6);
|
||||
return;
|
||||
|
||||
//SIOMLT_SEND (SIODATA8)
|
||||
@@ -385,10 +385,10 @@ auto CPU::writeIO(n32 address, n8 data) -> void {
|
||||
|
||||
//JOYCNT
|
||||
case 0x0400'0140:
|
||||
joybus.resetSignal = data.bit(0);
|
||||
joybus.receiveComplete = data.bit(1);
|
||||
joybus.sendComplete = data.bit(2);
|
||||
joybus.resetIRQEnable = data.bit(6);
|
||||
joybus.resetSignal &= ~data.bit(0);
|
||||
joybus.receiveComplete &= ~data.bit(1);
|
||||
joybus.sendComplete &= ~data.bit(2);
|
||||
joybus.resetIRQEnable = data.bit(6);
|
||||
return;
|
||||
case 0x0400'0141: return;
|
||||
case 0x0400'0142: return;
|
||||
@@ -417,12 +417,12 @@ auto CPU::writeIO(n32 address, n8 data) -> void {
|
||||
case 0x0400'0159: return;
|
||||
|
||||
//IE
|
||||
case 0x0400'0200: irq.enable.byte(0) = data; return;
|
||||
case 0x0400'0201: irq.enable.byte(1) = data; return;
|
||||
case 0x0400'0200: irq.enable[1].byte(0) = data; return;
|
||||
case 0x0400'0201: irq.enable[1].byte(1) = data; return;
|
||||
|
||||
//IF
|
||||
case 0x0400'0202: irq.flag.byte(0) = irq.flag.byte(0) & ~data; return;
|
||||
case 0x0400'0203: irq.flag.byte(1) = irq.flag.byte(1) & ~data; return;
|
||||
case 0x0400'0202: irq.flag[1].byte(0) = irq.flag[1].byte(0) & ~data; return;
|
||||
case 0x0400'0203: irq.flag[1].byte(1) = irq.flag[1].byte(1) & ~data; return;
|
||||
|
||||
//WAITCNT
|
||||
case 0x0400'0204:
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
auto CPU::Keypad::run() -> void {
|
||||
if(!enable) return;
|
||||
system.controls.poll();
|
||||
|
||||
const bool lookup[] = {
|
||||
system.controls.a->value(),
|
||||
system.controls.b->value(),
|
||||
@@ -15,12 +12,13 @@ auto CPU::Keypad::run() -> void {
|
||||
system.controls.l->value(),
|
||||
};
|
||||
|
||||
bool test = condition; //0 = OR, 1 = AND
|
||||
conditionMet = condition; //0 = OR, 1 = AND
|
||||
for(u32 index : range(10)) {
|
||||
if(!flag[index]) continue;
|
||||
bool input = lookup[index];
|
||||
if(condition == 0) test |= input;
|
||||
if(condition == 1) test &= input;
|
||||
n1 input = lookup[index];
|
||||
if(condition == 0) conditionMet |= input;
|
||||
if(condition == 1) conditionMet &= input;
|
||||
}
|
||||
if(test) cpu.irq.flag |= CPU::Interrupt::Keypad;
|
||||
|
||||
if(conditionMet && enable) cpu.setInterruptFlag(CPU::Interrupt::Keypad);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ auto CPU::prefetchSync(n32 address) -> void {
|
||||
|
||||
auto CPU::prefetchStep(u32 clocks) -> void {
|
||||
step(clocks);
|
||||
if(!wait.prefetch || context.dmaActive || (prefetch.addr == 0)) return;
|
||||
if(!wait.prefetch || context.dmaRomAccess || (prefetch.addr == 0)) return;
|
||||
|
||||
while(!prefetch.full() && clocks--) {
|
||||
if(--prefetch.wait) continue;
|
||||
@@ -28,12 +28,27 @@ auto CPU::prefetchReset() -> void {
|
||||
prefetch.wait = 0;
|
||||
}
|
||||
|
||||
auto CPU::prefetchRead() -> n16 {
|
||||
if(prefetch.empty()) prefetchStep(prefetch.wait);
|
||||
auto CPU::prefetchRead(u32 mode) -> n32 {
|
||||
//run prefetcher for 1 cycle or until buffer contains an instruction, blocking DMA
|
||||
context.prefetchActive = 1;
|
||||
if(prefetch.empty() && (mode & Word)) {
|
||||
prefetchStep(prefetch.wait + _wait(Half | Sequential, prefetch.load + 2));
|
||||
} else if(prefetch.empty() && (mode & Half)) {
|
||||
prefetchStep(prefetch.wait);
|
||||
} else if((prefetch.size() == 1) && (mode & Word)) {
|
||||
prefetchStep(prefetch.wait);
|
||||
} else {
|
||||
prefetchStep(1);
|
||||
}
|
||||
context.prefetchActive = 0;
|
||||
|
||||
if(prefetch.full()) prefetch.wait = _wait(Half | Sequential, prefetch.load);
|
||||
|
||||
n16 half = prefetch.slot[prefetch.addr >> 1 & 7];
|
||||
n32 word = prefetch.slot[prefetch.addr >> 1 & 7];
|
||||
prefetch.addr += 2;
|
||||
return half;
|
||||
if(mode & Word) {
|
||||
word |= prefetch.slot[prefetch.addr >> 1 & 7] << 16;
|
||||
prefetch.addr += 2;
|
||||
}
|
||||
return word;
|
||||
}
|
||||
@@ -5,8 +5,6 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(iwram);
|
||||
s(ewram);
|
||||
|
||||
s(dmabus.data);
|
||||
|
||||
for(auto& dma : this->dma) {
|
||||
s(dma.id);
|
||||
s(dma.active);
|
||||
@@ -31,6 +29,7 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(dma.latch.target.mask);
|
||||
s(dma.latch.length.data);
|
||||
s(dma.latch.length.mask);
|
||||
s(dma.latch.data);
|
||||
}
|
||||
|
||||
for(auto& timer : this->timer) {
|
||||
@@ -42,6 +41,10 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(timer.cascade);
|
||||
s(timer.irq);
|
||||
s(timer.enable);
|
||||
s(timer.latch.reload);
|
||||
s(timer.latch.control);
|
||||
s(timer.latch.reloadFlags);
|
||||
s(timer.latch.controlFlag);
|
||||
}
|
||||
|
||||
s(serial.shiftClockSelect);
|
||||
@@ -49,7 +52,8 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(serial.transferEnableReceive);
|
||||
s(serial.transferEnableSend);
|
||||
s(serial.startBit);
|
||||
s(serial.transferLength);
|
||||
s(serial.uartFlags);
|
||||
s(serial.mode);
|
||||
s(serial.irqEnable);
|
||||
for(auto& value : serial.data) s(value);
|
||||
s(serial.data8);
|
||||
@@ -57,6 +61,7 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(keypad.enable);
|
||||
s(keypad.condition);
|
||||
for(auto& flag : keypad.flag) s(flag);
|
||||
s(keypad.conditionMet);
|
||||
|
||||
s(joybus.sc);
|
||||
s(joybus.sd);
|
||||
@@ -79,8 +84,9 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(joybus.generalFlag);
|
||||
|
||||
s(irq.ime);
|
||||
s(irq.enable);
|
||||
s(irq.flag);
|
||||
for(auto& flag : irq.synchronizer) s(flag);
|
||||
for(auto& flag : irq.enable) s(flag);
|
||||
for(auto& flag : irq.flag) s(flag);
|
||||
|
||||
for(auto& flag : wait.nwait) s(flag);
|
||||
for(auto& flag : wait.swait) s(flag);
|
||||
@@ -103,5 +109,9 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(context.halted);
|
||||
s(context.stopped);
|
||||
s(context.booted);
|
||||
s(context.dmaRan);
|
||||
s(context.dmaRomAccess);
|
||||
s(context.dmaActive);
|
||||
s(context.prefetchActive);
|
||||
s(context.timerLatched);
|
||||
}
|
||||
+34
-6
@@ -1,10 +1,38 @@
|
||||
inline auto CPU::Timer::run() -> void {
|
||||
if(pending) {
|
||||
pending = false;
|
||||
if(enable) period = reload;
|
||||
return;
|
||||
auto CPU::Timer::stepLatch() -> void {
|
||||
if(latch.reloadFlags.bit(0)) {
|
||||
reload.byte(0) = latch.reload.byte(0);
|
||||
latch.reloadFlags.bit(0) = 0;
|
||||
}
|
||||
|
||||
if(latch.reloadFlags.bit(1)) {
|
||||
reload.byte(1) = latch.reload.byte(1);
|
||||
latch.reloadFlags.bit(1) = 0;
|
||||
}
|
||||
|
||||
if(latch.controlFlag) {
|
||||
n1 wasEnabled = enable;
|
||||
|
||||
frequency = latch.control.bit(0,1);
|
||||
irq = latch.control.bit(6);
|
||||
enable = latch.control.bit(7);
|
||||
|
||||
if(id != 0) cascade = latch.control.bit(2);
|
||||
|
||||
if(!wasEnabled && enable) { //0->1 transition
|
||||
pending = true;
|
||||
}
|
||||
latch.controlFlag = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline auto CPU::Timer::reloadLatch() -> void {
|
||||
if(pending) {
|
||||
period = reload;
|
||||
pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
inline auto CPU::Timer::run() -> void {
|
||||
if(!enable || cascade) return;
|
||||
|
||||
static const u32 mask[] = {0, 63, 255, 1023};
|
||||
@@ -15,7 +43,7 @@ auto CPU::Timer::step() -> void {
|
||||
if(++period == 0) {
|
||||
period = reload;
|
||||
|
||||
if(irq) cpu.irq.flag |= CPU::Interrupt::Timer0 << id;
|
||||
if(irq) cpu.setInterruptFlag(CPU::Interrupt::Timer0 << id);
|
||||
|
||||
if(apu.fifo[0].timer == id) cpu.runFIFO(0);
|
||||
if(apu.fifo[1].timer == id) cpu.runFIFO(1);
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace ares::GameBoyAdvance {
|
||||
#include <ares/inline.hpp>
|
||||
auto enumerate() -> vector<string>;
|
||||
auto load(Node::System& node, string name) -> bool;
|
||||
auto option(string name, string value) -> bool;
|
||||
|
||||
enum : u32 { //mode flags for bus read, write:
|
||||
Nonsequential = 1, //N cycle
|
||||
|
||||
@@ -63,7 +63,7 @@ auto Player::frame() -> void {
|
||||
&& !cpu.serial.transferEnableReceive
|
||||
&& cpu.serial.transferEnableSend
|
||||
&& cpu.serial.startBit
|
||||
&& cpu.serial.transferLength
|
||||
&& cpu.serial.mode == 0x1
|
||||
&& cpu.serial.irqEnable
|
||||
) {
|
||||
status.packet = (status.packet + 1) % 17;
|
||||
@@ -86,7 +86,7 @@ auto Player::frame() -> void {
|
||||
case 15: status.send = 0x30000003; break;
|
||||
case 16: status.send = 0x30000003; break;
|
||||
}
|
||||
cpu.irq.flag |= CPU::Interrupt::Serial;
|
||||
cpu.setInterruptFlag(CPU::Interrupt::Serial);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,19 @@ n1 PPU::Background::IO::frame;
|
||||
n5 PPU::Background::IO::mosaicWidth;
|
||||
n5 PPU::Background::IO::mosaicHeight;
|
||||
|
||||
auto PPU::Background::setEnable(n1 status) -> void {
|
||||
io.enable[3] = status;
|
||||
for(auto& flag : io.enable) flag &= status;
|
||||
}
|
||||
|
||||
auto PPU::Background::scanline(u32 y) -> void {
|
||||
memory::move(io.enable, io.enable + 1, sizeof(io.enable) - 1);
|
||||
mosaicOffset = 0;
|
||||
}
|
||||
|
||||
auto PPU::Background::run(u32 x, u32 y) -> void {
|
||||
output = {};
|
||||
if(ppu.blank() || !io.enable) {
|
||||
if(ppu.blank() || !io.enable[0]) {
|
||||
mosaic = {};
|
||||
return;
|
||||
}
|
||||
@@ -37,10 +43,11 @@ auto PPU::Background::run(u32 x, u32 y) -> void {
|
||||
}
|
||||
|
||||
//horizontal mosaic
|
||||
if(!io.mosaic || ++mosaicOffset >= 1 + io.mosaicWidth) {
|
||||
mosaicOffset = 0;
|
||||
if(!io.mosaic || !mosaicOffset) {
|
||||
mosaicOffset = 1 + io.mosaicWidth;
|
||||
mosaic = output;
|
||||
}
|
||||
mosaicOffset--;
|
||||
}
|
||||
|
||||
auto PPU::Background::linear(u32 x, u32 y) -> void {
|
||||
|
||||
@@ -16,4 +16,134 @@ auto PPU::Debugger::load(Node::Object parent) -> void {
|
||||
memory.pram->setWrite([&](u32 address, u8 data) -> void {
|
||||
ppu.pram[address >> 1].byte(address & 1) = data;
|
||||
});
|
||||
|
||||
graphics.tiles4bpp = parent->append<Node::Debugger::Graphics>("4 BPP Tiles");
|
||||
graphics.tiles4bpp->setSize(512, 384);
|
||||
graphics.tiles4bpp->setCapture([&]() -> vector<u32> {
|
||||
vector<u32> output;
|
||||
output.resize(512 * 384);
|
||||
for(u32 tileY : range(48)) {
|
||||
for(u32 tileX : range(64)) {
|
||||
n32 address = tileY * 64 + tileX << 5;
|
||||
for(u32 y : range(8)) {
|
||||
for(u32 x : range(4)) {
|
||||
n8 colors = ppu.vram[address + y * 4 + x];
|
||||
n4 leftPixel = colors & 0xf;
|
||||
n4 rightPixel = colors >> 4;
|
||||
output[(tileY * 8 + y) * 512 + (tileX * 8 + (x << 1))] = leftPixel * 0x111111;
|
||||
output[(tileY * 8 + y) * 512 + (tileX * 8 + (x << 1)) + 1] = rightPixel * 0x111111;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
});
|
||||
|
||||
graphics.tiles8bpp = parent->append<Node::Debugger::Graphics>("8 BPP Tiles");
|
||||
graphics.tiles8bpp->setSize(256, 384);
|
||||
graphics.tiles8bpp->setCapture([&]() -> vector<u32> {
|
||||
vector<u32> output;
|
||||
output.resize(256 * 384);
|
||||
for(u32 tileY : range(48)) {
|
||||
for(u32 tileX : range(32)) {
|
||||
n32 address = tileY * 32 + tileX << 6;
|
||||
for(u32 y : range(8)) {
|
||||
for(u32 x : range(8)) {
|
||||
n9 color = ppu.vram[address + y * 8 + x];
|
||||
if(tileY >= 32) color += 0x100; //bottom 1/3 of tile viewer uses OBJ palettes
|
||||
n15 pixel = ppu.pram[color];
|
||||
n8 r = pixel >> 0 & 31; r = r << 3 | r >> 2;
|
||||
n8 g = pixel >> 5 & 31; g = g << 3 | g >> 2;
|
||||
n8 b = pixel >> 10 & 31; b = b << 3 | b >> 2;
|
||||
n8 a = 255;
|
||||
output[(tileY * 8 + y) * 256 + (tileX * 8 + x)] = a << 24 | r << 16 | g << 8 | b << 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
});
|
||||
|
||||
graphics.mode3 = parent->append<Node::Debugger::Graphics>("Mode 3");
|
||||
graphics.mode3->setSize(240, 160);
|
||||
graphics.mode3->setCapture([&]() -> vector<u32> {
|
||||
vector<u32> output;
|
||||
output.resize(240 * 160);
|
||||
for(u32 y : range(160)) {
|
||||
for(u32 x : range(240)) {
|
||||
n15 pixel = ppu.vram[y * 480 + x * 2 + 1] << 8 | ppu.vram[y * 480 + x * 2] << 0;
|
||||
n8 r = pixel >> 0 & 31; r = r << 3 | r >> 2;
|
||||
n8 g = pixel >> 5 & 31; g = g << 3 | g >> 2;
|
||||
n8 b = pixel >> 10 & 31; b = b << 3 | b >> 2;
|
||||
n8 a = 255;
|
||||
output[y * 240 + x] = a << 24 | r << 16 | g << 8 | b << 0;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
});
|
||||
|
||||
graphics.mode4 = parent->append<Node::Debugger::Graphics>("Mode 4");
|
||||
graphics.mode4->setSize(240, 320);
|
||||
graphics.mode4->setCapture([&]() -> vector<u32> {
|
||||
vector<u32> output;
|
||||
output.resize(240 * 320);
|
||||
for(u32 y : range(160)) {
|
||||
for(u32 x : range(240)) {
|
||||
n8 color = ppu.vram[y * 240 + x];
|
||||
n15 pixel = ppu.pram[color];
|
||||
n8 r = pixel >> 0 & 31; r = r << 3 | r >> 2;
|
||||
n8 g = pixel >> 5 & 31; g = g << 3 | g >> 2;
|
||||
n8 b = pixel >> 10 & 31; b = b << 3 | b >> 2;
|
||||
n8 a = 255;
|
||||
output[y * 240 + x] = a << 24 | r << 16 | g << 8 | b << 0;
|
||||
}
|
||||
}
|
||||
for(u32 y : range(160)) {
|
||||
for(u32 x : range(240)) {
|
||||
n8 color = ppu.vram[0xa000 + y * 240 + x];
|
||||
n15 pixel = ppu.pram[color];
|
||||
n8 r = pixel >> 0 & 31; r = r << 3 | r >> 2;
|
||||
n8 g = pixel >> 5 & 31; g = g << 3 | g >> 2;
|
||||
n8 b = pixel >> 10 & 31; b = b << 3 | b >> 2;
|
||||
n8 a = 255;
|
||||
output[(y + 160) * 240 + x] = a << 24 | r << 16 | g << 8 | b << 0;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
});
|
||||
|
||||
graphics.mode5 = parent->append<Node::Debugger::Graphics>("Mode 5");
|
||||
graphics.mode5->setSize(160, 256);
|
||||
graphics.mode5->setCapture([&]() -> vector<u32> {
|
||||
vector<u32> output;
|
||||
output.resize(160 * 256);
|
||||
for(u32 y : range(256)) {
|
||||
for(u32 x : range(160)) {
|
||||
n15 pixel = ppu.vram[y * 320 + x * 2 + 1] << 8 | ppu.vram[y * 320 + x * 2] << 0;
|
||||
n8 r = pixel >> 0 & 31; r = r << 3 | r >> 2;
|
||||
n8 g = pixel >> 5 & 31; g = g << 3 | g >> 2;
|
||||
n8 b = pixel >> 10 & 31; b = b << 3 | b >> 2;
|
||||
n8 a = 255;
|
||||
output[y * 160 + x] = a << 24 | r << 16 | g << 8 | b << 0;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
auto PPU::Debugger::unload(Node::Object parent) -> void {
|
||||
parent->remove(memory.vram);
|
||||
parent->remove(memory.pram);
|
||||
parent->remove(graphics.tiles4bpp);
|
||||
parent->remove(graphics.tiles8bpp);
|
||||
parent->remove(graphics.mode3);
|
||||
parent->remove(graphics.mode4);
|
||||
parent->remove(graphics.mode5);
|
||||
memory.vram.reset();
|
||||
memory.pram.reset();
|
||||
graphics.tiles4bpp.reset();
|
||||
graphics.tiles8bpp.reset();
|
||||
graphics.mode3.reset();
|
||||
graphics.mode4.reset();
|
||||
graphics.mode5.reset();
|
||||
}
|
||||
+16
-16
@@ -8,17 +8,17 @@ auto PPU::readIO(n32 address) -> n8 {
|
||||
| Background::IO::frame << 4
|
||||
| objects.io.hblank << 5
|
||||
| objects.io.mapping << 6
|
||||
| io.forceBlank << 7
|
||||
| io.forceBlank[3] << 7
|
||||
);
|
||||
case 0x0400'0001: return (
|
||||
bg0.io.enable << 0
|
||||
| bg1.io.enable << 1
|
||||
| bg2.io.enable << 2
|
||||
| bg3.io.enable << 3
|
||||
| objects.io.enable << 4
|
||||
| window0.io.enable << 5
|
||||
| window1.io.enable << 6
|
||||
| window2.io.enable << 7
|
||||
bg0.io.enable[3] << 0
|
||||
| bg1.io.enable[3] << 1
|
||||
| bg2.io.enable[3] << 2
|
||||
| bg3.io.enable[3] << 3
|
||||
| objects.io.enable[3] << 4
|
||||
| window0.io.enable << 5
|
||||
| window1.io.enable << 6
|
||||
| window2.io.enable << 7
|
||||
);
|
||||
|
||||
//GRSWP
|
||||
@@ -99,7 +99,6 @@ auto PPU::readIO(n32 address) -> n8 {
|
||||
|
||||
}
|
||||
|
||||
if(cpu.context.dmaActive) return cpu.dmabus.data.byte(address & 3);
|
||||
return cpu.pipeline.fetch.instruction.byte(address & 1);
|
||||
}
|
||||
|
||||
@@ -113,14 +112,15 @@ auto PPU::writeIO(n32 address, n8 data) -> void {
|
||||
Background::IO::frame = data.bit(4);
|
||||
objects.io.hblank = data.bit(5);
|
||||
objects.io.mapping = data.bit(6);
|
||||
io.forceBlank = data.bit(7);
|
||||
io.forceBlank[3] = data.bit(7);
|
||||
for(auto& flag : io.forceBlank) flag |= data.bit(7);
|
||||
return;
|
||||
case 0x0400'0001:
|
||||
bg0.io.enable = data.bit(0);
|
||||
bg1.io.enable = data.bit(1);
|
||||
bg2.io.enable = data.bit(2);
|
||||
bg3.io.enable = data.bit(3);
|
||||
objects.io.enable = data.bit(4);
|
||||
bg0.setEnable(data.bit(0));
|
||||
bg1.setEnable(data.bit(1));
|
||||
bg2.setEnable(data.bit(2));
|
||||
bg3.setEnable(data.bit(3));
|
||||
objects.setEnable(data.bit(4));
|
||||
window0.io.enable = data.bit(5);
|
||||
window1.io.enable = data.bit(6);
|
||||
window2.io.enable = data.bit(7);
|
||||
|
||||
@@ -6,6 +6,9 @@ auto PPU::readVRAM_BG(u32 mode, n32 address) -> n32 {
|
||||
}
|
||||
|
||||
auto PPU::readVRAM(u32 mode, n32 address) -> n32 {
|
||||
address &= 0x1ffff;
|
||||
if(Background::IO::mode >= 3 && address < 0x1c000 && address >= 0x18000) return 0;
|
||||
|
||||
address &= (address & 0x10000) ? 0x17fff : 0x0ffff;
|
||||
|
||||
if(mode & Word) {
|
||||
@@ -22,6 +25,9 @@ auto PPU::readVRAM(u32 mode, n32 address) -> n32 {
|
||||
}
|
||||
|
||||
auto PPU::writeVRAM(u32 mode, n32 address, n32 word) -> void {
|
||||
address &= 0x1ffff;
|
||||
if(Background::IO::mode >= 3 && address < 0x1c000 && address >= 0x18000) return;
|
||||
|
||||
address &= (address & 0x10000) ? 0x17fff : 0x0ffff;
|
||||
|
||||
if(mode & Word) {
|
||||
|
||||
+20
-7
@@ -1,7 +1,16 @@
|
||||
auto PPU::Objects::setEnable(n1 status) -> void {
|
||||
io.enable[3] = status;
|
||||
for(auto& flag : io.enable) flag &= status;
|
||||
}
|
||||
|
||||
auto PPU::Objects::scanline(u32 y) -> void {
|
||||
memory::move(io.enable, io.enable + 1, sizeof(io.enable) - 1);
|
||||
if(y >= 160) return;
|
||||
|
||||
mosaicOffset = 0;
|
||||
auto& buffer = lineBuffers[y & 1];
|
||||
for(auto& pixel : buffer) pixel = {};
|
||||
if(ppu.blank() || !io.enable) return;
|
||||
if(ppu.io.forceBlank[1] || cpu.stopped() || !io.enable[1]) return; //checks if display conditions will be met next scanline
|
||||
|
||||
for(auto& object : ppu.object) {
|
||||
n8 py = y - object.y;
|
||||
@@ -60,7 +69,7 @@ auto PPU::Objects::scanline(u32 y) -> void {
|
||||
if(color) {
|
||||
if(object.colors == 0) color = object.palette * 16 + color;
|
||||
buffer[bx].enable = true;
|
||||
buffer[bx].color = ppu.pram[256 + color];
|
||||
buffer[bx].color = 256 + color;
|
||||
buffer[bx].translucent = object.mode == 1;
|
||||
}
|
||||
}
|
||||
@@ -74,26 +83,30 @@ auto PPU::Objects::scanline(u32 y) -> void {
|
||||
|
||||
auto PPU::Objects::run(u32 x, u32 y) -> void {
|
||||
output = {};
|
||||
if(ppu.blank() || !io.enable) {
|
||||
if(ppu.blank() || !io.enable[0]) {
|
||||
mosaic = {};
|
||||
return;
|
||||
}
|
||||
|
||||
auto& buffer = lineBuffers[y & 1];
|
||||
output = buffer[x];
|
||||
output.color = ppu.pram[output.color];
|
||||
|
||||
//horizontal mosaic
|
||||
if(mosaicOffset >= 1 + io.mosaicWidth) {
|
||||
mosaicOffset = 0;
|
||||
if(!mosaicOffset) {
|
||||
mosaicOffset = 1 + io.mosaicWidth;
|
||||
mosaic = output;
|
||||
} else if(!mosaic.mosaic || !output.mosaic) {
|
||||
mosaic = output;
|
||||
}
|
||||
mosaicOffset++;
|
||||
mosaicOffset--;
|
||||
}
|
||||
|
||||
auto PPU::Objects::power() -> void {
|
||||
io = {};
|
||||
for(auto& pixel : buffer) pixel = {};
|
||||
for(auto& buffer : lineBuffers) {
|
||||
for(auto& pixel : buffer) pixel = {};
|
||||
}
|
||||
output = {};
|
||||
mosaic = {};
|
||||
mosaicOffset = 0;
|
||||
|
||||
Loaded 100 of 2058 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user