mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
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:
61 files changed
+5916
-288
No files matched your search
@@ -87,6 +87,9 @@ SW_LTO ?= NO
|
||||
WS2812_P3_ACCEPTANCE ?= NO
|
||||
TINY_P7_ACCEPTANCE ?= NO
|
||||
TINY_P8_ACCEPTANCE ?= NO
|
||||
TINY_P9_ACCEPTANCE ?= NO
|
||||
TINY_P9_QUICK ?= NO
|
||||
TINY_P9_WORKLOAD ?= QVGA_RGB565
|
||||
COREMARK_MODE ?= quick
|
||||
NPU_P5_ACCEPTANCE ?= NO
|
||||
NPU_P6_ACCEPTANCE ?= NO
|
||||
@@ -135,6 +138,15 @@ endif
|
||||
ifeq ($(TINY_P8_ACCEPTANCE),YES)
|
||||
CONFIG_KEY_VARS += TINY_P8_ACCEPTANCE
|
||||
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 \
|
||||
--lock $(LOCK_FILE) --profile $(PROFILE_NAME) --timestamp $(BUILD_TIMESTAMP) \
|
||||
$(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,TINY_P7_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)
|
||||
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)
|
||||
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)
|
||||
ifneq ($(SOC)/$(APP)/$(HAVE_CSR),TINY/ci_smoke/YES)
|
||||
$(error TINY_P7_ACCEPTANCE requires SOC=TINY APP=ci_smoke HAVE_CSR=YES)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -8,3 +8,15 @@ endif
|
||||
ifeq ($(TINY_P8_ACCEPTANCE),YES)
|
||||
APP_SRCS := $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p8.c
|
||||
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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 (;;) {
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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.
|
||||
`TINY_P8_ACCEPTANCE=YES APP=ci_smoke` instead selects the optional XPI PSRAM
|
||||
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.
|
||||
`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
|
||||
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
|
||||
|
||||
@@ -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_clear(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_pio(uintptr_t destination, uint32_t word_capacity,
|
||||
uint32_t expected_words, rs_timeout_t timeout);
|
||||
void ip_dvp_test(void);
|
||||
|
||||
#endif
|
||||
@@ -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) {
|
||||
#ifdef RS_SOC_TINY
|
||||
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;
|
||||
}
|
||||
#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);
|
||||
burst = (tcd->control >> RS_DMA_TCD_BURST_SHIFT) & UINT32_C(0x1F);
|
||||
#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;
|
||||
}
|
||||
#endif
|
||||
|
||||
+264
-68
@@ -33,11 +33,73 @@
|
||||
#define RS_DVP_CTRL_CROP_MASK UINT32_C(0x00000004)
|
||||
#define RS_DVP_COMMAND_ABORT_MASK UINT32_C(0x00000001)
|
||||
#define RS_DVP_COMMAND_FLUSH_MASK UINT32_C(0x00000002)
|
||||
#define RS_DVP_STATUS_ACTIVE_MASK UINT32_C(0x00000001)
|
||||
#define RS_DVP_STATUS_ERROR_MASK UINT32_C(0x00000040)
|
||||
#define RS_DVP_STATUS_ACTIVE_MASK UINT32_C(0x00000002)
|
||||
#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) {
|
||||
return (volatile uint32_t *)(RS_SOC_APB4_DVP_BASE + (uintptr_t)offset);
|
||||
#ifdef RS_SOC_TINY
|
||||
#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) {
|
||||
@@ -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) {
|
||||
if ((version == NULL) || (capability == NULL))
|
||||
return RS_EINVAL;
|
||||
*version = *rs_dvp_register(RS_DVP_VERSION_OFFSET);
|
||||
*capability = *rs_dvp_register(RS_DVP_CAPABILITY_OFFSET);
|
||||
*version = rs_dvp_read(RS_DVP_VERSION_OFFSET);
|
||||
*capability = rs_dvp_read(RS_DVP_CAPABILITY_OFFSET);
|
||||
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))
|
||||
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;
|
||||
if (config->byte_swap)
|
||||
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);
|
||||
control = (config->snapshot ? RS_DVP_CTRL_SNAPSHOT_MASK : 0U) |
|
||||
(config->crop_enable ? RS_DVP_CTRL_CROP_MASK : 0U);
|
||||
*rs_dvp_register(RS_DVP_FORMAT_OFFSET) = format;
|
||||
*rs_dvp_register(RS_DVP_SYNC_CFG_OFFSET) = sync;
|
||||
*rs_dvp_register(RS_DVP_FRAME_SIZE_OFFSET) =
|
||||
((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_register(RS_DVP_CROP_SIZE_OFFSET) =
|
||||
((uint32_t)config->crop_height << 16) | config->crop_width;
|
||||
*rs_dvp_register(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_ERROR_SIZE | RS_DVP_ERROR_PARTIAL |
|
||||
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;
|
||||
rs_dvp_write(RS_DVP_FORMAT_OFFSET, format);
|
||||
rs_dvp_write(RS_DVP_SYNC_CFG_OFFSET, sync);
|
||||
rs_dvp_write(RS_DVP_FRAME_SIZE_OFFSET,
|
||||
((uint32_t)config->frame_height << 16) | config->frame_width);
|
||||
rs_dvp_write(RS_DVP_CROP_START_OFFSET, ((uint32_t)config->crop_y << 16) | config->crop_x);
|
||||
rs_dvp_write(RS_DVP_CROP_SIZE_OFFSET,
|
||||
((uint32_t)config->crop_height << 16) | config->crop_width);
|
||||
rs_dvp_write(RS_DVP_STREAM_CTRL_OFFSET, config->stream_enable ? 1U : 0U);
|
||||
rs_dvp_write(RS_DVP_INTR_STATE_OFFSET, RS_DVP_INTERRUPT_ALL);
|
||||
rs_dvp_write(RS_DVP_CTRL_OFFSET, control);
|
||||
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_dvp_register(RS_DVP_CTRL_OFFSET) |= RS_DVP_CTRL_ENABLE_MASK;
|
||||
return RS_OK;
|
||||
return rs_dvp_start_bounded(RS_TIMEOUT_DEFAULT);
|
||||
}
|
||||
|
||||
rs_status_t rs_dvp_abort(void) {
|
||||
*rs_dvp_register(RS_DVP_COMMAND_OFFSET) = RS_DVP_COMMAND_ABORT_MASK;
|
||||
*rs_dvp_register(RS_DVP_CTRL_OFFSET) &= ~RS_DVP_CTRL_ENABLE_MASK;
|
||||
return RS_OK;
|
||||
rs_dma_status_t dma_status;
|
||||
rs_timeout_t timeout = RS_TIMEOUT_DEFAULT;
|
||||
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_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;
|
||||
}
|
||||
|
||||
rs_status_t rs_dvp_get_status(rs_dvp_status_t *status) {
|
||||
if (status == NULL)
|
||||
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->frame_count = *rs_dvp_register(RS_DVP_FRAME_COUNT_OFFSET);
|
||||
status->line_count = *rs_dvp_register(RS_DVP_LINE_COUNT_OFFSET);
|
||||
status->pixel_count = *rs_dvp_register(RS_DVP_PIXEL_COUNT_OFFSET);
|
||||
status->word_count = *rs_dvp_register(RS_DVP_WORD_COUNT_OFFSET);
|
||||
status->drop_count = *rs_dvp_register(RS_DVP_DROP_COUNT_OFFSET);
|
||||
status->error_status = *rs_dvp_register(RS_DVP_ERROR_STATUS_OFFSET);
|
||||
status->interrupt_state = *rs_dvp_register(RS_DVP_INTR_STATE_OFFSET);
|
||||
status->frame_count = rs_dvp_read(RS_DVP_FRAME_COUNT_OFFSET);
|
||||
status->line_count = rs_dvp_read(RS_DVP_LINE_COUNT_OFFSET);
|
||||
status->pixel_count = rs_dvp_read(RS_DVP_PIXEL_COUNT_OFFSET);
|
||||
status->word_count = rs_dvp_read(RS_DVP_WORD_COUNT_OFFSET);
|
||||
status->drop_count = rs_dvp_read(RS_DVP_DROP_COUNT_OFFSET);
|
||||
status->error_status = rs_dvp_read(RS_DVP_ERROR_STATUS_OFFSET);
|
||||
status->interrupt_state = rs_dvp_read(RS_DVP_INTR_STATE_OFFSET);
|
||||
return RS_OK;
|
||||
}
|
||||
|
||||
rs_status_t rs_dvp_interrupt_enable(uint32_t mask) {
|
||||
if ((mask & ~RS_DVP_INTERRUPT_ALL) != 0U)
|
||||
return RS_EINVAL;
|
||||
*rs_dvp_register(RS_DVP_INTR_ENABLE_OFFSET) = mask;
|
||||
rs_dvp_write(RS_DVP_INTR_ENABLE_OFFSET, mask);
|
||||
return RS_OK;
|
||||
}
|
||||
|
||||
rs_status_t rs_dvp_interrupt_clear(uint32_t mask) {
|
||||
if ((mask & ~RS_DVP_INTERRUPT_ALL) != 0U)
|
||||
return RS_EINVAL;
|
||||
*rs_dvp_register(RS_DVP_INTR_STATE_OFFSET) = mask;
|
||||
rs_dvp_write(RS_DVP_INTR_STATE_OFFSET, mask);
|
||||
return RS_OK;
|
||||
}
|
||||
|
||||
rs_status_t rs_dvp_interrupt_test(uint32_t mask) {
|
||||
if ((mask & ~RS_DVP_INTERRUPT_ALL) != 0U)
|
||||
return RS_EINVAL;
|
||||
*rs_dvp_register(RS_DVP_INTR_TEST_OFFSET) = mask;
|
||||
rs_dvp_write(RS_DVP_INTR_TEST_OFFSET, mask);
|
||||
return RS_OK;
|
||||
}
|
||||
|
||||
static rs_status_t rs_dvp_dma_cleanup(void) {
|
||||
const rs_status_t status = rs_dma_abort_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT);
|
||||
static rs_status_t rs_dvp_dma_cleanup(uint32_t channel) {
|
||||
const rs_status_t status = rs_dma_abort_wait(channel, RS_TIMEOUT_DEFAULT);
|
||||
|
||||
if (status != RS_OK) {
|
||||
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_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_status_t result;
|
||||
if ((destination == 0U) || (word_capacity == 0U) ||
|
||||
(word_capacity > (UINT32_MAX / sizeof(uint32_t))))
|
||||
return RS_EINVAL;
|
||||
uint32_t words;
|
||||
uint32_t pixels;
|
||||
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.request = RS_DMA_REQUEST_DVP_RX;
|
||||
dma_config.source = (uintptr_t)0U;
|
||||
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.source_increment = false;
|
||||
dma_config.destination_increment = true;
|
||||
dma_config.priority = 3U;
|
||||
dma_config.burst_beats = RS_DMA_MAX_BURST_BEATS;
|
||||
result = rs_dma_configure(RS_DMA_CHANNEL_BULK, &dma_config);
|
||||
if (result == RS_OK) {
|
||||
result = rs_dma_start(RS_DMA_CHANNEL_BULK);
|
||||
}
|
||||
if (result == RS_OK) {
|
||||
result = rs_dvp_start();
|
||||
}
|
||||
dma_config.burst_beats = RS_SOC_DVP_DMA_BURST_BEATS;
|
||||
result = rs_dma_configure(RS_DVP_DMA_CHANNEL, &dma_config);
|
||||
if (result != RS_OK) {
|
||||
if ((RS_DMA_CH_REG(RS_DMA_CHANNEL_BULK, RS_DMA_CH_REG_STATUS) & RS_DMA_STATUS_BUSY) != 0U) {
|
||||
(void)rs_dvp_dma_cleanup();
|
||||
}
|
||||
// A rejected configure may belong to another owner: do not abort it.
|
||||
return RS_EIO;
|
||||
}
|
||||
while (timeout-- != 0U) {
|
||||
if (rs_dvp_get_status(&status) != RS_OK) {
|
||||
(void)rs_dvp_abort();
|
||||
(void)rs_dvp_dma_cleanup();
|
||||
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;
|
||||
}
|
||||
if ((status.error_status != 0U) ||
|
||||
((status.interrupt_state & RS_DVP_INTERRUPT_FRAME_DONE) != 0U)) {
|
||||
(void)rs_dvp_abort();
|
||||
if ((status.error_status == 0U) &&
|
||||
(rs_dma_wait(RS_DMA_CHANNEL_BULK, timeout) == RS_OK)) {
|
||||
return RS_OK;
|
||||
}
|
||||
(void)rs_dvp_dma_cleanup();
|
||||
}
|
||||
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) {
|
||||
if (rs_dvp_get_status(&status) != RS_OK || status.error_status != 0U) {
|
||||
(void)rs_dvp_abort();
|
||||
return RS_EIO;
|
||||
}
|
||||
if ((status.status & RS_DVP_STATUS_FIFO_EMPTY_MASK) == 0U) {
|
||||
if (words >= expected_words) {
|
||||
(void)rs_dvp_abort();
|
||||
return RS_ENOSPC;
|
||||
}
|
||||
((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;
|
||||
}
|
||||
}
|
||||
}
|
||||
(void)rs_dvp_abort();
|
||||
(void)rs_dvp_dma_cleanup();
|
||||
return RS_ETIMEOUT;
|
||||
}
|
||||
|
||||
|
||||
+87
-4
@@ -70,7 +70,7 @@ valid capture. RW registers support byte strobes.
|
||||
| `0x024` | `FRAME_COUNT` | RO | Completed frame count. |
|
||||
| `0x028` | `LINE_COUNT` | RO | Completed line 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. |
|
||||
| `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. |
|
||||
@@ -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
|
||||
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
|
||||
`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
|
||||
@@ -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
|
||||
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.
|
||||
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:
|
||||
`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
|
||||
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
|
||||
|
||||
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
|
||||
statistics beyond65535 words, coherent host-side active/configuration/status,
|
||||
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
|
||||
counter/status payload. A downstream image/checksum cannot establish those
|
||||
unimplemented source guarantees. Minimal shared correctness fixes must retain
|
||||
The R2-P9 candidate implements SYNC/SIZE/PARTIAL detection and a 32-bit word
|
||||
counter transported through a 144-bit completed-frame mailbox. These source
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
+349
-62
@@ -31,15 +31,36 @@ module axi4s_dvp (
|
||||
apb4_if.slave apb4,
|
||||
axi4_stream_if.source rx_axis,
|
||||
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
|
||||
);
|
||||
localparam int DvpPayloadWidth = 42;
|
||||
|
||||
logic [ 127:0] s_cfg_sys;
|
||||
logic [127:0] s_cfg_sys;
|
||||
logic s_cfg_v_sys;
|
||||
logic s_cfg_rdy_sys;
|
||||
logic [ 127:0] s_cfg_pclk_q;
|
||||
logic s_cfg_link_ready;
|
||||
logic s_cfg_admit;
|
||||
logic s_cfg_apply_pclk_q;
|
||||
logic [128:0] s_cfg_packet_pclk;
|
||||
logic s_cfg_epoch_q, s_cfg_epoch_pclk_q, s_cfg_epoch_latched_q;
|
||||
logic s_cfg_busy_d, s_cfg_busy_q, s_cfg_accept_sys;
|
||||
logic s_cmd_epoch_q, s_cmd_epoch_pclk_q;
|
||||
logic s_cmd_busy_d, s_cmd_busy_q, s_cmd_accept_sys;
|
||||
logic [3:0] s_cmd_packet_pclk;
|
||||
logic s_cfg_cancel_d, s_cfg_cancel_q;
|
||||
logic s_cfg_cancel_epoch_q;
|
||||
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;
|
||||
@@ -58,15 +79,31 @@ module axi4s_dvp (
|
||||
logic s_pclk_sel;
|
||||
logic s_pclk_falling;
|
||||
logic s_active;
|
||||
logic s_active_pclk;
|
||||
logic [ 5:0] s_err_clear;
|
||||
logic [5:0] s_clear_pending_d, s_clear_pending_q;
|
||||
logic [5:0] s_clear_mask_q;
|
||||
logic s_clear_busy_d, s_clear_busy_q;
|
||||
logic s_clear_epoch_q;
|
||||
logic s_clear_epoch_pclk_q;
|
||||
logic s_clear_ready_sys;
|
||||
logic s_clear_valid_pclk;
|
||||
logic s_clear_ready_pclk;
|
||||
logic [ 6:0] s_clear_packet_pclk;
|
||||
logic s_clear_accept_sys;
|
||||
logic s_clear_accept_pclk;
|
||||
logic [ 5:0] s_clear_done;
|
||||
logic [ 12:0] s_stat_sys;
|
||||
logic s_stat_valid_sys;
|
||||
logic s_stream_en;
|
||||
logic s_fifo_src_ready;
|
||||
logic s_fifo_dst_valid;
|
||||
logic [DvpPayloadWidth-1:0] s_fifo_dst_data;
|
||||
logic [ DvpPayloadWidth-1:0] s_fifo_dst_data;
|
||||
logic s_fifo_dst_ready;
|
||||
logic s_fifo_src_clear;
|
||||
logic s_fifo_src_clear_busy;
|
||||
logic s_fifo_dst_clear_busy;
|
||||
logic [DvpPayloadWidth-1:0] s_core_push_data;
|
||||
logic [ DvpPayloadWidth-1:0] s_core_push_data;
|
||||
logic s_core_push_valid;
|
||||
logic [ 31:0] s_fifo_rx_data;
|
||||
logic s_rx_pop;
|
||||
@@ -75,28 +112,52 @@ module axi4s_dvp (
|
||||
logic s_frm_start_tgl;
|
||||
logic s_line_done_tgl;
|
||||
logic s_frm_done_tgl;
|
||||
logic s_err_tgl;
|
||||
logic s_unused_err_tgl;
|
||||
logic s_frame_start_event;
|
||||
logic s_line_done_event;
|
||||
logic s_frame_done_event;
|
||||
logic s_err_evt;
|
||||
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 [ 127:0] s_frm_stats_pclk;
|
||||
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 [ 127:0] s_frm_stats_sys;
|
||||
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};
|
||||
// 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_pclk_falling = s_cfg_sys[106];
|
||||
assign s_fifo_src_clear = s_cmd_pclk[`APB4_DVP__COMMAND_FLUSH] ||
|
||||
s_cmd_pclk[`APB4_DVP__COMMAND_ABORT];
|
||||
assign s_fifo_src_clear = s_cmd_v_pclk && s_cmd_rdy_pclk &&
|
||||
(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_rx_data = s_fifo_dst_data[31:0];
|
||||
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 (
|
||||
.clk_i(dvp.pclk_i),
|
||||
@@ -126,14 +187,19 @@ module axi4s_dvp (
|
||||
.rst_n_i (rst_n_i),
|
||||
.apb4 (apb4),
|
||||
.active_i (s_active),
|
||||
.enabled_i (s_stat_sys[7]),
|
||||
.fifo_empty_i (s_fifo_empty_sys),
|
||||
.fifo_full_i (s_fifo_full_sys),
|
||||
.rx_data_i (s_fifo_rx_data),
|
||||
.frame_start_i (s_frame_start_event),
|
||||
.line_done_i (s_line_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_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_valid_i(s_frm_stats_v_sys),
|
||||
.cfg_o (s_cfg_sys),
|
||||
@@ -142,38 +208,146 @@ module axi4s_dvp (
|
||||
.cmd_abort_o (s_cmd_abort_sys),
|
||||
.cmd_flush_o (s_cmd_flush_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),
|
||||
.stream_enable_o (s_stream_en),
|
||||
.irq_o (irq_o)
|
||||
);
|
||||
|
||||
cdc_2phase #(
|
||||
.DATA_WIDTH(128)
|
||||
.DATA_WIDTH(129)
|
||||
) u_dvp_config_cdc (
|
||||
.src_clk_i (clk_i),
|
||||
.src_rst_n_i(rst_n_i),
|
||||
.src_data_i (s_cfg_sys),
|
||||
.src_valid_i(s_cfg_v_sys),
|
||||
.src_ready_o(s_cfg_rdy_sys),
|
||||
.src_data_i ({!s_cfg_epoch_q, s_cfg_sys}),
|
||||
.src_valid_i(s_cfg_v_sys && s_cfg_admit),
|
||||
.src_ready_o(s_cfg_link_ready),
|
||||
.dst_clk_i (s_pclk_sel),
|
||||
.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_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 #(
|
||||
.DATA_WIDTH(2)
|
||||
.DATA_WIDTH(4)
|
||||
) u_dvp_command_cdc (
|
||||
.src_clk_i (clk_i),
|
||||
.src_rst_n_i(rst_n_i),
|
||||
.src_data_i (s_cmd_sys),
|
||||
.src_valid_i(s_cmd_v_sys),
|
||||
.src_data_i ({s_cfg_epoch_q, !s_cmd_epoch_q, s_cmd_sys}),
|
||||
.src_valid_i(s_cmd_v_sys && !s_cmd_busy_q),
|
||||
.src_ready_o(s_cmd_rdy_sys),
|
||||
.dst_clk_i (s_pclk_sel),
|
||||
.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_ready_i(s_cmd_rdy_pclk)
|
||||
);
|
||||
@@ -196,7 +370,128 @@ module axi4s_dvp (
|
||||
.dst_valid_o (s_fifo_dst_valid),
|
||||
.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 #(
|
||||
.STAGE (2),
|
||||
@@ -207,61 +502,46 @@ module axi4s_dvp (
|
||||
.dat_i (!s_fifo_src_ready),
|
||||
.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 */
|
||||
edge_det #(
|
||||
.STAGE(2)
|
||||
) u_frame_start_event (
|
||||
.clk_i(clk_i),
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.dat_i(s_frm_start_tgl),
|
||||
.dat_o(), // Synchronized level is intentionally unused; only the rising edge is consumed.
|
||||
.re_o(s_frame_start_event),
|
||||
.fe_o() // Falling edge is intentionally unused.
|
||||
.dat_i (s_frm_start_tgl),
|
||||
.dat_o (), // Only the synchronized edge pulses are consumed.
|
||||
.re_o (s_event_rise[0]),
|
||||
.fe_o (s_event_fall[0])
|
||||
);
|
||||
edge_det #(
|
||||
.STAGE(2)
|
||||
) u_line_done_event (
|
||||
.clk_i(clk_i),
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.dat_i(s_line_done_tgl),
|
||||
.dat_o(), // Synchronized level is intentionally unused; only the rising edge is consumed.
|
||||
.re_o(s_line_done_event),
|
||||
.fe_o() // Falling edge is intentionally unused.
|
||||
.dat_i (s_line_done_tgl),
|
||||
.dat_o (), // Only the synchronized edge pulses are consumed.
|
||||
.re_o (s_event_rise[1]),
|
||||
.fe_o (s_event_fall[1])
|
||||
);
|
||||
edge_det #(
|
||||
.STAGE(2)
|
||||
) u_frame_done_event (
|
||||
.clk_i(clk_i),
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.dat_i(s_frm_done_tgl),
|
||||
.dat_o(), // Synchronized level is intentionally unused; only the rising edge is consumed.
|
||||
.re_o(s_frame_done_event),
|
||||
.fe_o() // Falling edge is intentionally unused.
|
||||
);
|
||||
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.
|
||||
.dat_i (s_frm_done_tgl),
|
||||
.dat_o (), // Only the synchronized edge pulses are consumed.
|
||||
.re_o (s_event_rise[2]),
|
||||
.fe_o (s_event_fall[2])
|
||||
);
|
||||
/* verilator lint_on PINCONNECTEMPTY */
|
||||
|
||||
cdc_2phase #(
|
||||
.DATA_WIDTH(128)
|
||||
.DATA_WIDTH(`APB4_DVP__FRAME_STATS_WIDTH)
|
||||
) u_dvp_stats_cdc (
|
||||
.src_clk_i (s_pclk_sel),
|
||||
.src_rst_n_i(s_pclk_rst_n),
|
||||
@@ -283,23 +563,30 @@ module axi4s_dvp (
|
||||
assign rx_axis.tdest = '0;
|
||||
assign rx_axis.tuser = s_fifo_dst_data[41];
|
||||
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 (
|
||||
.clk_i (s_pclk_sel),
|
||||
.rst_n_i (s_pclk_rst_n),
|
||||
.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),
|
||||
.error_clear_i (s_clear_accept_pclk ? s_clear_packet_pclk[5:0] : 6'd0),
|
||||
.cfg_ready_o (s_cfg_rdy_pclk),
|
||||
.push_ready_i (s_fifo_src_ready),
|
||||
.push_valid_o (s_core_push_valid),
|
||||
.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),
|
||||
.line_done_toggle_o (s_line_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_events_o (s_core_err_events),
|
||||
.frame_stats_o (s_frm_stats_pclk),
|
||||
.frame_stats_valid_o (s_frm_stats_v_pclk),
|
||||
.frame_stats_ready_i (s_frm_stats_rdy_pclk),
|
||||
|
||||
@@ -18,18 +18,22 @@ module dvp_core (
|
||||
input logic [127:0] cfg_i,
|
||||
/* verilator lint_on UNUSEDSIGNAL */
|
||||
input logic cfg_valid_i,
|
||||
input logic cfg_cancel_i,
|
||||
input logic [ 1:0] cmd_i,
|
||||
input logic [ 5:0] error_clear_i,
|
||||
output logic cfg_ready_o,
|
||||
input logic push_ready_i,
|
||||
output logic push_valid_o,
|
||||
output logic [ 41:0] push_data_o,
|
||||
output logic active_o,
|
||||
output logic capture_idle_o,
|
||||
output logic frame_start_toggle_o,
|
||||
output logic line_done_toggle_o,
|
||||
output logic frame_done_toggle_o,
|
||||
output logic error_toggle_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,
|
||||
input logic frame_stats_ready_i,
|
||||
input logic href_i,
|
||||
@@ -63,13 +67,15 @@ module dvp_core (
|
||||
logic [15:0] s_word_q;
|
||||
logic [31:0] s_frm_seq_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 [ 5:0] s_err_flags_q;
|
||||
logic s_out_valid_q;
|
||||
logic [41:0] s_out_data_q;
|
||||
logic s_stat_pending_q;
|
||||
logic s_first_word_q;
|
||||
logic s_snapshot_done_q;
|
||||
logic s_cfg_en_q;
|
||||
|
||||
logic s_vsync_active;
|
||||
logic s_href_active;
|
||||
@@ -99,10 +105,12 @@ module dvp_core (
|
||||
(s_byte_swap ? {dat_i, s_first_byte_q} :
|
||||
{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_data_o = s_out_data_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
|
||||
s_en = cfg_i[96];
|
||||
@@ -141,6 +149,9 @@ module dvp_core (
|
||||
s_out_data_q <= '0;
|
||||
s_stat_pending_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;
|
||||
line_done_toggle_o <= 1'b0;
|
||||
frame_done_toggle_o <= 1'b0;
|
||||
@@ -148,6 +159,9 @@ module dvp_core (
|
||||
frame_stats_o <= '0;
|
||||
frame_stats_valid_o <= 1'b0;
|
||||
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_href_q <= s_href_active;
|
||||
frame_stats_valid_o <= 1'b0;
|
||||
@@ -158,17 +172,30 @@ module dvp_core (
|
||||
frame_stats_valid_o <= 1'b1;
|
||||
end
|
||||
|
||||
if (cfg_valid_i && !s_frm_active_q) begin
|
||||
if ((s_frm_width == 0) || (s_frm_height == 0) || (s_format > 2'd1) ||
|
||||
(s_crop_en && ((s_crop_width == 0) || (s_crop_height == 0)))) begin
|
||||
// A pre-ABORT config can retire after the abort pulse. It must not
|
||||
// undo the stopped state or masquerade as a subsequent rearm.
|
||||
if (cfg_valid_i && !cfg_cancel_i && !s_frm_active_q) begin
|
||||
s_cfg_en_q <= s_en;
|
||||
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) s_frm_active_q <= 1'b0;
|
||||
if (!s_en) begin
|
||||
s_frm_active_q <= 1'b0;
|
||||
s_snapshot_done_q <= 1'b0;
|
||||
end
|
||||
end
|
||||
if (cmd_i[`APB4_DVP__COMMAND_ABORT]) begin
|
||||
s_frm_active_q <= 1'b0;
|
||||
s_snapshot_done_q <= 1'b1;
|
||||
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
|
||||
if (cmd_i[`APB4_DVP__COMMAND_FLUSH]) begin
|
||||
@@ -177,8 +204,16 @@ module dvp_core (
|
||||
s_word_phase_q <= 1'b0;
|
||||
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_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_stats_o <= {
|
||||
s_drop_count_q, s_word_total_q, s_pixel_total_q, s_line_q, s_frm_seq_q + 1'b1
|
||||
@@ -187,6 +222,7 @@ module dvp_core (
|
||||
s_frm_seq_q <= s_frm_seq_q + 1'b1;
|
||||
if (s_snap) begin
|
||||
s_frm_active_q <= 1'b0;
|
||||
s_snapshot_done_q <= 1'b1;
|
||||
end else begin
|
||||
s_line_q <= '0;
|
||||
s_pixel_q <= '0;
|
||||
@@ -204,10 +240,24 @@ module dvp_core (
|
||||
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
|
||||
s_first_byte_q <= dat_i;
|
||||
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
|
||||
if (s_crop_line && s_crop_pixel) begin
|
||||
if (!s_word_phase_q) begin
|
||||
@@ -223,6 +273,7 @@ module dvp_core (
|
||||
s_word_total_q <= s_word_total_q + 1'b1;
|
||||
end else begin
|
||||
s_err_flags_q[`APB4_DVP__ERROR_OVERFLOW] <= 1'b1;
|
||||
error_events_o[`APB4_DVP__ERROR_OVERFLOW] <= 1'b1;
|
||||
s_drop_count_q <= s_drop_count_q + 1'b1;
|
||||
error_toggle_o <= ~error_toggle_o;
|
||||
s_frm_active_q <= 1'b0;
|
||||
@@ -234,11 +285,24 @@ module dvp_core (
|
||||
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;
|
||||
s_line_q <= s_line_q + 1'b1;
|
||||
s_pixel_q <= '0;
|
||||
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
|
||||
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;
|
||||
@@ -247,6 +311,7 @@ module dvp_core (
|
||||
s_word_total_q <= s_word_total_q + 1'b1;
|
||||
end else if (s_word_phase_q) begin
|
||||
s_err_flags_q[`APB4_DVP__ERROR_OVERFLOW] <= 1'b1;
|
||||
error_events_o[`APB4_DVP__ERROR_OVERFLOW] <= 1'b1;
|
||||
error_toggle_o <= ~error_toggle_o;
|
||||
s_frm_active_q <= 1'b0;
|
||||
end
|
||||
@@ -254,6 +319,5 @@ module dvp_core (
|
||||
end
|
||||
end
|
||||
|
||||
// SYNC, SIZE and PARTIAL are reserved until their capture paths are implemented.
|
||||
assign error_flags_o = {s_err_flags_q[5:4], 3'b000, s_err_flags_q[0]};
|
||||
assign error_flags_o = s_err_flags_q;
|
||||
endmodule
|
||||
@@ -11,6 +11,10 @@
|
||||
`ifndef 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
|
||||
`define APB4_DVP_CTRL 12'h000
|
||||
`define APB4_DVP_RXDATA 12'h004
|
||||
@@ -39,6 +43,10 @@
|
||||
`define APB4_DVP__CTRL_SNAPSHOT 1
|
||||
`define APB4_DVP__CTRL_CROP_ENABLE 2
|
||||
`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_BYTE_SWAP 2
|
||||
`define APB4_DVP__FORMAT_PIXEL_SWAP 3
|
||||
|
||||
@@ -16,6 +16,7 @@ module dvp_reg (
|
||||
input logic rst_n_i,
|
||||
apb4_if.slave apb4,
|
||||
input logic active_i,
|
||||
input logic enabled_i,
|
||||
input logic fifo_empty_i,
|
||||
input logic fifo_full_i,
|
||||
input logic [31:0] rx_data_i,
|
||||
@@ -23,8 +24,12 @@ module dvp_reg (
|
||||
input logic line_done_i,
|
||||
input logic frame_done_i,
|
||||
input logic error_event_i,
|
||||
input logic [ 5:0] error_events_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,
|
||||
output logic [127:0] cfg_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_sent_q;
|
||||
logic [ 1:0] s_cmd_q;
|
||||
logic s_abort_accept;
|
||||
|
||||
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);
|
||||
@@ -107,8 +113,14 @@ module dvp_reg (
|
||||
cfg_o[106:104] = s_sync_q[2:0];
|
||||
end
|
||||
assign cfg_valid_o = s_cfg_seq_q != s_cfg_sent_q;
|
||||
assign s_abort_accept = s_accept && s_write && !s_access_err &&
|
||||
(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
|
||||
s_access_err = !s_aligned;
|
||||
@@ -119,7 +131,7 @@ module dvp_reg (
|
||||
`APB4_DVP_RXDATA: s_rdata_d = rx_data_i;
|
||||
`APB4_DVP_STATUS:
|
||||
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_FORMAT: s_rdata_d = s_format_q;
|
||||
@@ -284,31 +296,37 @@ module dvp_reg (
|
||||
s_word_cnt_q <= '0;
|
||||
s_drop_cnt_q <= '0;
|
||||
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 (s_accept && s_write && !s_access_err && (s_offset == `APB4_DVP_COMMAND)) begin
|
||||
s_cmd_q <= s_cmd_q | apb4.pwdata[1:0];
|
||||
end
|
||||
s_err_q <= (s_err_q | error_flags_i) &
|
||||
~((s_accept && s_write && (s_offset == `APB4_DVP_ERROR_STATUS))
|
||||
? apb4.pwdata[5:0]
|
||||
: 6'd0);
|
||||
// Keep errors visible until the pixel-domain clear has completed. A
|
||||
// coherent post-clear report can set a newly arriving error immediately.
|
||||
s_err_q <= (s_err_q & ~error_clear_done_i) | (error_valid_i ? error_flags_i : 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 (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 (error_event_i) s_intr_stat_q[`APB4_DVP__INTR_OVERFLOW] <= 1'b1;
|
||||
if (s_accept && s_write && (s_offset == `APB4_DVP_INTR_STATE))
|
||||
s_intr_stat_q <= s_intr_stat_q & ~apb4.pwdata[6:0];
|
||||
if (error_event_i) begin
|
||||
if (error_events_i[0]) s_intr_stat_q[`APB4_DVP__INTR_OVERFLOW] <= 1'b1;
|
||||
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))
|
||||
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
|
||||
s_frm_cnt_q <= frame_stats_i[31:0];
|
||||
s_line_cnt_q <= {16'd0, frame_stats_i[47:32]};
|
||||
s_pixel_cnt_q <= frame_stats_i[79:48];
|
||||
s_word_cnt_q <= {16'd0, frame_stats_i[95:80]};
|
||||
s_drop_cnt_q <= frame_stats_i[127:96];
|
||||
s_word_cnt_q <= frame_stats_i[111:80];
|
||||
s_drop_cnt_q <= frame_stats_i[143:112];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,19 +8,27 @@
|
||||
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the Mulan PSL v2 for more details.
|
||||
|
||||
`include "dvp_define.svh"
|
||||
|
||||
module dvp_formal_design (
|
||||
input logic clk_i,
|
||||
output logic rst_n_i,
|
||||
output logic f_past_valid,
|
||||
output logic rib_valid,
|
||||
output logic [31:0] rib_addr,
|
||||
output logic [31:0] rib_wdata,
|
||||
output logic [ 31:0] rib_addr,
|
||||
output logic [ 31:0] rib_wdata,
|
||||
output logic [ 3:0] rib_wstrb,
|
||||
output logic rib_ready,
|
||||
output logic rib_resp_err,
|
||||
output logic [ 6:0] intr_state,
|
||||
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 (
|
||||
.pclk (clk_i),
|
||||
@@ -30,9 +38,15 @@ module dvp_formal_design (
|
||||
(* anyseq *) logic [31:0] f_apb_addr, f_apb_wdata;
|
||||
(* anyseq *) logic [3:0] f_apb_pstrb;
|
||||
(* anyseq *) logic f_active, f_fifo_empty, f_fifo_full;
|
||||
(* anyseq *)logic f_cfg_ready;
|
||||
(* anyseq *)logic [ 5:0] f_error_flags;
|
||||
(* anyseq *)logic [127:0] f_frame_stats;
|
||||
(* anyseq *)logic [`APB4_DVP__FRAME_STATS_WIDTH-1:0] f_frame_stats;
|
||||
(* 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.pwrite = |f_apb_pstrb;
|
||||
@@ -48,24 +62,39 @@ module dvp_formal_design (
|
||||
assign rib_resp_err = apb4.pslverr;
|
||||
assign intr_state = u_dut.s_intr_stat_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 (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.apb4 (apb4),
|
||||
.active_i (f_active),
|
||||
.enabled_i (f_active),
|
||||
.fifo_empty_i (f_fifo_empty),
|
||||
.fifo_full_i (f_fifo_full),
|
||||
.rx_data_i (32'd0),
|
||||
.frame_start_i (1'b0),
|
||||
.line_done_i (1'b0),
|
||||
.frame_done_i (1'b0),
|
||||
.frame_start_i (f_frame_events[0]),
|
||||
.line_done_i (f_frame_events[1]),
|
||||
.frame_done_i (f_frame_events[2]),
|
||||
.error_event_i (|f_error_flags),
|
||||
.error_events_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_valid_i(f_frame_stats_valid),
|
||||
.cfg_o (),
|
||||
.cfg_valid_o (),
|
||||
.cfg_ready_i (1'b1),
|
||||
.cfg_valid_o (cfg_pending),
|
||||
.cfg_ready_i (f_cfg_ready),
|
||||
.cmd_abort_o (),
|
||||
.cmd_flush_o (),
|
||||
.cmd_valid_o (),
|
||||
@@ -79,6 +108,7 @@ module dvp_formal_design (
|
||||
f_past_valid = 1'b0;
|
||||
end
|
||||
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;
|
||||
f_past_valid <= 1'b1;
|
||||
end
|
||||
|
||||
@@ -14,19 +14,30 @@ module dvp_formal;
|
||||
logic [31:0] rib_addr, rib_wdata;
|
||||
logic [3:0] rib_wstrb;
|
||||
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 (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.f_past_valid(f_past_valid),
|
||||
.f_past_valid (f_past_valid),
|
||||
.rib_valid (rib_valid),
|
||||
.rib_addr (rib_addr),
|
||||
.rib_wdata (rib_wdata),
|
||||
.rib_wstrb (rib_wstrb),
|
||||
.rib_ready (rib_ready),
|
||||
.rib_resp_err(rib_resp_err),
|
||||
.rib_resp_err (rib_resp_err),
|
||||
.intr_state (intr_state),
|
||||
.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
|
||||
if (rst_n_i) begin
|
||||
@@ -38,6 +49,16 @@ module dvp_formal;
|
||||
assert (rib_ready);
|
||||
end
|
||||
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 (irq);
|
||||
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_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_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": "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"},
|
||||
|
||||
+1
-1
@@ -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 \
|
||||
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 \
|
||||
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 += $(ROOT_PATH)/crt/src/hal/sysctrl.c
|
||||
endif
|
||||
|
||||
@@ -156,6 +156,28 @@ netsim-boot: netcomp tiny-boot-image | manifest
|
||||
|
||||
.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.
|
||||
# Image creation always compiles and snapshots inputs before retaining a binary.
|
||||
# Simulation rebuilds its model once, then replays the same image three times.
|
||||
|
||||
+7
-6
@@ -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.
|
||||
The original R2 phase IDs/titles and performance-only scope remain intact.
|
||||
|
||||
Camera GPIO12-23 ALT1 remains mutually exclusive with I2S. DMA2/request 11,
|
||||
optional XPI NSS1 PSRAM at `0x54000000` through GPIO29 and shared CAM_XCLK
|
||||
retain their existing target contracts. RCU target 14 and DVP capabilities
|
||||
stay unsupported until actual integration. `TINY-R2-P8` and `TINY-R2-P9`
|
||||
require pin-level XPI PSRAM and full-frame readback, not fast-flash or
|
||||
separate-controller evidence.
|
||||
Camera GPIO12-23 ALT1 is now integrated and remains mutually exclusive with
|
||||
I2S. DVP uses APB4 `0x1000E000`, IRQ15, DMA2/request11, the existing pixel CDC
|
||||
FIFO and RCU target14. Optional XPI NSS1 PSRAM at `0x54000000` through GPIO29
|
||||
and shared CAM_XCLK retain their existing target contracts. `TINY-R2-P8` and
|
||||
`TINY-R2-P9` require pin-level XPI PSRAM and full-frame readback, not
|
||||
fast-flash or a separate controller; physical sensor and rate qualification
|
||||
remain later evidence.
|
||||
No camera/PSRAM rate is qualified by this freeze.
|
||||
|
||||
The [P8 implementation](../../docs/ip/tiny-soc-r2-psram.md) adds explicit optional
|
||||
|
||||
@@ -427,6 +427,15 @@
|
||||
"public": true,
|
||||
"user_access": "none"
|
||||
},
|
||||
{
|
||||
"symbol": "APB4_DVP",
|
||||
"base": "0x1000E000",
|
||||
"size": "0x00001000",
|
||||
"route": "apb4_periph",
|
||||
"kind": "active",
|
||||
"public": true,
|
||||
"user_access": "none"
|
||||
},
|
||||
{
|
||||
"symbol": "APB4_SDIO0",
|
||||
"base": "0x1000F000",
|
||||
|
||||
@@ -26,10 +26,38 @@ module retrosoc_tiny_tb;
|
||||
wire s_xpi_sck;
|
||||
wire [3:0] s_xpi_nss;
|
||||
tri1 [3:0] s_xpi_data;
|
||||
// Optional board population; the unchanged default has no PSRAM dependency.
|
||||
wire s_psram_csn = ($test$plusargs(
|
||||
"tiny_p8_psram"
|
||||
wire s_dvp_pclk;
|
||||
wire s_dvp_href;
|
||||
wire s_dvp_vsync;
|
||||
wire [7:0] s_dvp_data;
|
||||
logic s_p9_camera_population = $test$plusargs(
|
||||
"tiny_p9_camera"
|
||||
) && !$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"
|
||||
)) ? s_gpio[29] : 1'b1;
|
||||
ESP_PSRAM64H #(
|
||||
@@ -45,6 +73,8 @@ module retrosoc_tiny_tb;
|
||||
`include "tiny_p6_devices.svh"
|
||||
`include "tiny_p7_observers.svh"
|
||||
`include "tiny_p8_observers.svh"
|
||||
`include "tiny_p9_observers.svh"
|
||||
`include "tiny_p9_recovery.svh"
|
||||
`endif
|
||||
integer s_max_cycles = 20000000;
|
||||
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);
|
||||
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
|
||||
localparam bit ObserveUartAlways = 1'b1;
|
||||
`else
|
||||
localparam bit ObserveUartAlways = 1'b0;
|
||||
`endif
|
||||
wire ObserveP9Progress = $test$plusargs("tiny_p9_progress");
|
||||
// The Verilator RTL prints accepted TX bytes. Icarus observes serial output for
|
||||
// all firmware measurements; SYSCTRL remains the authoritative verdict.
|
||||
initial begin
|
||||
if (ObserveUartAlways) begin
|
||||
if (ObserveUartAlways || ObserveP9Progress) begin
|
||||
forever begin
|
||||
@(negedge s_uart0_tx);
|
||||
#1627.604;
|
||||
|
||||
@@ -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
|
||||
@@ -79,13 +79,18 @@ logic [23:0] s_p6_wire_shift = 0;
|
||||
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_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.
|
||||
always @(posedge s_gpio[12] or negedge s_p6_audio_enabled) begin
|
||||
if (!s_p6_audio_enabled) begin
|
||||
if (s_p6_wire_found) begin
|
||||
if (s_p6_wire_matched != 512) $fatal(1, "SIM_TEST_FAIL P6 truncated I2S wire payload");
|
||||
if (s_p6_wire_found || (s_p6_recovery_mode && s_p6_wire_examined != 0)) begin
|
||||
if (s_p6_wire_matched != (s_p6_recovery_mode ? 8 : 512))
|
||||
$fatal(1, "SIM_TEST_FAIL truncated I2S wire payload");
|
||||
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
|
||||
s_p6_wire_matched = 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_edges = 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;
|
||||
if (s_p6_wire_found && s_p6_wire_matched < 512) begin
|
||||
if (s_p6_wire_word != (s_p6_audio_wide ? 32'h120020 : 32'h2020) + 32'(s_p6_wire_matched))
|
||||
if (s_p6_wire_found && s_p6_wire_matched < (s_p6_recovery_mode ? 8 : 512)) begin
|
||||
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);
|
||||
s_p6_wire_matched++;
|
||||
end
|
||||
@@ -274,10 +281,11 @@ for (genvar owner = 0; owner < 3; owner++) begin : gen_p6_axi_observer
|
||||
`undef _P6_AXI
|
||||
end
|
||||
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.
|
||||
// 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 [3:0] s_p6_card_data;
|
||||
wire s_p6_card_irq;
|
||||
|
||||
@@ -13,7 +13,7 @@ initial begin
|
||||
// Case comparisons require the exact known reset values in both simulators.
|
||||
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.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(
|
||||
1,
|
||||
"SIM_TEST_FAIL P7 cold reset state cause=%h fault=%h profile=%h gates=%h",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// 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
|
||||
logic s_p8_previous_enable = 0;
|
||||
logic s_p8_finished = 0;
|
||||
@@ -56,7 +58,7 @@ end
|
||||
time s_p8_cs_start, s_p8_cs_max = 0;
|
||||
always @(negedge s_psram_csn) s_p8_cs_start = $time;
|
||||
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 > s_p8_cs_max) s_p8_cs_max = $time - s_p8_cs_start;
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -18,5 +18,6 @@
|
||||
+incdir+/ip/peripheral
|
||||
+incdir+/ip/core
|
||||
+incdir+/ip/security
|
||||
+incdir+/ip/multimedia
|
||||
+incdir+/clusterip/rng/rtl
|
||||
+incdir+/clusterip/crc/rtl
|
||||
@@ -84,3 +84,7 @@
|
||||
/ip/storage/sdio_reg.sv
|
||||
/ip/storage/sdio_core.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,4 +1,5 @@
|
||||
/dv/tiny_qspi_nor.sv
|
||||
/device_model/ESP_PSRAM64H.sv
|
||||
/dv/tiny_dvp_camera.sv
|
||||
/tests/rtl/sdio_native_model.sv
|
||||
/dv/retrosoc_tiny_tb.sv
|
||||
@@ -4,7 +4,7 @@
|
||||
"phase": "TINY-R2-P7",
|
||||
"clock_domain": "system",
|
||||
"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",
|
||||
"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",
|
||||
@@ -116,6 +116,21 @@
|
||||
"period_ns": 54.253472222,
|
||||
"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": {
|
||||
@@ -145,7 +160,8 @@
|
||||
"ws2812",
|
||||
"crypto",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"source": "peripheral",
|
||||
@@ -407,7 +479,8 @@
|
||||
"crc",
|
||||
"ws2812",
|
||||
"crypto",
|
||||
"i2s"
|
||||
"i2s",
|
||||
"dvp"
|
||||
]
|
||||
},
|
||||
"same_clock_isolation": [
|
||||
|
||||
@@ -37,9 +37,9 @@
|
||||
],
|
||||
"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
|
||||
},
|
||||
"gpio_pins": 32,
|
||||
"irq_vector_width": 32,
|
||||
@@ -166,6 +166,12 @@
|
||||
"regions": [
|
||||
"APB4_SDIO0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "dvp",
|
||||
"regions": [
|
||||
"APB4_DVP"
|
||||
]
|
||||
}
|
||||
],
|
||||
"interrupts": [
|
||||
@@ -214,6 +220,11 @@
|
||||
"core_bit": 10,
|
||||
"signal": "u_sdio_if.irq_o"
|
||||
},
|
||||
{
|
||||
"name": "dvp",
|
||||
"core_bit": 15,
|
||||
"signal": "s_dvp_irq"
|
||||
},
|
||||
{
|
||||
"name": "pwm",
|
||||
"core_bit": 11,
|
||||
@@ -441,7 +452,9 @@
|
||||
"oe": "1'b1"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.dat_i[0]"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -454,7 +467,9 @@
|
||||
"oe": "1'b1"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.dat_i[1]"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -467,7 +482,9 @@
|
||||
"oe": "1'b1"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.dat_i[2]"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -482,7 +499,9 @@
|
||||
"oe": "1'b0"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.dat_i[3]"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -495,7 +514,9 @@
|
||||
"oe": "1'b1"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.dat_i[4]"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -510,7 +531,9 @@
|
||||
"oe": "1'b0"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.dat_i[5]"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -523,7 +546,9 @@
|
||||
"oe": "u_pwm_if.oe_o[0]"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.dat_i[6]"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -536,7 +561,9 @@
|
||||
"oe": "u_pwm_if.oe_o[1]"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.dat_i[7]"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -549,7 +576,9 @@
|
||||
"oe": "u_pwm_if.oe_o[2]"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.pclk_i"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -562,7 +591,9 @@
|
||||
"oe": "u_pwm_if.oe_o[3]"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.href_i"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -577,7 +608,9 @@
|
||||
"oe": "1'b0"
|
||||
},
|
||||
"alt1": {
|
||||
"inputs": [],
|
||||
"inputs": [
|
||||
"u_dvp_if.vsync_i"
|
||||
],
|
||||
"do": "1'b0",
|
||||
"oe": "1'b0"
|
||||
}
|
||||
@@ -706,7 +739,7 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"dma_request_mask": "0x31ff",
|
||||
"dma_request_mask": "0x39ff",
|
||||
"private_crypto": {
|
||||
"banks": 6,
|
||||
"bank_bytes": 4096,
|
||||
|
||||
@@ -89,15 +89,15 @@ module retrosoc_tiny (
|
||||
logic [31:0] s_clkout_control;
|
||||
logic s_clkout, s_clkout_enable;
|
||||
logic s_clkout_idle_ref;
|
||||
logic [13: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_request, s_local_reset, s_actual_gate, s_reset_held, s_reset_ready;
|
||||
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 [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 s_bus_idle_ref;
|
||||
logic s_boot_release_d, s_boot_release_q;
|
||||
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_pwm_write, s_crc_write;
|
||||
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_i2s_tx_stall, s_i2s_rx_stall;
|
||||
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;
|
||||
`include "tiny_apb_interfaces.svh"
|
||||
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 ();
|
||||
dma_req_if u_dma_req_if ();
|
||||
i2s_if u_i2s_if ();
|
||||
dvp_if u_dvp_if ();
|
||||
ws2812_if u_ws2812_if ();
|
||||
sdio_if u_sdio_if ();
|
||||
rtc_if u_rtc_if (
|
||||
@@ -394,7 +396,7 @@ ahbl_if u_cpu_ahbl_if[2] (
|
||||
assign s_reset_ready[0] = 1'b0;
|
||||
cdc_sync #(
|
||||
.STAGE (2),
|
||||
.DATA_WIDTH(14)
|
||||
.DATA_WIDTH(15)
|
||||
) u_gate_feedback (
|
||||
.clk_i (s_ref_clk),
|
||||
.rst_n_i(s_por_rst_n),
|
||||
@@ -403,7 +405,7 @@ ahbl_if u_cpu_ahbl_if[2] (
|
||||
);
|
||||
cdc_sync #(
|
||||
.STAGE (2),
|
||||
.DATA_WIDTH(14)
|
||||
.DATA_WIDTH(15)
|
||||
) u_held_feedback (
|
||||
.clk_i (s_ref_clk),
|
||||
.rst_n_i(s_por_rst_n),
|
||||
@@ -412,7 +414,7 @@ ahbl_if u_cpu_ahbl_if[2] (
|
||||
);
|
||||
cdc_sync #(
|
||||
.STAGE (2),
|
||||
.DATA_WIDTH(14)
|
||||
.DATA_WIDTH(15)
|
||||
) u_ready_feedback (
|
||||
.clk_i (s_ref_clk),
|
||||
.rst_n_i(s_por_rst_n),
|
||||
@@ -421,7 +423,7 @@ ahbl_if u_cpu_ahbl_if[2] (
|
||||
);
|
||||
cdc_sync #(
|
||||
.STAGE (2),
|
||||
.DATA_WIDTH(14)
|
||||
.DATA_WIDTH(15)
|
||||
) u_disabled_sync (
|
||||
.clk_i (s_exec_clk),
|
||||
.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;
|
||||
cdc_sync #(
|
||||
.STAGE (2),
|
||||
.DATA_WIDTH(14)
|
||||
.DATA_WIDTH(15)
|
||||
) u_periph_idle_sync (
|
||||
.clk_i (s_ref_clk),
|
||||
.rst_n_i(s_por_rst_n),
|
||||
@@ -798,7 +800,7 @@ axi4_error_slave #(
|
||||
.PllPresent (TinyPllPresent),
|
||||
.PllProfiles(TinyPllPresent),
|
||||
.XinHzValue (`SOC_EXT_CLK_HZ),
|
||||
.TargetMask (14'h3fff)
|
||||
.TargetMask (15'h07fff)
|
||||
) u_sysctrl (
|
||||
.clk_i (s_exec_clk),
|
||||
.rst_n_i (s_sysctrl_rst_n),
|
||||
@@ -858,7 +860,7 @@ axi4_error_slave #(
|
||||
.ref_clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.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),
|
||||
.system_request_i (s_system_reset),
|
||||
.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),
|
||||
.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 #(
|
||||
.NumChannels (8),
|
||||
.MaxBurstBeats(16),
|
||||
.FifoDepth (32),
|
||||
.RequestMask (16'h31ff),
|
||||
.RequestMask (16'h39ff),
|
||||
.EnableStreams(1'b1)
|
||||
) u_dma (
|
||||
.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.i2c1_tx_proc = 1'b0;
|
||||
assign u_dma_req_if.i2c1_rx_proc = 1'b0;
|
||||
assign u_dvp_rx_axis_if.tvalid = '0;
|
||||
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;
|
||||
assign s_periph_idle[14] = s_dvp_idle && s_dma_idle_sys;
|
||||
|
||||
// Host logic remains SAFE24. Audio reset waits for its own clock, never CPU boot.
|
||||
tc_clk_buf u_audio_clock_buffer (
|
||||
|
||||
@@ -9,19 +9,19 @@ module tiny_peripheral_control (
|
||||
input logic warm_reset_i,
|
||||
input logic apply_i,
|
||||
input logic reset_command_i,
|
||||
input logic [13:0] request_i,
|
||||
input logic [13:0] idle_i,
|
||||
input logic [14:0] request_i,
|
||||
input logic [14:0] idle_i,
|
||||
input logic bus_idle_i,
|
||||
input logic [13:0] gate_actual_i,
|
||||
input logic [13:0] reset_held_i,
|
||||
input logic [13:0] reset_ready_i,
|
||||
input logic [14:0] gate_actual_i,
|
||||
input logic [14:0] reset_held_i,
|
||||
input logic [14:0] reset_ready_i,
|
||||
input logic [31:0] timeout_i,
|
||||
output logic [13:0] gate_o,
|
||||
output logic [13:0] reset_o,
|
||||
output logic [14:0] gate_o,
|
||||
output logic [14:0] reset_o,
|
||||
output logic block_o,
|
||||
output logic done_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] {
|
||||
Idle,
|
||||
@@ -34,20 +34,20 @@ module tiny_peripheral_control (
|
||||
typedef struct packed {
|
||||
state_e state;
|
||||
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 done;
|
||||
logic [4:0] fault;
|
||||
} control_t;
|
||||
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 gate_o = s_ctrl_q.gates;
|
||||
assign reset_o = s_ctrl_q.resets;
|
||||
assign reset_done_o = s_ctrl_q.reset_done;
|
||||
// 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.
|
||||
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 fault_o = s_ctrl_q.fault;
|
||||
always_comb begin
|
||||
@@ -74,8 +74,8 @@ module tiny_peripheral_control (
|
||||
end
|
||||
end
|
||||
Drain: begin
|
||||
if (s_ctrl_q.count >= 16 && ((s_affected[13:1] == 0) || (bus_idle_i && idle_i[9])) &&
|
||||
((idle_i[13:1] & s_affected[13:1]) == s_affected[13:1])) begin
|
||||
if (s_ctrl_q.count >= 16 && ((s_affected[14:1] == 0) || (bus_idle_i && idle_i[9])) &&
|
||||
((idle_i[14:1] & s_affected[14:1]) == s_affected[14:1])) begin
|
||||
s_ctrl_d.count = 0;
|
||||
if (s_ctrl_q.reset_command) begin
|
||||
s_ctrl_d.resets = s_ctrl_q.request_mask;
|
||||
@@ -90,7 +90,7 @@ module tiny_peripheral_control (
|
||||
s_ctrl_d.state = Idle;
|
||||
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:
|
||||
if ((reset_held_i & s_ctrl_q.request_mask) == s_ctrl_q.request_mask) begin
|
||||
s_ctrl_d.resets = 0;
|
||||
|
||||
@@ -7,7 +7,7 @@ module tiny_sysctrl #(
|
||||
parameter bit PllPresent = 1'b0,
|
||||
parameter bit PllProfiles = 1'b0,
|
||||
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 rst_n_i,
|
||||
@@ -37,11 +37,11 @@ module tiny_sysctrl #(
|
||||
output logic [31:0] clock_timeout_o,
|
||||
input logic periph_done_i,
|
||||
input logic [ 4:0] periph_fault_i,
|
||||
input logic [13:0] gate_actual_i,
|
||||
input logic [13:0] reset_done_i,
|
||||
input logic [14:0] gate_actual_i,
|
||||
input logic [14:0] reset_done_i,
|
||||
output logic periph_apply_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,
|
||||
input logic clkout_idle_i,
|
||||
output logic perf_enable_o,
|
||||
@@ -106,7 +106,7 @@ module tiny_sysctrl #(
|
||||
logic [1:0] s_clk_latched_d, s_clk_latched_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 [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 s_clock_apply_d, s_clock_apply_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_writable = 1'b1;
|
||||
end
|
||||
GateStatus: s_rcu_read_data = {18'd0, gate_actual_i};
|
||||
GateStatus: s_rcu_read_data = {17'd0, gate_actual_i};
|
||||
ResetRequest: begin
|
||||
s_rcu_read_data = s_rst_req_q;
|
||||
s_rcu_writable = 1'b1;
|
||||
@@ -334,7 +334,7 @@ module tiny_sysctrl #(
|
||||
MemHz: s_rcu_read_data = s_mem_hz_q;
|
||||
PclkHz: s_rcu_read_data = s_pclk_hz_q;
|
||||
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
|
||||
s_rcu_read_data = s_clkout_control_q;
|
||||
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;
|
||||
TimeoutRefCycles: s_rcu_config_err = s_rcu_busy_q || u_rcu_dst.pwdata == 0;
|
||||
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:
|
||||
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;
|
||||
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;
|
||||
@@ -363,11 +363,11 @@ module tiny_sysctrl #(
|
||||
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 >= 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:
|
||||
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 & {18'd0, gate_actual_i}) != 0;
|
||||
(s_rst_req_q & ~{17'd0, TargetMask[14:1], 1'b0}) != 0 ||
|
||||
(s_rst_req_q & {17'd0, gate_actual_i}) != 0;
|
||||
32'd8: begin
|
||||
end
|
||||
default: s_rcu_config_err = 1'b1;
|
||||
@@ -432,16 +432,16 @@ module tiny_sysctrl #(
|
||||
s_rcu_busy_d = 1'b1;
|
||||
s_active_cmd_d = 4'd8;
|
||||
end else if (TargetMask != 0 && u_rcu_dst.pwdata == 2 &&
|
||||
(s_gate_req_q & ~{18'd0, TargetMask}) == 0) begin
|
||||
s_periph_mask_d = s_gate_req_q[13:0];
|
||||
(s_gate_req_q & ~{17'd0, TargetMask}) == 0) begin
|
||||
s_periph_mask_d = s_gate_req_q[14:0];
|
||||
s_periph_reset_d = 1'b0;
|
||||
s_periph_apply_d = 1'b1;
|
||||
s_rcu_busy_d = 1'b1;
|
||||
s_active_cmd_d = 4'd2;
|
||||
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 & {18'd0, gate_actual_i}) == 0) begin
|
||||
s_periph_mask_d = s_rst_req_q[13:0];
|
||||
(s_rst_req_q & ~{17'd0, TargetMask[14:1], 1'b0}) == 0 &&
|
||||
(s_rst_req_q & {17'd0, gate_actual_i}) == 0) begin
|
||||
s_periph_mask_d = s_rst_req_q[14:0];
|
||||
s_periph_reset_d = 1'b1;
|
||||
s_periph_apply_d = 1'b1;
|
||||
s_reset_done_d = 0;
|
||||
@@ -501,7 +501,7 @@ module tiny_sysctrl #(
|
||||
if (periph_done_i) begin
|
||||
s_rcu_busy_d = 1'b0;
|
||||
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;
|
||||
s_rcu_irq_state_d[0] = 1'b1;
|
||||
end
|
||||
@@ -607,7 +607,7 @@ module tiny_sysctrl #(
|
||||
.dat_o (s_clk_latched_q)
|
||||
);
|
||||
dffr #(
|
||||
.DATA_WIDTH(20)
|
||||
.DATA_WIDTH(21)
|
||||
) u_periph_command (
|
||||
.clk_i (clk_ref_i),
|
||||
.rst_n_i(rst_ref_n_i),
|
||||
|
||||
@@ -140,7 +140,7 @@ def validate(document_path: Path, root: Path) -> None:
|
||||
raise ValueError("unrecognized generated APB crossing contract")
|
||||
expected = {"aon": ["clint", "archinfo"], "memory": ["xpi"],
|
||||
"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
|
||||
generated.get("same_clock_isolation") != ["sdio"] or
|
||||
generated.get("native_system") != ["sysctrl", "sram"] or
|
||||
|
||||
+15
-10
@@ -18,15 +18,15 @@ from scripts.setup_helpers import atomic_write # noqa: E402
|
||||
TINY = ROOT / "rtl/tiny"
|
||||
RCU_TARGETS = {"gpio": 1, "uart0": 2, "i2c0": 3, "timer0": 4, "timer1": 5,
|
||||
"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:
|
||||
document = json.loads(path.read_text())
|
||||
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
|
||||
or document.get("dma_request_mask") != "0x31ff"):
|
||||
raise ValueError("Tiny P6 requires eight DMA channels and request mask 0x31ff")
|
||||
or document.get("dma_request_mask") != "0x39ff"):
|
||||
raise ValueError("Tiny P9 requires eight DMA channels and request mask 0x39ff")
|
||||
if document.get("cpu") != {"module": "hazard3_cpu_2port", "local_ports": 2,
|
||||
"data_outstanding": 1}:
|
||||
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,
|
||||
"sdio_direction_outstanding": 1, "target_combined_outstanding": 1,
|
||||
"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:
|
||||
raise ValueError("Tiny requires a 32-bit IRQ vector and 30 external inputs")
|
||||
names: set[str] = set()
|
||||
@@ -65,10 +65,10 @@ def read_topology(path: Path, memory_map: Path) -> dict:
|
||||
assigned.add(region)
|
||||
if assigned != {r for r in regions if r.startswith("APB4_")}:
|
||||
raise ValueError("Tiny topology must route every APB region")
|
||||
if len(document["apb_targets"]) != 20:
|
||||
raise ValueError("Tiny P6 requires twenty independent APB targets")
|
||||
if len(document["apb_targets"]) != 21:
|
||||
raise ValueError("Tiny P9 requires twenty-one independent 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):
|
||||
raise ValueError("Tiny APB target names must be unique identifiers")
|
||||
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 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_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_UART0 1U\n#define RS_SOC_HAS_UART1 0U\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_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_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_PLL_PRESENT {platform['pll_present']}U\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".held_o(s_reset_held[{target_bit}]));")
|
||||
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":
|
||||
# CDC-ready outputs are observed in SYS, matching APB readiness.
|
||||
extra_ready = " && s_dma_ready_sys && s_dma_idle_sys"
|
||||
|
||||
@@ -35,6 +35,7 @@ TEST_SOURCES = (
|
||||
"crt/src/hal/i2s_math.c",
|
||||
"crt/src/hal/dma_math.c",
|
||||
"crt/src/hal/dma.c",
|
||||
"crt/src/hal/dvp.c",
|
||||
"crt/src/hal/crypto_lifecycle.c",
|
||||
"crt/src/hal/jpeg_math.c",
|
||||
"crt/src/hal/apu.c",
|
||||
@@ -115,6 +116,7 @@ def main() -> int:
|
||||
"-fdata-sections",
|
||||
"-Wl,--gc-sections",
|
||||
"-DRS_DMA_TEST_MMIO",
|
||||
"-DRS_DVP_TEST_MMIO",
|
||||
"-DRS_WS2812_TEST_MMIO",
|
||||
"-DRS_APU_TEST_MMIO",
|
||||
"-DRS_CRYPTO_TEST_MMIO",
|
||||
@@ -154,6 +156,20 @@ def main() -> int:
|
||||
"-o", str(tiny_object),
|
||||
], check=True)
|
||||
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([str(executable)], check=True)
|
||||
return 0
|
||||
|
||||
@@ -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
@@ -16,6 +16,7 @@
|
||||
#include <retrosoc/hal/rcu_regs.h>
|
||||
#include <retrosoc/hal/extension.h>
|
||||
#include <retrosoc/hal/dma.h>
|
||||
#include <retrosoc/hal/dvp.h>
|
||||
#include <retrosoc/hal/fabric_monitor.h>
|
||||
#include <retrosoc/hal/ga2d.h>
|
||||
#include <retrosoc/hal/i2c.h>
|
||||
@@ -42,6 +43,7 @@
|
||||
#include <retrosoc/media/wav_audio.h>
|
||||
|
||||
#include "../../crt/src/hal/ga2d_math.h"
|
||||
#include "../../app/apps/ci_smoke/p9_frame.h"
|
||||
#include "../../crt/src/hal/opipsram_math.c"
|
||||
|
||||
#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.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;
|
||||
}
|
||||
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_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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3235,6 +3451,27 @@ static int test_tiny_rcu_boundaries(void) {
|
||||
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) {
|
||||
const int results[] = {
|
||||
test_string_helpers(),
|
||||
@@ -3262,6 +3499,8 @@ int main(void) {
|
||||
test_gpio_helpers(),
|
||||
test_dma_config_validation(),
|
||||
test_tiny_dma_config_validation(),
|
||||
test_dvp_hal(),
|
||||
test_p9_frame_layout(),
|
||||
test_opipsram_helpers(),
|
||||
test_user_ip_validation(),
|
||||
test_extension_validation(),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -136,7 +136,11 @@ module dvp_tb;
|
||||
check_frame(2, 4, 4);
|
||||
read(32'h0000_0024, value);
|
||||
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);
|
||||
write(32'h0000_0048, 32'h0000_0004);
|
||||
read(32'h0000_003c, value);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -36,7 +36,7 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None:
|
||||
name = target["name"]
|
||||
assert f".presetn(s_{name}_rst_n)" in interfaces
|
||||
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".rst_n_i(s_leaf_rst_n[{index}])" 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 "SOC_ADDR_SRAM_END 32'h3001FFFF" in rtl
|
||||
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
|
||||
irq = (output / "include/retrosoc/generated/irq_metadata.h").read_text()
|
||||
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_I2C1" not 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_NPU" not in irq
|
||||
# A consumer of public SDK headers must see Tiny capabilities without
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -59,9 +59,9 @@ def test_reset_inventory_has_active_domains_and_all_existing_targets() -> None:
|
||||
reset = inventory["reset_distribution"]
|
||||
assert reset["release_edges"] == 5
|
||||
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"]} == {
|
||||
"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 inventory["local_memory"]["clock_domain"] == "system"
|
||||
|
||||
|
||||
@@ -400,11 +400,13 @@ def test_p6_map_irq_request_and_pin_contract():
|
||||
for name, base in {"RNG": 0x20001000, "CRC": 0x20006000, "WS2812": 0x10008000,
|
||||
"CRYPTO": 0x1000C000, "I2S": 0x10007000, "SDIO0": 0x1000F000}.items():
|
||||
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"]}
|
||||
assert {k: irqs[k] for k in ("rng", "ws2812", "crypto", "i2s", "sdio")} == {
|
||||
"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
|
||||
pins = topology["gpio_alt_functions"]
|
||||
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 :
|
||||
RS_DMA_KIND_MM_TO_MM);
|
||||
#ifdef RS_SOC_TINY
|
||||
bool supported=(0x31ffU & (1U << request)) != 0U;
|
||||
bool supported=(0x39ffU & (1U << request)) != 0U;
|
||||
#else
|
||||
bool supported=request < 14U;
|
||||
#endif
|
||||
@@ -540,7 +542,7 @@ int main(void) {
|
||||
def test_p6_reset_and_macro_policy_remains_explicit():
|
||||
text = (TINY / "top/retrosoc_tiny.sv").read_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
|
||||
tree = (TINY / "top/tiny_reset_tree.sv").read_text()
|
||||
assert "s_audio" not in tree and "crypto" not in tree
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user