It is currently Fri Nov 24, 2017 12:16 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 26 posts ]  Go to page Previous  1, 2
Author Message
 Post subject: Re: Hex to Decimal
PostPosted: Tue Jun 17, 2014 7:25 pm 
Offline
User avatar

Joined: Sun Jan 02, 2011 11:50 am
Posts: 522
Sure, tepples. Right now I am (slowly) working on something in C that will combine 6502 assembly and C in the same source file and call ca65 to build the assembly. I think my idea will work, but I would be interested to see how others solved this problem.


Top
 Profile  
 
 Post subject: Re: Hex to Decimal
PostPosted: Tue Jun 17, 2014 7:48 pm 
Offline
User avatar

Joined: Tue Jun 10, 2014 8:15 pm
Posts: 35
Location: Canada
Movax12 wrote:
Omegamatrix, nice code, but what I am more interested in at the moment is how you determine the following:

Code:
;--------------------------------
;0-99 conversion stats
;--------------------------------
  ;cycles  occurances
    ;28  -  10
    ;31  -  10
    ;34  -  10
    ;37  -  10
    ;39  -  10
    ;42  -  10
    ;45  -  10
    ;48  -  10
    ;50  -  10
    ;56  -  10

  ;average execution is 41 cycles


What method/tools do you use to create that information?


I'm running a 2600 program I wrote, because I don't know much about the NES yet. The 2600 has a 1 cycle timer which I'm loading before the subroutine and checking afterward. From there I run some code to check for the best times and worst times. The difference between best and worse times is small enough that I can use some zero page ram to record how many times each cycle scenario occurs. I subtract the best time to offset it to zero, and also 4 cycles for loading the 2600 timer, and an additional 3 cycles for saving the value left in A at the end of the routine (I do that before I load the timer in A).


The program I'm using scrolls through all possible values, and checks if they are all correct. If it's good I get a green passed screen, or a red fail screen. Here's some of the code:

Code:
.innerLoop:
    dec    hexValue
    lda    hexValue              ; A = 0-255

  IF DETERMINE_BEST_WORSE_TIME
    ldy    #255
    sty    TIM1T
  ENDIF
    jsr    HexToDec255

;... routine runs here

    sta    decOnes              ; save routine value
    lda    INTIM                ; get timer value
    sty    decHundreds
    stx    decTens

    eor    #$FF
    sec
    sbc    #4+3                 ; take away 3 cycles for STA decOnes, and 4 cycles for LDA INTIM
    cmp    worstTime            ; save best/worse time
    bcc    .lessThen
    sta    worstTime            ; worseTime begins the routine loaded with 0, and bestTime begins with $FF
.lessThen:
    cmp    bestTime
    bcs    .moreThen
    sta    bestTime
.moreThen:

    sec
    sbc    #35                   ; You have run the routine once to get the best time (35 in this case),
    tax                          ; and run the routine again afterward with that value.
    clc
    lda   #1
    adc   occurances,X           ; update count for each routine time occurance...
    sta   occurances,X



    ldy    hexValue              ; verify routine values against tables stored in rom
    lda    decOnes
    cmp    OnesTab,Y
    bne    .setBadFlag
    lda    decTens
    cmp    TensTab,Y
    bne    .setBadFlag
    lda    decHundreds
    cmp    HundredsTab,Y
    beq    .skipMarkBadResult

.setBadFlag:
    lda    #$FF
    sta    badValueFlag
    lda    firstBadValue
    bne    .skipMarkBadResult
    sty    firstBadValue

.skipMarkBadResult:
    dey
    cpy    #$FF
    bne    .innerLoop


Here in Stella's debugger you can see the cycles cases begin at $B0 and continues from there. This snapshot is for the results of 0-255.
Attachment:
DebuggerValues.jpg
DebuggerValues.jpg [ 72.54 KiB | Viewed 1396 times ]


Top
 Profile  
 
 Post subject: Re: Hex to Decimal
