The ecall
instruction is a special command in RISC-V, and corresponds to a environment/system call. For this project, we’ve created helper functions in utils.s
that wrap around the various different ecalls for you to use. Do not make ecalls directly in your own code. Use these helper functions instead.
All of these functions are documented in inline comments in utils.s, alongside their arguments and return values. The most important of these are highlighted below.
print_int
, print_str
, and print_char
for printing valuesfopen
, fread
, fwrite
, and fclose
for reading/writing to filesexit
to quit the program with a zero exit code (no error)exit2
to quit the program with the integer in a1
as the exit codemalloc
, which allocates a0
bytes on the heap, and returns a pointer to them in a0
.free
, which frees heap memory specified by the pointer in a0
print_int_array
, which prints out all elements of an integer arrayVenus currently supports the following environment calls through ecall
instruction. To use an environment call, load the ecall code into register a0
and load any arguments into a1
- a7
or fa0
-fa7
(floating-point) registers. You can invoke the ecall either directly or by invoking the helper function through util.s
Prints integer in register a1
.
.globl __start
.text
__start:
li a0, 1 # ecall code
li a1, 0xa # integer to print
ecall
# If util.s is imported
...
li a0, 1 # ecall code
jal print_int
a0
= 1a1
= integer to printPrints null-terminated string whose address is in register a1
.
``C void print_str(char *a1)
#### Example
```python
.globl __start
.rodata
msg: .asciiz "Hello World!!!"
.text
__start:
li a0, 4 # ecall code
la a1, msg # string to print
ecall
# If util.s is imported
...
la a1, msg
jal print_str
a0
= 4a1
= null-terminated string addressReads an integer from address and stores the result in register a0
.
int atoi(char* a1)
.globl __start
.data
msg: .asciiz "5"
.text
__start:
li a0, 5
la a1, msg # ecall code
ecall
# If util.s is imported
la a1, msg
jal atoi
a0
= 5a1
= null-terminated string addressStores a pointer to a block of memory containing n
additional bytes in register a0
. This pointer is word aligned.
void *sbrk(int a1)
.globl __start
.text
__start:
li a0, 9 # ecall code
li a1, 100 # number of bytes
ecall
# If util.s is imported
li a1,100
jal sbrk
a0
= 9a1
= number of bytes to reservea0
- pointer to allocated region(pseudo call that builds on sbrk)
void* malloc(int a0)
# If util.s is imported
li a0,100
jal malloc
a0
= number of bytes to reservea0
- pointer to allocated regionStops a program from running.
void noreturn exit()
.globl __start
.text
__start:
li a0, 10 # ecall code
ecall
# If util.s is imported
jal exit
a0
= 10void print_char(char a1)
Prints a character whose ascii code is in register a1
.
.globl __start
.text
__start:
li a0, 11 # ecall code
li a1, 'a' # character to print
ecall
li a1,'a'
jal print_char
a0
= 11a1
= ascii code to printOpens a file that we can then read and/or write to, depending on the permission bit. Returns a file descriptor, which is a unique integer tied to the file. Must be called on a file before any other operations can be done on it.
.globl __start
.rodata
# open flags
O_RD: .word 0b00000000
O_RDWR_CREAT: .word 0b0000001
# pathname
path: "example.txt"
.text
__start:
li a0, 13 # ecall code
la a1, path # pathname address
li a2,ORDWR_CREAT # w+ . Open for write. Create file if it does not exist.
jal fopen
a0
= 13a1
= is a pointer to a string containing the filename of the file to opena2
= open flags
a2
is an integer denoting the permissions we open the file with. For example, we can open the file with read permissions, which prevents us from writing to it. For this project, we only really care about a few basic permission bits: 0
, which corresponds to r
for read only permission, and 1
which corresponds to w
for write only permission. Note that w
will overwrite the file if it already exists, and create it if it doesn’t.a0
= is a file descriptor, which is a unique integer tied to the file. We will call future file-related functions on this file descriptor, so we know which opened file we’re reading/writing/closing. On failure, a0
is set to -1
.
int fread(int a1, void *a2, size_t a3)
Reads a given number of bytes from a file into a buffer, which is a preallocated chunk of memory to store the bytes. Note that repeated reads will read consecutive bytes from the file. For example, two fread
s of 8 bytes on a file will read the first 8 bytes and then the second 8 bytes. It will not read the same 8 bytes twice.
a0
is the number of bytes actually read from the file. If the number of bytes actually read differs from the number of bytes specified in the input then then we either hit the end of the file or there was an error.Reads data into a buffer whose address is in register a2
. Stores the number of bytes that were read in register a0
. If value is negative, then an error occurred. The buffer size should be greater or equal to the number bytes to read.
.globl __start
.data
read: .zero 1024
.text
__start:
li a0, 14 # ecall code
li a1, 1 # file descriptor
la a2, read # buffer address
li a3, 10 # number of bytes to read
ecall
# If util.s is imported
li a1, 1 # file descriptor
la a2, read # buffer address
li a3, 10 # number of elements to read
jal fread
a0
= 14a1
is the file descriptor of the file we want to read from, previously returned by fopen
.a2
is a pointer to the buffer that we’re going to read the bytes from the file into. This must be an appropriate amount of memory that was allocated before calling the function, and passed in as a pointer.a3
is the number of bytes to read from the file.a0
- number of bytes read. if a0 < a3, you have reached end of file.int fwrite(int a1, void *a2, size_t a3, size_t a4)
Writes a given number of elements of a given size. Like fread
, subsequent writes to the same file do not overlap, but are rather appended to each other. Note that unlike fread
, we don’t pass in the total number of bytes but rather the total number of elements and the size of each element in bytes. We can multiply the two to find the total number of bytes written.
Additionally, note that our writes aren’t actually saved until we run fclose
or fflush
.
.globl main
.data
write: .asciiz "Hello World"
.text
main:
li a0, 15 # ecall code
li a1, 1 # file descriptor
la a2, write # buffer address
li a3, 3 # number of bytes to write
li a4, 4 # size of each element 4 bytes. Totally a3*a4 = 3*4 = 12 bytes written
ecall
# If util.s is imported
li a1, 1 # file descriptor
la a2, write # buffer address
li a3, 3 # number of bytes to write
li a4, 4 # size of each element 4 bytes. Totally a3*a4 = 3*4 = 12 bytes written
jal fwrite
a0
= 15a1
is the file descriptor of the file we want to write to, previously returned by fopen
.a2
is a pointer to a buffer containing what we want to write to the file.a3
is the number of elements to write out of the buffera4
is the size of each buffer element in bytesa0
is the number of elements actually written to the file. If a0 != a3
, then we either hit the end of the file or there was an error.Closes an open file descriptor. Stores a 0 upon success in register a0
, and a -1 upon failure.
int fclose(int a1)
.globl __start
.text
__start:
li a0, 16 # ecall code
li a1, 1 # file descriptor
ecall
# if util.s is imported
li a1, 1 # file descriptor
jal fclose
a0
= 16a1
= file descriptorStops the program from running and exits
void noreturn exit2(int a1)
.globl __start
.text
__start:
li a0, 17 # ecall code
li a1, 1 # status code value
ecall
# if util.s is imported
li a1, 1 # exit2 code
jal exit2
a0
= 17a1
= exit status codeWrites closed file to disk.
.globl __start
__start:
li a0, 18 # fflush
li a1, 1 # file descriptor
ecall
li a1, 1
jal fflush
a0
= 18a1
= file descriptora0
= 0 on success, and EOF (-1) otherwise.Returns a nonzero value if the file stream has errors, otherwise it returns 0.
.globl __start
__start:
li a0, 20 # fflush
li a1, 1 # file descriptor
ecall
li a1, 1
jal ferror
a0
= 20a1
= file descriptora0
= Nonzero falue if the end of file is reached. 0 Otherwise.Prints integer in register a1
in hex representation. Displayed value is 8 hexadecimal digits, left-padding with zeroes if necessary.
.globl __start
.text
__start:
li a0, 34 # ecall code
li a1, 0xa # integer to print in hex
ecall
a0
= 34a1
= integer to printHelper function.
Prints an integer array, with spaces between the elements. DOES NOT terminate with new line. In the assignments please include new line after the array.
.globl __start
.data
m0: .word 1 2 3 4 5 6 7 8 9
.text
__start:
la a0,m0 # Base ptr
li a1,3 # number of rows
li a2,3 # number of cols
jal print_int_array
# Output
# 1 2 3
# 4 5 6
# 7 8 9
Helper function.
Prints an coo matrix array, with spaces between the elements. DOES NOT terminate with new line. In the assignments please include new line after the array.
.globl __start
.data
coo_vector0: .word 0 0 1 0 2 3 0 4 5 0 5 6 0 6 7 0 8 9
.text
__start:
la a0,coo_vector0 # Base ptr
li a1,3 # number of nonzero elements
jal print_coo_array
0,0 1
0,2 3
1,1 5
1,2 6
2,0 7
2,2 9