Skip to content
Open
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ Firmware TPM 2.0 running bare-metal on the Zynq UltraScale+ MPSoC R5 RPU
in lock-step mode. PetaLinux on the A53 APU acts as TPM client over
OpenAMP RPMsg via Linux remoteproc. Persistent NV in QSPI flash.

## AMD Spartan UltraScale+ SCU35 fwTPM on a MicroBlaze V soft core

See [Xilinx/fwtpm-scu35-microblazev](Xilinx/fwtpm-scu35-microblazev).

Firmware TPM 2.0 on a MicroBlaze V (RISC-V rv32imc) soft core in the fabric of an
AMD Spartan UltraScale+ SCU35 Evaluation Kit (`xcsu35p`, a pure FPGA), served over
UART with the raw swtpm/mssim framing - the AMD analog of the PolarFire Mi-V
example. The full RSA+ECC fwTPM is ~652 KB and needs a larger device, but a
minimal ECC-only build (`FWTPM_TINY_ECC`) fits the stock 192 KB of block RAM
(no DDR on this part): ~190 KB via wolfTPM's per-command-group gates (the
individual `FWTPM_NO_*` macros, selected explicitly in `user_settings.h`) and an
on-die SYSMONE4 fabric TRNG (added by `fpga/add_sysmon.tcl`) in place of MemUse
Comment on lines +47 to +50
entropy. Hardware-validated on the SCU35 - TPM2_Startup and TPM2_GetRandom pass and
GetRandom differs across cold boots, confirming real System-Monitor entropy.

## Microchip PolarFire SoC fwTPM on a RISC-V hart (AMP)

See [Microchip/fwtpm-polarfire-miv](Microchip/fwtpm-polarfire-miv).
Expand Down
27 changes: 27 additions & 0 deletions Xilinx/fwtpm-scu35-microblazev/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Build artifacts
build/
*.o
*.d
*.elf
*.hex
*.bin
*.lst
*.map