PostPosted: Tue Jun 17, 2014 7:54 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19254
Location: NE Indiana, USA (NTSC)
Movax12 wrote:
tepples wrote:
I wrote a 6502 simulator in Python so that I could run automated unit tests on 6502 code. [...] Want to see my simulator?

Sure, tepples.

Download my work-in-progress Cookie Clicker clone and look in the "float" folder.

Movax12 wrote:
Right now I am (slowly) working on something in C that will combine 6502 assembly and C in the same source file and call ca65 to build the assembly. I think my idea will work, but I would be interested to see how others solved this problem.

Anything like Leushenko's x86-to-C static recompiler?

Omegamatrix wrote:
I'm running a 2600 program I wrote [describes unit test harness]

Neat. It'd be harder to do that on an NES without mapper support.


Top
 Profile  
 
 Post subject: Re: Hex to Decimal
PostPosted: Tue Jun 17, 2014 7:59 pm 
Offline
User avatar

Joined: Sun Jan 02, 2011 11:50 am
Posts: 522
tepples wrote:
Anything like Leushenko's x86-to-C static recompiler?.


I considered that idea, but i want to be able to use actual ca65 compatible source, so I am going to load the binary output of ca65 into an open-source 6502 emulator's RAM and hook back to C code when needed.

Omegamatrix, interesting solution. I am going to have to look at that more than once to really get what you are doing.


Top
 Profile  
 
 Post subject: Re: Hex to Decimal
PostPosted: Tue Jun 17, 2014 8:02 pm 
Offline
User avatar

Joined: Tue Jun 10, 2014 8:15 pm
Posts: 35
Location: Canada
tepples wrote:
But so long as the cart doesn't put something with read side effects into $6xxx, BIT $6469 should work. (The addresses with read side effects on the NES are $2002, $2007, $4015-$4017, and $4020-$5FFF for the Vs. System's credit acknowledge.) The only mapper that I can think of that has read side effects there is Bandai boards with an I²C EEPROM.



I have another option I could try. If I substitute:
Code:
    .byte $2C                    ; BIT $6469
.use100
    adc    #100
.done


With this:
Code:
    .byte $2C                    ; BIT $9BE9
.use100
    sbc    #-101
.done


The function is the same. It's a little more convoluted and certainly harder to read, but if it doesn't conflict with anything I would certainly switch over to it.

If that still potentially breaks something I'm going to stick a branch in there with some comments that a byte can be saved with BIT, but the user must check for these odd cases.


Top
 Profile  
 
 Post subject: Re: Hex to Decimal
PostPosted: Tue Jun 17, 2014 8:07 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19254
Location: NE Indiana, USA (NTSC)
I don't think any NES mapper has read side effects in $8000-$FFFF because that's where the program itself is, except perhaps for really freaking complicated copy protection schemes.


Top
 Profile  
 
 Post subject: Re: Hex to Decimal
PostPosted: Tue Jun 17, 2014 8:22 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 6450
Location: UK (temporarily)
tepples wrote:
read side effects in $8000-$FFFF
Mapper 234 triggers bankswitches on reads from $FF80-$FFF7. (This doesn't pose a problem with using BIT here)


Top
 Profile  
 
 Post subject: Re: Hex to Decimal
PostPosted: Tue Jun 17, 2014 9:30 pm 
Offline
User avatar

Joined: Mon Feb 07, 2011 12:46 pm
Posts: 932
thefox wrote:
In fact, all of $4020..$FFFF. It could be RAM, ROM, mapper registers or nothing (= open bus).
Actually, I think it could even map things to the rest of the address space too if it wanted to (I have discussed deliberately interfering with mirrors of RAM and PPU registers, in order to allow writes to write two things at once; for example, a mapper may be designed to mirrors its write-only registers at $1xxx, $3xxx, $5xxx, $7xxx, $9xxx, $Bxxx, $Dxxx, $Fxxx).

Even if such a thing existed it still won't interfere with using BIT here (which only reads), although changing it to SBC #-101 like you have would in fact fix it with whatever mapper you might want to use (probably; I don't know if there are other cases).

