From 46a3a90be661cca6035ac724d1fc0b9e9a26f18a Mon Sep 17 00:00:00 2001 From: maksyuki Date: Mon, 24 Aug 2026 20:10:22 +0800 Subject: [PATCH] feat: improve the performance of the onchip sram ctrl --- Makefile | 12 +- README.md | 2 + app/apps/ci_smoke/main.c | 21 + configs/README.md | 5 + .../ihp130-hazard3-coremark-standard.mk | 1 + configs/benchmark/ihp130-hazard3-coremark.mk | 1 + configs/benchmark/ihp130-hazard3.mk | 1 + configs/ci/gf180.mk | 1 + configs/ci/ics55.mk | 1 + configs/ci/ihp130-debug.mk | 1 + configs/ci/ihp130-shell.mk | 1 + configs/ci/ihp130-xpi-flash-loader.mk | 1 + configs/ci/ihp130.mk | 1 + configs/ci/sky130.mk | 1 + configs/cluster/ics55.mk | 1 + crt/include/retrosoc/hal/onchip_sram.h | 32 + crt/include/retrosoc/hal/onchip_sram_regs.h | 41 ++ crt/src/hal/onchip_sram.c | 66 ++ docs/README.md | 3 + docs/axi4-interconnect.md | 29 +- docs/ip/onchip-sram.md | 161 +++++ rtl/README.md | 6 + rtl/filelist/pdk_ihp130.fl | 2 +- rtl/mini/Makefile | 3 +- rtl/mini/address_map/generate_memory_map.py | 37 +- rtl/mini/address_map/memory_map.json | 16 + rtl/mini/filelist/top.fl | 2 + rtl/mini/formal/generate_formal_filelist.py | 15 +- rtl/mini/formal/onchip_ram_formal.sv | 172 +++++ rtl/mini/formal/onchip_ram_formal_props.sv | 113 +++ rtl/mini/integration/soc_topology.json | 8 + rtl/mini/mk/formal.mk | 13 +- rtl/mini/mk/software.mk | 1 + rtl/mini/pin_map/generate_pin_map.py | 89 ++- rtl/mini/top/apb4_periph.sv | 1 + rtl/mini/top/axi4_bus.sv | 33 +- rtl/mini/top/onchip_ram.sv | 641 ++++++++++++++++-- rtl/mini/top/onchip_ram_define.svh | 39 ++ rtl/mini/top/onchip_ram_reg.sv | 108 +++ rtl/mini/top/retrosoc.sv | 29 +- rtl/mini/top/retrosoc_asic.sv | 14 - rtl/mini/top/retrosoc_core.sv | 100 +++ rtl/rtl_style_audit.json | 10 +- rtl/tech/tc_sram.sv | 10 +- tests/rtl/ics55_onchip_sram_stub.sv | 26 + tests/rtl/onchip_ram_absent_tb.sv | 146 ++++ tests/rtl/onchip_ram_perf_tb.sv | 192 ++++++ tests/rtl/onchip_ram_tb.sv | 387 +++++++++++ tests/rtl/tc_sram_1024x32_tb.sv | 63 ++ tests/test_memory_map.py | 21 +- tests/test_mgmt_core_spec.py | 14 +- tests/test_onchip_ram.py | 303 +++++++++ tests/test_onchip_sram_register_parity.py | 35 + 53 files changed, 2881 insertions(+), 151 deletions(-) create mode 100644 crt/include/retrosoc/hal/onchip_sram.h create mode 100644 crt/include/retrosoc/hal/onchip_sram_regs.h create mode 100644 crt/src/hal/onchip_sram.c create mode 100644 docs/ip/onchip-sram.md create mode 100644 rtl/mini/formal/onchip_ram_formal.sv create mode 100644 rtl/mini/formal/onchip_ram_formal_props.sv create mode 100644 rtl/mini/top/onchip_ram_define.svh create mode 100644 rtl/mini/top/onchip_ram_reg.sv create mode 100644 rtl/mini/top/retrosoc_core.sv create mode 100644 tests/rtl/ics55_onchip_sram_stub.sv create mode 100644 tests/rtl/onchip_ram_absent_tb.sv create mode 100644 tests/rtl/onchip_ram_perf_tb.sv create mode 100644 tests/rtl/onchip_ram_tb.sv create mode 100644 tests/rtl/tc_sram_1024x32_tb.sv create mode 100644 tests/test_onchip_ram.py create mode 100644 tests/test_onchip_sram_register_parity.py diff --git a/Makefile b/Makefile index 2d99b3ec..38673a74 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ PDK ?= IHP130 HAVE_PLL ?= NO HAVE_SRAM_IF ?= NO HAVE_SRAM_MACRO ?= NO +SRAM_SIZE_KIB ?= 128 PDK_BEHAV ?= NO HAVE_SVA ?= NO BUILD_RELEASE ?= NO @@ -85,7 +86,7 @@ VCS_SHELL_PYTHON := $(if $(filter VCS,$(SIMU)),$(if $(filter $(VCS_SHELL_GOALS JOBS ?= $(shell count=$$(nproc 2>/dev/null || printf '1'); \ if [ "$$count" -gt "$(MAX_JOBS)" ]; then printf '%s' '$(MAX_JOBS)'; \ else printf '%s' "$$count"; fi) -CONFIG_KEY_VARS := SOC PDK HAVE_PLL HAVE_SRAM_IF HAVE_SRAM_MACRO PDK_BEHAV HAVE_SVA \ +CONFIG_KEY_VARS := SOC PDK HAVE_PLL HAVE_SRAM_IF HAVE_SRAM_MACRO SRAM_SIZE_KIB PDK_BEHAV HAVE_SVA \ BUILD_RELEASE JTAG_IDCODE EXT_CLK_HZ AUD_CLK_HZ CLINT_TIMEBASE_HZ ISA HAVE_CSR APP LINK_TYPE \ COREMARK_MODE RTL_TOP FIRMWARE_NAME VARIANT_ID := $(strip $(shell $(VCS_SHELL_PYTHON) $(ROOT_PATH)/scripts/config_key.py \ @@ -131,6 +132,7 @@ VALID_ISA := RV32E RV32I RV32IM VALID_APP := benchmark bringup ci_smoke coremark debug shell xpi_flash_loader VALID_LINK_TYPE := xip jtag_sram ld2_all_sram ld2_sram ld2_psram ld2_sdram VALID_COREMARK_MODE := quick standard +VALID_SRAM_SIZE_KIB := 4 16 32 64 128 define validate_value $(if $(filter $($(1)),$(2)),,$(error Invalid $(1)='$($(1))'; expected one of: $(2))) @@ -145,6 +147,7 @@ $(call validate_value,PDK,$(VALID_PDK)) $(call validate_value,HAVE_PLL,$(VALID_BOOL)) $(call validate_value,HAVE_SRAM_IF,$(VALID_BOOL)) $(call validate_value,HAVE_SRAM_MACRO,$(VALID_BOOL)) +$(call validate_value,SRAM_SIZE_KIB,$(VALID_SRAM_SIZE_KIB)) $(call validate_value,PDK_BEHAV,$(VALID_BOOL)) $(call validate_value,HAVE_SVA,$(VALID_BOOL)) $(call validate_value,BUILD_RELEASE,$(VALID_BOOL)) @@ -276,7 +279,7 @@ include physical/librelane/Makefile benchmark-report coremark-report \ pin-map check-pin-map soc-topology check-soc-topology user-extensions check-user-extensions \ check-clock-reset-domains tech-cell-test rtl-lint check-rtl-lint \ - formal formal-bus formal-rib-adapter formal-rib2apb formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-dvp formal-i2s formal-opipsram formal-dma formal-sdio formal-clean formal-doctor \ + formal formal-bus formal-rib-adapter formal-rib2apb formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-dvp formal-i2s formal-onchip-ram formal-opipsram formal-dma formal-sdio formal-clean formal-doctor \ rtl-style-check-all rtl-readiness-check rtl-readiness-check-all .NOTPARALLEL: setup @@ -310,7 +313,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-opipsram | formal-dma | 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-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' \ @@ -350,7 +353,8 @@ config: STA '$(STA)' FORMAL '$(FORMAL)' \ VCS_USE_LSF '$(VCS_USE_LSF)' PDK '$(PDK)' \ HAVE_PLL '$(HAVE_PLL)' HAVE_SRAM_IF '$(HAVE_SRAM_IF)' \ - HAVE_SRAM_MACRO '$(HAVE_SRAM_MACRO)' PDK_BEHAV '$(PDK_BEHAV)' HAVE_SVA '$(HAVE_SVA)' \ + HAVE_SRAM_MACRO '$(HAVE_SRAM_MACRO)' SRAM_SIZE_KIB '$(SRAM_SIZE_KIB)' \ + PDK_BEHAV '$(PDK_BEHAV)' HAVE_SVA '$(HAVE_SVA)' \ BUILD_RELEASE '$(BUILD_RELEASE)' \ JTAG_IDCODE '$(JTAG_IDCODE)' EXT_CLK_HZ '$(EXT_CLK_HZ)' AUD_CLK_HZ '$(AUD_CLK_HZ)' \ CLINT_TIMEBASE_HZ '$(CLINT_TIMEBASE_HZ)' \ diff --git a/README.md b/README.md index c507242c..d8d5a94a 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ The committed profiles are the supported starting points. They select an ISA, PDK, application, linker layout, and optional features as one reproducible configuration. The management core is fixed to Hazard3. The user-extension fabric exposes C0-C3 user-core slots and software selects one active user core. +`SRAM_SIZE_KIB` selects a generated 4/16/32/64/128 KiB native-AXI4 on-chip +SRAM window; committed profiles explicitly retain the 128 KiB default. | Profile | ISA | Application | Coverage | | --- | --- | --- | --- | diff --git a/app/apps/ci_smoke/main.c b/app/apps/ci_smoke/main.c index a4983449..d050e1a0 100644 --- a/app/apps/ci_smoke/main.c +++ b/app/apps/ci_smoke/main.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,22 @@ static bool rs_ci_smoke_archinfo_v2(void) { (rs_archinfo_read_device_id(device_id) == RS_ENOTSUP) && (rs_rtc_probe() == RS_OK); } +static bool rs_ci_smoke_onchip_sram(void) { + rs_onchip_sram_info_t info; + rs_onchip_sram_perf_t performance; + + if ((rs_onchip_sram_probe(&info) != RS_OK) || + (rs_onchip_sram_read_performance(&performance) != RS_OK) || + (info.present != (RS_SOC_HAS_SRAM != 0U))) { + return false; + } + if (info.present) { + return (info.memory_bytes == RS_SOC_SRAM_SIZE) && + (info.bank_count == (RS_SOC_SRAM_SIZE / RS_ONCHIP_SRAM_BANK_BYTES_VALUE)); + } + return (info.memory_bytes == 0U) && (info.bank_count == 0U); +} + static bool rs_ci_smoke_user_ip(void) { rs_status_t status; uint32_t identifier = 0U; @@ -289,6 +306,10 @@ int main(void) { rs_test_finish(RS_TEST_FAILED, 1U); } printf("ci_smoke: archinfo passed\n"); + if (!rs_ci_smoke_onchip_sram()) { + rs_test_finish(RS_TEST_FAILED, 12U); + } + printf("ci_smoke: on-chip SRAM passed\n"); if (!rs_ci_smoke_user_ip()) { rs_test_finish(RS_TEST_FAILED, 11U); } diff --git a/configs/README.md b/configs/README.md index d1a676d9..a2ce7f05 100644 --- a/configs/README.md +++ b/configs/README.md @@ -17,6 +17,11 @@ by GDB/OpenOCD; it is a programming utility, not a normal boot application. Start builds from a committed profile rather than setting an unreviewed mix of variables on the command line. +`SRAM_SIZE_KIB` selects 4, 16, 32, 64, or 128 KiB of on-chip SRAM and is part +of the build variant key. Every committed profile sets 128 explicitly; +profiles with `HAVE_SRAM_IF=NO` keep a reproducible capacity selection while +the hardware capability reports the memory absent. + `PDK_BEHAV=YES` selects technology-wrapper functional models for behavioral simulation. It is a simulation-only setting, participates in the build variant key, and is invalid with Yosys synthesis. Profiles that execute from an SRAM diff --git a/configs/benchmark/ihp130-hazard3-coremark-standard.mk b/configs/benchmark/ihp130-hazard3-coremark-standard.mk index 816c0777..1321de91 100644 --- a/configs/benchmark/ihp130-hazard3-coremark-standard.mk +++ b/configs/benchmark/ihp130-hazard3-coremark-standard.mk @@ -3,6 +3,7 @@ PDK := IHP130 HAVE_PLL := NO HAVE_SRAM_IF := YES HAVE_SRAM_MACRO := YES +SRAM_SIZE_KIB := 128 PDK_BEHAV := YES HAVE_SVA := NO EXT_CLK_HZ := 72000000 diff --git a/configs/benchmark/ihp130-hazard3-coremark.mk b/configs/benchmark/ihp130-hazard3-coremark.mk index 6d966777..e8a4f151 100644 --- a/configs/benchmark/ihp130-hazard3-coremark.mk +++ b/configs/benchmark/ihp130-hazard3-coremark.mk @@ -3,6 +3,7 @@ PDK := IHP130 HAVE_PLL := NO HAVE_SRAM_IF := YES HAVE_SRAM_MACRO := YES +SRAM_SIZE_KIB := 128 PDK_BEHAV := YES HAVE_SVA := NO EXT_CLK_HZ := 72000000 diff --git a/configs/benchmark/ihp130-hazard3.mk b/configs/benchmark/ihp130-hazard3.mk index 8316eed2..a1ef847c 100644 --- a/configs/benchmark/ihp130-hazard3.mk +++ b/configs/benchmark/ihp130-hazard3.mk @@ -3,6 +3,7 @@ PDK := IHP130 HAVE_PLL := NO HAVE_SRAM_IF := YES HAVE_SRAM_MACRO := YES +SRAM_SIZE_KIB := 128 PDK_BEHAV := YES HAVE_SVA := NO EXT_CLK_HZ := 72000000 diff --git a/configs/ci/gf180.mk b/configs/ci/gf180.mk index 53d958c7..8d511997 100644 --- a/configs/ci/gf180.mk +++ b/configs/ci/gf180.mk @@ -3,6 +3,7 @@ PDK := GF180 HAVE_PLL := NO HAVE_SRAM_IF := NO HAVE_SRAM_MACRO := NO +SRAM_SIZE_KIB := 128 HAVE_SVA := NO EXT_CLK_HZ := 72000000 AUD_CLK_HZ := 18432000 diff --git a/configs/ci/ics55.mk b/configs/ci/ics55.mk index fa15dd9e..13fcc099 100644 --- a/configs/ci/ics55.mk +++ b/configs/ci/ics55.mk @@ -3,6 +3,7 @@ PDK := ICS55 HAVE_PLL := NO HAVE_SRAM_IF := NO HAVE_SRAM_MACRO := NO +SRAM_SIZE_KIB := 128 HAVE_SVA := NO EXT_CLK_HZ := 72000000 AUD_CLK_HZ := 18432000 diff --git a/configs/ci/ihp130-debug.mk b/configs/ci/ihp130-debug.mk index b7460a2b..aa402069 100644 --- a/configs/ci/ihp130-debug.mk +++ b/configs/ci/ihp130-debug.mk @@ -3,6 +3,7 @@ PDK := IHP130 HAVE_PLL := NO HAVE_SRAM_IF := YES HAVE_SRAM_MACRO := YES +SRAM_SIZE_KIB := 128 PDK_BEHAV := YES HAVE_SVA := NO EXT_CLK_HZ := 72000000 diff --git a/configs/ci/ihp130-shell.mk b/configs/ci/ihp130-shell.mk index 27359467..9ad0f239 100644 --- a/configs/ci/ihp130-shell.mk +++ b/configs/ci/ihp130-shell.mk @@ -3,6 +3,7 @@ PDK := IHP130 HAVE_PLL := NO HAVE_SRAM_IF := NO HAVE_SRAM_MACRO := NO +SRAM_SIZE_KIB := 128 HAVE_SVA := NO EXT_CLK_HZ := 72000000 AUD_CLK_HZ := 18432000 diff --git a/configs/ci/ihp130-xpi-flash-loader.mk b/configs/ci/ihp130-xpi-flash-loader.mk index e49ceefb..b4363e02 100644 --- a/configs/ci/ihp130-xpi-flash-loader.mk +++ b/configs/ci/ihp130-xpi-flash-loader.mk @@ -3,6 +3,7 @@ PDK := IHP130 HAVE_PLL := NO HAVE_SRAM_IF := YES HAVE_SRAM_MACRO := YES +SRAM_SIZE_KIB := 128 PDK_BEHAV := YES HAVE_SVA := NO EXT_CLK_HZ := 72000000 diff --git a/configs/ci/ihp130.mk b/configs/ci/ihp130.mk index 496ec5b7..03a678cd 100644 --- a/configs/ci/ihp130.mk +++ b/configs/ci/ihp130.mk @@ -3,6 +3,7 @@ PDK := IHP130 HAVE_PLL := NO HAVE_SRAM_IF := NO HAVE_SRAM_MACRO := NO +SRAM_SIZE_KIB := 128 HAVE_SVA := NO EXT_CLK_HZ := 72000000 AUD_CLK_HZ := 18432000 diff --git a/configs/ci/sky130.mk b/configs/ci/sky130.mk index 4778569e..ff3dc7af 100644 --- a/configs/ci/sky130.mk +++ b/configs/ci/sky130.mk @@ -3,6 +3,7 @@ PDK := SKY130 HAVE_PLL := NO HAVE_SRAM_IF := NO HAVE_SRAM_MACRO := NO +SRAM_SIZE_KIB := 128 HAVE_SVA := NO EXT_CLK_HZ := 72000000 AUD_CLK_HZ := 18432000 diff --git a/configs/cluster/ics55.mk b/configs/cluster/ics55.mk index fa15dd9e..13fcc099 100644 --- a/configs/cluster/ics55.mk +++ b/configs/cluster/ics55.mk @@ -3,6 +3,7 @@ PDK := ICS55 HAVE_PLL := NO HAVE_SRAM_IF := NO HAVE_SRAM_MACRO := NO +SRAM_SIZE_KIB := 128 HAVE_SVA := NO EXT_CLK_HZ := 72000000 AUD_CLK_HZ := 18432000 diff --git a/crt/include/retrosoc/hal/onchip_sram.h b/crt/include/retrosoc/hal/onchip_sram.h new file mode 100644 index 00000000..1d5ffcf9 --- /dev/null +++ b/crt/include/retrosoc/hal/onchip_sram.h @@ -0,0 +1,32 @@ +#ifndef RETROSOC_HAL_ONCHIP_SRAM_H +#define RETROSOC_HAL_ONCHIP_SRAM_H + +#include +#include + +#include +#include + +typedef struct { + uint32_t capability; + uint32_t memory_bytes; + uint32_t bank_count; + uint32_t bank_bytes; + uint8_t max_burst_beats; + uint8_t data_bytes; + bool present; +} rs_onchip_sram_info_t; + +typedef struct { + uint32_t read_requests; + uint32_t write_requests; + uint32_t read_beats; + uint32_t write_beats; + uint32_t stall_cycles; + uint32_t error_responses; +} rs_onchip_sram_perf_t; + +rs_status_t rs_onchip_sram_probe(rs_onchip_sram_info_t *info); +rs_status_t rs_onchip_sram_read_performance(rs_onchip_sram_perf_t *performance); + +#endif diff --git a/crt/include/retrosoc/hal/onchip_sram_regs.h b/crt/include/retrosoc/hal/onchip_sram_regs.h new file mode 100644 index 00000000..e5fc2b6c --- /dev/null +++ b/crt/include/retrosoc/hal/onchip_sram_regs.h @@ -0,0 +1,41 @@ +#ifndef RETROSOC_HAL_ONCHIP_SRAM_REGS_H +#define RETROSOC_HAL_ONCHIP_SRAM_REGS_H + +#include + +#include + +/* Keep this handwritten ABI synchronized with onchip_ram_define.svh. */ +#define RS_ONCHIP_SRAM_REG_IP_ID UINT32_C(0x000) +#define RS_ONCHIP_SRAM_REG_IP_VERSION UINT32_C(0x004) +#define RS_ONCHIP_SRAM_REG_CAPABILITY UINT32_C(0x008) +#define RS_ONCHIP_SRAM_REG_MEMORY_BYTES UINT32_C(0x00C) +#define RS_ONCHIP_SRAM_REG_BANK_COUNT UINT32_C(0x010) +#define RS_ONCHIP_SRAM_REG_BANK_BYTES UINT32_C(0x014) +#define RS_ONCHIP_SRAM_REG_PERF_READ_REQUESTS UINT32_C(0x020) +#define RS_ONCHIP_SRAM_REG_PERF_WRITE_REQUESTS UINT32_C(0x024) +#define RS_ONCHIP_SRAM_REG_PERF_READ_BEATS UINT32_C(0x028) +#define RS_ONCHIP_SRAM_REG_PERF_WRITE_BEATS UINT32_C(0x02C) +#define RS_ONCHIP_SRAM_REG_PERF_STALL_CYCLES UINT32_C(0x030) +#define RS_ONCHIP_SRAM_REG_PERF_ERROR_RESPONSES UINT32_C(0x034) + +#define RS_ONCHIP_SRAM_IP_ID_VALUE UINT32_C(0x5352414D) +#define RS_ONCHIP_SRAM_IP_VERSION_VALUE UINT32_C(0x00010000) +#define RS_ONCHIP_SRAM_BANK_BYTES_VALUE UINT32_C(4096) + +#define RS_ONCHIP_SRAM_CAP_PRESENT UINT32_C(0x00000001) +#define RS_ONCHIP_SRAM_CAP_NATIVE_AXI4 UINT32_C(0x00000002) +#define RS_ONCHIP_SRAM_CAP_BYTE_WRITE UINT32_C(0x00000004) +#define RS_ONCHIP_SRAM_CAP_FIXED UINT32_C(0x00000008) +#define RS_ONCHIP_SRAM_CAP_INCR UINT32_C(0x00000010) +#define RS_ONCHIP_SRAM_CAP_WRAP UINT32_C(0x00000020) +#define RS_ONCHIP_SRAM_CAP_PERFORMANCE UINT32_C(0x00000040) +#define RS_ONCHIP_SRAM_CAP_MAX_BEATS_SHIFT UINT32_C(8) +#define RS_ONCHIP_SRAM_CAP_MAX_BEATS_MASK UINT32_C(0x0000FF00) +#define RS_ONCHIP_SRAM_CAP_DATA_BYTES_SHIFT UINT32_C(16) +#define RS_ONCHIP_SRAM_CAP_DATA_BYTES_MASK UINT32_C(0x00FF0000) + +#define RS_ONCHIP_SRAM_REG(offset) \ + (*(volatile uint32_t *)(uintptr_t)(RS_SOC_APB4_SRAM_BASE + (offset))) + +#endif diff --git a/crt/src/hal/onchip_sram.c b/crt/src/hal/onchip_sram.c new file mode 100644 index 00000000..1914aea7 --- /dev/null +++ b/crt/src/hal/onchip_sram.c @@ -0,0 +1,66 @@ +#include + +#include + +rs_status_t rs_onchip_sram_probe(rs_onchip_sram_info_t *info) { + uint32_t capability; + uint32_t memory_bytes; + uint32_t bank_count; + uint32_t bank_bytes; + + if (info == NULL) { + return RS_EINVAL; + } + if ((RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_IP_ID) != RS_ONCHIP_SRAM_IP_ID_VALUE) || + (RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_IP_VERSION) != RS_ONCHIP_SRAM_IP_VERSION_VALUE)) { + return RS_EIO; + } + + capability = RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_CAPABILITY); + memory_bytes = RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_MEMORY_BYTES); + bank_count = RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_BANK_COUNT); + bank_bytes = RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_BANK_BYTES); + info->capability = capability; + info->memory_bytes = memory_bytes; + info->bank_count = bank_count; + info->bank_bytes = bank_bytes; + info->max_burst_beats = (uint8_t)((capability & RS_ONCHIP_SRAM_CAP_MAX_BEATS_MASK) >> + RS_ONCHIP_SRAM_CAP_MAX_BEATS_SHIFT); + info->data_bytes = (uint8_t)((capability & RS_ONCHIP_SRAM_CAP_DATA_BYTES_MASK) >> + RS_ONCHIP_SRAM_CAP_DATA_BYTES_SHIFT); + info->present = (capability & RS_ONCHIP_SRAM_CAP_PRESENT) != 0U; + + if ((bank_bytes != RS_ONCHIP_SRAM_BANK_BYTES_VALUE) || (info->max_burst_beats != 16U) || + (info->data_bytes != 4U) || + ((capability & (RS_ONCHIP_SRAM_CAP_NATIVE_AXI4 | RS_ONCHIP_SRAM_CAP_BYTE_WRITE | + RS_ONCHIP_SRAM_CAP_FIXED | RS_ONCHIP_SRAM_CAP_INCR | + RS_ONCHIP_SRAM_CAP_WRAP | RS_ONCHIP_SRAM_CAP_PERFORMANCE)) != + (RS_ONCHIP_SRAM_CAP_NATIVE_AXI4 | RS_ONCHIP_SRAM_CAP_BYTE_WRITE | + RS_ONCHIP_SRAM_CAP_FIXED | RS_ONCHIP_SRAM_CAP_INCR | RS_ONCHIP_SRAM_CAP_WRAP | + RS_ONCHIP_SRAM_CAP_PERFORMANCE))) { + return RS_EIO; + } + if (info->present) { + if ((memory_bytes != RS_SOC_SRAM_SIZE) || + (bank_count != (memory_bytes / RS_ONCHIP_SRAM_BANK_BYTES_VALUE)) || + (bank_count == 0U)) { + return RS_EIO; + } + } else if ((memory_bytes != 0U) || (bank_count != 0U)) { + return RS_EIO; + } + return RS_OK; +} + +rs_status_t rs_onchip_sram_read_performance(rs_onchip_sram_perf_t *performance) { + if (performance == NULL) { + return RS_EINVAL; + } + performance->read_requests = RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_PERF_READ_REQUESTS); + performance->write_requests = RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_PERF_WRITE_REQUESTS); + performance->read_beats = RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_PERF_READ_BEATS); + performance->write_beats = RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_PERF_WRITE_BEATS); + performance->stall_cycles = RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_PERF_STALL_CYCLES); + performance->error_responses = RS_ONCHIP_SRAM_REG(RS_ONCHIP_SRAM_REG_PERF_ERROR_RESPONSES); + return RS_OK; +} diff --git a/docs/README.md b/docs/README.md index c6806386..6ebb3e2f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,6 +46,9 @@ the root README and subsystem guides. counting modes, interrupt, debug-freeze, HAL, and verification contracts. - [ip/sysctrl.md](ip/sysctrl.md) defines the SystemCtrl register ABI, user-core lifecycle, PLL/fault/performance/RTC/test contracts, HAL, and verification. +- [ip/onchip-sram.md](ip/onchip-sram.md) defines the configurable native-AXI4 + SRAM architecture, APB capability ABI, technology mapping, verification, and + reliability roadmap. - [ip/user-ip.md](ip/user-ip.md) defines the selectable 4 KiB user-IP window, generic HAL, extension-owned register ABI, and verification contract. - [ip/clint.md](ip/clint.md) defines the standard CLINT register map, fixed diff --git a/docs/axi4-interconnect.md b/docs/axi4-interconnect.md index a0f97820..f99367b7 100644 --- a/docs/axi4-interconnect.md +++ b/docs/axi4-interconnect.md @@ -34,8 +34,11 @@ Fixed-address and APB4/MMIO endpoints are always single-beat. See the The fixed targets are the APB4 configuration plane (`apb4_periph`), APB (`apb4_system`), SRAM, SDRAM, 4-bit PSRAM, OPI PSRAM, XPI/Flash, SPI-SD, `DECERR`, and `SLVERR`. APB4 and APB accept only single-beat transactions. -SRAM uses a direct AXI4 target with pipelined synchronous reads and response -buffering. External-memory data windows accept up to sixteen beats; their +SRAM uses a direct AXI4 target with pipelined synchronous reads, a one-cycle +first response, backpressure, and one beat per cycle sustained bursts. Its +effective window is generated from `SRAM_SIZE_KIB`; see the +[on-chip SRAM contract](ip/onchip-sram.md). External-memory data windows accept +up to sixteen beats; their controller front ends validate and serialize or coalesce the physical transactions while preserving the documented AXI response contract. Register configuration remains on APB4. @@ -63,12 +66,16 @@ access-control rejection to `PROTERR`. ## Verification and Performance Gates -`tests/test_axi4.py` covers a four-beat write/read, response backpressure, and -rejection of a seventeen-beat request without touching the APB4 target. The -bridge unit test also covers Common address generation for FIXED and WRAP -sequencing. The IHP130 `ci_smoke` regression is the end-to-end boot and -configuration check. -CoreMark uses SRAM and must not regress more than five percent from the recorded -7,620,324-cycle baseline. A controller may be promoted to a native burst target -only after aligned sixteen-word tests show at least twenty percent improvement -and cover partial writes, backpressure, device timing, and error termination. +`tests/test_axi4.py` covers fabric classification. `tests/test_onchip_ram.py` +covers all five capacities, sixteen-beat reads/writes, response backpressure, +partial writes, technology mapping, and error termination. The bridge unit +test also covers Common address generation for FIXED and WRAP sequencing. The +IHP130 `ci_smoke` regression is the end-to-end boot and configuration check. +CoreMark uses SRAM and must not regress more than five percent from the native +4,662,868-cycle baseline; the retired compatibility path required 7,620,324 +cycles. A native SRAM target must return an aligned sixteen-word burst in +sixteen response cycles, report first-response and total latency against the +compatibility bridge, and cover partial writes, backpressure, device timing, +and error termination. The measured compatibility bridge needs 18 response +cycles; the native target removes both overhead cycles and reaches the 32-bit +AXI payload limit. diff --git a/docs/ip/onchip-sram.md b/docs/ip/onchip-sram.md new file mode 100644 index 00000000..d262a80a --- /dev/null +++ b/docs/ip/onchip-sram.md @@ -0,0 +1,161 @@ +# Configurable Native-AXI4 On-chip SRAM + +## Scope and maturity + +The Mini SoC on-chip SRAM is a synthesis-time configurable, 32-bit native-AXI4 +target backed by fixed `1024 x 32` single-port technology wrappers. Supported +capacities are 4, 16, 32, 64, and 128 KiB. The data aperture starts at +`0x3000_0000`; its generated end address and linker length match the selected +physical capacity. A management-only APB4 window at `0x1001_7000` reports the +configuration and saturating performance counters. + +This release is a protocol and performance MVP. It is not a safety-certified, +silicon-proven, or tapeout-ready memory subsystem. ECC, autonomous scrubbing, +MBIST, redundancy repair, power gating, retention, and independently licensed +AXI verification IP signoff remain outside the delivered scope. + +## Commercial reference review + +The reference review was refreshed on 2026-08-24 from vendor-owned material. +These products guide architecture and delivery expectations; they are not RTL +dependencies and do not transfer a vendor's qualification to retroSoC. + +| Reference | Problem and architecture | Status and decision | +| --- | --- | --- | +| [Raspberry Pi RP2350](https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf) | Main SRAM is split into two four-way word-striped groups. Two independent 4 KiB banks provide low-contention storage for stacks or hot data. | The official data sheet remains maintained. Reuse explicit bank ownership. Do not add low-bit striping while the Mini interconnect grants one transaction per target; it would increase switching without increasing target throughput. | +| [STM32H7 embedded SRAM and RAMECC](https://www.st.com/resource/en/reference_manual/rm0455-stm32h7a37b3-and-stm32h7b0-value-line-_advanced-armbased-32bit-mcus-stmicroelectronics.pdf) | AXI SRAM, AHB SRAM, TCM, backup SRAM, and power domains separate bandwidth, deterministic latency, and retention. SECDED reports failing address/data, and partial writes require read-modify-write. | STM32H7 products remain active. Reuse capability discovery, error localization, and a separate future ECC layer. Avoid claiming unchanged latency before ECC/RMW timing is verified. | +| [NXP i.MX RT FlexRAM](https://www.nxp.com/docs/en/application-note/AN12077.pdf) and [RT1170 ECC guidance](https://www.nxp.com/docs/en/application-note/AN13204.pdf) | Fixed banks can be assigned to OCRAM, ITCM, or DTCM. This depends on boot configuration, legal TCM sizes, linker agreement, and complete ECC initialization. | NXP continues to update RT1170 material. Reuse bank-based product configuration and generated linker consistency. Avoid runtime repartitioning and fuse/boot-order coupling for Mini. | +| [Infineon AURIX TC3xx MTU/SSH](https://www.infineon.com/assets/row/public/documents/10/44/infineon-aurix-tc3xx-part1-usermanual-en.pdf) | SRAM support hardware surrounds memories with ECC/EDC, address monitoring, error tracking, initialization, and MBIST. A central MTU exposes a uniform control plane. | AURIX documentation and products remain active. Reuse separation between functional access and reliability support. Avoid destructive runtime MBIST without a quiesce contract. | +| [CAST SRAM-CTRL and ECC-SRAM](https://www.cast-inc.com/peripherals/memory-controllers/sram-ctrl) | Commercial IP emphasizes native AXI/AHB/APB, byte accesses, low latency, lint/scan readiness, SECDED, testbenches, scripts, and integration documentation. | The products remain offered. Use the features and deliverables as an IP-readiness checklist, not as evidence for this implementation. | +| [Synopsys STAR Memory System](https://www.synopsys.com/articles/embedded-memory-test.html) | Hierarchical test, diagnosis, redundancy repair, tester patterns, and physical failed-bit mapping address manufacturing yield and field diagnosis. | The product family remains published. Keep DFT/repair as a physical-delivery phase instead of embedding an unqualified March engine in the AXI datapath. | + +The selected architecture is static capacity configuration plus a native AXI +datapath. Dynamic bank reassignment, caches, multiple outstanding IDs, and +multi-master bank scheduling require changes above this target and are not +cost-effective in the current one-owner-per-target fabric. + +## Configuration and address contract + +`SRAM_SIZE_KIB` is part of the reproducible configuration digest and accepts +only `4`, `16`, `32`, `64`, or `128`. Every committed profile sets `128` +explicitly to preserve the prior address map. The address-map generator uses +the selected value for RTL decode macros, SDK base/size/end constants, and the +linker `LENGTH(SRAM)`. + +`HAVE_SRAM_IF=YES` marks the memory present and elaborates the bank array. When +it is `NO`, the AXI target returns `DECERR`; the APB window remains readable +with `PRESENT=0`, zero memory bytes, and zero banks. + +| `SRAM_SIZE_KIB` | 4 KiB data banks | Address range | +| ---: | ---: | --- | +| 4 | 1 | `0x3000_0000..0x3000_0fff` | +| 16 | 4 | `0x3000_0000..0x3000_3fff` | +| 32 | 8 | `0x3000_0000..0x3000_7fff` | +| 64 | 16 | `0x3000_0000..0x3000_ffff` | +| 128 | 32 | `0x3000_0000..0x3001_ffff` | + +## AXI4 datapath + +The target uses the project-wide 32-bit address/data and 1-bit ID/USER +geometry. It accepts one active read or write transaction, gives AR priority +when AR and AW are simultaneously valid, and preserves the ID to the terminal +response. + +Supported requests are naturally aligned 1-, 2-, or 4-byte transfers using +`FIXED`, `INCR`, or legal 2/4/8/16-beat `WRAP` bursts. Bursts are limited to 16 +beats and one 4 KiB page. Because a bank is exactly one 4 KiB page, a legal +transaction selects one bank for its lifetime. + +The macro is issued directly on AR acceptance. The first response is available +one cycle later. With `RREADY` asserted, following beats issue on preceding +response cycles, giving one word per cycle without bubbles. Backpressure stops +new reads and holds data, ID, response, and `RLAST` stable. + +After AW acceptance the target consumes one W beat per cycle. Strobes outside +the lanes selected by `AWSIZE` and address are illegal. A zero strobe is a +no-op and does not enable a bank. `WLAST` must agree with `AWLEN`; a mismatching +beat is not written and terminates with `SLVERR`. Earlier accepted beats are +not rolled back. + +Malformed protocol requests return `SLVERR`. An absent memory or address +outside the parameterized capacity returns `DECERR`. Protocol-invalid requests +are capped to one error response beat at the IP boundary. The SoC interconnect +normally rejects them before they reach SRAM. + +## APB4 management ABI + +All registers are read-only. Writes, unaligned accesses, and unknown offsets +complete with `PSLVERR`. Counters are 32-bit saturating values controlled by +the existing SYSCTRL `PERF_CTRL` enable and clear signals; no second control +ABI or interrupt is introduced. + +| Offset | Name | Description | +| ---: | --- | --- | +| `0x000` | `IP_ID` | `0x5352414d` (`SRAM`) | +| `0x004` | `IP_VERSION` | ABI version `0x00010000` | +| `0x008` | `CAPABILITY` | Present, native AXI4, byte-write, burst, and performance flags; max beats in 15:8 and data bytes in 23:16 | +| `0x00c` | `MEMORY_BYTES` | Implemented capacity, or zero when absent | +| `0x010` | `BANK_COUNT` | Implemented data banks, or zero when absent | +| `0x014` | `BANK_BYTES` | Fixed value 4096 | +| `0x020` | `PERF_READ_REQUESTS` | Accepted AR requests | +| `0x024` | `PERF_WRITE_REQUESTS` | Accepted AW requests | +| `0x028` | `PERF_READ_BEATS` | Accepted R beats | +| `0x02c` | `PERF_WRITE_BEATS` | Accepted W beats, including drained error traffic | +| `0x030` | `PERF_STALL_CYCLES` | Cycles with any AXI VALID blocked by READY | +| `0x034` | `PERF_ERROR_RESPONSES` | Terminal non-OKAY responses | + +The SDK exposes `rs_onchip_sram_probe()` and +`rs_onchip_sram_read_performance()`. RTL and C constants remain handwritten; +the register parity test prevents drift without adding a generator. + +## Technology mapping + +`tc_sram_1024x32` is the stable technology boundary. Behavioral simulation +uses an inferred 1024-by-32 array. IHP130 maps to +`RM_IHPSG13_1P_1024x32_c2_bm_bist`; the former 1024-by-64 mapping and discarded +upper half are removed. ICS55 continues to map to its 1024-by-32 wrapper. Bank +count, rather than technology depth or width, implements every product size. + +The wrapper preserves byte masks and one-cycle synchronous reads. PDK timing, +LEF/GDS placement, power hookup, BIST pins, and foundry signoff remain +physical-flow responsibilities. + +## Verification and performance evidence + +`tests/test_onchip_ram.py` elaborates all five capacities and covers bank +boundaries, first/last words, byte/half/word masks, `FIXED`, `INCR`, `WRAP`, +16-beat continuous traffic, backpressure, AR/AW priority, absent-memory and +malformed accesses, counters, and IHP130/ICS55 x32 wrapper polarity and masks. + +`make CONFIG=configs/ci/ihp130.mk formal-onchip-ram` runs a 20-cycle bounded +proof and cover set for AXI stability, channel exclusion, response bounds, +physical-write qualification, and reachable read/write/error completions. +The directed performance comparison measures 16 response cycles for a 16-word +native read and 18 through the compatibility bridge. This is an 11.1 percent +total-latency reduction and a complete removal of the bridge's two non-payload +cycles; 16 cycles is the minimum possible on the 32-bit response channel. +The SRAM-resident four-iteration CoreMark run completes in 4,662,868 cycles +against the recorded 7,620,324-cycle compatibility baseline, a 38.8 percent +cycle reduction. It remains an unqualified regression workload rather than a +public CoreMark score. + +## Reliability and performance roadmap + +The next phase should add SECDED outside the stable data macro. For every four +data banks, one `1024 x 32` bank can store four independent ECC bytes per word +address. A full write computes one ECC byte and writes its byte lane; a partial +write reads and corrects the old word, merges bytes, then writes data and ECC. +The management ABI would add first-error address/syndrome, corrected and +uncorrectable counters, injection, sticky status, and an uncorrectable-error +interrupt. All locations must be initialized with valid data/ECC before reads. + +MBIST follows ECC. A future support wrapper must quiesce AXI, exclude CPUs, +DMA, and debuggers, run a reviewed startup March test or qualified +nondestructive runtime test, record the failing bank/address, and hand +technology BIST/redundancy pins to the physical flow. That phase requires DFT +and tester-ready evidence, not only RTL simulation. + +Further throughput work is conditional on the fabric. Independent read/write +target ownership, more IDs, or per-bank queues are useful only after concurrent +transactions can reach SRAM. RP2350-style striping, bank-aware arbitration, +QoS, and power gating should then be evaluated from measured contention. diff --git a/rtl/README.md b/rtl/README.md index 003b850c..746a7b5e 100644 --- a/rtl/README.md +++ b/rtl/README.md @@ -68,6 +68,12 @@ does not elaborate the tri-state delays in the Micron model. The Icarus testbench retains that Micron timing model, so it is the reference for SDRAM command timing while Verilator provides fast functional coverage. +The Mini SoC on-chip SRAM is a synthesis-time selectable 4/16/32/64/128 KiB +native 32-bit AXI4 target built from fixed 4 KiB technology banks. Its read-only +APB capability/performance ABI, technology mapping, verification evidence, and +ECC/MBIST roadmap are documented in +[Configurable Native-AXI4 On-chip SRAM](../docs/ip/onchip-sram.md). + The self-owned ESP-PSRAM64H controller exposes a 32-bit AXI4 data window and a separate APB4 management plane across four independently isolated 8 MiB chips. Its frozen architecture, register ABI, timing limits, model, formal target, and diff --git a/rtl/filelist/pdk_ihp130.fl b/rtl/filelist/pdk_ihp130.fl index 348d6442..84139398 100644 --- a/rtl/filelist/pdk_ihp130.fl +++ b/rtl/filelist/pdk_ihp130.fl @@ -2,6 +2,6 @@ /physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/verilog/sg13g2_udp.v /physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/verilog/sg13g2_stdcell.v /physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/RM_IHPSG13_1P_core_behavioral_bm_bist.v -/physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/RM_IHPSG13_1P_1024x64_c2_bm_bist.v +/physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/RM_IHPSG13_1P_1024x32_c2_bm_bist.v /physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/RM_IHPSG13_1P_4096x16_c3_bm_bist.v /physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/RM_IHPSG13_1P_4096x8_c3_bm_bist.v diff --git a/rtl/mini/Makefile b/rtl/mini/Makefile index 04254475..4d009712 100644 --- a/rtl/mini/Makefile +++ b/rtl/mini/Makefile @@ -64,7 +64,8 @@ gen_mpw_code: $(MPW_VARIANT_STAMP) $(MEMORY_MAP_STAMP): $(MEMORY_MAP_SOURCE) $(MEMORY_MAP_SCRIPT) $(FLOW_PYTHON) $(MEMORY_MAP_SCRIPT) --map $(MEMORY_MAP_SOURCE) \ - --output-dir $(MEMORY_MAP_DIR) --have-sram-if $(HAVE_SRAM_IF) + --output-dir $(MEMORY_MAP_DIR) --have-sram-if $(HAVE_SRAM_IF) \ + --sram-size-kib $(SRAM_SIZE_KIB) @touch $@ memory-map: $(MEMORY_MAP_STAMP) diff --git a/rtl/mini/address_map/generate_memory_map.py b/rtl/mini/address_map/generate_memory_map.py index 7d318c96..5095a4f1 100644 --- a/rtl/mini/address_map/generate_memory_map.py +++ b/rtl/mini/address_map/generate_memory_map.py @@ -16,6 +16,7 @@ VALID_ROUTES = {"apb4_periph", "apb4_system", "axi4", "ram", "reserved"} VALID_KINDS = {"active", "reserved"} VALID_USER_ACCESS = {"none", "ro", "rw"} VALID_BURST = {"incr1", "incr4", "incr16"} +VALID_SRAM_SIZE_KIB = (4, 16, 32, 64, 128) def parse_integer(value: Any, field: str) -> int: @@ -29,7 +30,11 @@ def parse_integer(value: Any, field: str) -> int: raise ValueError(f"{field} must be an integer") -def read_map(path: Path) -> tuple[int, list[dict[str, Any]], list[dict[str, Any]]]: +def read_map( + path: Path, sram_size_kib: int = 128 +) -> tuple[int, list[dict[str, Any]], list[dict[str, Any]]]: + if sram_size_kib not in VALID_SRAM_SIZE_KIB: + raise ValueError(f"sram_size_kib must be one of {VALID_SRAM_SIZE_KIB}") with path.open(encoding="utf-8") as source: document = json.load(source) if document.get("schema_version") != 2: @@ -89,6 +94,17 @@ def read_map(path: Path) -> tuple[int, list[dict[str, Any]], list[dict[str, Any] raise ValueError(f"regions[{index}] reserved regions cannot support bursts") base = parse_integer(region.get("base"), f"regions[{index}].base") size = parse_integer(region.get("size"), f"regions[{index}].size") + if symbol == "SRAM": + size_options = region.get("size_options") + if not isinstance(size_options, list): + raise ValueError(f"regions[{index}].size_options must be a list") + parsed_options = tuple( + parse_integer(value, f"regions[{index}].size_options") for value in size_options + ) + expected_options = tuple(value * 1024 for value in VALID_SRAM_SIZE_KIB) + if parsed_options != expected_options: + raise ValueError(f"regions[{index}].size_options must be {expected_options}") + size = sram_size_kib * 1024 if base < 0 or size <= 0 or base + size > MAX_ADDRESS: raise ValueError(f"regions[{index}] exceeds the 32-bit address space") if base % 4 != 0 or size % 4 != 0: @@ -155,6 +171,7 @@ def render_rtl( symbol = region["symbol"] lines += [ f"`define SOC_ADDR_{symbol}_BASE {hex32(region['base'])}", + f"`define SOC_ADDR_{symbol}_SIZE {hex32(region['size'])}", f"`define SOC_ADDR_{symbol}_END {hex32(region['end'])}", f"`define SOC_ADDR_IS_{symbol}(addr) " f"{range_expression(symbol, region['base'], region['end'])}", @@ -172,9 +189,7 @@ def render_rtl( for region in regions if region["route"] in {"apb4_periph", "axi4"} ] - lines.append( - f"`define SOC_ADDR_IS_RIB_LEGACY_TARGET(addr) ({join_or(legacy_rib_targets)})" - ) + lines.append(f"`define SOC_ADDR_IS_RIB_LEGACY_TARGET(addr) ({join_or(legacy_rib_targets)})") user_readable = [ f"`SOC_ADDR_IS_{region['symbol']}(addr)" for region in regions @@ -282,8 +297,8 @@ def atomic_write(path: Path, content: str) -> None: raise -def generate(map_path: Path, output_dir: Path, have_sram_if: str) -> None: - cpu_reset, regions, sysctrl_registers = read_map(map_path) +def generate(map_path: Path, output_dir: Path, have_sram_if: str, sram_size_kib: int) -> None: + cpu_reset, regions, sysctrl_registers = read_map(map_path, sram_size_kib) atomic_write( output_dir / "rtl" / "mmap_define.svh", render_rtl(cpu_reset, regions, sysctrl_registers), @@ -301,15 +316,21 @@ def main() -> int: parser.add_argument("--map", required=True, type=Path) parser.add_argument("--output-dir", type=Path) parser.add_argument("--have-sram-if", choices=("YES", "NO"), default="NO") + parser.add_argument("--sram-size-kib", type=int, choices=VALID_SRAM_SIZE_KIB, default=128) parser.add_argument("--check", action="store_true", help="validate only; write nothing") arguments = parser.parse_args() try: if arguments.check: - read_map(arguments.map) + read_map(arguments.map, arguments.sram_size_kib) else: if arguments.output_dir is None: parser.error("--output-dir is required unless --check is used") - generate(arguments.map, arguments.output_dir, arguments.have_sram_if) + generate( + arguments.map, + arguments.output_dir, + arguments.have_sram_if, + arguments.sram_size_kib, + ) except (OSError, ValueError, json.JSONDecodeError) as error: parser.error(str(error)) return 0 diff --git a/rtl/mini/address_map/memory_map.json b/rtl/mini/address_map/memory_map.json index d817e293..0c2218c9 100644 --- a/rtl/mini/address_map/memory_map.json +++ b/rtl/mini/address_map/memory_map.json @@ -379,6 +379,15 @@ "public": true, "user_access": "none" }, + { + "symbol": "APB4_SRAM", + "base": "0x10017000", + "size": "0x00001000", + "route": "apb4_periph", + "kind": "active", + "public": true, + "user_access": "none" + }, { "symbol": "APB4_ARCHINFO", "base": "0x20000000", @@ -455,6 +464,13 @@ "symbol": "SRAM", "base": "0x30000000", "size": "0x00020000", + "size_options": [ + "0x00001000", + "0x00004000", + "0x00008000", + "0x00010000", + "0x00020000" + ], "route": "ram", "kind": "active", "public": true, diff --git a/rtl/mini/filelist/top.fl b/rtl/mini/filelist/top.fl index 57fcf031..8b1475ed 100644 --- a/rtl/mini/filelist/top.fl +++ b/rtl/mini/filelist/top.fl @@ -17,6 +17,8 @@ /top/core_wrapper.sv /top/retrosoc.sv /top/retrosoc_asic.sv +/top/retrosoc_core.sv +/top/onchip_ram_reg.sv /top/onchip_ram.sv /top/apb4_periph.sv /top/apb4_system.sv diff --git a/rtl/mini/formal/generate_formal_filelist.py b/rtl/mini/formal/generate_formal_filelist.py index f6b6480f..db049e2b 100644 --- a/rtl/mini/formal/generate_formal_filelist.py +++ b/rtl/mini/formal/generate_formal_filelist.py @@ -26,7 +26,7 @@ TOP = ROOT / "rtl/mini/top" def target_defines(target: str) -> list[str]: - if target == "opipsram": + if target in {"onchip_ram", "opipsram"}: return ["+define+PDK_BEHAV"] return ["+define+SV_ASSRT_DISABLE"] @@ -197,6 +197,18 @@ def source_files(target: str) -> list[Path]: MEMORY / "psram_phy.sv", SCRIPT_DIR / "psram_formal.sv", ] + if target == "onchip_ram": + return [ + COMMON_RTL / "interface/axi4_if.sv", + COMMON_RTL / "interface/apb4_if.sv", + COMMON_RTL / "interface/axi4_addr_gen.sv", + COMMON_RTL / "utils/register.sv", + COMMON_RTL / "utils/xchecker.sv", + ROOT / "rtl/tech/tc_sram.sv", + TOP / "onchip_ram_reg.sv", + TOP / "onchip_ram.sv", + SCRIPT_DIR / "onchip_ram_formal.sv", + ] if target == "opipsram": return [ COMMON_RTL / "interface/axi4_if.sv", @@ -292,6 +304,7 @@ def parse_args() -> argparse.Namespace: "dvp", "i2s", "psram", + "onchip_ram", "opipsram", "dma", "sdio", diff --git a/rtl/mini/formal/onchip_ram_formal.sv b/rtl/mini/formal/onchip_ram_formal.sv new file mode 100644 index 00000000..fa5be67d --- /dev/null +++ b/rtl/mini/formal/onchip_ram_formal.sv @@ -0,0 +1,172 @@ +// 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 NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +module onchip_ram_formal_design ( + // verilog_format: off -- preserve AXI channel grouping + input logic clk_i, + output logic rst_n_i, + output logic f_past_valid, + output logic awvalid, + output logic awready, + output logic [31:0] awaddr, + output logic [7:0] awlen, + output logic [2:0] awsize, + output logic [1:0] awburst, + output logic wvalid, + output logic wready, + output logic [31:0] wdata, + output logic [3:0] wstrb, + output logic wlast, + output logic bvalid, + output logic bready, + output logic [1:0] bresp, + output logic arvalid, + output logic arready, + output logic [31:0] araddr, + output logic [7:0] arlen, + output logic [2:0] arsize, + output logic [1:0] arburst, + output logic rvalid, + output logic rready, + output logic [31:0] rdata, + output logic [1:0] rresp, + output logic rlast, + output logic memory_read, + output logic memory_write, + output logic [14:0] memory_word_addr, + output logic [3:0] memory_write_strobe + // verilog_format: on +); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) mem_axi4 ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + apb4_if cfg_apb4 ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + + (* anyseq *)logic f_awvalid; + (* anyseq *)logic [31:0] f_awaddr; + (* anyseq *)logic [ 7:0] f_awlen; + (* anyseq *)logic [ 2:0] f_awsize; + (* anyseq *)logic [ 1:0] f_awburst; + (* anyseq *)logic f_awlock; + (* anyseq *)logic f_wvalid; + (* anyseq *)logic [31:0] f_wdata; + (* anyseq *)logic [ 3:0] f_wstrb; + (* anyseq *)logic f_wlast; + (* anyseq *)logic f_bready; + (* anyseq *)logic f_arvalid; + (* anyseq *)logic [31:0] f_araddr; + (* anyseq *)logic [ 7:0] f_arlen; + (* anyseq *)logic [ 2:0] f_arsize; + (* anyseq *)logic [ 1:0] f_arburst; + (* anyseq *)logic f_arlock; + (* anyseq *)logic f_rready; + + assign mem_axi4.awid = 1'b0; + assign mem_axi4.awaddr = f_awaddr; + assign mem_axi4.awlen = f_awlen; + assign mem_axi4.awsize = f_awsize; + assign mem_axi4.awburst = f_awburst; + assign mem_axi4.awlock = f_awlock; + assign mem_axi4.awcache = '0; + assign mem_axi4.awprot = '0; + assign mem_axi4.awqos = '0; + assign mem_axi4.awregion = '0; + assign mem_axi4.awuser = '0; + assign mem_axi4.awvalid = f_awvalid; + assign mem_axi4.wdata = f_wdata; + assign mem_axi4.wstrb = f_wstrb; + assign mem_axi4.wlast = f_wlast; + assign mem_axi4.wuser = '0; + assign mem_axi4.wvalid = f_wvalid; + assign mem_axi4.bready = f_bready; + assign mem_axi4.arid = 1'b0; + assign mem_axi4.araddr = f_araddr; + assign mem_axi4.arlen = f_arlen; + assign mem_axi4.arsize = f_arsize; + assign mem_axi4.arburst = f_arburst; + assign mem_axi4.arlock = f_arlock; + assign mem_axi4.arcache = '0; + assign mem_axi4.arprot = '0; + assign mem_axi4.arqos = '0; + assign mem_axi4.arregion = '0; + assign mem_axi4.aruser = '0; + assign mem_axi4.arvalid = f_arvalid; + assign mem_axi4.rready = f_rready; + + assign cfg_apb4.paddr = '0; + assign cfg_apb4.pprot = '0; + assign cfg_apb4.psel = 1'b0; + assign cfg_apb4.penable = 1'b0; + assign cfg_apb4.pwrite = 1'b0; + assign cfg_apb4.pwdata = '0; + assign cfg_apb4.pstrb = '0; + + assign awvalid = mem_axi4.awvalid; + assign awready = mem_axi4.awready; + assign awaddr = mem_axi4.awaddr; + assign awlen = mem_axi4.awlen; + assign awsize = mem_axi4.awsize; + assign awburst = mem_axi4.awburst; + assign wvalid = mem_axi4.wvalid; + assign wready = mem_axi4.wready; + assign wdata = mem_axi4.wdata; + assign wstrb = mem_axi4.wstrb; + assign wlast = mem_axi4.wlast; + assign bvalid = mem_axi4.bvalid; + assign bready = mem_axi4.bready; + assign bresp = mem_axi4.bresp; + assign arvalid = mem_axi4.arvalid; + assign arready = mem_axi4.arready; + assign araddr = mem_axi4.araddr; + assign arlen = mem_axi4.arlen; + assign arsize = mem_axi4.arsize; + assign arburst = mem_axi4.arburst; + assign rvalid = mem_axi4.rvalid; + assign rready = mem_axi4.rready; + assign rdata = mem_axi4.rdata; + assign rresp = mem_axi4.rresp; + assign rlast = mem_axi4.rlast; + + onchip_ram #( + .Present (1'b1), + .CapacityKiB(4) + ) u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .perf_enable_i(1'b1), + .perf_clear_i (1'b0), + .mem_axi4 (mem_axi4), + .cfg_apb4 (cfg_apb4) + ); + + assign memory_read = u_dut.s_mem_read; + assign memory_write = u_dut.s_mem_write; + assign memory_word_addr = u_dut.s_mem_word_addr; + assign memory_write_strobe = u_dut.s_mem_wstrb; + + 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; + end +endmodule diff --git a/rtl/mini/formal/onchip_ram_formal_props.sv b/rtl/mini/formal/onchip_ram_formal_props.sv new file mode 100644 index 00000000..1a91b8c5 --- /dev/null +++ b/rtl/mini/formal/onchip_ram_formal_props.sv @@ -0,0 +1,113 @@ +// 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 NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +module onchip_ram_formal; + (* anyseq *) (* gclk *)reg clk_i; + logic rst_n_i; + logic f_past_valid; + logic awvalid; + logic awready; + logic [31:0] awaddr; + logic [ 7:0] awlen; + logic [ 2:0] awsize; + logic [ 1:0] awburst; + logic wvalid; + logic wready; + logic [31:0] wdata; + logic [ 3:0] wstrb; + logic wlast; + logic bvalid; + logic bready; + logic [ 1:0] bresp; + logic arvalid; + logic arready; + logic [31:0] araddr; + logic [ 7:0] arlen; + logic [ 2:0] arsize; + logic [ 1:0] arburst; + logic rvalid; + logic rready; + logic [31:0] rdata; + logic [ 1:0] rresp; + logic rlast; + logic memory_read; + logic memory_write; + logic [14:0] memory_word_addr; + logic [ 3:0] memory_write_strobe; + logic [ 4:0] s_read_beat_count_q; + logic [ 4:0] s_write_beat_count_q; + + onchip_ram_formal_design u_design (.*); + + always @(posedge clk_i) begin + if (!rst_n_i) begin + s_read_beat_count_q <= '0; + s_write_beat_count_q <= '0; + end else begin + if (arvalid && arready) s_read_beat_count_q <= '0; + else if (rvalid && rready) s_read_beat_count_q <= s_read_beat_count_q + 1'b1; + if (awvalid && awready) s_write_beat_count_q <= '0; + else if (wvalid && wready) s_write_beat_count_q <= s_write_beat_count_q + 1'b1; + end + + if (f_past_valid && $past(rst_n_i)) begin + if ($past(awvalid && !awready)) begin + assume (awvalid); + assume (awaddr == $past(awaddr)); + assume (awlen == $past(awlen)); + assume (awsize == $past(awsize)); + assume (awburst == $past(awburst)); + end + if ($past(wvalid && !wready)) begin + assume (wvalid); + assume (wdata == $past(wdata)); + assume (wstrb == $past(wstrb)); + assume (wlast == $past(wlast)); + end + if ($past(arvalid && !arready)) begin + assume (arvalid); + assume (araddr == $past(araddr)); + assume (arlen == $past(arlen)); + assume (arsize == $past(arsize)); + assume (arburst == $past(arburst)); + end + if ($past(bvalid && !bready)) begin + assert (bvalid); + assert (bresp == $past(bresp)); + end + if ($past(rvalid && !rready)) begin + assert (rvalid); + assert (rdata == $past(rdata)); + assert (rresp == $past(rresp)); + assert (rlast == $past(rlast)); + end + end + + if (rst_n_i) begin + assert (!((awvalid && awready) && (arvalid && arready))); + assert (!(memory_read && memory_write)); + assert (s_read_beat_count_q <= 5'd16); + assert (s_write_beat_count_q <= 5'd16); + if (memory_write) begin + assert (wvalid && wready); + assert (memory_write_strobe != 4'd0); + end + if (rlast && rvalid) assert (s_read_beat_count_q <= 5'd15); + if (bvalid) assert (s_write_beat_count_q <= 5'd16); + + cover (arvalid && arready && (arlen == 8'd3)); + cover (rvalid && rready && rlast && (rresp == 2'b00)); + cover (awvalid && awready && (awlen == 8'd3)); + cover (memory_write && (memory_write_strobe == 4'hF)); + cover (bvalid && bready && (bresp == 2'b00)); + cover (rvalid && rready && (rresp == 2'b11)); + end + end +endmodule diff --git a/rtl/mini/integration/soc_topology.json b/rtl/mini/integration/soc_topology.json index 662c7d80..a4292310 100644 --- a/rtl/mini/integration/soc_topology.json +++ b/rtl/mini/integration/soc_topology.json @@ -429,6 +429,14 @@ "timed_interface": "u_usb2_apb4_if", "pure_interface": "u_usb2_apb4_pure_if", "region": "APB4_USB2" + }, + { + "slot": 21, + "name": "sram", + "timed_interface": "sram_cfg", + "pure_interface": "sram_cfg_pure", + "region": "APB4_SRAM", + "external": true } ], "apb4_system_targets": [ diff --git a/rtl/mini/mk/formal.mk b/rtl/mini/mk/formal.mk index 420d8fee..c8a3c552 100644 --- a/rtl/mini/mk/formal.mk +++ b/rtl/mini/mk/formal.mk @@ -22,7 +22,7 @@ FORMAL_OPIPSRAM_TIMEOUT ?= 300 FORMAL_DMA_TIMEOUT ?= 120 FORMAL_SDIO_TIMEOUT ?= 120 FORMAL_COMPACT_COVER_TARGETS := i2c opipsram dma -FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram opipsram dma sdio +FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram onchip_ram opipsram dma 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 @@ -54,6 +54,8 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \ $(RTL_PATH)/formal/i2s_formal_props.sv \ $(RTL_PATH)/formal/psram_formal.sv \ $(RTL_PATH)/formal/psram_formal_props.sv \ + $(RTL_PATH)/formal/onchip_ram_formal.sv \ + $(RTL_PATH)/formal/onchip_ram_formal_props.sv \ $(RTL_PATH)/formal/opipsram_formal.sv \ $(RTL_PATH)/formal/opipsram_formal_props.sv \ $(RTL_PATH)/formal/dma_formal.sv \ @@ -115,6 +117,9 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \ $(ROOT_PATH)/rtl/ip/memory/psram_pkg.sv \ $(ROOT_PATH)/rtl/ip/memory/psram_axi4.sv \ $(ROOT_PATH)/rtl/ip/memory/psram_phy.sv \ + $(RTL_PATH)/top/onchip_ram_reg.sv \ + $(RTL_PATH)/top/onchip_ram.sv \ + $(ROOT_PATH)/rtl/tech/tc_sram.sv \ $(ROOT_PATH)/rtl/managed/clusterip/common/rtl/utils/register.sv \ $(ROOT_PATH)/rtl/managed/clusterip/common/rtl/utils/xchecker.sv \ $(ROOT_PATH)/rtl/managed/clusterip/common/rtl/utils/gray2bin.sv \ @@ -179,7 +184,7 @@ $(FORMAL_DIR)/%/prove.sby: $(FORMAL_DIR)/%/design.v $(RTL_PATH)/formal/%_formal_ $(FORMAL_SBY_GENERATOR) python3 $(FORMAL_SBY_GENERATOR) --top $*_formal --input $< \ --properties $(RTL_PATH)/formal/$*_formal_props.sv --solver $(FORMAL_SOLVER) \ - --mode $(if $(filter dma sdio,$*),bmc,prove) --depth $(if $(filter dma,$*),$(FORMAL_DMA_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 $@ + --mode $(if $(filter dma sdio onchip_ram,$*),bmc,prove) --depth $(if $(filter dma,$*),$(FORMAL_DMA_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 $(RTL_PATH)/formal/%_formal_props.sv \ @@ -277,6 +282,8 @@ formal-i2s: $(FORMAL_DIR)/i2s/.stamp | manifest formal-psram: $(FORMAL_DIR)/psram/.stamp | manifest +formal-onchip-ram: $(FORMAL_DIR)/onchip_ram/.stamp | manifest + formal-opipsram: $(FORMAL_DIR)/opipsram/.stamp | manifest formal-dma: $(FORMAL_DIR)/dma/.stamp | manifest @@ -289,4 +296,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-opipsram formal-dma 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-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 6ded66f9..e66646ac 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/dvp.c \ $(ROOT_PATH)/crt/src/hal/perf.c \ $(ROOT_PATH)/crt/src/hal/lcd.c \ + $(ROOT_PATH)/crt/src/hal/onchip_sram.c \ $(ROOT_PATH)/crt/src/hal/psram_math.c \ $(ROOT_PATH)/crt/src/hal/psram.c \ $(ROOT_PATH)/crt/src/hal/opipsram_math.c \ diff --git a/rtl/mini/pin_map/generate_pin_map.py b/rtl/mini/pin_map/generate_pin_map.py index dbb30c2a..99156b06 100644 --- a/rtl/mini/pin_map/generate_pin_map.py +++ b/rtl/mini/pin_map/generate_pin_map.py @@ -237,6 +237,15 @@ def render_ports(pads: list[Pad]) -> str: return "\n".join(lines) + "\n" +def render_core_ports(pads: list[Pad]) -> str: + """Render the logical-chip port list without technology power pins.""" + lines = ["// Generated by rtl/mini/pin_map/generate_pin_map.py; do not edit."] + for index, pad in enumerate(pads): + suffix = "," if index != len(pads) - 1 else "" + lines.extend(wrap_feature(pad, f" {pad.direction} {pad.name}{suffix}")) + return "\n".join(lines) + "\n" + + def render_pad_instance(pad: Pad) -> str | None: instance = f"u_{pad.name}" if pad.kind == "tri_input": @@ -308,14 +317,18 @@ def render_pad_bindings(pads: list[Pad]) -> str: f" for (genvar i = 0; i < {count}; i++) begin : {kind}_pads", ' (* keep *) (* dont_touch = "true" *)', f" sg13g2_IOPad{cell_suffixes[kind]} {kind}_pad (", - "`ifdef USE_POWER_PINS", - " .iovdd(IOVDD), .iovss(IOVSS), .vdd(VDD), .vss(VSS)", - "`endif", " );", " end", ] ) - lines.extend([" endgenerate", "`else", "`define RETROSOC_PAD_POWER_CONNECTIONS", "`endif"]) + lines.extend( + [ + " endgenerate", + "`else", + "`define RETROSOC_PAD_POWER_CONNECTIONS", + "`endif", + ] + ) for pad in pads: line = render_pad_instance(pad) if line is not None: @@ -324,6 +337,72 @@ def render_pad_bindings(pads: list[Pad]) -> str: return "\n".join(lines) + "\n" +def render_core_binding(pad: Pad) -> str | None: + """Bind a package-facing core port directly to the logical SoC interface.""" + if pad.kind in {"tri_input", "schmitt_input"}: + assert pad.signal is not None + return f" assign {pad.signal} = {pad.name};" + if pad.kind in {"tri_output", "peripheral_output"}: + assert pad.signal is not None + return f" assign {pad.name} = {pad.signal};" + if pad.kind == "peripheral_bidir": + assert pad.input_signal is not None + assert pad.output_signal is not None + assert pad.output_enable_signal is not None + return "\n".join( + [ + f" assign {pad.name} = {pad.output_enable_signal} ? {pad.output_signal} : 1'bz;", + f" assign {pad.input_signal} = {pad.name};", + ] + ) + if pad.kind == "full": + assert pad.interface is not None + assert pad.index is not None + return "\n".join( + [ + f" assign {pad.name} = {pad.interface}.oe_o[{pad.index}] ? " + f"{pad.interface}.do_o[{pad.index}] : 1'bz;", + f" assign {pad.interface}.di_i[{pad.index}] = {pad.name};", + ] + ) + if pad.kind == "xpi_data": + assert pad.index is not None + return "\n".join( + [ + f" assign {pad.name} = u_xpi_if.io_oe_o[{pad.index}] ? " + f"u_xpi_if.io_do_o[{pad.index}] : 1'bz;", + f" assign u_xpi_if.io_di_i[{pad.index}] = {pad.name};", + ] + ) + if pad.kind == "sdram_dq": + assert pad.index is not None + return "\n".join( + [ + f" assign {pad.name} = u_sdram_if.oe_o ? u_sdram_if.dq_o[{pad.index}] : 1'bz;", + f" assign u_sdram_if.dq_i[{pad.index}] = {pad.name};", + ] + ) + if pad.kind == "xtal" and pad.bind: + assert pad.signal is not None + assert pad.peer is not None + return "\n".join( + [ + f" assign {pad.signal} = {pad.name};", + f" assign {pad.peer} = 1'b0;", + ] + ) + return None + + +def render_core_bindings(pads: list[Pad]) -> str: + lines = ["// Generated by rtl/mini/pin_map/generate_pin_map.py; do not edit."] + for pad in pads: + binding = render_core_binding(pad) + if binding is not None: + lines.extend(wrap_feature(pad, binding)) + return "\n".join(lines) + "\n" + + def ihp130_pad_cell(pad: Pad) -> str | None: if pad.kind in {"tri_input", "schmitt_input"}: return "sg13g2_IOPadIn" @@ -359,6 +438,8 @@ def generate(map_path: Path, output_dir: Path) -> None: rtl_dir = output_dir / "rtl" atomic_write(rtl_dir / "retrosoc_asic_ports.svh", render_ports(pads)) atomic_write(rtl_dir / "retrosoc_asic_pad_bindings.svh", render_pad_bindings(pads)) + atomic_write(rtl_dir / "retrosoc_core_ports.svh", render_core_ports(pads)) + atomic_write(rtl_dir / "retrosoc_core_bindings.svh", render_core_bindings(pads)) for profile, bindings in profiles.items(): atomic_write( rtl_dir / f"retrosoc_asic_{profile}_bindings.svh", diff --git a/rtl/mini/top/apb4_periph.sv b/rtl/mini/top/apb4_periph.sv index 7df97fc4..04105a18 100644 --- a/rtl/mini/top/apb4_periph.sv +++ b/rtl/mini/top/apb4_periph.sv @@ -41,6 +41,7 @@ module apb4_periph ( sysctrl_if.dut sysctrl, pll_ctrl_if.sysctrl pll_ctrl, apb4_if.master sdram_cfg, + apb4_if.master sram_cfg, dvp_if.dut dvp, sdio_if.dut sdio0, sdio_if.dut sdio1, diff --git a/rtl/mini/top/axi4_bus.sv b/rtl/mini/top/axi4_bus.sv index fde2e68f..8530409f 100644 --- a/rtl/mini/top/axi4_bus.sv +++ b/rtl/mini/top/axi4_bus.sv @@ -21,9 +21,7 @@ module axi4_bus ( axi4_if.slave spisd_axi4, axi4_if.master cfg_axi4, axi4_if.master system_axi4, -`ifdef HAVE_SRAM_IF - ram_if.master ram, -`endif + axi4_if.master sram_axi4, axi4_if.master sdram_axi4, axi4_if.master psram_axi4, axi4_if.master xpi_axi4, @@ -76,16 +74,6 @@ module axi4_bus ( .aresetn(rst_n_i) ); - axi4_if #( - .ADDR_WIDTH(32), - .DATA_WIDTH(32), - .ID_WIDTH (1), - .USER_WIDTH(1) - ) u_ram_axi4_if ( - .aclk (clk_i), - .aresetn(rst_n_i) - ); - axi4_connector u_mgmt_connector ( .source(mgmt_axi4), .sink (u_master_axi4_if[0]) @@ -133,7 +121,7 @@ module axi4_bus ( axi4_connector u_ram_connector ( .source(u_target_axi4_if[2]), - .sink (u_ram_axi4_if) + .sink (sram_axi4) ); axi4_connector u_sdram_connector ( @@ -189,23 +177,6 @@ module axi4_bus ( .perf_opipsram_wait_o (perf_opipsram_wait_o) ); -`ifdef HAVE_SRAM_IF - axi42ram u_axi42ram ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .axi4 (u_ram_axi4_if), - .ram (ram) - ); -`else - axi4_error_slave #( - .Response(2'b11) - ) u_ram_error_slave ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .axi4 (u_ram_axi4_if) - ); -`endif - axi4_error_slave #( .Response(2'b11) ) u_decode_error_slave ( diff --git a/rtl/mini/top/onchip_ram.sv b/rtl/mini/top/onchip_ram.sv index f6384138..e85a0c30 100644 --- a/rtl/mini/top/onchip_ram.sv +++ b/rtl/mini/top/onchip_ram.sv @@ -8,64 +8,601 @@ // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -// 32x4KB=128KB -module onchip_ram ( - // verilog_format: off -- preserve aligned port declarations - input logic clk_i, - ram_if.slave ram +`include "axi4_define.svh" +`include "mmap_define.svh" + +module onchip_ram #( + parameter bit Present = 1'b1, + parameter int unsigned CapacityKiB = 128 +) ( + // verilog_format: off -- preserve the AXI, APB, and performance grouping + input logic clk_i, + input logic rst_n_i, + input logic perf_enable_i, + input logic perf_clear_i, + axi4_if.slave mem_axi4, + apb4_if.slave cfg_apb4 // verilog_format: on ); + localparam int unsigned NumBanks = CapacityKiB / 4; + localparam int unsigned BankIndexWidth = (NumBanks > 1) ? $clog2(NumBanks) : 1; + localparam logic [31:0] MemoryEnd = `SOC_ADDR_SRAM_BASE + 32'(CapacityKiB * 1024) - 1'b1; - logic s_cs [31:0]; - logic [31:0] s_rdata[0:31]; + typedef enum logic [2:0] { + Idle, + ReadData, + ReadError, + WriteData, + WriteDrain, + WriteResponse + } state_e; - // verilog_format: off -- preserve aligned bank decode - assign s_cs[0] = ~ram.addr[14] && ~ram.addr[13] && ~ram.addr[12] && ~ram.addr[11] && ~ram.addr[10]; - assign s_cs[1] = ~ram.addr[14] && ~ram.addr[13] && ~ram.addr[12] && ~ram.addr[11] && ram.addr[10]; - assign s_cs[2] = ~ram.addr[14] && ~ram.addr[13] && ~ram.addr[12] && ram.addr[11] && ~ram.addr[10]; - assign s_cs[3] = ~ram.addr[14] && ~ram.addr[13] && ~ram.addr[12] && ram.addr[11] && ram.addr[10]; - assign s_cs[4] = ~ram.addr[14] && ~ram.addr[13] && ram.addr[12] && ~ram.addr[11] && ~ram.addr[10]; - assign s_cs[5] = ~ram.addr[14] && ~ram.addr[13] && ram.addr[12] && ~ram.addr[11] && ram.addr[10]; - assign s_cs[6] = ~ram.addr[14] && ~ram.addr[13] && ram.addr[12] && ram.addr[11] && ~ram.addr[10]; - assign s_cs[7] = ~ram.addr[14] && ~ram.addr[13] && ram.addr[12] && ram.addr[11] && ram.addr[10]; - assign s_cs[8] = ~ram.addr[14] && ram.addr[13] && ~ram.addr[12] && ~ram.addr[11] && ~ram.addr[10]; - assign s_cs[9] = ~ram.addr[14] && ram.addr[13] && ~ram.addr[12] && ~ram.addr[11] && ram.addr[10]; - assign s_cs[10] = ~ram.addr[14] && ram.addr[13] && ~ram.addr[12] && ram.addr[11] && ~ram.addr[10]; - assign s_cs[11] = ~ram.addr[14] && ram.addr[13] && ~ram.addr[12] && ram.addr[11] && ram.addr[10]; - assign s_cs[12] = ~ram.addr[14] && ram.addr[13] && ram.addr[12] && ~ram.addr[11] && ~ram.addr[10]; - assign s_cs[13] = ~ram.addr[14] && ram.addr[13] && ram.addr[12] && ~ram.addr[11] && ram.addr[10]; - assign s_cs[14] = ~ram.addr[14] && ram.addr[13] && ram.addr[12] && ram.addr[11] && ~ram.addr[10]; - assign s_cs[15] = ~ram.addr[14] && ram.addr[13] && ram.addr[12] && ram.addr[11] && ram.addr[10]; - assign s_cs[16] = ram.addr[14] && ~ram.addr[13] && ~ram.addr[12] && ~ram.addr[11] && ~ram.addr[10]; - assign s_cs[17] = ram.addr[14] && ~ram.addr[13] && ~ram.addr[12] && ~ram.addr[11] && ram.addr[10]; - assign s_cs[18] = ram.addr[14] && ~ram.addr[13] && ~ram.addr[12] && ram.addr[11] && ~ram.addr[10]; - assign s_cs[19] = ram.addr[14] && ~ram.addr[13] && ~ram.addr[12] && ram.addr[11] && ram.addr[10]; - assign s_cs[20] = ram.addr[14] && ~ram.addr[13] && ram.addr[12] && ~ram.addr[11] && ~ram.addr[10]; - assign s_cs[21] = ram.addr[14] && ~ram.addr[13] && ram.addr[12] && ~ram.addr[11] && ram.addr[10]; - assign s_cs[22] = ram.addr[14] && ~ram.addr[13] && ram.addr[12] && ram.addr[11] && ~ram.addr[10]; - assign s_cs[23] = ram.addr[14] && ~ram.addr[13] && ram.addr[12] && ram.addr[11] && ram.addr[10]; - assign s_cs[24] = ram.addr[14] && ram.addr[13] && ~ram.addr[12] && ~ram.addr[11] && ~ram.addr[10]; - assign s_cs[25] = ram.addr[14] && ram.addr[13] && ~ram.addr[12] && ~ram.addr[11] && ram.addr[10]; - assign s_cs[26] = ram.addr[14] && ram.addr[13] && ~ram.addr[12] && ram.addr[11] && ~ram.addr[10]; - assign s_cs[27] = ram.addr[14] && ram.addr[13] && ~ram.addr[12] && ram.addr[11] && ram.addr[10]; - assign s_cs[28] = ram.addr[14] && ram.addr[13] && ram.addr[12] && ~ram.addr[11] && ~ram.addr[10]; - assign s_cs[29] = ram.addr[14] && ram.addr[13] && ram.addr[12] && ~ram.addr[11] && ram.addr[10]; - assign s_cs[30] = ram.addr[14] && ram.addr[13] && ram.addr[12] && ram.addr[11] && ~ram.addr[10]; - assign s_cs[31] = ram.addr[14] && ram.addr[13] && ram.addr[12] && ram.addr[11] && ram.addr[10]; + state_e s_state_d; + state_e s_state_q; + logic [2:0] s_state_bits_q; + logic s_id_d, s_id_q; + logic [7:0] s_len_d, s_len_q; + logic [7:0] s_beat_d, s_beat_q; + logic [2:0] s_size_d, s_size_q; + logic [1:0] s_burst_d, s_burst_q; + logic [31:0] s_addr_d, s_addr_q; + logic [1:0] s_write_resp_d, s_write_resp_q; + logic s_read_pending_d, s_read_pending_q; + logic [7:0] s_read_rsp_beat_d, s_read_rsp_beat_q; + logic s_read_rsp_last_d, s_read_rsp_last_q; + logic [1:0] s_read_rsp_d, s_read_rsp_q; + logic s_read_issue_valid_d, s_read_issue_valid_q; + logic [31:0] s_read_issue_addr_d, s_read_issue_addr_q; + logic [7:0] s_read_issue_beat_d, s_read_issue_beat_q; - assign ram.rdata = s_rdata[ram.addr[14:10]]; - // verilog_format: on + logic [31:0] s_perf_read_requests_d, s_perf_read_requests_q; + logic [31:0] s_perf_write_requests_d, s_perf_write_requests_q; + logic [31:0] s_perf_read_beats_d, s_perf_read_beats_q; + logic [31:0] s_perf_write_beats_d, s_perf_write_beats_q; + logic [31:0] s_perf_stall_cycles_d, s_perf_stall_cycles_q; + logic [31:0] s_perf_err_resps_d, s_perf_err_resps_q; - for (genvar i = 0; i < 32; ++i) begin : gen_sram_block - tc_sram_1024x32 u_ram ( - .clk_i (clk_i), - .cs_i (s_cs[i]), - .addr_i(ram.addr[9:0]), - .data_i(ram.wdata), - .mask_i(ram.wstrb), - .wren_i(|ram.wstrb), - .data_o(s_rdata[i]) - ); + logic [32:0] s_ar_last_addr; + logic [32:0] s_aw_last_addr; + logic [31:0] s_ar_next_addr; + logic [31:0] s_read_next_addr; + logic [31:0] s_write_next_addr; + logic s_ar_protocol_legal; + logic s_aw_protocol_legal; + logic s_ar_bounds_legal; + logic s_aw_bounds_legal; + logic [ 1:0] s_ar_response; + logic [ 1:0] s_aw_response; + logic s_ar_accept; + logic s_aw_accept; + logic s_w_accept; + logic s_read_accept; + logic s_b_accept; + logic s_read_issue; + logic s_read_can_issue; + logic s_expected_wlast; + logic [ 3:0] s_write_lane_mask; + logic s_write_strobe_legal; + logic s_write_beat_legal; + logic s_write_terminal; + logic s_err_resp_event; + logic s_stall_event; + + logic s_mem_read; + logic s_mem_write; + logic [14:0] s_mem_word_addr; + logic [31:0] s_mem_wdata; + logic [ 3:0] s_mem_wstrb; + logic [31:0] s_mem_rdata; + + function automatic logic wrap_length_legal(input logic [7:0] length_i); + return (length_i == 8'd1) || (length_i == 8'd3) || (length_i == 8'd7) || (length_i == 8'd15); + endfunction + + function automatic logic [32:0] burst_last_addr( + input logic [31:0] addr_i, input logic [7:0] length_i, input logic [2:0] size_i, + input logic [1:0] burst_i); + logic [32:0] beat_bytes; + logic [32:0] burst_bytes; + logic [32:0] wrap_base; + begin + beat_bytes = 33'd1 << size_i; + burst_bytes = beat_bytes * ({25'd0, length_i} + 1'b1); + wrap_base = {1'b0, addr_i} & ~(burst_bytes - 1'b1); + unique case (burst_i) + `AXI4_BURST_TYPE_FIXED: burst_last_addr = {1'b0, addr_i} + beat_bytes - 1'b1; + `AXI4_BURST_TYPE_WRAP: burst_last_addr = wrap_base + burst_bytes - 1'b1; + default: burst_last_addr = {1'b0, addr_i} + burst_bytes - 1'b1; + endcase + end + endfunction + + function automatic logic [3:0] transfer_lane_mask(input logic [2:0] size_i, + input logic [1:0] address_i); + unique case (size_i) + 3'd0: return 4'b0001 << address_i; + 3'd1: return 4'b0011 << address_i; + default: return 4'b1111; + endcase + endfunction + + function automatic logic [31:0] saturating_increment(input logic [31:0] value_i); + return (&value_i) ? value_i : value_i + 1'b1; + endfunction + + assign s_state_q = state_e'(s_state_bits_q); + + assign s_ar_last_addr = burst_last_addr( + mem_axi4.araddr, mem_axi4.arlen, mem_axi4.arsize, mem_axi4.arburst + ); + assign s_aw_last_addr = burst_last_addr( + mem_axi4.awaddr, mem_axi4.awlen, mem_axi4.awsize, mem_axi4.awburst + ); + assign s_ar_protocol_legal = + (mem_axi4.arlen <= 8'd15) && + ((mem_axi4.arburst == `AXI4_BURST_TYPE_FIXED) || + (mem_axi4.arburst == `AXI4_BURST_TYPE_INCR) || + ((mem_axi4.arburst == `AXI4_BURST_TYPE_WRAP) && wrap_length_legal( + mem_axi4.arlen + ))) && !mem_axi4.arlock && (mem_axi4.arsize <= `AXI4_BURST_SIZE_4BYTES) && + ((mem_axi4.araddr & ((32'd1 << mem_axi4.arsize) - 1'b1)) == 32'd0) && !s_ar_last_addr[32] && + (mem_axi4.araddr[31:12] == s_ar_last_addr[31:12]); + assign s_aw_protocol_legal = + (mem_axi4.awlen <= 8'd15) && + ((mem_axi4.awburst == `AXI4_BURST_TYPE_FIXED) || + (mem_axi4.awburst == `AXI4_BURST_TYPE_INCR) || + ((mem_axi4.awburst == `AXI4_BURST_TYPE_WRAP) && wrap_length_legal( + mem_axi4.awlen + ))) && !mem_axi4.awlock && (mem_axi4.awsize <= `AXI4_BURST_SIZE_4BYTES) && + ((mem_axi4.awaddr & ((32'd1 << mem_axi4.awsize) - 1'b1)) == 32'd0) && !s_aw_last_addr[32] && + (mem_axi4.awaddr[31:12] == s_aw_last_addr[31:12]); + assign s_ar_bounds_legal = Present && (mem_axi4.araddr >= `SOC_ADDR_SRAM_BASE) && + (s_ar_last_addr[31:0] <= MemoryEnd); + assign s_aw_bounds_legal = Present && (mem_axi4.awaddr >= `SOC_ADDR_SRAM_BASE) && + (s_aw_last_addr[31:0] <= MemoryEnd); + assign s_ar_response = !s_ar_protocol_legal ? `AXI4_RESP_SLAVE_ERROR : + !s_ar_bounds_legal ? `AXI4_RESP_DECODE_ERROR : `AXI4_RESP_OKAY; + assign s_aw_response = !s_aw_protocol_legal ? `AXI4_RESP_SLAVE_ERROR : + !s_aw_bounds_legal ? `AXI4_RESP_DECODE_ERROR : `AXI4_RESP_OKAY; + + assign mem_axi4.arready = s_state_q == Idle; + assign mem_axi4.awready = (s_state_q == Idle) && !mem_axi4.arvalid; + assign mem_axi4.wready = (s_state_q == WriteData) || (s_state_q == WriteDrain); + assign mem_axi4.rid = s_id_q; + assign mem_axi4.rdata = (s_read_rsp_q == `AXI4_RESP_OKAY) ? s_mem_rdata : 32'd0; + assign mem_axi4.rresp = s_read_rsp_q; + assign mem_axi4.rlast = s_read_rsp_last_q; + assign mem_axi4.ruser = '0; + assign mem_axi4.rvalid = s_read_pending_q; + assign mem_axi4.bid = s_id_q; + assign mem_axi4.bresp = s_write_resp_q; + assign mem_axi4.buser = '0; + assign mem_axi4.bvalid = s_state_q == WriteResponse; + + assign s_ar_accept = mem_axi4.arvalid && mem_axi4.arready; + assign s_aw_accept = mem_axi4.awvalid && mem_axi4.awready; + assign s_w_accept = mem_axi4.wvalid && mem_axi4.wready; + assign s_read_accept = mem_axi4.rvalid && mem_axi4.rready; + assign s_b_accept = mem_axi4.bvalid && mem_axi4.bready; + assign s_read_can_issue = !s_read_pending_q || mem_axi4.rready; + assign s_read_issue = (s_state_q == ReadData) && s_read_issue_valid_q && s_read_can_issue; + assign s_expected_wlast = s_beat_q == s_len_q; + assign s_write_lane_mask = transfer_lane_mask(s_size_q, s_addr_q[1:0]); + assign s_write_strobe_legal = (mem_axi4.wstrb & ~s_write_lane_mask) == 4'd0; + assign s_write_beat_legal = s_write_strobe_legal && (mem_axi4.wlast == s_expected_wlast); + assign s_write_terminal = s_expected_wlast || mem_axi4.wlast; + + assign s_mem_read = (s_ar_accept && (s_ar_response == `AXI4_RESP_OKAY)) || s_read_issue; + assign s_mem_write = (s_state_q == WriteData) && s_w_accept && s_write_beat_legal && + (|mem_axi4.wstrb); + assign s_mem_word_addr = + (s_ar_accept && (s_ar_response == `AXI4_RESP_OKAY)) ? mem_axi4.araddr[16:2] : + s_read_issue ? s_read_issue_addr_q[16:2] : s_addr_q[16:2]; + assign s_mem_wdata = mem_axi4.wdata; + assign s_mem_wstrb = s_mem_write ? mem_axi4.wstrb : 4'd0; + + assign s_err_resp_event = + (s_b_accept && (mem_axi4.bresp != `AXI4_RESP_OKAY)) || + (s_read_accept && mem_axi4.rlast && (mem_axi4.rresp != `AXI4_RESP_OKAY)); + assign s_stall_event = (mem_axi4.arvalid && !mem_axi4.arready) || + (mem_axi4.awvalid && !mem_axi4.awready) || + (mem_axi4.wvalid && !mem_axi4.wready) || + (mem_axi4.rvalid && !mem_axi4.rready) || + (mem_axi4.bvalid && !mem_axi4.bready); + + axi4_addr_gen #( + .ADDR_WIDTH(32) + ) u_ar_addr_gen ( + .alen_i (mem_axi4.arlen), + .asize_i (mem_axi4.arsize), + .aburst_i(mem_axi4.arburst), + .addr_i (mem_axi4.araddr), + .addr_o (s_ar_next_addr) + ); + + axi4_addr_gen #( + .ADDR_WIDTH(32) + ) u_read_addr_gen ( + .alen_i (s_len_q), + .asize_i (s_size_q), + .aburst_i(s_burst_q), + .addr_i (s_read_issue_addr_q), + .addr_o (s_read_next_addr) + ); + + axi4_addr_gen #( + .ADDR_WIDTH(32) + ) u_write_addr_gen ( + .alen_i (s_len_q), + .asize_i (s_size_q), + .aburst_i(s_burst_q), + .addr_i (s_addr_q), + .addr_o (s_write_next_addr) + ); + + always_comb begin + s_state_d = s_state_q; + s_id_d = s_id_q; + s_len_d = s_len_q; + s_beat_d = s_beat_q; + s_size_d = s_size_q; + s_burst_d = s_burst_q; + s_addr_d = s_addr_q; + s_write_resp_d = s_write_resp_q; + s_read_pending_d = s_read_pending_q; + s_read_rsp_beat_d = s_read_rsp_beat_q; + s_read_rsp_last_d = s_read_rsp_last_q; + s_read_rsp_d = s_read_rsp_q; + s_read_issue_valid_d = s_read_issue_valid_q; + s_read_issue_addr_d = s_read_issue_addr_q; + s_read_issue_beat_d = s_read_issue_beat_q; + + if (s_read_accept) begin + s_read_pending_d = 1'b0; + if (s_read_rsp_last_q) begin + s_state_d = Idle; + end + end + + if (s_ar_accept) begin + s_state_d = (s_ar_response == `AXI4_RESP_OKAY) ? ReadData : ReadError; + s_id_d = mem_axi4.arid; + s_len_d = s_ar_protocol_legal ? mem_axi4.arlen : 8'd0; + s_size_d = mem_axi4.arsize; + s_burst_d = mem_axi4.arburst; + s_read_pending_d = 1'b1; + s_read_rsp_beat_d = 8'd0; + s_read_rsp_last_d = !s_ar_protocol_legal || (mem_axi4.arlen == 8'd0); + s_read_rsp_d = s_ar_response; + s_read_issue_valid_d = (s_ar_response == `AXI4_RESP_OKAY) && (mem_axi4.arlen != 8'd0); + s_read_issue_addr_d = s_ar_next_addr; + s_read_issue_beat_d = 8'd1; + end else if (s_aw_accept) begin + s_state_d = (s_aw_response == `AXI4_RESP_OKAY) ? WriteData : WriteDrain; + s_id_d = mem_axi4.awid; + s_len_d = s_aw_protocol_legal ? mem_axi4.awlen : 8'd0; + s_beat_d = 8'd0; + s_size_d = mem_axi4.awsize; + s_burst_d = mem_axi4.awburst; + s_addr_d = mem_axi4.awaddr; + s_write_resp_d = s_aw_response; + end + + if (s_read_issue) begin + s_read_pending_d = 1'b1; + s_read_rsp_beat_d = s_read_issue_beat_q; + s_read_rsp_last_d = s_read_issue_beat_q == s_len_q; + s_read_rsp_d = `AXI4_RESP_OKAY; + if (s_read_issue_beat_q == s_len_q) begin + s_read_issue_valid_d = 1'b0; + end else begin + s_read_issue_addr_d = s_read_next_addr; + s_read_issue_beat_d = s_read_issue_beat_q + 1'b1; + end + end + + if ((s_state_q == ReadError) && s_read_accept && !s_read_rsp_last_q) begin + s_read_pending_d = 1'b1; + s_read_rsp_beat_d = s_read_rsp_beat_q + 1'b1; + s_read_rsp_last_d = (s_read_rsp_beat_q + 1'b1) == s_len_q; + end + + if (s_w_accept) begin + if ((s_state_q == WriteData) && !s_write_beat_legal) begin + s_write_resp_d = `AXI4_RESP_SLAVE_ERROR; + end + if (s_write_terminal) begin + s_state_d = WriteResponse; + end else begin + s_beat_d = s_beat_q + 1'b1; + s_addr_d = s_write_next_addr; + end + end + + if (s_b_accept) begin + s_state_d = Idle; + end end + always_comb begin + s_perf_read_requests_d = s_perf_read_requests_q; + s_perf_write_requests_d = s_perf_write_requests_q; + s_perf_read_beats_d = s_perf_read_beats_q; + s_perf_write_beats_d = s_perf_write_beats_q; + s_perf_stall_cycles_d = s_perf_stall_cycles_q; + s_perf_err_resps_d = s_perf_err_resps_q; + if (perf_clear_i) begin + s_perf_read_requests_d = '0; + s_perf_write_requests_d = '0; + s_perf_read_beats_d = '0; + s_perf_write_beats_d = '0; + s_perf_stall_cycles_d = '0; + s_perf_err_resps_d = '0; + end else if (perf_enable_i) begin + if (s_ar_accept) begin + s_perf_read_requests_d = saturating_increment(s_perf_read_requests_q); + end + if (s_aw_accept) begin + s_perf_write_requests_d = saturating_increment(s_perf_write_requests_q); + end + if (s_read_accept) begin + s_perf_read_beats_d = saturating_increment(s_perf_read_beats_q); + end + if (s_w_accept) begin + s_perf_write_beats_d = saturating_increment(s_perf_write_beats_q); + end + if (s_stall_event) begin + s_perf_stall_cycles_d = saturating_increment(s_perf_stall_cycles_q); + end + if (s_err_resp_event) begin + s_perf_err_resps_d = saturating_increment(s_perf_err_resps_q); + end + end + end + + if (Present) begin : gen_memory + logic [ NumBanks-1:0] s_bank_cs; + logic [ 31:0] s_bank_rdata [NumBanks]; + logic [BankIndexWidth-1:0] s_read_bank_q; + + for (genvar bank = 0; bank < NumBanks; bank++) begin : gen_bank + assign s_bank_cs[bank] = (s_mem_read || s_mem_write) && (s_mem_word_addr[14:10] == 5'(bank)); + tc_sram_1024x32 u_ram ( + .clk_i (clk_i), + .cs_i (s_bank_cs[bank]), + .addr_i(s_mem_word_addr[9:0]), + .data_i(s_mem_wdata), + .mask_i(s_mem_wstrb), + .wren_i(s_mem_write), + .data_o(s_bank_rdata[bank]) + ); + end + + dffer #( + .DATA_WIDTH(BankIndexWidth) + ) u_read_bank_dffer ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .en_i (s_mem_read), + .dat_i (s_mem_word_addr[BankIndexWidth+9:10]), + .dat_o (s_read_bank_q) + ); + + assign s_mem_rdata = s_bank_rdata[s_read_bank_q]; + +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_bank_cs)); +`endif + end else begin : gen_no_memory + assign s_mem_rdata = {32{1'b0 & ^{s_mem_read, s_mem_write, s_mem_word_addr, + s_mem_wdata, s_mem_wstrb}}}; + end + + onchip_ram_reg #( + .Present (Present), + .CapacityKiB(CapacityKiB) + ) u_reg ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .apb4 (cfg_apb4), + .perf_read_requests_i (s_perf_read_requests_q), + .perf_write_requests_i (s_perf_write_requests_q), + .perf_read_beats_i (s_perf_read_beats_q), + .perf_write_beats_i (s_perf_write_beats_q), + .perf_stall_cycles_i (s_perf_stall_cycles_q), + .perf_error_responses_i(s_perf_err_resps_q) + ); + + dffr #( + .DATA_WIDTH(3) + ) u_state_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_state_d), + .dat_o (s_state_bits_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_id_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_id_d), + .dat_o (s_id_q) + ); + dffr #( + .DATA_WIDTH(8) + ) u_len_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_len_d), + .dat_o (s_len_q) + ); + dffr #( + .DATA_WIDTH(8) + ) u_beat_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_beat_d), + .dat_o (s_beat_q) + ); + dffr #( + .DATA_WIDTH(3) + ) u_size_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_size_d), + .dat_o (s_size_q) + ); + dffr #( + .DATA_WIDTH(2) + ) u_burst_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_burst_d), + .dat_o (s_burst_q) + ); + dffr #( + .DATA_WIDTH(32) + ) u_addr_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_addr_d), + .dat_o (s_addr_q) + ); + dffr #( + .DATA_WIDTH(2) + ) u_write_resp_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_write_resp_d), + .dat_o (s_write_resp_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_read_pending_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_read_pending_d), + .dat_o (s_read_pending_q) + ); + dffr #( + .DATA_WIDTH(8) + ) u_read_rsp_beat_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_read_rsp_beat_d), + .dat_o (s_read_rsp_beat_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_read_rsp_last_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_read_rsp_last_d), + .dat_o (s_read_rsp_last_q) + ); + dffr #( + .DATA_WIDTH(2) + ) u_read_rsp_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_read_rsp_d), + .dat_o (s_read_rsp_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_read_issue_valid_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_read_issue_valid_d), + .dat_o (s_read_issue_valid_q) + ); + dffr #( + .DATA_WIDTH(32) + ) u_read_issue_addr_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_read_issue_addr_d), + .dat_o (s_read_issue_addr_q) + ); + dffr #( + .DATA_WIDTH(8) + ) u_read_issue_beat_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_read_issue_beat_d), + .dat_o (s_read_issue_beat_q) + ); + + dffr #( + .DATA_WIDTH(32) + ) u_perf_read_requests_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_perf_read_requests_d), + .dat_o (s_perf_read_requests_q) + ); + dffr #( + .DATA_WIDTH(32) + ) u_perf_write_requests_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_perf_write_requests_d), + .dat_o (s_perf_write_requests_q) + ); + dffr #( + .DATA_WIDTH(32) + ) u_perf_read_beats_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_perf_read_beats_d), + .dat_o (s_perf_read_beats_q) + ); + dffr #( + .DATA_WIDTH(32) + ) u_perf_write_beats_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_perf_write_beats_d), + .dat_o (s_perf_write_beats_q) + ); + dffr #( + .DATA_WIDTH(32) + ) u_perf_stall_cycles_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_perf_stall_cycles_d), + .dat_o (s_perf_stall_cycles_q) + ); + dffr #( + .DATA_WIDTH(32) + ) u_perf_err_resps_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_perf_err_resps_d), + .dat_o (s_perf_err_resps_q) + ); + +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) + mem_axi4.rvalid && !mem_axi4.rready |=> + $stable( + {mem_axi4.rid, mem_axi4.rdata, mem_axi4.rresp, mem_axi4.rlast} + )); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + mem_axi4.bvalid && !mem_axi4.bready |=> + $stable( + {mem_axi4.bid, mem_axi4.bresp} + )); + assert property (@(posedge clk_i) disable iff (!rst_n_i) !(s_mem_read && s_mem_write)); +`endif + +`ifndef SYNTHESIS + initial begin + if ((CapacityKiB != 4) && (CapacityKiB != 16) && (CapacityKiB != 32) && + (CapacityKiB != 64) && (CapacityKiB != 128)) begin + $fatal(1, "onchip_ram: CapacityKiB must be 4, 16, 32, 64, or 128"); + end + end +`endif endmodule diff --git a/rtl/mini/top/onchip_ram_define.svh b/rtl/mini/top/onchip_ram_define.svh new file mode 100644 index 00000000..24bf6595 --- /dev/null +++ b/rtl/mini/top/onchip_ram_define.svh @@ -0,0 +1,39 @@ +// 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 NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +`ifndef RETROSOC_ONCHIP_RAM_DEFINE_SVH +`define RETROSOC_ONCHIP_RAM_DEFINE_SVH + +// verilog_format: off -- preserve the handwritten register ABI alignment +`define APB4_ONCHIP_RAM__IP_ID 12'h000 +`define APB4_ONCHIP_RAM__IP_VERSION 12'h004 +`define APB4_ONCHIP_RAM__CAPABILITY 12'h008 +`define APB4_ONCHIP_RAM__MEMORY_BYTES 12'h00C +`define APB4_ONCHIP_RAM__BANK_COUNT 12'h010 +`define APB4_ONCHIP_RAM__BANK_BYTES 12'h014 +`define APB4_ONCHIP_RAM__PERF_READ_REQUESTS 12'h020 +`define APB4_ONCHIP_RAM__PERF_WRITE_REQUESTS 12'h024 +`define APB4_ONCHIP_RAM__PERF_READ_BEATS 12'h028 +`define APB4_ONCHIP_RAM__PERF_WRITE_BEATS 12'h02C +`define APB4_ONCHIP_RAM__PERF_STALL_CYCLES 12'h030 +`define APB4_ONCHIP_RAM__PERF_ERROR_RESPONSES 12'h034 + +`define APB4_ONCHIP_RAM__CAP_PRESENT 0 +`define APB4_ONCHIP_RAM__CAP_NATIVE_AXI4 1 +`define APB4_ONCHIP_RAM__CAP_BYTE_WRITE 2 +`define APB4_ONCHIP_RAM__CAP_FIXED 3 +`define APB4_ONCHIP_RAM__CAP_INCR 4 +`define APB4_ONCHIP_RAM__CAP_WRAP 5 +`define APB4_ONCHIP_RAM__CAP_PERFORMANCE 6 +`define APB4_ONCHIP_RAM__CAP_MAX_BEATS_LSB 8 +`define APB4_ONCHIP_RAM__CAP_DATA_BYTES_LSB 16 +// verilog_format: on + +`endif diff --git a/rtl/mini/top/onchip_ram_reg.sv b/rtl/mini/top/onchip_ram_reg.sv new file mode 100644 index 00000000..a3304bc6 --- /dev/null +++ b/rtl/mini/top/onchip_ram_reg.sv @@ -0,0 +1,108 @@ +// 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 NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +`include "onchip_ram_define.svh" + +module onchip_ram_reg #( + parameter bit Present = 1'b1, + parameter int unsigned CapacityKiB = 128 +) ( + // verilog_format: off -- preserve the register input grouping + input logic clk_i, + input logic rst_n_i, + apb4_if.slave apb4, + input logic [31:0] perf_read_requests_i, + input logic [31:0] perf_write_requests_i, + input logic [31:0] perf_read_beats_i, + input logic [31:0] perf_write_beats_i, + input logic [31:0] perf_stall_cycles_i, + input logic [31:0] perf_error_responses_i + // verilog_format: on +); + localparam logic [31:0] IpId = 32'h5352_414D; + localparam logic [31:0] IpVersion = 32'h0001_0000; + localparam int unsigned BankBytes = 4096; + localparam int unsigned BankCount = CapacityKiB / 4; + localparam logic [31:0] Capability = + 32'(4 << `APB4_ONCHIP_RAM__CAP_DATA_BYTES_LSB) | + 32'(16 << `APB4_ONCHIP_RAM__CAP_MAX_BEATS_LSB) | + 32'h0000_007E | 32'(Present); + + logic s_req; + logic s_aligned; + logic s_offset_valid; + logic s_access_err; + logic s_ready_d; + logic s_ready_q; + logic s_resp_err_d; + logic s_resp_err_q; + logic [31:0] s_rdata_d; + logic [31:0] s_rdata_q; + + assign s_req = apb4.psel && apb4.penable && !s_ready_q; + assign s_aligned = apb4.paddr[1:0] == 2'b00; + assign s_access_err = !s_aligned || apb4.pwrite || !s_offset_valid; + assign s_ready_d = s_req; + assign s_resp_err_d = s_req && s_access_err; + assign apb4.pready = s_ready_q; + assign apb4.prdata = s_rdata_q; + assign apb4.pslverr = s_resp_err_q; + + always_comb begin + s_offset_valid = 1'b1; + s_rdata_d = 32'd0; + unique case (apb4.paddr[11:0]) + `APB4_ONCHIP_RAM__IP_ID: s_rdata_d = IpId; + `APB4_ONCHIP_RAM__IP_VERSION: s_rdata_d = IpVersion; + `APB4_ONCHIP_RAM__CAPABILITY: s_rdata_d = Capability; + `APB4_ONCHIP_RAM__MEMORY_BYTES: s_rdata_d = Present ? 32'(CapacityKiB * 1024) : 32'd0; + `APB4_ONCHIP_RAM__BANK_COUNT: s_rdata_d = Present ? 32'(BankCount) : 32'd0; + `APB4_ONCHIP_RAM__BANK_BYTES: s_rdata_d = 32'(BankBytes); + `APB4_ONCHIP_RAM__PERF_READ_REQUESTS: s_rdata_d = perf_read_requests_i; + `APB4_ONCHIP_RAM__PERF_WRITE_REQUESTS: s_rdata_d = perf_write_requests_i; + `APB4_ONCHIP_RAM__PERF_READ_BEATS: s_rdata_d = perf_read_beats_i; + `APB4_ONCHIP_RAM__PERF_WRITE_BEATS: s_rdata_d = perf_write_beats_i; + `APB4_ONCHIP_RAM__PERF_STALL_CYCLES: s_rdata_d = perf_stall_cycles_i; + `APB4_ONCHIP_RAM__PERF_ERROR_RESPONSES: s_rdata_d = perf_error_responses_i; + default: begin + s_offset_valid = 1'b0; + s_rdata_d = 32'd0; + end + endcase + end + + dffr #( + .DATA_WIDTH(1) + ) u_ready_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_ready_d), + .dat_o (s_ready_q) + ); + + dffr #( + .DATA_WIDTH(1) + ) u_resp_err_dffr ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_resp_err_d), + .dat_o (s_resp_err_q) + ); + + dffer #( + .DATA_WIDTH(32) + ) u_rdata_dffer ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .en_i (s_req), + .dat_i (s_rdata_d), + .dat_o (s_rdata_q) + ); +endmodule diff --git a/rtl/mini/top/retrosoc.sv b/rtl/mini/top/retrosoc.sv index ce0d3dcf..8b76af13 100644 --- a/rtl/mini/top/retrosoc.sv +++ b/rtl/mini/top/retrosoc.sv @@ -22,9 +22,6 @@ module retrosoc ( input logic clkdiv4_i, input logic timebase_tick_i, pll_ctrl_if.sysctrl pll_ctrl, -`ifdef HAVE_SRAM_IF - ram_if.master ram, -`endif gpio_if.soc_pad gpio, input logic uart_rx_i, output logic uart_tx_o, @@ -48,6 +45,9 @@ module retrosoc ( // Generated fabric links use the common 32-bit AXI4 contract. `include "soc_fabric_interfaces.svh" apb4_if u_sdram_cfg_if (.pclk(clk_i), .presetn(rst_n_i)); + apb4_if u_sram_cfg_if (.pclk(clk_i), .presetn(rst_n_i)); + axi4_if #(.ADDR_WIDTH(32), .DATA_WIDTH(32), .ID_WIDTH(1), .USER_WIDTH(1)) + u_sram_axi4_if (.aclk(clk_i), .aresetn(rst_n_i)); axi4_if #(.ADDR_WIDTH(32), .DATA_WIDTH(32), .ID_WIDTH(1), .USER_WIDTH(1)) u_sdram_axi4_if (.aclk(clk_i), .aresetn(rst_n_i)); axi4_if #(.ADDR_WIDTH(32), .DATA_WIDTH(32), .ID_WIDTH(1), .USER_WIDTH(1)) @@ -104,6 +104,12 @@ module retrosoc ( logic [ `SOC_IRQ_VECTOR_WIDTH-1:0] s_user_irq; logic s_rtc_wake; +`ifdef HAVE_SRAM_IF + localparam bit SramPresent = 1'b1; +`else + localparam bit SramPresent = 1'b0; +`endif + assign u_sysctrl_if.fault_access_i = s_bus_fault_access; assign u_sysctrl_if.fault_master_i = s_bus_fault_master; assign u_sysctrl_if.fault_code_i = s_bus_fault_code; @@ -162,12 +168,22 @@ core_wrapper u_core_wrapper ( .core_reset_i(u_sysctrl_if.core_reset_o) ); + onchip_ram #( + .Present (SramPresent), + .CapacityKiB(`SOC_ADDR_SRAM_SIZE >> 10) + ) u_onchip_ram ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .perf_enable_i(s_perf_en), + .perf_clear_i (s_perf_clear), + .mem_axi4 (u_sram_axi4_if), + .cfg_apb4 (u_sram_cfg_if) + ); + axi4_bus u_bus ( .clk_i (clk_i), .rst_n_i (rst_n_i), -`ifdef HAVE_SRAM_IF - .ram (ram), -`endif + .sram_axi4 (u_sram_axi4_if), .user_bus_enable_i (u_sysctrl_if.user_bus_enable_o), .user_bus_idle_o (u_sysctrl_if.user_bus_idle_i), `include "soc_bus_fabric.svh" @@ -224,6 +240,7 @@ core_wrapper u_core_wrapper ( .sysctrl (u_sysctrl_if), .pll_ctrl (pll_ctrl), .sdram_cfg (u_sdram_cfg_if), + .sram_cfg (u_sram_cfg_if), .dvp (u_dvp_if), .sdio0 (u_sdio0_if), .sdio1 (sdio1), diff --git a/rtl/mini/top/retrosoc_asic.sv b/rtl/mini/top/retrosoc_asic.sv index a844dfc5..87585fe3 100644 --- a/rtl/mini/top/retrosoc_asic.sv +++ b/rtl/mini/top/retrosoc_asic.sv @@ -82,10 +82,6 @@ module retrosoc_asic ( (* keep = "true" *)logic s_test_pass; (* keep = "true" *)logic [7:0] s_test_code; -`ifdef HAVE_SRAM_IF - ram_if u_ram_if (); -`endif - gpio_if u_gpio_if (); xpi_if u_xpi_if (); sdram_if u_sdram_if (); @@ -115,13 +111,6 @@ rcu #( .timebase_tick_o(s_timebase_tick) ); -`ifdef HAVE_SRAM_IF - onchip_ram u_onchip_ram ( - .clk_i(s_sys_clk), - .ram (u_ram_if) - ); -`endif - retrosoc u_retrosoc ( .clk_i (s_sys_clk), .rst_n_i (s_sys_rst_n), @@ -131,9 +120,6 @@ rcu #( .clkdiv4_i (s_sys_clkdiv4), .timebase_tick_i(s_timebase_tick), .pll_ctrl (u_pll_ctrl_if), -`ifdef HAVE_SRAM_IF - .ram (u_ram_if), -`endif .gpio (u_gpio_if), .uart_rx_i (s_uart0_rx), .uart_tx_o (s_uart0_tx), diff --git a/rtl/mini/top/retrosoc_core.sv b/rtl/mini/top/retrosoc_core.sv new file mode 100644 index 00000000..f21f37d7 --- /dev/null +++ b/rtl/mini/top/retrosoc_core.sv @@ -0,0 +1,100 @@ +// Copyright (c) 2023-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 NON-INFRINGEMENT, +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +`include "gpio_define.svh" +`include "soc_clock_config.svh" +`include "user_extensions.svh" + +// Technology-independent hardening top. The generated bindings retain the +// exact logical pin contract of retrosoc_asic but intentionally omit pad cells. +module retrosoc_core ( + `include "retrosoc_core_ports.svh" +); + logic s_ext_clk; + logic s_aud_clk; + logic s_aud_clk_buf; + logic s_sys_clkdiv4; + logic s_timebase_tick; +`ifdef HAVE_PLL + logic s_xtal_io; +`endif + logic s_sys_clk; + logic s_ext_rst_n; + logic s_sys_rst_n; + logic s_aud_rst_n; + logic s_wdg_reset_req; + logic s_jtag_tck; + logic s_jtag_tms; + logic s_jtag_tdi; + logic s_jtag_trst_n; + logic s_jtag_tdo; + logic s_uart0_rx; + logic s_uart0_tx; + logic s_usb2_ulpi_clk; + (* keep = "true" *)logic s_test_done; + (* keep = "true" *)logic s_test_pass; + (* keep = "true" *)logic [7:0] s_test_code; + + gpio_if u_gpio_if (); + xpi_if u_xpi_if (); + sdram_if u_sdram_if (); + sdio_if u_sdio1_if (); + usb2_ulpi_if u_usb2_ulpi_if (); + pll_ctrl_if u_pll_ctrl_if (); + + `include "retrosoc_core_bindings.svh" + +rcu #( + .ExtClkHz (`SOC_EXT_CLK_HZ), + .ClintTimebaseHz(`SOC_CLINT_TIMEBASE_HZ) + ) u_rcu ( + .ext_clk_i (s_ext_clk), + .aud_clk_i (s_aud_clk), + .ext_rst_n_i (s_ext_rst_n), + .wdg_reset_req_i(s_wdg_reset_req), +`ifdef HAVE_PLL + .xtal_clk_i (s_xtal_io), +`endif + .pll_ctrl (u_pll_ctrl_if), + .sys_clk_o (s_sys_clk), + .sys_rst_n_o (s_sys_rst_n), + .aud_clk_o (s_aud_clk_buf), + .aud_rst_n_o (s_aud_rst_n), + .sys_clkdiv4_o (s_sys_clkdiv4), + .timebase_tick_o(s_timebase_tick) + ); + + retrosoc u_retrosoc ( + .clk_i (s_sys_clk), + .rst_n_i (s_sys_rst_n), + .clk_aud_i (s_aud_clk_buf), + .rst_aud_n_i (s_aud_rst_n), + .clk_ulpi_i (s_usb2_ulpi_clk), + .clkdiv4_i (s_sys_clkdiv4), + .timebase_tick_i(s_timebase_tick), + .pll_ctrl (u_pll_ctrl_if), + .gpio (u_gpio_if), + .uart_rx_i (s_uart0_rx), + .uart_tx_o (s_uart0_tx), + .xpi (u_xpi_if), + .sdram (u_sdram_if), + .sdio1 (u_sdio1_if), + .usb2 (u_usb2_ulpi_if), + .jtag_tck_i (s_jtag_tck), + .jtag_tms_i (s_jtag_tms), + .jtag_tdi_i (s_jtag_tdi), + .jtag_trst_n_i (s_jtag_trst_n), + .jtag_tdo_o (s_jtag_tdo), + .wdg_reset_req_o(s_wdg_reset_req), + .test_done_o (s_test_done), + .test_pass_o (s_test_pass), + .test_code_o (s_test_code) + ); + +endmodule diff --git a/rtl/rtl_style_audit.json b/rtl/rtl_style_audit.json index 57c2169d..9192a818 100644 --- a/rtl/rtl_style_audit.json +++ b/rtl/rtl_style_audit.json @@ -285,11 +285,14 @@ "mgmt_core_wrapper.sv", "mgmt_debug_reset.sv", "mgmt_debug_wrapper.sv", + "onchip_ram_define.svh", + "onchip_ram_reg.sv", "onchip_ram.sv", "pll_rcu_controller.sv", "rcu.sv", "retrosoc.sv", "retrosoc_asic.sv", + "retrosoc_core.sv", "rib2apb.sv", "rib2axi4.sv", "rib2ram.sv", @@ -360,10 +363,11 @@ }, { "paths": [ - "rtl/mini/top/onchip_ram.sv" + "rtl/mini/top/onchip_ram.sv", + "rtl/mini/top/onchip_ram_reg.sv" ], - "result": "technology boundary retained", - "rationale": "PDK SRAM cell and pin names remain compatible with the technology library." + "result": "reviewed AXI and technology-memory boundary", + "rationale": "AXI and APB state uses Common registers; the synchronous bank array retains the stable technology wrapper and PDK SRAM pin boundary." }, { "paths": [ diff --git a/rtl/tech/tc_sram.sv b/rtl/tech/tc_sram.sv index fdd0ce6f..a74afd10 100644 --- a/rtl/tech/tc_sram.sv +++ b/rtl/tech/tc_sram.sv @@ -38,17 +38,15 @@ module tc_sram_1024x32 ( end `elsif PDK_IHP130 `ifdef HAVE_SRAM_MACRO - logic [63:0] s_rd_data_mem; - assign data_o = s_rd_data_mem[31:0]; - RM_IHPSG13_1P_1024x64_c2_bm_bist u_mem ( + RM_IHPSG13_1P_1024x32_c2_bm_bist u_mem ( .A_CLK (clk_i), .A_ADDR (addr_i), - .A_BM ({32'h0, {8{mask_i[3]}}, {8{mask_i[2]}}, {8{mask_i[1]}}, {8{mask_i[0]}}}), + .A_BM ({{8{mask_i[3]}}, {8{mask_i[2]}}, {8{mask_i[1]}}, {8{mask_i[0]}}}), .A_MEN (cs_i), .A_WEN (wren_i), .A_REN (~wren_i), - .A_DIN ({32'h0, data_i[31:0]}), - .A_DOUT (s_rd_data_mem), + .A_DIN (data_i), + .A_DOUT (data_o), .A_DLY (1'b1), .A_BIST_CLK ('0), .A_BIST_EN ('0), diff --git a/tests/rtl/ics55_onchip_sram_stub.sv b/tests/rtl/ics55_onchip_sram_stub.sv new file mode 100644 index 00000000..1ed4e8c2 --- /dev/null +++ b/tests/rtl/ics55_onchip_sram_stub.sv @@ -0,0 +1,26 @@ +// Functional test model for the site-supplied ICS55 1024 x 32 SRAM macro. +module S55NLLG1PH_X256Y4D32_BW ( + input logic [31:0] D, + input logic CLK, + input logic CEN, + input logic WEN, + input logic [31:0] BWEN, + input logic [ 9:0] A, + output logic [31:0] Q +); + logic [31:0] s_memory[0:1023]; + + always_ff @(posedge CLK) begin + if (!CEN) begin + if (!WEN) begin + for (int bit_index = 0; bit_index < 32; bit_index++) begin + if (!BWEN[bit_index]) begin + s_memory[A][bit_index] <= D[bit_index]; + end + end + end else begin + Q <= s_memory[A]; + end + end + end +endmodule diff --git a/tests/rtl/onchip_ram_absent_tb.sv b/tests/rtl/onchip_ram_absent_tb.sv new file mode 100644 index 00000000..53b7eabe --- /dev/null +++ b/tests/rtl/onchip_ram_absent_tb.sv @@ -0,0 +1,146 @@ +`timescale 1ns / 1ps + +`include "axi4_define.svh" +`include "onchip_ram_define.svh" + +module onchip_ram_absent_tb; + logic clk_i = 1'b0; + logic rst_n_i = 1'b0; + + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) mem_axi4 ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + apb4_if cfg_apb4 ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + + always #5 clk_i = ~clk_i; + + onchip_ram #( + .Present (1'b0), + .CapacityKiB(128) + ) u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .perf_enable_i(1'b1), + .perf_clear_i (1'b0), + .mem_axi4 (mem_axi4), + .cfg_apb4 (cfg_apb4) + ); + + task automatic init_interfaces; + begin + mem_axi4.awid = '0; + mem_axi4.awaddr = '0; + mem_axi4.awlen = '0; + mem_axi4.awsize = `AXI4_BURST_SIZE_4BYTES; + mem_axi4.awburst = `AXI4_BURST_TYPE_INCR; + mem_axi4.awlock = `AXI4_LOCK_NORM; + mem_axi4.awcache = `AXI4_CACHE_NO_BUF; + mem_axi4.awprot = `AXI4_PROT_DATA; + mem_axi4.awqos = `AXI4_QOS_NORMAL; + mem_axi4.awregion = `AXI4_REGION_NORMAL; + mem_axi4.awuser = '0; + mem_axi4.awvalid = 1'b0; + mem_axi4.wdata = '0; + mem_axi4.wstrb = '0; + mem_axi4.wlast = 1'b0; + mem_axi4.wuser = '0; + mem_axi4.wvalid = 1'b0; + mem_axi4.bready = 1'b0; + mem_axi4.arid = '0; + mem_axi4.araddr = '0; + mem_axi4.arlen = '0; + mem_axi4.arsize = `AXI4_BURST_SIZE_4BYTES; + mem_axi4.arburst = `AXI4_BURST_TYPE_INCR; + mem_axi4.arlock = `AXI4_LOCK_NORM; + mem_axi4.arcache = `AXI4_CACHE_NO_BUF; + mem_axi4.arprot = `AXI4_PROT_DATA; + mem_axi4.arqos = `AXI4_QOS_NORMAL; + mem_axi4.arregion = `AXI4_REGION_NORMAL; + mem_axi4.aruser = '0; + mem_axi4.arvalid = 1'b0; + mem_axi4.rready = 1'b0; + cfg_apb4.paddr = '0; + cfg_apb4.pprot = '0; + cfg_apb4.psel = 1'b0; + cfg_apb4.penable = 1'b0; + cfg_apb4.pwrite = 1'b0; + cfg_apb4.pwdata = '0; + cfg_apb4.pstrb = '0; + end + endtask + + task automatic apb_read(input logic [11:0] offset, input logic [31:0] expected); + begin + @(negedge clk_i); + cfg_apb4.paddr = {20'd0, offset}; + cfg_apb4.psel = 1'b1; + cfg_apb4.penable = 1'b0; + @(negedge clk_i); + cfg_apb4.penable = 1'b1; + do @(posedge clk_i); while (!cfg_apb4.pready); + if (cfg_apb4.pslverr || (cfg_apb4.prdata !== expected)) begin + $fatal(1, "absent SRAM APB read mismatch offset=%03x", offset); + end + @(negedge clk_i); + cfg_apb4.psel = 1'b0; + cfg_apb4.penable = 1'b0; + end + endtask + + initial begin + init_interfaces(); + repeat (3) @(posedge clk_i); + @(negedge clk_i); + rst_n_i = 1'b1; + + apb_read(`APB4_ONCHIP_RAM__CAPABILITY, 32'h0004_107E); + apb_read(`APB4_ONCHIP_RAM__MEMORY_BYTES, 32'd0); + apb_read(`APB4_ONCHIP_RAM__BANK_COUNT, 32'd0); + + @(negedge clk_i); + mem_axi4.araddr = `SOC_ADDR_SRAM_BASE; + mem_axi4.arvalid = 1'b1; + do @(posedge clk_i); while (!mem_axi4.arready); + @(negedge clk_i); + mem_axi4.arvalid = 1'b0; + mem_axi4.rready = 1'b1; + do @(posedge clk_i); while (!mem_axi4.rvalid); + if ((mem_axi4.rresp != `AXI4_RESP_DECODE_ERROR) || !mem_axi4.rlast) begin + $fatal(1, "absent SRAM read did not return DECERR"); + end + @(negedge clk_i); + mem_axi4.rready = 1'b0; + + @(negedge clk_i); + mem_axi4.awaddr = `SOC_ADDR_SRAM_BASE; + mem_axi4.awvalid = 1'b1; + do @(posedge clk_i); while (!mem_axi4.awready); + @(negedge clk_i); + mem_axi4.awvalid = 1'b0; + mem_axi4.wdata = 32'hDEAD_BEEF; + mem_axi4.wstrb = 4'hF; + mem_axi4.wlast = 1'b1; + mem_axi4.wvalid = 1'b1; + do @(posedge clk_i); while (!mem_axi4.wready); + @(negedge clk_i); + mem_axi4.wvalid = 1'b0; + mem_axi4.wlast = 1'b0; + mem_axi4.bready = 1'b1; + do @(posedge clk_i); while (!mem_axi4.bvalid); + if (mem_axi4.bresp != `AXI4_RESP_DECODE_ERROR) begin + $fatal(1, "absent SRAM write did not return DECERR"); + end + + $display("absent on-chip SRAM test passed"); + $finish; + end +endmodule diff --git a/tests/rtl/onchip_ram_perf_tb.sv b/tests/rtl/onchip_ram_perf_tb.sv new file mode 100644 index 00000000..52127262 --- /dev/null +++ b/tests/rtl/onchip_ram_perf_tb.sv @@ -0,0 +1,192 @@ +`timescale 1ns / 1ps + +`include "axi4_define.svh" + +module onchip_ram_perf_tb; + logic clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic [31:0] s_legacy_memory [0:1023]; + integer s_native_cycles; + integer s_legacy_cycles; + + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) native_axi4 ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) legacy_axi4 ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + apb4_if cfg_apb4 ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + ram_if legacy_ram (); + + always #5 clk_i = ~clk_i; + + always_ff @(posedge clk_i) begin + legacy_ram.rdata <= s_legacy_memory[legacy_ram.addr[9:0]]; + for (int lane = 0; lane < 4; lane++) begin + if (legacy_ram.wstrb[lane]) begin + s_legacy_memory[legacy_ram.addr[9:0]][lane*8+:8] <= legacy_ram.wdata[lane*8+:8]; + end + end + end + + onchip_ram #( + .Present (1'b1), + .CapacityKiB(4) + ) u_native ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .perf_enable_i(1'b0), + .perf_clear_i (1'b0), + .mem_axi4 (native_axi4), + .cfg_apb4 (cfg_apb4) + ); + axi42ram u_legacy ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .axi4 (legacy_axi4), + .ram (legacy_ram) + ); + + task automatic init_axi4; + begin + native_axi4.awid = '0; + native_axi4.awaddr = '0; + native_axi4.awlen = '0; + native_axi4.awsize = `AXI4_BURST_SIZE_4BYTES; + native_axi4.awburst = `AXI4_BURST_TYPE_INCR; + native_axi4.awlock = 1'b0; + native_axi4.awcache = '0; + native_axi4.awprot = '0; + native_axi4.awqos = '0; + native_axi4.awregion = '0; + native_axi4.awuser = '0; + native_axi4.awvalid = 1'b0; + native_axi4.wdata = '0; + native_axi4.wstrb = '0; + native_axi4.wlast = 1'b0; + native_axi4.wuser = '0; + native_axi4.wvalid = 1'b0; + native_axi4.bready = 1'b0; + native_axi4.arid = '0; + native_axi4.araddr = '0; + native_axi4.arlen = '0; + native_axi4.arsize = `AXI4_BURST_SIZE_4BYTES; + native_axi4.arburst = `AXI4_BURST_TYPE_INCR; + native_axi4.arlock = 1'b0; + native_axi4.arcache = '0; + native_axi4.arprot = '0; + native_axi4.arqos = '0; + native_axi4.arregion = '0; + native_axi4.aruser = '0; + native_axi4.arvalid = 1'b0; + native_axi4.rready = 1'b1; + legacy_axi4.awid = '0; + legacy_axi4.awaddr = '0; + legacy_axi4.awlen = '0; + legacy_axi4.awsize = `AXI4_BURST_SIZE_4BYTES; + legacy_axi4.awburst = `AXI4_BURST_TYPE_INCR; + legacy_axi4.awlock = 1'b0; + legacy_axi4.awcache = '0; + legacy_axi4.awprot = '0; + legacy_axi4.awqos = '0; + legacy_axi4.awregion = '0; + legacy_axi4.awuser = '0; + legacy_axi4.awvalid = 1'b0; + legacy_axi4.wdata = '0; + legacy_axi4.wstrb = '0; + legacy_axi4.wlast = 1'b0; + legacy_axi4.wuser = '0; + legacy_axi4.wvalid = 1'b0; + legacy_axi4.bready = 1'b0; + legacy_axi4.arid = '0; + legacy_axi4.araddr = '0; + legacy_axi4.arlen = '0; + legacy_axi4.arsize = `AXI4_BURST_SIZE_4BYTES; + legacy_axi4.arburst = `AXI4_BURST_TYPE_INCR; + legacy_axi4.arlock = 1'b0; + legacy_axi4.arcache = '0; + legacy_axi4.arprot = '0; + legacy_axi4.arqos = '0; + legacy_axi4.arregion = '0; + legacy_axi4.aruser = '0; + legacy_axi4.arvalid = 1'b0; + legacy_axi4.rready = 1'b1; + cfg_apb4.paddr = '0; + cfg_apb4.pprot = '0; + cfg_apb4.psel = 1'b0; + cfg_apb4.penable = 1'b0; + cfg_apb4.pwrite = 1'b0; + cfg_apb4.pwdata = '0; + cfg_apb4.pstrb = '0; + end + endtask + + task automatic measure_native; + integer beats; + begin + @(negedge clk_i); + native_axi4.araddr = `SOC_ADDR_SRAM_BASE; + native_axi4.arlen = 8'd15; + native_axi4.arvalid = 1'b1; + do @(posedge clk_i); while (!native_axi4.arready); + @(negedge clk_i); + native_axi4.arvalid = 1'b0; + s_native_cycles = 0; + beats = 0; + while (beats < 16) begin + @(posedge clk_i); + s_native_cycles = s_native_cycles + 1; + if (native_axi4.rvalid) beats = beats + 1; + end + end + endtask + + task automatic measure_legacy; + integer beats; + begin + @(negedge clk_i); + legacy_axi4.araddr = `SOC_ADDR_SRAM_BASE; + legacy_axi4.arlen = 8'd15; + legacy_axi4.arvalid = 1'b1; + do @(posedge clk_i); while (!legacy_axi4.arready); + @(negedge clk_i); + legacy_axi4.arvalid = 1'b0; + s_legacy_cycles = 0; + beats = 0; + while (beats < 16) begin + @(posedge clk_i); + s_legacy_cycles = s_legacy_cycles + 1; + if (legacy_axi4.rvalid) beats = beats + 1; + end + end + endtask + + initial begin + init_axi4(); + repeat (3) @(posedge clk_i); + @(negedge clk_i); + rst_n_i = 1'b1; + measure_native(); + measure_legacy(); + $display("on-chip SRAM burst cycles native=%0d legacy=%0d", s_native_cycles, s_legacy_cycles); + if ((s_native_cycles != 16) || (s_legacy_cycles < (s_native_cycles + 2))) begin + $fatal(1, "native SRAM did not reach one beat per cycle or remove bridge overhead"); + end + $finish; + end +endmodule diff --git a/tests/rtl/onchip_ram_tb.sv b/tests/rtl/onchip_ram_tb.sv new file mode 100644 index 00000000..f941eb58 --- /dev/null +++ b/tests/rtl/onchip_ram_tb.sv @@ -0,0 +1,387 @@ +`timescale 1ns / 1ps + +`include "axi4_define.svh" +`include "mmap_define.svh" +`include "onchip_ram_define.svh" + +module onchip_ram_tb #( + parameter int unsigned CapacityKiB = 128 +); + localparam logic [31:0] SramBase = `SOC_ADDR_SRAM_BASE; + localparam logic [31:0] SramEnd = `SOC_ADDR_SRAM_END; + + logic clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic perf_enable_i = 1'b0; + logic perf_clear_i = 1'b0; + logic [31:0] s_apb_read_data; + logic [31:0] s_expected; + + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) mem_axi4 ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + apb4_if cfg_apb4 ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + + always #5 clk_i = ~clk_i; + + onchip_ram #( + .Present (1'b1), + .CapacityKiB(CapacityKiB) + ) u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .perf_enable_i(perf_enable_i), + .perf_clear_i (perf_clear_i), + .mem_axi4 (mem_axi4), + .cfg_apb4 (cfg_apb4) + ); + + task automatic init_interfaces; + begin + mem_axi4.awid = '0; + mem_axi4.awaddr = '0; + mem_axi4.awlen = '0; + mem_axi4.awsize = `AXI4_BURST_SIZE_4BYTES; + mem_axi4.awburst = `AXI4_BURST_TYPE_INCR; + mem_axi4.awlock = `AXI4_LOCK_NORM; + mem_axi4.awcache = `AXI4_CACHE_NO_BUF; + mem_axi4.awprot = `AXI4_PROT_DATA; + mem_axi4.awqos = `AXI4_QOS_NORMAL; + mem_axi4.awregion = `AXI4_REGION_NORMAL; + mem_axi4.awuser = '0; + mem_axi4.awvalid = 1'b0; + mem_axi4.wdata = '0; + mem_axi4.wstrb = '0; + mem_axi4.wlast = 1'b0; + mem_axi4.wuser = '0; + mem_axi4.wvalid = 1'b0; + mem_axi4.bready = 1'b0; + mem_axi4.arid = '0; + mem_axi4.araddr = '0; + mem_axi4.arlen = '0; + mem_axi4.arsize = `AXI4_BURST_SIZE_4BYTES; + mem_axi4.arburst = `AXI4_BURST_TYPE_INCR; + mem_axi4.arlock = `AXI4_LOCK_NORM; + mem_axi4.arcache = `AXI4_CACHE_NO_BUF; + mem_axi4.arprot = `AXI4_PROT_DATA; + mem_axi4.arqos = `AXI4_QOS_NORMAL; + mem_axi4.arregion = `AXI4_REGION_NORMAL; + mem_axi4.aruser = '0; + mem_axi4.arvalid = 1'b0; + mem_axi4.rready = 1'b0; + cfg_apb4.paddr = '0; + cfg_apb4.pprot = '0; + cfg_apb4.psel = 1'b0; + cfg_apb4.penable = 1'b0; + cfg_apb4.pwrite = 1'b0; + cfg_apb4.pwdata = '0; + cfg_apb4.pstrb = '0; + end + endtask + + task automatic write_burst(input logic [31:0] address, input logic [7:0] length, + input logic [2:0] size, input logic [1:0] burst, + input logic [31:0] seed, input logic [1:0] response); + integer previous_cycle; + integer cycle_count; + begin + @(negedge clk_i); + mem_axi4.awaddr = address; + mem_axi4.awlen = length; + mem_axi4.awsize = size; + mem_axi4.awburst = burst; + mem_axi4.awvalid = 1'b1; + do @(posedge clk_i); while (!mem_axi4.awready); + @(negedge clk_i); + mem_axi4.awvalid = 1'b0; + previous_cycle = -1; + cycle_count = 0; + for (int beat = 0; beat <= int'(length); beat++) begin + mem_axi4.wdata = seed + beat; + mem_axi4.wstrb = 4'hF; + mem_axi4.wlast = beat == int'(length); + mem_axi4.wvalid = 1'b1; + do begin + @(posedge clk_i); + cycle_count = cycle_count + 1; + end while (!mem_axi4.wready); + if ((previous_cycle >= 0) && (cycle_count != previous_cycle + 1)) begin + $fatal(1, "write burst inserted a bubble at beat %0d", beat); + end + previous_cycle = cycle_count; + @(negedge clk_i); + end + mem_axi4.wvalid = 1'b0; + mem_axi4.wlast = 1'b0; + mem_axi4.bready = 1'b1; + do @(posedge clk_i); while (!mem_axi4.bvalid); + if (mem_axi4.bresp != response) begin + $fatal(1, "write response=%0d expected=%0d", mem_axi4.bresp, response); + end + @(negedge clk_i); + mem_axi4.bready = 1'b0; + end + endtask + + task automatic read_burst(input logic [31:0] address, input logic [7:0] length, + input logic [2:0] size, input logic [1:0] burst, + input logic [31:0] seed, input logic [1:0] response, + input logic check_data, input logic add_backpressure); + logic [31:0] held_data; + logic [ 1:0] held_resp; + logic held_last; + integer previous_cycle; + integer cycle_count; + begin + @(negedge clk_i); + mem_axi4.araddr = address; + mem_axi4.arlen = length; + mem_axi4.arsize = size; + mem_axi4.arburst = burst; + mem_axi4.arvalid = 1'b1; + do @(posedge clk_i); while (!mem_axi4.arready); + @(negedge clk_i); + mem_axi4.arvalid = 1'b0; + mem_axi4.rready = 1'b1; + previous_cycle = -1; + cycle_count = 0; + for (int beat = 0; beat <= int'(length); beat++) begin + if (add_backpressure && (beat == 1)) begin + @(negedge clk_i); + mem_axi4.rready = 1'b0; + while (!mem_axi4.rvalid) @(negedge clk_i); + held_data = mem_axi4.rdata; + held_resp = mem_axi4.rresp; + held_last = mem_axi4.rlast; + repeat (3) begin + @(negedge clk_i); + if (!mem_axi4.rvalid || (mem_axi4.rdata !== held_data) || + (mem_axi4.rresp !== held_resp) || (mem_axi4.rlast !== held_last)) begin + $fatal(1, "read response changed under backpressure"); + end + end + mem_axi4.rready = 1'b1; + previous_cycle = -1; + end + do begin + @(posedge clk_i); + cycle_count = cycle_count + 1; + end while (!mem_axi4.rvalid); + if (mem_axi4.rresp != response || mem_axi4.rlast != (beat == int'(length))) begin + $fatal(1, "read response mismatch at beat %0d", beat); + end + s_expected = seed + beat; + if (check_data && (mem_axi4.rdata !== s_expected)) begin + $fatal(1, "read data=%08x expected=%08x at beat %0d", mem_axi4.rdata, s_expected, beat); + end + if ((previous_cycle >= 0) && (cycle_count != previous_cycle + 1)) begin + $fatal(1, "read burst inserted a bubble at beat %0d", beat); + end + previous_cycle = cycle_count; + end + @(negedge clk_i); + mem_axi4.rready = 1'b0; + end + endtask + + task automatic write_single(input logic [31:0] address, input logic [2:0] size, + input logic [31:0] data, input logic [3:0] strobe, + input logic [1:0] response); + begin + @(negedge clk_i); + mem_axi4.awaddr = address; + mem_axi4.awlen = 8'd0; + mem_axi4.awsize = size; + mem_axi4.awburst = `AXI4_BURST_TYPE_INCR; + mem_axi4.awvalid = 1'b1; + do @(posedge clk_i); while (!mem_axi4.awready); + @(negedge clk_i); + mem_axi4.awvalid = 1'b0; + mem_axi4.wdata = data; + mem_axi4.wstrb = strobe; + mem_axi4.wlast = 1'b1; + mem_axi4.wvalid = 1'b1; + do @(posedge clk_i); while (!mem_axi4.wready); + @(negedge clk_i); + mem_axi4.wvalid = 1'b0; + mem_axi4.wlast = 1'b0; + mem_axi4.bready = 1'b1; + do @(posedge clk_i); while (!mem_axi4.bvalid); + if (mem_axi4.bresp != response) begin + $fatal(1, "single write response=%0d expected=%0d", mem_axi4.bresp, response); + end + @(negedge clk_i); + mem_axi4.bready = 1'b0; + end + endtask + + task automatic write_missing_last(input logic [31:0] address, input logic [31:0] data); + begin + @(negedge clk_i); + mem_axi4.awaddr = address; + mem_axi4.awlen = 8'd0; + mem_axi4.awsize = `AXI4_BURST_SIZE_4BYTES; + mem_axi4.awburst = `AXI4_BURST_TYPE_INCR; + mem_axi4.awvalid = 1'b1; + do @(posedge clk_i); while (!mem_axi4.awready); + @(negedge clk_i); + mem_axi4.awvalid = 1'b0; + mem_axi4.wdata = data; + mem_axi4.wstrb = 4'hF; + mem_axi4.wlast = 1'b0; + mem_axi4.wvalid = 1'b1; + do @(posedge clk_i); while (!mem_axi4.wready); + @(negedge clk_i); + mem_axi4.wvalid = 1'b0; + mem_axi4.bready = 1'b1; + do @(posedge clk_i); while (!mem_axi4.bvalid); + if (mem_axi4.bresp != `AXI4_RESP_SLAVE_ERROR) begin + $fatal(1, "missing WLAST did not return SLVERR"); + end + @(negedge clk_i); + mem_axi4.bready = 1'b0; + end + endtask + + task automatic read_single(input logic [31:0] address, input logic [2:0] size, + input logic [31:0] expected, input logic [1:0] response); + begin + read_burst(address, 8'd0, size, `AXI4_BURST_TYPE_INCR, expected, response, + response == `AXI4_RESP_OKAY, 1'b0); + end + endtask + + task automatic apb_read(input logic [11:0] offset, input logic [31:0] expected); + begin + @(negedge clk_i); + cfg_apb4.paddr = {20'd0, offset}; + cfg_apb4.psel = 1'b1; + cfg_apb4.penable = 1'b0; + @(negedge clk_i); + cfg_apb4.penable = 1'b1; + do @(posedge clk_i); while (!cfg_apb4.pready); + s_apb_read_data = cfg_apb4.prdata; + if (cfg_apb4.pslverr || (s_apb_read_data !== expected)) begin + $fatal(1, "APB offset=%03x data=%08x expected=%08x error=%0b", offset, s_apb_read_data, + expected, cfg_apb4.pslverr); + end + @(negedge clk_i); + cfg_apb4.psel = 1'b0; + cfg_apb4.penable = 1'b0; + end + endtask + + task automatic apb_error(input logic [11:0] offset, input logic write_access); + begin + @(negedge clk_i); + cfg_apb4.paddr = {20'd0, offset}; + cfg_apb4.psel = 1'b1; + cfg_apb4.penable = 1'b0; + cfg_apb4.pwrite = write_access; + cfg_apb4.pwdata = 32'hFFFF_FFFF; + cfg_apb4.pstrb = 4'hF; + @(negedge clk_i); + cfg_apb4.penable = 1'b1; + do @(posedge clk_i); while (!cfg_apb4.pready); + if (!cfg_apb4.pslverr) begin + $fatal(1, "APB illegal access offset=%03x write=%0b did not return PSLVERR", offset, + write_access); + end + @(negedge clk_i); + cfg_apb4.psel = 1'b0; + cfg_apb4.penable = 1'b0; + cfg_apb4.pwrite = 1'b0; + cfg_apb4.pwdata = '0; + cfg_apb4.pstrb = '0; + end + endtask + + initial begin + init_interfaces(); + repeat (3) @(posedge clk_i); + @(negedge clk_i); + rst_n_i = 1'b1; + perf_enable_i = 1'b1; + + apb_read(`APB4_ONCHIP_RAM__IP_ID, 32'h5352_414D); + apb_read(`APB4_ONCHIP_RAM__IP_VERSION, 32'h0001_0000); + apb_read(`APB4_ONCHIP_RAM__MEMORY_BYTES, 32'(CapacityKiB * 1024)); + apb_read(`APB4_ONCHIP_RAM__BANK_COUNT, 32'(CapacityKiB / 4)); + apb_read(`APB4_ONCHIP_RAM__BANK_BYTES, 32'd4096); + apb_error(`APB4_ONCHIP_RAM__IP_ID, 1'b1); + apb_error(12'h018, 1'b0); + apb_error(12'h001, 1'b0); + + write_burst(SramEnd - 32'd63, 8'd15, `AXI4_BURST_SIZE_4BYTES, `AXI4_BURST_TYPE_INCR, + 32'hA500_0000, `AXI4_RESP_OKAY); + read_burst(SramEnd - 32'd63, 8'd15, `AXI4_BURST_SIZE_4BYTES, `AXI4_BURST_TYPE_INCR, + 32'hA500_0000, `AXI4_RESP_OKAY, 1'b1, 1'b1); + + for (int bank = 0; bank < (CapacityKiB / 4); bank++) begin + write_single(SramBase + 32'(bank * 4096), `AXI4_BURST_SIZE_4BYTES, 32'hB000_0000 + bank, 4'hF, + `AXI4_RESP_OKAY); + read_single(SramBase + 32'(bank * 4096), `AXI4_BURST_SIZE_4BYTES, 32'hB000_0000 + bank, + `AXI4_RESP_OKAY); + end + + write_single(SramBase + 32'h100, `AXI4_BURST_SIZE_4BYTES, 32'h1122_3344, 4'hF, `AXI4_RESP_OKAY); + write_single(SramBase + 32'h101, `AXI4_BURST_SIZE_1BYTE, 32'h0000_AA00, 4'b0010, + `AXI4_RESP_OKAY); + write_single(SramBase + 32'h102, `AXI4_BURST_SIZE_2BYTES, 32'hBEEF_0000, 4'b1100, + `AXI4_RESP_OKAY); + read_single(SramBase + 32'h100, `AXI4_BURST_SIZE_4BYTES, 32'hBEEF_AA44, `AXI4_RESP_OKAY); + + write_burst(SramBase + 32'h200, 8'd3, `AXI4_BURST_SIZE_4BYTES, `AXI4_BURST_TYPE_FIXED, + 32'hCAFE_0000, `AXI4_RESP_OKAY); + read_single(SramBase + 32'h200, `AXI4_BURST_SIZE_4BYTES, 32'hCAFE_0003, `AXI4_RESP_OKAY); + + write_burst(SramBase + 32'h248, 8'd3, `AXI4_BURST_SIZE_4BYTES, `AXI4_BURST_TYPE_WRAP, + 32'hFACE_0000, `AXI4_RESP_OKAY); + read_burst(SramBase + 32'h248, 8'd3, `AXI4_BURST_SIZE_4BYTES, `AXI4_BURST_TYPE_WRAP, + 32'hFACE_0000, `AXI4_RESP_OKAY, 1'b1, 1'b0); + + read_single(SramEnd + 1'b1, `AXI4_BURST_SIZE_4BYTES, 32'd0, `AXI4_RESP_DECODE_ERROR); + read_single(SramBase + 32'd2, `AXI4_BURST_SIZE_4BYTES, 32'd0, `AXI4_RESP_SLAVE_ERROR); + write_single(SramBase + 32'h100, `AXI4_BURST_SIZE_4BYTES, 32'hFFFF_FFFF, 4'b0000, + `AXI4_RESP_OKAY); + write_missing_last(SramBase + 32'h100, 32'hDEAD_BEEF); + write_single(SramBase + 32'h100, `AXI4_BURST_SIZE_1BYTE, 32'h0000_5500, 4'b0010, + `AXI4_RESP_SLAVE_ERROR); + read_single(SramBase + 32'h100, `AXI4_BURST_SIZE_4BYTES, 32'hBEEF_AA44, `AXI4_RESP_OKAY); + + @(negedge clk_i); + mem_axi4.araddr = SramBase; + mem_axi4.awaddr = SramBase + 32'd4; + mem_axi4.arvalid = 1'b1; + mem_axi4.awvalid = 1'b1; + @(posedge clk_i); + if (!mem_axi4.arready || mem_axi4.awready) begin + $fatal(1, "simultaneous AR/AW did not select the read channel"); + end + @(negedge clk_i); + mem_axi4.arvalid = 1'b0; + mem_axi4.awvalid = 1'b0; + mem_axi4.rready = 1'b1; + do @(posedge clk_i); while (!mem_axi4.rvalid); + @(negedge clk_i); + mem_axi4.rready = 1'b0; + + apb_read(`APB4_ONCHIP_RAM__PERF_READ_REQUESTS, 32'(CapacityKiB / 4 + 8)); + if (u_dut.s_perf_write_requests_q == 32'd0 || u_dut.s_perf_read_beats_q == 32'd0 || + u_dut.s_perf_write_beats_q == 32'd0 || u_dut.s_perf_err_resps_q != 32'd4) begin + $fatal(1, "performance counters did not record the directed traffic"); + end + + $display("on-chip SRAM AXI4 test passed capacity_kib=%0d", CapacityKiB); + $finish; + end +endmodule diff --git a/tests/rtl/tc_sram_1024x32_tb.sv b/tests/rtl/tc_sram_1024x32_tb.sv new file mode 100644 index 00000000..de53c8b3 --- /dev/null +++ b/tests/rtl/tc_sram_1024x32_tb.sv @@ -0,0 +1,63 @@ +`timescale 1ns / 1ps + +module tc_sram_1024x32_tb; + logic clk_i = 1'b0; + logic cs_i = 1'b0; + logic [ 9:0] addr_i = '0; + logic [31:0] data_i = '0; + logic [ 3:0] mask_i = '0; + logic wren_i = 1'b0; + logic [31:0] data_o; + + always #5 clk_i = ~clk_i; + + tc_sram_1024x32 u_dut ( + .clk_i (clk_i), + .cs_i (cs_i), + .addr_i(addr_i), + .data_i(data_i), + .mask_i(mask_i), + .wren_i(wren_i), + .data_o(data_o) + ); + + task automatic write_word(input logic [9:0] address, input logic [31:0] data, + input logic [3:0] mask); + begin + @(negedge clk_i); + cs_i = 1'b1; + addr_i = address; + data_i = data; + mask_i = mask; + wren_i = 1'b1; + @(posedge clk_i); + @(negedge clk_i); + cs_i = 1'b0; + wren_i = 1'b0; + mask_i = '0; + end + endtask + + task automatic read_word(input logic [9:0] address, input logic [31:0] expected); + begin + @(negedge clk_i); + cs_i = 1'b1; + addr_i = address; + wren_i = 1'b0; + @(posedge clk_i); + @(negedge clk_i); + if (data_o !== expected) begin + $fatal(1, "1024x32 SRAM data=%08x expected=%08x", data_o, expected); + end + cs_i = 1'b0; + end + endtask + + initial begin + write_word(10'h155, 32'h1122_3344, 4'hF); + write_word(10'h155, 32'hAABB_CCDD, 4'b0101); + read_word(10'h155, 32'h11BB_33DD); + $display("1024x32 SRAM wrapper test passed"); + $finish; + end +endmodule diff --git a/tests/test_memory_map.py b/tests/test_memory_map.py index 6e2d5772..98a75cf7 100644 --- a/tests/test_memory_map.py +++ b/tests/test_memory_map.py @@ -16,7 +16,7 @@ USER_EXTENSIONS = ROOT / "rtl/mini/integration/user_extensions.json" USER_GENERATOR = ROOT / "rtl/mini/integration/generate_user_extensions.py" -def generate(output_dir: Path, *, sram: str = "NO") -> None: +def generate(output_dir: Path, *, sram: str = "NO", sram_size_kib: int = 128) -> None: subprocess.run( [ sys.executable, @@ -27,6 +27,8 @@ def generate(output_dir: Path, *, sram: str = "NO") -> None: str(output_dir), "--have-sram-if", sram, + "--sram-size-kib", + str(sram_size_kib), ], check=True, ) @@ -66,6 +68,7 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None assert "RS_SOC_APB4_SDIO0_BASE UINT32_C(0x1000F000)" in header assert "RS_SOC_APB4_SDIO1_BASE UINT32_C(0x10015000)" in header assert "RS_SOC_APB4_USB2_BASE UINT32_C(0x10016000)" in header + assert "RS_SOC_APB4_SRAM_BASE UINT32_C(0x10017000)" in header assert "RS_SOC_NMI_" not in header assert "RS_SOC_OPIPSRAM_BASE UINT32_C(0x48000000)" in header assert "RS_SOC_HAS_SRAM 1U" in header @@ -101,6 +104,22 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None assert "OPIPSRAM (wxa!ri) : ORIGIN = 0x48000000, LENGTH = 0x08000000" in linker +def test_sram_capacity_selects_one_consistent_hardware_software_window(tmp_path: Path) -> None: + for size_kib in (4, 16, 32, 64, 128): + output = tmp_path / str(size_kib) + generate(output, sram="YES", sram_size_kib=size_kib) + size = size_kib * 1024 + end = 0x30000000 + size - 1 + rtl = (output / "rtl/mmap_define.svh").read_text(encoding="utf-8") + header = (output / "include/retrosoc/generated/memory_map.h").read_text(encoding="utf-8") + linker = (output / "linker/memory_regions.ld").read_text(encoding="utf-8") + + assert f"`define SOC_ADDR_SRAM_SIZE 32'h{size:08X}" in rtl + assert f"`define SOC_ADDR_SRAM_END 32'h{end:08X}" in rtl + assert f"#define RS_SOC_SRAM_SIZE UINT32_C(0x{size:08X})" in header + assert f"SRAM (wxa!ri) : ORIGIN = 0x30000000, LENGTH = 0x{size:08X}" in linker + + def test_user_ip_is_always_emitted_for_the_fixed_platform(tmp_path: Path) -> None: generate(tmp_path) diff --git a/tests/test_mgmt_core_spec.py b/tests/test_mgmt_core_spec.py index 9b2b87b7..af959171 100644 --- a/tests/test_mgmt_core_spec.py +++ b/tests/test_mgmt_core_spec.py @@ -28,12 +28,16 @@ def test_booter_prints_the_fixed_hazard3_specification() -> None: assert "Other: Zifencei_Zilsd_Xh3BextM_Xh3IRQ" in booter assert "Not present: U-mode PMP Zcb_Zclsd_Zcmp_Xh3PMPM_Xh3Power" in booter assert "IRQ: 30 external + software + timer; Xh3IRQ, 4 priority levels" in booter - assert "one shared instruction/data AHB5 manager port" in booter - assert "single-beat AXI4 normal access" in booter - assert "no burst, lock, or global-exclusive forwarding; no system A atomics" in booter + assert "AHB5 manager -> AHB-Lite/AXI4 fabric" in booter + assert "Single-beat access; no burst or exclusive transactions" in booter assert "RS_SOC_MGMT_JTAG_IDCODE" in booter - assert "halted-hart memory commands; system bus access disabled" in booter - assert "2 exact execute-address hardware breakpoints; no watchpoints" in booter + assert "RISC-V JTAG DTM/DM, IDCODE 0x%08x" in booter + assert "One hart: halt/resume, 2 breakpoints; no system-bus access" in booter + assert "AHB-Lite NSEQ subset" not in booter + assert "external cache/coherency handshake" not in booter + assert "TCK/TMS/TDI/TRST_n/TDO" not in booter + assert "instruction injection" not in booter + assert "exact execute-address hardware breakpoints" not in booter for parameter in ( ".EXTENSION_A (1)", diff --git a/tests/test_onchip_ram.py b/tests/test_onchip_ram.py new file mode 100644 index 00000000..21d1a999 --- /dev/null +++ b/tests/test_onchip_ram.py @@ -0,0 +1,303 @@ +"""Directed verification for the configurable native-AXI4 on-chip SRAM.""" + +from __future__ import annotations + +import os +import shutil +import subprocess +import sys +from pathlib import Path + +import pytest + + +ROOT = Path(__file__).resolve().parents[1] +CAPACITIES_KIB = (4, 16, 32, 64, 128) + + +@pytest.mark.parametrize("capacity_kib", CAPACITIES_KIB) +def test_onchip_ram_axi4_capacity_protocol_and_performance( + tmp_path: Path, capacity_kib: int +) -> None: + verilator = shutil.which("verilator") + if verilator is None: + return + + generated = 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(generated), + "--have-sram-if", + "YES", + "--sram-size-kib", + str(capacity_kib), + ], + check=True, + ) + output = tmp_path / f"onchip_ram_{capacity_kib}" + ccache_tmp = tmp_path / "ccache" + ccache_tmp.mkdir() + subprocess.run( + [ + verilator, + "--binary", + "--timing", + "-Wno-fatal", + "--top-module", + "onchip_ram_tb", + f"-GCapacityKiB={capacity_kib}", + "+define+PDK_BEHAV", + "+define+SV_ASSRT_DISABLE", + "-I" + str(generated / "rtl"), + "-I" + str(ROOT / "rtl/mini/top"), + "-I" + str(ROOT / "rtl/managed/clusterip/common/rtl"), + "-I" + str(ROOT / "rtl/managed/clusterip/common/rtl/interface"), + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_if.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/apb4_if.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_addr_gen.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"), + str(ROOT / "rtl/tech/tc_sram.sv"), + str(ROOT / "rtl/mini/top/onchip_ram_reg.sv"), + str(ROOT / "rtl/mini/top/onchip_ram.sv"), + str(ROOT / "tests/rtl/onchip_ram_tb.sv"), + "-Mdir", + str(tmp_path / "obj"), + "-o", + str(output), + ], + check=True, + text=True, + capture_output=True, + env={ + **os.environ, + "CCACHE_DIR": str(ccache_tmp), + "CCACHE_TEMPDIR": str(ccache_tmp), + }, + ) + result = subprocess.run([output], check=True, text=True, capture_output=True) + assert f"on-chip SRAM AXI4 test passed capacity_kib={capacity_kib}" in result.stdout + + +def test_absent_onchip_ram_reports_capability_and_decode_errors(tmp_path: Path) -> None: + verilator = shutil.which("verilator") + if verilator is None: + return + + generated = 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(generated), + "--have-sram-if", + "NO", + ], + check=True, + ) + output = tmp_path / "onchip_ram_absent" + ccache_tmp = tmp_path / "ccache-absent" + ccache_tmp.mkdir() + subprocess.run( + [ + verilator, + "--binary", + "--timing", + "-Wno-fatal", + "--top-module", + "onchip_ram_absent_tb", + "+define+SV_ASSRT_DISABLE", + "-I" + str(generated / "rtl"), + "-I" + str(ROOT / "rtl/mini/top"), + "-I" + str(ROOT / "rtl/managed/clusterip/common/rtl"), + "-I" + str(ROOT / "rtl/managed/clusterip/common/rtl/interface"), + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_if.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/apb4_if.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_addr_gen.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"), + str(ROOT / "rtl/tech/tc_sram.sv"), + str(ROOT / "rtl/mini/top/onchip_ram_reg.sv"), + str(ROOT / "rtl/mini/top/onchip_ram.sv"), + str(ROOT / "tests/rtl/onchip_ram_absent_tb.sv"), + "-Mdir", + str(tmp_path / "obj-absent"), + "-o", + str(output), + ], + check=True, + text=True, + capture_output=True, + env={ + **os.environ, + "CCACHE_DIR": str(ccache_tmp), + "CCACHE_TEMPDIR": str(ccache_tmp), + }, + ) + result = subprocess.run([output], check=True, text=True, capture_output=True) + assert "absent on-chip SRAM test passed" in result.stdout + + +def test_ihp130_wrapper_uses_functional_1024x32_macro(tmp_path: Path) -> None: + verilator = shutil.which("verilator") + if verilator is None: + return + + output = tmp_path / "tc_sram_ihp130" + ccache_tmp = tmp_path / "ccache-ihp130" + ccache_tmp.mkdir() + subprocess.run( + [ + verilator, + "--binary", + "--timing", + "-Wno-fatal", + "--top-module", + "tc_sram_1024x32_tb", + "+define+PDK_IHP130", + "+define+HAVE_SRAM_MACRO", + "+define+FUNCTIONAL", + "+define+SYNTHESIS", + str( + ROOT / "physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/" + "RM_IHPSG13_1P_core_behavioral_bm_bist.v" + ), + str( + ROOT / "physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/" + "RM_IHPSG13_1P_1024x32_c2_bm_bist.v" + ), + str(ROOT / "rtl/tech/tc_sram.sv"), + str(ROOT / "tests/rtl/tc_sram_1024x32_tb.sv"), + "-Mdir", + str(tmp_path / "obj-ihp130"), + "-o", + str(output), + ], + check=True, + text=True, + capture_output=True, + env={ + **os.environ, + "CCACHE_DIR": str(ccache_tmp), + "CCACHE_TEMPDIR": str(ccache_tmp), + }, + ) + result = subprocess.run([output], check=True, text=True, capture_output=True) + assert "1024x32 SRAM wrapper test passed" in result.stdout + + wrapper = (ROOT / "rtl/tech/tc_sram.sv").read_text(encoding="utf-8") + assert "RM_IHPSG13_1P_1024x32_c2_bm_bist" in wrapper + assert "RM_IHPSG13_1P_1024x64_c2_bm_bist" not in wrapper + + +def test_ics55_wrapper_preserves_active_low_controls_and_byte_masks(tmp_path: Path) -> None: + verilator = shutil.which("verilator") + if verilator is None: + return + + output = tmp_path / "tc_sram_ics55" + ccache_tmp = tmp_path / "ccache-ics55" + ccache_tmp.mkdir() + subprocess.run( + [ + verilator, + "--binary", + "--timing", + "-Wno-fatal", + "--top-module", + "tc_sram_1024x32_tb", + "+define+PDK_ICS55", + "+define+HAVE_SRAM_MACRO", + str(ROOT / "tests/rtl/ics55_onchip_sram_stub.sv"), + str(ROOT / "rtl/tech/tc_sram.sv"), + str(ROOT / "tests/rtl/tc_sram_1024x32_tb.sv"), + "-Mdir", + str(tmp_path / "obj-ics55"), + "-o", + str(output), + ], + check=True, + text=True, + capture_output=True, + env={ + **os.environ, + "CCACHE_DIR": str(ccache_tmp), + "CCACHE_TEMPDIR": str(ccache_tmp), + }, + ) + result = subprocess.run([output], check=True, text=True, capture_output=True) + assert "1024x32 SRAM wrapper test passed" in result.stdout + + +def test_native_axi4_burst_reaches_peak_and_removes_bridge_overhead(tmp_path: Path) -> None: + verilator = shutil.which("verilator") + if verilator is None: + return + + generated = 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(generated), + "--have-sram-if", + "YES", + "--sram-size-kib", + "4", + ], + check=True, + ) + output = tmp_path / "onchip_ram_perf" + ccache_tmp = tmp_path / "ccache-perf" + ccache_tmp.mkdir() + subprocess.run( + [ + verilator, + "--binary", + "--timing", + "-Wno-fatal", + "--top-module", + "onchip_ram_perf_tb", + "+define+PDK_BEHAV", + "+define+SV_ASSRT_DISABLE", + "-I" + str(generated / "rtl"), + "-I" + str(ROOT / "rtl/mini/top"), + "-I" + str(ROOT / "rtl/managed/clusterip/common/rtl"), + "-I" + str(ROOT / "rtl/managed/clusterip/common/rtl/interface"), + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_if.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/apb4_if.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/ram_if.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_addr_gen.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/utils/xchecker.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/utils/spill_register.sv"), + str(ROOT / "rtl/tech/tc_sram.sv"), + str(ROOT / "rtl/mini/top/axi42ram.sv"), + str(ROOT / "rtl/mini/top/onchip_ram_reg.sv"), + str(ROOT / "rtl/mini/top/onchip_ram.sv"), + str(ROOT / "tests/rtl/onchip_ram_perf_tb.sv"), + "-Mdir", + str(tmp_path / "obj-perf"), + "-o", + str(output), + ], + check=True, + text=True, + capture_output=True, + env={ + **os.environ, + "CCACHE_DIR": str(ccache_tmp), + "CCACHE_TEMPDIR": str(ccache_tmp), + }, + ) + result = subprocess.run([output], check=True, text=True, capture_output=True) + assert "on-chip SRAM burst cycles native=16 legacy=18" in result.stdout diff --git a/tests/test_onchip_sram_register_parity.py b/tests/test_onchip_sram_register_parity.py new file mode 100644 index 00000000..63d59d52 --- /dev/null +++ b/tests/test_onchip_sram_register_parity.py @@ -0,0 +1,35 @@ +"""Keep the handwritten on-chip SRAM RTL and SDK register ABI synchronized.""" + +from __future__ import annotations + +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +RTL_DEFINE = ROOT / "rtl/mini/top/onchip_ram_define.svh" +C_HEADER = ROOT / "crt/include/retrosoc/hal/onchip_sram_regs.h" + + +def _rtl_offsets() -> dict[str, int]: + return { + name: int(value, 16) + for name, value in re.findall( + r"`define\s+APB4_ONCHIP_RAM__([A-Z0-9_]+)\s+12'h([0-9A-Fa-f]+)", + RTL_DEFINE.read_text(encoding="utf-8"), + ) + } + + +def _c_offsets() -> dict[str, int]: + return { + name: int(value, 16) + for name, value in re.findall( + r"#define\s+RS_ONCHIP_SRAM_REG_([A-Z0-9_]+)\s+UINT32_C\(0x([0-9A-Fa-f]+)\)", + C_HEADER.read_text(encoding="utf-8"), + ) + } + + +def test_onchip_sram_register_offsets_match_rtl() -> None: + assert _c_offsets() == _rtl_offsets()