Assembly Language Programming

August 3, 2011

The PDP-11 Assembly Language

Filed under: Assembly Language, Compilers — Tags: , , , , — programmer209 @ 11:31 am

I have written a PDP-11 assembly language simulator in Javascript. There will be three posts for this project:

Post 1 (this post) gives an outline of the PDP-11 assembly language.

Post 2 contains some example programs for the CRC-16, MD4 Hash and TEA (Tiny Encryption Algorithm) algorithms.

Post 3 covers the actual simulator (written in HTML/Javascript).

The PDP-11

The PDP-11 was a 16-bit mini-computer manufactured by the Digital Equipment Corporation (DEC) during the 70’s and 80’s. The design for the architecture of this machine was one of the most successful and innovative of it’s era. The instruction set for the PDP-11 had a reputation among programmers as being one of the most elegant and well designed assembly languages, and was compact but powerful.

Both the PDP-11 architecture and instruction set introduced several new ideas, which influenced the design of subsequent generations of micro-processors (such as the Intel 386).

The 16 bit word size enabled access to 65,536 bytes of memory (but this was expandable). Memory was usually referenced in terms of 16 bit words, for a total of 32,768 words. The low byte of each word was always at an even memory location, and the high byte at an odd memory location. In other words, the byte ordering was little endian. The top 4096 words in the memory space (from 177777 downwards) were reserved for the system, giving 28,672 words for programming.

The PDP-11 had 8 16-bit registers that could be used by the programmer. They were numbered from 0 to 7. Registers R0 to R5 were general purpose registers. Register R6 (also known as SP) normally functioned as a stack pointer, while register R7 was the program counter (PC).

Also available to the programmer was the 16 bit Processor Status Word (PSW). Only the lower 5 bits of the PSW are relevant to the simulator that I have written. These are the TNZVC bits, being the Trap bit (T) and the Condition Code bits (NZVC). The condition code bits are set by the most recently executed instruction and contain information about the result of that instruction:

N = 1 if the result was negative.
Z = 1 if the result zero.
V = 1 if there was an arithmetic overflow.
C = 1 if there was a carry out of the msb.

Each PDP-11 machine language instruction has a length of 16 bits (1 word) and is always located at an even memory location. The flow of execution of a program is managed by the Program Counter (PC) register. This register stores the 16 bit address of the next instruction that is to be executed. It works like this:

– The current instruction pointed to by the PC is loaded into the CPU.
– The PC is incremented by 2 bytes (or 1 word) to point to the next word in memory.
– The instruction that was just loaded into the CPU is executed.

Note that whenever the PC is used to read a word from memory, it must be incremented by 2 bytes.

References

The PDP 11-40 Processor Handbook can be found at: http://pdos.csail.mit.edu/6.828/2005/readings/pdp11-40.pdf.

This handbook contains the instruction set for the pdp-11. My simulator implements most (but not all) of this instruction set. Refer to the tables below to see which instructions are implemented by my simulator.

The reference manual for the MACRO-11 assembler can be found at:

http://www.bitsavers.org/pdf/dec/pdp11/rsx11/RSX11M_V4.1_Apr83/4_ProgramDevelopment/AA-V027A-TC_macro11_Mar83.pdf.

My simulator only implements a few of the most basic directives and features of the MACRO-11 assembler, as described below.

The PDP-11 Instruction Set

I won’t describe all of the instructions available in the PDP-11 instruction set here. Full descriptions can be found in the PDP 11/40 Processor Handook. My simulator supports most (but not all) of the instructions decribed in this handbook.

One thing to note is that most of the single and double operand instructions have both a byte mode and a word mode. Byte mode effects either a single byte in main memory or the low byte of a (16 bit) register. Word mode effects an entire word in main memory or the entire contents of a register.

The PDP-11 Addressing Modes

The key to understanding the PDP-11 instruction set is in understanding how the addressing modes work. In a PDP-11 instruction, the source and/or destination operands can refer to either a location in main memory or a register. When the instruction is assembled, just 6 bits are set aside to represent these operands. The 3 high bits are used to specify a mode (0-7), and the 3 low bits a register number (0-7). Through this mechanism, an instruction is able to reference any location within the machine’s 16 bit address space.

As illustrated in the table here, an operand references a register, but how the contents of that register are interpreted depends upon the mode. So a register can contain either a data value, an address in main memory, or an address of an address. Apart from mode 0, where the register itself stores the data, the address in main memory that the operand ultimately points to is known as the effective address (EA). The 3 mode bits determine how the value in the register is used to calculate the effective address.

Note that the description in the Action column is for the case of a source operand. In the case of a destination operand, Data will be to the right of the equals sign. For example, Data = Rn (the data is read from the source register) becomes Rn = Data (the data is saved to the destination register).

