Jump to content
IGNORED

Dopey Novice Questions


Recommended Posts

Wow, so learning 6502 is brutal, especially as applied to the 2600.  For one, it's a struggle to get a useable IDE up and running if you don't know what you're doing.  Also, it's basically impossible to get "Hello World", then grab some basic functions, figure out variables and control flow and start building from there.  Unfortunately, this is the only way I learn anything when it comes to programming: get a template to do a simple thing or two, start tinkering, and sit there with a reference to add more things I can do.

 

Finally, finally, I've started to be able to do this is Andrew Davie and Spiceware's tutorials (big thanks to both).  I took one of Davie's playfield exercises and modified it to scroll a single rainbow column of pf around the screen with the joystick (stupid PF0 won't let you just rotate the carry from one register to the next), and to scroll the rainbow pattern up and down.  I'm looking at the reference, and I can kind of wrap my head around what the flags do, how you might use a branch on carry to do the equivalent of an "if x >= y then" thing.  I can somewhat understand the implications of being able to flip and mask bits with ANDs and ORAs.  Things are clicking a little bit, but clearly I still have very little knowledge or understanding on a technical level, and some of these things I don't are driving me nuts.  I probably am still at the "don't worry if you don't understand it all" phase, but I really get stuck when I can't understand all the different pieces I'm playing around with.  

 

First, what is this Interrupt Vector business at the end of the sample programs, and why is this necessary?  Why does it look like we're just defining the same word three times?  What's the significance of NMI, IRQ, and reset?

 

I don't understand some of these directives, particularly thinngs like ds or db.  Couldn't you just do the same thing with equates, and why wouldn't you?  Especially since of you declare a series (Thing: ds 4) you would still need to use relative addressing to access the other bytes (LDA Thing+2).  Am I totally garbling all the terminology here?

 

Am I totally off in thinking of a bunch of .byte definitions as being like a read only array?  

 

I'd there any way to explain what the stack in a way that makes sense to someone who doesn't know anything?  Why are we pushing thing onto it or pulling them off?  Is it for anything else other than that?  

 

Link to comment
Share on other sites

10 hours ago, MrTrust said:

First, what is this Interrupt Vector business at the end of the sample programs, and why is this necessary?  Why does it look like we're just defining the same word three times?  What's the significance of NMI, IRQ, and reset?

The 6502 chip uses the last 6 bytes in the address space to define 3 pointers that point to code in memory to call when Reset, Interrupts (IRQ), or Non-maskable Interrupts (NMI) occur.  On the 6507 chip, the IRQ and NMI lines were cut from being accessed, so they will never need to be handled.  BUT, the IRQ vector also is used by software interrupts: the BRK instruction.

 

Vectors in memory:

$FFFA - NMI

$FFFC - Reset

$FFFE - IRQ/BRK

 

You really only need to worry about Reset.  But it's always good to set BRK to the same thing as Reset... so that if your game resets, you know when your code has gone astray.

 

10 hours ago, MrTrust said:

I don't understand some of these directives, particularly thinngs like ds or db.  Couldn't you just do the same thing with equates, and why wouldn't you?  Especially since of you declare a series (Thing: ds 4) you would still need to use relative addressing to access the other bytes (LDA Thing+2).  Am I totally garbling all the terminology here?

 

Am I totally off in thinking of a bunch of .byte definitions as being like a read only array?

 

Equates just assign a value to a label and don't ever take any space.

DS and company usually define space within the file, similar to DATA command in basic.

 

BUT if you use DS in an "uninitialized segment", then it wil work more like defining variables that get assigned memory spots when the program is loaded.

 

Example of using an "uninitialized segment" to define variables:  (from Paint The City)

 

    SEG.U  variables
    RORG $80

mainGameTimer   ds 2		;-- uses $80,$81
mainGameTimerHi = mainGameTimer+1

bgScroll        ds 1		;-- this ends up being $82
lvlOfs          ds 1    	;-- this ends up being $83

Here I use DS instead of having to track the individual memory locations for each variable that I would have to do when using EQU/=.

 

10 hours ago, MrTrust said:

I'd there any way to explain what the stack in a way that makes sense to someone who doesn't know anything?  Why are we pushing thing onto it or pulling them off?  Is it for anything else other than that?  

You can think of a stack like "a stack of papers" on your desk.  You can stack up the papers by putting each one on top of the other (PUSH onto the stack).  Then pick them up individually (POP from the stack).

 

Typically, the stack is used to store the return address when a subroutine is called (JSR pushes current address onto the stack before jumping to the provided address,  RTS then pops the address and JMPs to it to return from the subroutine).

 

The stack can also store temporary variables.  In higher-level languages, stack space can be used to store function-local variables.  For Atari 2600 programming, you probably won't need to touch it. 

 

---

 

Hopefully that cleared some things up... Andrew Davie has probably explained this stuff better in his tutorials.

If you don't have it already, you should probably grab a copy of the book:

https://www.lulu.com/en/us/shop/andrew-davie/atari-2600-programming-for-newbies-revised-edition/paperback/product-1jq2q2wp.html?page=1&pageSize=4

 

 

  • Thanks 2
Link to comment
Share on other sites

You're welcome!

 

13 hours ago, MrTrust said:

stupid PF0 won't let you just rotate the carry from one register to the next

 

Yeah, that makes things a little more complicated.  In step 6 - Spec Change for the Collect tutorial I show how to do that for the new Timer Bar.

 

DecrementTimer:
    lsr TimerPF+5   ; PF2 right side, reversed bits so shift right
    rol TimerPF+4   ; PF1 right side, normal bits so shift left
    ror TimerPF+3   ; PF0 right side, reversed bits so shift right
    lda TimerPF+3   ; only upper nybble used, so we need to put bit 3 into C
    lsr
    lsr
    lsr
    lsr
    ror TimerPF+2   ; PF2 left side, reversed bits so shift right
    rol TimerPF+1   ; PF1 left side, normal bits so shift left
    ror TimerPF     ; PF0 left side, reversed bits so shift right
    rts

 

Of course that's only for the case of always shifting the playfield data to the left, so would need revisions if you wanted to shift both directions.

 

@splendidnut covers the rest, though I will point out that ds, db, and byte are part of DASM - a different assembler might use different ways to do the same thing.  We have a DASM club for getting help with it, such as this:

 

 

 

In this comment in the Collect tutorial I cover why you would use ds instead of = or equ:

 

On 12/9/2015 at 5:05 PM, SpiceWare said:

SEG.U VARS is used to specify an uninitialized segment with the name of VARS. The key thing about that, from dasm.txt (the instruction file for dasm), is:

 

That means whatever's after SEG.U does not end up in the ROM file. Here we're using it to allocate RAM usage.

 


  SEG.U VARS
  ORG $80 ; start of RAM
Score: ds 2
DigitOnes: ds 2
DigitTens: ds 2
...

Instead you could do this:

 


Score = $80
DigitOnes = $82
DigitTens = $84
...

but it becomes tedious (and error prone) to manually keep track of RAM usage yourself.

 

 

 

 

 

  • Thanks 2
Link to comment
Share on other sites

13 hours ago, MrTrust said:

Wow, so learning 6502 is brutal, especially as applied to the 2600.  For one, it's a struggle to get a useable IDE up and running if you don't know what you're doing.  Also, it's basically impossible to get "Hello World", then grab some basic functions, figure out variables and control flow and start building from there.  Unfortunately, this is the only way I learn anything when it comes to programming: get a template to do a simple thing or two, start tinkering, and sit there with a reference to add more things I can do.

These days I highly highly recommend using Atari Developer Studio

 

13 hours ago, MrTrust said:

 

Finally, finally, I've started to be able to do this is Andrew Davie and Spiceware's tutorials (big thanks to both).

My tutorials are approaching 20 years old now, so look to the more modern ones for improved examples and more correct code.

 

13 hours ago, MrTrust said:

I'm looking at the reference, and I can kind of wrap my head around what the flags do, how you might use a branch on carry to do the equivalent of an "if x >= y then" thing.

Generally we work with unsigned values.  In the case of unsigned comparisons (i.e, values are 0 to 255) then you can think of "BCC" as "less than" and "BCS" as greater-than-or-equal-to".  

 

lda var1
cmp var2
bcc lessThan

In the example, the branch will be taken if var1 is strictly less than var2

 

13 hours ago, MrTrust said:

