diff --git a/emulator.pas b/emulator.pas index b2df9c3..652353d 100644 --- a/emulator.pas +++ b/emulator.pas @@ -31,20 +31,31 @@ const {$endif} var - Hlt, Echo: boolean; //Halt and echo flags + Hlt, Echo{$ifdef floppy}, DiscRead, DiscWrite, DiscTrackSet, DiscSectSet{$endif}: boolean; //Halt and echo flags and disc system 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 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 Verbose, IC, LFX: integer; //Verbose flag, instruction counter for CPU speed, and line feed position marker - Fetched: byte; //Fetched byte + 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} //Terminal output procedure Output; @@ -68,9 +79,9 @@ end; {$ifndef fast} //Wait to emulate CPU speed of roughly 500 KIPS -procedure wait; +procedure wait (I: integer); begin - if IC div 500 = 0 then sleep (1) + if IC div 500 < I then sleep (I) else begin sleep (IC div 500); if IC mod 500 >= 250 then sleep (1); @@ -87,7 +98,7 @@ begin //Terminal input if W = $ffff then begin {$ifndef fast} - wait; + wait (1); {$endif} //Read a keypress Ch := ReadKey; @@ -116,8 +127,7 @@ begin //Tape reader else if W = $fffd then begin {$ifndef fast} - wait; - sleep (1); + wait (2); {$endif} assign (Tapes, ExpandFileName ('~/.thingamajig/tapes')); //Check the reader state @@ -153,6 +163,21 @@ begin 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} //Unused addresses else if W > LastRAM then B := 0 //Regular load @@ -166,7 +191,7 @@ begin //Terminal output if W = $ffff then begin {$ifndef fast} - wait; + wait (1); {$endif} Ch := ansichar (B); Output; @@ -176,6 +201,9 @@ begin {$ifdef printer} //Printer else if W = $fffe then begin + {$ifndef fast} + wait (1); + {$endif} assign (Prn, '/dev/usb/lp0'); try rewrite (Prn); @@ -189,8 +217,7 @@ begin //Tape punch else if W = $fffd then begin {$ifndef fast} - wait; - sleep (19); + wait (20); {$endif} assign (Tapes, ExpandFileName ('~/.thingamajig/tapes')); //Check the punch state @@ -246,6 +273,172 @@ begin 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 $c = 0 then begin + 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 + //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 + 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); + read (Disc, DiscSB [DiscSBP]); + end; + 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} //Regular store else if W <= LastRAM then Mem [W] := B; end; @@ -281,6 +474,21 @@ 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} + //Check the arguments if ParamCount = 0 then begin writeln ('Usage: emulator (-v) program (2> verbose_output)'); @@ -443,7 +651,7 @@ begin end; {$ifndef fast} - wait; + wait (1); {$endif} end. diff --git a/floppyctl.pas b/floppyctl.pas new file mode 100644 index 0000000..3a99785 --- /dev/null +++ b/floppyctl.pas @@ -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. diff --git a/readme.md b/readme.md index 48bcd8b..0edb0ea 100644 --- a/readme.md +++ b/readme.md @@ -8,11 +8,10 @@ https://ahti.space/git/crazyettin/Thingamajig. Included Software ----------------- -The repository includes an emulator implementation of Thingamajig with a -control program for the emulated paper 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 +storage 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 -------------------- @@ -92,12 +91,13 @@ 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 and Tapectl --------------------- +Emulator and Storage Device Control Programs +-------------------------------------------- Usage: * emulator (-v) program (2> verbose_output) * tapectl (-r tape) (-p tape) + * floppyctl (-0 disc) (-1 disc) 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 @@ -117,12 +117,36 @@ 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 high speed (roughly 500 CPS in and 50 CPS out) -8-bit paper 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 with the arguments -r and -p respectively. +printer, an emulated high speed (roughly 500 CPS in and 50 CPS out) +8-bit paper tape reader and punch, and an emulated two-drive 8" floppy +disc system with the arguments -dprinter, -dtape, and -dfloppy +respectively. The printer is mapped to address FFFE, the tape reader and +punch to FFFD, and the disc system to FFFB and FFFC. 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 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 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 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. + +The commands for the disc system are: + 0: Reset the system. + 1: " + 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 the disc to the buffer. + 7: Write a sector from the buffer to the disc. The IPL loads the program specified as an argument when running the emulator.