I'm working on writing some general-purpose math code. I'm starting with 16 bit unsigned integers, and then signed 16 bit, and then either 16 or 24 bit fixed point. I figured I'd breeze through the u16 stuff, but I've hit a few cases that I'm not sure how to handle.
The first thing I realized is that I don't know how to handle constants. Specifically, I don't know how to write code that can tell if a constant is a single byte vs two bytes and then treat it appropriately. Specifically, I mean declaring a constant like this:
Code:
SMALL_CONSTANT = $FF
LARGER_CONSTANT = $0101
MAX_CONSTANT = $FFFF
The second thing I realized is that it's up to me how to handle overflow / underflow when adding or subtracting numbers. My first thought is to make them "wrap correctly" like I would expect from C, but I'm wondering if that is worth it? It's some extra code, and at the end of the day it just generates a different "incorrect" value. I suppose you could also "clamp" them (underflows just become 0, overflows FFFF or whatever) but that doesn't really feel right. I guess this one is a bit of a value judgement, just curious how more experienced people handle it.
Cheers!
In ca65
expressions are evaluated as 32 bits, but will normally be implicitly converted to 8 or 16 bit size when you try to store them in specific places.
LDA $FF generates a LDA ZP instruction.
LDA $FFFF generates a LDA ABS instruction.
For labels it does that kind of selection based on whether the label value is known during that first pass assembly, or if it was known to belong to a zeropage segment.
You can also use address prefixes a: and z: to have the assembler check and enforce that a value fits before generating the instruction, instead of relying on the automated selection.
The < and > operators will take the low 8 bits or the next 8 bits of the expression (both produce an single byte result).
As for overflow, etc. it's generally the same two's complement as you're used to elsewhere, though there's some extra work attached to signed operations that doesn't apply to unsigned on 6502.
I find I'm constantly recommending this document for reference:
http://www.6502.org/tutorials/compare_beyond.html
Specifically, when you write the assembly line, you want to use a: or zp: or < or > to control how that line will assemble.
Lda #<MAX_CONSTANT
ldx #>MAX_CONSTANT
usually if I wanted an address in the zeropage, I would make a label with .res directives in a zeropage segment. Then references of that label will compile in the zeropage.
You mentioned math. What kind of math are you doing? Pointer math, or just adding 2 16 bit numbers?
Edit. Is it z: or zp: for zeropage addressing? I've seen examples of just using the < symbol and can't seem to find the letter symbol.
You can't write code to tell if a constant is 16 or 8 bit. The 6502 doesn't have anything but 8 bit, and it is up to the programmer to know if they need to perform a 16bit operation or an 8 bit operation.
So, I'm inclined to think it IS z: for forcing zero page. I couldn't find it in the documents, but comments like this...
https://cc65.github.io/mailarchive/2012-05/10300.html
samophlange wrote:
The second thing I realized is that it's up to me how to handle overflow / underflow when adding or subtracting numbers. My first thought is to make them "wrap correctly" like I would expect from C, but I'm wondering if that is worth it? It's some extra code, and at the end of the day it just generates a different "incorrect" value. I suppose you could also "clamp" them (underflows just become 0, overflows FFFF or whatever) but that doesn't really feel right. I guess this one is a bit of a value judgement, just curious how more experienced people handle it.
Cheers!
Normally I ensure that my maths won't overflow, If I'm making code that needs more than 16bits then I should use 24bit maths.. however the functions should return 'C' in the correct state so I can detect an overflow and treat it as needed in the calling code.
Oziphantom wrote:
You can't write code to tell if a constant is 16 or 8 bit. The 6502 doesn't have anything but 8 bit, and it is up to the programmer to know if they need to perform a 16bit operation or an 8 bit operation.
I think tokumaru mentioned that he uses
Code:
lda avariable+0
While the
+0 is pointless in code operation, it is super helpful when it is only used after the first byte in multibyte variables... makes it really easy to find sections of code using multibyte variables.
In variable declaration using asm6's
Code:
avariable .dsb 2
asmallervariable .dsb 1
it's easy to view that avariable is 16bit, especially when looking at your .lst file... if you are in the middle of the file, and using Windows, just press Ctrl+Home to instantly reach the very first character of the file. Your variable declarations should be near the top. To get back to where I was just have to enter the appropriate 16bit hex code in the Find box of my text editor.
After doing those a bunch it's really easy to know which variables are 8bit and which are 16bit, for me at least.
Hope this helps.
edit:
sta avariable+2 would store the accumulator's value into asmallervariable's memory location.
dougeff wrote:
Is it z: or zp: for zeropage addressing? I've seen examples of just using the < symbol and can't seem to find the letter symbol.
I've seen lots of people use < to force zero page addressing, but I don't recommend doing it that way.
z: will force it but also do a range check to make sure the value really fits in 8 bits. < will just silently discard the upper bits (hope they're zero).
So... if you know the value is less than 256, < is equivalent, but a habit of using z: will protect you against error. (e.g. what if you move a variable from ZP to elsewhere later on? with < you have to seek out all uses of it to fix them, with z: they'll produce an error for you.)
Yeah, I think the ca65 documentation neglects to document address prefixes, unfortunately. Someone might submit a ticket (or pull request) about that...
rainwarrior wrote:
I've seen lots of people use < to force zero page addressing, but I don't recommend doing it that way.
I saw that often in older code but the latest ca65 spit warning every time it saw them so I decided to remove such reference in the code samples I borrowed. I found them confusing too since it's the same operator used to extract low/hight byte so maybe this is why it was more or less removed for zp?.
I'm more inclined to say that forcing ZP addressing through syntax semantics is the backwards approach to take -- instead, default to ZP and forcing absolute/16-bit addressing through semantics. Getting a bit off-topic though.
I agree with unregistered's feelings about data byte definition sizes though; 1 vs. 2 makes it pretty obvious. But then again I'm also the "nut" who does the whole
Code:
var1 = $00
var2 = var1+1 ; byte
var3 = var2+1 ; byte
var4 = var3+2 ; word
var5 = var4+1 ; byte
...thing, depending on what I'm writing (*much*more common with disassembled works). *shrug* As covered in some other threads, every person has their own way of doing stuff based on whatever their experience is (both experience with things throughout 65xxx development, as well as experience level itself). I think there's pros and cons to pretty much every method out there, so I try not to harp too much on how others do it. :)
As for
< being used to force a ZP address, ex.
lda <$12 -- the
< operator is doing the exact same thing there as it would be for "extracting the low vs. high byte" for immediates, ex.
lda #<somevar. There's nothing special about it with regards to immediates vs. addresses. It's going to pick the low byte of the effective address.
Why are people talking about forcing ZP? I know this is NesDev but this is a giant leap in a single bound. He states and gives examples of CONSTANTS not VARIABLES.
koitsu wrote:
I'm more inclined to say that forcing ZP addressing through syntax semantics is the backwards approach to take -- instead, default to ZP and forcing absolute/16-bit addressing through semantics.
O_o? What 6502 assembler defaults to ZP and requires some explicit mechanism to use 16-bit addressing? I don't really understand how that would even be implemented.
I think, and personally I'm also of the option as to why these even needs to exist. He means when you do
*= $02
ZPTemp .byte ?
and then I do
LDA ZPTemp -> A5 02
The assembler just put in a ZP address, I don't need to force it to put in a ZP address, it just puts it in by default. However should I for timing of space reasons NEED a abs version I then do
LDA@w ZPTemp -> AD 02 00
So it defaults to ZP, and I override to ABS when needed. Rather than assume everything is ABS and ZP when somebody explicitly says ZP, to me that is backwards.
NESASM does everything ABS unless a < explicitly indicates ZP, which I would agree is wrong, but I think "backwards" is not the right way to describe this. Making everything ZP unless explicitly marked ABS would be equally wrong.
Actually I think it would be even more wrong. At least ZP instructions will still function the same way but slower if incorrectly assumed as ABS. The opposite is not true. You're now mandating annotations of probably most memory accesses in a program just for it to function?? That's what I mean that I don't understand how this would be implemented.
We're talking about ca65 though. ca65 will reduce anything with known 8-bit sized value to ZP, and assume ABS wherever it is unknown (or known to be larger), but either assumption can be explicitly overridden by z: or a: if needed (generally only rarely). This seems almost ideal to me, except for the fact that ca65 is single pass, so anything you want to auto-assume ZP has to be declared as ZP before the instruction in some way, of which there are many options:
- Using an explicit value.
- Placed in a segment that is marked zeropage.
- < or > operator result is correctly assumed to be byte sized.
- Imported as a zeropage symbol (.importzp)
- A z: prefix on use.
In my experience this produces the "right" thing with minimal fuss, as long as you keep the single pass concept in mind when working. With conventions and habits that remember to declare variables above their use, the issue of selecting operand size almost entirely disappears, and the z: and a: prefixes give you safe ways to override it with range checking that will tell you if you made a mistake!
(I don't offhand know how ASM6 does things, and its documentation doesn't seem to explicitly state what it does.)
Anyhow, sorry this seems to be quite a digression. The discussion was talking about appropriate practice for using < or z: etc. because the question was how does ca65 know about sizes of values. What a theoretical assembler should do is kind of a completely different problem.
rainwarrior wrote:
(I don't offhand know how ASM6 does things, and its documentation doesn't seem to explicitly state what it does.)
ASM6 uses ZP addressing whenever possible (i.e. address < $0100), and since it's multi-pass, labels don't need to be known before they're used to be ultimately treated as ZP. What ASM6 lacks is a way to force absolute addressing.
Did anyone really suggest ZP addressing being the default, even for addresses above $00FF? I thought they were just ZP addressing should be there default for addresses below $0100, as is the case in most assemblers, NESASM being the notable exception.
rainwarrior wrote:
Anyhow, sorry this seems to be quite a digression. The discussion was talking about appropriate practice for using < or z: etc. because the question was how does ca65 know about sizes of values. What a theoretical assembler should do is kind of a completely different problem.
I can talk about non-theoretical assemblers if you want, and give actual screenshots of concrete documentation showing exactly what they do. But maybe not here?
This general subject comes up (re: operand lengths + addressing modes, and how the assemblers decide which to use, and how they "determine lengths") every few months. It will continue to come up as long as we have new people coming into the scene and learning how assemblers behave.
Well, one way I could interpret what you said that would make sense to me is:
"I want all instruction pnemonics to have an explicit operand size (suffix), and I want the shortest pnemonic (no suffix) to be ZP size."
Is that what you meant by default? Like I can understand the desire for an assembly syntax with only explicit operand size and no assumptions. It definitely wouldn't be my preference, at least not for 6502, but I could understand someone wanting that.
(I guess NESASM is technically that but with ABS as the base pnemonic and < as an operand prefix instead of an instruction suffix... it has other problems encircling the issue, but it is at least explicit in this respect.)
If you want to show examples, I wouldn't mind, but really I just wanted to know what you meant, the question of "what assemblers do X" was just a means to try and understand that via example, if one exists.
tokumaru generally summed up what I meant. I've done a large write-up about all of this verbosely, but decided to save it to a txt file for a rainy day, or more likely a wiki page.
Forgive me, but the statement that confused me was: "forcing ZP addressing through syntax semantics is the backwards approach to take -- instead, default to ZP and forcing absolute/16-bit addressing through semantics."
That's all I was asking for clarification for, and I'm not quite sure what kind of answer you would have typed out that needs to be a large write up... I wasn't really asking for you to prove that some assembler does it, I just asked in case an example would help.
"forcing ZP addressing through syntax semantics"
I have a hard time understanding which methods they're referring to. Are you describing an assembler, or programming convention, and if it's a programming convention is it for ca65 or are you talking about many assemblers at once? Is this about < as a ZP forcing prefix? Is this about z:? Is this about : zeropage segments? I don't know which ones are covered by the statement, and that's what I was hoping you'd clarify.
"default to ZP and forcing absolute/16-bit addressing through semantics"
How do you force it through semantics? Instruction suffix? a: prefix? What does "default" mean in this context? Again not sure if talking about how you want an assembler to behave, or how a programmer should write code, or whether this is for ca65 or if you're talking about something more general.
I was genuinely confused by both halves of this statement, and really I just wanted to know what you were trying to express with it. It might sound like I was trying to shoot it down, maybe the way I talk comes across like that (apologies, if it does) but if you had something good to share in there I was hoping I could get you to spell it out with my questions, because I really couldn't parse that sentence as it was. I've made two guesses as to what you meant and you haven't really directly confirmed if either of those guesses were correct, but it sounds like not? (Tokumaru's post doesn't really make the connection to your sentence for me, either, sorry.)
...and if it really is a thing that requires too much effort to explain, or you don't want to explain for whatever reason, that's OK too, but it feels like maybe you felt I was pushing you in a way that I did not at all hope to.
Okay, here's the short version:
"Default to ZP and forcing absolute/16-bit addressing through semantics" means:
Assuming the addressing mode in question supports ZP addressing: if the calculated effective address is $0000 to $00ff, then use ZP addressing with the low byte of the effective address. Example: for lda $00ee, assemble to a5 ee. The only exception to this rule is if the user explicitly specifies they wanted absolute/16-bit addressing through syntactical sugar; common prefixes are ! or a:. Example: lda !$00ee would assemble to ad ee 00.
"Forcing ZP addressing through syntax semantics" could mean anything because it depends on the assembler. I intentionally worded this nebulously because "syntax semantics" vary per assembler. That said: I'll give you two examples (one I know of factually, the other might be true but I haven't checked): 1) using < to refer to the low byte of an effective address when combined with some opcodes could result in ZP addressing, ex. lda <$12ee might assemble to a5 ee, and 2) z: prefix described earlier (if that's true).
I would love to see a non-obfuscated example (meaning: I don't want to see 20 lines of macros and weird variable equate magic and complex stuff) where defaulting to 16-bit/absolute addressing, when the effective address is $0000 to $00ff, is preferred. I'll add that at least one Apple II assembler (ORCA/M) actually does this (defaults to absolute), HOWEVER, that's an assembler intended for 65816 (using it to write 6502 actually requires you to specify "sizes" on a lot of operands solely for that reason). ORCA/M's manually actually outlines very very clearly what the assembler does with all the different syntaxes, in a nice chart, that spreads across 2 or 3 pages. (My aforementioned txt file was basically a version of this but WRT 6502 and what we're discussing here; there are several effective addresses that could be assembled multiple ways, which I noted as "difficult" or "I can't decide").
To clarify the difference between < and z: and a: in ca65:
< takes the low byte of the operand. Will produce a ZP instruction, but there's no protection if your label actually needed ABS.
z: verifies that the operand is a ZP address, and throws an error if it doesn't. It's verification of correctness, not forcing.
a: promotes a ZP operand to ABS size, or keeps an ABS operand at the same size. (i.e. for when you don't want the normal behaviour of assuming ZP where it can be assumed.)
So I guess ca65's behaviour is actually what you were describing, as long as you're not using < to mean "ZP instruction", which a lot of people were used to doing from NESASM, and I think is not a good habit to cultivate, since z: instead will help protect against errors. ...but in general there's very little need to use z:, it's really just a tool for checking that you're really getting the addressing you want in the cases where it matters. (< is a good operator for its purpose of taking a low byte, but not a substitute for z:, IMO.)
Looking back I realize OP didn't explicitly mention ca65, but I knew previously that its what they're working with, so maybe that was a strongly assumed context on my part that others didn't have.
samophlange wrote:
The first thing I realized is that I don't know how to handle constants. Specifically, I don't know how to write code that can tell if a constant is a single byte vs two bytes and then treat it appropriately.
Perhaps the following thoughts help those who aren't very experienced with assemblers. It may be off topic or self-evident to you, but I didn't really stop to think about it until quite recently. Note: I haven't really studied compiler/assembler theory.
Most assemblers make no distinction between what the programmer calls "variables", "labels" or "constants". They are all just constants - they have a name and a value. (Edit: they have different syntax but I think they're internally the same.)
- "Constants" are simple: the value is just a value and nothing else.
- The value of a "label" is an address; the assembler knows both the address and what's stored there (assuming ROM and no bankswitching).
- The value of a "variable" is an address, too, but the assembler only knows the address, not what's stored there (because the assembler does not simulate what the 6502 program does).
The assembler is capable of very complex arithmetic but only at assembly time; at run time, the 6502 won't and can't distinguish the program from a manually-written one.
rainwarrior wrote:
z: verifies that the operand is a ZP address, and throws an error if it doesn't. It's verification of correctness, not forcing.
It is forcing if the address can't be calculated at the time the instruction is assembled. Kinda. It'll still throw an error if the address is ultimately found to not fit in 8 bits, of course.
I think I've only understood half of what has been talked about in this thread, but I still know twice as much as I did before!
qalle wrote:
Most assemblers make no distinction between what the programmer calls "variables", "labels" or "constants". They are all just constants - they have a name and a value. (Edit: they have different syntax but I think they're internally the same.)
Yeah, the sticking point for me is that the number of bytes used for a variable changes how you work with the variable, (and maybe how you interpret the value if it is signed), but constants don't have a "size" that you can determine apart from their value. This is compounded by the fact that I'm both new to the language and also the capabilities of ca65.
I tried two approaches that I thought would work, but resulted in an error. Then I tried another approach that I assumed would give me an error but actually worked.
This yielded the error "Error: Constant expression expected"
Code:
tempw0: .res 2
CONSTANT_1 = 1
.macro u16_set_from_constant setvar, constant
.if (constant >= 0 && constant <= 255)
lda #<constant
sta setvar+0
lda #$00
sta setvar+1
.elseif (constant >= 255 && constant <= 65535)
lda #<constant
sta setvar+0
lda #>constant
sta setvar+1
.else
.error "constant is out of range"
.endif
.endmacro
; macro called from some unit test subroutine
u16_set_from_constant tempw0, CONSTANT_1
This approach gave the error "Error: Size of `CONSTANT_1' is unknown"
Code:
tempw0: .res 2
CONSTANT_1 = 1
.macro u16_set_from_constant setvar, constant
.if (.sizeof(constant) == 1)
lda #<constant
sta setvar+0
lda #$00
sta setvar+1
.elseif (.sizeof(constant) == 2)
lda #<constant
sta setvar+0
lda #>constant
sta setvar+1
.else
.error "constant is out of range"
.endif
.endmacro
; macro called from some unit test subroutine
u16_set_from_constant tempw0, CONSTANT_1
This one worked fine, somewhat to my surprise. I expected using ">" with a constant that wouldn't logically be a 2 byte value to result in a compile time error or some garbage values at runtime, but it works.
Code:
.macro u16_set_from_constant setvar, constant
lda #<constant
sta setvar+0
lda #>constant
sta setvar+1
.endmacro
So I guess in this case ca65 knows that there is no "value" in the "high byte" and generates a value of $00 for the immediate mode operation. Maybe that should have been obvious to me, but I thought I was telling it to do something dumb and expected it to just let me shoot myself in the foot. But it works!
All of
ca65's expressions have 32-bit size, so they always have a 2nd byte accessible by >. (They also have a 3rd and 4th byte.
.bankbyte gets the 3rd byte.) Your last macro is IMO the "correct" way to do this.
The macro system is a bit weird in a lot of ways. Notably it's not a preprocessor; the macro gets tokenized and that stream of tokens is substituted instead, and there's a lot of strange restrictions about what needs to be known when. (Also be careful about parentheses in macros, since those have some additional meaning in the assembly context. {} can sometimes be used instead. Some examples
here.)
The
.sizeof directive doesn't really intuitively map to how it works in C, and probably doesn't work on constants at all.
I'm not sure what's going on with the first attempt though. (The error isn't even necessarily about one of the lines with "constant" in it. I personally find it really hard to diagnose problems with ca65 macros; usually involves a lot of removing things until you find the line that caused the error.)
samophlange wrote:
constants don't have a "size" that you can determine apart from their value.
In some type systems, they do. In the C language,
(uint8_t)5 and
(uint64_t)5 have different sizes.
As rainwarrior mentioned,
.sizeof doesn't do what you expect.
.addrsize, available in fairly recent ca65, might be closer. There are also some peculiar scope resolution restrictions for accessing top-level constants within a named scope (
.scope or
.proc): you'll often need to use
::symbol to force use of the top-level symbol rather than a shadowing symbol of the same name that ca65 thinks may be defined later in the scope.
I've been making progress getting some general purpose math routines in to the game. My current goal is to convert one object over to use signed 24bit fixed point values for position and velocity to get more natural looking movement and acceleration, and use that to get the hang of this stuff. Writing the routines for this, I'm realizing that there will be situations where I have a result that is zero, but still has the sign bit set to negative. I know that with the 8-bit operations there is no such thing as "negative zero". When I'm dealing with multi-byte values, should I detect this situation and clear out the sign bit after an operation? For folks who have been down that road, have you had weird bugs tracked down to "negative zero" causing otherwise sound logic to go haywire?
I'm basically not sure if I'm worrying about nothing.
(I guess thinking about representations of zero is inherently worrying about nothing, but you get the drift).
Huh I think you're confused about something.
There's no such thing as "negative zero" in multi-byte numbers either. A one followed by all zeroes is equal to -(2^(N-1)), where N is the number of bits. It works the same as 8-bit values.
Yeah, there's no negative 0 in two's complement notation, which is what the 6502 uses for signed numbers. You're probably doing something weird somewhere.
Yeah good call, I was a bit confused. I was thinking of a "divide by 2" situation where you don't shift the sign bit, but instead preserve it. Something like this: (assume some_val is a 24 bit signed variable)
Code:
lda @some_val+2
cmp #%10000000 ;copy bit 7 to carry to preserve sign
ror @some_val+2
ror @some_val+1
ror @some_val+0
Thinking it through again, in the case of a negative number, it looks like this would get down to -1 and then never decrease any more, while the same operations on a positive number would eventually get all the way down to 0.
Yeah, an arithmetic right shift will shift the most significant bit right, but also preserve it as the most significant bit.
When used for division, an arithmetic right shift will round the result down, towards negative infinity, not towards zero.
If you try to divide -3 by 2, the correct result is -1.5, but in two's complement that gets rounded down to -2. The same happens when you divide -1 by 2, which should be -0.5, but that gets rounded down to -1 and you're stuck with -1 no matter how much you keep shifting... there will never be negative 0.
tokumaru wrote:
Yeah, an arithmetic right shift will shift the most significant bit right, but also preserve it as the most significant bit.
This instruction doesn't exist on the 6502. There is "arithmetic shift left" (ASL) and "logical shift right" (LSR), both of which are the same as rotate (ROL/ROR) if carry was zero.
There is no sign preserving right shift, AFAIK, you have to manually move the sign to carry before starting with ROR. Samophlange's LDA/CMP is a valid way to load carry with the sign. Could also LDA/ROL for the same result in one less byte.
illegal opcode ARR #$FF is pretty close to being a sign-preserving right shift. At least when doing multiple shifts it is.
To round up, add N-1 to the value before shifting, where N is the amount you're dividing by.
rainwarrior wrote:
This instruction doesn't exist on the 6502.
I didn't say it did, I was just explaining how the operation he implemented in several 6502 instructions worked.
pubby wrote:
illegal opcode ARR #$FF is pretty close to being a sign-preserving right shift. At least when doing multiple shifts it is.
Just to fully clarify, ARR appears to be:
1. Overflow (V) is set as if an ADC of: (A & #immediate) + #immediate ?
2. Result is: AND A with #immediate, followed by ROR
3. Carry (C) is set from bit 6 of result.
4. Zero/Negative (Z/N) from result.
So you still need to load the carry with sign before starting the shifts, but if you're doing multiple shifts on a single byte ARR #-1 can keep putting the sign back into the carry after each shift.
Apparently only helps if you need to shift more than once. Looks like this:
Code:
; value to be shifted already in A
CMP #$80 ; load sign into carry
ARR #$FF ; signed /2
ARR #$FF ; signed /4
ARR #$FF ; signed /8
ROR ; signed /16
; note that the last shift in the chain can be optimized as a ROR to save a byte.
(ARR is illegal opcode $6B.)
We had basically the same conversation on 6502.org, at
http://forum.6502.org/viewtopic.php?f=2&t=5317 . Various code suggestions were made there.
Thanks to all the folks in this thread (and re-reading that article on 6502.org several times) I've got some good general purpose math routines and I'm slowly changing some game objects over to have some nicer movement characteristics.
Is there a clever/compact way to see if two variables have the same sign? The only way I can think of involves ANDing with %10000000 into two temporaries and comparing those, but it seems like there should be some way to use the bit 7 "negative flag".
AND, ORA, and EOR all set the sign flag with bit 7 of the result, which can be useful for stuff like this.
Comparing value in A to variable "v":
Code:
EOR v
; sign bit will be 0 if the signs match, 1 if they differ
AND v
; sign bit will be set if both signs are negative
ORA v
; sign bit will be clear if both signs are positive
It's very often useful to store bit flags in the high bit, for this reason. A lot of instructions have "easy" access to it. (Also note that ROR/ROL make it easy to pass the the sign in/out of the carry flag too.)
Bit 6 is also convenient just for the BIT instruction, which can test it directly and puts the result in the overflow flag V... which is weird and special to that one instruction, but pretty handy especially for testing some hardware registers.
Bit 6 and 0 are only slightly less convenient than bit 7. Even in indexed or indirect contexts, where you can't bit, you can still send bit 6 to the N flag using asl a or bit 0 to the C flag using lsr a.
Just to get a little meta, it might be worth looking into the overflow flag if you're not super familiar with it. Some cases where I used to want to check for same sign were helped by better understanding of the overflow flag. That's not to say using the eor is bad, just that there might be something better for the longer term goal sometimes.
samophlange wrote:
Is there a clever/compact way to see if two variables have the same sign? The only way I can think of involves ANDing with %10000000 into two temporaries and comparing those, but it seems like there should be some way to use the bit 7 "negative flag".
You don't need to isolate the bits or use any temporaries for this. Just EOR one value with the other directly and the N flag of the result will give you the answer. The result of EOR is 0 if both bits are equal, or 1 if they differ. So you can simply do this:
Code:
lda Variable0
eor Variable1
bmi DifferentSigns
SameSign:
;(...)
DifferentSigns:
;(...)
Bitwise operations like EOR, AND and OR between two values only operate in bits at the same position in their respective bytes, so you don't need to isolate the bits of interest out of fear that the other ones might influence the result in any way. You only need to isolate the result, if the CPU doesn't do it for you automatically (as it does by copying bit 7 to the N flag).