[MUSIC PLAYING]
Hi, I'm Dick Sites.
And I'm Dirk Meyer.
And we're going to talk today about Alpha.
In discussing Alpha, we make the distinction
between the architecture as a paper
document and the various implementations.
I'm going to talk about the architecture.
And then I'll be back in a little bit later
to talk about the first implementation
of that architecture.
I'm going to talk about Alpha architecture,
about the goals, an overview of the architecture,
about the things that are different from other risk
architectures, and then about some of the problems
that we addressed in designing the architecture
and how we thought about them.
Rich Witek and I are the co-architects for Alpha.
My part of the Alpha design has been influenced substantially
by three other architects, Fred Brooks, John Cocke, and Seymour
Cray.
When we started the Alpha project in 1988,
we set four goals for the architecture.
Performance, longevity, scalability, and generality.
In performance, we wanted the architecture
to allow implementations that would
be faster than anything else in the industry each year
for the foreseeable future.
That performance goal reflects itself
in a number of the details of the architecture,
as I'll show you in a few minutes.
For longevity, we wanted the architecture to last 15 or 20
or 25 years, much longer than a 10-year design cycle,
and that longevity goal implied a number of design decisions,
if we were honest about meeting that goal.
The first design decision is that it had
to be a 64-bit architecture.
All the 32-bit architecture will run out
of address bit sometime in the next 10 or 15 or 20 years.
In fact, in designing the first chip,
we ran out of address bits on VAXs last year.
In addition to being a 64-bit architecture,
the longevity goal implied that implementations
over the course of two decades would have
to scale up in performance.
We looked at how the scaling could happen.
The first chip runs at 200 megahertz
five nanosecond cycle.
It looked unlikely that the chip speed, clock speed,
would improve by a factor of 1,000 over 25 years,
but the industry curves say that the implementation performance
does need to improve by a factor of 1,000
if the architecture is going to last that long.
We thought it was realistic for the clock speed
to improve by a factor of 10.
So we looked for other places to pick up
the other factor of 100.
If the clock speed isn't going to pick up
by more than a factor of 10, then to gain more performance
you have to do more work in every clock cycle.
So very early in the design, we settled on the idea
that the architecture would have to gracefully allow
multiple instruction issue, that it
would be necessary for the longevity goal
and the performance goals for the architecture
to allow many instructions to be started every clock cycle.
We thought realistically over the course of a few decades,
perhaps using compilers that our children build,
that compiler technology could pick up a factor of 10
using multiple instruction issue.
That still left us a factor of 10 short.
So we looked at where else implementations could pick up
performance, and the only other way we saw
was to have multiple processors to have perhaps
as many as 10 processors in a box
25 years from now executing pieces of a common program.
So we focused the architecture, just because
of the longevity and the scalability goals,
on those three points of very fast clock
cycle, multiple instruction issue, and multiple processors.
Finally, we designed the architecture to be general.
It's not just a Unix hotbox.
It's designed to be able to run VMS, to be able to run OSF/1,
to be able to run other operating systems to track
different computing paradigms as the industry
changes over the next 5 or 10 or 20 years.
We also designed it to support a variety of computer languages,
not just Fortran and C but also COBOL, Pascal, Ada,
Basic, lots of other languages.
And finally, in looking at bringing a new architecture
to the market, we focused on migrating our current customers
from VAXs and from MIPS stack stations
to the new architecture.
We rejected things like hardware compatibility mode
in favor of doing software translation of binary images
and doing compilers with compatible front ends
for source code re-completion.
Here's an overview of the architecture.
It looks very much like other risk machines on the surface.
It's a 64-bit load store risk machine.
Instructions are 32-bits wide.
They describe operations on one, two, four,
or eight byte integers.
They describe operations on VAX single precision
and double precision floating point,
also IEEE single precision and double precision floating
point.
There are 32 integer registers, each of 64 bits.
One of those is hardwired to zero, and one of them
is used as a stack pointer.
There are also 32 floating point registers, each 64 bits,
and one of those is hardwired to zero.
In addition, the program counter is
a full 64-bit virtual address, and there
are a number of other counters and registers
to allow writing real operating system multitasking software,
things like a cycle calendar for performance studies, floating
point status register for IEEE rounding modes and status bits.
64-bit virtual addresses are used
throughout the architecture, although implementations
are allowed to implement a subset of the virtual address
space so long as they check the high order unimplemented bits
for validity.
The instruction formats are very straightforward.
There is a 6-bit opcode in every instruction.
The first format has call pal instructions.
It has a 26-bit function field that
describes one of a few dozen privilege subroutines
that are used to execute a number
of the complex operations that operating systems are
built on top of.
I'll discuss those operations in a few minutes.
The branch instructions have the six bit opcode
and then a five bit register number
to be tested that can be tested for positive, negative, zero,
non-zero.
In the case of the integer registers, even an odd,
and then there's a 21-bit sign displacement
field that's actually a long word displacement
describes relative to the branch instruction
a target address instruction anywhere within plus or minus
four megabytes.
The memory formats have the six bit opcode and then
two register fields of five bits each.
The first field specifies the register
to be loaded or stored.
Second field specifies the base register for the memory
address, and the remaining field specifies a 16-bit sign
displacement from the base register.
The last format is used for operations between registers,
six bit opcode, two source registers, a function
field that's really an extension of the opcode,
and then a destination register.
All the operations read two registers
and write one register.
They read all 64 bits of the registers,
and they write all 64 bits of the registers.
One of the simplest ways of characterizing
how this architecture is different
from other architectures is by what's not there.
There are no condition codes in the Alpha architecture
because they get in the way of doing multiple issue.
If you issue six instructions at once
and they all potentially set the condition codes
and there's really only one condition code register,
then you have to build a lot of very messy hardware
to sort out which of those six instructions
actually sets the condition code register.
And if the fourth of those takes an overflow and then, in fact,
you discover on the fly that the third one
has to set the condition code register, getting
that right is very messy.
So the condition code register is
sort of an example of a design technique
that's difficult to use when you're planning
on multiple instruction issue.
So we don't have it in Alpha.
In a similar way, we don't have fixed registers
for operations such as string pointers or multiply or divide.
Some architectures have a multiplier quotient register
or dedicated registers for other operations.
The problem with that is if you try
to launch like four instructions at once
and they're all multiplies and there's only
one multiplier quotient register, it's a bottleneck.
You either have to build hardware
that shadows multiple copies of the MQ register,
or you can't launch multiple multiplies.
So we simply don't have it, and all of the operations
are completely general between the general registers.
Finally, we don't have byte writes.
If you look at implementations of writing a single byte
memory, somewhere in real implementations,
memory is accessed in complete memory
words, which are typically four or eight bytes wide.
Updating a single byte in a memory word
somewhere involves reading the entire memory word,
possibly correcting a single bit or double bit
error if the memory system has error correction,
then updating the single byte, and then recalculating
any error correction or parity bits,
and then finally writing the entire memory work.
And that read, modify, write is really a sequence.
And if it's implemented in hardware in the memory board
or in the CPU chip near the pins,
wherever it's implemented, there's
a tendency to only have one implementation
of that sequencing hardware, and that
makes it difficult then to launch
six independent byte writes and to have them run in parallel.
Again, the byte write logic becomes a bottleneck.
So we took the fairly radical move in Alpha
of not having byte writes.
Instead, there are carefully tuned instruction sequences
that allow a program to do the read,
modify, write as a load and in-register modify,
and then a store.
We included in the architecture enough design information
to allow programs to do single byte
updates within the same memory word
even in the face of a other processors updating
adjacent bytes in that memory word, and that can all work.
And we expect over the course of a couple of decades
that removing that bottleneck will allow much faster byte
write or byte manipulation programs
than some other architectures that
have an instruction that looks like it would help instead.
Finally we tried to avoid having first implementation artifacts.
In contrast to doing no chip design
and shipping the first chip and, if it's successful,
then designing another one and shipping that, and it's
a little bit different, we tried to design into the architecture
solutions to all of the design problems
we were aware of even when that made the first chip
implementation more difficult.
Finally, we have included in the architecture careful support
for multiprocessing.
One of the design issues in multiprocessing
is how to do atomic updates in memory
between shared memory locations for multiple processors.
We picked a paradigm of load locked modify store conditional
that is also used in the MIPS R4,000 architecture.
That particular paradigm we chose
because it's the only thing we could find that
would scale up with processor speed over a couple of decades.
The idea is to do a load instruction but using load
locked, which remember a few bits of state,
then modify by doing an add or an or or whatever
the modification is in registers and then
do a store conditional instruction back
to the shared memory location.
If that entire sequence from the load locked through the modify
to the store conditional, if that sequence
runs with no interrupts and no exceptions and no interfering
write from another processor, then the store
conditional stores and leaves a bit
in register for the program to test
to determine that the store actually occurred.
If anything goes wrong, if there's
an interrupt in the middle, if there's an exception on any
of the operations, or if there's an interfering
write from another processor, then the store conditional
doesn't store and instead sets the bit
saying that it did not store.
And eventually, then, the program
needs to branch on that bit and go back around and start again
with the load locked, retry the modification and the store
conditional.
When the entire sequence runs to completion,
then, in fact, an atomic update has
been done with no interference.
So there are two advantages to this paradigm.
The first is that in the absence of an interfering write,
the entire sequence of load locked byte modify store
conditional can be done inside of an on-chip processor cache.
The other advantage is that multiple processors
can be doing independent sequences in parallel.
There's no common lock bit somewhere
in a shared memory system.
For multiple processor support, we also designed around
read write ordering.
Rather than the traditional strict read write ordering,
we specify that in the Alpha architecture,
from the point of view of a second processor,
reads and writes launched by a first processor
can arrive at the second processor in an arbitrary
order, and then we included a memory barrier instruction
to limit the permutations allowed in an implementation.
I'll talk about that more in a few minutes.
Finally, there's the usual number
of registers for writing real software to keep track
of which processor you're running on
and thread-specific context.
Here's a few things that are different in the Alpha
architecture compared to traditional risk architectures.
First, we tried to avoid first implementation difficulties
and instead to do clean features that we
hope will last many decades.
For example, rather than architecting strict read write
ordering, as I just talked about,
we architected arbitrary read write
ordering between processors and a memory barrier
as a mechanism to specify the ordering exactly when it's
needed.
It's possible that other architectures that
specify strict read write ordering
will find that that's a performance bottleneck
over the coming decades.
We also looked at issues like branch prediction
and bearing the latency of branches
rather than doing something like a branch delay
slot that could bury one cycle this year or maybe two cycles.
We looked at the long term and said
that that's a technique that will not
scale up well for a couple of decades
and will not scale well with multiple instruction issues.
So we looked at other ways of solving branch latency
problems.
Similarly, we don't have an Alpha complex instructions
that do multiple operations such as a multiply
add operation, which is really two operations in one
instruction.
We instead seriously designed to allow
implementations to gracefully do multiple instruction issue.
We believe that, through multiple instruction issue,
the same effects you get with the complex combined
instructions can be achieved in Alpha
but achieved in a more straightforward way
over the coming decades.
We also designed a hazard-free architecture.
This means that if an instruction writes
to a register and a later instruction
reads from that register, that the reading instruction always
gets the written value, that implementations must implement
whatever pipeline interlocks or score-boarding are
necessary to deliver the same results on all implementations,
that there are no hazards in some implementations where
a register can be read too soon and get an old value
and be not binary compatible with a different implementation
with different timing where the register turns out to be read
later and get a new value.
We also designed for the Alpha architecture
to have minimal global state.
We've discovered, particularly in implementing of VAXs
that having global state bits such as interrupt enables
or rounding mode bits gets in the way of building very
fast pipeline implementations.
The tendency is, when global state bits are changed,
to completely drain the pipeline,
change the global state bits, and then restart the pipeline
in order to guarantee that subsequent instructions see
the state change and the previously issued instructions
don't see the state change.
And that draining and restarting of the pipelines
eventually becomes a performance bottleneck,
particularly if the state bits need to be saved and restored
at every subroutine boundary.
So rather than having global state bits,
we designed to minimize them and instead put
in a re-instruction, things like interrupt
enable bits or rounding mode bits,
and the entire instruction and its local state
can then be pipelined with no drains.
We also have no mode bit such as a 32-bit mode
versus 64-bit mode or VAX floating point
versus IEEE floating point.
This, again, minimizes the hardware complexity
and maximizes the fraction of time
that the pipeline can be kept full.
As we were doing the architecture design,
we kept in mind a number of implementation issues
in terms of keeping many operations going
simultaneously, having pipelined implementations,
having long memory latencies that would grow with time.
So we included a number of features
such as specifying imprecise arithmetic exceptions rather
than precise ones as an enabling technology
to allow multiple instruction issued
to be done gracefully without a lot of hardware overhead.
We also included things like pre-fetching operations
as a mechanism to cover some of the main memory
latencies that we anticipate over the coming years.
Finally, we included in the design
a discussion of hardware-software agreements
of suggested ground rules for hardware implementers
and also for software implementers
so that, to the extent that both groups use the same ground
rules, you end up with very high performance implementations
running high performance software with very little work.
One of the things that's different about the Alpha
architecture is the existence of PAL code.
PAL stands for privileged architecture library.
It's a set of privilege subroutines,
a lot like the basic input output system in an IBM PC.
They perform specified functions,
but how they are implemented inside the subroutines
can vary from one implementation to another.
The specified functions are the complex operations
that are an operating system is built on top of such
as taking or returning from an interrupt or exception
or loading the memory management registers or context switching.
The PAL subroutines run with the interrupts turned off,
and they may have implementation-specific access
to physical hardware registers.
There's a different set of PAL code
for each operating system that allows the operating
system to run on what's really an extended machine.
For example, the port of VMS to alpha
runs with a set of PAL code that provides
VAX-like 32 interrupt levels and four memory management modes
and a number of the complex operations
that build interrupt vectors and include interlocked Q
instructions.
By having all of those in software,
the actual Alpha hardware is quite simple,
but the VMS port took two or three years less
than if VMS had been completely rewritten to avoid
using those VAX features.
In a similar way, the OSF/1 operating system
runs with its PAL code that presents to the operating
system a simple, Unix-like view of the world that does not
have 32 interrupt modes.
It has eight interrupt modes and a number
of other memory-mapping features that many Unix systems
are used to.
In a similar way, the Windows NT operating system
has its own PAL code that presents
a view of the world that's culturally
compatible with the 46 and the MIPS and T implementations.
So those are some of the things in the Alpha architecture
that are different from other risk architectures.
Now I'm going to talk about a number of the design problems
we had in trying to build a fast architecture to allow
fast implementations.
First design problem is branch latency.
The problem is when there's a discontinuity
in the instruction stream, when a branch goes off
to some other instruction, how to bury the time that
must occur in real implementations
before fetching the target instruction.
Some implementations or some architectures
specify branch delay slots in which, after a branch has
executed, the following instruction is also
executed on the theory that in real implementations
it's already been pre-fetched, so executing it is almost free,
and it can cover up the cycle of latency
while the target address is being fetched.
That was a good design technique in 1988,
but already we've reached the point where,
in real implementations that are quite fast,
the branch latency that you need to cover up
is two or three cycles instead of just one cycle,
and an architected branch delay slot can only bury one cycle.
In addition, branch delay slots don't make any sense
when you are anticipating implementations
that issue multiple instructions,
and different implementations issue different numbers
of instructions.
For example, if you have an implementation that
issues a peak of three instructions every cycle
and you have a branch delay slot that
specifies one instruction is executed after each branch,
then after every branch, the other two potential issue slots
have to be wasted.
You can only issue one of those three following instructions.
If branches occur about every eight instructions,
that can be quite a performance bottleneck.
So rather than branch delay slots,
we went back to the original problem
of covering branch latency and looked for other solutions
that we thought would scale well for a couple of decades.
We didn't find any magic bullets,
so instead we approached covering branch latency
on three different fronts.
First, we architected branch prediction hits
for two-way branches.
The rule is that a two-way branch that is a forward branch
is predicted not to be taken, and a two-way branch
that's a backward branch is predicted to be taken.
To the extent that the hardware implementers and the compiler
writers follow the same rule, you
get a very fast, very simple implementation
that involves less hardware than branch prediction caches
or things like that.
For branches that have calculated targets
such as some subroutine calls or case
statements or subroutine returns,
there were bits left over in those instructions.
So we use those bits as hints to the implementation about where
the most likely target is.
The low 14 bits of those branch instructions
can be used to directly drive an instruction
cache with the address of the most likely target.
Because they are hints, implementations
need not use them.
Also, because they are hints, if the hint is wrong,
implementations must eventually, although perhaps more slowly,
fetch the correctly specified target
and actually branch to it.
We also specified in those hints whether a given
calculated branch was a subroutine call or subroutine
return or neither.
That's enough information for an implementation
to run a small stack of likely subroutine return addresses.
And in fact, as Dirk will talk about in the first EV4
implementation, there is a four level subroutine return stack.
That allows an implementation, when
it encounters a subroutine return,
to pull off the top of that implementation stack, the most
likely I-cache address to fetch from
and, under good circumstances, have subroutine returns take
zero or one cycles.
That's an example of how we thought about problems, looked
at existing solutions in the marketplace,
asked the question of whether they would scale well
over the coming decades.
When the answer was yes, we used them
and did not deviate from conventional risk
architectures.
When the answer was no, we said, what problem
are these techniques solving?
Go back and look at the problem with a clean sheet of paper
and say, how else could the problem
be solved in a way that would scale well
over a couple of decades?
The last thing we did in trying to bury branch
latency is to include conditional move instructions
which test a register and, depending
on the result of the test, conditionally,
move a second register to a third register.
These can be used for simple cases of testing end defects
or doing maximum or minimum.
And when they are used, they completely eliminate branches.
By eliminating branches, eliminate the issues
of branch delays, store branch latencies, and also, in fact,
make basic blocks bigger and allow
compilers to do more optimization and more code
movement.
So in summary, we rejected the traditional branch delay slots
because they're incompatible with multiple instruction issue
and with scaling up performance over many decades.
We instead looked at fresh mechanisms that
would give the same effect.
Another design problem we looked at
was how to handle arithmetic exceptions, things
like overflows or divide by zero.
Some architecture specify precise exceptions
in which if an instruction gets an arithmetic exception,
the following instructions must not have executed.
Now, that makes it very difficult
to do things like four-way instruction issue.
You either cannot issue multiple instructions that could
possibly get arithmetic exceptions,
or you have to issue them and build a bunch of hardware,
things like trap silos, that back out of the instructions
that really should not have happened if you issue four
and the second one overflows.
So rather than specifying precise arithmetic exceptions,
we specified imprecise arithmetic exceptions.
We said, if you get an overflow, you find out about it later.
Because the architecture is not tied
to any particular implementation,
the architecture doesn't say how much later.
It just says later.
It's the Cray-1 model of exception handling,
but there are completely legitimate reasons
for people to want to get precise exceptions.
So we also included a trap barrier instruction
that specifies, of all the instructions
that have been issued at the barrier,
all the preceding instructions must deliver and the exceptions
that they're going to deliver at the barrier or earlier,
and then subsequent instructions can be issued.
So if you write a program with no trap barriers,
it runs at vector-like speeds, and you
get information on something like an overflow that it
occurred but not very precise information
on where it occurred.
If you do something like put a trap
barrier at the end of every subroutine,
that may involve draining a few cycles of the pipeline
at the end of the subroutine, but it then
localizes where the exception occurred to which subroutine.
If you put the trap barrier at the end of every source
language statement, you can localize
the generation of an exception to which statement it was.
And if you put a trap barrier after every single instruction,
you can discover exactly which instruction generated it.
Because the IEEE floating point standard
has an extensive set of exception handling,
we also included in the architecture manual
a design that allows one trap barrier per basic block,
rather than one per instruction, and still allows
complete IEEE exception handling to be performed in software
in the trap routine.
The basic idea is that if there's a software constraint,
that the compiler is not reuse registers within a basic block
or not reuse registers before the trap barrier
so that a software handler can successfully
backtrack through the instruction stream,
find out which exception occurred first,
process that exception, and then continue.
That allows a substantially faster implementation
than stopping after every single instruction
and waiting for any exceptions to occur.
In addition to the trap barrier, we
included exception enable disable bits
in every instruction so that integer add, for instance,
can be specified in the instruction
to take an overflow trap or not to take an overflow trap.
That allows languages such as Fortran that require overflows
to be generated to simply compile into the instructions
to do the overflow and also allows languages such as C,
which generally specify no overflow trapping,
to compile into the instruction not to overflow.
In a similar way, for floating point underflow,
disabling the underflow allows the program
to simply give a result of 0 and keep going.
Some of our customers have expressed
a preference for that behavior.
The whole point here is that by choosing compiler options,
you get to choose what level of performance
and what level of precision on exceptions you want rather
than me choosing for you.
So in summary on the arithmetic exceptions,
we rejected an architecture that would require hardware
implementations that issue multiple instruction,
silo register values, and then have to roll back
if an exception occurs.
We also rejected implementations that read operands
and then check whether an overflow or something could
possibly occur.
And if so, delay issuing other instructions into the pipeline
until the exception decision has been made.
That doesn't work for multiple issue,
and it doesn't work well for single issue
if the read of the registers and the check of the values
is actually two or three stages down the pipeline.
Another problem we looked at was memory latency.
Over the course of a few decades,
maybe memory latency will get longer and longer as processor
speeds get faster and faster, but memory chip speeds don't.
So we included in the architecture
a pair of pre-fetching instructions
as hints to an implementation.
For example, a Fortran program working down
one column of an array could issue
the pre-fetching instructions to be preferred
the second column of the array during the processing
of the first column.
It looked likely that those instructions
could bury about 100 cycles of memory latency
in a realistic way.
We also looked at memory latency and multiprocessor systems.
We specified that there is no implied read write ordering
in the following sense.
If one processor issues a sequence of reads and writes,
they are allowed to arrive at a second processor
in an arbitrary order.
Implementations are allowed to rearrange reads and writes.
That's an enabling technology that allows things
like write buffers, or multi-bank caches,
or routing networks between processors
if you have lots of processors, or things like memory buses
that do error detection and retry.
For example, if you have a processor that
does two writes and you pipeline those writes down a memory box
and the first right arrives with bad parity,
the second write then arrives, and then the first write
is retried and arrives successfully
with good parity on the second try.
That's an example of a good implementation technique that
has the effect of delivering the writes out
of order to another processor.
We wanted to allow implementations
to run at very high speeds.
And therefore, in the architecture,
we do not require precise read write ordering.
Instead, there's a memory barrier instruction that
says, at this point, I care.
At this point, all the reads and writes
that have been issued by a given processor in front
of the memory barrier have to be delivered to other processors
before any reads and writes issues
after the memory barrier.
And the phrasing was chosen carefully.
It doesn't say that the sending processor
has to stop issuing instructions or run slowly.
It simply says that whatever permutation
could occur in the implementations,
that there is a restriction at the memory barrier.
That may mean, for instance, that the memory barrier itself
is pipelined out along with reads and writes
to other processors, or it may mean exactly
at the memory barrier a pipeline memory bus sends a write
and then waits a few cycles to get confirmation
that that right arrived successfully
before sending writes that come after the memory barrier.
So we view this as an enabling technology for very high speed
implementations.
It's one of the design ideas that
led Cray research to choosing the Alpha architecture
for building 1,000 processor or bigger massively parallel
processor.
So in summary on the memory system design,
we rejected things like implicit read
write ordering in favor of explicit programmer
statement of where overlap can occur
and where overlap must not occur.
So the Alpha architecture is the new 64-bit architecture
designed to last a long time, designed
to allow high performance implementations
with specific emphasis on allowing multiple construction
issue, multiple processors, and very fast clock rates.
The architecture supports a very wide range of operating systems
and compiler languages.
Some of you who looked at earlier tapes in this series
may have seen Dave Patterson when
he predicted that by somewhere between 1993 and '96,
there would be super microprocessors running
anywhere from desktop to supercomputers.
We're very happy with Alpha to deliver on Dave's prediction
a year early.
I think now it's time for Dirk to talk
about the first implementation.
As Dick said, I am here to tell you about the first Alpha
implementation, which internal to [INAUDIBLE],,
we refer to as EV4.
Since I'm going to use that term throughout this presentation,
first a couple of words on the term.
When the program started, the original name
for the architecture, which the designers came up, with
was EVAX for extended VAX, which turned out in retrospect
to be not a very good name because the architecture,
in fact, has no formal relationship to the VAX
architecture.
However, we're engineers and not marketing people.
So that was the name we chose at the beginning it was later
changed to Alpha.
The name of the chip, however, has
remained EV for extended VAX four
because it was built in a fourth generation seamless process.
The presentation that I'm going to give has four basic parts.
First of all, I'm going to describe
some of the overall features of the chip,
then I'm going to describe some of the higher level
architectural features such as the pipelines,
the functional units, and the instruction issue rules.
I will next describe just a couple
of the more interesting micro architectural features EV4
and relate those back to the higher level
architectural concepts that Dick described previously.
And finally, I will talk a little bit
about the electrical interface between EV4
and the systems into which it's designed.
EV4 is a single chip implementation
of the Alpha architecture.
It's implemented in CMOS technology
and has two cycle time variance, the first of which
operates at a 200 megahertz internal clock rate,
and the second variant operates at 150 megahertz.
Now, these two speed variants don't really
represent separate designs.
They really represent distinct speed bins.
So it's one design, and various chips
happened to fall in one speed bin or the other.
Some work faster than others, and the ones that run fast,
we run fast.
As I said, the chip is built in a CMOS process.
It's built in digital CMOS four technology,
which means that it's our fourth generation CMOS technology.
This technology features 0.7 micron drawn feature sizes
with a 0.5 micron effective channel length that
also features three layers of metalization,
and the technology is really focused towards high speed
microprocessor implementations.
For example, the third layer of metal
is different than upper layers of metal in other CMOS
technologies where the metal is intended to maximize signal
routeability.
In this case, we use a third layer of metal
solely to distribute power and our internal high-speed clock.
And therefore, it's a much thicker, coarser, metalization
level which can both bring a lot of power into the chip
and distribute the clock with minimum skew.
The die, physically, is fairly large.
It's about 14 millimeters by 17 millimeters in size,
and it implements about 1.7 million transistors.
The part is packaged in a 431 pin grid array
of which 291 pins are signals.
The remaining 140 are power and ground.
The chip dissipates 30 watts at 200 megahertz or 23 watts
at 150 megahertz.
It implements a 43-bit subset of the architected 64-bit virtual
address space.
As Dick mentioned previously, implementations
are allowed by the architecture to implement a subset
of the 64-bit virtual address.
However, the entire 64 bits is checked.
The chip also supports a 34-bit physical address space,
giving us the ability to address some 16
gigabytes of physical memory.
The chip is a super scalar implementation in the sense
that it can issue, at its peak, two instructions in each CPU
cycle to any one of four fully pipelined functional units.
The functional units include an integer operation unit,
a floating point operation unit, a load storing unit,
and a branch unit.
The chip also includes a total of 44 translation lookaside
buffer entries, 32 of which are dedicated
to data stream references and 12 of which
are dedicated to instruction stream references.
Both translation buffers are fully associative.
There is an on-chip right buffer which
includes four entries where each entry can
contained 32 bytes of data.
The chip also includes two on-chip caches, 8 kilobytes
in size, one devoted to instruction stream references
and one devoted to data stream references.
Both of them are physical, direct map caches.
The data cache is right through.
The bus interface is flexible and can support system designs
with either 128-bit or 64-bit data paths between the system
and the chip.
And as previously mentioned, the external interface
supports a 34-bit physical address.
The next graphic shows a crude block diagram of the chip.
Starting from the top, you see an instruction cache,
which each cycle can feed to instructions
to the IBOX or instruction issue a unit of the chip.
The IBOX is responsible for decoding the instructions,
performing all on-chip resource checks,
and sending the instructions off to the respective functional
units.
The EBOX is an integer operate unit
which performs basic integer operations such as add,
subtract, or, and shifts.
The FBOX is the equivalent floating point operate unit.
Integer register file sits below the EBOX
while the floating point register
file sits below the FBOX.
The diagrams in the picture represent data buses
which represent data flowing to and from the functional
units from the register file.
You can correlate Dick's discussion of the instruction
formats laid out in the Alpha architecture
to the physical orientation of the chip itself.
From the top of the integer register file,
you see two 64-bit wide buses which carry integer operands
to the EBOX and another 64-bit bus which
carries integer results back to the integer register file.
Similarly, on the floating point side of the chip,
you see twp 64-bit buses which carry operands
to the floating point functional unit and another 64-bit bus
which carries results back to the register file.
Below the register files sits the ABOX.
The ABOX, fundamentally, is the load storage unit
within the machine.
You can see two register file parts feeding the ABOX.
One of these contains base addresses.
The other contains stored data coming
from the integer register file.
Similarly, on the floating point side,
you see a single 64-bit wide bus which
carries floating point stored data out to the ABOX.
Below the ABOX is the four entry by 32 byte wide right
buffer which gets its data from the ABOX
and supplies data out to the BIU or bus interface unit.
And lastly, at the bottom of the diagram,
you see that on-chip ship data cache
which, as I previously stated, is 8 kilobytes in size
and a write through cache.
The bus interface unit is responsible for orchestrating
activities between on-chip functional units
and the outside world, meaning the system.
It contains a 64-bit data path which
carries both filled data from the off-chip memory structures
to on-chip primary caches and also
carries write data from the write buffer out onto the pins.
The next graphic shows an integer pipeline
in the machine.
It is 7 stages long, and this diagram
can be used to describe the pipelines which
operate in the IBOX, the EBOX, and the ABOX.
Taking the IBOX first, you see fundamentally
four pipeline stages.
In stage zero, which we also refer to as the instruction
fetch stage, the IBOX reads a naturally aligned pair
of long word instructions from the instruction cache.
In the next cycle, the IBOX performs two primary functions.
First, it decodes a portion of the instructions
in order to determine which functional units
the instructions should be sent.
In parallel with that, it determines
for branch instructions the probable target for the branch.
The chip supports two methods for branch prediction.
In the first method, we support the architected hint alluded
to by Dick earlier in this talk where the hardware looks
at the sign bit of the displacement field
in the branch construction itself
and predicts that backward reaching branches are taken
but forward branches are not.
Alternatively, the chip also implements
a dynamic branch prediction structure in which,
associated with each instruction in the instruction cache,
is a single bit.
This bit indicates which direction the branch
took the last time it was executed.
When new instructions are brought into the instruction
cache, we load this bit with the sign
of the displacement as an initial guess,
and then this bit gets updated later.
Branches which are predicted by the hardware to be taken
result in a one cycle bubble being inserted
into the pipeline, meaning that if a branch instruction is
fetched from the instruction cache in cycle zero,
it takes all of cycle one to predict
the direction of the branch and generate
the target for the branch so that we are not
able to go back to I-cache for that target
until what would be cycle two for the branch.
This bubble, which gets inserted into the pipeline,
can in some cases be removed later.
In pipe stage two, labeled I0, we
perform more instruction decode.
And in pipe stage three, labeled I1, all the real action
takes place.
In that pipe stage, we read operands from the register
file and perform resource checks to see whether the instruction
can be issued.
These resource checks include both the availability
of register operands as well as the availability
of the on-chip functional units.
After pipe stage three, instructions either
execute to completion and write the results in the register
file, or perhaps they get aborted for a host of reasons,
which I'll describe later.
Within the IBOX, there's still a few more activities
that happen later down the pipe.
As Dick mentioned, conditional branches
test a register in order to decide
whether to take the branch.
Since we read the register in cycle three,
we can test its value against zero
in the beginning of cycle four and be in a position
to determine whether the branch should in fact be taken or not.
Notice then that although a branch instruction is fetched
from the instruction cache in cycle zero,
we don't know whether it's going to be taken until cycle four.
What this means is that if the branch was predicted
incorrectly, we incur a four cycle penalty
to go back and get the true branch target.
Also in cycle four, we're in a position
to generate the true virtual PC for the instruction
one stage back in the pipeline.
So we have this virtual PC at the end of pipe stage four.
In pipeline stage five, we can, from this virtual PC,
generate the corresponding physical PC
so that, at the end of the pipe stage five
in the beginning of pipe stage six,
we can determine whether the instruction that we
fetched five cycles ago, in fact,
hit in the instruction cache.
So this means that if an instruction fetched
in pipe stage zero missed the cache,
we don't know it until five cycles later.
Moving on to the activities which happen in the EBOX,
I'll start in pipe stage four.
In that pipeline stage, the box has two 64-bit operands
on which to perform an operation.
Most operations in the EBOX can be performed in a single cycle.
These include add and subtract and the simple logic functions
like add, and, and x or.
So at the end of pipeline stage four,
the results of these instructions are ready for use
by subsequent instructions, although the results won't
be, in fact, written to the register file for two more
cycles.
We can supply the data on bypass paths
back to the multiplexor which physically
sits between the integer register file and the EBOX.
Shift operations take two cycles to complete.
The results of shifts are therefore
available in pipe stage five.
Shift operations therefore have a latency of two cycles,
although they can be fully pipelined.
Also in pipe stage five, we produce,
based on the results generated in the EBOX,
a bit which indicates whether that result is zero.
We then store that bit in the register file.
This makes branch instructions easier to execute.
Lastly, in pipe stage six, the EBOX
writes its results into the register file.
Now, referencing the same diagram,
I'm going to describe the operations which
occur within the ABOX.
The box receives its instructions
issued from the IBOX at the end of pipe stage three.
In pipe stage four, it takes the operand
supplied to it from the register file and adds to it
the displacement.
Thus, at the end of pipe stage four,
it has an effective virtual address.
Because the size of the data cache
is equal to the size of the pages used by the memory
management system, we can start looking
in the decache at the beginning of pipe stage five.
We don't have to wait for the translation buffer
to produce a full physical address.
Therefore, at the end of pipe stage five,
we have on our hands the physical address
that corresponds to the generated virtual address
and the data which hopefully corresponds
as well to that virtual address.
We are in a position to know whether a load
instruction actually hit or missed at the beginning of pipe
stage six.
And if the instruction hit in the data cache,
we can supply the results of the instruction
to the functional units by the end of pipe stage six.
Hence, load instructions which hit in the data
have an effective latency of three CPU cycles.
For stores, we know as well at the beginning of pipe stage six
whether the store instruction hit in the data.
If it missed, no further operation
is required for the data cache.
The data is moved into the right buffer
independent of the results of the data cache lookup.
If the lookup resulted in a data cache hit,
we write the corresponding data into the data cache array
the next time that that array is otherwise not busy.
Turning next to the diagram which shows the floating point
pipeline, you see that floating point instructions share
the first four pipeline stages with integer instructions,
and those really correspond to activity, which I previously
described in the IBOX.
The floating point pipeline is quite a bit longer
than the integer pipeline, and floating point results
are not available until six CPU cycles
after the instruction was issued.
The floating point unit is fully pipelined, however,
so that we can start a new floating point
operation every CPU cycle.
However, as I said, the effective latency
of this functional unit is six CPU cycles.
Now I want to talk about the instruction
issue rules which hardware within EV4 enforces.
Generally, if you think back to the diagram which
showed the functional units within the chip,
you can see that the possibility for multiple instruction issue
is certainly there.
We have four fully pipelined units,
and the IBOX ought to be able to issue pairs of instructions
to any of these units.
And generally, that's the case.
We can issue load restore instructions
with operate instructions.
We can issue integer operator instructions
with floating point operate instructions.
We can issue floating point operator instructions
with corresponding floating point branches or integer
operator instructions with integer branches.
One quirk is that integer stores can't be issued together
with floating point operates, nor can floating point stores
be issued together with integer operates.
This is due to a internal hardware resource constraint
in the IBOX which, although not real difficult to get around,
wasn't worth the added complication in terms
of the performance that it would have bought us.
Next, I want to address a couple of
interesting micro architectural features which we
included within the EV4 chip.
The first of these addresses the branch latency problem,
which Dick referred to earlier.
Specifically, it addresses the problem associated
with memory format branches.
These instructions are used in computed jumps,
subroutine calls and returns and the like.
The instructions read the register
file and jump to the virtual address contained
within the operand register and represent
a special problem for the instruction pre-fetch hardware.
Thinking back to the earlier diagram which
showed the machine's pipeline, remember
that the branch prediction hardware
runs in pipe stage one of the machine
while registers aren't read until two cycles later.
Therefore, in order for the prediction hardware
to come up with a target for the branch,
something has to be done in hardware
if we want to come up with a target early in the pipe stage,
since the actual target isn't available until pipe stage
three.
The solution here is to implement
what we call a JSR stack.
This stack allows subroutine and call and return addresses
to be maintained back in the pipeline
by the instruction pre-fetcher and also requires
use of hint bits.
On a subroutine call, the compiler
inserts into the instruction a 2-bit field
which tells the hardware that this instruction is actually
implementing a subroutine call.
The prediction hardware then takes the virtual address
following the instruction, following the subroutine call,
and places that on a hardware stack.
When the subsequent return comes along,
we then pop that stack and use that
as a target for the instruction.
This JSR stack has a depth of four,
meaning it's capable of holding at any one time
a stack of four subroutine return addresses.
We also include hardware to make sure
that this stack doesn't get corrupted.
The way it might get corrupted is as follows.
We could fetch from the instruction cache
an instruction that's a call.
We might then send this instruction down the pipeline.
And later, for whatever reason, an exception, a branch
mispredict, an instruction cache miss,
the call instruction may never get executed.
If we updated the stack when we fetched the instruction rather
than when we committed to executing the instruction,
the stack could therefore get out of sync
with the rest of the machine.
Note that this is a performance optimization
and if the hardware stack ever gets out
of sync with the real programmed flow,
we do not end up with incorrect operations.
We merely take a little bit longer
to fetch the target of a call or return.
The next feature that I want to describe
relates to granularity hints.
The Alpha architecture uses a 2-bit field
within each page table entry, which
can be used by software to communicate
to hardware that a particular page table entry in fact maps
greater than a single page.
This 2-bit field can be used by the software
to communicate to the hardware that a given page table
entry in fact maps a contiguous physical region consisting
of one page, eight pages, 64 pages, or 512 pages.
Since EV4 implements an eight kilobyte page,
this translates to a region of 8 kilobytes, 64 kilobytes, 512
kilobytes, or 4 megabytes in size.
The data stream translation buffer contains 32 entries,
as I previously stated.
Each of these entries is able to support
any of the four granularity hints specified
by the architecture.
This allows a very flexible arrangement for page table
entries and also allows a relatively small
associative translation buffer to in fact map
a large memory space.
The size of the page mapped by each entry
is set when the page table is written by PAL code
into the translation buffer structure.
The istream translation buffer consists of 12 entries.
Eight of these entries are devoted to the smallest
granularity size, meaning that each of those eight entries
can only map an eight kilobyte region
corresponding to a single page.
Four of the entries, however, support the largest granularity
size, which means that each of those entries
can map a four megabyte region.
This is very useful for mapping large, non-paged instruction
areas such as the operating system kernel or large shared
libraries.
I'm next going to describe EV4's external interface.
This interface was designed with several goals in mind.
First of all, we needed the interface
to be extremely flexible.
Since this is the first chip in a new architecture
and since we didn't have the resources inside the company
to devote several design teams each to doing
an implementation targeted towards a particular end
of the system-wide spectrum, the single design
needed to be used in systems ranging from low-end desktop
machines to mid-range multiprocessor servers
all the way up to high-end, massively parallel
supercomputer systems.
With that in mind, we needed each system designer
to be able to make his or her own cost performance
trade-offs.
Lastly, because of the schedule constraints
confronted by our internal system partners,
we wanted to define an external interface around which you
could design a system using off the shelf industry standard
components.
The next graphic shows a crude schematic
for the external interface of the EV4 chip.
The chip takes as its input a 2x clock.
So for example, a chip that operates internally
at 200 megahertz requires a 400 megahertz input oscillator.
This is the only high speed signal
that a system designer really has to worry about.
The rest of the interface can be programmed
to operate much more slowly.
The chip also supports an external cache,
although this cache is not required
by the chip design itself.
Its presence or absence is left solely
up to the system designer.
For systems which do implement an off-chip cache,
the chip is capable of accessing that cache
with no help from external system-level logic.
So for example, if a load instruction
misses the on-chip cache, the on-ship BIU
can read the off-chip cash rams with no interaction
from the outside world.
This relieves the system designer
from having to design logic which has to directly interface
to the high speed clocking domain inside the chip.
The graphic shows three RAM structures which
make up the external cache.
The tag control RAM consists of four bits, a valid bit,
a shared bit, a dirty bit, and a parity bit
which contains parity across the previously mentioned
three bits.
The tag RAM contains a tag corresponding
to each block and the external cache,
and the data and check RAMs contain the cache data
and associated check or parity bits.
As I previously stated, the external bus
can be either 64-bits or 128-bits wide, as determined
by the system designer's need.
To begin a cache reference, the chip drives an address out
onto its address bus.
This address and associated RAM control flow
through system-dependent logic which physically
sit between these buses and the RAMs.
The RAM access is completely combinatorial.
The chip drives a new address out under the RAM
and then waits a user-specified period of time
before sampling the associated tag and data fields.
Some external transactions cannot be required by EV4 alone
using the external cache RAMs.
And for these transactions, system-level components
have to get into the picture.
Information between EV4 and these components
flows between the miscellaneous control
field listed on this graphic.
This field consists of a command field
onto which EV4 drives commands such as read, write,
memory barrier, load locked, et cetera, and a pair
of acknowledgment fields, which system-level components drive
back to EV4.
These control fields operate synchronously
with the internal chip clock but phase aligned to a system
clock, which is programmable.
This system clock can be user specified
to operate at frequencies ranging from 1/2 to 1/8,
the on-chip CPU cycle time.
In keeping with the previously mentioned
external interface goals of performance, flexibility,
and simplicity, EV4 supports external caches
ranging in size from 128 kilobytes to 8 megabytes.
Or, as I previously said, we could have no cache at all.
The external cash, if implemented by the system
designer, can be built with a variety of industry standard
RAMs.
High-end systems can use the fastest,
largest static RAMs they can buy while low-end systems can use
smaller, cheaper, slower RAMs.
The RAM timing is set by software
and can range from three cycles to 16 CPU cycles,
determined by the value which software
places into an on-chip control register.
The external interface, although it runs synchronously
to the on-chip CPU clock, can be specified
to run anywhere from 1/2 to 1/8 the speed
of this on-chip clock, again, allowing cost performance
trade-offs to be made by the system designer.
Lastly, there are no cache policy decisions
enforced by the EV4 chip.
This means that it's left to the system designer or to decide
what cache coherence protocol is appropriate for him or her.
The valid, dirty, and shared bits,
as I previously described within the tag control field,
imply a bias towards a conditional
write through cache coherence protocol in name only.
Really, the shared bid simply specifies to EV4 for that it
cannot write to an external cache block by itself
but requires external module-level interaction
in order to complete the write.
This means that, in addition to a conditional write
through protocol, the system designer could implement
a pure ownership coherence protocol where the shared
bit becomes really more of an ownership bit,
or the system designer could implement
a pure write through protocol where the shared bit is always
set.
This means that EV4 will always request help
from module-level components before writing data
into the external cache.
The external interface also allows the system designer
to implement either a 128-bit wide or 64-bit wide
external bus.
The chip supports both CMOS, TTL, and ECL
and supports both longword error-correcting codes
or longword parity.
The chip does not perform on-chip invalidate filtering
for decache and validates but does provide a mechanism
so that system designers can filter
these invalidates off-chip by implementing a back map.
Here's a die photograph of EV4.
On the left is the 8 kilobyte instruction cache
with branch history table located just below that.
Across the top of the die, you see the instruction
execute unit with the EBOX on the left
and the IBOX on the right.
In the middle of the chip, you see integer control
with the clock driver extending horizontally
across the center of the die.
Below the clock driver is located the floating point
operating unit while the data cache occupies
the right edge of the die.
The right buffer can be seen in the upper right hand corner.
In conclusion, EV4 is the first implementation
of the Alpha architecture.
It's a 200 megahertz single-chip microprocessor
with a flexible interface to allow
it to be used in systems ranging from desktop workstations
to massively parallel supercomputers.
It'll be supported initially by both VMS and OSF/1
with other operating systems to follow,
and it's the first in the family with more implementations
to come.
Speaking as a hardware system designer,
I'm grateful for people like Dick Sites who
has thought long and hard about the problems faced
by high performance system designers
when designing the Alpha architecture.
I think that the advantages of this architecture
are apparent by the high performance achieved
by its initial implementation, and the advantages
will become even more apparent in the future.
I hope this tape has been beneficial to you,
and thank you very much.
[MUSIC PLAYING]