Week: 13
Points: 20
Time: ~6 hours
Deliverable: virtus-os/ directory (all 9 services) + compliance suite pass + VCP verification + diary/week-13.md
What you ship
virtus-os/Math.vlvirtus-os/Memory.vlvirtus-os/String.vlvirtus-os/Array.vlvirtus-os/Output.vlvirtus-os/Screen.vlvirtus-os/Keyboard.vlvirtus-os/GamePad.vlvirtus-os/Sys.vl- Compliance suite output: 38/38 pass
- VCP verification evidence (photo or UART capture)
diary/week-13.md
Lab 13.1: Write Math.vl and Memory.vl
Start with the two foundational services. All other services depend on at least one of them.
virtus-os/Math.vl — implement all six methods:
class Math {
static Array twoToThe;
function void init() {
let twoToThe = Array.new(16);
let twoToThe[0] = 1;
let twoToThe[1] = 2;
let twoToThe[2] = 4;
let twoToThe[3] = 8;
let twoToThe[4] = 16;
let twoToThe[5] = 32;
let twoToThe[6] = 64;
let twoToThe[7] = 128;
let twoToThe[8] = 256;
let twoToThe[9] = 512;
let twoToThe[10] = 1024;
let twoToThe[11] = 2048;
let twoToThe[12] = 4096;
let twoToThe[13] = 8192;
let twoToThe[14] = 16384;
let twoToThe[15] = 16384; // intentional: 2^15 overflows 16-bit signed
return;
}
function int abs(int x) {
if (x < 0) { return -x; }
return x;
}
function int max(int a, int b) {
if (a > b) { return a; }
return b;
}
function int min(int a, int b) {
if (a < b) { return a; }
return b;
}
function int multiply(int x, int y) {
var int result, shiftedX, j;
let result = 0;
let shiftedX = x;
let j = 0;
while (j < 16) {
if (~((y & twoToThe[j]) = 0)) {
let result = result + shiftedX;
}
let shiftedX = shiftedX + shiftedX;
let j = j + 1;
}
return result;
}
function int divide(int x, int y) {
var int q;
if (Math.abs(y) > Math.abs(x)) { return 0; }
let q = Math.divide(x, y + y);
if ((x - Math.multiply(q + q, y)) < Math.abs(y)) {
return q + q;
} else {
return q + q + 1;
}
}
function int sqrt(int x) {
var int j, y, approx, approxSq;
let y = 0;
let j = 7;
while (~(j < 0)) {
let approx = y + twoToThe[j];
let approxSq = Math.multiply(approx, approx);
if (~(approxSq > x) & (approxSq > 0)) {
let y = approx;
}
let j = j - 1;
}
return y;
}
}
virtus-os/Memory.vl — implement peek, poke, alloc, deAlloc per the lecture:
The init function sets up the free list covering BRAM addresses 0x0800-0x3FFF. The alloc function uses first-fit. The deAlloc function prepends to the free list.
Run the Math and Memory compliance tests before proceeding to Lab 13.2:
python3 tests/os/run_compliance.py --os-dir virtus-os/ --cpu-sim vvp \
--services Math Memory
# Expected:
# Math: PASS (8/8)
# Memory: PASS (5/5)
Do not proceed to Lab 13.2 until both pass.
Lab 13.2: Implement the remaining seven services
virtus-os/String.vl — a VirtusLang string is a heap-allocated array with a length field:
class String {
field Array chars;
field int length;
field int maxLength;
constructor String new(int maxLen) {
let chars = Array.new(maxLen);
let length = 0;
let maxLength = maxLen;
return this;
}
method void dispose() { do Memory.deAlloc(this); return; }
method int length() { return length; }
method char charAt(int i) { return chars[i]; }
method void appendChar(char c) { let chars[length] = c; let length = length + 1; return; }
method int intValue() { /* parse decimal integer from chars */ }
method void setInt(int n) { /* convert integer to decimal string in chars */ }
function char backSpace() { return 129; }
function char doubleQuote() { return 34; }
function char newLine() { return 128; }
}
Implement intValue and setInt using repeated division and character arithmetic ('0' = ASCII 48).
virtus-os/Array.vl — wrapper around Memory.alloc:
class Array {
function Array new(int size) { return Memory.alloc(size); }
method void dispose() { do Memory.deAlloc(this); return; }
}
virtus-os/Sys.vl — system control:
class Sys {
function void halt() {
while (true) { } // spin forever
return;
}
function void error(int errorCode) {
do Output.printString("ERR");
do Output.printInt(errorCode);
do Sys.halt();
return;
}
function void wait(int duration) {
var int i, j;
let i = 0;
while (i < duration) {
let j = 0;
while (j < 100) {
let j = j + 1;
}
let i = i + 1;
}
return;
}
}
virtus-os/Output.vl — character display. The key method is printChar, which draws an 8x8 character bitmap from the font table and advances the cursor. Implement printString, printInt, and println in terms of printChar.
virtus-os/Screen.vl — pixel graphics. Implement drawPixel (direct BRAM write to the framebuffer), clearScreen (clear the framebuffer), setColor (track current draw color), drawLine (Bresenham's algorithm or slope interpolation), drawRectangle (two line calls or direct fill), drawCircle (midpoint algorithm).
virtus-os/Keyboard.vl — keyboard input. The keyboard register is memory-mapped; keyPressed() reads the register. readChar() waits until the key changes; readLine(prompt) builds a String by appending chars until newLine; readInt(prompt) calls readLine then String.intValue().
virtus-os/GamePad.vl — button register at 0x3FF0:
class GamePad {
static int GP_BASE;
function void init() {
let GP_BASE = 16368; // 0x3FF0
return;
}
function boolean buttonPressed(int button) {
return ~((Memory.peek(GP_BASE) & Math.twoToThe(button)) = 0);
}
}
Lab 13.3: Run the full compliance suite
python3 tests/os/run_compliance.py --os-dir virtus-os/ --cpu-sim vvp
Expected output:
Math: PASS (8/8 tests)
String: PASS (6/6 tests)
Array: PASS (3/3 tests)
Memory: PASS (5/5 tests)
Output: PASS (4/4 tests)
Screen: PASS (5/5 tests)
Keyboard: PASS (2/2 tests)
GamePad: PASS (2/2 tests)
Sys: PASS (3/3 tests)
TOTAL: 38/38
If any test fails, do not proceed to Lab 13.4 until it passes. Common failures:
- Math.divide(x, 0): your guard must handle the zero divisor case; the compliance suite tests it
- Memory.alloc on full heap:
Sys.error(6)must be called; verify the error path - Output.printChar for non-ASCII codes (backSpace, newLine): handle codes > 127 as control characters
Lab 13.4 (VCP integration): Verify on silicon
Choose one of the two verification paths:
Gamepad path:
GamePad.buttonPressed(0) returns true when button A is held.
Load a test program that calls GamePad.buttonPressed(0) in a loop and prints 1 when true, 0 when false. Hold button A on the physical gamepad. Observe 1 in the output. Record the verification in your Toolchain Diary.
HDMI pixel path:
Screen.drawPixel(320, 240) illuminates the center pixel on the HDMI display.
Load a test program that calls Screen.setColor(true) then Screen.drawPixel(320, 240). Observe a single illuminated pixel at the center of the HDMI output. Record the verification in your Toolchain Diary.
Take a photo or capture the UART/serial output as evidence. Include it in diary/week-13.md.
Toolchain Diary — Week 13
Record in diary/week-13.md:
- The boot sequence that prevents
Math.initfrom failing due to heap fragmentation. Why can this not fail on a freshly-booted system? - Which compliance test took the longest to debug and what was the root cause.
- Your VCP verification result: which path you chose, what you observed, and what the evidence shows.
- CSA-101 comparison: if you completed CSA-101, list the changes you made when porting your Virtus OS from 6502 conventions to RV32I-Lite. If this is your first Virtus OS, predict what would need to change if you were targeting the 6502 instead.
- Deadlock note (independent practice reading): in one paragraph, explain why Virtus OS does not need deadlock handling.
Grading
| Component | Points |
|---|---|
| Math.vl + Memory.vl: compliance suite pass (13/13 tests) | 6 |
| Remaining 7 services: compliance suite pass (25/25 tests) | 10 |
| Full suite: 38/38 | 2 |
| VCP integration evidence in Toolchain Diary | 2 |