diff --git a/Makefile b/Makefile index a5a4dec8..c619a1ab 100644 --- a/Makefile +++ b/Makefile @@ -130,6 +130,7 @@ HP_BOOT_BUNDLE_BIN := $(SW_BUILD_DIR)/$(HP_BOOT_BUNDLE_NAME).bin HP_BOOT_BUNDLE_HEX := $(SW_BUILD_DIR)/$(HP_BOOT_BUNDLE_NAME).hex HP_BOOT_BUNDLE_MANIFEST := $(SW_BUILD_DIR)/$(HP_BOOT_BUNDLE_NAME).json HP_LINUX_SIM_TIME ?= 7200 +HP_SMOKE_SIM_TIME ?= 300 HP_SMOKE_BUILD_DIR := $(VARIANT_ROOT)/hp-smoke HP_SMOKE_STAMP := $(HP_SMOKE_BUILD_DIR)/images/.stamp HP_SMOKE_BUNDLE_NAME ?= retrosoc_hp_smoke @@ -395,7 +396,7 @@ help: ' check-clock-reset-domains validate the root clock/reset and CDC inventory' \ ' rtl-lint | check-rtl-lint run/check strict Verilator RTL lint warnings' \ ' formal | formal-bus | formal-rib-adapter | formal-rib2apb run SBY protocol proofs' \ - 'formal-sysctrl | formal-pll-rcu | formal-gpio | formal-ws2812 | formal-uart | formal-i2c | formal-timer | formal-clint | formal-dvp | formal-i2s | formal-onchip-ram | formal-opipsram | formal-dma | formal-apu | formal-gateway-a | formal-sdio run peripheral proofs' \ + 'formal-sysctrl | formal-pll-rcu | formal-gpio | formal-ws2812 | formal-uart | formal-i2c | formal-timer | formal-clint | formal-dvp | formal-i2s | formal-onchip-ram | formal-opipsram | formal-dma | formal-ga2d | formal-apu | formal-gateway-a | formal-sdio run peripheral proofs' \ ' formal-doctor check the SBY, Yosys, sv2v, and Bitwuzla formal toolchain' \ ' benchmark-report run the memory/DMA profile and write meta/performance.json' \ ' coremark-report run the quick CoreMark profile and write meta/coremark.json' \ @@ -583,7 +584,15 @@ hp-smoke-bundle: $(HP_SMOKE_BUNDLE_BIN) $(HP_SMOKE_BUNDLE_HEX) hp-smoke-sim: hp-smoke-bundle comp @test '$(SIMU)' = VERILATOR - $(MAKE) BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) SIM_FIRMWARE_NAME=$(HP_SMOKE_BUNDLE_NAME) sim + $(MAKE) BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) SIM_FIRMWARE_NAME=$(HP_SMOKE_BUNDLE_NAME) \ + SOC_SIM_TIME=$(HP_SMOKE_SIM_TIME) VERILATOR_SIM_ARGS=--fast-flash sim + python3 $(ROOT_PATH)/scripts/check_simulation.py \ + --log $(SIM_BUILD_ROOT)/sim.log \ + --result $(SIM_BUILD_ROOT)/result-hp-smoke-sim-check.json \ + --require 'SIM_TEST_PASS code=0' \ + --require 'HP_LINUX_READY' \ + --require 'HP_GA2D_PASS' \ + --require 'HP_GA2D_CACHE_CLEAN' ifeq ($(HAVE_HP),YES) $(HP_GENERATED_STAMP): $(ROOT_PATH)/scripts/generate_vexiiriscv.py \ diff --git a/README.md b/README.md index 64c6a883..7b3f04e9 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,12 @@ under the [Mulan Permissive Software License, Version 2](LICENSE). [bidirectional PS/2](docs/ip/ps2.md), WS2812, SPI/QSPI, SDIO, PSRAM/[OPI-PSRAM](docs/ip/opipsram.md), SDRAM, DMA, LCD, RTC, an independent-clock window watchdog, CRC, and a - management-only RNG entropy controller. The P3 [GA2D](docs/ip/ga2d.md) APB4 shell and - ownership-routed IRQ are integrated, while its 2D DMA and pixel operations remain unavailable. + management-only RNG entropy controller. The P4 [GA2D](docs/ip/ga2d.md) is an + ownership-routed, direct single-job private-AXI64 2D FILL/COPY DMA engine with + RGB565, RGB888, XRGB8888, and ARGB8888 support, pitch and byte-edge handling, + snapshots, and IRQs at the existing APB4_GA2D window. It does not provide + CONVERT, BLEND, A8, in-place background composition, descriptors/rings/queues, + cache coherency, or a Linux graphics driver. The current deterministic RNG integration source is explicitly unqualified and intended only for diagnostics until a PDK-qualified entropy source is integrated. support. Available interfaces depend on the selected SoC configuration. @@ -100,7 +104,7 @@ used with commercial 32 KiB SRAM and PLL simulation models. CI Verilator firmware simulations explicitly select the `ci_smoke` application, which checks UART, archinfo APB readback, macro-backed on-chip SRAM, -RNG fail-closed behavior, and test-status +the GA2D direct FILL/COPY DMA route, RNG fail-closed behavior, and test-status completion without the verbose startup report. ARCHINFO checks include its ABI and the build/configuration identifiers generated for that variant. The IHP130 PR run links this image into the 32 KiB SRAM, explicitly enables the diff --git a/app/apps/benchmark/app.mk b/app/apps/benchmark/app.mk index bd7f3a43..ffadc886 100644 --- a/app/apps/benchmark/app.mk +++ b/app/apps/benchmark/app.mk @@ -1,2 +1,4 @@ APP_SRCS += $(ROOT_PATH)/app/apps/benchmark/main.c -APP_CFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections \ No newline at end of file +APP_CFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections +SOC_SIM_TIME ?= 600 +VERILATOR_SIM_ARGS ?= --fast-flash \ No newline at end of file diff --git a/app/apps/benchmark/main.c b/app/apps/benchmark/main.c index 6f5fc3bc..df22ef69 100644 --- a/app/apps/benchmark/main.c +++ b/app/apps/benchmark/main.c @@ -3,15 +3,28 @@ #include #include +#include #include #include #include -#define RS_BENCHMARK_WORDS UINT32_C(64) -#define RS_BENCHMARK_SRAM_OFFSET UINT32_C(0x10000) -#define RS_BENCHMARK_SRAM_SEED UINT32_C(0x13579BDF) -#define RS_BENCHMARK_SDRAM_SEED UINT32_C(0x2468ACE0) -#define RS_BENCHMARK_PSRAM_SEED UINT32_C(0x5A5A5A5A) +#define RS_BENCHMARK_WORDS UINT32_C(64) +/* Leave nearly 8 KiB of SRAM headroom above this buffer for the stack. */ +#define RS_BENCHMARK_SRAM_OFFSET UINT32_C(0x6000) +#define RS_BENCHMARK_SRAM_SEED UINT32_C(0x13579BDF) +#define RS_BENCHMARK_SDRAM_SEED UINT32_C(0x2468ACE0) +#define RS_BENCHMARK_PSRAM_SEED UINT32_C(0x5A5A5A5A) +#define RS_BENCHMARK_GA2D_WIDTH UINT16_C(8) +#define RS_BENCHMARK_GA2D_HEIGHT UINT16_C(4) +#define RS_BENCHMARK_GA2D_PITCH UINT32_C(48) +#define RS_BENCHMARK_GA2D_ROW_BYTES UINT32_C(32) +#define RS_BENCHMARK_GA2D_PIXEL_COUNT UINT32_C(32) +#define RS_BENCHMARK_GA2D_FILL_OFFSET UINT32_C(0x00001000) +#define RS_BENCHMARK_GA2D_SRC_OFFSET UINT32_C(0x00002000) +#define RS_BENCHMARK_GA2D_DST_OFFSET UINT32_C(0x00003000) +#define RS_BENCHMARK_GA2D_FILL_COLOR UINT32_C(0x80123456) + +static rs_ga2d_capability_t s_ga2d_capability; static void rs_benchmark_puts(const char *text) { while (*text != '\0') { @@ -103,23 +116,48 @@ static uint32_t rs_benchmark_read(volatile const uint32_t *memory) { return checksum; } -static void rs_benchmark_report(const char *region, const char *operation, uint32_t checksum, - uint64_t cycles, const rs_perf_snapshot_t *snapshot) { +static void rs_benchmark_report(const char *region, const char *operation, uint32_t words, + uint32_t checksum, uint64_t cpu_cycles, uint64_t workload_bytes, + uint32_t pixels, uint32_t jobs, const rs_perf_snapshot_t *snapshot, + const rs_ga2d_stats_t *ga2d_stats) { + uint64_t ga2d_cycles = 0U; + uint64_t ga2d_read_bytes = 0U; + uint64_t ga2d_write_bytes = 0U; + + if (ga2d_stats != NULL) { + ga2d_cycles = ga2d_stats->cycles; + ga2d_read_bytes = ga2d_stats->read_bytes; + ga2d_write_bytes = ga2d_stats->write_bytes; + } rs_benchmark_puts("PERF region="); rs_benchmark_puts(region); rs_benchmark_puts(" op="); rs_benchmark_puts(operation); rs_benchmark_puts(" words="); - rs_benchmark_put_dec(RS_BENCHMARK_WORDS); + rs_benchmark_put_dec(words); rs_benchmark_puts(" checksum="); rs_benchmark_put_hex(checksum); - rs_benchmark_put_counter(" cycles=", cycles); + rs_benchmark_put_counter(" cycles=", cpu_cycles); rs_benchmark_put_counter(" mgmt_wait=", snapshot->mgmt_wait); rs_benchmark_put_counter(" apb4_periph_wait=", snapshot->apb4_periph_wait); rs_benchmark_put_counter(" sdram_wait=", snapshot->sdram_wait); rs_benchmark_put_counter(" psram_wait=", snapshot->psram_wait); rs_benchmark_put_counter(" flash_wait=", snapshot->flash_wait); rs_benchmark_put_counter(" dma_wait=", snapshot->dma_wait); + rs_benchmark_put_counter(" workload_bytes=", workload_bytes); + rs_benchmark_put_counter(" pixels=", pixels); + rs_benchmark_put_counter(" jobs=", jobs); + rs_benchmark_put_counter(" cpu_cycles=", cpu_cycles); + rs_benchmark_put_counter(" cpu_hz=", RS_CPU_CLOCK_HZ); + rs_benchmark_puts(" ga2d_features=0x"); + rs_benchmark_put_hex(s_ga2d_capability.features); + rs_benchmark_puts(" ga2d_limits=0x"); + rs_benchmark_put_hex(s_ga2d_capability.limits); + rs_benchmark_puts(" ga2d_formats=0x"); + rs_benchmark_put_hex(s_ga2d_capability.formats); + rs_benchmark_put_counter(" ga2d_cycles=", ga2d_cycles); + rs_benchmark_put_counter(" ga2d_read_bytes=", ga2d_read_bytes); + rs_benchmark_put_counter(" ga2d_write_bytes=", ga2d_write_bytes); putch('\n'); } @@ -157,7 +195,8 @@ static bool rs_benchmark_memory(const char *region, volatile uint32_t *memory, u rs_benchmark_report_snapshot_failure(region, "write"); return false; } - rs_benchmark_report(region, "write", expected_checksum, cycles, &snapshot); + rs_benchmark_report(region, "write", RS_BENCHMARK_WORDS, expected_checksum, cycles, + RS_BENCHMARK_WORDS * sizeof(uint32_t), 0U, 0U, &snapshot, NULL); (void)rs_perf_start(); cycle_start = rs_benchmark_read_cycle_counter(); @@ -167,7 +206,8 @@ static bool rs_benchmark_memory(const char *region, volatile uint32_t *memory, u rs_benchmark_report_snapshot_failure(region, "read"); return false; } - rs_benchmark_report(region, "read", actual_checksum, cycles, &snapshot); + rs_benchmark_report(region, "read", RS_BENCHMARK_WORDS, actual_checksum, cycles, + RS_BENCHMARK_WORDS * sizeof(uint32_t), 0U, 0U, &snapshot, NULL); if (actual_checksum != expected_checksum) { rs_benchmark_report_data_failure(region, expected_checksum, actual_checksum); return false; @@ -222,7 +262,9 @@ static bool rs_benchmark_dma(volatile uint32_t *source, volatile uint32_t *desti return false; } checksum = rs_benchmark_read(destination); - rs_benchmark_report("dma", "copy", checksum, cycles, &snapshot); + rs_benchmark_report("dma", "copy", RS_BENCHMARK_WORDS, checksum, cycles, + UINT64_C(2) * RS_BENCHMARK_WORDS * sizeof(uint32_t), 0U, 1U, &snapshot, + NULL); if (checksum != expected_checksum) { rs_benchmark_report_data_failure("dma", expected_checksum, checksum); return false; @@ -230,12 +272,159 @@ static bool rs_benchmark_dma(volatile uint32_t *source, volatile uint32_t *desti return true; } +static uint32_t rs_benchmark_bytes_checksum(volatile const uint8_t *memory, uint32_t bytes) { + uint32_t checksum = 0U; + + for (uint32_t index = 0U; index < bytes; ++index) { + checksum = (checksum << 5U) ^ (checksum >> 2U) ^ (uint32_t)memory[index]; + } + return checksum; +} + +static bool rs_benchmark_ga2d_fill(volatile uint8_t *destination) { + const rs_ga2d_job_t job = { + .operation = RS_GA2D_OP_FILL, + .foreground = {0U, 0U, RS_GA2D_FORMAT_A8}, + .background = {0U, 0U, RS_GA2D_FORMAT_A8}, + .destination = {(uintptr_t)destination, RS_BENCHMARK_GA2D_PITCH, RS_GA2D_FORMAT_XRGB8888}, + .width = RS_BENCHMARK_GA2D_WIDTH, + .height = RS_BENCHMARK_GA2D_HEIGHT, + .global_alpha = UINT8_C(0), + .color = RS_BENCHMARK_GA2D_FILL_COLOR, + }; + rs_ga2d_stats_t ga2d_stats; + rs_perf_snapshot_t snapshot; + uint64_t cycle_start; + uint64_t cpu_cycles; + uint32_t checksum; + + for (uint32_t index = 0U; index < (RS_BENCHMARK_GA2D_HEIGHT * RS_BENCHMARK_GA2D_PITCH); + ++index) { + destination[index] = UINT8_C(0xD3); + } + (void)rs_perf_start(); + cycle_start = rs_benchmark_read_cycle_counter(); + if ((rs_ga2d_configure(&job) != RS_OK) || (rs_ga2d_start() != RS_OK) || + (rs_ga2d_wait(RS_TIMEOUT_DEFAULT) != RS_OK)) { + rs_benchmark_puts("PERF_FAIL region=ga2d op=fill reason=job\n"); + return false; + } + cpu_cycles = rs_benchmark_read_cycle_counter() - cycle_start; + if ((rs_ga2d_get_stats(&ga2d_stats) != RS_OK) || (rs_perf_snapshot(&snapshot) != RS_OK)) { + rs_benchmark_report_snapshot_failure("ga2d", "fill"); + return false; + } + for (uint32_t row = 0U; row < RS_BENCHMARK_GA2D_HEIGHT; ++row) { + for (uint32_t pixel = 0U; pixel < RS_BENCHMARK_GA2D_WIDTH; ++pixel) { + const uint32_t pixel_offset = (row * RS_BENCHMARK_GA2D_PITCH) + (pixel * UINT32_C(4)); + + if ((destination[pixel_offset] != UINT8_C(0x56)) || + (destination[pixel_offset + 1U] != UINT8_C(0x34)) || + (destination[pixel_offset + 2U] != UINT8_C(0x12)) || + (destination[pixel_offset + 3U] != UINT8_C(0xFF))) { + rs_benchmark_puts("PERF_FAIL region=ga2d op=fill reason=data\n"); + return false; + } + } + for (uint32_t byte = RS_BENCHMARK_GA2D_ROW_BYTES; byte < RS_BENCHMARK_GA2D_PITCH; ++byte) { + if (destination[(row * RS_BENCHMARK_GA2D_PITCH) + byte] != UINT8_C(0xD3)) { + rs_benchmark_puts("PERF_FAIL region=ga2d op=fill reason=guard\n"); + return false; + } + } + } + if ((ga2d_stats.lines_done != RS_BENCHMARK_GA2D_HEIGHT) || (ga2d_stats.read_bytes != 0U) || + (ga2d_stats.write_bytes != + (uint64_t)RS_BENCHMARK_GA2D_HEIGHT * RS_BENCHMARK_GA2D_ROW_BYTES)) { + rs_benchmark_puts("PERF_FAIL region=ga2d op=fill reason=stats\n"); + return false; + } + checksum = rs_benchmark_bytes_checksum(destination, + RS_BENCHMARK_GA2D_HEIGHT * RS_BENCHMARK_GA2D_PITCH); + rs_benchmark_report("ga2d", "fill", 0U, checksum, cpu_cycles, + (uint64_t)RS_BENCHMARK_GA2D_HEIGHT * RS_BENCHMARK_GA2D_ROW_BYTES, + RS_BENCHMARK_GA2D_PIXEL_COUNT, 1U, &snapshot, &ga2d_stats); + return true; +} + +static bool rs_benchmark_ga2d_copy(volatile uint8_t *source, volatile uint8_t *destination) { + const rs_ga2d_job_t job = { + .operation = RS_GA2D_OP_COPY, + .foreground = {(uintptr_t)source, RS_BENCHMARK_GA2D_PITCH, RS_GA2D_FORMAT_XRGB8888}, + .background = {0U, 0U, RS_GA2D_FORMAT_A8}, + .destination = {(uintptr_t)destination, RS_BENCHMARK_GA2D_PITCH, RS_GA2D_FORMAT_XRGB8888}, + .width = RS_BENCHMARK_GA2D_WIDTH, + .height = RS_BENCHMARK_GA2D_HEIGHT, + .global_alpha = UINT8_C(0), + .color = UINT32_C(0), + }; + rs_ga2d_stats_t ga2d_stats; + rs_perf_snapshot_t snapshot; + uint64_t cycle_start; + uint64_t cpu_cycles; + uint32_t checksum; + + for (uint32_t index = 0U; index < (RS_BENCHMARK_GA2D_HEIGHT * RS_BENCHMARK_GA2D_PITCH); + ++index) { + source[index] = (uint8_t)(index ^ UINT32_C(0x5A)); + destination[index] = UINT8_C(0xC7); + } + (void)rs_perf_start(); + cycle_start = rs_benchmark_read_cycle_counter(); + if ((rs_ga2d_configure(&job) != RS_OK) || (rs_ga2d_start() != RS_OK) || + (rs_ga2d_wait(RS_TIMEOUT_DEFAULT) != RS_OK)) { + rs_benchmark_puts("PERF_FAIL region=ga2d op=copy reason=job\n"); + return false; + } + cpu_cycles = rs_benchmark_read_cycle_counter() - cycle_start; + if ((rs_ga2d_get_stats(&ga2d_stats) != RS_OK) || (rs_perf_snapshot(&snapshot) != RS_OK)) { + rs_benchmark_report_snapshot_failure("ga2d", "copy"); + return false; + } + for (uint32_t row = 0U; row < RS_BENCHMARK_GA2D_HEIGHT; ++row) { + const uint32_t row_offset = row * RS_BENCHMARK_GA2D_PITCH; + + for (uint32_t byte = 0U; byte < RS_BENCHMARK_GA2D_ROW_BYTES; ++byte) { + if (destination[row_offset + byte] != source[row_offset + byte]) { + rs_benchmark_puts("PERF_FAIL region=ga2d op=copy reason=data\n"); + return false; + } + } + for (uint32_t byte = RS_BENCHMARK_GA2D_ROW_BYTES; byte < RS_BENCHMARK_GA2D_PITCH; ++byte) { + if (destination[row_offset + byte] != UINT8_C(0xC7)) { + rs_benchmark_puts("PERF_FAIL region=ga2d op=copy reason=guard\n"); + return false; + } + } + } + if ((ga2d_stats.lines_done != RS_BENCHMARK_GA2D_HEIGHT) || + (ga2d_stats.read_bytes != + (uint64_t)RS_BENCHMARK_GA2D_HEIGHT * RS_BENCHMARK_GA2D_ROW_BYTES) || + (ga2d_stats.write_bytes != + (uint64_t)RS_BENCHMARK_GA2D_HEIGHT * RS_BENCHMARK_GA2D_ROW_BYTES)) { + rs_benchmark_puts("PERF_FAIL region=ga2d op=copy reason=stats\n"); + return false; + } + checksum = rs_benchmark_bytes_checksum(destination, + RS_BENCHMARK_GA2D_HEIGHT * RS_BENCHMARK_GA2D_PITCH); + rs_benchmark_report("ga2d", "copy", 0U, checksum, cpu_cycles, + UINT64_C(2) * RS_BENCHMARK_GA2D_HEIGHT * RS_BENCHMARK_GA2D_ROW_BYTES, + RS_BENCHMARK_GA2D_PIXEL_COUNT, 1U, &snapshot, &ga2d_stats); + return true; +} + int main(void) { volatile uint32_t *const sram = (volatile uint32_t *)(uintptr_t)(RS_SOC_SRAM_BASE + RS_BENCHMARK_SRAM_OFFSET); volatile uint32_t *const sdram = (volatile uint32_t *)(uintptr_t)RS_SOC_SDRAM_BASE; volatile uint32_t *const psram = (volatile uint32_t *)(uintptr_t)RS_SOC_PSRAM_BASE; volatile uint32_t *const flash = (volatile uint32_t *)(uintptr_t)(RS_SOC_FLASH_BASE + 0x10000U); + volatile uint8_t *const ga2d_fill = + (volatile uint8_t *)(uintptr_t)(RS_SOC_SDRAM_BASE + RS_BENCHMARK_GA2D_FILL_OFFSET); + volatile uint8_t *const ga2d_source = + (volatile uint8_t *)(uintptr_t)(RS_SOC_SDRAM_BASE + RS_BENCHMARK_GA2D_SRC_OFFSET); + volatile uint8_t *const ga2d_destination = + (volatile uint8_t *)(uintptr_t)(RS_SOC_SDRAM_BASE + RS_BENCHMARK_GA2D_DST_OFFSET); uint32_t checksum; uint64_t cycle_start; uint64_t cycles; @@ -244,6 +433,12 @@ int main(void) { rs_test_finish(RS_TEST_FAILED, 5U); } rs_benchmark_puts("retroSoC: A Customized ASIC for Retro Stuff\n"); + if ((rs_ga2d_get_capability(&s_ga2d_capability) != RS_OK) || + (s_ga2d_capability.features != RS_GA2D_CAPABILITY_P4) || + (s_ga2d_capability.limits != RS_GA2D_LIMITS_P4) || + (s_ga2d_capability.formats != RS_GA2D_FORMAT_CAPABILITY_P4)) { + rs_test_finish(RS_TEST_FAILED, 6U); + } if (!rs_benchmark_memory("sram", sram, RS_BENCHMARK_SRAM_SEED)) { rs_test_finish(RS_TEST_FAILED, 1U); @@ -266,12 +461,19 @@ int main(void) { rs_benchmark_report_snapshot_failure("flash", "read"); rs_test_finish(RS_TEST_FAILED, 4U); } - rs_benchmark_report("flash", "read", checksum, cycles, &snapshot); + rs_benchmark_report("flash", "read", RS_BENCHMARK_WORDS, checksum, cycles, + RS_BENCHMARK_WORDS * sizeof(uint32_t), 0U, 0U, &snapshot, NULL); } if (!rs_benchmark_dma(psram, sdram + RS_BENCHMARK_WORDS, rs_benchmark_read(psram))) { rs_test_finish(RS_TEST_FAILED, 5U); } + if (!rs_benchmark_ga2d_fill(ga2d_fill)) { + rs_test_finish(RS_TEST_FAILED, 7U); + } + if (!rs_benchmark_ga2d_copy(ga2d_source, ga2d_destination)) { + rs_test_finish(RS_TEST_FAILED, 8U); + } (void)rs_perf_stop(); rs_benchmark_puts("PERF_BENCHMARK_PASS\n"); rs_test_finish(RS_TEST_PASSED, 0U); diff --git a/app/apps/ci_smoke/main.c b/app/apps/ci_smoke/main.c index bf4ca74a..605a7f92 100644 --- a/app/apps/ci_smoke/main.c +++ b/app/apps/ci_smoke/main.c @@ -31,12 +31,17 @@ static bool rs_ci_smoke_archinfo_v2(void) { } #ifdef CSR_ENABLE +#define RS_CI_SMOKE_GA2D_BUFFER_BYTES UINT32_C(62) + static volatile uint32_t rs_ci_smoke_external_irq_count; static volatile uint32_t rs_ci_smoke_external_irq_sequence[4]; static volatile uint32_t rs_ci_smoke_external_irq_sequence_count; static volatile uint32_t rs_ci_smoke_timer_irq_count; static volatile uint32_t rs_ci_smoke_software_irq_count; static volatile uint32_t rs_ci_smoke_ga2d_irq_count; +static volatile uint8_t rs_ci_smoke_ga2d_fill_buffer[RS_CI_SMOKE_GA2D_BUFFER_BYTES]; +static volatile uint8_t rs_ci_smoke_ga2d_copy_source_buffer[RS_CI_SMOKE_GA2D_BUFFER_BYTES]; +static volatile uint8_t rs_ci_smoke_ga2d_copy_destination_buffer[RS_CI_SMOKE_GA2D_BUFFER_BYTES]; static void rs_ci_smoke_external_irq_handler(uintptr_t mcause, uintptr_t stack_pointer) { const uint32_t context = (uint32_t)__RV_CSR_READ(CSR_HAZARD3_MEICONTEXT); @@ -72,7 +77,7 @@ static void rs_ci_smoke_software_irq_handler(uintptr_t mcause, uintptr_t stack_p static void rs_ci_smoke_ga2d_irq_handler(uintptr_t mcause, uintptr_t stack_pointer) { (void)mcause; (void)stack_pointer; - RS_GA2D_REG(RS_GA2D_REG_IRQ_STATE) = RS_GA2D_IRQ_DONE; + RS_GA2D_REG(RS_GA2D_REG_IRQ_STATE) = RS_GA2D_IRQ_ALL; ++rs_ci_smoke_ga2d_irq_count; } @@ -105,6 +110,13 @@ static bool rs_ci_smoke_wait_external_count(uint32_t target) { return rs_ci_smoke_external_irq_count >= target; } +static bool rs_ci_smoke_wait_ga2d_irq(uint32_t target) { + for (rs_timeout_t timeout = RS_TIMEOUT_DEFAULT; + (timeout != 0U) && (rs_ci_smoke_ga2d_irq_count < target); --timeout) { + } + return rs_ci_smoke_ga2d_irq_count >= target; +} + static bool rs_ci_smoke_external_single(uint32_t id) { rs_ci_smoke_external_irq_count = 0U; rs_ci_smoke_external_irq_sequence_count = 0U; @@ -268,12 +280,131 @@ static bool rs_ci_smoke_external_irq(void) { return (rs_ci_smoke_timer_irq_count != 0U) && (rs_ci_smoke_software_irq_count != 0U); } +static bool rs_ci_smoke_ga2d_fill_copy(void) { + const rs_ga2d_job_t fill = { + .operation = RS_GA2D_OP_FILL, + .foreground = {0U, 0U, RS_GA2D_FORMAT_A8}, + .background = {0U, 0U, RS_GA2D_FORMAT_A8}, + .destination = {(uintptr_t)&rs_ci_smoke_ga2d_fill_buffer[1U], 20U, RS_GA2D_FORMAT_RGB888}, + .width = 5U, + .height = 3U, + .global_alpha = UINT8_C(0), + .color = UINT32_C(0x80123456), + }; + const rs_ga2d_job_t copy = { + .operation = RS_GA2D_OP_COPY, + .foreground = {(uintptr_t)&rs_ci_smoke_ga2d_copy_source_buffer[1U], 20U, + RS_GA2D_FORMAT_RGB888}, + .background = {0U, 0U, RS_GA2D_FORMAT_A8}, + .destination = {(uintptr_t)&rs_ci_smoke_ga2d_copy_destination_buffer[1U], 20U, + RS_GA2D_FORMAT_RGB888}, + .width = 5U, + .height = 3U, + .global_alpha = UINT8_C(0), + .color = UINT32_C(0), + }; + const uint32_t row_bytes = UINT32_C(15); + const uint32_t span = UINT32_C(60); + volatile uint8_t *const fill_guard = rs_ci_smoke_ga2d_fill_buffer; + volatile uint8_t *const fill_destination = fill_guard + 1U; + volatile uint8_t *const copy_source_guard = rs_ci_smoke_ga2d_copy_source_buffer; + volatile uint8_t *const copy_source = copy_source_guard + 1U; + volatile uint8_t *const copy_destination_guard = rs_ci_smoke_ga2d_copy_destination_buffer; + volatile uint8_t *const copy_destination = copy_destination_guard + 1U; + rs_ga2d_error_t error; + rs_ga2d_stats_t stats; + rs_ga2d_status_t status; + uint32_t expected_irq; + + fill_guard[0] = UINT8_C(0xD3); + fill_guard[span + 1U] = UINT8_C(0xD3); + for (uint32_t index = 0U; index < span; ++index) { + fill_destination[index] = UINT8_C(0xD3); + } + expected_irq = rs_ci_smoke_ga2d_irq_count + 1U; + if (rs_ga2d_configure(&fill) != RS_OK) { + return false; + } + if ((rs_ga2d_start() != RS_OK) || !rs_ci_smoke_wait_ga2d_irq(expected_irq) || + (rs_ga2d_wait(RS_TIMEOUT_DEFAULT) != RS_OK) || (rs_ga2d_get_status(&status) != RS_OK) || + !status.done || status.busy || status.error || (rs_ga2d_get_error(&error) != RS_OK) || + error.valid || (rs_ga2d_get_stats(&stats) != RS_OK) || (stats.lines_done != fill.height) || + (stats.read_bytes != 0U) || (stats.write_bytes != UINT64_C(45))) { + return false; + } + if ((fill_guard[0] != UINT8_C(0xD3)) || (fill_guard[span + 1U] != UINT8_C(0xD3))) { + return false; + } + for (uint32_t row = 0U; row < fill.height; ++row) { + const uint32_t offset = row * fill.destination.pitch; + + for (uint32_t byte = 0U; byte < row_bytes; ++byte) { + const uint8_t expected = (byte % 3U == 0U) ? UINT8_C(0x12) + : (byte % 3U == 1U) ? UINT8_C(0x34) + : UINT8_C(0x56); + + if (fill_destination[offset + byte] != expected) { + return false; + } + } + for (uint32_t byte = row_bytes; byte < fill.destination.pitch; ++byte) { + if (fill_destination[offset + byte] != UINT8_C(0xD3)) { + return false; + } + } + } + + copy_source_guard[0] = UINT8_C(0xA5); + copy_source_guard[span + 1U] = UINT8_C(0xA5); + copy_destination_guard[0] = UINT8_C(0xC7); + copy_destination_guard[span + 1U] = UINT8_C(0xC7); + for (uint32_t index = 0U; index < span; ++index) { + copy_source[index] = (uint8_t)(index ^ UINT32_C(0x5A)); + copy_destination[index] = UINT8_C(0xC7); + } + expected_irq = rs_ci_smoke_ga2d_irq_count + 1U; + if (rs_ga2d_configure(©) != RS_OK) { + return false; + } + if ((rs_ga2d_start() != RS_OK) || !rs_ci_smoke_wait_ga2d_irq(expected_irq) || + (rs_ga2d_wait(RS_TIMEOUT_DEFAULT) != RS_OK) || (rs_ga2d_get_status(&status) != RS_OK) || + !status.done || status.busy || status.error || (rs_ga2d_get_error(&error) != RS_OK) || + error.valid || (rs_ga2d_get_stats(&stats) != RS_OK) || (stats.lines_done != copy.height) || + (stats.read_bytes != UINT64_C(45)) || (stats.write_bytes != UINT64_C(45))) { + return false; + } + if ((copy_source_guard[0] != UINT8_C(0xA5)) || + (copy_source_guard[span + 1U] != UINT8_C(0xA5)) || + (copy_destination_guard[0] != UINT8_C(0xC7)) || + (copy_destination_guard[span + 1U] != UINT8_C(0xC7))) { + return false; + } + for (uint32_t row = 0U; row < copy.height; ++row) { + const uint32_t offset = row * copy.destination.pitch; + + for (uint32_t byte = 0U; byte < row_bytes; ++byte) { + if (copy_destination[offset + byte] != copy_source[offset + byte]) { + return false; + } + } + for (uint32_t byte = row_bytes; byte < copy.destination.pitch; ++byte) { + if ((copy_source[offset + byte] != (uint8_t)((offset + byte) ^ UINT32_C(0x5A))) || + (copy_destination[offset + byte] != UINT8_C(0xC7))) { + return false; + } + } + } + return true; +} + static bool rs_ci_smoke_ga2d_irq(void) { rs_ga2d_capability_t capability; rs_ci_smoke_ga2d_irq_count = 0U; if ((rs_ga2d_get_capability(&capability) != RS_OK) || - (capability.features != RS_GA2D_CAPABILITY_P3) || + (capability.features != RS_GA2D_CAPABILITY_P4) || + (capability.limits != RS_GA2D_LIMITS_P4) || + (capability.formats != RS_GA2D_FORMAT_CAPABILITY_P4) || (rs_ga2d_irq_clear(RS_GA2D_IRQ_ALL) != RS_OK) || (rs_ga2d_irq_enable(RS_GA2D_IRQ_DONE) != RS_OK) || (rs_irq_enable_external(RS_SOC_EXT_IRQ_GA2D, rs_ci_smoke_ga2d_irq_handler) != RS_OK)) { @@ -281,10 +412,7 @@ static bool rs_ci_smoke_ga2d_irq(void) { } RS_GA2D_REG(RS_GA2D_REG_IRQ_TEST) = RS_GA2D_IRQ_DONE; __enable_irq(); - for (rs_timeout_t timeout = 100000U; (timeout != 0U) && (rs_ci_smoke_ga2d_irq_count < 1U); - --timeout) { - } - if (rs_ci_smoke_ga2d_irq_count != 1U) { + if (!rs_ci_smoke_wait_ga2d_irq(1U) || !rs_ci_smoke_ga2d_fill_copy()) { __disable_irq(); (void)rs_irq_disable_external(RS_SOC_EXT_IRQ_GA2D); __disable_ext_irq(); @@ -370,6 +498,7 @@ static bool rs_ci_smoke_fabric_monitor_check(void) { (RS_SOC_HAS_SRAM != 0U) ? RS_FABRIC_TARGET_SRAM : RS_FABRIC_TARGET_SDRAM; rs_fabric_monitor_status_t status; rs_fabric_master_stats_t master; + rs_fabric_master_stats_t ga2d_master; rs_fabric_target_stats_t target; rs_fabric_fault_t fault; uint32_t flush_count; @@ -377,6 +506,7 @@ static bool rs_ci_smoke_fabric_monitor_check(void) { if ((rs_fabric_monitor_snapshot() != RS_OK) || (rs_fabric_monitor_get_status(&status) != RS_OK) || (rs_fabric_monitor_read_master(RS_FABRIC_MASTER_LP, &master) != RS_OK) || + (rs_fabric_monitor_read_master(RS_FABRIC_MASTER_GA2D, &ga2d_master) != RS_OK) || (rs_fabric_monitor_read_target(active_memory_target, &target) != RS_OK) || (rs_fabric_monitor_read_fault(&fault) != RS_OK) || (rs_fabric_monitor_get_flush_count(&flush_count) != RS_OK)) { @@ -385,6 +515,8 @@ static bool rs_ci_smoke_fabric_monitor_check(void) { return !status.recovery && !status.flush_busy && !fault.valid && (flush_count == 0U) && (master.read_requests != 0U) && (master.write_requests != 0U) && (master.read_beats != 0U) && (master.write_beats != 0U) && + (ga2d_master.read_requests != 0U) && (ga2d_master.write_requests != 0U) && + (ga2d_master.read_beats != 0U) && (ga2d_master.write_beats != 0U) && (target.read_requests != 0U) && (target.write_requests != 0U) && (target.read_beats != 0U) && (target.write_beats != 0U); } @@ -672,17 +804,17 @@ int main(void) { rs_test_finish(RS_TEST_FAILED, 15U); } printf("ci_smoke: external irq passed\n"); + if (!rs_ci_smoke_fabric_monitor_start()) { + rs_test_finish(RS_TEST_FAILED, 13U); + } if (!rs_ci_smoke_ga2d_irq()) { rs_test_finish(RS_TEST_FAILED, 15U); } - printf("ci_smoke: GA2D shell IRQ passed\n"); + printf("ci_smoke: GA2D FILL/COPY IRQ passed\n"); if (!rs_ci_smoke_apu()) { rs_test_finish(RS_TEST_FAILED, 14U); } printf("ci_smoke: APU-P5 discovery passed\n"); - if (!rs_ci_smoke_fabric_monitor_start()) { - rs_test_finish(RS_TEST_FAILED, 13U); - } if (!rs_ci_smoke_onchip_sram() || !rs_ci_smoke_onchip_sram_access()) { rs_test_finish(RS_TEST_FAILED, 12U); } diff --git a/app/apps/hp_boot/main.c b/app/apps/hp_boot/main.c index dc0c2dd9..71ad787d 100644 --- a/app/apps/hp_boot/main.c +++ b/app/apps/hp_boot/main.c @@ -3,8 +3,10 @@ #include #include -#include #include +#include +#include +#include #include #include #include @@ -13,13 +15,25 @@ #include "hp_boot_bundle.h" -#define RS_HP_BOOT_HEADER_SIZE UINT32_C(128) -#define RS_HP_BOOT_READY_EVENT UINT32_C(1) -#define RS_HP_BOOT_READY_ARG UINT32_C(0x4C4E5801) -#define RS_HP_BOOT_READBACK_STRIDE_BYTES UINT32_C(1024) -#define RS_HP_BOOT_DMA_TIMEOUT (RS_TIMEOUT_DEFAULT * UINT32_C(64)) +#define RS_HP_BOOT_HEADER_SIZE UINT32_C(128) +#define RS_HP_BOOT_READY_EVENT UINT32_C(1) +#define RS_HP_BOOT_READY_ARG UINT32_C(0x4C4E5801) +#define RS_HP_BOOT_GA2D_START_COMMAND UINT32_C(0x47413250) +#define RS_HP_BOOT_GA2D_START_ARG UINT32_C(0x47413244) +#define RS_HP_BOOT_GA2D_PASS_EVENT UINT32_C(2) +#define RS_HP_BOOT_GA2D_FAIL_EVENT UINT32_C(3) +#define RS_HP_BOOT_GA2D_CACHE_EVENT UINT32_C(4) +#define RS_HP_BOOT_GA2D_PASS_ARG UINT32_C(0x47413244) +#define RS_HP_BOOT_GA2D_CACHE_ARG UINT32_C(0x43424F4B) +#define RS_HP_BOOT_MAILBOX_READY_SEQUENCE UINT32_C(1) +#define RS_HP_BOOT_MAILBOX_RESULT_SEQUENCE UINT32_C(2) +#define RS_HP_BOOT_MAILBOX_CACHE_SEQUENCE UINT32_C(3) +#define RS_HP_BOOT_READBACK_STRIDE_BYTES UINT32_C(1024) +#define RS_HP_BOOT_DMA_TIMEOUT (RS_TIMEOUT_DEFAULT * UINT32_C(64)) +#define RS_HP_BOOT_EVENT_TIMEOUT (RS_TIMEOUT_DEFAULT * UINT32_C(64)) static rs_dma_tcd_t s_hp_boot_tcd __attribute__((aligned(64))); +static bool s_hp_boot_ga2d_owned_by_hp; _Static_assert(sizeof(rs_hp_boot_header_t) == RS_HP_BOOT_HEADER_SIZE, "HP boot bundle header ABI mismatch"); @@ -189,16 +203,88 @@ static bool rs_hp_boot_wait_sdram(void) { return false; } +static bool rs_hp_boot_ga2d_idle(rs_resource_owner_t expected_owner) { + rs_ga2d_capability_t capability; + rs_ga2d_status_t ga2d_status; + rs_resource_status_t resource_status; + + return (rs_ga2d_get_capability(&capability) == RS_OK) && + (capability.features == RS_GA2D_CAPABILITY_P4) && + (capability.limits == RS_GA2D_LIMITS_P4) && + (capability.formats == RS_GA2D_FORMAT_CAPABILITY_P4) && + (rs_ga2d_get_status(&ga2d_status) == RS_OK) && !ga2d_status.busy && + !ga2d_status.draining && !ga2d_status.error && !ga2d_status.aborted && + !ga2d_status.recovery_required && ga2d_status.data_ready && + (rs_resource_get_status(RS_RESOURCE_GA2D, &resource_status) == RS_OK) && + (resource_status.owner == expected_owner) && resource_status.idle && + !resource_status.fault && !resource_status.blocked && !resource_status.quiesced && + !resource_status.in_reset; +} + +static bool rs_hp_boot_wait_ga2d_idle(rs_resource_owner_t expected_owner) { + for (uint32_t timeout = 0U; timeout < RS_HP_BOOT_EVENT_TIMEOUT; ++timeout) { + if (rs_hp_boot_ga2d_idle(expected_owner)) { + return true; + } + } + return false; +} + +static bool rs_hp_boot_wait_message(uint32_t code, uint32_t argument, uint32_t sequence) { + rs_hp_mailbox_message_t message; + + for (uint32_t timeout = 0U; timeout < RS_HP_BOOT_EVENT_TIMEOUT; ++timeout) { + if (rs_hp_mailbox_receive_from_hp(&message) != RS_OK) { + return false; + } + if (message.sequence == sequence) { + return (message.code == code) && (message.argument == argument); + } + } + return false; +} + +static bool rs_hp_boot_wait_cache_request(void) { + rs_resource_cache_status_t cache_status; + + for (uint32_t timeout = 0U; timeout < RS_HP_BOOT_EVENT_TIMEOUT; ++timeout) { + if (rs_resource_get_cache_status(&cache_status) != RS_OK) { + return false; + } + if (cache_status.request && !cache_status.clean) { + return true; + } + } + return false; +} + +static bool rs_hp_boot_wait_hp_held(void) { + rs_sysctrl_hp_status_t hp_status; + + for (uint32_t timeout = 0U; timeout < RS_HP_BOOT_EVENT_TIMEOUT; ++timeout) { + if ((rs_sysctrl_get_hp_status(&hp_status) == RS_OK) && hp_status.reset_asserted && + !hp_status.released && !hp_status.draining && !hp_status.forced_fault && + rs_hp_boot_ga2d_idle(RS_RESOURCE_OWNER_HP)) { + return true; + } + } + return false; +} + static void rs_hp_boot_fail(uint8_t code) { - (void)rs_sysctrl_set_hp_release(false); + if (!s_hp_boot_ga2d_owned_by_hp) { + (void)rs_sysctrl_set_hp_release(false); + } printf("HP_BOOT_FAILED:%u\n", (unsigned int)code); rs_test_finish(RS_TEST_FAILED, code); } int main(void) { + bool cache_clean_completed = false; rs_hp_boot_header_t header; rs_sysctrl_hp_status_t hp_status; rs_hp_mailbox_message_t message; + rs_status_t release_status; if (rs_uart_init(CPU_FREQ * UINT32_C(1000000), UART_BPS) != RS_OK) { rs_test_finish(RS_TEST_FAILED, UINT8_C(1)); @@ -226,22 +312,71 @@ int main(void) { } } + if ((rs_hp_mailbox_probe() != RS_OK) || !rs_hp_boot_wait_ga2d_idle(RS_RESOURCE_OWNER_LP) || + (rs_resource_set_owner(RS_RESOURCE_GA2D, RS_RESOURCE_OWNER_HP, false) != RS_OK)) { + rs_hp_boot_fail(UINT8_C(9)); + } + s_hp_boot_ga2d_owned_by_hp = true; + if (!rs_hp_boot_wait_ga2d_idle(RS_RESOURCE_OWNER_HP)) { + rs_hp_boot_fail(UINT8_C(9)); + } __asm__ volatile("fence rw, rw" ::: "memory"); if ((rs_hp_mailbox_clear_lp_interrupt() != RS_OK) || (rs_sysctrl_set_hp_release(true) != RS_OK) || (rs_sysctrl_get_hp_status(&hp_status) != RS_OK) || !hp_status.released) { - rs_hp_boot_fail(UINT8_C(9)); + rs_hp_boot_fail(UINT8_C(10)); } printf("HP_BOOT_RELEASED\n"); - for (;;) { - if ((rs_hp_mailbox_receive_from_hp(&message) == RS_OK) && (message.sequence != 0U)) { - if ((message.code == RS_HP_BOOT_READY_EVENT) && - (message.argument == RS_HP_BOOT_READY_ARG)) { - printf("HP_LINUX_READY\n"); - rs_test_finish(RS_TEST_PASSED, UINT8_C(0)); + if (!rs_hp_boot_wait_message(RS_HP_BOOT_READY_EVENT, RS_HP_BOOT_READY_ARG, + RS_HP_BOOT_MAILBOX_READY_SEQUENCE)) { + rs_hp_boot_fail(UINT8_C(11)); + } + printf("HP_LINUX_READY\n"); + message.code = RS_HP_BOOT_GA2D_START_COMMAND; + message.argument = RS_HP_BOOT_GA2D_START_ARG; + message.sequence = RS_HP_BOOT_MAILBOX_READY_SEQUENCE; + if (rs_hp_mailbox_send_to_hp(&message) != RS_OK) { + rs_hp_boot_fail(UINT8_C(12)); + } + if (!rs_hp_boot_wait_message(RS_HP_BOOT_GA2D_PASS_EVENT, RS_HP_BOOT_GA2D_PASS_ARG, + RS_HP_BOOT_MAILBOX_RESULT_SEQUENCE)) { + rs_hp_boot_fail(UINT8_C(13)); + } + printf("HP_GA2D_PASS\n"); + if (!rs_hp_boot_wait_ga2d_idle(RS_RESOURCE_OWNER_HP)) { + rs_hp_boot_fail(UINT8_C(14)); + } + printf("HP_GA2D_IDLE\n"); + release_status = rs_sysctrl_set_hp_release(false); + if ((release_status != RS_OK) && (release_status != RS_EIO)) { + rs_hp_boot_fail(UINT8_C(15)); + } + printf("HP_GA2D_CACHE_REQUESTED\n"); + if (rs_hp_boot_wait_cache_request()) { + printf("HP_GA2D_CACHE_REQUEST\n"); + if (rs_hp_boot_wait_message(RS_HP_BOOT_GA2D_CACHE_EVENT, RS_HP_BOOT_GA2D_CACHE_ARG, + RS_HP_BOOT_MAILBOX_CACHE_SEQUENCE)) { + printf("HP_GA2D_CACHE_MESSAGE\n"); + if (rs_resource_acknowledge_cache_clean() == RS_OK) { + printf("HP_GA2D_CACHE_ACK\n"); + if (rs_hp_boot_wait_hp_held()) { + printf("HP_GA2D_HELD\n"); + cache_clean_completed = true; + } } - rs_hp_boot_fail(UINT8_C(10)); } } + if (!cache_clean_completed) { + rs_hp_boot_fail(UINT8_C(16)); + } + if (rs_resource_set_owner(RS_RESOURCE_GA2D, RS_RESOURCE_OWNER_LP, false) != RS_OK) { + rs_hp_boot_fail(UINT8_C(17)); + } + s_hp_boot_ga2d_owned_by_hp = false; + if (!rs_hp_boot_wait_ga2d_idle(RS_RESOURCE_OWNER_LP)) { + rs_hp_boot_fail(UINT8_C(17)); + } + printf("HP_GA2D_CACHE_CLEAN\n"); + rs_test_finish(RS_TEST_PASSED, UINT8_C(0)); } diff --git a/app/ports/linux/smoke/start.S b/app/ports/linux/smoke/start.S index d4314be9..6719228a 100644 --- a/app/ports/linux/smoke/start.S +++ b/app/ports/linux/smoke/start.S @@ -20,6 +20,42 @@ .equ HP_SEQUENCE, 0x28 .equ HP_DOORBELL, 0x2c .equ HP_READY_ARG, 0x4c4e5801 + .equ LP_COMMAND, 0x10 + .equ LP_ARG0, 0x14 + .equ LP_SEQUENCE, 0x18 + .equ HP_GA2D_START, 0x47413250 + .equ HP_GA2D_ARG, 0x47413244 + .equ HP_GA2D_PASS, 2 + .equ HP_GA2D_FAIL, 3 + .equ HP_GA2D_CACHE, 4 + .equ HP_GA2D_CACHE_ARG, 0x43424f4b + + .equ GA2D_BASE, 0x10012000 + .equ GA2D_COMMAND, 0x10 + .equ GA2D_STATUS, 0x14 + .equ GA2D_IRQ_STATE, 0x18 + .equ GA2D_ERROR_STATUS, 0x24 + .equ GA2D_JOB_CONFIG, 0x30 + .equ GA2D_GLOBAL_ALPHA, 0x34 + .equ GA2D_COLOR, 0x38 + .equ GA2D_SIZE, 0x3c + .equ GA2D_FG_ADDRESS, 0x40 + .equ GA2D_FG_PITCH, 0x44 + .equ GA2D_FG_FORMAT, 0x48 + .equ GA2D_BG_ADDRESS, 0x4c + .equ GA2D_BG_PITCH, 0x50 + .equ GA2D_BG_FORMAT, 0x54 + .equ GA2D_DST_ADDRESS, 0x58 + .equ GA2D_DST_PITCH, 0x5c + .equ GA2D_DST_FORMAT, 0x60 + + .equ RESOURCE_CTRL_BASE, 0x2000a000 + .equ RESOURCE_CACHE_CONTROL, 0x10 + .equ GA2D_WIDTH, 8 + .equ GA2D_PITCH, 64 + .equ GA2D_SIZE_VALUE, 0x00010008 + .equ GA2D_XRGB8888, 2 + .equ GA2D_WAIT_POLLS, 0x00100000 _start: li t0, UART1_BASE @@ -60,9 +96,316 @@ publish_ready: fence w, w sw t1, HP_DOORBELL(t0) +wait_start_command: + li s1, GA2D_WAIT_POLLS +wait_start_command_loop: + li t0, MAILBOX_BASE + lw t1, LP_SEQUENCE(t0) + li t2, 1 + bne t1, t2, wait_start_command_next + lw t1, LP_COMMAND(t0) + li t2, HP_GA2D_START + bne t1, t2, wait_start_command_next + lw t1, LP_ARG0(t0) + li t2, HP_GA2D_ARG + beq t1, t2, start_ga2d +wait_start_command_next: + addi s1, s1, -1 + bnez s1, wait_start_command_loop + j publish_ga2d_fail + +start_ga2d: + jal ra, prepare_shared_lines + jal ra, clean_shared_lines + jal ra, ga2d_fill + beqz a0, publish_ga2d_fail + jal ra, invalidate_shared_lines + jal ra, check_fill + beqz a0, publish_ga2d_fail + + la a0, hp_destination_line + li a1, 0xc7c7c7c7 + li a2, 16 + jal ra, fill_words + jal ra, clean_shared_lines + jal ra, ga2d_copy + beqz a0, publish_ga2d_fail + jal ra, invalidate_shared_lines + jal ra, check_copy + beqz a0, publish_ga2d_fail + +publish_ga2d_pass: + li t0, MAILBOX_BASE + li t1, HP_GA2D_PASS + sw t1, HP_EVENT(t0) + li t1, HP_GA2D_ARG + sw t1, HP_ARG0(t0) + li t1, 2 + sw t1, HP_SEQUENCE(t0) + fence w, w + sw t1, HP_DOORBELL(t0) + +wait_cache_request: + li s1, GA2D_WAIT_POLLS +wait_cache_request_loop: + li t0, RESOURCE_CTRL_BASE + lw t1, RESOURCE_CACHE_CONTROL(t0) + andi t1, t1, 2 + bnez t1, clean_cache_request + addi s1, s1, -1 + bnez s1, wait_cache_request_loop + j publish_cache_fail + +clean_cache_request: + jal ra, clean_invalidate_shared_lines + li t0, MAILBOX_BASE + li t1, HP_GA2D_CACHE + sw t1, HP_EVENT(t0) + li t1, HP_GA2D_CACHE_ARG + sw t1, HP_ARG0(t0) + li t1, 3 + sw t1, HP_SEQUENCE(t0) + fence w, w + sw t1, HP_DOORBELL(t0) + j halt + +publish_cache_fail: + li t0, MAILBOX_BASE + li t1, HP_GA2D_FAIL + sw t1, HP_EVENT(t0) + li t1, 0xdead0002 + sw t1, HP_ARG0(t0) + li t1, 3 + sw t1, HP_SEQUENCE(t0) + fence w, w + sw t1, HP_DOORBELL(t0) + j halt + +publish_ga2d_fail: + li t0, MAILBOX_BASE + li t1, HP_GA2D_FAIL + sw t1, HP_EVENT(t0) + li t1, 0xdead0001 + sw t1, HP_ARG0(t0) + li t1, 2 + sw t1, HP_SEQUENCE(t0) + fence w, w + sw t1, HP_DOORBELL(t0) + halt: j halt +fill_words: + sw a1, 0(a0) + addi a0, a0, 4 + addi a2, a2, -1 + bnez a2, fill_words + ret + +prepare_shared_lines: + mv s0, ra + la a0, hp_source_guard + li a1, 0xa5a5a5a5 + li a2, 16 + jal ra, fill_words + la a0, hp_destination_guard + li a1, 0xd3d3d3d3 + li a2, 16 + jal ra, fill_words + la a0, hp_source_line + li a1, 0xa5a5a5a5 + li a2, 16 + jal ra, fill_words + la a0, hp_destination_line + li a1, 0xc7c7c7c7 + li a2, 16 + jal ra, fill_words + la t0, hp_source_line + li t1, 0x10203040 + li t2, 0x01020304 + li t3, 8 +prepare_source_data: + sw t1, 0(t0) + add t1, t1, t2 + addi t0, t0, 4 + addi t3, t3, -1 + bnez t3, prepare_source_data + mv ra, s0 + ret + +clean_shared_lines: + la a0, hp_source_guard + cbo.clean 0(a0) + la a0, hp_source_line + cbo.clean 0(a0) + la a0, hp_destination_line + cbo.clean 0(a0) + la a0, hp_destination_guard + cbo.clean 0(a0) + fence rw, rw + ret + +invalidate_shared_lines: + la a0, hp_source_guard + cbo.inval 0(a0) + la a0, hp_source_line + cbo.inval 0(a0) + la a0, hp_destination_line + cbo.inval 0(a0) + la a0, hp_destination_guard + cbo.inval 0(a0) + fence r, r + ret + +clean_invalidate_shared_lines: + mv s0, ra + jal ra, clean_shared_lines + jal ra, invalidate_shared_lines + mv ra, s0 + ret + +ga2d_fill: + li t0, GA2D_BASE + li t1, 7 + sw t1, GA2D_IRQ_STATE(t0) + li t1, 1 + sw t1, GA2D_ERROR_STATUS(t0) + sw zero, GA2D_JOB_CONFIG(t0) + sw zero, GA2D_GLOBAL_ALPHA(t0) + li t1, 0x80123456 + sw t1, GA2D_COLOR(t0) + li t1, GA2D_SIZE_VALUE + sw t1, GA2D_SIZE(t0) + sw zero, GA2D_FG_ADDRESS(t0) + sw zero, GA2D_FG_PITCH(t0) + sw zero, GA2D_FG_FORMAT(t0) + sw zero, GA2D_BG_ADDRESS(t0) + sw zero, GA2D_BG_PITCH(t0) + sw zero, GA2D_BG_FORMAT(t0) + la t1, hp_destination_line + sw t1, GA2D_DST_ADDRESS(t0) + li t1, GA2D_PITCH + sw t1, GA2D_DST_PITCH(t0) + li t1, GA2D_XRGB8888 + sw t1, GA2D_DST_FORMAT(t0) + fence rw, rw + li t1, 1 + sw t1, GA2D_COMMAND(t0) + j ga2d_wait + +ga2d_copy: + li t0, GA2D_BASE + li t1, 7 + sw t1, GA2D_IRQ_STATE(t0) + li t1, 1 + sw t1, GA2D_ERROR_STATUS(t0) + li t1, 1 + sw t1, GA2D_JOB_CONFIG(t0) + sw zero, GA2D_GLOBAL_ALPHA(t0) + sw zero, GA2D_COLOR(t0) + li t1, GA2D_SIZE_VALUE + sw t1, GA2D_SIZE(t0) + la t1, hp_source_line + sw t1, GA2D_FG_ADDRESS(t0) + li t1, GA2D_PITCH + sw t1, GA2D_FG_PITCH(t0) + li t1, GA2D_XRGB8888 + sw t1, GA2D_FG_FORMAT(t0) + sw zero, GA2D_BG_ADDRESS(t0) + sw zero, GA2D_BG_PITCH(t0) + sw zero, GA2D_BG_FORMAT(t0) + la t1, hp_destination_line + sw t1, GA2D_DST_ADDRESS(t0) + li t1, GA2D_PITCH + sw t1, GA2D_DST_PITCH(t0) + li t1, GA2D_XRGB8888 + sw t1, GA2D_DST_FORMAT(t0) + fence rw, rw + li t1, 1 + sw t1, GA2D_COMMAND(t0) + +ga2d_wait: + li t1, GA2D_WAIT_POLLS +ga2d_wait_loop: + lw t2, GA2D_STATUS(t0) + andi t3, t2, 0x60 + bnez t3, ga2d_wait_failed + andi t3, t2, 0x10 + beqz t3, ga2d_wait_next + andi t3, t2, 0x03 + bnez t3, ga2d_wait_next + li a0, 1 + ret +ga2d_wait_next: + addi t1, t1, -1 + bnez t1, ga2d_wait_loop +ga2d_wait_failed: + li a0, 0 + ret + +check_fill: + la t0, hp_destination_line + li t1, 0xff123456 + li t2, 8 +check_fill_pixels: + lw t3, 0(t0) + bne t3, t1, check_failed + addi t0, t0, 4 + addi t2, t2, -1 + bnez t2, check_fill_pixels + lw t3, 0(t0) + li t1, 0xc7c7c7c7 + bne t3, t1, check_failed + la t0, hp_destination_guard + lw t3, 0(t0) + li t1, 0xd3d3d3d3 + bne t3, t1, check_failed + li a0, 1 + ret + +check_copy: + la t0, hp_source_line + la t1, hp_destination_line + li t2, 8 +check_copy_pixels: + lw t3, 0(t0) + lw t4, 0(t1) + bne t3, t4, check_failed + addi t0, t0, 4 + addi t1, t1, 4 + addi t2, t2, -1 + bnez t2, check_copy_pixels + lw t3, 0(t1) + li t4, 0xc7c7c7c7 + bne t3, t4, check_failed + la t0, hp_source_guard + lw t3, 0(t0) + li t4, 0xa5a5a5a5 + bne t3, t4, check_failed + la t0, hp_destination_guard + lw t3, 0(t0) + li t4, 0xd3d3d3d3 + bne t3, t4, check_failed + li a0, 1 + ret + +check_failed: + li a0, 0 + ret + .section .rodata hp_message: .asciz "HP_SMOKE_READY\n" + + .balign 64 +hp_source_guard: + .space 64 + .balign 64 +hp_source_line: + .space 64 + .balign 64 +hp_destination_line: + .space 64 + .balign 64 +hp_destination_guard: + .space 64 diff --git a/crt/README.md b/crt/README.md index d4c177ec..312142a8 100644 --- a/crt/README.md +++ b/crt/README.md @@ -69,9 +69,12 @@ abort/reset, interrupt, and first-error access. The P5 surface remains coreless; MP3, KWS, model loading, and the APU RX route stay unavailable until their frozen later phases. -`` provides GA2D shell discovery, status/error, IRQ, -and bounded abort/reset access. In Phase 3 its private DMA, 2D job, and pixel -operations remain unavailable and return `RS_ENOTSUP`. +`` provides GA2D discovery, direct single-job 2D FILL/COPY +configuration and execution, status/error, snapshots, IRQ, and bounded +abort/reset access. Its private AXI64 DMA supports RGB565, RGB888, XRGB8888, +and ARGB8888 surfaces with byte pitches and byte edges. CONVERT, BLEND, A8, +in-place background composition, descriptor/ring/queue submission, cache +coherency, and a Linux graphics driver are not provided. `` owns root-management access to native AXI64 master/target counters, stable snapshots, warm-flush counts, target isolation, diff --git a/crt/include/retrosoc/hal/ga2d_regs.h b/crt/include/retrosoc/hal/ga2d_regs.h index 76a3a24c..bbdacb85 100644 --- a/crt/include/retrosoc/hal/ga2d_regs.h +++ b/crt/include/retrosoc/hal/ga2d_regs.h @@ -50,9 +50,9 @@ #define RS_GA2D_IP_VERSION_VALUE UINT32_C(0x00010000) #define RS_GA2D_IP_VERSION_MAJOR_MASK UINT32_C(0xFFFF0000) #define RS_GA2D_IP_VERSION_MAJOR_1 UINT32_C(0x00010000) -#define RS_GA2D_CAPABILITY_P3 UINT32_C(0x00000040) -#define RS_GA2D_LIMITS_P3 UINT32_C(0x00000000) -#define RS_GA2D_FORMAT_CAPABILITY_P3 UINT32_C(0x00000000) +#define RS_GA2D_CAPABILITY_P4 UINT32_C(0x000003E3) +#define RS_GA2D_LIMITS_P4 UINT32_C(0x08202010) +#define RS_GA2D_FORMAT_CAPABILITY_P4 UINT32_C(0x000F000F) #define RS_GA2D_TIMEOUT_CYCLES_RESET UINT32_C(0x00100000) #define RS_GA2D_GLOBAL_ALPHA_RESET UINT32_C(0x000000FF) diff --git a/crt/src/hal/ga2d.c b/crt/src/hal/ga2d.c index 03688e0a..474eea62 100644 --- a/crt/src/hal/ga2d.c +++ b/crt/src/hal/ga2d.c @@ -1,14 +1,20 @@ #include #include +#include #include +#include "ga2d_math.h" + #if defined(RS_GA2D_TEST_MMIO) extern volatile uint32_t rs_ga2d_test_mmio[1024]; +extern uint32_t rs_ga2d_test_mem_pad_mode; #undef RS_GA2D_REG #define RS_GA2D_REG(offset) rs_ga2d_test_mmio[(offset) / 4U] #endif +#define RS_GA2D_MEM_PAD_MODE_MASK UINT32_C(0x00000003) + static void rs_ga2d_fence(void) { #if defined(__riscv) __asm__ volatile("fence rw, rw" ::: "memory"); @@ -42,6 +48,89 @@ static rs_status_t rs_ga2d_require_irq(void) { return ((capability.features & RS_GA2D_CAPABILITY_IRQ) != 0U) ? RS_OK : RS_ENOTSUP; } +static rs_status_t rs_ga2d_require_snapshot(void) { + rs_ga2d_capability_t capability; + rs_status_t status; + + status = rs_ga2d_get_capability(&capability); + if (status != RS_OK) { + return status; + } + return ((capability.features & RS_GA2D_CAPABILITY_SNAPSHOT) != 0U) ? RS_OK : RS_ENOTSUP; +} + +static rs_status_t rs_ga2d_require_private_dma(void) { + rs_ga2d_capability_t capability; + rs_status_t status; + + status = rs_ga2d_get_capability(&capability); + if (status != RS_OK) { + return status; + } + return ((capability.features & RS_GA2D_CAPABILITY_PRIVATE_DMA) != 0U) ? RS_OK : RS_ENOTSUP; +} + +static rs_status_t rs_ga2d_require_safe_idle(void) { + const uint32_t status = RS_GA2D_REG(RS_GA2D_REG_STATUS); + + if (((status & (RS_GA2D_STATUS_BUSY | RS_GA2D_STATUS_DRAINING | RS_GA2D_STATUS_QUIESCED | + RS_GA2D_STATUS_RECOVERY_REQUIRED)) != 0U) || + ((status & RS_GA2D_STATUS_DATA_READY) == 0U)) { + return RS_EIO; + } + return RS_OK; +} + +static bool rs_ga2d_address_in_region(uintptr_t address, uint32_t region_base, + uint32_t region_size) { + const uint64_t base = (uint64_t)address; + const uint64_t start = (uint64_t)region_base; + const uint64_t end = start + (uint64_t)region_size; + + return (base >= start) && (base < end); +} + +static rs_memory_pad_mode_t rs_ga2d_memory_pad_mode(void) { +#if defined(RS_GA2D_TEST_MMIO) + return (rs_memory_pad_mode_t)(rs_ga2d_test_mem_pad_mode & RS_GA2D_MEM_PAD_MODE_MASK); +#else + return (rs_memory_pad_mode_t)(RS_SOC_REG32(RS_SOC_APB4_SYSCTRL_BASE, + RS_SOC_SYSCTRL_MEM_PAD_STATUS_OFFSET) & + RS_GA2D_MEM_PAD_MODE_MASK); +#endif +} + +static rs_status_t rs_ga2d_validate_runtime_memory_surface(const rs_ga2d_surface_t *surface) { + rs_memory_pad_mode_t pad_mode; + + if (!rs_ga2d_address_in_region(surface->address, RS_SOC_PSRAM_BASE, RS_SOC_PSRAM_SIZE) && + !rs_ga2d_address_in_region(surface->address, RS_SOC_OPIPSRAM_BASE, RS_SOC_OPIPSRAM_SIZE)) { + return RS_OK; + } + pad_mode = rs_ga2d_memory_pad_mode(); + if (rs_ga2d_address_in_region(surface->address, RS_SOC_PSRAM_BASE, RS_SOC_PSRAM_SIZE)) { + return (pad_mode == RS_MEMORY_PAD_QPI) ? RS_OK : RS_EINVAL; + } + return (pad_mode == RS_MEMORY_PAD_OPI) ? RS_OK : RS_EINVAL; +} + +static rs_status_t rs_ga2d_validate_runtime_memory(const rs_ga2d_job_t *job) { + rs_status_t status; + + status = rs_ga2d_validate_runtime_memory_surface(&job->destination); + if (status != RS_OK) { + return status; + } + if (job->operation != RS_GA2D_OP_COPY) { + return RS_OK; + } + return rs_ga2d_validate_runtime_memory_surface(&job->foreground); +} + +static uint64_t rs_ga2d_read_snapshot64(uint32_t low_offset, uint32_t high_offset) { + return ((uint64_t)RS_GA2D_REG(high_offset) << 32U) | (uint64_t)RS_GA2D_REG(low_offset); +} + rs_status_t rs_ga2d_get_capability(rs_ga2d_capability_t *capability) { uint32_t ip_id; @@ -61,26 +150,119 @@ rs_status_t rs_ga2d_get_capability(rs_ga2d_capability_t *capability) { } rs_status_t rs_ga2d_job_validate(const rs_ga2d_job_t *job) { + rs_ga2d_capability_t capability; + rs_status_t status; + if (job == NULL) { return RS_EINVAL; } - return RS_ENOTSUP; + status = rs_ga2d_get_capability(&capability); + if (status != RS_OK) { + return status; + } + status = rs_ga2d_job_validate_capability(job, &capability); + if (status != RS_OK) { + return status; + } + return rs_ga2d_validate_runtime_memory(job); } rs_status_t rs_ga2d_configure(const rs_ga2d_job_t *job) { + rs_ga2d_capability_t capability; + rs_status_t status; + if (job == NULL) { return RS_EINVAL; } - return RS_ENOTSUP; + status = rs_ga2d_get_capability(&capability); + if (status != RS_OK) { + return status; + } + status = rs_ga2d_job_validate_capability(job, &capability); + if (status != RS_OK) { + return status; + } + status = rs_ga2d_validate_runtime_memory(job); + if (status != RS_OK) { + return status; + } + status = rs_ga2d_require_safe_idle(); + if (status != RS_OK) { + return status; + } + + RS_GA2D_REG(RS_GA2D_REG_JOB_CONFIG) = + ((uint32_t)job->operation << RS_GA2D_JOB_CONFIG_OPERATION_SHIFT) & + RS_GA2D_JOB_CONFIG_OPERATION_MASK; + RS_GA2D_REG(RS_GA2D_REG_GLOBAL_ALPHA) = + ((uint32_t)job->global_alpha << RS_GA2D_GLOBAL_ALPHA_VALUE_SHIFT) & + RS_GA2D_GLOBAL_ALPHA_VALUE_MASK; + RS_GA2D_REG(RS_GA2D_REG_COLOR) = job->color; + RS_GA2D_REG(RS_GA2D_REG_SIZE) = ((uint32_t)job->width << RS_GA2D_SIZE_WIDTH_SHIFT) | + ((uint32_t)job->height << RS_GA2D_SIZE_HEIGHT_SHIFT); + + if (job->operation == RS_GA2D_OP_COPY) { + RS_GA2D_REG(RS_GA2D_REG_FG_ADDRESS) = (uint32_t)job->foreground.address; + RS_GA2D_REG(RS_GA2D_REG_FG_PITCH) = job->foreground.pitch; + RS_GA2D_REG(RS_GA2D_REG_FG_FORMAT) = + ((uint32_t)job->foreground.format << RS_GA2D_FG_FORMAT_VALUE_SHIFT) & + RS_GA2D_FORMAT_VALUE_MASK; + } else { + RS_GA2D_REG(RS_GA2D_REG_FG_ADDRESS) = 0U; + RS_GA2D_REG(RS_GA2D_REG_FG_PITCH) = 0U; + RS_GA2D_REG(RS_GA2D_REG_FG_FORMAT) = 0U; + } + RS_GA2D_REG(RS_GA2D_REG_BG_ADDRESS) = 0U; + RS_GA2D_REG(RS_GA2D_REG_BG_PITCH) = 0U; + RS_GA2D_REG(RS_GA2D_REG_BG_FORMAT) = 0U; + RS_GA2D_REG(RS_GA2D_REG_DST_ADDRESS) = (uint32_t)job->destination.address; + RS_GA2D_REG(RS_GA2D_REG_DST_PITCH) = job->destination.pitch; + RS_GA2D_REG(RS_GA2D_REG_DST_FORMAT) = + ((uint32_t)job->destination.format << RS_GA2D_DST_FORMAT_VALUE_SHIFT) & + RS_GA2D_FORMAT_VALUE_MASK; + return RS_OK; } rs_status_t rs_ga2d_start(void) { - return RS_ENOTSUP; + rs_status_t status; + + status = rs_ga2d_require_private_dma(); + if (status != RS_OK) { + return status; + } + status = rs_ga2d_require_safe_idle(); + if (status != RS_OK) { + return status; + } + RS_GA2D_REG(RS_GA2D_REG_IRQ_STATE) = RS_GA2D_IRQ_ALL; + RS_GA2D_REG(RS_GA2D_REG_ERROR_STATUS) = RS_GA2D_ERROR_STATUS_VALID; + rs_ga2d_fence(); + RS_GA2D_REG(RS_GA2D_REG_COMMAND) = RS_GA2D_COMMAND_START; + return RS_OK; } rs_status_t rs_ga2d_wait(rs_timeout_t timeout) { - (void)timeout; - return RS_ENOTSUP; + uint32_t polls; + rs_status_t status; + + status = rs_ga2d_require_private_dma(); + if (status != RS_OK) { + return status; + } + polls = rs_ga2d_poll_count(timeout); + while (polls-- != 0U) { + const uint32_t flags = RS_GA2D_REG(RS_GA2D_REG_STATUS); + + if ((flags & (RS_GA2D_STATUS_ERROR | RS_GA2D_STATUS_ABORTED)) != 0U) { + return RS_EIO; + } + if (((flags & RS_GA2D_STATUS_DONE) != 0U) && + ((flags & (RS_GA2D_STATUS_BUSY | RS_GA2D_STATUS_DRAINING)) == 0U)) { + rs_ga2d_fence(); + return RS_OK; + } + } + return RS_ETIMEOUT; } rs_status_t rs_ga2d_abort_wait(rs_timeout_t timeout) { @@ -94,7 +276,13 @@ rs_status_t rs_ga2d_abort_wait(rs_timeout_t timeout) { while (polls-- != 0U) { const uint32_t status = RS_GA2D_REG(RS_GA2D_REG_STATUS); + if ((status & RS_GA2D_STATUS_RECOVERY_REQUIRED) != 0U) { + return RS_EIO; + } if ((status & (RS_GA2D_STATUS_BUSY | RS_GA2D_STATUS_DRAINING)) == 0U) { + if (rs_ga2d_require_safe_idle() != RS_OK) { + return RS_EIO; + } rs_ga2d_fence(); return RS_OK; } @@ -168,10 +356,29 @@ rs_status_t rs_ga2d_get_error(rs_ga2d_error_t *error) { } rs_status_t rs_ga2d_get_stats(rs_ga2d_stats_t *stats) { + rs_status_t status; + if (stats == NULL) { return RS_EINVAL; } - return RS_ENOTSUP; + status = rs_ga2d_require_snapshot(); + if (status != RS_OK) { + return status; + } + RS_GA2D_REG(RS_GA2D_REG_PERF_SNAPSHOT) = UINT32_C(1); + stats->cycles = rs_ga2d_read_snapshot64(RS_GA2D_REG_SNAP_CYCLES_LO, RS_GA2D_REG_SNAP_CYCLES_HI); + stats->read_bytes = + rs_ga2d_read_snapshot64(RS_GA2D_REG_SNAP_READ_BYTES_LO, RS_GA2D_REG_SNAP_READ_BYTES_HI); + stats->write_bytes = + rs_ga2d_read_snapshot64(RS_GA2D_REG_SNAP_WRITE_BYTES_LO, RS_GA2D_REG_SNAP_WRITE_BYTES_HI); + stats->read_stalls = + rs_ga2d_read_snapshot64(RS_GA2D_REG_SNAP_READ_STALL_LO, RS_GA2D_REG_SNAP_READ_STALL_HI); + stats->write_stalls = + rs_ga2d_read_snapshot64(RS_GA2D_REG_SNAP_WRITE_STALL_LO, RS_GA2D_REG_SNAP_WRITE_STALL_HI); + stats->pipe_stalls = + rs_ga2d_read_snapshot64(RS_GA2D_REG_SNAP_PIPE_STALL_LO, RS_GA2D_REG_SNAP_PIPE_STALL_HI); + stats->lines_done = (uint16_t)RS_GA2D_REG(RS_GA2D_REG_SNAP_LINES_DONE); + return RS_OK; } rs_status_t rs_ga2d_irq_enable(uint32_t mask) { diff --git a/crt/src/hal/ga2d_math.c b/crt/src/hal/ga2d_math.c new file mode 100644 index 00000000..953082e9 --- /dev/null +++ b/crt/src/hal/ga2d_math.c @@ -0,0 +1,211 @@ +#include +#include +#include + +#include + +#include "ga2d_math.h" + +#define RS_GA2D_ADDRESS_LIMIT UINT64_C(0x100000000) + +static bool rs_ga2d_format_known(rs_ga2d_format_t format) { + return ((format == RS_GA2D_FORMAT_RGB565) || (format == RS_GA2D_FORMAT_RGB888) || + (format == RS_GA2D_FORMAT_XRGB8888) || (format == RS_GA2D_FORMAT_ARGB8888) || + (format == RS_GA2D_FORMAT_A8)); +} + +static uint32_t rs_ga2d_bytes_per_pixel(rs_ga2d_format_t format) { + uint32_t bytes = 0U; + + switch (format) { + case RS_GA2D_FORMAT_RGB565: + bytes = 2U; + break; + case RS_GA2D_FORMAT_RGB888: + bytes = 3U; + break; + case RS_GA2D_FORMAT_XRGB8888: + case RS_GA2D_FORMAT_ARGB8888: + bytes = 4U; + break; + case RS_GA2D_FORMAT_A8: + bytes = 1U; + break; + default: + break; + } + return bytes; +} + +static uint32_t rs_ga2d_pixel_alignment(rs_ga2d_format_t format) { + uint32_t alignment = 1U; + + switch (format) { + case RS_GA2D_FORMAT_RGB565: + alignment = 2U; + break; + case RS_GA2D_FORMAT_XRGB8888: + case RS_GA2D_FORMAT_ARGB8888: + alignment = 4U; + break; + default: + break; + } + return alignment; +} + +static bool rs_ga2d_color_format(rs_ga2d_format_t format) { + return (format == RS_GA2D_FORMAT_RGB565) || (format == RS_GA2D_FORMAT_RGB888) || + (format == RS_GA2D_FORMAT_XRGB8888) || (format == RS_GA2D_FORMAT_ARGB8888); +} + +static bool rs_ga2d_interval_in_region(uint64_t base, uint64_t end, uint32_t region_base, + uint32_t region_size) { + const uint64_t start = (uint64_t)region_base; + const uint64_t limit = start + (uint64_t)region_size; + + return (base >= start) && (end <= limit); +} + +static bool rs_ga2d_interval_mapped(uint64_t base, uint64_t end, bool write) { + if ((RS_SOC_HAS_SRAM != 0U) && + rs_ga2d_interval_in_region(base, end, RS_SOC_SRAM_BASE, RS_SOC_SRAM_SIZE)) { + return true; + } + if (rs_ga2d_interval_in_region(base, end, RS_SOC_SDRAM_BASE, RS_SOC_SDRAM_SIZE) || + rs_ga2d_interval_in_region(base, end, RS_SOC_PSRAM_BASE, RS_SOC_PSRAM_SIZE) || + rs_ga2d_interval_in_region(base, end, RS_SOC_OPIPSRAM_BASE, RS_SOC_OPIPSRAM_SIZE)) { + return true; + } + return !write && rs_ga2d_interval_in_region(base, end, RS_SOC_XPI_BASE, RS_SOC_XPI_SIZE); +} + +static bool rs_ga2d_format_advertised(uint32_t formats, uint32_t shift, rs_ga2d_format_t format) { + const uint32_t advertised = (formats >> shift) & RS_GA2D_FORMAT_CAPABILITY_MASK; + const uint32_t format_bit = UINT32_C(1) << (uint32_t)format; + + return (advertised & format_bit) != 0U; +} + +static rs_status_t rs_ga2d_validate_surface(const rs_ga2d_surface_t *surface, uint16_t width, + uint16_t height, bool write) { + const uint32_t bytes_per_pixel = rs_ga2d_bytes_per_pixel(surface->format); + const uint32_t alignment = rs_ga2d_pixel_alignment(surface->format); + const uint64_t base = (uint64_t)surface->address; + const uint64_t row_bytes = (uint64_t)width * (uint64_t)bytes_per_pixel; + uint64_t end; + +#if UINTPTR_MAX > UINT32_MAX + if (surface->address > (uintptr_t)UINT32_MAX) { + return RS_EINVAL; + } +#endif + if (((base % (uint64_t)alignment) != 0U) || + (((uint64_t)surface->pitch % (uint64_t)alignment) != 0U)) { + return RS_EINVAL; + } + if ((uint64_t)surface->pitch < row_bytes) { + return RS_EINVAL; + } + end = base + ((uint64_t)(height - 1U) * (uint64_t)surface->pitch) + row_bytes; + if (end > RS_GA2D_ADDRESS_LIMIT) { + return RS_EINVAL; + } + return rs_ga2d_interval_mapped(base, end, write) ? RS_OK : RS_EINVAL; +} + +static rs_status_t rs_ga2d_validate_operation(const rs_ga2d_job_t *job, + const rs_ga2d_capability_t *capability) { + uint32_t operation_capability; + + switch (job->operation) { + case RS_GA2D_OP_FILL: + operation_capability = RS_GA2D_CAPABILITY_FILL; + break; + case RS_GA2D_OP_COPY: + operation_capability = RS_GA2D_CAPABILITY_COPY; + break; + case RS_GA2D_OP_CONVERT: + case RS_GA2D_OP_BLEND: + return RS_ENOTSUP; + default: + return RS_EINVAL; + } + return ((capability->features & (operation_capability | RS_GA2D_CAPABILITY_PRIVATE_DMA)) == + (operation_capability | RS_GA2D_CAPABILITY_PRIVATE_DMA)) + ? RS_OK + : RS_ENOTSUP; +} + +static rs_status_t rs_ga2d_validate_used_format(const rs_ga2d_surface_t *surface, + const rs_ga2d_capability_t *capability, + uint32_t shift) { + if (!rs_ga2d_format_known(surface->format)) { + return RS_EINVAL; + } + if (!rs_ga2d_color_format(surface->format) || + !rs_ga2d_format_advertised(capability->formats, shift, surface->format)) { + return RS_ENOTSUP; + } + return RS_OK; +} + +static bool rs_ga2d_intervals_overlap(const rs_ga2d_surface_t *first, + const rs_ga2d_surface_t *second, uint16_t width, + uint16_t height) { + const uint64_t first_base = (uint64_t)first->address; + const uint64_t second_base = (uint64_t)second->address; + const uint64_t first_end = first_base + ((uint64_t)(height - 1U) * (uint64_t)first->pitch) + + ((uint64_t)width * (uint64_t)rs_ga2d_bytes_per_pixel(first->format)); + const uint64_t second_end = + second_base + ((uint64_t)(height - 1U) * (uint64_t)second->pitch) + + ((uint64_t)width * (uint64_t)rs_ga2d_bytes_per_pixel(second->format)); + + return (first_base < second_end) && (second_base < first_end); +} + +rs_status_t rs_ga2d_job_validate_capability(const rs_ga2d_job_t *job, + const rs_ga2d_capability_t *capability) { + rs_status_t status; + + if ((job == NULL) || (capability == NULL)) { + return RS_EINVAL; + } + status = rs_ga2d_validate_operation(job, capability); + if (status != RS_OK) { + return status; + } + if ((job->width == 0U) || (job->height == 0U)) { + return RS_EINVAL; + } + status = rs_ga2d_validate_used_format(&job->destination, capability, + RS_GA2D_FORMAT_CAPABILITY_DESTINATION_SHIFT); + if (status != RS_OK) { + return status; + } + if (job->operation == RS_GA2D_OP_COPY) { + status = rs_ga2d_validate_used_format(&job->foreground, capability, + RS_GA2D_FORMAT_CAPABILITY_FOREGROUND_SHIFT); + if (status != RS_OK) { + return status; + } + if (job->foreground.format != job->destination.format) { + return RS_EINVAL; + } + } + status = rs_ga2d_validate_surface(&job->destination, job->width, job->height, true); + if (status != RS_OK) { + return status; + } + if (job->operation == RS_GA2D_OP_COPY) { + status = rs_ga2d_validate_surface(&job->foreground, job->width, job->height, false); + if (status != RS_OK) { + return status; + } + if (rs_ga2d_intervals_overlap(&job->foreground, &job->destination, job->width, + job->height)) { + return RS_EINVAL; + } + } + return RS_OK; +} diff --git a/crt/src/hal/ga2d_math.h b/crt/src/hal/ga2d_math.h new file mode 100644 index 00000000..166ff304 --- /dev/null +++ b/crt/src/hal/ga2d_math.h @@ -0,0 +1,9 @@ +#ifndef RETROSOC_HAL_GA2D_MATH_H +#define RETROSOC_HAL_GA2D_MATH_H + +#include + +rs_status_t rs_ga2d_job_validate_capability(const rs_ga2d_job_t *job, + const rs_ga2d_capability_t *capability); + +#endif diff --git a/crt/src/hal/resource.c b/crt/src/hal/resource.c index 17a76ead..e9a42114 100644 --- a/crt/src/hal/resource.c +++ b/crt/src/hal/resource.c @@ -17,6 +17,8 @@ #define RS_RESOURCE_OWNER_LOCK UINT32_C(0x00000100) #define RS_RESOURCE_CONTROL_QUIESCE UINT32_C(0x00000001) #define RS_RESOURCE_CONTROL_RESET UINT32_C(0x00000002) +#define RS_RESOURCE_STATUS_QUIESCED UINT32_C(0x00000004) +#define RS_RESOURCE_STATUS_RESET UINT32_C(0x00000008) #define RS_RESOURCE_STATUS_IDLE UINT32_C(0x00000010) #define RS_RESOURCE_STATUS_FAULT UINT32_C(0x00000020) #define RS_RESOURCE_STATUS_IRQ UINT32_C(0x00000040) @@ -29,7 +31,13 @@ static bool rs_resource_valid(rs_resource_t resource) { } static volatile uint32_t *rs_resource_register(uint32_t offset) { +#if defined(RS_RESOURCE_TEST_MMIO) + extern volatile uint32_t rs_resource_test_mmio[1024]; + + return &rs_resource_test_mmio[offset / sizeof(uint32_t)]; +#else return (volatile uint32_t *)(RS_SOC_APB4_RESOURCE_CTRL_BASE + (uintptr_t)offset); +#endif } static uint32_t rs_resource_offset(rs_resource_t resource, uint32_t offset) { @@ -51,8 +59,8 @@ rs_status_t rs_resource_get_status(rs_resource_t resource, rs_resource_status_t status->owner_locked = (owner & RS_RESOURCE_OWNER_LOCK) != 0U; status->blocked = (state & RS_RESOURCE_STATUS_BLOCKED) != 0U; status->idle = (state & RS_RESOURCE_STATUS_IDLE) != 0U; - status->quiesced = (state & RS_RESOURCE_CONTROL_QUIESCE) != 0U; - status->in_reset = (state & RS_RESOURCE_CONTROL_RESET) != 0U; + status->quiesced = (state & RS_RESOURCE_STATUS_QUIESCED) != 0U; + status->in_reset = (state & RS_RESOURCE_STATUS_RESET) != 0U; status->fault = (state & RS_RESOURCE_STATUS_FAULT) != 0U; status->irq_pending = (state & RS_RESOURCE_STATUS_IRQ) != 0U; return RS_OK; diff --git a/docs/axi4-interconnect.md b/docs/axi4-interconnect.md index 3eba5f61..f9dbed43 100644 --- a/docs/axi4-interconnect.md +++ b/docs/axi4-interconnect.md @@ -3,18 +3,17 @@ Mini uses two cooperating AXI fabrics. `axi4_interconnect` is the 32-bit LP control/compatibility plane; `axi4_data_crossbar` is the 64-bit HP payload plane. Common `axi4_if`, `memory_map.json`, and `soc_topology.json` are the -executable protocol, address, and integration sources of truth. GA2D Phase 3 -activates the PCLK-only `APB4_GA2D` shell and its resource-owned interrupt; -the dedicated master-8 AXI bridge remains an idle source until a later phase -implements payload/DMA traffic. +executable protocol, address, and integration sources of truth. GA2D Phase 4 +uses `APB4_GA2D` and its resource-owned interrupt to control the direct +single-job private-AXI64 FILL/COPY DMA engine on dedicated master 8. [GA2D Phase 2](ip/ga2d.md#phase-2---expand-axi64-fabric-and-resource-integration) expands the fabric to nine native masters and seven-bit global IDs. It adds master 8 and preserves all existing identities, target policy, and timeout -behavior. Master 8 is fed only by its dedicated PCLK-to-HP AXI64/ID3 bridge, -whose source remains idle. Phase 3 activates the separate PCLK `APB4_GA2D` -shell and resource-owned IRQ, while payload/DMA, pixel work, and START remain -deferred. The GA2D specification also records the existing master-6/JPEG +behavior. Master 8 is fed only by its dedicated PCLK-to-HP AXI64/ID3 bridge and runs the +GA2D direct FILL/COPY DMA source. Phase 4 retains the separate PCLK +`APB4_GA2D` control plane and resource-owned IRQ. The GA2D specification also +records the existing master-6/JPEG admission discrepancy; it must not be interpreted as a free port. ## LP control plane @@ -50,7 +49,7 @@ IDs. It has nine masters: | 5 | LP data gateway | Hazard3 memory traffic, LP-to-HP CDC, AXI32-to-64 | | 6 | JPEG | PCLK-to-HP AXI64 CDC; zero normal admission credit (`GA2D-GAP-01`) | | 7 | EXT-H | PCLK-to-HP AXI64 CDC | -| 8 | GA2D bridge placeholder | dedicated PCLK-to-HP AXI64/ID3 CDC; source held idle | +| 8 | GA2D | dedicated PCLK-to-HP AXI64/ID3 CDC; direct single-job private-AXI64 FILL/COPY DMA | Each source receives a fixed four-bit master prefix over a three-bit source ID. The crossbar maintains @@ -77,7 +76,7 @@ the integration RTL: | HP I-cache | all five memories | none | allowed | cache attributes preserved | | HP D-cache | all five memories | SRAM, SDRAM, QPI, OPI | denied | cache attributes preserved | | DMA, I/O A/B, LP gateway | all five memories | SRAM, SDRAM, QPI, OPI | denied | `AxCACHE=0` required | -| GA2D bridge placeholder | all five memories | SRAM, SDRAM, QPI, OPI | denied | `AxCACHE=0` required; source remains idle in P3 | +| GA2D | all five memories | SRAM, SDRAM, QPI, OPI | denied | `AxCACHE=0` required; direct FILL/COPY DMA, no hardware coherency | | JPEG | none | none | denied | zero normal admission credit (`GA2D-GAP-01`) | | EXT-H | all five memories within slot ACL | SRAM, SDRAM, QPI, OPI within slot ACL | denied | `AxCACHE=0` required | diff --git a/docs/commercial-soc-alignment.md b/docs/commercial-soc-alignment.md index 903de9d0..65b8f7d8 100644 --- a/docs/commercial-soc-alignment.md +++ b/docs/commercial-soc-alignment.md @@ -20,13 +20,14 @@ ecosystem of the referenced devices. Official sources were rechecked on ## Technical Selection Mini remains a small, non-coherent, performance-first SoC. Hazard3 is the -only root-management master. Vexii I/D, DMA, I/O gateways, the LP data gateway, -EXT-H, and an idle GA2D bridge placeholder use a nine-master native AXI64 data -plane with seven-bit global IDs. The placeholder is a dedicated -PCLK-to-HP AXI64/ID3 bridge whose source remains idle. Phase 3 activates a -separate PCLK `APB4_GA2D` register shell and resource-owned IRQ route, but -payload/DMA and pixel functions remain deferred. APB remains a separate control -plane. Every data initiator has a generated fixed identity, target permissions, +only root-management master. Vexii I/D, DMA, I/O gateways, the LP data gateway, EXT-H, and GA2D use a +nine-master native AXI64 data plane with seven-bit global IDs. GA2D is a +dedicated PCLK-to-HP AXI64/ID3 bridge carrying direct, single-job private-AXI64 +2D FILL/COPY DMA traffic; `APB4_GA2D` remains its separate PCLK control plane +and resource-owned IRQ route. It supports RGB565, RGB888, XRGB8888, and +ARGB8888, not CONVERT, BLEND, A8, in-place background composition, or +descriptor/ring/queue submission. APB remains a separate control plane. Every +data initiator has a generated fixed identity, target permissions, execute/cache-attribute rules, outstanding limit, QoS class, and fault attribution. diff --git a/docs/ip/fabric-monitor.md b/docs/ip/fabric-monitor.md index 5bea9bab..0146d386 100644 --- a/docs/ip/fabric-monitor.md +++ b/docs/ip/fabric-monitor.md @@ -3,10 +3,9 @@ [GA2D Phase 2](ga2d.md#phase-2---expand-axi64-fabric-and-resource-integration) adds master bank 8, version 1.1, and master bit 3 at `FAULT[12]`, preserving the existing packed fields. It also carries the complete four-bit master -identity through SYSCTRL. Bank 8 observes the dedicated PCLK-to-HP -AXI64/ID3 GA2D bridge placeholder, whose source is idle; it does not imply an -implemented GA2D payload/DMA or pixel function. Phase 3 adds an `APB4_GA2D` -register shell and resource-owned IRQ without changing the idle bridge source. +identity through SYSCTRL. Phase 4 makes bank 8 observe GA2D's dedicated +PCLK-to-HP AXI64/ID3 direct single-job FILL/COPY DMA traffic, controlled by +`APB4_GA2D` and its resource-owned IRQ route. ## Scope @@ -64,7 +63,7 @@ Nine master banks start at `0x100 + master * 0x20`. Master bank 8 occupies | `0x1C` | write high-water `[5:3]`, read high-water `[2:0]` | Master indices are HP I-cache, HP D-cache, central DMA, I/O gateway A, I/O -gateway B, LP data gateway, JPEG, EXT-H, and the idle GA2D bridge placeholder. +gateway B, LP data gateway, JPEG, EXT-H, and GA2D. JPEG remains visible with zero normal admission credit (`GA2D-GAP-01`), so an integration defect cannot silently disappear from accounting. diff --git a/docs/ip/resource-controller.md b/docs/ip/resource-controller.md index a0ed022e..3e83f92a 100644 --- a/docs/ip/resource-controller.md +++ b/docs/ip/resource-controller.md @@ -1,13 +1,15 @@ # Mini Resource Controller [GA2D Phase 2](ga2d.md#phase-2---expand-axi64-fabric-and-resource-integration) -adds resource 8 and version 1.1. It covers only the dedicated PCLK-to-HP -AXI64/ID3 GA2D bridge placeholder, whose source is idle. LP vector 32 / HP -PLIC source 11 and `APB4_GA2D` shell wiring are implemented in Phase 3. The -shell has IRQ-only capability; it does not create GA2D payload/DMA or pixel -work, and the bridge source remains idle. Its source-stop/drain-before-block -sequence is specified there. Existing resources and register fields are not -renumbered or reinterpreted. +adds resource 8 and version 1.1. Phase 4 uses its dedicated PCLK-to-HP +AXI64/ID3 bridge for the GA2D direct single-job private-AXI64 2D FILL/COPY DMA +engine. LP vector 32 / HP PLIC source 11 and `APB4_GA2D` routing retain their +Phase 3 allocations. The engine supports RGB565, RGB888, XRGB8888, and +ARGB8888 with snapshots, two-dimensional pitch, and byte edges; it does not +implement CONVERT, BLEND, A8, in-place background composition, or +descriptor/ring/queue submission. Its source-stop/drain-before-block sequence +is specified there. Existing resources and register fields are not renumbered +or reinterpreted. ## Scope @@ -30,11 +32,11 @@ Resource indices are fixed: | 5 | EXT-H | 3 | | 6 | JPEG | 9 | | 7 | APU | 10 | -| 8 | GA2D APB shell and idle bridge placeholder | 11 | +| 8 | GA2D direct FILL/COPY DMA engine | 11 | For resources 0 through 7, owner `0` routes the resource interrupt to the existing LP vector. Owner `1` removes it from LP and routes it to the listed HP -PLIC source. Resource 8 routes the GA2D shell raw IRQ to LP vector 32/external +PLIC source. Resource 8 routes the GA2D raw IRQ to LP vector 32/external ordinal 30 for owner `0`, or HP PLIC source 11 for owner `1`. Reset masks both routes. Hardware never delivers one resource interrupt to both owners. @@ -91,10 +93,10 @@ and a Linux platform driver remain software responsibilities. ## Delivery Boundary Central owner/lock, quiesce-gated handoff, cache request/ACK, fault IRQ, and -LP/HP IRQ routing are implemented and directed-tested. Phase 3 adds the GA2D -PCLK shell raw IRQ to resource 8, while its AXI bridge remains idle and it does -not provide a functional GA2D engine. `CONTROL.QUIESCE` blocks the corresponding -data-crossbar master and waits per-master outstanding zero; shared I/O gateways +LP/HP IRQ routing are implemented and directed-tested. Phase 4 adds the GA2D +direct FILL/COPY DMA engine's raw IRQ and active private AXI64 master to +resource 8. `CONTROL.QUIESCE` blocks the corresponding data-crossbar master +and waits per-master outstanding zero; shared I/O gateways are conservatively blocked as a pair. `CONTROL.RESET` also blocks new data and masks IRQ but is not yet connected to every peripheral engine's internal reset state machine. The APU-P1 shell is an exception: its index-7 quiesce diff --git a/docs/ip/sysctrl.md b/docs/ip/sysctrl.md index bf455260..bf0b2514 100644 --- a/docs/ip/sysctrl.md +++ b/docs/ip/sysctrl.md @@ -63,7 +63,7 @@ offset macros. | `0x01C` | `PLL_STATUS` | RO | Active profile, valid, busy, error/error reason, safe clock, lock, and capability. | | `0x020` | `USER_CORE_RESET` | RO/unsupported | Product reads all ones and rejects writes. MPW retains its reset mask. | | `0x024` | `USER_CORE_STATUS` | RO/unsupported | Product reports present=0, idle=1, and sticky unsupported-write error. | -| `0x028` | `FAULT_MASTER` | RO | First fault master, a four-bit AXI master ID (0..8); HP is 7 and the P3 GA2D shell's idle bridge placeholder is 8. The offset is unchanged. | +| `0x028` | `FAULT_MASTER` | RO | First fault master, a four-bit AXI master ID (0..8); HP is 7 and the active P4 GA2D private-DMA master is 8. The offset is unchanged. | | `0x02C` | `FAULT_DETAIL` | RO | First raw RIB response code. | | `0x040` | `PERF_CTRL` | RW | Bit 0 enable, bit 1 clear pulse, bit 2 snapshot pulse. | | `0x044`-`0x098` | `PERF_*_WAIT_{LO,HI}` | RO | Snapshot of management, user, central DMA, SDIO0, SDIO1, APB4, SDRAM, PSRAM, and flash wait counters. | diff --git a/docs/lp-hp-architecture.md b/docs/lp-hp-architecture.md index fc39ee86..df1efbb7 100644 --- a/docs/lp-hp-architecture.md +++ b/docs/lp-hp-architecture.md @@ -2,11 +2,10 @@ The frozen [GA2D specification](ip/ga2d.md) defines the phased GA2D delivery. Phase 2 expands the implemented platform to nine AXI64 masters with seven-bit -global IDs and Resource Controller entry 8. Its dedicated PCLK-to-HP -AXI64/ID3 bridge remains an idle placeholder. Phase 3 activates -`APB4_GA2D` as a PCLK register shell and connects its resource-owned IRQ, but -payload/DMA, pixel function, and START remain deferred. Existing hart, memory, -and source identities remain fixed. +global IDs and Resource Controller entry 8. Phase 4 activates its dedicated +PCLK-to-HP AXI64/ID3 bridge as a direct single-job private-AXI64 2D FILL/COPY +DMA engine controlled at `APB4_GA2D`, with resource-owned IRQ. Existing hart, +memory, and source identities remain fixed. ## Product contract @@ -97,7 +96,7 @@ and a Common FIFO preserves write-data order where AXI4 W has no ID. | LP data gateway | Hazard3 memory traffic, LP-to-HP CDC and upsizer | | JPEG | PCLK-to-HP AXI64 async bridge, ID prefix 6; zero normal admission credit (`GA2D-GAP-01`) | | EXT-H | PCLK-to-HP AXI64 async bridge, ID prefix 7 | -| GA2D bridge placeholder | dedicated PCLK-to-HP AXI64/ID3 async bridge; source held idle | +| GA2D | dedicated PCLK-to-HP AXI64/ID3 async bridge; direct single-job FILL/COPY DMA | Targets are SRAM, SDRAM, QPI PSRAM, OPI/HyperBus PSRAM, XPI/flash, and a finite-latency error slave. SRAM is a native AXI64, seven-bit-ID target in HP and @@ -156,9 +155,10 @@ Its IRQ is delivered to LP IRQ 28 or HP PLIC source 3 according to owner, never both. Software discovers it through ``. The Resource Controller at `0x2000_A000` is the central owner and IRQ authority -for DMA, USB2, SDIO0/1, SPI-SD, EXT-H, JPEG, APU, and the P3 GA2D shell. Resource -8 routes its raw IRQ exclusively to LP vector 32/external ordinal 30 or HP PLIC -source 11 according to owner, while the associated AXI bridge remains idle. +for DMA, USB2, SDIO0/1, SPI-SD, EXT-H, JPEG, APU, and the P4 GA2D DMA engine. +Resource 8 routes its raw IRQ exclusively to LP vector 32/external ordinal 30 +or HP PLIC source 11 according to owner, while its associated AXI bridge carries +only that engine's direct FILL/COPY traffic. Handoff requires idle, owner lock is sticky, and rejected handoffs raise LP IRQ 29. APU index 7 routes exclusively to LP IRQ31 or HP PLIC source10. The controller also carries the AON cache request/clean acknowledgement used before diff --git a/docs/soc-family-positioning.md b/docs/soc-family-positioning.md index 841ce3a8..b45dca0c 100644 --- a/docs/soc-family-positioning.md +++ b/docs/soc-family-positioning.md @@ -144,10 +144,13 @@ capabilities: - AXI4 uses a 32-bit LP control plane and a 9x6 native AXI64 HP data plane with seven-bit global IDs. Vexii I/D ports remain independent; DMA and I/O masters cross into HP and current memory frontends are reached through - 64-to-32 target adapters. Master 8 is a dedicated PCLK-to-HP AXI64/ID3 GA2D - bridge whose source is idle. `APB4_GA2D` is an active PCLK register shell - with resource-owned IRQ routing, while GA2D payload/DMA and pixel functions - remain unavailable. + 64-to-32 target adapters. Master 8 is the dedicated PCLK-to-HP AXI64/ID3 + GA2D bridge for direct single-job private-AXI64 2D FILL/COPY DMA. + `APB4_GA2D` provides its PCLK control plane and resource-owned IRQ routing. + The P4 engine supports RGB565, RGB888, XRGB8888, and ARGB8888, snapshots, + two-dimensional pitch, and byte edges; CONVERT, BLEND, A8, in-place + background composition, and descriptor/ring/queue submission remain + deferred. - Current external-memory targets serialize accepted AXI4 bursts into ordered scalar engine accesses. They do not yet combine a burst into a native SDRAM, PSRAM, flash, or SPI-SD physical transaction. diff --git a/publications/datasheets/diagram-circuits.json b/publications/datasheets/diagram-circuits.json index ef091dc1..7e4dd9e8 100644 --- a/publications/datasheets/diagram-circuits.json +++ b/publications/datasheets/diagram-circuits.json @@ -8213,7 +8213,7 @@ "zigzag-dir": "horizontal" } ], - "note": "Nine resources retain independent owner/lock/quiesce/reset state; resource 8 routes the P3 GA2D shell IRQ while its AXI bridge stays idle. IRQ steering and cache-clean coordination are control relationships, not a hardware cache-coherence implementation." + "note": "Nine resources retain independent owner/lock/quiesce/reset state; resource 8 routes the active P4 GA2D direct FILL/COPY DMA IRQ and controls its AXI64 lifecycle. IRQ steering and cache-clean coordination are control relationships, not a hardware cache-coherence implementation." }, "monitor": { "nodes": [ diff --git a/publications/datasheets/features.json b/publications/datasheets/features.json index 547a4e3c..7702e2b7 100644 --- a/publications/datasheets/features.json +++ b/publications/datasheets/features.json @@ -150,12 +150,12 @@ "Lifecycle-gated device identity reads." ], "resource": [ - "Nine independently managed shared resources; resource 8 owns the P3 GA2D shell IRQ and idle bridge placeholder.", + "Nine independently managed shared resources; resource 8 owns the active P4 GA2D direct FILL/COPY DMA and IRQ route.", "Owner locks, quiesce/reset and LP/HP IRQ routing.", "Explicit cache-maintenance handoff and sticky faults." ], "monitor": [ - "Nine master and six target observation banks; bank 8 observes the idle GA2D bridge behind the P3 shell.", + "Nine master and six target observation banks; bank 8 observes active P4 GA2D direct FILL/COPY DMA traffic.", "Saturating counters, snapshots and outstanding high-water marks.", "Sticky first-fault attribution and live isolation status." ], diff --git a/publications/datasheets/ip-catalog.json b/publications/datasheets/ip-catalog.json index 17570bda..4c43ae54 100644 --- a/publications/datasheets/ip-catalog.json +++ b/publications/datasheets/ip-catalog.json @@ -333,7 +333,7 @@ "sources": [ "docs/ip/resource-controller.md" ], - "summary": "LP-controlled ownership, quiesce, reset and LP/HP IRQ routing for nine resources; resource 8 owns the P3 GA2D shell IRQ and an idle AXI bridge placeholder." + "summary": "LP-controlled ownership, quiesce, reset and LP/HP IRQ routing for nine resources; resource 8 owns the active P4 GA2D direct FILL/COPY DMA and IRQ route." }, { "id": "monitor", @@ -344,7 +344,7 @@ "sources": [ "docs/ip/fabric-monitor.md" ], - "summary": "Nine-master/six-target snapshot counters and sticky fault attribution beside the HP data crossbar; bank 8 observes the idle GA2D bridge behind the P3 shell." + "summary": "Nine-master/six-target snapshot counters and sticky fault attribution beside the HP data crossbar; bank 8 observes active P4 GA2D direct FILL/COPY DMA traffic." }, { "id": "i2s", @@ -487,9 +487,15 @@ "sources": [ "docs/ip/ga2d.md", "rtl/ip/multimedia/apb4_ga2d.sv", + "rtl/ip/multimedia/ga2d_pkg.sv", + "rtl/ip/multimedia/ga2d_addr_gen.sv", + "rtl/ip/multimedia/ga2d_axi4_master.sv", + "rtl/ip/multimedia/ga2d_dma.sv", + "rtl/ip/multimedia/ga2d_pixel.sv", + "rtl/ip/multimedia/ga2d_core.sv", "rtl/ip/multimedia/ga2d_reg.sv" ], - "summary": "P3 APB4 register shell with resource-owned IRQ; START, 2D DMA, and pixel operations remain unavailable." + "summary": "P4 APB4-controlled direct single-job private-AXI64 2D FILL/COPY DMA with resource-owned IRQ, snapshots, pitch, and byte-edge support; no CONVERT, BLEND, A8, in-place background, or descriptor/ring/queue submission." }, { "id": "reserved", diff --git a/publications/datasheets/ips/resource.typ b/publications/datasheets/ips/resource.typ index 04bd7328..65046ade 100644 --- a/publications/datasheets/ips/resource.typ +++ b/publications/datasheets/ips/resource.typ @@ -12,14 +12,17 @@ #ip-reference("resource","resource",4,legacy:[ Nine resources are managed: central DMA, USB2, SDIO0, SDIO1, SPI-SD, EXT-H, JPEG, -APU, and the P3 GA2D shell with its idle AXI bridge placeholder. Resource 8 routes -the shell IRQ according to its current owner; the shell does not submit GA2D work. +APU, and the P4 GA2D direct single-job FILL/COPY DMA engine. Resource 8 routes +its IRQ according to its current owner and controls its private AXI64 lifecycle. +GA2D supports RGB565, RGB888, XRGB8888, and ARGB8888 with snapshots, pitch, and +byte edges. It does not provide CONVERT, BLEND, A8, in-place background composition, +descriptor/ring/queue submission, hardware cache coherency, or a Linux graphics driver. Ownership changes require quiesce, completion of accepted traffic and cache-maintenance handoff. HP can inspect allowed status but cannot take over root writes. An ownership grant does not bypass the target access policy or EXT-H address bounds. ]) -#heading(level:5,numbering:none,outlined:false,bookmarked:false)[GA2D Phase 3 Register Shell] +#heading(level:5,numbering:none,outlined:false,bookmarked:false)[GA2D Phase 4 Direct FILL/COPY DMA] #register-section("ga2d",4) #context metadata((kind:"ip-end",id:"resource",page:here().page())) diff --git a/publications/datasheets/register-annotations.json b/publications/datasheets/register-annotations.json index 641b9c39..bb4dbc30 100644 --- a/publications/datasheets/register-annotations.json +++ b/publications/datasheets/register-annotations.json @@ -1988,7 +1988,7 @@ "msb": 31, "description": "Fixed value 0x47413244 (GA2D).", "reset": "0x47413244", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, @@ -2003,110 +2003,110 @@ "msb": 15, "description": "ABI minor version.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "MAJOR", "lsb": 16, "msb": 31, - "description": "ABI major version; P3 implements major 1.", + "description": "ABI major version; P4 retains major 1.", "reset": "1", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.CAPABILITY": { "access": "RO", - "reset": "0x00000040", - "description": "P3 capability bitmap; only IRQ routing is advertised.", + "reset": "0x000003E3", + "description": "P4 capability bitmap; FILL, COPY, private DMA, IRQ, snapshots, two-dimensional pitch, and byte edges are advertised.", "fields": [ { "name": "FILL", "lsb": 0, "msb": 0, - "description": "Fill operation capability; clear in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Fill operation capability; set in P4.", + "reset": "1", + "expression": "reviewed P4 ABI" }, { "name": "COPY", "lsb": 1, "msb": 1, - "description": "Copy operation capability; clear in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Copy operation capability; set in P4.", + "reset": "1", + "expression": "reviewed P4 ABI" }, { "name": "CONVERT", "lsb": 2, "msb": 2, - "description": "Conversion operation capability; clear in P3.", + "description": "Conversion operation capability; clear in P4.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "BLEND", "lsb": 3, "msb": 3, - "description": "Blend operation capability; clear in P3.", + "description": "Blend operation capability; clear in P4.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "A8_MASK", "lsb": 4, "msb": 4, - "description": "A8 mask capability; clear in P3.", + "description": "A8 mask capability; clear in P4.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "PRIVATE_DMA", "lsb": 5, "msb": 5, - "description": "Private AXI DMA capability; clear in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Private AXI64 DMA capability; set in P4.", + "reset": "1", + "expression": "reviewed P4 ABI" }, { "name": "IRQ", "lsb": 6, "msb": 6, - "description": "Shell IRQ capability.", + "description": "Direct-engine IRQ capability.", "reset": "1", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "SNAPSHOT", "lsb": 7, "msb": 7, - "description": "Performance snapshot capability; clear in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Performance snapshot capability; set in P4.", + "reset": "1", + "expression": "reviewed P4 ABI" }, { "name": "TWO_DIMENSIONAL_PITCH", "lsb": 8, "msb": 8, - "description": "Two-dimensional pitch capability; clear in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Two-dimensional pitch capability; set in P4.", + "reset": "1", + "expression": "reviewed P4 ABI" }, { "name": "BYTE_EDGES", "lsb": 9, "msb": 9, - "description": "Byte-edge DMA capability; clear in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Byte-edge DMA capability; set in P4.", + "reset": "1", + "expression": "reviewed P4 ABI" }, { "name": "INPLACE_BACKGROUND", "lsb": 10, "msb": 10, - "description": "In-place background capability; clear in P3.", + "description": "In-place background capability; clear in P4.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2120,63 +2120,63 @@ }, "main.LIMITS": { "access": "RO", - "reset": "0x00000000", - "description": "DMA geometry limits; all fields are zero until private DMA is implemented.", + "reset": "0x08202010", + "description": "P4 private-AXI64 DMA geometry limits.", "fields": [ { "name": "MAX_BURST_BEATS", "lsb": 0, "msb": 7, - "description": "Maximum DMA burst beats; zero in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Maximum DMA burst beats; 16 in P4.", + "reset": "16", + "expression": "reviewed P4 ABI" }, { "name": "INPUT_FIFO_DEPTH", "lsb": 8, "msb": 15, - "description": "Input FIFO depth; zero in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Input FIFO depth; 32 beats in P4.", + "reset": "32", + "expression": "reviewed P4 ABI" }, { "name": "OUTPUT_FIFO_DEPTH", "lsb": 16, "msb": 23, - "description": "Output FIFO depth; zero in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Output FIFO depth; 32 beats in P4.", + "reset": "32", + "expression": "reviewed P4 ABI" }, { "name": "AXI_BEAT_BYTES", "lsb": 24, "msb": 31, - "description": "AXI DMA beat width; zero in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "AXI DMA beat width; 8 bytes in P4.", + "reset": "8", + "expression": "reviewed P4 ABI" } ] }, "main.COMMAND": { "access": "WO", "reset": "Not retained", - "description": "Single-command write port; START is unsupported in P3.", + "description": "Single-command write port for P4 direct jobs.", "fields": [ { "name": "START", "lsb": 0, "msb": 0, - "description": "Start a job; rejected with PSLVERR in P3.", + "description": "Start an idle, recovered P4 direct FILL or COPY job.", "reset": "Not retained", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "ABORT", "lsb": 1, "msb": 1, - "description": "Request a safe abort; idle P3 shell is a no-op.", + "description": "Request a safe abort; an idle engine is a no-op.", "reset": "Not retained", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "SOFT_RESET", @@ -2184,7 +2184,7 @@ "msb": 2, "description": "Clear local shell state only when safe idle and recovered.", "reset": "Not retained", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2199,23 +2199,23 @@ "main.STATUS": { "access": "RO", "reset": "Dynamic", - "description": "Live shell, lifecycle, and recovery status.", + "description": "Live P4 engine, lifecycle, and recovery status.", "fields": [ { "name": "BUSY", "lsb": 0, "msb": 0, - "description": "Live job-active status; clear in the P3 shell.", + "description": "Live job-active status.", "reset": "Dynamic", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "DRAINING", "lsb": 1, "msb": 1, - "description": "Live drain status; clear in the P3 shell.", + "description": "Live drain status while abort, error, or resource stop retires accepted traffic.", "reset": "Dynamic", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "QUIESCED", @@ -2223,7 +2223,7 @@ "msb": 2, "description": "Resource stop has reached safe source idle and block acknowledgement.", "reset": "Dynamic", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "DATA_READY", @@ -2231,23 +2231,23 @@ "msb": 3, "description": "Qualified bridge data readiness.", "reset": "Dynamic", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "DONE", "lsb": 4, "msb": 4, - "description": "Last job completed; clear in the P3 shell.", + "description": "Last direct job completed successfully.", "reset": "Dynamic", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "ABORTED", "lsb": 5, "msb": 5, - "description": "Last job aborted; clear in the P3 shell.", + "description": "Last direct job reached safe abort completion.", "reset": "Dynamic", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "ERROR", @@ -2255,7 +2255,7 @@ "msb": 6, "description": "Sticky first-error validity.", "reset": "Dynamic", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "RECOVERY_REQUIRED", @@ -2263,7 +2263,7 @@ "msb": 7, "description": "Bridge clear, reset epoch, or data-readiness recovery is unresolved.", "reset": "Dynamic", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2286,7 +2286,7 @@ "msb": 0, "description": "Successful terminal job event.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "ERROR", @@ -2294,7 +2294,7 @@ "msb": 1, "description": "First-error event.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "ABORT_DONE", @@ -2302,7 +2302,7 @@ "msb": 2, "description": "Safe abort completion event.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2325,7 +2325,7 @@ "msb": 0, "description": "Enable DONE raw IRQ contribution.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "ERROR", @@ -2333,7 +2333,7 @@ "msb": 1, "description": "Enable ERROR raw IRQ contribution.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "ABORT_DONE", @@ -2341,7 +2341,7 @@ "msb": 2, "description": "Enable ABORT_DONE raw IRQ contribution.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2364,7 +2364,7 @@ "msb": 0, "description": "Set DONE state for route testing.", "reset": "Not retained", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "ERROR", @@ -2372,7 +2372,7 @@ "msb": 1, "description": "Set ERROR state for route testing.", "reset": "Not retained", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "ABORT_DONE", @@ -2380,7 +2380,7 @@ "msb": 2, "description": "Set ABORT_DONE state for route testing.", "reset": "Not retained", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2403,7 +2403,7 @@ "msb": 0, "description": "First-error record is valid.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "CODE", @@ -2411,7 +2411,7 @@ "msb": 7, "description": "First-error code.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "STAGE", @@ -2419,7 +2419,7 @@ "msb": 11, "description": "First-error processing stage.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "AXI_RESPONSE", @@ -2427,7 +2427,7 @@ "msb": 13, "description": "Captured AXI response for a bus error.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2450,7 +2450,7 @@ "msb": 31, "description": "Captured first-error byte address.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, @@ -2465,22 +2465,22 @@ "msb": 31, "description": "PCLK no-progress timeout budget.", "reset": "0x00100000", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.JOB_CONFIG": { "access": "RW idle", "reset": "0x00000000", - "description": "Shadow job operation selector; no operation capability is advertised in P3.", + "description": "Shadow job operation selector; P4 supports FILL and COPY only.", "fields": [ { "name": "OPERATION", "lsb": 0, "msb": 1, - "description": "0: FILL; 1: COPY; 2: CONVERT; 3: BLEND.", + "description": "0: FILL; 1: COPY; 2: CONVERT and 3: BLEND remain unsupported in P4.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2495,15 +2495,15 @@ "main.GLOBAL_ALPHA": { "access": "RW idle", "reset": "0x000000FF", - "description": "Shadow global alpha value.", + "description": "Shadow global alpha value; unused by P4 FILL/COPY.", "fields": [ { "name": "VALUE", "lsb": 0, "msb": 7, - "description": "Global alpha value.", + "description": "Global alpha value; ignored by P4 FILL/COPY.", "reset": "0xFF", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2526,7 +2526,7 @@ "msb": 31, "description": "AARRGGBB constant color.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, @@ -2541,7 +2541,7 @@ "msb": 15, "description": "Job width in pixels.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "HEIGHT", @@ -2549,7 +2549,7 @@ "msb": 31, "description": "Job height in pixels.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, @@ -2564,7 +2564,7 @@ "msb": 31, "description": "Foreground byte address.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, @@ -2579,7 +2579,7 @@ "msb": 31, "description": "Foreground bytes between rows.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, @@ -2592,9 +2592,9 @@ "name": "FORMAT", "lsb": 0, "msb": 2, - "description": "0: RGB565; 1: RGB888; 2: XRGB8888; 3: ARGB8888; 4: A8.", + "description": "0: RGB565; 1: RGB888; 2: XRGB8888; 3: ARGB8888 are supported in P4; 4: A8 is unsupported.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2609,45 +2609,45 @@ "main.BG_ADDRESS": { "access": "RW idle", "reset": "0x00000000", - "description": "Shadow background byte address.", + "description": "Shadow background byte address; unused by P4 FILL/COPY.", "fields": [ { "name": "ADDRESS", "lsb": 0, "msb": 31, - "description": "Background byte address.", + "description": "Background byte address; unused by P4 FILL/COPY.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.BG_PITCH": { "access": "RW idle", "reset": "0x00000000", - "description": "Shadow background row pitch in bytes.", + "description": "Shadow background row pitch in bytes; unused by P4 FILL/COPY.", "fields": [ { "name": "PITCH", "lsb": 0, "msb": 31, - "description": "Background bytes between rows.", + "description": "Background bytes between rows; unused by P4 FILL/COPY.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.BG_FORMAT": { "access": "RW idle", "reset": "0x00000000", - "description": "Shadow background format selector.", + "description": "Shadow background format selector; unused by P4 FILL/COPY.", "fields": [ { "name": "FORMAT", "lsb": 0, "msb": 2, - "description": "0: RGB565; 1: RGB888; 2: XRGB8888; 3: ARGB8888; 4: A8.", + "description": "0: RGB565; 1: RGB888; 2: XRGB8888; 3: ARGB8888 are supported in P4; 4: A8 is unsupported.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2670,7 +2670,7 @@ "msb": 31, "description": "Destination byte address.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, @@ -2685,7 +2685,7 @@ "msb": 31, "description": "Destination bytes between rows.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, @@ -2698,9 +2698,9 @@ "name": "FORMAT", "lsb": 0, "msb": 2, - "description": "0: RGB565; 1: RGB888; 2: XRGB8888; 3: ARGB8888; 4: A8.", + "description": "0: RGB565; 1: RGB888; 2: XRGB8888; 3: ARGB8888 are supported in P4; 4: A8 is unsupported.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2715,15 +2715,15 @@ "main.PERF_SNAPSHOT": { "access": "WO", "reset": "Not retained", - "description": "Performance snapshot command; rejected with PSLVERR in P3.", + "description": "Performance snapshot command for P4 direct-job counters.", "fields": [ { "name": "SNAPSHOT", "lsb": 0, "msb": 0, - "description": "Capture live counters; unsupported in P3.", + "description": "Capture live P4 direct-job counters into the read bank.", "reset": "Not retained", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2737,16 +2737,16 @@ }, "main.FORMAT_CAPABILITY": { "access": "RO", - "reset": "0x00000000", - "description": "Format support bitmap; all fields are zero in P3.", + "reset": "0x000F000F", + "description": "P4 format bitmap: foreground and destination RGB565/RGB888/XRGB8888/ARGB8888 bits are set; background bits are clear.", "fields": [ { "name": "FOREGROUND", "lsb": 0, "msb": 4, - "description": "Foreground format capability bits; zero in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Foreground format capability bits; RGB565/RGB888/XRGB8888/ARGB8888 are set in P4.", + "reset": "15", + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2760,9 +2760,9 @@ "name": "BACKGROUND", "lsb": 8, "msb": 12, - "description": "Background format capability bits; zero in P3.", + "description": "Background format capability bits; clear in P4 because BLEND is unavailable.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2776,9 +2776,9 @@ "name": "DESTINATION", "lsb": 16, "msb": 20, - "description": "Destination format capability bits; zero in P3.", - "reset": "0", - "expression": "reviewed P3 ABI" + "description": "Destination format capability bits; RGB565/RGB888/XRGB8888/ARGB8888 are set in P4.", + "reset": "15", + "expression": "reviewed P4 ABI" }, { "name": "Reserved", @@ -2793,7 +2793,7 @@ "main.SNAP_CYCLES_LO": { "access": "RO", "reset": "0x00000000", - "description": "Low 32 bits of the active-job PCLK cycle snapshot; zero in P3.", + "description": "Low 32 bits of the P4 active-job PCLK cycle snapshot.", "fields": [ { "name": "VALUE", @@ -2801,14 +2801,14 @@ "msb": 31, "description": "Low cycle-counter snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_CYCLES_HI": { "access": "RO", "reset": "0x00000000", - "description": "High 32 bits of the active-job PCLK cycle snapshot; zero in P3.", + "description": "High 32 bits of the P4 active-job PCLK cycle snapshot.", "fields": [ { "name": "VALUE", @@ -2816,14 +2816,14 @@ "msb": 31, "description": "High cycle-counter snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_READ_BYTES_LO": { "access": "RO", "reset": "0x00000000", - "description": "Low 32 bits of successful logical input bytes; zero in P3.", + "description": "Low 32 bits of P4 successful logical input bytes.", "fields": [ { "name": "VALUE", @@ -2831,14 +2831,14 @@ "msb": 31, "description": "Low read-byte snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_READ_BYTES_HI": { "access": "RO", "reset": "0x00000000", - "description": "High 32 bits of successful logical input bytes; zero in P3.", + "description": "High 32 bits of P4 successful logical input bytes.", "fields": [ { "name": "VALUE", @@ -2846,14 +2846,14 @@ "msb": 31, "description": "High read-byte snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_WRITE_BYTES_LO": { "access": "RO", "reset": "0x00000000", - "description": "Low 32 bits of acknowledged output bytes; zero in P3.", + "description": "Low 32 bits of P4 acknowledged output bytes.", "fields": [ { "name": "VALUE", @@ -2861,14 +2861,14 @@ "msb": 31, "description": "Low write-byte snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_WRITE_BYTES_HI": { "access": "RO", "reset": "0x00000000", - "description": "High 32 bits of acknowledged output bytes; zero in P3.", + "description": "High 32 bits of P4 acknowledged output bytes.", "fields": [ { "name": "VALUE", @@ -2876,14 +2876,14 @@ "msb": 31, "description": "High write-byte snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_READ_STALL_LO": { "access": "RO", "reset": "0x00000000", - "description": "Low 32 bits of read-channel stall cycles; zero in P3.", + "description": "Low 32 bits of P4 read-channel stall cycles.", "fields": [ { "name": "VALUE", @@ -2891,14 +2891,14 @@ "msb": 31, "description": "Low read-stall snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_READ_STALL_HI": { "access": "RO", "reset": "0x00000000", - "description": "High 32 bits of read-channel stall cycles; zero in P3.", + "description": "High 32 bits of P4 read-channel stall cycles.", "fields": [ { "name": "VALUE", @@ -2906,14 +2906,14 @@ "msb": 31, "description": "High read-stall snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_WRITE_STALL_LO": { "access": "RO", "reset": "0x00000000", - "description": "Low 32 bits of write-channel stall cycles; zero in P3.", + "description": "Low 32 bits of P4 write-channel stall cycles.", "fields": [ { "name": "VALUE", @@ -2921,14 +2921,14 @@ "msb": 31, "description": "Low write-stall snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_WRITE_STALL_HI": { "access": "RO", "reset": "0x00000000", - "description": "High 32 bits of write-channel stall cycles; zero in P3.", + "description": "High 32 bits of P4 write-channel stall cycles.", "fields": [ { "name": "VALUE", @@ -2936,14 +2936,14 @@ "msb": 31, "description": "High write-stall snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_PIPE_STALL_LO": { "access": "RO", "reset": "0x00000000", - "description": "Low 32 bits of pixel-pipeline stall cycles; zero in P3.", + "description": "Low 32 bits of P4 pixel-pipeline stall cycles.", "fields": [ { "name": "VALUE", @@ -2951,14 +2951,14 @@ "msb": 31, "description": "Low pipeline-stall snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_PIPE_STALL_HI": { "access": "RO", "reset": "0x00000000", - "description": "High 32 bits of pixel-pipeline stall cycles; zero in P3.", + "description": "High 32 bits of P4 pixel-pipeline stall cycles.", "fields": [ { "name": "VALUE", @@ -2966,14 +2966,14 @@ "msb": 31, "description": "High pipeline-stall snapshot word.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" } ] }, "main.SNAP_LINES_DONE": { "access": "RO", "reset": "0x00000000", - "description": "Completed destination rows; zero in P3.", + "description": "Completed P4 destination rows.", "fields": [ { "name": "LINES_DONE", @@ -2981,7 +2981,7 @@ "msb": 15, "description": "Completed destination row count.", "reset": "0", - "expression": "reviewed P3 ABI" + "expression": "reviewed P4 ABI" }, { "name": "Reserved", diff --git a/publications/datasheets/register-profiles.json b/publications/datasheets/register-profiles.json index 23bedc33..0f1bc4bb 100644 --- a/publications/datasheets/register-profiles.json +++ b/publications/datasheets/register-profiles.json @@ -974,7 +974,7 @@ "groups": [ { "id": "main", - "title": "Phase 3 register shell", + "title": "Phase 4 direct FILL/COPY DMA", "pattern": "APB4_GA2D__.+", "strip": "^APB4_GA2D__", "base": 0, @@ -983,7 +983,13 @@ } ], "support": [ - "rtl/ip/multimedia/apb4_ga2d.sv" + "rtl/ip/multimedia/apb4_ga2d.sv", + "rtl/ip/multimedia/ga2d_pkg.sv", + "rtl/ip/multimedia/ga2d_addr_gen.sv", + "rtl/ip/multimedia/ga2d_axi4_master.sv", + "rtl/ip/multimedia/ga2d_dma.sv", + "rtl/ip/multimedia/ga2d_pixel.sv", + "rtl/ip/multimedia/ga2d_core.sv" ] }, "dvp": { diff --git a/publications/datasheets/system-reference.json b/publications/datasheets/system-reference.json index 38b89fcc..d24e1495 100644 --- a/publications/datasheets/system-reference.json +++ b/publications/datasheets/system-reference.json @@ -590,7 +590,7 @@ "id": "resource", "title": "Resource Controller", "implementation": "Partial", - "scope": "Nine independently managed shared resources; resource 8 owns the P3 GA2D shell IRQ and an idle AXI bridge placeholder.", + "scope": "Nine independently managed shared resources; resource 8 owns the active P4 GA2D direct FILL/COPY DMA engine and IRQ route.", "dependencies": "No additional off-chip component implied; use the selected clock/reset and access policy.", "hal": "Published API and syntax example; see the IP chapter.", "linux": "Native driver support not established in this publication.", @@ -610,7 +610,7 @@ "id": "monitor", "title": "Fabric Monitor", "implementation": "Integrated", - "scope": "Nine master and six target observation banks; bank 8 observes the idle GA2D bridge behind the P3 shell.", + "scope": "Nine master and six target observation banks; bank 8 observes active P4 GA2D direct FILL/COPY DMA traffic.", "dependencies": "No additional off-chip component implied; use the selected clock/reset and access policy.", "hal": "Published API and syntax example; see the IP chapter.", "linux": "Native driver support not established in this publication.", @@ -3632,11 +3632,17 @@ "mode": "PRODUCT", "region": "APB4_GA2D", "catalog": "ga2d", - "qualification": "Phase 3 exposes only the GA2D APB4 shell and its resource-owned IRQ route. START, private DMA, and pixel operations remain unavailable.", + "qualification": "Phase 4 implements GA2D as a direct single-job private-AXI64 2D FILL/COPY DMA engine with resource-owned IRQ, snapshots, pitch, and byte-edge support. RGB565, RGB888, XRGB8888, and ARGB8888 are supported; CONVERT, BLEND, A8, in-place background composition, and descriptor/ring/queue submission are unavailable.", "sources": [ "docs/ip/ga2d.md", "rtl/ip/multimedia/ga2d_reg.sv", - "rtl/ip/multimedia/apb4_ga2d.sv" + "rtl/ip/multimedia/apb4_ga2d.sv", + "rtl/ip/multimedia/ga2d_pkg.sv", + "rtl/ip/multimedia/ga2d_addr_gen.sv", + "rtl/ip/multimedia/ga2d_axi4_master.sv", + "rtl/ip/multimedia/ga2d_dma.sv", + "rtl/ip/multimedia/ga2d_pixel.sv", + "rtl/ip/multimedia/ga2d_core.sv" ] }, { @@ -4902,11 +4908,20 @@ 3, 4, 9, - 10 + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 17 ], "expected_entries": 4, "meanings": { - "0": "Expected Linux-ready mailbox event received; successful terminal status.", + "0": "Linux-ready and P4 GA2D mailbox/cache-clean handoff completed; successful terminal status.", "1": "UART initialization failed; terminal failure is written directly without requiring a printable console.", "2": "HP hold/reset/debug-selection/presence prerequisite failed.", "3": "SDRAM did not reach the required ready/error-free state within the firmware wait.", @@ -4915,8 +4930,15 @@ "6": "DMA and software copy/CRC fallback failed for bundle entry index 1.", "7": "DMA and software copy/CRC fallback failed for bundle entry index 2.", "8": "DMA and software copy/CRC fallback failed for bundle entry index 3.", - "9": "Mailbox clear, HP release request or released-state check failed.", - "10": "Nonzero-sequence mailbox message did not carry the expected Linux-ready event/argument." + "9": "Mailbox probe, LP-owned idle check, GA2D handoff to HP, or HP-owned idle confirmation failed.", + "10": "LP interrupt clear, HP release request, or released-state check failed.", + "11": "The expected Linux-ready mailbox event/argument/sequence was not received.", + "12": "The P4 GA2D start mailbox command could not be sent to HP.", + "13": "The expected P4 GA2D FILL/COPY completion mailbox event/argument/sequence was not received.", + "14": "GA2D did not return to the expected HP-owned safe idle state after the P4 job.", + "15": "The request to hold HP after P4 GA2D completion returned an unexpected error.", + "16": "Cache-clean request, P4 GA2D cache mailbox event, clean acknowledgement, or HP-held confirmation failed.", + "17": "GA2D handoff back to LP or LP-owned safe-idle confirmation failed." } } }, @@ -5321,16 +5343,25 @@ "code": 15, "trigger": "rs_ci_smoke_external_irq(", "coverage": "Exercise Xh3irq ordinals 0/15/16/29/30/31/32/61, invalid 62, priority/masking, no-handler masking, timer/software coexistence, and handler entry/return.", - "boundary": "Only the CSR-enabled build exercises dispatch; the physical P3 GA2D shell route is covered by the following stage." + "boundary": "Only the CSR-enabled build exercises dispatch; the physical P4 GA2D DMA route is covered by the following stage." }, { - "id": "ga2d-irq", - "title": "GA2D shell IRQ route", + "id": "monitor-start", + "title": "Start Fabric Monitor", + "kind": "test-fail", + "code": 13, + "trigger": "rs_ci_smoke_fabric_monitor_start(", + "coverage": "Clear and enable monitoring before the GA2D direct-DMA and later memory traffic.", + "boundary": "Code 13 also identifies the later monitor-observation stage." + }, + { + "id": "ga2d-fill-copy-dma", + "title": "GA2D direct FILL/COPY DMA and IRQ route", "kind": "test-fail", "code": 15, "trigger": "rs_ci_smoke_ga2d_irq(", - "coverage": "Discover the P3 shell and use IRQ_TEST to exercise its resource-owned LP external route.", - "boundary": "Does not submit a GA2D datapath job or claim private DMA or pixel-operation availability." + "coverage": "Discover P4 capability, submit direct single-job FILL/COPY DMA work through master 8, verify the resource-owned LP external completion route, and inspect the destination.", + "boundary": "Exercises direct private-AXI64 FILL/COPY work only; it does not cover CONVERT, BLEND, A8, in-place background composition, descriptor/ring/queue submission, cache coherency, a Linux graphics driver, or physical performance." }, { "id": "apu", @@ -5341,15 +5372,6 @@ "coverage": "Compare APU discovery capabilities and ABI digest.", "boundary": "No production codec job is submitted." }, - { - "id": "monitor-start", - "title": "Start Fabric Monitor", - "kind": "test-fail", - "code": 13, - "trigger": "rs_ci_smoke_fabric_monitor_start(", - "coverage": "Clear and enable monitoring before memory traffic.", - "boundary": "Code 13 also identifies the later monitor-observation stage." - }, { "id": "sram", "title": "SRAM discovery and memory accesses", diff --git a/publications/retrieval_reference.py b/publications/retrieval_reference.py index 1c5879fd..f993c441 100644 --- a/publications/retrieval_reference.py +++ b/publications/retrieval_reference.py @@ -210,8 +210,8 @@ def boot_status(root: Path, spec: dict) -> dict: or int(count[0]) != spec["expected_entries"] or set(terminals) != {("RS_TEST_FAILED", "1"), ("RS_TEST_PASSED", "0")}): raise ValueError("HP boot diagnostic branches changed") - codes = [0, 1, *direct, *range(int(base[0]), int(base[0]) + int(count[0]))] - if len(set(codes)) != len(codes) or set(map(str, codes)) != set(spec["meanings"]): + codes = sorted({0, 1, *direct, *range(int(base[0]), int(base[0]) + int(count[0]))}) + if set(map(str, codes)) != set(spec["meanings"]): raise ValueError("HP boot code coverage or scope mismatch") hal = (root / "crt/src/hal/sysctrl.c").read_text(encoding="utf-8") rtl = (root / "rtl/ip/peripheral/sysctrl_define.svh").read_text(encoding="utf-8") diff --git a/rtl/README.md b/rtl/README.md index 00c5de53..32b3cbf4 100644 --- a/rtl/README.md +++ b/rtl/README.md @@ -119,13 +119,14 @@ ownership. Its implemented limits, handwritten ABI, measured performance, and commercial release gates are documented in [Baseline JPEG Codec](../docs/ip/jpeg.md). -The Mini GA2D graphics accelerator has a frozen architecture and ABI. Phase 3 -activates `APB4_GA2D` as a PCLK register shell with ownership-routed IRQ -support; it does not implement START, 2D DMA, pixel operations, or AXI payload -traffic. Its approved phases first expanded the LP interrupt platform and -native AXI64 fabric, then activated the former GA reservation before later -fill/copy/convert/alpha engine work. The exact allocations, Common reuse -limits, lifecycle, HAL, and evidence gates are in [GA2D](../docs/ip/ga2d.md). +The Mini GA2D graphics accelerator has a frozen architecture and ABI. Phase 4 +implements `APB4_GA2D` as a PCLK-controlled, direct single-job private-AXI64 +2D FILL/COPY DMA engine with ownership-routed IRQ support, snapshots, byte +pitches, and byte-edge transfers. RGB565, RGB888, XRGB8888, and ARGB8888 are +implemented. CONVERT, BLEND, A8, in-place background composition, and +descriptor/ring/queue submission remain unavailable. The earlier LP interrupt +platform and native AXI64 fabric expansion retain their fixed allocations. +The exact lifecycle, HAL, and evidence gates are in [GA2D](../docs/ip/ga2d.md). The Mini Audio Processing Unit has a frozen coreless architecture. APU-P5 adds the private AXI4 DMA and scheduler, microcode loader/sequencer, 112 KiB local diff --git a/rtl/ip/multimedia/apb4_ga2d.sv b/rtl/ip/multimedia/apb4_ga2d.sv index 47672357..e468948e 100644 --- a/rtl/ip/multimedia/apb4_ga2d.sv +++ b/rtl/ip/multimedia/apb4_ga2d.sv @@ -13,30 +13,55 @@ module apb4_ga2d ( input logic bridge_clear_busy_i, input logic [7:0] bridge_epoch_i, input logic data_ready_i, + input logic [1:0] mem_pad_mode_i, apb4_if.slave apb4, + axi4_if.master ga2d_axi4, output logic idle_o, + output logic core_safe_idle_o, output logic irq_o // verilog_format: on ); ga2d_pkg::ga2d_config_t s_config; logic [7:0] s_epoch_seen_d, s_epoch_seen_q; - logic s_epoch_changed; - logic s_quiesced; - logic s_data_ready; - logic s_recovery_required; - logic s_soft_reset; - logic s_abort; - logic s_reg_idle; - logic s_unused; + logic s_epoch_changed; + logic s_quiesced; + logic s_data_ready; + logic s_recovery_required; + logic s_soft_reset; + logic s_abort; + logic s_start; + logic unused_snapshot; + logic s_reg_idle; + logic s_core_busy; + logic s_core_draining; + logic s_core_done; + logic s_core_aborted; + logic s_core_err; + logic s_core_recovery_required; + logic s_core_safe_idle; + logic [ 2:0] s_core_irq_event; + logic s_core_err_valid; + logic [ 6:0] s_core_err_code; + logic [ 3:0] s_core_err_stage; + logic [ 1:0] s_core_err_axi_response; + logic [31:0] s_core_err_address; + logic [63:0] s_core_cycles; + logic [63:0] s_core_read_bytes; + logic [63:0] s_core_write_bytes; + logic [63:0] s_core_read_stalls; + logic [63:0] s_core_write_stalls; + logic [63:0] s_core_pipe_stalls; + logic [15:0] s_core_lines_done; assign s_epoch_seen_d = bridge_epoch_i; assign s_epoch_changed = bridge_epoch_i != s_epoch_seen_q; assign s_quiesced = (resource_quiesce_i || resource_reset_i) && source_stop_i && source_safe_idle_i && block_ack_i; assign s_data_ready = data_ready_i && !resource_quiesce_i && !resource_reset_i; - assign s_recovery_required = bridge_clear_busy_i || !data_ready_i || s_epoch_changed; + assign s_recovery_required = s_core_recovery_required || bridge_clear_busy_i || + !data_ready_i || s_epoch_changed; assign idle_o = s_reg_idle && source_safe_idle_i; - assign s_unused = ^s_config ^ s_soft_reset ^ s_abort; + assign core_safe_idle_o = s_core_safe_idle; dffr #( .DATA_WIDTH(8) @@ -50,25 +75,70 @@ module apb4_ga2d ( ga2d_reg u_ga2d_reg ( .clk_i (clk_i), .rst_n_i (rst_n_i), - .busy_i (1'b0), - .draining_i (1'b0), + .busy_i (s_core_busy), + .draining_i (s_core_draining), .quiesced_i (s_quiesced), .data_ready_i (s_data_ready), .recovery_required_i (s_recovery_required), - .idle_i (source_safe_idle_i), - .done_i (1'b0), - .aborted_i (1'b0), - .irq_event_i (3'd0), - .error_valid_i (1'b0), - .error_code_i (7'd0), - .error_stage_i (4'd0), - .error_axi_response_i(2'd0), - .error_address_i (32'd0), + .idle_i (s_core_safe_idle && source_safe_idle_i), + .done_i (s_core_done), + .aborted_i (s_core_aborted), + .error_i (s_core_err), + .irq_event_i (s_core_irq_event), + .error_valid_i (s_core_err_valid), + .error_code_i (s_core_err_code), + .error_stage_i (s_core_err_stage), + .error_axi_response_i(s_core_err_axi_response), + .error_address_i (s_core_err_address), + .cycles_i (s_core_cycles), + .read_bytes_i (s_core_read_bytes), + .write_bytes_i (s_core_write_bytes), + .read_stalls_i (s_core_read_stalls), + .write_stalls_i (s_core_write_stalls), + .pipe_stalls_i (s_core_pipe_stalls), + .lines_done_i (s_core_lines_done), .apb4 (apb4), .config_o (s_config), + .start_o (s_start), .soft_reset_o (s_soft_reset), .abort_o (s_abort), + .snapshot_o (unused_snapshot), .idle_o (s_reg_idle), .irq_o (irq_o) ); + + ga2d_core u_ga2d_core ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .start_i (s_start), + .abort_i (s_abort), + .soft_reset_i (s_soft_reset), + .resource_stop_i (resource_quiesce_i || resource_reset_i || source_stop_i), + .bridge_clear_busy_i (bridge_clear_busy_i), + .bridge_epoch_i (bridge_epoch_i), + .data_ready_i (s_data_ready), + .mem_pad_mode_i (mem_pad_mode_i), + .config_i (s_config), + .busy_o (s_core_busy), + .draining_o (s_core_draining), + .done_o (s_core_done), + .aborted_o (s_core_aborted), + .error_o (s_core_err), + .recovery_required_o (s_core_recovery_required), + .safe_idle_o (s_core_safe_idle), + .irq_event_o (s_core_irq_event), + .error_valid_o (s_core_err_valid), + .error_code_o (s_core_err_code), + .error_stage_o (s_core_err_stage), + .error_axi_response_o(s_core_err_axi_response), + .error_address_o (s_core_err_address), + .cycles_o (s_core_cycles), + .read_bytes_o (s_core_read_bytes), + .write_bytes_o (s_core_write_bytes), + .read_stalls_o (s_core_read_stalls), + .write_stalls_o (s_core_write_stalls), + .pipe_stalls_o (s_core_pipe_stalls), + .lines_done_o (s_core_lines_done), + .axi4 (ga2d_axi4) + ); endmodule diff --git a/rtl/ip/multimedia/ga2d_addr_gen.sv b/rtl/ip/multimedia/ga2d_addr_gen.sv new file mode 100644 index 00000000..9bcfaa78 --- /dev/null +++ b/rtl/ip/multimedia/ga2d_addr_gen.sv @@ -0,0 +1,41 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +module ga2d_addr_gen ( + input logic [ 2:0] read_address_lsb_i, + input logic [31:0] read_remaining_i, + input logic [31:0] write_address_i, + input logic [ 7:0] write_byte_i, + output logic [ 3:0] read_bytes_o, + output logic [ 2:0] read_size_o, + output logic [31:0] write_address_o, + output logic [63:0] write_data_o, + output logic [ 7:0] write_strobe_o +); + logic [3:0] s_read_to_alignment; + + always_comb begin + s_read_to_alignment = 4'd8 - {1'b0, read_address_lsb_i}; + read_bytes_o = 4'd1; + if ((read_address_lsb_i == 3'd0) && (read_remaining_i >= 32'd8)) begin + read_bytes_o = 4'd8; + end else if ((read_address_lsb_i[1:0] == 2'd0) && (s_read_to_alignment >= 4'd4) && + (read_remaining_i >= 32'd4)) begin + read_bytes_o = 4'd4; + end else if ((read_address_lsb_i[0] == 1'b0) && (s_read_to_alignment >= 4'd2) && + (read_remaining_i >= 32'd2)) begin + read_bytes_o = 4'd2; + end + + unique case (read_bytes_o) + 4'd8: read_size_o = 3'd3; + 4'd4: read_size_o = 3'd2; + 4'd2: read_size_o = 3'd1; + default: read_size_o = 3'd0; + endcase + + write_address_o = {write_address_i[31:3], 3'b000}; + write_data_o = ({56'd0, write_byte_i} << {write_address_i[2:0], 3'b000}); + write_strobe_o = (8'b0000_0001 << write_address_i[2:0]); + end +endmodule diff --git a/rtl/ip/multimedia/ga2d_axi4_master.sv b/rtl/ip/multimedia/ga2d_axi4_master.sv new file mode 100644 index 00000000..132ec94e --- /dev/null +++ b/rtl/ip/multimedia/ga2d_axi4_master.sv @@ -0,0 +1,283 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +module ga2d_axi4_master ( + input logic clk_i, + input logic rst_n_i, + input logic clear_i, + input logic read_request_valid_i, + output logic read_request_ready_o, + input logic [31:0] read_address_i, + input logic [ 2:0] read_size_i, + input logic [ 4:0] read_beats_i, + input logic read_stop_i, + output logic read_request_accept_o, + output logic read_address_presented_o, + output logic read_request_cancel_o, + output logic read_response_valid_o, + input logic read_response_ready_i, + output logic [63:0] read_data_o, + output logic [ 1:0] read_response_o, + output logic read_protocol_error_o, + output logic read_response_terminal_o, + output logic [31:0] read_response_address_o, + input logic write_request_valid_i, + output logic write_request_ready_o, + input logic [31:0] write_address_i, + input logic [ 4:0] write_beats_i, + input logic write_stop_i, + output logic write_request_accept_o, + output logic write_address_presented_o, + output logic write_request_cancel_o, + input logic write_payload_valid_i, + input logic [63:0] write_data_i, + input logic [ 7:0] write_strobe_i, + output logic write_payload_accept_o, + output logic write_response_valid_o, + input logic write_response_ready_i, + output logic [ 1:0] write_response_o, + output logic write_protocol_error_o, + output logic [31:0] write_response_address_o, + output logic read_busy_o, + output logic write_busy_o, + output logic idle_o, + output logic read_stall_o, + output logic write_stall_o, + axi4_if.master axi4 +); + typedef enum logic [1:0] { + ReadIdle, + ReadAddress, + ReadData + } read_state_e; + typedef enum logic [1:0] { + WriteIdle, + WriteAddress, + WriteData, + WriteResponse + } write_state_e; + + read_state_e s_read_state_q; + write_state_e s_write_state_q; + logic [31:0] s_read_addr_q; + logic [ 2:0] s_read_size_q; + logic [ 4:0] s_read_beats_q; + logic [ 4:0] s_read_beats_left_q; + logic s_rd_ar_presented_q; + logic s_read_resp_seen_q; + logic [31:0] s_write_addr_q; + logic [ 4:0] s_write_beats_q; + logic [ 4:0] s_write_beats_left_q; + logic s_wr_aw_presented_q; + logic s_write_resp_seen_q; + logic s_read_rsp_accept; + logic s_write_rsp_accept; + logic s_read_last_expected; + logic s_read_rsp_terminal; + logic s_read_protocol_fault_q; + logic s_write_protocol_fault_q; + + assign read_request_ready_o = (s_read_state_q == ReadIdle) && !clear_i; + assign write_request_ready_o = (s_write_state_q == WriteIdle) && !clear_i; + assign read_request_accept_o = read_request_valid_i && read_request_ready_o && !read_stop_i && + (read_beats_i != 5'd0) && (read_beats_i <= 5'd16); + assign read_address_presented_o = s_rd_ar_presented_q || + ((s_read_state_q == ReadAddress) && axi4.arvalid); + assign read_request_cancel_o = (s_read_state_q == ReadAddress) && read_stop_i && + !s_rd_ar_presented_q; + assign write_request_accept_o = write_request_valid_i && write_request_ready_o && !write_stop_i && + (write_beats_i != 5'd0) && (write_beats_i <= 5'd16); + assign write_address_presented_o = s_wr_aw_presented_q || + ((s_write_state_q == WriteAddress) && axi4.awvalid); + assign write_request_cancel_o = (s_write_state_q == WriteAddress) && write_stop_i && + !s_wr_aw_presented_q; + + assign read_response_valid_o = (s_read_state_q == ReadData) && + s_read_resp_seen_q && axi4.rvalid && !clear_i; + assign read_data_o = axi4.rdata; + assign read_response_o = axi4.rresp; + assign s_read_last_expected = s_read_beats_left_q == 5'd1; + assign read_protocol_error_o = (axi4.rid != '0) || (axi4.rlast != s_read_last_expected); + // A malformed response cannot safely retire local ID 0: only a matching + // final beat resolves the owner, and a prior protocol fault requires clear_i. + assign s_read_rsp_terminal = !s_read_protocol_fault_q && !read_protocol_error_o && + axi4.rlast && s_read_last_expected; + assign read_response_terminal_o = read_response_valid_o && s_read_rsp_terminal; + assign read_response_address_o = s_read_addr_q + + ({27'd0, s_read_beats_q - s_read_beats_left_q} << + s_read_size_q); + assign s_read_rsp_accept = read_response_valid_o && read_response_ready_i; + + assign write_response_valid_o = (s_write_state_q == WriteResponse) && + s_write_resp_seen_q && axi4.bvalid && !clear_i; + assign write_response_o = axi4.bresp; + assign write_protocol_error_o = axi4.bid != '0; + assign write_response_address_o = s_write_addr_q; + assign s_write_rsp_accept = write_response_valid_o && write_response_ready_i; + + assign read_busy_o = s_read_state_q != ReadIdle; + assign write_busy_o = s_write_state_q != WriteIdle; + assign idle_o = !read_busy_o && !write_busy_o; + assign read_stall_o = (axi4.arvalid && !axi4.arready) || (axi4.rvalid && !axi4.rready); + assign write_stall_o = (axi4.awvalid && !axi4.awready) || (axi4.wvalid && !axi4.wready) || + (axi4.bvalid && !axi4.bready); + + assign axi4.awid = '0; + assign axi4.awaddr = s_write_addr_q; + assign axi4.awlen = {3'd0, s_write_beats_q} - 1'b1; + assign axi4.awsize = 3'd3; + assign axi4.awburst = 2'b01; + assign axi4.awlock = 1'b0; + assign axi4.awcache = 4'd0; + assign axi4.awprot = 3'd0; + assign axi4.awqos = 4'd0; + assign axi4.awregion = 4'd0; + assign axi4.awuser = '0; + assign axi4.awvalid = (s_write_state_q == WriteAddress) && !clear_i && + (!write_stop_i || s_wr_aw_presented_q); + assign axi4.wdata = write_data_i; + assign axi4.wstrb = write_strobe_i; + assign axi4.wlast = s_write_beats_left_q == 5'd1; + assign axi4.wuser = '0; + assign axi4.wvalid = (s_write_state_q == WriteData) && write_payload_valid_i && !clear_i; + assign write_payload_accept_o = axi4.wvalid && axi4.wready; + assign axi4.bready = (s_write_state_q == WriteResponse) && write_response_ready_i && + s_write_resp_seen_q && !s_write_protocol_fault_q && !clear_i; + + assign axi4.arid = '0; + assign axi4.araddr = s_read_addr_q; + assign axi4.arlen = {3'd0, s_read_beats_q} - 1'b1; + assign axi4.arsize = s_read_size_q; + assign axi4.arburst = 2'b01; + assign axi4.arlock = 1'b0; + assign axi4.arcache = 4'd0; + assign axi4.arprot = 3'd0; + assign axi4.arqos = 4'd0; + assign axi4.arregion = 4'd0; + assign axi4.aruser = '0; + assign axi4.arvalid = (s_read_state_q == ReadAddress) && !clear_i && + (!read_stop_i || s_rd_ar_presented_q); + assign axi4.rready = (s_read_state_q == ReadData) && read_response_ready_i && + s_read_resp_seen_q && !s_read_protocol_fault_q && !clear_i; + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_read_state_q <= ReadIdle; + s_read_addr_q <= '0; + s_read_size_q <= '0; + s_read_beats_q <= '0; + s_read_beats_left_q <= '0; + s_rd_ar_presented_q <= 1'b0; + s_read_resp_seen_q <= 1'b0; + s_read_protocol_fault_q <= 1'b0; + s_write_state_q <= WriteIdle; + s_write_addr_q <= '0; + s_write_beats_q <= '0; + s_write_beats_left_q <= '0; + s_wr_aw_presented_q <= 1'b0; + s_write_resp_seen_q <= 1'b0; + s_write_protocol_fault_q <= 1'b0; + end else if (clear_i) begin + s_read_state_q <= ReadIdle; + s_write_state_q <= WriteIdle; + s_rd_ar_presented_q <= 1'b0; + s_wr_aw_presented_q <= 1'b0; + s_read_resp_seen_q <= 1'b0; + s_write_resp_seen_q <= 1'b0; + s_read_protocol_fault_q <= 1'b0; + s_write_protocol_fault_q <= 1'b0; + end else begin + unique case (s_read_state_q) + ReadIdle: begin + if (read_request_accept_o) begin + // A stop-qualified acceptance commits an AR presentation for the next AXI cycle. + s_read_addr_q <= read_address_i; + s_read_size_q <= read_size_i; + s_read_beats_q <= read_beats_i; + s_read_beats_left_q <= read_beats_i; + s_rd_ar_presented_q <= 1'b1; + s_read_resp_seen_q <= 1'b0; + s_read_protocol_fault_q <= 1'b0; + s_read_state_q <= ReadAddress; + end + end + ReadAddress: begin + if (read_request_cancel_o) begin + s_read_state_q <= ReadIdle; + end else begin + s_rd_ar_presented_q <= 1'b1; + end + if (axi4.arvalid && axi4.arready) begin + s_read_state_q <= ReadData; + end + end + ReadData: begin + if (axi4.rvalid) begin + s_read_resp_seen_q <= 1'b1; + end + if (s_read_rsp_accept) begin + if (read_protocol_error_o) begin + s_read_protocol_fault_q <= 1'b1; + end else if (s_read_rsp_terminal) begin + s_read_state_q <= ReadIdle; + s_rd_ar_presented_q <= 1'b0; + s_read_resp_seen_q <= 1'b0; + end else begin + s_read_beats_left_q <= s_read_beats_left_q - 1'b1; + end + end + end + default: s_read_state_q <= ReadIdle; + endcase + + unique case (s_write_state_q) + WriteIdle: begin + if (write_request_accept_o) begin + // A stop-qualified acceptance commits an AW presentation for the next AXI cycle. + s_write_addr_q <= write_address_i; + s_write_beats_q <= write_beats_i; + s_write_beats_left_q <= write_beats_i; + s_wr_aw_presented_q <= 1'b1; + s_write_resp_seen_q <= 1'b0; + s_write_protocol_fault_q <= 1'b0; + s_write_state_q <= WriteAddress; + end + end + WriteAddress: begin + if (write_request_cancel_o) begin + s_write_state_q <= WriteIdle; + end else begin + s_wr_aw_presented_q <= 1'b1; + end + if (axi4.awvalid && axi4.awready) begin + s_write_state_q <= WriteData; + end + end + WriteData: begin + if (write_payload_accept_o) begin + if (s_write_beats_left_q == 5'd1) begin + s_write_state_q <= WriteResponse; + end else begin + s_write_beats_left_q <= s_write_beats_left_q - 1'b1; + end + end + end + WriteResponse: begin + if (axi4.bvalid) begin + s_write_resp_seen_q <= 1'b1; + end + if (s_write_rsp_accept) begin + if (write_protocol_error_o) begin + s_write_protocol_fault_q <= 1'b1; + end else begin + s_write_state_q <= WriteIdle; + s_wr_aw_presented_q <= 1'b0; + s_write_resp_seen_q <= 1'b0; + end + end + end + default: s_write_state_q <= WriteIdle; + endcase + end + end +endmodule diff --git a/rtl/ip/multimedia/ga2d_core.sv b/rtl/ip/multimedia/ga2d_core.sv new file mode 100644 index 00000000..ea52a030 --- /dev/null +++ b/rtl/ip/multimedia/ga2d_core.sv @@ -0,0 +1,576 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "ga2d_define.svh" +`include "mmap_define.svh" + +module ga2d_core ( + // All state is PCLK-local. bridge_epoch_i is consumed only at the existing + // PCLK source endpoint, where a changed epoch invalidates local ownership. + input logic clk_i, + input logic rst_n_i, + input logic start_i, + input logic abort_i, + input logic soft_reset_i, + input logic resource_stop_i, + input logic bridge_clear_busy_i, + input logic [ 7:0] bridge_epoch_i, + input logic data_ready_i, + input logic [ 1:0] mem_pad_mode_i, + /* verilator lint_off UNUSEDSIGNAL */ + input ga2d_pkg::ga2d_config_t config_i, + /* verilator lint_on UNUSEDSIGNAL */ + output logic busy_o, + output logic draining_o, + output logic done_o, + output logic aborted_o, + output logic error_o, + output logic recovery_required_o, + output logic safe_idle_o, + output logic [ 2:0] irq_event_o, + output logic error_valid_o, + output logic [ 6:0] error_code_o, + output logic [ 3:0] error_stage_o, + output logic [ 1:0] error_axi_response_o, + output logic [31:0] error_address_o, + output logic [63:0] cycles_o, + output logic [63:0] read_bytes_o, + output logic [63:0] write_bytes_o, + output logic [63:0] read_stalls_o, + output logic [63:0] write_stalls_o, + output logic [63:0] pipe_stalls_o, + output logic [15:0] lines_done_o, + axi4_if.master axi4 +); + import ga2d_pkg::ga2d_config_t; + + typedef enum logic [1:0] { + Idle, + Validate, + Run, + Drain + } state_e; + + typedef struct packed { + logic [31:0] timeout_cycles; + logic [31:0] job_config; + logic [31:0] color; + logic [31:0] size; + logic [31:0] fg_address; + logic [31:0] fg_pitch; + logic [31:0] fg_format; + logic [31:0] dst_address; + logic [31:0] dst_pitch; + logic [31:0] dst_format; + } ga2d_p4_job_t; + + localparam logic [2:0] IrqDoneEvent = 3'b001 << `APB4_GA2D__IRQ_DONE; + localparam logic [2:0] IrqErrorEvent = 3'b001 << `APB4_GA2D__IRQ_ERROR; + localparam logic [2:0] IrqAbortDoneEvent = 3'b001 << `APB4_GA2D__IRQ_ABORT_DONE; + + state_e s_state_q; + ga2d_p4_job_t s_job_q; + logic [ 7:0] s_epoch_seen_q; + logic s_recovery_q; + logic s_recovery_seen_not_ready_q; + logic s_done_q; + logic s_aborted_q; + logic s_abort_reqed_q; + logic s_err_q; + logic [31:0] s_timeout_count_q; + logic [63:0] s_cycles_q; + logic [63:0] s_read_bytes_q; + logic [63:0] s_write_bytes_q; + logic [63:0] s_read_stalls_q; + logic [63:0] s_write_stalls_q; + logic [63:0] s_pipe_stalls_q; + logic [15:0] s_lines_done_q; + logic [ 2:0] s_irq_event_q; + logic s_err_valid_q; + logic [ 6:0] s_err_code_q; + logic [ 3:0] s_err_stage_q; + logic [ 1:0] s_err_axi_rsp_q; + logic [31:0] s_err_addr_q; + logic s_fill_format_valid; + logic [ 2:0] s_fill_bytes_per_pixel; + logic [31:0] s_fill_pixel; + logic s_validation_err; + logic [ 6:0] s_validation_code; + logic [ 3:0] s_validation_stage; + logic [31:0] s_validation_addr; + logic [ 2:0] s_dst_bpp; + logic [ 2:0] s_fg_bpp; + logic [31:0] s_dst_row_bytes; + logic [31:0] s_fg_row_bytes; + logic [64:0] s_dst_end; + logic [64:0] s_fg_end; + logic s_epoch_changed; + logic s_dma_clear; + logic s_dma_start; + logic s_dma_stop; + logic unused_dma_busy; + logic s_dma_draining; + logic s_dma_idle; + logic s_dma_done; + logic s_dma_aborted; + logic s_dma_err_valid; + logic [ 6:0] s_dma_err_code; + logic [ 3:0] s_dma_err_stage; + logic [ 1:0] s_dma_err_rsp; + logic [31:0] s_dma_err_addr; + logic s_dma_progress; + logic [ 7:0] s_dma_read_bytes; + logic [ 7:0] s_dma_write_bytes; + logic s_dma_line_done; + logic s_dma_read_stall; + logic s_dma_write_stall; + logic s_dma_pipe_stall; + logic s_timeout_expired; + logic s_runtime_stop; + + function automatic logic [2:0] bytes_per_pixel(input logic [2:0] format_i); + unique case (format_i) + `APB4_GA2D__FORMAT_RGB565: return 3'd2; + `APB4_GA2D__FORMAT_RGB888: return 3'd3; + `APB4_GA2D__FORMAT_XRGB8888, `APB4_GA2D__FORMAT_ARGB8888: return 3'd4; + default: return '0; + endcase + endfunction + + function automatic logic naturally_aligned(input logic [31:0] value_i, input logic [2:0] bytes_i); + unique case (bytes_i) + 3'd2: return value_i[0] == 1'b0; + 3'd3: return 1'b1; + 3'd4: return value_i[1:0] == 2'd0; + default: return 1'b0; + endcase + endfunction + + function automatic logic [64:0] plane_end(input logic [31:0] base_i, input logic [31:0] pitch_i, + input logic [31:0] row_bytes_i, + input logic [15:0] height_i); + logic [64:0] s_rows; + logic [64:0] s_pitch; + begin + s_rows = {49'd0, height_i} - 1'b1; + s_pitch = {33'd0, pitch_i}; + return {33'd0, base_i} + (s_rows * s_pitch) + {33'd0, row_bytes_i}; + end + endfunction + + function automatic logic range_allowed(input logic [31:0] base_i, input logic [64:0] end_i, + input logic write_i); + logic [64:0] s_sram_end; + logic [64:0] s_sdram_end; + logic [64:0] s_psram_end; + logic [64:0] s_opipsram_end; + logic [64:0] s_xpi_end; + begin + s_sram_end = {33'd0, `SOC_ADDR_SRAM_BASE} + {33'd0, `SOC_ADDR_SRAM_SIZE}; + s_sdram_end = {33'd0, `SOC_ADDR_SDRAM_BASE} + {33'd0, `SOC_ADDR_SDRAM_SIZE}; + s_psram_end = {33'd0, `SOC_ADDR_PSRAM_BASE} + {33'd0, `SOC_ADDR_PSRAM_SIZE}; + s_opipsram_end = {33'd0, `SOC_ADDR_OPIPSRAM_BASE} + {33'd0, `SOC_ADDR_OPIPSRAM_SIZE}; + s_xpi_end = {33'd0, `SOC_ADDR_XPI_BASE} + {33'd0, `SOC_ADDR_XPI_SIZE}; + if ((base_i >= `SOC_ADDR_SRAM_BASE) && (end_i <= s_sram_end)) begin + return 1'b1; + end + if ((base_i >= `SOC_ADDR_SDRAM_BASE) && (end_i <= s_sdram_end)) begin + return 1'b1; + end + if ((mem_pad_mode_i == 2'd1) && (base_i >= `SOC_ADDR_PSRAM_BASE) && + (end_i <= s_psram_end)) begin + return 1'b1; + end + if ((mem_pad_mode_i == 2'd2) && (base_i >= `SOC_ADDR_OPIPSRAM_BASE) && + (end_i <= s_opipsram_end)) begin + return 1'b1; + end + if (!write_i && (base_i >= `SOC_ADDR_XPI_BASE) && (end_i <= s_xpi_end)) begin + return 1'b1; + end + return 1'b0; + end + endfunction + + function automatic logic [63:0] saturating_add(input logic [63:0] value_i, + input logic [7:0] increment_i); + logic [64:0] s_sum; + begin + s_sum = {1'b0, value_i} + {57'd0, increment_i}; + return s_sum[64] ? 64'hffff_ffff_ffff_ffff : s_sum[63:0]; + end + endfunction + + function automatic logic [15:0] saturating_line_add(input logic [15:0] value_i, + input logic increment_i); + if (increment_i && (&value_i)) begin + return value_i; + end + return value_i + increment_i; + endfunction + + assign s_dst_bpp = bytes_per_pixel(s_job_q.dst_format[2:0]); + assign s_fg_bpp = bytes_per_pixel(s_job_q.fg_format[2:0]); + assign s_dst_row_bytes = s_job_q.size[15:0] * s_dst_bpp; + assign s_fg_row_bytes = s_job_q.size[15:0] * s_fg_bpp; + assign s_dst_end = plane_end( + s_job_q.dst_address, s_job_q.dst_pitch, s_dst_row_bytes, s_job_q.size[31:16] + ); + assign s_fg_end = plane_end( + s_job_q.fg_address, s_job_q.fg_pitch, s_fg_row_bytes, s_job_q.size[31:16] + ); + assign s_epoch_changed = bridge_epoch_i != s_epoch_seen_q; + assign s_dma_clear = bridge_clear_busy_i || s_epoch_changed; + assign s_dma_start = (s_state_q == Validate) && !s_validation_err; + assign s_runtime_stop = abort_i || resource_stop_i || bridge_clear_busy_i || s_epoch_changed; + assign s_dma_stop = (s_state_q == Drain) || ((s_state_q == Run) && s_runtime_stop); + assign s_timeout_expired = (s_state_q == Run) && !s_dma_progress && + (s_timeout_count_q == (s_job_q.timeout_cycles - 1'b1)); + + ga2d_pixel u_pixel ( + .color_i (s_job_q.color), + .format_i (s_job_q.dst_format[2:0]), + .pixel_o (s_fill_pixel), + .bytes_per_pixel_o(s_fill_bytes_per_pixel), + .format_valid_o (s_fill_format_valid) + ); + + ga2d_dma u_dma ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .clear_i (s_dma_clear), + .start_i (s_dma_start), + .stop_i (s_dma_stop), + .fill_i (s_job_q.job_config[1:0] == `APB4_GA2D__OP_FILL), + .width_i (s_job_q.size[15:0]), + .height_i (s_job_q.size[31:16]), + .bytes_per_pixel_i (s_fill_bytes_per_pixel), + .foreground_address_i (s_job_q.fg_address), + .foreground_pitch_i (s_job_q.fg_pitch), + .destination_address_i(s_job_q.dst_address), + .destination_pitch_i (s_job_q.dst_pitch), + .fill_pixel_i (s_fill_pixel), + .busy_o (unused_dma_busy), + .draining_o (s_dma_draining), + .idle_o (s_dma_idle), + .done_o (s_dma_done), + .aborted_o (s_dma_aborted), + .error_valid_o (s_dma_err_valid), + .error_code_o (s_dma_err_code), + .error_stage_o (s_dma_err_stage), + .error_response_o (s_dma_err_rsp), + .error_address_o (s_dma_err_addr), + .progress_o (s_dma_progress), + .read_bytes_o (s_dma_read_bytes), + .write_bytes_o (s_dma_write_bytes), + .line_done_o (s_dma_line_done), + .read_stall_o (s_dma_read_stall), + .write_stall_o (s_dma_write_stall), + .pipe_stall_o (s_dma_pipe_stall), + .axi4 (axi4) + ); + + always_comb begin + s_validation_err = 1'b0; + s_validation_code = `APB4_GA2D__ERROR_NONE; + s_validation_stage = `APB4_GA2D__ERROR_STAGE_VALIDATE; + s_validation_addr = '0; + if ((s_job_q.size[15:0] == 16'd0) || (s_job_q.size[31:16] == 16'd0)) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_INVALID_SIZE; + end else if ((s_job_q.job_config[1:0] != `APB4_GA2D__OP_FILL) && + (s_job_q.job_config[1:0] != `APB4_GA2D__OP_COPY)) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_INVALID_FORMAT; + end else if ((s_dst_bpp == 3'd0) || !s_fill_format_valid || + ((s_job_q.job_config[1:0] == `APB4_GA2D__OP_COPY) && + ((s_fg_bpp == 3'd0) || + (s_job_q.fg_format[2:0] != s_job_q.dst_format[2:0])))) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_INVALID_FORMAT; + end else if (!naturally_aligned(s_job_q.dst_address, s_dst_bpp)) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_INVALID_ALIGNMENT; + s_validation_addr = s_job_q.dst_address; + end else if ((s_job_q.dst_pitch < s_dst_row_bytes) || !naturally_aligned( + s_job_q.dst_pitch, s_dst_bpp + )) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_INVALID_PITCH; + s_validation_addr = s_job_q.dst_address; + end else if (s_dst_end > 65'h1_0000_0000) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_ADDRESS_OVERFLOW; + s_validation_addr = s_job_q.dst_address; + end else if (!range_allowed(s_job_q.dst_address, s_dst_end, 1'b1)) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_ADDRESS_RANGE; + s_validation_addr = s_job_q.dst_address; + end else if ((s_job_q.job_config[1:0] == `APB4_GA2D__OP_COPY) && !naturally_aligned( + s_job_q.fg_address, s_fg_bpp + )) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_INVALID_ALIGNMENT; + s_validation_addr = s_job_q.fg_address; + end else if ((s_job_q.job_config[1:0] == `APB4_GA2D__OP_COPY) && + ((s_job_q.fg_pitch < s_fg_row_bytes) || + !naturally_aligned( + s_job_q.fg_pitch, s_fg_bpp + ))) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_INVALID_PITCH; + s_validation_addr = s_job_q.fg_address; + end else if ((s_job_q.job_config[1:0] == `APB4_GA2D__OP_COPY) && + (s_fg_end > 65'h1_0000_0000)) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_ADDRESS_OVERFLOW; + s_validation_addr = s_job_q.fg_address; + end else if ((s_job_q.job_config[1:0] == `APB4_GA2D__OP_COPY) && !range_allowed( + s_job_q.fg_address, s_fg_end, 1'b0 + )) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_ADDRESS_RANGE; + s_validation_addr = s_job_q.fg_address; + end else if ((s_job_q.job_config[1:0] == `APB4_GA2D__OP_COPY) && + ({33'd0, s_job_q.dst_address} < s_fg_end) && + ({33'd0, s_job_q.fg_address} < s_dst_end)) begin + s_validation_err = 1'b1; + s_validation_code = `APB4_GA2D__ERROR_OVERLAP; + s_validation_addr = s_job_q.dst_address; + end + end + + assign busy_o = s_state_q != Idle; + assign draining_o = (s_state_q == Drain) || ((s_state_q == Run) && s_runtime_stop) || + s_dma_draining; + assign done_o = s_done_q; + assign aborted_o = s_aborted_q; + assign error_o = s_err_q; + assign recovery_required_o = s_recovery_q || bridge_clear_busy_i || !data_ready_i || + s_epoch_changed; + assign safe_idle_o = (s_state_q == Idle) && s_dma_idle; + assign irq_event_o = s_irq_event_q; + assign error_valid_o = s_err_valid_q; + assign error_code_o = s_err_code_q; + assign error_stage_o = s_err_stage_q; + assign error_axi_response_o = s_err_axi_rsp_q; + assign error_address_o = s_err_addr_q; + assign cycles_o = s_cycles_q; + assign read_bytes_o = s_read_bytes_q; + assign write_bytes_o = s_write_bytes_q; + assign read_stalls_o = s_read_stalls_q; + assign write_stalls_o = s_write_stalls_q; + assign pipe_stalls_o = s_pipe_stalls_q; + assign lines_done_o = s_lines_done_q; + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_state_q <= Idle; + s_job_q <= '0; + s_epoch_seen_q <= '0; + s_recovery_q <= 1'b0; + s_recovery_seen_not_ready_q <= 1'b0; + s_done_q <= 1'b0; + s_aborted_q <= 1'b0; + s_abort_reqed_q <= 1'b0; + s_err_q <= 1'b0; + s_timeout_count_q <= '0; + s_cycles_q <= '0; + s_read_bytes_q <= '0; + s_write_bytes_q <= '0; + s_read_stalls_q <= '0; + s_write_stalls_q <= '0; + s_pipe_stalls_q <= '0; + s_lines_done_q <= '0; + s_irq_event_q <= '0; + s_err_valid_q <= 1'b0; + s_err_code_q <= '0; + s_err_stage_q <= '0; + s_err_axi_rsp_q <= '0; + s_err_addr_q <= '0; + end else begin + s_epoch_seen_q <= bridge_epoch_i; + s_irq_event_q <= '0; + s_err_valid_q <= 1'b0; + + if (!data_ready_i) begin + s_recovery_seen_not_ready_q <= 1'b1; + end + if (s_epoch_changed || bridge_clear_busy_i) begin + s_recovery_q <= 1'b1; + end else if (s_recovery_q && s_recovery_seen_not_ready_q && data_ready_i && + (s_state_q == Idle) && s_dma_idle) begin + s_recovery_q <= 1'b0; + s_recovery_seen_not_ready_q <= 1'b0; + end + + if (soft_reset_i) begin + s_state_q <= Idle; + s_done_q <= 1'b0; + s_aborted_q <= 1'b0; + s_abort_reqed_q <= 1'b0; + s_err_q <= 1'b0; + s_timeout_count_q <= '0; + s_cycles_q <= '0; + s_read_bytes_q <= '0; + s_write_bytes_q <= '0; + s_read_stalls_q <= '0; + s_write_stalls_q <= '0; + s_pipe_stalls_q <= '0; + s_lines_done_q <= '0; + end else begin + if (s_state_q != Idle) begin + s_cycles_q <= saturating_add(s_cycles_q, 8'd1); + if (s_dma_read_stall) begin + s_read_stalls_q <= saturating_add(s_read_stalls_q, 8'd1); + end + if (s_dma_write_stall) begin + s_write_stalls_q <= saturating_add(s_write_stalls_q, 8'd1); + end + if (s_dma_pipe_stall) begin + s_pipe_stalls_q <= saturating_add(s_pipe_stalls_q, 8'd1); + end + s_read_bytes_q <= saturating_add(s_read_bytes_q, s_dma_read_bytes); + s_write_bytes_q <= saturating_add(s_write_bytes_q, s_dma_write_bytes); + s_lines_done_q <= saturating_line_add(s_lines_done_q, s_dma_line_done); + if ((s_state_q == Validate) || s_dma_progress) begin + s_timeout_count_q <= '0; + end else if (s_timeout_count_q != 32'hffff_ffff) begin + s_timeout_count_q <= s_timeout_count_q + 1'b1; + end + end else begin + s_timeout_count_q <= '0; + end + + unique case (s_state_q) + Idle: begin + if (start_i && data_ready_i && !resource_stop_i && !s_recovery_q && + !bridge_clear_busy_i && !s_epoch_changed) begin + s_job_q.timeout_cycles <= config_i.timeout_cycles; + s_job_q.job_config <= config_i.job_config; + s_job_q.color <= config_i.color; + s_job_q.size <= config_i.size; + s_job_q.fg_address <= config_i.fg_address; + s_job_q.fg_pitch <= config_i.fg_pitch; + s_job_q.fg_format <= config_i.fg_format; + s_job_q.dst_address <= config_i.dst_address; + s_job_q.dst_pitch <= config_i.dst_pitch; + s_job_q.dst_format <= config_i.dst_format; + s_done_q <= 1'b0; + s_aborted_q <= 1'b0; + s_abort_reqed_q <= 1'b0; + s_err_q <= 1'b0; + s_cycles_q <= '0; + s_read_bytes_q <= '0; + s_write_bytes_q <= '0; + s_read_stalls_q <= '0; + s_write_stalls_q <= '0; + s_pipe_stalls_q <= '0; + s_lines_done_q <= '0; + s_timeout_count_q <= '0; + s_state_q <= Validate; + end + end + Validate: begin + if (s_validation_err) begin + s_err_q <= 1'b1; + s_err_valid_q <= 1'b1; + s_err_code_q <= s_validation_code; + s_err_stage_q <= s_validation_stage; + s_err_axi_rsp_q <= '0; + s_err_addr_q <= s_validation_addr; + s_irq_event_q <= IrqErrorEvent; + s_state_q <= Idle; + end else if (s_runtime_stop) begin + s_abort_reqed_q <= abort_i || resource_stop_i; + s_state_q <= Drain; + end else begin + s_state_q <= Run; + end + end + Run: begin + if (s_epoch_changed || bridge_clear_busy_i) begin + s_err_q <= 1'b1; + s_err_valid_q <= 1'b1; + s_err_code_q <= `APB4_GA2D__ERROR_EPOCH_LOST; + s_err_stage_q <= `APB4_GA2D__ERROR_STAGE_LIFECYCLE; + s_err_axi_rsp_q <= '0; + s_err_addr_q <= '0; + s_irq_event_q <= IrqErrorEvent; + s_state_q <= Drain; + end else if (s_dma_err_valid) begin + s_abort_reqed_q <= abort_i || resource_stop_i; + s_err_q <= 1'b1; + s_err_valid_q <= 1'b1; + s_err_code_q <= s_dma_err_code; + s_err_stage_q <= s_dma_err_stage; + s_err_axi_rsp_q <= s_dma_err_rsp; + s_err_addr_q <= s_dma_err_addr; + s_irq_event_q <= IrqErrorEvent; + if (s_dma_err_code == `APB4_GA2D__ERROR_AXI_PROTOCOL) begin + s_recovery_q <= 1'b1; + end + s_state_q <= Drain; + end else if (abort_i || resource_stop_i) begin + s_abort_reqed_q <= 1'b1; + s_state_q <= Drain; + end else if (s_timeout_expired) begin + s_err_q <= 1'b1; + s_err_valid_q <= 1'b1; + s_err_code_q <= `APB4_GA2D__ERROR_TIMEOUT; + s_err_stage_q <= `APB4_GA2D__ERROR_STAGE_LIFECYCLE; + s_err_axi_rsp_q <= '0; + s_err_addr_q <= '0; + s_irq_event_q <= IrqErrorEvent; + s_recovery_q <= 1'b1; + s_state_q <= Drain; + end else if (s_dma_done) begin + s_done_q <= 1'b1; + s_irq_event_q <= IrqDoneEvent; + s_state_q <= Idle; + end + end + Drain: begin + // A later abort is still meaningful while an earlier error drains. + // Include the live inputs below to cover the final-retirement cycle. + if (abort_i || resource_stop_i) begin + s_abort_reqed_q <= 1'b1; + end + if (s_dma_err_valid && (s_dma_err_code == `APB4_GA2D__ERROR_AXI_PROTOCOL)) begin + s_recovery_q <= 1'b1; + end + if (s_dma_err_valid && !s_err_q) begin + s_err_q <= 1'b1; + s_err_valid_q <= 1'b1; + s_err_code_q <= s_dma_err_code; + s_err_stage_q <= s_dma_err_stage; + s_err_axi_rsp_q <= s_dma_err_rsp; + s_err_addr_q <= s_dma_err_addr; + s_irq_event_q <= IrqErrorEvent; + end + if (s_dma_idle) begin + if (s_dma_aborted || s_recovery_q || s_abort_reqed_q || abort_i || + resource_stop_i) begin + if (s_abort_reqed_q || abort_i || resource_stop_i) begin + s_aborted_q <= 1'b1; + s_irq_event_q <= s_irq_event_q | IrqAbortDoneEvent; + end + s_state_q <= Idle; + end + end + end + default: begin + s_err_q <= 1'b1; + s_err_valid_q <= 1'b1; + s_err_code_q <= `APB4_GA2D__ERROR_INTERNAL; + s_err_stage_q <= `APB4_GA2D__ERROR_STAGE_LIFECYCLE; + s_err_axi_rsp_q <= '0; + s_err_addr_q <= '0; + s_irq_event_q <= IrqErrorEvent; + s_state_q <= Drain; + end + endcase + end + end + end +endmodule diff --git a/rtl/ip/multimedia/ga2d_define.svh b/rtl/ip/multimedia/ga2d_define.svh index 4b812b3f..8603fc7e 100644 --- a/rtl/ip/multimedia/ga2d_define.svh +++ b/rtl/ip/multimedia/ga2d_define.svh @@ -48,9 +48,9 @@ `define APB4_GA2D__IP_ID_VALUE 32'h4741_3244 `define APB4_GA2D__IP_VERSION_VALUE 32'h0001_0000 -`define APB4_GA2D__CAPABILITY_P3 32'h0000_0040 -`define APB4_GA2D__LIMITS_P3 32'h0000_0000 -`define APB4_GA2D__FORMAT_CAPABILITY_P3 32'h0000_0000 +`define APB4_GA2D__CAPABILITY_P4 32'h0000_03e3 +`define APB4_GA2D__LIMITS_P4 32'h0820_2010 +`define APB4_GA2D__FORMAT_CAPABILITY_P4 32'h000f_000f `define APB4_GA2D__TIMEOUT_CYCLES_RESET 32'h0010_0000 `define APB4_GA2D__GLOBAL_ALPHA_RESET 32'h0000_00ff diff --git a/rtl/ip/multimedia/ga2d_dma.sv b/rtl/ip/multimedia/ga2d_dma.sv new file mode 100644 index 00000000..e570d079 --- /dev/null +++ b/rtl/ip/multimedia/ga2d_dma.sv @@ -0,0 +1,780 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "ga2d_define.svh" +module ga2d_dma ( + input logic clk_i, + input logic rst_n_i, + input logic clear_i, + input logic start_i, + input logic stop_i, + input logic fill_i, + input logic [15:0] width_i, + input logic [15:0] height_i, + input logic [ 2:0] bytes_per_pixel_i, + input logic [31:0] foreground_address_i, + input logic [31:0] foreground_pitch_i, + input logic [31:0] destination_address_i, + input logic [31:0] destination_pitch_i, + input logic [31:0] fill_pixel_i, + output logic busy_o, + output logic draining_o, + output logic idle_o, + output logic done_o, + output logic aborted_o, + output logic error_valid_o, + output logic [ 6:0] error_code_o, + output logic [ 3:0] error_stage_o, + output logic [ 1:0] error_response_o, + output logic [31:0] error_address_o, + output logic progress_o, + output logic [ 7:0] read_bytes_o, + output logic [ 7:0] write_bytes_o, + output logic line_done_o, + output logic read_stall_o, + output logic write_stall_o, + output logic pipe_stall_o, + axi4_if.master axi4 +); + typedef enum logic [3:0] { + Idle, + FillPlan, + FillPrepare, + CopyPlanWrite, + CopyPlanRead, + CopyReadRequest, + CopyReadReceive, + CopyTransform, + WriteRequest, + WriteWait, + Drain + } state_e; + + state_e s_state_q; + logic s_stop_pending_q; + logic s_fill_q; + logic [15:0] s_height_q; + logic [ 2:0] s_bytes_per_pixel_q; + logic [31:0] s_foreground_addr_q; + logic [31:0] s_foreground_pitch_q; + logic [31:0] s_destination_addr_q; + logic [31:0] s_destination_pitch_q; + logic [31:0] s_fill_pixel_q; + logic [31:0] s_row_bytes_q; + logic [15:0] s_row_index_q; + logic [31:0] s_row_byte_q; + logic [31:0] s_write_burst_addr_q; + logic [ 4:0] s_write_burst_beats_q; + logic [ 7:0] s_write_burst_bytes_q; + logic s_write_ends_row_q; + logic [ 4:0] s_fill_prepare_beat_q; + logic [ 7:0] s_copy_bytes_staged_q; + logic [63:0] s_copy_build_data_q; + logic [ 7:0] s_copy_build_strobe_q; + logic s_read_reserved_q; + logic s_rd_cmd_owned_q; + logic [ 4:0] s_read_beats_q; + logic [ 3:0] s_read_word_bytes_q; + logic [ 3:0] s_read_word_bytes_left_q; + logic [ 4:0] s_read_words_left_q; + logic [ 2:0] s_read_word_lane_q; + logic s_read_full_q; + logic s_read_err_seen_q; + logic s_write_reserved_q; + logic [ 4:0] s_write_reserved_beats_q; + logic [ 7:0] s_write_reserved_bytes_q; + logic [ 7:0] s_write_inflight_bytes_q; + logic s_wr_cmd_owned_q; + logic [ 4:0] s_output_strobe_wr_q; + logic [ 4:0] s_output_strobe_rd_q; + logic [ 7:0] s_output_strobe_mem [0:31]; + logic s_done_q; + logic s_aborted_q; + logic s_err_seen_q; + logic s_err_valid_q; + logic [ 6:0] s_err_code_q; + logic [ 3:0] s_err_stage_q; + logic [ 1:0] s_err_rsp_q; + logic [31:0] s_err_addr_q; + logic [31:0] s_source_byte_addr; + logic [31:0] s_destination_byte_addr; + logic [31:0] s_row_remaining; + logic [31:0] s_write_plan_addr; + logic [ 4:0] s_write_plan_beats; + logic [ 7:0] s_write_plan_bytes; + logic s_write_plan_ends_row; + logic [31:0] s_copy_source_addr; + logic [31:0] s_copy_read_remaining; + logic [ 4:0] s_read_plan_beats; + logic [ 3:0] s_read_plan_word_bytes; + logic [ 2:0] s_read_plan_size; + logic [ 2:0] s_read_plan_lane; + logic s_read_plan_full; + logic [ 3:0] s_addr_read_bytes; + logic [ 2:0] s_addr_read_size; + logic s_fg_fifo_full; + logic s_fg_fifo_empty; + logic [63:0] s_fg_fifo_data; + logic [ 5:0] s_fg_fifo_count; + logic [ 5:0] unused_fg_fifo_count; + logic unused_bg_fifo_full; + logic unused_bg_fifo_empty; + logic [63:0] unused_bg_fifo_data; + logic [ 5:0] unused_bg_fifo_count; + logic s_output_fifo_full; + logic s_output_fifo_empty; + logic [63:0] s_output_fifo_data; + logic [ 5:0] s_output_fifo_count; + logic [ 5:0] unused_output_fifo_count; + logic s_fg_fifo_push; + logic s_fg_fifo_pop; + logic s_output_fifo_push; + logic s_output_fifo_pop; + logic [63:0] s_output_push_data; + logic [ 7:0] s_output_push_strobe; + logic s_read_req_valid; + logic unused_read_req_ready; + logic s_read_req_accept; + logic s_rd_ar_presented; + logic s_rd_req_cancel; + logic s_read_rsp_valid; + logic s_read_rsp_ready; + logic [63:0] s_read_data; + logic [ 1:0] s_read_rsp; + logic s_read_protocol_err; + logic s_read_rsp_terminal; + logic [31:0] s_read_rsp_addr; + logic s_write_req_valid; + logic s_write_req_accept; + logic s_wr_aw_presented; + logic s_write_req_cancel; + logic [ 4:0] s_write_req_beats; + logic s_write_payload_accept; + logic s_write_rsp_valid; + logic s_write_rsp_ready; + logic [ 1:0] s_write_rsp; + logic s_write_protocol_err; + logic [31:0] s_write_rsp_addr; + logic s_master_read_busy; + logic s_master_write_busy; + logic s_master_idle; + logic s_master_read_stall; + logic s_master_write_stall; + logic s_read_rsp_accept; + logic s_read_rsp_err; + logic s_write_rsp_accept; + logic s_write_rsp_err; + logic s_fill_prepare_fire; + logic s_copy_transform_fire; + logic s_copy_transform_end_word; + logic s_copy_transform_end_read; + logic s_copy_transform_end_burst; + logic [ 2:0] s_copy_destination_lane; + logic [ 7:0] s_copy_byte; + logic [63:0] s_copy_build_data_next; + logic [ 7:0] s_copy_build_strobe_next; + logic [63:0] s_fill_push_data; + logic [ 7:0] s_fill_push_strobe; + logic s_fill_valid_lane; + logic [31:0] s_fill_byte_index; + logic [ 7:0] s_fill_byte; + logic [31:0] unused_addr_write_address; + logic [63:0] unused_addr_write_data; + logic [ 7:0] unused_addr_write_strobe; + logic unused_write_req_ready; + + function automatic logic [7:0] fill_byte_at(input logic [31:0] pixel_i, input logic [2:0] bytes_i, + input logic [31:0] index_i); + logic [ 1:0] s_lane; + logic [31:0] s_remainder; + begin + unique case (bytes_i) + 3'd2: s_lane = {1'b0, index_i[0]}; + 3'd3: begin + s_remainder = index_i % 32'd3; + unique case (s_remainder) + 32'd0: s_lane = 2'd0; + 32'd1: s_lane = 2'd1; + default: s_lane = 2'd2; + endcase + end + default: s_lane = index_i[1:0]; + endcase + return pixel_i[s_lane*8+:8]; + end + endfunction + + assign s_source_byte_addr = s_foreground_addr_q + + (s_row_index_q * s_foreground_pitch_q) + s_row_byte_q; + assign s_destination_byte_addr = s_destination_addr_q + + (s_row_index_q * s_destination_pitch_q) + s_row_byte_q; + assign s_row_remaining = s_row_bytes_q - s_row_byte_q; + assign s_copy_source_addr = s_source_byte_addr + {24'd0, s_copy_bytes_staged_q}; + assign s_copy_read_remaining = {24'd0, s_write_burst_bytes_q} - {24'd0, s_copy_bytes_staged_q}; + + ga2d_addr_gen u_addr_gen ( + .read_address_lsb_i(s_copy_source_addr[2:0]), + .read_remaining_i (s_copy_read_remaining), + .write_address_i (32'd0), + .write_byte_i (8'd0), + .read_bytes_o (s_addr_read_bytes), + .read_size_o (s_addr_read_size), + .write_address_o (unused_addr_write_address), + .write_data_o (unused_addr_write_data), + .write_strobe_o (unused_addr_write_strobe) + ); + + always_comb begin + logic [12:0] s_bytes_to_4k; + logic [12:0] s_beats_to_4k; + logic [31:0] s_full_beats; + s_bytes_to_4k = '0; + s_beats_to_4k = '0; + s_full_beats = '0; + s_write_plan_addr = {s_destination_byte_addr[31:3], 3'b000}; + s_bytes_to_4k = 13'd4096 - {1'b0, s_write_plan_addr[11:0]}; + s_beats_to_4k = s_bytes_to_4k >> 3; + s_full_beats = s_row_remaining >> 3; + s_write_plan_beats = 5'd1; + s_write_plan_bytes = s_row_remaining[7:0]; + if (s_destination_byte_addr[2:0] != 3'd0) begin + if (s_row_remaining < (32'd8 - {29'd0, s_destination_byte_addr[2:0]})) begin + s_write_plan_bytes = s_row_remaining[7:0]; + end else begin + s_write_plan_bytes = 8'd8 - {5'd0, s_destination_byte_addr[2:0]}; + end + end else if (s_row_remaining >= 32'd8) begin + if (s_full_beats > 5'd16) begin + s_write_plan_beats = 5'd16; + end else begin + s_write_plan_beats = s_full_beats[4:0]; + end + if (s_beats_to_4k < {8'd0, s_write_plan_beats}) begin + s_write_plan_beats = s_beats_to_4k[4:0]; + end + s_write_plan_bytes = {s_write_plan_beats, 3'b000}; + end + s_write_plan_ends_row = {24'd0, s_write_plan_bytes} == s_row_remaining; + end + + always_comb begin + logic [12:0] s_bytes_to_4k; + logic [12:0] s_beats_to_4k; + logic [31:0] s_full_beats; + s_bytes_to_4k = '0; + s_beats_to_4k = '0; + s_full_beats = '0; + s_read_plan_beats = 5'd1; + s_read_plan_word_bytes = s_addr_read_bytes; + s_read_plan_size = s_addr_read_size; + s_read_plan_lane = s_copy_source_addr[2:0]; + s_read_plan_full = 1'b0; + if ((s_copy_source_addr[2:0] == 3'd0) && (s_copy_read_remaining >= 32'd8)) begin + s_bytes_to_4k = 13'd4096 - {1'b0, s_copy_source_addr[11:0]}; + s_beats_to_4k = s_bytes_to_4k >> 3; + s_full_beats = s_copy_read_remaining >> 3; + if (s_full_beats > 5'd16) begin + s_read_plan_beats = 5'd16; + end else begin + s_read_plan_beats = s_full_beats[4:0]; + end + if (s_beats_to_4k < {8'd0, s_read_plan_beats}) begin + s_read_plan_beats = s_beats_to_4k[4:0]; + end + s_read_plan_word_bytes = 4'd8; + s_read_plan_size = 3'd3; + s_read_plan_lane = 3'd0; + s_read_plan_full = 1'b1; + end + end + + always_comb begin + s_fill_push_data = '0; + s_fill_push_strobe = '0; + for (int unsigned lane = 0; lane < 8; lane++) begin + s_fill_byte_index = ({27'd0, s_fill_prepare_beat_q} << 3) + + {29'd0, lane[2:0]} - + {29'd0, s_destination_byte_addr[2:0]}; + s_fill_valid_lane = (lane >= s_destination_byte_addr[2:0]) && + (s_fill_byte_index < s_write_burst_bytes_q); + s_fill_byte = + fill_byte_at(s_fill_pixel_q, s_bytes_per_pixel_q, s_row_byte_q + s_fill_byte_index); + if (s_fill_valid_lane) begin + s_fill_push_data[lane*8+:8] = s_fill_byte; + s_fill_push_strobe[lane] = 1'b1; + end + end + end + + assign s_copy_destination_lane = s_destination_byte_addr[2:0] + s_copy_bytes_staged_q[2:0]; + assign s_copy_byte = s_fg_fifo_data[s_read_word_lane_q*8+:8]; + assign s_copy_build_data_next = s_copy_build_data_q | + ({56'd0, s_copy_byte} << {s_copy_destination_lane, 3'b000}); + assign s_copy_build_strobe_next = s_copy_build_strobe_q | + (8'b0000_0001 << s_copy_destination_lane); + assign s_copy_transform_end_word = (s_copy_destination_lane == 3'd7) || + ((s_copy_bytes_staged_q + 1'b1) == + s_write_burst_bytes_q); + assign s_copy_transform_end_read = (s_read_word_bytes_left_q == 4'd1) && + (s_read_words_left_q == 5'd1); + assign s_copy_transform_end_burst = (s_copy_bytes_staged_q + 1'b1) == s_write_burst_bytes_q; + assign s_copy_transform_fire = (s_state_q == CopyTransform) && !s_fg_fifo_empty && + !s_output_fifo_full && !stop_i; + assign s_fill_prepare_fire = (s_state_q == FillPrepare) && !s_output_fifo_full && !stop_i; + + assign s_output_fifo_push = s_fill_prepare_fire || + (s_copy_transform_fire && s_copy_transform_end_word); + assign s_output_push_data = s_fill_prepare_fire ? s_fill_push_data : s_copy_build_data_next; + assign s_output_push_strobe = s_fill_prepare_fire ? s_fill_push_strobe : s_copy_build_strobe_next; + assign s_output_fifo_pop = s_write_payload_accept || + ((s_state_q == Drain) && !s_output_fifo_empty && + !s_master_write_busy); + assign s_fg_fifo_push = (s_state_q == CopyReadReceive) && s_read_rsp_accept && !s_read_rsp_err; + assign s_fg_fifo_pop = (s_copy_transform_fire && (s_read_word_bytes_left_q == 4'd1)) || + ((s_state_q == Drain) && !s_fg_fifo_empty); + + fifo #( + .DATA_WIDTH (64), + .BUFFER_DEPTH (32), + .LOG_BUFFER_DEPTH(5) + ) u_foreground_fifo ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(clear_i), + .push_i (s_fg_fifo_push), + .full_o (s_fg_fifo_full), + .dat_i (s_read_data), + .pop_i (s_fg_fifo_pop), + .empty_o(s_fg_fifo_empty), + .dat_o (s_fg_fifo_data), + .cnt_o (s_fg_fifo_count) + ); + fifo #( + .DATA_WIDTH (64), + .BUFFER_DEPTH (32), + .LOG_BUFFER_DEPTH(5) + ) u_background_fifo ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(clear_i), + .push_i (1'b0), + .full_o (unused_bg_fifo_full), + .dat_i ('0), + .pop_i (1'b0), + .empty_o(unused_bg_fifo_empty), + .dat_o (unused_bg_fifo_data), + .cnt_o (unused_bg_fifo_count) + ); + fifo #( + .DATA_WIDTH (64), + .BUFFER_DEPTH (32), + .LOG_BUFFER_DEPTH(5) + ) u_output_fifo ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(clear_i), + .push_i (s_output_fifo_push), + .full_o (s_output_fifo_full), + .dat_i (s_output_push_data), + .pop_i (s_output_fifo_pop), + .empty_o(s_output_fifo_empty), + .dat_o (s_output_fifo_data), + .cnt_o (s_output_fifo_count) + ); + + // Stable formal observation aliases for the three P4 payload FIFO counts. + assign unused_fg_fifo_count = s_fg_fifo_count; + assign unused_output_fifo_count = s_output_fifo_count; + + assign s_read_req_valid = (s_state_q == CopyReadRequest) && s_read_reserved_q && !stop_i; + assign s_read_rsp_ready = s_state_q == CopyReadReceive; + assign s_read_rsp_accept = s_read_rsp_valid && s_read_rsp_ready; + assign s_read_rsp_err = s_read_protocol_err || (s_read_rsp != 2'd0); + assign s_write_req_beats = s_write_reserved_beats_q; + assign s_write_req_valid = (s_state_q == WriteRequest) && !stop_i && + s_write_reserved_q && + (s_output_fifo_count >= {1'b0, s_write_req_beats}) && + (s_write_req_beats != 5'd0); + assign s_write_rsp_ready = s_state_q == WriteWait; + assign s_write_rsp_accept = s_write_rsp_valid && s_write_rsp_ready; + assign s_write_rsp_err = s_write_protocol_err || (s_write_rsp != 2'd0); + + assign busy_o = s_state_q != Idle; + assign draining_o = s_stop_pending_q && (s_state_q != Idle); + assign idle_o = (s_state_q == Idle) && s_master_idle && !s_master_read_busy && + !s_master_write_busy && s_fg_fifo_empty && s_output_fifo_empty && + !s_read_reserved_q && !s_rd_cmd_owned_q && !s_write_reserved_q && + !s_wr_cmd_owned_q; + assign done_o = s_done_q; + assign aborted_o = s_aborted_q; + assign error_valid_o = s_err_valid_q; + assign error_code_o = s_err_code_q; + assign error_stage_o = s_err_stage_q; + assign error_response_o = s_err_rsp_q; + assign error_address_o = s_err_addr_q; + assign progress_o = s_read_req_accept || s_read_rsp_accept || s_write_req_accept || + s_write_payload_accept || s_write_rsp_accept || s_output_fifo_push || + s_copy_transform_fire; + assign read_bytes_o = (s_read_rsp_accept && !s_read_rsp_err) ? + (s_read_full_q ? 8'd8 : {4'd0, s_read_word_bytes_q}) : 8'd0; + assign write_bytes_o = (s_write_rsp_accept && !s_write_rsp_err) ? s_write_inflight_bytes_q : 8'd0; + assign line_done_o = s_write_rsp_accept && !s_write_rsp_err && s_write_ends_row_q; + assign read_stall_o = s_master_read_stall; + assign write_stall_o = s_master_write_stall; + assign pipe_stall_o = ((s_state_q == FillPrepare) && s_output_fifo_full) || + ((s_state_q == CopyPlanRead) && s_fg_fifo_full) || + ((s_state_q == CopyTransform) && + (s_fg_fifo_empty || s_output_fifo_full)) || + ((s_state_q == WriteRequest) && !unused_write_req_ready); + + ga2d_axi4_master u_axi4_master ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .clear_i (clear_i), + .read_request_valid_i (s_read_req_valid), + .read_request_ready_o (unused_read_req_ready), + .read_address_i (s_copy_source_addr), + .read_size_i (s_read_plan_size), + .read_beats_i (s_read_beats_q), + .read_stop_i (stop_i), + .read_request_accept_o (s_read_req_accept), + .read_address_presented_o (s_rd_ar_presented), + .read_request_cancel_o (s_rd_req_cancel), + .read_response_valid_o (s_read_rsp_valid), + .read_response_ready_i (s_read_rsp_ready), + .read_data_o (s_read_data), + .read_response_o (s_read_rsp), + .read_protocol_error_o (s_read_protocol_err), + .read_response_terminal_o (s_read_rsp_terminal), + .read_response_address_o (s_read_rsp_addr), + .write_request_valid_i (s_write_req_valid), + .write_request_ready_o (unused_write_req_ready), + .write_address_i (s_write_burst_addr_q), + .write_beats_i (s_write_req_beats), + .write_stop_i (stop_i), + .write_request_accept_o (s_write_req_accept), + .write_address_presented_o(s_wr_aw_presented), + .write_request_cancel_o (s_write_req_cancel), + .write_payload_valid_i (!s_output_fifo_empty), + .write_data_i (s_output_fifo_data), + .write_strobe_i (s_output_strobe_mem[s_output_strobe_rd_q]), + .write_payload_accept_o (s_write_payload_accept), + .write_response_valid_o (s_write_rsp_valid), + .write_response_ready_i (s_write_rsp_ready), + .write_response_o (s_write_rsp), + .write_protocol_error_o (s_write_protocol_err), + .write_response_address_o (s_write_rsp_addr), + .read_busy_o (s_master_read_busy), + .write_busy_o (s_master_write_busy), + .idle_o (s_master_idle), + .read_stall_o (s_master_read_stall), + .write_stall_o (s_master_write_stall), + .axi4 (axi4) + ); + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_output_strobe_wr_q <= '0; + s_output_strobe_rd_q <= '0; + end else if (clear_i) begin + s_output_strobe_wr_q <= '0; + s_output_strobe_rd_q <= '0; + end else begin + if (s_output_fifo_push) begin + s_output_strobe_mem[s_output_strobe_wr_q] <= s_output_push_strobe; + s_output_strobe_wr_q <= s_output_strobe_wr_q + 1'b1; + end + if (s_output_fifo_pop) begin + s_output_strobe_rd_q <= s_output_strobe_rd_q + 1'b1; + end + end + end + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_state_q <= Idle; + s_stop_pending_q <= 1'b0; + s_fill_q <= 1'b0; + s_height_q <= '0; + s_bytes_per_pixel_q <= '0; + s_foreground_addr_q <= '0; + s_foreground_pitch_q <= '0; + s_destination_addr_q <= '0; + s_destination_pitch_q <= '0; + s_fill_pixel_q <= '0; + s_row_bytes_q <= '0; + s_row_index_q <= '0; + s_row_byte_q <= '0; + s_write_burst_addr_q <= '0; + s_write_burst_beats_q <= '0; + s_write_burst_bytes_q <= '0; + s_write_ends_row_q <= 1'b0; + s_fill_prepare_beat_q <= '0; + s_copy_bytes_staged_q <= '0; + s_copy_build_data_q <= '0; + s_copy_build_strobe_q <= '0; + s_read_reserved_q <= 1'b0; + s_rd_cmd_owned_q <= 1'b0; + s_read_beats_q <= '0; + s_read_word_bytes_q <= '0; + s_read_word_bytes_left_q <= '0; + s_read_words_left_q <= '0; + s_read_word_lane_q <= '0; + s_read_full_q <= 1'b0; + s_read_err_seen_q <= 1'b0; + s_write_reserved_q <= 1'b0; + s_write_reserved_beats_q <= '0; + s_write_reserved_bytes_q <= '0; + s_write_inflight_bytes_q <= '0; + s_wr_cmd_owned_q <= 1'b0; + s_done_q <= 1'b0; + s_aborted_q <= 1'b0; + s_err_seen_q <= 1'b0; + s_err_valid_q <= 1'b0; + s_err_code_q <= '0; + s_err_stage_q <= '0; + s_err_rsp_q <= '0; + s_err_addr_q <= '0; + end else if (clear_i) begin + s_state_q <= Idle; + s_stop_pending_q <= 1'b0; + s_read_reserved_q <= 1'b0; + s_rd_cmd_owned_q <= 1'b0; + s_write_reserved_q <= 1'b0; + s_write_reserved_beats_q <= '0; + s_write_reserved_bytes_q <= '0; + s_wr_cmd_owned_q <= 1'b0; + s_done_q <= 1'b0; + s_aborted_q <= 1'b0; + s_err_seen_q <= 1'b0; + s_err_valid_q <= 1'b0; + end else begin + s_done_q <= 1'b0; + s_aborted_q <= 1'b0; + s_err_valid_q <= 1'b0; + if (stop_i && (s_state_q != Idle)) begin + s_stop_pending_q <= 1'b1; + end + + unique case (s_state_q) + Idle: begin + s_stop_pending_q <= 1'b0; + s_read_reserved_q <= 1'b0; + s_rd_cmd_owned_q <= 1'b0; + s_write_reserved_q <= 1'b0; + s_write_reserved_beats_q <= '0; + s_write_reserved_bytes_q <= '0; + s_wr_cmd_owned_q <= 1'b0; + s_err_seen_q <= 1'b0; + if (start_i && !stop_i) begin + s_fill_q <= fill_i; + s_height_q <= height_i; + s_bytes_per_pixel_q <= bytes_per_pixel_i; + s_foreground_addr_q <= foreground_address_i; + s_foreground_pitch_q <= foreground_pitch_i; + s_destination_addr_q <= destination_address_i; + s_destination_pitch_q <= destination_pitch_i; + s_fill_pixel_q <= fill_pixel_i; + s_row_bytes_q <= width_i * bytes_per_pixel_i; + s_row_index_q <= '0; + s_row_byte_q <= '0; + s_state_q <= fill_i ? FillPlan : CopyPlanWrite; + end + end + FillPlan, CopyPlanWrite: begin + if (stop_i) begin + s_state_q <= Drain; + end else begin + s_write_burst_addr_q <= s_write_plan_addr; + s_write_burst_beats_q <= s_write_plan_beats; + s_write_burst_bytes_q <= s_write_plan_bytes; + s_write_ends_row_q <= s_write_plan_ends_row; + s_fill_prepare_beat_q <= '0; + s_copy_bytes_staged_q <= '0; + s_copy_build_data_q <= '0; + s_copy_build_strobe_q <= '0; + s_state_q <= (s_state_q == FillPlan) ? FillPrepare : CopyPlanRead; + end + end + FillPrepare: begin + if (stop_i) begin + s_state_q <= Drain; + end else if (s_fill_prepare_fire) begin + if (s_fill_prepare_beat_q == (s_write_burst_beats_q - 1'b1)) begin + s_write_reserved_q <= 1'b1; + s_write_reserved_beats_q <= s_write_burst_beats_q; + s_write_reserved_bytes_q <= s_write_burst_bytes_q; + s_state_q <= WriteRequest; + end else begin + s_fill_prepare_beat_q <= s_fill_prepare_beat_q + 1'b1; + end + end + end + CopyPlanRead: begin + if (stop_i) begin + s_state_q <= Drain; + end else if (!s_fg_fifo_full && ((s_fg_fifo_count + s_read_plan_beats) <= 6'd32)) begin + s_read_beats_q <= s_read_plan_beats; + s_read_word_bytes_q <= s_read_plan_word_bytes; + s_read_word_lane_q <= s_read_plan_lane; + s_read_full_q <= s_read_plan_full; + s_read_reserved_q <= 1'b1; + s_read_err_seen_q <= 1'b0; + s_state_q <= CopyReadRequest; + end + end + CopyReadRequest: begin + if (stop_i) begin + s_read_reserved_q <= 1'b0; + s_state_q <= Drain; + end else if (s_read_req_accept) begin + s_rd_cmd_owned_q <= 1'b1; + s_state_q <= CopyReadReceive; + end + end + CopyReadReceive: begin + if (s_rd_req_cancel && s_rd_cmd_owned_q && !s_rd_ar_presented) begin + s_rd_cmd_owned_q <= 1'b0; + s_state_q <= Drain; + end else if (s_read_rsp_accept) begin + if (s_read_rsp_err && !s_err_seen_q) begin + s_err_seen_q <= 1'b1; + s_err_valid_q <= 1'b1; + s_err_code_q <= s_read_protocol_err ? `APB4_GA2D__ERROR_AXI_PROTOCOL : + `APB4_GA2D__ERROR_AXI_READ; + s_err_stage_q <= `APB4_GA2D__ERROR_STAGE_FOREGROUND; + s_err_rsp_q <= s_read_protocol_err ? 2'd0 : s_read_rsp; + s_err_addr_q <= s_read_rsp_addr; + end + if (s_read_protocol_err) begin + // The AXI owner remains live until the existing bridge clear/epoch + // boundary contains a malformed response and any late residual beat. + s_state_q <= Drain; + end else if (s_read_rsp_terminal) begin + s_read_reserved_q <= 1'b0; + s_rd_cmd_owned_q <= 1'b0; + if (s_read_err_seen_q || s_read_rsp_err || stop_i) begin + s_state_q <= Drain; + end else begin + s_read_words_left_q <= s_read_beats_q; + s_read_word_bytes_left_q <= s_read_word_bytes_q; + s_state_q <= CopyTransform; + end + end + if (s_read_rsp_err) begin + s_read_err_seen_q <= 1'b1; + end + end + end + CopyTransform: begin + if (stop_i) begin + s_state_q <= Drain; + end else if (s_copy_transform_fire) begin + s_copy_bytes_staged_q <= s_copy_bytes_staged_q + 1'b1; + if (s_copy_transform_end_word) begin + s_copy_build_data_q <= '0; + s_copy_build_strobe_q <= '0; + end else begin + s_copy_build_data_q <= s_copy_build_data_next; + s_copy_build_strobe_q <= s_copy_build_strobe_next; + end + if (s_read_word_bytes_left_q == 4'd1) begin + s_read_words_left_q <= s_read_words_left_q - 1'b1; + if (s_read_words_left_q != 5'd1) begin + s_read_word_bytes_left_q <= s_read_full_q ? 4'd8 : s_read_word_bytes_q; + s_read_word_lane_q <= s_read_full_q ? 3'd0 : s_read_word_lane_q; + end + end else begin + s_read_word_bytes_left_q <= s_read_word_bytes_left_q - 1'b1; + s_read_word_lane_q <= s_read_word_lane_q + 1'b1; + end + if (s_copy_transform_end_read) begin + if (s_copy_transform_end_burst) begin + s_write_reserved_q <= 1'b1; + s_write_reserved_beats_q <= s_write_burst_beats_q; + s_write_reserved_bytes_q <= s_write_burst_bytes_q; + s_state_q <= WriteRequest; + end else begin + s_state_q <= CopyPlanRead; + end + end + end + end + WriteRequest: begin + if (stop_i) begin + s_state_q <= Drain; + end else if (s_write_req_accept) begin + s_write_inflight_bytes_q <= s_write_reserved_bytes_q; + s_wr_cmd_owned_q <= 1'b1; + s_state_q <= WriteWait; + end + end + WriteWait: begin + if (s_write_req_cancel && s_wr_cmd_owned_q && !s_wr_aw_presented) begin + s_wr_cmd_owned_q <= 1'b0; + s_state_q <= Drain; + end else if (s_write_rsp_accept) begin + if (s_write_rsp_err && !s_err_seen_q) begin + s_err_seen_q <= 1'b1; + s_err_valid_q <= 1'b1; + s_err_code_q <= s_write_protocol_err ? `APB4_GA2D__ERROR_AXI_PROTOCOL : + `APB4_GA2D__ERROR_AXI_WRITE; + s_err_stage_q <= `APB4_GA2D__ERROR_STAGE_DESTINATION; + s_err_rsp_q <= s_write_protocol_err ? 2'd0 : s_write_rsp; + s_err_addr_q <= s_write_rsp_addr; + end + if (s_write_protocol_err) begin + // Do not release the write reservation or owner on a malformed B. + // clear_i is the only safe containment boundary for local ID 0. + s_state_q <= Drain; + end else begin + s_wr_cmd_owned_q <= 1'b0; + s_write_reserved_q <= 1'b0; + s_write_reserved_beats_q <= '0; + s_write_reserved_bytes_q <= '0; + if (s_write_rsp_err || stop_i) begin + s_state_q <= Drain; + end else if (s_write_ends_row_q) begin + s_row_byte_q <= '0; + if (s_row_index_q == (s_height_q - 1'b1)) begin + s_done_q <= 1'b1; + s_state_q <= Idle; + end else begin + s_row_index_q <= s_row_index_q + 1'b1; + s_state_q <= s_fill_q ? FillPlan : CopyPlanWrite; + end + end else begin + s_row_byte_q <= s_row_byte_q + {24'd0, s_write_burst_bytes_q}; + s_state_q <= s_fill_q ? FillPlan : CopyPlanWrite; + end + end + end + end + Drain: begin + if (!s_fg_fifo_empty || !s_output_fifo_empty) begin + s_state_q <= Drain; + end else if (s_master_idle) begin + s_state_q <= Idle; + s_aborted_q <= 1'b1; + s_read_reserved_q <= 1'b0; + s_write_reserved_q <= 1'b0; + s_write_reserved_beats_q <= '0; + s_write_reserved_bytes_q <= '0; + end + end + default: begin + s_state_q <= Drain; + s_stop_pending_q <= 1'b1; + if (!s_err_seen_q) begin + s_err_seen_q <= 1'b1; + s_err_valid_q <= 1'b1; + s_err_code_q <= `APB4_GA2D__ERROR_INTERNAL; + s_err_stage_q <= `APB4_GA2D__ERROR_STAGE_LIFECYCLE; + s_err_rsp_q <= '0; + s_err_addr_q <= '0; + end + end + endcase + end + end +endmodule diff --git a/rtl/ip/multimedia/ga2d_pixel.sv b/rtl/ip/multimedia/ga2d_pixel.sv new file mode 100644 index 00000000..b63cb4b8 --- /dev/null +++ b/rtl/ip/multimedia/ga2d_pixel.sv @@ -0,0 +1,39 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "ga2d_define.svh" + +module ga2d_pixel ( + input logic [31:0] color_i, + input logic [ 2:0] format_i, + output logic [31:0] pixel_o, + output logic [ 2:0] bytes_per_pixel_o, + output logic format_valid_o +); + always_comb begin + pixel_o = '0; + bytes_per_pixel_o = '0; + format_valid_o = 1'b1; + unique case (format_i) + `APB4_GA2D__FORMAT_RGB565: begin + pixel_o = {16'd0, color_i[23:19], color_i[15:10], color_i[7:3]}; + bytes_per_pixel_o = 3'd2; + end + `APB4_GA2D__FORMAT_RGB888: begin + pixel_o = {8'd0, color_i[7:0], color_i[15:8], color_i[23:16]}; + bytes_per_pixel_o = 3'd3; + end + `APB4_GA2D__FORMAT_XRGB8888: begin + pixel_o = {8'hff, color_i[23:0]}; + bytes_per_pixel_o = 3'd4; + end + `APB4_GA2D__FORMAT_ARGB8888: begin + pixel_o = color_i; + bytes_per_pixel_o = 3'd4; + end + default: begin + format_valid_o = 1'b0; + end + endcase + end +endmodule diff --git a/rtl/ip/multimedia/ga2d_pkg.sv b/rtl/ip/multimedia/ga2d_pkg.sv index fb4bb1a2..804cacc9 100644 --- a/rtl/ip/multimedia/ga2d_pkg.sv +++ b/rtl/ip/multimedia/ga2d_pkg.sv @@ -43,4 +43,10 @@ package ga2d_pkg; logic [1:0] axi_response; logic [31:0] address; } ga2d_error_t; + + typedef struct packed { + logic [31:0] address; + logic [31:0] pitch; + logic [2:0] format; + } ga2d_plane_t; endpackage diff --git a/rtl/ip/multimedia/ga2d_reg.sv b/rtl/ip/multimedia/ga2d_reg.sv index 055afbcb..1ee92153 100644 --- a/rtl/ip/multimedia/ga2d_reg.sv +++ b/rtl/ip/multimedia/ga2d_reg.sv @@ -15,16 +15,26 @@ module ga2d_reg ( input logic idle_i, input logic done_i, input logic aborted_i, + input logic error_i, input logic [2:0] irq_event_i, input logic error_valid_i, input logic [6:0] error_code_i, input logic [3:0] error_stage_i, input logic [1:0] error_axi_response_i, input logic [31:0] error_address_i, + input logic [63:0] cycles_i, + input logic [63:0] read_bytes_i, + input logic [63:0] write_bytes_i, + input logic [63:0] read_stalls_i, + input logic [63:0] write_stalls_i, + input logic [63:0] pipe_stalls_i, + input logic [15:0] lines_done_i, apb4_if.slave apb4, output ga2d_pkg::ga2d_config_t config_o, + output logic start_o, output logic soft_reset_o, output logic abort_o, + output logic snapshot_o, output logic idle_o, output logic irq_o // verilog_format: on @@ -57,6 +67,13 @@ module ga2d_reg ( ga2d_error_t s_err_d, s_err_q; logic [2:0] s_irq_state_d, s_irq_state_q; logic [2:0] s_irq_en_d, s_irq_en_q; + logic [63:0] s_snap_cycles_d, s_snap_cycles_q; + logic [63:0] s_snap_read_bytes_d, s_snap_read_bytes_q; + logic [63:0] s_snap_write_bytes_d, s_snap_write_bytes_q; + logic [63:0] s_snap_read_stalls_d, s_snap_read_stalls_q; + logic [63:0] s_snap_write_stalls_d, s_snap_write_stalls_q; + logic [63:0] s_snap_pipe_stalls_d, s_snap_pipe_stalls_q; + logic [15:0] s_snap_lines_done_d, s_snap_lines_done_q; logic s_req_accept; logic s_write; logic [11:0] s_offset; @@ -70,20 +87,26 @@ module ga2d_reg ( logic s_err_clear; logic s_cmd_soft_reset; logic s_cmd_abort; + logic s_cmd_start; + logic s_cmd_snapshot; - assign s_req_accept = apb4.psel && apb4.penable && !s_access_seen_q; - assign s_write = s_req_accept && apb4.pwrite; - assign s_offset = apb4.paddr[11:0]; + assign s_req_accept = apb4.psel && apb4.penable && !s_access_seen_q; + assign s_write = s_req_accept && apb4.pwrite; + assign s_offset = apb4.paddr[11:0]; assign s_write_value = apb4.pwdata; + assign s_cmd_abort = s_write && (s_offset[1:0] == 2'b00) && (apb4.pstrb == 4'hf) && + (s_offset == `APB4_GA2D__COMMAND) && (s_write_value == 32'h0000_0002); - assign apb4.pready = s_apb4_ready_q; - assign apb4.prdata = s_apb4_rdata_q; - assign apb4.pslverr = s_apb4_resp_err_q; - assign config_o = s_config_q; - assign soft_reset_o = s_cmd_soft_reset; - assign abort_o = s_cmd_abort; - assign idle_o = idle_i; - assign irq_o = (s_irq_state_q & s_irq_en_q) != 3'd0; + assign apb4.pready = s_apb4_ready_q; + assign apb4.prdata = s_apb4_rdata_q; + assign apb4.pslverr = s_apb4_resp_err_q; + assign config_o = s_config_q; + assign start_o = s_cmd_start; + assign soft_reset_o = s_cmd_soft_reset; + assign abort_o = s_cmd_abort; + assign snapshot_o = s_cmd_snapshot; + assign idle_o = idle_i; + assign irq_o = (s_irq_state_q & s_irq_en_q) != 3'd0; always_comb begin s_read_data = 32'd0; @@ -91,13 +114,13 @@ module ga2d_reg ( unique case (s_offset) `APB4_GA2D__IP_ID: s_read_data = `APB4_GA2D__IP_ID_VALUE; `APB4_GA2D__IP_VERSION: s_read_data = `APB4_GA2D__IP_VERSION_VALUE; - `APB4_GA2D__CAPABILITY: s_read_data = `APB4_GA2D__CAPABILITY_P3; - `APB4_GA2D__LIMITS: s_read_data = `APB4_GA2D__LIMITS_P3; + `APB4_GA2D__CAPABILITY: s_read_data = `APB4_GA2D__CAPABILITY_P4; + `APB4_GA2D__LIMITS: s_read_data = `APB4_GA2D__LIMITS_P4; `APB4_GA2D__STATUS: s_read_data = { 24'd0, recovery_required_i, - s_err_q.valid, + error_i, aborted_i, done_i, data_ready_i, @@ -124,23 +147,21 @@ module ga2d_reg ( `APB4_GA2D__DST_ADDRESS: s_read_data = s_config_q.dst_address; `APB4_GA2D__DST_PITCH: s_read_data = s_config_q.dst_pitch; `APB4_GA2D__DST_FORMAT: s_read_data = s_config_q.dst_format; - `APB4_GA2D__FORMAT_CAPABILITY: s_read_data = `APB4_GA2D__FORMAT_CAPABILITY_P3; - `APB4_GA2D__SNAP_CYCLES_LO, - `APB4_GA2D__SNAP_CYCLES_HI, - `APB4_GA2D__SNAP_READ_BYTES_LO, - `APB4_GA2D__SNAP_READ_BYTES_HI, - `APB4_GA2D__SNAP_WRITE_BYTES_LO, - `APB4_GA2D__SNAP_WRITE_BYTES_HI, - `APB4_GA2D__SNAP_READ_STALL_LO, - `APB4_GA2D__SNAP_READ_STALL_HI, - `APB4_GA2D__SNAP_WRITE_STALL_LO, - `APB4_GA2D__SNAP_WRITE_STALL_HI, - `APB4_GA2D__SNAP_PIPE_STALL_LO, - `APB4_GA2D__SNAP_PIPE_STALL_HI, - `APB4_GA2D__SNAP_LINES_DONE, - `APB4_GA2D__COMMAND, - `APB4_GA2D__IRQ_TEST, - `APB4_GA2D__PERF_SNAPSHOT: begin + `APB4_GA2D__FORMAT_CAPABILITY: s_read_data = `APB4_GA2D__FORMAT_CAPABILITY_P4; + `APB4_GA2D__SNAP_CYCLES_LO: s_read_data = s_snap_cycles_q[31:0]; + `APB4_GA2D__SNAP_CYCLES_HI: s_read_data = s_snap_cycles_q[63:32]; + `APB4_GA2D__SNAP_READ_BYTES_LO: s_read_data = s_snap_read_bytes_q[31:0]; + `APB4_GA2D__SNAP_READ_BYTES_HI: s_read_data = s_snap_read_bytes_q[63:32]; + `APB4_GA2D__SNAP_WRITE_BYTES_LO: s_read_data = s_snap_write_bytes_q[31:0]; + `APB4_GA2D__SNAP_WRITE_BYTES_HI: s_read_data = s_snap_write_bytes_q[63:32]; + `APB4_GA2D__SNAP_READ_STALL_LO: s_read_data = s_snap_read_stalls_q[31:0]; + `APB4_GA2D__SNAP_READ_STALL_HI: s_read_data = s_snap_read_stalls_q[63:32]; + `APB4_GA2D__SNAP_WRITE_STALL_LO: s_read_data = s_snap_write_stalls_q[31:0]; + `APB4_GA2D__SNAP_WRITE_STALL_HI: s_read_data = s_snap_write_stalls_q[63:32]; + `APB4_GA2D__SNAP_PIPE_STALL_LO: s_read_data = s_snap_pipe_stalls_q[31:0]; + `APB4_GA2D__SNAP_PIPE_STALL_HI: s_read_data = s_snap_pipe_stalls_q[63:32]; + `APB4_GA2D__SNAP_LINES_DONE: s_read_data = {16'd0, s_snap_lines_done_q}; + `APB4_GA2D__COMMAND, `APB4_GA2D__IRQ_TEST, `APB4_GA2D__PERF_SNAPSHOT: begin end default: s_read_valid = 1'b0; endcase @@ -156,7 +177,8 @@ module ga2d_reg ( s_irq_test = 3'd0; s_err_clear = 1'b0; s_cmd_soft_reset = 1'b0; - s_cmd_abort = 1'b0; + s_cmd_start = 1'b0; + s_cmd_snapshot = 1'b0; if (s_write) begin if ((s_offset[1:0] != 2'b00) || (apb4.pstrb != 4'hf)) begin s_write_err = 1'b1; @@ -166,8 +188,16 @@ module ga2d_reg ( unique case (s_write_value) 32'd0: begin end - 32'h0000_0001: s_write_err = 1'b1; - 32'h0000_0002: s_cmd_abort = 1'b1; + 32'h0000_0001: begin + if (busy_i || draining_i || recovery_required_i || !idle_i || !data_ready_i || + (s_config_q.job_config[1:0] > `APB4_GA2D__OP_COPY)) begin + s_write_err = 1'b1; + end else begin + s_cmd_start = 1'b1; + end + end + 32'h0000_0002: begin + end 32'h0000_0004: begin if (busy_i || draining_i || recovery_required_i || !idle_i || !data_ready_i) begin s_write_err = 1'b1; @@ -175,7 +205,7 @@ module ga2d_reg ( s_cmd_soft_reset = 1'b1; end end - default: s_write_err = 1'b1; + default: s_write_err = 1'b1; endcase end `APB4_GA2D__IRQ_STATE: begin @@ -237,8 +267,14 @@ module ga2d_reg ( s_write_err = 1'b1; end end - `APB4_GA2D__PERF_SNAPSHOT: s_write_err = 1'b1; - default: s_write_err = 1'b1; + `APB4_GA2D__PERF_SNAPSHOT: begin + if (s_write_value != 32'h0000_0001) begin + s_write_err = 1'b1; + end else begin + s_cmd_snapshot = 1'b1; + end + end + default: s_write_err = 1'b1; endcase end end @@ -258,10 +294,17 @@ module ga2d_reg ( s_apb4_rdata_d = (!s_write && !s_read_err) ? s_read_data : 32'd0; end - s_config_d = s_config_q; - s_irq_state_d = (s_irq_state_q & ~s_irq_clear) | irq_event_i | s_irq_test; - s_irq_en_d = s_irq_en_q; - s_err_d = s_err_q; + s_config_d = s_config_q; + s_irq_state_d = (s_irq_state_q & ~s_irq_clear) | irq_event_i | s_irq_test; + s_irq_en_d = s_irq_en_q; + s_err_d = s_err_q; + s_snap_cycles_d = s_snap_cycles_q; + s_snap_read_bytes_d = s_snap_read_bytes_q; + s_snap_write_bytes_d = s_snap_write_bytes_q; + s_snap_read_stalls_d = s_snap_read_stalls_q; + s_snap_write_stalls_d = s_snap_write_stalls_q; + s_snap_pipe_stalls_d = s_snap_pipe_stalls_q; + s_snap_lines_done_d = s_snap_lines_done_q; if (s_err_clear) begin s_err_d = '0; @@ -277,10 +320,25 @@ module ga2d_reg ( end if (s_cmd_soft_reset) begin - s_config_d = ConfigReset; - s_irq_state_d = '0; - s_irq_en_d = '0; - s_err_d = '0; + s_config_d = ConfigReset; + s_irq_state_d = '0; + s_irq_en_d = '0; + s_err_d = '0; + s_snap_cycles_d = '0; + s_snap_read_bytes_d = '0; + s_snap_write_bytes_d = '0; + s_snap_read_stalls_d = '0; + s_snap_write_stalls_d = '0; + s_snap_pipe_stalls_d = '0; + s_snap_lines_done_d = '0; + end else if (s_cmd_snapshot) begin + s_snap_cycles_d = cycles_i; + s_snap_read_bytes_d = read_bytes_i; + s_snap_write_bytes_d = write_bytes_i; + s_snap_read_stalls_d = read_stalls_i; + s_snap_write_stalls_d = write_stalls_i; + s_snap_pipe_stalls_d = pipe_stalls_i; + s_snap_lines_done_d = lines_done_i; end else if (s_write && !s_write_err) begin unique case (s_offset) `APB4_GA2D__IRQ_ENABLE: s_irq_en_d = s_write_value[2:0]; @@ -369,4 +427,60 @@ module ga2d_reg ( .dat_i (s_irq_en_d), .dat_o (s_irq_en_q) ); + dffr #( + .DATA_WIDTH(64) + ) u_snap_cycles ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_snap_cycles_d), + .dat_o (s_snap_cycles_q) + ); + dffr #( + .DATA_WIDTH(64) + ) u_snap_read_bytes ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_snap_read_bytes_d), + .dat_o (s_snap_read_bytes_q) + ); + dffr #( + .DATA_WIDTH(64) + ) u_snap_write_bytes ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_snap_write_bytes_d), + .dat_o (s_snap_write_bytes_q) + ); + dffr #( + .DATA_WIDTH(64) + ) u_snap_read_stalls ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_snap_read_stalls_d), + .dat_o (s_snap_read_stalls_q) + ); + dffr #( + .DATA_WIDTH(64) + ) u_snap_write_stalls ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_snap_write_stalls_d), + .dat_o (s_snap_write_stalls_q) + ); + dffr #( + .DATA_WIDTH(64) + ) u_snap_pipe_stalls ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_snap_pipe_stalls_d), + .dat_o (s_snap_pipe_stalls_q) + ); + dffr #( + .DATA_WIDTH(16) + ) u_snap_lines_done ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_snap_lines_done_d), + .dat_o (s_snap_lines_done_q) + ); endmodule diff --git a/rtl/mini/filelist/ip.fl b/rtl/mini/filelist/ip.fl index 9ed7e0d3..879c8e75 100644 --- a/rtl/mini/filelist/ip.fl +++ b/rtl/mini/filelist/ip.fl @@ -145,6 +145,11 @@ /ip/multimedia/apu_reg.sv /ip/multimedia/apb4_apu.sv /ip/multimedia/ga2d_pkg.sv +/ip/multimedia/ga2d_addr_gen.sv +/ip/multimedia/ga2d_axi4_master.sv +/ip/multimedia/ga2d_dma.sv +/ip/multimedia/ga2d_pixel.sv +/ip/multimedia/ga2d_core.sv /ip/multimedia/ga2d_reg.sv /ip/multimedia/apb4_ga2d.sv /ip/multimedia/jpeg_pkg.sv diff --git a/rtl/mini/formal/ga2d_formal.sv b/rtl/mini/formal/ga2d_formal.sv new file mode 100644 index 00000000..da7b78c0 --- /dev/null +++ b/rtl/mini/formal/ga2d_formal.sv @@ -0,0 +1,385 @@ +`ifdef GA2D_FORMAL_PROPERTIES +// Copyright (c) 2026 Yuchi Miao +// retroSoC is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +module ga2d_formal; + // verilog_format: off -- protocol observations remain grouped by AXI channel. + (* anyseq *) (* gclk *) reg clk_i; + wire rst_n_i; + wire f_past_valid; + wire [3:0] scenario; + wire start_i; + wire resource_stop_i; + wire bridge_clear_busy_i; + wire [7:0] bridge_epoch_i; + wire busy; + wire draining; + wire done; + wire aborted; + wire error; + wire recovery_required; + wire safe_idle; + wire [2:0] irq_event; + wire error_valid; + wire [6:0] error_code; + wire [3:0] error_stage; + wire [1:0] error_axi_response; + wire [31:0] error_address; + wire [63:0] read_bytes; + wire [63:0] write_bytes; + wire [15:0] lines_done; + wire [5:0] foreground_fifo_count; + wire [5:0] background_fifo_count; + wire [5:0] output_fifo_count; + wire read_reserved; + wire awvalid; + wire awready; + wire [2:0] awid; + wire [31:0] awaddr; + wire [7:0] awlen; + wire [2:0] awsize; + wire [1:0] awburst; + wire awlock; + wire [3:0] awcache; + wire [2:0] awprot; + wire [3:0] awqos; + wire [3:0] awregion; + wire awuser; + wire wvalid; + wire wready; + wire [63:0] wdata; + wire [7:0] wstrb; + wire wlast; + wire wuser; + wire bvalid; + wire bready; + wire [2:0] bid; + wire [1:0] bresp; + wire buser; + wire arvalid; + wire arready; + wire [2:0] arid; + wire [31:0] araddr; + wire [7:0] arlen; + wire [2:0] arsize; + wire [1:0] arburst; + wire arlock; + wire [3:0] arcache; + wire [2:0] arprot; + wire [3:0] arqos; + wire [3:0] arregion; + wire aruser; + wire rvalid; + wire rready; + wire [2:0] rid; + wire [63:0] rdata; + wire [1:0] rresp; + wire rlast; + wire ruser; + wire protocol_residual_rvalid; + // verilog_format: on + + logic s_read_inflight_q; + logic s_write_inflight_q; + logic [8:0] s_read_beats_q; + logic [8:0] s_write_beats_q; + logic s_stop_seen_q; + logic [7:0] s_stop_age_q; + + ga2d_formal_design u_design ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .f_past_valid (f_past_valid), + .scenario (scenario), + .start_i (start_i), + .resource_stop_i (resource_stop_i), + .bridge_clear_busy_i (bridge_clear_busy_i), + .bridge_epoch_i (bridge_epoch_i), + .busy (busy), + .draining (draining), + .done (done), + .aborted (aborted), + .error (error), + .recovery_required (recovery_required), + .safe_idle (safe_idle), + .irq_event (irq_event), + .error_valid (error_valid), + .error_code (error_code), + .error_stage (error_stage), + .error_axi_response (error_axi_response), + .error_address (error_address), + .read_bytes (read_bytes), + .write_bytes (write_bytes), + .lines_done (lines_done), + .foreground_fifo_count (foreground_fifo_count), + .background_fifo_count (background_fifo_count), + .output_fifo_count (output_fifo_count), + .read_reserved (read_reserved), + .awvalid (awvalid), + .awready (awready), + .awid (awid), + .awaddr (awaddr), + .awlen (awlen), + .awsize (awsize), + .awburst (awburst), + .awlock (awlock), + .awcache (awcache), + .awprot (awprot), + .awqos (awqos), + .awregion (awregion), + .awuser (awuser), + .wvalid (wvalid), + .wready (wready), + .wdata (wdata), + .wstrb (wstrb), + .wlast (wlast), + .wuser (wuser), + .bvalid (bvalid), + .bready (bready), + .bid (bid), + .bresp (bresp), + .buser (buser), + .arvalid (arvalid), + .arready (arready), + .arid (arid), + .araddr (araddr), + .arlen (arlen), + .arsize (arsize), + .arburst (arburst), + .arlock (arlock), + .arcache (arcache), + .arprot (arprot), + .arqos (arqos), + .arregion (arregion), + .aruser (aruser), + .rvalid (rvalid), + .rready (rready), + .rid (rid), + .rdata (rdata), + .rresp (rresp), + .rlast (rlast), + .ruser (ruser), + .protocol_residual_rvalid(protocol_residual_rvalid) + ); + + always @(posedge clk_i) begin + if (!rst_n_i) begin + s_read_inflight_q <= 1'b0; + s_write_inflight_q <= 1'b0; + s_read_beats_q <= '0; + s_write_beats_q <= '0; + s_stop_seen_q <= 1'b0; + s_stop_age_q <= '0; + end else begin + if (resource_stop_i) begin + s_stop_seen_q <= 1'b1; + end + + if (bridge_clear_busy_i) begin + s_read_inflight_q <= 1'b0; + s_write_inflight_q <= 1'b0; + s_read_beats_q <= '0; + s_write_beats_q <= '0; + end else begin + if (arvalid && arready) begin + assert (!s_read_inflight_q); + s_read_inflight_q <= 1'b1; + s_read_beats_q <= {1'b0, arlen} + 1'b1; + end + if (rvalid && rready) begin + assert (s_read_inflight_q); + assert (s_read_beats_q != 0); + assert (rlast == (s_read_beats_q == 9'd1)); + if (rlast) begin + s_read_inflight_q <= 1'b0; + s_read_beats_q <= '0; + end else begin + s_read_beats_q <= s_read_beats_q - 1'b1; + end + end + + if (awvalid && awready) begin + assert (!s_write_inflight_q); + s_write_inflight_q <= 1'b1; + s_write_beats_q <= {1'b0, awlen} + 1'b1; + end + if (wvalid && wready) begin + assert (s_write_inflight_q); + assert (s_write_beats_q != 0); + assert (wlast == (s_write_beats_q == 9'd1)); + if (wlast) begin + s_write_beats_q <= '0; + end else begin + s_write_beats_q <= s_write_beats_q - 1'b1; + end + end + if (bvalid && bready) begin + assert (s_write_inflight_q); + assert (s_write_beats_q == 0); + s_write_inflight_q <= 1'b0; + end + end + + if (!s_stop_seen_q) begin + s_stop_age_q <= '0; + end else if (s_stop_age_q != 8'hff) begin + s_stop_age_q <= s_stop_age_q + 1'b1; + end + end + + if (rst_n_i && f_past_valid && $past(f_past_valid)) begin + if ($past(awvalid && !awready) && !$past(bridge_clear_busy_i) && !bridge_clear_busy_i) begin + assert (awvalid); + assert (awid == $past(awid)); + assert (awaddr == $past(awaddr)); + assert (awlen == $past(awlen)); + assert (awsize == $past(awsize)); + assert (awburst == $past(awburst)); + assert (awlock == $past(awlock)); + assert (awcache == $past(awcache)); + assert (awprot == $past(awprot)); + assert (awqos == $past(awqos)); + assert (awregion == $past(awregion)); + assert (awuser == $past(awuser)); + end + if ($past(wvalid && !wready) && !$past(bridge_clear_busy_i) && !bridge_clear_busy_i) begin + assert (wvalid); + assert (wdata == $past(wdata)); + assert (wstrb == $past(wstrb)); + assert (wlast == $past(wlast)); + assert (wuser == $past(wuser)); + end + if ($past(arvalid && !arready) && !$past(bridge_clear_busy_i) && !bridge_clear_busy_i) begin + assert (arvalid); + assert (arid == $past(arid)); + assert (araddr == $past(araddr)); + assert (arlen == $past(arlen)); + assert (arsize == $past(arsize)); + assert (arburst == $past(arburst)); + assert (arlock == $past(arlock)); + assert (arcache == $past(arcache)); + assert (arprot == $past(arprot)); + assert (arqos == $past(arqos)); + assert (arregion == $past(arregion)); + assert (aruser == $past(aruser)); + end + if ($past(bvalid && !bready)) begin + assert (bvalid); + assert (bid == $past(bid)); + assert (bresp == $past(bresp)); + assert (buser == $past(buser)); + end + if ($past(rvalid && !rready)) begin + assert (rvalid); + assert (rid == $past(rid)); + assert (rdata == $past(rdata)); + assert (rresp == $past(rresp)); + assert (rlast == $past(rlast)); + assert (ruser == $past(ruser)); + end + if (resource_stop_i && !$past(awvalid)) begin + assert (!awvalid); + end + if (resource_stop_i && !$past(arvalid)) begin + assert (!arvalid); + end + end + + if (rst_n_i) begin + if (awvalid) begin + assert (awid == 3'd0); + assert (awsize == 3'd3); + assert (awlen <= 8'd15); + assert (output_fifo_count >= ({1'b0, awlen} + 9'd1)); + assert (awburst == 2'b01); + assert (awaddr[2:0] == 3'd0); + assert (awlock == 1'b0); + assert (awcache == 4'd0); + assert (awprot == 3'd0); + assert (awqos == 4'd0); + assert (awregion == 4'd0); + assert (awuser == 1'b0); + assert ({1'b0, awaddr[11:0]} + (({5'd0, awlen} + 13'd1) << 3) <= 13'd4096); + end + if (arvalid) begin + assert (arid == 3'd0); + assert (arlen <= 8'd15); + assert ((arlen == 8'd0) || (arsize == 3'd3)); + assert ((araddr & ((32'd1 << arsize) - 1'b1)) == 32'd0); + assert (arburst == 2'b01); + assert (arlock == 1'b0); + assert (arcache == 4'd0); + assert (arprot == 3'd0); + assert (arqos == 4'd0); + assert (arregion == 4'd0); + assert (aruser == 1'b0); + assert ({1'b0, araddr[11:0]} + (({5'd0, arlen} + 13'd1) << arsize) <= 13'd4096); + end + if (wvalid) begin + assert (wstrb != 8'd0); + end + assert (foreground_fifo_count <= 6'd32); + assert (background_fifo_count <= 6'd32); + assert (output_fifo_count <= 6'd32); + if (arvalid) begin + assert (read_reserved); + assert ({3'd0, foreground_fifo_count} + ({1'b0, arlen} + 9'd1) <= 9'd32); + end + if (s_read_inflight_q && !bridge_clear_busy_i) begin + assert (read_reserved); + assert ({3'd0, foreground_fifo_count} + s_read_beats_q <= 9'd32); + end + if (safe_idle) begin + assert (!busy); + assert (!draining); + assert (!s_read_inflight_q); + assert (!s_write_inflight_q); + assert (foreground_fifo_count == 6'd0); + assert (background_fifo_count == 6'd0); + assert (output_fifo_count == 6'd0); + assert (!read_reserved); + end + if (error) begin + assert (!done); + end + assume (scenario <= 4'd8); + if (scenario == 4'd5) begin + assert (!done); + end + if (scenario == 4'd3 && s_stop_seen_q && (s_stop_age_q >= 8'd20)) begin + assert (safe_idle); + assert (aborted); + end + if ((scenario == 4'd8) && protocol_residual_rvalid) begin + assert (error); + assert (recovery_required); + assert (read_reserved); + assert (!rready); + assert (!arvalid); + assert (!safe_idle); + end + + cover (scenario == 4'd0 && done && safe_idle && (write_bytes == 64'd2)); + cover (scenario == 4'd1 && done && safe_idle && + (read_bytes == 64'd2) && (write_bytes == 64'd2)); + cover (scenario == 4'd3 && aborted && safe_idle); + cover (scenario == 4'd4 && error && recovery_required && safe_idle); + cover (scenario == 4'd5 && error && error_valid); + cover (scenario == 4'd2 && awvalid && awready && (awlen == 8'd14)); + cover (scenario == 4'd7 && awvalid && awready && (awlen == 8'd15)); + cover ((scenario == 4'd8) && protocol_residual_rvalid && error && + recovery_required && !rready && !arvalid && !safe_idle); + cover (awvalid && !awready); + cover (wvalid && !wready); + cover (arvalid && !arready); + end + end +endmodule +`endif diff --git a/rtl/mini/formal/ga2d_formal_props.sv b/rtl/mini/formal/ga2d_formal_props.sv new file mode 100644 index 00000000..4a24aa9f --- /dev/null +++ b/rtl/mini/formal/ga2d_formal_props.sv @@ -0,0 +1,391 @@ +`ifdef GA2D_FORMAL_DESIGN +// Copyright (c) 2026 Yuchi Miao +// retroSoC is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +module ga2d_formal_design ( + // verilog_format: off -- AXI observations are grouped by channel. + input logic clk_i, + output logic rst_n_i, + output logic f_past_valid, + output logic [3:0] scenario, + output logic start_i, + output logic resource_stop_i, + output logic bridge_clear_busy_i, + output logic [7:0] bridge_epoch_i, + output logic busy, + output logic draining, + output logic done, + output logic aborted, + output logic error, + output logic recovery_required, + output logic safe_idle, + output logic [2:0] irq_event, + output logic error_valid, + output logic [6:0] error_code, + output logic [3:0] error_stage, + output logic [1:0] error_axi_response, + output logic [31:0] error_address, + output logic [63:0] read_bytes, + output logic [63:0] write_bytes, + output logic [15:0] lines_done, + output logic [5:0] foreground_fifo_count, + output logic [5:0] background_fifo_count, + output logic [5:0] output_fifo_count, + output logic read_reserved, + output logic awvalid, + output logic awready, + output logic [2:0] awid, + output logic [31:0] awaddr, + output logic [7:0] awlen, + output logic [2:0] awsize, + output logic [1:0] awburst, + output logic awlock, + output logic [3:0] awcache, + output logic [2:0] awprot, + output logic [3:0] awqos, + output logic [3:0] awregion, + output logic awuser, + output logic wvalid, + output logic wready, + output logic [63:0] wdata, + output logic [7:0] wstrb, + output logic wlast, + output logic wuser, + output logic bvalid, + output logic bready, + output logic [2:0] bid, + output logic [1:0] bresp, + output logic buser, + output logic arvalid, + output logic arready, + output logic [2:0] arid, + output logic [31:0] araddr, + output logic [7:0] arlen, + output logic [2:0] arsize, + output logic [1:0] arburst, + output logic arlock, + output logic [3:0] arcache, + output logic [2:0] arprot, + output logic [3:0] arqos, + output logic [3:0] arregion, + output logic aruser, + output logic rvalid, + output logic rready, + output logic [2:0] rid, + output logic [63:0] rdata, + output logic [1:0] rresp, + output logic rlast, + output logic ruser, + output logic protocol_residual_rvalid + // verilog_format: on +); + // Properties are parsed separately from design.v, where this Yosys frontend + // cannot import ga2d_pkg; preserve the packed production port layout here. + typedef struct packed { + logic [31:0] timeout_cycles; + logic [31:0] job_config; + logic [31:0] global_alpha; + logic [31:0] color; + logic [31:0] size; + logic [31:0] fg_address; + logic [31:0] fg_pitch; + logic [31:0] fg_format; + logic [31:0] bg_address; + logic [31:0] bg_pitch; + logic [31:0] bg_format; + logic [31:0] dst_address; + logic [31:0] dst_pitch; + logic [31:0] dst_format; + } formal_ga2d_config_t; + + localparam logic [31:0] FormalSramBase = 32'h3000_0000; + localparam logic [1:0] OperationFill = 2'd0; + localparam logic [1:0] OperationCopy = 2'd1; + localparam logic [2:0] FormatRgb565 = 3'd0; + localparam logic [1:0] AxiRespOkay = 2'b00; + localparam logic [1:0] AxiRespSlverr = 2'b10; + + (* anyconst *)logic [3:0] f_scenario; + logic [7:0] s_cycle_q; + logic s_stop_after_aw_q; + logic s_ar_stalled_q; + logic s_aw_stalled_q; + logic s_w_stalled_q; + logic s_read_active_q; + logic [8:0] s_read_beats_q; + logic s_bad_read_sent_q; + logic s_residual_r_pending_q; + logic s_residual_r_valid_q; + logic [1:0] s_residual_r_delay_q; + logic s_write_active_q; + logic [8:0] s_write_beats_q; + logic s_bvalid_q; + logic [1:0] s_bresp_q; + formal_ga2d_config_t s_config; + + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(64), + .ID_WIDTH (3), + .USER_WIDTH(1) + ) axi4 ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + + assign scenario = f_scenario; + assign start_i = rst_n_i && (s_cycle_q == 8'd0); + assign resource_stop_i = (f_scenario == 4'd3) && s_stop_after_aw_q; + assign bridge_clear_busy_i = (f_scenario == 4'd4) && (s_cycle_q == 8'd4); + assign bridge_epoch_i = ((f_scenario == 4'd4) && (s_cycle_q >= 8'd5)) ? 8'd1 : 8'd0; + + always_comb begin + s_config = '0; + s_config.timeout_cycles = 32'd64; + s_config.job_config = {30'd0, OperationFill}; + s_config.color = 32'h7a12_3456; + s_config.size = {16'd1, 16'd1}; + s_config.dst_address = FormalSramBase + 32'h40; + s_config.dst_pitch = 32'd2; + s_config.dst_format = {29'd0, FormatRgb565}; + if (f_scenario == 4'd2) begin + s_config.size = {16'd1, 16'd60}; + s_config.dst_pitch = 32'd120; + end + if (f_scenario == 4'd7) begin + s_config.size = {16'd1, 16'd64}; + s_config.dst_pitch = 32'd128; + end + if ((f_scenario == 4'd1) || (f_scenario == 4'd6) || (f_scenario == 4'd8)) begin + s_config.job_config = {30'd0, OperationCopy}; + s_config.fg_address = FormalSramBase + 32'h20; + s_config.fg_pitch = 32'd2; + s_config.fg_format = {29'd0, FormatRgb565}; + end + end + + assign axi4.arready = !s_read_active_q && !s_residual_r_pending_q && + !s_residual_r_valid_q && (!axi4.arvalid || s_ar_stalled_q); + assign axi4.rid = ((f_scenario == 4'd8) && s_read_active_q && !s_bad_read_sent_q) ? 3'd1 : 3'd0; + assign axi4.rdata = 64'h8877_6655_4433_2211; + assign axi4.rresp = (f_scenario == 4'd6) ? AxiRespSlverr : AxiRespOkay; + assign axi4.rlast = s_residual_r_valid_q || (s_read_active_q && (s_read_beats_q == 9'd1)); + assign axi4.ruser = 1'b0; + assign axi4.rvalid = s_read_active_q || s_residual_r_valid_q; + assign axi4.awready = !s_write_active_q && !s_bvalid_q && (!axi4.awvalid || s_aw_stalled_q); + assign axi4.wready = s_write_active_q && !s_bvalid_q && (!axi4.wvalid || s_w_stalled_q); + assign axi4.bid = 3'd0; + assign axi4.bresp = s_bresp_q; + assign axi4.buser = 1'b0; + assign axi4.bvalid = s_bvalid_q; + + assign busy = u_dut.busy_o; + assign draining = u_dut.draining_o; + assign done = u_dut.done_o; + assign aborted = u_dut.aborted_o; + assign error = u_dut.error_o; + assign recovery_required = u_dut.recovery_required_o; + assign safe_idle = u_dut.safe_idle_o; + assign irq_event = u_dut.irq_event_o; + assign error_valid = u_dut.error_valid_o; + assign error_code = u_dut.error_code_o; + assign error_stage = u_dut.error_stage_o; + assign error_axi_response = u_dut.error_axi_response_o; + assign error_address = u_dut.error_address_o; + assign read_bytes = u_dut.read_bytes_o; + assign write_bytes = u_dut.write_bytes_o; + assign lines_done = u_dut.lines_done_o; + assign foreground_fifo_count = u_dut.u_dma.unused_fg_fifo_count; + assign background_fifo_count = u_dut.u_dma.unused_bg_fifo_count; + assign output_fifo_count = u_dut.u_dma.unused_output_fifo_count; + assign read_reserved = u_dut.u_dma.s_read_reserved_q; + assign awvalid = axi4.awvalid; + assign awready = axi4.awready; + assign awid = axi4.awid; + assign awaddr = axi4.awaddr; + assign awlen = axi4.awlen; + assign awsize = axi4.awsize; + assign awburst = axi4.awburst; + assign awlock = axi4.awlock; + assign awcache = axi4.awcache; + assign awprot = axi4.awprot; + assign awqos = axi4.awqos; + assign awregion = axi4.awregion; + assign awuser = axi4.awuser; + assign wvalid = axi4.wvalid; + assign wready = axi4.wready; + assign wdata = axi4.wdata; + assign wstrb = axi4.wstrb; + assign wlast = axi4.wlast; + assign wuser = axi4.wuser; + assign bvalid = axi4.bvalid; + assign bready = axi4.bready; + assign bid = axi4.bid; + assign bresp = axi4.bresp; + assign buser = axi4.buser; + assign arvalid = axi4.arvalid; + assign arready = axi4.arready; + assign arid = axi4.arid; + assign araddr = axi4.araddr; + assign arlen = axi4.arlen; + assign arsize = axi4.arsize; + assign arburst = axi4.arburst; + assign arlock = axi4.arlock; + assign arcache = axi4.arcache; + assign arprot = axi4.arprot; + assign arqos = axi4.arqos; + assign arregion = axi4.arregion; + assign aruser = axi4.aruser; + assign rvalid = axi4.rvalid; + assign rready = axi4.rready; + assign rid = axi4.rid; + assign rdata = axi4.rdata; + assign rresp = axi4.rresp; + assign rlast = axi4.rlast; + assign ruser = axi4.ruser; + assign protocol_residual_rvalid = s_residual_r_valid_q; + + ga2d_core u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .start_i (start_i), + .abort_i (1'b0), + .soft_reset_i (1'b0), + .resource_stop_i (resource_stop_i), + .bridge_clear_busy_i (bridge_clear_busy_i), + .bridge_epoch_i (bridge_epoch_i), + .data_ready_i (1'b1), + .mem_pad_mode_i (2'd0), + .config_i (s_config), + .busy_o (), + .draining_o (), + .done_o (), + .aborted_o (), + .error_o (), + .recovery_required_o (), + .safe_idle_o (), + .irq_event_o (), + .error_valid_o (), + .error_code_o (), + .error_stage_o (), + .error_axi_response_o(), + .error_address_o (), + .cycles_o (), + .read_bytes_o (), + .write_bytes_o (), + .read_stalls_o (), + .write_stalls_o (), + .pipe_stalls_o (), + .lines_done_o (), + .axi4 (axi4) + ); + + initial begin + rst_n_i = 1'b0; + f_past_valid = 1'b0; + end + + always_ff @(posedge clk_i) begin + rst_n_i <= 1'b1; + f_past_valid <= 1'b1; + if (!rst_n_i) begin + s_cycle_q <= '0; + s_stop_after_aw_q <= 1'b0; + s_ar_stalled_q <= 1'b0; + s_aw_stalled_q <= 1'b0; + s_w_stalled_q <= 1'b0; + s_read_active_q <= 1'b0; + s_read_beats_q <= '0; + s_bad_read_sent_q <= 1'b0; + s_residual_r_pending_q <= 1'b0; + s_residual_r_valid_q <= 1'b0; + s_residual_r_delay_q <= '0; + s_write_active_q <= 1'b0; + s_write_beats_q <= '0; + s_bvalid_q <= 1'b0; + s_bresp_q <= AxiRespOkay; + end else begin + s_cycle_q <= s_cycle_q + 1'b1; + if (axi4.awvalid && !axi4.awready) begin + s_aw_stalled_q <= 1'b1; + if (f_scenario == 4'd3) begin + s_stop_after_aw_q <= 1'b1; + end + end + if (axi4.wvalid && !axi4.wready) begin + s_w_stalled_q <= 1'b1; + end + if (axi4.arvalid && !axi4.arready) begin + s_ar_stalled_q <= 1'b1; + end + + if (bridge_clear_busy_i) begin + s_read_active_q <= 1'b0; + s_read_beats_q <= '0; + s_bad_read_sent_q <= 1'b0; + s_residual_r_pending_q <= 1'b0; + s_residual_r_valid_q <= 1'b0; + s_residual_r_delay_q <= '0; + s_write_active_q <= 1'b0; + s_write_beats_q <= '0; + s_bvalid_q <= 1'b0; + end else begin + if (axi4.arvalid && axi4.arready) begin + s_read_active_q <= 1'b1; + s_read_beats_q <= {1'b0, axi4.arlen} + 1'b1; + end + if (s_read_active_q && axi4.rvalid && axi4.rready) begin + if ((f_scenario == 4'd8) && !s_bad_read_sent_q) begin + s_read_active_q <= 1'b0; + s_read_beats_q <= '0; + s_bad_read_sent_q <= 1'b1; + s_residual_r_pending_q <= 1'b1; + s_residual_r_delay_q <= 2'd2; + end else if (s_read_beats_q == 9'd1) begin + s_read_active_q <= 1'b0; + s_read_beats_q <= '0; + end else begin + s_read_beats_q <= s_read_beats_q - 1'b1; + end + end + if (s_residual_r_pending_q) begin + if (s_residual_r_delay_q == 2'd0) begin + s_residual_r_pending_q <= 1'b0; + s_residual_r_valid_q <= 1'b1; + end else begin + s_residual_r_delay_q <= s_residual_r_delay_q - 1'b1; + end + end + if (s_residual_r_valid_q && axi4.rready) begin + s_residual_r_valid_q <= 1'b0; + end + + if (axi4.awvalid && axi4.awready) begin + s_write_active_q <= 1'b1; + s_write_beats_q <= {1'b0, axi4.awlen} + 1'b1; + end + if (s_write_active_q && axi4.wvalid && axi4.wready) begin + if (s_write_beats_q == 9'd1) begin + s_write_active_q <= 1'b0; + s_write_beats_q <= '0; + s_bvalid_q <= 1'b1; + s_bresp_q <= (f_scenario == 4'd5) ? AxiRespSlverr : AxiRespOkay; + end else begin + s_write_beats_q <= s_write_beats_q - 1'b1; + end + end + if (s_bvalid_q && axi4.bready) begin + s_bvalid_q <= 1'b0; + end + end + end + end +endmodule +`endif diff --git a/rtl/mini/formal/generate_formal_filelist.py b/rtl/mini/formal/generate_formal_filelist.py index 0482eb6e..8f8d793c 100644 --- a/rtl/mini/formal/generate_formal_filelist.py +++ b/rtl/mini/formal/generate_formal_filelist.py @@ -63,6 +63,8 @@ def target_defines(target: str) -> list[str]: return ["+define+PDK_BEHAV"] if target == "sysctrl": return ["+define+SV_ASSRT_DISABLE", "+define+MINI_PRODUCT"] + if target == "ga2d": + return ["+define+SV_ASSRT_DISABLE", "+define+GA2D_FORMAL_DESIGN"] return ["+define+SV_ASSRT_DISABLE"] @@ -278,6 +280,24 @@ def source_files(target: str) -> list[Path]: PERIPHERAL / "dma_core.sv", SCRIPT_DIR / "dma_formal.sv", ] + if target == "ga2d": + return [ + COMMON_RTL / "interface/apb4_if.sv", + COMMON_RTL / "interface/axi4_if.sv", + COMMON_RTL / "utils/register.sv", + COMMON_RTL / "utils/fifo.sv", + COMMON_RTL / "stream/round_robin_arbiter.sv", + MULTIMEDIA / "ga2d_pkg.sv", + MULTIMEDIA / "ga2d_addr_gen.sv", + MULTIMEDIA / "ga2d_axi4_master.sv", + MULTIMEDIA / "ga2d_dma.sv", + MULTIMEDIA / "ga2d_pixel.sv", + MULTIMEDIA / "ga2d_core.sv", + MULTIMEDIA / "ga2d_reg.sv", + MULTIMEDIA / "apb4_ga2d.sv", + SCRIPT_DIR / "ga2d_formal_props.sv", + SCRIPT_DIR / "ga2d_formal.sv", + ] if target == "apu": return [ COMMON_RTL / "interface/axi4_if.sv", @@ -395,6 +415,7 @@ def parse_args() -> argparse.Namespace: "onchip_ram", "opipsram", "dma", + "ga2d", "apu", "apu_codec", "apu_primitives", diff --git a/rtl/mini/formal/generate_sby_config.py b/rtl/mini/formal/generate_sby_config.py index 179a5107..d3710a39 100644 --- a/rtl/mini/formal/generate_sby_config.py +++ b/rtl/mini/formal/generate_sby_config.py @@ -23,12 +23,17 @@ def render( depth: int, vcd: bool = True, skip: int | None = None, + properties_define: str | None = None, ) -> str: options = ["[options]", f"mode {mode}", f"depth {depth}"] if skip is not None: options.append(f"skip {skip}") if not vcd: options.append("vcd off") + properties_read = "read_verilog -formal -sv" + if properties_define is not None: + properties_read += f" -D{properties_define}" + properties_read += " properties.v" return "\n".join( tuple(options) + ( @@ -38,7 +43,7 @@ def render( "", "[script]", "read_verilog -formal -sv design.v", - "read_verilog -formal -sv properties.v", + properties_read, f"prep -top {top}", "async2sync", "dffunmap", @@ -69,6 +74,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--depth", type=positive_int, required=True) parser.add_argument("--no-vcd", action="store_false", dest="vcd") parser.add_argument("--skip", type=positive_int) + parser.add_argument("--properties-define") parser.add_argument("--output", type=Path, required=True) return parser.parse_args() @@ -86,6 +92,7 @@ def main() -> int: args.depth, args.vcd, args.skip, + args.properties_define, ), ) return 0 diff --git a/rtl/mini/integration/README.md b/rtl/mini/integration/README.md index 8a43456d..f18a18d7 100644 --- a/rtl/mini/integration/README.md +++ b/rtl/mini/integration/README.md @@ -5,13 +5,14 @@ that is not part of the software address-map ABI or the package pad map. [GA2D](../../../docs/ip/ga2d.md) Phase 2 expands the topology to data master/resource 8 and seven-bit global IDs while retaining all existing -allocations. It adds a dedicated PCLK-to-HP AXI64/ID3 GA2D bridge whose source -remains idle. Phase 3 activates `APB4_GA2D` at slot 28 as a PCLK register -shell and routes its raw IRQ through resource 8: LP group bit 24, core vector -bit 32/external ordinal 30, or HP PLIC source 11 according to owner. The shell -exposes IRQ-only capability; payload/DMA, pixel work, and START remain deferred. -The Xh3irq SDK path also requires the Phase 1 implementation; a wiring-only -expansion is insufficient. +allocations. Phase 4 drives its dedicated PCLK-to-HP AXI64/ID3 bridge with the +direct single-job private-AXI64 FILL/COPY DMA engine at `APB4_GA2D` slot 28. +Its raw IRQ is routed through resource 8: LP group bit 24, core vector +bit 32/external ordinal 30, or HP PLIC source 11 according to owner. The +engine supports RGB565, RGB888, XRGB8888, and ARGB8888 with pitch and byte-edge +handling; CONVERT, BLEND, A8, in-place background composition, and +descriptor/ring/queue submission remain unavailable. The Xh3irq SDK path also +requires the Phase 1 implementation; a wiring-only expansion is insufficient. The topology generator validates APB4 island ownership against `../address_map/memory_map.json` and emits generated SystemVerilog include diff --git a/rtl/mini/mk/formal.mk b/rtl/mini/mk/formal.mk index b9ef51d6..587201c3 100644 --- a/rtl/mini/mk/formal.mk +++ b/rtl/mini/mk/formal.mk @@ -14,6 +14,8 @@ FORMAL_OPIPSRAM_DEPTH ?= 40 FORMAL_OPIPSRAM_BMC_DEPTH ?= 40 FORMAL_DMA_DEPTH ?= 24 FORMAL_DMA_COVER_DEPTH ?= 32 +FORMAL_GA2D_DEPTH ?= 36 +FORMAL_GA2D_COVER_DEPTH ?= 48 FORMAL_APU_DEPTH ?= 24 FORMAL_APU_CODEC_DEPTH ?= 32 FORMAL_APU_LOADER_DEPTH ?= 128 @@ -28,17 +30,20 @@ FORMAL_I2C_TIMEOUT ?= 300 FORMAL_OPIPSRAM_TIMEOUT ?= 300 FORMAL_OPIPSRAM_BMC_TIMEOUT ?= 600 FORMAL_DMA_TIMEOUT ?= 120 +FORMAL_GA2D_TIMEOUT ?= 180 FORMAL_APU_TIMEOUT ?= 300 FORMAL_GATEWAY_A_TIMEOUT ?= 120 FORMAL_SDIO_TIMEOUT ?= 120 FORMAL_APU_LOADER_TARGETS := apu_loader_success apu_loader_header_range apu_loader_descriptor_range apu_loader_crc apu_loader_control_flow apu_loader_abort apu_loader_resource_reset FORMAL_APU_PRIMITIVE_TARGETS := apu_primitives_invalid_read apu_primitives_local apu_primitives_local_overflow apu_primitives_kernel apu_primitives_input_fifo apu_primitives_alignment apu_primitives_output_fifo -FORMAL_TARGET_TIMEOUT = $(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter apu apu_codec apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TIMEOUT),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_TIMEOUT),$(if $(filter sdio,$*),$(FORMAL_SDIO_TIMEOUT),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_TIMEOUT),$(if $(filter ws2812,$*),$(FORMAL_WS2812_TIMEOUT),$(if $(filter i2c,$*),$(FORMAL_I2C_TIMEOUT),$(FORMAL_TIMEOUT)))))))) +FORMAL_TARGET_TIMEOUT = $(if $(filter ga2d,$*),$(FORMAL_GA2D_TIMEOUT),$(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter apu apu_codec apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TIMEOUT),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_TIMEOUT),$(if $(filter sdio,$*),$(FORMAL_SDIO_TIMEOUT),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_TIMEOUT),$(if $(filter ws2812,$*),$(FORMAL_WS2812_TIMEOUT),$(if $(filter i2c,$*),$(FORMAL_I2C_TIMEOUT),$(FORMAL_TIMEOUT))))))))) FORMAL_APU_TARGET_DEPTH = $(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_LOADER_DEPTH),$(if $(filter apu_sequencer,$*),$(FORMAL_APU_SEQUENCER_DEPTH),$(if $(filter apu_codec,$*),$(FORMAL_APU_CODEC_DEPTH),$(FORMAL_APU_DEPTH)))) FORMAL_APU_TARGET_COVER_DEPTH = $(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_LOADER_COVER_DEPTH),$(FORMAL_APU_TARGET_DEPTH)) FORMAL_TARGET_TOP = $(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),apu_loader,$(if $(filter $(FORMAL_APU_PRIMITIVE_TARGETS),$*),apu_primitives,$*)) -FORMAL_COMPACT_COVER_TARGETS := i2c opipsram dma $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS) -FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram onchip_ram opipsram dma apu apu_codec $(FORMAL_APU_PRIMITIVE_TARGETS) $(FORMAL_APU_LOADER_TARGETS) apu_sequencer gateway_a sdio +FORMAL_PROPERTIES_SOURCE = $(if $(filter ga2d,$*),$(RTL_PATH)/formal/ga2d_formal.sv,$(RTL_PATH)/formal/$(FORMAL_TARGET_TOP)_formal_props.sv) +FORMAL_PROPERTIES_DEFINE = $(if $(filter ga2d,$*),--properties-define GA2D_FORMAL_PROPERTIES,) +FORMAL_COMPACT_COVER_TARGETS := i2c opipsram dma ga2d $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS) +FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram onchip_ram opipsram dma ga2d apu apu_codec $(FORMAL_APU_PRIMITIVE_TARGETS) $(FORMAL_APU_LOADER_TARGETS) apu_sequencer gateway_a sdio FORMAL_FILELIST_GENERATOR := $(RTL_PATH)/formal/generate_formal_filelist.py FORMAL_SBY_GENERATOR := $(RTL_PATH)/formal/generate_sby_config.py FORMAL_RESULT_GENERATOR := $(RTL_PATH)/formal/formal_results.py @@ -76,6 +81,8 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \ $(RTL_PATH)/formal/opipsram_formal_props.sv \ $(RTL_PATH)/formal/dma_formal.sv \ $(RTL_PATH)/formal/dma_formal_props.sv \ + $(RTL_PATH)/formal/ga2d_formal.sv \ + $(RTL_PATH)/formal/ga2d_formal_props.sv \ $(RTL_PATH)/formal/apu_formal.sv \ $(RTL_PATH)/formal/apu_formal_props.sv \ $(RTL_PATH)/formal/apu_codec_formal.sv \ @@ -219,15 +226,15 @@ $(FORMAL_DIR)/%/design.v: $(FORMAL_DIR)/%/formal.fl $(RTL_PATH)/script/convt_sv2 $(FORMAL_DIR)/%/prove.sby: $(FORMAL_DIR)/%/design.v \ $(FORMAL_SBY_GENERATOR) python3 $(FORMAL_SBY_GENERATOR) --top $(FORMAL_TARGET_TOP)_formal --input $< \ - --properties $(RTL_PATH)/formal/$(FORMAL_TARGET_TOP)_formal_props.sv --solver $(FORMAL_SOLVER) \ - --mode $(if $(filter dma apu apu_codec apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS) gateway_a sdio onchip_ram,$*),bmc,prove) --depth $(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_DEPTH),$(if $(filter apu apu_codec apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TARGET_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))))))) --output $@ + --properties $(FORMAL_PROPERTIES_SOURCE) $(FORMAL_PROPERTIES_DEFINE) --solver $(FORMAL_SOLVER) \ + --mode $(if $(filter ga2d,$*),bmc,$(if $(filter dma apu apu_codec apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS) gateway_a sdio onchip_ram,$*),bmc,prove)) --depth $(if $(filter ga2d,$*),$(FORMAL_GA2D_DEPTH),$(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_DEPTH),$(if $(filter apu apu_codec apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TARGET_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH)))))))))))) --output $@ @if [ "$*" = opipsram ]; then sed -i '/^async2sync/i clk2fflogic' $@; fi $(FORMAL_DIR)/%/cover.sby: $(FORMAL_DIR)/%/design.v \ $(FORMAL_SBY_GENERATOR) python3 $(FORMAL_SBY_GENERATOR) --top $(FORMAL_TARGET_TOP)_formal --input $< \ - --properties $(RTL_PATH)/formal/$(FORMAL_TARGET_TOP)_formal_props.sv --solver $(FORMAL_SOLVER) \ - --mode cover --depth $(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter apu apu_codec apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TARGET_COVER_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_COVER_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))))))) \ + --properties $(FORMAL_PROPERTIES_SOURCE) $(FORMAL_PROPERTIES_DEFINE) --solver $(FORMAL_SOLVER) \ + --mode cover --depth $(if $(filter ga2d,$*),$(FORMAL_GA2D_COVER_DEPTH),$(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter apu apu_codec apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TARGET_COVER_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_COVER_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH)))))))))))) \ $(if $(filter $(FORMAL_COMPACT_COVER_TARGETS),$*),--no-vcd) --output $@ @if [ "$*" = opipsram ]; then sed -i '/^async2sync/i clk2fflogic' $@; fi @@ -324,6 +331,8 @@ formal-opipsram: $(FORMAL_DIR)/opipsram/.stamp | manifest formal-dma: $(FORMAL_DIR)/dma/.stamp | manifest +formal-ga2d: $(FORMAL_DIR)/ga2d/.stamp | manifest + formal-apu: $(FORMAL_DIR)/apu/.stamp $(FORMAL_DIR)/apu_codec/.stamp $(addsuffix /.stamp,$(addprefix $(FORMAL_DIR)/,$(FORMAL_APU_PRIMITIVE_TARGETS))) | manifest formal-apu-loader: $(addsuffix /.stamp,$(addprefix $(FORMAL_DIR)/,$(FORMAL_APU_LOADER_TARGETS))) | manifest @@ -340,4 +349,4 @@ formal-doctor: formal-clean: python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(FORMAL_DIR) -.PHONY: formal formal-bus formal-rib-adapter formal-rib2apb formal-sysctrl formal-pll-rcu formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-clint formal-dvp formal-i2s formal-psram formal-onchip-ram formal-opipsram formal-dma formal-apu formal-apu-loader formal-apu-sequencer formal-gateway-a formal-sdio formal-doctor formal-clean \ No newline at end of file +.PHONY: formal formal-bus formal-rib-adapter formal-rib2apb formal-sysctrl formal-pll-rcu formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-clint formal-dvp formal-i2s formal-psram formal-onchip-ram formal-opipsram formal-dma formal-ga2d formal-apu formal-apu-loader formal-apu-sequencer formal-gateway-a formal-sdio formal-doctor formal-clean \ No newline at end of file diff --git a/rtl/mini/mk/software.mk b/rtl/mini/mk/software.mk index 76ac44c5..9d440f40 100644 --- a/rtl/mini/mk/software.mk +++ b/rtl/mini/mk/software.mk @@ -103,6 +103,7 @@ CRT_SRCS := $(ROOT_PATH)/crt/arch/riscv/startup.S \ $(ROOT_PATH)/crt/src/hal/dma.c \ $(ROOT_PATH)/crt/src/hal/crypto.c \ $(ROOT_PATH)/crt/src/hal/apu.c \ + $(ROOT_PATH)/crt/src/hal/ga2d_math.c \ $(ROOT_PATH)/crt/src/hal/ga2d.c \ $(ROOT_PATH)/crt/src/hal/jpeg_math.c \ $(ROOT_PATH)/crt/src/hal/jpeg.c \ diff --git a/rtl/mini/top/apb4_periph.sv b/rtl/mini/top/apb4_periph.sv index c185da32..088291c6 100644 --- a/rtl/mini/top/apb4_periph.sv +++ b/rtl/mini/top/apb4_periph.sv @@ -40,6 +40,7 @@ module apb4_periph ( input logic ga2d_bridge_clear_busy_i, input logic [7:0] ga2d_bridge_epoch_i, input logic ga2d_data_ready_i, + input logic [1:0] mem_pad_mode_i, axi4_if.slave cfg_axi4, axi4_if.slave psram_axi4, axi4_if.slave xpi_axi4, @@ -61,6 +62,7 @@ module apb4_periph ( axi4_if.master usb2_axi4, axi4_if.master apu_axi4, axi4_if.master jpeg_axi4, + axi4_if.master ga2d_axi4, sysctrl_if.dut sysctrl, pll_ctrl_if.sysctrl pll_ctrl, clock_ctrl_if.sysctrl clock_ctrl, @@ -84,6 +86,7 @@ module apb4_periph ( output logic apu_idle_o, output logic jpeg_idle_o, output logic ga2d_idle_o, + output logic ga2d_core_safe_idle_o, output logic [7:0] resource_irq_raw_o, output logic [`SOC_IRQ_APB4_PERIPH_WIDTH-1:0] irq_o // verilog_format: on @@ -548,8 +551,11 @@ axi4_stream_if #( .bridge_clear_busy_i(ga2d_bridge_clear_busy_i), .bridge_epoch_i (ga2d_bridge_epoch_i), .data_ready_i (ga2d_data_ready_i), + .mem_pad_mode_i (mem_pad_mode_i), .apb4 (u_ga2d_apb4_if), + .ga2d_axi4 (ga2d_axi4), .idle_o (ga2d_idle_o), + .core_safe_idle_o (ga2d_core_safe_idle_o), .irq_o (s_ga2d_irq_raw) ); diff --git a/rtl/mini/top/retrosoc.sv b/rtl/mini/top/retrosoc.sv index 60c14202..2551c0a5 100644 --- a/rtl/mini/top/retrosoc.sv +++ b/rtl/mini/top/retrosoc.sv @@ -258,6 +258,7 @@ module retrosoc ( logic s_apu_idle; logic s_jpeg_idle; logic s_ga2d_idle; + logic s_ga2d_core_safe_idle; logic [ 8:0] s_resource_idle_combined; logic [ 8:0] s_resource_block_ack_combined; logic s_ga2d_source_stop; @@ -276,6 +277,8 @@ module retrosoc ( `else localparam bit SramPresent = 1'b0; `endif + // Permit the HP cache-maintenance and mailbox round trip before forced containment. + localparam logic [15:0] HpCacheHandshakeTimeout = 16'hffff; // PCLK-visible data-plane faults retain their legacy priority over local bus faults. assign u_sysctrl_if.fault_access_i = s_data_plane_fault_valid_pclk ? @@ -515,7 +518,7 @@ module retrosoc ( .hp_idle_i (s_hp_idle_aon), .flush_busy_i (s_hp_flush_busy_aon), .cache_clean_i (s_hp_cache_clean_aon), - .timeout_i (16'd1024), + .timeout_i (HpCacheHandshakeTimeout), .hp_release_o (s_hp_lifecycle_release_aon), .block_new_o (s_hp_lifecycle_block), .flush_o (s_hp_lifecycle_flush), @@ -608,6 +611,7 @@ module retrosoc ( .apu_axi4 (u_apu_axi4_if), .jpeg_axi4 (u_jpeg_wide_axi4_if), .ga2d_axi4 (u_ga2d_wide_axi4_if), + .ga2d_core_safe_idle_i (s_ga2d_core_safe_idle), .lp_data_axi4 (u_mgmt_data_axi4_if), .ext_h_axi4 (u_ext_h_wide_axi4_if), .sram_gateway_axi4 (u_data_sram_axi4_if), @@ -678,7 +682,6 @@ module retrosoc ( ); axi4_master_idle u_idle_master (.axi4(u_idle_axi4_if)); - axi4_master_idle u_ga2d_master_idle (.axi4(u_ga2d_wide_axi4_if)); axi4_master_idle u_retired_sram_master (.axi4(u_retired_data_sram_axi4_if)); axi4_master_idle u_retired_sdram_master (.axi4(u_retired_data_sdram_axi4_if)); axi4_master_idle u_retired_qpi_master (.axi4(u_retired_data_qpi_axi4_if)); @@ -878,6 +881,7 @@ module retrosoc ( .ga2d_bridge_clear_busy_i (s_ga2d_bridge_clear_busy), .ga2d_bridge_epoch_i (s_ga2d_bridge_epoch), .ga2d_data_ready_i (s_ga2d_data_ready), + .mem_pad_mode_i (s_mem_pad_mode_lp), .cfg_axi4 (u_cfg_pclk_axi4_if), .dma_axi4 (u_dma_axi4_if), .sdio0_axi4 (u_sdio0_axi4_if), @@ -885,6 +889,7 @@ module retrosoc ( .usb2_axi4 (u_usb2_axi4_if), .apu_axi4 (u_apu_axi4_if), .jpeg_axi4 (u_jpeg_wide_axi4_if), + .ga2d_axi4 (u_ga2d_wide_axi4_if), .psram_axi4 (u_data_qpi_axi4_if), .xpi_axi4 (u_data_xpi_axi4_if), .spisd_axi4 (u_spisd_axi4_if), @@ -923,6 +928,7 @@ module retrosoc ( .apu_idle_o (s_apu_idle), .jpeg_idle_o (s_jpeg_idle), .ga2d_idle_o (s_ga2d_idle), + .ga2d_core_safe_idle_o (s_ga2d_core_safe_idle), .irq_o (s_apb4_periph_irq) ); diff --git a/rtl/mini/top/soc_data_plane.sv b/rtl/mini/top/soc_data_plane.sv index 4abcbff5..d63c3abb 100644 --- a/rtl/mini/top/soc_data_plane.sv +++ b/rtl/mini/top/soc_data_plane.sv @@ -33,6 +33,8 @@ module soc_data_plane ( axi4_if.slave apu_axi4, axi4_if.slave jpeg_axi4, axi4_if.slave ga2d_axi4, + input logic ga2d_core_safe_idle_i + = 1'b1, axi4_if.slave lp_data_axi4, axi4_if.slave ext_h_axi4, axi4_if.master sram_gateway_axi4, @@ -113,6 +115,8 @@ module soc_data_plane ( logic s_ga2d_recovery_condition; logic s_ga2d_source_stop_q; logic s_ga2d_source_stop_ready; + logic s_ga2d_addr_gate_idle; + logic s_ga2d_core_safe_idle; logic s_ga2d_source_safe_idle; logic s_ga2d_write_pending; logic s_ga2d_source_safe_idle_hp; @@ -394,6 +398,8 @@ module soc_data_plane ( assign s_ga2d_recovery_condition = resource_block_i[8] || !s_ga2d_hp_reset_n_pclk || s_ga2d_flush_pclk || s_ga2d_clear_busy || (s_ga2d_epoch != s_ga2d_epoch_seen_q); + assign s_ga2d_core_safe_idle = ga2d_core_safe_idle_i; + assign s_ga2d_source_safe_idle = s_ga2d_addr_gate_idle && s_ga2d_core_safe_idle; assign s_ga2d_source_stop_ready = (!ga2d_axi4.awvalid || ga2d_axi4.awready) && (!ga2d_axi4.arvalid || ga2d_axi4.arready) && (!ga2d_axi4.wvalid || s_ga2d_write_pending); @@ -540,7 +546,9 @@ module soc_data_plane ( .src_rst_n_i (rst_lp_n_i), .dst_clk_i (clk_hp_i), .dst_rst_n_i (rst_hp_n_i), - .clear_i (flush_i), + // An HP lifecycle flush invalidates HP transport, not an LP request + // still queued before crossbar admission. + .clear_i (1'b0), .clear_busy_o(s_lp_clear_busy), .epoch_o (unused_lp_epoch), .src_axi4 (lp_data_axi4), @@ -586,7 +594,7 @@ module soc_data_plane ( .clear_i (s_ga2d_flush_pclk), .source (ga2d_axi4), .sink (u_ga2d_gated_axi4), - .idle_o (s_ga2d_source_safe_idle), + .idle_o (s_ga2d_addr_gate_idle), .write_pending_o(s_ga2d_write_pending) ); axi4_async_bridge #( diff --git a/rtl/rtl_style_audit.json b/rtl/rtl_style_audit.json index 8c6824e9..da5862ca 100644 --- a/rtl/rtl_style_audit.json +++ b/rtl/rtl_style_audit.json @@ -159,7 +159,12 @@ "dvp_core.sv", "dvp_define.svh", "dvp_reg.sv", + "ga2d_addr_gen.sv", + "ga2d_axi4_master.sv", + "ga2d_core.sv", + "ga2d_dma.sv", "ga2d_define.svh", + "ga2d_pixel.sv", "ga2d_pkg.sv", "ga2d_reg.sv", "jpeg_bit_packer.sv", diff --git a/scripts/parse_performance_log.py b/scripts/parse_performance_log.py index 45402b62..fe20272b 100644 --- a/scripts/parse_performance_log.py +++ b/scripts/parse_performance_log.py @@ -25,6 +25,17 @@ REQUIRED_FIELDS = { "psram_wait", "flash_wait", "dma_wait", + "workload_bytes", + "pixels", + "jobs", + "cpu_cycles", + "cpu_hz", + "ga2d_features", + "ga2d_limits", + "ga2d_formats", + "ga2d_cycles", + "ga2d_read_bytes", + "ga2d_write_bytes", } TOKEN = re.compile(r"([a-z0-9_]+)=([^\s]+)") @@ -48,16 +59,47 @@ def parse_log(content: str) -> dict[str, object]: "words": parse_number(fields["words"]), "checksum": int(fields["checksum"], 16), "cycles": parse_number(fields["cycles"]), + "workload_bytes": parse_number(fields["workload_bytes"]), + "pixels": parse_number(fields["pixels"]), + "jobs": parse_number(fields["jobs"]), + "cpu_cycles": parse_number(fields["cpu_cycles"]), + "ga2d_cycles": parse_number(fields["ga2d_cycles"]), + "ga2d_read_bytes": parse_number(fields["ga2d_read_bytes"]), + "ga2d_write_bytes": parse_number(fields["ga2d_write_bytes"]), + "configuration": { + "cpu_hz": parse_number(fields["cpu_hz"]), + "ga2d_features": parse_number(fields["ga2d_features"]), + "ga2d_limits": parse_number(fields["ga2d_limits"]), + "ga2d_formats": parse_number(fields["ga2d_formats"]), + }, } for counter in sorted( - REQUIRED_FIELDS - {"region", "op", "words", "checksum", "cycles"} + REQUIRED_FIELDS + - { + "region", + "op", + "words", + "checksum", + "cycles", + "workload_bytes", + "pixels", + "jobs", + "cpu_cycles", + "cpu_hz", + "ga2d_features", + "ga2d_limits", + "ga2d_formats", + "ga2d_cycles", + "ga2d_read_bytes", + "ga2d_write_bytes", + } ): sample[counter] = parse_number(fields[counter]) samples.append(sample) passed = PASS_MARKER in content failure_marker = any(line.startswith(FAIL_PREFIX) for line in content.splitlines()) return { - "schema_version": 1, + "schema_version": 2, "status": "passed" if passed and samples and not failure_marker else "failed", "failure_marker": failure_marker, "pass_marker": passed, diff --git a/scripts/run_c_tests.py b/scripts/run_c_tests.py index aedaebdb..bca5373e 100644 --- a/scripts/run_c_tests.py +++ b/scripts/run_c_tests.py @@ -34,6 +34,7 @@ TEST_SOURCES = ( "crt/src/hal/dma_math.c", "crt/src/hal/jpeg_math.c", "crt/src/hal/apu.c", + "crt/src/hal/ga2d_math.c", "crt/src/hal/ga2d.c", "crt/src/hal/fabric_monitor.c", "crt/src/hal/sysctrl.c", @@ -99,6 +100,7 @@ def main() -> int: "-DRS_APU_TEST_MMIO", "-DRS_GA2D_TEST_MMIO", "-DRS_FABRIC_MONITOR_TEST_MMIO", + "-DRS_RESOURCE_TEST_MMIO", "-I", str(memory_map_root / "include"), "-I", diff --git a/tests/c/test_runtime.c b/tests/c/test_runtime.c index 7b3ae722..db074da5 100644 --- a/tests/c/test_runtime.c +++ b/tests/c/test_runtime.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -47,11 +48,14 @@ static uint8_t storage[32]; static uint32_t image_call_count; volatile uint32_t rs_apu_test_mmio[1024]; volatile uint32_t rs_ga2d_test_mmio[1024]; +uint32_t rs_ga2d_test_mem_pad_mode; volatile uint32_t rs_fabric_monitor_test_mmio[1024]; +volatile uint32_t rs_resource_test_mmio[1024]; #define APU_TEST_REG(offset) rs_apu_test_mmio[(offset) / 4U] #define GA2D_TEST_REG(offset) rs_ga2d_test_mmio[(offset) / 4U] #define FABRIC_MONITOR_TEST_REG(offset) rs_fabric_monitor_test_mmio[(offset) / 4U] +#define RESOURCE_TEST_REG(offset) rs_resource_test_mmio[(offset) / 4U] void putch(char ch) { (void)ch; @@ -860,6 +864,7 @@ static int test_extension_validation(void) { static int test_resource_validation(void) { rs_resource_status_t status; + const uint32_t ga2d_base = UINT32_C(0x100) + ((uint32_t)RS_RESOURCE_GA2D * UINT32_C(0x20)); if ((RS_RESOURCE_DMA != 0) || (RS_RESOURCE_USB2 != 1) || (RS_RESOURCE_SDIO0 != 2) || (RS_RESOURCE_SDIO1 != 3) || (RS_RESOURCE_SPISD != 4) || (RS_RESOURCE_EXT_H != 5) || @@ -877,6 +882,30 @@ static int test_resource_validation(void) { (rs_resource_get_cache_status(NULL) != RS_EINVAL)) { return 2; } + + for (size_t index = 0U; + index < (sizeof(rs_resource_test_mmio) / sizeof(rs_resource_test_mmio[0])); ++index) { + rs_resource_test_mmio[index] = 0U; + } + RESOURCE_TEST_REG(ga2d_base) = (uint32_t)RS_RESOURCE_OWNER_HP; + RESOURCE_TEST_REG(ga2d_base + UINT32_C(0x008)) = UINT32_C(0x00000011); + RESOURCE_TEST_REG(ga2d_base + UINT32_C(0x010)) = UINT32_C(0x00001234); + if ((rs_resource_get_status(RS_RESOURCE_GA2D, &status) != RS_OK) || + (status.owner != RS_RESOURCE_OWNER_HP) || (status.handoff_count != UINT16_C(0x1234)) || + status.owner_locked || status.blocked || !status.idle || status.quiesced || + status.in_reset || status.irq_pending || status.fault) { + return 3; + } + + RESOURCE_TEST_REG(ga2d_base) = (uint32_t)RS_RESOURCE_OWNER_LP | UINT32_C(0x00000100); + RESOURCE_TEST_REG(ga2d_base + UINT32_C(0x008)) = UINT32_C(0x000000FC); + RESOURCE_TEST_REG(ga2d_base + UINT32_C(0x010)) = UINT32_C(0x0000FFFF); + if ((rs_resource_get_status(RS_RESOURCE_GA2D, &status) != RS_OK) || + (status.owner != RS_RESOURCE_OWNER_LP) || (status.handoff_count != UINT16_MAX) || + !status.owner_locked || !status.blocked || !status.idle || !status.quiesced || + !status.in_reset || !status.irq_pending || !status.fault) { + return 4; + } return 0; } @@ -1118,8 +1147,11 @@ static void test_ga2d_mmio_reset(void) { } GA2D_TEST_REG(RS_GA2D_REG_IP_ID) = RS_GA2D_IP_ID_VALUE; GA2D_TEST_REG(RS_GA2D_REG_IP_VERSION) = RS_GA2D_IP_VERSION_VALUE; - GA2D_TEST_REG(RS_GA2D_REG_CAPABILITY) = RS_GA2D_CAPABILITY_P3; + GA2D_TEST_REG(RS_GA2D_REG_CAPABILITY) = RS_GA2D_CAPABILITY_P4; + GA2D_TEST_REG(RS_GA2D_REG_LIMITS) = RS_GA2D_LIMITS_P4; + GA2D_TEST_REG(RS_GA2D_REG_FORMAT_CAPABILITY) = RS_GA2D_FORMAT_CAPABILITY_P4; GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_DATA_READY; + rs_ga2d_test_mem_pad_mode = (uint32_t)RS_MEMORY_PAD_QPI; } static int test_ga2d_hal_contract(void) { @@ -1127,14 +1159,34 @@ static int test_ga2d_hal_contract(void) { rs_ga2d_status_t status; rs_ga2d_error_t error; rs_ga2d_stats_t stats; - rs_ga2d_job_t job = {0}; + rs_ga2d_job_t fill = { + .operation = RS_GA2D_OP_FILL, + .foreground = {0U, 0U, RS_GA2D_FORMAT_A8}, + .background = {0U, 0U, RS_GA2D_FORMAT_A8}, + .destination = {(uintptr_t)UINT32_C(0x38001000), 15U, RS_GA2D_FORMAT_RGB888}, + .width = 5U, + .height = 3U, + .global_alpha = UINT8_C(0xA5), + .color = UINT32_C(0x80123456), + }; + rs_ga2d_job_t copy = { + .operation = RS_GA2D_OP_COPY, + .foreground = {(uintptr_t)UINT32_C(0x38002000), 32U, RS_GA2D_FORMAT_ARGB8888}, + .background = {0U, 0U, RS_GA2D_FORMAT_A8}, + .destination = {(uintptr_t)UINT32_C(0x38003000), 32U, RS_GA2D_FORMAT_ARGB8888}, + .width = 4U, + .height = 3U, + .global_alpha = UINT8_C(0), + .color = UINT32_C(0), + }; test_ga2d_mmio_reset(); if ((rs_ga2d_get_capability(NULL) != RS_EINVAL) || (rs_ga2d_get_capability(&capability) != RS_OK) || (capability.version != RS_GA2D_IP_VERSION_VALUE) || - (capability.features != RS_GA2D_CAPABILITY_P3) || (capability.limits != 0U) || - (capability.formats != 0U)) { + (capability.features != RS_GA2D_CAPABILITY_P4) || + (capability.limits != RS_GA2D_LIMITS_P4) || + (capability.formats != RS_GA2D_FORMAT_CAPABILITY_P4)) { return 1; } GA2D_TEST_REG(RS_GA2D_REG_IP_VERSION) = UINT32_C(0x00020000); @@ -1142,22 +1194,163 @@ static int test_ga2d_hal_contract(void) { return 2; } test_ga2d_mmio_reset(); + GA2D_TEST_REG(RS_GA2D_REG_CAPABILITY) = RS_GA2D_CAPABILITY_IRQ; GA2D_TEST_REG(RS_GA2D_REG_COMMAND) = UINT32_C(0xA5A5A5A5); - if ((rs_ga2d_job_validate(NULL) != RS_EINVAL) || (rs_ga2d_job_validate(&job) != RS_ENOTSUP) || - (rs_ga2d_configure(NULL) != RS_EINVAL) || (rs_ga2d_configure(&job) != RS_ENOTSUP) || + if ((rs_ga2d_job_validate(NULL) != RS_EINVAL) || (rs_ga2d_job_validate(&fill) != RS_ENOTSUP) || + (rs_ga2d_configure(NULL) != RS_EINVAL) || (rs_ga2d_configure(&fill) != RS_ENOTSUP) || (rs_ga2d_start() != RS_ENOTSUP) || (rs_ga2d_wait(1U) != RS_ENOTSUP) || (rs_ga2d_get_stats(NULL) != RS_EINVAL) || (rs_ga2d_get_stats(&stats) != RS_ENOTSUP) || (GA2D_TEST_REG(RS_GA2D_REG_COMMAND) != UINT32_C(0xA5A5A5A5))) { return 3; } + test_ga2d_mmio_reset(); + if ((rs_ga2d_job_validate(&fill) != RS_OK) || (rs_ga2d_configure(&fill) != RS_OK) || + (GA2D_TEST_REG(RS_GA2D_REG_JOB_CONFIG) != 0U) || + (GA2D_TEST_REG(RS_GA2D_REG_GLOBAL_ALPHA) != fill.global_alpha) || + (GA2D_TEST_REG(RS_GA2D_REG_COLOR) != fill.color) || + (GA2D_TEST_REG(RS_GA2D_REG_SIZE) != UINT32_C(0x00030005)) || + (GA2D_TEST_REG(RS_GA2D_REG_FG_ADDRESS) != 0U) || + (GA2D_TEST_REG(RS_GA2D_REG_FG_PITCH) != 0U) || + (GA2D_TEST_REG(RS_GA2D_REG_FG_FORMAT) != 0U) || + (GA2D_TEST_REG(RS_GA2D_REG_BG_ADDRESS) != 0U) || + (GA2D_TEST_REG(RS_GA2D_REG_BG_PITCH) != 0U) || + (GA2D_TEST_REG(RS_GA2D_REG_BG_FORMAT) != 0U) || + (GA2D_TEST_REG(RS_GA2D_REG_DST_ADDRESS) != fill.destination.address) || + (GA2D_TEST_REG(RS_GA2D_REG_DST_PITCH) != fill.destination.pitch) || + (GA2D_TEST_REG(RS_GA2D_REG_DST_FORMAT) != fill.destination.format)) { + return 4; + } + if ((rs_ga2d_configure(©) != RS_OK) || + (GA2D_TEST_REG(RS_GA2D_REG_JOB_CONFIG) != RS_GA2D_OP_COPY) || + (GA2D_TEST_REG(RS_GA2D_REG_FG_ADDRESS) != copy.foreground.address) || + (GA2D_TEST_REG(RS_GA2D_REG_FG_PITCH) != copy.foreground.pitch) || + (GA2D_TEST_REG(RS_GA2D_REG_FG_FORMAT) != copy.foreground.format) || + (GA2D_TEST_REG(RS_GA2D_REG_BG_ADDRESS) != 0U) || + (GA2D_TEST_REG(RS_GA2D_REG_BG_PITCH) != 0U) || + (GA2D_TEST_REG(RS_GA2D_REG_BG_FORMAT) != 0U) || + (GA2D_TEST_REG(RS_GA2D_REG_DST_ADDRESS) != copy.destination.address) || + (GA2D_TEST_REG(RS_GA2D_REG_DST_PITCH) != copy.destination.pitch) || + (GA2D_TEST_REG(RS_GA2D_REG_DST_FORMAT) != copy.destination.format)) { + return 5; + } + + fill.width = 0U; + if (rs_ga2d_job_validate(&fill) != RS_EINVAL) { + return 6; + } + fill.width = 5U; + fill.destination.format = RS_GA2D_FORMAT_A8; + if (rs_ga2d_job_validate(&fill) != RS_ENOTSUP) { + return 7; + } + fill.destination.format = (rs_ga2d_format_t)5; + if (rs_ga2d_job_validate(&fill) != RS_EINVAL) { + return 8; + } + fill.destination.format = RS_GA2D_FORMAT_RGB888; + fill.destination.pitch = 14U; + if (rs_ga2d_job_validate(&fill) != RS_EINVAL) { + return 9; + } + fill.destination.address = (uintptr_t)UINT32_C(0x38001001); + fill.destination.pitch = 16U; + if (rs_ga2d_job_validate(&fill) != RS_OK) { + return 10; + } + fill.destination.format = RS_GA2D_FORMAT_RGB565; + if (rs_ga2d_job_validate(&fill) != RS_EINVAL) { + return 11; + } + fill.destination.address = (uintptr_t)UINT32_C(0xFFFFFFFC); + fill.destination.pitch = 4U; + fill.destination.format = RS_GA2D_FORMAT_XRGB8888; + fill.width = 1U; + fill.height = 2U; + if (rs_ga2d_job_validate(&fill) != RS_EINVAL) { + return 12; + } + fill.destination.address = (uintptr_t)UINT32_C(0x10000000); + fill.destination.pitch = 4U; + fill.destination.format = RS_GA2D_FORMAT_XRGB8888; + fill.width = 1U; + fill.height = 1U; + if (rs_ga2d_job_validate(&fill) != RS_EINVAL) { + return 13; + } +#if UINTPTR_MAX > UINT32_MAX + fill.destination.address = (uintptr_t)UINT64_C(0x100000000); + if (rs_ga2d_job_validate(&fill) != RS_EINVAL) { + return 14; + } +#endif + fill.destination.address = (uintptr_t)UINT32_C(0x38001000); + fill.destination.pitch = 15U; + fill.destination.format = RS_GA2D_FORMAT_RGB888; + fill.width = 5U; + fill.height = 3U; + fill.destination.address = (uintptr_t)UINT32_C(0x40001001); + fill.destination.pitch = 16U; + if (rs_ga2d_job_validate(&fill) != RS_OK) { + return 15; + } + rs_ga2d_test_mem_pad_mode = (uint32_t)RS_MEMORY_PAD_OPI; + if (rs_ga2d_job_validate(&fill) != RS_EINVAL) { + return 16; + } + fill.destination.address = (uintptr_t)UINT32_C(0x48001001); + if (rs_ga2d_job_validate(&fill) != RS_OK) { + return 17; + } + rs_ga2d_test_mem_pad_mode = (uint32_t)RS_MEMORY_PAD_QPI; + if (rs_ga2d_job_validate(&fill) != RS_EINVAL) { + return 18; + } + fill.destination.address = (uintptr_t)UINT32_C(0x38001000); + fill.destination.pitch = 15U; + copy.destination.address = copy.foreground.address; + if (rs_ga2d_job_validate(©) != RS_EINVAL) { + return 19; + } + copy.destination.address = (uintptr_t)UINT32_C(0x38003000); + copy.operation = RS_GA2D_OP_CONVERT; + if (rs_ga2d_job_validate(©) != RS_ENOTSUP) { + return 20; + } + copy.operation = RS_GA2D_OP_COPY; + + GA2D_TEST_REG(RS_GA2D_REG_IRQ_STATE) = RS_GA2D_IRQ_ALL; + GA2D_TEST_REG(RS_GA2D_REG_ERROR_STATUS) = RS_GA2D_ERROR_STATUS_VALID; + if ((rs_ga2d_start() != RS_OK) || (GA2D_TEST_REG(RS_GA2D_REG_IRQ_STATE) != RS_GA2D_IRQ_ALL) || + (GA2D_TEST_REG(RS_GA2D_REG_ERROR_STATUS) != RS_GA2D_ERROR_STATUS_VALID) || + (GA2D_TEST_REG(RS_GA2D_REG_COMMAND) != RS_GA2D_COMMAND_START)) { + return 21; + } + GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_DATA_READY; + GA2D_TEST_REG(RS_GA2D_REG_IRQ_STATE) = RS_GA2D_IRQ_DONE; + if (rs_ga2d_wait(1U) != RS_ETIMEOUT) { + return 22; + } + GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_DONE; + if (rs_ga2d_wait(0U) != RS_OK) { + return 23; + } + GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_DONE | RS_GA2D_STATUS_ERROR; + if (rs_ga2d_wait(1U) != RS_EIO) { + return 24; + } + GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_DONE | RS_GA2D_STATUS_BUSY; + if (rs_ga2d_wait(1U) != RS_ETIMEOUT) { + return 25; + } + GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_BUSY | RS_GA2D_STATUS_DATA_READY | RS_GA2D_STATUS_ERROR; GA2D_TEST_REG(RS_GA2D_REG_IRQ_STATE) = RS_GA2D_IRQ_DONE | RS_GA2D_IRQ_ABORT_DONE; if ((rs_ga2d_get_status(NULL) != RS_EINVAL) || (rs_ga2d_get_status(&status) != RS_OK) || !status.busy || !status.data_ready || !status.error || (status.irq_state != (RS_GA2D_IRQ_DONE | RS_GA2D_IRQ_ABORT_DONE))) { - return 4; + return 26; } GA2D_TEST_REG(RS_GA2D_REG_ERROR_STATUS) = RS_GA2D_ERROR_STATUS_VALID | (UINT32_C(11) << RS_GA2D_ERROR_STATUS_CODE_SHIFT) | @@ -1167,7 +1360,7 @@ static int test_ga2d_hal_contract(void) { if ((rs_ga2d_get_error(NULL) != RS_EINVAL) || (rs_ga2d_get_error(&error) != RS_OK) || !error.valid || (error.code != 11U) || (error.stage != 5U) || (error.axi_response != 2U) || (error.address != (uintptr_t)UINT32_C(0x30000040))) { - return 5; + return 27; } if ((rs_ga2d_irq_enable(RS_GA2D_IRQ_ALL) != RS_OK) || @@ -1178,29 +1371,61 @@ static int test_ga2d_hal_contract(void) { (rs_ga2d_irq_clear(RS_GA2D_IRQ_ERROR) != RS_OK) || (GA2D_TEST_REG(RS_GA2D_REG_IRQ_STATE) != RS_GA2D_IRQ_ERROR) || (rs_ga2d_irq_enable(UINT32_C(0x8)) != RS_EINVAL)) { - return 6; + return 28; } GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_DATA_READY; + GA2D_TEST_REG(RS_GA2D_REG_SNAP_CYCLES_LO) = UINT32_C(0x89ABCDEF); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_CYCLES_HI) = UINT32_C(0x01234567); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_READ_BYTES_LO) = UINT32_C(0x11111111); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_READ_BYTES_HI) = UINT32_C(0x22222222); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_WRITE_BYTES_LO) = UINT32_C(0x33333333); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_WRITE_BYTES_HI) = UINT32_C(0x44444444); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_READ_STALL_LO) = UINT32_C(0x55555555); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_READ_STALL_HI) = UINT32_C(0x66666666); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_WRITE_STALL_LO) = UINT32_C(0x77777777); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_WRITE_STALL_HI) = UINT32_C(0x88888888); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_PIPE_STALL_LO) = UINT32_C(0x99999999); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_PIPE_STALL_HI) = UINT32_C(0xAAAAAAAA); + GA2D_TEST_REG(RS_GA2D_REG_SNAP_LINES_DONE) = UINT32_C(0x0000FEDC); + if ((rs_ga2d_get_stats(&stats) != RS_OK) || + (GA2D_TEST_REG(RS_GA2D_REG_PERF_SNAPSHOT) != UINT32_C(1)) || + (stats.cycles != UINT64_C(0x0123456789ABCDEF)) || + (stats.read_bytes != UINT64_C(0x2222222211111111)) || + (stats.write_bytes != UINT64_C(0x4444444433333333)) || + (stats.read_stalls != UINT64_C(0x6666666655555555)) || + (stats.write_stalls != UINT64_C(0x8888888877777777)) || + (stats.pipe_stalls != UINT64_C(0xAAAAAAAA99999999)) || + (stats.lines_done != UINT16_C(0xFEDC))) { + return 29; + } if ((rs_ga2d_reset() != RS_OK) || (GA2D_TEST_REG(RS_GA2D_REG_COMMAND) != RS_GA2D_COMMAND_SOFT_RESET)) { - return 7; + return 30; } GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_RECOVERY_REQUIRED; GA2D_TEST_REG(RS_GA2D_REG_COMMAND) = UINT32_C(0x5A5A5A5A); if ((rs_ga2d_reset() != RS_EIO) || (GA2D_TEST_REG(RS_GA2D_REG_COMMAND) != UINT32_C(0x5A5A5A5A))) { - return 8; + return 31; } - GA2D_TEST_REG(RS_GA2D_REG_STATUS) = 0U; + GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_DATA_READY; if ((rs_ga2d_abort_wait(0U) != RS_OK) || (GA2D_TEST_REG(RS_GA2D_REG_COMMAND) != RS_GA2D_COMMAND_ABORT)) { - return 9; + return 32; + } + GA2D_TEST_REG(RS_GA2D_REG_STATUS) = 0U; + if (rs_ga2d_abort_wait(1U) != RS_EIO) { + return 33; } GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_BUSY; if (rs_ga2d_abort_wait(1U) != RS_ETIMEOUT) { - return 10; + return 34; + } + GA2D_TEST_REG(RS_GA2D_REG_STATUS) = RS_GA2D_STATUS_RECOVERY_REQUIRED; + if (rs_ga2d_abort_wait(1U) != RS_EIO) { + return 35; } return 0; } diff --git a/tests/ga2d_reference.py b/tests/ga2d_reference.py new file mode 100644 index 00000000..3639cec9 --- /dev/null +++ b/tests/ga2d_reference.py @@ -0,0 +1,107 @@ +"""Independent byte-accurate reference helpers for GA2D P4 FILL and COPY.""" + +from __future__ import annotations + +from dataclasses import dataclass + + +RGB565 = 0 +RGB888 = 1 +XRGB8888 = 2 +ARGB8888 = 3 + +BYTES_PER_PIXEL = { + RGB565: 2, + RGB888: 3, + XRGB8888: 4, + ARGB8888: 4, +} + + +@dataclass(frozen=True) +class Plane: + base: int + pitch: int + fmt: int + + +def bytes_per_pixel(fmt: int) -> int: + try: + return BYTES_PER_PIXEL[fmt] + except KeyError as error: + raise ValueError(f"unsupported P4 format {fmt}") from error + + +def row_bytes(width: int, fmt: int) -> int: + if not 1 <= width <= 0xFFFF: + raise ValueError("width is outside the P4 range") + return width * bytes_per_pixel(fmt) + + +def extent(plane: Plane, width: int, height: int) -> tuple[int, int]: + if not 1 <= height <= 0xFFFF: + raise ValueError("height is outside the P4 range") + bpp = bytes_per_pixel(plane.fmt) + if plane.base < 0 or plane.base > 0xFFFFFFFF: + raise ValueError("base is outside the 32-bit P4 address space") + if plane.base % bpp != 0 or plane.pitch % bpp != 0: + raise ValueError("plane is not naturally pixel aligned") + bytes_per_row = row_bytes(width, plane.fmt) + if plane.pitch < bytes_per_row: + raise ValueError("pitch is shorter than a logical row") + end = plane.base + (height - 1) * plane.pitch + bytes_per_row + if end > (1 << 32): + raise ValueError("plane extent overflows the P4 address space") + return plane.base, end + + +def ranges_overlap(left: tuple[int, int], right: tuple[int, int]) -> bool: + return left[0] < right[1] and right[0] < left[1] + + +def pack_color(color: int, fmt: int) -> bytes: + """Return the frozen P4 memory pixel bytes for COLOR=0xAARRGGBB.""" + + if not 0 <= color <= 0xFFFFFFFF: + raise ValueError("COLOR must be an unsigned 32-bit value") + alpha = (color >> 24) & 0xFF + red = (color >> 16) & 0xFF + green = (color >> 8) & 0xFF + blue = color & 0xFF + if fmt == RGB565: + packed = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3) + return packed.to_bytes(2, byteorder="little") + if fmt == RGB888: + return bytes((red, green, blue)) + if fmt == XRGB8888: + return bytes((blue, green, red, 0xFF)) + if fmt == ARGB8888: + return bytes((blue, green, red, alpha)) + raise ValueError(f"unsupported P4 format {fmt}") + + +def fill(memory: bytearray, plane: Plane, width: int, height: int, color: int) -> None: + extent(plane, width, height) + pixel = pack_color(color, plane.fmt) + logical_row = pixel * width + for line in range(height): + start = plane.base + line * plane.pitch + memory[start : start + len(logical_row)] = logical_row + + +def copy( + memory: bytearray, + foreground: Plane, + destination: Plane, + width: int, + height: int, +) -> None: + if foreground.fmt != destination.fmt: + raise ValueError("P4 COPY requires matching foreground and destination formats") + if ranges_overlap(extent(foreground, width, height), extent(destination, width, height)): + raise ValueError("P4 COPY rejects all foreground/destination overlap") + bytes_per_row = row_bytes(width, foreground.fmt) + for line in range(height): + source = foreground.base + line * foreground.pitch + target = destination.base + line * destination.pitch + memory[target : target + bytes_per_row] = memory[source : source + bytes_per_row] diff --git a/tests/rtl/ga2d_dma_tb.sv b/tests/rtl/ga2d_dma_tb.sv new file mode 100644 index 00000000..0aedcdd6 --- /dev/null +++ b/tests/rtl/ga2d_dma_tb.sv @@ -0,0 +1,1833 @@ +`timescale 1ns / 1ps + +`include "ga2d_define.svh" + +module ga2d_dma_tb; + localparam logic [31:0] SramBase = 32'h3000_0000; + localparam int unsigned MemoryBytes = 32768; + localparam logic [2:0] IrqDoneMask = 3'b001 << `APB4_GA2D__IRQ_DONE; + localparam logic [2:0] IrqErrorMask = 3'b001 << `APB4_GA2D__IRQ_ERROR; + localparam logic [2:0] IrqAbortDoneMask = 3'b001 << `APB4_GA2D__IRQ_ABORT_DONE; + + logic clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic resource_quiesce_i = 1'b0; + logic resource_reset_i = 1'b0; + logic source_stop_i = 1'b0; + logic source_safe_idle_i = 1'b1; + logic block_ack_i = 1'b0; + logic bridge_clear_busy_i = 1'b0; + logic [ 7:0] bridge_epoch_i = 8'd0; + logic data_ready_i = 1'b1; + logic [ 1:0] mem_pad_mode_i = 2'd0; + logic idle_o; + logic core_safe_idle_o; + logic irq_o; + logic [ 7:0] s_memory [0:MemoryBytes-1]; + logic s_rvalid_q; + logic [ 2:0] s_rid_q; + logic [ 1:0] s_rresp_q; + logic [31:0] s_raddr_q; + logic [63:0] s_rdata_q; + logic [ 2:0] s_rsize_q; + logic [ 8:0] s_rbeats_q; + logic s_residual_r_pending_q; + logic s_residual_r_valid_q; + logic [ 1:0] s_residual_r_delay_q; + logic s_aw_pending_q; + logic [31:0] s_awaddr_q; + logic [ 8:0] s_wbeats_q; + logic s_bvalid_q; + logic [ 2:0] s_bid_q; + logic [ 1:0] s_bresp_q; + logic s_residual_b_pending_q; + logic s_residual_b_valid_q; + logic [ 1:0] s_residual_b_delay_q; + logic s_inject_read_error; + logic s_inject_write_error; + logic s_inject_read_decerr; + logic s_inject_write_decerr; + logic s_inject_bad_rid; + logic s_inject_bad_rlast; + logic s_inject_bad_bid; + logic s_schedule_residual_r_i; + logic s_schedule_residual_b_i; + logic s_hold_ar_i; + logic s_hold_r_i; + logic s_hold_aw_i; + logic s_hold_w_i; + logic s_hold_b_i; + logic s_random_delays_i; + logic s_random_delay_reseed_i; + logic s_random_delay_coverage_clear_i; + logic [31:0] s_random_delay_seed_i; + logic [31:0] s_ar_delay_lfsr_q; + logic [31:0] s_aw_delay_lfsr_q; + logic [31:0] s_w_delay_lfsr_q; + logic [31:0] s_r_delay_lfsr_q; + logic [31:0] s_b_delay_lfsr_q; + logic [ 1:0] s_ar_delay_q; + logic [ 1:0] s_aw_delay_q; + logic [ 1:0] s_w_delay_q; + logic [ 1:0] s_r_delay_q; + logic [ 1:0] s_b_delay_q; + logic s_ar_delay_pending_q; + logic s_aw_delay_pending_q; + logic s_w_delay_pending_q; + logic s_r_delay_pending_q; + logic s_b_delay_pending_q; + logic [ 3:0] s_ar_delay_modes_q; + logic [ 3:0] s_aw_delay_modes_q; + logic [ 3:0] s_w_delay_modes_q; + logic [ 3:0] s_r_delay_modes_q; + logic [ 3:0] s_b_delay_modes_q; + logic s_clear_burst_observations_i; + logic s_check_read_bounds_i; + logic [31:0] s_read_row0_start_i; + logic [31:0] s_read_row0_end_i; + logic [31:0] s_read_row1_start_i; + logic [31:0] s_read_row1_end_i; + logic s_check_write_bounds_i; + logic [31:0] s_write_row0_start_i; + logic [31:0] s_write_row0_end_i; + logic [31:0] s_write_row1_start_i; + logic [31:0] s_write_row1_end_i; + logic s_saw_arsize_0_q; + logic s_saw_arsize_1_q; + logic s_saw_arsize_2_q; + logic s_saw_arlen_0_q; + logic s_saw_arlen_14_q; + logic s_saw_arlen_15_q; + logic s_saw_awlen_0_q; + logic s_saw_awlen_7_q; + logic s_saw_awlen_14_q; + logic s_saw_awlen_15_q; + int unsigned s_ar_count; + int unsigned s_aw_count; + int unsigned s_ar_count_before_validation; + int unsigned s_aw_count_before_validation; + logic s_aw_stalled_q; + logic [31:0] s_awaddr_stalled_q; + logic [ 7:0] s_awlen_stalled_q; + logic [ 2:0] s_awsize_stalled_q; + logic s_w_stalled_q; + logic [63:0] s_wdata_stalled_q; + logic [ 7:0] s_wstrb_stalled_q; + logic s_ar_stalled_q; + logic [31:0] s_araddr_stalled_q; + logic [ 7:0] s_arlen_stalled_q; + logic [ 2:0] s_arsize_stalled_q; + logic s_source_stop_seen_q; + logic s_aw_allowed_after_stop_q; + logic s_ar_allowed_after_stop_q; + logic s_previous_awvalid_q; + logic s_previous_arvalid_q; + + apb4_if apb4 ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(64), + .ID_WIDTH (3), + .USER_WIDTH(1) + ) ga2d_axi4 ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + + always #5 clk_i = ~clk_i; + + function automatic logic [7:0] memory_byte(input logic [31:0] address_i); + if ((address_i >= SramBase) && (address_i < (SramBase + MemoryBytes))) begin + return s_memory[address_i-SramBase]; + end + return 8'd0; + endfunction + + function automatic logic [63:0] memory_word(input logic [31:0] address_i); + logic [63:0] data; + logic [31:0] aligned_address; + begin + data = '0; + aligned_address = {address_i[31:3], 3'b000}; + for (int unsigned lane = 0; lane < 8; lane++) begin + data[lane*8+:8] = memory_byte(aligned_address + lane); + end + return data; + end + endfunction + + function automatic logic [31:0] next_delay_lfsr(input logic [31:0] value_i); + return {value_i[30:0], value_i[31] ^ value_i[21] ^ value_i[1] ^ value_i[0]}; + endfunction + + assign ga2d_axi4.arready = !s_hold_ar_i && !s_rvalid_q && !s_residual_r_pending_q && + !s_residual_r_valid_q && + (!s_random_delays_i || + (s_ar_delay_pending_q && (s_ar_delay_q == 2'd0))); + assign ga2d_axi4.rid = s_residual_r_valid_q ? 3'd0 : s_rid_q; + assign ga2d_axi4.rdata = s_rdata_q; + assign ga2d_axi4.rresp = s_rresp_q; + assign ga2d_axi4.rlast = s_residual_r_valid_q ? 1'b1 : + (!s_inject_bad_rlast && (s_rbeats_q == 9'd1)); + assign ga2d_axi4.ruser = '0; + assign ga2d_axi4.rvalid = !s_hold_r_i && + ((s_rvalid_q && (!s_random_delays_i || !s_r_delay_pending_q)) || + s_residual_r_valid_q); + assign ga2d_axi4.awready = !s_hold_aw_i && !s_aw_pending_q && !s_bvalid_q && + !s_residual_b_pending_q && !s_residual_b_valid_q && + (!s_random_delays_i || + (s_aw_delay_pending_q && (s_aw_delay_q == 2'd0))); + assign ga2d_axi4.wready = !s_hold_w_i && s_aw_pending_q && !s_bvalid_q && + !s_residual_b_pending_q && !s_residual_b_valid_q && + (!s_random_delays_i || + (s_w_delay_pending_q && (s_w_delay_q == 2'd0))); + assign ga2d_axi4.bid = s_residual_b_valid_q ? 3'd0 : s_bid_q; + assign ga2d_axi4.bresp = s_bresp_q; + assign ga2d_axi4.buser = '0; + assign ga2d_axi4.bvalid = !s_hold_b_i && + ((s_bvalid_q && (!s_random_delays_i || !s_b_delay_pending_q)) || + s_residual_b_valid_q); + + apb4_ga2d u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .resource_quiesce_i (resource_quiesce_i), + .resource_reset_i (resource_reset_i), + .source_stop_i (source_stop_i), + .source_safe_idle_i (source_safe_idle_i), + .block_ack_i (block_ack_i), + .bridge_clear_busy_i(bridge_clear_busy_i), + .bridge_epoch_i (bridge_epoch_i), + .data_ready_i (data_ready_i), + .mem_pad_mode_i (mem_pad_mode_i), + .apb4 (apb4), + .ga2d_axi4 (ga2d_axi4), + .idle_o (idle_o), + .core_safe_idle_o (core_safe_idle_o), + .irq_o (irq_o) + ); + + // Each channel chooses a separately seeded 0..3 cycle delay. Once chosen, + // a request or response stays eligible until its handshake, so the BFM has + // bounded progress and cannot hide a DUT deadlock behind random starvation. + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_ar_delay_lfsr_q <= 32'h1f12_3bb5; + s_aw_delay_lfsr_q <= 32'h2c34_4cc7; + s_w_delay_lfsr_q <= 32'h3d56_5dd9; + s_r_delay_lfsr_q <= 32'h4e78_6eeb; + s_b_delay_lfsr_q <= 32'h5f9a_7ffd; + s_ar_delay_q <= '0; + s_aw_delay_q <= '0; + s_w_delay_q <= '0; + s_r_delay_q <= '0; + s_b_delay_q <= '0; + s_ar_delay_pending_q <= 1'b0; + s_aw_delay_pending_q <= 1'b0; + s_w_delay_pending_q <= 1'b0; + s_r_delay_pending_q <= 1'b0; + s_b_delay_pending_q <= 1'b0; + s_ar_delay_modes_q <= '0; + s_aw_delay_modes_q <= '0; + s_w_delay_modes_q <= '0; + s_r_delay_modes_q <= '0; + s_b_delay_modes_q <= '0; + end else begin + if (s_random_delay_coverage_clear_i) begin + s_ar_delay_modes_q <= '0; + s_aw_delay_modes_q <= '0; + s_w_delay_modes_q <= '0; + s_r_delay_modes_q <= '0; + s_b_delay_modes_q <= '0; + end + if (s_random_delay_reseed_i) begin + s_ar_delay_lfsr_q <= s_random_delay_seed_i ^ 32'h1f12_3bb5; + s_aw_delay_lfsr_q <= s_random_delay_seed_i ^ 32'h2c34_4cc7; + s_w_delay_lfsr_q <= s_random_delay_seed_i ^ 32'h3d56_5dd9; + s_r_delay_lfsr_q <= s_random_delay_seed_i ^ 32'h4e78_6eeb; + s_b_delay_lfsr_q <= s_random_delay_seed_i ^ 32'h5f9a_7ffd; + s_ar_delay_q <= '0; + s_aw_delay_q <= '0; + s_w_delay_q <= '0; + s_r_delay_q <= '0; + s_b_delay_q <= '0; + s_ar_delay_pending_q <= 1'b0; + s_aw_delay_pending_q <= 1'b0; + s_w_delay_pending_q <= 1'b0; + s_r_delay_pending_q <= 1'b0; + s_b_delay_pending_q <= 1'b0; + end else if (!s_random_delays_i || bridge_clear_busy_i) begin + s_ar_delay_q <= '0; + s_aw_delay_q <= '0; + s_w_delay_q <= '0; + s_r_delay_q <= '0; + s_b_delay_q <= '0; + s_ar_delay_pending_q <= 1'b0; + s_aw_delay_pending_q <= 1'b0; + s_w_delay_pending_q <= 1'b0; + s_r_delay_pending_q <= 1'b0; + s_b_delay_pending_q <= 1'b0; + end else begin + if (!ga2d_axi4.arvalid || s_rvalid_q || s_residual_r_pending_q || + s_residual_r_valid_q) begin + s_ar_delay_q <= '0; + s_ar_delay_pending_q <= 1'b0; + end else if (!s_ar_delay_pending_q) begin + s_ar_delay_q <= s_ar_delay_lfsr_q[1:0]; + s_ar_delay_pending_q <= 1'b1; + s_ar_delay_modes_q[s_ar_delay_lfsr_q[1:0]] <= 1'b1; + s_ar_delay_lfsr_q <= next_delay_lfsr(s_ar_delay_lfsr_q); + end else if (s_ar_delay_q != 2'd0) begin + s_ar_delay_q <= s_ar_delay_q - 1'b1; + end else if (ga2d_axi4.arready) begin + s_ar_delay_pending_q <= 1'b0; + end + + if (!ga2d_axi4.awvalid || s_aw_pending_q || s_bvalid_q || + s_residual_b_pending_q || s_residual_b_valid_q) begin + s_aw_delay_q <= '0; + s_aw_delay_pending_q <= 1'b0; + end else if (!s_aw_delay_pending_q) begin + s_aw_delay_q <= s_aw_delay_lfsr_q[1:0]; + s_aw_delay_pending_q <= 1'b1; + s_aw_delay_modes_q[s_aw_delay_lfsr_q[1:0]] <= 1'b1; + s_aw_delay_lfsr_q <= next_delay_lfsr(s_aw_delay_lfsr_q); + end else if (s_aw_delay_q != 2'd0) begin + s_aw_delay_q <= s_aw_delay_q - 1'b1; + end else if (ga2d_axi4.awready) begin + s_aw_delay_pending_q <= 1'b0; + end + + if (!ga2d_axi4.wvalid || !s_aw_pending_q || s_bvalid_q || + s_residual_b_pending_q || s_residual_b_valid_q) begin + s_w_delay_q <= '0; + s_w_delay_pending_q <= 1'b0; + end else if (!s_w_delay_pending_q) begin + s_w_delay_q <= s_w_delay_lfsr_q[1:0]; + s_w_delay_pending_q <= 1'b1; + s_w_delay_modes_q[s_w_delay_lfsr_q[1:0]] <= 1'b1; + s_w_delay_lfsr_q <= next_delay_lfsr(s_w_delay_lfsr_q); + end else if (s_w_delay_q != 2'd0) begin + s_w_delay_q <= s_w_delay_q - 1'b1; + end else if (ga2d_axi4.wready) begin + s_w_delay_pending_q <= 1'b0; + end + + if (ga2d_axi4.arvalid && ga2d_axi4.arready) begin + s_r_delay_q <= s_r_delay_lfsr_q[1:0]; + s_r_delay_pending_q <= 1'b1; + s_r_delay_modes_q[s_r_delay_lfsr_q[1:0]] <= 1'b1; + s_r_delay_lfsr_q <= next_delay_lfsr(s_r_delay_lfsr_q); + end else if (s_rvalid_q && ga2d_axi4.rvalid && ga2d_axi4.rready) begin + if (s_rbeats_q == 9'd1) begin + s_r_delay_pending_q <= 1'b0; + end else begin + s_r_delay_q <= s_r_delay_lfsr_q[1:0]; + s_r_delay_pending_q <= 1'b1; + s_r_delay_modes_q[s_r_delay_lfsr_q[1:0]] <= 1'b1; + s_r_delay_lfsr_q <= next_delay_lfsr(s_r_delay_lfsr_q); + end + end else if (s_rvalid_q && s_r_delay_pending_q) begin + if (s_r_delay_q == 2'd0) begin + s_r_delay_pending_q <= 1'b0; + end else begin + s_r_delay_q <= s_r_delay_q - 1'b1; + end + end else if (!s_rvalid_q) begin + s_r_delay_pending_q <= 1'b0; + end + + if (ga2d_axi4.wvalid && ga2d_axi4.wready && (s_wbeats_q == 9'd1)) begin + s_b_delay_q <= s_b_delay_lfsr_q[1:0]; + s_b_delay_pending_q <= 1'b1; + s_b_delay_modes_q[s_b_delay_lfsr_q[1:0]] <= 1'b1; + s_b_delay_lfsr_q <= next_delay_lfsr(s_b_delay_lfsr_q); + end else if (s_bvalid_q && ga2d_axi4.bvalid && ga2d_axi4.bready) begin + s_b_delay_pending_q <= 1'b0; + end else if (s_bvalid_q && s_b_delay_pending_q) begin + if (s_b_delay_q == 2'd0) begin + s_b_delay_pending_q <= 1'b0; + end else begin + s_b_delay_q <= s_b_delay_q - 1'b1; + end + end else if (!s_bvalid_q) begin + s_b_delay_pending_q <= 1'b0; + end + end + end + end + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_rvalid_q <= 1'b0; + s_rid_q <= '0; + s_rresp_q <= 2'd0; + s_raddr_q <= '0; + s_rdata_q <= '0; + s_rsize_q <= '0; + s_rbeats_q <= '0; + s_residual_r_pending_q <= 1'b0; + s_residual_r_valid_q <= 1'b0; + s_residual_r_delay_q <= '0; + s_aw_pending_q <= 1'b0; + s_awaddr_q <= '0; + s_wbeats_q <= '0; + s_bvalid_q <= 1'b0; + s_bid_q <= '0; + s_bresp_q <= 2'd0; + s_residual_b_pending_q <= 1'b0; + s_residual_b_valid_q <= 1'b0; + s_residual_b_delay_q <= '0; + s_ar_count <= 0; + s_aw_count <= 0; + s_saw_arsize_0_q <= 1'b0; + s_saw_arsize_1_q <= 1'b0; + s_saw_arsize_2_q <= 1'b0; + s_saw_arlen_0_q <= 1'b0; + s_saw_arlen_14_q <= 1'b0; + s_saw_arlen_15_q <= 1'b0; + s_saw_awlen_0_q <= 1'b0; + s_saw_awlen_7_q <= 1'b0; + s_saw_awlen_14_q <= 1'b0; + s_saw_awlen_15_q <= 1'b0; + s_source_stop_seen_q <= 1'b0; + s_aw_allowed_after_stop_q <= 1'b0; + s_ar_allowed_after_stop_q <= 1'b0; + s_previous_awvalid_q <= 1'b0; + s_previous_arvalid_q <= 1'b0; + end else begin + if (!source_stop_i) begin + s_source_stop_seen_q <= 1'b0; + s_aw_allowed_after_stop_q <= 1'b0; + s_ar_allowed_after_stop_q <= 1'b0; + end else if (!s_source_stop_seen_q) begin + s_source_stop_seen_q <= 1'b1; + s_aw_allowed_after_stop_q <= s_previous_awvalid_q && + ga2d_axi4.awvalid && !ga2d_axi4.awready; + s_ar_allowed_after_stop_q <= s_previous_arvalid_q && + ga2d_axi4.arvalid && !ga2d_axi4.arready; + end + if (s_clear_burst_observations_i) begin + s_saw_arsize_0_q <= 1'b0; + s_saw_arsize_1_q <= 1'b0; + s_saw_arsize_2_q <= 1'b0; + s_saw_arlen_0_q <= 1'b0; + s_saw_arlen_14_q <= 1'b0; + s_saw_arlen_15_q <= 1'b0; + s_saw_awlen_0_q <= 1'b0; + s_saw_awlen_7_q <= 1'b0; + s_saw_awlen_14_q <= 1'b0; + s_saw_awlen_15_q <= 1'b0; + end + if (bridge_clear_busy_i) begin + // Model the existing bridge clear boundary: it contains every queued + // target-side response before the local source can be rearmed. + s_rvalid_q <= 1'b0; + s_rbeats_q <= '0; + s_residual_r_pending_q <= 1'b0; + s_residual_r_valid_q <= 1'b0; + s_residual_r_delay_q <= '0; + s_aw_pending_q <= 1'b0; + s_wbeats_q <= '0; + s_bvalid_q <= 1'b0; + s_residual_b_pending_q <= 1'b0; + s_residual_b_valid_q <= 1'b0; + s_residual_b_delay_q <= '0; + s_previous_awvalid_q <= 1'b0; + s_previous_arvalid_q <= 1'b0; + end else begin + if (ga2d_axi4.arvalid && ga2d_axi4.arready) begin + if (source_stop_i && + ((!s_source_stop_seen_q && !s_previous_arvalid_q) || + (s_source_stop_seen_q && !s_ar_allowed_after_stop_q))) begin + $fatal(1, "GA2D issued a new AR after source stop"); + end + if ((ga2d_axi4.arlen != 8'd0) && (ga2d_axi4.arsize != 3'd3)) begin + $fatal(1, "GA2D emitted a non-full-width read burst"); + end + if ((ga2d_axi4.araddr & ((32'd1 << ga2d_axi4.arsize) - 1'b1)) != 32'd0) begin + $fatal(1, "GA2D emitted an unaligned read transfer"); + end + if ({1'b0, ga2d_axi4.araddr[11:0]} + + (({5'd0, ga2d_axi4.arlen} + 13'd1) << ga2d_axi4.arsize) > 13'd4096) begin + $fatal(1, "GA2D read burst crossed a 4 KiB boundary"); + end + if (s_check_read_bounds_i && + !(((ga2d_axi4.araddr >= s_read_row0_start_i) && + ({1'b0, ga2d_axi4.araddr} + + (({25'd0, ga2d_axi4.arlen} + 33'd1) << ga2d_axi4.arsize) <= + {1'b0, s_read_row0_end_i})) || + ((ga2d_axi4.araddr >= s_read_row1_start_i) && + ({1'b0, ga2d_axi4.araddr} + + (({25'd0, ga2d_axi4.arlen} + 33'd1) << ga2d_axi4.arsize) <= + {1'b0, s_read_row1_end_i})))) begin + $fatal(1, "GA2D read padding or crossed a logical source row"); + end + s_rvalid_q <= 1'b1; + s_rid_q <= s_inject_bad_rid ? 3'd1 : 3'd0; + s_rresp_q <= s_inject_read_decerr ? 2'd3 : (s_inject_read_error ? 2'd2 : 2'd0); + s_raddr_q <= ga2d_axi4.araddr; + s_rdata_q <= memory_word(ga2d_axi4.araddr); + s_rsize_q <= ga2d_axi4.arsize; + s_rbeats_q <= {1'b0, ga2d_axi4.arlen} + 1'b1; + s_ar_count <= s_ar_count + 1; + if (ga2d_axi4.arsize == 3'd0) begin + s_saw_arsize_0_q <= 1'b1; + end + if (ga2d_axi4.arsize == 3'd1) begin + s_saw_arsize_1_q <= 1'b1; + end + if (ga2d_axi4.arsize == 3'd2) begin + s_saw_arsize_2_q <= 1'b1; + end + if (ga2d_axi4.arlen == 8'd14) begin + s_saw_arlen_14_q <= 1'b1; + end + if (ga2d_axi4.arlen == 8'd0) begin + s_saw_arlen_0_q <= 1'b1; + end + if (ga2d_axi4.arlen == 8'd15) begin + s_saw_arlen_15_q <= 1'b1; + end + end else if (s_rvalid_q && ga2d_axi4.rvalid && ga2d_axi4.rready) begin + if (s_rbeats_q == 9'd1) begin + s_rvalid_q <= 1'b0; + s_rbeats_q <= '0; + if (s_schedule_residual_r_i) begin + s_residual_r_pending_q <= 1'b1; + s_residual_r_delay_q <= 2'd2; + end + end else begin + s_raddr_q <= s_raddr_q + (32'd1 << s_rsize_q); + s_rdata_q <= memory_word(s_raddr_q + (32'd1 << s_rsize_q)); + s_rbeats_q <= s_rbeats_q - 1'b1; + end + end + if (s_residual_r_pending_q) begin + if (s_residual_r_delay_q == 2'd0) begin + s_residual_r_pending_q <= 1'b0; + s_residual_r_valid_q <= 1'b1; + end else begin + s_residual_r_delay_q <= s_residual_r_delay_q - 1'b1; + end + end else if (s_residual_r_valid_q && ga2d_axi4.rvalid && ga2d_axi4.rready) begin + s_residual_r_valid_q <= 1'b0; + end + if (ga2d_axi4.arvalid && ga2d_axi4.arready && s_source_stop_seen_q) begin + s_ar_allowed_after_stop_q <= 1'b0; + end + + if (ga2d_axi4.awvalid && ga2d_axi4.awready) begin + if (source_stop_i && + ((!s_source_stop_seen_q && !s_previous_awvalid_q) || + (s_source_stop_seen_q && !s_aw_allowed_after_stop_q))) begin + $fatal(1, "GA2D issued a new AW after source stop"); + end + if (s_check_write_bounds_i && (ga2d_axi4.awlen != 8'd0) && + !(((ga2d_axi4.awaddr >= s_write_row0_start_i) && + ({1'b0, ga2d_axi4.awaddr} + + (({25'd0, ga2d_axi4.awlen} + 33'd1) << 3) <= + {1'b0, s_write_row0_end_i})) || + ((ga2d_axi4.awaddr >= s_write_row1_start_i) && + ({1'b0, ga2d_axi4.awaddr} + + (({25'd0, ga2d_axi4.awlen} + 33'd1) << 3) <= + {1'b0, s_write_row1_end_i})))) begin + $fatal(1, "GA2D write burst crossed a logical destination row"); + end + if (ga2d_axi4.awaddr[2:0] != 3'd0) begin + $fatal(1, "GA2D emitted an unaligned AXI64 write address"); + end + if ({1'b0, ga2d_axi4.awaddr[11:0]} + + (({5'd0, ga2d_axi4.awlen} + 13'd1) << 3) > 13'd4096) begin + $fatal(1, "GA2D write burst crossed a 4 KiB boundary"); + end + s_aw_pending_q <= 1'b1; + s_awaddr_q <= ga2d_axi4.awaddr; + s_wbeats_q <= {1'b0, ga2d_axi4.awlen} + 1'b1; + s_aw_count <= s_aw_count + 1; + if (ga2d_axi4.awlen == 8'd14) begin + s_saw_awlen_14_q <= 1'b1; + end + if (ga2d_axi4.awlen == 8'd0) begin + s_saw_awlen_0_q <= 1'b1; + end + if (ga2d_axi4.awlen == 8'd7) begin + s_saw_awlen_7_q <= 1'b1; + end + if (ga2d_axi4.awlen == 8'd15) begin + s_saw_awlen_15_q <= 1'b1; + end + end + if (ga2d_axi4.wvalid && ga2d_axi4.wready) begin + if (!s_aw_pending_q) begin + $fatal(1, "GA2D issued W without an accepted AW"); + end + if (ga2d_axi4.wlast != (s_wbeats_q == 9'd1)) begin + $fatal(1, "GA2D emitted an incorrect WLAST sequence"); + end + for (int unsigned lane = 0; lane < 8; lane++) begin + if (ga2d_axi4.wstrb[lane]) begin + if (s_check_write_bounds_i && + !((((s_awaddr_q + lane) >= s_write_row0_start_i) && + ((s_awaddr_q + lane) < s_write_row0_end_i)) || + (((s_awaddr_q + lane) >= s_write_row1_start_i) && + ((s_awaddr_q + lane) < s_write_row1_end_i)))) begin + $fatal(1, "GA2D wrote padding or crossed a logical destination row"); + end + if ((s_awaddr_q + lane) < SramBase || + (s_awaddr_q + lane) >= (SramBase + MemoryBytes)) begin + $fatal(1, "GA2D write escaped the SRAM BFM"); + end + s_memory[(s_awaddr_q+lane)-SramBase] <= ga2d_axi4.wdata[lane*8+:8]; + end + end + if (s_wbeats_q == 9'd1) begin + s_aw_pending_q <= 1'b0; + s_wbeats_q <= '0; + s_bvalid_q <= 1'b1; + s_bid_q <= s_inject_bad_bid ? 3'd1 : 3'd0; + s_bresp_q <= s_inject_write_decerr ? 2'd3 : (s_inject_write_error ? 2'd2 : 2'd0); + end else begin + s_awaddr_q <= s_awaddr_q + 32'd8; + s_wbeats_q <= s_wbeats_q - 1'b1; + end + end else if (s_bvalid_q && ga2d_axi4.bvalid && ga2d_axi4.bready) begin + s_bvalid_q <= 1'b0; + if (s_schedule_residual_b_i) begin + s_residual_b_pending_q <= 1'b1; + s_residual_b_delay_q <= 2'd2; + end + end + if (s_residual_b_pending_q) begin + if (s_residual_b_delay_q == 2'd0) begin + s_residual_b_pending_q <= 1'b0; + s_residual_b_valid_q <= 1'b1; + end else begin + s_residual_b_delay_q <= s_residual_b_delay_q - 1'b1; + end + end else if (s_residual_b_valid_q && ga2d_axi4.bvalid && ga2d_axi4.bready) begin + s_residual_b_valid_q <= 1'b0; + end + if (ga2d_axi4.awvalid && ga2d_axi4.awready && s_source_stop_seen_q) begin + s_aw_allowed_after_stop_q <= 1'b0; + end + s_previous_awvalid_q <= ga2d_axi4.awvalid; + s_previous_arvalid_q <= ga2d_axi4.arvalid; + end + end + end + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_aw_stalled_q <= 1'b0; + s_awaddr_stalled_q <= '0; + s_awlen_stalled_q <= '0; + s_awsize_stalled_q <= '0; + s_w_stalled_q <= 1'b0; + s_wdata_stalled_q <= '0; + s_wstrb_stalled_q <= '0; + s_ar_stalled_q <= 1'b0; + s_araddr_stalled_q <= '0; + s_arlen_stalled_q <= '0; + s_arsize_stalled_q <= '0; + end else begin + if (s_aw_stalled_q) begin + if (!ga2d_axi4.awvalid || (ga2d_axi4.awaddr != s_awaddr_stalled_q) || + (ga2d_axi4.awlen != s_awlen_stalled_q) || + (ga2d_axi4.awsize != s_awsize_stalled_q)) begin + $fatal(1, "GA2D AW payload changed while stalled"); + end + end + if (s_w_stalled_q) begin + if (!ga2d_axi4.wvalid || (ga2d_axi4.wdata != s_wdata_stalled_q) || + (ga2d_axi4.wstrb != s_wstrb_stalled_q)) begin + $fatal(1, "GA2D W payload changed while stalled"); + end + end + if (s_ar_stalled_q) begin + if (!ga2d_axi4.arvalid || (ga2d_axi4.araddr != s_araddr_stalled_q) || + (ga2d_axi4.arlen != s_arlen_stalled_q) || + (ga2d_axi4.arsize != s_arsize_stalled_q)) begin + $fatal(1, "GA2D AR payload changed while stalled"); + end + end + if (ga2d_axi4.awvalid) begin + if ((ga2d_axi4.awid != 3'd0) || (ga2d_axi4.awlen > 8'd15) || + (ga2d_axi4.awsize != 3'd3) || (ga2d_axi4.awburst != 2'd1) || + (ga2d_axi4.awlock != 1'b0) || (ga2d_axi4.awcache != 4'd0) || + (ga2d_axi4.awprot != 3'd0) || (ga2d_axi4.awqos != 4'd0) || + (ga2d_axi4.awregion != 4'd0)) begin + $fatal(1, "GA2D AW attributes violate the P4 AXI contract"); + end + end + if (ga2d_axi4.arvalid) begin + if ((ga2d_axi4.arid != 3'd0) || (ga2d_axi4.arlen > 8'd15) || + (ga2d_axi4.arburst != 2'd1) || (ga2d_axi4.arlock != 1'b0) || + (ga2d_axi4.arcache != 4'd0) || (ga2d_axi4.arprot != 3'd0) || + (ga2d_axi4.arqos != 4'd0) || (ga2d_axi4.arregion != 4'd0)) begin + $fatal(1, "GA2D AR attributes violate the P4 AXI contract"); + end + end + if (ga2d_axi4.wvalid && (ga2d_axi4.wstrb == 8'd0)) begin + $fatal(1, "GA2D emitted a zero-strobe write"); + end + s_aw_stalled_q <= ga2d_axi4.awvalid && !ga2d_axi4.awready; + s_awaddr_stalled_q <= ga2d_axi4.awaddr; + s_awlen_stalled_q <= ga2d_axi4.awlen; + s_awsize_stalled_q <= ga2d_axi4.awsize; + s_w_stalled_q <= ga2d_axi4.wvalid && !ga2d_axi4.wready; + s_wdata_stalled_q <= ga2d_axi4.wdata; + s_wstrb_stalled_q <= ga2d_axi4.wstrb; + s_ar_stalled_q <= ga2d_axi4.arvalid && !ga2d_axi4.arready; + s_araddr_stalled_q <= ga2d_axi4.araddr; + s_arlen_stalled_q <= ga2d_axi4.arlen; + s_arsize_stalled_q <= ga2d_axi4.arsize; + end + end + + task automatic apb_write(input logic [11:0] offset_i, input logic [31:0] value_i, + input logic expected_error_i); + begin + @(negedge clk_i); + apb4.paddr = {20'd0, offset_i}; + apb4.pwrite = 1'b1; + apb4.pwdata = value_i; + apb4.pstrb = 4'hf; + apb4.psel = 1'b1; + apb4.penable = 1'b1; + @(posedge clk_i); + #1; + if (!apb4.pready || (apb4.pslverr != expected_error_i)) begin + $fatal(1, "GA2D APB write response mismatch at %h", offset_i); + end + @(negedge clk_i); + apb4.psel = 1'b0; + apb4.penable = 1'b0; + apb4.pwrite = 1'b0; + apb4.pstrb = '0; + end + endtask + + task automatic apb_read(input logic [11:0] offset_i, output logic [31:0] value_o); + begin + @(negedge clk_i); + apb4.paddr = {20'd0, offset_i}; + apb4.pwrite = 1'b0; + apb4.pstrb = '0; + apb4.psel = 1'b1; + apb4.penable = 1'b1; + @(posedge clk_i); + #1; + if (!apb4.pready || apb4.pslverr) begin + $fatal(1, "GA2D APB read response mismatch at %h", offset_i); + end + value_o = apb4.prdata; + @(negedge clk_i); + apb4.psel = 1'b0; + apb4.penable = 1'b0; + end + endtask + + task automatic wait_for_terminal(input logic expected_done_i, input logic expected_error_i, + input logic expected_abort_i); + logic [31:0] status; + logic terminal_seen; + begin + terminal_seen = 1'b0; + for (int unsigned attempt = 0; (attempt < 2000) && !terminal_seen; attempt++) begin + apb_read(`APB4_GA2D__STATUS, status); + if (!status[`APB4_GA2D__STATUS_BUSY] && + (status[`APB4_GA2D__STATUS_DONE] || + status[`APB4_GA2D__STATUS_ERROR] || + status[`APB4_GA2D__STATUS_ABORTED])) begin + if ((status[`APB4_GA2D__STATUS_DONE] != expected_done_i) || + (status[`APB4_GA2D__STATUS_ERROR] != expected_error_i) || + (status[`APB4_GA2D__STATUS_ABORTED] != expected_abort_i)) begin + $fatal(1, "GA2D terminal status mismatch: %h", status); + end + terminal_seen = 1'b1; + end + end + if (!terminal_seen) begin + $fatal(1, "GA2D DMA terminal state timed out"); + end + end + endtask + + task automatic wait_for_runtime_error(output logic [31:0] status_o); + logic error_seen; + begin + error_seen = 1'b0; + status_o = '0; + for (int unsigned attempt = 0; (attempt < 2000) && !error_seen; attempt++) begin + apb_read(`APB4_GA2D__STATUS, status_o); + if (status_o[`APB4_GA2D__STATUS_ERROR]) begin + error_seen = 1'b1; + end + end + if (!error_seen) begin + $fatal(1, "GA2D runtime error was not reported"); + end + end + endtask + + task automatic clear_memory(input logic [7:0] value_i); + begin + for (int unsigned index = 0; index < MemoryBytes; index++) begin + s_memory[index] = value_i; + end + end + endtask + + task automatic clear_burst_observations; + begin + @(negedge clk_i); + s_clear_burst_observations_i = 1'b1; + @(posedge clk_i); + @(negedge clk_i); + s_clear_burst_observations_i = 1'b0; + end + endtask + + function automatic logic [15:0] rgb565_fill_pixel(input logic [31:0] color_i); + return {color_i[23:19], color_i[15:10], color_i[7:3]}; + endfunction + + function automatic logic [31:0] fill_pixel(input logic [2:0] format_i, + input logic [31:0] color_i); + logic [15:0] rgb565; + begin + rgb565 = rgb565_fill_pixel(color_i); + unique case (format_i) + `APB4_GA2D__FORMAT_RGB565: return {16'd0, rgb565}; + `APB4_GA2D__FORMAT_RGB888: return {8'd0, color_i[7:0], color_i[15:8], color_i[23:16]}; + `APB4_GA2D__FORMAT_XRGB8888: return {8'hff, color_i[23:0]}; + default: return color_i; + endcase + end + endfunction + + task automatic configure_fill(input logic [2:0] format_i, input logic [15:0] width_i, + input logic [15:0] height_i, input logic [31:0] destination_i, + input logic [31:0] pitch_i, input logic [31:0] color_i); + begin + apb_write(`APB4_GA2D__JOB_CONFIG, 32'd0, 1'b0); + apb_write(`APB4_GA2D__COLOR, color_i, 1'b0); + apb_write(`APB4_GA2D__SIZE, {height_i, width_i}, 1'b0); + apb_write(`APB4_GA2D__DST_ADDRESS, destination_i, 1'b0); + apb_write(`APB4_GA2D__DST_PITCH, pitch_i, 1'b0); + apb_write(`APB4_GA2D__DST_FORMAT, {29'd0, format_i}, 1'b0); + end + endtask + + task automatic configure_copy( + input logic [2:0] format_i, input logic [15:0] width_i, input logic [15:0] height_i, + input logic [31:0] foreground_i, input logic [31:0] foreground_pitch_i, + input logic [31:0] destination_i, input logic [31:0] destination_pitch_i); + begin + apb_write(`APB4_GA2D__JOB_CONFIG, 32'd1, 1'b0); + apb_write(`APB4_GA2D__SIZE, {height_i, width_i}, 1'b0); + apb_write(`APB4_GA2D__FG_ADDRESS, foreground_i, 1'b0); + apb_write(`APB4_GA2D__FG_PITCH, foreground_pitch_i, 1'b0); + apb_write(`APB4_GA2D__FG_FORMAT, {29'd0, format_i}, 1'b0); + apb_write(`APB4_GA2D__DST_ADDRESS, destination_i, 1'b0); + apb_write(`APB4_GA2D__DST_PITCH, destination_pitch_i, 1'b0); + apb_write(`APB4_GA2D__DST_FORMAT, {29'd0, format_i}, 1'b0); + end + endtask + + task automatic start_job; + begin + apb_write(`APB4_GA2D__COMMAND, 32'h0000_0001, 1'b0); + end + endtask + + task automatic read_snapshot64(input logic [11:0] low_offset_i, input logic [11:0] high_offset_i, + output logic [63:0] value_o); + logic [31:0] low; + logic [31:0] high; + begin + apb_read(low_offset_i, low); + apb_read(high_offset_i, high); + value_o = {high, low}; + end + endtask + + task automatic check_terminal_counters(input logic [63:0] expected_read_bytes_i, + input logic [63:0] expected_write_bytes_i, + input logic [15:0] expected_lines_i); + logic [63:0] read_bytes; + logic [63:0] write_bytes; + logic [31:0] lines; + begin + apb_write(`APB4_GA2D__PERF_SNAPSHOT, 32'h0000_0001, 1'b0); + read_snapshot64(`APB4_GA2D__SNAP_READ_BYTES_LO, `APB4_GA2D__SNAP_READ_BYTES_HI, read_bytes); + read_snapshot64(`APB4_GA2D__SNAP_WRITE_BYTES_LO, `APB4_GA2D__SNAP_WRITE_BYTES_HI, + write_bytes); + apb_read(`APB4_GA2D__SNAP_LINES_DONE, lines); + if ((read_bytes != expected_read_bytes_i) || + (write_bytes != expected_write_bytes_i) || + (lines[15:0] != expected_lines_i)) begin + $fatal(1, "GA2D terminal accounting mismatch: read=%h write=%h lines=%h", read_bytes, + write_bytes, lines); + end + end + endtask + + task automatic check_error_code(input logic [6:0] expected_code_i); + logic [31:0] error_status; + begin + apb_read(`APB4_GA2D__ERROR_STATUS, error_status); + if (!error_status[`APB4_GA2D__ERROR_STATUS_VALID] || + (error_status[`APB4_GA2D__ERROR_STATUS_CODE+:7] != expected_code_i)) begin + $fatal(1, "GA2D error code mismatch: expected=%h actual=%h", expected_code_i, + error_status[`APB4_GA2D__ERROR_STATUS_CODE+:7]); + end + end + endtask + + task automatic check_error_axi_response(input logic [1:0] expected_response_i); + logic [31:0] error_status; + begin + apb_read(`APB4_GA2D__ERROR_STATUS, error_status); + if (error_status[`APB4_GA2D__ERROR_STATUS_AXI_RESPONSE+:2] != expected_response_i) begin + $fatal(1, "GA2D AXI error response mismatch: expected=%h actual=%h", expected_response_i, + error_status[`APB4_GA2D__ERROR_STATUS_AXI_RESPONSE+:2]); + end + end + endtask + + task automatic clear_first_error; + logic [31:0] error_status; + begin + apb_write(`APB4_GA2D__ERROR_STATUS, 32'h0000_0001, 1'b0); + apb_read(`APB4_GA2D__ERROR_STATUS, error_status); + if (error_status[`APB4_GA2D__ERROR_STATUS_VALID]) begin + $fatal(1, "GA2D ERROR_STATUS W1C did not clear the first-error record"); + end + end + endtask + + task automatic check_done_event; + logic [31:0] irq_state; + begin + repeat (2) @(posedge clk_i); + apb_read(`APB4_GA2D__IRQ_STATE, irq_state); + if (!irq_state[`APB4_GA2D__IRQ_DONE]) begin + $fatal(1, "GA2D completion did not latch IRQ_STATE.DONE"); + end + if (!irq_o) begin + $fatal(1, "GA2D completion did not assert the enabled raw IRQ"); + end + apb_write(`APB4_GA2D__IRQ_STATE, {29'd0, IrqDoneMask}, 1'b0); + apb_read(`APB4_GA2D__IRQ_STATE, irq_state); + if (irq_state[`APB4_GA2D__IRQ_DONE] || irq_o) begin + $fatal(1, "GA2D completion IRQ_STATE.DONE W1C did not clear"); + end + end + endtask + + task automatic check_error_event; + logic [31:0] irq_state; + begin + repeat (2) @(posedge clk_i); + apb_read(`APB4_GA2D__IRQ_STATE, irq_state); + if (!irq_state[`APB4_GA2D__IRQ_ERROR]) begin + $fatal(1, "GA2D error did not latch IRQ_STATE.ERROR"); + end + apb_write(`APB4_GA2D__IRQ_STATE, {29'd0, IrqErrorMask}, 1'b0); + apb_read(`APB4_GA2D__IRQ_STATE, irq_state); + if (irq_state[`APB4_GA2D__IRQ_ERROR]) begin + $fatal(1, "GA2D error IRQ_STATE.ERROR W1C did not clear"); + end + end + endtask + + task automatic check_abort_done_event; + logic [31:0] irq_state; + begin + repeat (2) @(posedge clk_i); + apb_read(`APB4_GA2D__IRQ_STATE, irq_state); + if (!irq_state[`APB4_GA2D__IRQ_ABORT_DONE]) begin + $fatal(1, "GA2D abort did not latch IRQ_STATE.ABORT_DONE"); + end + apb_write(`APB4_GA2D__IRQ_STATE, {29'd0, IrqAbortDoneMask}, 1'b0); + apb_read(`APB4_GA2D__IRQ_STATE, irq_state); + if (irq_state[`APB4_GA2D__IRQ_ABORT_DONE]) begin + $fatal(1, "GA2D abort IRQ_STATE.ABORT_DONE W1C did not clear"); + end + end + endtask + + task automatic check_first_error_immutability; + logic [31:0] first_error_status; + logic [31:0] first_error_address; + logic [31:0] error_status; + logic [31:0] error_address; + begin + configure_fill(`APB4_GA2D__FORMAT_RGB565, 16'd2, 16'd1, SramBase + MemoryBytes - 2, 32'd4, + 32'hffff_ffff); + start_job(); + wait_for_terminal(1'b0, 1'b1, 1'b0); + apb_read(`APB4_GA2D__ERROR_STATUS, first_error_status); + apb_read(`APB4_GA2D__ERROR_ADDRESS, first_error_address); + if ((first_error_status[`APB4_GA2D__ERROR_STATUS_CODE+:7] != + `APB4_GA2D__ERROR_ADDRESS_RANGE) || + (first_error_address != (SramBase + MemoryBytes - 2))) begin + $fatal(1, "GA2D first validation error record mismatch"); + end + check_error_event(); + + s_inject_write_error = 1'b1; + configure_fill(`APB4_GA2D__FORMAT_RGB565, 16'd2, 16'd1, SramBase + 32'h1600, 32'd4, + 32'h8012_3456); + start_job(); + wait_for_terminal(1'b0, 1'b1, 1'b0); + apb_read(`APB4_GA2D__ERROR_STATUS, error_status); + apb_read(`APB4_GA2D__ERROR_ADDRESS, error_address); + if ((error_status != first_error_status) || (error_address != first_error_address)) begin + $fatal(1, "GA2D START overwrote an uncleared first-error record"); + end + + clear_first_error(); + configure_fill(`APB4_GA2D__FORMAT_RGB565, 16'd2, 16'd1, SramBase + 32'h1640, 32'd4, + 32'h8012_3456); + start_job(); + wait_for_terminal(1'b0, 1'b1, 1'b0); + check_error_code(`APB4_GA2D__ERROR_AXI_WRITE); + s_inject_write_error = 1'b0; + clear_first_error(); + end + endtask + + task automatic run_fill_case(input logic [2:0] format_i, input int unsigned bytes_per_pixel_i, + input logic [31:0] destination_i, input logic [31:0] pitch_i, + input logic [31:0] color_i, input logic [31:0] pixel_i); + logic [31:0] row; + begin + clear_memory(8'hd3); + s_check_write_bounds_i = 1'b1; + s_write_row0_start_i = destination_i; + s_write_row0_end_i = destination_i + 3 * bytes_per_pixel_i; + s_write_row1_start_i = destination_i + pitch_i; + s_write_row1_end_i = destination_i + pitch_i + 3 * bytes_per_pixel_i; + configure_fill(format_i, 16'd3, 16'd2, destination_i, pitch_i, color_i); + start_job(); + wait_for_terminal(1'b1, 1'b0, 1'b0); + check_done_event(); + s_check_write_bounds_i = 1'b0; + for (int unsigned line = 0; line < 2; line++) begin + row = destination_i + (line * pitch_i); + if (memory_byte(row - 1'b1) != 8'hd3) begin + $fatal(1, "GA2D FILL changed the leading guard"); + end + for (int unsigned pixel = 0; pixel < 3; pixel++) begin + for (int unsigned byte_index = 0; byte_index < bytes_per_pixel_i; byte_index++) begin + if (memory_byte( + row + pixel * bytes_per_pixel_i + byte_index + ) != pixel_i[byte_index*8+:8]) begin + $fatal(1, "GA2D FILL pixel byte mismatch"); + end + end + end + for (int unsigned padding = 3 * bytes_per_pixel_i; padding < pitch_i; padding++) begin + if (memory_byte(row + padding) != 8'hd3) begin + $fatal(1, "GA2D FILL changed row padding"); + end + end + end + end + endtask + + task automatic run_copy_case(input logic [2:0] format_i, input int unsigned bytes_per_pixel_i, + input logic [31:0] foreground_i, input logic [31:0] destination_i); + logic [31:0] row_bytes; + logic [31:0] pitch; + logic [31:0] source_row; + logic [31:0] destination_row; + int unsigned ar_count_before; + int unsigned aw_count_before; + begin + clear_memory(8'hc7); + ar_count_before = s_ar_count; + aw_count_before = s_aw_count; + row_bytes = 3 * bytes_per_pixel_i; + pitch = row_bytes + bytes_per_pixel_i; + for (int unsigned line = 0; line < 2; line++) begin + source_row = foreground_i + (line * pitch); + for (int unsigned byte_index = 0; byte_index < row_bytes; byte_index++) begin + s_memory[(source_row + byte_index) - SramBase] = + (8'h31 * line) + byte_index + bytes_per_pixel_i; + end + end + s_check_read_bounds_i = 1'b1; + s_read_row0_start_i = foreground_i; + s_read_row0_end_i = foreground_i + row_bytes; + s_read_row1_start_i = foreground_i + pitch; + s_read_row1_end_i = foreground_i + pitch + row_bytes; + s_check_write_bounds_i = 1'b1; + s_write_row0_start_i = destination_i; + s_write_row0_end_i = destination_i + row_bytes; + s_write_row1_start_i = destination_i + pitch; + s_write_row1_end_i = destination_i + pitch + row_bytes; + configure_copy(format_i, 16'd3, 16'd2, foreground_i, pitch, destination_i, pitch); + start_job(); + wait_for_terminal(1'b1, 1'b0, 1'b0); + check_done_event(); + s_check_read_bounds_i = 1'b0; + s_check_write_bounds_i = 1'b0; + for (int unsigned line = 0; line < 2; line++) begin + source_row = foreground_i + (line * pitch); + destination_row = destination_i + (line * pitch); + if (memory_byte(destination_row - 1'b1) != 8'hc7) begin + $fatal(1, "GA2D COPY changed the leading guard"); + end + for (int unsigned byte_index = 0; byte_index < row_bytes; byte_index++) begin + if (memory_byte( + destination_row + byte_index + ) != memory_byte( + source_row + byte_index + )) begin + $fatal( + 1, + "GA2D COPY byte mismatch: line=%0d byte=%0d source=%h value=%h destination=%h value=%h ar=%0d aw=%0d", + line, byte_index, source_row + byte_index, memory_byte(source_row + byte_index), + destination_row + byte_index, memory_byte(destination_row + byte_index), + s_ar_count - ar_count_before, s_aw_count - aw_count_before); + end + end + for (int unsigned padding = row_bytes; padding < pitch; padding++) begin + if (memory_byte(destination_row + padding) != 8'hc7) begin + $fatal(1, "GA2D COPY changed row padding"); + end + end + end + end + endtask + + task automatic enable_random_delays; + begin + @(negedge clk_i); + s_random_delay_coverage_clear_i = 1'b1; + @(posedge clk_i); + @(negedge clk_i); + s_random_delay_coverage_clear_i = 1'b0; + s_random_delays_i = 1'b1; + end + endtask + + task automatic reseed_random_delays(input logic [31:0] seed_i); + begin + @(negedge clk_i); + s_random_delay_seed_i = seed_i; + s_random_delay_reseed_i = 1'b1; + @(posedge clk_i); + @(negedge clk_i); + s_random_delay_reseed_i = 1'b0; + end + endtask + + task automatic check_random_delay_coverage; + begin + @(negedge clk_i); + s_random_delays_i = 1'b0; + @(posedge clk_i); + if ((s_ar_delay_modes_q != 4'hf) || (s_aw_delay_modes_q != 4'hf) || + (s_w_delay_modes_q != 4'hf) || (s_r_delay_modes_q != 4'hf) || + (s_b_delay_modes_q != 4'hf)) begin + $fatal(1, "GA2D randomized campaign did not cover every independent AXI delay mode"); + end + end + endtask + + task automatic wait_for_protocol_quarantine(input logic read_i); + logic [31:0] status; + logic quarantine_seen; + begin + wait_for_runtime_error(status); + if (!status[`APB4_GA2D__STATUS_DRAINING] || + !status[`APB4_GA2D__STATUS_RECOVERY_REQUIRED]) begin + $fatal(1, "GA2D protocol error did not enter recovery drain"); + end + check_error_code(`APB4_GA2D__ERROR_AXI_PROTOCOL); + apb_write(`APB4_GA2D__COMMAND, 32'h0000_0001, 1'b1); + quarantine_seen = 1'b0; + for (int unsigned attempt = 0; (attempt < 64) && !quarantine_seen; attempt++) begin + @(posedge clk_i); + if (read_i) begin + quarantine_seen = ga2d_axi4.rvalid && !ga2d_axi4.rready; + end else begin + quarantine_seen = ga2d_axi4.bvalid && !ga2d_axi4.bready; + end + end + if (!quarantine_seen) begin + $fatal(1, "GA2D accepted a residual malformed-response channel before recovery"); + end + end + endtask + + task automatic complete_protocol_recovery; + logic [31:0] status; + logic recovered; + begin + @(negedge clk_i); + data_ready_i = 1'b0; + bridge_clear_busy_i = 1'b1; + bridge_epoch_i = bridge_epoch_i + 1'b1; + repeat (3) @(posedge clk_i); + @(negedge clk_i); + bridge_clear_busy_i = 1'b0; + repeat (2) @(posedge clk_i); + @(negedge clk_i); + data_ready_i = 1'b1; + recovered = 1'b0; + for (int unsigned attempt = 0; (attempt < 128) && !recovered; attempt++) begin + apb_read(`APB4_GA2D__STATUS, status); + recovered = !status[`APB4_GA2D__STATUS_BUSY] && + !status[`APB4_GA2D__STATUS_DRAINING] && + !status[`APB4_GA2D__STATUS_RECOVERY_REQUIRED]; + end + if (!recovered || !core_safe_idle_o) begin + $fatal(1, "GA2D protocol recovery did not safely contain the old local ID"); + end + end + endtask + + task automatic run_read_protocol_quarantine(input logic bad_id_i); + begin + clear_memory(8'hc7); + configure_copy(`APB4_GA2D__FORMAT_RGB565, 16'd4, 16'd1, SramBase + 32'h1200, 32'd8, + SramBase + 32'h1400, 32'd8); + s_inject_bad_rid = bad_id_i; + s_inject_bad_rlast = !bad_id_i; + s_schedule_residual_r_i = 1'b1; + start_job(); + wait_for_protocol_quarantine(1'b1); + s_inject_bad_rid = 1'b0; + s_inject_bad_rlast = 1'b0; + s_schedule_residual_r_i = 1'b0; + complete_protocol_recovery(); + clear_first_error(); + run_copy_case(`APB4_GA2D__FORMAT_RGB565, 2, SramBase + 32'h1200, SramBase + 32'h1400); + end + endtask + + task automatic run_write_protocol_quarantine; + begin + clear_memory(8'hd3); + configure_fill(`APB4_GA2D__FORMAT_RGB565, 16'd3, 16'd1, SramBase + 32'h1600, 32'd8, + 32'h8012_3456); + s_inject_bad_bid = 1'b1; + s_schedule_residual_b_i = 1'b1; + start_job(); + wait_for_protocol_quarantine(1'b0); + s_inject_bad_bid = 1'b0; + s_schedule_residual_b_i = 1'b0; + complete_protocol_recovery(); + clear_first_error(); + run_fill_case(`APB4_GA2D__FORMAT_RGB565, 2, SramBase + 32'h1600, 32'd8, 32'h8012_3456, + 32'h0000_11aa); + end + endtask + + task automatic hold_after_first_read_response; + logic response_seen; + begin + response_seen = 1'b0; + for (int unsigned attempt = 0; (attempt < 256) && !response_seen; attempt++) begin + @(posedge clk_i); + response_seen = ga2d_axi4.rvalid && ga2d_axi4.rready; + end + if (!response_seen) begin + $fatal(1, "GA2D did not return the first response for the error-drain test"); + end + @(negedge clk_i); + s_hold_r_i = 1'b1; + end + endtask + + task automatic check_error_and_abort_done_events; + logic [31:0] irq_state; + begin + repeat (2) @(posedge clk_i); + apb_read(`APB4_GA2D__IRQ_STATE, irq_state); + if (!irq_state[`APB4_GA2D__IRQ_ERROR] || !irq_state[`APB4_GA2D__IRQ_ABORT_DONE]) begin + $fatal(1, "GA2D error drain did not retain ERROR and ABORT_DONE IRQ events"); + end + apb_write(`APB4_GA2D__IRQ_STATE, {29'd0, IrqErrorMask | IrqAbortDoneMask}, 1'b0); + end + endtask + + task automatic run_abort_during_error_drain(input logic resource_stop_i); + logic [31:0] status; + begin + clear_memory(8'ha5); + configure_copy(`APB4_GA2D__FORMAT_RGB565, 16'd8, 16'd1, SramBase + 32'h1200, 32'd16, + SramBase + 32'h1400, 32'd16); + apb_write(`APB4_GA2D__TIMEOUT_CYCLES, 32'd1024, 1'b0); + s_inject_read_error = 1'b1; + start_job(); + hold_after_first_read_response(); + wait_for_runtime_error(status); + if (!status[`APB4_GA2D__STATUS_DRAINING]) begin + $fatal(1, "GA2D read error did not remain in drain before abort"); + end + if (resource_stop_i) begin + @(negedge clk_i); + resource_quiesce_i = 1'b1; + source_stop_i = 1'b1; + @(posedge clk_i); + end else begin + apb_write(`APB4_GA2D__COMMAND, 32'h0000_0002, 1'b0); + end + s_inject_read_error = 1'b0; + @(negedge clk_i); + s_hold_r_i = 1'b0; + wait_for_terminal(1'b0, 1'b1, 1'b1); + check_error_code(`APB4_GA2D__ERROR_AXI_READ); + check_error_and_abort_done_events(); + if (resource_stop_i) begin + @(negedge clk_i); + block_ack_i = 1'b1; + resource_quiesce_i = 1'b0; + source_stop_i = 1'b0; + @(posedge clk_i); + @(negedge clk_i); + block_ack_i = 1'b0; + end + clear_first_error(); + end + endtask + + task automatic run_fill_burst_case(input logic [15:0] width_i, input logic [31:0] destination_i, + input logic [7:0] expected_awlen_i); + logic [15:0] pixel; + logic [31:0] row_bytes; + begin + pixel = rgb565_fill_pixel(32'h8012_3456); + row_bytes = {16'd0, width_i} * 32'd2; + clear_memory(8'hd3); + clear_burst_observations(); + s_check_write_bounds_i = 1'b1; + s_write_row0_start_i = destination_i; + s_write_row0_end_i = destination_i + row_bytes; + s_write_row1_start_i = '0; + s_write_row1_end_i = '0; + configure_fill(`APB4_GA2D__FORMAT_RGB565, width_i, 16'd1, destination_i, row_bytes, + 32'h8012_3456); + start_job(); + wait_for_terminal(1'b1, 1'b0, 1'b0); + s_check_write_bounds_i = 1'b0; + check_terminal_counters(64'd0, {32'd0, row_bytes}, 16'd1); + if ((expected_awlen_i == 8'd14) && !s_saw_awlen_14_q) begin + $fatal(1, "GA2D did not issue the required 15-beat write burst"); + end + if ((expected_awlen_i == 8'd0) && !s_saw_awlen_0_q) begin + $fatal(1, "GA2D did not issue the required one-beat write burst"); + end + if ((expected_awlen_i == 8'd15) && !s_saw_awlen_15_q) begin + $fatal(1, "GA2D did not issue the required 16-beat write burst"); + end + if ((expected_awlen_i == 8'd7) && !s_saw_awlen_7_q) begin + $fatal(1, "GA2D did not split a 4 KiB-crossing row at eight beats"); + end + if (memory_byte(destination_i - 1'b1) != 8'hd3) begin + $fatal(1, "GA2D burst FILL changed a leading guard byte"); + end + for (int unsigned byte_index = 0; byte_index < row_bytes; byte_index++) begin + if (memory_byte(destination_i + byte_index) != pixel[(byte_index%2)*8+:8]) begin + $fatal(1, "GA2D burst FILL byte mismatch"); + end + end + if (((destination_i + row_bytes) < (SramBase + MemoryBytes)) && (memory_byte( + destination_i + row_bytes + ) != 8'hd3)) begin + $fatal(1, "GA2D burst FILL changed a trailing guard byte"); + end + end + endtask + + task automatic run_copy_burst_case(input logic [15:0] width_i, input logic [31:0] foreground_i, + input logic [31:0] destination_i, + input logic [7:0] expected_len_i); + logic [31:0] row_bytes; + begin + row_bytes = {16'd0, width_i} * 32'd2; + clear_memory(8'hc7); + for (int unsigned byte_index = 0; byte_index < row_bytes; byte_index++) begin + s_memory[(foreground_i+byte_index)-SramBase] = 8'h53 + byte_index; + end + clear_burst_observations(); + s_check_read_bounds_i = 1'b1; + s_read_row0_start_i = foreground_i; + s_read_row0_end_i = foreground_i + row_bytes; + s_read_row1_start_i = '0; + s_read_row1_end_i = '0; + s_check_write_bounds_i = 1'b1; + s_write_row0_start_i = destination_i; + s_write_row0_end_i = destination_i + row_bytes; + s_write_row1_start_i = '0; + s_write_row1_end_i = '0; + configure_copy(`APB4_GA2D__FORMAT_RGB565, width_i, 16'd1, foreground_i, row_bytes, + destination_i, row_bytes); + start_job(); + wait_for_terminal(1'b1, 1'b0, 1'b0); + s_check_read_bounds_i = 1'b0; + s_check_write_bounds_i = 1'b0; + check_terminal_counters({32'd0, row_bytes}, {32'd0, row_bytes}, 16'd1); + if ((expected_len_i == 8'd14) && (!s_saw_arlen_14_q || !s_saw_awlen_14_q)) begin + $fatal(1, "GA2D did not issue the required 15-beat COPY bursts"); + end + if ((expected_len_i == 8'd0) && (!s_saw_arlen_0_q || !s_saw_awlen_0_q)) begin + $fatal(1, "GA2D did not issue the required one-beat COPY bursts"); + end + if ((expected_len_i == 8'd15) && (!s_saw_arlen_15_q || !s_saw_awlen_15_q)) begin + $fatal(1, "GA2D did not issue the required 16-beat COPY bursts"); + end + if (memory_byte(destination_i - 1'b1) != 8'hc7) begin + $fatal(1, "GA2D burst COPY changed a leading guard byte"); + end + for (int unsigned byte_index = 0; byte_index < row_bytes; byte_index++) begin + if (memory_byte(destination_i + byte_index) != memory_byte(foreground_i + byte_index)) begin + $fatal(1, "GA2D burst COPY byte mismatch"); + end + end + if (memory_byte(destination_i + row_bytes) != 8'hc7) begin + $fatal(1, "GA2D burst COPY changed a trailing guard byte"); + end + end + endtask + + task automatic run_random_job(input logic fill_i, input logic [2:0] format_i, + input logic [31:0] random_i, input int unsigned job_i); + logic [31:0] foreground; + logic [31:0] destination; + logic [31:0] pixel; + logic [31:0] source_row; + logic [31:0] destination_row; + logic [15:0] width; + logic [15:0] height; + int unsigned bytes_per_pixel; + int unsigned row_bytes; + int unsigned foreground_pitch; + int unsigned destination_pitch; + begin + width = (random_i[15:12] % 8) + 1; + height = (random_i[18:16] % 3) + 1; + unique case (format_i) + `APB4_GA2D__FORMAT_RGB565: begin + bytes_per_pixel = 2; + foreground = SramBase + 32'h1000 + ((job_i % 16) * 128) + ((random_i[5:4] % 4) * 2); + destination = SramBase + 32'h5000 + ((job_i % 16) * 128) + ((random_i[7:6] % 4) * 2); + end + `APB4_GA2D__FORMAT_RGB888: begin + bytes_per_pixel = 3; + foreground = SramBase + 32'h1000 + ((job_i % 16) * 128) + (random_i[2:0] % 8); + destination = SramBase + 32'h5000 + ((job_i % 16) * 128) + (random_i[10:8] % 8); + end + default: begin + bytes_per_pixel = 4; + foreground = SramBase + 32'h1000 + ((job_i % 16) * 128) + ((random_i[3] % 2) * 4); + destination = SramBase + 32'h5000 + ((job_i % 16) * 128) + ((random_i[11] % 2) * 4); + end + endcase + row_bytes = width * bytes_per_pixel; + foreground_pitch = row_bytes + bytes_per_pixel * (random_i[21:20] % 4); + destination_pitch = row_bytes + bytes_per_pixel * (random_i[23:22] % 4); + pixel = fill_pixel(format_i, random_i); + for (int unsigned line = 0; line < height; line++) begin + source_row = foreground + line * foreground_pitch; + destination_row = destination + line * destination_pitch; + if (line == 0) begin + s_memory[(destination_row-SramBase)-1] = 8'hc7; + end + for (int unsigned byte_index = 0; byte_index < row_bytes; byte_index++) begin + s_memory[(destination_row-SramBase)+byte_index] = 8'hc7; + s_memory[(source_row - SramBase) + byte_index] = + random_i[(byte_index % 4)*8+:8] ^ line[7:0] ^ byte_index[7:0]; + end + for (int unsigned padding = row_bytes; padding < foreground_pitch; padding++) begin + s_memory[(source_row-SramBase)+padding] = 8'hc7; + end + for (int unsigned padding = row_bytes; padding < destination_pitch; padding++) begin + s_memory[(destination_row-SramBase)+padding] = 8'hc7; + end + end + s_memory[(destination+((height-1'b1)*destination_pitch)+row_bytes)-SramBase] = 8'hc7; + if (fill_i) begin + configure_fill(format_i, width, height, destination, destination_pitch, random_i); + end else begin + configure_copy(format_i, width, height, foreground, foreground_pitch, destination, + destination_pitch); + end + start_job(); + wait_for_terminal(1'b1, 1'b0, 1'b0); + if (memory_byte(destination - 1'b1) != 8'hc7) begin + $fatal(1, "GA2D randomized job changed a leading guard byte"); + end + for (int unsigned line = 0; line < height; line++) begin + source_row = foreground + line * foreground_pitch; + destination_row = destination + line * destination_pitch; + for (int unsigned byte_index = 0; byte_index < row_bytes; byte_index++) begin + if (fill_i) begin + if (memory_byte( + destination_row + byte_index + ) != pixel[(byte_index%bytes_per_pixel)*8+:8]) begin + $fatal(1, "GA2D randomized FILL byte mismatch"); + end + end else if (memory_byte( + destination_row + byte_index + ) != memory_byte( + source_row + byte_index + )) begin + $fatal(1, "GA2D randomized COPY byte mismatch"); + end + end + for (int unsigned padding = row_bytes; padding < destination_pitch; padding++) begin + if (memory_byte(destination_row + padding) != 8'hc7) begin + $fatal(1, "GA2D randomized job changed destination padding"); + end + end + end + if (memory_byte( + destination + ((height - 1'b1) * destination_pitch) + row_bytes + ) != 8'hc7) begin + $fatal(1, "GA2D randomized job changed a trailing guard byte"); + end + end + endtask + + task automatic run_random_campaign; + logic [31:0] random_value; + logic [ 2:0] format; + logic [ 9:0] seeds_seen; + int unsigned completed; + begin + completed = 0; + seeds_seen = '0; + enable_random_delays(); + for (int unsigned seed = 0; seed < 10; seed++) begin + reseed_random_delays(32'h9e37_79b9 ^ seed); + seeds_seen[seed] = 1'b1; + random_value = 32'h9e37_79b9 ^ seed; + for (int unsigned job = 0; job < 1000; job++) begin + random_value = (random_value * 32'd1664525) + 32'd1013904223; + format = random_value[1:0]; + run_random_job(random_value[2], format, random_value, job); + completed++; + end + end + if (completed != 10000) begin + $fatal(1, "GA2D randomized campaign did not execute 10,000 jobs"); + end + if (seeds_seen != 10'h3ff) begin + $fatal(1, "GA2D randomized campaign did not execute all ten delay seeds"); + end + check_random_delay_coverage(); + end + endtask + + initial begin + logic [31:0] error_status; + logic [31:0] status; + + apb4.paddr = '0; + apb4.pprot = '0; + apb4.psel = 1'b0; + apb4.penable = 1'b0; + apb4.pwrite = 1'b0; + apb4.pwdata = '0; + apb4.pstrb = '0; + s_inject_read_error = 1'b0; + s_inject_write_error = 1'b0; + s_inject_read_decerr = 1'b0; + s_inject_write_decerr = 1'b0; + s_inject_bad_rid = 1'b0; + s_inject_bad_rlast = 1'b0; + s_inject_bad_bid = 1'b0; + s_schedule_residual_r_i = 1'b0; + s_schedule_residual_b_i = 1'b0; + s_hold_ar_i = 1'b0; + s_hold_r_i = 1'b0; + s_hold_aw_i = 1'b0; + s_hold_w_i = 1'b0; + s_hold_b_i = 1'b0; + s_random_delays_i = 1'b0; + s_random_delay_reseed_i = 1'b0; + s_random_delay_coverage_clear_i = 1'b0; + s_random_delay_seed_i = '0; + s_clear_burst_observations_i = 1'b0; + s_check_read_bounds_i = 1'b0; + s_read_row0_start_i = '0; + s_read_row0_end_i = '0; + s_read_row1_start_i = '0; + s_read_row1_end_i = '0; + s_check_write_bounds_i = 1'b0; + s_write_row0_start_i = '0; + s_write_row0_end_i = '0; + s_write_row1_start_i = '0; + s_write_row1_end_i = '0; + + repeat (3) @(posedge clk_i); + rst_n_i = 1'b1; + apb_write(`APB4_GA2D__IRQ_ENABLE, {29'd0, IrqDoneMask}, 1'b0); + + run_fill_case(`APB4_GA2D__FORMAT_RGB565, 2, SramBase + 32'h100, 32'd8, 32'h8012_3456, + 32'h0000_11aa); + run_fill_case(`APB4_GA2D__FORMAT_RGB888, 3, SramBase + 32'h181, 32'd12, 32'h8012_3456, + 32'h0056_3412); + run_fill_case(`APB4_GA2D__FORMAT_XRGB8888, 4, SramBase + 32'h200, 32'd16, 32'h8012_3456, + 32'hff12_3456); + run_fill_case(`APB4_GA2D__FORMAT_ARGB8888, 4, SramBase + 32'h280, 32'd16, 32'h8012_3456, + 32'h8012_3456); + + run_copy_case(`APB4_GA2D__FORMAT_RGB565, 2, SramBase + 32'h400, SramBase + 32'h600); + clear_burst_observations(); + run_copy_case(`APB4_GA2D__FORMAT_RGB888, 3, SramBase + 32'h501, SramBase + 32'h701); + if (!s_saw_arsize_0_q || !s_saw_arsize_1_q || !s_saw_arsize_2_q) begin + $fatal(1, "GA2D did not use aligned 1/2/4-byte RGB888 edge reads"); + end + run_copy_case(`APB4_GA2D__FORMAT_XRGB8888, 4, SramBase + 32'h800, SramBase + 32'ha00); + run_copy_case(`APB4_GA2D__FORMAT_ARGB8888, 4, SramBase + 32'hc00, SramBase + 32'he00); + + check_first_error_immutability(); + + clear_memory(8'hc7); + for (int unsigned byte_index = 0; byte_index < 8; byte_index++) begin + s_memory[32'h1100+byte_index] = 8'h41 + byte_index; + end + configure_copy(`APB4_GA2D__FORMAT_RGB565, 16'd4, 16'd1, SramBase + 32'h1100, 32'd8, + SramBase + 32'h1300, 32'd8); + apb_write(`APB4_GA2D__TIMEOUT_CYCLES, 32'd1024, 1'b0); + s_hold_ar_i = 1'b1; + start_job(); + for (int unsigned attempt = 0; (attempt < 256) && !ga2d_axi4.arvalid; attempt++) begin + @(posedge clk_i); + end + if (!ga2d_axi4.arvalid) begin + $fatal(1, "GA2D did not present AR for the channel-stall test"); + end + s_hold_r_i = 1'b1; + s_hold_ar_i = 1'b0; + for (int unsigned attempt = 0; (attempt < 256) && !s_rvalid_q; attempt++) begin + @(posedge clk_i); + end + if (!s_rvalid_q) begin + $fatal(1, "GA2D did not receive R for the channel-stall test"); + end + s_hold_aw_i = 1'b1; + s_hold_r_i = 1'b0; + for (int unsigned attempt = 0; (attempt < 256) && !ga2d_axi4.awvalid; attempt++) begin + @(posedge clk_i); + end + if (!ga2d_axi4.awvalid) begin + $fatal(1, "GA2D did not present AW for the channel-stall test"); + end + s_hold_w_i = 1'b1; + s_hold_aw_i = 1'b0; + for (int unsigned attempt = 0; (attempt < 256) && !ga2d_axi4.wvalid; attempt++) begin + @(posedge clk_i); + end + if (!ga2d_axi4.wvalid) begin + $fatal(1, "GA2D did not present W for the channel-stall test"); + end + s_hold_b_i = 1'b1; + s_hold_w_i = 1'b0; + for (int unsigned attempt = 0; (attempt < 256) && !s_bvalid_q; attempt++) begin + @(posedge clk_i); + end + if (!s_bvalid_q) begin + $fatal(1, "GA2D did not receive B for the channel-stall test"); + end + s_hold_b_i = 1'b0; + wait_for_terminal(1'b1, 1'b0, 1'b0); + for (int unsigned byte_index = 0; byte_index < 8; byte_index++) begin + if (memory_byte( + SramBase + 32'h1300 + byte_index + ) != memory_byte( + SramBase + 32'h1100 + byte_index + )) begin + $fatal(1, "GA2D channel-stall COPY did not preserve a source byte"); + end + end + + run_fill_burst_case(16'd4, SramBase + 32'h1700, 8'd0); + run_fill_burst_case(16'd60, SramBase + 32'h1800, 8'd14); + run_fill_burst_case(16'd64, SramBase + 32'h1A00, 8'd15); + run_fill_burst_case(16'd64, SramBase + 32'h0FC0, 8'd7); + run_fill_burst_case(16'd64, SramBase + MemoryBytes - 32'd128, 8'd15); + run_copy_burst_case(16'd4, SramBase + 32'h1f00, SramBase + 32'h1f40, 8'd0); + run_copy_burst_case(16'd60, SramBase + 32'h2000, SramBase + 32'h2400, 8'd14); + run_copy_burst_case(16'd64, SramBase + 32'h2600, SramBase + 32'h2A00, 8'd15); + + run_random_campaign(); + + clear_memory(8'ha5); + configure_copy(`APB4_GA2D__FORMAT_RGB888, 16'd3, 16'd1, SramBase + 32'h1200, 32'd12, + SramBase + 32'h1400, 32'd12); + s_inject_read_error = 1'b1; + start_job(); + wait_for_terminal(1'b0, 1'b1, 1'b0); + check_error_code(`APB4_GA2D__ERROR_AXI_READ); + check_error_axi_response(2'd2); + for (int unsigned byte_index = 0; byte_index < 9; byte_index++) begin + if (memory_byte(SramBase + 32'h1400 + byte_index) != 8'ha5) begin + $fatal(1, "GA2D emitted an untrusted pixel after a read error"); + end + end + s_inject_read_error = 1'b0; + clear_first_error(); + + configure_copy(`APB4_GA2D__FORMAT_RGB888, 16'd3, 16'd1, SramBase + 32'h1200, 32'd12, + SramBase + 32'h1400, 32'd12); + s_inject_read_decerr = 1'b1; + start_job(); + wait_for_terminal(1'b0, 1'b1, 1'b0); + check_error_code(`APB4_GA2D__ERROR_AXI_READ); + check_error_axi_response(2'd3); + s_inject_read_decerr = 1'b0; + clear_first_error(); + + run_read_protocol_quarantine(1'b1); + run_read_protocol_quarantine(1'b0); + + configure_fill(`APB4_GA2D__FORMAT_RGB565, 16'd3, 16'd1, SramBase + 32'h1600, 32'd8, + 32'h8012_3456); + s_inject_write_error = 1'b1; + start_job(); + wait_for_terminal(1'b0, 1'b1, 1'b0); + check_error_code(`APB4_GA2D__ERROR_AXI_WRITE); + check_error_axi_response(2'd2); + s_inject_write_error = 1'b0; + clear_first_error(); + + configure_fill(`APB4_GA2D__FORMAT_RGB565, 16'd3, 16'd1, SramBase + 32'h1640, 32'd8, + 32'h8012_3456); + s_inject_write_decerr = 1'b1; + start_job(); + wait_for_terminal(1'b0, 1'b1, 1'b0); + check_error_code(`APB4_GA2D__ERROR_AXI_WRITE); + check_error_axi_response(2'd3); + s_inject_write_decerr = 1'b0; + clear_first_error(); + + run_write_protocol_quarantine(); + run_abort_during_error_drain(1'b0); + run_abort_during_error_drain(1'b1); + + configure_fill(`APB4_GA2D__FORMAT_RGB565, 16'd2, 16'd1, SramBase + MemoryBytes - 2, 32'd4, + 32'hffff_ffff); + s_ar_count_before_validation = s_ar_count; + s_aw_count_before_validation = s_aw_count; + start_job(); + wait_for_terminal(1'b0, 1'b1, 1'b0); + check_error_code(`APB4_GA2D__ERROR_ADDRESS_RANGE); + clear_first_error(); + if ((s_ar_count != s_ar_count_before_validation) || + (s_aw_count != s_aw_count_before_validation)) begin + $fatal(1, "GA2D validation failure issued AXI traffic"); + end + + configure_fill(`APB4_GA2D__FORMAT_RGB565, 16'd1, 16'd1, SramBase + 32'h1700, 32'd2, + 32'h8012_3456); + apb_write(`APB4_GA2D__TIMEOUT_CYCLES, 32'd8, 1'b0); + s_hold_aw_i = 1'b1; + start_job(); + wait_for_runtime_error(status); + if (!status[`APB4_GA2D__STATUS_DRAINING]) begin + $fatal(1, "GA2D watchdog error did not enter drain"); + end + apb_read(`APB4_GA2D__ERROR_STATUS, error_status); + if (error_status[`APB4_GA2D__ERROR_STATUS_CODE+:7] != `APB4_GA2D__ERROR_TIMEOUT) begin + $fatal(1, "GA2D watchdog error was not attributed as TIMEOUT"); + end + s_hold_aw_i = 1'b0; + wait_for_terminal(1'b0, 1'b1, 1'b0); + if (!core_safe_idle_o) begin + $fatal(1, "GA2D watchdog drain did not prove core-safe idle"); + end + resource_reset_i = 1'b1; + repeat (3) @(posedge clk_i); + resource_reset_i = 1'b0; + repeat (3) @(posedge clk_i); + apb_read(`APB4_GA2D__STATUS, status); + if (status[`APB4_GA2D__STATUS_RECOVERY_REQUIRED]) begin + $fatal(1, "GA2D coordinated resource recovery did not clear timeout state"); + end + apb_write(`APB4_GA2D__COMMAND, 32'h0000_0004, 1'b0); + + configure_fill(`APB4_GA2D__FORMAT_RGB565, 16'd1, 16'd1, SramBase + 32'h1740, 32'd2, + 32'h8012_3456); + s_hold_aw_i = 1'b1; + start_job(); + repeat (3) @(posedge clk_i); + bridge_clear_busy_i = 1'b1; + bridge_epoch_i = bridge_epoch_i + 1'b1; + @(posedge clk_i); + bridge_clear_busy_i = 1'b0; + s_hold_aw_i = 1'b0; + wait_for_runtime_error(status); + apb_read(`APB4_GA2D__ERROR_STATUS, error_status); + if (error_status[`APB4_GA2D__ERROR_STATUS_CODE+:7] != `APB4_GA2D__ERROR_EPOCH_LOST) begin + $fatal(1, "GA2D bridge epoch change was not attributed as EPOCH_LOST"); + end + wait_for_terminal(1'b0, 1'b1, 1'b0); + resource_reset_i = 1'b1; + repeat (3) @(posedge clk_i); + resource_reset_i = 1'b0; + repeat (3) @(posedge clk_i); + apb_read(`APB4_GA2D__STATUS, status); + if (status[`APB4_GA2D__STATUS_RECOVERY_REQUIRED]) begin + $fatal(1, "GA2D epoch recovery did not wait for the resource reset boundary"); + end + apb_write(`APB4_GA2D__COMMAND, 32'h0000_0004, 1'b0); + + clear_memory(8'hd3); + configure_fill(`APB4_GA2D__FORMAT_RGB565, 16'd16, 16'd2, SramBase + 32'h1800, 32'd32, + 32'h8012_3456); + apb_write(`APB4_GA2D__TIMEOUT_CYCLES, 32'd64, 1'b0); + s_hold_aw_i = 1'b1; + start_job(); + for (int unsigned attempt = 0; (attempt < 256) && !ga2d_axi4.awvalid; attempt++) begin + @(posedge clk_i); + end + if (!ga2d_axi4.awvalid) begin + $fatal(1, "GA2D did not present AW before the resource-stop test"); + end + @(posedge clk_i); + if (!ga2d_axi4.awvalid || ga2d_axi4.awready) begin + $fatal(1, "GA2D did not retain the stalled AW for a full permit cycle"); + end + resource_quiesce_i = 1'b1; + source_stop_i = 1'b1; + repeat (2) @(posedge clk_i); + s_hold_aw_i = 1'b0; + wait_for_terminal(1'b0, 1'b0, 1'b1); + check_abort_done_event(); + if (!core_safe_idle_o) begin + $fatal(1, "GA2D resource stop did not wait for core-safe idle"); + end + for (int unsigned byte_index = 0; byte_index < 32; byte_index++) begin + if (memory_byte(SramBase + 32'h1820 + byte_index) != 8'hd3) begin + $fatal(1, "GA2D emitted an unissued second-row write after source stop"); + end + end + block_ack_i = 1'b1; + repeat (2) @(posedge clk_i); + resource_quiesce_i = 1'b0; + source_stop_i = 1'b0; + block_ack_i = 1'b0; + repeat (4) @(posedge clk_i); + + $display("GA2D P4 DMA test passed with independent AXI delay coverage"); + $finish; + end + + initial begin + repeat (2000000) @(posedge clk_i); + $fatal(1, "GA2D P4 DMA test timed out"); + end +endmodule diff --git a/tests/rtl/ga2d_platform_tb.sv b/tests/rtl/ga2d_platform_tb.sv index e4428624..1ff35726 100644 --- a/tests/rtl/ga2d_platform_tb.sv +++ b/tests/rtl/ga2d_platform_tb.sv @@ -116,6 +116,7 @@ module ga2d_platform_tb; logic flush_i = 1'b0; logic [ 8:0] resource_block_i = '0; logic [ 1:0] mem_pad_mode_i = 2'd1; + logic ga2d_core_safe_idle_i = 1'b1; logic ext_h_block_i = 1'b0; logic sram_hold_ar_i = 1'b0; logic sram_hold_response_i = 1'b0; @@ -315,7 +316,6 @@ module ga2d_platform_tb; axi4_master_idle u_usb2_idle (.axi4(usb2_axi4)); axi4_master_idle u_apu_idle (.axi4(apu_axi4)); axi4_master_idle u_jpeg_idle (.axi4(jpeg_axi4)); - axi4_master_idle u_lp_data_idle (.axi4(lp_data_axi4)); axi4_master_idle u_ext_h_idle (.axi4(ext_h_axi4)); ga2d_platform_sram_target u_sram_target ( .clk_i (clk_hp_i), @@ -347,6 +347,7 @@ module ga2d_platform_tb; .flush_i (flush_i), .resource_block_i (resource_block_i), .mem_pad_mode_i (mem_pad_mode_i), + .ga2d_core_safe_idle_i (ga2d_core_safe_idle_i), .ext_h_block_i (ext_h_block_i), .ext_h_read_base_i (32'h3000_0000), .ext_h_read_limit_i (32'h4FFF_FFFF), @@ -491,6 +492,36 @@ module ga2d_platform_tb; end endtask + task automatic issue_lp_read(input logic id, input logic [31:0] address); + begin + @(negedge clk_lp_i); + lp_data_axi4.arid = id; + lp_data_axi4.araddr = address; + lp_data_axi4.arlen = '0; + lp_data_axi4.arsize = 3'd2; + lp_data_axi4.arburst = 2'b01; + lp_data_axi4.arlock = 1'b0; + lp_data_axi4.arcache = '0; + lp_data_axi4.arprot = '0; + lp_data_axi4.arqos = '0; + lp_data_axi4.arregion = '0; + lp_data_axi4.aruser = '0; + lp_data_axi4.arvalid = 1'b1; + do @(posedge clk_lp_i); while (!lp_data_axi4.arready); + @(negedge clk_lp_i); + lp_data_axi4.arvalid = 1'b0; + end + endtask + + task automatic expect_lp_read(input logic id); + begin + wait (lp_data_axi4.rvalid); + if ((lp_data_axi4.rid != id) || (lp_data_axi4.rresp != 2'b00)) begin + $fatal(1, "LP response did not survive the HP lifecycle flush"); + end + end + endtask + task automatic wait_for_data_ready; begin wait (ga2d_data_ready_o && !ga2d_source_stop_o); @@ -498,6 +529,38 @@ module ga2d_platform_tb; endtask initial begin + lp_data_axi4.awid = '0; + lp_data_axi4.awaddr = '0; + lp_data_axi4.awlen = '0; + lp_data_axi4.awsize = 3'd2; + lp_data_axi4.awburst = 2'b01; + lp_data_axi4.awlock = 1'b0; + lp_data_axi4.awcache = '0; + lp_data_axi4.awprot = '0; + lp_data_axi4.awqos = '0; + lp_data_axi4.awregion = '0; + lp_data_axi4.awuser = '0; + lp_data_axi4.awvalid = 1'b0; + lp_data_axi4.wdata = '0; + lp_data_axi4.wstrb = '0; + lp_data_axi4.wlast = 1'b1; + lp_data_axi4.wuser = '0; + lp_data_axi4.wvalid = 1'b0; + lp_data_axi4.bready = 1'b1; + lp_data_axi4.arid = '0; + lp_data_axi4.araddr = '0; + lp_data_axi4.arlen = '0; + lp_data_axi4.arsize = 3'd2; + lp_data_axi4.arburst = 2'b01; + lp_data_axi4.arlock = 1'b0; + lp_data_axi4.arcache = '0; + lp_data_axi4.arprot = '0; + lp_data_axi4.arqos = '0; + lp_data_axi4.arregion = '0; + lp_data_axi4.aruser = '0; + lp_data_axi4.arvalid = 1'b0; + lp_data_axi4.rready = 1'b1; + ga2d_axi4.awid = '0; ga2d_axi4.awaddr = '0; ga2d_axi4.awlen = '0; @@ -577,6 +640,22 @@ module ga2d_platform_tb; rst_mem_n_i = 1'b1; wait_for_data_ready(); + // A core that has not established a known-safe idle state cannot hand the + // resource to HP even when the address gate itself is empty. + @(negedge clk_io_i); + ga2d_core_safe_idle_i = 1'b0; + resource_block_i[8] = 1'b1; + repeat (5) @(posedge clk_hp_i); + if (ga2d_source_safe_idle_o || resource_block_ack_o[8] || resource_idle_o[8]) begin + $fatal(1, "unsafe GA2D core idle state allowed resource-8 handoff"); + end + @(negedge clk_io_i); + ga2d_core_safe_idle_i = 1'b1; + wait (resource_block_ack_o[8]); + @(negedge clk_io_i); + resource_block_i[8] = 1'b0; + wait_for_data_ready(); + fork issue_ga2d_read(3'd0, 32'h3000_0040); expect_ga2d_read(3'd0); @@ -595,6 +674,24 @@ module ga2d_platform_tb; $fatal(1, "GA2D write did not preserve global ID 7'h40"); end + wait (!flush_busy_o); + block_new_i = 1'b1; + repeat (2) @(posedge clk_hp_i); + issue_lp_read(1'b0, 32'h3000_0180); + repeat (2) @(posedge clk_hp_i); + @(negedge clk_hp_i); + flush_i = 1'b1; + repeat (4) @(posedge clk_lp_i); + repeat (4) @(posedge clk_hp_i); + @(negedge clk_hp_i); + flush_i = 1'b0; + wait (!flush_busy_o); + block_new_i = 1'b0; + expect_lp_read(1'b0); + if (sram_last_arid != 7'h28) begin + $fatal(1, "HP lifecycle flush discarded the queued LP request"); + end + wait (ga2d_source_safe_idle_o); @(negedge clk_io_i); resource_block_i[8] = 1'b1; @@ -688,7 +785,8 @@ module ga2d_platform_tb; wait (sram_last_arid == 7'h42); epoch_before = ga2d_bridge_epoch_o; @(negedge clk_hp_i); - flush_i = 1'b1; + flush_i = 1'b1; + sram_hold_response_i = 1'b0; repeat (4) @(posedge clk_io_i); if (ga2d_data_ready_o) begin $fatal(1, "GA2D data-ready remained asserted during a warm flush"); @@ -701,8 +799,8 @@ module ga2d_platform_tb; if (ga2d_axi4.rvalid) begin $fatal(1, "late pre-flush response reached the GA2D source"); end - sram_hold_response_i = 1'b0; wait_for_data_ready(); + wait (!flush_busy_o); epoch_before = ga2d_bridge_epoch_o; @(negedge clk_hp_i); @@ -726,7 +824,7 @@ module ga2d_platform_tb; rst_io_n_i = 1'b1; wait_for_data_ready(); - $display("GA2D P2 bridge, ID7, lifecycle, flush, and reset test passed"); + $display("GA2D P2 bridge, ID7, lifecycle, flush, LP retention, and reset test passed"); $finish; end diff --git a/tests/rtl/ga2d_tb.sv b/tests/rtl/ga2d_tb.sv index e9ffcdff..b8cff526 100644 --- a/tests/rtl/ga2d_tb.sv +++ b/tests/rtl/ga2d_tb.sv @@ -13,6 +13,7 @@ module ga2d_tb; logic idle_i = 1'b1; logic done_i = 1'b0; logic aborted_i = 1'b0; + logic error_i = 1'b0; logic [ 2:0] irq_event_i = 3'd0; logic error_valid_i = 1'b0; logic [ 6:0] error_code_i = 7'd0; @@ -21,10 +22,21 @@ module ga2d_tb; logic [31:0] error_address_i = 32'd0; logic soft_reset_o; logic abort_o; + logic start_o; + logic snapshot_o; logic idle_o; logic irq_o; + logic [63:0] cycles_i = 64'd0; + logic [63:0] read_bytes_i = 64'd0; + logic [63:0] write_bytes_i = 64'd0; + logic [63:0] read_stalls_i = 64'd0; + logic [63:0] write_stalls_i = 64'd0; + logic [63:0] pipe_stalls_i = 64'd0; + logic [15:0] lines_done_i = 16'd0; logic [31:0] read_data; int unsigned soft_reset_count; + int unsigned start_count; + int unsigned snapshot_count; apb4_if apb4 ( .pclk (clk_i), @@ -37,6 +49,12 @@ module ga2d_tb; if (soft_reset_o) begin soft_reset_count++; end + if (start_o) begin + start_count++; + end + if (snapshot_o) begin + snapshot_count++; + end end ga2d_reg u_dut ( @@ -50,16 +68,26 @@ module ga2d_tb; .idle_i (idle_i), .done_i (done_i), .aborted_i (aborted_i), + .error_i (error_i), .irq_event_i (irq_event_i), .error_valid_i (error_valid_i), .error_code_i (error_code_i), .error_stage_i (error_stage_i), .error_axi_response_i(error_axi_response_i), .error_address_i (error_address_i), + .cycles_i (cycles_i), + .read_bytes_i (read_bytes_i), + .write_bytes_i (write_bytes_i), + .read_stalls_i (read_stalls_i), + .write_stalls_i (write_stalls_i), + .pipe_stalls_i (pipe_stalls_i), + .lines_done_i (lines_done_i), .apb4 (apb4), .config_o (), + .start_o (start_o), .soft_reset_o (soft_reset_o), .abort_o (abort_o), + .snapshot_o (snapshot_o), .idle_o (idle_o), .irq_o (irq_o) ); @@ -149,6 +177,8 @@ module ga2d_tb; apb4.pwdata = '0; apb4.pstrb = '0; soft_reset_count = 0; + start_count = 0; + snapshot_count = 0; repeat (3) @(posedge clk_i); rst_n_i = 1'b1; @@ -158,7 +188,11 @@ module ga2d_tb; apb_read(`APB4_GA2D__IP_VERSION, 1'b0, read_data); if (read_data != 32'h0001_0000) $fatal(1, "GA2D IP_VERSION mismatch"); apb_read(`APB4_GA2D__CAPABILITY, 1'b0, read_data); - if (read_data != 32'h0000_0040) $fatal(1, "GA2D shell capability mismatch"); + if (read_data != 32'h0000_03e3) $fatal(1, "GA2D P4 capability mismatch"); + apb_read(`APB4_GA2D__LIMITS, 1'b0, read_data); + if (read_data != 32'h0820_2010) $fatal(1, "GA2D P4 limits mismatch"); + apb_read(`APB4_GA2D__FORMAT_CAPABILITY, 1'b0, read_data); + if (read_data != 32'h000f_000f) $fatal(1, "GA2D P4 format capability mismatch"); apb_read(`APB4_GA2D__TIMEOUT_CYCLES, 1'b0, read_data); if (read_data != 32'h0010_0000) $fatal(1, "GA2D timeout reset mismatch"); apb_read(`APB4_GA2D__GLOBAL_ALPHA, 1'b0, read_data); @@ -177,10 +211,20 @@ module ga2d_tb; apb_read(12'h06c, 1'b1, read_data); apb_read(12'h003, 1'b1, read_data); - apb_write(`APB4_GA2D__COMMAND, 32'h0000_0001, 4'hf, 1'b1); - apb_write(`APB4_GA2D__PERF_SNAPSHOT, 32'h0000_0001, 4'hf, 1'b1); + apb_write(`APB4_GA2D__COMMAND, 32'h0000_0001, 4'hf, 1'b0); + if (start_count != 1) $fatal(1, "GA2D P4 START did not execute exactly once"); + cycles_i = 64'h1122_3344_5566_7788; + read_bytes_i = 64'h0102_0304_0506_0708; + write_bytes_i = 64'h8877_6655_4433_2211; + lines_done_i = 16'h0023; + apb_write(`APB4_GA2D__PERF_SNAPSHOT, 32'h0000_0001, 4'hf, 1'b0); + if (snapshot_count != 1) $fatal(1, "GA2D P4 snapshot did not execute exactly once"); apb_read(`APB4_GA2D__SNAP_CYCLES_LO, 1'b0, read_data); - if (read_data != 32'd0) $fatal(1, "GA2D P3 counter stub is nonzero"); + if (read_data != 32'h5566_7788) $fatal(1, "GA2D P4 snapshot cycle mismatch"); + apb_read(`APB4_GA2D__SNAP_READ_BYTES_HI, 1'b0, read_data); + if (read_data != 32'h0102_0304) $fatal(1, "GA2D P4 snapshot read mismatch"); + apb_read(`APB4_GA2D__SNAP_LINES_DONE, 1'b0, read_data); + if (read_data != 32'h0000_0023) $fatal(1, "GA2D P4 snapshot line mismatch"); @(negedge clk_i); irq_event_i = `APB4_GA2D__IRQ_ALL; @@ -239,12 +283,12 @@ module ga2d_tb; apb_read(`APB4_GA2D__STATUS, 1'b0, read_data); if (read_data != 32'h0000_00b7) $fatal(1, "GA2D live status mismatch"); - $display("GA2D P3 APB shell test passed"); + $display("GA2D P4 APB shell test passed"); $finish; end initial begin repeat (400) @(posedge clk_i); - $fatal(1, "GA2D P3 APB shell test timed out"); + $fatal(1, "GA2D P4 APB shell test timed out"); end endmodule diff --git a/tests/rtl/ga2d_wrapper_tb.sv b/tests/rtl/ga2d_wrapper_tb.sv index 2ac6368c..b5122542 100644 --- a/tests/rtl/ga2d_wrapper_tb.sv +++ b/tests/rtl/ga2d_wrapper_tb.sv @@ -13,6 +13,7 @@ module ga2d_wrapper_tb; logic bridge_clear_busy_i = 1'b0; logic [ 7:0] bridge_epoch_i = 8'd0; logic data_ready_i = 1'b1; + logic [ 1:0] mem_pad_mode_i = 2'd0; logic idle_o; logic irq_o; logic [31:0] read_data; @@ -21,9 +22,32 @@ module ga2d_wrapper_tb; .pclk (clk_i), .presetn(rst_n_i) ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(64), + .ID_WIDTH (3), + .USER_WIDTH(1) + ) ga2d_axi4 ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); always #5 clk_i = ~clk_i; + assign ga2d_axi4.awready = 1'b1; + assign ga2d_axi4.wready = 1'b1; + assign ga2d_axi4.bid = '0; + assign ga2d_axi4.bresp = 2'd0; + assign ga2d_axi4.buser = '0; + assign ga2d_axi4.bvalid = 1'b0; + assign ga2d_axi4.arready = 1'b1; + assign ga2d_axi4.rid = '0; + assign ga2d_axi4.rdata = '0; + assign ga2d_axi4.rresp = 2'd0; + assign ga2d_axi4.rlast = 1'b1; + assign ga2d_axi4.ruser = '0; + assign ga2d_axi4.rvalid = 1'b0; + apb4_ga2d u_dut ( .clk_i (clk_i), .rst_n_i (rst_n_i), @@ -35,7 +59,9 @@ module ga2d_wrapper_tb; .bridge_clear_busy_i(bridge_clear_busy_i), .bridge_epoch_i (bridge_epoch_i), .data_ready_i (data_ready_i), + .mem_pad_mode_i (mem_pad_mode_i), .apb4 (apb4), + .ga2d_axi4 (ga2d_axi4), .idle_o (idle_o), .irq_o (irq_o) ); @@ -248,12 +274,12 @@ module ga2d_wrapper_tb; $fatal(1, "GA2D soft reset did not clear retained diagnostic state"); end - $display("GA2D P3 wrapper lifecycle test passed"); + $display("GA2D P4 wrapper lifecycle test passed"); $finish; end initial begin repeat (400) @(posedge clk_i); - $fatal(1, "GA2D P3 wrapper lifecycle test timed out"); + $fatal(1, "GA2D P4 wrapper lifecycle test timed out"); end endmodule diff --git a/tests/rtl/hp_lifecycle_controller_tb.sv b/tests/rtl/hp_lifecycle_controller_tb.sv index ff6add57..b403a1ce 100644 --- a/tests/rtl/hp_lifecycle_controller_tb.sv +++ b/tests/rtl/hp_lifecycle_controller_tb.sv @@ -23,7 +23,7 @@ module hp_lifecycle_controller_tb; .hp_idle_i (hp_idle_i), .flush_busy_i (flush_busy_i), .cache_clean_i (cache_clean_i), - .timeout_i (16'd4), + .timeout_i (16'd16), .hp_release_o (hp_release_o), .block_new_o (block_new_o), .flush_o (flush_o), @@ -58,6 +58,10 @@ module hp_lifecycle_controller_tb; if (block_new_o || !hp_release_o) begin $fatal(1, "cache-maintenance phase blocked HP before acknowledgement"); end + repeat (8) @(negedge clk_i); + if (!cache_request_o || forced_fault_o) begin + $fatal(1, "cache-maintenance phase did not retain a delayed acknowledgement window"); + end cache_clean_i = 1'b1; @(posedge clk_i); @(negedge clk_i); diff --git a/tests/rtl/resource_controller_tb.sv b/tests/rtl/resource_controller_tb.sv index 2273343b..5dde5f5e 100644 --- a/tests/rtl/resource_controller_tb.sv +++ b/tests/rtl/resource_controller_tb.sv @@ -121,6 +121,10 @@ module resource_controller_tb; if ((owner_o[0] != 2'd1) || irq_lp_o[0] || !irq_hp_o[0]) begin $fatal(1, "HP resource ownership or IRQ routing mismatch"); end + apb_read(12'h108, read_data); + if (read_data != 32'h0000_00D5) begin + $fatal(1, "resource STATUS bit layout mismatch after HP handoff"); + end apb_read(12'h110, read_data); if (read_data != 32'd1) $fatal(1, "resource handoff counter mismatch"); @@ -140,6 +144,10 @@ module resource_controller_tb; if (!quiesce_o[1] || !reset_o[1]) begin $fatal(1, "resource lifecycle controls did not update"); end + apb_read(12'h128, read_data); + if (read_data != 32'h0000_005C) begin + $fatal(1, "resource STATUS lifecycle bit layout mismatch: %h", read_data); + end irq_i[7] = 1'b1; #1; diff --git a/tests/test_ga2d.py b/tests/test_ga2d.py index eccb5a3e..3a67bca2 100644 --- a/tests/test_ga2d.py +++ b/tests/test_ga2d.py @@ -1,10 +1,11 @@ -"""Directed GA2D Phase 3 APB4 shell verification.""" +"""Required GA2D Phase 4 APB4 shell and private-DMA RTL verification.""" from __future__ import annotations import os import shutil import subprocess +import sys from pathlib import Path import pytest @@ -13,16 +14,69 @@ import pytest ROOT = Path(__file__).resolve().parents[1] -def run_ga2d_rtl_test( - tmp_path: Path, top_module: str, testbench: str, success_marker: str -) -> None: - verilator = shutil.which("verilator") - if verilator is None: - pytest.fail("GA2D P3 RTL tests require Verilator") +def _require_tool(name: str) -> str: + path = shutil.which(name) + if path is None: + pytest.fail(f"GA2D P4 RTL tests require {name}") + return path + +def _generate_memory_map(tmp_path: Path) -> Path: + output = tmp_path / "memory_map" + subprocess.run( + [ + sys.executable, + str(ROOT / "rtl/mini/address_map/generate_memory_map.py"), + "--map", + str(ROOT / "rtl/mini/address_map/memory_map.json"), + "--output-dir", + str(output), + "--have-sram-if", + "YES", + "--sram-size-kib", + "32", + ], + check=True, + capture_output=True, + text=True, + ) + return output / "rtl" + + +def _ga2d_sources( + testbench: str, memory_map: Path, *, sv2v_filelist: bool = False +) -> list[str]: common = ROOT / "rtl/managed/clusterip/common/rtl" multimedia = ROOT / "rtl/ip/multimedia" + include_prefix = "+incdir+" if sv2v_filelist else "-I" + return [ + include_prefix + str(memory_map), + include_prefix + str(common), + include_prefix + str(common / "interface"), + include_prefix + str(multimedia), + str(common / "interface/apb4_if.sv"), + str(common / "interface/axi4_if.sv"), + str(common / "utils/register.sv"), + str(common / "utils/fifo.sv"), + str(common / "stream/round_robin_arbiter.sv"), + str(multimedia / "ga2d_pkg.sv"), + str(multimedia / "ga2d_addr_gen.sv"), + str(multimedia / "ga2d_axi4_master.sv"), + str(multimedia / "ga2d_dma.sv"), + str(multimedia / "ga2d_pixel.sv"), + str(multimedia / "ga2d_core.sv"), + str(multimedia / "ga2d_reg.sv"), + str(multimedia / "apb4_ga2d.sv"), + str(ROOT / "tests/rtl" / testbench), + ] + + +def _run_verilator_test( + tmp_path: Path, top_module: str, testbench: str, success_marker: str +) -> None: + verilator = _require_tool("verilator") output = tmp_path / top_module + memory_map = _generate_memory_map(tmp_path) ccache_tmp = tmp_path / "ccache" ccache_tmp.mkdir() subprocess.run( @@ -33,15 +87,7 @@ def run_ga2d_rtl_test( "-Wno-fatal", "--top-module", top_module, - "-I" + str(common), - "-I" + str(common / "interface"), - "-I" + str(multimedia), - str(common / "interface/apb4_if.sv"), - str(common / "utils/register.sv"), - str(multimedia / "ga2d_pkg.sv"), - str(multimedia / "ga2d_reg.sv"), - str(multimedia / "apb4_ga2d.sv"), - str(ROOT / "tests/rtl" / testbench), + *_ga2d_sources(testbench, memory_map), "-Mdir", str(tmp_path / "obj"), "-o", @@ -60,19 +106,56 @@ def run_ga2d_rtl_test( assert success_marker in result.stdout -def test_ga2d_p3_apb_shell(tmp_path: Path) -> None: - run_ga2d_rtl_test( +def _run_iverilog_test( + tmp_path: Path, top_module: str, testbench: str, success_marker: str +) -> None: + iverilog = _require_tool("iverilog") + vvp = _require_tool("vvp") + _require_tool("sv2v") + memory_map = _generate_memory_map(tmp_path) + source_list = tmp_path / f"{top_module}.fl" + source_list.write_text( + "\n".join((*_ga2d_sources(testbench, memory_map, sv2v_filelist=True), "")), + encoding="utf-8", + ) + converted = tmp_path / f"{top_module}.v" + subprocess.run( + [ + sys.executable, + str(ROOT / "rtl/mini/script/convt_sv2v.py"), + "-f", + str(source_list), + "--output", + str(converted), + ], + check=True, + capture_output=True, + text=True, + ) + simulation = tmp_path / top_module + subprocess.run( + [iverilog, "-g2012", "-s", top_module, "-o", str(simulation), str(converted)], + check=True, + capture_output=True, + text=True, + ) + result = subprocess.run([vvp, str(simulation)], check=True, capture_output=True, text=True) + assert success_marker in result.stdout + + +def test_ga2d_p4_apb_shell(tmp_path: Path) -> None: + _run_verilator_test( tmp_path, "ga2d_tb", "ga2d_tb.sv", - "GA2D P3 APB shell test passed", + "GA2D P4 APB shell test passed", ) -def test_ga2d_p3_wrapper_lifecycle(tmp_path: Path) -> None: - run_ga2d_rtl_test( +def test_ga2d_p4_wrapper_lifecycle(tmp_path: Path) -> None: + _run_verilator_test( tmp_path, "ga2d_wrapper_tb", "ga2d_wrapper_tb.sv", - "GA2D P3 wrapper lifecycle test passed", + "GA2D P4 wrapper lifecycle test passed", ) diff --git a/tests/test_ga2d_dma.py b/tests/test_ga2d_dma.py new file mode 100644 index 00000000..0d69bb89 --- /dev/null +++ b/tests/test_ga2d_dma.py @@ -0,0 +1,159 @@ +"""Independent P4 FILL/COPY reference-model coverage.""" + +from __future__ import annotations + +import random +from pathlib import Path + +import pytest + +from test_ga2d import _run_iverilog_test, _run_verilator_test + +from ga2d_reference import ( + ARGB8888, + RGB565, + RGB888, + XRGB8888, + Plane, + copy, + extent, + fill, + pack_color, +) + + +def test_p4_dma_rtl_verilator(tmp_path: Path) -> None: + _run_verilator_test( + tmp_path, + "ga2d_dma_tb", + "ga2d_dma_tb.sv", + "GA2D P4 DMA test passed with independent AXI delay coverage", + ) + + +def test_p4_dma_rtl_iverilog(tmp_path: Path) -> None: + _run_iverilog_test( + tmp_path, + "ga2d_dma_tb", + "ga2d_dma_tb.sv", + "GA2D P4 DMA test passed with independent AXI delay coverage", + ) + + +@pytest.mark.parametrize( + ("fmt", "expected"), + [ + (RGB565, bytes((0xAA, 0x11))), + (RGB888, bytes((0x12, 0x34, 0x56))), + (XRGB8888, bytes((0x56, 0x34, 0x12, 0xFF))), + (ARGB8888, bytes((0x56, 0x34, 0x12, 0x80))), + ], +) +def test_p4_fill_color_packing_is_byte_accurate(fmt: int, expected: bytes) -> None: + assert pack_color(0x80123456, fmt) == expected + + +def test_p4_fill_writes_logical_rows_without_touching_guards_or_padding() -> None: + memory = bytearray([0xD3] * 256) + destination = Plane(base=18, pitch=18, fmt=RGB888) + + fill(memory, destination, width=3, height=3, color=0xAA102030) + + expected = bytes((0x10, 0x20, 0x30)) * 3 + for line in range(3): + row = destination.base + line * destination.pitch + assert memory[row : row + len(expected)] == expected + assert memory[row - 1] == 0xD3 + assert memory[row + len(expected) : row + destination.pitch] == bytes( + [0xD3] * (destination.pitch - len(expected)) + ) + + +def test_p4_copy_preserves_bytes_and_leaves_row_padding_and_guards_untouched() -> None: + memory = bytearray([0xC7] * 512) + foreground = Plane(base=20, pitch=24, fmt=ARGB8888) + destination = Plane(base=292, pitch=32, fmt=ARGB8888) + logical_bytes = 5 * 4 + for line in range(3): + source = foreground.base + line * foreground.pitch + memory[source : source + logical_bytes] = bytes( + (line * 31 + offset for offset in range(logical_bytes)) + ) + + copy(memory, foreground, destination, width=5, height=3) + + for line in range(3): + source = foreground.base + line * foreground.pitch + target = destination.base + line * destination.pitch + assert memory[target : target + logical_bytes] == memory[source : source + logical_bytes] + assert memory[target - 1] == 0xC7 + assert memory[target + logical_bytes : target + destination.pitch] == bytes( + [0xC7] * (destination.pitch - logical_bytes) + ) + + +@pytest.mark.parametrize( + ("foreground", "destination"), + [ + (Plane(32, 16, RGB565), Plane(32, 16, RGB565)), + (Plane(32, 16, RGB565), Plane(40, 16, RGB565)), + (Plane(32, 16, RGB565), Plane(48, 16, RGB565)), + ], +) +def test_p4_copy_rejects_all_foreground_destination_overlap( + foreground: Plane, destination: Plane +) -> None: + memory = bytearray(256) + with pytest.raises(ValueError, match="overlap"): + copy(memory, foreground, destination, width=4, height=2) + + +def test_p4_extent_allows_end_at_32_bit_limit_and_rejects_overflow() -> None: + edge = Plane(base=0xFFFFFFFC, pitch=4, fmt=XRGB8888) + assert extent(edge, width=1, height=1) == (0xFFFFFFFC, 1 << 32) + with pytest.raises(ValueError, match="overflows"): + extent(Plane(base=0xFFFFFFFC, pitch=8, fmt=XRGB8888), width=1, height=2) + + +def test_p4_reference_campaign_is_deterministic_for_ten_thousand_small_jobs() -> None: + formats = (RGB565, RGB888, XRGB8888, ARGB8888) + completed = 0 + for seed in range(10): + random_source = random.Random(seed) + for _ in range(1000): + fmt = random_source.choice(formats) + bytes_per_pixel = len(pack_color(0, fmt)) + width = random_source.randint(1, 15) + height = random_source.randint(1, 7) + logical_bytes = width * bytes_per_pixel + foreground = Plane( + base=120, + pitch=logical_bytes + bytes_per_pixel * random_source.randint(0, 3), + fmt=fmt, + ) + destination = Plane( + base=2064, + pitch=logical_bytes + bytes_per_pixel * random_source.randint(0, 3), + fmt=fmt, + ) + memory = bytearray([0xA5] * 4096) + for line in range(height): + source = foreground.base + line * foreground.pitch + memory[source : source + logical_bytes] = random_source.randbytes(logical_bytes) + if random_source.randrange(2) == 0: + copy(memory, foreground, destination, width, height) + for line in range(height): + source = foreground.base + line * foreground.pitch + target = destination.base + line * destination.pitch + assert memory[target : target + logical_bytes] == memory[ + source : source + logical_bytes + ] + else: + color = random_source.getrandbits(32) + fill(memory, destination, width, height, color) + expected = pack_color(color, fmt) * width + for line in range(height): + target = destination.base + line * destination.pitch + assert memory[target : target + logical_bytes] == expected + completed += 1 + assert completed == 10000 diff --git a/tests/test_ga2d_platform.py b/tests/test_ga2d_platform.py index 22e5c06a..2cdfb64e 100644 --- a/tests/test_ga2d_platform.py +++ b/tests/test_ga2d_platform.py @@ -9,11 +9,13 @@ import subprocess import sys from pathlib import Path +import pytest + ROOT = Path(__file__).resolve().parents[1] -def test_ga2d_p3_activates_only_the_apb_shell_and_irq_route() -> None: +def test_ga2d_p4_activates_the_real_private_axi64_master_and_irq_route() -> None: memory_map = json.loads( (ROOT / "rtl/mini/address_map/memory_map.json").read_text(encoding="utf-8") ) @@ -23,6 +25,7 @@ def test_ga2d_p3_activates_only_the_apb_shell_and_irq_route() -> None: ) apb4_periph = (ROOT / "rtl/mini/top/apb4_periph.sv").read_text(encoding="utf-8") apb4_system = (ROOT / "rtl/mini/top/apb4_system.sv").read_text(encoding="utf-8") + data_plane = (ROOT / "rtl/mini/top/soc_data_plane.sv").read_text(encoding="utf-8") top = (ROOT / "rtl/mini/top/retrosoc.sv").read_text(encoding="utf-8") assert "APB4_GA" not in regions @@ -48,6 +51,11 @@ def test_ga2d_p3_activates_only_the_apb_shell_and_irq_route() -> None: assert topology["interrupts"][-1]["signal"] == "resource_irq_lp_i[7]" assert { "ga2d_pkg.sv", + "ga2d_addr_gen.sv", + "ga2d_axi4_master.sv", + "ga2d_dma.sv", + "ga2d_pixel.sv", + "ga2d_core.sv", "ga2d_reg.sv", "apb4_ga2d.sv", } <= {path.name for path in (ROOT / "rtl/ip/multimedia").glob("*ga2d*.sv")} @@ -57,13 +65,21 @@ def test_ga2d_p3_activates_only_the_apb_shell_and_irq_route() -> None: assert "s_hp_plic_source[11] = resource_irq_hp_i[7];" in apb4_periph assert "s_ga2d_idle && s_ga2d_source_safe_idle && s_resource_idle_pclk[8]" in top assert "s_ga2d_idle && s_ga2d_source_safe_idle && s_resource_block_ack_pclk[8]" in top - assert "axi4_master_idle u_ga2d_master_idle" in top + assert "axi4_master_idle u_ga2d_master_idle" not in top + assert "axi4_if.master" in apb4_periph + assert "ga2d_axi4" in apb4_periph + assert "ga2d_axi4" in top + assert "core_safe_idle" in apb4_periph + assert "assign s_ga2d_core_safe_idle = ga2d_core_safe_idle_i;" in data_plane + lp_data_cdc = data_plane.split(") u_lp_data_cdc (", 1)[1].split(");", 1)[0] + assert ".clear_i (1'b0)" in lp_data_cdc + assert "HP lifecycle flush invalidates HP transport" in lp_data_cdc -def test_ga2d_p2_bridge_prefix_lifecycle_and_recovery(tmp_path: Path) -> None: +def test_ga2d_p4_preserves_p2_bridge_prefix_lifecycle_and_recovery(tmp_path: Path) -> None: verilator = shutil.which("verilator") if verilator is None: - return + pytest.fail("GA2D P4 platform lifecycle test requires Verilator") topology = tmp_path / "topology" memory_map = tmp_path / "memory_map" @@ -158,4 +174,4 @@ def test_ga2d_p2_bridge_prefix_lifecycle_and_recovery(tmp_path: Path) -> None: }, ) result = subprocess.run([output], check=True, text=True, capture_output=True) - assert "GA2D P2 bridge, ID7, lifecycle, flush, and reset test passed" in result.stdout + assert "GA2D P2 bridge, ID7, lifecycle, flush, LP retention, and reset test passed" in result.stdout diff --git a/tests/test_ga2d_register_parity.py b/tests/test_ga2d_register_parity.py index 30656a49..fab8daae 100644 --- a/tests/test_ga2d_register_parity.py +++ b/tests/test_ga2d_register_parity.py @@ -106,9 +106,9 @@ def test_ga2d_register_offsets_and_scalar_encodings_are_exhaustive() -> None: scalar_mapping = { "APB4_GA2D__IP_ID_VALUE": "RS_GA2D_IP_ID_VALUE", "APB4_GA2D__IP_VERSION_VALUE": "RS_GA2D_IP_VERSION_VALUE", - "APB4_GA2D__CAPABILITY_P3": "RS_GA2D_CAPABILITY_P3", - "APB4_GA2D__LIMITS_P3": "RS_GA2D_LIMITS_P3", - "APB4_GA2D__FORMAT_CAPABILITY_P3": "RS_GA2D_FORMAT_CAPABILITY_P3", + "APB4_GA2D__CAPABILITY_P4": "RS_GA2D_CAPABILITY_P4", + "APB4_GA2D__LIMITS_P4": "RS_GA2D_LIMITS_P4", + "APB4_GA2D__FORMAT_CAPABILITY_P4": "RS_GA2D_FORMAT_CAPABILITY_P4", "APB4_GA2D__TIMEOUT_CYCLES_RESET": "RS_GA2D_TIMEOUT_CYCLES_RESET", "APB4_GA2D__GLOBAL_ALPHA_RESET": "RS_GA2D_GLOBAL_ALPHA_RESET", "APB4_GA2D__IRQ_ALL": "RS_GA2D_IRQ_ALL", @@ -118,6 +118,9 @@ def test_ga2d_register_offsets_and_scalar_encodings_are_exhaustive() -> None: for rtl_name, c_name in {**offset_mapping, **scalar_mapping}.items(): assert rtl[rtl_name] == c[c_name], f"{rtl_name} != {c_name}" + assert rtl["APB4_GA2D__CAPABILITY_P4"] == 0x000003E3 + assert rtl["APB4_GA2D__LIMITS_P4"] == 0x08202010 + assert rtl["APB4_GA2D__FORMAT_CAPABILITY_P4"] == 0x000F000F assert {name for name in rtl if name.endswith(tuple(OFFSETS))} >= set(offset_mapping) assert expected_c <= set(c) @@ -230,9 +233,9 @@ def test_ga2d_handwritten_definition_sets_have_no_missing_or_extra_entries() -> rtl_scalar_names = { "IP_ID_VALUE", "IP_VERSION_VALUE", - "CAPABILITY_P3", - "LIMITS_P3", - "FORMAT_CAPABILITY_P3", + "CAPABILITY_P4", + "LIMITS_P4", + "FORMAT_CAPABILITY_P4", "TIMEOUT_CYCLES_RESET", "GLOBAL_ALPHA_RESET", "IRQ_ALL", @@ -316,9 +319,9 @@ def test_ga2d_handwritten_definition_sets_have_no_missing_or_extra_entries() -> "RS_GA2D_IP_VERSION_VALUE", "RS_GA2D_IP_VERSION_MAJOR_MASK", "RS_GA2D_IP_VERSION_MAJOR_1", - "RS_GA2D_CAPABILITY_P3", - "RS_GA2D_LIMITS_P3", - "RS_GA2D_FORMAT_CAPABILITY_P3", + "RS_GA2D_CAPABILITY_P4", + "RS_GA2D_LIMITS_P4", + "RS_GA2D_FORMAT_CAPABILITY_P4", "RS_GA2D_TIMEOUT_CYCLES_RESET", "RS_GA2D_GLOBAL_ALPHA_RESET", "RS_GA2D_COMMAND_START", diff --git a/tests/test_hp_boot_bundle.py b/tests/test_hp_boot_bundle.py index 3abc0d2a..b3b162f4 100644 --- a/tests/test_hp_boot_bundle.py +++ b/tests/test_hp_boot_bundle.py @@ -105,3 +105,31 @@ def test_mailbox_c_offsets_match_handwritten_rtl() -> None: "LP_INTR_ENABLE", ) } + + +def test_hp_boot_uses_private_ga2d_mailbox_and_resource_lifecycle() -> None: + source = (ROOT / "app/apps/hp_boot/main.c").read_text(encoding="utf-8") + + for requirement in ( + "RS_HP_BOOT_READY_EVENT", + "RS_HP_BOOT_READY_ARG", + "RS_HP_BOOT_GA2D_START_COMMAND", + "RS_HP_BOOT_GA2D_PASS_EVENT", + "RS_HP_BOOT_GA2D_FAIL_EVENT", + "RS_HP_BOOT_GA2D_CACHE_EVENT", + "rs_resource_set_owner(RS_RESOURCE_GA2D, RS_RESOURCE_OWNER_HP, false)", + "rs_resource_set_owner(RS_RESOURCE_GA2D, RS_RESOURCE_OWNER_LP, false)", + "rs_resource_acknowledge_cache_clean()", + "rs_hp_boot_wait_message", + "rs_hp_boot_wait_cache_request", + "rs_hp_boot_wait_hp_held", + "HP_GA2D_PASS", + "HP_GA2D_IDLE", + "HP_GA2D_CACHE_REQUESTED", + "HP_GA2D_CACHE_REQUEST", + "HP_GA2D_CACHE_MESSAGE", + "HP_GA2D_CACHE_ACK", + "HP_GA2D_HELD", + "HP_GA2D_CACHE_CLEAN", + ): + assert requirement in source diff --git a/tests/test_hp_platform.py b/tests/test_hp_platform.py index ba729651..899b6605 100644 --- a/tests/test_hp_platform.py +++ b/tests/test_hp_platform.py @@ -99,6 +99,45 @@ def test_hp_smoke_payload_uses_hart1_platform_abi() -> None: assert "0x10019000" in source assert "0x4c4e5801" in source.lower() assert "HP_SMOKE_READY" in source + assert "0x10012000" in source + assert "0x2000a000" in source.lower() + assert "cbo.clean" in source + assert "cbo.inval" in source + assert source.count(".balign 64") >= 4 + for marker in ("HP_GA2D_START", "HP_GA2D_PASS", "HP_GA2D_FAIL", "HP_GA2D_CACHE"): + assert marker in source + + +def test_hp_smoke_simulation_requires_ga2d_result_and_cache_lifecycle_markers() -> None: + makefile = (ROOT / "Makefile").read_text(encoding="utf-8") + target = makefile.split("hp-smoke-sim: hp-smoke-bundle comp", 1)[1].split("\nifeq", 1)[0] + + assert "HP_SMOKE_SIM_TIME ?= 300" in makefile + assert "SOC_SIM_TIME=$(HP_SMOKE_SIM_TIME)" in target + assert "VERILATOR_SIM_ARGS=--fast-flash" in target + + for marker in ( + "SIM_TEST_PASS code=0", + "HP_LINUX_READY", + "HP_GA2D_PASS", + "HP_GA2D_CACHE_CLEAN", + ): + assert f"--require '{marker}'" in target + + +def test_hp_cache_handshake_budget_covers_cbo_and_mailbox_round_trip() -> None: + source = (ROOT / "rtl/mini/top/retrosoc.sv").read_text(encoding="utf-8") + + assert "HpCacheHandshakeTimeout = 16'hffff" in source + assert ".timeout_i (HpCacheHandshakeTimeout)" in source + + +def test_hp_lifecycle_flush_preserves_unadmitted_lp_requests() -> None: + source = (ROOT / "rtl/mini/top/soc_data_plane.sv").read_text(encoding="utf-8") + lp_data_cdc = source.split(") u_lp_data_cdc (", 1)[1].split(");", 1)[0] + + assert ".clear_i (1'b0)" in lp_data_cdc + assert "HP lifecycle flush invalidates HP transport" in lp_data_cdc def test_linux_build_uses_external_opensbi_platform_and_actual_initrd_end() -> None: diff --git a/tests/test_performance_log.py b/tests/test_performance_log.py index 74b9e3e9..18d19d7a 100644 --- a/tests/test_performance_log.py +++ b/tests/test_performance_log.py @@ -12,7 +12,10 @@ from scripts.parse_performance_log import parse_log def test_parse_log_requires_terminal_marker_and_complete_samples() -> None: report = parse_log( "PERF region=sdram op=read words=1024 checksum=0x1234 cycles=123 mgmt_wait=10 " - "apb4_periph_wait=9 sdram_wait=8 psram_wait=0 flash_wait=0 dma_wait=0\n" + "apb4_periph_wait=9 sdram_wait=8 psram_wait=0 flash_wait=0 dma_wait=0 " + "workload_bytes=4096 pixels=0 jobs=0 cpu_cycles=123 cpu_hz=24000000 " + "ga2d_features=0x3e3 ga2d_limits=0x08202010 ga2d_formats=0x000f000f " + "ga2d_cycles=0 ga2d_read_bytes=0 ga2d_write_bytes=0\n" "PERF_BENCHMARK_PASS\n" ) @@ -22,15 +25,28 @@ def test_parse_log_requires_terminal_marker_and_complete_samples() -> None: assert report["samples"] == [ { "checksum": 0x1234, + "configuration": { + "cpu_hz": 24000000, + "ga2d_features": 0x3E3, + "ga2d_formats": 0x000F000F, + "ga2d_limits": 0x08202010, + }, + "cpu_cycles": 123, "cycles": 123, "dma_wait": 0, "flash_wait": 0, + "ga2d_cycles": 0, + "ga2d_read_bytes": 0, + "ga2d_write_bytes": 0, + "jobs": 0, "mgmt_wait": 10, "apb4_periph_wait": 9, "operation": "read", + "pixels": 0, "psram_wait": 0, "region": "sdram", "sdram_wait": 8, + "workload_bytes": 4096, "words": 1024, } ] @@ -39,7 +55,10 @@ def test_parse_log_requires_terminal_marker_and_complete_samples() -> None: def test_parse_log_rejects_missing_benchmark_marker() -> None: report = parse_log( "PERF region=flash op=read words=1024 checksum=0 cycles=10 mgmt_wait=1 apb4_periph_wait=1 " - "sdram_wait=0 psram_wait=0 flash_wait=1 dma_wait=0\n" + "sdram_wait=0 psram_wait=0 flash_wait=1 dma_wait=0 workload_bytes=4096 pixels=0 " + "jobs=0 cpu_cycles=10 cpu_hz=24000000 ga2d_features=0x3e3 " + "ga2d_limits=0x08202010 ga2d_formats=0x000f000f ga2d_cycles=0 " + "ga2d_read_bytes=0 ga2d_write_bytes=0\n" ) assert report["status"] == "failed" @@ -48,7 +67,10 @@ def test_parse_log_rejects_missing_benchmark_marker() -> None: def test_parse_log_rejects_report_with_performance_failure() -> None: report = parse_log( "PERF region=sdram op=read words=1024 checksum=0x1234 cycles=123 mgmt_wait=10 " - "apb4_periph_wait=9 sdram_wait=8 psram_wait=0 flash_wait=0 dma_wait=0\n" + "apb4_periph_wait=9 sdram_wait=8 psram_wait=0 flash_wait=0 dma_wait=0 " + "workload_bytes=4096 pixels=0 jobs=0 cpu_cycles=123 cpu_hz=24000000 " + "ga2d_features=0x3e3 ga2d_limits=0x08202010 ga2d_formats=0x000f000f " + "ga2d_cycles=0 ga2d_read_bytes=0 ga2d_write_bytes=0\n" "PERF_FAIL region=sdram op=read reason=data expected=1234 actual=0\n" "PERF_BENCHMARK_PASS\n" ) @@ -57,6 +79,33 @@ def test_parse_log_rejects_report_with_performance_failure() -> None: assert report["failure_marker"] is True +def test_parse_log_preserves_ga2d_workload_and_configuration_facts() -> None: + report = parse_log( + "PERF region=ga2d op=copy words=0 checksum=0x4567 cycles=99 mgmt_wait=3 " + "apb4_periph_wait=2 sdram_wait=7 psram_wait=0 flash_wait=0 dma_wait=0 " + "workload_bytes=192 pixels=32 jobs=1 cpu_cycles=99 cpu_hz=24000000 " + "ga2d_features=0x3e3 ga2d_limits=0x08202010 ga2d_formats=0x000f000f " + "ga2d_cycles=75 ga2d_read_bytes=96 ga2d_write_bytes=96\n" + "PERF_BENCHMARK_PASS\n" + ) + + sample = report["samples"][0] + assert sample["operation"] == "copy" + assert sample["workload_bytes"] == 192 + assert sample["pixels"] == 32 + assert sample["jobs"] == 1 + assert sample["cpu_cycles"] == 99 + assert sample["ga2d_cycles"] == 75 + assert sample["ga2d_read_bytes"] == 96 + assert sample["ga2d_write_bytes"] == 96 + assert sample["configuration"] == { + "cpu_hz": 24000000, + "ga2d_features": 0x3E3, + "ga2d_limits": 0x08202010, + "ga2d_formats": 0x000F000F, + } + + def test_parse_coremark_quick_report() -> None: report = parse_coremark_log( "COREMARK_RESULT mode=quick qualified=0 memory=sram iterations=4 cycles=2000 " diff --git a/tests/test_publication_retrieval.py b/tests/test_publication_retrieval.py index c71d9dec..5d55d838 100644 --- a/tests/test_publication_retrieval.py +++ b/tests/test_publication_retrieval.py @@ -129,7 +129,7 @@ def test_all_code_namespaces_and_boot_codes_are_source_checked(): assert next(g for g in groups if g["id"] == "rib-detail")["expected"]["RIB_RESP_BURSTERR"] == 6 assert next(g for g in groups if g["id"] == "sdio-dma-mask")["kind"] == "mask" assert len(next(g for g in groups if g["id"] == "jpeg-error")["rows"]) == 19 - assert [r["value"] for r in rr.boot_status(ROOT, SPEC["boot"])["rows"]] == list(range(11)) + assert [r["value"] for r in rr.boot_status(ROOT, SPEC["boot"])["rows"]] == list(range(18)) @pytest.mark.parametrize("mutation", ["missing", "value", "meaning", "mask"]) diff --git a/tests/test_publication_software.py b/tests/test_publication_software.py index a49feea2..a699c605 100644 --- a/tests/test_publication_software.py +++ b/tests/test_publication_software.py @@ -95,7 +95,7 @@ def test_application_results_keep_early_return_and_repeated_stage_values(): ("return", 1), ("test-fail", 1), ("test-fail", 2), ("test-fail", 3), ("test-pass", 0)] assert [r["id"] for r in smoke["stages"] if r["code"] == 12] == ["sram", "usb"] assert [r["id"] for r in smoke["stages"] if r["code"] == 13] == ["monitor-start", "monitor-check"] - assert [r["id"] for r in smoke["stages"] if r["code"] == 15] == ["external-irq", "ga2d-irq"] + assert [r["id"] for r in smoke["stages"] if r["code"] == 15] == ["external-irq", "ga2d-fill-copy-dma"] assert len(smoke["stages"]) == 19 diff --git a/tests/test_script_tools.py b/tests/test_script_tools.py index d33e1e77..ab7fe20a 100644 --- a/tests/test_script_tools.py +++ b/tests/test_script_tools.py @@ -329,6 +329,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non clint_filelist = tmp_path / "clint.fl" onchip_ram_filelist = tmp_path / "onchip_ram.fl" opipsram_filelist = tmp_path / "opipsram.fl" + ga2d_filelist = tmp_path / "ga2d.fl" apu_filelist = tmp_path / "apu.fl" apu_primitives_filelist = tmp_path / "apu_primitives.fl" apu_loader_filelist = tmp_path / "apu_loader.fl" @@ -359,6 +360,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non assert generate_formal_filelist( "opipsram", opipsram_filelist, memory_map, topology, user_extensions ) + assert generate_formal_filelist("ga2d", ga2d_filelist, memory_map, topology, user_extensions) assert generate_formal_filelist("apu", apu_filelist, memory_map, topology, user_extensions) assert generate_formal_filelist( "apu_primitives", apu_primitives_filelist, memory_map, topology, user_extensions @@ -384,6 +386,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non clint = parse_filelists([clint_filelist], require_files=False) onchip_ram = parse_filelists([onchip_ram_filelist], require_files=False) opipsram = parse_filelists([opipsram_filelist], require_files=False) + ga2d = parse_filelists([ga2d_filelist], require_files=False) apu = parse_filelists([apu_filelist], require_files=False) apu_primitives = parse_filelists([apu_primitives_filelist], require_files=False) apu_loader = parse_filelists([apu_loader_filelist], require_files=False) @@ -444,6 +447,15 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non assert ROOT / "rtl/tech/tc_clk.sv" in opipsram.files assert ROOT / "rtl/tech/tc_opipsram_delay.sv" in opipsram.files assert ROOT / "rtl/mini/formal/opipsram_formal.sv" in opipsram.files + assert ROOT / "rtl/ip/multimedia/ga2d_pkg.sv" in ga2d.files + assert ROOT / "rtl/ip/multimedia/ga2d_addr_gen.sv" in ga2d.files + assert ROOT / "rtl/ip/multimedia/ga2d_axi4_master.sv" in ga2d.files + assert ROOT / "rtl/ip/multimedia/ga2d_dma.sv" in ga2d.files + assert ROOT / "rtl/ip/multimedia/ga2d_pixel.sv" in ga2d.files + assert ROOT / "rtl/ip/multimedia/ga2d_core.sv" in ga2d.files + assert ROOT / "rtl/ip/multimedia/ga2d_reg.sv" in ga2d.files + assert ROOT / "rtl/ip/multimedia/apb4_ga2d.sv" in ga2d.files + assert ROOT / "rtl/mini/formal/ga2d_formal.sv" in ga2d.files assert ROOT / "rtl/ip/multimedia/apu_dma.sv" in apu.files assert ROOT / "rtl/mini/formal/apu_formal.sv" in apu.files assert ROOT / "rtl/ip/multimedia/apu_resampler.sv" in apu_primitives.files @@ -483,6 +495,53 @@ def test_opipsram_formal_keeps_full_depth_with_hosted_runner_budget() -> None: assert "$(FORMAL_OPIPSRAM_BMC_TIMEOUT)s $(FORMAL_SBY)" in formal_makefile +def test_ga2d_formal_target_uses_the_p4_production_hierarchy() -> None: + formal_makefile = (ROOT / "rtl/mini/mk/formal.mk").read_text(encoding="utf-8") + filelist_generator = ( + ROOT / "rtl/mini/formal/generate_formal_filelist.py" + ).read_text(encoding="utf-8") + formal = (ROOT / "rtl/mini/formal/ga2d_formal.sv").read_text(encoding="utf-8") + properties = (ROOT / "rtl/mini/formal/ga2d_formal_props.sv").read_text( + encoding="utf-8" + ) + + assert re.search(r"^FORMAL_GA2D_DEPTH\s+\?= 36$", formal_makefile, re.MULTILINE) + assert re.search(r"^FORMAL_GA2D_COVER_DEPTH\s+\?= 48$", formal_makefile, re.MULTILINE) + assert re.search(r"^FORMAL_GA2D_TIMEOUT\s+\?= 180$", formal_makefile, re.MULTILINE) + assert "formal-ga2d: $(FORMAL_DIR)/ga2d/.stamp | manifest" in formal_makefile + assert 'if target == "ga2d":' in filelist_generator + for source in ( + "ga2d_pkg.sv", + "ga2d_addr_gen.sv", + "ga2d_axi4_master.sv", + "ga2d_dma.sv", + "ga2d_pixel.sv", + "ga2d_core.sv", + "ga2d_reg.sv", + "apb4_ga2d.sv", + "ga2d_formal.sv", + ): + assert source in filelist_generator + assert "ga2d_formal_design u_design" in formal + assert "ga2d_core u_dut" in properties + assert "assert (awlen <= 8'd15);" in formal + assert "assert (arlen <= 8'd15);" in formal + assert "assert ((arlen == 8'd0) || (arsize == 3'd3));" in formal + assert "assert (output_fifo_count >= ({1'b0, awlen} + 9'd1));" in formal + assert "if (s_read_inflight_q && !bridge_clear_busy_i)" in formal + assert "assert (wstrb != 8'd0);" in formal + assert "cover (scenario == 4'd0 && done" in formal + assert "cover (scenario == 4'd3 && aborted && safe_idle);" in formal + assert "cover (scenario == 4'd2 && awvalid && awready && (awlen == 8'd14));" in formal + assert "cover (scenario == 4'd7 && awvalid && awready && (awlen == 8'd15));" in formal + assert "protocol_residual_rvalid" in formal + assert "assert (!rready);" in formal + assert "assert (!arvalid);" in formal + assert "cover ((scenario == 4'd8) && protocol_residual_rvalid" in formal + assert "output logic [3:0] scenario" in properties + assert "s_residual_r_valid_q" in properties + + def test_sysctrl_formal_properties_use_exported_user_core_shape() -> None: design = (ROOT / "rtl/mini/formal/sysctrl_formal.sv").read_text(encoding="utf-8") properties = (ROOT / "rtl/mini/formal/sysctrl_formal_props.sv").read_text(encoding="utf-8") @@ -1135,6 +1194,7 @@ def test_hazard3_debug_flow_is_locked_and_uses_remote_bitbang() -> None: def test_benchmark_profile_uses_functional_sram_and_reserved_data() -> None: makefile = (ROOT / "Makefile").read_text(encoding="utf-8") profile = (ROOT / "configs/benchmark/ihp130-hazard3.mk").read_text(encoding="utf-8") + benchmark_profile = (ROOT / "app/apps/benchmark/app.mk").read_text(encoding="utf-8") benchmark = (ROOT / "app/apps/benchmark/main.c").read_text(encoding="utf-8") assert re.search(r"^PDK_BEHAV\s+\?= NO$", makefile, re.MULTILINE) @@ -1143,7 +1203,26 @@ def test_benchmark_profile_uses_functional_sram_and_reserved_data() -> None: assert re.search(r"^HAVE_SRAM_MACRO\s*:= YES$", profile, re.MULTILINE) assert re.search(r"^SRAM_SIZE_KIB\s*:= 32$", profile, re.MULTILINE) assert re.search(r"^PDK_BEHAV\s*:= YES$", profile, re.MULTILINE) - assert "RS_BENCHMARK_SRAM_OFFSET UINT32_C(0x10000)" in benchmark + assert "SOC_SIM_TIME ?= 600" in benchmark_profile + assert "VERILATOR_SIM_ARGS ?= --fast-flash" in benchmark_profile + offset_match = re.search( + r"^#define\s+RS_BENCHMARK_SRAM_OFFSET\s+UINT32_C\((0x[0-9A-F]+)\)$", + benchmark, + re.MULTILINE, + ) + words_match = re.search( + r"^#define\s+RS_BENCHMARK_WORDS\s+UINT32_C\(([0-9]+)\)$", + benchmark, + re.MULTILINE, + ) + assert offset_match is not None + assert words_match is not None + + sram_size_bytes = 32 * 1024 + benchmark_bytes = int(words_match.group(1), 10) * 4 + benchmark_offset = int(offset_match.group(1), 16) + assert benchmark_offset + benchmark_bytes <= sram_size_bytes + assert sram_size_bytes - (benchmark_offset + benchmark_bytes) >= 7 * 1024 def test_open_pdk_profiles_enable_32kib_macro_sram_and_ics55_stays_absent() -> None: diff --git a/tests/test_soc_topology.py b/tests/test_soc_topology.py index 012633a3..e1f36e7d 100644 --- a/tests/test_soc_topology.py +++ b/tests/test_soc_topology.py @@ -263,7 +263,7 @@ def test_topology_preserves_default_irq_compatibility_mapping() -> None: ] -def test_ga2d_p3_topology_activates_the_shell_without_a_data_source() -> None: +def test_ga2d_p4_topology_preserves_the_private_master_policy_and_active_shell() -> None: document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) policies = document["data_master_policies"]