The Extra Word column indicates for which addressing modes an extra word (following the 16 bit machine code for the instruction) needs to be assembled. This will be explained shortly.

Syntax Mode Action Machine Code Extra Word
Rn Register Data = Rn 0n
(Rn)+ Autoincrement Data = (Rn)
Rn++
2n
-(Rn) Autodecrement Rn–
Data = (Rn)
4n
X(Rn) Index Offset address X = (PC)
PC += 2
Base address = Rn
Data = (Rn + X)
6n Yes
@Rn or (Rn) Register Deferred Data = (Rn) 1n
@(Rn)+ Autoincrement Deferred Data =((Rn))
Rn++
3n
@-(Rn) Autodecrement Deferred Rn–
Data =((Rn))
5n
@X(Rn) Index Deferred Offset address X = (PC)
PC += 2
Base address = Rn
Data = ((Rn + X))
7n Yes
#n Immediate Data = (PC) = n 27 Yes
@#A Immediate Deferred (Absolute) Data = ((PC)) = (A) 37 Yes
A or X(PC) Relative Offset address X = (PC)
PC += 2
Data = (PC + X) = (A)
67 Yes
@A or @X(PC) Relative Deferred Offset address X = (PC)
PC += 2
Data = ((PC + X)) = ((A))
77 Yes

In the above table, if N is an address in memory then (N) is the data stored at the address N.

For the autoincrement and autodecrement modes, the size of the increment/decrement depends on whether the instruction is a byte operation or a word operation. The size is 1 byte for a byte operation, or 2 bytes for a word operation. There are some exceptions to this. The increment/decrement is always 2 bytes for modes 3 and 5, or if the register being used is R6 (the stack pointer SP).

Also note that modes 4 and 5 cannot be used with the PC. The PC can only be incremented, not decremented. The PC always increments by 2 when it is used to read a word from memory e.g. data = (PC).

Register Mode

In register mode, the contents of a register is just a 16 bit data value. For example the operation mov r0 r1 just copies the data in register 0 to register 1.

Register Deferred Mode

In register deferred mode, the contents of the register are read as an address in main memory. Since a register stores 16 bits, any location in memory can be accessed with this mode. Usually the @ character is used to indicate register deferred mode, but the parentheses can also be used.

Some examples:

(i) MOV R0 @R1

The source operand uses register mode, while the destination operand uses register deferred mode. 
This instruction copies the contents of register 0 to the memory location stored in register 1. So 
if the value 1234 is stored in R0 and the value 100 is stored in R1, then the instruction will copy 
the value 1234 into the word at memory address 100.

(ii) MOV R0 (R1)

This is the same as (i).

(iii) ADD @R0 @R1

Both operands use register deferred mode. The instruction adds the value in the memory location 
pointed to by R0 to the value in the memory location pointed to by R1. Let there be two locations 
in memory at 100 and 200. Location 100 stores the word 000004, while location 200 stores the word 
000003. The result of the add operation will be that the value at location 200 becomes 000007. 

This can also be written as: 

ADD (R0) (R1) 

or even

add (r0) (r1)

(case only matters for user defined labels and symbols).

Autoincrement Mode

In autoincrement mode, as for register deferred mode, the contents of the register is an address. The difference is that once the address in the register is read, it is then incremented (by either 1 or 2 bytes, depending on the situation).

(i) movb r0 (r1)+

This operation will copy the low byte of the value stored in r0 to the byte at the memory location 
pointed to by r1. It will then increment the address stored in r1 by 1 byte.

Let r0 hold the value 177777 and r1 the address 100. Execution of the instruction will cause the value 
377 to be stored in the byte at location 100. The contents of r1 will then be incremented to 101. 

(ii) mov r0 (r1)+

Copy the word value stored in r0 to the word at the memory location pointed to by r1. The address in 
r1 is then incremented by 2 bytes. For the example in (i), the word at 100 becomes 177777 and the 
address in r1 is incremented to 102.

(iii) movb (sp)+ r0

Copy the byte at the memory location pointed to by sp to the low byte of the register r0. Then 
increment the contents of the register sp. But since the register here is r6 (the stack pointer) 
the increment will be by 2 bytes, even though the instruction movb is a byte operation.

Autodecrement Mode

In autodecrement mode, as for autoincrement mode, the contents of the register is an address. The difference is that the address in the register is decremented, and that the decrement occurs before the address is read. The decrement can be either 1 or 2 bytes, depending on the situation. This mode cannot be used with the PC register (which only ever increments).

(i) mov r0 -(r1)

