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.