Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

1Learning Outcomes

No video. We recommend pulling up the memory section of the RISC-V Green Card.

2Local variables on the stack

So far, we have been able to translate very tiny amounts of C code to fit into registers. If we have too many (data) words to fit into registers, we must use memory.

3Register Conventions and Register Names

As mentioned earlier, a register can also be referred to by its register name. Register names define convention—that is, specifying how assembly instructions should use specific registers for specific common functions. These restrictions help build “agreement” upon how to translate separate components of a program so that the assembly instructions slot together.

The RISC-V green card lists all register names; we begin introducing and using them in this example.

4Problem Description

Translate the below C program by only using the temporary registers t0, t1, t2, and the stack pointer sp . You may access memory as needed.

1
2
3
4
5
6
int a = 5;
char b[] = "string"; // Array will get stored on stack
int c[10];
uint8_t d = b[3];
c[4] = a+d;
c[a] = 20;

5Setup

We will use temporary registers to store addresses, arithmetic data, and so on. We will store local variable on the stack by assigning each variable to some offset from our stack pointer sp.

The exact addresses of these local variables don’t matter, so long as we’re consistent. Suppose we use the following assignment:

VariableAddress relative to stack pointer
int a0(sp)
char b[7]4(sp)
int c[10]12(sp)
uint8_t d52(sp)

6Solution

Here is the full assembly translation of the original C code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
li t0 5            # R[t0] = 5
sw t0 0(sp)        # store int a on stack
li t0 0x69727473   # load "stri"
sw t0 4(sp)        # store first part of string
li t1 0x0000676E   # load rest of string
sw t1 8(sp)        # store rest of string
lb t0 7(sp)        # 4(sp) from b, 3(sp) from [3] 
sb t0 52(sp)       # store into d
lw t0 0(sp)        # load a 
lbu t1 52(sp)      # load d
add t2 t0 t1       # R[t2] = a+d
sw t2 28(sp)       # 12(sp) from c, 16(sp) from [4] 
li t0 20           # R[t0] = 20
lw t1 0(sp)        # R[t1] = a
slli t1 t1 2       # 5*sizeof(int) = 5*4 = 5<<2
addi t1 t1 12      # t1 from [a], 12 from c  
add t1 t1 sp       # compute &c[a]
sw t0 0(t1)        # c[a] = 20

We discuss line-by-line translation below. For each section, try to use the image to interpret why we specified the corresponding assembly instructions. Then, check your reasoning.

6.1Line 2: int a = 5;

1
2
li t0 5            # R[t0] = 5
sw t0 0(sp)        # store int a on stack
"Slide for int a = 5: assembly li and sw store 0x00000005 at 0(sp) on the stack; higher stack words show placeholder garbage with a map linking a, b, c, and d to sp offsets."

Figure 1:Line 1 of Stack Pointer Example.

6.2Line 3: Initialize string char b[] = "string";

We would like to store the bytes of "string" onto our stack, starting at location 4(sp). There is a straightforward but tedious way to do so, and a concise way that requires a bit of cleverness.

One approach (straightforward but tedious):

1
2
3
4
5
6
7
8
9
10
11
12
13
li t0 0x73
sb t0 4(sp)
li t0 0x74
sb t0 5(sp)
li t0 0x72
sb t0 6(sp)
li t0 0x69
sb t0 7(sp)
li t0 0x6E
sb t0 8(sp)
li t0 0x67
sb t0 9(sp)
sb x0 10(sp)
"Byte-by-byte string setup: repeated li and sb instructions place ASCII for string plus a null at 4(sp) through 10(sp); associated stack diagram highlights stored characters in red against garbage elsewhere."

Figure 2:Line 2 of Stack Pointer Example.

Alternate approach with much fewer instructions:

1
2
3
4
li t0 0x69727473   # load "stri"
sw t0 4(sp)        # store first part of string
li t1 0x0000676E   # load rest of string
sw t1 8(sp)        # store rest of string
"Concise string setup: two immediates pack stri and ng with padding, then sw at 4(sp) and 8(sp) lay out little-endian words; registers t0 and t1 hold the packed constants."

Figure 3:Line 2 of Stack Pointer Example, concise version.

6.3Line 4: Uninitialized array int c[10];

No instructions needed.

"Uninitialized int c[10]: slide notes no instructions run; stack shows int a and string data unchanged while 12(sp) onward remains labeled random garbage for c’s reserved space."

Figure 4:Line 3 of Stack Pointer Example.

6.4Line 4: Read array element uint8_t d = b[3];

1
2
lb t0 7(sp)        # 4(sp) from b, 3(sp) from [3] 
sb t0 52(sp)       # store into d
"uint8_t d = b[3]: lb from 7(sp) loads byte 0x69 into t0, then sb writes that byte to 52(sp); memory highlights the indexed character and d’s low byte."

Figure 5:Line 4 of Stack Pointer Example.

6.5Line 5: Store array element c[4] = a + d;

1
2
3
4
lw t0 0(sp)        # load a 
lbu t1 52(sp)      # load d
add t2 t0 t1       # R[t2] = a+d
sw t2 28(sp)       # 12(sp) from c, 16(sp) from [4]
"c[4] = a + d example: lw and lbu bring int a and uint8_t d into t0 and t1, add puts the sum in t2, and sw stores it at 28(sp); a diagram lists word offsets 12 through 32 for c[0] through c[5]."

Figure 6:Line 5 of Stack Pointer Example.

6.6Line 6: Variable array indexing c[a] = 20;

This line is the most challenging of the bunch, so we recommend working it out via Venus once you learn how to use Venus in lab.

1
2
3
4
5
6
li t0 20           # R[t0] = 20
lw t1 0(sp)        # R[t1] = a
slli t1 t1 2       # 5*sizeof(int) = 5*4 = 5<<2
addi t1 t1 12      # t1 from [a], 12 from c  
add t1 t1 sp       # compute &c[a]
sw t0 0(t1)        # c[a] = 20

7Quick Checks