Compare commits
19 Commits
Author | SHA1 | Date |
---|---|---|
CrazyEttin | 1f7bc8b5b5 | |
CrazyEttin | 64ddf705ea | |
CrazyEttin | e626369a16 | |
CrazyEttin | 85495426b2 | |
CrazyEttin | d324e795ed | |
CrazyEttin | f8b27b8465 | |
CrazyEttin | 7c2905b558 | |
CrazyEttin | da67a19ac6 | |
CrazyEttin | 23b32d7835 | |
CrazyEttin | bf36aab7a4 | |
CrazyEttin | 0b07a2b214 | |
CrazyEttin | 2feaba4ee9 | |
CrazyEttin | 3f7cf0d8ea | |
CrazyEttin | 5fd7fa707f | |
CrazyEttin | f0e676facb | |
CrazyEttin | 9d318c4c75 | |
CrazyEttin | c78d7c69ef | |
CrazyEttin | 13e56f57a7 | |
CrazyEttin | d4720f48c2 |
|
@ -170,6 +170,7 @@ begin
|
|||
LP := 1;
|
||||
BP := 0;
|
||||
SP := 0;
|
||||
EP := 0;
|
||||
|
||||
//Begin the main loop
|
||||
repeat
|
||||
|
@ -258,8 +259,22 @@ begin
|
|||
else LblError;
|
||||
end
|
||||
else begin
|
||||
writeln ('Error (line ', LP, '): ORG must be the first instruction');
|
||||
halt (1);
|
||||
if Elem [0] = '' then begin
|
||||
//Set the starting point
|
||||
if ExtractWord (3, Line, [' ']) <> '' then ArgError;
|
||||
DatOrg := ExtractWord (2, Line, [' ']);
|
||||
try
|
||||
if Hex2Dec (DatOrg) <=$ffff then begin
|
||||
if BP > EP then EP := BP;
|
||||
BP := Hex2Dec (DatOrg);
|
||||
if BP < SP then SP := BP;
|
||||
end
|
||||
else ArgError;
|
||||
except
|
||||
ArgError;
|
||||
end;
|
||||
end
|
||||
else LblError;
|
||||
end;
|
||||
end
|
||||
|
||||
|
@ -331,17 +346,22 @@ begin
|
|||
else if Elem [3] <> '' then ArgError
|
||||
else if Elem [4] <> '' then ArgError;
|
||||
end
|
||||
else if Bin [BP] <= $50 then begin
|
||||
if Elem [3] <> '' then ArgError
|
||||
else if Elem [4] <> '' then ArgError;
|
||||
end
|
||||
else if Bin [BP] <= $b0 then begin
|
||||
if Elem [4] <> '' then ArgError;
|
||||
end;
|
||||
|
||||
//Assemble the arguments
|
||||
//Shifts
|
||||
if Bin [BP] >= $20 then if Bin [BP] <= $50 then OneArgReg (2);
|
||||
if Bin [BP] >= $20 then if Bin [BP] <= $50 then begin
|
||||
//First argument
|
||||
OneArgReg (2);
|
||||
//Second argument
|
||||
if CompareText (Elem [3], '1') = 0 then Bin [BP] := Bin [BP] + 1
|
||||
else if CompareText (Elem [3], '2') = 0 then Bin [BP] := Bin [BP] + 2
|
||||
else if CompareText (Elem [3], '3') = 0 then Bin [BP] := Bin [BP] + 3
|
||||
else if CompareText (Elem [3], '4') = 0 then Bin [BP] := Bin [BP] + 0
|
||||
else ArgError;
|
||||
end;
|
||||
//Logical operations
|
||||
if Bin [BP] >= $60 then if Bin [BP] <= $90 then TwoArgRegs;
|
||||
//Load
|
||||
|
@ -417,7 +437,7 @@ begin
|
|||
if AllRefsResolved = false then halt (1);
|
||||
|
||||
//Set the end pointer and reset the byte pointer to the start of the program
|
||||
EP := BP;
|
||||
if BP > EP then EP := BP;
|
||||
BP := SP;
|
||||
|
||||
//Write the program file
|
||||
|
|
|
@ -111,7 +111,13 @@ begin
|
|||
write (Opcodes [Op]);
|
||||
if Op = $b then writeln (IntToHex (Addr, 1), ', R', X)
|
||||
else begin
|
||||
if Op >= 2 then write ('R', X);
|
||||
if Op >= 2 then begin
|
||||
write ('R', X);
|
||||
if Op <= 5 then begin
|
||||
if Y = 0 then write (', 4')
|
||||
else write (', ', Y);
|
||||
end;
|
||||
end;
|
||||
if Op >= 6 then if Op <= 9 then write (', R', Y);
|
||||
if OP >= $c then write (', R', Y);
|
||||
if Op >= $a then begin
|
||||
|
|
736
emulator.pas
736
emulator.pas
|
@ -2,17 +2,38 @@ program Emulator;
|
|||
|
||||
{$MODE OBJFPC}
|
||||
|
||||
uses SysUtils, Crt;
|
||||
{$ifdef full}
|
||||
{$define RAM64}
|
||||
{$define printer}
|
||||
{$define tape}
|
||||
{$define floppy}
|
||||
{$define modem}
|
||||
{$define status}
|
||||
{$endif}
|
||||
|
||||
uses SysUtils, Crt{$ifdef modem}, BaseUnix, Sockets{$endif}{$ifdef status}{$ifndef modem}, BaseUnix{$endif}{$endif};
|
||||
|
||||
{$ifdef tape}
|
||||
//Tape file path and reset state
|
||||
type
|
||||
//Tape file path and reset state
|
||||
Tape = record
|
||||
Path: shortstring;
|
||||
Reset: boolean;
|
||||
Pos: integer;
|
||||
end;
|
||||
{$endif}
|
||||
{$ifdef modem}
|
||||
//Modem connection state
|
||||
type
|
||||
Connection = record
|
||||
Originate: boolean;
|
||||
Answer: boolean;
|
||||
Dial: boolean;
|
||||
Addr: longword;
|
||||
Port: word;
|
||||
Hang: boolean;
|
||||
end;
|
||||
{$endif}
|
||||
|
||||
const
|
||||
//The last address of RAM
|
||||
|
@ -31,20 +52,49 @@ const
|
|||
{$endif}
|
||||
|
||||
var
|
||||
Hlt, Verbose, Echo: boolean; //Halt, verbose, and echo flags
|
||||
Hlt, Echo{$ifdef floppy}, DiscRead, DiscWrite, DiscTrackSet, DiscSectSet{$endif}{$ifdef modem}, Listening, Connected{$endif}: boolean; //Halt and echo flags, disc system flags, and modem connection flags
|
||||
Op, Regs: 0 .. $f; //Opcode
|
||||
X, Y: 0 .. 3; //Register arguments
|
||||
Addr, IP, RP: word; //Immediate or address argument and instruction and return pointers
|
||||
R: array [0 .. 3] of byte; //General-purpose registers
|
||||
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
|
||||
{$ifdef floppy}
|
||||
DiscSB: array [0 .. $88] of byte; //Sector buffer for the disc system
|
||||
DiscSBP: 0 .. $88; //Sector buffer pointer
|
||||
{$endif}
|
||||
Prog{$ifdef printer}, Prn{$endif}{$ifdef tape}, TapeIn, TapeOut{$endif}{$ifdef floppy}, Disc{$endif}: file of byte; //Program file, line printer, tape reader and punch tapes, and current disc
|
||||
{$ifdef tape}
|
||||
Reader, Punch: Tape; //States of the tape reader and punch
|
||||
State: file of Tape; //File storing the states of the tape reader and punch
|
||||
Tapes: file of Tape; //File storing the states of the tape reader and punch
|
||||
{$endif}
|
||||
{$ifdef floppy}
|
||||
Disc0Path, Disc1Path, DiscPath: shortstring; //Paths of the discs in the drives and the current disc
|
||||
Discs: file of shortstring; //File storing the state of the disc system
|
||||
{$endif}
|
||||
Ch, Scan: ansichar; //Character for input and output and scancode for non-ASCII keys
|
||||
IC, LFX: integer; //Instruction counter for CPU speed
|
||||
Fetched: byte; //Fetched byte
|
||||
Verbose, IC, LFX: integer; //Verbose flag, instruction counter for CPU speed, and line feed position marker
|
||||
Fetched{$ifdef floppy}, Disc0Track, Disc1Track, Disc0Sect, Disc1Sect{$endif}: byte; //Fetched byte and disc drive locations
|
||||
{$ifdef floppy}
|
||||
DiscDrive: 0 .. 1; //Current disc drive number
|
||||
{$endif}
|
||||
{$ifdef modem}
|
||||
ConnVar: Connection; //State of the modem
|
||||
ConnFile: file of Connection; //File storing the state of the modem
|
||||
Mode: (Originate, Answer); //Modem mode
|
||||
ServerSocket, ListenSocket, ClientSocket, ClientAddrSize: longint; //Server socket
|
||||
ServerAddr, ClientAddr: TInetSockAddr; //Server address
|
||||
SigPipeHandler: pSigActionRec; //SIGPIPE handler
|
||||
{$endif}
|
||||
{$ifdef status}
|
||||
FileDescs: TFDset; //File descriptor set
|
||||
{$endif}
|
||||
|
||||
//Ignore signal
|
||||
{$ifdef modem}
|
||||
procedure DoSig (Sig: cint); cdecl;
|
||||
begin
|
||||
end;
|
||||
{$endif}
|
||||
|
||||
//Terminal output
|
||||
procedure Output;
|
||||
|
@ -66,16 +116,136 @@ begin
|
|||
else write (Ch);
|
||||
end;
|
||||
|
||||
//Wait to emulate CPU speed of ~500 KIPS
|
||||
{$ifndef fast}
|
||||
procedure wait;
|
||||
//Wait to emulate CPU speed of roughly 500 KIPS
|
||||
procedure wait (I: integer);
|
||||
begin
|
||||
if IC div 500 = 0 then sleep (1)
|
||||
else sleep (IC div 500);
|
||||
if IC div 500 < I then sleep (I)
|
||||
else begin
|
||||
sleep (IC div 500);
|
||||
if IC mod 500 >= 250 then sleep (1);
|
||||
end;
|
||||
IC := 0;
|
||||
end;
|
||||
{$endif}
|
||||
|
||||
{$ifdef modem}
|
||||
//Check the modem state
|
||||
procedure CheckModem;
|
||||
begin
|
||||
assign (ConnFile, ExpandFileName ('~/.thingamajig/connection'));
|
||||
//Check the modem state
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/connection')) then begin
|
||||
try
|
||||
reset (ConnFile);
|
||||
read (ConnFile, ConnVar);
|
||||
close (ConnFile);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
//Auto-set things when dialing
|
||||
if ConnVar.Dial then begin
|
||||
//Auto-change to originate mode if dialing in answer mode
|
||||
if Mode = Answer then ConnVar.Originate := true;
|
||||
//Auto-hang if dialing
|
||||
if Mode = Originate then if Connected then ConnVar.Hang := true;
|
||||
end;
|
||||
//Mode change
|
||||
//Originate
|
||||
if ConnVar.Originate then begin
|
||||
//Change the mode
|
||||
if Mode = Originate then begin
|
||||
if Connected then begin
|
||||
CloseSocket (ServerSocket);
|
||||
Connected := false;
|
||||
end;
|
||||
end
|
||||
else if Mode = Answer then begin
|
||||
if Connected then begin
|
||||
CloseSocket (ClientSocket);
|
||||
Connected := false;
|
||||
end;
|
||||
CloseSocket (ListenSocket);
|
||||
Listening := false;
|
||||
Mode := Originate;
|
||||
end;
|
||||
ConnVar.Originate := false;
|
||||
end
|
||||
//Answer
|
||||
else if ConnVar.Answer then begin
|
||||
//Change the mode
|
||||
if Mode = Originate then begin
|
||||
if Connected then begin
|
||||
CloseSocket (ServerSocket);
|
||||
Connected := false;
|
||||
end;
|
||||
Mode := Answer;
|
||||
end
|
||||
else if Mode = Answer then begin
|
||||
if Connected then begin
|
||||
CloseSocket (ClientSocket);
|
||||
Connected := false;
|
||||
end;
|
||||
CloseSocket (ListenSocket);
|
||||
Listening := false;
|
||||
end;
|
||||
//Create a listening socket
|
||||
ListenSocket := fpSocket (AF_INET, SOCK_STREAM, 0);
|
||||
if ListenSocket <> -1 then begin
|
||||
ServerAddr.sin_family := AF_INET;
|
||||
ServerAddr.sin_addr.s_addr := htonl (ConnVar.Addr);
|
||||
ServerAddr.sin_port := htons (ConnVar.Port);
|
||||
if fpBind (ListenSocket, @ServerAddr, Sizeof (ServerAddr)) <> -1 then begin
|
||||
if fpListen (ListenSocket, 1) <> -1 then begin
|
||||
ConnVar.Answer := false;
|
||||
Listening := true;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
//Hang
|
||||
if ConnVar.Hang then begin
|
||||
if Mode = Originate then begin
|
||||
if Connected then begin
|
||||
CloseSocket (ServerSocket);
|
||||
Connected := false;
|
||||
end;
|
||||
end
|
||||
else if Mode = Answer then begin
|
||||
if Connected then begin
|
||||
CloseSocket (ClientSocket);
|
||||
Connected := false;
|
||||
end;
|
||||
end;
|
||||
ConnVar.Hang := false;
|
||||
end;
|
||||
//Dial
|
||||
if ConnVar.Dial then if Connected = false then begin
|
||||
//Create a server socket
|
||||
ServerSocket := fpSocket (AF_INET, SOCK_STREAM, 0);
|
||||
if ServerSocket <> -1 then begin
|
||||
//Connect
|
||||
ServerAddr.sin_family := AF_INET;
|
||||
ServerAddr.sin_addr.s_addr := htonl (ConnVar.Addr);
|
||||
ServerAddr.sin_port := htons (ConnVar.Port);
|
||||
if fpConnect (ServerSocket, @ServerAddr, Sizeof (ServerAddr)) <> -1 then begin
|
||||
ConnVar.Dial := false;
|
||||
Connected := true;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
//Save the modem state
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/connection')) then begin
|
||||
try
|
||||
rewrite (ConnFile);
|
||||
write (ConnFile, ConnVar);
|
||||
close (ConnFile);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
{$endif}
|
||||
|
||||
//Load a byte from memory
|
||||
function LoadByte (W: word): byte;
|
||||
var
|
||||
|
@ -84,7 +254,7 @@ begin
|
|||
//Terminal input
|
||||
if W = $ffff then begin
|
||||
{$ifndef fast}
|
||||
wait;
|
||||
wait (1);
|
||||
{$endif}
|
||||
//Read a keypress
|
||||
Ch := ReadKey;
|
||||
|
@ -109,20 +279,20 @@ begin
|
|||
if Echo then Output; //Local echo
|
||||
B := byte (Ch);
|
||||
end
|
||||
//Tape reader
|
||||
{$ifdef tape}
|
||||
//Tape reader
|
||||
else if W = $fffd then begin
|
||||
{$ifndef fast}
|
||||
wait;
|
||||
wait (2);
|
||||
{$endif}
|
||||
assign (State, ExpandFileName ('~/.tapes.thingamajig'));
|
||||
assign (Tapes, ExpandFileName ('~/.thingamajig/tapes'));
|
||||
//Check the reader state
|
||||
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/tapes')) then begin
|
||||
try
|
||||
reset (State);
|
||||
read (State, Reader);
|
||||
read (State, Punch);
|
||||
close (State);
|
||||
reset (Tapes);
|
||||
read (Tapes, Reader);
|
||||
read (Tapes, Punch);
|
||||
close (Tapes);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
|
@ -138,17 +308,124 @@ begin
|
|||
B := $ff;
|
||||
end;
|
||||
//Save the reader state
|
||||
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/tapes')) then begin
|
||||
try
|
||||
rewrite (State);
|
||||
write (State, Reader);
|
||||
write (State, Punch);
|
||||
close (State);
|
||||
rewrite (Tapes);
|
||||
write (Tapes, Reader);
|
||||
write (Tapes, Punch);
|
||||
close (Tapes);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
end
|
||||
{$endif}
|
||||
{$ifdef floppy}
|
||||
//Floppy disc drive system
|
||||
//Data
|
||||
else if W = $fffc then begin
|
||||
if DiscRead then begin
|
||||
B := DiscSB [DiscSBP];
|
||||
if DiscSBP < $88 then DiscSBP := DiscSBP + 1
|
||||
else begin
|
||||
DiscSBP := 0;
|
||||
DiscRead := false;
|
||||
end;
|
||||
end
|
||||
else B := 0;
|
||||
end
|
||||
{$endif}
|
||||
{$ifdef modem}
|
||||
//Modem
|
||||
//Data
|
||||
else if W = $fffa then begin
|
||||
{$ifndef fast}
|
||||
wait (33);
|
||||
{$endif}
|
||||
//Check the modem state
|
||||
CheckModem;
|
||||
//Recieve
|
||||
if Mode = Originate then begin
|
||||
if Connected then begin
|
||||
if fpRecv (ServerSocket, @B, 1, 0) <> 1 then begin
|
||||
CloseSocket (ServerSocket);
|
||||
Connected := false;
|
||||
B := 0;
|
||||
end;
|
||||
end
|
||||
else B := 0;
|
||||
end
|
||||
else if Mode = Answer then begin
|
||||
if Connected then begin
|
||||
if fpRecv (ClientSocket, @B, 1, 0) <> 1 then begin
|
||||
CloseSocket (ClientSocket);
|
||||
Connected := false;
|
||||
B := 0;
|
||||
end;
|
||||
end
|
||||
else B := 0;
|
||||
end
|
||||
else B := 0;
|
||||
end
|
||||
//Status
|
||||
else if W = $fff9 then begin
|
||||
//Check the modem state
|
||||
CheckModem;
|
||||
//Load the status
|
||||
if Connected then B := 1
|
||||
else B := 0;
|
||||
end
|
||||
{$endif}
|
||||
{$ifdef status}
|
||||
//Input status register
|
||||
else if W = $fff8 then begin
|
||||
{$ifndef fast}
|
||||
wait (1);
|
||||
{$endif}
|
||||
//Initialise the register
|
||||
B := 0;
|
||||
//FFFF: Terminal
|
||||
fpfd_zero (FileDescs);
|
||||
fpfd_set (0, FileDescs);
|
||||
if fpSelect (1, @FileDescs, nil, nil, 1) > 0 then B := B or 1;
|
||||
//FFFE: No input
|
||||
B := B or 2;
|
||||
//FFFD: Tape reader or no input
|
||||
B := B or 4;
|
||||
//FFFC: Disc system data or no input
|
||||
B := B or 8;
|
||||
//FFFB: No input
|
||||
B := B or $10;
|
||||
//FFFA: Modem or no input
|
||||
{$ifdef modem}
|
||||
//Check the modem state
|
||||
CheckModem;
|
||||
//Check connection status
|
||||
if Mode = Originate then begin
|
||||
if Connected then begin
|
||||
fpfd_zero (FileDescs);
|
||||
fpfd_set (ServerSocket, FileDescs);
|
||||
if fpSelect (ServerSocket + 1, @FileDescs, nil, nil, 0) > 0 then B := B or $20;
|
||||
end
|
||||
else B := B or $20;
|
||||
end
|
||||
else if Mode = Answer then begin
|
||||
if Connected then begin
|
||||
fpfd_zero (FileDescs);
|
||||
fpfd_set (ClientSocket, FileDescs);
|
||||
if fpSelect (ClientSocket + 1, @FileDescs, nil, nil, 0) > 0 then B := B or $20;
|
||||
end
|
||||
else B := B or $20;
|
||||
end;
|
||||
{$endif}
|
||||
{$ifndef modem}
|
||||
B := B or $20;
|
||||
{$endif}
|
||||
//FFF9: No input
|
||||
B := B or $40;
|
||||
//FFF8: Input status register
|
||||
B := B or $80;
|
||||
end
|
||||
{$endif}
|
||||
//Unused addresses
|
||||
else if W > LastRAM then B := 0
|
||||
//Regular load
|
||||
|
@ -162,18 +439,18 @@ begin
|
|||
//Terminal output
|
||||
if W = $ffff then begin
|
||||
{$ifndef fast}
|
||||
wait;
|
||||
wait (1);
|
||||
{$endif}
|
||||
Ch := ansichar (B);
|
||||
Output;
|
||||
if Ch = ansichar ($12) then Echo := true;
|
||||
if Ch = ansichar ($14) then Echo := false;
|
||||
end
|
||||
//Printer
|
||||
{$ifdef printer}
|
||||
//Printer
|
||||
else if W = $fffe then begin
|
||||
{$ifndef fast}
|
||||
wait;
|
||||
wait (1);
|
||||
{$endif}
|
||||
assign (Prn, '/dev/usb/lp0');
|
||||
try
|
||||
|
@ -184,20 +461,20 @@ begin
|
|||
end;
|
||||
end
|
||||
{$endif}
|
||||
//Tape punch
|
||||
{$ifdef tape}
|
||||
//Tape punch
|
||||
else if W = $fffd then begin
|
||||
{$ifndef fast}
|
||||
wait;
|
||||
wait (20);
|
||||
{$endif}
|
||||
assign (State, ExpandFileName ('~/.tapes.thingamajig'));
|
||||
assign (Tapes, ExpandFileName ('~/.thingamajig/tapes'));
|
||||
//Check the punch state
|
||||
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/tapes')) then begin
|
||||
try
|
||||
reset (State);
|
||||
read (State, Reader);
|
||||
read (State, Punch);
|
||||
close (State);
|
||||
reset (Tapes);
|
||||
read (Tapes, Reader);
|
||||
read (Tapes, Punch);
|
||||
close (Tapes);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
|
@ -233,17 +510,313 @@ begin
|
|||
end;
|
||||
end;
|
||||
//Save the punch state
|
||||
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/tapes')) then begin
|
||||
try
|
||||
rewrite (State);
|
||||
write (State, Reader);
|
||||
write (State, Punch);
|
||||
close (State);
|
||||
rewrite (Tapes);
|
||||
write (Tapes, Reader);
|
||||
write (Tapes, Punch);
|
||||
close (Tapes);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
end
|
||||
{$endif}
|
||||
{$ifdef floppy}
|
||||
//Floppy disc drive system
|
||||
//Data
|
||||
else if W = $fffc then begin
|
||||
if DiscWrite then begin
|
||||
DiscSB [DiscSBP] := B;
|
||||
if DiscSBP < $88 then DiscSBP := DiscSBP + 1
|
||||
else begin
|
||||
DiscSBP := 0;
|
||||
DiscWrite := false;
|
||||
end;
|
||||
end
|
||||
else if DiscTrackSet then begin
|
||||
if B <= $4c then begin
|
||||
if DiscDrive = 0 then begin
|
||||
{$ifndef fast}
|
||||
if Disc0Track > B then wait (((Disc0Track - B) * 10) + 45)
|
||||
else if B > Disc0Track then wait (((B - Disc0Track) * 10) + 45);
|
||||
{$endif}
|
||||
Disc0Track := B;
|
||||
end
|
||||
else begin
|
||||
{$ifndef fast}
|
||||
if Disc1Track > B then wait (((Disc1Track - B) * 10) + 45)
|
||||
else if B > Disc1Track then wait (((B - Disc1Track) * 10) + 45);
|
||||
{$endif}
|
||||
Disc1Track := B;
|
||||
end;
|
||||
end;
|
||||
DiscTrackSet := false;
|
||||
end
|
||||
else if DiscSectSet then begin
|
||||
if B <= $1f then begin
|
||||
if DiscDrive = 0 then Disc0Sect := B
|
||||
else Disc1Sect := B;
|
||||
end;
|
||||
DiscSectSet := false;
|
||||
end;
|
||||
end
|
||||
//Command
|
||||
else if W = $fffb then begin
|
||||
B := B and $f;
|
||||
//Reset the system
|
||||
if B and $e = 0 then begin
|
||||
{$ifndef fast}
|
||||
if ((Disc0Track * 10) + 45 ) > ((Disc1Track * 10) + 45 ) then wait ((Disc0Track * 10) + 45)
|
||||
else wait ((Disc1Track * 10) + 45);
|
||||
{$endif}
|
||||
Disc0Track := 0;
|
||||
Disc1Track := 0;
|
||||
Disc0Sect := 0;
|
||||
Disc1Sect := 0;
|
||||
DiscRead := false;
|
||||
DiscWrite := false;
|
||||
DiscTrackSet := false;
|
||||
DiscSectSet := false;
|
||||
for DiscSBP := 0 to $88 do DiscSB [DiscSBP] := 0;
|
||||
DiscSBP := 0;
|
||||
end
|
||||
//Format the disc
|
||||
else if B and $e = 2 then begin
|
||||
if DiscRead = false then if DiscWrite = false then if DiscTrackSet = false then if DiscSectSet = false then begin
|
||||
{$ifndef fast}
|
||||
wait (30000);
|
||||
{$endif}
|
||||
assign (Discs, ExpandFileName ('~/.thingamajig/discs'));
|
||||
//Check the system state
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/discs')) then begin
|
||||
try
|
||||
reset (Discs);
|
||||
read (Discs, Disc0Path);
|
||||
read (Discs, Disc1Path);
|
||||
close (Discs);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
//Set the drive
|
||||
if B and 1 = 0 then DiscPath := Disc0Path
|
||||
else DiscPath := Disc1Path;
|
||||
if DiscPath <> '' then begin
|
||||
assign (Disc, DiscPath);
|
||||
//Write
|
||||
try
|
||||
reset (Disc);
|
||||
if FileSize (Disc) = $526a0 then begin
|
||||
if B and 1 = 0 then begin
|
||||
for Disc0Track := 0 to $4c do begin
|
||||
for Disc0Sect := 0 to $1f do begin
|
||||
for DiscSBP := 0 to $88 do begin
|
||||
seek (Disc, (Disc0Track * $1120) + (Disc0Sect * $89) + DiscSBP);
|
||||
if DiscSBP = 0 then write (Disc, $80)
|
||||
else write (Disc, 0);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
else begin
|
||||
for Disc1Track := 0 to $4c do begin
|
||||
for Disc1Sect := 0 to $1f do begin
|
||||
for DiscSBP := 0 to $88 do begin
|
||||
seek (Disc, (Disc1Track * $1120) + (Disc1Sect * $89) + DiscSBP);
|
||||
if DiscSBP = 0 then write (Disc, $80)
|
||||
else write (Disc, 0);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
close (Disc);
|
||||
Disc0Track := 0;
|
||||
Disc1Track := 0;
|
||||
Disc0Sect := 0;
|
||||
Disc1Sect := 0;
|
||||
DiscRead := false;
|
||||
DiscWrite := false;
|
||||
DiscTrackSet := false;
|
||||
DiscSectSet := false;
|
||||
for DiscSBP := 0 to $88 do DiscSB [DiscSBP] := 0;
|
||||
DiscSBP := 0;
|
||||
end
|
||||
else close (Disc);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
//Read a sector from the buffer to the computer
|
||||
else if B and $e = 4 then begin
|
||||
if DiscWrite = false then if DiscTrackSet = false then if DiscSectSet = false then begin
|
||||
DiscRead := true;
|
||||
DiscSBP := 0;
|
||||
end;
|
||||
end
|
||||
//Write a sector from the computer to the buffer
|
||||
else if B and $e = 6 then begin
|
||||
if DiscRead = false then if DiscTrackSet = false then if DiscSectSet = false then begin
|
||||
DiscWrite := true;
|
||||
for DiscSBP := 0 to $88 do DiscSB [DiscSBP] := 0;
|
||||
DiscSBP := 0;
|
||||
end;
|
||||
end
|
||||
//Set the track to be accessed
|
||||
else if B and $e = 8 then begin
|
||||
if DiscRead = false then if DiscWrite = false then if DiscSectSet = false then begin
|
||||
DiscDrive := B and 1;
|
||||
DiscTrackSet := true;
|
||||
end;
|
||||
end
|
||||
//Set the sector to be accessed
|
||||
else if B and $e = $a then begin
|
||||
if DiscRead = false then if DiscWrite = false then if DiscTrackSet = false then begin
|
||||
DiscDrive := B and 1;
|
||||
DiscSectSet := true;
|
||||
end;
|
||||
end
|
||||
//Read a sector from the disc to the buffer
|
||||
else if B and $e = $c then begin
|
||||
if DiscRead = false then if DiscWrite = false then if DiscTrackSet = false then if DiscSectSet = false then begin
|
||||
{$ifndef fast}
|
||||
wait (5);
|
||||
{$endif}
|
||||
assign (Discs, ExpandFileName ('~/.thingamajig/discs'));
|
||||
//Check the system state
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/discs')) then begin
|
||||
try
|
||||
reset (Discs);
|
||||
read (Discs, Disc0Path);
|
||||
read (Discs, Disc1Path);
|
||||
close (Discs);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
//Set the drive
|
||||
if B and 1 = 0 then DiscPath := Disc0Path
|
||||
else DiscPath := Disc1Path;
|
||||
assign (Disc, DiscPath);
|
||||
//Read
|
||||
try
|
||||
reset (Disc);
|
||||
if FileSize (Disc) = $526a0 then begin
|
||||
if B and 1 = 0 then seek (Disc, (Disc0Track * $1120) + (Disc0Sect * $89))
|
||||
else seek (Disc, (Disc1Track * $1120) + (Disc1Sect * $89));
|
||||
read (Disc, DiscSB [0]);
|
||||
if DiscSB [0] and $80 = $80 then begin
|
||||
for DiscSBP := 1 to $88 do begin
|
||||
if B and 1 = 0 then seek (Disc, (Disc0Track * $1120) + (Disc0Sect * $89) + DiscSBP)
|
||||
else seek (Disc, (Disc1Track * $1120) + (Disc1Sect * $89) + DiscSBP);
|
||||
read (Disc, DiscSB [DiscSBP]);
|
||||
end;
|
||||
end
|
||||
else for DiscSBP := 0 to $88 do DiscSB [DiscSBP] := 0;
|
||||
close (Disc)
|
||||
end
|
||||
else begin
|
||||
close (Disc);
|
||||
for DiscSBP := 0 to $88 do DiscSB [DiscSBP] := 0;
|
||||
end;
|
||||
except
|
||||
for DiscSBP := 0 to $88 do DiscSB [DiscSBP] := 0;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
//Write a sector from the buffer to the disc
|
||||
else if B and $e = $e then begin
|
||||
if DiscRead = false then if DiscWrite = false then if DiscTrackSet = false then if DiscSectSet = false then begin
|
||||
{$ifndef fast}
|
||||
wait (5);
|
||||
{$endif}
|
||||
assign (Discs, ExpandFileName ('~/.thingamajig/discs'));
|
||||
//Check the system state
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/discs')) then begin
|
||||
try
|
||||
reset (Discs);
|
||||
read (Discs, Disc0Path);
|
||||
read (Discs, Disc1Path);
|
||||
close (Discs);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
//Set the drive
|
||||
if B and 1 = 0 then DiscPath := Disc0Path
|
||||
else DiscPath := Disc1Path;
|
||||
if DiscPath <> '' then begin
|
||||
assign (Disc, DiscPath);
|
||||
//Write
|
||||
try
|
||||
reset (Disc);
|
||||
if FileSize (Disc) = $526a0 then begin
|
||||
for DiscSBP := 0 to $88 do begin
|
||||
if B and 1 = 0 then seek (Disc, (Disc0Track * $1120) + (Disc0Sect * $89) + DiscSBP)
|
||||
else seek (Disc, (Disc1Track * $1120) + (Disc1Sect * $89) + DiscSBP);
|
||||
write (Disc, DiscSB [DiscSBP]);
|
||||
end;
|
||||
close (Disc);
|
||||
end
|
||||
else close (Disc);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
{$endif}
|
||||
{$ifdef modem}
|
||||
//Modem
|
||||
//Data
|
||||
else if W = $fffa then begin
|
||||
{$ifndef fast}
|
||||
wait (33);
|
||||
{$endif}
|
||||
//Check the modem state
|
||||
CheckModem;
|
||||
//Send
|
||||
if Mode = Originate then begin
|
||||
if Connected then begin
|
||||
if fpSend (ServerSocket, @B, 1, 0) <> 1 then begin
|
||||
CloseSocket (ServerSocket);
|
||||
Connected := false;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
else if Mode = Answer then begin
|
||||
if Connected then begin
|
||||
if fpSend (ClientSocket, @B, 1, 0) <> 1 then begin
|
||||
CloseSocket (ClientSocket);
|
||||
Connected := false;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
//Status
|
||||
else if W = $fff9 then begin
|
||||
//Check the modem state
|
||||
CheckModem;
|
||||
//Change the status
|
||||
if Mode = Originate then begin
|
||||
if Connected then if (B and 1) = 0 then begin
|
||||
CloseSocket (ServerSocket);
|
||||
Connected := false;
|
||||
end;
|
||||
end
|
||||
else if Mode = Answer then begin
|
||||
if Connected then begin
|
||||
CloseSocket (ClientSocket);
|
||||
Connected := false;
|
||||
end;
|
||||
if Listening then if (B and 1) = 1 then begin
|
||||
//Connect
|
||||
ClientAddrSize := sizeof (ClientAddr);
|
||||
ClientSocket := fpAccept (ListenSocket, @ClientAddr, @ClientAddrSize) ;
|
||||
if ClientSocket <> -1 then begin
|
||||
Connected := true;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
{$endif}
|
||||
//Regular store
|
||||
else if W <= LastRAM then Mem [W] := B;
|
||||
end;
|
||||
|
@ -269,8 +842,8 @@ begin
|
|||
RP := LastRAM + 1;
|
||||
IC := 0;
|
||||
|
||||
//Initialise the tape reader and punch
|
||||
{$ifdef tape}
|
||||
//Initialise the tape reader and punch
|
||||
Reader.Path := '';
|
||||
Reader.Reset := true;
|
||||
Reader.Pos := 0;
|
||||
|
@ -279,20 +852,59 @@ begin
|
|||
Punch.Pos := 0;
|
||||
{$endif}
|
||||
|
||||
{$ifdef floppy}
|
||||
//Initialise the disc system
|
||||
Disc0Path := '';
|
||||
Disc1Path := '';
|
||||
DiscRead := false;
|
||||
DiscWrite := false;
|
||||
DiscTrackSet := false;
|
||||
DiscSectSet := false;
|
||||
DiscSBP := 0;
|
||||
Disc0Track := 0;
|
||||
Disc1Track := 0;
|
||||
Disc0Sect := 0;
|
||||
Disc1Sect := 0;
|
||||
{$endif}
|
||||
|
||||
{$ifdef modem}
|
||||
//Initialise the modem
|
||||
Mode := Originate;
|
||||
Listening := false;
|
||||
Connected := false;
|
||||
//Initialise the SIGPIPE handler
|
||||
new (SigPipeHandler);
|
||||
SigPipeHandler^.sa_Handler := SigActionHandler (@DoSig);
|
||||
fillchar (SigPipeHandler^.Sa_Mask, sizeof (SigPipeHandler^.sa_mask), #0);
|
||||
SigPipeHandler^.Sa_Flags := 0;
|
||||
SigPipeHandler^.Sa_Restorer := nil;
|
||||
try
|
||||
fpSigAction (SigPipe, SigPipeHandler, nil);
|
||||
except
|
||||
end;
|
||||
{$endif}
|
||||
|
||||
//Check the arguments
|
||||
if ParamCount = 0 then begin
|
||||
writeln ('Usage: emulator (-v) program (2> verbose_output)');
|
||||
halt (1);
|
||||
end;
|
||||
if ParamStr (1) = '-v' then begin
|
||||
Verbose := true;
|
||||
Verbose := 1;
|
||||
if ParamCount <> 2 then begin
|
||||
writeln ('Usage: emulator (-v) program (2> verbose_output)');
|
||||
halt (1);
|
||||
end;
|
||||
end
|
||||
else if ParamStr (2) = '-v' then begin
|
||||
Verbose := 2;
|
||||
if ParamCount <> 2 then begin
|
||||
writeln ('Usage: emulator (-v) program (2> verbose_output)');
|
||||
halt (1);
|
||||
end;
|
||||
end
|
||||
else begin
|
||||
Verbose := false;
|
||||
Verbose := 0;
|
||||
if ParamCount <> 1 then begin
|
||||
writeln ('Usage: emulator (-v) program (2> verbose_output)');
|
||||
halt (1);
|
||||
|
@ -301,7 +913,7 @@ begin
|
|||
|
||||
//Read a program file and check for errors
|
||||
{$i-}
|
||||
if Verbose = true then assign (Prog, ParamStr (2))
|
||||
if Verbose = 1 then assign (Prog, ParamStr (2))
|
||||
else assign (Prog, ParamStr (1));
|
||||
reset (Prog);
|
||||
if FileSize (Prog) > LastRAM + 1 then begin
|
||||
|
@ -316,6 +928,7 @@ begin
|
|||
repeat
|
||||
read (Prog, Mem [IP]);
|
||||
IP := IP + 1;
|
||||
IC := IC + 1;
|
||||
until (eof (Prog));
|
||||
|
||||
//Reinitialise the instruction pointer
|
||||
|
@ -325,7 +938,7 @@ begin
|
|||
while Hlt = false do begin
|
||||
|
||||
//Print the CPU state to StdErr
|
||||
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 <> 0 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 opcode and register arguments
|
||||
|
@ -374,13 +987,25 @@ begin
|
|||
RP := RP + 1;
|
||||
end
|
||||
//Shl
|
||||
else if Op = 2 then R [X] := R [X] shl 1
|
||||
else if Op = 2 then begin
|
||||
if Y = 0 then R [X] := R [X] shl 4
|
||||
else R [X] := R [X] shl Y;
|
||||
end
|
||||
//Shr
|
||||
else if Op = 3 then R [X] := R [X] shr 1
|
||||
else if Op = 3 then begin
|
||||
if Y = 0 then R [X] := R [X] shr 4
|
||||
else R [X] := R [X] shr Y;
|
||||
end
|
||||
//Rol
|
||||
else if Op = 4 then R [X] := RolByte (R [X])
|
||||
else if Op = 4 then begin
|
||||
if Y = 0 then R [X] := RolByte (R [X], 4)
|
||||
else R [X] := RolByte (R [X], Y);
|
||||
end
|
||||
//Ror
|
||||
else if Op = 5 then R [X] := RorByte (R [X])
|
||||
else if Op = 5 then begin
|
||||
if Y = 0 then R [X] := RorByte (R [X], 4)
|
||||
else R [X] := RorByte (R [X], Y);
|
||||
end
|
||||
//Nand
|
||||
else if Op = 6 then R [X] := not (R [X] and R [Y])
|
||||
//And
|
||||
|
@ -420,8 +1045,17 @@ begin
|
|||
|
||||
end;
|
||||
|
||||
{$ifdef modem}
|
||||
//Disconnect the modem
|
||||
if Mode = Originate then if Connected then CloseSocket (ServerSocket);
|
||||
if Mode = Answer then if Connected then begin
|
||||
CloseSocket (ClientSocket);
|
||||
CloseSocket (ListenSocket);
|
||||
end;
|
||||
{$endif}
|
||||
|
||||
{$ifndef fast}
|
||||
wait;
|
||||
wait (1);
|
||||
{$endif}
|
||||
|
||||
end.
|
||||
|
|
|
@ -16,10 +16,7 @@
|
|||
|
||||
;Convert and print the high nibble
|
||||
;Convert
|
||||
ror r0
|
||||
ror r0
|
||||
ror r0
|
||||
ror r0
|
||||
ror r0, 4
|
||||
cleq r0, r0, n2hex
|
||||
;Print
|
||||
store ffff, r0
|
||||
|
|
|
@ -41,8 +41,8 @@ inloop: load r1, ffff
|
|||
load r2, bfsize
|
||||
brneq r0, r2, chstor
|
||||
|
||||
;Backtrack if at the buffer end
|
||||
load r2, #8
|
||||
;Ignore the input and print an underscore if at the buffer end
|
||||
load r2, #5f
|
||||
store ffff, r2
|
||||
breq r0, r0, inloop
|
||||
|
||||
|
@ -228,11 +228,11 @@ sumlop: xor r1, r1
|
|||
xor r1, r1
|
||||
xor r1, r2
|
||||
;Shift the carry
|
||||
shl r2
|
||||
shl r2, 1
|
||||
|
||||
;Check for and store overflow if any
|
||||
;Check
|
||||
rol r1
|
||||
rol r1, 1
|
||||
breq r1, r2, nvrflw
|
||||
;Store
|
||||
load r1, #1
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
program FloppyCtl;
|
||||
|
||||
{$MODE OBJFPC}
|
||||
|
||||
uses SysUtils;
|
||||
|
||||
var
|
||||
Disc0, Disc1: shortstring; //States of the floppy discs
|
||||
State: file of shortstring; //File storing the states of the floppy discs
|
||||
Set0, Set1: integer; //Whether to (re)set the discs
|
||||
|
||||
begin
|
||||
|
||||
//Check the arguments
|
||||
if ParamCount > 4 then begin
|
||||
writeln ('Usage: floppyctl (-0 disc) (-1 disc)');
|
||||
halt (1);
|
||||
end
|
||||
else if ParamCount mod 2 <> 0 then begin
|
||||
writeln ('Usage: floppyctl (-0 disc) (-1 disc)');
|
||||
halt (1);
|
||||
end;
|
||||
if ParamStr (1) = '-0' then begin
|
||||
Set0 := 2;
|
||||
Set1 := 0;
|
||||
if ParamStr (3) = '-1' then begin
|
||||
Set1 := 4;
|
||||
end;
|
||||
end
|
||||
else if ParamStr (1) = '-1' then begin
|
||||
Set0 := 0;
|
||||
Set1 := 2;
|
||||
if ParamStr (3) = '-0' then Set0 := 4;
|
||||
end
|
||||
else begin
|
||||
writeln ('Usage: floppyctl (-0 disc) (-1 disc)');
|
||||
halt (1);
|
||||
end;
|
||||
|
||||
//Assign the state file
|
||||
assign (State, ExpandFileName ('~/.thingamajig/discs'));
|
||||
|
||||
//Read existing state if any
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/discs')) then begin
|
||||
try
|
||||
reset (State);
|
||||
read (State, Disc0);
|
||||
read (State, Disc1);
|
||||
close (State);
|
||||
except
|
||||
end;
|
||||
end
|
||||
//Or else assign a default state
|
||||
else begin
|
||||
Disc0 := '';
|
||||
Disc1 := '';
|
||||
end;
|
||||
|
||||
//Get the files
|
||||
if Set0 <> 0 then Disc0 := ExpandFileName (ParamStr (Set0));
|
||||
if Set1 <> 0 then Disc1 := ExpandFileName (ParamStr (Set1));
|
||||
|
||||
//Write the state
|
||||
try
|
||||
rewrite (State);
|
||||
write (State, Disc0);
|
||||
write (State, Disc1);
|
||||
close (State);
|
||||
except
|
||||
writeln ('Error: could not set the disc(s)');
|
||||
end;
|
||||
|
||||
end.
|
|
@ -0,0 +1,118 @@
|
|||
program ModemCtl;
|
||||
|
||||
{$MODE OBJFPC}
|
||||
|
||||
uses SysUtils, StrUtils, Sockets;
|
||||
|
||||
type
|
||||
//Connection state
|
||||
Connection = record
|
||||
Originate: boolean;
|
||||
Answer: boolean;
|
||||
Dial: boolean;
|
||||
Addr: longword;
|
||||
Port: word;
|
||||
Hang: boolean;
|
||||
end;
|
||||
|
||||
var
|
||||
ConnVar: Connection; //State of the modem
|
||||
ConnFile: file of Connection; //File storing the state of the modem
|
||||
Addr: in_addr; //Temporary variable
|
||||
|
||||
begin
|
||||
|
||||
//Check the arguments
|
||||
if ParamStr (1) = '-a' then begin
|
||||
if ParamCount <> 2 then begin
|
||||
writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
|
||||
halt (1);
|
||||
end;
|
||||
end
|
||||
else if ParamStr (1) = '-d' then begin
|
||||
if ParamCount <> 2 then begin
|
||||
writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
|
||||
halt (1);
|
||||
end;
|
||||
end
|
||||
else begin
|
||||
if ParamCount > 1 then begin
|
||||
writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
|
||||
halt (1);
|
||||
end;
|
||||
end;
|
||||
|
||||
//Assign the state file
|
||||
assign (ConnFile, ExpandFileName ('~/.thingamajig/connection'));
|
||||
|
||||
//Set the connection
|
||||
if ParamStr (1) = '-o' then begin
|
||||
ConnVar.Originate := true;
|
||||
ConnVar.Answer := false;
|
||||
ConnVar.Dial := false;
|
||||
ConnVar.Addr := 0;
|
||||
ConnVar.Port := 0;
|
||||
ConnVar.Hang := false;
|
||||
end
|
||||
else if ParamStr (1) = '-a' then begin
|
||||
ConnVar.Originate := false;
|
||||
ConnVar.Answer := true;
|
||||
ConnVar.Dial := false;
|
||||
try
|
||||
Addr := StrToHostAddr (ExtractDelimited (1, ParamStr (2), [':']));
|
||||
ConnVar.Addr := Addr.s_addr;
|
||||
except
|
||||
writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
|
||||
halt (1);
|
||||
end;
|
||||
try
|
||||
ConnVar.Port := StrToInt (ExtractDelimited (2, ParamStr (2), [':']));
|
||||
except
|
||||
writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
|
||||
halt (1);
|
||||
end;
|
||||
ConnVar.Hang := false;
|
||||
ConnVar.Hang := false;
|
||||
end
|
||||
else if ParamStr (1) = '-d' then begin
|
||||
ConnVar.Originate := false;
|
||||
ConnVar.Answer := false;
|
||||
ConnVar.Dial := true;
|
||||
try
|
||||
Addr := StrToHostAddr (ExtractDelimited (1, ParamStr (2), [':']));
|
||||
ConnVar.Addr := Addr.s_addr;
|
||||
except
|
||||
writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
|
||||
halt (1);
|
||||
end;
|
||||
try
|
||||
ConnVar.Port := StrToInt (ExtractDelimited (2, ParamStr (2), [':']));
|
||||
except
|
||||
writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
|
||||
halt (1);
|
||||
end;
|
||||
ConnVar.Hang := false;
|
||||
end
|
||||
else if ParamStr (1) = '-h' then begin
|
||||
ConnVar.Originate := false;
|
||||
ConnVar.Answer := false;
|
||||
ConnVar.Dial := false;
|
||||
ConnVar.Addr := 0;
|
||||
ConnVar.Port := 0;
|
||||
ConnVar.Hang := true;
|
||||
end
|
||||
else begin
|
||||
writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
|
||||
halt (1);
|
||||
end;
|
||||
|
||||
//Write the state
|
||||
try
|
||||
rewrite (ConnFile);
|
||||
write (ConnFile, ConnVar);
|
||||
close (ConnFile);
|
||||
except
|
||||
writeln ('Error: could not set the connection');
|
||||
end;
|
||||
|
||||
end.
|
170
readme.md
170
readme.md
|
@ -1,18 +1,17 @@
|
|||
Thingamajig v1.0
|
||||
Thingamajig v2.0
|
||||
================
|
||||
|
||||
Thingamajig is a RISC/MISC homebrew computer architecture. Its git
|
||||
Thingamajig v2.0 is a RISC/MISC homebrew computer architecture. Its git
|
||||
repository can be found at
|
||||
https://ahti.space/git/crazyettin/Thingamajig.
|
||||
|
||||
Included Software
|
||||
-----------------
|
||||
|
||||
The repository includes an emulator implementation of Thingamajig with a
|
||||
control program for the emulated punched tape reader and punch, and an
|
||||
assembler and a disassembler, all written in FreePascal. It also
|
||||
includes couple of simple example programs for Thingamajig written in
|
||||
assembly.
|
||||
The repository includes an emulator implementation of Thingamajig with
|
||||
device control programs, and an assembler and a disassembler, all
|
||||
written in FreePascal. It also includes couple of simple example
|
||||
programs for Thingamajig written in assembly.
|
||||
|
||||
Registers and Memory
|
||||
--------------------
|
||||
|
@ -23,9 +22,12 @@ Registers and Memory
|
|||
* 8-bit memory locations 0-FFFF
|
||||
|
||||
Multi-byte values are big-endian. Memory locations 0-FFEF are used for
|
||||
RAM while FFF0-FFFF are reserved for memory mapped devices. Input and
|
||||
output are mapped to address FFFF, while arbitrary devices can be mapped
|
||||
to the other reserved addresses.
|
||||
RAM while FFF0-FFFF are reserved for memory mapped devices.
|
||||
|
||||
Input and output are mapped to address FFFF, while arbitrary devices can
|
||||
be mapped to the other reserved addresses. When interacting with memory
|
||||
mapped devices Thingamajig will stop processing to wait for the device
|
||||
to be ready if needed.
|
||||
|
||||
Instructions
|
||||
------------
|
||||
|
@ -37,10 +39,10 @@ instruction pointer is incremented before being accessed or modified.
|
|||
0 HALT
|
||||
1 RET IP = *RP; RP += 2
|
||||
|
||||
2 SHL RX RX <<= 1 Logical shifts
|
||||
3 SHR RX RX >>= 1
|
||||
4 ROL RX RX <<= 1 Rotating shifts
|
||||
5 ROR RX RX >>= 1
|
||||
2 SHL RX, N RX <<= N (logical) Shifts of 1-4 steps,
|
||||
3 SHR RX, N RX >>= N (logical) with 4 encoded as 0
|
||||
4 ROL RX, N RX <<= N (rotating) in machine code.
|
||||
5 ROR RX, N RX >>= N (rotating)
|
||||
|
||||
6 NAND RX, RY RX = ~(RX & RY)
|
||||
7 AND RX, RY RX &= RY
|
||||
|
@ -74,11 +76,11 @@ relative to a label. Relative references are of the form LABEL +/- N;
|
|||
the spacing is optional.
|
||||
|
||||
In addition to the true instructions there are three
|
||||
pseudo-instructions. ORG defines the starting address of the program: it
|
||||
can only occur as the first instruction and cannot have a label, and is
|
||||
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
|
||||
reference to or relative to a label.
|
||||
pseudo-instructions. ORG sets the location of the following code and
|
||||
data; as it has no direct equivalent in machine code it cannot have a
|
||||
label. The default starting address of 0 does not need to be indicated
|
||||
with ORG. DATA introduces a byte of data. ADDR introduces two bytes of
|
||||
data containing the address of a reference to or relative to a label.
|
||||
|
||||
Boot
|
||||
----
|
||||
|
@ -89,32 +91,118 @@ implementation has a front panel the IPL is optional. The instruction
|
|||
and return pointers are initialised as 0 and the first address after RAM
|
||||
respectively, while other registers and RAM are uninitialised.
|
||||
|
||||
Emulator
|
||||
--------
|
||||
Emulator and Device Control Programs
|
||||
------------------------------------
|
||||
|
||||
By default the emulator runs at roughly 500 KIPS, has 2 KiB of RAM, and
|
||||
interacts with memory mapped devices at roughly 1000 B/s. The arguments
|
||||
-dRAM4, -dRAM8, -dRAM16, -dRAM32, and -dRAM64 can be used to compile
|
||||
the emulator with 4, 8, 16, 32, or 64 KiB (minus the reserved addresses)
|
||||
of RAM respectively instead and the speed limitations can be removed
|
||||
with the argument -dfast.
|
||||
Usage:
|
||||
* emulator (-v) program (2> verbose_output)
|
||||
* tapectl (-r tape) (-p tape)
|
||||
* floppyctl (-0 disc) (-1 disc)
|
||||
* modemctl -o/[-a address:port]/[-d address:port]/-h
|
||||
|
||||
Input and output are handled by an emulated glass teletype terminal with
|
||||
local echo on by default. Of the control characters bell (^G),
|
||||
backspace (^H), line feed (^J), carriage return (^M), and device control
|
||||
characters two (^R) and four (^T) are used by the terminal: the device
|
||||
control characters are used to turn the local echo on and off
|
||||
respectively while the rest have their standard uses. The backspace and
|
||||
delete keys input their respective characters and non-character keys
|
||||
null.
|
||||
By default the emulator runs at roughly 500 KIPS and has 2 KiB of RAM.
|
||||
The arguments -dRAM4, -dRAM8, -dRAM16, -dRAM32, and -dRAM64 can be used
|
||||
to compile the emulator with 4, 8, 16, 32, or 64 KiB (minus the reserved
|
||||
addresses) of RAM respectively instead and the speed limitations can be
|
||||
removed with the argument -dfast. When run with the argument -v the
|
||||
current state of the registers is output to stderr before each
|
||||
instruction.
|
||||
|
||||
Input and output are handled by an emulated roughly 1000 CPS
|
||||
ASCII-compatible glass teletype terminal with local echo on by default.
|
||||
Of the control characters bell (^G), backspace (^H), line feed (^J),
|
||||
carriage return (^M), and device control characters two (^R) and four
|
||||
(^T) are used by the terminal: the device control characters are used to
|
||||
turn the local echo on and off respectively while the rest have their
|
||||
standard uses. The backspace and delete keys input their respective
|
||||
characters and non-character keys null.
|
||||
|
||||
In Linux the emulator can be compiled with support for a character
|
||||
printer and an emulated punched tape reader and punch with the arguments
|
||||
-dprinter and -dtape respectively. The printer is mapped to address FFFE
|
||||
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
|
||||
using the program tapectl.
|
||||
printer, an emulated high speed 8-bit paper tape reader and punch, an
|
||||
emulated two-drive 8" floppy disc system, a modem, and an input status
|
||||
register with the arguments -dprinter, -dtape, -dfloppy, -dmodem, and
|
||||
-dstatus respectively. Full 64 KiB of RAM and all of these options can
|
||||
also be enabled with the argument -dfull. The printer is mapped to
|
||||
address FFFE, the tape reader and punch to FFFD, the disc system to FFFB
|
||||
and FFFC, the modem to FFF9 and FFFA, and the input status register to
|
||||
FFF8.
|
||||
|
||||
The IPL loads a program from a file specified when launching the
|
||||
The printer prints into /dev/usb/lp0. The tape files read from and
|
||||
punched to are (re)set using the program tapectl with the arguments -r
|
||||
and -p respectively and the disc files in drives 0 and 1 using the
|
||||
program floppyctl with the arguments -0 and -1 respectively. The disc
|
||||
system uses hard sectored single-sided discs with 77 tracks of 32
|
||||
sectors of 137 bytes, or 337568 bytes in total: the disc files must be
|
||||
of this size. The modem is controlled by the program modemctl: the
|
||||
options -o and -a are used to set the originate and answer modes, the
|
||||
latter with the address and port to be listened; -d to dial an address
|
||||
and port to be called; and -h to hang. Dialing when in answer mode
|
||||
automatically switches the modem to originate mode. The modem hangs
|
||||
automatically if the connection is lost or hanged from the other side,
|
||||
if another address and port are dialed, if a mode change command is
|
||||
given, or if the emulator is halted.
|
||||
|
||||
The floppy disc system uses two ports: command at FFFB and data at FFFC.
|
||||
Only the low nibble of the command port is used, consisting of a
|
||||
three-bit command followed by a one-bit drive number used as an argument
|
||||
by instructions 1 and 4-7. The track or sector to be accessed is input
|
||||
to and the buffer accessed sequentially through the data port after the
|
||||
relevant command is input to the command port. New commands other than
|
||||
resetting the system are ignored until the current command is fully
|
||||
executed. Note that each sector begins with a synchronisation bit that
|
||||
must always be set.
|
||||
|
||||
The commands for the disc system are:
|
||||
0: Reset the system.
|
||||
1: Format a disc.
|
||||
2: Read a sector from the buffer to the computer.
|
||||
3: Write a sector from the computer to the buffer.
|
||||
4: Set the track to be accessed.
|
||||
5: Set the sector to be accessed.
|
||||
6: Read a sector from a disc to the buffer.
|
||||
7: Write a sector from the buffer to a disc.
|
||||
|
||||
The modem also uses two ports: status at FFF9 and data at FFFA. Only the
|
||||
least significant bit of the status port is used, indicating whether
|
||||
the modem is connected or not. Unsetting the bit hangs the modem while
|
||||
setting it in answer mode hangs any current call and listens for an
|
||||
incoming call to answer. Setting the bit in originate mode is ignored.
|
||||
|
||||
The input status register can be used to check if a device is ready to
|
||||
be read from. The reserved addresses FFF8-FFFF are mapped to the bits
|
||||
7-0: a set bit means either that the device is ready or that no input
|
||||
device is mapped to the address in question.
|
||||
|
||||
The IPL loads the program specified as an argument when running the
|
||||
emulator.
|
||||
|
||||
Assembler and Disassembler
|
||||
--------------------------
|
||||
|
||||
Usage:
|
||||
* assembler program (< input)
|
||||
* disassembler program (> output)
|
||||
|
||||
Both the assembler and the disassembler are run with a program as their
|
||||
sole argument: they take their input from and print their output to
|
||||
stdin and stdout respectively.
|
||||
|
||||
An initial gap created with ORG is not included in an assembled program.
|
||||
All possible interpretations of a disassembled program are included in
|
||||
its listing, resulting in overlapping information.
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
v2.0
|
||||
* Added the ability to shift a register multiple steps in one
|
||||
instruction
|
||||
* Changed the register argument of STORE from RX to RY
|
||||
* Added support for an emulated floppy disc system and modem and an
|
||||
input status register to the emulator
|
||||
* Added the ability to use ORG non-initially to the assembler
|
||||
* Changed tapectl so that it uses arguments for input
|
||||
|
||||
v1.0
|
||||
* Initial release
|
||||
|
52
tapectl.pas
52
tapectl.pas
|
@ -15,37 +15,41 @@ type
|
|||
var
|
||||
Reader, Punch: Tape; //States of the reader and punch
|
||||
State: file of Tape; //File storing the states of the reader and punch
|
||||
DoRead, DoPunch: boolean; //Whether to reset the reader or the punch
|
||||
DoRead, DoPunch: integer; //Whether to (re)set the reader or the punch
|
||||
|
||||
begin
|
||||
|
||||
//Check whether to set the reader, the punch, or both
|
||||
if ParamCount <> 1 then begin
|
||||
writeln ('Usage: tapectl reader/punch/both');
|
||||
//Check the arguments
|
||||
if ParamCount > 4 then begin
|
||||
writeln ('Usage: tapectl (-r tape) (-p tape)');
|
||||
halt (1);
|
||||
end
|
||||
else if ParamCount mod 2 <> 0 then begin
|
||||
writeln ('Usage: tapectl (-r tape) (-p tape)');
|
||||
halt (1);
|
||||
end;
|
||||
if ParamStr (1) = 'reader' then begin
|
||||
DoRead := true;
|
||||
DoPunch := false;
|
||||
if ParamStr (1) = '-r' then begin
|
||||
DoRead := 2;
|
||||
DoPunch := 0;
|
||||
if ParamStr (3) = '-p' then begin
|
||||
DoPunch := 4;
|
||||
end;
|
||||
end
|
||||
else if ParamStr (1) = 'punch' then begin
|
||||
DoRead := false;
|
||||
DoPunch := true;
|
||||
end
|
||||
else if ParamStr (1) = 'both' then begin
|
||||
DoRead := true;
|
||||
DoPunch := true;
|
||||
else if ParamStr (1) = '-p' then begin
|
||||
DoRead := 0;
|
||||
DoPunch := 2;
|
||||
if ParamStr (3) = '-r' then DoRead := 4;
|
||||
end
|
||||
else begin
|
||||
writeln ('Usage: tapectl reader/punch/both');
|
||||
writeln ('Usage: tapectl (-r tape) (-p tape)');
|
||||
halt (1);
|
||||
end;
|
||||
|
||||
//Assign the state file
|
||||
assign (State, ExpandFileName ('~/.tapes.thingamajig'));
|
||||
assign (State, ExpandFileName ('~/.thingamajig/tapes'));
|
||||
|
||||
//Read existing state if any
|
||||
if FileExists (ExpandFileName ('~/.tapes.thingamajig')) then begin
|
||||
if FileExists (ExpandFileName ('~/.thingamajig/tapes')) then begin
|
||||
try
|
||||
reset (State);
|
||||
read (State, Reader);
|
||||
|
@ -64,18 +68,14 @@ begin
|
|||
Punch.Pos := 0;
|
||||
end;
|
||||
|
||||
//Input the files to be read from or punched to
|
||||
if DoRead then begin
|
||||
write ('Reader: ');
|
||||
readln (Reader.Path);
|
||||
Reader.Path := ExpandFileName (Reader.Path);
|
||||
//Get the files to be read from or punched to
|
||||
if DoRead <> 0 then begin
|
||||
Reader.Path := ExpandFileName (ParamStr (DoRead));
|
||||
Reader.Reset := true;
|
||||
Reader.Pos := 0;
|
||||
end;
|
||||
if DoPunch then begin
|
||||
write ('Punch: ');
|
||||
readln (Punch.Path);
|
||||
Punch.Path := ExpandFileName (Punch.Path);
|
||||
if DoPunch <> 0 then begin
|
||||
Punch.Path := ExpandFileName (ParamStr (DoPunch));
|
||||
Punch.Reset := true;
|
||||
Punch.Pos := 0;
|
||||
end;
|
||||
|
|
Loading…
Reference in New Issue