Thingamajig/emulator.pas

289 lines
8.9 KiB
Plaintext

program Emulator;
{$MODE OBJFPC}
uses SysUtils, Crt;
const
IO = $ffff;
var
Hlt, ASCII: boolean; //Halt and ASCII flags
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
Prog{$ifdef printer}, Prn{$endif}{$ifdef tape}, TapeIn, TapeOut{$endif}: file of byte; //Program file, line printer, and punched tape reader and punch
{$ifdef tape}
TapeInFile: string; //Punched tape reader source file
TapeInPos: integer; //Punched tape reader position pointer
{$endif}
Ch, Scan: ansichar; //Character for input and output and scancode for non-ASCII keys
begin
//Initialise the halt flag and the pointers
Hlt := false;
IP := 0;
RP := $fff0;
{$ifdef tape}
TapeInFile := ' ';
TapeInPos := 0;
{$endif}
//Read a program file and check for errors
if ParamCount <> 1 then begin
writeln ('Usage: emulator program');
halt;
end;
{$i-}
assign (Prog, ParamStr (1));
reset (Prog);
{$i+}
if IOResult <> 0 then begin
writeln ('Error: program file cannot be read from');
halt;
end;
repeat
read (Prog, Mem [IP]);
IP := IP + 1;
until (eof (Prog)) or (IP = $fff0);
if IP = $fff0 then begin
writeln ('Error: memory overflow');
halt;
end;
//Reinitialise the instruction pointer
IP := 0;
//Begin the main loop
while Hlt = false do begin
//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;
if IP > $ffef then begin
writeln ('Error: illegal instruction pointer value');
halt;
end;
//Address argument
if Op >= $a then begin
//High byte
Addr := Mem [IP];
Addr := Addr shl 8;
IP := IP + 1;
if IP > $ffef then begin
writeln ('Error: illegal instruction pointer value');
halt;
end;
//Low byte
Addr := Addr + Mem [IP];
IP := IP + 1;
if IP > $ffef then begin
writeln ('Error: illegal instruction pointer value');
halt;
end;
end;
//Decode and execute the instruction
//Halt
if Op = 0 then Hlt := true
//Ret
else if Op = 1 then begin
//High byte of the return address
IP := Mem [RP];
IP := IP shl 8;
RP := RP + 1;
if RP > $fff0 then begin
writeln ('Error: illegal return pointer value');
halt;
end;
//Low byte of the return address
IP := IP + Mem [RP];
RP := RP + 1;
if RP > $fff0 then begin
writeln ('Error: illegal return pointer value');
halt;
end;
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
//Terminal input
if Addr = IO then begin
//Read a keypress
repeat
Ch := ReadKey;
//Check for non-ASCII keys
if Ch = ansichar (0) then begin
//Non-ASCII
if keypressed then begin
Scan := ReadKey;
{$ifdef tape}
//Insert a tape in the tape reader
if Scan = ansichar ($52) then begin
writeln ();
write ('Tape to be read: ');
readln (TapeInFile);
if TapeInFile = '' then TapeInFile := ' ';
TapeInPos := 0
end;
{$endif}
ASCII := false;
end
//Null
else ASCII := true;
end
//Other ASCII
else ASCII := true;
until ASCII = true;
//Process the keypress
write (Ch); //Local echo
R [X] := byte (Ch);
end
//Tape reader
{$ifdef tape}
else if Addr = $fffd then begin
assign (TapeIn, TapeInFile);
try
reset (TapeIn);
seek (TapeIn, TapeInPos);
read (TapeIn, R [X]);
TapeInPos := TapeInPos + 1;
close (TapeIn);
except
R [X] := 0;
end;
end
{$endif}
//Regular load
else R [X] := Mem [Addr];
end
//Store
else if Op = $b then begin
//Terminal output
if Addr = IO then begin
Ch := ansichar (R [X]);
write (Ch);
end
//Printer
{$ifdef printer}
else if Addr = $fffe then begin
assign (Prn, '/dev/usb/lp0');
try
rewrite (Prn);
write (Prn, R [X]);
close (Prn);
except
end;
end
{$endif}
//Tape punch
{$ifdef tape}
else if Addr = $fffd then begin
assign (TapeOut, 'tapeout');
if FileExists ('tapeout') = false then begin
try
rewrite (TapeOut);
write (TapeOut, R [X]);
close (TapeOut);
except
end;
end
else begin
try
reset (TapeOut);
seek (TapeOut, FileSize (TapeOut));
write (TapeOut, R [X]);
close (TapeOut);
except
end;
end;
end
{$endif}
//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
//Low byte of the return address
RP := RP - 1;
if RP > $fff0 then begin
writeln ('Error: illegal return pointer value');
halt;
end;
Mem [RP] := IP and $ff;
//High byte of the return address
RP := RP - 1;
if RP > $fff0 then begin
writeln ('Error: illegal return pointer value');
halt;
end;
Mem [RP] := IP shr 8;
//Call
IP := Addr;
if IP > $ffef then begin
writeln ('Error: illegal instruction pointer value');
halt;
end;
end;
end
//Clneq
else if Op = $f then begin
if R [X] <> R [Y] then begin
//Low byte of the return address
RP := RP - 1;
if RP > $fff0 then begin
writeln ('Error: illegal return pointer value');
halt;
end;
Mem [RP] := IP and $ff;
//High byte of the return address
RP := RP - 1;
if RP > $fff0 then begin
writeln ('Error: illegal return pointer value');
halt;
end;
Mem [RP] := IP shr 8;
//Call
IP := Addr;
if IP > $ffef then begin
writeln ('Error: illegal instruction pointer value');
halt;
end;
end;
end;
end;
end.