EightBitNet/MC6809/MC6809.Test/Board.cs
Adrian Conlon cd4af67177 Work my way through a bunch of the analysis suggestions.
Signed-off-by: Adrian Conlon <adrian.conlon@gmail.com>
2020-06-22 00:00:15 +01:00

227 lines
7.4 KiB
C#

// <copyright file="Board.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
namespace EightBit
{
using System;
public sealed class Board : Bus
{
private readonly Configuration configuration;
private readonly Ram ram = new Ram(0x8000); // 0000 - 7FFF, 32K RAM
private readonly UnusedMemory unused2000 = new UnusedMemory(0x2000, 0xff); // 8000 - 9FFF, 8K unused
private readonly Ram io = new Ram(0x2000); // A000 - BFFF, 8K serial interface, minimally decoded
private readonly Rom rom = new Rom(0x4000); // C000 - FFFF, 16K ROM
private readonly Disassembler disassembler;
private readonly Profiler profiler;
private ulong totalCycleCount = 0UL;
private long frameCycleCount = 0L;
// The m_disassembleAt and m_ignoreDisassembly are used to skip pin events
private ushort disassembleAt = 0x0000;
private bool ignoreDisassembly = false;
public Board(Configuration configuration)
{
this.configuration = configuration;
this.CPU = new MC6809(this);
this.disassembler = new Disassembler(this, this.CPU);
this.profiler = new Profiler(this, this.CPU, this.disassembler);
}
public MC6809 CPU { get; }
public MC6850 ACIA { get; } = new MC6850();
public override MemoryMapping Mapping(ushort absolute)
{
if (absolute < 0x8000)
{
return new MemoryMapping(this.ram, 0x0000, Mask.Sixteen, AccessLevel.ReadWrite);
}
if (absolute < 0xa000)
{
return new MemoryMapping(this.unused2000, 0x8000, Mask.Sixteen, AccessLevel.ReadOnly);
}
if (absolute < 0xc000)
{
return new MemoryMapping(this.io, 0xa000, Mask.Sixteen, AccessLevel.ReadWrite);
}
return new MemoryMapping(this.rom, 0xc000, Mask.Sixteen, AccessLevel.ReadOnly);
}
public override void RaisePOWER()
{
base.RaisePOWER();
// Get the CPU ready for action
this.CPU.RaisePOWER();
this.CPU.LowerRESET();
this.CPU.RaiseINT();
this.CPU.RaiseNMI();
this.CPU.RaiseFIRQ();
this.CPU.RaiseHALT();
// Get the ACIA ready for action
this.Address.Word = 0b1010000000000000;
this.Data = (byte)(MC6850.ControlRegister.CR0 | MC6850.ControlRegister.CR1); // Master reset
this.ACIA.CTS.Lower();
this.ACIA.RW.Lower();
this.UpdateAciaPins();
this.ACIA.RaisePOWER();
this.AccessAcia();
}
public override void LowerPOWER()
{
////this.profiler.Generate();
this.ACIA.LowerPOWER();
this.CPU.LowerPOWER();
base.LowerPOWER();
}
public override void Initialize()
{
System.Console.TreatControlCAsInput = true;
// Load our BASIC interpreter
var directory = this.configuration.RomDirectory + "\\";
this.LoadHexFile(directory + "ExBasROM.hex");
// Catch a byte being transmitted
this.ACIA.Transmitting += this.ACIA_Transmitting;
// Keyboard wiring, check for input once per frame
this.CPU.ExecutedInstruction += this.CPU_ExecutedInstruction;
if (this.configuration.DebugMode)
{
// MC6809 disassembly wiring
this.CPU.ExecutingInstruction += this.CPU_ExecutingInstruction;
this.CPU.ExecutedInstruction += this.CPU_ExecutedInstruction_Debug;
}
if (this.configuration.TerminatesEarly)
{
// Early termination condition for CPU timing code
this.CPU.ExecutedInstruction += this.CPU_ExecutedInstruction_Termination;
}
////this.profiler.Enable();
////this.profiler.EmitLine += this.Profiler_EmitLine;
}
// Marshal data from ACIA -> memory
protected override void OnReadingByte()
{
this.UpdateAciaPins();
this.ACIA.RW.Raise();
if (this.AccessAcia())
{
this.Poke(this.ACIA.DATA);
}
base.OnReadingByte();
}
// Marshal data from memory -> ACIA
protected override void OnWrittenByte()
{
base.OnWrittenByte();
this.UpdateAciaPins();
if (this.ACIA.Selected)
{
this.ACIA.RW.Lower();
this.AccessAcia();
}
}
private void Profiler_EmitLine(object sender, ProfileLineEventArgs e)
{
var cycles = e.Cycles;
var disassembled = e.Source;
System.Console.Error.WriteLine(disassembled);
}
private void CPU_ExecutedInstruction_Termination(object sender, EventArgs e)
{
this.totalCycleCount += (ulong)this.CPU.Cycles;
if (this.totalCycleCount > Configuration.TerminationCycles)
{
this.LowerPOWER();
}
}
private void CPU_ExecutedInstruction_Debug(object sender, EventArgs e)
{
if (!this.ignoreDisassembly)
{
var disassembled = $"{this.disassembler.Trace(this.disassembleAt)}\t{this.ACIA.DumpStatus()}";
System.Console.Error.WriteLine(disassembled);
}
}
private void CPU_ExecutingInstruction(object sender, EventArgs e)
{
this.disassembleAt = this.CPU.PC.Word;
this.ignoreDisassembly = this.disassembler.Ignore || this.disassembler.Pause;
}
private void CPU_ExecutedInstruction(object sender, EventArgs e)
{
this.frameCycleCount -= this.CPU.Cycles;
if (this.frameCycleCount < 0)
{
if (System.Console.KeyAvailable)
{
var key = System.Console.ReadKey(true);
if (key.Key == ConsoleKey.F12)
{
this.LowerPOWER();
}
this.ACIA.RDR = System.Convert.ToByte(key.KeyChar);
this.ACIA.MarkReceiveStarting();
}
this.frameCycleCount = (long)Configuration.FrameCycleInterval;
}
}
private void ACIA_Transmitting(object sender, EventArgs e)
{
System.Console.Out.Write(Convert.ToChar(this.ACIA.TDR));
this.ACIA.MarkTransmitComplete();
}
private void UpdateAciaPins()
{
this.ACIA.DATA = this.Data;
this.ACIA.RS.Match(this.Address.Word & (ushort)Bits.Bit0);
this.ACIA.CS0.Match(this.Address.Word & (ushort)Bits.Bit15);
this.ACIA.CS1.Match(this.Address.Word & (ushort)Bits.Bit13);
this.ACIA.CS2.Match(this.Address.Word & (ushort)Bits.Bit14);
}
private bool AccessAcia()
{
this.ACIA.E.Raise();
try
{
this.ACIA.Tick();
return this.ACIA.Activated;
}
finally
{
this.ACIA.E.Lower();
}
}
}
}