Decrement the address stored in r1. Then copy the word in r0 to the (new) address pointed to by r1. 
Let r0 store the value 1234 and r1 the address 100. The result of this instruction is that the value 
1234 is copied to the word in memory at the address 76, because the address 100 has been decremented 
by 2 bytes to a value of 76.

Note that the default number system for the PDP-11 is octal. Thus, 100 - 2 = 76.

Autoincrement Deferred Mode

In autoincrement deferred mode, the contents of the register is the address of an address. As for the autoincrement mode, the increment occurs after the address in the register has been read. However, unlike for the autoincrement mode, the increment here is always by 2 bytes. This is because the storage for an address value is always 16 bits (and this is what the address in the register is pointing to).

loop:
mov r0 @(r1)+
dec r0
bne loop

What effect will this code fragment have?

Let:

r0 = 3
r1 = 100

Let:

(100) = 120
(102) = 144
(104) = 164

and:

(120) = 0
(144) = 0
(164) = 0

The loop repeats as long as the value in r0 is not zero. Each iteration copies the current value 
in r0 to the location in memory pointed to by the destination operand. The value in r0 is then 
decremented by 1 (by the dec instruction). The loop executes 3 times:

Iteration 1:

r0 = 3, r1 = 100

effective address of the destination operand = (100) = 120

mov --> (120) = 3

increment r1, decrement r0

Iteration 2:

r0 = 2, r1 = 102

effective address of the destination operand = (102) = 144

mov --> (144) = 2

increment r1, decrement r0

Iteration 3:

r0 = 1, r1 = 104

effective address of the destination operand = (104) = 164

mov --> (164) = 1

increment r1, decrement r0

Final state:

r0 = 0, r1 = 106

Autodecrement Deferred Mode

In autodecrement deferred mode, the contents of the register is the address of an address. As for the autodecrement mode, the decrement occurs before the address in the register is read. However, unlike for the autodecrement mode, the decrement here is always by 2 bytes. This is because the storage for an address value is always 16 bits (and this is what the address in the register is pointing to).

clear:
clr @-(r1)
dec r0
bne clear

Let:

r0 = 3
r1 = 106

As long as r0 is reset to a value of 3, this code fragment will clear the contents of the 
memory locations 120, 144 and 164 that were set in the previous example.

Index Mode

In Index Mode, the operand specifies both the register and an offset value X. The value X can be either a number, a symbol, or an expression. Adding X to the value in the register
gives an effective address. This mode can be used to randomly access elements in an array, where the register contains a base address that marks the start of the array, and X is an offset from this base address.

When an instruction that contains extra information in an operand is asembled, the 6 bit address field is not able to store this extra value. Therefore, extra storage needs to be allocated for this information. For the addressing modes that require extra storage, the extra data is stored in memory just after the 16 bit machine code for the assembled instruction. A word of storage is allocated for each piece of extra data. Some examples:

(i) clr 1234(r0)

Assembles to:

005060  <--  clr  dest: mode=6  R=0
001234  <--  1234

(ii) mov 66(r0) 24(r1)

Assembles to:

016061  <--  mov  src: mode=6  R=0  dest: mode=6  R=1
000066  <--  66
000024  <--  24

(iii) mov r0 24(r1)

Assembles to:

010061  <--  mov  src: mode=0  R=0  dest: mode=6  R=1
000024  <--  24

Example usage:

(i)

loop:
movb r0 pArr(r1)
inc r1
dec r0
bne loop

Let:

r0 = 5
r1 = 0
pArr = 60

The loop will execute 5 times and populate an array of byte values that starts at location 60:

(60) = 5
(61) = 4
(62) = 3
(63) = 2
(64) = 1

In this case, X (pArr) is fixed and the array elements are iterated through by incrementing r1.

(ii)

movb 2(r1) r2
movb 3(r1) r3
movb 7(r1) r4
movb 11(r1) r5

Let:

r1 = 60

This code will randomly access the array starting at location 60, and copy the byte values from 
the array to the low bytes of the specified registers.

In this case, r1 is kept fixed while X varies.

Note that when MOVB is used to copy a byte to a register, it extends the msb of the byte up 
through the high byte of the register.

Index Deferred Mode

Index Deferred Mode works in the same way as Index Mode, except that Rn + X gives the address of an address. As before, X is evaluated as a 16 bit word and allocated storage in a memory word immediately following the assembled instruction.

Example usage:

mov r0 @6(r1)

Let:

r0 = 1234
r1 = 40

(r1) + X = 40 + 6 = 46 is an address of an address.

Let:

(46) = 100

The mov instruction will copy 1234 to the word in memory at location 100.

(100) = 1234

Program Counter Addressing Modes

There are 4 addressing modes that use the PC as the register. These are the immediate, absolute, relative and relative deferred modes.

Immediate Mode

