Compare commits

..

17 Commits
v100 ... v101

Author SHA1 Message Date
Tim Allen
e39987a3e3 Update to v101 release.
byuu says (in the public announcement):

Not a large changelog this time, sorry. This release is mostly to fix
the SA-1 issue, and to get some real-world testing of the new scheduler
model. Most of the work in the past month has gone into writing a 68000
CPU core; yet it's still only about half-way finished.

Changelog (since the previous release):

  - fixed SNES SA-1 IRQ regression (fixes Super Mario RPG level-up
    screen)
  - new scheduler for all emulator cores (precision of 2^-127)
  - icarus database adds nine new SNES games
  - added Input/Frequency to settings file (allows simulation of
    latency)

byuu says (in the WIP forum):

Changelog:

  - in 32-bit mode, Thread uses uint64\_t with 2^-63 time units (10^-7
    precision in the worst case)
      - nearly ten times the precision of an attosecond
  - in 64-bit mode, Thread uses uint128\_t with 2^-127 time units
    (10^-26 precision in the worst case)
      - far more accurate than yoctoseconds; almost closing in on planck
        time

Note: a quartz crystal is accurate to 10^-4 or 10^-5. A cesium fountain
atomic clock is accurate to 10^-15. So ... yeah. 2^-63 was perfectly
fine; but there was no speed penalty whatsoever for using uint128\_t in
64-bit mode, so why not?
2016-08-08 20:04:15 +10:00
Tim Allen
f5e5bf1772 Update to v100r16 release.
byuu says:

(Windows users may need to include <sys/time.h> at the top of
nall/chrono.hpp, not sure.)

Unchangelog:
- forgot to add the Scheduler clock=0 fix because I have the memory of
  a goldfish

Changelog:
- new icarus database with nine additional games
- hiro(GTK,Qt) won't constantly write its settings.bml file to disk
  anymore
- added latency simulator for fun (settings.bml => Input/Latency in
  milliseconds)

So the last one ... I wanted to test out nall::chrono, and I was also
thinking that by polling every emulated frame, it's pretty wasteful when
you are using Fast Forward and hitting 200+fps. As I've said before,
calls to ruby::input::poll are not cheap.

So to get around this, I added a limiter so that if you called the
hardware poll function within N milliseconds, it'll return without
doing any actual work. And indeed, that increases my framerate of Zelda
3 uncapped from 133fps to 142fps. Yay. But it's not a "real" speedup,
as it only helps you when you exceed 100% speed (theoretically, you'd
need to crack 300% speed since the game itself will poll at 16ms at 100%
speed, but yet it sped up Zelda 3, so who am I to complain?)

I threw the latency value into the settings file. It should be 16,
but I set it to 5 since that was the lowest before it started negatively
impacting uncapped speeds. You're wasting your time and CPU cycles setting
it lower than 5, but if people like placebo effects it might work. Maybe
I should let it be a signed integer so people can set it to -16 and think
it's actually faster :P (I'm only joking. I took out the 96000hz audio
placebo effect as well. Not really into psychological tricks anymore.)

But yeah seriously, I didn't do this to start this discussion again for
the billionth time. Please don't go there. And please don't tell me this
WIP has higher/lower latency than before. I don't want to hear it.

The only reason I bring it up is for the fun part that is worth
discussing: put up or shut up time on how sensitive you are to
latency! You can set the value above 5 to see how games feel.

I personally can't really tell a difference until about 50. And I can't
be 100% confident it's worse until about 75. But ... when I set it to
150, games become "extra difficult" ... the higher it goes, the worse
it gets :D

For this WIP, I've left no upper limit cap. I'll probably set a cap of
something like 500ms or 1000ms for the official release. Need to balance
user error/trolling with enjoyability. I'll think about it.

[...]

Now, what I worry about is stupid people seeing it and thinking it's an
"added latency" setting, as if anyone would intentionally make things
worse by default. This is a limiter. So if 5ms have passed since the
game last polled, and that will be the case 99.9% of the time in games,
the next poll will happen just in time, immediately when the game polls
the inputs. Thus, a value below 1/<framerate>ms is not only pointless,
if you go too low it will ruin your fast forward max speeds.

I did say I didn't want to resort to placebo tricks, but I also don't
want to spark up public discussion on this again either. So it might
be best to default Input/Latency to 0ms, and internally have a max(5,
latency) wrapper around the value.
2016-08-03 22:32:40 +10:00
Tim Allen
c50723ef61 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 12:11:20 +10:00
Tim Allen
ca277cd5e8 Update to v100r14 release.
byuu says:

(Windows: compile with -fpermissive to silence an annoying error. I'll
fix it in the next WIP.)

I completely replaced the time management system in higan and overhauled
the scheduler.

Before, processor threads would have "int64 clock"; and there would
be a 1:1 relationship between two threads. When thread A ran for X
cycles, it'd subtract X * B.Frequency from clock; and when thread B ran
for Y cycles, it'd add Y * A.Frequency from clock. This worked well
and allowed perfect precision; but it doesn't work when you have more
complicated relationships: eg the 68K can sync to the Z80 and PSG; the
Z80 to the 68K and PSG; so the PSG needs two counters.

The new system instead uses a "uint64 clock" variable that represents
time in attoseconds. Every time the scheduler exits, it subtracts
the smallest clock count from all threads, to prevent an overflow
scenario. The only real downside is that rounding errors mean that
roughly every 20 minutes, we have a rounding error of one clock cycle
(one 20,000,000th of a second.) However, this only applies to systems
with multiple oscillators, like the SNES. And when you're in that
situation ... there's no such thing as a perfect oscillator anyway. A
real SNES will be thousands of times less out of spec than 1hz per 20
minutes.

The advantages are pretty immense. First, we obviously can now support
more complex relationships between threads. Second, we can build a
much more abstracted scheduler. All of libco is now abstracted away
completely, which may permit a state-machine / coroutine version of
Thread in the future. We've basically gone from this:

    auto SMP::step(uint clocks) -> void {
      clock += clocks * (uint64)cpu.frequency;
      dsp.clock -= clocks;
      if(dsp.clock < 0 && !scheduler.synchronizing()) co_switch(dsp.thread);
      if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
    }

To this:

    auto SMP::step(uint clocks) -> void {
      Thread::step(clocks);
      synchronize(dsp);
      synchronize(cpu);
    }

As you can see, we don't have to do multiple clock adjustments anymore.
This is a huge win for the SNES CPU that had to update the SMP, DSP, all
peripherals and all coprocessors. Likewise, we don't have to synchronize
all coprocessors when one runs, now we can just synchronize the active
one to the CPU.

Third, when changing the frequencies of threads (think SGB speed setting
modes, GBC double-speed mode, etc), it no longer causes the "int64
clock" value to be erroneous.

Fourth, this results in a fairly decent speedup, mostly across the
board. Aside from the GBA being mostly a wash (for unknown reasons),
it's about an 8% - 12% speedup in every other emulation core.

Now, all of this said ... this was an unbelievably massive change, so
... you know what that means >_> If anyone can help test all types of
SNES coprocessors, and some other system games, it'd be appreciated.

----

Lastly, we have a bitchin' new about screen. It unfortunately adds
~200KiB onto the binary size, because the PNG->C++ header file
transformation doesn't compress very well, and I want to keep the
original resource files in with the higan archive. I might try some
things to work around this file size increase in the future, but for now
... yeah, slightly larger archive sizes, sorry.

The logo's a bit busted on Windows (the Label control's background
transparency and alignment settings aren't working), but works well on
GTK. I'll have to fix Windows before the next official release. For now,
look on my Twitter feed if you want to see what it's supposed to look
like.

----

EDIT: forgot about ICD2::Enter. It's doing some weird inverse
run-to-save thing that I need to implement support for somehow. So, save
states on the SGB core probably won't work with this WIP.
2016-07-30 13:56:12 +10:00
Tim Allen
306cac2b54 Update to v100r13 release.
byuu says:

Changelog: M68K improvements, new instructions added.
2016-07-26 20:46:43 +10:00
Tim Allen
f230d144b5 Update to v100r12 release.
byuu says:

All of the above fixes, plus I added all 24 variations on the shift
opcodes, plus SUBQ, plus fixes to the BCC instruction.

I can now run 851,767 instructions into Sonic the Hedgehog before hitting
an unimplemented instruction (SUB).

The 68K core is probably only ~35% complete, and yet it's already within
4KiB of being the largest CPU core, code size wise, in all of higan. Fuck
this chip.
2016-07-25 23:15:54 +10:00
Tim Allen
7ccfbe0206 Update to v100r11 release.
byuu says:

I split the Register class and read/write handlers into DataRegister and
AddressRegister, given that they have different behaviors on byte/word
accesses (data tends to preserve the upper bits; address tends to
sign-extend things.)

I expanded EA to EffectiveAddress. No sense in abbreviating things
to death.

I've now implemented 26 instructions. But the new ones are just all the
stupid from/to ccr/sr instructions.

Ryphecha confirmed that you can't set the undefined bits, so I don't
think the BitField concept is appropriate for the CCR/SR. Instead, I'm
just storing direct flags and have (read,write)(CCR,SR) instead. This
isn't like the 65816 where you have subroutines that push and pop the
flag register. It's much more common to access individual flags. Doesn't
match the consistency angle of the other CPU cores, but ... I think this
is the right thing to for the 68K specifically.
2016-07-23 12:32:35 +10:00
Tim Allen
4b897ba791 Update to v100r10 release.
byuu says:

Redesigned the handling of reading/writing registers to be about eight
times faster than the old system. More work may be needed ... it seems
data registers tend to preserve their upper bits upon assignment; whereas
address registers tend to sign-extend values into them. It may make
sense to have DataRegister and AddressRegister classes with separate
read/write handlers. I'd have to hold two Register objects inside the
EffectiveAddress (EA) class if we do that.

Implemented 19 opcodes now (out of somewhere between 60 and 90.) That gets
the first ~530,000 instructions in Sonic the Hedgehog running (though
probably wrong. But we can run a lot thanks to large initialization
loops.)

If I force the core to loop back to the reset vector on an invalid opcode,
I'm getting about 1500fps with a dumb 320x240 blit 60 times a second and
just the 68K running alone (no Z80, PSG, VDP, YM2612.) I don't know if
that's good or not. I guess we'll find out.

I had to stop tonight because the final opcode I execute is an RTS
(return from subroutine) that's branching back to address 0; which is
invalid ... meaning something went terribly wrong and the system crashed.
2016-07-22 22:03:25 +10:00
Tim Allen
be3f6ac0d5 Update to v100r09 release.
byuu says:

Another six hours in ...

I have all of the opcodes, memory access functions, disassembler mnemonics
and table building converted over to the new template<uint Size> format.

Certainly, it would be quite easy for this nightmare chip to throw me
another curveball, but so far I can handle:

- MOVE (EA to, EA from) case
  - read(from) has to update register index for +/-(aN) mode
- MOVEM (EA from) case
  - when using +/-(aN), RA can't actually be updated until the transfer
    is completed
- LEA (EA from) case
  - doesn't actually perform the final read; just returns the address
    to be read from
- ANDI (EA from-and-to) case
  - same EA has to be read from and written to
  - for -(aN), the read has to come from aN-2, but can't update aN yet;
    so that the write also goes to aN-2
- no opcode can ever fetch the extension words more than once
- manually control the order of extension word fetching order for proper
  opcode decoding

To do all of that without a whole lot of duplicated code (or really
bloating out every single instruction with red tape), I had to bring
back the "bool valid / uint32 address" variables inside the EA struct =(

If weird exceptions creep in like timing constraints only on certain
opcodes, I can use template flags to the EA read/write functions to
handle that.
2016-07-19 19:12:05 +10:00
Tim Allen
92fe5b0813 Update to v100r08 release.
byuu says:

Six and a half hours this time ... one new opcode, and all old opcodes
now in a deprecated format. Hooray, progress!

For building the table, I've decided to move from:

    for(uint opcode : range(65536)) {
      if(match(...)) bind(opNAME, ...);
    }

To instead having separate for loops for each supported opcode. This
lets me specialize parts I want with templates.

And to this aim, I'm moving to replace all of the
(read,write)(size, ...) functions with (read,write)<Size>(...) functions.

This will amount to the ~70ish instructions being triplicated ot ~210ish
instructions; but I think this is really important.

When I was getting into flag calculations, a ton of conditionals
were needed to mask sizes to byte/word/long. There was also lots of
conditionals in all the memory access handlers.

The template code is ugly, but we eliminate a huge amount of branch
conditions this way.
2016-07-18 08:11:29 +10:00
Tim Allen
059347e575 Update to v100r07 release.
byuu says:

Four and a half hours of work and ... zero new opcodes implemented.

This was the best job I could do refining the effective address
computations. Should have all twelve 68000 modes implemented now. Still
have a billion questions about when and how I'm supposed to perform
certain edge case operations, though.
2016-07-17 13:24:28 +10:00
Tim Allen
0d6a09f9f8 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 18:39:44 +10:00
Tim Allen
b72f35a13e Update to v100r05 release.
byuu says:

Alright, I'm definitely going to need to find some people willing to
tolerate my questions on this chip, so I'm going to go ahead and announce
I'm working on this I guess.

This core is way too big for a surprise like the NES and WS cores
were. It'll probably even span multiple v10x releases before it's
even ready.
2016-07-13 08:47:04 +10:00
Tim Allen
1c0ef793fe Update to v100r04 release.
byuu says:

I now have enough of three instructions implemented to get through the
first four instructions in Sonic the Hedgehog.

But they're far from complete. The very first instruction uses EA
addressing, which is similar to x86's ModRM in terms of how disgustingly
complex it is. And it also accesses Z80 control registers, which obviously
isn't going to do anything yet.

The slow speed was me being stupid again. It's not 7.6MHz per frame,
it's 7.67MHz per second. So yeah, speed is so far acceptable again. But
we'll see how things go as I keep emulating more. The 68K decode is not
pretty at all.
2016-07-12 20:19:31 +10:00
Tim Allen
76a8ecd32a Update to v100r03 release.
byuu says:

Changelog:
- moved Thread, Scheduler, Cheat functionality into emulator/ for
  all cores
- start of actual Mega Drive emulation (two 68K instructions)

I'm going to be rather terse on MD emulation, as it's too early for any
meaningful dialogue here.
2016-07-10 15:28:26 +10:00
Tim Allen
3dd1aa9c1b Update to v100r02 release.
byuu says:

Sigh ... I'm really not a good person. I'm inherently selfish.

My responsibility and obligation right now is to work on loki, and
then on the Tengai Makyou Zero translation, and then on improving the
Famicom emulation.

And yet ... it's not what I really want to do. That shouldn't matter;
I should work on my responsibilities first.

Instead, I'm going to be a greedy, self-centered asshole, and work on
what I really want to instead.

I'm really sorry, guys. I'm sure this will make a few people happy,
and probably upset even more people.

I'm also making zero guarantees that this ever gets finished. As always,
I wish I could keep these things secret, so if I fail / give up, I could
just drop it with no shame. But I would have to cut everyone out of the
WIP process completely to make it happen. So, here goes ...

This WIP adds the initial skeleton for Sega Mega Drive / Genesis
emulation. God help us.

(minor note: apparently the new extension for Mega Drive games is .md,
neat. That's what I chose for the folders too. I thought it was .smd,
so that'll be fixed in icarus for the next WIP.)

(aside: this is why I wanted to get v100 out. I didn't want this code in
a skeleton state in v100's source. Nor did I want really broken emulation,
which the first release is sure to be, tarring said release.)

...

So, basically, I've been ruminating on the legacy I want to leave behind
with higan. 3D systems are just plain out. I'm never going to support
them. They're too complex for my abilities, and they would run too slowly
with my design style. I'm not willing to compromise my design ideals. And
I would never want to play a 3D game system at native 240p/480i resolution
... but 1080p+ upscaling is not accurate, so that's a conflict I want
to avoid entirely. It's also never going to emulate computer systems
(X68K, PC-98, FM-Towns, etc) because holy shit that would completely
destroy me. It's also never going emulate arcade machines.

So I think of higan as a collection of 2D emulators for consoles
and handhelds. I've gone over every major 2D gaming system there is,
looking for ones with games I actually care about and enjoy. And I
basically have five of those systems supported already. Looking at the
remaining list, I see only three systems left that I have any interest
in whatsoever: PC-Engine, Master System, Mega Drive. Again, I'm not in
any way committing to emulating any of these, but ... if I had all of
those in higan, I think I'd be content to really, truly, finally stop
writing more emulators for the rest of my life.

And so I decided to tackle the most difficult system first. If I'm
successful, the Z80 core should cover a lot of the work on the SMS. And
the HuC6280 should land somewhere between the NES and SNES in terms of
difficulty ... closer to the NES.

The systems that just don't appeal to me at all, which I will never touch,
include, but are not limited to:
* Atari 2600/5200/7800
* Lynx
* Jaguar
* Vectrex
* Colecovision
* Commodore 64
* Neo-Geo
* Neo-Geo Pocket / Color
* Virtual Boy
* Super A'can
* 32X
* CD-i
* etc, etc, etc.

And really, even if something were mildly interesting in there ... we
have to stop. I can't scale infinitely. I'm already way past my limit,
but I'm doing this anyway. Too many cores bloats everything and kills
quality on everything. I don't want higan to become MESS v2.

I don't know what I'll do about the Famicom Disk System, PC-Engine CD,
and Mega CD. I don't think I'll be able to achieve 60fps emulating the
Mega CD, even if I tried to.

I don't know what's going to happen here with even the Mega Drive. Maybe
I'll get driven crazy with the documentation and quit. Maybe it'll end
up being too complicated and I'll quit. Maybe the emulation will end up
way too slow and I'll give up. Maybe it'll take me seven years to get
any games playable at all. Maybe Steve Snake, AamirM and Mike Pavone
will pool money to hire a hitman to come after me. Who knows.

But this is what I want to do, so ... here goes nothing.
2016-07-09 14:21:37 +10:00
Tim Allen
88c79e56a0 Update to v100r01 release.
[This version, with the internal version number changed back to "v100",
replaced the original v100 source archive on byuu.org soon after v100's
release, because it fixes important bugs in that version. --Ed]

byuu says:

Changelog:
- fixed default paths for Sufami Turbo slotted games
- moved WonderSwan orientation controls to the port rather than the device
  - I do like hex_usr's idea here; but that'll need more consideration;
    so this is a temporary fix
- added new debugger interface (see the public topic for more on that)
2016-07-08 22:31:35 +10:00
209 changed files with 6956 additions and 1426 deletions

48
higan/emulator/cheat.hpp Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
namespace Emulator {
struct Cheat {
struct Code {
uint addr;
uint data;
maybe<uint> comp;
};
explicit operator bool() const {
return codes.size() > 0;
}
auto reset() -> void {
codes.reset();
}
auto append(uint addr, uint data, maybe<uint> comp = nothing) -> void {
codes.append({addr, data, comp});
}
auto assign(const string_vector& list) -> void {
reset();
for(auto& entry : list) {
for(auto code : entry.split("+")) {
auto part = code.split("/");
if(part.size() == 2) append(part[0].hex(), part[1].hex());
if(part.size() == 3) append(part[0].hex(), part[2].hex(), part[1].hex());
}
}
}
auto find(uint addr, uint comp) -> maybe<uint> {
for(auto& code : codes) {
if(code.addr == addr && (!code.comp || code.comp() == comp)) {
return code.data;
}
}
return nothing;
}
private:
vector<Code> codes;
};
}

View File

@@ -0,0 +1,13 @@
#pragma once
namespace Emulator {
#if defined(DEBUGGER)
#define debug(id, ...) if(debugger.id) debugger.id(__VA_ARGS__)
#define privileged public
#else
#define debug(id, ...)
#define privileged private
#endif
}

View File

@@ -11,19 +11,21 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "100";
static const string Version = "101";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";
//incremented only when serialization format changes
static const string SerializerVersion = "100";
static const string SerializerVersion = "101";
namespace Constants {
namespace Colorburst {
static constexpr double NTSC = 315.0 / 88.0 * 1'000'000.0;
static constexpr double PAL = 283.75 * 15'625.0 + 25.0;
}
}
}
#include "interface.hpp"
#if defined(DEBUGGER)
#define privileged public
#else
#define privileged private
#endif
#include "debugger.hpp"

View File

@@ -0,0 +1,91 @@
#pragma once
namespace Emulator {
struct Scheduler {
enum class Mode : uint {
Run,
SynchronizeMaster,
SynchronizeSlave,
};
enum class Event : uint {
Step,
Frame,
Synchronize,
};
inline auto synchronizing() const -> bool { return _mode == Mode::SynchronizeSlave; }
auto reset() -> void {
_host = co_active();
_threads.reset();
}
auto primary(Thread& thread) -> void {
_master = _resume = thread.handle();
}
auto append(Thread& thread) -> bool {
if(_threads.find(&thread)) return false;
thread._clock += _threads.size(); //this bias prioritizes threads appended earlier first
return _threads.append(&thread), true;
}
auto remove(Thread& thread) -> bool {
if(auto offset = _threads.find(&thread)) return _threads.remove(*offset), true;
return false;
}
auto enter(Mode mode = Mode::Run) -> Event {
_mode = mode;
_host = co_active();
co_switch(_resume);
return _event;
}
inline auto resume(Thread& thread) -> void {
if(_mode != Mode::SynchronizeSlave) co_switch(thread.handle());
}
auto exit(Event event) -> void {
uintmax minimum = -1;
for(auto thread : _threads) {
if(thread->_clock < minimum) minimum = thread->_clock;
}
for(auto thread : _threads) {
thread->_clock -= minimum;
}
_event = event;
_resume = co_active();
co_switch(_host);
}
inline auto synchronize(Thread& thread) -> void {
if(thread.handle() == _master) {
while(enter(Mode::SynchronizeMaster) != Event::Synchronize);
} else {
_resume = thread.handle();
while(enter(Mode::SynchronizeSlave) != Event::Synchronize);
}
}
inline auto synchronize() -> void {
if(co_active() == _master) {
if(_mode == Mode::SynchronizeMaster) return exit(Event::Synchronize);
} else {
if(_mode == Mode::SynchronizeSlave) return exit(Event::Synchronize);
}
}
private:
cothread_t _host = nullptr; //program thread (used to exit scheduler)
cothread_t _resume = nullptr; //resume thread (used to enter scheduler)
cothread_t _master = nullptr; //primary thread (used to synchronize components)
Mode _mode = Mode::Run;
Event _event = Event::Step;
vector<Thread*> _threads;
};
}

54
higan/emulator/thread.hpp Normal file
View File

@@ -0,0 +1,54 @@
#pragma once
namespace Emulator {
struct Thread {
virtual ~Thread() {
if(_handle) co_delete(_handle);
}
inline auto active() const { return co_active() == _handle; }
inline auto handle() const { return _handle; }
inline auto frequency() const { return _frequency; }
inline auto scalar() const { return _scalar; }
inline auto clock() const { return _clock; }
auto setFrequency(double frequency) -> void {
_frequency = frequency + 0.5;
_scalar = ((uintmax)1 << (8 * sizeof(uintmax) - 1)) / _frequency;
}
auto setScalar(uintmax scalar) -> void {
_scalar = scalar;
}
auto setClock(uintmax clock) -> void {
_clock = clock;
}
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
if(_handle) co_delete(_handle);
_handle = co_create(64 * 1024 * sizeof(void*), entrypoint);
setFrequency(frequency);
}
inline auto step(uint clocks) -> void {
_clock += _scalar * clocks;
}
auto serialize(serializer& s) -> void {
s.integer(_frequency);
s.integer(_scalar);
s.integer(_clock);
}
protected:
cothread_t _handle = nullptr;
uintmax _frequency = 0;
uintmax _scalar = 0;
uintmax _clock = 0;
friend class Scheduler;
};
}

