RISC-V Syscalls

Making a syscall

An ecall asks the simulator to provide a service such as printing text, reading input, checking elapsed time, or working with a project file. These services are part of this simulator environment; they are not a RISC-V operating-system interface, and another RISC-V system may use different service numbers or conventions.

Each syscall has a unique code that is used to identify it. You must put the syscall code inside the a7 register before calling the ecall instruction. As an example:

# Load the integer 42 into register a0, which is
# the register that will be printed by the syscall
li a0, 42
# Set the syscall code for printing an integer
li a7, 1
ecall

A C or C++ program reaches the same services through <sim.h>. Each entry names its function in From C; sim_print_int(42); performs the same service.

How services behave in this simulator

The same service numbers and register conventions work in RV32 and RV64. Interactive runs connect to the editor's Terminal and Project FileSystem. Testcases use scripted input, an isolated copy of project files, and a virtual clock so the same program can be run reproducibly.

  • Line-based reads wait for Enter; a character read completes on one key, and Enter has value 10. In a Testcase, reads take characters from the testcase's input. Standard input (descriptor 0) also accepts Ctrl+D or End of input and returns zero bytes.

  • Time (30) reports Unix epoch milliseconds. In a Testcase it starts at 2000-01-01 UTC and advances through sleep (32), whose wait completes immediately. Interactive sleeps keep the page responsive. The time and RV32 timeh CSRs use the same program clock, and Undo restores the previous reading.

  • Random services (40–44) use Java-compatible pseudorandom sequences. An unseeded stream starts from host randomness interactively and a fixed per-generator seed in a Testcase. Service 40 selects the same sequence in either mode. Undo restores a stream before a draw or reseed.

  • Files live in the Project's FileSystem, with descriptors from 3; they do not reach the computer's disk. Open flags are 0 (read), 1 (write with create), or 9 (write with create and append); the mode argument is ignored. Errors return -1 where the service returns a result. Undo restores file changes. Each Testcase starts with its own copy of the Project Files.

  • The memory-mapped keyboard device queues typed keys, so fast typing and scripted input do not lose keys. The display transmitter is always ready. Device interrupt-enable bits are unsupported: this editor polls the device and stops the program with an error if an interrupt is requested.

  • Exit (10) reports code 0; exit2 reports its signed code in the Log. A runtime error stops the program until Undo, Stop, or Build. The Terminal transcript is retained through Undo.

  • Compiled C and C++ programs place the heap at the first 4 KiB page after static data when it needs to move. Static data occupies 4,128,768 bytes starting at 0x10010000; its last byte is 0x103FFFFF and the exclusive end is 0x10400000. Constants, globals, and a SIM_SCREEN grid share this range. Assembly programs use the assembler's memory layout.

  • MIDI services 31 and 33 are unavailable; calling either stops the program with an unsupported-service error.

  • GetCWD (17) writes /, the Project root, rather than a host working directory. It needs at least two bytes including the terminating NUL; failure returns -1.

  • Input dialog double (53) reads its message from a0 and returns the value in fa0.

Open supports read-write extensions: flag 2 opens an existing File (r+), 3 creates or truncates (w+), and 10 creates and appends writes (a+). All three allow reading and writing with one shared position. Original flags 0, 1 and 9 retain their reference behavior. Runtime-only service 1100 returns -1 for heap exhaustion; service 9 still stops with a runtime error.

Program arguments

Enter Arguments on the Terminal's first line, the Command line, or in the project's Settings panel. They take effect at the next Build; during a Debug session, the line shows what the current run started with. Stop the Debug session to edit the Command line. Arguments are startup strings, separate from text the program reads later through Terminal input.

A Testcase can supply its own Arguments. An empty or whitespace-only field inherits the project's Arguments, so it cannot request an empty argument list while project Arguments are set.

Entering argument strings

The field uses these quoting rules:

  • Whitespace separates arguments unless quoted or escaped.
  • Single quotes ('two words') and double quotes ("two words") keep text in one argument. The quote marks are removed; '' and "" each create an empty argument.
  • Outside quotes, a backslash takes the next character literally: two\ words is one argument. A trailing backslash stays a backslash.
  • Inside single quotes, every character is literal, including a backslash. Inside double quotes, a backslash escapes only a double quote, backslash, dollar sign, or backtick; before other characters it remains a backslash.
  • Adjacent quoted and unquoted parts form one argument: pre"two words" becomes pretwo words.
  • Nothing expands and no command runs. Characters such as $, *, ~, #, |, >, ;, and & are ordinary text.

An unfinished quote stops Build with an Arguments problem naming the column where that quote opened.

For a program named ./main, entering hello "two words" '' a\ b produces argc = 5:

