• Please review our updated Terms and Rules here

PDP-8 OS handlers

Correct Dave.

The only time there can be an issue is if the application talking to the console sends the wakeup command which is unlikely. The handler puts the console port back the way it was when it was called so the console looks the same after as it was before.

There isn't even a delay if the TSF flag set when the handler was called. And I wait a second character time before I check the keyboard to make sure I catch that after the serial disk server should have switched to serial disk mode. The sequence of events is as follows.
  1. The handler is entered. The user program may or may not have a character in transit to the console.
  2. Wait for up to a character time for the TSF flag to be set. The serial disk server will process this last character if there is one in console mode. OS/8 does not use interrupts so nothing else can affect this.
  3. Remember the state of TSF so we can put it back when we are done.
  4. The handler sends the Wakeup code of 0020 through 0027. Think of this as an escape code for the disk.
  5. The handler sends the address of the application that called the handler.
  6. Read the state of the keyboard flag and the character in the input buffer and save this state. At this point the serial disk server has been in disk mode for 2 character times and will no longer send anything else from the console keyboard.
  7. Send the keyboard state to the server since it will have to re-send the character at the end to put the character back in the keyboard buffer. (I am not sure this is necessary.)
  8. Handler goes into command mode where it does what the server tells it to
  9. One command is Read a block of memory and send it to the server which can be thought of as a disk write.
  10. Another command is Write a block of memory which can be thought of as a disk read.
  11. The final command is to transfer control to a specific location in memory with the AC loaded with an arbitrary value. This last one will clear the TSF flag if necessary to match the state at call. The handler will then wait for the KSF flag to be set indicating that the serial disk server has resent the code that was in the buffer at call. It will clear the KSF flag if that was the state upon entry. Finally it will load the AC and perform the jump. Most of the time this will be the return to the handlers caller.
  12. The server at this point goes back into console mode.
The only thing that allows this to work is the fact that almost nothing sends binary data to the console. The classic incrementing the code and sending it to the console test program would be an example of an ill behaved program but the server can shrug this off because the code sent after the 0020 through 0027 wakeup code will not look right. In fact most cases can be detected because the follow up character doesn't look like what would be expected.

There are programs that use interrupts. The FTRS in FORTRAN comes to mind. But they universally turn them off when they call OS/8 as this is a requirement.

I hope that is clear. Since it isn't finished I may find I need to add or can delete some of that but for the most part the above is the idea I am shooting for. I've only been thinking about this on and off for 4 or 5 years now.
 
I set up a Raspberry Pi to use as the server last night. I don't have a serial port for it yet and there are several ways to deal with that.

This morning I revisited the boot loader and found that because it is using the console port I could simplify. One of the restrictions of code loaded by the help loader is that device code 03 is the largest that can be used. Fortunately the KSF and KRB instructions are device code 03. Here is the now 8 instruction sequence that needs to be hand toggled into a Straight 8, 8/s, 8/i, or 8/l. For the others it could be done with Roland's M847 or a reprogrammed DEC M847. Or you could hand toggle it in.
Code:
             / CONSOLE SERIAL DISK HELP LOADER

             / TO MINIMIZE THIS WE EXPECT THIS TO BE RUNNING AND THEN TELL THE SERVER
             / TO SEND THE HELP BOOT CODE.

                     NOPUNCH

             /BEGINNING OF HAND TOGGLED CODE

             / POSITION SO THAT BOOT1 IS AT 0037 WHERE IT GETS OVERWRITTEN BY A
             / JMP BOOT2 WHICH STARTS THE BOOT2 LOADER.

       0037          *0037

             START,                  /NO WAKEUP CODE IS SENT.  AC & LINK MUST BE 0
00037  6031  BOOT1,  KSF             /SKIP IF SERVER HAS SENT US A CHARACTER
00040  5037          JMP .-1         /WAIT FOR A CHARACTER
00041  6036          KRB             /READ THE CHARACTER
00042  7012          RTR             /MOVE BITS INTO POSITION
00043  7010          RAR
00044  3001          DCA Z 1         /STORE THE INSTRUCTION IN MEMORY.  (SEE NOTE)
00045  2044          ISZ .-1         /BUMP THE STORE ADDRESS
00046  5037          JMP BOOT1       /GO DO NEXT ONE

             / NOTE: THE DCA Z 1 IS MODIFIED BY THE ISZ .-1 THAT FOLLOWS IT TO CHANGE
             / THE STORE ADDRESS.  THIS LETS US POKE OUR LIMITED INSTRUCTIONS
             / SEQUENTIALLY ON PAGE ZERO.  WE START AT ADDRESS 1 BECAUSE THE ADDRESS
             / ZERO IS THE RETURN ADDRESS OF A SUBROUTINE AND DOES NOT MATTER.

             /END OF HAND TOGGLED CODE

                     ENPUNCH
                     $

