6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Nov 16, 2024 9:53 pm

All times are UTC




Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Wed Aug 22, 2012 9:38 am 
Offline

Joined: Wed Aug 22, 2012 9:27 am
Posts: 1
Location: Hanoi
Hi all,

I'm reading about the 6502 processor's instruction set from the many links at 6502.org, and one tutorial states:
"The stack pointer (S) points to a byte on Page 1, that is, to a byte whose address is from 0100 to 01FF, where the last two digits are supplied by S. When a byte is pushed on the stack, it is written at the address in S, and then S is decremented."

The S register is 1 byte, so it obviously holds a value from 00 to FF, but since it decrements upon pushes, when nothing has yet been pushed on, it must start at FF. Does the physical hardware (transistors) in the chip set all the bits in that register to '1' when the chip gets its first breath of power?

I just like to know the low-level details.

Thanks,

Susan.


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 22, 2012 9:59 am 
Online
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8541
Location: Southern California
Your reset routine will normally include LDX #$FF, TXS to initialize the stack, since it it not self-initializing.

_________________
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: Wed Aug 22, 2012 11:23 am 
Offline

Joined: Sat Jul 28, 2012 11:41 am
Posts: 442
Location: Wiesbaden, Germany
If nothing else uses page 1 ($100-$1ff) it is not necessary to initialize the stackpointer. The stackpointer will simply wrap around if decremented below zero. So there is no reason for the hardware to initialize it.

However, it still is good practice to initialize it by software to $ff. This allows you to use the lower part of page 1 for other purposes than stack. Debugging of a runaway stack may also benefit from this (knowing where the SP should point at).

_________________
6502 sources on GitHub: https://github.com/Klaus2m5


Last edited by Klaus2m5 on Wed Aug 22, 2012 1:40 pm, edited 2 times in total.

Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 22, 2012 1:04 pm 
Online
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8541
Location: Southern California
Good point. I should have mentioned that tests show that the amount of stack space you normally need is only a tiny fraction of what's there. If you hear the myth that the 6502's stack space is quite limited, know that it's just that—a myth. If you overflow it, it's normally because you have a situation that would not be happy with any quantity of stack space. I use the bottom of page 1 for variables also.

_________________
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: Wed Aug 22, 2012 2:28 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
susan12 wrote:
I just like to know the low-level details.

Welcome, Susan. You'll find lots of folks here that share your appetite for understanding :D And, of course, the 6502 family of CPU's has plenty to engage our attention!

As for processor initialization, really this is an indirect result of the power coming on. The computer actually has a simple circuit external to the CPU that detects the power-up event and triggers the RESET pin on the CPU. Some computers include a manual reset push-button as well.

The RESET pin overrides some of the random information that spuriously appears in the newly-awakened CPU. Other random info is allowed to persist, at least temporarily. For example, the Program Counter is loaded with a known value taken from ROM (at the "Reset Vector," $FFFC, $FFFD). Also, the status register, P, has the I bit (Interrupt Disable) set. But, as noted, the SP is not initialized; nor are registers A, X and Y.

65xx variants such as the 6509 and the 65816 have other registers and status bits that are initialized by the reset signal. But, to keep on-chip CPU wiring as simple as possible, it's generally true that the effects of the reset pin are strictly limited to the bare minimum that's absolutely necessary to reliably start the program. The SP is an example of something that can be initialized with software instead.

cheers,

Jeff

ps- An exception to the "only the bare minimum" rule is the Decimal Mode Flag, D, in the status register. Although the original NMOS 6502 adhered to the rule (and relied on the programmer to include a CLD instruction in the initialization sequence), some of the later CPU's automatically clear D when the RESET pin is activated. This appears to be a concession to programmer forgetfulness. CPU manufacturers apparently found it justifiable, perhaps because of the maddeningly erratic program bugs that can arise when decimal mode is unintentionally selected!

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 22, 2012 4:09 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10981
Location: England
Welcome!
As I recall, the CMOS version (65C02) does initialise the stack pointer, but the original NMOS 6502 certainly doesn't.

