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: FX33

Convert VX to binary coded decimal (BCD) and store the result in the three bytes of memory pointed to by I

Binary Coded Decimal (BCD) is a means of storing decimal numbers in a binary format. There are two common methods for storing BCD numbers in an 8-bit system. The most efficient in terms of memory is to used packed BCD. With this encoding, each decimal digit occupies a nibble or four bits, so each byte holds two decimal digits. Chip-8 uses an uncompressed format, in which each digit is stored in a single byte. This might seem a strange choice given the efforts taken to save memory elsewhere in the interpreter. But to put it into context, for an 8-bit number, using an uncompressed rather than a packed BCD format makes the difference of just one byte, which is a good trade off against the additional complexity and length of a BCD routine to create a packed format. It also means the converted values can be loaded into VX and used directly with the FX29 instruction, which will point to the data for the relevant character sprite for that digit.

Only four bits of each byte are needed to hold each decimal digit, as shown in this table:

Binary value | Digit |
---|---|

00000000 | 0 |

00000001 | 1 |

00000010 | 2 |

00000011 | 3 |

00000100 | 4 |

00000101 | 5 |

00000110 | 6 |

00000111 | 7 |

00001000 | 8 |

00001001 | 9 |

00001010 | Invalid value |

00001011 | Invalid value |

. . . |
Invalid values |

11111110 | Invalid value |

11111111 | Invalid value |

Chip-8 uses the division by digit value method to calculate the BCD digits. Here’s an example. Let’s say we were converting the binary value 01111011 (0x7B) into BCD. We are going to end up with three decimal digits from an 8-bit amount, since the maximum value is 255. So first we set these three digits to zero:

‑‑‑‑‑‑‑‑‑‑‑

0 0 0

Now we are going to calculate the a digit value for the 100s column. 100 in binary is 01100100 (0x64). First we try to subtract this from our original value:

01100100 –

‑‑‑‑‑‑‑‑

00010111 =

That worked so we can add one to the 100s column:

‑‑‑‑‑‑‑‑‑‑‑

1 0 0

The remainder from the previous operation (00010111) is now less than 100 (01100100) and a further subtraction would result in a negative value, so we know we are finished with the 100s column and 1 is the correct digit. Let’s move on to the 10s column. 10 in binary is 00001010 (0x0A). So we apply the same operation here – try to subtract this from the remainder:

00001010 –

‑‑‑‑‑‑‑‑

00001101 =

That worked, so we can add 1 to our 10s column:

‑‑‑‑‑‑‑‑‑‑‑

1 1 0

This time the remainder (0001101) is not less than 10 (0001010), so we repeat the operation:

00001010 –

‑‑‑‑‑‑‑‑

00000011 =

One again we add 1 to our 10s column:

‑‑‑‑‑‑‑‑‑‑‑

1 2 0

At this point our remainder (00000011) is less than 10 (00001010) and a further subtraction would result in a negative value, so we know we are finished with the 10s column and 2 is the correct digit. So finally we move on to the 1s column. 1 in binary is 00000001 (0x01). Once again, we try to subtract this from the remainder:

00000001 –

‑‑‑‑‑‑‑‑

00000010 =

That worked, so we can add 1 to the 1s column:

‑‑‑‑‑‑‑‑‑‑‑

1 2 1

The remainder (00000010) is not less than 1 (00000001) so we repeat the operation:

00000001 –

‑‑‑‑‑‑‑‑

00000001 =

Once again we add 1 to our 1s column:

‑‑‑‑‑‑‑‑‑‑‑

1 2 2

The remainder (00000001) is not less than 1 (00000001) so we repeat the operation:

00000001 –

‑‑‑‑‑‑‑‑

00000000 =

Again, adding 1 to our 1s column:

‑‑‑‑‑‑‑‑‑‑‑

1 2 3

Now the remainder (00000000) is less than 1 (00000001) and a further subtraction would result in a negative number, so we know we are finished with our 1s column and 3 is the correct digit. And since there are no more columns to process we have our final decimal digits: 123.

Here’s that sequence, as it is implemented in the Chip-8 interpreter, shown as a flowchart:

Here’s the interpreter code for this instruction:

Address (hex) | Code (hex) | Labels | Assembly | Comments |
---|---|---|---|---|

011B | 64 0A 01 | BCD_ DENOMINATORS: | DB 0x64, 0x0A, 0x01 | Three constants with the decimal values of 100, 10 and 1, used by the BCD instruction FX33 |