No detected errors
No links generated
It doesn't have to be at 0037, I just chose that so when I find the inevitable bugs in the BOOT2 program I won't have to move it. At the moment the best place would be 0033 and I may move it down later. Moving it would require the server to send a few less characters during boot. Every little bit helps.

I spent a lot of time understanding just how clever the DEC help loader is and I still think it looks like it can't possibly work. It seems like you could make it shorter by one instruction if you use and auto-index register but then you would have to initialize the auto-index register so it is worse from that standpoint. This is shorter than the DEC help loader because I don't have to deal with the paper tape leader code.
 
Of course the hand toggled boot code moved! It now starts at the last location of the BOOT2 code. I got rid of one instruction from BOOT2 because of this. Same instructions, just starts at 0032 now.

Lets take a step back and talk a little about handlers. I've written a whole book about them so this is just a very brief summary.

System handlers consist of three pieces.
  1. The boot code that you type in or is in the M847 boot loader board. I call this BOOT1.
  2. The boot code that BOOT1 loads in which then copies sector 0 into memory. Part of sector 0 is the SYStem handler and part is the resident part of OS/8. When complete this code jumps to 7600 to start OS/8. I call this BOOT2. Normally BOOT2 becomes part of sector 0 when you run the BUILD program.
  3. The system handler which resides in memory from 7607 through 7743 of field 0 and normally becomes part of sector 0 when you run the BUILD program.
