24

The best background I have found so far on the early development of the ARM processor at Acorn comes from this interview with Steve Furber.

The interview does not mention the development of the first ARM "model" and "simulator" programs. I assume these programs were used to debug some key aspects of the new processor's instruction set and timing, and perhaps to predict its performance metrics. I have read elsewhere that they were written on the BBC Micro in BASIC. Is this statement verifiable, and does the original BASIC code still exist somewhere?

@see YouTube video here.

29

Per Sophie Wilson:

To prove that [Steve had] designed the microarchitecture correctly, he wrote, in BBC BASIC, a model of the microarchitecture. To prove that I'd designed the architecture correctly, I wrote an interpreter for the processor's instruction set and wrote programs in it.

So, well before any actual commitment to doing things, we could demonstrate that you could write sensible programs in this stuff — programs did what I claimed, Steve's microarchitecture did what he claimed. You can then do all the verification that you need to do.

So on my models of the processor you write programs that are expected to produce particular results. Those models run quite quickly — on a 6502 second processor we could run an ARM emulator at hundreds of thousands of instructions per second, where the microarchitecture model, and later on the transistor model, of the processor ran very much more slowly than that.

But we built a test suite that demonstrated that the processor was the processor, ran it on my models of the processor and then on Steve's models of the processor to prove that his model did the same as mine, and then when we had a transistor model of the processor we ran it on that too, and then we made one. And to nobody's great surprise really, the processor arrived back in April 1985, we plugged it into the development board and it worked.

Furthermore, when interviewed by the Computer History Museum, Sophie commented (final sentence of Page 22 onwards):

So Steve wrote in BBC Basic the first behavioral model of an ARM. We ran test programs on that. ... We then also started writing instructions and interpreters ...

So it's definitely the case that both designers strongly remember using BBC BASIC for the very first models, writing various further models, running them on BBC Micro hardware (the 6502 second processor was an ordinary market option for that machine) and producing hardware only much later. Earlier in that same interview, Sophie has established that a common practice elsewhere was to fab early and not get anything that works until the seventh or eighth revision, so the simulation story is given in contrast to that.

If those pre-hardware software models of the ARM survive, I'm not sure that they've ever seen public release. The ARM evaluation kit — a very early ARM provided as a second processor for the BBC — is probably the first thing that survives in public.

  • 5
    No, BBC BASIC is only mentioned in relation to the model of the microarchitecture. For an emulator to run at hundreds of thousands of instructions per second on a 6502, it has to be written in the assembly language. – Leo B. Jun 22 '17 at 15:19
  • 5
    I disagree with that assessment, though Sophie also wrote BBC BASIC so clearly she'd have no problem either way. Reasons: (i) the second processor runs at 3Mhz and is not responsible for anything other than the actual program, so e.g. is automatically more than three times as fast as, say, a C64; (ii) BBC BASIC is significantly faster than most, both in general and e.g. because it includes integer types separately from floating point types; and (iii) in any case it supports inline assembler for critical sections. So I guess it depends where you want to split hairs if that approach was taken. – Tommy Jun 22 '17 at 16:13
  • 3
    The assembly language for performance-critical applications was the default, thus required no mention. Actually, I doubt that her recollection of "hundreds of thousands" instructions per second is accurate: just think how many instructions of an 8-bit architecture are needed in average to decode and emulate an instruction of a 32-bit architecture. – Leo B. Jun 22 '17 at 16:35
  • 2
    Could it be "hundreds OR thousands" rather than "hundreds OF thousands"? – Leo B. Jun 22 '17 at 17:37
  • 2
    @Tommy Well then she was mistaken. hundreds of thousands isn't possible in assembly and it's definitely not possible in BASIC. That doesn't mean she didn't write an emulator in BASIC, she did. The objective was only to prove the instruction set is sensible so it doesn't really matter how fast it is. – JeremyP Jun 23 '17 at 13:03
4

