resolved conflict

This commit is contained in:
Will Senn
2016-03-01 10:18:55 -06:00
19 changed files with 1583 additions and 869 deletions
+254 -23
View File
@@ -1,42 +1,273 @@
Basics of the Unix assembler syntax Basics of the "Unix v0" 'as' assembler syntax and PDP-7 coding:
Lines starting with " are comment lines ASSEMBLER SYNTAX:
=================
Anything from " to end of line is a comment.
. is the location counter, where the next code or constant will be placed . is the location counter, where the next code or constant will be placed
.. is the relocation counter .. is the relocation counter (not currently handled)
t = 0 Sets a built-in variable to this value without generating any machine code. Symbol names start with a letter, and can contain letters and "."
Variables so far are . .. and t. Not sure what t's purpose is yet. The dump of label values in "scans/sysmap" appears to show truncation
after 8 characters (not currently enforced by the "as7" Perl script).
orig: is a label. It looks like labels can have dots in them. symbol = expression
Dots seem to separate structures and fields, e.g. u.base
but there are some symbols that start with dots, e.g. .seek
jms copy; 10; u.rg+2; 6 Semicolons separate instructions and Sets a built-in variable to this value without generating any
word expressions that follow the instruction machine code.
Some expressions are 0:0, not sure about this. as7 enters machine instructions and the indirect bit (i)
Is it two 9-bit fields? No idea into the variable table.
label: is a label.
single digit decimal numbers can be used as "local" labels,
which are referenced with Nf and Nb:
jmp 1f Jump back to the closest 1: label jmp 1f Jump back to the closest 1: label
jmp 1b Jump forward to the closest 1: label jmp 1b Jump forward to the closest 1: label
Some lines are indented differently to others e.g. multiple words can be entered on one line, separated by semi-colon.
jms betwen; o10000; o17762
jms error
dac .+1
I think this is to indicate the skip logic, but it plays no part in the assembly syntax.
-1 occurs instead of instructions, so it looks like the assembler allows
literal constants at any time.
String literals seem to be two characters decorated with < > characters, e.g. String literals seem to be two characters decorated with < > characters, e.g.
"ab" is <a>b. I'm guessing these are placed as pairs in one word. The syntax "ab" is <a>b. I'm guessing these are placed as pairs in one word. The syntax
is confusing, because I've also seen <no> ; 040 ; <fi> ; <le>; <s 012 which is confusing, because I've also seen <no> ; 040 ; <fi> ; <le>; <s 012 which
appears to mean "no files\n". appears to mean "no files\n".
Looks like a line can contain multiple labels, e.g. o12: d10: 10 A line can contain multiple labels, e.g. o12: d10: 10
Numeric literals: 0xx are octal values, [1-9]xx are decimal values. Numeric literals: 0xx are octal values, [1-9]xx are decimal values.
CONVENTIONS:
============
Because there is no immediate mode (and "as" lacks the literal syntax
found in DEC assemblers), there is a convention for literal (manifest)
constants:
dNNN indicates the location of a constant for decimal NNN.
oOOO for octal constant OOO.
dmN (decimal minus) indicates a constant for decimal -N.
High order bits of the "law" (load ac with word) instruction are all
ones, so "-1" as an instruction loads -1 into AC.
Subroutine arguments are often located in the words following the
call. When an argument is a variable, sometimes a "0:" local label is
used to tag the location:
jms namei; 0:0
Indentation is (sometimes) used to indicate an instruction
or subroutine that may skip:
jms betwen; o10000; o17762
jms error
dac .+1
NOTE! the "betwen" (between) routine (which appears in multiple
places) takes ADDRESSES of values (which can be literals, as above),
or could be symbol names for variables.
I haven't seen any cases where "skip chains" (sequences of skip
instructions) are indicated by multiple indents.
There is no hardware stack, so the only form of subroutine call (jms)
is "impure" and leaves the (updated) program counter in the first word
of the subroutine, and execution starts on the second word.
Routines which fetch arguments from after the jms instruction may do:
routine: 0
lac routine i " pick up argument after jms
dac temp " store in temp location
isz routine " increment return PC to skip argument
Routines which (optionally) skip (eg; on success) may use "isz
routine" to (conditionally) increment the return PC.
In the system code t is used as a (current) offset into a block of
temporary location(s) for a routine or group of routines , at label
9f, so you'll see:
this:
0
lac 9f+t+N
....
t=t+M
....
that:
0
lac 9f+t+I
....
t=t+J
.....
.....
.....
9f:
.=.+t
HARDWARE:
=========
References:
http://simh.trailing-edge.com/docs/architecture18b.pdf
http://www.soemtron.org/pdp7.html
http://www.soemtron.org/pdp7history.html
NOTE! All opcodes are defined in "sop.s" (and in "as7") and are
commented as below.
The PDP-4/7/9/15 family started as a simplified version of the PDP-1,
(itself an evolution of the TX-0, designed at MIT Lincoln Labs). The
PDP-4 wasn't very successful (offering 5/8 PDP-1 performance at 1/2
the price).
The PDP-7 was originally concieved of as a repackaging of the PDP-1,
but DEC had a built up more system software for the PDP-4 than for the
PDP-1 (including a FORTRAN II compiler!), so they continued with the
new architecture. See Bob's paper (first link above) for more detail.
The Living Computing Museum in Seattle has a running PDP-7, and
intends to build simulated disk hardware to enable running "Unix v0".
Words are 18 bits, words are typically represented as six octal digits.
There is one 18 bit accumulator, called "AC".
The "LINK" register is a 1-bit register that is included in shifts.
The Extended Arithmetic Element (EAE) option adds an "MQ" register which
has limited uses.
Bit numbering is "big endian": bit zero is 400000.
There are no "addressing" modes: memory referencing instructions,
(opcodes 0 thru 060) decode the low 14 bits as:
I (020000) "indirect" bit
Y (017777) 13-bit address field.
When the "I" bit is clear, the "Y" field is the address of the operand.
When the "I" bit is set, the word referenced by the contents of the
word addressed by "Y" field is used as the operand.
Unlike the PDP-1 (and PDP-6/10) indirect references are not
multi-level, and end after the first indirect fetch.
The PDP-7 found by Ken Thompson apparently did not have extended
memory or memory protection options and could directly reference all
8K words of memory (at all times).
Unix system code appears to have resided in the low 4K of memory, and
a single user program in the high 4K. The system had a "fast"
fixed-head disk, but indirect memory access locks out DMA by the disk
controller, and indirect access cannot be used while disk transfers
are active (the interrupt service routines for clock and TTY, are
coded without using indirect!). Because of this, disk access cannot
be "overlapped" with user code, but users are free to use "indirect"
freely.
When locations 010 through 017 of memory are referenced *INDIRECTLY*,
they auto-increment after access, and can be used as "index
registers".
System calls are made using the "CAL" (call) instruction (octal 00).
The Y field indicates the system call number. "CAL" behaves like "jms
020". "CAL" with the "I" bit set behaves like "jms 20 i". The system
handler appears to deal with both (although the non-indirect form
destroys the contents of location 020), and has a longer code path, so
it seems likely that "sys=cal i" became the preferred system call at
some point?
The kernel preserves the contents of the users' AC and MQ registers
and locations 8-15 in the "userdata" block (symbols u.ac, u.mq and
u.rq). Location u.rq+8 is the saved PC of the last system call.
The "save" system call (and any undefined system call) write high 4K
of memory and the "userdata" block (to fd 1?????)
The system did not have advanced interrupt processing hardware, so all
"priority interrupts" (also known in the past as "address break"
processing) dispatched as if a "jms 0" was executed, and are processed
in the "pibreak" routine.
Summary of memory instructions (from annotated sop.s):
dac = 0040000 " MEM: deposit AC
jms = 0100000 " MEM: jump to subroutine
dzm = 0140000 " MEM: deposit zero to memory
lac = 0200000 " MEM: load AC
xor = 0240000 " MEM: XOR with AC
add = 0300000 " MEM: one's complement add
tad = 0340000 " MEM: two's complement add
xct = 0400000 " MEM: execute
isz = 0440000 " MEM: increment and skip if zero
and = 0500000 " MEM: AND with memory
sad = 0540000 " MEM: skip if AC different
jmp = 0600000 " MEM: jump
Other instruction groups do not interpret the "I" bit, and all begin
with '7' in the high three bits.
I/O Transfer (or IOT) have 111000 (070) in the high six bits. the next
six bits (two octal digits) indicate the device number.
Operate (OPR) which is "microcoded" with low order bits indicating
"micro operations" to be performed have 11110 (074) in the high four
bits:
cma = 0740001 " OPR: complement AC
ral = 0740010 " OPR: rotate AC left
rar = 0740020 " OPR: rotate AC right
hlt = 0740040 " OPR: halt
sma = 0740100 " OPR: skip on minus AC
sza = 0740200 " OPR: skip on zero AC
snl = 0740400 " OPR: skip on non-zero link
skp = 0741000 " OPR: skip unconditionally
sna = 0741200 " OPR: skip on negative AC
szl = 0741400 " OPR: skip on zero link
rtl = 0742010 " OPR: rotate two left
rtr = 0742020 " OPR: rotate two right
cll = 0744000 " OPR: clear link
rcl = 0744010 " OPR: clear link, rotate left
rcr = 0744020 " OPR: clear link, rotate right
cla = 0750000 " OPR: clear AC
las = 0750004 " OPR: load AC from switches
With some limitations, OPR instructions can be OR-ed together.
(Ordering of operations is determined by the hardware, not by their
order in the source!!):
sna cla " skip on negative AC, clear AC
sna spa " skip on negative or positive AC
sna ral " skip on negative AC, rotate AC left
cla cll sza " skip on AC zero, clear AC, clear LINK
The last "operate" instruction is not microcoded:
law = 0760000 " OPR: load accumulator with (instr)
and as noted above, is often coded directly as an immediate negative constant.
Unix v0 depends on the EAE option, which uses instructions with 064 in
the top four bits, and is used for multiply, divide, and the 18-bit MQ
register:
lrs = 0640500 " EAE: long right shift
lrss = 0660500 " EAE: long right shift, signed
lls = 0640600 " EAE: long left shift
llss = 0660600 " EAE: long left shift, signed
als = 0640700 " EAE: AC left shift
alss = 0660700 " EAE: AC left shift, signed
mul = 0653323 " EAE: multiply
idiv = 0653323 " EAE: integer divide
lacq = 0641002 " EAE: load AC with MQ
clq = 0650000 " EAE: clear MQ
omq = 0650002 " EAE: OR MQ into AC
cmq = 0650004 " EAE: complement MQ
lmq = 0652000 " EAE: load MQ from AC
+1 -1
View File
@@ -46,7 +46,7 @@ init.s pages 47-51 of 07-13-19.pdf
cas.s: pages 1-17 of 04-cas.pdf cas.s: pages 1-17 of 04-cas.pdf
ind.b: page 2 of 08-rest.pdf ind.b: page 2 of 08-rest.pdf
lease.b: page 4 of 08-rest.pdf lcase.b: page 4 of 08-rest.pdf
adm.s pages 2-11 of 05-1-4.pdf adm.s pages 2-11 of 05-1-4.pdf
ald.s pages 13-17 of 05-1-4.pdf ald.s pages 13-17 of 05-1-4.pdf
+7 -7
View File
@@ -211,7 +211,7 @@ proc1:
jmp proc2 jmp proc2
jms bufwr jms bufwr
jms copyz; buf; 64 jms copyz; buf; 64
lac lyrand lac lvrand
and o17700 and o17700
dac bufadd dac bufadd
dac 1f dac 1f
@@ -277,7 +277,7 @@ bufwr: 0
lacq lacq
tad o60 tad o60
dac i 8 dac i 8
iac 2b lac 2b
cll cll
idiv; 10 idiv; 10
lacq lacq
@@ -454,7 +454,7 @@ nf1:
lac fname lac fname
tad d4 tad d4
dac fname dac fname
sys open; frame: 0; 0 sys open; fname: 0; 0
dac iof dac iof
sma sma
lac passno lac passno
@@ -513,7 +513,7 @@ gchar: 0
gch3: gch3:
dac char dac char
jms betwn; d0; o200 jms betwen; d0; o200
cla cla
tad lactab tad lactab
dac .+1 dac .+1
@@ -814,12 +814,12 @@ exp2:
dac srand dac srand
lac r+1 lac r+1
dac srand+1 dac srand+1
jmp expb jmp exp5
exp3: exp3:
sad d5 sad d5
jmp exp4 jmp exp4
jms error; x> jms error; x>
smp skip jmp skip
exp4: exp4:
jms pickup jms pickup
jmp i expr jmp i expr
@@ -939,7 +939,7 @@ o74: 074
o76: 076 o76: 076
namsiz: -2 namsiz: -2
namistp: namlst namlstp: namlst
fnamep: fakename fnamep: fakename
lactab: lac .+1 lactab: lac .+1
8;8;8;8;8;8;8;8 8;8;8;8;8;8;8;8
View File
+2 -2
View File
@@ -53,7 +53,7 @@ orig:
jms halt jms halt
okexit: okexit:
dzm: u.ac dzm u.ac
sysexit: sysexit:
ion ion
lac .savblk lac .savblk
@@ -72,7 +72,7 @@ sysexit:
dac 9 dac 9
lac u.rq lac u.rq
dac 8 dac 8
lac u.rq lac u.mq
lmq lmq
lac u.ac lac u.ac
jmp u.rq+8 i jmp u.rq+8 i
+1 -1
View File
@@ -32,7 +32,7 @@ error:
1: 077012 1: 077012
a.out: a.out:
<a.>;<ou><t 040; 040040 <a.>;<ou>;<t 040; 040040
t1: 0 t1: 0
t2: 0 t2: 0
c1: 0 c1: 0
+482 -440
View File
File diff suppressed because it is too large Load Diff
+66 -64
View File
@@ -10,17 +10,17 @@
dac name dac name
loop: loop:
sys open; name:0; 0; " Open file, get fd back sys open; name:0; 0 " Open file, get fd back
spa spa
jmp badfile " Negative fd, exit with an error message jmp badfile " Negative fd, exit with an error message
dac fi " save file descriptor in fi dac fi " Save file descriptor in fi
1: 1:
jms getc " get a character in AC jms getc " Get a character in AC
sad o4 sad o4
jmp 1f jmp 1f " Break the loop when we get a ctrl-D
jms putc " write the character on stdout jms putc " Write the character on stdout
jmp 1b jmp 1b " and loop back
1: 1:
lac fi " Close the file descriptor in fi lac fi " Close the file descriptor in fi
@@ -57,93 +57,95 @@ nofiles:
1: <no>; 040; <fi>;<le>;<s 012 1: <no>; 040; <fi>;<le>;<s 012
done: done:
lac noc lac noc " Is the number of characters left zero?
sna sna
sys exit sys exit " Yes, exit
and d1 and d1
sna cla sna cla
jmp 1f jmp 1f
jms putc jms putc " Store the character in the buffer
jmp done jmp done " and loop back
1: 1:
lac noc lac noc " Get the number of characters in the buffer
rcr rcr " Divide by two to convert to words
dac 1f dac 1f " Save in the write's word count below
lac fo " Load fd 1, stdout lac fo " Load fd 1, stdout
sys write; iopt+1; 1 sys write; iopt+1; 1:.. " Write the leftover buffer and exit
sys exit sys exit
getc: 0 getc: 0
lac ipt lac ipt " Load the pointer to the next word in the buffer
sad eipt sad eipt
jmp 1f jmp 1f " We've reached the end of the buffer, so read more
dac 2f dac 2f " Save the pointer
add o400000 add o400000 " Flip the msb and save into ipt
dac ipt dac ipt
ral ral " Move the msb into the link register
lac 2f i lac 2f i " Load the word from the buffer
szl szl " Skip if this is the second character in the word
lrss 9 lrss 9 " It's the first char, shift down the top character
and o177 " keep the lowest 7 bits and o177 " Keep the lowest 7 bits
sna sna
jmp getc+1 jmp getc+1 " Skip a NUL characters and read another one
jmp getc i " return from subroutine jmp getc i " Return the character from the subroutine
1: 1:
lac fi lac fi " Buffer is empty, read another 64 characters
sys read; iipt+1; 64 sys read; iipt+1; 64
sna sna
jmp 1f jmp 1f " No characters were read in
tad iipt tad iipt " Add the word count to the base of the buffer
dac eipt dac eipt " and store in the end buffer pointer
lac iipt lac iipt " Reset the ipt to the base of the buffer
dac ipt dac ipt
jmp getc+1 jmp getc+1 " and loop back to get one character
1: 1:
lac o4 lac o4 " No character, return with ctrl-D
jmp getc i " return from subroutine jmp getc i " return from subroutine
putc: 0 putc: 0
and o177 " keep the lowest 7 bits and o177 " Keep the lowest 7 bits and save into 2f+1
dac 2f+1 dac 2f+1
lac opt lac opt " Save the pointer to the empty buffer
dac 2f dac 2f " position to 2f
add o400000 add o400000 " Flip the msb and save back into opt
dac opt dac opt " This also has the effect of incrementing
spa " the opt pointer every second addition!
jmp 1f
lac 2f i spa " If the bit was set, we already have one
jmp 1f " character at 2f+1. If no previous character,
lac 2f i " merge the old and new character together
xor 2f+1 xor 2f+1
jmp 3f jmp 3f " and go to the "save it in buffer" code
1: 1:
lac 2f+1 lac 2f+1 " Move the character up into the top half
alss 9 alss 9
3: 3:
dac 2f i dac 2f i " Save the word into the buffer
isz noc isz noc " Add 1 to the char count, never skipping
lac noc lac noc " Have we reached 128 characters, 64 words?
sad d128 sad d128
skp skp
jmp putc i jmp putc i " No, so return (more room still in the buffer)
lac fo " load fd 1, stdout lac fo " Load fd1 (i.e stdout)
sys write; iopt+1; 64 sys write; iopt+1; 64 " and write out the 64 words in the buffer
lac iopt lac iopt
dac opt dac opt " Set opt pointing back to base of buffer
dzm noc dzm noc " Set the number of chars in the buffer to 0
jmp putc i jmp putc i " and return
2: 0;0 2: 0;0 " Current input and output word pointers
ipt: 0 ipt: 0 " Current input buffer base
eipt: 0 eipt: 0 " Pointer to end of data read in input buffer
iipt: .+1; .=.+64 " 64 word input buffer iipt: .+1; .=.+64 " 64 word input buffer and pointer to it
fi: 0 fi: 0 " Input file descriptor
opt: .+2 opt: .+2 " Current output buffer base
iopt: .+1; .=.+64 " 64 word output buffer iopt: .+1; .=.+64 " 64 word output buffer and pointer to it
noc: 0 noc: 0 " Number of output characters
fo: 1 " output file descriptor, fd 1 is stdout fo: 1 " Output file descriptor, fd 1 is stdout
d1: 1 " octal and decimal constants d1: 1 " Octal and decimal constants
o4:d4: 4 o4:d4: 4
d8: 8 d8: 8
o400000: 0400000 o400000: 0400000 " Msb toggle bit
o177: 0177 o177: 0177 " ASCII mask
d128: 128 d128: 128 " 128 words in the output buffer
View File
+105
View File
@@ -0,0 +1,105 @@
" Warren's cat program: cat [arg1 arg2 ...]
main:
" Load the pointer pointer in 017777 to see if we have any arguments
lac 017777 i
sad d4 " Skip if we have more than four argument words
jmp stdinout " Only four argument words, so no arguments
lac 017777 " Move five words past the argument word count
tad d1 " so that AC points at the first argument
tad d4
" This section opens files and copies their contents to standard output
catfiles:
" We start with AC pointing to an argument. Save it at the "name" label
dac name
" Open the file and get the fd into AC
sys open; name:0; 0;
spa
jmp badfile " Negative fd, exit with an error message
dac fd " Save the file descriptor
fileloop:
" Read 64 words into the buffer from the input file
lac fd
sys read; buf; 64
spa " Skip if result was >= 0
jmp error " Result was -ve, so error result
sna " Skip if result was >0
jmp fileend " Result was zero, so nothing left to read
" Save the count of words read in
dac 1f
" Write five words from the buffer to stdout
lac d1
sys write; buf; 1:0
" and loop back for more words to read
jmp fileloop
fileend:
" Close the open file descriptor
lac fd
sys close
" Subtract 4 from the count of argument words
-4
tad 017777 i
dac 017777 i
sad d4 " Is the value 4, i.e. no args left?
jmp end " Yes, so exit
" Still an argument, so move up to the next filename argument
lac name
tad d4
dac name
jmp catfiles " and loop back to cat this file
end:
" exit
sys exit
" This section copies from standard input to standard output
" We cheat by setting the fd value to zero and storing 8
" into the argc word count, so that when the code hits
" fileend, the word count drops to 4 and we exit.
stdinout:
lac d8
dac 017777 i " Save 8 into the word count
lac d0
dac fd " Save file descriptor 0
jmp fileloop
" This code comes from the real cat.s
badfile:
lac name " Get the pointer to the filename
dac 1f " Store it in 1f below
lac d8 " Load fd 8 which is stderr
sys write; 1:0; 4 " Write the name, max 4 words
lac d8 " Then write " ?\n"
sys write; 1f; 2
sys exit " and exit
1: 040; 077012 " String literal: " ?\n"
error:
" Print an "err read" string on stderr and exit
lac d8
sys write; noreadstr; 5
sys exit
noreadstr:
<er>;<r 040;<re>;<ad>;012000
fd: 0 " fd of the open file
d0: 0 " Constants 0, 1, 4 and 8
d1: 1
d4: 4
d8: 8 " stderr seems to have fd 8
minus4: 0777774 " Constant -4
" Input buffer for read
buf: .=.+64
+110
View File
@@ -0,0 +1,110 @@
" Warren's cp program: cp arg1 arg2
main:
" Load the pointer pointer in 017777 to see if we have any arguments
lac 017777 i
sad d12
jmp 1f " We have 12 words, so we have 2 arguments
jmp argserror " Otherwise, print an error and exit
1: lac 017777 " Move five words past the argument word count
tad d5 " so that AC points at the first argument
" Save the pointer to the file name
dac name
" Open the input file and get the fd into AC
sys open; name:0; 0;
spa
jmp badfile " Negative fd, exit with an error message
dac infd " Save the file descriptor
lac 017777 " Move nine words past the argument word count
tad d9 " so that AC points at the second argument
dac name
dac name2
" Open the ouput file and get the fd into AC
sys open; name2:0; 1;
spa
jmp badfile " Negative fd, exit with an error message
dac outfd " Save the file descriptor
fileloop:
" Read 64 words into the buffer from the input file
lac infd
sys read; buf; 64
spa " Skip if result was >= 0
jmp readerror " Result was -ve, so error result
sna " Skip if result was >0
jmp fileend " Result was zero, so nothing left to read
" Save the count of words read in
dac 1f
" Write five words from the buffer to the output file
lac outfd
sys write; buf; 1:0
spa " Skip if result was >= 0
jmp writeerror " Result was -ve, so error result
" and loop back for more words to read
jmp fileloop
fileend:
" Close the open file descriptors
lac infd
sys close
lac outfd
sys close
sys exit
" This code comes from the real cat.s
badfile:
lac name " Get the pointer to the filename
dac 1f " Store it in 1f below
lac d8 " Load fd 8 which is stderr
sys write; 1:0; 4 " Write the name, max 4 words
lac d8 " Then write " ?\n"
sys write; 1f; 2
sys exit " and exit
1: 040; 077012 " String literal: " ?\n"
readerror:
" Print an "err read" string on stderr and exit
lac d8
sys write; noreadstr; 5
sys exit
noreadstr:
<er>;<r 040;<re>;<ad>;012000
writeerror:
" Print an "err write" string on stderr and exit
lac d8
sys write; nowritestr; 6
sys exit
nowritestr:
<er>;<r 040;<wr>;<it>;<e 012
argserror:
" Print an "bad args" string on stderr and exit
lac d8
sys write; badargs; 5
sys exit
badargs:
<ba>;<d 040;<ar>;<gs>;012000
infd: 0 " fd of the input file
outfd: 0 " fd of the output file
d5: 5
d9: 9
d8: 8 " stderr seems to have fd 8
d12: 12
" Input buffer for read
buf: .=.+64
+61
View File
@@ -0,0 +1,61 @@
" Test program for several system calls: open, read, write, close, exit
" Do:
" ./as7 write_test.s > a.out
" ./a7out -d a.out
main:
" Test the lac, dac instructions
lac in
dac out
" Write hello to fd1 i.e stdout
lac d1
sys write; hello; 7
" Test if the assembler can dac into a mid-line label
lac helloptr
dac 1f
lac d1
sys write; 1:0; 7
" Try to open file fred
sys open; fred; 0;
" read 5 words into the buffer from stdin: type in 10 or more characters!
lac d0
sys read; buf; 5
" Stop and dump memory, so you can see five words at location 0400
" Comment out the hlt instruction to test close and exit
hlt
" close stdin
lac d0
sys close
" exit
sys exit
" We should not get to the halt instruction
hlt
" Some memory locations for lac and dac
. = 0100
in: 023
. = 0200
out: 0
" Hello, world\n, two ASCII chars per word
hello: <He>; <l 0154; 0157 ,>; 040; <wo>; <rl>; <d 012
helloptr: hello
" fred as a four word filename
fred: <fr>; <ed>; 040040; 040040
" Input buffer for read
. = 0400
buf: 0
d0: 0
d1: 1
+43 -43
View File
@@ -21,61 +21,61 @@ orig:
jmp 1f+1 jmp 1f+1
1f 1f
1: 0 1: 0
iof iof " interrupts off
dac u.ac dac u.ac " save user AC
lacq lacq
dac u.mq dac u.mq " save user MQ
lac 8 lac 8
dac u.rq dac u.rq " save user auto-index location 8
lac 9 lac 9
dac u.rq+1 dac u.rq+1 " save user auto-index location 9
jms copy; 10; u.rq+2; 6 jms copy; 10; u.rq+2; 6 " save user auto-index locations 10-15
lac 1b lac 1b " load user PC after system call
dac u.rq+8 dac u.rq+8 " save user PC
-1 " load -1
dac .savblk " set "save" flag (cleared by disk I/O?)
dac .insys " set "in system" flag
lac uquant " load user quantum count
jms betwen; d0; maxquant " check if between 0 & maxquant??
jms swap " no: swap processes
ion " interrupts on
-1 -1
dac .savblk tad u.rq+8 " get address of system call
dac .insys jms laci " load AC indirect??
lac uquant jms betwen; o20001; swn " range check
jms betwen; d0; maxquant jmp badcal " bad system call
jms swap tad swp " add system call table base
ion dac .+1 " save as next instruction
-1 jmp .. i " dispatch system call
tad u.rq+8
jms laci
jms betwen; o20001; swn
jmp badcal
tad swp
dac .+1
jmp .. i
. = orig+0100 . = orig+0100
jmp coldentry jmp coldentry
jms halt jms halt
okexit: okexit:
dzm: u.ac dzm u.ac " 'OK' system call exit: clear user AC
sysexit: sysexit: " common system call exit code
ion ion " enable interrupts
lac .savblk lac .savblk " load "save" flag
sza sza " is zero (cleared by disk I/O)?
jmp 1f jmp 1f " no: no disk I/O done?
jms copy; sysdata; dskbuf; 64 jms copy; sysdata; dskbuf; 64 " copy system data to disk buffer
cla cla
jms dskio; 07000 jms dskio; 07000 " save to disk?
1: 1:
dzm .insys dzm .insys " clear "in system call" flag
jms chkint jms chkint
skp skp
jmp .save jmp .save " dump core??
jms copy; u.rq+2; 10; 6 jms copy; u.rq+2; 10; 6 " restore auto-index locations 10-15
lac u.rq+1 lac u.rq+1 " restore auto-index location 9
dac 9 dac 9
lac u.rq lac u.rq " restore auto-index location 8
dac 8 dac 8
lac u.rq lac u.mq " restore MQ register
lmq lmq
lac u.ac lac u.ac " restore AC register
jmp u.rq+8 i jmp u.rq+8 i " return to user
swap: 0 swap: 0
ion ion
@@ -129,21 +129,21 @@ swap: 0
jmp swap i jmp swap i
t = t+1 t = t+1
swp: swp: " system call dispatch table
jmp . jmp . " base instruction
.save; .getuid; .open; .read; .write; .creat; .seek; .tell .save; .getuid; .open; .read; .write; .creat; .seek; .tell
.close; .link; .unlink; .setuid; .rename; .exit; .time; .intrp .close; .link; .unlink; .setuid; .rename; .exit; .time; .intrp
.chdir; .chmod; .chown; badcal; .sysloc; badcal; .capt; .rele .chdir; .chmod; .chown; badcal; .sysloc; badcal; .capt; .rele
.status; badcal; .smes; .rmes; .fork .status; badcal; .smes; .rmes; .fork
swn: swn:
.-swp-1 i .-swp-1 i " count of system calls, plus indirect!
.intrp: .intrp:
lac u.ac lac u.ac
dac u.intflg dac u.intflg
jmp okexit jmp okexit
.sysloc: .sysloc: " "sysloc": syscall to return system addresses
lac u.ac lac u.ac
and o17777 and o17777
jms betwen; d1; locn jms betwen; d1; locn
@@ -154,7 +154,7 @@ swn:
dac u.ac dac u.ac
jmp sysexit jmp sysexit
locsw: locsw: " table of system data structures for "sysloc" call
lac . lac .
iget; inode; userdata; sysdata; copy; copyz; betwen; dskrd iget; inode; userdata; sysdata; copy; copyz; betwen; dskrd
dskwr; dskbuf; dpdata; namei; pbsflgs; alloc; free; dspdata dskwr; dskbuf; dpdata; namei; pbsflgs; alloc; free; dspdata
+15 -12
View File
@@ -53,9 +53,9 @@
jms iput jms iput
jmp okexit jmp okexit
.getuid: .getuid: " getuid system call
lac u.uid lac u.uid
dac u.ac dac u.ac " return u.uid in user AC
jmp sysexit jmp sysexit
.seek: .seek:
@@ -146,12 +146,12 @@
jms iput jms iput
jmp sysexit jmp sysexit
.setuid: .setuid: " setuid system call
lac u.uid lac u.uid " load current user id
sma sma " negative?
jms error jms error " no: error!!
lac u.ac lac u.ac " load user AC
dac u.uid dac u.uid " save as new uid
jmp sysexit jmp sysexit
.rename: .rename:
@@ -168,11 +168,14 @@
jms copy; 1:0; d.name; 4 jms copy; 1:0; d.name; 4
jmp okexit jmp okexit
" time system call returns line (mains) frequency ticks since boot?
" note: returns uptime!?
" at 60Hz, 36 bits would last 36+ years!
.time: .time:
lac s.tim lac s.tim " load high order bits
dac u.ac dac u.ac " return in AC
lac s.tim+1 lac s.tim+1 " load low order bits
dac u.mq dac u.mq " return in MQ
jmp sysexit jmp sysexit
.chdir: .chdir:
+9 -9
View File
@@ -1,7 +1,7 @@
"** 01-s1.pdf page 41 "** 01-s1.pdf page 41
" s7 " s7
pibreak: pibreak: " priority interrupt processing "chain"
dac .ac "** CROSSED OUT.... dac .ac "** CROSSED OUT....
dpsf dpsf
@@ -25,15 +25,15 @@ pibreak:
dac dpwrite dac dpwrite
jmp piret "** END OF CROSSOUT jmp piret "** END OF CROSSOUT
1: clsf 1: clsf " clock overflow (line frequency ticks)?
jmp 1f jmp 1f " no
lpb lpb " load display push buttons
dac pbsflgs dac pbsflgs " save
isz s.tim+1 isz s.tim+1 " increment low order tick count
skp skp " no overflow, skip second increment
isz s.tim isz s.tim " low order overflowed, increment high order count
isz uquant isz uquant " increment user quantum counter
"** written: ttydelay -> ttyd1 "** written: ttydelay -> ttyd1
"** written: ttyrestart -> ttyres1 "** written: ttyrestart -> ttyres1
cnop: cnop:
+89 -91
View File
@@ -1,97 +1,95 @@
"** 01-s1.pdf page 62 "** 01-s1.pdf page 62
" sop " sop
dac = 0040000 dac = 0040000 " MEM: deposit AC
jms = 0100000 jms = 0100000 " MEM: jump to subroutine
dzm = 0140000 dzm = 0140000 " MEM: deposit zero to memory
lac = 0200000 lac = 0200000 " MEM: load AC
xor = 0240000 xor = 0240000 " MEM: XOR with AC
add = 0300000 add = 0300000 " MEM: one's complement add
tad = 0340000 tad = 0340000 " MEM: two's complement add
xct = 0400000 xct = 0400000 " MEM: execute
isz = 0440000 isz = 0440000 " MEM: increment and skip if zero
and = 0500000 and = 0500000 " MEM: AND
sad = 0540000 sad = 0540000 " MEM: skip if AC different
jmp = 0600000 jmp = 0600000 " MEM: jump
nop = 0740000 nop = 0740000 " OPR: no-op
i = 020000 i = 020000 " indirect
law = 0760000 law = 0760000 " OPR: load accumulator with (instr)
cma = 0740001 cma = 0740001 " OPR: complement AC
las = 0750004 las = 0750004 " OPR: load AC from switches
ral = 0740010 ral = 0740010 " OPR: rotate AC left
rar = 0740020 rar = 0740020 " OPR: rotate AC right
hlt = 0740040 hlt = 0740040 " OPR: halt
sma = 0740100 sma = 0740100 " OPR: skip on minus AC
sza = 0740200 sza = 0740200 " OPR: skip on zero AC
snl = 0740400 snl = 0740400 " OPR: skip on non-zero link
skp = 0741000 skp = 0741000 " OPR: skip unconditionally
sna = 0741200 sna = 0741200 " OPR: skip on non-zero AC
szl = 0741400 szl = 0741400 " OPR: skip on zero link
rtl = 0742010 rtl = 0742010 " OPR: rotate two left
rtr = 0742020 rtr = 0742020 " OPR: rotate two right
cil = 0744000 cll = 0744000 " OPR: clear link
rcl = 0744010 rcl = 0744010 " OPR: clear link, rotate left
rcr = 0744020 rcr = 0744020 " OPR: clear link, rotate right
cia = 0750000 cla = 0750000 " OPR: clear AC
lrs = 0640500 lrs = 0640500 " EAE: long right shift
lrss = 0660500 lrss = 0660500 " EAE: long right shift, signed
lls = 0640600 lls = 0640600 " EAE: long left shift
llss = 0660600 llss = 0660600 " EAE: long left shift, signed
als = 0640700 als = 0640700 " EAE: AC left shift
alss = 0660700 alss = 0660700 " EAE: AC left shift, signed
mul = 0653323 mul = 0653323 " EAE: multiply
idiv = 0653323 idiv = 0653323 " EAE: integer divide
lacq = 0641002 lacq = 0641002 " EAE: load AC with MQ
clq = 0650000 clq = 0650000 " EAE: clear MQ
omq = 0650002 omq = 0650002 " EAE: OR MQ into AC
cmq = 0650004 cmq = 0650004 " EAE: complement MQ
lmq = 0652000 lmq = 0652000 " EAE: load MQ from AC
dscs = 0707141 dscs = 0707141 " DSK: clear status register
dslw = 0707124 dslw = 0707124 " DSK: clear and load WC from AC
dslm = 0707142 dslm = 0707142 " DSK: clear and load MAC from AC
dsld = 0707104 dsld = 0707104 " DSK: clear and load TA and SA from AC
dsls = 0707144 dsls = 0707144 " DSK: load status
dssf = 0707121 dssf = 0707121 " DSK: skip on flags
dsrs = 0707132 dsrs = 0707132 " DSK: read status register
iof = 0700002
ion = 0700042
caf = 0703302
clon = 0700044
clsf = 0700001
iof = 0700002 " PIC: interrupts off
ion = 0700042 " PIC: interrupts on
caf = 0703302 " CPU: clear all flags
clon = 0700044 " CLK: clear flag, enable
clsf = 0700001 " CLK: skip if overflow
"** 01-s1.pdf page 63 "** 01-s1.pdf page 63
clof = 0700004 clof = 0700004 " CLK: clear flag, disable
ksf = 0700301 ksf = 0700301 " KBD: skip if flag set
krb = 0700312 krb = 0700312 " KBD: read buffer
tsf = 0700401 tsf = 0700401 " TTY: skip if flag set
tcf = 0700402 tcf = 0700402 " TTY: clear flag
tls = 0700406 tls = 0700406 " TTY: load buffer, select
sck = 0704301 sck = 0704301 " S-2: skip on console keyboard
cck = 0704304 cck = 0704304 " S-2: clear console keyboard
lck = 0704312 lck = 0704312 " S-2: load console keyboard
rsf = 0700101 rsf = 0700101 " PTR: skip if flag set
rsa = 0700104 rsa = 0700104 " PTR: select alphanumeric mode
rrb = 0700112 rrb = 0700112 " PTR: clear flag, or read buffer
psf = 0700201 psf = 0700201 " PTP: skip if flag set
pcf = 0700202 pcf = 0700202 " PTP: clear flag
psa = 0700204 psa = 0700204 " PTP: alphanumeric mode
cdf = 0700501 cdf = 0700501 " ???
lds = 0701052 lds = 0701052 " S-2: load display status
lda = 0701012 lda = 0701012 " S-2: load display address
wcga = 0704206 wcga = 0704206 " S-2: ???
raef = 0700742 raef = 0700742 " S-2: resume after edges flag
rlpd = 0700723 rlpd = 0700723 " S-2: resume after light pen stop, disabled
beg = 0700547 beg = 0700547 " S-2: begin
spb = 0704401 spb = 0704401 " S-2: skip on push button flag
cpb = 0704404 cpb = 0704404 " S-2: clear push button flag
lpb = 0704412 lpb = 0704412 " S-2: load push buttons
wbl = 0704424 wbl = 0704424 " S-2: write button lights
dprs = 0704752 dprs = 0704752 " dataphone: read status
dpsf = 0704741 dpsf = 0704741 " dataphone: skip on flag
dpcf = 0704761 dpcf = 0704761 " dataphone: clear flag
dprc = 0704712 dprc = 0704712 " dataphone: read character
crsf = 0706701 crsf = 0706701 " CDR: skip if ready
crrb = 0706712 crrb = 0706712 " CDR: read buffer
+151 -107
View File
@@ -27,6 +27,8 @@ use constant MAXPOSINT => 0377777; # Biggest signed integer
use constant MAXADDR => 017777; # Largest memory address use constant MAXADDR => 017777; # Largest memory address
use constant LINKMASK => 01000000; # Mask for LINK register use constant LINKMASK => 01000000; # Mask for LINK register
use constant EAESTEP => 077; # EAE step count mask use constant EAESTEP => 077; # EAE step count mask
use constant EAEIMASK => 0777700; # EAE instruction mask
use constant SIGN => 0400000; # Sign bit
### Main program ### ### Main program ###
@@ -35,12 +37,13 @@ while ( defined( $ARGV[0] ) && ( $ARGV[0] =~ m{^-} ) ) {
# -d: debug mode # -d: debug mode
if ( $ARGV[0] eq "-d" ) { if ( $ARGV[0] eq "-d" ) {
$debug = 1; shift(@ARGV); $debug = 1;
shift(@ARGV);
} }
# -b: set a breakpoint # -b: set a breakpoint
if ( $ARGV[0] eq "-b" ) { if ( $ARGV[0] eq "-b" ) {
$singlestep = 1; shift(@ARGV); shift(@ARGV);
$Breakpoint{ oct( shift(@ARGV) ) } = 1; $Breakpoint{ oct( shift(@ARGV) ) } = 1;
} }
} }
@@ -166,8 +169,7 @@ sub simulate {
oct("054") => \&sad, oct("054") => \&sad,
oct("060") => \&jmp, oct("060") => \&jmp,
oct("064") => \&eae, oct("064") => \&eae,
oct("070") => \&iot, oct("074") => \&opr,
oct("074") => \&special,
); );
# Loop indefinitely # Loop indefinitely
@@ -183,7 +185,10 @@ sub simulate {
my $indaddr = ($indirect) ? $Mem[$addr] & MAXADDR : $addr; my $indaddr = ($indirect) ? $Mem[$addr] & MAXADDR : $addr;
# If this is a breakpoint, stop now and get a user command # If this is a breakpoint, stop now and get a user command
$singlestep = 1 if ( defined( $Breakpoint{$PC} ) ); if ( defined( $Breakpoint{$PC} ) ) {
$singlestep = 1;
dprintf( "break at PC %06o\n", $PC );
}
get_user_command() if ($singlestep); get_user_command() if ($singlestep);
dprintf( "PC %06o: ", $PC ); dprintf( "PC %06o: ", $PC );
@@ -194,7 +199,7 @@ sub simulate {
if ( defined( $Oplist{$opcode} ) ) { if ( defined( $Oplist{$opcode} ) ) {
$Oplist{$opcode}->( $instruction, $addr, $indaddr ); $Oplist{$opcode}->( $instruction, $addr, $indaddr );
} else { } else {
printf( STDERR "Unknown instruction 0%o at location 0%o\n", printf( STDERR "Unknown instruction 0%06o at location 0%06o\n",
$instruction, $PC ); $instruction, $PC );
exit(1); exit(1);
} }
@@ -243,14 +248,18 @@ sub tad {
sub add { sub add {
my ( $instruction, $addr, $indaddr ) = @_; my ( $instruction, $addr, $indaddr ) = @_;
dprintf( "add AC (value %06o) with addr %06o (%06o)\n", dprintf( "add AC (value %06o) with addr %06o (%06o)\n",
$PC, $AC, $indaddr, $Mem[$indaddr] ); $AC, $indaddr, $Mem[$indaddr] );
$LINK = 0;
$AC = $AC + $Mem[$indaddr]; # This logic shamelessly borrowed from SimH
if ( $AC & LINKMASK ) { # https://github.com/simh/simh/blob/master/PDP18B/pdp18b_cpu.c
$AC++; # End-around carry my $sum = $AC + $Mem[$indaddr];
$LINK = LINKMASK; if ( $sum > MAXINT ) { # end around carry
$sum = ( $sum + 1 ) & MAXINT;
} }
$AC = $AC & MAXINT; if ( ( ( ~$AC ^ $sum ) & ( $AC ^ $sum ) ) & SIGN ) { # overflow?
$LINK = LINKMASK; # set link
}
$AC = $sum;
$PC++; $PC++;
} }
@@ -290,7 +299,7 @@ sub dzm {
# Index and skip if zero # Index and skip if zero
sub isz { sub isz {
my ( $instruction, $addr, $indaddr ) = @_; my ( $instruction, $addr, $indaddr ) = @_;
dprintf( "isz %06o\n", $Mem[$indaddr] ); dprintf( "isz %06o (value %06o)\n", $indaddr, $Mem[$indaddr] );
$Mem[$indaddr]++; $Mem[$indaddr]++;
$Mem[$indaddr] &= MAXINT; $Mem[$indaddr] &= MAXINT;
$PC += ( $Mem[$indaddr] == 0 ) ? 2 : 1; $PC += ( $Mem[$indaddr] == 0 ) ? 2 : 1;
@@ -313,79 +322,127 @@ sub jms {
$PC = $indaddr; $PC = $indaddr;
} }
# Special instructions # OPR instructions
sub special { sub opr {
my $instruction = shift; my ( $instruction, $addr, $indaddr ) = @_;
# Deal with each one in turn # hlt: halt simulation
# hlt
if ( $instruction == 0740040 ) { if ( $instruction == 0740040 ) {
printf( STDERR "PC %06o: program halted\n", $PC ); printf( STDERR "PC %06o: program halted\n", $PC );
dump_memory( 0, MAXADDR, 0 ) if ($debug); dump_memory( 0, MAXADDR, 0 ) if ($debug);
exit(1); exit(1);
} }
if ( $instruction == 0741100 ) { # spa: skip on positive AC
dprintf( "spa AC %06o\n", $AC );
# Because we are dealing with 18 bits, compare the range
$PC += ( ( $AC >= 0 ) && ( $AC <= MAXPOSINT ) ) ? 2 : 1;
return;
}
if ( $instruction == 0741200 ) { # sna: skip on non-zero AC
dprintf( "sna AC %06o\n", $AC );
$PC += ( $AC != 0 ) ? 2 : 1;
return;
}
if ( $instruction == 0740200 ) { # sza: skip on zero AC
dprintf( "sza AC %06o\n", $AC );
$PC += ( $AC == 0 ) ? 2 : 1;
return;
}
if ( $instruction == 0741400 ) { # szl: Skip when $LINK is zero
dprintf( "szl LINK %0o\n", $LINK );
$PC += ( $LINK == 0 ) ? 2 : 1;
return;
}
if ( $instruction == 0740400 ) { # snl: Skip when $LINK not zero
dprintf( "snl LINK %0o\n", $LINK );
$PC += ( $LINK != 0 ) ? 2 : 1;
return;
}
if ( $instruction == 0741000 ) { # ska: skip always
dprintf("skp\n");
$PC += 2;
return;
}
# ral: rotate left or rcr: clear link then rotate left
if ( ( $instruction == 0740010 ) || ( $instruction == 0744010 ) ) {
$LINK = 0 if ( $instruction == 0744010 );
$AC = $AC << 1 + ($LINK) ? 1 : 0;
$LINK = $AC & LINKMASK;
$AC = $AC & MAXINT;
$PC++;
return;
}
# rar: rotate right or rcr: clear link then rotate right
if ( ( $instruction == 0740020 ) || ( $instruction == 0744020 ) ) {
$LINK = 0 if ( $instruction == 0744020 );
my $newlink = ( $AC & 1 ) ? LINKMASK : 0;
$AC = ( $LINK | $AC ) >> 1;
$LINK = $newlink;
$PC++;
return;
}
# law: load word into AC # law: load word into AC
if ( ( $instruction >= 0760000 ) && ( $instruction <= MAXINT ) ) { my $indirect = ( $instruction >> 13 ) & 1;
if ($indirect) {
dprintf( "law %06o into AC\n", $instruction ); dprintf( "law %06o into AC\n", $instruction );
$AC = $instruction; $AC = $instruction;
$PC++; $PC++;
return; return;
} }
printf( STDERR "PC %06o: unknown instruction %06o\n", $PC, $instruction );
exit(1); # List of skip opcode names for the next section
my @skipop = (
'', 'sma', 'sza', 'sza sma',
'snl', 'snl sma', 'snl sza', 'snl sza sma',
'skp', 'spa', 'sna', 'sna spa',
'szl', 'szl spa', 'szl sna', 'szl sna spa'
);
# This logic shamelessly borrowed from SimH
# https://github.com/simh/simh/blob/master/PDP18B/pdp18b_cpu.c
my $skip = 0;
my $i = ( $instruction >> 6 ) & 017; # decode IR<8:11>
dprintf( "L.AC %d.%06o %s", ($LINK) ? 1 : 0, $AC, $skipop[$i] );
$skip = 1 if ( ( $i == 1 ) && ( $AC & SIGN ) != 0 ); # sma
$skip = 1 if ( ( $i == 2 ) && ( $AC & MAXINT ) == 0 ); # sza
$skip = 1 if ( ( $i == 3 )
&& ( ( ( $AC & MAXINT ) == 0 ) || ( ( $AC & SIGN ) != 0 ) ) )
; # sza | sma
$skip = 1 if ( ( $i == 4 ) && ($LINK) ); # snl
$skip = 1 if ( ( $i == 5 ) && ( $LINK || ( $AC >= SIGN ) ) ); # snl | sma
$skip = 1 if ( ( $i == 6 ) && ( $LINK || ( $AC == 0 ) ) ); # snl | sza
$skip = 1 if ( ( $i == 7 )
&& ( $LINK || ( $AC >= SIGN ) || ( $AC == 0 ) ) ); # snl | sza | sma
$skip = 1 if ( $i == 010 ); # skp
$skip = 1 if ( ( $i == 011 ) && ( ( $AC & SIGN ) == 0 ) ); # spa
$skip = 1 if ( ( $i == 012 ) && ( ( $AC & MAXINT ) != 0 ) ); # sna
$skip = 1 if ( ( $i == 013 )
&& ( ( $AC & MAXINT ) != 0 )
&& ( ( $AC & SIGN ) == 0 ) ); # sna & spa
$skip = 1 if ( ( $i == 014 ) && ( $LINK == 0 ) ); # szl
$skip = 1
if ( ( $i == 015 ) && ( $LINK == 0 ) && ( $AC < SIGN ) ); # szl & spa
$skip = 1
if ( ( $i == 016 ) && ( $LINK == 0 ) && ( $AC != 0 ) ); # szl & sna
$skip = 1 if ( ( $i == 017 )
&& ( $LINK == 0 )
&& ( $AC != 0 )
&& ( $AC != 0 ) ); # szl & sna & spa
# Clear operations
if ( $instruction & 010000 ) { # cla
dprintf(" cla"); $AC = 0;
}
if ( $instruction & 004000 ) { # cli
dprintf(" cli"); $LINK = 0;
}
if ( $instruction & 000002 ) { # cmi
dprintf(" cmi");
$LINK = ($LINK) ? 0 : LINKMASK;
}
if ( $instruction & 000001 ) { # cma
dprintf(" cma");
$AC = ( $AC ^ MAXINT ) & MAXINT;
}
# Rotate instructions
$i = $instruction & 02030;
# Single rotate right
if ( $i == 020 ) {
dprintf(" rar");
my $newlink = ( $AC & 1 ) ? LINKMASK : 0;
$AC = ( $LINK | $AC ) >> 1;
$LINK = $newlink;
}
# Double rotate right
if ( $i == 02020 ) {
dprintf(" rtr");
my $msb = ( $AC & 1 ) << 17;
my $newlink = ( $AC & 2 ) ? LINKMASK : 0;
$AC = ( ( $LINK | $AC ) >> 2 ) | $msb;
$LINK = $newlink;
}
# Single rotate left
if ( $i == 010 ) {
dprintf(" ral");
my $newlink = ( $AC & SIGN ) ? LINKMASK : 0;
my $lsb = $LINK ? 1 : 0;
$AC = ( ( $AC << 1 ) | $lsb ) & MAXINT;
$LINK = $newlink;
}
# Double rotate left
if ( $i == 02010 ) {
dprintf(" rtl");
my $newlink = ( $AC & 0200000 ) ? LINKMASK : 0;
my $lsb = ( $AC & SIGN ) ? 1 : 0;
my $twolsb = $LINK ? 2 : 0;
$AC = ( ( $AC << 2 ) | $twolsb | $lsb ) & MAXINT;
$LINK = $newlink;
}
# Impossible left and right rotates: 02030 or 00030. Do nothing!
# Note: We didn't do the oas instruction above.
$PC += 1 + $skip;
dprintf("\n");
return;
} }
# cal: used for system calls # cal: used for system calls
@@ -414,25 +471,23 @@ sub cal {
sub eae { sub eae {
my ( $instruction, $addr, $indaddr ) = @_; my ( $instruction, $addr, $indaddr ) = @_;
my $step = $instruction & EAESTEP; my $step = $instruction & EAESTEP;
$instruction &= EAEIMASK;
if ( $instruction == 0660500 ) { # lrss: long right shift, signed if ( $instruction == 0660500 ) { # lrss: long right shift, signed
# We ignore the MQ as it's not
# used by any user-mode programs
dprintf( "lrss %06o AC step %d\n", $AC, $step ); dprintf( "lrss %06o AC step %d\n", $AC, $step );
# Save the AC's sign into LINK # Save the AC's sign into LINK
$LINK = ( $AC << 1 ) & LINKMASK; my $newlink = ( $AC << 1 ) & LINKMASK;
$AC = ( ( $LINK | $AC ) >> $step ) & MAXINT;
# XXX: Do we need to preserve the AC sign? $LINK = $newlink;
$AC = $AC >> $step;
$PC++; $PC++;
return; return;
} }
if ( $instruction == 0660711 ) { # alss: long left shift, signed if ( $instruction == 0660700 ) { # alss: long left shift, signed
# We don't fill the lsb with LINK yet
dprintf( "alss %06o AC step %d\n", $AC, $step ); dprintf( "alss %06o AC step %d\n", $AC, $step );
# Save the AC's sign into LINK
$LINK = ( $AC << 1 ) & LINKMASK;
# XXX: Do we need to preserve the AC sign?
$AC = ( $AC << $step ) & MAXINT; $AC = ( $AC << $step ) & MAXINT;
$PC++; $PC++;
return; return;
@@ -472,6 +527,7 @@ sub sys_close {
# Open system call # Open system call
sub sys_open { sub sys_open {
# Open seems to have 2 arguments: PC+1 is a pointer to the filename. # Open seems to have 2 arguments: PC+1 is a pointer to the filename.
# PC+2 seems to be 1 for write, 0 for read. # PC+2 seems to be 1 for write, 0 for read.
# Some programs seem to have a third argument always set to 0. # Some programs seem to have a third argument always set to 0.
@@ -480,17 +536,21 @@ sub sys_open {
# Get the start address of the string # Get the start address of the string
my $start = $Mem[ $PC + 1 ]; my $start = $Mem[ $PC + 1 ];
# Convert this to a sensible ASCII filename
my $filename = mem2arg($start);
# Choose to open read-only or write-only
my $readorwrite = ( $Mem[ $PC + 2 ] ) ? ">" : "<";
dprintf( "open: base %06o, %s file %s\n", $start, $readorwrite, $filename );
# Bump up the PC # Bump up the PC
$PC += 3; $PC += 3;
# Convert this to a sensible ASCII filename
my $filename = mem2arg($start);
dprintf( "open: base %06o, file %s\n", $start, $filename );
# Open the file # Open the file
if ( open( my $FH, "<", $filename ) ) { if ( open( my $FH, $readorwrite, $filename ) ) {
# Find a place in the @FD array to store this filehandle. 99 is arbitrary # Find a place in the @FD array to store this filehandle.
# 99 is arbitrary
foreach my $fd ( 0 .. 99 ) { foreach my $fd ( 0 .. 99 ) {
if ( !defined( $FD[$fd] ) ) { if ( !defined( $FD[$fd] ) ) {
$FD[$fd] = $FH; $FD[$fd] = $FH;
@@ -537,22 +597,12 @@ sub sys_read {
$count = 0; $count = 0;
foreach my $addr ( $start .. $end ) { foreach my $addr ( $start .. $end ) {
# It's a terminal, so convert from ASCII
if ( -t $FH ) {
my $c1 = getc($FH); my $c1 = getc($FH);
last if ( !defined($c1) ); # No character, leave the loop last if ( !defined($c1) ); # No character, leave the loop
my $c2 = getc($FH) || ""; # No character, make it a NUL my $c2 = getc($FH) || ""; # No character, make it a NUL
$Mem[$addr] = $Mem[$addr] =
( ord($c1) << 9 ) | ord($c2); # Pack both into one word ( ord($c1) << 9 ) | ord($c2); # Pack both into one word
$count++; $count++;
} else {
# otherwise (for now) read in one line and convert to octal
my $line = <$FH>;
last if ( !defined($line) ); # No line, leave the loop
chomp($line);
$Mem[$addr] = oct($line) & MAXINT;
$count++;
}
} }
# No error # No error
@@ -573,7 +623,7 @@ sub sys_write {
my $end = ( $start + $count - 1 ) & MAXADDR; my $end = ( $start + $count - 1 ) & MAXADDR;
die("sys_write: bad start/end addresses $start $end\n") die("sys_write: bad start/end addresses $start $end\n")
if ( $end < $start ); if ( $end < $start );
dprintf( "write: %d words from %o to fd %d\n", $count, $start, $fd ); dprintf( "write: %d words from %06o to fd %d\n", $count, $start, $fd );
# Bump up the PC # Bump up the PC
$PC += 3; $PC += 3;
@@ -589,13 +639,7 @@ sub sys_write {
my $FH = $FD[$fd]; my $FH = $FD[$fd];
foreach my $addr ( $start .. $end ) { foreach my $addr ( $start .. $end ) {
# It's a terminal, so convert to ASCII
# otherwise (for now) print in octal
if ( -t $FH ) {
print( $FH word2ascii( $Mem[$addr] ) ); print( $FH word2ascii( $Mem[$addr] ) );
} else {
printf( $FH "%06o\n", $Mem[$addr] );
}
} }
# No error # No error
+45 -38
View File
@@ -4,7 +4,7 @@
# and convert them into PDP-7 machine code # and convert them into PDP-7 machine code
# #
# (c) 2016 Warren Toomey, GPL3 # (c) 2016 Warren Toomey, GPL3
# Tweaked by Phil Budne (expression parsing, "list" format) # Tweaked by Phil Budne (line, expression parsing, "list" format)
# #
use strict; use strict;
use warnings; use warnings;
@@ -25,7 +25,6 @@ my $origline; # The original current input line of code
my $line; # line being parsed my $line; # line being parsed
my $stage = 1; # Pass one or pass two my $stage = 1; # Pass one or pass two
my $errors = 0; # set to non-zero on error my $errors = 0; # set to non-zero on error
my %Undef; # undefined symbols: only complain once
my $line_error = ' '; my $line_error = ' ';
my $file; # current file name my $file; # current file name
my $lineno; # current line number my $lineno; # current line number
@@ -149,7 +148,7 @@ usage() if ( @ARGV < 1 );
rsb => 0700144, # select PTR in binary mode rsb => 0700144, # select PTR in binary mode
psf => 0700201, # skip if PTP flag set psf => 0700201, # skip if PTP flag set
pcf => 0700202, # clear PTP clag pcf => 0700202, # clear PTP flag
psa => 0700204, # punch PTP in alphanumeric mode psa => 0700204, # punch PTP in alphanumeric mode
psb => 0700244, # punch PTP in binary mode psb => 0700244, # punch PTP in binary mode
@@ -157,7 +156,7 @@ usage() if ( @ARGV < 1 );
krb => 0700312, # read KBD buffer krb => 0700312, # read KBD buffer
iors => 0700314, # input/output read status iors => 0700314, # input/output read status
tsf => 0700401, # if if TTY output flag set tsf => 0700401, # skip if if TTY output flag set
tcf => 0700402, # clear TTY output flag tcf => 0700402, # clear TTY output flag
tls => 0700406, # load TTY output buffer and select tls => 0700406, # load TTY output buffer and select
@@ -272,8 +271,9 @@ sub parse_file {
close($IN); close($IN);
} }
# process a label and set its value to the location counter (only called on pass 1) # process a label and set its value to the location counter
# (if called on pass 2, should check if values are identical) # only called on pass 1;
# if called on pass 2, should check if values are identical
sub process_label { sub process_label {
my $label = shift; my $label = shift;
@@ -294,25 +294,21 @@ sub process_label {
} }
# Blame Phil for this.... # Blame Phil for this....
# parses global $line based on prefixes # parses global $line based on prefixes, nibbling of a bit at a time
# (nibbling of a bit at a time) # (: and ; can appear in char literals)
# handles multiple ';' separated words per line # handles multiple ';' separated words per line
sub parse_line { sub parse_line {
$line_error = ' ';
while (1) { while (1) {
$line_error = ' '; # clear listing error indicator
return if ($line eq '' || $line =~ m{^"}); # empty or comment: quit
# Lose any leading whitespace # Lose any leading whitespace
$line =~ s{^\s*}{}; $line =~ s{^\s*}{};
print "parse_line: '$line'\n" if ($debug); print "parse_line: '$line'\n" if ($debug);
return if ($line eq ''); while ($line =~ s{^([a-z0-9\.]+):\s*}{}) { # labels
if ($line =~ m{^"}) { # remainder of line is comment
return;
}
if ($line =~ s{^([a-z0-9\.]+):}{}) { # label
my $label = $1; my $label = $1;
# First pass: parse the labels # First pass: parse the labels
@@ -321,13 +317,10 @@ sub parse_line {
process_label($1); process_label($1);
} }
} }
else {
my $lhs = undef;
if ( $line =~ s{^(\S+)\s*=}{}) { # assignment if ( $line =~ s{^(\S+)\s*=}{}) { # assignment
$lhs = $1; my $lhs = $1;
}
my $word = parse_expression(); my $word = parse_expression();
if ($lhs) {
printf( "Setting variable %s to 0%o\n", $lhs, $word ) if ($debug); printf( "Setting variable %s to 0%o\n", $lhs, $word ) if ($debug);
$Var{$lhs} = $word; $Var{$lhs} = $word;
printf("\t%06o %s\n", $word, $line_error) if ($stage == 2 && $format eq 'list'); printf("\t%06o %s\n", $word, $line_error) if ($stage == 2 && $format eq 'list');
@@ -335,6 +328,7 @@ sub parse_line {
else { # bare expression else { # bare expression
# Get its value on pass two and save to memory # Get its value on pass two and save to memory
# Also save the input line that altered memory # Also save the input line that altered memory
my $word = parse_expression();
if ( $stage == 2 ) { if ( $stage == 2 ) {
my $location = $Var{'.'}; my $location = $Var{'.'};
$Mem[$location] = $word; $Mem[$location] = $word;
@@ -347,18 +341,16 @@ sub parse_line {
# Move up to the next location in both passes # Move up to the next location in both passes
$Var{'.'}++; $Var{'.'}++;
} # expr } # expr
} # assignment or expression
# eat trailing whitespace and ";", if any # eat trailing whitespace and ";", if any
$line =~ s{^\s*}{}; $line =~ s{^\s*;?}{};
$line =~ s{^;}{};
} # while } # while
} }
# Blame Phil for this bit too... # Blame Phil for this bit too...
# Parse an expression off $line and return a PDP-7 word # Parse an expression off $line and return a PDP-7 word
# as a series of whitespace separated "syllables" # as a series of whitespace separated "syllables"
# and adds them together. # ORed, added, or subtracted
sub parse_expression { sub parse_expression {
my $word = 0; my $word = 0;
@@ -366,23 +358,22 @@ sub parse_expression {
while (1) { while (1) {
my $syllable = 0; my $syllable = 0;
my $sign = 1; my $op = '|';
$line =~ s{^\s+}{}; $line =~ s{^\s+}{};
print " '$line'\n" if ($debug);
if ($line eq '' || $line =~ m{^[";]}) { # EOL ; and " terminate expr if ($line eq '' || $line =~ m{^[";]}) { # EOL ; and " terminate expr
printf("\tparse_expression => %#o\n", $word) if ($debug); printf("\tparse_expression => %#o\n", $word) if ($debug);
return $word; return $word;
} }
print " '$line'\n" if ($debug);
if ($line =~ s{^-}{}) { if ($line =~ s{^-}{}) {
# leading '-' negates upcomming syllable. $op = '-';
$sign = -$sign;
} }
else { elsif ($line =~ s{^\+}{}) {
# ignore leading '+' $op = '+';
$line =~ s{^\+}{};
} }
if ($line =~ s{^<(.)}{}) { # <char if ($line =~ s{^<(.)}{}) { # <char
@@ -406,8 +397,7 @@ sub parse_expression {
printf("\tlbl: %s: %#o\n", $sym, $syllable) if ($debug); printf("\tlbl: %s: %#o\n", $sym, $syllable) if ($debug);
} }
elsif ($stage == 2) { elsif ($stage == 2) {
err('U', "$sym not defined") unless (defined $Undef{$sym}); err('U', "$sym not defined")
$Undef{$sym} = 1; # only complain once
} # pass 2 } # pass 2
} # symbol } # symbol
elsif ( $line =~ s{^(\d+)([fb])}{} ) { # relative label elsif ( $line =~ s{^(\d+)([fb])}{} ) { # relative label
@@ -421,14 +411,31 @@ sub parse_expression {
else { else {
$syllable = $value + 0; $syllable = $value + 0;
} }
$syllable &= 0777777;
} }
else { else {
# From the BSD fortune file:
# Ken Thompson has an automobile which he helped design.
# Unlike most automobiles, it has neither speedometer,
# nor gas gauge, nor any of the numerous idiot lights
# which plague the modern driver. Rather, if the driver
# makes any mistake, a giant "?" lights up in the center
# of the dashboard. "The experienced driver",
# he says, "will usually know what's wrong.
err('?', "huh? '$line'"); err('?', "huh? '$line'");
$line = ''; $line = ''; # abort processing
return $word; return $word;
} }
$syllable = ($syllable * $sign) & 0777777; if ($op eq '+') {
$word = ($word + $syllable) & 0777777; $word += $syllable;
}
elsif ($op eq '-') {
$word -= $syllable;
}
else {
$word |= $syllable;
}
$word &= 0777777;
printf("\tsyllable: %#o word: %#o\n", $syllable, $word) if ($debug); printf("\tsyllable: %#o word: %#o\n", $syllable, $word) if ($debug);
} }
} }
+111
View File
@@ -0,0 +1,111 @@
" Warren's cp program: cp arg1 arg2
main:
" Load the pointer pointer in 017777 to see if we have any arguments
lac 017777 i
sad d12
jmp 1f " We have 12 words, so we have 2 arguments
jmp argserror " Otherwise, print an error and exit
1: lac 017777 " Move five words past the argument word count
tad d5 " so that AC points at the first argument
" Save the pointer to the file name
dac name
" Open the input file and get the fd into AC
sys open; name:0; 0;
spa
jmp badfile " Negative fd, exit with an error message
dac infd " Save the file descriptor
lac 017777 " Move nine words past the argument word count
tad d9 " so that AC points at the second argument
dac name
dac name2
" Open the ouput file and get the fd into AC
sys open; name2:0; 1;
spa
jmp badfile " Negative fd, exit with an error message
dac outfd " Save the file descriptor
fileloop:
" Read five words into the buffer from the input file
" Five was chosen arbitrarily
lac infd
sys read; buf; 5
spa " Skip if result was >= 0
jmp readerror " Result was -ve, so error result
sna " Skip if result was >0
jmp fileend " Result was zero, so nothing left to read
" Save the count of words read in
dac 1f
" Write five words from the buffer to the output file
lac outfd
sys write; buf; 1:0
spa " Skip if result was >= 0
jmp writeerror " Result was -ve, so error result
" and loop back for more words to read
jmp fileloop
fileend:
" Close the open file descriptors
lac infd
sys close
lac outfd
sys close
sys exit
" This code comes from the real cat.s
badfile:
lac name " Get the pointer to the filename
dac 1f " Store it in 1f below
lac d8 " Load fd 8 which is stderr
sys write; 1:0; 4 " Write the name, max 4 words
lac d8 " Then write " ?\n"
sys write; 1f; 2
sys exit " and exit
1: 040; 077012 " String literal: " ?\n"
readerror:
" Print an "err read" string on stderr and exit
lac d8
sys write; noreadstr; 5
sys exit
noreadstr:
<er>;<r 040;<re>;<ad>;012000
writeerror:
" Print an "err write" string on stderr and exit
lac d8
sys write; nowritestr; 6
sys exit
nowritestr:
<er>;<r 040;<wr>;<it>;<e 012
argserror:
" Print an "bad args" string on stderr and exit
lac d8
sys write; badargs; 5
sys exit
badargs:
<ba>;<d 040;<ar>;<gs>;012000
infd: 0 " fd of the input file
outfd: 0 " fd of the output file
d5: 5
d9: 9
d8: 8 " stderr seems to have fd 8
d12: 12
" Input buffer for read
buf: .=.+5