The syntax for the operand in immediate mode is #N. When the instruction is assembled, the value N will be assembled as an extra word. N can be a number, user defined symbol or expression. Immediate mode allows a numerical value to be loaded directly into a register or memory location. For example:

mov #1234 r1

This instruction will store the value 1234 in register 1.

It assembles as:

012701  <--  mov  src: mode=2  R=7  dest: mode=0  R=1
001234  <--  1234

What happens when this instruction is executed?

- The MOV instruction is loaded into the CPU.

- The PC increments and is now pointing to the location in memory that is storing 
  the value 1234.

- The MOV instruction is executed. This causes the address in the PC to be used to read 
  the value 1234.

- As a result the PC increments again.

- The value 1234 is copied into register 1.

Immediate mode is equivalent to autoincrement mode, but with the PC as the register. The address in the PC is used to read a value and then the PC is incremented (by 2 bytes).

Most of the time it won’t make sense to use immediate mode for a destination operand. But what happens if you do?

Consider the example: 

CLR #1234

This assembles to:

005027
001234

After the CLR instruction is executed, the same 2 words in memory look like this:

005027
000000 

In a PDP-11 assembly language instruction, the source and destination operands point to storage in memory (which is either a register or a location in main memory). So where does the #1234 operand point to? It points to the location in memory immediately following the machine code for the CLR operation (005027), where the value specified in the operand (1234) has been stored. So the result of the CLR operation is that this location in memory is cleared.

Immediate Deferred Mode

Immediate Deferred Mode is also known as absolute mode. The syntax for the operand is @#A. A can be a number, user defined symbol or expression. The value A is assembled into memory as an extra word following the machine code for the instruction. When the instruction is executed, A is interpreted as an address.

Immediate deferred mode is equivalent to autoincrement deferred mode, but with the PC as the register. The address pointed to by the PC is the location that stores the address value A i.e. the PC contains the address of an address.

mov #1234 @#100

In this instruction, the 1st operand uses immediate mode and the 2nd operand uses 
immediate deferred mode. When executed, the operation will copy the value 1234 into 
the memory location 100.

If this instruction is assembled at location 40 in memory, it will look like this:

000040  012737  <--  mov  src: mode=2  R=7  dest: mode=3  R=7
000042  001234  <--  the value 1234
000044  000100  <--  the address 100

The instruction executes as follows:

- The instruction is loaded into the CPU.

- The PC increments and now points to the location 42 that 
  holds the value 1234 (i.e. addressing mode 2). 

- The PC is used to read the value 1234 from memory, and 
  therefore increments.

- The PC now points to the location 44 that holds the address 
  100 (i.e. addressing mode 3).

- The PC is used to read the address 100 from memory, and 
  therefore increments.

- The instruction has completed execution and the PC is left 
  pointing to the location 46, containing the next instruction. 

Relative Mode

The syntax for the operand is just A, where A can be a number, user defined symbol, or expression. The operand A represents an address in memory.

Relative mode causes an extra word to be asembled in memory, but A is not the value stored in this extra word. Instead a value X is calculated, which is the offset of the address A relative to the location being pointed to by the PC. But where is the PC pointing to at the time the offset X is used to calculate A?

Consider the examples:

(i)

CLR 10

When assembled at location 0, it looks like this:

000000  005067  <--  clr  dest:  mode=6  R=7
000002  000004  <--  X = 4

The instruction executes as follows:

- The instruction is loaded into the CPU and the PC increments to 2.

- The PC is used to read the value X and increments to 4.

- At this point the address A is calculated: A = PC + X = 4 + 4 = 10 (in octal).

So, as required, the memory at location 10 is cleared.

(ii)

CLR 20

If this is assembled at location 40, it looks like this:

000040  005067  <--  clr  dest:  mode=6  R=7
000042  177754  <--  X = -24

In this case, the offset X is negative.

At the point the address A is calculated, the value X has just been loaded and the PC 
incremented such that it is pointing to the location 44.

A = PC + X = 44 + (-24) = 20 (in octal).

Relative Deferred Mode

The syntax for the operand is @A, where A can be a number, user defined symbol, or expression. The operand A is the address of an address in memory.

As for Relative mode, an extra word is assembled in memory containing the value X (where X is the offset of the address A relative to the location being pointed to by the PC). When the instruction is executed, A is calculated in exactly the same way as for relative mode.

CLR @10

This assembles to:

000000  005077  <--  clr  dest:  mode=7  R=7
000002  000004  <--  X = 4

When executed: A = PC + X = 4 + 4 = 10 (in octal).

Let the location 10 contain the value 100. This means that the effective address of the 
operand is 100.

Therefore, the instruction will clear the contents of memory location 100.

Macro-11 Symbols