I have addressed BOOT1 in the previous post. In this post I will address the BOOT2 program which I list a portion of here. What I don't list here is a LOT of comments.
Code:
  185        0000  *0000
  186 00000  0000  GETNUM, .-.
  187 00001  6031          KSF             /GET A CHARACTER FROM SERIAL DISK SERVER
  188 00002  5001          JMP .-1
  189 00003  6036          KRB
  190 00004  7006          RTL             /LEFT SHIFT 6 BITS TO BUILD UPPER HALF
  191 00005  7006          RTL
  192 00006  7006          RTL
  193 00007  3033          DCA TEMP        /SAVE FOR COMBINE
  194 00010  6031          KSF             /GET LOWER 6 BITS
  195 00011  5010          JMP .-1
  196 00012  6036          KRB
  197 00013  1033          TAD TEMP
  198              / THE FIRST TIME THROUGH THE DCA . GETS REPLACED WITH JMP I GETNUM
  199 00014  3014          DCA .           /THIS WILL OVERWRITE ITSELF WITH JMP I GETNUM
  200              /
  201              / VARIABLES
  202 00015  5001  CA,     JMP GETNUM+1    /AND WE RESTART GETNUM THE FIRST TIME THROUGH.
  203 00016  0000  WC,     .-.             /WORD COUNT USED FOR BLOCK TRANSFER
  204
  205              / THIS IS THE MAIN LOOP OF BOOT 2.  THE SERVER SENDS IT THREE ARGUMENTS.
  206              / 1) THE ADDRESS-1 WHERE THE BLOCK WILL BE STORED.
  207              / 2) THE TWO'S COMPLEMENT OF THE WORD COUNT.
  208              / 3) AN INSTRUCTION TO BE EXECUTED.  USUALLY A CDF TO THE DESIRED FIELD.
  209              / THE SERVER WILL THEN SEND WORD COUNT NUMBER OF 12 BIT WORDS THAT WILL
  210              / BE STORED SEQUENTIALLY STARTING AT THE SPECIFIED ADDRESS.
  211              / THE DATA WILL BE WRITTEN IN THE FIELD SPECIFIED BY THE CDF INSTRUCTION
  212              / SPEFICIED IN THE THIRD ARGUMENT.  THE ADDRESS GIVEN IN THE FIRST
  213              / ARGUMENT USES AN AUTO INDEX MEMORY LOCATION SO IT MUST BE SPEFICIED AS
  214              / ONE BEFORE THE DESIRED ADDRESS SINCE THE AUTO INDEX INCREMENT TAKES
  215              / PLACE BEFORE THE MEMORY WRITE.  THE WORD COUNT IS PASSED AS THE TWO'S
  216              / COMPLEMENT SINCE AN ISZ TYPE LOOP IS USED.  A ZERO PASSED AS THE WORD
  217              / COUNT WILL TRANSFER 4096 WORDS.
  218              / THE FIRST TIME THROUGH THE LOOP A DCA I CA WILL NEED TO BE PATCHED
  219              / OVER THE DCA .  THE WORD COUNT WILL NEED TO BE ONE GREATER FOR THE
  220              / FIRST BLOCK TRANSFER.
  221              / ONCE ALL DATA HAS BEEN TRANSFERED AND YOU WISH TO TRANSFER CONTROL TO
  222              / THE NEWLY LOADED CODE THE IDEA IS TO USE THE FIRST ARGUMENT AS THE
  223              / DESTINATION ADDRESS FOR A JMP I CA INSTRUCTION WHICH WILL BE SENT
  224              / INSTEAD OF A CDF FOR THE THIRD ARGUMENT.  THE WORD COUNT WOULD BE
  225              / IGNORED IN THIS CASE.  THIS DOES NOT DIRECTLY SUPPORT JMPS TO FIELDS
  226              / OTHER THAN 0.  IN THE CASE OF OS/8 WE JUST NEED TO JMP TO 7600 IN
  227              / FIELD 0.
  228
  229 00017  4000  BOOT2,  JMS GETNUM      /FETCH A 12 BIT VALUE
  230 00020  3015          DCA CA          /SAVE THE STORE ADDRESS
  231 00021  4000          JMS GETNUM      /FETCH ANOTHER 12 BIT VALUE
  232 00022  3016          DCA WC          /SAVE THE WORD COUNT
  233 00023  4000          JMS GETNUM      /FETCH THE CDF TO SPECIFY THE FIELD
  234 00024  3025          DCA NEWDF       /PUT IT INLINE
  235 00025  0000  NEWDF,  .-.             /WILL BE A CDF
  236 00026  4000  BT2LP,  JMS GETNUM      /FETCH ANOTHER 12 BIT VALUE
  237 00027  3027          DCA .           /THIS GETS PATCHED TO A DCA I CA
  238                                      /WHICH STORES THE VALUE AT MEMORY POINTED TO BY CA
  239              /       DCA I CA        /CA IS AUTO-INDEX
  240 00030  2016          ISZ WC          /SKIP IF DONE
  241 00031  5026          JMP BT2LP       /GO DO ANOTHER
  242 00032  5017          JMP BOOT2       /DO THE NEXT ONE
That is what will be in memory when BOOT1 has completed. Getting there involved converting the 12 bit instructions into the 8 bit help codes BOOT1 expects to see. I have a program called hlpgen.c which is fed the above code excluding the blank and comment lines and it spits out this:
Code:
/ 01  317  6031
/ 02  012  5001
/ 03  367  6036
/ 04  067  7006
/ 05  067  7006
/ 06  067  7006
/ 07  331  3033
/ 10  317  6031
/ 11  102  5010
/ 12  367  6036
/ 13  334  1033
/ 14  145  3014
/ 15  012  5001
/ 16  000  0000
/ 17  006  4000
/ 20  151  3015
/ 21  006  4000
/ 22  161  3016
/ 23  006  4000
/ 24  251  3025
/ 25  000  0000
/ 26  006  4000
/ 27  271  3027
/ 30  165  2016
/ 31  266  5026
/ 32  172  5017
The first octal number is the address. The second octal number is the 8 bit help code. The third octal number is the instruction that will end up placed in memory. The way it will work is you toggle in the BOOT1 program and load the starting address of 0032 and tell it go. Then you start the console serial disk server in boot mode and it starts off by sending the help codes above in the blind (no handshake). Once the last help code is sent the BOOT2 program takes over and the server then sends a JMP I 0 instruction which will patch the DCA . at the end of the GETNUM subroutine that starts at 0. The server then needs to send the CA (Current Address) minus 1 of the first block to be written to memory. It then sends the two's complement of the WC (Word Count) plus 1 (this time only for the plus 1). It sends the CDF instruction for the field to be written to. And then it sends a DCA I CA instruction to overwrite the DCA . at address 0027. It is this last patch that is the reason why the WC must be one extra. After this the server sends the block of data and it will get written to memory where it is told. In the case of OS/8 the server will tell it to write the OS/8 resident code on field 1 as the first transfer. The second block will be the SYS handler that goes on field 0 starting at 7600 and then it will send a CA of 7577 a WC of 0 because it doesn't matter and instead of the CDF it will send a JMP I CA instruction which will cause control to transfer to OS/8 at 7600. The beauty of having a smart device on the other side of the serial cable is that you can load anything you want into any field, it does not have to be OS/8. I will probably add a bunch of the diagnostics to the console serial disk program. Having just gone through this with the DECset testing I am really glad Roland put the Bin loader into his M847 clone board.