IndexString
argv[0]./main
argv[1]hello
argv[2]two words
argv[3]An empty string
argv[4]a b

Access from assembly and C/C++

The program starts with a0 = argc, the count of all strings, and a1 = argv, the address of an array of pointers to them. Each string is UTF-8 with a terminating NUL; argv[argc] is a null pointer ending the array. argv[0] is the program name: ./ plus the Entry path without its final extension (./main for main.s), or the C or C++ source path without its final extension for a compiled program (./main for main.c). User Arguments begin at argv[1], so argc is at least 1.

Pointers are 4 bytes in RV32 and 8 bytes in RV64. To print the first user argument with the print-string service, perform these operations in order:

OperationRV32RV64
Skip the load if argc < 2li t0, 2, then bltu a0, t0, no_argumentli t0, 2, then bltu a0, t0, no_argument
Load the pointer in argv[1]lw a0, 4(a1)ld a0, 8(a1)
Print the NUL-terminated string at that pointerli a7, 4, then ecallli a7, 4, then ecall

no_argument is a label you supply for the case with no user argument. Loading the pointer into a0 replaces the count, so save argc and argv first if you need them after this service call.

For C or C++, declare int main(int argc, char *argv[]). Check argc > 1 before using argv[1]; for example, if (argc > 1) printf("%s\n", argv[1]); prints the first user argument when <stdio.h> is included. See the Runtime library for startup and function support.

Memory layout and limits

The strings and pointer array occupy the Arguments memory region above the stack, below 0x7FFFFFFC. sp keeps its initial value, 0x7FFFEFFC. The memory view labels the array argv and the strings argv[0], argv[1], … . With the program name ./main and Arguments hello "two words", argc is 3:

ItemRV32RV64
a0 (argc)33
a1 (address of argv)0x7FFFFFD40x7FFFFFC0
argv[1] (address of hello)0x7FFFFFEC0x7FFFFFEC

Undo back to the start keeps these initial arguments. A Testcase's starting registers are applied afterwards, so overriding the argument registers replaces the supplied count or array address.

The Arguments field may contain at most 1,024 arguments, excluding argv[0]. The entire stored block may use at most 4,080 bytes. That total includes the program name and argument strings as UTF-8 bytes, one terminating NUL byte per string, one pointer per string, the final null pointer, and padding to align the pointer array. Pointers use 4 bytes in RV32 and 8 bytes in RV64; alignment padding uses 0–3 bytes or 0–7 bytes respectively. A string cannot contain a NUL character. Build stops with an Arguments problem if the count or storage limit is exceeded.

When porting code from RARS, another RISC-V simulator, account for its different argument layout: RARS omits the program name, starts argv with the first user argument, and places argc and the pointers below the stack, lowering sp. It also uses 4-byte pointers in RV64. In this editor, keep the program-name slot and use the pointer width for your selected architecture.

Print integer

a7 = 1
In
a0 = Integer to print
From C
void sim_print_int(int value)

Print float

a7 = 2
In
fa0 = Float to print
From C
void sim_print_float(float value)

Print double

a7 = 3
In
fa0 = Double to print
From C
void sim_print_double(double value)

Print string

a7 = 4
In
a0 = Address of null-terminated string to print
From C
void sim_print_string(const char *text)

Read integer

a7 = 5
Out
a0 = Contains the integer parsed from the input line
Note
Reads one line, trims surrounding whitespace, and parses it as an integer. Invalid input stops the program with an input error.
From C
int sim_read_int(void)

Read float

a7 = 6
Out
fa0 = Contains the floating-point value parsed from the input line
Note
Reads one line, trims surrounding whitespace, and parses it as a floating-point value. Invalid input stops the program with an input error.
From C
float sim_read_float(void)

Read double

a7 = 7
Out
fa0 = Contains the double-precision value parsed from the input line
Note
Reads one line, trims surrounding whitespace, and parses it as a double-precision value. Invalid input stops the program with an input error.
From C
double sim_read_double(void)

Read string

a7 = 8
In
a0 = Address of input buffer; a1 = Maximum number of characters to read
Out
Memory at the address passed in a0 is written through the buffer pointer.
Note
Service 8 - Follows semantics of UNIX 'fgets'. For specified length n, string can be no longer than n-1. If less than that, adds newline to end. In either case, then pads with null byte If n = 1, input is ignored and null byte placed at buffer address. If n < 1, input is ignored and nothing is written to the buffer.
From C
void sim_read_string(char *buffer, int size)

Sbrk (allocate heap memory)

a7 = 9
In
a0 = Number of bytes to allocate
Out
a0 = Contains address of allocated memory
From C
void *sim_sbrk(int bytes)

Exit (terminate execution)

a7 = 10
From C
void sim_exit(void)

Print character