I don't understand some of these directives, particularly thinngs like ds or db.  Couldn't you just do the same thing with equates, and why wouldn't you?  Especially since of you declare a series (Thing: ds 4) you would still need to use relative addressing to access the other bytes (LDA Thing+2).  Am I totally garbling all the terminology here?

dasm has a really really really good manual, and you should definitely look there to see what dasm directives actually do.

For example...

 

107444080_ScreenShot2021-07-11at12_03_10am.thumb.png.d004c0eb7132ed067519a425654f2e14.png

 

Whoever wrote that thing did a great job covering lots of stuff.

From the above, you can see that 'ds' for example also allows you to specify a fill value.

 

So, whereas you might go

table ds 20

... to reserve 20 bytes in ROM for a table, the contents will be zero

but, you could go

 

table ds 20,100

And now (because you've specified the optional fillvalue) you have a table of 20 bytes each initialised with the value 100.

Now this can be done in other ways, too, for example

table db 100,100,100,100,100,100,100,100,100,100
 db 100,100,100,100,100,100,100,100,100,100

It's really (for ROM usage) just a matter of convenience.

 

But for RAM, of course, you cannot initialise it using assembler directives. You can only define the space.

So why, as you ask, use 'ds' instead of EQU/equates?

 

One of the big reasons is saving you, the programmer, extra work... and reducing the probability of unexpected bugs.

Say, for example, you had some zeropage defined like this...

 

var1 = $80
var2 = $90
var3 = $91

So here we've reserved 16 bytes for "var1" (from $80 to $8F). But it's completely non-obvious and if you change the size of anything you have to change the addresses (manually) of everything following it.

SO maybe we'd rewrite it like this...

var1 = $80
var2 = var1+16
var3 = var2 + 1

That makes it obvious. Let's say we needed an extra byte for var1

so now we have to write...

 

var1 = $80
var2 = var1 + 17
var3 = var2 + 1

But note that although we have "17" on the line with var2 (i.e., it starts 17 bytes after var1) it's prone to error.

 

However, with 'ds', the size of the block is defined/declared with the label/variable itself.

 

var1 ds 17
var2 ds 1
var3 ds 1

Now that's super-clear. And it means that I can move those lines anywhere (i.e., swap their order, for example) and they will all be calculated with correct addresses and have the correct amount of bytes reserved.

 

var3 ds 1
var1 ds 17
var2 ds 1

In other words, we're letting the assembler do the work and we don't really know/care the exact address where things live. Or even the size - we can let the assembler handle this, too by using calculated equates for the size. Sounds odd?  How about this...

 

 org $80
array ds MAX_SIZE


 org $F000

table db 1,2,3,4,5,6,10
MAX_SIZE = * - table

 

So here we're defining a zero page variable array named... array... to have MAX_SIZE elements.

And how many is MAX_SIZE?  Well, its calculated at assembly time by the assembler. So if we add elements to 'table', then the RAM array will automatically adjust to be the correct size.  In other words, let the assembler do the work wherever possible.

 

 

Now to address your specific example (lda Thing+2) ... well let's use the example table again...

 

 ldx #MAX_SIZE-1
loop lda table,x
 sta array,x
 dex
 bpl loop

Here I've copied all the elements from the ROM table into the RAM array.

Note I didn't hardwire the size, and I didn't use absolute offsets (+2) to access stuff.

Generally (except for the use of word-based- hi/lo address variables), it's better to declare new variables than have unnamed offsets to variables.

For example, rather than "lda Thing+2" you might go "lda Thing+VALUE" where you have set "VALUE=2" somewhere.

Or even, use different variables rather than an array in this instance.

 

13 hours ago, MrTrust said:

Am I totally off in thinking of a bunch of .byte definitions as being like a read only array?  

 

I'd there any way to explain what the stack in a way that makes sense to someone who doesn't know anything?  Why are we pushing thing onto it or pulling them off?  Is it for anything else other than that?  

 

 

The stack is just RAM, and can be used just like RAM.  Because, in fact, the stack and your zero page RAM *overlap*.  This is actually a bad thing, because if you're not careful, your program (automatically) using the stack can corrupt your variables.  So when your 6502 code calls a subroutine (via "jsr") then it has to know how to get back to where it was when that subroutine finishes.  In other words, the PC (program counter/pointer) needs to be restored.  So it has to be stored somewhere when it is diverted to that subroutine, and restored to the original value after that subroutine finishes. And if THAT subroutine also calls subroutine(s) ... ad infinitum... then those program counter values need to be stored/restored.

 

So here's where the idea of a "stack" comes in.  When the 6502/7 calls a subroutine, it "pushes" the current PC onto the stack. This basically means that it's stored into the RAM where the stack points to, and then the stack points "one entry further on" in RAM.  SO you can keep "pushing" things onto the stack, and you'll always be storing to a fresh RAM location. And the opposite is called "pop" from the stack. When you pop something from the stack, you get the value just pushed. It's a last-in-first-out buffer, in other words. Push something (A), push something (B). Do a pop and you get B. Do another pop and you get A.  SO the stack holds those PC "return addresses" and this is automatic whenever a "rts" is executed - a return from subroutine instruction.

 

Now, because the '2600 is so low on RAM (just 128 bytes of zero page), the stack has been set to use the top part of this RAM, and your variables occupy the bottom part.  Stack from $FF downwards, and variables from $80 upwards. If you use a LOT of variables, and you also do a lot of nested subroutine calls (which push the PC to the stack), then you can get a clash - the push that happens on a subroutine call can STOMP on your uppermost RAM variables. How do you avoid this? By being very very careful/frugal about variable usage and nested subroutine calls.

 

One other thing; you can also use the stack as a temporary storage, using "pha" to push a value to it, and "pla" to pull a value.

For example, let's swap the value of two variables...

 

 lda var1
 pha
 lda var2
 sta var1
 pla
 sta var2

That's not the most efficient way, of course... but just showing stack usage.

I don't know/care WHERE that "pha" put the variable. Just "on the top of the stack". And the pop pulled it "off the top of the stack".

Don't forget, though, that the stack grows downwards in RAM, so it's all a bit confusing.  Just think of it, though, as a pile of paper. You put things on top of the pile, and you pull things off the top. It doesn't matter how the memory is actually organised/used; you rarely need to worry about the details.

 

 

 

 

  • Like 1
  • Thanks 2
Link to comment
Share on other sites

8 hours ago, splendidnut said:

The 6502 chip uses the last 6 bytes in the address space to define 3 pointers that point to code in memory to call when Reset, Interrupts (IRQ), or Non-maskable Interrupts (NMI) occur.  On the 6507 chip, the IRQ and NMI lines were cut from being accessed, so they will never need to be handled.  BUT, the IRQ vector also is used by software interrupts: the BRK instruction.

 

Okay, not sure I understand any of those things, or when they might occur, but maybe I get it conceptually.  Basically, we want to write a value to those addresses the corresponds to an address where we want the PC to go if one of those events happens, probably wherever you initialize everything in your code?

 

5 hours ago, SpiceWare said:

Yeah, that makes things a little more complicated.  In step 6 - Spec Change for the Collect tutorial I show how to do that for the new Timer Bar.

 

Yeah, I didn't get that far because I got caught up in tinkering.  I don't have what I did in front of me, so I don't quite remember it exactly, but since I was just shifting a single one, I was just checking for the bit to hit the lower nybble, then clearing the the PF0 and resetting either PF1 or PF2 to $80 depending on which way I was going.  But probably just shifting it over and over is way more economical.  It really is like a puzzle doing anything on the machine.

 

5 hours ago, Andrew Davie said:

dasm has a really really really good manual, and you should definitely look there to see what dasm directives actually do.

For example...

 

I'm sure it is, but I think it requires a better baseline level of understanding than I have to be maximally useful.  I did try to RTFM on this one, and I see basically what it does, but clean a why from it.

 

5 hours ago, Andrew Davie said:

Here I've copied all the elements from the ROM table into the RAM array.

Note I didn't hardwire the size, and I didn't use absolute offsets (+2) to access stuff.

Generally (except for the use of word-based- hi/lo address variables), it's better to declare new variables than have unnamed offsets to variables.

For example, rather than "lda Thing+2" you might go "lda Thing+VALUE" where you have set "VALUE=2" somewhere.

Or even, use different variables rather than an array in this instance.

 

Phew!  Okay, I think I'm following you here.  So, essentially, it gives you a quick way to declare an array and a mechanism for adding/subtracting elements without having to go back and redeclare everything, and keeps your declarations from stepping over each other if you make changes?  I understand the example of using the loop counter to index the array and fill the elements, but you theoretically could still do that even if you just hardwired or explicitly declared everything, no?

 

5 hours ago, Andrew Davie said:

The stack is just RAM, and can be used just like RAM.  Because, in fact, the stack and your zero page RAM *overlap*...

 

Ahhhhhh, okay.  So, another way of thinking about it is that the number of JSRs you can "rack up" before you hit a RTS is 128 minus the number of variables you're using (or zero page addresses)?  And maybe you would want to do a calculation, save the processor status, do another calculation, and then retrieve the PS before you move on.  Or, there's the variable swap you did, which you could do by using a scratch variable, but then you'd have to load that into a register and do a store or transfer and then zero out the scratch variable so it doesn't mess anything up later, which would be way less economical.  I think I get it, conceptually speaking.  Also, I assume that if you're going to push anything to the stack during a subroutine, you'd better get it back off before the next RTS or you're going to end up in the wrong place in your program on return.

 

That explains what happened to me at one point with a game I've been writing in bB.  At one point, I was branching to the game over loop as with a gosub rather than a goto, but was branching back out of the loop with one of two gotos and no gosub.  I was using I think 24 variables out of the 26 available, so every third game over, the game would crash, because that would be where the stack leeched into the used RAM.

 

 

Edited by MrTrust
Link to comment
Share on other sites

4 hours ago, MrTrust said:

Phew!  Okay, I think I'm following you here.  So, essentially, it gives you a quick way to declare an array and a mechanism for adding/subtracting elements without having to go back and redeclare everything, and keeps your declarations from stepping over each other if you make changes?  I understand the example of using the loop counter to index the array and fill the elements, but you theoretically could still do that even if you just hardwired or explicitly declared everything, no?

Pretty much. It's about convenience, yes - but also about safety. Letting the assembler handle things as much as possible means that you don't have to remember to adjust/fix things when you make changes.  Yes, you could do the loop the same way with hardwiring. But hardwiring is generally a bad idea.

 

4 hours ago, MrTrust said:

Ahhhhhh, okay.  So, another way of thinking about it is that the number of JSRs you can "rack up" before you hit a RTS is 128 minus the number of variables you're using (or zero page addresses)?

Yes. But generally nested subroutine calls are uncommon.  My games only need 2 or 3 levels of calls (hence 4 or 6 bytes of stack space). The rest I use for variables.  I'm careful about how many subroutines I call "inside each other". It's not hard to do.

 

4 hours ago, MrTrust said:

Also, I assume that if you're going to push anything to the stack during a subroutine, you'd better get it back off before the next RTS or you're going to end up in the wrong place in your program on return.

Exactly. You got it.

 

  • Thanks 1
Link to comment
Share on other sites

22 hours ago, MrTrust said:

So, another way of thinking about it is that the number of JSRs you can "rack up" before you hit a RTS is 128 minus the number of variables you're using (or zero page addresses)?


close - JSR pushes a 16 bit return address onto the stack, so (128 - # variables) / 2

Link to comment
Share on other sites

On 7/10/2021 at 3:59 PM, MrTrust said:

Okay, not sure I understand any of those things, or when they might occur, but maybe I get it conceptually.  Basically, we want to write a value to those addresses the corresponds to an address where we want the PC to go if one of those events happens, probably wherever you initialize everything in your code?

It's quite a bit outside the scope of what you really need to know about Atari 2600 programming, but hardware interrupts are a mechanism in pretty much all programmable CPUs that makes it possible for a CPU to respond to events external to itself in a timely fashion. It gives a CPU the means to handle more than one task at once, and is part of how threading is implemented at the low level.

 

To respond to events quickly, hardware is designed to allow code execution to be halted immediately at an arbitrary point, and jump to a different subroutine in such that the CPU is able to return where it left off later and continue execution. There are two core interrupt types, Maskable and Non-Maskable.

  • Maskable interrupts are interrupts that can be temporarily ignored by the CPU. This allows the CPU to finish up a system critical task like modifying a variable that different threads need to access.
  • Non-Maskable interrupts are interrupts that can't be ignored by the CPU, like the system being turned on, a reset button being pressed, or a critical hardware fault.

Lastly, the Interrupt Vector table is a hardwired table that lets you assign a different handler for each type of interrupt supported by the CPU.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...