From a52e9d7dc1b21b3da1eb5cf4abb15967dbc52eb8 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Sun, 28 May 2017 17:17:18 -0400 Subject: [PATCH] https://www.youtube.com/watch?v=0sKRRY5tQz8 --- .../BizHawk.Emulation.Cores.csproj | 2 + .../Consoles/Nintendo/VB/LibVirtualBoyee.cs | 92 ++ .../Consoles/Nintendo/VB/VirtualBoyee.cs | 135 ++ waterbox/vb/.vscode/settings.json | 9 + waterbox/vb/Makefile | 43 + waterbox/vb/blip/Blip_Buffer.cpp | 457 ++++++ waterbox/vb/blip/Blip_Buffer.h | 498 ++++++ waterbox/vb/endian.h | 494 ++++++ waterbox/vb/git.h | 127 ++ waterbox/vb/input.cpp | 196 +++ waterbox/vb/input.h | 45 + waterbox/vb/math_ops.h | 278 ++++ waterbox/vb/timer.cpp | 233 +++ waterbox/vb/timer.h | 48 + waterbox/vb/v810/v810_cpu.cpp | 1369 ++++++++++++++++ waterbox/vb/v810/v810_cpu.h | 335 ++++ waterbox/vb/v810/v810_do_am.h | 72 + waterbox/vb/v810/v810_fp_ops.cpp | 405 +++++ waterbox/vb/v810/v810_fp_ops.h | 74 + waterbox/vb/v810/v810_oploop.inc | 1130 +++++++++++++ waterbox/vb/v810/v810_opt.h | 170 ++ waterbox/vb/vb.cpp | 780 +++++++++ waterbox/vb/vb.h | 114 ++ waterbox/vb/vb.wbx | Bin 0 -> 165390 bytes waterbox/vb/vb.wbx.in | Bin 0 -> 343350 bytes waterbox/vb/vip.cpp | 1399 +++++++++++++++++ waterbox/vb/vip.h | 83 + waterbox/vb/vip_draw.inc | 493 ++++++ waterbox/vb/vsu.cpp | 498 ++++++ waterbox/vb/vsu.h | 93 ++ 30 files changed, 9672 insertions(+) create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/VB/LibVirtualBoyee.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/VB/VirtualBoyee.cs create mode 100644 waterbox/vb/.vscode/settings.json create mode 100644 waterbox/vb/Makefile create mode 100644 waterbox/vb/blip/Blip_Buffer.cpp create mode 100644 waterbox/vb/blip/Blip_Buffer.h create mode 100644 waterbox/vb/endian.h create mode 100644 waterbox/vb/git.h create mode 100644 waterbox/vb/input.cpp create mode 100644 waterbox/vb/input.h create mode 100644 waterbox/vb/math_ops.h create mode 100644 waterbox/vb/timer.cpp create mode 100644 waterbox/vb/timer.h create mode 100644 waterbox/vb/v810/v810_cpu.cpp create mode 100644 waterbox/vb/v810/v810_cpu.h create mode 100644 waterbox/vb/v810/v810_do_am.h create mode 100644 waterbox/vb/v810/v810_fp_ops.cpp create mode 100644 waterbox/vb/v810/v810_fp_ops.h create mode 100644 waterbox/vb/v810/v810_oploop.inc create mode 100644 waterbox/vb/v810/v810_opt.h create mode 100644 waterbox/vb/vb.cpp create mode 100644 waterbox/vb/vb.h create mode 100644 waterbox/vb/vb.wbx create mode 100644 waterbox/vb/vb.wbx.in create mode 100644 waterbox/vb/vip.cpp create mode 100644 waterbox/vb/vip.h create mode 100644 waterbox/vb/vip_draw.inc create mode 100644 waterbox/vb/vsu.cpp create mode 100644 waterbox/vb/vsu.h diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index b1339dc7bd..3066f74cdd 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -992,6 +992,8 @@ + + diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/LibVirtualBoyee.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/LibVirtualBoyee.cs new file mode 100644 index 0000000000..968403112f --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/LibVirtualBoyee.cs @@ -0,0 +1,92 @@ +using BizHawk.Common.BizInvoke; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.VB +{ + public abstract class LibVirtualBoyee + { + private const CallingConvention CC = CallingConvention.Cdecl; + + [StructLayout(LayoutKind.Sequential)] + public struct Rect + { + public int X; + public int Y; + public int W; + public int H; + } + + [StructLayout(LayoutKind.Sequential)] + public class EmulateSpec + { + // Pitch(32-bit) must be equal to width and >= the "fb_width" specified in the MDFNGI struct for the emulated system. + // Height must be >= to the "fb_height" specified in the MDFNGI struct for the emulated system. + // The framebuffer pointed to by surface->pixels is written to by the system emulation code. + public IntPtr Pixels; + + // Pointer to sound buffer, set by the driver code, that the emulation code should render sound to. + // Guaranteed to be at least 500ms in length, but emulation code really shouldn't exceed 40ms or so. Additionally, if emulation code + // generates >= 100ms, + // DEPRECATED: Emulation code may set this pointer to a sound buffer internal to the emulation module. + public IntPtr SoundBuf; + + // Number of cycles that this frame consumed, using MDFNGI::MasterClock as a time base. + // Set by emulation code. + public long MasterCycles; + + // Set by the system emulation code every frame, to denote the horizontal and vertical offsets of the image, and the size + // of the image. If the emulated system sets the elements of LineWidths, then the width(w) of this structure + // is ignored while drawing the image. + public Rect DisplayRect; + + // Maximum size of the sound buffer, in frames. Set by the driver code. + public int SoundBufMaxSize; + + // Number of frames currently in internal sound buffer. Set by the system emulation code, to be read by the driver code. + public int SoundBufSize; + + // 0 UDLR SelectStartBA UDLR(right dpad) LtrigRtrig 13 + public Buttons Buttons; + } + + public enum MemoryArea : int + { + Wram, Sram, Rom + } + + public enum Buttons : int + { + Up = 0x1, + Down = 0x2, + Left = 0x4, + Right = 0x8, + Select = 0x10, + Start = 0x20, + B = 0x40, + A = 0x80, + Up_R = 0x100, + Down_R = 0x200, + Left_R = 0x400, + Right_R = 0x800, + L = 0x1000, + R = 0x2000 + } + + [BizImport(CC)] + public abstract bool Load(byte[] rom, int length); + + [BizImport(CC)] + public abstract void GetMemoryArea(MemoryArea which, ref IntPtr ptr, ref int size); + + [BizImport(CC)] + public abstract void Emulate(EmulateSpec espec); + + [BizImport(CC)] + public abstract void HardReset(); + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/VirtualBoyee.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/VirtualBoyee.cs new file mode 100644 index 0000000000..05e84aebf7 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/VB/VirtualBoyee.cs @@ -0,0 +1,135 @@ +using BizHawk.Common.BizInvoke; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Waterbox; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.VB +{ + public class VirtualBoyee : IEmulator, IVideoProvider + { + private PeRunner _exe; + private LibVirtualBoyee _boyee; + + [CoreConstructor("VB")] + public VirtualBoyee(CoreComm comm, byte[] rom) + { + ServiceProvider = new BasicServiceProvider(this); + CoreComm = comm; + + _exe = new PeRunner(new PeRunnerOptions + { + Path = comm.CoreFileProvider.DllPath(), + Filename = "vb.wbx", + NormalHeapSizeKB = 1024, + SealedHeapSizeKB = 12 * 1024, + InvisibleHeapSizeKB = 6 * 1024, + SpecialHeapSizeKB = 64 + }); + + _boyee = BizInvoker.GetInvoker(_exe, _exe); + + if (!_boyee.Load(rom, rom.Length)) + { + throw new InvalidOperationException("Core rejected the rom"); + } + } + + private bool _disposed = false; + + public void Dispose() + { + if (!_disposed) + { + _exe.Dispose(); + _exe = null; + _disposed = true; + } + } + + public IEmulatorServiceProvider ServiceProvider { get; private set; } + + public unsafe void FrameAdvance(IController controller, bool render, bool rendersound = true) + { + var scratch = new short[16384]; + + fixed(int*vp = _videoBuffer) + fixed(short*sp = scratch) + { + var spec = new LibVirtualBoyee.EmulateSpec + { + Pixels = (IntPtr)vp, + SoundBuf = (IntPtr)sp, + SoundBufMaxSize = 8192 + }; + + _boyee.Emulate(spec); + VirtualWidth = BufferWidth = spec.DisplayRect.W; + VirtualWidth = BufferHeight = spec.DisplayRect.H; + Console.WriteLine(spec.SoundBufSize); + } + + Frame++; + + /*_core.biz_set_input_callback(InputCallbacks.Count > 0 ? _inputCallback : null); + + if (controller.IsPressed("Power")) + _core.biz_hard_reset(); + else if (controller.IsPressed("Reset")) + _core.biz_soft_reset(); + + UpdateControls(controller); + Frame++; + LibSnes9x.frame_info frame = new LibSnes9x.frame_info(); + + _core.biz_run(frame, _inputState); + IsLagFrame = frame.padread == 0; + if (IsLagFrame) + LagCount++; + using (_exe.EnterExit()) + { + Blit(frame); + Sblit(frame); + }*/ + } + + public int Frame { get; private set; } + + public void ResetCounters() + { + Frame = 0; + } + + public string SystemId { get { return "VB"; } } + public bool DeterministicEmulation { get { return true; } } + public CoreComm CoreComm { get; private set; } + + public ControllerDefinition ControllerDefinition => NullController.Instance.Definition; + + #region IVideoProvider + + private int[] _videoBuffer = new int[0]; + + public int[] GetVideoBuffer() + { + throw new NotImplementedException(); + } + + public int VirtualWidth { get; private set; } + public int VirtualHeight { get; private set; } + + public int BufferWidth { get; private set; } + public int BufferHeight { get; private set; } + + public int VsyncNumerator { get; private set; } + + public int VsyncDenominator { get; private set; } + + public int BackgroundColor => unchecked((int)0xff000000); + + #endregion + } +} diff --git a/waterbox/vb/.vscode/settings.json b/waterbox/vb/.vscode/settings.json new file mode 100644 index 0000000000..edb0a83e80 --- /dev/null +++ b/waterbox/vb/.vscode/settings.json @@ -0,0 +1,9 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "files.associations": { + "xiosbase": "cpp", + "xlocale": "cpp" + } +} \ No newline at end of file diff --git a/waterbox/vb/Makefile b/waterbox/vb/Makefile new file mode 100644 index 0000000000..b3f5d5b4e4 --- /dev/null +++ b/waterbox/vb/Makefile @@ -0,0 +1,43 @@ +CC = x86_64-nt64-midipix-g++ + +CCFLAGS:= -I. -I../emulibc \ + -Wall -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=implicit-function-declaration \ + -std=c++0x -fomit-frame-pointer -fvisibility=hidden -fno-exceptions -fno-rtti \ + -DLSB_FIRST \ + -O0 + +TARGET = vb.wbx + +LDFLAGS = -Wl,--dynamicbase,--export-all-symbols + +ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +SRCS:=$(shell find $(ROOT_DIR) -type f -name '*.cpp') +OBJ_DIR:=$(ROOT_DIR)/obj + +_OBJS:=$(SRCS:.cpp=.o) +OBJS:=$(patsubst $(ROOT_DIR)%,$(OBJ_DIR)%,$(_OBJS)) + +$(OBJ_DIR)/%.o: %.cpp + @mkdir -p $(@D) + @$(CC) -c -o $@ $< $(CCFLAGS) + +all: $(TARGET) + +.PHONY: clean all + +$(TARGET).in: $(OBJS) + @$(CC) -o $@ $(LDFLAGS) $(CCFLAGS) $(OBJS) ../emulibc/libemuhost.so + +$(TARGET): $(TARGET).in + strip $< -o $@ -R /4 -R /14 -R /29 -R /41 -R /55 -R /67 -R /78 -R /89 -R /104 + +clean: + rm -rf $(OBJ_DIR) + rm -f $(TARGET).in + rm -f $(TARGET) + +print-%: + @echo $* = $($*) + +#install: +# $(CP) $(TARGET) $(DEST_$(ARCH)) diff --git a/waterbox/vb/blip/Blip_Buffer.cpp b/waterbox/vb/blip/Blip_Buffer.cpp new file mode 100644 index 0000000000..f04a1fc599 --- /dev/null +++ b/waterbox/vb/blip/Blip_Buffer.cpp @@ -0,0 +1,457 @@ +// Blip_Buffer 0.4.1. http://www.slack.net/~ant/ + +#include "Blip_Buffer.h" + +#include +#include +#include +#include +#include +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +int const silent_buf_size = 1; // size used for Silent_Blip_Buffer + +Blip_Buffer::Blip_Buffer() +{ + factor_ = (blip_u64)ULLONG_MAX; + offset_ = 0; + buffer_ = 0; + buffer_size_ = 0; + sample_rate_ = 0; + reader_accum_ = 0; + bass_shift_ = 0; + clock_rate_ = 0; + bass_freq_ = 16; + length_ = 0; + + // assumptions code makes about implementation-defined features + #ifndef NDEBUG + // right shift of negative value preserves sign + buf_t_ i = -0x7FFFFFFE; + assert( (i >> 1) == -0x3FFFFFFF ); + + // casting to short truncates to 16 bits and sign-extends + i = 0x18000; + assert( (short) i == -0x8000 ); + #endif +} + +Blip_Buffer::~Blip_Buffer() +{ + if ( buffer_size_ != silent_buf_size ) + free( buffer_ ); +} + +Silent_Blip_Buffer::Silent_Blip_Buffer() +{ + factor_ = 0; + buffer_ = buf; + buffer_size_ = silent_buf_size; + memset( buf, 0, sizeof buf ); // in case machine takes exception for signed overflow +} + +void Blip_Buffer::clear( int entire_buffer ) +{ + offset_ = 0; + reader_accum_ = 0; + modified_ = 0; + if ( buffer_ ) + { + long count = (entire_buffer ? buffer_size_ : samples_avail()); + memset( buffer_, 0, (count + blip_buffer_extra_) * sizeof (buf_t_) ); + } +} + +Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec ) +{ + if ( buffer_size_ == silent_buf_size ) + { + assert( 0 ); + return "Internal (tried to resize Silent_Blip_Buffer)"; + } + + // start with maximum length that resampled time can represent + blip_s64 new_size = (ULLONG_MAX >> BLIP_BUFFER_ACCURACY) - blip_buffer_extra_ - 64; + + // simple safety check, since code elsewhere may not be safe for sizes approaching (2 ^ 31). + if(new_size > ((1LL << 30) - 1)) + new_size = (1LL << 30) - 1; + + if ( msec != blip_max_length ) + { + blip_s64 s = ((blip_s64)new_rate * (msec + 1) + 999) / 1000; + if ( s < new_size ) + new_size = s; + else + assert( 0 ); // fails if requested buffer length exceeds limit + } + + if ( buffer_size_ != new_size ) + { + void* p = realloc( buffer_, (new_size + blip_buffer_extra_) * sizeof *buffer_ ); + if ( !p ) + return "Out of memory"; + + //if(new_size > buffer_size_) + // memset(buffer_ + buffer_size_, 0, (new_size + blip_buffer_extra_) * sizeof *buffer_ + + buffer_ = (buf_t_*) p; + } + + buffer_size_ = new_size; + assert( buffer_size_ != silent_buf_size ); + + // update things based on the sample rate + sample_rate_ = new_rate; + length_ = new_size * 1000 / new_rate - 1; + if ( msec ) + assert( length_ == msec ); // ensure length is same as that passed in + if ( clock_rate_ ) + clock_rate( clock_rate_ ); + bass_freq( bass_freq_ ); + + clear(); + + return 0; // success +} + +blip_resampled_time_t Blip_Buffer::clock_rate_factor( long rate ) const +{ + double ratio = (double) sample_rate_ / rate; + blip_s64 factor = (blip_s64) floor( ratio * (1LL << BLIP_BUFFER_ACCURACY) + 0.5 ); + assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large + return (blip_resampled_time_t) factor; +} + +void Blip_Buffer::bass_freq( int freq ) +{ + bass_freq_ = freq; + int shift = 31; + if ( freq > 0 ) + { + shift = 13; + long f = (freq << 16) / sample_rate_; + while ( (f >>= 1) && --shift ) { } + } + bass_shift_ = shift; + //printf("%d\n", bass_shift_); +} + +void Blip_Buffer::end_frame( blip_time_t t ) +{ + offset_ += t * factor_; + assert( samples_avail() <= (long) buffer_size_ ); // time outside buffer length +} + +void Blip_Buffer::remove_silence( long count ) +{ + assert( count <= samples_avail() ); // tried to remove more samples than available + offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; +} + +long Blip_Buffer::count_samples( blip_time_t t ) const +{ + unsigned long last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY; + unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY; + return (long) (last_sample - first_sample); +} + +blip_time_t Blip_Buffer::count_clocks( long count ) const +{ + if ( !factor_ ) + { + assert( 0 ); // sample rate and clock rates must be set first + return 0; + } + + if ( count > buffer_size_ ) + count = buffer_size_; + blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; + return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_); +} + +void Blip_Buffer::remove_samples( long count ) +{ + if ( count ) + { + remove_silence( count ); + + // copy remaining samples to beginning and clear old samples + long remain = samples_avail() + blip_buffer_extra_; + memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ ); + memset( buffer_ + remain, 0, count * sizeof *buffer_ ); + } +} + +// Blip_Synth_ + +Blip_Synth_Fast_::Blip_Synth_Fast_() +{ + buf = 0; + last_amp = 0; + delta_factor = 0; +} + +void Blip_Synth_Fast_::volume_unit( double new_unit ) +{ + delta_factor = int (new_unit * (1L << blip_sample_bits) + 0.5); +} + +#if !BLIP_BUFFER_FAST + +Blip_Synth_::Blip_Synth_( short* p, int w ) : + impulses( p ), + width( w ) +{ + volume_unit_ = 0.0; + kernel_unit = 0; + buf = 0; + last_amp = 0; + delta_factor = 0; +} + +#undef PI +#define PI 3.1415926535897932384626433832795029 + +static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff ) +{ + if ( cutoff >= 0.999 ) + cutoff = 0.999; + + if ( treble < -300.0 ) + treble = -300.0; + if ( treble > 5.0 ) + treble = 5.0; + + double const maxh = 4096.0; + double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) ); + double const pow_a_n = pow( rolloff, maxh - maxh * cutoff ); + double const to_angle = PI / 2 / maxh / oversample; + for ( int i = 0; i < count; i++ ) + { + double angle = ((i - count) * 2 + 1) * to_angle; + double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle ); + double cos_nc_angle = cos( maxh * cutoff * angle ); + double cos_nc1_angle = cos( (maxh * cutoff - 1.0) * angle ); + double cos_angle = cos( angle ); + + c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle; + double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle); + double b = 2.0 - cos_angle - cos_angle; + double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; + + out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d + } +} + +void blip_eq_t::generate( float* out, int count ) const +{ + // lower cutoff freq for narrow kernels with their wider transition band + // (8 points->1.49, 16 points->1.15) + double oversample = blip_res * 2.25 / count + 0.85; + double half_rate = sample_rate * 0.5; + if ( cutoff_freq ) + oversample = half_rate / cutoff_freq; + double cutoff = rolloff_freq * oversample / half_rate; + + gen_sinc( out, count, blip_res * oversample, treble, cutoff ); + + // apply (half of) hamming window + double to_fraction = PI / (count - 1); + for ( int i = count; i--; ) + out [i] *= 0.54f - 0.46f * (float) cos( i * to_fraction ); +} + +void Blip_Synth_::adjust_impulse() +{ + // sum pairs for each phase and add error correction to end of first half + int const size = impulses_size(); + for ( int p = blip_res; p-- >= blip_res / 2; ) + { + int p2 = blip_res - 2 - p; + long error = kernel_unit; + for ( int i = 1; i < size; i += blip_res ) + { + error -= impulses [i + p ]; + error -= impulses [i + p2]; + } + if ( p == p2 ) + error /= 2; // phase = 0.5 impulse uses same half for both sides + impulses [size - blip_res + p] += (short) error; + //printf( "error: %ld\n", error ); + } + + //for ( int i = blip_res; i--; printf( "\n" ) ) + // for ( int j = 0; j < width / 2; j++ ) + // printf( "%5ld,", impulses [j * blip_res + i + 1] ); +} + +void Blip_Synth_::treble_eq( blip_eq_t const& eq ) +{ + float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2]; + + int const half_size = blip_res / 2 * (width - 1); + eq.generate( &fimpulse [blip_res], half_size ); + + int i; + + // need mirror slightly past center for calculation + for ( i = blip_res; i--; ) + fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i]; + + // starts at 0 + for ( i = 0; i < blip_res; i++ ) + fimpulse [i] = 0.0f; + + // find rescale factor + double total = 0.0; + for ( i = 0; i < half_size; i++ ) + total += fimpulse [blip_res + i]; + + //double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB + //double const base_unit = 37888.0; // allows treble to +5 dB + double const base_unit = 32768.0; // necessary for blip_unscaled to work + double rescale = base_unit / 2 / total; + kernel_unit = (long) base_unit; + + // integrate, first difference, rescale, convert to int + double sum = 0.0; + double next = 0.0; + int const impulses_size_local = this->impulses_size(); + for ( i = 0; i < impulses_size_local; i++ ) + { + impulses [i] = (short) floor( (next - sum) * rescale + 0.5 ); + sum += fimpulse [i]; + next += fimpulse [i + blip_res]; + } + adjust_impulse(); + + // volume might require rescaling + double vol = volume_unit_; + if ( vol ) + { + volume_unit_ = 0.0; + volume_unit( vol ); + } +} + +void Blip_Synth_::volume_unit( double new_unit ) +{ + if ( new_unit != volume_unit_ ) + { + // use default eq if it hasn't been set yet + if ( !kernel_unit ) + treble_eq( -8.0 ); + + volume_unit_ = new_unit; + double factor = new_unit * (1L << blip_sample_bits) / kernel_unit; + + if ( factor > 0.0 ) + { + int shift = 0; + + // if unit is really small, might need to attenuate kernel + while ( factor < 2.0 ) + { + shift++; + factor *= 2.0; + } + + if ( shift ) + { + kernel_unit >>= shift; + assert( kernel_unit > 0 ); // fails if volume unit is too low + + // keep values positive to avoid round-towards-zero of sign-preserving + // right shift for negative values + long offset = 0x8000 + (1 << (shift - 1)); + long offset2 = 0x8000 >> shift; + for ( int i = impulses_size(); i--; ) + impulses [i] = (short) (((impulses [i] + offset) >> shift) - offset2); + adjust_impulse(); + } + } + delta_factor = (int) floor( factor + 0.5 ); + //printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit ); + } +} +#endif + +long Blip_Buffer::read_samples( blip_sample_t* BLIP_RESTRICT out, long max_samples, int stereo ) +{ + long count = samples_avail(); + if ( count > max_samples ) + count = max_samples; + + if ( count ) + { + int const bass = BLIP_READER_BASS( *this ); + BLIP_READER_BEGIN( reader, *this ); + + if ( !stereo ) + { + for ( blip_long n = count; n; --n ) + { + blip_long s = BLIP_READER_READ( reader ); + if ( (blip_sample_t) s != s ) + s = 0x7FFF - (s >> 24); + *out++ = (blip_sample_t) s; + BLIP_READER_NEXT( reader, bass ); + } + } + else + { + for ( blip_long n = count; n; --n ) + { + blip_long s = BLIP_READER_READ( reader ); + if ( (blip_sample_t) s != s ) + s = 0x7FFF - (s >> 24); + *out = (blip_sample_t) s; + out += 2; + BLIP_READER_NEXT( reader, bass ); + } + } + BLIP_READER_END( reader, *this ); + + remove_samples( count ); + } + return count; +} + +void Blip_Buffer::mix_samples( blip_sample_t const* in, long count ) +{ + if ( buffer_size_ == silent_buf_size ) + { + assert( 0 ); + return; + } + + buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; + + int const sample_shift = blip_sample_bits - 16; + int prev = 0; + while ( count-- ) + { + blip_long s = (blip_long) *in++ << sample_shift; + *out += s - prev; + prev = s; + ++out; + } + *out -= prev; +} + diff --git a/waterbox/vb/blip/Blip_Buffer.h b/waterbox/vb/blip/Blip_Buffer.h new file mode 100644 index 0000000000..a8e90ee053 --- /dev/null +++ b/waterbox/vb/blip/Blip_Buffer.h @@ -0,0 +1,498 @@ +// Band-limited sound synthesis buffer +// Various changes and hacks for use in Mednafen. + +#ifdef __GNUC__ + #define blip_inline inline __attribute__((always_inline)) +#else + #define blip_inline inline +#endif + +#include +#include + +// Blip_Buffer 0.4.1 +#ifndef BLIP_BUFFER_H +#define BLIP_BUFFER_H + +// Internal +typedef int32_t blip_long; +typedef uint32_t blip_ulong; +typedef int64_t blip_s64; +typedef uint64_t blip_u64; + +// Time unit at source clock rate +typedef blip_long blip_time_t; + +// Output samples are 16-bit signed, with a range of -32768 to 32767 +typedef short blip_sample_t; +enum { blip_sample_max = 32767 }; + +class Blip_Buffer { +public: + typedef const char* blargg_err_t; + + // Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults + // to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there + // isn't enough memory, returns error without affecting current buffer setup. + blargg_err_t set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 ); + + // Set number of source time units per second + void clock_rate( long ); + + // End current time frame of specified duration and make its samples available + // (along with any still-unread samples) for reading with read_samples(). Begins + // a new time frame at the end of the current frame. + void end_frame( blip_time_t time ); + + // Read at most 'max_samples' out of buffer into 'dest', removing them from from + // the buffer. Returns number of samples actually read and removed. If stereo is + // true, increments 'dest' one extra time after writing each sample, to allow + // easy interleving of two channels into a stereo output buffer. + long read_samples( blip_sample_t* dest, long max_samples, int stereo = 0 ); + +// Additional optional features + + // Current output sample rate + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // Number of source time units per second + long clock_rate() const; + + // Set frequency high-pass filter frequency, where higher values reduce bass more + void bass_freq( int frequency ); + + // Number of samples delay from synthesis to samples read out + int output_latency() const; + + // Remove all available samples and clear buffer to silence. If 'entire_buffer' is + // false, just clears out any samples waiting rather than the entire buffer. + void clear( int entire_buffer = 1 ); + + // Number of samples available for reading with read_samples() + long samples_avail() const; + + // Remove 'count' samples from those waiting to be read + void remove_samples( long count ); + +// Experimental features + + // Count number of clocks needed until 'count' samples will be available. + // If buffer can't even hold 'count' samples, returns number of clocks until + // buffer becomes full. + blip_time_t count_clocks( long count ) const; + + // Number of raw samples that can be mixed within frame of specified duration. + long count_samples( blip_time_t duration ) const; + + // Mix 'count' samples from 'buf' into buffer. + void mix_samples( blip_sample_t const* buf, long count ); + + // not documented yet + void set_modified() { modified_ = 1; } + int clear_modified() { int b = modified_; modified_ = 0; return b; } + typedef blip_u64 blip_resampled_time_t; + void remove_silence( long count ); + blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; } + blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; } + blip_resampled_time_t clock_rate_factor( long clock_rate ) const; +public: + Blip_Buffer(); + ~Blip_Buffer(); + + // Deprecated + typedef blip_resampled_time_t resampled_time_t; + blargg_err_t sample_rate( long r ) { return set_sample_rate( r ); } + blargg_err_t sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); } +private: + // noncopyable + Blip_Buffer( const Blip_Buffer& ); + Blip_Buffer& operator = ( const Blip_Buffer& ); +public: + typedef blip_time_t buf_t_; + blip_u64 factor_; + blip_resampled_time_t offset_; + buf_t_* buffer_; + blip_long buffer_size_; + blip_long reader_accum_; + int bass_shift_; +private: + long sample_rate_; + long clock_rate_; + int bass_freq_; + int length_; + int modified_; + friend class Blip_Reader; +}; + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#define BLIP_BUFFER_ACCURACY 32 +#define BLIP_PHASE_BITS 8 + +// Number of bits in resample ratio fraction. Higher values give a more accurate ratio +// but reduce maximum buffer size. +//#ifndef BLIP_BUFFER_ACCURACY +// #define BLIP_BUFFER_ACCURACY 16 +//#endif + +// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in +// noticeable broadband noise when synthesizing high frequency square waves. +// Affects size of Blip_Synth objects since they store the waveform directly. +//#ifndef BLIP_PHASE_BITS +// #if BLIP_BUFFER_FAST +// #define BLIP_PHASE_BITS 8 +// #else +// #define BLIP_PHASE_BITS 6 +// #endif +//#endif + + // Internal + typedef blip_u64 blip_resampled_time_t; + int const blip_widest_impulse_ = 16; + int const blip_buffer_extra_ = blip_widest_impulse_ + 2; + int const blip_res = 1 << BLIP_PHASE_BITS; + class blip_eq_t; + + class Blip_Synth_Fast_ { + public: + Blip_Buffer* buf; + int last_amp; + int delta_factor; + + void volume_unit( double ); + Blip_Synth_Fast_(); + void treble_eq( blip_eq_t const& ) { } + }; + + class Blip_Synth_ { + public: + Blip_Buffer* buf; + int last_amp; + int delta_factor; + + void volume_unit( double ); + Blip_Synth_( short* impulses, int width ); + void treble_eq( blip_eq_t const& ); + private: + double volume_unit_; + short* const impulses; + int const width; + blip_long kernel_unit; + int impulses_size() const { return blip_res / 2 * width + 1; } + void adjust_impulse(); + }; + +// Quality level. Start with blip_good_quality. +const int blip_med_quality = 8; +const int blip_good_quality = 12; +const int blip_high_quality = 16; + +// Range specifies the greatest expected change in amplitude. Calculate it +// by finding the difference between the maximum and minimum expected +// amplitudes (max - min). +template +class Blip_Synth { +public: + // Set overall volume of waveform + void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); } + + // Configure low-pass filter (see blip_buffer.txt) + void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); } + + // Get/set Blip_Buffer used for output + Blip_Buffer* output() const { return impl.buf; } + void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; } + + // Update amplitude of waveform at given time. Using this requires a separate + // Blip_Synth for each waveform. + void update( blip_time_t time, int amplitude ); + +// Low-level interface + + // Add an amplitude transition of specified delta, optionally into specified buffer + // rather than the one set with output(). Delta can be positive or negative. + // The actual change in amplitude is delta * (volume / range) + void offset( blip_time_t, int delta, Blip_Buffer* ) const; + void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); } + + // Works directly in terms of fractional output samples. Contact author for more info. + void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; + + // Same as offset(), except code is inlined for higher performance + void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const { + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); + } + void offset_inline( blip_time_t t, int delta ) const { + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); + } + +private: +#if BLIP_BUFFER_FAST + Blip_Synth_Fast_ impl; +#else + Blip_Synth_ impl; + typedef short imp_t; + imp_t impulses [blip_res * (quality / 2) + 1]; +public: + Blip_Synth() : impl( impulses, quality ) { } +#endif +}; + +// Low-pass equalization parameters +class blip_eq_t { +public: + // Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce + // treble, small positive values (0 to 5.0) increase treble. + blip_eq_t( double treble_db = 0 ); + + // See blip_buffer.txt + blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 ); + +private: + double treble; + long rolloff_freq; + long sample_rate; + long cutoff_freq; + void generate( float* out, int count ) const; + friend class Blip_Synth_; +}; + +int const blip_sample_bits = 30; + +// Dummy Blip_Buffer to direct sound output to, for easy muting without +// having to stop sound code. +class Silent_Blip_Buffer : public Blip_Buffer { + buf_t_ buf [blip_buffer_extra_ + 1]; +public: + // The following cannot be used (an assertion will fail if attempted): + blargg_err_t set_sample_rate( long samples_per_sec, int msec_length ); + blip_time_t count_clocks( long count ) const; + void mix_samples( blip_sample_t const* buf, long count ); + + Silent_Blip_Buffer(); +}; + + #if defined (__GNUC__) || _MSC_VER >= 1100 + #define BLIP_RESTRICT __restrict + #else + #define BLIP_RESTRICT + #endif + +// Optimized reading from Blip_Buffer, for use in custom sample output + +// Begin reading from buffer. Name should be unique to the current block. +#define BLIP_READER_BEGIN( name, blip_buffer ) \ + const Blip_Buffer::buf_t_* BLIP_RESTRICT name##_reader_buf = (blip_buffer).buffer_;\ + blip_long name##_reader_accum = (blip_buffer).reader_accum_ + +// Get value to pass to BLIP_READER_NEXT() +#define BLIP_READER_BASS( blip_buffer ) ((blip_buffer).bass_shift_) + +// Constant value to use instead of BLIP_READER_BASS(), for slightly more optimal +// code at the cost of having no bass control +int const blip_reader_default_bass = 9; + +// Current sample +#define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16)) + +// Current raw sample in full internal resolution +#define BLIP_READER_READ_RAW( name ) (name##_reader_accum) + +// Advance to next sample +#define BLIP_READER_NEXT( name, bass ) \ + (void) (name##_reader_accum += *name##_reader_buf++ - (name##_reader_accum >> (bass))) + +// End reading samples from buffer. The number of samples read must now be removed +// using Blip_Buffer::remove_samples(). +#define BLIP_READER_END( name, blip_buffer ) \ + (void) ((blip_buffer).reader_accum_ = name##_reader_accum) + + +// Compatibility with older version +const long blip_unscaled = 65535; +const int blip_low_quality = blip_med_quality; +const int blip_best_quality = blip_high_quality; + +// Deprecated; use BLIP_READER macros as follows: +// Blip_Reader r; r.begin( buf ); -> BLIP_READER_BEGIN( r, buf ); +// int bass = r.begin( buf ) -> BLIP_READER_BEGIN( r, buf ); int bass = BLIP_READER_BASS( buf ); +// r.read() -> BLIP_READER_READ( r ) +// r.read_raw() -> BLIP_READER_READ_RAW( r ) +// r.next( bass ) -> BLIP_READER_NEXT( r, bass ) +// r.next() -> BLIP_READER_NEXT( r, blip_reader_default_bass ) +// r.end( buf ) -> BLIP_READER_END( r, buf ) +class Blip_Reader { +public: + int begin( Blip_Buffer& ); + blip_long read() const { return accum >> (blip_sample_bits - 16); } + blip_long read_raw() const { return accum; } + void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); } + void end( Blip_Buffer& b ) { b.reader_accum_ = accum; } + +private: + const Blip_Buffer::buf_t_* buf; + blip_long accum; +}; + +// End of public interface + +#include + +template +blip_inline void Blip_Synth::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const +{ + // Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the + // need for a longer buffer as set by set_sample_rate(). + assert( (blip_long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ ); + delta *= impl.delta_factor; + blip_long* BLIP_RESTRICT buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); + int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1)); + +#if BLIP_BUFFER_FAST + blip_long left = buf [0] + delta; + + // Kind of crappy, but doing shift after multiply results in overflow. + // Alternate way of delaying multiply by delta_factor results in worse + // sub-sample resolution. + blip_long right = (delta >> BLIP_PHASE_BITS) * phase; + left -= right; + right += buf [1]; + + buf [0] = left; + buf [1] = right; +#else + + int const fwd = (blip_widest_impulse_ - quality) / 2; + int const rev = fwd + quality - 2; + int const mid = quality / 2 - 1; + + imp_t const* BLIP_RESTRICT imp = impulses + blip_res - phase; + + #if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + + // straight forward implementation resulted in better code on GCC for x86 + + #define ADD_IMP( out, in ) \ + buf [out] += (blip_long) imp [blip_res * (in)] * delta + + #define BLIP_FWD( i ) {\ + ADD_IMP( fwd + i, i );\ + ADD_IMP( fwd + 1 + i, i + 1 );\ + } + #define BLIP_REV( r ) {\ + ADD_IMP( rev - r, r + 1 );\ + ADD_IMP( rev + 1 - r, r );\ + } + + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + ADD_IMP( fwd + mid - 1, mid - 1 ); + ADD_IMP( fwd + mid , mid ); + imp = impulses + phase; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + ADD_IMP( rev , 1 ); + ADD_IMP( rev + 1, 0 ); + + #else + + // for RISC processors, help compiler by reading ahead of writes + + #define BLIP_FWD( i ) {\ + blip_long t0 = i0 * delta + buf [fwd + i];\ + blip_long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i];\ + i0 = imp [blip_res * (i + 2)];\ + buf [fwd + i] = t0;\ + buf [fwd + 1 + i] = t1;\ + } + #define BLIP_REV( r ) {\ + blip_long t0 = i0 * delta + buf [rev - r];\ + blip_long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r];\ + i0 = imp [blip_res * (r - 1)];\ + buf [rev - r] = t0;\ + buf [rev + 1 - r] = t1;\ + } + + blip_long i0 = *imp; + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + blip_long t0 = i0 * delta + buf [fwd + mid - 1]; + blip_long t1 = imp [blip_res * mid] * delta + buf [fwd + mid ]; + imp = impulses + phase; + i0 = imp [blip_res * mid]; + buf [fwd + mid - 1] = t0; + buf [fwd + mid ] = t1; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + blip_long t0 = i0 * delta + buf [rev ]; + blip_long t1 = *imp * delta + buf [rev + 1]; + buf [rev ] = t0; + buf [rev + 1] = t1; + #endif + +#endif +} + +#undef BLIP_FWD +#undef BLIP_REV + +template +#if BLIP_BUFFER_FAST + blip_inline +#endif +void Blip_Synth::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const +{ + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); +} + +template +#if BLIP_BUFFER_FAST + blip_inline +#endif +void Blip_Synth::update( blip_time_t t, int amp ) +{ + int delta = amp - impl.last_amp; + impl.last_amp = amp; + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); +} + +blip_inline blip_eq_t::blip_eq_t( double t ) : + treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { } +blip_inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) : + treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { } + +blip_inline int Blip_Buffer::length() const { return length_; } +blip_inline long Blip_Buffer::samples_avail() const { return (long) (offset_ >> BLIP_BUFFER_ACCURACY); } +blip_inline long Blip_Buffer::sample_rate() const { return sample_rate_; } +blip_inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; } +blip_inline long Blip_Buffer::clock_rate() const { return clock_rate_; } +blip_inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); } + +blip_inline int Blip_Reader::begin( Blip_Buffer& blip_buf ) +{ + buf = blip_buf.buffer_; + accum = blip_buf.reader_accum_; + return blip_buf.bass_shift_; +} + +int const blip_max_length = 0; +int const blip_default_length = 250; + +#endif diff --git a/waterbox/vb/endian.h b/waterbox/vb/endian.h new file mode 100644 index 0000000000..e78a0c577f --- /dev/null +++ b/waterbox/vb/endian.h @@ -0,0 +1,494 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* endian.h: +** Copyright (C) 2006-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef __MDFN_ENDIAN_H +#define __MDFN_ENDIAN_H + +void Endian_A16_Swap(void *src, uint32 nelements); +void Endian_A32_Swap(void *src, uint32 nelements); +void Endian_A64_Swap(void *src, uint32 nelements); + +void Endian_A16_NE_LE(void *src, uint32 nelements); +void Endian_A32_NE_LE(void *src, uint32 nelements); +void Endian_A64_NE_LE(void *src, uint32 nelements); + +void Endian_A16_NE_BE(void *src, uint32 nelements); +void Endian_A32_NE_BE(void *src, uint32 nelements); +void Endian_A64_NE_BE(void *src, uint32 nelements); + +void Endian_V_NE_LE(void* p, size_t len); +void Endian_V_NE_BE(void* p, size_t len); + +// +// +// + +static INLINE uint32 BitsExtract(const uint8* ptr, const size_t bit_offset, const size_t bit_count) +{ + uint32 ret = 0; + + for(size_t x = 0; x < bit_count; x++) + { + size_t co = bit_offset + x; + bool b = (ptr[co >> 3] >> (co & 7)) & 1; + + ret |= (uint64)b << x; + } + + return ret; +} + +static INLINE void BitsIntract(uint8* ptr, const size_t bit_offset, const size_t bit_count, uint32 value) +{ + for(size_t x = 0; x < bit_count; x++) + { + size_t co = bit_offset + x; + bool b = (value >> x) & 1; + uint8 tmp = ptr[co >> 3]; + + tmp &= ~(1 << (co & 7)); + tmp |= b << (co & 7); + + ptr[co >> 3] = tmp; + } +} + +/* + Regarding safety of calling MDFN_*sb on dynamically-allocated memory with new uint8[], see C++ standard 3.7.3.1(i.e. it should be + safe provided the offsets into the memory are aligned/multiples of the MDFN_*sb access type). malloc()'d and calloc()'d + memory should be safe as well. + + Statically-allocated arrays/memory should be unioned with a big POD type or C++11 "alignas"'d. (May need to audit code to ensure + this is being done). +*/ + +static INLINE uint16 MDFN_bswap16(uint16 v) +{ + return (v << 8) | (v >> 8); +} + +static INLINE uint32 MDFN_bswap32(uint32 v) +{ + return (v << 24) | ((v & 0xFF00) << 8) | ((v >> 8) & 0xFF00) | (v >> 24); +} + +static INLINE uint64 MDFN_bswap64(uint64 v) +{ + return (v << 56) | (v >> 56) | ((v & 0xFF00) << 40) | ((v >> 40) & 0xFF00) | ((uint64)MDFN_bswap32(v >> 16) << 16); +} + +#ifdef LSB_FIRST + #define MDFN_ENDIANH_IS_BIGENDIAN 0 +#else + #define MDFN_ENDIANH_IS_BIGENDIAN 1 +#endif + +// +// X endian. +// +template +static INLINE T MDFN_deXsb(const void* ptr) +{ + T tmp; + + memcpy(&tmp, MDFN_ASSUME_ALIGNED(ptr, (aligned ? sizeof(T) : 1)), sizeof(T)); + + if(isbigendian != -1 && isbigendian != MDFN_ENDIANH_IS_BIGENDIAN) + { + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, "Gummy penguins."); + + if(sizeof(T) == 8) + return MDFN_bswap64(tmp); + else if(sizeof(T) == 4) + return MDFN_bswap32(tmp); + else if(sizeof(T) == 2) + return MDFN_bswap16(tmp); + } + + return tmp; +} + +// +// Native endian. +// +template +static INLINE T MDFN_densb(const void* ptr) +{ + return MDFN_deXsb<-1, T, aligned>(ptr); +} + +// +// Little endian. +// +template +static INLINE T MDFN_delsb(const void* ptr) +{ + return MDFN_deXsb<0, T, aligned>(ptr); +} + +template +static INLINE uint16 MDFN_de16lsb(const void* ptr) +{ + return MDFN_delsb(ptr); +} + +static INLINE uint32 MDFN_de24lsb(const void* ptr) +{ + const uint8* ptr_u8 = (const uint8*)ptr; + + return (ptr_u8[0] << 0) | (ptr_u8[1] << 8) | (ptr_u8[2] << 16); +} + +template +static INLINE uint32 MDFN_de32lsb(const void* ptr) +{ + return MDFN_delsb(ptr); +} + +template +static INLINE uint64 MDFN_de64lsb(const void* ptr) +{ + return MDFN_delsb(ptr); +} + +// +// Big endian. +// +template +static INLINE T MDFN_demsb(const void* ptr) +{ + return MDFN_deXsb<1, T, aligned>(ptr); +} + +template +static INLINE uint16 MDFN_de16msb(const void* ptr) +{ + return MDFN_demsb(ptr); +} + +static INLINE uint32 MDFN_de24msb(const void* ptr) +{ + const uint8* ptr_u8 = (const uint8*)ptr; + + return (ptr_u8[0] << 16) | (ptr_u8[1] << 8) | (ptr_u8[2] << 0); +} + +template +static INLINE uint32 MDFN_de32msb(const void* ptr) +{ + return MDFN_demsb(ptr); +} + +template +static INLINE uint64 MDFN_de64msb(const void* ptr) +{ + return MDFN_demsb(ptr); +} + +// +// +// +// +// +// +// +// + +// +// X endian. +// +template +static INLINE void MDFN_enXsb(void* ptr, T value) +{ + T tmp = value; + + if(isbigendian != -1 && isbigendian != MDFN_ENDIANH_IS_BIGENDIAN) + { + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, "Gummy penguins."); + + if(sizeof(T) == 8) + tmp = MDFN_bswap64(value); + else if(sizeof(T) == 4) + tmp = MDFN_bswap32(value); + else if(sizeof(T) == 2) + tmp = MDFN_bswap16(value); + } + + memcpy(MDFN_ASSUME_ALIGNED(ptr, (aligned ? sizeof(T) : 1)), &tmp, sizeof(T)); +} + +// +// Native endian. +// +template +static INLINE void MDFN_ennsb(void* ptr, T value) +{ + MDFN_enXsb<-1, T, aligned>(ptr, value); +} + +// +// Little endian. +// +template +static INLINE void MDFN_enlsb(void* ptr, T value) +{ + MDFN_enXsb<0, T, aligned>(ptr, value); +} + +template +static INLINE void MDFN_en16lsb(void* ptr, uint16 value) +{ + MDFN_enlsb(ptr, value); +} + +static INLINE void MDFN_en24lsb(void* ptr, uint32 value) +{ + uint8* ptr_u8 = (uint8*)ptr; + + ptr_u8[0] = value >> 0; + ptr_u8[1] = value >> 8; + ptr_u8[2] = value >> 16; +} + +template +static INLINE void MDFN_en32lsb(void* ptr, uint32 value) +{ + MDFN_enlsb(ptr, value); +} + +template +static INLINE void MDFN_en64lsb(void* ptr, uint64 value) +{ + MDFN_enlsb(ptr, value); +} + + +// +// Big endian. +// +template +static INLINE void MDFN_enmsb(void* ptr, T value) +{ + MDFN_enXsb<1, T, aligned>(ptr, value); +} + +template +static INLINE void MDFN_en16msb(void* ptr, uint16 value) +{ + MDFN_enmsb(ptr, value); +} + +static INLINE void MDFN_en24msb(void* ptr, uint32 value) +{ + uint8* ptr_u8 = (uint8*)ptr; + + ptr_u8[0] = value >> 16; + ptr_u8[1] = value >> 8; + ptr_u8[2] = value >> 0; +} + +template +static INLINE void MDFN_en32msb(void* ptr, uint32 value) +{ + MDFN_enmsb(ptr, value); +} + +template +static INLINE void MDFN_en64msb(void* ptr, uint64 value) +{ + MDFN_enmsb(ptr, value); +} + + +// +// +// +// +// +// + +template +static INLINE uint8* ne16_ptr_be(BT* const base, const size_t byte_offset) +{ +#ifdef MSB_FIRST + return (uint8*)base + (byte_offset &~ (sizeof(T) - 1)); +#else + return (uint8*)base + (((byte_offset &~ (sizeof(T) - 1)) ^ (2 - std::min(2, sizeof(T))))); +#endif +} + +template +static INLINE void ne16_wbo_be(uint16* const base, const size_t byte_offset, const T value) +{ + uint8* const ptr = ne16_ptr_be(base, byte_offset); + + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "Unsupported type size"); + + if(sizeof(T) == 4) + { + uint16* const ptr16 = (uint16*)ptr; + + ptr16[0] = value >> 16; + ptr16[1] = value; + } + else + *(T*)ptr = value; +} + +template +static INLINE T ne16_rbo_be(const uint16* const base, const size_t byte_offset) +{ + uint8* const ptr = ne16_ptr_be(base, byte_offset); + + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "Unsupported type size"); + + if(sizeof(T) == 4) + { + uint16* const ptr16 = (uint16*)ptr; + T tmp; + + tmp = ptr16[0] << 16; + tmp |= ptr16[1]; + + return tmp; + } + else + return *(T*)ptr; +} + +template +static INLINE void ne16_rwbo_be(uint16* const base, const size_t byte_offset, T* value) +{ + if(IsWrite) + ne16_wbo_be(base, byte_offset, *value); + else + *value = ne16_rbo_be(base, byte_offset); +} + +// +// +// + +template +static INLINE uint8* ne16_ptr_le(BT* const base, const size_t byte_offset) +{ +#ifdef LSB_FIRST + return (uint8*)base + (byte_offset &~ (sizeof(T) - 1)); +#else + return (uint8*)base + (((byte_offset &~ (sizeof(T) - 1)) ^ (2 - std::min(2, sizeof(T))))); +#endif +} + +template +static INLINE void ne16_wbo_le(uint16* const base, const size_t byte_offset, const T value) +{ + uint8* const ptr = ne16_ptr_le(base, byte_offset); + + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "Unsupported type size"); + + if(sizeof(T) == 4) + { + uint16* const ptr16 = (uint16*)ptr; + + ptr16[0] = value; + ptr16[1] = value >> 16; + } + else + *(T*)ptr = value; +} + +template +static INLINE T ne16_rbo_le(const uint16* const base, const size_t byte_offset) +{ + uint8* const ptr = ne16_ptr_le(base, byte_offset); + + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "Unsupported type size"); + + if(sizeof(T) == 4) + { + uint16* const ptr16 = (uint16*)ptr; + T tmp; + + tmp = ptr16[0]; + tmp |= ptr16[1] << 16; + + return tmp; + } + else + return *(T*)ptr; +} + + +template +static INLINE void ne16_rwbo_le(uint16* const base, const size_t byte_offset, T* value) +{ + if(IsWrite) + ne16_wbo_le(base, byte_offset, *value); + else + *value = ne16_rbo_le(base, byte_offset); +} + +// +// +// +template +static INLINE uint8* ne64_ptr_be(uint64* const base, const size_t byte_offset) +{ +#ifdef MSB_FIRST + return (uint8*)base + (byte_offset &~ (sizeof(T) - 1)); +#else + return (uint8*)base + (((byte_offset &~ (sizeof(T) - 1)) ^ (8 - sizeof(T)))); +#endif +} + +template +static INLINE void ne64_wbo_be(uint64* const base, const size_t byte_offset, const T value) +{ + uint8* const ptr = ne64_ptr_be(base, byte_offset); + + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, "Unsupported type size"); + + memcpy(MDFN_ASSUME_ALIGNED(ptr, sizeof(T)), &value, sizeof(T)); +} + +template +static INLINE T ne64_rbo_be(uint64* const base, const size_t byte_offset) +{ + uint8* const ptr = ne64_ptr_be(base, byte_offset); + T ret; + + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "Unsupported type size"); + + memcpy(&ret, MDFN_ASSUME_ALIGNED(ptr, sizeof(T)), sizeof(T)); + + return ret; +} + +template +static INLINE void ne64_rwbo_be(uint64* const base, const size_t byte_offset, T* value) +{ + if(IsWrite) + ne64_wbo_be(base, byte_offset, *value); + else + *value = ne64_rbo_be(base, byte_offset); +} + +#endif diff --git a/waterbox/vb/git.h b/waterbox/vb/git.h new file mode 100644 index 0000000000..edcd8a8415 --- /dev/null +++ b/waterbox/vb/git.h @@ -0,0 +1,127 @@ +#pragma once + +struct MDFN_Surface +{ + uint32 *pixels; + int pitch32; +}; + +struct MDFN_Rect +{ + int x, y, w, h; +}; + +struct EmulateSpecStruct +{ + // Pitch(32-bit) must be equal to width and >= the "fb_width" specified in the MDFNGI struct for the emulated system. + // Height must be >= to the "fb_height" specified in the MDFNGI struct for the emulated system. + // The framebuffer pointed to by surface->pixels is written to by the system emulation code. + uint32 *pixels; + + // Pointer to sound buffer, set by the driver code, that the emulation code should render sound to. + // Guaranteed to be at least 500ms in length, but emulation code really shouldn't exceed 40ms or so. Additionally, if emulation code + // generates >= 100ms, + // DEPRECATED: Emulation code may set this pointer to a sound buffer internal to the emulation module. + int16 *SoundBuf; + + // Number of cycles that this frame consumed, using MDFNGI::MasterClock as a time base. + // Set by emulation code. + int64 MasterCycles; + + // Set by the system emulation code every frame, to denote the horizontal and vertical offsets of the image, and the size + // of the image. If the emulated system sets the elements of LineWidths, then the width(w) of this structure + // is ignored while drawing the image. + MDFN_Rect DisplayRect; + + // Maximum size of the sound buffer, in frames. Set by the driver code. + int32 SoundBufMaxSize; + + // Number of frames currently in internal sound buffer. Set by the system emulation code, to be read by the driver code. + int32 SoundBufSize; + + // 0 UDLR SelectStartBA UDLR(right dpad) LtrigRtrig 13 + int32 Buttons; +}; + +/*typedef struct +{ + + void (*Emulate)(EmulateSpecStruct *espec); + void (*TransformInput)(void); // Called before Emulate, and within MDFN_MidSync(), to implement stuff like setting-controlled PC Engine SEL+RUN button exclusion in a way + // that won't cause desyncs with movies and netplay. + + void (*SetInput)(unsigned port, const char *type, uint8* data); + bool (*SetMedia)(uint32 drive_idx, uint32 state_idx, uint32 media_idx, uint32 orientation_idx); + + + // Called when netplay starts, or the controllers controlled by local players changes during + // an existing netplay session. Called with ~(uint64)0 when netplay ends. + // (For future use in implementing portable console netplay) + void (*NPControlNotif)(uint64 c); + + const MDFNSetting *Settings; + + // Time base for EmulateSpecStruct::MasterCycles + // MasterClock must be >= MDFN_MASTERCLOCK_FIXED(1.0) + // All or part of the fractional component may be ignored in some timekeeping operations in the emulator to prevent integer overflow, + // so it is unwise to have a fractional component when the integral component is very small(less than say, 10000). + #define MDFN_MASTERCLOCK_FIXED(n) ((int64)((double)(n) * (1LL << 32))) + int64 MasterClock; + + // Nominal frames per second * 65536 * 256, truncated. + // May be deprecated in the future due to many systems having slight frame rate programmability. + uint32 fps; + + // multires is a hint that, if set, indicates that the system has fairly programmable video modes(particularly, the ability + // to display multiple horizontal resolutions, such as the PCE, PC-FX, or Genesis). In practice, it will cause the driver + // code to set the linear interpolation on by default. + // + // lcm_width and lcm_height are the least common multiples of all possible + // resolutions in the frame buffer as specified by DisplayRect/LineWidths(Ex for PCE: widths of 256, 341.333333, 512, + // lcm = 1024) + // + // nominal_width and nominal_height specify the resolution that Mednafen should display + // the framebuffer image in at 1x scaling, scaled from the dimensions of DisplayRect, and optionally the LineWidths array + // passed through espec to the Emulate() function. + // + bool multires; + + int lcm_width; + int lcm_height; + + + int nominal_width; + int nominal_height; + + int fb_width; // Width of the framebuffer(not necessarily width of the image). MDFN_Surface width should be >= this. + int fb_height; // Height of the framebuffer passed to the Emulate() function(not necessarily height of the image) + + int soundchan; // Number of output sound channels. Only values of 1 and 2 are currently supported. + + + int rotated; + + std::string name; // Game name, UTF-8 encoding + uint8 MD5[16]; + uint8 GameSetMD5[16]; // A unique ID for the game set this CD belongs to, only used in PC-FX emulation. + bool GameSetMD5Valid; // True if GameSetMD5 is valid. + + VideoSystems VideoSystem; + GameMediumTypes GameType; // Deprecated. + + RMD_Layout* RMD; + + const char *cspecial; // Special cart expansion: DIP switches, barcode reader, etc. + + std::vectorDesiredInput; // Desired input device for the input ports, NULL for don't care + + // For mouse relative motion. + double mouse_sensitivity; + + + // + // For absolute coordinates(IDIT_X_AXIS and IDIT_Y_AXIS), usually mapped to a mouse(hence the naming). + // + float mouse_scale_x, mouse_scale_y; + float mouse_offs_x, mouse_offs_y; +} MDFNGI;*/ diff --git a/waterbox/vb/input.cpp b/waterbox/vb/input.cpp new file mode 100644 index 0000000000..b895f9a5d9 --- /dev/null +++ b/waterbox/vb/input.cpp @@ -0,0 +1,196 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* input.cpp: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "vb.h" +#include "input.h" + +namespace MDFN_IEN_VB +{ +static bool InstantReadHack; + +static bool IntPending; + +static uint16 PadData; +static uint16 PadLatched; + +static uint8 SCR; +static uint16 SDR; + +#define SCR_S_ABT_DIS 0x01 +#define SCR_SI_STAT 0x02 +#define SCR_HW_SI 0x04 +#define SCR_SOFT_CLK 0x10 + +#define SCR_PARA_SI 0x20 +#define SCR_K_INT_INH 0x80 + +static uint32 ReadBitPos; +static int32 ReadCounter; + +static v810_timestamp_t last_ts; + +void VBINPUT_Init(void) +{ + InstantReadHack = true; +} + +void VBINPUT_SetInstantReadHack(bool enabled) +{ + InstantReadHack = enabled; +} + +uint8 VBINPUT_Read(v810_timestamp_t ×tamp, uint32 A) +{ + uint8 ret = 0; + + VBINPUT_Update(timestamp); + + //if(((A & 0xFF) == 0x10 || (A & 0xFF) == 0x14)) + // printf("Read %d\n", timestamp); + + //if(((A & 0xFF) == 0x10 || (A & 0xFF) == 0x14) && ReadCounter > 0) + //{ + // printf("Input port read during hardware transfer: %08x %d\n", A, timestamp); + //} + + switch (A & 0xFF) + { + case 0x10: + if (InstantReadHack) + ret = PadData; + else + ret = SDR & 0xFF; + break; + + case 0x14: + if (InstantReadHack) + ret = PadData >> 8; + else + ret = SDR >> 8; + break; + + case 0x28: + ret = SCR | (0x40 | 0x08 | SCR_HW_SI); + if (ReadCounter > 0) + ret |= SCR_SI_STAT; + break; + } + + // printf("Input Read: %08x %02x\n", A, ret); + VB_SetEvent(VB_EVENT_INPUT, (ReadCounter > 0) ? (timestamp + ReadCounter) : VB_EVENT_NONONO); + + return (ret); +} + +void VBINPUT_Write(v810_timestamp_t ×tamp, uint32 A, uint8 V) +{ + VBINPUT_Update(timestamp); + + //printf("Input write: %d, %08x %02x\n", timestamp, A, V); + switch (A & 0xFF) + { + case 0x28: + if ((V & SCR_HW_SI) && !(SCR & SCR_S_ABT_DIS) && ReadCounter <= 0) + { + //printf("Start Read: %d\n", timestamp); + PadLatched = PadData; + ReadBitPos = 0; + ReadCounter = 640; + } + + if (V & SCR_S_ABT_DIS) + { + ReadCounter = 0; + ReadBitPos = 0; + } + + if (V & SCR_K_INT_INH) + { + IntPending = false; + VBIRQ_Assert(VBIRQ_SOURCE_INPUT, IntPending); + } + + SCR = V & (0x80 | 0x20 | 0x10 | 1); + break; + } + + VB_SetEvent(VB_EVENT_INPUT, (ReadCounter > 0) ? (timestamp + ReadCounter) : VB_EVENT_NONONO); +} + +void VBINPUT_Frame(const void* ptr) +{ + PadData = (MDFN_de16lsb(ptr) << 2) | 0x2; +} + +v810_timestamp_t VBINPUT_Update(const v810_timestamp_t timestamp) +{ + int32 clocks = timestamp - last_ts; + + if (ReadCounter > 0) + { + ReadCounter -= clocks; + + while (ReadCounter <= 0) + { + SDR &= ~(1 << ReadBitPos); + SDR |= PadLatched & (1 << ReadBitPos); + + ReadBitPos++; + if (ReadBitPos < 16) + ReadCounter += 640; + else + { + //printf("Read End: %d\n", timestamp); + if (!(SCR & SCR_K_INT_INH)) + { + //printf("Input IRQ: %d\n", timestamp); + IntPending = true; + VBIRQ_Assert(VBIRQ_SOURCE_INPUT, IntPending); + } + break; + } + } + } + + last_ts = timestamp; + + return ((ReadCounter > 0) ? (timestamp + ReadCounter) : VB_EVENT_NONONO); +} + +void VBINPUT_ResetTS(void) +{ + last_ts = 0; +} + +void VBINPUT_Power(void) +{ + last_ts = 0; + PadData = 0; + PadLatched = 0; + SDR = 0; + SCR = 0; + ReadBitPos = 0; + ReadCounter = 0; + IntPending = false; + + VBIRQ_Assert(VBIRQ_SOURCE_INPUT, 0); +} +} diff --git a/waterbox/vb/input.h b/waterbox/vb/input.h new file mode 100644 index 0000000000..1ee57c19d7 --- /dev/null +++ b/waterbox/vb/input.h @@ -0,0 +1,45 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* input.h: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef __VB_INPUT_H +#define __VB_INPUT_H + +namespace MDFN_IEN_VB +{ + +void VBINPUT_Init(void) MDFN_COLD; +void VBINPUT_SetInstantReadHack(bool); + +void VBINPUT_SetInput(unsigned port, const char *type, uint8 *ptr); + +uint8 VBINPUT_Read(v810_timestamp_t ×tamp, uint32 A); + +void VBINPUT_Write(v810_timestamp_t ×tamp, uint32 A, uint8 V); + +void VBINPUT_Frame(const void* ptr); + +int32 VBINPUT_Update(const int32 timestamp); +void VBINPUT_ResetTS(void); + + +void VBINPUT_Power(void); +} +#endif diff --git a/waterbox/vb/math_ops.h b/waterbox/vb/math_ops.h new file mode 100644 index 0000000000..4154f2d49b --- /dev/null +++ b/waterbox/vb/math_ops.h @@ -0,0 +1,278 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* math_ops.h: +** Copyright (C) 2007-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/* +** Some ideas from: +** blargg +** http://graphics.stanford.edu/~seander/bithacks.html +*/ + +#ifndef __MDFN_MATH_OPS_H +#define __MDFN_MATH_OPS_H + +#if defined(_MSC_VER) + #include +#endif + +static INLINE unsigned MDFN_lzcount16_0UD(uint16 v) +{ + #if defined(__GNUC__) || defined(__clang__) || defined(__ICC) || defined(__INTEL_COMPILER) + return 15 ^ 31 ^ __builtin_clz(v); + #elif defined(_MSC_VER) + unsigned long idx; + + _BitScanReverse(&idx, v); + + return 15 ^ idx; + #else + unsigned ret = 0; + unsigned tmp; + + tmp = !(v & 0xFF00) << 3; v <<= tmp; ret += tmp; + tmp = !(v & 0xF000) << 2; v <<= tmp; ret += tmp; + tmp = !(v & 0xC000) << 1; v <<= tmp; ret += tmp; + tmp = !(v & 0x8000) << 0; ret += tmp; + + return(ret); + #endif +} + +static INLINE unsigned MDFN_lzcount32_0UD(uint32 v) +{ + #if defined(__GNUC__) || defined(__clang__) || defined(__ICC) || defined(__INTEL_COMPILER) + return __builtin_clz(v); + #elif defined(_MSC_VER) + unsigned long idx; + + _BitScanReverse(&idx, v); + + return 31 ^ idx; + #else + unsigned ret = 0; + unsigned tmp; + + tmp = !(v & 0xFFFF0000) << 4; v <<= tmp; ret += tmp; + tmp = !(v & 0xFF000000) << 3; v <<= tmp; ret += tmp; + tmp = !(v & 0xF0000000) << 2; v <<= tmp; ret += tmp; + tmp = !(v & 0xC0000000) << 1; v <<= tmp; ret += tmp; + tmp = !(v & 0x80000000) << 0; ret += tmp; + + return(ret); + #endif +} + +static INLINE unsigned MDFN_lzcount64_0UD(uint64 v) +{ + #if defined(__GNUC__) || defined(__clang__) || defined(__ICC) || defined(__INTEL_COMPILER) + return __builtin_clzll(v); + #elif defined(_MSC_VER) + #if defined(_WIN64) + unsigned long idx; + _BitScanReverse64(&idx, v); + return 63 ^ idx; + #else + unsigned long idx0; + unsigned long idx1; + + _BitScanReverse(&idx1, v >> 0); + idx1 -= 32; + if(!_BitScanReverse(&idx0, v >> 32)) + idx0 = idx1; + + idx0 += 32; + + return 63 ^ idx0; + #endif + #else + unsigned ret = 0; + unsigned tmp; + + tmp = !(v & 0xFFFFFFFF00000000ULL) << 5; v <<= tmp; ret += tmp; + tmp = !(v & 0xFFFF000000000000ULL) << 4; v <<= tmp; ret += tmp; + tmp = !(v & 0xFF00000000000000ULL) << 3; v <<= tmp; ret += tmp; + tmp = !(v & 0xF000000000000000ULL) << 2; v <<= tmp; ret += tmp; + tmp = !(v & 0xC000000000000000ULL) << 1; v <<= tmp; ret += tmp; + tmp = !(v & 0x8000000000000000ULL) << 0; ret += tmp; + + return(ret); + #endif +} + +static INLINE unsigned MDFN_tzcount16_0UD(uint16 v) +{ + #if defined(__GNUC__) || defined(__clang__) || defined(__ICC) || defined(__INTEL_COMPILER) + return __builtin_ctz(v); + #elif defined(_MSC_VER) + unsigned long idx; + + _BitScanForward(&idx, v); + + return idx; + #else + unsigned ret = 0; + unsigned tmp; + + tmp = !( (uint8)v) << 3; v >>= tmp; ret += tmp; + tmp = !(v & 0x000F) << 2; v >>= tmp; ret += tmp; + tmp = !(v & 0x0003) << 1; v >>= tmp; ret += tmp; + tmp = !(v & 0x0001) << 0; ret += tmp; + + return ret; + #endif +} + +static INLINE unsigned MDFN_tzcount32_0UD(uint32 v) +{ + #if defined(__GNUC__) || defined(__clang__) || defined(__ICC) || defined(__INTEL_COMPILER) + return __builtin_ctz(v); + #elif defined(_MSC_VER) + unsigned long idx; + + _BitScanForward(&idx, v); + + return idx; + #else + unsigned ret = 0; + unsigned tmp; + + tmp = !((uint16)v) << 4; v >>= tmp; ret += tmp; + tmp = !( (uint8)v) << 3; v >>= tmp; ret += tmp; + tmp = !(v & 0x000F) << 2; v >>= tmp; ret += tmp; + tmp = !(v & 0x0003) << 1; v >>= tmp; ret += tmp; + tmp = !(v & 0x0001) << 0; ret += tmp; + + return ret; + #endif +} + +static INLINE unsigned MDFN_tzcount64_0UD(uint64 v) +{ + #if defined(__GNUC__) || defined(__clang__) || defined(__ICC) || defined(__INTEL_COMPILER) + return __builtin_ctzll(v); + #elif defined(_MSC_VER) + #if defined(_WIN64) + unsigned long idx; + _BitScanForward64(&idx, v); + return idx; + #else + unsigned long idx0, idx1; + + _BitScanForward(&idx1, v >> 32); + idx1 += 32; + if(!_BitScanForward(&idx0, v)) + idx0 = idx1; + + return idx0; + #endif + #else + unsigned ret = 0; + unsigned tmp; + + tmp = !((uint32)v) << 5; v >>= tmp; ret += tmp; + tmp = !((uint16)v) << 4; v >>= tmp; ret += tmp; + tmp = !( (uint8)v) << 3; v >>= tmp; ret += tmp; + tmp = !(v & 0x000F) << 2; v >>= tmp; ret += tmp; + tmp = !(v & 0x0003) << 1; v >>= tmp; ret += tmp; + tmp = !(v & 0x0001) << 0; ret += tmp; + + return ret; + #endif +} + +// +// Result is defined for all possible inputs(including 0). +// +static INLINE unsigned MDFN_lzcount16(uint16 v) { return !v ? 16 : MDFN_lzcount16_0UD(v); } +static INLINE unsigned MDFN_lzcount32(uint32 v) { return !v ? 32 : MDFN_lzcount32_0UD(v); } +static INLINE unsigned MDFN_lzcount64(uint64 v) { return !v ? 64 : MDFN_lzcount64_0UD(v); } + +static INLINE unsigned MDFN_tzcount16(uint16 v) { return !v ? 16 : MDFN_tzcount16_0UD(v); } +static INLINE unsigned MDFN_tzcount32(uint32 v) { return !v ? 32 : MDFN_tzcount32_0UD(v); } +static INLINE unsigned MDFN_tzcount64(uint64 v) { return !v ? 64 : MDFN_tzcount64_0UD(v); } + +static INLINE unsigned MDFN_log2(uint32 v) { return 31 ^ MDFN_lzcount32_0UD(v | 1); } +static INLINE unsigned MDFN_log2(uint64 v) { return 63 ^ MDFN_lzcount64_0UD(v | 1); } + +static INLINE unsigned MDFN_log2(int32 v) { return MDFN_log2((uint32)v); } +static INLINE unsigned MDFN_log2(int64 v) { return MDFN_log2((uint64)v); } + +// Rounds up to the nearest power of 2(treats input as unsigned to a degree, but be aware of integer promotion rules). +// Returns 0 on overflow. +static INLINE uint64 round_up_pow2(uint32 v) { uint64 tmp = (uint64)1 << MDFN_log2(v); return tmp << (tmp < v); } +static INLINE uint64 round_up_pow2(uint64 v) { uint64 tmp = (uint64)1 << MDFN_log2(v); return tmp << (tmp < v); } + +static INLINE uint64 round_up_pow2(int32 v) { return round_up_pow2((uint32)v); } +static INLINE uint64 round_up_pow2(int64 v) { return round_up_pow2((uint64)v); } + +// Rounds to the nearest power of 2(treats input as unsigned to a degree, but be aware of integer promotion rules). +static INLINE uint64 round_nearest_pow2(uint32 v, bool round_half_up = true) { uint64 tmp = (uint64)1 << MDFN_log2(v); return tmp << (v && (((v - tmp) << 1) >= (tmp + !round_half_up))); } +static INLINE uint64 round_nearest_pow2(uint64 v, bool round_half_up = true) { uint64 tmp = (uint64)1 << MDFN_log2(v); return tmp << (v && (((v - tmp) << 1) >= (tmp + !round_half_up))); } + +static INLINE uint64 round_nearest_pow2(int32 v, bool round_half_up = true) { return round_nearest_pow2((uint32)v, round_half_up); } +static INLINE uint64 round_nearest_pow2(int64 v, bool round_half_up = true) { return round_nearest_pow2((uint64)v, round_half_up); } + +// Some compilers' optimizers and some platforms might fubar the generated code from these macros, +// so some tests are run in...tests.cpp +#define sign_8_to_s16(_value) ((int16)(int8)(_value)) +#define sign_9_to_s16(_value) (((int16)((unsigned int)(_value) << 7)) >> 7) +#define sign_10_to_s16(_value) (((int16)((uint32)(_value) << 6)) >> 6) +#define sign_11_to_s16(_value) (((int16)((uint32)(_value) << 5)) >> 5) +#define sign_12_to_s16(_value) (((int16)((uint32)(_value) << 4)) >> 4) +#define sign_13_to_s16(_value) (((int16)((uint32)(_value) << 3)) >> 3) +#define sign_14_to_s16(_value) (((int16)((uint32)(_value) << 2)) >> 2) +#define sign_15_to_s16(_value) (((int16)((uint32)(_value) << 1)) >> 1) + +// This obviously won't convert higher-than-32 bit numbers to signed 32-bit ;) +// Also, this shouldn't be used for 8-bit and 16-bit signed numbers, since you can +// convert those faster with typecasts... +#define sign_x_to_s32(_bits, _value) (((int32)((uint32)(_value) << (32 - _bits))) >> (32 - _bits)) + +static INLINE int32 clamp_to_u8(int32 i) +{ + if(i & 0xFFFFFF00) + i = (((~i) >> 30) & 0xFF); + + return(i); +} + +static INLINE int32 clamp_to_u16(int32 i) +{ + if(i & 0xFFFF0000) + i = (((~i) >> 31) & 0xFFFF); + + return(i); +} + +template static INLINE void clamp(T *val, U minimum, V maximum) +{ + if(*val < minimum) + { + //printf("Warning: clamping to minimum(%d)\n", (int)minimum); + *val = minimum; + } + if(*val > maximum) + { + //printf("Warning: clamping to maximum(%d)\n", (int)maximum); + *val = maximum; + } +} + +#endif diff --git a/waterbox/vb/timer.cpp b/waterbox/vb/timer.cpp new file mode 100644 index 0000000000..43cfe5fd45 --- /dev/null +++ b/waterbox/vb/timer.cpp @@ -0,0 +1,233 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* timer.cpp: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "vb.h" +#include "timer.h" + +namespace MDFN_IEN_VB +{ + +#define TC_TENABLE 0x01 +#define TC_ZSTAT 0x02 +#define TC_ZSTATCLR 0x04 +#define TC_TIMZINT 0x08 +#define TC_TCLKSEL 0x10 + +static uint8 TimerControl; +static uint16 TimerReloadValue; +static uint16 TimerCounter; +static int32 TimerDivider; +static v810_timestamp_t TimerLastTS; +static bool TimerStatus, TimerStatusShadow; +static bool ReloadPending; + +v810_timestamp_t TIMER_Update(v810_timestamp_t timestamp) +{ + int32 run_time = timestamp - TimerLastTS; + + if (TimerControl & TC_TENABLE) + { + TimerDivider -= run_time; + while (TimerDivider <= 0) + { + if (!TimerCounter || ReloadPending) + { + TimerCounter = TimerReloadValue; + ReloadPending = false; + } + + if (TimerCounter) + TimerCounter--; + + if (!TimerCounter || TimerStatus) + { + TimerStatusShadow = TimerStatus = true; + } + + VBIRQ_Assert(VBIRQ_SOURCE_TIMER, TimerStatusShadow && (TimerControl & TC_TIMZINT)); + TimerDivider += (TimerControl & TC_TCLKSEL) ? 500 : 2000; + } + } + + TimerLastTS = timestamp; + + return ((TimerControl & TC_TENABLE) ? (timestamp + TimerDivider) : VB_EVENT_NONONO); +} + +void TIMER_ResetTS(void) +{ + TimerLastTS = 0; +} + +uint8 TIMER_Read(const v810_timestamp_t ×tamp, uint32 A) +{ + uint8 ret = 0; + + //if(A <= 0x1C) + //printf("Read: %d, %08x\n", timestamp, A); + TIMER_Update(timestamp); + + switch (A & 0xFF) + { + case 0x18: + ret = TimerCounter; + break; + + case 0x1C: + ret = TimerCounter >> 8; + break; + + case 0x20: + ret = TimerControl | (0xE0 | TC_ZSTATCLR) | (TimerStatus ? TC_ZSTAT : 0); + break; + } + + return (ret); +} + +void TIMER_Write(const v810_timestamp_t ×tamp, uint32 A, uint8 V) +{ + if (A & 0x3) + { + puts("HWCtrl Bogus Write?"); + return; + } + + TIMER_Update(timestamp); + + //if((A & 0xFF) <= 0x1C) + //printf("Write: %d, %08x %02x\n", timestamp, A, V); + + switch (A & 0xFF) + { + case 0x18: + TimerReloadValue &= 0xFF00; + TimerReloadValue |= V; + ReloadPending = true; + break; + + case 0x1C: + TimerReloadValue &= 0x00FF; + TimerReloadValue |= V << 8; + ReloadPending = true; + break; + + case 0x20: + if (V & TC_ZSTATCLR) + { + if ((TimerControl & TC_TENABLE) && TimerCounter == 0) + { + //puts("Faulty Z-Stat-Clr"); + } + else + { + TimerStatus = false; + } + TimerStatusShadow = false; + } + if ((V & TC_TENABLE) && !(TimerControl & TC_TENABLE)) + { + //TimerCounter = TimerReloadValue; + TimerDivider = (V & TC_TCLKSEL) ? 500 : 2000; + } + TimerControl = V & (0x10 | 0x08 | 0x01); + + if (!(TimerControl & TC_TIMZINT)) + TimerStatus = TimerStatusShadow = false; + + VBIRQ_Assert(VBIRQ_SOURCE_TIMER, TimerStatusShadow && (TimerControl & TC_TIMZINT)); + + if (TimerControl & TC_TENABLE) + VB_SetEvent(VB_EVENT_TIMER, timestamp + TimerDivider); + break; + } +} + +void TIMER_Power(void) +{ + TimerLastTS = 0; + + TimerCounter = 0xFFFF; + TimerReloadValue = 0; + TimerDivider = 2000; //2150; //2000; + + TimerStatus = false; + TimerStatusShadow = false; + TimerControl = 0; + + ReloadPending = false; + + VBIRQ_Assert(VBIRQ_SOURCE_TIMER, false); +} + +uint32 TIMER_GetRegister(const unsigned int id, char *special, const uint32 special_len) +{ + uint32 ret = 0xDEADBEEF; + + switch (id) + { + case TIMER_GSREG_TCR: + ret = TimerControl; + if (special) + trio_snprintf(special, special_len, "TEnable: %d, TimZInt: %d, TClkSel: %d(%.3f KHz)", + (int)(bool)(ret & TC_TENABLE), + (int)(bool)(ret & TC_TIMZINT), + (int)(bool)(ret & TC_TCLKSEL), + (double)VB_MASTER_CLOCK / ((ret & TC_TCLKSEL) ? 500 : 2000) / 1000); + break; + + case TIMER_GSREG_DIVCOUNTER: + ret = TimerDivider; + break; + + case TIMER_GSREG_RELOAD_VALUE: + ret = TimerReloadValue; + break; + + case TIMER_GSREG_COUNTER: + ret = TimerCounter; + break; + } + return (ret); +} + +void TIMER_SetRegister(const unsigned int id, const uint32 value) +{ + switch (id) + { + case TIMER_GSREG_TCR: + TimerControl = value & (TC_TENABLE | TC_TIMZINT | TC_TCLKSEL); + break; + + case TIMER_GSREG_DIVCOUNTER: + TimerDivider = value % ((TimerControl & TC_TCLKSEL) ? 500 : 2000); + break; + + case TIMER_GSREG_RELOAD_VALUE: + TimerReloadValue = value; + break; + + case TIMER_GSREG_COUNTER: + TimerCounter = value; + break; + } +} +} diff --git a/waterbox/vb/timer.h b/waterbox/vb/timer.h new file mode 100644 index 0000000000..20f0f7e9b6 --- /dev/null +++ b/waterbox/vb/timer.h @@ -0,0 +1,48 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* timer.h: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef __MDFN_VB_TIMER_H +#define __MDFN_VB_TIMER_H + +namespace MDFN_IEN_VB +{ + +v810_timestamp_t TIMER_Update(v810_timestamp_t timestamp); +void TIMER_ResetTS(void); +uint8 TIMER_Read(const v810_timestamp_t ×tamp, uint32 A); +void TIMER_Write(const v810_timestamp_t ×tamp, uint32 A, uint8 V); + +void TIMER_Power(void) MDFN_COLD; + +enum +{ + TIMER_GSREG_TCR, + TIMER_GSREG_DIVCOUNTER, + TIMER_GSREG_RELOAD_VALUE, + TIMER_GSREG_COUNTER, +}; + +uint32 TIMER_GetRegister(const unsigned int id, char *special, const uint32 special_len); +void TIMER_SetRegister(const unsigned int id, const uint32 value); + +} + +#endif diff --git a/waterbox/vb/v810/v810_cpu.cpp b/waterbox/vb/v810/v810_cpu.cpp new file mode 100644 index 0000000000..617bc3fc0f --- /dev/null +++ b/waterbox/vb/v810/v810_cpu.cpp @@ -0,0 +1,1369 @@ +/* V810 Emulator + * + * Copyright (C) 2006 David Tucker + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Alternatively, the V810 emulator code(and all V810 emulation header files) can be used/distributed under the following license(you can adopt either + license exclusively for your changes by removing one of these license headers, but it's STRONGLY preferable + to keep your changes dual-licensed as well): + +This Reality Boy emulator is copyright (C) David Tucker 1997-2008, all rights +reserved. You may use this code as long as you make no money from the use of +this code and you acknowledge the original author (Me). I reserve the right to +dictate who can use this code and how (Just so you don't do something stupid +with it). + Most Importantly, this code is swap ware. If you use It send along your new +program (with code) or some other interesting tidbits you wrote, that I might be +interested in. + This code is in beta, there are bugs! I am not responsible for any damage +done to your computer, reputation, ego, dog, or family life due to the use of +this code. All source is provided as is, I make no guaranties, and am not +responsible for anything you do with the code (legal or otherwise). + Virtual Boy is a trademark of Nintendo, and V810 is a trademark of NEC. I am +in no way affiliated with either party and all information contained hear was +found freely through public domain sources. +*/ + +////////////////////////////////////////////////////////// +// CPU routines + +#include "vb.h" + +//#include "pcfx.h" +//#include "debug.h" + +#include +#include + +#include "v810_opt.h" +#include "v810_cpu.h" + +V810::V810() +{ + MemRead8 = NULL; + MemRead16 = NULL; + MemRead32 = NULL; + + IORead8 = NULL; + IORead16 = NULL; + IORead32 = NULL; + + MemWrite8 = NULL; + MemWrite16 = NULL; + MemWrite32 = NULL; + + IOWrite8 = NULL; + IOWrite16 = NULL; + IOWrite32 = NULL; + + memset(FastMap, 0, sizeof(FastMap)); + + memset(MemReadBus32, 0, sizeof(MemReadBus32)); + memset(MemWriteBus32, 0, sizeof(MemWriteBus32)); + + v810_timestamp = 0; + next_event_ts = 0x7FFFFFFF; +} + +V810::~V810() +{ + Kill(); +} + +INLINE void V810::RecalcIPendingCache(void) +{ + IPendingCache = 0; + + // Of course don't generate an interrupt if there's not one pending! + if (ilevel < 0) + return; + + // If CPU is halted because of a fatal exception, don't let an interrupt + // take us out of this halted status. + if (Halted == HALT_FATAL_EXCEPTION) + return; + + // If the NMI pending, exception pending, and/or interrupt disabled bit + // is set, don't accept any interrupts. + if (S_REG[PSW] & (PSW_NP | PSW_EP | PSW_ID)) + return; + + // If the interrupt level is lower than the interrupt enable level, don't + // accept it. + if (ilevel < (int)((S_REG[PSW] & PSW_IA) >> 16)) + return; + + IPendingCache = 0xFF; +} + +// TODO: "An interrupt that occurs during restore/dump/clear operation is internally held and is accepted after the +// operation in progress is finished. The maskable interrupt is held internally only when the EP, NP, and ID flags +// of PSW are all 0." +// +// This behavior probably doesn't have any relevance on the PC-FX, unless we're sadistic +// and try to restore cache from an interrupt acknowledge register or dump it to a register +// controlling interrupt masks... I wanna be sadistic~ + +void V810::CacheClear(v810_timestamp_t ×tamp, uint32 start, uint32 count) +{ + //printf("Cache clear: %08x %08x\n", start, count); + for (uint32 i = 0; i < count && (i + start) < 128; i++) + memset(&Cache[i + start], 0, sizeof(V810_CacheEntry_t)); +} + +INLINE void V810::CacheOpMemStore(v810_timestamp_t ×tamp, uint32 A, uint32 V) +{ + if (MemWriteBus32[A >> 24]) + { + timestamp += 2; + MemWrite32(timestamp, A, V); + } + else + { + timestamp += 2; + MemWrite16(timestamp, A, V & 0xFFFF); + + timestamp += 2; + MemWrite16(timestamp, A | 2, V >> 16); + } +} + +INLINE uint32 V810::CacheOpMemLoad(v810_timestamp_t ×tamp, uint32 A) +{ + if (MemReadBus32[A >> 24]) + { + timestamp += 2; + return (MemRead32(timestamp, A)); + } + else + { + uint32 ret; + + timestamp += 2; + ret = MemRead16(timestamp, A); + + timestamp += 2; + ret |= MemRead16(timestamp, A | 2) << 16; + return (ret); + } +} + +void V810::CacheDump(v810_timestamp_t ×tamp, const uint32 SA) +{ + printf("Cache dump: %08x\n", SA); + + for (int i = 0; i < 128; i++) + { + CacheOpMemStore(timestamp, SA + i * 8 + 0, Cache[i].data[0]); + CacheOpMemStore(timestamp, SA + i * 8 + 4, Cache[i].data[1]); + } + + for (int i = 0; i < 128; i++) + { + uint32 icht = Cache[i].tag | ((int)Cache[i].data_valid[0] << 22) | ((int)Cache[i].data_valid[1] << 23); + + CacheOpMemStore(timestamp, SA + 1024 + i * 4, icht); + } +} + +void V810::CacheRestore(v810_timestamp_t ×tamp, const uint32 SA) +{ + printf("Cache restore: %08x\n", SA); + + for (int i = 0; i < 128; i++) + { + Cache[i].data[0] = CacheOpMemLoad(timestamp, SA + i * 8 + 0); + Cache[i].data[1] = CacheOpMemLoad(timestamp, SA + i * 8 + 4); + } + + for (int i = 0; i < 128; i++) + { + uint32 icht; + + icht = CacheOpMemLoad(timestamp, SA + 1024 + i * 4); + + Cache[i].tag = icht & ((1 << 22) - 1); + Cache[i].data_valid[0] = (icht >> 22) & 1; + Cache[i].data_valid[1] = (icht >> 23) & 1; + } +} + +INLINE uint32 V810::RDCACHE(v810_timestamp_t ×tamp, uint32 addr) +{ + const int CI = (addr >> 3) & 0x7F; + const int SBI = (addr & 4) >> 2; + + if (Cache[CI].tag == (addr >> 10)) + { + if (!Cache[CI].data_valid[SBI]) + { + timestamp += 2; // or higher? Penalty for cache miss seems to be higher than having cache disabled. + if (MemReadBus32[addr >> 24]) + Cache[CI].data[SBI] = MemRead32(timestamp, addr & ~0x3); + else + { + timestamp++; + + uint32 tmp; + + tmp = MemRead16(timestamp, addr & ~0x3); + tmp |= MemRead16(timestamp, (addr & ~0x3) | 0x2) << 16; + + Cache[CI].data[SBI] = tmp; + } + Cache[CI].data_valid[SBI] = TRUE; + } + } + else + { + Cache[CI].tag = addr >> 10; + + timestamp += 2; // or higher? Penalty for cache miss seems to be higher than having cache disabled. + if (MemReadBus32[addr >> 24]) + Cache[CI].data[SBI] = MemRead32(timestamp, addr & ~0x3); + else + { + timestamp++; + + uint32 tmp; + + tmp = MemRead16(timestamp, addr & ~0x3); + tmp |= MemRead16(timestamp, (addr & ~0x3) | 0x2) << 16; + + Cache[CI].data[SBI] = tmp; + } + //Cache[CI].data[SBI] = MemRead32(timestamp, addr & ~0x3); + Cache[CI].data_valid[SBI] = TRUE; + Cache[CI].data_valid[SBI ^ 1] = FALSE; + } + + //{ + // // Caution: This can mess up DRAM page change penalty timings + // uint32 dummy_timestamp = 0; + // if(Cache[CI].data[SBI] != mem_rword(addr & ~0x3, dummy_timestamp)) + // { + // printf("Cache/Real Memory Mismatch: %08x %08x/%08x\n", addr & ~0x3, Cache[CI].data[SBI], mem_rword(addr & ~0x3, dummy_timestamp)); + // } + //} + + return (Cache[CI].data[SBI]); +} + +INLINE uint16 V810::RDOP(v810_timestamp_t ×tamp, uint32 addr, uint32 meow) +{ + uint16 ret; + + if (S_REG[CHCW] & 0x2) + { + uint32 d32 = RDCACHE(timestamp, addr); + ret = d32 >> ((addr & 2) * 8); + } + else + { + timestamp += meow; //++; + ret = MemRead16(timestamp, addr); + } + return (ret); +} + +#define BRANCH_ALIGN_CHECK(x) \ + { \ + if ((S_REG[CHCW] & 0x2) && (x & 0x2)) \ + { \ + ADDCLOCK(1); \ + } \ + } + +// Reinitialize the defaults in the CPU +void V810::Reset() +{ + memset(&Cache, 0, sizeof(Cache)); + + memset(P_REG, 0, sizeof(P_REG)); + memset(S_REG, 0, sizeof(S_REG)); + memset(Cache, 0, sizeof(Cache)); + + P_REG[0] = 0x00000000; + SetPC(0xFFFFFFF0); + + S_REG[ECR] = 0x0000FFF0; + S_REG[PSW] = 0x00008000; + + if (VBMode) + S_REG[PIR] = 0x00005346; + else + S_REG[PIR] = 0x00008100; + + S_REG[TKCW] = 0x000000E0; + Halted = HALT_NONE; + ilevel = -1; + + lastop = 0; + + in_bstr = FALSE; + + RecalcIPendingCache(); +} + +bool V810::Init(V810_Emu_Mode mode, bool vb_mode) +{ + EmuMode = mode; + VBMode = vb_mode; + + in_bstr = FALSE; + in_bstr_to = 0; + + if (mode == V810_EMU_MODE_FAST) + { + memset(DummyRegion, 0, V810_FAST_MAP_PSIZE); + + for (unsigned int i = V810_FAST_MAP_PSIZE; i < V810_FAST_MAP_PSIZE + V810_FAST_MAP_TRAMPOLINE_SIZE; i += 2) + { + DummyRegion[i + 0] = 0; + DummyRegion[i + 1] = 0x36 << 2; + } + + for (uint64 A = 0; A < (1ULL << 32); A += V810_FAST_MAP_PSIZE) + FastMap[A / V810_FAST_MAP_PSIZE] = DummyRegion - A; + } + + return (TRUE); +} + +void V810::Kill(void) +{ + FastMapAllocList.clear(); +} + +void V810::SetInt(int level) +{ + assert(level >= -1 && level <= 15); + + ilevel = level; + RecalcIPendingCache(); +} + +uint8 *V810::SetFastMap(uint32 addresses[], uint32 length, unsigned int num_addresses, const char *name) +{ + for (unsigned int i = 0; i < num_addresses; i++) + { + assert((addresses[i] & (V810_FAST_MAP_PSIZE - 1)) == 0); + } + assert((length & (V810_FAST_MAP_PSIZE - 1)) == 0); + + FastMapAllocList.emplace_back(std::unique_ptr(new uint8[length + V810_FAST_MAP_TRAMPOLINE_SIZE])); + uint8 *ret = FastMapAllocList.back().get(); + + for (unsigned int i = length; i < length + V810_FAST_MAP_TRAMPOLINE_SIZE; i += 2) + { + ret[i + 0] = 0; + ret[i + 1] = 0x36 << 2; + } + + for (unsigned int i = 0; i < num_addresses; i++) + { + for (uint64 addr = addresses[i]; addr != (uint64)addresses[i] + length; addr += V810_FAST_MAP_PSIZE) + { + //printf("%08x, %d, %s\n", addr, length, name); + + FastMap[addr / V810_FAST_MAP_PSIZE] = ret - addresses[i]; + } + } + + return ret; +} + +void V810::SetMemReadBus32(uint8 A, bool value) +{ + MemReadBus32[A] = value; +} + +void V810::SetMemWriteBus32(uint8 A, bool value) +{ + MemWriteBus32[A] = value; +} + +void V810::SetMemReadHandlers(uint8 MDFN_FASTCALL (*read8)(v810_timestamp_t &, uint32), uint16 MDFN_FASTCALL (*read16)(v810_timestamp_t &, uint32), uint32 MDFN_FASTCALL (*read32)(v810_timestamp_t &, uint32)) +{ + MemRead8 = read8; + MemRead16 = read16; + MemRead32 = read32; +} + +void V810::SetMemWriteHandlers(void MDFN_FASTCALL (*write8)(v810_timestamp_t &, uint32, uint8), void MDFN_FASTCALL (*write16)(v810_timestamp_t &, uint32, uint16), void MDFN_FASTCALL (*write32)(v810_timestamp_t &, uint32, uint32)) +{ + MemWrite8 = write8; + MemWrite16 = write16; + MemWrite32 = write32; +} + +void V810::SetIOReadHandlers(uint8 MDFN_FASTCALL (*read8)(v810_timestamp_t &, uint32), uint16 MDFN_FASTCALL (*read16)(v810_timestamp_t &, uint32), uint32 MDFN_FASTCALL (*read32)(v810_timestamp_t &, uint32)) +{ + IORead8 = read8; + IORead16 = read16; + IORead32 = read32; +} + +void V810::SetIOWriteHandlers(void MDFN_FASTCALL (*write8)(v810_timestamp_t &, uint32, uint8), void MDFN_FASTCALL (*write16)(v810_timestamp_t &, uint32, uint16), void MDFN_FASTCALL (*write32)(v810_timestamp_t &, uint32, uint32)) +{ + IOWrite8 = write8; + IOWrite16 = write16; + IOWrite32 = write32; +} + +INLINE void V810::SetFlag(uint32 n, bool condition) +{ + S_REG[PSW] &= ~n; + + if (condition) + S_REG[PSW] |= n; +} + +INLINE void V810::SetSZ(uint32 value) +{ + SetFlag(PSW_Z, !value); + SetFlag(PSW_S, value & 0x80000000); +} + +#define SetPREG(n, val) \ + { \ + P_REG[n] = val; \ + } + +INLINE void V810::SetSREG(v810_timestamp_t ×tamp, unsigned int which, uint32 value) +{ + switch (which) + { + default: // Reserved + printf("LDSR to reserved system register: 0x%02x : 0x%08x\n", which, value); + break; + + case ECR: // Read-only + break; + + case PIR: // Read-only (obviously) + break; + + case TKCW: // Read-only + break; + + case EIPSW: + case FEPSW: + S_REG[which] = value & 0xFF3FF; + break; + + case PSW: + S_REG[which] = value & 0xFF3FF; + RecalcIPendingCache(); + break; + + case EIPC: + case FEPC: + S_REG[which] = value & 0xFFFFFFFE; + break; + + case ADDTRE: + S_REG[ADDTRE] = value & 0xFFFFFFFE; + printf("Address trap(unemulated): %08x\n", value); + break; + + case CHCW: + S_REG[CHCW] = value & 0x2; + + switch (value & 0x31) + { + default: + printf("Undefined cache control bit combination: %08x\n", value); + break; + + case 0x00: + break; + + case 0x01: + CacheClear(timestamp, (value >> 20) & 0xFFF, (value >> 8) & 0xFFF); + break; + + case 0x10: + CacheDump(timestamp, value & ~0xFF); + break; + + case 0x20: + CacheRestore(timestamp, value & ~0xFF); + break; + } + break; + } +} + +INLINE uint32 V810::GetSREG(unsigned int which) +{ + uint32 ret; + + if (which != 24 && which != 25 && which >= 8) + { + printf("STSR from reserved system register: 0x%02x", which); + } + + ret = S_REG[which]; + + return (ret); +} + +#define RB_SETPC(new_pc_raw) \ + { \ + const uint32 new_pc = new_pc_raw; /* So RB_SETPC(RB_GETPC()) won't mess up */ \ + if (RB_AccurateMode) \ + PC = new_pc; \ + else \ + { \ + PC_ptr = &FastMap[(new_pc) >> V810_FAST_MAP_SHIFT][(new_pc)]; \ + PC_base = PC_ptr - (new_pc); \ + } \ + } + +#define RB_PCRELCHANGE(delta) \ + { \ + if (RB_AccurateMode) \ + PC += (delta); \ + else \ + { \ + uint32 PC_tmp = RB_GETPC(); \ + PC_tmp += (delta); \ + RB_SETPC(PC_tmp); \ + } \ + } + +#define RB_INCPCBY2() \ + { \ + if (RB_AccurateMode) \ + PC += 2; \ + else \ + PC_ptr += 2; \ + } +#define RB_INCPCBY4() \ + { \ + if (RB_AccurateMode) \ + PC += 4; \ + else \ + PC_ptr += 4; \ + } + +#define RB_DECPCBY2() \ + { \ + if (RB_AccurateMode) \ + PC -= 2; \ + else \ + PC_ptr -= 2; \ + } +#define RB_DECPCBY4() \ + { \ + if (RB_AccurateMode) \ + PC -= 4; \ + else \ + PC_ptr -= 4; \ + } + +// Define accurate mode defines +#define RB_GETPC() PC +#define RB_RDOP(PC_offset, ...) RDOP(timestamp, PC + PC_offset, ##__VA_ARGS__) + +void V810::Run_Accurate(int32 MDFN_FASTCALL (*event_handler)(const v810_timestamp_t timestamp)) +{ + const bool RB_AccurateMode = true; + +#define RB_ADDBT(n, o, p) +#define RB_CPUHOOK(n) + +#include "v810_oploop.inc" + +#undef RB_CPUHOOK +#undef RB_ADDBT +} + +// +// Undefine accurate mode defines +// +#undef RB_GETPC +#undef RB_RDOP + +// +// Define fast mode defines +// +#define RB_GETPC() ((uint32)(PC_ptr - PC_base)) + +#define RB_RDOP(PC_offset, ...) MDFN_de16lsb(&PC_ptr[PC_offset]) + +void V810::Run_Fast(int32 MDFN_FASTCALL (*event_handler)(const v810_timestamp_t timestamp)) +{ + const bool RB_AccurateMode = false; + +#define RB_ADDBT(n, o, p) +#define RB_CPUHOOK(n) + +#include "v810_oploop.inc" + +#undef RB_CPUHOOK +#undef RB_ADDBT +} + +// +// Undefine fast mode defines +// +#undef RB_GETPC +#undef RB_RDOP + +v810_timestamp_t V810::Run(int32 MDFN_FASTCALL (*event_handler)(const v810_timestamp_t timestamp)) +{ + Running = true; + if (EmuMode == V810_EMU_MODE_FAST) + Run_Fast(event_handler); + else + Run_Accurate(event_handler); + return (v810_timestamp); +} + +void V810::Exit(void) +{ + Running = false; +} + +uint32 V810::GetRegister(unsigned int which, char *special, const uint32 special_len) +{ + if (which >= GSREG_PR && which <= GSREG_PR + 31) + { + return GetPR(which - GSREG_PR); + } + else if (which >= GSREG_SR && which <= GSREG_SR + 31) + { + uint32 val = GetSREG(which - GSREG_SR); + + if (special && which == GSREG_SR + PSW) + { + trio_snprintf(special, special_len, "Z: %d, S: %d, OV: %d, CY: %d, ID: %d, AE: %d, EP: %d, NP: %d, IA: %2d", + (int)(bool)(val & PSW_Z), (int)(bool)(val & PSW_S), (int)(bool)(val & PSW_OV), (int)(bool)(val & PSW_CY), + (int)(bool)(val & PSW_ID), (int)(bool)(val & PSW_AE), (int)(bool)(val & PSW_EP), (int)(bool)(val & PSW_NP), + (val & PSW_IA) >> 16); + } + + return val; + } + else if (which == GSREG_PC) + { + return GetPC(); + } + else if (which == GSREG_TIMESTAMP) + { + return v810_timestamp; + } + + return 0xDEADBEEF; +} + +void V810::SetRegister(unsigned int which, uint32 value) +{ + if (which >= GSREG_PR && which <= GSREG_PR + 31) + { + if (which) + P_REG[which - GSREG_PR] = value; + } + else if (which >= GSREG_SR && which <= GSREG_SR + 31) + { + // SetSREG(timestamp, which - GSREG_SR, value); + } + else if (which == GSREG_PC) + { + SetPC(value & ~1); + } + else if (which == GSREG_TIMESTAMP) + { + //v810_timestamp = value; + } +} + +uint32 V810::GetPC(void) +{ + if (EmuMode == V810_EMU_MODE_ACCURATE) + return (PC); + else + { + return (PC_ptr - PC_base); + } +} + +void V810::SetPC(uint32 new_pc) +{ + if (EmuMode == V810_EMU_MODE_ACCURATE) + PC = new_pc; + else + { + PC_ptr = &FastMap[new_pc >> V810_FAST_MAP_SHIFT][new_pc]; + PC_base = PC_ptr - new_pc; + } +} + +#define BSTR_OP_MOV \ + dst_cache &= ~(1 << dstoff); \ + dst_cache |= ((src_cache >> srcoff) & 1) << dstoff; +#define BSTR_OP_NOT \ + dst_cache &= ~(1 << dstoff); \ + dst_cache |= (((src_cache >> srcoff) & 1) ^ 1) << dstoff; + +#define BSTR_OP_XOR dst_cache ^= ((src_cache >> srcoff) & 1) << dstoff; +#define BSTR_OP_OR dst_cache |= ((src_cache >> srcoff) & 1) << dstoff; +#define BSTR_OP_AND dst_cache &= ~((((src_cache >> srcoff) & 1) ^ 1) << dstoff); + +#define BSTR_OP_XORN dst_cache ^= (((src_cache >> srcoff) & 1) ^ 1) << dstoff; +#define BSTR_OP_ORN dst_cache |= (((src_cache >> srcoff) & 1) ^ 1) << dstoff; +#define BSTR_OP_ANDN dst_cache &= ~(((src_cache >> srcoff) & 1) << dstoff); + +INLINE uint32 V810::BSTR_RWORD(v810_timestamp_t ×tamp, uint32 A) +{ + if (MemReadBus32[A >> 24]) + { + timestamp += 2; + return (MemRead32(timestamp, A)); + } + else + { + uint32 ret; + + timestamp += 2; + ret = MemRead16(timestamp, A); + + timestamp += 2; + ret |= MemRead16(timestamp, A | 2) << 16; + return (ret); + } +} + +INLINE void V810::BSTR_WWORD(v810_timestamp_t ×tamp, uint32 A, uint32 V) +{ + if (MemWriteBus32[A >> 24]) + { + timestamp += 2; + MemWrite32(timestamp, A, V); + } + else + { + timestamp += 2; + MemWrite16(timestamp, A, V & 0xFFFF); + + timestamp += 2; + MemWrite16(timestamp, A | 2, V >> 16); + } +} + +#define DO_BSTR(op) \ + { \ + while (len) \ + { \ + if (!have_src_cache) \ + { \ + have_src_cache = TRUE; \ + src_cache = BSTR_RWORD(timestamp, src); \ + } \ + \ + if (!have_dst_cache) \ + { \ + have_dst_cache = TRUE; \ + dst_cache = BSTR_RWORD(timestamp, dst); \ + } \ + \ + op; \ + srcoff = (srcoff + 1) & 0x1F; \ + dstoff = (dstoff + 1) & 0x1F; \ + len--; \ + \ + if (!srcoff) \ + { \ + src += 4; \ + have_src_cache = FALSE; \ + } \ + \ + if (!dstoff) \ + { \ + BSTR_WWORD(timestamp, dst, dst_cache); \ + dst += 4; \ + have_dst_cache = FALSE; \ + if (timestamp >= next_event_ts) \ + break; \ + } \ + } \ + if (have_dst_cache) \ + BSTR_WWORD(timestamp, dst, dst_cache); \ + } + +INLINE bool V810::Do_BSTR_Search(v810_timestamp_t ×tamp, const int inc_mul, unsigned int bit_test) +{ + uint32 srcoff = (P_REG[27] & 0x1F); + uint32 len = P_REG[28]; + uint32 bits_skipped = P_REG[29]; + uint32 src = (P_REG[30] & 0xFFFFFFFC); + bool found = false; + +#if 0 + // TODO: Better timing. + if(!in_bstr) // If we're just starting the execution of this instruction(kind of spaghetti-code), so FIXME if we change + // bstr handling in v810_oploop.inc + { + timestamp += 13 - 1; + } +#endif + + while (len) + { + if (!have_src_cache) + { + have_src_cache = TRUE; + timestamp++; + src_cache = BSTR_RWORD(timestamp, src); + } + + if (((src_cache >> srcoff) & 1) == bit_test) + { + found = true; + + /* Fix the bit offset and word address to "1 bit before" it was found */ + srcoff -= inc_mul * 1; + if (srcoff & 0x20) /* Handles 0x1F->0x20(0x00) and 0x00->0xFFFF... */ + { + src -= inc_mul * 4; + srcoff &= 0x1F; + } + break; + } + srcoff = (srcoff + inc_mul * 1) & 0x1F; + bits_skipped++; + len--; + + if (!srcoff) + { + have_src_cache = FALSE; + src += inc_mul * 4; + if (timestamp >= next_event_ts) + break; + } + } + + P_REG[27] = srcoff; + P_REG[28] = len; + P_REG[29] = bits_skipped; + P_REG[30] = src; + + if (found) // Set Z flag to 0 if the bit was found + SetFlag(PSW_Z, 0); + else if (!len) // ...and if the search is over, and the bit was not found, set it to 1 + SetFlag(PSW_Z, 1); + + if (found) // Bit found, so don't continue the search. + return (false); + + return ((bool)len); // Continue the search if any bits are left to search. +} + +bool V810::bstr_subop(v810_timestamp_t ×tamp, int sub_op, int arg1) +{ + if ((sub_op >= 0x10) || (!(sub_op & 0x8) && sub_op >= 0x4)) + { + printf("%08x\tBSR Error: %04x\n", PC, sub_op); + + SetPC(GetPC() - 2); + Exception(INVALID_OP_HANDLER_ADDR, ECODE_INVALID_OP); + + return (false); + } + + // printf("BSTR: %02x, %02x %02x; src: %08x, dst: %08x, len: %08x\n", sub_op, P_REG[27], P_REG[26], P_REG[30], P_REG[29], P_REG[28]); + + if (sub_op & 0x08) + { + uint32 dstoff = (P_REG[26] & 0x1F); + uint32 srcoff = (P_REG[27] & 0x1F); + uint32 len = P_REG[28]; + uint32 dst = (P_REG[29] & 0xFFFFFFFC); + uint32 src = (P_REG[30] & 0xFFFFFFFC); + +#if 0 + // Be careful not to cause 32-bit integer overflow, and careful about not shifting by 32. + // TODO: + + // Read src[0], src[4] into shifter. + // Read dest[0]. + DO_BSTR_PROLOGUE(); // if(len) { blah blah blah masking blah } + src_cache = BSTR_RWORD(timestamp, src); + + if((uint64)(srcoff + len) > 0x20) + src_cache |= (uint64)BSTR_RWORD(timestamp, src + 4) << 32; + + dst_cache = BSTR_RWORD(timestamp, dst); + + if(len) + { + uint32 dst_preserve_mask; + uint32 dst_change_mask; + + dst_preserve_mask = (1U << dstoff) - 1; + + if((uint64)(dstoff + len) < 0x20) + dst_preserve_mask |= ((1U << ((0x20 - (dstoff + len)) & 0x1F)) - 1) << (dstoff + len); + + dst_change_mask = ~dst_preserve_mask; + + src_cache = BSTR_RWORD(timestamp, src); + src_cache |= (uint64)BSTR_RWORD(timestamp, src + 4) << 32; + dst_cache = BSTR_RWORD(timestamp, dst); + + dst_cache = (dst_cache & dst_preserve_mask) | ((dst_cache OP_THINGY_HERE (src_cache >> srcoff)) & dst_change_mask); + BSTR_WWORD(timestamp, dst, dst_cache); + + if((uint64)(dstoff + len) < 0x20) + { + srcoff += len; + dstoff += len; + len = 0; + } + else + { + srcoff += (0x20 - dstoff); + dstoff = 0; + len -= (0x20 - dstoff); + dst += 4; + } + + if(srcoff >= 0x20) + { + srcoff &= 0x1F; + src += 4; + + if(len) + { + src_cache >>= 32; + src_cache |= (uint64)BSTR_RWORD(timestamp, src + 4) << 32; + } + } + } + + DO_BSTR_PRIMARY(); // while(len >= 32) (do allow interruption; interrupt and emulator-return - + // they must be handled differently!) + while(len >= 32) + { + dst_cache = BSTR_RWORD(timestamp, dst); + dst_cache = OP_THINGY_HERE(dst_cache, src_cache >> srcoff); + BSTR_WWORD(timestamp, dst, dst_cache); + len -= 32; + dst += 4; + src += 4; + src_cache >>= 32; + src_cache |= (uint64)BSTR_RWORD(timestamp, src + 4) << 32; + } + + DO_BSTR_EPILOGUE(); // if(len) { blah blah blah masking blah } + if(len) + { + uint32 dst_preserve_mask; + uint32 dst_change_mask; + + dst_preserve_mask = (1U << ((0x20 - len) & 0x1F) << len; + dst_change_mask = ~dst_preserve_mask; + + dst_cache = BSTR_RWORD(timestamp, dst); + dst_cache = OP_THINGY_HERE(dst_cache, src_cache >> srcoff); + BSTR_WWORD(timestamp, dst, dst_cache); + dstoff += len; + srcoff += len; + + if(srcoff >= 0x20) + { + srcoff &= 0x1F; + src += 4; + } + len = 0; + } +#endif + + switch (sub_op) + { + case ORBSU: + DO_BSTR(BSTR_OP_OR); + break; + + case ANDBSU: + DO_BSTR(BSTR_OP_AND); + break; + + case XORBSU: + DO_BSTR(BSTR_OP_XOR); + break; + + case MOVBSU: + DO_BSTR(BSTR_OP_MOV); + break; + + case ORNBSU: + DO_BSTR(BSTR_OP_ORN); + break; + + case ANDNBSU: + DO_BSTR(BSTR_OP_ANDN); + break; + + case XORNBSU: + DO_BSTR(BSTR_OP_XORN); + break; + + case NOTBSU: + DO_BSTR(BSTR_OP_NOT); + break; + } + + P_REG[26] = dstoff; + P_REG[27] = srcoff; + P_REG[28] = len; + P_REG[29] = dst; + P_REG[30] = src; + + return ((bool)P_REG[28]); + } + else + { + printf("BSTR Search: %02x\n", sub_op); + return (Do_BSTR_Search(timestamp, ((sub_op & 1) ? -1 : 1), (sub_op & 0x2) >> 1)); + } + assert(0); + return (false); +} + +INLINE void V810::SetFPUOPNonFPUFlags(uint32 result) +{ + // Now, handle flag setting + SetFlag(PSW_OV, 0); + + if (!(result & 0x7FFFFFFF)) // Check to see if exponent and mantissa are 0 + { + // If Z flag is set, S and CY should be clear, even if it's negative 0(confirmed on real thing with subf.s, at least). + SetFlag(PSW_Z, 1); + SetFlag(PSW_S, 0); + SetFlag(PSW_CY, 0); + } + else + { + SetFlag(PSW_Z, 0); + SetFlag(PSW_S, result & 0x80000000); + SetFlag(PSW_CY, result & 0x80000000); + } + //printf("MEOW: %08x\n", S_REG[PSW] & (PSW_S | PSW_CY)); +} + +bool V810::FPU_DoesExceptionKillResult(void) +{ + const uint32 float_exception_flags = fpo.get_flags(); + + if (float_exception_flags & V810_FP_Ops::flag_reserved) + return (true); + + if (float_exception_flags & V810_FP_Ops::flag_invalid) + return (true); + + if (float_exception_flags & V810_FP_Ops::flag_divbyzero) + return (true); + + // Return false here, so that the result of this calculation IS put in the output register. + // Wrap the exponent on overflow, rather than generating an infinity. The wrapping behavior is specified in IEE 754 AFAIK, + // and is useful in cases where you divide a huge number + // by another huge number, and fix the result afterwards based on the number of overflows that occurred. Probably requires some custom assembly code, + // though. And it's the kind of thing you'd see in an engineering or physics program, not in a perverted video game :b). + if (float_exception_flags & V810_FP_Ops::flag_overflow) + return (false); + + return (false); +} + +void V810::FPU_DoException(void) +{ + const uint32 float_exception_flags = fpo.get_flags(); + + if (float_exception_flags & V810_FP_Ops::flag_reserved) + { + S_REG[PSW] |= PSW_FRO; + + SetPC(GetPC() - 4); + Exception(FPU_HANDLER_ADDR, ECODE_FRO); + + return; + } + + if (float_exception_flags & V810_FP_Ops::flag_invalid) + { + S_REG[PSW] |= PSW_FIV; + + SetPC(GetPC() - 4); + Exception(FPU_HANDLER_ADDR, ECODE_FIV); + + return; + } + + if (float_exception_flags & V810_FP_Ops::flag_divbyzero) + { + S_REG[PSW] |= PSW_FZD; + + SetPC(GetPC() - 4); + Exception(FPU_HANDLER_ADDR, ECODE_FZD); + + return; + } + + if (float_exception_flags & V810_FP_Ops::flag_underflow) + { + S_REG[PSW] |= PSW_FUD; + } + + if (float_exception_flags & V810_FP_Ops::flag_inexact) + { + S_REG[PSW] |= PSW_FPR; + } + + // + // FPR can be set along with overflow, so put the overflow exception handling at the end here(for Exception() messes with PSW). + // + if (float_exception_flags & V810_FP_Ops::flag_overflow) + { + S_REG[PSW] |= PSW_FOV; + + SetPC(GetPC() - 4); + Exception(FPU_HANDLER_ADDR, ECODE_FOV); + } +} + +bool V810::IsSubnormal(uint32 fpval) +{ + if (((fpval >> 23) & 0xFF) == 0 && (fpval & ((1 << 23) - 1))) + return (true); + + return (false); +} + +INLINE void V810::FPU_Math_Template(uint32 (V810_FP_Ops::*func)(uint32, uint32), uint32 arg1, uint32 arg2) +{ + uint32 result; + + fpo.clear_flags(); + result = (fpo.*func)(P_REG[arg1], P_REG[arg2]); + + if (!FPU_DoesExceptionKillResult()) + { + SetFPUOPNonFPUFlags(result); + SetPREG(arg1, result); + } + FPU_DoException(); +} + +void V810::fpu_subop(v810_timestamp_t ×tamp, int sub_op, int arg1, int arg2) +{ + //printf("FPU: %02x\n", sub_op); + if (VBMode) + { + switch (sub_op) + { + case XB: + timestamp++; // Unknown + P_REG[arg1] = (P_REG[arg1] & 0xFFFF0000) | ((P_REG[arg1] & 0xFF) << 8) | ((P_REG[arg1] & 0xFF00) >> 8); + return; + + case XH: + timestamp++; // Unknown + P_REG[arg1] = (P_REG[arg1] << 16) | (P_REG[arg1] >> 16); + return; + + // Does REV use arg1 or arg2 for the source register? + case REV: + timestamp++; // Unknown + printf("Revvie bits\n"); + { + // Public-domain code snippet from: http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel + uint32 v = P_REG[arg2]; // 32-bit word to reverse bit order + + // swap odd and even bits + v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1); + // swap consecutive pairs + v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2); + // swap nibbles ... + v = ((v >> 4) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 4); + // swap bytes + v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8); + // swap 2-byte long pairs + v = (v >> 16) | (v << 16); + + P_REG[arg1] = v; + } + return; + + case MPYHW: + timestamp += 9 - 1; // Unknown? + P_REG[arg1] = (int32)(int16)(P_REG[arg1] & 0xFFFF) * (int32)(int16)(P_REG[arg2] & 0xFFFF); + return; + } + } + + switch (sub_op) + { + // Virtual-Boy specific(probably!) + default: + { + SetPC(GetPC() - 4); + Exception(INVALID_OP_HANDLER_ADDR, ECODE_INVALID_OP); + } + break; + + case CVT_WS: + timestamp += 5; + { + uint32 result; + + fpo.clear_flags(); + result = fpo.itof(P_REG[arg2]); + + if (!FPU_DoesExceptionKillResult()) + { + SetPREG(arg1, result); + SetFPUOPNonFPUFlags(result); + } + FPU_DoException(); + } + break; // End CVT.WS + + case CVT_SW: + timestamp += 8; + { + int32 result; + + fpo.clear_flags(); + result = fpo.ftoi(P_REG[arg2], false); + + if (!FPU_DoesExceptionKillResult()) + { + SetPREG(arg1, result); + SetFlag(PSW_OV, 0); + SetSZ(result); + } + FPU_DoException(); + } + break; // End CVT.SW + + case ADDF_S: + timestamp += 8; + FPU_Math_Template(&V810_FP_Ops::add, arg1, arg2); + break; + + case SUBF_S: + timestamp += 11; + FPU_Math_Template(&V810_FP_Ops::sub, arg1, arg2); + break; + + case CMPF_S: + timestamp += 6; + // Don't handle this like subf.s because the flags + // have slightly different semantics(mostly regarding underflow/subnormal results) (confirmed on real V810). + fpo.clear_flags(); + { + int32 result; + + result = fpo.cmp(P_REG[arg1], P_REG[arg2]); + + if (!FPU_DoesExceptionKillResult()) + { + SetFPUOPNonFPUFlags(result); + } + FPU_DoException(); + } + break; + + case MULF_S: + timestamp += 7; + FPU_Math_Template(&V810_FP_Ops::mul, arg1, arg2); + break; + + case DIVF_S: + timestamp += 43; + FPU_Math_Template(&V810_FP_Ops::div, arg1, arg2); + break; + + case TRNC_SW: + timestamp += 7; + { + int32 result; + + fpo.clear_flags(); + result = fpo.ftoi(P_REG[arg2], true); + + if (!FPU_DoesExceptionKillResult()) + { + SetPREG(arg1, result); + SetFlag(PSW_OV, 0); + SetSZ(result); + } + FPU_DoException(); + } + break; // end TRNC.SW + } +} + +// Generate exception +void V810::Exception(uint32 handler, uint16 eCode) +{ +// Exception overhead is unknown. + printf("Exception: %08x %04x\n", handler, eCode); + + // Invalidate our bitstring state(forces the instruction to be re-read, and the r/w buffers reloaded). + in_bstr = FALSE; + have_src_cache = FALSE; + have_dst_cache = FALSE; + + if (S_REG[PSW] & PSW_NP) // Fatal exception + { + printf("Fatal exception; Code: %08x, ECR: %08x, PSW: %08x, PC: %08x\n", eCode, S_REG[ECR], S_REG[PSW], PC); + Halted = HALT_FATAL_EXCEPTION; + IPendingCache = 0; + return; + } + else if (S_REG[PSW] & PSW_EP) //Double Exception + { + S_REG[FEPC] = GetPC(); + S_REG[FEPSW] = S_REG[PSW]; + + S_REG[ECR] = (S_REG[ECR] & 0xFFFF) | (eCode << 16); + S_REG[PSW] |= PSW_NP; + S_REG[PSW] |= PSW_ID; + S_REG[PSW] &= ~PSW_AE; + + SetPC(0xFFFFFFD0); + IPendingCache = 0; + return; + } + else // Regular exception + { + S_REG[EIPC] = GetPC(); + S_REG[EIPSW] = S_REG[PSW]; + S_REG[ECR] = (S_REG[ECR] & 0xFFFF0000) | eCode; + S_REG[PSW] |= PSW_EP; + S_REG[PSW] |= PSW_ID; + S_REG[PSW] &= ~PSW_AE; + + SetPC(handler); + IPendingCache = 0; + return; + } +} diff --git a/waterbox/vb/v810/v810_cpu.h b/waterbox/vb/v810/v810_cpu.h new file mode 100644 index 0000000000..82a1d738c0 --- /dev/null +++ b/waterbox/vb/v810/v810_cpu.h @@ -0,0 +1,335 @@ +//////////////////////////////////////////////////////////////// +// Defines for the V810 CPU + +#pragma once + +#include + +typedef int32 v810_timestamp_t; + +#define V810_FAST_MAP_SHIFT 16 +#define V810_FAST_MAP_PSIZE (1 << V810_FAST_MAP_SHIFT) +#define V810_FAST_MAP_TRAMPOLINE_SIZE 1024 + +// Exception codes +enum +{ + ECODE_TRAP_BASE = 0xFFA0, + ECODE_INVALID_OP = 0xFF90, + ECODE_ZERO_DIV = 0xFF80, // Integer divide by 0 + ECODE_FIV = 0xFF70, // Floating point invalid operation + ECODE_FZD = 0xFF68, // Floating point zero division + ECODE_FOV = 0xFF64, // Floating point overflow + //#define ECODE_FUD 0xFF62 // Floating point underflow(unused on V810) + //#define ECODE_FPR 0xFF61 // Floating point precision degradation(unused on V810) + ECODE_FRO = 0xFF60 // Floating point reserved operand +}; + +enum +{ + INVALID_OP_HANDLER_ADDR = 0xFFFFFF90, // Invalid opcode/instruction code! + ZERO_DIV_HANDLER_ADDR = 0xFFFFFF80, // Integer divide by 0 exception + FPU_HANDLER_ADDR = 0xFFFFFF60, // FPU exception + TRAP_HANDLER_BASE = 0xFFFFFFA0 // TRAP instruction +}; + +//System Register Defines (these are the only valid system registers!) +#define EIPC 0 //Exeption/Interupt PC +#define EIPSW 1 //Exeption/Interupt PSW + +#define FEPC 2 //Fatal Error PC +#define FEPSW 3 //Fatal Error PSW + +#define ECR 4 //Exception Cause Register +#define PSW 5 //Program Status Word +#define PIR 6 //Processor ID Register +#define TKCW 7 //Task Controll Word +#define CHCW 24 //Cashe Controll Word +#define ADDTRE 25 //ADDTRE + +//PSW Specifics +#define PSW_IA 0xF0000 // All Interupt bits... +#define PSW_I3 0x80000 +#define PSW_I2 0x40000 +#define PSW_I1 0x20000 +#define PSW_I0 0x10000 + +#define PSW_NP 0x08000 +#define PSW_EP 0x04000 + +#define PSW_AE 0x02000 + +#define PSW_ID 0x01000 + +#define PSW_FRO 0x00200 // Floating point reserved operand(set on denormal, NaN, or indefinite) +#define PSW_FIV 0x00100 // Floating point invalid operation(set when trying to convert a number too large to an (un)signed integer) + +#define PSW_FZD 0x00080 // Floating point divide by zero +#define PSW_FOV 0x00040 // Floating point overflow +#define PSW_FUD 0x00020 // Floating point underflow +#define PSW_FPR 0x00010 // Floating point precision degradation + +#define PSW_CY 0x00008 +#define PSW_OV 0x00004 +#define PSW_S 0x00002 +#define PSW_Z 0x00001 + +//condition codes +#define COND_V 0 +#define COND_C 1 +#define COND_Z 2 +#define COND_NH 3 +#define COND_S 4 +#define COND_T 5 +#define COND_LT 6 +#define COND_LE 7 +#define COND_NV 8 +#define COND_NC 9 +#define COND_NZ 10 +#define COND_H 11 +#define COND_NS 12 +#define COND_F 13 +#define COND_GE 14 +#define COND_GT 15 + +#define TESTCOND_V (S_REG[PSW] & PSW_OV) + +#define TESTCOND_L (S_REG[PSW] & PSW_CY) +#define TESTCOND_C TESTCOND_L + +#define TESTCOND_E (S_REG[PSW] & PSW_Z) +#define TESTCOND_Z TESTCOND_E + +#define TESTCOND_NH ((S_REG[PSW] & PSW_Z) || (S_REG[PSW] & PSW_CY)) +#define TESTCOND_N (S_REG[PSW] & PSW_S) +#define TESTCOND_S TESTCOND_N + +#define TESTCOND_LT ((!!(S_REG[PSW] & PSW_S)) ^ (!!(S_REG[PSW] & PSW_OV))) +#define TESTCOND_LE (((!!(S_REG[PSW] & PSW_S)) ^ (!!(S_REG[PSW] & PSW_OV))) || (S_REG[PSW] & PSW_Z)) +#define TESTCOND_NV (!(S_REG[PSW] & PSW_OV)) + +#define TESTCOND_NL (!(S_REG[PSW] & PSW_CY)) +#define TESTCOND_NC TESTCOND_NL + +#define TESTCOND_NE (!(S_REG[PSW] & PSW_Z)) +#define TESTCOND_NZ TESTCOND_NE + +#define TESTCOND_H (!((S_REG[PSW] & PSW_Z) || (S_REG[PSW] & PSW_CY))) +#define TESTCOND_P (!(S_REG[PSW] & PSW_S)) +#define TESTCOND_NS TESTCOND_P + +#define TESTCOND_GE (!((!!(S_REG[PSW] & PSW_S)) ^ (!!(S_REG[PSW] & PSW_OV)))) +#define TESTCOND_GT (!(((!!(S_REG[PSW] & PSW_S)) ^ (!!(S_REG[PSW] & PSW_OV))) || (S_REG[PSW] & PSW_Z))) + +// Tag layout +// Bit 0-21: TAG31-TAG10 +// Bit 22-23: Validity bits(one for each 4-byte subblock) +// Bit 24-27: NECRV("Reserved") +// Bit 28-31: 0 + +typedef enum { + V810_EMU_MODE_FAST = 0, + V810_EMU_MODE_ACCURATE = 1, + _V810_EMU_MODE_COUNT +} V810_Emu_Mode; + +class V810 +{ + public: + V810() + MDFN_COLD; + ~V810() MDFN_COLD; + + // Pass TRUE for vb_mode if we're emulating a VB-specific enhanced V810 CPU core + bool Init(V810_Emu_Mode mode, bool vb_mode) MDFN_COLD; + void Kill(void) MDFN_COLD; + + void SetInt(int level); + + void SetMemWriteBus32(uint8 A, bool value) MDFN_COLD; + void SetMemReadBus32(uint8 A, bool value) MDFN_COLD; + + void SetMemReadHandlers(uint8 MDFN_FASTCALL (*read8)(v810_timestamp_t &, uint32), uint16 MDFN_FASTCALL (*read16)(v810_timestamp_t &, uint32), uint32 MDFN_FASTCALL (*read32)(v810_timestamp_t &, uint32)) MDFN_COLD; + void SetMemWriteHandlers(void MDFN_FASTCALL (*write8)(v810_timestamp_t &, uint32, uint8), void MDFN_FASTCALL (*write16)(v810_timestamp_t &, uint32, uint16), void MDFN_FASTCALL (*write32)(v810_timestamp_t &, uint32, uint32)) MDFN_COLD; + + void SetIOReadHandlers(uint8 MDFN_FASTCALL (*read8)(v810_timestamp_t &, uint32), uint16 MDFN_FASTCALL (*read16)(v810_timestamp_t &, uint32), uint32 MDFN_FASTCALL (*read32)(v810_timestamp_t &, uint32)) MDFN_COLD; + void SetIOWriteHandlers(void MDFN_FASTCALL (*write8)(v810_timestamp_t &, uint32, uint8), void MDFN_FASTCALL (*write16)(v810_timestamp_t &, uint32, uint16), void MDFN_FASTCALL (*write32)(v810_timestamp_t &, uint32, uint32)) MDFN_COLD; + + // Length specifies the number of bytes to map in, at each location specified by addresses[] (for mirroring) + uint8 *SetFastMap(uint32 addresses[], uint32 length, unsigned int num_addresses, const char *name) MDFN_COLD; + + INLINE void ResetTS(v810_timestamp_t new_base_timestamp) + { + assert(next_event_ts > v810_timestamp); + + next_event_ts -= (v810_timestamp - new_base_timestamp); + v810_timestamp = new_base_timestamp; + } + + INLINE void SetEventNT(const v810_timestamp_t timestamp) + { + next_event_ts = timestamp; + } + + INLINE v810_timestamp_t GetEventNT(void) + { + return (next_event_ts); + } + + v810_timestamp_t Run(int32 MDFN_FASTCALL (*event_handler)(const v810_timestamp_t timestamp)); + void Exit(void); + + void Reset(void) MDFN_COLD; + + enum + { + GSREG_PR = 0, + GSREG_SR = 32, + GSREG_PC = 64, + GSREG_TIMESTAMP + }; + + uint32 GetRegister(unsigned int which, char *special, const uint32 special_len); + void SetRegister(unsigned int which, uint32 value); + + uint32 GetPC(void); + void SetPC(uint32); + + INLINE uint32 GetPR(unsigned int which) + { + return which ? P_REG[which] : 0; + } + + private: + // Make sure P_REG[] is the first variable/array in this class, so non-zerfo offset encoding(at assembly level) isn't necessary to access it. + uint32 P_REG[32]; // Program registers pr0-pr31 + uint32 S_REG[32]; // System registers sr0-sr31 + uint32 PC; + uint8 *PC_ptr; + uint8 *PC_base; + + uint32 IPendingCache; + void RecalcIPendingCache(void); + + public: + v810_timestamp_t v810_timestamp; // Will never be less than 0. + + private: + v810_timestamp_t next_event_ts; + + enum + { + LASTOP_NORMAL = 0, + LASTOP_LOAD = 1, + LASTOP_STORE = 2, + LASTOP_IN = 3, + LASTOP_OUT = 4, + LASTOP_HEAVY_MATH = 5 + }; + + V810_Emu_Mode EmuMode; + bool VBMode; + + void Run_Fast(int32 MDFN_FASTCALL (*event_handler)(const v810_timestamp_t timestamp)) NO_INLINE; + void Run_Accurate(int32 MDFN_FASTCALL (*event_handler)(const v810_timestamp_t timestamp)) NO_INLINE; + + uint8 MDFN_FASTCALL (*MemRead8)(v810_timestamp_t ×tamp, uint32 A); + uint16 MDFN_FASTCALL (*MemRead16)(v810_timestamp_t ×tamp, uint32 A); + uint32 MDFN_FASTCALL (*MemRead32)(v810_timestamp_t ×tamp, uint32 A); + + void MDFN_FASTCALL (*MemWrite8)(v810_timestamp_t ×tamp, uint32 A, uint8 V); + void MDFN_FASTCALL (*MemWrite16)(v810_timestamp_t ×tamp, uint32 A, uint16 V); + void MDFN_FASTCALL (*MemWrite32)(v810_timestamp_t ×tamp, uint32 A, uint32 V); + + uint8 MDFN_FASTCALL (*IORead8)(v810_timestamp_t ×tamp, uint32 A); + uint16 MDFN_FASTCALL (*IORead16)(v810_timestamp_t ×tamp, uint32 A); + uint32 MDFN_FASTCALL (*IORead32)(v810_timestamp_t ×tamp, uint32 A); + + void MDFN_FASTCALL (*IOWrite8)(v810_timestamp_t ×tamp, uint32 A, uint8 V); + void MDFN_FASTCALL (*IOWrite16)(v810_timestamp_t ×tamp, uint32 A, uint16 V); + void MDFN_FASTCALL (*IOWrite32)(v810_timestamp_t ×tamp, uint32 A, uint32 V); + + bool MemReadBus32[256]; // Corresponding to the upper 8 bits of the memory address map. + bool MemWriteBus32[256]; + + int32 lastop; // Set to -1 on FP/MUL/DIV, 0x100 on LD, 0x200 on ST, 0x400 on in, 0x800 on out, and the actual opcode * 2(or >= 0) on everything else. + +#define LASTOP_LD 0x100 +#define LASTOP_ST 0x200 +#define LASTOP_IN 0x400 +#define LASTOP_OUT 0x800 + + enum + { + HALT_NONE = 0, + HALT_HALT = 1, + HALT_FATAL_EXCEPTION = 2 + }; + + uint8 Halted; + + bool Running; + + int ilevel; + + bool in_bstr; + uint16 in_bstr_to; + + bool bstr_subop(v810_timestamp_t ×tamp, int sub_op, int arg1); + void fpu_subop(v810_timestamp_t ×tamp, int sub_op, int arg1, int arg2); + + void Exception(uint32 handler, uint16 eCode); + + // Caching-related: + typedef struct + { + uint32 tag; + uint32 data[2]; + bool data_valid[2]; + } V810_CacheEntry_t; + + V810_CacheEntry_t Cache[128]; + + // Bitstring variables. + uint32 src_cache; + uint32 dst_cache; + bool have_src_cache, have_dst_cache; + + uint8 *FastMap[(1ULL << 32) / V810_FAST_MAP_PSIZE]; + std::vector> FastMapAllocList; + + // For CacheDump and CacheRestore + void CacheOpMemStore(v810_timestamp_t ×tamp, uint32 A, uint32 V); + uint32 CacheOpMemLoad(v810_timestamp_t ×tamp, uint32 A); + + void CacheClear(v810_timestamp_t ×tamp, uint32 start, uint32 count); + void CacheDump(v810_timestamp_t ×tamp, const uint32 SA); + void CacheRestore(v810_timestamp_t ×tamp, const uint32 SA); + + uint32 RDCACHE(v810_timestamp_t ×tamp, uint32 addr); + // + // End caching related + // + + uint16 RDOP(v810_timestamp_t ×tamp, uint32 addr, uint32 meow = 2); + void SetFlag(uint32 n, bool condition); + void SetSZ(uint32 value); + + void SetSREG(v810_timestamp_t ×tamp, unsigned int which, uint32 value); + uint32 GetSREG(unsigned int which); + + bool IsSubnormal(uint32 fpval); + void FPU_Math_Template(uint32 (V810_FP_Ops::*func)(uint32, uint32), uint32 arg1, uint32 arg2); + void FPU_DoException(void); + bool CheckFPInputException(uint32 fpval); + bool FPU_DoesExceptionKillResult(void); + void SetFPUOPNonFPUFlags(uint32 result); + + uint32 BSTR_RWORD(v810_timestamp_t ×tamp, uint32 A); + void BSTR_WWORD(v810_timestamp_t ×tamp, uint32 A, uint32 V); + bool Do_BSTR_Search(v810_timestamp_t ×tamp, const int inc_mul, unsigned int bit_test); + + V810_FP_Ops fpo; + + uint8 DummyRegion[V810_FAST_MAP_PSIZE + V810_FAST_MAP_TRAMPOLINE_SIZE]; +}; diff --git a/waterbox/vb/v810/v810_do_am.h b/waterbox/vb/v810/v810_do_am.h new file mode 100644 index 0000000000..9cc01568d3 --- /dev/null +++ b/waterbox/vb/v810/v810_do_am.h @@ -0,0 +1,72 @@ +#define DO_MOV_AM(); DO_AM_I(); +#define DO_ADD_AM(); DO_AM_I(); +#define DO_SUB_AM(); DO_AM_I(); +#define DO_CMP_AM(); DO_AM_I(); +#define DO_SHL_AM(); DO_AM_I(); +#define DO_SHR_AM(); DO_AM_I(); +#define DO_JMP_AM(); DO_AM_I(); +#define DO_SAR_AM(); DO_AM_I(); +#define DO_MUL_AM(); DO_AM_I(); +#define DO_DIV_AM(); DO_AM_I(); +#define DO_MULU_AM(); DO_AM_I(); +#define DO_DIVU_AM(); DO_AM_I(); +#define DO_OR_AM(); DO_AM_I(); +#define DO_AND_AM(); DO_AM_I(); +#define DO_XOR_AM(); DO_AM_I(); +#define DO_NOT_AM(); DO_AM_I(); +#define DO_MOV_I_AM(); DO_AM_II(); +#define DO_ADD_I_AM(); DO_AM_II(); +#define DO_SETF_AM(); DO_AM_II(); +#define DO_CMP_I_AM(); DO_AM_II(); +#define DO_SHL_I_AM(); DO_AM_II(); +#define DO_SHR_I_AM(); DO_AM_II(); +#define DO_EI_AM(); DO_AM_II(); +#define DO_SAR_I_AM(); DO_AM_II(); +#define DO_TRAP_AM(); DO_AM_II(); +#define DO_RETI_AM(); DO_AM_IX(); +#define DO_HALT_AM(); DO_AM_IX(); +#define DO_LDSR_AM(); DO_AM_II(); +#define DO_STSR_AM(); DO_AM_II(); +#define DO_DI_AM(); DO_AM_II(); +#define DO_BSTR_AM(); DO_AM_BSTR(); +#define DO_MOVEA_AM(); DO_AM_V(); +#define DO_ADDI_AM(); DO_AM_V(); +#define DO_JR_AM(); DO_AM_IV(); +#define DO_JAL_AM(); DO_AM_IV(); +#define DO_ORI_AM(); DO_AM_V(); +#define DO_ANDI_AM(); DO_AM_V(); +#define DO_XORI_AM(); DO_AM_V(); +#define DO_MOVHI_AM(); DO_AM_V(); +#define DO_LD_B_AM(); DO_AM_VIa(); +#define DO_LD_H_AM(); DO_AM_VIa(); +#define DO_LD_W_AM(); DO_AM_VIa(); +#define DO_ST_B_AM(); DO_AM_VIb(); +#define DO_ST_H_AM(); DO_AM_VIb(); +#define DO_ST_W_AM(); DO_AM_VIb(); +#define DO_IN_B_AM(); DO_AM_VIa(); +#define DO_IN_H_AM(); DO_AM_VIa(); +#define DO_CAXI_AM(); DO_AM_VIa(); +#define DO_IN_W_AM(); DO_AM_VIa(); +#define DO_OUT_B_AM(); DO_AM_VIb(); +#define DO_OUT_H_AM(); DO_AM_VIb(); +#define DO_FPP_AM(); DO_AM_FPP(); +#define DO_OUT_W_AM(); DO_AM_VIb(); +#define DO_BV_AM(); DO_AM_III(); +#define DO_BL_AM(); DO_AM_III(); +#define DO_BE_AM(); DO_AM_III(); +#define DO_BNH_AM(); DO_AM_III(); +#define DO_BN_AM(); DO_AM_III(); +#define DO_BR_AM(); DO_AM_III(); +#define DO_BLT_AM(); DO_AM_III(); +#define DO_BLE_AM(); DO_AM_III(); +#define DO_BNV_AM(); DO_AM_III(); +#define DO_BNL_AM(); DO_AM_III(); +#define DO_BNE_AM(); DO_AM_III(); +#define DO_BH_AM(); DO_AM_III(); +#define DO_BP_AM(); DO_AM_III(); +#define DO_NOP_AM(); DO_AM_III(); +#define DO_BGE_AM(); DO_AM_III(); +#define DO_BGT_AM(); DO_AM_III(); + + +#define DO_INVALID_AM(); DO_AM_UDEF(); diff --git a/waterbox/vb/v810/v810_fp_ops.cpp b/waterbox/vb/v810/v810_fp_ops.cpp new file mode 100644 index 0000000000..53aa937fae --- /dev/null +++ b/waterbox/vb/v810/v810_fp_ops.cpp @@ -0,0 +1,405 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* v810_fp_ops.cpp: +** Copyright (C) 2014-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "vb.h" + +bool V810_FP_Ops::fp_is_zero(uint32 v) +{ + return ((v & 0x7FFFFFFF) == 0); +} + +#if 0 +bool V810_FP_Ops::fp_is_nan(uint32 v) +{ + return((v & 0x7FFFFFFF) > (255 << 23)); +} + +bool V810_FP_Ops::fp_is_inf(uint32 v) +{ + return((v & 0x7FFFFFFF) == (255 << 23)); +} +#endif + +bool V810_FP_Ops::fp_is_inf_nan_sub(uint32 v) +{ + if ((v & 0x7FFFFFFF) == 0) + return (false); + + switch ((v >> 23) & 0xFF) + { + case 0x00: + case 0xff: + return (true); + } + return (false); +} + +void V810_FP_Ops::fpim_decode(fpim *df, uint32 v) +{ + df->exp = ((v >> 23) & 0xFF) - 127; + df->f = (v & 0x7FFFFF) | ((v & 0x7FFFFFFF) ? 0x800000 : 0); + df->sign = v >> 31; +} + +void V810_FP_Ops::fpim_round(fpim *df) +{ + int vbc = 64 - MDFN_lzcount64(df->f); + + if (vbc > 24) + { + const unsigned sa = vbc - 24; + + if (1) // round to nearest + { + uint64 old_f = df->f; + + df->f = (df->f + ((df->f >> sa) & 1) + ((1ULL << (sa - 1)) - 1)) & ~((1ULL << sa) - 1); + + if (df->f != old_f) + { + //printf("Inexact mr\n"); + exception_flags |= flag_inexact; + } + } + else + abort(); + } +} + +void V810_FP_Ops::fpim_round_int(fpim *df, bool truncate) +{ + if (df->exp < 23) + { + const unsigned sa = 23 - df->exp; + uint64 old_f = df->f; + + //if(sa >= 2) + // printf("RI: %lld, %d\n", df->f, sa); + + // round to nearest + if (sa > 24) + df->f = 0; + else + { + if (truncate) + df->f = df->f & ~((1ULL << sa) - 1); + else + df->f = (df->f + ((df->f >> sa) & 1) + ((1ULL << (sa - 1)) - 1)) & ~((1ULL << sa) - 1); + } + + if (df->f != old_f) + { + //printf("Inexact\n"); + exception_flags |= flag_inexact; + } + } +} + +uint32 V810_FP_Ops::fpim_encode(fpim *df) +{ + const int lzc = MDFN_lzcount64(df->f); + int tmp_exp = df->exp - lzc; + uint64 tmp_walrus = df->f << (lzc & 0x3F); + int tmp_sign = df->sign; + + tmp_exp += 40; + tmp_walrus >>= 40; + + if (tmp_walrus == 0) + tmp_exp = -127; + else if (tmp_exp <= -127) + { + exception_flags |= flag_underflow | flag_inexact; + //printf("Subnormal: %lld. %d\n", tmp_walrus, tmp_exp); + if (1) + { + tmp_exp = -127; + tmp_walrus = 0; + } + else + { + tmp_walrus >>= -(tmp_exp + 126); + tmp_exp = -127; + } + } + else if (tmp_exp >= 128) + { + exception_flags |= flag_overflow; + //printf("Overflow!\n"); + + if (1) + tmp_exp -= 192; + else + { + tmp_exp = 128; + tmp_walrus = 0; + } + } + return (tmp_sign << 31) | ((tmp_exp + 127) << 23) | (tmp_walrus & 0x7FFFFF); +} + +uint32 V810_FP_Ops::mul(uint32 a, uint32 b) +{ + fpim ins[2]; + fpim res; + + if (fp_is_inf_nan_sub(a) || fp_is_inf_nan_sub(b)) + { + exception_flags |= flag_reserved; + return (~0U); + } + + fpim_decode(&ins[0], a); + fpim_decode(&ins[1], b); + + //printf("%08x %08x - %d %d %d - %d %d %d\n", a, b, a_exp, a_walrus, a_sign, b_exp, b_walrus, b_sign); + + res.exp = ins[0].exp + ins[1].exp - 23; + res.f = ins[0].f * ins[1].f; + res.sign = ins[0].sign ^ ins[1].sign; + + fpim_round(&res); + + return fpim_encode(&res); +} + +uint32 V810_FP_Ops::add(uint32 a, uint32 b) +{ + fpim ins[2]; + fpim res; + int64 ft[2]; + int64 tr; + int max_exp; + + if (fp_is_inf_nan_sub(a) || fp_is_inf_nan_sub(b)) + { + exception_flags |= flag_reserved; + return (~0U); + } + + if (a == b && !(a & 0x7FFFFFFF)) + { + return (a & 0x80000000); + } + + fpim_decode(&ins[0], a); + fpim_decode(&ins[1], b); + + max_exp = std::max(ins[0].exp, ins[1].exp); + + //printf("%d:%08llx %d:%08llx\n", ins[0].exp, ins[0].f, ins[1].exp, ins[1].f); + + for (unsigned i = 0; i < 2; i++) + { + unsigned sd = (max_exp - ins[i].exp); + + ft[i] = ins[i].f << 24; + + if (sd >= 48) + { + if (ft[i] != 0) + ft[i] = 1; + } + else + { + int64 nft = ft[i] >> sd; + + if (ft[i] != (nft << sd)) + { + nft |= 1; + } + //{ + // puts("FPR"); + // } + + ft[i] = nft; + } + + if (ins[i].sign) + ft[i] = -ft[i]; + } + + //printf("SOON: %08llx %08llx\n", ft[0], ft[1]); + + tr = ft[0] + ft[1]; + if (tr < 0) + { + tr = -tr; + res.sign = true; + } + else + res.sign = false; + + res.f = tr; + res.exp = max_exp - 24; + + fpim_round(&res); + + return fpim_encode(&res); +} + +uint32 V810_FP_Ops::sub(uint32 a, uint32 b) +{ + return add(a, b ^ 0x80000000); +} + +uint32 V810_FP_Ops::div(uint32 a, uint32 b) +{ + fpim ins[2]; + fpim res; + uint64 mtmp; + + if (fp_is_inf_nan_sub(a) || fp_is_inf_nan_sub(b)) + { + exception_flags |= flag_reserved; + return (~0U); + } + + if (fp_is_zero(a) && fp_is_zero(b)) + { + exception_flags |= flag_invalid; + return (~0U); + } + + fpim_decode(&ins[0], a); + fpim_decode(&ins[1], b); + + res.sign = ins[0].sign ^ ins[1].sign; + + if (ins[1].f == 0) + { + //puts("Divide by zero!"); + exception_flags |= flag_divbyzero; + return ((res.sign << 31) | (255 << 23)); + } + else + { + res.exp = ins[0].exp - ins[1].exp - 2 - 1; // + 23 - 2; + res.f = ((ins[0].f << 24) / ins[1].f) << 2; + mtmp = ((ins[0].f << 24) % ins[1].f) << 1; + + //printf("%lld %lld\n", (ins[0].f << 23) % ins[1].f, ins[1].f); + + if (mtmp > ins[1].f) + res.f |= 3; + else if (mtmp == ins[1].f) + res.f |= 2; + else if (mtmp > 0) + res.f |= 1; + } + + fpim_round(&res); + + return fpim_encode(&res); +} + +int V810_FP_Ops::cmp(uint32 a, uint32 b) +{ + fpim ins[2]; + + if (fp_is_inf_nan_sub(a) || fp_is_inf_nan_sub(b)) + { + exception_flags |= flag_reserved; + return (~0U); + } + + fpim_decode(&ins[0], a); + fpim_decode(&ins[1], b); + + if (ins[0].exp > ins[1].exp) + return (ins[0].sign ? -1 : 1); + + if (ins[0].exp < ins[1].exp) + return (ins[1].sign ? 1 : -1); + + if (ins[0].f > ins[1].f) + return (ins[0].sign ? -1 : 1); + + if (ins[0].f < ins[1].f) + return (ins[1].sign ? 1 : -1); + + if ((ins[0].sign ^ ins[1].sign) && ins[0].f != 0) + return (ins[0].sign ? -1 : 1); + + return (0); +} + +uint32 V810_FP_Ops::itof(uint32 v) +{ + fpim res; + + res.sign = (bool)(v & 0x80000000); + res.exp = 23; + res.f = res.sign ? (0x80000000 - (v & 0x7FFFFFFF)) : (v & 0x7FFFFFFF); + + fpim_round(&res); + + return fpim_encode(&res); +} + +uint32 V810_FP_Ops::ftoi(uint32 v, bool truncate) +{ + fpim ins; + int sa; + int ret; + + if (fp_is_inf_nan_sub(v)) + { + exception_flags |= flag_reserved; + return (~0U); + } + + fpim_decode(&ins, v); + fpim_round_int(&ins, truncate); + + sa = ins.exp - 23; + + if (sa < 0) + { + if (sa <= -32) + ret = 0; + else + ret = ins.f >> -sa; + } + else + { + if (sa >= 8) + { + if (sa == 8 && ins.f == 0x800000 && ins.sign) + return (0x80000000); + else + { + ret = ~0U; + exception_flags |= flag_invalid; + } + } + else + { + ret = ins.f << sa; + } + } + //printf("%d\n", sa); + + if (ins.sign) + ret = -ret; + + return (ret); +} diff --git a/waterbox/vb/v810/v810_fp_ops.h b/waterbox/vb/v810/v810_fp_ops.h new file mode 100644 index 0000000000..20c607e7d2 --- /dev/null +++ b/waterbox/vb/v810/v810_fp_ops.h @@ -0,0 +1,74 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* v810_fp_ops.h: +** Copyright (C) 2014-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +class V810_FP_Ops +{ + public: + uint32 mul(uint32 a, uint32 b); + uint32 div(uint32 a, uint32 b); + uint32 add(uint32 a, uint32 b); + uint32 sub(uint32 a, uint32 b); + int cmp(uint32 a, uint32 b); + + uint32 itof(uint32 v); + uint32 ftoi(uint32 v, bool truncate); + + enum + { + flag_invalid = 0x0001, + flag_divbyzero = 0x0002, + flag_overflow = 0x0004, + flag_underflow = 0x0008, + flag_inexact = 0x0010, + flag_reserved = 0x0020 + }; + + inline uint32 get_flags(void) + { + return exception_flags; + } + + inline void clear_flags(void) + { + exception_flags = 0; + } + + private: + unsigned exception_flags; + + struct fpim + { + uint64 f; + int exp; + bool sign; + }; + + bool fp_is_zero(uint32 v); + bool fp_is_inf_nan_sub(uint32 v); + + unsigned clz64(uint64 v); + void fpim_decode(fpim *df, uint32 v); + void fpim_round(fpim *df); + void fpim_round_int(fpim *df, bool truncate = false); + uint32 fpim_encode(fpim *df); +}; diff --git a/waterbox/vb/v810/v810_oploop.inc b/waterbox/vb/v810/v810_oploop.inc new file mode 100644 index 0000000000..6b5015c744 --- /dev/null +++ b/waterbox/vb/v810/v810_oploop.inc @@ -0,0 +1,1130 @@ +/* V810 Emulator + * + * Copyright (C) 2006 David Tucker + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + + // Macro test taken from http://gcc.gnu.org/viewcvs/trunk/gcc/testsuite/gcc.dg/20020919-1.c?view=markup&pathrev=142696 + //#if defined (__powerpc__) || defined (__PPC__) || defined (__ppc__) || defined (__POWERPC__) || defined (PPC) || defined (_IBMR2) + // register v810_timestamp_t timestamp_rl asm("15") = v810_timestamp; + //#elif defined(__x86_64__) + // register v810_timestamp_t timestamp_rl asm("r11") = v810_timestamp; + //#else + register v810_timestamp_t timestamp_rl = v810_timestamp; + //#endif + + uint32 opcode; + uint32 tmp2; + int val = 0; + + + #define ADDCLOCK(__n) { timestamp += __n; } + + #define CHECK_HALTED(); { if(Halted && timestamp < next_event_ts) { timestamp = next_event_ts; } } + + while(Running) + { + #ifdef RB_DEBUGMODE + uint32 old_PC = RB_GETPC(); + #endif + uint32 tmpop; + + assert(timestamp_rl <= next_event_ts); + + if(!IPendingCache) + { + if(Halted) + { + timestamp_rl = next_event_ts; + } + else if(in_bstr) + { + tmpop = in_bstr_to; + opcode = tmpop >> 9; + goto op_BSTR; + } + } + + while(timestamp_rl < next_event_ts) + { + #ifdef RB_DEBUGMODE + old_PC = RB_GETPC(); + #endif + + P_REG[0] = 0; //Zero the Zero Reg!!! + + RB_CPUHOOK(RB_GETPC()); + + { + //printf("%08x\n", RB_GETPC()); + { + v810_timestamp_t timestamp = timestamp_rl; + + tmpop = RB_RDOP(0, 0); + + timestamp_rl = timestamp; + } + + opcode = (tmpop >> 9) | IPendingCache; + + //printf("%02x\n", opcode >> 1); +#if HAVE_COMPUTED_GOTO + #define CGBEGIN static const void *const op_goto_table[256] = { + #define CGE(l) &&l, + #define CGEND }; goto *op_goto_table[opcode]; +#else + /* (uint8) cast for cheaper alternative to generated branch+compare bounds check instructions, but still more + expensive than computed goto which needs no masking nor bounds checking. + */ + #define CGBEGIN { enum { CGESB = 1 + __COUNTER__ }; switch((uint8)opcode) { + #define CGE(l) case __COUNTER__ - CGESB: goto l; + #define CGEND } } +#endif + + CGBEGIN + CGE(op_MOV) CGE(op_MOV) CGE(op_ADD) CGE(op_ADD) CGE(op_SUB) CGE(op_SUB) CGE(op_CMP) CGE(op_CMP) + CGE(op_SHL) CGE(op_SHL) CGE(op_SHR) CGE(op_SHR) CGE(op_JMP) CGE(op_JMP) CGE(op_SAR) CGE(op_SAR) + CGE(op_MUL) CGE(op_MUL) CGE(op_DIV) CGE(op_DIV) CGE(op_MULU) CGE(op_MULU) CGE(op_DIVU) CGE(op_DIVU) + CGE(op_OR) CGE(op_OR) CGE(op_AND) CGE(op_AND) CGE(op_XOR) CGE(op_XOR) CGE(op_NOT) CGE(op_NOT) + CGE(op_MOV_I) CGE(op_MOV_I) CGE(op_ADD_I) CGE(op_ADD_I) CGE(op_SETF) CGE(op_SETF) CGE(op_CMP_I) CGE(op_CMP_I) + CGE(op_SHL_I) CGE(op_SHL_I) CGE(op_SHR_I) CGE(op_SHR_I) CGE(op_EI) CGE(op_EI) CGE(op_SAR_I) CGE(op_SAR_I) + CGE(op_TRAP) CGE(op_TRAP) CGE(op_RETI) CGE(op_RETI) CGE(op_HALT) CGE(op_HALT) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_LDSR) CGE(op_LDSR) CGE(op_STSR) CGE(op_STSR) CGE(op_DI) CGE(op_DI) CGE(op_BSTR) CGE(op_BSTR) + CGE(op_BV) CGE(op_BL) CGE(op_BE) CGE(op_BNH) CGE(op_BN) CGE(op_BR) CGE(op_BLT) CGE(op_BLE) + CGE(op_BNV) CGE(op_BNL) CGE(op_BNE) CGE(op_BH) CGE(op_BP) CGE(op_NOP) CGE(op_BGE) CGE(op_BGT) + CGE(op_MOVEA) CGE(op_MOVEA) CGE(op_ADDI) CGE(op_ADDI) CGE(op_JR) CGE(op_JR) CGE(op_JAL) CGE(op_JAL) + CGE(op_ORI) CGE(op_ORI) CGE(op_ANDI) CGE(op_ANDI) CGE(op_XORI) CGE(op_XORI) CGE(op_MOVHI) CGE(op_MOVHI) + CGE(op_LD_B) CGE(op_LD_B) CGE(op_LD_H) CGE(op_LD_H) CGE(op_INVALID) CGE(op_INVALID) CGE(op_LD_W) CGE(op_LD_W) + CGE(op_ST_B) CGE(op_ST_B) CGE(op_ST_H) CGE(op_ST_H) CGE(op_INVALID) CGE(op_INVALID) CGE(op_ST_W) CGE(op_ST_W) + CGE(op_IN_B) CGE(op_IN_B) CGE(op_IN_H) CGE(op_IN_H) CGE(op_CAXI) CGE(op_CAXI) CGE(op_IN_W) CGE(op_IN_W) + CGE(op_OUT_B) CGE(op_OUT_B) CGE(op_OUT_H) CGE(op_OUT_H) CGE(op_FPP) CGE(op_FPP) CGE(op_OUT_W) CGE(op_OUT_W) + + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) + CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INVALID) CGE(op_INT_HANDLER) + CGEND + + // Bit string subopcodes + #define DO_AM_BSTR() \ + const uint32 arg1 = (tmpop >> 5) & 0x1F; \ + const uint32 arg2 = (tmpop & 0x1F); \ + RB_INCPCBY2(); + + + #define DO_AM_FPP() \ + const uint32 arg1 = (tmpop >> 5) & 0x1F; \ + const uint32 arg2 = (tmpop & 0x1F); \ + const uint32 arg3 = ((RB_RDOP(2) >> 10)&0x3F); \ + RB_INCPCBY4(); + + + #define DO_AM_UDEF() \ + RB_INCPCBY2(); + + #define DO_AM_I() \ + const uint32 arg1 = tmpop & 0x1F; \ + const uint32 arg2 = (tmpop >> 5) & 0x1F; \ + RB_INCPCBY2(); + + #define DO_AM_II() DO_AM_I(); + + + #define DO_AM_IV() \ + const uint32 arg1 = ((tmpop & 0x000003FF) << 16) | RB_RDOP(2); \ + + + #define DO_AM_V() \ + const uint32 arg3 = (tmpop >> 5) & 0x1F; \ + const uint32 arg2 = tmpop & 0x1F; \ + const uint32 arg1 = RB_RDOP(2); \ + RB_INCPCBY4(); + + + #define DO_AM_VIa() \ + const uint32 arg1 = RB_RDOP(2); \ + const uint32 arg2 = tmpop & 0x1F; \ + const uint32 arg3 = (tmpop >> 5) & 0x1F; \ + RB_INCPCBY4(); \ + + + #define DO_AM_VIb() \ + const uint32 arg1 = (tmpop >> 5) & 0x1F; \ + const uint32 arg2 = RB_RDOP(2); \ + const uint32 arg3 = (tmpop & 0x1F); \ + RB_INCPCBY4(); \ + + #define DO_AM_IX() \ + const uint32 arg1 = (tmpop & 0x1); \ + RB_INCPCBY2(); \ + + #define DO_AM_III() \ + const uint32 arg1 = tmpop & 0x1FE; + + #include "v810_do_am.h" + + #define BEGIN_OP(meowtmpop) { op_##meowtmpop: v810_timestamp_t timestamp = timestamp_rl; DO_##meowtmpop ##_AM(); + #define END_OP() timestamp_rl = timestamp; goto OpFinished; } + #define END_OP_SKIPLO() timestamp_rl = timestamp; goto OpFinishedSkipLO; } + + BEGIN_OP(MOV); + ADDCLOCK(1); + SetPREG(arg2, P_REG[arg1]); + END_OP(); + + + BEGIN_OP(ADD); + ADDCLOCK(1); + uint32 temp = P_REG[arg2] + P_REG[arg1]; + + SetFlag(PSW_OV, ((P_REG[arg2]^(~P_REG[arg1]))&(P_REG[arg2]^temp))&0x80000000); + SetFlag(PSW_CY, temp < P_REG[arg2]); + + SetPREG(arg2, temp); + SetSZ(P_REG[arg2]); + END_OP(); + + + BEGIN_OP(SUB); + ADDCLOCK(1); + uint32 temp = P_REG[arg2] - P_REG[arg1]; + + SetFlag(PSW_OV, ((P_REG[arg2]^P_REG[arg1])&(P_REG[arg2]^temp))&0x80000000); + SetFlag(PSW_CY, temp > P_REG[arg2]); + + SetPREG(arg2, temp); + SetSZ(P_REG[arg2]); + END_OP(); + + + BEGIN_OP(CMP); + ADDCLOCK(1); + uint32 temp = P_REG[arg2] - P_REG[arg1]; + + SetSZ(temp); + SetFlag(PSW_OV, ((P_REG[arg2]^P_REG[arg1])&(P_REG[arg2]^temp))&0x80000000); + SetFlag(PSW_CY, temp > P_REG[arg2]); + END_OP(); + + + BEGIN_OP(SHL); + ADDCLOCK(1); + val = P_REG[arg1] & 0x1F; + + // set CY before we destroy the regisrer info.... + SetFlag(PSW_CY, (val != 0) && ((P_REG[arg2] >> (32 - val))&0x01) ); + SetFlag(PSW_OV, FALSE); + SetPREG(arg2, P_REG[arg2] << val); + SetSZ(P_REG[arg2]); + END_OP(); + + BEGIN_OP(SHR); + ADDCLOCK(1); + val = P_REG[arg1] & 0x1F; + // set CY before we destroy the regisrer info.... + SetFlag(PSW_CY, (val) && ((P_REG[arg2] >> (val-1))&0x01)); + SetFlag(PSW_OV, FALSE); + SetPREG(arg2, P_REG[arg2] >> val); + SetSZ(P_REG[arg2]); + END_OP(); + + BEGIN_OP(JMP); + + (void)arg2; // arg2 is unused. + + ADDCLOCK(3); + RB_SETPC((P_REG[arg1] & 0xFFFFFFFE)); + if(RB_AccurateMode) + { + BRANCH_ALIGN_CHECK(PC); + } + RB_ADDBT(old_PC, RB_GETPC(), 0); + END_OP(); + + BEGIN_OP(SAR); + ADDCLOCK(1); + val = P_REG[arg1] & 0x1F; + + SetFlag(PSW_CY, (val) && ((P_REG[arg2]>>(val-1))&0x01) ); + SetFlag(PSW_OV, FALSE); + + SetPREG(arg2, (uint32) ((int32)P_REG[arg2] >> val)); + + SetSZ(P_REG[arg2]); + END_OP(); + + BEGIN_OP(OR); + ADDCLOCK(1); + SetPREG(arg2, P_REG[arg1] | P_REG[arg2]); + SetFlag(PSW_OV, FALSE); + SetSZ(P_REG[arg2]); + END_OP(); + + BEGIN_OP(AND); + ADDCLOCK(1); + SetPREG(arg2, P_REG[arg1] & P_REG[arg2]); + SetFlag(PSW_OV, FALSE); + SetSZ(P_REG[arg2]); + END_OP(); + + BEGIN_OP(XOR); + ADDCLOCK(1); + SetPREG(arg2, P_REG[arg1] ^ P_REG[arg2]); + SetFlag(PSW_OV, FALSE); + SetSZ(P_REG[arg2]); + END_OP(); + + BEGIN_OP(NOT); + ADDCLOCK(1); + SetPREG(arg2, ~P_REG[arg1]); + SetFlag(PSW_OV, FALSE); + SetSZ(P_REG[arg2]); + END_OP(); + + BEGIN_OP(MOV_I); + ADDCLOCK(1); + SetPREG(arg2,sign_5(arg1)); + END_OP(); + + BEGIN_OP(ADD_I); + ADDCLOCK(1); + uint32 temp = P_REG[arg2] + sign_5(arg1); + + SetFlag(PSW_OV, ((P_REG[arg2]^(~sign_5(arg1)))&(P_REG[arg2]^temp))&0x80000000); + SetFlag(PSW_CY, (uint32)temp < P_REG[arg2]); + + SetPREG(arg2, (uint32)temp); + SetSZ(P_REG[arg2]); + END_OP(); + + + BEGIN_OP(SETF); + ADDCLOCK(1); + + P_REG[arg2] = 0; + + switch (arg1 & 0x0F) + { + case COND_V: + if (TESTCOND_V) P_REG[arg2] = 1; + break; + case COND_C: + if (TESTCOND_C) P_REG[arg2] = 1; + break; + case COND_Z: + if (TESTCOND_Z) P_REG[arg2] = 1; + break; + case COND_NH: + if (TESTCOND_NH) P_REG[arg2] = 1; + break; + case COND_S: + if (TESTCOND_S) P_REG[arg2] = 1; + break; + case COND_T: + P_REG[arg2] = 1; + break; + case COND_LT: + if (TESTCOND_LT) P_REG[arg2] = 1; + break; + case COND_LE: + if (TESTCOND_LE) P_REG[arg2] = 1; + break; + case COND_NV: + if (TESTCOND_NV) P_REG[arg2] = 1; + break; + case COND_NC: + if (TESTCOND_NC) P_REG[arg2] = 1; + break; + case COND_NZ: + if (TESTCOND_NZ) P_REG[arg2] = 1; + break; + case COND_H: + if (TESTCOND_H) P_REG[arg2] = 1; + break; + case COND_NS: + if (TESTCOND_NS) P_REG[arg2] = 1; + break; + case COND_F: + //always false! do nothing more + break; + case COND_GE: + if (TESTCOND_GE) P_REG[arg2] = 1; + break; + case COND_GT: + if (TESTCOND_GT) P_REG[arg2] = 1; + break; + } + END_OP(); + + BEGIN_OP(CMP_I); + ADDCLOCK(1); + uint32 temp = P_REG[arg2] - sign_5(arg1); + + SetSZ(temp); + SetFlag(PSW_OV, ((P_REG[arg2]^(sign_5(arg1)))&(P_REG[arg2]^temp))&0x80000000); + SetFlag(PSW_CY, temp > P_REG[arg2]); + END_OP(); + + BEGIN_OP(SHR_I); + ADDCLOCK(1); + SetFlag(PSW_CY, arg1 && ((P_REG[arg2] >> (arg1-1))&0x01) ); + // set CY before we destroy the regisrer info.... + SetPREG(arg2, P_REG[arg2] >> arg1); + SetFlag(PSW_OV, FALSE); + SetSZ(P_REG[arg2]); + END_OP(); + + BEGIN_OP(SHL_I); + ADDCLOCK(1); + SetFlag(PSW_CY, arg1 && ((P_REG[arg2] >> (32 - arg1))&0x01) ); + // set CY before we destroy the regisrer info.... + + SetPREG(arg2, P_REG[arg2] << arg1); + SetFlag(PSW_OV, FALSE); + SetSZ(P_REG[arg2]); + END_OP(); + + BEGIN_OP(SAR_I); + ADDCLOCK(1); + SetFlag(PSW_CY, arg1 && ((P_REG[arg2]>>(arg1-1))&0x01) ); + + SetPREG(arg2, (uint32) ((int32)P_REG[arg2] >> arg1)); + + SetFlag(PSW_OV, FALSE); + SetSZ(P_REG[arg2]); + END_OP(); + + BEGIN_OP(LDSR); // Loads a Sys Reg with the value in specified PR + ADDCLOCK(1); // ? + + SetSREG(timestamp, arg1 & 0x1F, P_REG[arg2 & 0x1F]); + END_OP(); + + BEGIN_OP(STSR); // Loads a PR with the value in specified Sys Reg + ADDCLOCK(1); // ? + P_REG[arg2 & 0x1F] = GetSREG(arg1 & 0x1F); + END_OP(); + + BEGIN_OP(EI); + (void)arg1; // arg1 is unused. + (void)arg2; // arg2 is unused. + + if(VBMode) + { + ADDCLOCK(1); + S_REG[PSW] = S_REG[PSW] &~ PSW_ID; + RecalcIPendingCache(); + } + else + { + ADDCLOCK(1); + RB_DECPCBY2(); + Exception(INVALID_OP_HANDLER_ADDR, ECODE_INVALID_OP); + CHECK_HALTED(); + } + END_OP(); + + BEGIN_OP(DI); + (void)arg1; // arg1 is unused. + (void)arg2; // arg2 is unused. + + if(VBMode) + { + ADDCLOCK(1); + S_REG[PSW] |= PSW_ID; + IPendingCache = 0; + } + else + { + ADDCLOCK(1); + RB_DECPCBY2(); + Exception(INVALID_OP_HANDLER_ADDR, ECODE_INVALID_OP); + CHECK_HALTED(); + } + END_OP(); + + + #define COND_BRANCH(cond) \ + if(cond) \ + { \ + ADDCLOCK(3); \ + RB_PCRELCHANGE(sign_9(arg1) & 0xFFFFFFFE); \ + if(RB_AccurateMode) \ + { \ + BRANCH_ALIGN_CHECK(PC); \ + } \ + RB_ADDBT(old_PC, RB_GETPC(), 0); \ + } \ + else \ + { \ + ADDCLOCK(1); \ + RB_INCPCBY2(); \ + } + + BEGIN_OP(BV); + COND_BRANCH(TESTCOND_V); + END_OP(); + + + BEGIN_OP(BL); + COND_BRANCH(TESTCOND_L); + END_OP(); + + BEGIN_OP(BE); + COND_BRANCH(TESTCOND_E); + END_OP(); + + BEGIN_OP(BNH); + COND_BRANCH(TESTCOND_NH); + END_OP(); + + BEGIN_OP(BN); + COND_BRANCH(TESTCOND_N); + END_OP(); + + BEGIN_OP(BR); + COND_BRANCH(TRUE); + END_OP(); + + BEGIN_OP(BLT); + COND_BRANCH(TESTCOND_LT); + END_OP(); + + BEGIN_OP(BLE); + COND_BRANCH(TESTCOND_LE); + END_OP(); + + BEGIN_OP(BNV); + COND_BRANCH(TESTCOND_NV); + END_OP(); + + BEGIN_OP(BNL); + COND_BRANCH(TESTCOND_NL); + END_OP(); + + BEGIN_OP(BNE); + COND_BRANCH(TESTCOND_NE); + END_OP(); + + BEGIN_OP(BH); + COND_BRANCH(TESTCOND_H); + END_OP(); + + BEGIN_OP(BP); + COND_BRANCH(TESTCOND_P); + END_OP(); + + BEGIN_OP(BGE); + COND_BRANCH(TESTCOND_GE); + END_OP(); + + BEGIN_OP(BGT); + COND_BRANCH(TESTCOND_GT); + END_OP(); + + BEGIN_OP(JR); + ADDCLOCK(3); + RB_PCRELCHANGE(sign_26(arg1) & 0xFFFFFFFE); + if(RB_AccurateMode) + { + BRANCH_ALIGN_CHECK(PC); + } + RB_ADDBT(old_PC, RB_GETPC(), 0); + END_OP(); + + BEGIN_OP(JAL); + ADDCLOCK(3); + P_REG[31] = RB_GETPC() + 4; + RB_PCRELCHANGE(sign_26(arg1) & 0xFFFFFFFE); + if(RB_AccurateMode) + { + BRANCH_ALIGN_CHECK(PC); + } + RB_ADDBT(old_PC, RB_GETPC(), 0); + END_OP(); + + BEGIN_OP(MOVEA); + ADDCLOCK(1); + SetPREG(arg3, P_REG[arg2] + sign_16(arg1)); + END_OP(); + + BEGIN_OP(ADDI); + ADDCLOCK(1); + uint32 temp = P_REG[arg2] + sign_16(arg1); + + SetFlag(PSW_OV, ((P_REG[arg2]^(~sign_16(arg1)))&(P_REG[arg2]^temp))&0x80000000); + SetFlag(PSW_CY, (uint32)temp < P_REG[arg2]); + + SetPREG(arg3, (uint32)temp); + SetSZ(P_REG[arg3]); + END_OP(); + + BEGIN_OP(ORI); + ADDCLOCK(1); + SetPREG(arg3, arg1 | P_REG[arg2]); + SetFlag(PSW_OV, FALSE); + SetSZ(P_REG[arg3]); + END_OP(); + + BEGIN_OP(ANDI); + ADDCLOCK(1); + SetPREG(arg3, (arg1 & P_REG[arg2])); + SetFlag(PSW_OV, FALSE); + SetSZ(P_REG[arg3]); + END_OP(); + + BEGIN_OP(XORI); + ADDCLOCK(1); + SetPREG(arg3, arg1 ^ P_REG[arg2]); + SetFlag(PSW_OV, FALSE); + SetSZ(P_REG[arg3]); + END_OP(); + + BEGIN_OP(MOVHI); + ADDCLOCK(1); + SetPREG(arg3, (arg1 << 16) + P_REG[arg2]); + END_OP(); + + // LD.B + BEGIN_OP(LD_B); + ADDCLOCK(1); + tmp2 = (sign_16(arg1)+P_REG[arg2])&0xFFFFFFFF; + + SetPREG(arg3, sign_8(MemRead8(timestamp, tmp2))); + + //should be 3 clocks when executed alone, 2 when precedes another LD, or 1 + //when precedes an instruction with many clocks (I'm guessing FP, MUL, DIV, etc) + if(lastop >= 0) + { + if(lastop == LASTOP_LD) + { + ADDCLOCK(1); + } + else + { + ADDCLOCK(2); + } + } + lastop = LASTOP_LD; + END_OP_SKIPLO(); + + // LD.H + BEGIN_OP(LD_H); + ADDCLOCK(1); + tmp2 = (sign_16(arg1)+P_REG[arg2]) & 0xFFFFFFFE; + SetPREG(arg3, sign_16(MemRead16(timestamp, tmp2))); + + if(lastop >= 0) + { + if(lastop == LASTOP_LD) + { + ADDCLOCK(1); + } + else + { + ADDCLOCK(2); + } + } + lastop = LASTOP_LD; + END_OP_SKIPLO(); + + + // LD.W + BEGIN_OP(LD_W); + ADDCLOCK(1); + + tmp2 = (sign_16(arg1)+P_REG[arg2]) & 0xFFFFFFFC; + + if(MemReadBus32[tmp2 >> 24]) + { + SetPREG(arg3, MemRead32(timestamp, tmp2)); + + if(lastop >= 0) + { + if(lastop == LASTOP_LD) + { + ADDCLOCK(1); + } + else + { + ADDCLOCK(2); + } + } + } + else + { + uint32 rv; + + rv = MemRead16(timestamp, tmp2); + rv |= MemRead16(timestamp, tmp2 | 2) << 16; + + SetPREG(arg3, rv); + + if(lastop >= 0) + { + if(lastop == LASTOP_LD) + { + ADDCLOCK(3); + } + else + { + ADDCLOCK(4); + } + } + } + lastop = LASTOP_LD; + END_OP_SKIPLO(); + + // ST.B + BEGIN_OP(ST_B); + ADDCLOCK(1); + MemWrite8(timestamp, sign_16(arg2)+P_REG[arg3], P_REG[arg1] & 0xFF); + + if(lastop == LASTOP_ST) + { + ADDCLOCK(1); + } + lastop = LASTOP_ST; + END_OP_SKIPLO(); + + // ST.H + BEGIN_OP(ST_H); + ADDCLOCK(1); + + MemWrite16(timestamp, (sign_16(arg2)+P_REG[arg3])&0xFFFFFFFE, P_REG[arg1] & 0xFFFF); + + if(lastop == LASTOP_ST) + { + ADDCLOCK(1); + } + lastop = LASTOP_ST; + END_OP_SKIPLO(); + + // ST.W + BEGIN_OP(ST_W); + ADDCLOCK(1); + tmp2 = (sign_16(arg2)+P_REG[arg3]) & 0xFFFFFFFC; + + if(MemWriteBus32[tmp2 >> 24]) + { + MemWrite32(timestamp, tmp2, P_REG[arg1]); + + if(lastop == LASTOP_ST) + { + ADDCLOCK(1); + } + } + else + { + MemWrite16(timestamp, tmp2, P_REG[arg1] & 0xFFFF); + MemWrite16(timestamp, tmp2 | 2, P_REG[arg1] >> 16); + + if(lastop == LASTOP_ST) + { + ADDCLOCK(3); + } + } + lastop = LASTOP_ST; + END_OP_SKIPLO(); + + // IN.B + BEGIN_OP(IN_B); + { + ADDCLOCK(3); + SetPREG(arg3, IORead8(timestamp, sign_16(arg1)+P_REG[arg2])); + } + lastop = LASTOP_IN; + END_OP_SKIPLO(); + + + // IN.H + BEGIN_OP(IN_H); + { + ADDCLOCK(3); + SetPREG(arg3, IORead16(timestamp, (sign_16(arg1)+P_REG[arg2]) & 0xFFFFFFFE)); + } + lastop = LASTOP_IN; + END_OP_SKIPLO(); + + + // IN.W + BEGIN_OP(IN_W); + if(IORead32) + { + ADDCLOCK(3); + SetPREG(arg3, IORead32(timestamp, (sign_16(arg1)+P_REG[arg2]) & 0xFFFFFFFC)); + } + else + { + uint32 eff_addr = (sign_16(arg1) + P_REG[arg2]) & 0xFFFFFFFC; + uint32 rv; + + ADDCLOCK(5); + + rv = IORead16(timestamp, eff_addr); + rv |= IORead16(timestamp, eff_addr | 2) << 16; + + SetPREG(arg3, rv); + } + lastop = LASTOP_IN; + END_OP_SKIPLO(); + + + // OUT.B + BEGIN_OP(OUT_B); + ADDCLOCK(1); + IOWrite8(timestamp, sign_16(arg2)+P_REG[arg3],P_REG[arg1]&0xFF); + + if(lastop == LASTOP_OUT) + { + ADDCLOCK(1); + } + lastop = LASTOP_OUT; + END_OP_SKIPLO(); + + + // OUT.H + BEGIN_OP(OUT_H); + ADDCLOCK(1); + IOWrite16(timestamp, (sign_16(arg2)+P_REG[arg3])&0xFFFFFFFE,P_REG[arg1]&0xFFFF); + + if(lastop == LASTOP_OUT) + { + ADDCLOCK(1); + } + lastop = LASTOP_OUT; + END_OP_SKIPLO(); + + + // OUT.W + BEGIN_OP(OUT_W); + ADDCLOCK(1); + + if(IOWrite32) + IOWrite32(timestamp, (sign_16(arg2)+P_REG[arg3])&0xFFFFFFFC,P_REG[arg1]); + else + { + uint32 eff_addr = (sign_16(arg2)+P_REG[arg3])&0xFFFFFFFC; + IOWrite16(timestamp, eff_addr, P_REG[arg1] & 0xFFFF); + IOWrite16(timestamp, eff_addr | 2, P_REG[arg1] >> 16); + } + + if(lastop == LASTOP_OUT) + { + if(IOWrite32) + { + ADDCLOCK(1); + } + else + { + ADDCLOCK(3); + } + } + lastop = LASTOP_OUT; + END_OP_SKIPLO(); + + BEGIN_OP(NOP); + (void)arg1; // arg1 is unused. + + ADDCLOCK(1); + RB_INCPCBY2(); + END_OP(); + + BEGIN_OP(RETI); + (void)arg1; // arg1 is unused. + + ADDCLOCK(10); + + //Return from Trap/Interupt + if(S_REG[PSW] & PSW_NP) { // Read the FE Reg + RB_SETPC(S_REG[FEPC] & 0xFFFFFFFE); + S_REG[PSW] = S_REG[FEPSW]; + } else { //Read the EI Reg Interupt + RB_SETPC(S_REG[EIPC] & 0xFFFFFFFE); + S_REG[PSW] = S_REG[EIPSW]; + } + RecalcIPendingCache(); + + RB_ADDBT(old_PC, RB_GETPC(), 0); + END_OP(); + + BEGIN_OP(MUL); + ADDCLOCK(13); + + uint64 temp = (int64)(int32)P_REG[arg1] * (int32)P_REG[arg2]; + + SetPREG(30, (uint32)(temp >> 32)); + SetPREG(arg2, temp); + SetSZ(P_REG[arg2]); + SetFlag(PSW_OV, temp != (uint64)(int64)(int32)(uint32)temp); + lastop = -1; + END_OP_SKIPLO(); + + BEGIN_OP(MULU); + ADDCLOCK(13); + uint64 temp = (uint64)P_REG[arg1] * (uint64)P_REG[arg2]; + + SetPREG(30, (uint32)(temp >> 32)); + SetPREG(arg2, (uint32)temp); + + SetSZ(P_REG[arg2]); + SetFlag(PSW_OV, temp != (uint32)temp); + lastop = -1; + END_OP_SKIPLO(); + + BEGIN_OP(DIVU); + ADDCLOCK(36); + if(P_REG[arg1] == 0) // Divide by zero! + { + RB_DECPCBY2(); + Exception(ZERO_DIV_HANDLER_ADDR, ECODE_ZERO_DIV); + CHECK_HALTED(); + } + else + { + // Careful here, since arg2 can be == 30 + uint32 quotient = (uint32)P_REG[arg2] / (uint32)P_REG[arg1]; + uint32 remainder = (uint32)P_REG[arg2] % (uint32)P_REG[arg1]; + + SetPREG(30, remainder); + SetPREG(arg2, quotient); + + SetFlag(PSW_OV, FALSE); + SetSZ(quotient); + } + lastop = -1; + END_OP_SKIPLO(); + + BEGIN_OP(DIV); + //if(P_REG[arg1] & P_REG[arg2] & 0x80000000) + //{ + // printf("Div: %08x %08x\n", P_REG[arg1], P_REG[arg2]); + //} + + ADDCLOCK(38); + if((uint32)P_REG[arg1] == 0) // Divide by zero! + { + RB_DECPCBY2(); + Exception(ZERO_DIV_HANDLER_ADDR, ECODE_ZERO_DIV); + CHECK_HALTED(); + } + else + { + if((P_REG[arg2]==0x80000000)&&(P_REG[arg1]==0xFFFFFFFF)) + { + SetFlag(PSW_OV, TRUE); + P_REG[30]=0; + SetPREG(arg2, 0x80000000); + SetSZ(P_REG[arg2]); + } + else + { + // Careful here, since arg2 can be == 30 + uint32 quotient = (int32)P_REG[arg2] / (int32)P_REG[arg1]; + uint32 remainder = (int32)P_REG[arg2] % (int32)P_REG[arg1]; + + SetPREG(30, remainder); + SetPREG(arg2, quotient); + + SetFlag(PSW_OV, FALSE); + SetSZ(quotient); + } + } + lastop = -1; + END_OP_SKIPLO(); + + BEGIN_OP(FPP); + ADDCLOCK(1); + fpu_subop(timestamp, arg3, arg1, arg2); + lastop = -1; + CHECK_HALTED(); + END_OP_SKIPLO(); + + BEGIN_OP(BSTR); + if(!in_bstr) + { + ADDCLOCK(1); + } + + if(bstr_subop(timestamp, arg2, arg1)) + { + RB_DECPCBY2(); + in_bstr = TRUE; + in_bstr_to = tmpop; + } + else + { + in_bstr = FALSE; + have_src_cache = have_dst_cache = FALSE; + } + END_OP(); + + BEGIN_OP(HALT); + (void)arg1; // arg1 is unused. + + ADDCLOCK(1); + Halted = HALT_HALT; + //printf("Untested opcode: HALT\n"); + END_OP(); + + BEGIN_OP(TRAP); + (void)arg2; // arg2 is unused. + + ADDCLOCK(15); + + Exception(TRAP_HANDLER_BASE + (arg1 & 0x10), ECODE_TRAP_BASE + (arg1 & 0x1F)); + CHECK_HALTED(); + END_OP(); + + BEGIN_OP(CAXI); + //printf("Untested opcode: caxi\n"); + + // Lock bus(N/A) + + ADDCLOCK(26); + + { + uint32 addr, tmp, compare_temp; + uint32 to_write; + + addr = sign_16(arg1) + P_REG[arg2]; + addr &= ~3; + + if(MemReadBus32[addr >> 24]) + tmp = MemRead32(timestamp, addr); + else + { + tmp = MemRead16(timestamp, addr); + tmp |= MemRead16(timestamp, addr | 2) << 16; + } + + compare_temp = P_REG[arg3] - tmp; + + SetSZ(compare_temp); + SetFlag(PSW_OV, ((P_REG[arg3]^tmp)&(P_REG[arg3]^compare_temp))&0x80000000); + SetFlag(PSW_CY, compare_temp > P_REG[arg3]); + + if(!compare_temp) // If they're equal... + to_write = P_REG[30]; + else + to_write = tmp; + + if(MemWriteBus32[addr >> 24]) + MemWrite32(timestamp, addr, to_write); + else + { + MemWrite16(timestamp, addr, to_write & 0xFFFF); + MemWrite16(timestamp, addr | 2, to_write >> 16); + } + P_REG[arg3] = tmp; + } + + // Unlock bus(N/A) + + END_OP(); + + + + op_INT_HANDLER: + { + int iNum = ilevel; + + S_REG[EIPC] = GetPC(); + S_REG[EIPSW] = S_REG[PSW]; + + SetPC(0xFFFFFE00 | (iNum << 4)); + + RB_ADDBT(old_PC, RB_GETPC(), 0xFE00 | (iNum << 4)); + + S_REG[ECR] = 0xFE00 | (iNum << 4); + + S_REG[PSW] |= PSW_EP; + S_REG[PSW] |= PSW_ID; + S_REG[PSW] &= ~PSW_AE; + + // Now, set need to set the interrupt enable level to he level that is being processed + 1, + // saturating at 15. + iNum++; + + if(iNum > 0x0F) + iNum = 0x0F; + + S_REG[PSW] &= ~PSW_IA; + S_REG[PSW] |= iNum << 16; + + // Accepting an interrupt takes us out of normal HALT status, of course! + Halted = HALT_NONE; + + // Invalidate our bitstring state(forces the instruction to be re-read, and the r/w buffers reloaded). + in_bstr = FALSE; + have_src_cache = FALSE; + have_dst_cache = FALSE; + + IPendingCache = 0; + + goto OpFinished; + } + + + BEGIN_OP(INVALID); + RB_DECPCBY2(); + if(!RB_AccurateMode) + { + RB_SETPC(RB_GETPC()); + if((uint32)(RB_RDOP(0, 0) >> 9) != opcode) + { + //printf("Trampoline: %08x %02x\n", RB_GETPC(), opcode >> 1); + } + else + { + ADDCLOCK(1); + Exception(INVALID_OP_HANDLER_ADDR, ECODE_INVALID_OP); + CHECK_HALTED(); + } + } + else + { + ADDCLOCK(1); + Exception(INVALID_OP_HANDLER_ADDR, ECODE_INVALID_OP); + CHECK_HALTED(); + } + END_OP(); + + } + + OpFinished: ; + lastop = opcode; + OpFinishedSkipLO: ; + } // end while(timestamp_rl < next_event_ts) + next_event_ts = event_handler(timestamp_rl); + //printf("Next: %d, Cur: %d\n", next_event_ts, timestamp); + } + +v810_timestamp = timestamp_rl; diff --git a/waterbox/vb/v810/v810_opt.h b/waterbox/vb/v810/v810_opt.h new file mode 100644 index 0000000000..585c50b269 --- /dev/null +++ b/waterbox/vb/v810/v810_opt.h @@ -0,0 +1,170 @@ +/////////////////////////////////////////////////////////////// +// File: v810_opt.h +// +// Description: Defines used in v810_dis.cpp +// + +#ifndef V810_OPT_H_ +#define V810_OPT_H_ + +#define sign_26(num) ((uint32)sign_x_to_s32(26, num)) +#define sign_16(num) ((uint32)(int16)(num)) +#define sign_14(num) ((uint32)sign_x_to_s32(14, num)) +#define sign_12(num) ((uint32)sign_x_to_s32(12, num)) +#define sign_9(num) ((uint32)sign_x_to_s32(9, num)) +#define sign_8(_value) ((uint32)(int8)(_value)) +#define sign_5(num) ((uint32)sign_x_to_s32(5, num)) + +/////////////////////////////////////////////////////////////////// +// Define Modes +#define AM_I 0x01 +#define AM_II 0x02 +#define AM_III 0x03 +#define AM_IV 0x04 +#define AM_V 0x05 +#define AM_VIa 0x06 // Mode6 form1 +#define AM_VIb 0x0A // Mode6 form2 +#define AM_VII 0x07 +#define AM_VIII 0x08 +#define AM_IX 0x09 +#define AM_BSTR 0x0B // Bit String Instructions +#define AM_FPP 0x0C // Floating Point Instructions +#define AM_UDEF 0x0D // Unknown/Undefined Instructions + +/////////////////////////////////////////////////////////////////// +// Table of Instructions Address Modes + +static const int addr_mode[80] = { + AM_I, AM_I, AM_I, AM_I, AM_I, AM_I, AM_I, AM_I, + AM_I, AM_I, AM_I, AM_I, AM_I, AM_I, AM_I, AM_I, + AM_II, AM_II, AM_II, AM_II, AM_II, AM_II, AM_II, AM_II, + AM_II, AM_IX, AM_IX, AM_UDEF, AM_II, AM_II, AM_II, AM_BSTR, + AM_UDEF, AM_UDEF, AM_UDEF, AM_UDEF, AM_UDEF, AM_UDEF, AM_UDEF, AM_UDEF, + AM_V, AM_V, AM_IV, AM_IV, AM_V, AM_V, AM_V, AM_V, + AM_VIa, AM_VIa, AM_UDEF, AM_VIa, AM_VIb, AM_VIb, AM_UDEF, AM_VIb, + AM_VIa, AM_VIa, AM_VIa, AM_VIa, AM_VIb, AM_VIb, AM_FPP, AM_VIb, + AM_III, AM_III, AM_III, AM_III, AM_III, AM_III, AM_III, AM_III, + AM_III, AM_III, AM_III, AM_III, AM_III, AM_III, AM_III, AM_III +}; +// All instructions greater than 0x50 are undefined (this should not be posible of cource) + + +/////////////////////////////////////////////////////////////////// +// Opcodes for V810 Instruction set +#define MOV 0x00 +#define ADD 0x01 +#define SUB 0x02 +#define CMP 0x03 +#define SHL 0x04 +#define SHR 0x05 +#define JMP 0x06 +#define SAR 0x07 +#define MUL 0x08 +#define DIV 0x09 +#define MULU 0x0A +#define DIVU 0x0B +#define OR 0x0C +#define AND 0x0D +#define XOR 0x0E +#define NOT 0x0F +#define MOV_I 0x10 +#define ADD_I 0x11 +#define SETF 0x12 +#define CMP_I 0x13 +#define SHL_I 0x14 +#define SHR_I 0x15 +#define EI 0x16 +#define SAR_I 0x17 +#define TRAP 0x18 +#define RETI 0x19 +#define HALT 0x1A + //0x1B +#define LDSR 0x1C +#define STSR 0x1D +#define DI 0x1E +#define BSTR 0x1F //Special Bit String Inst + //0x20 - 0x27 // Lost to Branch Instructions +#define MOVEA 0x28 +#define ADDI 0x29 +#define JR 0x2A +#define JAL 0x2B +#define ORI 0x2C +#define ANDI 0x2D +#define XORI 0x2E +#define MOVHI 0x2F +#define LD_B 0x30 +#define LD_H 0x31 + //0x32 +#define LD_W 0x33 +#define ST_B 0x34 +#define ST_H 0x35 + //0x36 +#define ST_W 0x37 +#define IN_B 0x38 +#define IN_H 0x39 +#define CAXI 0x3A +#define IN_W 0x3B +#define OUT_B 0x3C +#define OUT_H 0x3D +#define FPP 0x3E //Special Float Inst +#define OUT_W 0x3F + + +// Branch Instructions ( Extended opcode only for Branch command) +// Common instrcutions commented out + +#define BV 0x40 +#define BL 0x41 +#define BE 0x42 +#define BNH 0x43 +#define BN 0x44 +#define BR 0x45 +#define BLT 0x46 +#define BLE 0x47 +#define BNV 0x48 +#define BNL 0x49 +#define BNE 0x4A +#define BH 0x4B +#define BP 0x4C +#define NOP 0x4D +#define BGE 0x4E +#define BGT 0x4F + +//#define BC 0x41 +//#define BZ 0x42 +//#define BNC 0x49 +//#define BNZ 0x4A + +// Bit String Subopcodes +#define SCH0BSU 0x00 +#define SCH0BSD 0x01 +#define SCH1BSU 0x02 +#define SCH1BSD 0x03 + +#define ORBSU 0x08 +#define ANDBSU 0x09 +#define XORBSU 0x0A +#define MOVBSU 0x0B +#define ORNBSU 0x0C +#define ANDNBSU 0x0D +#define XORNBSU 0x0E +#define NOTBSU 0x0F + + +// Floating Point Subopcodes +#define CMPF_S 0x00 + +#define CVT_WS 0x02 +#define CVT_SW 0x03 +#define ADDF_S 0x04 +#define SUBF_S 0x05 +#define MULF_S 0x06 +#define DIVF_S 0x07 +#define XB 0x08 +#define XH 0x09 +#define REV 0x0A +#define TRNC_SW 0x0B +#define MPYHW 0x0C + +#endif //DEFINE_H + diff --git a/waterbox/vb/vb.cpp b/waterbox/vb/vb.cpp new file mode 100644 index 0000000000..b9940aa171 --- /dev/null +++ b/waterbox/vb/vb.cpp @@ -0,0 +1,780 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vb.cpp: +** Copyright (C) 2010-2017 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "vb.h" +#include +#define EXPORT extern "C" ECL_EXPORT + +namespace MDFN_IEN_VB +{ +enum +{ + ANAGLYPH_PRESET_DISABLED = 0, + ANAGLYPH_PRESET_RED_BLUE, + ANAGLYPH_PRESET_RED_CYAN, + ANAGLYPH_PRESET_RED_ELECTRICCYAN, + ANAGLYPH_PRESET_RED_GREEN, + ANAGLYPH_PRESET_GREEN_MAGENTA, + ANAGLYPH_PRESET_YELLOW_BLUE, +}; + +static const uint32 AnaglyphPreset_Colors[][2] = + { + {0, 0}, + {0xFF0000, 0x0000FF}, + {0xFF0000, 0x00B7EB}, + {0xFF0000, 0x00FFFF}, + {0xFF0000, 0x00FF00}, + {0x00FF00, 0xFF00FF}, + {0xFFFF00, 0x0000FF}, +}; + +int32 VB_InDebugPeek; + +static uint32 VB3DMode; + +static uint8 *WRAM = NULL; + +static uint8 *GPRAM = NULL; +static const uint32 GPRAM_Mask = 0xFFFF; + +static uint8 *GPROM = NULL; +static uint32 GPROM_Mask; + +V810 *VB_V810 = NULL; + +VSU *VB_VSU = NULL; +static uint32 VSU_CycleFix; + +static uint8 WCR; + +static int32 next_vip_ts, next_timer_ts, next_input_ts; + +static uint32 IRQ_Asserted; + +static INLINE void RecalcIntLevel(void) +{ + int ilevel = -1; + + for (int i = 4; i >= 0; i--) + { + if (IRQ_Asserted & (1 << i)) + { + ilevel = i; + break; + } + } + + VB_V810->SetInt(ilevel); +} + +void VBIRQ_Assert(int source, bool assert) +{ + assert(source >= 0 && source <= 4); + + IRQ_Asserted &= ~(1 << source); + + if (assert) + IRQ_Asserted |= 1 << source; + + RecalcIntLevel(); +} + +static MDFN_FASTCALL uint8 HWCTRL_Read(v810_timestamp_t ×tamp, uint32 A) +{ + uint8 ret = 0; + + if (A & 0x3) + { + //puts("HWCtrl Bogus Read?"); + return (ret); + } + + switch (A & 0xFF) + { + default: //printf("Unknown HWCTRL Read: %08x\n", A); + break; + + case 0x18: + case 0x1C: + case 0x20: + ret = TIMER_Read(timestamp, A); + break; + + case 0x24: + ret = WCR | 0xFC; + break; + + case 0x10: + case 0x14: + case 0x28: + ret = VBINPUT_Read(timestamp, A); + break; + } + + return (ret); +} + +static MDFN_FASTCALL void HWCTRL_Write(v810_timestamp_t ×tamp, uint32 A, uint8 V) +{ + if (A & 0x3) + { + puts("HWCtrl Bogus Write?"); + return; + } + + switch (A & 0xFF) + { + default: //printf("Unknown HWCTRL Write: %08x %02x\n", A, V); + break; + + case 0x18: + case 0x1C: + case 0x20: + TIMER_Write(timestamp, A, V); + break; + + case 0x24: + WCR = V & 0x3; + break; + + case 0x10: + case 0x14: + case 0x28: + VBINPUT_Write(timestamp, A, V); + break; + } +} + +uint8 MDFN_FASTCALL MemRead8(v810_timestamp_t ×tamp, uint32 A) +{ + uint8 ret = 0; + A &= (1 << 27) - 1; + + //if((A >> 24) <= 2) + // printf("Read8: %d %08x\n", timestamp, A); + + switch (A >> 24) + { + case 0: + ret = VIP_Read8(timestamp, A); + break; + + case 1: + break; + + case 2: + ret = HWCTRL_Read(timestamp, A); + break; + + case 3: + break; + case 4: + break; + + case 5: + ret = WRAM[A & 0xFFFF]; + break; + + case 6: + if (GPRAM) + ret = GPRAM[A & GPRAM_Mask]; + break; + + case 7: + ret = GPROM[A & GPROM_Mask]; + break; + } + return (ret); +} + +uint16 MDFN_FASTCALL MemRead16(v810_timestamp_t ×tamp, uint32 A) +{ + uint16 ret = 0; + + A &= (1 << 27) - 1; + + //if((A >> 24) <= 2) + // printf("Read16: %d %08x\n", timestamp, A); + + switch (A >> 24) + { + case 0: + ret = VIP_Read16(timestamp, A); + break; + + case 1: + break; + + case 2: + ret = HWCTRL_Read(timestamp, A); + break; + + case 3: + break; + + case 4: + break; + + case 5: + ret = MDFN_de16lsb(&WRAM[A & 0xFFFF]); + break; + + case 6: + if (GPRAM) + ret = MDFN_de16lsb(&GPRAM[A & GPRAM_Mask]); + break; + + case 7: + ret = MDFN_de16lsb(&GPROM[A & GPROM_Mask]); + break; + } + return ret; +} + +void MDFN_FASTCALL MemWrite8(v810_timestamp_t ×tamp, uint32 A, uint8 V) +{ + A &= (1 << 27) - 1; + + //if((A >> 24) <= 2) + // printf("Write8: %d %08x %02x\n", timestamp, A, V); + + switch (A >> 24) + { + case 0: + VIP_Write8(timestamp, A, V); + break; + + case 1: + VB_VSU->Write((timestamp + VSU_CycleFix) >> 2, A, V); + break; + + case 2: + HWCTRL_Write(timestamp, A, V); + break; + + case 3: + break; + + case 4: + break; + + case 5: + WRAM[A & 0xFFFF] = V; + break; + + case 6: + if (GPRAM) + GPRAM[A & GPRAM_Mask] = V; + break; + + case 7: // ROM, no writing allowed! + break; + } +} + +void MDFN_FASTCALL MemWrite16(v810_timestamp_t ×tamp, uint32 A, uint16 V) +{ + A &= (1 << 27) - 1; + + //if((A >> 24) <= 2) + // printf("Write16: %d %08x %04x\n", timestamp, A, V); + + switch (A >> 24) + { + case 0: + VIP_Write16(timestamp, A, V); + break; + + case 1: + VB_VSU->Write((timestamp + VSU_CycleFix) >> 2, A, V); + break; + + case 2: + HWCTRL_Write(timestamp, A, V); + break; + + case 3: + break; + + case 4: + break; + + case 5: + MDFN_en16lsb(&WRAM[A & 0xFFFF], V); + break; + + case 6: + if (GPRAM) + MDFN_en16lsb(&GPRAM[A & GPRAM_Mask], V); + break; + + case 7: // ROM, no writing allowed! + break; + } +} + +static void FixNonEvents(void) +{ + if (next_vip_ts & 0x40000000) + next_vip_ts = VB_EVENT_NONONO; + + if (next_timer_ts & 0x40000000) + next_timer_ts = VB_EVENT_NONONO; + + if (next_input_ts & 0x40000000) + next_input_ts = VB_EVENT_NONONO; +} + +static void EventReset(void) +{ + next_vip_ts = VB_EVENT_NONONO; + next_timer_ts = VB_EVENT_NONONO; + next_input_ts = VB_EVENT_NONONO; +} + +static INLINE int32 CalcNextTS(void) +{ + int32 next_timestamp = next_vip_ts; + + if (next_timestamp > next_timer_ts) + next_timestamp = next_timer_ts; + + if (next_timestamp > next_input_ts) + next_timestamp = next_input_ts; + + return (next_timestamp); +} + +static void RebaseTS(const v810_timestamp_t timestamp) +{ + //printf("Rebase: %08x %08x %08x\n", timestamp, next_vip_ts, next_timer_ts); + + assert(next_vip_ts > timestamp); + assert(next_timer_ts > timestamp); + assert(next_input_ts > timestamp); + + next_vip_ts -= timestamp; + next_timer_ts -= timestamp; + next_input_ts -= timestamp; +} + +void VB_SetEvent(const int type, const v810_timestamp_t next_timestamp) +{ + //assert(next_timestamp > VB_V810->v810_timestamp); + + if (type == VB_EVENT_VIP) + next_vip_ts = next_timestamp; + else if (type == VB_EVENT_TIMER) + next_timer_ts = next_timestamp; + else if (type == VB_EVENT_INPUT) + next_input_ts = next_timestamp; + + if (next_timestamp < VB_V810->GetEventNT()) + VB_V810->SetEventNT(next_timestamp); +} + +static int32 MDFN_FASTCALL EventHandler(const v810_timestamp_t timestamp) +{ + if (timestamp >= next_vip_ts) + next_vip_ts = VIP_Update(timestamp); + + if (timestamp >= next_timer_ts) + next_timer_ts = TIMER_Update(timestamp); + + if (timestamp >= next_input_ts) + next_input_ts = VBINPUT_Update(timestamp); + + return (CalcNextTS()); +} + +// Called externally from debug.cpp in some cases. +void ForceEventUpdates(const v810_timestamp_t timestamp) +{ + next_vip_ts = VIP_Update(timestamp); + next_timer_ts = TIMER_Update(timestamp); + next_input_ts = VBINPUT_Update(timestamp); + + VB_V810->SetEventNT(CalcNextTS()); + //printf("FEU: %d %d %d\n", next_vip_ts, next_timer_ts, next_input_ts); +} + +static void VB_Power(void) +{ + memset(WRAM, 0, 65536); + + VIP_Power(); + VB_VSU->Power(); + TIMER_Power(); + VBINPUT_Power(); + + EventReset(); + IRQ_Asserted = 0; + RecalcIntLevel(); + VB_V810->Reset(); + + VSU_CycleFix = 0; + WCR = 0; + + ForceEventUpdates(0); //VB_V810->v810_timestamp); +} + +/*struct VB_HeaderInfo +{ + char game_title[256]; + uint32 game_code; + uint16 manf_code; + uint8 version; +};*/ + +/*static void ReadHeader(const uint8 *const rom_data, const uint64 rom_size, VB_HeaderInfo *hi) +{ + iconv_t sjis_ict = iconv_open("UTF-8", "shift_jis"); + + if (sjis_ict != (iconv_t)-1) + { + char *in_ptr, *out_ptr; + size_t ibl, obl; + + ibl = 20; + obl = sizeof(hi->game_title) - 1; + + in_ptr = (char *)rom_data + (0xFFFFFDE0 & (rom_size - 1)); + out_ptr = hi->game_title; + + iconv(sjis_ict, (ICONV_CONST char **)&in_ptr, &ibl, &out_ptr, &obl); + iconv_close(sjis_ict); + + *out_ptr = 0; + + MDFN_zapctrlchars(hi->game_title); + MDFN_trim(hi->game_title); + } + else + hi->game_title[0] = 0; + + hi->game_code = MDFN_de32lsb(rom_data + (0xFFFFFDFB & (rom_size - 1))); + hi->manf_code = MDFN_de16lsb(rom_data + (0xFFFFFDF9 & (rom_size - 1))); + hi->version = rom_data[0xFFFFFDFF & (rom_size - 1)]; +}*/ + +void VB_ExitLoop(void) +{ + VB_V810->Exit(); +} + + +/*MDFNGI EmulatedVB = + { + + PortInfo, + Load, + TestMagic, + NULL, + NULL, + CloseGame, + + SetLayerEnableMask, + NULL, // Layer names, null-delimited + + NULL, + NULL, + + VIP_CPInfo, + 1 << 0, + + CheatInfo_Empty, + + false, + StateAction, + Emulate, + NULL, + VBINPUT_SetInput, + NULL, + DoSimpleCommand, + NULL, + VBSettings, + MDFN_MASTERCLOCK_FIXED(VB_MASTER_CLOCK), + 0, + false, // Multires possible? + + 0, // lcm_width + 0, // lcm_height + NULL, // Dummy + + 384, // Nominal width + 224, // Nominal height + + 384, // Framebuffer width + 256, // Framebuffer height + + 2, // Number of output sound channels +};*/ +} + +using namespace MDFN_IEN_VB; + +EXPORT int Load(const uint8 *rom, int length) +{ + const uint64 rom_size = length; + V810_Emu_Mode cpu_mode = V810_EMU_MODE_ACCURATE; + + VB_InDebugPeek = 0; + + if (rom_size != round_up_pow2(rom_size)) + { + return 0; + // throw MDFN_Error(0, _("VB ROM image size is not a power of 2.")); + } + + if (rom_size < 256) + { + return 0; + //throw MDFN_Error(0, _("VB ROM image size is too small.")); + } + + if (rom_size > (1 << 24)) + { + return 0; + //throw MDFN_Error(0, _("VB ROM image size is too large.")); + } + + VB_V810 = new V810(); + VB_V810->Init(cpu_mode, true); + + VB_V810->SetMemReadHandlers(MemRead8, MemRead16, NULL); + VB_V810->SetMemWriteHandlers(MemWrite8, MemWrite16, NULL); + + VB_V810->SetIOReadHandlers(MemRead8, MemRead16, NULL); + VB_V810->SetIOWriteHandlers(MemWrite8, MemWrite16, NULL); + + for (int i = 0; i < 256; i++) + { + VB_V810->SetMemReadBus32(i, false); + VB_V810->SetMemWriteBus32(i, false); + } + + std::vector Map_Addresses; + + for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27)) + { + for (uint64 sub_A = 5 << 24; sub_A < (6 << 24); sub_A += 65536) + { + Map_Addresses.push_back(A + sub_A); + } + } + + WRAM = VB_V810->SetFastMap(&Map_Addresses[0], 65536, Map_Addresses.size(), "WRAM"); + Map_Addresses.clear(); + + // Round up the ROM size to 65536(we mirror it a little later) + GPROM_Mask = (rom_size < 65536) ? (65536 - 1) : (rom_size - 1); + + for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27)) + { + for (uint64 sub_A = 7 << 24; sub_A < (8 << 24); sub_A += GPROM_Mask + 1) + { + Map_Addresses.push_back(A + sub_A); + //printf("%08x\n", (uint32)(A + sub_A)); + } + } + + GPROM = VB_V810->SetFastMap(&Map_Addresses[0], GPROM_Mask + 1, Map_Addresses.size(), "Cart ROM"); + Map_Addresses.clear(); + + memcpy(GPROM, rom, rom_size); + + // Mirror ROM images < 64KiB to 64KiB + for (uint64 i = rom_size; i < 65536; i += rom_size) + { + memcpy(GPROM + i, GPROM, rom_size); + } + + /*VB_HeaderInfo hinfo; + + ReadHeader(GPROM, rom_size, &hinfo); + + MDFN_printf(_("Title: %s\n"), hinfo.game_title); + MDFN_printf(_("Game ID Code: %u\n"), hinfo.game_code); + MDFN_printf(_("Manufacturer Code: %d\n"), hinfo.manf_code); + MDFN_printf(_("Version: %u\n"), hinfo.version); + + MDFN_printf(_("ROM: %uKiB\n"), (unsigned)(rom_size / 1024)); + MDFN_printf(_("ROM MD5: 0x%s\n"), md5_context::asciistr(MDFNGameInfo->MD5, 0).c_str());*/ + + /*MDFN_printf("\n"); + + MDFN_printf(_("V810 Emulation Mode: %s\n"), (cpu_mode == V810_EMU_MODE_ACCURATE) ? _("Accurate") : _("Fast"));*/ + + for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27)) + { + for (uint64 sub_A = 6 << 24; sub_A < (7 << 24); sub_A += GPRAM_Mask + 1) + { + //printf("GPRAM: %08x\n", A + sub_A); + Map_Addresses.push_back(A + sub_A); + } + } + + GPRAM = VB_V810->SetFastMap(&Map_Addresses[0], GPRAM_Mask + 1, Map_Addresses.size(), "Cart RAM"); + Map_Addresses.clear(); + + memset(GPRAM, 0, GPRAM_Mask + 1); + + VIP_Init(); + VB_VSU = new VSU(); + VBINPUT_Init(); + + VB3DMode = VB3DMODE_ANAGLYPH; + uint32 prescale = 2; + uint32 sbs_separation = 0; + bool reverse = false; + + VIP_Set3DMode(VB3DMode, reverse, prescale, sbs_separation); + + VIP_SetParallaxDisable(false); + { + auto presetColor = ANAGLYPH_PRESET_RED_BLUE; + + uint32 lcolor, rcolor; + + if (presetColor != ANAGLYPH_PRESET_DISABLED) + { + lcolor = AnaglyphPreset_Colors[presetColor][0]; + rcolor = AnaglyphPreset_Colors[presetColor][1]; + } + VIP_SetAnaglyphColors(lcolor, rcolor); + VIP_SetDefaultColor(0xf0f0f0); + } + + VBINPUT_SetInstantReadHack(true); + + VIP_SetLEDOnScale(1.75); + + //MDFNGameInfo->fps = (int64)20000000 * 65536 * 256 / (259 * 384 * 4); + + VB_Power(); + + /*MDFNGameInfo->nominal_width = 384; + MDFNGameInfo->nominal_height = 224; + MDFNGameInfo->fb_width = 384; + MDFNGameInfo->fb_height = 224;*/ + + /*switch (VB3DMode) + { + default: + break; + + case VB3DMODE_VLI: + MDFNGameInfo->nominal_width = 768 * prescale; + MDFNGameInfo->nominal_height = 224; + MDFNGameInfo->fb_width = 768 * prescale; + MDFNGameInfo->fb_height = 224; + break; + + case VB3DMODE_HLI: + MDFNGameInfo->nominal_width = 384; + MDFNGameInfo->nominal_height = 448 * prescale; + MDFNGameInfo->fb_width = 384; + MDFNGameInfo->fb_height = 448 * prescale; + break; + + case VB3DMODE_CSCOPE: + MDFNGameInfo->nominal_width = 512; + MDFNGameInfo->nominal_height = 384; + MDFNGameInfo->fb_width = 512; + MDFNGameInfo->fb_height = 384; + break; + + case VB3DMODE_SIDEBYSIDE: + MDFNGameInfo->nominal_width = 384 * 2 + sbs_separation; + MDFNGameInfo->nominal_height = 224; + MDFNGameInfo->fb_width = 384 * 2 + sbs_separation; + MDFNGameInfo->fb_height = 224; + break; + } + MDFNGameInfo->lcm_width = MDFNGameInfo->fb_width; + MDFNGameInfo->lcm_height = MDFNGameInfo->fb_height;*/ + + VB_VSU->SetSoundRate(44100); + + return 1; +} + +EXPORT void GetMemoryArea(int which, void **ptr, int *size) +{ + switch (which) + { + case 0: + *ptr = WRAM; + *size = 65536; + break; + case 1: + *ptr = GPRAM; + *size = GPRAM_Mask + 1; + break; + case 2: + *ptr = GPROM; + *size = GPROM_Mask + 1; + break; + default: + *ptr = nullptr; + *size = 0; + break; + } +} + +EXPORT void Emulate(EmulateSpecStruct *espec) +{ + v810_timestamp_t v810_timestamp; + + VBINPUT_Frame(&espec->Buttons); + + VIP_StartFrame(espec); + + v810_timestamp = VB_V810->Run(EventHandler); + + FixNonEvents(); + ForceEventUpdates(v810_timestamp); + + espec->SoundBufSize = VB_VSU->EndFrame((v810_timestamp + VSU_CycleFix) >> 2, espec->SoundBuf, espec->SoundBufMaxSize); + + VSU_CycleFix = (v810_timestamp + VSU_CycleFix) & 3; + + espec->MasterCycles = v810_timestamp; + + TIMER_ResetTS(); + VBINPUT_ResetTS(); + VIP_ResetTS(); + + RebaseTS(v810_timestamp); + + VB_V810->ResetTS(0); +} + +EXPORT void HardReset() +{ + VB_Power(); +} + +int main() +{ + return 0; +} diff --git a/waterbox/vb/vb.h b/waterbox/vb/vb.h new file mode 100644 index 0000000000..8b26411e72 --- /dev/null +++ b/waterbox/vb/vb.h @@ -0,0 +1,114 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vb.h: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +typedef int64_t int64; + +#define MDFN_FASTCALL +#define INLINE inline +#define MDFN_COLD +#define NO_INLINE +//#define MDFN_ASSUME_ALIGNED(p, align) ((decltype(p))__builtin_assume_aligned((p), (align))) +#define MDFN_ASSUME_ALIGNED(p, align) (p) +#define trio_snprintf snprintf +#define TRUE true +#define FALSE false +#ifndef __alignas_is_defined +#define alignas(p) +#endif + +#include "endian.h" +#include "math_ops.h" +#include "blip/Blip_Buffer.h" +#include "v810/v810_fp_ops.h" +#include "v810/v810_cpu.h" + +#include "git.h" + +#include "vsu.h" +#include "vip.h" +#include "timer.h" +#include "input.h" + + +namespace MDFN_IEN_VB +{ + +enum +{ + VB3DMODE_ANAGLYPH = 0, + VB3DMODE_CSCOPE = 1, + VB3DMODE_SIDEBYSIDE = 2, + VB3DMODE_OVERUNDER = 3, + VB3DMODE_VLI, + VB3DMODE_HLI +}; + +#define VB_MASTER_CLOCK 20000000.0 + +enum +{ + VB_EVENT_VIP = 0, + VB_EVENT_TIMER, + VB_EVENT_INPUT, + // VB_EVENT_COMM +}; + +#define VB_EVENT_NONONO 0x7fffffff + +void VB_SetEvent(const int type, const v810_timestamp_t next_timestamp); + +#define VBIRQ_SOURCE_INPUT 0 +#define VBIRQ_SOURCE_TIMER 1 +#define VBIRQ_SOURCE_EXPANSION 2 +#define VBIRQ_SOURCE_COMM 3 +#define VBIRQ_SOURCE_VIP 4 + +void VBIRQ_Assert(int source, bool assert); + +void VB_ExitLoop(void); + +void ForceEventUpdates(const v810_timestamp_t timestamp); + +uint8 MDFN_FASTCALL MemRead8(v810_timestamp_t ×tamp, uint32 A); +uint16 MDFN_FASTCALL MemRead16(v810_timestamp_t ×tamp, uint32 A); + +void MDFN_FASTCALL MemWrite8(v810_timestamp_t ×tamp, uint32 A, uint8 V); +void MDFN_FASTCALL MemWrite16(v810_timestamp_t ×tamp, uint32 A, uint16 V); + +extern int32 VB_InDebugPeek; +} diff --git a/waterbox/vb/vb.wbx b/waterbox/vb/vb.wbx new file mode 100644 index 0000000000000000000000000000000000000000..7cb32426524fe9972bdd64a8ef2521626ccddbcc GIT binary patch literal 165390 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≤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 + +#define VIP_DBGMSG(format, ...) \ + { \ + } +//#define VIP_DBGMSG(format, ...) printf(format "\n", ## __VA_ARGS__) + +namespace MDFN_IEN_VB +{ + +static uint8 FB[2][2][0x6000]; +static uint16 CHR_RAM[0x8000 / sizeof(uint16)]; +static uint16 DRAM[0x20000 / sizeof(uint16)]; + +#define INT_SCAN_ERR 0x0001 +#define INT_LFB_END 0x0002 +#define INT_RFB_END 0x0004 +#define INT_GAME_START 0x0008 +#define INT_FRAME_START 0x0010 + +#define INT_SB_HIT 0x2000 +#define INT_XP_END 0x4000 +#define INT_TIME_ERR 0x8000 + +static uint16 InterruptPending; +static uint16 InterruptEnable; + +static uint8 BRTA, BRTB, BRTC, REST; +static uint8 Repeat; + +static void CopyFBColumnToTarget_Anaglyph(void) NO_INLINE; +static void CopyFBColumnToTarget_AnaglyphSlow(void) NO_INLINE; +static void CopyFBColumnToTarget_CScope(void) NO_INLINE; +static void CopyFBColumnToTarget_SideBySide(void) NO_INLINE; +static void CopyFBColumnToTarget_VLI(void) NO_INLINE; +static void CopyFBColumnToTarget_HLI(void) NO_INLINE; +static void (*CopyFBColumnToTarget)(void) = NULL; +static float VBLEDOnScale; +static uint32 VB3DMode; +static uint32 VB3DReverse; +static uint32 VBPrescale; +static uint32 VBSBS_Separation; +static uint32 HLILUT[256]; +static uint32 ColorLUT[2][256]; +static int32 BrightnessCache[4]; +static uint32 BrightCLUT[2][4]; + +static float ColorLUTNoGC[2][256][3]; +static uint32 AnaSlowColorLUT[256][256]; + +static bool VidSettingsDirty; +static bool ParallaxDisabled; +static uint32 Anaglyph_Colors[2]; +static uint32 Default_Color; + +static void MakeColorLUT() +{ + for (int lr = 0; lr < 2; lr++) + { + for (int i = 0; i < 256; i++) + { + float r, g, b; + uint32 modcolor_prime; + + if (VB3DMode == VB3DMODE_ANAGLYPH) + modcolor_prime = Anaglyph_Colors[lr ^ VB3DReverse]; + else + modcolor_prime = Default_Color; + + r = g = b = std::min(1.0, i * VBLEDOnScale / 255.0); + + // Modulate. + r = r * pow(((modcolor_prime >> 16) & 0xFF) / 255.0, 2.2 / 1.0); + g = g * pow(((modcolor_prime >> 8) & 0xFF) / 255.0, 2.2 / 1.0); + b = b * pow(((modcolor_prime >> 0) & 0xFF) / 255.0, 2.2 / 1.0); + + ColorLUTNoGC[lr][i][0] = r; + ColorLUTNoGC[lr][i][1] = g; + ColorLUTNoGC[lr][i][2] = b; + + // Apply gamma correction + const float r_prime = pow(r, 1.0 / 2.2); + const float g_prime = pow(g, 1.0 / 2.2); + const float b_prime = pow(b, 1.0 / 2.2); + + ColorLUT[lr][i] = (int)(r_prime * 255) & 0xff | (int)(g_prime * 255) << 8 & 0xff00 | (int)(b_prime * 255) << 16 & 0xff0000 | 0xff000000; + } + } + + // Anaglyph slow-mode LUT calculation + for (int l_b = 0; l_b < 256; l_b++) + { + for (int r_b = 0; r_b < 256; r_b++) + { + float r, g, b; + float r_prime, g_prime, b_prime; + + r = ColorLUTNoGC[0][l_b][0] + ColorLUTNoGC[1][r_b][0]; + g = ColorLUTNoGC[0][l_b][1] + ColorLUTNoGC[1][r_b][1]; + b = ColorLUTNoGC[0][l_b][2] + ColorLUTNoGC[1][r_b][2]; + + if (r > 1.0) + r = 1.0; + if (g > 1.0) + g = 1.0; + if (b > 1.0) + b = 1.0; + + r_prime = pow(r, 1.0 / 2.2); + g_prime = pow(g, 1.0 / 2.2); + b_prime = pow(b, 1.0 / 2.2); + + AnaSlowColorLUT[l_b][r_b] = (int)(r_prime * 255) & 0xff | (int)(g_prime * 255) << 8 & 0xff00 | (int)(b_prime * 255) << 16 & 0xff0000 | 0xff000000; + } + } +} + +static void RecalcBrightnessCache(void) +{ + static const int32 MaxTime = 255; + int32 CumulativeTime = (BRTA + 1 + BRTB + 1 + BRTC + 1 + REST + 1) + 1; + + //printf("BRTA: %d, BRTB: %d, BRTC: %d, Rest: %d --- %d\n", BRTA, BRTB, BRTC, REST, BRTA + 1 + BRTB + 1 + BRTC); + + BrightnessCache[0] = 0; + BrightnessCache[1] = 0; + BrightnessCache[2] = 0; + BrightnessCache[3] = 0; + + for (int i = 0; i < Repeat + 1; i++) + { + int32 btemp[4]; + + if ((i * CumulativeTime) >= MaxTime) + break; + + btemp[1] = (i * CumulativeTime) + BRTA; + if (btemp[1] > MaxTime) + btemp[1] = MaxTime; + btemp[1] -= (i * CumulativeTime); + if (btemp[1] < 0) + btemp[1] = 0; + + btemp[2] = (i * CumulativeTime) + BRTA + 1 + BRTB; + if (btemp[2] > MaxTime) + btemp[2] = MaxTime; + btemp[2] -= (i * CumulativeTime) + BRTA + 1; + if (btemp[2] < 0) + btemp[2] = 0; + + //btemp[3] = (i * CumulativeTime) + BRTA + 1 + BRTB + 1 + BRTC; + //if(btemp[3] > MaxTime) + // btemp[3] = MaxTime; + //btemp[3] -= (i * CumulativeTime); + //if(btemp[3] < 0) + // btemp[3] = 0; + + btemp[3] = (i * CumulativeTime) + BRTA + BRTB + BRTC + 1; + if (btemp[3] > MaxTime) + btemp[3] = MaxTime; + btemp[3] -= (i * CumulativeTime) + 1; + if (btemp[3] < 0) + btemp[3] = 0; + + BrightnessCache[1] += btemp[1]; + BrightnessCache[2] += btemp[2]; + BrightnessCache[3] += btemp[3]; + } + + //printf("BC: %d %d %d %d\n", BrightnessCache[0], BrightnessCache[1], BrightnessCache[2], BrightnessCache[3]); + + for (int lr = 0; lr < 2; lr++) + for (int i = 0; i < 4; i++) + { + BrightCLUT[lr][i] = ColorLUT[lr][BrightnessCache[i]]; + //printf("%d %d, %08x\n", lr, i, BrightCLUT[lr][i]); + } +} + +static void Recalc3DModeStuff(bool non_rgb_output = false) +{ + switch (VB3DMode) + { + default: + if (((Anaglyph_Colors[0] & 0xFF) && (Anaglyph_Colors[1] & 0xFF)) || + ((Anaglyph_Colors[0] & 0xFF00) && (Anaglyph_Colors[1] & 0xFF00)) || + ((Anaglyph_Colors[0] & 0xFF0000) && (Anaglyph_Colors[1] & 0xFF0000)) || + non_rgb_output) + { + CopyFBColumnToTarget = CopyFBColumnToTarget_AnaglyphSlow; + } + else + CopyFBColumnToTarget = CopyFBColumnToTarget_Anaglyph; + break; + + case VB3DMODE_CSCOPE: + CopyFBColumnToTarget = CopyFBColumnToTarget_CScope; + break; + + case VB3DMODE_SIDEBYSIDE: + CopyFBColumnToTarget = CopyFBColumnToTarget_SideBySide; + break; + + case VB3DMODE_VLI: + CopyFBColumnToTarget = CopyFBColumnToTarget_VLI; + break; + + case VB3DMODE_HLI: + CopyFBColumnToTarget = CopyFBColumnToTarget_HLI; + break; + } + RecalcBrightnessCache(); +} + +void VIP_Set3DMode(uint32 mode, bool reverse, uint32 prescale, uint32 sbs_separation) +{ + VB3DMode = mode; + VB3DReverse = reverse ? 1 : 0; + VBPrescale = prescale; + VBSBS_Separation = sbs_separation; + + VidSettingsDirty = true; + + for (uint32 p = 0; p < 256; p++) + { + uint32 v; + uint8 s[4]; + + s[0] = (p >> 0) & 0x3; + s[1] = (p >> 2) & 0x3; + s[2] = (p >> 4) & 0x3; + s[3] = (p >> 6) & 0x3; + + v = 0; + for (unsigned int i = 0, shifty = 0; i < 4; i++) + { + for (unsigned int ps = 0; ps < prescale; ps++) + { + v |= s[i] << shifty; + shifty += 2; + } + } + + HLILUT[p] = v; + } +} + +void VIP_SetParallaxDisable(bool disabled) +{ + ParallaxDisabled = disabled; +} + +void VIP_SetDefaultColor(uint32 default_color) +{ + Default_Color = default_color; + + VidSettingsDirty = true; +} + +void VIP_SetLEDOnScale(float coeff) +{ + VBLEDOnScale = coeff; +} + +void VIP_SetAnaglyphColors(uint32 lcolor, uint32 rcolor) +{ + Anaglyph_Colors[0] = lcolor; + Anaglyph_Colors[1] = rcolor; + + VidSettingsDirty = true; +} + +static uint16 FRMCYC; + +static uint16 DPCTRL; +static bool DisplayActive; + +#define XPCTRL_XP_RST 0x0001 +#define XPCTRL_XP_EN 0x0002 +static uint16 XPCTRL; +static uint16 SBCMP; // Derived from XPCTRL + +static uint16 SPT[4]; // SPT0~SPT3, 5f848~5f84e +static uint16 GPLT[4]; +static uint8 GPLT_Cache[4][4]; + +static INLINE void Recalc_GPLT_Cache(int which) +{ + for (int i = 0; i < 4; i++) + GPLT_Cache[which][i] = (GPLT[which] >> (i * 2)) & 3; +} + +static uint16 JPLT[4]; +static uint8 JPLT_Cache[4][4]; + +static INLINE void Recalc_JPLT_Cache(int which) +{ + for (int i = 0; i < 4; i++) + JPLT_Cache[which][i] = (JPLT[which] >> (i * 2)) & 3; +} + +static uint16 BKCOL; + +// +// +// +static int32 CalcNextEvent(void); + +static int32 last_ts; + +static uint32 Column; +static int32 ColumnCounter; + +static int32 DisplayRegion; +static bool DisplayFB; + +static int32 GameFrameCounter; + +static int32 DrawingCounter; +static bool DrawingActive; +static bool DrawingFB; +static uint32 DrawingBlock; +static int32 SB_Latch; +static int32 SBOUT_InactiveTime; + +//static uint8 CTA_L, CTA_R; + +static void CheckIRQ(void) +{ + VBIRQ_Assert(VBIRQ_SOURCE_VIP, (bool)(InterruptEnable & InterruptPending)); + +#if 0 + printf("%08x\n", InterruptEnable & InterruptPending); + if((bool)(InterruptEnable & InterruptPending)) + puts("IRQ asserted"); + else + puts("IRQ not asserted"); +#endif +} + +void VIP_Init(void) +{ + ParallaxDisabled = false; + Anaglyph_Colors[0] = 0xFF0000; + Anaglyph_Colors[1] = 0x0000FF; + VB3DMode = VB3DMODE_ANAGLYPH; + Default_Color = 0xFFFFFF; + VB3DReverse = 0; + VBPrescale = 1; + VBSBS_Separation = 0; + + VidSettingsDirty = true; +} + +void VIP_Kill(void) +{ +} + +void VIP_Power(void) +{ + Repeat = 0; + SB_Latch = 0; + SBOUT_InactiveTime = -1; + last_ts = 0; + + Column = 0; + ColumnCounter = 259; + + DisplayRegion = 0; + DisplayFB = 0; + + GameFrameCounter = 0; + + DrawingCounter = 0; + DrawingActive = false; + DrawingFB = 0; + DrawingBlock = 0; + + DPCTRL = 2; + DisplayActive = false; + + memset(FB, 0, 0x6000 * 2 * 2); + memset(CHR_RAM, 0, 0x8000); + memset(DRAM, 0, 0x20000); + + InterruptPending = 0; + InterruptEnable = 0; + + BRTA = 0; + BRTB = 0; + BRTC = 0; + REST = 0; + + FRMCYC = 0; + + XPCTRL = 0; + SBCMP = 0; + + for (int i = 0; i < 4; i++) + { + SPT[i] = 0; + GPLT[i] = 0; + JPLT[i] = 0; + + Recalc_GPLT_Cache(i); + Recalc_JPLT_Cache(i); + } + + BKCOL = 0; +} + +static INLINE uint16 ReadRegister(int32 ×tamp, uint32 A) +{ + uint16 ret = 0; //0xFFFF; + + if (A & 1) + VIP_DBGMSG("Misaligned VIP Read: %08x", A); + + switch (A & 0xFE) + { + default: + VIP_DBGMSG("Unknown VIP register read: %08x", A); + break; + + case 0x00: + ret = InterruptPending; + break; + + case 0x02: + ret = InterruptEnable; + break; + + case 0x20: //printf("Read DPSTTS at %d\n", timestamp); + ret = DPCTRL & 0x702; + if ((DisplayRegion & 1) && DisplayActive) + { + unsigned int DPBSY = 1 << ((DisplayRegion >> 1) & 1); + + if (DisplayFB) + DPBSY <<= 2; + + ret |= DPBSY << 2; + } + //if(!(DisplayRegion & 1)) // FIXME? (Had to do it this way for Galactic Pinball...) + ret |= 1 << 6; + break; + + // Note: Upper bits of BRTA, BRTB, BRTC, and REST(?) are 0 when read(on real hardware) + case 0x24: + ret = BRTA; + break; + + case 0x26: + ret = BRTB; + break; + + case 0x28: + ret = BRTC; + break; + + case 0x2A: + ret = REST; + break; + + case 0x30: + ret = 0xFFFF; + break; + + case 0x40: + ret = XPCTRL & 0x2; + if (DrawingActive) + { + ret |= (1 + DrawingFB) << 2; + } + if (timestamp < SBOUT_InactiveTime) + { + ret |= 0x8000; + ret |= /*DrawingBlock*/ SB_Latch << 8; + } + break; // XPSTTS, read-only + + case 0x44: + ret = 2; // VIP version. 2 is a known valid version, while the validity of other numbers is unknown, so we'll just go with 2. + break; + + case 0x48: + case 0x4a: + case 0x4c: + case 0x4e: + ret = SPT[(A >> 1) & 3]; + break; + + case 0x60: + case 0x62: + case 0x64: + case 0x66: + ret = GPLT[(A >> 1) & 3]; + break; + + case 0x68: + case 0x6a: + case 0x6c: + case 0x6e: + ret = JPLT[(A >> 1) & 3]; + break; + + case 0x70: + ret = BKCOL; + break; + } + + return (ret); +} + +static INLINE void WriteRegister(int32 ×tamp, uint32 A, uint16 V) +{ + if (A & 1) + VIP_DBGMSG("Misaligned VIP Write: %08x %04x", A, V); + + switch (A & 0xFE) + { + default: + VIP_DBGMSG("Unknown VIP register write: %08x %04x", A, V); + break; + + case 0x00: + break; // Interrupt pending, read-only + + case 0x02: + { + InterruptEnable = V & 0xE01F; + + VIP_DBGMSG("Interrupt Enable: %04x", V); + + if (V & 0x2000) + VIP_DBGMSG("Warning: VIP SB Hit Interrupt enable: %04x\n", V); + CheckIRQ(); + } + break; + + case 0x04: + InterruptPending &= ~V; + CheckIRQ(); + break; + + case 0x20: + break; // Display control, read-only. + + case 0x22: + DPCTRL = V & (0x703); // Display-control, write-only + if (V & 1) + { + DisplayActive = false; + InterruptPending &= ~(INT_TIME_ERR | INT_FRAME_START | INT_GAME_START | INT_RFB_END | INT_LFB_END | INT_SCAN_ERR); + CheckIRQ(); + } + break; + + case 0x24: + BRTA = V & 0xFF; // BRTA + RecalcBrightnessCache(); + break; + + case 0x26: + BRTB = V & 0xFF; // BRTB + RecalcBrightnessCache(); + break; + + case 0x28: + BRTC = V & 0xFF; // BRTC + RecalcBrightnessCache(); + break; + + case 0x2A: + REST = V & 0xFF; // REST + RecalcBrightnessCache(); + break; + + case 0x2E: + FRMCYC = V & 0xF; // FRMCYC, write-only? + break; + + case 0x30: + break; // CTA, read-only( + + case 0x40: + break; // XPSTTS, read-only + + case 0x42: + XPCTRL = V & 0x0002; // XPCTRL, write-only + SBCMP = (V >> 8) & 0x1F; + + if (V & 1) + { + VIP_DBGMSG("XPRST"); + DrawingActive = 0; + DrawingCounter = 0; + InterruptPending &= ~(INT_SB_HIT | INT_XP_END | INT_TIME_ERR); + CheckIRQ(); + } + break; + + case 0x44: + break; // Version Control, read-only? + + case 0x48: + case 0x4a: + case 0x4c: + case 0x4e: + SPT[(A >> 1) & 3] = V & 0x3FF; + break; + + case 0x60: + case 0x62: + case 0x64: + case 0x66: + GPLT[(A >> 1) & 3] = V & 0xFC; + Recalc_GPLT_Cache((A >> 1) & 3); + break; + + case 0x68: + case 0x6a: + case 0x6c: + case 0x6e: + JPLT[(A >> 1) & 3] = V & 0xFC; + Recalc_JPLT_Cache((A >> 1) & 3); + break; + + case 0x70: + BKCOL = V & 0x3; + break; + } +} + +// +// Don't update the VIP state on reads/writes, the event system will update it with enough precision as far as VB software cares. +// + +MDFN_FASTCALL uint8 VIP_Read8(int32 ×tamp, uint32 A) +{ + uint8 ret = 0; //0xFF; + + //VIP_Update(timestamp); + + switch (A >> 16) + { + case 0x0: + case 0x1: + if ((A & 0x7FFF) >= 0x6000) + { + ret = ne16_rbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000)); + } + else + { + ret = FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF]; + } + break; + + case 0x2: + case 0x3: + ret = ne16_rbo_le(DRAM, A & 0x1FFFF); + break; + + case 0x4: + case 0x5: + if (A >= 0x5E000) + ret = ReadRegister(timestamp, A); + else + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + + case 0x6: + break; + + case 0x7: + if (A >= 0x8000) + { + ret = ne16_rbo_le(CHR_RAM, A & 0x7FFF); + } + else + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + + default: + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + } + + //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); + + return (ret); +} + +MDFN_FASTCALL uint16 VIP_Read16(int32 ×tamp, uint32 A) +{ + uint16 ret = 0; //0xFFFF; + + //VIP_Update(timestamp); + + switch (A >> 16) + { + case 0x0: + case 0x1: + if ((A & 0x7FFF) >= 0x6000) + { + ret = ne16_rbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000)); + } + else + { + ret = MDFN_de16lsb(&FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF]); + } + break; + + case 0x2: + case 0x3: + ret = ne16_rbo_le(DRAM, A & 0x1FFFF); + break; + + case 0x4: + case 0x5: + if (A >= 0x5E000) + ret = ReadRegister(timestamp, A); + else + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + + case 0x6: + break; + + case 0x7: + if (A >= 0x8000) + { + ret = ne16_rbo_le(CHR_RAM, A & 0x7FFF); + } + else + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + + default: + VIP_DBGMSG("Unknown VIP Read: %08x", A); + break; + } + + //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); + return (ret); +} + +MDFN_FASTCALL void VIP_Write8(int32 ×tamp, uint32 A, uint8 V) +{ + //VIP_Update(timestamp); + + //if(A >= 0x3DC00 && A < 0x3E000) + // printf("%08x %02x\n", A, V); + + switch (A >> 16) + { + case 0x0: + case 0x1: + if ((A & 0x7FFF) >= 0x6000) + ne16_wbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000), V); + else + FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF] = V; + break; + + case 0x2: + case 0x3: + ne16_wbo_le(DRAM, A & 0x1FFFF, V); + break; + + case 0x4: + case 0x5: + if (A >= 0x5E000) + WriteRegister(timestamp, A, V); + else + VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); + break; + + case 0x6: + VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); + break; + + case 0x7: + if (A >= 0x8000) + ne16_wbo_le(CHR_RAM, A & 0x7FFF, V); + else + VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); + break; + + default: + VIP_DBGMSG("Unknown VIP Write: %08x %02x", A, V); + break; + } + + //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); +} + +MDFN_FASTCALL void VIP_Write16(int32 ×tamp, uint32 A, uint16 V) +{ + //VIP_Update(timestamp); + + //if(A >= 0x3DC00 && A < 0x3E000) + // printf("%08x %04x\n", A, V); + + switch (A >> 16) + { + case 0x0: + case 0x1: + if ((A & 0x7FFF) >= 0x6000) + ne16_wbo_le(CHR_RAM, (A & 0x1FFF) | ((A >> 2) & 0x6000), V); + else + MDFN_en16lsb(&FB[(A >> 15) & 1][(A >> 16) & 1][A & 0x7FFF], V); + break; + + case 0x2: + case 0x3: + ne16_wbo_le(DRAM, A & 0x1FFFF, V); + break; + + case 0x4: + case 0x5: + if (A >= 0x5E000) + WriteRegister(timestamp, A, V); + else + VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); + break; + + case 0x6: + VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); + break; + + case 0x7: + if (A >= 0x8000) + ne16_wbo_le(CHR_RAM, A & 0x7FFF, V); + else + VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); + break; + + default: + VIP_DBGMSG("Unknown VIP Write: %08x %04x", A, V); + break; + } + + //VB_SetEvent(VB_EVENT_VIP, timestamp + CalcNextEvent()); +} + +static MDFN_Surface real_surface; +static MDFN_Surface *surface; + +void VIP_StartFrame(EmulateSpecStruct *espec) +{ + // puts("Start frame"); + + if (VidSettingsDirty) + { + MakeColorLUT(); + Recalc3DModeStuff(); + + VidSettingsDirty = false; + } + + espec->DisplayRect.x = 0; + espec->DisplayRect.y = 0; + + switch (VB3DMode) + { + default: + espec->DisplayRect.w = 384; + espec->DisplayRect.h = 224; + break; + + case VB3DMODE_VLI: + espec->DisplayRect.w = 768 * VBPrescale; + espec->DisplayRect.h = 224; + break; + + case VB3DMODE_HLI: + espec->DisplayRect.w = 384; + espec->DisplayRect.h = 448 * VBPrescale; + break; + + case VB3DMODE_CSCOPE: + espec->DisplayRect.w = 512; + espec->DisplayRect.h = 384; + break; + + case VB3DMODE_SIDEBYSIDE: + espec->DisplayRect.w = 768 + VBSBS_Separation; + espec->DisplayRect.h = 224; + break; + } + + surface = &real_surface; + real_surface.pixels = espec->pixels; + real_surface.pitch32 = espec->DisplayRect.w; +} + +void VIP_ResetTS(void) +{ + if (SBOUT_InactiveTime >= 0) + SBOUT_InactiveTime -= last_ts; + last_ts = 0; +} + +static int32 CalcNextEvent(void) +{ + return (ColumnCounter); +} + +#include "vip_draw.inc" + +static INLINE void CopyFBColumnToTarget_Anaglyph_BASE(const bool DisplayActive_arg, const int lr) +{ + const int fb = DisplayFB; + uint32 *target = surface->pixels + Column; + const int32 pitch32 = surface->pitch32; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + uint32 pixel = BrightCLUT[lr][source_bits & 3]; + + if (!DisplayActive_arg) + pixel = 0; + + if (lr) + *target |= pixel; + else + *target = pixel; + + source_bits >>= 2; + target += pitch32; + } + fb_source++; + } +} + +static void CopyFBColumnToTarget_Anaglyph(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_Anaglyph_BASE(0, 0); + else + CopyFBColumnToTarget_Anaglyph_BASE(0, 1); + } + else + { + if (!lr) + CopyFBColumnToTarget_Anaglyph_BASE(1, 0); + else + CopyFBColumnToTarget_Anaglyph_BASE(1, 1); + } +} + +static uint32 AnaSlowBuf[384][224]; + +static INLINE void CopyFBColumnToTarget_AnaglyphSlow_BASE(const bool DisplayActive_arg, const int lr) +{ + const int fb = DisplayFB; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + if (!lr) + { + uint32 *target = AnaSlowBuf[Column]; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + uint32 pixel = BrightnessCache[source_bits & 3]; + + if (!DisplayActive_arg) + pixel = 0; + + *target = pixel; + source_bits >>= 2; + target++; + } + fb_source++; + } + } + else + { + uint32 *target = surface->pixels + Column; + const uint32 *left_src = AnaSlowBuf[Column]; + const int32 pitch32 = surface->pitch32; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + uint32 pixel = AnaSlowColorLUT[*left_src][DisplayActive_arg ? BrightnessCache[source_bits & 3] : 0]; + + *target = pixel; + + source_bits >>= 2; + target += pitch32; + left_src++; + } + fb_source++; + } + } +} + +static void CopyFBColumnToTarget_AnaglyphSlow(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_AnaglyphSlow_BASE(0, 0); + else + CopyFBColumnToTarget_AnaglyphSlow_BASE(0, 1); + } + else + { + if (!lr) + CopyFBColumnToTarget_AnaglyphSlow_BASE(1, 0); + else + CopyFBColumnToTarget_AnaglyphSlow_BASE(1, 1); + } +} + +static void CopyFBColumnToTarget_CScope_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) +{ + const int fb = DisplayFB; + uint32 *target = surface->pixels + (dest_lr ? 512 - 16 - 1 : 16) + (dest_lr ? Column : 383 - Column) * surface->pitch32; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + if (DisplayActive_arg) + *target = BrightCLUT[lr][source_bits & 3]; + else + *target = 0; + + source_bits >>= 2; + if (dest_lr) + target--; + else + target++; + } + fb_source++; + } +} + +static void CopyFBColumnToTarget_CScope(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_CScope_BASE(0, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_CScope_BASE(0, 1, 1 ^ VB3DReverse); + } + else + { + if (!lr) + CopyFBColumnToTarget_CScope_BASE(1, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_CScope_BASE(1, 1, 1 ^ VB3DReverse); + } +} + +static void CopyFBColumnToTarget_SideBySide_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) +{ + const int fb = DisplayFB; + uint32 *target = surface->pixels + Column + (dest_lr ? (384 + VBSBS_Separation) : 0); + const int32 pitch32 = surface->pitch32; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + if (DisplayActive_arg) + *target = BrightCLUT[lr][source_bits & 3]; + else + *target = 0; + source_bits >>= 2; + target += pitch32; + } + fb_source++; + } +} + +static void CopyFBColumnToTarget_SideBySide(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_SideBySide_BASE(0, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_SideBySide_BASE(0, 1, 1 ^ VB3DReverse); + } + else + { + if (!lr) + CopyFBColumnToTarget_SideBySide_BASE(1, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_SideBySide_BASE(1, 1, 1 ^ VB3DReverse); + } +} + +static INLINE void CopyFBColumnToTarget_VLI_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) +{ + const int fb = DisplayFB; + uint32 *target = surface->pixels + Column * 2 * VBPrescale + dest_lr; + const int32 pitch32 = surface->pitch32; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + uint32 tv; + + if (DisplayActive_arg) + tv = BrightCLUT[lr][source_bits & 3]; + else + tv = 0; + + for (uint32 ps = 0; ps < VBPrescale; ps++) + target[ps * 2] = tv; + + source_bits >>= 2; + target += pitch32; + } + fb_source++; + } +} + +static void CopyFBColumnToTarget_VLI(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_VLI_BASE(0, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_VLI_BASE(0, 1, 1 ^ VB3DReverse); + } + else + { + if (!lr) + CopyFBColumnToTarget_VLI_BASE(1, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_VLI_BASE(1, 1, 1 ^ VB3DReverse); + } +} + +static INLINE void CopyFBColumnToTarget_HLI_BASE(const bool DisplayActive_arg, const int lr, const int dest_lr) +{ + const int fb = DisplayFB; + const int32 pitch32 = surface->pitch32; + uint32 *target = surface->pixels + Column + dest_lr * pitch32; + const uint8 *fb_source = &FB[fb][lr][64 * Column]; + + if (VBPrescale <= 4) + for (int y = 56; y; y--) + { + uint32 source_bits = HLILUT[*fb_source]; + + for (int y_sub = 4 * VBPrescale; y_sub; y_sub--) + { + if (DisplayActive_arg) + *target = BrightCLUT[lr][source_bits & 3]; + else + *target = 0; + + target += pitch32 * 2; + source_bits >>= 2; + } + fb_source++; + } + else + for (int y = 56; y; y--) + { + uint32 source_bits = *fb_source; + + for (int y_sub = 4; y_sub; y_sub--) + { + for (uint32 ps = 0; ps < VBPrescale; ps++) + { + if (DisplayActive_arg) + *target = BrightCLUT[lr][source_bits & 3]; + else + *target = 0; + + target += pitch32 * 2; + } + + source_bits >>= 2; + } + fb_source++; + } +} + +static void CopyFBColumnToTarget_HLI(void) +{ + const int lr = (DisplayRegion & 2) >> 1; + + if (!DisplayActive) + { + if (!lr) + CopyFBColumnToTarget_HLI_BASE(0, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_HLI_BASE(0, 1, 1 ^ VB3DReverse); + } + else + { + if (!lr) + CopyFBColumnToTarget_HLI_BASE(1, 0, 0 ^ VB3DReverse); + else + CopyFBColumnToTarget_HLI_BASE(1, 1, 1 ^ VB3DReverse); + } +} + +v810_timestamp_t MDFN_FASTCALL VIP_Update(const v810_timestamp_t timestamp) +{ + int32 clocks = timestamp - last_ts; + int32 running_timestamp = timestamp; + + while (clocks > 0) + { + int32 chunk_clocks = clocks; + + if (DrawingCounter > 0 && chunk_clocks > DrawingCounter) + chunk_clocks = DrawingCounter; + if (chunk_clocks > ColumnCounter) + chunk_clocks = ColumnCounter; + + running_timestamp += chunk_clocks; + + if (DrawingCounter > 0) + { + DrawingCounter -= chunk_clocks; + if (DrawingCounter <= 0) + { + alignas(8) uint8 DrawingBuffers[2][512 * 8]; // Don't decrease this from 512 unless you adjust vip_draw.inc(including areas that draw off-visible >= 384 and >= -7 for speed reasons) + + VIP_DrawBlock(DrawingBlock, DrawingBuffers[0] + 8, DrawingBuffers[1] + 8); + + for (int lr = 0; lr < 2; lr++) + { + uint8 *FB_Target = FB[DrawingFB][lr] + DrawingBlock * 2; + + for (int x = 0; x < 384; x++) + { + FB_Target[64 * x + 0] = (DrawingBuffers[lr][8 + x + 512 * 0] << 0) | (DrawingBuffers[lr][8 + x + 512 * 1] << 2) | (DrawingBuffers[lr][8 + x + 512 * 2] << 4) | (DrawingBuffers[lr][8 + x + 512 * 3] << 6); + FB_Target[64 * x + 1] = (DrawingBuffers[lr][8 + x + 512 * 4] << 0) | (DrawingBuffers[lr][8 + x + 512 * 5] << 2) | (DrawingBuffers[lr][8 + x + 512 * 6] << 4) | (DrawingBuffers[lr][8 + x + 512 * 7] << 6); + } + } + + SBOUT_InactiveTime = running_timestamp + 1120; + SB_Latch = DrawingBlock; // Not exactly correct, but probably doesn't matter. + + DrawingBlock++; + if (DrawingBlock == 28) + { + DrawingActive = false; + + InterruptPending |= INT_XP_END; + CheckIRQ(); + } + else + DrawingCounter += 1120 * 4; + } + } + + ColumnCounter -= chunk_clocks; + if (ColumnCounter == 0) + { + if (DisplayRegion & 1) + { + if (!(Column & 3)) + { + const int lr = (DisplayRegion & 2) >> 1; + uint16 ctdata = ne16_rbo_le(DRAM, 0x1DFFE - ((Column >> 2) * 2) - (lr ? 0 : 0x200)); + + //printf("%02x, repeat: %02x\n", ctdata & 0xFF, ctdata >> 8); + + if ((ctdata >> 8) != Repeat) + { + Repeat = ctdata >> 8; + RecalcBrightnessCache(); + } + } + CopyFBColumnToTarget(); + } + + ColumnCounter = 259; + Column++; + if (Column == 384) + { + Column = 0; + + if (DisplayActive) + { + if (DisplayRegion & 1) // Did we just finish displaying an active region? + { + if (DisplayRegion & 2) // finished displaying right eye + InterruptPending |= INT_RFB_END; + else // Otherwise, left eye + InterruptPending |= INT_LFB_END; + + CheckIRQ(); + } + } + + DisplayRegion = (DisplayRegion + 1) & 3; + + if (DisplayRegion == 0) // New frame start + { + DisplayActive = DPCTRL & 0x2; + + if (DisplayActive) + { + InterruptPending |= INT_FRAME_START; + CheckIRQ(); + } + GameFrameCounter++; + if (GameFrameCounter > FRMCYC) // New game frame start? + { + InterruptPending |= INT_GAME_START; + CheckIRQ(); + + if (XPCTRL & XPCTRL_XP_EN) + { + DisplayFB ^= 1; + + DrawingBlock = 0; + DrawingActive = true; + DrawingCounter = 1120 * 4; + DrawingFB = DisplayFB ^ 1; + } + + GameFrameCounter = 0; + } + + VB_ExitLoop(); + } + } + } + + clocks -= chunk_clocks; + } + + last_ts = timestamp; + + return (timestamp + CalcNextEvent()); +} +} diff --git a/waterbox/vb/vip.h b/waterbox/vb/vip.h new file mode 100644 index 0000000000..61bc1c59aa --- /dev/null +++ b/waterbox/vb/vip.h @@ -0,0 +1,83 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vip.h: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +namespace MDFN_IEN_VB +{ +void VIP_Init(void) MDFN_COLD; +void VIP_Kill(void) MDFN_COLD; +void VIP_Power(void) MDFN_COLD; + +void VIP_SetInstantDisplayHack(bool) MDFN_COLD; +void VIP_SetAllowDrawSkip(bool) MDFN_COLD; +void VIP_Set3DMode(uint32 mode, bool reverse, uint32 prescale, uint32 sbs_separation) MDFN_COLD; +void VIP_SetParallaxDisable(bool disabled) MDFN_COLD; +void VIP_SetDefaultColor(uint32 default_color) MDFN_COLD; +void VIP_SetAnaglyphColors(uint32 lcolor, uint32 rcolor) MDFN_COLD; // R << 16, G << 8, B << 0 +void VIP_SetLEDOnScale(float coeff) MDFN_COLD; + +v810_timestamp_t MDFN_FASTCALL VIP_Update(const v810_timestamp_t timestamp); +void VIP_ResetTS(void); + +void VIP_StartFrame(EmulateSpecStruct *espec); + +MDFN_FASTCALL uint8 VIP_Read8(v810_timestamp_t ×tamp, uint32 A); +MDFN_FASTCALL uint16 VIP_Read16(v810_timestamp_t ×tamp, uint32 A); + +MDFN_FASTCALL void VIP_Write8(v810_timestamp_t ×tamp, uint32 A, uint8 V); +MDFN_FASTCALL void VIP_Write16(v810_timestamp_t ×tamp, uint32 A, uint16 V); + +enum +{ + VIP_GSREG_IPENDING = 0, // Current pending interrupt(bits) + VIP_GSREG_IENABLE, + + VIP_GSREG_DPCTRL, + + VIP_GSREG_BRTA, + VIP_GSREG_BRTB, + VIP_GSREG_BRTC, + VIP_GSREG_REST, + VIP_GSREG_FRMCYC, + VIP_GSREG_XPCTRL, + + VIP_GSREG_SPT0, + VIP_GSREG_SPT1, + VIP_GSREG_SPT2, + VIP_GSREG_SPT3, + + VIP_GSREG_GPLT0, + VIP_GSREG_GPLT1, + VIP_GSREG_GPLT2, + VIP_GSREG_GPLT3, + + VIP_GSREG_JPLT0, + VIP_GSREG_JPLT1, + VIP_GSREG_JPLT2, + VIP_GSREG_JPLT3, + + VIP_GSREG_BKCOL, +}; + +uint32 VIP_GetRegister(const unsigned int id, char *special, const uint32 special_len); +void VIP_SetRegister(const unsigned int id, const uint32 value); +} diff --git a/waterbox/vb/vip_draw.inc b/waterbox/vb/vip_draw.inc new file mode 100644 index 0000000000..f7eb222589 --- /dev/null +++ b/waterbox/vb/vip_draw.inc @@ -0,0 +1,493 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vip_draw.inc: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#define BGM_AFFINE 0x2 +#define BGM_OBJ 0x3 + + +static void DrawBG(uint8 *target, uint16 RealY, bool lr, uint8 bgmap_base_raw, bool overplane, uint16 overplane_char, uint32 SourceX, uint32 SourceY, uint32 scx, uint32 scy, uint16 DestX, uint16 DestY, uint16 DestWidth, uint16 DestHeight) +{ + const uint16 *CHR16 = CHR_RAM; + const uint16 *BGMap = DRAM; + uint32 BGMap_Base = bgmap_base_raw << 12; + int32 start_x, final_x; + const uint32 bgsc_overplane = DRAM[overplane_char]; + const uint32 BGMap_XCount = 1 << scx; + const uint32 BGMap_YCount = 1 << scy; + const uint32 SourceX_Size = 512 * BGMap_XCount; + const uint32 SourceY_Size = 512 * BGMap_YCount; + const uint32 SourceX_Mask = overplane ? 0x1FFF : (SourceX_Size - 1); + const uint32 SourceY_Mask = overplane ? 0x1FFF : (SourceY_Size - 1); + + if((uint16)(RealY - DestY) > DestHeight) + return; + + //printf("%d, %d, %d, %d\n", overplane, srcXSize, srcYSize, bgmap_base_raw); + + DestX = sign_10_to_s16(DestX); + + if(DestX & 0x8000) + SourceX -= DestX; + + start_x = (int16)DestX; + final_x = (int16)DestX + DestWidth; + + if(start_x < 0) + start_x = 0; + + if(final_x > 383) + final_x = 383; + + if(start_x > final_x) + return; + + // Optimization: + SourceY &= SourceY_Mask; + BGMap_Base |= (((SourceY >> 3) & 0x3F) * 0x40) | (((SourceY << 3) & ~0xFFF) << scx); + + for(int x = start_x; x <= final_x; x++) + { + uint32 bgsc; + uint32 char_no; + uint32 palette_selector; + uint32 hflip_xor; + uint32 vflip_xor; + + SourceX &= SourceX_Mask; + + bgsc = bgsc_overplane; + + if(SourceX < SourceX_Size && SourceY < SourceY_Size) + bgsc = BGMap[(BGMap_Base | ((SourceX << 3) & ~0xFFF) | ((SourceX >> 3) & 0x3F)) & 0xFFFF]; + + char_no = bgsc & 0x7FF; + palette_selector = bgsc >> 14; + hflip_xor = (bgsc & 0x2000) ? 7 : 0; //(((int32)bgsc << 18) >> 31) & 0x7; + vflip_xor = (bgsc & 0x1000) ? 7 : 0; //(((int32)bgsc << 19) >> 31) & 0x7; + + unsigned int char_sub_y = vflip_xor ^ (SourceY & 0x7); + + if(!(SourceX & 7) && (x + 7) <= final_x) + { + uint32 pixels = CHR16[char_no * 8 + char_sub_y]; + + #if 0 + unsigned int char_sub_x; + uint8 *sub_target = target + x + 8; + + for(int sub_x = -8; sub_x < 0; sub_x++) + { + if(pixels & 3) sub_target[sub_x] = GPLT_Cache[palette_selector][pixels & 3]; + pixels >>= 2; + } + #endif + + if(bgsc & 0x2000) + { + if((pixels >> 14) & 3) target[0 + x] = GPLT_Cache[palette_selector][(pixels >> 14) & 3]; + if((pixels >> 12) & 3) target[1 + x] = GPLT_Cache[palette_selector][(pixels >> 12) & 3]; + if((pixels >> 10) & 3) target[2 + x] = GPLT_Cache[palette_selector][(pixels >> 10) & 3]; + if((pixels >> 8) & 3) target[3 + x] = GPLT_Cache[palette_selector][(pixels >> 8) & 3]; + if((pixels >> 6) & 3) target[4 + x] = GPLT_Cache[palette_selector][(pixels >> 6) & 3]; + if((pixels >> 4) & 3) target[5 + x] = GPLT_Cache[palette_selector][(pixels >> 4) & 3]; + if((pixels >> 2) & 3) target[6 + x] = GPLT_Cache[palette_selector][(pixels >> 2) & 3]; + if((pixels >> 0) & 3) target[7 + x] = GPLT_Cache[palette_selector][(pixels >> 0) & 3]; + } + else + { + if((pixels >> 0) & 3) target[0 + x] = GPLT_Cache[palette_selector][(pixels >> 0) & 3]; + if((pixels >> 2) & 3) target[1 + x] = GPLT_Cache[palette_selector][(pixels >> 2) & 3]; + if((pixels >> 4) & 3) target[2 + x] = GPLT_Cache[palette_selector][(pixels >> 4) & 3]; + if((pixels >> 6) & 3) target[3 + x] = GPLT_Cache[palette_selector][(pixels >> 6) & 3]; + if((pixels >> 8) & 3) target[4 + x] = GPLT_Cache[palette_selector][(pixels >> 8) & 3]; + if((pixels >> 10) & 3) target[5 + x] = GPLT_Cache[palette_selector][(pixels >> 10) & 3]; + if((pixels >> 12) & 3) target[6 + x] = GPLT_Cache[palette_selector][(pixels >> 12) & 3]; + if((pixels >> 14) & 3) target[7 + x] = GPLT_Cache[palette_selector][(pixels >> 14) & 3]; + } + + x += 7; + SourceX += 8; + } + else + { + unsigned int char_sub_x; + + char_sub_x = hflip_xor ^ (SourceX & 0x7); + + uint8 pixel = (CHR16[char_no * 8 + char_sub_y] >> (char_sub_x * 2)) & 0x3; + + if(pixel) + target[x] = GPLT_Cache[palette_selector][pixel]; //target[x] = (GPLT[palette_selector] >> (pixel * 2)) & 0x3; + SourceX++; + } + } +} + +static void DrawAffine(uint8 *target, uint16 RealY, bool lr, uint32 ParamBase, uint32 BGMap_Base, bool OverplaneMode, uint16 OverplaneChar, uint32 scx, uint32 scy, + uint16 DestX, uint16 DestY, uint16 DestWidth, uint16 DestHeight) +{ + const uint16 *CHR16 = CHR_RAM; + const uint16 *BGMap = DRAM; + + const uint32 BGMap_XCount = 1 << scx; + const uint32 BGMap_YCount = 1 << scy; + const uint32 SourceX_Size = 512 * BGMap_XCount; + const uint32 SourceY_Size = 512 * BGMap_YCount; + + const uint16 *param_ptr = &DRAM[(ParamBase + 8 * (RealY - DestY)) & 0xFFFF]; + int16 mx = param_ptr[0], mp = (ParallaxDisabled ? 0 : param_ptr[1]), my = param_ptr[2], dx = param_ptr[3], dy = param_ptr[4]; + + uint32 SourceX, SourceY; + uint32 SourceX_Mask, SourceY_Mask; + + int32 start_x, final_x; + const uint32 bgsc_overplane = DRAM[OverplaneChar]; + + + DestX = sign_10_to_s16(DestX); + + if((uint16)(RealY - DestY) > DestHeight) + return; + + SourceX = (int32)mx << 6; + SourceY = (int32)my << 6; + + if(DestX & 0x8000) + { + SourceX += dx * (65536 - DestX); + SourceY += dy * (65536 - DestX); + } + + if(mp >= 0 && lr) + { + SourceX += dx * mp; + SourceY += dy * mp; + } + else if(mp < 0 && !lr) + { + SourceX += dx * -mp; + SourceY += dy * -mp; + } + + if(OverplaneMode) + { + SourceX_Mask = 0x3FFFFFF; //(((uint32)SourceX_Size << 9) * 2) - 1; + SourceY_Mask = 0x3FFFFFF; //(((uint32)SourceY_Size << 9) * 2) - 1; + } + else + { + SourceX_Mask = ((uint32)SourceX_Size << 9) - 1; + SourceY_Mask = ((uint32)SourceY_Size << 9) - 1; + } + + start_x = (int16)DestX; + final_x = (int16)DestX + DestWidth; + + if(start_x < 0) + start_x = 0; + + if(final_x > 383) + final_x = 383; + +if(dy == 0) // Optimization for no rotation. +{ + SourceY &= SourceY_Mask; + + if(SourceY >= (SourceY_Size << 9)) + return; + + BGMap_Base |= (((SourceY >> 6) & ~0xFFF) << scx) | (((SourceY >> 12) & 0x3F) * 0x40); + for(int x = start_x; x <= final_x; x++) + { + uint32 bgsc; + uint32 hflip_xor; + uint32 vflip_xor; + uint32 pixel = 0; + + SourceX &= SourceX_Mask; + + bgsc = bgsc_overplane; + + if(SourceX < (SourceX_Size << 9)) + bgsc = BGMap[(BGMap_Base | ((SourceX >> 6) & ~0xFFF) | ((SourceX >> 12) & 0x3F)) & 0xFFFF]; + + //hflip_xor = bgsc & 0x2000 ? 0xE : 0; + //vflip_xor = bgsc & 0x1000 ? 0x7 : 0; + hflip_xor = ((int32)(bgsc << 18) >> 30) & 0xE; + vflip_xor = ((int32)(bgsc << 19) >> 31) & 0x7; + + unsigned int char_sub_y = vflip_xor ^ ((SourceY >> 9) & 0x7); + unsigned int char_sub_x = hflip_xor ^ ((SourceX >> 8) & 0xE); + + pixel = (CHR16[((bgsc & 0x7FF) * 8) | char_sub_y] >> char_sub_x) & 0x3; + + if(pixel) + target[x] = GPLT_Cache[bgsc >> 14][pixel]; + + SourceX += dx; + } +} +else + for(int x = start_x; x <= final_x; x++) + { + uint32 bgsc; + uint32 char_no; + uint32 palette_selector; + uint32 hflip_xor; + uint32 vflip_xor; + uint8 pixel = 0; + + SourceX &= SourceX_Mask; + SourceY &= SourceY_Mask; + + bgsc = bgsc_overplane; + + if(SourceX < (SourceX_Size << 9) && SourceY < (SourceY_Size << 9)) + { + uint32 m_index = ((SourceX >> 6) & ~0xFFF) + (((SourceY >> 6) & ~0xFFF) << scx); + uint32 sub_index = ((SourceX >> 12) & 0x3F) + (((SourceY >> 12) & 0x3F) * 0x40); + + bgsc = BGMap[(BGMap_Base | m_index | sub_index) & 0xFFFF]; + + //bgsc = BGMap[(BGMapBase + (SourceX >> 12) + (SourceY >> 12) * (SourceX_Size >> 3)) & 0xFFFF ]; + } + char_no = bgsc & 0x7FF; + palette_selector = bgsc >> 14; + hflip_xor = bgsc & 0x2000 ? 7 : 0; //(((int32)bgsc << 18) >> 31) & 0x7; + vflip_xor = bgsc & 0x1000 ? 7 : 0; //(((int32)bgsc << 19) >> 31) & 0x7; + + unsigned int char_sub_y = vflip_xor ^ ((SourceY >> 9) & 0x7); + unsigned int char_sub_x = hflip_xor ^ ((SourceX >> 9) & 0x7); + + pixel = (CHR16[char_no * 8 + char_sub_y] >> (char_sub_x * 2)) & 0x3; + + if(pixel) + target[x] = GPLT_Cache[palette_selector][pixel]; + + SourceX += dx; + SourceY += dy; + } +} + +static int obj_search_which; + +static void DrawOBJ(uint8 *fb[2], uint16 Y, bool lron[2]) +{ + const uint16 *CHR16 = CHR_RAM; + + int32 start_oam; + int32 end_oam; + + start_oam = SPT[obj_search_which]; + + end_oam = 1023; + if(obj_search_which) + end_oam = SPT[obj_search_which - 1]; + + int32 oam = start_oam; + do + { + const uint16 *oam_ptr = &DRAM[(0x1E000 + (oam * 8)) >> 1]; + const uint32 jy = oam_ptr[2]; + const uint32 tile_y = (Y - jy) & 0xFF; // I think this mask is right. See: http://www.planetvb.com/modules/newbb/viewtopic.php?topic_id=3797&forum=2 + + if(tile_y >= 8) + continue; + + uint32 jx = oam_ptr[0]; + uint32 jp = ParallaxDisabled ? 0 : (oam_ptr[1] & 0x3FFF); + uint32 palette_selector = oam_ptr[3] >> 14; + uint32 vflip_xor = (oam_ptr[3] & 0x1000) ? 7 : 0; + uint32 char_sub_y = vflip_xor ^ tile_y; + bool jlron[2] = { (bool)(oam_ptr[1] & 0x8000), (bool)(oam_ptr[1] & 0x4000) }; + uint32 char_no = oam_ptr[3] & 0x7FF; + const uint32 pixels_save = CHR16[char_no * 8 + char_sub_y]; + + for(int lr = 0; lr < 2; lr++) + { + if(!(jlron[lr] & lron[lr])) + continue; + + uint32 pixels = pixels_save; + int32 x = sign_x_to_s32(10, (jx + (lr ? jp : -jp))); // It may actually be 9, TODO? + + if(x >= -7 && x < 384) // Make sure we always keep the pitch of our 384x8 buffer large enough(with padding before and after the visible space) + { + uint8 *target = &fb[lr][x]; + + if(oam_ptr[3] & 0x2000) + { + target += 7; + + for(int meow = 8; meow; meow--) + { + if(pixels & 3) + *target = JPLT_Cache[palette_selector][pixels & 3]; + target--; + pixels >>= 2; + } + } + else + { + for(int meow = 8; meow; meow--) + { + if(pixels & 3) + *target = JPLT_Cache[palette_selector][pixels & 3]; + target++; + pixels >>= 2; + } + } + #if 0 + if(oam_ptr[3] & 0x2000) + { + if((pixels >> 14) & 3) fb[lr][0 + x] = JPLT_Cache[palette_selector][(pixels >> 14) & 3]; + if((pixels >> 12) & 3) fb[lr][1 + x] = JPLT_Cache[palette_selector][(pixels >> 12) & 3]; + if((pixels >> 10) & 3) fb[lr][2 + x] = JPLT_Cache[palette_selector][(pixels >> 10) & 3]; + if((pixels >> 8) & 3) fb[lr][3 + x] = JPLT_Cache[palette_selector][(pixels >> 8) & 3]; + if((pixels >> 6) & 3) fb[lr][4 + x] = JPLT_Cache[palette_selector][(pixels >> 6) & 3]; + if((pixels >> 4) & 3) fb[lr][5 + x] = JPLT_Cache[palette_selector][(pixels >> 4) & 3]; + if((pixels >> 2) & 3) fb[lr][6 + x] = JPLT_Cache[palette_selector][(pixels >> 2) & 3]; + if((pixels >> 0) & 3) fb[lr][7 + x] = JPLT_Cache[palette_selector][(pixels >> 0) & 3]; + } + else + { + if((pixels >> 0) & 3) fb[lr][0 + x] = JPLT_Cache[palette_selector][(pixels >> 0) & 3]; + if((pixels >> 2) & 3) fb[lr][1 + x] = JPLT_Cache[palette_selector][(pixels >> 2) & 3]; + if((pixels >> 4) & 3) fb[lr][2 + x] = JPLT_Cache[palette_selector][(pixels >> 4) & 3]; + if((pixels >> 6) & 3) fb[lr][3 + x] = JPLT_Cache[palette_selector][(pixels >> 6) & 3]; + if((pixels >> 8) & 3) fb[lr][4 + x] = JPLT_Cache[palette_selector][(pixels >> 8) & 3]; + if((pixels >> 10) & 3) fb[lr][5 + x] = JPLT_Cache[palette_selector][(pixels >> 10) & 3]; + if((pixels >> 12) & 3) fb[lr][6 + x] = JPLT_Cache[palette_selector][(pixels >> 12) & 3]; + if((pixels >> 14) & 3) fb[lr][7 + x] = JPLT_Cache[palette_selector][(pixels >> 14) & 3]; + } +#endif + + } + + } + } while( (oam = (oam - 1) & 1023) != end_oam); + +} + + +void VIP_DrawBlock(uint8 block_no, uint8 *fb_l, uint8 *fb_r) +{ + for(int y = 0; y < 8; y++) + { + memset(fb_l + y * 512, BKCOL, 384); + memset(fb_r + y * 512, BKCOL, 384); + } + + obj_search_which = 3; + + for(int world = 31; world >= 0; world--) + { + const uint16 *world_ptr = &DRAM[(0x1D800 + world * 0x20) >> 1]; + + uint32 bgmap_base = world_ptr[0] & 0xF; + bool end = world_ptr[0] & 0x40; + bool over = world_ptr[0] & 0x80; + uint32 scy = (world_ptr[0] >> 8) & 3; + uint32 scx = (world_ptr[0] >> 10) & 3; + uint32 bgm = (world_ptr[0] >> 12) & 3; + bool lron[2] = { (bool)(world_ptr[0] & 0x8000), (bool)(world_ptr[0] & 0x4000) }; + + uint16 gx = sign_11_to_s16(world_ptr[1]); + uint16 gp = ParallaxDisabled ? 0 : sign_9_to_s16(world_ptr[2]); + uint16 gy = sign_11_to_s16(world_ptr[3]); + uint16 mx = world_ptr[4]; + uint16 mp = ParallaxDisabled ? 0 : sign_9_to_s16(world_ptr[5]); + uint16 my = world_ptr[6]; + uint16 window_width = sign_11_to_s16(world_ptr[7]); + uint16 window_height = (world_ptr[8] & 0x3FF); + uint32 param_base = (world_ptr[9] & 0xFFF0); + uint16 overplane_char = world_ptr[10]; + + if(end) + break; + + if(((512 << scx) + (512 << scy)) > 4096) + { + printf("BG Size too large for world: %d(scx=%d, scy=%d)\n", world, scx, scy); + } + +// if(world != 2) +// continue; + + // if(block_no == 8) + // printf("World: %d; gx: %d, gp: %d, gy: %d, mx: %d, mp: %d, my: %d, window_width: %d, window_height: %d\n", world, gx, gp, gy, mx, mp, my, window_width, window_height); + + for(int y = 0; y < 8; y++) + { + uint8 *fb[2] = { &fb_l[y * 512], &fb_r[y * 512] }; + + if(bgm == BGM_OBJ) + { + if(!lron[0] || !lron[1]) + printf("Bad OBJ World? %d(%d/%d) %d~%d\n", world, lron[0], lron[1], SPT[obj_search_which], obj_search_which ? (SPT[obj_search_which - 1] + 1) : 0); + + DrawOBJ(fb, (block_no * 8) + y, lron); + } + else if(bgm == BGM_AFFINE) + { + //if(((block_no * 8) + y) == 128) + // printf("Draw affine: %d %d\n", gx, gp); + for(int lr = 0; lr < 2; lr++) + { + if(lron[lr]) + { + DrawAffine(fb[lr], (block_no * 8) + y, lr, param_base, bgmap_base * 4096, over, overplane_char, scx, scy, + gx + (lr ? gp : -gp), gy, window_width, window_height); + } + } + } + else + for(int lr = 0; lr < 2; lr++) + { + uint16 srcX, srcY; + uint16 RealY = (block_no * 8) + y; + uint16 DestX; + uint16 DestY; + + srcX = mx + (lr ? mp : -mp); + srcY = my + (RealY - gy); + + DestX = gx + (lr ? gp : -gp); + DestY = gy; + + if(lron[lr]) + { + if(bgm == 1) // HBias + srcX += (int16)DRAM[(param_base + (((RealY - DestY) * 2) | lr)) & 0xFFFF]; + + DrawBG(fb[lr], RealY, lr, bgmap_base, over, overplane_char, (int32)(int16)srcX, (int32)(int16)srcY, scx, scy, DestX, DestY, window_width, window_height); + } + } + } + + if(bgm == BGM_OBJ) + if(obj_search_which) + obj_search_which--; + + } + + +} diff --git a/waterbox/vb/vsu.cpp b/waterbox/vb/vsu.cpp new file mode 100644 index 0000000000..d99b254509 --- /dev/null +++ b/waterbox/vb/vsu.cpp @@ -0,0 +1,498 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vsu.cpp: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "vb.h" + +static const unsigned int Tap_LUT[8] = {15 - 1, 11 - 1, 14 - 1, 5 - 1, 9 - 1, 7 - 1, 10 - 1, 12 - 1}; + +VSU::VSU() +{ + Synth.volume(1.0 / 6 / 2); + + for (int ch = 0; ch < 6; ch++) + { + for (int lr = 0; lr < 2; lr++) + last_output[ch][lr] = 0; + } +} + +VSU::~VSU() +{ +} + +void VSU::SetSoundRate(double rate) +{ + for (int y = 0; y < 2; y++) + { + sbuf[y].set_sample_rate(rate ? rate : 44100, 50); + sbuf[y].clock_rate((long)(VB_MASTER_CLOCK / 4)); + sbuf[y].bass_freq(20); + } +} + +void VSU::Power(void) +{ + SweepControl = 0; + SweepModCounter = 0; + SweepModClockDivider = 1; + + for (int ch = 0; ch < 6; ch++) + { + IntlControl[ch] = 0; + LeftLevel[ch] = 0; + RightLevel[ch] = 0; + Frequency[ch] = 0; + EnvControl[ch] = 0; + RAMAddress[ch] = 0; + + EffFreq[ch] = 0; + Envelope[ch] = 0; + WavePos[ch] = 0; + FreqCounter[ch] = 1; + IntervalCounter[ch] = 0; + EnvelopeCounter[ch] = 1; + + EffectsClockDivider[ch] = 4800; + IntervalClockDivider[ch] = 4; + EnvelopeClockDivider[ch] = 4; + + LatcherClockDivider[ch] = 120; + } + + NoiseLatcherClockDivider = 120; + NoiseLatcher = 0; + + memset(WaveData, 0, sizeof(WaveData)); + memset(ModData, 0, sizeof(ModData)); + + last_ts = 0; +} + +void VSU::Write(int32 timestamp, uint32 A, uint8 V) +{ + A &= 0x7FF; + + Update(timestamp); + + //printf("VSU Write: %d, %08x %02x\n", timestamp, A, V); + + if (A < 0x280) + WaveData[A >> 7][(A >> 2) & 0x1F] = V & 0x3F; + else if (A < 0x400) + { + //if(A >= 0x300) + // printf("Modulation mirror write? %08x %02x\n", A, V); + ModData[(A >> 2) & 0x1F] = V; + } + else if (A < 0x600) + { + int ch = (A >> 6) & 0xF; + + //if(ch < 6) + //printf("Ch: %d, Reg: %d, Value: %02x\n", ch, (A >> 2) & 0xF, V); + + if (ch > 5) + { + if (A == 0x580 && (V & 1)) + { + //puts("STOP, HAMMER TIME"); + for (int i = 0; i < 6; i++) + IntlControl[i] &= ~0x80; + } + } + else + switch ((A >> 2) & 0xF) + { + case 0x0: + IntlControl[ch] = V & ~0x40; + + if (V & 0x80) + { + EffFreq[ch] = Frequency[ch]; + if (ch == 5) + FreqCounter[ch] = 10 * (2048 - EffFreq[ch]); + else + FreqCounter[ch] = 2048 - EffFreq[ch]; + IntervalCounter[ch] = (V & 0x1F) + 1; + EnvelopeCounter[ch] = (EnvControl[ch] & 0x7) + 1; + + if (ch == 4) + { + SweepModCounter = (SweepControl >> 4) & 7; + SweepModClockDivider = (SweepControl & 0x80) ? 8 : 1; + ModWavePos = 0; + } + + WavePos[ch] = 0; + + if (ch == 5) // Not sure if this is correct. + lfsr = 1; + + //if(!(IntlControl[ch] & 0x80)) + // Envelope[ch] = (EnvControl[ch] >> 4) & 0xF; + + EffectsClockDivider[ch] = 4800; + IntervalClockDivider[ch] = 4; + EnvelopeClockDivider[ch] = 4; + } + break; + + case 0x1: + LeftLevel[ch] = (V >> 4) & 0xF; + RightLevel[ch] = (V >> 0) & 0xF; + break; + + case 0x2: + Frequency[ch] &= 0xFF00; + Frequency[ch] |= V << 0; + EffFreq[ch] &= 0xFF00; + EffFreq[ch] |= V << 0; + break; + + case 0x3: + Frequency[ch] &= 0x00FF; + Frequency[ch] |= (V & 0x7) << 8; + EffFreq[ch] &= 0x00FF; + EffFreq[ch] |= (V & 0x7) << 8; + break; + + case 0x4: + EnvControl[ch] &= 0xFF00; + EnvControl[ch] |= V << 0; + + Envelope[ch] = (V >> 4) & 0xF; + break; + + case 0x5: + EnvControl[ch] &= 0x00FF; + if (ch == 4) + EnvControl[ch] |= (V & 0x73) << 8; + else if (ch == 5) + { + EnvControl[ch] |= (V & 0x73) << 8; + lfsr = 1; + } + else + EnvControl[ch] |= (V & 0x03) << 8; + break; + + case 0x6: + RAMAddress[ch] = V & 0xF; + break; + + case 0x7: + if (ch == 4) + { + SweepControl = V; + } + break; + } + } +} + +INLINE void VSU::CalcCurrentOutput(int ch, int &left, int &right) +{ + if (!(IntlControl[ch] & 0x80)) + { + left = right = 0; + return; + } + + int WD; + int l_ol, r_ol; + + if (ch == 5) + WD = NoiseLatcher; //(NoiseLatcher << 6) - NoiseLatcher; + else + { + if (RAMAddress[ch] > 4) + WD = 0; + else + WD = WaveData[RAMAddress[ch]][WavePos[ch]]; // - 0x20; + } + l_ol = Envelope[ch] * LeftLevel[ch]; + if (l_ol) + { + l_ol >>= 3; + l_ol += 1; + } + + r_ol = Envelope[ch] * RightLevel[ch]; + if (r_ol) + { + r_ol >>= 3; + r_ol += 1; + } + + left = WD * l_ol; + right = WD * r_ol; +} + +void VSU::Update(int32 timestamp) +{ + //puts("VSU Start"); + int left, right; + + for (int ch = 0; ch < 6; ch++) + { + int32 clocks = timestamp - last_ts; + int32 running_timestamp = last_ts; + + // Output sound here + CalcCurrentOutput(ch, left, right); + Synth.offset_inline(running_timestamp, left - last_output[ch][0], &sbuf[0]); + Synth.offset_inline(running_timestamp, right - last_output[ch][1], &sbuf[1]); + last_output[ch][0] = left; + last_output[ch][1] = right; + + if (!(IntlControl[ch] & 0x80)) + continue; + + while (clocks > 0) + { + int32 chunk_clocks = clocks; + + if (chunk_clocks > EffectsClockDivider[ch]) + chunk_clocks = EffectsClockDivider[ch]; + + if (ch == 5) + { + if (chunk_clocks > NoiseLatcherClockDivider) + chunk_clocks = NoiseLatcherClockDivider; + } + else + { + if (EffFreq[ch] >= 2040) + { + if (chunk_clocks > LatcherClockDivider[ch]) + chunk_clocks = LatcherClockDivider[ch]; + } + else + { + if (chunk_clocks > FreqCounter[ch]) + chunk_clocks = FreqCounter[ch]; + } + } + + if (ch == 5 && chunk_clocks > NoiseLatcherClockDivider) + chunk_clocks = NoiseLatcherClockDivider; + + FreqCounter[ch] -= chunk_clocks; + while (FreqCounter[ch] <= 0) + { + if (ch == 5) + { + int feedback = ((lfsr >> 7) & 1) ^ ((lfsr >> Tap_LUT[(EnvControl[5] >> 12) & 0x7]) & 1) ^ 1; + lfsr = ((lfsr << 1) & 0x7FFF) | feedback; + + FreqCounter[ch] += 10 * (2048 - EffFreq[ch]); + } + else + { + FreqCounter[ch] += 2048 - EffFreq[ch]; + WavePos[ch] = (WavePos[ch] + 1) & 0x1F; + } + } + + LatcherClockDivider[ch] -= chunk_clocks; + while (LatcherClockDivider[ch] <= 0) + LatcherClockDivider[ch] += 120; + + if (ch == 5) + { + NoiseLatcherClockDivider -= chunk_clocks; + if (!NoiseLatcherClockDivider) + { + NoiseLatcherClockDivider = 120; + NoiseLatcher = ((lfsr & 1) << 6) - (lfsr & 1); + } + } + + EffectsClockDivider[ch] -= chunk_clocks; + while (EffectsClockDivider[ch] <= 0) + { + EffectsClockDivider[ch] += 4800; + + IntervalClockDivider[ch]--; + while (IntervalClockDivider[ch] <= 0) + { + IntervalClockDivider[ch] += 4; + + if (IntlControl[ch] & 0x20) + { + IntervalCounter[ch]--; + if (!IntervalCounter[ch]) + { + IntlControl[ch] &= ~0x80; + } + } + + EnvelopeClockDivider[ch]--; + while (EnvelopeClockDivider[ch] <= 0) + { + EnvelopeClockDivider[ch] += 4; + + if (EnvControl[ch] & 0x0100) // Enveloping enabled? + { + EnvelopeCounter[ch]--; + if (!EnvelopeCounter[ch]) + { + EnvelopeCounter[ch] = (EnvControl[ch] & 0x7) + 1; + + if (EnvControl[ch] & 0x0008) // Grow + { + if (Envelope[ch] < 0xF || (EnvControl[ch] & 0x200)) + Envelope[ch] = (Envelope[ch] + 1) & 0xF; + } + else // Decay + { + if (Envelope[ch] > 0 || (EnvControl[ch] & 0x200)) + Envelope[ch] = (Envelope[ch] - 1) & 0xF; + } + } + } + + } // end while(EnvelopeClockDivider[ch] <= 0) + } // end while(IntervalClockDivider[ch] <= 0) + + if (ch == 4) + { + SweepModClockDivider--; + while (SweepModClockDivider <= 0) + { + SweepModClockDivider += (SweepControl & 0x80) ? 8 : 1; + + if (((SweepControl >> 4) & 0x7) && (EnvControl[ch] & 0x4000)) + { + if (SweepModCounter) + SweepModCounter--; + + if (!SweepModCounter) + { + SweepModCounter = (SweepControl >> 4) & 0x7; + + if (EnvControl[ch] & 0x1000) // Modulation + { + if (ModWavePos < 32 || (EnvControl[ch] & 0x2000)) + { + ModWavePos &= 0x1F; + + EffFreq[ch] = (EffFreq[ch] + (int8)ModData[ModWavePos]); + if (EffFreq[ch] < 0) + { + //puts("Underflow"); + EffFreq[ch] = 0; + } + else if (EffFreq[ch] > 0x7FF) + { + //puts("Overflow"); + EffFreq[ch] = 0x7FF; + } + ModWavePos++; + } + //puts("Mod"); + } + else // Sweep + { + int32 delta = EffFreq[ch] >> (SweepControl & 0x7); + int32 NewFreq = EffFreq[ch] + ((SweepControl & 0x8) ? delta : -delta); + + //printf("Sweep(%d): Old: %d, New: %d\n", ch, EffFreq[ch], NewFreq); + + if (NewFreq < 0) + EffFreq[ch] = 0; + else if (NewFreq > 0x7FF) + { + //EffFreq[ch] = 0x7FF; + IntlControl[ch] &= ~0x80; + } + else + EffFreq[ch] = NewFreq; + } + } + } + } // end while(SweepModClockDivider <= 0) + } // end if(ch == 4) + } // end while(EffectsClockDivider[ch] <= 0) + clocks -= chunk_clocks; + running_timestamp += chunk_clocks; + + // Output sound here too. + CalcCurrentOutput(ch, left, right); + Synth.offset_inline(running_timestamp, left - last_output[ch][0], &sbuf[0]); + Synth.offset_inline(running_timestamp, right - last_output[ch][1], &sbuf[1]); + last_output[ch][0] = left; + last_output[ch][1] = right; + } + } + last_ts = timestamp; + //puts("VSU End"); +} + +int32 VSU::EndFrame(int32 timestamp, int16 *SoundBuf, int32 SoundBufMaxSize) +{ + int32 ret = 0; + + Update(timestamp); + last_ts = 0; + + if (SoundBuf) + { + for (int y = 0; y < 2; y++) + { + sbuf[y].end_frame(timestamp); + ret = sbuf[y].read_samples(SoundBuf + y, SoundBufMaxSize, 1); + } + } + + return ret; +} + +uint8 VSU::PeekWave(const unsigned int which, uint32 Address) +{ + assert(which <= 4); + + Address &= 0x1F; + + return (WaveData[which][Address]); +} + +void VSU::PokeWave(const unsigned int which, uint32 Address, uint8 value) +{ + assert(which <= 4); + + Address &= 0x1F; + + WaveData[which][Address] = value & 0x3F; +} + +uint8 VSU::PeekModWave(uint32 Address) +{ + Address &= 0x1F; + return (ModData[Address]); +} + +void VSU::PokeModWave(uint32 Address, uint8 value) +{ + Address &= 0x1F; + + ModData[Address] = value & 0xFF; +} diff --git a/waterbox/vb/vsu.h b/waterbox/vb/vsu.h new file mode 100644 index 0000000000..c8230beb85 --- /dev/null +++ b/waterbox/vb/vsu.h @@ -0,0 +1,93 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vsu.h: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +class VSU +{ + public: + VSU() + MDFN_COLD; + ~VSU() MDFN_COLD; + + void SetSoundRate(double rate) MDFN_COLD; + + void Power(void) MDFN_COLD; + + void Write(int32 timestamp, uint32 A, uint8 V); + + int32 EndFrame(int32 timestamp, int16 *SoundBuf, int32 SoundBufMaxSize); + + uint8 PeekWave(const unsigned int which, uint32 Address); + void PokeWave(const unsigned int which, uint32 Address, uint8 value); + + uint8 PeekModWave(uint32 Address); + void PokeModWave(uint32 Address, uint8 value); + + private: + void CalcCurrentOutput(int ch, int &left, int &right); + + void Update(int32 timestamp); + + uint8 IntlControl[6]; + uint8 LeftLevel[6]; + uint8 RightLevel[6]; + uint16 Frequency[6]; + uint16 EnvControl[6]; // Channel 5/6 extra functionality tacked on too. + + uint8 RAMAddress[6]; + + uint8 SweepControl; + + uint8 WaveData[5][0x20]; + + uint8 ModData[0x20]; + + int32 EffFreq[6]; + int32 Envelope[6]; + + int32 WavePos[6]; + int32 ModWavePos; + + int32 LatcherClockDivider[6]; + + int32 FreqCounter[6]; + int32 IntervalCounter[6]; + int32 EnvelopeCounter[6]; + int32 SweepModCounter; + + int32 EffectsClockDivider[6]; + int32 IntervalClockDivider[6]; + int32 EnvelopeClockDivider[6]; + int32 SweepModClockDivider; + + int32 NoiseLatcherClockDivider; + uint32 NoiseLatcher; + + uint32 lfsr; + + int32 last_output[6][2]; + int32 last_ts; + + Blip_Buffer sbuf[2]; + Blip_Synth Synth; + Blip_Synth NoiseSynth; +};