Compare commits

...

19 Commits
v1.0 ... main

Author SHA1 Message Date
CrazyEttin 1f7bc8b5b5 Version 2.0: switching to a semver-ish system 2022-10-15 13:51:14 +03:00
CrazyEttin 64ddf705ea Add auto-answering and auto-hanging functionality as well as the ability to control answering and hanging via a memory-mapped device to the emulated modem 2022-10-05 20:23:26 +03:00
CrazyEttin e626369a16 Correct some bugs in the emulator and tweak the readme 2022-10-03 00:57:36 +03:00
CrazyEttin 85495426b2 Tweak the emulated modem 2022-10-02 19:09:32 +03:00
CrazyEttin d324e795ed Make the emulated modem recognise dropped connection and act accordingly 2022-10-02 14:05:28 +03:00
CrazyEttin f8b27b8465 Add server functionality to the emulated modem 2022-10-01 19:54:30 +03:00
CrazyEttin 7c2905b558 Add input status register to the emulator 2022-09-30 17:07:10 +03:00
CrazyEttin da67a19ac6 Add option for emulated modem 2022-09-27 11:21:14 +03:00
CrazyEttin 23b32d7835 Add disc formatting and deal with disc sectors with an unset sync bit 2022-09-23 13:25:08 +03:00
CrazyEttin bf36aab7a4 Add a floppy disc system option and tweak the timings to make them more accurate 2022-09-23 11:56:31 +03:00
CrazyEttin 0b07a2b214 Add more detail to the readme, reword tapectl instructions slightly, and modify the behaviour at the buffer end in Echo 2022-09-22 01:04:43 +03:00
CrazyEttin 2feaba4ee9 Adjust the tape reader and punch timings to be more realistic and remove the artificial speed limit from the printer since it is not emulated 2022-09-19 02:05:29 +03:00
CrazyEttin 3f7cf0d8ea Reword the readme slightly 2022-09-18 16:34:36 +03:00
CrazyEttin 5fd7fa707f Make tapectl use arguments for input, make the emulator order-agnostic with its arguments, and include running instructions for the included software in the readme 2022-09-16 15:04:05 +03:00
CrazyEttin f0e676facb Fix a bug with non-initial ORG statements in the assembler 2022-09-15 22:26:30 +03:00
CrazyEttin 9d318c4c75 Add the same speed limitations to the IPL that already applied to the CPU in the emulator, make it possible to use ORG in non-initial positions, and add information on flow control to the readme 2022-09-15 20:54:29 +03:00
CrazyEttin c78d7c69ef Reword the readme slightly 2022-09-13 00:05:37 +03:00
CrazyEttin 13e56f57a7 Make the CPU timing more precise 2022-09-12 20:32:15 +03:00
CrazyEttin d4720f48c2 Add ability to shift multiple steps in one instruction 2022-09-11 19:44:53 +03:00
9 changed files with 1071 additions and 135 deletions

View File

@ -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

View 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

View File

@ -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.

View File

@ -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

View File

@ -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

73
floppyctl.pas Normal file
View File

@ -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.

118
modemctl.pas Normal file
View File

@ -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
View File

@ -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


View File

@ -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;