1 | unit CpuZ80;
|
---|
2 |
|
---|
3 | interface
|
---|
4 |
|
---|
5 | uses
|
---|
6 | Classes, SysUtils, Memory;
|
---|
7 |
|
---|
8 | type
|
---|
9 | TReadEvent = function (Address: Word): Byte of object;
|
---|
10 | TWriteEvent = procedure (Address: Word; Data: Byte) of object;
|
---|
11 |
|
---|
12 | TInstruction = (inNop = 0, inLdBcNn = $1, inLdBcIndirectA = $2,
|
---|
13 | inIncBc = $3, inIncB = $4, inDecB = $5, inLdBN = $6, inRlca = $7,
|
---|
14 | inLdDeNn = $11, inJrNzD = $20, inLdHlNn = $21,
|
---|
15 | inIncHl = $23, inLdHlIndirectNn = $2a, inDecHl = $2b, inJrNcD = $30,
|
---|
16 | inLdSpNn = $31, inLdNnIndirectA = $32, inLdHlIndirectN = $36, inLdAN = $3e,
|
---|
17 | inLdCHlIndirect = $4e,
|
---|
18 | inLdHlIndirectD = $72, inLdHlIndirectE = $73, inHalt = $76, inLdAC = $79,
|
---|
19 | inLdAHlIndirect = $7e,
|
---|
20 | inXorA = $af, inCpD = $ba, inCpE = $bb,
|
---|
21 | inJpNn = $c3, inPushBc = $c5, inRst00 = $c7, inRet = $c9, inJpZNn = $ca, inPrefixCb = $cb,
|
---|
22 | inCallNn = $cd, inRst08 = $cf,
|
---|
23 | inOutNA = $d3, inPushDe = $d5, inRst10 = $d7, inPrefixDd = $dd,
|
---|
24 | inRst18 = $df, inRst20 = $e0, inPushHl = $e5, inExDeHl = $eb,
|
---|
25 | inPrefixEd = $ed, inRst28 = $ef, inDi = $f3,
|
---|
26 | inRst30 = $f7, inEi = $fb, inPrefixFd = $fd, inCpN = $fe,
|
---|
27 | inRst38 = $ff, inSbcHlDe = $ed52, inIm1 = $ed56);
|
---|
28 |
|
---|
29 | TCpuZ80 = class;
|
---|
30 |
|
---|
31 | { TCpuThread }
|
---|
32 |
|
---|
33 | TCpuThread = class(TThread)
|
---|
34 | Cpu: TCpuZ80;
|
---|
35 | procedure Execute; override;
|
---|
36 | end;
|
---|
37 |
|
---|
38 | TRegBC = record
|
---|
39 | case Byte of
|
---|
40 | 0: (B, C: Byte);
|
---|
41 | 1: (Value: Word);
|
---|
42 | end;
|
---|
43 |
|
---|
44 | TRegDE = record
|
---|
45 | case Byte of
|
---|
46 | 0: (D, E: Byte);
|
---|
47 | 1: (Value: Word);
|
---|
48 | end;
|
---|
49 |
|
---|
50 | TRegHL = record
|
---|
51 | case Byte of
|
---|
52 | 0: (H, L: Byte);
|
---|
53 | 1: (Value: Word);
|
---|
54 | end;
|
---|
55 |
|
---|
56 | TDebugMode = (dmNone, dmStepIn, dmStepOut, dmStepOver, dmStopAddress);
|
---|
57 |
|
---|
58 | { TCpuZ80 }
|
---|
59 |
|
---|
60 | TCpuZ80 = class
|
---|
61 | private
|
---|
62 | FOnInput: TReadEvent;
|
---|
63 | FOnOutput: TWriteEvent;
|
---|
64 | FOnRead: TReadEvent;
|
---|
65 | FOnWrite: TWriteEvent;
|
---|
66 | FRunning: Boolean;
|
---|
67 | FThread: TCpuThread;
|
---|
68 | Instruction: TInstruction;
|
---|
69 | procedure SetRunning(AValue: Boolean);
|
---|
70 | function DoRead(Address: Word): Byte;
|
---|
71 | procedure DoWrite(Address: Word; Data: Byte);
|
---|
72 | function DoInput(Address: Word): Byte;
|
---|
73 | procedure DoOutput(Address: Word; Data: Byte);
|
---|
74 | function ReadByte: Byte;
|
---|
75 | function ReadWord: Word;
|
---|
76 | procedure PushWord(Data: Word); inline;
|
---|
77 | function PopWord: Word; inline;
|
---|
78 | procedure Call(Address: Word); inline;
|
---|
79 | procedure Cp(Data: Byte); inline;
|
---|
80 | procedure Jr(Condition: Boolean); inline;
|
---|
81 | procedure Jp(Condition: Boolean); inline;
|
---|
82 | public
|
---|
83 | A: Byte;
|
---|
84 | PC: Word;
|
---|
85 | SP: Word;
|
---|
86 | BC: TRegBC;
|
---|
87 | DE: TRegDE;
|
---|
88 | HL: TRegHL;
|
---|
89 | Carry: Boolean;
|
---|
90 | Zero: Boolean;
|
---|
91 | Memory: TMemory;
|
---|
92 | Ticks: Cardinal;
|
---|
93 | InterruptEnabled: Boolean;
|
---|
94 | InterruptMode: Byte;
|
---|
95 | DebugMode: TDebugMode;
|
---|
96 | DebugStopAddress: Word;
|
---|
97 | procedure Step;
|
---|
98 | procedure Reset;
|
---|
99 | constructor Create;
|
---|
100 | destructor Destroy; override;
|
---|
101 | property Running: Boolean read FRunning write SetRunning;
|
---|
102 | property OnRead: TReadEvent read FOnRead write FOnRead;
|
---|
103 | property OnWrite: TWriteEvent read FOnWrite write FOnWrite;
|
---|
104 | property OnInput: TReadEvent read FOnInput write FOnInput;
|
---|
105 | property OnOutput: TWriteEvent read FOnOutput write FOnOutput;
|
---|
106 | end;
|
---|
107 |
|
---|
108 | implementation
|
---|
109 |
|
---|
110 | { TCpuThread }
|
---|
111 |
|
---|
112 | procedure TCpuThread.Execute;
|
---|
113 | begin
|
---|
114 | while not Terminated do begin
|
---|
115 | Cpu.Step;
|
---|
116 | if Cpu.DebugMode <> dmNone then begin
|
---|
117 | if Cpu.DebugMode = dmStepIn then Terminate;
|
---|
118 | if (Cpu.DebugMode = dmStopAddress) and (Cpu.DebugStopAddress = Cpu.PC) then
|
---|
119 | Terminate;
|
---|
120 | Cpu.DebugMode := dmNone;
|
---|
121 | end;
|
---|
122 | end;
|
---|
123 | Cpu.FRunning := False;
|
---|
124 | end;
|
---|
125 |
|
---|
126 | { TCpuZ80 }
|
---|
127 |
|
---|
128 | procedure TCpuZ80.SetRunning(AValue: Boolean);
|
---|
129 | begin
|
---|
130 | if FRunning = AValue then Exit;
|
---|
131 | if FRunning then begin
|
---|
132 | FThread.Terminate;
|
---|
133 | FThread.WaitFor;
|
---|
134 | FreeAndNil(FThread);
|
---|
135 | end;
|
---|
136 | FRunning := AValue;
|
---|
137 | if FRunning then begin
|
---|
138 | if Assigned(FThread) then FThread.Free;
|
---|
139 | FThread := TCpuThread.Create(True);
|
---|
140 | FThread.FreeOnTerminate := False;
|
---|
141 | FThread.Cpu := Self;
|
---|
142 | FThread.Start;
|
---|
143 | end;
|
---|
144 | end;
|
---|
145 |
|
---|
146 | function TCpuZ80.DoRead(Address: Word): Byte;
|
---|
147 | begin
|
---|
148 | if Assigned(FOnRead) then Result := FOnRead(Address);
|
---|
149 | end;
|
---|
150 |
|
---|
151 | procedure TCpuZ80.DoWrite(Address: Word; Data: Byte);
|
---|
152 | begin
|
---|
153 | if Assigned(FOnRead) then FOnWrite(Address, Data);
|
---|
154 | end;
|
---|
155 |
|
---|
156 | function TCpuZ80.DoInput(Address: Word): Byte;
|
---|
157 | begin
|
---|
158 | if Assigned(FOnInput) then Result := FOnInput(Address);
|
---|
159 | end;
|
---|
160 |
|
---|
161 | procedure TCpuZ80.DoOutput(Address: Word; Data: Byte);
|
---|
162 | begin
|
---|
163 | if Assigned(FOnOutput) then FOnOutput(Address, Data);
|
---|
164 | end;
|
---|
165 |
|
---|
166 | function TCpuZ80.ReadByte: Byte;
|
---|
167 | begin
|
---|
168 | Result := DoRead(PC);
|
---|
169 | PC := (PC + SizeOf(Byte)) mod $10000;
|
---|
170 | end;
|
---|
171 |
|
---|
172 | function TCpuZ80.ReadWord: Word;
|
---|
173 | begin
|
---|
174 | Result := DoRead(PC) or (DoRead(PC + 1) shl 8);
|
---|
175 | PC := (PC + SizeOf(Word)) mod $10000;
|
---|
176 | end;
|
---|
177 |
|
---|
178 | procedure TCpuZ80.PushWord(Data: Word);
|
---|
179 | begin
|
---|
180 | SP := (SP + $10000 - SizeOf(Word)) mod $10000;
|
---|
181 | DoWrite(SP, Data and $ff);
|
---|
182 | DoWrite(SP + 1, Data shr 8);
|
---|
183 | end;
|
---|
184 |
|
---|
185 | function TCpuZ80.PopWord: Word;
|
---|
186 | begin
|
---|
187 | Result := DoRead(SP) or (DoRead(SP + 1) shl 8);
|
---|
188 | SP := (SP + SizeOf(Word)) mod $10000;
|
---|
189 | end;
|
---|
190 |
|
---|
191 | procedure TCpuZ80.Call(Address: Word);
|
---|
192 | begin
|
---|
193 | PushWord(PC);
|
---|
194 | PC := Address;
|
---|
195 | end;
|
---|
196 |
|
---|
197 | procedure TCpuZ80.Cp(Data: Byte);
|
---|
198 | var
|
---|
199 | TempByte: Byte;
|
---|
200 | begin
|
---|
201 | Carry := Data > A;
|
---|
202 | TempByte := Byte(ShortInt(A) - ShortInt(Data));
|
---|
203 | Zero := TempByte = 0;
|
---|
204 | end;
|
---|
205 |
|
---|
206 | procedure TCpuZ80.Jr(Condition: Boolean);
|
---|
207 | var
|
---|
208 | Temp: Byte;
|
---|
209 | begin
|
---|
210 | Temp := ReadByte;
|
---|
211 | if Condition then
|
---|
212 | PC := PC + ShortInt(Temp);
|
---|
213 | end;
|
---|
214 |
|
---|
215 | procedure TCpuZ80.Jp(Condition: Boolean);
|
---|
216 | var
|
---|
217 | Temp: Word;
|
---|
218 | begin
|
---|
219 | Temp := ReadWord;
|
---|
220 | if Condition then PC := Temp;
|
---|
221 | end;
|
---|
222 |
|
---|
223 | procedure TCpuZ80.Step;
|
---|
224 | var
|
---|
225 | Opcode: Byte;
|
---|
226 | TempWord: Word;
|
---|
227 | begin
|
---|
228 | Opcode := ReadByte;
|
---|
229 | if Opcode = $cb then begin
|
---|
230 | Opcode := ReadByte;
|
---|
231 | Instruction := TInstruction($cb00 or Opcode)
|
---|
232 | end
|
---|
233 | else if Opcode = $dd then begin
|
---|
234 | Opcode := ReadByte;
|
---|
235 | Instruction := TInstruction($dd00 or Opcode)
|
---|
236 | end
|
---|
237 | else if Opcode = $ed then begin
|
---|
238 | Opcode := ReadByte;
|
---|
239 | Instruction := TInstruction($ed00 or Opcode)
|
---|
240 | end
|
---|
241 | else if Opcode = $fd then begin
|
---|
242 | Opcode := ReadByte;
|
---|
243 | Instruction := TInstruction($fd00 or Opcode)
|
---|
244 | end
|
---|
245 | else Instruction := TInstruction(Opcode);
|
---|
246 | case Instruction of
|
---|
247 | inNop: ;
|
---|
248 | inHalt: ;
|
---|
249 | inLdHlNn: HL.Value := ReadWord;
|
---|
250 | inLdBcNn: BC.Value := ReadWord;
|
---|
251 | inLdDeNn: DE.Value := ReadWord;
|
---|
252 | inLdSpNn: SP := ReadWord;
|
---|
253 | inLdAN: A := ReadByte;
|
---|
254 | inLdBN: BC.B := ReadByte;
|
---|
255 | inLdAC: A := BC.C;
|
---|
256 | inLdHlIndirectN: DoWrite(HL.Value, ReadByte);
|
---|
257 | inLdNnIndirectA: DoWrite(ReadWord, A);
|
---|
258 | inLdHlIndirectD: DoWrite(HL.Value, DE.D);
|
---|
259 | inLdHlIndirectE: DoWrite(HL.Value, DE.E);
|
---|
260 | inLdCHlIndirect: BC.C := DoRead(HL.Value);
|
---|
261 | inLdAHlIndirect: A := DoRead(HL.Value);
|
---|
262 | inLdBcIndirectA: DoWrite(BC.Value, A);
|
---|
263 | inLdHlIndirectNn: begin
|
---|
264 | TempWord := ReadWord;
|
---|
265 | DoWrite(HL.Value, TempWord and $ff);
|
---|
266 | DoWrite(HL.Value + 1, TempWord shr 8);
|
---|
267 | end;
|
---|
268 | inJpNn: PC := ReadWord;
|
---|
269 | inJpZNn: Jp(not Zero);
|
---|
270 | inCpN: Cp(ReadByte);
|
---|
271 | inCpD: Cp(DE.D);
|
---|
272 | inCpE: Cp(DE.E);
|
---|
273 | inCallNn: begin
|
---|
274 | TempWord := ReadWord;
|
---|
275 | if DebugMode = dmStepOver then begin
|
---|
276 | DebugStopAddress := PC;
|
---|
277 | DebugMode := dmStopAddress;
|
---|
278 | end;
|
---|
279 | Call(TempWord);
|
---|
280 | end;
|
---|
281 | inRet: begin
|
---|
282 | PC := PopWord;
|
---|
283 | if DebugMode = dmStepOut then begin
|
---|
284 | FThread.Terminate;
|
---|
285 | DebugMode := dmNone;
|
---|
286 | end;
|
---|
287 | end;
|
---|
288 | inRst00: Call($00);
|
---|
289 | inRst08: Call($08);
|
---|
290 | inRst10: Call($10);
|
---|
291 | inRst18: Call($18);
|
---|
292 | inRst20: Call($20);
|
---|
293 | inRst28: Call($28);
|
---|
294 | inRst30: Call($30);
|
---|
295 | inRst38: Call($08);
|
---|
296 | inDi: InterruptEnabled := False;
|
---|
297 | inEi: InterruptEnabled := True;
|
---|
298 | inIm1: InterruptMode := 1;
|
---|
299 | inOutNA: DoOutput(ReadByte, A);
|
---|
300 | inXorA: A := A xor A;
|
---|
301 | inPushBc: PushWord(BC.Value);
|
---|
302 | inPushDe: PushWord(DE.Value);
|
---|
303 | inPushHl: PushWord(HL.Value);
|
---|
304 | inSbcHlDe: HL.Value := HL.Value - DE.Value;
|
---|
305 | inExDeHl: begin
|
---|
306 | TempWord := DE.Value;
|
---|
307 | DE.Value := HL.Value;
|
---|
308 | HL.Value := TempWord;
|
---|
309 | end;
|
---|
310 | inDecHl: Dec(HL.Value);
|
---|
311 | inIncHl: Inc(HL.Value);
|
---|
312 | inIncBc: Inc(BC.Value);
|
---|
313 | inIncB: Inc(BC.B);
|
---|
314 | inDecB: Dec(BC.B);
|
---|
315 | inJrNzD: Jr(not Zero);
|
---|
316 | inJrNcD: Jr(not Carry);
|
---|
317 | else raise Exception.Create('Unsupported instruction ' + IntToHex(Word(Instruction), 4));
|
---|
318 | end;
|
---|
319 | Ticks := Cardinal(Ticks + 1);
|
---|
320 | end;
|
---|
321 |
|
---|
322 | procedure TCpuZ80.Reset;
|
---|
323 | begin
|
---|
324 | A := 0;
|
---|
325 | PC := 0;
|
---|
326 | BC.Value := 0;
|
---|
327 | DE.Value := 0;
|
---|
328 | HL.Value := 0;
|
---|
329 | SP := 0;
|
---|
330 | Carry := False;
|
---|
331 | Zero := False;
|
---|
332 | InterruptEnabled := True;
|
---|
333 | InterruptMode := 0;
|
---|
334 | end;
|
---|
335 |
|
---|
336 | constructor TCpuZ80.Create;
|
---|
337 | begin
|
---|
338 | Reset;
|
---|
339 | end;
|
---|
340 |
|
---|
341 | destructor TCpuZ80.Destroy;
|
---|
342 | begin
|
---|
343 | Running := False;
|
---|
344 | inherited;
|
---|
345 | end;
|
---|
346 |
|
---|
347 | end.
|
---|
348 |
|
---|