Skip to content

Commit 9ce3507

Browse files
authored
Add support for 16 bit port numbers (#9)
New interfaces are added instaed of modifying the old ones in order to keep compatibility. The most important change is the addition of the InstructionExecutorExtendedPorts property to the Z80Processor class.
1 parent 081c556 commit 9ce3507

25 files changed

+933
-178
lines changed

Docs/Configuration.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,42 @@ _NOTE:_ Remember that plugging [custom dependencies](Dependencies.md) is an alte
2121
* **`SetPortWaitStates`** and **`GetPortWaitStates`** methods allow to configure the amount of extra T states that will be counted for timing purposes when a port is read or written during the execution of an instruction (not including the extra T state that is always added by the Z80 itself). The default value is zero for the entire ports space.
2222

2323
* **`UserState`**: This is a convenience property that can be used by the client code to store just anything. The code of the processor class will never access this property for anything. The default value is _null_.
24+
25+
* **`UseExtendedPortsSpace`**: Enables or disables the extended ports space (it's disabled by default), see below for details.
26+
27+
28+
### The extended ports space
29+
30+
In a real Z80 processor the port access instructions (`IN`, `INI`, `INIR`, `IND`, `INDR`, `OUT`, `OUTI`, `OTIR`, `OUTD`, `OTDR`) work with 16 bit port numbers. These port numbers are composed as follows:
31+
32+
* The lower half, whose value is set to pins A7-A0 of the address bus, is included in the instruction itself, either explicitly (as part of the instruction opcode) or implicitly (in register C).
33+
* The higher half, whose value is set to pins A15-A8 of the address bus, is taken from register A or B (when the instruction begins its execution) as follows:
34+
* For the `IN A,(n)` and `OUT (n),A` instructions: register A.
35+
* For the other instructions: register B.
36+
37+
Z80.NET 1.0 didn't support 16 bit port numbers: register A/B was ignored and the actual port accessed was always a number in the range 0-255, as specified by the instruction opcode or as taken from register C.
38+
39+
Z80.NET 1.1 implements a new boolean property, `UseExtendedPortsSpace`, that works as follows:
40+
41+
* When it's `false` (default) the behavior is as in Z80.NET 1.0: port numbers are always 8 bit values.
42+
* When it's `true`, 16 bit port numbers are supported as specified above.
43+
44+
For example, take the following snippet:
45+
46+
```
47+
ld a,12h
48+
in a,(34h)
49+
```
50+
51+
* When `UseExtendedPortsSpace` is `false` this will load A with the value of `PortsSpace[0x34]`.
52+
* When `UseExtendedPortsSpace` is `true` this will load A with the value of `PortsSpace[0x1234]`.
53+
54+
Note that before setting `UseExtendedPortsSpace` to `true` you need to set `PortsSpace` to an instance of `IMemory` that reports a size of at least 65536, otherwise you'll get an exception; example:
55+
56+
```C#
57+
var Z80 = new Z80Processor();
58+
Z80.PortsSpace = new PlainMemory(65536);
59+
Z80.UseExtendedPortsSpace = true;
60+
```
61+
62+
You may want to take a look at [the relevant unit tests](../Main.Tests/Z80ProcessorTests_PortsAccess.cs) for more details on how port access works depending on the value of `UseExtendedPortsSpace`.

Main.Tests/Instructions Execution/IN A,(n) .Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public void IN_A_n_reads_value_from_port()
1515
var oldValue = Fixture.Create<byte>();
1616

1717
Registers.A = oldValue;
18-
SetPortValue(portNumber, value);
18+
SetPortValue(NumberUtils.CreateUshort(portNumber, oldValue), value);
1919

2020
Execute(IN_A_n_opcode, null, portNumber);
2121

Main.Tests/Instructions Execution/IN r,(C). .Tests.cs

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ public class IN_r_C : InstructionsExecutionTestsBase
2828
[TestCaseSource(nameof(IN_r_C_Source))]
2929
public void IN_r_C_reads_value_from_port(string reg, byte opcode)
3030
{
31-
var portNumber = Fixture.Create<byte>();
31+
var portNumberLow = Fixture.Create<byte>();
32+
var portNumberHigh = Fixture.Create<byte>();
3233
var value = Fixture.Create<byte>();
3334
var oldValue = Fixture.Create<byte>();
3435

3536
if(reg != "C")
36-
SetReg(reg, value);
37-
Registers.A = oldValue;
37+
SetReg(reg, oldValue);
3838

39-
Execute(opcode, portNumber, value);
39+
Execute(opcode, portNumberLow, portNumberHigh, value);
4040

4141
Assert.That(GetReg<byte>(reg), Is.EqualTo(value));
4242
}
@@ -46,18 +46,19 @@ public void IN_r_C_reads_value_from_port(string reg, byte opcode)
4646
[TestCaseSource(nameof(IN_F_C_Source))]
4747
public void IN_r_C_sets_SF_appropriately(string reg, byte opcode)
4848
{
49-
var portNumber = Fixture.Create<byte>();
49+
var portNumberLow = Fixture.Create<byte>();
50+
var portNumberHigh = Fixture.Create<byte>();
5051

51-
Execute(opcode, portNumber, 0xFE);
52+
Execute(opcode, portNumberLow, portNumberHigh, 0xFE);
5253
Assert.That(Registers.SF.Value, Is.EqualTo(1));
5354

54-
Execute(opcode, portNumber, 0xFF);
55+
Execute(opcode, portNumberLow, portNumberHigh, 0xFF);
5556
Assert.That(Registers.SF.Value, Is.EqualTo(1));
5657

57-
Execute(opcode, portNumber, 0);
58+
Execute(opcode, portNumberLow, portNumberHigh, 0);
5859
Assert.That(Registers.SF.Value, Is.EqualTo(0));
5960

60-
Execute(opcode, portNumber, 1);
61+
Execute(opcode, portNumberLow, portNumberHigh, 1);
6162
Assert.That(Registers.SF.Value, Is.EqualTo(0));
6263
}
6364

@@ -66,15 +67,16 @@ public void IN_r_C_sets_SF_appropriately(string reg, byte opcode)
6667
[TestCaseSource(nameof(IN_F_C_Source))]
6768
public void IN_r_C_sets_ZF_appropriately(string reg, byte opcode)
6869
{
69-
var portNumber = Fixture.Create<byte>();
70+
var portNumberLow = Fixture.Create<byte>();
71+
var portNumberHigh = Fixture.Create<byte>();
7072

71-
Execute(opcode, portNumber, 0xFF);
73+
Execute(opcode, portNumberLow, portNumberHigh, 0xFF);
7274
Assert.That(Registers.ZF.Value, Is.EqualTo(0));
7375

74-
Execute(opcode, portNumber, 0);
76+
Execute(opcode, portNumberLow, portNumberHigh, 0);
7577
Assert.That(Registers.ZF.Value, Is.EqualTo(1));
7678

77-
Execute(opcode, portNumber, 1);
79+
Execute(opcode, portNumberLow, portNumberHigh, 1);
7880
Assert.That(Registers.ZF.Value, Is.EqualTo(0));
7981
}
8082

@@ -92,16 +94,17 @@ public void IN_r_C_resets_HF_NF(string reg, byte opcode)
9294
public void IN_r_C_does_not_change_CF(string reg, byte opcode)
9395
{
9496
var randomValues = Fixture.Create<byte[]>();
95-
var portNumber = Fixture.Create<byte>();
97+
var portNumberLow = Fixture.Create<byte>();
98+
var portNumberHigh = Fixture.Create<byte>();
9699

97100
foreach (var value in randomValues)
98101
{
99102
Registers.CF = 0;
100-
Execute(opcode, portNumber, value);
103+
Execute(opcode, portNumberLow, portNumberHigh, value);
101104
Assert.That(Registers.CF.Value, Is.EqualTo(0));
102105

103106
Registers.CF = 1;
104-
Execute(opcode, portNumber, value);
107+
Execute(opcode, portNumberLow, portNumberHigh, value);
105108
Assert.That(Registers.CF.Value, Is.EqualTo(1));
106109
}
107110
}
@@ -112,11 +115,12 @@ public void IN_r_C_does_not_change_CF(string reg, byte opcode)
112115
public void IN_r_C_sets_PF_as_parity(string reg, byte opcode)
113116
{
114117
var randomValues = Fixture.Create<byte[]>();
115-
var portNumber = Fixture.Create<byte>();
118+
var portNumberLow = Fixture.Create<byte>();
119+
var portNumberHigh = Fixture.Create<byte>();
116120

117121
foreach (var value in randomValues)
118122
{
119-
Execute(opcode, portNumber, value);
123+
Execute(opcode, portNumberLow, portNumberHigh, value);
120124
Assert.That(Registers.PF.Value, Is.EqualTo(Parity[value]));
121125
}
122126
}
@@ -126,17 +130,18 @@ public void IN_r_C_sets_PF_as_parity(string reg, byte opcode)
126130
[TestCaseSource(nameof(IN_F_C_Source))]
127131
public void IN_r_C_sets_bits_3_and_5_from_result(string reg, byte opcode)
128132
{
129-
var portNumber = Fixture.Create<byte>();
133+
var portNumberLow = Fixture.Create<byte>();
134+
var portNumberHigh = Fixture.Create<byte>();
130135
var value = ((byte)0).WithBit(3, 1).WithBit(5, 0);
131-
Execute(opcode, portNumber, value);
136+
Execute(opcode, portNumberLow, portNumberHigh, value);
132137
Assert.Multiple(() =>
133138
{
134139
Assert.That(Registers.Flag3.Value, Is.EqualTo(1));
135140
Assert.That(Registers.Flag5.Value, Is.EqualTo(0));
136141
});
137142

138143
value = ((byte)0).WithBit(3, 0).WithBit(5, 1);
139-
Execute(opcode, portNumber, value);
144+
Execute(opcode, portNumberLow, portNumberHigh, value);
140145
Assert.Multiple(() =>
141146
{
142147
Assert.That(Registers.Flag3.Value, Is.EqualTo(0));
@@ -149,16 +154,18 @@ public void IN_r_C_sets_bits_3_and_5_from_result(string reg, byte opcode)
149154
[TestCaseSource(nameof(IN_F_C_Source))]
150155
public void IN_r_C_returns_proper_T_states(string reg, byte opcode)
151156
{
152-
var portNumber = Fixture.Create<byte>();
157+
var portNumberLow = Fixture.Create<byte>();
158+
var portNumberHigh = Fixture.Create<byte>();
153159
var value = Fixture.Create<byte>();
154-
var states = Execute(opcode, portNumber, value);
160+
var states = Execute(opcode, portNumberLow, portNumberHigh, value);
155161
Assert.That(states, Is.EqualTo(12));
156162
}
157163

158-
private int Execute(byte opcode, byte portNumber, byte value)
164+
private int Execute(byte opcode, byte portNumberLow, byte portNumberHigh, byte value)
159165
{
160-
Registers.C = portNumber;
161-
SetPortValue(portNumber, value);
166+
Registers.C = portNumberLow;
167+
Registers.B = portNumberHigh;
168+
SetPortValue(NumberUtils.CreateUshort(portNumberLow, portNumberHigh), value);
162169
return Execute(opcode, 0xED);
163170
}
164171
}

Main.Tests/Instructions Execution/OUT (n),A .Tests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@ public class OUT_A_n_tests : InstructionsExecutionTestsBase
88
private const byte OUT_A_n_opcode = 0xD3;
99

1010
[Test]
11-
public void OUT_A_n_reads_value_from_port()
11+
public void OUT_A_n_writes_value_to_port()
1212
{
13-
var portNumber = Fixture.Create<byte>();
13+
var portNumberLow = Fixture.Create<byte>();
1414
var value = Fixture.Create<byte>();
1515
var oldValue = Fixture.Create<byte>();
1616

1717
Registers.A = value;
18-
SetPortValue(portNumber, oldValue);
18+
SetPortValue(NumberUtils.CreateUshort(portNumberLow, value), oldValue);
1919

20-
Execute(OUT_A_n_opcode, null, portNumber);
20+
Execute(OUT_A_n_opcode, null, portNumberLow);
2121

22-
Assert.That(GetPortValue(portNumber), Is.EqualTo(value));
22+
Assert.That(GetPortValue(NumberUtils.CreateUshort(portNumberLow, value)), Is.EqualTo(value));
2323
}
2424

2525
[Test]

Main.Tests/Instructions Execution/_InstructionsExecutionTestsBase.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public void Setup()
2020
{
2121
Sut = new Z80InstructionExecutor();
2222
Sut.ProcessorAgent = ProcessorAgent = new FakeProcessorAgent();
23+
Sut.ProcessorAgentExtendedPorts = ProcessorAgent;
2324
Registers = ProcessorAgent.Registers;
2425
Sut.InstructionFetchFinished += (s, e) => { };
2526

@@ -30,12 +31,12 @@ public void Setup()
3031

3132
protected int nextFetchesAddress;
3233

33-
protected void SetPortValue(byte portNumber, byte value)
34+
protected void SetPortValue(ushort portNumber, byte value)
3435
{
3536
ProcessorAgent.Ports[portNumber] = value;
3637
}
3738

38-
protected byte GetPortValue(byte portNumber)
39+
protected byte GetPortValue(ushort portNumber)
3940
{
4041
return ProcessorAgent.Ports[portNumber];
4142
}
@@ -61,7 +62,8 @@ protected FakeInstructionExecutor NewFakeInstructionExecutor()
6162
{
6263
var sut = new FakeInstructionExecutor();
6364
sut.ProcessorAgent = ProcessorAgent = new FakeProcessorAgent();
64-
Registers = ProcessorAgent.Registers;
65+
sut.ProcessorAgentExtendedPorts = ProcessorAgent;
66+
Registers = ProcessorAgent.Registers;
6567
sut.InstructionFetchFinished += (s, e) => { };
6668
return sut;
6769
}
@@ -375,13 +377,13 @@ protected override int ExecuteUnsopported_ED_Instruction(byte secondOpcodeByte)
375377
}
376378
}
377379

378-
protected class FakeProcessorAgent : IZ80ProcessorAgent
380+
protected class FakeProcessorAgent : IZ80ProcessorAgent, IZ80ProcessorAgentExtendedPorts
379381
{
380382
public FakeProcessorAgent()
381383
{
382384
Registers = new Z80Registers();
383385
Memory = new byte[65536];
384-
Ports = new byte[256];
386+
Ports = new byte[65536];
385387
}
386388

387389
public byte[] Memory { get; set; }
@@ -433,6 +435,16 @@ public void Stop(bool isPause = false)
433435
{
434436
throw new NotImplementedException();
435437
}
438+
439+
public byte ReadFromPort(byte portNumberLow, byte portNumberHigh)
440+
{
441+
return Ports[NumberUtils.CreateUshort(portNumberLow, portNumberHigh)];
442+
}
443+
444+
public void WriteToPort(byte portNumberLow, byte portNumberHigh, byte value)
445+
{
446+
Ports[NumberUtils.CreateUshort(portNumberLow, portNumberHigh)] = value;
447+
}
436448
}
437449

438450
#endregion

0 commit comments

Comments
 (0)