easmith Posted February 5, 2022 Share Posted February 5, 2022 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 Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted February 5, 2022 Share Posted February 5, 2022 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." Quote Link to comment Share on other sites More sharing options...
easmith Posted February 5, 2022 Author Share Posted February 5, 2022 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 1 Quote Link to comment Share on other sites More sharing options...
+Andrew Davie Posted February 5, 2022 Share Posted February 5, 2022 (edited) bogus post sorry Edited February 5, 2022 by Andrew Davie Quote Link to comment Share on other sites More sharing options...
+Omegamatrix Posted February 6, 2022 Share Posted February 6, 2022 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 3 Quote Link to comment Share on other sites More sharing options...
+Omegamatrix Posted February 6, 2022 Share Posted February 6, 2022 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). 2 Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted February 6, 2022 Share Posted February 6, 2022 (edited) 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 February 7, 2022 by Thomas Jentzsch 1 Quote Link to comment Share on other sites More sharing options...
easmith Posted February 6, 2022 Author Share Posted February 6, 2022 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 . Quote Link to comment Share on other sites More sharing options...
+Omegamatrix Posted February 7, 2022 Share Posted February 7, 2022 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. 1 Quote Link to comment Share on other sites More sharing options...
+Al_Nafuur Posted February 7, 2022 Share Posted February 7, 2022 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. Quote Link to comment Share on other sites More sharing options...
+Omegamatrix Posted February 7, 2022 Share Posted February 7, 2022 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. Quote Link to comment Share on other sites More sharing options...
+Stephen Posted February 7, 2022 Share Posted February 7, 2022 I just love these little code snippets. So many ways to accomplish a task with the 6502, and just when you think you've come up with a smart way of doing it, someone else does it either: faster, with less code, or less RAM required. Quote Link to comment Share on other sites More sharing options...
+Omegamatrix Posted February 7, 2022 Share Posted February 7, 2022 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 2 Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted February 7, 2022 Share Posted February 7, 2022 (edited) I like how SED is done, very clever. So, OmegaMatrix has the most compact code for 0..$63 (16 bytes), mine needs two more bytes (18), but is much faster (~150 vs. 28..70 cycles). Edited February 7, 2022 by Thomas Jentzsch Quote Link to comment Share on other sites More sharing options...
+Al_Nafuur Posted February 7, 2022 Share Posted February 7, 2022 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. 1 Quote Link to comment Share on other sites More sharing options...
+Omegamatrix Posted February 7, 2022 Share Posted February 7, 2022 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 4 Quote Link to comment Share on other sites More sharing options...
+Omegamatrix Posted February 9, 2022 Share Posted February 9, 2022 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! ? 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.