Any line in the PDP-11 assembly language source code can include a label field. A label must begin with a letter, and can only contain letters and numbers. On the first pass of the compiler, the label is placed into the user defined symbol table. Only the first 6 characters of a label are recognised by the compiler and this is what will be placed into the symbol table. The value assigned to the label is the address of the CLC (current location counter) of the line that contains the label. Once stored in the symbol table, a label can be used as part of an expression anywhere in the program. A label is terminated by the colon character ‘:’. For example:

A label should be used to mark the beginning of a loop:

loop: dec r0
bne loop

The symbol 'loop' can then be used as an operand to the bne instruction.

Multiple labels can appear on one line:

A: B: C: mov r0 r1

Or alternatively:

A:
B:
C:
mov r0 r1

In this example each label is assigned the address of the mov instruction. Labels 
by themselves do not increment the value of the CLC.

Symbols or labels can only be defined once in the source code.

Macro-11 Directives

My simulator supports only a few of the Macro-11 directives:

.odd

 Add 1 if the CLC is even. Do nothing if the CLC is already odd.

.even 

 Add 1 if the CLC is odd. Do nothing if the CLC is already even.

.blkb
 
 Usage: .blkb exp - where exp can be an expression, number or symbol.

 Increment the CLC by exp bytes, to reserve a block of bytes in memory.

.blkw

 Usage: .blkw exp - where exp can be an expression, number or symbol.

 Increment the CLC by exp words, to reserve a block of words in memory.

.byte

 Usage: .byte exp1, exp2, ... 

 This directive can have multiple arguments, where each argument exp can be an 
 expression, number or symbol. Each exp evaluates to an 8 bit value which is 
 stored into memory at the address of the CLC. The CLC is incremented by 1 byte 
 for each argument. If there are no arguments, a single zero byte is placed 
 into memory.

.word

 Usage: .word exp1, exp2, ... 

 This directive can have multiple arguments, where each argument exp can be an 
 expression, number or symbol. Each exp evaluates to a 16 bit value which is 
 stored into memory at the address of the CLC. The CLC is incremented by 1 word 
 for each argument. If there are no arguments, a single zero word is placed 
 into memory.

.ascii

 Usage: .ascii "text string"

 Store a text string in memory, as a sequence of ascii values. Two 8 bit ascii 
 values will be stored in each word of memory. The string is enclosed in double 
 quotes. Double quotes cannot be used within a string, but single quotes can. 
 The string can only contain printable characters.

 My implementation of the .ascii directive is very different from how it was 
 done in Macro-11.

.end

 Usage: .end exp

 This directive tells the compiler to stop. Optionally, it also sets the entry 
 point for the execution of the code. If the argument exp is included, it will be 
 evaluated as a 16 bit value. The PC will then be set to the value of exp, which 
 can be an expression, number or symbol. If exp is not present, the PC is just 
 set to 0.

I have also added my own directive:

.hex

 Usage: .hex h1, h2, ...

 This directive can have multiple arguments, where each argument h has to be a 
 number in hexadecimal format that will evaluate to a 16 bit value which is 
 stored into memory at the address of the CLC. The CLC is incremented by 1 word 
 for each argument. If there are no arguments, a single zero word is placed 
 into memory.

Macro-11 Expressions

My simulator supports most of the Macro-11 approach to the evaluation of assignments and expressions, with a few differences.

The operators that can be used in an expression are:

addition: +
subtraction: -
multiplication: *
division: /
AND: &
OR: !

There are no operator precedence rules. An expression is just evaluated from left to right.

Brackets can be used in an expression : < and >. Parentheses cannot be used in an expression, because they are already reserved for use within instruction operands to indicate register modes. Any part of the expression that is enclosed in brackets is evaluated first. If there are multiple brackets, then the inner brackets will be evaluated first.

Some examples:

(i)

2 + 4 * 7 / 6 & 4 ! 7

= 6 * 7 / 6 & 4 ! 7

= 42 / 6 & 4 ! 7

= 7 & 4 ! 7

= 4 ! 7

= 7

(ii)

< 2 + 4 > * < < 6 / 3 > & < 4 ! 7 > >

= 6 * < < 6 / 3 > & < 4 ! 7 > >

= 6 * < 2 & 7 >

= 6 * 2

= 14 (in octal)

(iii)

Expressions can also include user defined symbols:

A * B + C / < < E / F > - 2 * 7 >

A user defined symbol can be created via an assignment. For example:

(i)

A = 2 * 3 - 7

B = A + 5

(ii)

Create multiple symbols and set them to the same value:

A = B = C = 5 * 6 - D

An assignment is evaluated during the first pass of the compiler. In this case, any symbol or label used within an expression that is on the right hand side of an equals operator must have already been defined on a preceding line of code.

