Bank Switching on the Mitel SX-20 PABX
There's a really neat trick that the SX-20 does. It supports 96k of EPROM firmware, on a 6809 processor. The processor only has a 16-bit address bus, so it can't access all 96k at the same time. It relies on bank switching.
The firmware is located between addresses 4000 (all addresses in hexadecimal) and FFFF (a 48k address range) and controlled by a hardware bank-switch flip-flop.
Hardware support for bank switching is provided by flip-flop registers at two I/O memory locations. When the processor writes to 2168, it selects bank zero, and when it writes to 2169, it selects bank one. The bank switch takes effect on the next CPU instruction fetch.
An area near the top of memory is dedicated as a "trampoline" area — a place where the software can call functions located in the other bank. For example, software in bank zero needs to call a function in bank one, so it calls into the trampoline area at the top of memory in bank zero. The trampoline code is carefully controlled between the two banks to do the "right thing."
Watch what happens when the software (currently running in bank zero) needs to do a checksum of the EPROM in bank one:
## LABEL E:ADDR OPCODES MNEM ARGUMENTS A B X Y U S PC DP CC
-- ------------ ------ ----------- ----- -------------- -- -- ---- ---- ---- ---- ---- -- --
01 0:BB91 BD FF 7E JSR CSUM16p0 00:00 4000 0000 0000 3E8B FF7E 00 30
02 CSUM16p0 0:FF7E BD FE EC JSR FLIPPG 00:00 4000 0000 0000 3E89 FEEC 00 30
03 FLIPPG 0:FEEC 34 03 PSHS CC,A 00:00 4000 0000 0000 3E87 FEEE 00 30
04 0:FEEE 86 01 LDA #$01 01:00 4000 0000 0000 3E87 FEF0 00 30
05 0:FEF0 B7 21 69 STA PAGE1 01:00 4000 0000 0000 3E87 FEF3 00 30
06 1:FEF3 35 03 PULS CC,A 00:00 4000 0000 0000 3E89 FEF5 00 30
07 1:FEF5 AD F1 JSR [,S++] 00:00 4000 0000 0000 3E89 FF81 00 30
08 CSUM16p1 1:FF81 7E FB BF JMP CSUM16 00:00 4000 0000 0000 3E89 FBBF 00 30
09 CSUM16 1:FBBF 34 04 PSHS B 00:00 4000 0000 0000 3E88 FBC1 00 30
There's a lot going on there, so let's unpack it.
First off, the "E" column shows you the EPROM bank — zero or one. As you can see, at the first instruction (line 01, at BB91), we're executing out of bank zero (the E column is 0), and we execute a JSR CSUM16p0.
This calls into the trampoline area at FF7E (in bank zero), which then calls FLIPPG to set the hardware to the other bank.
In FLIPPG, we:
- push the condition code (CC) and accumulator (A) registers via PSHS CC,A,
- set the accumulator to 1 (LDA #$01), and
- write the accumulator to PAGE1 (the hardware location 2169, which selects bank one)
Notice that E has changed on line 06 to 1 — we're executing out of bank one.
The CPU itself has no idea that this happened! It was executing address FEF0 (in bank zero), and on line 06 is executing address FEF3 (but in bank one).
That's what I meant about the trampoline area being carefully controlled — the transition has to be coordinated.
So, at line 06, the CPU restores the condition code (CC) and accumulator (A) off the stack.
At this point, the stack contains two 16-bit values:
FF 81 BB 94
The FF81 is at the top of the stack, and represents the return address for the JSR FLIPPG subroutine call. Recall that took place on line 02 at FF7E and is a three byte instruction, so occupies FF7E, FF7F and FF80, making the next instruction FF81. This corresponds to the top of the stack, so everything is as expected here.
The next entry on the stack is BB94, and represents the return address for the JSR CSUM16p0 subroutine call from line 02, which came from BB91. BB94 is three bytes later, so everything is as expected here as well.
And now ... magic
On line 07 we see the instruction JSR [,S++]:
## LABEL E:ADDR OPCODES MNEM ARGUMENTS A B X Y U S PC DP CC
-- ------------ ------ ----------- ----- -------------- -- -- ---- ---- ---- ---- ---- -- --
07 1:FEF5 AD F1 JSR [,S++] 00:00 4000 0000 0000 3E89 FF81 00 30
This instruction is one of the reasons people developed a love / hate relationship with the 6809 processor. It packs a lot of punch!
JSR on its own pushes the next address (the one to return to) onto the stack. We saw that twice already.
But the square brackets mean that the target of the JSR is indirect — it's specified by the value given within the brackets. That value is S — the stack pointer!
Ignoring the "++" for now, a plain JSR [S] would mean "jump to the address that's currently on the stack." You'll recall that that's the return address from line 02's JSR, that is, the value FF81.
Still ignoring the "++", that would now mean that we push our return address, which would be FEF7 (the next instruction, being two bytes later) onto the stack, and control would be transferred to FF81.
What the "++" does, is it increments the stack pointer, pointing it to the previous return address (that is, at the BB 94 position). When the JSR pushes its return address onto the stack (at the adjusted location), it ends up effectively replacing the FF81 return value with FEF7.
The JSR [,S++] calls into what would have been the return address and replaces the return address with the address of the next instruction.
Stepping back...
If we step back just a little, and ignore banks, this is what happened:
## LABEL E:ADDR OPCODES MNEM ARGUMENTS A B X Y U S PC DP CC
-- ------------ ------ ----------- ----- -------------- -- -- ---- ---- ---- ---- ---- -- --
01 0:BB91 BD FF 7E JSR CSUM16p0 00:00 4000 0000 0000 3E8B FF7E 00 30
02 CSUM16p0 0:FF7E BD FE EC JSR FLIPPG 00:00 4000 0000 0000 3E89 FEEC 00 30
...
08 CSUM16p1 1:FF81 7E FB BF JMP CSUM16 00:00 4000 0000 0000 3E89 FBBF 00 30
09 CSUM16 1:FBBF 34 04 PSHS B 00:00 4000 0000 0000 3E88 FBC1 00 30
Get it?
At line 01 we called FF7E, which is bank zero's trampoline area, and at line 08 we end up at FF81 in bank one, dispatching off to the bank one version of the function we called. In this stream of instructions, you can think of the JSR FLIPPG as effectively a no-op, with a bizarre side effect of flipping the page to page one.
So what's on the stack now?
Excellent question!
FE F7 BB 94
The magical JSR [,S++] replaced the content at the top of the stack (FF 81) with the return address of the instruction following the JSR [,S++], address FEF7.
The PSHS B instruction on line 09 pushes the B register onto the stack, making it:
00 FE F7 BB 94
And the checksum routine is off and running.
How do we get back?
One would be legitimately curious to see what happens when the checksum routine CSUM16 completes, right? Here's the trace:
## LABEL E:ADDR OPCODES MNEM ARGUMENTS A B X Y U S PC DP CC
-- ------------ ------ ----------- ----- -------------- -- -- ---- ---- ---- ---- ---- -- --
10 1:FBCB 35 84 PULS B,PC A6:00 400F 0000 0000 3E8B FEF7 00 30
11 1:FEF7 34 03 PSHS CC,A A6:00 400F 0000 0000 3E89 FEF9 00 30
12 1:FEF9 86 00 LDA #$00 00:00 400F 0000 0000 3E89 FEFB 00 34
13 1:FEFB B7 21 68 STA PAGE0 00:00 400F 0000 0000 3E89 FEFE 00 34
14 0:FEFE 35 83 PULS CC,A,PC A6:00 400F 0000 0000 3E8D BB94 00 30
15 0:BB94 AB E0 ADDA ,S+ A6:00 400F 0000 0000 3E8E BB96 00 18
At this point, our stack contains:
00 FE F7 BB 94
Where 00 is the B register, and FE F7 is our return address.
Ok, on line 10 the checksum routine finishes, and effectively does an RTS instruction (except that it's one of those clever 6809 instructions that also restores the B register).
As expected, line 11 shows us that we now return to FEF7, meaning that our stack now contains just:
BB 94
This does a similar thing to what we saw in the FLIPPG routine — it pushes the CC and A registers on to the stack, but loads a 0 into A and writes it to PAGE0 (that is, 2168), which causes the E bank bit to go to zero.
Now we're running in bank zero again!
Our stack has:
30 A6 BB 94
This is the CC, A, and return address, respectively.
We restore three registers via the PULS CC,A,PC instruction, and find ourselves at BB94 in bank zero.
This effectively continues off right after the first call on line 01 where it all started.
Additional notes
The SX-20 does a similar trick for its RAM bank as well; there's a bit of RAM that is not banked, and then some RAM that is banked:
| Address Range | Usage |
|---|---|
| 0000 - 1FFF | Not banked (common to both) |
| 2800 - 3FFF | Banked |
The address range 2000 → 27FF is reserved for I/O space (e.g., the 2198 address we saw above for the EPROM "select page 0" flip-flop).