This isn't really an answer to the question, but the comments repeatedly talk about Sophie Wilson's claim to be able to execute 100k+ ARM instructions per second. This is an impossibility on any normal 6502 and so she must have misremembered or the commenters must be misinterpreting what she said. However, it's hard to show why it is wrong in a comment.

This is a refutation of the claim and a demonstration of why it can't be true.

Take supercat's example:

add r0,r1,r2

This instruction adds the content of r2 to r1 and places the result in r0. Registers are 32 bits wide, so in the 6502, the minimum program is

CLC           ; 2 cycles
LDA r1_0      ; 3 cycles for zero page
ADC r2_0      ; 3 cycles for zero page
STA r0_0      ; 3 cycles for zero page
LDA r1_1      ; 3 cycles for zero page
ADC r2_1      ; 3 cycles for zero page
STA r0_1      ; 3 cycles for zero page
LDA r1_2      ; 3 cycles for zero page
ADC r2_2      ; 3 cycles for zero page
STA r0_2      ; 3 cycles for zero page
LDA r1_3      ; 3 cycles for zero page
ADC r2_3      ; 3 cycles for zero page
STA r0_3      ; 3 cycles for zero page

38 clock cycles in total.

But note that all of the addresses are hardcoded. Using this scheme you would need 16 x 16 x 16 blocks of code just to account for all possible ways to add two registers together. You have to be a bit more clever.

Note that the last operand need not be a register, so it would make sense to fetch it from an address in a zero page location. The simplest solution for the other source and destination registers would be to do the same thing so you would end up with some code like this (note that the Y register is used to index the byte number):

CLC                  ; 2 cycles
LDY #0               ; 2 cycles
LDA (sourceR),Y      ; 5 cycles for zero page
ADC (operand),Y      ; 5 cycles for zero page
STA (destR),Y        ; 5 cycles for zero page
INY                  ; 2 cycles
LDA (sourceR),Y      ; 5 cycles for zero page
ADC (operand),Y      ; 5 cycles for zero page
STA (destR),Y        ; 5 cycles for zero page
INY                  ; 2 cycles
LDA (sourceR),Y      ; 5 cycles for zero page
ADC (operand),Y      ; 5 cycles for zero page
STA (destR),Y        ; 5 cycles for zero page
INY                  ; 2 cycles
LDA (sourceR),Y      ; 5 cycles for zero page
ADC (operand),Y      ; 5 cycles for zero page
STA (destR),Y        ; 5 cycles for zero page

70 clock cycles.