011E – 0132 |
The code for instructions FX1E and FX29 occupies this part of the interpreter |
|||

0133 | E6 | FX_33: | SEX 6 | This is the entry point for instruction FX33 Use VX (R6) for register indirect addressing |

0134 | 06 | LDN 6 | Get the value to be converted from VX | |

0135 | BF | PHI F | Preserve the original value by temporarily storing it in RF.1 | |

0136 | 93 | GHI 3 | Get the high order byte of the address of the BCD denominator constants | |

0137 | BE | PHI E | Store this in RE.1 | |

0138 | F8 1B | LDI BCD_DENOMINATORS | Get the low order byte of the address of the BCD denominator constants | |

013A | AE | PLO E | RE now points to first BCD denominator constant | |

013B | 2A | DEC A | The I pointer (RA) is pointing to first byte of memory to store BCD but it needs to be moved to the byte before that before entering the loop | |

013C | 1A | BCD_ LOOP: | INC A | Point I (RA) to location of next BCD digit |

013D | F8 00 | LDI 0x00 | Create a zero value | |

013F | 5A | STR A | Use this to initialise the BCD digit | |

0140 | 0E | DIVISION_ LOOP: | LDN E | Get the current BCD constant |

0141 | F5 | SD | Subtract it from the current value of VX | |

0142 | 3B 4B | BNF NEXT_DIGIT | If the result is negative then the current digit is at the correct value, so move on to the next one | |

0144 | 56 | STR 6 | Store the remainder back in VX | |

0145 | 0A | LDN A | Get the value of the current BCD digit | |

0146 | FC 01 | ADI 0x01 | Add 1 to it | |

0148 | 5A | STR A | And put it back into memory | |

0149 | 30 40 | BR DIVISION_LOOP | Continue to divide VX by current denominator | |

014B | 4E | LDA E | Get current BCD constant and point to next one | |

014C | F6 | SHR | Test the least significant bit | |

014D | 3B 3C | BNF BCD_LOOP | If it’s not set (i.e. the constant we just using was not 0x01) then loop back and do the next digit | |

014F | 9F | GHI F | Get the preserved original value of VX | |

0150 | 56 | STR 6 | Restore this to VX | |

0151 | 2A | DEC A | This and the next instruction restores I (RA) so it is pointing to the first stored BCD digit | |

0152 | 2A | DEC A | ||

0153 | D4 | SEP 4 | Return to the fetch and decode routine |

The execution time for this instruction is 80 machine cycles (363.2 microseconds) for case of VX = 0. For each unit above zero in the 100s, 10s and 1s columns add a further 16 machine cycles (72.64 microseconds). For example, if the converted number was 123, that would require 80 + (1 + 2 + 3) * 16 machine cycles, which is 176 machine cycles (799.04 microseconds). As this is a group FXXX instruction you will have to add the small overhead for the second stage decoding of these instructions. See this earlier post for details.

An obvious application for the BCD instruction is to convert a score, or other counter, before displaying it as a series of three character sprites (using instructions FX29 followed by DXY5). However, this instruction is limited in that it will only convert an 8-bit number, giving a range of 0 to 255.

This instruction should be handled by a contemporary Chip-8 interpreter.

## By Chip-8 on the COSMAC VIP: Index | Laurence Scotford October 19, 2013 - 11:39 am

[…] The following posts analyse different parts of the interpreter. These are useful if you want to ensure that your implementations of Chip-8 instructions follow the behaviour of the original interpreter: Chip-8 on the COSMAC VIP: Initialisation Chip-8 on the COSMAC VIP: The Call Routine (Fetch and Decode) Chip-8 on the COSMAC VIP: Machine Code Integration Chip-8 on the COSMAC VIP: Branch and Call Instructions Chip-8 on the COSMAC VIP: Skip Instructions Chip-8 on the COSMAC VIP: Loading and Saving Variables Chip-8 on the COSMAC VIP: Arithmetic and Logic Instructions Chip-8 on the COSMAC VIP: Interrupts Chip-8 on the COSMAC VIP: Generating Random Numbers Chip-8 on the COSMAC VIP: Drawing Sprites Chip-8 on the COSMAC VIP: The General Purpose Timer Chip-8 on the COSMAC VIP: Keyboard Input Chip-8 on the COSMAC VIP: Sound Chip-8 on the COSMAC VIP: Indexing the Memory Chip-8 on the COSMAC VIP: The Character Set Chip-8 on the COSMAC VIP: Binary Coded Decimal […]