RISC-V Syscalls

Making a syscall

An ecall asks the simulator to provide a service such as printing text, reading input, reading calendar 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.

The same service numbers and register conventions work in RV32 and RV64. 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.

Sound

Services 31 and 33 play a note with a pitch, instrument, volume, and duration in milliseconds. Service 31 starts the note and continues immediately, so notes started close together can overlap to make a chord. Service 33 waits for the requested duration before the next instruction, which lets a sequence of notes keep time as a melody. Notes finish their duration even if the program exits; Stop or a new Build stops them.

For programmable waveforms and sample playback, this editor adds the Synth, a sound chip whose registers are memory at 0xffff0100. RARS does not have it.

Pitch numbers run from 0 to 127. Each increase of 1 moves up a semitone: one piano-key step, counting both white and black keys. For example, 60 is middle C, 62 is D, 64 is E, 67 is G, and 72 is the C one octave above middle C.

General MIDI is a standard list of instrument sounds. These services number instruments from 0 to 127: 0 is acoustic grand piano, 40 is violin, 56 is trumpet, and 73 is flute. No MIDI file or external instrument is needed. Volume runs from 0 (silent) to 127 (loudest).

The editor supplies a General MIDI sound bank, a collection of instrument sounds. The browser loads it automatically when the editor page first plays a note or MIDI file, so that first request waits for loading. The bank is shared across program runs and projects until the page is reloaded.

After a program requests sound, the Sound button appears in the execution dock as a mute/unmute control. It sets Play sound in Preferences, where Sound volume is also available. Both settings apply across projects. Muting leaves program-visible timing and results unchanged.

This editor differs from RARS in two ways:

  • At most 64 notes sound at once, where RARS has no limit. Starting another stops the oldest.
  • Testcases are silent. Service 33 still waits for the requested duration on the testcase's virtual clock, so the wait completes immediately and advances that clock.

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 NUL-terminated UTF-8 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
Waits for a line in the Terminal and Enter, or reads scripted input in a Testcase. 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
Waits for a line in the Terminal and Enter, or reads scripted input in a Testcase. 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
Waits for a line in the Terminal and Enter, or reads scripted input in a Testcase. 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 = Size of input buffer in bytes
Out
Memory at the address passed in a0 is written through the buffer pointer.
Note
Waits for a line in the Terminal and Enter, or reads scripted input in a Testcase. Stores UTF-8 with fgets-style limits: for buffer size n, writes at most n-1 bytes, adds a newline if there is room, then a NUL byte. Truncation can split a multibyte character. If n = 1, ignores input and writes only NUL; if n < 1, ignores input and writes nothing.
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
Note
Heap exhaustion stops execution with a runtime error.
From C
void *sim_sbrk(int bytes)

Exit (terminate execution)

a7 = 10
Note
Ends the program successfully with exit code 0.
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
Note
Waits for one key in the Terminal and completes immediately; Enter returns 10. A Testcase supplies scripted input.
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. The time CSR (and timeh in RV32) reads this same clock. Undo restores recorded register and CSR readings.
From C
long long sim_time(void)

MIDI out

a7 = 31
In
a0 = Pitch, 0 to 127: 60 is middle C, 62 is D, 64 is E; each step is one semitone; a1 = Duration in milliseconds; a2 = Instrument sound from the General MIDI list, numbered from 0 to 127: 0 piano, 40 violin, 56 trumpet, 73 flute; a3 = Volume, 0 (silent) to 127 (loudest)
Note
Plays a note and continues immediately, allowing notes to overlap. Values outside 0 to 127 use these defaults: invalid pitch becomes 60 (middle C), invalid instrument becomes 0 (piano), and invalid volume becomes 100. A negative duration becomes 1000 milliseconds, as in RARS. The first note or MIDI-file request after opening or reloading the editor page waits while the browser loads the sound bank, the collection of instrument sounds; later runs share it. At most 64 notes sound at once, where RARS has no limit: starting another stops the oldest. Notes finish their duration after the program exits. Testcases are silent.
From C
void sim_midi_out(int pitch, int duration, int instrument, int volume)

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 read by service 30 and the time CSR (timeh in RV32) instead.
From C
void sim_sleep(int milliseconds)

MIDI out synchronous

a7 = 33
In
a0 = Pitch, 0 to 127: 60 is middle C, 62 is D, 64 is E; each step is one semitone; a1 = Duration in milliseconds; a2 = Instrument sound from the General MIDI list, numbered from 0 to 127: 0 piano, 40 violin, 56 trumpet, 73 flute; a3 = Volume, 0 (silent) to 127 (loudest)
Note
Plays a note with the same argument defaults as service 31, then waits for the requested duration before the next instruction. A sequence of these calls can play a melody. Muting or stopping the audible note does not shorten the wait. Testcases are silent; the wait completes immediately and advances the virtual clock by the duration.
From C
void sim_midi_out_sync(int pitch, int duration, int instrument, int volume)

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 NUL-terminated UTF-8 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 NUL-terminated UTF-8 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 NUL-terminated UTF-8 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 NUL-terminated UTF-8 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 NUL-terminated UTF-8 string that is the message to user; a1 = Address of input buffer; a2 = Size of input buffer in bytes
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
Stores the entered string as UTF-8 in the buffer: at most n-1 bytes for buffer size n, followed by a newline if there is room and a terminating NUL. Truncation can split a multibyte character.
From C
int sim_input_dialog_string(const char *message, char *buffer, int size)

MessageDialog

a7 = 55
In
a0 = Address of NUL-terminated UTF-8 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 NUL-terminated UTF-8 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 NUL-terminated UTF-8 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 NUL-terminated UTF-8 string that is an information-type message to user; a1 = Address of NUL-terminated UTF-8 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 NUL-terminated UTF-8 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 bytes to read
Out
a0 = Contains number of bytes read (0 if end-of-file, -1 if error); Memory at the address passed in a1 is written through the buffer pointer.
Note
Reads bytes from an open Project File. Descriptor 0 reads standard input from the Terminal: enter a line and press Enter. Ctrl+D or End of input on an empty line ends standard input; a Testcase uses scripted input and reaches end of input when its answers run out. Returns 0 at end of input or file, and -1 on failure. Undo restores file state; each Testcase uses its own copy of Project Files.
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 bytes to write
Out
a0 = Contains number of bytes written (-1 if error)
Note
Writes bytes to an open Project File; descriptors 1 and 2 write to the Terminal as standard output and standard error. Returns the number of bytes written, or -1 on failure. Undo restores file changes; each Testcase uses its own copy of Project Files.
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 NUL-terminated UTF-8 path in Project Files; a1 = Flags; a2 = Mode
Out
a0 = Contains file descriptor (-1 if error)
Note
Opens a file in the Project Files, without accessing the computer’s disk. 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. Failure returns -1. Descriptors 0, 1, and 2 are reserved for standard input, standard output, and standard error; project files receive descriptors starting at 3. Undo restores file changes; each Testcase starts with its own copy of the Project Files.
From C
int sim_open(const char *path, int flags, int mode)