|
Many years ago I started writing a document on how to write efficient code for the Atari
Falcon030. Before I was able to finish the document, university studies started requiring more
and more of my time and eventually I found myself with no time left for the Atari. I forgot about
the document, but the other day I found it on my hard disk.
As more and more people seem to be interested in programming the Falcon030 again I have decided
to spend some time on finishing the document, or at least parts of it. The first section is about
the caches of the Motorola 68030 as I consider good knowledge about the caches being the single most
important factor in being able to write efficient code for the Falcon030.
If you read this and find any of the information useful, please let me know! It will help
me stay motivated, and to finish and publish more parts. You can find my e-mail address
here.
Thanks,
Daniel Hedberg
68030 Caches
Memory access is very expensive on the Falcon030 due to its poor 16-bit data bus, which is also
used by various other chips besides the CPU. Maximizing the performance requires good use of the
internal CPU caches.
The Motorola 68030 CPU of the Falcon030 has two on-chip caches, an instruction cache and a data
cache. The caches improve performance by reducing external bus activity.
Unfortunately, the caches are somewhat crippled by the lack of a proper 32-bit bus, and with it
the loss of burst mode. The two are really the same limitation. The 68030 can only burst over a
32-bit bus, so a 16-bit port rules burst out from the start. This was not something Atari could
influence. As there is nothing to burst over on a 16-bit bus, the 68030's burst pins simply go
unused. In some ways, though, the 16-bit bus made the caches even more important. Every memory
access is so expensive that each cache hit counts for more than it would on a 32-bit machine.
The caches are controlled by the CACR: Cache Control Register.
| CACR: Cache Control Register |
| 13 | 12 | 11 | 10 | 9 | 8 | | | 4 | 3 | 2 | 1 | 0 |
| WA | DBE | CD | CED | FD | ED | | | IBE | CI | CEI | FI | EI |
| WA: | Write Allocate |
| DBE: | Data Cache Burst Enable - Not supported by the Falcon030! |
| CD: | Clear Data Cache |
| CED: | Clear Entry in Data Cache |
| FD: | Freeze Data Cache |
| ED: | Enable Data Cache |
| IBE: | Instruction Cache Burst Enable - Not supported by the Falcon030! |
| CI: | Clear Instruction Cache |
| CEI: | Clear Entry in Instruction Cache |
| FI: | Freeze Instruction Cache |
| EI: | Enable Instruction Cache |
The CACR register can be written to using the movec instruction, e.g., to enable
and clear the data cache you would do the following:
movec cacr,d0
bset #8,d0 ; Enable data cache
bset #11,d0 ; Clear data cache
movec d0,cacr
Instruction Cache
The instruction cache has 256 bytes with 16 lines (4 longwords each).
All longwords/entries of a cache line share the same tag value (the 24 most significant address
bits).
The instruction cache is fairly simple. Instructions are loaded into the instruction cache, one
longword at a time as the instructions are fetched from memory. Whenever an instruction is executed
multiple times and the memory address of the instruction is present in the cache, the instruction is
fetched from the cache rather than from memory.
Even without taking the instruction cache into consideration when writing code you will benefit
from it, but with some extra care you can easily boost the performance of your code even more:
- UNROLL LOOPS
Unroll loops to maximize the use of the cache, but make sure the size of the loop does not exceed
256 bytes.
- INLINE SMALL SUBROUTINES (FUNCTIONS)
If you call a subroutine from a loop that fits in the cache, be aware that the instructions of the
subroutine may overwrite the instructions of the loop in the cache. This happens if any of the
addresses of the code in the loop and the subroutine overlap with respect to their least
significant byte. If your subroutine is small enough to fit in the cache along with the
loop, you should inline the subroutine to avoid this from happening. Small subroutines can be
rewritten as parameterized macros, making inlining easy.
- FREEZE BEFORE BRANCHING OUT OF LOOPS
If you branch out to subroutines from a loop that fits in the cache, and the subroutines cannot
be inlined without exceeding the size of the cache, freeze the cache before branching out and
unfreeze it when you return. Any subroutine that shares cache lines with the loop will evict those
lines on every call, and whether that happens depends on where the code falls in memory, which is
hard to predict. If you freeze the cache you no longer have to worry about this, as the loop will
stay cached no matter what the subroutines touch.
- MARK INTERRUPT SERVICE CODE AS NON-CACHEABLE
If you make use of timers or other interrupts occurring many times during a VBL, the performance
of the instruction cache will suffer as the interrupt service code will overwrite entries in the
cache every time it's executed. The best solution to keep the interrupt service code from messing
with the contents of the cache is to reprogram the PMMU to make the address space used by the interrupt
handlers non-cacheable. To some extent this advice also applies to the data cache, but only
under very specific circumstances.
Data Cache
The data cache has 256 bytes with 16 lines (4 longwords each).
All longwords/entries of a cache line share the same tag value (the 24 most significant address
bits).
The data cache is a write-through cache, which means that on memory writes, the CPU writes to
memory even if a cache hit occurs. In the event of a cache hit, the cache is also updated, even
if the cache is frozen. On a cache miss, the cache is only updated if the WA (Write Allocate) bit
is set.
On memory reads, the data is fetched from the cache rather than from memory if the memory
address of the data is present in the cache. If a cache miss occurs, the data is read from memory
and the cache is updated.
On average, enabling the data cache should boost the performance, but on the Falcon030 that is
not always true due to its 16-bit bus. It may very well be that enabling the data cache causes a
drop in performance. The cache deals only in whole longwords, so even a single byte or word read
fills the entry with a full longword. This is only worth it if that longword is used more than
once. A non-sequential read of bytes or words takes two bus cycles for each element where an
uncached read would have spent only one. A copy has the same problem for a different reason. It
does use every longword in full, but it reads each one only once and then moves on, so the fill
never gets the chance to pay for itself. On the Falcon's 16-bit bus that wasted effort can easily
outweigh any benefit.
Whether you should keep the data cache on by default and disable/freeze it when needed, or
keep it off by default and enable/freeze it when needed will depend on what your code looks like.
If you take the time to analyze your code and follow the advice below it should not make much of
a difference. Personally I usually keep the data cache enabled by default.
- CLEAR WHEN ENABLING
Always clear the data cache when enabling it to avoid possible memory/cache inconsistencies.
- DISABLE FOR SPARSE BYTE OR WORD READS
If you read scattered bytes or words, one element per longword and never the rest, such as walking
a table with a stride or pulling a single field from each entry in an array, disable the data
cache first. This is the non-sequential read from above at its worst. Every read misses and drags
in a full longword for a single element, and the cache does not just fail to help, it doubles the
memory traffic, so a loop like this can take close to twice as long with the cache on. A 32-bit
machine would not care, since it pulls the longword in a single access either way, but on the
Falcon030 this is the worst thing you can do with the data cache, worse than a straight copy.
- DISABLE BEFORE COPYING LARGE AMOUNT OF DATA
When copying large amounts of data with move.l (a0)+,(a1)+ in a loop, disable the
data cache first. Each longword is read only once and never looked at again, so the cache simply
fills up with data it cannot reuse. The writes gain nothing either, since the data cache is
write-through, so all the cache really adds here is overhead. I have measured this on real
hardware. The cache makes a move.l or move.w copy slower, and an
unrolled movem.l block copy is no exception. Only move.b breaks even,
because its four byte reads share one longword fetch. The cause is not the 68030 itself, but the
Falcon's 16-bit path to memory. The very same processor in an Atari TT, where it can reach memory
32 bits wide (with burst disabled), does not show this penalty at all. The exact mechanism is not
in the MC68030 manual, which describes only the chip, but the effect is real and repeatable, so
disable the data cache for a large copy on the Falcon030. When you turn it back on, clear it
before reading back anything you wrote while it was disabled, or you may get stale data.
- PRELOAD AND FREEZE
If you have a loop where you frequently access some addresses in memory, such as entries in a
small look-up table, preload the data cache with the data by reading from those memory addresses
and then freeze the cache before entering the loop. Avoid collisions in the cache by ensuring that
the preloaded data is sequential (a single block of memory) and that its size is not exceeding
256 bytes. By freezing the cache you prevent having the preloaded data in the cache being
overwritten when accessing other memory addresses during loop iterations. Do not forget to
unfreeze the cache when the loop is finished (clearing the cache when unfreezing it is not needed).
Please note that access to the cached memory addresses are not restricted to reads, you may write
to them as well as any cached data is updated in the cache even when the cache is frozen! The
memory addresses in the cache are frozen but the data is not. Writes will not be cheaper though,
as the data cache is a write-through cache.
- AVOID STALE DATA
Stale data is when the data in the cache no longer matches the data in memory. Stale data
conditions can arise if the data cache is enabled while other DMA devices (such as the Blitter)
are writing to the memory, effectively bypassing the data cache. This can easily be avoided by
disabling the data cache while other DMA devices are operating, and clearing the data cache before
enabling it again.
If the data cache is configured to use the no write allocation mode (WA=0), stale data conditions
can also arise when no other device than the CPU is accessing the memory. These situations are
fairly rare and either involves accessing the same physical memory address using two or more
logical addresses, or accessing the same physical address using different program space
encodings.
When reading the chapter on the caches in the MC68030 User's Manual, I got the impression that
unless you are messing with the MMU or switching back and forth between user and supervisor mode
while accessing the same physical memory addresses, you have nothing to worry about. Not true.
There is actually one situation which is not too uncommon and which is not described explicitly in
the manual: PC-relative addressing.
PC-relative addressing modes can only be used for reading data and are often used as a form of
optimization. However, for PC-relative addressing modes, the reference is a PROGRAM space
reference, while for any other addressing mode, the reference is a DATA space reference. This is
of importance as the data cache stores data references to any address space (except CPU space),
and the address space is part of the tag of each line in the data cache.
So, when reading from a specific memory location using PC-relative addressing, the data cache will
be updated with a PROGRAM SPACE reference. If you at a later time write to that memory location
using any other addressing mode (remember that PC-relative addressing cannot be used with writes),
the data cache will not be updated due to the difference in program space. The data in the cache
no longer matches that in memory and is stale.
As a consequence, the next time you read from the memory location using a PC-relative addressing
mode, there will be a cache hit and the value you obtain is the stale data in the data cache.
If you plan to use PC-relative addressing with the data cache enabled, either restrict it and use
it only for const data (data that is never modified), or enable the write allocation mode (WA=1)
of the data cache. Be aware of the limit of the WA=1 fix, though: it works only because the
data-space write evicts the stale program-space entry from its cache line, and the 68030
write-allocates only on aligned longword writes. If you modify the location with a byte or word
write, WA=1 will not allocate, will not evict the stale entry, and the problem remains.
So to be completely safe you should restrict PC-relative reads to constant data, as that is really
the only fully reliable solution.
|