bsnes/higan/processor/m68k/instruction.cpp

1037 lines
31 KiB
C++
Raw Normal View History

auto M68K::trap() -> void {
instructionsExecuted--;
r.pc -= 2;
print("[M68K] unimplemented instruction: ", hex(r.pc, 6L), " = ", hex(opcode, 4L), "\n");
print("[M68K] instructions executed: ", instructionsExecuted, "\n");
while(true) step(5);
}
auto M68K::instruction() -> void {
instructionsExecuted++;
//if(instructionsExecuted >= 2000010) trap();
//if(instructionsExecuted >= 2000000) {
// print(disassembleRegisters(), "\n");
// print(disassemble(r.pc), "\n");
// print("\n");
//}
Update to v100r06 release. byuu says: Up to ten 68K instructions out of somewhere between 61 and 88, depending upon which PDF you look at. Of course, some of them aren't 100% completed yet, either. Lots of craziness with MOVEM, and BCC has a BSR variant that needs stack push/pop functions. This WIP actually took over eight hours to make, going through every possible permutation on how to design the core itself. The updated design now builds both the instruction decoder+dispatcher and the disassembler decoder into the same main loop during M68K's constructor. The special cases are also really psychotic on this processor, and I'm afraid of missing something via the fallthrough cases. So instead, I'm ordering the instructions alphabetically, and including exclusion cases to ignore binding invalid cases. If I end up remapping an existing register, then it'll throw a run-time assertion at program startup. I wanted very much to get rid of struct EA (EffectiveAddress), but it's too difficult to keep track of the internal effective address without it. So I split out the size to a separate parameter, since every opcode only has one size parameter, and otherwise it was getting duplicated in opcodes that take two EAs, and was also awkward with the flag testing. It's a bit more typing, but I feel it's more clean this way. Overall, I'm really worried this is going to be too slow. I don't want to turn the EA stuff into templates, because that will massively bloat out compilation times and object sizes, and will also need a special DSL preprocessor since C++ doesn't have a static for loop. I can definitely optimize a lot of EA's address/read/write functions away once the core is completed, but it's never going to hold a candle to a templatized 68K core. ---- Forgot to include the SA-1 regression fix. I always remember immediately after I upload and archive the WIP. Will try to get that in next time, I guess.
2016-07-16 08:39:44 +00:00
opcode = readPC();
return instructionTable[opcode]();
}
M68K::M68K() {
#define bind(id, name, ...) { \
assert(!instructionTable[id]); \
instructionTable[id] = [=] { return instruction##name(__VA_ARGS__); }; \
disassembleTable[id] = [=] { return disassemble##name(__VA_ARGS__); }; \
}
#define unbind(id) { \
instructionTable[id].reset(); \
disassembleTable[id].reset(); \
}
#define pattern(s) \
std::integral_constant<uint16_t, bit::test(s)>::value
//ADD
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1101 ---0 ++-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode == 7 && reg >= 5) continue;
EffectiveAddress from{mode, reg};
DataRegister with{dreg};
bind(opcode | 0 << 6, ADD<Byte>, from, with);
bind(opcode | 1 << 6, ADD<Word>, from, with);
bind(opcode | 2 << 6, ADD<Long>, from, with);
if(mode == 1) unbind(opcode | 0 << 6);
}
//ADD
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1101 ---1 ++-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
DataRegister from{dreg};
EffectiveAddress with{mode, reg};
bind(opcode | 0 << 6, ADD<Byte>, from, with);
bind(opcode | 1 << 6, ADD<Word>, from, with);
bind(opcode | 2 << 6, ADD<Long>, from, with);
}
//ADDA
for(uint3 areg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1101 ---+ 11-- ----") | areg << 9 | mode << 3 | reg << 0;
if(mode == 7 && reg >= 5) continue;
AddressRegister ar{areg};
EffectiveAddress ea{mode, reg};
bind(opcode | 0 << 8, ADDA<Word>, ar, ea);
bind(opcode | 1 << 8, ADDA<Long>, ar, ea);
}
//ADDI
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 0110 ++-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress modify{mode, reg};
bind(opcode | 0 << 6, ADDI<Byte>, modify);
bind(opcode | 1 << 6, ADDI<Word>, modify);
bind(opcode | 2 << 6, ADDI<Long>, modify);
}
//ADDQ
for(uint3 data : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0101 ---0 ++-- ----") | data << 9 | mode << 3 | reg << 0;
if(mode == 7 && reg >= 2) continue;
uint4 immediate = data ? (uint4)data : (uint4)8;
EffectiveAddress modify{mode, reg};
bind(opcode | 0 << 6, ADDQ<Byte>, immediate, modify);
bind(opcode | 1 << 6, ADDQ<Word>, immediate, modify);
bind(opcode | 2 << 6, ADDQ<Long>, immediate, modify);
if(mode == 1) unbind(opcode | 0 << 6);
}
Update to v100r15 release. byuu wrote: Aforementioned scheduler changes added. Longer explanation of why here: http://hastebin.com/raw/toxedenece Again, we really need to test this as thoroughly as possible for regressions :/ This is a really major change that affects absolutely everything: all emulation cores, all coprocessors, etc. Also added ADDX and SUB to the 68K core, which brings us just barely above 50% of the instruction encoding space completed. [Editor's note: The "aformentioned scheduler changes" were described in a previous forum post: Unfortunately, 64-bits just wasn't enough precision (we were getting misalignments ~230 times a second on 21/24MHz clocks), so I had to move to 128-bit counters. This of course doesn't exist on 32-bit architectures (and probably not on all 64-bit ones either), so for now ... higan's only going to compile on 64-bit machines until we figure something out. Maybe we offer a "lower precision" fallback for machines that lack uint128_t or something. Using the booth algorithm would be way too slow. Anyway, the precision is now 2^-96, which is roughly 10^-29. That puts us far beyond the yoctosecond. Suck it, MAME :P I'm jokingly referring to it as the byuusecond. The other 32-bits of precision allows a 1Hz clock to run up to one full second before all clocks need to be normalized to prevent overflow. I fixed a serious wobbling issue where I was using clock > other.clock for synchronization instead of clock >= other.clock; and also another aliasing issue when two threads share a common frequency, but don't run in lock-step. The latter I don't even fully understand, but I did observe it in testing. nall/serialization.hpp has been extended to support 128-bit integers, but without explicitly naming them (yay generic code), so nall will still compile on 32-bit platforms for all other applications. Speed is basically a wash now. FC's a bit slower, SFC's a bit faster. The "longer explanation" in the linked hastebin is: Okay, so the idea is that we can have an arbitrary number of oscillators. Take the SNES: - CPU/PPU clock = 21477272.727272hz - SMP/DSP clock = 24576000hz - Cartridge DSP1 clock = 8000000hz - Cartridge MSU1 clock = 44100hz - Controller Port 1 modem controller clock = 57600hz - Controller Port 2 barcode battler clock = 115200hz - Expansion Port exercise bike clock = 192000hz Is this a pathological case? Of course it is, but it's possible. The first four do exist in the wild already: see Rockman X2 MSU1 patch. Manifest files with higan let you specify any frequency you want for any component. The old trick higan used was to hold an int64 counter for each thread:thread synchronization, and adjust it like so: - if thread A steps X clocks; then clock += X * threadB.frequency - if clock >= 0; switch to threadB - if thread B steps X clocks; then clock -= X * threadA.frequency - if clock < 0; switch to threadA But there are also system configurations where one processor has to synchronize with more than one other processor. Take the Genesis: - the 68K has to sync with the Z80 and PSG and YM2612 and VDP - the Z80 has to sync with the 68K and PSG and YM2612 - the PSG has to sync with the 68K and Z80 and YM2612 Now I could do this by having an int64 clock value for every association. But these clock values would have to be outside the individual Thread class objects, and we would have to update every relationship's clock value. So the 68K would have to update the Z80, PSG, YM2612 and VDP clocks. That's four expensive 64-bit multiply-adds per clock step event instead of one. As such, we have to account for both possibilities. The only way to do this is with a single time base. We do this like so: - setup: scalar = timeBase / frequency - step: clock += scalar * clocks Once per second, we look at every thread, find the smallest clock value. Then subtract that value from all threads. This prevents the clock counters from overflowing. Unfortunately, these oscillator values are psychotic, unpredictable, and often times repeating fractions. Even with a timeBase of 1,000,000,000,000,000,000 (one attosecond); we get rounding errors every ~16,300 synchronizations. Specifically, this happens with a CPU running at 21477273hz (rounded) and SMP running at 24576000hz. That may be good enough for most emulators, but ... you know how I am. Plus, even at the attosecond level, we're really pushing against the limits of 64-bit integers. Given the reciprocal inverse, a frequency of 1Hz (which does exist in higan!) would have a scalar that consumes 1/18th of the entire range of a uint64 on every single step. Yes, I could raise the frequency, and then step by that amount, I know. But I don't want to have weird gotchas like that in the scheduler core. Until I increase the accuracy to about 100 times greater than a yoctosecond, the rounding errors are too great. And since the only choice above 64-bit values is 128-bit values; we might as well use all the extra headroom. 2^-96 as a timebase gives me the ability to have both a 1Hz and 4GHz clock; and run them both for a full second; before an overflow event would occur. Another hastebin includes demonstration code: #include <libco/libco.h> #include <nall/nall.hpp> using namespace nall; // cothread_t mainThread = nullptr; const uint iterations = 100'000'000; const uint cpuFreq = 21477272.727272 + 0.5; const uint smpFreq = 24576000.000000 + 0.5; const uint cpuStep = 4; const uint smpStep = 5; // struct ThreadA { cothread_t handle = nullptr; uint64 frequency = 0; int64 clock = 0; auto create(auto (*entrypoint)() -> void, uint frequency) { this->handle = co_create(65536, entrypoint); this->frequency = frequency; this->clock = 0; } }; struct CPUA : ThreadA { static auto Enter() -> void; auto main() -> void; CPUA() { create(&CPUA::Enter, cpuFreq); } } cpuA; struct SMPA : ThreadA { static auto Enter() -> void; auto main() -> void; SMPA() { create(&SMPA::Enter, smpFreq); } } smpA; uint8 queueA[iterations]; uint offsetA; cothread_t resumeA = cpuA.handle; auto EnterA() -> void { offsetA = 0; co_switch(resumeA); } auto QueueA(uint value) -> void { queueA[offsetA++] = value; if(offsetA >= iterations) { resumeA = co_active(); co_switch(mainThread); } } auto CPUA::Enter() -> void { while(true) cpuA.main(); } auto CPUA::main() -> void { QueueA(1); smpA.clock -= cpuStep * smpA.frequency; if(smpA.clock < 0) co_switch(smpA.handle); } auto SMPA::Enter() -> void { while(true) smpA.main(); } auto SMPA::main() -> void { QueueA(2); smpA.clock += smpStep * cpuA.frequency; if(smpA.clock >= 0) co_switch(cpuA.handle); } // struct ThreadB { cothread_t handle = nullptr; uint128_t scalar = 0; uint128_t clock = 0; auto print128(uint128_t value) { string s; while(value) { s.append((char)('0' + value % 10)); value /= 10; } s.reverse(); print(s, "\n"); } //femtosecond (10^15) = 16306 //attosecond (10^18) = 688838 //zeptosecond (10^21) = 13712691 //yoctosecond (10^24) = 13712691 (hitting a dead-end on a rounding error causing a wobble) //byuusecond? ( 2^96) = (perfect? 79,228 times more precise than a yoctosecond) auto create(auto (*entrypoint)() -> void, uint128_t frequency) { this->handle = co_create(65536, entrypoint); uint128_t unitOfTime = 1; //for(uint n : range(29)) unitOfTime *= 10; unitOfTime <<= 96; //2^96 time units ... this->scalar = unitOfTime / frequency; print128(this->scalar); this->clock = 0; } auto step(uint128_t clocks) -> void { clock += clocks * scalar; } auto synchronize(ThreadB& thread) -> void { if(clock >= thread.clock) co_switch(thread.handle); } }; struct CPUB : ThreadB { static auto Enter() -> void; auto main() -> void; CPUB() { create(&CPUB::Enter, cpuFreq); } } cpuB; struct SMPB : ThreadB { static auto Enter() -> void; auto main() -> void; SMPB() { create(&SMPB::Enter, smpFreq); clock = 1; } } smpB; auto correct() -> void { auto minimum = min(cpuB.clock, smpB.clock); cpuB.clock -= minimum; smpB.clock -= minimum; } uint8 queueB[iterations]; uint offsetB; cothread_t resumeB = cpuB.handle; auto EnterB() -> void { correct(); offsetB = 0; co_switch(resumeB); } auto QueueB(uint value) -> void { queueB[offsetB++] = value; if(offsetB >= iterations) { resumeB = co_active(); co_switch(mainThread); } } auto CPUB::Enter() -> void { while(true) cpuB.main(); } auto CPUB::main() -> void { QueueB(1); step(cpuStep); synchronize(smpB); } auto SMPB::Enter() -> void { while(true) smpB.main(); } auto SMPB::main() -> void { QueueB(2); step(smpStep); synchronize(cpuB); } // #include <nall/main.hpp> auto nall::main(string_vector) -> void { mainThread = co_active(); uint masterCounter = 0; while(true) { print(masterCounter++, " ...\n"); auto A = clock(); EnterA(); auto B = clock(); print((double)(B - A) / CLOCKS_PER_SEC, "s\n"); auto C = clock(); EnterB(); auto D = clock(); print((double)(D - C) / CLOCKS_PER_SEC, "s\n"); for(uint n : range(iterations)) { if(queueA[n] != queueB[n]) return print("fail at ", n, "\n"); } } } ...and that's everything.]
2016-07-31 02:11:20 +00:00
//ADDX
for(uint3 treg : range(8))
for(uint3 sreg : range(8)) {
auto opcode = pattern("1101 ---1 ++00 ----") | treg << 9 | sreg << 0;
EffectiveAddress dataTarget{DataRegisterDirect, treg};
EffectiveAddress dataSource{DataRegisterDirect, sreg};
bind(opcode | 0 << 6 | 0 << 3, ADDX<Byte>, dataTarget, dataSource);
bind(opcode | 1 << 6 | 0 << 3, ADDX<Word>, dataTarget, dataSource);
bind(opcode | 2 << 6 | 0 << 3, ADDX<Long>, dataTarget, dataSource);
EffectiveAddress addressTarget{AddressRegisterIndirectWithPreDecrement, treg};
EffectiveAddress addressSource{AddressRegisterIndirectWithPreDecrement, sreg};
bind(opcode | 0 << 6 | 1 << 3, ADDX<Byte>, addressTarget, addressSource);
bind(opcode | 1 << 6 | 1 << 3, ADDX<Word>, addressTarget, addressSource);
bind(opcode | 2 << 6 | 1 << 3, ADDX<Long>, addressTarget, addressSource);
}
//AND
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1100 ---0 ++-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 5)) continue;
EffectiveAddress from{mode, reg};
DataRegister with{dreg};
bind(opcode | 0 << 6, AND<Byte>, from, with);
bind(opcode | 1 << 6, AND<Word>, from, with);
bind(opcode | 2 << 6, AND<Long>, from, with);
}
//AND
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1100 ---1 ++-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
DataRegister from{dreg};
EffectiveAddress with{mode, reg};
bind(opcode | 0 << 6, AND<Byte>, from, with);
bind(opcode | 1 << 6, AND<Word>, from, with);
bind(opcode | 2 << 6, AND<Long>, from, with);
}
//ANDI
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 0010 ++-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress ea{mode, reg};
bind(opcode | 0 << 6, ANDI<Byte>, ea);
bind(opcode | 1 << 6, ANDI<Word>, ea);
bind(opcode | 2 << 6, ANDI<Long>, ea);
}
//ANDI_TO_CCR
{ auto opcode = pattern("0000 0010 0011 1100");
bind(opcode, ANDI_TO_CCR);
}
//ANDI_TO_SR
{ auto opcode = pattern("0000 0010 0111 1100");
bind(opcode, ANDI_TO_SR);
}
//ASL (immediate)
for(uint3 count : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---1 ++00 0---") | count << 9 | dreg << 0;
auto shift = count ? (uint4)count : (uint4)8;
DataRegister modify{dreg};
bind(opcode | 0 << 6, ASL<Byte>, shift, modify);
bind(opcode | 1 << 6, ASL<Word>, shift, modify);
bind(opcode | 2 << 6, ASL<Long>, shift, modify);
}
//ASL (register)
for(uint3 sreg : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---1 ++10 0---") | sreg << 9 | dreg << 0;
DataRegister shift{sreg};
DataRegister modify{dreg};
bind(opcode | 0 << 6, ASL<Byte>, shift, modify);
bind(opcode | 1 << 6, ASL<Word>, shift, modify);
bind(opcode | 2 << 6, ASL<Long>, shift, modify);
}
//ASL (effective address)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1110 0001 11-- ----") | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress modify{mode, reg};
bind(opcode, ASL, modify);
}
//ASR (immediate)
for(uint3 count : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---0 ++00 0---") | count << 9 | dreg << 0;
auto shift = count ? (uint4)count : (uint4)8;
DataRegister modify{dreg};
bind(opcode | 0 << 6, ASR<Byte>, shift, modify);
bind(opcode | 1 << 6, ASR<Word>, shift, modify);
bind(opcode | 2 << 6, ASR<Long>, shift, modify);
}
//ASR (register)
for(uint3 sreg : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---0 ++10 0---") | sreg << 9 | dreg << 0;
DataRegister shift{sreg};
DataRegister modify{dreg};
bind(opcode | 0 << 6, ASR<Byte>, shift, modify);
bind(opcode | 1 << 6, ASR<Word>, shift, modify);
bind(opcode | 2 << 6, ASR<Long>, shift, modify);
}
//ASR (effective address)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1110 0000 11-- ----") | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress modify{mode, reg};
bind(opcode, ASR, modify);
}
//BCC
for(uint4 condition : range( 16))
for(uint8 displacement : range(256)) {
auto opcode = pattern("0110 ---- ---- ----") | condition << 8 | displacement << 0;
bind(opcode, BCC, condition, displacement);
}
Update to v100r06 release. byuu says: Up to ten 68K instructions out of somewhere between 61 and 88, depending upon which PDF you look at. Of course, some of them aren't 100% completed yet, either. Lots of craziness with MOVEM, and BCC has a BSR variant that needs stack push/pop functions. This WIP actually took over eight hours to make, going through every possible permutation on how to design the core itself. The updated design now builds both the instruction decoder+dispatcher and the disassembler decoder into the same main loop during M68K's constructor. The special cases are also really psychotic on this processor, and I'm afraid of missing something via the fallthrough cases. So instead, I'm ordering the instructions alphabetically, and including exclusion cases to ignore binding invalid cases. If I end up remapping an existing register, then it'll throw a run-time assertion at program startup. I wanted very much to get rid of struct EA (EffectiveAddress), but it's too difficult to keep track of the internal effective address without it. So I split out the size to a separate parameter, since every opcode only has one size parameter, and otherwise it was getting duplicated in opcodes that take two EAs, and was also awkward with the flag testing. It's a bit more typing, but I feel it's more clean this way. Overall, I'm really worried this is going to be too slow. I don't want to turn the EA stuff into templates, because that will massively bloat out compilation times and object sizes, and will also need a special DSL preprocessor since C++ doesn't have a static for loop. I can definitely optimize a lot of EA's address/read/write functions away once the core is completed, but it's never going to hold a candle to a templatized 68K core. ---- Forgot to include the SA-1 regression fix. I always remember immediately after I upload and archive the WIP. Will try to get that in next time, I guess.
2016-07-16 08:39:44 +00:00
//BCHG (register)
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 ---1 01-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
DataRegister bit{dreg};
EffectiveAddress with{mode, reg};
if(mode == 0) bind(opcode, BCHG<Long>, bit, with);
if(mode != 0) bind(opcode, BCHG<Byte>, bit, with);
}
//BCHG (immediate)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 1000 01-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress with{mode, reg};
if(mode == 0) bind(opcode, BCHG<Long>, with);
if(mode != 0) bind(opcode, BCHG<Byte>, with);
}
//BCLR (register)
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 ---1 10-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
DataRegister bit{dreg};
EffectiveAddress with{mode, reg};
if(mode == 0) bind(opcode, BCLR<Long>, bit, with);
if(mode != 0) bind(opcode, BCLR<Byte>, bit, with);
}
//BCLR (immediate)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 1000 10-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress with{mode, reg};
if(mode == 0) bind(opcode, BCLR<Long>, with);
if(mode != 0) bind(opcode, BCLR<Byte>, with);
}
//BSET (register)
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 ---1 11-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
DataRegister bit{dreg};
EffectiveAddress with{mode, reg};
if(mode == 0) bind(opcode, BSET<Long>, bit, with);
if(mode != 0) bind(opcode, BSET<Byte>, bit, with);
}
//BSET (immediate)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 1000 11-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress with{mode, reg};
if(mode == 0) bind(opcode, BSET<Long>, with);
if(mode != 0) bind(opcode, BSET<Byte>, with);
}
//BTST (register)
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 ---1 00-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 5)) continue;
DataRegister dr{dreg};
EffectiveAddress ea{mode, reg};
if(mode == 0) bind(opcode, BTST<Long>, dr, ea);
if(mode != 0) bind(opcode, BTST<Byte>, dr, ea);
}
//BTST (immediate)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 1000 00-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 4)) continue;
EffectiveAddress ea{mode, reg};
if(mode == 0) bind(opcode, BTST<Long>, ea);
if(mode != 0) bind(opcode, BTST<Byte>, ea);
}
//CLR
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 0010 ++-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress ea{mode, reg};
bind(opcode | 0 << 6, CLR<Byte>, ea);
bind(opcode | 1 << 6, CLR<Word>, ea);
bind(opcode | 2 << 6, CLR<Long>, ea);
}
//CMP
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1011 ---0 ++-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode == 7 && reg >= 5) continue;
DataRegister dr{dreg};
EffectiveAddress ea{mode, reg};
bind(opcode | 0 << 6, CMP<Byte>, dr, ea);
bind(opcode | 1 << 6, CMP<Word>, dr, ea);
bind(opcode | 2 << 6, CMP<Long>, dr, ea);
if(mode == 1) unbind(opcode | 0 << 6);
}
//CMPA
for(uint3 areg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1011 ---+ 11-- ----") | areg << 9 | mode << 3 | reg << 0;
AddressRegister ar{areg};
EffectiveAddress ea{mode, reg};
bind(opcode | 0 << 8, CMPA<Word>, ar, ea);
bind(opcode | 1 << 8, CMPA<Long>, ar, ea);
}
//CMPI
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 1100 ++-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress ea{mode, reg};
bind(opcode | 0 << 6, CMPI<Byte>, ea);
bind(opcode | 1 << 6, CMPI<Word>, ea);
bind(opcode | 2 << 6, CMPI<Long>, ea);
}
//CMPM
for(uint3 xreg : range(8))
for(uint3 yreg : range(8)) {
auto opcode = pattern("1011 ---1 ++00 1---") | xreg << 9 | yreg << 0;
EffectiveAddress ax{AddressRegisterIndirectWithPostIncrement, xreg};
EffectiveAddress ay{AddressRegisterIndirectWithPostIncrement, yreg};
bind(opcode | 0 << 6, CMPM<Byte>, ax, ay);
bind(opcode | 1 << 6, CMPM<Word>, ax, ay);
bind(opcode | 2 << 6, CMPM<Long>, ax, ay);
}
//DBCC
for(uint4 condition : range(16))
for(uint3 dreg : range( 8)) {
auto opcode = pattern("0101 ---- 1100 1---") | condition << 8 | dreg << 0;
DataRegister dr{dreg};
bind(opcode, DBCC, condition, dr);
}
//EOR
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1011 ---1 ++-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
DataRegister from{dreg};
EffectiveAddress with{mode, reg};
bind(opcode | 0 << 6, EOR<Byte>, from, with);
bind(opcode | 1 << 6, EOR<Word>, from, with);
bind(opcode | 2 << 6, EOR<Long>, from, with);
}
//EORI
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 1010 ++-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress with{mode, reg};
bind(opcode | 0 << 6, EORI<Byte>, with);
bind(opcode | 1 << 6, EORI<Word>, with);
bind(opcode | 2 << 6, EORI<Long>, with);
}
//EORI_TO_CCR
{ auto opcode = pattern("0000 1010 0011 1100");
bind(opcode, EORI_TO_CCR);
}
//EORI_TO_SR
{ auto opcode = pattern("0000 1010 0111 1100");
bind(opcode, EORI_TO_SR);
}
//JMP
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 1110 11-- ----") | mode << 3 | reg << 0;
if(mode <= 1 || mode == 3 || mode == 4 || (mode == 7 && reg >= 4)) continue;
EffectiveAddress target{mode, reg};
bind(opcode, JMP, target);
}
//JSR
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 1110 10-- ----") | mode << 3 | reg << 0;
if(mode <= 1 || mode == 3 || mode == 4 || (mode == 7 && reg >= 4)) continue;
EffectiveAddress target{mode, reg};
bind(opcode, JSR, target);
}
//LEA
for(uint3 areg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 ---1 11-- ----") | areg << 9 | mode << 3 | reg << 0;
if(mode <= 1 || mode == 3 || mode == 4 || (mode == 7 && reg >= 4)) continue;
Update to v100r06 release. byuu says: Up to ten 68K instructions out of somewhere between 61 and 88, depending upon which PDF you look at. Of course, some of them aren't 100% completed yet, either. Lots of craziness with MOVEM, and BCC has a BSR variant that needs stack push/pop functions. This WIP actually took over eight hours to make, going through every possible permutation on how to design the core itself. The updated design now builds both the instruction decoder+dispatcher and the disassembler decoder into the same main loop during M68K's constructor. The special cases are also really psychotic on this processor, and I'm afraid of missing something via the fallthrough cases. So instead, I'm ordering the instructions alphabetically, and including exclusion cases to ignore binding invalid cases. If I end up remapping an existing register, then it'll throw a run-time assertion at program startup. I wanted very much to get rid of struct EA (EffectiveAddress), but it's too difficult to keep track of the internal effective address without it. So I split out the size to a separate parameter, since every opcode only has one size parameter, and otherwise it was getting duplicated in opcodes that take two EAs, and was also awkward with the flag testing. It's a bit more typing, but I feel it's more clean this way. Overall, I'm really worried this is going to be too slow. I don't want to turn the EA stuff into templates, because that will massively bloat out compilation times and object sizes, and will also need a special DSL preprocessor since C++ doesn't have a static for loop. I can definitely optimize a lot of EA's address/read/write functions away once the core is completed, but it's never going to hold a candle to a templatized 68K core. ---- Forgot to include the SA-1 regression fix. I always remember immediately after I upload and archive the WIP. Will try to get that in next time, I guess.
2016-07-16 08:39:44 +00:00
AddressRegister ar{areg};
EffectiveAddress ea{mode, reg};
bind(opcode, LEA, ar, ea);
}
//LSL (immediate)
for(uint3 data : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---1 ++00 1---") | data << 9 | dreg << 0;
auto immediate = data ? (uint4)data : (uint4)8;
DataRegister dr{dreg};
bind(opcode | 0 << 6, LSL<Byte>, immediate, dr);
bind(opcode | 1 << 6, LSL<Word>, immediate, dr);
bind(opcode | 2 << 6, LSL<Long>, immediate, dr);
}
//LSL (register)
for(uint3 sreg : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---1 ++10 1---") | sreg << 9 | dreg << 0;
DataRegister sr{sreg};
DataRegister dr{dreg};
bind(opcode | 0 << 6, LSL<Byte>, sr, dr);
bind(opcode | 1 << 6, LSL<Word>, sr, dr);
bind(opcode | 2 << 6, LSL<Long>, sr, dr);
}
//LSL (effective address)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1110 0011 11-- ----") | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress ea{mode, reg};
bind(opcode, LSL, ea);
}
//LSR (immediate)
for(uint3 data : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---0 ++00 1---") | data << 9 | dreg << 0;
auto immediate = data ? (uint4)data : (uint4)8;
DataRegister dr{dreg};
bind(opcode | 0 << 6, LSR<Byte>, immediate, dr);
bind(opcode | 1 << 6, LSR<Word>, immediate, dr);
bind(opcode | 2 << 6, LSR<Long>, immediate, dr);
}
//LSR (register)
for(uint3 count : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---0 ++10 1---") | count << 9 | dreg << 0;
DataRegister shift{count};
DataRegister dr{dreg};
bind(opcode | 0 << 6, LSR<Byte>, shift, dr);
bind(opcode | 1 << 6, LSR<Word>, shift, dr);
bind(opcode | 2 << 6, LSR<Long>, shift, dr);
}
//LSR (effective address)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1110 0010 11-- ----") | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress ea{mode, reg};
bind(opcode, LSR, ea);
}
//MOVE
for(uint3 toReg : range(8))
for(uint3 toMode : range(8))
for(uint3 fromMode : range(8))
for(uint3 fromReg : range(8)) {
auto opcode = pattern("00++ ---- ---- ----") | toReg << 9 | toMode << 6 | fromMode << 3 | fromReg << 0;
if(toMode == 1 || (toMode == 7 && toReg >= 2)) continue;
if(fromMode == 7 && fromReg >= 5) continue;
EffectiveAddress to{toMode, toReg};
EffectiveAddress from{fromMode, fromReg};
bind(opcode | 1 << 12, MOVE<Byte>, to, from);
bind(opcode | 3 << 12, MOVE<Word>, to, from);
bind(opcode | 2 << 12, MOVE<Long>, to, from);
if(fromMode == 1) unbind(opcode | 1 << 12);
}
//MOVEA
for(uint3 areg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("00++ ---0 01-- ----") | areg << 9 | mode << 3 | reg << 0;
if(mode == 7 && reg >= 5) continue;
AddressRegister ar{areg};
EffectiveAddress ea{mode, reg};
bind(opcode | 3 << 12, MOVEA<Word>, ar, ea);
bind(opcode | 2 << 12, MOVEA<Long>, ar, ea);
}
//MOVEM
for(uint1 direction : range(2))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 1-00 1+-- ----") | direction << 10 | mode << 3 | reg << 0;
if(direction == 0 && (mode <= 1 || mode == 3 || (mode == 7 && reg >= 2)));
if(direction == 1 && (mode <= 1 || mode == 4 || (mode == 7 && reg >= 4)));
EffectiveAddress ea{mode, reg};
bind(opcode | 0 << 6, MOVEM<Word>, direction, ea);
bind(opcode | 1 << 6, MOVEM<Long>, direction, ea);
}
//MOVEQ
for(uint3 dreg : range( 8))
for(uint8 immediate : range(256)) {
auto opcode = pattern("0111 ---0 ---- ----") | dreg << 9 | immediate << 0;
DataRegister dr{dreg};
bind(opcode, MOVEQ, dr, immediate);
}
//MOVE_FROM_SR
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 0000 11-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress ea{mode, reg};
bind(opcode, MOVE_FROM_SR, ea);
}
//MOVE_TO_CCR
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 0100 11-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 5)) continue;
EffectiveAddress ea{mode, reg};
bind(opcode, MOVE_TO_CCR, ea);
}
//MOVE_TO_SR
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 0110 11-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 5)) continue;
EffectiveAddress ea{mode, reg};
bind(opcode, MOVE_TO_SR, ea);
}
//MOVE_FROM_USP
for(uint3 areg : range(8)) {
auto opcode = pattern("0100 1110 0110 1---") | areg << 0;
AddressRegister to{areg};
bind(opcode, MOVE_FROM_USP, to);
}
//MOVE_TO_USP
for(uint3 areg : range(8)) {
auto opcode = pattern("0100 1110 0110 0---") | areg << 0;
AddressRegister from{areg};
bind(opcode, MOVE_TO_USP, from);
}
//NEG
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 0100 ++-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress with{mode, reg};
bind(opcode | 0 << 6, NEG<Byte>, with);
bind(opcode | 1 << 6, NEG<Word>, with);
bind(opcode | 2 << 6, NEG<Long>, with);
}
//NEGX
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 0000 ++-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress with{mode, reg};
bind(opcode | 0 << 6, NEGX<Byte>, with);
bind(opcode | 1 << 6, NEGX<Word>, with);
bind(opcode | 2 << 6, NEGX<Long>, with);
}
//NOP
{ auto opcode = pattern("0100 1110 0111 0001");
bind(opcode, NOP);
}
//NOT
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 0110 ++-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress with{mode, reg};
bind(opcode | 0 << 6, NOT<Byte>, with);
bind(opcode | 1 << 6, NOT<Word>, with);
bind(opcode | 2 << 6, NOT<Long>, with);
}
//OR
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1000 ---0 ++-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 5)) continue;
EffectiveAddress from{mode, reg};
DataRegister with{dreg};
bind(opcode | 0 << 6, OR<Byte>, from, with);
bind(opcode | 1 << 6, OR<Word>, from, with);
bind(opcode | 2 << 6, OR<Long>, from, with);
}
//OR
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1000 ---1 ++-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
DataRegister from{dreg};
EffectiveAddress with{mode, reg};
bind(opcode | 0 << 6, OR<Byte>, from, with);
bind(opcode | 1 << 6, OR<Word>, from, with);
bind(opcode | 2 << 6, OR<Long>, from, with);
}
//ORI
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 0000 ++-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress with{mode, reg};
bind(opcode | 0 << 6, ORI<Byte>, with);
bind(opcode | 1 << 6, ORI<Word>, with);
bind(opcode | 2 << 6, ORI<Long>, with);
}
//ORI_TO_CCR
{ auto opcode = pattern("0000 0000 0011 1100");
bind(opcode, ORI_TO_CCR);
}
//ORI_TO_SR
{ auto opcode = pattern("0000 0000 0111 1100");
bind(opcode, ORI_TO_SR);
}
//ROL (immediate)
for(uint3 count : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---1 ++01 1---") | count << 9 | dreg << 0;
auto shift = count ? (uint4)count : (uint4)8;
DataRegister modify{dreg};
bind(opcode | 0 << 6, ROL<Byte>, shift, modify);
bind(opcode | 1 << 6, ROL<Word>, shift, modify);
bind(opcode | 2 << 6, ROL<Long>, shift, modify);
}
//ROL (register)
for(uint3 sreg : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---1 ++11 1---") | sreg << 9 | dreg << 0;
DataRegister shift{sreg};
DataRegister modify{dreg};
bind(opcode | 0 << 6, ROL<Byte>, shift, modify);
bind(opcode | 1 << 6, ROL<Word>, shift, modify);
bind(opcode | 2 << 6, ROL<Long>, shift, modify);
}
//ROL (effective address)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1110 0111 11-- ----") | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress modify{mode, reg};
bind(opcode, ROL, modify);
}
//ROR (immediate)
for(uint3 count : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---0 ++01 1---") | count << 9 | dreg << 0;
auto shift = count ? (uint4)count : (uint4)8;
DataRegister modify{dreg};
bind(opcode | 0 << 6, ROR<Byte>, shift, modify);
bind(opcode | 1 << 6, ROR<Word>, shift, modify);
bind(opcode | 2 << 6, ROR<Long>, shift, modify);
}
//ROR (register)
for(uint3 sreg : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---0 ++11 1---") | sreg << 9 | dreg << 0;
DataRegister shift{sreg};
DataRegister modify{dreg};
bind(opcode | 0 << 6, ROR<Byte>, shift, modify);
bind(opcode | 1 << 6, ROR<Word>, shift, modify);
bind(opcode | 2 << 6, ROR<Long>, shift, modify);
}
//ROR (effective address)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1110 0110 11-- ----") | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress modify{mode, reg};
bind(opcode, ROR, modify);
}
//ROXL (immediate)
for(uint3 count : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---1 ++01 0---") | count << 9 | dreg << 0;
auto shift = count ? (uint4)count : (uint4)8;
DataRegister modify{dreg};
bind(opcode | 0 << 6, ROXL<Byte>, shift, modify);
bind(opcode | 1 << 6, ROXL<Word>, shift, modify);
bind(opcode | 2 << 6, ROXL<Long>, shift, modify);
}
//ROXL (register)
for(uint3 sreg : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---1 ++11 0---") | sreg << 9 | dreg << 0;
DataRegister shift{sreg};
DataRegister modify{dreg};
bind(opcode | 0 << 6, ROXL<Byte>, shift, modify);
bind(opcode | 1 << 6, ROXL<Word>, shift, modify);
bind(opcode | 2 << 6, ROXL<Long>, shift, modify);
}
//ROXL (effective address)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1110 0101 11-- ----") | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress modify{mode, reg};
bind(opcode, ROXL, modify);
}
//ROXR (immediate)
for(uint3 count : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---0 ++01 0---") | count << 9 | dreg << 0;
auto shift = count ? (uint4)count : (uint4)8;
DataRegister modify{dreg};
bind(opcode | 0 << 6, ROXR<Byte>, shift, modify);
bind(opcode | 1 << 6, ROXR<Word>, shift, modify);
bind(opcode | 2 << 6, ROXR<Long>, shift, modify);
}
//ROXR (register)
for(uint3 sreg : range(8))
for(uint3 dreg : range(8)) {
auto opcode = pattern("1110 ---0 ++11 0---") | sreg << 9 | dreg << 0;
DataRegister shift{sreg};
DataRegister modify{dreg};
bind(opcode | 0 << 6, ROXR<Byte>, shift, modify);
bind(opcode | 1 << 6, ROXR<Word>, shift, modify);
bind(opcode | 2 << 6, ROXR<Long>, shift, modify);
}
//ROXR (effective address)
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1110 0100 11-- ----") | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress modify{mode, reg};
bind(opcode, ROXR, modify);
}
//RTE
{ auto opcode = pattern("0100 1110 0111 0011");
bind(opcode, RTE);
}
//RTR
{ auto opcode = pattern("0100 1110 0111 0111");
bind(opcode, RTR);
}
//RTS
{ auto opcode = pattern("0100 1110 0111 0101");
bind(opcode, RTS);
}
//SCC
for(uint4 condition : range(16))
for(uint3 mode : range( 8))
for(uint3 reg : range( 8)) {
auto opcode = pattern("0101 ---- 11-- ----") | condition << 8 | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress to{mode, reg};
bind(opcode, SCC, condition, to);
}
Update to v100r15 release. byuu wrote: Aforementioned scheduler changes added. Longer explanation of why here: http://hastebin.com/raw/toxedenece Again, we really need to test this as thoroughly as possible for regressions :/ This is a really major change that affects absolutely everything: all emulation cores, all coprocessors, etc. Also added ADDX and SUB to the 68K core, which brings us just barely above 50% of the instruction encoding space completed. [Editor's note: The "aformentioned scheduler changes" were described in a previous forum post: Unfortunately, 64-bits just wasn't enough precision (we were getting misalignments ~230 times a second on 21/24MHz clocks), so I had to move to 128-bit counters. This of course doesn't exist on 32-bit architectures (and probably not on all 64-bit ones either), so for now ... higan's only going to compile on 64-bit machines until we figure something out. Maybe we offer a "lower precision" fallback for machines that lack uint128_t or something. Using the booth algorithm would be way too slow. Anyway, the precision is now 2^-96, which is roughly 10^-29. That puts us far beyond the yoctosecond. Suck it, MAME :P I'm jokingly referring to it as the byuusecond. The other 32-bits of precision allows a 1Hz clock to run up to one full second before all clocks need to be normalized to prevent overflow. I fixed a serious wobbling issue where I was using clock > other.clock for synchronization instead of clock >= other.clock; and also another aliasing issue when two threads share a common frequency, but don't run in lock-step. The latter I don't even fully understand, but I did observe it in testing. nall/serialization.hpp has been extended to support 128-bit integers, but without explicitly naming them (yay generic code), so nall will still compile on 32-bit platforms for all other applications. Speed is basically a wash now. FC's a bit slower, SFC's a bit faster. The "longer explanation" in the linked hastebin is: Okay, so the idea is that we can have an arbitrary number of oscillators. Take the SNES: - CPU/PPU clock = 21477272.727272hz - SMP/DSP clock = 24576000hz - Cartridge DSP1 clock = 8000000hz - Cartridge MSU1 clock = 44100hz - Controller Port 1 modem controller clock = 57600hz - Controller Port 2 barcode battler clock = 115200hz - Expansion Port exercise bike clock = 192000hz Is this a pathological case? Of course it is, but it's possible. The first four do exist in the wild already: see Rockman X2 MSU1 patch. Manifest files with higan let you specify any frequency you want for any component. The old trick higan used was to hold an int64 counter for each thread:thread synchronization, and adjust it like so: - if thread A steps X clocks; then clock += X * threadB.frequency - if clock >= 0; switch to threadB - if thread B steps X clocks; then clock -= X * threadA.frequency - if clock < 0; switch to threadA But there are also system configurations where one processor has to synchronize with more than one other processor. Take the Genesis: - the 68K has to sync with the Z80 and PSG and YM2612 and VDP - the Z80 has to sync with the 68K and PSG and YM2612 - the PSG has to sync with the 68K and Z80 and YM2612 Now I could do this by having an int64 clock value for every association. But these clock values would have to be outside the individual Thread class objects, and we would have to update every relationship's clock value. So the 68K would have to update the Z80, PSG, YM2612 and VDP clocks. That's four expensive 64-bit multiply-adds per clock step event instead of one. As such, we have to account for both possibilities. The only way to do this is with a single time base. We do this like so: - setup: scalar = timeBase / frequency - step: clock += scalar * clocks Once per second, we look at every thread, find the smallest clock value. Then subtract that value from all threads. This prevents the clock counters from overflowing. Unfortunately, these oscillator values are psychotic, unpredictable, and often times repeating fractions. Even with a timeBase of 1,000,000,000,000,000,000 (one attosecond); we get rounding errors every ~16,300 synchronizations. Specifically, this happens with a CPU running at 21477273hz (rounded) and SMP running at 24576000hz. That may be good enough for most emulators, but ... you know how I am. Plus, even at the attosecond level, we're really pushing against the limits of 64-bit integers. Given the reciprocal inverse, a frequency of 1Hz (which does exist in higan!) would have a scalar that consumes 1/18th of the entire range of a uint64 on every single step. Yes, I could raise the frequency, and then step by that amount, I know. But I don't want to have weird gotchas like that in the scheduler core. Until I increase the accuracy to about 100 times greater than a yoctosecond, the rounding errors are too great. And since the only choice above 64-bit values is 128-bit values; we might as well use all the extra headroom. 2^-96 as a timebase gives me the ability to have both a 1Hz and 4GHz clock; and run them both for a full second; before an overflow event would occur. Another hastebin includes demonstration code: #include <libco/libco.h> #include <nall/nall.hpp> using namespace nall; // cothread_t mainThread = nullptr; const uint iterations = 100'000'000; const uint cpuFreq = 21477272.727272 + 0.5; const uint smpFreq = 24576000.000000 + 0.5; const uint cpuStep = 4; const uint smpStep = 5; // struct ThreadA { cothread_t handle = nullptr; uint64 frequency = 0; int64 clock = 0; auto create(auto (*entrypoint)() -> void, uint frequency) { this->handle = co_create(65536, entrypoint); this->frequency = frequency; this->clock = 0; } }; struct CPUA : ThreadA { static auto Enter() -> void; auto main() -> void; CPUA() { create(&CPUA::Enter, cpuFreq); } } cpuA; struct SMPA : ThreadA { static auto Enter() -> void; auto main() -> void; SMPA() { create(&SMPA::Enter, smpFreq); } } smpA; uint8 queueA[iterations]; uint offsetA; cothread_t resumeA = cpuA.handle; auto EnterA() -> void { offsetA = 0; co_switch(resumeA); } auto QueueA(uint value) -> void { queueA[offsetA++] = value; if(offsetA >= iterations) { resumeA = co_active(); co_switch(mainThread); } } auto CPUA::Enter() -> void { while(true) cpuA.main(); } auto CPUA::main() -> void { QueueA(1); smpA.clock -= cpuStep * smpA.frequency; if(smpA.clock < 0) co_switch(smpA.handle); } auto SMPA::Enter() -> void { while(true) smpA.main(); } auto SMPA::main() -> void { QueueA(2); smpA.clock += smpStep * cpuA.frequency; if(smpA.clock >= 0) co_switch(cpuA.handle); } // struct ThreadB { cothread_t handle = nullptr; uint128_t scalar = 0; uint128_t clock = 0; auto print128(uint128_t value) { string s; while(value) { s.append((char)('0' + value % 10)); value /= 10; } s.reverse(); print(s, "\n"); } //femtosecond (10^15) = 16306 //attosecond (10^18) = 688838 //zeptosecond (10^21) = 13712691 //yoctosecond (10^24) = 13712691 (hitting a dead-end on a rounding error causing a wobble) //byuusecond? ( 2^96) = (perfect? 79,228 times more precise than a yoctosecond) auto create(auto (*entrypoint)() -> void, uint128_t frequency) { this->handle = co_create(65536, entrypoint); uint128_t unitOfTime = 1; //for(uint n : range(29)) unitOfTime *= 10; unitOfTime <<= 96; //2^96 time units ... this->scalar = unitOfTime / frequency; print128(this->scalar); this->clock = 0; } auto step(uint128_t clocks) -> void { clock += clocks * scalar; } auto synchronize(ThreadB& thread) -> void { if(clock >= thread.clock) co_switch(thread.handle); } }; struct CPUB : ThreadB { static auto Enter() -> void; auto main() -> void; CPUB() { create(&CPUB::Enter, cpuFreq); } } cpuB; struct SMPB : ThreadB { static auto Enter() -> void; auto main() -> void; SMPB() { create(&SMPB::Enter, smpFreq); clock = 1; } } smpB; auto correct() -> void { auto minimum = min(cpuB.clock, smpB.clock); cpuB.clock -= minimum; smpB.clock -= minimum; } uint8 queueB[iterations]; uint offsetB; cothread_t resumeB = cpuB.handle; auto EnterB() -> void { correct(); offsetB = 0; co_switch(resumeB); } auto QueueB(uint value) -> void { queueB[offsetB++] = value; if(offsetB >= iterations) { resumeB = co_active(); co_switch(mainThread); } } auto CPUB::Enter() -> void { while(true) cpuB.main(); } auto CPUB::main() -> void { QueueB(1); step(cpuStep); synchronize(smpB); } auto SMPB::Enter() -> void { while(true) smpB.main(); } auto SMPB::main() -> void { QueueB(2); step(smpStep); synchronize(cpuB); } // #include <nall/main.hpp> auto nall::main(string_vector) -> void { mainThread = co_active(); uint masterCounter = 0; while(true) { print(masterCounter++, " ...\n"); auto A = clock(); EnterA(); auto B = clock(); print((double)(B - A) / CLOCKS_PER_SEC, "s\n"); auto C = clock(); EnterB(); auto D = clock(); print((double)(D - C) / CLOCKS_PER_SEC, "s\n"); for(uint n : range(iterations)) { if(queueA[n] != queueB[n]) return print("fail at ", n, "\n"); } } } ...and that's everything.]
2016-07-31 02:11:20 +00:00
//SUB
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1001 ---0 ++-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode == 7 && reg >= 5) continue;
EffectiveAddress source{mode, reg};
DataRegister target{dreg};
bind(opcode | 0 << 6, SUB<Byte>, source, target);
bind(opcode | 1 << 6, SUB<Word>, source, target);
bind(opcode | 2 << 6, SUB<Long>, source, target);
if(mode == 1) unbind(opcode | 0 << 6);
}
//SUB
for(uint3 dreg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1001 ---1 ++-- ----") | dreg << 9 | mode << 3 | reg << 0;
if(mode <= 1 || (mode == 7 && reg >= 2)) continue;
DataRegister source{dreg};
EffectiveAddress target{mode, reg};
bind(opcode | 0 << 6, SUB<Byte>, source, target);
bind(opcode | 1 << 6, SUB<Word>, source, target);
bind(opcode | 2 << 6, SUB<Long>, source, target);
}
//SUBA
for(uint3 areg : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1001 ---+ 11-- ----") | areg << 9 | mode << 3 | reg << 0;
if(mode == 7 && reg >= 5) continue;
AddressRegister to{areg};
EffectiveAddress from{mode, reg};
bind(opcode | 0 << 8, SUBA<Word>, to, from);
bind(opcode | 1 << 8, SUBA<Long>, to, from);
}
//SUBI
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0000 0100 ++-- ----") | mode << 3 | reg << 0;
if(mode == 1 || (mode == 7 && reg >= 2)) continue;
EffectiveAddress with{mode, reg};
bind(opcode | 0 << 6, SUBI<Byte>, with);
bind(opcode | 1 << 6, SUBI<Word>, with);
bind(opcode | 2 << 6, SUBI<Long>, with);
}
//SUBQ
for(uint3 data : range(8))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0101 ---1 ++-- ----") | data << 9 | mode << 3 | reg << 0;
if(mode == 7 && reg >= 2) continue;
auto immediate = data ? (uint4)data : (uint4)8;
EffectiveAddress ea{mode, reg};
bind(opcode | 0 << 6, SUBQ<Byte>, immediate, ea);
bind(opcode | 1 << 6, SUBQ<Word>, immediate, ea);
bind(opcode | 2 << 6, SUBQ<Long>, immediate, ea);
if(mode == 1) unbind(opcode | 0 << 6);
}
//SUBX
for(uint3 treg : range(8))
for(uint3 sreg : range(8)) {
auto opcode = pattern("1001 ---1 ++00 ----") | treg << 9 | sreg << 0;
EffectiveAddress dataTarget{DataRegisterDirect, treg};
EffectiveAddress dataSource{DataRegisterDirect, sreg};
bind(opcode | 0 << 6 | 0 << 3, SUBX<Byte>, dataTarget, dataSource);
bind(opcode | 1 << 6 | 0 << 3, SUBX<Word>, dataTarget, dataSource);
bind(opcode | 2 << 6 | 0 << 3, SUBX<Long>, dataTarget, dataSource);
EffectiveAddress addressTarget{AddressRegisterIndirectWithPreDecrement, treg};
EffectiveAddress addressSource{AddressRegisterIndirectWithPreDecrement, sreg};
bind(opcode | 0 << 6 | 1 << 3, SUBX<Byte>, addressTarget, addressSource);
bind(opcode | 1 << 6 | 1 << 3, SUBX<Word>, addressTarget, addressSource);
bind(opcode | 2 << 6 | 1 << 3, SUBX<Long>, addressTarget, addressSource);
}
//TST
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("0100 1010 ++-- ----") | mode << 3 | reg << 0;
if(mode == 7 && reg >= 2) continue;
EffectiveAddress ea{mode, reg};
bind(opcode | 0 << 6, TST<Byte>, ea);
bind(opcode | 1 << 6, TST<Word>, ea);
bind(opcode | 2 << 6, TST<Long>, ea);
if(mode == 1) unbind(opcode | 0 << 6);
Update to v100r06 release. byuu says: Up to ten 68K instructions out of somewhere between 61 and 88, depending upon which PDF you look at. Of course, some of them aren't 100% completed yet, either. Lots of craziness with MOVEM, and BCC has a BSR variant that needs stack push/pop functions. This WIP actually took over eight hours to make, going through every possible permutation on how to design the core itself. The updated design now builds both the instruction decoder+dispatcher and the disassembler decoder into the same main loop during M68K's constructor. The special cases are also really psychotic on this processor, and I'm afraid of missing something via the fallthrough cases. So instead, I'm ordering the instructions alphabetically, and including exclusion cases to ignore binding invalid cases. If I end up remapping an existing register, then it'll throw a run-time assertion at program startup. I wanted very much to get rid of struct EA (EffectiveAddress), but it's too difficult to keep track of the internal effective address without it. So I split out the size to a separate parameter, since every opcode only has one size parameter, and otherwise it was getting duplicated in opcodes that take two EAs, and was also awkward with the flag testing. It's a bit more typing, but I feel it's more clean this way. Overall, I'm really worried this is going to be too slow. I don't want to turn the EA stuff into templates, because that will massively bloat out compilation times and object sizes, and will also need a special DSL preprocessor since C++ doesn't have a static for loop. I can definitely optimize a lot of EA's address/read/write functions away once the core is completed, but it's never going to hold a candle to a templatized 68K core. ---- Forgot to include the SA-1 regression fix. I always remember immediately after I upload and archive the WIP. Will try to get that in next time, I guess.
2016-07-16 08:39:44 +00:00
}
#undef bind
#undef unbind
#undef pattern
uint unimplemented = 0;
Update to v100r06 release. byuu says: Up to ten 68K instructions out of somewhere between 61 and 88, depending upon which PDF you look at. Of course, some of them aren't 100% completed yet, either. Lots of craziness with MOVEM, and BCC has a BSR variant that needs stack push/pop functions. This WIP actually took over eight hours to make, going through every possible permutation on how to design the core itself. The updated design now builds both the instruction decoder+dispatcher and the disassembler decoder into the same main loop during M68K's constructor. The special cases are also really psychotic on this processor, and I'm afraid of missing something via the fallthrough cases. So instead, I'm ordering the instructions alphabetically, and including exclusion cases to ignore binding invalid cases. If I end up remapping an existing register, then it'll throw a run-time assertion at program startup. I wanted very much to get rid of struct EA (EffectiveAddress), but it's too difficult to keep track of the internal effective address without it. So I split out the size to a separate parameter, since every opcode only has one size parameter, and otherwise it was getting duplicated in opcodes that take two EAs, and was also awkward with the flag testing. It's a bit more typing, but I feel it's more clean this way. Overall, I'm really worried this is going to be too slow. I don't want to turn the EA stuff into templates, because that will massively bloat out compilation times and object sizes, and will also need a special DSL preprocessor since C++ doesn't have a static for loop. I can definitely optimize a lot of EA's address/read/write functions away once the core is completed, but it's never going to hold a candle to a templatized 68K core. ---- Forgot to include the SA-1 regression fix. I always remember immediately after I upload and archive the WIP. Will try to get that in next time, I guess.
2016-07-16 08:39:44 +00:00
for(uint16 opcode : range(65536)) {
if(instructionTable[opcode]) continue;
instructionTable[opcode] = [=] { trap(); };
disassembleTable[opcode] = [=] { return string{"???"}; };
unimplemented++;
}
print("[M68K] unimplemented opcodes: ", unimplemented, "\n");
}