Remove limits on the pointer values, specify access to unused addresses and modify instruction fetching and calling to take into account unused addresses and memory mapped devices in the emulator, and write a separate section for the emulator in the readme

This commit is contained in:
CrazyEttin 2022-08-26 14:36:42 +03:00
parent 3eba71ab0e
commit 6d14b9a5bf
2 changed files with 247 additions and 281 deletions

View File

@ -16,6 +16,7 @@ type
const const
IO = $ffff; IO = $ffff;
LastRAM = $ffef;
var var
Hlt, ASCII, Verbose: boolean; //Halt, ASCII, and verbose flags Hlt, ASCII, Verbose: boolean; //Halt, ASCII, and verbose flags
@ -23,7 +24,7 @@ var
X, Y: 0 .. 3; //Register arguments X, Y: 0 .. 3; //Register arguments
Addr, IP, RP: word; //Immediate or address argument and instruction and return pointers Addr, IP, RP: word; //Immediate or address argument and instruction and return pointers
R: array [0 .. 3] of byte; //General-purpose registers R: array [0 .. 3] of byte; //General-purpose registers
Mem: array [0 .. $ffef] of byte; //Memory Mem: array [0 .. LastRAM] of byte; //Random access memory
Prog{$ifdef printer}, Prn{$endif}{$ifdef tape}, TapeIn, TapeOut{$endif}: file of byte; //Program file, line printer, and tape reader and punch tapes Prog{$ifdef printer}, Prn{$endif}{$ifdef tape}, TapeIn, TapeOut{$endif}: file of byte; //Program file, line printer, and tape reader and punch tapes
{$ifdef tape} {$ifdef tape}
Reader, Punch: Tape; //States of the tape reader and punch Reader, Punch: Tape; //States of the tape reader and punch
@ -31,6 +32,7 @@ var
{$endif} {$endif}
Ch, Scan: ansichar; //Character for input and output and scancode for non-ASCII keys Ch, Scan: ansichar; //Character for input and output and scancode for non-ASCII keys
IC, LFX: integer; //Instruction counter for CPU speed IC, LFX: integer; //Instruction counter for CPU speed
Fetched: byte; //Fetched byte
//Terminal output //Terminal output
procedure Output; procedure Output;
@ -60,12 +62,204 @@ begin
IC := 0; IC := 0;
end; end;
//Load a byte from memory
function LoadByte (W: word): byte;
var
B: byte;
begin
//Terminal input
if W = IO then begin
wait;
//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;
//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;
end
//Null
else ASCII := true;
end
//Other ASCII
else ASCII := true;
until ASCII = true;
//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;
//Process the keypress
Output; //Local echo
B := byte (Ch);
end
//Tape reader
{$ifdef tape}
else if W = $fffd then begin
wait;
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);
try
reset (TapeIn);
seek (TapeIn, Reader.Pos);
read (TapeIn, B);
close (TapeIn);
Reader.Pos := Reader.Pos + 1;
except
B := $ff;
end;
//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;
end
{$endif}
//Unused addresses
else if W > LastRAM then B := 0
//Regular load
else B := Mem [W];
//Result
LoadByte := B;
end;
procedure StoreByte (W: word; B: byte);
begin
//Terminal output
if W = IO then begin
wait;
Ch := ansichar (B);
Output;
end
//Printer
{$ifdef printer}
else if W = $fffe then begin
wait;
assign (Prn, '/dev/usb/lp0');
try
rewrite (Prn);
write (Prn, B);
close (Prn);
except
end;
end
{$endif}
//Tape punch
{$ifdef tape}
else if W = $fffd then begin
wait;
assign (State, ExpandFileName ('~/.tapes.thingamajig'));
//Check the punch state
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
try
reset (State);
read (State, Reader);
read (State, Punch);
close (State);
except
end;
end;
//Punch
if Punch.Path <> '' then begin
assign (TapeOut, Punch.Path);
if FileExists (Punch.Path) = false then begin
try
rewrite (TapeOut);
write (TapeOut, B);
close (TapeOut);
Punch.Reset := false;
except
end;
end
else if Punch.Reset then begin
try
rewrite (TapeOut);
write (TapeOut, B);
close (TapeOut);
Punch.Reset := false;
except
end;
end
else begin
try
reset (TapeOut);
seek (TapeOut, FileSize (TapeOut));
write (TapeOut, B);
close (TapeOut);
except
end;
end;
end;
//Save the punch state
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
try
rewrite (State);
write (State, Reader);
write (State, Punch);
close (State);
except
end;
end;
end
{$endif}
//Regular store
else if W <= LastRAM then Mem [W] := B;
end;
procedure Call;
begin
//Low byte of the return address
RP := RP - 1;
StoreByte (RP, IP and $ff);
//High byte of the return address
RP := RP - 1;
StoreByte (RP, IP shr 8);
//Call
IP := Addr;
end;
begin begin
//Initialise the halt flag, the pointers, and the instruction counter //Initialise the halt flag, the pointers, and the instruction counter
Hlt := false; Hlt := false;
IP := 0; IP := 0;
RP := $fff0; RP := LastRAM + 1;
IC := 0; IC := 0;
//Initialise the tape reader and punch //Initialise the tape reader and punch
@ -103,6 +297,10 @@ begin
if Verbose = true then assign (Prog, ParamStr (2)) if Verbose = true then assign (Prog, ParamStr (2))
else assign (Prog, ParamStr (1)); else assign (Prog, ParamStr (1));
reset (Prog); reset (Prog);
if FileSize (Prog) > LastRAM + 1 then begin
writeln ('Error: program size cannot exceed ', LastRam + 1, ' bytes');
halt;
end;
{$i+} {$i+}
if IOResult <> 0 then begin if IOResult <> 0 then begin
writeln ('Error: program file cannot be read from'); writeln ('Error: program file cannot be read from');
@ -111,11 +309,7 @@ begin
repeat repeat
read (Prog, Mem [IP]); read (Prog, Mem [IP]);
IP := IP + 1; IP := IP + 1;
until (eof (Prog)) or (IP = $fff0); until (eof (Prog));
if IP = $fff0 then begin
writeln ('Error: memory overflow');
halt;
end;
//Reinitialise the instruction pointer //Reinitialise the instruction pointer
IP := 0; IP := 0;
@ -127,45 +321,34 @@ begin
if Verbose = true then writeln (StdErr, 'IR: ', IntToHex (Op, 1), IntToHex (Regs, 1), IntToHex (Addr, 4), '; IP: ', IntToHex (IP, 4), ', RP: ', IntToHex (RP, 4), '; R0: ', IntToHex (R[0], 2), ', R1: ', IntToHex (R[1], 2), ', R2: ', IntToHex (R[2], 2), ', R3: ', IntToHex (R[3], 2), ansichar ($d)); if Verbose = true then writeln (StdErr, 'IR: ', IntToHex (Op, 1), IntToHex (Regs, 1), IntToHex (Addr, 4), '; IP: ', IntToHex (IP, 4), ', RP: ', IntToHex (RP, 4), '; R0: ', IntToHex (R[0], 2), ', R1: ', IntToHex (R[1], 2), ', R2: ', IntToHex (R[2], 2), ', R3: ', IntToHex (R[3], 2), ansichar ($d));
//Fetch the instruction and increment the instruction pointer //Fetch the instruction and increment the instruction pointer
//Opcode //Fetch the opcode and register arguments
Op := Mem [IP] and $f0 shr 4; Fetched := LoadByte (IP);
//Register arguments //Decode the opcode
Regs := Mem [IP] and $f; Op := Fetched and $f0 shr 4;
X := Mem [IP] and $c shr 2; //Decode the register arguments
Y := Mem [IP] and 3; Regs := Fetched and $f;
X := Fetched and $c shr 2;
Y := Fetched and 3;
IP := IP + 1; IP := IP + 1;
if IP > $ffef then begin
writeln ('Error: illegal instruction pointer value');
halt;
end;
//Immediate or address argument //Immediate or address argument
if Op >= $a then begin if Op >= $a then begin
//Immediate or high byte of address //Immediate or high byte of address
Addr := Mem [IP]; Fetched := LoadByte (IP);
Addr := Fetched;
Addr := Addr shl 8; Addr := Addr shl 8;
IP := IP + 1; IP := IP + 1;
if IP > $ffef then begin
writeln ('Error: illegal instruction pointer value');
halt;
end;
//Low byte of address //Low byte of address
if Op = $a then begin if Op = $a then begin
if Y = 0 then begin if Y = 0 then begin
Addr := Addr + Mem [IP]; Fetched := LoadByte (IP);
Addr := Addr + Fetched;
IP := IP + 1; IP := IP + 1;
if IP > $ffef then begin
writeln ('Error: illegal instruction pointer value');
halt;
end;
end; end;
end end
else begin else begin
Addr := Addr + Mem [IP]; Fetched := LoadByte (IP);
Addr := Addr + Fetched;
IP := IP + 1; IP := IP + 1;
if IP > $ffef then begin
writeln ('Error: illegal instruction pointer value');
halt;
end;
end; end;
end end
else Addr := 0; else Addr := 0;
@ -176,20 +359,12 @@ begin
//Ret //Ret
else if Op = 1 then begin else if Op = 1 then begin
//High byte of the return address //High byte of the return address
IP := Mem [RP]; IP := LoadByte (RP);
IP := IP shl 8; IP := IP shl 8;
RP := RP + 1; RP := RP + 1;
if RP > $fff0 then begin
writeln ('Error: illegal return pointer value');
halt;
end;
//Low byte of the return address //Low byte of the return address
IP := IP + Mem [RP]; IP := IP + LoadByte (RP);
RP := RP + 1; RP := RP + 1;
if RP > $fff0 then begin
writeln ('Error: illegal return pointer value');
halt;
end;
end end
//Shl //Shl
else if Op = 2 then R [X] := R [X] shl 1 else if Op = 2 then R [X] := R [X] shl 1
@ -212,177 +387,10 @@ begin
//Immediate //Immediate
if Y <> 0 then R [X] := Addr shr 8 if Y <> 0 then R [X] := Addr shr 8
//Address //Address
else begin else R [X] := LoadByte (Addr);
//Terminal input
if Addr = IO then begin
wait;
//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;
//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;
end
//Null
else ASCII := true;
end
//Other ASCII
else ASCII := true;
until ASCII = true;
//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;
//Process the keypress
Output; //Local echo
R [X] := byte (Ch);
end
//Tape reader
{$ifdef tape}
else if Addr = $fffd then begin
wait;
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);
try
reset (TapeIn);
seek (TapeIn, Reader.Pos);
read (TapeIn, R [X]);
close (TapeIn);
Reader.Pos := Reader.Pos + 1;
except
R [X] := $ff;
end;
//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;
end
{$endif}
//Regular load
else R [X] := Mem [Addr];
end;
end end
//Store //Store
else if Op = $b then begin else if Op = $b then StoreByte (Addr, R [Y])
//Terminal output
if Addr = IO then begin
wait;
Ch := ansichar (R [Y]);
Output;
end
//Printer
{$ifdef printer}
else if Addr = $fffe then begin
wait;
assign (Prn, '/dev/usb/lp0');
try
rewrite (Prn);
write (Prn, R [Y]);
close (Prn);
except
end;
end
{$endif}
//Tape punch
{$ifdef tape}
else if Addr = $fffd then begin
wait;
assign (State, ExpandFileName ('~/.tapes.thingamajig'));
//Check the punch state
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
try
reset (State);
read (State, Reader);
read (State, Punch);
close (State);
except
end;
end;
//Punch
if Punch.Path <> '' then begin
assign (TapeOut, Punch.Path);
if FileExists (Punch.Path) = false then begin
try
rewrite (TapeOut);
write (TapeOut, R [Y]);
close (TapeOut);
Punch.Reset := false;
except
end;
end
else if Punch.Reset then begin
try
rewrite (TapeOut);
write (TapeOut, R [Y]);
close (TapeOut);
Punch.Reset := false;
except
end;
end
else begin
try
reset (TapeOut);
seek (TapeOut, FileSize (TapeOut));
write (TapeOut, R [Y]);
close (TapeOut);
except
end;
end;
end;
//Save the punch state
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
try
rewrite (State);
write (State, Reader);
write (State, Punch);
close (State);
except
end;
end;
end
{$endif}
//Regular store
else Mem [Addr] := R [Y];
end
//Breq //Breq
else if Op = $c then begin else if Op = $c then begin
if R [X] = R [Y] then IP := Addr; if R [X] = R [Y] then IP := Addr;
@ -393,53 +401,11 @@ begin
end end
//Cleq //Cleq
else if Op = $e then begin else if Op = $e then begin
if R [X] = R [Y] then begin if R [X] = R [Y] then Call;
//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
//Clneq //Clneq
else if Op = $f then begin else if Op = $f then begin
if R [X] <> R [Y] then begin if R [X] <> R [Y] then Call;
//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;
//Increment the instruction counter //Increment the instruction counter