In my next post you will see the handler portion after I finish looking it over again. I am currently adding the console serial disk server code to my emulator and writing the standalone server code to run on a Rasp Pi. I believe when I am all done that writing the standalone server is going to be the most difficult portion of process. As for testing on real hardware I have three choices. The Straight 8. It is currently suffering from neglect and won't boot OS/8 from DECtape. There are so many things that can cause this that I feel it would be a time sink to work on it right now. Also, almost nobody has a Straight 8 so that will be the third choice. The second machine is my trusty 8/a. This would probably be the best choice as it currently has a working RX01 (15 minute warmup required). The third choice is the DECset 8k pdp-8/e which I have been working on. Yeah, it is going to be the DECset. The 8/a is in a rack in the wrong room.

And so completes my status update. Please read and comment, especially if you find a mistake. And don't worry about asking questions. If something doesn't make sense I need better comments and your questions will help address this.
 
Went through the handler with a fine toothed comb and found a half a dozen bugs. Cleaned up some odd constructs and changed a couple of variable names. Moved three variables to the data break addresses to make more room. There are now 4 words free which might just be enough to add the other 3 drives to the SYS handler. I am going to leave it at one drive until I get it actually working.
 
Doug,

Could you put your code out on Github so folks can follow along?

(Not relevant to your effort). I tried my own improvements to SerialDisk a few years back, mainly to learn more about PDP8 assembly and OS/8. The wall I hit was the size restrictions of the OS/8 handler and trying to save a word here and there. One of the things I tried was sending the bytes to the external server process in a different order (than Kyle’s code) since it was more word-thrifty on the Pdp8 side. I wanted to still keep interoperability with Kyle’s OS/8 handler though. In the end, I used lower case command codes to identify my handler, and left Kyle’s code using upper case commands. The server code handled the appropriate byte order send/receive based on the codes sent. The complexity was handled in the C server code as it was easy to modify and test there.

Long story short: I wish I had used github for my code so I wouldn’t have to go digging through disks and usb sticks to find it!

-Crawford
 
I managed to get the Pi to talk to the 8 at 9600 baud. I have a loopback test program running on the Pi and on the 8 I am running the short echo program I always use.
Code:
0200 6031    KSF
0201 5200    JMP 200
0202 6036    KRB
0203 6046    TLS
0204 5200    JMP 200
I believe that is the shortest character echo program.

The program on the Pi sends a character, reads it back in while counting the number of times the read didn't block, and verifies the received character was the same as transmitted.

The next step is to put the code in it to make it look like a teletype. This is the part that doesn't exist in any form in the old Serial Disk server. It shouldn't be too difficult. I am also keeping in mind that a lot of people will want to have two serial ports on the server, one going to the pdp-8 and the other going to their old console device. I have several terminals but I will probably dig out a teletype model 43. This never worked well with the Straight 8 because I couldn't get it to send mark parity which is what the 8 wants. That should not be a problem here as the Pi can do that part..

I will eventually put the code up on github. For the moment I will post pieces of it here in this thread so people can follow along.

The easiest speedup to the original serial disk would be to go to 6N1 and use BSW to swap the upper and lower halves of the words. I have an untested NON-SYS handler that does 2 word to 3 byte and 3 byte to 2 word packing which gives a little bit better performance than going to 6 bits on the serial link. I had to use every coding trick I know to get it to fit in a page. And it has not been tested as I felt it required more than simple changes to the server. Someday....
 
