Compare commits

..

55 Commits
v097 ... v099

Author SHA1 Message Date
Tim Allen
c074c6e064 Update to v099 release.
byuu says:

Time for a new release. There are a few important emulation improvements
and a few new features; but for the most part, this release focuses on
major code refactoring, the details of which I will mostly spare you.

The major change is that, as of v099, the SNES balanced and performance
cores have been removed from higan. Basically, in addition to my five
other emulation cores, these were too much of a burden to maintain. And
they've come along as far as I was able to develop them. If you need to
use these cores, please use these two from the v098 release.

I'm very well aware that ~80% of the people using higan for SNES
emulation were using the two removed profiles. But they simply had
to go. Hopefully in the future, we can compensate for their loss by
increasing the performance of the accuracy core.

Changelog (since v098):

SFC: balanced profile removed
SFC: performance profile removed
SFC: expansion port devices can now be changed during gameplay (atlhough
    you shouldn't)
SFC: fixed bug in SharpRTC leap year calculations
SFC: emulated new research findings for the S-DD1 coprocessor
SFC: fixed CPU emulation-mode wrapping bug with pei, [dp], [dp]+y
    instructions [AWJ]
SFC: fixed Super Game Boy bug that caused the bottom tile-row to flicker
    in games
GB: added MBC1M (multi-cart) mapper; icarus can't detect these so manual
    manifests are needed for now
GB: corrected return value when HuC3 unmapped RAM is read; fixes Robopon
    [endrift]
GB: improved STAT IRQ emulation; fixes Altered Space, etc [endrift,
    gekkio]
GB: partial emulation of DMG STAT write IRQ bug; fixes Legend of Zerd,
    Road Rash, etc
nall: execute() fix, for some Linux platforms that had trouble detecting
    icarus
nall: new BitField class; which allows for simplifying flag/register
    emulation in various cores
ruby: added Windows WASAPI audio driver (experimental)
ruby: remove attempts to call glSwapIntervalEXT (fixes crashing on some
    Linux systems)
ui: timing settings panel removed
video: restored saturation, gamma, luminance settings
video: added new post-emulation sprite system; light gun cursors are
    now higher-resolution
audio: new resampler (6th-order Butterworth biquad IIR); quite a bit
    faster than the old one
audio: added optional basic reverb filter (for fun)
higan: refresh video outside cooperative threads (workaround for shoddy
    code in AMD graphics drivers)
higan: individual emulation cores no longer have unique names
higan: really substantial code refactoring; 43% reduction in binary size

Off the bat, here are the known bugs:

hiro/Windows: focus stealing bug on startup. Needs to be fixed in hiro,
    not with a cheap hack to tomoko.

higan/SFC: some of the coprocessors are saving some volatile memory to
    disk. Completely harmless, but still needs to be fixed.

ruby/WASAPI: some sound cards have a lot of issues with the current driver
    (eg FitzRoy's). We need to find a clean way to fix this before it
    can be made the default driver. Which would be a huge win because
    the latency improvements are substantial, and in exclusive mode,
    WASAPI allows G-sync to work very well.

[From the v099 WIP thread, here's the changelog since v098r19:

- GB: don't force mode 1 during force-blank; fixes v098r16 regression
  with many Game Boy games
- GB: only perform the STAT write IRQ bug during vblank, not hblank
  (still not hardware accurate, though)

-Ed.]
2016-06-11 11:13:18 +10:00
Tim Allen
50420e3dd2 Update to v098r19 release.
byuu says:

Changelog:
- added nall/bit-field.hpp
- updated all CPU cores (sans LR35902 due to some complexities) to use
  BitFields instead of bools
- updated as many CPU cores as I could to use BitFields instead of union {
  struct { uint8_t ... }; }; pairs

The speed changes are mostly a wash for this. In some instances,
I noticed a ~2-3% speedup (eg SNES emulation), and in others a 2-3%
slowdown (eg Famicom emulation.) It's within the margin of error, so
it's safe to say it has no impact.

This does give us a lot of new useful things, however:

- no more manual reconstruction of flag values from lots of left shifts
  and ORs
- no more manual deconstruction of flag values from lots of ANDs
- ability to get completely free aliases to flag groups (eg GSU can
  provide alt2, alt1 and also alt (which is alt2,alt1 combined)
- removes the need for the nasty order_lsbN macro hack (eventually will
  make higan 100% endian independent)
- saves us from insane compilers that try and do nasty things with
  alignment on union-structs
- saves us from insane compilers that try to store bit-field bits in
  reverse order
- will allow some really novel new use cases (I'm planning an
  instant-decode ARM opcode function, for instance.)
- reduces code size (we can serialize flag registers in one line instead
  of one for each flag)

However, I probably won't use it for super critical code that's constantly
reading out register values (eg PPU MMIO registers.) I think there we
would end up with a performance penalty.
2016-06-09 08:26:35 +10:00
Tim Allen
b08449215a Update to v098r18 release.
byuu says:

Changelog:
- hiro: fixed the BrowserDialog column resizing when navigating to new
  folders (prevents clipping of filenames)
  - note: this is kind of a quick-fix; but I have a good idea how to do
    the proper fix now
- nall: added BitField<T, Lo, Hi> class
  - note: not yet working on the SFC CPU class; need to go at it with
    a debugger to find out what's happening
- GB: emulated DMG/SGB STAT IRQ bug; fixes Zerd no Densetsu and Road Rash
  (won't fix anything else; don't get hopes up)
2016-06-07 21:55:03 +10:00
Tim Allen
9b452c9f5f Update to v098r17 release.
byuu says:

Changelog:
- fixed Super Game Boy regression from v096r04 with bottom tile row
  flickering
- fixed GB STAT IRQ regression from previous WIP
  - Altered Space is now playable
  - GBVideoPlayer isn't; but nobody seems to know exactly what weird
    hardware quirk that one relies on to work
- ~3-4% speed improvement in SuperFX games by eliminating function<>
  callback on register assignments
  - most noticeable in Doom in-game; least noticeable on Yoshi's Island
    title screen (darn)
- finished GSU core and SuperFX coprocessor code cleanups
- did some more work cleaning up the LR35902 core and GB CPU code

Just a fair warning: don't get your hopes up on these GB
fixes. Cliffhanger now hangs completely (har har), and none of the
other bugs are fixed. We pretty much did all this work just for Altered
Space. So, I hope you like playing Altered Space.
2016-06-06 08:10:01 +10:00
Tim Allen
3681961ca5 Update to v098r16 release.
byuu says:

Changelog:
- GNUmakefile: reverted $(call unique,) to $(strip)
- processor/r6502: removed templates; reduces object size from 146.5kb
  to 107.6kb
- processor/lr35902: removed templates; reduces object size from 386.2kb
  to 197.4kb
- processor/spc700: merged op macros for switch table declarations
- sfc/coprocessor/sa1: partial cleanups; flattened directory structure
- sfc/coprocessor/superfx: partial cleanups; flattened directory structure
- sfc/coprocessor/icd2: flattened directory structure
- gb/ppu: changed behavior of STAT IRQs

Major caveat! The GB/GBC STAT IRQ changes has a major bug in it somewhere
that's seriously breaking most games. I'm pushing the WIP anyway, because
I believe the changes to be mostly correct. I'd like to get more people
looking at these changes, and also try more heavy-handed hacking and
diff comparison logging between the previous WIP and this one.
2016-06-05 15:03:21 +10:00
Tim Allen
20ac95ee49 Update to v098r15 release.
byuu says:

Changelog:
- removed template usage from processor/spc700; cleaned up many function
  names and the switch table
  - object size: 176.8kb => 127.3kb
  - source code size: 43.5kb => 37.0kb
- fixed processor/r65816 BRK/COP vector regression [hex_usr]
- corrected HuC3 unmapped RAM read value; fixes Robopon [endrift]
- cosmetic: simplified the butterworth constant calculation
  [Wolfram|Alpha]

The SPC700 core changes took forever, about three hours of work.

Only the LR35902 and R6502 still need their template functions
removed. The point of this is that it doesn't cause any speed penalty
to do so, and it results in smaller binary sizes and faster compilation
times.
2016-06-05 14:52:43 +10:00
Tim Allen
fdc41611cf Update to v098r14 release.
byuu says:

Changelog:
- improved attenuation of biquad filter by computing butterworth Q
  coefficients correctly (instead of using the same constant)
- adding 1e-25 to each input sample into the biquad filters to try and
  prevent denormalization
- updated normalization from [0.0 to 1.0] to [-1.0 to +1.0]; volume/reverb
  happen in floating-point mode now
- good amount of work to make the base Emulator::Audio support any number
  of output channels
  - so that we don't have to do separate work on left/right channels;
    and can instead share the code for each channel
- Emulator::Interface::audioSample(int16 left, int16 right); changed to:
  - Emulator::Interface::audioSample(double* samples, uint channels);
  - samples are normalized [-1.0 to +1.0]
  - for now at least, channels will be the value given to
    Emulator::Audio::reset()
- fixed GUI crash on startup when audio driver is set to None

I'm probably going to be updating ruby to accept normalized doubles as
well; but I'm not sure if I will try and support anything other 2-channel
audio output. It'll depend on how easy it is to do so; perhaps it'll be
a per-driver setting.

The denormalization thing is fierce. If that happens, it drops the
emulator framerate from 220fps to about 20fps for Game Boy emulation. And
that happens basically whenever audio output is silent. I'm probably
also going to make a nall/denormal.hpp file at some point with
platform-specific functionality to set the CPU state to "denormals as
zero" where applicable. I'll still add the 1e-25 offset (inaudible)
as another fallback.
2016-06-01 21:23:22 +10:00
Tim Allen
839813d0f1 Update to v098r13 release.
byuu says:

Changelog:
- nall/dsp returns with new iir/biquad.hpp and resampler/cubic.hpp files
- nall/queue.hpp added (simple ring buffer ... nall/vector wouldn't
  cause too many moves with FIFO)
- audio streams now only buffer 20ms; so even if multiple audio streams
  desync, latency can never exceed 20ms
- replaced blackman windwed sinc FIR hermite audio filter with transposed
  direct form II biquadratic sixth-order IIR butterworth filter (better
  attenuation of frequencies above 20KHz, faster, no need for decimation,
  less code)
- put in experimental eight-tap echo filter (a lot better than what I
  had before, but still rather weak)
- substantial cleanups to the SuperFX GSU processor core (slightly
  faster, 479KB->100KB object file, 42.7KB->33.4KB source code size,
  way less code duplication)

We'll definitely want to test the whole SuperFX library (not many games)
just to make sure there's no regressions caused by this one.

Not sure what I want to do with audio processing effects yet. I've always
really wanted lots of fun controls to customize audio, and now finally
with this new biquad filter, I can finally start implementing real
effects. For instance, an equalizer wouldn't be too complicated anymore.

The new reverb effect is still a poor man's version. I need to find human
readable source for implementing a comb-filter properly. I'm pretty sure
I can already treat nall::queue as an all-pass filter since all that
does is phase shift (fancy audio term for "delay audio"). What's really
going to be hard is figuring out how to expose user-friendly settings for
controlling it. It looks like you need a bunch of coprime coefficients,
and I don't think casual users are going to be able to hand-enter coprime
values to get the echo effect they want. I uh ... don't even know how
to calculate coprime values dynamically right now >_> But we're going
to have to, as they are correlated to the output sampling rate.

We'll definitely want to make some audio profiles so that users can
quickly select pre-configured themes that sound nice, but expose the
underlying coefficients so that they can tweak stuff to their liking. This
isn't just about higan, this is about me trying to learn digital signal
processing, so please don't be too upset about feature creep or anything
on this.

Anyway ... I'm having some difficulties with my audio right now. When
the reverb effect is enabled, there's a bunch of static on system
reset for just a moment. But this should not be possible. nall::queue
is initializing all previous reverb sample elements to 0.0. I don't
understand where static is coming in from. Further, we have the same
issue with both the windowed sinc and the biquad filters ... a bit of
a popping sound when starting a game. Any help tracking this down would
be appreciated.

There's also one really annoying issue ... I can't seem to do reverb
or volume adjustments with normalized samples. If I say "volume *= 0.5"
in higan/audio/audio.cpp line 68, it doesn't just halve the volume, it
adds a whole bunch of distortion. This makes absolutely zero sense to
me. The sample values are between 0.0 (mute) and 1.0 (full volume) here,
so multiplying a double by 0.5 shouldn't cause distortion. So right now,
I'm doing these adjustments with less precision after denormalizing back
to int16. Anyone ever see something like that? :/
2016-06-01 08:29:36 +10:00
Tim Allen
7f3cfa17b9 Update to v098r12 release.
byuu says:

Changelog:
- higan/video: added support for Emulator::Sprite
- higan/resource: a new system for accessing embedded binary files
  inside the emulation cores; holds the sprites
- higan/sfc/superscope,justifier: re-enabled display of crosshairs
- higan/sfc/superscope: fixed turbo toggle (also shows different
  crosshair color when in turbo mode)
- higan/sfc/ppu: always outputs at 512x480 resolution now
  - causes a slight speed-hit from ~127fps to ~125fps;
  - but allows high-resolution 32x32 cursors that look way better;
  - also avoids the need to implement sprite scaling logic

Right now, the PPU code to always output at 480-height is a really gross
hack. Don't worry, I'll make that nicer before release.

Also, superscope.cpp and justifier.cpp are built around a 256x240
screen. But since we now have 512x480, we can make the cursor's movement
much smoother by doubling the resolution on both axes. The actual games
won't see any accuracy improvements when firing the light guns, but the
cursors will animate nicer so I think it's still worth it. I'll work on
that before the next release as well.

The current 32x32 cursors are nicer, but we can do better now with full
24-bit color. So feel free to submit alternatives. I'll probably reject
them, but you can always try :D

The sprites don't support alpha blending, just color keying (0x00000000
= transparent; anything else is 0xff......). We can revisit that later
if necessary.

The way I have it designed, the only files that do anything with
Emulator::Sprite at all are the superscope and justifier folders.
I didn't have to add any hooks anywhere else. Rendering the sprite is
a lot cleaner than the old code, too.
2016-05-26 21:20:15 +10:00
Tim Allen
ae5d380d06 Update to v098r11 release.
byuu says:

Changelog:
- fixed nall/path.hpp compilation issue
- fixed ruby/audio/xaudio header declaration compilation issue (again)
- cleaned up xaudio2.hpp file to match my coding syntax (12.5% of the
  file was whitespace overkill)
- added null terminator entry to nall/windows/utf8.hpp argc[] array
- nall/windows/guid.hpp uses the Windows API for generating the GUID
  - this should stop all the bug reports where two nall users were
    generating GUIDs at the exact same second
- fixed hiro/cocoa compilation issue with uint# types
- fixed major higan/sfc Super Game Boy audio latency issue
- fixed higan/sfc CPU core bug with pei, [dp], [dp]+y instructions
- major cleanups to higan/processor/r65816 core
  - merged emulation/native-mode opcodes
  - use camel-case naming on memory.hpp functions
  - simplify address masking code for memory.hpp functions
  - simplify a few opcodes themselves (avoid redundant copies, etc)
  - rename regs.* to r.* to match modern convention of other CPU cores
- removed device.order<> concept from Emulator::Interface
  - cores will now do the translation to make the job of the UI easier
- fixed plurality naming of arrays in Emulator::Interface
  - example: emulator.ports[p].devices[d].inputs[i]
  - example: vector<Medium> media
- probably more surprises

Major show-stoppers to the next official release:
- we need to work on GB core improvements: LY=153/0 case, multiple STAT
  IRQs case, GBC audio output regs, etc.
- we need to re-add software cursors for light guns (Super Scope,
  Justifier)
- after the above, we need to fix the turbo button for the Super Scope

I really have no idea how I want to implement the light guns. Ideally,
we'd want it in higan/video, so we can support the NES Zapper with the
same code. But this isn't going to be easy, because only the SNES knows
when its output is interlaced, and its resolutions can vary as
{256,512}x{224,240,448,480} which requires pixel doubling that was
hard-coded to the SNES-specific behavior, but isn't appropriate to be
exposed in higan/video.
2016-05-25 21:13:02 +10:00
Tim Allen
3ebc77c148 Update to v098r10 release.
byuu says:

Changelog:
- synchronized tomoko, loki, icarus with extensive changes to nall
  (118KiB diff)
2016-05-16 19:51:12 +10:00
Tim Allen
6ae0abe3d3 Update to v098r09 release.
byuu says:

Changelog:
- fixed major nall/vector/prepend bug
- renamed hiro/ListView to hiro/TableView
- added new hiro/ListView control which is a simplified abstraction of
  hiro/TableView
- updated higan's cheat database window and icarus' scan dialog to use
  the new ListView control
- compilation works once again on all platforms (Windows, Cocoa, GTK,
  Qt)
- the loki skeleton compiles once again (removed nall/DSP references;
  updated port/device ID names)

Small catch: need to capture layout resize events internally in Windows
to call resizeColumns. For now, just resize the icarus window to get it
to use the full window width for list view items.
2016-05-04 20:07:13 +10:00
Tim Allen
0955295475 Update to v098r08 release.
byuu says:

Changelog:
- nall/vector rewritten from scratch
- higan/audio uses nall/vector instead of raw pointers
- higan/sfc/coprocessor/sdd1 updated with new research information
- ruby/video/glx and ruby/video/glx2: fuck salt glXSwapIntervalEXT!

The big change here is definitely nall/vector. The Windows, OS X and Qt
ports won't compile until you change some first/last strings to
left/right, but GTK will compile.

I'd be really grateful if anyone could stress-test nall/vector. Pretty
much everything I do relies on this class. If we introduce a bug, the
worst case scenario is my entire SFC game dump database gets corrupted,
or the byuu.org server gets compromised. So it's really critical that we
test the hell out of this right now.

The S-DD1 changes mean you need to update your installation of icarus
again. Also, even though the Lunar FMV never really worked on the
accuracy core anyway (it didn't initialize the PPU properly), it really
won't work now that we emulate the hard-limit of 16MiB for S-DD1 games.
2016-05-02 19:57:04 +10:00
Tim Allen
7cdae5195a Update to v098r07 release.
byuu says:

Changelog:
- GB: support modeSelect and RAM for MBC1M (Momotarou Collection)
- audio: implemented native resampling support into Emulator::Stream
- audio: removed nall::DSP completely

Unfortunately, the new resampler didn't turn out quite as fast as I had
hoped. The final hermite resampling added some overhead; and I had to
bump up the kernel count to 500 from 400 to get the buzzing to go away
on my main PC. I think that's due to it running at 48000hz output
instead of 44100hz output, maybe?

Compared to Ryphecha's:
(NES) Mega Man 2: 167fps -> 166fps
(GB) Mega Man II: 224fps -> 200fps
(WSC) Riviera: 143fps -> 151fps

Odd that the WS/WSC ends up faster while the DMG/CGB ends up slower.

But this knocks 922 lines down to 146 lines. The only files left in all
of higan not written (or rewritten) by me are ruby/xaudio2.h and
libco/ppc.c
2016-04-23 17:55:59 +10:00
Tim Allen
e2ee6689a0 Update to v098r06 release.
byuu says:

Changelog:
- emulation cores now refresh video from host thread instead of
  cothreads (fix AMD crash)
- SFC: fixed another bug with leap year months in SharpRTC emulation
- SFC: cleaned up camelCase on function names for
  armdsp,epsonrtc,hitachidsp,mcc,nss,sharprtc classes
- GB: added MBC1M emulation (requires manually setting mapper=MBC1M in
  manifest.bml for now, sorry)
- audio: implemented Emulator::Audio mixer and effects processor
- audio: implemented Emulator::Stream interface
  - it is now possible to have more than two audio streams: eg SNES
    + SGB + MSU1 + Voicer-Kun (eventually)
- audio: added reverb delay + reverb level settings; exposed balance
  configuration in UI
- video: reworked palette generation to re-enable saturation, gamma,
  luminance adjustments
- higan/emulator.cpp is gone since there was nothing left in it

I know you guys are going to say the color adjust/balance/reverb stuff
is pointless. And indeed it mostly is. But I like the idea of allowing
some fun special effects and configurability that isn't system-wide.

Note: there seems to be some kind of added audio lag in the SGB
emulation now, and I don't really understand why. The code should be
effectively identical to what I had before. The only main thing is that
I'm sampling things to 48000hz instead of 32040hz before mixing. There's
no point where I'm intentionally introducing added latency though. I'm
kind of stumped, so if anyone wouldn't mind taking a look at it, it'd be
much appreciated :/

I don't have an MSU1 test ROM, but the latency issue may affect MSU1 as
well, and that would be very bad.
2016-04-22 23:35:51 +10:00
Tim Allen
55e507d5df Update to v098r05 release.
byuu says:

Changelog:
- WS/WSC: re-added support for screen rotation (code is inside WS core)
- ruby: changed sample(uint16_t left, uint16_t right) to sample(int16_t
  left, int16_t right);
  - requires casting to uint prior to shifting in each driver, but
    I felt it was misleading to use uint16_t just to avoid that
- ruby: WASAPI is now built in by default; has wareya's improvements,
  and now supports latency adjust
- tomoko: audio settings panel has new "Exclusive Mode" checkbox for
  WASAPI driver only
  - note: although the setting *does* take effect in real-time, I'd
    suggest restarting the emulator after changing it
- tomoko: audio latency can now be set to 0ms (which in reality means
  "the minimum supported by the driver")
- all: increased cothread size from 512KiB to 2MiB to see if it fixes
  bullshit AMD driver crashes
  - this appears to cause a slight speed penalty due to cache locality
    going down between threads, though
2016-04-18 20:49:45 +10:00
Tim Allen
a2d3b8ba15 Update to v098r04 release.
byuu says:

Changelog:
- SFC: fixed behavior of 21fx $21fe register when no device is connected
  (must return zero)
- SFC: reduced 21fx buffer size to 1024 bytes in both directions to
  mirror the FT232H we are using
- SFC: eliminated dsp/modulo-array.hpp [1]
- higan: implemented higan/video interface and migrated all cores to it
  [2]

[1] the echo history buffer was 8-bytes, so there was no need for it at
all here. Not sure what I was thinking. The BRR buffer was 12-bytes, and
has very weird behavior ... but there's only a single location in the
code where it actually writes to this buffer. It's much easier to just
write to the buffer three times there instead of implementing an entire
class just to abstract away two lines of code. This change actually
boosted the speed from ~124.5fps to around ~127.5fps, but that's within
the margin of error for GCC. I doubt it's actually faster this way.

The DSP core could really use a ton of work. It comes from a port of
blargg's spc_dsp to my coding style, but he was extremely fond of using
32-bit signed integers everywhere. There's a lot of opportunity to
remove red tape masking by resizing the variables to their actual state
sizes.

I really need to find where I put spc_dsp6.sfc from blargg. It's a great
test to verify if I've made any mistakes in my implementation that would
cause regressions. Don't suppose anyone has it?

[2] so again, the idea is that higan/audio and higan/video are going to
sit between the emulation cores and the user interfaces. The hope is to
output raw encoding data from the emulation cores without having to
worry about the video display format (generally 24-bit RGB) of the host
display. And also to avoid having to repeat myself with eg three
separate implementations of interframe blending, and so on.

Furthermore, the idea is that the user interface can configure its side
of the settings, and the emulation cores can configure their sides.
Thus, neither has to worry about the other end. And now we can spin off
new user interfaces much easier without having to mess with all of these
things.

Right now, I've implemented color emulation, interframe blending and
SNES horizontal color bleed. I did not implement scanlines (and
interlace effects for them) yet, but I probably will at some point.

Further, for right now, the WonderSwan/Color screen rotation is busted
and will only show games in the horizontal orientation. Obviously this
must be fixed before the next official release, but I'll want to think
about how to implement it.

Also, the SNES light gun pointers are missing for now.

Things are a bit messy right now as I've gone through several revisions
of how to handle these things, so a good house cleaning is in order once
everything is feature-complete again. I need to sit down and think
through how and where I want to handle things like light gun cursors,
LCD icons, and maybe even rasterized text messages.

And obviously ... higan/audio is still just nall::DSP's headers. I need
to revamp that whole interface. I want to make it quite powerful with
a true audio mixer so I can handle things like
SNES+SGB+MSU1+Voicer-Kun+SNES-CD (five separate audio streams at once.)

The video system has the concept of "effects" for things like color
bleed and interframe blending. I want to extend on this with useful
other effects, such as NTSC simulation, maybe bringing back my mini-HQ2x
filter, etc. I'd also like to restore the saturation/gamma/luma
adjustment sliders ... I always liked allowing people to compensate for
their displays without having to change settings system-wide. Lastly,
I've always wanted to see some audio effects. Although I doubt we'll
ever get my dream of CoreAudio-style profiles, I'd like to get some
basic equalizer settings and echo/reverb effects in there.
2016-04-12 07:29:56 +10:00
Tim Allen
1929ad47d2 Update to v098r03 release.
byuu says:

It took several hours, but I've rebuilt much of the SNES' bus memory
mapping architecture.

The new design unifies the cartridge string-based mapping
("00-3f,80-bf:8000-ffff") and internal bus.map calls. The map() function
now has an accompanying unmap() function, and instead of a fixed 256
callbacks, it'll scan to find the first available slot. unmap() will
free slots up when zero addresses reference a given slot.

The controllers and expansion port are now both entirely dynamic.
Instead of load/unload/power/reset, they only have the constructor
(power/reset/load) and destructor (unload). What this means is you can
now dynamically change even expansion port devices after the system is
loaded.

Note that this is incredibly dangerous and stupid, but ... oh well. The
whole point of this was for 21fx. There's no way to change the expansion
port device prior to loading a game, but if the 21fx isn't active, then
the reset vector hijack won't work. Now you can load a 21fx game, change
the expansion port device, and simply reset the system to active the
device.

The unification of design between controller port devices and expansion
port devices is nice, and overall this results in a reduction of code
(all of the Mapping stuff in Cartridge is gone, replaced with direct bus
mapping.) And there's always the potential to expand this system more in
the future now.

The big missing feature right now is the ability to push/pop mappings.
So if you look at how the 21fx does the reset vector, you might vomit
a little bit. But ... it works.

Also changed exit(0) to _exit(0) in the POSIX version of nall::execute.

[The _exit(0) thing is an attempt to make higan not crash when it tries
to launch icarus and it's not on $PATH. The theory is that higan forks,
then the child tries to exec icarus and fails, so it exits, all the
unique_ptrs clean up their resources and tell the X server to free
things the parent process is still using. Calling _exit() prevents
destructors from running, and seems to prevent the problem. -Ed.]
2016-04-09 20:21:18 +10:00
Tim Allen
7403e69307 Update to v098r02 release.
byuu says:

Changelog:
- SFC: fixed a regression on auto joypad polling due to missing
  parentheses
- SFC: exported new PPU::vdisp() const -> uint; function [1]
- SFC: merged PPU MMIO functions into the read/write handles (as
  I previously did for the CPU)
- higan: removed individual emulator core names (bnes, bsnes, bgb, bgba,
  bws) [2] Forgot:
- to remove /tomoko from the about dialog

[1] note that technically I was relying on the cached, per-frame
overscan setting when the CPU and light guns were polling the number of
active display scanlines per frame. This was technically incorrect as
you can change this value mid-frame and it'll kick in. I've never seen
any game toggle overscan every frame, we only know about this because
anomie tested this a long time ago. So, nothing should break, but ...
you know how the SNES is. You can't even look at the code without
something breaking, so I figured I'd mention it >_>

[2] I'll probably keep referring to the SNES core as bsnes anyway.
I don't mind if you guys use the b<system> names as shorthand. The
simplification is mostly to make the branding easier.
2016-04-09 15:20:41 +10:00
Tim Allen
19e1d89f00 Update to v098r01 release.
byuu says:

Changelog:
- SFC: balanced profile removed
- SFC: performance profile removed
- SFC: code for handling non-threaded CPU, SMP, DSP, PPU removed
- SFC: Coprocessor, Controller (and expansion port) shared Thread code
  merged to SFC::Cothread
  - Cothread here just means "Thread with CPU affinity" (couldn't think
    of a better name, sorry)
- SFC: CPU now has vector<Thread*> coprocessors, peripherals;
  - this is the beginning of work to allow expansion port devices to be
    dynamically changed at run-time
- ruby: all audio drivers default to 48000hz instead of 22050hz now if
  no frequency is assigned
  - note: the WASAPI driver can default to whatever the native frequency
    is; doesn't have to be 48000hz
- tomoko: removed the ability to change the frequency from the UI (but
  it will display the frequency used)
- tomoko: removed the timing settings panel
  - the goal is to work toward smooth video via adaptive sync
  - the model is broken by not being in control of the audio frequency
    anyway
  - it's further broken by PAL running at 50hz and WSC running at 75hz
  - it was always broken anyway by SNES interlace timing varying from
    progressive timing
- higan: audio/ stub created (for now, it's just nall/dsp/ moved here
  and included as a header)
- higan: video/ stub created
- higan/GNUmakefile: now includes build rules for essential components
  (libco, emulator, audio, video)

The audio changes are in preparation to merge wareya's awesome WASAPI
work without the need for the nall/dsp resampler.
2016-04-09 13:40:12 +10:00
Tim Allen
aff00506c5 Update to v098 hotfix release.
byuu says:

There was a minor SNES input regression spotted very shortly after
release.
2016-04-09 12:43:12 +10:00
Tim Allen
e846c83d47 Update to v098 release.
byuu says:

This release most notably adds WonderSwan and WonderSwan Color
emulation.

It is also the final release that will include the balanced and
performance profiles for bsnes.

Changelog (since v097):
- higan: added WonderSwan and WonderSwan Color emulation
- higan: simplified the coooperative-thread schedulers for all emulation
  cores
- higan: moved from native (u)int[8,16,32,64]_t types to
  Natural<T>/Integer<T> classes
- higan: major cleanups to the Makefiles; including auto-selection of
  processor cores
- loki: very barebones skeleton in place now; does absolutely nothing
  - these allow the removal of huge amounts of manual bit-twiddling with
    more readable alternatives
- FC: fixed PPU OAM reads (mask the correct bits when writing) [hex_usr]
- SFC: fixed expansion port device mapping on game load
- SFC: reworked the way SGB games were loaded -* SFC core can now be
  compiled without GB core (and thus without SGB support)
- SFC: added Super Disc expansion port device (although it's just
  a non-functional skeleton so far)
- SFC: bugfix to SharpRTC emulation regarding leap year extra day counts
  (Dai Kaijuu Monogatari II)
- SFC: major code cleanups to the CPU core and the R65816 processor base
  class
- SFC: added 21fx emulation (not the old 21fx that became MSu1; reusing
  the name for a new idea)
  - basic idea is to move the serial USART to the expansion port along
    with a reset vector hijack
- SFC: emulate reset vector pushing PC onto the stack on system soft
  reset
- GB: pass gekkio's if_ie_registers and boot_hwio-G test ROMs
- GBA: reworked all handling of MMIO functions: removed the get/set
  class functions
- nall: improved edge case return values for
  (basename,pathname,dirname,...)
- ruby: fixed ~AudioXAudio2() typo (now calls destructor on exit)
- ruby: if DirectSoundCreate fails (no sound device present), return
  false from init instead of crashing
- tomoko: added "All" option to filetype dropdown for ROM loading
  - allows loading GBC games in SGB mode
- tomoko: locate() updated to search multiple paths [2]
- tomoko: fixed some oddities when changing the audio frequency/latency
  settings
- icarus: can now work with WonderSwan and WonderSwan Color games

Note 1: 90% of the changelog for this release was related to the
WonderSwan emulation being in development. Doesn't make a lot of sense
to post about fixes since the code didn't exist publicly prior to this
release.
2016-04-09 12:38:50 +10:00
Tim Allen
06d44b4878 Update to v097r32 release.
byuu says:

Changelog:
- bsnes-accuracy emulates reset vector properly[1]
- bsnes-balanced compiles once more
- bsnes-performance compiles once more

The balanced and performance profiles are fixed for the last time. They
will be removed for v098r01.

Please test this WIP as much as you can. I intend to release v098 soon.
I know save states are a little unstable for the WS/WSC, but they work
well enough for a release. If I can't figure it out soon, I'm going to
post v098 anyway.

[1] this one's been a really long time coming, but ... one of the bugs
I found when I translated Tekkaman Blade was that my translation patch
would crash every now and again when you hit the reset button on a real
SNES, but it always worked upon power on.

Turns out that while power-on initializes the stack register to $01ff,
reset does things a little bit differently. Reset actually triggers the
reset interrupt vector after putting the CPU into emulation mode, but it
doesn't initialize the stack pointer. The net effect is that the stack
high byte is set to $01, and the low byte is left as it was. And then
the reset vector runs, which pushes the low 16-bits of the program
counter, plus the processor flags, onto the stack frame. So you can
actually tell where the game was at when the system was reset ... sort
of.

It's a really weird behavior to be sure. But here's the catch: say
you're hacking a game, and so you hook the reset vector with jsl
showMyTranslationCreditsSplashScreen, and inside this new subroutine,
you then perform whatever bytes you hijacked, and then initialize the
stack frame to go about your business drawing the screen, and when
you're done, you return via rtl.

Generally, this works fine. But if S={0100, 0101, or 0102}, then the
stack will wrap due to being in emulation mode at reset. So it will
write to {0100, 01ff, 01fe}. But now in your subroutine, you enable
native mode. So when you return from your subroutine hijack, it reads
the return address from {01ff, 0200, 0201} instead of the expected
{01ff, 0100, 0101}. Thus, you get an invalid address back, and you
"return" to the wrong location, and your program dies.

The odds of this happening depend on how the game handles S, but
generally speaking, it's a ~1:85 chance.

By emulating this behavior, I'll likely expose this bug in many ROM
hacks that do splash screen hooks like this, including my own Tekkaman
Blade translation. And it's also very possible that there are commercial
games that screw this up as well.

But, it's what the real system does. So if any crashes start happening
as of this WIP upon resetting the game, well ... it'd happen on real
hardware, too.
2016-04-03 21:17:20 +10:00
Tim Allen
25eaaa82f4 Update to v097r31 release.
byuu says:

Changelog:
- WS: fixed sprite window clipping (again)
- WS: don't set IRQ status bits of IRQ enable bits are clear
- SFC: signed/unsigned -> int/uint for DSP core
- SFC: removed eBoot
- SFC: added 21fx (not the same as the old precursor to MSU1; just
  reusing the name)

Note: XI Little doesn't seem to be fixed after all ... but the other
three are. So I guess we're at 13 bugs :( And holy shit that music when
you choose a menu option is one of the worst sounds I've ever heard in
my life >_<
2016-03-29 20:15:01 +11:00
Tim Allen
2d83300235 Update to v097r30 release.
byuu says:

Changelog:
- fixed sprite window attribute bit (Final Fantasy, Tekken Card
  Challenge, etc)
- rewrote renderer to support 2bpp color mode (Dark Eyes, Dokodemo
  Hamster, Flash Koibito-kun, etc)
2016-03-29 19:44:03 +11:00
Tim Allen
680d16561e Update to v097r29 release.
byuu says:

Changelog:
- fixed DAS instruction (Judgment Silversword score)
- fixed [VH]TMR_FREQ writes (Judgement Silversword audio after area 20)
- fixed initialization of SP (fixes seven games that were hanging on
  startup)
- added SER_STATUS and SER_DATA stubs (fixes four games that were
  hanging on startup)
- initialized IEEP data (fixes Super Robot Taisen Compact 2 series)
  - note: you'll need to delete your internal.com in WonderSwan
    (Color).sys folders
- fixed CMPS and SCAS termination condition (fixes serious bugs in four
  games)
- set read/writeCompleted flags for EEPROM status (fixes Tetsujin 28
  Gou)
- major code cleanups to SFC/R65816 and SFC/CPU
  - mostly refactored disassembler to output strings instead of using
    char* buffer
  - unrolled all the subfolders on sfc/cpu to a single directory
  - corrected casing for all of sfc/cpu and a large portion of
    processor/r65816

I kind of went overboard on the code cleanup with this WIP. Hopefully
nothing broke. Any testing one can do with the SFC accuracy core would
be greatly appreciated.

There's still an absolutely huge amount of work left to go, but I do
want to eventually refresh the entire codebase to my current coding
style, which is extremely different from stuff that's been in higan
mostly untouched since ~2006 or so. It's dangerous and fickle work, but
if I don't do it, then the code will be a jumbled mess of several
different styles.
2016-03-26 12:56:15 +11:00
Tim Allen
379ab6991f Update to v097r28 release.
byuu says:

Changelog: (all WSC unless otherwise noted)
- fixed LINECMP=0 interrupt case (fixes FF4 world map during airship
  sequence)
- improved CPU timing (fixes Magical Drop flickering and FF1 battle
  music)
- added per-frame OAM caching (fixes sprite glitchiness in Magical Drop,
  Riviera, etc.)
- added RTC emulation (fixes Dicing Knight and Judgement Silversword)
- added save state support
- added cheat code support (untested because I don't know of any cheat
  codes that exist for this system)
- icarus: can now detect games with RTC chips
- SFC: bugfix to SharpRTC emulation (Dai Kaijuu Monogatari II)
  - ( I was adding the extra leap year day to all 12 months instead of
    just February ... >_< )

Note that the RTC emulation is very incomplete. It's not really
documented at all, and the two games I've tried that use it never even
ask you to set the date/time (so they're probably just using it to count
seconds.) I'm not even sure if I've implement the level-sensitive
behavior correctly (actually, now that I think about it, I need to mask
the clear bit in INT_ACK for the level-sensitive interrupts ...)

A bit worried about the RTC alarm, because it seems like it'll fire
continuously for a full minute. Or even if you turn it off after it
fires, then that doesn't seem to be lowering the line until the next
second ticks on the RTC, so that likely needs to happen when changing
the alarm flag.

Also not sure on this RTC's weekday byte. On the SharpRTC, it actually
computes this for you. Because it's not at all an easy thing to
calculate yourself in 65816 or V30MZ assembler. About 40 lines of code
to do it in C. For now, I'm requiring the program to calculate the value
itself.

Also note that there's some gibberish tiles in Judgement Silversword,
sadly. Not sure what's up there, but the game's still fully playable at
least.

Finally, no surprise: Beat-Mania doesn't run :P
2016-03-25 17:19:08 +11:00
Tim Allen
d3413db04a Update to v097r27 release.
byuu says:

Absolutely major improvements to the WS/C emulation today.

Changelog: (all WS/C related)
- fixed channel 3 sweep pitch adjustment
- fixed channel 3 sweep value sign extension
- removed errant channel 5 speed setting (not what's really going on)
- fixed sign extension on channel 5 samples
- improved DAC mixing of all five audio channels
- fixed r26 regression with PPU timing loop
- fixed sprite windowing behavior (sprite attribute flag is window mode;
  not window enable)
- added per-scanline register latching to the PPU
- IRQs should terminate HLT even when the IRQ enable register bits are
  clear
- fixed PALMONO reads
- added blur emulation
- added color emulation (based on GBA, so it heavily desaturates colors;
  not entirely correct, but it helps a lot)
- no longer decimating audio to 24KHz; running at full 3.072MHz through
  the windowed sinc filter [1]
- cleaned up PPU portRead / portWrite functions significantly
- emulated a weird quirk as mentioned by trap15 regarding timer
  frequency writes enabling said timers [2]
- emulated LCD_CTRL sleep bit; screen can now be disabled (always draws
  black in this case for now)
- improved OAM caching; but it's still disabled because it causes huge
  amounts of sprite glitches (unsure why)
- fixed rendering of sprites that wrap around the screen edges back to
  the top/left of the display
- emulated keypad interrupts
- icarus: detect orientation bit in game header
- higan: use orientation setting in manifest to set default screen
  rotation

[1] the 24KHz -> 3.072MHz sound change is huge. Sound is substantially
improved over the previous WIPs. It does come at a pretty major speed
penalty, though. This is the highest frequency of any system in higan
running through an incredibly (amazing, yet) demanding sinc resampler.
Frame rate dropped from around 240fps to 150fps with the sinc filter on.
If you choose a different audio filter, you'll get most of that speed
back, but audio will sound worse again.

[2] we aren't sure if this is correct hardware behavior or not. It seems
to very slightly help Magical Drop, but not much.

The blur emulation is brutal. It's absolutely required for Riviera's
translucency simulation of selected menu items, but it causes serious
headaches due to the WS's ~75hz refresh rate running on ~60hz monitors
without vsync. It's probably best to leave it off and just deal with the
awful flickering on Riviera's menu options.

Overall, WS/C emulation is starting to get quite usable indeed. Couple
of major bugs that I'd really like to get fixed before releasing it,
though. But they're getting harder and harder to fix ...

Major Bugs:
- Final Fantasy battle background music is absent. Sound effects still
  work. Very weird.
- Final Fantasy IV scrolling during airship flight opening sequence is
  horribly broken. Scrolls one screen at a time.
- Magical Drop flickers like crazy in-game. Basically unplayable like
  this.
- Star Hearts character names don't appear in the smaller dialog box
  that pops up.

Minor Bugs:
- Occasional flickering during Riviera opening scenes.
- One-frame flicker of Leda's sprite at the start of the first stage.
2016-03-19 18:35:25 +11:00
Tim Allen
a7f7985581 Update to v097r26 release.
byuu says:

Changelog:
- WS: fixed 8-bit sign-extended imul (fixes Star Hearts completely,
  Final Fantasy world map)
- WS: fixed rcl/rcr carry shifting (fixes Crazy Climber, others)
- WS: added sound DMA emulation (Star Hearts rain sound for one example)
- WS: added OAM caching, but it's forced every line for now because
  otherwise there are too many sprite glitches
- WS: use headphoneEnable bit instead of speakerEnable bit (fixes muted
  audio in games)
- WS: various code cleanups (I/O mapping, audio channel naming, etc)

The hypervoice channel doesn't sound all that great just yet. But I'm
not sure how it's supposed to sound. I need a better example of some
more complex music.

What's left are some unknown register status bits (especially in the
sound area), keypad interrupts, RTC emulation, CPU prefetch emulation.
And then it's all just bugs. Lots and lots of bugs that need to be
fixed.

EDIT: oops, bad typo in the code.

ws/ppu/ppu.cpp line 20: change range(256) to range(224).

Also, delete the r.speed stuff from channel5.cpp to make the rain sound
a lot better in Star Hearts. Apparently that's outdated and not what the
bits really do.
2016-03-17 22:28:15 +11:00
Tim Allen
b586471562 Update to v097r25 release.
byuu says:

Changelog:
- WS: added HblankTimer and VblankTimer IRQs; although they don't appear
  to have any effect on any games that use them :/
- WS: added sound emulation; works perfectly in some games (eg Riviera);
  is completely silent in most games (eg GunPey)

The sound emulation only partially supports the hypervoice (headphone
only) channel. I need to implement the SDMA before it'll actually do
anything useful. I'm a bit confused about how exactly things work. It
looks like the speaker volume shift and clamp only applies to speaker
mode and not headphone mode, which is very weird. Then there's the
software possibility of muting the headphones and/or the speaker.
Preferably, I want to leave the emulator always in headphone mode for
the extra audio channel. If there are games that force-mute the
headphones, but not speakers, then I may need to force headphones back
on but with the hypervoice channel disabled. I guess we'll see how
things go.

Rough guess is probably that the channels default to enabled after the
IPLROM, and games aren't bothering to manually enable them or something.
2016-03-14 22:03:32 +11:00
Tim Allen
c33065fbd1 Update to v097r24 release.
byuu says:

Changelog:
- WS: fixed bug when IRQs triggered during a rep string instruction
- WS: added sprite attribute caching (per-scanline); absolutely massive
  speed-up
- WS: emulated limit of 32 sprites per scanline
- WS: emulated the extended PPU register bit behavior based on the
  DISP_CTRL tile bit-depth setting
- WS: added "Rotate" key binding; can be used to flip the WS display
  between horizontal and vertical in real-time

The prefix emulation may not be 100% hardware-accurate, but the edge
cases should be extreme enough to not come up in the WS library. No way
to get the emulation 100% down without intensive hardware testing.
trap15 pointed me at a workflow diagram for it, but that diagram is
impossible without a magic internal stack frame that grows with every
IRQ, and can thus grow infinitely large.

The rotation thing isn't exactly the most friendly set-up, but oh well.
I'll see about adding a default rotation setting to manifests, so that
games like GunPey can start in the correct orientation. After that, if
the LCD orientation icon turns out to be reliable, then I'll start using
that. But if there are cases where it's not reliable, then I'll leave it
to manual button presses.

Speaking of icons, I'll need a set of icons to render on the screen.
Going to put them to the top right on vertical orientation, and on the
bottom left for horizontal orientation. Just outside of the video
output, of course.

Overall, WS is getting pretty far along, but still some major bugs in
various games. I really need sound emulation, though. Nobody's going to
use this at all without that.
2016-03-13 11:22:15 +11:00
Tim Allen
79e7e6ab9e Update to v097r23 release.
byuu says:

Changelog:
- emulated SuperDisc $21e1 basic interface (NEC 4-bit MCU); all hardware
  tests pass now (but they don't test much)
- WS/V30MZ: fixed inc/dec reg flag calculation
- WS/V30MZ: fixed lds/les instructions

WS/C compatibility should be way up now. SuperDisc BIOS passes all tests
now (but they only test for the presence of the interface, nothing
more.)
2016-03-13 11:22:14 +11:00
Tim Allen
3d3ac8c1db Update to v097r22 release.
byuu says:

Changelog:
- WS: fixed lods, scas instructions
- WS: implemented missing GRP4 instructions
- WS: fixed transparency for screen one
- WSC: added color-mode PPU rendering
- WS+WSC: added packed pixel mode support
- WS+WSC: added dummy sound register reads/writes
- SFC: added threading to SuperDisc (it's hanging for right now; need to
  clear IRQ on $21e2 writes)

SuperDisc Timer and Sound Check were failing before due to not turning
off IRQs on $21e4 clear, so I'm happy that's fixed now.

Riviera starts now, and displays the first intro screen before crashing.
Huge, huge amounts of corrupted graphics, though. This game's really
making me work for it :(

No color games seem fully playable yet, but a lot of monochrome and
color games are now at least showing more intro screen graphics before
dying.

This build defaults to horizontal orientation, but I left the inputs
bound to vertical orientation. Whoops. I still need to implement
a screen flip key binding.
2016-03-13 11:22:14 +11:00
Tim Allen
b0d2f5033e Update to v097r21 release.
byuu says:

Changelog:
- icarus: WS/C detects RAM type/size heuristically now
- icarus: WS/C uses ram type=$type instead of $type
- WS: use back color instead of white for backdrop
- WS: fixed sprite count limit; removes all the garbled sprites from
  GunPey
- WS: hopefully fixed sprite priority with screen 2
- WS: implemented keypad polling; GunPey is now fully playable
- SNES: added Super Disc expansion port device (doesn't do anything,
  just for testing)

Note: WS is hard-coded to vertical orientation right now. But there's
basic code in there for all the horizontal stuff.
2016-03-13 11:22:14 +11:00
Tim Allen
570eb9c5f5 Update to v097r20 release.
byuu says:

Changelog:
- WS: fixed a major CPU bug where I was using the wrong bits for
  ModR/M's memory mode
- WS: added grayscale PPU emulation (exceptionally buggy)

GunPey now runs, as long as you add:

    eeprom name=save.ram size=0x800

to the manifest after importing with icarus.

Right now, you can't control the game due to missing keypad polling.
There's also a lot of glitchiness with the sprites. Seems like they're
not getting properly cleared sometimes or something.

Also, the PPU emulation is totally unrealistic bullshit. I decode and
evaluate every single tile and sprite on every single pixel of output.
No way in hell the hardware could ever come close to that. The speed's
around 500fps without the insane sprite evaluations, and around 90fps
with it. Obviously, I'll fix this in time.

Nothing else seems to run that I've tried. Not even far enough to
display any output whatsoever. Tried Langrisser Millenium, Rockman
& Forte and Riviera. I really need to update icarus to try and encode
eeprom/sram sizes, because that's going to break a lot of stuff if it's
missing.
2016-03-13 11:22:14 +11:00
Tim Allen
7dc62e3a69 Update to v097r19 release.
byuu says:

Changelog:
- fixed nall/windows/guard.hpp
- fixed hiro/(windows,gtk)/header.hpp
- fixed Famicom PPU OAM reads (mask the correct bits when writing)
  [hex_usr]
- removed the need for (system := system) lines from higan/GNUmakefile
- added "All" option to filetype dropdown for ROM loading
  - allows loading GBC games in SGB mode (and technically non-GB(C)
    games, which will obviously fail to do anything)
- loki can load and play game folders now (command-line only) (extremely
  unimpressive; don't waste your time :P)
  - the input is extremely hacked in as a quick placeholder; not sure
    how I'm going to do mapping yet for it
2016-03-13 11:22:14 +11:00
Tim Allen
fc7d5991ce Update to v097r18 release.
byuu says:

Changelog:
- fixed SNES sprite priority regression from r17
- added nall/windows/guard.hpp to guard against global namespace
  pollution (similar to nall/xorg/guard.hpp)
- almost fixed Windows compilation (still accuracy profile only, sorry)
- finished porting all of gba/ppu's registers over to the new .bit,.bits
  format ... all GBA registers.cpp files gone now
- the "processors :=" line in the target-$(ui)/GNUmakefile is no longer
  required
  - processors += added to each emulator core
  - duplicates are removed using the new nall/GNUmakefile's $(unique)
    function
- SFC core can be compiled without the GB core now
  - "-DSFC_SUPERGAMEBOY" is required to build in SGB support now (it's
    set in target-tomoko/GNUmakefile)
- started once again on loki (higan/target-loki/) [as before, loki is
  Linux/BSD only on account of needing hiro::Console]

loki shouldn't be too horrendous ... I hope. I just have the base
skeleton ready for now. But the code from v094r08 should be mostly
copyable over to it. It's just that it's about 50KiB of incredibly
tricky code that has to be just perfect, so it's not going to be quick.
But at least with the skeleton, it'll be a lot easier to pick away at it
as I want.

Windows compilation fix: move hiro/windows/header.hpp line 18 (header
guard) to line 16 instead.
2016-03-13 11:22:14 +11:00
Tim Allen
29be18ce0c Update to v097r17 release.
byuu says:

Changelog:
- ruby: if DirectSoundCreate fails (no sound device present), return
  false from init instead of crashing
- nall: improved edge case return values for
  (basename,pathname,dirname,...)
- nall: renamed file_system_object class to inode
- nall: varuint_t replaced with VariadicNatural; which contains
  .bit,.bits,.byte ala Natural/Integer
- nall: fixed boolean compilation error on Windows
- WS: popa should not restore SP
- GBA: rewrote the CPU/APU cores to use the .bit,.bits functions;
  removed registers.cpp from each

Note that the GBA changes are extremely major. This is about five hours
worth of extremely delicate work. Any slight errors could break
emulation in extremely bad ways. Let's hold off on extensive testing
until the next WIP, after I do the same to the PPU.

So far ... endrift's SOUNDCNT_X I/O test is failing, although that code
didn't change, so clearly I messed up SOUNDCNT_H somehow ...

To compile on Windows:

1. change nall/string/platform.hpp line 47 to

    return slice(result, 0, 3);

2. change ruby/video.wgl.cpp line 72 to

    auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {

3. add this line to the very top of hiro/windows/header.cpp:

    #define boolean FuckYouMicrosoft
2016-03-13 11:22:14 +11:00
Tim Allen
810cbdafb4 Update to v097r16 release.
byuu says:

Changelog:
- sfc/ppu/sprite updated to use new .bit(s) functions; masked sizes
  better; added valid flags instead of using magic numbers
- ws/ppu updates to use new .bit(s) functions
- ws/ppu: added line compare interrupt support
- added ws/eeprom; emulation of WS/WSC internal EEPROM and cartridge
  EEPROM (1kbit - 16kbit supported)
- added basic read/write handlers for remaining WS/WSC PPU registers

WS EEPROM emulation is basically a direct copy of trap15's code. Still
some unknown areas in there, but hopefully it's enough to get further
into games that depend on EEPROM support. Note that you'll have to
manually add the eeprom line to the manifest for now, as icarus doesn't
know how to detect EEPROM/sizes yet.

I figured the changes to the SNES PPU sprites would slow it down a tad,
but it actually sped it up. Most of the impact from the integer classes
are gone now.
2016-03-13 11:22:10 +11:00
Tim Allen
4b29f4bad7 Update to v097r15 release.
byuu says:

Changelog:
- higan now uses Natural<Size>/Integer<Size> for its internal types
- Super Famicom emulation now uses uint24 instead of uint for bus
  addresses (it's a 24-bit bus)
- cleaned up gb/apu MMIO writes
- cleaned up sfc/coprocessor/msu1 MMIO writes
- ~3% speed penalty

I've wanted to do that 24-bit bus thing for so long, but have always
been afraid of the speed impact. It's probably going to hurt
balanced/performance once they compile again, but it wasn't significant
enough to harm the accuracy core's frame rate, thankfully. Only lost one
frame per second.

The GBA core handlers are clearly going to take a lot more work. The
bit-ranges will make it substantially easier to handle, though. Lots of
32-bit registers where certain values span multiple bytes, but we have
to be able to read/write at byte-granularity.
2016-02-16 20:32:49 +11:00
Tim Allen
ef65bb862a Update to 20160215 release.
byuu says:

Got it. Wow, that didn't hurt nearly as much as I thought it was going
to.

Dropped from 127.5fps to 123.5fps to use Natural/Integer for
(u)int(8,16,32,64).

That's totally worth the cost.
2016-02-16 20:27:55 +11:00
Tim Allen
0d0af39b44 Update to v097r14 release.
byuu says:

This is a few days old, but oh well.

This WIP changes nall,hiro,ruby,icarus back to (u)int(8,16,32,64)_t.

I'm slowly pushing for (u)int(8,16,32,64) to use my custom
Integer<Size>/Natural<Size> classes instead. But it's going to be one
hell of a struggle to get that into higan.
2016-02-16 20:11:58 +11:00
Tim Allen
6c83329cae Update to v097r13 release.
byuu says:

I refactored my schedulers. Added about ten lines to each scheduler, and
removed about 100 lines of calling into internal state in the scheduler
for the FC,SFC cores and about 30-40 lines for the other cores. All of
its state is now private.

Also reworked all of the entry points to static auto Enter() and auto
main(). Where Enter() handles all the synchronization stuff, and main()
doesn't need the while(true); loop forcing another layer of indentation
everywhere.

Took a few hours to do, but totally worth it. I'm surprised I didn't do
this sooner.

Also updated icarus gmake install rule to copy over the database.
2016-02-09 22:51:12 +11:00
Tim Allen
32a95a9761 Update to v097r12 release.
byuu says:

Nothing WS-related this time.

First, I fixed expansion port device mapping. On first load, it was
mapping the expansion port device too late, so it ended up not taking
effect. I had to spin out the logic for that into
Program::connectDevices(). This was proving to be quite annoying while
testing eBoot (SNES-Hook simulation.)

Second, I fixed the audio->set(Frequency, Latency) functions to take
(uint) parameters from the configuration file, so the weird behavior
around changing settings in the audio panel should hopefully be gone
now.

Third, I rewrote the interface->load,unload functions to call into the
(Emulator)::System::load,unload functions. And I have those call out to
Cartridge::load,unload. Before, this was inverted, and Cartridge::load()
was invoking System::load(), which I felt was kind of backward.

The Super Game Boy really didn't like this change, however. And it took
me a few hours to power through it. Before, I had the Game Boy core
dummying out all the interface->(load,save)Request calls, and having the
SNES core make them for it. This is because the folder paths and IDs
will be different between the two cores.

I've redesigned things so that ICD2's Emulator::Interface overloads
loadRequest and saveRequest, and translates the requests into new
requests for the SuperFamicom core. This allows the Game Boy code to do
its own loading for everything without a bunch of Super Game Boy special
casing, and without any awkwardness around powering on with no cartridge
inserted.

This also lets the SNES side of things simply call into higher-level
GameBoy::interface->load,save(id, stream) functions instead of stabbing
at the raw underlying state inside of various Game Boy core emulation
classes. So things are a lot better abstracted now.
2016-02-08 14:17:59 +11:00
Tim Allen
a89a3da77a Update to v097r11 release.
byuu says:

Alright, well interrupts are in. At least Vblank is.

I also fixed a bug in vector() indexing, MoDRM mod!=3&&reg==6 using SS
instead of DS, opcodes a0-a3 allowing segment override, and added the
"irq_disable" stuff to the relevant opcodes to suppress IRQs after
certain instructions.

But unfortunately ... still no go on Riviera. It's not reading any
unmapped ports, and although it enables Vblank IRQs and they set port
$b4's status bit, the game never sets the IE flag, so no interrupts ever
actually fire. The game does indeed appear to be sitting in a rather
huge loop, which is probably dependent upon some RAM variable being set
from the Vblank IRQ, but I don't know how I'm supposed to be triggering
it.

... I'm really quite stumped here >_>
2016-02-05 08:18:06 +11:00
Tim Allen
7a748e093e Update to v097r10 release.
byuu says:

All 256 instructions implemented fully. Fixed a major bug with
instructions that both read and write to ModRM with displacement.
Riviera now runs into an infinite loop ... possibly crashed, possibly
waiting on interrupts or in to return something. Added a bunch of PPU
settings registers, but nothing's actually rendering with them yet.
2016-02-04 21:29:08 +11:00
Tim Allen
d158c8f293 Update to v097r09 release.
244 of 256 opcodes implemented now, although the interrupt triggering
portions are missing from them still. Much better handling of prefixes
now.

I definitely have a newfound hatepreciation for x86 now >_>
2016-02-03 21:24:58 +11:00
Tim Allen
71bda4144a Update to v097r08 release.
byuu says:

Up to 211 opcodes implemented, with the caveat that the four opcodes
that make up group 3 and group 4 don't do anything yet. Both groups seem
to have some "illegal" instructions in them, so that'll be "fun".

I have a new mechanic in place for opcode prefixes, but it could use
some work still. I also only have it working to override ModRM mem
addressing, but of course it does it in a lot of other places like the
string operations.

Making it about 5.5 million instructions into Gunpey now, but of course
that doesn't mean much. Could be going off the rails at any point due to
CPU bugs or unimplemented ports. Riviera's still crashing.
2016-02-03 21:07:50 +11:00
Tim Allen
ad51f1478e Update to v097r07 release.
byuu says:

26 hours in, 173 instructions implemented. Although the four segment
prefix opcodes don't actually do anything yet. There's less than 256
actual instructions on the 80186, not sure of the exact count.

Gunpey gets around ~8,200 instructions in before hitting an unsupported
opcode (loop). Riviera goes off the rails on a retf and ends up
executing an endless stream of bad opcodes in RAM =( Both games hammer
the living shit out of the in/out ports pretty much immediately.
2016-02-02 21:51:17 +11:00
Tim Allen
d0ddd87e9c Update to v097r06 release.
byuu says:

Man, the 80186 is taking a lot longer to implement than I thought it
would. So far I'm 18 hours into this emulator. Whereas I had Super Mario
Bros fully playable (no sound) in 12 hours for the NES >_>

I refactored all the byte/word variant functions to single functions
that take a size parameter. Cuts the amount of code in half.

Also implemented repz/repnz + movsb/movsw, so Riviera now gets 299
instructions in before dying. Nobody really bothers to explain how the
CPU actually implements these instructions, but I think I have it right:
ignore non-string opcodes that follow rep, invoke the string operations
inside the rep opcodes to prevent interrupts from triggering between the
two (which will be even more fun for segment selector overrides ...)

The next opcode needed is 0xC7, which ... throws ModRM on its head. In
this mode, ModRM is only used to determine the target operand (and it
doesn't use the middle bits for that at all), and the source is an
immediate that follows it. Gonna have to waste a few more hours thinking
about how best to handle that.

Also, disabled HiDPI for higan as well on OS X.
2016-01-31 18:59:44 +11:00
Tim Allen
605a8aa3e9 Update to v097r05 release.
byuu says:

More V30MZ implemented, a lot more to go.

icarus now supports importing WS and WSC games. It expects them to have
the correct file extension, same for GB and GBC.

> Ugh, apparently HiDPI icarus doesn't let you press the check boxes.

I set the flag value in the plist to false for now. Forgot to do it for
higan, but hopefully I won't forget before release.
2016-01-30 17:40:35 +11:00
Tim Allen
a8323d0d2b Update to v097r04 release.
byuu says:

Lots of improvements. We're now able to start executing some V30MZ
instructions. 32 of 256 opcodes implemented so far.

I hope this goes without saying, but there's absolutely no point in
loading WS/WSC games right now. You won't see anything until I have the
full CPU and partial PPU implemented.

ROM bank 2 works properly now, the I/O map is 16-bit (address) x 16-bit
(data) as it should be*, and I have a basic disassembler in place
(adding to it as I emulate new opcodes.)

(* I don't know what happens if you access an 8-bit port in 16-bit mode
or vice versa, so for now I'm just treating the handlers as always being
16-bit, and discarding the upper 8-bits when not needed.)
2016-01-28 22:39:49 +11:00
Tim Allen
d7998b23ef Update to v097r03 release.
byuu says:

So, this WIP starts work on something new for higan. Obviously, I can't
keep it a secret until it's ready, because I want to continue daily WIP
releases, and of course, solicit feedback as I go along.
2016-01-27 22:31:39 +11:00
Tim Allen
344e63d928 Update to v097r02 release.
byuu says:

Note: balanced/performance profiles still broken, sorry.

Changelog:
- added nall/GNUmakefile unique() function; used on linking phase of
  higan
- added nall/unique_pointer
- target-tomoko and {System}::Video updated to use
  unique_pointer<ClassName> instead of ClassName* [1]
- locate() updated to search multiple paths [2]
- GB: pass gekkio's if_ie_registers and boot_hwio-G test ROMs
- FC, GB, GBA: merge video/ into the PPU cores
- ruby: fixed ~AudioXAudio2() typo

[1] I expected this to cause new crashes on exit due to changing the
order of destruction of objects (and deleting things that weren't
deleted before), but ... so far, so good. I guess we'll see what crops
up, especially on OS X (which is already crashing for unknown reasons on
exit.)

[2] right now, the search paths are: programpath(), {configpath(),
"higan/"}, {localpath(), "higan/"}; but we can add as many more as we
want, and we can also add platform-specific versions.
2016-01-25 22:27:18 +11:00
Tim Allen
f1ebef2ea8 Update to v097r01 release.
byuu says:

A minor WIP to get us started.

Changelog:
- System::Video merged to PPU::Video
- System::Audio merged to DSP::Audio
- System::Configuration merged to Interface::Settings
- created emulator/emulator.cpp and accompanying object file for shared
  code between all cores

Currently, emulator.cpp just holds a videoColor() function that takes
R16G16B16, performs gamma/saturation/luma adjust, and outputs
(currently) A8R8G8B8. It's basically an internal function call for cores
to use when generating palette entries. This code used to exist inside
ui-tomoko/program/interface.cpp, but we have to move it internal for
software display emulation. But in the future, we could add other useful
cross-core functionality here.
2016-01-23 18:29:34 +11:00
815 changed files with 24476 additions and 35850 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
higan/profile/WonderSwan.sys/internal.ram
higan/profile/WonderSwan Color.sys/internal.ram

View File

@@ -1,16 +1,11 @@
include ../nall/GNUmakefile
fc := fc
sfc := sfc
gb := gb
gba := gba
profile := accuracy
target := tomoko
# target := loki
# console := true
flags += -I. -I.. -O3
objects := libco
objects := libco audio video resource
# profile-guided optimization mode
# pgo := instrument
@@ -58,6 +53,11 @@ compile = \
all: build;
obj/libco.o: ../libco/libco.c $(call rwildcard,../libco/)
obj/audio.o: audio/audio.cpp $(call rwildcard,audio/)
obj/video.o: video/video.cpp $(call rwildcard,video/)
obj/resource.o: resource/resource.cpp $(call rwildcard,resource/)
ui := target-$(target)
include $(ui)/GNUmakefile

90
higan/audio/audio.cpp Normal file
View File

@@ -0,0 +1,90 @@
#include <emulator/emulator.hpp>
namespace Emulator {
#include "stream.cpp"
Audio audio;
auto Audio::reset(maybe<uint> channels_, maybe<double> frequency_) -> void {
if(channels_) channels = channels_();
if(frequency_) frequency = frequency_();
streams.reset();
reverb.reset();
reverb.resize(channels);
for(auto c : range(channels)) {
reverb[c].resize(7);
reverb[c][0].resize(1229);
reverb[c][1].resize(1559);
reverb[c][2].resize(1907);
reverb[c][3].resize(4057);
reverb[c][4].resize(8117);
reverb[c][5].resize(8311);
reverb[c][6].resize(9931);
}
}
auto Audio::setInterface(Interface* interface) -> void {
this->interface = interface;
}
auto Audio::setVolume(double volume) -> void {
this->volume = volume;
}
auto Audio::setBalance(double balance) -> void {
this->balance = balance;
}
auto Audio::setReverb(bool enabled) -> void {
this->reverbEnable = enabled;
}
auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stream> {
shared_pointer<Stream> stream = new Stream;
stream->reset(channels, frequency, this->frequency);
streams.append(stream);
return stream;
}
auto Audio::process() -> void {
while(true) {
for(auto& stream : streams) {
if(!stream->pending()) return;
}
double samples[channels] = {0};
for(auto& stream : streams) {
double buffer[16];
uint length = stream->read(buffer), offset = 0;
for(auto c : range(channels)) {
samples[c] += buffer[offset];
if(++offset >= length) offset = 0;
}
}
for(auto c : range(channels)) {
samples[c] /= streams.size();
if(reverbEnable) {
samples[c] *= 0.125;
for(auto n : range(7)) samples[c] += 0.125 * reverb[c][n].last();
for(auto n : range(7)) reverb[c][n].write(samples[c]);
samples[c] *= 8.000;
}
samples[c] *= volume;
}
if(channels == 2) {
if(balance < 0.0) samples[1] *= 1.0 + balance;
if(balance > 0.0) samples[0] *= 1.0 - balance;
}
interface->audioSample(samples, channels);
}
}
}

65
higan/audio/audio.hpp Normal file
View File

@@ -0,0 +1,65 @@
#pragma once
#include <nall/dsp/iir/biquad.hpp>
#include <nall/dsp/resampler/cubic.hpp>
namespace Emulator {
struct Interface;
struct Audio;
struct Stream;
struct Audio {
auto reset(maybe<uint> channels = nothing, maybe<double> frequency = nothing) -> void;
auto setInterface(Interface*) -> void;
auto setVolume(double volume) -> void;
auto setBalance(double balance) -> void;
auto setReverb(bool enabled) -> void;
auto createStream(uint channels, double frequency) -> shared_pointer<Stream>;
private:
auto process() -> void;
Interface* interface = nullptr;
vector<shared_pointer<Stream>> streams;
uint channels = 0;
double frequency = 0.0;
double volume = 1.0;
double balance = 0.0;
bool reverbEnable = false;
vector<vector<queue<double>>> reverb;
friend class Stream;
};
struct Stream {
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
auto pending() const -> bool;
auto read(double* samples) -> uint;
auto write(const double* samples) -> void;
template<typename... P> auto sample(P&&... p) -> void {
double samples[sizeof...(P)] = {forward<P>(p)...};
write(samples);
}
private:
const uint order = 6; //Nth-order filter (must be an even number)
struct Channel {
vector<DSP::IIR::Biquad> iir;
DSP::Resampler::Cubic resampler;
};
vector<Channel> channels;
friend class Audio;
};
extern Audio audio;
}

35
higan/audio/stream.cpp Normal file
View File

@@ -0,0 +1,35 @@
auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency) -> void {
channels.reset();
channels.resize(channels_);
for(auto& channel : channels) {
if(outputFrequency / inputFrequency <= 0.5) {
channel.iir.resize(order / 2);
for(auto phase : range(order / 2)) {
double q = DSP::IIR::Biquad::butterworth(order, phase);
channel.iir[phase].reset(DSP::IIR::Biquad::Type::LowPass, 20000.0 / inputFrequency, q);
}
}
channel.resampler.reset(inputFrequency, outputFrequency);
}
}
auto Stream::pending() const -> bool {
return channels && channels[0].resampler.pending();
}
auto Stream::read(double* samples) -> uint {
for(auto c : range(channels)) samples[c] = channels[c].resampler.read();
return channels.size();
}
auto Stream::write(const double* samples) -> void {
for(auto c : range(channels)) {
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
for(auto& iir : channels[c].iir) sample = iir.process(sample);
channels[c].resampler.write(sample);
}
audio.process();
}

View File

@@ -1,6 +1,6 @@
[Desktop Entry]
Name=higan
Comment=Nintendo emulator
Comment=Emulator
Exec=higan
Icon=higan
Terminal=false

View File

@@ -11,7 +11,7 @@
<key>CFBundleIconFile</key>
<string>higan.icns</string>
<key>NSHighResolutionCapable</key>
<true/>
<false/>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>

View File

@@ -1,23 +1,18 @@
#pragma once
#include <nall/nall.hpp>
#include <nall/dsp.hpp>
using namespace nall;
#include <audio/audio.hpp>
#include <video/video.hpp>
#include <resource/resource.hpp>
namespace Emulator {
static const string Name = "higan";
static const string Version = "097";
static const string Version = "099";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";
#if defined(PROFILE_ACCURACY)
static const string Profile = "Accuracy";
#elif defined(PROFILE_BALANCED)
static const string Profile = "Balanced";
#elif defined(PROFILE_PERFORMANCE)
static const string Profile = "Performance";
#endif
}
#include "interface.hpp"
@@ -52,5 +47,3 @@ template<typename R, typename... P> struct hook<auto (P...) -> R> {
#else
#define privileged private
#endif
using varuint = varuint_t<uint>;

View File

@@ -4,6 +4,7 @@ namespace Emulator {
struct Interface {
struct Information {
string manufacturer;
string name;
uint width;
uint height;
@@ -16,13 +17,13 @@ struct Interface {
} capability;
} information;
struct Media {
struct Medium {
uint id;
string name;
string type;
bool bootable; //false for cartridge slots (eg Sufami Turbo cartridges)
};
vector<Media> media;
vector<Medium> media;
struct Device {
uint id;
@@ -34,23 +35,22 @@ struct Interface {
string name;
uintptr guid; //user data field
};
vector<Input> input;
vector<uint> order;
vector<Input> inputs;
};
struct Port {
uint id;
string name;
vector<Device> device;
vector<Device> devices;
};
vector<Port> port;
vector<Port> ports;
struct Bind {
virtual auto loadRequest(uint, string, string, bool) -> void {}
virtual auto loadRequest(uint, string, bool) -> void {}
virtual auto saveRequest(uint, string) -> void {}
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
virtual auto audioSample(int16, int16) -> void {}
virtual auto audioSample(const double*, uint) -> void {}
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
virtual auto inputRumble(uint, uint, uint, bool) -> void {}
virtual auto dipSettings(const Markup::Node&) -> uint { return 0; }
@@ -64,7 +64,7 @@ struct Interface {
auto loadRequest(uint id, string path, bool required) -> void { return bind->loadRequest(id, path, required); }
auto saveRequest(uint id, string path) -> void { return bind->saveRequest(id, path); }
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); }
auto audioSample(int16 lsample, int16 rsample) -> void { return bind->audioSample(lsample, rsample); }
auto audioSample(const double* samples, uint channels) -> void { return bind->audioSample(samples, channels); }
auto inputPoll(uint port, uint device, uint input) -> int16 { return bind->inputPoll(port, device, input); }
auto inputRumble(uint port, uint device, uint input, bool enable) -> void { return bind->inputRumble(port, device, input, enable); }
auto dipSettings(const Markup::Node& node) -> uint { return bind->dipSettings(node); }
@@ -74,7 +74,13 @@ struct Interface {
//information
virtual auto manifest() -> string = 0;
virtual auto title() -> string = 0;
//video information
virtual auto videoFrequency() -> double = 0;
virtual auto videoColors() -> uint32 { return 1 << 19; }
virtual auto videoColor(uint32 color) -> uint64 { return 0; }
//audio information
virtual auto audioFrequency() -> double = 0;
//media interface
@@ -108,6 +114,9 @@ struct Interface {
virtual auto cap(const string& name) -> bool { return false; }
virtual auto get(const string& name) -> any { return {}; }
virtual auto set(const string& name, const any& value) -> bool { return false; }
//shared functions
auto videoColor(uint16 r, uint16 g, uint16 b) -> uint32;
};
}

View File

@@ -1,16 +1,16 @@
fc_objects := fc-interface fc-system fc-scheduler fc-input
fc_objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
fc_objects += fc-cheat fc-video
objects += $(fc_objects)
processors += r6502
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-input.o: $(fc)/input/input.cpp $(call rwildcard,$(fc)/input/)
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/)
obj/fc-video.o: $(fc)/video/video.cpp $(call rwildcard,$(fc)/video/)
objects += fc-interface fc-system fc-scheduler fc-input
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-input.o: fc/input/input.cpp $(call rwildcard,fc/input/)
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

@@ -34,16 +34,11 @@ APU::APU() {
}
}
auto APU::Main() -> void {
apu.main();
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();
}
auto APU::main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
uint pulse_output, triangle_output, noise_output, dmc_output;
pulse_output = pulse[0].clock();
@@ -59,18 +54,17 @@ auto APU::main() -> void {
output = filter.run_hipass_strong(output);
output += cartridge_sample;
output = filter.run_hipass_weak(output);
//output = filter.run_lopass(output);
//output = filter.run_lopass(output);
output = sclamp<16>(output);
interface->audioSample(output, output);
stream->sample(output / 32768.0);
tick();
}
}
auto APU::tick() -> void {
clock += 12;
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
}
auto APU::set_irq_line() -> void {
@@ -94,7 +88,8 @@ auto APU::power() -> void {
}
auto APU::reset() -> void {
create(APU::Main, 21477272);
create(APU::Enter, 21'477'272);
stream = Emulator::audio.createStream(1, 21'477'272.0 / 12.0);
pulse[0].reset();
pulse[1].reset();

View File

@@ -1,7 +1,9 @@
struct APU : Thread {
shared_pointer<Emulator::Stream> stream;
APU();
static auto Main() -> void;
static auto Enter() -> void;
auto main() -> void;
auto tick() -> void;
auto set_irq_line() -> void;

View File

@@ -5,11 +5,6 @@ struct BandaiFCG : Board {
}
auto main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_counter_enable) {
if(--irq_counter == 0xffff) {
cpu.set_irq_line(1);
@@ -19,7 +14,6 @@ struct BandaiFCG : Board {
tick();
}
}
auto ciram_addr(uint addr) const -> uint {
switch(mirror) {
@@ -33,7 +27,7 @@ struct BandaiFCG : Board {
auto prg_read(uint addr) -> uint8 {
if(addr & 0x8000) {
bool region = addr & 0x4000;
uint bank = (region == 0 ? prg_bank : 0x0f);
uint bank = (region == 0 ? prg_bank : (uint8)0x0f);
return prgrom.read((bank << 14) | (addr & 0x3fff));
}
return cpu.mdr();

View File

@@ -36,10 +36,10 @@ Board::Board(Markup::Node& document) {
chrrom.size = crom["size"].natural();
chrram.size = cram["size"].natural();
if(prgrom.size) prgrom.data = new uint8[prgrom.size]();
if(prgram.size) prgram.data = new uint8[prgram.size]();
if(chrrom.size) chrrom.data = new uint8[chrrom.size]();
if(chrram.size) chrram.data = new uint8[chrram.size]();
if(prgrom.size) prgrom.data = new uint8_t[prgrom.size]();
if(prgram.size) prgram.data = new uint8_t[prgram.size]();
if(chrrom.size) chrrom.data = new uint8_t[chrrom.size]();
if(chrram.size) chrram.data = new uint8_t[chrram.size]();
if(auto name = prom["name"].text()) interface->loadRequest(ID::ProgramROM, name, true);
if(auto name = pram["name"].text()) interface->loadRequest(ID::ProgramRAM, name, false);
@@ -80,19 +80,13 @@ auto Board::mirror(uint addr, uint size) -> uint {
}
auto Board::main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
cartridge.clock += 12 * 4095;
tick();
}
}
auto Board::tick() -> void {
cartridge.clock += 12;
if(cartridge.clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
if(cartridge.clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
}
auto Board::chr_read(uint addr) -> uint8 {

View File

@@ -44,11 +44,6 @@ struct Sunsoft5B : Board {
} pulse[3];
auto main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_counter_enable) {
if(--irq_counter == 0xffff) {
cpu.set_irq_line(irq_enable);
@@ -63,7 +58,6 @@ struct Sunsoft5B : Board {
tick();
}
}
auto prg_read(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();

View File

@@ -6,10 +6,6 @@ namespace Famicom {
#include "board/board.cpp"
Cartridge cartridge;
auto Cartridge::loaded() const -> bool {
return _loaded;
}
auto Cartridge::sha256() const -> string {
return _sha256;
}
@@ -22,8 +18,8 @@ auto Cartridge::title() const -> string {
return information.title;
}
auto Cartridge::Main() -> void {
cartridge.main();
auto Cartridge::Enter() -> void {
while(true) scheduler.synchronize(), cartridge.main();
}
auto Cartridge::main() -> void {
@@ -40,14 +36,9 @@ auto Cartridge::load() -> void {
sha.data(board->prgrom.data, board->prgrom.size);
sha.data(board->chrrom.data, board->chrrom.size);
_sha256 = sha.digest();
system.load();
_loaded = true;
}
auto Cartridge::unload() -> void {
if(!loaded()) return;
_loaded = false;
memory.reset();
}
@@ -56,7 +47,7 @@ auto Cartridge::power() -> void {
}
auto Cartridge::reset() -> void {
create(Cartridge::Main, 21477272);
create(Cartridge::Enter, 21'477'272);
board->reset();
}

View File

@@ -2,10 +2,9 @@
#include "board/board.hpp"
struct Cartridge : Thread {
static auto Main() -> void;
static auto Enter() -> void;
auto main() -> void;
auto loaded() const -> bool;
auto sha256() const -> string;
auto manifest() const -> string;
auto title() const -> string;
@@ -31,7 +30,6 @@ struct Cartridge : Thread {
//privileged:
Board* board = nullptr;
bool _loaded = false;
string _sha256;
auto prg_read(uint addr) -> uint8;

View File

@@ -4,15 +4,9 @@ struct MMC1 : Chip {
}
auto main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(writedelay) writedelay--;
tick();
}
}
auto prg_addr(uint addr) -> uint {
bool region = addr & 0x4000;

View File

@@ -3,16 +3,10 @@ struct MMC3 : Chip {
}
auto main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_delay) irq_delay--;
cpu.set_irq_line(irq_line);
tick();
}
}
auto irq_test(uint addr) -> void {
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {

View File

@@ -4,18 +4,12 @@ struct MMC5 : Chip {
}
auto main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
//scanline() resets this; if no scanlines detected, enter video blanking period
if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
cpu.set_irq_line(irq_enable && irq_pending);
tick();
}
}
auto scanline(uint y) -> void {
//used for testing only, to verify MMC5 scanline detection is accurate:
@@ -94,7 +88,7 @@ struct MMC5 : Chip {
auto prg_write(uint addr, uint8 data) -> void {
if((addr & 0xfc00) == 0x5c00) {
//writes 0x00 *during* Vblank (not during screen rendering ...)
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : 0x00;
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : (uint8)0x00;
if(exram_mode == 2) exram[addr & 0x03ff] = data;
return;
}
@@ -277,7 +271,7 @@ struct MMC5 : Chip {
switch(nametable_mode[(addr >> 10) & 3]) {
case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff));
case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff));
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : 0x00;
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
}
}

View File

@@ -3,16 +3,10 @@ struct MMC6 : Chip {
}
auto main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_delay) irq_delay--;
cpu.set_irq_line(irq_line);
tick();
}
}
auto irq_test(uint addr) -> void {
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {

View File

@@ -3,11 +3,6 @@ struct VRC3 : Chip {
}
auto main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) { //16-bit
if(++irq_counter.w == 0) {
@@ -28,7 +23,6 @@ struct VRC3 : Chip {
cpu.set_irq_line(irq_line);
tick();
}
}
auto prg_addr(uint addr) const -> uint {
uint bank = (addr < 0xc000 ? (uint)prg_bank : 0x0f);
@@ -90,8 +84,8 @@ struct VRC3 : Chip {
uint16 irq_latch;
struct {
union {
uint16 w;
struct { uint8 order_lsb2(l, h); };
uint16_t w;
struct { uint8_t order_lsb2(l, h); };
};
} irq_counter;
bool irq_line;

View File

@@ -3,11 +3,6 @@ struct VRC4 : Chip {
}
auto main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) {
irq_scalar -= 3;
@@ -35,7 +30,6 @@ struct VRC4 : Chip {
cpu.set_irq_line(irq_line);
tick();
}
}
auto prg_addr(uint addr) const -> uint {
uint bank = 0, banks = board.prgrom.size / 0x2000;

View File

@@ -77,11 +77,6 @@ struct VRC6 : Chip {
} sawtooth;
auto main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) {
irq_scalar -= 3;
@@ -115,7 +110,6 @@ struct VRC6 : Chip {
tick();
}
}
auto prg_addr(uint addr) const -> uint {
if((addr & 0xc000) == 0x8000) return (prg_bank[0] << 14) | (addr & 0x3fff);

View File

@@ -6,11 +6,6 @@ struct VRC7 : Chip {
}
auto main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) {
irq_scalar -= 3;
@@ -38,7 +33,6 @@ struct VRC7 : Chip {
tick();
}
}
auto reg_write(uint addr, uint8 data) -> void {
switch(addr) {

View File

@@ -7,33 +7,23 @@ namespace Famicom {
CPU cpu;
auto CPU::Enter() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
cpu.main();
}
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::main() -> void {
if(status.interrupt_pending) {
interrupt();
return;
}
if(status.interrupt_pending) return interrupt();
exec();
}
auto CPU::add_clocks(uint clocks) -> void {
apu.clock -= clocks;
if(apu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(apu.thread);
if(apu.clock < 0 && !scheduler.synchronizing()) co_switch(apu.thread);
ppu.clock -= clocks;
if(ppu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(ppu.thread);
if(ppu.clock < 0 && !scheduler.synchronizing()) co_switch(ppu.thread);
cartridge.clock -= clocks;
if(cartridge.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cartridge.thread);
if(cartridge.clock < 0 && !scheduler.synchronizing()) co_switch(cartridge.thread);
}
auto CPU::power() -> void {
@@ -48,7 +38,7 @@ auto CPU::power() -> void {
auto CPU::reset() -> void {
R6502::reset();
create(CPU::Enter, 21477272);
create(CPU::Enter, 21'477'272);
regs.pc = bus.read(0xfffc) << 0;
regs.pc |= bus.read(0xfffd) << 8;

View File

@@ -1,22 +1,17 @@
#pragma once
//license: GPLv3
//started: 2011-09-05
#include <emulator/emulator.hpp>
#include <processor/r6502/r6502.hpp>
namespace Famicom {
namespace Info {
static const string Name = "bnes";
static const uint SerializerVersion = 2;
static const uint SerializerVersion = 3;
}
}
/*
bnes - Famicom emulator
authors: byuu, Ryphecha
license: GPLv3
project started: 2011-09-05
*/
#include <libco/libco.h>
namespace Famicom {
@@ -27,7 +22,7 @@ namespace Famicom {
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
if(thread) co_delete(thread);
thread = co_create(65536 * sizeof(void*), entrypoint);
thread = co_create(65'536 * sizeof(void*), entrypoint);
this->frequency = frequency;
clock = 0;
}
@@ -51,7 +46,6 @@ namespace Famicom {
#include <fc/apu/apu.hpp>
#include <fc/ppu/ppu.hpp>
#include <fc/cheat/cheat.hpp>
#include <fc/video/video.hpp>
}
#include <fc/interface/interface.hpp>

View File

@@ -15,12 +15,15 @@ auto Input::latch(bool data) -> void {
}
auto Input::data(bool port) -> bool {
//table to convert native button ordering to Emulator::Interface ordering
static const uint lookup[] = {5, 4, 6, 7, 0, 1, 2, 3};
bool result = 0;
if(port == 0) {
if(port1 == Device::Joypad) {
if(counter1 >= 8) return 1;
result = interface->inputPoll(0, 0u, counter1);
result = interface->inputPoll(0, 0u, lookup[counter1]);
if(latchdata == 0) counter1++;
}
}
@@ -28,7 +31,7 @@ auto Input::data(bool port) -> bool {
if(port == 1) {
if(port2 == Device::Joypad) {
if(counter2 >= 8) return 1;
result = interface->inputPoll(1, 0u, counter2);
result = interface->inputPoll(1, 0u, lookup[counter2]);
if(latchdata == 0) counter2++;
}
}

View File

@@ -8,37 +8,38 @@ Settings settings;
Interface::Interface() {
interface = this;
information.manufacturer = "Nintendo";
information.name = "Famicom";
information.width = 256;
information.height = 240;
information.overscan = true;
information.aspectRatio = 8.0 / 7.0;
information.resettable = true;
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::Famicom, "Famicom", "fc", true});
{ Device device{0, ID::Port1 | ID::Port2, "Controller"};
device.input.append({0, 0, "A" });
device.input.append({1, 0, "B" });
device.input.append({2, 0, "Select"});
device.input.append({3, 0, "Start" });
device.input.append({4, 0, "Up" });
device.input.append({5, 0, "Down" });
device.input.append({6, 0, "Left" });
device.input.append({7, 0, "Right" });
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
this->device.append(device);
device.inputs.append({0, 0, "Up" });
device.inputs.append({1, 0, "Down" });
device.inputs.append({2, 0, "Left" });
device.inputs.append({3, 0, "Right" });
device.inputs.append({4, 0, "B" });
device.inputs.append({5, 0, "A" });
device.inputs.append({6, 0, "Select"});
device.inputs.append({7, 0, "Start" });
devices.append(device);
}
port.append({0, "Port 1"});
port.append({1, "Port 2"});
ports.append({0, "Port 1"});
ports.append({1, "Port 2"});
for(auto& device : this->device) {
for(auto& port : this->port) {
for(auto& device : devices) {
for(auto& port : ports) {
if(device.portmask & (1 << port.id)) {
port.device.append(device);
port.devices.append(device);
}
}
}
@@ -56,12 +57,67 @@ auto Interface::videoFrequency() -> double {
return 21477272.0 / (262.0 * 1364.0 - 4.0);
}
auto Interface::videoColors() -> uint32 {
return 1 << 9;
}
auto Interface::videoColor(uint32 n) -> uint64 {
double saturation = 2.0;
double hue = 0.0;
double contrast = 1.0;
double brightness = 1.0;
double gamma = settings.colorEmulation ? 1.8 : 2.2;
int color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
static const double black = 0.518, white = 1.962, attenuation = 0.746;
static const double levels[8] = {
0.350, 0.518, 0.962, 1.550,
1.094, 1.506, 1.962, 1.962,
};
double lo_and_hi[2] = {
levels[level + 4 * (color == 0x0)],
levels[level + 4 * (color < 0xd)],
};
double y = 0.0, i = 0.0, q = 0.0;
auto wave = [](int p, int color) { return (color + p + 8) % 12 < 6; };
for(int p : range(12)) {
double spot = lo_and_hi[wave(p, color)];
if(((n & 0x040) && wave(p, 12))
|| ((n & 0x080) && wave(p, 4))
|| ((n & 0x100) && wave(p, 8))
) spot *= attenuation;
double v = (spot - black) / (white - black);
v = (v - 0.5) * contrast + 0.5;
v *= brightness / 12.0;
y += v;
i += v * cos((Math::Pi / 6.0) * (p + hue));
q += v * sin((Math::Pi / 6.0) * (p + hue));
}
i *= saturation;
q *= saturation;
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : pow(f, 2.2 / gamma); };
uint64 r = uclamp<16>(65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q));
uint64 g = uclamp<16>(65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q));
uint64 b = uclamp<16>(65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q));
return r << 32 | g << 16 | b << 0;
}
auto Interface::audioFrequency() -> double {
return 21477272.0 / 12.0;
}
auto Interface::loaded() -> bool {
return cartridge.loaded();
return system.loaded();
}
auto Interface::sha256() -> string {
@@ -84,7 +140,7 @@ auto Interface::group(uint id) -> uint {
}
auto Interface::load(uint id) -> void {
cartridge.load();
system.load();
}
auto Interface::save() -> void {
@@ -131,7 +187,7 @@ auto Interface::save(uint id, const stream& stream) -> void {
auto Interface::unload() -> void {
save();
cartridge.unload();
system.unload();
}
auto Interface::power() -> void {
@@ -147,7 +203,7 @@ auto Interface::run() -> void {
}
auto Interface::serialize() -> serializer {
system.runtosave();
system.runToSave();
return system.serialize();
}
@@ -180,7 +236,11 @@ auto Interface::get(const string& name) -> any {
}
auto Interface::set(const string& name, const any& value) -> bool {
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
system.configureVideoPalette();
return true;
}
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
return false;
}

View File

@@ -28,6 +28,8 @@ struct Interface : Emulator::Interface {
auto manifest() -> string;
auto title() -> string;
auto videoFrequency() -> double;
auto videoColors() -> uint32;
auto videoColor(uint32 color) -> uint64;
auto audioFrequency() -> double;
auto loaded() -> bool;
@@ -53,7 +55,7 @@ struct Interface : Emulator::Interface {
auto set(const string& name, const any& value) -> bool override;
private:
vector<Device> device;
vector<Device> devices;
};
struct Settings {

View File

@@ -2,21 +2,16 @@
namespace Famicom {
#include "serialization.cpp"
PPU ppu;
auto PPU::Main() -> void {
ppu.main();
#include "serialization.cpp"
auto PPU::Enter() -> void {
while(true) scheduler.synchronize(), ppu.main();
}
auto PPU::main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::PPU) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
raster_scanline();
}
}
auto PPU::tick() -> void {
@@ -47,14 +42,18 @@ auto PPU::scanline() -> void {
auto PPU::frame() -> void {
status.field ^= 1;
scheduler.exit(Scheduler::ExitReason::FrameEvent);
scheduler.exit(Scheduler::Event::Frame);
}
auto PPU::refresh() -> void {
Emulator::video.refresh(buffer, 256 * sizeof(uint32), 256, 240);
}
auto PPU::power() -> void {
}
auto PPU::reset() -> void {
create(PPU::Main, 21477272);
create(PPU::Enter, 21'477'272);
status.mdr = 0x00;
status.field = 0;
@@ -113,7 +112,6 @@ auto PPU::read(uint16 addr) -> uint8 {
break;
case 4: //OAMDATA
result = oam[status.oam_addr];
if((status.oam_addr & 3) == 3) result &= 0xe3;
break;
case 7: //PPUDATA
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return 0x00;
@@ -164,6 +162,7 @@ auto PPU::write(uint16 addr, uint8 data) -> void {
status.oam_addr = data;
return;
case 4: //OAMDATA
if(status.oam_addr.bits(0,1) == 2) data.bits(2,4) = 0; //clear non-existent bits (always read back as 0)
oam[status.oam_addr++] = data;
return;
case 5: //PPUSCROLL

View File

@@ -1,10 +1,11 @@
struct PPU : Thread {
static auto Main() -> void;
static auto Enter() -> void;
auto main() -> void;
auto tick() -> void;
auto scanline() -> void;
auto frame() -> void;
auto refresh() -> void;
auto power() -> void;
auto reset() -> void;

View File

@@ -4,25 +4,41 @@ namespace Famicom {
Scheduler scheduler;
auto Scheduler::enter() -> void {
host_thread = co_active();
co_switch(thread);
}
auto Scheduler::exit(ExitReason reason) -> void {
exit_reason = reason;
thread = co_active();
co_switch(host_thread);
}
auto Scheduler::power() -> void {
}
auto Scheduler::reset() -> void {
host_thread = co_active();
thread = cpu.thread;
sync = SynchronizeMode::None;
exit_reason = ExitReason::UnknownEvent;
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 == ppu.thread) {
while(enter(Mode::SynchronizePPU) != Event::Synchronize);
} else {
resume = thread;
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
}
}
auto Scheduler::synchronize() -> void {
if(co_active() == ppu.thread && mode == Mode::SynchronizePPU) return exit(Event::Synchronize);
if(co_active() != ppu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
}
auto Scheduler::synchronizing() const -> bool {
return mode == Mode::SynchronizeAll;
}
}

View File

@@ -1,16 +1,28 @@
struct Scheduler : property<Scheduler> {
enum class SynchronizeMode : uint { None, PPU, All } sync;
enum class ExitReason : uint { UnknownEvent, FrameEvent, SynchronizeEvent };
struct Scheduler {
enum class Mode : uint {
Run,
SynchronizePPU,
SynchronizeAll,
};
auto enter() -> void;
auto exit(ExitReason) -> void;
enum class Event : uint {
Unknown,
Frame,
Synchronize,
};
auto power() -> void;
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;
cothread_t host_thread; //program thread (used to exit emulation)
cothread_t thread; //active emulation thread (used to enter emulation)
readonly<ExitReason> exit_reason;
private:
cothread_t host = nullptr;
cothread_t resume = nullptr;
Mode mode = Mode::Run;
Event event = Event::Unknown;
};
extern Scheduler scheduler;

View File

@@ -1,5 +1,5 @@
auto System::serialize() -> serializer {
serializer s(serialize_size);
serializer s(_serializeSize);
uint signature = 0x31545342, version = Info::SerializerVersion;
char hash[64], description[512];
@@ -11,7 +11,7 @@ auto System::serialize() -> serializer {
s.array(hash);
s.array(description);
serialize_all(s);
serializeAll(s);
return s;
}
@@ -28,14 +28,14 @@ auto System::unserialize(serializer& s) -> bool {
if(version != Info::SerializerVersion) return false;
power();
serialize_all(s);
serializeAll(s);
return true;
}
auto System::serialize(serializer& s) -> void {
}
auto System::serialize_all(serializer& s) -> void {
auto System::serializeAll(serializer& s) -> void {
system.serialize(s);
input.serialize(s);
cartridge.serialize(s);
@@ -44,7 +44,7 @@ auto System::serialize_all(serializer& s) -> void {
ppu.serialize(s);
}
auto System::serialize_init() -> void {
auto System::serializeInit() -> void {
serializer s;
uint signature = 0, version = 0;
@@ -55,6 +55,6 @@ auto System::serialize_init() -> void {
s.array(hash);
s.array(description);
serialize_all(s);
serialize_size = s.size();
serializeAll(s);
_serializeSize = s.size();
}

View File

@@ -2,50 +2,35 @@
namespace Famicom {
#include "video.cpp"
#include "serialization.cpp"
System system;
auto System::loaded() const -> bool { return _loaded; }
auto System::run() -> void {
scheduler.enter();
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
video.refresh();
}
}
auto System::runtosave() -> void {
scheduler.sync = Scheduler::SynchronizeMode::PPU;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.thread = cpu.thread;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.thread = apu.thread;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.thread = cartridge.thread;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::None;
}
auto System::runthreadtosave() -> void {
while(true) {
scheduler.enter();
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
video.refresh();
}
}
auto System::runToSave() -> void {
scheduler.synchronize(ppu.thread);
scheduler.synchronize(cpu.thread);
scheduler.synchronize(apu.thread);
scheduler.synchronize(cartridge.thread);
}
auto System::load() -> void {
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
auto document = BML::unserialize(information.manifest);
cartridge.load();
serializeInit();
_loaded = true;
}
serialize_init();
auto System::unload() -> void {
if(!loaded()) return;
cartridge.unload();
_loaded = false;
}
auto System::power() -> void {
@@ -54,22 +39,28 @@ auto System::power() -> void {
apu.power();
ppu.power();
input.reset();
scheduler.power();
reset();
}
auto System::reset() -> void {
Emulator::video.reset();
Emulator::video.setInterface(interface);
configureVideoPalette();
configureVideoEffects();
Emulator::audio.reset();
Emulator::audio.setInterface(interface);
cartridge.reset();
cpu.reset();
apu.reset();
ppu.reset();
input.reset();
scheduler.reset();
video.reset();
}
auto System::init() -> void {
assert(interface != 0);
assert(interface != nullptr);
input.connect(0, Input::Device::Joypad);
input.connect(1, Input::Device::None);
}

View File

@@ -1,27 +1,36 @@
struct System {
auto loaded() const -> bool;
auto run() -> void;
auto runtosave() -> void;
auto runthreadtosave() -> void;
auto runToSave() -> void;
auto load() -> void;
auto unload() -> void;
auto power() -> void;
auto reset() -> void;
auto init() -> void;
auto term() -> void;
//video.cpp
auto configureVideoPalette() -> void;
auto configureVideoEffects() -> void;
//serialization.cpp
auto serialize() -> serializer;
auto unserialize(serializer&) -> bool;
auto serialize(serializer&) -> void;
auto serialize_all(serializer&) -> void;
auto serialize_init() -> void;
auto serializeAll(serializer&) -> void;
auto serializeInit() -> void;
struct Information {
string manifest;
} information;
uint serialize_size;
private:
bool _loaded = false;
uint _serializeSize = 0;
};
extern System system;

View File

@@ -0,0 +1,6 @@
auto System::configureVideoPalette() -> void {
Emulator::video.setPalette();
}
auto System::configureVideoEffects() -> void {
}

View File

@@ -1,105 +0,0 @@
#include <fc/fc.hpp>
#include <cmath>
#define VIDEO_CPP
namespace Famicom {
Video video;
Video::Video() {
output = new uint32[256 * 480];
paletteStandard = new uint32[1 << 9];
paletteEmulation = new uint32[1 << 9];
}
Video::~Video() {
delete[] output;
delete[] paletteStandard;
delete[] paletteEmulation;
}
auto Video::reset() -> void {
memory::fill(output, 256 * 480);
for(auto color : range(1 << 9)) {
paletteStandard[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 2.2);
paletteEmulation[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 1.8);
}
}
auto Video::refresh() -> void {
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
if(settings.scanlineEmulation) {
for(uint y = 0; y < 240; y++) {
auto source = ppu.buffer + y * 256;
auto targetLo = output + y * 512;
auto targetHi = output + y * 512 + 256;
for(uint x = 0; x < 256; x++) {
auto color = palette[*source++];
*targetLo++ = color;
*targetHi++ = (255 << 24) | ((color & 0xfefefe) >> 1);
}
}
interface->videoRefresh(output, 256 * sizeof(uint32), 256, 480);
} else {
for(uint y = 0; y < 240; y++) {
auto source = ppu.buffer + y * 256;
auto target = output + y * 256;
for(uint x = 0; x < 256; x++) {
*target++ = palette[*source++];
}
}
interface->videoRefresh(output, 256 * sizeof(uint32), 256, 240);
}
}
auto Video::generateColor(
uint n, double saturation, double hue,
double contrast, double brightness, double gamma
) -> uint32 {
int color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
static const double black = 0.518, white = 1.962, attenuation = 0.746;
static const double levels[8] = {
0.350, 0.518, 0.962, 1.550,
1.094, 1.506, 1.962, 1.962,
};
double lo_and_hi[2] = {
levels[level + 4 * (color == 0x0)],
levels[level + 4 * (color < 0xd)],
};
double y = 0.0, i = 0.0, q = 0.0;
auto wave = [](int p, int color) { return (color + p + 8) % 12 < 6; };
for(int p : range(12)) {
double spot = lo_and_hi[wave(p, color)];
if(((n & 0x040) && wave(p, 12))
|| ((n & 0x080) && wave(p, 4))
|| ((n & 0x100) && wave(p, 8))
) spot *= attenuation;
double v = (spot - black) / (white - black);
v = (v - 0.5) * contrast + 0.5;
v *= brightness / 12.0;
y += v;
i += v * std::cos((3.141592653 / 6.0) * (p + hue));
q += v * std::sin((3.141592653 / 6.0) * (p + hue));
}
i *= saturation;
q *= saturation;
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); };
uint r = uclamp<16>(65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q));
uint g = uclamp<16>(65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q));
uint b = uclamp<16>(65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q));
return (255 << 24) | ((r >> 8) << 16) | ((g >> 8) << 8) | ((b >> 8) << 0);
}
}

View File

@@ -1,16 +0,0 @@
struct Video {
Video();
~Video();
auto reset() -> void;
auto refresh() -> void;
uint32* output = nullptr;
uint32* paletteStandard = nullptr;
uint32* paletteEmulation = nullptr;
private:
auto generateColor(uint, double, double, double, double, double) -> uint32;
};
extern Video video;

View File

@@ -1,16 +1,16 @@
gb_objects := gb-interface gb-system gb-scheduler
gb_objects += gb-memory gb-cartridge
gb_objects += gb-cpu gb-ppu gb-apu
gb_objects += gb-cheat gb-video
objects += $(gb_objects)
processors += lr35902
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/)
obj/gb-video.o: $(gb)/video/video.cpp $(call rwildcard,$(gb)/video/)
objects += gb-interface gb-system gb-scheduler
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

@@ -10,16 +10,11 @@ namespace GameBoy {
#include "serialization.cpp"
APU apu;
auto APU::Main() -> void {
apu.main();
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();
}
auto APU::main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
square1.run();
square2.run();
wave.run();
@@ -30,7 +25,12 @@ auto APU::main() -> void {
hipass(sequencer.left, sequencer.leftBias);
hipass(sequencer.right, sequencer.rightBias);
interface->audioSample(sequencer.left, sequencer.right);
if(!system.sgb()) {
stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0);
} else {
double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0};
interface->audioSample(samples, 2);
}
if(cycle == 0) { //512hz
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
@@ -52,10 +52,7 @@ auto APU::main() -> void {
cycle++;
clock += cpu.frequency;
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
co_switch(scheduler.active_thread = cpu.thread);
}
}
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
}
//filter to remove DC bias
@@ -65,7 +62,8 @@ auto APU::hipass(int16& sample, int64& bias) -> void {
}
auto APU::power() -> void {
create(Main, 2 * 1024 * 1024);
create(Enter, 2 * 1024 * 1024);
if(!system.sgb()) stream = Emulator::audio.createStream(2, 2 * 1024 * 1024);
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
square1.power();

View File

@@ -1,5 +1,7 @@
struct APU : Thread, MMIO {
static auto Main() -> void;
shared_pointer<Emulator::Stream> stream;
static auto Enter() -> void;
auto main() -> void;
auto hipass(int16& sample, int64& bias) -> void;
auto power() -> void;

View File

@@ -66,16 +66,16 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
}
if(addr == 0xff21) { //NR42
envelopeVolume = data >> 4;
envelopeDirection = data & 0x08;
envelopeFrequency = data & 0x07;
envelopeVolume = data.bits(7,4);
envelopeDirection = data.bit (3);
envelopeFrequency = data.bits(2,0);
if(!dacEnable()) enable = false;
}
if(addr == 0xff22) { //NR43
frequency = data >> 4;
narrow = data & 0x08;
divisor = data & 0x07;
frequency = data.bits(7,4);
narrow = data.bit (3);
divisor = data.bits(2,0);
period = getPeriod();
}
@@ -84,10 +84,9 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
if(length && --length == 0) enable = false;
}
bool initialize = data & 0x80;
counter = data & 0x40;
counter = data.bit(6);
if(initialize) {
if(data.bit(7)) {
enable = dacEnable();
lfsr = -1;
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
@@ -95,7 +94,7 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
if(!length) {
length = 64;
if((apu.phase & 1) && counter) length--;
if(apu.phase.bit(0) && counter) length--;
}
}
}

View File

@@ -68,26 +68,26 @@ auto APU::Sequencer::read(uint16 addr) -> uint8 {
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff24) { //NR50
leftEnable = (uint1)(data >> 7);
leftVolume = (uint3)(data >> 4);
rightEnable = (uint1)(data >> 3);
rightVolume = (uint3)(data >> 0);
leftEnable = data.bit (7);
leftVolume = data.bits(6,4);
rightEnable = data.bit (3);
rightVolume = data.bits(2,0);
}
if(addr == 0xff25) { //NR51
noise.leftEnable = data & 0x80;
wave.leftEnable = data & 0x40;
square2.leftEnable = data & 0x20;
square1.leftEnable = data & 0x10;
noise.rightEnable = data & 0x08;
wave.rightEnable = data & 0x04;
square2.rightEnable = data & 0x02;
square1.rightEnable = data & 0x01;
noise.leftEnable = data.bit(7);
wave.leftEnable = data.bit(6);
square2.leftEnable = data.bit(5);
square1.leftEnable = data.bit(4);
noise.rightEnable = data.bit(3);
wave.rightEnable = data.bit(2);
square2.rightEnable = data.bit(1);
square1.rightEnable = data.bit(0);
}
if(addr == 0xff26) { //NR52
if(enable != (bool)(data & 0x80)) {
enable = data & 0x80;
if(enable != data.bit(7)) {
enable = data.bit(7);
if(!enable) {
//power(bool) resets length counters when true (eg for CGB only)

View File

@@ -86,38 +86,37 @@ auto APU::Square1::read(uint16 addr) -> uint8 {
auto APU::Square1::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff10) { //NR10
if(sweepEnable && sweepNegate && !(data & 0x08)) enable = false;
sweepFrequency = (data >> 4) & 7;
sweepDirection = data & 0x08;
sweepShift = data & 0x07;
if(sweepEnable && sweepNegate && !data.bit(3)) enable = false;
sweepFrequency = data.bits(6,4);
sweepDirection = data.bit (3);
sweepShift = data.bits(2,0);
}
if(addr == 0xff11) { //NR11
duty = data >> 6;
length = 64 - (data & 0x3f);
duty = data.bits(7,6);
length = 64 - data.bits(5,0);
}
if(addr == 0xff12) { //NR12
envelopeVolume = data >> 4;
envelopeDirection = data & 0x08;
envelopeFrequency = data & 0x07;
envelopeVolume = data.bits(7,4);
envelopeDirection = data.bit (3);
envelopeFrequency = data.bits(2,0);
if(!dacEnable()) enable = false;
}
if(addr == 0xff13) { //NR13
frequency = (frequency & 0x0700) | data;
frequency.bits(7,0) = data;
}
if(addr == 0xff14) { //NR14
if((apu.phase & 1) && !counter && (data & 0x40)) {
if(apu.phase.bit(0) && !counter && data.bit(6)) {
if(length && --length == 0) enable = false;
}
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
counter = data.bit(6);
frequency.bits(10,8) = data.bits(2,0);
if(initialize) {
if(data.bit(7)) {
enable = dacEnable();
period = 2 * (2048 - frequency);
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
@@ -125,7 +124,7 @@ auto APU::Square1::write(uint16 addr, uint8 data) -> void {
if(!length) {
length = 64;
if((apu.phase & 1) && counter) length--;
if(apu.phase.bit(0) && counter) length--;
}
frequencyShadow = frequency;

View File

@@ -60,31 +60,30 @@ auto APU::Square2::read(uint16 addr) -> uint8 {
auto APU::Square2::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff16) { //NR21
duty = data >> 6;
length = 64 - (data & 0x3f);
duty = data.bits(7,6);
length = 64 - data.bits(5,0);
}
if(addr == 0xff17) { //NR22
envelopeVolume = data >> 4;
envelopeDirection = data & 0x08;
envelopeFrequency = data & 0x07;
envelopeVolume = data.bits(7,4);
envelopeDirection = data.bit (3);
envelopeFrequency = data.bits(2,0);
if(!dacEnable()) enable = false;
}
if(addr == 0xff18) { //NR23
frequency = (frequency & 0x0700) | data;
frequency.bits(7,0) = data;
}
if(addr == 0xff19) { //NR24
if((apu.phase & 1) && !counter && (data & 0x40)) {
if(apu.phase.bit(0) && !counter && data.bit(6)) {
if(length && --length == 0) enable = false;
}
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
counter = data.bit(6);
frequency.bits(10,8) = data.bits(2,0);
if(initialize) {
if(data.bit(7)) {
enable = dacEnable();
period = 2 * (2048 - frequency);
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
@@ -92,7 +91,7 @@ auto APU::Square2::write(uint16 addr, uint8 data) -> void {
if(!length) {
length = 64;
if((apu.phase & 1) && counter) length--;
if(apu.phase.bit(0) && counter) length--;
}
}
}

View File

@@ -59,7 +59,7 @@ auto APU::Wave::read(uint16 addr) -> uint8 {
auto APU::Wave::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff1a) { //NR30
dacEnable = data & 0x80;
dacEnable = data.bit(7);
if(!dacEnable) enable = false;
}
@@ -68,23 +68,22 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
}
if(addr == 0xff1c) { //NR32
volume = data >> 5;
volume = data.bits(6,5);
}
if(addr == 0xff1d) { //NR33
frequency = (frequency & 0x0700) | data;
frequency.bits(7,0) = data;
}
if(addr == 0xff1e) { //NR34
if((apu.phase & 1) && !counter && (data & 0x40)) {
if(apu.phase.bit(0) && !counter && data.bit(6)) {
if(length && --length == 0) enable = false;
}
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
counter = data.bit(6);
frequency.bits(10,8) = data.bits(2,0);
if(initialize) {
if(data.bit(7)) {
if(!system.cgb() && patternHold) {
//DMG,SGB trigger while channel is being read corrupts wave RAM
if((patternOffset >> 1) <= 3) {
@@ -107,7 +106,7 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
if(!length) {
length = 256;
if((apu.phase & 1) && counter) length--;
if(apu.phase.bit(0) && counter) length--;
}
}
}

View File

@@ -4,6 +4,7 @@ namespace GameBoy {
#include "mbc0/mbc0.cpp"
#include "mbc1/mbc1.cpp"
#include "mbc1m/mbc1m.cpp"
#include "mbc2/mbc2.cpp"
#include "mbc3/mbc3.cpp"
#include "mbc5/mbc5.cpp"
@@ -13,15 +14,6 @@ namespace GameBoy {
#include "serialization.cpp"
Cartridge cartridge;
Cartridge::Cartridge() {
loaded = false;
sha256 = "";
}
Cartridge::~Cartridge() {
unload();
}
auto Cartridge::manifest() const -> string {
return information.markup;
}
@@ -30,25 +22,9 @@ auto Cartridge::title() const -> string {
return information.title;
}
//intended for use with Super Game Boy for when no Game Boy cartridge is inserted
auto Cartridge::load_empty(System::Revision revision) -> void {
unload();
romsize = 32768;
romdata = allocate<uint8>(romsize, 0xff);
ramsize = 0;
mapper = &mbc0;
sha256 = Hash::SHA256(romdata, romsize).digest();
loaded = true;
system.load(revision);
}
auto Cartridge::load(System::Revision revision) -> void {
unload();
system.revision = revision; //needed for ID::Manifest to return correct group ID
if(revision != System::Revision::SuperGameBoy) {
interface->loadRequest(ID::Manifest, "manifest.bml", true);
}
information.markup = "";
interface->loadRequest(ID::Manifest, "manifest.bml", !system.sgb());
information.mapper = Mapper::Unknown;
information.ram = false;
@@ -65,6 +41,7 @@ auto Cartridge::load(System::Revision revision) -> void {
auto mapperid = document["board/mapper"].text();
if(mapperid == "none" ) information.mapper = Mapper::MBC0;
if(mapperid == "MBC1" ) information.mapper = Mapper::MBC1;
if(mapperid == "MBC1M") information.mapper = Mapper::MBC1M;
if(mapperid == "MBC2" ) information.mapper = Mapper::MBC2;
if(mapperid == "MBC3" ) information.mapper = Mapper::MBC3;
if(mapperid == "MBC5" ) information.mapper = Mapper::MBC5;
@@ -78,26 +55,24 @@ auto Cartridge::load(System::Revision revision) -> void {
auto rom = document["board/rom"];
auto ram = document["board/ram"];
romsize = rom["size"].natural();
romsize = max(32768u, rom["size"].natural());
romdata = allocate<uint8>(romsize, 0xff);
ramsize = ram["size"].natural();
ramdata = allocate<uint8>(ramsize, 0xff);
//Super Game Boy core loads memory from Super Famicom core
if(revision != System::Revision::SuperGameBoy) {
if(auto name = rom["name"].text()) interface->loadRequest(ID::ROM, name, true);
if(auto name = rom["name"].text()) interface->loadRequest(ID::ROM, name, !system.sgb());
if(auto name = ram["name"].text()) interface->loadRequest(ID::RAM, name, false);
if(auto name = ram["name"].text()) memory.append({ID::RAM, name});
}
information.romsize = rom["size"].natural();
information.ramsize = ram["size"].natural();
information.romsize = romsize;
information.ramsize = ramsize;
information.battery = (bool)ram["name"];
switch(information.mapper) { default:
case Mapper::MBC0: mapper = &mbc0; break;
case Mapper::MBC1: mapper = &mbc1; break;
case Mapper::MBC1M: mapper = &mbc1m; break;
case Mapper::MBC2: mapper = &mbc2; break;
case Mapper::MBC3: mapper = &mbc3; break;
case Mapper::MBC5: mapper = &mbc5; break;
@@ -107,14 +82,11 @@ auto Cartridge::load(System::Revision revision) -> void {
}
sha256 = Hash::SHA256(romdata, romsize).digest();
loaded = true;
system.load(revision);
}
auto Cartridge::unload() -> void {
if(romdata) { delete[] romdata; romdata = nullptr; romsize = 0; }
if(ramdata) { delete[] ramdata; ramdata = nullptr; ramsize = 0; }
loaded = false;
}
auto Cartridge::rom_read(uint addr) -> uint8 {
@@ -144,7 +116,7 @@ auto Cartridge::mmio_read(uint16 addr) -> uint8 {
if(bootrom_enable) {
const uint8* data = nullptr;
switch(system.revision) { default:
switch(system.revision()) { default:
case System::Revision::GameBoy: data = system.bootROM.dmg; break;
case System::Revision::SuperGameBoy: data = system.bootROM.sgb; break;
case System::Revision::GameBoyColor: data = system.bootROM.cgb; break;
@@ -170,6 +142,7 @@ auto Cartridge::power() -> void {
mbc0.power();
mbc1.power();
mbc1m.power();
mbc2.power();
mbc3.power();
mbc5.power();

View File

@@ -1,8 +1,4 @@
struct Cartridge : MMIO, property<Cartridge> {
Cartridge();
~Cartridge();
auto load_empty(System::Revision revision) -> void;
auto load(System::Revision revision) -> void;
auto unload() -> void;
@@ -20,6 +16,7 @@ struct Cartridge : MMIO, property<Cartridge> {
#include "mbc0/mbc0.hpp"
#include "mbc1/mbc1.hpp"
#include "mbc1m/mbc1m.hpp"
#include "mbc2/mbc2.hpp"
#include "mbc3/mbc3.hpp"
#include "mbc5/mbc5.hpp"
@@ -30,6 +27,7 @@ struct Cartridge : MMIO, property<Cartridge> {
enum Mapper : uint {
MBC0,
MBC1,
MBC1M,
MBC2,
MBC3,
MBC5,
@@ -62,7 +60,6 @@ struct Cartridge : MMIO, property<Cartridge> {
};
vector<Memory> memory;
readonly<bool> loaded;
readonly<string> sha256;
uint8* romdata = nullptr;

View File

@@ -9,7 +9,7 @@ auto Cartridge::HuC3::mmio_read(uint16 addr) -> uint8 {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
return 0xff;
return 0x01; //does not return open collection
}
return 0xff;

View File

@@ -0,0 +1,40 @@
auto Cartridge::MBC1M::mmio_read(uint16 addr) -> uint8 {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
if(!modeSelect) return cartridge.rom_read(addr & 0x3fff);
return cartridge.rom_read((romHi << 4) * 0x4000 + (addr & 0x3fff));
}
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom_read((romHi << 4 | romLo) * 0x4000 + (addr & 0x3fff));
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.ram_read(addr & 0x1fff);
}
return 0xff;
}
auto Cartridge::MBC1M::mmio_write(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0x2000) { //$2000-3fff
romLo = data.bits(0,3);
}
if((addr & 0xe000) == 0x4000) { //$4000-5fff
romHi = data.bits(0,1);
}
if((addr & 0xe000) == 0x6000) { //$6000-7fff
modeSelect = data.bit(0);
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
cartridge.ram_write(addr & 0x1fff, data);
}
}
auto Cartridge::MBC1M::power() -> void {
romHi = 0;
romLo = 1;
modeSelect = 0;
}

View File

@@ -0,0 +1,9 @@
struct MBC1M : MMIO {
auto mmio_read(uint16 addr) -> uint8;
auto mmio_write(uint16 addr, uint8 data) -> void;
auto power() -> void;
uint4 romLo;
uint2 romHi;
uint1 modeSelect;
} mbc1m;

View File

@@ -7,6 +7,10 @@ auto Cartridge::serialize(serializer& s) -> void {
s.integer(mbc1.ram_select);
s.integer(mbc1.mode_select);
s.integer(mbc1m.romLo);
s.integer(mbc1m.romHi);
s.integer(mbc1m.modeSelect);
s.integer(mbc2.ram_enable);
s.integer(mbc2.rom_select);

View File

@@ -8,23 +8,16 @@ namespace GameBoy {
#include "serialization.cpp"
CPU cpu;
auto CPU::Main() -> void {
cpu.main();
auto CPU::Enter() -> void {
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::CPU) {
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
interrupt_test();
exec();
}
instruction();
}
auto CPU::interrupt_raise(CPU::Interrupt id) -> void {
auto CPU::raise(CPU::Interrupt id) -> void {
if(id == Interrupt::Vblank) {
status.interrupt_request_vblank = 1;
if(status.interrupt_enable_vblank) r.halt = false;
@@ -52,42 +45,32 @@ auto CPU::interrupt_raise(CPU::Interrupt id) -> void {
}
auto CPU::interrupt_test() -> void {
if(r.ime) {
if(!r.ime) return;
if(status.interrupt_request_vblank && status.interrupt_enable_vblank) {
status.interrupt_request_vblank = 0;
return interrupt_exec(0x0040);
return interrupt(0x0040);
}
if(status.interrupt_request_stat && status.interrupt_enable_stat) {
status.interrupt_request_stat = 0;
return interrupt_exec(0x0048);
return interrupt(0x0048);
}
if(status.interrupt_request_timer && status.interrupt_enable_timer) {
status.interrupt_request_timer = 0;
return interrupt_exec(0x0050);
return interrupt(0x0050);
}
if(status.interrupt_request_serial && status.interrupt_enable_serial) {
status.interrupt_request_serial = 0;
return interrupt_exec(0x0058);
return interrupt(0x0058);
}
if(status.interrupt_request_joypad && status.interrupt_enable_joypad) {
status.interrupt_request_joypad = 0;
return interrupt_exec(0x0060);
return interrupt(0x0060);
}
}
}
auto CPU::interrupt_exec(uint16 pc) -> void {
op_io();
op_io();
op_io();
r.ime = 0;
op_write(--r[SP], r[PC] >> 8);
op_write(--r[SP], r[PC] >> 0);
r[PC] = pc;
}
auto CPU::stop() -> bool {
@@ -102,7 +85,7 @@ auto CPU::stop() -> bool {
}
auto CPU::power() -> void {
create(Main, 4 * 1024 * 1024);
create(Enter, 4 * 1024 * 1024);
LR35902::power();
for(uint n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM

View File

@@ -1,11 +1,10 @@
struct CPU : Processor::LR35902, Thread, MMIO {
enum class Interrupt : uint { Vblank, Stat, Timer, Serial, Joypad };
static auto Main() -> void;
static auto Enter() -> void;
auto main() -> void;
auto interrupt_raise(Interrupt id) -> void;
auto raise(Interrupt id) -> void;
auto interrupt_test() -> void;
auto interrupt_exec(uint16 pc) -> void;
auto stop() -> bool;
auto power() -> void;
@@ -18,9 +17,9 @@ struct CPU : Processor::LR35902, Thread, MMIO {
auto mmio_write(uint16 addr, uint8 data) -> void;
//memory.cpp
auto op_io() -> void;
auto op_read(uint16 addr) -> uint8;
auto op_write(uint16 addr, uint8 data) -> void;
auto io() -> void override;
auto read(uint16 addr) -> uint8 override;
auto write(uint16 addr, uint8 data) -> void override;
auto cycle_edge() -> void;
auto dma_read(uint16 addr) -> uint8;
auto dma_write(uint16 addr, uint8 data) -> void;

View File

@@ -1,15 +1,15 @@
auto CPU::op_io() -> void {
auto CPU::io() -> void {
cycle_edge();
add_clocks(4);
}
auto CPU::op_read(uint16 addr) -> uint8 {
auto CPU::read(uint16 addr) -> uint8 {
cycle_edge();
add_clocks(4);
return bus.read(addr);
}
auto CPU::op_write(uint16 addr, uint8 data) -> void {
auto CPU::write(uint16 addr, uint8 data) -> void {
cycle_edge();
add_clocks(4);
bus.write(addr, data);

View File

@@ -18,7 +18,7 @@ auto CPU::mmio_joyp_poll() -> void {
dpad |= interface->inputPoll(0, 0, (uint)Input::Left) << 1;
dpad |= interface->inputPoll(0, 0, (uint)Input::Right) << 0;
if(system.revision != System::Revision::SuperGameBoy) {
if(system.revision() != System::Revision::SuperGameBoy) {
//D-pad pivot makes it impossible to press opposing directions at the same time
//however, Super Game Boy BIOS is able to set these bits together
if(dpad & 4) dpad &= ~8; //disallow up+down
@@ -29,7 +29,7 @@ auto CPU::mmio_joyp_poll() -> void {
if(status.p15 == 1 && status.p14 == 1) status.joyp -= status.mlt_req;
if(status.p15 == 0) status.joyp &= button ^ 0x0f;
if(status.p14 == 0) status.joyp &= dpad ^ 0x0f;
if(status.joyp != 0x0f) interrupt_raise(Interrupt::Joypad);
if(status.joyp != 0x0f) raise(Interrupt::Joypad);
}
auto CPU::mmio_read(uint16 addr) -> uint8 {
@@ -38,17 +38,19 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
if(addr == 0xff00) { //JOYP
mmio_joyp_poll();
return (status.p15 << 5)
return 0xc0
| (status.p15 << 5)
| (status.p14 << 4)
| (status.joyp << 0);
}
if(addr == 0xff01) { //SB
return 0xff;
return 0x00;
}
if(addr == 0xff02) { //SC
return (status.serial_transfer << 7)
| 0x7e
| (status.serial_clock << 0);
}
@@ -65,12 +67,14 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
}
if(addr == 0xff07) { //TAC
return (status.timer_enable << 2)
return 0xf8
| (status.timer_enable << 2)
| (status.timer_clock << 0);
}
if(addr == 0xff0f) { //IF
return (status.interrupt_request_joypad << 4)
return 0xe0
| (status.interrupt_request_joypad << 4)
| (status.interrupt_request_serial << 3)
| (status.interrupt_request_timer << 2)
| (status.interrupt_request_stat << 1)
@@ -123,7 +127,8 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
}
if(addr == 0xffff) { //IE
return (status.interrupt_enable_joypad << 4)
return 0xe0
| (status.interrupt_enable_joypad << 4)
| (status.interrupt_enable_serial << 3)
| (status.interrupt_enable_timer << 2)
| (status.interrupt_enable_stat << 1)

View File

@@ -3,9 +3,7 @@
// 154 scanlines/frame
auto CPU::add_clocks(uint clocks) -> void {
if(system.sgb()) system.clocks_executed += clocks;
while(clocks--) {
for(auto n : range(clocks)) {
if(++status.clock == 0) {
cartridge.mbc3.second();
}
@@ -19,20 +17,23 @@ auto CPU::add_clocks(uint clocks) -> void {
if((status.div & 1023) == 0) timer_4096hz();
ppu.clock -= ppu.frequency;
if(ppu.clock < 0) co_switch(scheduler.active_thread = ppu.thread);
if(ppu.clock < 0) co_switch(ppu.thread);
apu.clock -= apu.frequency;
if(apu.clock < 0) co_switch(scheduler.active_thread = apu.thread);
if(apu.clock < 0) co_switch(apu.thread);
}
if(system.sgb()) scheduler.exit(Scheduler::ExitReason::StepEvent);
if(system.sgb()) {
system._clocksExecuted += clocks;
scheduler.exit(Scheduler::Event::Step);
}
}
auto CPU::timer_262144hz() -> void {
if(status.timer_enable && status.timer_clock == 1) {
if(++status.tima == 0) {
status.tima = status.tma;
interrupt_raise(Interrupt::Timer);
raise(Interrupt::Timer);
}
}
}
@@ -41,7 +42,7 @@ auto CPU::timer_65536hz() -> void {
if(status.timer_enable && status.timer_clock == 2) {
if(++status.tima == 0) {
status.tima = status.tma;
interrupt_raise(Interrupt::Timer);
raise(Interrupt::Timer);
}
}
}
@@ -50,7 +51,7 @@ auto CPU::timer_16384hz() -> void {
if(status.timer_enable && status.timer_clock == 3) {
if(++status.tima == 0) {
status.tima = status.tma;
interrupt_raise(Interrupt::Timer);
raise(Interrupt::Timer);
}
}
}
@@ -59,7 +60,7 @@ auto CPU::timer_8192hz() -> void {
if(status.serial_transfer && status.serial_clock) {
if(--status.serial_bits == 0) {
status.serial_transfer = 0;
interrupt_raise(Interrupt::Serial);
raise(Interrupt::Serial);
}
}
}
@@ -68,7 +69,7 @@ auto CPU::timer_4096hz() -> void {
if(status.timer_enable && status.timer_clock == 0) {
if(++status.tima == 0) {
status.tima = status.tma;
interrupt_raise(Interrupt::Timer);
raise(Interrupt::Timer);
}
}
}

View File

@@ -1,22 +1,17 @@
#pragma once
//license: GPLv3
//started: 2010-12-27
#include <emulator/emulator.hpp>
#include <processor/lr35902/lr35902.hpp>
namespace GameBoy {
namespace Info {
static const string Name = "bgb";
static const uint SerializerVersion = 4;
static const uint SerializerVersion = 5;
}
}
/*
bgb - Game Boy, Super Game Boy, and Game Boy Color emulator
author: byuu
license: GPLv3
project started: 2010-12-27
*/
#include <libco/libco.h>
namespace GameBoy {
@@ -27,7 +22,7 @@ namespace GameBoy {
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
if(thread) co_delete(thread);
thread = co_create(65536 * sizeof(void*), entrypoint);
thread = co_create(65'536 * sizeof(void*), entrypoint);
this->frequency = frequency;
clock = 0;
}
@@ -50,7 +45,6 @@ namespace GameBoy {
#include <gb/ppu/ppu.hpp>
#include <gb/apu/apu.hpp>
#include <gb/cheat/cheat.hpp>
#include <gb/video/video.hpp>
}
#include <gb/interface/interface.hpp>

View File

@@ -9,12 +9,14 @@ Interface::Interface() {
interface = this;
hook = nullptr;
information.manufacturer = "Nintendo";
information.name = "Game Boy";
information.width = 160;
information.height = 144;
information.overscan = false;
information.aspectRatio = 1.0;
information.resettable = false;
information.capability.states = true;
information.capability.cheats = true;
@@ -23,19 +25,18 @@ Interface::Interface() {
{
Device device{0, ID::Device, "Controller"};
device.input.append({0, 0, "Up" });
device.input.append({1, 0, "Down" });
device.input.append({2, 0, "Left" });
device.input.append({3, 0, "Right" });
device.input.append({4, 0, "B" });
device.input.append({5, 0, "A" });
device.input.append({6, 0, "Select"});
device.input.append({7, 0, "Start" });
device.order = {0, 1, 2, 3, 4, 5, 6, 7};
this->device.append(device);
device.inputs.append({0, 0, "Up" });
device.inputs.append({1, 0, "Down" });
device.inputs.append({2, 0, "Left" });
device.inputs.append({3, 0, "Right" });
device.inputs.append({4, 0, "B" });
device.inputs.append({5, 0, "A" });
device.inputs.append({6, 0, "Select"});
device.inputs.append({7, 0, "Start" });
devices.append(device);
}
port.append({0, "Device", {device[0]}});
ports.append({0, "Device", {devices[0]}});
}
auto Interface::manifest() -> string {
@@ -50,12 +51,73 @@ auto Interface::videoFrequency() -> double {
return 4194304.0 / (154.0 * 456.0);
}
auto Interface::videoColors() -> uint32 {
return !system.cgb() ? 1 << 2 : 1 << 15;
}
auto Interface::videoColor(uint32 color) -> uint64 {
if(!system.cgb()) {
if(!settings.colorEmulation) {
uint64 L = image::normalize(3 - color, 2, 16);
return L << 32 | L << 16 | L << 0;
} else {
#define DMG_PALETTE_GREEN
//#define DMG_PALETTE_YELLOW
//#define DMG_PALETTE_WHITE
const uint16 monochrome[4][3] = {
#if defined(DMG_PALETTE_GREEN)
{0xaeae, 0xd9d9, 0x2727},
{0x5858, 0xa0a0, 0x2828},
{0x2020, 0x6262, 0x2929},
{0x1a1a, 0x4545, 0x2a2a},
#elif defined(DMG_PALETTE_YELLOW)
{0xffff, 0xf7f7, 0x7b7b},
{0xb5b5, 0xaeae, 0x4a4a},
{0x6b6b, 0x6969, 0x3131},
{0x2121, 0x2020, 0x1010},
#elif defined(DMG_PALETTE_WHITE)
{0xffff, 0xffff, 0xffff},
{0xaaaa, 0xaaaa, 0xaaaa},
{0x5555, 0x5555, 0x5555},
{0x0000, 0x0000, 0x0000},
#endif
};
uint64 R = monochrome[color][0];
uint64 G = monochrome[color][1];
uint64 B = monochrome[color][2];
return R << 32 | G << 16 | B << 0;
}
} else {
uint r = color.bits( 0, 4);
uint g = color.bits( 5, 9);
uint b = color.bits(10,14);
uint64_t R = image::normalize(r, 5, 16);
uint64_t G = image::normalize(g, 5, 16);
uint64_t B = image::normalize(b, 5, 16);
if(settings.colorEmulation) {
R = (r * 26 + g * 4 + b * 2);
G = ( g * 24 + b * 8);
B = (r * 6 + g * 4 + b * 22);
R = image::normalize(min(960, R), 10, 16);
G = image::normalize(min(960, G), 10, 16);
B = image::normalize(min(960, B), 10, 16);
}
return R << 32 | G << 16 | B << 0;
}
}
auto Interface::audioFrequency() -> double {
return 4194304.0 / 2.0;
}
auto Interface::loaded() -> bool {
return cartridge.loaded();
return system.loaded();
}
auto Interface::sha256() -> string {
@@ -72,7 +134,7 @@ auto Interface::group(uint id) -> uint {
case ID::Manifest:
case ID::ROM:
case ID::RAM:
switch(system.revision) {
switch(system.revision()) {
case System::Revision::GameBoy: return ID::GameBoy;
case System::Revision::SuperGameBoy: return ID::SuperGameBoy;
case System::Revision::GameBoyColor: return ID::GameBoyColor;
@@ -83,9 +145,9 @@ auto Interface::group(uint id) -> uint {
}
auto Interface::load(uint id) -> void {
if(id == ID::GameBoy) cartridge.load(System::Revision::GameBoy);
if(id == ID::SuperGameBoy) cartridge.load(System::Revision::SuperGameBoy);
if(id == ID::GameBoyColor) cartridge.load(System::Revision::GameBoyColor);
if(id == ID::GameBoy) system.load(System::Revision::GameBoy);
if(id == ID::SuperGameBoy) system.load(System::Revision::SuperGameBoy);
if(id == ID::GameBoyColor) system.load(System::Revision::GameBoyColor);
}
auto Interface::save() -> void {
@@ -100,15 +162,15 @@ auto Interface::load(uint id, const stream& stream) -> void {
}
if(id == ID::GameBoyBootROM) {
stream.read(system.bootROM.dmg, min( 256u, stream.size()));
stream.read((uint8_t*)system.bootROM.dmg, min( 256u, stream.size()));
}
if(id == ID::SuperGameBoyBootROM) {
stream.read(system.bootROM.sgb, min( 256u, stream.size()));
stream.read((uint8_t*)system.bootROM.sgb, min( 256u, stream.size()));
}
if(id == ID::GameBoyColorBootROM) {
stream.read(system.bootROM.cgb, min(2048u, stream.size()));
stream.read((uint8_t*)system.bootROM.cgb, min(2048u, stream.size()));
}
if(id == ID::Manifest) {
@@ -116,23 +178,23 @@ auto Interface::load(uint id, const stream& stream) -> void {
}
if(id == ID::ROM) {
stream.read(cartridge.romdata, min(cartridge.romsize, stream.size()));
stream.read((uint8_t*)cartridge.romdata, min(cartridge.romsize, stream.size()));
}
if(id == ID::RAM) {
stream.read(cartridge.ramdata, min(stream.size(), cartridge.ramsize));
stream.read((uint8_t*)cartridge.ramdata, min(stream.size(), cartridge.ramsize));
}
}
auto Interface::save(uint id, const stream& stream) -> void {
if(id == ID::RAM) {
stream.write(cartridge.ramdata, cartridge.ramsize);
stream.write((uint8_t*)cartridge.ramdata, cartridge.ramsize);
}
}
auto Interface::unload() -> void {
save();
cartridge.unload();
system.unload();
}
auto Interface::power() -> void {
@@ -148,7 +210,7 @@ auto Interface::run() -> void {
}
auto Interface::serialize() -> serializer {
system.runtosave();
system.runToSave();
return system.serialize();
}
@@ -193,8 +255,18 @@ auto Interface::get(const string& name) -> any {
}
auto Interface::set(const string& name, const any& value) -> bool {
if(name == "Blur Emulation" && value.is<bool>()) return settings.blurEmulation = value.get<bool>(), true;
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
if(name == "Blur Emulation" && value.is<bool>()) {
settings.blurEmulation = value.get<bool>();
system.configureVideoEffects();
return true;
}
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
system.configureVideoPalette();
return true;
}
return false;
}

View File

@@ -30,6 +30,8 @@ struct Interface : Emulator::Interface {
auto manifest() -> string;
auto title() -> string;
auto videoFrequency() -> double;
auto videoColors() -> uint32;
auto videoColor(uint32 color) -> uint64;
auto audioFrequency() -> double;
auto loaded() -> bool;
@@ -67,7 +69,7 @@ struct Interface : Emulator::Interface {
auto joypWrite(bool p15, bool p14) -> void;
private:
vector<Device> device;
vector<Device> devices;
};
struct Settings {

View File

@@ -16,13 +16,13 @@ auto Memory::operator[](uint addr) -> uint8& {
auto Memory::allocate(uint size_) -> void {
free();
size = size_;
data = new uint8_t[size]();
data = new uint8[size];
}
auto Memory::copy(const uint8_t* data_, unsigned size_) -> void {
auto Memory::copy(const uint8* data_, unsigned size_) -> void {
free();
size = size_;
data = new uint8_t[size];
data = new uint8[size];
memcpy(data, data_, size);
}

View File

@@ -37,6 +37,7 @@ auto PPU::cgb_read_tile(bool select, uint x, uint y, uint& attr, uint& data) ->
auto PPU::cgb_scanline() -> void {
px = 0;
if(!enabled()) return;
const uint Height = (status.ob_size == 0 ? 8 : 16);
sprites = 0;
@@ -68,7 +69,7 @@ auto PPU::cgb_run() -> void {
ob.priority = 0;
uint color = 0x7fff;
if(status.display_enable) {
if(enabled()) {
cgb_run_bg();
if(status.window_display_enable) cgb_run_window();
if(status.ob_enable) cgb_run_ob();

View File

@@ -19,6 +19,7 @@ auto PPU::dmg_read_tile(bool select, uint x, uint y, uint& data) -> void {
auto PPU::dmg_scanline() -> void {
px = 0;
if(!enabled()) return;
const uint Height = (status.ob_size == 0 ? 8 : 16);
sprites = 0;
@@ -59,7 +60,7 @@ auto PPU::dmg_run() -> void {
ob.palette = 0;
uint color = 0;
if(status.display_enable) {
if(enabled()) {
if(status.bg_enable) dmg_run_bg();
if(status.window_display_enable) dmg_run_window();
if(status.ob_enable) dmg_run_ob();

View File

@@ -24,18 +24,12 @@ auto PPU::mmio_read(uint16 addr) -> uint8 {
}
if(addr == 0xff41) { //STAT
uint mode;
if(status.ly >= 144) mode = 1; //Vblank
else if(status.lx < 80) mode = 2; //OAM
else if(status.lx < 252) mode = 3; //LCD
else mode = 0; //Hblank
return (status.interrupt_lyc << 6)
| (status.interrupt_oam << 5)
| (status.interrupt_vblank << 4)
| (status.interrupt_hblank << 3)
| ((status.ly == status.lyc) << 2)
| (mode << 0);
| (status.mode << 0);
}
if(addr == 0xff42) { //SCY
@@ -125,7 +119,7 @@ auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
//restart cothread to begin new frame
auto clock = this->clock;
create(Main, 4 * 1024 * 1024);
create(Enter, 4 * 1024 * 1024);
this->clock = clock;
}
@@ -145,6 +139,13 @@ auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
status.interrupt_oam = data & 0x20;
status.interrupt_vblank = data & 0x10;
status.interrupt_hblank = data & 0x08;
//hardware bug: writes to STAT on DMG,SGB during vblank triggers STAT IRQ
//note: this behavior isn't entirely correct; more research is needed ...
if(!system.cgb() && status.mode == 1) {
cpu.raise(CPU::Interrupt::Stat);
}
return;
}

View File

@@ -1,77 +1,82 @@
#include <gb/gb.hpp>
//LY = 0-153
//Raster = 0-143
//Vblank = 144-153
//LX = 0-455
namespace GameBoy {
PPU ppu;
#include "mmio.cpp"
#include "dmg.cpp"
#include "cgb.cpp"
#include "serialization.cpp"
PPU ppu;
auto PPU::Main() -> void {
ppu.main();
auto PPU::enabled() const -> bool { return status.display_enable; }
auto PPU::Enter() -> void {
while(true) scheduler.synchronize(), ppu.main();
}
auto PPU::main() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
status.lx = 0;
interface->lcdScanline(); //Super Game Boy notification
if(status.display_enable) {
//LYC of zero triggers on LY==153
if((status.lyc && status.ly == status.lyc) || (!status.lyc && status.ly == 153)) {
if(status.interrupt_lyc) cpu.interrupt_raise(CPU::Interrupt::Stat);
if(status.ly <= 143) {
mode(2);
scanline();
wait(92);
mode(3);
for(auto n : range(160)) {
run();
wait(1);
}
if(status.ly <= 143) {
scanline();
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
mode(0);
if(enabled()) cpu.hblank();
wait(204);
} else {
mode(1);
wait(456);
}
status.ly++;
if(status.ly == 144) {
if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
else if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat); //hardware quirk
cpu.interrupt_raise(CPU::Interrupt::Vblank);
}
if(enabled()) cpu.raise(CPU::Interrupt::Vblank);
scheduler.exit(Scheduler::Event::Frame);
}
add_clocks(92);
if(status.ly <= 143) {
for(auto n : range(160)) {
if(status.display_enable) run();
add_clocks(1);
}
if(status.display_enable) {
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
cpu.hblank();
}
} else {
add_clocks(160);
}
add_clocks(204);
if(++status.ly == 154) {
if(status.ly == 154) {
status.ly = 0;
scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
}
}
auto PPU::add_clocks(uint clocks) -> void {
auto PPU::mode(uint mode) -> void {
status.mode = mode;
}
auto PPU::stat() -> void {
bool irq = status.irq;
status.irq = status.interrupt_hblank && status.mode == 0;
status.irq |= status.interrupt_vblank && status.mode == 1;
status.irq |= status.interrupt_oam && status.mode == 2;
status.irq |= status.interrupt_lyc && coincidence();
if(!irq && status.irq) cpu.raise(CPU::Interrupt::Stat);
}
auto PPU::coincidence() -> bool {
uint ly = status.ly;
if(ly == 153 && status.lx >= 92) ly = 0; //LYC=0 triggers early during LY=153
return ly == status.lyc;
}
auto PPU::refresh() -> void {
if(!system.sgb()) Emulator::video.refresh(screen, 160 * sizeof(uint32), 160, 144);
}
auto PPU::wait(uint clocks) -> void {
while(clocks--) {
stat();
if(status.dma_active) {
uint hi = status.dma_clock++;
uint lo = hi & (cpu.status.speed_double ? 1 : 3);
@@ -90,21 +95,19 @@ auto PPU::add_clocks(uint clocks) -> void {
status.lx++;
clock += cpu.frequency;
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
co_switch(scheduler.active_thread = cpu.thread);
}
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
}
}
auto PPU::hflip(uint data) const -> uint {
return ((data & 0x8080) >> 7) | ((data & 0x4040) >> 5)
| ((data & 0x2020) >> 3) | ((data & 0x1010) >> 1)
| ((data & 0x0808) << 1) | ((data & 0x0404) << 3)
| ((data & 0x0202) << 5) | ((data & 0x0101) << 7);
return (data & 0x8080) >> 7 | (data & 0x4040) >> 5
| (data & 0x2020) >> 3 | (data & 0x1010) >> 1
| (data & 0x0808) << 1 | (data & 0x0404) << 3
| (data & 0x0202) << 5 | (data & 0x0101) << 7;
}
auto PPU::power() -> void {
create(Main, 4 * 1024 * 1024);
create(Enter, 4 * 1024 * 1024);
if(system.cgb()) {
scanline = {&PPU::cgb_scanline, this};
@@ -141,11 +144,12 @@ auto PPU::power() -> void {
for(auto& n : vram) n = 0x00;
for(auto& n : oam) n = 0x00;
for(auto& n : bgp) n = 0x00;
for(auto& n : obp[0]) n = 0x00;
for(auto& n : obp[1]) n = 0x00;
for(auto& n : obp[0]) n = 3;
for(auto& n : obp[1]) n = 3;
for(auto& n : bgpd) n = 0x0000;
for(auto& n : obpd) n = 0x0000;
status.irq = false;
status.lx = 0;
status.display_enable = 0;
@@ -161,6 +165,7 @@ auto PPU::power() -> void {
status.interrupt_oam = 0;
status.interrupt_vblank = 0;
status.interrupt_hblank = 0;
status.mode = 0;
status.scy = 0;
status.scx = 0;

View File

@@ -1,7 +1,13 @@
struct PPU : Thread, MMIO {
static auto Main() -> void;
auto enabled() const -> bool;
static auto Enter() -> void;
auto main() -> void;
auto add_clocks(uint clocks) -> void;
auto mode(uint) -> void;
auto stat() -> void;
auto coincidence() -> bool;
auto refresh() -> void;
auto wait(uint clocks) -> void;
auto hflip(uint data) const -> uint;
@@ -41,6 +47,7 @@ struct PPU : Thread, MMIO {
function<auto () -> void> run;
struct Status {
bool irq; //STAT IRQ line
uint lx;
//$ff40 LCDC
@@ -58,6 +65,7 @@ struct PPU : Thread, MMIO {
bool interrupt_oam;
bool interrupt_vblank;
bool interrupt_hblank;
uint2 mode;
//$ff42 SCY
uint8 scy;

View File

@@ -9,6 +9,7 @@ auto PPU::serialize(serializer& s) -> void {
s.array(bgpd);
s.array(obpd);
s.integer(status.irq);
s.integer(status.lx);
s.integer(status.display_enable);
@@ -24,6 +25,7 @@ auto PPU::serialize(serializer& s) -> void {
s.integer(status.interrupt_oam);
s.integer(status.interrupt_vblank);
s.integer(status.interrupt_hblank);
s.integer(status.mode);
s.integer(status.scy);
s.integer(status.scx);

View File

@@ -4,20 +4,41 @@ namespace GameBoy {
Scheduler scheduler;
auto Scheduler::init() -> void {
host_thread = co_active();
active_thread = cpu.thread;
auto Scheduler::power() -> void {
host = co_active();
resume = cpu.thread;
}
auto Scheduler::enter() -> void {
host_thread = co_active();
co_switch(active_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(ExitReason reason) -> void {
exit_reason = reason;
active_thread = co_active();
co_switch(host_thread);
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,14 +1,29 @@
struct Scheduler {
enum class SynchronizeMode : uint { None, CPU, All } sync;
enum class ExitReason : uint { UnknownEvent, StepEvent, FrameEvent, SynchronizeEvent };
enum class Mode : uint {
Run,
SynchronizeCPU,
SynchronizeAll,
};
auto init() -> void;
auto enter() -> void;
auto exit(ExitReason) -> void;
enum class Event : uint {
Unknown,
Step,
Frame,
Synchronize,
};
cothread_t host_thread = nullptr;
cothread_t active_thread = nullptr;
ExitReason exit_reason = ExitReason::UnknownEvent;
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

@@ -1,5 +1,5 @@
auto System::serialize() -> serializer {
serializer s(serialize_size);
serializer s(_serializeSize);
uint signature = 0x31545342, version = Info::SerializerVersion;
char hash[64], description[512];
@@ -11,7 +11,7 @@ auto System::serialize() -> serializer {
s.array(hash);
s.array(description);
serialize_all(s);
serializeAll(s);
return s;
}
@@ -28,15 +28,15 @@ auto System::unserialize(serializer& s) -> bool {
if(version != Info::SerializerVersion) return false;
power();
serialize_all(s);
serializeAll(s);
return true;
}
auto System::serialize(serializer& s) -> void {
s.integer(clocks_executed);
s.integer(_clocksExecuted);
}
auto System::serialize_all(serializer& s) -> void {
auto System::serializeAll(serializer& s) -> void {
cartridge.serialize(s);
system.serialize(s);
cpu.serialize(s);
@@ -44,10 +44,10 @@ auto System::serialize_all(serializer& s) -> void {
apu.serialize(s);
}
auto System::serialize_init() -> void {
auto System::serializeInit() -> void {
serializer s;
uint signature = 0, version = 0, crc32 = 0;
uint signature = 0, version = 0;
char hash[64], description[512];
s.integer(signature);
@@ -55,6 +55,6 @@ auto System::serialize_init() -> void {
s.array(hash);
s.array(description);
serialize_all(s);
serialize_size = s.size();
serializeAll(s);
_serializeSize = s.size();
}

View File

@@ -2,9 +2,14 @@
namespace GameBoy {
#include "video.cpp"
#include "serialization.cpp"
System system;
auto System::loaded() const -> bool { return _loaded; }
auto System::revision() const -> Revision { return _revision; }
auto System::clocksExecuted() const -> uint { return _clocksExecuted; }
System::System() {
for(auto& byte : bootROM.dmg) byte = 0;
for(auto& byte : bootROM.sgb) byte = 0;
@@ -12,37 +17,13 @@ System::System() {
}
auto System::run() -> void {
scheduler.sync = Scheduler::SynchronizeMode::None;
scheduler.enter();
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
video.refresh();
}
}
auto System::runtosave() -> void {
scheduler.sync = Scheduler::SynchronizeMode::CPU;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.active_thread = ppu.thread;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.active_thread = apu.thread;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::None;
}
auto System::runthreadtosave() -> void {
while(true) {
scheduler.enter();
if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break;
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
video.refresh();
}
}
auto System::runToSave() -> void {
scheduler.synchronize(cpu.thread);
scheduler.synchronize(ppu.thread);
scheduler.synchronize(apu.thread);
}
auto System::init() -> void {
@@ -50,31 +31,53 @@ auto System::init() -> void {
}
auto System::load(Revision revision) -> void {
this->revision = revision;
serialize_init();
if(revision == Revision::SuperGameBoy) return; //Super Famicom core loads boot ROM for SGB
_revision = revision;
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
auto document = BML::unserialize(information.manifest);
string path = "system/cpu/rom/name";
if(revision == Revision::SuperGameBoy) path = "board/icd2/rom/name";
if(auto bootROM = document["system/cpu/rom/name"].text()) {
if(auto bootROM = document[path].text()) {
interface->loadRequest(
revision == Revision::GameBoy ? ID::GameBoyBootROM : ID::GameBoyColorBootROM,
revision == Revision::GameBoy ? ID::GameBoyBootROM
: revision == Revision::SuperGameBoy ? ID::SuperGameBoyBootROM
: revision == Revision::GameBoyColor ? ID::GameBoyColorBootROM
: ID::GameBoyBootROM,
bootROM, true
);
}
cartridge.load(revision);
serializeInit();
_loaded = true;
}
auto System::unload() -> void {
if(!loaded()) return;
cartridge.unload();
_loaded = false;
}
auto System::power() -> void {
if(!system.sgb()) {
Emulator::video.reset();
Emulator::video.setInterface(interface);
configureVideoPalette();
configureVideoEffects();
Emulator::audio.reset();
Emulator::audio.setInterface(interface);
}
bus.power();
cartridge.power();
cpu.power();
ppu.power();
apu.power();
video.power();
scheduler.init();
scheduler.power();
clocks_executed = 0;
_clocksExecuted = 0;
}
}

View File

@@ -9,29 +9,37 @@ struct System {
GameBoy,
SuperGameBoy,
GameBoyColor,
} revision;
};
System();
inline auto dmg() const { return revision == Revision::GameBoy; }
inline auto sgb() const { return revision == Revision::SuperGameBoy; }
inline auto cgb() const { return revision == Revision::GameBoyColor; }
auto loaded() const -> bool;
auto revision() const -> Revision;
auto clocksExecuted() const -> uint;
inline auto dmg() const { return _revision == Revision::GameBoy; }
inline auto sgb() const { return _revision == Revision::SuperGameBoy; }
inline auto cgb() const { return _revision == Revision::GameBoyColor; }
auto run() -> void;
auto runtosave() -> void;
auto runthreadtosave() -> void;
auto runToSave() -> void;
auto init() -> void;
auto load(Revision) -> void;
auto unload() -> void;
auto power() -> void;
//video.cpp
auto configureVideoPalette() -> void;
auto configureVideoEffects() -> void;
//serialization.cpp
auto serialize() -> serializer;
auto unserialize(serializer&) -> bool;
auto serialize(serializer&) -> void;
auto serialize_all(serializer&) -> void;
auto serialize_init() -> void;
auto serializeAll(serializer&) -> void;
auto serializeInit() -> void;
struct BootROM {
uint8 dmg[ 256];
@@ -43,8 +51,10 @@ struct System {
string manifest;
} information;
uint clocks_executed = 0;
uint serialize_size = 0;
bool _loaded = false;
Revision _revision = Revision::GameBoy;
uint _serializeSize = 0;
uint _clocksExecuted = 0;
};
#include <gb/interface/interface.hpp>

View File

@@ -0,0 +1,9 @@
auto System::configureVideoPalette() -> void {
if(sgb()) return;
Emulator::video.setPalette();
}
auto System::configureVideoEffects() -> void {
if(sgb()) return;
Emulator::video.setEffect(Emulator::Video::Effect::InterframeBlending, settings.blurEmulation);
}

View File

@@ -1,111 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
Video video;
Video::Video() {
output = new uint32[160 * 144];
paletteStandard = new uint32[1 << 15];
paletteEmulation = new uint32[1 << 15];
}
Video::~Video() {
delete[] output;
delete[] paletteStandard;
delete[] paletteEmulation;
}
auto Video::power() -> void {
memory::fill(output, 160 * 144 * sizeof(uint32));
if(system.dmg()) {
for(auto color : range(1 << 2)) {
uint L = image::normalize(3 - color, 2, 8);
uint R = monochrome[color][0] >> 8;
uint G = monochrome[color][1] >> 8;
uint B = monochrome[color][2] >> 8;
paletteStandard[color] = (255 << 24) | (L << 16) | (L << 8) | (L << 0);
paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
}
}
if(system.sgb()) {
for(auto color : range(1 << 2)) {
paletteStandard[color] = color;
paletteEmulation[color] = color;
}
}
if(system.cgb()) {
for(auto color : range(1 << 15)) {
uint r = (uint5)(color >> 0);
uint g = (uint5)(color >> 5);
uint b = (uint5)(color >> 10);
{ uint R = image::normalize(r, 5, 8);
uint G = image::normalize(g, 5, 8);
uint B = image::normalize(b, 5, 8);
paletteStandard[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
}
{ uint R = (r * 26 + g * 4 + b * 2);
uint G = ( g * 24 + b * 8);
uint B = (r * 6 + g * 4 + b * 22);
R = min(960, R) >> 2;
G = min(960, G) >> 2;
B = min(960, B) >> 2;
paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
}
}
}
}
auto Video::refresh() -> void {
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
for(uint y = 0; y < 144; y++) {
auto source = ppu.screen + y * 160;
auto target = output + y * 160;
if(settings.blurEmulation) {
for(uint x = 0; x < 160; x++) {
auto a = palette[*source++];
auto b = *target;
*target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1;
}
} else {
for(uint x = 0; x < 160; x++) {
auto color = palette[*source++];
*target++ = color;
}
}
}
interface->videoRefresh(output, 4 * 160, 160, 144);
}
#define DMG_PALETTE_GREEN
//#define DMG_PALETTE_YELLOW
//#define DMG_PALETTE_WHITE
const uint16 Video::monochrome[4][3] = {
#if defined(DMG_PALETTE_GREEN)
{0xaeae, 0xd9d9, 0x2727},
{0x5858, 0xa0a0, 0x2828},
{0x2020, 0x6262, 0x2929},
{0x1a1a, 0x4545, 0x2a2a},
#elif defined(DMG_PALETTE_YELLOW)
{0xffff, 0xf7f7, 0x7b7b},
{0xb5b5, 0xaeae, 0x4a4a},
{0x6b6b, 0x6969, 0x3131},
{0x2121, 0x2020, 0x1010},
#else //DMG_PALETTE_WHITE
{0xffff, 0xffff, 0xffff},
{0xaaaa, 0xaaaa, 0xaaaa},
{0x5555, 0x5555, 0x5555},
{0x0000, 0x0000, 0x0000},
#endif
};
}

View File

@@ -1,16 +0,0 @@
struct Video {
Video();
~Video();
auto power() -> void;
auto refresh() -> void;
uint32* output = nullptr;
uint32* paletteStandard = nullptr;
uint32* paletteEmulation = nullptr;
private:
static const uint16 monochrome[4][3];
};
extern Video video;

View File

@@ -1,15 +1,15 @@
gba_objects := gba-memory gba-interface gba-scheduler gba-system
gba_objects += gba-video gba-cartridge gba-player
gba_objects += gba-cpu gba-ppu gba-apu
objects += $(gba_objects)
processors += arm
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-video.o: $(gba)/video/video.cpp $(call rwildcard,$(gba)/video)
obj/gba-cartridge.o: $(gba)/cartridge/cartridge.cpp $(call rwildcard,$(gba)/cartridge)
obj/gba-player.o: $(gba)/player/player.cpp $(call rwildcard,$(gba)/player)
obj/gba-cpu.o: $(gba)/cpu/cpu.cpp $(call rwildcard,$(gba)/cpu)
obj/gba-ppu.o: $(gba)/ppu/ppu.cpp $(call rwildcard,$(gba)/ppu)
obj/gba-apu.o: $(gba)/apu/apu.cpp $(call rwildcard,$(gba)/apu)
objects += gba-memory gba-interface gba-scheduler 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)
obj/gba-cpu.o: gba/cpu/cpu.cpp $(call rwildcard,gba/cpu)
obj/gba-ppu.o: gba/ppu/ppu.cpp $(call rwildcard,gba/ppu)
obj/gba-apu.o: gba/apu/apu.cpp $(call rwildcard,gba/apu)

View File

@@ -2,7 +2,6 @@
namespace GameBoyAdvance {
#include "registers.cpp"
#include "mmio.cpp"
#include "square.cpp"
#include "square1.cpp"
@@ -15,13 +14,7 @@ namespace GameBoyAdvance {
APU apu;
auto APU::Enter() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
apu.main();
}
while(true) scheduler.synchronize(), apu.main();
}
auto APU::main() -> void {
@@ -70,17 +63,18 @@ auto APU::main() -> void {
if(regs.bias.amplitude == 3) lsample &= ~15, rsample &= ~15;
if(cpu.regs.mode == CPU::Registers::Mode::Stop) lsample = 0, rsample = 0;
interface->audioSample(sclamp<16>(lsample << 6), sclamp<16>(rsample << 6)); //should be <<5, use <<6 for added volume
stream->sample(sclamp<16>(lsample << 6) / 32768.0, sclamp<16>(rsample << 6) / 32768.0); //should be <<5; use <<6 for added volume
step(512);
}
auto APU::step(uint clocks) -> void {
clock += clocks;
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
}
auto APU::power() -> void {
create(APU::Enter, 16777216);
create(APU::Enter, 16'777'216);
stream = Emulator::audio.createStream(2, 16'777'216.0 / 512.0);
square1.power();
square2.power();
@@ -90,7 +84,8 @@ auto APU::power() -> void {
fifo[0].power();
fifo[1].power();
regs.bias = 0x0200;
regs.bias.amplitude = 0;
regs.bias.level = 0x200;
for(uint n = 0x060; n <= 0x0a7; n++) bus.mmio[n] = this;
}

View File

@@ -1,4 +1,6 @@
struct APU : Thread, MMIO {
shared_pointer<Emulator::Stream> stream;
#include "registers.hpp"
static auto Enter() -> void;

View File

@@ -2,215 +2,234 @@ auto APU::read(uint32 addr) -> uint8 {
switch(addr) {
//NR10
case 0x04000060: return square1.read(0);
case 0x04000061: return 0u;
case 0x0400'0060: return square1.read(0);
case 0x0400'0061: return 0;
//NR11 + NR12
case 0x04000062: return square1.read(1);
case 0x04000063: return square1.read(2);
//NR11, NR12
case 0x0400'0062: return square1.read(1);
case 0x0400'0063: return square1.read(2);
//NR13 + NR14
case 0x04000064: return square1.read(3);
case 0x04000065: return square1.read(4);
//NR13, NR14
case 0x0400'0064: return square1.read(3);
case 0x0400'0065: return square1.read(4);
//NR21 + NR22
case 0x04000068: return square2.read(1);
case 0x04000069: return square2.read(2);
//NR21, NR22
case 0x0400'0068: return square2.read(1);
case 0x0400'0069: return square2.read(2);
//NR23 + NR24
case 0x0400006c: return square2.read(3);
case 0x0400006d: return square2.read(4);
//NR23, NR24
case 0x0400'006c: return square2.read(3);
case 0x0400'006d: return square2.read(4);
//NR30
case 0x04000070: return wave.read(0);
case 0x04000071: return 0u;
case 0x0400'0070: return wave.read(0);
case 0x0400'0071: return 0;
//NR31 + NR32
case 0x04000072: return wave.read(1);
case 0x04000073: return wave.read(2);
//NR31, NR32
case 0x0400'0072: return wave.read(1);
case 0x0400'0073: return wave.read(2);
//NR33 + NR34
case 0x04000074: return wave.read(3);
case 0x04000075: return wave.read(4);
//NR33, NR34
case 0x0400'0074: return wave.read(3);
case 0x0400'0075: return wave.read(4);
//NR41 + NR42
case 0x04000078: return noise.read(1);
case 0x04000079: return noise.read(2);
//NR41, NR42
case 0x0400'0078: return noise.read(1);
case 0x0400'0079: return noise.read(2);
//NR43 + NR44
case 0x0400007c: return noise.read(3);
case 0x0400007d: return noise.read(4);
//NR43, NR44
case 0x0400'007c: return noise.read(3);
case 0x0400'007d: return noise.read(4);
//NR50 + NR51
case 0x04000080: return sequencer.read(0);
case 0x04000081: return sequencer.read(1);
//NR50, NR51
case 0x0400'0080: return sequencer.read(0);
case 0x0400'0081: return sequencer.read(1);
//SOUND_CNT_H
case 0x04000082:
return (fifo[1].volume << 3) | (fifo[0].volume << 2) | (sequencer.volume << 0);
case 0x04000083:
return (fifo[1].timer << 6) | (fifo[1].lenable << 5) | (fifo[1].renable << 4)
| (fifo[0].timer << 2) | (fifo[0].lenable << 1) | (fifo[0].renable << 0);
case 0x0400'0082: return (
sequencer.volume << 0
| fifo[0].volume << 2
| fifo[1].volume << 3
);
case 0x0400'0083: return (
fifo[0].renable << 0
| fifo[0].lenable << 1
| fifo[0].timer << 2
| fifo[1].renable << 4
| fifo[1].lenable << 5
| fifo[1].timer << 6
);
//NR52
case 0x04000084: return sequencer.read(2);
case 0x04000085: return 0u;
case 0x0400'0084: return sequencer.read(2);
case 0x0400'0085: return 0;
//SOUNDBIAS
case 0x04000088: return regs.bias >> 0;
case 0x04000089: return regs.bias >> 8;
case 0x0400'0088: return (
regs.bias.level.bits(0,7)
);
case 0x0400'0089: return (
regs.bias.level.bits(8,9) << 0
| regs.bias.amplitude << 6
);
//WAVE_RAM0_L
case 0x04000090: return wave.readram( 0);
case 0x04000091: return wave.readram( 1);
case 0x0400'0090: return wave.readram( 0);
case 0x0400'0091: return wave.readram( 1);
//WAVE_RAM0_H
case 0x04000092: return wave.readram( 2);
case 0x04000093: return wave.readram( 3);
case 0x0400'0092: return wave.readram( 2);
case 0x0400'0093: return wave.readram( 3);
//WAVE_RAM1_L
case 0x04000094: return wave.readram( 4);
case 0x04000095: return wave.readram( 5);
case 0x0400'0094: return wave.readram( 4);
case 0x0400'0095: return wave.readram( 5);
//WAVE_RAM1_H
case 0x04000096: return wave.readram( 6);
case 0x04000097: return wave.readram( 7);
case 0x0400'0096: return wave.readram( 6);
case 0x0400'0097: return wave.readram( 7);
//WAVE_RAM2_L
case 0x04000098: return wave.readram( 8);
case 0x04000099: return wave.readram( 9);
case 0x0400'0098: return wave.readram( 8);
case 0x0400'0099: return wave.readram( 9);
//WAVE_RAM2_H
case 0x0400009a: return wave.readram(10);
case 0x0400009b: return wave.readram(11);
case 0x0400'009a: return wave.readram(10);
case 0x0400'009b: return wave.readram(11);
//WAVE_RAM3_L
case 0x0400009c: return wave.readram(12);
case 0x0400009d: return wave.readram(13);
case 0x0400'009c: return wave.readram(12);
case 0x0400'009d: return wave.readram(13);
//WAVE_RAM3_H
case 0x0400009e: return wave.readram(14);
case 0x0400009f: return wave.readram(15);
case 0x0400'009e: return wave.readram(14);
case 0x0400'009f: return wave.readram(15);
}
return 0u;
return 0;
}
auto APU::write(uint32 addr, uint8 byte) -> void {
auto APU::write(uint32 addr, uint8 data) -> void {
switch(addr) {
//NR10
case 0x04000060: return square1.write(0, byte);
case 0x04000061: return;
case 0x0400'0060: return square1.write(0, data);
case 0x0400'0061: return;
//NR11 + NR12
case 0x04000062: return square1.write(1, byte);
case 0x04000063: return square1.write(2, byte);
//NR11, NR12
case 0x0400'0062: return square1.write(1, data);
case 0x0400'0063: return square1.write(2, data);
//NR13 + NR14
case 0x04000064: return square1.write(3, byte);
case 0x04000065: return square1.write(4, byte);
//NR13, NR14
case 0x0400'0064: return square1.write(3, data);
case 0x0400'0065: return square1.write(4, data);
//NR21 + NR22
case 0x04000068: return square2.write(1, byte);
case 0x04000069: return square2.write(2, byte);
//NR21, NR22
case 0x0400'0068: return square2.write(1, data);
case 0x0400'0069: return square2.write(2, data);
//NR23 + NR24
case 0x0400006c: return square2.write(3, byte);
case 0x0400006d: return square2.write(4, byte);
//NR23, NR24
case 0x0400'006c: return square2.write(3, data);
case 0x0400'006d: return square2.write(4, data);
//NR30
case 0x04000070: return wave.write(0, byte);
case 0x04000071: return;
case 0x0400'0070: return wave.write(0, data);
case 0x0400'0071: return;
//NR31 + NR32
case 0x04000072: return wave.write(1, byte);
case 0x04000073: return wave.write(2, byte);
//NR31, NR32
case 0x0400'0072: return wave.write(1, data);
case 0x0400'0073: return wave.write(2, data);
//NR33 + NR34
case 0x04000074: return wave.write(3, byte);
case 0x04000075: return wave.write(4, byte);
//NR33, NR34
case 0x0400'0074: return wave.write(3, data);
case 0x0400'0075: return wave.write(4, data);
//NR41 + NR42
case 0x04000078: return noise.write(1, byte);
case 0x04000079: return noise.write(2, byte);
//NR41, NR42
case 0x0400'0078: return noise.write(1, data);
case 0x0400'0079: return noise.write(2, data);
//NR43 + NR44
case 0x0400007c: return noise.write(3, byte);
case 0x0400007d: return noise.write(4, byte);
//NR43, NR44
case 0x0400'007c: return noise.write(3, data);
case 0x0400'007d: return noise.write(4, data);
//NR50 + NR51
case 0x04000080: return sequencer.write(0, byte);
case 0x04000081: return sequencer.write(1, byte);
//NR50, NR51
case 0x0400'0080: return sequencer.write(0, data);
case 0x0400'0081: return sequencer.write(1, data);
//SOUND_CNT_H
case 0x04000082:
sequencer.volume = byte >> 0;
fifo[0].volume = byte >> 2;
fifo[1].volume = byte >> 3;
case 0x0400'0082:
sequencer.volume = data.bits(0,1);
fifo[0].volume = data.bit (2);
fifo[1].volume = data.bit (3);
return;
case 0x04000083:
fifo[0].renable = byte >> 0;
fifo[0].lenable = byte >> 1;
fifo[0].timer = byte >> 2;
if(byte & 1 << 3) fifo[0].reset();
fifo[1].renable = byte >> 4;
fifo[1].lenable = byte >> 5;
fifo[1].timer = byte >> 6;
if(byte & 1 << 7) fifo[1].reset();
case 0x0400'0083:
fifo[0].renable = data.bit(0);
fifo[0].lenable = data.bit(1);
fifo[0].timer = data.bit(2);
if(data.bit(3)) fifo[0].reset();
fifo[1].renable = data.bit(4);
fifo[1].lenable = data.bit(5);
fifo[1].timer = data.bit(6);
if(data.bit(7)) fifo[1].reset();
return;
//NR52
case 0x04000084: return sequencer.write(2, byte);
case 0x04000085: return;
case 0x0400'0084: return sequencer.write(2, data);
case 0x0400'0085: return;
//SOUNDBIAS
case 0x04000088: regs.bias = (regs.bias & 0xff00) | (byte << 0); return;
case 0x04000089: regs.bias = (regs.bias & 0x00ff) | (byte << 8); return;
case 0x0400'0088:
regs.bias.level.bits(0,7) = data;
return;
case 0x0400'0089:
regs.bias.level.bits(8,9) = data.bits(0,1);
regs.bias.amplitude = data.bits(6,7);
return;
//WAVE_RAM0_L
case 0x04000090: return wave.writeram( 0, byte);
case 0x04000091: return wave.writeram( 1, byte);
case 0x0400'0090: return wave.writeram( 0, data);
case 0x0400'0091: return wave.writeram( 1, data);
//WAVE_RAM0_H
case 0x04000092: return wave.writeram( 2, byte);
case 0x04000093: return wave.writeram( 3, byte);
case 0x0400'0092: return wave.writeram( 2, data);
case 0x0400'0093: return wave.writeram( 3, data);
//WAVE_RAM1_L
case 0x04000094: return wave.writeram( 4, byte);
case 0x04000095: return wave.writeram( 5, byte);
case 0x0400'0094: return wave.writeram( 4, data);
case 0x0400'0095: return wave.writeram( 5, data);
//WAVE_RAM1_H
case 0x04000096: return wave.writeram( 6, byte);
case 0x04000097: return wave.writeram( 7, byte);
case 0x0400'0096: return wave.writeram( 6, data);
case 0x0400'0097: return wave.writeram( 7, data);
//WAVE_RAM2_L
case 0x04000098: return wave.writeram( 8, byte);
case 0x04000099: return wave.writeram( 9, byte);
case 0x0400'0098: return wave.writeram( 8, data);
case 0x0400'0099: return wave.writeram( 9, data);
//WAVE_RAM2_H
case 0x0400009a: return wave.writeram(10, byte);
case 0x0400009b: return wave.writeram(11, byte);
case 0x0400'009a: return wave.writeram(10, data);
case 0x0400'009b: return wave.writeram(11, data);
//WAVE_RAM3_L
case 0x0400009c: return wave.writeram(12, byte);
case 0x0400009d: return wave.writeram(13, byte);
case 0x0400'009c: return wave.writeram(12, data);
case 0x0400'009d: return wave.writeram(13, data);
//WAVE_RAM3_H
case 0x0400009e: return wave.writeram(14, byte);
case 0x0400009f: return wave.writeram(15, byte);
case 0x0400'009e: return wave.writeram(14, data);
case 0x0400'009f: return wave.writeram(15, data);
//FIFO_A_L
//FIFO_A_H
case 0x040000a0: case 0x040000a1:
case 0x040000a2: case 0x040000a3:
return fifo[0].write(byte);
case 0x0400'00a0: case 0x0400'00a1:
case 0x0400'00a2: case 0x0400'00a3:
return fifo[0].write(data);
//FIFO_B_L
//FIFO_B_H
case 0x040000a4: case 0x040000a5:
case 0x040000a6: case 0x040000a7:
return fifo[1].write(byte);
case 0x0400'00a4: case 0x0400'00a5:
case 0x0400'00a6: case 0x0400'00a7:
return fifo[1].write(data);
}
}

View File

@@ -1,12 +0,0 @@
APU::Registers::SoundBias::operator uint16() const {
return (
(level << 0)
| (amplitude << 14)
);
}
auto APU::Registers::SoundBias::operator=(uint16 source) -> uint16 {
level = (source >> 0) & 1023;
amplitude = (source >> 14) & 3;
return operator uint16();
}

View File

@@ -2,10 +2,6 @@ struct Registers {
struct SoundBias {
uint10 level;
uint2 amplitude;
operator uint16() const;
auto operator=(uint16 source) -> uint16;
auto operator=(const SoundBias&) -> SoundBias& = delete;
} bias;
uint clock;

View File

@@ -23,10 +23,6 @@ Cartridge::~Cartridge() {
delete[] flash.data;
}
auto Cartridge::loaded() const -> bool {
return isLoaded;
}
auto Cartridge::sha256() const -> string {
return information.sha256;
}
@@ -96,16 +92,10 @@ auto Cartridge::load() -> void {
}
information.sha256 = Hash::SHA256(mrom.data, mrom.size).digest();
system.load();
isLoaded = true;
}
auto Cartridge::unload() -> void {
if(isLoaded) {
isLoaded = false;
memory.reset();
}
}
auto Cartridge::power() -> void {

View File

@@ -1,7 +1,6 @@
struct Cartridge {
#include "memory.hpp"
auto loaded() const -> bool;
auto sha256() const -> string;
auto manifest() const -> string;
auto title() const -> string;
@@ -31,7 +30,6 @@ struct Cartridge {
auto serialize(serializer&) -> void;
private:
bool isLoaded = false;
bool hasSRAM = false;
bool hasEEPROM = false;
bool hasFLASH = false;

View File

@@ -1,10 +1,10 @@
auto CPU::bus_idle() -> void {
auto CPU::busIdle() -> void {
prefetch_step(1);
}
auto CPU::bus_read(unsigned mode, uint32 addr) -> uint32 {
unsigned wait = bus_wait(mode, addr);
unsigned word = pipeline.fetch.instruction;
auto CPU::busRead(uint mode, uint32 addr) -> uint32 {
uint wait = busWait(mode, addr);
uint word = pipeline.fetch.instruction;
if(addr >= 0x1000'0000) {
prefetch_step(wait);
@@ -35,8 +35,8 @@ auto CPU::bus_read(unsigned mode, uint32 addr) -> uint32 {
return word;
}
auto CPU::bus_write(unsigned mode, uint32 addr, uint32 word) -> void {
unsigned wait = bus_wait(mode, addr);
auto CPU::busWrite(uint mode, uint32 addr, uint32 word) -> void {
uint wait = busWait(mode, addr);
if(addr >= 0x1000'0000) {
prefetch_step(wait);
@@ -57,7 +57,7 @@ auto CPU::bus_write(unsigned mode, uint32 addr, uint32 word) -> void {
}
}
auto CPU::bus_wait(unsigned mode, uint32 addr) -> unsigned {
auto CPU::busWait(uint mode, uint32 addr) -> uint {
if(addr >= 0x1000'0000) return 1; //unmapped
if(addr < 0x0200'0000) return 1;
if(addr < 0x0300'0000) return (16 - regs.memory.control.ewramwait) * (mode & Word ? 2 : 1);
@@ -65,9 +65,9 @@ auto CPU::bus_wait(unsigned mode, uint32 addr) -> unsigned {
if(addr < 0x0700'0000) return mode & Word ? 2 : 1;
if(addr < 0x0800'0000) return 1;
static unsigned timings[] = {5, 4, 3, 9};
unsigned n = timings[regs.wait.control.nwait[addr >> 25 & 3]];
unsigned s = regs.wait.control.swait[addr >> 25 & 3];
static uint timings[] = {5, 4, 3, 9};
uint n = timings[regs.wait.control.nwait[addr >> 25 & 3]];
uint s = regs.wait.control.swait[addr >> 25 & 3];
switch(addr & 0x0e00'0000) {
case 0x0800'0000: s = s ? 2 : 3; break;
@@ -79,7 +79,7 @@ auto CPU::bus_wait(unsigned mode, uint32 addr) -> unsigned {
bool sequential = (mode & Sequential);
if((addr & 0x1fffe) == 0) sequential = false; //N cycle on 16-bit ROM crossing 128KB page boundary (RAM S==N)
unsigned clocks = sequential ? s : n;
uint clocks = sequential ? s : n;
if(mode & Word) clocks += s; //16-bit bus requires two transfers for words
return clocks;
}

View File

@@ -2,7 +2,6 @@
namespace GameBoyAdvance {
#include "registers.cpp"
#include "prefetch.cpp"
#include "bus.cpp"
#include "mmio.cpp"
@@ -16,21 +15,21 @@ CPU::CPU() {
iwram = new uint8[ 32 * 1024];
ewram = new uint8[256 * 1024];
regs.dma[0].source.bits(27); regs.dma[0].run.source.bits(27);
regs.dma[0].target.bits(27); regs.dma[0].run.target.bits(27);
regs.dma[0].length.bits(14); regs.dma[0].run.length.bits(14);
regs.dma[0].source.resize(27); regs.dma[0].run.source.resize(27);
regs.dma[0].target.resize(27); regs.dma[0].run.target.resize(27);
regs.dma[0].length.resize(14); regs.dma[0].run.length.resize(14);
regs.dma[1].source.bits(28); regs.dma[1].run.source.bits(28);
regs.dma[1].target.bits(27); regs.dma[1].run.target.bits(27);
regs.dma[1].length.bits(14); regs.dma[1].run.length.bits(14);
regs.dma[1].source.resize(28); regs.dma[1].run.source.resize(28);
regs.dma[1].target.resize(27); regs.dma[1].run.target.resize(27);
regs.dma[1].length.resize(14); regs.dma[1].run.length.resize(14);
regs.dma[2].source.bits(28); regs.dma[2].run.source.bits(28);
regs.dma[2].target.bits(27); regs.dma[2].run.target.bits(27);
regs.dma[2].length.bits(14); regs.dma[2].run.length.bits(14);
regs.dma[2].source.resize(28); regs.dma[2].run.source.resize(28);
regs.dma[2].target.resize(27); regs.dma[2].run.target.resize(27);
regs.dma[2].length.resize(14); regs.dma[2].run.length.resize(14);
regs.dma[3].source.bits(28); regs.dma[3].run.source.bits(28);
regs.dma[3].target.bits(28); regs.dma[3].run.target.bits(28);
regs.dma[3].length.bits(16); regs.dma[3].run.length.bits(16);
regs.dma[3].source.resize(28); regs.dma[3].run.source.resize(28);
regs.dma[3].target.resize(28); regs.dma[3].run.target.resize(28);
regs.dma[3].length.resize(16); regs.dma[3].run.length.resize(16);
}
CPU::~CPU() {
@@ -39,14 +38,7 @@ CPU::~CPU() {
}
auto CPU::Enter() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::CPU) {
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
cpu.main();
}
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::main() -> void {
@@ -63,7 +55,7 @@ auto CPU::main() -> void {
processor.irqline = regs.ime && (regs.irq.enable & regs.irq.flag);
if(regs.mode == Registers::Mode::Stop) {
if((regs.irq.enable.keypad & regs.irq.flag.keypad) == 0) {
if(!(regs.irq.enable & regs.irq.flag & Interrupt::Keypad)) {
sync_step(16); //STOP does not advance timers
} else {
regs.mode = Registers::Mode::Normal;
@@ -74,7 +66,7 @@ auto CPU::main() -> void {
dma_run();
if(regs.mode == Registers::Mode::Halt) {
if((regs.irq.enable & regs.irq.flag) == 0) {
if(!(regs.irq.enable & regs.irq.flag)) {
step(16);
} else {
regs.mode = Registers::Mode::Normal;
@@ -99,16 +91,19 @@ auto CPU::sync_step(uint clocks) -> void {
}
auto CPU::keypad_run() -> void {
if(regs.keypad.control.enable == false) return;
//lookup table to convert button indexes to Emulator::Interface indexes
static const uint lookup[] = {5, 4, 8, 9, 3, 2, 0, 1, 7, 6};
if(!regs.keypad.control.enable) return;
bool test = regs.keypad.control.condition; //0 = OR, 1 = AND
for(auto n : range(10)) {
if(regs.keypad.control.flag[n] == false) continue;
bool input = interface->inputPoll(0, 0, n);
if(!regs.keypad.control.flag[n]) continue;
bool input = interface->inputPoll(0, 0, lookup[n]);
if(regs.keypad.control.condition == 0) test |= input;
if(regs.keypad.control.condition == 1) test &= input;
}
if(test) regs.irq.flag.keypad = true;
if(test) regs.irq.flag |= Interrupt::Keypad;
}
auto CPU::power() -> void {
@@ -123,7 +118,14 @@ auto CPU::power() -> void {
dma.target = 0;
dma.length = 0;
dma.data = 0;
dma.control = 0;
dma.control.targetmode = 0;
dma.control.sourcemode = 0;
dma.control.repeat = 0;
dma.control.size = 0;
dma.control.drq = 0;
dma.control.timingmode = 0;
dma.control.irq = 0;
dma.control.enable = 0;
dma.pending = 0;
dma.run.target = 0;
dma.run.source = 0;
@@ -133,17 +135,30 @@ auto CPU::power() -> void {
timer.period = 0;
timer.reload = 0;
timer.pending = false;
timer.control = 0;
timer.control.frequency = 0;
timer.control.cascade = 0;
timer.control.irq = 0;
timer.control.enable = 0;
}
regs.keypad.control = 0;
for(auto& flag : regs.keypad.control.flag) flag = 0;
regs.keypad.control.enable = 0;
regs.keypad.control.condition = 0;
regs.ime = 0;
regs.irq.enable = 0;
regs.irq.flag = 0;
regs.wait.control = 0;
for(auto& nwait : regs.wait.control.nwait) nwait = 0;
for(auto& swait : regs.wait.control.swait) swait = 0;
regs.wait.control.phi = 0;
regs.wait.control.prefetch = 0;
regs.wait.control.gametype = 0;
regs.postboot = 0;
regs.mode = Registers::Mode::Normal;
regs.clock = 0;
regs.memory.control = 0x0d00'0020;
regs.memory.control.disable = 0;
regs.memory.control.unknown1 = 0;
regs.memory.control.ewram = 1;
regs.memory.control.ewramwait = 13;
regs.memory.control.unknown2 = 0;
pending.dma.vblank = 0;
pending.dma.hblank = 0;

View File

@@ -2,6 +2,25 @@ struct CPU : Processor::ARM, Thread, MMIO {
using ARM::read;
using ARM::write;
struct Interrupt {
enum : uint {
VBlank = 0x0001,
HBlank = 0x0002,
VCoincidence = 0x0004,
Timer0 = 0x0008,
Timer1 = 0x0010,
Timer2 = 0x0020,
Timer3 = 0x0040,
Serial = 0x0080,
DMA0 = 0x0100,
DMA1 = 0x0200,
DMA2 = 0x0400,
DMA3 = 0x0800,
Keypad = 0x1000,
Cartridge = 0x2000,
};
};
#include "registers.hpp"
#include "prefetch.hpp"
#include "state.hpp"
@@ -11,7 +30,6 @@ struct CPU : Processor::ARM, Thread, MMIO {
~CPU();
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void override;
@@ -21,10 +39,10 @@ struct CPU : Processor::ARM, Thread, MMIO {
auto power() -> void;
//bus.cpp
auto bus_idle() -> void override;
auto bus_read(uint mode, uint32 addr) -> uint32 override;
auto bus_write(uint mode, uint32 addr, uint32 word) -> void override;
auto bus_wait(uint mode, uint32 addr) -> uint;
auto busIdle() -> void override;
auto busRead(uint mode, uint32 addr) -> uint32 override;
auto busWrite(uint mode, uint32 addr, uint32 word) -> void override;
auto busWait(uint mode, uint32 addr) -> uint;
//mmio.cpp
auto read(uint32 addr) -> uint8;

View File

@@ -7,8 +7,8 @@ auto CPU::dma_run() -> void {
auto& dma = regs.dma[n];
if(dma.pending) {
dma_exec(dma);
if(dma.control.irq) regs.irq.flag.dma[n] = 1;
if(dma.control.drq && n == 3) regs.irq.flag.cartridge = 1;
if(dma.control.irq) regs.irq.flag |= Interrupt::DMA0 << n;
if(dma.control.drq && n == 3) regs.irq.flag |= Interrupt::Cartridge;
transferred = true;
break;
}
@@ -39,7 +39,7 @@ auto CPU::dma_exec(Registers::DMA& dma) -> void {
uint32 addr = dma.run.source;
if(mode & Word) addr &= ~3;
if(mode & Half) addr &= ~1;
dma.data = bus_read(mode, addr);
dma.data = busRead(mode, addr);
}
if(dma.run.target < 0x0200'0000) {
@@ -48,7 +48,7 @@ auto CPU::dma_exec(Registers::DMA& dma) -> void {
uint32 addr = dma.run.target;
if(mode & Word) addr &= ~3;
if(mode & Half) addr &= ~1;
bus_write(mode, addr, dma.data);
busWrite(mode, addr, dma.data);
}
switch(dma.control.sourcemode) {

View File

@@ -1,321 +1,416 @@
auto CPU::read(uint32 addr) -> uint8 {
uint8 result = 0;
auto dma = [&]() -> Registers::DMA& { return regs.dma[addr / 12 & 3]; };
auto timer = [&]() -> Registers::Timer& { return regs.timer[addr.bits(2,3)]; };
switch(addr) {
//DMA0CNT_H
//DMA1CNT_H
//DMA2CNT_H
//DMA3CNT_H
case 0x040000ba: case 0x040000bb:
case 0x040000c6: case 0x040000c7:
case 0x040000d2: case 0x040000d3:
case 0x040000de: case 0x040000df: {
auto& dma = regs.dma[(addr - 0x040000ba) / 12];
unsigned shift = (addr & 1) * 8;
return dma.control >> shift;
}
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de: return (
dma().control.targetmode << 5
| dma().control.sourcemode.bit(0) << 7
);
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: return (
dma().control.sourcemode.bit(1) << 0
| dma().control.repeat << 1
| dma().control.size << 2
| dma().control.drq << 3
| dma().control.timingmode << 4
| dma().control.irq << 6
| dma().control.enable << 7
);
//TM0CNT_L
//TM1CNT_L
//TM2CNT_L
//TM3CNT_L
case 0x04000100: case 0x04000101:
case 0x04000104: case 0x04000105:
case 0x04000108: case 0x04000109:
case 0x0400010c: case 0x0400010d: {
auto& timer = regs.timer[(addr >> 2) & 3];
unsigned shift = (addr & 1) * 8;
return timer.period >> shift;
}
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: return timer().period.byte(0);
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: return timer().period.byte(1);
//TIM0CNT_H
case 0x04000102: case 0x04000103:
case 0x04000106: case 0x04000107:
case 0x0400010a: case 0x0400010b:
case 0x0400010e: case 0x0400010f: {
auto& timer = regs.timer[(addr >> 2) & 3];
unsigned shift = (addr & 1) * 8;
return timer.control >> shift;
}
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: return (
timer().control.frequency << 0
| timer().control.cascade << 2
| timer().control.irq << 6
| timer().control.enable << 7
);
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f: return 0;
//SIOMULTI0 (SIODATA32_L)
//SIOMULTI1 (SIODATA32_H)
//SIOMULTI2
//SIOMULTI3
case 0x04000120: case 0x04000121:
case 0x04000122: case 0x04000123:
case 0x04000124: case 0x04000125:
case 0x04000126: case 0x04000127: {
if(auto data = player.read()) return data() >> ((addr & 3) << 3);
unsigned shift = (addr & 1) * 8;
auto& data = regs.serial.data[(addr >> 1) & 3];
return data >> shift;
//SIOMULTI0 (SIODATA32_L), SIOMULTI1 (SIODATA32_H), SIOMULTI2, SIOMULTI3
case 0x0400'0120: case 0x0400'0122: case 0x0400'0124: case 0x0400'0126: {
if(auto data = player.read()) return data().byte(addr.bits(0,1));
return regs.serial.data[addr.bits(1,2)].byte(0);
}
case 0x0400'0121: case 0x0400'0123: case 0x0400'0125: case 0x0400'0127: {
if(auto data = player.read()) return data().byte(addr.bits(0,1));
return regs.serial.data[addr.bits(1,2)].byte(1);
}
//SIOCNT
case 0x04000128: return regs.serial.control >> 0;
case 0x04000129: return regs.serial.control >> 8;
case 0x0400'0128: return (
regs.serial.control.shiftclockselect << 0
| regs.serial.control.shiftclockfrequency << 1
| regs.serial.control.transferenablereceive << 2
| regs.serial.control.transferenablesend << 3
| regs.serial.control.startbit << 7
);
case 0x0400'0129: return (
regs.serial.control.transferlength << 4
| regs.serial.control.irqenable << 6
);
//SIOMLT_SEND (SIODATA8)
case 0x0400012a: return regs.serial.data8;
case 0x0400012b: return 0u;
case 0x0400'012a: return regs.serial.data8;
case 0x0400'012b: return 0;
//KEYINPUT
case 0x04000130:
case 0x04000130: {
static const uint lookup[] = {5, 4, 8, 9, 3, 2, 0, 1};
if(auto result = player.keyinput()) return result() >> 0;
for(unsigned n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, n) << n;
if((result & 0xc0) == 0xc0) result &= ~0xc0; //up+down cannot be pressed simultaneously
if((result & 0x30) == 0x30) result &= ~0x30; //left+right cannot be pressed simultaneously
uint8 result = 0;
for(uint n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, lookup[n]) << n;
if((result & 0xc0) == 0xc0) result &= (uint8)~0xc0; //up+down cannot be pressed simultaneously
if((result & 0x30) == 0x30) result &= (uint8)~0x30; //left+right cannot be pressed simultaneously
return result ^ 0xff;
case 0x04000131:
}
case 0x04000131: {
if(auto result = player.keyinput()) return result() >> 8;
result |= interface->inputPoll(0, 0, 8) << 0;
result |= interface->inputPoll(0, 0, 9) << 1;
uint8 result = 0;
result |= interface->inputPoll(0, 0, 7) << 0;
result |= interface->inputPoll(0, 0, 6) << 1;
return result ^ 0x03;
}
//KEYCNT
case 0x04000132: return regs.keypad.control >> 0;
case 0x04000133: return regs.keypad.control >> 8;
case 0x0400'0132: return (
regs.keypad.control.flag[0] << 0
| regs.keypad.control.flag[1] << 1
| regs.keypad.control.flag[2] << 2
| regs.keypad.control.flag[3] << 3
| regs.keypad.control.flag[4] << 4
| regs.keypad.control.flag[5] << 5
| regs.keypad.control.flag[6] << 6
| regs.keypad.control.flag[7] << 7
);
case 0x0400'0133: return (
regs.keypad.control.flag[8] << 0
| regs.keypad.control.flag[9] << 1
| regs.keypad.control.enable << 6
| regs.keypad.control.condition << 7
);
//RCNT
case 0x04000134: return regs.joybus.settings >> 0;
case 0x04000135: return regs.joybus.settings >> 8;
case 0x0400'0134: return (
regs.joybus.settings.sc << 0
| regs.joybus.settings.sd << 1
| regs.joybus.settings.si << 2
| regs.joybus.settings.so << 3
| regs.joybus.settings.scmode << 4
| regs.joybus.settings.sdmode << 5
| regs.joybus.settings.simode << 6
| regs.joybus.settings.somode << 7
);
case 0x0400'0135: return (
regs.joybus.settings.irqenable << 0
| regs.joybus.settings.mode << 6
);
//JOYCNT
case 0x04000140: return regs.joybus.control >> 0;
case 0x04000141: return regs.joybus.control >> 8;
case 0x0400'0140: return (
regs.joybus.control.resetsignal << 0
| regs.joybus.control.receivecomplete << 1
| regs.joybus.control.sendcomplete << 2
| regs.joybus.control.irqenable << 6
);
case 0x0400'0141: return 0;
case 0x0400'0142: return 0;
case 0x0400'0143: return 0;
//JOY_RECV_L
//JOY_RECV_H
case 0x04000150: return regs.joybus.receive >> 0;
case 0x04000151: return regs.joybus.receive >> 8;
case 0x04000152: return regs.joybus.receive >> 16;
case 0x04000153: return regs.joybus.receive >> 24;
//JOY_RECV_L, JOY_RECV_H
case 0x0400'0150: return regs.joybus.receive.byte(0);
case 0x0400'0151: return regs.joybus.receive.byte(1);
case 0x0400'0152: return regs.joybus.receive.byte(2);
case 0x0400'0153: return regs.joybus.receive.byte(3);
//JOY_TRANS_L
//JOY_TRANS_H
case 0x04000154: return regs.joybus.transmit >> 0;
case 0x04000155: return regs.joybus.transmit >> 8;
case 0x04000156: return regs.joybus.transmit >> 16;
case 0x04000157: return regs.joybus.transmit >> 24;
//JOY_TRANS_L, JOY_TRANS_H
case 0x0400'0154: return regs.joybus.transmit.byte(0);
case 0x0400'0155: return regs.joybus.transmit.byte(1);
case 0x0400'0156: return regs.joybus.transmit.byte(2);
case 0x0400'0157: return regs.joybus.transmit.byte(3);
//JOYSTAT
case 0x04000158: return regs.joybus.status >> 0;
case 0x04000159: return regs.joybus.status >> 8;
case 0x0400'0158: return (
regs.joybus.status.receiveflag << 1
| regs.joybus.status.sendflag << 3
| regs.joybus.status.generalflag << 4
);
case 0x0400'0159: return 0;
case 0x0400'015a: return 0;
case 0x0400'015b: return 0;
//IE
case 0x04000200: return regs.irq.enable >> 0;
case 0x04000201: return regs.irq.enable >> 8;
case 0x0400'0200: return regs.irq.enable.byte(0);
case 0x0400'0201: return regs.irq.enable.byte(1);
//IF
case 0x04000202: return regs.irq.flag >> 0;
case 0x04000203: return regs.irq.flag >> 8;
case 0x0400'0202: return regs.irq.flag.byte(0);
case 0x0400'0203: return regs.irq.flag.byte(1);
//WAITCNT
case 0x04000204: return regs.wait.control >> 0;
case 0x04000205: return regs.wait.control >> 8;
case 0x0400'0204: return (
regs.wait.control.nwait[3] << 0
| regs.wait.control.nwait[0] << 2
| regs.wait.control.swait[0] << 4
| regs.wait.control.nwait[1] << 5
| regs.wait.control.swait[1] << 7
);
case 0x0400'0205: return (
regs.wait.control.nwait[2] << 0
| regs.wait.control.swait[2] << 2
| regs.wait.control.phi << 3
| regs.wait.control.prefetch << 6
| regs.wait.control.gametype << 7
);
//IME
case 0x04000208: return regs.ime;
case 0x04000209: return 0u;
case 0x0400'0208: return regs.ime;
case 0x0400'0209: return 0;
//POSTFLG + HALTCNT
case 0x04000300: return regs.postboot;
case 0x04000301: return 0u;
case 0x0400'0300: return regs.postboot;
case 0x0400'0301: return 0;
//MEMCNT_L
case 0x04000800: return regs.memory.control >> 0;
case 0x04000801: return regs.memory.control >> 8;
case 0x0400'0800: return (
regs.memory.control.disable << 0
| regs.memory.control.unknown1 << 1
| regs.memory.control.ewram << 5
);
case 0x0400'0801: return 0;
//MEMCNT_H
case 0x04000802: return regs.memory.control >> 16;
case 0x04000803: return regs.memory.control >> 24;
case 0x0400'0802: return 0;
case 0x0400'0803: return (
regs.memory.control.ewramwait << 0
| regs.memory.control.unknown2 << 4
);
}
return 0u;
return 0;
}
auto CPU::write(uint32 addr, uint8 byte) -> void {
auto CPU::write(uint32 addr, uint8 data) -> void {
auto dma = [&]() -> Registers::DMA& { return regs.dma[addr / 12 & 3]; };
auto timer = [&]() -> Registers::Timer& { return regs.timer[addr.bits(2,3)]; };
switch(addr) {
//DMA0SAD
//DMA1SAD
//DMA2SAD
//DMA3SAD
case 0x040000b0: case 0x040000b1: case 0x040000b2: case 0x040000b3:
case 0x040000bc: case 0x040000bd: case 0x040000be: case 0x040000bf:
case 0x040000c8: case 0x040000c9: case 0x040000ca: case 0x040000cb:
case 0x040000d4: case 0x040000d5: case 0x040000d6: case 0x040000d7: {
auto& dma = regs.dma[(addr - 0x040000b0) / 12];
unsigned shift = (addr & 3) * 8;
dma.source = (dma.source & ~(255 << shift)) | (byte << shift);
return;
}
//DMA0SAD, DMA1SAD, DMA2SAD, DMA3SAD
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: dma().source.byte(0) = data; return;
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: dma().source.byte(1) = data; return;
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: dma().source.byte(2) = data; return;
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: dma().source.byte(3) = data; return;
//DMA0DAD
//DMA1DAD
//DMA2DAD
//DMA3DAD
case 0x040000b4: case 0x040000b5: case 0x040000b6: case 0x040000b7:
case 0x040000c0: case 0x040000c1: case 0x040000c2: case 0x040000c3:
case 0x040000cc: case 0x040000cd: case 0x040000ce: case 0x040000cf:
case 0x040000d8: case 0x040000d9: case 0x040000da: case 0x040000db: {
auto& dma = regs.dma[(addr - 0x040000b4) / 12];
unsigned shift = (addr & 3) * 8;
dma.target = (dma.target & ~(255 << shift)) | (byte << shift);
return;
}
//DMA0DAD, DMA1DAD, DMA2DAD, DMA3DAD
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: dma().target.byte(0) = data; return;
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: dma().target.byte(1) = data; return;
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: dma().target.byte(2) = data; return;
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: dma().target.byte(3) = data; return;
//DMA0CNT_L
//DMA1CNT_L
//DMA2CNT_L
//DMA3CNT_L
case 0x040000b8: case 0x040000b9:
case 0x040000c4: case 0x040000c5:
case 0x040000d0: case 0x040000d1:
case 0x040000dc: case 0x040000dd: {
auto& dma = regs.dma[(addr - 0x040000b8) / 12];
unsigned shift = (addr & 1) * 8;
dma.length = (dma.length & ~(255 << shift)) | (byte << shift);
return;
}
//DMA0CNT_L, DMA1CNT_L, DMA2CNT_L, DMA3CNT_L
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: dma().length.byte(0) = data; return;
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: dma().length.byte(1) = data; return;
//DMA0CNT_H
//DMA1CNT_H
//DMA2CNT_H
//DMA3CNT_H
case 0x040000ba: case 0x040000bb:
case 0x040000c6: case 0x040000c7:
case 0x040000d2: case 0x040000d3:
case 0x040000de: case 0x040000df: {
if(addr == 0x040000bb || addr == 0x040000c7 || addr == 0x040000d3) byte &= 0xf7; //gamepak DRQ valid for DMA3 only
auto& dma = regs.dma[(addr - 0x040000ba) / 12];
unsigned shift = (addr & 1) * 8;
bool enable = dma.control.enable;
dma.control = (dma.control & ~(255 << shift)) | (byte << shift);
if(enable == 0 && dma.control.enable) {
if(dma.control.timingmode == 0) dma.pending = true; //immediate transfer mode
dma.run.target = dma.target;
dma.run.source = dma.source;
dma.run.length = dma.length;
} else if(dma.control.enable == 0) {
dma.pending = false;
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de:
dma().control.targetmode = data.bits(5,6);
dma().control.sourcemode.bit(0) = data.bit (7);
return;
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: {
bool enable = dma().control.enable;
if(addr != 0x0400'00df) data.bit(3) = 0; //gamepad DRQ valid for DMA3 only
dma().control.sourcemode.bit(1) = data.bit (0);
dma().control.repeat = data.bit (1);
dma().control.size = data.bit (2);
dma().control.drq = data.bit (3);
dma().control.timingmode = data.bits(4,5);
dma().control.irq = data.bit (6);
dma().control.enable = data.bit (7);
if(!enable && dma().control.enable) { //0->1 transition
if(dma().control.timingmode == 0) dma().pending = true; //immediate transfer mode
dma().run.target = dma().target;
dma().run.source = dma().source;
dma().run.length = dma().length;
} else if(!dma().control.enable) {
dma().pending = false;
}
return;
}
//TM0CNT_L
//TM1CNT_L
//TM2CNT_L
//TM3CNT_L
case 0x04000100: case 0x04000101:
case 0x04000104: case 0x04000105:
case 0x04000108: case 0x04000109:
case 0x0400010c: case 0x0400010d: {
auto& timer = regs.timer[(addr >> 2) & 3];
unsigned shift = (addr & 1) * 8;
timer.reload = (timer.reload & ~(255 << shift)) | (byte << shift);
return;
}
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: timer().reload.byte(0) = data; return;
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: timer().reload.byte(1) = data; return;
//TM0CNT_H
//TM1CNT_H
//TM2CNT_H
//TM3CNT_H
case 0x04000102:
case 0x04000106:
case 0x0400010a:
case 0x0400010e: {
auto& timer = regs.timer[(addr >> 2) & 3];
bool enable = timer.control.enable;
timer.control = byte;
if(enable == 0 && timer.control.enable == 1) {
timer.pending = true;
}
return;
}
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: {
bool enable = timer().control.enable;
//SIOMULTI0 (SIODATA32_L)
//SIOMULTI1 (SIODATA32_H)
//SIOMULTI2
//SIOMULTI3
case 0x04000120: case 0x04000121:
case 0x04000122: case 0x04000123:
case 0x04000124: case 0x04000125:
case 0x04000126: case 0x04000127: {
player.write(byte, addr & 3);
auto& data = regs.serial.data[(addr >> 1) & 3];
unsigned shift = (addr & 1) * 8;
data = (data & ~(255 << shift)) | (byte << shift);
timer().control.frequency = data.bits(0,1);
timer().control.cascade = data.bit (2);
timer().control.irq = data.bit (6);
timer().control.enable = data.bit (7);
if(!enable && timer().control.enable) { //0->1 transition
timer().pending = true;
}
return;
}
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f:
return;
//SIOMULTI0 (SIODATA32_L), SIOMULTI1 (SIODATA32_H), SIOMULTI2, SIOMULTI3
case 0x0400'0120: case 0x0400'0122: case 0x0400'0124: case 0x0400'0126:
player.write(addr.bits(0,1), data);
regs.serial.data[addr.bits(1,2)].byte(0) = data;
return;
case 0x0400'0121: case 0x0400'0123: case 0x0400'0125: case 0x0400'0127:
player.write(addr.bits(0,1), data);
regs.serial.data[addr.bits(1,2)].byte(1) = data;
return;
//SIOCNT
case 0x04000128: regs.serial.control = (regs.serial.control & 0xff00) | (byte << 0); return;
case 0x04000129: regs.serial.control = (regs.serial.control & 0x00ff) | (byte << 8); return;
case 0x0400'0128:
regs.serial.control.shiftclockselect = data.bit(0);
regs.serial.control.shiftclockfrequency = data.bit(1);
regs.serial.control.transferenablereceive = data.bit(2);
regs.serial.control.transferenablesend = data.bit(3);
regs.serial.control.startbit = data.bit(7);
return;
case 0x0400'0129:
regs.serial.control.transferlength = data.bit(4);
regs.serial.control.irqenable = data.bit(6);
return;
//SIOMLT_SEND (SIODATA8)
case 0x0400012a: regs.serial.data8 = byte; return;
case 0x0400012b: return;
case 0x0400'012a: regs.serial.data8 = data; return;
case 0x0400'012b: return;
//KEYCNT
case 0x04000132: regs.keypad.control = (regs.keypad.control & 0xff00) | (byte << 0); return;
case 0x04000133: regs.keypad.control = (regs.keypad.control & 0x00ff) | (byte << 8); return;
case 0x0400'0132:
regs.keypad.control.flag[0] = data.bit(0);
regs.keypad.control.flag[1] = data.bit(1);
regs.keypad.control.flag[2] = data.bit(2);
regs.keypad.control.flag[3] = data.bit(3);
regs.keypad.control.flag[4] = data.bit(4);
regs.keypad.control.flag[5] = data.bit(5);
regs.keypad.control.flag[6] = data.bit(6);
regs.keypad.control.flag[7] = data.bit(7);
return;
case 0x0400'0133:
regs.keypad.control.flag[8] = data.bit(0);
regs.keypad.control.flag[9] = data.bit(1);
regs.keypad.control.enable = data.bit(6);
regs.keypad.control.condition = data.bit(7);
return;
//RCNT
case 0x04000134: regs.joybus.settings = (regs.joybus.settings & 0xff00) | (byte << 0); return;
case 0x04000135: regs.joybus.settings = (regs.joybus.settings & 0x00ff) | (byte << 8); return;
case 0x0400'0134:
regs.joybus.settings.sc = data.bit(0);
regs.joybus.settings.sd = data.bit(1);
regs.joybus.settings.si = data.bit(2);
regs.joybus.settings.so = data.bit(3);
regs.joybus.settings.scmode = data.bit(4);
regs.joybus.settings.sdmode = data.bit(5);
regs.joybus.settings.simode = data.bit(6);
regs.joybus.settings.somode = data.bit(7);
return;
case 0x0400'0135:
regs.joybus.settings.irqenable = data.bit (0);
regs.joybus.settings.mode = data.bits(6,7);
return;
//JOYCNT
case 0x04000140: regs.joybus.control = (regs.joybus.control & 0xff00) | (byte << 0); return;
case 0x04000141: regs.joybus.control = (regs.joybus.control & 0x00ff) | (byte << 8); return;
case 0x0400'0140:
regs.joybus.control.resetsignal = data.bit(0);
regs.joybus.control.receivecomplete = data.bit(1);
regs.joybus.control.sendcomplete = data.bit(2);
regs.joybus.control.irqenable = data.bit(6);
return;
case 0x0400'0141: return;
case 0x0400'0142: return;
case 0x0400'0143: return;
//JOY_RECV_L
//JOY_RECV_H
case 0x04000150: regs.joybus.receive = (regs.joybus.receive & 0xffffff00) | (byte << 0); return;
case 0x04000151: regs.joybus.receive = (regs.joybus.receive & 0xffff00ff) | (byte << 8); return;
case 0x04000152: regs.joybus.receive = (regs.joybus.receive & 0xff00ffff) | (byte << 16); return;
case 0x04000153: regs.joybus.receive = (regs.joybus.receive & 0x00ffffff) | (byte << 24); return;
case 0x0400'0150: regs.joybus.receive.byte(0) = data; return;
case 0x0400'0151: regs.joybus.receive.byte(1) = data; return;
case 0x0400'0152: regs.joybus.receive.byte(2) = data; return;
case 0x0400'0153: regs.joybus.receive.byte(3) = data; return;
//JOY_TRANS_L
//JOY_TRANS_H
case 0x04000154: regs.joybus.transmit = (regs.joybus.transmit & 0xffffff00) | (byte << 0); return;
case 0x04000155: regs.joybus.transmit = (regs.joybus.transmit & 0xffff00ff) | (byte << 8); return;
case 0x04000156: regs.joybus.transmit = (regs.joybus.transmit & 0xff00ffff) | (byte << 16); return;
case 0x04000157: regs.joybus.transmit = (regs.joybus.transmit & 0x00ffffff) | (byte << 24); return;
case 0x0400'0154: regs.joybus.transmit.byte(0) = data; return;
case 0x0400'0155: regs.joybus.transmit.byte(1) = data; return;
case 0x0400'0156: regs.joybus.transmit.byte(2) = data; return;
case 0x0400'0157: regs.joybus.transmit.byte(3) = data; return;
//JOYSTAT
case 0x04000158: regs.joybus.status = (regs.joybus.status & 0xff00) | (byte << 0); return;
case 0x04000159: regs.joybus.status = (regs.joybus.status & 0x00ff) | (byte << 8); return;
case 0x0400'0158:
regs.joybus.status.receiveflag = data.bit (1);
regs.joybus.status.sendflag = data.bit (3);
regs.joybus.status.generalflag = data.bits(4,5);
return;
case 0x0400'0159: return;
//IE
case 0x04000200: regs.irq.enable = (regs.irq.enable & 0xff00) | (byte << 0); return;
case 0x04000201: regs.irq.enable = (regs.irq.enable & 0x00ff) | (byte << 8); return;
case 0x0400'0200: regs.irq.enable.byte(0) = data; return;
case 0x0400'0201: regs.irq.enable.byte(1) = data; return;
//IF
case 0x04000202: regs.irq.flag = regs.irq.flag & ~(byte << 0); return;
case 0x04000203: regs.irq.flag = regs.irq.flag & ~(byte << 8); return;
case 0x0400'0202: regs.irq.flag.byte(0) = regs.irq.flag.byte(0) & ~data; return;
case 0x0400'0203: regs.irq.flag.byte(1) = regs.irq.flag.byte(1) & ~data; return;
//WAITCNT
case 0x04000204: regs.wait.control = (regs.wait.control & 0xff00) | ((byte & 0xff) << 0); return;
case 0x04000205: regs.wait.control = (regs.wait.control & 0x00ff) | ((byte & 0x7f) << 8); return;
case 0x0400'0204:
regs.wait.control.swait[3] = data.bit (0); //todo: is this correct?
regs.wait.control.nwait[3] = data.bits(0,1);
regs.wait.control.nwait[0] = data.bits(2,3);
regs.wait.control.swait[0] = data.bit (4);
regs.wait.control.nwait[1] = data.bits(5,6);
regs.wait.control.swait[1] = data.bit (7);
return;
case 0x0400'0205:
regs.wait.control.nwait[2] = data.bits(0,1);
regs.wait.control.swait[2] = data.bit (2);
regs.wait.control.phi = data.bit (3);
regs.wait.control.prefetch = data.bit (6);
regs.wait.control.gametype = data.bit (7);
return;
//IME
case 0x04000208: regs.ime = byte >> 0; return;
case 0x04000209: return;
case 0x0400'0208: regs.ime = data.bit(0); return;
case 0x0400'0209: return;
//POSTFLG, HALTCNT
case 0x04000300: regs.postboot |= byte >> 0; return;
case 0x04000301: regs.mode = byte & 0x80 ? Registers::Mode::Stop : Registers::Mode::Halt; return;
case 0x0400'0300:
if(data.bit(0)) regs.postboot = 1;
return;
case 0x0400'0301:
regs.mode = data.bit(7) ? Registers::Mode::Stop : Registers::Mode::Halt;
return;
//MEMCNT_L
//MEMCNT_H
case 0x04000800: regs.memory.control = (regs.memory.control & 0xffffff00) | (byte << 0); return;
case 0x04000801: regs.memory.control = (regs.memory.control & 0xffff00ff) | (byte << 8); return;
case 0x04000802: regs.memory.control = (regs.memory.control & 0xff00ffff) | (byte << 16); return;
case 0x04000803: regs.memory.control = (regs.memory.control & 0x00ffffff) | (byte << 24); return;
case 0x0400'0800:
regs.memory.control.disable = data.bit (0);
regs.memory.control.unknown1 = data.bits(1,3);
regs.memory.control.ewram = data.bit (5);
return;
case 0x0400'0801: return;
case 0x0400'0802: return;
case 0x0400'0803:
regs.memory.control.ewramwait = data.bits(0,3);
regs.memory.control.unknown2 = data.bits(4,7);
return;
}
}

View File

@@ -3,7 +3,7 @@ auto CPU::prefetch_sync(uint32 addr) -> void {
prefetch.addr = addr;
prefetch.load = addr;
prefetch.wait = bus_wait(Half | Nonsequential, prefetch.load);
prefetch.wait = busWait(Half | Nonsequential, prefetch.load);
}
auto CPU::prefetch_step(uint clocks) -> void {
@@ -14,7 +14,7 @@ auto CPU::prefetch_step(uint clocks) -> void {
if(--prefetch.wait) continue;
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.read(Half, prefetch.load);
prefetch.load += 2;
prefetch.wait = bus_wait(Half | Sequential, prefetch.load);
prefetch.wait = busWait(Half | Sequential, prefetch.load);
}
}
@@ -22,14 +22,14 @@ auto CPU::prefetch_wait() -> void {
if(!regs.wait.control.prefetch || active.dma || prefetch.full()) return;
prefetch_step(prefetch.wait);
prefetch.wait = bus_wait(Half | Nonsequential, prefetch.load);
prefetch.wait = busWait(Half | Nonsequential, prefetch.load);
}
auto CPU::prefetch_read() -> uint16 {
if(prefetch.empty()) prefetch_step(prefetch.wait);
else prefetch_step(1);
if(prefetch.full()) prefetch.wait = bus_wait(Half | Sequential, prefetch.load);
if(prefetch.full()) prefetch.wait = busWait(Half | Sequential, prefetch.load);
uint16 half = prefetch.slot[prefetch.addr >> 1 & 7];
prefetch.addr += 2;

View File

@@ -1,244 +0,0 @@
CPU::Registers::DMAControl::operator uint16() const {
return (
(targetmode << 5)
| (sourcemode << 7)
| (repeat << 9)
| (size << 10)
| (drq << 11)
| (timingmode << 12)
| (irq << 14)
| (enable << 15)
);
}
auto CPU::Registers::DMAControl::operator=(uint16 source) -> uint16 {
targetmode = source >> 5;
sourcemode = source >> 7;
repeat = source >> 9;
size = source >> 10;
drq = source >> 11;
timingmode = source >> 12;
irq = source >> 14;
enable = source >> 15;
return operator uint16();
}
CPU::Registers::TimerControl::operator uint16() const {
return (
(frequency << 0)
| (cascade << 2)
| (irq << 6)
| (enable << 7)
);
}
auto CPU::Registers::TimerControl::operator=(uint16 source) -> uint16 {
frequency = source >> 0;
cascade = source >> 2;
irq = source >> 6;
enable = source >> 7;
return operator uint16();
}
CPU::Registers::SerialControl::operator uint16() const {
return (
(shiftclockselect << 0)
| (shiftclockfrequency << 1)
| (transferenablereceive << 2)
| (transferenablesend << 3)
| (startbit << 7)
| (transferlength << 12)
| (irqenable << 14)
);
}
auto CPU::Registers::SerialControl::operator=(uint16 source) -> uint16 {
shiftclockselect = source >> 0;
shiftclockfrequency = source >> 1;
transferenablereceive = source >> 2;
transferenablesend = source >> 3;
startbit = source >> 7;
transferlength = source >> 12;
irqenable = source >> 14;
return operator uint16();
}
CPU::Registers::KeypadControl::operator uint16() const {
return (
(flag[0] << 0)
| (flag[1] << 1)
| (flag[2] << 2)
| (flag[3] << 3)
| (flag[4] << 4)
| (flag[5] << 5)
| (flag[6] << 6)
| (flag[7] << 7)
| (flag[8] << 8)
| (flag[9] << 9)
| (enable << 14)
| (condition << 15)
);
}
auto CPU::Registers::KeypadControl::operator=(uint16 source) -> uint16 {
flag[0] = source >> 0;
flag[1] = source >> 1;
flag[2] = source >> 2;
flag[3] = source >> 3;
flag[4] = source >> 4;
flag[5] = source >> 5;
flag[6] = source >> 6;
flag[7] = source >> 7;
flag[8] = source >> 8;
flag[9] = source >> 9;
enable = source >> 14;
condition = source >> 15;
return operator uint16();
}
CPU::Registers::JoybusSettings::operator uint16() const {
return (
(sc << 0)
| (sd << 1)
| (si << 2)
| (so << 3)
| (scmode << 4)
| (sdmode << 5)
| (simode << 6)
| (somode << 7)
| (irqenable << 8)
| (mode << 14)
);
}
auto CPU::Registers::JoybusSettings::operator=(uint16 source) -> uint16 {
sc = source >> 0;
sd = source >> 1;
si = source >> 2;
so = source >> 3;
scmode = source >> 4;
sdmode = source >> 5;
simode = source >> 6;
somode = source >> 7;
irqenable = source >> 8;
mode = source >> 14;
return operator uint16();
}
CPU::Registers::JoybusControl::operator uint16() const {
return (
(resetsignal << 0)
| (receivecomplete << 1)
| (sendcomplete << 2)
| (irqenable << 6)
);
}
auto CPU::Registers::JoybusControl::operator=(uint16 source) -> uint16 {
resetsignal = source >> 0;
receivecomplete = source >> 1;
sendcomplete = source >> 2;
irqenable = source >> 6;
return operator uint16();
}
CPU::Registers::JoybusStatus::operator uint16() const {
return (
(receiveflag << 1)
| (sendflag << 3)
| (generalflag << 4)
);
}
auto CPU::Registers::JoybusStatus::operator=(uint16 source) -> uint16 {
receiveflag = source >> 1;
sendflag = source >> 3;
generalflag = source >> 4;
return operator uint16();
}
CPU::Registers::Interrupt::operator uint16() const {
return (
(vblank << 0)
| (hblank << 1)
| (vcoincidence << 2)
| (timer[0] << 3)
| (timer[1] << 4)
| (timer[2] << 5)
| (timer[3] << 6)
| (serial << 7)
| (dma[0] << 8)
| (dma[1] << 9)
| (dma[2] << 10)
| (dma[3] << 11)
| (keypad << 12)
| (cartridge << 13)
);
}
auto CPU::Registers::Interrupt::operator=(uint16 source) -> uint16 {
vblank = source >> 0;
hblank = source >> 1;
vcoincidence = source >> 2;
timer[0] = source >> 3;
timer[1] = source >> 4;
timer[2] = source >> 5;
timer[3] = source >> 6;
serial = source >> 7;
dma[0] = source >> 8;
dma[1] = source >> 9;
dma[2] = source >> 10;
dma[3] = source >> 11;
keypad = source >> 12;
cartridge = source >> 13;
return operator uint16();
}
CPU::Registers::WaitControl::operator uint16() const {
return (
(nwait[3] << 0)
| (nwait[0] << 2)
| (swait[0] << 4)
| (nwait[1] << 5)
| (swait[1] << 7)
| (nwait[2] << 8)
| (swait[2] << 10)
| (phi << 11)
| (prefetch << 14)
| (gametype << 15)
);
}
auto CPU::Registers::WaitControl::operator=(uint16 source) -> uint16 {
nwait[3] = (source >> 0) & 3;
nwait[0] = (source >> 2) & 3;
swait[0] = (source >> 4) & 1;
nwait[1] = (source >> 5) & 3;
swait[1] = (source >> 7) & 1;
nwait[2] = (source >> 8) & 3;
swait[2] = (source >> 10) & 1;
phi = (source >> 11) & 3;
prefetch = (source >> 14) & 1;
gametype = (source >> 15) & 1;
swait[3] = nwait[3];
return operator uint16();
}
CPU::Registers::MemoryControl::operator uint32() const {
return (
(disable << 0)
| (unknown1 << 1)
| (ewram << 5)
| (ewramwait << 24)
| (unknown2 << 28)
);
}
auto CPU::Registers::MemoryControl::operator=(uint32 source) -> uint32 {
disable = source >> 0;
unknown1 = source >> 1;
ewram = source >> 5;
ewramwait = source >> 24;
unknown2 = source >> 28;
return operator uint32();
}

View File

@@ -1,5 +1,10 @@
struct Registers {
struct DMAControl {
struct DMA {
VariadicNatural source;
VariadicNatural target;
VariadicNatural length;
uint32 data;
struct Control {
uint2 targetmode;
uint2 sourcemode;
uint1 repeat;
@@ -8,47 +13,32 @@ struct Registers {
uint2 timingmode;
uint1 irq;
uint1 enable;
operator uint16() const;
auto operator=(uint16 source) -> uint16;
auto operator=(const DMAControl&) -> DMAControl& = delete;
};
struct DMA {
varuint source;
varuint target;
varuint length;
uint32 data;
DMAControl control;
} control;
//internal
bool pending;
struct Run {
varuint target;
varuint source;
varuint length;
VariadicNatural target;
VariadicNatural source;
VariadicNatural length;
} run;
} dma[4];
struct TimerControl {
uint2 frequency;
uint1 cascade;
uint1 irq;
uint1 enable;
operator uint16() const;
auto operator=(uint16 source) -> uint16;
auto operator=(const TimerControl&) -> TimerControl& = delete;
};
struct Timer {
uint16 period;
uint16 reload;
bool pending;
TimerControl control;
struct Control {
uint2 frequency;
uint1 cascade;
uint1 irq;
uint1 enable;
} control;
} timer[4];
struct SerialControl {
struct Serial {
uint16 data[4];
struct Control {
uint1 shiftclockselect;
uint1 shiftclockfrequency;
uint1 transferenablereceive;
@@ -56,33 +46,20 @@ struct Registers {
uint1 startbit;
uint1 transferlength;
uint1 irqenable;
operator uint16() const;
auto operator=(uint16 source) -> uint16;
auto operator=(const SerialControl&) -> SerialControl& = delete;
};
struct Serial {
uint16 data[4];
SerialControl control;
} control;
uint8 data8;
} serial;
struct KeypadControl {
struct Keypad {
struct Control {
uint1 flag[10];
uint1 enable;
uint1 condition;
operator uint16() const;
auto operator=(uint16 source) -> uint16;
auto operator=(const KeypadControl&) -> KeypadControl& = delete;
};
struct Keypad {
KeypadControl control;
} control;
} keypad;
struct JoybusSettings {
struct Joybus {
struct Settings {
uint1 sc;
uint1 sd;
uint1 si;
@@ -93,93 +70,47 @@ struct Registers {
uint1 somode;
uint1 irqenable;
uint2 mode;
operator uint16() const;
auto operator=(uint16 source) -> uint16;
auto operator=(const JoybusSettings&) -> JoybusSettings& = delete;
};
struct JoybusControl {
} settings;
struct Control {
uint1 resetsignal;
uint1 receivecomplete;
uint1 sendcomplete;
uint1 irqenable;
operator uint16() const;
auto operator=(uint16 source) -> uint16;
auto operator=(const JoybusControl&) -> JoybusControl& = delete;
};
struct JoybusStatus {
} control;
uint32 receive;
uint32 transmit;
struct Status {
uint1 receiveflag;
uint1 sendflag;
uint2 generalflag;
operator uint16() const;
auto operator=(uint16 source) -> uint16;
auto operator=(const JoybusStatus&) -> JoybusStatus& = delete;
};
struct Joybus {
JoybusSettings settings;
JoybusControl control;
uint32 receive;
uint32 transmit;
JoybusStatus status;
} status;
} joybus;
uint1 ime;
struct Interrupt {
uint1 vblank;
uint1 hblank;
uint1 vcoincidence;
uint1 timer[4];
uint1 serial;
uint1 dma[4];
uint1 keypad;
uint1 cartridge;
operator uint16() const;
auto operator=(uint16 source) -> uint16;
auto operator=(const Interrupt&) -> Interrupt& = delete;
};
struct IRQ {
Interrupt enable;
Interrupt flag;
uint16 enable;
uint16 flag;
} irq;
struct WaitControl {
struct Wait {
struct Control {
uint2 nwait[4];
uint1 swait[4];
uint2 phi;
uint1 prefetch;
uint1 gametype;
operator uint16() const;
auto operator=(uint16 source) -> uint16;
auto operator=(const WaitControl&) -> WaitControl& = delete;
};
struct Wait {
WaitControl control;
} control;
} wait;
struct MemoryControl {
struct Memory {
struct Control {
uint1 disable;
uint3 unknown1;
uint1 ewram;
uint4 ewramwait;
uint4 unknown2;
operator uint32() const;
auto operator=(uint32 source) -> uint32;
auto operator=(const MemoryControl&) -> MemoryControl& = delete;
};
struct Memory {
MemoryControl control;
} control;
} memory;
uint1 postboot;

View File

@@ -72,23 +72,8 @@ auto CPU::serialize(serializer& s) -> void {
s.integer(regs.ime);
s.integer(regs.irq.enable.vblank);
s.integer(regs.irq.enable.hblank);
s.integer(regs.irq.enable.vcoincidence);
for(auto& flag : regs.irq.enable.timer) s.integer(flag);
s.integer(regs.irq.enable.serial);
for(auto& flag : regs.irq.enable.dma) s.integer(flag);
s.integer(regs.irq.enable.keypad);
s.integer(regs.irq.enable.cartridge);
s.integer(regs.irq.flag.vblank);
s.integer(regs.irq.flag.hblank);
s.integer(regs.irq.flag.vcoincidence);
for(auto& flag : regs.irq.flag.timer) s.integer(flag);
s.integer(regs.irq.flag.serial);
for(auto& flag : regs.irq.flag.dma) s.integer(flag);
s.integer(regs.irq.flag.keypad);
s.integer(regs.irq.flag.cartridge);
s.integer(regs.irq.enable);
s.integer(regs.irq.flag);
for(auto& flag : regs.wait.control.nwait) s.integer(flag);
for(auto& flag : regs.wait.control.swait) s.integer(flag);

View File

@@ -28,7 +28,7 @@ auto CPU::timer_increment(uint n) -> void {
if(++timer.period == 0) {
timer.period = timer.reload;
if(timer.control.irq) regs.irq.flag.timer[n] = 1;
if(timer.control.irq) regs.irq.flag |= Interrupt::Timer0 << n;
if(apu.fifo[0].timer == n) timer_fifo_run(0);
if(apu.fifo[1].timer == n) timer_fifo_run(1);

View File

@@ -1,22 +1,17 @@
#pragma once
//license: GPLv3
//started: 2012-03-19
#include <emulator/emulator.hpp>
#include <processor/arm/arm.hpp>
namespace GameBoyAdvance {
namespace Info {
static const string Name = "bgba";
static const uint SerializerVersion = 3;
static const uint SerializerVersion = 4;
}
}
/*
bgba - Game Boy Advance emulator
authors: byuu, Cydrak
license: GPLv3
project started: 2012-03-19
*/
#include <libco/libco.h>
namespace GameBoyAdvance {
@@ -39,7 +34,7 @@ namespace GameBoyAdvance {
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
if(thread) co_delete(thread);
thread = co_create(65536 * sizeof(void*), entrypoint);
thread = co_create(65'536 * sizeof(void*), entrypoint);
this->frequency = frequency;
clock = 0;
}
@@ -62,7 +57,6 @@ namespace GameBoyAdvance {
#include <gba/cpu/cpu.hpp>
#include <gba/ppu/ppu.hpp>
#include <gba/apu/apu.hpp>
#include <gba/video/video.hpp>
}
#include <gba/interface/interface.hpp>

View File

@@ -8,34 +8,35 @@ Settings settings;
Interface::Interface() {
interface = this;
information.manufacturer = "Nintendo";
information.name = "Game Boy Advance";
information.width = 240;
information.height = 160;
information.overscan = false;
information.aspectRatio = 1.0;
information.resettable = false;
information.capability.states = true;
information.capability.cheats = false;
media.append({ID::GameBoyAdvance, "Game Boy Advance", "gba", true});
{ Device device{0, ID::Device, "Controller"};
device.input.append({ 0, 0, "A" });
device.input.append({ 1, 0, "B" });
device.input.append({ 2, 0, "Select"});
device.input.append({ 3, 0, "Start" });
device.input.append({ 4, 0, "Right" });
device.input.append({ 5, 0, "Left" });
device.input.append({ 6, 0, "Up" });
device.input.append({ 7, 0, "Down" });
device.input.append({ 8, 0, "R" });
device.input.append({ 9, 0, "L" });
device.input.append({10, 2, "Rumble"});
device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3, 10};
this->device.append(device);
device.inputs.append({ 0, 0, "Up" });
device.inputs.append({ 1, 0, "Down" });
device.inputs.append({ 2, 0, "Left" });
device.inputs.append({ 3, 0, "Right" });
device.inputs.append({ 4, 0, "B" });
device.inputs.append({ 5, 0, "A" });
device.inputs.append({ 6, 0, "L" });
device.inputs.append({ 7, 0, "R" });
device.inputs.append({ 8, 0, "Select"});
device.inputs.append({ 9, 0, "Start" });
device.inputs.append({10, 2, "Rumble"});
devices.append(device);
}
port.append({0, "Device", {device[0]}});
ports.append({0, "Device", {devices[0]}});
}
auto Interface::manifest() -> string {
@@ -50,12 +51,38 @@ auto Interface::videoFrequency() -> double {
return 16777216.0 / (228.0 * 1232.0);
}
auto Interface::videoColors() -> uint32 {
return 1 << 15;
}
auto Interface::videoColor(uint32 color) -> uint64 {
uint R = color.bits( 0, 4);
uint G = color.bits( 5, 9);
uint B = color.bits(10,14);
uint64 r = image::normalize(R, 5, 16);
uint64 g = image::normalize(G, 5, 16);
uint64 b = image::normalize(B, 5, 16);
if(settings.colorEmulation) {
double lcdGamma = 4.0, outGamma = 2.2;
double lb = pow(B / 31.0, lcdGamma);
double lg = pow(G / 31.0, lcdGamma);
double lr = pow(R / 31.0, lcdGamma);
r = pow(( 0 * lb + 50 * lg + 255 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
g = pow(( 30 * lb + 230 * lg + 10 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
b = pow((220 * lb + 10 * lg + 50 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
}
return r << 32 | g << 16 | b << 0;
}
auto Interface::audioFrequency() -> double {
return 16777216.0 / 512.0;
}
auto Interface::loaded() -> bool {
return cartridge.loaded();
return system.loaded();
}
auto Interface::group(uint id) -> uint {
@@ -75,7 +102,7 @@ auto Interface::group(uint id) -> uint {
}
auto Interface::load(uint id) -> void {
cartridge.load();
system.load();
}
auto Interface::save() -> void {
@@ -90,7 +117,7 @@ auto Interface::load(uint id, const stream& stream) -> void {
}
if(id == ID::BIOS) {
stream.read(bios.data, min(bios.size, stream.size()));
stream.read((uint8_t*)bios.data, min(bios.size, stream.size()));
}
if(id == ID::Manifest) {
@@ -98,39 +125,39 @@ auto Interface::load(uint id, const stream& stream) -> void {
}
if(id == ID::MROM) {
stream.read(cartridge.mrom.data, min(cartridge.mrom.size, stream.size()));
stream.read((uint8_t*)cartridge.mrom.data, min(cartridge.mrom.size, stream.size()));
}
if(id == ID::SRAM) {
stream.read(cartridge.sram.data, min(cartridge.sram.size, stream.size()));
stream.read((uint8_t*)cartridge.sram.data, min(cartridge.sram.size, stream.size()));
}
if(id == ID::EEPROM) {
stream.read(cartridge.eeprom.data, min(cartridge.eeprom.size, stream.size()));
stream.read((uint8_t*)cartridge.eeprom.data, min(cartridge.eeprom.size, stream.size()));
}
if(id == ID::FLASH) {
stream.read(cartridge.flash.data, min(cartridge.flash.size, stream.size()));
stream.read((uint8_t*)cartridge.flash.data, min(cartridge.flash.size, stream.size()));
}
}
auto Interface::save(uint id, const stream& stream) -> void {
if(id == ID::SRAM) {
stream.write(cartridge.sram.data, cartridge.sram.size);
stream.write((uint8_t*)cartridge.sram.data, cartridge.sram.size);
}
if(id == ID::EEPROM) {
stream.write(cartridge.eeprom.data, cartridge.eeprom.size);
stream.write((uint8_t*)cartridge.eeprom.data, cartridge.eeprom.size);
}
if(id == ID::FLASH) {
stream.write(cartridge.flash.data, cartridge.flash.size);
stream.write((uint8_t*)cartridge.flash.data, cartridge.flash.size);
}
}
auto Interface::unload() -> void {
save();
cartridge.unload();
system.unload();
}
auto Interface::power() -> void {
@@ -146,7 +173,7 @@ auto Interface::run() -> void {
}
auto Interface::serialize() -> serializer {
system.runtosave();
system.runToSave();
return system.serialize();
}
@@ -167,8 +194,18 @@ auto Interface::get(const string& name) -> any {
}
auto Interface::set(const string& name, const any& value) -> bool {
if(name == "Blur Emulation" && value.is<bool>()) return settings.blurEmulation = value.get<bool>(), true;
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
if(name == "Blur Emulation" && value.is<bool>()) {
settings.blurEmulation = value.get<bool>();
system.configureVideoEffects();
return true;
}
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
system.configureVideoPalette();
return true;
}
return false;
}

View File

@@ -28,6 +28,8 @@ struct Interface : Emulator::Interface {
auto manifest() -> string;
auto title() -> string;
auto videoFrequency() -> double;
auto videoColors() -> uint32;
auto videoColor(uint32 color) -> uint64;
auto audioFrequency() -> double;
auto loaded() -> bool;
@@ -50,7 +52,7 @@ struct Interface : Emulator::Interface {
auto set(const string& name, const any& value) -> bool override;
private:
vector<Device> device;
vector<Device> devices;
};
struct Settings {

View File

@@ -31,7 +31,26 @@ auto Player::frame() -> void {
if(!status.enable) return;
if(cpu.regs.joybus.settings == 0x0000 && cpu.regs.serial.control == 0x5088) {
//todo: verify which settings are actually required
//values were taken from observing GBP-compatible games
if(!cpu.regs.joybus.settings.sc
&& !cpu.regs.joybus.settings.sd
&& !cpu.regs.joybus.settings.si
&& !cpu.regs.joybus.settings.so
&& !cpu.regs.joybus.settings.scmode
&& !cpu.regs.joybus.settings.sdmode
&& !cpu.regs.joybus.settings.simode
&& !cpu.regs.joybus.settings.somode
&& !cpu.regs.joybus.settings.irqenable
&& !cpu.regs.joybus.settings.mode
&& !cpu.regs.serial.control.shiftclockselect
&& !cpu.regs.serial.control.shiftclockfrequency
&& !cpu.regs.serial.control.transferenablereceive
&& cpu.regs.serial.control.transferenablesend
&& cpu.regs.serial.control.startbit
&& cpu.regs.serial.control.transferlength
&& cpu.regs.serial.control.irqenable
) {
status.packet = (status.packet + 1) % 17;
switch(status.packet) {
case 0: status.send = 0x0000494e; break;
@@ -52,16 +71,16 @@ auto Player::frame() -> void {
case 15: status.send = 0x30000003; break;
case 16: status.send = 0x30000003; break;
}
cpu.regs.irq.flag.serial = true;
cpu.regs.irq.flag |= CPU::Interrupt::Serial;
}
}
auto Player::keyinput() -> maybe<uint16> {
if(status.logoDetected) {
switch(status.logoCounter) {
case 0: return 0x03ff;
case 1: return 0x03ff;
case 2: return 0x030f;
case 0: return {0x03ff};
case 1: return {0x03ff};
case 2: return {0x030f};
}
}
return nothing;
@@ -72,7 +91,7 @@ auto Player::read() -> maybe<uint32> {
return nothing;
}
auto Player::write(uint8 byte, uint2 addr) -> void {
auto Player::write(uint2 addr, uint8 byte) -> void {
if(!status.enable) return;
uint shift = addr << 3;

View File

@@ -16,7 +16,7 @@ struct Player {
auto keyinput() -> maybe<uint16>;
auto read() -> maybe<uint32>;
auto write(uint8 byte, uint2 addr) -> void;
auto write(uint2 addr, uint8 byte) -> void;
auto serialize(serializer& s) -> void;
};

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