0 Introduction

To command a computer’s hardware, you must speak its language. The words of a computer’s language are called instructions, and its vocabulary is called an
instruction set (指令集).

The basic job of a CPU is to execute lots of instructions. Instructions are the primitive operations that the CPU may execute.

You might think that the languages of computers would be as diverse as those of people, but in reality, computer languages are quite similar, more like regional dialects than independent languages. Hence, once you learn one, it is easy to pick up others.

Different CPUs implement different sets of instructions. The set of instructions a particular CPU implements is an Instruction Set Architecture (ISA). For example, ARM(cell phones), Intel x86, RISC-V, …

Early trend was to add more and more instructions to new CPUs to do elaborate operations.

RISC philosophy (Cocke IBM, Patterson, Hennessy, 1980s) - Reduced Instruction Set Computing

  • Keep the instruction set small and simple, makes it easier to build fast hardware.
  • Let software do complicated operations by composing simpler ones.
  • This went against the convention wisdom of the time.

RISC-V started in summer 2010 to support open research and teaching at UC Berkeley. It’s a new open-source, license-free ISA spec supported by growing shared software ecosystem and appropriate for all levels of computing system.

1 Add/Sub Instructions

The syntax of instructions is rigid: 1 operator and 3 operands

1
2
3
4
5
6
7
8
add x1, x2, x3   # x1 = x2 + x3
sub x3, x4, x5 # x3 = x4 - x5

# opname rd, rs1, rs2
# opname is the name of the opration.
# rd is the operand getting result.
# rs1 is the first operand for operation.
# rs2 is the second operand for operation.

The compiler translates from C to RISC-V assembly language instructions. How to do the C statement a = b + c + d - e; ?

1
2
3
add x10, x1, x2
add x10, x10, x3
sub x10, x10, x4

2 Operands of the Computer Hardware

2.1 Registers

Unlike programs in high-level languages, the operands of arithmetic instructions are restricted; they must be from a limited number of special locations built directly in hardware called registers (寄存器).

Registers are primitives used in hardware design that
are also visible to the programmer when the computer is completed, so you can think of registers as the bricks of computer construction. The size of a register in the RISC-V architecture is 64 bits; groups of 64 bits occur so frequently that they are given the name doubleword in the RISC-V architecture. (Another popular size is a group of 32 bits, called a word in the RISC-V architecture.)

Instruction set for a particular architecture (e.g. RISC-V) is represented by the assembly language.

Each line of assembly code represents one instruction for the computer.

Assembly can’t use variables. Assembly operands are registers, which are limited number of special locations, built directly into the hardware. Register contents are just bits.

Each ISA has a predetermined number of registers because registers are built into hardware. There are 32 registers in RISC-V. Each RISC-V register is 32 bits wide in RV32 variant. Groups of 32 bits is called a word in RV32. The width of a word is associated with a variant of the architecture.

Registers are numbered from 0 to 31 (x0 to x31)

x0 is special, always holds value zero.

2.2 Memory Operands

Programming languages have simple variables that contain single data elements, as in these examples, but they also have more complex data structures—arrays and structures. These composite data structures can contain many more data elements than there are registers in a computer. How can a computer represent and access such large structures?

The processor can keep only a small amount of data in registers, but computer memory contains billions of data elements. Hence, data structures (arrays and structures) are kept in memory.

As explained above, arithmetic operations occur only on registers in RISC-V instructions; thus, RISC-V must include instructions that transfer data between
memory and registers. Such instructions are called data transfer instructions.

To access a word or doubleword in memory, the instruction must supply the memory address. Memory is just a large, single-dimensional array, with the
address acting as the index to that array, starting at 0.

The data transfer instruction that copies data from memory to a register is called load. The format of the load instruction is the name of the operation followed by the register to be loaded, then register and a constant used to access memory. The sum of the constant portion of the instruction and the contents
of the second register forms the memory address. The real RISC-V name for this instruction is ld, standing for load doubleword.

Let’s assume that A is an array of 100 doublewords and that the compiler has associated the variables g and h with the registers x20 and x21 as before. Let’s also assume that the starting address, or base address, of the array is in x22.
Compile this C assignment statement

1
g = h + A[8];

Although there is a single operation in this assignment statement, one of the operands is in memory, so we must first transfer A[8] to a register. The address of this array element is the sum of the base of the array A, found in register x22, plus the number to select element 8. The data should be placed in a temporary register for use in the next instruction

1
2
ld x9, 64(x22) # Temporary reg x9 gets A[8]
add x20, x21, x9

The register added to form the address is called the base register, and the constant in a data transfer instruction is called the offset

Since 8-bit bytes are useful in many programs, virtually all architectures today address individual bytes. Therefore, the address of a doubleword matches the address of one of the 8 bytes within the doubleword.

Computers divide into those that use the address of the leftmost or “big end” byte as the doubleword address versus those that use the rightmost or “little end” byte. RISC-V belongs to the latter camp, referred to as little-endian.

Byte addressing also affects the array index. To get the proper byte address in the code above, the offset to be added to the base register x22 must be 8 × 8, or 64.

The instruction complementary to load is traditionally called store; it copies data from a register to memory. The format of a store is similar to that of a load: the name of the operation, followed by the register to be stored, then the base register, and finally the offset to select the array element.

Assume variable h is associated with register x21 and the base address of the array A is in x22. What is the RISC-V assembly code for the C assignment
statement below?

1
A[12] = h + A[8];
1
2
3
ld x9, 64(x22)
add x9, x21, x9
sd x9, 96(x22)

lw指load word,额感觉61C的课的质量比61A、61B下降了,是我错觉么

Many programs have more variables than computers have registers. Consequently, the compiler tries to keep the most frequently used variables in registers and places the rest in memory, using loads and stores to move variables between registers and memory. The process of putting less frequently used variables (or those needed later) into memory is called spilling registers (寄存器换出).

Registers take less time to access and have higher throughput than memory, making data in registers both considerably faster to access and simpler to use. Accessing registers also uses much less energy than accessing memory. To achieve the highest performance and conserve energy, an instruction set architecture must have enough registers, and compilers must use registers efficiently.

2.3 RISC-V Immediates

Immediates are numerical constants

1
2
3
4
5
6
7
8
9
10
11
# Add Immediate instruction
# The last operand must be a number, not a register.
addi x3, x4, 10 # f = g + 10

addi x3, x4, -10 # f = g - 10

add x3, x4, x0 # f = g

addi x3, x0, 0xff # f = 0xff

add x0, x3, x4 # Will not do anything.

3 Instructions for Making Decisions

What distinguishes a computer from a simple calculator is its ability to make decisions. Based on the input data and the values created during computation,
different instructions execute.

1
beq rs1, rs2, L1

This instruction means go to the statement labeled L1 if the value in register rs1 equals the value in register rs2. The mnemonic beq stands for branch if equal.

1
bne rs1, rs2, L1

It means go to the statement labeled L1 if the value in register rs1 does not equal the value in register rs2. These two instructions are traditionally called conditional branches.

1
2
3
4
beq x5, x6, Equal    # 如果 x5 == x6,跳到 Equal
addi x7, x7, 1 # 如果不相等,就继续执行这一行
Equal:
add x8, x8, x9

无条件的分支更简单,j Label即可
例如

1
2
3
4
5
if (i == j) {
f = g + h;
} else {
f = g - h;
}
1
2
3
4
5
bne x13, x14, Else
add x10, x11, x12
j Exit
Else: sub x10, x11, x12
Exit: #...
1
2
3
4
blt reg1, reg2, Label # if (reg1 < reg2) goto Label
bge reg1, reg2, Label # if (reg1 >= reg2) goto Label
bltu reg1, reg2, Label # compare unsigned numbers
bgeu reg1, reg2, Label # unsigned

对于循环

1
2
3
4
double save[5];
while (save[i] == k) {
i += 1;
}
1
2
3
4
5
6
7
8
9
10
11
# x22寄存器对应i,x25保存数组的基地址
Loop:
slli x10, x22, 3 # x10 = i × 8,得到字节偏移量
add x10, x10, x25 # x10 = 数组首地址 + i × 8
ld x9, 0(x10) # x9 = save[i]

bne x9, x24, Exit # save[i] != k,就退出
addi x22, x22, 1 # 否则 i++
j Loop # 跳回去,重新检查

Exit: