1

I have recovered an old 6502 emulator I did years ago to implement some new features. During testing I discovered something wrong, surely due to an error in my implementation.
I have to loop through a 16 bit subtraction until the result is negative: quite simple, no? Here is an example:

V1 equ $90  
V2 equ $01  
Label:
 sec  
 lda V2  
 sbc #2  
 sta V2  
 lda V1  
 sbc #10  
 sta V1   
 "Branch" to Label if result is >0, otherwise exit  
 rts  

Now, the problem is to identify which branch to select or to find a different solution. The BCS is not valid if the V2 subtraction clears the carry.
The BPL in not valid if V1 is 'negative' (>$80).
It should be easy, but...

EDIT
I did not find in the aswers a real solution.
Let me try to follow the logic, firstly with the original values as in code.

  1. Carry is set by SEC
  2. The fist sub (1-2) clears the carry. V1 = $FF
  3. the second sub ($90-$0A-1 (not borrow)) results in V2=$85
  4. carry is cleared; result ($85FF) is still negative)
    I can not test the result with BCS (to jump to label) nor BMI since V2 is negative.
    So?

With a different set, i.e. V1=$1 and V2=$0A I will have a result < 0, which is my goal to stop iterations.
Any suggestion?

Cactus
  • 27,075
  • 9
  • 69
  • 149
Orionis
  • 983
  • 7
  • 11
  • I would have to dig into the 6502 specifically but the carry bit does indicate a greater than or less than zero result. Normally 6 - 5 = 1 for example the result of the addition (subtract uses an adder) is the carry bit is SET. Some isas invert it into a borrow. so 5 - 6 should be the opposite, should only take a second to figure out which is which with the 6502, then if you want greater than or equal 5-5 = 0 should leave the carry flag in the same state as 6-5 (try it yourself) so if you want greater than or equal the carry flag is it. if you want less than, ditto, if you want... – old_timer May 23 '21 at 18:48
  • less than or equal then flip the operands as less than or equal is the same as not greater than. flip the operands and greater than or equal is what you check the flag for. – old_timer May 23 '21 at 18:49
  • 2
    if you are looking to see if the result is negative then the msbit of the result tells that answer. – old_timer May 23 '21 at 18:50
  • @PeterCordes he's setting carry on the third line, which I think is correct for a 6502, which implements SBC via a ones' complement of the operand rather than a two's? (Otherwise, I think old_timer has the right answer: `BPL`). – Tommy May 23 '21 at 20:46
  • 1
    "I did not find in the answers a real solution" Seeing your edit, I see what is the real problem: **You are confused about what are immediates and addresses** The *V1* and *V2* in your original code must be memory addresses. They have to be, how else could the CPU execute `sta V1` or `sta V2`? Remember that immediates are always prefixed by an #. – Sep Roland May 24 '21 at 17:40
  • I did not explain clearly what I have to obtain. This code is part of a Bin to Ascii conversion, made by power of ten subtraction. The bin value could be >$8000, so it is 'negative' but this does not matter. In the first iteration I sub 10000 each cycle until the result is 'below 0', then I restore the previous value and continue with the remainder. The problem is how to detect the 'below 0' condition as said in the post. I beg your pardon for the confused post. – Orionis May 25 '21 at 09:20
  • Ok, for integer -> string, you always want unsigned division. (After taking absolute value and saving the sign, if your input is actually signed). So you want to look at borrow from `x -= pow10` to detect when it wraps, not the MSB. – Peter Cordes May 25 '21 at 21:13
  • Please read my answer. If the numbers you are telling us are correct $90 - $0A -1 *will set the carry*. If the carry is cleared after that subtraction, there is a bug in the emulator you are using. Are you asking us to fix the bug in your emulator? If so, you'll need to post some code to show us how it is doing subtraction. – JeremyP Jun 14 '21 at 07:32
  • Thanks Jeremy, your answer is definitive and I voted it. To correct my emulator (written in VB6 !) is a very simple job of few minutes. I'm sorry if I badly explained the question, but doing in English is not so easy and honestly this forum (editor, rules and conventions) confuses me quite a lot. – Orionis Jun 15 '21 at 15:08

3 Answers3

3