All other expressions (those that appear as part of an operand for an instruction) will be evaluated during the second pass of the compiler.

Decimal And Binary Numbers

By default, any number appearing in the PDP-11 assembly language code is interpreted as an octal number. Macro-11 has the .radix directive and the ^O, ^D, ^B operators to change the number base, but my simulator does not support any of these features.

Instead, a decimal number can be specified in my simulator by appending either a ‘d’ or a dot ‘.’ to the end of a number. A binary number can be specified by appending a ‘b’ to the end of a binary value. For example:

A = 56789d

B = 89065.

C = 1001110110b

5 * 3 + 7 * 89d + 101b * 8948.

The CLC Symbol

The dot ‘.’ is the symbol for the current location counter (except when it is appended to the end of a number). It can be used in an expression, in which case the current value of the CLC will be substituted. An assignment can be used to change the value of the CLC, however it cannot be used in a multiple assignment statement. Some examples:

(i)

. = 500

(ii)

A = . + 4 * 12

(iii)

. = . + 6

(iv)

mov .+10 r0

(v)

mov #. .

(vi)

Cannot do this:

A = B = . = C = 4 * 5

The Branch Instructions

The branch instructions have the format: bxx dest. These instructions test the condition code bits (N, Z, V and C) to determine whether to branch to the specified location. If a branch occurs, the PC will be set to the effective address of the destination operand.

The limitation of the branch instructions is that dest must be within -128 to +127 words of the current PC. This is because the branch instructions do not use the addressing modes as described above (the operand is just a symbol or number). Instead, 8 bits within the instruction machine code are set aside to specify the offset from the current location in words.

The table below shows how each instruction tests the NZVC bits.

Branch NZVC
br
bne Z == 0
beq Z == 1
bpl N == 0
bmi N == 1
bvc V == 0
bvs V == 1
bcc C == 0
bcs C == 1
Branch – Signed Conditional NZVC
bge N ^ V == 0
blt N ^ V == 1
bgt Z | (N ^ V) == 0
ble Z | (N ^ V) == 1
Branch – Unsigned Conditional NZVC
bhi C == 0 && Z == 0
blos C | Z == 1
bhis C == 0
blo C == 1

Most of the PDP-11 instructions set the NZVC bits depending on what the outcome of the instruction was. The table below shows how. Note that src and dst refer to the state of the operands before the operation is executed, while result refers to the state of the dst operand after completion of the operation.

Unless otherwise specified in the table, most of the operations below will set the N bit to 1 if the result is negative, or the Z bit to 1 if the result is zero.

Single Op NZVC
clr N = V = C = 0, Z = 1
com V = 0, C = 1
inc V = (dst == 077777)
C = C
dec V = (dst == 0100000)
C = C
neg V = (result == 0100000)
C = (result != 0)
tst V = C = 0
asr V = N ^ C
C = dst & 1
asl V = N ^ C
C = dst & 0100000
ror V = N ^ C
C = dst & 1
rol V = N ^ C
C = dst & 0100000
swab V = C = 0
adc V = (dst == 077777) & (C == 1)
C = (dst == 0177777) & (C == 1)
sbc V = (dst == 0100000)
C = !(dst == 0 && C == 1)
sxt N = N
Z = (N == 0)
V = V
C = C
Double Op NZVC
mov V = 0
C = C
cmp V = msb(src ^ dst) && !msb(dst ^ result)
C = !(result & 0200000)
add V = !msb(src ^ dst) && msb(src ^ result)
C = (result & 0200000)
sub V = msb(src ^ dst) && !msb(src ^ result)
C = !(result & 0200000)
bit V = 0
C = C
bic V = 0
C = C
bis V = 0
C = C
Double Op – Register NZVC
ash V = msb(R ^ R’)
C = last bit out of R
ashc V = msb(R,R+1 ^ (R,R+1)’)
C = last bit out of R,R+1
xor V = 0
C = C

The condition code bits can also be set or cleared by the programmer, by using the instructions: sen, sez, sev, sec, scc, cln, clz, clv,clc, ccc. These instructions have no operands.

A couple of the instructions in the table above can be used to directly test and compare operands, and set the NZVC bits accordingly:

(i)

The TST(B) instruction:

tst 177777 --> NZVC = 1000

tst 77777 --> NZVC = 0000

tst 0 --> NZVC = 0100

tstb 377 --> NZVC = 1000

(ii)

The CMP(B) instruction:

Tests src and dst by calculating result = (src - dest) = src + ~dest + 1.

Let:

src = 177777
dst = 100000

result = 177777 + 77777 + 1 = 277777

cmp #177777 #100000 --> NZVC = 0001

Loops

Loops can be easily constructed by using the INC(B) or DEC(B) instructions:

