Skip to content

Chip-8 on the COSMAC VIP: Arithmetic and Logic Instructions

This is part of a series of posts analysing the Chip-8 interpreter on the RCA COSMAC VIP computer. These posts may be useful if you are building a Chip-8 interpreter on another platform or if you have an interest in the operation of the COSMAC VIP. For other posts in the series refer to the index or instruction index.

INSTRUCTION GROUP: 7XNN
Add NN to VX

INSTRUCTION GROUP: 8XY1
OR VY with VX

INSTRUCTION GROUP: 8XY2
AND VY with VX

INSTRUCTION GROUP: 8XY3
XOR VY with VX

INSTRUCTION GROUP: 8XY4
Add VY to VX, load VF with carry bit

INSTRUCTION GROUP: 8XY5
Subtract VY from VX, load VF with carry bit

INSTRUCTION GROUP: 8XY6
Shift VY one bit right, load VX with result, load VF with bit 0

INSTRUCTION GROUP: 8XY7
Subtract VX from VY, load VX with result

INSTRUCTION GROUP: 8XYE
Shift VY one bit left, load VX with result, load VF with bit 7

The first of these instruction groups, 7XNN, adds the immediate operand NN to VX. It’s simple enough to not warrant a flowchart, so here’s the code:

Labels
Address (hex)

Assembly
Code (hex)

Comments

7XNN:
01B7

45
LDA 5

Get the value in the second byte of the instruction into the accumulator (D) and then advance the Chip-8 programme counter to the next instruction.

01B8

E6
SEX 6

Set the VX pointer to be used for register indirect addressing instructions.

01B9

F4
ADD

Add the value in VX to the accumulator (D), which currently holds the immediate operand from the second byte of the Chip-8 instruction

01BA

56
STR 6

Store the result back in VX.

01BB

D4
SEP 4

Return to the fetch and decode routine

An important thing to note about this routine is that any carry is ignored. If the result is more than 0xFF then only the least significant byte will be stored. For example, if V0 is 0xF1 and the instruction is 7010 then adding 0x10 to 0xF1 should give 0x101. However, since only the least significant byte will be preserved, VX will contain 0x01 after this operation. Because the carry bit is not preserved, subsequent arithmetic operations on the value in VX would be inaccurate. If you need addition that preserves the carry bit, use instruction group 8XY4 instead (see below).

This instruction requires 10 machine cycles (45.4 microseconds).

The instruction groups that begin 8X?? all work in the same way, using a single routine. They make use of the fact that there is a series of 1802 instructions (0xF1 to 0xF7 and 0xF9 to 0xFF) that perform arithmetic and logic instructions. Instructions 0xF9, 0xFA, 0xFB, 0xFC, 0XFD and 0xFF all use immediate operands, so they cannot be used. The remaining instructions 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7 and 0xFE all operate on either the accumulator alone or the accumulator and whatever memory location is currently pointed to by the register indicated by X, so these instructions can be made use of by the Chip-8 interpreter. The flowchart shows how the routine works:

A flowchart describing the function of Chip-8 8X?? instructions.
A flowchart describing the function of Chip-8 8X?? instructions

Here’s the code:

Labels
Address (hex)

Code (hex)
Assembly

Comments

8XYN:
01BC

AF
LDA 5

Get the value in the second byte of the instruction into the accumulator (D) and then advance the Chip-8 programme counter to the next instruction.

01BD

FA 0F
ANI 0x0F

Mask the byte to save just the second hex digit.

01BF

3A C4
BNZ DECODE_ AL_ INSTR

If the second digit is not zero, it’s an arithmetic and logic instruction, so branch to the decode routine for these.

01C1 – 01C3

There’s a short routine here to copy VY into VX. This was analysed in a previous post.

DECODE_ AL_ INSTR:
01C4

AF
PLO F

Temporarily save the last digit of the instruction in RF.0.

01C5

22
DEC 2

Decrement the stack pointer, ready for a push operation.

01C6

F8 D3
LDI 0xD3

Load a 0xD3 1802 instruction (SEP 3) into the accumulator.

01C8

73
STXD

Push this onto the stack and decrement the stack pointer.

01C9

8F
GLO F

Restore the last digit of the Chip-8 instruction to the accumulator.

01CA

F9 F0
ORI 0xF0