a7 = 11
In
a0 = Character to print
Note
Prints the ASCII character whose code is in the low-order byte.
From C
void sim_print_char(int character)

Read character

a7 = 12
Out
a0 = Contains the input character code
From C
int sim_read_char(void)

Get cwd

a7 = 17
In
a0 = Address of the buffer to write the path into; a1 = Length of the buffer
Out
a0 = Contains -1 if the path and its terminating null do not fit in the buffer, and is left unchanged otherwise; Memory at the address passed in a0 is written through the buffer pointer.
Note
Writes /, the Project root, followed by a null byte. The minimum buffer size is 2.
From C
int sim_get_cwd(char *buffer, int size)

Time (program time)

a7 = 30
Out
a0 = Low order 32 bits of the program time; a1 = High order 32 bits of the program time
Note
Returns milliseconds since 1970-01-01 UTC. In a Testcase, calendar time starts at 2000-01-01 UTC and advances through waits of service 32.
From C
long long sim_time(void)

Sleep

a7 = 32
In
a0 = The length of time to sleep in milliseconds
Note
Service 32 - Lets that much program time pass before the next instruction. The editor stays responsive while it waits and the wait costs no instructions, so a program idling on the keyboard never reaches the execution limit; in a testcase it completes at once and advances the virtual clock instead.
From C
void sim_sleep(int milliseconds)

Print integer in hexadecimal

a7 = 34
In
a0 = Integer to print
Note
Displayed value is 8 hexadecimal digits, left-padding with zeroes if necessary.
From C
void sim_print_hex(int value)

Print integer in binary

a7 = 35
In
a0 = Integer to print
Note
Displayed value is 32 bits, left-padding with zeroes if necessary.
From C
void sim_print_binary(int value)

Print integer as unsigned

a7 = 36
In
a0 = Integer to print
Note
Displayed as unsigned decimal value.
From C
void sim_print_unsigned(unsigned value)

Set seed

a7 = 40
In
a0 = Generator ID: any integer selects an independent pseudorandom sequence; a1 = Seed for corresponding pseudorandom number generator
Note
Seeds the generator selected by a0. Each generator ID has an independent sequence. An unseeded ID starts from host randomness in an interactive run and a fixed per-ID seed in a testcase, making testcase runs repeatable. Values follow Java’s Random sequence for the same seed. Undo restores the state before a draw or reseed.
From C
void sim_random_seed(int generator, int seed)

Random int

a7 = 41
In
a0 = Generator ID: any integer selects an independent pseudorandom sequence
Out
a0 = Contains the next pseudorandom, uniformly distributed int value from this random number generator's sequence
Note
Each generator ID has an independent sequence. An unseeded ID starts from host randomness in an interactive run and a fixed per-ID seed in a testcase, making testcase runs repeatable. Service 40 sets an explicit seed. Values follow Java’s Random sequence for the same seed. Undo restores the state before a draw or reseed.
From C
int sim_random_int(int generator)

Random int range

a7 = 42
In
a0 = Generator ID: any integer selects an independent pseudorandom sequence; a1 = Upper bound of range of returned values
Out
a0 = Contains pseudorandom, uniformly distributed int value in the range 0 <= [int] < [upper bound], drawn from this random number generator's sequence
Note
Each generator ID has an independent sequence. An unseeded ID starts from host randomness in an interactive run and a fixed per-ID seed in a testcase, making testcase runs repeatable. Service 40 sets an explicit seed. Values follow Java’s Random sequence for the same seed. Undo restores the state before a draw or reseed.
From C
int sim_random_int_range(int generator, int bound)

Random float

a7 = 43
In
a0 = Generator ID: any integer selects an independent pseudorandom sequence
Out
fa0 = Contains the next pseudorandom, uniformly distributed float value in the range 0.0 <= f < 1.0 from this random number generator's sequence
Note
Each generator ID has an independent sequence. An unseeded ID starts from host randomness in an interactive run and a fixed per-ID seed in a testcase, making testcase runs repeatable. Service 40 sets an explicit seed. Values follow Java’s Random sequence for the same seed. Undo restores the state before a draw or reseed.
From C
float sim_random_float(int generator)

Random double

a7 = 44
In
a0 = Generator ID: any integer selects an independent pseudorandom sequence
Out
fa0 = Contains the next pseudorandom, uniformly distributed double value in the range 0.0 <= f < 1.0 from this random number generator's sequence
Note
Each generator ID has an independent sequence. An unseeded ID starts from host randomness in an interactive run and a fixed per-ID seed in a testcase, making testcase runs repeatable. Service 40 sets an explicit seed. Values follow Java’s Random sequence for the same seed. Undo restores the state before a draw or reseed.
From C
double sim_random_double(int generator)

ConfirmDialog