View File

@@ -1,16 +1,13 @@
processors += r6502
objects += fc-interface fc-system fc-scheduler fc-controller
objects += fc-interface fc-system fc-controller
objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
objects += fc-cheat
obj/fc-interface.o: fc/interface/interface.cpp $(call rwildcard,fc/interface/)
obj/fc-system.o: fc/system/system.cpp $(call rwildcard,fc/system/)
obj/fc-scheduler.o: fc/scheduler/scheduler.cpp $(call rwildcard,fc/scheduler/)
obj/fc-controller.o: fc/controller/controller.cpp $(call rwildcard,fc/controller/)
obj/fc-memory.o: fc/memory/memory.cpp $(call rwildcard,fc/memory/)
obj/fc-cartridge.o: fc/cartridge/cartridge.cpp $(call rwildcard,fc/cartridge/)
obj/fc-cpu.o: fc/cpu/cpu.cpp $(call rwildcard,fc/cpu/)
obj/fc-apu.o: fc/apu/apu.cpp $(call rwildcard,fc/apu/)
obj/fc-ppu.o: fc/ppu/ppu.cpp $(call rwildcard,fc/ppu/)
obj/fc-cheat.o: fc/cheat/cheat.cpp $(call rwildcard,fc/cheat/)

View File

@@ -63,8 +63,8 @@ auto APU::main() -> void {
}
auto APU::tick() -> void {
clock += 12;
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
Thread::step(12);
synchronize(cpu);
}
auto APU::setIRQ() -> void {
@@ -88,8 +88,8 @@ auto APU::power() -> void {
}
auto APU::reset() -> void {
create(APU::Enter, 21'477'272);
stream = Emulator::audio.createStream(1, 21'477'272.0 / 12.0);
create(APU::Enter, system.colorburst() * 6.0);
stream = Emulator::audio.createStream(1, system.colorburst() / 2.0);
pulse[0].reset();
pulse[1].reset();

View File

@@ -109,13 +109,13 @@ auto Board::mirror(uint addr, uint size) -> uint {
}
auto Board::main() -> void {
cartridge.clock += 12 * 4095;
cartridge.step(12 * 4095);
tick();
}
auto Board::tick() -> void {
cartridge.clock += 12;
if(cartridge.clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
cartridge.step(12);
cartridge.synchronize(cpu);
}
auto Board::readCHR(uint addr) -> uint8 {

View File

@@ -49,7 +49,7 @@ auto Cartridge::power() -> void {
}
auto Cartridge::reset() -> void {
create(Cartridge::Enter, 21'477'272);
create(Cartridge::Enter, system.colorburst() * 6.0);
board->reset();
}

View File

@@ -1,28 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Cheat cheat;
auto Cheat::reset() -> void {
codes.reset();
}
auto Cheat::append(uint addr, uint data) -> void {
codes.append({addr, Unused, data});
}
auto Cheat::append(uint addr, uint comp, uint data) -> void {
codes.append({addr, comp, data});
}
auto Cheat::find(uint addr, uint comp) -> maybe<uint> {
for(auto& code : codes) {
if(code.addr == addr && (code.comp == Unused || code.comp == comp)) {
return code.data;
}
}
return nothing;
}
}

View File

@@ -1,17 +0,0 @@
struct Cheat {
struct Code {
uint addr;
uint comp;
uint data;
};
vector<Code> codes;
enum : uint { Unused = ~0u };
alwaysinline auto enable() const -> bool { return codes.size() > 0; }
auto reset() -> void;
auto append(uint addr, uint data) -> void;
auto append(uint addr, uint comp, uint data) -> void;
auto find(uint addr, uint comp) -> maybe<uint>;
};
extern Cheat cheat;

View File

@@ -5,22 +5,24 @@ namespace Famicom {
#include "gamepad/gamepad.cpp"
Controller::Controller(bool port) : port(port) {
if(!thread) create(Controller::Enter, 1);
if(!handle()) create(Controller::Enter, 1);
}
Controller::~Controller() {
scheduler.remove(*this);
}
auto Controller::Enter() -> void {
while(true) {
scheduler.synchronize();
if(co_active() == peripherals.controllerPort1->thread) peripherals.controllerPort1->main();
if(co_active() == peripherals.controllerPort2->thread) peripherals.controllerPort2->main();
if(peripherals.controllerPort1->active()) peripherals.controllerPort1->main();
if(peripherals.controllerPort2->active()) peripherals.controllerPort2->main();
}
}
auto Controller::main() -> void {
step(1);
synchronize(cpu);
}
}

View File

@@ -16,7 +16,7 @@
// 6: data4 $4016.d4 read $4017.d4 read
// 7: gnd
struct Controller : Cothread {
struct Controller : Thread {
enum : bool { Port1 = 0, Port2 = 1 };
Controller(bool port);

View File

@@ -17,19 +17,11 @@ auto CPU::main() -> void {
}
auto CPU::step(uint clocks) -> void {
apu.clock -= clocks;
if(apu.clock < 0) co_switch(apu.thread);
ppu.clock -= clocks;
if(ppu.clock < 0) co_switch(ppu.thread);
cartridge.clock -= clocks;
if(cartridge.clock < 0) co_switch(cartridge.thread);
for(auto peripheral : peripherals) {
peripheral->clock -= clocks * (uint64)peripheral->frequency;
if(peripheral->clock < 0) co_switch(peripheral->thread);
}
Thread::step(clocks);
synchronize(apu);
synchronize(ppu);
synchronize(cartridge);
for(auto peripheral : peripherals) synchronize(*peripheral);
}
auto CPU::power() -> void {
@@ -44,7 +36,7 @@ auto CPU::power() -> void {
auto CPU::reset() -> void {
R6502::reset();
create(CPU::Enter, 21'477'272);
create(CPU::Enter, system.colorburst() * 6.0);
regs.pc = bus.read(0xfffc) << 0;
regs.pc |= bus.read(0xfffd) << 8;

View File

@@ -4,39 +4,30 @@
//started: 2011-09-05
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
#include <emulator/scheduler.hpp>
#include <emulator/cheat.hpp>
#include <processor/r6502/r6502.hpp>
namespace Famicom {
using File = Emulator::File;
using Scheduler = Emulator::Scheduler;
using Cheat = Emulator::Cheat;
extern Scheduler scheduler;
extern Cheat cheat;
struct Thread {
~Thread() {
if(thread) co_delete(thread);
struct Thread : Emulator::Thread {
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
Emulator::Thread::create(entrypoint, frequency);
scheduler.append(*this);
}
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
if(thread) co_delete(thread);
thread = co_create(65'536 * sizeof(void*), entrypoint);
this->frequency = frequency;
clock = 0;
inline auto synchronize(Thread& thread) -> void {
if(clock() >= thread.clock()) scheduler.resume(thread);
}
auto serialize(serializer& s) -> void {
s.integer(frequency);
s.integer(clock);
}
cothread_t thread = nullptr;
uint frequency = 0;
int64 clock = 0;
};
struct Cothread : Thread {
auto step(uint clocks) -> void;
auto synchronizeCPU() -> void;
};
#include <fc/scheduler/scheduler.hpp>
#include <fc/controller/controller.hpp>
#include <fc/system/system.hpp>
#include <fc/memory/memory.hpp>
@@ -44,16 +35,6 @@ namespace Famicom {
#include <fc/cpu/cpu.hpp>
#include <fc/apu/apu.hpp>
#include <fc/ppu/ppu.hpp>
#include <fc/cheat/cheat.hpp>
inline auto Cothread::step(uint clocks) -> void {
clock += clocks * (uint64)cpu.frequency;
synchronizeCPU();
}
inline auto Cothread::synchronizeCPU() -> void {
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
}
}
#include <fc/interface/interface.hpp>

View File

@@ -164,15 +164,7 @@ auto Interface::unserialize(serializer& s) -> bool {
}
auto Interface::cheatSet(const string_vector& list) -> void {
cheat.reset();
for(auto& codeset : list) {
auto codes = codeset.split("+");
for(auto& code : codes) {
auto part = code.split("/");
if(part.size() == 2) cheat.append(part[0].hex(), part[1].hex());
if(part.size() == 3) cheat.append(part[0].hex(), part[1].hex(), part[2].hex());
}
}
cheat.assign(list);
}
auto Interface::cap(const string& name) -> bool {

View File

@@ -17,7 +17,7 @@ auto Bus::read(uint16 addr) -> uint8 {
else if(addr <= 0x3fff) data = ppu.readIO(addr);
else if(addr <= 0x4017) data = cpu.readIO(addr);
if(cheat.enable()) {
if(cheat) {
if(auto result = cheat.find(addr, data)) return result();
}

View File

@@ -27,8 +27,8 @@ auto PPU::step(uint clocks) -> void {
if(io.ly == 261 && io.lx == 0) io.nmiFlag = io.nmiHold;
if(io.ly == 261 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
clock += 4;
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
Thread::step(4);
synchronize(cpu);
io.lx++;
}
@@ -56,7 +56,7 @@ auto PPU::power() -> void {
}
auto PPU::reset() -> void {
create(PPU::Enter, 21'477'272);
create(PPU::Enter, system.colorburst() * 6.0);
memory::fill(&io, sizeof(IO));
memory::fill(&latch, sizeof(Latches));

View File

@@ -1,44 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Scheduler scheduler;
auto Scheduler::reset() -> void {
host = co_active();
resume = cpu.thread;
}
auto Scheduler::enter(Mode mode_) -> Event {
mode = mode_;
host = co_active();
co_switch(resume);
if(event == Event::Frame) ppu.refresh();
return event;
}
auto Scheduler::exit(Event event_) -> void {
event = event_;
resume = co_active();
co_switch(host);
}
auto Scheduler::synchronize(cothread_t thread) -> void {
if(thread == cpu.thread) {
while(enter(Mode::SynchronizeCPU) != Event::Synchronize);
} else {
resume = thread;
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
}
}
auto Scheduler::synchronize() -> void {
if(co_active() == cpu.thread && mode == Mode::SynchronizeCPU) return exit(Event::Synchronize);
if(co_active() != cpu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
}
auto Scheduler::synchronizing() const -> bool {
return mode == Mode::SynchronizeAll;
}
}

View File

@@ -1,28 +0,0 @@
struct Scheduler {
enum class Mode : uint {
Run,
SynchronizeCPU,
SynchronizeAll,
};
enum class Event : uint {
Unknown,
Frame,
Synchronize,
};
auto reset() -> void;
auto enter(Mode = Mode::Run) -> Event;
auto exit(Event) -> void;
auto synchronize(cothread_t) -> void;
auto synchronize() -> void;
auto synchronizing() const -> bool;
private:
cothread_t host = nullptr;
cothread_t resume = nullptr;
Mode mode = Mode::Run;
Event event = Event::Unknown;
};
extern Scheduler scheduler;

View File

@@ -6,19 +6,19 @@ namespace Famicom {
#include "video.cpp"
#include "serialization.cpp"
System system;
Scheduler scheduler;
Cheat cheat;
auto System::run() -> void {
scheduler.enter();
if(scheduler.enter() == Scheduler::Event::Frame) ppu.refresh();
}
auto System::runToSave() -> void {
scheduler.synchronize(cpu.thread);
scheduler.synchronize(apu.thread);
scheduler.synchronize(ppu.thread);
scheduler.synchronize(cartridge.thread);
for(auto peripheral : cpu.peripherals) {
scheduler.synchronize(peripheral->thread);
}
scheduler.synchronize(cpu);
scheduler.synchronize(apu);
scheduler.synchronize(ppu);
scheduler.synchronize(cartridge);
for(auto peripheral : cpu.peripherals) scheduler.synchronize(*peripheral);
}
auto System::load() -> bool {
@@ -30,6 +30,7 @@ auto System::load() -> bool {
}
auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
serializeInit();
return information.loaded = true;
}
@@ -62,11 +63,12 @@ auto System::reset() -> void {
Emulator::audio.reset();
Emulator::audio.setInterface(interface);
scheduler.reset();
cartridge.reset();
cpu.reset();
apu.reset();
ppu.reset();
scheduler.reset();
scheduler.primary(cpu);
peripherals.reset();
}

View File

@@ -1,5 +1,6 @@
struct System {
auto loaded() const -> bool { return information.loaded; }
auto colorburst() const -> double { return information.colorburst; }
auto run() -> void;
auto runToSave() -> void;
@@ -27,6 +28,7 @@ struct System {
struct Information {
bool loaded = false;
double colorburst = 0.0;
string manifest;
} information;

View File

@@ -1,16 +1,13 @@
processors += lr35902
objects += gb-interface gb-system gb-scheduler
objects += gb-interface gb-system
objects += gb-memory gb-cartridge
objects += gb-cpu gb-ppu gb-apu
objects += gb-cheat
obj/gb-interface.o: gb/interface/interface.cpp $(call rwildcard,gb/interface/)
obj/gb-system.o: gb/system/system.cpp $(call rwildcard,gb/system/)
obj/gb-scheduler.o: gb/scheduler/scheduler.cpp $(call rwildcard,gb/scheduler/)
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp $(call rwildcard,gb/cartridge/)
obj/gb-memory.o: gb/memory/memory.cpp $(call rwildcard,gb/memory/)
obj/gb-cpu.o: gb/cpu/cpu.cpp $(call rwildcard,gb/cpu/)
obj/gb-ppu.o: gb/ppu/ppu.cpp $(call rwildcard,gb/ppu/)
obj/gb-apu.o: gb/apu/apu.cpp $(call rwildcard,gb/apu/)
obj/gb-cheat.o: gb/cheat/cheat.cpp $(call rwildcard,gb/cheat/)

View File

@@ -51,8 +51,8 @@ auto APU::main() -> void {
}
cycle++;
clock += cpu.frequency;
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
Thread::step(1);
synchronize(cpu);
}
//filter to remove DC bias

View File

@@ -1,28 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
Cheat cheat;
auto Cheat::reset() -> void {
codes.reset();
}
auto Cheat::append(uint addr, uint data) -> void {
codes.append({addr, Unused, data});
}
auto Cheat::append(uint addr, uint comp, uint data) -> void {
codes.append({addr, comp, data});
}
auto Cheat::find(uint addr, uint comp) -> maybe<uint> {
for(auto& code : codes) {
if(code.addr == addr && (code.comp == Unused || code.comp == comp)) {
return code.data;
}
}
return nothing;
}
}

View File

@@ -1,18 +0,0 @@
struct Cheat {
struct Code {
uint addr;
uint comp;
uint data;
};
vector<Code> codes;
enum : uint { Unused = ~0u };
alwaysinline auto enable() const -> bool { return codes.size() > 0; }
auto reset() -> void;
auto append(uint addr, uint data) -> void;
auto append(uint addr, uint comp, uint data) -> void;
auto find(uint addr, uint comp) -> maybe<uint>;
};
extern Cheat cheat;

View File

@@ -77,8 +77,8 @@ auto CPU::stop() -> bool {
if(status.speedSwitch) {
status.speedSwitch = 0;
status.speedDouble ^= 1;
if(status.speedDouble == 0) frequency = 4 * 1024 * 1024;
if(status.speedDouble == 1) frequency = 8 * 1024 * 1024;
if(status.speedDouble == 0) setFrequency(4 * 1024 * 1024);
if(status.speedDouble == 1) setFrequency(8 * 1024 * 1024);
return true;
}
return false;

View File

@@ -16,11 +16,9 @@ auto CPU::step(uint clocks) -> void {
if((status.div & 511) == 0) timer8192hz();
if((status.div & 1023) == 0) timer4096hz();
ppu.clock -= ppu.frequency;
if(ppu.clock < 0) co_switch(ppu.thread);
apu.clock -= apu.frequency;
if(apu.clock < 0) co_switch(apu.thread);
Thread::step(1);
synchronize(ppu);
synchronize(apu);
}
if(system.sgb()) {

View File

@@ -4,41 +4,36 @@
//started: 2010-12-27
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
#include <emulator/scheduler.hpp>
#include <emulator/cheat.hpp>
#include <processor/lr35902/lr35902.hpp>
namespace GameBoy {
using File = Emulator::File;
using Scheduler = Emulator::Scheduler;
using Cheat = Emulator::Cheat;
extern Scheduler scheduler;
extern Cheat cheat;
struct Thread {
~Thread() {
if(thread) co_delete(thread);
struct Thread : Emulator::Thread {
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
Emulator::Thread::create(entrypoint, frequency);
scheduler.append(*this);
}
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
if(thread) co_delete(thread);
thread = co_create(65'536 * sizeof(void*), entrypoint);
this->frequency = frequency;
clock = 0;
inline auto synchronize(Thread& thread) -> void {
if(clock() >= thread.clock()) scheduler.resume(thread);
}
auto serialize(serializer& s) -> void {
s.integer(frequency);
s.integer(clock);
}
cothread_t thread = nullptr;
uint frequency = 0;
int64 clock = 0;
};
#include <gb/memory/memory.hpp>
#include <gb/system/system.hpp>
#include <gb/scheduler/scheduler.hpp>
#include <gb/cartridge/cartridge.hpp>
#include <gb/cpu/cpu.hpp>
#include <gb/ppu/ppu.hpp>
#include <gb/apu/apu.hpp>
#include <gb/cheat/cheat.hpp>
}
#include <gb/interface/interface.hpp>

View File

@@ -163,15 +163,7 @@ auto Interface::unserialize(serializer& s) -> bool {
}
auto Interface::cheatSet(const string_vector& list) -> void {
cheat.reset();
for(auto& codeset : list) {
auto codes = codeset.split("+");
for(auto& code : codes) {
auto part = code.split("/");
if(part.size() == 2) cheat.append(part[0].hex(), part[1].hex());
if(part.size() == 3) cheat.append(part[0].hex(), part[1].hex(), part[2].hex());
}
}
cheat.assign(list);
}
auto Interface::lcdScanline() -> void {

View File

@@ -38,7 +38,7 @@ auto Memory::free() -> void {
auto Bus::read(uint16 addr) -> uint8 {
uint8 data = mmio[addr]->readIO(addr);
if(cheat.enable()) {
if(cheat) {
if(auto result = cheat.find(addr, data)) return result();
}

View File

@@ -118,9 +118,9 @@ auto PPU::writeIO(uint16 addr, uint8 data) -> void {
status.lx = 0;
//restart cothread to begin new frame
auto clock = this->clock;
auto clock = Thread::clock();
create(Enter, 4 * 1024 * 1024);
this->clock = clock;
Thread::setClock(clock);
}
status.displayEnable = data & 0x80;

View File

@@ -94,8 +94,8 @@ auto PPU::step(uint clocks) -> void {
}
status.lx++;
clock += cpu.frequency;
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
Thread::step(1);
synchronize(cpu);
}
}

View File

@@ -1,44 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
Scheduler scheduler;
auto Scheduler::power() -> void {
host = co_active();
resume = cpu.thread;
}
auto Scheduler::enter(Mode mode_) -> Event {
mode = mode_;
host = co_active();
co_switch(resume);
if(event == Event::Frame) ppu.refresh();
return event;
}
auto Scheduler::exit(Event event_) -> void {
event = event_;
resume = co_active();
co_switch(host);
}
auto Scheduler::synchronize(cothread_t thread) -> void {
if(thread == cpu.thread) {
while(enter(Mode::SynchronizeCPU) != Event::Synchronize);
} else {
resume = thread;
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
}
}
auto Scheduler::synchronize() -> void {
if(co_active() == cpu.thread && mode == Mode::SynchronizeCPU) return exit(Event::Synchronize);
if(co_active() != cpu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
}
auto Scheduler::synchronizing() const -> bool {
return mode == Mode::SynchronizeAll;
}
}

View File

@@ -1,29 +0,0 @@
struct Scheduler {
enum class Mode : uint {
Run,
SynchronizeCPU,
SynchronizeAll,
};
enum class Event : uint {
Unknown,
Step,
Frame,
Synchronize,
};
auto power() -> void;
auto enter(Mode = Mode::Run) -> Event;
auto exit(Event) -> void;
auto synchronize(cothread_t) -> void;
auto synchronize() -> void;
auto synchronizing() const -> bool;
private:
cothread_t host = nullptr;
cothread_t resume = nullptr;
Mode mode = Mode::Run;
Event event = Event::Unknown;
};
extern Scheduler scheduler;

View File

@@ -5,15 +5,17 @@ namespace GameBoy {
#include "video.cpp"
#include "serialization.cpp"
System system;
Scheduler scheduler;
Cheat cheat;
auto System::run() -> void {
scheduler.enter();
if(scheduler.enter() == Scheduler::Event::Frame) ppu.refresh();
}
auto System::runToSave() -> void {
scheduler.synchronize(cpu.thread);
scheduler.synchronize(ppu.thread);
scheduler.synchronize(apu.thread);
scheduler.synchronize(cpu);
scheduler.synchronize(ppu);
scheduler.synchronize(apu);
}
auto System::init() -> void {
@@ -66,12 +68,13 @@ auto System::power() -> void {
Emulator::audio.setInterface(interface);
}
scheduler.reset();
bus.power();
cartridge.power();
cpu.power();
ppu.power();
apu.power();
scheduler.power();
scheduler.primary(cpu);
_clocksExecuted = 0;
}

View File

@@ -1,12 +1,11 @@
processors += arm
objects += gba-memory gba-interface gba-scheduler gba-system
objects += gba-memory gba-interface gba-system
objects += gba-cartridge gba-player
objects += gba-cpu gba-ppu gba-apu
obj/gba-memory.o: gba/memory/memory.cpp $(call rwildcard,gba/memory)
obj/gba-interface.o: gba/interface/interface.cpp $(call rwildcard,gba/interface)
obj/gba-scheduler.o: gba/scheduler/scheduler.cpp $(call rwildcard,gba/scheduler)
obj/gba-system.o: gba/system/system.cpp $(call rwildcard,gba/system)
obj/gba-cartridge.o: gba/cartridge/cartridge.cpp $(call rwildcard,gba/cartridge)
obj/gba-player.o: gba/player/player.cpp $(call rwildcard,gba/player)

View File

@@ -68,8 +68,8 @@ auto APU::main() -> void {
}
auto APU::step(uint clocks) -> void {
clock += clocks;
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
Thread::step(clocks);
synchronize(cpu);
}
auto APU::power() -> void {

View File

@@ -83,11 +83,9 @@ auto CPU::step(uint clocks) -> void {
}
auto CPU::syncStep(uint clocks) -> void {
ppu.clock -= clocks;
if(ppu.clock < 0) co_switch(ppu.thread);
apu.clock -= clocks;
if(apu.clock < 0) co_switch(apu.thread);
Thread::step(clocks);
synchronize(ppu);
synchronize(apu);
}
auto CPU::keypadRun() -> void {

View File

@@ -4,10 +4,15 @@
//started: 2012-03-19
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
#include <emulator/scheduler.hpp>
#include <processor/arm/arm.hpp>
namespace GameBoyAdvance {
using File = Emulator::File;
using Scheduler = Emulator::Scheduler;
extern Scheduler scheduler;
enum : uint { //mode flags for bus read, write:
Nonsequential = 1, //N cycle
@@ -21,30 +26,22 @@ namespace GameBoyAdvance {
Signed = 256, //sign extended
};
struct Thread {
~Thread() {
if(thread) co_delete(thread);
struct Thread : Emulator::Thread {
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
Emulator::Thread::create(entrypoint, frequency);
scheduler.append(*this);
}
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
if(thread) co_delete(thread);
thread = co_create(65'536 * sizeof(void*), entrypoint);
this->frequency = frequency;
clock = 0;
inline auto synchronize(Thread& thread) -> void {
if(clock() >= thread.clock()) scheduler.resume(thread);
}
auto serialize(serializer& s) -> void {
s.integer(frequency);
s.integer(clock);
inline auto step(uint clocks) -> void {
_clock += clocks;
}
cothread_t thread = nullptr;
uint frequency = 0;
int clock = 0;
};
#include <gba/memory/memory.hpp>
#include <gba/scheduler/scheduler.hpp>
#include <gba/system/system.hpp>
#include <gba/cartridge/cartridge.hpp>
#include <gba/player/player.hpp>

View File

@@ -43,8 +43,8 @@ auto PPU::main() -> void {
}
auto PPU::step(uint clocks) -> void {
clock += clocks;
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
Thread::step(clocks);
synchronize(cpu);
}
auto PPU::power() -> void {

View File

@@ -1,44 +0,0 @@
#include <gba/gba.hpp>
namespace GameBoyAdvance {
Scheduler scheduler;
auto Scheduler::power() -> void {
host = co_active();
resume = cpu.thread;
}
auto Scheduler::enter(Mode mode_) -> Event {
mode = mode_;
host = co_active();
co_switch(resume);
if(event == Event::Frame) ppu.refresh();
return event;
}
auto Scheduler::exit(Event event_) -> void {
event = event_;
resume = co_active();
co_switch(host);
}
auto Scheduler::synchronize(cothread_t thread) -> void {
if(thread == cpu.thread) {
while(enter(Mode::SynchronizeCPU) != Event::Synchronize);
} else {
resume = thread;
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
}
}
auto Scheduler::synchronize() -> void {
if(co_active() == cpu.thread && mode == Mode::SynchronizeCPU) return exit(Event::Synchronize);
if(co_active() != cpu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
}
auto Scheduler::synchronizing() const -> bool {
return mode == Mode::SynchronizeAll;
}
}

View File

@@ -1,28 +0,0 @@
struct Scheduler {
enum class Mode : uint {
Run,
SynchronizeCPU,
SynchronizeAll,
};
enum class Event : uint {
Unknown,
Frame,
Synchronize,
};
auto power() -> void;
auto enter(Mode = Mode::Run) -> Event;
auto exit(Event) -> void;
auto synchronize(cothread_t) -> void;
auto synchronize() -> void;
auto synchronizing() const -> bool;
private:
cothread_t host = nullptr;
cothread_t resume = nullptr;
Mode mode = Mode::Run;
Event event = Event::Unknown;
};
extern Scheduler scheduler;

View File

@@ -7,6 +7,7 @@ namespace GameBoyAdvance {
#include "serialization.cpp"
BIOS bios;
System system;
Scheduler scheduler;
auto System::init() -> void {
}
@@ -23,13 +24,14 @@ auto System::power() -> void {
Emulator::audio.reset();
Emulator::audio.setInterface(interface);
scheduler.reset();
bus.power();
player.power();
cpu.power();
ppu.power();
apu.power();
cartridge.power();
scheduler.power();
scheduler.primary(cpu);
}
auto System::load() -> bool {
@@ -61,13 +63,13 @@ auto System::unload() -> void {
}
auto System::run() -> void {
while(scheduler.enter() != Scheduler::Event::Frame);
if(scheduler.enter() == Scheduler::Event::Frame) ppu.refresh();
}
auto System::runToSave() -> void {
scheduler.synchronize(cpu.thread);
scheduler.synchronize(ppu.thread);
scheduler.synchronize(apu.thread);
scheduler.synchronize(cpu);
scheduler.synchronize(ppu);
scheduler.synchronize(apu);
}
}

14
higan/md/GNUmakefile Normal file
View File

@@ -0,0 +1,14 @@
processors += m68k z80
objects += md-interface
objects += md-cpu md-apu md-vdp md-psg md-ym2612
objects += md-system md-cartridge
obj/md-interface.o: md/interface/interface.cpp $(call rwildcard,md/interface)
obj/md-cpu.o: md/cpu/cpu.cpp $(call rwildcard,md/cpu)
obj/md-apu.o: md/apu/apu.cpp $(call rwildcard,md/apu)
obj/md-vdp.o: md/vdp/vdp.cpp $(call rwildcard,md/vdp)
obj/md-psg.o: md/psg/psg.cpp $(call rwildcard,md/psg)
obj/md-ym2612.o: md/ym2612/ym2612.cpp $(call rwildcard,md/ym2612)
obj/md-system.o: md/system/system.cpp $(call rwildcard,md/system)
obj/md-cartridge.o: md/cartridge/cartridge.cpp $(call rwildcard,md/cartridge)

25
higan/md/apu/apu.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include <md/md.hpp>
namespace MegaDrive {
APU apu;
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();
}
auto APU::main() -> void {
step(system.colorburst());
}
auto APU::step(uint clocks) -> void {
}
auto APU::power() -> void {
}
auto APU::reset() -> void {
create(APU::Enter, system.colorburst());
}
}

12
higan/md/apu/apu.hpp Normal file
View File

@@ -0,0 +1,12 @@
//Zilog Z80
struct APU : Processor::Z80, Thread {
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> void;
auto reset() -> void;
};
extern APU apu;

View File

@@ -0,0 +1,82 @@
#include <md/md.hpp>
namespace MegaDrive {
Cartridge cartridge;
auto Cartridge::load() -> bool {
information = Information();
if(auto pathID = interface->load(ID::MegaDrive, "Mega Drive", "md")) {
information.pathID = pathID();
} else return false;
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
auto document = BML::unserialize(information.manifest);
information.title = document["information/title"].text();
if(auto node = document["board/rom"]) {
rom.size = node["size"].natural();
rom.mask = bit::round(rom.size) - 1;
if(rom.size) {
rom.data = new uint8[rom.mask + 1];
if(auto name = node["name"].text()) {
if(auto fp = interface->open(pathID(), name, File::Read, File::Required)) {
fp->read(rom.data, rom.size);
}
}
}
}
if(auto node = document["board/ram"]) {
ram.size = node["size"].natural();
ram.mask = bit::round(ram.size) - 1;
if(ram.size) {
ram.data = new uint8[ram.mask + 1];
if(auto name = node["name"].text()) {
if(auto fp = interface->open(pathID(), name, File::Read)) {
fp->read(ram.data, ram.size);
}
}
}
}
return true;
}
auto Cartridge::save() -> void {
auto document = BML::unserialize(information.manifest);
if(auto name = document["board/ram/name"].text()) {
if(auto fp = interface->open(pathID(), name, File::Write)) {
fp->write(ram.data, ram.size);
}
}
}
auto Cartridge::unload() -> void {
delete[] rom.data;
delete[] ram.data;
rom = Memory();
ram = Memory();
}
auto Cartridge::power() -> void {
}
auto Cartridge::reset() -> void {
}
auto Cartridge::read(bool word, uint24 addr) -> uint16 {
uint16 data = rom.data[addr & rom.mask];
if(!word) return data;
return data << 8 | rom.data[addr + 1 & rom.mask];
}
auto Cartridge::write(bool word, uint24 addr, uint16 data) -> void {
}
}

View File

@@ -0,0 +1,33 @@
struct Cartridge {
auto pathID() const -> uint { return information.pathID; }
auto sha256() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
auto load() -> bool;
auto save() -> void;
auto unload() -> void;
auto power() -> void;
auto reset() -> void;
auto read(bool word, uint24 addr) -> uint16;
auto write(bool word, uint24 addr, uint16 data) -> void;
struct Information {
uint pathID = 0;
string sha256;
string manifest;
string title;
} information;
struct Memory {
uint8* data = nullptr;
uint size = 0;
uint mask = 0;
};
Memory rom;
Memory ram;
};
extern Cartridge cartridge;

63
higan/md/cpu/cpu.cpp Normal file
View File

@@ -0,0 +1,63 @@
#include <md/md.hpp>
namespace MegaDrive {
CPU cpu;
auto CPU::Enter() -> void {
cpu.boot();
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::boot() -> void {
r.a[7] = read(1, 0) << 16 | read(1, 2) << 0;
r.pc = read(1, 4) << 16 | read(1, 6) << 0;
}
auto CPU::main() -> void {
instruction();
}
auto CPU::step(uint clocks) -> void {
Thread::step(clocks);
cycles += clocks;
if(cycles >= frequency() / 60) {
cycles = 0;
scheduler.exit(Scheduler::Event::Frame);
}
}
auto CPU::power() -> void {
M68K::power();
for(auto& byte : ram) byte = 0x00;
}
auto CPU::reset() -> void {
M68K::reset();
create(CPU::Enter, system.colorburst() * 15.0 / 7.0);
cycles = 0;
}
auto CPU::read(bool word, uint24 addr) -> uint16 {
if(addr < 0x400000) return cartridge.read(word, addr);
if(addr < 0xe00000) return 0x0000;
uint16 data = ram[addr & 65535];
if(word) data = data << 8 | ram[addr + 1 & 65535];
return data;
}
auto CPU::write(bool word, uint24 addr, uint16 data) -> void {
if(addr < 0x400000) return cartridge.write(word, addr, data);
if(addr < 0xe00000) return;
if(!word) {
ram[addr & 65535] = data;
} else {
ram[addr + 0 & 65535] = data >> 8;
ram[addr + 1 & 65535] = data >> 0;
}
}
}

21
higan/md/cpu/cpu.hpp Normal file
View File

@@ -0,0 +1,21 @@
//Motorola 68000
struct CPU : Processor::M68K, Thread {
static auto Enter() -> void;
auto boot() -> void;
auto main() -> void;
auto step(uint clocks) -> void override;
auto power() -> void;
auto reset() -> void;
auto read(bool word, uint24 addr) -> uint16 override;
auto write(bool word, uint24 addr, uint16 data) -> void override;
private:
uint8 ram[64 * 1024];
uint cycles = 0;
};
extern CPU cpu;

View File

@@ -0,0 +1,127 @@
#include <md/md.hpp>
namespace MegaDrive {
Interface* interface = nullptr;
Settings settings;
Interface::Interface() {
interface = this;
information.manufacturer = "Sega";
information.name = "Mega Drive";
information.width = 320; //1280
information.height = 240; // 480
information.overscan = true;
information.aspectRatio = 4.0 / 3.0;
information.resettable = true;
information.capability.states = false;
information.capability.cheats = false;
media.append({ID::MegaDrive, "Mega Drive", "md"});
Port controllerPort1{ID::Port::Controller1, "Controller Port 1"};
Port controllerPort2{ID::Port::Controller2, "Controller Port 2"};
{ Device device{ID::Device::Gamepad, "Gamepad"};
device.inputs.append({0, "Up" });
device.inputs.append({0, "Down" });
device.inputs.append({0, "Left" });
device.inputs.append({0, "Right"});
device.inputs.append({0, "A" });
device.inputs.append({0, "B" });
device.inputs.append({0, "C" });
device.inputs.append({0, "X" });
device.inputs.append({0, "Y" });
device.inputs.append({0, "Z" });
device.inputs.append({0, "Start"});
controllerPort1.devices.append(device);
controllerPort2.devices.append(device);
}
ports.append(move(controllerPort1));
ports.append(move(controllerPort2));
}
auto Interface::manifest() -> string {
return cartridge.manifest();
}
auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::videoFrequency() -> double {
return 60.0;
}
auto Interface::videoColors() -> uint32 {
return 1 << 9;
}
auto Interface::videoColor(uint32 color) -> uint64 {
uint B = color.bits(0,2);
uint G = color.bits(3,5);
uint R = color.bits(6,8);
uint64 r = image::normalize(R, 3, 16);
uint64 g = image::normalize(G, 3, 16);
uint64 b = image::normalize(B, 3, 16);
return r << 32 | g << 16 | b << 0;
}
auto Interface::audioFrequency() -> double {
return 52'000.0;
}
auto Interface::loaded() -> bool {
return system.loaded();
}
auto Interface::load(uint id) -> bool {
return system.load();
}
auto Interface::save() -> void {
system.save();
}
auto Interface::unload() -> void {
system.unload();
}
auto Interface::power() -> void {
system.power();
}
auto Interface::reset() -> void {
system.reset();
}
auto Interface::run() -> void {
system.run();
}
auto Interface::serialize() -> serializer {
return {};
}
auto Interface::unserialize(serializer& s) -> bool {
return false;
}
auto Interface::cap(const string& name) -> bool {
return false;
}
auto Interface::get(const string& name) -> any {
return {};
}
auto Interface::set(const string& name, const any& value) -> bool {
return false;
}
}

View File

@@ -0,0 +1,54 @@
namespace MegaDrive {
struct ID {
enum : uint {
System,
MegaDrive,
};
struct Port { enum : uint {
Controller1,
Controller2,
};};
struct Device { enum : uint {
Gamepad,
};};
};
struct Interface : Emulator::Interface {
using Emulator::Interface::load;
Interface();
auto manifest() -> string override;
auto title() -> string override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;
auto load(uint id) -> bool override;
auto save() -> void override;
auto unload() -> void override;
auto power() -> void override;
auto reset() -> void override;
auto run() -> void override;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
};
struct Settings {
};
extern Interface* interface;
extern Settings settings;
}

39
higan/md/md.hpp Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
//license: GPLv3
//started: 2016-07-08
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
#include <emulator/scheduler.hpp>
#include <processor/m68k/m68k.hpp>
#include <processor/z80/z80.hpp>
namespace MegaDrive {
using File = Emulator::File;
using Scheduler = Emulator::Scheduler;
extern Scheduler scheduler;
struct Thread : Emulator::Thread {
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
Emulator::Thread::create(entrypoint, frequency);
scheduler.append(*this);
}
inline auto synchronize(Thread& thread) -> void {
if(clock() >= thread.clock()) scheduler.resume(thread);
}
};
#include <md/cpu/cpu.hpp>
#include <md/apu/apu.hpp>
#include <md/vdp/vdp.hpp>
#include <md/psg/psg.hpp>
#include <md/ym2612/ym2612.hpp>
#include <md/system/system.hpp>
#include <md/cartridge/cartridge.hpp>
}
#include <md/interface/interface.hpp>

25
higan/md/psg/psg.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include <md/md.hpp>
namespace MegaDrive {
PSG psg;
auto PSG::Enter() -> void {
while(true) scheduler.synchronize(), psg.main();
}
auto PSG::main() -> void {
step(system.colorburst());
}
auto PSG::step(uint clocks) -> void {
}
auto PSG::power() -> void {
}
auto PSG::reset() -> void {
create(PSG::Enter, system.colorburst());
}
}

12
higan/md/psg/psg.hpp Normal file
View File

@@ -0,0 +1,12 @@
//TI SN76489
struct PSG : Thread {
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> void;
auto reset() -> void;
};
extern PSG psg;

View File

@@ -0,0 +1,62 @@
#include <md/md.hpp>
namespace MegaDrive {
System system;
Scheduler scheduler;
auto System::run() -> void {
if(scheduler.enter() == Scheduler::Event::Frame) {
static uint32 output[320 * 240] = {0};
Emulator::video.refresh(output, 320 * sizeof(uint32), 320, 240);
}
}
auto System::load() -> bool {
information = Information();
if(auto fp = interface->open(ID::System, "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
return information.loaded = true;
}
auto System::save() -> void {
cartridge.save();
}
auto System::unload() -> void {
cartridge.unload();
}
auto System::power() -> void {
cartridge.power();
cpu.power();
apu.power();
vdp.power();
psg.power();
ym2612.power();
reset();
}
auto System::reset() -> void {
Emulator::video.reset();
Emulator::video.setInterface(interface);
Emulator::video.setPalette();
Emulator::audio.reset();
Emulator::audio.setInterface(interface);
scheduler.reset();
cartridge.reset();
cpu.reset();
apu.reset();
vdp.reset();
psg.reset();
ym2612.reset();
scheduler.primary(cpu);
}
}

View File

@@ -0,0 +1,20 @@
struct System {
auto loaded() const -> bool { return information.loaded; }
auto colorburst() const -> double { return information.colorburst; }
auto run() -> void;
auto load() -> bool;
auto save() -> void;
auto unload() -> void;
auto power() -> void;
auto reset() -> void;
struct Information {
bool loaded = false;
string manifest;
double colorburst = 0.0;
} information;
};
extern System system;

28
higan/md/vdp/vdp.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include <md/md.hpp>
//256-width = colorburst * 15 / 10
//320-width = colorburst * 15 / 8
namespace MegaDrive {
VDP vdp;
auto VDP::Enter() -> void {
while(true) scheduler.synchronize(), vdp.main();
}
auto VDP::main() -> void {
step(system.colorburst() * 15.0 / 10.0);
}
auto VDP::step(uint clocks) -> void {
}
auto VDP::power() -> void {
}
auto VDP::reset() -> void {
create(VDP::Enter, system.colorburst() * 15.0 / 10.0);
}
}

12
higan/md/vdp/vdp.hpp Normal file
View File

@@ -0,0 +1,12 @@
//Yamaha YM7101
struct VDP : Thread {
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> void;
auto reset() -> void;
};
extern VDP vdp;

View File

@@ -0,0 +1,25 @@
#include <md/md.hpp>
namespace MegaDrive {
YM2612 ym2612;
auto YM2612::Enter() -> void {
while(true) scheduler.synchronize(), ym2612.main();
}
auto YM2612::main() -> void {
step(system.colorburst() * 15.0 / 7.0);
}
auto YM2612::step(uint clocks) -> void {
}
auto YM2612::power() -> void {
}
auto YM2612::reset() -> void {
create(YM2612::Enter, system.colorburst() * 15.0 / 7.0);
}
}

View File

@@ -0,0 +1,12 @@
//Yamaha YM2612
struct YM2612 : Thread {
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> void;
auto reset() -> void;
};
extern YM2612 ym2612;

View File

@@ -4,18 +4,22 @@ objects += $(if $(findstring arm,$(processors)),processor-arm)
objects += $(if $(findstring gsu,$(processors)),processor-gsu)
objects += $(if $(findstring hg51b,$(processors)),processor-hg51b)
objects += $(if $(findstring lr35902,$(processors)),processor-lr35902)
objects += $(if $(findstring m68k,$(processors)),processor-m68k)
objects += $(if $(findstring r6502,$(processors)),processor-r6502)
objects += $(if $(findstring r65816,$(processors)),processor-r65816)
objects += $(if $(findstring spc700,$(processors)),processor-spc700)
objects += $(if $(findstring upd96050,$(processors)),processor-upd96050)
objects += $(if $(findstring v30mz,$(processors)),processor-v30mz)
objects += $(if $(findstring z80,$(processors)),processor-z80)
obj/processor-arm.o: processor/arm/arm.cpp $(call rwildcard,processor/arm)
obj/processor-gsu.o: processor/gsu/gsu.cpp $(call rwildcard,processor/gsu)
obj/processor-hg51b.o: processor/hg51b/hg51b.cpp $(call rwildcard,processor/hg51b)
obj/processor-lr35902.o: processor/lr35902/lr35902.cpp $(call rwildcard,processor/lr35902)
obj/processor-m68k.o: processor/m68k/m68k.cpp $(call rwildcard,processor/m68k)
obj/processor-r6502.o: processor/r6502/r6502.cpp $(call rwildcard,processor/r6502)
obj/processor-r65816.o: processor/r65816/r65816.cpp $(call rwildcard,processor/r65816)
obj/processor-spc700.o: processor/spc700/spc700.cpp $(call rwildcard,processor/spc700)
obj/processor-upd96050.o: processor/upd96050/upd96050.cpp $(call rwildcard,processor/upd96050)
obj/processor-v30mz.o: processor/v30mz/v30mz.cpp $(call rwildcard,processor/v30mz)
obj/processor-z80.o: processor/z80/z80.cpp $(call rwildcard,processor/z80)

View File

@@ -0,0 +1,364 @@
template<> auto M68K::_read<Byte>(uint32 addr) -> uint32 {
return read(0, addr);
}
template<> auto M68K::_read<Word>(uint32 addr) -> uint32 {
return read(1, addr);
}
template<> auto M68K::_read<Long>(uint32 addr) -> uint32 {
uint32 data = _read<Word>(addr + 0) << 16;
return data | _read<Word>(addr + 2) << 0;
}
template<uint Size> auto M68K::_readPC() -> uint32 {
auto data = _read<Size == Byte ? Word : Size>(_pc);
_pc += Size == Long ? 4 : 2;
return clip<Size>(data);
}
auto M68K::_dataRegister(DataRegister dr) -> string {
return {"d", dr.number};
}
auto M68K::_addressRegister(AddressRegister ar) -> string {
return {"a", ar.number};
}
template<uint Size> auto M68K::_immediate() -> string {
return {"#$", hex(_readPC<Size>(), 2 << Size)};
}
template<uint Size> auto M68K::_address(EffectiveAddress& ea) -> string {
if(ea.mode == 9) return {"$", hex(_pc + (int16)_readPC(), 6L)};
return "???";
}
template<uint Size> auto M68K::_effectiveAddress(EffectiveAddress& ea) -> string {
if(ea.mode == 0) return {_dataRegister(DataRegister{ea.reg})};
if(ea.mode == 1) return {_addressRegister(AddressRegister{ea.reg})};
if(ea.mode == 2) return {"(", _addressRegister(AddressRegister{ea.reg}), ")"};
if(ea.mode == 3) return {"(", _addressRegister(AddressRegister{ea.reg}), ")+"};
if(ea.mode == 4) return {"-(", _addressRegister(AddressRegister{ea.reg}), ")"};
if(ea.mode == 5) return {"($", hex(read(AddressRegister{ea.reg}) + (int16)_readPC(), 6L), ")"};
if(ea.mode == 7) return {"($", hex((int16)_readPC<Word>(), 6L), ")"};
if(ea.mode == 8) return {"($", hex(_readPC<Long>(), 6L), ")"};
if(ea.mode == 11) return {"#$", hex(_readPC<Size>(), 2 << Size)};
return "???";
}
auto M68K::_branch(uint8 displacement) -> string {
uint16 extension = _readPC();
_pc -= 2;
int32 offset = displacement ? sign<Byte>(displacement) : sign<Word>(extension);
return {"$", hex(_pc + offset, 6L)};
}
template<uint Size> auto M68K::_suffix() -> string {
return Size == Byte ? ".b" : Size == Word ? ".w" : ".l";
}
auto M68K::_condition(uint4 condition) -> string {
static const string conditions[16] = {
"t ", "f ", "hi", "ls", "cc", "cs", "ne", "eq",
"vc", "vs", "pl", "mi", "ge", "lt", "gt", "le",
};
return conditions[condition];
}
auto M68K::disassemble(uint32 pc) -> string {
uint16 opcode;
return {hex(_pc = pc, 6L), " ", hex(opcode = _readPC(), 4L), " ", disassembleTable[opcode]()};
}
auto M68K::disassembleRegisters() -> string {
return {
hex(r.d[0], 8L), " ", hex(r.d[1], 8L), " ", hex(r.d[2], 8L), " ", hex(r.d[3], 8L), " ",
hex(r.d[4], 8L), " ", hex(r.d[5], 8L), " ", hex(r.d[6], 8L), " ", hex(r.d[7], 8L), " ",
r.t ? "T" : "t",
r.s ? "S" : "s",
(uint)r.i,
r.c ? "C" : "c",
r.v ? "V" : "v",
r.z ? "Z" : "z",
r.n ? "N" : "n",
r.x ? "X" : "x", "\n",
hex(r.a[0], 8L), " ", hex(r.a[1], 8L), " ", hex(r.a[2], 8L), " ", hex(r.a[3], 8L), " ",
hex(r.a[4], 8L), " ", hex(r.a[5], 8L), " ", hex(r.a[6], 8L), " ", hex(r.a[7], 8L), " ", hex(r.sp, 8L)
};
}
//
template<uint Size> auto M68K::disassembleADD(DataRegister dr, uint1 direction, EffectiveAddress ea) -> string {
string op{"add", _suffix<Size>(), " "};
if(direction == 0) {
return {op, _effectiveAddress<Size>(ea), ",", _dataRegister(dr)};
} else {
return {op, "", _dataRegister(dr), ",", _effectiveAddress<Size>(ea)};
}
}
template<uint Size> auto M68K::disassembleADDA(AddressRegister ar, EffectiveAddress ea) -> string {
return {"adda", _suffix<Size>(), " ", _effectiveAddress<Size>(ea), ",", _addressRegister(ar)};
}
template<uint Size> auto M68K::disassembleADDI(EffectiveAddress ea) -> string {
return {"addi", _suffix<Size>(), " ", _immediate<Size>(), ",", _effectiveAddress<Size>(ea)};
}
template<uint Size> auto M68K::disassembleADDQ(uint4 immediate, EffectiveAddress modify) -> string {
return {"addq", _suffix<Size>(), " #", immediate, ",", _effectiveAddress<Size>(modify)};
}
template<uint Size> auto M68K::disassembleADDX(EffectiveAddress target, EffectiveAddress source) -> string {
return {"addx", _suffix<Size>(), " ", _effectiveAddress<Size>(target), ",", _effectiveAddress<Size>(source)};
}
template<uint Size> auto M68K::disassembleANDI(EffectiveAddress ea) -> string {
return {"andi", _suffix<Size>(), " ", _immediate<Size>(), ",", _effectiveAddress<Size>(ea)};
}
auto M68K::disassembleANDI_TO_CCR() -> string {
return {"andi ", _immediate<Byte>(), ",ccr"};
}
auto M68K::disassembleANDI_TO_SR() -> string {
return {"andi ", _immediate<Word>(), ",sr"};
}
template<uint Size> auto M68K::disassembleASL(uint4 shift, DataRegister modify) -> string {
return {"asl", _suffix<Size>(), " #", shift, ",", _dataRegister(modify)};
}
template<uint Size> auto M68K::disassembleASL(DataRegister shift, DataRegister modify) -> string {
return {"asl", _suffix<Size>(), " ", _dataRegister(shift), ",", _dataRegister(modify)};
}
auto M68K::disassembleASL(EffectiveAddress modify) -> string {
return {"asl", _suffix<Word>(), " ", _effectiveAddress<Word>(modify)};
}
template<uint Size> auto M68K::disassembleASR(uint4 shift, DataRegister modify) -> string {
return {"asr", _suffix<Size>(), " #", shift, ",", _dataRegister(modify)};
}
template<uint Size> auto M68K::disassembleASR(DataRegister shift, DataRegister modify) -> string {
return {"asr", _suffix<Size>(), " ", _dataRegister(shift), ",", _dataRegister(modify)};
}
auto M68K::disassembleASR(EffectiveAddress modify) -> string {
return {"asr", _suffix<Word>(), " ", _effectiveAddress<Word>(modify)};
}
auto M68K::disassembleBCC(uint4 condition, uint8 displacement) -> string {
auto cc = _condition(condition);
if(condition == 0) cc = "ra";
if(condition == 1) cc = "sr";
return {"b", cc, " ", _branch(displacement)};
}
template<uint Size> auto M68K::disassembleBTST(DataRegister dr, EffectiveAddress ea) -> string {
return {"btst ", _dataRegister(dr), ",", _effectiveAddress<Size>(ea)};
}
template<uint Size> auto M68K::disassembleBTST(EffectiveAddress ea) -> string {
return {"btst ", _immediate<Byte>(), ",", _effectiveAddress<Size>(ea)};
}
template<uint Size> auto M68K::disassembleCLR(EffectiveAddress ea) -> string {
return {"clr", _suffix<Size>(), " ", _effectiveAddress<Size>(ea)};
}
template<uint Size> auto M68K::disassembleCMP(DataRegister dr, EffectiveAddress ea) -> string {
return {"cmp", _suffix<Size>(), " ", _effectiveAddress<Size>(ea), ",", _dataRegister(dr)};
}
template<uint Size> auto M68K::disassembleCMPA(AddressRegister ar, EffectiveAddress ea) -> string {
return {"cmpa", _suffix<Size>(), " ", _effectiveAddress<Size>(ea), ",", _addressRegister(ar)};
}
template<uint Size> auto M68K::disassembleCMPI(EffectiveAddress ea) -> string {
return {"cmpi", _suffix<Size>(), " ", _immediate<Size>(), ",", _effectiveAddress<Size>(ea)};
}
template<uint Size> auto M68K::disassembleCMPM(EffectiveAddress ax, EffectiveAddress ay) -> string {
return {"cmpm", _suffix<Size>(), " ", _effectiveAddress<Size>(ay), ",", _effectiveAddress<Size>(ax)};
}
auto M68K::disassembleDBCC(uint4 condition, DataRegister dr) -> string {
auto base = _pc;
auto displacement = (int16)_readPC();
return {"db", _condition(condition), " ", _dataRegister(dr), ",$", hex(base + displacement, 6L)};
}
auto M68K::disassembleEORI_TO_CCR() -> string {
return {"eori ", _immediate<Byte>(), ",ccr"};
}
auto M68K::disassembleEORI_TO_SR() -> string {
return {"eori ", _immediate<Word>(), ",sr"};
}
auto M68K::disassembleJSR(EffectiveAddress target) -> string {
return {"jsr ", _effectiveAddress<Long>(target)};
}
auto M68K::disassembleLEA(AddressRegister ar, EffectiveAddress ea) -> string {
return {"lea ", _address<Long>(ea), ",", _addressRegister(ar)};
}
template<uint Size> auto M68K::disassembleLSL(uint4 immediate, DataRegister dr) -> string {
return {"lsl", _suffix<Size>(), " #", immediate, ",", _dataRegister(dr)};
}
template<uint Size> auto M68K::disassembleLSL(DataRegister sr, DataRegister dr) -> string {
return {"lsl", _suffix<Size>(), " ", _dataRegister(sr), ",", _dataRegister(dr)};
}
auto M68K::disassembleLSL(EffectiveAddress ea) -> string {
return {"lsl", _suffix<Word>(), " ", _effectiveAddress<Word>(ea)};
}
template<uint Size> auto M68K::disassembleLSR(uint4 immediate, DataRegister dr) -> string {
return {"lsr", _suffix<Size>(), " #", immediate, ",", _dataRegister(dr)};
}
template<uint Size> auto M68K::disassembleLSR(DataRegister shift, DataRegister dr) -> string {
return {"lsr", _suffix<Size>(), " ", _dataRegister(shift), ",", _dataRegister(dr)};
}
auto M68K::disassembleLSR(EffectiveAddress ea) -> string {
return {"lsr", _suffix<Word>(), " ", _effectiveAddress<Word>(ea)};
}
template<uint Size> auto M68K::disassembleMOVE(EffectiveAddress to, EffectiveAddress from) -> string {
return {"move", _suffix<Size>(), " ", _effectiveAddress<Size>(from), ",", _effectiveAddress<Size>(to)};
}
template<uint Size> auto M68K::disassembleMOVEA(AddressRegister ar, EffectiveAddress ea) -> string {
return {"movea ", _effectiveAddress<Size>(ea), ",", _addressRegister(ar)};
}
template<uint Size> auto M68K::disassembleMOVEM(uint1 direction, EffectiveAddress ea) -> string {
string op{"movem", _suffix<Size>(), " "};
uint16 list = _readPC();
string regs;
for(uint n : range(8)) if(list.bit(0 + n)) regs.append(_dataRegister(DataRegister{n}), ",");
regs.trimRight(",");
if(regs && list >> 8) regs.append("/");
for(uint n : range(8)) if(list.bit(8 + n)) regs.append(_addressRegister(AddressRegister{n}), ",");
regs.trimRight(",");
if(direction == 0) {
return {op, regs, ",", _effectiveAddress<Size>(ea)};
} else {
return {op, _effectiveAddress<Size>(ea), ",", regs};
}
}
auto M68K::disassembleMOVEQ(DataRegister dr, uint8 immediate) -> string {
return {"moveq #$", hex(immediate, 2L), ",", _dataRegister(dr)};
}
auto M68K::disassembleMOVE_FROM_SR(EffectiveAddress ea) -> string {
return {"move sr,", _effectiveAddress<Word>(ea)};
}
auto M68K::disassembleMOVE_TO_CCR(EffectiveAddress ea) -> string {
return {"move ", _effectiveAddress<Byte>(ea), ",ccr"};
}
auto M68K::disassembleMOVE_TO_SR(EffectiveAddress ea) -> string {
return {"move ", _effectiveAddress<Word>(ea), ",sr"};
}
auto M68K::disassembleMOVE_USP(uint1 direction, AddressRegister ar) -> string {
if(direction == 0) {
return {"move ", _addressRegister(ar), ",usp"};
} else {
return {"move usp,", _addressRegister(ar)};
}
}
auto M68K::disassembleNOP() -> string {
return {"nop "};
}
auto M68K::disassembleORI_TO_CCR() -> string {
return {"ori ", _immediate<Byte>(), ",ccr"};
}
auto M68K::disassembleORI_TO_SR() -> string {
return {"ori ", _immediate<Word>(), ",sr"};
}
template<uint Size> auto M68K::disassembleROL(uint4 shift, DataRegister modify) -> string {
return {"rol", _suffix<Size>(), " #", shift, ",", _dataRegister(modify)};
}
template<uint Size> auto M68K::disassembleROL(DataRegister shift, DataRegister modify) -> string {
return {"rol", _suffix<Size>(), " ", _dataRegister(shift), ",", _dataRegister(modify)};
}
auto M68K::disassembleROL(EffectiveAddress modify) -> string {
return {"rol", _suffix<Word>(), " ", _effectiveAddress<Word>(modify)};
}
template<uint Size> auto M68K::disassembleROR(uint4 shift, DataRegister modify) -> string {
return {"ror", _suffix<Size>(), " #", shift, ",", _dataRegister(modify)};
}
template<uint Size> auto M68K::disassembleROR(DataRegister shift, DataRegister modify) -> string {
return {"ror", _suffix<Size>(), " ", _dataRegister(shift) ,",", _dataRegister(modify)};
}
auto M68K::disassembleROR(EffectiveAddress modify) -> string {
return {"ror", _suffix<Word>(), " ", _effectiveAddress<Word>(modify)};
}
template<uint Size> auto M68K::disassembleROXL(uint4 shift, DataRegister modify) -> string {
return {"roxl", _suffix<Size>(), " #", shift, ",", _dataRegister(modify)};
}
template<uint Size> auto M68K::disassembleROXL(DataRegister shift, DataRegister modify) -> string {
return {"roxl", _suffix<Size>(), " ", _dataRegister(shift), ",", _dataRegister(modify)};
}
auto M68K::disassembleROXL(EffectiveAddress modify) -> string {
return {"roxl", _suffix<Word>(), " ", _effectiveAddress<Word>(modify)};
}
template<uint Size> auto M68K::disassembleROXR(uint4 shift, DataRegister modify) -> string {
return {"roxr", _suffix<Size>(), " #", shift, ",", _dataRegister(modify)};
}
template<uint Size> auto M68K::disassembleROXR(DataRegister shift, DataRegister modify) -> string {
return {"roxr", _suffix<Size>(), " ", _dataRegister(shift), ",", _dataRegister(modify)};
}
auto M68K::disassembleROXR(EffectiveAddress modify) -> string {
return {"roxr", _suffix<Word>(), " ", _effectiveAddress<Word>(modify)};
}
auto M68K::disassembleRTS() -> string {
return {"rts "};
}
template<uint Size> auto M68K::disassembleSUB(EffectiveAddress source, DataRegister target) -> string {
return {"sub", _suffix<Size>(), " ", _effectiveAddress<Size>(source), ",", _dataRegister(target)};
}
template<uint Size> auto M68K::disassembleSUB(DataRegister source, EffectiveAddress target) -> string {
return {"sub", _suffix<Size>(), " ", _dataRegister(source), ",", _effectiveAddress<Size>(target)};
}
template<uint Size> auto M68K::disassembleSUBQ(uint4 immediate, EffectiveAddress ea) -> string {
return {"subq", _suffix<Size>(), " #", immediate, _effectiveAddress<Size>(ea)};
}
template<uint Size> auto M68K::disassembleTST(EffectiveAddress ea) -> string {
return {"tst", _suffix<Size>(), " ", _effectiveAddress<Size>(ea)};
}

View File

@@ -0,0 +1,207 @@
template<uint Size> auto M68K::fetch(EffectiveAddress& ea) -> uint32 {
if(!ea.valid.raise()) return ea.address;
switch(ea.mode) {
case DataRegisterDirect: {
return read(DataRegister{ea.reg});
}
case AddressRegisterDirect: {
return read(AddressRegister{ea.reg});
}
case AddressRegisterIndirect: {
return read(AddressRegister{ea.reg});
}
case AddressRegisterIndirectWithPostIncrement: {
return read(AddressRegister{ea.reg});
}
case AddressRegisterIndirectWithPreDecrement: {
return read(AddressRegister{ea.reg});
}
case AddressRegisterIndirectWithDisplacement: {
return read(AddressRegister{ea.reg}) + (int16)readPC();
}
case AddressRegisterIndirectWithIndex: {
auto extension = readPC();
auto index = extension & 0x8000
? read(AddressRegister{extension >> 12})
: read(DataRegister{extension >> 12});
if(extension & 0x800) index = (int16)index;
return read(AddressRegister{ea.reg}) + index + (int8)extension;
}
case AbsoluteShortIndirect: {
return (int16)readPC();
}
case AbsoluteLongIndirect: {
return readPC<Long>();
}
case ProgramCounterIndirectWithDisplacement: {
auto base = r.pc;
return base + (int16)readPC();
}
case ProgramCounterIndirectWithIndex: {
auto base = r.pc;
auto extension = readPC();
auto index = extension & 0x8000
? read(AddressRegister{extension >> 12})
: read(DataRegister{extension >> 12});
if(extension & 0x800) index = (int16)index;
return base + index + (int8)extension;
}
case Immediate: {
return readPC<Size>();
}
}
return 0;
}
template<uint Size, bool Update> auto M68K::read(EffectiveAddress& ea) -> uint32 {
ea.address = fetch<Size>(ea);
switch(ea.mode) {
case DataRegisterDirect: {
return clip<Size>(ea.address);
}
case AddressRegisterDirect: {
return clip<Size>(ea.address);
}
case AddressRegisterIndirect: {
return read<Size>(ea.address);
}
case AddressRegisterIndirectWithPostIncrement: {
auto data = read<Size>(ea.address);
if(Update) write(AddressRegister{ea.reg}, ea.address += bytes<Size>());
return data;
}
case AddressRegisterIndirectWithPreDecrement: {
auto data = read<Size>(ea.address - bytes<Size>());
if(Update) write(AddressRegister{ea.reg}, ea.address -= bytes<Size>());
return data;
}
case AddressRegisterIndirectWithDisplacement: {
return read<Size>(ea.address);
}
case AddressRegisterIndirectWithIndex: {
return read<Size>(ea.address);
}
case AbsoluteShortIndirect: {
return read<Size>(ea.address);
}
case AbsoluteLongIndirect: {
return read<Size>(ea.address);
}
case ProgramCounterIndirectWithDisplacement: {
return read<Size>(ea.address);
}
case ProgramCounterIndirectWithIndex: {
return read<Size>(ea.address);
}
case Immediate: {
return clip<Size>(ea.address);
}
}
return 0;
}
template<uint Size, bool Update> auto M68K::write(EffectiveAddress& ea, uint32 data) -> void {
ea.address = fetch<Size>(ea);
switch(ea.mode) {
case DataRegisterDirect: {
return write<Size>(DataRegister{ea.reg}, data);
}
case AddressRegisterDirect: {
return write<Size>(AddressRegister{ea.reg}, data);
}
case AddressRegisterIndirect: {
return write<Size>(ea.address, data);
}
case AddressRegisterIndirectWithPostIncrement: {
write<Size>(ea.address, data);
if(Update) write(AddressRegister{ea.reg}, ea.address += bytes<Size>());
return;
}
case AddressRegisterIndirectWithPreDecrement: {
write<Size, Reverse>(ea.address - bytes<Size>(), data);
if(Update) write(AddressRegister{ea.reg}, ea.address -= bytes<Size>());
return;
}
case AddressRegisterIndirectWithDisplacement: {
return write<Size>(ea.address, data);
}
case AddressRegisterIndirectWithIndex: {
return write<Size>(ea.address, data);
}
case AbsoluteShortIndirect: {
return write<Size>(ea.address, data);
}
case AbsoluteLongIndirect: {
return write<Size>(ea.address, data);
}
case ProgramCounterIndirectWithDisplacement: {
return write<Size>(ea.address, data);
}
case ProgramCounterIndirectWithIndex: {
return write<Size>(ea.address, data);
}
case Immediate: {
return;
}
}
}
template<uint Size> auto M68K::flush(EffectiveAddress& ea, uint32 data) -> void {
switch(ea.mode) {
case AddressRegisterIndirectWithPostIncrement: {
write<Size>(AddressRegister{ea.reg}, data);
return;
}
case AddressRegisterIndirectWithPreDecrement: {
write<Size>(AddressRegister{ea.reg}, data);
return;
}
}
}

View File

@@ -0,0 +1,738 @@
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");
//}
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(uint1 direction : range(2))
for(uint3 mode : range(8))
for(uint3 reg : range(8)) {
auto opcode = pattern("1101 ---- ++-- ----") | dreg << 9 | direction << 8 | mode << 3 | reg << 0;
if(direction == 1 && (mode == 0 || mode == 1 || (mode == 7 && reg >= 2))) continue;
DataRegister dr{dreg};
EffectiveAddress ea{mode, reg};
bind(opcode | 0 << 6, ADD<Byte>, dr, direction, ea);
bind(opcode | 1 << 6, ADD<Word>, dr, direction, ea);
bind(opcode | 2 << 6, ADD<Long>, dr, direction, ea);
if(direction == 0 && mode == 1) unbind(opcode | 0 << 6);
}
//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);
}
//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);
}
//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);
}
//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 == 2 || reg >= 5))) 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);
}
//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);
}
//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;
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_USP
for(uint1 direction : range(2))
for(uint3 areg : range(8)) {
auto opcode = pattern("0100 1110 0110 ----") | direction << 3 | areg << 0;
AddressRegister ar{areg};
bind(opcode, MOVE_USP, direction, ar);
}
//NOP
{ auto opcode = pattern("0100 1110 0111 0001");
bind(opcode, NOP);
}
//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);
}
//RTS
{ auto opcode = pattern("0100 1110 0111 0101");
bind(opcode, RTS);
}
//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);
}
//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);
}
//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);
}
#undef bind
#undef unbind
#undef pattern
uint unimplemented = 0;
for(uint16 opcode : range(65536)) {
if(instructionTable[opcode]) continue;
instructionTable[opcode] = [=] { trap(); };
disassembleTable[opcode] = [=] { return string{"???"}; };
unimplemented++;
}
print("[M68K] unimplemented opcodes: ", unimplemented, "\n");
}

View File

@@ -0,0 +1,645 @@
auto M68K::testCondition(uint4 condition) -> bool {
switch(condition) {
case 0: return true; //T
case 1: return false; //F
case 2: return !r.c && !r.z; //HI
case 3: return r.c || r.z; //LS
case 4: return !r.c; //CC,HS
case 5: return r.c; //CS,LO
case 6: return !r.z; //NE
case 7: return r.z; //EQ
case 8: return !r.v; //VC
case 9: return r.v; //VS
case 10: return !r.n; //PL
case 11: return r.n; //MI
case 12: return r.n == r.v; //GE
case 13: return r.n != r.v; //LT
case 14: return r.n == r.v && !r.z; //GT
case 15: return r.n != r.v || r.z; //LE
}
unreachable;
}
//
template<> auto M68K::bytes<Byte>() -> uint { return 1; }
template<> auto M68K::bytes<Word>() -> uint { return 2; }
template<> auto M68K::bytes<Long>() -> uint { return 4; }
template<> auto M68K::bits<Byte>() -> uint { return 8; }
template<> auto M68K::bits<Word>() -> uint { return 16; }
template<> auto M68K::bits<Long>() -> uint { return 32; }
template<uint Size> auto M68K::lsb() -> uint32 { return 1; }
template<> auto M68K::msb<Byte>() -> uint32 { return 0x80; }
template<> auto M68K::msb<Word>() -> uint32 { return 0x8000; }
template<> auto M68K::msb<Long>() -> uint32 { return 0x80000000; }
template<> auto M68K::mask<Byte>() -> uint32 { return 0xff; }
template<> auto M68K::mask<Word>() -> uint32 { return 0xffff; }
template<> auto M68K::mask<Long>() -> uint32 { return 0xffffffff; }
template<> auto M68K::clip<Byte>(uint32 data) -> uint32 { return data & 0xff; }
template<> auto M68K::clip<Word>(uint32 data) -> uint32 { return data & 0xffff; }
template<> auto M68K::clip<Long>(uint32 data) -> uint32 { return data & 0xffffffff; }
template<> auto M68K::sign<Byte>(uint32 data) -> int32 { return (int8)data; }
template<> auto M68K::sign<Word>(uint32 data) -> int32 { return (int16)data; }
template<> auto M68K::sign<Long>(uint32 data) -> int32 { return (int32)data; }
template<uint Size> auto M68K::zero(uint32 result) -> bool {
return clip<Size>(result) == 0;
}
template<uint Size> auto M68K::negative(uint32 result) -> bool {
return sign<Size>(result) < 0;
}
//
template<uint Size, bool Extend> auto M68K::ADD(uint32 source, uint32 target) -> uint32 {
uint64 result = (uint64)source + (uint64)target;
if(Extend) result += r.x;
r.c = sign<Size>(result >> 1) < 0;
r.v = sign<Size>(~(target ^ source) & (target ^ result)) < 0;
if(Extend == 0) r.z = clip<Size>(result) == 0;
if(Extend == 1) if(clip<Size>(result)) r.z = 0;
r.n = sign<Size>(result) < 0;
r.x = r.c;
return clip<Size>(result);
}
template<uint Size> auto M68K::instructionADD(DataRegister dr, uint1 direction, EffectiveAddress ea) -> void {
if(direction == 0) {
auto source = read<Size>(ea);
auto target = read<Size>(dr);
auto result = ADD<Size>(source, target);
write<Size>(dr, result);
} else {
auto source = read<Size>(dr);
auto target = read<Size>(ea);
auto result = ADD<Size>(source, target);
write<Size>(ea, result);
}
}
template<uint Size> auto M68K::instructionADDI(EffectiveAddress modify) -> void {
auto source = readPC<Size>();
auto target = read<Size>(modify);
auto result = ADD<Size>(source, target);
write<Size>(modify, result);
}
template<uint Size> auto M68K::instructionADDA(AddressRegister ar, EffectiveAddress ea) -> void {
auto source = read<Size>(ea);
auto target = read<Size>(ar);
write<Long>(ar, source + target);
}
template<uint Size> auto M68K::instructionADDQ(uint4 immediate, EffectiveAddress modify) -> void {
auto source = read<Size>(modify);
auto target = immediate;
auto result = ADD<Size>(source, target);
write<Size>(modify, result);
}
template<uint Size> auto M68K::instructionADDX(EffectiveAddress target_, EffectiveAddress source_) -> void {
auto source = read<Size>(source_);
auto target = read<Size>(target_);
auto result = ADD<Size, Extend>(source, target);
write<Size>(target, result);
}
template<uint Size> auto M68K::instructionANDI(EffectiveAddress ea) -> void {
auto source = readPC<Size>();
auto target = read<Size, NoUpdate>(ea);
auto result = target & source;
write<Size>(ea, result);
r.c = 0;
r.v = 0;
r.z = zero<Size>(result);
r.n = negative<Size>(result);
}
auto M68K::instructionANDI_TO_CCR() -> void {
auto data = readPC<Word>();
writeCCR(readCCR() & data);
}
auto M68K::instructionANDI_TO_SR() -> void {
if(!supervisor()) return;
auto data = readPC<Word>();
writeSR(readSR() & data);
}
template<uint Size> auto M68K::ASL(uint32 result, uint shift) -> uint32 {
bool carry = false;
uint32 overflow = 0;
for(auto _ : range(shift)) {
carry = result & msb<Size>();
uint32 before = result;
result <<= 1;
overflow |= before ^ result;
}
r.c = carry;
r.v = sign<Size>(overflow) < 0;
r.z = clip<Size>(result) == 0;
r.n = sign<Size>(result) < 0;
if(shift) r.x = r.c;
return clip<Size>(result);
}
template<uint Size> auto M68K::instructionASL(uint4 shift, DataRegister modify) -> void {
auto result = ASL<Size>(read<Size>(modify), shift);
write<Size>(modify, result);
}
template<uint Size> auto M68K::instructionASL(DataRegister shift, DataRegister modify) -> void {
auto count = read<Long>(shift) & 63;
auto result = ASL<Size>(read<Size>(modify), count);
write<Size>(modify, result);
}
auto M68K::instructionASL(EffectiveAddress modify) -> void {
auto result = ASL<Word>(read<Word, NoUpdate>(modify), 1);
write<Word>(modify, result);
}
template<uint Size> auto M68K::ASR(uint32 result, uint shift) -> uint32 {
bool carry = false;
uint32 overflow = 0;
for(auto _ : range(shift)) {
carry = result & lsb<Size>();
uint32 before = result;
result = sign<Size>(result) >> 1;
overflow |= before ^ result;
}
r.c = carry;
r.v = sign<Size>(overflow) < 0;
r.z = clip<Size>(result) == 0;
r.n = sign<Size>(result) < 0;
if(shift) r.x = r.c;
return clip<Size>(result);
}
template<uint Size> auto M68K::instructionASR(uint4 shift, DataRegister modify) -> void {
auto result = ASR<Size>(read<Size>(modify), shift);
write<Size>(modify, result);
}
template<uint Size> auto M68K::instructionASR(DataRegister shift, DataRegister modify) -> void {
auto count = read<Long>(shift) & 63;
auto result = ASR<Size>(read<Size>(modify), count);
write<Size>(modify, result);
}
auto M68K::instructionASR(EffectiveAddress modify) -> void {
auto result = ASR<Word>(read<Word, NoUpdate>(modify), 1);
write<Word>(modify, result);
}
auto M68K::instructionBCC(uint4 condition, uint8 displacement) -> void {
auto extension = readPC<Word>();
if(condition == 1) push<Long>(r.pc);
if(condition >= 2 && !testCondition(condition)) { //0 = BRA; 1 = BSR
if(displacement) r.pc -= 2;
} else {
r.pc -= 2;
r.pc += displacement ? sign<Byte>(displacement) : sign<Word>(extension);
}
}
template<uint Size> auto M68K::instructionBTST(DataRegister dr, EffectiveAddress ea) -> void {
auto bit = read<Size>(dr);
auto test = read<Size>(ea);
bit &= bits<Size>() - 1;
r.z = test.bit(bit) == 0;
}
template<uint Size> auto M68K::instructionBTST(EffectiveAddress ea) -> void {
auto bit = (uint8)readPC<Word>();
auto test = read<Size>(ea);
bit &= bits<Size>() - 1;
r.z = test.bit(bit) == 0;
}
template<uint Size> auto M68K::instructionCLR(EffectiveAddress ea) -> void {
read<Size>(ea);
write<Size>(ea, 0);
r.c = 0;
r.v = 0;
r.z = 1;
r.n = 0;
}
template<uint Size> auto M68K::CMP(uint32 source, uint32 target) -> uint32 {
uint64 result = (uint64)target - (uint64)source;
r.c = sign<Size>(result >> 1) < 0;
r.v = sign<Size>((target ^ source) & (target ^ result)) < 0;
r.z = clip<Size>(result) == 0;
r.n = sign<Size>(result) < 0;
return clip<Size>(result);
}
template<uint Size> auto M68K::instructionCMP(DataRegister dr, EffectiveAddress ea) -> void {
auto source = read<Size>(ea);
auto target = read<Size>(dr);
CMP<Size>(source, target);
}
template<uint Size> auto M68K::instructionCMPA(AddressRegister ar, EffectiveAddress ea) -> void {
auto source = read<Size>(ea);
auto target = read<Size>(ar);
CMP<Size>(source, target);
}
template<uint Size> auto M68K::instructionCMPI(EffectiveAddress ea) -> void {
auto source = readPC<Size>();
auto target = read<Size>(ea);
CMP<Size>(source, target);
}
template<uint Size> auto M68K::instructionCMPM(EffectiveAddress ax, EffectiveAddress ay) -> void {
auto source = read<Size>(ay);
auto target = read<Size>(ax);
CMP<Size>(source, target);
}
auto M68K::instructionDBCC(uint4 condition, DataRegister dr) -> void {
auto displacement = readPC<Word>();
if(!testCondition(condition)) {
uint16 result = read<Word>(dr);
write<Word>(dr, result - 1);
if(result) r.pc -= 2, r.pc += sign<Word>(displacement);
}
}
auto M68K::instructionEORI_TO_CCR() -> void {
auto data = readPC<Word>();
writeCCR(readCCR() ^ data);
}
auto M68K::instructionEORI_TO_SR() -> void {
if(!supervisor()) return;
auto data = readPC<Word>();
writeSR(readSR() ^ data);
}
auto M68K::instructionJSR(EffectiveAddress target) -> void {
push<Long>(r.pc);
r.pc = fetch<Long>(target);
}
auto M68K::instructionLEA(AddressRegister ar, EffectiveAddress ea) -> void {
write<Long>(ar, fetch<Long>(ea));
}
template<uint Size> auto M68K::LSL(uint32 result, uint shift) -> uint32 {
bool carry = false;
for(auto _ : range(shift)) {
carry = result & msb<Size>();
result <<= 1;
}
r.c = carry;
r.v = 0;
r.z = clip<Size>(result) == 0;
r.n = sign<Size>(result) < 0;
if(shift) r.x = r.c;
return clip<Size>(result);
}
template<uint Size> auto M68K::instructionLSL(uint4 immediate, DataRegister dr) -> void {
auto result = LSL<Size>(read<Size>(dr), immediate);
write<Size>(dr, result);
}
template<uint Size> auto M68K::instructionLSL(DataRegister sr, DataRegister dr) -> void {
auto shift = read<Long>(sr) & 63;
auto result = LSL<Size>(read<Size>(dr), shift);
write<Size>(dr, result);
}
auto M68K::instructionLSL(EffectiveAddress ea) -> void {
auto result = LSL<Word>(read<Word, NoUpdate>(ea), 1);
write<Word>(ea, result);
}
template<uint Size> auto M68K::LSR(uint32 result, uint shift) -> uint32 {
bool carry = false;
for(auto _ : range(shift)) {
carry = result & lsb<Size>();
result >>= 1;
}
r.c = carry;
r.v = 0;
r.z = clip<Size>(result) == 0;
r.n = sign<Size>(result) < 0;
if(shift) r.x = r.c;
return clip<Size>(result);
}
template<uint Size> auto M68K::instructionLSR(uint4 immediate, DataRegister dr) -> void {
auto result = LSR<Size>(read<Size>(dr), immediate);
write<Size>(dr, result);
}
template<uint Size> auto M68K::instructionLSR(DataRegister shift, DataRegister dr) -> void {
auto count = read<Long>(shift) & 63;
auto result = LSR<Size>(read<Size>(dr), count);
write<Size>(dr, result);
}
auto M68K::instructionLSR(EffectiveAddress ea) -> void {
auto result = LSR<Word>(read<Word, NoUpdate>(ea), 1);
write<Word>(ea, result);
}
template<uint Size> auto M68K::instructionMOVE(EffectiveAddress to, EffectiveAddress from) -> void {
auto data = read<Size>(from);
write<Size>(to, data);
r.c = 0;
r.v = 0;
r.z = zero<Size>(data);
r.n = negative<Size>(data);
}
template<uint Size> auto M68K::instructionMOVEA(AddressRegister ar, EffectiveAddress ea) -> void {
auto data = read<Size>(ea);
write<Long>(ar, data);
}
template<uint Size> auto M68K::instructionMOVEM(uint1 direction, EffectiveAddress ea) -> void {
auto list = readPC();
auto addr = fetch<Long>(ea);
for(uint n : range(16)) {
if(!list.bit(n)) continue;
//pre-decrement mode traverses registers in reverse order {A7-A0, D7-D0}
uint index = ea.mode == AddressRegisterIndirectWithPreDecrement ? 15 - n : n;
if(ea.mode == AddressRegisterIndirectWithPreDecrement) addr -= bytes<Size>();
if(direction == 0) {
auto data = index < 8 ? read<Size>(DataRegister{index}) : read<Size>(AddressRegister{index});
write<Size>(addr, data);
} else {
auto data = read<Size>(addr);
data = sign<Size>(data);
index < 8 ? write<Long>(DataRegister{index}, data) : write<Long>(AddressRegister{index}, data);
}
if(ea.mode == AddressRegisterIndirectWithPostIncrement) addr += bytes<Size>();
}
flush<Long>(ea, addr);
}
auto M68K::instructionMOVEQ(DataRegister dr, uint8 immediate) -> void {
write<Long>(dr, immediate);
r.c = 0;
r.v = 0;
r.z = zero<Byte>(immediate);
r.n = negative<Byte>(immediate);
}
auto M68K::instructionMOVE_FROM_SR(EffectiveAddress ea) -> void {
auto data = readSR();
write<Word>(ea, data);
}
auto M68K::instructionMOVE_TO_CCR(EffectiveAddress ea) -> void {
auto data = read<Byte>(ea);
writeCCR(data);
}
auto M68K::instructionMOVE_TO_SR(EffectiveAddress ea) -> void {
if(!supervisor()) return;
auto data = read<Word>(ea);
writeSR(data);
}
auto M68K::instructionMOVE_USP(uint1 direction, AddressRegister ar) -> void {
if(!supervisor()) return;
if(direction == 0) {
r.sp = read<Long>(ar);
} else {
write<Long>(ar, r.sp);
}
}
auto M68K::instructionNOP() -> void {
}
auto M68K::instructionORI_TO_CCR() -> void {
auto data = readPC<Word>();
writeCCR(readCCR() | data);
}
auto M68K::instructionORI_TO_SR() -> void {
if(!supervisor()) return;
auto data = readPC<Word>();
writeSR(readSR() | data);
}
template<uint Size> auto M68K::ROL(uint32 result, uint shift) -> uint32 {
bool carry = false;
for(auto _ : range(shift)) {
carry = result & msb<Size>();
result = result << 1 | carry;
}
r.c = carry;
r.v = 0;
r.z = clip<Size>(result) == 0;
r.n = sign<Size>(result) < 0;
return clip<Size>(result);
}
template<uint Size> auto M68K::instructionROL(uint4 shift, DataRegister modify) -> void {
auto result = ROL<Size>(read<Size>(modify), shift);
write<Size>(modify, result);
}
template<uint Size> auto M68K::instructionROL(DataRegister shift, DataRegister modify) -> void {
auto count = read<Long>(shift) & 63;
auto result = ROL<Size>(read<Size>(modify), count);
write<Size>(modify, result);
}
auto M68K::instructionROL(EffectiveAddress modify) -> void {
auto result = ROL<Word>(read<Word, NoUpdate>(modify), 1);
write<Word>(modify, result);
}
template<uint Size> auto M68K::ROR(uint32 result, uint shift) -> uint32 {
bool carry = false;
for(auto _ : range(shift)) {
carry = result & lsb<Size>();
result >>= 1;
if(carry) result |= msb<Size>();
}
r.c = carry;
r.v = 0;
r.z = clip<Size>(result) == 0;
r.n = sign<Size>(result) < 0;
return clip<Size>(result);
}
template<uint Size> auto M68K::instructionROR(uint4 shift, DataRegister modify) -> void {
auto result = ROR<Size>(read<Size>(modify), shift);
write<Size>(modify, result);
}
template<uint Size> auto M68K::instructionROR(DataRegister shift, DataRegister modify) -> void {
auto count = read<Long>(shift) & 63;
auto result = ROR<Size>(read<Size>(modify), count);
write<Size>(modify, result);
}
auto M68K::instructionROR(EffectiveAddress modify) -> void {
auto result = ROR<Word>(read<Word, NoUpdate>(modify), 1);
write<Word>(modify, result);
}
template<uint Size> auto M68K::ROXL(uint32 result, uint shift) -> uint32 {
bool carry = r.x;
for(auto _ : range(shift)) {
bool extend = carry;
carry = result & msb<Size>();
result = result << 1 | extend;
}
r.c = carry;
r.v = 0;
r.z = clip<Size>(result) == 0;
r.n = sign<Size>(result) < 0;
r.x = r.c;
return clip<Size>(result);
}
template<uint Size> auto M68K::instructionROXL(uint4 shift, DataRegister modify) -> void {
auto result = ROXL<Size>(read<Size>(modify), shift);
write<Size>(modify, result);
}
template<uint Size> auto M68K::instructionROXL(DataRegister shift, DataRegister modify) -> void {
auto count = read<Long>(shift) & 63;
auto result = ROXL<Size>(read<Size>(modify), count);
write<Size>(modify, result);
}
auto M68K::instructionROXL(EffectiveAddress modify) -> void {
auto result = ROXL<Word>(read<Word, NoUpdate>(modify), 1);
write<Word>(modify, result);
}
template<uint Size> auto M68K::ROXR(uint32 result, uint shift) -> uint32 {
bool carry = r.x;
for(auto _ : range(shift)) {
bool extend = carry;
carry = result & lsb<Size>();
result >>= 1;
if(extend) result |= msb<Size>();
}
r.c = carry;
r.v = 0;
r.z = clip<Size>(result) == 0;
r.n = sign<Size>(result) < 0;
r.x = r.c;
return clip<Size>(result);
}
template<uint Size> auto M68K::instructionROXR(uint4 shift, DataRegister modify) -> void {
auto result = ROXR<Size>(read<Size>(modify), shift);
write<Size>(modify, result);
}
template<uint Size> auto M68K::instructionROXR(DataRegister shift, DataRegister modify) -> void {
auto count = read<Long>(shift) & 63;
auto result = ROXR<Size>(read<Size>(modify), count);
write<Size>(modify, result);
}
auto M68K::instructionROXR(EffectiveAddress modify) -> void {
auto result = ROXR<Word>(read<Word, NoUpdate>(modify), 1);
write<Word>(modify, result);
}
auto M68K::instructionRTS() -> void {
r.pc = pop<Long>();
}
template<uint Size, bool Extend> auto M68K::SUB(uint32 source, uint32 target) -> uint32 {
uint64 result = source - target;
if(Extend) result -= r.x;
r.c = sign<Size>(result >> 1) < 0;
r.v = sign<Size>((target ^ source) & (target ^ result)) < 0;
if(Extend == 0) r.z = clip<Size>(result == 0);
if(Extend == 1) if(clip<Size>(result)) r.z = 0;
r.n = sign<Size>(result) < 0;
r.x = r.c;
return result;
}
template<uint Size> auto M68K::instructionSUB(EffectiveAddress source_, DataRegister target_) -> void {
auto source = read<Size>(source_);
auto target = read<Size>(target_);
auto result = SUB<Size>(source, target);
write<Size>(target_, result);
}
template<uint Size> auto M68K::instructionSUB(DataRegister source_, EffectiveAddress target_) -> void {
auto source = read<Size>(source_);
auto target = read<Size>(target_);
auto result = SUB<Size>(source, target);
write<Size>(target_, result);
}
template<uint Size> auto M68K::instructionSUBQ(uint4 immediate, EffectiveAddress ea) -> void {
auto source = immediate;
auto target = read<Size, NoUpdate>(ea);
auto result = SUB<Size>(source, target);
write<Size>(ea, result);
}
template<uint Size> auto M68K::instructionTST(EffectiveAddress ea) -> void {
auto data = read<Size>(ea);
r.c = 0;
r.v = 0;
r.z = zero<Size>(data);
r.n = negative<Size>(data);
}

View File

@@ -0,0 +1,43 @@
#include <processor/processor.hpp>
#include "m68k.hpp"
namespace Processor {
enum : uint { Byte, Word, Long };
enum : bool { Reverse = 1 };
#include "registers.cpp"
#include "memory.cpp"
#include "effective-address.cpp"
#include "instruction.cpp"
#include "instructions.cpp"
#include "disassembler.cpp"
auto M68K::power() -> void {
}
auto M68K::reset() -> void {
instructionsExecuted = 0;
for(auto& dr : r.d) dr = 0;
for(auto& ar : r.a) ar = 0;
r.sp = 0;
r.pc = 0;
r.c = 0;
r.v = 0;
r.z = 0;
r.n = 0;
r.x = 0;
r.i = 7;
r.s = 1;
r.t = 0;
}
auto M68K::supervisor() -> bool {
if(r.s) return true;
//todo: raise TRAP exception
return false;
}
}

View File

@@ -0,0 +1,277 @@
#pragma once
//Motorola M68000
namespace Processor {
struct M68K {
enum : bool { User, Supervisor };
enum : uint { Byte, Word, Long };
enum : bool { NoUpdate = 0, Reverse = 1, Extend = 1 };
enum : uint {
DataRegisterDirect,
AddressRegisterDirect,
AddressRegisterIndirect,
AddressRegisterIndirectWithPostIncrement,
AddressRegisterIndirectWithPreDecrement,
AddressRegisterIndirectWithDisplacement,
AddressRegisterIndirectWithIndex,
AbsoluteShortIndirect,
AbsoluteLongIndirect,
ProgramCounterIndirectWithDisplacement,
ProgramCounterIndirectWithIndex,
Immediate,
};
M68K();
virtual auto step(uint clocks) -> void = 0;
virtual auto read(bool word, uint24 addr) -> uint16 = 0;
virtual auto write(bool word, uint24 addr, uint16 data) -> void = 0;
auto power() -> void;
auto reset() -> void;
auto supervisor() -> bool;
//registers.cpp
struct DataRegister {
explicit DataRegister(uint number_) : number(number_) {}
uint3 number;
};
template<uint Size = Long> auto read(DataRegister reg) -> uint32;
template<uint Size = Long> auto write(DataRegister reg, uint32 data) -> void;
struct AddressRegister {
explicit AddressRegister(uint number_) : number(number_) {}
uint3 number;
};
template<uint Size = Long> auto read(AddressRegister reg) -> uint32;
template<uint Size = Long> auto write(AddressRegister reg, uint32 data) -> void;
auto readCCR() -> uint8;
auto readSR() -> uint16;
auto writeCCR(uint8 ccr) -> void;
auto writeSR(uint16 sr) -> void;
//memory.cpp
template<uint Size> auto read(uint32 addr) -> uint32;
template<uint Size, bool Order = 0> auto write(uint32 addr, uint32 data) -> void;
template<uint Size = Word> auto readPC() -> uint32;
template<uint Size> auto pop() -> uint32;
template<uint Size> auto push(uint32 data) -> void;
//effective-address.cpp
struct EffectiveAddress {
explicit EffectiveAddress(uint mode_, uint reg_) : mode(mode_), reg(reg_) {
if(mode == 7) mode += reg; //optimization: convert modes {7; 0-4} to {8-11}
}
uint4 mode;
uint3 reg;
boolean valid;
uint32 address;
};
template<uint Size> auto fetch(EffectiveAddress& ea) -> uint32;
template<uint Size, bool Update = 1> auto read(EffectiveAddress& ea) -> uint32;
template<uint Size, bool Update = 1> auto write(EffectiveAddress& ea, uint32 data) -> void;
template<uint Size> auto flush(EffectiveAddress& ea, uint32 data) -> void;
//instruction.cpp
auto trap() -> void;
auto instruction() -> void;
//instructions.cpp
auto testCondition(uint4 condition) -> bool;
template<uint Size> auto bytes() -> uint;
template<uint Size> auto bits() -> uint;
template<uint Size> auto lsb() -> uint32;
template<uint Size> auto msb() -> uint32;
template<uint Size> auto mask() -> uint32;
template<uint Size> auto clip(uint32 data) -> uint32;
template<uint Size> auto sign(uint32 data) -> int32;
template<uint Size> auto zero(uint32 result) -> bool;
template<uint Size> auto negative(uint32 result) -> bool;
template<uint Size, bool Extend = false> auto ADD(uint32 source, uint32 target) -> uint32;
template<uint Size> auto instructionADD(DataRegister dr, uint1 direction, EffectiveAddress ea) -> void;
template<uint Size> auto instructionADDA(AddressRegister ar, EffectiveAddress ea) -> void;
template<uint Size> auto instructionADDI(EffectiveAddress modify) -> void;
template<uint Size> auto instructionADDQ(uint4 immediate, EffectiveAddress modify) -> void;
template<uint Size> auto instructionADDX(EffectiveAddress target, EffectiveAddress source) -> void;
template<uint Size> auto instructionANDI(EffectiveAddress ea) -> void;
auto instructionANDI_TO_CCR() -> void;
auto instructionANDI_TO_SR() -> void;
template<uint Size> auto ASL(uint32 result, uint shift) -> uint32;
template<uint Size> auto instructionASL(uint4 shift, DataRegister modify) -> void;
template<uint Size> auto instructionASL(DataRegister shift, DataRegister modify) -> void;
auto instructionASL(EffectiveAddress modify) -> void;
template<uint Size> auto ASR(uint32 result, uint shift) -> uint32;
template<uint Size> auto instructionASR(uint4 shift, DataRegister modify) -> void;
template<uint Size> auto instructionASR(DataRegister shift, DataRegister modify) -> void;
auto instructionASR(EffectiveAddress modify) -> void;
auto instructionBCC(uint4 condition, uint8 displacement) -> void;
template<uint Size> auto instructionBTST(DataRegister dr, EffectiveAddress ea) -> void;
template<uint Size> auto instructionBTST(EffectiveAddress ea) -> void;
template<uint Size> auto instructionCLR(EffectiveAddress ea) -> void;
template<uint Size> auto CMP(uint32 source, uint32 target) -> uint32;
template<uint Size> auto instructionCMP(DataRegister dr, EffectiveAddress ea) -> void;
template<uint Size> auto instructionCMPA(AddressRegister ar, EffectiveAddress ea) -> void;
template<uint Size> auto instructionCMPI(EffectiveAddress ea) -> void;
template<uint Size> auto instructionCMPM(EffectiveAddress ax, EffectiveAddress ay) -> void;
auto instructionDBCC(uint4 condition, DataRegister dr) -> void;
auto instructionEORI_TO_CCR() -> void;
auto instructionEORI_TO_SR() -> void;
auto instructionJSR(EffectiveAddress target) -> void;
auto instructionLEA(AddressRegister ar, EffectiveAddress ea) -> void;
template<uint Size> auto LSL(uint32 result, uint shift) -> uint32;
template<uint Size> auto instructionLSL(uint4 immediate, DataRegister dr) -> void;
template<uint Size> auto instructionLSL(DataRegister sr, DataRegister dr) -> void;
auto instructionLSL(EffectiveAddress ea) -> void;
template<uint Size> auto LSR(uint32 result, uint shift) -> uint32;
template<uint Size> auto instructionLSR(uint4 immediate, DataRegister dr) -> void;
template<uint Size> auto instructionLSR(DataRegister shift, DataRegister dr) -> void;
auto instructionLSR(EffectiveAddress ea) -> void;
template<uint Size> auto instructionMOVE(EffectiveAddress to, EffectiveAddress from) -> void;
template<uint Size> auto instructionMOVEA(AddressRegister ar, EffectiveAddress ea) -> void;
template<uint Size> auto instructionMOVEM(uint1 direction, EffectiveAddress ea) -> void;
auto instructionMOVEQ(DataRegister dr, uint8 immediate) -> void;
auto instructionMOVE_FROM_SR(EffectiveAddress ea) -> void;
auto instructionMOVE_TO_CCR(EffectiveAddress ea) -> void;
auto instructionMOVE_TO_SR(EffectiveAddress ea) -> void;
auto instructionMOVE_USP(uint1 direction, AddressRegister ar) -> void;
auto instructionNOP() -> void;
auto instructionORI_TO_CCR() -> void;
auto instructionORI_TO_SR() -> void;
template<uint Size> auto ROL(uint32 result, uint shift) -> uint32;
template<uint Size> auto instructionROL(uint4 shift, DataRegister modify) -> void;
template<uint Size> auto instructionROL(DataRegister shift, DataRegister modify) -> void;
auto instructionROL(EffectiveAddress modify) -> void;
template<uint Size> auto ROR(uint32 result, uint shift) -> uint32;
template<uint Size> auto instructionROR(uint4 shift, DataRegister modify) -> void;
template<uint Size> auto instructionROR(DataRegister shift, DataRegister modify) -> void;
auto instructionROR(EffectiveAddress modify) -> void;
template<uint Size> auto ROXL(uint32 result, uint shift) -> uint32;
template<uint Size> auto instructionROXL(uint4 shift, DataRegister modify) -> void;
template<uint Size> auto instructionROXL(DataRegister shift, DataRegister modify) -> void;
auto instructionROXL(EffectiveAddress modify) -> void;
template<uint Size> auto ROXR(uint32 result, uint shift) -> uint32;
template<uint Size> auto instructionROXR(uint4 shift, DataRegister modify) -> void;
template<uint Size> auto instructionROXR(DataRegister shift, DataRegister modify) -> void;
auto instructionROXR(EffectiveAddress modify) -> void;
auto instructionRTS() -> void;
template<uint Size, bool Extend = false> auto SUB(uint32 source, uint32 target) -> uint32;
template<uint Size> auto instructionSUB(EffectiveAddress source, DataRegister target) -> void;
template<uint Size> auto instructionSUB(DataRegister source, EffectiveAddress target) -> void;
template<uint Size> auto instructionSUBQ(uint4 immediate, EffectiveAddress ea) -> void;
template<uint Size> auto instructionTST(EffectiveAddress ea) -> void;
//disassembler.cpp
auto disassemble(uint32 pc) -> string;
auto disassembleRegisters() -> string;
struct Registers {
uint32 d[8];
uint32 a[8];
uint32 sp;
uint32 pc;
bool c; //carry
bool v; //overflow
bool z; //zero
bool n; //negative
bool x; //extend
uint3 i; //interrupt mask
bool s; //supervisor mode
bool t; //trace mode
} r;
uint16 opcode = 0;
uint instructionsExecuted = 0;
function<void ()> instructionTable[65536];
private:
//disassembler.cpp
template<uint Size> auto disassembleADD(DataRegister dr, uint1 direction, EffectiveAddress ea) -> string;
template<uint Size> auto disassembleADDA(AddressRegister ar, EffectiveAddress ea) -> string;
template<uint Size> auto disassembleADDI(EffectiveAddress modify) -> string;
template<uint Size> auto disassembleADDQ(uint4 immediate, EffectiveAddress modify) -> string;
template<uint Size> auto disassembleADDX(EffectiveAddress target, EffectiveAddress source) -> string;
template<uint Size> auto disassembleANDI(EffectiveAddress ea) -> string;
auto disassembleANDI_TO_CCR() -> string;
auto disassembleANDI_TO_SR() -> string;
template<uint Size> auto disassembleASL(uint4 shift, DataRegister modify) -> string;
template<uint Size> auto disassembleASL(DataRegister shift, DataRegister modify) -> string;
auto disassembleASL(EffectiveAddress modify) -> string;
template<uint Size> auto disassembleASR(uint4 shift, DataRegister modify) -> string;
template<uint Size> auto disassembleASR(DataRegister shift, DataRegister modify) -> string;
auto disassembleASR(EffectiveAddress modify) -> string;
auto disassembleBCC(uint4 condition, uint8 displacement) -> string;
template<uint Size> auto disassembleBTST(DataRegister dr, EffectiveAddress ea) -> string;
template<uint Size> auto disassembleBTST(EffectiveAddress ea) -> string;
template<uint Size> auto disassembleCLR(EffectiveAddress ea) -> string;
template<uint Size> auto disassembleCMP(DataRegister dr, EffectiveAddress ea) -> string;
template<uint Size> auto disassembleCMPA(AddressRegister ar, EffectiveAddress ea) -> string;
template<uint Size> auto disassembleCMPI(EffectiveAddress ea) -> string;
template<uint Size> auto disassembleCMPM(EffectiveAddress ax, EffectiveAddress ay) -> string;
auto disassembleDBCC(uint4 condition, DataRegister dr) -> string;
auto disassembleEORI_TO_CCR() -> string;
auto disassembleEORI_TO_SR() -> string;
auto disassembleJSR(EffectiveAddress target) -> string;
auto disassembleLEA(AddressRegister ar, EffectiveAddress ea) -> string;
template<uint Size> auto disassembleLSL(uint4 immediate, DataRegister dr) -> string;
template<uint Size> auto disassembleLSL(DataRegister sr, DataRegister dr) -> string;
auto disassembleLSL(EffectiveAddress ea) -> string;
template<uint Size> auto disassembleLSR(uint4 immediate, DataRegister dr) -> string;
template<uint Size> auto disassembleLSR(DataRegister shift, DataRegister dr) -> string;
auto disassembleLSR(EffectiveAddress ea) -> string;
template<uint Size> auto disassembleMOVE(EffectiveAddress to, EffectiveAddress from) -> string;
template<uint Size> auto disassembleMOVEA(AddressRegister ar, EffectiveAddress ea) -> string;
template<uint Size> auto disassembleMOVEM(uint1 direction, EffectiveAddress ea) -> string;
auto disassembleMOVEQ(DataRegister dr, uint8 immediate) -> string;
auto disassembleMOVE_FROM_SR(EffectiveAddress ea) -> string;
auto disassembleMOVE_TO_CCR(EffectiveAddress ea) -> string;
auto disassembleMOVE_TO_SR(EffectiveAddress ea) -> string;
auto disassembleMOVE_USP(uint1 direction, AddressRegister ar) -> string;
auto disassembleNOP() -> string;
auto disassembleORI_TO_CCR() -> string;
auto disassembleORI_TO_SR() -> string;
template<uint Size> auto disassembleROL(uint4 shift, DataRegister modify) -> string;
template<uint Size> auto disassembleROL(DataRegister shift, DataRegister modify) -> string;
auto disassembleROL(EffectiveAddress modify) -> string;
template<uint Size> auto disassembleROR(uint4 shift, DataRegister modify) -> string;
template<uint Size> auto disassembleROR(DataRegister shift, DataRegister modify) -> string;
auto disassembleROR(EffectiveAddress modify) -> string;
template<uint Size> auto disassembleROXL(uint4 shift, DataRegister modify) -> string;
template<uint Size> auto disassembleROXL(DataRegister shift, DataRegister modify) -> string;
auto disassembleROXL(EffectiveAddress modify) -> string;
template<uint Size> auto disassembleROXR(uint4 shift, DataRegister modify) -> string;
template<uint Size> auto disassembleROXR(DataRegister shift, DataRegister modify) -> string;
auto disassembleROXR(EffectiveAddress modify) -> string;
auto disassembleRTS() -> string;
template<uint Size> auto disassembleSUB(EffectiveAddress source, DataRegister target) -> string;
template<uint Size> auto disassembleSUB(DataRegister source, EffectiveAddress target) -> string;
template<uint Size> auto disassembleSUBQ(uint4 immediate, EffectiveAddress ea) -> string;
template<uint Size> auto disassembleTST(EffectiveAddress ea) -> string;
template<uint Size> auto _read(uint32 addr) -> uint32;
template<uint Size = Word> auto _readPC() -> uint32;
auto _dataRegister(DataRegister dr) -> string;
auto _addressRegister(AddressRegister ar) -> string;
template<uint Size> auto _immediate() -> string;
template<uint Size> auto _address(EffectiveAddress& ea) -> string;
template<uint Size> auto _effectiveAddress(EffectiveAddress& ea) -> string;
auto _branch(uint8 displacement) -> string;
template<uint Size> auto _suffix() -> string;
auto _condition(uint4 condition) -> string;
uint32 _pc;
function<string ()> disassembleTable[65536];
};
}

View File

@@ -0,0 +1,91 @@
template<> auto M68K::read<Byte>(uint32 addr) -> uint32 {
step(4);
return read(0, addr);
}
template<> auto M68K::read<Word>(uint32 addr) -> uint32 {
step(4);
return read(1, addr);
}
template<> auto M68K::read<Long>(uint32 addr) -> uint32 {
step(4);
uint32 data = read(1, addr + 0) << 16;
step(4);
return data | read(1, addr + 2) << 0;
}
//
template<> auto M68K::write<Byte>(uint32 addr, uint32 data) -> void {
step(4);
return write(0, addr, data);
}
template<> auto M68K::write<Word>(uint32 addr, uint32 data) -> void {
step(4);
return write(1, addr, data);
}
template<> auto M68K::write<Long>(uint32 addr, uint32 data) -> void {
step(4);
write(1, addr + 0, data >> 16);
step(4);
write(1, addr + 2, data >> 0);
}
template<> auto M68K::write<Byte, Reverse>(uint32 addr, uint32 data) -> void {
step(4);
return write(0, addr, data);
}
template<> auto M68K::write<Word, Reverse>(uint32 addr, uint32 data) -> void {
step(4);
return write(1, addr, data);
}
template<> auto M68K::write<Long, Reverse>(uint32 addr, uint32 data) -> void {
step(4);
write(1, addr + 2, data >> 0);
step(4);
write(1, addr + 0, data >> 16);
}
//
template<> auto M68K::readPC<Byte>() -> uint32 {
step(4);
auto data = read(1, r.pc);
r.pc += 2;
return (uint8)data;
}
template<> auto M68K::readPC<Word>() -> uint32 {
step(4);
auto data = read(1, r.pc);
r.pc += 2;
return data;
}
template<> auto M68K::readPC<Long>() -> uint32 {
step(4);
auto hi = read(1, r.pc);
r.pc += 2;
step(4);
auto lo = read(1, r.pc);
r.pc += 2;
return hi << 16 | lo << 0;
}
//
template<uint Size> auto M68K::pop() -> uint32 {
auto data = read<Size>((uint32)r.a[7]);
r.a[7] += bytes<Size>();
return data;
}
template<uint Size> auto M68K::push(uint32 data) -> void {
r.a[7] -= bytes<Size>();
return write<Size, Reverse>((uint32)r.a[7], data);
}

View File

@@ -0,0 +1,48 @@
template<uint Size> auto M68K::read(DataRegister reg) -> uint32 {
return clip<Size>(r.d[reg.number]);
}
template<uint Size> auto M68K::write(DataRegister reg, uint32 data) -> void {
r.d[reg.number] = (r.d[reg.number] & ~mask<Size>()) | (data & mask<Size>());
}
//
template<uint Size> auto M68K::read(AddressRegister reg) -> uint32 {
return sign<Size>(r.a[reg.number]);
}
template<uint Size> auto M68K::write(AddressRegister reg, uint32 data) -> void {
r.a[reg.number] = sign<Size>(data);
}
//
//CCR,SR unused bits cannot be set; always read out as 0
auto M68K::readCCR() -> uint8 {
return r.c << 0 | r.v << 1 | r.z << 2 | r.n << 3 | r.x << 4;
}
auto M68K::readSR() -> uint16 {
return readCCR() << 0 | r.i << 8 | r.s << 13 | r.t << 15;
}
auto M68K::writeCCR(uint8 ccr) -> void {
r.c = ccr.bit(0);
r.v = ccr.bit(1);
r.z = ccr.bit(2);
r.n = ccr.bit(3);
r.x = ccr.bit(4);
}
auto M68K::writeSR(uint16 sr) -> void {
writeCCR(sr);
//when entering or exiting supervisor mode; swap SSP and USP into A7
if(r.s != sr.bit(13)) swap(r.a[7], r.sp);
r.i = sr.bits(8,10);
r.s = sr.bit(13);
r.t = sr.bit(15);
}

View File

@@ -19,6 +19,20 @@ namespace Processor {
#include "instructions-misc.cpp"
#include "switch.cpp"
auto R65816::interrupt() -> void {
read(r.pc.d);
idle();
N writeSP(r.pc.b);
writeSP(r.pc.h);
writeSP(r.pc.l);
writeSP(r.e ? (r.p & ~0x10) : r.p);
r.pc.l = read(r.vector + 0);
r.p.i = 1;
r.p.d = 0;
r.pc.h = read(r.vector + 1);
r.pc.b = 0x00;
}
//immediate, 2-cycle opcodes with idle cycle will become bus read
//when an IRQ is to be triggered immediately after opcode completion.
//this affects the following opcodes:
@@ -54,20 +68,6 @@ auto R65816::idle6(uint16 addr) -> void {
}
}
auto R65816::interrupt() -> void {
read(r.pc.d);
idle();
N writeSP(r.pc.b);
writeSP(r.pc.h);
writeSP(r.pc.l);
writeSP(r.e ? (r.p & ~0x10) : r.p);
r.pc.l = read(r.vector + 0);
r.p.i = 1;
r.p.d = 0;
r.pc.h = read(r.vector + 1);
r.pc.b = 0x00;
}
#undef E
#undef N
#undef L

View File

@@ -18,6 +18,7 @@ struct R65816 {
virtual auto write(uint24 addr, uint8 data) -> void = 0;
virtual auto lastCycle() -> void = 0;
virtual auto interruptPending() const -> bool = 0;
virtual auto interrupt() -> void;
virtual auto readDisassembler(uint24 addr) -> uint8 { return 0; }
@@ -26,7 +27,6 @@ struct R65816 {
alwaysinline auto idle2() -> void;
alwaysinline auto idle4(uint16 x, uint16 y) -> void;
alwaysinline auto idle6(uint16 addr) -> void;
auto interrupt() -> void;
//algorithms.cpp
auto op_adc_b();

View File

@@ -1,5 +1,5 @@
auto SPC700::serialize(serializer& s) -> void {
s.integer(regs.pc);
s.integer(regs.pc.w);
s.integer(regs.a);
s.integer(regs.x);
s.integer(regs.y);

View File

@@ -53,7 +53,7 @@ auto V30MZ::opGroup3MemImm(Size size) {
auto mem = getMem(size);
switch(modrm.reg) {
case 0: alAnd(size, mem, fetch(size)); break;
case 1: debug("[V30MZ] GRP3.1"); break;
case 1: warning("[V30MZ] GRP3.1"); break;
case 2: wait(2); setMem(size, alNot(size, mem)); break;
case 3: wait(2); setMem(size, alNeg(size, mem)); break;
case 4: wait(2); setAcc(size * 2, alMul(size, getAcc(size), mem)); break;
@@ -77,13 +77,13 @@ auto V30MZ::opGroup4MemImm(Size size) {
setMem(size, alDec(size, getMem(size)));
break;
case 2:
if(size == Byte) { debug("[V30MZ] GRP4.2"); break; }
if(size == Byte) { warning("[V30MZ] GRP4.2"); break; }
wait(5);
push(r.ip);
r.ip = getMem(Word);
break;
case 3:
if(size == Byte) { debug("[V30MZ] GRP4.3"); break; }
if(size == Byte) { warning("[V30MZ] GRP4.3"); break; }
wait(11);
push(r.cs);
push(r.ip);
@@ -91,23 +91,23 @@ auto V30MZ::opGroup4MemImm(Size size) {
r.cs = getMem(Word, 2);
break;
case 4:
if(size == Byte) { debug("[V30MZ] GRP4.4"); break; }
if(size == Byte) { warning("[V30MZ] GRP4.4"); break; }
wait(4);
r.ip = getMem(Word);
break;
case 5:
if(size == Byte) { debug("[V30MZ] GRP4.5"); break; }
if(size == Byte) { warning("[V30MZ] GRP4.5"); break; }
wait(9);
r.ip = getMem(Word, 0);
r.cs = getMem(Word, 2);
break;
case 6:
if(size == Byte) { debug("[V30MZ] GRP4.6"); break; }
if(size == Byte) { warning("[V30MZ] GRP4.6"); break; }
wait(1);
push(getMem(Word));
break;
case 7:
debug("[V30MZ] GRP4.7");
warning("[V30MZ] GRP4.7");
break;
}
}

View File

@@ -18,7 +18,7 @@ namespace Processor {
#include "serialization.cpp"
#include "disassembler.cpp"
auto V30MZ::debug(string text) -> void {
auto V30MZ::warning(string text) -> void {
//print(text, "\n");
}

View File

@@ -23,7 +23,7 @@ struct V30MZ {
virtual auto in(uint16 port) -> uint8 = 0;
virtual auto out(uint16 port, uint8 data) -> void = 0;
auto debug(string text) -> void;
auto warning(string text) -> void;
auto power() -> void;
auto exec() -> void;
auto interrupt(uint8 vector) -> void;

View File

@@ -0,0 +1,5 @@
#include <processor/processor.hpp>
namespace Processor {
}

View File

@@ -0,0 +1,10 @@
#pragma once
//Zilog Z80
namespace Processor {
struct Z80 {
};
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -1,4 +1,6 @@
namespace name=Resource
namespace name=Logo
binary name=higan file=logo/higan.png
namespace name=Sprite
binary name=CrosshairRed file=sprite/crosshair-red.png
binary name=CrosshairGreen file=sprite/crosshair-green.png

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,7 @@
namespace Resource {
namespace Logo {
extern const nall::vector<uint8_t> higan;
}
namespace Sprite {
extern const nall::vector<uint8_t> CrosshairRed;
extern const nall::vector<uint8_t> CrosshairGreen;

View File

@@ -1,8 +1,8 @@
processors += r65816 spc700 arm gsu hg51b upd96050
objects += sfc-interface sfc-system sfc-scheduler sfc-controller
objects += sfc-cartridge sfc-cheat
objects += sfc-memory sfc-cpu sfc-smp sfc-dsp sfc-ppu
objects += sfc-interface sfc-system sfc-controller
objects += sfc-cartridge sfc-memory
objects += sfc-cpu sfc-smp sfc-dsp sfc-ppu
objects += sfc-expansion sfc-satellaview sfc-superdisc
objects += sfc-21fx
objects += sfc-icd2 sfc-mcc sfc-nss sfc-event
@@ -15,10 +15,8 @@ objects += sfc-bsmemory sfc-sufamiturbo
obj/sfc-interface.o: sfc/interface/interface.cpp $(call rwildcard,sfc/interface)
obj/sfc-system.o: sfc/system/system.cpp $(call rwildcard,sfc/system/)
obj/sfc-scheduler.o: sfc/scheduler/scheduler.cpp $(call rwildcard,sfc/scheduler/)
obj/sfc-controller.o: sfc/controller/controller.cpp $(call rwildcard,sfc/controller/)
obj/sfc-cartridge.o: sfc/cartridge/cartridge.cpp $(call rwildcard,sfc/cartridge/)
obj/sfc-cheat.o: sfc/cheat/cheat.cpp $(call rwildcard,sfc/cheat/)
obj/sfc-memory.o: sfc/memory/memory.cpp $(call rwildcard,sfc/memory/)
obj/sfc-cpu.o: sfc/cpu/cpu.cpp $(call rwildcard,sfc/cpu/)

View File

@@ -10,7 +10,7 @@ auto Cartridge::loadCartridge(Markup::Node node) -> void {
}
}
if(board["sufamiturbo"]) {
if(auto pathID = interface->load(ID::SufamiTurboA, "Sufami Turbo - Slot A", "st")) {
if(auto pathID = interface->load(ID::SufamiTurboA, "Sufami Turbo", "st")) {
sufamiturboA.pathID = pathID();
loadSufamiTurboA();
}
@@ -55,7 +55,7 @@ auto Cartridge::loadSufamiTurboA(Markup::Node node) -> void {
loadMemory(sufamiturboA.ram, node["board/ram"], File::Optional, sufamiturboA.pathID);
if(node["board/linkable"]) {
if(auto pathID = interface->load(ID::SufamiTurboB, "Sufami Turbo - Slot B", "st")) {
if(auto pathID = interface->load(ID::SufamiTurboB, "Sufami Turbo", "st")) {
sufamiturboB.pathID = pathID();
loadSufamiTurboB();
}
@@ -199,7 +199,7 @@ auto Cartridge::loadHitachiDSP(Markup::Node node, uint roms) -> void {
has.HitachiDSP = true;
hitachidsp.Frequency = node["frequency"].natural();
if(hitachidsp.Frequency == 0) hitachidsp.frequency = 20000000;
if(hitachidsp.Frequency == 0) hitachidsp.Frequency = 20'000'000;
hitachidsp.Roms = roms; //1 or 2
loadMemory(hitachidsp.rom, node["rom"], File::Required);
@@ -224,8 +224,8 @@ auto Cartridge::loadHitachiDSP(Markup::Node node, uint roms) -> void {
auto Cartridge::loadNECDSP(Markup::Node node) -> void {
has.NECDSP = true;
necdsp.frequency = node["frequency"].natural();
if(necdsp.frequency == 0) necdsp.frequency = 8000000;
necdsp.Frequency = node["frequency"].natural();
if(necdsp.Frequency == 0) necdsp.Frequency = 8000000;
necdsp.revision
= node["model"].text() == "uPD7725" ? NECDSP::Revision::uPD7725
: node["model"].text() == "uPD96050" ? NECDSP::Revision::uPD96050

View File

@@ -1,32 +0,0 @@
#include <sfc/sfc.hpp>
namespace SuperFamicom {
Cheat cheat;
auto Cheat::reset() -> void {
codes.reset();
}
auto Cheat::append(uint addr, uint data) -> void {
codes.append({addr, Unused, data});
}
auto Cheat::append(uint addr, uint comp, uint data) -> void {
codes.append({addr, comp, data});
}
auto Cheat::find(uint addr, uint comp) -> maybe<unsigned> {
//WRAM mirroring: $00-3f,80-bf:0000-1fff -> $7e:0000-1fff
if((addr & 0x40e000) == 0x000000) addr = 0x7e0000 | (addr & 0x1fff);
for(auto& code : codes) {
if(code.addr == addr && (code.comp == Unused || code.comp == comp)) {
return code.data;
}
}
return nothing;
}
}

View File

@@ -1,19 +0,0 @@
struct Cheat {
enum : uint { Unused = ~0u };
alwaysinline auto enable() const -> bool { return codes.size() > 0; }
auto reset() -> void;
auto append(uint addr, uint data) -> void;
auto append(uint addr, uint comp, uint data) -> void;
auto find(uint addr, uint comp) -> maybe<uint>;
struct Code {
uint addr;
uint comp;
uint data;
};
vector<Code> codes;
};
extern Cheat cheat;

View File

@@ -9,22 +9,24 @@ namespace SuperFamicom {
#include "justifier/justifier.cpp"
Controller::Controller(bool port) : port(port) {
if(!thread) create(Controller::Enter, 1);
if(!handle()) create(Controller::Enter, 1);
}
Controller::~Controller() {
scheduler.remove(*this);
}
auto Controller::Enter() -> void {
while(true) {
scheduler.synchronize();
if(co_active() == peripherals.controllerPort1->thread) peripherals.controllerPort1->main();
if(co_active() == peripherals.controllerPort2->thread) peripherals.controllerPort2->main();
if(peripherals.controllerPort1->active()) peripherals.controllerPort1->main();
if(peripherals.controllerPort2->active()) peripherals.controllerPort2->main();
}
}
auto Controller::main() -> void {
step(1);
synchronize(cpu);
}
auto Controller::iobit() -> bool {

View File

@@ -11,7 +11,7 @@
// 6: iobit $4201.d6 write; $4213.d6 read $4201.d7 write; $4213.d7 read
// 7: gnd
struct Controller : Cothread {
struct Controller : Thread {
enum : bool { Port1 = 0, Port2 = 1 };
Controller(bool port);

View File

@@ -76,6 +76,7 @@ auto Justifier::main() -> void {
prev = next;
step(2);
synchronize(cpu);
}
auto Justifier::data() -> uint2 {

View File

@@ -66,6 +66,7 @@ auto SuperScope::main() -> void {
prev = next;
step(2);
synchronize(cpu);
}
auto SuperScope::data() -> uint2 {

View File

@@ -42,7 +42,7 @@ auto ArmDSP::main() -> void {
print(disassembleRegisters(), "\n");
print(disassembleInstructionARM(pipeline.execute.address), "\n");
print("Executed: ", instructions, "\n");
while(true) step(frequency);
while(true) step(21'477'272);
}
stepARM();
@@ -50,8 +50,8 @@ auto ArmDSP::main() -> void {
auto ArmDSP::step(uint clocks) -> void {
if(bridge.timer && --bridge.timer == 0);
Cothread::step(clocks);
synchronizeCPU();
Thread::step(clocks);
synchronize(cpu);
}
//MMIO: 00-3f,80-bf:3800-38ff
@@ -59,7 +59,7 @@ auto ArmDSP::step(uint clocks) -> void {
//a0 ignored
auto ArmDSP::read(uint24 addr, uint8) -> uint8 {
cpu.synchronizeCoprocessors();
cpu.synchronize(*this);
uint8 data = 0x00;
addr &= 0xff06;
@@ -83,7 +83,7 @@ auto ArmDSP::read(uint24 addr, uint8) -> uint8 {
}
auto ArmDSP::write(uint24 addr, uint8 data) -> void {
cpu.synchronizeCoprocessors();
cpu.synchronize(*this);
addr &= 0xff06;

View File

@@ -1,6 +1,6 @@
//ARMv3 (ARM60)
struct ArmDSP : Processor::ARM, Cothread {
struct ArmDSP : Processor::ARM, Thread {
#include "registers.hpp"
ArmDSP();

View File

@@ -1,6 +1,3 @@
struct Coprocessor : Cothread {
};
#include <sfc/coprocessor/icd2/icd2.hpp>
#include <sfc/coprocessor/mcc/mcc.hpp>
#include <sfc/coprocessor/nss/nss.hpp>

View File

@@ -27,7 +27,7 @@ auto EpsonRTC::main() -> void {
}
step(1);
synchronizeCPU();
synchronize(cpu);
}
auto EpsonRTC::init() -> void {
@@ -136,7 +136,7 @@ auto EpsonRTC::sync() -> void {
}
auto EpsonRTC::read(uint24 addr, uint8 data) -> uint8 {
cpu.synchronizeCoprocessors();
cpu.synchronize(*this);
addr &= 3;
if(addr == 0) {
@@ -161,7 +161,7 @@ auto EpsonRTC::read(uint24 addr, uint8 data) -> uint8 {
}
auto EpsonRTC::write(uint24 addr, uint8 data) -> void {
cpu.synchronizeCoprocessors();
cpu.synchronize(*this);
addr &= 3, data &= 15;
if(addr == 0) {

View File

@@ -1,6 +1,6 @@
//Epson RTC-4513 Real-Time Clock
struct EpsonRTC : Cothread {
struct EpsonRTC : Thread {
static auto Enter() -> void;
auto main() -> void;

View File

@@ -25,7 +25,7 @@ auto Event::main() -> void {
}
step(1);
synchronizeCPU();
synchronize(cpu);
}
auto Event::init() -> void {
@@ -47,6 +47,7 @@ auto Event::power() -> void {
auto Event::reset() -> void {
create(Event::Enter, 1);
for(auto n : range(ram.size())) ram.write(n, 0x00);
status = 0x00;
select = 0x00;

View File

@@ -2,7 +2,7 @@
//* Campus Challenge '92
//* Powerfest '94
struct Event : Cothread {
struct Event : Thread {
static auto Enter() -> void;
auto main() -> void;
auto init() -> void;

View File

@@ -15,14 +15,14 @@ auto HitachiDSP::main() -> void {
for(auto n : range(mmio.dmaLength)) {
write(mmio.dmaTarget + n, read(mmio.dmaSource + n));
step(2);
synchronize(cpu);
}
mmio.dma = false;
}
exec(mmio.programOffset);
step(1);
synchronizeCPU();
synchronize(cpu);
}
auto HitachiDSP::init() -> void {

View File

@@ -1,4 +1,4 @@
struct HitachiDSP : Processor::HG51B, Cothread {
struct HitachiDSP : Processor::HG51B, Thread {
MappedRAM rom;
MappedRAM ram;

Some files were not shown because too many files have changed in this diff Show More