I figured out how to put the tty keyboard from my ssh connection into non-canonical mode, turn off translations, echo, and ctrl C detection. I want to use the F1-F12 keys to change the operating state of the server so I spent some time working on that. There is madness is the mappings and it is not helped by the terminal program I am using.

What I mean by changing the operating state: Normal operating state will be terminal mode where you type a character and it is sent to the PDP-8 and characters the PDP-8 send are displayed on the glass teletype. One function key will force the server into boot mode where it will send the help codes and then the boot record to the pdp-8. One function key will cause the server to copy the 8 bit data to a file in the same way as turning on the teletype punch. Another Function key will turn this off. There will be a pair of keys that will send an 8 bit binary file to the PDP-8 as if you had mounted a paper tape and told it to read. There will be a function key to quit out of the server. I am certain that there will be more of these but that is what I have planned for the moment.
 
More work on the server. Finished mapping the Function key sequences for the Rasp pi keyboard. They are mostly the same as the ones Mobaxterm sends.

I also added the simple teletype conversions to make it look like a teletype attached to an 8.

The plan is to use shift F12 to initiate the boot sequence.
 
Hi Doug, I'm Just very curious to the code you are making :) I really like the idea that we can give these old machines a bit extra functionality with adding a bit of modern technology and new software. Too bad we can't emulate a bit of extra memory trough the serial port 😁

Regards, Roland
 
Moved pieces of code into their own source files for modularity.

Added a program to do a loopback test. It sends 8k characters of a 0 through 0377 counting sequence and verifies the echo. If this runs correctly then the serial link should work.

I have part of the boot code in the server now. Just need to debug it. You hit shift F12 on the console keyboard and the server blindly starts sending the OS/8 boot.
 
Hi Doug, I'm Just very curious to the code you are making :) I really like the idea that we can give these old machines a bit extra functionality with adding a bit of modern technology and new software. Too bad we can't emulate a bit of extra memory trough the serial port 😁
You could do something crazy with the timeshare option like emulate extra banks of memory. You would need to trap CDF, CIF, GTF and RTF at the least. Swapping via serial port would be painful in the extreme I am afraid.

I am looking forward to getting this going and then writing a version of the server for my 8/a instead of running it on a RasPi. In that case there would be no modern technology involved.
 
I spent several hours yesterday coding on the server. I am about to fire up the 8/e and do some debugging. Still quite a ways from done but enough is there to debug some stuff.
 
Did some debugging (On real hardware no less. Yeah, I know! None of this simulation garbage!) and surprisingly what is there seems to work. It acts like a Teletype console. It detects the context switch to disk mode. It sees the shift F12 as the command to initiate the boot and sends the help loader codes and starts to try to boot.