Like they said there are some mappers that have side-effects on read from some registers, but $9BE9 is not in that range, and I am not sure why anyone would make one where there are side-effects on read at that address.

Maybe you can put a comment explaining it; if needed, it can then be changed between ADD #100 or SBC #-101 or a branch instead of BIT, depending on the mapper, if it is necessary to do so.

_________________
.


Top
 Profile  
 
 Post subject: Re: Hex to Decimal
PostPosted: Tue Jun 17, 2014 9:54 pm 
Offline
User avatar

Joined: Tue Jun 10, 2014 8:15 pm
Posts: 35
Location: Canada
zzo38 wrote:
Maybe you can put a comment explaining it; if needed, it can then be changed between ADD #100 or SBC #-101 or a branch instead of BIT, depending on the mapper, if it is necessary to do so.


Good idea. I added in the comment with some choices.

I also added some ASCII support. Cost was just 2 bytes and 2 cycles. :)


Top
 Profile  
 
 Post subject: Re: Hex to Decimal
PostPosted: Wed Jun 18, 2014 11:00 pm 
Offline
User avatar

Joined: Tue Jun 10, 2014 8:15 pm
Posts: 35
Location: Canada
I took a look and realized there is a easy way to chop another couple of bytes out of the routine by eliminating a branch. This will bring the total size down to 41 bytes, which is not too bad. The downside is that doing so will add an extra 3 cycles to some cases, including the worst case. To me that seemed like no gain at all. In the end I decided to to leave the routine as is and just post the changes here for user to make their own decision.


Old:
Code:
HexToDec99; SUBROUTINE
    ldx    #0 + ASCII_OFFSET
    cmp    #50                   ; A = 0-99
    bcc    .try20
    sbc    #50
    ldx    #5 + ASCII_OFFSET
    bne    .try20                ;always branch

.div20:
    inx
    inx
    sbc    #20
.try20:


New (save 2 bytes by eliminating branch, but add 3 more cycles):
Code:
HexToDec99; SUBROUTINE
    ldx    #0 + ASCII_OFFSET
    cmp    #50                   ; A = 0-99
    bcc    .try20
    sbc    #30
    ldx    #3 + ASCII_OFFSET
.div20:
    inx
    inx
    sbc    #20
.try20:


Top
 Profile  
 
 Post subject: Re: Hex to Decimal
PostPosted: Sat Jun 28, 2014 10:54 am 
Offline
User avatar

Joined: Tue Jun 10, 2014 8:15 pm
Posts: 35
Location: Canada
I've wrote a couple more 16 bit number (0-65535) to decimal routines. My old, original routine took 258 bytes, and 150-162 cycles. That routine however was not set up for JSR. To do so would requires 3 more bytes, and 15 cycles. That brings it true cost to 261 bytes, and 165-177 cycles.

The first new routine I made takes only 234 bytes, and 157-162 cycles (including the JSR, RTS). Its worse case time is 3 cycles faster then the old routines best case, and it saves 27 bytes. The second new routine saves even more taking only 174 bytes, but takes longer requiring 178-186 cycles to execute. Still, those cycles are not too bad, and I'm pretty happy about the overall performance of both routines. :D

The basic approach of both new routines is different to the original. Now I'm going after the 10,000's and 1,000's digit by shifting the high byte to the 1024 value bit. This gives a pretty close approximation to an integer divide by 1000. I start with that and correct the result. The fast routine uses a multiply by 24 table to correct the result. I doubled that table up storing the high byte value (0-6) in the bits 0-2. Since the low byte value only ever uses bits 3-7 this was easy to do.

Support has also been added for entries into the routine to do 0-999, 0-255, and 0-99 conversions. Adding support for HexToDec255 and HexToDec999 requires 9 more bytes. Ascii support is also available at a cost of 2 bytes and 2 cycles for each digit.