# Local FPGA project / bitstreams (not redistributed)
fpga/*/
*.pdi
*.xsa
*.bit

# Vivado run logs / journals (can land in any dir the tool is invoked from)
vivado*.log
vivado*.jou
.Xil/

# Local-only notes
*.local.md

# Python
__pycache__/
*.pyc
107 changes: 107 additions & 0 deletions Xilinx/fwtpm-scu35-microblazev/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# fwTPM on AMD Spartan UltraScale+ SCU35 (MicroBlaze V soft core)

Firmware TPM 2.0 (from [wolfTPM](https://github.com/wolfSSL/wolfTPM) `fwtpm`) on a **MicroBlaze V** (RISC-V rv32imc) soft core instantiated in the fabric of an AMD **Spartan UltraScale+ SCU35 Evaluation Kit** (`xcsu35p`) - a pure FPGA with no hardened CPU. It is the AMD analog of the PolarFire Mi-V example: the fwTPM server is driven from a host over UART with the same raw swtpm + Microsoft-simulator ("mssim") framing, so the stock wolfTPM swtpm client drives it unmodified.

## Status

The bare-metal platform HAL and a hello-world are **hardware-validated on the stock reference-design bitstream**. The **full** (RSA+ECC) fwTPM builds but is too large for this device. The **minimal ECC-only** fwTPM (`FWTPM_TINY_ECC`) is **hardware-validated on the SCU35**: ~190 KB, fits the stock 192 KB, and runs on a bitstream with the SYSMONE4 fabric TRNG added (`fpga/add_sysmon.tcl`) - TPM2_Startup and TPM2_GetRandom pass and GetRandom differs across cold boots, confirming real System-Monitor entropy. No memory enlargement is needed.

| Item | Status |
|------|--------|
| MicroBlaze-V HAL (AXI UARTLite, AXI Timer, startup, retarget) | Hardware-validated (SCU35) |
| hello-world (banner + timer heartbeat) | Hardware-validated (SCU35): banner + 1 s heartbeat at 225 MHz |
| fwTPM over UART, full (RSA+ECC) | Builds (~652 KB); too large for the xcsu35p |
| fwTPM over UART, minimal ECC-only (`FWTPM_TINY_ECC`) | **Hardware-validated (SCU35)**: ~190 KB fits the 192 KB xcsu35p; boots on the SYSMON-TRNG bitstream, TPM2_Startup + TPM2_GetRandom pass, GetRandom differs across cold boots (real entropy) |
| SYSMONE4 fabric TRNG (System Management Wizard) | Hardware-validated: live temp/VCCINT/VCCAUX ADC codes with jittering LSBs seed the Hash-DRBG |
| Persistent NV in AXI QuadSPI | Planned |
| SRAM PUF | Planned (needs an uninitialized fabric-SRAM primitive in the design) |

**Memory is tight but workable.** The `xcsu35p` has only **48 block-RAM primitives = 192 KB total**, no UltraRAM, and the SCU35 board has no external DDR. The **full** fwTPM (RSA+ECC, ~652 KB) does not fit. But the **minimal ECC-only** build (`FWTPM_TINY_ECC`) fits in ~190 KB, using: ECC-P256 only, table-free AES, no SHA-1, reduced TPM context/NV, an on-die **SYSMONE4 fabric TRNG** in place of wolfCrypt MemUse entropy (saves ~30 KB), and wolfTPM's finer per-command-group gating - the individual `FWTPM_NO_*` command-group macros, selected explicitly in `user_settings.h` (wolfTPM has no "minimal" umbrella; each gate is a deliberate choice) - compiling out the key-migration / ECDH / hash-command / context / symmetric-encrypt / clock command groups. The stock 192 KB BRAM is now sufficient; running it requires only that the bitstream instantiate the SYSMON (AXI System Management) IP for the TRNG - no memory enlargement is needed. See `fpga/README.md`.

## Platform (from the SCU35 Zephyr TRD)

- Core: MicroBlaze V, RISC-V **rv32imc** (M + C, no atomics/FPU), reset vector `0x00000000`, AXI clock **225 MHz**.
- Console: **AXI UARTLite** `axi_uartlite_0` @ `0x40600000`, fixed 115200 8N1.
- Time base: **AXI Timer** `axi_timer_0` @ `0x41C00000` (free-running 32-bit up-counter + software accumulator).
- NV flash (future): **AXI QuadSPI** `axi_quad_spi_0` @ `0x44A00000`.
- Debug: `mdm_riscv` (JTAG over the onboard FT4232H).

## Layout

```
firmware/
common/ shared bare-metal MicroBlaze-V HAL (wolfSSL-authored)
scu35_board.h address book (UARTLite, Timer, QSPI, clock, BRAM)
mbv_uart.c/.h AXI UARTLite console driver
mbv_time.c/.h AXI Timer time base
startup.S RV32 reset/startup (reset vector 0x0)
retarget.c newlib stubs (printf -> UART, _sbrk heap)
hello/ sanity image: banner + timer heartbeat (fits 192 KB)
fwtpm-mbv/ the fwTPM server (full build ~652 KB; FWTPM_TINY_ECC fits 192 KB)
main.c HAL registration + UART swtpm/mssim command loop
fwtpm_clock_mbv.c clock HAL (AXI Timer) + entropy hi-res timer
fwtpm_nv_ram.c volatile NV backend
fwtpm_trng_sysmon.c SYSMONE4 fabric TRNG seed source (-DFWTPM_TINY_HWTRNG)
user_settings.h wolfSSL + wolfTPM config (SP-32; MemUse or SYSMON entropy)
mbv-bram.ld linker (BRAM @ 0x0)
bench/ standalone wolfCrypt benchmark (no TPM), fits 192 KB
main.c UART/timer bring-up + current_time() + benchmark_test()
user_settings.h ECC-P256/SHA-256 config (mirrors the deployed fTPM)
Makefile builds wolfcrypt/benchmark bare-metal
fpga/
README.md how to get/rebuild the bitstream + sizing analysis
add_sysmon.tcl overlay: add the SYSMONE4 AXI TRNG to the TRD block design
build_sysmon.tcl build driver: TRD + overlay -> synth/impl -> PDI
```

## Prerequisites

- The Vitis 2025.x RISC-V bare-metal toolchain: `export PATH=/opt/Xilinx/<ver>/gnu/riscv/lin/bin:$PATH` (the multilib `riscv64-unknown-elf-gcc` targets `riscv32-xilinx-elf`).
- wolfSSL and wolfTPM source trees as siblings of `wolftpm-examples` (default `../../../../../wolfssl`, `../../../../../wolftpm`).
- A programmed SCU35 bitstream and `hw_server`/`xsdb` for the JTAG load (see `fpga/README.md`).

## Build

```bash
export PATH=/opt/Xilinx/2025.2/gnu/riscv/lin/bin:$PATH

cd firmware/hello && make # sanity image (scu35-hello.elf, fits 192 KB)
cd firmware/fwtpm-mbv && make # full fwTPM server (scu35-fwtpm.elf, ~652 KB)

# minimal ECC-only fwTPM that fits the 192 KB xcsu35p (~190 KB); needs the
# SYSMON TRNG bitstream from fpga/add_sysmon.tcl:
cd firmware/fwtpm-mbv && make \
EXTRA_CFLAGS="-DFWTPM_TINY_ECC -DFWTPM_TINY_PCR8 -DFWTPM_TINY_HWTRNG" \
EXTRA_LDFLAGS="-Wl,--defsym=__heap_size=0x3000 -Wl,--defsym=__stack_size=0x2000"

cd firmware/bench && make # wolfCrypt benchmark (scu35-bench.elf, ~191 KB)
```

## Run (JTAG load over the programmed bitstream)

Program the bitstream with the Vivado Hardware Manager (`program_hw_devices` on the `xcsu35p`), then JTAG-load the ELF onto the MicroBlaze V via `xsdb` (`hw_server` running): `targets -set -filter {name == "Hart #0"}`, `dow scu35-*.elf`, `rwr pc <entry>` (the ELF's `_start`), `con`. The console is the design's `serial1` = **`axi_uartlite_1` (0x40700000)**, wired to the SCU35 USB-UART; read it with `uart-monitor`. This is exactly how the hello image was validated. Drive the fwTPM from the host with the shared `swtpm_uart_bridge.py` / `fwtpm_uart_test.py` clients (see the Mi-V example's `host-client/`).

## Performance (measured on hardware)

Direct wolfCrypt benchmark from `firmware/bench`, run on the SCU35 MicroBlaze V soft core (RISC-V rv32imc @ 225 MHz, 32-bit portable-C SP math, `-O2`). The build mirrors the deployed fTPM's algorithm set (ECC-P256, SHA-256, no RSA). Symmetric/hash throughput uses `BENCH_EMBEDDED` 1 KB buffers, so it reflects per-call cost on a soft core, not a bulk-streaming rate.

| Operation | Result |
|-----------|--------|
| ECC P-256 keygen | 8.98 ops/sec (111 ms) |
| ECDHE P-256 agree | 8.99 ops/sec (111 ms) |
| ECDSA P-256 sign | 8.06 ops/sec (124 ms) |
| ECDSA P-256 verify | 4.44 ops/sec (225 ms) |
| SHA-256 | 1.98 MiB/s |
| SHA-1 | 6.19 MiB/s |
| SHA3-256 | 515 KiB/s |
| HMAC-SHA256 | 1.96 MiB/s |
| AES-128-CBC | 50 KiB/s |
| AES-256-GCM | 34 KiB/s |
| RNG (SHA-256 DRBG) | 761 KiB/s |

The benchmark image uses a deterministic bench-only RNG seed (not the SYSMON TRNG), so it runs on the stock bitstream. This measures raw wolfCrypt throughput on the core; end-to-end TPM command latency additionally includes the 115200-baud UART transport.

## See also

- `Microchip/miv-mpf300-splash` - fwTPM on a soft Mi-V RV32 core; the closest analog (same rv32 firmware shape).
- `Xilinx/fwtpm-zc702-a9` - fwTPM on the Zynq-7000 Cortex-A9 (with the wolfCrypt SRAM PUF).
78 changes: 78 additions & 0 deletions Xilinx/fwtpm-scu35-microblazev/firmware/bench/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Makefile - SCU35 MicroBlaze V wolfCrypt benchmark
#
# Standalone wolfCrypt (no wolfTPM) benchmark image for the MicroBlaze V soft
# core. Measures raw core crypto throughput and reports over the AXI UARTLite.
# Requires the wolfSSL source tree as a sibling of wolftpm-examples (override
# with WOLFSSL_DIR=...). Uses the Vitis RISC-V bare-metal toolchain.
#
# export PATH=/opt/Xilinx/2025.2/gnu/riscv/lin/bin:$PATH
#
# Copyright (C) 2006-2026 wolfSSL Inc. GPLv2+ (see source headers).

CROSS_COMPILE ?= riscv64-unknown-elf-
CC = $(CROSS_COMPILE)gcc
SIZE = $(CROSS_COMPILE)size

WOLFSSL_DIR ?= ../../../../../wolfssl
COMMON_DIR = ../common
TARGET = scu35-bench

ifeq ($(wildcard $(WOLFSSL_DIR)/wolfcrypt/src/aes.c),)
$(error wolfSSL sources not found at WOLFSSL_DIR=$(WOLFSSL_DIR) - set WOLFSSL_DIR)
endif

ARCH ?= rv32imc_zicsr_zifencei
OPT ?= -O2
EXTRA_CFLAGS ?=
EXTRA_LDFLAGS ?=

ARCHFLAGS = -march=$(ARCH) -mabi=ilp32 -mcmodel=medany
CFLAGS = $(ARCHFLAGS) $(OPT) -g3 -MMD -MP -ffunction-sections -fdata-sections \
-ffreestanding -DWOLFSSL_USER_SETTINGS \
-I. -I$(COMMON_DIR) -I$(WOLFSSL_DIR) $(EXTRA_CFLAGS)
APP_WARN = -Wall -Wextra

ASFLAGS = $(ARCHFLAGS)
LDSCRIPT = mbv-bram.ld
NOWARN_RWX := $(shell $(CC) -Wl,--no-warn-rwx-segments -Wl,--version >/dev/null 2>&1 && echo -Wl,--no-warn-rwx-segments)
# -u _printf_float pulls in newlib float printf (benchmark prints MB/s, ops/sec).
LDFLAGS = $(ARCHFLAGS) $(OPT) -T $(LDSCRIPT) -nostartfiles --specs=nosys.specs \
-Wl,--gc-sections -Wl,-Map=$(TARGET).map -u _printf_float \
$(NOWARN_RWX) $(EXTRA_LDFLAGS)

WOLF_SRCS = $(wildcard $(WOLFSSL_DIR)/wolfcrypt/src/*.c) \
$(WOLFSSL_DIR)/wolfcrypt/benchmark/benchmark.c
APP_SRCS = main.c \
$(COMMON_DIR)/mbv_uart.c $(COMMON_DIR)/mbv_time.c \
$(COMMON_DIR)/retarget.c
ASM_SRCS = $(COMMON_DIR)/startup.S

BUILD = build
WOLF_OBJS = $(patsubst %.c,$(BUILD)/wolf/%.o,$(notdir $(WOLF_SRCS)))
APP_OBJS = $(patsubst %.c,$(BUILD)/%.o,$(notdir $(APP_SRCS))) \
$(patsubst %.S,$(BUILD)/%.o,$(notdir $(ASM_SRCS)))

VPATH = $(COMMON_DIR):$(WOLFSSL_DIR)/wolfcrypt/src:$(WOLFSSL_DIR)/wolfcrypt/benchmark

all: $(TARGET).elf
$(SIZE) $(TARGET).elf

$(BUILD)/wolf/%.o: %.c | $(BUILD)/wolf
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD)/%.o: %.c | $(BUILD)
$(CC) $(CFLAGS) $(APP_WARN) -c $< -o $@
$(BUILD)/%.o: %.S | $(BUILD)
$(CC) $(ASFLAGS) -c $< -o $@

$(BUILD) $(BUILD)/wolf:
mkdir -p $@

$(TARGET).elf: $(APP_OBJS) $(WOLF_OBJS) $(LDSCRIPT)
$(CC) $(LDFLAGS) $(APP_OBJS) $(WOLF_OBJS) -o $@

clean:
rm -rf $(BUILD) $(TARGET).elf $(TARGET).map

-include $(APP_OBJS:.o=.d) $(WOLF_OBJS:.o=.d)

.PHONY: all clean
83 changes: 83 additions & 0 deletions Xilinx/fwtpm-scu35-microblazev/firmware/bench/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* main.c
*
* wolfCrypt benchmark harness for the MicroBlaze V (RISC-V rv32imc) soft core on
* the AMD Spartan UltraScale+ SCU35. Brings up the console UART and the AXI
* Timer, provides the benchmark time source and a deterministic (bench-only)
* RNG seed, then runs wolfCrypt's benchmark_test() and reports over the UART.
*
* Copyright (C) 2006-2026 wolfSSL Inc.
*
* This file is part of wolfTPM.
*
* wolfTPM is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfTPM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/

#include <stdint.h>
#include <stdio.h>

#include "scu35_board.h"
#include "mbv_uart.h"
#include "mbv_time.h"

#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/wc_port.h>

extern int benchmark_test(void* args);

/* Benchmark time source: fractional seconds from the free-running AXI Timer.
* mbv_ticks() is a 64-bit tick count at the AXI clock (SCU35_SYS_CLK_FREQ). */
double current_time(int reset)
{
(void)reset;
return (double)mbv_ticks() / (double)SCU35_SYS_CLK_FREQ;
}

/* Deterministic bench-only RNG seed (an LCG). This is NOT an entropy source and
* must never be used to generate real keys; it only makes the benchmark's
* key-generation and DRBG paths run reproducibly. */
int bench_seed(unsigned char* out, unsigned int sz)
{
static uint32_t s = 0x2468ACE1u;
unsigned int i;

for (i = 0; i < sz; i++) {
s = (s * 1103515245u) + 12345u;
out[i] = (unsigned char)(s >> 16);
}
return 0;
}

int main(void)
{
mbv_uart_init(SCU35_CONSOLE_UART_BASE);
mbv_timer_init();

printf("\r\n");
printf("========================================================\r\n");
printf(" wolfCrypt benchmark on AMD Spartan UltraScale+ SCU35\r\n");
printf(" MicroBlaze V (RISC-V rv32imc) soft core @ %lu MHz\r\n",
(unsigned long)(SCU35_SYS_CLK_FREQ / 1000000UL));
printf(" 32-bit portable-C SP math, ECC-only (no RSA)\r\n");
printf("========================================================\r\n");

(void)wolfCrypt_Init();
benchmark_test(NULL);
(void)wolfCrypt_Cleanup();

printf("=== benchmark complete ===\r\n");
for (;;) {
}
return 0;
}
Loading