Edit: but this isn't mentioned in WDC's programming manual (table on page 202), and is contradicted by the Rockwell datasheet, so my recollection is wrong. Oops.

Cheers
Ed


Last edited by BigEd on Wed Aug 22, 2012 4:23 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 22, 2012 4:22 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8491
Location: Midwestern USA
susan12 wrote:
Hi all,

I'm reading about the 6502 processor's instruction set from the many links at 6502.org, and one tutorial states:
"The stack pointer (S) points to a byte on Page 1, that is, to a byte whose address is from 0100 to 01FF, where the last two digits are supplied by S. When a byte is pushed on the stack, it is written at the address in S, and then S is decremented."

The S register is 1 byte, so it obviously holds a value from 00 to FF, but since it decrements upon pushes, when nothing has yet been pushed on, it must start at FF. Does the physical hardware (transistors) in the chip set all the bits in that register to '1' when the chip gets its first breath of power?

I just like to know the low-level details.

Thanks,

Susan.

First off, welcome to our little 6502 world!

As noted above, the stack pointer is at an undefined value at power-on (but not necessarily at reset—two fundamentally different events). While it is true that many stack operations may be carried out without knowing to where the stack pointer is currently pointing, good programming practice is to initialize the stack pointer to a known value (usually $FF in the case of the 65(c)02) before any operation occurs that will use the stack. The first several lines of code in a reset handler would appears like so:

Code:
         sei                   ;see below
         cld                   ;select binary mode
         ldx #$ff
         txs                   ;initialize stack pointer

...etc...

The seemingly redundant SEI is there to handle the case where the reset handler is called through software, as in JMP ($FFFC).

Please don't be shy about asking questions. We're never shy about offering answers...or opinions. :D

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 22, 2012 4:26 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8491
Location: Midwestern USA
BigEd wrote:
Welcome!
As I recall, the CMOS version (65C02) does initialise the stack pointer, but the original NMOS 6502 certainly doesn't.

Edit: but this isn't mentioned in WDC's programming manual (table on page 202), so my recollection is probably wrong. Oops.

Cheers
Ed

I looked at some documentation for the Rockwell version of the 65C02. No mention is made of the stack pointer being initialized at power on. As far as I know, reset doesn't affect the value of the stack pointer at all. The 65C816, which starts up in emulation mode at reset, doesn't initialize the stack pointer either, which I determined through testing. At power on, the '816's stack pointer is randomly between $00 and $FF.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 22, 2012 4:30 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10981
Location: England
Yes, I was probably checking the Rockwell datasheet while you were composing that!
Don't know how I'd got the wrong idea, or when.
Cheers
Ed


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 22, 2012 5:59 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8491
Location: Midwestern USA
GARTHWILSON wrote:
Good point. I should have mentioned that tests show that the amount of stack space you normally need is only a tiny fraction of what's there. If you hear the myth that the 6502's stack space is quite limited, know that it's just that-- a myth. If you overflow it, it's normally because you have a situation that would not be happy with any quantity of stack space. I use the bottom of page 1 for variables also.

All true until one starts using the stack for heavy parameter passing, as is done in C function calls. Some time ago I worked out the details of calling a kernel API via the int n mechanism, where n is an API number. Consider the following:

Code:
         read fd,buf,n         ;get some data

The above is a function macro that reads from an open file and stores the data stream somewhere—this macro is an analog to the UNIX/Linux read() kernel API call. fd is the file descriptor, buf is a location to store data and n is the number of bytes to read. When assembled, the macro expands as follows (65C816 assembly language):

Code:
         pea n                 ;push bytes to read
         pea buf               ;push buffer address
         pea fd                ;push file descriptor
         pea nparms            ;number of parameters pushed
         int kc_read           ;call kernel read API

Note that the macro parameters are pushed in reverse order and in this case, nparms would be 3. The int kc_read instruction is also a macro and would expand to:

Code:
        .byte $00,kc_read

where kc_read is the (non-zero) API number.