Stats:
Code:
;slow routine - 174 bytes, 183 bytes with HexToDec255 and HexToDec999
;HexToDec99     ; 37 cycles
;HexToDec255    ; 52-57 cycles
;HexToDec999    ; 72-77 cycles
;HexToDec65535  ; 178-186 cycles


;Fast routine - 234 bytes, 243 bytes with HexToDec255 and HexToDec999
;HexToDec99     ; 37 cycles
;HexToDec255    ; 52-57 cycles
;HexToDec999    ; 72-77 cycles
;HexToDec65535  ; 157-162 cycles

;-------------------------------------------------------------------------------

;HexToDec99
; start in A
; end with A = 10's, decOnes

;HexToDec255
; start in A
; end with Y = 100's, A = 10's, decOnes

;HexToDec999
; start with A = high byte, X = low byte
; end with Y = 100's, A = 10's, decOnes
; requires 1 extra temp register on top of decOnes, could combine
; these two if HexToDec65535 was eliminiated...

;HexToDec65535
; start with A = high byte, X = low byte
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes
; requires 2 extra temp registers on top of decTenThousand, decThousand, decOnes



Here's the fast routine:
Code:
;Hex to Decimal (0-65535) conversion
;by Omegamatrix
;
;HexToDec99     ; 37 cycles
;HexToDec255    ; 52-57 cycles
;HexToDec999    ; 72-77 cycles
;HexToDec65535  ; 157-162 cycles

ASCII_OFFSET = $00
temp         = decOnes
hexHigh      = temp2
hexLow       = temp3


Mult24Tab:
     .byte $00,$18,$30,$48,$60,$78,$90,$A8,$C0,$D8
     .byte $F0,$09,$21,$39,$51,$69,$81,$99,$B1,$C9
     .byte $E1,$F9,$12,$2A,$42,$5A,$72,$8A,$A2,$BA
     .byte $D2,$EA,$03,$1B,$33,$4B,$63,$7B,$93,$AB
     .byte $C3,$DB,$F3,$0C,$24,$3C,$54,$6C,$84,$9C
     .byte $B4,$CC,$E4,$FC,$15,$2D,$45,$5D,$75,$8D
     .byte $A5,$BD,$D5,$ED,$06,$1E

Mod100Tab:
    .byte 0,56,12,56+12

ShiftedBcdTab
    .byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
    .byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
    .byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
    .byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
    .byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C

HexToDec65535; SUBROUTINE
    sta    hexHigh               ;3  @9
    stx    hexLow                ;3  @12
    tax                          ;2  @14
    lsr                          ;2  @16
    lsr                          ;2  @18   integer divide 1024 (result 0-63)

    cpx    #$A7                  ;2  @20   account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
    adc    #0                    ;2  @22   we can just round it to $A700, and the divide by 1024 is fine...
    tay                          ;2  @24
    lda    Mult24Tab+1,Y         ;4  @28   could use LAX...
    tax                          ;2  @30
    and    #$F8                  ;2  @32
    adc    hexLow                ;3  @35
    txa                          ;2  @37
    and    #$07                  ;2  @39
    adc    hexHigh               ;3  @42
    ror                          ;2  @44
    lsr                          ;2  @46
    tay                          ;2  @48   integer divide 1,000 (result 0-65)

    lsr                          ;2  @50   split the 1,000 and 10,000 digit
    tax                          ;2  @52
    lda    ShiftedBcdTab,X       ;4  @56
    tax                          ;2  @58
    rol                          ;2  @60
    and    #$0F                  ;2  @62
  IF ASCII_OFFSET
    ora    #ASCII_OFFSET
  ENDIF
    sta    decThousands          ;3  @65
    txa                          ;2  @67
    lsr                          ;2  @69
    lsr                          ;2  @71
    lsr                          ;2  @73
  IF ASCII_OFFSET
    ora    #ASCII_OFFSET
  ENDIF
    sta    decTenThousands       ;3  @76


    ;at this point we have a number 0-65 that we have to times by 24,
    ;add to original sum, and Mod 1024 to get the remainder 0-999

    lda    Mult24Tab,Y           ;4  @80   could use LAX...
    tax                          ;2  @82
    and    #$F8                  ;2  @84
    clc                          ;2  @86
    adc    hexLow                ;3  @89
    sta    temp                  ;3  @92
    txa                          ;2  @94
    adc    hexHigh               ;3  @97