I verified memory at the point just after the help loader has finished and the boot2 code is active but not patched (the help loader can't load instructions that do indirection). Everything looks correct except for one thing. The first instruction of the hand toggled code is supposed to be overwritten by a JMP BOOT2 which transfers control to the longer and more capable loader. And that word was changed appropriately. The word after that one also got changed to a 0301 and I don't see how that happened.

One thing I don't like is the help loader that you have to hand toggle in gets changed so you can't run it a second time without fixing the words that get changed. I am going to have the server put it back as its first act. Of course if the server is messed up this won't work. This probably will only matter during my debugging stage so it might not be worth spending any time on.

The next thing to do is create a disk image and use build to put the handler on it. Then get the server to load it. My current thinking is to read the image into memory and do all operations to and from the memory image. Only write it out when the server closes or you ask it to. This will help protect the micro SD card. They do not have infinite write cycles and depend on a lot of factors it could be as low as only 500 cycles but it is probably closer to 10000. Even that is not all that many.
 
Only write it out when the server closes or you ask it to. This will help protect the micro SD card.
Sounds good to me! That is also the way how my VRC 8256 RK05 emulator works. In this way you can do whatever you want without messing up your original image. But don't forget to save it if you want to keep your modifications. I have lost my work several times... 😤

VRC 8256.jpg
 
I guess I am more OCD than I care to admit. At least when it comes to programming. And since I am retired and my time is mostly my own I find I have become a perfectionist. When coding in the real world, you have deadlines. This partly explains why there is so much bad code out there.

I found I was unhappy with the way the boot code worked. It was too complex and somewhat difficult to understand. So I re-wrote it again this morning. The new code is more like the way most handlers work, namely:
  1. Toggle in and start running the boot code or flip the boot switch which does essentially the same thing.
  2. The boot sector is read into memory.
  3. Words 47 through 177 of the boot sector is copied to field 1 addresses 7647 through 7777.
  4. Words 200 through 377 is copied to field 0 addresses 7600 through 7777.
  5. A JMP is performed to Field 0 address 7605 which starts up OS/8.
All of the handlers pretty much do that although some of them combine parts in clever ways.

The serial disk program works the same way although the boot code that you have to toggle in was painfully long (22 instructions I think). Fixing that was my only contribution to the original serial disk so far. We went from 22 words down to 9 that have to be toggled in. This did require a change to the server program which Vince did. It is kind of clunky because of choices I made. At the time the only goal was to minimize the code you have to toggle in. Here is the current code you need to toggle in.
Code:
BOOT1,  KSF
        JMP .-1
        KRB
        RTR
        RAR
        DCA 1
        ISZ .-1
        JMP BOOT1
This is the shortest sequence that we know of to load in code that does not use a data break device. It is based on the original DEC HELP loader although it is shorter because we don't need to worry about skipping paper tape leader. The version currently on Github is one instruction longer because it sends a wakeup code of zero to the server. I eliminated sending the wakeup code principally because it makes it one instruction shorter. Vince told me that he would see the server go into boot mode by mistake when he removed power from the PDP-8 as the server would see a Break condition on the serial line and interpret that as a boot wakeup. This loader can reside pretty much anywhere on page 0 but it is best to place it right after the boot 2 code so you don't have to send a bunch of padding codes. This is because the secondary boot takes control from it when it overwrites the KSF with a JMP.

What was bothering me about my code was all the extra stuff the server had to do during boot to support the boot. What I have now has the server doing only 1 additional thing on boot. The process goes like this:
  1. Toggle in and start running the boot1 code.
  2. Start the server with the -B option or if already running Press Shift F12 on the server keyboard. This signals boot state to the server.
  3. The server sends the help codes as a sequence of 8 bit characters. This loads the boot2 code which takes control from boot1. The boot2 code reads the boot block the server is about to send and proceeds as any other boot detailed above.
  4. The server sends the boot sector as pairs of characters.
  5. The server goes into console mode.
What I am replacing was my attempt to make something wonderful. (Why is it that making something wonderful almost never works out for the best?) It included step 3 above but had a bunch of additional futzing around with sending patch codes to the boot loader to get around the help code limitations. I have this working but it makes the server boot code ugly! I think Vince would agree with me on this but he was too nice to say it.

The boot portion of the server now looks like this:
Code:
for( i=0; i<sizeof(helpboot); i++ ) pdp_putch(helpboot[i]);
for( i=0; i<256; i++ ) pdp_put12(disk[0][i]);
Which is not too ugly.
 
I spent several more hours this evening cleaning up the re-write and fixing the comments. I also generated the new help boot code. Tomorrow it will be time to test it.

The next bit will be putting the code in the server to read and write the disk image so I can feed the boot code the real boot block. I guess I also need to make a disk image that has this handler on it as the system device.

I feel like I made good progress today.
 
I think Vince would agree with me on this but he was too nice to say it.
My main unspoken concerns were really about the migration path, as there are a bunch of existing SerialDisk implementations, not all of which will get properly re-worked with the latest code. I feared that the next bootloader/server would not be "upward compatible" in the sense that the new server could be used with the older drivers and bootloaders.

What you are working on now is sufficiently different that I'm not sure that backward compatibility will even be a thing.

Wrt your OCD, I think of it as an "EE" thing. Unlike "proper engineering", my background is software, where the assumption is that "anything useable today is far better than something better tomorrow, let alone something perfect next year". There are pros and cons to each approach.

The standards for engineering are different. When a bridge or a building falls down, that's (rightly) a scandal. In software a "self driving feature" that kills a few riders is somehow par for the course.
There's something hard and cynical in there about how easy it is to blame the users for believing the hype, too. But that's all way off subject.

Vince
 
"Wrt your OCD, I think of it as an "EE" thing. Unlike "proper engineering", my background is software, where the assumption is that "anything useable today is far better than something better tomorrow, let alone something perfect next year". There are pros and cons to each approach."

Sometime success hinges on "gettin' there firstest with the mostest."

 
Back
Top