How the creation of Lander on the ARM1 influenced the code
Lander was the first game to be written for the ARM platform. Indeed, it was such an early game that it started life on the ARM1, which was only available as part of Acorn's ARM Evaluation System. Released in 1986, this was the world's first ARM-based development system, and its "cheese wedge" case houses an ARM1 processor in a second processor that you plug into the Tube interface in a BBC Micro or BBC Master, effectively using the BBC as an input and output device for the ARM1 processor in the external case.
Here's the ARM1 in all its glory, inside the Acorn ARM Evaluation System:
(Photo by Peter Howkins, covered by CC BY-SA 3.0)
Here's a quote from Retro Gamer's article about Lander and Zarch in issue 128, in which David Braben talks about his first experience with the ARM1:
"I first saw an ARM CPU - then it stood for 'Acorn Risc Machine' - in a 'second processor' unit," David recalls. "It [was] one of the very first ARM chips. Later I got one of the first CMOS 'second processor' units to play around with, and wrote a number of demos, then started Lander. At this point I got an A500 - an amazing machine with an incredible 4Mb of RAM. The availability of a prototype 32-bit machine was a great opportunity for me."
And here's a quote from an interview with David Braben that appeared in Edge magazine:
The Archimedes was an entirely new machine. Centred on a custom designed 32-bit RISC processor, it clearly had power, but Braben would have to figure out how it worked as he went along. As it turned out, a lot of it didn't. "I was actually doing all the programming through the BBC because the machine was missing its operating system," he explains. "It didn't even have a disk drive, that's how basic it was."
Gallingly, the final machine promised features that would dramatically speed up 3D applications, but were missing from the machine Acorn supplied to Braben: "In the prototype version of the chip, the multiply operation just didn't work. It just wasn't there. It didn't exist."
So Lander started life on an ARM1 hooked up to a BBC Micro, before moving on to an ARM2 in a prototype A500. Interestingly, the effects of this development history are visible in the game's code; as mentioned in the above quote, the ARM1 didn't have a multiply instruction. This was introduced into the ARM2, which was the chip that Braben would graduate to when Acorn lent him the ARM2-based A500 prototype, but by that stage he had presumably written all of the maths code, as there are no MUL or MLA instructions in Lander anywhere.
Instead, multiplication is done using an inline shift-and-add algorithm. Here's an example from the GetDotProduct routine:
\ We now calculate R3 = R4 * R5 using the \ shift-and-add multiplication algorithm EOR R7, R4, R5 \ Set the sign of the result in R7 TEQ R4, #0 \ Set R4 = 4 * |R4| RSBMI R4, R4, #0 MOVS R4, R4, LSL #2 TEQ R5, #0 \ Set R5 = 2 * |R5| RSBMI R5, R5, #0 MOV R5, R5, LSL #1 AND R4, R4, #&FE000000 \ Zero all but the top byte of R4, ORR R4, R4, #&01000000 \ ensuring that bit 0 of the top byte is \ set so the value of the fractional \ part is set to 0.5 MOV R3, #0 \ Set R3 = 0 to use for building the sum \ in our shift-and-add multiplication \ result .dotp1 MOV R5, R5, LSR #1 \ If bit 0 of R5 is set, add R5 to the ADDCS R3, R3, R5 \ result in R3, shifting R5 to the right MOVS R4, R4, LSL #1 \ Shift R4 left by one place BNE dotp1 \ Loop back if R4 is non-zero MOV R3, R3, LSR #1 \ Set R3 = R3 / 2 TEQ R7, #0 \ Apply the sign from R7 to R3 to get the RSBMI R3, R3, #0 \ final result, so: \ \ R3 = R4 * R5
I suspect that this algorithm was included using a macro, as the code is identical every time a multiplication is required, apart from the registers that are used. So you would think it would have been worth replacing the macro with a MUL or MLA instruction when the ARM2 came along... but deadlines were tight, so the algorithm remains. (I believe Zarch is also devoid of multiplication instructions; if it ain't broke, don't fix it, I guess!).
For a discussion of this algorithm (albeit for 8-bit computers), see this deep dive in my Elite project.
Another interesting point to note is that Lander doesn't use the ADR directive anywhere. This pseudo-instruction loads an address into a register in a PC-relative way, resulting in moveable code, but instead Lander uses vectors to store addresses. For example, the landscapeOffsetAddr vector contains the address of the landscapeOffset variable, so to fetch the address of the landscape offset table into R0, we can use the following:
LDR R0, landscapeOffsetAddr
This has the advantage of always working, as the ADR directive is fairly fussy about which addresses it can encode. Back in the day, ARM programmers would typically roll their own ADRL functions to use instead of ADR, adding in logic that would support arbitrary addresses; here's the version I always used:
10 REM Subroutine to implement ADRL directive 20 : 30 : 40 DEF FNadr(reg,loc) 50 p%=P% 60 IF loc>p% THEN 70 [ OPT pass% 80 ADD reg,PC,#(loc-p%-8)MOD&100 90 ADD reg,reg,#(loc-p%-8)DIV&100<<8 100 ] 110 ELSE 120 [ OPT pass% 130 SUB reg,PC,#(p%+8-loc)MOD&100 140 SUB reg,reg,#(p%+8-loc)DIV&100<<8 150 ] 160 ENDIF 170 =""
But Lander sidesteps the ADR approach altogether in favour of vectors, which is a much more 6502-influenced approach. It works, but it does make the code harder to relocate without building it from the source; not that this is an issue with Lander, but big projects that used vectors instead of ADRL would probably live to regret it.
Incidentally, Lander also fails to use the SBC and RBC instructions, and it doesn't need the ROR shift operator either. Given how incredibly simple the ARM1 instruction set is, this means the entire game is made up of a tiny handful of instructions. Here's a complete list of all the instructions used in Lander:
- ADD, ADC
- SUB, RSB
- MOV, MVN
- AND, ORR, EOR, BIC
- CMP, CMN
- TST, TEQ
- LDR, STR
- LDM, STM
- B, BL, SWI
That's all of them - just 21 different instructions! It truly is Reduced Instruction Set codebase, as befits the world's very first game for the ARM platform, and the world's only ever ARM1 game.