(i)

mov #10 r5

Loop:

....

dec r5
bne Loop ; branch as long as r5 is not zero.

(ii)

mov #-10 r5

Loop:

....

inc r5
bne Loop ; branch as long as r5 is not zero.

Another instruction available for loop control is SOB. This has the syntax: SOB Rn dest. When this instruction executes the contents of the register Rn are decremented by 1. If Rn is not equal to 0, program control is transfered to the destination address.

The regular addressing modes are not used for the destination operand here. It is just a symbol or number that represents an address. When the SOB instruction is assembled, the offset of this address (in words) relative to the PC is calculated. In the machine code for the instruction, 6 bits are set aside for this offset, which is interpreted as a positive number. When executed, twice this offset is subtracted from the PC to get the destination address. Therefore, the destination must always be less than the PC and must also be within 63 words (126 bytes).

Stacks

A stack is a last in, first out data structure. Only 2 operations can be performed on a stack, push and pop. Push adds a data element to the stack, while pop removes a data element. A stack needs a stack pointer, which points to the most recent element pushed onto the stack. In the PDP-11 a stack is just an area in memory, with a register acting as the stack pointer. Any register apart from the PC (register 7) can be used as a stack pointer. On the PDP-11 a stack grows downward in memory.

The PDP-11 has its own processor stack. The register used for this stack is R6. Therefore R6 is also be referred to as SP (stack pointer). In my program, only the JSR and RTS instructions make use of the processor stack through the SP register. By default in my program, when the first word is pushed onto the processor stack it will be stored at memory location 177776, and the stack will grow downward in memory from there. Of course, the user can set the value of SP to any memory location they desire before using it.

The processor stack always contains words, therefore SP can only be incremented or decremented by 2 bytes. The byte instructions can be used with the processor stack, but they will only operate on the lower byte of each word in the stack.

Other stacks however (using registers 0 to 5) can contain either words or bytes. They can be placed anywhere in memory, and multiple stacks can be maintained at any one time (using one register per stack).

The autoincrement, autodecrement and index register modes are used to respectively pop, push and randomly access data bytes or words belonging to a stack on the PDP-11. Some examples:

(i)

; use r5 as the stack pointer

mov #170 r5

; build the stack

mov #1 -(r5)
mov #2 -(r5)
mov #3 -(r5)

; access the top of the stack

mov (r5) r0

; randomly access a stack element

mov 2(r5) r1

; pop data from the top of the stack
 
mov (r5)+ r2

; reset the stack pointer

add #4 r5

halt

(ii) Two stacks:

; use r4 and r5 as stack pointers

mov #170 r4
mov #220 r5

; build the r4 stack

mov #1 -(r4)
mov #2 -(r4)
mov #3 -(r4)

; copy to build the r5 stack

mov 4(r4) -(r5)
mov 2(r4) -(r5)
mov 0(r4) -(r5)

halt

(iii) A byte stack:

mov #170 r5

; build the stack

movb #1 -(r5)
movb #2 -(r5)
movb #3 -(r5)
movb #4 -(r5)

; the top of the stack

movb (r5) r0

; random access

movb 3(r5) r1

halt

Jumps and Subroutines

The unconditional jump instruction is: JMP dest. Unlike the branch instructions, JMP can transfer program control to any location in memory. However, address mode 0 (register mode) cannot be used for the destination operand here, because there is no way to calculate an effective address.

The instructions for jumping to and returning from subroutines are: JSR and RTS.

The usage for the JSR instruction is: JSR Rn dest. When this instruction is executed, 3 things happen:

(i) The contents of the register Rn are pushed onto the processor stack.

(ii) The contents of the PC register are copied to Rn.

(iii) The effective address pointed to by the destination operand is loaded into the PC.

Execution of the program will continue from the new location pointed to by the PC until a RTS instruction is encountered. The usage for the RTS instruction is: RTS Rn. When this instruction is executed:

(i) The contents of Rn are copied to the PC.

(ii) The top of the processor stack is popped into the register Rn.

In other words, the original contents of the PC and Rn are restored and the program resumes execution from the next instruction after the original JSR call. Usually the register Rn will be the same for both the JSR and RTS instructions. This register is known as the Linkage Pointer. Some examples:

(i) Without arguments:

mov #1 r0
mov #2 r1
jsr r5 SUB_0
clr r0
clr r1
halt

.=40

SUB_0:
add r0 r2
add r1 r2
rts r5

(ii) With arguments:

In this example there are 3 arguments, that are stored in the 3 words 
following the JSR instruction. Just before the JSR instruction is 
executed the PC is incremented as usual, and therefore points to 
the first argument. Execution of JSR results in the PC being copied to 
r5. As a result, the arguments can be accessed inside the subroutine 
SUB_1 through the linkage register (r5). SUB_1 just uses r5 to load the 
arguments into the registers 0, 1 and 2.

