mirror of
https://github.com/MoleskiCoder/EightBitNet.git
synced 2024-09-23 19:54:47 +00:00
239 lines
7.4 KiB
C#
239 lines
7.4 KiB
C#
|
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 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);
|
|||
|
}
|
|||
|
|
|||
|
public MC6809 CPU { get; }
|
|||
|
public MC6850 ACIA { get; } = new MC6850();
|
|||
|
|
|||
|
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.ACIA.DATA = (byte)(MC6850.ControlRegister.CR0 | MC6850.ControlRegister.CR1); // Master reset
|
|||
|
this.UpdateAciaPinsWrite();
|
|||
|
this.ACIA.CTS.Lower();
|
|||
|
this.ACIA.RaisePOWER();
|
|||
|
this.AccessAcia();
|
|||
|
}
|
|||
|
|
|||
|
public override void LowerPOWER()
|
|||
|
{
|
|||
|
this.ACIA.LowerPOWER();
|
|||
|
this.CPU.LowerPOWER();
|
|||
|
base.LowerPOWER();
|
|||
|
}
|
|||
|
|
|||
|
public override void Initialize()
|
|||
|
{
|
|||
|
// 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;
|
|||
|
|
|||
|
// Marshal data from memory -> ACIA
|
|||
|
this.WrittenByte += this.Board_WrittenByte;
|
|||
|
|
|||
|
// Marshal data from ACIA -> memory
|
|||
|
this.ReadingByte += this.Board_ReadingByte;
|
|||
|
|
|||
|
// 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;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
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.Out.WriteLine(disassembled);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void CPU_ExecutingInstruction(object sender, EventArgs e)
|
|||
|
{
|
|||
|
this.disassembleAt = this.CPU.PC.Word;
|
|||
|
this.ignoreDisassembly = this.disassembler.Ignore;
|
|||
|
}
|
|||
|
|
|||
|
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();
|
|||
|
this.ACIA.RDR = System.Convert.ToByte(key.KeyChar);
|
|||
|
this.ACIA.MarkReceiveStarting();
|
|||
|
}
|
|||
|
|
|||
|
this.frameCycleCount = (long)Configuration.FrameCycleInterval;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void Board_ReadingByte(object sender, EventArgs e)
|
|||
|
{
|
|||
|
this.UpdateAciaPinsRead();
|
|||
|
if (this.AccessAcia())
|
|||
|
{
|
|||
|
this.Poke(this.ACIA.DATA);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void Board_WrittenByte(object sender, EventArgs e)
|
|||
|
{
|
|||
|
this.UpdateAciaPinsWrite();
|
|||
|
if (this.ACIA.Selected)
|
|||
|
{
|
|||
|
this.ACIA.DATA = this.Data;
|
|||
|
this.AccessAcia();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void ACIA_Transmitting(object sender, EventArgs e)
|
|||
|
{
|
|||
|
System.Console.Out.Write(Convert.ToChar(this.ACIA.TDR));
|
|||
|
this.ACIA.MarkTransmitComplete();
|
|||
|
}
|
|||
|
|
|||
|
public override MemoryMapping Mapping(ushort absolute)
|
|||
|
{
|
|||
|
if (absolute < 0x8000)
|
|||
|
{
|
|||
|
return new MemoryMapping(this.ram, 0x0000, Mask.Mask16, AccessLevel.ReadWrite);
|
|||
|
}
|
|||
|
|
|||
|
if (absolute < 0xa000)
|
|||
|
{
|
|||
|
return new MemoryMapping(this.unused2000, 0x8000, Mask.Mask16, AccessLevel.ReadOnly);
|
|||
|
}
|
|||
|
|
|||
|
if (absolute < 0xc000)
|
|||
|
{
|
|||
|
return new MemoryMapping(this.io, 0xa000, Mask.Mask16, AccessLevel.ReadWrite);
|
|||
|
}
|
|||
|
|
|||
|
return new MemoryMapping(this.rom, 0xc000, Mask.Mask16, AccessLevel.ReadOnly);
|
|||
|
}
|
|||
|
|
|||
|
// Use the bus data to update the ACIA access/address pins
|
|||
|
private void UpdateAciaPinsRead()
|
|||
|
{
|
|||
|
this.ACIA.RW.Raise();
|
|||
|
this.UpdateAciaPins();
|
|||
|
}
|
|||
|
|
|||
|
private void UpdateAciaPinsWrite()
|
|||
|
{
|
|||
|
this.ACIA.RW.Lower();
|
|||
|
this.UpdateAciaPins();
|
|||
|
}
|
|||
|
|
|||
|
private void UpdateAciaPins()
|
|||
|
{
|
|||
|
this.ACIA.DATA = this.Data;
|
|||
|
|
|||
|
if ((this.Address.Word & (ushort)Bits.Bit0) != 0)
|
|||
|
{
|
|||
|
this.ACIA.RS.Raise();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
this.ACIA.RS.Lower();
|
|||
|
}
|
|||
|
|
|||
|
if ((this.Address.Word & (ushort)Bits.Bit15) != 0)
|
|||
|
{
|
|||
|
this.ACIA.CS0.Raise();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
this.ACIA.CS0.Lower();
|
|||
|
}
|
|||
|
|
|||
|
if ((this.Address.Word & (ushort)Bits.Bit13) != 0)
|
|||
|
{
|
|||
|
this.ACIA.CS1.Raise();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
this.ACIA.CS1.Lower();
|
|||
|
}
|
|||
|
|
|||
|
if ((this.Address.Word & (ushort)Bits.Bit14) != 0)
|
|||
|
{
|
|||
|
this.ACIA.CS2.Raise();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
this.ACIA.CS2.Lower();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private bool AccessAcia()
|
|||
|
{
|
|||
|
this.ACIA.E.Raise();
|
|||
|
this.ACIA.Tick();
|
|||
|
this.ACIA.E.Lower();
|
|||
|
return this.ACIA.Activated;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|