Data Transfer Operations
Just using registries to store data is not enough which is why we also have main memory and secondary memory. Main memory is especially useful when working with composite data such as data structures or dynamic data.
As mentioned previously we can not directly work on data that is stored in memory, the CPU can only work on data that is in a registry. This leads us to load and store data between the registries and the main memory.
Each byte in memory has an address. For composite data, RISC-V uses the little endian byte ordering meaning that the LSB byte is at the smallest address.
RISC-V defines a word as data that consists of 32 bits this corresponds to the size of the registry and is the most common size to read and write to and from memory. However, we can also only read a byte which is useful since ASCII only uses a byte. RISC-V also supports reading a so-called halfword which corresponds to 16 bits which is useful when working with Unicode characters.
We do however need to keep in mind that in memory we only store the value, no context. So if we want a word to be handled like an unsigned integer we also need to specify that otherwise, it will treat it by default as a signed integer.
Instruction | Type | Example | Meaning |
---|---|---|---|
Load word | I | lw rd, imm12(rs1) | R[rd] = Mem4[R[rs1] + SignExt(imm12)] |
Load halfword | I | lh rd, imm12(rs1) | R[rd] = SignExt(Mem2[R[rs1] + SignExt(imm12)]) |
Load byte | I | lb rd, imm12(rs1) | R[rd] = SignExt(Mem1[R[rs1] + SignExt(imm12)]) |
Load word unsigned | I | lwu rd, imm12(rs1) | R[rd] = ZeroExt(Mem4[R[rs1] + SignExt(imm12)]) |
Load halfword unsigned | I | lhu rd, imm12(rs1) | R[rd] = ZeroExt(Mem2[R[rs1] + SignExt(imm12)]) |
Load byte unsigned | I | lbu rd, imm12(rs1) | R[rd] = ZeroExt(Mem1[R[rs1] + SignExt(imm12)]) |
Store word | S | sw rs2, imm12(rs1) | Mem4[R[rs1] + SignExt(imm12)] = R[rs2] |
Store halfword | S | sh rs2, imm12(rs1) | Mem2[R[rs1] + SignExt(imm12)] = R[rs2](15:0) |
Store byte | S | sb rs2, imm12(rs1) | Mem1[R[rs1] + SignExt(imm12)] = R[rs2](7:0) |
Loading With Pointers
Pointers in C are nothing else but memory addresses which means we can also load data from and to them. The most simple use of pointers is to swap to values:
// x in a0, y in a1
void swap(int *x, int *y)
{
int temp_x = *x;
int temp_y = *y;
*x = temp_y;
*y = temp_x;
}
And as we can see we can use addresses stored in registries to load and write data:
lw a4, 0(a0)
lw a5, 0(a1)
sw a5, 0(a0)
sw a4, 0(a1)
Loading Sequential Data
When reading sequential data we do need to keep in mind that each address only corresponds to a byte. This leads us to make "jumps" of size 4.
// h in x21, base address of A in x22
A[9] = h + A[8]
lw x9, 32(x22)
add x9, x21, x9
sw x9, 46(x22)