Start100s:
    and    #$03                  ;2  @99
    tax                          ;2  @101   0,1,2,3
    cmp    #2                    ;2  @103
    rol                          ;2  @105   0,2,5,7
  IF ASCII_OFFSET
    ora    #ASCII_OFFSET
  ENDIF
    tay                          ;2  @107   Y = Hundreds digit

    lda    temp                  ;3  @110
    adc    Mod100Tab,X           ;4  @114  adding remainder of 256, 512, and 256+512 (all mod 100)
    bcs    .doSub200             ;2³ @116/117

.try200:
    cmp    #200                  ;2  @118
    bcc    .try100               ;2³ @120/121
.doSub200:
    iny                          ;2  @122
    iny                          ;2  @124
    sbc    #200                  ;2  @126
.try100:
    cmp    #100                  ;2  @128
    bcc    HexToDec99            ;2³ @130/131
    iny                          ;2  @132
    sbc    #100                  ;2  @134
HexToDec99; SUBROUTINE
    lsr                          ;2  @136
    tax                          ;2  @138
    lda    ShiftedBcdTab,X       ;4  @142
    tax                          ;2  @144
    rol                          ;2  @146
    and    #$0F                  ;2  @148
  IF ASCII_OFFSET
    ora    #ASCII_OFFSET
  ENDIF
    sta    decOnes               ;3  @151
    txa                          ;2  @153
    lsr                          ;2  @155
    lsr                          ;2  @157
    lsr                          ;2  @159
  IF ASCII_OFFSET
    ora    #ASCII_OFFSET
  ENDIF
    rts                          ;6  @165   A = tens digit



HexToDec255; SUBROUTINE
  IF ASCII_OFFSET
    ldy    #ASCII_OFFSET         ;2  @8
    bne    .try200               ;3  @11    always branch
  ELSE
    ldy    #0                    ;2  @8
    beq    .try200               ;3  @11    always branch
  ENDIF

HexToDec999; SUBROUTINE
    stx    temp                  ;3  @9
    jmp    Start100s             ;3  @12



Here's the slower routine, which saves a lot of bytes:
Code:
;Hex to Decimal (0-65535) conversion
;by Omegamatrix
;
;HexToDec99     ; 37 cycles
;HexToDec255    ; 52-57 cycles
;HexToDec999    ; 72-77 cycles
;HexToDec65535  ; 178-186 cycles

ASCII_OFFSET = $00
temp         = decOnes
hexHigh      = temp2
hexLow       = temp3


Mod100Tab:
    .byte 0,56,12,56+12

ShiftedBcdTab
    .byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
    .byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
    .byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
    .byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
    .byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C