Since the API is called via (effectively) a BRK instruction, more stack usage occurs in the front end that processes the BRK. Here's an excerpt from a test program I wrote to see if my thinking was correct. It wedges into the POC's software interrupt handler in ROM through an indirect vector (shades of the C-64!). I edited out the setup part of the program, as it isn't germane to the discussion, leaving only the code that intercepts the int n call. I also replaced the '816 instruction macros in the original source code with the equivalent assembly language instructions to improve clarity:

Code:
;   start of kernal API front end—intercepts BRK handler...
;
kernapi  phb                   ;save current data bank
         phd                   ;save zero page pointer
         rep #%00110000        ;save 16 bit user registers
         pha
         phx
         phy
         cli                   ;re-enable IRQs
;
;—————————————————————————————————————————————————————
apiregy  .set 1                ;.Y (16 bits)
apiregx  .set apiregy+s_word   ;.X (16 bits)
apirega  .set apiregx+s_word   ;.A (16 bits)
apiregdp .set apirega+s_word   ;direct page (16 bits)
apiregdb .set apiregdp+s_word  ;data bank (8 bits)
apiregsr .set apiregdb+s_byte  ;status register (8 bits)
apiregpc .set apiregsr+s_byte  ;RTI address (16 bits)
apiregpb .set apiregpc+s_word  ;RTI program bank (8 bits)
;
;
;   following is the API part of the stack frame...
;
apinparm .set apiregpb+s_byte  ;NPARMS (16 bits)
readfd   .set apinparm+s_word  ;file descriptor (16 bits)
readbuf  .set readfd+s_word    ;buffer address (16 bits)
readn    .set readbuf+s_word   ;number of bytes (16 bits)
;—————————————————————————————————————————————————————
;
         lda apiregpc,s        ;get RTI address pushed by MPU following BRK
         dec a                 ;now API number address
         pha                   ;push it to stack (16 bits)
         lda !#0               ;clear both accumulators
         sep #%00110000        ;8 bit registers
         tay                   ;relative index
         lda (1,s),y           ;get API number
         plx                   ;clear API number address...
         plx                   ;from stack
         sta apinum            ;store API number
;
;   ————————————————————————————————————————————————
;   At this point the kernal API has control. Later,
;   we clean up the stack & return to the caller.
;   ————————————————————————————————————————————————
;
         rep #%00110000        ;16 bit registers
         lda apinparm,s        ;number of parameters...
         inc a                 ;plus NPARMS
         asl a                 ;convert to bytes &...
         sta apinparm,s        ;save on stack
         tsc                   ;entry stack pointer
         clc
         adc !#apiregpb        ;compute stack...
         tax                   ;shift-from address
         adc apinparm,s        ;compute stack...
         tay                   ;shift-to address
         tsc                   ;entry stack pointer
         sei                   ;no IRQs during...
         clc                   ;stack realignment
         adc apinparm,s        ;adjust...
         tcs                   ;stack pointer
         lda !#apiregpb        ;bytes to shift
         mvp sbnk,dbnk         ;realign stack
         ply                   ;restore...
         plx                   ;register...
         pla                   ;entry values
         plb                   ;restore DB
         pld                   ;restore DP
         cli                   ;reenable IRQs
         rti

In the above, !# means to assemble a 16 bit operand, e.g., lda !#12 would generate the code A9 12 00.

Pay particular note to the stack frame definitions. Here's what they look like once assembled:

Code:
00379    ;—————————————————————————————————————————————————————
00380      0001             apiregy  .set 1                ;.Y (16 bits)
00381      0003             apiregx  .set apiregy+s_word   ;.X (16 bits)
00382      0005             apirega  .set apiregx+s_word   ;.A (16 bits)
00383      0007             apiregdp .set apirega+s_word   ;direct page (16 bits)
00384      0009             apiregdb .set apiregdp+s_word  ;data bank (8 bits)
00385      000A             apiregsr .set apiregdb+s_byte  ;status register (8 bits)
00386      000B             apiregpc .set apiregsr+s_byte  ;RTI address (16 bits)
00387      000D             apiregpb .set apiregpc+s_word  ;RTI program bank (8 bits)
00388    ;
00389    ;
00390    ;   following is API-specific stack frame...
00391    ;
00392      000E             apinparm .set apiregpb+s_byte  ;NPARMS (16 bits)
00393      0010             readfd   .set apinparm+s_word  ;file descriptor (16 bits)
00394      0012             readbuf  .set readfd+s_word    ;buffer address (16 bits)
00395      0014             readn    .set readbuf+s_word   ;number of bytes (16 bits)
00396    ;—————————————————————————————————————————————————————

The stack frame generated for this one API call is 21 bytes. It is conceivable during an API call that an IRQ could hit—pushing another 14 bytes. In the case of my POC unit, which has a fully reentrant ISR (interrupt handler), the ISR itself could be interrupted—interrupts are re-enabled if the source is determined to be the DUART or watchdog timer—and a SCSI interrupt could hit while a DUART or WDT interrupt is being serviced. This would result in at least another 14 bytes being pushed to the stack on top of the above stack frame. Adding insult to injury, during the execution of the read API, other subroutines calls would be made, such as to determine to which file fd points, or to a primitive that translates a file pointer into a logical block address and offset into the block. Calls would be required to load a disk block into core (and, in the process, causing several SCSI interrupts to occur) and to fetch bytes from the block image once loaded. Stack usage would quickly mount.

Also keep in mind that the read API call itself would be embedded in a subroutine that may been called by something higher up that also pushed parameters to the stack. For example, the UNIX/Linux fread() library function has at its core the read() kernel API call. In order for fread() to work, it has to push parameters that are needed by read(), plus the code that calls fread() has to likewise push parameters. It all makes for a very busy stack.

Granted, it isn't likely that something like the above will be implemented on the average 65(c)02 powered unit (although André Fachat had a full-blown operating system running on his unit, so I imagine he made heavy usage of the stack). While it is unlikely the average 65(c)02 program will wrap the stack, one shouldn't underestimate how much stack space might get used up.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 22, 2012 6:20 pm 
Online
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8541
Location: Southern California
...and that's why I did say "normally." Pascal I understand puts entire procedures and arrays on the stack instead of just an address to them; but the beginner won't be doing that. Even passing a bunch of 1- to 3-byte parameters won't take up much space if you pass a half-dozen or less at a time and have very little nesting.

Forth puts the data stack in page 0 while keeping the return stack in page 1 (on the 6502, whereas the 65816 can have many, many pages of return stack which I have considered using for local variables and whole environments). The indexing modes, especially the ZP ones, allow making additional stacks anyway for special purposes if needed, as I was thinking of doing for extended-precision and complex numbers at viewtopic.php?f=9&t=493

It sounds like we have the opportunity to help a relative beginner get started here though, and we should keep things kind of basic here.

_________________
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: Wed Aug 22, 2012 7:34 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8491
Location: Midwestern USA
GARTHWILSON wrote:
...and that's why I did say "normally." Pascal I understand puts entire procedures and arrays on the stack instead of just an address to them; but the beginner won't be doing that. Even passing a bunch of 1- to 3-byte parameters won't take up much space if you pass a half-dozen or less at a time and have very little nesting.

With a 65(c)02, you can nest 42 subroutine calls if each call starts by preserving all registers (and removes them when done) and the stack isn't otherwise used for parameter passing.

Quote:
Forth puts the data stack in page 0 while keeping the return stack in page 1 (on the 6502...

For good reason, since 6502 stack acrobatics are awkward—only .A can be pushed and there are no stack manipulation instructions beyond PHA, PLA, TSX and TXS. It's a little better with the 'C02, since you can push .X and .Y as well. However, use of ZP as a pseudo-stack with these MPUs makes sense, as long as there isn't a lot of contention for ZP space.

Quote:
...whereas the 65816 can have many, many pages of return stack which I have considered using for local variables and whole environments...

Yeppers. The '816 is tailor-made for stack-oriented environments, especially Forth and C (I don't know anyone who uses Pascal anymore). Indeed, WDC's C compiler generates surprisingly compact code for the '816, thanks to the built-in stack operations like LDA <offset>,S and PEA.