On top of that, you need to add instruction decoding. This will involve, at a minimum, reading all four bytes of the instruction and because it is specified by the PC, you must do four zero page indirect loads and increment the PC either between each load or add four to it at the end (that's a 32 bit add).

It would look something like this:

LDY #0          ; 2 cycles
LDA (r15),Y     ; 5 cycles (or 6)
; Do something    ? cycles
INY             ; 2 cycles
LDA (r15),Y     ; 5 cycles (or 6)
; Do something    ? cycles
INY             ; 2 cycles
LDA (r15),Y     ; 5 cycles (or 6)
; Do something    ? cycles
INY             ; 2 cycles
LDA (r15),Y     ; 5 cycles (or 6)
; Do something    ? cycles
CLC             ; 2 cycles
LDA r15         ; 3 cycles
ADC #4          ; 2 cycles
STA r15         ; 3 cycles
BCC endIncr15   ; 2 or 3 cycles
INC r15+1       ; 5 cycles
BNE endIncr15   ; 2 or 3 cycles
INC r15+2       ; 5 cycles
BNE endIncr15   ; 2 or 3 cycles
INC r15+3       ; 5 cycles

A minimum of 38 cycles, assuming that adding 4 to the PC does not cause a carry.

We are up to a total of at least 108 clock cycles without even considering the decoding logic. Thinking about that, just to get the registers we need to do a mask (2 cycles) and two shifts (4 cycles) for each one to multiply the register number by four to get its address (NB the destination register is held in the top four bits of the third byte of the instruction, so actually, we need to divide it by four). That's another 18 cycles.

On a 4MHz 6502, we are already down to a maximum theoretical ARM speed of around 30,000 instructions per second for the simplest possible add instruction.


Update:

superheat has come up with an optimisation for the add which is a compromise between my original all hard coded example and the generalised zero page indirect example.

You can have 16 copies of the following

CLC                   ; 2 cycles
LDA regByte0,X        ; 4 cycles
ADC regByte0,Y        ; 4 cycles
STA regDestByte0      ; 3 cycles (assuming in zero page)
LDA regByte1,X        ; 4 cycles
ADC regByte1,Y        ; 4 cycles
STA regDestByte1      ; 3 cycles (assuming in zero page)
LDA regByte2,X        ; 4 cycles
ADC regByte2,Y        ; 4 cycles
STA regDestByte2      ; 3 cycles (assuming in zero page)
LDA regByte3,X        ; 4 cycles
ADC regByte3,Y        ; 4 cycles
STA regDestByte3      ; 3 cycles (assuming in zero page)

which comes in at 46 cycles instead of 70. X and Y are used as the the register numbers (or 2x or 4x of the register number depending on how the bytes of the registers are organised in memory).

However, you wouldn't use this in practice because one of the source operands can be far more general than the content of a register. It's far more likely that you would calculate the effective address of the second operand, put it in a zero page location and then indirectly address off that.

And of course, none of the solutions addresses the fact that we have hard coded an ADC between the load and the store. In reality, you'd do a JMP through a vector table (5 extra cycles) and then a JMP back to the relevant store (3 cycles) and you'd probably fetch both operands into two zero page locations, do the calculation into another zero page location and then store all four bytes back to the specified register, especially if you weren't deliberately optimising for speed.

So, although supercat's optimisation would improve on my upper estimate of 30k operations per second, it wouldn't be enough to get to 100k and it does not take into account (neither did may example) the fact that generalising to more than just adding adds a whole lot more overhead that makes my 30k look optimistic.

  • Using sixteen copies of e.g. lda reglo,x / adc reglo,y / sta reglo+4 / lda reglh,x / adc reglh,y / sta reglo+5, etc. with registers not in zero page, would take 48 cycles plus the time to set up x and y, which would likely come in a fair bit below 70. Note that the load abs+index is the same speed as zp+index, so the only time penalty for not using ZP is on the store. – supercat Apr 11 at 20:13
  • @supercat Except you would put the registers in zero page because you want to be able to do indirect addressing off them, especially for r15. – JeremyP Apr 13 at 11:26
  • There's likely no real benefit to keeping all four octets of any particular register consecutive. Code could keep the two lower octets in zero-page, and put the other two elsewhere, though the only register for which even that would seem particularly useful would be the PC, and operations involving PC need special handling in any case. While an 8-bit load/store with no displacement (e.g. ldrb r2,[r1]) might be usefully handled by loading x and y with indices for registers 1 and 2 and using (zp,x) addressing mode for the load and regbase,y for the store, longer loads/stores... – supercat Apr 15 at 15:41
  • ...or those with non-zero displacement would likely need to be processed using effective-address calculations, as you note. From a practical standpoint, a bigger issue is that to fit within a code size that would have been reasonable in that era, an emulator would more likely have to handle an instruction like the add r0,r1,r2 as by first using a computed jump to select a "set up for ALU operation" routine, then have a routine copy r1 and r2 to fixed addresses, then use a computed jump to select the "add" routine which would compute the sum and store it at a fixed address, and then... – supercat Apr 15 at 15:57
  • ...jump to a routine that would copy that value to the destination register as appropriate. Maybe the code could an indexed addressing mode to update the destination in place, but in any case the code isn't going to be fast. – supercat Apr 15 at 16:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.