Chip-8 on the COSMAC VIP: Binary Coded Decimal

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:

100s 10s 1s
‑‑‑‑‑‑‑‑‑‑‑
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:

01111011
01100100 –
‑‑‑‑‑‑‑‑
00010111 =

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

100s 10s 1s
‑‑‑‑‑‑‑‑‑‑‑
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:

00010111
00001010 –
‑‑‑‑‑‑‑‑
00001101 =

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

100s 10s 1s
‑‑‑‑‑‑‑‑‑‑‑
1    1   0

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

00001101
00001010 –
‑‑‑‑‑‑‑‑
00000011 =

One again we add 1 to our 10s column:

100s 10s 1s
‑‑‑‑‑‑‑‑‑‑‑
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:

00000011
00000001 –
‑‑‑‑‑‑‑‑
00000010 =

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

100s 10s 1s
‑‑‑‑‑‑‑‑‑‑‑
1    2   1

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

00000010
00000001 –
‑‑‑‑‑‑‑‑
00000001 =

Once again we add 1 to our 1s column:

100s 10s 1s
‑‑‑‑‑‑‑‑‑‑‑
1    2   2

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

00000001
00000001 –
‑‑‑‑‑‑‑‑
00000000 =

Again, adding 1 to our 1s column:

100s 10s 1s
‑‑‑‑‑‑‑‑‑‑‑
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:

A flowchart showing the Chip-8 interpreter sequence for converting a binary number to BCD

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.

This entry was posted in Chip-8, Retro Computing and tagged , , , , , , , , . Bookmark the permalink.

One Response to "Chip-8 on the COSMAC VIP: Binary Coded Decimal"

Leave a reply