Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ test:stress_k_ast1060_evb --run_under="//target/ast10x0/harness:test_runner \
--timeout 0 "
test:stress_k_ast1060_evb --test_env=AST1060_EVB_PI_HOST

# ── VeeR RISC-V kernel target ──
# Platform setup for building //target/veer/* artifacts.
common:k_veer --platforms=//target/veer:veer
build:k_veer --build_tag_filters=-do_not_build,-kernel_doc_test
# Usage: VCK190_FPGA_HOST=<host> bazel test --config=k_veer //target/veer/unittest_runner:fpga_test
test:k_veer --test_env=VCK190_FPGA_HOST

# Site-local overrides. Kept last so developer machine-specific settings (e.g. a
# local QEMU source override via --override_repository) take precedence. This
# file is not checked in; see third_party/qemu/setup.md.
Expand Down
6 changes: 6 additions & 0 deletions target/veer/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ rust_binary(
rust_library(
name = "console",
srcs = ["console.rs"],
crate_features = select({
":emulator": ["emulator"],
":fpga": ["fpga"],
":silicon": ["silicon"],
"//conditions:default": [],
}),
crate_name = "console_backend",
edition = "2024",
target_compatible_with = TARGET_COMPATIBLE_WITH,
Expand Down
5 changes: 4 additions & 1 deletion target/veer/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ pub struct KernelConfig;
impl KernelConfigInterface for KernelConfig {
#[cfg(feature = "silicon")]
const SYSTEM_CLOCK_HZ: u64 = 100_000_000;
// Matches TIMER_FREQUENCY_HZ in
// third_party/caliptra/caliptra-mcu-sw/platforms/fpga/runtime/src/main.rs,
// the confirmed clock rate for this core on the VCK190 build.
#[cfg(feature = "fpga")]
const SYSTEM_CLOCK_HZ: u64 = 10_000_000; //FIXME
const SYSTEM_CLOCK_HZ: u64 = 20_000_000;
#[cfg(feature = "emulator")]
const SYSTEM_CLOCK_HZ: u64 = 1_000_000;
}
Expand Down
22 changes: 22 additions & 0 deletions target/veer/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,21 @@
use kernel::sync::spinlock::SpinLock;
use pw_status::Result;

/// FPGA wrapper debug FIFO push register (`dbg_fifo_push`), absolute address
/// `0xA401_1014`. Writing a byte with bit 8 set pushes it into the FIFO the
/// ARM PS host drains for console output. Matches
/// `caliptra-mcu-sw/platforms/fpga/rom/src/io.rs::FPGA_UART_OUTPUT`, which
/// uses the identical mechanism on the same board.
#[cfg(feature = "fpga")]
const FPGA_DBG_FIFO_PUSH: *mut u32 = core::ptr::without_provenance_mut(0xA401_1014);

#[cfg(feature = "fpga")]
const FPGA_CHAR_VALID: u32 = 0x100;

struct Uart;

impl Uart {
#[cfg(not(feature = "fpga"))]
fn write_all(&mut self, buf: &[u8]) -> Result<()> {
let tx = core::ptr::with_exposed_provenance_mut::<u8>(0x1000_1041);
for &byte in buf.iter() {
Expand All @@ -17,6 +29,16 @@ impl Uart {
}
Ok(())
}

#[cfg(feature = "fpga")]
fn write_all(&mut self, buf: &[u8]) -> Result<()> {
for &byte in buf.iter() {
unsafe {
FPGA_DBG_FIFO_PUSH.write_volatile(byte as u32 | FPGA_CHAR_VALID);
}
}
Ok(())
}
}

static UART: SpinLock<arch_riscv::Arch, Uart> = SpinLock::new(Uart);
Expand Down
13 changes: 12 additions & 1 deletion target/veer/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn main() -> ! {
}

pub fn exit(code: u32) -> ! {
#[cfg(not(feature = "emulator"))]
#[cfg(not(any(feature = "emulator", feature = "fpga")))]
let _ = code;

#[cfg(feature = "emulator")]
Expand All @@ -34,6 +34,17 @@ pub fn exit(code: u32) -> ! {
let exitcode = core::ptr::with_exposed_provenance_mut::<u32>(0x1000_2000);
exitcode.write_volatile(code);
}

#[cfg(feature = "fpga")]
unsafe {
// SAFETY: dbg_fifo_push (0xA401_1014) is the FPGA wrapper's
// debug/exit register; writing 0xff/0x01 (valid bit 0x100 set)
// signals pass/fail to the host, matching
// caliptra-mcu-sw/platforms/fpga/rom/src/io.rs::exit_fpga.
let dbg_fifo_push = core::ptr::without_provenance_mut::<u32>(0xA401_1014);
let byte: u32 = if code == 0 { 0xff } else { 0x01 };
dbg_fifo_push.write_volatile(byte | 0x100);
}
loop {}
}

Expand Down
9 changes: 8 additions & 1 deletion target/veer/tooling/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-License-Identifier: Apache-2.0

load("@pigweed//pw_build:pw_py_importable_runfile.bzl", "pw_py_importable_runfile")
load("@rules_python//python:defs.bzl", "py_binary")
load("@rules_python//python:defs.bzl", "py_binary", "py_test")
load(":caliptra_runner.bzl", "mcu_rom_wrapper")

mcu_rom_wrapper(
Expand Down Expand Up @@ -65,3 +65,10 @@ py_binary(
"@rules_python//python/runfiles",
],
)

py_test(
name = "caliptra_runner_test",
srcs = ["caliptra_runner_test.py"],
imports = ["."],
deps = [":caliptra_runner"],
)
97 changes: 97 additions & 0 deletions target/veer/tooling/caliptra_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import argparse
import logging
import os
import subprocess
import sys
import tempfile
Expand All @@ -17,6 +18,31 @@
_LOG = logging.getLogger(__name__)
_LOG.setLevel(logging.INFO)

# Environment variable naming the SSH host (user@host or host alias) for the
# VCK190 FPGA board used by the "fpga" interface.
FPGA_HOST = "VCK190_FPGA_HOST"

# Timeout (seconds) for the remote fpga run. The VeeR core's exit()
# implementation writes the PASS/FAIL sentinel and then spins forever (there's
# no way for it to fully halt itself back to the host), so this bounds how
# long we wait on a stuck or unreachable board rather than hanging forever.
_FPGA_RUN_TIMEOUT_SECONDS = 300


def scan_output_for_result(lines):
"""Scan detokenized output lines for a PASS/FAIL sentinel.

Returns 0 on a line containing "PASS", 1 on a line containing "FAIL",
or None if no sentinel has appeared yet.
"""
for line in lines:
if "PASS" in line:
return 0
if "FAIL" in line:
return 1
return None


try:

import caliptra.emulator_cptra_rom # type: ignore
Expand Down Expand Up @@ -116,6 +142,7 @@ def load_and_run(
interface: str,
manifest: str,
vendor_pk_hash: str,
elf: Path | None = None,
) -> list[str]:
"""Prepare arguments to load an image into a board and spawn a console."""
if interface == "emulator":
Expand Down Expand Up @@ -151,6 +178,75 @@ def load_and_run(
if vendor_pk_hash and str(vendor_pk_hash) != "None":
cmd.append(f"--vendor-pk-hash={vendor_pk_hash}")
return cmd
elif interface == "fpga":
host = os.environ.get(FPGA_HOST)
if not host:
_LOG.fatal("%s is not set; cannot reach the VCK190 board", FPGA_HOST)
sys.exit(1)

remote_bin = f"/tmp/{Path(image).name}"
try:
subprocess.run(["scp", str(image), f"{host}:{remote_bin}"], check=True)
except subprocess.CalledProcessError as e:
_LOG.fatal("Failed to copy %s to %s: %s", image, host, e)
sys.exit(1)

# Loads the image into the MCU ROM backdoor SRAM, deasserts
# cptra_ss_rst_b, and streams the debug FIFO back over stdout.
# See hw/fpga/README.md's "JTAG debug" section and
# hw/fpga/kernel-modules/mcu_rom_backdoor.c for the mechanism.
cmd = [
"ssh",
host,
"sudo",
"caliptra-mcu-sw/hw/fpga/launch_openocd.sh",
"load-and-run",
remote_bin,
]
_LOG.info("Invoking fpga runner: %s", cmd)
try:
proc = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False,
timeout=_FPGA_RUN_TIMEOUT_SECONDS,
)
except subprocess.TimeoutExpired as e:
_LOG.fatal(
"fpga runner timed out after %s seconds; stdout so far: %s; "
"stderr so far: %s",
_FPGA_RUN_TIMEOUT_SECONDS,
e.stdout,
e.stderr,
)
sys.exit(1)

if proc.stderr:
_LOG.info("fpga runner stderr: %s", proc.stderr)

if proc.returncode != 0:
_LOG.fatal(
"ssh/remote command failed with exit code %d: %s",
proc.returncode,
proc.stderr,
)
sys.exit(1)

# This target's kernel config pins its log backend to
# log_backend_basic (a plain-text logger; see target/veer/BUILD.bazel's
# platform rule), so the board's console output is never tokenized and
# there is nothing to detokenize here, unlike the emulator's tokenized
# console path (see _detokenizer() above).
print(proc.stdout)
result = scan_output_for_result(proc.stdout.splitlines())
if result is None:
_LOG.fatal(
"Device produced no PASS/FAIL sentinel; fpga runner stderr: %s",
proc.stderr,
)
sys.exit(1)
sys.exit(result)
else:
raise Exception("unknown mechanism", mechanism)

Expand Down Expand Up @@ -200,6 +296,7 @@ def _main(args) -> int:
args.interface,
args.manifest,
args.vendor_pk_hash,
elf=args.elf,
)
# TODO(cfrantz): add support for the tokenized console.
return_code = simple_console(cmd)
Expand Down
25 changes: 25 additions & 0 deletions target/veer/tooling/caliptra_runner_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0
import unittest

from caliptra_runner import scan_output_for_result


class ScanOutputForResultTest(unittest.TestCase):
def test_no_sentinel_yet(self):
self.assertIsNone(scan_output_for_result(["booting...", "init i3c"]))

def test_pass_sentinel(self):
self.assertEqual(0, scan_output_for_result(["running tests", "PASS"]))

def test_fail_sentinel(self):
self.assertEqual(1, scan_output_for_result(["running tests", "FAIL: 1"]))

def test_first_sentinel_wins(self):
self.assertEqual(
0, scan_output_for_result(["PASS", "unrelated trailing noise"])
)


if __name__ == "__main__":
unittest.main()
11 changes: 11 additions & 0 deletions target/veer/unittest_runner/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ caliptra_test(
target = ":unittest_runner",
)

caliptra_test(
name = "fpga_test",
interface = "fpga",
tags = [
"fpga",
"manual",
"requires-fpga",
],
target = ":unittest_runner",
)

filegroup(
name = "system_config",
srcs = ["system.json5"],
Expand Down
23 changes: 22 additions & 1 deletion workflows.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,26 @@
"//target/veer/..."
]
},
{
"name": "caliptra_fpga_build",
"build_config": {
"name": "caliptra_fpga_build_config",
"description": "Build (do not run) the Caliptra MCU fpga target image. Catches fpga cfg/build-wiring regressions without requiring lab hardware; actually running it needs a VCK190 board (see //target/veer/unittest_runner:fpga_test, tagged manual).",
"build_type": "bazel",
"args": [
"--keep_going",
"--config=k_veer",
"--//target/veer:target_type=fpga"
],
"driver_options": {
"@type": "pw.build.proto.BazelDriverOptions",
"no_test": true
}
},
"targets": [
"//target/veer/unittest_runner:fpga_test"
]
},
{
"name": "earlgrey_qemu_tests",
"build_config": {
Expand Down Expand Up @@ -204,7 +224,8 @@
"ci_tests",
"ast10x0_qemu_tests",
"earlgrey_qemu_tests",
"caliptra_emulator_tests"
"caliptra_emulator_tests",
"caliptra_fpga_build"
]
},
{
Expand Down