When the SUB_1 subroutine has finished executing, the register r5 should 
be left pointing to the instruction following the last argument, so that 
the RTS instruction will return program execution to the right place.

jsr r5 SUB_1
arg1: .word 10
arg2: .word 20
arg3: .word 30
clr r0
clr r1
clr r2
halt

.=60

SUB_1:
mov (r5)+ r0
mov (r5)+ r1
mov (r5)+ r2
rts r5

If random access to the arguments is required inside the subroutine 
SUB_0, index mode can be used on the linkage register r5. In this 
case the add instruction is used just before the RTS instruction, to 
make sure that program execution returns to the right place.

jsr r5 SUB_1
arg1: .word 10
arg2: .word 20
arg3: .word 30
clr r0
clr r1
clr r2
halt

.=60

SUB_1:
mov (r5) r0
mov 2(r5) r1
mov 4(r5) r2
add #6 r5
rts r5

(iii) PC as the linkage pointer:

It is alright to use the PC as the linkage register. The value of the 
PC is pushed onto the processor stack when JSR is executed, and restored 
(popped off the stack) when RTS is called.

mov #1 r0
mov #2 r1
jsr pc SUB_0
clr r0
clr r1
halt

.=40

SUB_0:
add r0 r2
add r1 r2
rts pc

(iv) Passing arguments via the processor stack:

If the PC is also the linkage register, arguments can still be passed by 
using the processor stack. They must be added to the processor stack before 
the JSR instruction is executed. Inside the SUB_0 subroutine, the arguments 
can then be randomly accessed using index mode on the SP register. Just 
remember that the PC value needed to restore program execution is also on 
the processor stack.

mov arg1 -(sp)
mov arg2 -(sp)
mov arg3 -(sp)
jsr r7 SUB_1
clr r0
clr r1
clr r2
add #6 sp
halt

.= 40

SUB_1:
mov 6(sp) r0
mov 4(sp) r1
mov 2(sp) r2
rts r7

.=100
arg1: .word 10
arg2: .word 20
arg3: .word 30

Mixing data and instructions, as in (ii) above, is undesirable. Passing arguments 
via the stack avoids this. 

If the arguments do not need to be on the stack after the subroutine returns, just 
use the add instruction to adjust the stack pointer (sp).

Function Pointers

Function pointers are easy to implement, as the following example shows:

mov #fn_A F_ptr
jsr r7 @F_ptr

mov #fn_B F_ptr
jsr r7 @F_ptr

mov #fn_C F_ptr
jsr r7 @F_ptr

halt

; function A

fn_A:
mov #-1 r0
rts r7

; function B

fn_B:
mov #-1 r1
rts r7

; function C

fn_C:
mov #-1 r2
rts r7

; the function pointer

F_ptr: .word

.end 0

Recursion

Through the use of JSR and RTS, recursion is possible, as shown in the example:

; compute Total = 1 + 2 + ... + N

; initialise r0 to N

mov N r0

; call the subroutine

jsr r5 SUM

; save the result

mov r1 Total

halt

.=40

; the subroutine

SUM:

; if r0 is 0, stop

tst r0
beq stop

; add r0 and then decrement

add r0 r1
dec r0

; call the subroutine again

jsr r5 SUM

stop:

rts r5

; storage for N and Total

N: .word 5

Total: .word 0

.end 0

Bitwise AND

A bitwise AND operation that leaves the result in the destination location is not available in the PDP-11 instruction set. There is the BIT (bit test) instruction that uses the bitwise AND operation to test a pair of operands, but this instruction only modifies the condition code bits. However, a bitwise AND operation can be constructed by using the bitwise complement (COM) and bit set (BIS) instructions. Note that the BIS instruction is just a bitwise OR. The following identity expresses the bitwise AND in terms of the bitwise complement and OR operations:

A & B = ~(~A | ~B)

The following code demonstrates that this identity is correct:

; 0 and 0

clr r0
clr r1

jsr r7 AND

mov r1 AND_00

; 0 and 1

clr r0
mov #1 r1

jsr r7 AND

mov r1 AND_01

; 1 and 0

mov #1 r0
clr r1

jsr r7 AND

mov r1 AND_10

; 1 and 1

mov #1 r0
mov #1 r1

jsr r7 AND

mov r1 AND_11

halt

AND:

; Bitwise AND r0 and r1

com r0
com r1
bis r0 r1
com r1

rts r7

; truth table

AND_00: .word
AND_01: .word
AND_10: .word
AND_11: .word

.end 0

Create a free website or blog at WordPress.com.