mirror of
https://github.com/Dennis1000/mos6502-delphi.git
synced 2024-12-30 07:29:29 +00:00
initial commit
This commit is contained in:
parent
5c472c0c66
commit
dcd1f81487
4
.gitignore
vendored
4
.gitignore
vendored
@ -44,6 +44,10 @@
|
||||
*.a
|
||||
*.o
|
||||
*.ocx
|
||||
*.res
|
||||
*.dproj
|
||||
basic*.bin
|
||||
kernal*.bin
|
||||
|
||||
# Delphi autogenerated files (duplicated info)
|
||||
*.cfg
|
||||
|
1
C64/ROMs/README.md
Normal file
1
C64/ROMs/README.md
Normal file
@ -0,0 +1 @@
|
||||
Put your BASIC and Kernal ROMs here
|
41
C64/Source/C64.Thread.pas
Normal file
41
C64/Source/C64.Thread.pas
Normal file
@ -0,0 +1,41 @@
|
||||
unit C64.Thread;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.Classes, C64;
|
||||
|
||||
type
|
||||
TC64Thread = class(TThread)
|
||||
private
|
||||
C64: TC64;
|
||||
protected
|
||||
public
|
||||
procedure Execute; override;
|
||||
constructor Create(C64Instance: TC64);
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TC64Thread }
|
||||
|
||||
constructor TC64Thread.Create(C64Instance: TC64);
|
||||
begin
|
||||
inherited Create(True);
|
||||
C64 := C64Instance;
|
||||
end;
|
||||
|
||||
destructor TC64Thread.Destroy;
|
||||
begin
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
procedure TC64Thread.Execute;
|
||||
begin
|
||||
inherited;
|
||||
|
||||
end;
|
||||
|
||||
end.
|
197
C64/Source/C64.pas
Normal file
197
C64/Source/C64.pas
Normal file
@ -0,0 +1,197 @@
|
||||
unit C64;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.Classes, WinApi.Messages, MOS6502;
|
||||
|
||||
const
|
||||
WM_SCREEN_WRITE = WM_USER + 0;
|
||||
|
||||
CIA1 = $DC00;
|
||||
KEY_TRANSLATION = '1£+9753'#8+#8'*piyrw'#13+#0';ljgda'#0+'2'#0'-0864'#0+' '#0'.mbcz'#0+#0'=:khfs'#0+'q'#0'@oute'#0+
|
||||
#0'/,nvx'#0#0+'!'#0#0')'#39'%#'+#0+#0#0#0#0#0#0#0#13+#0']'#0#0#0#0#0#0+'"'#0#0#0'(&$'#0+' '#0'>'#0#0#0#0#0+
|
||||
#0#0'['#0#0#0#0#0+#0#0#0#0#0#0#0#0+#0'?<';
|
||||
|
||||
type
|
||||
TC64 = class;
|
||||
PC64 = ^TC64;
|
||||
|
||||
TC64Thread = class(TThread)
|
||||
private
|
||||
C64: TC64;
|
||||
protected
|
||||
public
|
||||
procedure Execute; override;
|
||||
constructor Create(C64Instance: TC64);
|
||||
end;
|
||||
|
||||
TC64 = class(TMOS6502)
|
||||
private
|
||||
Thread: TC64Thread;
|
||||
TimerHandle: Integer;
|
||||
LastKey: Char;
|
||||
procedure BusWrite(Adr: Word; Value: Byte);
|
||||
function BusRead(Adr: Word): Byte;
|
||||
function KeyRead: Byte;
|
||||
protected
|
||||
KeyMatrix: Array[0 .. 7, 0 .. 7] of Byte;
|
||||
Memory: PByte;
|
||||
InterruptRequest: Boolean;
|
||||
public
|
||||
WndHandle: THandle;
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
procedure LoadROM(Filename: String; Addr: Word);
|
||||
procedure Exec;
|
||||
procedure SetKey(Key: Char; Value: Byte);
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.SysUtils, Winapi.Windows, WinApi.MMSystem;
|
||||
|
||||
{ TC64 }
|
||||
|
||||
procedure TimerProcedure(TimerID, Msg: Uint; dwUser, dw1, dw2: DWORD); pascal;
|
||||
var
|
||||
C64: TC64;
|
||||
begin
|
||||
C64 := TC64(dwUser);
|
||||
|
||||
if C64.Status and $04 = 0 then // if IRQ allowed then set irq
|
||||
C64.InterruptRequest := True;
|
||||
end;
|
||||
|
||||
|
||||
function TC64.BusRead(Adr: Word): Byte;
|
||||
begin
|
||||
Result := Memory[Adr];
|
||||
end;
|
||||
|
||||
procedure TC64.BusWrite(Adr: Word; Value: Byte);
|
||||
begin
|
||||
// test for I/O requests
|
||||
case Adr of
|
||||
CIA1:
|
||||
begin
|
||||
// Handle keyboard reading
|
||||
Memory[Adr] := Value;
|
||||
Memory[CIA1 + 1] := KeyRead;
|
||||
end;
|
||||
|
||||
CIA1 + 5: // Timer
|
||||
if TimerHandle = 0 then
|
||||
TimerHandle := TimeSetEvent(34, 2, @TimerProcedure, DWORD(Self), TIME_PERIODIC);
|
||||
end;
|
||||
|
||||
if (Adr >= $A000) then // treat anything above as ROM
|
||||
Exit;
|
||||
|
||||
Memory[Adr] := Value;
|
||||
|
||||
// video RAM
|
||||
if (Adr >= $400) and (Adr <= $07E7) then
|
||||
PostMessage(WndHandle, WM_SCREEN_WRITE, Adr - $400, Value);
|
||||
end;
|
||||
|
||||
constructor TC64.Create;
|
||||
begin
|
||||
inherited Create(BusRead, BusWrite);
|
||||
|
||||
// create 64kB memory table
|
||||
GetMem(Memory, 65536);
|
||||
|
||||
Thread := TC64Thread.Create(Self);
|
||||
end;
|
||||
|
||||
destructor TC64.Destroy;
|
||||
begin
|
||||
if TimerHandle <> 0 then
|
||||
Timekillevent(TimerHandle);
|
||||
Thread.Terminate;
|
||||
Thread.WaitFor;
|
||||
FreeMem(Memory);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
procedure TC64.Exec;
|
||||
begin
|
||||
Reset;
|
||||
Thread.Start;
|
||||
end;
|
||||
|
||||
function TC64.KeyRead: Byte;
|
||||
var
|
||||
Row, Col, Cols: Byte;
|
||||
begin
|
||||
Result := 0;
|
||||
Cols := Memory[CIA1];
|
||||
for Col := 0 to 7 do
|
||||
if Cols and (1 shl Col) = 0 then // a 0 indicates a column read
|
||||
for Row := 0 to 7 do
|
||||
if KeyMatrix[7 - Col, Row] = 1 then
|
||||
Result := Result + (1 shl Row);
|
||||
Result := not Result;
|
||||
end;
|
||||
|
||||
procedure TC64.LoadROM(Filename: String; Addr: Word);
|
||||
var
|
||||
Stream: TFileStream;
|
||||
begin
|
||||
Stream := TFileStream.Create(Filename, fmOpenRead);
|
||||
try
|
||||
Stream.Read(Memory[Addr], Stream.Size);
|
||||
finally
|
||||
Stream.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TC64.SetKey(Key: Char; Value: Byte);
|
||||
var
|
||||
KeyPos: Integer;
|
||||
begin
|
||||
KeyPos := Pos(Key, KEY_TRANSLATION) - 1;
|
||||
if KeyPos >= 0 then
|
||||
begin
|
||||
// always release last key on keypress
|
||||
if Value = 1 then
|
||||
begin
|
||||
SetKey(LastKey, 0);
|
||||
LastKey := Key;
|
||||
end;
|
||||
|
||||
if KeyPos > 63 then // set right shift on/off
|
||||
begin
|
||||
KeyMatrix[1, 4] := Value;
|
||||
Dec(KeyPos, 64);
|
||||
end;
|
||||
|
||||
KeyMatrix[KeyPos mod 8, KeyPos div 8] := Value;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TC64Thread }
|
||||
|
||||
constructor TC64Thread.Create(C64Instance: TC64);
|
||||
begin
|
||||
inherited Create(True);
|
||||
C64 := C64Instance;
|
||||
end;
|
||||
|
||||
procedure TC64Thread.Execute;
|
||||
begin
|
||||
while (not Terminated) do
|
||||
begin
|
||||
if C64.InterruptRequest then
|
||||
begin
|
||||
C64.InterruptRequest := False;
|
||||
C64.IRQ;
|
||||
end;
|
||||
C64.Step;
|
||||
Sleep(0);
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
16
C64/Source/C6Emu.dpr
Normal file
16
C64/Source/C6Emu.dpr
Normal file
@ -0,0 +1,16 @@
|
||||
program C6Emu;
|
||||
|
||||
uses
|
||||
Vcl.Forms,
|
||||
FormC64 in 'FormC64.pas' {FrmC64},
|
||||
C64 in 'C64.pas',
|
||||
MOS6502 in '..\..\Source\MOS6502.pas';
|
||||
|
||||
{$R *.res}
|
||||
|
||||
begin
|
||||
Application.Initialize;
|
||||
Application.MainFormOnTaskbar := True;
|
||||
Application.CreateForm(TFrmC64, FrmC64);
|
||||
Application.Run;
|
||||
end.
|
23
C64/Source/FormC64.dfm
Normal file
23
C64/Source/FormC64.dfm
Normal file
@ -0,0 +1,23 @@
|
||||
object FrmC64: TFrmC64
|
||||
Left = 0
|
||||
Top = 0
|
||||
BorderStyle = bsDialog
|
||||
Caption = 'C64Emu'
|
||||
ClientHeight = 310
|
||||
ClientWidth = 645
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -11
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = []
|
||||
KeyPreview = True
|
||||
OldCreateOrder = False
|
||||
Position = poScreenCenter
|
||||
OnCreate = FormCreate
|
||||
OnDestroy = FormDestroy
|
||||
OnKeyPress = FormKeyPress
|
||||
OnKeyUp = FormKeyUp
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 13
|
||||
end
|
96
C64/Source/FormC64.pas
Normal file
96
C64/Source/FormC64.pas
Normal file
@ -0,0 +1,96 @@
|
||||
unit FormC64;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
|
||||
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, C64;
|
||||
|
||||
const
|
||||
ScreenZoom = 2;
|
||||
ScreenWidth = 320 * ScreenZoom;
|
||||
ScreenHeight = 200 * ScreenZoom;
|
||||
|
||||
ColorTable: array [0 .. 2] of Cardinal = ($801010, $D0A0A0, $D0A0A0);
|
||||
|
||||
type
|
||||
TFrmC64 = class(TForm)
|
||||
procedure FormCreate(Sender: TObject);
|
||||
procedure FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
|
||||
procedure FormKeyPress(Sender: TObject; var Key: Char);
|
||||
procedure FormDestroy(Sender: TObject);
|
||||
private
|
||||
{ Private declarations }
|
||||
procedure OnScreenWrite(var Msg: TMessage); message WM_SCREEN_WRITE;
|
||||
public
|
||||
{ Public declarations }
|
||||
LastKey: Char;
|
||||
C64: TC64;
|
||||
end;
|
||||
|
||||
var
|
||||
FrmC64: TFrmC64;
|
||||
|
||||
implementation
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
procedure TFrmC64.FormCreate(Sender: TObject);
|
||||
begin
|
||||
ClientWidth := ScreenWidth;
|
||||
ClientHeight := ScreenHeight;
|
||||
|
||||
Canvas.Font.Name := 'cbm';
|
||||
Canvas.Font.Height := 8 * ScreenZoom;
|
||||
Canvas.Brush.Style := bsSolid;
|
||||
|
||||
C64 := TC64.Create;
|
||||
C64.WndHandle := Handle;
|
||||
C64.LoadROM('..\ROMs\basic.901226-01.bin', $A000);
|
||||
C64.LoadROM('..\ROMs\kernal.901227-03.bin', $E000);
|
||||
C64.Exec;
|
||||
end;
|
||||
|
||||
procedure TFrmC64.FormDestroy(Sender: TObject);
|
||||
begin
|
||||
C64.Free;
|
||||
end;
|
||||
|
||||
procedure TFrmC64.FormKeyPress(Sender: TObject; var Key: Char);
|
||||
begin
|
||||
C64.SetKey(Key, 1);
|
||||
LastKey := Key;
|
||||
end;
|
||||
|
||||
procedure TFrmC64.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
|
||||
begin
|
||||
if LastKey <> #0 then
|
||||
C64.SetKey(LastKey, 0);
|
||||
LastKey := #0;
|
||||
end;
|
||||
|
||||
procedure TFrmC64.OnScreenWrite(var Msg: TMessage);
|
||||
var
|
||||
Addr: Word;
|
||||
Value: Integer;
|
||||
X, Y: Integer;
|
||||
Flag: Cardinal;
|
||||
Q1: Byte;
|
||||
Sc: Char;
|
||||
begin
|
||||
Addr := Msg.WParam;
|
||||
Value := Msg.LParam;
|
||||
|
||||
Y := (Addr div 40);
|
||||
X := (Addr - Y*40);
|
||||
|
||||
Flag := (Value shr 7) and $FF;
|
||||
Q1 := Value and $7F;
|
||||
Sc := Char((Q1 + 32 * (ord(Q1 < 32) * 2 + ord(Q1 > 63) + ord(Q1 > 95))));
|
||||
|
||||
Canvas.Font.Color := ColorTable[1 - Flag];
|
||||
Canvas.Brush.Color := ColorTable[Flag];
|
||||
Canvas.TextOut(X * (8*ScreenZoom), Y * (8*ScreenZoom), Sc);
|
||||
end;
|
||||
|
||||
end.
|
26
README.md
26
README.md
@ -1 +1,27 @@
|
||||
##MOS6502 Emulator in Delphi##
|
||||
|
||||
This is the Delphi / Pascal port of the C++ implementation for the MOS Technology 6502 CPU by [Gianluca Ghettini](https://github.com/gianlucag/mos6502). The code is written to be more readable than fast, however some minor tricks have been introduced to greatly reduce the overall execution time.
|
||||
|
||||
Main features:
|
||||
|
||||
* 100% coverage of legal opcodes
|
||||
* decimal mode implemented
|
||||
* read/write bus callback
|
||||
* jump table opcode selection
|
||||
|
||||
# mos6502-delphi
|
||||
|
||||
The port was written with minor changes to the original file: the run(N) function was replaced by a single Step() function - if you need to run more than one cycle, just put that function inside a loop.
|
||||
|
||||
# 6502 functional test
|
||||
|
||||
The [6502 functional test](https://github.com/Klaus2m5/6502_65C02_functional_tests) (version 16-aug-2013) by Klaus Dormann is included.
|
||||
|
||||
# C64 emulator
|
||||
|
||||
A very basic C64 emulator is included. You need to download the BASIC ROM [basic.901226-01.bin](http://www.commodore.ca/manuals/funet/cbm/firmware/computers/c64/basic.901226-01.bin) and the Kernal ROM [kernal.901227-03.bin](http://www.commodore.ca/manuals/funet/cbm/firmware/computers/c64/kernal.901227-03.bin) and put both files inside the ROMs folder. Install the commodore [CBM.ttf](https://github.com/bobsummerwill/VICE/raw/master/data/fonts/CBM.ttf) font found in the VICE package.
|
||||
|
||||
The C64 Emulator uses a symbolic keyboard translation thus any keyboard layout should work.
|
||||
|
||||
This C64 emulator is just a a very basic 6502/6510 emulation example and is not feature complete. Please take a look at [VICE - Versatile Commodore Emulator](http://vice-emu.sourceforge.net/) instead.
|
||||
|
||||
|
1610
Source/MOS6502.pas
Normal file
1610
Source/MOS6502.pas
Normal file
File diff suppressed because it is too large
Load Diff
116
Testsuite/Source/TestSuite.dpr
Normal file
116
Testsuite/Source/TestSuite.dpr
Normal file
@ -0,0 +1,116 @@
|
||||
program TestSuite;
|
||||
|
||||
{$APPTYPE CONSOLE}
|
||||
|
||||
{$R *.res}
|
||||
|
||||
uses
|
||||
System.Classes, System.SysUtils,
|
||||
MOS6502 in '..\..\Source\MOS6502.pas';
|
||||
|
||||
type
|
||||
TMOS6502TestSuite = class(TMOS6502)
|
||||
protected
|
||||
procedure BusWrite(Adr: Word; Value: Byte);
|
||||
function BusRead(Adr: Word): Byte;
|
||||
public
|
||||
Memory: PByte;
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
procedure RunTest(EndAddress: Word);
|
||||
procedure Load(Filename: String);
|
||||
end;
|
||||
|
||||
{ TMOS6502TestSuite }
|
||||
|
||||
function TMOS6502TestSuite.BusRead(Adr: Word): Byte;
|
||||
begin
|
||||
Result := Memory[Adr];
|
||||
end;
|
||||
|
||||
procedure TMOS6502TestSuite.BusWrite(Adr: Word; Value: Byte);
|
||||
begin
|
||||
Memory[Adr] := Value;
|
||||
end;
|
||||
|
||||
constructor TMOS6502TestSuite.Create;
|
||||
begin
|
||||
inherited Create(BusRead, BusWrite);
|
||||
|
||||
// create memory
|
||||
GetMem(Memory, 65536);
|
||||
end;
|
||||
|
||||
destructor TMOS6502TestSuite.Destroy;
|
||||
begin
|
||||
FreeMem(Memory);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMOS6502TestSuite.Load(Filename: String);
|
||||
var
|
||||
Stream: TFileStream;
|
||||
begin
|
||||
Stream := TFileStream.Create(Filename, fmOpenRead);
|
||||
Stream.Read(Memory[0], Stream.Size);
|
||||
Stream.Free;
|
||||
end;
|
||||
|
||||
procedure TMOS6502TestSuite.RunTest(EndAddress: Word);
|
||||
var
|
||||
LastPc: Word;
|
||||
LastTest: Byte;
|
||||
begin
|
||||
// reset (jump to $0400)
|
||||
Reset;
|
||||
|
||||
LastTest := $FF;
|
||||
repeat
|
||||
LastPc := Pc;
|
||||
if Memory[$200] <> LastTest then
|
||||
begin
|
||||
LastTest := Memory[$200];
|
||||
Writeln('test case ' + LastTest.ToString + ' at $' +IntToHex(Pc, 4));
|
||||
end;
|
||||
|
||||
// Run 1 instruction
|
||||
Step;
|
||||
until (IllegalOpcode) or (Pc = LastPC) or (Pc = EndAddress);
|
||||
|
||||
if Pc = EndAddress then
|
||||
writeln('test successful')
|
||||
else
|
||||
writeln('failed at ' + IntToHex(Pc, 4));
|
||||
end;
|
||||
|
||||
var
|
||||
MOS6502: TMOS6502TestSuite;
|
||||
|
||||
begin
|
||||
try
|
||||
MOS6502 := TMOS6502TestSuite.Create;
|
||||
try
|
||||
// load test bin
|
||||
MOS6502.Load('..\Test-Files\6502_functional_test.bin');
|
||||
|
||||
// set reset vectors to $0400
|
||||
MOS6502.Memory[$FFFC] := 0;
|
||||
MOS6502.Memory[$FFFD] := 4;
|
||||
|
||||
|
||||
// and run test suite, if PC reaches $3399 then test is successful
|
||||
MOS6502.RunTest($3399);
|
||||
|
||||
Readln;
|
||||
|
||||
finally
|
||||
MOS6502.Free;
|
||||
end;
|
||||
|
||||
except
|
||||
on E: Exception do
|
||||
Writeln(E.ClassName, ': ', E.Message);
|
||||
end;
|
||||
end.
|
||||
|
BIN
Testsuite/Test-Files/6502_functional_test.bin
Normal file
BIN
Testsuite/Test-Files/6502_functional_test.bin
Normal file
Binary file not shown.
13999
Testsuite/Test-Files/6502_functional_test.lst
Normal file
13999
Testsuite/Test-Files/6502_functional_test.lst
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user