Live data from Hacker News

Solving the Mystery of ARM7TDMI Multiply Carry Flag

bmchtech.github.io

21–30 of 35 posts

Re: Solving the Mystery of ARM7TDMI Multiply Carry Flag

#21
post #2

https://shonumi.github.io/blog/nds_rolling.html More context on how this value affects (at least one) DS game- see post from December 27th, 2019.

I'm not really familiar with ARM Asm but do you think that's handwritten Asm that its author overlooked the effects of the carry and it just happened to work, or a clever "emulator trap" added by Nintendo's compiler?

It's handwritten and hand-obfuscated assembly, the author didn't know the effect of the carry but knew it was deterministic; and the code worked.

Though it's not clear to me where the corruption after the second MLA instruction comes from, because the second block of three instructions should produce the same output as the first. It is possible that it was copied/pasted incorrectly.

Re: Solving the Mystery of ARM7TDMI Multiply Carry Flag

#22
post #5

> Seriously, they decided that the program counter should be a general purpose register. Why??? Don't really understand this reaction. Why not? Seems to make for a nice regular design that the PC is just another register.

The thing is... it isn't just another regular register. Reads from the PC are 8 bytes ahead, because that's where the PC is in the pipeline at read time. Branches are PC+8 relative for the same reason.

It's a design choice that makes sense in the classic RISC world where pipeline details are leaked to make implementation simpler. Delay slots in other RISCs work the same way. But it causes a lot of pain as implementations evolve beyond past the original design, and it's why Aarch64 junked a lot of Aarch32's quirks.

Re: Solving the Mystery of ARM7TDMI Multiply Carry Flag

#23
post #8

Earlier quoted context omitted.

Yeah, it was that way for all previous ARM processors too, for exactly that reason. Adding special cases would have increased the transistor count, for no great benefit. The only downside was that it exposed internal details of the pipelining IIRC. In the ARM2, a read of the PC would give the current instruction's location + 8, rather than its actual location, because by the time the instruction 'took place' the PC h…

Hey, I'm the author, sorry it came off that way, I was really just poking fun. I should've definitely phrased that better! But no, I really think that making the program counter a GPR isn't a good design decision - there's pretty good reasons why no modern arches do things that way anymore. I admittedly was originally in the same boat when I first heard of ARMv4T - I thought putting the PC as a GPR was quite clean, b…

The reason was because it makes subroutine return and stack frame cleanup simpler.

You know this, but background for anyone else:

ARM's subroutine calling convention places the return address in a register, LR (which is itself a general purpose register, numbered R14). To save memory cycles - ARM1 was designed to take advantage of page mode DRAM - the processor features store-multiple and load-multiple instructions, which have a 16-bit bitfield to indicate which registers to store or load, and can be set to increment or decrement before or after each register is stored or loaded.

The easy way to set up a stack frame (the way mandated by many calling conventions that need to unwind the stack) is to use the Store Multiple, Decrement Before instruction, STMDB. Say you need to preserve R8, R9, R10:

STMDB R8-R10, LR

At the end of the function you can clean up the stack and return in a single instruction, Load Multiple with Increment After:

LDMIA R8-R10, PC

This seemed like a good decision to a team producing their first ever processor, on a minimal budget, needing to fit into 25,000 transistors and to keep the thermal design power cool enough to use a plastic package, because a ceramic package would have blown their budget.

Branch prediction wasn't a consideration as it didn't have branch prediction, and register pressure wasn't likely a consideration for a team going from the 3-register 6502, where the registers are far from orthogonal.