OR this with 0xF0 to create a 1802 instruction of the form 0xFN, where N is the last hex digit of the Chip-8 instruction.

01CC

52
STR 2

Push this onto the stack.

01CD

E6
SEX 6

Set the VX pointer to be used for register indirect addressing memory instructions.

01CE

07
LDN 7

Load the value in VY into the accumulator.

01CF

D2
SEP 2

Execute the two instructions at the top of the stack (the first of these will be the AL instruction, the second will be a SEP 3 instruction to return control to this routine at the instruction following this one.

01D0

56
STR 6

Save the result of the operation in VX.

01D1

F8 FF
LDI 0xFF

0xFF is the low-order byte of the address of Chip-8 variable VF.

01D3

A6
PLO 6

The VX pointer now points to VF.

01D4

F8 00
LDI 0x00

Clear the accumulator.

01D6

7E
SHLC

Move the carry flag into the least significant bit of the accumulator.

01D7

56
STR 6

Save this in VF.

01D8

D4
SEP 4

Return to the fetch and decode routine.

There are a few interesting things to note about this routine. Firstly it cleverly uses the stack as a temporary scratch pad for a dynamically created, two-instruction routine that performs the actual arithmetic or logic instruction. You might have noticed that although two instructions are pushed onto the stack at the start of the routine, they are never popped off again. This is because, when the routine is run, R2 (the stack pointer) is incremented after each instruction is executed. So when control is returned to the Chip-8 interpreter routine, R2 has been restored to its original position.

Note that all these instructions overwrite variable VF. This is used to show the status of the carry bit, which is copied into the least significant bit of VF. All other bits of VF will be set to 0. This means, if you use VF as the VX argument, the result will be overwritten by the flag status.

Only the first five of these instructions were officially documented by RCA. 8XY6, 8XY7 and 8XYE are all undocumented. Unfortunately an erroneous assumption about 8XY6 and 8XYE, the two shift instructions, seems to have crept into Chip-8 lore at some point. Most recent documentation on Chip-8 suggests these instructions shift VX rather than VY. Some documents suggest that the correct format for them is 8X06 and 8X0E. However, when these instructions are run on the original Chip-8 interpreter, they will shift VY not VX! An instruction of the form 8X06 or 8X0E will not work as expected. The programmer would be expecting this to shift VX and store the result back in VX. What would actually happen is that V0 would be shifted and the result stored back in VX. This leaves the programmer of a contemporary interpreter with a bit of a dilemma. Do you make these instructions function as they would have done in the original interpreter or do you make them function in accordance with the erroneous assumption? Or do you offer both modes and a way to switch between them? This post documents the way the instructions worked in the original Chip-8 interpreter.

All these instructions require 44 machine cycles (199.76 microseconds) to execute.

The table below shows the register state for VX, VY and VF for each of these instructions.

Instruction

VX (entry)

VY (entry)

VX (exit)

VY (exit)

VF (exit)

8XY1

VX argument

VY argument

Bitwise OR of VX with VY

Unchanged

Undefined

8XY2

VX argument

VY argument

Bitwise AND of VX with VY

Unchanged

Undefined

8XY3

VX argument

VY argument

Bitwise XOR of VX with VY

Unchanged

Undefined

8XY4

VX argument

VY argument

VX added to VY

Unchanged

0x00 if no carry occurred, 0x01 if a carry occurred

8XY5

VX argument

VY argument

VY subtracted from VX

Unchanged

0x00 if a borrow occurred, 0x01 if no borrow occurred

8XY6

VX argument (initial value is irrelevant)

VY argument

VY shifted right by one bit. Most significant bit of VX will be set to 0

VY shifted right by one bit. Most significant bit of VY will be set to 0

0x00 if least significant bit of VY before shift was 0, 0x01 if least significant bit of VY before shift was 1

8XY7

VX argument

VY argument

VX subtracted from VY

Unchanged

0x00 if a borrow occurred, 0x01 if no borrow occurred

8XYE

VX argument (initial value is irrelevant)

VY argument

VY shifted left by one bit. Least significant bit of VX will be set to 0

VY shifted left by one bit. Least significant bit of VY will be set to 0

0x00 if most significant bit of VY before shift was 0, 0x01 if most significant bit of VY before shift was 1

Note that when VF is shown as undefined, VF will still have been set to either 0x00 or 0x01, but this will reflect whatever 1802 instruction last affected the DF register and will bear no relation to the outcome of the current instruction.

Another thing to note is that, although some of these instructions set VF to reflect whether a carry or borrow occurred or to reflect the bit that was lost in a shift operation, none of these instructions take that status of VF into account when they are executed. This means the programmer must explicitly take whatever action is necessary as a result of the value in VF. For example, if the programmer was adding two four-byte integers, they would have to check VF after each addition operation and, if necessary, manually increment one of the addends for the next addition operation before performing it.

Here are examples of each instruction:

8XY1 (VX OR VY)
VX (entry): 0x00101101 (0x2D)
VY (entry): 0x01001011 (0x4B)
VX (exit): 0x01101111 (0x6F)

8XY2 (VX AND VY)
VX (entry): 0x00101101 (0x2D)
VY (entry): 0x01001011 (0x4B)
VX (exit): 0x00001001 (0x09)

8XY3 (VX XOR VY)
VX (entry): 0x00101101 (0x2D)
VY (entry): 0x01001011 (0x4B)
VX (exit): 0x01100110 (0x66)

8XY4 (VX + VY resulting in no carry)
VX (entry): 0x00101101 (0x2D)
VY (entry): 0x01001011 (0x4B)
VX (exit): 0x01111000 (0x78)
VF (exit): 0x00000000 (0x00)

8XY4 (VX + VY resulting in carry)
VX (entry): 0x11101101 (0xED)
VY (entry): 0x01001011 (0x4B)
VX (exit): 0x00111000 (0x38)
VF (exit): 0x00000001 (0x01)

8XY5 (VX – VY resulting in no borrow)
VX (entry): 0x01001011 (0x4B)
VY (entry): 0x00101101 (0x2D)
VX (exit): 0x00011110 (0x1E)
VF (exit): 0x00000001 (0x01)

8XY5 (VX – VY resulting in borrow)
VX (entry): 0x00101101 (0x2D)
VY (entry): 0x01001011 (0x4B)
VX (exit): 0x11100010 (0xE2)
VF (exit): 0x00000000 (0x00)

8XY6 (SHR VY with 0 as LSB)
VY (entry): 0x00101100 (0x2C)
VX (exit): 0x00010110 (0x16)
VF (exit): 0x00000000 (0x00)

8XY6 (SHR VY with 1 as LSB)
VY (entry): 0x00101101 (0x2D)
VX (exit): 0x00010110 (0x16)
VF (exit): 0x00000001 (0x01)

8XY7 (VY – VX resulting in no borrow)
VX (entry): 0x00101101 (0x2D)
VY (entry): 0x01001011 (0x4B)
VX (exit): 0x00011110 (0x1E)
VF (exit): 0x00000001 (0x01)

8XY7 (VY – VX resulting in borrow)
VX (entry): 0x01001011 (0x4B)
VY (entry): 0x00101101 (0x2D)
VX (exit): 0x11100010 (0xE2)
VF (exit): 0x00000000 (0x00)

8XYE (SHL VY with 0 as MSB)
VY (entry): 0x00101101 (0x2D)
VX (exit): 0x01011010 (0x5A)
VF (exit): 0x00000000 (0x00)

8XYE (SHR VY with 1 as MSB)
VY (entry): 0x10101101 (0xAD)
VX (exit): 0x01011010 (0x5A)
VF (exit): 0x00000001 (0x01)

If you are writing an interpreter to strictly mirror the documented functionality of the original Chip-8 interpreter, then you only need to implement instruction groups 7XNN and 8XY1 through 8XY5. However, if you do so, you will almost certainly find some more recent Chip-8 programmes that don’t function correctly.

If you do decide to implement the undocumented instructions, then you need to decide whether to implement the shift instructions as they are shown here (i.e. mirror the behaviour of the original interpreter) or implement them as they are documented by most recent descriptions of the Chip-8 instruction set (i.e. with VX being shifted rather than VY). Alternatively, you could provide both behaviours and then allow users to switch between them if they need to. The reality is that you will probably find more Chip-8 programmes that depend on the erroneous behaviour, so that may be a better choice as a default.

Published inProgrammingRetro Computing

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *