Skip to navigation

Elite on the BBC Micro

6502 Second Processor Tube communication

How the 6502 Second Processor version of Elite talks over the Tube

One of the more intriguing features of the BBC Micro is the Tube interface, which allows users to expand their systems by adding "Second Processors". Sporting a distinctive wedge-shaped box design, this range of expansion boxes included 6502, Z80 and 32016 Second Processor options, and by 1986 there was even an ARM Evaluation System available containing an 8Mhz ARM1 processor, the first version of the now ubiquitous ARM processor.

The basic concept of the Tube system is that the fast processor in the expansion box runs the majority of tasks, delegating input and output to the attached BBC Micro (input and output in this context meaning keyboard, display, keyboard, sound and so on). In this relationship the BBC Micro is known as the "I/O processor" (or sometimes the "host"), while the Second Processor is known as the "parasite". Communication between the two is via the high-speed Tube interface, and both computers run independently using their own CPUs and memory.

The 6502 Second Processor version of Elite has become the poster child for the Tube system. The main game code runs on the 6502 Second Processor, with its 3MHz 65C02 processor and 64K of RAM, and it uses the BBC Micro, with its 2Mhz 6502 processor and 32K of RAM, to manage the display and keyboard. This enables the game to have more colours with faster graphics, and the extra memory in the parasite lets the game load all the ship blueprints at once, as well as supporting nearly twice as many concurrent ships in the local bubble.

One of the reasons for Elite's reputation as a Second Processor killer app is that it is naturally suited to such a split. Most games from this era were based on bitmap graphics, from space-invading sprites to platform-jumping cavemen, but the 3D universe at the heart of Elite is mathematical, and it's drawn using vector graphics that are made up of lines rather than dots. A line can be described succintly using just four bytes - two bytes for the (X1, Y1) start point and two more for the (X2, Y2) endpoint - and this lends itself beautifully to the Second Processor setup. The parasite does all the complicated vector mathematics with its faster processor, and it sends the results to the I/O processor as relatively small bundles of coordinate data, which the BBC Micro then draws on-screen. It's a perfect fit for the Tube-based system.

Talking over the Tube
---------------------

There are various ways that the parasite and I/O processor can communicate, and for programs that use the standard OS commands, most of the heavy lifting is done by the Tube code that's built into the system. For example, when saving and loading commander files in the QUS1 routine, the parasite just calls the standard OSWORD routine and the system takes care of the rest - there's no need to contact the I/O processor explicitly.

Elite, however, needs to tell the I/O processor what to do, and for that the parasite needs to be able to communicate with its host. It uses two broadly similar approaches, one using OSWRCH to send single bytes to the I/O processor, and the other using OSWORD to send and receive larger blocks of data. The idea behind the two approaches is the same: the parasite issues commands to the I/O processor to get it to update the screen, scan the keyboard and so on, thus splitting up the work in an efficient manner. When it comes to 6502 Second Processor Elite, two heads are better than one.

These special commands are implemented by custom OSWRCH and OSWORD handlers in the I/O processor. These are installed in the usual manner by redirecting the WRCHV and WORDV vectors to custom handlers, in this case to the USOSWRCH and NWOSWD routines. These routines use the address tables at JMPTAB and OSWVECS to jump to the relevant routines in the I/O processor code to implement the specified commands.

The OSWRCH commands
-------------------

Elite implements custom OSWRCH commands for values of A from 128 to 147. In the commentary, we might therefore refer to the OSWRCH 129 command (which tells the I/O processor we are about to send over a batch of line coordinates for drawing), or the OSWRCH 130 command (which sends one of those line coordinates, and tells the I/O processor to draw the line once the last coordinate is sent). Some of these commands have associated configuration variables for their numbers, so we might talk about the #SETXC command, which moves the text cursor to a specific column, for example. The SETXC variable has the value 133, so this is the same as the OSWRCH 133 command, but using the variable name makes things a bit easier to follow. Here's a list of supported commands:

OffsetVariableOSWRCH #ActionI/O routine
0128 (&80)Put back to USOSWRCHUSOSWRCH
1129 (&81)Begin drawing a lineBEGINLIN
2130 (&82)Add line byte/draw lineADDBYT
3#DOFE21131 (&83)Show energy bomb effectDOFE21
4#DOhfx132 (&84)Show hyperspace coloursDOHFX
5#SETXC133 (&85)Set text cursor columnSETXC
6#SETYC134 (&86)Set text cursor rowSETYC
7#clyns135 (&87)Clear bottom of screenCLYNS
8#RDPARAMS136 (&88)Update dashboardRDPARAMS
9137 (&89)Add dashboard parameterADPARAMS
10#DODIALS138 (&8A)Show or hide dashboardDODIALS
11#VIAE139 (&8B)Set 6522 System VIA IERDOVIAE
12#DOBULB140 (&8C)Toggle dashboard bulbDOBULB
13#DOCATF141 (&8D)Set disc catalogue flagDOCATF
14#SETCOL142 (&8E)Set the current colourDOCOL
15#SETVDU19143 (&8F)Change mode 1 paletteSETVDU19
16#DOsvn144 (&90)Set file saving flagDOSVN
17145 (&91)Execute BRK instructionDOBRK
18#printcode146 (&92)Write to printer/screenprinter
19#prilf147 (&93)Blank line on printerprilf

OSWRCH transmits just one byte to the I/O processor, the value of A, so that's the only parameter that's available to the handler in USOSWRCH. The I/O processor supports parameters for OSWRCH commands by switching the handler address to that in the JMPTAB jump table (i.e. the routine in the last column above), and the handler accepts subsequent OSWRCH calls until all the expected parameters have been received, at which point it switches the handler back to USOSWRCH by calling the PUTBACK routine. This means that although each OSWRCH command only passes one byte, we can use multiple OSWRCH calls to pass multiple parameters - they just get passed one call at a time. (It also means that OSWRCH calls that don't take a parameter, such as the #RDPARAMS command, still need to call OSWRCH twice, as we need a second call to put the USOSWRCH handler back, even the second call does nothing else.)

For example, the aforementioned #SETXC command takes an argument containing the new column number for the text cursor (we can refer to the full version as the #SETXC <col> command). The full command gets sent by two calls to OSWRCH, as in this example, which moves the text cursor to column 10:

 LDA #SETXC             \ Send the first part of a #SETXC command to the I/O
 JSR OSWRCH             \ processor

 LDA #10                \ Send the column number to the I/O processor
 JSR OSWRCH

The most extreme example of an OSWRCH call with parameters is the #RDPARAMS command, which tells the I/O processor to expect a set of 15 dashboard values, such as speed and altitude. Once all 15 are received, the I/O processor updates the various dials on the dashboard. You can see these OSWRCH calls in the parasite's DIALS routine, each of which triggers either the I/O processor's RDPARAMS routine (for the first command, which tells the I/O processor to expect a batch of dashboard values) or the ADPARAMS routine (for all the calls that actually send those values).

The OSWORD commands
-------------------

The other approach to Tube communication in Elite is via the OSWORD command. While the OSWRCH command can send one byte of information in one direction (to the I/O processor) with each call, OSWORD commands can both send and receive entire blocks of data, with up to 128 bytes being transmitted between the parasite and I/O processor as part of each call. For OSWORD calls with A >= 128, this data transmission is automatically handled by the Tube's own host code, so it's an ideal way to pass large amounts of data between the second processor and the BBC Micro. Here's a list of supported commands:

OffsetVariableOSWORD #ActionI/O routine
0240 (&F0)Scan the keyboardKEYBOARD
1241 (&F1)Draw space view pixelsPIXEL
2#DOmsbar242 (&F2)Update missile indicatorsMSBAR
3#wscn243 (&F3)Wait for vertical syncWSCAN
4#onescan244 (&F4)Draw a ship on the 3D scannerSC48
5#DOdot245 (&F5)Draw a dot on the compassDOT
6#DODKS4246 (&F6)Scan for a specific keyDODKS4
7247 (&F7)Draw orange sun linesHLOIN
8248 (&F8)Display the ship hangerHANGER
9249 (&F9)Copy protectionSOMEPROT

Elite implements OSWORD commands for values of A from 240 to 249, and as with OSWRCH, some have associated variables, so we might talk about the OSWORD 240 command that scans the keyboard and returns the result, or the #onescan command that draws a ship on the 3D scanner, for example.

When the parasite calls OSWORD to send a command to the I/O processor, it sets (Y X) to point to the parameter block. The first byte of the block contains the number of bytes that will be transmitted with this command, while the second byte contains the number of bytes that will be sent back from the I/O processor once the command has finished executing (both of these counts should include these two bytes, so a value of 2 indicates that no parameters are transmitted in that direction). When the call is made to OSWORD, a block the size of the first parameter is copied from the parasite to the I/O processor, and the OSWORD handler is called with (Y X) pointing to the copied bytes. When the handler finishes, a block the size of the second parameter is copied from the I/O processor back to the parasite, overwriting the original parameter block. Up to 128 bytes can be transmitted either way using this approach.

Apart from the payload size, there's another important difference between OSWRCH and OSWORD. Calls to OSWRCH return immediately, so when the parasite issues an OSWRCH command, the I/O processor starts working on it while the parasite goes back about its business. It's a bit like having a two-core processor on-hand, and it means that when the parasite sends the last coordinate in a long sequence of lines, or it sends the last value in a dashboard update, then the main game running on the parasite can get straight back to running the game while the BBC Micro updates the screen. OSWORD calls, on the other hand, do not have this kind of parallel processing, and instead the parasite will stop and wait after sending the OSWORD command, until the I/O processor responds by returning an updated parameter block. It's a bit like the difference between simply issuing an order (OSWRCH) and having a conversation (OSWORD).

Putting it all together
-----------------------

So the parasite and I/O processor communicate, and code runs on both processors at various times, but how does Elite set up this meeting of digital minds? It's all in the loader, which, once it has configured things like the screen mode and sound effects and drawn the loading screen, then *RUNs not one but two different binaries:

  • I.CODE is the I/O processor's game code, which loads at &2400 in the BBC Micro (the file has a load address of &FFFF2400, and the &FFFF part specifies it should load into the I/O processor)
  • P.CODE is the parasite's game code, which loads at &1000 in the Second Processor (the file has a load address of &00001000, and the &0000 part specifies it should load into the parasite)

When I.CODE is run in the I/O processor, it sets up the vector handlers mentioned above and terminates, leaving the BBC Micro just sitting there, twiddling its thumbs. Meanwhile P.CODE starts up on the parasite, and starts issuing OSWRCH and OSWORD commands across the Tube, asking the I/O processor to update the screen, scan the keyboard and so on, at which point the BBC Micro perks up and does what it's told.

It's almost as if the 6502 Second Processor is the quick-thinking pilot, while the BBC Micro is the ship, with its sensors and screens and chattering disc drives...