Also, it doesn't waste instruction space: you already need 4 bits to encode 14 registers, and it means that you don't need a 'branch indirect' instruction (you just do MOV PC,Rn) nor 'return' (MOV PC,LR if there's no stack frame to restore).

There is a branch instruction, but only so that it can accommodate a 24-bit immediate (implicitly left-shifted by 2 bits so that it actually addresses a 26-bit range, which was enough for the original 26-bit address space). The MOV immediate instruction can only manage up to 12 bits (14 if doing a shift-left with the barrel shifter), so I can see why Branch was included.

Indeed, mentioning the original 26-bit address space: this was because the processor status flags and mode bits were also available to read or write through R15, along with the program counter. A return (e.g. MOV PC,LR) has an additional bit indicating whether to restore the flags and processor state, indicated by an S suffix. If you were returning from an interrupt it was necessary to write "MOVS PC, LR" to ensure that the processor mode and flags were restored.

# It was acceptable in the 80s', It was acceptable at the time... #

Ken Shirriff has a great article "Reverse engineering the ARM1" at https://www.righto.com/2015/12/reverse-engineering-arm1-ance....

Getting back to multipliers:

ARM1 didn't have a multiply instruction at all, but experimenting with the ARM Evaluation System (an expansion for the BBC Micro) revealed that multiplying in software was just too slow.

ARM2 added the multiply and multiply-accumulate instructions to the instruction set. The implementation just used the Booth recoding, performing the additions through the ALU, and took up to 16 cycles to execute. In other words it only performed one Booth chunk per clock cycle, with early exit if there was no more work to do. And as in your article, it used the carry flag as an additional bit.

I suspect the documentation says 'the carry is unreliable' because the carry behaviour could be different between the ARM2 implementation and ARM7TDMI, when given the same operands. Or indeed between implementations of ARMv4, because the fast multiplier was an optional component if I recall correctly. The 'M' in ARM7TDMI indicates the presence of the fast multiplier.

Re: Solving the Mystery of ARM7TDMI Multiply Carry Flag

#24

And just to get this out of the way, the carry flag’s behavior after multiplication isn’t an important detail to emulate at all. Software doesn’t rely on it. On as fixed of a hardware as a game console, and with the accompanying anti-piracy/anti-cheating/emulation efforts of that industry, I'd expect it to be. From the history of emulating previous consoles, we know that any deterministic difference can and will be e…

In the GBA scene, people didn't actually tend to exploit the carry flag at all. If there was any anti emulation, it was usually flashcart related or cpu timing related.

It was mostly the Game Pak Prefetch feature that was used to foil GBA emulators. A game can detect if the number of cycles to access ROM (with prefetch enabled) is incorrect.

Re: Solving the Mystery of ARM7TDMI Multiply Carry Flag

#25
post #5

> Seriously, they decided that the program counter should be a general purpose register. Why??? Don't really understand this reaction. Why not? Seems to make for a nice regular design that the PC is just another register.

The big issue is that it doesn't really need to be a GPR. You never find yourself using the PC in instructions other than in, say, the occasional add instruction for switch case jumptables, or pushes / pops. So it ends up wasting instruction space, when you could've had an additional register, or encoded a zero register (which is what AARCH64 does nowadays).

PC-relative instructions is how ARM loads 32-bit literals. "ldr,=label" is a psuedoinstruction that becomes relative to the PC, and it picks the nearest literal pool as the acutal address to read from.

Re: Solving the Mystery of ARM7TDMI Multiply Carry Flag

#26
post #16

Earlier quoted context omitted.

Ah I miscommunicated, I still think PC can and should be used in places like the operand of an LDR / ADD. It's using it as the output of certain instructions (and allowing it to be used as such) that I take issue with. ARMv4T allowed you to set PC as the output of basically any instruction, allowing you to create cursed instructions like this lol: eor pc, pc, pc

Isn't writing to it except by a branch instruction undefined behaviour? If you can use it as an operand, it has a register number, so you can use it as a result, unless you special-case one or the other, which ARM didn't do because it was supposed to be simple. They could have ignored it by omitting some write decode circuitry, but why?

Not undefined behavior, just won't switch in or out of THUMB mode.

Re: Solving the Mystery of ARM7TDMI Multiply Carry Flag

#27

> it allows the program counter to be used a general purpose register From a CPU emulator writer's perspective this isn't all that strange. For instance on Z80 the immediate jump instruction `JP nnnn` is loading a 16-bit immediate value into the internal PC register, which is the same thing as loading a 16-bit value into a regular register pair (e.g. 'LD HL,nnnn') - e.g. the mnemonics for the jump instruction could j…

The reason why the PC is not normally exposed as a regular register is that the set of operations needed for the PC is different than for the regular registers.

There are operations required for the PC that are not needed for regular registers, e.g. conditional add (a.k.a. conditional relative jump), add-and-store and load-and-store (a.k.a. procedure call).

On the other hand, there are many operations that are needed for regular registers and which are useless for the PC, e.g. logical operations, shift/rotate, multiplication and division and others.

Because of this, encoding the PC as a regular register is pointless and wasteful of the instruction encoding space.

Moreover, when the ISA has an implicit stack pointer, which is also the only register that can be used as a stack pointer, like the x86 ISA, the set of operations that are used with the SP is a very small subset of the operations available for the regular registers, so encoding the SP as a regular register is also wasteful. Especially in 32-bit x86, where the number of architectural registers was very small, it would have been better if the SP would not have been encoded as a regular register, wasting a register number.

Re: Solving the Mystery of ARM7TDMI Multiply Carry Flag

#28
post #8
post #5

> Seriously, they decided that the program counter should be a general purpose register. Why??? Don't really understand this reaction. Why not? Seems to make for a nice regular design that the PC is just another register.

Yeah, it was that way for all previous ARM processors too, for exactly that reason. Adding special cases would have increased the transistor count, for no great benefit. The only downside was that it exposed internal details of the pipelining IIRC. In the ARM2, a read of the PC would give the current instruction's location + 8, rather than its actual location, because by the time the instruction 'took place' the PC h…

> Adding special cases would have increased the transistor count, for no great benefit.

No, it's exactly backwards: supporting PC as a GPR requires special circuitry, especially in original ARM where PC was not even fully a part of the register file. Stephen Furber in his "VLSI RISC Architecture and Organization" describes in section 4.1 "Instruction Set and Datapath Definition" that quite a lot of additional activity happens when PC is involved (which may affect the instruction timings and require additional memory cycles).

Re: Solving the Mystery of ARM7TDMI Multiply Carry Flag

#29
post #5

> Seriously, they decided that the program counter should be a general purpose register. Why??? Don't really understand this reaction. Why not? Seems to make for a nice regular design that the PC is just another register.

Prefetching and other pipeline interactions need to be special-cased when any instruction reads or writes it.

Re: Solving the Mystery of ARM7TDMI Multiply Carry Flag

#30

> it allows the program counter to be used a general purpose register From a CPU emulator writer's perspective this isn't all that strange. For instance on Z80 the immediate jump instruction `JP nnnn` is loading a 16-bit immediate value into the internal PC register, which is the same thing as loading a 16-bit value into a regular register pair (e.g. 'LD HL,nnnn') - e.g. the mnemonics for the jump instruction could j…

The reason why the PC is not normally exposed as a regular register is that the set of operations needed for the PC is different than for the regular registers. There are operations required for the PC that are not needed for regular registers, e.g. conditional add (a.k.a. conditional relative jump), add-and-store and load-and-store (a.k.a. procedure call). On the other hand, there are many operations that are needed…

> The reason why the PC is not normally exposed as a regular register is that the set of operations needed for the PC is different than for the regular registers.

I'd add to that, that what you give is the reason it's /okay/ to expose the PC as a special register instead of a GPR. The reason that it's /important/ to is that the PC is accessed on every instruction fetch, so if it's part of a uniform register file, it basically eats up an entire read port of that register file. Register file size scaled badly with port count (much worse than it does with register count), so this ends up adding quite a bit of area. (You can hack around this by having a single dedicated read port for just the PC register, but then you're half way to an SPR.)

Post reply on HN