Initial VIC-20 emulator based on C64 source

This commit is contained in:
SteveNew 2017-03-18 22:05:49 +01:00 committed by GitHub
parent 08b496e423
commit 097fd41cbb
6 changed files with 379 additions and 27 deletions

View File

@ -25,3 +25,7 @@ The C64 Emulator uses a symbolic keyboard translation thus any keyboard layout s
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
View File

@ -0,0 +1 @@
Put your BASIC and Kernal ROMs here

23
VIC-20/Source/FormV20.dfm Normal file
View 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
View 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
View 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.

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