A thread where I'm going to ask some math questions

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

User avatar
samophlange
Posts: 50
Joined: Sun Apr 08, 2018 11:45 pm
Location: Southern California

A thread where I'm going to ask some math questions

Post by samophlange »

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: Select all

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! :D
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: A thread where I'm going to ask some math questions

Post by rainwarrior »

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
Last edited by rainwarrior on Tue Oct 16, 2018 10:11 am, edited 1 time in total.
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: A thread where I'm going to ask some math questions

Post by dougeff »

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.
Last edited by dougeff on Tue Oct 16, 2018 3:20 am, edited 1 time in total.
nesdoug.com -- blog/tutorial on programming for the NES
Oziphantom
Posts: 1565
Joined: Tue Feb 07, 2017 2:03 am

Re: A thread where I'm going to ask some math questions

Post by Oziphantom »

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.
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: A thread where I'm going to ask some math questions

Post by dougeff »

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
nesdoug.com -- blog/tutorial on programming for the NES
Oziphantom
Posts: 1565
Joined: Tue Feb 07, 2017 2:03 am

Re: A thread where I'm going to ask some math questions

Post by Oziphantom »

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! :D
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.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: A thread where I'm going to ask some math questions

Post by unregistered »

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: Select all

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: Select all

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.
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: A thread where I'm going to ask some math questions

Post by rainwarrior »

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...
User avatar
Banshaku
Posts: 2417
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: A thread where I'm going to ask some math questions

Post by Banshaku »

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?.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: A thread where I'm going to ask some math questions

Post by koitsu »

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: Select all

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.
Oziphantom
Posts: 1565
Joined: Tue Feb 07, 2017 2:03 am

Re: A thread where I'm going to ask some math questions

Post by Oziphantom »

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.
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: A thread where I'm going to ask some math questions

Post by rainwarrior »

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.
Oziphantom
Posts: 1565
Joined: Tue Feb 07, 2017 2:03 am

Re: A thread where I'm going to ask some math questions

Post by Oziphantom »

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.
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: A thread where I'm going to ask some math questions

Post by rainwarrior »

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.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: A thread where I'm going to ask some math questions

Post by tokumaru »

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.
Post Reply