I recently saw a FaceBook question, "Remember Y2K? If you're old enough, you do." Sigh. Whippersnappers these days.
So I replied with:
DEC's "DATE-75" project dealt with a problem in their 15 bit field. The fundamental problems was that people didn't think hardware or operating systems would be around for more than 2^15th days. Wow, there's essentially nothing about it on the web, but I did find this:
... OTOH, the infamous "Date75" (the first of the Y2K-like bugs) hit all Digital Equipment Corporation users of the TOPS-10 operating system. There were only 15 bits used for the date, and they overflowed sometime early in 1975. We got another three bits by hijacking a different field of the file info field, but this did not apply to DECtapes, where there were no spare bits. Oh, wait a minute. The high-order bits of three other fields were not used, so they could be hijacked to form the three high-order bits of the date.
Every program that read and reported a file date had to be modified to support both of these quite different approaches. [And we used to have to walk barefoot through the snow to pick up our output].
I forget when that time started. Ah, looks like 1964. And a 12 bit field. And low-order bit. I don't know when computers started storing dates in file systems, but in 1964, 1975 looked to be a long way off. OTOH, a computer was expected to last 10 years or so, even then, million dollar systems were awfully expensive. Operating systems were developed for each architecture until Unix came along and disrupted that model. Actually, I do have code on the web that deals with DATE75, see http://wermenh.com/folklore/tulip.doc.html
I included an excerpt from that document, part of the assembler listing of the relevant code, with formatting mangled by FaceBook. After comments about "old school" and "we definitely don't code like that anymore," I offered to translate some into C and suggested it might not be more compact than PDP-10 assembler code. So I did, and spent a bit more time at it than I should, so instead of letting FaceBook mangle C code too, here is both the assembler and C code I to add. I might go back to include the first part of the directory listing subroutine later. I've added some comments along the way to describe things that people are not likely to understand these days. One thing first - this code is from an example of how to use the TULIP I/O package, non-PDP-10 instructions like DISIX (DIsplay SIXbit) and WDECI (Write DECimal Immediate) are "Unimplemented User Operations" that trigger a fault that is captured by user level code. They are the heart of TULIP and made I/O in assembler programming much, much easier. See this one page summary of the PDP-10 instruction set for help with that.
DIRSIZ==200 ;SIZE OF DTA DIRECTORY BLOCK DIRADR==^D100 ;ADDRESS OF DIRECTORY BLOCK MAXFIL==^D22 ;MAX # OF FILES THAT WILL FIT ON A TAPE TAPLEN==^D578 ;# OF BLOCKS ON A DECTAPE DIRBYT==0 ;RELATIVE ADDR OF DIRECTORY BYTE MAP (5 BIT BYTES) DIRFIL==^D83 ;RELATIVE ADDR OF FIRST FILENAME DIREXT==^D105 ;RELATIVE ADDR OF EXTENSION/DATE WORD DIRLBL==^D127 ;RELATIVE ADDR OF LABEL WORD MOVEI T4,1 ;MAKE INDEX INTO FILSIZ FOR BLOCKS USED ;NO NEED TO SAVE P1 NOW, USE IT AS AOBJN WORD DIRLOP: SKIPN DIRFIL(P1) ;DOES THIS FILE EXIST? JRST DIRAOB ;NO, TRY NEXT LDB T1,[POINT 12,DIREXT(P1),35];GET LOW 12 BITS OF CREATION DATE MOVEI T2,1 ;CHECK THE BYTE MAP FOR THE TOP 3 BITS TDNE T2,DIRBYT(P1) IORI T1,1B23 ;BRING UPTO 1985 TDNE T2,DIRBYT+MAXFIL(P1) IORI T1,1B22 ;UPTO 2007 TDNE T2,DIRBYT+<2*MAXFIL>(P1) IORI T1,1B21 ;UPTO 2051 (FOR THE PDP-10 IN THE SMITHSONIAN) DISIX [[SIXBIT\%.% % %#!\] WSIX 6,DIRFIL(P1);FILE WSIX 3,DIREXT(P1);AND EXTENSION WDEC 3,FILSIZ(T4);THEN LENGTH PUSHJ P,DATTHN] ;AND CREATION DATE DIRAOB: MOVEI T4,1(T4) ;POINT TO NEXT FILE NUMBER AOBJN P1,DIRLOP ;AND LOOP FOR NEXT FILE POPJ P, ;OR RETURN WHEN DONE
MACRO-10 takes the source within the square brackets and replaces it with the address where it puts the relevant object code. So the DISIX instruction gets the address of memory that has the address of the SIXBIT string followed by instructions that usually print something. It's very much like printf, except that the arguments have the formatting information, and the arguments can be regular instructions or even subroutine calls. As you'll see below, I can't do some of this easily with printf(). Think of SIXBIT as ASCII without lower case. In those years, many terminals were upper case only, and TULIP had control characters to change the output from upper to lower case, so it was easy to write code that would display lower case strings on terminals that could. Also, note that six SIXBIT characters fit in the PDP-10's 36 bit word. They were pervasive in PDP-10 code, e.g. TOPS-10 file names were six characters long with a three character extension.
The PDP-10 instruction set includes a rich collection of medium strength instructions, AOBJN is one of them. It does "Add One to both halves of register and Jump if the result is Negative." I.e. it's often found at the bottom of loops. The high half of the register has the negative of a loop count, and the right half has either an index into an array, or an adress of the array element that was just processed.
;SUBROUTINE TO PRINT CURRENT DATE AND TIME AS ; 'ON <DATE> AT <TIME>' ;USES T1-T4 DATTIM: DISIX [CPOPJ##,,[SIXBIT\&ON % AT %!\];PRINT DATE, TIME, THEN RETURN PUSHJ P,DATPRT ;PRINT CURRENT DATE PUSHJ P,TIMPRT];PRINT CURRENT TIME
This little routine (hmm, one instruction by some counting) isn't called from my sample. The address supplied to DISIX points to a 36 bit word split (by ',,') into high and low 18 bit values, the PDP-10 has many instructions that work with half words. The high half is the address of a "POPJ P," instruction, the standard subroutine return. The '##' merely told the assembler that the symbol might be defined in another source file. After DISIX does the output, it goes to that CPOPJ which saves us from wasting a word of memory to return from this subroutine.
;SUBROUTINE TO PRINT EITHER CURRENT DATE (ENTER AT DATPRT) OR DATE ;PASSED IN T1 (ENTER AT DATTHN). USES T1-T3 DATPRT: DATE T1, ;GET TODAY'S DATE DATTHN: IDIVI T1,^D<12*31> ;T1_YEAR-64 IDIVI T2,^D< 31> ;T2_MONTH-1, T3_DAY-1 WDECI 2,1(T3) ;DAY WNAME MONTAB(T2) ;.MONTH. WDECI ^D64(T1) ;AND YEAR POPJ P, ;AND RETURN ;SUBROUTINE TO PRINT TODAYS TIME. USES T1-T4, EXITS WITH LZEFLG OFF TIMPRT: MSTIME T1, ;GET CURRENT TIME IDIVX T1,^D<60*60*1000>;T1_HOURS IDIVX T2,^D< 60*1000>;T2_MINUTES IDIVX T3,^D< 1000>;T3_SECONDS, T4_THOUSANTHS TXO F,LZEFLG ;PRINT TIME WITH LEADING ZEROS DISIX [[SIXBIT\%:%:%!\] WDEC 2,T1 ;HOURS WDEC 2,T2 ;MINUTES WDEC 2,T3] ;SECONDS TXZ F,LZEFLG ;TURN OFF AS PROMISED POPJ P, ;AND RETURN DEFINE MAKLST (A)< IRP A<SIXBIT/.'A'./>> MONTAB: MAKLST <JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC>
I hate, I hate, I hate the C preprocessor. When I first learned C, I expected that the preprocessor would grow into something broadly useful. Much to my everlasting disappointment, it never did. MAKLST is a MACRO that takes any number of arguments. IRP (Indefinte RePeat) takes each item in the list and, in this case, creates a word of data with a period on either side of the month, e.g.
SIXBIT /.JAN./ SIXBIT /.FEB./ ... SIXBIT /.DEC./
There may be bugs. There are certainly "missing" things like forward references of subroutines. In some places I've done things in more of a C-style than we did in MACRO-10. Some are symbol names (MACRO-10 had a six character limit), some are just the way we write C code.
struct dir { // Layout of the DECtape directory block int dirbyt[83]; int dirfil[22]; int dirext[22]; int dirlbl; } dirblk; dirsub(struct dir *dirp, int *file_size) { int loop_count; // This is the high half of P1, it's the negative of // the loop count int dir_ind; // The low half of P1 is the address of the directory // block, and the PDP-10 code increments in in the // loop to access the elements in the block's arrays. // We use a separate index here so we won't offend // the C compiler with weird casts and increments. int file_num; // Register T4, index to a file size table for (loop_count = -MAXFIL, dir_ind = 0, file_num = 1; loop_count; loop_count++, dir_ind++) { if (dir->dirfil[dir_ind]) { t1 = dirp->dirext[dir_ind] & 07777; // Low 12 bits of creation date if (dirp->dirbyt[dir_ind] & 1) t1 |= 010000; // Bring upto 1985 if (dirp->dirbyt[MAXFIL + dir_ind] & 1) t1 |= 020000; // Upto 2007 if (dirp->dirbyt[2*MAXFIL + dir_ind] & 1) t1 |= 040000; // Upto 2051 (for the PDP-10 in the Smithsonian) printf("%6s.%3s. %3d %s\n", dir->dirfil[dir_ind], \ dir->dirext[dir_ind], file_size[file_num], date_then(t1)); } } } /* * Subroutine to print a date encoded as (((year - 1964) * 12 + (month - 1)) * 31) + day - 1. * We can't call printf recursively as a printf argument, so we have to settle for * returning a sprintf string that is generated while arguments are evaluated. */ char *date_print() { return date_then(DATE_SYSCALL()); } char *date_then(int date) { int day, month, year; char buff[10]; // Overflows in year 2000! Y2K bug! day = date % 31 + 1; date = date / 31; month = date % 12; year = date / 12 + 64; return sprintf(buff, "%2d%s%d", day, montab[month], year); } /* This horror is about the best I can do with cpp. Of course, C programmers don't do this. */ #define x(month) "." #month ".", #define MAKELIST \ x(Jan) \ x(Feb) \ x(Mar) \ x(Apr) \ x(May) \ x(Jun) \ x(Jul) \ x(Aug) \ x(Sep) \ x(Oct) \ x(Nov) \ x(Dec) char *montab[] = { MAKELIST };
Contact Ric Werme or return to his home page.
Written 2021 Jan 13, last updated 2024 Mar 29.