ISADesign:ARMDesign:C

Copyright 2018, Clinton A Staley

Concepts

  1. Branch instruction design
  2. Multiplication instructions
  3. SWI instruction
  4. Use of fixed bits

Overview

In this segment we'll look at the ARM branch, multiply, and swi instruction formats. And we'll revisit the way fixed bits are cleverly used to identify instruction formats.

Branch instructions

The branch instruction format is one of the simplest in the table, but it still has several interesting points. After the cond, it has the fixed bits 101, then a single bit to indicate if the lr register should be set (making it a bl).

After that, there is just the 24-bit offset. The branch instructions we saw in our code examples always had a label (representing an address) as a target, but as we've seen over and over, it's not possible to store a 32-bit address in an instruction along with opcodes, condition codes, etc. Branches thus use PC-relative addressing just like ldr and str, jumping to a location a given number of bytes before or after the current PC. However, their offset is considerably larger, at 24 bits. A 24 bit signed integer ranges from less than -8 million to over 8 million, which is a pretty large range of instructions before or after the PC.

But, the range is even larger than that. The offset is really 26-bits because the CPU automatically appends two 0-bits to the right end of it.

Question 1

Why does it make sense to append 00 to the end of the offset? Isn't it possible that the desired offset might end with 01, or 10? Explain your answer.

Answer 1

Instructions are all 4 bytes long, without exception, so we would only jump by values divisible by 4. Those always have 00 as their last two bytes, so the 00 may as well be assumed. Another way to look at it is that the offset is in units of words, not bytes. To get a byte offset we shift left by two bits, filling in with 00.

With this boost, the range of branch offsets becomes better than -32 million to +32 million bytes, or -8 million to 8 million instructions, so one can jump from one instruction to any other instruction, forward or backward, in a code block of over 8 million instructions.

Question 2

In some rare cases we might want to jump even farther than that. How would be do so? As a hint, we wouldn't use a branch instruction. We need to be able to jump to an address specified in a full 32-bit register.

Answer 2

Load the desired address into, say, r0 and then do mov pc, r0. Or save a step and load directly into pc: ldr pc, =targetLabel

Multiplication instructions

We used mul and mla in an early example. Lets see how those instructions are organized. In most tables, they're just under the data processing line, under Multiply (not Multiply Long)

The mul instruction has 6 fixed bits: 000000 to identify it. It has just two 1-bit flags: A and S. And it has 4 register fields, plus more fixed bits in positions 4-7. It performs the operation Rd = Rm * Rs. But, the same format provides the mla instruction. Set the 'A' (for Acccumulate) bit, and the instruction performs Rd = Rm * Rs + Rn. And if you want to set condition codes based on the result, set the S flag, just as for data processing instructions.

Question 3

Recall that mul doesn't allow constant operand, only registers. Explain why this restriction is needed.

Answer 3

The instruction doesn't offer any place to specify a constant. Too many bits are taken up by fixed bits and by specifying up to 4 registers.

This proves to be less of an inconvenience than it might seem, because multiplication by any reasonably small constant is usually done instead by addition and shifts, as discussed in the module on optimization.

Long Multiplication

We dd not use the Multiply Long instruction (assembly MULL), but it's worth discussing since we're on the topic. This instruction computes Rm * Rn, but puts the result into two registers, specified as RdLo and RdHi.

Question 4

What reason would there be for using two 32-bit registers to store the product? Consider all possible 32-bit values we might multiply.

Answer 4

The product of large 32-bit values will take more than 32 bits to represent. 2 billion * 2 billion, for instance, is 4 quintillion, which takes at least 62 bits to represent. In general, for a product of X-bit numbers, you'll need 2*X bits.

To see this more easily, consider what the result will be of multiplying two 3-digit decimal numbers. 999*999 is 998001, which takes 6 digits, twice the number of either of the operands.

So, why does a mul or mla use only 32 bits for the result? Because it is designed for small products, which are the most common case. If the product exceeds 32 bits, mul and mla just drop the upper 32 bits. You (or the compiler) are expected to know when operands are small enough for this not to be a problem, and use mul or mula only in that case.

SWI

The only instruction we've used in the course, but not examined in this topic, is swi. (Aside from the floating point instructions we deferred to a later topic.) The format for swi is at the bottom of most tables, labelled Software Interrupt and it's quite simple, just the usual cond, plus fixed bits of 1111, plus a software interrupt number. Each interrupt number causes a jump to a different point in the operating system, and thus each system call (roughly) has its own interrupt number. 24 bits leaves ample room for an arbitrary number of system call choices.

Telling them all apart

We've covered seven different instruction formats so far, and there are obviously more. When the processor gets a 32-bit instruction from memory, how can it tell which type of instruction it is? If we look closely at the fixed-bit patterns, we'll see that it's always possible to distinguish one instruction type from another. Let's try a few.

Question 5

How do we distinguish an ldr/str instruction from a DP instruction? How about a load/store multiple function? How do we distinguish it from either of the other two? Answer the same question for Branch and Software Interrupt

Answer 5

This is all about the fixed bits after the cond field. They're 00 for data processing, 01 for ldr/str, 100 for load/store multiple, 101 for branch, and 1111 for swi.

But, the two multiply formats are a little more complex. They start with 000000 and 00001, respectively, which distinguishes them from one another.

Question 6

But those five fixed bits don't distinguish them from the data processing instructions. Why not?

Answer 6

Because a data processing instruction could have 000000 or 00001 after the cond field, if its I-flag is 0 and the first bits of the opcode are 000 or 01.

OK, so then how does the CPU differentiate a mul or mull instruction from a data processing instruction?

Question 7

The fixed 1001 in bits 4-7 seems like a good way to do so, but couldn't the right operand2 value in a data processing instruction also have that pattern in the middle? Why or why not? Consider both the I-flag = 1 case and the I-flag = 0 case. The answer is different in each case.

Answer 7

If the I-flag is 1, then operand2 is a constant, and could readily have 1001 in the middle. But if the I-flag is 0, operand2 is a register-and-shift value, and per the discussion earlier in this topic, the pattern XXXX1XX1XXXX is impossible, by intentional design.

Question 8

OK, but if a constant operand2 could include 1001 in the middle, then what distinguishes a data processing instruction with a constant operand2 of form XXXX1001XXXX from a mul or mull?

Answer 8

The I-flag is 1 if operand2 is constant, so the data processing instruction could not have 000000 or 00001 after the cond.

By now it's clear that the design of those fixed-bit patterns is very clever. Fixed bits are dead weight; they don't communicate register numbers, flags, or other data. But, they're essential to differentiate one instruction format from another. Very careful design can minimize them, without introducing ambiguity. We'll examine this more closely in the next topic on general ISA design.