Product Documentation

Guides and examples for CubeLab hardware, the browser simulation, and the secure console environment.

/

CubeLab

Copy linkUpdated Sep 2025

The CubeLab is a 4×4×4 LED matrix controlled by a microcontroller. It’s typically powered by a single-cell 3.7 V battery (with a regulated 5 V rail for logic, if required) and consists of 64 LEDs arranged in 4 layers of 16 columns.

Note: Pin maps vary by board (Arduino vs. ESP32). Treat the mappings below as a template—verify your exact pins.
  • Layers (example): [A0, A1, A2, A3]
  • Columns (example): [A4, A5, RX(D0), TX(D1), D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13]

Diagram of CubeLab wiring & connections:

CubeLab Diagram

Steps to Operate the CubeLab

  1. Install the Arduino IDE. Download it from the official site and follow the guide for your OS.
  2. Set up the CubeLab.
    • Kit: Watch the assembly video; ensure all connections match your pin map.
    • Finished unit: Proceed to connect to your microcontroller/USB and verify power.
  3. Write code. Open the Arduino IDE and create your sketch. See examples below.
  4. Upload code. Select the correct board & port, then upload.
  5. Power the CubeLab. Use a charged 3.7 V battery (with proper regulation for your board as needed).

Examples (Arduino / C++)

Tip: use arrays for pin maps rather than numeric ranges so you don’t depend on implicit numbering of A/D pins.

Setup: define layers & columns
cpp
// Adjust pins to your board:
// Note TX is D1; if you include TX, don't duplicate D1 again.
const int layers[4]  = {A0, A1, A2, A3};
const int columns[16]= {A4, A5, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};

void setup() {
  for (int i = 0; i < 4;  i++) pinMode(layers[i], OUTPUT);
  for (int i = 0; i < 16; i++) pinMode(columns[i], OUTPUT);
  // Default OFF: columns LOW? depends on your wiring (common anode/cathode).
  // Set a known state here:
  for (int i = 0; i < 16; i++) digitalWrite(columns[i], LOW);
  for (int i = 0; i < 4;  i++) digitalWrite(layers[i], HIGH); // e.g., HIGH = layer off
}
Turn on a single LED
cpp
// Example assumes: column HIGH turns LED on, layer LOW selects layer (inverted).
void loop() {
  int col = columns[0]; // pick a column
  int lay = layers[0];  // pick a layer

  digitalWrite(col, HIGH); // turn on column
  digitalWrite(lay, LOW);  // enable layer
  delay(500);

  // clear
  digitalWrite(col, LOW);
  digitalWrite(lay, HIGH);
  delay(500);
}
Blink a single LED
cpp
void loop() {
  int col = columns[1];
  int lay = layers[0];

  // on
  digitalWrite(col, HIGH);
  digitalWrite(lay, LOW);
  delay(500);

  // off
  digitalWrite(col, LOW);
  digitalWrite(lay, HIGH);
  delay(500);
}
Light an entire layer
cpp
void loop() {
  int lay = layers[0];

  // turn all columns on
  for (int i = 0; i < 16; i++) digitalWrite(columns[i], HIGH);
  digitalWrite(lay, LOW);
  delay(600);

  // clear
  for (int i = 0; i < 16; i++) digitalWrite(columns[i], LOW);
  digitalWrite(lay, HIGH);
  delay(400);
}
Cycle layers (basic multiplex)
cpp
void loop() {
  for (int l = 0; l < 4; l++) {
    // Turn on some pattern (here: all columns)
    for (int i = 0; i < 16; i++) digitalWrite(columns[i], HIGH);

    digitalWrite(layers[l], LOW);   // enable layer
    delay(3);                       // small persistence
    digitalWrite(layers[l], HIGH);  // disable

    // Clear columns if needed for next step
    for (int i = 0; i < 16; i++) digitalWrite(columns[i], LOW);
  }
}
Diagonal sweep (demo)
cpp
void loop() {
  // naive demo; adapt to your physical mapping (x,y,z -> index in columns[] & layers[])
  for (int i = 0; i < 4; i++) {
    int col = columns[i]; // diagonal "column"
    int lay = layers[i];

    digitalWrite(col, HIGH);
    digitalWrite(lay, LOW);
    delay(200);
    digitalWrite(col, LOW);
    digitalWrite(lay, HIGH);
  }
}

Simulation

Copy linkUpdated Sep 2025

LED Cube Simulation

Design and test Arduino-style sketches for a 4×4×4 LED cube directly in the browser. Pick a product (pin map), choose a color with the picker, paste a sketch, and run it live on the 3D cube.

Shortcuts: Ctrl/⌘ + Enter Run · Esc Stop
Pin maps: Use the product selector to switch between boards. The Pin Map card shows layers/columns you can click to ping in 3D.
Stop behavior: Stop cancels delays immediately and halts loop() on the next yield.

Sketch API (C++ like)

  • void setup() — runs once.
  • void loop() — repeats until you press Stop.
  • pinMode(PIN, MODE) — accepted (no-op in sim, for compatibility).
  • digitalWrite(PIN, HIGH|LOW) — toggles a layer pin (e.g. A0..A3) or a column pin (depends on product pin map).
  • delay(ms) — pauses; cancellable by Stop.
  • Helpers: layerOn/Off(y), columnOn/Off(x,z), ledOn/Off(x,y,z), setColor("#hex") (optional; the color picker also sets LED color).

Coordinates are 0…3. Columns are addressed by (x,z), layers by y. The exact pin names for columns vary by product; see the Pin Map on the simulation page. Multiple statements on one line are supported (;), but one per line is clearer.

Examples (paste into the editor)
Minimal starter
cpp
void setup() {
  // quick layer pulse
  digitalWrite(A0, HIGH);
  delay(200);
  digitalWrite(A0, LOW);
}

void loop() {
  // blink one voxel
  ledOn(1, 1, 1);
  delay(200);
  ledOff(1, 1, 1);
  delay(200);
}
Orbit Glow (perimeter spin + layer scan)
cpp
// Keep all layers on for a tall beam
void setup() {
  layerOn(0);
  layerOn(1);
  layerOn(2);
  layerOn(3);
}

void loop() {
  // Perimeter spin (clockwise)
  columnOn(0, 0); delay(90); columnOff(0, 0);
  columnOn(1, 0); delay(90); columnOff(1, 0);
  columnOn(2, 0); delay(90); columnOff(2, 0);
  columnOn(3, 0); delay(90); columnOff(3, 0);
  columnOn(3, 1); delay(90); columnOff(3, 1);
  columnOn(3, 2); delay(90); columnOff(3, 2);
  columnOn(3, 3); delay(90); columnOff(3, 3);
  columnOn(2, 3); delay(90); columnOff(2, 3);
  columnOn(1, 3); delay(90); columnOff(1, 3);
  columnOn(0, 3); delay(90); columnOff(0, 3);
  columnOn(0, 2); delay(90); columnOff(0, 2);
  columnOn(0, 1); delay(90); columnOff(0, 1);

  // Layer scan with all columns on
  columnOn(0,0); columnOn(1,0); columnOn(2,0); columnOn(3,0);
  columnOn(0,1); columnOn(1,1); columnOn(2,1); columnOn(3,1);
  columnOn(0,2); columnOn(1,2); columnOn(2,2); columnOn(3,2);
  columnOn(0,3); columnOn(1,3); columnOn(2,3); columnOn(3,3);

  layerOn(0); delay(120); layerOff(0);
  layerOn(1); delay(120); layerOff(1);
  layerOn(2); delay(120); layerOff(2);
  layerOn(3); delay(150); layerOff(3);

  columnOff(0,0); columnOff(1,0); columnOff(2,0); columnOff(3,0);
  columnOff(0,1); columnOff(1,1); columnOff(2,1); columnOff(3,1);
  columnOff(0,2); columnOff(1,2); columnOff(2,2); columnOff(3,2);
  columnOff(0,3); columnOff(1,3); columnOff(2,3); columnOff(3,3);

  delay(120);
}
Comet Rain (falling droplets)
cpp
void setup() {
  layerOff(0); layerOff(1); layerOff(2); layerOff(3);
}

void loop() {
  // Drop at (x=1,z=2)
  columnOn(1,2); layerOn(3); delay(90); layerOff(3); columnOff(1,2);
  columnOn(1,2); layerOn(2); delay(90); layerOff(2); columnOff(1,2);
  columnOn(1,2); layerOn(1); delay(90); layerOff(1); columnOff(1,2);
  columnOn(1,2); layerOn(0); delay(120); layerOff(0); columnOff(1,2);

  // Drop at (x=2,z=1)
  columnOn(2,1); layerOn(3); delay(90); layerOff(3); columnOff(2,1);
  columnOn(2,1); layerOn(2); delay(90); layerOff(2); columnOff(2,1);
  columnOn(2,1); layerOn(1); delay(90); layerOff(1); columnOff(2,1);
  columnOn(2,1); layerOn(0); delay(120); layerOff(0); columnOff(2,1);

  // Drop at (x=0,z=3)
  columnOn(0,3); layerOn(3); delay(90); layerOff(3); columnOff(0,3);
  columnOn(0,3); layerOn(2); delay(90); layerOff(2); columnOff(0,3);
  columnOn(0,3); layerOn(1); delay(90); layerOff(1); columnOff(0,3);
  columnOn(0,3); layerOn(0); delay(120); layerOff(0); columnOff(0,3);

  delay(120);
}

Secure Console, Kria KV260, and Runtime Monitor

Copy linkUpdated Sep 2025

Secure Console Environment

The Secure Console lets you open a temporary, authenticated web terminal to shared lab devices (Kria, Jetson, Coral) without exposing them to the public internet. Access is granted per user/per device—if you don't see a device, request access from your instructor or the project owner.

Safety: Commands run directly on real hardware. Avoid destructive operations (rm -rf, random reboot, flashing, repartitioning, etc.) unless you have explicit approval. Sessions may be logged.

Access a shared board

  1. Request access
    Ask your instructor / project owner to grant you access to a device (e.g. Kria KV260). Once approved, you'll see it on the Devices page and you'll be able to open its console.
  2. Open the Devices page
    In the Cube Laboratory site, go to /devices and select the Kria KV260 device card.
  3. Launch the Secure Console
    Click "Open web terminal". A new page opens with a full-screen terminal connected to the device.
  4. Log in
    Use the credentials provided by the owner. Credentials can change—always use the latest instructions from your instructor.
  5. Verify you are on the Kria
    Quick environment checks
    bash
    uname -m
    cat /etc/os-release
    python3 --version
    gcc --version
    make --version

    The Kria KV260 image used in this course is typically aarch64 and runs a PetaLinux build.

Runtime Monitor (RTM) with R2U2

The Runtime Monitor (RTM) checks a live data stream against safety/quality rules. On the Kria board we use R2U2 as the monitor engine. You feed it:

  • a specification compiled into a binary (.bin) that encodes the rules you want enforced, and
  • a trace (CSV) where each row is a new frame/step of sensor or model outputs (for example: contrast/blur/brightness for an eye image).
What RTM does: For each timestamp, R2U2 outputs verdicts for each rule (formula). A verdict indicates whether the trace up to that point satisfies the rule. This is useful for catching bad inputs (too dark / too bright / too blurry) early and reacting immediately in a pipeline.

Kria RTM quickstart (example: eye image metrics)

This example treats each CSV row as: contrast,blur,brightness in the range 0–255.

1) Create a trace (10 lines: contrast,blur,brightness)
bash
cat > ~/eye_trace.csv <<'CSV'
120,10,140
118,12,142
115,18,145
110,25,150
100,35,155
90,45,160
80,55,165
95,40,170
110,20,175
130,12,180
CSV
2) Create a map (column indices)
bash
cat > ~/eye.map <<'MAP'
contrast:0
blur:1
brightness:2
MAP
3) Create a spec (rules)
bash
cat > ~/eye.c2po <<'C2PO'
INPUT
    contrast, blur, brightness: int;

DEFINE
    too_dark := (brightness < 60);
    too_bright := (brightness > 200);
    low_contrast := (contrast < 40);
    too_blurry := (blur > 40);

FTSPEC
    too_dark -> F[0,5] (!too_dark);
    too_bright -> F[0,5] (!too_bright);
    low_contrast -> F[0,8] (!low_contrast);
    too_blurry -> F[0,6] (!too_blurry);
C2PO
4) Compile spec → binary
bash
cd ~/r2u2
python3 compiler/c2po.py -bz --output ~/eye_spec.bin --map ~/eye.map ~/eye.c2po
ls -lh ~/eye_spec.bin
5) Run RTM (R2U2) on the trace
bash
# Run by redirecting stdin:
~/r2u2_install/bin/r2u2 ~/eye_spec.bin < ~/eye_trace.csv

# Or pass the trace file as arg 2:
# ~/r2u2_install/bin/r2u2 ~/eye_spec.bin ~/eye_trace.csv

Output format is <formula_id>:<timestamp>,<T|F>. The formula IDs are assigned by the order of rules in FTSPEC.

Interpreting RTM output

  • formula_id: which rule is being reported (0 = first rule in FTSPEC, 1 = second, etc.).
  • timestamp: the trace row index (0-based). Row 0 is the first frame, row 1 the next frame, and so on.
  • T / F: whether the rule is satisfied at that time.
Show only failures
bash
~/r2u2_install/bin/r2u2 ~/eye_spec.bin < ~/eye_trace.csv | grep ',F$' || true
Why failures may appear later: Some rules look ahead within a bounded window (e.g. F[0,5]). The monitor may need to read enough future samples to decide whether a recovery happened inside the window. The timestamp in the output still indicates which frame the rule applies to.

Where R2U2 is installed on the Kria

On the Kria board image used for this project, the R2U2 binary is placed here:

Installed path
bash
ls -lah ~/r2u2_install/bin/
~/r2u2_install/bin/r2u2 --help || true