[ChannelF] Cleanup + optimize some code

This commit is contained in:
CasualPokePlayer 2024-09-06 13:42:58 -07:00
parent 314f32b95f
commit 84f4d6ae28
26 changed files with 418 additions and 578 deletions

View File

@ -8,7 +8,7 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
/// <summary> /// <summary>
/// Disassembler /// Disassembler
/// </summary> /// </summary>
public sealed partial class F3850 : IDisassemblable public sealed partial class F3850<TLink> : IDisassemblable
{ {
private static string Result(string format, Func<ushort, byte> read, ref ushort addr) private static string Result(string format, Func<ushort, byte> read, ref ushort addr)
{ {

View File

@ -2,7 +2,7 @@
namespace BizHawk.Emulation.Cores.Components.FairchildF8 namespace BizHawk.Emulation.Cores.Components.FairchildF8
{ {
public sealed partial class F3850 public sealed partial class F3850<TLink>
{ {
public const int MaxInstructionLength = 48; public const int MaxInstructionLength = 48;

View File

@ -5,21 +5,21 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
/// <summary> /// <summary>
/// ALU Operations /// ALU Operations
/// </summary> /// </summary>
public sealed partial class F3850 public sealed partial class F3850<TLink>
{ {
public void Read_Func(byte dest, byte src_l, byte src_h) public void Read_Func(byte dest, byte src_l, byte src_h)
{ {
Regs[dest] = ReadMemory((ushort)(Regs[src_l] | (Regs[src_h]) << 8)); Regs[dest] = _link.ReadMemory((ushort)(Regs[src_l] | (Regs[src_h]) << 8));
} }
public void Write_Func(byte dest_l, byte dest_h, byte src) public void Write_Func(byte dest_l, byte dest_h, byte src)
{ {
WriteMemory((ushort)(Regs[dest_l] | (Regs[dest_h] << 8)), Regs[src]); _link.WriteMemory((ushort)(Regs[dest_l] | (Regs[dest_h] << 8)), Regs[src]);
} }
public void IN_Func(byte dest, byte src) public void IN_Func(byte dest, byte src)
{ {
Regs[dest] = ReadHardware(Regs[src]); Regs[dest] = _link.ReadHardware(Regs[src]);
} }
/// <summary> /// <summary>
@ -48,7 +48,7 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
{ {
// data is complemented between accumulator and I/O pins (because PINs are active-low) // data is complemented between accumulator and I/O pins (because PINs are active-low)
// however for ease here we will make them active-high // however for ease here we will make them active-high
WriteHardware(Regs[dest], Regs[src]); _link.WriteHardware(Regs[dest], Regs[src]);
} }
public void ClearFlags_Func() public void ClearFlags_Func()

View File

@ -7,7 +7,7 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
/// <summary> /// <summary>
/// Internal Registers /// Internal Registers
/// </summary> /// </summary>
public sealed partial class F3850 public sealed partial class F3850<TLink>
{ {
/// <summary> /// <summary>
/// Registers (counters and scratchpad) /// Registers (counters and scratchpad)

View File

@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// Vectors of Instruction Operations /// Vectors of Instruction Operations
/// </summary> /// </summary>
public sealed partial class F3850 public sealed partial class F3850<TLink>
{ {
/// <summary> /// <summary>
/// LR - LOAD REGISTER /// LR - LOAD REGISTER

View File

@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
/// ///
/// Note: Programmable timer and interrupt logic from the F3851 is not currently emulated /// Note: Programmable timer and interrupt logic from the F3851 is not currently emulated
/// </summary> /// </summary>
public sealed partial class F3850 public sealed partial class F3850<TLink> where TLink : IF3850Link
{ {
// operations that can take place in an instruction // operations that can take place in an instruction
public const byte ROMC_01 = 1; public const byte ROMC_01 = 1;
@ -100,8 +100,11 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
public const byte OP_DS = 157; public const byte OP_DS = 157;
public const byte OP_LIS = 158; public const byte OP_LIS = 158;
public F3850() private readonly TLink _link;
public F3850(TLink link)
{ {
_link = link;
Reset(); Reset();
} }
@ -133,36 +136,6 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
public IMemoryCallbackSystem MemoryCallbacks { get; set; } public IMemoryCallbackSystem MemoryCallbacks { get; set; }
// Memory Access
public Func<ushort, byte> ReadMemory;
public Action<ushort, byte> WriteMemory;
public Func<ushort, byte> PeekMemory;
public Func<ushort, byte> DummyReadMemory;
// Hardware I/O Port Access
public Func<ushort, byte> ReadHardware;
public Action<ushort, byte> WriteHardware;
public Action<ushort> OnExecFetch;
public void SetCallbacks
(
Func<ushort, byte> ReadMemory,
Func<ushort, byte> DummyReadMemory,
Func<ushort, byte> PeekMemory,
Action<ushort, byte> WriteMemory,
Func<ushort, byte> ReadHardware,
Action<ushort, byte> WriteHardware
)
{
this.ReadMemory = ReadMemory;
this.DummyReadMemory = DummyReadMemory;
this.PeekMemory = PeekMemory;
this.WriteMemory = WriteMemory;
this.ReadHardware = ReadHardware;
this.WriteHardware = WriteHardware;
}
/// <summary> /// <summary>
/// Runs a single CPU clock cycle /// Runs a single CPU clock cycle
/// </summary> /// </summary>
@ -180,7 +153,7 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
{ {
// always the last tick within an opcode instruction cycle // always the last tick within an opcode instruction cycle
case END: case END:
OnExecFetch?.Invoke(RegPC0); _link.OnExecFetch(RegPC0);
TraceCallback?.Invoke(State()); TraceCallback?.Invoke(State());
opcode = Regs[DB]; opcode = Regs[DB];
instr_pntr = 0; instr_pntr = 0;
@ -785,12 +758,12 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
{ {
int bytes_read = 0; int bytes_read = 0;
ushort pc = (ushort)(RegPC0 - 1); ushort pc = (ushort)(RegPC0 - 1);
string disasm = disassemble ? Disassemble(pc, ReadMemory, out bytes_read) : "---"; string disasm = disassemble ? Disassemble(pc, _link.ReadMemory, out bytes_read) : "---";
string byte_code = null; string byte_code = null;
for (ushort i = 0; i < bytes_read; i++) for (ushort i = 0; i < bytes_read; i++)
{ {
byte_code += ReadMemory((ushort)(pc + i)).ToString("X2"); byte_code += _link.ReadMemory((ushort)(pc + i)).ToString("X2");
if (i < (bytes_read - 1)) if (i < (bytes_read - 1))
{ {
byte_code += " "; byte_code += " ";
@ -868,7 +841,7 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
public void SyncState(Serializer ser) public void SyncState(Serializer ser)
{ {
ser.BeginSection(nameof(F3850)); ser.BeginSection("F3850");
ser.Sync(nameof(Regs), ref Regs, false); ser.Sync(nameof(Regs), ref Regs, false);
ser.Sync(nameof(cur_instr), ref cur_instr, false); ser.Sync(nameof(cur_instr), ref cur_instr, false);
ser.Sync(nameof(instr_pntr), ref instr_pntr); ser.Sync(nameof(instr_pntr), ref instr_pntr);

View File

@ -0,0 +1,19 @@
namespace BizHawk.Emulation.Cores.Components.FairchildF8
{
// Interface that has all the methods required by the F3850 to talk to
// the emulator core.
// Should only be used as a generic type argument for the F3850, and
// implementations should be structs where possible. This combination allows
// the JITer to generate much faster code than calling a Func<> or Action<>.
public interface IF3850Link
{
byte ReadMemory(ushort address);
void WriteMemory(ushort address, byte value);
byte ReadHardware(ushort address);
void WriteHardware(ushort address, byte value);
// This only calls when the first byte of an instruction is fetched.
void OnExecFetch(ushort address);
}
}

View File

@ -2,36 +2,29 @@
{ {
/// <summary> /// <summary>
/// Hangman ChannelF Cartridge /// Hangman ChannelF Cartridge
/// 2KB ROM / NO RAM /// Utilises 2102 SRAM over IO
/// </summary> /// </summary>
public class mapper_HANG : VesCartBase public class MapperHANG : VesCartBase
{ {
public override string BoardType => "HANG"; public override string BoardType => "HANG";
public mapper_HANG(byte[] rom) public MapperHANG(byte[] rom)
{ {
ROM = new byte[0x10000 - 0x800]; _rom = new byte[0x10000 - 0x800];
for (int i = 0; i < rom.Length; i++) Array.Copy(rom, _rom, rom.Length);
{ _rom.AsSpan(rom.Length).Fill(0xFF);
ROM[i] = rom[i]; _ram = new byte[0x400];
if (i > 3000)
{
var test = rom[i];
}
}
RAM = new byte[0x400];
} }
public override byte ReadBus(ushort addr) public override byte ReadBus(ushort addr)
{ {
var off = addr - 0x800; var off = addr - 0x800;
return ROM[off]; return _rom[off];
} }
public override void WriteBus(ushort addr, byte value) public override void WriteBus(ushort addr, byte value)
{ {
// no writeable memory // no directly writeable memory
} }
public override byte ReadPort(ushort addr) public override byte ReadPort(ushort addr)
@ -45,6 +38,5 @@
var index = addr - 0x20; var index = addr - 0x20;
SRAM2102_Write(index, data); SRAM2102_Write(index, data);
} }
} }
} }

View File

@ -1,27 +1,25 @@
namespace BizHawk.Emulation.Cores.Consoles.ChannelF namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{ {
/// <summary> /// <summary>
/// ChannelF Cartridge that utilises 2102 SRAM over IO /// Maze ChannelF Cartridge
/// Utilises 2102 SRAM over IO
/// </summary> /// </summary>
public class mapper_MAZE : VesCartBase public class MapperMAZE : VesCartBase
{ {
public override string BoardType => "MAZE"; public override string BoardType => "MAZE";
public mapper_MAZE(byte[] rom) public MapperMAZE(byte[] rom)
{ {
ROM = new byte[0x10000 - 0x800]; _rom = new byte[0x10000 - 0x800];
for (int i = 0; i < rom.Length; i++) Array.Copy(rom, _rom, rom.Length);
{ _rom.AsSpan(rom.Length).Fill(0xFF);
ROM[i] = rom[i]; _ram = new byte[0x400];
}
RAM = new byte[0x400];
} }
public override byte ReadBus(ushort addr) public override byte ReadBus(ushort addr)
{ {
var off = addr - 0x800; var off = addr - 0x800;
return ROM[off]; return _rom[off];
} }
public override void WriteBus(ushort addr, byte value) public override void WriteBus(ushort addr, byte value)
@ -38,7 +36,7 @@
public override void WritePort(ushort addr, byte data) public override void WritePort(ushort addr, byte data)
{ {
var index = addr - 0x24; var index = addr - 0x24;
SRAM2102_Write(index, data); SRAM2102_Write(index, data);
} }
} }
} }

View File

@ -3,19 +3,15 @@
/// <summary> /// <summary>
/// Sean Riddle's modified SCHACH cart mapper (multi-cart) (WIP) /// Sean Riddle's modified SCHACH cart mapper (multi-cart) (WIP)
/// </summary> /// </summary>
public class mapper_RIDDLE : VesCartBase public class MapperRIDDLE : VesCartBase
{ {
public override string BoardType => "RIDDLE"; public override string BoardType => "RIDDLE";
public mapper_RIDDLE(byte[] rom) public MapperRIDDLE(byte[] rom)
{ {
ROM = new byte[rom.Length]; _rom = new byte[rom.Length];
for (int i = 0; i < rom.Length; i++) Array.Copy(rom, _rom, rom.Length);
{ _ram = new byte[0x800];
ROM[i] = rom[i];
}
RAM = new byte[0x800];
} }
public override byte ReadBus(ushort addr) public override byte ReadBus(ushort addr)
@ -23,15 +19,15 @@
var result = 0xFF; var result = 0xFF;
var off = addr - 0x800; var off = addr - 0x800;
if (addr >= 0x2800 && addr < 0x3000) if (addr is >= 0x2800 and < 0x3000)
{ {
// 2KB RAM // 2KB RAM
result = RAM[addr - 0x2800]; result = _ram[addr - 0x2800];
} }
else else
{ {
if (off < ROM.Length) if (off < _rom.Length)
result = ROM[off + (MultiBank * 0x2000) + (MultiHalfBank * 0x1000)]; result = _rom[off + (MultiBank * 0x2000) + (MultiHalfBank * 0x1000)];
} }
return (byte)result; return (byte)result;
@ -40,9 +36,9 @@
public override void WriteBus(ushort addr, byte value) public override void WriteBus(ushort addr, byte value)
{ {
// 2KB writeable memory at 0x2800; // 2KB writeable memory at 0x2800;
if (addr >= 0x2800 && addr < 0x3000) if (addr is >= 0x2800 and < 0x3000)
{ {
RAM[addr - 0x2800] = value; _ram[addr - 0x2800] = value;
} }
else if (addr == 0x3000) else if (addr == 0x3000)
{ {
@ -50,16 +46,10 @@
MultiBank = value & 0x1F; MultiBank = value & 0x1F;
MultiHalfBank = (value & 0x20) >> 5; MultiHalfBank = (value & 0x20) >> 5;
} }
else
{
}
} }
public override byte ReadPort(ushort addr) public override byte ReadPort(ushort addr)
{ => 0xFF;
return 0xFF;
}
public override void WritePort(ushort addr, byte data) public override void WritePort(ushort addr, byte data)
{ {

View File

@ -5,48 +5,42 @@
/// Any size ROM / 2KB RAM mapped at 0x2800 - 0x2FFF /// Any size ROM / 2KB RAM mapped at 0x2800 - 0x2FFF
/// Info here: http://www.seanriddle.com/chanfmulti.html /// Info here: http://www.seanriddle.com/chanfmulti.html
/// </summary> /// </summary>
public class mapper_SCHACH : VesCartBase public class MapperSCHACH : VesCartBase
{ {
public override string BoardType => "SCHACH"; public override string BoardType => "SCHACH";
public override bool HasActivityLED => true; public override bool HasActivityLED => true;
public override string ActivityLEDDescription => "Chess Brain Thinking Activity"; public override string ActivityLEDDescription => "Chess Brain Thinking Activity";
public mapper_SCHACH(byte[] rom) public MapperSCHACH(byte[] rom)
{ {
ROM = new byte[0x10000 - 0x800]; _rom = new byte[0x10000 - 0x800];
for (int i = 0; i < rom.Length; i++) Array.Copy(rom, _rom, rom.Length);
{ _rom.AsSpan(rom.Length).Fill(0xFF);
ROM[i] = rom[i]; _ram = new byte[0x800];
}
RAM = new byte[0x800];
} }
public override byte ReadBus(ushort addr) public override byte ReadBus(ushort addr)
{ {
var result = 0xFF; byte result;
var off = addr - 0x800; if (addr is >= 0x2800 and < 0x3000)
if (addr >= 0x2800 && addr < 0x3000)
{ {
// 2KB RAM // 2KB RAM
result = RAM[addr - 0x2800]; result = _ram[addr - 0x2800];
} }
else else
{ {
if (off < ROM.Length) result = _rom[addr - 0x800];
result = ROM[off];
} }
return (byte)result; return result;
} }
public override void WriteBus(ushort addr, byte value) public override void WriteBus(ushort addr, byte value)
{ {
// 2KB writeable memory at 0x2800; // 2KB writeable memory at 0x2800;
if (addr >= 0x2800 && addr < 0x3000) if (addr is >= 0x2800 and < 0x3000)
{ {
RAM[addr - 0x2800] = value; _ram[addr - 0x2800] = value;
} }
else if (addr == 0x3800) else if (addr == 0x3800)
{ {
@ -59,9 +53,7 @@
} }
public override byte ReadPort(ushort addr) public override byte ReadPort(ushort addr)
{ => 0xFF;
return 0xFF;
}
public override void WritePort(ushort addr, byte data) public override void WritePort(ushort addr, byte data)
{ {

View File

@ -4,28 +4,22 @@
/// Standard ChannelF Cartridge /// Standard ChannelF Cartridge
/// 2KB ROM / NO RAM /// 2KB ROM / NO RAM
/// </summary> /// </summary>
public class mapper_STD : VesCartBase public class MapperSTD : VesCartBase
{ {
public override string BoardType => "STD"; public override string BoardType => "STD";
public mapper_STD(byte[] rom) public MapperSTD(byte[] rom)
{ {
ROM = new byte[0x10000 - 0x800]; _rom = new byte[0x10000 - 0x800];
for (int i = 0; i < rom.Length; i++) Array.Copy(rom, _rom, rom.Length);
{ _rom.AsSpan(rom.Length).Fill(0xFF);
ROM[i] = rom[i]; _ram = [ ];
}
RAM = new byte[0];
} }
public override byte ReadBus(ushort addr) public override byte ReadBus(ushort addr)
{ {
var off = addr - 0x800; var off = addr - 0x800;
if (off < ROM.Length) return _rom[off];
return ROM[off];
else
return 0xFF;
} }
public override void WriteBus(ushort addr, byte value) public override void WriteBus(ushort addr, byte value)
@ -34,9 +28,7 @@
} }
public override byte ReadPort(ushort addr) public override byte ReadPort(ushort addr)
{ => 0xFF;
return 0xFF;
}
public override void WritePort(ushort addr, byte data) public override void WritePort(ushort addr, byte data)
{ {

View File

@ -8,7 +8,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{ {
public abstract class VesCartBase public abstract class VesCartBase
{ {
public abstract string BoardType { get; } public abstract string BoardType { get; }
public virtual void SyncByteArrayDomain(ChannelF sys) public virtual void SyncByteArrayDomain(ChannelF sys)
{ {
@ -19,23 +19,11 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
} }
} }
public virtual byte[] ROM protected byte[] _rom;
{
get { return _rom; }
protected set { _rom = value; }
}
protected byte[] _rom;
public virtual byte[] RAM
{
get { return _ram; }
protected set { _ram = value; }
}
protected byte[] _ram; protected byte[] _ram;
public virtual bool HasActivityLED { get; set; } public virtual bool HasActivityLED { get; set; }
public virtual string ActivityLEDDescription { get; set; } public virtual string ActivityLEDDescription { get; set; }
public bool ActivityLED; public bool ActivityLED;
public int MultiBank; public int MultiBank;
@ -58,38 +46,36 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
public static VesCartBase Configure(GameInfo gi, byte[] rom) public static VesCartBase Configure(GameInfo gi, byte[] rom)
{ {
// get board type // get board type
string boardStr = gi.OptionPresent("board") ? gi.GetStringValue("board") : "STD"; var boardStr = gi.OptionPresent("board") ? gi.GetStringValue("board") : "STD";
switch (boardStr) switch (boardStr)
{ {
// The supplied ROM is actually a BIOS // The supplied ROM is actually a BIOS
case "BIOS": case "BIOS":
// we can just pass the rom into channel f and because it does not detect a 0x55 at rom[0] it will just jump straight to onboard games // we can just pass the rom into channel f and because it does not detect a 0x55 at rom[0] it will just jump straight to onboard games
// (hockey and tennis) // (hockey and tennis)
return new mapper_STD(rom); return new MapperSTD(rom);
// standard cart layout // standard cart layout
case "STD": case "STD":
// any number of F3851 Program Storage Units (1KB ROM each) or F3856 Program Storage Unit (2KB ROM) // any number of F3851 Program Storage Units (1KB ROM each) or F3856 Program Storage Unit (2KB ROM)
// no on-pcb RAM and no extra IO // no on-pcb RAM and no extra IO
return new mapper_STD(rom); return new MapperSTD(rom);
case "MAZE": case "MAZE":
return new mapper_MAZE(rom); return new MapperMAZE(rom);
case "RIDDLE": case "RIDDLE":
// Sean Riddle's modified SCHACH multi-cart // Sean Riddle's modified SCHACH multi-cart
return new mapper_RIDDLE(rom); return new MapperRIDDLE(rom);
case "SCHACH": case "SCHACH":
default: default:
// F3853 Memory Interface Chip, 6KB of ROM and 2KB of RAM // F3853 Memory Interface Chip, 6KB of ROM and 2KB of RAM
// - default to this // - default to this
return new mapper_SCHACH(rom); return new MapperSCHACH(rom);
case "HANG": case "HANG":
return new MapperHANG(rom);
return new mapper_HANG(rom);
} }
} }
@ -106,7 +92,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
if (m_read_write == 0) if (m_read_write == 0)
{ {
m_addr = m_addr_latch; m_addr = m_addr_latch;
m_data0 = RAM[m_addr] & 1; m_data0 = _ram[m_addr] & 1;
return (byte)((m_latch[0] & 0x7f) | (m_data0 << 7)); return (byte)((m_latch[0] & 0x7f) | (m_data0 << 7));
} }
@ -141,8 +127,8 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
if (m_read_write == 1) if (m_read_write == 1)
{ {
RAM[m_addr] = (byte)m_data0; _ram[m_addr] = (byte)m_data0;
} }
} }
else else
{ {
@ -171,7 +157,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
b.CopyTo(resBytes, 0); b.CopyTo(resBytes, 0);
m_addr_latch = (ushort)(resBytes[0] | resBytes[1] << 8); m_addr_latch = (ushort)(resBytes[0] | resBytes[1] << 8);
} }
} }
public virtual void Reset() public virtual void Reset()
{ {
@ -187,7 +173,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
public virtual void SyncState(Serializer ser) public virtual void SyncState(Serializer ser)
{ {
ser.BeginSection("Cart"); ser.BeginSection("Cart");
ser.Sync(nameof(RAM), ref _ram, false); ser.Sync(nameof(_ram), ref _ram, false);
ser.Sync(nameof(m_latch), ref m_latch, false); ser.Sync(nameof(m_latch), ref m_latch, false);
ser.Sync(nameof(m_addr_latch), ref m_addr_latch); ser.Sync(nameof(m_addr_latch), ref m_addr_latch);
ser.Sync(nameof(m_addr), ref m_addr); ser.Sync(nameof(m_addr), ref m_addr);

View File

@ -1,117 +1,116 @@
using System.Collections.Generic; using BizHawk.Emulation.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Consoles.ChannelF namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{ {
public partial class ChannelF public partial class ChannelF
{ {
public ControllerDefinition ChannelFControllerDefinition private static readonly Lazy<ControllerDefinition> _channelFControllerDefinition = new(() =>
{ {
get ControllerDefinition definition = new("ChannelF Controller");
// sticks
const string P1_PREFIX = "P1 ";
string[] stickR =
[
// P1 (right) stick
P1_PREFIX + "Forward", P1_PREFIX + "Back", P1_PREFIX + "Left", P1_PREFIX + "Right", P1_PREFIX + "CCW", P1_PREFIX + "CW", P1_PREFIX + "Pull", P1_PREFIX + "Push"
];
foreach (var s in stickR)
{ {
ControllerDefinition definition = new("ChannelF Controller"); definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "Right Controller";
string pre = "P1 ";
// sticks
var stickR = new List<string>
{
// P1 (right) stick
pre + "Forward", pre + "Back", pre + "Left", pre + "Right", pre + "CCW", pre + "CW", pre + "Pull", pre + "Push"
};
foreach (var s in stickR)
{
definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "Right Controller";
}
pre = "P2 ";
var stickL = new List<string>
{
// P2 (left) stick
pre + "Forward", pre + "Back", pre + "Left", pre + "Right", pre + "CCW", pre + "CW", pre + "Pull", pre + "Push"
};
foreach (var s in stickL)
{
definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "Left Controller";
}
// console
var consoleButtons = new List<string>
{
"TIME", "MODE", "HOLD", "START", "RESET"
};
foreach (var s in consoleButtons)
{
definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "Console";
}
return definition.MakeImmutable();
} }
}
public bool[] StateConsole = new bool[5]; const string P2_PREFIX = "P2 ";
public string[] ButtonsConsole = string[] stickL =
{ [
// P2 (left) stick
P2_PREFIX + "Forward", P2_PREFIX + "Back", P2_PREFIX + "Left", P2_PREFIX + "Right", P2_PREFIX + "CCW", P2_PREFIX + "CW", P2_PREFIX + "Pull", P2_PREFIX + "Push"
];
foreach (var s in stickL)
{
definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "Left Controller";
}
// console
string[] consoleButtons =
[
"TIME", "MODE", "HOLD", "START", "RESET"
];
foreach (var s in consoleButtons)
{
definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "Console";
}
return definition.MakeImmutable();
});
private readonly string[] _buttonsConsole =
[
"TIME", "MODE", "HOLD", "START", "RESET" "TIME", "MODE", "HOLD", "START", "RESET"
}; ];
public byte DataConsole private bool[] _stateConsole = new bool[5];
private byte DataConsole
{ {
get get
{ {
int w = 0; var w = 0;
for (int i = 0; i < 5; i++) for (var i = 0; i < 5; i++)
{ {
byte mask = (byte) (1 << i); var mask = (byte)(1 << i);
w = StateConsole[i] ? w | mask : w & ~mask; w = _stateConsole[i] ? w | mask : w & ~mask;
} }
return (byte)(w & 0xFF); return (byte)(w & 0xFF);
} }
} }
public bool[] StateRight = new bool[8]; private bool[] _stateRight = new bool[8];
public string[] ButtonsRight =
{ private readonly string[] _buttonsRight =
"Right", "Left", "Back", "Forward", "CCW", "CW", "Pull", "Push" [
}; "P1 Right", "P1 Left", "P1 Back", "P1 Forward", "P1 CCW", "P1 CW", "P1 Pull", "P1 Push"
public byte DataRight ];
private byte DataRight
{ {
get get
{ {
int w = 0; var w = 0;
for (int i = 0; i < 8; i++) for (var i = 0; i < 8; i++)
{ {
byte mask = (byte)(1 << i); var mask = (byte)(1 << i);
w = StateRight[i] ? w | mask : w & ~mask; w = _stateRight[i] ? w | mask : w & ~mask;
} }
return (byte)(w & 0xFF); return (byte)(w & 0xFF);
} }
} }
public bool[] StateLeft = new bool[8]; private bool[] _stateLeft = new bool[8];
public string[] ButtonsLeft =
{ private readonly string[] _buttonsLeft =
"Right", "Left", "Back", "Forward", "CCW", "CW", "Pull", "Push" [
}; "P2 Right", "P2 Left", "P2 Back", "P2 Forward", "P2 CCW", "P2 CW", "P2 Pull", "P2 Push"
public byte DataLeft ];
private byte DataLeft
{ {
get get
{ {
int w = 0; var w = 0;
for (int i = 0; i < 8; i++) for (var i = 0; i < 8; i++)
{ {
byte mask = (byte)(1 << i); var mask = (byte)(1 << i);
w = StateLeft[i] ? w | mask : w & ~mask; w = _stateLeft[i] ? w | mask : w & ~mask;
} }
return (byte)(w & 0xFF); return (byte)(w & 0xFF);

View File

@ -0,0 +1,27 @@
using BizHawk.Emulation.Cores.Components.FairchildF8;
namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{
public partial class ChannelF
{
public readonly struct CpuLink(ChannelF channelF) : IF3850Link
{
public byte ReadMemory(ushort address)
=> channelF.ReadBus(address);
public void WriteMemory(ushort address, byte value)
=> channelF.WriteBus(address, value);
public byte ReadHardware(ushort address)
=> channelF.ReadPort(address);
public void WriteHardware(ushort address, byte value)
=> channelF.WritePort(address, value);
public void OnExecFetch(ushort address)
{
// TODO: implement
}
}
}
}

View File

@ -7,10 +7,10 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
public partial class ChannelF : IDebuggable public partial class ChannelF : IDebuggable
{ {
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters() public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
=> CPU.GetCpuFlagsAndRegisters(); => _cpu.GetCpuFlagsAndRegisters();
public void SetCpuRegister(string register, int value) public void SetCpuRegister(string register, int value)
=> CPU.SetCpuRegister(register, value); => _cpu.SetCpuRegister(register, value);
public IMemoryCallbackSystem MemoryCallbacks { get; } public IMemoryCallbackSystem MemoryCallbacks { get; }
@ -19,6 +19,6 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
[FeatureNotImplemented] [FeatureNotImplemented]
public void Step(StepType type) => throw new NotImplementedException(); public void Step(StepType type) => throw new NotImplementedException();
public long TotalExecutedCycles => CPU.TotalExecutedCycles; public long TotalExecutedCycles => _cpu.TotalExecutedCycles;
} }
} }

View File

@ -6,21 +6,20 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{ {
public IEmulatorServiceProvider ServiceProvider { get; } public IEmulatorServiceProvider ServiceProvider { get; }
public ControllerDefinition ControllerDefinition { get; set; } public ControllerDefinition ControllerDefinition { get; }
public string SystemId => VSystemID.Raw.ChannelF; public string SystemId => VSystemID.Raw.ChannelF;
public bool DeterministicEmulation { get; set; } public bool DeterministicEmulation => true;
public int CpuClocksPerFrame; private int _cpuClocksPerFrame;
public int FrameClock; private int _frameClock;
private void CalcClock() private void CalcClock()
{ {
// CPU speeds from https://en.wikipedia.org/wiki/Fairchild_Channel_F // CPU speeds from https://en.wikipedia.org/wiki/Fairchild_Channel_F
// also https://github.com/mamedev/mame/blob/c8192c898ce7f68c0c0b87e44199f0d3e710439b/src/mame/drivers/channelf.cpp // also https://github.com/mamedev/mame/blob/c8192c898ce7f68c0c0b87e44199f0d3e710439b/src/mame/drivers/channelf.cpp
double cpuFreq, pixelClock; double cpuFreq, pixelClock;
int pixelClocksPerFrame;
if (Region == DisplayType.NTSC) if (Region == DisplayType.NTSC)
{ {
HTotal = 256; HTotal = 256;
@ -37,8 +36,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
cpuFreq = NTSC_COLORBURST / 2; cpuFreq = NTSC_COLORBURST / 2;
// NTSC pixel clock is NTSC Colorburst * 8 / 7 // NTSC pixel clock is NTSC Colorburst * 8 / 7
pixelClock = NTSC_COLORBURST * 8 / 7; pixelClock = NTSC_COLORBURST * 8 / 7;
// NTSC refresh rate is (pixelclock * 8 / 7) / (256 * 264) // NTSC refresh rate is (pixelclock * 8 / 7) / (HTotal * VTotal)
pixelClocksPerFrame = 256 * 264;
// (aka (1023750000 * 8) / (256 * 264 * 286 * 7) // (aka (1023750000 * 8) / (256 * 264 * 286 * 7)
// reduced to 234375 / 3872 // reduced to 234375 / 3872
VsyncNumerator = 234375; VsyncNumerator = 234375;
@ -55,19 +53,18 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
ScanlineRepeats = 5; ScanlineRepeats = 5;
PixelWidth = 2; PixelWidth = 2;
if (version == ConsoleVersion.ChannelF) if (_version == ConsoleVersion.ChannelF)
{ {
// PAL CPU speed is 2MHz // PAL CPU speed is 2MHz
cpuFreq = 2000000; cpuFreq = 2000000;
// PAL pixel clock is 4MHz // PAL pixel clock is 4MHz
pixelClock = 4000000; pixelClock = 4000000;
// PAL refresh rate is pixelclock / (256 * 312) // PAL refresh rate is pixelclock / (HTotal * VTotal)
pixelClocksPerFrame = 256 * 312;
// reduced to 15625 / 312 // reduced to 15625 / 312
VsyncNumerator = 15625; VsyncNumerator = 15625;
VsyncDenominator = 312; VsyncDenominator = 312;
} }
else if (version == ConsoleVersion.ChannelF_II) else if (_version == ConsoleVersion.ChannelF_II)
{ {
// PAL CPU speed for gen 2 seems to be contested // PAL CPU speed for gen 2 seems to be contested
// various sources seem to say 1.77MHz (i.e. PAL Colorburst * 2 / 5) // various sources seem to say 1.77MHz (i.e. PAL Colorburst * 2 / 5)
@ -81,8 +78,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
// not entirely sure what the pixel clock for PAL is here // not entirely sure what the pixel clock for PAL is here
// presumingly, it's just cpuFreq * 2? // presumingly, it's just cpuFreq * 2?
pixelClock = PAL_COLORBURST * 8 / 9; pixelClock = PAL_COLORBURST * 8 / 9;
// PAL refresh rate is pixelclock / (256 * 312) // PAL refresh rate is pixelclock / (HTotal * VTotal)
pixelClocksPerFrame = 256 * 312;
// (aka (4433618.75 * 8) / (256 * 312 * 9) // (aka (4433618.75 * 8) / (256 * 312 * 9)
// reduced to 17734475 / 359424 // reduced to 17734475 / 359424
VsyncNumerator = 17734475; VsyncNumerator = 17734475;
@ -94,10 +90,12 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
} }
} }
var c = cpuFreq * pixelClocksPerFrame / pixelClock;
CpuClocksPerFrame = (int) c;
PixelClocksPerCpuClock = pixelClock / cpuFreq; PixelClocksPerCpuClock = pixelClock / cpuFreq;
PixelClocksPerFrame = pixelClocksPerFrame; PixelClocksPerFrame = HTotal * VTotal;
var c = cpuFreq * PixelClocksPerFrame / pixelClock;
// note: this always results in a nice integer, no precision is lost!
_cpuClocksPerFrame = (int)c;
SetupAudio(); SetupAudio();
} }
@ -109,22 +107,22 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
if (_tracer.IsEnabled()) if (_tracer.IsEnabled())
{ {
CPU.TraceCallback = s => _tracer.Put(s); _cpu.TraceCallback = s => _tracer.Put(s);
} }
else else
{ {
CPU.TraceCallback = null; _cpu.TraceCallback = null;
} }
PollInput(); PollInput();
while (FrameClock++ < CpuClocksPerFrame) while (_frameClock++ < _cpuClocksPerFrame)
{ {
CPU.ExecuteOne(); _cpu.ExecuteOne();
ClockVideo(); ClockVideo();
} }
FrameClock = 0; _frameClock = 0;
_frame++; _frame++;
if (_isLag) if (_isLag)
@ -134,10 +132,6 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
} }
private int _frame; private int _frame;
#pragma warning disable CS0414
//private int _lagcount;
//private bool _islag;
#pragma warning restore CS0414
public void ResetCounters() public void ResetCounters()
{ {
@ -154,8 +148,8 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
private void ConsoleReset() private void ConsoleReset()
{ {
CPU.Reset(); _cpu.Reset();
Cartridge.Reset(); _cartridge.Reset();
} }
} }
} }

View File

@ -25,38 +25,6 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None; return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None;
} }
[CoreSettings]
public class ChannelFSyncSettings
{
[DisplayName("Deterministic Emulation")]
[Description("If true, the core agrees to behave in a completely deterministic manner")]
[DefaultValue(true)]
public bool DeterministicEmulation { get; set; }
[DisplayName("Region")]
[Description("NTSC or PAL - Affects the CPU clock speed and refresh rate")]
[DefaultValue(RegionType.NTSC)]
public RegionType Region { get; set; }
[DisplayName("Version")]
[Description("Channel F II has a very slightly different BIOS to Channel F and a slightly slower CPU in the PAL version compared to v1")]
[DefaultValue(ConsoleVersion.ChannelF)]
public ConsoleVersion Version { get; set; }
public ChannelFSyncSettings Clone()
{
return (ChannelFSyncSettings) MemberwiseClone();
}
public ChannelFSyncSettings()
{
SettingsUtil.SetDefaultValues(this);
}
public static bool NeedsReboot(ChannelFSyncSettings x, ChannelFSyncSettings y)
{
return !DeepEquality.DeepEquals(x, y);
}
}
public enum RegionType public enum RegionType
{ {
NTSC, NTSC,
@ -68,5 +36,28 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
ChannelF, ChannelF,
ChannelF_II ChannelF_II
} }
[CoreSettings]
public class ChannelFSyncSettings
{
[DisplayName("Region")]
[Description("NTSC or PAL - Affects the CPU clock speed and refresh rate")]
[DefaultValue(RegionType.NTSC)]
public RegionType Region { get; set; }
[DisplayName("Version")]
[Description("Channel F II has a very slightly different BIOS to Channel F and a slightly slower CPU in the PAL version compared to v1")]
[DefaultValue(ConsoleVersion.ChannelF)]
public ConsoleVersion Version { get; set; }
public ChannelFSyncSettings Clone()
=> (ChannelFSyncSettings)MemberwiseClone();
public ChannelFSyncSettings()
=> SettingsUtil.SetDefaultValues(this);
public static bool NeedsReboot(ChannelFSyncSettings x, ChannelFSyncSettings y)
=> !DeepEquality.DeepEquals(x, y);
}
} }
} }

View File

@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{ {
_samplesPerFrame = (int)(SAMPLE_RATE * VsyncDenominator / VsyncNumerator); _samplesPerFrame = (int)(SAMPLE_RATE * VsyncDenominator / VsyncNumerator);
// TODO: more precise audio clocking // TODO: more precise audio clocking
_cyclesPerSample = CpuClocksPerFrame / (double)_samplesPerFrame; _cyclesPerSample = _cpuClocksPerFrame / (double)_samplesPerFrame;
_sampleBuffer = new short[_samplesPerFrame]; _sampleBuffer = new short[_samplesPerFrame];
_filteredSampleBuffer = new double[_samplesPerFrame]; _filteredSampleBuffer = new double[_samplesPerFrame];
_toneBuffer = new int[_samplesPerFrame]; _toneBuffer = new int[_samplesPerFrame];
@ -37,8 +37,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
private void AudioChange() private void AudioChange()
{ {
var currSample = (int)(FrameClock / _cyclesPerSample); var currSample = (int)(_frameClock / _cyclesPerSample);
while (currSample < _samplesPerFrame) while (currSample < _samplesPerFrame)
{ {
_toneBuffer[currSample++] = _tone; _toneBuffer[currSample++] = _tone;
@ -130,7 +129,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
for (var i = 2; i < samples.Length; i++) for (var i = 2; i < samples.Length; i++)
{ {
filteredSamples[i] = (b0 / a0) * samples[i] + (b1 / a0) * samples[i - 1] + (b2 / a0) * samples[i - 2] filteredSamples[i] = (b0 / a0) * samples[i] + (b1 / a0) * samples[i - 1] + (b2 / a0) * samples[i - 2]
- (a1 / a0) * filteredSamples[i - 1] - (a2 / a0) * filteredSamples[i - 2]; - (a1 / a0) * filteredSamples[i - 1] - (a2 / a0) * filteredSamples[i - 2];
} }
for (var i = 0; i < samples.Length; i++) for (var i = 0; i < samples.Length; i++)
@ -150,9 +149,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
} }
public void GetSamplesAsync(short[] samples) public void GetSamplesAsync(short[] samples)
{ => throw new NotSupportedException("Async is not available");
throw new NotSupportedException("Async is not available");
}
public void DiscardSamples() public void DiscardSamples()
{ {
@ -166,7 +163,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
public void GetSamplesSync(out short[] samples, out int nsamp) public void GetSamplesSync(out short[] samples, out int nsamp)
{ {
// process tone buffer // process tone buffer
for (var t = 0; t < _toneBuffer.Length; t++) for (var t = 0; t < _toneBuffer.Length; t++)
{ {
var tValue = _toneBuffer[t]; var tValue = _toneBuffer[t];
@ -189,7 +186,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
_currTone = tValue; _currTone = tValue;
if (_rampCounter <= 0) if (_rampCounter <= 0)
_sampleBuffer[t] = (short)((GetWaveSample(_samplePosition++, _currTone) * _amplitude) / 30); _sampleBuffer[t] = (short)((GetWaveSample(_samplePosition++, _currTone) * _amplitude) / 30);
} }
} }
else if (_currTone > 0) else if (_currTone > 0)

View File

@ -7,14 +7,14 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
private void SyncState(Serializer ser) private void SyncState(Serializer ser)
{ {
ser.BeginSection("ChannelF"); ser.BeginSection("ChannelF");
ser.Sync(nameof(VRAM), ref VRAM, false); ser.Sync(nameof(_vram), ref _vram, false);
ser.Sync(nameof(_latch_colour), ref _latch_colour); ser.Sync(nameof(_latchColour), ref _latchColour);
ser.Sync(nameof(_latch_x), ref _latch_x); ser.Sync(nameof(_latchX), ref _latchX);
ser.Sync(nameof(_latch_y), ref _latch_y); ser.Sync(nameof(_latchY), ref _latchY);
ser.Sync(nameof(_pixelClockCounter), ref _pixelClockCounter); ser.Sync(nameof(_pixelClockCounter), ref _pixelClockCounter);
ser.Sync(nameof(_pixelClocksRemaining), ref _pixelClocksRemaining); ser.Sync(nameof(_pixelClocksRemaining), ref _pixelClocksRemaining);
ser.Sync(nameof(FrameClock), ref FrameClock); ser.Sync(nameof(_frameClock), ref _frameClock);
ser.Sync(nameof(_frame), ref _frame); ser.Sync(nameof(_frame), ref _frame);
ser.Sync(nameof(_isLag), ref _isLag); ser.Sync(nameof(_isLag), ref _isLag);
ser.Sync(nameof(_lagCount), ref _lagCount); ser.Sync(nameof(_lagCount), ref _lagCount);
@ -30,75 +30,21 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
ser.Sync(nameof(_currTone), ref _currTone); ser.Sync(nameof(_currTone), ref _currTone);
ser.Sync(nameof(_samplePosition), ref _samplePosition); ser.Sync(nameof(_samplePosition), ref _samplePosition);
ser.Sync(nameof(StateConsole), ref StateConsole, false); ser.Sync(nameof(_stateConsole), ref _stateConsole, false);
ser.Sync(nameof(StateRight), ref StateRight, false); ser.Sync(nameof(_stateRight), ref _stateRight, false);
ser.Sync(nameof(StateLeft), ref StateLeft, false); ser.Sync(nameof(_stateLeft), ref _stateLeft, false);
ser.Sync(nameof(OutputLatch), ref OutputLatch, false); ser.Sync(nameof(_outputLatch), ref _outputLatch, false);
ser.Sync(nameof(LS368Enable), ref LS368Enable); ser.Sync(nameof(LS368Enable), ref LS368Enable);
//ser.Sync(nameof(ControllersEnabled), ref ControllersEnabled); _cpu.SyncState(ser);
CPU.SyncState(ser); _cartridge.SyncState(ser);
Cartridge.SyncState(ser);
ser.EndSection(); ser.EndSection();
if (ser.IsReader) if (ser.IsReader)
{ {
SyncAllByteArrayDomains(); SyncAllByteArrayDomains();
} }
/*
byte[] core = null;
if (ser.IsWriter)
{
var ms = new MemoryStream();
ms.Close();
core = ms.ToArray();
}
if (ser.IsWriter)
{
ser.SyncEnum(nameof(_machineType), ref _machineType);
_cpu.SyncState(ser);
ser.BeginSection(nameof(ChannelF));
_machine.SyncState(ser);
ser.Sync("Frame", ref _machine.FrameCount);
ser.Sync("LagCount", ref _lagCount);
ser.Sync("IsLag", ref _isLag);
ser.EndSection();
}
if (ser.IsReader)
{
var tmpM = _machineType;
ser.SyncEnum(nameof(_machineType), ref _machineType);
if (tmpM != _machineType && _machineType.ToString() != "72")
{
string msg = "SAVESTATE FAILED TO LOAD!!\n\n";
msg += "Current Configuration: " + tmpM.ToString();
msg += "\n";
msg += "Saved Configuration: " + _machineType.ToString();
msg += "\n\n";
msg += "If you wish to load this SaveState ensure that you have the correct machine configuration selected, reboot the core, then try again.";
CoreComm.ShowMessage(msg);
_machineType = tmpM;
}
else
{
_cpu.SyncState(ser);
ser.BeginSection(nameof(ChannelF));
_machine.SyncState(ser);
ser.Sync("Frame", ref _machine.FrameCount);
ser.Sync("LagCount", ref _lagCount);
ser.Sync("IsLag", ref _isLag);
ser.EndSection();
SyncAllByteArrayDomains();
}
}
*/
} }
} }
} }

View File

@ -8,41 +8,38 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
/// <summary> /// <summary>
/// 128x64 pixels - 8192x2bits (2 KB) /// 128x64 pixels - 8192x2bits (2 KB)
/// For the purposes of this core we will use 8192 bytes and just mask 0x03 /// For the purposes of this core we will use 8192 bytes and just mask 0x03
/// (Also adding an additional 10 rows to the RAM buffer so that it's more aligned with the actual display)
/// </summary> /// </summary>
public byte[] VRAM = new byte[128 * 64]; private byte[] _vram = new byte[128 * 64];
public static readonly int[] FPalette = public static readonly int[] FPalette =
{ [
//0x101010, 0xFDFDFD, 0x5331FF, 0x5DCC02, 0xF33F4B, 0xE0E0E0, 0xA6FF91, 0xD0CEFF // 0x101010, 0xFDFDFD, 0x5331FF, 0x5DCC02, 0xF33F4B, 0xE0E0E0, 0xA6FF91, 0xD0CEFF
Colors.ARGB(0x10, 0x10, 0x10), // Black Colors.ARGB(0x10, 0x10, 0x10), // Black
Colors.ARGB(0xFD, 0xFD, 0xFD), // White Colors.ARGB(0xFD, 0xFD, 0xFD), // White
Colors.ARGB(0xFF, 0x31, 0x53), // Red Colors.ARGB(0xFF, 0x31, 0x53), // Red
Colors.ARGB(0x02, 0xCC, 0x5D), // Green Colors.ARGB(0x02, 0xCC, 0x5D), // Green
Colors.ARGB(0x4B, 0x3F, 0xF3), // Blue Colors.ARGB(0x4B, 0x3F, 0xF3), // Blue
Colors.ARGB(0xE0, 0xE0, 0xE0), // Gray Colors.ARGB(0xE0, 0xE0, 0xE0), // Gray
Colors.ARGB(0x91, 0xFF, 0xA6), // BGreen Colors.ARGB(0x91, 0xFF, 0xA6), // BGreen
Colors.ARGB(0xCE, 0xD0, 0xFF), // BBlue Colors.ARGB(0xCE, 0xD0, 0xFF), // BBlue
}; ];
public static readonly int[] CMap = public static readonly int[] CMap =
{ [
0, 1, 1, 1, 0, 1, 1, 1,
7, 4, 2, 3, 7, 4, 2, 3,
5, 4, 2, 3, 5, 4, 2, 3,
6, 4, 2, 3, 6, 4, 2, 3,
}; ];
private int _latch_colour = 2; private int _latchColour = 2;
private int _latch_x; private int _latchX;
private int _latch_y; private int _latchY;
private int[] videoBuffer; private int[] _videoBuffer;
private double _pixelClockCounter; private double _pixelClockCounter;
private double _pixelClocksRemaining; private double _pixelClocksRemaining;
private int ScanlineRepeats; private int ScanlineRepeats;
private int PixelWidth; private int PixelWidth;
private int HTotal; private int HTotal;
@ -54,20 +51,18 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
private double PixelClocksPerCpuClock; private double PixelClocksPerCpuClock;
private double PixelClocksPerFrame; private double PixelClocksPerFrame;
public void SetupVideo() private void SetupVideo()
{ => _videoBuffer = new int[HTotal * VTotal];
videoBuffer = new int[HTotal * VTotal];
}
/// <summary> /// <summary>
/// Called after every CPU clock /// Called after every CPU clock
/// </summary> /// </summary>
private void ClockVideo() private void ClockVideo()
{ {
while (_pixelClocksRemaining > 1) while (_pixelClocksRemaining > 1)
{ {
var currScanline = (int)(_pixelClockCounter / HTotal); var currScanline = (int)(_pixelClockCounter / HTotal);
var currPixelInLine = (int)(_pixelClockCounter % HTotal); var currPixelInLine = (int)(_pixelClockCounter - currScanline * HTotal);
var currRowInVram = currScanline / ScanlineRepeats; var currRowInVram = currScanline / ScanlineRepeats;
var currColInVram = currPixelInLine / PixelWidth; var currColInVram = currPixelInLine / PixelWidth;
@ -84,27 +79,30 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
// active display // active display
if (currRowInVram < 64) if (currRowInVram < 64)
{ {
var p1 = (VRAM[(currRowInVram * 0x80) + 125]) & 0x03; var p1 = _vram[(currRowInVram * 0x80) + 125] & 0x03;
var p2 = (VRAM[(currRowInVram * 0x80) + 126]) & 0x03; var p2 = _vram[(currRowInVram * 0x80) + 126] & 0x03;
var pOffset = ((p2 & 0x02) | (p1 >> 1)) << 2; var pOffset = ((p2 & 0x02) | (p1 >> 1)) << 2;
var colourIndex = pOffset + (VRAM[currColInVram | (currRowInVram << 7)] & 0x03); var colourIndex = pOffset + (_vram[currColInVram | (currRowInVram << 7)] & 0x03);
videoBuffer[(currScanline * HTotal) + currPixelInLine] = FPalette[CMap[colourIndex]]; _videoBuffer[(currScanline * HTotal) + currPixelInLine] = FPalette[CMap[colourIndex]];
} }
} }
_pixelClockCounter++; _pixelClockCounter++;
_pixelClocksRemaining -= 1; _pixelClocksRemaining -= 1;
} }
_pixelClocksRemaining += PixelClocksPerCpuClock; _pixelClocksRemaining += PixelClocksPerCpuClock;
_pixelClockCounter %= PixelClocksPerFrame; while (_pixelClockCounter >= PixelClocksPerFrame)
{
_pixelClockCounter -= PixelClocksPerFrame;
}
} }
private int HDisplayable => HBlankOn - HBlankOff; private int HDisplayable => HBlankOn - HBlankOff;
private int VDisplayable => VBlankOn - VBlankOff; private int VDisplayable => VBlankOn - VBlankOff;
private int[] ClampBuffer(int[] buffer, int originalWidth, int originalHeight, int trimLeft, int trimTop, int trimRight, int trimBottom) private static int[] ClampBuffer(int[] buffer, int originalWidth, int originalHeight, int trimLeft, int trimTop, int trimRight, int trimBottom)
{ {
var newWidth = originalWidth - trimLeft - trimRight; var newWidth = originalWidth - trimLeft - trimRight;
var newHeight = originalHeight - trimTop - trimBottom; var newHeight = originalHeight - trimTop - trimBottom;
@ -112,7 +110,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
for (var y = 0; y < newHeight; y++) for (var y = 0; y < newHeight; y++)
{ {
for (int x = 0; x < newWidth; x++) for (var x = 0; x < newWidth; x++)
{ {
var originalIndex = (y + trimTop) * originalWidth + (x + trimLeft); var originalIndex = (y + trimTop) * originalWidth + (x + trimLeft);
var newIndex = y * newWidth + x; var newIndex = y * newWidth + x;
@ -139,40 +137,18 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
public int VsyncDenominator { get; private set; } public int VsyncDenominator { get; private set; }
// https://channelf.se/veswiki/index.php?title=VRAM
// 'The emulator MESS uses a fixed 102x58 resolution starting at (4,4) but the safe area for a real system is about 95x58 pixels'
// 'Note that the pixel aspect is a lot closer to widescreen (16:9) than standard definition (4:3). On a TV from the 70's or 80's pixels are rectangular, standing up. In widescreen mode they are close to perfect squares'
// https://channelf.se/veswiki/index.php?title=Resolution
// 'Even though PAL televisions system has more lines vertically, the Channel F displays about the same as on the original NTSC video system'
//
// Right now we are just trimming based on the HBLANK and VBLANK values (we might need to go further like the other emulators)
// VirtualWidth is being used to force the aspect ratio into 4:3
// On real hardware it looks like this (so we are close): https://www.youtube.com/watch?v=ZvQA9tiEIuQ
public int[] GetVideoBuffer() public int[] GetVideoBuffer()
{ => ClampBuffer(_videoBuffer, HTotal, VTotal, HBlankOff, VBlankOff, HTotal - HBlankOn, VTotal - VBlankOn);
// https://channelf.se/veswiki/index.php?title=VRAM
// 'The emulator MESS uses a fixed 102x58 resolution starting at (4,4) but the safe area for a real system is about 95x58 pixels'
// 'Note that the pixel aspect is a lot closer to widescreen (16:9) than standard definition (4:3). On a TV from the 70's or 80's pixels are rectangular, standing up. In widescreen mode they are close to perfect squares'
// https://channelf.se/veswiki/index.php?title=Resolution
// 'Even though PAL televisions system has more lines vertically, the Channel F displays about the same as on the original NTSC video system'
//
// Right now we are just trimming based on the HBLANK and VBLANK values (we might need to go further like the other emulators)
// VirtualWidth is being used to force the aspect ratio into 4:3
// On real hardware it looks like this (so we are close): https://www.youtube.com/watch?v=ZvQA9tiEIuQ
return ClampBuffer(videoBuffer, HTotal, VTotal, HBlankOff, VBlankOff, HTotal - HBlankOn, VTotal - VBlankOn);
}
public DisplayType Region => region == RegionType.NTSC ? DisplayType.NTSC : DisplayType.PAL; public DisplayType Region => _region == RegionType.NTSC ? DisplayType.NTSC : DisplayType.PAL;
/*
private void BuildFrameFromRAM()
{
for (int r = 0; r < 64; r++)
{
// lines
var p1 = (VRAM[(r * 0x80) + 125]) & 0x03;
var p2 = (VRAM[(r * 0x80) + 126]) & 0x03;
var pOffset = ((p2 & 0x02) | (p1 >> 1)) << 2;
for (int c = 0; c < 128; c++)
{
// columns
var colourIndex = pOffset + (VRAM[c | (r << 7)] & 0x03);
frameBuffer[(r << 7) + c] = CMap[colourIndex];
}
}
}
*/
} }
} }

View File

@ -18,63 +18,48 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem(); public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem();
private int _lagCount = 0; private int _lagCount;
private bool _isLag = false; private bool _isLag;
/// <summary> /// <summary>
/// Cycles through all the input callbacks /// Cycles through all the input callbacks
/// This should be done once per frame /// This should be done once per frame
/// </summary> /// </summary>
public bool PollInput() private void PollInput()
{ {
bool noInput = true; for (var i = 0; i < _buttonsConsole.Length; i++)
for (int i = 0; i < ButtonsConsole.Length; i++)
{ {
var key = ButtonsConsole[i]; var key = _buttonsConsole[i];
bool prevState = StateConsole[i]; // CTRLConsole.Bit(i); var prevState = _stateConsole[i];
bool currState = _controller.IsPressed(key); var currState = _controller.IsPressed(key);
if (currState != prevState) if (currState != prevState)
{ {
StateConsole[i] = currState; _stateConsole[i] = currState;
noInput = false;
if (key == "RESET" && StateConsole[i]) if (key == "RESET" && _stateConsole[i])
{ {
ConsoleReset(); ConsoleReset();
for (int l = 0; l < OutputLatch.Length; l++) for (var l = 0; l < _outputLatch.Length; l++)
{ {
OutputLatch[l] = 0; _outputLatch[l] = 0;
} }
return true;
return;
} }
} }
} }
for (int i = 0; i < ButtonsRight.Length; i++) for (var i = 0; i < _buttonsRight.Length; i++)
{ {
var key = "P1 " + ButtonsRight[i]; var key = _buttonsRight[i];
bool prevState = StateRight[i]; _stateRight[i] = _controller.IsPressed(key);
bool currState = _controller.IsPressed(key);
if (currState != prevState)
{
StateRight[i] = currState;
noInput = false;
}
} }
for (int i = 0; i < ButtonsLeft.Length; i++) for (var i = 0; i < _buttonsLeft.Length; i++)
{ {
var key = "P2 " + ButtonsLeft[i]; var key = _buttonsLeft[i];
bool prevState = StateLeft[i]; _stateLeft[i] = _controller.IsPressed(key);
bool currState = _controller.IsPressed(key);
if (currState != prevState)
{
StateLeft[i] = currState;
noInput = false;
}
} }
return noInput;
} }
} }
} }

View File

@ -19,12 +19,12 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
addr => addr =>
{ {
if (addr is < 0 or > 63) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); if (addr is < 0 or > 63) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range");
return CPU.Regs[addr]; return _cpu.Regs[addr];
}, },
(addr, value) => (addr, value) =>
{ {
if (addr is < 0 or > 63) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); if (addr is < 0 or > 63) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range");
CPU.Regs[addr] = value; _cpu.Regs[addr] = value;
}, 1), }, 1),
new MemoryDomainDelegate("System Bus", 0x10000, MemoryDomain.Endian.Big, new MemoryDomainDelegate("System Bus", 0x10000, MemoryDomain.Endian.Big,
addr => addr =>
@ -49,11 +49,10 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
private void SyncAllByteArrayDomains() private void SyncAllByteArrayDomains()
{ {
SyncByteArrayDomain("BIOS1", BIOS01); SyncByteArrayDomain("BIOS1", _bios01);
SyncByteArrayDomain("BIOS2", BIOS02); SyncByteArrayDomain("BIOS2", _bios02);
Cartridge.SyncByteArrayDomain(this); _cartridge.SyncByteArrayDomain(this);
//SyncByteArrayDomain("ROM", Rom); SyncByteArrayDomain("VRAM", _vram);
SyncByteArrayDomain("VRAM", VRAM);
} }
public void SyncByteArrayDomain(string name, byte[] data) public void SyncByteArrayDomain(string name, byte[] data)

View File

@ -1,7 +1,4 @@
using System.Collections.Generic; using BizHawk.Emulation.Common;
using System.Linq;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components.FairchildF8; using BizHawk.Emulation.Cores.Components.FairchildF8;
namespace BizHawk.Emulation.Cores.Consoles.ChannelF namespace BizHawk.Emulation.Cores.Consoles.ChannelF
@ -15,71 +12,60 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
var ser = new BasicServiceProvider(this); var ser = new BasicServiceProvider(this);
ServiceProvider = ser; ServiceProvider = ser;
CoreComm = lp.Comm; CoreComm = lp.Comm;
_gameInfo = lp.Roms.Select(r => r.Game).ToList(); var gameInfo = lp.Roms[0].Game;
_files = lp.Roms.Select(r => r.RomData).ToList(); var rom = lp.Roms[0].RomData;
_syncSettings = lp.SyncSettings ?? new ChannelFSyncSettings(); _syncSettings = lp.SyncSettings ?? new ChannelFSyncSettings();
region = _syncSettings.Region; _region = _syncSettings.Region;
version = _syncSettings.Version; _version = _syncSettings.Version;
MemoryCallbacks = new MemoryCallbackSystem([ "System Bus" ]); MemoryCallbacks = new MemoryCallbackSystem([ "System Bus" ]);
ControllerDefinition = ChannelFControllerDefinition; ControllerDefinition = _channelFControllerDefinition.Value;
if (version == ConsoleVersion.ChannelF) if (_version == ConsoleVersion.ChannelF)
{ {
BIOS01 = CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("ChannelF", "ChannelF_sl131253")); _bios01 = CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("ChannelF", "ChannelF_sl131253"));
BIOS02 = CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("ChannelF", "ChannelF_sl131254")); _bios02 = CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("ChannelF", "ChannelF_sl131254"));
} }
else else
{ {
BIOS01 = CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("ChannelF", "ChannelF_sl90025")); _bios01 = CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("ChannelF", "ChannelF_sl90025"));
BIOS02 = CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("ChannelF", "ChannelF_sl131254")); _bios02 = CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("ChannelF", "ChannelF_sl131254"));
} }
Cartridge = VesCartBase.Configure(_gameInfo[0], _files[0]); if (_bios01.Length != 1024 || _bios02.Length != 1024)
CPU = new F3850
{ {
ReadMemory = ReadBus, throw new InvalidOperationException("BIOS must be exactly 1024 bytes!");
WriteMemory = WriteBus, }
ReadHardware = ReadPort,
WriteHardware = WritePort,
DummyReadMemory = ReadBus
};
_tracer = new TraceBuffer(CPU.TraceHeader); _cartridge = VesCartBase.Configure(gameInfo, rom);
//var rom = _files.First(); _cpu = new F3850<CpuLink>(new CpuLink(this));
//Array.Copy(rom, 0, Rom, 0, rom.Length); _tracer = new TraceBuffer(_cpu.TraceHeader);
CalcClock(); CalcClock();
SetupVideo(); SetupVideo();
ser.Register<IVideoProvider>(this);
ser.Register<ITraceable>(_tracer); ser.Register<ITraceable>(_tracer);
ser.Register<IDisassemblable>(CPU); ser.Register<IDisassemblable>(_cpu);
ser.Register<ISoundProvider>(this);
ser.Register<IStatable>(new StateSerializer(SyncState)); ser.Register<IStatable>(new StateSerializer(SyncState));
SetupMemoryDomains(); SetupMemoryDomains();
} }
internal CoreComm CoreComm { get; } private CoreComm CoreComm { get; }
public List<GameInfo> _gameInfo; private readonly F3850<CpuLink> _cpu;
private readonly List<byte[]> _files;
public F3850 CPU;
private readonly TraceBuffer _tracer; private readonly TraceBuffer _tracer;
public IController _controller; private IController _controller;
public VesCartBase Cartridge; private readonly VesCartBase _cartridge;
public RegionType region; private readonly RegionType _region;
public ConsoleVersion version; private readonly ConsoleVersion _version;
public bool DriveLightEnabled => Cartridge.HasActivityLED; public bool DriveLightEnabled => _cartridge.HasActivityLED;
public bool DriveLightOn => Cartridge.ActivityLED; public bool DriveLightOn => _cartridge.ActivityLED;
public string DriveLightIconDescription => "Computer thinking activity"; public string DriveLightIconDescription => "Computer thinking activity";
} }

View File

@ -5,8 +5,8 @@
/// </summary> /// </summary>
public partial class ChannelF public partial class ChannelF
{ {
public byte[] BIOS01 = new byte[1024]; private readonly byte[] _bios01;
public byte[] BIOS02 = new byte[1024]; private readonly byte[] _bios02;
/// <summary> /// <summary>
/// Simulates reading a byte of data from the address space /// Simulates reading a byte of data from the address space
@ -16,17 +16,17 @@
if (addr < 0x400) if (addr < 0x400)
{ {
// BIOS ROM 1 // BIOS ROM 1
return BIOS01[addr]; return _bios01[addr];
} }
else if (addr < 0x800) else if (addr < 0x800)
{ {
// BIOS ROM 2 // BIOS ROM 2
return BIOS02[addr - 0x400]; return _bios02[addr - 0x400];
} }
else else
{ {
// Cartridge Memory Space // Cartridge Memory Space
return Cartridge.ReadBus(addr); return _cartridge.ReadBus(addr);
} }
} }
@ -35,8 +35,6 @@
/// Channel F addressable through the address space) /// Channel F addressable through the address space)
/// </summary> /// </summary>
public void WriteBus(ushort addr, byte value) public void WriteBus(ushort addr, byte value)
{ => _cartridge.WriteBus(addr, value);
Cartridge.WriteBus(addr, value);
}
} }
} }

View File

@ -18,27 +18,26 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
/// Depending on the attached cartridge, there may be additional hardware on the IO bus /// Depending on the attached cartridge, there may be additional hardware on the IO bus
/// All CPU and PSU I/O ports are active-low with output-latches /// All CPU and PSU I/O ports are active-low with output-latches
/// </summary> /// </summary>
public byte[] OutputLatch = new byte[0xFF]; private byte[] _outputLatch = new byte[0xFF];
public bool LS368Enable; private bool LS368Enable;
/// <summary> /// <summary>
/// CPU is attempting to read from a port /// CPU is attempting to read from a port
/// </summary> /// </summary>
public byte ReadPort(ushort addr) private byte ReadPort(ushort addr)
{ {
var result = 0xFF; int result;
switch (addr) switch (addr)
{ {
case 0: case 0:
// Console Buttons - these are connected to pins 0-3 (bits 0-3) through a 7404 Hex Inverter // Console Buttons - these are connected to pins 0-3 (bits 0-3) through a 7404 Hex Inverter
// b0: TIME // b0: TIME
// b1: MODE // b1: MODE
// b2: HOLD // b2: HOLD
// b3: START // b3: START
// RESET button is connected directly to the RST pin on the CPU (this is handled here in the PollInput() method) // RESET button is connected directly to the RST pin on the CPU (this is handled here in the PollInput() method)
result = (~DataConsole & 0x0F) | OutputLatch[addr]; result = (~DataConsole & 0x0F) | _outputLatch[addr];
InputCallbacks.Call(); InputCallbacks.Call();
_isLag = false; _isLag = false;
break; break;
@ -46,14 +45,14 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
case 1: case 1:
// right controller (player 1) // right controller (player 1)
// connected through 7404 Hex Inverter // connected through 7404 Hex Inverter
// b0: RIGHT // b0: RIGHT
// b1: LEFT // b1: LEFT
// b2: BACK // b2: BACK
// b3: FORWARD // b3: FORWARD
// b4: CCW // b4: CCW
// b5: CW // b5: CW
var v1 = LS368Enable ? DataRight : DataRight | 0xC0; var v1 = LS368Enable ? DataRight : DataRight | 0xC0;
result = (~v1) | OutputLatch[addr]; result = ~v1 | _outputLatch[addr];
InputCallbacks.Call(); InputCallbacks.Call();
_isLag = false; _isLag = false;
break; break;
@ -62,30 +61,31 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
// left controller (player 2) // left controller (player 2)
// connected through LS368 Hex Interting 3-State Buffer // connected through LS368 Hex Interting 3-State Buffer
// the enable pin of this IC is driven by a CPU write to pin 6 on port 0 // the enable pin of this IC is driven by a CPU write to pin 6 on port 0
// b0: RIGHT // b0: RIGHT
// b1: LEFT // b1: LEFT
// b2: BACK // b2: BACK
// b3: FORWARD // b3: FORWARD
// b4: CCW // b4: CCW
// b5: CW // b5: CW
// b6: PULL // b6: PULL
// b7: PUSH // b7: PUSH
var v2 = LS368Enable ? DataLeft : 0xFF; var v2 = LS368Enable ? DataLeft : 0xFF;
result = (~v2) | OutputLatch[addr]; result = ~v2 | _outputLatch[addr];
if (LS368Enable) if (LS368Enable)
{ {
InputCallbacks.Call(); InputCallbacks.Call();
_isLag = false; _isLag = false;
} }
break; break;
case 5: case 5:
result = OutputLatch[addr]; result = _outputLatch[addr];
break; break;
default: default:
// possible cartridge hardware IO space // possible cartridge hardware IO space
result = (Cartridge.ReadPort(addr)); result = _cartridge.ReadPort(addr);
break; break;
} }
@ -95,37 +95,37 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
/// <summary> /// <summary>
/// CPU is attempting to write to the specified IO port /// CPU is attempting to write to the specified IO port
/// </summary> /// </summary>
public void WritePort(ushort addr, byte value) private void WritePort(ushort addr, byte value)
{ {
switch (addr) switch (addr)
{ {
case 0: case 0:
OutputLatch[addr] = value; _outputLatch[addr] = value;
LS368Enable = !value.Bit(6); LS368Enable = !value.Bit(6);
if (value.Bit(5)) if (value.Bit(5))
{ {
// WRT pulse // WRT pulse
// pulse clocks the 74195 parallel access shift register which feeds inputs of 2 NAND gates // pulse clocks the 74195 parallel access shift register which feeds inputs of 2 NAND gates
// writing data to both sets of even and odd VRAM chips (based on the row and column addresses latched into the 7493 ICs) // writing data to both sets of even and odd VRAM chips (based on the row and column addresses latched into the 7493 ICs)
VRAM[((_latch_y) * 0x80) + _latch_x] = (byte)_latch_colour; _vram[_latchY * 0x80 + _latchX] = (byte)_latchColour;
} }
break; break;
case 1: case 1:
OutputLatch[addr] = value; _outputLatch[addr] = value;
_latch_colour = ((value ^ 0xFF) >> 6) & 0x03; _latchColour = ((value ^ 0xFF) >> 6) & 0x03;
break; break;
case 4: case 4:
OutputLatch[addr] = value; _outputLatch[addr] = value;
_latch_x = (value | 0x80) ^ 0xFF; _latchX = (value | 0x80) ^ 0xFF;
break; break;
case 5: case 5:
OutputLatch[addr] = value; _outputLatch[addr] = value;
_latch_y = (value | 0xC0) ^ 0xFF; _latchY = (value | 0xC0) ^ 0xFF;
var audio = ((value) >> 6) & 0x03; var audio = (value >> 6) & 0x03;
if (audio != _tone) if (audio != _tone)
{ {
_tone = audio; _tone = audio;
@ -136,7 +136,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
default: default:
// possible write to cartridge hardware // possible write to cartridge hardware
Cartridge.WritePort(addr, value); _cartridge.WritePort(addr, value);
break; break;
} }
} }