HexToDec65535; SUBROUTINE
    sta    hexHigh               ;3  @9
    stx    hexLow                ;3  @12
    tax                          ;2  @14
    lsr                          ;2  @16
    lsr                          ;2  @18   integer divide 1024 (result 0-63)

    cpx    #$A7                  ;2  @20   account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
    adc    #1                    ;2  @22   we can just round it to $A700, and the divide by 1024 is fine...

    ;at this point we have a number 1-65 that we have to times by 24,
    ;add to original sum, and Mod 1024 to get a remainder 0-999


    sta    temp                  ;3  @25
    asl                          ;2  @27
    adc    temp                  ;3  @30  x3
    tay                          ;2  @32
    lsr                          ;2  @34
    lsr                          ;2  @36
    lsr                          ;2  @38
    lsr                          ;2  @40
    lsr                          ;2  @42
    tax                          ;2  @44
    tya                          ;2  @46
    asl                          ;2  @48
    asl                          ;2  @50
    asl                          ;2  @52
    clc                          ;2  @54
    adc    hexLow                ;3  @57
    sta    hexLow                ;3  @60
    txa                          ;2  @62
    adc    hexHigh               ;3  @65
    sta    hexHigh               ;3  @68
    ror                          ;2  @70
    lsr                          ;2  @72
    tay                          ;2  @74    integer divide 1,000 (result 0-65)

    lsr                          ;2  @76    split the 1,000 and 10,000 digit
    tax                          ;2  @78
    lda    ShiftedBcdTab,X       ;4  @82
    tax                          ;2  @84
    rol                          ;2  @86
    and    #$0F                  ;2  @88
  IF ASCII_OFFSET
    ora    #ASCII_OFFSET
  ENDIF
    sta    decThousands          ;3  @91
    txa                          ;2  @93
    lsr                          ;2  @95
    lsr                          ;2  @97
    lsr                          ;2  @99
  IF ASCII_OFFSET
    ora    #ASCII_OFFSET
  ENDIF
    sta    decTenThousands       ;3  @102

    lda    hexLow                ;3  @105
    cpy    temp                  ;3  @108
    bmi    .doSubtract           ;2³ @110/111
    beq    useZero               ;2³ @112/113
    adc    #23 + 24              ;2  @114
.doSubtract:
    sbc    #23                   ;2  @116
    sta    hexLow                ;3  @119
useZero:
    lda    hexHigh               ;3  @122
    sbc    #0                    ;2  @124

Start100s:
    and    #$03                  ;2  @126
    tax                          ;2  @128   0,1,2,3
    cmp    #2                    ;2  @130
    rol                          ;2  @132   0,2,5,7
  IF ASCII_OFFSET
    ora    #ASCII_OFFSET
  ENDIF
    tay                          ;2  @134   Y = Hundreds digit

    lda    hexLow                ;3  @137
    adc    Mod100Tab,X           ;4  @141    adding remainder of 256, 512, and 256+512 (all mod 100)
    bcs    .doSub200             ;2³ @143/144

.try200:
    cmp    #200                  ;2  @145
    bcc    .try100               ;2³ @147/148
.doSub200:
    iny                          ;2  @149
    iny                          ;2  @151
    sbc    #200                  ;2  @153
.try100:
    cmp    #100                  ;2  @155
    bcc    HexToDec99            ;2³ @157/158
    iny                          ;2  @159
    sbc    #100                  ;2  @161

HexToDec99; SUBROUTINE
    lsr                          ;2  @163
    tax                          ;2  @165
    lda    ShiftedBcdTab,X       ;4  @169
    tax                          ;2  @171
    rol                          ;2  @173
    and    #$0F                  ;2  @175
  IF ASCII_OFFSET
    ora    #ASCII_OFFSET
  ENDIF
    sta    decOnes               ;3  @178
    txa                          ;2  @180
    lsr                          ;2  @182
    lsr                          ;2  @184
    lsr                          ;2  @186
  IF ASCII_OFFSET
    ora    #ASCII_OFFSET
  ENDIF
    rts                          ;6  @192   A = tens digit


HexToDec255; SUBROUTINE
  IF ASCII_OFFSET
    ldy    #ASCII_OFFSET         ;2  @8
    bne    .try200               ;3  @11    always branch
  ELSE
    ldy    #0                    ;2  @8
    beq    .try200               ;3  @11    always branch
  ENDIF

HexToDec999; SUBROUTINE
    stx    hexLow                ;3  @9
    jmp    Start100s             ;3  @12


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 26 posts ]  Go to page Previous  1, 2

All times are UTC - 7 hours


Who is online

Users browsing this forum: Bregalad and 8 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group