2022-07-23 20:05:32 +00:00
|
|
|
program Emulator;
|
|
|
|
|
2022-08-11 07:34:49 +00:00
|
|
|
{$MODE OBJFPC}
|
|
|
|
|
2022-08-14 09:54:37 +00:00
|
|
|
uses SysUtils, Crt;
|
2022-07-23 20:05:32 +00:00
|
|
|
|
2022-08-18 10:55:38 +00:00
|
|
|
{$ifdef tape}
|
|
|
|
type
|
|
|
|
//Tape file path and reset state
|
|
|
|
Tape = record
|
|
|
|
Path: shortstring;
|
|
|
|
Reset: boolean;
|
|
|
|
Pos: integer;
|
|
|
|
end;
|
|
|
|
{$endif}
|
|
|
|
|
2022-07-23 20:05:32 +00:00
|
|
|
const
|
|
|
|
IO = $ffff;
|
|
|
|
|
|
|
|
var
|
2022-08-15 14:21:01 +00:00
|
|
|
Hlt, ASCII: boolean; //Halt and ASCII flags
|
2022-07-23 20:05:32 +00:00
|
|
|
Op: 0 .. $f; //Opcode
|
|
|
|
X, Y: 0 .. 3; //Register arguments
|
|
|
|
Addr, IP, RP: word; //Address argument and instruction and return pointers
|
|
|
|
R: array [0 .. 3] of byte; //General-purpose registers
|
|
|
|
Mem: array [0 .. $ffef] of byte; //Memory
|
2022-08-18 10:55:38 +00:00
|
|
|
Prog{$ifdef printer}, Prn{$endif}{$ifdef tape}, TapeIn, TapeOut{$endif}: file of byte; //Program file, line printer, and tape reader and punch tapes
|
2022-08-14 09:54:37 +00:00
|
|
|
{$ifdef tape}
|
2022-08-18 10:55:38 +00:00
|
|
|
Reader, Punch: Tape; //States of the tape reader and punch
|
|
|
|
State: file of Tape; //File storing the states of the tape reader and punch
|
2022-08-14 09:54:37 +00:00
|
|
|
{$endif}
|
2022-08-15 14:21:01 +00:00
|
|
|
Ch, Scan: ansichar; //Character for input and output and scancode for non-ASCII keys
|
2022-07-23 20:05:32 +00:00
|
|
|
|
2022-08-21 07:27:02 +00:00
|
|
|
//Terminal output
|
|
|
|
procedure Output;
|
|
|
|
begin
|
|
|
|
//Do not output most of the control codes
|
|
|
|
if Ch <= ansichar ($1f) then begin
|
|
|
|
if Ch = ansichar (7) then write (Ch) //Bell
|
|
|
|
else if Ch = ansichar (8) then write (Ch) //Backspace
|
|
|
|
else if Ch = ansichar ($a) then write (Ch) //Line feed
|
|
|
|
else if Ch = ansichar ($d) then write (Ch) //Carriage return
|
|
|
|
else write (''); //Others
|
|
|
|
end
|
|
|
|
else if Ch = ansichar ($7f) then write ('') //Delete
|
|
|
|
//Output all regular characters
|
|
|
|
else write (Ch);
|
|
|
|
end;
|
|
|
|
|
2022-07-23 20:05:32 +00:00
|
|
|
begin
|
|
|
|
|
|
|
|
//Initialise the halt flag and the pointers
|
2022-07-31 11:09:15 +00:00
|
|
|
Hlt := false;
|
2022-07-23 20:05:32 +00:00
|
|
|
IP := 0;
|
|
|
|
RP := $fff0;
|
2022-08-18 10:55:38 +00:00
|
|
|
|
|
|
|
//Initialise the tape reader and punch
|
2022-08-14 09:54:37 +00:00
|
|
|
{$ifdef tape}
|
2022-08-18 10:55:38 +00:00
|
|
|
Reader.Path := '';
|
|
|
|
Reader.Reset := true;
|
|
|
|
Reader.Pos := 0;
|
|
|
|
Punch.Path := '';
|
|
|
|
Punch.Reset := true;
|
|
|
|
Punch.Pos := 0;
|
2022-08-14 09:54:37 +00:00
|
|
|
{$endif}
|
2022-07-23 20:05:32 +00:00
|
|
|
|
|
|
|
//Read a program file and check for errors
|
|
|
|
if ParamCount <> 1 then begin
|
|
|
|
writeln ('Usage: emulator program');
|
2022-07-31 11:09:15 +00:00
|
|
|
halt;
|
2022-07-23 20:05:32 +00:00
|
|
|
end;
|
|
|
|
{$i-}
|
|
|
|
assign (Prog, ParamStr (1));
|
|
|
|
reset (Prog);
|
|
|
|
{$i+}
|
|
|
|
if IOResult <> 0 then begin
|
2022-07-31 11:09:15 +00:00
|
|
|
writeln ('Error: program file cannot be read from');
|
|
|
|
halt;
|
2022-07-23 20:05:32 +00:00
|
|
|
end;
|
|
|
|
repeat
|
|
|
|
read (Prog, Mem [IP]);
|
|
|
|
IP := IP + 1;
|
|
|
|
until (eof (Prog)) or (IP = $fff0);
|
2022-07-31 18:58:23 +00:00
|
|
|
if IP = $fff0 then begin
|
|
|
|
writeln ('Error: memory overflow');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-23 20:05:32 +00:00
|
|
|
|
|
|
|
//Reinitialise the instruction pointer
|
|
|
|
IP := 0;
|
|
|
|
|
|
|
|
//Begin the main loop
|
2022-07-31 11:09:15 +00:00
|
|
|
while Hlt = false do begin
|
2022-07-23 20:05:32 +00:00
|
|
|
|
|
|
|
//Fetch the instruction and increment the instruction pointer
|
|
|
|
//Opcode
|
|
|
|
Op := Mem [IP] and $f0 shr 4;
|
|
|
|
//Register arguments
|
|
|
|
X := Mem [IP] and $c shr 2;
|
|
|
|
Y := Mem [IP] and 3;
|
|
|
|
IP := IP + 1;
|
2022-07-31 11:09:15 +00:00
|
|
|
if IP > $ffef then begin
|
|
|
|
writeln ('Error: illegal instruction pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-23 20:05:32 +00:00
|
|
|
//Address argument
|
|
|
|
if Op >= $a then begin
|
2022-07-27 14:11:09 +00:00
|
|
|
//High byte
|
2022-07-23 20:05:32 +00:00
|
|
|
Addr := Mem [IP];
|
|
|
|
Addr := Addr shl 8;
|
|
|
|
IP := IP + 1;
|
2022-07-31 11:09:15 +00:00
|
|
|
if IP > $ffef then begin
|
|
|
|
writeln ('Error: illegal instruction pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-27 14:11:09 +00:00
|
|
|
//Low byte
|
2022-07-23 20:05:32 +00:00
|
|
|
Addr := Addr + Mem [IP];
|
|
|
|
IP := IP + 1;
|
2022-07-31 11:09:15 +00:00
|
|
|
if IP > $ffef then begin
|
|
|
|
writeln ('Error: illegal instruction pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-23 20:05:32 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
//Decode and execute the instruction
|
|
|
|
//Halt
|
2022-07-31 11:09:15 +00:00
|
|
|
if Op = 0 then Hlt := true
|
2022-07-23 20:05:32 +00:00
|
|
|
//Ret
|
|
|
|
else if Op = 1 then begin
|
2022-07-27 14:11:09 +00:00
|
|
|
//High byte of the return address
|
2022-07-23 20:05:32 +00:00
|
|
|
IP := Mem [RP];
|
2022-07-27 14:11:09 +00:00
|
|
|
IP := IP shl 8;
|
|
|
|
RP := RP + 1;
|
2022-07-31 11:09:15 +00:00
|
|
|
if RP > $fff0 then begin
|
|
|
|
writeln ('Error: illegal return pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-27 14:11:09 +00:00
|
|
|
//Low byte of the return address
|
|
|
|
IP := IP + Mem [RP];
|
2022-07-23 20:05:32 +00:00
|
|
|
RP := RP + 1;
|
2022-07-31 11:09:15 +00:00
|
|
|
if RP > $fff0 then begin
|
|
|
|
writeln ('Error: illegal return pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-23 20:05:32 +00:00
|
|
|
end
|
|
|
|
//Shl
|
|
|
|
else if Op = 2 then R [X] := R [X] shl 1
|
|
|
|
//Shr
|
|
|
|
else if Op = 3 then R [X] := R [X] shr 1
|
|
|
|
//Rol
|
|
|
|
else if Op = 4 then R [X] := RolByte (R [X])
|
|
|
|
//Ror
|
|
|
|
else if Op = 5 then R [X] := RorByte (R [X])
|
|
|
|
//Nand
|
|
|
|
else if Op = 6 then R [X] := not (R [X] and R [Y])
|
|
|
|
//And
|
|
|
|
else if Op = 7 then R [X] := R [X] and R [Y]
|
|
|
|
//Or
|
|
|
|
else if Op = 8 then R [X] := R [X] or R [Y]
|
|
|
|
//Xor
|
|
|
|
else if Op = 9 then R [X] := R [X] xor R [Y]
|
|
|
|
//Load
|
|
|
|
else if Op = $a then begin
|
2022-08-15 09:16:42 +00:00
|
|
|
//Terminal input
|
2022-07-23 20:05:32 +00:00
|
|
|
if Addr = IO then begin
|
2022-08-15 14:21:01 +00:00
|
|
|
//Read a keypress
|
|
|
|
repeat
|
2022-07-23 20:05:32 +00:00
|
|
|
Ch := ReadKey;
|
2022-08-15 14:21:01 +00:00
|
|
|
//Check for non-ASCII keys
|
|
|
|
if Ch = ansichar (0) then begin
|
|
|
|
//Non-ASCII
|
|
|
|
if keypressed then begin
|
|
|
|
Scan := ReadKey;
|
2022-08-16 10:35:39 +00:00
|
|
|
//The delete key inserts the delete character
|
|
|
|
if Scan = ansichar ($53) then begin
|
|
|
|
Ch := ansichar ($7f);
|
|
|
|
ASCII := true;
|
|
|
|
end
|
|
|
|
//Unused function keys insert a null
|
|
|
|
else ASCII := true;
|
2022-08-15 14:21:01 +00:00
|
|
|
end
|
|
|
|
//Null
|
|
|
|
else ASCII := true;
|
|
|
|
end
|
|
|
|
//Other ASCII
|
|
|
|
else ASCII := true;
|
|
|
|
until ASCII = true;
|
2022-08-16 10:35:39 +00:00
|
|
|
//Bodge for the home and end keys
|
|
|
|
if Ch = ansichar ($37) then begin
|
|
|
|
if keypressed then begin
|
|
|
|
Scan := ReadKey;
|
|
|
|
Scan := ReadKey;
|
|
|
|
Scan := ReadKey;
|
|
|
|
Ch := ansichar (0);
|
|
|
|
end;
|
|
|
|
end
|
|
|
|
else if Ch = ansichar ($38) then begin
|
|
|
|
if keypressed then begin
|
|
|
|
Scan := ReadKey;
|
|
|
|
Scan := ReadKey;
|
|
|
|
Scan := ReadKey;
|
|
|
|
Ch := ansichar (0);
|
|
|
|
end;
|
|
|
|
end;
|
2022-08-15 14:21:01 +00:00
|
|
|
//Process the keypress
|
2022-08-21 07:27:02 +00:00
|
|
|
Output; //Local echo
|
2022-07-23 20:05:32 +00:00
|
|
|
R [X] := byte (Ch);
|
|
|
|
end
|
2022-08-15 09:16:42 +00:00
|
|
|
//Tape reader
|
2022-08-14 09:54:37 +00:00
|
|
|
{$ifdef tape}
|
|
|
|
else if Addr = $fffd then begin
|
2022-08-18 10:55:38 +00:00
|
|
|
assign (State, ExpandFileName ('~/.tapes.thingamajig'));
|
|
|
|
//Check the reader state
|
|
|
|
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
|
|
|
|
try
|
|
|
|
reset (State);
|
|
|
|
read (State, Reader);
|
|
|
|
read (State, Punch);
|
|
|
|
close (State);
|
|
|
|
except
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
//Read
|
|
|
|
assign (TapeIn, Reader.Path);
|
2022-08-14 09:54:37 +00:00
|
|
|
try
|
|
|
|
reset (TapeIn);
|
2022-08-18 10:55:38 +00:00
|
|
|
seek (TapeIn, Reader.Pos);
|
2022-08-15 14:21:01 +00:00
|
|
|
read (TapeIn, R [X]);
|
2022-08-14 09:54:37 +00:00
|
|
|
close (TapeIn);
|
2022-08-18 10:55:38 +00:00
|
|
|
Reader.Pos := Reader.Pos + 1;
|
2022-08-14 09:54:37 +00:00
|
|
|
except
|
2022-08-19 19:29:19 +00:00
|
|
|
R [X] := $ff;
|
2022-08-14 09:54:37 +00:00
|
|
|
end;
|
2022-08-18 10:55:38 +00:00
|
|
|
//Save the reader state
|
|
|
|
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
|
|
|
|
try
|
|
|
|
rewrite (State);
|
|
|
|
write (State, Reader);
|
|
|
|
write (State, Punch);
|
|
|
|
close (State);
|
|
|
|
except
|
|
|
|
end;
|
|
|
|
end;
|
2022-08-14 09:54:37 +00:00
|
|
|
end
|
|
|
|
{$endif}
|
2022-07-23 20:05:32 +00:00
|
|
|
//Regular load
|
|
|
|
else R [X] := Mem [Addr];
|
|
|
|
end
|
|
|
|
//Store
|
|
|
|
else if Op = $b then begin
|
2022-08-15 09:16:42 +00:00
|
|
|
//Terminal output
|
2022-07-23 20:05:32 +00:00
|
|
|
if Addr = IO then begin
|
|
|
|
Ch := ansichar (R [X]);
|
2022-08-21 07:27:02 +00:00
|
|
|
Output;
|
2022-07-23 20:05:32 +00:00
|
|
|
end
|
2022-08-15 09:16:42 +00:00
|
|
|
//Printer
|
2022-08-09 22:01:26 +00:00
|
|
|
{$ifdef printer}
|
|
|
|
else if Addr = $fffe then begin
|
2022-08-11 07:34:49 +00:00
|
|
|
assign (Prn, '/dev/usb/lp0');
|
|
|
|
try
|
|
|
|
rewrite (Prn);
|
|
|
|
write (Prn, R [X]);
|
|
|
|
close (Prn);
|
|
|
|
except
|
|
|
|
end;
|
2022-08-09 22:01:26 +00:00
|
|
|
end
|
|
|
|
{$endif}
|
2022-08-15 09:16:42 +00:00
|
|
|
//Tape punch
|
2022-08-14 09:54:37 +00:00
|
|
|
{$ifdef tape}
|
|
|
|
else if Addr = $fffd then begin
|
2022-08-18 10:55:38 +00:00
|
|
|
assign (State, ExpandFileName ('~/.tapes.thingamajig'));
|
|
|
|
//Check the punch state
|
|
|
|
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
|
2022-08-14 09:54:37 +00:00
|
|
|
try
|
2022-08-18 10:55:38 +00:00
|
|
|
reset (State);
|
|
|
|
read (State, Reader);
|
|
|
|
read (State, Punch);
|
|
|
|
close (State);
|
2022-08-14 09:54:37 +00:00
|
|
|
except
|
|
|
|
end;
|
2022-08-18 10:55:38 +00:00
|
|
|
end;
|
|
|
|
//Punch
|
|
|
|
if Punch.Path <> '' then begin
|
|
|
|
assign (TapeOut, Punch.Path);
|
2022-08-18 21:50:09 +00:00
|
|
|
if FileExists (Punch.Path) = false then begin
|
|
|
|
try
|
|
|
|
rewrite (TapeOut);
|
|
|
|
write (TapeOut, R [X]);
|
|
|
|
close (TapeOut);
|
|
|
|
Punch.Reset := false;
|
|
|
|
except
|
|
|
|
end;
|
|
|
|
end
|
|
|
|
else if Punch.Reset then begin
|
2022-08-18 10:55:38 +00:00
|
|
|
try
|
|
|
|
rewrite (TapeOut);
|
|
|
|
write (TapeOut, R [X]);
|
|
|
|
close (TapeOut);
|
|
|
|
Punch.Reset := false;
|
|
|
|
except
|
|
|
|
end;
|
|
|
|
end
|
|
|
|
else begin
|
|
|
|
try
|
|
|
|
reset (TapeOut);
|
|
|
|
seek (TapeOut, FileSize (TapeOut));
|
|
|
|
write (TapeOut, R [X]);
|
|
|
|
close (TapeOut);
|
|
|
|
except
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
//Save the punch state
|
|
|
|
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
|
2022-08-14 09:54:37 +00:00
|
|
|
try
|
2022-08-18 10:55:38 +00:00
|
|
|
rewrite (State);
|
|
|
|
write (State, Reader);
|
|
|
|
write (State, Punch);
|
|
|
|
close (State);
|
2022-08-14 09:54:37 +00:00
|
|
|
except
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end
|
|
|
|
{$endif}
|
2022-08-18 10:55:38 +00:00
|
|
|
|
2022-07-23 20:05:32 +00:00
|
|
|
//Regular store
|
|
|
|
else Mem [Addr] := R [X];
|
|
|
|
end
|
|
|
|
//Breq
|
|
|
|
else if Op = $c then begin
|
|
|
|
if R [X] = R [Y] then IP := Addr;
|
|
|
|
end
|
|
|
|
//Brneq
|
|
|
|
else if Op = $d then begin
|
|
|
|
if R [X] <> R [Y] then IP := Addr;
|
|
|
|
end
|
|
|
|
//Cleq
|
|
|
|
else if Op = $e then begin
|
|
|
|
if R [X] = R [Y] then begin
|
2022-07-27 14:11:09 +00:00
|
|
|
//Low byte of the return address
|
|
|
|
RP := RP - 1;
|
2022-07-31 11:09:15 +00:00
|
|
|
if RP > $fff0 then begin
|
|
|
|
writeln ('Error: illegal return pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-27 14:11:09 +00:00
|
|
|
Mem [RP] := IP and $ff;
|
|
|
|
//High byte of the return address
|
2022-07-23 20:05:32 +00:00
|
|
|
RP := RP - 1;
|
2022-07-31 11:09:15 +00:00
|
|
|
if RP > $fff0 then begin
|
|
|
|
writeln ('Error: illegal return pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-27 14:11:09 +00:00
|
|
|
Mem [RP] := IP shr 8;
|
|
|
|
//Call
|
2022-07-23 20:05:32 +00:00
|
|
|
IP := Addr;
|
2022-07-31 11:09:15 +00:00
|
|
|
if IP > $ffef then begin
|
|
|
|
writeln ('Error: illegal instruction pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-23 20:05:32 +00:00
|
|
|
end;
|
|
|
|
end
|
|
|
|
//Clneq
|
|
|
|
else if Op = $f then begin
|
|
|
|
if R [X] <> R [Y] then begin
|
2022-07-27 14:11:09 +00:00
|
|
|
//Low byte of the return address
|
|
|
|
RP := RP - 1;
|
2022-07-31 11:09:15 +00:00
|
|
|
if RP > $fff0 then begin
|
|
|
|
writeln ('Error: illegal return pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-27 14:11:09 +00:00
|
|
|
Mem [RP] := IP and $ff;
|
|
|
|
//High byte of the return address
|
2022-07-23 20:05:32 +00:00
|
|
|
RP := RP - 1;
|
2022-07-31 11:09:15 +00:00
|
|
|
if RP > $fff0 then begin
|
|
|
|
writeln ('Error: illegal return pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-27 14:11:09 +00:00
|
|
|
Mem [RP] := IP shr 8;
|
|
|
|
//Call
|
2022-07-23 20:05:32 +00:00
|
|
|
IP := Addr;
|
2022-07-31 11:09:15 +00:00
|
|
|
if IP > $ffef then begin
|
|
|
|
writeln ('Error: illegal instruction pointer value');
|
|
|
|
halt;
|
|
|
|
end;
|
2022-07-23 20:05:32 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2022-08-09 22:01:26 +00:00
|
|
|
end;
|
|
|
|
|
2022-07-23 20:05:32 +00:00
|
|
|
end.
|