BizHawk/BizHawk.Emulation.Cores/Calculator/TI83.cs

520 lines
13 KiB
C#

using System;
using System.Globalization;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components.Z80A;
// http://www.ticalc.org/pub/text/calcinfo/
namespace BizHawk.Emulation.Cores.Calculators
{
[Core(
"TI83Hawk",
"zeromus",
isPorted: false,
isReleased: true)]
[ServiceNotApplicable(typeof(ISoundProvider), typeof(ISaveRam), typeof(IRegionable), typeof(IDriveLight), typeof(IBoardInfo))]
public partial class TI83 : IEmulator, IVideoProvider, IStatable, IDebuggable, IInputPollable, ISettable<TI83.TI83Settings, object>
{
[CoreConstructor("TI83")]
public TI83(CoreComm comm, GameInfo game, byte[] rom, object settings)
{
var ser = new BasicServiceProvider(this);
ServiceProvider = ser;
PutSettings((TI83Settings)settings ?? new TI83Settings());
CoreComm = comm;
_cpu.FetchMemory = ReadMemory;
_cpu.ReadMemory = ReadMemory;
_cpu.WriteMemory = WriteMemory;
_cpu.ReadHardware = ReadHardware;
_cpu.WriteHardware = WriteHardware;
_cpu.IRQCallback = IRQCallback;
_cpu.NMICallback = NMICallback;
_cpu.MemoryCallbacks = MemoryCallbacks;
_rom = rom;
LinkPort = new TI83LinkPort(this);
HardReset();
SetupMemoryDomains();
_tracer = new TraceBuffer { Header = _cpu.TraceHeader };
ser.Register<ITraceable>(_tracer);
ser.Register<IDisassemblable>(_cpu);
}
private readonly TraceBuffer _tracer;
private readonly Z80A _cpu = new Z80A();
private readonly byte[] _rom;
// configuration
private IController _controller = NullController.Instance;
private byte[] _ram;
private byte[] _vram = new byte[0x300];
private int _romPageLow3Bits;
private int _romPageHighBit;
private byte _maskOn;
private bool _onPressed;
private int _keyboardMask;
private int _displayMode;
private int _displayMove;
private uint _displayX, _displayY;
private bool _cursorMoved;
private int _frame;
public bool ON_key_int, ON_key_int_EN;
public bool TIM_1_int, TIM_1_int_EN;
public int TIM_frq, TIM_mult, TIM_count, TIM_hit;
// Link Cable
public TI83LinkPort LinkPort { get; }
private int _linkOutput;
internal int LinkOutput => _linkOutput;
internal bool LinkActive { private get; set; }
internal int LinkInput { private get; set; }
internal int LinkState => (_linkOutput | LinkInput) ^ 3;
private static readonly ControllerDefinition TI83Controller =
new ControllerDefinition
{
Name = "TI83 Controller",
BoolButtons =
{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "DOT",
"ON", "ENTER",
"DOWN", "LEFT", "UP", "RIGHT",
"PLUS", "MINUS", "MULTIPLY", "DIVIDE",
"CLEAR", "EXP", "DASH", "PARACLOSE", "TAN", "VARS", "PARAOPEN",
"COS", "PRGM", "STAT", "COMMA", "SIN", "MATRIX", "X",
"STO", "LN", "LOG", "SQUARED", "NEG1", "MATH", "ALPHA",
"GRAPH", "TRACE", "ZOOM", "WINDOW", "Y", "2ND", "MODE", "DEL"
}
};
private byte ReadMemory(ushort addr)
{
byte ret;
int romPage = _romPageLow3Bits | (_romPageHighBit << 3);
if (addr < 0x4000)
{
ret = _rom[addr]; // ROM zero-page
}
else if (addr < 0x8000)
{
ret = _rom[(romPage * 0x4000) + addr - 0x4000]; // other rom page
}
else
{
ret = _ram[addr - 0x8000];
}
return ret;
}
private void WriteMemory(ushort addr, byte value)
{
if (addr < 0x4000)
{
// ROM zero-page
}
else if (addr < 0x8000)
{
// other rom page
}
else
{
_ram[addr - 0x8000] = value;
}
}
private void WriteHardware(ushort addr, byte value)
{
addr &= 0xFF;
switch (addr)
{
case 0: // PORT_LINK
_romPageHighBit = (value >> 4) & 1;
_linkOutput = value & 3;
if (LinkActive)
{
// Prevent rom calls from disturbing link port activity
if (LinkActive && _cpu.RegPC < 0x4000)
{
return;
}
LinkPort.Update();
}
break;
case 1: // PORT_KEYBOARD:
_lagged = false;
_keyboardMask = value;
////Console.WriteLine("write PORT_KEYBOARD {0:X2}",value);
break;
case 2: // PORT_ROMPAGE
_romPageLow3Bits = value & 0x7;
break;
case 3: // PORT_STATUS
// controls ON key interrupts
if ((value & 0x1) == 0)
{
ON_key_int = false;
ON_key_int_EN = false;
}
else
{
ON_key_int_EN = true;
}
// controls first timer interrupts
if ((value & 0x2) == 0)
{
TIM_1_int = false;
TIM_1_int_EN = false;
}
else
{
TIM_1_int_EN = true;
}
// controls second timer, not yet implemented and unclear how to differentiate
if ((value & 0x4) == 0)
{
}
else
{
}
// controls low power mode, not yet implemeneted
if ((value & 0x8) == 0)
{
}
else
{
}
break;
case 4: // PORT_INTCTRL
// controls ON key interrupts
TIM_frq = value & 6;
TIM_mult = ((value & 0x10) == 0x10) ? 1800 : 1620;
TIM_hit = (int)Math.Floor((double)TIM_mult / (3 + TIM_frq * 2));
TIM_hit = (int)Math.Floor((double)6000000 / TIM_hit);
// Bit 0 is some form of memory mapping
// Bit 5 controls reset
// Bit 6-7 controls battery power compare (not implemented, will always return full power)
break;
case 16: // PORT_DISPCTRL
////Console.WriteLine("write PORT_DISPCTRL {0}",value);
WriteDispCtrl(value);
break;
case 17: // PORT_DISPDATA
////Console.WriteLine("write PORT_DISPDATA {0}",value);
WriteDispData(value);
break;
}
}
private byte ReadHardware(ushort addr)
{
addr &= 0xFF;
switch (addr)
{
case 0: // PORT_LINK
LinkPort.Update();
return (byte)((_romPageHighBit << 4) | (LinkState << 2) | LinkOutput);
case 1: // PORT_KEYBOARD:
////Console.WriteLine("read PORT_KEYBOARD");
return ReadKeyboard();
case 2: // PORT_ROMPAGE
return (byte)_romPageLow3Bits;
case 3: // PORT_STATUS
{
// Console.WriteLine("read PORT_STATUS");
// Bits:
// 0 - Set if ON key Interrupt generated
// 1 - Update things (keyboard etc)
// 2 - Unknown, but used
// 3 - Set if ON key is up
// 4-7 - Unknown
return (byte)((_controller.IsPressed("ON") ? 0 : 8) |
(TIM_1_int ? 2 : 0) |
(ON_key_int ? 1 : 0));
}
case 4: // PORT_INTCTRL
// returns mirror of link port
return (byte)((_romPageHighBit << 4) | (LinkState << 2) | LinkOutput);
case 16: // PORT_DISPCTRL
// Console.WriteLine("read DISPCTRL");
break;
case 17: // PORT_DISPDATA
return ReadDispData();
}
return 0xFF;
}
private byte ReadKeyboard()
{
InputCallbacks.Call();
// ref TI-9X
int ret = 0xFF;
////Console.WriteLine("keyboardMask: {0:X2}",keyboardMask);
if ((_keyboardMask & 1) == 0)
{
if (_controller.IsPressed("DOWN")) ret ^= 1;
if (_controller.IsPressed("LEFT")) ret ^= 2;
if (_controller.IsPressed("RIGHT")) ret ^= 4;
if (_controller.IsPressed("UP")) ret ^= 8;
}
if ((_keyboardMask & 2) == 0)
{
if (_controller.IsPressed("ENTER")) ret ^= 1;
if (_controller.IsPressed("PLUS")) ret ^= 2;
if (_controller.IsPressed("MINUS")) ret ^= 4;
if (_controller.IsPressed("MULTIPLY")) ret ^= 8;
if (_controller.IsPressed("DIVIDE")) ret ^= 16;
if (_controller.IsPressed("EXP")) ret ^= 32;
if (_controller.IsPressed("CLEAR")) ret ^= 64;
}
if ((_keyboardMask & 4) == 0)
{
if (_controller.IsPressed("DASH")) ret ^= 1;
if (_controller.IsPressed("3")) ret ^= 2;
if (_controller.IsPressed("6")) ret ^= 4;
if (_controller.IsPressed("9")) ret ^= 8;
if (_controller.IsPressed("PARACLOSE")) ret ^= 16;
if (_controller.IsPressed("TAN")) ret ^= 32;
if (_controller.IsPressed("VARS")) ret ^= 64;
}
if ((_keyboardMask & 8) == 0)
{
if (_controller.IsPressed("DOT")) ret ^= 1;
if (_controller.IsPressed("2")) ret ^= 2;
if (_controller.IsPressed("5")) ret ^= 4;
if (_controller.IsPressed("8")) ret ^= 8;
if (_controller.IsPressed("PARAOPEN")) ret ^= 16;
if (_controller.IsPressed("COS")) ret ^= 32;
if (_controller.IsPressed("PRGM")) ret ^= 64;
if (_controller.IsPressed("STAT")) ret ^= 128;
}
if ((_keyboardMask & 16) == 0)
{
if (_controller.IsPressed("0")) ret ^= 1;
if (_controller.IsPressed("1")) ret ^= 2;
if (_controller.IsPressed("4")) ret ^= 4;
if (_controller.IsPressed("7")) ret ^= 8;
if (_controller.IsPressed("COMMA")) ret ^= 16;
if (_controller.IsPressed("SIN")) ret ^= 32;
if (_controller.IsPressed("MATRIX")) ret ^= 64;
if (_controller.IsPressed("X")) ret ^= 128;
}
if ((_keyboardMask & 32) == 0)
{
if (_controller.IsPressed("STO")) ret ^= 2;
if (_controller.IsPressed("LN")) ret ^= 4;
if (_controller.IsPressed("LOG")) ret ^= 8;
if (_controller.IsPressed("SQUARED")) ret ^= 16;
if (_controller.IsPressed("NEG1")) ret ^= 32;
if (_controller.IsPressed("MATH")) ret ^= 64;
if (_controller.IsPressed("ALPHA")) ret ^= 128;
}
if ((_keyboardMask & 64) == 0)
{
if (_controller.IsPressed("GRAPH")) ret ^= 1;
if (_controller.IsPressed("TRACE")) ret ^= 2;
if (_controller.IsPressed("ZOOM")) ret ^= 4;
if (_controller.IsPressed("WINDOW")) ret ^= 8;
if (_controller.IsPressed("Y")) ret ^= 16;
if (_controller.IsPressed("2ND")) ret ^= 32;
if (_controller.IsPressed("MODE")) ret ^= 64;
if (_controller.IsPressed("DEL")) ret ^= 128;
}
return (byte)ret;
}
private byte ReadDispData()
{
if (_cursorMoved)
{
_cursorMoved = false;
return 0x00; // not accurate this should be stale data or something
}
byte ret;
if (_displayMode == 1)
{
ret = _vram[(_displayY * 12) + _displayX];
}
else
{
int column = 6 * (int)_displayX;
int offset = (int)(_displayY * 12) + (column >> 3);
int shift = 10 - (column & 7);
ret = (byte)(((_vram[offset] << 8) | _vram[offset + 1]) >> shift);
}
DoDispMove();
return ret;
}
private void WriteDispData(byte value)
{
int offset;
if (_displayMode == 1)
{
offset = (int)(_displayY * 12) + (int)_displayX;
_vram[offset] = value;
}
else
{
int column = 6 * (int)_displayX;
offset = (int)(_displayY * 12) + (column >> 3);
if (offset < 0x300)
{
int shift = column & 7;
int mask = ~(252 >> shift);
int data = value << 2;
_vram[offset] = (byte)(_vram[offset] & mask | (data >> shift));
if (shift > 2 && offset < 0x2ff)
{
offset++;
shift = 8 - shift;
mask = ~(252 << shift);
_vram[offset] = (byte)(_vram[offset] & mask | (data << shift));
}
}
}
DoDispMove();
}
private void DoDispMove()
{
switch (_displayMove)
{
case 0:
_displayY--;
break;
case 1:
_displayY++;
break;
case 2:
_displayX--;
break;
case 3:
_displayX++;
break;
}
_displayX &= 0xF; // 0xF or 0x1F? dunno
_displayY &= 0x3F;
}
private void WriteDispCtrl(byte value)
{
if (value <= 1)
{
_displayMode = value;
}
else if (value >= 4 && value <= 7)
{
_displayMove = value - 4;
}
else if ((value & 0xC0) == 0x40)
{
// hardware scroll
}
else if ((value & 0xE0) == 0x20)
{
_displayX = (uint)(value & 0x1F);
_cursorMoved = true;
}
else if ((value & 0xC0) == 0x80)
{
_displayY = (uint)(value & 0x3F);
_cursorMoved = true;
}
else if ((value & 0xC0) == 0xC0)
{
// contrast
}
else if (value == 2)
{
}
else if (value == 3)
{
}
}
private void IRQCallback()
{
//Console.WriteLine("IRQ with vec {0} and cpu.InterruptMode {1}", _cpu.Regs[_cpu.I], _cpu.InterruptMode);
_cpu.FlagI = false;
}
private void NMICallback()
{
//Console.WriteLine("NMI");
_cpu.NonMaskableInterrupt = false;
}
private void HardReset()
{
_cpu.Reset();
_ram = new byte[0x8000];
for (int i = 0; i < 0x8000; i++)
{
_ram[i] = 0xFF;
}
_cpu.RegPC = 0;
_cpu.IFF1 = false;
_cpu.IFF2 = false;
_cpu.InterruptMode = 2;
_maskOn = 1;
_romPageHighBit = 0;
_romPageLow3Bits = 0;
_keyboardMask = 0;
_displayMode = 0;
_displayMove = 0;
_displayX = _displayY = 0;
}
}
}