Test 4a: the decimal counter
A game shows numbers: the score, the fuel, the level. The 8086 adds in binary, but a person reads decimal digits. This test keeps the counter in packed decimal: one digit in each half of a byte. Two bytes hold 0000 to 9999. After each ADD, the word DAA makes the byte decimal again. The digits are then ready to show, with no division.

A What the machine does
The routine bcd_inc for the step to the value on the screen now.
Waiting for the copy of the PC to start…
Under each digit are the 4 bits of its nibble: yellow is 1, gray is 0. The cell C on the left is the carry. It is red after 9999 becomes 0000. Press Jump to 9990 to see that step soon.

B What the check expects
The same value, made from the number of steps with MOD and ISHFT, as lab/check_bcd.f90 does.
What is new in test 4a
- A Packed decimal. The number 1234 is the two bytes 12h and 34h: each hexadecimal digit of the byte is one decimal digit. A nibble (4 bits) holds 0 to 15, so the values 10 to 15 are never used. That is the cost of the method: two bytes hold 0 to 9999, not 0 to 65535.
- A ADD, then DAA. The 8086 has no decimal flag. It always adds in binary: 09h + 1 = 0Ah. The next word,
DAA(decimal adjust after addition), corrects AL: 0Ah becomes 10h, and 9Ah becomes 00h with a carry.DAAworks only on AL, so the program does one byte at a time. - A ADD, not INC.
INCdoes not change the carry flag (CF). The program needs the carry from the low byte, so it usesadd al, 1. Thenadc al, 0adds that carry to the high byte. The SNES developer met the same trap: on the 65C816, INC ignores the decimal flag. - A A digit is a table lookup.
bcd_digitsmoves each nibble into AL and usesXLATon the table "0123456789ABCDEF". There is no division by 10. - A B The check runs every value.The counter has only 10,000 values, so the check tests all of them. The test (
test/checkbcd.asm) runsbcd_inc10,000 times and writes each value toBCD.BIN, then the value after 9999 and the carry. The checker (lab/check_bcd.f90) makes each value again withMODandISHFTand compares them.
The check report
File: BCD.BIN
A side and B side agree on 10000 of 10000 values
After 9999: 0000, carry 1 (must be 0000, carry 1): rightWhere this test comes from
Test 4 is the first test that comes from the review of the SNES video guides. Each step below leads to the next.
- The video. In "It Took Every SNES Hardware Trick To Make My Game" at 32:05, Inkbox keeps three game counters in packed decimal. The 65C816 has a decimal flag that does the carry. He also warns that INC and DEC ignore that flag, so he adds with ADC. A 16-bit counter "fits exactly the 10,000" values that he needs.
- The review findings.D13-M11: the research report called the counter an unverified detail, but the video shows it in full.D15-M7: the interactive page "simulates" the counter with a decimal string, and it stops at 9999. A real packed decimal counter goes to 0000 with a carry. Thus test 4 checks the step after 9999.D14-M1 and D15-M1: the number 9,999 is the largest value of the counter, not the number of floors.
- The concept review. The 8086 has no decimal flag. The idea moves to a different word:
DAAafterADDorADC. The same trap moves too:INCdoes not change CF. This is learning unit U3, digits on the screen, in thesummary of the SNES video guides. - The segment pages (decision D26). Showing text and reading keyshad the limit "no number formatting". It now says that a packed decimal counter is the smallest answer.Doing arithmetic says: "The 8086 has no decimal flag. After ADD or ADC on packed decimal bytes, write DAA". The B side shows the formats I4.4 and I0.
- The plan. PLAN.md, Phase 4d gave the design of test 4: count 0000 to 9999 with ADD, ADC and DAA, check all 10,000 values with MOD and ISHFT, and build a wrong twin without DAA that must fail on 9,990 values. This page is that design, built.
- The dictionary. The review added the words that this test uses:DAA, ADC,XLAT and AAM on the A side, and MOD andISHFT on the B side.
The words that each program uses
The pills show the words that the dictionary already has. "New words" are real words of the language that the dictionary does not have yet.
A src/counter.asm· 240 lines · The game (test 4a): shows the counter as four large digits and its 16 bits
52 dictionary words: MOVPUSHPOPAXBXCXDXSIDIBPDSESCS[ ]BYTEWORDDBDWEQUADDINCDECSHLORXORADCDAACMPTESTJMPJE / JZJNE / JNZJB / JALOOPCALLRETlabel:REPSTOSBLODSBCLDORGBITS%define%includeTIMES;,hINTINT 10h, AX=0013hA000h
New words (10): AHALBHBLCLCPUDHDLJNCLDS
A src/bcd.inc· 55 lines · The shared routines: add 1 with ADD, ADC and DAA, and change the nibbles into digits
19 dictionary words: MOVBXDI[ ]DBDWXLATADDINCSHRANDADCDAACALLRETlabel:;,h
New words (3): AHALCL
A test/checkbcd.asm· 57 lines · The test: runs bcd_inc 10,000 times and writes each value to a file
24 dictionary words: MOVAXBXCXDXDI[ ]DBDWXORADCLOOPCALLlabel:STOSWCLDORGBITS%includeTIMES;,hINT
New words (4): AHALCPUJC
B lab/check_bcd.f90· 78 lines · The checker: makes each value again with MOD and ISHFT, and compares all 10,000
42 dictionary words: INTEGERLOGICALCHARACTER::=PARAMETERINT8 / INT16+ -* /( )MODINT( )IANDISHFT / SHIFTAIF ... THEN ... END IFDOSTOPERROR STOPMERGE== /= < > <= >=.and. .or. .not.x(2:5)COUNTANY / ALLPROGRAMENDUSECONTAINSFUNCTIONIMPLICIT NONECALLINTENT(IN)!&PRINTWRITEREADOPENCLOSEGET_COMMAND_ARGUMENT'(F8.2)'ISO_FORTRAN_ENV
New words (7): COMMAND_ARGUMENT_COUNTDIMFINDLOCINTRINSICLENONLYTRIM
Next: test 4b runs this program without DAA, and shows how the check finds it.