An 8051 executes small instructions on bytes and individual bits. The accumulator A is the main arithmetic register; B assists multiplication and division. R0-R7 are eight working registers selected from one of four banks in internal RAM. DPTR is a 16-bit address register, split into DPH and DPL. PC is the program counter; SP points to the last occupied byte of the internal-RAM stack.
A byte holds 0-255 unsigned or -128 to +127 in two's-complement notation. The bit pattern is the same: your program chooses the interpretation. Hexadecimal 0x2A is decimal 42. In these examples # means an immediate constant, @ means indirect addressing and a semicolon begins a comment. DOS/51 accepts 0x-prefixed numbers; many desktop assemblers also accept 2AH. Directives such as ORG, DB, EQU and END are assembler commands, not CPU instructions, and their spelling depends on the assembler.
The classic CPU has 44 instruction mnemonics, conventionally grouped into 111 operand forms, represented by 255 defined opcode bytes. Opcode A5 is reserved. Register-number and page-address bits explain why one form can have several opcodes. This guide covers the original instruction set, not 8051-compatible vendors' later extra opcodes.
One classic machine cycle is 12 oscillator clocks. At the DOS/51 board's 11.0592 MHz that is 1.085 microseconds. Most instructions take one or two machine cycles; MUL and DIV take four. A modern 1T 8051 derivative is not timing-compatible. The reference gives classic hardware timing, not a benchmark of JavaScript execution.
TRY IT IN DOS/51
Open nano first.asm, enter the program below, save with Ctrl-O and leave with Ctrl-X. Run asm -r first.asm. DOS/51 assembles at 0x8000; RET returns to the shell. This example prints an asterisk through the board's existing UART configuration. It does not reinitialize hardware owned by the operating system.
clr 0x99 ; clear stale TI before transmitting
mov a,#42
mov 0x99,a ; SBUF: transmit '*'
wait_tx:
jnb 0x99,wait_tx ; bit address 0x99 is TI, not SBUF
clr 0x99 ; acknowledge transmit completion
ret
That apparent address collision is deliberate: direct byte 0x99 is SBUF, whereas bit address 0x99 is SCON.1 (TI). The CPU instruction determines which address space is used.
Bare-metal programs are different: they start at reset address 0x0000, provide vectors, initialize SP, ports and peripherals, then run forever. Do not copy a reset-vector program into the DOS/51 transient area and expect it to own the machine. Save work before experiments that alter the stack, interrupt state or UART.
THREE SPACES, ONE NUMBER
CODE is up to 64 KB of program memory, fetched by PC and read as data with MOVC. XDATA is a separate, up-to-64-KB external data space accessed with MOVX. Internal DATA contains the CPU's small RAM and its directly addressed special function registers. Thus MOV A,0x30, MOVC A,@A+DPTR and MOVX A,@DPTR can read three different physical places even if the numerical address is the same.
INTERNAL MAP (128-byte 80C31)
00-07 register bank 0 (reset default)
08-0F register bank 1
10-17 register bank 2
18-1F register bank 3
20-2F 16 bytes / 128 individually addressable RAM bits
30-7F general RAM and stack, allocated by your program
80-FF directly addressed SFRs, NOT another 128 RAM bytes
WHAT IS A REGISTER BANK?
The working registers R0-R7 are eight names for eight bytes in internal RAM. There are four sets of those bytes, called register banks. Only one bank is selected at a time. Selecting another bank changes which RAM bytes the names refer to; it does not copy or erase their contents.
For example, with bank 0 selected, MOV A,R3 reads internal RAM address 0x03. With bank 1 selected, the same instruction reads address 0x0B. R3 is register number 3 (the fourth register, because numbering starts at zero). By contrast, MOV A,0x03 always reads address 0x03, regardless of the selected bank.
HOW THE CPU SELECTS A BANK
PSW stands for Program Status Word. It is a special function register: a byte used by the CPU for status flags and control settings, rather than ordinary data storage. Two of its bits select the register bank: RS1 is bit 4 and RS0 is bit 3. Written as RS1:RS0, they form a two-bit number:
Reset selects bank 0. Other banks can give interrupt handlers their own working registers, but software must manage the selection and reserve the RAM. In DOS/51, do not casually switch banks or overwrite these bytes: the running firmware also uses internal RAM and the stack.
WHAT DOES INDIRECT ADDRESSING MEAN?
A direct operand names the address in the instruction itself: MOV A,0x30 reads RAM address 0x30. An indirect operand instead uses an address held in a register. After MOV R0,#0x30, MOV A,@R0 reads that same RAM byte. The @ means 'use the address stored in this register'; ordinary indirect MOV supports R0 or R1, not any arbitrary register.
Addressing examples:
mov a,#0x30 ; A = literal 48
mov a,0x30 ; A = RAM byte at 30
mov r0,#0x30
mov a,@r0 ; same RAM byte, indirect
mov dptr,#0x1234
movx a,@dptr ; external DATA byte at 1234
clr a
movc a,@a+dptr ; CODE byte at 1234
ORDINARY MOV: RAM OR A HARDWARE REGISTER?
The 80C31 has only 128 bytes of internal RAM, at 00-7F. Addresses 80-FF select special function registers (SFRs) only when used as DIRECT operands. These SFRs control hardware such as ports and timers. For instance, MOV A,0x90 reads the Port 1 register, P1.
Ordinary indirect MOV does not use that SFR address space. Loading R0 with 0x90 and executing MOV A,@R0 therefore does NOT read P1: it attempts to read an internal RAM byte that does not exist on this 80C31. The 80C32 is different: it adds another 128 internal RAM bytes at indirect addresses 80-FF. That extra RAM still does not turn indirect accesses into SFR accesses. Choose both the address AND the addressing mode deliberately.
MOVX through @R0/@R1 uses an 8-bit low address; classic external hardware uses P2's latch for the high address. MOVX @DPTR uses all 16 address bits. Neither is the same as ordinary MOV @Ri. MOVC uses A as an unsigned index; the @A+PC base is PC AFTER the MOVC opcode. JMP @A+DPTR also uses an unsigned byte offset, not a table of pointers automatically dereferenced by hardware.
BIT MEMORY
Bit addresses 00-7F map to bytes 20-2F: byte = 20 + (bit_address / 8), bit = bit_address modulo 8. Bit 00 is RAM 20.0; bit 7F is RAM 2F.7. Bit addresses 80-FF select implemented bit-addressable SFRs whose BYTE address is divisible by eight. Not every SFR supports bit operations: TMOD at 89 does not. Bit address 89 is TCON.1, not TMOD. Use ANL/ORL on a whole byte for non-bit-addressable registers.
FLAGS: PSW = CY AC F0 RS1 RS0 OV user P
CY is carry for addition and borrow for SUBB. AC is carry/borrow across bit 3. OV signals signed arithmetic overflow. P is maintained from A: 1 for an odd number of set bits. F0 is a software flag. The classic CPU has NO zero or negative flag; JZ/JNZ inspect A itself. INC and DEC do not update CY or OV. They still affect P if they change A.
For unsigned comparison, CJNE sets CY when its first unsigned operand is less than the second. For signed comparison you must account for sign/overflow yourself. Example: 0x7F + 1 becomes 0x80 with OV=1 but CY=0. Conversely 0xFF + 1 becomes 0 with CY=1 but OV=0.
16-BIT ADD / SUBTRACT
mov a,0x30 ; low byte of left operand
add a,0x32 ; low byte of right operand
mov 0x34,a
mov a,0x31
addc a,0x33
mov 0x35,a ; final CY is the 16-bit carry
For subtraction start with CLR C and use SUBB on both low and high bytes. A plain SUB instruction does not exist.
STACK
Reset sets SP=07; a PUSH first increments SP, then writes. CALL pushes the return address low byte first, then high; RET reverses that sequence. A bare-metal MOV SP,#0x5F makes the first pushed byte land at 60, leaving 32 bytes through 7F. Reserve that area and check worst-case calls plus interrupt nesting. The stack does not automatically grow into external RAM. PUSH/POP accept direct byte addresses, not immediate data or an Rn operand. PUSH 0x00 saves bank-0 R0, not necessarily the current bank's R0.
PORT READS
MOV A,P1 reads pins. Read-modify-write instructions with a port destination (ANL P1,#mask, ORL P1,#mask, CPL P1.0 and friends) use the output latch. That matters when external circuitry pulls a released input low. Use masked or bit writes so unrelated outputs survive.
All 111 operand forms, all 255 defined opcode bytes. Rn = R0–R7; @Ri = @R0 or @R1; dir = direct byte address; #imm = immediate byte; rel = signed displacement from the next instruction; addr11 = same-2-KB-page target; addr16 = full CODE address. Commas separate opcodes within a form, not bytes of one encoding. Multi-byte operands follow the opcode; MOV dir,dir encodes source then destination. Full addresses/immediate words are high byte then low byte; stack PC pushes are low then high.
MC means classic 12-clock machine cycles. Flag columns list arithmetic/Boolean effects, not every possible destination side effect. Parity P follows A automatically; writes to PSW or its bits can explicitly alter flags. All conditional branches cost two MC whether taken or not. Reserved A5 has no portable behavior.
44 mnemonics · 111 operand forms
ACALL
flow
Push the address after this instruction and call a target in the same 2-KB page as that return address. Uses two internal stack bytes; no automatic register saves.
Operand form
Opcode(s), hex
Bytes
MC
Flags
ACALL addr11
11, 31, 51, 71, 91, B1, D1, F1
2
2
—
acall worker
Watch: Use LCALL if placement is not guaranteed. Page bits are checked against PC after the two-byte call.
ADD
arithmetic
A = A + source; CY, AC and OV describe the unsigned, nibble and signed results. Incoming CY is ignored.
Operand form
Opcode(s), hex
Bytes
MC
Flags
ADD A,#imm
24
2
1
CY AC OV
ADD A,dir
25
2
1
CY AC OV
ADD A,@Ri
26, 27
1
1
CY AC OV
ADD A,Rn
28, 29, 2A, 2B, 2C, 2D, 2E, 2F
1
1
CY AC OV
add a,#1
Watch: For multi-byte addition use ADD on the low byte and ADDC on subsequent bytes.
ADDC
arithmetic
A = A + source + incoming CY. Updates CY, AC and OV.
Operand form
Opcode(s), hex
Bytes
MC
Flags
ADDC A,#imm
34
2
1
CY AC OV
ADDC A,dir
35
2
1
CY AC OV
ADDC A,@Ri
36, 37
1
1
CY AC OV
ADDC A,Rn
38, 39, 3A, 3B, 3C, 3D, 3E, 3F
1
1
CY AC OV
addc a,r2
Watch: Initialize carry deliberately if this is the first byte of a calculation.
AJMP
flow
Jump within the 2-KB page containing the first byte after this instruction. Does not push a return address.
Operand form
Opcode(s), hex
Bytes
MC
Flags
AJMP addr11
01, 21, 41, 61, 81, A1, C1, E1
2
2
—
ajmp destination
Watch: Crossing a page boundary during layout can invalidate a previously legal target.
ANL
logic
Bitwise AND for byte operands. Boolean forms compute C AND bit or C AND NOT bit; the / complements the source only.
Operand form
Opcode(s), hex
Bytes
MC
Flags
ANL dir,A
52
2
1
—
ANL dir,#imm
53
3
2
—
ANL A,#imm
54
2
1
—
ANL A,dir
55
2
1
—
ANL A,@Ri
56, 57
1
1
—
ANL A,Rn
58, 59, 5A, 5B, 5C, 5D, 5E, 5F
1
1
—
ANL C,bit
82
2
2
CY
ANL C,/bit
B0
2
2
CY
anl 0x90,#0xFE
Watch: Byte forms preserve CY/AC/OV unless explicitly writing PSW. Boolean forms change C. Port destinations use their latch.
CJNE
flow
Compare without modifying either operand; branch if unequal. CY becomes 1 when the first operand is unsigned-less-than the second, otherwise 0.
Operand form
Opcode(s), hex
Bytes
MC
Flags
CJNE A,#imm,rel
B4
3
2
CY
CJNE A,dir,rel
B5
3
2
CY
CJNE @Ri,#imm,rel
B6, B7
3
2
CY
CJNE Rn,#imm,rel
B8, B9, BA, BB, BC, BD, BE, BF
3
2
CY
cjne a,#10,different
Watch: This is unsigned comparison, not a signed less-than instruction. CY changes even if the branch is not taken.
CLR
bits
Clear A, C or one directly addressed bit. CLR A does not clear carry.
Operand form
Opcode(s), hex
Bytes
MC
Flags
CLR bit
C2
2
1
—
CLR C
C3
1
1
CY
CLR A
E4
1
1
—
clr c
Watch: Use CLR C before the first SUBB when no incoming borrow is intended.
CPL
bits
Complement all bits in A, carry alone or a single directly addressed bit.
Operand form
Opcode(s), hex
Bytes
MC
Flags
CPL bit
B2
2
1
—
CPL C
B3
1
1
CY
CPL A
F4
1
1
—
cpl 0x90
Watch: CPL A leaves CY alone; CPL C toggles CY. A port-bit complement reads the latch.
DA
arithmetic
Decimal-adjust A after packed-BCD ADD/ADDC, using the low nibble, AC, high digit and CY. Can set carry for a decimal carry out; does not clear an existing carry.
Operand form
Opcode(s), hex
Bytes
MC
Flags
DA A
D4
1
1
CY
da a
Watch: Only for valid packed-BCD addition. Not binary-to-BCD conversion or decimal subtraction.
DEC
arithmetic
Subtract one modulo 256 from A, an Rn register, a direct byte or internal RAM through @Ri. Does not update CY, AC or OV.
Operand form
Opcode(s), hex
Bytes
MC
Flags
DEC A
14
1
1
—
DEC dir
15
2
1
—
DEC @Ri
16, 17
1
1
—
DEC Rn
18, 19, 1A, 1B, 1C, 1D, 1E, 1F
1
1
—
dec r7
Watch: There is no DEC DPTR instruction. A becomes FF when decrementing zero.
DIV
arithmetic
Unsigned divide A by B: quotient in A, remainder in B. CY is cleared; OV is cleared for a nonzero divisor and set for zero.
Operand form
Opcode(s), hex
Bytes
MC
Flags
DIV AB
84
1
4
CY=0 OV
div ab
Watch: When B is zero A and B are undefined. Test the divisor first.
DJNZ
flow
Decrement an Rn register or direct byte modulo 256, then branch if that result is nonzero. Arithmetic flags are unchanged.
Operand form
Opcode(s), hex
Bytes
MC
Flags
DJNZ dir,rel
D5
3
2
—
DJNZ Rn,rel
D8, D9, DA, DB, DC, DD, DE, DF
2
2
—
djnz r7,loop
Watch: An initial zero count wraps to FF and yields 256 iterations in a bottom-tested loop.
INC
arithmetic
Add one modulo 256 to a byte, or modulo 65536 to DPTR. Does not update CY, AC or OV.
Operand form
Opcode(s), hex
Bytes
MC
Flags
INC A
04
1
1
—
INC dir
05
2
1
—
INC @Ri
06, 07
1
1
—
INC Rn
08, 09, 0A, 0B, 0C, 0D, 0E, 0F
1
1
—
INC DPTR
A3
1
2
—
inc dptr
Watch: DPTR is the only 16-bit INC destination; it does not provide a carry flag on rollover.
JB
flow
Branch when the addressed bit is one, leaving it unchanged.
Operand form
Opcode(s), hex
Bytes
MC
Flags
JB bit,rel
20
3
2
—
jb 0x90,high
Watch: A port test reads the pin; bit address 90 is P1.0, not the full P1 byte.
JBC
flow
If the addressed bit is one, clear it and branch. If zero, neither branch nor clear has an effect.
Operand form
Opcode(s), hex
Bytes
MC
Flags
JBC bit,rel
10
3
2
—
jbc 0x00,event
Watch: Useful for consuming a one-bit event flag. A port operation of this kind uses the latch.
JC
flow
Branch when CY is one.
Operand form
Opcode(s), hex
Bytes
MC
Flags
JC rel
40
2
2
—
jc carry_out
Watch: After SUBB a set CY means borrow; after CJNE it means unsigned less-than.
JMP
flow
Set PC to DPTR plus unsigned A. This is the one hardware computed JMP form.
Operand form
Opcode(s), hex
Bytes
MC
Flags
JMP @A+DPTR
73
1
2
—
jmp @a+dptr
Watch: A generic JMP label accepted by some assemblers is a pseudo-instruction, not an additional CPU opcode. A jump table must contain executable jump entries or code at the computed address.
JNB
flow
Branch when the addressed bit is zero without modifying it.
Operand form
Opcode(s), hex
Bytes
MC
Flags
JNB bit,rel
30
3
2
—
jnb 0x99,wait_tx
Watch: TI is bit address 99; direct byte address 99 is the different SBUF register.
JNC
flow
Branch when CY is zero.
Operand form
Opcode(s), hex
Bytes
MC
Flags
JNC rel
50
2
2
—
jnc no_borrow
Watch: No-borrow is unsigned greater-or-equal after a properly initialized SUBB.
JNZ
flow
Branch when A is not zero; no separate zero flag is consulted.
Operand form
Opcode(s), hex
Bytes
MC
Flags
JNZ rel
70
2
2
—
jnz nonzero
Watch: An INC R7 followed by JNZ still tests A, not R7.
JZ
flow
Branch when A is zero.
Operand form
Opcode(s), hex
Bytes
MC
Flags
JZ rel
60
2
2
—
jz empty
Watch: The branch neither clears A nor modifies carry.
LCALL
flow
Push the return PC and call any address in the 64-KB CODE space.
Operand form
Opcode(s), hex
Bytes
MC
Flags
LCALL addr16
12
3
2
—
lcall worker
Watch: The stack is internal RAM. Budget two return-address bytes plus software saves and nesting.
LJMP
flow
Replace PC with a full 16-bit CODE address.
Operand form
Opcode(s), hex
Bytes
MC
Flags
LJMP addr16
02
3
2
—
ljmp destination
Watch: No bank switching or external memory decoding is created by the instruction.
MOV
data
Copy a byte, bit or 16-bit immediate pointer. Destination comes first. Source is unchanged; MOV does not implicitly go through A.
Operand form
Opcode(s), hex
Bytes
MC
Flags
MOV A,#imm
74
2
1
—
MOV dir,#imm
75
3
2
—
MOV @Ri,#imm
76, 77
2
1
—
MOV Rn,#imm
78, 79, 7A, 7B, 7C, 7D, 7E, 7F
2
1
—
MOV dir,dir
85
3
2
—
MOV dir,@Ri
86, 87
2
2
—
MOV dir,Rn
88, 89, 8A, 8B, 8C, 8D, 8E, 8F
2
2
—
MOV DPTR,#imm16
90
3
2
—
MOV bit,C
92
2
2
—
MOV C,bit
A2
2
1
CY
MOV @Ri,dir
A6, A7
2
2
—
MOV Rn,dir
A8, A9, AA, AB, AC, AD, AE, AF
2
2
—
MOV A,dir
E5
2
1
—
MOV A,@Ri
E6, E7
1
1
—
MOV A,Rn
E8, E9, EA, EB, EC, ED, EE, EF
1
1
—
MOV dir,A
F5
2
1
—
MOV @Ri,A
F6, F7
1
1
—
MOV Rn,A
F8, F9, FA, FB, FC, FD, FE, FF
1
1
—
mov 0x30,#0x2A
Watch: MOV direct,direct encodes the SOURCE address before the destination, even though assembly syntax writes destination first. MOV C,bit and MOV bit,C explicitly read/write carry; other forms do not set arithmetic flags unless targeting PSW.
MOVC
data
Read a CODE byte at A+DPTR or A+PC into A. The PC-relative base is the address after the one-byte opcode. The index addition wraps in 16 bits.
Operand form
Opcode(s), hex
Bytes
MC
Flags
MOVC A,@A+PC
83
1
2
—
MOVC A,@A+DPTR
93
1
2
—
movc a,@a+dptr
Watch: A is both the input index and the returned byte. DPTR is not incremented.
MOVX
data
Transfer between A and external DATA using @DPTR or @R0/@R1 addressing. Does not access SFRs or internal RAM.
Operand form
Opcode(s), hex
Bytes
MC
Flags
MOVX A,@DPTR
E0
1
2
—
MOVX A,@Ri
E2, E3
1
2
—
MOVX @DPTR,A
F0
1
2
—
MOVX @Ri,A
F2, F3
1
2
—
movx @dptr,a
Watch: Only A transfers data. @Ri provides an 8-bit low address; P2 supplies the board's high-address context on classic hardware.
MUL
arithmetic
Unsigned multiply A by B; the low product byte is A and the high byte B. CY is cleared; OV is one if B's result is nonzero.
Operand form
Opcode(s), hex
Bytes
MC
Flags
MUL AB
A4
1
4
CY=0 OV
mul ab
Watch: B:A is a 16-bit result; neither operand is signed automatically.
NOP
flow
Consume one machine cycle without changing program-visible state apart from PC and normal peripheral progress.
Operand form
Opcode(s), hex
Bytes
MC
Flags
NOP
00
1
1
—
nop
Watch: This is a timing instruction, not a sleep mode; timers and interrupts still matter.
ORL
logic
Bitwise OR for byte forms; Boolean forms compute C OR bit or C OR NOT bit.
Operand form
Opcode(s), hex
Bytes
MC
Flags
ORL dir,A
42
2
1
—
ORL dir,#imm
43
3
2
—
ORL A,#imm
44
2
1
—
ORL A,dir
45
2
1
—
ORL A,@Ri
46, 47
1
1
—
ORL A,Rn
48, 49, 4A, 4B, 4C, 4D, 4E, 4F
1
1
—
ORL C,bit
72
2
2
CY
ORL C,/bit
A0
2
2
CY
orl 0x90,#1
Watch: Only the Boolean forms inherently change CY. A byte destination that is PSW can of course change flags.
POP
data
Read the byte at SP into the direct destination, then decrement SP.
Operand form
Opcode(s), hex
Bytes
MC
Flags
POP dir
D0
2
2
—
pop 0xE0
Watch: Pop in reverse order of pushes. No POP Rn hardware form exists; use a direct byte address with the intended register bank.
PUSH
data
Increment SP, then copy the direct source byte onto the internal-RAM stack.
Operand form
Opcode(s), hex
Bytes
MC
Flags
PUSH dir
C0
2
2
—
push 0xE0
Watch: No PUSH #immediate instruction. Stack overflow above 7F is not valid extra RAM on this 80C31.
RET
flow
Pop the two-byte return PC for a subroutine.
Operand form
Opcode(s), hex
Bytes
MC
Flags
RET
22
1
2
—
ret
Watch: Does not restore scratch registers or release interrupt priority in-service state.
RETI
flow
Restore the return PC and notify the interrupt controller that the interrupt service has finished.
Operand form
Opcode(s), hex
Bytes
MC
Flags
RETI
32
1
2
—
reti
Watch: Save/restore the working state yourself. Use for an ISR, not as an interchangeable spelling of RET.
RL
logic
Rotate A left: old bit 7 becomes bit 0. Carry is unchanged.
Operand form
Opcode(s), hex
Bytes
MC
Flags
RL A
23
1
1
—
rl a
Watch: A circular rotate is not a zero-filling shift.
RLC
logic
Rotate CY:A left through carry: old A.7 becomes CY and old CY becomes A.0.
Operand form
Opcode(s), hex
Bytes
MC
Flags
RLC A
33
1
1
CY
rlc a
Watch: Clear carry first for the low byte of a logical left shift.
RR
logic
Rotate A right: old bit 0 becomes bit 7. Carry is unchanged.
Operand form
Opcode(s), hex
Bytes
MC
Flags
RR A
03
1
1
—
rr a
Watch: Not an arithmetic right shift.
RRC
logic
Rotate CY:A right through carry: old A.0 becomes CY and old CY becomes A.7.
Operand form
Opcode(s), hex
Bytes
MC
Flags
RRC A
13
1
1
CY
rrc a
Watch: Clear carry for a zero-filled shift; for signed arithmetic shifting supply the original sign bit as carry.
SETB
bits
Set carry or a directly addressed bit to one.
Operand form
Opcode(s), hex
Bytes
MC
Flags
SETB bit
D2
2
1
—
SETB C
D3
1
1
CY
setb 0x97
Watch: Setting a quasi-bidirectional port latch releases it for input; here bit 97 is P1.7.
SJMP
flow
Jump by a signed 8-bit displacement relative to PC after the two-byte instruction.
Operand form
Opcode(s), hex
Bytes
MC
Flags
SJMP rel
80
2
2
—
sjmp loop
Watch: Range -128..+127 bytes, not instructions. SJMP to itself is an infinite loop, not a return.
SUBB
arithmetic
A = A - source - incoming CY. Sets CY for unsigned borrow and updates AC and OV.
Operand form
Opcode(s), hex
Bytes
MC
Flags
SUBB A,#imm
94
2
1
CY AC OV
SUBB A,dir
95
2
1
CY AC OV
SUBB A,@Ri
96, 97
1
1
CY AC OV
SUBB A,Rn
98, 99, 9A, 9B, 9C, 9D, 9E, 9F
1
1
CY AC OV
subb a,#1
Watch: Clear carry for ordinary subtraction; keep the borrow chain for multi-byte subtraction.
SWAP
logic
Exchange the high and low nibbles of A. Arithmetic flags remain unchanged.
Operand form
Opcode(s), hex
Bytes
MC
Flags
SWAP A
C4
1
1
—
swap a
Watch: AB becomes BA in hexadecimal; this is not byte-order swapping of a 16-bit value.
XCH
data
Exchange A with an Rn register, direct byte or internal RAM through @Ri.
Operand form
Opcode(s), hex
Bytes
MC
Flags
XCH A,dir
C5
2
1
—
XCH A,@Ri
C6, C7
1
1
—
XCH A,Rn
C8, C9, CA, CB, CC, CD, CE, CF
1
1
—
xch a,r3
Watch: There is no XCH between two arbitrary direct bytes or with XDATA.
XCHD
data
Exchange the low four bits of A with the low four bits of internal RAM at @R0/@R1. High nibbles are unchanged.
Operand form
Opcode(s), hex
Bytes
MC
Flags
XCHD A,@Ri
D6, D7
1
1
—
xchd a,@r0
Watch: Only internal indirect RAM addressing is legal; it is not an external nibble-memory operation.
XRL
logic
Bitwise exclusive-OR using A or a direct byte destination. There is no Boolean XRL C,bit form.
Operand form
Opcode(s), hex
Bytes
MC
Flags
XRL dir,A
62
2
1
—
XRL dir,#imm
63
3
2
—
XRL A,#imm
64
2
1
—
XRL A,dir
65
2
1
—
XRL A,@Ri
66, 67
1
1
—
XRL A,Rn
68, 69, 6A, 6B, 6C, 6D, 6E, 6F
1
1
—
xrl a,#0xFF
Watch: For Boolean XOR implement the operation with available carry/bit instructions instead.
FIELD NOTES / CODE51.TXT
04 / Practical assembly patterns
BIT TESTS AND COUNTED LOOPS
mov r7,#10
again:
; body executes ten times
djnz r7,again
ret
Starting R7 at zero executes 256 iterations, not zero. DJNZ changes no arithmetic flags. Conditional branches and SJMP have a signed -128..+127 byte displacement measured from the instruction AFTER the branch. For a far condition, invert the test and use LJMP. AJMP/ACALL require the target to share the 2-KB page of the NEXT instruction, so code near a page boundary needs special care.
UNSIGNED COMPARE
cjne a,#10,different
; A == 10; CY is now 0
sjmp done
different:
jc below ; unsigned A < 10
; otherwise A > 10
below:
done:
ret
PACKED BCD
mov a,#0x45
add a,#0x38
da a ; A = 83 (hex encoding of decimal 83)
ret
DA is for the result of packed-BCD ADD/ADDC with valid BCD operands. It is not binary-to-decimal conversion and is not the correction for subtraction. DA may set CY; it does not clear an existing carry. AC/OV are not a decimal result test.
LOOKUP TABLE (DOS/51 PROGRAM)
mov dptr,#table
mov a,#2
movc a,@a+dptr ; A = ASCII '2'
ret
table:
db 48,49,50,51
CODE and XDATA overlap physically only where a BOARD wires them that way. DOS/51's upper RAM is deliberately visible in both spaces, allowing its assembler to write instructions with MOVX and then execute them. A generic 80C31 does not create executable RAM by itself.
MULTIPLY / DIVIDE
mov a,#200
mov 0xF0,#3 ; B
mul ab ; B:A = 0x0258 (600), CY=0, OV=1
mov a,#200
mov 0xF0,#7
div ab ; A=28 quotient, B=4 remainder, CY=0
ret
DIV by zero sets OV and leaves A/B results undefined. Check B before dividing. MUL and DIV are unsigned; signed multiplication/division need additional sign handling.
ROTATE / SHIFT
RL/RR rotate within A and leave CY alone. RLC/RRC rotate the nine-bit CY:A chain. For an unsigned right shift use CLR C followed by RRC A. A multi-byte right shift starts at the high byte; a left shift starts at the low byte. SWAP A exchanges nibbles; XCH exchanges whole bytes; XCHD exchanges only the low nibbles of A and an indirectly addressed RAM byte.
MASKS
anl 0x90,#0xFE ; clear P1.0, preserve other latches
orl 0x90,#0x01 ; set P1.0, preserve other latches
cpl 0x90 ; BIT address 90: toggle P1.0
These examples use numeric SFR/bit addresses for DOS/51 portability. In a desktop assembler, prefer named EQU/bit declarations from the exact chip header. Do not assume another assembler's names, macros or directives exist in the small on-board assembler.
SUBROUTINE CONTRACTS
Document inputs, outputs, scratch registers and flags. Save only what your calling convention requires, but do not silently change the register bank. RET restores PC, not A, B, PSW or DPTR. Interrupt entry likewise does not preserve your working registers. For a callable DOS/51 program, keep the shell's stack valid and restore any state you borrow. An endless loop, disabled interrupts or a changed UART can make the shell appear dead; Reset recovers execution but does not undo files overwritten on the RAM disk.
EXACT DEVICE, NOT AN IMAGINARY SUPER-8051
The shop part is Philips P80C31SFPN in PDIP-40. The Philips ordering table identifies S as the 16 MHz grade, F as -40 to +85 C and PN as plastic DIP. It is ROMless: an external program-memory circuit is required. Its 128 internal RAM bytes are not the DOS/51 board's external SRAM. No ADC, DAC, native SPI, I2C, USB, internal program flash, PCA or watchdog is supplied by the basic part.
IMPORTANT DATASHEET REVISION DISTINCTION
The classic 8031/80C31 baseline has Timer 0, Timer 1, five interrupt sources and one DPTR. Philips' 7 August 2000 80C31/80C32 specification explicitly lists additional functions even in its Table 1 headed 8XC51/80C31: Timer 2, dual DPTR, enhanced UART and four-level interrupt priority. Its prose and tables must not be replaced by a generic 8051 feature list. This guide covers that documented Philips register set separately below. The current emulator implements the classic baseline, NOT those extensions. No silicon-revision or bench test of the stocked chips has been performed here; consult the exact production revision before depending on an extension. The shared document explicitly excludes WUPD from the 80C31.
T2/T2EX labels refer to the documented Philips extension, not the emulator. INT0/INT1, WR and RD are active-low alternate signals. RST is active HIGH. /EA is the external-access PIN, not the interrupt-enable bit called EA. Tie /EA low for this ROMless CPU. Hold RST high for at least two machine cycles after the oscillator is running; allow oscillator startup at power-on.
EXTERNAL BUS
P0 multiplexes A0-A7 and D0-D7. ALE allows an external latch to retain the low address; P2 supplies A8-A15. /PSEN reads program memory, /RD and /WR access external data. A fetch begins with an address on the bus, ALE latches its low byte, then P0 changes to data and the selected read strobe enables memory output. Address decoding decides which physical chip responds. Use correctly timed glue logic and avoid two devices driving P0 together.
PORTS
P0 is open-drain as ordinary GPIO and needs external pull-ups for a high output; its bus function has active pull-ups. P1/P2/P3 have quasi-bidirectional pull-ups: write a one to release a pin for input, then read it. Writing zero drives low. There is no separate DDR register. The advertised 32 port lines are not 32 spare GPIOs on a ROMless system: external code uses P0/P2 continuously, and UART, interrupts, timers and memory strobes consume P3 pins.
CLOCK / POWER
The oscillator amplifier uses XTAL1/XTAL2 with a crystal circuit; for an external clock drive XTAL1 and leave XTAL2 open as specified. The part is static and supports a stopped clock. IDL=PCON.0 stops the CPU but keeps relevant peripherals running; an enabled interrupt or reset leaves idle. PD=PCON.1 enters power-down, stopping the oscillator while retaining RAM under the specified supply conditions. Do not assume an external interrupt wakes an 80C31: the shared datasheet's WUPD feature is explicitly unavailable on it. Reset is the conservative recovery path. GPIO/reset behavior and supply limits must be checked for the chosen mode.
The ordering table lists 2.7-5.5 V and up to 16 MHz for this S grade; use the electrical and AC tables for the actual supply, clock and loads. DOS/51 describes a 5 V board. An SD card needs 3.3 V power and suitable signal-level conversion; it is not a 5 V peripheral. Use a TTL-compatible serial adapter or a proper RS-232 transceiver, never raw RS-232 voltage on RXD.
BASELINE SFR DESK REFERENCE (BYTE ADDRESSES)
80 P0 81 SP 82 DPL 83 DPH 87 PCON
88 TCON 89 TMOD 8A TL0 8B TL1 8C TH0 8D TH1
90 P1 98 SCON 99 SBUF A0 P2 A8 IE B0 P3
B8 IP D0 PSW E0 ACC F0 B
Bit-addressable: P0, TCON, P1, SCON, P2, IE, P3, IP, PSW, ACC, B. SP resets to 07 and port latches to FF. Reset is not an instruction to clear all RAM.
TIMER 0 AND TIMER 1
TMOD has one four-bit field per timer: Timer 1 in bits 7-4, Timer 0 in bits 3-0. Each field is GATE, C/T, M1, M0. GATE=1 requires the corresponding INT pin high AND TRn set. C/T=0 counts internal machine cycles; C/T=1 counts sampled falling edges on T0/T1. Mode 0 is a 13-bit counter (THn plus the low five bits of TLn); mode 1 is 16-bit; mode 2 is 8-bit auto-reload from THn into TLn. Mode 3 splits Timer 0 into two 8-bit timers: TL0 keeps Timer 0 controls, TH0 borrows TR1/TF1. Timer 1's normal interrupt/control use is consequently restricted; do not treat mode 3 as two independent ordinary 16-bit timers. Timer 1 mode 3 stops its counting.
TCON bits: TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0. TRn starts a timer; TFn records overflow. Timer overflow flags are cleared on hardware interrupt vectoring; polling code must clear them itself. Stop the timer before making a coherent multi-byte configuration change.
At 11.0592 MHz, 922 ticks are approximately 1 ms. Polling/setup overhead makes this NOT an exact 1-ms software delay. For periodic precision use a suitable reload strategy and account for interrupt latency. External event counting is sampled, so input edge rate and pulse widths must meet the chip timing requirements.
UART: SCON = SM0 SM1 SM2 REN TB8 RB8 TI RI
Mode 0: synchronous shift register, normally fosc/12.
Mode 1: 8-bit UART with start/stop bits, variable baud.
Mode 2: 9-bit UART, fixed fosc/64 or fosc/32 via SMOD.
Mode 3: 9-bit UART, variable baud.
REN enables reception. Write SBUF to transmit; read SBUF to retrieve the separately buffered received byte. RI and TI share one interrupt vector and are cleared by SOFTWARE, not by reading SBUF or entering the ISR. Mode 1's RB8 reflects the received stop bit when appropriate; modes 2/3 use TB8/RB8 for the ninth data bit and SM2 for multiprocessor filtering.
With Timer 1 mode 2, UART modes 1/3 use:
baud = (2^SMOD * fosc) / (384 * (256 - TH1)).
At 11.0592 MHz: TH1=FD and SMOD=0 gives 9600; TH1=FD and SMOD=1 gives 19200; TH1=FF and SMOD=1 gives 57600. Arbitrary baud rates are not all representable.
Do not run that setup inside DOS/51: its console is already 57600 baud, and Timer 1 belongs to it. To poll reception, wait for RI, read SBUF and clear RI. A polled transmitter writes SBUF, waits for TI and clears TI; ensure TI was not left set by an earlier transmission. At higher throughput handle overrun in software: there is no general-purpose deep hardware FIFO.
INTERRUPTS
Reset jumps to 0000. Baseline vectors: INT0=0003, Timer0=000B, INT1=0013, Timer1=001B, serial=0023. Each vector is normally a short LJMP stub because adjacent entries are only eight bytes apart. IE.7 (EA) globally enables requests; IE bits 0-4 enable EX0, ET0, EX1, ET1 and ES. IP bits select low/high priority in the baseline core. A high-priority handler can preempt a low-priority handler, not vice versa; equal-priority service is not reentrant. Fixed polling order resolves simultaneous equal-priority requests in the vector order above.
IT0/IT1=1 selects falling-edge external interrupts; zero selects active-low level requests. Release a level-triggered request before returning or it can immediately retrigger. Switch bounce requires debouncing. Hardware pushes PC only. Save every clobbered register including PSW as necessary; restore in reverse order and finish with RETI so the interrupt controller releases its in-service state. RET alone is not an interrupt return. Serial ISRs test BOTH RI and TI. A register-bank switch can reduce saves, but each nested priority level needs a nonconflicting bank and RAM allocation.
On DOS/51 the lower CODE vectors are in ROM. An application at 8000 cannot patch them just by writing that numerical XDATA address. Use documented firmware facilities or a different ROM build rather than assuming vector ownership.
SCOPE
This chapter follows Philips' 2000-08-07 80C31/80C32 specification, including its Table 1 explicitly headed 8XC51/80C31. These functions are not part of the standard instruction set and are not implemented by the present DOS/51 emulator. The shop's actual silicon revision has not been bench-identified; the shared datasheet must be reconciled with the particular chip before writing extension-dependent production code. Core instructions do not change: SFR configuration exposes the extra hardware.
TIMER 2
Documented registers: T2CON=C8, T2MOD=C9, RCAP2L=CA, RCAP2H=CB, TL2=CC, TH2=CD. T2CON is bit-addressable; T2MOD is not. Timer 2 adds 16-bit capture, auto-reload up/down counting and an independent UART baud source. P1.0 is T2/clock-out and P1.1 is T2EX.
T2CON bits: TF2 EXF2 RCLK TCLK EXEN2 TR2 C/T2 CP/RL2.
TR2 starts the counter; C/T2 chooses internal timer or external falling-edge count. CP/RL2=1 selects capture; zero selects reload when not overridden by the UART baud configuration. EXEN2 enables external capture/reload events. Software must clear TF2 and EXF2 in the shared Timer 2 handler; unlike Timer 0/1 overflow flags they are not automatically cleared on vector entry.
Capture copies TL2/TH2 into RCAP2L/H on a qualifying T2EX falling edge without stopping the running counter. Auto-reload reloads the capture/reload value on overflow, and optionally on a T2EX edge. T2MOD.DCEN enables up/down selection by T2EX; EXF2 behavior then changes and is not the same interrupt source as ordinary capture. Consult the mode diagram rather than reusing a capture ISR unaltered.
Setting TCLK and/or RCLK routes Timer 2 to transmit/receive baud respectively; the other direction can remain on Timer 1. Internally clocked UART baud is fosc / [32 * (65536 - RCAP2)]. At 11.0592 MHz, RCAP2=FFDC gives 9600 baud. In this baud mode the internal counter advances at fosc/2, not fosc/12, and rollover does not set TF2. Stop TR2 before changing reload/counter configuration.
For clock output on P1.0, clear C/T2, set T2MOD.T2OE and TR2. Frequency = fosc / [4 * (65536 - RCAP2)]. The pin is no longer ordinary GPIO. Clock-out and baud generation do not provide an ordinary overflow interrupt in the same way as timer mode. These formulas do not describe the emulator's bit-banged SD clock.
DUAL DATA POINTER
AUXR1 at A2, bit 0 (DPS), selects DPTR0 or DPTR1. The selected pointer appears through DPL/DPH and is used by MOV DPTR,#imm16, INC DPTR, MOVC A,@A+DPTR, MOVX through DPTR and JMP @A+DPTR. Save selection as well as pointer values across calls/interrupts. A2 is NOT a bit-addressable SFR; do not write SETB 0xA2 expecting to select DPTR1 (that bit address belongs to P2). Mask the byte with ORL 0xA2,#1 / ANL 0xA2,#0xFE. Reserve undefined bits. Two pointers let a copy loop alternate source/destination addresses without reloading both address bytes every iteration.
FOUR INTERRUPT PRIORITIES
The documented IPH register at B7 supplies the high priority bit; IP at B8 supplies the low bit. IPH:IP combinations 00,01,10,11 select levels 0-3 for each source. IPH is not bit-addressable. Timer 2 adds ET2 at IE.5, PT2 at IP.5 and PT2H at IPH.5, with vector 002B. The serial source still shares RI/TI. Preserve unused bits and arrange a real stack/register-save strategy for nested handlers.
ENHANCED UART
SADDR=A9 holds a slave address and SADEN=B9 masks it for automatic address recognition. These byte SFRs are not bit-addressable. SM2 and the ninth-bit multiprocessor protocol allow address filtering rather than interrupting for every data byte. Review given/broadcast-address matching in the Philips diagrams before choosing a mask.
PCON.6 (SMOD0) chooses whether SCON.7 is the mode bit SM0 or framing-error status FE. A missing stop bit sets FE when this feature is selected; software clears it. Configure UART mode with the correct SMOD0 setting first, so a status bit is not mistaken for the mode-selection bit. PCON.7 remains baud doubler SMOD1. This is not a new CPU opcode or a second UART.
POWER / ALE
AUXR=8E, bit 0 (AO), controls the reduced-EMI ALE feature described by Philips. It is not bit-addressable. A ROMless board relies on the external fetch bus, so do not casually suppress the address-latch mechanism. Verify the documented behavior for external program/data cycles and the actual board's latch.
PCON also documents a power-off flag POF and software flags GF1/GF0; reset source affects reset values. The shared datasheet describes asynchronous port reset and power-down behavior. Its AUXR1.WUPD external-interrupt wake option is specifically marked unavailable on 80C31; do not promise 80C32 wake behavior for this stocked part. Full electrical limits, reset timing and mode transitions belong to the original manufacturer specification linked in the web edition.
WHAT THE EMULATOR ACTUALLY MODELS
Classic 80C31 CPU, 128-byte internal RAM, Timer 0/1, baseline interrupt controller, UART and power-control states. The board adds an external 32-KB ROM and two 32-KB SRAMs, then implements SD-card SPI in software. Timer 2, dual DPTR, enhanced address-recognition UART, IPH and analog electrical behavior are not modeled. Reading the Philips extensions chapter is not proof that those features run in this emulator.
BOARD MAP
CODE 0000-7FFF : DOS/51 ROM
CODE 8000-FFFF : upper SRAM (executable)
XDATA 0000-7FFF : lower SRAM (data)
XDATA 8000-FFFF : same upper SRAM as CODE
The RAM disk uses XDATA 2000-7FFF (24 KB). Application/assembler/editor storage uses the upper SRAM. Firmware state also occupies internal/external RAM: those bytes are not all free application scratch space.
SD CARD / FIELD MANUAL
A first-use browser card includes this ASCII field manual and DropBlocks. Existing browser cards are not replaced. Use the Install field manual button to add missing manual files to a FAT16 card; existing files with the same names are left unchanged. FAT32 cards can be inserted for firmware access, but the browser-side installer is FAT16-only.
Remove an imported chapter from the RAM disk when done; the SD copy remains. The full manual is split into small files because the RAM disk is only 24 KB and may already contain your work. If sdget reports insufficient space, save/delete unneeded RAM files first; do not blank the card to solve a RAM-disk capacity problem. Use the online edition for search and the complete opcode table. Download card saves the actual FAT16 image with the text files in its root.
BOARD GPIO
P1.0-P1.3 are the example LED lines (active low). P1.4=/CS, P1.5=SCK, P1.6=MOSI, P1.7=MISO for software SPI. Leave the MISO latch high for input. Use bit instructions on LED bits; do not replace all of P1 with an arbitrary byte while the SD card is attached. P0/P2 serve the external memory bus; P3.0/1 serve the serial console; P3.6/7 serve external RAM strobes.
The SPI protocol and FAT filesystem are firmware/board features, not CPU instructions or on-chip 80C31 peripherals. A real SD card needs a 3.3-V supply and suitable logic translation. The browser models digital transactions, not regulator current, wire capacitance, analogue signal edges or component damage.
GETTING UNSTUCK
- No console after a test: did you change Timer 1, SCON, PCON, SP or IE? Reset restarts the ROM while preserving SRAM. Power cycle clears RAM and RAM-disk files; the SD card has separate persistence.
- A conditional branch behaves strangely: check A rather than an imaginary zero flag; check signed versus unsigned comparisons and whether an instruction changed CY.
- Wrong RAM byte: distinguish #literal from direct address and MOV from MOVX/MOVC. Check the selected register bank.
- A bit write hits the wrong peripheral: byte and bit addresses are different namespaces. Only selected SFR bytes are bit-addressable.
- Table lookup reads nonsense: initialize A; MOVC adds it to DPTR or post-opcode PC. It does not increment DPTR afterwards.
- RET jumps into nowhere: inspect unmatched PUSH/POP, stack overflow above 7F, corrupted return addresses or an unintended jump into a subroutine.
- A port will not go high: an output latch zero drives low; release it with one for input. P0 GPIO needs pull-ups. On the actual ROMless board many pins are already the memory bus.
- Timer counts at the wrong rate: classic timer mode is oscillator/12; UART Timer 2 baud mode is a distinct Philips extension. A cycle table is not an oscillator-clock table.
- New SD files are missing: an old saved card is intentionally preserved. Click Install field manual; then sdls. Do not erase a visitor's saved work just to seed a new example.
NEXT EXPERIMENTS
1. Change the printed character in first.asm and predict the ASCII code.
2. Add 0x7F+1 and 0xFF+1; inspect results and PSW with the debugger/memory facilities available to your setup.
3. Build a lookup table and prove the difference between MOVC and MOVX.
4. Toggle only P1.0 while preserving all SD lines.
5. On a separate bare-metal ROM, build a Timer 0 ISR with an explicit register-save contract; keep DOS/51's ROM untouched.
High nibble down the left, low nibble across the top. Scroll the map horizontally on a small screen; select a cell for the instruction explanation. A5 is deliberately reserved.
Written for the Philips P80C31SFPN stocked at iMania, with standard instruction semantics checked against Intel’s original instruction descriptions. This is an independently written teaching guide, not a reproduction of a manufacturer manual. Hardware extensions are attributed to the dated Philips specification and separated from emulator support; no physical chip bench-validation is claimed.
Edition 2026-11-14. Classic CPU timings are hardware reference values; browser speed and modeled peripheral coverage are separate matters. The Philips shared-family datasheet contains revision-sensitive extensions: see chapter 07 instead of assuming every generic 8051, emulator or old 80C31 has them.