Project 2
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
1 of 19
Project 2
Due October 25, 2021 at 9:00 PM
You will be working with a partner for this project. This specification is subject to change at
anytime for additional clarification.
Desired Outcomes
• Exposure to low level RISC-V programming
• An understanding of how system calls are implemented
• Exposure to concurrency and non-determinism
Project Description
For this project, you will be writing the beginnings of a simple operating system designed to run
on a RISC-V based game console called RVCOS. Your OS must be written in C and RISC-V
assembly. You may use the C standard library newlib that is built into the environment, this will
be particularly helpful for malloc and free. You will want to begin by getting the simulation
environment downloaded and up and running. The tools you will need for this project are in a git
repository at https://github.com/UCDClassNitta/riscv-console. There are directions for getting up
and running on multiple systems assuming you already have Docker installed. We may be adding
more features to help debugging as time goes on, and will update as that occurs. Note that the build
of gcc will take a long time, so you should get the tools up and running ASAP.
The RISC-V gcc version necessary has been built and runs on the CSIF. It is currently in
/home/cjnitta/riscv32/bin You can add this to your path with the following command
and will be able to make the example as if compiling it within the Docker container.
export PATH=”$PATH:/home/cjnitta/riscv32/bin”
The simulator is on the CSIF at /home/cjnitta/ecs150/riscv-console-sim. You can
do X11 forwarding to execute the simulator remotely. See directions for X11 forwarding on CSIF
docs here. Running through X11 from the CSIF will be slow and likely problematic if desiring to
run full speed; however, for debugging and stepping through the code it should be fine.
The simulated hardware you will be developing your operating system for has a RISC-V RV32EM
processor with only machine mode enabled. This means that only the first 16 registers are valid,
and the integer multiply/divide instructions are supported. The game console is designed to support
cartridges that are inserted, so only one “application” will ever be running at a time. Since there is
only a single mode, the memory space is accessible to both the OS and the game application. The
memory map is shown in Table 1. The current version of RVCOS dedicates the low 2MiB of RAM
to OS and provides the upper 14MiB for the application. Figure 1shows the layout of the RAM for
the OS and the application, remember that the Text section will be in the Flash/ROM section.
Table 1. RISC-V Console Memory Map
Base Address Size Description
0x00000000 16MiB Firmware Flash
0x20000000 16MiB Cartridge ROM
0x40000000 72B Chipset Registers
0x50000000 1MiB Video Controller Memory
0x70000000 16MiB RAM
https://github.com/UCDClassNitta/riscv-console
http://csifdocs.cs.ucdavis.edu/about-us/csif-general-faq#TOC-How-do-I-allow-SSH-to-do-X11-Forwarding-for-forwarding-GUI-application-windows-
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
2 of 19
Figure 1. RAM Layout
The RVCOS API that you will mainly be developing for this this project are the thread APIs.
Figure 2 shows the possible states a RVCOS thread can be in as well as the transitions between
them. The threading support in RVCOS is designed for potentially short run threads that can
reactivated as needed. The reactivation allows for the rerunning of a thread without the overhead
of allocating resources.
Figure 2. RVCOS Thread States and Transitions
1. Thread created
2. Thread activated
3. Thread selected to run
4. Thread quantum expired or higher priority thread selected
5. Thread terminated
6. Thread unblocked due to I/O, other thread, or timeout
7. Thread blocked waiting for time, I/O or another thread
App
OS
0x70000000
0x70200000
0x71000000
Stack
Data
Heap
Stack
Data
Heap
gp for OS
gp for App
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
3 of 19
Threads have one of three priority levels (high, normal, and low), which is statically assigned at
creation. The main thread has normal thread priority. The highest priority ready thread always
runs, if a thread is preempted it is put to the end of its priority queue. If multiple threads are ready
at the same priority level, they each receive one quantum of time that is equivalent to one tick of
the timer. The current design is targeted to have one tick every two milliseconds.
Currently RVCOS will not support beyond 256 threads including the idle and main thread;
therefore, a maximum of 254 threads can be created through calls to RVCThreadCreate.
Directions for submitting will be posted later. The following sections describe the API in more
detail. A working compiled binary and working application examples will be posted soon.
You should avoid using existing source code as a primer that is currently available on the Internet.
You must specify in your readme file any sources of code that you have viewed to help you
complete this project. Any copied code snippets from online sources must have the URL source
in comments next to its use. All class projects will be submitted to MOSS to determine if pairs of
students have excessively collaborated with other pairs. Excessive collaboration, or failure to list
external code sources will result in the matter being transferred to Student Judicial Affairs.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
4 of 19
Name
RVCOS – Initializes cartridge main thread in RVCOS
Synopsys
#include “RVCOS.h”
TStatus RVCInitalize(uint32_t *gp);
Description
RVCInitialize() initializes the cartridges main thread and sets the cartridges global pointer through
the gp variable. This system call be invoked as part of the cartridge startup code.
Return Value
Upon successful initialization of the main thread, RVCInitialize() will return
RVCOS_STATUS_SUCCESS. If RVCInitialize() function is called after the initialization it will
return RVCOS_STATUS_ ERROR_INVALID_STATE.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
5 of 19
Name
RVCThreadCreate – Creates a thread in the RVCOS.
Synopsys
#include “RVCOS.H”
typedef TThreadReturn (*TThreadEntry)(void *);
TStatus RVCThreadCreate(TThreadEntry entry, void *param, TMemorySize memsize,
TThreadPriority prio, TThreadIDRef tid);
Description
RVCThreadCreate() creates a thread in the RVCOS. Once created the thread is in the created state
RVCOS_THREAD_STATE_CREATED. The entry parameter specifies the function of the
thread, and param specifies the parameter that is passed to the function. The size of the threads
stack is specified by memsize, and the priority is specified by prio. The thread identifier is put into
the location specified by the tid parameter.
Return Value
Upon successful creation of the thread RVCThreadCreate() returns
RVCOS_STATUS_SUCCESS. If either entry or tid is NULL, RVCThreadCreate() returns
RVCOS_STATUS_ERROR_INVALID_PARAMETER.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
6 of 19
Name
RVCThreadDelete – Deletes a dead thread from the RVCOS.
Synopsys
#include “RVCOS.H”
TStatus RVCThreadDelete(TThreadID thread);
Description
RVCThreadDelete() deletes the dead thread specified by thread parameter from the RVCOS.
Return Value
Upon successful deletion of the thread from the RVCOS, RVCThreadDelete() returns
RVCOS_STATUS_SUCCESS. If the thread specified by the thread identifier thread does not
exist, RVCOS_STATUS_ERROR_INVALID_ID is returned. If the thread does exist but is not in
the RVCOS_THREAD_STATE_DEAD state, RVCOS_STATUS_ERROR_INVALID_STATE
is returned.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
7 of 19
Name
RVCThreadActivate – Activates a newly created or dead thread in the RVCOS.
Synopsys
#include “RVCOS.H”
TStatus RVCThreadActivate(TThreadID thread);
Description
RVCThreadActivate() activates the newly created or dead thread specified by thread parameter in
the RVCOS. After activation the thread enters the RVCOS_THREAD_STATE_READY state,
and must begin at the entry function specified.
Return Value
Upon successful activation of the thread in the RVCOS, RVCThreadActivate() returns
RVCOS_STATUS_SUCCESS. If the thread specified by the thread identifier thread does not
exist, RVCOS_STATUS_ERROR_INVALID_ID is returned. If the thread does exist but is not in
the newly created or dead sates, RVCOS_THREAD_STATE_CREATED or
RVCOS_THREAD_STATE_DEAD, RVCOS_STATUS_ERROR_INVALID_STATE is
returned.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
8 of 19
Name
RVCThreadTerminate – Terminates a thread in the RVCOS.
Synopsys
#include “RVCOS.H”
TStatus RVCThreadTerminate(TThreadID thread, TThreadReturn returnval);
Description
RVCThreadTerminate() terminates the thread specified by thread parameter in the RVCOS. After
termination the thread enters the state RVCOS_THREAD_STATE_DEAD, and the thread return
value returnval is stored for return values from RVCThreadWait(). The termination of a thread
can trigger another thread to be scheduled.
Return Value
Upon successful termination of the thread in the RVCOS, RVCThreadTerminate() returns
RVCOS_STATUS_SUCCESS. If the thread specified by the thread identifier thread does not
exist, RVCOS_STATUS_ERROR_INVALID_ID is returned. If the thread does exist but is in the
newly created or dead states, RVCOS_THREAD_STATE_CREATED or
RVCOS_THREAD_STATE_DEAD, RVCOS_STATUS_ERROR_INVALID_STATE is
returned.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
9 of 19
Name
RVCThreadWait – Terminates a thread in the RVCOS.
Synopsys
#include “RVCOS.H”
TStatus RVCThreadWait(TThreadID thread, TThreadReturnRef returnref);
Description
RVCThreadWait() waits for the thread specified by thread parameter to terminate. The return
value passed with the associated RVCThreadTerminate() call will be placed in the location
specified by returnref. RVCThreadWait() can be called multiple times per thread.
Return Value
Upon successful termination of the thread in the RVCOS, RVCThreadWait() returns
RVCOS_STATUS_SUCCESS. If the thread specified by the thread identifier thread does not
exist, RVCOS_STATUS_ERROR_INVALID_ID is returned. If the parameter returnref is NULL,
RVCOS_STATUS_ERROR_INVALID_PARAMETER is returned.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
10 of 19
Name
RVCThreadID – Retrieves thread identifier of the current operating thread.
Synopsys
#include “RVCOS.H”
TStatus RVCThreadID(TThreadIDRef threadref);
Description
RVCThreadID() puts the thread identifier of the currently running thread in the location specified
by threadref.
Return Value
Upon successful retrieval of the thread identifier from the RVCOS, RVCThreadID() returns
RVCOS_STATUS_SUCCESS. If the parameter threadref is NULL,
RVCOS_STATUS_ERROR_INVALID_PARAMETER is returned.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
11 of 19
Name
RVCThreadState – Retrieves the state of a thread in the RVCOS.
Synopsys
#include “RVCOS.H”
#define RVCOS_THREAD_STATE_CREATED ((TThreadState)0x01)
#define RVCOS_THREAD_STATE_DEAD ((TThreadState)0x02)
#define RVCOS_THREAD_STATE_RUNNING ((TThreadState)0x03)
#define RVCOS_THREAD_STATE_READY ((TThreadState)0x04)
#define RVCOS_THREAD_STATE_WAITING ((TThreadState)0x05)
TStatus RVCThreadState(TThreadID thread, TThreadStateRef state);
Description
RVCThreadState() retrieves the state of the thread specified by thread and places the state in the
location specified by state.
Return Value
Upon successful retrieval of the thread state from the RVCOS, RVCThreadState() returns
RVCOS_STATUS_SUCCESS. If the thread specified by the thread identifier thread does not
exist, RVCOS_STATUS_ERROR_INVALID_ID is returned. If the parameter stateref is NULL,
RVCOS_STATUS_ERROR_INVALID_PARAMETER is returned.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
12 of 19
Name
RVCThreadSleep – Puts the current thread in the RVCOS to sleep.
Synopsys
#include “RVCOS.H”
#define RVCOS_TIMEOUT_INFINITE ((TTick)0)
#define RVCOS_TIMEOUT_IMMEDIATE ((TTick)-1)
TStatus RVCThreadSleep(TTick tick);
Description
RVCThreadSleep() puts the currently running thread to sleep for tick ticks. If tick is specified as
RVCOS_TIMEOUT_IMMEDIATE the current process yields the remainder of its processing
quantum to the next ready process of equal priority.
Return Value
Upon successful sleep of the currently running thread, RVCThreadSleep() returns
RVCOS_STATUS_SUCCESS. If the sleep duration tick specified is
RVCOS_TIMEOUT_INFINITE, RVCOS_STATUS_ERROR_INVALID_PARAMETER is
returned.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
13 of 19
Name
RVCWriteText – Writes text out to the RISC-V Console in text mode.
Synopsys
#include “RVCOS.H”
TStatus RVCWriteText(const TTextCharacter *buffer, TMemorySize writesize);
Description
RVCWriteText() writes writesize characters starting at the location specified by buffer to the
RISC-V Console in text mode.
Return Value
Upon successful writing of characters to the console RVCWriteText() returns
RVCOS_STATUS_SUCCESS. If the buffer parameter is NULL,
RVCOS_STATUS_ERROR_INVALID_PARAMETER is returned.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
14 of 19
Name
RVCReadController – Reads the current status of the RISC-V Console controller.
Synopsys
#include “RVCOS.H”
typedef struct{
uint32_t DLeft:1;
uint32_t DUp:1;
uint32_t DDown:1;
uint32_t DRight:1;
uint32_t DButton1:1;
uint32_t DButton2:1;
uint32_t DButton3:1;
uint32_t DButton4:1;
uint32_t DReserved:24;
} SControllerStatus, *SControllerStatusRef;
TStatus RVCReadController(SControllerStatusRef statusref);
Description
RVCReadController() reads the current status of the RISC-V Console controller into the location
specified by statusref.
Return Value
Upon successful reading the console controller status RVCReadController() returns
RVCOS_STATUS_SUCCESS. If the statusref parameter is NULL,
RVCOS_STATUS_ERROR_INVALID_PARAMETER is returned.
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
15 of 19
RVCOS System Call Calling Convention
The system call calling convention is shown in Table 2. Notice that register a5 is used to specify
the system number, this is so that the stub function only needs to set the register and not modify
any other registers to invoke the system call. The return value for system call is through register
a0, also so that the stub does not need to modify the return value location.
Table 2. RVCOS Calling Convention
System Call a0 a1 a2 a3 a4 a5
RVCInitalize gp 0x00
RVCThreadCreate entry param memsize prio tid 0x01
RVCThreadDelete thread 0x02
RVCThreadActivate thread 0x03
RVCThreadTerminate thread returnval 0x04
RVCThreadWait thread returnref 0x05
RVCThreadID threadref 0x06
RVCThreadState thread stateref 0x07
RVCThreadSleep tick 0x08
RVCTickMS tickmsref 0x09
RVCTickCount tickref 0x0A
RVCWriteText buffer writesize 0x0B
RVCReadController statusref 0x0C
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
16 of 19
Helpful Resources
Some resources you will find helpful:
• RISC-V Console Repository – The toolchain plus hardware description
• RISC-V Unprivileged Specification – Useful for normal instruction behavior
• RISC-V Privileged Specification – Necessary for understanding exceptions
Suggested Approach
1. Get the tools up and running. Make the example from the repository and run it.
2. Create a base OS firmware project based upon the example. Modify it to limit the memory
locations as specified and add support for malloc if using newlib.
3. Modify your OS so that it can jump to the application when a cartridge is input.
4. Add support for responding to system call when an ECALL instruction occurs.
5. Implement the RVCInitalize() system call to keep track of the application global pointer.
6. Add support for writing to the console in text mode. This will aid in debugging as well.
7. Add support to read from the control. This will add support for inputting.
8. Implement your TCB and create functions to initialize a context and to switch contexts.
9. Add support for querying all of the status/IDs of the threads.
10. Implement the hardware timing support and add sleeping support for the threads.
11. Add support for waiting/terminating of the threads.
Helpful Hints
• You will want to use a skeleton/wrapper function to be the initial entry point for the thread,
and for it to call the entry from the TRVCThreadCreate. This is necessary in case the
thread doesn’t explicitly call RVCThreadTerminate.
• You may need to use the volatile keyword for variables that may get modified during
an interrupt. The volatile keyword guarantees that the compiler will generate code to
go to memory for every access of the variable.
• You will likely want an idle thread that will execute when all other threads are blocked.
Conveniently the thread priorities are set to HIGH, NORMAL, and LOW as 3, 2, 1, so a
lower priority could be used for IDLE.
• The global pointer gp register points to 0x800 above the base of data section. It will be
different for the OS and the application, so take care when switching between the two.
• The thread pointer tp register is best to use to keep track of the current running thread, it
can either hold the thread ID, or a pointer to the TCB.
• The mepc register will likely need to be saved on the stack in the interrupt handler as it is
possible that threads will be switched during the interrupt and the mepc may be rewritten
from another interrupt in the interim.
• Page 40 of the Privileged spec has important information about the ECALL instruction.
“ECALL and EBREAK cause the receiving privilege mode’s epc register to be set to the
address of the ECALL or EBREAK instruction itself, not the address of the following
instruction.” You will either need to add 4 to the mepc before calling mret, or possibly set
the mepc to what ra is at the beginning of the interrupt, this will be the return address from
the system call stub.
https://github.com/UCDClassNitta/riscv-console
https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf
https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMFDQC-and-Priv-v1.11/riscv-privileged-20190608.pdf
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
17 of 19
• In order to use malloc _sbrk needs to be defined. An example of what it would look like
can be found at https://sourceware.org/newlib/libc.html#Syscalls. Instead of calling the
external variable _end, something like _heapbase might be more appropriate. This
will also need to be defined in the linker script as something like:
_heapbase = MAX(_edata,MAX(_esdata,_ebss));
Grading
The point breakdown can be seen in the table below. Make sure your code compiles on the CSIF
as that is where it is expected to run. You will make an interactive grading appointment with a TA
to have your assignment graded. You must have a working webcam for the interactive grading
appointment. Project submissions received 24hr prior to the due date/time will received 10% extra
credit. The extra credit bonus will drop off at a rate of 0.5% per hour after that, with no additional
credit being received for submissions within 4hr of the due date/time.
Points Description
5 Has Makefile and submission compiles
5 Has README with sources cited
10 Jumps into cartridge application when inserted
5 Responds to system call and return control to app
5 Initializes the main thread and returns control to cartridge
10 Supports text output to console
5 Able to read input from console controller
15 Able to create threads and switch contexts
5 Supports querying of status/thread ID of threads
5 Can setup and respond to timer interrupts
10 Threads can sleep and wait on other threads
10 Full applications execute correctly
10 Student understands all code they have provided
Change Log
2021-10-11 Added information about newlib in helpful hints, added RAM figure.
2021-10-12 Added change log, removed space from PATH in description. Added description
of scheduling behavior.
2021-10-14 Added description of maximum number of threads. Added explanation of mepc
with ECALL.
2021-10-17 Modified DReserved of SControllerStatus from 28 to 24 bits.
https://sourceware.org/newlib/libc.html#Syscalls
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
18 of 19
ECS150 FQ21 October 18, 2021
This content is protected and may not be shared, uploaded, or distributed.
Project 2
19 of 19
Project 2
Name
Synopsys
Description
Return Value
Name
Synopsys
Description
Return Value
Name
Synopsys
Description
Return Value
Name
Synopsys
Description
Return Value
Name
Synopsys
Description
Return Value
Name
Synopsys
Description
Return Value
Name
Synopsys
Description
Return Value
Name
Synopsys
Description
Return Value
Name
Synopsys
Description
Return Value
Name
Synopsys
Description
Return Value
Name
Synopsys
Description
Return Value