Generally, a sequence like this (I've slightly amended the code in your question to make it more self contained)

V1 equ $91  
V2 equ $90  
 lda #00
 sta V2
 lda #20
 sta V1 
Label:
 sec  
 lda V2  
 sbc #2  
 sta V2  
 lda V1  
 sbc #10  
 sta V1   

will work as expected. With the values used in the code, the carry will be cleared after the first subtraction and will be set again after the second. If it is not, then the most likely cause is a bug in your emulator. I have run this in an online 6502 emulation and the carry was set by the time it got to the final sta V1. I also ran it in visual6502 and here is the trace output:


cycle ab    db  rw  Fetch   pc      a   x   y   s   p
0   0000    a9  1   LDA #   0000    aa  00  00  fd  nv‑BdIZc
0   0000    a9  1   LDA #   0000    aa  00  00  fd  nv‑BdIZc
2   0002    85  1   STA zp  0002    00  00  00  fd  nv‑BdIZc
4   0090    00  0           0004    00  00  00  fd  nv‑BdIZc
5   0004    a9  1   LDA #   0004    00  00  00  fd  nv‑BdIZc
7   0006    85  1   STA zp  0006    14  00  00  fd  nv‑BdIzc
9   0091    14  0           0008    14  00  00  fd  nv‑BdIzc
10  0008    38  1   SEC     0008    14  00  00  fd  nv‑BdIzc
12  0009    a5  1   LDA zp  0009    14  00  00  fd  nv‑BdIzC
15  000b    e9  1   SBC #   000b    00  00  00  fd  nv‑BdIZC
17  000d    85  1   STA zp  000d    00  00  00  fd  nv‑BdIZC
19  0090    fe  0           000f    fe  00  00  fd  Nv‑BdIzc
20  000f    a5  1   LDA zp  000f    fe  00  00  fd  Nv‑BdIzc
23  0011    e9  1   SBC #   0011    14  00  00  fd  nv‑BdIzc
25  0013    85  1   STA zp  0013    14  00  00  fd  nv‑BdIzc
27  0091    09  0           0015    09  00  00  fd  nv‑BdIzC

As you can see at the end, the carry is set (denoted by a capital C).

There is another problem with your code, by the way. If you have 2000 (decimal), then to load it into two locations, you need to convert it to hex before you slice it into two bytes. If you load 0 into a zero page location and 20 decimal into its successor, you've actually loaded 20 * 256 = 5120. The hex for 2000 is $07d0.


Following the latest edit to the question. Here is a simplified bit of code that subtracts $0a from $90 with the carry clear.

clc
lda #$90
sbc #$0a

And here is the Visual 6502 trace

cycle ab    db  rw  Fetch   pc      a   x   y   s   p
0   0000    18  1   CLC     0000    aa  00  00  fd  nv‑BdIZc
0   0000    18  1   CLC     0000    aa  00  00  fd  nv‑BdIZc
2   0001    a9  1   LDA #   0001    aa  00  00  fd  nv‑BdIZc
4   0003    e9  1   SBC #   0003    90  00  00  fd  Nv‑BdIzc
6   0005    ea  1   NOP     0005    90  00  00  fd  Nv‑BdIzc
8   0006    ea  1   NOP     0006    85  00  00  fd  Nv‑BdIzC

If your emulator leaves the carry unset in that situation, your emulator has a bug.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • Hi Jeremy, I see...if your trace is correct, for sure my emulator as a bug and my brain (memory) too. I was 100% sure that the second sub does not set to 1 a cleared carry after a succesful operation like in 10-5. Unfortunately I do not have anymore a real HW to test my emulator. With this info, the update of my emulator is really easy. As far as the second point is concerned, my emul takes a number preceeded by an # but without a $ as a decimal value and then convert it in hex in the code. I made it in this way to simplify the entry. The 2000 stored is actually $07d0 as you said. – Orionis Jun 15 '21 at 14:12
  • To Jeremy: I assume, for symmetry, that ADC has a similar behaviour. If the carry is set by a preceeding sum, the next one will reset it to 0 if the sum does not produces a new carry. Is it correct? – Orionis Jun 17 '21 at 13:30
  • @Orionis yes is the answer to your second comment. I'll let you into a secret: the 6502 cannot do subtraction. `SBC` is exactly the same as `ADC` except that it inverts all the bits of the operand before doing the addition. – JeremyP Jun 17 '21 at 19:03
  • @Orionis As for not having hardware, if you're writing an emulator, [visual6502.org](http://www.visual6502.org) is your first port of call. They created their emulation by reverse engineering a real 6502. It's guaranteed to be cycle correct. – JeremyP Jun 17 '21 at 19:08
1

loop through a 16 bit subtraction until the result is negative

"Branch" to Label if result is >0,

Do you see that these descriptions contradict each other?
The 1st one continues on 0, the 2nd one stops on 0.
Only you can decide which one is correct!


From a comment:

This code is part of a Bin to Ascii conversion, made by power of ten subtraction. The bin value could be >$8000, so it is 'negative' but this does not matter. In the first iteration I sub 10000 each cycle until the result is 'below 0', then I restore the previous value and continue with the remainder. The problem is how to detect the 'below 0' condition as said in the post

Do ... Loop While GE 0

Next example subtracts 10000 ($2710) from the unsigned word stored at zero page address $90. The low byte is at $90, the high byte is at $91 (little endian).

Lo equ $90       ; The 16-bit bin is stored at zero page address $90
Hi equ $91
      sec        ; Because SBC subtracts the complement of C
      ldx #-1
Label inx
      lda Lo     ; Load from a zero page address
      sbc #$10   ; Subtracting immediate $10
      sta Lo     ; Store to a zero page address
      lda Hi     ; Load from a zero page address
      sbc #$27   ; Subtracting immediate $27
      sta Hi     ; Store to a zero page address
      bcs Label  ; Result GE 0 (greater or equal)
      rts

The X register now contains how many times 10000 fitted in the original number.


Do ... Loop While GT 0

You can add an ora instruction to see if the resulting word has become negative. Then you branch based on the Z from the ora and the C from the (last) sbc.

Lo equ $90       ; The 16-bit bin is stored at zero page address $90
Hi equ $91
      sec        ; Because SBC subtracts the complement of C
Label lda Lo     ; Load from a zero page address
      sbc #$10   ; Subtracting immediate $10
      sta Lo     ; Store to a zero page address
      lda Hi     ; Load from a zero page address
      sbc #$27   ; Subtracting immediate $27
      sta Hi     ; Store to a zero page address
      ora Lo     ; To define Z for the whole word
      beq Done   ; Result EQ 0 (equal)
      bcs Label  ; Result GT 0 (greater)
Done  rts

My 6502 manual says: "Above all, remember that comparing is done with BCS and BCC (not BPL or BMI).

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
-1

I found a solution, cumbersone and inelegant, but effective:

           LDY #0
_B2D161    INY  
           PHA  
           TXA  
           SEC  
           SBC  #$10
           TAX  
           PLA
;----------------------  
           BCS  _BD1  ;normal flow
           SEC  ;set carry for the next sub
           SBC  #$28  ;the carry=0 would have incremented the minuend
           BCS  _B2D161  ;loop again
           BCC  _BD11  ;exit and restore the previous value
;-----------------------
_BD1       SBC  #$27  ;normal jump comes here
           BCS  _B2D161

_BD11      PHA  
           TXA  
           CLC  
           ADC  #$10
           TAX  
           PLA  
           ADC  #$27
           DEY  
           STY  _L10K
           ... continue with next section

The isolated section shows the variant applied: if the firtst sub clears the carry then this is 'emulated' by subtracting a valude increased by 1 (#28 instead of #27) and restoring the carry for the next sub.
In this way I can again rely on Carry to decide where to branch.

I wish to thank Sep Roland for the suggested code (the same that I originally used) which ubfortunatly does not do the job.

Orionis
  • 983
  • 7
  • 11
  • 1
    In the *isolated section* there is nothing that would not be covered by the hardware/emulator executing a cascaded subtraction. The whole idea of cascaded subtraction (or addition) is that the hardware takes care of intermediate carries/borrows and that you, the programmer, only have to inspect the final `C` flag. My end conclusion here would have to be that the emulator is faulty in the way it executes the `sbc` instruction. Most probably **the emulator forgets to even use the `C` flag** (let alone subtract the complement of the value of the `C` flag as it should). – Sep Roland May 27 '21 at 09:24
  • If you still have access to the emulator's source, you should inspect the `sbc` instruction's implementation. If my suspicion is right and you can correct the emulator, it will save you from writing these ugly workarounds... – Sep Roland May 27 '21 at 09:31
  • My goal is to subtract a power of 10 from the input vale until the result goes below 0. I wrote the emulator and it Does handle the carry flag. Let me give an example: Let's say that the imput value is $FF08 (65288)). The first sub, with carry set, subtract $10, giving $FEF8 and the carry is cleared. The second sub ($27xx) gives correctly $D7f8, but the carry is still 0 from the previous sub. The result is still higher than $2710 (10000) so I should continue the loop, but based on what? The carry is 0 and the result is 'negative': no way to use BCS or BPL. Am I doing wrong? – Orionis Jun 03 '21 at 13:54
  • If the code in @Sep's answer doesn't work in your emulator, but this answer does, it's likely your emulator is broken. You don't need to branch on the carry manually between the halves of a 16-bit subtraction, that's what `sbc` on the high half is for. flags should be set after the final `sbc` just like they would be after a normal 8-bit subtract, so any branch condition you'd want to use based on an 8-bit subtract should work the same after the high-half `sbc` of a 16-bit. But if you have unsigned numbers, BPL doesn't make sense; the compare result is in carry, not the MSB. – Peter Cordes Jun 04 '21 at 06:00
  • Thanks Peter, but I'still confused. If the first sub clears the carry, the second one does not restore it to 1, even if the first argument is greater than the second (i.e. $10 - $08) right? Maybe I'm wrong or possibly I have some difficult time in translating your comment. In my previous comment I used some number as an example, which is: $FF08-$2710, loop again if result is still >$2710. Where is the error? – Orionis Jun 06 '21 at 15:47
  • I didn't see your comment since you didn't @peter notify me. Only looked at this question since it had a new answer. But it looks like that "answer" should actually be an edit to the question, since it's asking a question and doesn't seem to be answering. I don't know 6502 that well, but I think its carry flag works a a not-borrow for subtraction, that's why you have to set it before an `sbc` to do a normal subtract without borrow input. So if `x - y` produces a borrow (carry flag cleared), `x < y` (And you should add it back in because your loop over-shot by one 16-bit subtract.) – Peter Cordes Jun 10 '21 at 15:04