6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Thu Sep 19, 2024 11:52 pm

All times are UTC




Post new topic Reply to topic  [ 27 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Thu Apr 23, 2020 7:09 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
Most 6502 instructions either update or depend on the status register in some way. However, its effects can seem a bit arcane and unintuitive to the novice. In fact, there are definite patterns to this behaviour, which I hope to illustrate below. As with most aspects of the 6502's design, there are good reasons for most of it.

For the purposes of this discussion, I'll focus on the 65C02. The NMOS 6502 is the same, with a few instructions missing and a couple of minor quirks. The 65C816 is noticeably more complicated.

The first aspect to realise is that the status register does not hold a value - it holds six independent bits, laid out as NVxxDIZC, with the x's always reading as 1 if you push the status register on the stack (using PHP) and never being changed by any means. All the other bits can be restored from the stack by PLP or RTI. Aside from that, each bit is updated by its own set of circumstances, and has a different effect on the CPU's behaviour.

There is one other circumstance where the status register is pushed onto the stack - when entering an interrupt handler. In this case there is an additional "virtual" flag known as B, which indicates whether the interrupt was a hardware interrupt (when B is cleared) or a software BRK (when B is set). This "virtual flag" does not exist in the status register; it is simply manipulated while shoving it on the stack by the microcode that handles interrupts in the 6502. The only way to test it is to inspect the saved status byte on the stack. The proper technique is detailed elsewhere.

The N, V, Z, and C bits are arithmetic status flags, which are set by the ALU and can all be branched on. I'll save them for later, and get the others out of the way first.

The D bit controls the Decimal mode of the ADC and SBC instructions, and is changed using SED and CLD. On the 65C02, it is automatically cleared at Reset and on entry to any interrupt handler; on the NMOS 6502, you have to do that yourself. Most programs won't need or want to use Decimal mode, so just leave the D flag cleared. If you do want to enter the wonderful world of Binary Coded Decimal, there's a tutorial here.

The I bit is the Interrupt mask, which can be changed by SEI and CLI. When set, it prevents an /IRQ (interrupt request, active low) signal from entering the interrupt handler; this can be useful if you're doing something ticklish that should not be interrupted. However, the BRK instruction and /NMI (non maskable interrupt, active low) are not affected by this. The I bit is automatically set on entering an interrupt handler, and is automatically restored to clear (if appropriate) by a subsequent RTI - so don't use CLI just before RTI, it's not needed and might land you with recursive interrupt handling! There is much more about interrupts here.

Which brings us back to NVZC, the flags that you'll be working with most often. These are abbreviations for Negative, oVerflow, Zero, and Carry.

V is probably the least used of these four. You can test it using BVC (branch on V clear) or BVS (…set). It is altered only by ADC, SBC, BIT, and CLV (besides those restore-from-stack operations which can change all of them) - and notably not by CMP, CPX or CPY, and there is no SEV instruction. For ADC and SBC, it indicates whether a signed overflow occurred, so that the two's complement sign bit of the result byte differs from the correct result of adding (or subtracting) two signed bytes. For BIT, it simply reflects bit 6 of the value tested in memory - except the immediate form, which updates only Z.

C is the other flag that is altered only by a minority of instructions. It can be tested using BCC or BCS, changed directly by CLC and SEC, is both an input and output of ADC, SBC, ROL, and ROR, and is an output only of CMP, CPX, CPY, ASL, and LSR. Because it's affected by relatively few instructions and is easy to set to either state, it is often used as an error flag from subroutines. Important to note: C is not affected by INX, INY, INC, DEX, DEY, DEC.

N and Z are both updated by any instruction that either performs an ALU operation, or loads data into A, X, or Y, including transfer instructions between registers (except TXS, whose destination is the stack pointer). They are left alone by store instructions. The associated branch instructions are not so clearly named as for V and C: BEQ (branch if equal) tests for Z set, BNE (branch if not equal) for Z clear, BMI (branch if minus) for N set, BPL (branch if plus) for N clear.

The general rule is that N receives bit 7 (the two's complement sign bit) of the result, and Z is set if the full 8-bit result is zero, and cleared otherwise. The only exceptions are TRB, TSB, and the immediate form of BIT, which update only Z and leave N (and V) alone. For other addressing modes of BIT, N reflects bit 7 of the value tested in memory, regardless of whether it was masked; Z reflects whether the masked value was zero.

You will often want to use the status flags to compare values. There's a tutorial about that.

So let's have some example code:
Code:
; Add two 16-bit unsigned numbers
  CLC         ; must initialise Carry before ADC; there is no ADD instruction without a Carry input.
  LDA i+0     ; low byte of first operand
  ADC j+0     ; low byte of second operand; C now reflects Carry out of this partial sum
  STA k+0     ; low byte of result
  LDA i+1     ; high byte…
  ADC j+1     ; Carry used here, new Carry generated
  STA k+1
  BCS overflow  ; if this branch is taken, the sum didn't fit in 16 bits.

; Add two 16-bit signed numbers
  CLC         ; must initialise Carry before ADC; there is no ADD instruction without a Carry input.
  LDA i+0     ; low byte of first operand
  ADC j+0     ; low byte of second operand; C now reflects Carry out of this partial sum
  STA k+0     ; low byte of result
  LDA i+1     ; high byte…
  ADC j+1     ; Carry used here, new Carry generated, V flag now reflects overflow into sign bit
  STA k+1
  BVS overflow  ; if this branch is taken, the sum didn't fit in 15 bits plus sign bit.

; Subtract two 16-bit unsigned numbers
  SEC         ; must initialise Carry before SBC; there is no SUB instruction without a Carry input.
  LDA i+0
  SBC j+0
  STA k+0
  LDA i+1
  ABC j+1
  STA k+1
  BCC underflow  ; if this branch is taken, there was a borrow from beyond the highest bit - so the result is negative

; Compare two 16-bit unsigned numbers
  LDA i+1     ; the high bytes are most significant
  CMP j+1     ; unlike SBC, CMP does not use the Carry flag as an input
  BNE :+      ; only if the high bytes are equal should we test the low bytes
  LDA i+0
  CMP j+0
: BEQ equal   ; the real tests begin here
  BCS i_greater  ; this test requires the BEQ preceding it to be accurate, otherwise it reflects (i >= j) instead of (i > j).
  BCC j_greater  ; this test can be used alone for (i < j)

; execute a task ten times
  LDX #10
: NOP         ; replace this with the task
  DEX         ; this automatically sets N and Z with the result of the decrement
  BNE :-

; alternative, with increasing index
  LDY #0
: NOP         ; replace this with the task
  INY
  CPY #10     ; unlike the previous example, this clobbers the Carry
  BNE :-

; check if a 16-bit value is zero
  LDA i+0
  ORA i+1
  BEQ zero
  BNE nonzero

; test a status flag in the most-significant bit of a hardware register
  BIT register
  BMI flag_set
  BPL flag_clear

; test a status flag in the least-significant bit of a hardware register
  LDA #1
  BIT register
  BNE flag_set
  BEQ flag_clear

; multiply a 16-bit value by 2
  ASL i+0     ; inserts a 0 bit at the least-significant end, and pushes the most-significant bit into the Carry
  ROL i+1     ; inserts the Carry into the least-significant end, and pushes the most-significant bit into the Carry
  BCS overflow  ; the result doesn't fit in 16 bits
Hopefully this clears up at least some confusion…


Top
 Profile  
Reply with quote  
PostPosted: Mon Apr 27, 2020 3:43 pm 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 746
Location: Germany
Very helpful, though i still had to look up more info online to finally completely understand the flags (atleast i think so).

about the V Flag:
Chromatix wrote:
For ADC and SBC, it indicates whether a signed overflow occurred, so that the two's complement sign bit of the result byte differs from the correct result of adding (or subtracting) two signed bytes.


the small detail i missed about this is the fact that both input numbers must have the same sign, otherwise the V Flag will never be set.
for example:

80 + 80 = 160

because 160 < 256, the Carry flag will not be set as the number can still fit inside an unsigned 8 bit number.
but 160 > 127, so the Overflow flag will be set as the number cannot fit inside a signed 8 bit number.

basically the Oveflow flag will be set when the result of a signed operation (Addition/Subtraction) is larger than 127 or smaller than -128.
and the Carry flag will be set when the result of an unsigned Addition is larger than 255, or be cleared when the result of an unsinged Subtraction is smaller than 0.

and this is also something confusing about the 6502. the Carry flag is sort of reversed for subtraction.
the way the 6502 (probably) does subtration is by negating one of the input numbers and then just adding that to the Accumulator.
for example, here normal binary subtraction:

Code:
  00000101 (5)
- 00000010 (2)
--------------
= 00000011 (3)


now lets do that with addition, but first the second number needs to be negated.
in order to negate a number to it's 2's complement counterpart you need to invert all of the bits and add one to it.
the "add one" is important because if you don't do it it's the same as subtracting 1 from the result.
so for 2 the process would be like this:

Code:
  00000010 (2)
! 11111101 (-3) (unsigned 253)
+ 00000001 (1)
--------------
= 11111110 (-2) (unsigned 254)


and now it can be used in the addition:

Code:
  00000101 (5)
+ 11111110 (-2) (unsigned 254)
--------------
= 00000011 (3)


2 things should be questioned here.

1. where the does the "add one" come from?
2. what about the Carry output?

for the first one it's quite simple, the Carry input is taken from the Carry flag.
this is the reason you need to set the Carry before doing a regular 8 bit subtraction.
as otherwise when it isn't set it would subtract 1 from the final result.

Code:
LDA #0
SBC #0
STA OUTPUT


OUTPUT will contain FF if the Carry was not set, or contains 0 if the Carry was set.

for the second one, i already said this at the top, but now it should hopefully be clear why.
the Carry gets set like normal when the result of an addition is larger than 255, in this case 5 + 254 = 259 (gets cut off to 3).
because that's what the ALU is actually doing, even though we input the numbers 5 and 2.
so for a result to overflow (or actually underflow) below 0 our second number needs to be larger than the first one.

Code:
  00000010 (2)
+ 11111011 (-5) (unsigned 251)
--------------
= 11111101 (-3) (unsigned 253)


so now we got a subtraction result that doesn't set the Carry flag, which means it's below 0 (-3 in this case).

honestly the unused flag should've been used to enable some kind of "pure" ADD/SUB mode where the carry doesn't get used as input for any Addition/Subtraction.

.

anyways i hope this cleared it up a bit more, it helped me atleast to understand the Overflow flag and how Subtraction actually worked on this thing.
obviously, if i did anything wrong let me know.


Top
 Profile  
Reply with quote  
PostPosted: Mon Apr 27, 2020 7:50 pm 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1948
Location: Sacramento, CA, USA
The use of borrow as ~carry in SBC and CMP was a rather unique "feature", at least at the time, but I believe it had simplifying implications in the ALU logic, whereby "SBC operand" could be directly replaced with "ADC ~operand", at least in binary mode. Two's complement isn't an expensive operation, but one's complement is even cheaper.

As has been stated before, ADD and SUB would have been great, but also would have gobbled up a lot of opcode space with all the cool addressing modes enjoyed by the other accumulator arithmetic and logic instructions. Careful planning can often eliminate the need for CLC or SEC, and to me that's part of the charm of squeezing "the last byte" out of a piece of code.

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Mon Apr 27, 2020 8:03 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
Was the use of C as not-borrow so unusual? It seems to me there are two ways to do it, and whichever way you come across first (as a programmer) will seem OK, and the other way might seem strange. As you note, the implementation might push the instruction set designer in one or other direction.


Top
 Profile  
Reply with quote  
PostPosted: Mon Apr 27, 2020 8:32 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
The 6800 family, at least, uses the Carry bit for both addition carry and as positive borrow. SBC is defined there as R = R-M-C. The 68000 does the same through the X bit, rather than the C bit.

The PDP-11 does things a little differently - it has separate instructions for adding and subtracting just the carry bit to the next higher word. But the sense of the carry seems to be that it is set on borrow.

The ARM does it the 6502 way, with the Carry bit being cleared after a borrow. This was probably influenced by Acorn's long experience of the 6502 by the time they were designing the ARM.

The PowerPC appears to follow suit. Here's the description of subfe (Subtract From, Extended), in typically verbose IBM-isms:
Adds the one's complement of the contents of a general-purpose register to another general-purpose register and then adds the value of the Fixed-Point Exception Register Carry bit and stores the result in a third general-purpose register.
The existence of addze (Add to Zero Extended) and addme (Add to Minus One Extended) instructions, for propagating just the carry or borrow into the next higher word, is also indicative. The PDP-11 has SBC (Subtract Carry) in place of addme.

But x86 follows the 6800, 68K and PDP-11's convention with SBB:
Adds the source operand (second operand) and the carry (CF) flag, and subtracts the result from the destination operand (first operand).

I think we see a general pattern that RISC-like architectures do it the 6502 way, while CISC-like architectures do it the other way!


Top
 Profile  
Reply with quote  
PostPosted: Mon Apr 27, 2020 9:38 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
I found wikipedia has a couple of short lists:
System/360, 6502, MSP430, ARM and PowerPC
vs
8080, Z80, 8051, x86 and 68k (among others)


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 28, 2020 1:30 am 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 746
Location: Germany
barrym95838 wrote:
As has been stated before, ADD and SUB would have been great, but also would have gobbled up a lot of opcode space with all the cool addressing modes enjoyed by the other accumulator arithmetic and logic instructions. Careful planning can often eliminate the need for CLC or SEC, and to me that's part of the charm of squeezing "the last byte" out of a piece of code.


that is why i said to make it use the unused bit in the Processor Status. it would just add 2 instructions to set and clear that bit to enable/disable ADD/SUB mode.


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 28, 2020 1:52 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1948
Location: Sacramento, CA, USA
Okay, but isn't that a zero-sum game, since ADD is CLC:ADC and SUB is SEC:SBC? What advantage could we gain with something like ENC or DIC?

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 28, 2020 3:39 am 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 746
Location: Germany
barrym95838 wrote:
Okay, but isn't that a zero-sum game, since ADD is CLC:ADC and SUB is SEC:SBC? What advantage could we gain with something like ENC or DIC?

the advatage is that you don't need to do it for every single ADD/SUB instruction you have.
you can just enable ADD/SUB at the start of your program once, and then leave it like that for the rest of the program. saving you memory for every following Addition/Subtraction.

and if you have a function that adds 2 16 bit numbers or something you can just switch back to the ADC/SBC mode at the start of the function, and then back to the ADD/SUB mode at the end of the function.
it would still be more memory efficent than the way it's usually done with a SEC or CLC at the start of every Add/Subtract.


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 28, 2020 4:01 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8509
Location: Southern California
Proxy wrote:
and if you have a function that adds 2 16-bit numbers or something you can just switch back to the ADC/SBC mode at the start of the function, and then back to the ADD/SUB mode at the end of the function.
it would still be more memory efficient than the way it's usually done with a SEC or CLC at the start of every Add/Subtract.

That would make the code even longer in many situations; for example
Code:
        DIC                    ; DIsable use of the Carry flag.
        LDA   <variable1_lo>
        ADD   <variable2_lo>
        STA   <variable1_lo>
        ENC                    ; Enable use of the carry flag.
        LDA   <variable1_hi>
        ADD   <variable2_hi>
        STA   <variable1_hi>

where as it is with what we already have, the DIC is CLC instead, and the ENC goes away. For the next 16-bitter, you'd have to repeat. You can't just leave it with C enabled and not do the SEC/CLC.

Some have advocated having separate instructions; but that would take up a lot of room on the op-code table, room that the 65816 needed for its added instructions and addressing modes. ADC and SBC each have 8 op codes for the NMOS 6502, 9 for the CMOS 65c02, and 15 for the 65816.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 28, 2020 4:25 am 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 746
Location: Germany
GARTHWILSON wrote:

That would make the code even longer in many situations;

where as it is with what we already have, the DIC is CLC instead, and the ENC goes away. For the next 16-bitter, you'd have to repeat. You can't just leave it with C enabled and not do the SEC/CLC.

Some have advocated having separate instructions; but that would take up a lot of room on the op-code table, room that the 65816 needed for its added instructions and addressing modes. ADC and SBC each have 8 op codes for the NMOS 6502, 9 for the CMOS 65c02, and 15 for the 65816.


it only becomes efficent if you have 8 bit addition/subtraction. for any 16 bit stuff it would add a few bytes at the start/end of the function.

and true, adding non-carry versions of the regular 8 bit ADC/SBC would be a waste of opcodes, like you said.
it would be better to use those otherwise wasted opcodes to add 16 bit versions of ADC and SBC. so that even 16 bit code becomes more efficent with the DIC and ENC instructions.

though that is a whole nother can of worms i don't want to open right here.

back to the topic, are there any other oddities about the flags or other things missed?
anything trap like that could be weird to people getting into programming or emulating?


Last edited by Proxy on Tue Apr 28, 2020 4:57 am, edited 2 times in total.

Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 28, 2020 4:53 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8509
Location: Southern California
I guess you edited and shortened the post while I was writing, so I'll save the response to the removed part.

Quote:
yea adding more opcodes would solve that problem but as you said yourself it would take up a lot of space. 16 additional opcodes if i saw correctly. which would increase the 6502's instructions from 151 to 167. so technically there is more than enough space for that.

That would be ok for the '02 if the '816 never came along; but the '816 completely fills the op-code table, with not a single spot left over. It can run '02 code in the '02-emulation mode, but still offers additional instructions and addressing modes even without going into native mode. If we introduce an '02 with the extra ADD and SUB instructions with all the addressing modes, the '816 would not be able to run it (which may not matter for your goals, which is ok).

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 28, 2020 1:02 pm 
Offline

Joined: Thu Mar 12, 2020 10:04 pm
Posts: 702
Location: North Tejas
Chromatix wrote:
You will often want to use the status flags to compare values. There's a tutorial about that.



One thing I really miss about coming from the 680x is the full set of conditional branches.

BRA - Branch Always
BEQ - Branch if EQual
BNE - Branch if Not Equal
BLO - Branch if LOwer
BLS - Branch if Lower or Same
BHS - Branch if Higher or Same
BHI - Branch if HIgher
BLT - Branch if Less Than (signed)
BLE - Branch if Less than or Equal (signed)
BGE - Branch if Greater than or Equal (signed)
BGT - Branch if Greater Than (signed)

If you are comparing with a constant, you have a bit more leeway.

For example, to test for unsigned greater than, instead of

Code:
    cmp  #Value
    beq   Here
    bcs    There
Here


There


you can do

Code:
    cmp  #Value+1
    bcs   There


There


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 28, 2020 5:20 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8509
Location: Southern California
Aren't a lot of those just aliases? For example a lot of assemblers let you do BLT as an alias for BMI. You can do your code example on the '02 also. And the 65c02, starting in 1983, had the Branch Relative Always (BRA) instruction as well. The '816 has branch-long (ie, 16-bit offset) too.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 28, 2020 6:37 pm 
Offline

Joined: Thu Mar 12, 2020 10:04 pm
Posts: 702
Location: North Tejas
GARTHWILSON wrote:
Aren't a lot of those just aliases? For example a lot of assemblers let you do BLT as an alias for BMI. You can do your code example on the '02 also. And the 65c02, starting in 1983, had the Branch Relative Always (BRA) instruction as well. The '816 has branch-long (ie, 16-bit offset) too.


None of the ones I listed are aliases of each other. Those are the ones meaningful after a compare operation.

The others are:

BCC - Branch if Carry Clear - alias for BHS
BCS - Branch if Carry Set - alias for BLO
BMI - Branch if MInus
BPL - Branch is PLus
BSR - Branch to SubRoutine
BVC - Branch if oVerflow Clear
BVS - Branch if oVerflow Set

BLT is not the same as BMI. BLT also takes the overflow flag into consideration.

The 6809 also has long (16-bit displacement) versions of these.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 27 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 22 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: