Digital Garden
Computer Science
Computer Architecture
RISC-V
Data Transfer Operations

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.

InstructionTypeExampleMeaning
Load wordIlw rd, imm12(rs1)R[rd] = Mem4[R[rs1] + SignExt(imm12)]
Load halfwordIlh rd, imm12(rs1)R[rd] = SignExt(Mem2[R[rs1] + SignExt(imm12)])
Load byteIlb rd, imm12(rs1)R[rd] = SignExt(Mem1[R[rs1] + SignExt(imm12)])
Load word unsignedIlwu rd, imm12(rs1)R[rd] = ZeroExt(Mem4[R[rs1] + SignExt(imm12)])
Load halfword unsignedIlhu rd, imm12(rs1)R[rd] = ZeroExt(Mem2[R[rs1] + SignExt(imm12)])
Load byte unsignedIlbu rd, imm12(rs1)R[rd] = ZeroExt(Mem1[R[rs1] + SignExt(imm12)])
Store wordSsw rs2, imm12(rs1)Mem4[R[rs1] + SignExt(imm12)] = R[rs2]
Store halfwordSsh rs2, imm12(rs1)Mem2[R[rs1] + SignExt(imm12)] = R[rs2](15:0)
Store byteSsb 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.

Example
// 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)