Aside from the 16 bit stack pointer giving the programmer potentially huge stack space, the '816 lets ZP be moved around. One can assign an ephemeral ZP and ephemeral stack to each subroutine if desired. As all of the grunt-work needed to access the stack is now being done in hardware, the implementation shrinks and runs faster.

Quote:
The indexing modes allow making additional stacks anyway for special purposes if needed, as I was thinking of doing for extended-precision and complex numbers at viewtopic.php?f=9&t=493

It's interesting that you mention this. Once I got POC off the ground, I went to work developing a machine language monitor for it to run in ROM. I started by resurrecting an ancient monitor I wrote c. 1985 and then enhanced in 1991 for the 65C02. Not too long into it, I "came to my senses" and realizing that I wasn't taking advantage of the substantially greater capabilities of the '816, started from scratch with an '816-specific monitor. Thanks to the stack instructions and 16 bit capabilities, I ended up with a smaller program having features not present in the 'C02 version.

Quote:
It sounds like we have the opportunity to help a relative beginner get started here though, and we should keep things kind of basic here.

Probably. You know me: I tend to get carried away with the esoterica. :lol:

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 22, 2012 8:06 pm 
Online
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8541
Location: Southern California
Susan, if you'll indulge us once again... But do jump in and bring things back on topic again anytime, as I'm sure you'll have more questions.

BigDumbDinosaur wrote:
With a 65(c)02, you can nest 42 subroutine calls if each call starts by preserving all registers (and removes them when done) and the stack isn't otherwise used for parameter-passing.

Fortunately it's seldom necessary for a subroutine, or even an ISR, to start by preserving all the registers.

Quote:
Quote:
Forth puts the data stack in page 0 while keeping the return stack in page 1 (on the 6502...

For good reason, since 6502 [hardware] stack acrobatics are awkward—

Actually it's to take advantage of the extra addressing modes that are only available in ZP. X is the data stack pointer virtually full time, so you have things like LDA (ZP,X).

_________________
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: Wed Aug 22, 2012 8:19 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8491
Location: Midwestern USA
GARTHWILSON wrote:
Susan, if you'll indulge us once again... But do jump in a bring things back on topic again anytime, as I'm sure you'll have more questions.

Gotta watch out for topic hijacking, although everything above is/was peripheral(!) to discussion about a stack pointer. :D

Quote:
BigDumbDinosaur wrote:
With a 65(c)02, you can nest 42 subroutine calls if each call starts by preserving all registers (and removes them when done) and the stack isn't otherwise used for parameter-passing.

Fortunately it's seldom necessary for a subroutine, or even an ISR, to start by preserving all the registers.

True, although a central ISR dispatcher in anything more than trivial usually ends up having to preserve the registers. That in itself wouldn't be much of a strain on the stack.

Quote:
Quote:
Quote:
Forth puts the data stack in page 0 while keeping the return stack in page 1 (on the 6502...

For good reason, since 6502 [hardware] stack acrobatics are awkward—

Actually it's to take advantage of the extra addressing modes that are only available in ZP. X is the data stack pointer virtually full time, so you have things like LDA (ZP,X).

I can't say I have much knowledge of Forth internals—mostly I know to not spell it FORTH or forth or worse yet, Fourth. I can see though where an '816-specific version of Forth would greatly benefit from the much-enhanced stack capabilities.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 22, 2012 8:42 pm 
Online
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8541
Location: Southern California
Quote:
I can see though where an '816-specific version of Forth would greatly benefit from the much-enhanced stack capabilities.

What immediately comes to mind is DO...LOOPs or similar, especially nested. These put the loop indexes (ie, counters) and limits on the hardware stack; so if you want to get the index value of a loop a level or two out (I can't imagine ever having to go further), the 65816's stack-relative addressing makes it much easier.

_________________
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  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: Google [Bot] and 9 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: