feat(tiny): implement R2-P9 DVP capture and recovery

Connect DVP to Tiny DMA2/request 11, IRQ15 and the camera pad/reset lifecycle.
Harden shared capture CDC, frame statistics, per-row size validation and HAL
cleanup while preserving the Mini DMA3 binding and existing register ABI.

Add deterministic capture/recovery firmware, source-bound evidence tooling,
focused RTL/host tests and compact netlist acceptance. Preserve SAFE24,
128 KiB main SRAM, private Crypto storage and the locked dependencies.

Retained validation: focused RTL, host/policy checks, Mini firmware, and
ICS55/IHP130 RECOVERY, QVGA, synthesis and boot/DVP netlist checks pass.
Full-system VGA remains historical; long full Icarus and broad regressions
remain deferred. Global publication/format gaps and negative observational
STA are retained, not reported as passing qualification.

Specification: docs/ip/tiny-soc.md, TINY-R2-P9.
Evidence: docs/ip/tiny-soc-verification.md.
This commit is contained in:
maksyuki committed 2026-10-17 15:32:59 +08:00
1 parent fd03776a36
commit 8f5630fb41
61 files changed
+6038 -410

No files matched your search

+30
View File
@@ -87,6 +87,9 @@ SW_LTO ?= NO
WS2812_P3_ACCEPTANCE ?= NO WS2812_P3_ACCEPTANCE ?= NO
TINY_P7_ACCEPTANCE ?= NO TINY_P7_ACCEPTANCE ?= NO
TINY_P8_ACCEPTANCE ?= NO TINY_P8_ACCEPTANCE ?= NO
TINY_P9_ACCEPTANCE ?= NO
TINY_P9_QUICK ?= NO
TINY_P9_WORKLOAD ?= QVGA_RGB565
COREMARK_MODE ?= quick COREMARK_MODE ?= quick
NPU_P5_ACCEPTANCE ?= NO NPU_P5_ACCEPTANCE ?= NO
NPU_P6_ACCEPTANCE ?= NO NPU_P6_ACCEPTANCE ?= NO
@@ -135,6 +138,15 @@ endif
ifeq ($(TINY_P8_ACCEPTANCE),YES) ifeq ($(TINY_P8_ACCEPTANCE),YES)
CONFIG_KEY_VARS += TINY_P8_ACCEPTANCE CONFIG_KEY_VARS += TINY_P8_ACCEPTANCE
endif endif
ifeq ($(TINY_P9_ACCEPTANCE),YES)
CONFIG_KEY_VARS += TINY_P9_ACCEPTANCE
ifneq ($(TINY_P9_WORKLOAD),QVGA_RGB565)
CONFIG_KEY_VARS += TINY_P9_WORKLOAD
endif
endif
ifeq ($(TINY_P9_QUICK),YES)
CONFIG_KEY_VARS += TINY_P9_QUICK
endif
VARIANT_ID := $(strip $(shell $(VCS_SHELL_PYTHON) $(ROOT_PATH)/scripts/config_key.py \ VARIANT_ID := $(strip $(shell $(VCS_SHELL_PYTHON) $(ROOT_PATH)/scripts/config_key.py \
--lock $(LOCK_FILE) --profile $(PROFILE_NAME) --timestamp $(BUILD_TIMESTAMP) \ --lock $(LOCK_FILE) --profile $(PROFILE_NAME) --timestamp $(BUILD_TIMESTAMP) \
$(foreach var,$(CONFIG_KEY_VARS),--value $(var)=$($(var))) | tail -n 1)) $(foreach var,$(CONFIG_KEY_VARS),--value $(var)=$($(var))) | tail -n 1))
@@ -297,11 +309,29 @@ $(call validate_value,SW_LTO,$(VALID_BOOL))
$(call validate_value,WS2812_P3_ACCEPTANCE,$(VALID_BOOL)) $(call validate_value,WS2812_P3_ACCEPTANCE,$(VALID_BOOL))
$(call validate_value,TINY_P7_ACCEPTANCE,$(VALID_BOOL)) $(call validate_value,TINY_P7_ACCEPTANCE,$(VALID_BOOL))
$(call validate_value,TINY_P8_ACCEPTANCE,$(VALID_BOOL)) $(call validate_value,TINY_P8_ACCEPTANCE,$(VALID_BOOL))
$(call validate_value,TINY_P9_ACCEPTANCE,$(VALID_BOOL))
$(call validate_value,TINY_P9_QUICK,$(VALID_BOOL))
$(call validate_value,TINY_P9_WORKLOAD,QVGA_RGB565 VGA_YUV422 RECOVERY)
ifneq ($(TINY_P9_WORKLOAD),QVGA_RGB565)
ifneq ($(TINY_P9_ACCEPTANCE)/$(TINY_P9_QUICK),YES/NO)
$(error Selected P9 workload requires TINY_P9_ACCEPTANCE=YES TINY_P9_QUICK=NO)
endif
endif
ifeq ($(TINY_P8_ACCEPTANCE),YES) ifeq ($(TINY_P8_ACCEPTANCE),YES)
ifneq ($(SOC)/$(APP)/$(HAVE_CSR)/$(TINY_P7_ACCEPTANCE),TINY/ci_smoke/YES/NO) ifneq ($(SOC)/$(APP)/$(HAVE_CSR)/$(TINY_P7_ACCEPTANCE),TINY/ci_smoke/YES/NO)
$(error TINY_P8_ACCEPTANCE requires SOC=TINY APP=ci_smoke HAVE_CSR=YES TINY_P7_ACCEPTANCE=NO) $(error TINY_P8_ACCEPTANCE requires SOC=TINY APP=ci_smoke HAVE_CSR=YES TINY_P7_ACCEPTANCE=NO)
endif endif
endif endif
ifeq ($(TINY_P9_ACCEPTANCE),YES)
ifneq ($(SOC)/$(APP)/$(HAVE_CSR)/$(TINY_P7_ACCEPTANCE)/$(TINY_P8_ACCEPTANCE),TINY/ci_smoke/YES/NO/NO)
$(error TINY_P9_ACCEPTANCE requires SOC=TINY APP=ci_smoke HAVE_CSR=YES and no P7/P8 acceptance image)
endif
endif
ifeq ($(TINY_P9_QUICK),YES)
ifneq ($(TINY_P9_ACCEPTANCE),YES)
$(error TINY_P9_QUICK requires TINY_P9_ACCEPTANCE=YES)
endif
endif
ifeq ($(TINY_P7_ACCEPTANCE),YES) ifeq ($(TINY_P7_ACCEPTANCE),YES)
ifneq ($(SOC)/$(APP)/$(HAVE_CSR),TINY/ci_smoke/YES) ifneq ($(SOC)/$(APP)/$(HAVE_CSR),TINY/ci_smoke/YES)
$(error TINY_P7_ACCEPTANCE requires SOC=TINY APP=ci_smoke HAVE_CSR=YES) $(error TINY_P7_ACCEPTANCE requires SOC=TINY APP=ci_smoke HAVE_CSR=YES)
+33
View File
@@ -0,0 +1,33 @@
#ifndef RS_P9_FRAME_H
#define RS_P9_FRAME_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <retrosoc/core/status.h>
/* Application-only geometry; the sensor supplies two bytes per pixel. */
static inline rs_status_t rs_p9_frame_words(uint32_t width, uint32_t height, uint32_t *words) {
if (words == NULL || width == 0U || width > UINT16_MAX || height == 0U || height > UINT16_MAX) {
return RS_EINVAL;
}
const uint32_t row_words = (width + 1U) / 2U;
if (height > (UINT32_MAX / sizeof(uint32_t)) / row_words) {
return RS_ENOSPC;
}
*words = row_words * height;
return RS_OK;
}
/* Independent firmware oracle for byte (column_byte + 3 * row) modulo 256. */
static inline uint32_t rs_p9_frame_word(uint32_t row, uint32_t column_word, bool partial) {
const uint32_t first = (column_word * 4U + row * 3U) & UINT32_C(0xFF);
uint32_t expected = (first << 8) | ((first + 1U) & UINT32_C(0xFF));
if (!partial) {
expected |=
(((first + 2U) & UINT32_C(0xFF)) << 24) | (((first + 3U) & UINT32_C(0xFF)) << 16);
}
return expected;
}
#endif
+33
View File
@@ -0,0 +1,33 @@
#ifndef RETROSOC_P9_STARTUP_H
#define RETROSOC_P9_STARTUP_H
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <retrosoc/core/status.h>
#include <retrosoc/hal/gpio.h>
#include <retrosoc/hal/rcu.h>
typedef rs_status_t (*rs_p9_uart_init_fn)(uint32_t source_clock_hz, uint32_t baud_rate);
typedef rs_status_t (*rs_p9_gpio_configure_fn)(uint32_t pin, const rs_gpio_config_t *config);
static inline bool rs_p9_console_ready(const rs_tiny_rcu_snapshot_t *snapshot,
rs_p9_uart_init_fn uart_init) {
if ((snapshot == NULL) || (uart_init == NULL) || (snapshot->pclk_hz == 0U)) {
return false;
}
return uart_init(snapshot->pclk_hz, UINT32_C(921600)) == RS_OK;
}
static inline bool rs_p9_psram_pin_ready(rs_p9_gpio_configure_fn gpio_configure) {
const rs_gpio_config_t pin = {
.mode = RS_GPIO_MODE_ALT0,
.pull = RS_GPIO_PULL_NONE,
.trigger = RS_GPIO_TRIGGER_NONE,
.output_high = true,
};
return (gpio_configure != NULL) && (gpio_configure(29U, &pin) == RS_OK);
}
#endif
+12
View File
@@ -8,3 +8,15 @@ endif
ifeq ($(TINY_P8_ACCEPTANCE),YES) ifeq ($(TINY_P8_ACCEPTANCE),YES)
APP_SRCS := $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p8.c APP_SRCS := $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p8.c
endif endif
ifeq ($(TINY_P9_ACCEPTANCE),YES)
APP_SRCS := $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p9.c
ifeq ($(TINY_P9_WORKLOAD),RECOVERY)
APP_SRCS := $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p9_recovery.c
endif
ifeq ($(TINY_P9_WORKLOAD),VGA_YUV422)
APP_CFLAGS += -DRS_TINY_P9_VGA_YUV422
endif
ifeq ($(TINY_P9_QUICK),YES)
APP_CFLAGS += -DRS_TINY_P9_QUICK
endif
endif
+256
View File
@@ -0,0 +1,256 @@
#include <retrosoc/arch/riscv/system_base.h>
#include <retrosoc/hal/dma.h>
#include <retrosoc/hal/dvp.h>
#include <retrosoc/hal/gpio.h>
#include <retrosoc/hal/rcu.h>
#include <retrosoc/hal/sysctrl.h>
#include <retrosoc/hal/uart.h>
#include <retrosoc/hal/xpi_psram.h>
#include <retrosoc/lib/printf.h>
#include "p9_startup.h"
#include "p9_frame.h"
#ifdef RS_TINY_P9_QUICK
#define RS_P9_QVGA_WIDTH UINT32_C(16)
#define RS_P9_QVGA_HEIGHT UINT32_C(8)
#elif defined(RS_TINY_P9_VGA_YUV422)
#define RS_P9_QVGA_WIDTH UINT32_C(640)
#define RS_P9_QVGA_HEIGHT UINT32_C(480)
#else
#define RS_P9_QVGA_WIDTH UINT32_C(320)
#define RS_P9_QVGA_HEIGHT UINT32_C(240)
#endif
#ifdef RS_TINY_P9_VGA_YUV422
#define RS_P9_FORMAT RS_DVP_FORMAT_YUV422
#else
#define RS_P9_FORMAT RS_DVP_FORMAT_RGB565
#endif
#define RS_P9_QVGA_BYTES (RS_P9_QVGA_WIDTH * RS_P9_QVGA_HEIGHT * 2U)
#define RS_P9_QVGA_WORDS (RS_P9_QVGA_BYTES / sizeof(uint32_t))
#ifndef RS_TINY_P9_QUICK
#define RS_P9_FRAME_ADDR UINT32_C(0x54010000)
#endif
static _Alignas(64) uint32_t rs_p9_pio_words[32];
static uint32_t rs_p9_dma_bytes;
static uint32_t rs_p9_word_count;
static uint32_t rs_p9_pixel_count;
static uint32_t rs_p9_xpi_error;
static bool rs_p9_readback;
#ifdef RS_TINY_P9_QUICK
static _Alignas(64) uint32_t rs_p9_frame[RS_P9_QVGA_WORDS + 2U];
#endif
static void rs_p9_boot_fail(uint8_t code) {
rs_sysctrl_write_test_status(false, code);
for (;;) {
}
}
static void rs_p9_finish(bool pass, uint8_t code) {
printf("Tiny P9 %s code=%u\n", pass ? "passed" : "failed", code);
rs_sysctrl_write_test_status(pass, code);
for (;;) {
}
}
static bool rs_p9_camera_pins(void) {
const rs_gpio_config_t camera = {
.mode = RS_GPIO_MODE_ALT1,
.pull = RS_GPIO_PULL_NONE,
.trigger = RS_GPIO_TRIGGER_NONE,
.output_high = false,
};
printf("P9_STAGE CAMERA_PINS_START\n");
for (uint32_t pin = 12U; pin <= 23U; pin++) {
if (rs_gpio_configure(pin, &camera) != RS_OK) {
printf("P9_STAGE CAMERA_PIN_FAIL pin=%u\n", pin);
return false;
}
if ((pin & 3U) == 3U) {
printf("P9_STAGE CAMERA_PIN pin=%u\n", pin);
}
}
printf("P9_STAGE CAMERA_PINS_DONE\n");
return true;
}
static bool rs_p9_dma_frame(uintptr_t destination) {
const rs_dvp_config_t config = {
.format = RS_P9_FORMAT,
.frame_width = RS_P9_QVGA_WIDTH,
.frame_height = RS_P9_QVGA_HEIGHT,
.crop_width = RS_P9_QVGA_WIDTH,
.crop_height = RS_P9_QVGA_HEIGHT,
.snapshot = true,
.stream_enable = true,
};
volatile uint32_t *const frame = (volatile uint32_t *)destination;
volatile uint32_t *prefix_guard;
rs_dvp_status_t status;
rs_dma_status_t dma_status;
uint32_t expected_words = 0U;
if (rs_p9_frame_words(RS_P9_QVGA_WIDTH, RS_P9_QVGA_HEIGHT, &expected_words) != RS_OK ||
expected_words != RS_P9_QVGA_WORDS) {
return false;
}
if (destination < sizeof(uint32_t)) {
return false;
}
prefix_guard = (volatile uint32_t *)(destination - sizeof(uint32_t));
if (rs_dvp_configure(&config) != RS_OK) {
printf("P9_STAGE DVP_CONFIG_FAIL\n");
return false;
}
printf("P9_STAGE DMA_CAPTURE_START\n");
*prefix_guard = UINT32_C(0x13579bdf);
frame[RS_P9_QVGA_WORDS] = UINT32_C(0x2468ace0);
if (rs_dvp_capture_dma(destination, RS_P9_QVGA_WORDS, RS_TIMEOUT_DEFAULT) != RS_OK ||
rs_dvp_get_status(&status) != RS_OK || status.error_status != 0U ||
status.pixel_count != RS_P9_QVGA_WIDTH * RS_P9_QVGA_HEIGHT ||
*prefix_guard != UINT32_C(0x13579bdf) || frame[RS_P9_QVGA_WORDS] != UINT32_C(0x2468ace0)) {
printf("P9_STAGE DMA_CAPTURE_FAIL\n");
return false;
}
if (rs_dma_get_status(RS_DMA_CHANNEL_DVP, &dma_status) != RS_OK) {
return false;
}
rs_p9_dma_bytes = dma_status.bytes_done;
rs_p9_word_count = status.word_count;
rs_p9_pixel_count = status.pixel_count;
for (uint32_t index = 0U; index < RS_P9_QVGA_WORDS; index++) {
const uint32_t row = index / (RS_P9_QVGA_WIDTH / 2U);
const uint32_t column = index % (RS_P9_QVGA_WIDTH / 2U);
const uint32_t expected = rs_p9_frame_word(row, column, false);
if (frame[index] != expected) {
printf("P9_STAGE READBACK_FAIL word=%u\n", index);
return false;
}
}
#ifndef RS_TINY_P9_QUICK
rs_xpi_error_t xpi_error;
if (rs_xpi_error_get(&xpi_error) != RS_OK) {
return false;
}
rs_p9_xpi_error = xpi_error.valid ? (uint32_t)xpi_error.code + 1U : 0U;
if (rs_p9_xpi_error != 0U) {
return false;
}
#endif
rs_p9_readback = true;
printf("P9_STAGE DMA_CAPTURE_DONE\n");
return true;
}
static bool rs_p9_pio_odd(void) {
const rs_dvp_config_t config = {
.format = RS_DVP_FORMAT_RGB565,
.frame_width = RS_P9_QVGA_WIDTH,
.frame_height = RS_P9_QVGA_HEIGHT,
.crop_width = 7U,
.crop_height = 3U,
.crop_enable = true,
.snapshot = true,
.stream_enable = false,
};
for (uint32_t index = 0U; index < 32U; index++) {
rs_p9_pio_words[index] = UINT32_C(0xdeadbeef);
}
if (rs_dvp_configure(&config) != RS_OK) {
printf("P9_STAGE PIO_CONFIG_FAIL\n");
return false;
}
if (rs_dvp_capture_pio((uintptr_t)&rs_p9_pio_words[1], 30U, 12U, RS_TIMEOUT_DEFAULT) != RS_OK) {
printf("P9_STAGE PIO_CAPTURE_FAIL\n");
return false;
}
if (rs_p9_pio_words[0] != UINT32_C(0xDEADBEEF) || rs_p9_pio_words[13] != UINT32_C(0xDEADBEEF)) {
return false;
}
for (uint32_t index = 0U; index < 12U; index++) {
const uint32_t first = ((index % 4U) * 4U + (index / 4U) * 3U) & UINT32_C(0xFF);
uint32_t expected = (first << 8) | ((first + 1U) & UINT32_C(0xFF));
if ((index % 4U) != 3U) {
expected |=
(((first + 2U) & UINT32_C(0xFF)) << 24) | (((first + 3U) & UINT32_C(0xFF)) << 16);
}
if (rs_p9_pio_words[index + 1U] != expected)
return false;
}
printf("P9_PIO width=7 height=3 valid_bytes=42 storage_bytes=48 readback=1 guards=1\n");
printf("P9_STAGE PIO_CAPTURE_DONE\n");
return true;
}
int main(void) {
uint32_t version;
uint32_t capability;
rs_tiny_rcu_snapshot_t snapshot;
#ifndef RS_TINY_P9_QUICK
rs_xpi_psram_t psram;
#endif
uintptr_t frame_destination;
bool pass = true;
uint8_t code = 0U;
rs_tiny_rcu_snapshot_t startup_snapshot;
__disable_irq();
if ((rs_tiny_rcu_get_snapshot(&startup_snapshot) != RS_OK) ||
!rs_p9_console_ready(&startup_snapshot, rs_uart_init)) {
rs_p9_boot_fail(4U);
} else if (!rs_p9_psram_pin_ready(rs_gpio_configure)) {
rs_p9_boot_fail(2U);
} else if (!rs_p9_camera_pins()) {
printf("P9_STAGE CAMERA_PINS_FAIL\n");
code = 1U;
} else if (rs_tiny_rcu_set_clkout(1U, 1U) != RS_OK ||
rs_tiny_rcu_get_snapshot(&snapshot) != RS_OK ||
(snapshot.target_capability & (UINT32_C(1) << 14U)) == 0U ||
rs_dvp_probe(&version, &capability) != RS_OK || version != UINT32_C(0x00020000)) {
printf("P9_STAGE RCU_OR_DVP_PROBE_FAIL\n");
code = 1U;
#ifdef RS_TINY_P9_QUICK
} else {
printf("P9_STAGE QUICK_BUFFER_READY\n");
frame_destination = (uintptr_t)&rs_p9_frame[1];
pass = rs_p9_dma_frame(frame_destination);
if (pass) {
pass = rs_p9_pio_odd();
}
if (!pass) {
code = 3U;
}
}
#else
} else {
printf("P9_STAGE PSRAM_INIT_START\n");
if (rs_xpi_psram_initialize(snapshot.mem_hz, RS_TIMEOUT_DEFAULT, &psram) != RS_OK ||
rs_xpi_psram_validate_dma(&psram, RS_P9_FRAME_ADDR, RS_P9_QVGA_BYTES, 4U) != RS_OK) {
printf("P9_STAGE PSRAM_INIT_FAIL\n");
code = 2U;
} else {
printf("P9_STAGE PSRAM_INIT_DONE\n");
frame_destination = RS_P9_FRAME_ADDR;
pass = rs_p9_dma_frame(frame_destination);
if (pass) {
pass = rs_p9_pio_odd();
}
if (!pass) {
code = 3U;
}
}
}
#endif
pass = pass && code == 0U;
printf("P9_FRAME format=%u width=%u height=%u bytes=%u dma_bytes=%u dma_channel=2 request=11 "
"guards=%u readback=%u\n",
(unsigned int)RS_P9_FORMAT, RS_P9_QVGA_WIDTH, RS_P9_QVGA_HEIGHT, RS_P9_QVGA_BYTES,
rs_p9_dma_bytes, pass ? 1U : 0U, rs_p9_readback ? 1U : 0U);
printf("P9_COUNTS words=%u pixels=%u xpi_error=%u\n", rs_p9_word_count, rs_p9_pixel_count,
rs_p9_xpi_error);
printf("P9_RESULT code=%u frames=%u bytes=%u\n", code, pass ? 1U : 0U,
pass ? RS_P9_QVGA_BYTES : 0U);
(void)rs_tiny_rcu_set_clkout(0U, 0U);
rs_p9_finish(pass && code == 0U, code);
}
+271
View File
@@ -0,0 +1,271 @@
#include <retrosoc/arch/riscv/system_base.h>
#include <retrosoc/core/irq.h>
#include <retrosoc/core/soc.h>
#include <retrosoc/generated/irq_metadata.h>
#include <retrosoc/hal/clint.h>
#include <retrosoc/hal/dma.h>
#include <retrosoc/hal/dvp.h>
#include <retrosoc/hal/gpio.h>
#include <retrosoc/hal/i2s.h>
#include <retrosoc/hal/rcu.h>
#include <retrosoc/hal/rcu_regs.h>
#include <retrosoc/hal/sysctrl.h>
#include <retrosoc/hal/uart.h>
#include <retrosoc/hal/xpi_psram.h>
#include <retrosoc/lib/printf.h>
#include "p9_frame.h"
#include "p9_startup.h"
#define RS_P9_RECOVERY_TIMEOUT ((rs_timeout_t)20000U)
#define RS_P9_RECOVERY_DVP_MASK UINT32_C(0x4000)
#define RS_P9_RECOVERY_ADDRESS UINT32_C(0x54010000)
static _Alignas(64) uint32_t rs_p9_recovery_buffer[66];
static volatile uint32_t rs_p9_recovery_irqs;
static uint32_t rs_p9_recovery_frames;
static uint32_t rs_p9_recovery_cases;
static void rs_p9_recovery_irq(uintptr_t cause, uintptr_t stack) {
(void)cause;
(void)stack;
/* Preserve FRAME_DONE for the polling helper while quiescing the level. */
(void)rs_dvp_interrupt_enable(0U);
rs_p9_recovery_irqs++;
}
static bool rs_p9_recovery_delay(uint32_t microseconds) {
uint64_t start;
uint64_t now;
rs_timeout_t budget = RS_P9_RECOVERY_TIMEOUT;
if (rs_clint_get_time(&start) != RS_OK) {
return false;
}
do {
if (budget-- == 0U || rs_clint_get_time(&now) != RS_OK) {
return false;
}
} while (now - start < microseconds);
return true;
}
static bool rs_p9_recovery_route(bool camera) {
const rs_gpio_config_t config = {.mode = camera ? RS_GPIO_MODE_ALT1 : RS_GPIO_MODE_ALT0,
.pull = RS_GPIO_PULL_NONE,
.trigger = RS_GPIO_TRIGGER_NONE};
if (camera) {
printf("P9_CTRL camera\n");
} else {
printf("P9_CTRL audio\n");
}
if (!rs_p9_recovery_delay(32U)) {
return false;
}
for (uint32_t pin = 12U; pin <= (camera ? 23U : 17U); pin++) {
if (rs_gpio_configure(pin, &config) != RS_OK) {
return false;
}
}
return rs_tiny_rcu_set_clkout(camera ? 1U : 0U, camera ? 1U : 0U) == RS_OK;
}
static bool rs_p9_recovery_audio(void) {
const rs_i2s_config_t audio = {
.preset = RS_I2S_PRESET_16B_48K, .upbound = 96U, .lowbound = 32U};
rs_i2s_status_t status;
uint32_t matched = 0U;
if (!rs_p9_recovery_route(false) || rs_i2s_configure(&audio) != RS_OK ||
rs_i2s_flush(true, true, RS_P9_RECOVERY_TIMEOUT) != RS_OK) {
return false;
}
for (uint32_t index = 0U; index < 16U; index++) {
if (rs_i2s_write(UINT32_C(0x12341234), RS_P9_RECOVERY_TIMEOUT) != RS_OK) {
return false;
}
}
if (rs_i2s_enable(true, true) != RS_OK) {
return false;
}
for (uint32_t index = 0U; index < 32U && matched < 4U; index++) {
uint32_t received;
if (rs_i2s_read(&received, RS_P9_RECOVERY_TIMEOUT) != RS_OK) {
printf("P9_RECOVERY_AUDIO receive_timeout matched=%u index=%u\n", matched, index);
return false;
}
matched = received == UINT32_C(0x12341234) ? matched + 1U : 0U;
}
printf("P9_RECOVERY_AUDIO matched=%u\n", matched);
if (matched != 4U || rs_i2s_disable() != RS_OK ||
rs_i2s_flush(true, true, RS_P9_RECOVERY_TIMEOUT) != RS_OK ||
rs_i2s_get_status(&status) != RS_OK || status.enable ||
(status.status & RS_I2S_STATUS_BUSY) != 0U) {
return false;
}
return true;
}
static rs_dvp_config_t rs_p9_recovery_config(bool crop, rs_dvp_format_t format) {
const rs_dvp_config_t config = {.format = format,
.frame_width = 16U,
.frame_height = 8U,
.crop_enable = crop,
.crop_x = crop ? 2U : 0U,
.crop_y = crop ? 1U : 0U,
.crop_width = crop ? 8U : 16U,
.crop_height = crop ? 4U : 8U,
.snapshot = true,
.stream_enable = true};
return config;
}
static bool rs_p9_recovery_capture(uintptr_t destination, bool crop, rs_dvp_format_t format) {
const rs_dvp_config_t config = rs_p9_recovery_config(crop, format);
const uint32_t width = config.crop_width;
const uint32_t words = width * config.crop_height / 2U;
volatile uint32_t *const buffer = (volatile uint32_t *)destination;
volatile uint32_t *const prefix = (volatile uint32_t *)(destination - sizeof(uint32_t));
rs_dvp_status_t status;
rs_xpi_error_t error;
const uint32_t previous_irqs = rs_p9_recovery_irqs;
if (rs_dvp_configure(&config) != RS_OK ||
rs_dvp_interrupt_enable(RS_DVP_INTERRUPT_FRAME_DONE) != RS_OK) {
return false;
}
*prefix = UINT32_C(0x13579BDF);
buffer[words] = UINT32_C(0x2468ACE0);
const rs_status_t capture = rs_dvp_capture_dma(destination, words, RS_P9_RECOVERY_TIMEOUT);
if (rs_dvp_get_status(&status) != RS_OK) {
return false;
}
printf("P9_RECOVERY_CAPTURE format=%u crop=%u code=%u words=%u pixels=%u dvp_error=%u "
"irq_delta=%u\n",
(unsigned int)format, crop ? 1U : 0U, (unsigned int)capture, status.word_count,
status.pixel_count, status.error_status, rs_p9_recovery_irqs - previous_irqs);
if (capture != RS_OK || status.error_status != 0U || status.word_count != words ||
status.pixel_count != words * 2U || rs_p9_recovery_irqs != previous_irqs + 1U) {
return false;
}
for (uint32_t index = 0U; index < words; index++) {
const uint32_t row = index / (width / 2U) + config.crop_y;
const uint32_t column = index % (width / 2U) + config.crop_x / 2U;
if (buffer[index] != rs_p9_frame_word(row, column, false)) {
return false;
}
}
if (*prefix != UINT32_C(0x13579BDF) || buffer[words] != UINT32_C(0x2468ACE0) ||
rs_xpi_error_get(&error) != RS_OK || error.valid) {
return false;
}
rs_p9_recovery_frames++;
return true;
}
static bool rs_p9_recovery_rejections(void) {
rs_dvp_config_t config = rs_p9_recovery_config(false, RS_DVP_FORMAT_RGB565);
rs_dma_status_t before, after;
if (rs_dvp_configure(&config) != RS_OK ||
rs_dma_get_status(RS_DMA_CHANNEL_DVP, &before) != RS_OK ||
rs_dvp_capture_dma(RS_P9_RECOVERY_ADDRESS, 63U, 100U) != RS_ENOSPC) {
return false;
}
config.frame_width = 15U;
if (rs_dvp_configure(&config) != RS_OK ||
rs_dvp_capture_dma(RS_P9_RECOVERY_ADDRESS, 64U, 100U) != RS_EINVAL ||
rs_dma_get_status(RS_DMA_CHANNEL_DVP, &after) != RS_OK || after.busy ||
after.bytes_done != before.bytes_done) {
return false;
}
config.crop_enable = true;
config.crop_x = 15U;
config.crop_width = 2U;
return rs_dvp_configure(&config) == RS_EINVAL;
}
static bool rs_p9_recovery_stop(bool high) {
rs_dvp_config_t config = rs_p9_recovery_config(false, RS_DVP_FORMAT_RGB565);
rs_dvp_status_t status;
rs_tiny_rcu_snapshot_t snapshot;
rs_timeout_t budget = RS_P9_RECOVERY_TIMEOUT;
config.stream_enable = false;
if (rs_dvp_configure(&config) != RS_OK || rs_dvp_start() != RS_OK) {
return false;
}
do {
if (budget-- == 0U || rs_dvp_get_status(&status) != RS_OK) {
return false;
}
} while (!status.active);
if (high) {
printf("P9_CTRL stop_high\n");
} else {
printf("P9_CTRL stop_low\n");
}
if (!rs_p9_recovery_delay(32U)) {
return false;
}
const uint32_t old_timeout = RS_SOC_REG32(RS_SOC_APB4_SYSCTRL_BASE, RS_TINY_RCU_TIMEOUT_OFFSET);
RS_SOC_REG32(RS_SOC_APB4_SYSCTRL_BASE, RS_TINY_RCU_TIMEOUT_OFFSET) = 1024U;
const rs_status_t reset =
rs_tiny_rcu_apply_reset(RS_P9_RECOVERY_DVP_MASK, RS_P9_RECOVERY_TIMEOUT);
if (rs_tiny_rcu_get_snapshot(&snapshot) != RS_OK || rs_dvp_get_status(&status) != RS_OK) {
return false;
}
printf("P9_RECOVERY_DIAG high=%u reset_status=%u fault=%u dvp_error=%u words=%u\n",
high ? 1U : 0U, (unsigned int)reset, snapshot.fault, status.error_status,
status.word_count);
if (reset != RS_ETIMEOUT || snapshot.fault == 0U || snapshot.gate_status != 0U) {
return false;
}
printf("P9_CTRL resume\n");
if (!rs_p9_recovery_delay(100U) || rs_dvp_abort() != RS_OK ||
rs_tiny_rcu_clear_fault(RS_TINY_RCU_FAULT_MASK) != RS_OK ||
rs_tiny_rcu_apply_reset(RS_P9_RECOVERY_DVP_MASK, RS_P9_RECOVERY_TIMEOUT) != RS_OK) {
return false;
}
RS_SOC_REG32(RS_SOC_APB4_SYSCTRL_BASE, RS_TINY_RCU_TIMEOUT_OFFSET) = old_timeout;
return rs_p9_recovery_capture(RS_P9_RECOVERY_ADDRESS, false, RS_DVP_FORMAT_RGB565);
}
static bool rs_p9_recovery_record(uint32_t id, bool pass) {
printf("P9_RECOVERY_CASE id=%u pass=%u\n", id, pass ? 1U : 0U);
if (pass) {
rs_p9_recovery_cases++;
}
return pass;
}
int main(void) {
rs_tiny_rcu_snapshot_t snapshot;
rs_xpi_psram_t psram;
__disable_irq();
bool pass = rs_tiny_rcu_get_snapshot(&snapshot) == RS_OK &&
rs_p9_console_ready(&snapshot, rs_uart_init) &&
rs_p9_psram_pin_ready(rs_gpio_configure);
pass = pass && rs_p9_recovery_record(1U, rs_p9_recovery_audio());
pass = pass && rs_p9_recovery_route(true) &&
rs_xpi_psram_initialize(snapshot.mem_hz, RS_TIMEOUT_DEFAULT, &psram) == RS_OK &&
rs_xpi_psram_validate_dma(&psram, RS_P9_RECOVERY_ADDRESS, 256U, 4U) == RS_OK &&
rs_irq_enable_external(RS_SOC_EXT_IRQ_DVP, rs_p9_recovery_irq) == RS_OK;
__enable_irq();
pass = pass &&
rs_p9_recovery_record(
2U, rs_p9_recovery_capture(RS_P9_RECOVERY_ADDRESS, false, RS_DVP_FORMAT_RGB565) &&
rs_p9_recovery_capture(RS_P9_RECOVERY_ADDRESS, true, RS_DVP_FORMAT_YUV422));
pass = pass && rs_p9_recovery_record(3U, rs_p9_recovery_rejections());
pass = pass &&
rs_p9_recovery_record(4U, rs_p9_recovery_capture((uintptr_t)&rs_p9_recovery_buffer[1],
true, RS_DVP_FORMAT_RGB565));
pass = pass && rs_p9_recovery_record(5U, rs_p9_recovery_stop(false));
pass = pass && rs_p9_recovery_record(6U, rs_p9_recovery_stop(true));
pass = pass && rs_dvp_abort() == RS_OK &&
rs_p9_recovery_record(
7U, rs_p9_recovery_audio() && rs_p9_recovery_route(true) &&
rs_p9_recovery_capture(RS_P9_RECOVERY_ADDRESS, false, RS_DVP_FORMAT_YUV422));
__disable_irq();
printf("P9_RECOVERY_RESULT code=%u cases=%u frames=%u irqs=%u\n", pass ? 0U : 1U,
rs_p9_recovery_cases, rs_p9_recovery_frames, rs_p9_recovery_irqs);
/* Drain the last serial record before the testbench's terminal marker. */
pass = pass && rs_p9_recovery_delay(1000U);
rs_sysctrl_write_test_status(pass, pass ? 0U : 9U);
for (;;) {
}
}
+87
View File
@@ -0,0 +1,87 @@
/* Copyright (c) 2026 Yuchi Miao; SPDX-License-Identifier: MulanPSL-2.0 */
#include "retrosoc/generated/memory_map.h"
/* Compact gate-level DVP check. No hierarchical DUT access or C runtime.
* Register offsets mirror the frozen GPIO admin and DVP V2 ABI. */
.section .text
.global _start
_start:
li s0, RS_SOC_APB4_GPIO_ADMIN_BASE
li t0, 0x007ff000
sw t0, 0x038(s0) /* ALT_SELECT: camera data/clock/sync inputs. */
sw t0, 0x034(s0) /* ALT_ENABLE */
sw t0, 0x01c(s0) /* OE_CLEAR */
li s1, RS_SOC_APB4_DVP_BASE
lw t0, 0x0f8(s1)
li t1, 0x00020000
bne t0, t1, failed
li t0, 0x00080010
sw t0, 0x018(s1)
sw zero, 0x00c(s1)
li t0, 3
sw t0, 0x000(s1)
li s2, 20000
wait_frame:
lw t0, 0x038(s1)
bnez t0, failed
lw t0, 0x024(s1)
li t1, 1
beq t0, t1, check_counts
addi s2, s2, -1
bnez s2, wait_frame
j failed
check_counts:
lw t0, 0x028(s1)
li t1, 8
bne t0, t1, failed
lw t0, 0x02c(s1)
li t1, 128
bne t0, t1, failed
lw t0, 0x030(s1)
li t1, 64
bne t0, t1, failed
lw t0, 0x03c(s1)
andi t0, t0, 4
beqz t0, failed
li a0, 0
read_word:
lw t0, 0x008(s1)
andi t0, t0, 4
bnez t0, failed
srli t0, a0, 3
li t1, 3
mul t0, t0, t1
andi t1, a0, 7
slli t1, t1, 2
add t0, t0, t1
andi t0, t0, 255
addi t1, t0, 1
andi t1, t1, 255
addi t2, t0, 2
andi t2, t2, 255
addi t3, t0, 3
andi t3, t3, 255
slli t0, t0, 8
or t0, t0, t1
slli t2, t2, 24
or t0, t0, t2
slli t3, t3, 16
or t0, t0, t3
lw t1, 0x004(s1)
bne t0, t1, failed
addi a0, a0, 1
li t0, 64
bne a0, t0, read_word
lw t0, 0x008(s1)
andi t0, t0, 6
li t1, 4
bne t0, t1, failed
li t1, 0x80000001
j finish
failed:
li t1, 0x80000900
finish:
li t0, RS_SOC_APB4_SYSCTRL_BASE
sw t1, RS_SOC_SYSCTRL_TEST_STATUS_OFFSET(t0)
halt:
j halt
+11 -1
View File
@@ -84,8 +84,18 @@ external input to REF24 at boot and permit no-PLL XIN transitions. See the
and configuration digest; ordinary Tiny acceptance firmware remains the default. and configuration digest; ordinary Tiny acceptance firmware remains the default.
`TINY_P8_ACCEPTANCE=YES APP=ci_smoke` instead selects the optional XPI PSRAM `TINY_P8_ACCEPTANCE=YES APP=ci_smoke` instead selects the optional XPI PSRAM
transport image and its distinct digest. Populate the verification device with transport image and its distinct digest. Populate the verification device with
`TINY_SIM_ARGS=+tiny_p8_psram`; P7/P8 acceptance selectors are mutually exclusive. `TINY_SIM_ARGS=+tiny_p8_psram`; P7/P8/P9 acceptance selectors are mutually exclusive.
See the [P8 runbook](../docs/ip/tiny-soc-r2-psram.md) for bounded DMA and evidence. See the [P8 runbook](../docs/ip/tiny-soc-r2-psram.md) for bounded DMA and evidence.
`TINY_P9_ACCEPTANCE=YES APP=ci_smoke` selects the DVP camera capture image;
use `TINY_SIM_ARGS='+tiny_psram +tiny_p9_camera'` for the pin-level camera
model. The optional `TINY_P9_QUICK=YES` selector is a small focused smoke image,
not QVGA/VGA acceptance. See the [P9 runbook](../docs/ip/tiny-soc-r2-dvp.md).
`TINY_P9_WORKLOAD=VGA_YUV422` selects the full 640x480 YUV422 image and
changes its configuration digest. It requires P9 acceptance with QUICK=NO;
the default `QVGA_RGB565` retains the existing configuration key. Pair the
VGA image with `+tiny_p9_vga`; mismatched source geometry is not acceptance.
For passive progress add `+tiny_p9_monitor +tiny_p9_monitor_log=<absolute-evidence-file>`;
the separate log preserves firmware UART record boundaries.
`make SOC=TINY` selects this `make SOC=TINY` selects this
committed profile, and `make SOC=TINY PDK=IHP130` selects explicit compatibility. committed profile, and `make SOC=TINY PDK=IHP130` selects explicit compatibility.
Do not substitute Mini's `ci/ics55.mk` for Tiny. Acceptance evidence belongs in Do not substitute Mini's `ci/ics55.mk` for Tiny. Acceptance evidence belongs in
+6
View File
@@ -66,7 +66,13 @@ rs_status_t rs_dvp_get_status(rs_dvp_status_t *status);
rs_status_t rs_dvp_interrupt_enable(uint32_t mask); rs_status_t rs_dvp_interrupt_enable(uint32_t mask);
rs_status_t rs_dvp_interrupt_clear(uint32_t mask); rs_status_t rs_dvp_interrupt_clear(uint32_t mask);
rs_status_t rs_dvp_interrupt_test(uint32_t mask); rs_status_t rs_dvp_interrupt_test(uint32_t mask);
/* Single-snapshot helpers. Capacity is storage in 32-bit words, not the
* requested transfer length. DMA rejects odd active widths before starting;
* PIO stores a zero-padded halfword at every odd-width row end. Failed DMA
* cleanup must complete before a source abort/flush may discard its tail. */
rs_status_t rs_dvp_capture_dma(uintptr_t destination, uint32_t word_capacity, rs_timeout_t timeout); rs_status_t rs_dvp_capture_dma(uintptr_t destination, uint32_t word_capacity, rs_timeout_t timeout);
rs_status_t rs_dvp_capture_pio(uintptr_t destination, uint32_t word_capacity,
uint32_t expected_words, rs_timeout_t timeout);
void ip_dvp_test(void); void ip_dvp_test(void);
#endif #endif
+2 -2
View File
@@ -20,7 +20,7 @@ static bool rs_dma_address_valid(uintptr_t address, uint32_t byte_count, bool in
static bool rs_dma_request_valid(const rs_dma_config_t *config) { static bool rs_dma_request_valid(const rs_dma_config_t *config) {
#ifdef RS_SOC_TINY #ifdef RS_SOC_TINY
if (((uint32_t)config->request >= 16U) || if (((uint32_t)config->request >= 16U) ||
((UINT32_C(0x31FF) & (UINT32_C(1) << (uint32_t)config->request)) == 0U)) { ((UINT32_C(0x39FF) & (UINT32_C(1) << (uint32_t)config->request)) == 0U)) {
return false; return false;
} }
#endif #endif
@@ -98,7 +98,7 @@ rs_status_t rs_dma_tcd_validate(uint32_t channel, const rs_dma_tcd_t *tcd) {
request = (tcd->control >> RS_DMA_TCD_REQUEST_SHIFT) & UINT32_C(0xF); request = (tcd->control >> RS_DMA_TCD_REQUEST_SHIFT) & UINT32_C(0xF);
burst = (tcd->control >> RS_DMA_TCD_BURST_SHIFT) & UINT32_C(0x1F); burst = (tcd->control >> RS_DMA_TCD_BURST_SHIFT) & UINT32_C(0x1F);
#ifdef RS_SOC_TINY #ifdef RS_SOC_TINY
if ((UINT32_C(0x31FF) & (UINT32_C(1) << request)) == 0U) { if ((UINT32_C(0x39FF) & (UINT32_C(1) << request)) == 0U) {
return RS_ENOTSUP; return RS_ENOTSUP;
} }
#endif #endif
+289 -93
View File
@@ -5,39 +5,101 @@
#include <retrosoc/hal/dvp.h> #include <retrosoc/hal/dvp.h>
#include <retrosoc/lib/printf.h> #include <retrosoc/lib/printf.h>
#define RS_DVP_CTRL_OFFSET UINT32_C(0x000) #define RS_DVP_CTRL_OFFSET UINT32_C(0x000)
#define RS_DVP_RXDATA_OFFSET UINT32_C(0x004) #define RS_DVP_RXDATA_OFFSET UINT32_C(0x004)
#define RS_DVP_STATUS_OFFSET UINT32_C(0x008) #define RS_DVP_STATUS_OFFSET UINT32_C(0x008)
#define RS_DVP_STREAM_CTRL_OFFSET UINT32_C(0x00C) #define RS_DVP_STREAM_CTRL_OFFSET UINT32_C(0x00C)
#define RS_DVP_FORMAT_OFFSET UINT32_C(0x010) #define RS_DVP_FORMAT_OFFSET UINT32_C(0x010)
#define RS_DVP_SYNC_CFG_OFFSET UINT32_C(0x014) #define RS_DVP_SYNC_CFG_OFFSET UINT32_C(0x014)
#define RS_DVP_FRAME_SIZE_OFFSET UINT32_C(0x018) #define RS_DVP_FRAME_SIZE_OFFSET UINT32_C(0x018)
#define RS_DVP_CROP_START_OFFSET UINT32_C(0x01C) #define RS_DVP_CROP_START_OFFSET UINT32_C(0x01C)
#define RS_DVP_CROP_SIZE_OFFSET UINT32_C(0x020) #define RS_DVP_CROP_SIZE_OFFSET UINT32_C(0x020)
#define RS_DVP_FRAME_COUNT_OFFSET UINT32_C(0x024) #define RS_DVP_FRAME_COUNT_OFFSET UINT32_C(0x024)
#define RS_DVP_LINE_COUNT_OFFSET UINT32_C(0x028) #define RS_DVP_LINE_COUNT_OFFSET UINT32_C(0x028)
#define RS_DVP_PIXEL_COUNT_OFFSET UINT32_C(0x02C) #define RS_DVP_PIXEL_COUNT_OFFSET UINT32_C(0x02C)
#define RS_DVP_WORD_COUNT_OFFSET UINT32_C(0x030) #define RS_DVP_WORD_COUNT_OFFSET UINT32_C(0x030)
#define RS_DVP_DROP_COUNT_OFFSET UINT32_C(0x034) #define RS_DVP_DROP_COUNT_OFFSET UINT32_C(0x034)
#define RS_DVP_ERROR_STATUS_OFFSET UINT32_C(0x038) #define RS_DVP_ERROR_STATUS_OFFSET UINT32_C(0x038)
#define RS_DVP_INTR_STATE_OFFSET UINT32_C(0x03C) #define RS_DVP_INTR_STATE_OFFSET UINT32_C(0x03C)
#define RS_DVP_INTR_ENABLE_OFFSET UINT32_C(0x040) #define RS_DVP_INTR_ENABLE_OFFSET UINT32_C(0x040)
#define RS_DVP_INTR_STATUS_OFFSET UINT32_C(0x044) #define RS_DVP_INTR_STATUS_OFFSET UINT32_C(0x044)
#define RS_DVP_INTR_TEST_OFFSET UINT32_C(0x048) #define RS_DVP_INTR_TEST_OFFSET UINT32_C(0x048)
#define RS_DVP_COMMAND_OFFSET UINT32_C(0x04C) #define RS_DVP_COMMAND_OFFSET UINT32_C(0x04C)
#define RS_DVP_VERSION_OFFSET UINT32_C(0x0F8) #define RS_DVP_VERSION_OFFSET UINT32_C(0x0F8)
#define RS_DVP_CAPABILITY_OFFSET UINT32_C(0x0FC) #define RS_DVP_CAPABILITY_OFFSET UINT32_C(0x0FC)
#define RS_DVP_CTRL_ENABLE_MASK UINT32_C(0x00000001) #define RS_DVP_CTRL_ENABLE_MASK UINT32_C(0x00000001)
#define RS_DVP_CTRL_SNAPSHOT_MASK UINT32_C(0x00000002) #define RS_DVP_CTRL_SNAPSHOT_MASK UINT32_C(0x00000002)
#define RS_DVP_CTRL_CROP_MASK UINT32_C(0x00000004) #define RS_DVP_CTRL_CROP_MASK UINT32_C(0x00000004)
#define RS_DVP_COMMAND_ABORT_MASK UINT32_C(0x00000001) #define RS_DVP_COMMAND_ABORT_MASK UINT32_C(0x00000001)
#define RS_DVP_COMMAND_FLUSH_MASK UINT32_C(0x00000002) #define RS_DVP_COMMAND_FLUSH_MASK UINT32_C(0x00000002)
#define RS_DVP_STATUS_ACTIVE_MASK UINT32_C(0x00000001) #define RS_DVP_STATUS_ACTIVE_MASK UINT32_C(0x00000002)
#define RS_DVP_STATUS_ERROR_MASK UINT32_C(0x00000040) #define RS_DVP_STATUS_ENABLE_MASK UINT32_C(0x00000001)
#define RS_DVP_STATUS_ERROR_MASK UINT32_C(0x00000020)
#define RS_DVP_STATUS_FIFO_EMPTY_MASK UINT32_C(0x00000004)
static volatile uint32_t *rs_dvp_register(uint32_t offset) { #ifdef RS_SOC_TINY
return (volatile uint32_t *)(RS_SOC_APB4_DVP_BASE + (uintptr_t)offset); #define RS_DVP_DMA_CHANNEL RS_DMA_CHANNEL_DVP
#else
#define RS_DVP_DMA_CHANNEL RS_DMA_CHANNEL_BULK
#endif
#ifndef RS_SOC_DVP_DMA_BURST_BEATS
#define RS_SOC_DVP_DMA_BURST_BEATS RS_DMA_MAX_BURST_BEATS
#endif
#ifdef RS_DVP_TEST_MMIO
uint32_t rs_dvp_test_read(uint32_t offset);
void rs_dvp_test_write(uint32_t offset, uint32_t value);
#endif
static uint32_t rs_dvp_read(uint32_t offset) {
#ifdef RS_DVP_TEST_MMIO
return rs_dvp_test_read(offset);
#else
return *(volatile uint32_t *)(RS_SOC_APB4_DVP_BASE + (uintptr_t)offset);
#endif
}
static void rs_dvp_write(uint32_t offset, uint32_t value) {
#ifdef RS_DVP_TEST_MMIO
rs_dvp_test_write(offset, value);
#else
*(volatile uint32_t *)(RS_SOC_APB4_DVP_BASE + (uintptr_t)offset) = value;
#endif
}
static rs_status_t rs_dvp_clear_errors(rs_timeout_t timeout) {
if (rs_dvp_read(RS_DVP_ERROR_STATUS_OFFSET) == 0U) {
return RS_OK;
}
rs_dvp_write(RS_DVP_ERROR_STATUS_OFFSET, UINT32_C(0x3F));
while (timeout-- != 0U) {
if (rs_dvp_read(RS_DVP_ERROR_STATUS_OFFSET) == 0U) {
return RS_OK;
}
}
return RS_ETIMEOUT;
}
static rs_status_t rs_dvp_wait_disabled(rs_timeout_t timeout) {
while (timeout-- != 0U) {
if ((rs_dvp_read(RS_DVP_STATUS_OFFSET) & RS_DVP_STATUS_ENABLE_MASK) == 0U) {
return RS_OK;
}
}
return RS_ETIMEOUT;
}
static rs_status_t rs_dvp_rearm(rs_timeout_t timeout) {
const uint32_t status = rs_dvp_read(RS_DVP_STATUS_OFFSET);
if ((status & RS_DVP_STATUS_ACTIVE_MASK) != 0U ||
(status & RS_DVP_STATUS_FIFO_EMPTY_MASK) == 0U ||
rs_dvp_read(RS_DVP_ERROR_STATUS_OFFSET) != 0U) {
return RS_EIO;
}
rs_dvp_write(RS_DVP_CTRL_OFFSET, rs_dvp_read(RS_DVP_CTRL_OFFSET) & ~RS_DVP_CTRL_ENABLE_MASK);
return rs_dvp_wait_disabled(timeout);
} }
static bool rs_dvp_config_valid(const rs_dvp_config_t *config) { static bool rs_dvp_config_valid(const rs_dvp_config_t *config) {
@@ -57,8 +119,8 @@ static bool rs_dvp_config_valid(const rs_dvp_config_t *config) {
rs_status_t rs_dvp_probe(uint32_t *version, uint32_t *capability) { rs_status_t rs_dvp_probe(uint32_t *version, uint32_t *capability) {
if ((version == NULL) || (capability == NULL)) if ((version == NULL) || (capability == NULL))
return RS_EINVAL; return RS_EINVAL;
*version = *rs_dvp_register(RS_DVP_VERSION_OFFSET); *version = rs_dvp_read(RS_DVP_VERSION_OFFSET);
*capability = *rs_dvp_register(RS_DVP_CAPABILITY_OFFSET); *capability = rs_dvp_read(RS_DVP_CAPABILITY_OFFSET);
return (*version == UINT32_C(0x00020000)) ? RS_OK : RS_EIO; return (*version == UINT32_C(0x00020000)) ? RS_OK : RS_EIO;
} }
@@ -69,7 +131,10 @@ rs_status_t rs_dvp_configure(const rs_dvp_config_t *config) {
if (!rs_dvp_config_valid(config)) if (!rs_dvp_config_valid(config))
return RS_EINVAL; return RS_EINVAL;
(void)rs_dvp_abort(); const rs_status_t stop = rs_dvp_abort();
if (stop != RS_OK) {
return stop;
}
format = (uint32_t)config->format; format = (uint32_t)config->format;
if (config->byte_swap) if (config->byte_swap)
format |= UINT32_C(0x00000004); format |= UINT32_C(0x00000004);
@@ -79,133 +144,264 @@ rs_status_t rs_dvp_configure(const rs_dvp_config_t *config) {
(config->pclk_falling ? 4U : 0U); (config->pclk_falling ? 4U : 0U);
control = (config->snapshot ? RS_DVP_CTRL_SNAPSHOT_MASK : 0U) | control = (config->snapshot ? RS_DVP_CTRL_SNAPSHOT_MASK : 0U) |
(config->crop_enable ? RS_DVP_CTRL_CROP_MASK : 0U); (config->crop_enable ? RS_DVP_CTRL_CROP_MASK : 0U);
*rs_dvp_register(RS_DVP_FORMAT_OFFSET) = format; rs_dvp_write(RS_DVP_FORMAT_OFFSET, format);
*rs_dvp_register(RS_DVP_SYNC_CFG_OFFSET) = sync; rs_dvp_write(RS_DVP_SYNC_CFG_OFFSET, sync);
*rs_dvp_register(RS_DVP_FRAME_SIZE_OFFSET) = rs_dvp_write(RS_DVP_FRAME_SIZE_OFFSET,
((uint32_t)config->frame_height << 16) | config->frame_width; ((uint32_t)config->frame_height << 16) | config->frame_width);
*rs_dvp_register(RS_DVP_CROP_START_OFFSET) = ((uint32_t)config->crop_y << 16) | config->crop_x; rs_dvp_write(RS_DVP_CROP_START_OFFSET, ((uint32_t)config->crop_y << 16) | config->crop_x);
*rs_dvp_register(RS_DVP_CROP_SIZE_OFFSET) = rs_dvp_write(RS_DVP_CROP_SIZE_OFFSET,
((uint32_t)config->crop_height << 16) | config->crop_width; ((uint32_t)config->crop_height << 16) | config->crop_width);
*rs_dvp_register(RS_DVP_STREAM_CTRL_OFFSET) = config->stream_enable ? 1U : 0U; rs_dvp_write(RS_DVP_STREAM_CTRL_OFFSET, config->stream_enable ? 1U : 0U);
*rs_dvp_register(RS_DVP_ERROR_STATUS_OFFSET) = RS_DVP_ERROR_OVERFLOW | RS_DVP_ERROR_SYNC | rs_dvp_write(RS_DVP_INTR_STATE_OFFSET, RS_DVP_INTERRUPT_ALL);
RS_DVP_ERROR_SIZE | RS_DVP_ERROR_PARTIAL | rs_dvp_write(RS_DVP_CTRL_OFFSET, control);
RS_DVP_ERROR_CONFIG | RS_DVP_ERROR_ABORT;
*rs_dvp_register(RS_DVP_INTR_STATE_OFFSET) = RS_DVP_INTERRUPT_ALL;
*rs_dvp_register(RS_DVP_CTRL_OFFSET) = control;
return RS_OK; return RS_OK;
} }
static rs_status_t rs_dvp_start_bounded(rs_timeout_t timeout) {
const rs_status_t rearm = rs_dvp_rearm(timeout);
if (rearm != RS_OK) {
return rearm;
}
rs_dvp_write(RS_DVP_CTRL_OFFSET, rs_dvp_read(RS_DVP_CTRL_OFFSET) | RS_DVP_CTRL_ENABLE_MASK);
while (timeout-- != 0U) {
if ((rs_dvp_read(RS_DVP_STATUS_OFFSET) & RS_DVP_STATUS_ENABLE_MASK) != 0U) {
return RS_OK;
}
}
return RS_ETIMEOUT;
}
rs_status_t rs_dvp_start(void) { rs_status_t rs_dvp_start(void) {
*rs_dvp_register(RS_DVP_CTRL_OFFSET) |= RS_DVP_CTRL_ENABLE_MASK; return rs_dvp_start_bounded(RS_TIMEOUT_DEFAULT);
return RS_OK;
} }
rs_status_t rs_dvp_abort(void) { rs_status_t rs_dvp_abort(void) {
*rs_dvp_register(RS_DVP_COMMAND_OFFSET) = RS_DVP_COMMAND_ABORT_MASK; rs_dma_status_t dma_status;
*rs_dvp_register(RS_DVP_CTRL_OFFSET) &= ~RS_DVP_CTRL_ENABLE_MASK; rs_timeout_t timeout = RS_TIMEOUT_DEFAULT;
return RS_OK; if (rs_dma_get_status(RS_DVP_DMA_CHANNEL, &dma_status) != RS_OK || dma_status.busy) {
return RS_EIO;
}
if (rs_dvp_clear_errors(timeout) != RS_OK) {
return RS_ETIMEOUT;
}
rs_dvp_write(RS_DVP_COMMAND_OFFSET, RS_DVP_COMMAND_ABORT_MASK);
while (timeout-- != 0U) {
const uint32_t status = rs_dvp_read(RS_DVP_STATUS_OFFSET);
if ((rs_dvp_read(RS_DVP_ERROR_STATUS_OFFSET) & RS_DVP_ERROR_ABORT) != 0U &&
(status & RS_DVP_STATUS_ACTIVE_MASK) == 0U &&
(status & RS_DVP_STATUS_FIFO_EMPTY_MASK) != 0U) {
rs_dvp_write(RS_DVP_CTRL_OFFSET,
rs_dvp_read(RS_DVP_CTRL_OFFSET) & ~RS_DVP_CTRL_ENABLE_MASK);
const rs_status_t clear = rs_dvp_clear_errors(RS_TIMEOUT_DEFAULT);
return clear == RS_OK ? rs_dvp_wait_disabled(RS_TIMEOUT_DEFAULT) : clear;
}
}
return RS_ETIMEOUT;
} }
rs_status_t rs_dvp_flush(void) { rs_status_t rs_dvp_flush(void) {
*rs_dvp_register(RS_DVP_COMMAND_OFFSET) = RS_DVP_COMMAND_FLUSH_MASK; rs_dvp_write(RS_DVP_COMMAND_OFFSET, RS_DVP_COMMAND_FLUSH_MASK);
return RS_OK; return RS_OK;
} }
rs_status_t rs_dvp_get_status(rs_dvp_status_t *status) { rs_status_t rs_dvp_get_status(rs_dvp_status_t *status) {
if (status == NULL) if (status == NULL)
return RS_EINVAL; return RS_EINVAL;
status->status = *rs_dvp_register(RS_DVP_STATUS_OFFSET); status->status = rs_dvp_read(RS_DVP_STATUS_OFFSET);
status->active = (status->status & RS_DVP_STATUS_ACTIVE_MASK) != 0U; status->active = (status->status & RS_DVP_STATUS_ACTIVE_MASK) != 0U;
status->frame_count = *rs_dvp_register(RS_DVP_FRAME_COUNT_OFFSET); status->frame_count = rs_dvp_read(RS_DVP_FRAME_COUNT_OFFSET);
status->line_count = *rs_dvp_register(RS_DVP_LINE_COUNT_OFFSET); status->line_count = rs_dvp_read(RS_DVP_LINE_COUNT_OFFSET);
status->pixel_count = *rs_dvp_register(RS_DVP_PIXEL_COUNT_OFFSET); status->pixel_count = rs_dvp_read(RS_DVP_PIXEL_COUNT_OFFSET);
status->word_count = *rs_dvp_register(RS_DVP_WORD_COUNT_OFFSET); status->word_count = rs_dvp_read(RS_DVP_WORD_COUNT_OFFSET);
status->drop_count = *rs_dvp_register(RS_DVP_DROP_COUNT_OFFSET); status->drop_count = rs_dvp_read(RS_DVP_DROP_COUNT_OFFSET);
status->error_status = *rs_dvp_register(RS_DVP_ERROR_STATUS_OFFSET); status->error_status = rs_dvp_read(RS_DVP_ERROR_STATUS_OFFSET);
status->interrupt_state = *rs_dvp_register(RS_DVP_INTR_STATE_OFFSET); status->interrupt_state = rs_dvp_read(RS_DVP_INTR_STATE_OFFSET);
return RS_OK; return RS_OK;
} }
rs_status_t rs_dvp_interrupt_enable(uint32_t mask) { rs_status_t rs_dvp_interrupt_enable(uint32_t mask) {
if ((mask & ~RS_DVP_INTERRUPT_ALL) != 0U) if ((mask & ~RS_DVP_INTERRUPT_ALL) != 0U)
return RS_EINVAL; return RS_EINVAL;
*rs_dvp_register(RS_DVP_INTR_ENABLE_OFFSET) = mask; rs_dvp_write(RS_DVP_INTR_ENABLE_OFFSET, mask);
return RS_OK; return RS_OK;
} }
rs_status_t rs_dvp_interrupt_clear(uint32_t mask) { rs_status_t rs_dvp_interrupt_clear(uint32_t mask) {
if ((mask & ~RS_DVP_INTERRUPT_ALL) != 0U) if ((mask & ~RS_DVP_INTERRUPT_ALL) != 0U)
return RS_EINVAL; return RS_EINVAL;
*rs_dvp_register(RS_DVP_INTR_STATE_OFFSET) = mask; rs_dvp_write(RS_DVP_INTR_STATE_OFFSET, mask);
return RS_OK; return RS_OK;
} }
rs_status_t rs_dvp_interrupt_test(uint32_t mask) { rs_status_t rs_dvp_interrupt_test(uint32_t mask) {
if ((mask & ~RS_DVP_INTERRUPT_ALL) != 0U) if ((mask & ~RS_DVP_INTERRUPT_ALL) != 0U)
return RS_EINVAL; return RS_EINVAL;
*rs_dvp_register(RS_DVP_INTR_TEST_OFFSET) = mask; rs_dvp_write(RS_DVP_INTR_TEST_OFFSET, mask);
return RS_OK; return RS_OK;
} }
static rs_status_t rs_dvp_dma_cleanup(void) { static rs_status_t rs_dvp_dma_cleanup(uint32_t channel) {
const rs_status_t status = rs_dma_abort_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT); const rs_status_t status = rs_dma_abort_wait(channel, RS_TIMEOUT_DEFAULT);
if (status != RS_OK) { if (status != RS_OK) {
return status; return status;
} }
return rs_dma_reset(RS_DMA_CHANNEL_BULK); return rs_dma_reset(channel);
}
static rs_status_t rs_dvp_cancel_dma(void) {
const rs_status_t result = rs_dvp_dma_cleanup(RS_DVP_DMA_CHANNEL);
return result == RS_OK ? rs_dvp_abort() : result;
}
static rs_status_t rs_dvp_layout(uintptr_t destination, uint32_t capacity, bool dma,
uint32_t *words, uint32_t *pixels) {
const uint32_t control = rs_dvp_read(RS_DVP_CTRL_OFFSET);
const uint32_t size =
rs_dvp_read((control & RS_DVP_CTRL_CROP_MASK) != 0U ? RS_DVP_CROP_SIZE_OFFSET
: RS_DVP_FRAME_SIZE_OFFSET);
const uint32_t width = size & UINT32_C(0xFFFF);
const uint32_t height = size >> 16;
if (width == 0U || height == 0U || (control & RS_DVP_CTRL_SNAPSHOT_MASK) == 0U ||
(dma && (width & 1U) != 0U) || destination == 0U ||
(destination & (sizeof(uint32_t) - 1U)) != 0U) {
return RS_EINVAL;
}
*words = ((width + 1U) / 2U) * height;
*pixels = width * height;
if (*words > UINT32_MAX / sizeof(uint32_t) || capacity < *words) {
return RS_ENOSPC;
}
if (destination > (uintptr_t)UINT32_MAX ||
destination > (uintptr_t)UINT32_MAX - (*words * sizeof(uint32_t) - 1U)) {
return RS_EINVAL;
}
return RS_OK;
} }
rs_status_t rs_dvp_capture_dma(uintptr_t destination, uint32_t word_capacity, rs_status_t rs_dvp_capture_dma(uintptr_t destination, uint32_t word_capacity,
rs_timeout_t timeout) { rs_timeout_t timeout) {
rs_dma_config_t dma_config; rs_dma_config_t dma_config = {0};
rs_dma_status_t dma_status;
rs_dvp_status_t status; rs_dvp_status_t status;
rs_status_t result; rs_status_t result;
if ((destination == 0U) || (word_capacity == 0U) || uint32_t words;
(word_capacity > (UINT32_MAX / sizeof(uint32_t)))) uint32_t pixels;
return RS_EINVAL; const uint32_t previous_frame = rs_dvp_read(RS_DVP_FRAME_COUNT_OFFSET);
result = rs_dvp_layout(destination, word_capacity, true, &words, &pixels);
if (result != RS_OK) {
return result;
}
if (rs_dvp_read(RS_DVP_STREAM_CTRL_OFFSET) == 0U ||
rs_dvp_read(RS_DVP_ERROR_STATUS_OFFSET) != 0U) {
return RS_EIO;
}
(void)rs_dvp_interrupt_clear(RS_DVP_INTERRUPT_ALL);
dma_config.kind = RS_DMA_KIND_STREAM_TO_MM; dma_config.kind = RS_DMA_KIND_STREAM_TO_MM;
dma_config.request = RS_DMA_REQUEST_DVP_RX; dma_config.request = RS_DMA_REQUEST_DVP_RX;
dma_config.source = (uintptr_t)0U; dma_config.source = (uintptr_t)0U;
dma_config.destination = destination; dma_config.destination = destination;
dma_config.byte_count = word_capacity * sizeof(uint32_t); dma_config.byte_count = words * sizeof(uint32_t);
dma_config.width = RS_DMA_WIDTH_32; dma_config.width = RS_DMA_WIDTH_32;
dma_config.source_increment = false; dma_config.source_increment = false;
dma_config.destination_increment = true; dma_config.destination_increment = true;
dma_config.priority = 3U; dma_config.priority = 3U;
dma_config.burst_beats = RS_DMA_MAX_BURST_BEATS; dma_config.burst_beats = RS_SOC_DVP_DMA_BURST_BEATS;
result = rs_dma_configure(RS_DMA_CHANNEL_BULK, &dma_config); result = rs_dma_configure(RS_DVP_DMA_CHANNEL, &dma_config);
if (result == RS_OK) {
result = rs_dma_start(RS_DMA_CHANNEL_BULK);
}
if (result == RS_OK) {
result = rs_dvp_start();
}
if (result != RS_OK) { if (result != RS_OK) {
if ((RS_DMA_CH_REG(RS_DMA_CHANNEL_BULK, RS_DMA_CH_REG_STATUS) & RS_DMA_STATUS_BUSY) != 0U) { // A rejected configure may belong to another owner: do not abort it.
(void)rs_dvp_dma_cleanup();
}
return RS_EIO; return RS_EIO;
} }
result = rs_dma_start(RS_DVP_DMA_CHANNEL);
if (result != RS_OK)
return result;
result = rs_dvp_start_bounded(timeout);
if (result != RS_OK) {
const rs_status_t cleanup = rs_dvp_cancel_dma();
return cleanup == RS_OK ? result : cleanup;
}
while (timeout != 0U) {
timeout--;
if ((rs_dvp_get_status(&status) != RS_OK) ||
(rs_dma_get_status(RS_DVP_DMA_CHANNEL, &dma_status) != RS_OK)) {
result = rs_dvp_cancel_dma();
return result == RS_OK ? RS_EIO : result;
}
if (status.error_status != 0U || dma_status.error || dma_status.aborted) {
result = rs_dvp_cancel_dma();
return result == RS_OK ? RS_EIO : result;
}
if ((status.interrupt_state & RS_DVP_INTERRUPT_FRAME_DONE) != 0U &&
status.frame_count == previous_frame + 1U && !status.active) {
if ((rs_dma_wait(RS_DVP_DMA_CHANNEL, timeout) != RS_OK) ||
(rs_dma_get_status(RS_DVP_DMA_CHANNEL, &dma_status) != RS_OK) ||
(rs_dvp_get_status(&status) != RS_OK) || dma_status.error || dma_status.aborted ||
!dma_status.done || dma_status.remaining != 0U ||
dma_status.bytes_done != dma_config.byte_count || status.error_status != 0U ||
status.pixel_count != pixels || status.word_count != words ||
(status.status & RS_DVP_STATUS_FIFO_EMPTY_MASK) == 0U) {
result = rs_dvp_cancel_dma();
return result == RS_OK ? RS_EIO : result;
}
return RS_OK;
}
}
result = rs_dvp_cancel_dma();
return result == RS_OK ? RS_ETIMEOUT : result;
}
rs_status_t rs_dvp_capture_pio(uintptr_t destination, uint32_t word_capacity,
uint32_t expected_words, rs_timeout_t timeout) {
rs_dvp_status_t status;
rs_dma_status_t dma_status;
uint32_t words = 0U;
uint32_t layout_words;
uint32_t pixels;
if (rs_dma_get_status(RS_DVP_DMA_CHANNEL, &dma_status) != RS_OK || dma_status.busy ||
dma_status.suspended) {
return RS_EIO;
}
const uint32_t previous_frame = rs_dvp_read(RS_DVP_FRAME_COUNT_OFFSET);
const rs_status_t layout =
rs_dvp_layout(destination, word_capacity, false, &layout_words, &pixels);
if (layout != RS_OK) {
return layout;
}
if (expected_words != layout_words) {
return RS_EINVAL;
}
const rs_status_t rearm = rs_dvp_rearm(timeout);
if (rearm != RS_OK)
return rearm;
rs_dvp_write(RS_DVP_STREAM_CTRL_OFFSET, 0U);
(void)rs_dvp_interrupt_clear(RS_DVP_INTERRUPT_ALL);
const rs_status_t start = rs_dvp_start_bounded(timeout);
if (start != RS_OK)
return start;
while (timeout-- != 0U) { while (timeout-- != 0U) {
if (rs_dvp_get_status(&status) != RS_OK) { if (rs_dvp_get_status(&status) != RS_OK || status.error_status != 0U) {
(void)rs_dvp_abort(); (void)rs_dvp_abort();
(void)rs_dvp_dma_cleanup();
return RS_EIO; return RS_EIO;
} }
if ((status.error_status != 0U) || if ((status.status & RS_DVP_STATUS_FIFO_EMPTY_MASK) == 0U) {
((status.interrupt_state & RS_DVP_INTERRUPT_FRAME_DONE) != 0U)) { if (words >= expected_words) {
(void)rs_dvp_abort(); (void)rs_dvp_abort();
if ((status.error_status == 0U) && return RS_ENOSPC;
(rs_dma_wait(RS_DMA_CHANNEL_BULK, timeout) == RS_OK)) { }
((volatile uint32_t *)(uintptr_t)destination)[words] =
rs_dvp_read(RS_DVP_RXDATA_OFFSET);
words++;
}
if (((status.interrupt_state & RS_DVP_INTERRUPT_FRAME_DONE) != 0U) &&
status.frame_count == previous_frame + 1U && (words == expected_words) &&
!status.active) {
if ((rs_dvp_read(RS_DVP_STATUS_OFFSET) & RS_DVP_STATUS_FIFO_EMPTY_MASK) != 0U &&
status.pixel_count == pixels && status.word_count == expected_words) {
return RS_OK; return RS_OK;
} }
(void)rs_dvp_dma_cleanup();
return RS_EIO;
} }
} }
(void)rs_dvp_abort(); (void)rs_dvp_abort();
(void)rs_dvp_dma_cleanup();
return RS_ETIMEOUT; return RS_ETIMEOUT;
} }
+87 -4
View File
@@ -70,7 +70,7 @@ valid capture. RW registers support byte strobes.
| `0x024` | `FRAME_COUNT` | RO | Completed frame count. | | `0x024` | `FRAME_COUNT` | RO | Completed frame count. |
| `0x028` | `LINE_COUNT` | RO | Completed line count from the last frame. | | `0x028` | `LINE_COUNT` | RO | Completed line count from the last frame. |
| `0x02C` | `PIXEL_COUNT` | RO | Captured pixel count from the last frame. | | `0x02C` | `PIXEL_COUNT` | RO | Captured pixel count from the last frame. |
| `0x030` | `WORD_COUNT` | RO | AXI4-Stream word count from the last frame. | | `0x030` | `WORD_COUNT` | RO | Full 32-bit AXI4-Stream word count from the last frame. |
| `0x034` | `DROP_COUNT` | RO | Number of dropped words caused by overflow. | | `0x034` | `DROP_COUNT` | RO | Number of dropped words caused by overflow. |
| `0x038` | `ERROR_STATUS` | RW1C | Sticky overflow, sync, size, partial, config, and abort errors. | | `0x038` | `ERROR_STATUS` | RW1C | Sticky overflow, sync, size, partial, config, and abort errors. |
| `0x03C` | `INTR_STATE` | RW1C | Sticky frame, line, overflow, configuration, and abort events. | | `0x03C` | `INTR_STATE` | RW1C | Sticky frame, line, overflow, configuration, and abort events. |
@@ -94,6 +94,46 @@ interrupt state are write-one-to-clear. Hardware events have priority over a
simultaneous software clear so that an event cannot be lost. The external simultaneous software clear so that an event cannot be lost. The external
interrupt is the reduction OR of `INTR_STATE & INTR_ENABLE`. interrupt is the reduction OR of `INTR_STATE & INTR_ENABLE`.
`STATUS` bit 0 is ENABLE, bit 1 is ACTIVE, bit 2 is FIFO_EMPTY and bit 5
is ERROR. ENABLE reports the configuration actually applied in the pixel
domain; `CTRL` continues to read back the requested control value. Software
can therefore acknowledge a disable/enable transition without a new register.
ACTIVE is delivered with a coherent pixel-domain status snapshot;
it is not an alias for ENABLE. An error W1C is retained until the pixel domain
applies its mask and returns a status packet with the matching clear epoch.
Old reports cannot acknowledge that operation. New source errors take priority
over a simultaneous clear. APB response timing and register offsets remain
unchanged; software must poll with a timeout. A stopped pixel clock cannot
complete a clear or recovery operation.
Configuration transfer is held behind pending error clears. A separate applied
pixel configuration is updated only when the core accepts the mailbox; the
mailbox's earlier data presentation cannot modify an active frame.
Configuration and command transactions carry internal epochs; lifecycle idle
stays low until the matching applied/drained status is returned. Waiting for
the first VSYNC is armed work, not idle. Local reset-ready requires a native
pixel-domain status after reset. Tiny global boot uses its existing internal
leaf/APB/AXI prerequisites independently of this local-reset readiness, so an
absent sensor clock does not become a global CPU boot dependency.
ABORT cancels older configuration requests still waiting in the APB register
shell without changing requested-register readback. Its command mailbox also
carries the last issued configuration epoch. If that configuration arrives or
applies after ABORT, it completes the existing acknowledgement but cannot
rearm the core. The cancellation record is retired by the next different
applied configuration epoch, before the one-bit epoch can be reused. Repeated
or coalesced ABORT commands do not advance configuration epochs. ABORT does
not wait for configuration readiness, including when an active frame blocks
configuration application. Later configuration writes remain eligible after
the command drains; normal recovery still clears errors and acknowledges
disable/enable before using a new frame. No register or FIFO ABI is added.
Error interrupt causes use a retained six-bit pending mask and an acknowledged
mailbox, separate from the status snapshots. Repeated causes can coalesce while
pending, but two events between status samples cannot cancel one another. New
causes on mailbox acceptance remain pending for the next transfer. Clearing
an interrupt does not repeatedly reassert it merely because ERROR_STATUS still
holds an old error; a new occurrence does notify again.
The RTL ABI constants are maintained directly in The RTL ABI constants are maintained directly in
`rtl/ip/multimedia/dvp_define.svh`. Matching HAL offsets and masks are `rtl/ip/multimedia/dvp_define.svh`. Matching HAL offsets and masks are
maintained directly in `crt/src/hal/dvp.c`. Changes to either definition must maintained directly in `crt/src/hal/dvp.c`. Changes to either definition must
@@ -120,6 +160,20 @@ pixel sampling and its falling edge completes a line. Snapshot mode stops
after the next completed frame; continuous mode resets line and pixel counters after the next completed frame; continuous mode resets line and pixel counters
and captures the next frame. Crop coordinates are applied in the normalized and captures the next frame. Crop coordinates are applied in the normalized
frame coordinate space, and a crop must fit within the configured frame size. frame coordinate space, and a crop must fit within the configured frame size.
Snapshot completion remains stopped until a disabled configuration rearms it.
Source row width is checked against `FRAME_SIZE`, independently of crop
selection. The first complete pixel beyond the configured width reports
SIZE and ends the active frame before the column counter can wrap. HREF
falling with fewer complete pixels also reports SIZE. Compensating long and
short rows cannot form a valid frame merely because aggregate counts match.
Rows outside the crop are checked too. An incomplete final byte additionally
reports PARTIAL. These faults retain the existing sticky-error/event path;
they do not flush already accepted FIFO or DMA data. Software discards the
failed destination, drains/cancels DMA before source cleanup, and uses the
existing ABORT, W1C and acknowledged disable/enable rearm sequence.
An enabled source waiting for its first VSYNC does not treat the preceding
partial frame as captured data. ABORT/FLUSH execute only on an accepted
command handshake, not repeatedly from a retained CDC payload.
The PIO path and AXI4-Stream path are mutually exclusive at the output: The PIO path and AXI4-Stream path are mutually exclusive at the output:
`RXDATA` pops a word only while stream output is disabled. The DMA HAL uses a `RXDATA` pops a word only while stream output is disabled. The DMA HAL uses a
@@ -147,6 +201,27 @@ for one-frame DMA capture. Applications should use `rs_dvp_configure()`,
`rs_dvp_capture_dma()` rather than `rs_dvp_capture_dma()` rather than
accessing the registers directly. accessing the registers directly.
The snapshot helpers derive transfer length from the current frame/crop
geometry. `word_capacity` bounds the destination; it is not a request to fill
the entire allocation. DMA rejects odd active widths, insufficient capacity
and address overflow before starting. PIO uses `ceil(width / 2) * height`
storage words and preserves the zero-padded final halfword in each odd row.
Success requires a new completed frame, exact pixel/word and DMA counts, an
empty FIFO and all destination responses. The helper initializes CRC options
explicitly. Tiny uses channel 2/four-beat bursts; Mini retains channel 3 and
its existing maximum-burst default.
Each convenience capture rearms the existing snapshot configuration through
an acknowledged non-flushing disable/enable sequence; callers need not repeat
`rs_dvp_configure()` between successful captures. PIO refuses a busy or suspended
product-selected DMA consumer before changing stream mode, interrupts or FIFO
contents. The source must also be inactive with an empty FIFO before rearm.
Recovery drains/aborts DMA before issuing a source abort that clears FIFO
storage. A failed DMA drain is returned to the caller without clearing the
source tail. `rs_dvp_abort()` refuses a busy product-selected DMA channel and
waits for an acknowledged abort, FIFO empty and error clear. Recovery timeout
is an error, not permission to reuse the buffer or rearm capture.
## Frozen PPALite Source and Route Contract ## Frozen PPALite Source and Route Contract
The [PPALite contract](ppalite.md) owns the processed pixel formats, derived The [PPALite contract](ppalite.md) owns the processed pixel formats, derived
@@ -176,11 +251,19 @@ Before processed capture is qualified, PPALITE-P2 must close or reuse current
R2 evidence for source correctness: real SYNC/SIZE/PARTIAL detection, exact R2 evidence for source correctness: real SYNC/SIZE/PARTIAL detection, exact
statistics beyond65535 words, coherent host-side active/configuration/status, statistics beyond65535 words, coherent host-side active/configuration/status,
every repeated frame event, error epochs and snapshot stop/rearm behavior. every repeated frame event, error epochs and snapshot stop/rearm behavior.
Current `dvp_core.sv` leaves three error paths reserved and uses a16-bit word The R2-P9 candidate implements SYNC/SIZE/PARTIAL detection and a 32-bit word
counter/status payload. A downstream image/checksum cannot establish those counter transported through a 144-bit completed-frame mailbox. These source
unimplemented source guarantees. Minimal shared correctness fixes must retain guarantees still require matching validation; a downstream image/checksum
alone does not establish them. Minimal shared correctness fixes must retain
register offsets/meanings, the512 B payload FIFO and Mini compatibility. register offsets/meanings, the512 B payload FIFO and Mini compatibility.
The internal statistics layout is `{drop[31:0], words[31:0], pixels[31:0],
lines[15:0], frames[31:0]}`. The existing two-phase CDC carries the entire
144-bit snapshot; register offsets and the separate 128-bit configuration
channel are unchanged. VGA 640x480 at two bytes per pixel reports 153600
words rather than the former 16-bit wrapped value 22528. PIO counts one
additional halfword-bearing word at each odd-width row end.
Valid processed completion requires this source's full-frame/statistic/error Valid processed completion requires this source's full-frame/statistic/error
verdict, a checked non-flushing stop, processor counts and exact DMA length verdict, a checked non-flushing stop, processor counts and exact DMA length
including final memory write responses. PPALite PIPE_DONE or a last EOL is including final memory write responses. PPALite PIPE_DONE or a last EOL is
+398 -111
View File
@@ -31,72 +31,133 @@ module axi4s_dvp (
apb4_if.slave apb4, apb4_if.slave apb4,
axi4_stream_if.source rx_axis, axi4_stream_if.source rx_axis,
dvp_if.dut dvp, dvp_if.dut dvp,
output logic irq_o output logic irq_o,
output logic lifecycle_idle_o,
output logic lifecycle_reset_ready_o
// verilog_format: on // verilog_format: on
); );
localparam int DvpPayloadWidth = 42; localparam int DvpPayloadWidth = 42;
logic [ 127:0] s_cfg_sys; logic [127:0] s_cfg_sys;
logic s_cfg_v_sys; logic s_cfg_v_sys;
logic s_cfg_rdy_sys; logic s_cfg_rdy_sys;
logic [ 127:0] s_cfg_pclk_q; logic s_cfg_link_ready;
logic s_cfg_v_pclk; logic s_cfg_admit;
logic s_cfg_rdy_pclk; logic s_cfg_apply_pclk_q;
logic [ 1:0] s_cmd_sys; logic [128:0] s_cfg_packet_pclk;
logic [ 1:0] s_cmd_pclk; logic s_cfg_epoch_q, s_cfg_epoch_pclk_q, s_cfg_epoch_latched_q;
logic s_cmd_v_pclk; logic s_cfg_busy_d, s_cfg_busy_q, s_cfg_accept_sys;
logic s_cmd_rdy_pclk; logic s_cmd_epoch_q, s_cmd_epoch_pclk_q;
logic s_cmd_rdy_sys; logic s_cmd_busy_d, s_cmd_busy_q, s_cmd_accept_sys;
logic s_cmd_v_sys; logic [3:0] s_cmd_packet_pclk;
logic [ 1:0] s_cmd_core; logic s_cfg_cancel_d, s_cfg_cancel_q;
logic s_cmd_abort_sys; logic s_cfg_cancel_epoch_q;
logic s_cmd_flush_sys; logic s_cfg_cancel_apply;
logic s_ctrl_pending;
logic s_stat_seen_q;
logic s_capture_idle_pclk;
logic [5:0] s_core_err_events;
logic [5:0] s_err_pending_d, s_err_pending_q;
logic s_err_report_ready, s_err_report_valid;
logic [ 5:0] s_err_report_sys;
logic [127:0] s_cfg_pclk_q;
logic s_cfg_v_pclk;
logic s_cfg_rdy_pclk;
logic [ 1:0] s_cmd_sys;
logic [ 1:0] s_cmd_pclk;
logic s_cmd_v_pclk;
logic s_cmd_rdy_pclk;
logic s_cmd_rdy_sys;
logic s_cmd_v_sys;
logic [ 1:0] s_cmd_core;
logic s_cmd_abort_sys;
logic s_cmd_flush_sys;
logic s_pclk_inv; logic s_pclk_inv;
logic s_pclk_buf; logic s_pclk_buf;
logic s_pclk_rst_n; logic s_pclk_rst_n;
logic s_pclk_sel; logic s_pclk_sel;
logic s_pclk_falling; logic s_pclk_falling;
logic s_active; logic s_active;
logic s_stream_en; logic s_active_pclk;
logic s_fifo_src_ready; logic [ 5:0] s_err_clear;
logic s_fifo_dst_valid; logic [5:0] s_clear_pending_d, s_clear_pending_q;
logic [DvpPayloadWidth-1:0] s_fifo_dst_data; logic [5:0] s_clear_mask_q;
logic s_fifo_dst_ready; logic s_clear_busy_d, s_clear_busy_q;
logic s_fifo_src_clear; logic s_clear_epoch_q;
logic s_fifo_src_clear_busy; logic s_clear_epoch_pclk_q;
logic s_fifo_dst_clear_busy; logic s_clear_ready_sys;
logic [DvpPayloadWidth-1:0] s_core_push_data; logic s_clear_valid_pclk;
logic s_core_push_valid; logic s_clear_ready_pclk;
logic [ 31:0] s_fifo_rx_data; logic [ 6:0] s_clear_packet_pclk;
logic s_rx_pop; logic s_clear_accept_sys;
logic s_fifo_full_sys; logic s_clear_accept_pclk;
logic s_fifo_empty_sys; logic [ 5:0] s_clear_done;
logic s_frm_start_tgl; logic [ 12:0] s_stat_sys;
logic s_line_done_tgl; logic s_stat_valid_sys;
logic s_frm_done_tgl; logic s_stream_en;
logic s_err_tgl; logic s_fifo_src_ready;
logic s_frame_start_event; logic s_fifo_dst_valid;
logic s_line_done_event; logic [ DvpPayloadWidth-1:0] s_fifo_dst_data;
logic s_frame_done_event; logic s_fifo_dst_ready;
logic s_err_evt; logic s_fifo_src_clear;
logic [ 5:0] s_err_flags_pclk; logic s_fifo_src_clear_busy;
logic [ 5:0] s_err_flags_sys; logic s_fifo_dst_clear_busy;
logic [ 127:0] s_frm_stats_pclk; logic [ DvpPayloadWidth-1:0] s_core_push_data;
logic s_frm_stats_v_pclk; logic s_core_push_valid;
logic s_frm_stats_rdy_pclk; logic [ 31:0] s_fifo_rx_data;
logic [ 127:0] s_frm_stats_sys; logic s_rx_pop;
logic s_frm_stats_v_sys; logic s_fifo_full_sys;
logic s_fifo_empty_sys;
logic s_frm_start_tgl;
logic s_line_done_tgl;
logic s_frm_done_tgl;
logic s_unused_err_tgl;
logic s_frame_start_event;
logic s_line_done_event;
logic s_frame_done_event;
logic [ 2:0] s_event_rise;
logic [ 2:0] s_event_fall;
logic [ 5:0] s_err_flags_pclk;
logic [ 5:0] s_err_flags_sys;
logic [`APB4_DVP__FRAME_STATS_WIDTH-1:0] s_frm_stats_pclk;
logic s_frm_stats_v_pclk;
logic s_frm_stats_rdy_pclk;
logic [`APB4_DVP__FRAME_STATS_WIDTH-1:0] s_frm_stats_sys;
logic s_frm_stats_v_sys;
assign s_cmd_sys = {s_cmd_flush_sys, s_cmd_abort_sys}; assign s_cmd_sys = {s_cmd_flush_sys, s_cmd_abort_sys};
// Commands are consumed in the pixel domain as soon as they cross the CDC. // Commands are consumed in the pixel domain as soon as they cross the CDC.
assign s_cmd_rdy_pclk = !s_fifo_src_clear_busy; assign s_cmd_rdy_pclk = !s_fifo_src_clear_busy;
assign s_pclk_falling = s_cfg_sys[106]; assign s_pclk_falling = s_cfg_sys[106];
assign s_fifo_src_clear = s_cmd_pclk[`APB4_DVP__COMMAND_FLUSH] || assign s_fifo_src_clear = s_cmd_v_pclk && s_cmd_rdy_pclk &&
s_cmd_pclk[`APB4_DVP__COMMAND_ABORT]; (s_cmd_pclk[`APB4_DVP__COMMAND_FLUSH] ||
s_cmd_pclk[`APB4_DVP__COMMAND_ABORT]);
assign s_fifo_dst_ready = !s_fifo_dst_clear_busy && (s_stream_en ? rx_axis.tready : s_rx_pop); assign s_fifo_dst_ready = !s_fifo_dst_clear_busy && (s_stream_en ? rx_axis.tready : s_rx_pop);
assign s_fifo_rx_data = s_fifo_dst_data[31:0]; assign s_fifo_rx_data = s_fifo_dst_data[31:0];
assign s_fifo_empty_sys = !s_fifo_dst_valid; assign s_fifo_empty_sys = !s_fifo_dst_valid;
assign s_cfg_admit = !s_clear_busy_q && !(|s_clear_pending_q) && !(|s_err_clear) &&
!s_cfg_busy_q && !s_cmd_busy_q && !s_cmd_v_sys;
assign s_cfg_rdy_sys = s_cfg_admit && s_cfg_link_ready;
assign s_cfg_accept_sys = s_cfg_v_sys && s_cfg_rdy_sys;
assign s_cfg_busy_d = (s_stat_valid_sys && s_cfg_busy_q &&
s_stat_sys[10] == s_cfg_epoch_q) ? 1'b0 :
(s_cfg_accept_sys || s_cfg_busy_q);
assign s_cmd_accept_sys = s_cmd_v_sys && !s_cmd_busy_q && s_cmd_rdy_sys;
assign s_cmd_busy_d = (s_stat_valid_sys && s_cmd_busy_q && s_stat_sys[12] &&
s_stat_sys[11] == s_cmd_epoch_q) ? 1'b0 :
(s_cmd_accept_sys || s_cmd_busy_q);
assign s_cmd_pclk = s_cmd_packet_pclk[1:0];
// One config may be in flight. ABORT carries its epoch independently of
// config readiness, so an active frame can always be stopped. The cancelled
// config still retires through the normal CDC/application acknowledgement.
assign s_cfg_cancel_apply = s_cfg_cancel_q && (s_cfg_epoch_latched_q == s_cfg_cancel_epoch_q);
assign s_cfg_cancel_d = s_cmd_core[`APB4_DVP__COMMAND_ABORT] ? 1'b1 :
(s_cfg_apply_pclk_q &&
(s_cfg_epoch_latched_q != s_cfg_cancel_epoch_q)) ? 1'b0 :
s_cfg_cancel_q;
assign s_ctrl_pending = s_cfg_v_sys || s_cfg_busy_q || s_cmd_v_sys || s_cmd_busy_q ||
s_clear_busy_q || (|s_clear_pending_q) || (|s_err_clear);
tc_clk_buf u_dvp_pclk_clk_buf ( tc_clk_buf u_dvp_pclk_clk_buf (
.clk_i(dvp.pclk_i), .clk_i(dvp.pclk_i),
@@ -126,14 +187,19 @@ module axi4s_dvp (
.rst_n_i (rst_n_i), .rst_n_i (rst_n_i),
.apb4 (apb4), .apb4 (apb4),
.active_i (s_active), .active_i (s_active),
.enabled_i (s_stat_sys[7]),
.fifo_empty_i (s_fifo_empty_sys), .fifo_empty_i (s_fifo_empty_sys),
.fifo_full_i (s_fifo_full_sys), .fifo_full_i (s_fifo_full_sys),
.rx_data_i (s_fifo_rx_data), .rx_data_i (s_fifo_rx_data),
.frame_start_i (s_frame_start_event), .frame_start_i (s_frame_start_event),
.line_done_i (s_line_done_event), .line_done_i (s_line_done_event),
.frame_done_i (s_frame_done_event), .frame_done_i (s_frame_done_event),
.error_event_i (s_err_evt), .error_event_i (s_err_report_valid),
.error_events_i (s_err_report_sys),
.error_flags_i (s_err_flags_sys), .error_flags_i (s_err_flags_sys),
.error_valid_i (s_stat_valid_sys),
.error_clear_done_i (s_clear_done),
.error_clear_o (s_err_clear),
.frame_stats_i (s_frm_stats_sys), .frame_stats_i (s_frm_stats_sys),
.frame_stats_valid_i(s_frm_stats_v_sys), .frame_stats_valid_i(s_frm_stats_v_sys),
.cfg_o (s_cfg_sys), .cfg_o (s_cfg_sys),
@@ -142,38 +208,146 @@ module axi4s_dvp (
.cmd_abort_o (s_cmd_abort_sys), .cmd_abort_o (s_cmd_abort_sys),
.cmd_flush_o (s_cmd_flush_sys), .cmd_flush_o (s_cmd_flush_sys),
.cmd_valid_o (s_cmd_v_sys), .cmd_valid_o (s_cmd_v_sys),
.cmd_ready_i (s_cmd_rdy_sys), .cmd_ready_i (s_cmd_rdy_sys && !s_cmd_busy_q),
.rx_pop_o (s_rx_pop), .rx_pop_o (s_rx_pop),
.stream_enable_o (s_stream_en), .stream_enable_o (s_stream_en),
.irq_o (irq_o) .irq_o (irq_o)
); );
cdc_2phase #( cdc_2phase #(
.DATA_WIDTH(128) .DATA_WIDTH(129)
) u_dvp_config_cdc ( ) u_dvp_config_cdc (
.src_clk_i (clk_i), .src_clk_i (clk_i),
.src_rst_n_i(rst_n_i), .src_rst_n_i(rst_n_i),
.src_data_i (s_cfg_sys), .src_data_i ({!s_cfg_epoch_q, s_cfg_sys}),
.src_valid_i(s_cfg_v_sys), .src_valid_i(s_cfg_v_sys && s_cfg_admit),
.src_ready_o(s_cfg_rdy_sys), .src_ready_o(s_cfg_link_ready),
.dst_clk_i (s_pclk_sel), .dst_clk_i (s_pclk_sel),
.dst_rst_n_i(s_pclk_rst_n), .dst_rst_n_i(s_pclk_rst_n),
.dst_data_o (s_cfg_pclk_q), .dst_data_o (s_cfg_packet_pclk),
.dst_valid_o(s_cfg_v_pclk), .dst_valid_o(s_cfg_v_pclk),
.dst_ready_i(s_cfg_rdy_pclk) .dst_ready_i(s_cfg_rdy_pclk && !s_cfg_apply_pclk_q)
);
// The CDC mailbox presents data before acceptance. Freeze the applied
// configuration during capture, and notify the core after it is latched.
dffer #(
.DATA_WIDTH(128)
) u_applied_config (
.clk_i (s_pclk_sel),
.rst_n_i(s_pclk_rst_n),
.en_i (s_cfg_v_pclk && s_cfg_rdy_pclk && !s_cfg_apply_pclk_q),
.dat_i (s_cfg_packet_pclk[127:0]),
.dat_o (s_cfg_pclk_q)
);
dffr #(
.DATA_WIDTH(1)
) u_config_applied (
.clk_i (s_pclk_sel),
.rst_n_i(s_pclk_rst_n),
.dat_i (s_cfg_v_pclk && s_cfg_rdy_pclk && !s_cfg_apply_pclk_q),
.dat_o (s_cfg_apply_pclk_q)
);
dffr #(
.DATA_WIDTH(1)
) u_config_busy (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_cfg_busy_d),
.dat_o (s_cfg_busy_q)
);
dffer #(
.DATA_WIDTH(1)
) u_config_epoch (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_cfg_accept_sys),
.dat_i (!s_cfg_epoch_q),
.dat_o (s_cfg_epoch_q)
);
dffer #(
.DATA_WIDTH(1)
) u_config_epoch_latch (
.clk_i (s_pclk_sel),
.rst_n_i(s_pclk_rst_n),
.en_i (s_cfg_v_pclk && s_cfg_rdy_pclk && !s_cfg_apply_pclk_q),
.dat_i (s_cfg_packet_pclk[128]),
.dat_o (s_cfg_epoch_latched_q)
);
dffer #(
.DATA_WIDTH(1)
) u_config_epoch_applied (
.clk_i (s_pclk_sel),
.rst_n_i(s_pclk_rst_n),
.en_i (s_cfg_apply_pclk_q),
.dat_i (s_cfg_epoch_latched_q),
.dat_o (s_cfg_epoch_pclk_q)
);
dffr #(
.DATA_WIDTH(1)
) u_command_busy (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_cmd_busy_d),
.dat_o (s_cmd_busy_q)
);
// Source admission prevents a newer config until this command has drained.
// Clear the tombstone on the first different applied epoch, before the
// one-bit epoch can be reused. Repeated ABORTs of the same item cannot wrap it.
dffr #(
.DATA_WIDTH(1)
) u_config_cancel (
.clk_i (s_pclk_sel),
.rst_n_i(s_pclk_rst_n),
.dat_i (s_cfg_cancel_d),
.dat_o (s_cfg_cancel_q)
);
dffer #(
.DATA_WIDTH(1)
) u_config_cancel_epoch (
.clk_i (s_pclk_sel),
.rst_n_i(s_pclk_rst_n),
.en_i (s_cmd_core[`APB4_DVP__COMMAND_ABORT]),
.dat_i (s_cmd_packet_pclk[3]),
.dat_o (s_cfg_cancel_epoch_q)
);
dffer #(
.DATA_WIDTH(1)
) u_command_epoch (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_cmd_accept_sys),
.dat_i (!s_cmd_epoch_q),
.dat_o (s_cmd_epoch_q)
);
dffer #(
.DATA_WIDTH(1)
) u_command_epoch_applied (
.clk_i (s_pclk_sel),
.rst_n_i(s_pclk_rst_n),
.en_i (s_cmd_v_pclk && s_cmd_rdy_pclk),
.dat_i (s_cmd_packet_pclk[2]),
.dat_o (s_cmd_epoch_pclk_q)
);
dffr #(
.DATA_WIDTH(1)
) u_status_seen (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_stat_seen_q || s_stat_valid_sys),
.dat_o (s_stat_seen_q)
); );
cdc_2phase #( cdc_2phase #(
.DATA_WIDTH(2) .DATA_WIDTH(4)
) u_dvp_command_cdc ( ) u_dvp_command_cdc (
.src_clk_i (clk_i), .src_clk_i (clk_i),
.src_rst_n_i(rst_n_i), .src_rst_n_i(rst_n_i),
.src_data_i (s_cmd_sys), .src_data_i ({s_cfg_epoch_q, !s_cmd_epoch_q, s_cmd_sys}),
.src_valid_i(s_cmd_v_sys), .src_valid_i(s_cmd_v_sys && !s_cmd_busy_q),
.src_ready_o(s_cmd_rdy_sys), .src_ready_o(s_cmd_rdy_sys),
.dst_clk_i (s_pclk_sel), .dst_clk_i (s_pclk_sel),
.dst_rst_n_i(s_pclk_rst_n), .dst_rst_n_i(s_pclk_rst_n),
.dst_data_o (s_cmd_pclk), .dst_data_o (s_cmd_packet_pclk),
.dst_valid_o(s_cmd_v_pclk), .dst_valid_o(s_cmd_v_pclk),
.dst_ready_i(s_cmd_rdy_pclk) .dst_ready_i(s_cmd_rdy_pclk)
); );
@@ -196,7 +370,128 @@ module axi4s_dvp (
.dst_valid_o (s_fifo_dst_valid), .dst_valid_o (s_fifo_dst_valid),
.dst_ready_i (s_fifo_dst_ready) .dst_ready_i (s_fifo_dst_ready)
); );
assign s_cmd_core = s_cmd_v_pclk ? s_cmd_pclk : 2'b00; assign s_cmd_core = (s_cmd_v_pclk && s_cmd_rdy_pclk) ? s_cmd_pclk : 2'b00;
// W1C is acknowledged by a coherent post-clear status packet, not by
// sampling the old asynchronously synchronized sticky error level.
assign s_clear_accept_sys = !s_clear_busy_q && (|s_clear_pending_q) && s_clear_ready_sys;
assign s_clear_ready_pclk = !s_fifo_src_clear_busy && !s_cmd_v_pclk;
assign s_clear_accept_pclk = s_clear_valid_pclk && s_clear_ready_pclk;
assign s_clear_done = (s_stat_valid_sys && s_clear_busy_q &&
s_stat_sys[8] == s_clear_epoch_q) ? s_clear_mask_q : 6'd0;
assign s_clear_pending_d = (s_clear_accept_sys ? 6'd0 : s_clear_pending_q) | s_err_clear;
assign s_err_flags_sys = s_stat_sys[5:0];
assign s_active = s_stat_sys[6];
assign s_clear_busy_d = (|s_clear_done) ? 1'b0 : (s_clear_accept_sys || s_clear_busy_q);
dffr #(
.DATA_WIDTH(6)
) u_clear_pending (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_clear_pending_d),
.dat_o (s_clear_pending_q)
);
dffer #(
.DATA_WIDTH(6)
) u_clear_mask (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_clear_accept_sys),
.dat_i (s_clear_pending_q),
.dat_o (s_clear_mask_q)
);
dffr #(
.DATA_WIDTH(1)
) u_clear_busy (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_clear_busy_d),
.dat_o (s_clear_busy_q)
);
dffer #(
.DATA_WIDTH(1)
) u_clear_epoch (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.en_i (s_clear_accept_sys),
.dat_i (!s_clear_epoch_q),
.dat_o (s_clear_epoch_q)
);
dffer #(
.DATA_WIDTH(1)
) u_clear_pixel_epoch (
.clk_i (s_pclk_sel),
.rst_n_i(s_pclk_rst_n),
.en_i (s_clear_accept_pclk),
.dat_i (s_clear_packet_pclk[6]),
.dat_o (s_clear_epoch_pclk_q)
);
cdc_2phase #(
.DATA_WIDTH(7)
) u_error_clear_cdc (
.src_clk_i (clk_i),
.src_rst_n_i(rst_n_i),
.src_data_i ({!s_clear_epoch_q, s_clear_pending_q}),
.src_valid_i(!s_clear_busy_q && (|s_clear_pending_q)),
.src_ready_o(s_clear_ready_sys),
.dst_clk_i (s_pclk_sel),
.dst_rst_n_i(s_pclk_rst_n),
.dst_data_o (s_clear_packet_pclk),
.dst_valid_o(s_clear_valid_pclk),
.dst_ready_i(s_clear_ready_pclk)
);
/* verilator lint_off PINCONNECTEMPTY */
cdc_2phase #(
.DATA_WIDTH(13)
) u_status_cdc (
.src_clk_i(s_pclk_sel),
.src_rst_n_i(s_pclk_rst_n),
.src_data_i({
!s_fifo_src_clear_busy,
s_cmd_epoch_pclk_q,
s_cfg_epoch_pclk_q,
s_capture_idle_pclk && !s_fifo_src_clear_busy && !(|s_err_pending_q) && s_err_report_ready,
s_clear_epoch_pclk_q,
s_cfg_pclk_q[96],
s_active_pclk,
s_err_flags_pclk
}),
.src_valid_i(1'b1),
.src_ready_o(), // Polling status producer has no payload to retire.
.dst_clk_i(clk_i),
.dst_rst_n_i(rst_n_i),
.dst_data_o(s_stat_sys),
.dst_valid_o(s_stat_valid_sys),
.dst_ready_i(1'b1)
);
/* verilator lint_on PINCONNECTEMPTY */
// Coalesce causes while a notification is in flight. A new cause on the
// acceptance edge remains pending for the next transfer, even for the same
// bit. Unlike a sampled toggle, an even number of events cannot disappear.
assign s_err_pending_d = (s_err_report_ready ? 6'd0 : s_err_pending_q) | s_core_err_events;
dffr #(
.DATA_WIDTH(6)
) u_error_pending (
.clk_i (s_pclk_sel),
.rst_n_i(s_pclk_rst_n),
.dat_i (s_err_pending_d),
.dat_o (s_err_pending_q)
);
cdc_2phase #(
.DATA_WIDTH(6)
) u_error_events_cdc (
.src_clk_i (s_pclk_sel),
.src_rst_n_i(s_pclk_rst_n),
.src_data_i (s_err_pending_q),
.src_valid_i(|s_err_pending_q),
.src_ready_o(s_err_report_ready),
.dst_clk_i (clk_i),
.dst_rst_n_i(rst_n_i),
.dst_data_o (s_err_report_sys),
.dst_valid_o(s_err_report_valid),
.dst_ready_i(1'b1)
);
cdc_sync #( cdc_sync #(
.STAGE (2), .STAGE (2),
@@ -207,61 +502,46 @@ module axi4s_dvp (
.dat_i (!s_fifo_src_ready), .dat_i (!s_fifo_src_ready),
.dat_o (s_fifo_full_sys) .dat_o (s_fifo_full_sys)
); );
cdc_sync #(
.STAGE (2),
.DATA_WIDTH(6)
) u_error_flags_sync (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_err_flags_pclk),
.dat_o (s_err_flags_sys)
);
// Each source event toggles its level. Both synchronized edges represent
// events; consuming only rising edges loses every second frame/line/error.
assign {s_frame_done_event, s_line_done_event, s_frame_start_event} =
s_event_rise[2:0] | s_event_fall[2:0];
/* verilator lint_off PINCONNECTEMPTY */ /* verilator lint_off PINCONNECTEMPTY */
edge_det #( edge_det #(
.STAGE(2) .STAGE(2)
) u_frame_start_event ( ) u_frame_start_event (
.clk_i(clk_i), .clk_i (clk_i),
.rst_n_i(rst_n_i), .rst_n_i(rst_n_i),
.dat_i(s_frm_start_tgl), .dat_i (s_frm_start_tgl),
.dat_o(), // Synchronized level is intentionally unused; only the rising edge is consumed. .dat_o (), // Only the synchronized edge pulses are consumed.
.re_o(s_frame_start_event), .re_o (s_event_rise[0]),
.fe_o() // Falling edge is intentionally unused. .fe_o (s_event_fall[0])
); );
edge_det #( edge_det #(
.STAGE(2) .STAGE(2)
) u_line_done_event ( ) u_line_done_event (
.clk_i(clk_i), .clk_i (clk_i),
.rst_n_i(rst_n_i), .rst_n_i(rst_n_i),
.dat_i(s_line_done_tgl), .dat_i (s_line_done_tgl),
.dat_o(), // Synchronized level is intentionally unused; only the rising edge is consumed. .dat_o (), // Only the synchronized edge pulses are consumed.
.re_o(s_line_done_event), .re_o (s_event_rise[1]),
.fe_o() // Falling edge is intentionally unused. .fe_o (s_event_fall[1])
); );
edge_det #( edge_det #(
.STAGE(2) .STAGE(2)
) u_frame_done_event ( ) u_frame_done_event (
.clk_i(clk_i), .clk_i (clk_i),
.rst_n_i(rst_n_i), .rst_n_i(rst_n_i),
.dat_i(s_frm_done_tgl), .dat_i (s_frm_done_tgl),
.dat_o(), // Synchronized level is intentionally unused; only the rising edge is consumed. .dat_o (), // Only the synchronized edge pulses are consumed.
.re_o(s_frame_done_event), .re_o (s_event_rise[2]),
.fe_o() // Falling edge is intentionally unused. .fe_o (s_event_fall[2])
);
edge_det #(
.STAGE(2)
) u_error_event (
.clk_i(clk_i),
.rst_n_i(rst_n_i),
.dat_i(s_err_tgl),
.dat_o(), // Synchronized level is intentionally unused; only the rising edge is consumed.
.re_o(s_err_evt),
.fe_o() // Falling edge is intentionally unused.
); );
/* verilator lint_on PINCONNECTEMPTY */ /* verilator lint_on PINCONNECTEMPTY */
cdc_2phase #( cdc_2phase #(
.DATA_WIDTH(128) .DATA_WIDTH(`APB4_DVP__FRAME_STATS_WIDTH)
) u_dvp_stats_cdc ( ) u_dvp_stats_cdc (
.src_clk_i (s_pclk_sel), .src_clk_i (s_pclk_sel),
.src_rst_n_i(s_pclk_rst_n), .src_rst_n_i(s_pclk_rst_n),
@@ -275,31 +555,38 @@ module axi4s_dvp (
.dst_ready_i(1'b1) .dst_ready_i(1'b1)
); );
assign rx_axis.tdata = s_fifo_dst_data[31:0]; assign rx_axis.tdata = s_fifo_dst_data[31:0];
assign rx_axis.tkeep = s_fifo_dst_data[39:36]; assign rx_axis.tkeep = s_fifo_dst_data[39:36];
assign rx_axis.tstrb = s_fifo_dst_data[35:32]; assign rx_axis.tstrb = s_fifo_dst_data[35:32];
assign rx_axis.tlast = s_fifo_dst_data[40]; assign rx_axis.tlast = s_fifo_dst_data[40];
assign rx_axis.tid = '0; assign rx_axis.tid = '0;
assign rx_axis.tdest = '0; assign rx_axis.tdest = '0;
assign rx_axis.tuser = s_fifo_dst_data[41]; assign rx_axis.tuser = s_fifo_dst_data[41];
assign rx_axis.tvalid = s_stream_en && s_fifo_dst_valid; assign rx_axis.tvalid = s_stream_en && s_fifo_dst_valid;
assign lifecycle_idle_o = !s_ctrl_pending && (!s_stat_seen_q || s_stat_sys[9]) &&
s_fifo_empty_sys && !s_fifo_dst_clear_busy && !rx_axis.tvalid;
assign lifecycle_reset_ready_o = s_stat_seen_q && lifecycle_idle_o && s_stat_sys[12];
dvp_core u_dvp_core ( dvp_core u_dvp_core (
.clk_i (s_pclk_sel), .clk_i (s_pclk_sel),
.rst_n_i (s_pclk_rst_n), .rst_n_i (s_pclk_rst_n),
.cfg_i (s_cfg_pclk_q), .cfg_i (s_cfg_pclk_q),
.cfg_valid_i (s_cfg_v_pclk), .cfg_valid_i (s_cfg_apply_pclk_q),
.cfg_cancel_i (s_cfg_cancel_apply),
.cmd_i (s_cmd_core), .cmd_i (s_cmd_core),
.error_clear_i (s_clear_accept_pclk ? s_clear_packet_pclk[5:0] : 6'd0),
.cfg_ready_o (s_cfg_rdy_pclk), .cfg_ready_o (s_cfg_rdy_pclk),
.push_ready_i (s_fifo_src_ready), .push_ready_i (s_fifo_src_ready),
.push_valid_o (s_core_push_valid), .push_valid_o (s_core_push_valid),
.push_data_o (s_core_push_data), .push_data_o (s_core_push_data),
.active_o (s_active), .active_o (s_active_pclk),
.capture_idle_o (s_capture_idle_pclk),
.frame_start_toggle_o(s_frm_start_tgl), .frame_start_toggle_o(s_frm_start_tgl),
.line_done_toggle_o (s_line_done_tgl), .line_done_toggle_o (s_line_done_tgl),
.frame_done_toggle_o (s_frm_done_tgl), .frame_done_toggle_o (s_frm_done_tgl),
.error_toggle_o (s_err_tgl), .error_toggle_o (s_unused_err_tgl),
.error_flags_o (s_err_flags_pclk), .error_flags_o (s_err_flags_pclk),
.error_events_o (s_core_err_events),
.frame_stats_o (s_frm_stats_pclk), .frame_stats_o (s_frm_stats_pclk),
.frame_stats_valid_o (s_frm_stats_v_pclk), .frame_stats_valid_o (s_frm_stats_v_pclk),
.frame_stats_ready_i (s_frm_stats_rdy_pclk), .frame_stats_ready_i (s_frm_stats_rdy_pclk),
+89 -25
View File
@@ -18,18 +18,22 @@ module dvp_core (
input logic [127:0] cfg_i, input logic [127:0] cfg_i,
/* verilator lint_on UNUSEDSIGNAL */ /* verilator lint_on UNUSEDSIGNAL */
input logic cfg_valid_i, input logic cfg_valid_i,
input logic cfg_cancel_i,
input logic [ 1:0] cmd_i, input logic [ 1:0] cmd_i,
input logic [ 5:0] error_clear_i,
output logic cfg_ready_o, output logic cfg_ready_o,
input logic push_ready_i, input logic push_ready_i,
output logic push_valid_o, output logic push_valid_o,
output logic [ 41:0] push_data_o, output logic [ 41:0] push_data_o,
output logic active_o, output logic active_o,
output logic capture_idle_o,
output logic frame_start_toggle_o, output logic frame_start_toggle_o,
output logic line_done_toggle_o, output logic line_done_toggle_o,
output logic frame_done_toggle_o, output logic frame_done_toggle_o,
output logic error_toggle_o, output logic error_toggle_o,
output logic [ 5:0] error_flags_o, output logic [ 5:0] error_flags_o,
output logic [127:0] frame_stats_o, output logic [ 5:0] error_events_o,
output logic [`APB4_DVP__FRAME_STATS_WIDTH-1:0] frame_stats_o,
output logic frame_stats_valid_o, output logic frame_stats_valid_o,
input logic frame_stats_ready_i, input logic frame_stats_ready_i,
input logic href_i, input logic href_i,
@@ -63,13 +67,15 @@ module dvp_core (
logic [15:0] s_word_q; logic [15:0] s_word_q;
logic [31:0] s_frm_seq_q; logic [31:0] s_frm_seq_q;
logic [31:0] s_pixel_total_q; logic [31:0] s_pixel_total_q;
logic [15:0] s_word_total_q; logic [31:0] s_word_total_q;
logic [31:0] s_drop_count_q; logic [31:0] s_drop_count_q;
logic [ 5:0] s_err_flags_q; logic [ 5:0] s_err_flags_q;
logic s_out_valid_q; logic s_out_valid_q;
logic [41:0] s_out_data_q; logic [41:0] s_out_data_q;
logic s_stat_pending_q; logic s_stat_pending_q;
logic s_first_word_q; logic s_first_word_q;
logic s_snapshot_done_q;
logic s_cfg_en_q;
logic s_vsync_active; logic s_vsync_active;
logic s_href_active; logic s_href_active;
@@ -99,10 +105,12 @@ module dvp_core (
(s_byte_swap ? {dat_i, s_first_byte_q} : (s_byte_swap ? {dat_i, s_first_byte_q} :
{s_first_byte_q, dat_i}); {s_first_byte_q, dat_i});
assign cfg_ready_o = !s_stat_pending_q; assign cfg_ready_o = !s_stat_pending_q && !s_frm_active_q;
assign push_valid_o = s_out_valid_q; assign push_valid_o = s_out_valid_q;
assign push_data_o = s_out_data_q; assign push_data_o = s_out_data_q;
assign active_o = s_frm_active_q; assign active_o = s_frm_active_q;
assign capture_idle_o = !s_frm_active_q && (!s_en || s_snapshot_done_q) &&
!s_out_valid_q && !s_stat_pending_q && !frame_stats_valid_o;
always_comb begin always_comb begin
s_en = cfg_i[96]; s_en = cfg_i[96];
@@ -141,6 +149,9 @@ module dvp_core (
s_out_data_q <= '0; s_out_data_q <= '0;
s_stat_pending_q <= 1'b0; s_stat_pending_q <= 1'b0;
s_first_word_q <= 1'b0; s_first_word_q <= 1'b0;
s_snapshot_done_q <= 1'b0;
s_cfg_en_q <= 1'b0;
error_events_o <= '0;
frame_start_toggle_o <= 1'b0; frame_start_toggle_o <= 1'b0;
line_done_toggle_o <= 1'b0; line_done_toggle_o <= 1'b0;
frame_done_toggle_o <= 1'b0; frame_done_toggle_o <= 1'b0;
@@ -148,6 +159,9 @@ module dvp_core (
frame_stats_o <= '0; frame_stats_o <= '0;
frame_stats_valid_o <= 1'b0; frame_stats_valid_o <= 1'b0;
end else begin end else begin
// New errors below win over a simultaneous acknowledged W1C.
s_err_flags_q <= s_err_flags_q & ~error_clear_i;
error_events_o <= '0;
s_vsync_q <= s_vsync_active; s_vsync_q <= s_vsync_active;
s_href_q <= s_href_active; s_href_q <= s_href_active;
frame_stats_valid_o <= 1'b0; frame_stats_valid_o <= 1'b0;
@@ -158,18 +172,31 @@ module dvp_core (
frame_stats_valid_o <= 1'b1; frame_stats_valid_o <= 1'b1;
end end
if (cfg_valid_i && !s_frm_active_q) begin // A pre-ABORT config can retire after the abort pulse. It must not
if ((s_frm_width == 0) || (s_frm_height == 0) || (s_format > 2'd1) || // undo the stopped state or masquerade as a subsequent rearm.
(s_crop_en && ((s_crop_width == 0) || (s_crop_height == 0)))) begin if (cfg_valid_i && !cfg_cancel_i && !s_frm_active_q) begin
s_err_flags_q[`APB4_DVP__ERROR_CONFIG] <= 1'b1; s_cfg_en_q <= s_en;
error_toggle_o <= ~error_toggle_o; if (!s_en || !s_cfg_en_q) s_snapshot_done_q <= 1'b0;
if (s_en && ((s_frm_width == 0) || (s_frm_height == 0) || (s_format > 2'd1) ||
(s_crop_en && ((s_crop_width == 0) || (s_crop_height == 0))))) begin
s_err_flags_q[`APB4_DVP__ERROR_CONFIG] <= 1'b1;
error_events_o[`APB4_DVP__ERROR_CONFIG] <= 1'b1;
error_toggle_o <= ~error_toggle_o;
end
if (!s_en) begin
s_frm_active_q <= 1'b0;
s_snapshot_done_q <= 1'b0;
end end
if (!s_en) s_frm_active_q <= 1'b0;
end end
if (cmd_i[`APB4_DVP__COMMAND_ABORT]) begin if (cmd_i[`APB4_DVP__COMMAND_ABORT]) begin
s_frm_active_q <= 1'b0; s_frm_active_q <= 1'b0;
s_err_flags_q[`APB4_DVP__ERROR_ABORT] <= 1'b1; s_snapshot_done_q <= 1'b1;
error_toggle_o <= ~error_toggle_o; s_out_valid_q <= 1'b0;
s_byte_phase_q <= 1'b0;
s_word_phase_q <= 1'b0;
s_err_flags_q[`APB4_DVP__ERROR_ABORT] <= 1'b1;
error_events_o[`APB4_DVP__ERROR_ABORT] <= 1'b1;
error_toggle_o <= ~error_toggle_o;
end end
if (cmd_i[`APB4_DVP__COMMAND_FLUSH]) begin if (cmd_i[`APB4_DVP__COMMAND_FLUSH]) begin
s_out_valid_q <= 1'b0; s_out_valid_q <= 1'b0;
@@ -177,8 +204,16 @@ module dvp_core (
s_word_phase_q <= 1'b0; s_word_phase_q <= 1'b0;
end end
if (s_en && s_vsync_rise) begin if (s_en && s_vsync_rise && !s_snapshot_done_q && !(|cmd_i)) begin
if (s_frm_active_q) begin if (s_frm_active_q) begin
if ((s_line_q != s_frm_height) ||
(s_pixel_total_q != (s_crop_en ?
((32'(s_crop_width)) * (32'(s_crop_height))) :
((32'(s_frm_width)) * (32'(s_frm_height)))))) begin
s_err_flags_q[`APB4_DVP__ERROR_SIZE] <= 1'b1;
error_events_o[`APB4_DVP__ERROR_SIZE] <= 1'b1;
error_toggle_o <= ~error_toggle_o;
end
frame_done_toggle_o <= ~frame_done_toggle_o; frame_done_toggle_o <= ~frame_done_toggle_o;
frame_stats_o <= { frame_stats_o <= {
s_drop_count_q, s_word_total_q, s_pixel_total_q, s_line_q, s_frm_seq_q + 1'b1 s_drop_count_q, s_word_total_q, s_pixel_total_q, s_line_q, s_frm_seq_q + 1'b1
@@ -186,7 +221,8 @@ module dvp_core (
s_stat_pending_q <= 1'b1; s_stat_pending_q <= 1'b1;
s_frm_seq_q <= s_frm_seq_q + 1'b1; s_frm_seq_q <= s_frm_seq_q + 1'b1;
if (s_snap) begin if (s_snap) begin
s_frm_active_q <= 1'b0; s_frm_active_q <= 1'b0;
s_snapshot_done_q <= 1'b1;
end else begin end else begin
s_line_q <= '0; s_line_q <= '0;
s_pixel_q <= '0; s_pixel_q <= '0;
@@ -204,10 +240,24 @@ module dvp_core (
end end
end end
if (s_frm_active_q && s_href_active) begin if (s_frm_active_q && s_href_active && s_vsync_active) begin
s_err_flags_q[`APB4_DVP__ERROR_SYNC] <= 1'b1;
error_events_o[`APB4_DVP__ERROR_SYNC] <= 1'b1;
error_toggle_o <= ~error_toggle_o;
end
if (s_frm_active_q && s_href_active && !(|cmd_i)) begin
if (!s_byte_phase_q) begin if (!s_byte_phase_q) begin
s_first_byte_q <= dat_i; s_first_byte_q <= dat_i;
s_byte_phase_q <= 1'b1; s_byte_phase_q <= 1'b1;
end else if (s_pixel_q >= s_frm_width) begin
// Count source pixels, including those outside the crop. Stop at
// the first extra pixel, before the 16-bit column can wrap.
s_err_flags_q[`APB4_DVP__ERROR_SIZE] <= 1'b1;
error_events_o[`APB4_DVP__ERROR_SIZE] <= 1'b1;
error_toggle_o <= ~error_toggle_o;
s_frm_active_q <= 1'b0;
s_byte_phase_q <= 1'b0;
end else begin end else begin
if (s_crop_line && s_crop_pixel) begin if (s_crop_line && s_crop_pixel) begin
if (!s_word_phase_q) begin if (!s_word_phase_q) begin
@@ -222,10 +272,11 @@ module dvp_core (
s_word_phase_q <= 1'b0; s_word_phase_q <= 1'b0;
s_word_total_q <= s_word_total_q + 1'b1; s_word_total_q <= s_word_total_q + 1'b1;
end else begin end else begin
s_err_flags_q[`APB4_DVP__ERROR_OVERFLOW] <= 1'b1; s_err_flags_q[`APB4_DVP__ERROR_OVERFLOW] <= 1'b1;
s_drop_count_q <= s_drop_count_q + 1'b1; error_events_o[`APB4_DVP__ERROR_OVERFLOW] <= 1'b1;
error_toggle_o <= ~error_toggle_o; s_drop_count_q <= s_drop_count_q + 1'b1;
s_frm_active_q <= 1'b0; error_toggle_o <= ~error_toggle_o;
s_frm_active_q <= 1'b0;
end end
s_pixel_total_q <= s_pixel_total_q + 1'b1; s_pixel_total_q <= s_pixel_total_q + 1'b1;
end end
@@ -234,11 +285,24 @@ module dvp_core (
end end
end end
if (s_frm_active_q && s_href_fall) begin if (s_frm_active_q && s_href_fall && !(|cmd_i)) begin
line_done_toggle_o <= ~line_done_toggle_o; line_done_toggle_o <= ~line_done_toggle_o;
s_line_q <= s_line_q + 1'b1; s_line_q <= s_line_q + 1'b1;
s_pixel_q <= '0; s_pixel_q <= '0;
s_byte_phase_q <= 1'b0; s_byte_phase_q <= 1'b0;
// Aggregate frame totals cannot detect compensating long/short rows.
if (s_pixel_q != s_frm_width) begin
s_err_flags_q[`APB4_DVP__ERROR_SIZE] <= 1'b1;
error_events_o[`APB4_DVP__ERROR_SIZE] <= 1'b1;
error_toggle_o <= ~error_toggle_o;
s_frm_active_q <= 1'b0;
end
if (s_byte_phase_q) begin
s_err_flags_q[`APB4_DVP__ERROR_PARTIAL] <= 1'b1;
error_events_o[`APB4_DVP__ERROR_PARTIAL] <= 1'b1;
error_toggle_o <= ~error_toggle_o;
s_frm_active_q <= 1'b0;
end
if (s_word_phase_q && (!s_out_valid_q || push_ready_i)) begin if (s_word_phase_q && (!s_out_valid_q || push_ready_i)) begin
s_out_data_q <= {s_first_word_q, 1'b1, 4'b0011, 4'b0011, 16'd0, s_word_q[15:0]}; s_out_data_q <= {s_first_word_q, 1'b1, 4'b0011, 4'b0011, 16'd0, s_word_q[15:0]};
s_out_valid_q <= 1'b1; s_out_valid_q <= 1'b1;
@@ -246,14 +310,14 @@ module dvp_core (
s_word_phase_q <= 1'b0; s_word_phase_q <= 1'b0;
s_word_total_q <= s_word_total_q + 1'b1; s_word_total_q <= s_word_total_q + 1'b1;
end else if (s_word_phase_q) begin end else if (s_word_phase_q) begin
s_err_flags_q[`APB4_DVP__ERROR_OVERFLOW] <= 1'b1; s_err_flags_q[`APB4_DVP__ERROR_OVERFLOW] <= 1'b1;
error_toggle_o <= ~error_toggle_o; error_events_o[`APB4_DVP__ERROR_OVERFLOW] <= 1'b1;
s_frm_active_q <= 1'b0; error_toggle_o <= ~error_toggle_o;
s_frm_active_q <= 1'b0;
end end
end end
end end
end end
// SYNC, SIZE and PARTIAL are reserved until their capture paths are implemented. assign error_flags_o = s_err_flags_q;
assign error_flags_o = {s_err_flags_q[5:4], 3'b000, s_err_flags_q[0]};
endmodule endmodule
+8
View File
@@ -11,6 +11,10 @@
`ifndef APB4_DVP_DEFINE_SVH `ifndef APB4_DVP_DEFINE_SVH
`define APB4_DVP_DEFINE_SVH `define APB4_DVP_DEFINE_SVH
// Internal completed-frame mailbox; configuration remains 128 bits.
// {drop[31:0], words[31:0], pixels[31:0], lines[15:0], frames[31:0]}.
`define APB4_DVP__FRAME_STATS_WIDTH 144
// verilog_format: off -- preserve reviewed column alignment // verilog_format: off -- preserve reviewed column alignment
`define APB4_DVP_CTRL 12'h000 `define APB4_DVP_CTRL 12'h000
`define APB4_DVP_RXDATA 12'h004 `define APB4_DVP_RXDATA 12'h004
@@ -39,6 +43,10 @@
`define APB4_DVP__CTRL_SNAPSHOT 1 `define APB4_DVP__CTRL_SNAPSHOT 1
`define APB4_DVP__CTRL_CROP_ENABLE 2 `define APB4_DVP__CTRL_CROP_ENABLE 2
`define APB4_DVP__STREAM_ENABLE 0 `define APB4_DVP__STREAM_ENABLE 0
`define APB4_DVP__STATUS_ACTIVE 1
`define APB4_DVP__STATUS_ENABLE 0
`define APB4_DVP__STATUS_FIFO_EMPTY 2
`define APB4_DVP__STATUS_ERROR 5
`define APB4_DVP__FORMAT_SELECT 1:0 `define APB4_DVP__FORMAT_SELECT 1:0
`define APB4_DVP__FORMAT_BYTE_SWAP 2 `define APB4_DVP__FORMAT_BYTE_SWAP 2
`define APB4_DVP__FORMAT_PIXEL_SWAP 3 `define APB4_DVP__FORMAT_PIXEL_SWAP 3
+34 -16
View File
@@ -16,6 +16,7 @@ module dvp_reg (
input logic rst_n_i, input logic rst_n_i,
apb4_if.slave apb4, apb4_if.slave apb4,
input logic active_i, input logic active_i,
input logic enabled_i,
input logic fifo_empty_i, input logic fifo_empty_i,
input logic fifo_full_i, input logic fifo_full_i,
input logic [31:0] rx_data_i, input logic [31:0] rx_data_i,
@@ -23,8 +24,12 @@ module dvp_reg (
input logic line_done_i, input logic line_done_i,
input logic frame_done_i, input logic frame_done_i,
input logic error_event_i, input logic error_event_i,
input logic [ 5:0] error_events_i,
input logic [ 5:0] error_flags_i, input logic [ 5:0] error_flags_i,
input logic [127:0] frame_stats_i, input logic error_valid_i,
input logic [ 5:0] error_clear_done_i,
output logic [ 5:0] error_clear_o,
input logic [`APB4_DVP__FRAME_STATS_WIDTH-1:0] frame_stats_i,
input logic frame_stats_valid_i, input logic frame_stats_valid_i,
output logic [127:0] cfg_o, output logic [127:0] cfg_o,
output logic cfg_valid_o, output logic cfg_valid_o,
@@ -69,6 +74,7 @@ module dvp_reg (
logic [31:0] s_cfg_seq_d, s_cfg_seq_q; logic [31:0] s_cfg_seq_d, s_cfg_seq_q;
logic [31:0] s_cfg_sent_q; logic [31:0] s_cfg_sent_q;
logic [ 1:0] s_cmd_q; logic [ 1:0] s_cmd_q;
logic s_abort_accept;
function automatic logic [31:0] merge_wstrb( function automatic logic [31:0] merge_wstrb(
input logic [31:0] current_i, input logic [31:0] value_i, input logic [3:0] strobe_i); input logic [31:0] current_i, input logic [31:0] value_i, input logic [3:0] strobe_i);
@@ -107,8 +113,14 @@ module dvp_reg (
cfg_o[106:104] = s_sync_q[2:0]; cfg_o[106:104] = s_sync_q[2:0];
end end
assign cfg_valid_o = s_cfg_seq_q != s_cfg_sent_q; assign cfg_valid_o = s_cfg_seq_q != s_cfg_sent_q;
assign rx_pop_o = s_accept && !s_write && !stream_enable_o && (s_offset == `APB4_DVP_RXDATA); assign s_abort_accept = s_accept && s_write && !s_access_err &&
assign irq_o = |(s_intr_stat_q & s_intr_en_q); (s_offset == `APB4_DVP_COMMAND) &&
apb4.pwdata[`APB4_DVP__COMMAND_ABORT];
assign rx_pop_o = s_accept && !s_write && !stream_enable_o && (s_offset == `APB4_DVP_RXDATA);
assign irq_o = |(s_intr_stat_q & s_intr_en_q);
assign error_clear_o = (s_accept && s_write && !s_access_err &&
s_offset == `APB4_DVP_ERROR_STATUS && apb4.pstrb[0]) ?
apb4.pwdata[5:0] : 6'd0;
always_comb begin always_comb begin
s_access_err = !s_aligned; s_access_err = !s_aligned;
@@ -119,7 +131,7 @@ module dvp_reg (
`APB4_DVP_RXDATA: s_rdata_d = rx_data_i; `APB4_DVP_RXDATA: s_rdata_d = rx_data_i;
`APB4_DVP_STATUS: `APB4_DVP_STATUS:
s_rdata_d = { s_rdata_d = {
26'd0, s_err_q != 0, stream_enable_o, !fifo_full_i, fifo_empty_i, active_i, s_ctrl_q[0] 26'd0, s_err_q != 0, stream_enable_o, !fifo_full_i, fifo_empty_i, active_i, enabled_i
}; };
`APB4_DVP_STREAM_CTRL: s_rdata_d = s_stream_q; `APB4_DVP_STREAM_CTRL: s_rdata_d = s_stream_q;
`APB4_DVP_FORMAT: s_rdata_d = s_format_q; `APB4_DVP_FORMAT: s_rdata_d = s_format_q;
@@ -284,31 +296,37 @@ module dvp_reg (
s_word_cnt_q <= '0; s_word_cnt_q <= '0;
s_drop_cnt_q <= '0; s_drop_cnt_q <= '0;
end else begin end else begin
if (cfg_valid_o && cfg_ready_i) s_cfg_sent_q <= s_cfg_seq_q; // ABORT consumes any older configuration still waiting at the source.
// Requested register readback is unchanged; a later write is a new item.
if ((cfg_valid_o && cfg_ready_i) || s_abort_accept) s_cfg_sent_q <= s_cfg_seq_q;
if (cmd_valid_o && cmd_ready_i) s_cmd_q <= '0; if (cmd_valid_o && cmd_ready_i) s_cmd_q <= '0;
if (s_accept && s_write && !s_access_err && (s_offset == `APB4_DVP_COMMAND)) begin if (s_accept && s_write && !s_access_err && (s_offset == `APB4_DVP_COMMAND)) begin
s_cmd_q <= s_cmd_q | apb4.pwdata[1:0]; s_cmd_q <= s_cmd_q | apb4.pwdata[1:0];
end end
s_err_q <= (s_err_q | error_flags_i) & // Keep errors visible until the pixel-domain clear has completed. A
~((s_accept && s_write && (s_offset == `APB4_DVP_ERROR_STATUS)) // coherent post-clear report can set a newly arriving error immediately.
? apb4.pwdata[5:0] s_err_q <= (s_err_q & ~error_clear_done_i) | (error_valid_i ? error_flags_i : 6'd0);
: 6'd0); if (s_accept && s_write && (s_offset == `APB4_DVP_INTR_STATE) && apb4.pstrb[0])
s_intr_stat_q <= s_intr_stat_q & ~apb4.pwdata[6:0];
if (s_accept && s_write && (s_offset == `APB4_DVP_INTR_TEST) && apb4.pstrb[0])
s_intr_stat_q <= s_intr_stat_q | apb4.pwdata[6:0];
if (frame_start_i) s_intr_stat_q[`APB4_DVP__INTR_FRAME_START] <= 1'b1; if (frame_start_i) s_intr_stat_q[`APB4_DVP__INTR_FRAME_START] <= 1'b1;
if (line_done_i) s_intr_stat_q[`APB4_DVP__INTR_LINE_DONE] <= 1'b1; if (line_done_i) s_intr_stat_q[`APB4_DVP__INTR_LINE_DONE] <= 1'b1;
if (frame_done_i) s_intr_stat_q[`APB4_DVP__INTR_FRAME_DONE] <= 1'b1; if (frame_done_i) s_intr_stat_q[`APB4_DVP__INTR_FRAME_DONE] <= 1'b1;
if (error_event_i) s_intr_stat_q[`APB4_DVP__INTR_OVERFLOW] <= 1'b1; if (error_event_i) begin
if (s_accept && s_write && (s_offset == `APB4_DVP_INTR_STATE)) if (error_events_i[0]) s_intr_stat_q[`APB4_DVP__INTR_OVERFLOW] <= 1'b1;
s_intr_stat_q <= s_intr_stat_q & ~apb4.pwdata[6:0]; if (|(error_events_i & 6'b001110)) s_intr_stat_q[`APB4_DVP__INTR_SYNC_ERROR] <= 1'b1;
if (error_events_i[4]) s_intr_stat_q[`APB4_DVP__INTR_CONFIG_ERROR] <= 1'b1;
if (error_events_i[5]) s_intr_stat_q[`APB4_DVP__INTR_ABORTED] <= 1'b1;
end
if (s_accept && s_write && (s_offset == `APB4_DVP_INTR_ENABLE)) if (s_accept && s_write && (s_offset == `APB4_DVP_INTR_ENABLE))
s_intr_en_q <= apb4.pwdata[6:0]; s_intr_en_q <= apb4.pwdata[6:0];
if (s_accept && s_write && (s_offset == `APB4_DVP_INTR_TEST))
s_intr_stat_q <= s_intr_stat_q | apb4.pwdata[6:0];
if (frame_stats_valid_i) begin if (frame_stats_valid_i) begin
s_frm_cnt_q <= frame_stats_i[31:0]; s_frm_cnt_q <= frame_stats_i[31:0];
s_line_cnt_q <= {16'd0, frame_stats_i[47:32]}; s_line_cnt_q <= {16'd0, frame_stats_i[47:32]};
s_pixel_cnt_q <= frame_stats_i[79:48]; s_pixel_cnt_q <= frame_stats_i[79:48];
s_word_cnt_q <= {16'd0, frame_stats_i[95:80]}; s_word_cnt_q <= frame_stats_i[111:80];
s_drop_cnt_q <= frame_stats_i[127:96]; s_drop_cnt_q <= frame_stats_i[143:112];
end end
end end
end end
+63 -33
View File
@@ -8,19 +8,27 @@
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. // MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details. // See the Mulan PSL v2 for more details.
`include "dvp_define.svh"
module dvp_formal_design ( module dvp_formal_design (
input logic clk_i, input logic clk_i,
output logic rst_n_i, output logic rst_n_i,
output logic f_past_valid, output logic f_past_valid,
output logic rib_valid, output logic rib_valid,
output logic [31:0] rib_addr, output logic [ 31:0] rib_addr,
output logic [31:0] rib_wdata, output logic [ 31:0] rib_wdata,
output logic [ 3:0] rib_wstrb, output logic [ 3:0] rib_wstrb,
output logic rib_ready, output logic rib_ready,
output logic rib_resp_err, output logic rib_resp_err,
output logic [ 6:0] intr_state, output logic [ 6:0] intr_state,
output logic [ 6:0] intr_enable, output logic [ 6:0] intr_enable,
output logic irq output logic irq,
output logic cfg_pending,
output logic abort_accept,
output logic [ 6:0] event_set,
output logic [`APB4_DVP__FRAME_STATS_WIDTH-1:0] stats_input,
output logic [`APB4_DVP__FRAME_STATS_WIDTH-1:0] stats_latched,
output logic stats_valid
); );
apb4_if apb4 ( apb4_if apb4 (
.pclk (clk_i), .pclk (clk_i),
@@ -30,42 +38,63 @@ module dvp_formal_design (
(* anyseq *) logic [31:0] f_apb_addr, f_apb_wdata; (* anyseq *) logic [31:0] f_apb_addr, f_apb_wdata;
(* anyseq *) logic [3:0] f_apb_pstrb; (* anyseq *) logic [3:0] f_apb_pstrb;
(* anyseq *) logic f_active, f_fifo_empty, f_fifo_full; (* anyseq *) logic f_active, f_fifo_empty, f_fifo_full;
(* anyseq *)logic [ 5:0] f_error_flags; (* anyseq *)logic f_cfg_ready;
(* anyseq *)logic [127:0] f_frame_stats; (* anyseq *)logic [ 5:0] f_error_flags;
(* anyseq *)logic f_frame_stats_valid; (* anyseq *)logic [`APB4_DVP__FRAME_STATS_WIDTH-1:0] f_frame_stats;
assign apb4.psel = f_apb_sel; (* anyseq *)logic f_frame_stats_valid;
logic [ 5:0] s_error_clear;
(* anyseq *)logic [ 2:0] f_frame_events;
assign event_set = {
f_error_flags[5], f_error_flags[4], |f_error_flags[3:1], f_error_flags[0], f_frame_events
};
assign apb4.psel = f_apb_sel;
assign apb4.penable = f_apb_sel; assign apb4.penable = f_apb_sel;
assign apb4.pwrite = |f_apb_pstrb; assign apb4.pwrite = |f_apb_pstrb;
assign apb4.paddr = f_apb_addr; assign apb4.paddr = f_apb_addr;
assign apb4.pwdata = f_apb_wdata; assign apb4.pwdata = f_apb_wdata;
assign apb4.pstrb = f_apb_pstrb; assign apb4.pstrb = f_apb_pstrb;
assign apb4.pprot = 3'b000; assign apb4.pprot = 3'b000;
assign rib_valid = apb4.psel; assign rib_valid = apb4.psel;
assign rib_addr = apb4.paddr; assign rib_addr = apb4.paddr;
assign rib_wdata = apb4.pwdata; assign rib_wdata = apb4.pwdata;
assign rib_wstrb = apb4.pstrb; assign rib_wstrb = apb4.pstrb;
assign rib_ready = apb4.pready; assign rib_ready = apb4.pready;
assign rib_resp_err = apb4.pslverr; assign rib_resp_err = apb4.pslverr;
assign intr_state = u_dut.s_intr_stat_q; assign intr_state = u_dut.s_intr_stat_q;
assign intr_enable = u_dut.s_intr_en_q; assign intr_enable = u_dut.s_intr_en_q;
assign abort_accept = u_dut.s_abort_accept;
assign stats_input = f_frame_stats;
assign stats_valid = f_frame_stats_valid;
assign stats_latched = {
u_dut.s_drop_cnt_q,
u_dut.s_word_cnt_q,
u_dut.s_pixel_cnt_q,
u_dut.s_line_cnt_q[15:0],
u_dut.s_frm_cnt_q
};
dvp_reg u_dut ( dvp_reg u_dut (
.clk_i (clk_i), .clk_i (clk_i),
.rst_n_i (rst_n_i), .rst_n_i (rst_n_i),
.apb4 (apb4), .apb4 (apb4),
.active_i (f_active), .active_i (f_active),
.enabled_i (f_active),
.fifo_empty_i (f_fifo_empty), .fifo_empty_i (f_fifo_empty),
.fifo_full_i (f_fifo_full), .fifo_full_i (f_fifo_full),
.rx_data_i (32'd0), .rx_data_i (32'd0),
.frame_start_i (1'b0), .frame_start_i (f_frame_events[0]),
.line_done_i (1'b0), .line_done_i (f_frame_events[1]),
.frame_done_i (1'b0), .frame_done_i (f_frame_events[2]),
.error_event_i (|f_error_flags), .error_event_i (|f_error_flags),
.error_events_i (f_error_flags),
.error_flags_i (f_error_flags), .error_flags_i (f_error_flags),
.error_valid_i (1'b1),
.error_clear_done_i (s_error_clear),
.error_clear_o (s_error_clear),
.frame_stats_i (f_frame_stats), .frame_stats_i (f_frame_stats),
.frame_stats_valid_i(f_frame_stats_valid), .frame_stats_valid_i(f_frame_stats_valid),
.cfg_o (), .cfg_o (),
.cfg_valid_o (), .cfg_valid_o (cfg_pending),
.cfg_ready_i (1'b1), .cfg_ready_i (f_cfg_ready),
.cmd_abort_o (), .cmd_abort_o (),
.cmd_flush_o (), .cmd_flush_o (),
.cmd_valid_o (), .cmd_valid_o (),
@@ -79,6 +108,7 @@ module dvp_formal_design (
f_past_valid = 1'b0; f_past_valid = 1'b0;
end end
always_ff @(posedge clk_i) begin always_ff @(posedge clk_i) begin
if (rst_n_i) assert (u_dut.s_line_cnt_q[31:16] == 16'd0);
rst_n_i <= 1'b1; rst_n_i <= 1'b1;
f_past_valid <= 1'b1; f_past_valid <= 1'b1;
end end
+33 -12
View File
@@ -14,19 +14,30 @@ module dvp_formal;
logic [31:0] rib_addr, rib_wdata; logic [31:0] rib_addr, rib_wdata;
logic [3:0] rib_wstrb; logic [3:0] rib_wstrb;
logic [6:0] intr_state, intr_enable; logic [6:0] intr_state, intr_enable;
logic [6:0] event_set;
logic cfg_pending, abort_accept;
logic stats_valid;
// Independent expected mailbox width; properties compile without RTL headers.
logic [143:0] stats_input, stats_latched;
dvp_formal_design u_design ( dvp_formal_design u_design (
.clk_i (clk_i), .clk_i (clk_i),
.rst_n_i (rst_n_i), .rst_n_i (rst_n_i),
.f_past_valid(f_past_valid), .f_past_valid (f_past_valid),
.rib_valid (rib_valid), .rib_valid (rib_valid),
.rib_addr (rib_addr), .rib_addr (rib_addr),
.rib_wdata (rib_wdata), .rib_wdata (rib_wdata),
.rib_wstrb (rib_wstrb), .rib_wstrb (rib_wstrb),
.rib_ready (rib_ready), .rib_ready (rib_ready),
.rib_resp_err(rib_resp_err), .rib_resp_err (rib_resp_err),
.intr_state (intr_state), .intr_state (intr_state),
.intr_enable (intr_enable), .intr_enable (intr_enable),
.irq (irq) .irq (irq),
.cfg_pending (cfg_pending),
.abort_accept (abort_accept),
.event_set (event_set),
.stats_input (stats_input),
.stats_latched(stats_latched),
.stats_valid (stats_valid)
); );
always_ff @(posedge clk_i) begin always_ff @(posedge clk_i) begin
if (rst_n_i) begin if (rst_n_i) begin
@@ -38,6 +49,16 @@ module dvp_formal;
assert (rib_ready); assert (rib_ready);
end end
assert (irq == ((intr_state & intr_enable) != 0)); assert (irq == ((intr_state & intr_enable) != 0));
if (f_past_valid && $past(rst_n_i))
assert ((intr_state & $past(event_set)) == $past(event_set));
if (f_past_valid && $past(rst_n_i && abort_accept)) assert (!cfg_pending);
if (f_past_valid && $past(rst_n_i)) begin
if ($past(stats_valid))
assert (stats_latched == $past(stats_input));
else assert (stats_latched == $past(stats_latched));
end
cover (stats_latched[111:80] == 32'd153600 && stats_latched[143:112] != 0);
cover (cfg_pending && abort_accept);
cover (rib_resp_err); cover (rib_resp_err);
cover (irq); cover (irq);
end end
@@ -201,6 +201,11 @@
{"name": "memory_pad_mode_hp", "source": "aon", "destination": "hp", "primitive": "cdc_sync", "path": "rtl/mini/top/soc_data_plane.sv", "instance": "u_mem_pad_mode_sync"}, {"name": "memory_pad_mode_hp", "source": "aon", "destination": "hp", "primitive": "cdc_sync", "path": "rtl/mini/top/soc_data_plane.sv", "instance": "u_mem_pad_mode_sync"},
{"name": "memory_pad_mode_lp", "source": "aon", "destination": "lp", "primitive": "cdc_sync", "path": "rtl/mini/top/retrosoc.sv", "instance": "u_mem_pad_mode_sync"}, {"name": "memory_pad_mode_lp", "source": "aon", "destination": "lp", "primitive": "cdc_sync", "path": "rtl/mini/top/retrosoc.sv", "instance": "u_mem_pad_mode_sync"},
{"name": "dvp_enable", "source": "pclk", "destination": "dvp", "primitive": "cdc_2phase", "path": "rtl/ip/multimedia/axi4s_dvp.sv", "instance": "u_dvp_config_cdc"}, {"name": "dvp_enable", "source": "pclk", "destination": "dvp", "primitive": "cdc_2phase", "path": "rtl/ip/multimedia/axi4s_dvp.sv", "instance": "u_dvp_config_cdc"},
{"name": "dvp_commands", "source": "pclk", "destination": "dvp", "primitive": "cdc_2phase", "path": "rtl/ip/multimedia/axi4s_dvp.sv", "instance": "u_dvp_command_cdc"},
{"name": "dvp_error_clear", "source": "pclk", "destination": "dvp", "primitive": "cdc_2phase", "path": "rtl/ip/multimedia/axi4s_dvp.sv", "instance": "u_error_clear_cdc"},
{"name": "dvp_status", "source": "dvp", "destination": "pclk", "primitive": "cdc_2phase", "path": "rtl/ip/multimedia/axi4s_dvp.sv", "instance": "u_status_cdc"},
{"name": "dvp_errors", "source": "dvp", "destination": "pclk", "primitive": "cdc_2phase", "path": "rtl/ip/multimedia/axi4s_dvp.sv", "instance": "u_error_events_cdc"},
{"name": "dvp_statistics", "source": "dvp", "destination": "pclk", "primitive": "cdc_2phase", "path": "rtl/ip/multimedia/axi4s_dvp.sv", "instance": "u_dvp_stats_cdc"},
{"name": "dvp_fifo", "source": "dvp", "destination": "pclk", "primitive": "cdc_fifo_warm_flush", "path": "rtl/ip/multimedia/axi4s_dvp.sv", "instance": "u_dvp_payload_fifo"}, {"name": "dvp_fifo", "source": "dvp", "destination": "pclk", "primitive": "cdc_fifo_warm_flush", "path": "rtl/ip/multimedia/axi4s_dvp.sv", "instance": "u_dvp_payload_fifo"},
{"name": "rtc_command", "source": "pclk", "destination": "audio", "primitive": "async_reqack", "path": "rtl/managed/clusterip/rtc/rtl/apb4_rtc.sv", "instance": "u_command_mailbox"}, {"name": "rtc_command", "source": "pclk", "destination": "audio", "primitive": "async_reqack", "path": "rtl/managed/clusterip/rtc/rtl/apb4_rtc.sv", "instance": "u_command_mailbox"},
{"name": "rtc_response", "source": "audio", "destination": "pclk", "primitive": "async_reqack", "path": "rtl/managed/clusterip/rtc/rtl/apb4_rtc.sv", "instance": "u_response_mailbox"}, {"name": "rtc_response", "source": "audio", "destination": "pclk", "primitive": "async_reqack", "path": "rtl/managed/clusterip/rtc/rtl/apb4_rtc.sv", "instance": "u_response_mailbox"},
+1 -1
View File
@@ -183,7 +183,7 @@ TINY_CRT_NAMES := startup.S clzsi2.c divdi3.c ffssi2.c udivdi3.c umoddi3.c \
stdlib.c string.c console.c printf.c archinfo.c clint.c uart_math.c uart.c \ stdlib.c string.c console.c printf.c archinfo.c clint.c uart_math.c uart.c \
gpio_math.c gpio.c timer_math.c timer.c pwm.c rtc.c wdg.c watchdog.c i2c_math.c i2c.c \ gpio_math.c gpio.c timer_math.c timer.c pwm.c rtc.c wdg.c watchdog.c i2c_math.c i2c.c \
dma_math.c dma.c onchip_sram.c xpi.c xpi_psram.c xpi_psram_math.c rng.c crc.c ws2812_math.c ws2812.c \ dma_math.c dma.c onchip_sram.c xpi.c xpi_psram.c xpi_psram_math.c rng.c crc.c ws2812_math.c ws2812.c \
crypto.c crypto_lifecycle.c i2s_math.c i2s.c sdio_math.c sdio.c rcu.c crypto.c crypto_lifecycle.c i2s_math.c i2s.c dvp.c sdio_math.c sdio.c rcu.c
CRT_SRCS := $(foreach source,$(CRT_SRCS),$(if $(filter $(notdir $(source)),$(TINY_CRT_NAMES)),$(source))) CRT_SRCS := $(foreach source,$(CRT_SRCS),$(if $(filter $(notdir $(source)),$(TINY_CRT_NAMES)),$(source)))
CRT_SRCS += $(ROOT_PATH)/crt/src/hal/sysctrl.c CRT_SRCS += $(ROOT_PATH)/crt/src/hal/sysctrl.c
endif endif
+22
View File
@@ -156,6 +156,28 @@ netsim-boot: netcomp tiny-boot-image | manifest
.PHONY: tiny-boot-image .PHONY: tiny-boot-image
# Small DVP-specific mapped-netlist check; keep boot evidence in its own log.
TINY_P9_NETLIST_ELF := $(SW_BUILD_DIR)/tiny_p9_netlist.elf
# The unchanged compact image needs about 178k cycles when executing from XPI.
# Retain the cycle guard and allow slower gate models enough host time.
TINY_P9_NETLIST_WALL_TIMEOUT ?= $(if $(filter ICS55,$(PDK)),10800,7200)
$(TINY_P9_NETLIST_ELF): $(ROOT_PATH)/app/asm/tiny_p9_netlist.S $(FILELIST_STAMP)
@mkdir -p $(@D)
$(CC) -march=rv32im_zicsr -mabi=ilp32 -nostdlib -I$(MEMORY_MAP_C_DIR) -I$(SOC_TOPOLOGY_INCLUDE_DIR) \
-Wl,-Ttext=0x0,--entry=_start,--build-id=none -o $@ $<
$(OBJC) -O verilog $@ $(SW_BUILD_DIR)/tiny_p9_netlist.hex
netsim-dvp: netcomp $(TINY_P9_NETLIST_ELF) | manifest
$(PYTHON) $(ROOT_PATH)/scripts/run_flow.py --tool iverilog-sim \
--log $(NETLIST_SIM_ROOT)/dvp/sim.log --result $(NETLIST_SIM_ROOT)/dvp/result-sim.json -- \
timeout --foreground --kill-after=5s $(TINY_P9_NETLIST_WALL_TIMEOUT)s $(VVP) $(NETLIST_SIM_ROOT)/simv \
+firmware=$(SW_BUILD_DIR)/tiny_p9_netlist.hex +max_cycles=300000 \
+tiny_p9_camera +tiny_p9_small +tiny_p9_half_ns=500
$(PYTHON) $(ROOT_PATH)/scripts/check_simulation.py --log $(NETLIST_SIM_ROOT)/dvp/sim.log \
--result $(NETLIST_SIM_ROOT)/dvp/result-sim-check.json --require 'SIM_TEST_PASS Tiny'
.PHONY: netsim-dvp
# R2-P1 verification image: normal APP selection and the flat linker stay intact. # R2-P1 verification image: normal APP selection and the flat linker stay intact.
# Image creation always compiles and snapshots inputs before retaining a binary. # Image creation always compiles and snapshots inputs before retaining a binary.
# Simulation rebuilds its model once, then replays the same image three times. # Simulation rebuilds its model once, then replays the same image three times.
+7 -6
View File
@@ -86,12 +86,13 @@ qualified external-I/O rates. `PIOLITE-P5` may join R2-P11 physical work only
on the same PIO-inclusive source and configuration with both evidence sets. on the same PIO-inclusive source and configuration with both evidence sets.
The original R2 phase IDs/titles and performance-only scope remain intact. The original R2 phase IDs/titles and performance-only scope remain intact.
Camera GPIO12-23 ALT1 remains mutually exclusive with I2S. DMA2/request 11, Camera GPIO12-23 ALT1 is now integrated and remains mutually exclusive with
optional XPI NSS1 PSRAM at `0x54000000` through GPIO29 and shared CAM_XCLK I2S. DVP uses APB4 `0x1000E000`, IRQ15, DMA2/request11, the existing pixel CDC
retain their existing target contracts. RCU target 14 and DVP capabilities FIFO and RCU target14. Optional XPI NSS1 PSRAM at `0x54000000` through GPIO29
stay unsupported until actual integration. `TINY-R2-P8` and `TINY-R2-P9` and shared CAM_XCLK retain their existing target contracts. `TINY-R2-P8` and
require pin-level XPI PSRAM and full-frame readback, not fast-flash or `TINY-R2-P9` require pin-level XPI PSRAM and full-frame readback, not
separate-controller evidence. fast-flash or a separate controller; physical sensor and rate qualification
remain later evidence.
No camera/PSRAM rate is qualified by this freeze. No camera/PSRAM rate is qualified by this freeze.
The [P8 implementation](../../docs/ip/tiny-soc-r2-psram.md) adds explicit optional The [P8 implementation](../../docs/ip/tiny-soc-r2-psram.md) adds explicit optional
+9
View File
@@ -427,6 +427,15 @@
"public": true, "public": true,
"user_access": "none" "user_access": "none"
}, },
{
"symbol": "APB4_DVP",
"base": "0x1000E000",
"size": "0x00001000",
"route": "apb4_periph",
"kind": "active",
"public": true,
"user_access": "none"
},
{ {
"symbol": "APB4_SDIO0", "symbol": "APB4_SDIO0",
"base": "0x1000F000", "base": "0x1000F000",
+57 -4
View File
@@ -26,10 +26,38 @@ module retrosoc_tiny_tb;
wire s_xpi_sck; wire s_xpi_sck;
wire [3:0] s_xpi_nss; wire [3:0] s_xpi_nss;
tri1 [3:0] s_xpi_data; tri1 [3:0] s_xpi_data;
// Optional board population; the unchanged default has no PSRAM dependency. wire s_dvp_pclk;
wire s_psram_csn = ($test$plusargs( wire s_dvp_href;
"tiny_p8_psram" wire s_dvp_vsync;
wire [7:0] s_dvp_data;
logic s_p9_camera_population = $test$plusargs(
"tiny_p9_camera"
) && !$test$plusargs( ) && !$test$plusargs(
"tiny_p9_recovery"
);
tiny_dvp_camera u_dvp_camera (
.pclk_o (s_dvp_pclk),
.href_o (s_dvp_href),
.vsync_o(s_dvp_vsync),
.dat_o (s_dvp_data)
);
assign s_gpio_12 = s_p9_camera_population ? s_dvp_data[0] : 1'bz;
assign s_gpio_13 = s_p9_camera_population ? s_dvp_data[1] : 1'bz;
assign s_gpio_14 = s_p9_camera_population ? s_dvp_data[2] : 1'bz;
assign s_gpio_15 = s_p9_camera_population ? s_dvp_data[3] : 1'bz;
assign s_gpio_16 = s_p9_camera_population ? s_dvp_data[4] : 1'bz;
assign s_gpio_17 = s_p9_camera_population ? s_dvp_data[5] : 1'bz;
assign s_gpio_18 = s_p9_camera_population ? s_dvp_data[6] : 1'bz;
assign s_gpio_19 = s_p9_camera_population ? s_dvp_data[7] : 1'bz;
assign s_gpio_20 = s_p9_camera_population ? s_dvp_pclk : 1'bz;
assign s_gpio_21 = s_p9_camera_population ? s_dvp_href : 1'bz;
assign s_gpio_22 = s_p9_camera_population ? s_dvp_vsync : 1'bz;
// Optional board population; the unchanged default has no PSRAM dependency.
wire s_psram_csn = (($test$plusargs(
"tiny_psram"
) || $test$plusargs(
"tiny_p8_psram"
)) && !$test$plusargs(
"p8_absent" "p8_absent"
)) ? s_gpio[29] : 1'b1; )) ? s_gpio[29] : 1'b1;
ESP_PSRAM64H #( ESP_PSRAM64H #(
@@ -45,6 +73,8 @@ module retrosoc_tiny_tb;
`include "tiny_p6_devices.svh" `include "tiny_p6_devices.svh"
`include "tiny_p7_observers.svh" `include "tiny_p7_observers.svh"
`include "tiny_p8_observers.svh" `include "tiny_p8_observers.svh"
`include "tiny_p9_observers.svh"
`include "tiny_p9_recovery.svh"
`endif `endif
integer s_max_cycles = 20000000; integer s_max_cycles = 20000000;
integer s_done_cycles = 0; integer s_done_cycles = 0;
@@ -92,15 +122,38 @@ module retrosoc_tiny_tb;
if (s_done_cycles != 128) $fatal(1, "SIM_TEST_TIMEOUT Tiny cycles=%0d", s_cycles); if (s_done_cycles != 128) $fatal(1, "SIM_TEST_TIMEOUT Tiny cycles=%0d", s_cycles);
end end
`ifndef RETROSOC_SOC__TINY_NETLIST
// Legacy RTL-only diagnostics reference hierarchy removed by synthesis.
integer p9_progress_cycle = 0;
always @(posedge s_clk) begin
p9_progress_cycle = p9_progress_cycle + 1;
if ($test$plusargs("tiny_p9_progress") && ((p9_progress_cycle % 10000) == 0)) begin
$display(
"P9_TB_PROGRESS cycle=%0d cpu_boot=%b core_rst=%b boot_release=%b fabric=%b dma_ready=%b apb_ready=%b i_trans=%b i_ready=%b d_trans=%b d_ready=%b slow_req=%0d slow_state=%0d slow_htrans=%b slow_hready=%b xpi_ar=%b/%b xpi_r=%b/%b",
p9_progress_cycle, u_dut.u_soc.s_cpu_boot_ready, u_dut.u_soc.s_core_rst_n,
u_dut.u_soc.s_boot_release_q, u_dut.u_soc.s_fabric_rst_n, u_dut.u_soc.s_dma_ready_sys,
&u_dut.u_soc.s_apb_cdc_ready, u_dut.u_soc.u_cpu_ahbl_if[0].htrans[1],
u_dut.u_soc.u_cpu_ahbl_if[0].hready, u_dut.u_soc.u_cpu_ahbl_if[1].htrans[1],
u_dut.u_soc.u_cpu_ahbl_if[1].hready, u_dut.u_soc.u_cpu_mem.s_req_q[0].state,
u_dut.u_soc.u_cpu_mem.s_slow_q.state, u_dut.u_soc.u_cpu_mem.u_slow_ahbl_if.htrans,
u_dut.u_soc.u_cpu_mem.u_slow_ahbl_if.hready, u_dut.u_soc.u_xpi_mem_axi4_if.arvalid,
u_dut.u_soc.u_xpi_mem_axi4_if.arready, u_dut.u_soc.u_xpi_mem_axi4_if.rvalid,
u_dut.u_soc.u_xpi_mem_axi4_if.rready);
end
end
`endif
`ifdef SIMU_IVERILOG `ifdef SIMU_IVERILOG
localparam bit ObserveUartAlways = 1'b1; localparam bit ObserveUartAlways = 1'b1;
`else `else
localparam bit ObserveUartAlways = 1'b0; localparam bit ObserveUartAlways = 1'b0;
`endif `endif
wire ObserveP9Progress = $test$plusargs("tiny_p9_progress");
// The Verilator RTL prints accepted TX bytes. Icarus observes serial output for // The Verilator RTL prints accepted TX bytes. Icarus observes serial output for
// all firmware measurements; SYSCTRL remains the authoritative verdict. // all firmware measurements; SYSCTRL remains the authoritative verdict.
initial begin initial begin
if (ObserveUartAlways) begin if (ObserveUartAlways || ObserveP9Progress) begin
forever begin forever begin
@(negedge s_uart0_tx); @(negedge s_uart0_tx);
#1627.604; #1627.604;
+85
View File
@@ -0,0 +1,85 @@
// Copyright (c) 2026 Yuchi Miao
// SPDX-License-Identifier: MulanPSL-2.0
// Deterministic Tiny P9 camera source. The model drives the existing GPIO12-22
// camera profile and remains electrically idle unless +tiny_p9_camera is set.
module tiny_dvp_camera (
output logic pclk_o,
output logic href_o,
output logic vsync_o,
output logic [7:0] dat_o
);
integer pixel_half_ns = 500;
integer h_active;
integer v_active;
integer line_count;
integer byte_count;
integer frame_count;
bit enabled;
integer state;
// Verification-only control, written by the opt-in recovery board observer.
// 0: run, 1: stop low, 2: stop high. Normal camera workloads retain zero.
integer recovery_pause = 0;
initial begin
if ($value$plusargs("tiny_p9_half_ns=%d", pixel_half_ns)) begin
end
if (pixel_half_ns < 1) $fatal(1, "invalid Tiny P9 pixel half-period");
h_active = $test$plusargs("tiny_p9_small") ? 16 : ($test$plusargs("tiny_p9_vga") ? 640 : 320);
v_active = $test$plusargs("tiny_p9_small") ? 8 : ($test$plusargs("tiny_p9_vga") ? 480 : 240);
enabled = $test$plusargs("tiny_p9_camera");
pclk_o = 1'b0;
href_o = 1'b0;
vsync_o = 1'b0;
dat_o = 8'h00;
line_count = 0;
byte_count = 0;
frame_count = 0;
state = 0;
end
always #(pixel_half_ns) begin
if (enabled && !$test$plusargs("tiny_p9_no_pixclk")) begin
if (recovery_pause == 0 || (recovery_pause == 1 && pclk_o) ||
(recovery_pause == 2 && !pclk_o))
pclk_o = !pclk_o;
end else if (enabled) begin
pclk_o = $test$plusargs("tiny_p9_pixclk_high");
end else begin
pclk_o = 1'b0;
end
end
always @(posedge pclk_o) begin
if (!enabled) begin
href_o <= 1'b0;
vsync_o <= 1'b0;
dat_o <= 8'h00;
end else if (state == 0) begin
vsync_o <= 1'b1;
href_o <= 1'b0;
state <= 1;
end else if (state == 1) begin
vsync_o <= 1'b0;
href_o <= 1'b0;
line_count <= 0;
byte_count <= 0;
state <= 2;
end else if (line_count < v_active) begin
if (byte_count < (h_active * 2)) begin
href_o <= 1'b1;
dat_o <= 8'(byte_count + (line_count * 3));
byte_count <= byte_count + 1;
end else begin
href_o <= 1'b0;
byte_count <= 0;
line_count <= line_count + 1;
end
end else begin
href_o <= 1'b0;
frame_count <= frame_count + 1;
line_count <= 0;
state <= 0;
end
end
endmodule
+16 -8
View File
@@ -79,13 +79,18 @@ logic [23:0] s_p6_wire_shift = 0;
logic [31:0] s_p6_wire_word; logic [31:0] s_p6_wire_word;
wire s_p6_audio_enabled = u_dut.u_soc.u_i2s.u_i2s_core.enable_i; wire s_p6_audio_enabled = u_dut.u_soc.u_i2s.u_i2s_core.enable_i;
wire s_p6_audio_wide = u_dut.u_soc.u_i2s.u_i2s_core.s_bitmode; wire s_p6_audio_wide = u_dut.u_soc.u_i2s.u_i2s_core.s_bitmode;
wire s_p6_recovery_mode = $test$plusargs("tiny_p9_recovery");
// Decode actual BCLK/LRCLK/DOUT pads, independently of the receive engine. // Decode actual BCLK/LRCLK/DOUT pads, independently of the receive engine.
always @(posedge s_gpio[12] or negedge s_p6_audio_enabled) begin always @(posedge s_gpio[12] or negedge s_p6_audio_enabled) begin
if (!s_p6_audio_enabled) begin if (!s_p6_audio_enabled) begin
if (s_p6_wire_found) begin if (s_p6_wire_found || (s_p6_recovery_mode && s_p6_wire_examined != 0)) begin
if (s_p6_wire_matched != 512) $fatal(1, "SIM_TEST_FAIL P6 truncated I2S wire payload"); if (s_p6_wire_matched != (s_p6_recovery_mode ? 8 : 512))
$fatal(1, "SIM_TEST_FAIL truncated I2S wire payload");
s_p6_wire_cases++; s_p6_wire_cases++;
$display("R2_P6_I2S_WIRE bits=%0d samples=%0d", s_p6_audio_wide ? 24 : 16, s_p6_wire_matched); if (!s_p6_recovery_mode)
$display(
"R2_P6_I2S_WIRE bits=%0d samples=%0d", s_p6_audio_wide ? 24 : 16, s_p6_wire_matched
);
end end
s_p6_wire_matched = 0; s_p6_wire_matched = 0;
s_p6_wire_examined = 0; s_p6_wire_examined = 0;
@@ -104,10 +109,12 @@ always @(posedge s_gpio[12] or negedge s_p6_audio_enabled) begin
s_p6_wire_boundary = 1; s_p6_wire_boundary = 1;
s_p6_wire_edges = 0; s_p6_wire_edges = 0;
s_p6_wire_word = s_p6_audio_wide ? {8'd0, s_p6_wire_shift} : {16'd0, s_p6_wire_shift[15:0]}; s_p6_wire_word = s_p6_audio_wide ? {8'd0, s_p6_wire_shift} : {16'd0, s_p6_wire_shift[15:0]};
if (!s_p6_wire_found && s_p6_wire_word == (s_p6_audio_wide ? 32'h120020 : 32'h2020)) if (!s_p6_wire_found && s_p6_wire_word ==
(s_p6_recovery_mode ? 32'h1234 : (s_p6_audio_wide ? 32'h120020 : 32'h2020)))
s_p6_wire_found = 1; s_p6_wire_found = 1;
if (s_p6_wire_found && s_p6_wire_matched < 512) begin if (s_p6_wire_found && s_p6_wire_matched < (s_p6_recovery_mode ? 8 : 512)) begin
if (s_p6_wire_word != (s_p6_audio_wide ? 32'h120020 : 32'h2020) + 32'(s_p6_wire_matched)) if (s_p6_wire_word != (s_p6_recovery_mode ? 32'h1234 :
(s_p6_audio_wide ? 32'h120020 : 32'h2020) + 32'(s_p6_wire_matched)))
$fatal(1, "SIM_TEST_FAIL P6 I2S wire ordering/data index=%0d", s_p6_wire_matched); $fatal(1, "SIM_TEST_FAIL P6 I2S wire ordering/data index=%0d", s_p6_wire_matched);
s_p6_wire_matched++; s_p6_wire_matched++;
end end
@@ -274,10 +281,11 @@ for (genvar owner = 0; owner < 3; owner++) begin : gen_p6_axi_observer
`undef _P6_AXI `undef _P6_AXI
end end
always #27.126736111 if (s_p6_audio_running) s_p6_audio_reference = !s_p6_audio_reference; always #27.126736111 if (s_p6_audio_running) s_p6_audio_reference = !s_p6_audio_reference;
assign s_gpio_17 = s_p6_audio_reference; // Camera and codec are alternative board populations on these shared pads.
assign s_gpio_17 = s_p9_camera_population ? 1'bz : s_p6_audio_reference;
// The first integration case is an external codec loopback, across actual pads. // The first integration case is an external codec loopback, across actual pads.
// Independent wire decoding and error injection supplement firmware readback. // Independent wire decoding and error injection supplement firmware readback.
assign s_gpio_15 = s_gpio_14; assign s_gpio_15 = s_p9_camera_population ? 1'bz : s_gpio_14;
wire s_p6_card_cmd; wire s_p6_card_cmd;
wire [3:0] s_p6_card_data; wire [3:0] s_p6_card_data;
wire s_p6_card_irq; wire s_p6_card_irq;
+1 -1
View File
@@ -13,7 +13,7 @@ initial begin
// Case comparisons require the exact known reset values in both simulators. // Case comparisons require the exact known reset values in both simulators.
if (u_dut.u_soc.u_sysctrl.s_reset_cause_q !== 32'd1 || if (u_dut.u_soc.u_sysctrl.s_reset_cause_q !== 32'd1 ||
u_dut.u_soc.u_sysctrl.s_rcu_fault_q !== 32'd0 || u_dut.u_soc.u_sysctrl.s_rcu_fault_q !== 32'd0 ||
u_dut.u_soc.s_backend_current !== 2'd0 || u_dut.u_soc.s_gate_request !== 14'd0) u_dut.u_soc.s_backend_current !== 2'd0 || u_dut.u_soc.s_gate_request !== 15'd0)
$fatal( $fatal(
1, 1,
"SIM_TEST_FAIL P7 cold reset state cause=%h fault=%h profile=%h gates=%h", "SIM_TEST_FAIL P7 cold reset state cause=%h fault=%h profile=%h gates=%h",
+3 -1
View File
@@ -1,4 +1,6 @@
// Verification-only MEM-clock observations. No DUT state is driven. // Verification-only MEM-clock observations. No DUT state is driven.
// tiny_psram populates the shared board device; tiny_p8_psram additionally
// selects the legacy P8 workload's performance-window acceptance contract.
`define _P8_AXI u_dut.u_soc.u_xpi_mem_axi4_if `define _P8_AXI u_dut.u_soc.u_xpi_mem_axi4_if
logic s_p8_previous_enable = 0; logic s_p8_previous_enable = 0;
logic s_p8_finished = 0; logic s_p8_finished = 0;
@@ -56,7 +58,7 @@ end
time s_p8_cs_start, s_p8_cs_max = 0; time s_p8_cs_start, s_p8_cs_max = 0;
always @(negedge s_psram_csn) s_p8_cs_start = $time; always @(negedge s_psram_csn) s_p8_cs_start = $time;
always @(posedge s_psram_csn) begin always @(posedge s_psram_csn) begin
if ($test$plusargs("tiny_p8_psram") && s_p8_cs_start != 0) begin if (($test$plusargs("tiny_psram") || $test$plusargs("tiny_p8_psram")) && s_p8_cs_start != 0) begin
if ($time - s_p8_cs_start > 8000) $fatal(1, "SIM_TEST_FAIL P8 active CS exceeds 8 us"); if ($time - s_p8_cs_start > 8000) $fatal(1, "SIM_TEST_FAIL P8 active CS exceeds 8 us");
if ($time - s_p8_cs_start > s_p8_cs_max) s_p8_cs_max = $time - s_p8_cs_start; if ($time - s_p8_cs_start > s_p8_cs_max) s_p8_cs_max = $time - s_p8_cs_start;
end end
+106
View File
@@ -0,0 +1,106 @@
// Copyright (c) 2026 Yuchi Miao
// SPDX-License-Identifier: MulanPSL-2.0
// Passive acceptance progress. Unlike tiny_p9_diag, this never calls $finish.
// Counters are sampled on their native clocks. The REF report is a set of
// latest domain samples, not an atomic CDC snapshot or a retirement counter.
bit s_p9_monitor_enabled = 0;
integer s_p9_monitor_file = 0;
string s_p9_monitor_path;
longint unsigned s_p9_monitor_ref = 0;
longint unsigned s_p9_monitor_fetch = 0, s_p9_monitor_data = 0;
longint unsigned s_p9_monitor_vsync = 0, s_p9_monitor_vsync_ref = 0;
longint unsigned s_p9_monitor_frame_start = 0, s_p9_monitor_frame_done = 0;
longint unsigned s_p9_monitor_aw = 0, s_p9_monitor_wbytes = 0;
longint unsigned s_p9_monitor_b = 0, s_p9_monitor_rbytes = 0;
longint unsigned s_p9_monitor_bus_errors = 0;
logic [31:0] s_p9_monitor_dma_bytes = 0, s_p9_monitor_dma_remaining = 0;
logic s_p9_monitor_dma_busy = 0, s_p9_monitor_dma_error = 0;
logic s_p9_monitor_dvp_active = 0;
logic [5:0] s_p9_monitor_dvp_errors = 0;
longint unsigned s_p9_monitor_dma_ref = 0, s_p9_monitor_dvp_ref = 0;
longint unsigned s_p9_monitor_mem_ref = 0;
initial begin
s_p9_monitor_enabled = $test$plusargs("tiny_p9_monitor");
if (s_p9_monitor_enabled) begin
if ($test$plusargs("tiny_p9_diag") || $test$plusargs("tiny_p9_progress"))
$fatal(1, "SIM_TEST_FAIL P9 monitor conflicts with diagnostic or legacy progress mode");
if (!$test$plusargs("tiny_p9_camera"))
$fatal(1, "SIM_TEST_FAIL P9 monitor requires camera mode");
if (!$value$plusargs("tiny_p9_monitor_log=%s", s_p9_monitor_path))
$fatal(1, "SIM_TEST_FAIL P9 monitor requires log path");
s_p9_monitor_file = $fopen(s_p9_monitor_path, "w");
if (s_p9_monitor_file == 0) $fatal(1, "SIM_TEST_FAIL P9 monitor cannot open log");
$fdisplay(s_p9_monitor_file,
"P9_MONITOR_BEGIN version=1 ref_interval=100000 native_samples=1 early_stop=0");
$fflush(s_p9_monitor_file);
end
end
always @(posedge s_clk) if (s_p9_monitor_enabled) s_p9_monitor_ref++;
always @(posedge s_dvp_vsync) begin
if (s_p9_monitor_enabled) begin
s_p9_monitor_vsync++;
s_p9_monitor_vsync_ref = s_p9_monitor_ref;
end
end
always @(posedge u_dut.u_soc.s_exec_clk) begin
if (s_p9_monitor_enabled && u_dut.u_soc.s_core_rst_n) begin
if (u_dut.u_soc.u_cpu_mem.s_terminal[0]) s_p9_monitor_fetch++;
if (u_dut.u_soc.u_cpu_mem.s_terminal[1]) s_p9_monitor_data++;
end
end
always @(posedge u_dut.u_soc.s_dma_clk) begin
if (s_p9_monitor_enabled && u_dut.u_soc.s_dma_rst_n) begin
s_p9_monitor_dma_bytes = u_dut.u_soc.u_dma.s_bytes_done[64+:32];
s_p9_monitor_dma_remaining = u_dut.u_soc.u_dma.s_remaining[64+:32];
s_p9_monitor_dma_busy = u_dut.u_soc.u_dma.s_busy[2];
s_p9_monitor_dma_error = u_dut.u_soc.u_dma.s_error[2];
s_p9_monitor_dma_ref = s_p9_monitor_ref;
end
end
always @(posedge u_dut.u_soc.s_dvp_clk) begin
if (s_p9_monitor_enabled && u_dut.u_soc.s_dvp_rst_n) begin
if (u_dut.u_soc.u_dvp.s_frame_start_event) s_p9_monitor_frame_start++;
if (u_dut.u_soc.u_dvp.s_frame_done_event) s_p9_monitor_frame_done++;
s_p9_monitor_dvp_active = u_dut.u_soc.u_dvp.s_active;
s_p9_monitor_dvp_errors = u_dut.u_soc.u_dvp.s_err_flags_sys;
s_p9_monitor_dvp_ref = s_p9_monitor_ref;
end
end
`define _P9_MON_AXI u_dut.u_soc.u_xpi_mem_axi4_if
always @(posedge u_dut.u_soc.s_backend_mem_clk) begin
if (s_p9_monitor_enabled && u_dut.u_soc.s_xpi_rst_n) begin
s_p9_monitor_mem_ref = s_p9_monitor_ref;
if (`_P9_MON_AXI.awvalid && `_P9_MON_AXI.awready) s_p9_monitor_aw++;
if (`_P9_MON_AXI.wvalid && `_P9_MON_AXI.wready)
s_p9_monitor_wbytes += $countones(`_P9_MON_AXI.wstrb);
if (`_P9_MON_AXI.bvalid && `_P9_MON_AXI.bready) begin
s_p9_monitor_b++;
if (`_P9_MON_AXI.bresp != 0) s_p9_monitor_bus_errors++;
end
if (`_P9_MON_AXI.rvalid && `_P9_MON_AXI.rready) begin
s_p9_monitor_rbytes += 4;
if (`_P9_MON_AXI.rresp != 0) s_p9_monitor_bus_errors++;
end
end
end
`undef _P9_MON_AXI
always @(negedge s_clk) begin
if (s_p9_monitor_enabled && s_p9_monitor_ref % 100000 == 0) begin
// Firmware UART is emitted character-by-character; a separate stream
// prevents progress records from splitting strict P9 acceptance lines.
$fdisplay(
s_p9_monitor_file,
"P9_PROGRESS ref=%0d vsync=%0d vsync_ref=%0d frames_started=%0d frames_done=%0d accepted_words=%0d delivered_words=%0d fifo_high_water=%0d backpressure=%0d dvp_active=%b dvp_errors=%h dma_busy=%b dma_bytes=%0d dma_remaining=%0d dma_error=%b xpi_aw=%0d xpi_wbytes=%0d xpi_b=%0d xpi_rbytes=%0d xpi_errors=%0d fetch_done=%0d data_done=%0d dma_ref=%0d dvp_ref=%0d mem_ref=%0d",
s_p9_monitor_ref, s_p9_monitor_vsync, s_p9_monitor_vsync_ref, s_p9_monitor_frame_start,
s_p9_monitor_frame_done, p9_accepted_words, p9_delivered_words, p9_fifo_high_water,
p9_backpressure, s_p9_monitor_dvp_active, s_p9_monitor_dvp_errors, s_p9_monitor_dma_busy,
s_p9_monitor_dma_bytes, s_p9_monitor_dma_remaining, s_p9_monitor_dma_error,
s_p9_monitor_aw, s_p9_monitor_wbytes, s_p9_monitor_b, s_p9_monitor_rbytes,
s_p9_monitor_bus_errors, s_p9_monitor_fetch, s_p9_monitor_data, s_p9_monitor_dma_ref,
s_p9_monitor_dvp_ref, s_p9_monitor_mem_ref);
$fflush(s_p9_monitor_file);
end
end
+442
View File
@@ -0,0 +1,442 @@
// Copyright (c) 2026 Yuchi Miao
// SPDX-License-Identifier: MulanPSL-2.0
// Verification-only session counters. Count actual FIFO handshakes, including
// PIO pops. They are neither hardware registers nor a worst-case rate bound.
integer p9_accepted_words = 0;
integer p9_delivered_words = 0;
integer p9_fifo_high_water = 0;
integer p9_stall_run = 0;
integer p9_backpressure = 0;
integer p9_ifetch_completions = 0;
integer p9_xpi_completions = 0;
integer p9_last_progress = 0;
bit p9_reported = 0;
`include "tiny_p9_monitor.svh"
always @(posedge u_dut.u_soc.u_dvp.s_pclk_sel) begin
if ($test$plusargs(
"tiny_p9_camera"
) && u_dut.u_soc.u_dvp.s_pclk_rst_n && u_dut.u_soc.u_dvp.s_core_push_valid &&
u_dut.u_soc.u_dvp.s_fifo_src_ready) begin
p9_accepted_words = p9_accepted_words + 1;
if (p9_accepted_words - p9_delivered_words > p9_fifo_high_water)
p9_fifo_high_water = p9_accepted_words - p9_delivered_words;
end
end
// Opt-in diagnostic only. These observers never drive the design and do not
// enable the serial UART character observer selected by tiny_p9_progress.
bit p9_diag_enabled = 0;
bit p9_diag_camera_started = 0;
bit p9_diag_pin15_seen = 0;
bit p9_diag_ended = 0;
integer p9_diag_file = 0;
string p9_diag_path;
string p9_diag_uart_line = "";
longint unsigned p9_diag_ref_cycles = 0;
longint unsigned p9_diag_sys_cycles = 0;
longint unsigned p9_diag_core_cycles = 0;
longint unsigned p9_diag_gpio_cycles = 0;
longint unsigned p9_diag_uart_cycles = 0;
longint unsigned p9_diag_pin15_cycle = 0;
longint unsigned p9_diag_uart_pushes = 0;
longint unsigned p9_diag_uart_pops = 0;
longint unsigned p9_diag_xpi_apb_requests = 0;
longint unsigned p9_diag_xpi_apb_responses = 0;
longint unsigned p9_diag_xpi_apb_wait = 0;
longint unsigned p9_diag_post_pin15_cycles = 100000;
bit p9_diag_xpi_apb_pending = 0;
// Endpoint numbers: 0=GPIO SYS, 1=GPIO leaf, 2=UART SYS, 3=UART leaf.
wire [3:0] p9_diag_apb_clk = {
u_dut.u_soc.s_uart0_clk, u_dut.u_soc.s_exec_clk, u_dut.u_soc.s_gpio_clk, u_dut.u_soc.s_exec_clk
};
wire [3:0] p9_diag_apb_reset = {
u_dut.u_soc.s_uart0_rst_n,
u_dut.u_soc.s_fabric_rst_n,
u_dut.u_soc.s_gpio_rst_n,
u_dut.u_soc.s_fabric_rst_n
};
wire [3:0] p9_diag_apb_sel = {
u_dut.u_soc.u_uart0_apb4_if.psel,
u_dut.u_soc.u_uart0_sys_apb4_if.psel,
u_dut.u_soc.u_gpio_apb4_if.psel,
u_dut.u_soc.u_gpio_sys_apb4_if.psel
};
wire [3:0] p9_diag_apb_enable = {
u_dut.u_soc.u_uart0_apb4_if.penable,
u_dut.u_soc.u_uart0_sys_apb4_if.penable,
u_dut.u_soc.u_gpio_apb4_if.penable,
u_dut.u_soc.u_gpio_sys_apb4_if.penable
};
wire [3:0] p9_diag_apb_ready = {
u_dut.u_soc.u_uart0_apb4_if.pready,
u_dut.u_soc.u_uart0_sys_apb4_if.pready,
u_dut.u_soc.u_gpio_apb4_if.pready,
u_dut.u_soc.u_gpio_sys_apb4_if.pready
};
wire [3:0] p9_diag_apb_error = {
u_dut.u_soc.u_uart0_apb4_if.pslverr,
u_dut.u_soc.u_uart0_sys_apb4_if.pslverr,
u_dut.u_soc.u_gpio_apb4_if.pslverr,
u_dut.u_soc.u_gpio_sys_apb4_if.pslverr
};
wire [3:0] p9_diag_apb_write = {
u_dut.u_soc.u_uart0_apb4_if.pwrite,
u_dut.u_soc.u_uart0_sys_apb4_if.pwrite,
u_dut.u_soc.u_gpio_apb4_if.pwrite,
u_dut.u_soc.u_gpio_sys_apb4_if.pwrite
};
wire [3:0][31:0] p9_diag_apb_addr = {
u_dut.u_soc.u_uart0_apb4_if.paddr,
u_dut.u_soc.u_uart0_sys_apb4_if.paddr,
u_dut.u_soc.u_gpio_apb4_if.paddr,
u_dut.u_soc.u_gpio_sys_apb4_if.paddr
};
wire [3:0][31:0] p9_diag_apb_wdata = {
u_dut.u_soc.u_uart0_apb4_if.pwdata,
u_dut.u_soc.u_uart0_sys_apb4_if.pwdata,
u_dut.u_soc.u_gpio_apb4_if.pwdata,
u_dut.u_soc.u_gpio_sys_apb4_if.pwdata
};
wire [3:0][31:0] p9_diag_apb_rdata = {
u_dut.u_soc.u_uart0_apb4_if.prdata,
u_dut.u_soc.u_uart0_sys_apb4_if.prdata,
u_dut.u_soc.u_gpio_apb4_if.prdata,
u_dut.u_soc.u_gpio_sys_apb4_if.prdata
};
wire [3:0][3:0] p9_diag_apb_strb = {
u_dut.u_soc.u_uart0_apb4_if.pstrb,
u_dut.u_soc.u_uart0_sys_apb4_if.pstrb,
u_dut.u_soc.u_gpio_apb4_if.pstrb,
u_dut.u_soc.u_gpio_sys_apb4_if.pstrb
};
initial begin
p9_diag_enabled = $test$plusargs("tiny_p9_diag");
if (p9_diag_enabled) begin
if ($test$plusargs("tiny_p9_diag_long")) p9_diag_post_pin15_cycles = 2000000;
if ($test$plusargs("tiny_p9_progress"))
$fatal(1, "P9_DIAG_ERROR diagnostic and legacy UART progress are mutually exclusive");
if (!$value$plusargs("tiny_p9_diag_log=%s", p9_diag_path))
$fatal(1, "P9_DIAG_ERROR missing diagnostic log path");
p9_diag_file = $fopen(p9_diag_path, "w");
if (p9_diag_file == 0) $fatal(1, "P9_DIAG_ERROR cannot open diagnostic log");
$fdisplay(
p9_diag_file,
"P9_DIAG_BEGIN version=1 time_unit=ns ref_interval=10000 post_pin15=%0d cycle_limit=5000000",
p9_diag_post_pin15_cycles);
$fflush(p9_diag_file);
$display("P9_DIAG_BEGIN passive GPIO/UART diagnostic only");
end
end
// The XPI controller is clocked in the memory domain, so keep this endpoint
// separate from the SYS/PCLK APB vector above. This is passive diagnostic data
// only; it does not alter APB handshakes or the controller state.
always @(posedge u_dut.u_soc.s_backend_mem_clk) begin
if (p9_diag_enabled && p9_diag_file != 0) begin
if (!u_dut.u_soc.s_xpi_rst_n) begin
p9_diag_xpi_apb_pending = 0;
p9_diag_xpi_apb_wait = 0;
end else if (u_dut.u_soc.u_xpi.apb4.psel && u_dut.u_soc.u_xpi.apb4.penable) begin
if (!p9_diag_xpi_apb_pending) begin
p9_diag_xpi_apb_pending = 1;
p9_diag_xpi_apb_requests++;
p9_diag_xpi_apb_wait = 0;
if (p9_diag_camera_started)
$fdisplay(
p9_diag_file,
"P9_DIAG_XPI_APB_REQ time_ns=%0.3f ref=%0d seq=%0d addr=%h write=%b wdata=%h strb=%h",
$realtime,
p9_diag_ref_cycles,
p9_diag_xpi_apb_requests,
u_dut.u_soc.u_xpi.apb4.paddr,
u_dut.u_soc.u_xpi.apb4.pwrite,
u_dut.u_soc.u_xpi.apb4.pwdata,
u_dut.u_soc.u_xpi.apb4.pstrb
);
end
if (u_dut.u_soc.u_xpi.apb4.pready) begin
p9_diag_xpi_apb_responses++;
if (p9_diag_camera_started)
$fdisplay(
p9_diag_file,
"P9_DIAG_XPI_APB_RESP time_ns=%0.3f ref=%0d seq=%0d wait=%0d error=%b rdata=%h",
$realtime,
p9_diag_ref_cycles,
p9_diag_xpi_apb_requests,
p9_diag_xpi_apb_wait,
u_dut.u_soc.u_xpi.apb4.pslverr,
u_dut.u_soc.u_xpi.apb4.prdata
);
p9_diag_xpi_apb_pending = 0;
p9_diag_xpi_apb_wait = 0;
end else p9_diag_xpi_apb_wait++;
end
end
end
task automatic p9_diag_finish(input string reason);
p9_diag_ended = 1;
$fdisplay(p9_diag_file, "P9_DIAG_END reason=%s ref=%0d pin15=%0d pin15_ref=%0d", reason,
p9_diag_ref_cycles, p9_diag_pin15_seen, p9_diag_pin15_cycle);
$fflush(p9_diag_file);
$fclose(p9_diag_file);
p9_diag_file = 0;
$display("P9_DIAG_END reason=%s ref=%0d diagnostic_only=1", reason, p9_diag_ref_cycles);
$finish;
endtask
always @(posedge u_dut.u_soc.u_cpu.s_cpu_clk) if (p9_diag_enabled) p9_diag_core_cycles++;
always @(posedge u_dut.u_soc.s_gpio_clk) if (p9_diag_enabled) p9_diag_gpio_cycles++;
always @(posedge u_dut.u_soc.s_exec_clk) if (p9_diag_enabled) p9_diag_sys_cycles++;
always @(posedge u_dut.u_soc.s_uart0_clk) begin
if (p9_diag_enabled && p9_diag_file != 0) begin
p9_diag_uart_cycles++;
if (u_dut.u_soc.s_uart0_rst_n) begin
if (u_dut.u_soc.u_uart0.s_tx_data_pop) p9_diag_uart_pops++;
if (u_dut.u_soc.u_uart0.u_uart_reg.s_tx_push && !u_dut.u_soc.u_uart0.s_loopback) begin
p9_diag_uart_pushes++;
if (u_dut.u_soc.u_uart0_apb4_if.pwdata[7:0] == 8'd10) begin
if (p9_diag_uart_line == "P9_STAGE CAMERA_PINS_START") p9_diag_camera_started = 1;
if (p9_diag_uart_line == "P9_STAGE CAMERA_PIN pin=15" && !p9_diag_pin15_seen) begin
p9_diag_pin15_seen = 1;
p9_diag_pin15_cycle = p9_diag_ref_cycles;
end
$fdisplay(p9_diag_file, "P9_DIAG_UART time_ns=%0.3f ref=%0d line=%s", $realtime,
p9_diag_ref_cycles, p9_diag_uart_line);
$fflush(p9_diag_file);
p9_diag_uart_line = "";
end else if (u_dut.u_soc.u_uart0_apb4_if.pwdata[7:0] != 8'd13) begin
if (p9_diag_uart_line.len() >= 256)
$fatal(1, "P9_DIAG_ERROR diagnostic UART line exceeded 256 bytes");
p9_diag_uart_line = {
p9_diag_uart_line, $sformatf("%c", u_dut.u_soc.u_uart0_apb4_if.pwdata[7:0])
};
end
end
end
end
end
for (genvar port_index = 0; port_index < 2; port_index++) begin : gen_p9_diag_cpu
longint unsigned requests = 0, responses = 0, wait_cycles = 0;
logic [31:0] last_response_addr = 0;
always @(posedge u_dut.u_soc.s_exec_clk) begin
if (p9_diag_enabled && u_dut.u_soc.s_core_rst_n) begin
if (u_dut.u_soc.u_cpu_ahbl_if[port_index].hready &&
u_dut.u_soc.u_cpu_ahbl_if[port_index].htrans[1])
requests++;
// s_terminal retires the saved request; HREADY/HTRANS alone accepts
// an address and must not be mislabeled as instruction retirement.
if (u_dut.u_soc.u_cpu_mem.s_terminal[port_index]) begin
responses++;
last_response_addr = u_dut.u_soc.u_cpu_mem.s_req_q[port_index].addr;
wait_cycles = 0;
end else if (u_dut.u_soc.u_cpu_mem.s_req_q[port_index].state != 0) wait_cycles++;
else wait_cycles = 0;
end else wait_cycles = 0;
end
always @(negedge s_clk) begin
if (p9_diag_enabled && p9_diag_file != 0 && p9_diag_ref_cycles % 10000 == 0) begin
$fdisplay(
p9_diag_file,
"P9_DIAG_CPU time_ns=%0.3f ref=%0d port=%0d requests=%0d responses=%0d wait=%0d addr=%h write=%b trans=%b ready=%b resp=%b state=%0d saved_addr=%h saved_data=%h saved_write=%b last_response_addr=%h",
$realtime, p9_diag_ref_cycles, port_index, requests, responses, wait_cycles,
u_dut.u_soc.u_cpu_ahbl_if[port_index].haddr, u_dut.u_soc.u_cpu_ahbl_if[port_index].hwrite,
u_dut.u_soc.u_cpu_ahbl_if[port_index].htrans,
u_dut.u_soc.u_cpu_ahbl_if[port_index].hready, u_dut.u_soc.u_cpu_ahbl_if[port_index].hresp,
u_dut.u_soc.u_cpu_mem.s_req_q[port_index].state,
u_dut.u_soc.u_cpu_mem.s_req_q[port_index].addr,
u_dut.u_soc.u_cpu_mem.s_req_q[port_index].data,
u_dut.u_soc.u_cpu_mem.s_req_q[port_index].write, last_response_addr);
$fflush(p9_diag_file);
end
end
end
for (genvar endpoint = 0; endpoint < 4; endpoint++) begin : gen_p9_diag_apb
bit pending = 0;
longint unsigned requests = 0, responses = 0, wait_cycles = 0;
always @(posedge p9_diag_apb_clk[endpoint]) begin
if (p9_diag_enabled && p9_diag_file != 0) begin
if (!p9_diag_apb_reset[endpoint]) begin
if (pending && p9_diag_camera_started)
$fdisplay(
p9_diag_file,
"P9_DIAG_APB_RESET time_ns=%0.3f endpoint=%0d seq=%0d",
$realtime,
endpoint,
requests
);
pending = 0;
wait_cycles = 0;
end else if (p9_diag_apb_sel[endpoint] && p9_diag_apb_enable[endpoint]) begin
if (!pending) begin
pending = 1;
requests++;
wait_cycles = 0;
if (p9_diag_camera_started)
$fdisplay(
p9_diag_file,
"P9_DIAG_APB_REQ time_ns=%0.3f endpoint=%0d seq=%0d addr=%h write=%b wdata=%h strb=%h",
$realtime,
endpoint,
requests,
p9_diag_apb_addr[endpoint],
p9_diag_apb_write[endpoint],
p9_diag_apb_wdata[endpoint],
p9_diag_apb_strb[endpoint]
);
end
if (p9_diag_apb_ready[endpoint]) begin
responses++;
if (p9_diag_camera_started)
$fdisplay(
p9_diag_file,
"P9_DIAG_APB_RESP time_ns=%0.3f endpoint=%0d seq=%0d wait=%0d error=%b rdata=%h",
$realtime,
endpoint,
requests,
wait_cycles,
p9_diag_apb_error[endpoint],
p9_diag_apb_rdata[endpoint]
);
pending = 0;
wait_cycles = 0;
end else wait_cycles++;
end
end
end
always @(negedge s_clk) begin
if (p9_diag_enabled && p9_diag_file != 0 && p9_diag_ref_cycles % 10000 == 0) begin
$fdisplay(
p9_diag_file,
"P9_DIAG_APB time_ns=%0.3f ref=%0d endpoint=%0d reset=%b pending=%b requests=%0d responses=%0d wait=%0d sel=%b enable=%b ready=%b error=%b addr=%h write=%b wdata=%h rdata=%h",
$realtime, p9_diag_ref_cycles, endpoint, p9_diag_apb_reset[endpoint], pending, requests,
responses, wait_cycles, p9_diag_apb_sel[endpoint], p9_diag_apb_enable[endpoint],
p9_diag_apb_ready[endpoint], p9_diag_apb_error[endpoint], p9_diag_apb_addr[endpoint],
p9_diag_apb_write[endpoint], p9_diag_apb_wdata[endpoint], p9_diag_apb_rdata[endpoint]);
$fflush(p9_diag_file);
end
end
end
always @(posedge s_clk) if (p9_diag_enabled) p9_diag_ref_cycles++;
// Sample a half REF cycle after the edge counters and native endpoint events.
always @(negedge s_clk) begin
if (p9_diag_enabled && p9_diag_file != 0 && !p9_diag_ended) begin
if (p9_diag_ref_cycles % 10000 == 0) begin
$fdisplay(
p9_diag_file,
"P9_DIAG_CLOCK time_ns=%0.3f ref=%0d sys=%0d core=%0d gpio=%0d uart=%0d boot=%b core_reset=%b fabric_reset=%b gated=%h disabled=%h clock_block=%b periph_block=%b",
$realtime, p9_diag_ref_cycles, p9_diag_sys_cycles, p9_diag_core_cycles,
p9_diag_gpio_cycles, p9_diag_uart_cycles, u_dut.u_soc.s_boot_release_q,
u_dut.u_soc.s_core_rst_n, u_dut.u_soc.s_fabric_rst_n, u_dut.u_soc.s_actual_gate,
u_dut.u_soc.s_disabled_sys, u_dut.u_soc.s_clock_block, u_dut.u_soc.s_periph_block);
$fdisplay(
p9_diag_file,
"P9_DIAG_SLOW time_ns=%0.3f ref=%0d owner=%b state=%0d addr=%h write=%b ahb_trans=%b ahb_ready=%b ahb_resp=%b aw=%b/%b w=%b/%b b=%b/%b bresp=%b ar=%b/%b r=%b/%b rresp=%b",
$realtime, p9_diag_ref_cycles, u_dut.u_soc.u_cpu_mem.s_slow_q.owner,
u_dut.u_soc.u_cpu_mem.s_slow_q.state, u_dut.u_soc.u_cpu_mem.s_slow_q.addr,
u_dut.u_soc.u_cpu_mem.s_slow_q.write, u_dut.u_soc.u_cpu_mem.u_slow_ahbl_if.htrans,
u_dut.u_soc.u_cpu_mem.u_slow_ahbl_if.hready, u_dut.u_soc.u_cpu_mem.u_slow_ahbl_if.hresp,
u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.awvalid,
u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.awready,
u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.wvalid, u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.wready,
u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.bvalid, u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.bready,
u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.bresp, u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.arvalid,
u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.arready, u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.rvalid,
u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.rready, u_dut.u_soc.u_cpu_mem.u_slow_axi4_if.rresp);
$fdisplay(
p9_diag_file,
"P9_DIAG_CDC time_ns=%0.3f ref=%0d gpio_src=%0d gpio_dst=%0d gpio_link=%b/%b gpio_ready=%b uart_src=%0d uart_dst=%0d uart_link=%b/%b uart_ready=%b",
$realtime, p9_diag_ref_cycles, u_dut.u_soc.u_gpio_cfg_cdc.s_src_q.state,
u_dut.u_soc.u_gpio_cfg_cdc.s_dst_q.state, u_dut.u_soc.u_gpio_cfg_cdc.s_link_src_rst_n,
u_dut.u_soc.u_gpio_cfg_cdc.s_link_dst_rst_n, u_dut.u_soc.s_apb_cdc_ready[0],
u_dut.u_soc.u_uart0_cfg_cdc.s_src_q.state, u_dut.u_soc.u_uart0_cfg_cdc.s_dst_q.state,
u_dut.u_soc.u_uart0_cfg_cdc.s_link_src_rst_n,
u_dut.u_soc.u_uart0_cfg_cdc.s_link_dst_rst_n, u_dut.u_soc.s_apb_cdc_ready[1]);
$fdisplay(
p9_diag_file,
"P9_DIAG_TX time_ns=%0.3f ref=%0d pushes=%0d pops=%0d count=%0d full=%b enable=%b config_valid=%b busy=%b pop=%b flow_blocked=%b baud_int=%0d baud_frac=%0d",
$realtime, p9_diag_ref_cycles, p9_diag_uart_pushes, p9_diag_uart_pops,
u_dut.u_soc.u_uart0.u_uart_reg.s_tx_count, u_dut.u_soc.u_uart0.u_uart_reg.s_tx_full,
u_dut.u_soc.u_uart0.s_tx_en, u_dut.u_soc.u_uart0.u_uart_reg.s_config_valid,
u_dut.u_soc.u_uart0.s_tx_busy, u_dut.u_soc.u_uart0.s_tx_data_pop,
u_dut.u_soc.u_uart0.s_tx_flow_blocked, u_dut.u_soc.u_uart0.s_baud_int,
u_dut.u_soc.u_uart0.s_baud_frac);
$fdisplay(
p9_diag_file,
"P9_DIAG_XPI time_ns=%0.3f ref=%0d apb_pending=%b apb_req=%0d apb_resp=%0d apb_wait=%0d controller=%b mm_busy=%b indirect_busy=%b core_busy=%b owner=%0d tx_count=%0d rx_count=%0d core_start=%b core_done=%b core_err=%b err_event=%b err_code=%0d err_addr=%h",
$realtime, p9_diag_ref_cycles, p9_diag_xpi_apb_pending, p9_diag_xpi_apb_requests,
p9_diag_xpi_apb_responses, p9_diag_xpi_apb_wait, u_dut.u_soc.u_xpi.s_controller_en,
u_dut.u_soc.u_xpi.s_mm_busy, u_dut.u_soc.u_xpi.s_indirect_busy,
u_dut.u_soc.u_xpi.s_core_busy, u_dut.u_soc.u_xpi.s_owner_q,
u_dut.u_soc.u_xpi.s_tx_fifo_count, u_dut.u_soc.u_xpi.s_rx_fifo_count,
u_dut.u_soc.u_xpi.s_core_start, u_dut.u_soc.u_xpi.s_core_done,
u_dut.u_soc.u_xpi.s_core_err, u_dut.u_soc.u_xpi.s_err_event,
u_dut.u_soc.u_xpi.s_err_code, u_dut.u_soc.u_xpi.s_err_addr);
if ((u_dut.u_soc.u_sram.gen_group[0].u_group.s_addr[0] == 32'h30002a4c) ||
(u_dut.u_soc.u_sram.gen_group[0].u_group.s_read_pending_q &&
(u_dut.u_soc.u_sram.gen_group[0].u_group.s_read_owner_q == 0))) begin
$fdisplay(
p9_diag_file,
"P9_DIAG_SRAM time_ns=%0.3f ref=%0d addr=%h req=%b ready=%b grant=%b issue=%b pending=%b owner=%0d bank=%0d bank_sel=%b rsp_valid=%b rsp_ready=%b rdata=%h bank_data=%h",
$realtime, p9_diag_ref_cycles, u_dut.u_soc.u_sram.gen_group[0].u_group.s_addr[0],
u_dut.u_soc.u_sram.gen_group[0].u_group.s_req[0],
u_dut.u_soc.u_sram.gen_group[0].u_group.s_grant[0],
u_dut.u_soc.u_sram.gen_group[0].u_group.s_grant[0],
u_dut.u_soc.u_sram.gen_group[0].u_group.s_issue,
u_dut.u_soc.u_sram.gen_group[0].u_group.s_read_pending_q,
u_dut.u_soc.u_sram.gen_group[0].u_group.s_read_owner_q,
u_dut.u_soc.u_sram.gen_group[0].u_group.s_read_bank_q,
u_dut.u_soc.u_sram.gen_group[0].u_group.s_bank_select[2],
u_dut.u_soc.u_sram.gen_group[0].u_group.s_rsp_valid_q[0],
u_dut.u_soc.u_cpu_mem.s_req_q[0].state == 3,
u_dut.u_soc.u_sram.gen_group[0].u_group.s_rsp_data_q[0],
u_dut.u_soc.u_sram.gen_group[0].u_group.s_bank_data[2]);
end
$fflush(p9_diag_file);
end
if (p9_diag_pin15_seen && p9_diag_ref_cycles - p9_diag_pin15_cycle >= p9_diag_post_pin15_cycles)
p9_diag_finish("post_pin15");
else if (p9_diag_ref_cycles >= 5000000) p9_diag_finish("cycle_limit");
end
end
always @(posedge u_dut.u_soc.s_dvp_clk) begin
if ($test$plusargs("tiny_p9_camera") && u_dut.u_soc.s_dvp_rst_n) begin
if (u_dut.u_soc.u_dvp.s_fifo_dst_valid && u_dut.u_soc.u_dvp.s_fifo_dst_ready)
p9_delivered_words = p9_delivered_words + 1;
if (u_dut.u_soc.u_dvp.s_fifo_dst_valid && !u_dut.u_soc.u_dvp.s_fifo_dst_ready) begin
p9_stall_run = p9_stall_run + 1;
if (p9_stall_run > p9_backpressure) p9_backpressure = p9_stall_run;
end else p9_stall_run = 0;
end
end
always @(posedge s_clk) begin
if (u_dut.u_soc.u_cpu_ahbl_if[0].hready && u_dut.u_soc.u_cpu_ahbl_if[0].htrans[1]) begin
p9_ifetch_completions = p9_ifetch_completions + 1;
p9_last_progress = s_cycles;
end
if (u_dut.u_soc.u_xpi_mem_axi4_if.rvalid && u_dut.u_soc.u_xpi_mem_axi4_if.rready) begin
p9_xpi_completions = p9_xpi_completions + 1;
p9_last_progress = s_cycles;
end
if ($test$plusargs("tiny_p9_progress") && s_cycles % 10000 == 0)
$display(
"P9_BOOT_PROGRESS cycle=%0d ifetch_address=%h ifetch_completed=%0d xpi_beats=%0d last_progress=%0d",
s_cycles,
u_dut.u_soc.u_cpu_ahbl_if[0].haddr,
p9_ifetch_completions,
p9_xpi_completions,
p9_last_progress
);
if ($test$plusargs("tiny_p9_camera") && u_dut.s_test_done && !p9_reported) begin
p9_reported = 1;
$display(
"P9_OBSERVATION version=1 accepted_words=%0d delivered_words=%0d fifo_high_water=%0d backpressure=%0d pixel_half_ns=%0d",
p9_accepted_words, p9_delivered_words, p9_fifo_high_water, p9_backpressure,
u_dvp_camera.pixel_half_ns);
end
end
+52
View File
@@ -0,0 +1,52 @@
// Copyright (c) 2026 Yuchi Miao; SPDX-License-Identifier: MulanPSL-2.0
// Verification board control. Observe firmware UART writes; only change the
// external board population and camera model, never a DUT signal or register.
string s_p9_recovery_line = "";
integer s_p9_recovery_controls = 0;
bit s_p9_recovery_reported = 0;
always @(posedge s_clk) begin
if ($test$plusargs("tiny_p9_recovery") && u_dut.s_test_done && !s_p9_recovery_reported) begin
s_p9_recovery_reported = 1;
$display("P9_RECOVERY_BOARD controls=%0d audio_cases=%0d", s_p9_recovery_controls,
s_p6_wire_cases);
end
end
always @(posedge u_dut.u_soc.s_uart0_clk) begin
if ($test$plusargs(
"tiny_p9_recovery"
) && u_dut.u_soc.s_uart0_rst_n && u_dut.u_soc.u_uart0.u_uart_reg.s_tx_push &&
!u_dut.u_soc.u_uart0.s_loopback) begin
if (u_dut.u_soc.u_uart0_apb4_if.pwdata[7:0] == 8'd10) begin
case (s_p9_recovery_line)
"P9_CTRL camera": begin
s_p9_camera_population = 1;
s_p9_recovery_controls++;
end
"P9_CTRL audio": begin
s_p9_camera_population = 0;
s_p9_recovery_controls++;
end
"P9_CTRL stop_low": begin
u_dvp_camera.recovery_pause = 1;
s_p9_recovery_controls++;
end
"P9_CTRL stop_high": begin
u_dvp_camera.recovery_pause = 2;
s_p9_recovery_controls++;
end
"P9_CTRL resume": begin
u_dvp_camera.recovery_pause = 0;
s_p9_recovery_controls++;
end
default: begin
end
endcase
s_p9_recovery_line = "";
end else if (u_dut.u_soc.u_uart0_apb4_if.pwdata[7:0] != 8'd13) begin
if (s_p9_recovery_line.len() >= 256) $fatal(1, "SIM_TEST_FAIL recovery UART record overflow");
s_p9_recovery_line = {
s_p9_recovery_line, $sformatf("%c", u_dut.u_soc.u_uart0_apb4_if.pwdata[7:0])
};
end
end
end
+1
View File
@@ -18,5 +18,6 @@
+incdir+/ip/peripheral +incdir+/ip/peripheral
+incdir+/ip/core +incdir+/ip/core
+incdir+/ip/security +incdir+/ip/security
+incdir+/ip/multimedia
+incdir+/clusterip/rng/rtl +incdir+/clusterip/rng/rtl
+incdir+/clusterip/crc/rtl +incdir+/clusterip/crc/rtl
+4
View File
@@ -84,3 +84,7 @@
/ip/storage/sdio_reg.sv /ip/storage/sdio_reg.sv
/ip/storage/sdio_core.sv /ip/storage/sdio_core.sv
/ip/storage/apb4_sdio.sv /ip/storage/apb4_sdio.sv
/ip/multimedia/dvp_define.svh
/ip/multimedia/dvp_core.sv
/ip/multimedia/dvp_reg.sv
/ip/multimedia/axi4s_dvp.sv
+1
View File
@@ -1,4 +1,5 @@
/dv/tiny_qspi_nor.sv /dv/tiny_qspi_nor.sv
/device_model/ESP_PSRAM64H.sv /device_model/ESP_PSRAM64H.sv
/dv/tiny_dvp_camera.sv
/tests/rtl/sdio_native_model.sv /tests/rtl/sdio_native_model.sv
/dv/retrosoc_tiny_tb.sv /dv/retrosoc_tiny_tb.sv
+76 -3
View File
@@ -4,7 +4,7 @@
"phase": "TINY-R2-P7", "phase": "TINY-R2-P7",
"clock_domain": "system", "clock_domain": "system",
"target_controllers": "u_soc.u_fabric.gen_target[%d].u_controller", "target_controllers": "u_soc.u_fabric.gen_target[%d].u_controller",
"target_count": 27, "target_count": 28,
"apb_bridges": "SYS frontends; acknowledged per-endpoint crossings to AON, PCLK and MEM", "apb_bridges": "SYS frontends; acknowledged per-endpoint crossings to AON, PCLK and MEM",
"fabric_reset": "fabric leaf; target reset blocks admission and aborts accepted target work with DECERR", "fabric_reset": "fabric leaf; target reset blocks admission and aborts accepted target work with DECERR",
"hart_reset": "CPU-only accepted-work drain; DMA and all target responses stay active", "hart_reset": "CPU-only accepted-work drain; DMA and all target responses stay active",
@@ -116,6 +116,21 @@
"period_ns": 54.253472222, "period_ns": 54.253472222,
"async_group": "audio" "async_group": "audio"
} }
},
{
"name": "dvp_pixel",
"clock": "dvp_pixel_clk",
"reset": "dvp_pixel_rst_n",
"reset_source": "gpio_20_io_pad",
"reset_primitive": "rst_sync",
"path": "rtl/ip/multimedia/axi4s_dvp.sv",
"instance": "u_dvp_pclk_rst_sync",
"sta": {
"source_port": "gpio_20_io_pad",
"object_type": "port",
"period_ns": 83.333333333,
"async_group": "dvp_pixel"
}
} }
], ],
"reset_distribution": { "reset_distribution": {
@@ -145,7 +160,8 @@
"ws2812", "ws2812",
"crypto", "crypto",
"i2s", "i2s",
"sdio" "sdio",
"dvp"
], ],
"cpu_prerequisite": "all global leaf resets and actual APB/DMA/XPI/RCU barriers; boot readiness latches until the next system reset", "cpu_prerequisite": "all global leaf resets and actual APB/DMA/XPI/RCU barriers; boot readiness latches until the next system reset",
"hart": "u_soc.u_cpu.u_mgmt_debug_wrapper.u_mgmt_debug_reset.u_core_rst_sync", "hart": "u_soc.u_cpu.u_mgmt_debug_wrapper.u_mgmt_debug_reset.u_core_rst_sync",
@@ -319,6 +335,62 @@
"path": "rtl/ip/serial/apb4_i2s.sv", "path": "rtl/ip/serial/apb4_i2s.sv",
"instance": "u_rx_push_cnt_cdc" "instance": "u_rx_push_cnt_cdc"
}, },
{
"name": "dvp_configuration",
"source": "peripheral",
"destination": "dvp_pixel",
"primitive": "cdc_2phase",
"path": "rtl/ip/multimedia/axi4s_dvp.sv",
"instance": "u_dvp_config_cdc"
},
{
"name": "dvp_commands",
"source": "peripheral",
"destination": "dvp_pixel",
"primitive": "cdc_2phase",
"path": "rtl/ip/multimedia/axi4s_dvp.sv",
"instance": "u_dvp_command_cdc"
},
{
"name": "dvp_error_clear",
"source": "peripheral",
"destination": "dvp_pixel",
"primitive": "cdc_2phase",
"path": "rtl/ip/multimedia/axi4s_dvp.sv",
"instance": "u_error_clear_cdc"
},
{
"name": "dvp_status",
"source": "dvp_pixel",
"destination": "peripheral",
"primitive": "cdc_2phase",
"path": "rtl/ip/multimedia/axi4s_dvp.sv",
"instance": "u_status_cdc"
},
{
"name": "dvp_errors",
"source": "dvp_pixel",
"destination": "peripheral",
"primitive": "cdc_2phase",
"path": "rtl/ip/multimedia/axi4s_dvp.sv",
"instance": "u_error_events_cdc"
},
{
"name": "dvp_payload",
"source": "dvp_pixel",
"destination": "peripheral",
"primitive": "cdc_fifo_warm_flush",
"path": "rtl/ip/multimedia/axi4s_dvp.sv",
"instance": "u_dvp_payload_fifo"
},
{
"name": "dvp_statistics",
"source": "dvp_pixel",
"destination": "peripheral",
"primitive": "cdc_2phase",
"path": "rtl/ip/multimedia/axi4s_dvp.sv",
"instance": "u_dvp_stats_cdc"
},
{ {
"name": "dma_axi", "name": "dma_axi",
"source": "peripheral", "source": "peripheral",
@@ -407,7 +479,8 @@
"crc", "crc",
"ws2812", "ws2812",
"crypto", "crypto",
"i2s" "i2s",
"dvp"
] ]
}, },
"same_clock_isolation": [ "same_clock_isolation": [
+48 -15
View File
@@ -37,9 +37,9 @@
], ],
"xpi_target": 4, "xpi_target": 4,
"apb_target_base": 5, "apb_target_base": 5,
"decode_error_target": 25, "decode_error_target": 26,
"protocol_error_target": 26, "protocol_error_target": 27,
"target_count": 27 "target_count": 28
}, },
"gpio_pins": 32, "gpio_pins": 32,
"irq_vector_width": 32, "irq_vector_width": 32,
@@ -166,6 +166,12 @@
"regions": [ "regions": [
"APB4_SDIO0" "APB4_SDIO0"
] ]
},
{
"name": "dvp",
"regions": [
"APB4_DVP"
]
} }
], ],
"interrupts": [ "interrupts": [
@@ -214,6 +220,11 @@
"core_bit": 10, "core_bit": 10,
"signal": "u_sdio_if.irq_o" "signal": "u_sdio_if.irq_o"
}, },
{
"name": "dvp",
"core_bit": 15,
"signal": "s_dvp_irq"
},
{ {
"name": "pwm", "name": "pwm",
"core_bit": 11, "core_bit": 11,
@@ -441,7 +452,9 @@
"oe": "1'b1" "oe": "1'b1"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.dat_i[0]"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -454,7 +467,9 @@
"oe": "1'b1" "oe": "1'b1"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.dat_i[1]"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -467,7 +482,9 @@
"oe": "1'b1" "oe": "1'b1"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.dat_i[2]"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -482,7 +499,9 @@
"oe": "1'b0" "oe": "1'b0"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.dat_i[3]"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -495,7 +514,9 @@
"oe": "1'b1" "oe": "1'b1"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.dat_i[4]"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -510,7 +531,9 @@
"oe": "1'b0" "oe": "1'b0"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.dat_i[5]"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -523,7 +546,9 @@
"oe": "u_pwm_if.oe_o[0]" "oe": "u_pwm_if.oe_o[0]"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.dat_i[6]"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -536,7 +561,9 @@
"oe": "u_pwm_if.oe_o[1]" "oe": "u_pwm_if.oe_o[1]"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.dat_i[7]"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -549,7 +576,9 @@
"oe": "u_pwm_if.oe_o[2]" "oe": "u_pwm_if.oe_o[2]"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.pclk_i"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -562,7 +591,9 @@
"oe": "u_pwm_if.oe_o[3]" "oe": "u_pwm_if.oe_o[3]"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.href_i"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -577,7 +608,9 @@
"oe": "1'b0" "oe": "1'b0"
}, },
"alt1": { "alt1": {
"inputs": [], "inputs": [
"u_dvp_if.vsync_i"
],
"do": "1'b0", "do": "1'b0",
"oe": "1'b0" "oe": "1'b0"
} }
@@ -706,7 +739,7 @@
} }
} }
], ],
"dma_request_mask": "0x31ff", "dma_request_mask": "0x39ff",
"private_crypto": { "private_crypto": {
"banks": 6, "banks": 6,
"bank_bytes": 4096, "bank_bytes": 4096,
+25 -20
View File
@@ -89,15 +89,15 @@ module retrosoc_tiny (
logic [31:0] s_clkout_control; logic [31:0] s_clkout_control;
logic s_clkout, s_clkout_enable; logic s_clkout, s_clkout_enable;
logic s_clkout_idle_ref; logic s_clkout_idle_ref;
logic [13:0] s_gate_request, s_local_reset, s_actual_gate, s_reset_held, s_reset_ready; logic [14:0] s_gate_request, s_local_reset, s_actual_gate, s_reset_held, s_reset_ready;
logic [13:0] s_gate_ref, s_held_ref, s_ready_ref, s_disabled_sys; logic [14:0] s_gate_ref, s_held_ref, s_ready_ref, s_disabled_sys;
logic s_periph_apply, s_periph_reset_command, s_periph_block, s_periph_done; logic s_periph_apply, s_periph_reset_command, s_periph_block, s_periph_done;
logic [13:0] s_periph_request, s_periph_reset_done; logic [14:0] s_periph_request, s_periph_reset_done;
logic [4:0] s_periph_fault; logic [4:0] s_periph_fault;
logic s_bus_idle_ref; logic s_bus_idle_ref;
logic s_boot_release_d, s_boot_release_q; logic s_boot_release_d, s_boot_release_q;
logic s_wfi_gate_sys; logic s_wfi_gate_sys;
logic [13:0] s_periph_idle, s_periph_idle_ref; logic [14:0] s_periph_idle, s_periph_idle_ref;
logic s_xpi_idle, s_xpi_idle_ref; logic s_xpi_idle, s_xpi_idle_ref;
logic s_pwm_write, s_crc_write; logic s_pwm_write, s_crc_write;
logic [ 4:0] s_clock_fault; logic [ 4:0] s_clock_fault;
@@ -109,6 +109,7 @@ module retrosoc_tiny (
logic s_crypto_irq, s_crypto_input_ready, s_crypto_output_ready; logic s_crypto_irq, s_crypto_input_ready, s_crypto_output_ready;
logic s_i2s_tx_stall, s_i2s_rx_stall; logic s_i2s_tx_stall, s_i2s_rx_stall;
logic s_i2s_reset_ready; logic s_i2s_reset_ready;
logic s_dvp_irq, s_dvp_idle, s_dvp_reset_ready;
logic s_done_pclk_rst_n, s_done_mem_rst_n; logic s_done_pclk_rst_n, s_done_mem_rst_n;
`include "tiny_apb_interfaces.svh" `include "tiny_apb_interfaces.svh"
ahbl_if u_cpu_ahbl_if[2] ( ahbl_if u_cpu_ahbl_if[2] (
@@ -133,6 +134,7 @@ ahbl_if u_cpu_ahbl_if[2] (
user_gpio_if u_user_gpio_if (); user_gpio_if u_user_gpio_if ();
dma_req_if u_dma_req_if (); dma_req_if u_dma_req_if ();
i2s_if u_i2s_if (); i2s_if u_i2s_if ();
dvp_if u_dvp_if ();
ws2812_if u_ws2812_if (); ws2812_if u_ws2812_if ();
sdio_if u_sdio_if (); sdio_if u_sdio_if ();
rtc_if u_rtc_if ( rtc_if u_rtc_if (
@@ -394,7 +396,7 @@ ahbl_if u_cpu_ahbl_if[2] (
assign s_reset_ready[0] = 1'b0; assign s_reset_ready[0] = 1'b0;
cdc_sync #( cdc_sync #(
.STAGE (2), .STAGE (2),
.DATA_WIDTH(14) .DATA_WIDTH(15)
) u_gate_feedback ( ) u_gate_feedback (
.clk_i (s_ref_clk), .clk_i (s_ref_clk),
.rst_n_i(s_por_rst_n), .rst_n_i(s_por_rst_n),
@@ -403,7 +405,7 @@ ahbl_if u_cpu_ahbl_if[2] (
); );
cdc_sync #( cdc_sync #(
.STAGE (2), .STAGE (2),
.DATA_WIDTH(14) .DATA_WIDTH(15)
) u_held_feedback ( ) u_held_feedback (
.clk_i (s_ref_clk), .clk_i (s_ref_clk),
.rst_n_i(s_por_rst_n), .rst_n_i(s_por_rst_n),
@@ -412,7 +414,7 @@ ahbl_if u_cpu_ahbl_if[2] (
); );
cdc_sync #( cdc_sync #(
.STAGE (2), .STAGE (2),
.DATA_WIDTH(14) .DATA_WIDTH(15)
) u_ready_feedback ( ) u_ready_feedback (
.clk_i (s_ref_clk), .clk_i (s_ref_clk),
.rst_n_i(s_por_rst_n), .rst_n_i(s_por_rst_n),
@@ -421,7 +423,7 @@ ahbl_if u_cpu_ahbl_if[2] (
); );
cdc_sync #( cdc_sync #(
.STAGE (2), .STAGE (2),
.DATA_WIDTH(14) .DATA_WIDTH(15)
) u_disabled_sync ( ) u_disabled_sync (
.clk_i (s_exec_clk), .clk_i (s_exec_clk),
.rst_n_i(s_fabric_rst_n), .rst_n_i(s_fabric_rst_n),
@@ -479,7 +481,7 @@ ahbl_if u_cpu_ahbl_if[2] (
assign s_periph_idle[11] = !s_rng_en && !s_rng_valid; assign s_periph_idle[11] = !s_rng_en && !s_rng_valid;
cdc_sync #( cdc_sync #(
.STAGE (2), .STAGE (2),
.DATA_WIDTH(14) .DATA_WIDTH(15)
) u_periph_idle_sync ( ) u_periph_idle_sync (
.clk_i (s_ref_clk), .clk_i (s_ref_clk),
.rst_n_i(s_por_rst_n), .rst_n_i(s_por_rst_n),
@@ -798,7 +800,7 @@ axi4_error_slave #(
.PllPresent (TinyPllPresent), .PllPresent (TinyPllPresent),
.PllProfiles(TinyPllPresent), .PllProfiles(TinyPllPresent),
.XinHzValue (`SOC_EXT_CLK_HZ), .XinHzValue (`SOC_EXT_CLK_HZ),
.TargetMask (14'h3fff) .TargetMask (15'h07fff)
) u_sysctrl ( ) u_sysctrl (
.clk_i (s_exec_clk), .clk_i (s_exec_clk),
.rst_n_i (s_sysctrl_rst_n), .rst_n_i (s_sysctrl_rst_n),
@@ -858,7 +860,7 @@ axi4_error_slave #(
.ref_clk_i (clk_i), .ref_clk_i (clk_i),
.rst_n_i (rst_n_i), .rst_n_i (rst_n_i),
.idle_i (s_clock_safe), .idle_i (s_clock_safe),
.clients_idle_i ((&s_periph_idle_ref[13:1]) && s_xpi_idle_ref), .clients_idle_i ((&s_periph_idle_ref[14:1]) && s_xpi_idle_ref),
.warm_reset_i (u_wdg_if.reset_req_o), .warm_reset_i (u_wdg_if.reset_req_o),
.system_request_i (s_system_reset), .system_request_i (s_system_reset),
.command_ack_i (s_cpu_clock_idle && s_target_idle[`RETROSOC_TINY__TARGET_SYSCTRL]), .command_ack_i (s_cpu_clock_idle && s_target_idle[`RETROSOC_TINY__TARGET_SYSCTRL]),
@@ -989,11 +991,21 @@ cdc_sync #(
.apb4(u_wdg_apb4_if), .apb4(u_wdg_apb4_if),
.wdg (u_wdg_if) .wdg (u_wdg_if)
); );
axi4s_dvp u_dvp (
.clk_i (s_dvp_clk),
.rst_n_i (s_dvp_rst_n),
.apb4 (u_dvp_apb4_if),
.rx_axis (u_dvp_rx_axis_if),
.dvp (u_dvp_if),
.irq_o (s_dvp_irq),
.lifecycle_idle_o (s_dvp_idle),
.lifecycle_reset_ready_o(s_dvp_reset_ready)
);
apb4_dma #( apb4_dma #(
.NumChannels (8), .NumChannels (8),
.MaxBurstBeats(16), .MaxBurstBeats(16),
.FifoDepth (32), .FifoDepth (32),
.RequestMask (16'h31ff), .RequestMask (16'h39ff),
.EnableStreams(1'b1) .EnableStreams(1'b1)
) u_dma ( ) u_dma (
.lifecycle_idle_o(s_periph_idle[9]), .lifecycle_idle_o(s_periph_idle[9]),
@@ -1022,14 +1034,7 @@ cdc_sync #(
assign u_dma_req_if.i2c0_rx_proc = !s_dma_stall[5]; assign u_dma_req_if.i2c0_rx_proc = !s_dma_stall[5];
assign u_dma_req_if.i2c1_tx_proc = 1'b0; assign u_dma_req_if.i2c1_tx_proc = 1'b0;
assign u_dma_req_if.i2c1_rx_proc = 1'b0; assign u_dma_req_if.i2c1_rx_proc = 1'b0;
assign u_dvp_rx_axis_if.tvalid = '0; assign s_periph_idle[14] = s_dvp_idle && s_dma_idle_sys;
assign u_dvp_rx_axis_if.tdata = '0;
assign u_dvp_rx_axis_if.tstrb = '0;
assign u_dvp_rx_axis_if.tkeep = '0;
assign u_dvp_rx_axis_if.tlast = '0;
assign u_dvp_rx_axis_if.tid = '0;
assign u_dvp_rx_axis_if.tdest = '0;
assign u_dvp_rx_axis_if.tuser = '0;
// Host logic remains SAFE24. Audio reset waits for its own clock, never CPU boot. // Host logic remains SAFE24. Audio reset waits for its own clock, never CPU boot.
tc_clk_buf u_audio_clock_buffer ( tc_clk_buf u_audio_clock_buffer (
+14 -14
View File
@@ -9,19 +9,19 @@ module tiny_peripheral_control (
input logic warm_reset_i, input logic warm_reset_i,
input logic apply_i, input logic apply_i,
input logic reset_command_i, input logic reset_command_i,
input logic [13:0] request_i, input logic [14:0] request_i,
input logic [13:0] idle_i, input logic [14:0] idle_i,
input logic bus_idle_i, input logic bus_idle_i,
input logic [13:0] gate_actual_i, input logic [14:0] gate_actual_i,
input logic [13:0] reset_held_i, input logic [14:0] reset_held_i,
input logic [13:0] reset_ready_i, input logic [14:0] reset_ready_i,
input logic [31:0] timeout_i, input logic [31:0] timeout_i,
output logic [13:0] gate_o, output logic [14:0] gate_o,
output logic [13:0] reset_o, output logic [14:0] reset_o,
output logic block_o, output logic block_o,
output logic done_o, output logic done_o,
output logic [ 4:0] fault_o, output logic [ 4:0] fault_o,
output logic [13:0] reset_done_o output logic [14:0] reset_done_o
); );
typedef enum logic [2:0] { typedef enum logic [2:0] {
Idle, Idle,
@@ -34,20 +34,20 @@ module tiny_peripheral_control (
typedef struct packed { typedef struct packed {
state_e state; state_e state;
logic reset_command; logic reset_command;
logic [13:0] request_mask, gates, previous_gates, resets, reset_done, affected; logic [14:0] request_mask, gates, previous_gates, resets, reset_done, affected;
logic [31:0] count; logic [31:0] count;
logic done; logic done;
logic [4:0] fault; logic [4:0] fault;
} control_t; } control_t;
control_t s_ctrl_d, s_ctrl_q; control_t s_ctrl_d, s_ctrl_q;
logic [13:0] s_affected; logic [14:0] s_affected;
assign s_affected = s_ctrl_q.affected; assign s_affected = s_ctrl_q.affected;
assign gate_o = s_ctrl_q.gates; assign gate_o = s_ctrl_q.gates;
assign reset_o = s_ctrl_q.resets; assign reset_o = s_ctrl_q.resets;
assign reset_done_o = s_ctrl_q.reset_done; assign reset_done_o = s_ctrl_q.reset_done;
// Arming the WFI leaf permission must not pause an unrelated DMA job. // Arming the WFI leaf permission must not pause an unrelated DMA job.
// The CPU leaf itself waits for its accepted I/D work before stopping. // The CPU leaf itself waits for its accepted I/D work before stopping.
assign block_o = (s_ctrl_q.state != Idle) && (s_affected[13:1] != 0); assign block_o = (s_ctrl_q.state != Idle) && (s_affected[14:1] != 0);
assign done_o = s_ctrl_q.done; assign done_o = s_ctrl_q.done;
assign fault_o = s_ctrl_q.fault; assign fault_o = s_ctrl_q.fault;
always_comb begin always_comb begin
@@ -74,8 +74,8 @@ module tiny_peripheral_control (
end end
end end
Drain: begin Drain: begin
if (s_ctrl_q.count >= 16 && ((s_affected[13:1] == 0) || (bus_idle_i && idle_i[9])) && if (s_ctrl_q.count >= 16 && ((s_affected[14:1] == 0) || (bus_idle_i && idle_i[9])) &&
((idle_i[13:1] & s_affected[13:1]) == s_affected[13:1])) begin ((idle_i[14:1] & s_affected[14:1]) == s_affected[14:1])) begin
s_ctrl_d.count = 0; s_ctrl_d.count = 0;
if (s_ctrl_q.reset_command) begin if (s_ctrl_q.reset_command) begin
s_ctrl_d.resets = s_ctrl_q.request_mask; s_ctrl_d.resets = s_ctrl_q.request_mask;
@@ -90,7 +90,7 @@ module tiny_peripheral_control (
s_ctrl_d.state = Idle; s_ctrl_d.state = Idle;
end end
end end
GateWait: if (gate_actual_i[13:1] == s_ctrl_q.gates[13:1]) s_ctrl_d.state = Complete; GateWait: if (gate_actual_i[14:1] == s_ctrl_q.gates[14:1]) s_ctrl_d.state = Complete;
Hold: Hold:
if ((reset_held_i & s_ctrl_q.request_mask) == s_ctrl_q.request_mask) begin if ((reset_held_i & s_ctrl_q.request_mask) == s_ctrl_q.request_mask) begin
s_ctrl_d.resets = 0; s_ctrl_d.resets = 0;
+19 -19
View File
@@ -7,7 +7,7 @@ module tiny_sysctrl #(
parameter bit PllPresent = 1'b0, parameter bit PllPresent = 1'b0,
parameter bit PllProfiles = 1'b0, parameter bit PllProfiles = 1'b0,
parameter int unsigned XinHzValue = 24_000_000, parameter int unsigned XinHzValue = 24_000_000,
parameter logic [13:0] TargetMask = 14'd0 parameter logic [14:0] TargetMask = 15'd0
) ( ) (
input logic clk_i, input logic clk_i,
input logic rst_n_i, input logic rst_n_i,
@@ -37,11 +37,11 @@ module tiny_sysctrl #(
output logic [31:0] clock_timeout_o, output logic [31:0] clock_timeout_o,
input logic periph_done_i, input logic periph_done_i,
input logic [ 4:0] periph_fault_i, input logic [ 4:0] periph_fault_i,
input logic [13:0] gate_actual_i, input logic [14:0] gate_actual_i,
input logic [13:0] reset_done_i, input logic [14:0] reset_done_i,
output logic periph_apply_o, output logic periph_apply_o,
output logic periph_reset_o, output logic periph_reset_o,
output logic [13:0] periph_request_o, output logic [14:0] periph_request_o,
output logic [31:0] clkout_control_o, output logic [31:0] clkout_control_o,
input logic clkout_idle_i, input logic clkout_idle_i,
output logic perf_enable_o, output logic perf_enable_o,
@@ -106,7 +106,7 @@ module tiny_sysctrl #(
logic [1:0] s_clk_latched_d, s_clk_latched_q; logic [1:0] s_clk_latched_d, s_clk_latched_q;
logic s_system_reset_d, s_system_reset_q; logic s_system_reset_d, s_system_reset_q;
logic s_periph_apply_d, s_periph_apply_q, s_periph_reset_d, s_periph_reset_q; logic s_periph_apply_d, s_periph_apply_q, s_periph_reset_d, s_periph_reset_q;
logic [13:0] s_periph_mask_d, s_periph_mask_q; logic [14:0] s_periph_mask_d, s_periph_mask_q;
logic [3:0] s_active_cmd_d, s_active_cmd_q; logic [3:0] s_active_cmd_d, s_active_cmd_q;
logic s_clock_apply_d, s_clock_apply_q; logic s_clock_apply_d, s_clock_apply_q;
logic s_rcu_busy_d, s_rcu_busy_q; logic s_rcu_busy_d, s_rcu_busy_q;
@@ -306,7 +306,7 @@ module tiny_sysctrl #(
s_rcu_read_data = s_gate_req_q; s_rcu_read_data = s_gate_req_q;
s_rcu_writable = 1'b1; s_rcu_writable = 1'b1;
end end
GateStatus: s_rcu_read_data = {18'd0, gate_actual_i}; GateStatus: s_rcu_read_data = {17'd0, gate_actual_i};
ResetRequest: begin ResetRequest: begin
s_rcu_read_data = s_rst_req_q; s_rcu_read_data = s_rst_req_q;
s_rcu_writable = 1'b1; s_rcu_writable = 1'b1;
@@ -334,7 +334,7 @@ module tiny_sysctrl #(
MemHz: s_rcu_read_data = s_mem_hz_q; MemHz: s_rcu_read_data = s_mem_hz_q;
PclkHz: s_rcu_read_data = s_pclk_hz_q; PclkHz: s_rcu_read_data = s_pclk_hz_q;
ClintHz: s_rcu_read_data = 32'd1_000_000; ClintHz: s_rcu_read_data = 32'd1_000_000;
TargetCapability: s_rcu_read_data = {18'd0, TargetMask}; TargetCapability: s_rcu_read_data = {17'd0, TargetMask};
ClkoutControl: begin ClkoutControl: begin
s_rcu_read_data = s_clkout_control_q; s_rcu_read_data = s_clkout_control_q;
s_rcu_writable = 1'b1; s_rcu_writable = 1'b1;
@@ -350,9 +350,9 @@ module tiny_sysctrl #(
ClockRequest: s_rcu_config_err = s_rcu_busy_q || u_rcu_dst.pwdata[31:2] != 0; ClockRequest: s_rcu_config_err = s_rcu_busy_q || u_rcu_dst.pwdata[31:2] != 0;
TimeoutRefCycles: s_rcu_config_err = s_rcu_busy_q || u_rcu_dst.pwdata == 0; TimeoutRefCycles: s_rcu_config_err = s_rcu_busy_q || u_rcu_dst.pwdata == 0;
GateRequest: GateRequest:
s_rcu_config_err = s_rcu_busy_q || (u_rcu_dst.pwdata & ~{18'd0, TargetMask}) != 0; s_rcu_config_err = s_rcu_busy_q || (u_rcu_dst.pwdata & ~{17'd0, TargetMask}) != 0;
ResetRequest: ResetRequest:
s_rcu_config_err = s_rcu_busy_q || (u_rcu_dst.pwdata & ~{18'd0, TargetMask[13:1], 1'b0}) != 0; s_rcu_config_err = s_rcu_busy_q || (u_rcu_dst.pwdata & ~{17'd0, TargetMask[14:1], 1'b0}) != 0;
ResetCause: s_rcu_config_err = u_rcu_dst.pwdata[31:6] != 0; ResetCause: s_rcu_config_err = u_rcu_dst.pwdata[31:6] != 0;
RcuFault: s_rcu_config_err = u_rcu_dst.pwdata[31:5] != 0; RcuFault: s_rcu_config_err = u_rcu_dst.pwdata[31:5] != 0;
RcuIrqState, RcuIrqEnable: s_rcu_config_err = u_rcu_dst.pwdata[31:2] != 0; RcuIrqState, RcuIrqEnable: s_rcu_config_err = u_rcu_dst.pwdata[31:2] != 0;
@@ -363,11 +363,11 @@ module tiny_sysctrl #(
s_rcu_config_err |= s_clkout_control_q[2:0] != 0 || !clkout_idle_i || s_rcu_config_err |= s_clkout_control_q[2:0] != 0 || !clkout_idle_i ||
!(s_clk_req_q == 0 || (s_clk_req_q == 1 && !PllPresent) || !(s_clk_req_q == 0 || (s_clk_req_q == 1 && !PllPresent) ||
(s_clk_req_q >= 2 && PllPresent && PllProfiles)); (s_clk_req_q >= 2 && PllPresent && PllProfiles));
32'd2: s_rcu_config_err |= TargetMask == 0 || (s_gate_req_q & ~{18'd0, TargetMask}) != 0; 32'd2: s_rcu_config_err |= TargetMask == 0 || (s_gate_req_q & ~{17'd0, TargetMask}) != 0;
32'd4: 32'd4:
s_rcu_config_err |= s_rst_req_q == 0 || s_rcu_config_err |= s_rst_req_q == 0 ||
(s_rst_req_q & ~{18'd0, TargetMask[13:1], 1'b0}) != 0 || (s_rst_req_q & ~{17'd0, TargetMask[14:1], 1'b0}) != 0 ||
(s_rst_req_q & {18'd0, gate_actual_i}) != 0; (s_rst_req_q & {17'd0, gate_actual_i}) != 0;
32'd8: begin 32'd8: begin
end end
default: s_rcu_config_err = 1'b1; default: s_rcu_config_err = 1'b1;
@@ -432,16 +432,16 @@ module tiny_sysctrl #(
s_rcu_busy_d = 1'b1; s_rcu_busy_d = 1'b1;
s_active_cmd_d = 4'd8; s_active_cmd_d = 4'd8;
end else if (TargetMask != 0 && u_rcu_dst.pwdata == 2 && end else if (TargetMask != 0 && u_rcu_dst.pwdata == 2 &&
(s_gate_req_q & ~{18'd0, TargetMask}) == 0) begin (s_gate_req_q & ~{17'd0, TargetMask}) == 0) begin
s_periph_mask_d = s_gate_req_q[13:0]; s_periph_mask_d = s_gate_req_q[14:0];
s_periph_reset_d = 1'b0; s_periph_reset_d = 1'b0;
s_periph_apply_d = 1'b1; s_periph_apply_d = 1'b1;
s_rcu_busy_d = 1'b1; s_rcu_busy_d = 1'b1;
s_active_cmd_d = 4'd2; s_active_cmd_d = 4'd2;
end else if (u_rcu_dst.pwdata == 4 && s_rst_req_q != 0 && end else if (u_rcu_dst.pwdata == 4 && s_rst_req_q != 0 &&
(s_rst_req_q & ~{18'd0, TargetMask[13:1], 1'b0}) == 0 && (s_rst_req_q & ~{17'd0, TargetMask[14:1], 1'b0}) == 0 &&
(s_rst_req_q & {18'd0, gate_actual_i}) == 0) begin (s_rst_req_q & {17'd0, gate_actual_i}) == 0) begin
s_periph_mask_d = s_rst_req_q[13:0]; s_periph_mask_d = s_rst_req_q[14:0];
s_periph_reset_d = 1'b1; s_periph_reset_d = 1'b1;
s_periph_apply_d = 1'b1; s_periph_apply_d = 1'b1;
s_reset_done_d = 0; s_reset_done_d = 0;
@@ -501,7 +501,7 @@ module tiny_sysctrl #(
if (periph_done_i) begin if (periph_done_i) begin
s_rcu_busy_d = 1'b0; s_rcu_busy_d = 1'b0;
s_active_cmd_d = 0; s_active_cmd_d = 0;
s_reset_done_d = {18'd0, reset_done_i}; s_reset_done_d = {17'd0, reset_done_i};
if (s_periph_reset_q && reset_done_i != 0) s_reset_cause_d[5] = 1'b1; if (s_periph_reset_q && reset_done_i != 0) s_reset_cause_d[5] = 1'b1;
s_rcu_irq_state_d[0] = 1'b1; s_rcu_irq_state_d[0] = 1'b1;
end end
@@ -607,7 +607,7 @@ module tiny_sysctrl #(
.dat_o (s_clk_latched_q) .dat_o (s_clk_latched_q)
); );
dffr #( dffr #(
.DATA_WIDTH(20) .DATA_WIDTH(21)
) u_periph_command ( ) u_periph_command (
.clk_i (clk_ref_i), .clk_i (clk_ref_i),
.rst_n_i(rst_ref_n_i), .rst_n_i(rst_ref_n_i),
+1 -1
View File
@@ -140,7 +140,7 @@ def validate(document_path: Path, root: Path) -> None:
raise ValueError("unrecognized generated APB crossing contract") raise ValueError("unrecognized generated APB crossing contract")
expected = {"aon": ["clint", "archinfo"], "memory": ["xpi"], expected = {"aon": ["clint", "archinfo"], "memory": ["xpi"],
"peripheral": ["gpio", "uart0", "timer0", "timer1", "i2c0", "dma", "peripheral": ["gpio", "uart0", "timer0", "timer1", "i2c0", "dma",
"pwm", "rtc", "wdg", "rng", "crc", "ws2812", "crypto", "i2s"]} "pwm", "rtc", "wdg", "rng", "crc", "ws2812", "crypto", "i2s", "dvp"]}
if (generated.get("destinations") != expected or if (generated.get("destinations") != expected or
generated.get("same_clock_isolation") != ["sdio"] or generated.get("same_clock_isolation") != ["sdio"] or
generated.get("native_system") != ["sysctrl", "sram"] or generated.get("native_system") != ["sysctrl", "sram"] or
+15 -10
View File
@@ -18,15 +18,15 @@ from scripts.setup_helpers import atomic_write # noqa: E402
TINY = ROOT / "rtl/tiny" TINY = ROOT / "rtl/tiny"
RCU_TARGETS = {"gpio": 1, "uart0": 2, "i2c0": 3, "timer0": 4, "timer1": 5, RCU_TARGETS = {"gpio": 1, "uart0": 2, "i2c0": 3, "timer0": 4, "timer1": 5,
"pwm": 6, "i2s": 7, "sdio": 8, "dma": 9, "ws2812": 10, "pwm": 6, "i2s": 7, "sdio": 8, "dma": 9, "ws2812": 10,
"rng": 11, "crc": 12, "crypto": 13} "rng": 11, "crc": 12, "crypto": 13, "dvp": 14}
def read_topology(path: Path, memory_map: Path) -> dict: def read_topology(path: Path, memory_map: Path) -> dict:
document = json.loads(path.read_text()) document = json.loads(path.read_text())
regions = {r["symbol"] for r in json.loads(memory_map.read_text())["regions"]} regions = {r["symbol"] for r in json.loads(memory_map.read_text())["regions"]}
if (document.get("schema_version") != 1 or document.get("dma_channels") != 8 if (document.get("schema_version") != 1 or document.get("dma_channels") != 8
or document.get("dma_request_mask") != "0x31ff"): or document.get("dma_request_mask") != "0x39ff"):
raise ValueError("Tiny P6 requires eight DMA channels and request mask 0x31ff") raise ValueError("Tiny P9 requires eight DMA channels and request mask 0x39ff")
if document.get("cpu") != {"module": "hazard3_cpu_2port", "local_ports": 2, if document.get("cpu") != {"module": "hazard3_cpu_2port", "local_ports": 2,
"data_outstanding": 1}: "data_outstanding": 1}:
raise ValueError("Tiny P4 requires independent I/D and one ordered data operation") raise ValueError("Tiny P4 requires independent I/D and one ordered data operation")
@@ -43,9 +43,9 @@ def read_topology(path: Path, memory_map: Path) -> dict:
"cpu_combined_outstanding": 1, "dma_direction_outstanding": 1, "cpu_combined_outstanding": 1, "dma_direction_outstanding": 1,
"sdio_direction_outstanding": 1, "target_combined_outstanding": 1, "sdio_direction_outstanding": 1, "target_combined_outstanding": 1,
"sram_targets": [0, 1, 2, 3], "xpi_target": 4, "apb_target_base": 5, "sram_targets": [0, 1, 2, 3], "xpi_target": 4, "apb_target_base": 5,
"decode_error_target": 25, "protocol_error_target": 26, "target_count": 27, "decode_error_target": 26, "protocol_error_target": 27, "target_count": 28,
}: }:
raise ValueError("Tiny P6 requires three active owners and 27 targets") raise ValueError("Tiny P9 requires three active owners and 28 targets")
if document.get("irq_vector_width") != 32 or document.get("external_irq_count") != 30: if document.get("irq_vector_width") != 32 or document.get("external_irq_count") != 30:
raise ValueError("Tiny requires a 32-bit IRQ vector and 30 external inputs") raise ValueError("Tiny requires a 32-bit IRQ vector and 30 external inputs")
names: set[str] = set() names: set[str] = set()
@@ -65,10 +65,10 @@ def read_topology(path: Path, memory_map: Path) -> dict:
assigned.add(region) assigned.add(region)
if assigned != {r for r in regions if r.startswith("APB4_")}: if assigned != {r for r in regions if r.startswith("APB4_")}:
raise ValueError("Tiny topology must route every APB region") raise ValueError("Tiny topology must route every APB region")
if len(document["apb_targets"]) != 20: if len(document["apb_targets"]) != 21:
raise ValueError("Tiny P6 requires twenty independent APB targets") raise ValueError("Tiny P9 requires twenty-one independent APB targets")
target_names = [target["name"] for target in document["apb_targets"]] target_names = [target["name"] for target in document["apb_targets"]]
if len(set(target_names)) != 20 or any(re.fullmatch(r"[a-z][a-z0-9_]*", name) is None if len(set(target_names)) != 21 or any(re.fullmatch(r"[a-z][a-z0-9_]*", name) is None
for name in target_names): for name in target_names):
raise ValueError("Tiny APB target names must be unique identifiers") raise ValueError("Tiny APB target names must be unique identifiers")
if sorted(item["pin"] for item in document["gpio_alt_functions"]) != list(range(32)): if sorted(item["pin"] for item in document["gpio_alt_functions"]) != list(range(32)):
@@ -140,14 +140,15 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130",
"#define RETROSOC_GENERATED_SOC_FEATURES_H\n" "#define RETROSOC_GENERATED_SOC_FEATURES_H\n"
"#define RS_SOC_TINY 1\n#define RS_SOC_HAS_PSRAM 0U\n" "#define RS_SOC_TINY 1\n#define RS_SOC_HAS_PSRAM 0U\n"
"#define RS_SOC_DMA_CHANNELS 8U\n#define RS_SOC_ID 0x54494E59U\n" "#define RS_SOC_DMA_CHANNELS 8U\n#define RS_SOC_ID 0x54494E59U\n"
"#define RS_SOC_DMA_REQUEST_MASK 0x31FFU\n" f"#define RS_SOC_DMA_REQUEST_MASK {document['dma_request_mask']}U\n"
"#define RS_SOC_HAS_I2C1 0U\n#define RS_SOC_HAS_SDIO1 0U\n" "#define RS_SOC_HAS_I2C1 0U\n#define RS_SOC_HAS_SDIO1 0U\n"
"#define RS_SOC_HAS_UART0 1U\n#define RS_SOC_HAS_UART1 0U\n" "#define RS_SOC_HAS_UART0 1U\n#define RS_SOC_HAS_UART1 0U\n"
"#define RS_SOC_HAS_I2C0 1U\n" "#define RS_SOC_HAS_I2C0 1U\n"
"#define RS_SOC_HAS_RNG 1U\n#define RS_SOC_HAS_CRC 1U\n" "#define RS_SOC_HAS_RNG 1U\n#define RS_SOC_HAS_CRC 1U\n"
"#define RS_SOC_HAS_WS2812 1U\n#define RS_SOC_HAS_CRYPTO 1U\n" "#define RS_SOC_HAS_WS2812 1U\n#define RS_SOC_HAS_CRYPTO 1U\n"
"#define RS_SOC_HAS_I2S 1U\n#define RS_SOC_HAS_SDIO0 1U\n" "#define RS_SOC_HAS_I2S 1U\n#define RS_SOC_HAS_SDIO0 1U\n"
"#define RS_SOC_HAS_DVP 0U\n#define RS_SOC_RNG_QUALIFIED 0U\n" "#define RS_SOC_HAS_DVP 1U\n#define RS_SOC_DVP_DMA_CHANNEL 2U\n"
"#define RS_SOC_DVP_DMA_BURST_BEATS 4U\n#define RS_SOC_RNG_QUALIFIED 0U\n"
f"#define RS_SOC_TECHNOLOGY_ID 0x{platform['technology']:08X}U\n" f"#define RS_SOC_TECHNOLOGY_ID 0x{platform['technology']:08X}U\n"
f"#define RS_SOC_PLL_PRESENT {platform['pll_present']}U\n" f"#define RS_SOC_PLL_PRESENT {platform['pll_present']}U\n"
f"#define RS_SOC_GPIO_HAS_PULLS {platform['gpio_pulls']}U\n#endif\n") f"#define RS_SOC_GPIO_HAS_PULLS {platform['gpio_pulls']}U\n#endif\n")
@@ -187,6 +188,10 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130",
f".gated_o(s_actual_gate[{target_bit}]), " f".gated_o(s_actual_gate[{target_bit}]), "
f".held_o(s_reset_held[{target_bit}]));") f".held_o(s_reset_held[{target_bit}]));")
extra_ready = " && s_i2s_reset_ready" if name == "i2s" else "" extra_ready = " && s_i2s_reset_ready" if name == "i2s" else ""
if name == "dvp":
# Local camera reset waits for its pixel domain. Global CPU
# boot uses APB/AXI barriers, not this local-reset vector.
extra_ready = " && s_dvp_reset_ready"
if name == "dma": if name == "dma":
# CDC-ready outputs are observed in SYS, matching APB readiness. # CDC-ready outputs are observed in SYS, matching APB readiness.
extra_ready = " && s_dma_ready_sys && s_dma_idle_sys" extra_ready = " && s_dma_ready_sys && s_dma_idle_sys"
+16
View File
@@ -35,6 +35,7 @@ TEST_SOURCES = (
"crt/src/hal/i2s_math.c", "crt/src/hal/i2s_math.c",
"crt/src/hal/dma_math.c", "crt/src/hal/dma_math.c",
"crt/src/hal/dma.c", "crt/src/hal/dma.c",
"crt/src/hal/dvp.c",
"crt/src/hal/crypto_lifecycle.c", "crt/src/hal/crypto_lifecycle.c",
"crt/src/hal/jpeg_math.c", "crt/src/hal/jpeg_math.c",
"crt/src/hal/apu.c", "crt/src/hal/apu.c",
@@ -115,6 +116,7 @@ def main() -> int:
"-fdata-sections", "-fdata-sections",
"-Wl,--gc-sections", "-Wl,--gc-sections",
"-DRS_DMA_TEST_MMIO", "-DRS_DMA_TEST_MMIO",
"-DRS_DVP_TEST_MMIO",
"-DRS_WS2812_TEST_MMIO", "-DRS_WS2812_TEST_MMIO",
"-DRS_APU_TEST_MMIO", "-DRS_APU_TEST_MMIO",
"-DRS_CRYPTO_TEST_MMIO", "-DRS_CRYPTO_TEST_MMIO",
@@ -154,6 +156,20 @@ def main() -> int:
"-o", str(tiny_object), "-o", str(tiny_object),
], check=True) ], check=True)
command.append(str(tiny_object)) command.append(str(tiny_object))
tiny_dvp_object = temporary_root / "tiny_dvp.o"
dvp_functions = ("probe", "configure", "start", "abort", "flush", "get_status",
"interrupt_enable", "interrupt_clear", "interrupt_test",
"capture_dma", "capture_pio")
subprocess.run([
compiler, "-std=c11", "-Wall", "-Wextra", "-Werror", "-DRS_SOC_TINY",
"-DRS_DVP_TEST_MMIO", "-DRS_SOC_DVP_DMA_BURST_BEATS=4U",
*[f"-Drs_dvp_{name}=rs_tiny_dvp_{name}" for name in dvp_functions],
"-Dip_dvp_test=ip_tiny_dvp_test",
"-I", str(memory_map_root / "include"),
"-I", str(user_extensions_root / "include"), "-I", str(root / "crt/include"),
"-c", str(root / "crt/src/hal/dvp.c"), "-o", str(tiny_dvp_object),
], check=True)
command.append(str(tiny_dvp_object))
subprocess.run(command, check=True) subprocess.run(command, check=True)
subprocess.run([str(executable)], check=True) subprocess.run([str(executable)], check=True)
return 0 return 0
+349
View File
@@ -0,0 +1,349 @@
"""Validate strict Tiny R2-P9 camera workload records."""
from __future__ import annotations
import re
import hashlib
from pathlib import Path
from copy import deepcopy
import argparse
import subprocess
import sys
import shutil
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.check_simulation import DEFAULT_FAILURE # noqa: E402
PHASE = "TINY-R2-P9"
def recovery_measurements(content: str, config: dict) -> dict:
if config.get("SOC") != "TINY" or config.get("PDK") not in ("ICS55", "IHP130") or config.get("TINY_P9_QUICK", "NO") != "NO":
raise ValueError("invalid recovery configuration")
cases = re.findall(r"^P9_RECOVERY_CASE id=(\d+) pass=(\d+)$", content, re.M)
if cases != [(str(index), "1") for index in range(1, 8)]:
raise ValueError("missing, failed or duplicated recovery case")
terminal = re.findall(r"^P9_RECOVERY_RESULT code=(\d+) cases=(\d+) frames=(\d+) irqs=(\d+)$", content, re.M)
if terminal != [("0", "7", "6", "6")]:
raise ValueError("incomplete recovery frame/IRQ result")
diagnostics = re.findall(r"^P9_RECOVERY_DIAG high=(\d+) reset_status=(\d+) fault=(\d+) dvp_error=(\d+) words=(\d+)$", content, re.M)
if (len(diagnostics) != 2 or [row[0] for row in diagnostics] != ["0", "1"] or
any(row[1] != str((1 << 32) - 4) or int(row[2]) == 0 for row in diagnostics)):
raise ValueError("missing stopped-clock reset rejection diagnostics")
board = re.findall(r"^P9_RECOVERY_BOARD controls=(\d+) audio_cases=(\d+)$", content, re.M)
if board != [("8", "2")]:
raise ValueError("missing recovery board-control evidence")
return {"cases": cases, "terminal": terminal[0], "diagnostics": diagnostics, "board": board}
def measurements(content: str, config: dict) -> dict:
if DEFAULT_FAILURE.search(content) or len(re.findall(
r"^SIM_TEST_PASS Tiny cycles=[1-9][0-9]*$", content, re.M)) != 1:
raise ValueError("missing strict Tiny terminal success or forbidden marker")
if config.get("TINY_P9_WORKLOAD") == "RECOVERY":
return recovery_measurements(content, config)
terminal = re.findall(r"^P9_RESULT code=(\d+) frames=(\d+) bytes=(\d+)$", content, re.M)
if len(terminal) != 1 or terminal[0][0] != "0":
raise ValueError("missing or failed P9 result")
def records(prefix: str) -> list[dict]:
result = []
for line in content.splitlines():
if not line.startswith(prefix + " "):
continue
fields = line.split()[1:]
if any(re.fullmatch(r"[a-z_]+=[0-9]+", field) is None for field in fields):
raise ValueError("malformed measurement field")
row = dict((key, int(value)) for key, value in (field.split("=") for field in fields))
if len(row) != len(fields):
raise ValueError("duplicate measurement field")
result.append(row)
return result
rows = records("P9_FRAME")
workload = config.get("TINY_P9_WORKLOAD", "QVGA_RGB565")
quick = config.get("TINY_P9_QUICK", "NO") == "YES"
if workload not in ("QVGA_RGB565", "VGA_YUV422") or (quick and workload != "QVGA_RGB565"):
raise ValueError("invalid P9 workload selection")
expected = (1, 640, 480) if workload == "VGA_YUV422" else (
(0, 16, 8) if quick else (0, 320, 240))
if len(rows) != 1 or tuple(rows[0].get(key) for key in ("format", "width", "height")) != expected:
raise ValueError("frame does not match selected workload")
if (not rows or len(rows) != int(terminal[0][1]) or
sum(row.get("bytes", 0) for row in rows) != int(terminal[0][2])):
raise ValueError("frame records do not match terminal byte count")
for row in rows:
if row.get("bytes", 0) <= 0 or row.get("dma_channel") != 2 or row.get("request") != 11:
raise ValueError("invalid DVP DMA binding or frame length")
if row.get("dma_bytes") != row.get("bytes") or row.get("guards") != 1:
raise ValueError("incomplete DMA/readback/guard evidence")
if (row.get("format") not in (0, 1) or row.get("width", 0) <= 0 or
row["width"] % 2 or row.get("height", 0) <= 0 or
row["bytes"] != row["width"] * row["height"] * 2 or row.get("readback") != 1):
raise ValueError("invalid dimensions or missing payload verification")
pio = records("P9_PIO")
counts = records("P9_COUNTS")
expected_counts = {"words": expected[1] * expected[2] // 2,
"pixels": expected[1] * expected[2], "xpi_error": 0}
if (counts and counts != [expected_counts]) or (workload == "VGA_YUV422" and not counts):
raise ValueError("missing or inconsistent full-width frame counts")
if len(pio) != 1 or pio[0] != {"width": 7, "height": 3, "valid_bytes": 42,
"storage_bytes": 48, "readback": 1, "guards": 1}:
raise ValueError("missing odd-width PIO readback")
observations = records("P9_OBSERVATION")
words = sum(row["bytes"] // 4 for row in rows) + 12
if len(observations) != 1:
raise ValueError("missing session observation")
observed = observations[0]
if (observed.get("version") != 1 or observed.get("accepted_words") != words or
observed.get("delivered_words") != words or
not 0 < observed.get("fifo_high_water", 0) <= 128 or
observed.get("backpressure", -1) < 0 or observed.get("pixel_half_ns", 0) <= 0):
raise ValueError("incomplete or inconsistent FIFO observations")
if config.get("SOC") != "TINY" or config.get("PDK") not in ("ICS55", "IHP130"):
raise ValueError("P9 evidence is not bound to a Tiny supported profile")
return {"frames": rows, "pio": pio, "counts": counts,
"observations": observations, "terminal": terminal[0]}
def validate_run(content: str, config: dict, flow: dict, source: dict, current: dict,
*, build: dict | None = None) -> dict:
"""Acceptance requires the captured run identity as well as parsed UART.
The caller retains the build record captured from fresh outputs and the
source snapshot taken before compilation, separately from the run record.
A parsed log alone is never a qualified report.
"""
digest = hashlib.sha256(content.encode()).hexdigest()
if (flow.get("status") != "passed" or flow.get("exit_code") != 0 or
flow.get("log_sha256") != digest or not flow.get("command")):
raise ValueError("unsuccessful or unbound simulation command")
if not source or source != current or any(
not isinstance(value, str) or re.fullmatch(r"[0-9a-f]{64}", value) is None
for value in source.values()):
raise ValueError("missing or stale source snapshot")
if config.get("APP") != "ci_smoke" or config.get("TINY_P9_ACCEPTANCE") != "YES":
raise ValueError("not the P9 acceptance image")
if flow.get("configuration") != config or flow.get("source_inputs") != source:
raise ValueError("run does not match source/configuration")
if (not build or build.get("phase") != PHASE or build.get("status") != "passed" or
build.get("source_before") != source or build.get("source_after") != source or
build.get("configuration") != config):
raise ValueError("missing or stale build identity")
if (not build.get("fresh_outputs") or not build.get("build_results") or
any(result.get("exit_code") != 0 or result.get("status") != "passed"
for result in build["build_results"])):
raise ValueError("missing successful fresh-build commands")
artifacts = build.get("artifacts", {})
if set(artifacts) != {"elf", "hex", "simulator"} or flow.get("artifacts") != artifacts:
raise ValueError("missing or mismatched run artifacts")
for name, artifact in artifacts.items():
try:
path = Path(artifact["path"])
data = path.read_bytes()
if (not path.is_absolute() or len(data) != artifact["bytes"] or
hashlib.sha256(data).hexdigest() != artifact["sha256"]):
raise ValueError(f"changed {name} artifact")
except (OSError, KeyError, TypeError) as error:
raise ValueError(f"missing {name} artifact") from error
# Bind the launch arguments to the retained executable and firmware, not
# merely to an arbitrary nonempty command string supplied with the log.
cwd = Path(flow.get("cwd", ".")).resolve()
command = flow["command"]
def resolved(value: str) -> Path:
return (cwd / value).resolve()
simulator = Path(artifacts["simulator"]["path"]).resolve()
firmware = Path(artifacts["hex"]["path"]).resolve()
if (not isinstance(command, list) or not all(isinstance(value, str) for value in command) or
simulator not in [resolved(value) for value in command if not value.startswith(("+", "-"))] or
[resolved(value.split("=", 1)[1]) for value in command if value.startswith("+firmware=")] != [firmware]):
raise ValueError("launch does not use the captured executable/firmware")
measured = measurements(content, config)
if config.get("TINY_P9_WORKLOAD") == "RECOVERY":
required = ("+tiny_psram", "+tiny_p9_camera", "+tiny_p9_small", "+tiny_p9_recovery")
forbidden = ("+tiny_p9_vga", "+tiny_p9_diag", "+tiny_p9_progress", "+tiny_p9_no_pixclk", "+p8_absent")
if any(command.count(flag) != 1 for flag in required) or any(flag.startswith(forbidden) for flag in command):
raise ValueError("recovery launch mismatch")
if config.get("TINY_P9_WORKLOAD") == "VGA_YUV422":
expected_camera = ("+tiny_psram", "+tiny_p9_camera", "+tiny_p9_vga")
periods = [value for value in command if value.startswith("+tiny_p9_half_ns=")]
rejected = ("+tiny_p9_small", "+tiny_p9_diag", "+tiny_p9_progress",
"+tiny_p9_no_pixclk", "+tiny_p9_pixclk_high", "+tiny_p8_psram", "+p8_absent")
if (any(command.count(value) != 1 for value in expected_camera) or
any(value.startswith(rejected) for value in command) or len(periods) != 1 or
periods[0] != f"+tiny_p9_half_ns={measured['observations'][0]['pixel_half_ns']}"):
raise ValueError("camera launch does not match VGA workload observations")
return measured
def require_absent_outputs(outputs: dict[str, Path]) -> None:
if any(path.exists() or path.is_symlink() for path in outputs.values()):
raise ValueError("select a fresh build variant; outputs already exist")
def prepare_build(config: dict, source: dict, outputs: dict[str, Path]) -> dict:
"""Capture input identity before building into previously absent outputs."""
if not source or set(outputs) != {"elf", "hex", "simulator"}:
raise ValueError("incomplete fresh-build inputs")
require_absent_outputs(outputs)
return deepcopy({"configuration": config, "source_before": source,
"outputs": {name: str(path.resolve()) for name, path in outputs.items()}})
def capture_build(config: dict, source_before: dict, source_after: dict,
results: list[dict], outputs: dict[str, Path], *, prepared: dict) -> dict:
"""Record fresh build outputs after all source-bound build commands succeed.
Use a new variant directory; capture source_before before compilation.
Keep this record immutable when recording subsequent simulation runs.
"""
if (not source_before or source_before != source_after or not results or
any(result.get("exit_code") != 0 or result.get("status") != "passed" for result in results)):
raise ValueError("build failed or sources changed during compilation")
if set(outputs) != {"elf", "hex", "simulator"}:
raise ValueError("incomplete build outputs")
if (prepared.get("configuration") != config or prepared.get("source_before") != source_before or
prepared.get("outputs") != {name: str(path.resolve()) for name, path in outputs.items()}):
raise ValueError("outputs do not match the pre-build capture")
artifacts = {}
for name, path in outputs.items():
data = path.read_bytes()
artifacts[name] = {"path": str(path.resolve()), "bytes": len(data),
"sha256": hashlib.sha256(data).hexdigest()}
return deepcopy({"phase": PHASE, "status": "passed", "configuration": config,
"source_before": source_before, "source_after": source_after,
"artifacts": artifacts, "build_results": results, "fresh_outputs": True})
def run_campaign(args: argparse.Namespace) -> int:
"""One fresh build and one bounded execution; never retry or overwrite."""
from scripts import tiny_r2_baseline as baseline
profile = f"configs/ci/{args.pdk.lower()}-tiny.mk"
make = ["make", "--no-print-directory", f"CONFIG={profile}", "SOC=TINY",
f"PDK={args.pdk}", "APP=ci_smoke", "TINY_P9_ACCEPTANCE=YES", "TINY_P9_QUICK=NO",
f"TINY_P9_WORKLOAD={args.workload}", "SIMU=VERILATOR",
f"BUILD_TIMESTAMP={args.timestamp}", f"JOBS={args.jobs}"]
expanded = subprocess.run([*make, "config"], cwd=ROOT, text=True,
capture_output=True, check=True)
values = dict(line.split(None, 1) for line in expanded.stdout.splitlines() if line.strip())
variant = Path(values["VARIANT_ROOT"]).resolve()
evidence = variant / "meta/tiny-r2-p9/full-frame"
outputs = {"elf": variant / "sw/firmware", "hex": variant / "sw/retrosoc_fw.hex",
"simulator": variant / "sim/verilator/emu"}
# Reject collisions before even writing evidence: generation can overwrite
# an older build's manifest/filelists although its final outputs are intact.
try:
require_absent_outputs(outputs)
if evidence.exists() or evidence.is_symlink():
raise ValueError("select a fresh build variant; evidence already exists")
evidence.mkdir(parents=True, exist_ok=False)
except (ValueError, OSError) as error:
print(f"{args.pdk}: campaign rejected: {error}", file=sys.stderr)
return 1
assessment = {"phase": PHASE, "phase_accepted": False, "profile": profile, "pdk": args.pdk,
"workload": args.workload, "operating_point": "SAFE24", "status": "running",
"maximum_attempts": 1, "unrun": ["full Icarus", "broad regression",
"synthesis/netlist", "STA", "physical qualification", "remaining P9 matrix"]}
baseline.dump(evidence / "assessment.json", assessment)
try:
identities = {}
for program in ("riscv32-unknown-elf-gcc", "verilator", "verilator_bin", "make",
"cc", "c++", "timeout", "stdbuf"):
executable = shutil.which(program)
if executable is None:
raise ValueError(f"required tool unavailable: {program}")
identities[program] = baseline.artifact(Path(executable).resolve())
baseline.dump(evidence / "tool-identity.json", {
"executables": identities, "python": baseline.artifact(Path(sys.executable).resolve()),
"lock": baseline.artifact(ROOT / "dependencies/dependencies.lock.json"),
"archive_verification": "not performed by this campaign"})
baseline.execute(ROOT, evidence, "generate", [*make, "generate_filelist", "manifest"],
cwd=ROOT, quiet=True)
config = baseline.configuration(variant)["configuration"]
def inputs() -> dict:
result = baseline.source_inputs(ROOT, args.pdk) | baseline.runtime_inputs(
ROOT, variant, "verilator")
for name in ("docs/ip/tiny-soc.md", "docs/ip/tiny-soc-r2-dvp.md",
"tests/test_tiny_r2_p9.py", "tests/test_tiny_r2_p9_evidence.py",
"tests/c/test_runtime.c"):
result[name] = baseline.sha256(ROOT / name)
return result
before = inputs()
prepared = prepare_build(config, before, outputs)
baseline.dump(evidence / "prepared-build.json", prepared)
baseline.dump(evidence / "source-snapshot.json", baseline.snapshot(ROOT, evidence / "source", before))
print(f"{args.pdk}: building fresh {args.workload}; evidence={evidence}", flush=True)
built = baseline.execute(ROOT, evidence, "build", [*make, "firmware", "comp", "manifest"],
cwd=ROOT, quiet=True)
build = capture_build(config, before, inputs(), [built], outputs, prepared=prepared)
baseline.dump(evidence / "build-identity.json", build)
for artifact in identities.values():
baseline.check_artifact(artifact)
command = ["timeout", "--foreground", "--kill-after=30s", f"{args.wall_timeout}s",
"stdbuf", "-oL", "-eL", str(outputs["simulator"]),
f"+firmware={outputs['hex']}", f"+max_cycles={args.max_cycles}",
"+tiny_psram", "+tiny_p9_camera", f"+tiny_p9_half_ns={args.pixel_half_ns}",
"+tiny_p9_monitor", f"+tiny_p9_monitor_log={evidence / 'progress.log'}"]
if args.workload == "VGA_YUV422":
command.append("+tiny_p9_vga")
elif args.workload == "RECOVERY":
command += ["+tiny_p9_small", "+tiny_p9_recovery"]
baseline.dump(evidence / "launch.json", {
"command": command, "cwd": str(ROOT), "configuration": config,
"source_inputs": before, "artifacts": build["artifacts"], "maximum_attempts": 1,
"build_identity": baseline.artifact(evidence / "build-identity.json")})
print(f"{args.pdk}: starting single {args.workload} replay", flush=True)
run = subprocess.run([sys.executable, str(ROOT / "scripts/run_flow.py"),
"--tool", "tiny-p9-full-frame", "--cwd", str(ROOT),
"--log", str(evidence / "console.log"), "--result", str(evidence / "result-sim.json"),
"--", *command], cwd=ROOT, check=False, stdout=subprocess.DEVNULL)
flow = baseline.read_json(evidence / "result-sim.json")
content = (evidence / "console.log").read_text()
bound = {**flow, "source_inputs": before, "configuration": config,
"artifacts": build["artifacts"], "log_sha256": hashlib.sha256(content.encode()).hexdigest()}
baseline.dump(evidence / "bound-result.json", bound)
check = subprocess.run([sys.executable, str(ROOT / "scripts/check_simulation.py"),
"--log", str(evidence / "console.log"),
"--result", str(evidence / "result-sim-check.json"),
"--require", "SIM_TEST_PASS Tiny"], cwd=ROOT, check=False)
measured = validate_run(content, config, bound, before, inputs(), build=build)
if run.returncode != 0 or check.returncode != 0:
raise ValueError("simulation command/check failed")
for artifact in identities.values():
baseline.check_artifact(artifact)
assessment.update(status="workload_passed", measurements=measured,
duration_seconds=flow.get("duration_seconds"),
build=baseline.artifact(evidence / "build-identity.json"),
log=baseline.artifact(evidence / "console.log"))
except (ValueError, OSError, subprocess.CalledProcessError) as error:
assessment.update(status="failed_or_incomplete", reason=str(error))
baseline.dump(evidence / "assessment.json", assessment)
print(f"{args.pdk}: {assessment['status']}; {evidence / 'assessment.json'}", flush=True)
return 0 if assessment["status"] == "workload_passed" else 1
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
actions = parser.add_subparsers(dest="action", required=True)
run = actions.add_parser("run", help="fresh build plus one source-bound Verilator execution")
run.add_argument("--pdk", required=True, choices=("ICS55", "IHP130"))
run.add_argument("--workload", required=True, choices=("QVGA_RGB565", "VGA_YUV422", "RECOVERY"))
run.add_argument("--timestamp", required=True)
run.add_argument("--jobs", type=int, default=4)
run.add_argument("--pixel-half-ns", type=int, default=500)
run.add_argument("--max-cycles", type=int, default=160000000)
run.add_argument("--wall-timeout", type=int, default=43200)
args = parser.parse_args()
if not re.fullmatch(r"\d{4}-\d{2}-\d{2}-\d{2}-\d{2}", args.timestamp):
parser.error("timestamp must be YYYY-MM-DD-HH-MM")
if min(args.jobs, args.pixel_half_ns, args.max_cycles, args.wall_timeout) <= 0:
parser.error("jobs, pixel period and execution limits must be positive")
return run_campaign(args)
if __name__ == "__main__":
sys.exit(main())
+241 -2
View File
@@ -16,6 +16,7 @@
#include <retrosoc/hal/rcu_regs.h> #include <retrosoc/hal/rcu_regs.h>
#include <retrosoc/hal/extension.h> #include <retrosoc/hal/extension.h>
#include <retrosoc/hal/dma.h> #include <retrosoc/hal/dma.h>
#include <retrosoc/hal/dvp.h>
#include <retrosoc/hal/fabric_monitor.h> #include <retrosoc/hal/fabric_monitor.h>
#include <retrosoc/hal/ga2d.h> #include <retrosoc/hal/ga2d.h>
#include <retrosoc/hal/i2c.h> #include <retrosoc/hal/i2c.h>
@@ -42,6 +43,7 @@
#include <retrosoc/media/wav_audio.h> #include <retrosoc/media/wav_audio.h>
#include "../../crt/src/hal/ga2d_math.h" #include "../../crt/src/hal/ga2d_math.h"
#include "../../app/apps/ci_smoke/p9_frame.h"
#include "../../crt/src/hal/opipsram_math.c" #include "../../crt/src/hal/opipsram_math.c"
#define TEST_STORAGE_ADDRESS ((uintptr_t)0x1000U) #define TEST_STORAGE_ADDRESS ((uintptr_t)0x1000U)
@@ -1152,15 +1154,229 @@ static int test_tiny_dma_config_validation(void) {
} }
config.kind = RS_DMA_KIND_STREAM_TO_MM; config.kind = RS_DMA_KIND_STREAM_TO_MM;
config.request = RS_DMA_REQUEST_DVP_RX; config.request = RS_DMA_REQUEST_DVP_RX;
if (rs_tiny_dma_config_validate(2U, &config) != RS_EINVAL) { if (rs_tiny_dma_config_validate(2U, &config) != RS_OK) {
return 4; return 4;
} }
descriptor.control = RS_DMA_TCD_VALID | RS_DMA_TCD_DST_INC | descriptor.control = RS_DMA_TCD_VALID | RS_DMA_TCD_DST_INC |
((uint32_t)RS_DMA_KIND_STREAM_TO_MM << RS_DMA_TCD_KIND_SHIFT) | ((uint32_t)RS_DMA_KIND_STREAM_TO_MM << RS_DMA_TCD_KIND_SHIFT) |
((uint32_t)RS_DMA_REQUEST_DVP_RX << RS_DMA_TCD_REQUEST_SHIFT); ((uint32_t)RS_DMA_REQUEST_DVP_RX << RS_DMA_TCD_REQUEST_SHIFT);
if (rs_tiny_dma_tcd_validate(2U, &descriptor) != RS_ENOTSUP) { descriptor.destination = UINT32_C(0x54000C00);
if (rs_tiny_dma_tcd_validate(2U, &descriptor) != RS_OK) {
return 5; return 5;
} }
config.request = RS_DMA_REQUEST_I2C1_RX;
config.kind = RS_DMA_KIND_MM_TO_MM;
if (rs_tiny_dma_config_validate(2U, &config) != RS_EINVAL) {
return 6;
}
descriptor.control = RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | RS_DMA_TCD_DST_INC |
((uint32_t)RS_DMA_REQUEST_I2C1_RX << RS_DMA_TCD_REQUEST_SHIFT);
if (rs_tiny_dma_tcd_validate(2U, &descriptor) != RS_ENOTSUP) {
return 7;
}
return 0;
}
rs_status_t rs_tiny_dvp_configure(const rs_dvp_config_t *config);
rs_status_t rs_tiny_dvp_capture_dma(uintptr_t destination, uint32_t capacity, rs_timeout_t timeout);
rs_status_t rs_tiny_dvp_capture_pio(uintptr_t destination, uint32_t capacity, uint32_t expected,
rs_timeout_t timeout);
rs_status_t rs_tiny_dvp_abort(void);
static uint32_t dvp_registers[64];
static uint32_t dvp_test_channel;
static uint32_t dvp_test_polls;
static uint32_t dvp_test_words;
static uint32_t dvp_test_pixels;
static uint32_t dvp_test_popped;
static uint32_t dvp_test_writes;
static uint32_t dvp_test_aborts;
static bool dvp_test_started;
static bool dvp_test_snapshot_done;
static bool dvp_test_no_clock;
static bool dvp_test_hold_response;
static bool dvp_test_truncate_words;
static bool dvp_test_size_error;
static uint32_t dvp_test_control_pending;
static uint32_t dvp_test_applied_control;
static uint32_t dvp_pio_buffer[6];
static void dvp_test_apply_control(uint32_t value) {
if ((value & 1U) == 0U) {
dvp_test_started = false;
dvp_test_snapshot_done = false;
dvp_registers[2] = 4U;
} else if (!dvp_test_snapshot_done || (dvp_test_applied_control & 1U) == 0U) {
const uint32_t size = dvp_registers[(value & 4U) != 0U ? 8U : 6U];
dvp_test_pixels = (size & UINT32_C(0xFFFF)) * (size >> 16);
dvp_test_words = (((size & UINT32_C(0xFFFF)) + 1U) / 2U) * (size >> 16);
dvp_test_started = true;
dvp_test_snapshot_done = false;
dvp_test_polls = 0U;
dvp_test_popped = 0U;
if (dvp_test_hold_response)
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_STATUS) = RS_DMA_STATUS_BUSY;
}
dvp_test_applied_control = value;
}
uint32_t rs_dvp_test_read(uint32_t offset) {
if (offset == 8U && dvp_test_control_pending != 0U && !dvp_test_no_clock) {
dvp_test_control_pending--;
if (dvp_test_control_pending == 0U)
dvp_test_apply_control(dvp_registers[0]);
}
if (offset == 8U && dvp_test_started) {
dvp_test_polls++;
if (dvp_test_polls == 3U) {
dvp_test_snapshot_done = true;
dvp_registers[0x24U / 4U]++;
dvp_registers[0x2CU / 4U] = dvp_test_pixels;
dvp_registers[0x30U / 4U] =
dvp_test_truncate_words ? (dvp_test_words & UINT32_C(0xFFFF)) : dvp_test_words;
dvp_registers[0x3CU / 4U] = RS_DVP_INTERRUPT_FRAME_DONE;
if (dvp_test_size_error)
dvp_registers[0x38U / 4U] |= RS_DVP_ERROR_SIZE;
if (!dvp_test_hold_response) {
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_STATUS) = RS_DMA_STATUS_DONE;
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_BYTES_DONE) = dvp_test_words * 4U;
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_REMAINING) = 0U;
}
}
/* ENABLE stays high after snapshot; ACTIVE goes low independently. */
return 1U | (dvp_test_polls < 3U ? 2U : 0U) |
((dvp_registers[3] != 0U || dvp_test_popped == dvp_test_words) ? 4U : 0U);
}
if (offset == 4U) {
return UINT32_C(0x10203040) + dvp_test_popped++;
}
return dvp_registers[offset / 4U];
}
void rs_dvp_test_write(uint32_t offset, uint32_t value) {
dvp_test_writes++;
if (offset == 0x38U || offset == 0x3CU) {
if (!dvp_test_no_clock || offset == 0x3CU)
dvp_registers[offset / 4U] &= ~value;
} else if (offset == 0x4CU && (value & 1U) != 0U) {
dvp_test_aborts++;
if (!dvp_test_no_clock) {
dvp_test_started = false;
dvp_test_snapshot_done = true;
dvp_registers[2] = (dvp_test_applied_control & 1U) | 4U;
dvp_registers[0x38U / 4U] |= RS_DVP_ERROR_ABORT;
}
} else {
dvp_registers[offset / 4U] = value;
if (offset == 0U)
dvp_test_control_pending = 3U;
}
}
static int test_dvp_hal(void) {
for (uint32_t tiny = 0U; tiny < 2U; tiny++) {
rs_dvp_config_t config = {.format = RS_DVP_FORMAT_RGB565,
.frame_width = 4U,
.frame_height = 2U,
.snapshot = true,
.stream_enable = true};
rs_status_t (*configure)(const rs_dvp_config_t *) =
tiny != 0U ? rs_tiny_dvp_configure : rs_dvp_configure;
rs_status_t (*capture)(uintptr_t, uint32_t, rs_timeout_t) =
tiny != 0U ? rs_tiny_dvp_capture_dma : rs_dvp_capture_dma;
dvp_test_channel = tiny != 0U ? 2U : 3U;
dvp_test_started = false;
dvp_test_snapshot_done = false;
dvp_test_no_clock = false;
dvp_test_hold_response = false;
dvp_test_truncate_words = false;
dvp_test_size_error = false;
dvp_test_control_pending = 0U;
dvp_test_applied_control = 0U;
for (uint32_t i = 0U; i < 64U; i++)
dvp_registers[i] = 0U;
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_STATUS) = 0U;
if (configure(&config) != RS_OK)
return 1;
const uint32_t writes = dvp_test_writes;
if (capture(UINT32_C(0x54000C00), 3U, 20U) != RS_ENOSPC || dvp_test_writes != writes)
return 2;
if (capture(UINT32_C(0x54000C00), 8U, 20U) != RS_OK ||
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_BYTE_COUNT) != 16U ||
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_CRC_EXPECTED) != 0U ||
(RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_CFG) &
(RS_DMA_CH_CFG_CRC_ENABLE | RS_DMA_CH_CFG_CRC_FINAL)) != 0U ||
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_BURST_CFG) != (tiny != 0U ? 4U : 16U))
return 3;
if (capture(UINT32_C(0x54000C00), 8U, 20U) != RS_OK)
return 9;
/* Correct aggregate counts and DMA DONE do not override a row fault. */
dvp_test_size_error = true;
if (capture(UINT32_C(0x54000C00), 8U, 20U) != RS_EIO)
return 14;
dvp_test_size_error = false;
if (capture(UINT32_C(0x54000C00), 8U, 20U) != RS_OK)
return 15;
config.frame_width = 640U;
config.frame_height = 480U;
if (configure(&config) != RS_OK ||
capture(UINT32_C(0x54000C00), UINT32_C(153600), 20U) != RS_OK ||
dvp_registers[0x30U / 4U] != UINT32_C(153600) ||
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_BYTE_COUNT) != UINT32_C(614400))
return 12;
dvp_test_truncate_words = true;
if (capture(UINT32_C(0x54000C00), UINT32_C(153600), 20U) != RS_EIO)
return 13;
dvp_test_truncate_words = false;
config.frame_height = 2U;
config.frame_width = 3U;
config.stream_enable = false;
if (configure(&config) != RS_OK || capture(UINT32_C(0x54000C00), 4U, 20U) != RS_EINVAL)
return 4;
dvp_pio_buffer[0] = UINT32_C(0xCAFEBABE);
dvp_pio_buffer[5] = UINT32_C(0xCAFEBABE);
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_STATUS) = RS_DMA_STATUS_BUSY;
const uint32_t busy_writes = dvp_test_writes;
rs_status_t busy_result =
tiny != 0U ? rs_tiny_dvp_capture_pio((uintptr_t)&dvp_pio_buffer[1], 4U, 4U, 20U)
: rs_dvp_capture_pio((uintptr_t)&dvp_pio_buffer[1], 4U, 4U, 20U);
if (busy_result == RS_OK || dvp_test_writes != busy_writes)
return 10;
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_STATUS) = 0U;
rs_status_t result =
tiny != 0U ? rs_tiny_dvp_capture_pio((uintptr_t)&dvp_pio_buffer[1], 4U, 4U, 20U)
: rs_dvp_capture_pio((uintptr_t)&dvp_pio_buffer[1], 4U, 4U, 20U);
if (result != RS_OK || dvp_pio_buffer[0] != UINT32_C(0xCAFEBABE) ||
dvp_pio_buffer[5] != UINT32_C(0xCAFEBABE) || dvp_pio_buffer[4] != UINT32_C(0x10203043))
return 5;
result = tiny != 0U ? rs_tiny_dvp_capture_pio((uintptr_t)&dvp_pio_buffer[1], 4U, 4U, 20U)
: rs_dvp_capture_pio((uintptr_t)&dvp_pio_buffer[1], 4U, 4U, 20U);
if (result != RS_OK)
return 11;
dvp_test_size_error = true;
result = tiny != 0U ? rs_tiny_dvp_capture_pio((uintptr_t)&dvp_pio_buffer[1], 4U, 4U, 20U)
: rs_dvp_capture_pio((uintptr_t)&dvp_pio_buffer[1], 4U, 4U, 20U);
if (result != RS_EIO)
return 16;
dvp_test_size_error = false;
result = tiny != 0U ? rs_tiny_dvp_capture_pio((uintptr_t)&dvp_pio_buffer[1], 4U, 4U, 20U)
: rs_dvp_capture_pio((uintptr_t)&dvp_pio_buffer[1], 4U, 4U, 20U);
if (result != RS_OK)
return 17;
config.frame_width = 4U;
config.stream_enable = true;
if (configure(&config) != RS_OK)
return 6;
dvp_test_hold_response = true;
const uint32_t aborts = dvp_test_aborts;
if (capture(UINT32_C(0x54000C00), 4U, 20U) == RS_OK || dvp_test_aborts != aborts)
return 7;
RS_DMA_CH_REG(dvp_test_channel, RS_DMA_CH_REG_STATUS) = 0U;
dvp_test_no_clock = true;
dvp_registers[0x38U / 4U] = RS_DVP_ERROR_ABORT;
result = tiny != 0U ? rs_tiny_dvp_abort() : rs_dvp_abort();
if (result != RS_ETIMEOUT)
return 8;
}
return 0; return 0;
} }
@@ -3235,6 +3451,27 @@ static int test_tiny_rcu_boundaries(void) {
return 0; return 0;
} }
static int test_p9_frame_layout(void) {
uint32_t words = 0U;
if (rs_p9_frame_words(640U, 480U, &words) != RS_OK || words != 153600U ||
rs_p9_frame_words(320U, 240U, &words) != RS_OK || words != 38400U ||
rs_p9_frame_words(7U, 3U, &words) != RS_OK || words != 12U ||
rs_p9_frame_words(512U, 256U, &words) != RS_OK || words != 65536U ||
rs_p9_frame_words(0U, 480U, &words) != RS_EINVAL ||
rs_p9_frame_words(640U, 480U, NULL) != RS_EINVAL ||
rs_p9_frame_words(65535U, 65535U, &words) != RS_ENOSPC) {
return 1;
}
/* Fixed vectors include modulo-256 carry, a partial row and the VGA tail. */
if (rs_p9_frame_word(0U, 0U, false) != UINT32_C(0x02030001) ||
rs_p9_frame_word(1U, 63U, false) != UINT32_C(0x0102FF00) ||
rs_p9_frame_word(2U, 3U, true) != UINT32_C(0x00001213) ||
rs_p9_frame_word(479U, 319U, false) != UINT32_C(0x9B9C999A)) {
return 2;
}
return 0;
}
int main(void) { int main(void) {
const int results[] = { const int results[] = {
test_string_helpers(), test_string_helpers(),
@@ -3262,6 +3499,8 @@ int main(void) {
test_gpio_helpers(), test_gpio_helpers(),
test_dma_config_validation(), test_dma_config_validation(),
test_tiny_dma_config_validation(), test_tiny_dma_config_validation(),
test_dvp_hal(),
test_p9_frame_layout(),
test_opipsram_helpers(), test_opipsram_helpers(),
test_user_ip_validation(), test_user_ip_validation(),
test_extension_validation(), test_extension_validation(),
+64
View File
@@ -0,0 +1,64 @@
#include <stdint.h>
#include <retrosoc/core/status.h>
#include <retrosoc/hal/rcu.h>
#include "app/apps/ci_smoke/p9_startup.h"
static uint32_t calls;
static uint32_t source_clock;
static uint32_t baud_rate;
static rs_status_t callback_status;
static uint32_t gpio_calls;
static uint32_t gpio_pin;
static rs_gpio_config_t gpio_config;
static rs_status_t record_uart_init(uint32_t source_clock_hz, uint32_t requested_baud_rate) {
calls++;
source_clock = source_clock_hz;
baud_rate = requested_baud_rate;
return callback_status;
}
static rs_status_t record_gpio_configure(uint32_t pin, const rs_gpio_config_t *config) {
gpio_calls++;
gpio_pin = pin;
gpio_config = *config;
return callback_status;
}
int main(void) {
rs_tiny_rcu_snapshot_t snapshot = {0};
snapshot.pclk_hz = UINT32_C(24000000);
callback_status = RS_OK;
if (!rs_p9_console_ready(&snapshot, record_uart_init) || calls != 1U ||
source_clock != UINT32_C(24000000) || baud_rate != UINT32_C(921600)) {
return 1;
}
callback_status = RS_EIO;
if (rs_p9_console_ready(&snapshot, record_uart_init) || calls != 2U) {
return 2;
}
snapshot.pclk_hz = 0U;
if (rs_p9_console_ready(&snapshot, record_uart_init) || calls != 2U) {
return 3;
}
if ((rs_p9_console_ready(NULL, record_uart_init) != false) ||
(rs_p9_console_ready(&snapshot, NULL) != false) || (calls != 2U)) {
return 4;
}
callback_status = RS_OK;
if (!rs_p9_psram_pin_ready(record_gpio_configure) || gpio_calls != 1U || gpio_pin != 29U ||
gpio_config.mode != RS_GPIO_MODE_ALT0 || !gpio_config.output_high) {
return 5;
}
callback_status = RS_EIO;
if (rs_p9_psram_pin_ready(record_gpio_configure) || gpio_calls != 2U) {
return 6;
}
if (rs_p9_psram_pin_ready(NULL) || gpio_calls != 2U) {
return 7;
}
return 0;
}
+209
View File
@@ -0,0 +1,209 @@
// Copyright (c) 2026 Yuchi Miao
// SPDX-License-Identifier: MulanPSL-2.0
`timescale 1ns / 1ps
module dvp_stats_tb;
logic clk = 0, pixel_clk = 0, rst_n = 0, host_run = 1;
always #5 if (host_run) clk = !clk;
always #7 pixel_clk = !pixel_clk;
apb4_if apb (
clk,
rst_n
);
axi4_stream_if #(
.DATA_WIDTH(32),
.ID_WIDTH (1),
.DEST_WIDTH(1),
.USER_WIDTH(1)
) stream (
clk,
rst_n
);
dvp_if camera ();
logic href = 0, vsync = 0;
logic [7:0] data = 0;
wire irq;
assign camera.pclk_i = pixel_clk;
assign camera.href_i = href;
assign camera.vsync_i = vsync;
assign camera.dat_i = data;
assign stream.tready = 1;
axi4s_dvp u_dut (
.clk_i (clk),
.rst_n_i(rst_n),
.apb4 (apb),
.rx_axis(stream),
.dvp (camera),
.irq_o (irq)
);
integer width = 4, height = 2, case_id = 0;
integer received = 0;
integer expected_words = 0;
logic checking = 0;
logic [31:0] value;
task automatic access (input bit write, input logic [31:0] address, input logic [31:0] wdata,
output logic [31:0] rdata);
@(negedge clk);
apb.psel = 1;
apb.penable = 0;
apb.pwrite = write;
apb.paddr = address;
apb.pwdata = wdata;
apb.pstrb = write ? 4'hf : 4'h0;
@(negedge clk);
apb.penable = 1;
do @(negedge clk); while (!apb.pready);
rdata = apb.prdata;
if (apb.pslverr) $fatal(1, "SIM_TEST_FAIL stats APB error addr=%h", address);
apb.psel = 0;
apb.penable = 0;
endtask
task automatic put(input logic [31:0] address, input logic [31:0] wdata);
logic [31:0] ignored;
access (1, address, wdata, ignored);
endtask
task automatic expect_reg(input logic [31:0] address, input logic [31:0] expected);
logic [31:0] got;
access (0, address, 0, got);
if (got !== expected)
$fatal(1, "SIM_TEST_FAIL stats addr=%h got=%0d expected=%0d", address, got, expected);
endtask
always @(posedge clk) begin : check_words
integer row, column;
logic [ 7:0] first;
logic [31:0] expected;
logic partial;
if (checking && stream.tvalid && stream.tready) begin
row = received / ((width + 1) / 2);
column = received % ((width + 1) / 2);
first = 8'(row * 3 + column * 4);
partial = (width % 2 != 0) && (column == width / 2);
expected = partial ? {16'd0, first, 8'(first + 1)} :
{8'(first + 2), 8'(first + 3), first, 8'(first + 1)};
if (received >= expected_words || stream.tdata !== expected ||
stream.tkeep !== (partial ? 4'h3 : 4'hf) ||
stream.tstrb !== (partial ? 4'h3 : 4'hf) ||
stream.tlast !== (column == (width + 1) / 2 - 1) ||
stream.tuser !== (received == 0))
$fatal(
1,
"SIM_TEST_FAIL stats payload/sideband word=%0d got=%h expected=%h",
received,
stream.tdata,
expected
);
received++;
end
end
task automatic capture(input integer frame_index, input integer previous_words);
put(0, 0);
repeat (80) @(negedge pixel_clk);
put('h18, {16'(height), 16'(width)});
put('h10, $test$plusargs("yuv") ? 1 : 0);
put('hc, 1);
put(0, 3);
repeat (80) @(negedge pixel_clk);
received = 0;
expected_words = ((width + 1) / 2) * height;
checking = 1;
vsync = 1;
repeat (3) @(negedge pixel_clk);
vsync = 0;
repeat (3) @(negedge pixel_clk);
for (int row = 0; row < height; row++) begin
href = 1;
for (int byte_index = 0; byte_index < width * 2; byte_index++) begin
data = 8'(row * 3 + byte_index);
@(negedge pixel_clk);
end
href = 0;
repeat (3) @(negedge pixel_clk);
if (row == 0) begin
expect_reg('h30, 32'(previous_words));
expect_reg('h24, 32'(frame_index - 1));
@(negedge pixel_clk);
end
end
repeat (40) @(negedge pixel_clk);
if (received != expected_words) $fatal(1, "SIM_TEST_FAIL stats incomplete stream");
// The actual mailbox must retain the completed frame while its receiver
// clock is stopped. No internal DUT state or valid/ready signal is forced.
if ($test$plusargs("pause_host")) begin
@(negedge clk);
host_run = 0;
end
@(negedge pixel_clk);
vsync = 1;
repeat (3) @(negedge pixel_clk);
vsync = 0;
repeat (80) @(negedge pixel_clk);
host_run = 1;
repeat (100) @(negedge clk);
expect_reg('h24, 32'(frame_index));
expect_reg('h28, 32'(height));
expect_reg('h2c, 32'(width * height));
expect_reg('h30, 32'(expected_words));
expect_reg('h34, 0);
expect_reg('h38, 0);
repeat (40) @(negedge pixel_clk);
expect_reg('h30, 32'(expected_words));
checking = 0;
$display("DVP_STATS_FRAME frame=%0d width=%0d height=%0d words=%0d pixels=%0d", frame_index,
width, height, received, width * height);
endtask
initial begin
#30000000;
$fatal(1, "SIM_TEST_TIMEOUT DVP statistics");
end
initial begin
apb.psel = 0;
apb.penable = 0;
apb.pwrite = 0;
apb.paddr = 0;
apb.pwdata = 0;
apb.pstrb = 0;
apb.pprot = 0;
if ($value$plusargs("case=%d", case_id)) begin
end
case (case_id)
0: begin
width = 4;
height = 2;
end
1: begin
width = 510;
height = 257;
end
2: begin
width = 512;
height = 256;
end
3: begin
width = 640;
height = 480;
end
4: begin
width = 511;
height = 256;
end
5: begin
width = 65535;
height = 1;
end
default: $fatal(1, "SIM_TEST_FAIL unknown stats case");
endcase
repeat (5) @(negedge clk);
rst_n = 1;
repeat (100) @(negedge clk);
capture(1, 0);
value = 32'(expected_words);
width = 4;
height = 2;
capture(2, int'(value));
$display("SIM_TEST_PASS DVP statistics case=%0d", case_id);
$finish;
end
endmodule
+5 -1
View File
@@ -136,7 +136,11 @@ module dvp_tb;
check_frame(2, 4, 4); check_frame(2, 4, 4);
read(32'h0000_0024, value); read(32'h0000_0024, value);
if (value == 0) $fatal(1, "frame count/beats invalid count=%h beats=%0d", value, beats); if (value == 0) $fatal(1, "frame count/beats invalid count=%h beats=%0d", value, beats);
write(32'h0000_0018, {16'd2, 16'd3}); write(32'h0000_0000, 32'd0);
repeat (50) @(negedge pclk_i);
write(32'h0000_0018, {16'd1, 16'd3});
write(32'h0000_0000, 32'd3);
repeat (50) @(negedge pclk_i);
check_frame(1, 3, 2); check_frame(1, 3, 2);
write(32'h0000_0048, 32'h0000_0004); write(32'h0000_0048, 32'h0000_0004);
read(32'h0000_003c, value); read(32'h0000_003c, value);
+35
View File
@@ -0,0 +1,35 @@
// Copyright (c) 2026 Yuchi Miao; SPDX-License-Identifier: MulanPSL-2.0
`timescale 1ns / 1ps
module tiny_p9_audio_observer_tb;
logic [31:0] s_gpio = 0;
logic audio_enable = 0;
logic audio_wide = 0;
`include "audio_observer.svh"
task automatic sample (input logic [15:0] word);
for (int bit_index = 15; bit_index >= 0; bit_index--) begin
s_gpio[12] = 0;
s_gpio[14] = word[bit_index];
if (bit_index == 0) s_gpio[13] = !s_gpio[13];
#5;
s_gpio[12] = 1;
#5;
end
endtask
initial begin
#20;
for (int session = 0; session < 2; session++) begin
audio_enable = 1;
for (int index = 0; index < ($test$plusargs("tiny_p9_recovery") ? 8 : 512); index++) begin
if ($test$plusargs("corrupt") && index == 3) sample (16'hdead);
else sample ($test$plusargs("tiny_p9_recovery") ? 16'h1234 : 16'(16'h2020 + index));
end
s_gpio[12] = 0;
audio_enable = 0;
#10;
s_gpio[13] = 0;
end
if (s_p6_wire_cases != 2) $fatal(1, "SIM_TEST_FAIL missing audio wire sessions");
$display("SIM_TEST_PASS audio observer sessions=2");
$finish;
end
endmodule
+207
View File
@@ -0,0 +1,207 @@
// Copyright (c) 2026 Yuchi Miao
// SPDX-License-Identifier: MulanPSL-2.0
`timescale 1ns / 1ps
`include "dvp_define.svh"
`include "pad_dvp_interface.svh"
module tiny_p9_pad_isolation_tb;
logic s_ref_clk = 0;
always #20.833 s_ref_clk = !s_ref_clk;
// Supply only the two inputs used by the actual P6 startup check.
tiny_p9_pad_boot_fixture u_dut (.clk_i(s_ref_clk));
`include "tiny_tb_gpio.svh"
`include "pad_audio_startup.svh"
`include "pad_audio_drivers.svh"
wire s_dvp_pclk, s_dvp_href, s_dvp_vsync;
wire [7:0] s_camera_data;
logic [7:0] s_sweep_data = 0;
logic s_codec_data = 0;
wire [7:0] s_dvp_data = $test$plusargs("sweep") ? s_sweep_data : s_camera_data;
tiny_dvp_camera u_camera (
.pclk_o (s_dvp_pclk),
.href_o (s_dvp_href),
.vsync_o(s_dvp_vsync),
.dat_o (s_camera_data)
);
`include "pad_camera_drivers.svh"
assign s_gpio_14 = !$test$plusargs("tiny_p9_camera") ? s_codec_data : 1'bz;
// Negative control reproduces the old, simultaneously populated codec.
assign s_gpio_17 = ($test$plusargs(
"legacy_conflict"
) || $test$plusargs(
"legacy_clock"
)) ? s_p6_audio_reference : 1'bz;
assign s_gpio_15 = $test$plusargs("legacy_conflict") ? s_gpio_14 : 1'bz;
wire [7:0] s_pad_data;
wire s_pad_clk, s_pad_href, s_pad_vsync;
tiny_p9_pad_fixture u_pads (
.data_o (s_pad_data),
.clk_o (s_pad_clk),
.href_o (s_pad_href),
.vsync_o(s_pad_vsync),
`include "pad_tb_bindings.svh"
);
logic s_reset_n = 0;
logic [127:0] s_config = 0;
logic s_config_valid = 0;
wire s_push_valid, s_stats_valid;
wire [ 41:0] s_push_data;
wire [ 5:0] s_errors;
wire [`APB4_DVP__FRAME_STATS_WIDTH-1:0] s_stats;
integer s_words = 0;
dvp_core u_core (
.clk_i (s_pad_clk),
.rst_n_i (s_reset_n),
.cfg_i (s_config),
.cfg_valid_i (s_config_valid),
.cfg_cancel_i (1'b0),
.cmd_i (2'b00),
.error_clear_i (6'd0),
.cfg_ready_o (),
.push_ready_i (1'b1),
.push_valid_o (s_push_valid),
.push_data_o (s_push_data),
.active_o (),
.capture_idle_o (),
.frame_start_toggle_o(),
.line_done_toggle_o (),
.frame_done_toggle_o (),
.error_toggle_o (),
.error_flags_o (s_errors),
.error_events_o (),
.frame_stats_o (s_stats),
.frame_stats_valid_o (s_stats_valid),
.frame_stats_ready_i (1'b1),
.href_i (s_pad_href),
.vsync_i (s_pad_vsync),
.dat_i (s_pad_data)
);
function automatic logic [31:0] expected_word(input integer index);
logic [7:0] first;
first = 8'((index % 8) * 4 + (index / 8) * 3);
return {8'(first + 2), 8'(first + 3), first, 8'(first + 1)};
endfunction
always @(posedge s_pad_clk) begin
if (s_reset_n && $test$plusargs("capture") && s_push_valid) begin
if (s_words >= 64 || s_push_data[31:0] !== expected_word(s_words))
$fatal(
1,
"SIM_TEST_FAIL P9 DVP word=%0d got=%h expected=%h",
s_words,
s_push_data[31:0],
expected_word(
s_words
)
);
if (s_words == 0) $display("P9_PAD_FIRST_WORD value=%h", s_push_data[31:0]);
s_words++;
end
end
initial begin
#1000000;
$fatal(1, "SIM_TEST_TIMEOUT P9 pad fixture");
end
initial begin
if ($test$plusargs("capture")) begin
s_config[15:0] = 16;
s_config[31:16] = 8;
s_config[79:64] = 16;
s_config[95:80] = 8;
s_config[97:96] = 2'b11;
#2000;
@(negedge s_pad_clk);
s_reset_n = 1;
s_config_valid = 1;
repeat (2) @(negedge s_pad_clk);
s_config_valid = 0;
wait (s_stats_valid);
@(negedge s_pad_clk);
if (s_errors !== 0 || s_words != 64 || s_stats[31:0] != 1)
$fatal(1, "SIM_TEST_FAIL P9 pad frame accounting");
$display("SIM_TEST_PASS Tiny P9 pad capture words=%0d", s_words);
$finish;
end else if ($test$plusargs("tiny_p9_camera")) begin
wait (s_p6_audio_running);
// Hold each external audio level while sweeping all camera bytes.
s_p6_audio_running = 0;
for (int audio = 0; audio < 2; audio++) begin
s_p6_audio_reference = 1'(audio);
for (int value = 0; value < 256; value++) begin
s_sweep_data = 8'(value);
#100;
if (s_pad_data !== s_sweep_data)
$fatal(
1,
"SIM_TEST_FAIL P9 pad mismatch expected=%h got=%h audio=%0d",
s_sweep_data,
s_pad_data,
audio
);
end
end
$display("SIM_TEST_PASS Tiny P9 pad sweep bytes=512");
$finish;
end else begin
// No camera: retain the actual delayed audio-clock startup and loopback.
#100;
if (s_p6_audio_running !== 0 || s_gpio_17 !== 0)
$fatal(1, "SIM_TEST_FAIL P6 premature audio reference");
wait (s_p6_audio_running);
repeat (8) begin
@(posedge s_p6_audio_reference);
s_codec_data = 1;
#5;
if (s_gpio_17 !== 1 || s_gpio_15 !== 1 || s_pad_data[3] !== 1)
$fatal(1, "SIM_TEST_FAIL P6 audio high/loopback");
@(negedge s_p6_audio_reference);
s_codec_data = 0;
#5;
if (s_gpio_17 !== 0 || s_gpio_15 !== 0 || s_pad_data[3] !== 0)
$fatal(1, "SIM_TEST_FAIL P6 audio low/loopback");
end
$display("SIM_TEST_PASS Tiny P6 audio pad compatibility");
$finish;
end
end
endmodule
module tiny_p9_pad_fixture (
output wire [7:0] data_o,
output wire clk_o,
href_o,
vsync_o,
`include "pad_ports.svh"
);
gpio_if u_gpio_if ();
dvp_if u_dvp_if ();
assign u_gpio_if.oe_o = 0;
assign u_gpio_if.do_o = 0;
assign u_gpio_if.pu_o = 0;
assign u_gpio_if.pd_o = 0;
assign u_gpio_if.cs_o = '1;
`define RETROSOC_PAD_POWER_CONNECTIONS
`include "pad_instances.svh"
`undef RETROSOC_PAD_POWER_CONNECTIONS
`include "pad_dvp_routes.svh"
assign data_o = u_dvp_if.dat_i;
assign clk_o = u_dvp_if.pclk_i;
assign href_o = u_dvp_if.href_i;
assign vsync_o = u_dvp_if.vsync_i;
endmodule
module tiny_p9_pad_boot_fixture (
input logic clk_i
);
tiny_p9_pad_boot_soc_fixture u_soc (.clk_i(clk_i));
endmodule
module tiny_p9_pad_boot_soc_fixture (
input logic clk_i
);
wire s_ref_clk = clk_i;
wire s_cpu_ready_rst_n = 1'b1;
endmodule
+198
View File
@@ -0,0 +1,198 @@
// Copyright (c) 2026 Yuchi Miao
// SPDX-License-Identifier: MulanPSL-2.0
// verilog_syntax: parse-as-module-body
// Verification owner is used only before DMA admission and after DMA drain.
// Every memory access, including guards/readback, crosses the real serial pins.
apb4_if xpi_apb (
clk,
rst_n
);
axi4_if #(
.DATA_WIDTH(32)
) xpi_bus (
clk,
rst_n
);
xpi_if xpi_pins ();
tri1 [3:0] psram_data;
logic test_owner = 1, test_awvalid = 0, test_wvalid = 0, test_arvalid = 0;
logic [31:0] test_address = 0, test_data = 0;
logic xpi_idle, xpi_irq, xpi_tx_stall, xpi_rx_stall;
logic [31:0] xpi_value;
for (genvar lane = 0; lane < 4; lane++) begin : gen_psram_pins
assign psram_data[lane] = xpi_pins.io_oe_o[lane] ? xpi_pins.io_do_o[lane] : 1'bz;
end
assign xpi_pins.io_di_i = psram_data;
apb4_xpi u_xpi (
.clk_i (clk),
.rst_n_i (rst_n),
.dma_xfer_done_i (dma_done),
.lifecycle_idle_o(xpi_idle),
.dma_tx_stall_o (xpi_tx_stall),
.dma_rx_stall_o (xpi_rx_stall),
.irq_o (xpi_irq),
.apb4 (xpi_apb),
.mem_axi4 (xpi_bus),
.xpi (xpi_pins)
);
ESP_PSRAM64H #(
.POWER_UP_CHECK (1),
.TIMING_VIOLATION_FATAL(1)
) u_psram (
.sclk(xpi_pins.sck_o),
.csn (xpi_pins.nss_o[1]),
.sio (psram_data)
);
assign xpi_bus.awid = test_owner ? 0 : bus.awid;
assign xpi_bus.awaddr = test_owner ? test_address : bus.awaddr;
assign xpi_bus.awlen = test_owner ? 0 : bus.awlen;
assign xpi_bus.awsize = 2;
assign xpi_bus.awburst = 1;
assign {xpi_bus.awlock, xpi_bus.awcache, xpi_bus.awprot, xpi_bus.awqos,
xpi_bus.awregion, xpi_bus.awuser} = '0;
assign xpi_bus.awvalid = test_owner ? test_awvalid : bus.awvalid;
assign xpi_bus.wdata = test_owner ? test_data : bus.wdata;
assign xpi_bus.wstrb = test_owner ? 4'hf : bus.wstrb;
assign xpi_bus.wlast = test_owner ? 1'b1 : bus.wlast;
assign xpi_bus.wuser = 0;
assign xpi_bus.wvalid = test_owner ? test_wvalid : bus.wvalid;
assign xpi_bus.bready = test_owner || (bus.bready && (written_words < FrameWords || release_response));
assign bus.awready = !test_owner && xpi_bus.awready;
assign bus.wready = !test_owner && xpi_bus.wready;
assign bus.bvalid = !test_owner && xpi_bus.bvalid && (written_words < FrameWords || release_response);
assign bus.bid = xpi_bus.bid;
assign bus.bresp = xpi_bus.bresp;
assign bus.buser = xpi_bus.buser;
assign xpi_bus.arid = 0;
assign xpi_bus.araddr = test_address;
assign xpi_bus.arlen = 0;
assign xpi_bus.arsize = 2;
assign xpi_bus.arburst = 1;
assign {xpi_bus.arlock, xpi_bus.arcache, xpi_bus.arprot, xpi_bus.arqos,
xpi_bus.arregion, xpi_bus.aruser} = '0;
assign xpi_bus.arvalid = test_owner && test_arvalid;
assign xpi_bus.rready = test_owner;
assign bus.arready = 0;
assign {bus.rvalid, bus.rlast, bus.rdata, bus.rid, bus.rresp, bus.ruser} = '0;
task automatic xpi_access(input bit write, input logic [11:0] address, input logic [31:0] data,
output logic [31:0] result);
@(negedge clk);
xpi_apb.paddr = {20'd0, address};
xpi_apb.pwdata = data;
xpi_apb.pwrite = write;
xpi_apb.pstrb = write ? 4'hf : 0;
xpi_apb.psel = 1;
xpi_apb.penable = 0;
@(negedge clk);
xpi_apb.penable = 1;
do @(posedge clk); while (!xpi_apb.pready);
if (xpi_apb.pslverr) $fatal(1, "P9 XPI APB error %h", address);
result = xpi_apb.prdata;
@(negedge clk);
xpi_apb.psel = 0;
xpi_apb.penable = 0;
endtask
task automatic xpi_write(input logic [11:0] address, input logic [31:0] data);
logic [31:0] ignored;
xpi_access(1, address, data, ignored);
endtask
task automatic xpi_wait_idle;
do xpi_access(0, 12'h010, 0, xpi_value); while ((xpi_value & 32'he) != 0);
endtask
task automatic xpi_command(input logic [7:0] opcode);
xpi_wait_idle();
xpi_write(12'h00c, 0);
xpi_write(12'h230, {16'd0, 8'h10, opcode});
xpi_write(12'h234, 0);
xpi_write(12'h00c, 1);
xpi_write(12'h024, 255);
xpi_write(12'h048, 0);
xpi_write(12'h04c, 0);
xpi_write(12'h050, 32'h31);
xpi_write(12'h014, 1);
xpi_wait_idle();
endtask
task automatic memory_word(input bit write, input logic [31:0] address, input logic [31:0] data,
output logic [31:0] result);
@(negedge clk);
test_address = address;
test_data = data;
if (write) begin
test_awvalid = 1;
do @(posedge clk); while (!xpi_bus.awready);
@(negedge clk);
test_awvalid = 0;
test_wvalid = 1;
do @(posedge clk); while (!xpi_bus.wready);
@(negedge clk);
test_wvalid = 0;
do @(posedge clk); while (!xpi_bus.bvalid);
if (xpi_bus.bresp !== 0) $fatal(1, "P9 XPI guard write response");
result = 0;
end else begin
test_arvalid = 1;
do @(posedge clk); while (!xpi_bus.arready);
@(negedge clk);
test_arvalid = 0;
do @(posedge clk); while (!xpi_bus.rvalid);
if (xpi_bus.rresp !== 0 || xpi_bus.rlast !== 1'b1) $fatal(1, "P9 XPI readback response");
result = xpi_bus.rdata;
end
@(negedge clk);
endtask
task automatic initialize_psram;
xpi_apb.psel = 0;
xpi_apb.penable = 0;
xpi_apb.pwrite = 0;
xpi_apb.paddr = 0;
xpi_apb.pwdata = 0;
xpi_apb.pstrb = 0;
xpi_apb.pprot = 0;
#150000;
xpi_write(12'h00c, 0);
xpi_write(12'h120, 1);
xpi_write(12'h124, 32'h800000);
xpi_write(12'h128, 32'h21);
xpi_write(12'h12c, 32'h02010100);
xpi_write(12'h130, 192);
xpi_write(12'h134, 1024);
xpi_write(12'h210, 32'h281818eb);
xpi_write(12'h214, 32'h68004006);
xpi_write(12'h220, 32'h28181838);
xpi_write(12'h224, 32'h00005800);
xpi_write(12'h240, 32'h2018109f);
xpi_write(12'h244, 32'h00006000);
xpi_write(12'h00c, 1);
xpi_command(8'h66);
xpi_command(8'h99);
#100;
xpi_write(12'h024, 255);
xpi_write(12'h048, 0);
xpi_write(12'h04c, 6);
xpi_write(12'h050, 32'h41);
xpi_write(12'h014, 1);
xpi_wait_idle();
xpi_access(0, 12'h044, 0, xpi_value);
if (xpi_value[23:0] !== 24'h265d0d) $fatal(1, "P9 PSRAM identity mismatch %h", xpi_value);
xpi_access(0, 12'h044, 0, xpi_value);
xpi_command(8'h35);
xpi_write(12'h00c, 0);
xpi_write(12'h120, 7);
xpi_write(12'h00c, 1);
for (int word_index = 0; word_index < FrameWords + 2; word_index++)
memory_word(1, Destination - 4 + 32'(word_index * 4), 32'hcafebabe, xpi_value);
test_owner = 0;
endtask
task automatic verify_psram;
if (!dma_idle || response_pending) $fatal(1, "P9 XPI verification before DMA drain");
test_owner = 1;
for (int word_index = 0; word_index < FrameWords + 2; word_index++) begin
memory_word(0, Destination - 4 + 32'(word_index * 4), 0, xpi_value);
memory[word_index] = xpi_value;
end
xpi_wait_idle();
xpi_access(0, 12'h024, 0, xpi_value);
if ((xpi_value & 32'h60) != 0) $fatal(1, "P9 XPI error after DMA capture");
$display("P9_XPI_READBACK bytes=256 guards=2 pixel_hz=1000000");
endtask
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) 2026 Yuchi Miao
// SPDX-License-Identifier: MulanPSL-2.0
`define TINY_P9_XPI
`define TINY_P9_FIXTURE tiny_p9_xpi_tb
`include "tiny_r2_p9_dma_tb.sv"
`undef TINY_P9_XPI
+108
View File
@@ -0,0 +1,108 @@
// Copyright (c) 2026 Yuchi Miao
// SPDX-License-Identifier: MulanPSL-2.0
`timescale 1ns / 1ps
// Mock only observer inputs; execute the real included acceptance checks.
module tiny_psram_observers_tb;
logic s_clk = 0;
logic s_psram_csn = 1;
logic s_dvp_vsync = 0;
integer p9_accepted_words = 0, p9_delivered_words = 0;
integer p9_fifo_high_water = 0, p9_backpressure = 0;
tiny_psram_observer_dut u_dut (.clk_i(s_clk));
always #5 s_clk = !s_clk;
`include "tiny_p8_observers.svh"
`include "tiny_p9_monitor.svh"
initial begin
repeat (3) @(negedge s_clk);
if ($test$plusargs("cs_violation")) begin
s_psram_csn = 0;
#8010;
s_psram_csn = 1;
#20;
end else begin
s_psram_csn = 0;
#100;
s_psram_csn = 1;
@(negedge s_clk);
end
if ($test$plusargs("windows") || $test$plusargs("short_windows")) begin
repeat ($test$plusargs(
"short_windows"
) ? 3 : 4) begin
u_dut.u_soc.s_perf_enable = 1;
repeat (2) @(negedge s_clk);
u_dut.u_soc.s_perf_enable = 0;
repeat (2) @(negedge s_clk);
end
end
if ($test$plusargs("write")) begin
u_dut.u_soc.u_xpi_mem_axi4_if.wvalid = 1;
u_dut.u_soc.u_xpi_mem_axi4_if.wready = 1;
u_dut.u_soc.u_xpi_mem_axi4_if.wstrb = 4'hf;
@(negedge s_clk);
u_dut.u_soc.u_xpi_mem_axi4_if.wvalid = 0;
end
if ($test$plusargs("monitor_duration")) begin
s_dvp_vsync = 1;
u_dut.u_soc.u_cpu_mem.s_terminal = 2'b11;
u_dut.u_soc.u_dma.s_bytes_done[64+:32] = 16;
u_dut.u_soc.u_dma.s_remaining[64+:32] = 32;
u_dut.u_soc.u_dma.s_busy[2] = 1;
repeat (5000010) @(negedge s_clk);
if (s_p9_monitor_ref <= 5000000 || s_p9_monitor_fetch < 5000000 ||
s_p9_monitor_data < 5000000 || s_p9_monitor_vsync != 1 ||
s_p9_monitor_dma_bytes != 16 || s_p9_monitor_dma_remaining != 32 ||
s_p9_monitor_wbytes != 4)
$fatal(1, "SIM_TEST_FAIL observer counters or duration");
$display("MONITOR_SURVIVED ref=%0d", s_p9_monitor_ref);
end
u_dut.s_test_done = 1;
repeat (4) @(negedge s_clk);
$display("SIM_TEST_PASS Tiny PSRAM observer fixture");
$finish;
end
endmodule
module tiny_psram_observer_dut (
input logic clk_i
);
logic s_test_done = 0;
tiny_psram_observer_soc u_soc (.clk_i(clk_i));
endmodule
module tiny_psram_observer_soc (
input logic clk_i
);
wire s_backend_mem_clk = clk_i;
wire s_exec_clk = clk_i;
wire s_dma_clk = clk_i;
wire s_dvp_clk = clk_i;
logic s_xpi_rst_n = 1, s_core_rst_n = 1, s_dma_rst_n = 1, s_dvp_rst_n = 1;
logic s_perf_enable = 0;
tiny_psram_observer_bus u_xpi_mem_axi4_if ();
tiny_psram_observer_cpu u_cpu_mem ();
tiny_psram_observer_dma u_dma ();
tiny_psram_observer_dvp u_dvp ();
endmodule
module tiny_psram_observer_bus;
logic awvalid = 0, awready = 0, wvalid = 0, wready = 0;
logic bvalid = 0, bready = 0, arvalid = 0, arready = 0, rvalid = 0, rready = 0;
logic [3:0] wstrb = 0;
logic [1:0] bresp = 0, rresp = 0;
endmodule
module tiny_psram_observer_cpu;
logic [1:0] s_terminal = 0;
endmodule
module tiny_psram_observer_dma;
logic [255:0] s_bytes_done = 0, s_remaining = 0;
logic [7:0] s_busy = 0, s_error = 0;
endmodule
module tiny_psram_observer_dvp;
logic s_frame_start_event = 0, s_frame_done_event = 0, s_active = 0;
logic [5:0] s_err_flags_sys = 0;
endmodule
File diff suppressed because it is too large. Load diff
+39
View File
@@ -0,0 +1,39 @@
"""DVP word-counter boundaries through real pixel capture, CDC and APB."""
import subprocess
import pytest
from scripts.check_simulation import DEFAULT_FAILURE
from test_tiny_r2_p7 import COMMON, ROOT, run_rtl
@pytest.fixture(scope="module", params=["iverilog", "verilator"])
def stats_simulator(request, tmp_path_factory):
backend = request.param
directory = tmp_path_factory.mktemp(f"dvp-stats-{backend}")
run_rtl(directory, backend, "dvp_stats_tb", [
*[COMMON / path for path in (
"interface/axi4_stream_if.sv", "utils/fifo.sv", "utils/spill_register.sv",
"utils/edge_det.sv", "cdc/async_reqack.sv", "cdc/cdc_fifo.sv",
"cdc/cdc_warm_flush.sv")],
ROOT / "rtl/tech/tc_clk.sv",
*[ROOT / "rtl/ip/multimedia" / name for name in (
"dvp_core.sv", "dvp_reg.sv", "axi4s_dvp.sv")],
], defines=["-DPDK_BEHAV", f"-I{ROOT / 'rtl/ip/multimedia'}"])
launch = [str(directory / "sim")] if backend == "verilator" else ["vvp", str(directory / "sim")]
return launch, directory
@pytest.mark.parametrize("case,words,extra", [
(1, 65535, []), (2, 65536, ["+pause_host"]),
(3, 153600, ["+pause_host", "+yuv"]), (4, 65536, []), (5, 32768, []),
])
def test_full_frame_statistics(stats_simulator, case, words, extra):
launch, directory = stats_simulator
result = subprocess.run([*launch, f"+case={case}", *extra], cwd=directory,
capture_output=True, text=True, timeout=120)
log = result.stdout + result.stderr
(directory / f"case-{case}.log").write_text(log)
assert result.returncode == 0 and not DEFAULT_FAILURE.search(log), log
assert f"SIM_TEST_PASS DVP statistics case={case}" in log, log
assert f"words={words} " in log and "frame=2 width=4 height=2 words=4" in log
+3 -1
View File
@@ -36,7 +36,7 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None:
name = target["name"] name = target["name"]
assert f".presetn(s_{name}_rst_n)" in interfaces assert f".presetn(s_{name}_rst_n)" in interfaces
if name in {"gpio", "uart0", "i2c0", "timer0", "timer1", "pwm", "i2s", "sdio", if name in {"gpio", "uart0", "i2c0", "timer0", "timer1", "pwm", "i2s", "sdio",
"dma", "ws2812", "rng", "crc", "crypto"}: "dma", "ws2812", "rng", "crc", "crypto", "dvp"}:
assert f"tiny_peripheral_leaf u_{name}_lifecycle" in interfaces assert f"tiny_peripheral_leaf u_{name}_lifecycle" in interfaces
assert f".rst_n_i(s_leaf_rst_n[{index}])" in interfaces assert f".rst_n_i(s_leaf_rst_n[{index}])" in interfaces
assert f".rst_n_o(s_{name}_rst_n)" in interfaces assert f".rst_n_o(s_{name}_rst_n)" in interfaces
@@ -57,12 +57,14 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None:
assert "RIB" not in rtl assert "RIB" not in rtl
assert "SOC_ADDR_SRAM_END 32'h3001FFFF" in rtl assert "SOC_ADDR_SRAM_END 32'h3001FFFF" in rtl
assert "RS_SOC_SRAM_SIZE UINT32_C(0x00020000)" in sdk assert "RS_SOC_SRAM_SIZE UINT32_C(0x00020000)" in sdk
assert "RS_SOC_APB4_DVP_BASE UINT32_C(0x1000E000)" in sdk
assert "RS_SOC_PSRAM_BASE" not in sdk assert "RS_SOC_PSRAM_BASE" not in sdk
irq = (output / "include/retrosoc/generated/irq_metadata.h").read_text() irq = (output / "include/retrosoc/generated/irq_metadata.h").read_text()
assert "RS_SOC_EXTERNAL_IRQ_COUNT UINT32_C(30)" in irq assert "RS_SOC_EXTERNAL_IRQ_COUNT UINT32_C(30)" in irq
assert "RS_SOC_EXT_IRQ_UART1" not in irq assert "RS_SOC_EXT_IRQ_UART1" not in irq
assert "RS_SOC_EXT_IRQ_I2C1" not in irq assert "RS_SOC_EXT_IRQ_I2C1" not in irq
assert "RS_SOC_EXT_IRQ_SDIO UINT32_C(8)" in irq assert "RS_SOC_EXT_IRQ_SDIO UINT32_C(8)" in irq
assert "RS_SOC_EXT_IRQ_DVP UINT32_C(13)" in irq
assert "RS_SOC_EXT_IRQ_DMA UINT32_C(18)" in irq assert "RS_SOC_EXT_IRQ_DMA UINT32_C(18)" in irq
assert "RS_SOC_EXT_IRQ_NPU" not in irq assert "RS_SOC_EXT_IRQ_NPU" not in irq
# A consumer of public SDK headers must see Tiny capabilities without # A consumer of public SDK headers must see Tiny capabilities without
+124
View File
@@ -0,0 +1,124 @@
"""Exercise actual Tiny board drivers/routes through the ICS55 pad wrapper."""
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import pytest
from scripts.check_simulation import DEFAULT_FAILURE
from scripts.generate_tiny import generate_bindings, read_topology
from scripts.rtl.generate_pin_map import generate as generate_pads
ROOT = Path(__file__).resolve().parents[1]
TINY = ROOT / "rtl/tiny"
def one(pattern: str, source: str) -> str:
matches = re.findall(pattern, source, re.M | re.S)
assert len(matches) == 1, (pattern, len(matches))
return matches[0]
def prepare_wiring(directory: Path) -> None:
topology = read_topology(TINY / "integration/soc_topology.json",
TINY / "address_map/memory_map.json")
generate_bindings(topology, directory / "bindings", pdk="ICS55", have_pll=True)
generate_pads(TINY / "pin_map/pin_map.json", directory / "pads", read_while_driving=True)
board = (TINY / "dv/retrosoc_tiny_tb.sv").read_text()
p6 = (TINY / "dv/tiny_p6_devices.svh").read_text()
dvp = (ROOT / "rtl/ip/multimedia/axi4s_dvp.sv").read_text()
paths = (TINY / "dv/retrosoc_tiny_tb.sv", TINY / "dv/tiny_p6_devices.svh",
TINY / "integration/soc_topology.json", TINY / "pin_map/pin_map.json",
ROOT / "rtl/ip/multimedia/axi4s_dvp.sv")
hashes = {str(path.relative_to(ROOT)): hashlib.sha256(path.read_bytes()).hexdigest()
for path in paths}
(directory / "extracted-source-hashes.json").write_text(json.dumps(hashes, indent=2) + "\n")
(directory / "pad_dvp_interface.svh").write_text(
one(r"(^interface dvp_if\b.*?^endinterface)", dvp) + "\n")
(directory / "pad_audio_startup.svh").write_text(
one(r"(^logic\s+s_p6_audio_reference\s*=.*?^end\b)", p6) + "\n")
audio = [one(r"(^\s*logic s_p9_camera_population\s*=[^;]+;)", board)]
audio += [one(rf"(^assign s_gpio_{pin}\s*=[^;]+;)", p6) for pin in (17, 15)]
audio.append(one(r"(^always #[^\n]+s_p6_audio_reference[^;]+;)", p6))
(directory / "pad_audio_drivers.svh").write_text("\n".join(audio) + "\n")
camera = [one(rf"(^\s*assign s_gpio_{pin}\s*=[^;]+;)", board) for pin in range(12, 23)]
(directory / "pad_camera_drivers.svh").write_text("\n".join(camera) + "\n")
generated = directory / "pads/rtl"
ports = (generated / "retrosoc_asic_ports.svh").read_text()
binds = (generated / "retrosoc_asic_verilator_bindings.svh").read_text()
pads = (generated / "retrosoc_asic_pad_bindings.svh").read_text()
selected_ports = [one(rf"(inout\s+gpio_{pin}_io_pad)\b", ports) for pin in range(12, 23)]
selected_binds = [one(rf"(\.gpio_{pin}_io_pad\([^\n]+?\))", binds)
for pin in range(12, 23)]
selected_pads = [one(rf"(^\s*tc_io_tri_full_pad[^\n]*u_gpio_{pin}_io_pad[^;]+;)", pads)
for pin in range(12, 23)]
(directory / "pad_ports.svh").write_text(",\n".join(selected_ports) + "\n")
(directory / "pad_tb_bindings.svh").write_text(",\n".join(selected_binds) + "\n")
(directory / "pad_instances.svh").write_text("\n".join(selected_pads) + "\n")
routes = (directory / "bindings/rtl/tiny_gpio.svh").read_text()
selected_routes = re.findall(r"^assign u_dvp_if\.[^;]+;", routes, re.M)
assert len(selected_routes) == 11
(directory / "pad_dvp_routes.svh").write_text("\n".join(selected_routes) + "\n")
@pytest.fixture(scope="module", params=["iverilog", "verilator"])
def pad_simulator(request, tmp_path_factory):
backend = request.param
assert shutil.which(backend)
directory = tmp_path_factory.mktemp(f"p9-pad-{backend}")
prepare_wiring(directory)
model = ROOT / ("rtl/tech/ics55_io_sim_cells.sv" if backend == "verilator" else
"physical/pdk/icsprout55-pdk/IP/IO/ICsprout_55LLULP1233_IO_251013/verilog/icsIOA_N55_3P3.v")
sources = [ROOT / "rtl/ip/peripheral/gpio_if.sv",
ROOT / "tests/rtl/tiny_p9_pad_isolation_tb.sv", ROOT / "rtl/tech/tc_io.sv",
model, TINY / "dv/tiny_dvp_camera.sv", ROOT / "rtl/ip/multimedia/dvp_core.sv"]
flags = ["-DPDK_ICS55", "-Dfunctional", *[f"-I{p}" for p in (
directory, directory / "bindings/rtl", ROOT / "rtl/ip/peripheral",
ROOT / "rtl/ip/multimedia")]]
binary = directory / "sim"
if backend == "iverilog":
command = [backend, "-g2012", "-s", "tiny_p9_pad_isolation_tb", *flags,
*map(str, sources), "-o", str(binary)]
launch = ["vvp", str(binary)]
else:
command = [backend, "--binary", "--timing", "--assert", "--timescale", "1ns/1ps",
"-Wno-fatal", "--top-module", "tiny_p9_pad_isolation_tb", *flags,
*map(str, sources), "--Mdir", str(directory / "obj"), "-o", str(binary)]
launch = [str(binary)]
result = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, timeout=120,
env={**os.environ, "CCACHE_DISABLE": "1"})
(directory / "compile.log").write_text(result.stdout + result.stderr)
assert result.returncode == 0, result.stdout + result.stderr
return launch, directory
@pytest.mark.parametrize("scenario,args", [
("audio", []),
("sweep", ["+tiny_p9_camera", "+sweep"]),
("capture", ["+tiny_p9_camera", "+tiny_p9_small", "+capture"]),
("legacy_conflict", ["+tiny_p9_camera", "+sweep", "+legacy_conflict"]),
("legacy_clock_capture", ["+tiny_p9_camera", "+tiny_p9_small", "+capture", "+legacy_clock"]),
])
def test_camera_audio_pad_isolation(pad_simulator, scenario, args):
launch, directory = pad_simulator
result = subprocess.run([*launch, *args], cwd=directory, capture_output=True,
text=True, timeout=60)
log = result.stdout + result.stderr
(directory / f"{scenario}.log").write_text(log)
if scenario in ("legacy_conflict", "legacy_clock_capture"):
error = ("SIM_TEST_FAIL P9 pad mismatch" if scenario == "legacy_conflict" else
"SIM_TEST_FAIL P9 DVP word=0")
assert error in log and DEFAULT_FAILURE.search(log), log
assert "SIM_TEST_PASS" not in log, log
else:
assert result.returncode == 0 and not DEFAULT_FAILURE.search(log), log
assert "SIM_TEST_PASS Tiny" in log, log
if scenario == "capture":
assert "P9_PAD_FIRST_WORD value=02030001" in log, log
assert "pad capture words=64" in log, log
+74
View File
@@ -0,0 +1,74 @@
"""Execute the actual shared PSRAM/P8 checks and non-terminating P9 monitor."""
import re
import subprocess
import pytest
from scripts.check_simulation import DEFAULT_FAILURE
from test_tiny_r2_p7 import ROOT, run_rtl
@pytest.fixture(scope="module", params=["iverilog", "verilator"])
def observer_simulator(request, tmp_path_factory):
backend = request.param
directory = tmp_path_factory.mktemp(f"psram-observer-{backend}")
run_rtl(directory, backend, "tiny_psram_observers_tb", [],
defines=[f"-I{ROOT / 'rtl/tiny/dv'}"])
launch = [str(directory / "sim")] if backend == "verilator" else ["vvp", str(directory / "sim")]
return launch, directory
@pytest.mark.parametrize("name,args,error", [
("p9_population", ["+tiny_psram", "+tiny_p9_camera"], None),
("p8_ok", ["+tiny_p8_psram", "+windows", "+write"], None),
("p8_missing_windows", ["+tiny_p8_psram", "+short_windows", "+write"],
"SIM_TEST_FAIL P8 missing mapped transport evidence"),
("p8_missing_write", ["+tiny_p8_psram", "+windows"],
"SIM_TEST_FAIL P8 missing mapped transport evidence"),
("absent_ok", ["+tiny_p8_psram", "+p8_absent"], None),
("absent_write", ["+tiny_p8_psram", "+p8_absent", "+write"],
"SIM_TEST_FAIL P8 absent device exposed mapped writes"),
("generic_cs", ["+tiny_psram", "+cs_violation"],
"SIM_TEST_FAIL P8 active CS exceeds 8 us"),
("legacy_cs", ["+tiny_p8_psram", "+cs_violation"],
"SIM_TEST_FAIL P8 active CS exceeds 8 us"),
("monitor", ["+tiny_psram", "+tiny_p9_camera", "+tiny_p9_monitor",
"+monitor_duration", "+write"], None),
("monitor_diag", ["+tiny_p9_camera", "+tiny_p9_monitor", "+tiny_p9_diag"],
"SIM_TEST_FAIL P9 monitor conflicts"),
("monitor_long", ["+tiny_p9_camera", "+tiny_p9_monitor", "+tiny_p9_diag_long"],
"SIM_TEST_FAIL P9 monitor conflicts"),
("monitor_legacy", ["+tiny_p9_camera", "+tiny_p9_monitor", "+tiny_p9_progress"],
"SIM_TEST_FAIL P9 monitor conflicts"),
("monitor_no_camera", ["+tiny_p9_monitor"],
"SIM_TEST_FAIL P9 monitor requires camera mode"),
("monitor_missing_log", ["+tiny_p9_camera", "+tiny_p9_monitor"],
"SIM_TEST_FAIL P9 monitor requires log path"),
])
def test_observer_modes(observer_simulator, name, args, error):
launch, directory = observer_simulator
progress = directory / f"{name}-progress.log"
log_args = ([f"+tiny_p9_monitor_log={progress}"]
if "+tiny_p9_monitor" in args and name != "monitor_missing_log" else [])
result = subprocess.run([*launch, *args, *log_args], cwd=directory, capture_output=True,
text=True, timeout=60)
log = result.stdout + result.stderr
(directory / f"{name}.log").write_text(log)
if error:
assert error in log and DEFAULT_FAILURE.search(log), log
assert "SIM_TEST_PASS" not in log, log
else:
assert result.returncode == 0 and not DEFAULT_FAILURE.search(log), log
assert "SIM_TEST_PASS Tiny PSRAM observer fixture" in log, log
if name == "monitor":
assert "MONITOR_SURVIVED" in log
assert "P9_PROGRESS" not in log and "P9_MONITOR_BEGIN" not in log
assert "P9_PROGRESS ref=5000000" in progress.read_text()
def test_generic_psram_population_binding():
source = (ROOT / "rtl/tiny/dv/retrosoc_tiny_tb.sv").read_text()
assert '$test$plusargs("tiny_psram")' in re.sub(r"\s+", "", source)
assert '"tiny_p8_psram"' in source and '"p8_absent"' in source
assert ".csn (s_psram_csn)" in source
+2 -2
View File
@@ -59,9 +59,9 @@ def test_reset_inventory_has_active_domains_and_all_existing_targets() -> None:
reset = inventory["reset_distribution"] reset = inventory["reset_distribution"]
assert reset["release_edges"] == 5 assert reset["release_edges"] == 5
assert reset["leaf_names"] == ["fabric", *(target["name"] for target in topology["apb_targets"])] assert reset["leaf_names"] == ["fabric", *(target["name"] for target in topology["apb_targets"])]
assert len(reset["leaf_names"]) == 21 assert len(reset["leaf_names"]) == 22
assert {domain["name"] for domain in inventory["domains"]} == { assert {domain["name"] for domain in inventory["domains"]} == {
"aon", "system", "memory", "peripheral", "jtag", "audio"} "aon", "system", "memory", "peripheral", "jtag", "audio", "dvp_pixel"}
assert reset["clock_domain"] == "domain-specific" and reset["main_sram_payload_reset"] == "none" assert reset["clock_domain"] == "domain-specific" and reset["main_sram_payload_reset"] == "none"
assert inventory["local_memory"]["clock_domain"] == "system" assert inventory["local_memory"]["clock_domain"] == "system"
+6 -4
View File
@@ -400,11 +400,13 @@ def test_p6_map_irq_request_and_pin_contract():
for name, base in {"RNG": 0x20001000, "CRC": 0x20006000, "WS2812": 0x10008000, for name, base in {"RNG": 0x20001000, "CRC": 0x20006000, "WS2812": 0x10008000,
"CRYPTO": 0x1000C000, "I2S": 0x10007000, "SDIO0": 0x1000F000}.items(): "CRYPTO": 0x1000C000, "I2S": 0x10007000, "SDIO0": 0x1000F000}.items():
assert regions["APB4_" + name] == base assert regions["APB4_" + name] == base
assert not {"APB4_UART1", "APB4_I2C1", "APB4_DVP", "APB4_SPI0", "APB4_PIOLITE"} & regions.keys() assert regions["APB4_DVP"] == 0x1000E000
assert not {"APB4_UART1", "APB4_I2C1", "APB4_SPI0", "APB4_PIOLITE"} & regions.keys()
irqs = {i["name"]: i["core_bit"] for i in topology["interrupts"]} irqs = {i["name"]: i["core_bit"] for i in topology["interrupts"]}
assert {k: irqs[k] for k in ("rng", "ws2812", "crypto", "i2s", "sdio")} == { assert {k: irqs[k] for k in ("rng", "ws2812", "crypto", "i2s", "sdio")} == {
"rng": 16, "ws2812": 17, "crypto": 23, "i2s": 8, "sdio": 10} "rng": 16, "ws2812": 17, "crypto": 23, "i2s": 8, "sdio": 10}
assert {15, 19, 24, 25, 26, 27}.isdisjoint(irqs.values()) assert irqs["dvp"] == 15
assert {19, 24, 25, 26, 27}.isdisjoint(irqs.values())
assert irqs["rcu"] == 31 assert irqs["rcu"] == 31
pins = topology["gpio_alt_functions"] pins = topology["gpio_alt_functions"]
assert pins[9]["alt0"]["inputs"] == ["u_uart0_if.rx_i"] assert pins[9]["alt0"]["inputs"] == ["u_uart0_if.rx_i"]
@@ -506,7 +508,7 @@ int main(void) {
((request==2 || request==11 || request==13) ? RS_DMA_KIND_STREAM_TO_MM : ((request==2 || request==11 || request==13) ? RS_DMA_KIND_STREAM_TO_MM :
RS_DMA_KIND_MM_TO_MM); RS_DMA_KIND_MM_TO_MM);
#ifdef RS_SOC_TINY #ifdef RS_SOC_TINY
bool supported=(0x31ffU & (1U << request)) != 0U; bool supported=(0x39ffU & (1U << request)) != 0U;
#else #else
bool supported=request < 14U; bool supported=request < 14U;
#endif #endif
@@ -540,7 +542,7 @@ int main(void) {
def test_p6_reset_and_macro_policy_remains_explicit(): def test_p6_reset_and_macro_policy_remains_explicit():
text = (TINY / "top/retrosoc_tiny.sv").read_text() text = (TINY / "top/retrosoc_tiny.sv").read_text()
assert ".clk_aud_i" in text and "s_audio_rst_n" in text assert ".clk_aud_i" in text and "s_audio_rst_n" in text
assert ".EnableStreams" in text and "16'h31ff" in text assert ".EnableStreams" in text and "16'h39ff" in text
assert "apb4_crypto u_crypto" in text assert "apb4_crypto u_crypto" in text
tree = (TINY / "top/tiny_reset_tree.sv").read_text() tree = (TINY / "top/tiny_reset_tree.sv").read_text()
assert "s_audio" not in tree and "crypto" not in tree assert "s_audio" not in tree and "crypto" not in tree
+255
View File
@@ -0,0 +1,255 @@
"""Tiny R2-P9 DVP topology, HAL binding and executable configuration checks."""
from __future__ import annotations
import json
import re
import subprocess
import shutil
from pathlib import Path
import pytest
from scripts.check_simulation import DEFAULT_FAILURE
from scripts.generate_tiny import generate_bindings, read_topology
from scripts.rtl.generate_memory_map import generate as generate_memory_map
from test_tiny_r2_p7 import COMMON, run_rtl
ROOT = Path(__file__).resolve().parents[1]
TINY = ROOT / "rtl/tiny"
def test_p9_console_startup_host(tmp_path: Path) -> None:
compiler = shutil.which("cc")
assert compiler is not None
executable = tmp_path / "tiny_p9_startup"
result = subprocess.run([
compiler, "-std=c11", "-Wall", "-Wextra", "-Werror",
"-I", str(ROOT / "crt/include"),
"-I", str(ROOT),
str(ROOT / "tests/c/test_tiny_p9_startup.c"), "-o", str(executable),
], cwd=ROOT, capture_output=True, text=True, check=False)
assert result.returncode == 0, result.stdout + result.stderr
result = subprocess.run([str(executable)], cwd=ROOT, capture_output=True, text=True, check=False)
assert result.returncode == 0, result.stdout + result.stderr
def test_p9_topology_and_camera_routes() -> None:
topology = json.loads((TINY / "integration/soc_topology.json").read_text())
memory = json.loads((TINY / "address_map/memory_map.json").read_text())
regions = {item["symbol"]: item for item in memory["regions"]}
assert regions["APB4_DVP"]["base"] == "0x1000E000"
assert topology["fabric"]["target_count"] == 28
assert topology["fabric"]["decode_error_target"] == 26
assert topology["fabric"]["protocol_error_target"] == 27
assert any(item["name"] == "dvp" and item["regions"] == ["APB4_DVP"]
for item in topology["apb_targets"])
assert {item["core_bit"] for item in topology["interrupts"] if item["name"] == "dvp"} == {15}
routes = {item["pin"]: item for item in topology["gpio_alt_functions"]}
assert [routes[pin]["alt1"]["inputs"][0] for pin in range(12, 20)] == [
f"u_dvp_if.dat_i[{index}]" for index in range(8)
]
assert routes[20]["alt1"]["inputs"] == ["u_dvp_if.pclk_i"]
assert routes[21]["alt1"]["inputs"] == ["u_dvp_if.href_i"]
assert routes[22]["alt1"]["inputs"] == ["u_dvp_if.vsync_i"]
assert routes[23]["alt1"]["do"] == "s_clkout"
assert read_topology(TINY / "integration/soc_topology.json",
TINY / "address_map/memory_map.json")["dma_request_mask"] == "0x39ff"
def test_p9_generated_and_hal_bindings() -> None:
generator = (ROOT / "scripts/generate_tiny.py").read_text()
hal = (ROOT / "crt/src/hal/dvp.c").read_text()
assert '"dvp": 14' in generator
assert "RS_SOC_HAS_DVP 1U" in generator
assert "RS_DVP_DMA_CHANNEL RS_DMA_CHANNEL_DVP" in hal
assert "RS_SOC_DVP_DMA_BURST_BEATS" in hal
assert "rs_dvp_capture_pio" in hal
assert "rs_dma_wait(RS_DVP_DMA_CHANNEL" in hal
assert "RS_DMA_CHANNEL_BULK" in hal # Mini compatibility fallback remains explicit.
def test_p9_recovery_irq_uses_generated_external_id(tmp_path):
generate_bindings(read_topology(TINY / "integration/soc_topology.json",
TINY / "address_map/memory_map.json"), tmp_path)
header = (tmp_path / "include/retrosoc/generated/irq_metadata.h").read_text()
assert "RS_SOC_IRQ_DVP UINT32_C(15)" in header
assert "RS_SOC_EXT_IRQ_DVP UINT32_C(13)" in header
wrapper = (TINY / "top/tiny_cpu_wrapper.sv").read_text()
assert ".irq (irq_i[ExternalIrqCount+1:2])" in wrapper
application = (ROOT / "app/apps/ci_smoke/tiny_r2_p9_recovery.c").read_text()
assert "rs_irq_enable_external(RS_SOC_EXT_IRQ_DVP, rs_p9_recovery_irq)" in application
def test_p9_status_register_parity() -> None:
sv = (ROOT / "rtl/ip/multimedia/dvp_define.svh").read_text()
c = (ROOT / "crt/src/hal/dvp.c").read_text()
for name in ("ENABLE", "ACTIVE", "FIFO_EMPTY", "ERROR"):
bit = int(re.search(rf"`define APB4_DVP__STATUS_{name}\s+(\d+)", sv)[1])
mask = int(re.search(rf"#define RS_DVP_STATUS_{name}_MASK\s+UINT32_C\(0x([0-9A-F]+)\)", c)[1], 16)
assert mask == 1 << bit
def test_p9_acceptance_image_is_explicit() -> None:
result = subprocess.run(
["make", "--no-print-directory", "-n", "CONFIG=configs/ci/ics55-tiny.mk",
"APP=ci_smoke", "TINY_P9_ACCEPTANCE=YES", "firmware"],
cwd=ROOT, text=True, capture_output=True, check=False,
)
assert result.returncode == 0, result.stdout + result.stderr
assert "tiny_r2_p9.c" in result.stdout
assert "crt/src/hal/dvp.c" in result.stdout
def test_p9_workload_configuration_identity() -> None:
base = ["make", "--no-print-directory", "CONFIG=configs/ci/ics55-tiny.mk", "SOC=TINY",
"PDK=ICS55", "APP=ci_smoke", "TINY_P9_ACCEPTANCE=YES", "BUILD_TIMESTAMP=2026-10-16-00-00"]
def config(extra):
return subprocess.run([*base, *extra, "config"], cwd=ROOT, text=True,
capture_output=True, check=False)
qvga = config([])
vga = config(["TINY_P9_WORKLOAD=VGA_YUV422"])
assert qvga.returncode == 0 and vga.returncode == 0
def variant(result):
return dict(line.split(None, 1) for line in result.stdout.splitlines() if line.strip())["VARIANT_ROOT"]
assert variant(qvga) != variant(vga)
explicit_default = config(["TINY_P9_WORKLOAD=QVGA_RGB565"])
assert explicit_default.returncode == 0 and variant(qvga) == variant(explicit_default)
for extra in (["TINY_P9_WORKLOAD=VGA_YUV422", "TINY_P9_QUICK=YES"],
["TINY_P9_WORKLOAD=VGA_YUV422", "TINY_P9_ACCEPTANCE=NO"],
["TINY_P9_WORKLOAD=TYPO"]):
assert config(extra).returncode != 0
build = subprocess.run([*base, "TINY_P9_WORKLOAD=VGA_YUV422", "-n", "firmware"],
cwd=ROOT, text=True, capture_output=True, check=False)
assert build.returncode == 0 and "-DRS_TINY_P9_VGA_YUV422" in build.stdout
recovery = config(["TINY_P9_WORKLOAD=RECOVERY"])
assert recovery.returncode == 0 and variant(recovery) not in (variant(qvga), variant(vga))
build = subprocess.run([*base, "TINY_P9_WORKLOAD=RECOVERY", "-n", "firmware"],
cwd=ROOT, text=True, capture_output=True, check=False)
assert build.returncode == 0 and "tiny_r2_p9_recovery.c" in build.stdout
def test_p9_compact_netlist_register_parity():
source = (ROOT / "app/asm/tiny_p9_netlist.S").read_text()
gpio = (ROOT / "crt/src/hal/gpio.c").read_text()
dvp = (ROOT / "rtl/ip/multimedia/dvp_define.svh").read_text()
for name, offset in (("ALT_ENABLE", "034"), ("ALT_SELECT", "038"), ("OE_CLEAR", "01C")):
assert re.search(rf"RS_GPIO_{name}_OFFSET\s+UINT32_C\(0x{offset}\)", gpio)
assert f"0x{offset.lower()}(s0)" in source
for name, offset in (("FRAME_COUNT", "024"), ("LINE_COUNT", "028"), ("PIXEL_COUNT", "02c"),
("WORD_COUNT", "030"), ("RXDATA", "004"), ("ERROR_STATUS", "038")):
assert re.search(rf"APB4_DVP_{name}\s+12'h{offset}", dvp, re.I)
assert f"0x{offset}(s1)" in source
@pytest.mark.parametrize("simulator", ["iverilog", "verilator"])
def test_p9_audio_observer_modes(tmp_path, simulator):
source = (TINY / "dv/tiny_p6_devices.svh").read_text()
observer = source[source.index("integer s_p6_wire_cases"):source.index("logic s_p6_busy_previous")]
for old, new in (("u_dut.u_soc.u_i2s.u_i2s_core.enable_i", "audio_enable"),
("u_dut.u_soc.u_i2s.u_i2s_core.s_bitmode", "audio_wide")):
assert observer.count(old) == 1
observer = observer.replace(old, new)
(tmp_path / "audio_observer.svh").write_text(observer)
run_rtl(tmp_path, simulator, "tiny_p9_audio_observer_tb", [], defines=[f"-I{tmp_path}"])
launch = [str(tmp_path / "sim")] if simulator == "verilator" else ["vvp", str(tmp_path / "sim")]
for extra in (["+tiny_p9_recovery"], ["+corrupt"], ["+tiny_p9_recovery", "+corrupt"]):
result = subprocess.run([*launch, *extra], capture_output=True, text=True, timeout=30)
log = result.stdout + result.stderr
(tmp_path / ("-".join(flag[1:] for flag in extra) + ".log")).write_text(log)
if "+corrupt" in extra:
assert "I2S wire ordering/data" in log and DEFAULT_FAILURE.search(log), log
else:
assert result.returncode == 0 and not DEFAULT_FAILURE.search(log), log
assert "SIM_TEST_PASS audio observer sessions=2" in log
def test_p9_psram_pin_is_configured_before_initialization() -> None:
source = (ROOT / "app/apps/ci_smoke/tiny_r2_p9.c").read_text()
startup = (ROOT / "app/apps/ci_smoke/p9_startup.h").read_text()
topology = json.loads((TINY / "integration/soc_topology.json").read_text())
routes = {item["pin"]: item for item in topology["gpio_alt_functions"]}
pin_setup = "rs_p9_psram_pin_ready(rs_gpio_configure)"
psram_init = "rs_xpi_psram_initialize"
assert pin_setup in source
assert source.index(pin_setup) < source.index(psram_init)
assert ".mode = RS_GPIO_MODE_ALT0" in startup
assert routes[29]["alt0"]["do"] == "xpi.nss_o[1]"
assert routes[29]["alt0"]["oe"] == "1'b1"
@pytest.mark.parametrize("simulator", ["verilator", "iverilog"])
@pytest.mark.parametrize("backend", ["controlled", "xpi"])
def test_p9_dvp_dma_drain(tmp_path: Path, simulator: str, backend: str) -> None:
# Use the production instance's parameters, including its admission mask.
# A test-only mask would hide a disconnected DVP request in the product.
top = (TINY / "top/retrosoc_tiny.sv").read_text()
parameters = re.findall(r"apb4_dma\s*#\((.*?)\)\s*u_dma\s*\(", top, re.S)
assert len(parameters) == 1
(tmp_path / "tiny_p9_dma_parameters.svh").write_text(parameters[0])
generate_memory_map(TINY / "address_map/memory_map.json", tmp_path / "map", "YES", 128)
generate_bindings(read_topology(TINY / "integration/soc_topology.json",
TINY / "address_map/memory_map.json"), tmp_path / "bindings")
bindings = (tmp_path / "bindings/rtl/tiny_apb_interfaces.svh").read_text()
reset_binding = re.findall(r"assign s_reset_ready\[14\] = [^;]+;", bindings)
assert len(reset_binding) == 1
boot_binding = re.findall(r"assign s_boot_release_d\s*=[^;]+;", top)
assert len(boot_binding) == 1
(tmp_path / "tiny_p9_reset_binding.svh").write_text(reset_binding[0] + "\n" + boot_binding[0] + "\n")
xpi_sources = [COMMON / "interface/axi4_addr_gen.sv",
*[ROOT / "rtl/ip/storage" / name for name in (
"xpi_if.sv", "xpi_reg.sv", "xpi_mm.sv", "xpi_indirect.sv",
"xpi_clkgen.sv", "xpi_core.sv", "apb4_xpi.sv")],
ROOT / "rtl/model/ESP_PSRAM64H.sv"] if backend == "xpi" else []
fixture = "tiny_p9_xpi_tb" if backend == "xpi" else "tiny_r2_p9_dma_tb"
log = run_rtl(tmp_path, simulator, fixture, [
*xpi_sources,
*[COMMON / path for path in (
"interface/axi4_stream_if.sv", "utils/fifo.sv", "utils/spill_register.sv",
"utils/edge_det.sv", "stream/round_robin_arbiter.sv", "cdc/async_reqack.sv",
"cdc/cdc_fifo.sv", "cdc/cdc_warm_flush.sv")],
ROOT / "rtl/tech/tc_clk.sv",
TINY / "top/tiny_peripheral_control.sv",
*[ROOT / "rtl/ip/multimedia" / name for name in (
"dvp_core.sv", "dvp_reg.sv", "axi4s_dvp.sv")],
*[ROOT / "rtl/ip/peripheral" / name for name in (
"dma_req_if.sv", "dma_axi4_master.sv", "dma_reg.sv", "dma_core.sv", "apb4_dma.sv")],
], defines=["-DPDK_BEHAV", "-DRETROSOC_SOC__TINY", f"-I{ROOT / 'rtl/ip/storage'}",
f"-I{tmp_path}", f"-I{tmp_path / 'map/rtl'}",
f"-I{ROOT / 'rtl/ip/peripheral'}", f"-I{ROOT / 'rtl/ip/multimedia'}"],
packages=[ROOT / "rtl/ip/peripheral/dma_pkg.sv", ROOT / "rtl/ip/storage/xpi_pkg.sv"])
assert "SIM_TEST_PASS Tiny P9 focused DVP DMA" in log
if backend == "xpi":
assert "P9_XPI_READBACK bytes=256 guards=2" in log
launch = [str(tmp_path / "sim")] if simulator == "verilator" else ["vvp", str(tmp_path / "sim")]
result = subprocess.run([*launch, "+xpi_protection"], cwd=ROOT, capture_output=True,
text=True, timeout=30)
content = result.stdout + result.stderr
(tmp_path / "xpi-protection.log").write_text(content)
assert result.returncode == 0 and not DEFAULT_FAILURE.search(content), content
assert "SIM_TEST_PASS Tiny P9 XPI rejection diagnostics guards recovery" in content
return
# Reuse the compiled executable for the delayed SLVERR case.
launch = [str(tmp_path / "sim")] if simulator == "verilator" else ["vvp", str(tmp_path / "sim")]
result = subprocess.run([*launch, "+write_error"], cwd=ROOT, capture_output=True,
text=True, timeout=30)
content = result.stdout + result.stderr
(tmp_path / "write-error.log").write_text(content)
assert result.returncode == 0 and not DEFAULT_FAILURE.search(content), content
assert "SIM_TEST_PASS Tiny P9 focused DVP DMA late response error" in content
result = subprocess.run([*launch, "+lifecycle"], cwd=ROOT, capture_output=True,
text=True, timeout=30)
content = result.stdout + result.stderr
(tmp_path / "lifecycle.log").write_text(content)
assert result.returncode == 0 and not DEFAULT_FAILURE.search(content), content
assert "SIM_TEST_PASS Tiny P9 clear recovery stopped-low/high" in content
for scenario in ("row_geometry", "control_pending", "error_pair", "native_reset", "rearm", "abort_order",
"abort_active", "abort_applied", "fault_matrix", "crop_repeat", "service_budget"):
extra = ["+pixel_half_ns=500"] if scenario == "service_budget" else []
result = subprocess.run([*launch, f"+{scenario}", *extra], cwd=ROOT, capture_output=True,
text=True, timeout=30)
content = result.stdout + result.stderr
(tmp_path / f"{scenario}.log").write_text(content)
assert result.returncode == 0 and not DEFAULT_FAILURE.search(content), content
assert "SIM_TEST_PASS Tiny P9" in content
+298
View File
@@ -0,0 +1,298 @@
"""Fail-closed P9 evidence parser tests."""
import pytest
import hashlib
import json
import os
from argparse import Namespace
from copy import deepcopy
from pathlib import Path
from subprocess import CompletedProcess
from scripts.tiny_r2_p9 import capture_build, measurements, prepare_build, validate_run
from scripts import tiny_r2_p9 as p9, tiny_r2_baseline as baseline
CONFIG = {"SOC": "TINY", "PDK": "IHP130"}
@pytest.fixture
def valid_log() -> str:
return ("P9_FRAME format=0 width=320 height=240 bytes=153600 dma_bytes=153600 "
"dma_channel=2 request=11 guards=1 readback=1\n"
"P9_PIO width=7 height=3 valid_bytes=42 storage_bytes=48 readback=1 guards=1\n"
"P9_OBSERVATION version=1 accepted_words=38412 delivered_words=38412 "
"fifo_high_water=64 backpressure=12 pixel_half_ns=500\n"
"P9_RESULT code=0 frames=1 bytes=153600\n"
"SIM_TEST_PASS Tiny cycles=100000\n")
def test_p9_measurement_record(valid_log: str) -> None:
assert measurements(valid_log, CONFIG)["frames"][0]["dma_channel"] == 2
@pytest.mark.parametrize("replacement", [
("SIM_TEST_PASS Tiny", "Hello retroSoC!"),
("code=0", "code=2"),
("dma_bytes=153600", "dma_bytes=153596"),
("dma_channel=2", "dma_channel=3"),
("guards=1", "guards=0"),
("frames=1", "frames=999"),
("width=320", "width=1"),
("SIM_TEST_PASS Tiny cycles=100000", "prefixSIM_TEST_PASS Tiny diagnostic"),
("readback=1", "readback=0"),
("fifo_high_water=64", "fifo_high_water=0"),
("delivered_words=38412", "delivered_words=38411"),
("format=0", "format=0 format=1"),
])
def test_p9_rejects_incomplete_evidence(valid_log: str, replacement: tuple[str, str]) -> None:
with pytest.raises(ValueError):
measurements(valid_log.replace(*replacement), CONFIG)
def test_p9_rejects_forbidden_markers(valid_log: str) -> None:
with pytest.raises(ValueError):
measurements(valid_log + "\nSIM_TEST_FAIL", CONFIG)
@pytest.mark.parametrize("defect", ["none", "exit", "hash", "source", "config", "elf", "hex",
"simulator", "build_source", "missing_build", "launch"])
def test_run_provenance(valid_log: str, defect: str, tmp_path) -> None:
config = {**CONFIG, "APP": "ci_smoke", "TINY_P9_ACCEPTANCE": "YES"}
source = {"fixture": "a" * 64}
outputs = {name: tmp_path / name for name in ("elf", "hex", "simulator")}
prepared = prepare_build(config, source, outputs)
for name, path in outputs.items():
path.write_bytes(name.encode())
build = capture_build(config, source, source, [{"status": "passed", "exit_code": 0}],
outputs, prepared=prepared)
flow = {"status": "passed", "exit_code": 0,
"command": [str(outputs["simulator"]), f"+firmware={outputs['hex']}"],
"log_sha256": hashlib.sha256(valid_log.encode()).hexdigest(),
"source_inputs": source, "configuration": config,
"artifacts": deepcopy(build["artifacts"])}
current = source.copy()
if defect == "exit":
flow["exit_code"] = 1
elif defect == "hash":
flow["log_sha256"] = "b" * 64
elif defect == "source":
current["fixture"] = "c" * 64
elif defect == "config":
flow["configuration"] = {"SOC": "MINI"}
elif defect in outputs:
outputs[defect].write_bytes(b"stale build artifact")
elif defect == "build_source":
build["source_before"] = {"fixture": "b" * 64}
elif defect == "missing_build":
build = None
elif defect == "launch":
flow["command"] = ["old-emu", f"+firmware={outputs['hex']}"]
if defect == "none":
assert validate_run(valid_log, config, flow, source, current, build=build)
else:
with pytest.raises(ValueError):
validate_run(valid_log, config, flow, source, current, build=build)
def test_reject_changed_sources_during_build() -> None:
with pytest.raises(ValueError):
capture_build(CONFIG, {"a": "a" * 64}, {"a": "b" * 64},
[{"status": "passed", "exit_code": 0}], {}, prepared={})
def test_reject_existing_build_outputs(tmp_path) -> None:
outputs = {name: tmp_path / name for name in ("elf", "hex", "simulator")}
outputs["elf"].write_bytes(b"old ELF")
with pytest.raises(ValueError):
prepare_build(CONFIG, {"a": "a" * 64}, outputs)
@pytest.fixture
def vga_log(valid_log: str) -> str:
return (valid_log.replace("format=0 width=320 height=240", "format=1 width=640 height=480")
.replace("153600", "614400").replace("38412", "153612") +
"P9_COUNTS words=153600 pixels=307200 xpi_error=0\n")
@pytest.mark.parametrize("defect", ["none", "qvga", "format", "words", "pixels", "xpi",
"missing_counts", "duplicate_counts", "quick", "small",
"camera", "period", "diagnostic", "stopped", "duplicate_vga"])
def test_vga_workload_binding(vga_log, valid_log, defect, tmp_path):
config = {**CONFIG, "APP": "ci_smoke", "TINY_P9_ACCEPTANCE": "YES",
"TINY_P9_WORKLOAD": "VGA_YUV422"}
content = vga_log
launch = ["+tiny_psram", "+tiny_p9_camera", "+tiny_p9_vga", "+tiny_p9_half_ns=500"]
if defect == "qvga":
content = valid_log
elif defect == "format":
content = content.replace("format=1", "format=0")
elif defect == "words":
content = content.replace("words=153600", "words=22528")
elif defect == "pixels":
content = content.replace("pixels=307200", "pixels=76800")
elif defect == "xpi":
content = content.replace("xpi_error=0", "xpi_error=1")
elif defect == "missing_counts":
content = content[:content.index("P9_COUNTS")]
elif defect == "duplicate_counts":
content += "P9_COUNTS words=153600 pixels=307200 xpi_error=0\n"
elif defect == "quick":
config["TINY_P9_QUICK"] = "YES"
elif defect == "small":
launch.append("+tiny_p9_small")
elif defect == "camera":
launch.remove("+tiny_p9_vga")
elif defect == "period":
launch[-1] = "+tiny_p9_half_ns=250"
elif defect == "diagnostic":
launch.append("+tiny_p9_diag_long")
elif defect == "stopped":
launch.append("+tiny_p9_no_pixclk")
elif defect == "duplicate_vga":
launch.append("+tiny_p9_vga")
source = {"fixture": "a" * 64}
outputs = {name: tmp_path / name for name in ("elf", "hex", "simulator")}
prepared = prepare_build(config, source, outputs)
for name, path in outputs.items():
path.write_bytes(name.encode())
build = capture_build(config, source, source, [{"status": "passed", "exit_code": 0}],
outputs, prepared=prepared)
flow = {"status": "passed", "exit_code": 0, "configuration": config,
"source_inputs": source, "artifacts": build["artifacts"],
"log_sha256": hashlib.sha256(content.encode()).hexdigest(),
"command": [str(outputs["simulator"]), f"+firmware={outputs['hex']}", *launch]}
if defect == "none":
assert validate_run(content, config, flow, source, source, build=build)["counts"][0]["words"] == 153600
else:
with pytest.raises(ValueError):
validate_run(content, config, flow, source, source, build=build)
@pytest.fixture(params=["ICS55", "IHP130"])
def campaign_fixture(request, tmp_path, monkeypatch, vga_log):
"""Exercise real orchestration/filesystem guards with fake external tools."""
root = tmp_path / "repository"
variant = root / "build" / "test-variant"
evidence = variant / "meta/tiny-r2-p9/full-frame"
for name in ("dependencies/dependencies.lock.json", "docs/ip/tiny-soc.md",
"docs/ip/tiny-soc-r2-dvp.md", "tests/test_tiny_r2_p9.py",
"tests/test_tiny_r2_p9_evidence.py", "tests/c/test_runtime.c", "tool"):
path = root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("test input\n")
args = Namespace(pdk=request.param, workload="VGA_YUV422", timestamp="2026-10-16-00-00",
jobs=4, pixel_half_ns=500, max_cycles=160000000, wall_timeout=43200)
config = {"SOC": "TINY", "PDK": args.pdk, "APP": "ci_smoke",
"TINY_P9_ACCEPTANCE": "YES", "TINY_P9_WORKLOAD": args.workload}
calls = []
monkeypatch.setattr(p9, "ROOT", root)
monkeypatch.setattr(p9.shutil, "which", lambda _: str(root / "tool"))
monkeypatch.setattr(baseline, "configuration", lambda _: {"configuration": config})
monkeypatch.setattr(baseline, "source_inputs", lambda *_: {"tool": baseline.sha256(root / "tool")})
monkeypatch.setattr(baseline, "runtime_inputs", lambda *_: {})
monkeypatch.setattr(baseline, "snapshot", lambda *_: {})
def execute(_root, _directory, label, command, **_kwargs):
calls.append(label)
if label == "generate":
assert command[-2:] == ["generate_filelist", "manifest"]
(variant / "meta/manifest.json").write_text(json.dumps({"configuration": config}))
elif label == "build":
for name in ("sw/firmware", "sw/retrosoc_fw.hex", "sim/verilator/emu"):
path = variant / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("fresh test artifact\n")
else:
pytest.fail(f"unexpected flow {label}")
return {"status": "passed", "exit_code": 0, "command": command}
def subprocess_run(command, **_kwargs):
if command[0] == "make" and command[-1] == "config":
calls.append("config")
return CompletedProcess(command, 0, stdout=f"VARIANT_ROOT {variant}\n")
if Path(command[1]).name == "run_flow.py":
calls.append("simulate")
launch = command[command.index("--") + 1:]
baseline.dump(evidence / "result-sim.json", {
"status": "passed", "exit_code": 0, "command": launch, "cwd": str(root)})
(evidence / "console.log").write_text(vga_log)
elif Path(command[1]).name == "check_simulation.py":
calls.append("check")
else:
pytest.fail(f"unexpected subprocess {command}")
return CompletedProcess(command, 0)
monkeypatch.setattr(baseline, "execute", execute)
monkeypatch.setattr(p9.subprocess, "run", subprocess_run)
return args, variant, evidence, calls
@pytest.mark.parametrize("existing", ["sw/firmware", "sw/retrosoc_fw.hex", "sim/verilator/emu",
"meta/tiny-r2-p9/full-frame/assessment.json", "dangling-elf"])
def test_campaign_rejection_preserves_old_variant(campaign_fixture, existing):
args, variant, evidence, calls = campaign_fixture
for name in ("meta/manifest.json", "sim/verilator/filelists/top.fl"):
path = variant / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(b"previous evidence\n")
os.utime(path, ns=(1600000000000000000, 1600000000000000000))
path = variant / ("sw/firmware" if existing == "dangling-elf" else existing)
path.parent.mkdir(parents=True, exist_ok=True)
if existing == "dangling-elf":
path.symlink_to("missing-old-artifact")
else:
path.write_bytes(b"previous artifact\n")
os.utime(path, ns=(1600000000000000000, 1600000000000000000))
def snapshot():
result = {}
for item in (variant, *variant.rglob("*")):
stat = item.lstat()
data = os.readlink(item) if item.is_symlink() else item.read_bytes() if item.is_file() else None
result[str(item.relative_to(variant))] = (stat.st_mode, stat.st_mtime_ns, data)
return result
before = snapshot()
assert p9.run_campaign(args) == 1
assert calls == ["config"] # No generation, build, simulation or tool probing.
assert snapshot() == before
if not existing.startswith("meta/"):
assert not evidence.exists()
def test_campaign_fresh_variant_runs_once(campaign_fixture):
args, variant, evidence, calls = campaign_fixture
assert not variant.exists()
assert p9.run_campaign(args) == 0
assert calls == ["config", "generate", "build", "simulate", "check"]
report = json.loads((evidence / "assessment.json").read_text())
assert report["status"] == "workload_passed" and report["phase_accepted"] is False
assert report["pdk"] == args.pdk
assert report["measurements"]["counts"][0]["words"] == 153600
@pytest.mark.parametrize("defect", ["none", "missing_case", "failed_case", "duplicate_case",
"irq", "frame", "reset", "missing_diag", "board", "marker"])
def test_recovery_matrix_verdict(defect):
content = "".join(f"P9_RECOVERY_CASE id={i} pass=1\n" for i in range(1, 8))
content += "P9_RECOVERY_RESULT code=0 cases=7 frames=6 irqs=6\n"
content += "P9_RECOVERY_DIAG high=0 reset_status=4294967292 fault=2 dvp_error=0 words=0\n"
content += "P9_RECOVERY_DIAG high=1 reset_status=4294967292 fault=2 dvp_error=0 words=0\n"
content += "P9_RECOVERY_BOARD controls=8 audio_cases=2\nSIM_TEST_PASS Tiny cycles=100\n"
replacements = {
"missing_case": ("P9_RECOVERY_CASE id=3 pass=1\n", ""),
"failed_case": ("id=3 pass=1", "id=3 pass=0"),
"duplicate_case": ("id=3 pass=1", "id=2 pass=1"),
"irq": ("irqs=6", "irqs=5"), "frame": ("frames=6", "frames=5"),
"reset": ("reset_status=4294967292", "reset_status=0"),
"missing_diag": ("P9_RECOVERY_DIAG high=1", "OTHER high=1"),
"board": ("controls=8", "controls=0"),
"marker": ("SIM_TEST_PASS Tiny cycles=100", "Hello retroSoC!"),
}
config = {**CONFIG, "TINY_P9_WORKLOAD": "RECOVERY"}
if defect == "none":
assert measurements(content, config)["terminal"] == ("0", "7", "6", "6")
else:
with pytest.raises(ValueError):
measurements(content.replace(*replacements[defect]), config)