1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-01-11 23:52:43 +00:00

454 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using Contralto.Logging;
using System;
namespace Contralto.CPU
{
public partial class AltoCPU
{
/// <summary>
/// EmulatorTask provides emulator (NOVA instruction set) specific operations.
/// </summary>
private sealed class EmulatorTask : Task
{
public EmulatorTask(AltoCPU cpu) : base(cpu)
{
_taskType = TaskType.Emulator;
// The Wakeup signal is always true for the Emulator task.
_wakeup = true;
}
public override void Reset()
{
base.Reset();
_wakeup = true;
}
public override void BlockTask()
{
throw new InvalidOperationException("The emulator task cannot be blocked.");
}
public override void WakeupTask()
{
throw new InvalidOperationException("The emulator task is always in wakeup state.");
}
protected override ushort GetBusSource(int bs)
{
EmulatorBusSource ebs = (EmulatorBusSource)bs;
switch (ebs)
{
case EmulatorBusSource.ReadSLocation:
if (_srSelect != 0)
{
return _cpu._s[_rb][_srSelect];
}
else
{
// "...when reading data from the S registers onto the processor bus,
// the RSELECT value 0 causes the current value of the M register to
// appear on the bus..."
return _cpu._m;
}
case EmulatorBusSource.LoadSLocation:
// "When an S register is being loaded from M, the processor bus receives an
// undefined value rather than being set to zero."
_loadS = true;
return 0xffff; // Technically this is an "undefined value," we're defining it as -1.
default:
throw new InvalidOperationException(String.Format("Unhandled bus source {0}", bs));
}
}
protected override void ExecuteSpecialFunction1Early(MicroInstruction instruction)
{
EmulatorF1 ef1 = (EmulatorF1)instruction.F1;
switch (ef1)
{
case EmulatorF1.RSNF:
//
// Early:
// "...decoded by the Ethernet interface, which gates the host address wired on the
// backplane onto BUS[8-15]. BUS[0-7] is not driven and will therefore be -1. If
// no Ethernet interface is present, BUS will be -1.
//
_busData &= (ushort)((0xff00 | _cpu._system.EthernetController.Address));
break;
}
}
protected override void ExecuteSpecialFunction1(MicroInstruction instruction)
{
EmulatorF1 ef1 = (EmulatorF1)instruction.F1;
switch (ef1)
{
case EmulatorF1.LoadRMR:
//
// "The emulator F1 RMR<- causes the reset mode register to be loaded from the processor bus. The 16 bits of the
// processor bus correspond to the 16 Alto tasks in the following way: the low order bit of the processor
// bus specifies the initial mode of task 0, the lowest priority task (emulator), and the high-order bit of the
// bus specifies the initial mode of task 15, the highest priority task(recall that task i starts at location i; the
// reset mode register determines only which microinstruction bank will be used at the outset). A task will
// commence in ROM0 if its associated bit in the reset mode register contains the value 1; otherwise it will
// start in RAM0.Upon initial power - up of the Alto, and after each reset operation, the reset mode register
// is automatically set to all ones, corresponding to starting all tasks in ROM0."
//
_cpu._rmr = _busData;
break;
case EmulatorF1.RSNF:
// Handled in the Early handler.
break;
case EmulatorF1.STARTF:
// Dispatch function to Ethernet I/O based on contents of AC0.
if ((_busData & 0x8000) != 0)
{
//
// BOOT (soft-reset) operation.
// Reset the CPU using the current RMR (start tasks in RAM or ROM as specified.)
_cpu.SoftReset();
// Since this is a soft reset, we don't want MPC to be taken from the NEXT
// field at the end of the cycle, setting this flag causes the main Task
// implementation to skip updating _mpc at the end of this instruction.
_softReset = true;
}
else if(_busData != 0)
{
//
// Dispatch to the appropriate device.
// The Ethernet controller is the only common device that is documented
// to have used STARTF, so we'll just go there directly; if other
// hardware is determined to be worth emulating we'll put together a more flexible dispatch.
//
if (_busData < 4)
{
_cpu._system.EthernetController.STARTF(_busData);
}
else
{
Log.Write(Logging.LogType.Warning, Logging.LogComponent.EmulatorTask, "STARTF for non-Ethernet device (code {0})",
Conversion.ToOctal(_busData));
}
}
break;
case EmulatorF1.SWMODE:
_swMode = true;
break;
case EmulatorF1.RDRAM:
_rdRam = true;
break;
case EmulatorF1.WRTRAM:
_wrtRam = true;
break;
case EmulatorF1.LoadESRB:
_rb = (ushort)((_busData & 0xe) >> 1);
if (_rb != 0 && _systemType != SystemType.ThreeKRam)
{
// Force bank 0 for machines with only 1K RAM.
_rb = 0;
}
break;
default:
throw new InvalidOperationException(String.Format("Unhandled emulator F1 {0}.", ef1));
}
}
protected override void ExecuteSpecialFunction2Early(MicroInstruction instruction)
{
EmulatorF2 ef2 = (EmulatorF2)instruction.F2;
switch (ef2)
{
case EmulatorF2.ACSOURCE:
// Early: modify R select field:
// "...it replaces the two-low order bits of the R select field with
// the complement of the SrcAC field of IR, (IR[1-2] XOR 3), allowing the emulator
// to address its accumulators (which are assigned to R0-R3)."
_rSelect = (_rSelect & 0xfffc) | ((((uint)_cpu._ir & 0x6000) >> 13) ^ 3);
break;
case EmulatorF2.ACDEST:
// "...causes (IR[3-4] XOR 3) to be used as the low-order two bits of the RSELECT field.
// This address the accumulators from the destination field of the instruction. The selected
// register may be loaded or read."
case EmulatorF2.LoadDNS:
//
// "...DNS also addresses R from (3-IR[3 - 4])..."
//
_rSelect = (_rSelect & 0xfffc) | ((((uint)_cpu._ir & 0x1800) >> 11) ^ 3);
break;
}
}
protected override void ExecuteSpecialFunction2(MicroInstruction instruction)
{
EmulatorF2 ef2 = (EmulatorF2)instruction.F2;
switch (ef2)
{
case EmulatorF2.LoadIR:
// Load IR from the bus
_cpu._ir = _busData;
// "IR<- also merges bus bits 0, 5, 6 and 7 into NEXT[6-9] which does a first level
// instruction dispatch."
_nextModifier = (ushort)(((_busData & 0x8000) >> 12) | ((_busData & 0x0700) >> 8));
// "IR<- clears SKIP"
_skip = 0;
break;
case EmulatorF2.IDISP:
// "The IDISP function (F2=15B) does a 16 way dispatch under control of a PROM and a
// multiplexer. The values are tabulated below:
// Conditions ORed onto NEXT Comment
//
// if IR[0] = 1 3-IR[8-9] complement of SH field of IR
// elseif IR[1-2] = 0 IR[3-4] JMP, JSR, ISZ, DSZ ; dispatch selects register
// elseif IR[1-2] = 1 4 LDA
// elseif IR[1-2] = 2 5 STA
// elseif IR[4-7] = 0 1
// elseif IR[4-7] = 1 0
// elseif IR[4-7] = 6 16B CONVERT
// elseif IR[4-7] = 16B 6
// else IR[4-7]
// NB: as always, Xerox labels bits in the opposite order from modern convention;
// (bit 0 is the msb...)
//
// NOTE: The above table is accurate and functions correctly; using the PROM is faster.
//
if ((_cpu._ir & 0x8000) != 0)
{
_nextModifier = (ushort)(3 - ((_cpu._ir & 0xc0) >> 6));
}
else
{
_nextModifier = ControlROM.ACSourceROM[((_cpu._ir & 0x7f00) >> 8) + 0x80];
}
break;
case EmulatorF2.ACSOURCE:
// Late:
// "...a dispatch is performed:
// Conditions ORed onto NEXT Comment
//
// if IR[0] = 1 3-IR[8-9] complement of SH field of IR
// if IR[1-2] = 3 IR[5] the Indirect bit of R
// if IR[3-7] = 0 2 CYCLE
// if IR[3-7] = 1 5 RAMTRAP
// if IR[3-7] = 2 3 NOPAR -- parameterless opcode group
// if IR[3-7] = 3 6 RAMTRAP
// if IR[3-7] = 4 7 RAMTRAP
// if IR[3-7] = 11B 4 JSRII
// if IR[3-7] = 12B 4 JSRIS
// if IR[3-7] = 16B 1 CONVERT
// if IR[3-7] = 37B 17B ROMTRAP -- used by Swat, the debugger
// else 16B ROMTRAP
//
// NOTE: the above table from the Hardware Manual is incorrect
// (or at least incomplete / out of date / misleading).
// There is considerably more that goes into determining the dispatch, which is controlled by a 256x8
// PROM. We just use the PROM rather than implementing the above logic (because it works.)
//
if ((_cpu._ir & 0x8000) != 0)
{
// 3-IR[8-9] (shift field of arithmetic instruction)
_nextModifier = (ushort)(3 - ((_cpu._ir & 0xc0) >> 6));
}
else
{
// Use the PROM.
_nextModifier = ControlROM.ACSourceROM[((_cpu._ir & 0x7f00) >> 8)];
}
break;
case EmulatorF2.ACDEST:
// Handled in early handler, nothing to do here.
break;
case EmulatorF2.BUSODD:
// "...merges BUS[15] into NEXT[9]."
_nextModifier |= (ushort)(_busData & 0x1);
break;
case EmulatorF2.MAGIC:
Shifter.SetMagic(true);
break;
case EmulatorF2.LoadDNS:
// DNS<- does the following:
// - modifies the normal shift operations to perform Nova-style shifts (done here)
// - addresses R from 3-IR[3-4] (destination AC) (see Early LoadDNS handler)
// - stores into R unless IR[12] is set (done here)
// - calculates Nova-style CARRY bit (done here)
// - sets the SKIP and CARRY flip-flops appropriately (see Late LoadDNS handler)
int carry = 0;
// Also indicates modifying CARRY
_loadR = (_cpu._ir & 0x0008) == 0;
// At this point the ALU has already done its operation but the shifter has not yet run.
// We need to set the CARRY bit that will be passed through the shifter appropriately.
// Select carry input value based on carry control
switch(_cpu._ir & 0x30)
{
case 0x00:
// Nothing; CARRY unaffected.
carry = _carry;
break;
case 0x10:
carry = 0; // Z
break;
case 0x20:
carry = 1; // O
break;
case 0x30:
carry = (~_carry) & 0x1; // C
break;
}
// Now modify the result based on the current ALU result
switch (_cpu._ir & 0x700)
{
case 0x000:
case 0x200:
case 0x700:
// COM, MOV, AND - Carry unaffected
break;
case 0x100:
case 0x300:
case 0x400:
case 0x500:
case 0x600:
// NEG, INC, ADC, SUB, ADD - invert the carry bit
if (_cpu._aluC0 != 0)
{
carry = (~carry) & 0x1;
}
break;
}
// Tell the Shifter to do a Nova-style shift with the
// given carry bit.
Shifter.SetDNS(true, carry);
break;
default:
throw new InvalidOperationException(String.Format("Unhandled emulator F2 {0}.", ef2));
}
}
protected override void ExecuteSpecialFunction2Late(MicroInstruction instruction)
{
EmulatorF2 ef2 = (EmulatorF2)instruction.F2;
switch (ef2)
{
case EmulatorF2.LoadDNS:
//
// Set SKIP and CARRY flip-flops based on the final result of the operation after having
// passed through the shifter.
//
ushort result = Shifter.Output;
int carry = Shifter.DNSCarry;
switch (_cpu._ir & 0x7)
{
case 0:
// None, SKIP is reset
_skip = 0;
break;
case 1: // SKP
// Always skip
_skip = 1;
break;
case 2: // SZC
// Skip if carry result is zero
_skip = (carry == 0) ? 1 : 0;
break;
case 3: // SNC
// Skip if carry result is nonzero
_skip = carry;
break;
case 4: // SZR
_skip = (result == 0) ? 1 : 0;
break;
case 5: // SNR
_skip = (result != 0) ? 1 : 0;
break;
case 6: // SEZ
_skip = (result == 0 || carry == 0) ? 1 : 0;
break;
case 7: // SBN
_skip = (result != 0 && carry != 0) ? 1 : 0;
break;
}
if (_loadR)
{
// Write carry flag back.
_carry = carry;
}
break;
}
}
// From Section 3, Pg. 31:
// "The emulator has two additional bits of state, the SKIP and CARRY flip flops. CARRY is distinct from the
// microprocessors ALUC0 bit, tested by the ALUCY function. CARRY is set or cleared as a function of IR and
// many other things(see section 3.1) when the DNS<-(do novel shifts, F2= 12B) function is executed. In
// particular, if IR[12] is true, CARRY will not change. DNS also addresses R from (3-IR[3 - 4]), causes a store
// into R unless IR[12] is set, and sets the SKIP flip flop if appropriate(see section 3.1). The emulator
// microcode increments PC by 1 at the beginning of the next emulated instruction if SKIP is set, using
// BUS+SKIP(ALUF= 13B). IR<- clears SKIP."
//
// NB: _skip is in the encapsulating AltoCPU class to make it easier to reference since the ALU needs to know about it.
private int _carry;
}
}
}