Add Z80 support for the RFRSH pin, triggered by M1

Signed-off-by: Adrian Conlon <Adrian.conlon@gmail.com>
This commit is contained in:
Adrian Conlon 2019-11-16 20:56:08 +00:00
parent 56e1e67850
commit 5575fec896
1 changed files with 142 additions and 67 deletions

View File

@ -31,6 +31,7 @@ namespace EightBit
private PinLevel nmiLine = PinLevel.Low;
private PinLevel m1Line = PinLevel.Low;
private PinLevel rfshLine = PinLevel.Low;
private PinLevel mreqLine = PinLevel.Low;
private PinLevel iorqLine = PinLevel.Low;
private PinLevel rdLine = PinLevel.Low;
@ -65,6 +66,14 @@ namespace EightBit
public event EventHandler<EventArgs> LoweredM1;
public event EventHandler<EventArgs> RaisingRFSH;
public event EventHandler<EventArgs> RaisedRFSH;
public event EventHandler<EventArgs> LoweringRFSH;
public event EventHandler<EventArgs> LoweredRFSH;
public event EventHandler<EventArgs> RaisingMREQ;
public event EventHandler<EventArgs> RaisedMREQ;
@ -125,12 +134,29 @@ namespace EightBit
public byte IYL { get => this.IY.Low; set => this.IY.Low = value; }
// ** From the Z80 CPU User Manual
// Memory Refresh(R) Register.The Z80 CPU contains a memory refresh counter,
// enabling dynamic memories to be used with the same ease as static memories.Seven bits
// of this 8-bit register are automatically incremented after each instruction fetch.The eighth
// bit remains as programmed, resulting from an LD R, A instruction. The data in the refresh
// counter is sent out on the lower portion of the address bus along with a refresh control
// signal while the CPU is decoding and executing the fetched instruction. This mode of refresh
// is transparent to the programmer and does not slow the CPU operation.The programmer
// can load the R register for testing purposes, but this register is normally not used by the
// programmer. During refresh, the contents of the I Register are placed on the upper eight
// bits of the address bus.
public ref RefreshRegister REFRESH => ref this.refresh;
public ref PinLevel NMI => ref this.nmiLine;
public ref PinLevel M1 => ref this.m1Line;
// ** From the Z80 CPU User Manual
// RFSH.Refresh(output, active Low). RFSH, together with MREQ, indicates that the lower
// seven bits of the systems address bus can be used as a refresh address to the systems
// dynamic memories.
public ref PinLevel RFSH => ref this.rfshLine;
public ref PinLevel MREQ => ref this.mreqLine;
public ref PinLevel IORQ => ref this.iorqLine;
@ -192,6 +218,26 @@ namespace EightBit
}
}
public virtual void RaiseRFSH()
{
if (this.RFSH.Lowered())
{
this.OnRaisingRFSH();
this.RFSH.Raise();
this.OnRaisedRFSH();
}
}
public virtual void LowerRFSH()
{
if (this.RFSH.Raised())
{
this.OnLoweringRFSH();
this.RFSH.Lower();
this.OnLoweredRFSH();
}
}
public virtual void RaiseMREQ()
{
if (this.MREQ.Lowered())
@ -328,6 +374,21 @@ namespace EightBit
}
else if (this.HALT.Lowered())
{
// ** From the Z80 CPU User Manual
// When a software HALT instruction is executed, the CPU executes NOPs until an interrupt
// is received(either a nonmaskable or a maskable interrupt while the interrupt flip-flop is
// enabled). The two interrupt lines are sampled with the rising clock edge during each T4
// state as depicted in Figure 11.If a nonmaskable interrupt is received or a maskable interrupt
// is received and the interrupt enable flip-flop is set, then the HALT state is exited on
// the next rising clock edge.The following cycle is an interrupt acknowledge cycle corresponding
// to the type of interrupt that was received.If both are received at this time, then
// the nonmaskable interrupt is acknowledged because it is the highest priority.The purpose
// of executing NOP instructions while in the HALT state is to keep the memory refresh signals
// active.Each cycle in the HALT state is a normal M1(fetch) cycle except that the data
// received from the memory is ignored and an NOP instruction is forced internally to the
// CPU.The HALT acknowledge signal is active during this time indicating that the processor
// is in the HALT state.
var discarded = this.ReadInitialOpCode();
this.Execute(0); // NOP
handled = true;
}
@ -390,6 +451,14 @@ namespace EightBit
this.LoweredM1?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnRaisingRFSH() => this.RaisingRFSH?.Invoke(this, EventArgs.Empty);
protected virtual void OnRaisedRFSH() => this.RaisedRFSH?.Invoke(this, EventArgs.Empty);
protected virtual void OnLoweringRFSH() => this.LoweringRFSH?.Invoke(this, EventArgs.Empty);
protected virtual void OnLoweredRFSH() => this.LoweredRFSH?.Invoke(this, EventArgs.Empty);
protected virtual void OnLoweringMREQ() => this.LoweringMREQ?.Invoke(this, EventArgs.Empty);
protected virtual void OnLoweredMREQ() => this.LoweredMREQ?.Invoke(this, EventArgs.Empty);
@ -731,10 +800,10 @@ namespace EightBit
}
this.F = AdjustSZP(this.F, operand);
this.Tick(8);
this.Tick(4);
break;
case 1: // BIT y, r[z]
this.Tick(8);
this.Tick(4);
this.BIT(y, operand);
if (direct)
{
@ -748,11 +817,11 @@ namespace EightBit
break;
case 2: // RES y, r[z]
this.Tick(8);
this.Tick(4);
operand = RES(y, operand);
break;
case 3: // SET y, r[z]
this.Tick(8);
this.Tick(4);
operand = SET(y, operand);
break;
default:
@ -788,7 +857,6 @@ namespace EightBit
{
case 0:
case 3: // Invalid instruction, equivalent to NONI followed by NOP
this.Tick(8);
break;
case 1:
switch (z)
@ -804,7 +872,7 @@ namespace EightBit
this.F = AdjustSZPXY(this.F, this.Bus.Data);
this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC);
this.Tick(12);
this.Tick(4);
break;
case 1: // Output to port with 16-bit address
this.MEMPTR.Word = this.Bus.Address.Word = this.BC.Word;
@ -812,7 +880,7 @@ namespace EightBit
this.Bus.Data = y != 6 ? this.R(y) : (byte)0;
this.WritePort();
this.Tick(12);
this.Tick(4);
break;
case 2: // 16-bit add/subtract with carry
switch (q)
@ -827,7 +895,7 @@ namespace EightBit
throw new NotSupportedException("Invalid operation mode");
}
this.Tick(15);
this.Tick(7);
break;
case 3: // Retrieve/store register pair from/to immediate address
this.Bus.Address.Word = this.FetchWord().Word;
@ -843,11 +911,10 @@ namespace EightBit
throw new NotSupportedException("Invalid operation mode");
}
this.Tick(20);
this.Tick(12);
break;
case 4: // Negate accumulator
this.NEG();
this.Tick(8);
break;
case 5: // Return from interrupt
switch (y)
@ -860,7 +927,7 @@ namespace EightBit
break;
}
this.Tick(14);
this.Tick(6);
break;
case 6: // Set interrupt mode
switch (y)
@ -883,42 +950,40 @@ namespace EightBit
throw new NotSupportedException("Invalid operation mode");
}
this.Tick(8);
break;
case 7: // Assorted ops
switch (y)
{
case 0: // LD I,A
this.IV = this.A;
this.Tick(9);
this.Tick();
break;
case 1: // LD R,A
this.REFRESH = this.A;
this.Tick(9);
this.Tick();
break;
case 2: // LD A,I
this.F = AdjustSZXY(this.F, this.A = this.IV);
this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC);
this.F = SetBit(this.F, StatusBits.PF, this.IFF2);
this.Tick(9);
this.Tick();
break;
case 3: // LD A,R
this.F = AdjustSZXY(this.F, this.A = this.REFRESH);
this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC);
this.F = SetBit(this.F, StatusBits.PF, this.IFF2);
this.Tick(9);
this.Tick();
break;
case 4: // RRD
this.RRD();
this.Tick(18);
this.Tick(10);
break;
case 5: // RLD
this.RLD();
this.Tick(18);
this.Tick(10);
break;
case 6: // NOP
case 7: // NOP
this.Tick(4);
break;
default:
throw new NotSupportedException("Invalid operation mode");
@ -1055,7 +1120,7 @@ namespace EightBit
break;
}
this.Tick(16);
this.Tick(8);
break;
}
}
@ -1073,16 +1138,9 @@ namespace EightBit
switch (y)
{
case 0: // NOP
if (this.prefixDD)
{
this.Tick(4);
}
this.Tick(4);
break;
case 1: // EX AF AF'
this.ExxAF();
this.Tick(4);
break;
case 2: // DJNZ d
if (this.JumpRelativeConditional(--this.B != 0))
@ -1090,11 +1148,11 @@ namespace EightBit
this.Tick(5);
}
this.Tick(8);
this.Tick(4);
break;
case 3: // JR d
this.JumpRelative((sbyte)this.FetchByte());
this.Tick(12);
this.Tick(8);
break;
case 4: // JR cc,d
case 5:
@ -1105,7 +1163,7 @@ namespace EightBit
this.Tick(5);
}
this.Tick(5);
this.Tick(3);
break;
default:
throw new NotSupportedException("Invalid operation mode");
@ -1117,11 +1175,11 @@ namespace EightBit
{
case 0: // LD rp,nn
this.RP(p).Word = this.FetchWord().Word;
this.Tick(10);
this.Tick(6);
break;
case 1: // ADD HL,rp
this.HL2().Word = this.Add(this.HL2(), this.RP(p));
this.Tick(11);
this.Tick(7);
break;
default:
throw new NotSupportedException("Invalid operation mode");
@ -1139,26 +1197,26 @@ namespace EightBit
++this.MEMPTR.Word;
this.MEMPTR.High = this.Bus.Data = this.A;
this.BusWrite();
this.Tick(7);
this.Tick(3);
break;
case 1: // LD (DE),A
this.MEMPTR.Word = this.Bus.Address.Word = this.DE.Word;
++this.MEMPTR.Word;
this.MEMPTR.High = this.Bus.Data = this.A;
this.BusWrite();
this.Tick(7);
this.Tick(3);
break;
case 2: // LD (nn),HL
this.Bus.Address.Word = this.FetchWord().Word;
this.SetWord(this.HL2());
this.Tick(16);
this.Tick(12);
break;
case 3: // LD (nn),A
this.MEMPTR.Word = this.Bus.Address.Word = this.FetchWord().Word;
++this.MEMPTR.Word;
this.MEMPTR.High = this.Bus.Data = this.A;
this.BusWrite();
this.Tick(13);
this.Tick(9);
break;
default:
throw new NotSupportedException("Invalid operation mode");
@ -1172,24 +1230,24 @@ namespace EightBit
this.MEMPTR.Word = this.Bus.Address.Word = this.BC.Word;
++this.MEMPTR.Word;
this.A = this.BusRead();
this.Tick(7);
this.Tick(3);
break;
case 1: // LD A,(DE)
this.MEMPTR.Word = this.Bus.Address.Word = this.DE.Word;
++this.MEMPTR.Word;
this.A = this.BusRead();
this.Tick(7);
this.Tick(3);
break;
case 2: // LD HL,(nn)
this.Bus.Address.Word = this.FetchWord().Word;
this.HL2().Word = this.GetWord().Word;
this.Tick(16);
this.Tick(12);
break;
case 3: // LD A,(nn)
this.MEMPTR.Word = this.Bus.Address.Word = this.FetchWord().Word;
++this.MEMPTR.Word;
this.A = this.BusRead();
this.Tick(13);
this.Tick(9);
break;
default:
throw new NotSupportedException("Invalid operation mode");
@ -1214,7 +1272,7 @@ namespace EightBit
throw new NotSupportedException("Invalid operation mode");
}
this.Tick(6);
this.Tick(2);
break;
case 4: // 8-bit INC
if (this.displaced && memoryY)
@ -1223,7 +1281,6 @@ namespace EightBit
}
this.R(y, this.Increment(this.R(y)));
this.Tick(4);
break;
case 5: // 8-bit DEC
if (memoryY)
@ -1236,7 +1293,6 @@ namespace EightBit
}
this.R(y, this.Decrement(this.R(y)));
this.Tick(4);
break;
case 6: // 8-bit load immediate
if (memoryY)
@ -1249,7 +1305,7 @@ namespace EightBit
}
this.R(y, this.FetchByte()); // LD r,n
this.Tick(7);
this.Tick(3);
break;
case 7: // Assorted operations on accumulator/flags
switch (y)
@ -1282,7 +1338,6 @@ namespace EightBit
throw new NotSupportedException("Invalid operation mode");
}
this.Tick(4);
break;
default:
throw new NotSupportedException("Invalid operation mode");
@ -1347,7 +1402,6 @@ namespace EightBit
this.LowerHALT(); // Exception (replaces LD (HL), (HL))
}
this.Tick(4);
break;
case 2:
{ // Operate on accumulator and register/memory location
@ -1391,7 +1445,6 @@ namespace EightBit
throw new NotSupportedException("Invalid operation mode");
}
this.Tick(4);
break;
}
@ -1404,33 +1457,30 @@ namespace EightBit
this.Tick(6);
}
this.Tick(5);
this.Tick();
break;
case 1: // POP & various ops
switch (q)
{
case 0: // POP rp2[p]
this.RP2(p).Word = this.PopWord().Word;
this.Tick(10);
this.Tick(6);
break;
case 1:
switch (p)
{
case 0: // RET
this.Return();
this.Tick(10);
this.Tick(6);
break;
case 1: // EXX
this.Exx();
this.Tick(4);
break;
case 2: // JP HL
this.Jump(this.HL2().Word);
this.Tick(4);
break;
case 3: // LD SP,HL
this.SP.Word = this.HL2().Word;
this.Tick(4);
break;
default:
throw new NotSupportedException("Invalid operation mode");
@ -1444,14 +1494,14 @@ namespace EightBit
break;
case 2: // Conditional jump
this.JumpConditionalFlag(y);
this.Tick(10);
this.Tick(6);
break;
case 3: // Assorted operations
switch (y)
{
case 0: // JP nn
this.Jump(this.MEMPTR.Word = this.FetchWord().Word);
this.Tick(10);
this.Tick(6);
break;
case 1: // CB prefix
this.prefixCB = true;
@ -1468,27 +1518,24 @@ namespace EightBit
break;
case 2: // OUT (n),A
this.WritePort(this.FetchByte());
this.Tick(11);
this.Tick(7);
break;
case 3: // IN A,(n)
this.A = this.ReadPort(this.FetchByte());
this.Tick(11);
this.Tick(7);
break;
case 4: // EX (SP),HL
this.XHTL(this.HL2());
this.Tick(19);
this.Tick(15);
break;
case 5: // EX DE,HL
(this.DE.Word, this.HL.Word) = (this.HL.Word, this.DE.Word);
this.Tick(4);
break;
case 6: // DI
this.DisableInterrupts();
this.Tick(4);
break;
case 7: // EI
this.EnableInterrupts();
this.Tick(4);
break;
default:
throw new NotSupportedException("Invalid operation mode");
@ -1501,21 +1548,21 @@ namespace EightBit
this.Tick(7);
}
this.Tick(10);
this.Tick(6);
break;
case 5: // PUSH & various ops
switch (q)
{
case 0: // PUSH rp2[p]
this.PushWord(this.RP2(p));
this.Tick(11);
this.Tick(7);
break;
case 1:
switch (p)
{
case 0: // CALL nn
this.Call(this.MEMPTR.Word = this.FetchWord().Word);
this.Tick(17);
this.Tick(13);
break;
case 1: // DD prefix
this.displaced = this.prefixDD = true;
@ -1572,13 +1619,13 @@ namespace EightBit
throw new NotSupportedException("Invalid operation mode");
}
this.Tick(7);
this.Tick(3);
break;
}
case 7: // Restart: RST y * 8
this.Restart((byte)(y << 3));
this.Tick(11);
this.Tick(7);
break;
default:
throw new NotSupportedException("Invalid operation mode");
@ -1603,10 +1650,38 @@ namespace EightBit
private void FetchDisplacement() => this.displacement = (sbyte)this.FetchByte();
private byte FetchInitialOpCode()
{
var returned = this.ReadInitialOpCode();
++this.PC.Word;
return returned;
}
// ** From the Z80 CPU User Manual
// Figure 5 depicts the timing during an M1 (op code fetch) cycle. The Program Counter is
// placed on the address bus at the beginning of the M1 cycle. One half clock cycle later, the
// MREQ signal goes active. At this time, the address to memory has had time to stabilize so
// that the falling edge of MREQ can be used directly as a chip enable clock to dynamic
// memories. The RD line also goes active to indicate that the memory read data should be
// enabled onto the CPU data bus. The CPU samples the data from the memory space on the
// data bus with the rising edge of the clock of state T3, and this same edge is used by the
// CPU to turn off the RD and MREQ signals. As a result, the data is sampled by the CPU
// before the RD signal becomes inactive. Clock states T3 and T4 of a fetch cycle are used to
// refresh dynamic memories. The CPU uses this time to decode and execute the fetched
// instruction so that no other concurrent operation can be performed.
private byte ReadInitialOpCode()
{
this.LowerM1();
var returned = this.FetchByte();
var returned = this.BusRead(this.PC.Word);
this.RaiseM1();
this.Tick(2);
this.Bus.Address.Low = this.REFRESH;
this.Bus.Address.High = this.IV;
this.LowerRFSH();
this.LowerMREQ();
this.Tick();
this.RaiseMREQ();
this.RaiseRFSH();
this.Tick();
return returned;
}