View File

@ -14,12 +14,6 @@ assembler and a disassembler, all written in FreePascal. It also
includes couple of simple example programs for Thingamajig written in includes couple of simple example programs for Thingamajig written in
Assembly. Assembly.
Speed
-----
Thingamajig does not have a prescribed speed. The emulator runs at
roughly 500 KIPS.
Registers and Memory Registers and Memory
-------------------- --------------------
@ -28,11 +22,10 @@ Registers and Memory
* 8-bit general-purpose registers R0-R3 * 8-bit general-purpose registers R0-R3
* 8-bit memory locations 0-FFFF * 8-bit memory locations 0-FFFF
Multi-byte values are big-endian. Memory addresses FFF0-FFFF are Multi-byte values are big-endian. Memory locations 0-FFEF are used for
reserved for memory mapped devices; the instruction and return pointers RAM while FFF0-FFFF are reserved for memory mapped devices. Input and
cannot have values higher than FFEF and FFF0 respectively to avoid them. output are mapped to address FFFF, while arbitrary devices can be mapped
The instruction and return pointers are initialised as 0 and FFF0 to the other reserved addresses.
respectively, while other registers and memory are unitialised.
Instructions Instructions
------------ ------------
@ -78,8 +71,7 @@ For the arguments of each instruction see the previous section.
Address arguments can be either absolute addresses or references to or Address arguments can be either absolute addresses or references to or
relative to a label. Relative references are of the form LABEL +/- N, relative to a label. Relative references are of the form LABEL +/- N,
the spacing being optional. Note that the assembler does not check for the spacing being optional.
addresses or references to reserved addresses.
In addition to the true instructions there are three In addition to the true instructions there are three
pseudo-instructions. ORG defines the starting address of the program: it pseudo-instructions. ORG defines the starting address of the program: it
@ -88,26 +80,34 @@ not required if the starting address is 0. DATA introduces a byte of
data. ADDR introduces two bytes of data containing the address of a data. ADDR introduces two bytes of data containing the address of a
reference to or relative to a label. reference to or relative to a label.
Memory-Mapped Devices Boot
--------------------- ----
Input and output are mapped to address FFFF; the emulator emulates a At boot the initial program loader (IPL) loads a program to the RAM
roughly 1000 CPS glass teletype with local echo. starting at address 0 after which is cedes control to the CPU. The
instruction and return pointers are initialised as 0 and the first
address after RAM respectively, while other registers and RAM are
uninitialised.
Arbitrary devices can be mapped to the other reserved addresses. Emulator
--------
The emulator runs at roughly 500 KIPS and has the full 65520 bytes of
RAM.
Input and output are handled by an emulated roughly 1000 CPS glass
teletype terminal with local echo. Note that of the control characters
only bell, backspace, line feed, and carriage return are used by the
terminal, and the backspace and delete keys are tied to their respective
characters and non-character keys to null.
In Linux the emulator can be compiled with support for a character In Linux the emulator can be compiled with support for a character
printer and an emulated punched tape reader and punch with the arguments printer and an emulated punched tape reader and punch with the arguments
-dprinter and -dtape respectively. The printer is mapped to address FFFE -dprinter and -dtape respectively. The printer is mapped to address FFFE
and the tape reader and punch to FFFD. The printer prints into and the tape reader and punch to FFFD. The printer prints into
/dev/usb/lp0 and the tape files read from and punched to are (re)set /dev/usb/lp0 and the tape files read from and punched to are (re)set
using the program tapectl. If you wish to use a different setup you have using the program tapectl.
to modify the code yourself.
Initial Program Loader The IPL loads a program from a file specified when launching the
---------------------- emulator.
At boot the initial program loader (IPL) loads a program to the memory
starting from address 0 after which is cedes control to the CPU. The
emulator loads the program from a ROM file.