Jump to content
IGNORED

Binary to BCD


easmith

Recommended Posts

Probably simple but I am struggling with this .....

I am needing to take a binary value and store it as a BCD value to be displayed in a score routine ( I am using Spice's 2 digit score routine that uses BCD numbers).

 

Say I have a binary coded value in the accumulator  like   $26  ( this would be a RAM variable not a constant ) .

and want to use $ 38 in the score routine , or add this value to an existing BCD  coded  value. 

Thanks.

 

ES

 

Link to comment
Share on other sites

37 minutes ago, SpiceWare said:

Have you checked out 6502.org? Their Source Code Repository includes many useful routines, such as this Hexadecimal to Decimal Conversion - "Routines for converting 8- and 16-bit hexadecimal numbers to their decimal (BCD) equivalents."

Wow, yes that did it thanks !

 

I was aware of the site , but was not looking in the right place or searching the right key words .  Sometimes you need someone to point you to right place .  You are the man ..

 

This worked   where BIN is binary number RAM  location and BCD is BCD output RAM location :

 


BINBCD8:    SED        ; Switch to decimal mode
        LDA #0        ; Ensure the result is clear
        STA BCD+0
        STA BCD+1
        LDX #8        ; The number of source bits

CNVBIT:        ASL BIN        ; Shift out one bit
        LDA BCD+0    ; And add into result
        ADC BCD+0
        STA BCD+0
        LDA BCD+1    ; propagating any carry
        ADC BCD+1
        STA BCD+1
        DEX        ; And repeat for next bit
        BNE CNVBIT
        CLD        ; Back to binary

 

 

 

  • Like 1
Link to comment
Share on other sites

On 2/5/2022 at 9:11 AM, easmith said:

Wow, yes that did it thanks !

 

I was aware of the site , but was not looking in the right place or searching the right key words .  Sometimes you need someone to point you to right place .  You are the man ..

 

This worked   where BIN is binary number RAM  location and BCD is BCD output RAM location :

 


BINBCD8:    SED        ; Switch to decimal mode
        LDA #0        ; Ensure the result is clear
        STA BCD+0
        STA BCD+1
        LDX #8        ; The number of source bits

CNVBIT:        ASL BIN        ; Shift out one bit
        LDA BCD+0    ; And add into result
        ADC BCD+0
        STA BCD+0
        LDA BCD+1    ; propagating any carry
        ADC BCD+1
        STA BCD+1
        DEX        ; And repeat for next bit
        BNE CNVBIT
        CLD        ; Back to binary

 

 

 

I love math routines like this. I took a look and did my own take.

 

Over the routine that you posted this saves 7 bytes, is faster, and uses the same resources (A, X, and 3 bytes of ram). It is good for any value 0-255.

; Begin with value to convert in a zp ram register called "binary"
; The value of binary can be 0-255.

BinToBcd8:
    lda    #0           ; clear
    sta    bcdHundreds
    sta    bcdTensOnes
.loop8bits:
    ldx    #$F8         ; When looping we jump between operator and operand, to perform SED instruction (Opcode $F8)
    asl    binary       ; Shift out one bit,
    adc    bcdTensOnes  ; And add into result...
    sta    bcdTensOnes
    rol    bcdHundreds  ; bcdHundreds ends up being = 0, 1, or 2 (hundreds value)
    inx                 ; Repeat for next bit, Note X starts at $F8 and increments to $00 which breaks the loop
    bne    .loop8bits+1 ; The first iteration does not require decimal mode set, as the value of A can be 0 or 1 only.

    cld                 ; Back to binary

; Hundreds digit (BCD) is stored in bcdHundreds
; Tens and ones digit (BCD) is stored in bcdTensOnes, and is also the value currently stored in the accumulator

 

  • Like 3
Link to comment
Share on other sites

Just running out the door, but had an idea and wanted to post the improved routine:

 

; Begin with value to convert in a zp ram register called "binary"
; The value of binary can be 0-255.

BinToBcd8:
    lda    #0           ; clear
    sta    bcdTensOnes
.loop8bits:
    ldx    #$F8         ; When looping we jump between operator and operand, to perform SED instruction (Opcode $F8)
    rol    binary       ; Shift out one bit,
    adc    bcdTensOnes  ; And add into result...
    sta    bcdTensOnes
    inx                 ; Repeat for next bit, Note X starts at $F8 and increments to $00 which breaks the loop
    bne    .loop8bits+1 ; The first iteration does not require decimal mode set, as the value of A can be 0 or 1 only.

    cld                 ; Back to binary
    rol    binary       ; stores the hundred digit in "binary", and ends up being = 0, 1, or 2 (hundreds value)
    
; Hundreds digit (BCD) is stored in the binary
; Tens and ones digit (BCD) is stored in bcdTensOnes, and is also the value currently stored in the accumulator

 

This now saves 9 bytes, is the fastest yet, and uses less resources (A, X, and only 2 bytes of ram).

  • Like 2
Link to comment
Share on other sites

This tricky one also uses the BCD mode to convert (works from $0..$63):

; A = value (0..$63)  
    tax
    lsr
    lsr
    lsr
    lsr
    tay
    txa
    clc
    sed
    adc     #0
    .byte   $2c
.loopAdd
    adc     #6
    dey
    bpl     .loopAdd
    cld

The first add is for adjusting xA..xF values and then you add (#value / 16) times 6. 

 

If required this can be extended for values above $63. Just use the carry after the add.

Edited by Thomas Jentzsch
  • Like 1
Link to comment
Share on other sites

1 hour ago, easmith said:

thanks much to all.   I only need for values between 0 and 99 or $0 to $63.    I will play around with these and see if I can get them to work .

 

If you just need a range of 0-99, then this is for you:

; Converts a decimal value of 0-99 to BCD
; Uses 16 bytes

BinToBcd99:             ; Begin with value to convert in accumulator
    sta    temp1
    lda    #0           
.loop8bits:
    ldx    #$F8         ; When looping we jump between operator and operand, to perform SED instruction (Opcode $F8)
    asl    temp1
    sta    temp2
    adc    temp2
    inx                 ; Repeat for next bit, Note X starts at $F8 and increments to $00 which breaks the loop
    bne    .loop8bits+1 ; The first iteration does not have decimal mode set, but testing showed it didn't mater.
    cld                 
  
; Accumulator now holds BCD value

 

This is essentially @batari's code in 6502 Killer Hacks, but I took a byte out of the routine by jumping between operator and operand to do SED, and then changed DEX to INX.

  • Like 1
Link to comment
Share on other sites

1 hour ago, easmith said:

thanks much to all.   I only need for values between 0 and 99 or $0 to $63.    I will play around with these and see if I can get them to work .

 

 

If you only need one BCD number and you have enough ROM space left, than a data table is the fastest way.

 

Link to comment
Share on other sites

11 minutes ago, Al_Nafuur said:

If you only need one BCD number and you have enough ROM space left, than a data table is the fastest way.

 

Yes this is true, or just skip conversions altogether and do everything in decimal mode. That probably makes the most sense since the range does not require more than one byte.

Link to comment
Share on other sites

Thought I'd post one more (0-255) conversion to BCD. This takes all of these routines and combines them. It's only 17 bytes but depends on A holding the value to convert at the beginning of the routine.

 

; A = value to convert (0 to 255)

BinToBcd8:
    asl
    sta    bcdHundreds
    lda    #0           ; clear
.loop8bits:
    ldx    #$F8         ; When looping we jump between operator and operand, to perform SED instruction (Opcode $F8)
    sta    temp1
    adc    temp1
    rol    bcdHundreds
    inx                 ; Note X starts at $F8 and increments to $00 which breaks the loop
    bne    .loop8bits+1 ; The first iteration does not require decimal mode set, as the value of A can be 0 or 1 only.
    cld

; A = Tens and ones digit (BCD)
; Hundreds digit (BCD) is stored in bcdHundreds

 

  • Like 2
Link to comment
Share on other sites

Just so that all options are represented as code:

bin_to_bcd:             ; Begin with value to convert in x register
    lda hex_to_bcd,x    ; 4
                        ; Accumulator now holds BCD value

    align 256
hex_to_bcd
    .byte $00, $01, $02, $03, $04, $05, $06, $07, $08, $09, $10, $11, $12, $13, $14, $15
    .byte $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31
    .byte $32, ...

Super fast (needs only 4 cpu cycles). Requires a whopping 103 bytes, but only 3 additional bytes when you reuse it.

 

 

 

  • Like 1
Link to comment
Share on other sites

There is also a hybrid version, which tries to balance byte savings with the need for speed. From my blog in 2015:

 

;Hex2Bcd (good 0-99)
;22 bytes, 26 cycles
    tay              ;2  @2
    lsr              ;2  @4
    lsr              ;2  @6
    lsr              ;2  @8
    lsr              ;2  @10
    tax              ;2  @12
    tya              ;2  @14
    sed              ;2  @16
    clc              ;2  @18
    adc   #0         ;2  @20
    adc   BcdTab,X   ;4  @24
    cld              ;2  @26


BcdTab:
    .byte $00,$06,$12,$18,$24,$30,$36

 

  • Like 4
Link to comment
Share on other sites

Just a footnote, to critque my own code here:

;Hex2Bcd (good 0-99)
;22 bytes, 26 cycles
    tay              ;2  @2
    lsr              ;2  @4
    lsr              ;2  @6
    lsr              ;2  @8
    lsr              ;2  @10
    tax              ;2  @12
    tya              ;2  @14
    sed              ;2  @16
    clc              ;2  @18
    adc   #0         ;2  @20
    adc   BcdTab,X   ;4  @24
    cld              ;2  @26


BcdTab:
    .byte $00,$06,$12,$18,$24,$30,$36

There are ways to either go faster by a couple of cycles, or save a byte. None are really great but I'll explain it.

 

First to save 2 cycles one naturally wonders about eliminating the CLC instruction. Years ago I would use the illegal opcode ASR (AND and shift right) to eliminate some bits before doing a right shift thus making the carry clear. It wouldn't save any bytes in the above code but is 2 cycles faster. The problem though is that I and others have found that ASR is not reliable on all consoles (Jr's). Even if people found an explaination of it's behaviour I wouldn't feel comrfortable risking it anymore just to save 2 cycles. It's a pity though because it is really a useful opcode.

 

Another way to save 2 cycles has a dependency in other routines that have look up tables the same size, and like the code above shift the high nibble then put it into the index register. The trick lies in interlacing the data of the look up tables, and then masking off bits you don't need. This might be better explained in an example:

;Our program has code like this, in at least 4 places
    lsr               ;2  
    lsr               ;2 
    lsr               ;2 
    lsr               ;2 
    tax               ;2 
    lda  LookUpTab1,X ;4  each lookup table though is different, but has data of the same size

LookUpTab1:
    .byte $0A,$0A,$0A,$0A
LookUpTab2:
    .byte $0B,$0B,$0B,$0B
LookUpTab1:
    .byte $0C,$0C,$0C,$0C
LookUpTab1:
    .byte $0D,$0D,$0D,$0D

 

;What if we did this instead, to go 2 cycles faster?
    lsr               ;2  
    lsr               ;2 
    and  #$FC         ;2   Forget about the last 2 shifts (divide by 4)
    tax               ;2 
    lda  LookUpTab1,X ;4

;We can do it, if our tables are re-arranged to be interlaced
LookUpTab1:
    .byte $0A
LookUpTab2:
    .byte $0B
LookUpTab1:
    .byte $0C
LookUpTab1:
    .byte $0D
    .byte $0A,$0B,$0C,$0D   ; interlaced data
    .byte $0A,$0B,$0C,$0D
    .byte $0A,$0B,$0C,$0D

 

Next to save a byte there is a very special case (which makes it impractical, but is interesting). An observation in the code is that there are two processor status instructions in a row (SED and CLC). If PLP is used with the correct value then both SED and CLC can be put into the correct status with just one byte while using the same amount of cycles (4). The natural question is wouldn't you have to set it ahead of time thus destroying any potential savings? Well the only way I every really see this working is in an odd situation where there is a JSR that wants to change it's return address to come back to a different spot. For example the subroutine could contain a bunch of code (including this routine) that happens to have a low byte of the return address from the JSR which works pefectly for using PLP. Later on a new return address would be determined and maybe PHA would be used to set the new low byte of the return address and restore the stack pointer. Along that lines, instead of PHA maybe the stack pointer might simply be reset and the routine ends with jump to a whole new place for whatever reason. Yes I warned you that it is special case, but ironically I have seen this done in some games (JSR with no return).

 

Another byte saving method is looking for an opportunity to combine the data table right into the routine to use as code. This might be possible by manipulation of the data table and the ADC #0 so that it the data could be somehow inserted somewhere into the routine if the bytes in the table just happened to match opcodes that don't affect the overall routine. The consequence is even if it worked more cycles would be used, and for that reason I didn't look at it too hard just to save a byte. Alternatively one might switch to SBC to help with manipulation of the data table, or perhaps the end of the table could be made to match TAY or SED allowing it to be put in front of the code, if the entry point the routine skipped over the data.

 

Finally it's worth mentioning that the easiest way to save a byte or two is just to take the data table and combine it with data somewhere else.

 

 

That's about it. Now be off with you if happened to be reading for this long. I don't even know why I bothered to type all of this out! ?

 

 

 

 

 

  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...