| 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 |
|
|---|