mirror of
https://github.com/Dennis1000/mos6502-delphi.git
synced 2024-12-21 10:30:30 +00:00
Initial VIC-20 emulator based on C64 source
This commit is contained in:
parent
08b496e423
commit
097fd41cbb
58
README.md
58
README.md
@ -1,27 +1,31 @@
|
|||||||
##MOS6502 Emulator in Delphi##
|
##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.
|
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:
|
Main features:
|
||||||
|
|
||||||
* 100% coverage of legal opcodes
|
* 100% coverage of legal opcodes
|
||||||
* decimal mode implemented
|
* decimal mode implemented
|
||||||
* read/write bus callback
|
* read/write bus callback
|
||||||
* jump table opcode selection
|
* jump table opcode selection
|
||||||
|
|
||||||
# mos6502-delphi
|
# 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.
|
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
|
# 6502 functional test
|
||||||
|
|
||||||
The [6502 functional test](https://github.com/Klaus2m5/6502_65C02_functional_tests) (version 16-aug-2013) by Klaus Dormann is included.
|
The [6502 functional test](https://github.com/Klaus2m5/6502_65C02_functional_tests) (version 16-aug-2013) by Klaus Dormann is included.
|
||||||
|
|
||||||
# C64 emulator
|
# 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.
|
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.
|
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.
|
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.
|
||||||
|
|
||||||
|
# VIC-20 emulator
|
||||||
|
|
||||||
|
Based on the C64 emulator, a VIC-20 emulator is now included. You need to download the BASIC ROM [basic.901486-01.bin](http://www.commodore.ca/manuals/funet/cbm/firmware/computers/vic20/basic.901486-01.bin) and the Kernal ROM [kernal.901486-07.bin](http://www.commodore.ca/manuals/funet/cbm/firmware/computers/vic20/kernal.901486-07.bin) and put both files inside the ROMs folder. Changes compared to the C64 source are Addr and Keyboard matrix.
|
||||||
|
|
||||||
|
1
VIC-20/ROMs/README.md
Normal file
1
VIC-20/ROMs/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Put your BASIC and Kernal ROMs here
|
23
VIC-20/Source/FormV20.dfm
Normal file
23
VIC-20/Source/FormV20.dfm
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
object FrmVC20: TFrmVC20
|
||||||
|
Left = 0
|
||||||
|
Top = 0
|
||||||
|
BorderStyle = bsDialog
|
||||||
|
Caption = 'VIC-20 Emu'
|
||||||
|
ClientHeight = 393
|
||||||
|
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
VIC-20/Source/FormV20.pas
Normal file
96
VIC-20/Source/FormV20.pas
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
unit FormV20;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
|
||||||
|
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VIC20, Vcl.StdCtrls, Vcl.ExtCtrls;
|
||||||
|
|
||||||
|
const
|
||||||
|
ScreenZoom = 2;
|
||||||
|
ScreenWidth = 176 * ScreenZoom; // 320
|
||||||
|
ScreenHeight = 184 * ScreenZoom; // 200
|
||||||
|
|
||||||
|
ColorTable: array [0 .. 2] of Cardinal = ($801010, $D0A0A0, $D0A0A0);
|
||||||
|
|
||||||
|
type
|
||||||
|
TFrmVC20 = 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;
|
||||||
|
VC20: TVC20;
|
||||||
|
end;
|
||||||
|
|
||||||
|
var
|
||||||
|
FrmVC20: TFrmVC20;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{$R *.dfm}
|
||||||
|
|
||||||
|
procedure TFrmVC20.FormCreate(Sender: TObject);
|
||||||
|
begin
|
||||||
|
ClientWidth := ScreenWidth;
|
||||||
|
ClientHeight := ScreenHeight;
|
||||||
|
|
||||||
|
Canvas.Font.Name := 'cbm';
|
||||||
|
Canvas.Font.Height := 8 * ScreenZoom;
|
||||||
|
Canvas.Brush.Style := bsSolid;
|
||||||
|
|
||||||
|
VC20 := TVC20.Create;
|
||||||
|
VC20.WndHandle := Handle;
|
||||||
|
VC20.LoadROM('..\ROMs\kernal.901486-06.bin', $E000); // E000-FFFF
|
||||||
|
VC20.LoadROM('..\ROMs\basic.901486-01.bin', $C000); // C000-DFFF
|
||||||
|
VC20.Exec;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TFrmVC20.FormDestroy(Sender: TObject);
|
||||||
|
begin
|
||||||
|
VC20.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TFrmVC20.FormKeyPress(Sender: TObject; var Key: Char);
|
||||||
|
begin
|
||||||
|
VC20.SetKey(Key, 1);
|
||||||
|
LastKey := Key;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TFrmVC20.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
|
||||||
|
begin
|
||||||
|
if LastKey <> #0 then
|
||||||
|
VC20.SetKey(LastKey, 0);
|
||||||
|
LastKey := #0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TFrmVC20.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 22); //40
|
||||||
|
X := (Addr - Y*22); //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.
|
212
VIC-20/Source/VIC20.pas
Normal file
212
VIC-20/Source/VIC20.pas
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
unit VIC20;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.Classes, WinApi.Messages, MOS6502;
|
||||||
|
|
||||||
|
const
|
||||||
|
WM_SCREEN_WRITE = WM_USER + 0;
|
||||||
|
|
||||||
|
CIA1 = $9120; // $DC00;
|
||||||
|
|
||||||
|
// VIC-20 keyboard matrix
|
||||||
|
KEY_TRANSLATION = '2q'#0' '#27#0#0'1'+
|
||||||
|
'4esz'#0'aw3'+
|
||||||
|
'6tfcxdr5'+
|
||||||
|
'8uhbvgy7'+
|
||||||
|
'0okmnji9'+
|
||||||
|
'-@:.,lp+'+
|
||||||
|
#0#0'='#0'/;*£'+
|
||||||
|
#0#0#0#0#0#0#13#8+
|
||||||
|
|
||||||
|
'"'#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'[><l'#0#0+
|
||||||
|
#0#0'='#0'?]'#0#0+
|
||||||
|
#0#0#0#0#0#0#13#8;
|
||||||
|
|
||||||
|
type
|
||||||
|
TVC20 = class;
|
||||||
|
|
||||||
|
TVC20Thread = class(TThread)
|
||||||
|
private
|
||||||
|
VC20: TVC20;
|
||||||
|
protected
|
||||||
|
public
|
||||||
|
procedure Execute; override;
|
||||||
|
constructor Create(VC20Instance: TVC20);
|
||||||
|
end;
|
||||||
|
|
||||||
|
TVC20 = class(TMOS6502)
|
||||||
|
private
|
||||||
|
Thread: TVC20Thread;
|
||||||
|
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;
|
||||||
|
|
||||||
|
{ TVC20 }
|
||||||
|
|
||||||
|
procedure TimerProcedure(TimerID, Msg: Uint; dwUser, dw1, dw2: DWORD); pascal;
|
||||||
|
var
|
||||||
|
VC20: TVC20;
|
||||||
|
begin
|
||||||
|
VC20 := TVC20(dwUser);
|
||||||
|
|
||||||
|
if VC20.Status and VC20.INTERRUPT = 0 then // if IRQ allowed then set irq
|
||||||
|
VC20.InterruptRequest := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TVC20.BusRead(Adr: Word): Byte;
|
||||||
|
begin
|
||||||
|
Result := Memory[Adr];
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVC20.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 >= $2000) then // $A000 // treat anything above as ROM
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
Memory[Adr] := Value;
|
||||||
|
|
||||||
|
// video RAM
|
||||||
|
if (Adr >= $1E00) and (Adr <= $1FF9) then // $400 - $07E7
|
||||||
|
PostMessage(WndHandle, WM_SCREEN_WRITE, Adr - $1E00, Value); // $400
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor TVC20.Create;
|
||||||
|
begin
|
||||||
|
inherited Create(BusRead, BusWrite);
|
||||||
|
|
||||||
|
// create 64kB memory table
|
||||||
|
GetMem(Memory, 65536);
|
||||||
|
|
||||||
|
Thread := TVC20Thread.Create(Self);
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TVC20.Destroy;
|
||||||
|
begin
|
||||||
|
if TimerHandle <> 0 then
|
||||||
|
Timekillevent(TimerHandle);
|
||||||
|
Thread.Terminate;
|
||||||
|
Thread.WaitFor;
|
||||||
|
FreeMem(Memory);
|
||||||
|
inherited;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVC20.Exec;
|
||||||
|
begin
|
||||||
|
Reset;
|
||||||
|
Thread.Start;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TVC20.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 TVC20.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 TVC20.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[3, 6] := Value; // 1, 4
|
||||||
|
Dec(KeyPos, 64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
KeyMatrix[KeyPos mod 8, KeyPos div 8] := Value;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TVC20Thread }
|
||||||
|
|
||||||
|
constructor TVC20Thread.Create(VC20Instance: TVC20);
|
||||||
|
begin
|
||||||
|
inherited Create(True);
|
||||||
|
VC20 := VC20Instance;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVC20Thread.Execute;
|
||||||
|
begin
|
||||||
|
while (not Terminated) do
|
||||||
|
begin
|
||||||
|
if VC20.InterruptRequest then
|
||||||
|
begin
|
||||||
|
VC20.InterruptRequest := False;
|
||||||
|
VC20.IRQ;
|
||||||
|
end;
|
||||||
|
VC20.Step;
|
||||||
|
Sleep(0);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
16
VIC-20/Source/VIC20Emu.dpr
Normal file
16
VIC-20/Source/VIC20Emu.dpr
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
program VIC20Emu;
|
||||||
|
|
||||||
|
uses
|
||||||
|
Vcl.Forms,
|
||||||
|
FormV20 in 'FormV20.pas' {FrmVC20},
|
||||||
|
VIC20 in 'VIC20.pas',
|
||||||
|
MOS6502 in '..\..\Source\MOS6502.pas';
|
||||||
|
|
||||||
|
{$R *.res}
|
||||||
|
|
||||||
|
begin
|
||||||
|
Application.Initialize;
|
||||||
|
Application.MainFormOnTaskbar := True;
|
||||||
|
Application.CreateForm(TFrmVC20, FrmVC20);
|
||||||
|
Application.Run;
|
||||||
|
end.
|
Loading…
Reference in New Issue
Block a user