On Thursday 31 October 2002 03:03 pm, Greg Smith wrote:
> I understand that much.... but why did Intel want you to use a top-down
> stack ??  I remember from my Pascal days that you could reference your
> caller's local variables, so I guess it's easier to reference them in a
> top-down stack.  Just a guess, I have no idea.

>From a guy whose first exposure to computers, at all, was learning to hand-
assemble for the hot new Intel 8080A chip...

I think the thing people are overlooking in the question of "why a downward
stack?" is that there's a radical difference in how things worked back then,
in the microprocessor world.

Microprocessors ran single-tasking operating systems.

Every application owned the whole machine.

Security? Why would I need that when there's only one terminal and one paper
tape punch? Just lock up your tape reels. If you actually care that someone
looks at your program for tallying red, green, and blue widgets as they come
down their three chutes.

Mainframes have been multitasking for a lot longer than microcomputers. So I
think this crowd may be overlooking just how *primitive* things were in the
micro world when that stack design decision was made.

Back in the really early days of the Intel architecture, there was only 16
bit addressing of memory. There were no segment registers, no memory protection
at all, just one flat memory space of 65536 bytes maximum. The use of an
external stack was a significant innovation of the 8080 over the 8008 that
preceded it.

So, where did it make sense to put the stack?

Well, the program counter (PC) was reset to 0x0000 on power up or restart.
Until the 8085 there was no specialized use of low memory as interrupt
vectors, and even then, the 0x0000 address was still the initial PC
value. So applications, which expected to own the machine, were typically
assembled with "ORG 0" as their stated or implied location. You started
at address zero because it was simple to debug that way, because you could
guarantee that there had to be ROM there (else the 8080 wouldn't have any
initialization code), and because there was no good reason to complicate
things by starting elsewhere.

If your application was loading into RAM instead of ROM, you still thought
of memory as growing "upward from zero" as far as your application's static
storage and code was concerned.

The stack was a weird duck. In an era when lots of programs statically
allocated their variables and didn't even really use subroutine parameters
except in registers, the stack might very well be the *only* dynamically-
growing memory structure in the entire system. Really. Ask anyone who did
embedded systems work back then. You avoided dynamic memory structures if
you could because the programming and debugging tools were primitive and
because often the applications were simple enough not to need them. And
because dynamic memory structures needed to be managed by the application
or at least the compiler runtime, and that took CPU cycles. With a clock
speed of around 2 megahertz, and cycle times measured in microseconds,
this mattered a lot.

So, again we are back to, "Where do we put the stack?"

The best case from the coder's standpoint is that the stack, which was often
used only for saving register state and return address (actually part of
register state, because it was just a save of the next PC address), should
just be somewhere .... out of the way. Somewhere that it could be initialized
at power up and simply forgotten.

Since ROM had to start at 0x0000, it follows that RAM -- which, of course, is
where stacks have to live -- used the higher memory addresses. Often the
hardware designer would start RAM at either 0x8000 and go upward, leaving room
for ROM expansion, or at 0xffff going downward (same reasoning, different
way of expressing it). There were a couple of easy ways to make the RAM/ROM
divide easily in hardware. 0x8000 meant using A15 as a simple RAM/!ROM select.
Using 0x4000 meant using RAM=(A15+A14) and ROM=!(A15+A14). Using 0xc000 meant
RAM=(A15*A14) and ROM=!(A15*A14). If your ROM chip used only A11 through A0
(4K), so what? Just let it phantom through the whole ROM address space. If
you add a bigger one in the next design iteration, just route over the other
address pin (A12) and things sort themselves out automatically. Easy.

Again, remember that saying to a hardware designer, "If you do it this way,
you can eliminate one TTL logic package for address decode" --- MATTERED!
You probably had two spare NOR gates in another package somewhere, so if you
could decode just the upper two address lines, A15 and A14, you often could
in fact save the extra logic package for decode.

Intel's designers no doubt went through a lot of these thought processes as
they planned the 8080, and also as they used the existing 8008 processors
in real-world situations.

So they came up with the downward-growing stack. Put the stack pointer
initially at one address above the top of memory, because PUSH and POP used
predecrement/postincrement logic. If you were lucky enough to have a full
load of 64K of memory, you could set the SP to 0x0000 so that its first
byte stored would wrap around to 0xffff, the top of RAM. In any case, init
the SP at power up, don't use the top few words of RAM, and basically forget
about the stack.

Subroutine callbacks were shallow, typically. Few if any variables were
allocated on the stack, if you were coding in assembler, because it was
too cumbersome to manage. Variables were often statically allocated. That
meant the stack in most applications stayed very small, just a few dozen
bytes in some cases. If you had 4K or 8K or 16K (wow!) of RAM, you stuck
the SP at (top_of_ram + 1), let it grow down into RAM, and forgot about
it altogether. You just knew in your code design not to use the top hundred
bytes or so of RAM for static variables.

This was pretty loose-and-free with memory management, and I'm not saying
all programs were written that way. But when the 8080 first came out in
1973, microprocessor tools were pretty primitive and assembler was still
the best way to wring out the last drops of precious performance. And
people weren't running complex multithreaded, multitasking systems on the
8080. You had control of the application and the hardware environment, and
so if you were a fairly careful designer, you actually could get away with
things like this that would be unthinkable in a modern system.

By the end of the 8080/8085 series' lifespan, programmers were using other
languages more. PL/M, BASIC, FORTRAN, and C were available later on, and
the reasons for the original downward-growing stack faded away as the
programmer was relieved of the need to manage it. But the reasons why Intel
chose this approach are entirely sensible and understandable, at least to
me, because of the mindset of the microprocessor programmers of the day.

Incidentally, I noted in one other post a comment that there should be
hardware protection to keep from executing instructions off the stack.
Great idea. Starting with the 8086, Intel *had* this hardware protection,
because the stack had its own memory segment that could be physically
isolated from other segments. You had to explicitly set the code segment
register to point into the stack segment if you wanted to execute stack
data as code. The 8086 didn't have any security features to keep malicious
code from doing this, but at least it didn't happen by accident. Starting
with the 80286, there were actually security privilege levels that kept
application programs from tinkering with segment registers -- as long as
you were running in an operating system that supported this. DOS, of
course, didn't.

Then came our modern age, the age of flat memory models. Segment registers
are anachronistic. Toss them out. One simple, flat memory model is the only
way to go.

Hello simple memory management and portability. Bye-bye hardware stack
protection.

To be fair, I should mention that I generally *agree* with the flat memory
model. It really goes a long way toward improving portability, since there are
tons of hardware architectures that have no analogue to the Intel segment
registers. But in dumping the segment registers, we didn't get our lunch
totally for free. <GRIN>

    "The Intel segment registers are something of the trouble sort.
     The Sinclair crashes every time you implement a bubble sort.
     And those who bought a cray will find they haven't spent their money well.
     And need I even mention Nixdorf, Unisys, and Honeywell?"

     -- Steve Levine, "I've Built a Better Model Than the One at Data General"
     http://www.poppyfields.net/filks/00007.html
     (and, oddly, the version published here has slightly different words
     than the one I know, but I have a tape of it performed by the author
     with the above words)

Kind regards,

Scott

--
-----------------------------------------------------------------------------
Scott D. Courtney, Senior Engineer                     Sine Nomine Associates
[EMAIL PROTECTED]                           http://www.sinenomine.net/

Reply via email to