a7 = 50
In
a0 = Address of null-terminated string that is the message to user
Out
a0 = Contains value of user-chosen option 0: Yes 1: No 2: Cancel
From C
int sim_confirm_dialog(const char *message)

InputDialogInt

a7 = 51
In
a0 = Address of null-terminated string that is the message to user
Out
a0 = Contains int read; a1 = Contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field
From C
int sim_input_dialog_int(const char *message, int *status)

InputDialogFloat

a7 = 52
In
a0 = Address of null-terminated string that is the message to user
Out
f0 = Contains the floating-point value parsed from the input line; a1 = Contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field
From C
float sim_input_dialog_float(const char *message, int *status)

InputDialogDouble

a7 = 53
In
a0 = Address of null-terminated string that is the message to user
Out
fa0 = Contains the double-precision value parsed from the input line; a1 = Contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field
From C
double sim_input_dialog_double(const char *message, int *status)

InputDialogString

a7 = 54
In
a0 = Address of null-terminated string that is the message to user; a1 = Address of input buffer; a2 = Maximum number of characters to read
Out
a1 = Contains status value 0: OK status. Buffer contains the input string. -2: Cancel was chosen. No change to buffer. -3: OK was chosen but no data had been input into field. No change to buffer. -4: length of the input string exceeded the specified maximum. Buffer contains the maximum allowable input string plus a terminating null.; Memory at the address passed in a1 is written through the buffer pointer.
Note
See Service 8 note below table
From C
int sim_input_dialog_string(const char *message, char *buffer, int size)

MessageDialog

a7 = 55
In
a0 = Address of null-terminated string that is the message to user; a1 = The type of message to be displayed: 0: error message, indicated by Error icon 1: information message, indicated by Information icon 2: warning message, indicated by Warning icon 3: question message, indicated by Question icon other: plain message (no icon displayed)
From C
void sim_message_dialog(const char *message, int type)

MessageDialogInt

a7 = 56
In
a0 = Address of null-terminated string that is an information-type message to user; a1 = Int value to display in string form after the first string
From C
void sim_message_dialog_int(const char *message, int value)

Close file

a7 = 57
In
a0 = File descriptor
From C
void sim_close(int fd)

MessageDialogDouble

a7 = 58
In
a0 = Address of null-terminated string that is an information-type message to user; fa0 = Double value to display in string form after the first string
From C
void sim_message_dialog_double(const char *message, double value)

MessageDialogString

a7 = 59
In
a0 = Address of null-terminated string that is an information-type message to user; a1 = Address of null-terminated string to display after the first string
From C
void sim_message_dialog_string(const char *message, const char *text)

MessageDialogFloat

a7 = 60
In
a0 = Address of null-terminated string that is an information-type message to user; fa1 = Float value to display in string form after the first string
From C
void sim_message_dialog_float(const char *message, float value)

Lseek (move file position)

a7 = 62
In
a0 = File descriptor; a1 = Offset in bytes; a2 = Where the offset counts from: 0 the start of the file, 1 the current position, 2 the end of the file
Out
a0 = Contains the new position, counted from the beginning of the file (-1 if error)
Note
Descriptors 0, 1, and 2 (standard input, output, and error) cannot be repositioned.
From C
int sim_lseek(int fd, int offset, int whence)

Read from file

a7 = 63
In
a0 = File descriptor; a1 = Address of input buffer; a2 = Maximum number of characters to read
Out
a0 = Contains number of characters read (0 if end-of-file, negative if error); Memory at the address passed in a1 is written through the buffer pointer.
From C
int sim_read(int fd, void *buffer, int length)

Write to file

a7 = 64
In
a0 = File descriptor; a1 = Address of output buffer; a2 = Number of characters to write
Out
a0 = Contains number of characters written (negative if error)
From C
int sim_write(int fd, const void *buffer, int length)

Exit2 (terminate with value)

a7 = 93
In
a0 = Termination result
Note
Ends the program with the signed exit code in a0. The editor shows this code in the Log.
From C
void sim_exit2(int code)

Open file

a7 = 1024
In
a0 = Address of null-terminated string containing filename; a1 = Flags; a2 = Mode
Out
a0 = Contains file descriptor (negative if error)
Note
Use flag 0 to open for reading, 1 to open for writing (creating the file if needed), or 9 to open for writing and append (creating the file if needed). Editor extensions 2, 3, and 10 open for reading and writing: 2 requires an existing file, 3 creates or truncates, and 10 creates and appends writes. They share one file position. The mode argument is ignored. A negative descriptor means the operation failed. Descriptors 0, 1, and 2 are reserved for standard input, standard output, and standard error; project files receive descriptors starting at 3.
From C
int sim_open(const char *path, int flags, int mode)