Merge branch 'feat/tiny' into dev

# Conflicts:
#	Makefile
#	README.md
#	publications/README.md
#	rtl/mk/software.mk
#	rtl/rtl_style_audit.json
This commit is contained in:
maksyuki committed 2026-09-27 09:09:53 +08:00
commit a45ebf9688
253 files changed
+15235 -501

No files matched your search

+4 -2
View File
@@ -22,7 +22,7 @@ truth wins.
records approved Required-rule deviations. records approved Required-rule deviations.
- [crt/README.md](crt/README.md) defines the freestanding RISC-V runtime and - [crt/README.md](crt/README.md) defines the freestanding RISC-V runtime and
SDK layout; [app/README.md](app/README.md) defines application composition. SDK layout; [app/README.md](app/README.md) defines application composition.
- `Makefile`, `rtl/mini/mk/software.mk`, committed `configs/*.mk`, and the - `Makefile`, `rtl/mk/software.mk`, committed `configs/*.mk`, and the
GitHub workflows define the active build and regression behavior. GitHub workflows define the active build and regression behavior.
- `quality/embedded_c_policy.json`, `quality/warnings/`, and - `quality/embedded_c_policy.json`, `quality/warnings/`, and
`quality/metrics/policy.json` define the active quality policy. `quality/metrics/policy.json` define the active quality policy.
@@ -77,7 +77,9 @@ README that states its ownership, source of truth, and validation expectations.
`bringup`, `debug`, and `shell`; each profile is declared in `bringup`, `debug`, and `shell`; each profile is declared in
`app/apps/<name>/app.mk`. The `debug` application is an RTL debug-transport `app/apps/<name>/app.mk`. The `debug` application is an RTL debug-transport
acceptance image, not a user-facing firmware profile. acceptance image, not a user-facing firmware profile.
- Do not add new dependencies on `crt/inc`, retired `tiny` names, or legacy - Tiny is a distinct wired MCU product under `rtl/tiny`; `SOC=TINY` selects its
IHP130 profile and AXI4/APB4 integration.
- Do not add new dependencies on `crt/inc`, retired SDK `tiny` names, or legacy
`rs_*.h`/`tiny*.h` include paths. `rs_*.h`/`tiny*.h` include paths.
- Treat `app/coremark/coremark-main`, `app/fatfs/ff16`, `app/lvgl/lvgl-main`, - Treat `app/coremark/coremark-main`, `app/fatfs/ff16`, `app/lvgl/lvgl-main`,
and managed ports as third-party or managed code. Update them through their and managed ports as third-party or managed code. Update them through their
+40 -14
View File
@@ -29,7 +29,7 @@ $(error HAVE_DEBUG has been removed; the Hazard3 Debug Module is always enabled)
endif endif
SOC ?= MINI SOC ?= MINI
MINI_MODE ?= PRODUCT MINI_MODE ?= $(if $(filter MINI,$(SOC)),PRODUCT,NONE)
SIMU ?= VCS SIMU ?= VCS
SYNTH ?= NONE SYNTH ?= NONE
SYNTH_RECIPE ?= balanced SYNTH_RECIPE ?= balanced
@@ -44,7 +44,7 @@ SRAM_SIZE_KIB ?= $(if $(filter ICS55,$(PDK)),128,32)
PDK_BEHAV ?= NO PDK_BEHAV ?= NO
HAVE_SVA ?= NO HAVE_SVA ?= NO
APU_ENABLE_P7 ?= NO APU_ENABLE_P7 ?= NO
HAVE_HP ?= YES HAVE_HP ?= $(if $(filter TINY,$(SOC)),NO,YES)
HP_CONFIG ?= rv64imafdc_zicbom_max HP_CONFIG ?= rv64imafdc_zicbom_max
BUILD_RELEASE ?= NO BUILD_RELEASE ?= NO
JTAG_IDCODE ?= DEADBEEF JTAG_IDCODE ?= DEADBEEF
@@ -65,7 +65,7 @@ RTL_SIM_TIMEOUT ?= -1
SIM_FIRMWARE_NAME ?= $(FIRMWARE_NAME) SIM_FIRMWARE_NAME ?= $(FIRMWARE_NAME)
SIM_SUCCESS_MARKER ?= SIM_TEST_PASS SIM_SUCCESS_MARKER ?= SIM_TEST_PASS
RTL_PATH := $(ROOT_PATH)/rtl/mini RTL_PATH := $(ROOT_PATH)/rtl/$(shell printf '%s' '$(SOC)' | tr '[:upper:]' '[:lower:]')
RTL_TOP ?= retrosoc_tb RTL_TOP ?= retrosoc_tb
@@ -218,8 +218,8 @@ else
FLOW_FILELIST_DIR := $(SIM_BUILD_ROOT)/filelists FLOW_FILELIST_DIR := $(SIM_BUILD_ROOT)/filelists
endif endif
VALID_SOC := MINI VALID_SOC := MINI TINY
VALID_MINI_MODE := PRODUCT MPW VALID_MINI_MODE := PRODUCT MPW NONE
VALID_SIMU := VCS VERILATOR IVERILOG VALID_SIMU := VCS VERILATOR IVERILOG
VALID_SYNTH := NONE YOSYS VALID_SYNTH := NONE YOSYS
VALID_SYNTH_RECIPE := balanced area speed VALID_SYNTH_RECIPE := balanced area speed
@@ -282,11 +282,18 @@ $(error NPU_P5_ACCEPTANCE and NPU_P6_ACCEPTANCE are mutually exclusive)
endif endif
endif endif
ifeq ($(SOC),MINI)
ifeq ($(MINI_MODE),NONE)
$(error SOC=MINI requires MINI_MODE=PRODUCT or MPW)
endif
ifeq ($(MINI_MODE),PRODUCT) ifeq ($(MINI_MODE),PRODUCT)
ifneq ($(HAVE_HP),YES) ifneq ($(HAVE_HP),YES)
$(error MINI_MODE=PRODUCT requires HAVE_HP=YES) $(error MINI_MODE=PRODUCT requires HAVE_HP=YES)
endif endif
endif endif
else
include $(ROOT_PATH)/rtl/tiny/mk/config.mk
endif
MISSING_LOCAL_RTL_FILES := $(filter-out $(wildcard $(LOCAL_RTL_FILES)),$(LOCAL_RTL_FILES)) MISSING_LOCAL_RTL_FILES := $(filter-out $(wildcard $(LOCAL_RTL_FILES)),$(LOCAL_RTL_FILES))
ifneq ($(strip $(MISSING_LOCAL_RTL_FILES)),) ifneq ($(strip $(MISSING_LOCAL_RTL_FILES)),)
@@ -395,7 +402,9 @@ ifeq ($(APU_ENABLE_P7), YES)
DEF_LIST += +define+APU_ENABLE_P7 DEF_LIST += +define+APU_ENABLE_P7
endif endif
ifeq ($(MINI_MODE), PRODUCT) ifeq ($(SOC), TINY)
DEF_LIST += +define+RETROSOC_SOC__TINY
else ifeq ($(MINI_MODE), PRODUCT)
DEF_LIST += +define+MINI_PRODUCT DEF_LIST += +define+MINI_PRODUCT
else else
DEF_LIST += +define+MINI_MPW DEF_LIST += +define+MINI_MPW
@@ -405,8 +414,10 @@ ifeq ($(SYNTH), YOSYS)
DEF_LIST += +define+SYNTHESIS DEF_LIST += +define+SYNTHESIS
endif endif
include rtl/mini/Makefile include $(RTL_PATH)/Makefile
ifeq ($(SOC),MINI)
include rtl/mini/mk/formal.mk include rtl/mini/mk/formal.mk
endif
ifeq ($(SIMU),IVERILOG) ifeq ($(SIMU),IVERILOG)
PERF_LOG ?= $(IVERILOG_BEHV_DIR)/sim.log PERF_LOG ?= $(IVERILOG_BEHV_DIR)/sim.log
@@ -416,15 +427,21 @@ endif
ifeq ($(SYNTH), YOSYS) ifeq ($(SYNTH), YOSYS)
include physical/smoke/syn/yosys/yosys.mk include physical/smoke/syn/yosys/yosys.mk
ifeq ($(SOC),MINI)
include physical/smoke/syn/yosys/ga2d_block.mk include physical/smoke/syn/yosys/ga2d_block.mk
include physical/smoke/syn/yosys/apu_block.mk include physical/smoke/syn/yosys/apu_block.mk
endif endif
endif
ifeq ($(STA), OPENSTA) ifeq ($(STA), OPENSTA)
include physical/smoke/sta/opensta/opensta.mk include physical/smoke/sta/opensta/opensta.mk
endif endif
include physical/librelane/Makefile ifeq ($(SOC),TINY)
include physical/librelane/tiny/Makefile
else
include physical/librelane/mini/Makefile
endif
include physical/ecc/Makefile include physical/ecc/Makefile
.PHONY: crypto-p0-constants crypto-p0-rtl crypto-p0-baseline crypto-p0-report .PHONY: crypto-p0-constants crypto-p0-rtl crypto-p0-baseline crypto-p0-report
@@ -486,7 +503,7 @@ crypto-p2-report: manifest
apu-block-filelist apu-block-synth apu-block-sta apu-block-report apu-block-evidence apu-block-clean \ apu-block-filelist apu-block-synth apu-block-sta apu-block-report apu-block-evidence apu-block-clean \
pin-map check-pin-map soc-topology check-soc-topology user-extensions check-user-extensions \ 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 \ 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-onchip-ram formal-opipsram formal-dma formal-apu formal-apu-kws formal-apu-p9 formal-gateway-a 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-apu formal-apu-kws formal-apu-p9 formal-gateway-a formal-sdio formal-clean formal-doctor \
rtl-style-check-all rtl-readiness-check rtl-readiness-check-all vexii-generate rtl-style-check-all rtl-readiness-check rtl-readiness-check-all vexii-generate
.NOTPARALLEL: setup .NOTPARALLEL: setup
@@ -619,7 +636,7 @@ config:
doctor: doctor:
@python3 $(ROOT_PATH)/scripts/doctor.py \ @python3 $(ROOT_PATH)/scripts/doctor.py \
--root $(ROOT_PATH) --simu $(SIMU) --synth $(SYNTH) --sta $(STA) \ --root $(ROOT_PATH) --simu $(SIMU) --synth $(SYNTH) --sta $(STA) \
--pdk $(PDK) --have-sram-macro $(HAVE_SRAM_MACRO) \ --pdk $(PDK) --soc $(SOC) --have-sram-macro $(HAVE_SRAM_MACRO) \
--formal $(FORMAL) --lock $(LOCK_FILE) --formal $(FORMAL) --lock $(LOCK_FILE)
benchmark-report: firmware benchmark-report: firmware
@@ -641,7 +658,13 @@ hp-performance-check:
--lp $(LP_COREMARK_REPORT) --hp $(HP_COREMARK_REPORT) \ --lp $(LP_COREMARK_REPORT) --hp $(HP_COREMARK_REPORT) \
--minimum-ratio $(HP_PERF_MIN_RATIO) --output $(META_DIR)/lp-hp-performance.json --minimum-ratio $(HP_PERF_MIN_RATIO) --output $(META_DIR)/lp-hp-performance.json
ifeq ($(SOC),TINY)
setup: setup-tiny
setup-tiny:
python3 $(ROOT_PATH)/scripts/setup_tiny.py
else
setup: setup-mpw setup-vexiiriscv setup-clusterip setup-ip setup-pdk setup-app setup: setup-mpw setup-vexiiriscv setup-clusterip setup-ip setup-pdk setup-app
endif
setup-regression: setup-regression:
$(MAKE) CONFIG=configs/ci/ihp130.mk setup $(MAKE) CONFIG=configs/ci/ihp130.mk setup
@@ -1158,19 +1181,22 @@ commercial-package: $(MPW_VARIANT_DEP) $(FILELIST_STAMP) manifest
--variant-root $(VARIANT_ROOT) --output-dir $(VARIANT_ROOT)/commercial/input --variant-root $(VARIANT_ROOT) --output-dir $(VARIANT_ROOT)/commercial/input
regress-smoke: regress-smoke:
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite smoke --pdk IHP130 python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite smoke --pdk IHP130 $(if $(filter TINY,$(SOC)),--soc TINY)
regress-rtl: regress-rtl:
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite rtl --pdk IHP130 python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite rtl --pdk IHP130 $(if $(filter TINY,$(SOC)),--soc TINY)
regress-pr: regress-pr:
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk IHP130 $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only) python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk IHP130 $(if $(filter TINY,$(SOC)),--soc TINY) $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only)
ifneq ($(SOC),TINY)
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk GF180 $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only) python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk GF180 $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only)
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk SKY130 $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only) python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk SKY130 $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only)
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk ICS55 $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only) python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk ICS55 $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only)
endif
regress-nightly: regress-nightly:
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite nightly python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite nightly $(if $(filter TINY,$(SOC)),--soc TINY)
sim-asm: asm sim-asm: asm
$(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim $(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim
+47
View File
@@ -66,6 +66,19 @@ under the [Mulan Permissive Software License, Version 2](LICENSE).
## Repository Layout ## Repository Layout
**Mini and Tiny have independent integration flows.** Mini combines a
low-power Hazard3 management hart and a VexiiRiscv application hart; its separate
MPW profile retains selectable-core compatibility. [Tiny](docs/ip/tiny-soc.md)
is a wired MCU with one Hazard3 RV32IMC hart, 128 KiB SRAM, AXI4/APB4 and no
wireless IP. Tiny first targets IHP130; [Std and Pro](docs/soc-family-positioning.md)
remain roadmap products. Qualification is determined by each profile's retained
verification and physical reports.
The [Tiny Gen1 datasheet](publications/datasheets/tiny/README.md) provides the
independent product, register, software and evidence reference using the
established publication layout. Its build does not require Mini's HP or
multimedia inputs.
| Path | Contents | | Path | Contents |
| --- | --- | | --- | --- |
| [`rtl/`](rtl) | SoC RTL, CPU integration, peripherals, interfaces, testbenches, and technology wrappers. | | [`rtl/`](rtl) | SoC RTL, CPU integration, peripherals, interfaces, testbenches, and technology wrappers. |
@@ -209,6 +222,40 @@ Select the interactive shell application without editing source files:
make CONFIG=configs/ci/ihp130-shell.mk firmware make CONFIG=configs/ci/ihp130-shell.mk firmware
``` ```
### Build and simulate Tiny
```sh
make CONFIG=configs/ci/ihp130-tiny.mk setup
make CONFIG=configs/ci/ihp130-tiny.mk doctor
make CONFIG=configs/ci/ihp130-tiny.mk firmware sim
make CONFIG=configs/ci/ihp130-tiny.mk SIMU=IVERILOG firmware sim
```
Tiny uses its own source lists and product maps under `rtl/tiny`. Its firmware
loads into on-chip SRAM without PSRAM/SDRAM initialization. The first release
uses a fixed 24 MHz clock and disables atomic instructions. Wireless, external
RAM, HP cores, accelerators and advanced low-power modes remain deferred.
## Configurations and common flows
Start from a [committed profile](configs/README.md). These are selected entry
points, not the complete profile or application inventory.
| Profile | Purpose |
| --- | --- |
| [IHP130 Tiny](configs/ci/ihp130-tiny.mk) | Single-hart wired MCU, 128 KiB SRAM, pin-level Flash boot and automated acceptance. |
| [IHP130 Mini](configs/ci/ihp130.mk) | Manual bring-up with 32 KiB macro-backed SRAM. |
| [GF180](configs/ci/gf180.mk) / [SKY130](configs/ci/sky130.mk) | Alternative PDK profiles with 32 KiB macro-backed SRAM. |
| [ICS55](configs/ci/ics55.mk) | Regression-compatible profile with SRAM and PLL disabled. |
| [Interactive shell](configs/ci/ihp130-shell.mk) | Shell firmware with CSR support enabled. |
| [Hazard3 debug](configs/ci/ihp130-debug.mk) | JTAG acceptance using Verilator, OpenOCD, and GDB. |
| [HP Linux](configs/ci/ihp130-hp.mk) | HP image/bundle flow; outside the supported PR matrix. |
| [APU LP/HP](configs/ci/ihp130-apu.mk) | Audio and KWS evidence flow with ownership handoff to HP. |
| [CoreMark](configs/benchmark/ihp130-hazard3-coremark.mk) | Fixed LP SRAM benchmark with the product HP core present. |
| [Mini MPW](configs/cluster/mini-mpw.mk) | Legacy C0-C3/user-IP compatibility, separate from the product ABI. |
After the corresponding setup, use these common commands:
## Common Flows ## Common Flows
All commands use a committed profile. `make config` prints the effective configuration and All commands use a committed profile. `make config` prints the effective configuration and
+7
View File
@@ -146,3 +146,10 @@ make regress-nightly
See the [repository README](../README.md) for prerequisites, supported See the [repository README](../README.md) for prerequisites, supported
configurations, and build artifact locations. configurations, and build artifact locations.
For `SOC=TINY`, `bringup` and `ci_smoke` use the MCU acceptance composition. It
boots through XPI into SRAM, checks CPU C/A behavior and bus exceptions, DMA
(including UART requests), interrupts, RTC, GPIO, UART1 and I2C error completion,
then verifies watchdog reset by completing after the next NOR boot. It uses
SYSCTRL terminal status. The small `asm/tiny_boot.S` image separately qualifies
the synthesized SRAM/pad path; it is not a replacement for the full RTL test.
+3
View File
@@ -0,0 +1,3 @@
APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny.c
APP_CFLAGS += -Os
APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S
+31
View File
@@ -0,0 +1,31 @@
/* Copyright (c) 2026 Yuchi Miao; SPDX-License-Identifier: MulanPSL-2.0 */
.section .text
.balign 4
.global rs_mcu_compressed_probe
.type rs_mcu_compressed_probe,@function
rs_mcu_compressed_probe:
.option push
.option rvc
c.li a0, 21
c.slli a0, 1
c.jr ra
.option pop
.size rs_mcu_compressed_probe, .-rs_mcu_compressed_probe
.balign 4
.global rs_mcu_atomic_probe
.type rs_mcu_atomic_probe,@function
rs_mcu_atomic_probe:
/* lr.w a0,(a0): must trap because the Tiny A extension is disabled. */
.word 0x1005252f
ret
.size rs_mcu_atomic_probe, .-rs_mcu_atomic_probe
.balign 4
.global rs_mcu_unmapped_probe
.type rs_mcu_unmapped_probe,@function
rs_mcu_unmapped_probe:
li a0, 0x38000000
lw a0, 0(a0)
ret
.size rs_mcu_unmapped_probe, .-rs_mcu_unmapped_probe
+10
View File
@@ -0,0 +1,10 @@
#ifndef RETROSOC_MCU_ACCEPTANCE_H
#define RETROSOC_MCU_ACCEPTANCE_H
#include <stdint.h>
uint32_t rs_mcu_compressed_probe(void);
void rs_mcu_atomic_probe(uint32_t *address);
void rs_mcu_unmapped_probe(void);
#endif
+278
View File
@@ -0,0 +1,278 @@
#include <stddef.h>
#include <stdint.h>
#include <retrosoc/arch/riscv/system_base.h>
#include <retrosoc/core/archinfo.h>
#include <retrosoc/core/irq.h>
#include <retrosoc/core/soc.h>
#include <retrosoc/generated/irq_metadata.h>
#include <retrosoc/hal/clint.h>
#include <retrosoc/hal/dma.h>
#include <retrosoc/hal/gpio.h>
#include <retrosoc/hal/i2c.h>
#include <retrosoc/hal/onchip_sram.h>
#include <retrosoc/hal/pwm.h>
#include <retrosoc/hal/rtc.h>
#include <retrosoc/hal/sysctrl.h>
#include <retrosoc/hal/timer.h>
#include <retrosoc/hal/uart.h>
#include <retrosoc/hal/watchdog.h>
#include <retrosoc/lib/printf.h>
#include "mcu_acceptance.h"
static _Alignas(64) uint32_t rs_mcu_source[80];
static _Alignas(64) uint32_t rs_mcu_destination[80];
static volatile uint32_t rs_mcu_irq_count;
static volatile uint32_t rs_mcu_exception_count;
static volatile bool rs_mcu_handler_failed;
static void rs_mcu_finish(bool pass, uint8_t code) {
printf("Tiny acceptance %s code=%u\n", pass ? "passed" : "failed", code);
rs_sysctrl_write_test_status(pass, code);
/* The terminal status must retain the first accepted full-word write. */
rs_sysctrl_write_test_status(!pass, UINT8_C(255));
for (;;) {
}
}
static void rs_mcu_exception(uintptr_t mcause, uintptr_t stack_pointer) {
volatile uint32_t *const frame = (volatile uint32_t *)stack_pointer;
if ((mcause != UINT32_C(2)) && (mcause != UINT32_C(5))) {
rs_mcu_handler_failed = true;
}
++rs_mcu_exception_count;
/* system_irq.S saves MEPC in slot 11 of the RV32 trap frame. Both test
* instructions are explicitly 32 bits; normal exceptions do not use this handler. */
frame[11] += UINT32_C(4);
}
static void rs_mcu_timer_irq(uintptr_t mcause, uintptr_t stack_pointer) {
(void)mcause;
(void)stack_pointer;
++rs_mcu_irq_count;
if (rs_timer_interrupt_clear(RS_TIMER_0, RS_TIMER_INTERRUPT_ALL) != RS_OK) {
rs_mcu_handler_failed = true;
}
}
static bool rs_mcu_dma(void) {
rs_dma_config_t config = {
.kind = RS_DMA_KIND_MM_TO_MM,
.request = RS_DMA_REQUEST_SOFTWARE,
.source = (uintptr_t)rs_mcu_source,
.destination = (uintptr_t)rs_mcu_destination,
.byte_count = UINT32_C(259),
.width = RS_DMA_WIDTH_32,
.source_increment = true,
.destination_increment = true,
.priority = 0U,
.burst_beats = 16U,
};
for (uint32_t index = 0U; index < 80U; ++index) {
rs_mcu_source[index] = UINT32_C(0xa519c300) ^ index;
rs_mcu_destination[index] = UINT32_C(0xcccccccc);
}
if ((rs_dma_configure(4U, &config) != RS_EINVAL) ||
(rs_dma_configure(RS_DMA_CHANNEL_BULK, &config) != RS_OK) ||
(rs_dma_start(RS_DMA_CHANNEL_BULK) != RS_OK) ||
(rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK)) {
return false;
}
for (uint32_t index = 0U; index < 64U; ++index) {
if (rs_mcu_source[index] != rs_mcu_destination[index]) {
return false;
}
}
if (rs_mcu_destination[64] !=
((rs_mcu_source[64] & UINT32_C(0x00ffffff)) | UINT32_C(0xcc000000))) {
return false;
}
config.kind = RS_DMA_KIND_MM_TO_STREAM;
config.request = RS_DMA_REQUEST_I2S_TX;
return rs_dma_config_validate(0U, &config) != RS_OK;
}
static bool rs_mcu_interrupts(void) {
uint32_t timeout = UINT32_C(20000);
if ((rs_irq_enable_external(RS_SOC_EXT_IRQ_TIMER0, rs_mcu_timer_irq) != RS_OK) ||
(rs_timer_interrupt_enable(RS_TIMER_0, RS_TIMER_INTERRUPT_TIMEOUT) != RS_OK) ||
(rs_timer_interrupt_test(RS_TIMER_0, RS_TIMER_INTERRUPT_TIMEOUT) != RS_OK)) {
return false;
}
__RV_CSR_SET(CSR_MSTATUS, MSTATUS_MIE);
while ((rs_mcu_irq_count == 0U) && (timeout != 0U)) {
--timeout;
}
__RV_CSR_CLEAR(CSR_MSTATUS, MSTATUS_MIE);
return (rs_mcu_irq_count == 1U) && !rs_mcu_handler_failed &&
(rs_irq_disable_external(RS_SOC_EXT_IRQ_TIMER0) == RS_OK);
}
static bool rs_mcu_io(void) {
const rs_gpio_config_t output = {
.mode = RS_GPIO_MODE_OUTPUT,
.pull = RS_GPIO_PULL_NONE,
.trigger = RS_GPIO_TRIGGER_NONE,
.output_high = false,
};
const rs_uart_config_t loopback = {
.source_clock_hz = RS_CPU_CLOCK_HZ,
.baud_rate = UART_BPS,
.data_bits = 8U,
.stop_bits = 1U,
.parity = RS_UART_PARITY_NONE,
.rx_watermark = 1U,
.rx_timeout_bits = 16U,
.tx_enable = true,
.rx_enable = true,
.loopback_enable = true,
};
const uint32_t transmit[4] = {UINT32_C(0x54), UINT32_C(0x49), UINT32_C(0x4e), UINT32_C(0x59)};
uint32_t receive[4] = {0U};
rs_uart_timing_t timing;
bool high;
uint32_t timeout = UINT32_C(10000);
uint8_t payload = UINT8_C(0x55);
const rs_i2c_transfer_t transfer = {
.address = UINT16_C(0x50),
.tx_data = &payload,
.tx_length = 1U,
};
rs_i2c_status_t status;
if ((rs_gpio_configure(15U, &output) != RS_OK) || (rs_gpio_write(15U, true) != RS_OK) ||
(rs_gpio_read(15U, &high) != RS_OK) || !high || (rs_gpio_write(15U, false) != RS_OK) ||
(rs_gpio_read(15U, &high) != RS_OK) || high) {
return false;
}
if ((rs_uart_configure(&loopback, RS_TIMEOUT_DEFAULT) != RS_OK) ||
(rs_uart_write_dma(transmit, 4U, RS_TIMEOUT_DEFAULT) != RS_OK) ||
(rs_uart_read_dma(receive, 4U, RS_TIMEOUT_DEFAULT) != RS_OK)) {
return false;
}
for (uint32_t index = 0U; index < 4U; ++index) {
if (transmit[index] != receive[index]) {
return false;
}
}
if ((rs_uart_init(RS_CPU_CLOCK_HZ, UART_BPS) != RS_OK) ||
(rs_uart_timing_calculate(RS_CPU_CLOCK_HZ, UART_BPS, &timing) != RS_OK)) {
return false;
}
/* UART1 uses the same register ABI; the current shared high-level UART
* API selects UART0. Exercise UART1 PIO and its internal loopback here. */
RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x00)) = timing.baud_integer;
RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x04)) = timing.baud_fraction;
RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x08)) = UINT32_C(3);
RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x0c)) = UINT32_C(7);
RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x10)) = UINT32_C(0x5a);
while (((RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x18)) & UINT32_C(0x40)) != 0U) &&
(timeout != 0U)) {
--timeout;
}
if ((timeout == 0U) || ((RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x14)) &
UINT32_C(0xff)) != UINT32_C(0x5a))) {
return false;
}
/* There is no slave on the pulled-up acceptance board: a real address
* transaction must terminate as NACK rather than stick waiting on the bus. */
for (uint32_t bus = 0U; bus < 2U; ++bus) {
const rs_i2c_bus_t instance = (bus == 0U) ? RS_I2C_BUS_0 : RS_I2C_BUS_1;
if ((rs_i2c_init(instance, RS_CPU_CLOCK_HZ, UINT32_C(100000)) != RS_OK) ||
(rs_i2c_transfer(instance, &transfer, RS_TIMEOUT_DEFAULT) != RS_EIO) ||
(rs_i2c_get_status(instance, &status) != RS_OK) ||
((status.errors & RS_I2C_ERROR_ADDR_NACK) == 0U)) {
return false;
}
}
return true;
}
static bool rs_mcu_rtc(void) {
const rs_rtc_config_t config = {
.second_cycles = UINT32_C(4096),
.calibration_ppm = 0,
.interrupt_enable = 0U,
.wake_enable = 0U,
.enable = true,
};
const rs_rtc_time_t value = {.seconds = UINT64_C(1234), .subsecond = 0U};
rs_rtc_time_t observed;
return (rs_rtc_probe() == RS_OK) && (rs_rtc_configure(&config, RS_TIMEOUT_DEFAULT) == RS_OK) &&
(rs_rtc_set_time(&value, RS_TIMEOUT_DEFAULT) == RS_OK) &&
(rs_rtc_get_time(&observed, RS_TIMEOUT_DEFAULT) == RS_OK) &&
(observed.seconds >= value.seconds);
}
int main(void) {
rs_archinfo_t info;
rs_onchip_sram_info_t memory;
rs_sysctrl_fault_status_t fault;
rs_watchdog_status_t watchdog;
uint64_t before;
uint64_t after;
const rs_watchdog_config_t watchdog_config = {
.prescale_divider = 1U,
.timeout_ticks = UINT32_C(20000),
.window_min_ticks = 0U,
.early_warning_ticks = 0U,
.window_enable = false,
.early_warning_enable = false,
.debug_freeze_enable = true,
};
if (rs_uart_init(RS_CPU_CLOCK_HZ, UART_BPS) != RS_OK) {
rs_mcu_finish(false, 1U);
}
printf("Hello retroSoC! Tiny MCU\n");
if (rs_watchdog_get_status(&watchdog, RS_TIMEOUT_DEFAULT) != RS_OK) {
rs_mcu_finish(false, 2U);
}
if (watchdog.reset_count != 0U) {
rs_mcu_finish(true, 0U);
}
if ((rs_archinfo_read(&info) != RS_OK) || (rs_archinfo_validate_build(&info) != RS_OK) ||
(info.soc_id != RS_SOC_ID) || (info.topology != UINT32_C(0x20200001)) ||
(rs_onchip_sram_probe(&memory) != RS_OK) || (memory.memory_bytes != UINT32_C(131072)) ||
(memory.data_bytes != 4U) || (memory.bank_count != 32U)) {
rs_mcu_finish(false, 3U);
}
if ((rs_mcu_compressed_probe() != 42U) || ((__RV_CSR_READ(CSR_MISA) & UINT32_C(1)) != 0U) ||
(rs_irq_register_exception(2U, rs_mcu_exception) != RS_OK) ||
(rs_irq_register_exception(5U, rs_mcu_exception) != RS_OK)) {
rs_mcu_finish(false, 4U);
}
rs_mcu_atomic_probe(rs_mcu_source);
rs_mcu_unmapped_probe();
if ((rs_mcu_exception_count != 2U) || rs_mcu_handler_failed ||
(rs_sysctrl_get_fault_status(&fault) != RS_OK) || !fault.pending ||
(fault.address != UINT32_C(0x38000000)) || (rs_sysctrl_clear_fault() != RS_OK)) {
rs_mcu_finish(false, 5U);
}
if (!rs_mcu_dma()) {
rs_mcu_finish(false, 6U);
}
if ((rs_clint_get_time(&before) != RS_OK) || (rs_clint_get_time(&after) != RS_OK) ||
(after <= before) || !rs_mcu_interrupts()) {
rs_mcu_finish(false, 7U);
}
if (!rs_mcu_rtc() || (rs_pwm_probe() != RS_OK) ||
(rs_sysctrl_set_hp_release(true) != RS_ENOTSUP)) {
rs_mcu_finish(false, 8U);
}
if (!rs_mcu_io()) {
rs_mcu_finish(false, 10U);
}
printf("Tiny CPU/DMA/IRQ/RTC/I/O passed; exercising watchdog reset\n");
if ((rs_watchdog_configure(&watchdog_config) != RS_OK) ||
(rs_watchdog_start(RS_TIMEOUT_DEFAULT) != RS_OK)) {
rs_mcu_finish(false, 9U);
}
for (;;) {
}
}
+3
View File
@@ -0,0 +1,3 @@
APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny.c
APP_CFLAGS += -Os
APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S
+58
View File
@@ -0,0 +1,58 @@
/* Copyright (c) 2026 Yuchi Miao; SPDX-License-Identifier: MulanPSL-2.0 */
#include "retrosoc/generated/memory_map.h"
/* Small gate-level acceptance image. Executes through the real XPI pins,
* checks both ends of SRAM and byte strobes, then checks APB/UART/SYSCTRL.
* The full SDK/application acceptance runs separately in both RTL simulators. */
.section .text
.global _start
_start:
li t0, RS_SOC_SRAM_BASE
li t1, 0x12345678
sw t1, 0(t0)
lw t2, 0(t0)
bne t1, t2, failed
li t0, RS_SOC_SRAM_END - 3
sw t1, 0(t0)
li t2, 0xab
sb t2, 1(t0)
lw t2, 0(t0)
li t1, 0x1234ab78
bne t1, t2, failed
li s0, RS_SOC_APB4_UART0_BASE
li t0, 26
sw t0, 0(s0)
li t0, 11
sw t0, 4(s0)
li t0, 3
sw t0, 8(s0)
sw t0, 12(s0)
la s1, message
print_loop:
lbu s2, 0(s1)
beqz s2, passed
wait_uart:
lw t0, 24(s0)
andi t0, t0, 32
bnez t0, wait_uart
sw s2, 16(s0)
addi s1, s1, 1
j print_loop
passed:
/* Let the serial output drain before setting the terminal verdict. */
lw t0, 24(s0)
andi t0, t0, 4
bnez t0, passed
li t0, RS_SOC_APB4_SYSCTRL_BASE
li t1, 0x80000001
sw t1, RS_SOC_SYSCTRL_TEST_STATUS_OFFSET(t0)
halt:
j halt
failed:
li t0, RS_SOC_APB4_SYSCTRL_BASE
li t1, 0x80000100
sw t1, RS_SOC_SYSCTRL_TEST_STATUS_OFFSET(t0)
j halt
message:
.asciz "Hello retroSoC! Tiny netlist boot\n"
+9
View File
@@ -51,3 +51,12 @@ macro must select either this mode or a qualified macro timing model.
For supported profiles and commands, see the root [README](../README.md) and For supported profiles and commands, see the root [README](../README.md) and
[agent contract](../AGENTS.md). [agent contract](../AGENTS.md).
## Tiny MCU
`ci/ihp130-tiny.mk` selects `SOC=TINY`, one RV32IMC Hazard3 (A disabled),
128 KiB SRAM, AXI32/APB4, four DMA channels and the wired peripheral subset.
The profile uses 24 MHz, `HAVE_HP=NO`, `MINI_MODE=NONE`, CSR-enabled RV32IM
firmware and `ld2_all_sram`. Both `bringup` and `ci_smoke` select Tiny acceptance
firmware. Icarus and Verilator use the same pin-level Flash model. Other PDKs,
wireless, external RAM and HP applications are rejected by the Tiny config gate.
+18
View File
@@ -0,0 +1,18 @@
SOC := TINY
MINI_MODE := NONE
PDK := IHP130
SIMU := VERILATOR
HAVE_PLL := NO
HAVE_SRAM_IF := YES
HAVE_SRAM_MACRO := YES
SRAM_SIZE_KIB := 128
HAVE_HP := NO
HAVE_SVA := NO
EXT_CLK_HZ := 24000000
AUD_CLK_HZ := 24000000
CLINT_TIMEBASE_HZ := 1000000
ISA := RV32IM
HAVE_CSR := YES
APP := bringup
LINK_TYPE := ld2_all_sram
RTL_TOP := retrosoc_tiny_tb
+15 -1
View File
@@ -167,7 +167,7 @@ on it.
## Build Integration ## Build Integration
The central software build assembles the runtime sources in The central software build assembles the runtime sources in
[`../rtl/mini/mk/software.mk`](../rtl/mini/mk/software.mk). It selects the [`../rtl/mk/software.mk`](../rtl/mk/software.mk). It selects the
RISC-V ISA, optional CSR interrupt support, linker layout, and the application RISC-V ISA, optional CSR interrupt support, linker layout, and the application
profile. Applications add their own sources after the common runtime is profile. Applications add their own sources after the common runtime is
selected. selected.
@@ -226,3 +226,17 @@ hardware validation.
For the complete firmware, simulation, synthesis, and timing flow, see the For the complete firmware, simulation, synthesis, and timing flow, see the
[repository README](../README.md). [repository README](../README.md).
## Product composition
Tiny uses the same `rs_` SDK and selects its generated memory, IRQ and capability
headers through the committed profile. The generated memory-map header includes
Tiny capability metadata, so public DMA headers report four channels without
requiring an application to reproduce private compiler flags. Unavailable IP
drivers are omitted; Tiny SYSCTRL rejects unsupported lifecycle/PLL operations
before MMIO. Startup skips external-RAM initialization and loads into SRAM.
`<retrosoc/hal/watchdog.h>` provides `rs_watchdog_configure`, `rs_watchdog_start`,
`rs_watchdog_service`, `rs_watchdog_get_status` and `rs_watchdog_clear_reset_cause`,
with SDK status values and bounded command waits. These wrappers preserve the
locked watchdog register ABI.
+6
View File
@@ -17,6 +17,7 @@ _start:
/* Direct Mode: All exceptions set pc to BASE */ /* Direct Mode: All exceptions set pc to BASE */
csrc CSR_MTVEC, 0x3 csrc CSR_MTVEC, 0x3
#endif #endif
#ifndef RS_SOC_TINY
# wait psram reset done # wait psram reset done
la a0, _flash_wait_start la a0, _flash_wait_start
la a1, 2000 la a1, 2000
@@ -25,6 +26,8 @@ wait_psram_reset:
addi a0, a0, 4 addi a0, a0, 4
blt a0, a1, wait_psram_reset blt a0, a1, wait_psram_reset
#endif
# ===== Startup Stage 1 ===== # ===== Startup Stage 1 =====
# init the regfile # init the regfile
# zero-initialize register file # zero-initialize register file
@@ -68,6 +71,7 @@ wait_psram_reset:
# when code LMA is different with VMA # when code LMA is different with VMA
# test: using psram before loading # test: using psram before loading
#ifndef RS_SOC_TINY
# Define the addresses # Define the addresses
.equ GPIO_ALT_ENABLE, RS_SOC_APB4_GPIO_ADMIN_BASE + 0x34 .equ GPIO_ALT_ENABLE, RS_SOC_APB4_GPIO_ADMIN_BASE + 0x34
.equ GPIO_ALT_SELECT, RS_SOC_APB4_GPIO_ADMIN_BASE + 0x38 .equ GPIO_ALT_SELECT, RS_SOC_APB4_GPIO_ADMIN_BASE + 0x38
@@ -98,6 +102,8 @@ PSRAM_READY_WAIT:
andi t1, t1, PSRAM_READY andi t1, t1, PSRAM_READY
beqz t1, PSRAM_READY_WAIT beqz t1, PSRAM_READY_WAIT
#endif
app_loader_start: app_loader_start:
la a0, _ram_lma la a0, _ram_lma
la a1, _ram_vma la a1, _ram_vma
+9 -5
View File
@@ -77,11 +77,15 @@
#define RS_DMA_ERROR_READ UINT32_C(0x00000040) #define RS_DMA_ERROR_READ UINT32_C(0x00000040)
#define RS_DMA_ERROR_STREAM_LAST UINT32_C(0x00000100) #define RS_DMA_ERROR_STREAM_LAST UINT32_C(0x00000100)
#define RS_DMA_CHANNEL_COUNT 8U #ifdef RS_SOC_TINY
#define RS_DMA_CHANNEL_HP 6U #define RS_DMA_CHANNEL_COUNT 4U
#define RS_DMA_CHANNEL_RESERVED 7U #else
#define RS_DMA_MAX_BURST_BEATS 16U #define RS_DMA_CHANNEL_COUNT 8U
#define RS_DMA_DATA_WIDTH_BITS 32U #endif
#define RS_DMA_CHANNEL_HP 6U
#define RS_DMA_CHANNEL_RESERVED 7U
#define RS_DMA_MAX_BURST_BEATS 16U
#define RS_DMA_DATA_WIDTH_BITS 32U
#define RS_DMA_CHANNEL_OFFSET(channel, offset) \ #define RS_DMA_CHANNEL_OFFSET(channel, offset) \
(RS_DMA_CH_BASE + ((uint32_t)(channel)*RS_DMA_CH_STRIDE) + (offset)) (RS_DMA_CH_BASE + ((uint32_t)(channel)*RS_DMA_CH_STRIDE) + (offset))
+13
View File
@@ -1,5 +1,18 @@
#ifndef RETROSOC_WDG_H #ifndef RETROSOC_WDG_H
#define RETROSOC_WDG_H #define RETROSOC_WDG_H
#include <wdg.h>
#include <retrosoc/core/status.h>
typedef wdg_config_t rs_watchdog_config_t;
typedef wdg_snapshot_t rs_watchdog_status_t;
rs_status_t rs_watchdog_configure(const rs_watchdog_config_t *config);
rs_status_t rs_watchdog_start(rs_timeout_t timeout);
rs_status_t rs_watchdog_service(rs_timeout_t timeout);
rs_status_t rs_watchdog_get_status(rs_watchdog_status_t *status, rs_timeout_t timeout);
rs_status_t rs_watchdog_clear_reset_cause(rs_timeout_t timeout);
void ip_wdg_test(int argc, char **argv); void ip_wdg_test(int argc, char **argv);
#endif #endif
+13
View File
@@ -39,7 +39,20 @@ rs_status_t rs_archinfo_validate_build(const rs_archinfo_t *info) {
.config_id = ARCHINFO_INTEGRATION_CONFIG_ID, .config_id = ARCHINFO_INTEGRATION_CONFIG_ID,
}; };
#ifdef RS_SOC_TINY
if ((info == NULL) || (info->soc_id != RS_SOC_ID) ||
(info->component_id != ARCHINFO_COMPONENT_ID_VALUE) ||
(info->ip_version != ARCHINFO_IP_VERSION_VALUE) ||
(((info->capability & ARCHINFO_CAPABILITY_ABI_MASK) >> ARCHINFO_CAPABILITY_ABI_SHIFT) !=
ARCHINFO_ABI_VERSION) ||
(expectation.check_build_id && (info->build_id != expectation.build_id)) ||
(info->config_id != expectation.config_id)) {
return RS_EFORMAT;
}
return RS_OK;
#else
return rs_archinfo_status(archinfo_validate(info, &expectation)); return rs_archinfo_status(archinfo_validate(info, &expectation));
#endif
} }
rs_status_t rs_archinfo_read_device_id(uint32_t device_id[4]) { rs_status_t rs_archinfo_read_device_id(uint32_t device_id[4]) {
+10
View File
@@ -18,6 +18,11 @@ static bool rs_dma_address_valid(uintptr_t address, uint32_t byte_count, bool in
} }
static bool rs_dma_request_valid(const rs_dma_config_t *config) { static bool rs_dma_request_valid(const rs_dma_config_t *config) {
#ifdef RS_SOC_TINY
if (config->kind != RS_DMA_KIND_MM_TO_MM) {
return false;
}
#endif
switch (config->kind) { switch (config->kind) {
case RS_DMA_KIND_MM_TO_MM: case RS_DMA_KIND_MM_TO_MM:
return (config->request == RS_DMA_REQUEST_SOFTWARE) || return (config->request == RS_DMA_REQUEST_SOFTWARE) ||
@@ -91,6 +96,11 @@ rs_status_t rs_dma_tcd_validate(uint32_t channel, const rs_dma_tcd_t *tcd) {
kind = (tcd->control >> RS_DMA_TCD_KIND_SHIFT) & UINT32_C(0x7); kind = (tcd->control >> RS_DMA_TCD_KIND_SHIFT) & UINT32_C(0x7);
request = (tcd->control >> RS_DMA_TCD_REQUEST_SHIFT) & UINT32_C(0xF); request = (tcd->control >> RS_DMA_TCD_REQUEST_SHIFT) & UINT32_C(0xF);
burst = (tcd->control >> RS_DMA_TCD_BURST_SHIFT) & UINT32_C(0x1F); burst = (tcd->control >> RS_DMA_TCD_BURST_SHIFT) & UINT32_C(0x1F);
#ifdef RS_SOC_TINY
if (kind != (uint32_t)RS_DMA_KIND_MM_TO_MM) {
return RS_ENOTSUP;
}
#endif
if ((kind > (uint32_t)RS_DMA_KIND_STREAM_TO_MM) || if ((kind > (uint32_t)RS_DMA_KIND_STREAM_TO_MM) ||
(request > (uint32_t)RS_DMA_REQUEST_CRYPTO_OUT) || (burst > RS_DMA_MAX_BURST_BEATS) || (request > (uint32_t)RS_DMA_REQUEST_CRYPTO_OUT) || (burst > RS_DMA_MAX_BURST_BEATS) ||
((tcd->source & UINT32_C(0x3)) != UINT32_C(0)) || ((tcd->source & UINT32_C(0x3)) != UINT32_C(0)) ||
+2 -2
View File
@@ -454,7 +454,7 @@ rs_status_t rs_i2c_write_dma(rs_i2c_bus_t bus, uint16_t address, bool ten_bit_ad
const uint32_t channel = (bus == RS_I2C_BUS_0) ? RS_DMA_CHANNEL_I2C0 : RS_DMA_CHANNEL_I2C1; const uint32_t channel = (bus == RS_I2C_BUS_0) ? RS_DMA_CHANNEL_I2C0 : RS_DMA_CHANNEL_I2C1;
const rs_dma_request_t request = const rs_dma_request_t request =
(bus == RS_I2C_BUS_0) ? RS_DMA_REQUEST_I2C0_TX : RS_DMA_REQUEST_I2C1_TX; (bus == RS_I2C_BUS_0) ? RS_DMA_REQUEST_I2C0_TX : RS_DMA_REQUEST_I2C1_TX;
rs_dma_config_t dma_config; rs_dma_config_t dma_config = {0};
if (!rs_i2c_bus_valid(bus) || (data == NULL) || (workspace == NULL) || (length == 0U) || if (!rs_i2c_bus_valid(bus) || (data == NULL) || (workspace == NULL) || (length == 0U) ||
(length > RS_I2C_DMA_MAX_BYTES)) { (length > RS_I2C_DMA_MAX_BYTES)) {
@@ -499,7 +499,7 @@ rs_status_t rs_i2c_read_dma(rs_i2c_bus_t bus, uint16_t address, bool ten_bit_add
const uint32_t channel = (bus == RS_I2C_BUS_0) ? RS_DMA_CHANNEL_I2C0 : RS_DMA_CHANNEL_I2C1; const uint32_t channel = (bus == RS_I2C_BUS_0) ? RS_DMA_CHANNEL_I2C0 : RS_DMA_CHANNEL_I2C1;
const rs_dma_request_t request = const rs_dma_request_t request =
(bus == RS_I2C_BUS_0) ? RS_DMA_REQUEST_I2C0_RX : RS_DMA_REQUEST_I2C1_RX; (bus == RS_I2C_BUS_0) ? RS_DMA_REQUEST_I2C0_RX : RS_DMA_REQUEST_I2C1_RX;
rs_dma_config_t dma_config; rs_dma_config_t dma_config = {0};
if (!rs_i2c_bus_valid(bus) || (data == NULL) || (workspace == NULL) || (length == 0U) || if (!rs_i2c_bus_valid(bus) || (data == NULL) || (workspace == NULL) || (length == 0U) ||
(length > RS_I2C_DMA_MAX_BYTES)) { (length > RS_I2C_DMA_MAX_BYTES)) {
+30
View File
@@ -86,6 +86,12 @@ static rs_status_t rs_sysctrl_perf_offsets(rs_sysctrl_perf_counter_t counter, ui
return RS_EINVAL; return RS_EINVAL;
} }
#ifdef RS_SOC_TINY
if ((counter != RS_SYSCTRL_PERF_MGMT_WAIT) && (counter != RS_SYSCTRL_PERF_DMA_WAIT)) {
return RS_ENOTSUP;
}
#endif
switch (counter) { switch (counter) {
case RS_SYSCTRL_PERF_MGMT_WAIT: case RS_SYSCTRL_PERF_MGMT_WAIT:
*low_offset = RS_SYSCTRL_PERF_MGMT_WAIT_LO_OFFSET; *low_offset = RS_SYSCTRL_PERF_MGMT_WAIT_LO_OFFSET;
@@ -215,6 +221,9 @@ rs_status_t rs_sysctrl_clear_user_core_config_error(void) {
} }
rs_status_t rs_sysctrl_get_pll_config(uint8_t *selection) { rs_status_t rs_sysctrl_get_pll_config(uint8_t *selection) {
#ifdef RS_SOC_TINY
return RS_ENOTSUP;
#endif
if (selection == NULL) { if (selection == NULL) {
return RS_EINVAL; return RS_EINVAL;
} }
@@ -224,6 +233,9 @@ rs_status_t rs_sysctrl_get_pll_config(uint8_t *selection) {
} }
rs_status_t rs_sysctrl_set_pll_config(uint8_t selection) { rs_status_t rs_sysctrl_set_pll_config(uint8_t selection) {
#ifdef RS_SOC_TINY
return RS_ENOTSUP;
#endif
if (((uint32_t)selection & ~RS_SYSCTRL_PLL_SELECTION_MASK) != 0U) { if (((uint32_t)selection & ~RS_SYSCTRL_PLL_SELECTION_MASK) != 0U) {
return RS_EINVAL; return RS_EINVAL;
} }
@@ -232,16 +244,25 @@ rs_status_t rs_sysctrl_set_pll_config(uint8_t selection) {
} }
rs_status_t rs_sysctrl_apply_pll_config(void) { rs_status_t rs_sysctrl_apply_pll_config(void) {
#ifdef RS_SOC_TINY
return RS_ENOTSUP;
#endif
*rs_sysctrl_register(RS_SYSCTRL_PLL_CMD_OFFSET) = RS_SYSCTRL_PLL_CMD_APPLY; *rs_sysctrl_register(RS_SYSCTRL_PLL_CMD_OFFSET) = RS_SYSCTRL_PLL_CMD_APPLY;
return RS_OK; return RS_OK;
} }
rs_status_t rs_sysctrl_clear_pll_error(void) { rs_status_t rs_sysctrl_clear_pll_error(void) {
#ifdef RS_SOC_TINY
return RS_ENOTSUP;
#endif
*rs_sysctrl_register(RS_SYSCTRL_PLL_CMD_OFFSET) = RS_SYSCTRL_PLL_CMD_CLEAR_ERROR; *rs_sysctrl_register(RS_SYSCTRL_PLL_CMD_OFFSET) = RS_SYSCTRL_PLL_CMD_CLEAR_ERROR;
return RS_OK; return RS_OK;
} }
rs_status_t rs_sysctrl_get_pll_status(rs_sysctrl_pll_status_t *status) { rs_status_t rs_sysctrl_get_pll_status(rs_sysctrl_pll_status_t *status) {
#ifdef RS_SOC_TINY
return RS_ENOTSUP;
#endif
uint32_t value; uint32_t value;
if (status == NULL) { if (status == NULL) {
@@ -282,6 +303,9 @@ rs_status_t rs_sysctrl_clear_fault(void) {
} }
rs_status_t rs_sysctrl_set_hp_release(bool release) { rs_status_t rs_sysctrl_set_hp_release(bool release) {
#ifdef RS_SOC_TINY
return RS_ENOTSUP;
#endif
rs_sysctrl_hp_status_t status; rs_sysctrl_hp_status_t status;
if ((rs_sysctrl_get_hp_status(&status) != RS_OK) || !status.present) { if ((rs_sysctrl_get_hp_status(&status) != RS_OK) || !status.present) {
@@ -295,6 +319,9 @@ rs_status_t rs_sysctrl_set_hp_release(bool release) {
} }
rs_status_t rs_sysctrl_get_hp_status(rs_sysctrl_hp_status_t *status) { rs_status_t rs_sysctrl_get_hp_status(rs_sysctrl_hp_status_t *status) {
#ifdef RS_SOC_TINY
return RS_ENOTSUP;
#endif
uint32_t value; uint32_t value;
if (status == NULL) { if (status == NULL) {
@@ -311,6 +338,9 @@ rs_status_t rs_sysctrl_get_hp_status(rs_sysctrl_hp_status_t *status) {
} }
rs_status_t rs_sysctrl_select_hp_debug(bool select_hp) { rs_status_t rs_sysctrl_select_hp_debug(bool select_hp) {
#ifdef RS_SOC_TINY
return RS_ENOTSUP;
#endif
rs_sysctrl_hp_status_t status; rs_sysctrl_hp_status_t status;
uint32_t value; uint32_t value;
+2 -2
View File
@@ -279,7 +279,7 @@ rs_status_t rs_uart_irq_test(uint32_t mask) {
} }
rs_status_t rs_uart_write_dma(const uint32_t *words, size_t count, rs_timeout_t timeout) { rs_status_t rs_uart_write_dma(const uint32_t *words, size_t count, rs_timeout_t timeout) {
rs_dma_config_t config; rs_dma_config_t config = {0};
size_t index; size_t index;
rs_status_t status; rs_status_t status;
@@ -314,7 +314,7 @@ rs_status_t rs_uart_write_dma(const uint32_t *words, size_t count, rs_timeout_t
} }
rs_status_t rs_uart_read_dma(uint32_t *words, size_t count, rs_timeout_t timeout) { rs_status_t rs_uart_read_dma(uint32_t *words, size_t count, rs_timeout_t timeout) {
rs_dma_config_t config; rs_dma_config_t config = {0};
rs_status_t status; rs_status_t status;
if ((words == NULL) || (count == 0U) || (count > (UINT32_MAX / sizeof(*words)))) { if ((words == NULL) || (count == 0U) || (count > (UINT32_MAX / sizeof(*words)))) {
+35
View File
@@ -7,6 +7,41 @@
#define RS_WDG_COMMAND_TIMEOUT UINT32_C(1024) #define RS_WDG_COMMAND_TIMEOUT UINT32_C(1024)
static rs_status_t rs_watchdog_status(wdg_status_t status) {
switch (status) {
case WDG_STATUS_OK:
return RS_OK;
case WDG_STATUS_INVALID_ARGUMENT:
return RS_EINVAL;
case WDG_STATUS_TIMEOUT:
return RS_ETIMEOUT;
case WDG_STATUS_INCOMPATIBLE:
return RS_EFORMAT;
default:
return RS_EIO;
}
}
rs_status_t rs_watchdog_configure(const rs_watchdog_config_t *config) {
return rs_watchdog_status(wdg_configure((uintptr_t)RS_SOC_APB4_WDG_BASE, config));
}
rs_status_t rs_watchdog_start(rs_timeout_t timeout) {
return rs_watchdog_status(wdg_start((uintptr_t)RS_SOC_APB4_WDG_BASE, timeout));
}
rs_status_t rs_watchdog_service(rs_timeout_t timeout) {
return rs_watchdog_status(wdg_service((uintptr_t)RS_SOC_APB4_WDG_BASE, timeout));
}
rs_status_t rs_watchdog_get_status(rs_watchdog_status_t *status, rs_timeout_t timeout) {
return rs_watchdog_status(wdg_snapshot((uintptr_t)RS_SOC_APB4_WDG_BASE, status, timeout));
}
rs_status_t rs_watchdog_clear_reset_cause(rs_timeout_t timeout) {
return rs_watchdog_status(wdg_clear_reset_cause((uintptr_t)RS_SOC_APB4_WDG_BASE, timeout));
}
void ip_wdg_test(int argc, char **argv) { void ip_wdg_test(int argc, char **argv) {
static const wdg_config_t config = { static const wdg_config_t config = {
.prescale_divider = 1U, .prescale_divider = 1U,
+3
View File
@@ -3,6 +3,9 @@
This directory contains repository-level engineering policy that supplements This directory contains repository-level engineering policy that supplements
the root README and subsystem guides. the root README and subsystem guides.
- [Tiny MCU](ip/tiny-soc.md) defines the independent wired Tiny product, AXI4/APB4, SRAM-only startup and IHP130 acceptance.
- [Tiny Gen1 datasheet](../publications/datasheets/tiny/README.md) describes the independently built product, register, software and qualification-boundary reference.
- [Contributing](../CONTRIBUTING.md) describes community discussion, development, - [Contributing](../CONTRIBUTING.md) describes community discussion, development,
review, validation evidence, and maintainer responsibilities. review, validation evidence, and maintainer responsibilities.
- [git-workflow.md](git-workflow.md) defines branch and commit conventions, - [git-workflow.md](git-workflow.md) defines branch and commit conventions,
+1
View File
@@ -4,6 +4,7 @@
| Tier | Profile | Automated coverage | | Tier | Profile | Automated coverage |
| --- | --- | --- | | --- | --- | --- |
| Pull request | `configs/ci/ihp130-tiny.mk` | Tiny firmware, Verilator/Icarus, lint; local Yosys, netlist and OpenSTA |
| Smoke | `configs/ci/ihp130.mk` | strict Verilator RTL lint, firmware, Verilator SVA compilation, Icarus assembly self-test | | Smoke | `configs/ci/ihp130.mk` | strict Verilator RTL lint, firmware, Verilator SVA compilation, Icarus assembly self-test |
| Pull request | `configs/ci/ihp130.mk` | strict Verilator RTL lint, firmware, Verilator, Icarus | | Pull request | `configs/ci/ihp130.mk` | strict Verilator RTL lint, firmware, Verilator, Icarus |
| Pull request | `configs/ci/ihp130-debug.mk` | Verilator remote-bitbang JTAG DTM, Debug Module, OpenOCD, and GDB acceptance | | Pull request | `configs/ci/ihp130-debug.mk` | Verilator remote-bitbang JTAG DTM, Debug Module, OpenOCD, and GDB acceptance |
+2
View File
@@ -4,6 +4,8 @@ This directory owns architecture, register ABI, software contract, and
verification documentation for self-owned retroSoC peripheral IP and platform verification documentation for self-owned retroSoC peripheral IP and platform
extension interfaces. extension interfaces.
- [Tiny MCU](tiny-soc.md) defines the independent wired Tiny product, AXI4/APB4, SRAM-only startup and IHP130 acceptance.
- [timer.md](timer.md) defines the dual APB4 general timer. - [timer.md](timer.md) defines the dual APB4 general timer.
- [sysctrl.md](sysctrl.md) defines the APB4 SystemCtrl register ABI, control-plane contracts, HAL, and verification. - [sysctrl.md](sysctrl.md) defines the APB4 SystemCtrl register ABI, control-plane contracts, HAL, and verification.
- [resource-controller.md](resource-controller.md) defines root-managed resource ownership, IRQ routing, and cache-maintenance handoff. - [resource-controller.md](resource-controller.md) defines root-managed resource ownership, IRQ routing, and cache-maintenance handoff.
+146
View File
@@ -0,0 +1,146 @@
# Tiny MCU Verification Record
This record accompanies the [frozen Tiny contract](tiny-soc.md). It describes
local implementation evidence from 2026-09-25, not a release or silicon signoff.
The working tree is uncommitted and the readiness status remains `prototype`.
## Configuration and artifact roots
The selected profile is `configs/ci/ihp130-tiny.mk`: IHP130, 128 KiB SRAM,
24 MHz, RV32IM firmware on an RV32IMC hart with A disabled. Runs below used
`BUILD_TIMESTAMP=2026-09-25-12-00`:
- Default `bringup`: `build/ihp130-tiny-2026-09-25-12-00-d6009644a134/`.
- `APP=ci_smoke`: `build/ihp130-tiny-2026-09-25-12-00-044d45791ef7/`.
- `APP=ci_smoke HAVE_SVA=YES`:
`build/ihp130-tiny-2026-09-25-12-00-6874f2ffdf11/`.
Configuration digests identify settings and the dependency lock, not a clean
source revision. A release requires rerunning from its reviewed commit.
## Functional and quality checks
| Check | Evidence/boundary |
| --- | --- |
| Tiny maps and source isolation | Generated address, IRQ, pad and clock/reset inputs validate; source closure excludes Mini product RTL, RIB/RIBP and HP generation. |
| AXI fabric, both simulators | Directed tests cover competing CPU/DMA requests, W-before-AW, delayed W, response backpressure, ID/RLAST, 16-beat bursts, 4 KiB crossing, alignment, reserved regions and continued operation after errors. |
| Tiny SYSCTRL, both simulators | APB tests cover unsupported controls, partial terminal writes, sticky first status, counter snapshots, first-fault retention, W1C, RTC wake and reset. |
| Verilator full firmware | `SIM_TEST_PASS`; the CI composition also passes with SVA enabled. Pin-level NOR, compressed instructions, disabled atomics, load faults, SRAM discovery, memory/UART DMA, external timer IRQ, CLINT time, RTC, GPIO, UART1 loopback, I2C NACK and watchdog reboot are exercised. |
| Icarus full firmware | Final expanded CI firmware passes at 3,705,751 cycles, matching the SVA-enabled Verilator run. |
| Icarus synthesized boot | `netsim-boot` passes at 29,362 cycles using the compact assembly image. Covers pin-level NOR, SRAM first/last words, byte writes, UART and terminal status; does not establish full C-firmware gate-level coverage. |
| SDK gates | C formatting, embedded-C policy and host tests pass, including Tiny channel/endpoint rejection compiled from the real DMA validator. No new Required-rule MISRA deviation is recorded; these partial checks do not certify MISRA conformance. |
| Focused Python tests | 112 tests passed across Tiny, address/pad generation and build/regression tooling. Shared debug-reset and SRAM/DMA register-parity checks also pass. After fixing parameterization/publication compatibility and preparing the existing MPW/device-model inputs, all 54 non-reference failures selected for rerun pass. |
| RTL policy | Changed RTL formatting, full owned RTL style audit and readiness metadata pass. Global formatting limitations are listed below. |
| Reproducibility | Dependency lock validation, Ruff, PR/nightly dry-runs and `git diff --check` pass. Tiny source packaging exports the Tiny top, actual filelist and its core timing SDC. |
The full-firmware verdict is authoritative only when both the tool result and
`result-sim-check.json` pass. UART text alone is not sufficient. Assembly netlist
acceptance uses the same strict terminal marker policy.
## Synthesis and timing
For the CI composition, Yosys completed with the IHP130 technology libraries
and 32 `tc_sram_1024x32` banks. The `balanced` report records 207,739 cells and
9,798,326.725198 square micrometers of library area. The area includes the
instantiated memory/pad hierarchy and is not a placed die-area estimate.
OpenSTA completed with the selected slow library corner and the 41.666666667 ns
system clock. Reported setup WNS is **-455.18 ns**, setup TNS is
**-13,049,600 ns**, and hold WNS/TNS are zero. The worst path starts at the
synchronized system-reset register and reaches peripheral FIFO storage through
the high-fanout reset distribution. This is an open physical/timing issue;
24 MHz is a functional target, not a timing-qualified operating point.
Reports reside under the CI variant's `syn/yosys/rpt/` and `sta/opensta/`.
The metric collector understands the Tiny top and timestamp-prefixed Yosys JSON
reports. Metrics remain in `observe` mode; no warning or metric baseline has
been edited to accept this feature.
## Commands and remaining coverage
The build/verification commands were run with the profile and timestamp above:
```sh
make CONFIG=configs/ci/ihp130-tiny.mk doctor firmware sim
make CONFIG=configs/ci/ihp130-tiny.mk APP=ci_smoke HAVE_SVA=YES firmware sim
make CONFIG=configs/ci/ihp130-tiny.mk APP=ci_smoke SIMU=IVERILOG firmware sim
make CONFIG=configs/ci/ihp130-tiny.mk APP=ci_smoke SYNTH=YOSYS synth
make CONFIG=configs/ci/ihp130-tiny.mk APP=ci_smoke SIMU=IVERILOG netsim-boot
make CONFIG=configs/ci/ihp130-tiny.mk APP=ci_smoke STA=OPENSTA sta
make CONFIG=configs/ci/ihp130-tiny.mk metrics package
make sw-format-check sw-policy-check sw-host-test rtl-style-check-all rtl-readiness-check
python3 -m pytest -q tests/test_tiny.py tests/test_memory_map.py tests/test_pin_map.py tests/test_script_tools.py
python3 scripts/regress.py --root . --suite pr --pdk IHP130 --netsim-boot-only
```
The combined IHP130 regression stops in its Mini phase because the locked
VexiiRiscv checkout is absent. Tiny can be selected independently with
`--soc TINY`, or `make CONFIG=configs/ci/ihp130-tiny.mk regress-pr`.
The initial full Pytest run completed with 1,234 passed, 26 failed, 19 skipped
and 71 errors. Parameterization/format-sensitive expectations and the Mini
publication's constant/cast parsing were corrected; the affected cases were
rerun successfully. MPW/device-model inputs and a writable test ccache resolved
additional environment failures. The remaining 16 cases require the absent
locked FLAC corpus, MLPerf Tiny models or TensorFlow oracle sources. These are
not Tiny setup dependencies. The full suite was not rerun after the focused
corrections; it is not reported as a clean full-suite pass.
The initial run, focused results, combined-regression failure and remaining
reference-input case list are retained under the CI variant's
`meta/validation/`.
Global RTL/Make formatting checks encounter existing APU/accelerator files
outside this change. Changed-file formatting is checked independently.
Remaining qualification includes reset-tree/timing closure, placement/routing,
PVT/MMMC, CDC/RDC signoff, silicon and power measurements, full C netlist
acceptance, a Tiny-specific external OpenOCD session, positive external I2C
slave transactions and comprehensive pad-level PWM/RTC coverage. Wireless,
retention, secure boot and other PDKs remain explicit deferred features.
## Shared-path migration follow-up
Mini now references the canonical shared RTL, `scripts/rtl` helpers and
`rtl/mk/software.mk` directly. The 21 Mini compatibility links and seven Tiny
helper links have been removed. Filelists, simulator/formal/synthesis rules,
Python imports, tests, publication tooling and ownership documents use the new
paths. No Git file-type changes remain. Historical warning baselines are
unchanged.
The resolved Mini `commonip.fl`, `ip.fl` and `top.fl` retain the same 299
unique sources in the same order. `netlist_support.fl` retains its two sources;
`inc.fl` adds `rtl/ip/core`. All relocated shared RTL content hashes match the
pre-migration snapshot. This follow-up changes paths, not RTL behavior or the
public hardware/software interface.
Validation after removing the links:
- 245 focused tests passed: 144 build/generator/topology/style/Tiny tests,
96 publication/LibreLane tests and five debug-reset/SRAM simulations.
Full-suite collection succeeds with 1,356 tests; the full suite was not
rerun because of the reference-input gaps documented above.
- Mini `configs/ci/ihp130.mk` address/pad/topology/clock-reset checks and firmware
build pass. Its Verilator compile reaches HP generation and stops because
the locked VexiiRiscv checkout is missing; full Mini simulation and the
combined regression remain unverified.
- Tiny `configs/ci/ihp130-tiny.mk`, `APP=ci_smoke HAVE_SVA=YES`, firmware and
Verilator simulation pass again at 3,705,751 cycles. Full Icarus, synthesis
and STA were not repeated for this path-only follow-up.
- Eleven Python CLI entrypoints work outside the repository directory.
Mini SRAM formal filelist generation, `sv2v` conversion, filelist combination
and dependency generation pass using the canonical shared paths, including
the relocated SRAM header. Formal proofs were not rerun.
- Ruff, owned RTL style, readiness metadata, 251 local Markdown links and
`git diff --check` pass. The Make formatter still reports existing EOF and
formal-command indentation differences in APU/GA2D/formal fragments; those
unrelated differences were preserved.
Run logs, exact commands and path snapshots are retained under
`build/ihp130-2026-09-25-12-00-9903b3d112b7/meta/validation/shared-path-migration/`.
No C register/API change or additional MISRA deviation belongs to this
follow-up; the SDK gates from the implementation record above were not rerun.
## Next review
```text
Use $retrosoc-mini-feature-review in review mode for feature tiny-soc. Review the current worktree diff against docs/ip/tiny-soc.md and docs/ip/tiny-soc-verification.md. Verify Tiny isolation, default Mini compatibility, AXI/APB/error/reset behavior and the SDK ABI. Treat the observed reset-fanout timing deficit and missing APU/NPU/VexiiRiscv inputs as explicit qualification gaps; do not claim timing closure or alter warning/metric baselines.
```
+193
View File
@@ -0,0 +1,193 @@
# retroSoC Tiny MCU Contract
## Purpose and research boundary
This contract freezes the approved Tiny wired-MCU first release. Tiny owns its
integration in `rtl/tiny`; Mini remains a separate product. The committed
`configs/ci/ihp130-tiny.mk` profile is the reproducible implementation boundary.
The design was approved on 2026-09-25. Source presence does not establish
verification, timing closure, silicon qualification, or low-power performance.
The [RP2350](https://www.raspberrypi.com/products/rp2350/) is a Hazard3-based MCU
reference for software, SRAM, and deterministic I/O. Its dual-core, security,
USB, and PIO features are not Tiny requirements. The
[CAST SRAM controller](https://www.cast-inc.com/peripherals/memory-controllers/sram-ctrl)
illustrates native AXI synchronous SRAM and documented verification delivery;
no commercial implementation or qualification is reused.
## Requirements and non-goals
- TINY-001: one Hazard3 hart, RV32IMC with A disabled, mandatory debug support;
existing RV32IM firmware remains the default compiler target.
- TINY-002: native 32-bit AXI4 data and APB4 control. The CPU's native AHB-Lite
terminates at a direct AXI adapter. No RIB/RIBP source or interface is part of
Tiny's compilation or elaboration closure.
- TINY-003: 128 KiB technology-backed SRAM, XPI NOR boot, no external RAM
dependency. Reset vector is `0x00000000`; SRAM begins at `0x30000000`.
- TINY-004: 32 GPIO, two UARTs, two I2C controllers, two general timers,
CLINT, four DMA channels, PWM, RTC, watchdog, SYSCTRL and architecture info.
- TINY-005: IHP130, 24 MHz system clock, no PLL, 1 MHz CLINT timebase. CPU,
AXI, APB, RTC and watchdog share the system clock.
- TINY-006: Tiny must build independently of MPW, HP CPU generation and
multimedia inputs. Shared SDK APIs retain the `rs_` namespace.
- TINY-007: no wireless IP, radio-specific host integration, or wireless stack.
Deferred: RV32 A atomics, RTOS ports, authenticated boot, retention/power gating,
independent sleep clock, 256–512 KiB SRAM, USB, SDIO, standalone general SPI,
I2S, CAN, ADC, multimedia accelerators and other PDK qualification. XPI retains
the existing four-chip-select interface; initial pin-level acceptance uses NOR
on NSS0. The unpopulated chip selects are not qualified device configurations.
## Selected architecture and interfaces
`retrosoc_tiny` integrates the CPU, two-master fabric, SRAM, XPI and one APB
island. `retrosoc_tiny_asic` owns technology pads; `retrosoc_tiny_tb` owns device
models and terminal verdicts. Product maps, filelists and bindings belong to
Tiny; reusable core/debug, memory and bus utilities belong to shared RTL IP.
AXI has 32-bit addresses/data and one-bit ID/USER. CPU and DMA share one
globally active read or write transaction. Round-robin ownership changes only
after B or RLAST handshake; accepted W traffic cannot change owners. Backpressure
must preserve VALID payloads. Memory accepts naturally aligned 1/2/4-byte INCR
transactions of 1–16 beats within a target and 4 KiB page. MMIO accepts one-beat
FIXED/INCR accesses. Unsupported size/burst/lock/alignment or crossing returns
SLVERR; absent regions return DECERR. Rejected reads return the advertised
number of response beats; rejected writes drain their accepted data before B.
APB uses full setup/access phases, PSTRB and PSLVERR. Registers retain their
existing IP offset, access and reset contracts. A missing peripheral has no
successful decode. GPIO alternate functions retain the selected Mini mappings;
unsupported alternate functions drive neither pads nor peripheral requests.
UART TX/RX and XPI have dedicated pins.
## DMA and interrupt contract
DMA retains the V2 manual register ABI, direct and TCD modes, 32-bit transfers,
up-to-16-beat memory bursts and existing completion/error/W1C/abort semantics.
Four channels are available: 0 UART0, 1 I2C0, 2 I2C1, 3 bulk/XPI. UART1 uses
PIO/IRQ. Stream endpoints and requests for omitted devices are unsupported and
must fail validation rather than wait indefinitely. Capability discovery and
SDK range checks must agree with hardware.
CLINT software/timer occupy core bits 0/1. Reused external sources keep Mini
core-bit numbering: UART0 2, timer0 3, timer1 4, I2C0 7, XPI 9, PWM 11,
RTC 13, watchdog 14, GPIO 18, I2C1 19, DMA 20, UART1 26. The vector has 32
bits (30 Hazard3 external inputs); all omitted sources are zero.
## Register and software ABI
Tiny maps reuse the addresses of implemented Mini peripherals, Flash alias and
XPI aperture. Generated outputs contain address/IRQ/capability metadata and
linker regions, not new generated IP register definitions. SVH/C register pairs
remain handwritten and checked for parity. Hardware identity must describe Tiny,
one MCU hart, 128 KiB SRAM and actual capabilities.
Tiny SYSCTRL preserves fault capture, performance enable/clear and TEST_STATUS
at the existing offsets. The first full-word TEST_STATUS write with bit 31 set
is sticky until system reset; bit 0 is pass and bits 15:8 are the result code.
Unsupported Mini lifecycle/clock controls fail safely; they do not enable HP or
user-core behavior. Unavailable IP drivers are omitted from the Tiny firmware composition.
Unsupported SYSCTRL lifecycle/PLL operations and DMA channel/endpoint requests
return an error before MMIO. GPIO alternate modes follow the Tiny pin map.
The `bringup` and `ci_smoke` applications select product-specific entrypoints.
Startup skips PSRAM setup on Tiny and uses `ld2_all_sram`: code/data copy from
Flash into SRAM, with SRAM BSS and stack. IRQ/CSR support is enabled. Public
headers remain under `<retrosoc/...>`; retired `tiny*.h` paths are not restored.
## Clock, reset and lifecycle
External reset is asynchronously asserted and synchronously released. Watchdog
reset restarts the system. Debug hart reset waits for the CPU AXI adapter to
be idle; it must not abandon an accepted transaction. JTAG and asynchronous
external inputs retain the approved Common synchronizer/handshake boundaries.
There is no cross-domain system bus or dynamic clock switching in this release.
RTC/WDG run at the configured system frequency and provide no independent
deep-sleep guarantee. SRAM contents are not initialized by reset.
## Errors, observability and security
Decode/protocol faults complete with AXI errors and feed sticky SYSCTRL fault
information. DMA records errors and supports bounded software waits and abort.
Counters and terminal-test status permit firmware and simulation diagnosis.
Watchdog provides whole-system recovery from a nonresponsive target; this
release makes no unrestricted AXI liveness, secure-boot, production-entropy,
isolation, or safety-certification claim.
## Development order and acceptance
### TINY-P0 — Freeze Tiny MCU Contract
Record the approved requirements here and update document indexes. Acceptance:
reviewable specification, valid links and `git diff --check`.
### TINY-P1 — Product Selection and Shared Infrastructure
Add SOC=TINY, the committed profile, dependency selection and shared build/IP
boundaries. Preserve Mini defaults. Acceptance: configuration/generator tests,
dependency lock validation, no implicit Tiny dependency on Mini product RTL.
### TINY-P2 — AXI4/APB4 Tiny RTL
Implement the hierarchy and contracts above. Acceptance: lint, standalone AXI,
APB, memory, reset and IRQ checks; no RIB/RIBP in the resolved source closure.
### TINY-P3 — SDK Boot and System Verification
Implement generated capabilities, SRAM-only startup, HAL selection and firmware.
Acceptance: both simulators boot through pin-level NOR and emit SIM_TEST_PASS;
test SRAM boundaries, DMA, peripherals, IRQ, disabled A, C instructions and debug.
### TINY-P4 — IHP130 Qualification and Documentation
Add Tiny to IHP130 regression without replacing Mini coverage. Run format/style,
lint, software policy/host tests, Ruff, Pytest, firmware and supported regressions.
Run Yosys, Icarus netlist and OpenSTA; archive macro mapping, area, cell count,
24 MHz timing and warnings under the configuration's build variant. Check docs
and example commands. Record unrun/failed gates explicitly; do not promote
warning baselines or metrics policy as part of this feature.
## Reproducible commands and current evidence
Use the committed profile, and preserve one `BUILD_TIMESTAMP` when commands
must share artifacts:
```sh
make CONFIG=configs/ci/ihp130-tiny.mk setup doctor
make CONFIG=configs/ci/ihp130-tiny.mk firmware sim
make CONFIG=configs/ci/ihp130-tiny.mk SIMU=IVERILOG firmware sim
make CONFIG=configs/ci/ihp130-tiny.mk SYNTH=YOSYS synth
make CONFIG=configs/ci/ihp130-tiny.mk SIMU=IVERILOG netsim-boot
make CONFIG=configs/ci/ihp130-tiny.mk STA=OPENSTA sta
make CONFIG=configs/ci/ihp130-tiny.mk metrics package
python3 scripts/regress.py --root . --suite pr --pdk IHP130 --soc TINY --dry-run
```
`netsim-boot` runs the dedicated `app/asm/tiny_boot.S` gate-level image. It checks
pin-level NOR boot, both ends of physical SRAM, a byte-lane write, UART and the
SYSCTRL terminal result. It requires `SIM_TEST_PASS`; this is separate from the
complete SDK/IRQ/DMA/watchdog acceptance in both RTL simulators. `netsim` retains
the full firmware option. The shared SRAM IP's capability register describes
the IP's native superset; end-to-end requests must obey Tiny's narrower fabric
contract above.
Tiny uses two-cycle AHB ERROR completion so Hazard3 can trap AXI failures. The
shared core/bridge parameters retain Mini's existing default behavior. Tiny
ArchInfo reports SOC_ID `0x54494e59`, topology `0x20200001`, and FEATURES0
`0x00007ffe`: bits 1/2 SRAM interface/macro, 3 AXI, 4 APB, 5 DMA, 6 UART,
7 I2C, 8 PWM, 9 RTC, 10 watchdog, 11 GPIO, 12 CLINT, 13 timers and 14 XPI.
Bit 0 (PLL) is zero. Tiny FAULT_DETAIL contains the raw AXI response (2/3),
FAULT_STATUS reason is 1 for decode and 2 for slave/protocol errors,
and FAULT_MASTER uses 0 for CPU and 1 for DMA; no RIB encoding is exported.
The public watchdog API is `rs_watchdog_*` with `rs_status_t` and bounded waits.
The [verification record](tiny-soc-verification.md) distinguishes successful
functional checks from missing reference inputs and physical closure. The
product remains `prototype`: negative pre-layout reset-fanout timing must not
be presented as a qualified 24 MHz implementation.
## Commercial delivery gaps
Current-revision verification and physical reports govern readiness. Reusable
VIP, coverage closure, full CDC/RDC, DFT/MBIST, PVT/MMMC, post-layout timing,
power characterization, regulatory and silicon qualification remain separate.
+3 -1
View File
@@ -32,8 +32,10 @@ and reviewed evidence for the applicable rules.
- Use `rs_status_t` for fallible operations. Prefer bounded string/format APIs - Use `rs_status_t` for fallible operations. Prefer bounded string/format APIs
and timeout-based register waits. Dynamic allocation is not permitted in and timeout-based register waits. Dynamic allocation is not permitted in
self-owned embedded C. self-owned embedded C.
- New `tiny` identifiers, legacy include paths, and prohibited library calls - Retired `tiny`-prefixed SDK identifiers, legacy include paths, and prohibited library calls
are not allowed outside the compatibility files explicitly listed by policy. are not allowed outside the compatibility files explicitly listed by policy.
The Tiny product name in prose/string literals and `RS_SOC_TINY` product
selection do not restore those retired SDK interfaces.
The standard is referenced by name and category only. Consult the licensed The standard is referenced by name and category only. Consult the licensed
MISRA publication for the complete rule text and applicability guidance; do MISRA publication for the complete rule text and applicability guidance; do
+1 -1
View File
@@ -10,7 +10,7 @@ validated without changing functional behavior.
The owned profile in The owned profile in
[`rtl/rtl_style_manifest.json`](../rtl/rtl_style_manifest.json) is the source [`rtl/rtl_style_manifest.json`](../rtl/rtl_style_manifest.json) is the source
of truth for the reviewed source set. It includes `rtl/ip/**` and of truth for the reviewed source set. It includes `rtl/ip/**` and
`rtl/mini/top/**`, but excludes managed, generated, PDK, formal, device-model, `rtl/mini/top/**` and `rtl/tiny/top/**`, but excludes managed, generated, PDK, formal, device-model,
and third-party sources. and third-party sources.
[`rtl/rtl_style_audit.json`](../rtl/rtl_style_audit.json) records the exact [`rtl/rtl_style_audit.json`](../rtl/rtl_style_audit.json) records the exact
+1 -1
View File
@@ -10,7 +10,7 @@ differ: retroSoC uses `clk_i` and `rst_n_i`, not `rst_ni`.
## 1. Scope and rule levels ## 1. Scope and rule levels
The owned profile applies to `rtl/ip/**` and `rtl/mini/top/**`. Formal, The owned profile applies to `rtl/ip/**`, `rtl/mini/top/**`, and `rtl/tiny/top/**`. Formal,
device-model, PDK, generated, managed, and third-party sources use the profiles device-model, PDK, generated, managed, and third-party sources use the profiles
declared in [`rtl/rtl_style_manifest.json`](../rtl/rtl_style_manifest.json). declared in [`rtl/rtl_style_manifest.json`](../rtl/rtl_style_manifest.json).
+27 -36
View File
@@ -3,9 +3,9 @@
## Status and Scope ## Status and Scope
This document defines the intended Tiny, Mini, Std, and Pro product ladder. This document defines the intended Tiny, Mini, Std, and Pro product ladder.
It is a product roadmap, not a statement of implemented repository support. Tiny and Mini have executable build profiles; Std and Pro remain roadmap
The current build system accepts only `SOC=MINI`, and the committed Mini targets. Committed product profiles and retained validation evidence define
profiles remain the executable source of truth. implemented support. Tiny first targets IHP130 as a wired MCU.
Mini is the family anchor. It establishes the common product model: an open Mini is the family anchor. It establishes the common product model: an open
RISC-V SoC in which a small, always-available Hazard3 management core owns RISC-V SoC in which a small, always-available Hazard3 management core owns
@@ -14,8 +14,8 @@ add application processors and accelerators without transferring final
lifecycle control to Linux. Tiny reduces this model to an MCU-class device and lifecycle control to Linux. Tiny reduces this model to an MCU-class device and
does not require a separate application processor. does not require a separate application processor.
All frequencies, memory sizes, bus widths, accelerator rates, and software Except for the explicitly identified executable baselines, frequencies, memory
features below are product targets. They require separate RTL integration, sizes, bus widths, accelerator rates, and software features below are product targets. They require separate RTL integration,
driver enablement, verification, and physical qualification before they can be driver enablement, verification, and physical qualification before they can be
advertised for a device or PDK. advertised for a device or PDK.
@@ -47,45 +47,36 @@ nodes, protocol bridges, and smart-home edge endpoints. It is MCU-first: boot
latency, sleep behavior, deterministic I/O, security, package cost, and usable latency, sleep behavior, deterministic I/O, security, package cost, and usable
energy per event take precedence over Linux compatibility. energy per event take precedence over Linux compatibility.
Tiny has two productization profiles: The first executable product is **Tiny-MCU**, an independent wired MCU
integration in `rtl/tiny`, selected with `configs/ci/ihp130-tiny.mk`. It has no
wireless IP, wireless protocol stack, or radio-specific companion integration.
Tiny-Connect, BLE, IEEE 802.15.4 and Wi-Fi are deferred product research.
- **Tiny-MCU** contains no radio and targets wired control or products that do ### First-release Architecture
not need connectivity on every unit.
- **Tiny-Connect** provides Bluetooth Low Energy and IEEE 802.15.4 capability.
The open digital-RTL baseline should expose a qualified SPI or SDIO host,
interrupt, reset, wake, and power-sequencing interface to a certified radio.
On-die or co-packaged RF becomes a product option only after suitable RF IP,
analog integration, PDK support, and regulatory qualification exist.
Wi-Fi 6 is an optional upper Tiny-Connect SKU rather than a baseline | Area | Tiny MCU baseline |
requirement. This avoids imposing its RF, memory, and active-power costs on
802.15.4 and Bluetooth endpoint products.
### Recommended Architecture
| Area | Product target |
| --- | --- | | --- | --- |
| CPU | One Hazard3 RV32IMAC core at 64-160 MHz, subject to PDK qualification | | CPU | One Hazard3 RV32IMC hart; A extension disabled until atomic bus semantics are qualified |
| On-chip memory | 256-512 KiB banked SRAM, boot ROM, and 8-32 KiB retention SRAM | | On-chip memory | 128 KiB macro-backed SRAM; 256–512 KiB and retention SRAM deferred |
| Code storage | QSPI flash with execute-in-place and authenticated recovery boot | | Code storage | XPI NOR boot, loaded into SRAM; authenticated recovery boot deferred |
| Memory model | No MMU and no external DRAM dependency | | Memory model | No MMU, HP hart, or external DRAM/PSRAM dependency |
| Interconnect | 32-bit RIB; bounded `INCR4` support for DMA and XIP, without a full AXI4 fabric | | Interconnect | 32-bit AXI4 data plane and APB4 control; no RIB/RIBP |
| Software | Freestanding SDK, bare metal, and Zephyr- or FreeRTOS-class RTOS ports | | Software | Existing freestanding SDK, RV32IM compiler target, CSR/IRQ-enabled acceptance firmware |
| Low power | Clock gating, switchable SRAM banks, RTC/event wake, retention, and a separately measured deep-sleep state | | Clock/reset | 24 MHz system clock, no PLL, 1 MHz CLINT timebase, watchdog and JTAG reset |
| Security | Immutable boot root, signed boot, OTP key material, PMP, TRNG, and symmetric/hash acceleration | | Edge I/O | 32 GPIO, two UARTs, two I2C controllers, two timers, four DMA channels, PWM, RTC, watchdog and XPI |
| Edge I/O | UART, SPI, I2C, PWM, ADC, I2S/PDM, USB full speed, and CAN FD selected by package profile |
No power-current number should be published until it is measured on a The normative [Tiny MCU contract](ip/tiny-soc.md) defines the AXI subset,
qualified physical implementation with the wake sources and retention state address/interrupt ABI, startup, errors and phased acceptance. Independent
specified. Tiny's acceptance criteria should include sleep-to-active latency, low-power clocks, clock/power gating, retention, secure boot, RTOS ports, USB,
energy per sensing/reporting cycle, and certified-radio interoperability, not SDIO, standalone general SPI, CAN and ADC remain future work. No measured
only CPU benchmarks. frequency ceiling, power-current, wake-latency or security claim follows from
this initial functional profile.
### Commercial Reference Points ### Commercial Reference Points
| Commercial SoC | Relevant axis | Position relative to Tiny | | Commercial SoC | Relevant axis | Position relative to Tiny |
| --- | --- | --- | | --- | --- | --- |
| [Espressif ESP32-H2](https://www.espressif.com/en/products/socs/esp32-h2) | Low-power RV32 MCU with Bluetooth LE and IEEE 802.15.4 | Primary Tiny-Connect endpoint and Thread/Zigbee market reference | | [Espressif ESP32-H2](https://www.espressif.com/en/products/socs/esp32-h2) | Low-power RV32 MCU with Bluetooth LE and IEEE 802.15.4 | Deferred Tiny-Connect research reference |
| [Espressif ESP32-C6](https://www.espressif.com/en/products/socs/esp32-c6) | RV32 high- and low-power cores with Wi-Fi 6, Bluetooth LE, and IEEE 802.15.4 | Upper connectivity reference; RF and protocol integration are well beyond a radio-companion Tiny baseline | | [Espressif ESP32-C6](https://www.espressif.com/en/products/socs/esp32-c6) | RV32 high- and low-power cores with Wi-Fi 6, Bluetooth LE, and IEEE 802.15.4 | Upper connectivity reference; RF and protocol integration are well beyond a radio-companion Tiny baseline |
| [Raspberry Pi RP2350](https://www.raspberrypi.com/products/rp2350/) | Dual Hazard3 option, 520 KiB SRAM, security, USB, and programmable I/O | Closest open-core MCU and deterministic-I/O reference, without integrated radio | | [Raspberry Pi RP2350](https://www.raspberrypi.com/products/rp2350/) | Dual Hazard3 option, 520 KiB SRAM, security, USB, and programmable I/O | Closest open-core MCU and deterministic-I/O reference, without integrated radio |
| [ST STM32U5](https://www.st.com/en/microcontrollers-microprocessors/stm32u5-series.html) | Ultra-low-power secure MCU family with large embedded memory and graphics options | Energy-efficiency, security, and industrial MCU ecosystem reference rather than an ISA-equivalent peer | | [ST STM32U5](https://www.st.com/en/microcontrollers-microprocessors/stm32u5-series.html) | Ultra-low-power secure MCU family with large embedded memory and graphics options | Energy-efficiency, security, and industrial MCU ecosystem reference rather than an ISA-equivalent peer |
@@ -416,7 +407,7 @@ software quality.
| Tier | Claim gate | | Tier | Claim gate |
| --- | --- | | --- | --- |
| Tiny | Measured active and sleep power, bounded wake latency, secure-boot validation, and qualified edge-connectivity operation | | Tiny | First release: wired-MCU boot, AXI/APB/DMA/IRQ and IHP130 evidence; future low-power/security claims require separate measurements and qualification |
| Mini | Repeatable Linux boot, at least 64 MiB usable main memory, native memory bursts, and management-controlled start/stop recovery | | Mini | Repeatable Linux boot, at least 64 MiB usable main memory, native memory bursts, and management-controlled start/stop recovery |
| Std | Full AXI4 ordering tests, coherent accelerator traffic, 1080p60 graphical desktop, audio playback, and NPU inference under concurrent DMA load | | Std | Full AXI4 ordering tests, coherent accelerator traffic, 1080p60 graphical desktop, audio playback, and NPU inference under concurrent DMA load |
| Pro | Four-hart coherent SMP stress, RV64 distribution boot, more-than-4-GiB memory validation, and concurrent GPU/NPU/video operation | | Pro | Four-hart coherent SMP stress, RV64 distribution boot, more-than-4-GiB memory validation, and concurrent GPU/NPU/video operation |
+3 -1
View File
@@ -7,7 +7,9 @@ drivers.
- `pdk/` contains the locked PDK setup helpers and managed PDK checkouts. - `pdk/` contains the locked PDK setup helpers and managed PDK checkouts.
- `smoke/syn/` contains Yosys synthesis and source-export integration. - `smoke/syn/` contains Yosys synthesis and source-export integration.
- `smoke/sta/` contains OpenSTA constraints and timing integration. - `smoke/sta/` contains OpenSTA constraints and timing integration.
- `librelane/` contains the open-source IHP130 core-hardening and full-chip pad-ring flows. - `librelane/` contains the open-source IHP130 implementation flows, organized
per product line: `librelane/mini/` (core hardening and full-chip pad ring)
and `librelane/tiny/` (Tiny full-chip pad ring).
- `ecc/` contains the open-source ICS55 padless-core hardening flow backed by - `ecc/` contains the open-source ICS55 padless-core hardening flow backed by
the locked ECOS Chip Compiler release. the locked ECOS Chip Compiler release.
- `commercial/` contains licensed-tool orchestration and flow logic. PDKs, - `commercial/` contains licensed-tool orchestration and flow logic. PDKs,
@@ -12,7 +12,7 @@ from typing import Any
ROOT = Path(__file__).resolve().parents[3] ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
from rtl.mini.pin_map.generate_pin_map import read_map # noqa: E402 from scripts.rtl.generate_pin_map import read_map # noqa: E402
from scripts.check_clock_reset_domains import validate # noqa: E402 from scripts.check_clock_reset_domains import validate # noqa: E402
from scripts.setup_helpers import atomic_write # noqa: E402 from scripts.setup_helpers import atomic_write # noqa: E402
+17 -40
View File
@@ -1,50 +1,27 @@
# LibreLane IHP130 Flows # LibreLane IHP130 Flows
This directory owns the open-source IHP130 implementation entry points for two This directory groups the open-source LibreLane/IHP130 implementation flows by
independent `retrosoc` deliverables: product line:
- `librelane-core` hardens `retrosoc_core`, a padless logical macro with the - [`mini/`](mini/README.md) hardens the Mini SoC deliverables (`retrosoc_core`
canonical external pin contract and the three USB packet SRAM macros. padless macro and the bare-die `retrosoc_asic` pad-ring chip) for
- `librelane-chip` implements the bare-die `retrosoc_asic` package-facing SoC, `SOC=MINI` profiles such as `configs/ci/ihp130.mk`.
including its pad ring, bondpads, fillers, corners, seal ring, and the same - [`tiny/`](tiny/README.md) implements the bare-die Tiny MCU
SRAM macros. (`retrosoc_tiny_asic`, pad-ring chip only) for `SOC=TINY` with
`configs/ci/ihp130-tiny.mk`.
- `bondpad/` holds the shared 70 um bondpad LEF collateral used by both chip
flows.
These are separate single-level runs. The chip flow does not consume the core The top-level `Makefile` includes `mini/Makefile` or `tiny/Makefile` according
run as a hierarchical macro, so either artifact can be developed and reviewed to `SOC`, so the target names (`librelane-doctor`, `librelane-chip`,
without a two-level integration dependency. `librelane-package`, and the Mini-only `librelane-core`) are uniform across
product lines.
The flow consumes the committed `configs/ci/ihp130.mk` profile, canonical SoC
pin map, and IHP-Open-PDK revision in `dependencies/dependencies.lock.json`.
LibreLane reads the upstream `ihp-sg13g2/libs.tech/librelane` configuration
directly through manual-PDK mode. Generated RTL, SDC, configuration, run
databases, reports, and final views are written below the selected
`build/<variant>/physical/librelane/{core,chip}/` directory.
Use the LibreLane 3.0.5 environment supplied by the development shell:
```sh
make CONFIG=configs/ci/ihp130.mk librelane-doctor
make CONFIG=configs/ci/ihp130.mk librelane-core
make CONFIG=configs/ci/ihp130.mk librelane-chip
make CONFIG=configs/ci/ihp130.mk librelane-core-package
make CONFIG=configs/ci/ihp130.mk librelane-package
```
The chip target places all 111 signal pads, 80 core/IO power and ground pads,
four corners, I/O fillers, and 70 um bondpads. It is a bare-die plan, not a
QFN/BGA package definition. The SDC constrains the external clock at 72 MHz;
mid-PnR timing report passes are skipped because LibreLane 3.0.5 emits 1000
expanded paths per group, while final post-RCX multi-corner STA remains enabled.
Both lines require the LibreLane 3.0.5 development environment, the locked
IHP-Open-PDK checkout under `physical/pdk/`, and the locked OpenSTA 3.0.0.
`PDK=GF180`, `PDK=SKY130`, and `PDK=ICS55` are intentionally rejected until `PDK=GF180`, `PDK=SKY130`, and `PDK=ICS55` are intentionally rejected until
their technology-specific LibreLane adapters, pad cells, macro collateral, and their technology-specific LibreLane adapters, pad cells, macro collateral, and
signoff decks are qualified. The target split is technology-neutral so those signoff decks are qualified.
adapters can reuse the core/chip interface when they are ready.
IHP SG13G2 does not currently provide pad cells implementing retroSoC's
programmable pull-up, pull-down, or Schmitt controls. The IHP implementation
uses input-only, 4 mA output, and 4 mA bidirectional cells; unsupported GPIO
pad-control features must not be represented as production-qualified behavior.
A successful open-source run is evidence for implementation development, not a A successful open-source run is evidence for implementation development, not a
foundry production-signoff claim. Release review still requires qualified foundry production-signoff claim. Release review still requires qualified
@@ -1,11 +1,11 @@
LIBRELANE_FLOW_ROOT := $(ROOT_PATH)/physical/librelane LIBRELANE_FLOW_ROOT := $(ROOT_PATH)/physical/librelane/mini
LIBRELANE_TARGET ?= chip LIBRELANE_TARGET ?= chip
LIBRELANE_RUN_ROOT := $(VARIANT_ROOT)/physical/librelane/$(LIBRELANE_TARGET) LIBRELANE_RUN_ROOT := $(VARIANT_ROOT)/physical/librelane/mini/$(LIBRELANE_TARGET)
LIBRELANE_INPUT_DIR := $(LIBRELANE_RUN_ROOT)/input LIBRELANE_INPUT_DIR := $(LIBRELANE_RUN_ROOT)/input
LIBRELANE_RTL := $(LIBRELANE_INPUT_DIR)/retrosoc_asic_sources.sv LIBRELANE_RTL := $(LIBRELANE_INPUT_DIR)/retrosoc_asic_sources.sv
LIBRELANE_SDC := $(LIBRELANE_INPUT_DIR)/retrosoc_$(LIBRELANE_TARGET).sdc LIBRELANE_SDC := $(LIBRELANE_INPUT_DIR)/retrosoc_$(LIBRELANE_TARGET).sdc
LIBRELANE_BONDPAD_GDS := $(LIBRELANE_INPUT_DIR)/bondpad_70x70.gds LIBRELANE_BONDPAD_GDS := $(LIBRELANE_INPUT_DIR)/bondpad_70x70.gds
LIBRELANE_BONDPAD_LEF := $(LIBRELANE_FLOW_ROOT)/bondpad/bondpad_70x70.lef LIBRELANE_BONDPAD_LEF := $(ROOT_PATH)/physical/librelane/bondpad/bondpad_70x70.lef
LIBRELANE_CONFIG := $(LIBRELANE_RUN_ROOT)/config.json LIBRELANE_CONFIG := $(LIBRELANE_RUN_ROOT)/config.json
LIBRELANE_LOG := $(LIBRELANE_RUN_ROOT)/librelane.log LIBRELANE_LOG := $(LIBRELANE_RUN_ROOT)/librelane.log
LIBRELANE_RESULT := $(LIBRELANE_RUN_ROOT)/result.json LIBRELANE_RESULT := $(LIBRELANE_RUN_ROOT)/result.json
@@ -65,7 +65,7 @@ endif
librelane-check-pdk: librelane-check-pdk:
@test '$(PDK)' = IHP130 || { \ @test '$(PDK)' = IHP130 || { \
echo 'physical/librelane currently implements only PDK=IHP130' >&2; exit 2; } echo 'physical/librelane/mini currently implements only PDK=IHP130' >&2; exit 2; }
@test '$(HAVE_PLL)' = NO || { \ @test '$(HAVE_PLL)' = NO || { \
echo 'the IHP130 LibreLane flow has no qualified crystal PAD' >&2; exit 2; } echo 'the IHP130 LibreLane flow has no qualified crystal PAD' >&2; exit 2; }
+52
View File
@@ -0,0 +1,52 @@
# Mini LibreLane IHP130 Flows
This directory owns the Mini product line's open-source IHP130 implementation
entry points for two independent `retrosoc` deliverables:
- `librelane-core` hardens `retrosoc_core`, a padless logical macro with the
canonical external pin contract and the three USB packet SRAM macros.
- `librelane-chip` implements the bare-die `retrosoc_asic` package-facing SoC,
including its pad ring, bondpads, fillers, corners, seal ring, and the same
SRAM macros.
These are separate single-level runs. The chip flow does not consume the core
run as a hierarchical macro, so either artifact can be developed and reviewed
without a two-level integration dependency.
The flow consumes the committed `configs/ci/ihp130.mk` profile, canonical SoC
pin map, and IHP-Open-PDK revision in `dependencies/dependencies.lock.json`.
LibreLane reads the upstream `ihp-sg13g2/libs.tech/librelane` configuration
directly through manual-PDK mode. Generated RTL, SDC, configuration, run
databases, reports, and final views are written below the selected
`build/<variant>/physical/librelane/mini/{core,chip}/` directory.
Use the LibreLane 3.0.5 environment supplied by the development shell:
```sh
make CONFIG=configs/ci/ihp130.mk librelane-doctor
make CONFIG=configs/ci/ihp130.mk librelane-core
make CONFIG=configs/ci/ihp130.mk librelane-chip
make CONFIG=configs/ci/ihp130.mk librelane-core-package
make CONFIG=configs/ci/ihp130.mk librelane-package
```
The chip target places all 111 signal pads, 80 core/IO power and ground pads,
four corners, I/O fillers, and 70 um bondpads. It is a bare-die plan, not a
QFN/BGA package definition. The SDC constrains the external clock at 72 MHz;
mid-PnR timing report passes are skipped because LibreLane 3.0.5 emits 1000
expanded paths per group, while final post-RCX multi-corner STA remains enabled.
`PDK=GF180`, `PDK=SKY130`, and `PDK=ICS55` are intentionally rejected until
their technology-specific LibreLane adapters, pad cells, macro collateral, and
signoff decks are qualified. The target split is technology-neutral so those
adapters can reuse the core/chip interface when they are ready.
IHP SG13G2 does not currently provide pad cells implementing retroSoC's
programmable pull-up, pull-down, or Schmitt controls. The IHP implementation
uses input-only, 4 mA output, and 4 mA bidirectional cells; unsupported GPIO
pad-control features must not be represented as production-qualified behavior.
A successful open-source run is evidence for implementation development, not a
foundry production-signoff claim. Release review still requires qualified
foundry decks, package and bond planning, electrical/ESD review, and approved
waivers.
File renamed without changes.
File renamed without changes.
@@ -11,10 +11,10 @@ import sys
from pathlib import Path from pathlib import Path
ROOT = Path(__file__).resolve().parents[3] ROOT = Path(__file__).resolve().parents[4]
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
from librelane.flows import Flow # noqa: E402 from librelane.flows import Flow # noqa: E402
from rtl.mini.pin_map.generate_pin_map import IHP130_POWER_PAD_COUNTS # noqa: E402 from scripts.rtl.generate_pin_map import IHP130_POWER_PAD_COUNTS # noqa: E402
from scripts.setup_helpers import atomic_write # noqa: E402 from scripts.setup_helpers import atomic_write # noqa: E402
@@ -11,9 +11,9 @@ import sys
from pathlib import Path from pathlib import Path
ROOT = Path(__file__).resolve().parents[3] ROOT = Path(__file__).resolve().parents[4]
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
from rtl.mini.pin_map.generate_pin_map import ( # noqa: E402 from scripts.rtl.generate_pin_map import ( # noqa: E402
IHP130_POWER_PAD_COUNTS, IHP130_POWER_PAD_COUNTS,
Pad, Pad,
ihp130_pad_instance, ihp130_pad_instance,
@@ -206,7 +206,7 @@ def build_config(args: argparse.Namespace) -> dict[str, object]:
"SIGNOFF_SDC_FILE": config_path(args.sdc, config_dir), "SIGNOFF_SDC_FILE": config_path(args.sdc, config_dir),
"FALLBACK_SDC": config_path(args.sdc, config_dir), "FALLBACK_SDC": config_path(args.sdc, config_dir),
"STA_EXTRA_CORNER_TCL_FILE": config_path( "STA_EXTRA_CORNER_TCL_FILE": config_path(
ROOT / "physical/librelane/sta_report_limit.tcl", config_dir ROOT / "physical/librelane/mini/sta_report_limit.tcl", config_dir
), ),
"CLOCK_PORT": list(CLOCK_PORTS), "CLOCK_PORT": list(CLOCK_PORTS),
"CLOCK_PERIOD": 1_000_000_000 / args.ext_clk_hz, "CLOCK_PERIOD": 1_000_000_000 / args.ext_clk_hz,
@@ -259,7 +259,7 @@ def build_config(args: argparse.Namespace) -> dict[str, object]:
"CORE_AREA": [365, 365, 7635, 7635], "CORE_AREA": [365, 365, 7635, 7635],
"PL_TARGET_DENSITY_PCT": 45, "PL_TARGET_DENSITY_PCT": 45,
"PDN_CORE_RING_CONNECT_TO_PADS": True, "PDN_CORE_RING_CONNECT_TO_PADS": True,
"PAD_CFG": config_path(ROOT / "physical/librelane/pad_cfg.tcl", config_dir), "PAD_CFG": config_path(ROOT / "physical/librelane/mini/pad_cfg.tcl", config_dir),
"PAD_BONDPAD_NAME": "bondpad_70x70", "PAD_BONDPAD_NAME": "bondpad_70x70",
"EXTRA_GDS": [config_path(args.bondpad_gds, config_dir)], "EXTRA_GDS": [config_path(args.bondpad_gds, config_dir)],
"EXTRA_LEFS": [config_path(args.bondpad_lef, config_dir)], "EXTRA_LEFS": [config_path(args.bondpad_lef, config_dir)],
@@ -9,9 +9,9 @@ import sys
from pathlib import Path from pathlib import Path
ROOT = Path(__file__).resolve().parents[3] ROOT = Path(__file__).resolve().parents[4]
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
from rtl.mini.pin_map.generate_pin_map import Pad, ihp130_pad_instance, read_map # noqa: E402 from scripts.rtl.generate_pin_map import Pad, ihp130_pad_instance, read_map # noqa: E402
from scripts.check_clock_reset_domains import validate # noqa: E402 from scripts.check_clock_reset_domains import validate # noqa: E402
from scripts.setup_helpers import atomic_write # noqa: E402 from scripts.setup_helpers import atomic_write # noqa: E402
@@ -91,7 +91,7 @@ def render(args: argparse.Namespace) -> str:
groups[clock]["outputs"].append(pad.name) groups[clock]["outputs"].append(pad.name)
lines = [ lines = [
"# Generated by physical/librelane/scripts/generate_sdc.py; do not edit.", "# Generated by physical/librelane/mini/scripts/generate_sdc.py; do not edit.",
"current_design $::env(DESIGN_NAME)", "current_design $::env(DESIGN_NAME)",
"set_units -time ns", "set_units -time ns",
"", "",
+133
View File
@@ -0,0 +1,133 @@
LIBRELANE_FLOW_ROOT := $(ROOT_PATH)/physical/librelane/tiny
LIBRELANE_RUN_ROOT := $(VARIANT_ROOT)/physical/librelane/tiny/chip
LIBRELANE_INPUT_DIR := $(LIBRELANE_RUN_ROOT)/input
LIBRELANE_RTL := $(LIBRELANE_INPUT_DIR)/retrosoc_tiny_asic_sources.sv
LIBRELANE_SDC := $(LIBRELANE_INPUT_DIR)/retrosoc_tiny_chip.sdc
LIBRELANE_BONDPAD_GDS := $(LIBRELANE_INPUT_DIR)/bondpad_70x70.gds
LIBRELANE_BONDPAD_LEF := $(ROOT_PATH)/physical/librelane/bondpad/bondpad_70x70.lef
LIBRELANE_CONFIG := $(LIBRELANE_RUN_ROOT)/config.json
LIBRELANE_LOG := $(LIBRELANE_RUN_ROOT)/librelane.log
LIBRELANE_RESULT := $(LIBRELANE_RUN_ROOT)/result.json
LIBRELANE_META_RESULT := $(META_DIR)/librelane-chip.json
LIBRELANE_DOCTOR_RESULT := $(META_DIR)/librelane-chip-doctor.json
LIBRELANE_FINAL_DIR := $(LIBRELANE_RUN_ROOT)/final
LIBRELANE_ARCHIVE := $(LIBRELANE_RUN_ROOT)/retrosoc-tiny-ihp130-chip.tar.gz
LIBRELANE_RUN_TAG ?= current
LIBRELANE_PDK_ROOT := $(ROOT_PATH)/physical/pdk/IHP-Open-PDK
LIBRELANE_PDK := ihp-sg13g2
LIBRELANE_SCL := sg13g2_stdcell
LIBRELANE_PAD := sg13g2_io
LIBRELANE_KLAYOUT_PATH := $(LIBRELANE_PDK_ROOT)/$(LIBRELANE_PDK)/libs.tech/klayout
LIBRELANE_BONDPAD_SCRIPT := $(LIBRELANE_PDK_ROOT)/$(LIBRELANE_PDK)/libs.tech/klayout/tech/scripts/bondpad.py
LIBRELANE_PDN := $(LIBRELANE_FLOW_ROOT)/pdn_cfg.tcl
LIBRELANE_SRAM_VH := $(LIBRELANE_FLOW_ROOT)/sram_blackboxes.vh
LIBRELANE_STA_REPORT_LIMIT := $(LIBRELANE_FLOW_ROOT)/sta_report_limit.tcl
LIBRELANE_CLOCK_DOMAINS := $(RTL_PATH)/integration/clock_reset_domains.json
LIBRELANE_PIN_MAP := $(RTL_PATH)/pin_map/pin_map.json
LIBRELANE_TRACKED_RTL := $(shell git -C $(ROOT_PATH) ls-files \
'rtl/**/*.sv' 'rtl/**/*.svh' 'rtl/**/*.v' 'rtl/**/*.vh')
LIBRELANE_SOURCE_PYTHON ?= /usr/bin/python3
LIBRELANE_PYTHON ?= python3
LIBRELANE_COMMAND := $(LIBRELANE_PYTHON) -m librelane
# LibreLane 3.0.5 writes 1000 expanded paths per group at every mid-PnR STA.
# Final multi-corner RCX STA remains mandatory; skip only the redundant reports.
LIBRELANE_MID_PNR_SKIP ?= --skip OpenROAD.STAMidPNR
ifneq ($(filter librelane-input librelane-doctor librelane-chip \
librelane-openroad librelane-klayout librelane-package,$(MAKECMDGOALS)),)
FLOW_PYTHON := $(LIBRELANE_SOURCE_PYTHON)
endif
LIBRELANE_FEATURE_ARGS := \
$(if $(filter YES,$(HAVE_PLL)),--have-pll) \
$(if $(filter YES,$(HAVE_SRAM_IF)),--have-sram-if) \
$(if $(filter YES,$(HAVE_SRAM_MACRO)),--have-sram-macro) \
$(if $(filter YES,$(HAVE_SVA)),--have-sva)
LIBRELANE_CONFIG_DEPS := $(LIBRELANE_RTL) $(LIBRELANE_SDC) \
$(LIBRELANE_PDN) $(LIBRELANE_SRAM_VH) \
$(LIBRELANE_STA_REPORT_LIMIT) $(LIBRELANE_BONDPAD_GDS) $(LIBRELANE_BONDPAD_LEF) \
$(LIBRELANE_FLOW_ROOT)/scripts/generate_chip_config.py
.PHONY: librelane-check-pdk librelane-input librelane-doctor librelane-chip \
librelane-openroad librelane-klayout librelane-package
librelane-check-pdk:
@test '$(PDK)' = IHP130 || { \
echo 'physical/librelane/tiny currently implements only PDK=IHP130' >&2; exit 2; }
@test '$(HAVE_PLL)' = NO || { \
echo 'the IHP130 LibreLane flow has no qualified crystal PAD' >&2; exit 2; }
$(LIBRELANE_RTL): $(FILELIST_STAMP) $(LIBRELANE_TRACKED_RTL) \
$(ROOT_PATH)/physical/smoke/syn/tools/export_soc_sources.py | librelane-check-pdk
@mkdir -p $(@D)
$(FLOW_PYTHON) $(ROOT_PATH)/physical/smoke/syn/tools/export_soc_sources.py sv \
--output-dir $(@D) --soc TINY --pdk IHP130 --simu $(SIMU) \
--jtag-idcode $(JTAG_IDCODE) \
--ext-clk-hz $(EXT_CLK_HZ) --aud-clk-hz $(AUD_CLK_HZ) \
--clint-timebase-hz $(CLINT_TIMEBASE_HZ) \
--source-filelist-dir $(GENERATED_FL_DIR) \
--librelane-safe \
$(LIBRELANE_FEATURE_ARGS)
$(LIBRELANE_SDC): $(LIBRELANE_FLOW_ROOT)/scripts/generate_sdc.py \
$(LIBRELANE_CLOCK_DOMAINS) $(LIBRELANE_PIN_MAP) | librelane-check-pdk
@mkdir -p $(@D)
$(FLOW_PYTHON) $(LIBRELANE_FLOW_ROOT)/scripts/generate_sdc.py \
--domains $(LIBRELANE_CLOCK_DOMAINS) --pin-map $(LIBRELANE_PIN_MAP) \
--ext-clk-hz $(EXT_CLK_HZ) --output $@
$(LIBRELANE_BONDPAD_GDS): $(LIBRELANE_BONDPAD_SCRIPT) | librelane-check-pdk
@mkdir -p $(@D)
KLAYOUT_PATH=$(LIBRELANE_KLAYOUT_PATH) klayout -n sg13g2 -zz \
-r $(LIBRELANE_BONDPAD_SCRIPT) -rd diameter=70.0 -rd shape=square \
-rd output=$@
@test -s $@
$(LIBRELANE_CONFIG): $(LIBRELANE_CONFIG_DEPS)
$(FLOW_PYTHON) $(LIBRELANE_FLOW_ROOT)/scripts/generate_chip_config.py \
--pin-map $(LIBRELANE_PIN_MAP) --rtl $(LIBRELANE_RTL) --sdc $(LIBRELANE_SDC) \
--pdn $(LIBRELANE_PDN) --bondpad-gds $(LIBRELANE_BONDPAD_GDS) \
--bondpad-lef $(LIBRELANE_BONDPAD_LEF) --sram-vh $(LIBRELANE_SRAM_VH) \
--ext-clk-hz $(EXT_CLK_HZ) --sram-size-kib $(SRAM_SIZE_KIB) \
$(if $(filter YES,$(HAVE_SRAM_MACRO)),--have-sram-macro) \
--output $@
librelane-input: $(LIBRELANE_CONFIG)
$(LIBRELANE_DOCTOR_RESULT): $(LIBRELANE_CONFIG) \
$(LIBRELANE_FLOW_ROOT)/scripts/doctor.py
@mkdir -p $(@D)
$(LIBRELANE_PYTHON) $(LIBRELANE_FLOW_ROOT)/scripts/doctor.py \
--root $(ROOT_PATH) --config $(LIBRELANE_CONFIG) --lock $(LOCK_FILE) \
--pin-map $(LIBRELANE_PIN_MAP) --pdk-root $(LIBRELANE_PDK_ROOT) --output $@
librelane-doctor: $(LIBRELANE_DOCTOR_RESULT)
librelane-chip: librelane-doctor manifest
$(FLOW_PYTHON) $(ROOT_PATH)/scripts/run_flow.py --tool librelane \
--log $(LIBRELANE_LOG) --result $(LIBRELANE_RESULT) --cwd $(LIBRELANE_RUN_ROOT) -- \
$(LIBRELANE_COMMAND) $(LIBRELANE_CONFIG) --manual-pdk --pdk $(LIBRELANE_PDK) \
--pdk-root $(LIBRELANE_PDK_ROOT) --scl $(LIBRELANE_SCL) --pad $(LIBRELANE_PAD) \
--run-tag $(LIBRELANE_RUN_TAG) --overwrite --jobs $(JOBS) $(LIBRELANE_MID_PNR_SKIP) \
--condensed --hide-progress-bar --save-views-to $(LIBRELANE_FINAL_DIR)
@mkdir -p $(META_DIR)
@cp $(LIBRELANE_RESULT) $(LIBRELANE_META_RESULT)
librelane-openroad: librelane-doctor
cd $(LIBRELANE_RUN_ROOT) && $(LIBRELANE_COMMAND) $(LIBRELANE_CONFIG) --manual-pdk \
--pdk $(LIBRELANE_PDK) --pdk-root $(LIBRELANE_PDK_ROOT) \
--scl $(LIBRELANE_SCL) --pad $(LIBRELANE_PAD) \
--run-tag $(LIBRELANE_RUN_TAG) --last-run --flow OpenInOpenROAD
librelane-klayout: librelane-doctor
cd $(LIBRELANE_RUN_ROOT) && $(LIBRELANE_COMMAND) $(LIBRELANE_CONFIG) --manual-pdk \
--pdk $(LIBRELANE_PDK) --pdk-root $(LIBRELANE_PDK_ROOT) \
--scl $(LIBRELANE_SCL) --pad $(LIBRELANE_PAD) \
--run-tag $(LIBRELANE_RUN_TAG) --last-run --flow OpenInKLayout
librelane-package: librelane-chip
$(FLOW_PYTHON) $(LIBRELANE_FLOW_ROOT)/scripts/package.py \
--root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) \
--run-root $(LIBRELANE_RUN_ROOT) --output $(LIBRELANE_ARCHIVE)
+52
View File
@@ -0,0 +1,52 @@
# Tiny LibreLane IHP130 Chip Flow
This directory owns the Tiny product line's open-source IHP130 implementation
entry point: `librelane-chip` implements the bare-die `retrosoc_tiny_asic`
MCU, including its pad ring, bondpads, fillers, corners, seal ring, and the
32 on-chip SRAM bank macros.
The flow consumes the committed `configs/ci/ihp130-tiny.mk` profile, the
canonical Tiny pin map (`rtl/tiny/pin_map/pin_map.json`), the Tiny clock/reset
inventory (`rtl/tiny/integration/clock_reset_domains.json`), and the
IHP-Open-PDK revision in `dependencies/dependencies.lock.json`. LibreLane reads
the upstream `ihp-sg13g2/libs.tech/librelane` configuration directly through
manual-PDK mode. Generated RTL, SDC, configuration, run databases, reports, and
final views are written below `build/<variant>/physical/librelane/tiny/chip/`.
Use the LibreLane 3.0.5 development shell (`~/squashfs-root/AppRun`) and put
the locked OpenSTA 3.0.0 first on `PATH`:
```sh
~/squashfs-root/AppRun bash -c 'export PATH=/nfs/home/miaoyuchi/artifact-opensta/bin:$PATH; cd <repo>'
make CONFIG=configs/ci/ihp130-tiny.mk librelane-doctor
make CONFIG=configs/ci/ihp130-tiny.mk librelane-chip
make CONFIG=configs/ci/ihp130-tiny.mk librelane-package
```
## Pad Ring
The Tiny pin map declares 52 signal PADs (external clock/reset, JTAG, two
UARTs, 32 GPIOs, and the quad-XPI NOR flash interface). Tiny ships a reduced
supply set of 8 VDD + 8 VSS + 8 IOVDD + 8 IOVSS (two pairs of each per side),
84 placed PADs in total. The reduction from the Mini line's 24/24/16/16 follows
the taped-out Basilisk IHP SG13G2 SoC pad frame (68 signals with 24/24/16/16
supplies feeding a Linux-capable CVA6 at a much higher clock and IO rate):
Tiny's 24 MHz Hazard3 core draws a few mA, and even with all 52 4 mA outputs
switching simultaneously each IO supply pair carries only ~26 mA.
The side assignment is south = clock/reset/JTAG/UART, north = XPI,
east = GPIO[15:0], west = GPIO[31:16]; the `power_pads` counts come from
`rtl/tiny/pin_map/pin_map.json` and must match `pad_cfg.tcl`.
## Constraints
The SDC constrains the external clock at 24 MHz and JTAG at 10 MHz, defines a
generated system clock at the top-level clock buffer, and keeps the two groups
asynchronous. Mid-PnR timing report passes are skipped because LibreLane 3.0.5
emits 1000 expanded paths per group; final post-RCX multi-corner STA remains
enabled.
A successful open-source run is evidence for implementation development, not a
foundry production-signoff claim. Release review still requires qualified
foundry decks, package and bond planning, electrical/ESD review, and approved
waivers.
+17
View File
@@ -0,0 +1,17 @@
# IHP SG13G2 supply pads are physical-only cells and are not retained by Yosys.
# Tiny ships two core and two IO supply pairs per pad-ring side (8/8/8/8).
foreach {kind master count} {
vdd sg13g2_IOPadVdd 8
vss sg13g2_IOPadVss 8
iovdd sg13g2_IOPadIOVdd 8
iovss sg13g2_IOPadIOVss 8
} {
for {set index 0} {$index < $count} {incr index} {
set instance_name [format {%s_pads[%d].%s_pad} $kind $index $kind]
set db_block [ord::get_db_block]
if {[$db_block findInst $instance_name] == "NULL"} {
make_instance $instance_name $master
}
}
}
source $::env(SCRIPTS_DIR)/openroad/common/pad_cfg.tcl
+193
View File
@@ -0,0 +1,193 @@
# Copyright 2025-2026 LibreLane Contributors and retroSoC Authors
# SPDX-License-Identifier: Apache-2.0
# Tiny IHP130 chip PDN: LibreLane default sg13g2 topology (M1 rails connect to
# the TopMetal1 stripe network directly) plus a macro grid that ties the SRAM
# banks' Metal4 power columns into the stripe network with Metal5 cross straps.
source $::env(SCRIPTS_DIR)/openroad/common/io.tcl
source $::env(SCRIPTS_DIR)/openroad/common/set_global_connections.tcl
set_global_connections
# Use the PDK-native alternating routing directions (H/V/H/V/H).
set secondary_supplies []
foreach vdd $::env(VDD_NETS) gnd $::env(GND_NETS) {
if {$vdd != $::env(VDD_NET)} {
lappend secondary_supplies $vdd
set db_net [[ord::get_db_block] findNet $vdd]
if {$db_net == "NULL"} {
set net [odb::dbNet_create [ord::get_db_block] $vdd]
$net setSpecial
$net setSigType "POWER"
}
}
if {$gnd != $::env(GND_NET)} {
lappend secondary_supplies $gnd
set db_net [[ord::get_db_block] findNet $gnd]
if {$db_net == "NULL"} {
set net [odb::dbNet_create [ord::get_db_block] $gnd]
$net setSpecial
$net setSigType "GROUND"
}
}
}
# I/O rails are pad-ring internal and are absent from the synthesized top-level.
foreach {net signal_type} {IOVDD POWER IOVSS GROUND} {
set db_net [[ord::get_db_block] findNet $net]
if {$db_net == "NULL"} {
set db_net [odb::dbNet_create [ord::get_db_block] $net]
$db_net setSpecial
$db_net setSigType $signal_type
}
}
# IHP IO cells carry both core and external-IO power rails on lower-case pins.
add_global_connection -net $::env(VDD_NET) -inst_pattern .* -pin_pattern vdd -power
add_global_connection -net $::env(GND_NET) -inst_pattern .* -pin_pattern vss -ground
add_global_connection -net IOVDD -inst_pattern .* -pin_pattern iovdd -power
add_global_connection -net IOVSS -inst_pattern .* -pin_pattern iovss -ground
global_connect -verbose
# connect_by_abutment assigns IHP pad rails to corner-local *_RING nets. Rebind
# those physical pad and filler pins so the PDN knows they are VDD/VSS terminals.
proc reconnect_ihp_padring_rails {block} {
foreach {net_name pin_name} {
VDD vdd
VSS vss
IOVDD iovdd
IOVSS iovss
} {
set net [$block findNet $net_name]
foreach inst [$block getInsts] {
set master_name [[$inst getMaster] getName]
if {![string match "sg13g2_IOPad*" $master_name] &&
![string match "sg13g2_Filler*" $master_name] &&
![string match "sg13g2_Corner*" $master_name]} {
continue
}
foreach iterm [$inst getITerms] {
if {[[$iterm getMTerm] getName] != $pin_name} {
continue
}
odb::dbITerm_disconnect $iterm
odb::dbITerm_connect $iterm $net
}
}
}
}
reconnect_ihp_padring_rails [ord::get_db_block]
set_voltage_domain -name CORE -power $::env(VDD_NET) -ground $::env(GND_NET) \
-secondary_power $secondary_supplies
set stdcell_grid_args [list]
if {$::env(PDN_ENABLE_PINS) == 1} {
lappend stdcell_grid_args -pins "$::env(PDN_VERTICAL_LAYER) $::env(PDN_HORIZONTAL_LAYER)"
}
define_pdn_grid \
-name stdcell_grid \
-starts_with POWER \
-voltage_domain CORE \
{*}$stdcell_grid_args
add_pdn_stripe \
-grid stdcell_grid \
-layer $::env(PDN_VERTICAL_LAYER) \
-width $::env(PDN_VWIDTH) \
-pitch $::env(PDN_VPITCH) \
-offset $::env(PDN_VOFFSET) \
-spacing $::env(PDN_VSPACING) \
-starts_with POWER \
-extend_to_core_ring
add_pdn_stripe \
-grid stdcell_grid \
-layer $::env(PDN_HORIZONTAL_LAYER) \
-width $::env(PDN_HWIDTH) \
-pitch $::env(PDN_HPITCH) \
-offset $::env(PDN_HOFFSET) \
-spacing $::env(PDN_HSPACING) \
-starts_with POWER \
-extend_to_core_ring
# A low-metal ring just inside the core edge collects the Metal1 rails; the
# top-metal stripe mesh bridges to it where the stripes cross the ring legs.
add_pdn_ring \
-grid stdcell_grid \
-layers "Metal2 Metal3" \
-widths "0.52 0.52" \
-spacings "0.8 0.8" \
-core_offsets "1.6 1.6" \
-starts_with POWER \
-add_connect
if {$::env(PDN_ENABLE_RAILS) == 1} {
add_pdn_stripe \
-grid stdcell_grid \
-layer $::env(PDN_RAIL_LAYER) \
-width $::env(PDN_RAIL_WIDTH) \
-followpins \
-extend_to_core_ring
add_pdn_connect -grid stdcell_grid -layers "Metal1 Metal2"
add_pdn_connect -grid stdcell_grid -layers "Metal1 Metal3"
# The rail network meets the top-metal mesh at the low ring legs and at
# every Metal1 rail / TopMetal1 stripe crossing (covers the standard-cell
# channels between SRAM banks as well).
add_pdn_connect -grid stdcell_grid -layers "Metal3 TopMetal1"
add_pdn_connect -grid stdcell_grid -layers "Metal1 TopMetal1"
add_pdn_connect -grid stdcell_grid -layers "Metal2 TopMetal2"
}
if {$::env(PDN_CORE_RING) == 1} {
add_pdn_ring \
-grid stdcell_grid \
-layers "$::env(PDN_VERTICAL_LAYER) $::env(PDN_HORIZONTAL_LAYER)" \
-widths "$::env(PDN_CORE_RING_VWIDTH) $::env(PDN_CORE_RING_HWIDTH)" \
-spacings "$::env(PDN_CORE_RING_VSPACING) $::env(PDN_CORE_RING_HSPACING)" \
-core_offsets "$::env(PDN_CORE_RING_VOFFSET) $::env(PDN_CORE_RING_HOFFSET)" \
-connect_to_pads \
-connect_to_pad_layers TopMetal2 \
-add_connect
}
# Tiny hardens a single SRAM master: the 128 KiB on-chip RAM banks. Their power
# pins are vertical Metal4 columns spanning the bank height; horizontal Metal5
# straps cross every column and hand off to the stdcell TopMetal1 stripes
# outside the bank boundary.
set sram_cells [list \
RM_IHPSG13_1P_1024x32_c2_bm_bist]
define_pdn_grid \
-macro \
-cells $sram_cells \
-name sram_bank_grid \
-grid_over_boundary \
-voltage_domains CORE \
-starts_with POWER
add_pdn_ring \
-grid sram_bank_grid \
-layers "Metal2 Metal3" \
-widths "0.52 0.52" \
-spacings "0.8 0.8" \
-core_offsets "1.6 1.6" \
-starts_with POWER \
-add_connect
add_pdn_stripe \
-grid sram_bank_grid \
-layer Metal5 \
-width 2.5 \
-pitch 67.24 \
-offset 10.0 \
-spacing 2.5 \
-starts_with POWER
add_pdn_connect -grid sram_bank_grid -layers "Metal4 Metal5"
add_pdn_connect -grid sram_bank_grid -layers "Metal5 TopMetal1"
add_pdn_connect -grid sram_bank_grid -layers "Metal2 TopMetal1"
add_pdn_connect -grid sram_bank_grid -layers "Metal3 TopMetal1"
add_pdn_connect -grid sram_bank_grid -layers "TopMetal1 TopMetal2"
# Rails trapped in the channels between banks tie into the bank rings.
add_pdn_connect -grid sram_bank_grid -layers "Metal1 Metal2"
add_pdn_connect -grid sram_bank_grid -layers "Metal1 Metal3"
+165
View File
@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""Validate the local LibreLane/IHP130 environment and Tiny chip configuration."""
from __future__ import annotations
import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
sys.path.insert(0, str(ROOT))
from librelane.flows import Flow # noqa: E402
from scripts.rtl.generate_pin_map import read_power_pad_counts # noqa: E402
from scripts.setup_helpers import atomic_write # noqa: E402
EXPECTED_LIBRELANE_VERSION = "LibreLane v3.0.5"
EXPECTED_OPENSTA_VERSION = "3.0.0"
EXPECTED_SIGNAL_PADS = 52
def command_output(command: list[str]) -> str:
return subprocess.check_output(command, text=True, stderr=subprocess.STDOUT).strip()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--lock", type=Path, required=True)
parser.add_argument("--pin-map", type=Path, required=True)
parser.add_argument("--pdk-root", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
errors: list[str] = []
details: dict[str, object] = {}
for tool in ("librelane", "klayout", "sta"):
path = shutil.which(tool)
details[f"{tool}_path"] = path
if path is None:
errors.append(f"required tool is missing: {tool}")
if shutil.which("librelane") is not None:
version = command_output(
[sys.executable, "-m", "librelane", "--version"]
).splitlines()[0]
details["librelane_version"] = version
if version != EXPECTED_LIBRELANE_VERSION:
errors.append(f"expected {EXPECTED_LIBRELANE_VERSION}, found {version or '<empty>'}")
if shutil.which("sta") is not None:
version = command_output(["sta", "-version"])
details["opensta_version"] = version
if version != EXPECTED_OPENSTA_VERSION:
errors.append(
f"expected OpenSTA {EXPECTED_OPENSTA_VERSION}, "
f"found {version or '<empty>'}"
)
lock = json.loads(args.lock.read_text(encoding="utf-8"))
expected_pdk_revision = lock["sources"]["pdk_ihp130"]["revision"]
details["locked_pdk_revision"] = expected_pdk_revision
pdk_checkout = args.pdk_root.resolve()
if not (pdk_checkout / ".git").exists():
errors.append(f"IHP PDK checkout is missing: {pdk_checkout}")
else:
revision = command_output(["git", "-C", str(pdk_checkout), "rev-parse", "HEAD"])
details["pdk_revision"] = revision
if revision != expected_pdk_revision:
errors.append(f"expected IHP PDK {expected_pdk_revision}, found {revision}")
required = (
args.config,
pdk_checkout / "ihp-sg13g2/libs.tech/librelane/config.tcl",
pdk_checkout / "ihp-sg13g2/libs.tech/klayout/tech/drc/ihp-sg13g2.drc",
pdk_checkout / "ihp-sg13g2/libs.tech/klayout/tech/scripts/sealring.py",
pdk_checkout / "ihp-sg13g2/libs.ref/sg13g2_io/spice/sg13g2_io.spice",
)
for path in required:
if not path.is_file():
errors.append(f"required LibreLane/IHP file is missing: {path}")
try:
power_counts = read_power_pad_counts(args.pin_map)
except (OSError, ValueError, json.JSONDecodeError) as error:
power_counts = None
errors.append(f"failed to read power PAD counts: {error}")
config: dict[str, object] = {}
flow_name = ""
if args.config.is_file():
try:
config = json.loads(args.config.read_text(encoding="utf-8"))
meta = config.get("meta", {})
if not isinstance(meta, dict) or not isinstance(meta.get("flow"), str):
raise ValueError("config meta.flow must name a LibreLane flow")
flow_name = meta["flow"]
except (OSError, ValueError, json.JSONDecodeError) as error:
errors.append(f"failed to read LibreLane configuration: {error}")
if args.config.is_file() and not errors:
try:
flow_class = Flow.factory.get(flow_name)
if flow_class is None:
raise ValueError(f"unknown LibreLane flow: {flow_name}")
flow = flow_class(
str(args.config.resolve()),
pdk="ihp-sg13g2",
pdk_root=str(pdk_checkout),
scl="sg13g2_stdcell",
pad="sg13g2_io",
design_dir=str(args.config.resolve().parent),
)
details["flow"] = flow_name
details["config_design"] = flow.config["DESIGN_NAME"]
details["drc_runset"] = str(flow.config["KLAYOUT_DRC_RUNSET"])
details["lvs_setup"] = str(flow.config["NETGEN_SETUP"])
details["pad_spice"] = [str(path) for path in flow.config["PAD_SPICE_MODELS"]]
except Exception as error: # LibreLane reports structured configuration exceptions.
errors.append(f"LibreLane {flow_name or 'unknown'} configuration failed to load: {error}")
if config:
if details.get("config_design") not in (None, "retrosoc_tiny_asic"):
errors.append(
f"Tiny chip flow expects DESIGN_NAME retrosoc_tiny_asic, "
f"found {details['config_design']}"
)
placed = [
item
for side in ("PAD_SOUTH", "PAD_EAST", "PAD_NORTH", "PAD_WEST")
for item in config.get(side, [])
]
details["placed_pad_count"] = len(placed)
if flow_name == "Chip" and power_counts is not None:
expected = EXPECTED_SIGNAL_PADS + sum(power_counts.values())
if len(placed) != expected or len(placed) != len(set(placed)):
errors.append(
f"expected {expected} unique placed signal/power PADs, found {len(placed)}"
)
elif flow_name != "Chip":
errors.append(f"Tiny supports only the Chip flow, found {flow_name}")
result = {
"schema_version": 1,
"status": "passed" if not errors else "failed",
"errors": errors,
"details": details,
}
atomic_write(args.output, json.dumps(result, indent=2, sort_keys=True) + "\n")
if errors:
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
return 1
print(f"LibreLane doctor passed: {args.output}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,295 @@
#!/usr/bin/env python3
"""Generate the IHP130 LibreLane Chip configuration for the Tiny SoC."""
from __future__ import annotations
import argparse
import json
import os
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
sys.path.insert(0, str(ROOT))
from scripts.rtl.generate_pin_map import ( # noqa: E402
POWER_PAD_KINDS,
Pad,
ihp130_pad_instance,
read_map,
read_power_pad_counts,
)
from scripts.setup_helpers import atomic_write # noqa: E402
SIDE_ORDER = ("south", "east", "north", "west")
EXPECTED_SIGNAL_PADS = 52
CLOCK_PORTS = ("extclk_i_pad", "jtag_tck_i_pad")
SRAM_MASTER = "RM_IHPSG13_1P_1024x32_c2_bm_bist"
SRAM_INSTANCE = "u_soc.u_sram.gen_memory.gen_bank[{index}].u_ram.u_mem"
SRAM_COLUMNS = 8
SRAM_ORIGIN = (400, 600)
# Detailed routing requires an M2 track inside each bank's 0.26 um bottom-edge
# pin stubs: the bank y origin must keep residue {0, 0.24} mod 0.42 um (the M2
# vertical track pitch); a y pitch of 504 um (1200 x 0.42) preserves it per row.
SRAM_PITCH = (480, 504)
DIE_AREA = [0, 0, 4700, 4700]
CORE_AREA = [365, 365, 4335, 4335]
def config_path(path: Path, base: Path) -> str:
return f"dir::{os.path.relpath(path.resolve(), base.resolve())}"
def signal_side(pad: Pad) -> str:
name = pad.name
if name.startswith("gpio_"):
return "east" if pad.index is not None and pad.index < 16 else "west"
if name.startswith("xpi_"):
return "north"
return "south"
def power_instances(counts: dict[str, int], side_index: int) -> list[str]:
per_side: dict[str, int] = {}
for kind in POWER_PAD_KINDS:
count = counts[kind]
if count % len(SIDE_ORDER) != 0:
raise ValueError(
f"power_pads.{kind}={count} cannot be distributed across "
f"{len(SIDE_ORDER)} pad-ring sides"
)
per_side[kind] = count // len(SIDE_ORDER)
result: list[str] = []
for local_index in range(per_side["vdd"]):
for kind in ("vdd", "vss"):
index = (side_index * per_side[kind]) + local_index
result.append(f"{kind}_pads[{index}].{kind}_pad")
for local_index in range(per_side["iovdd"]):
for kind in ("iovdd", "iovss"):
index = (side_index * per_side[kind]) + local_index
result.append(f"{kind}_pads[{index}].{kind}_pad")
return result
def interleave(signals: list[str], supplies: list[str]) -> list[str]:
result: list[str] = []
signal_index = 0
for supply_index, supply in enumerate(supplies):
target = round(((supply_index + 1) * len(signals)) / (len(supplies) + 1))
result.extend(signals[signal_index:target])
signal_index = target
result.append(supply)
result.extend(signals[signal_index:])
return result
def sram_instances(capacity_kib: int) -> dict[str, tuple[list[int], str]]:
if capacity_kib <= 0 or capacity_kib % 4 != 0:
raise ValueError("SRAM_SIZE_KIB must be a positive multiple of 4")
instances: dict[str, tuple[list[int], str]] = {}
for index in range(capacity_kib // 4):
column, row = index % SRAM_COLUMNS, index // SRAM_COLUMNS
location = [
SRAM_ORIGIN[0] + column * SRAM_PITCH[0],
SRAM_ORIGIN[1] + row * SRAM_PITCH[1],
]
instances[SRAM_INSTANCE.format(index=index)] = (location, "N")
return instances
def macro_config(instances: dict[str, tuple[list[int], str]], header: str) -> dict[str, object]:
base = "pdk_dir::libs.ref/sg13g2_sram"
master = SRAM_MASTER
return {
"gds": [f"{base}/gds/{master}.gds"],
"lef": [f"{base}/lef/{master}.lef"],
"vh": [header],
"spice": [f"{base}/cdl/{master}.cdl"],
"lib": {
"*_typ_1p20V_25C": [f"{base}/lib/{master}_typ_1p20V_25C.lib"],
"*_fast_1p32V_m40C": [f"{base}/lib/{master}_fast_1p32V_m55C.lib"],
"*_slow_1p08V_125C": [f"{base}/lib/{master}_slow_1p08V_125C.lib"],
},
"instances": {
name: {"location": location, "orientation": orientation}
for name, (location, orientation) in instances.items()
},
}
def macro_hooks(instances: dict[str, tuple[list[int], str]]) -> list[str]:
hooks: list[str] = []
for instance in instances:
bracket_start = instance.find("[")
if bracket_start >= 0:
bracket_end = instance.index("]", bracket_start)
instance_pattern = (
re.escape(instance[:bracket_start])
+ ".*"
+ instance[bracket_start + 1 : bracket_end]
+ ".*"
+ re.escape(instance[bracket_end + 1 :])
)
else:
instance_pattern = re.escape(instance)
hooks.extend(
[
f"{instance_pattern} VDD VSS VDDARRAY! VSS!",
f"{instance_pattern} VDD VSS VDD! VSS!",
]
)
return hooks
def build_config(args: argparse.Namespace) -> dict[str, object]:
pads, _ = read_map(args.pin_map)
power_counts = read_power_pad_counts(args.pin_map)
signal_instances: dict[str, list[str]] = {side: [] for side in SIDE_ORDER}
for pad in pads:
instance = ihp130_pad_instance(pad)
if instance is None:
raise ValueError(f"IHP130 physical PAD mapping is missing for {pad.name}")
signal_instances[signal_side(pad)].append(instance)
flattened = [item for side in SIDE_ORDER for item in signal_instances[side]]
if len(flattened) != EXPECTED_SIGNAL_PADS:
raise ValueError(
f"expected {EXPECTED_SIGNAL_PADS} signal PADs, got {len(flattened)}"
)
if len(flattened) != len(set(flattened)):
raise ValueError("physical signal PAD instances are not unique")
pad_sides = {
side.upper(): interleave(signal_instances[side], power_instances(power_counts, index))
for index, side in enumerate(SIDE_ORDER)
}
all_placed = [item for side in SIDE_ORDER for item in pad_sides[side.upper()]]
expected_total = len(flattened) + sum(power_counts.values())
if len(all_placed) != expected_total or len(all_placed) != len(set(all_placed)):
raise ValueError(
"PAD ring placement does not cover every signal and power PAD exactly once"
)
config_dir = args.output.resolve().parent
sram_header = config_path(args.sram_vh, config_dir)
active_sram_instances = sram_instances(args.sram_size_kib) if args.have_sram_macro else {}
macros = (
{SRAM_MASTER: macro_config(active_sram_instances, sram_header)}
if active_sram_instances
else {}
)
return {
"meta": {"version": 3, "flow": "Chip"},
"DESIGN_NAME": "retrosoc_tiny_asic",
"VERILOG_FILES": [config_path(args.rtl, config_dir)],
"USE_SLANG": True,
"SLANG_ARGUMENTS": ["--keep-hierarchy"],
"VERILOG_POWER_DEFINE": None,
"SYNTH_SHARE_RESOURCES": False,
"SYNTH_HIERARCHY_MODE": "deferred_flatten",
"SYNTH_KEEP_HIERARCHY_MODULES": [
"sg13g2_IOPadVdd",
"sg13g2_IOPadVss",
"sg13g2_IOPadIOVdd",
"sg13g2_IOPadIOVss",
],
"YOSYS_LOG_LEVEL": "WARNING",
"SYNTH_STRATEGY": "AREA 3",
# Hotspots sit at the SRAM bank pin edges; detailed routing resolves the
# moderate estimated overflow. The signoff bar remains detailed-routing
# DRC, post-RCX multi-corner STA, KLayout DRC, and netgen LVS.
"GRT_ALLOW_CONGESTION": True,
# This OpenROAD build's antenna analysis leaks/stalls on the Tiny netlist
# (check_antennas -verbose ballooned to 248 GB RSS; repair_antennas did
# not converge in 100 minutes). Skip the report/repair steps; antenna
# coverage is a documented gap for this flow revision.
"RUN_ANTENNA_REPAIR": False,
# Hazard3 with EXTENSION_A=0 (RV32IM) leaves x_amo_phase undriven by
# design; every use is masked by a constant-false guard. Waive the
# three benign "used but has no driver" check errors.
"ERROR_ON_SYNTH_CHECKS": False,
"RUN_POST_GPL_DESIGN_REPAIR": False,
"RUN_CTS": False,
"RUN_POST_CTS_RESIZER_TIMING": False,
"EXTRA_EXCLUDED_CELLS": ["sg13g2_IOPad*"],
"PRIMARY_GDSII_STREAMOUT_TOOL": "klayout",
"PNR_SDC_FILE": config_path(args.sdc, config_dir),
"SIGNOFF_SDC_FILE": config_path(args.sdc, config_dir),
"FALLBACK_SDC": config_path(args.sdc, config_dir),
"STA_EXTRA_CORNER_TCL_FILE": config_path(
ROOT / "physical/librelane/tiny/sta_report_limit.tcl", config_dir
),
"CLOCK_PORT": list(CLOCK_PORTS),
"CLOCK_PERIOD": 1_000_000_000 / args.ext_clk_hz,
"VDD_NETS": ["VDD"],
"GND_NETS": ["VSS"],
"FP_SIZING": "absolute",
"PDN_CORE_RING": True,
"PDN_CORE_RING_VWIDTH": 15,
"PDN_CORE_RING_HWIDTH": 15,
"PDN_CORE_RING_VSPACING": 5,
"PDN_CORE_RING_HSPACING": 5,
"PDN_ENABLE_PINS": True,
"ERROR_ON_PDN_VIOLATIONS": True,
"PDN_CFG": config_path(args.pdn, config_dir),
"MACROS": macros,
"PDN_MACRO_CONNECTIONS": macro_hooks(active_sram_instances),
"MAGIC_GDS_FLATGLOB": [
"lvsres_*",
"VIA_M1_*",
"VIA_M2_*",
"*_CELL_CORNER",
"RSC_*",
"*_CELL_SUB",
],
"PAD_SOUTH": pad_sides["SOUTH"],
"PAD_EAST": pad_sides["EAST"],
"PAD_NORTH": pad_sides["NORTH"],
"PAD_WEST": pad_sides["WEST"],
"DIE_AREA": DIE_AREA,
"CORE_AREA": CORE_AREA,
"PL_TARGET_DENSITY_PCT": 45,
"PDN_CORE_RING_CONNECT_TO_PADS": True,
"PAD_CFG": config_path(ROOT / "physical/librelane/tiny/pad_cfg.tcl", config_dir),
"PAD_BONDPAD_NAME": "bondpad_70x70",
"EXTRA_GDS": [config_path(args.bondpad_gds, config_dir)],
"EXTRA_LEFS": [config_path(args.bondpad_lef, config_dir)],
"IGNORE_DISCONNECTED_MODULES": ["bondpad_70x70"],
"MAGIC_EXT_UNIQUE": "notopports",
}
def positive_integer(value: str) -> int:
parsed = int(value)
if parsed <= 0:
raise argparse.ArgumentTypeError("value must be positive")
return parsed
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--pin-map", type=Path, required=True)
parser.add_argument("--rtl", type=Path, required=True)
parser.add_argument("--sdc", type=Path, required=True)
parser.add_argument("--pdn", type=Path, required=True)
parser.add_argument("--bondpad-gds", type=Path, required=True)
parser.add_argument("--bondpad-lef", type=Path, required=True)
parser.add_argument("--sram-vh", type=Path, required=True)
parser.add_argument("--ext-clk-hz", type=positive_integer, required=True)
parser.add_argument("--have-sram-macro", action="store_true")
parser.add_argument("--sram-size-kib", type=positive_integer, default=128)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
try:
config = build_config(args)
atomic_write(args.output, json.dumps(config, indent=2, sort_keys=True) + "\n")
except (OSError, ValueError, json.JSONDecodeError) as error:
parser.error(str(error))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""Generate pad-aware timing constraints for the Tiny IHP130 Chip flow."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
sys.path.insert(0, str(ROOT))
from scripts.rtl.generate_pin_map import Pad, ihp130_pad_instance, read_map # noqa: E402
from scripts.check_clock_reset_domains import validate # noqa: E402
from scripts.setup_helpers import atomic_write # noqa: E402
CLOCK_PADS = {
"external": "extclk_i_pad",
"jtag": "jtag_tck_i_pad",
}
RESET_PADS = {"ext_rst_n_i_pad", "jtag_trst_n_i_pad"}
IHP130_SYSTEM_CLOCK_PIN = "u_clock_buffer.u_sg13g2_buf_1/X"
JTAG_PERIOD_NS = 100.0
def positive_integer(value: str) -> int:
parsed = int(value)
if parsed <= 0:
raise argparse.ArgumentTypeError("value must be positive")
return parsed
def timing_clock(pad: Pad) -> str:
if pad.name.startswith("jtag_"):
return "jtag"
return "external"
def logical_directions(pad: Pad) -> tuple[bool, bool]:
input_kinds = {"tri_input", "schmitt_input"}
output_kinds = {"tri_output", "peripheral_output"}
bidir_kinds = {"full", "peripheral_bidir", "xpi_data"}
if pad.kind in input_kinds:
return True, False
if pad.kind in output_kinds:
return False, True
if pad.kind in bidir_kinds:
return True, True
return False, False
def render(args: argparse.Namespace) -> str:
validate(args.domains, ROOT)
pads, _ = read_map(args.pin_map)
by_name = {pad.name: pad for pad in pads}
for name in CLOCK_PADS.values():
if name not in by_name:
raise ValueError(f"clock PAD is missing from the pin map: {name}")
periods = {
"external": 1_000_000_000 / args.ext_clk_hz,
"jtag": JTAG_PERIOD_NS,
}
groups: dict[str, dict[str, list[str]]] = {
name: {"inputs": [], "outputs": []} for name in periods
}
excluded = set(CLOCK_PADS.values()) | RESET_PADS
for pad in pads:
if pad.name in excluded:
continue
is_input, is_output = logical_directions(pad)
clock = timing_clock(pad)
if is_input:
groups[clock]["inputs"].append(pad.name)
if is_output:
groups[clock]["outputs"].append(pad.name)
lines = [
"# Generated by physical/librelane/tiny/scripts/generate_sdc.py; do not edit.",
"current_design $::env(DESIGN_NAME)",
"set_units -time ns",
"",
"proc require_pins {label name} {",
" set objects [get_pins -quiet $name]",
' if {$objects eq ""} { error "required SDC pin is missing: $label ($name)" }',
" return $objects",
"}",
"proc require_ports {label name} {",
" set objects [get_ports -quiet $name]",
' if {$objects eq ""} { error "required SDC port is missing: $label ($name)" }',
" return $objects",
"}",
"",
]
for name, pad_name in CLOCK_PADS.items():
instance = ihp130_pad_instance(by_name[pad_name])
if instance is None:
raise ValueError(f"clock PAD has no IHP130 physical instance: {pad_name}")
lines.append(
f'set clk_{name}_pin [require_pins "clock {name}" {{{instance}/p2c}}]'
)
lines.append(
f"create_clock -name clk_{name} -period {periods[name]:.12g} $clk_{name}_pin"
)
lines.append("")
lines.extend(
[
f'set clk_system_pin [require_pins "clock system" {{{IHP130_SYSTEM_CLOCK_PIN}}}]',
"create_generated_clock -name clk_system -source $clk_external_pin "
"-divide_by 1 $clk_system_pin",
"",
"set_clock_groups -name retrosoc_async -asynchronous \\",
" -group [get_clocks {clk_external clk_system}] \\",
" -group [get_clocks {clk_jtag}]",
"",
"set all_retrosoc_clocks [get_clocks {clk_external clk_system clk_jtag}]",
"set_clock_uncertainty -setup 0.2 $all_retrosoc_clocks",
"set_clock_uncertainty -hold 0.1 $all_retrosoc_clocks",
"set_clock_transition 0.1 $all_retrosoc_clocks",
"set_timing_derate -early 0.95",
"set_timing_derate -late 1.05",
"set_max_fanout 10 [current_design]",
"",
]
)
for reset in sorted(RESET_PADS):
lines.extend(
[
f'set reset_{reset} [require_ports "reset {reset}" {{{reset}}}]',
f"set_false_path -from $reset_{reset}",
"",
]
)
for clock, port_groups in groups.items():
delay = periods[clock] * 0.2
for direction, ports in port_groups.items():
if not ports:
continue
variable = f"{clock}_{direction}"
lines.append(f"set {variable} [get_ports {{{' '.join(ports)}}}]")
if direction == "inputs":
lines.append(f"set_input_delay -min 0 -clock clk_{clock} ${variable}")
lines.append(f"set_input_delay -max {delay:.12g} -clock clk_{clock} ${variable}")
else:
lines.append(f"set_output_delay -min 0 -clock clk_{clock} ${variable}")
lines.append(f"set_output_delay -max {delay:.12g} -clock clk_{clock} ${variable}")
lines.append(f"set_load 0.006 ${variable}")
lines.append("")
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--domains", type=Path, required=True)
parser.add_argument("--pin-map", type=Path, required=True)
parser.add_argument("--ext-clk-hz", type=positive_integer, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
try:
atomic_write(args.output, render(args))
except (OSError, ValueError, json.JSONDecodeError) as error:
parser.error(str(error))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Create a checksummed Tiny LibreLane implementation delivery archive."""
from __future__ import annotations
import argparse
import hashlib
import shutil
import tarfile
import tempfile
from pathlib import Path
def hash_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
while block := source.read(1024 * 1024):
digest.update(block)
return digest.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--variant-root", type=Path, required=True)
parser.add_argument("--run-root", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
root = args.root.resolve()
variant_root = args.variant_root.resolve()
run_root = args.run_root.resolve()
required = (
run_root / "config.json",
run_root / "result.json",
run_root / "final",
run_root / "runs/current",
variant_root / "meta/manifest.json",
variant_root / "meta/librelane-chip-doctor.json",
variant_root / "meta/librelane-chip.json",
root / "dependencies/dependencies.lock.json",
)
missing = [str(path) for path in required if not path.exists()]
if missing:
parser.error("missing LibreLane delivery input(s): " + ", ".join(missing))
args.output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="retrosoc-librelane-package-") as temporary:
staging = Path(temporary) / "retrosoc-tiny-ihp130-chip"
shutil.copytree(run_root / "final", staging / "final")
shutil.copytree(run_root / "runs/current", staging / "evidence/run")
shutil.copy2(run_root / "config.json", staging / "evidence/config.json")
shutil.copy2(run_root / "result.json", staging / "evidence/result.json")
shutil.copy2(variant_root / "meta/manifest.json", staging / "evidence/manifest.json")
shutil.copy2(
variant_root / "meta/librelane-chip-doctor.json",
staging / "evidence/librelane-doctor.json",
)
shutil.copy2(
variant_root / "meta/librelane-chip.json",
staging / "evidence/librelane-chip.json",
)
shutil.copy2(
root / "dependencies/dependencies.lock.json",
staging / "evidence/dependencies.lock.json",
)
files = sorted(path for path in staging.rglob("*") if path.is_file())
sums = "\n".join(f"{hash_file(path)} {path.relative_to(staging)}" for path in files)
(staging / "SHA256SUMS").write_text(sums + "\n", encoding="utf-8")
with tarfile.open(args.output, "w:gz") as archive:
archive.add(staging, arcname=staging.name)
print(f"created LibreLane delivery: {args.output}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,25 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// SPDX-License-Identifier: MulanPSL-2.0
// Synthesis-only header for the locked IHP SRAM macro physical views.
(* blackbox *)
module RM_IHPSG13_1P_1024x32_c2_bm_bist (
input wire A_CLK,
input wire A_MEN,
input wire A_WEN,
input wire A_REN,
input wire [ 9:0] A_ADDR,
input wire [31:0] A_DIN,
input wire A_DLY,
output wire [31:0] A_DOUT,
input wire [31:0] A_BM,
input wire A_BIST_CLK,
input wire A_BIST_EN,
input wire A_BIST_MEN,
input wire A_BIST_WEN,
input wire A_BIST_REN,
input wire [ 9:0] A_BIST_ADDR,
input wire [31:0] A_BIST_DIN,
input wire [31:0] A_BIST_BM
);
endmodule
@@ -0,0 +1,35 @@
# LibreLane 3.0.5 hard-codes 1000 expanded paths per clock group. Cap only
# report enumeration; worst-slack/TNS analysis still covers the full graph.
set ::retrosoc_sta_group_path_count 10
puts "\[INFO\] OpenSTA executable: [info nameofexecutable]"
rename report_checks ::retrosoc_report_checks
proc report_checks {args} {
set count_index [lsearch -exact $args -group_path_count]
if {$count_index >= 0} {
set value_index [expr {$count_index + 1}]
if {$value_index < [llength $args]} {
lset args $value_index $::retrosoc_sta_group_path_count
}
} else {
lappend args -group_path_count $::retrosoc_sta_group_path_count
}
uplevel 1 [list ::retrosoc_report_checks {*}$args]
}
rename report_check_types ::retrosoc_report_check_types
proc report_check_types {args} {
set violator_index [lsearch -exact $args -violators]
if {$violator_index >= 0} {
set args [lreplace $args $violator_index $violator_index]
}
uplevel 1 [list ::retrosoc_report_check_types {*}$args]
}
rename report_parasitic_annotation ::retrosoc_report_parasitic_annotation
proc report_parasitic_annotation {args} {
set detail_index [lsearch -exact $args -report_unannotated]
if {$detail_index >= 0} {
set args [lreplace $args $detail_index $detail_index]
}
uplevel 1 [list ::retrosoc_report_parasitic_annotation {*}$args]
}
+2 -1
View File
@@ -30,7 +30,8 @@ the locked HD library submodule; OpenRAM's build-space and Ciel inputs are
recorded in the macro artifact rather than added to the SoC PDK checkout. recorded in the macro artifact rather than added to the SoC PDK checkout.
The IHP130 dependency is also the technology source for the independent The IHP130 dependency is also the technology source for the independent
`physical/librelane/` core-hardening and full-chip pad-ring flows. The locked `physical/librelane/{mini,tiny}/` core-hardening and full-chip pad-ring flows.
The locked
checkout includes the upstream `libs.tech/librelane` standard-cell, pad, checkout includes the upstream `libs.tech/librelane` standard-cell, pad,
extraction, DRC, LVS, filler, bondpad, and seal-ring configuration; LibreLane extraction, DRC, LVS, filler, bondpad, and seal-ring configuration; LibreLane
consumes it directly in manual-PDK mode. The other three open PDKs do not yet consumes it directly in manual-PDK mode. The other three open PDKs do not yet
+1 -1
View File
@@ -12,7 +12,7 @@ from typing import Any
ROOT = Path(__file__).resolve().parents[4] ROOT = Path(__file__).resolve().parents[4]
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
from rtl.mini.pin_map.generate_pin_map import read_map # noqa: E402 from scripts.rtl.generate_pin_map import read_map # noqa: E402
from scripts.check_clock_reset_domains import validate # noqa: E402 from scripts.check_clock_reset_domains import validate # noqa: E402
from scripts.setup_helpers import atomic_write # noqa: E402 from scripts.setup_helpers import atomic_write # noqa: E402
+4 -3
View File
@@ -1,5 +1,6 @@
OPENSTA ?= sta OPENSTA ?= sta
OPENSTA_TOP ?= $(if $(filter TINY,$(SOC)),retrosoc_tiny_asic,retrosoc_asic)
OPENSTA_THREADS ?= $(JOBS) OPENSTA_THREADS ?= $(JOBS)
OPENSTA_NETLIST ?= $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v OPENSTA_NETLIST ?= $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v
OPENSTA_REPORT ?= $(STA_BUILD_ROOT)/retrosoc_sta.log OPENSTA_REPORT ?= $(STA_BUILD_ROOT)/retrosoc_sta.log
@@ -8,8 +9,8 @@ OPENSTA_METRICS ?= $(STA_BUILD_ROOT)/timing_metrics.rpt
OPENSTA_CONFIG ?= $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.config OPENSTA_CONFIG ?= $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.config
OPENSTA_SDC ?= $(STA_BUILD_ROOT)/retrosoc_core.sdc OPENSTA_SDC ?= $(STA_BUILD_ROOT)/retrosoc_core.sdc
OPENSTA_SDC_GEN := $(ROOT_PATH)/physical/smoke/sta/opensta/generate_sdc.py OPENSTA_SDC_GEN := $(ROOT_PATH)/physical/smoke/sta/opensta/generate_sdc.py
OPENSTA_DOMAIN_MAP := $(ROOT_PATH)/rtl/mini/integration/clock_reset_domains.json OPENSTA_DOMAIN_MAP := $(RTL_PATH)/integration/clock_reset_domains.json
OPENSTA_PIN_MAP := $(ROOT_PATH)/rtl/mini/pin_map/pin_map.json OPENSTA_PIN_MAP := $(RTL_PATH)/pin_map/pin_map.json
include $(ROOT_PATH)/physical/smoke/sta/opensta/pdk_timing.mk include $(ROOT_PATH)/physical/smoke/sta/opensta/pdk_timing.mk
@@ -36,7 +37,7 @@ sta: $(OPENSTA_SDC) | manifest
} }
python3 $(ROOT_PATH)/scripts/run_flow.py --tool opensta --log $(OPENSTA_LOG) \ python3 $(ROOT_PATH)/scripts/run_flow.py --tool opensta --log $(OPENSTA_LOG) \
--result $(STA_BUILD_ROOT)/result-sta.json \ --result $(STA_BUILD_ROOT)/result-sta.json \
--env OPENSTA_NETLIST=$(OPENSTA_NETLIST) --env OPENSTA_LIBERTY=$(OPENSTA_LIBERTY) \ --env OPENSTA_TOP=$(OPENSTA_TOP) --env OPENSTA_NETLIST=$(OPENSTA_NETLIST) --env OPENSTA_LIBERTY=$(OPENSTA_LIBERTY) \
--env 'OPENSTA_LINK_LIBS=$(OPENSTA_LINK_LIBS)' --env 'OPENSTA_SRAM_LIBS=$(OPENSTA_SRAM_LIBS)' \ --env 'OPENSTA_LINK_LIBS=$(OPENSTA_LINK_LIBS)' --env 'OPENSTA_SRAM_LIBS=$(OPENSTA_SRAM_LIBS)' \
--env OPENSTA_SDC=$(OPENSTA_SDC) --env OPENSTA_REPORT=$(OPENSTA_REPORT) \ --env OPENSTA_SDC=$(OPENSTA_SDC) --env OPENSTA_REPORT=$(OPENSTA_REPORT) \
--env OPENSTA_METRICS=$(OPENSTA_METRICS) -- \ --env OPENSTA_METRICS=$(OPENSTA_METRICS) -- \
+5 -1
View File
@@ -14,7 +14,11 @@ if {[info exists ::env(OPENSTA_SRAM_LIBS)] && $::env(OPENSTA_SRAM_LIBS) ne ""} {
} }
} }
read_verilog $::env(OPENSTA_NETLIST) read_verilog $::env(OPENSTA_NETLIST)
link_design retrosoc_asic set top_design retrosoc_asic
if {[info exists ::env(OPENSTA_TOP)] && $::env(OPENSTA_TOP) ne ""} {
set top_design $::env(OPENSTA_TOP)
}
link_design $top_design
read_sdc $::env(OPENSTA_SDC) read_sdc $::env(OPENSTA_SDC)
report_checks -path_delay min_max -sort_by_slack -slack_max 0.0 -group_path_count 1000 -endpoint_path_count 1000 > $::env(OPENSTA_REPORT) report_checks -path_delay min_max -sort_by_slack -slack_max 0.0 -group_path_count 1000 -endpoint_path_count 1000 > $::env(OPENSTA_REPORT)
@@ -18,10 +18,9 @@ from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[4] REPO_ROOT = Path(__file__).resolve().parents[4]
SCRIPT_DIR = REPO_ROOT / "rtl/mini/script" sys.path.insert(0, str(REPO_ROOT))
sys.path.insert(0, str(SCRIPT_DIR)) from scripts.rtl.filelist import FileList, parse_filelists, write_filelist # noqa: E402
from filelist import FileList, parse_filelists, write_filelist # noqa: E402 from rtl.mini.script.generate_filelist import generate_all # noqa: E402
from generate_filelist import generate_all # noqa: E402
APU_SOURCE_MARKERS = ("/ip/multimedia/apu_", "/ip/multimedia/apb4_apu.sv") APU_SOURCE_MARKERS = ("/ip/multimedia/apu_", "/ip/multimedia/apb4_apu.sv")
+26 -13
View File
@@ -13,13 +13,10 @@ from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[4] REPO_ROOT = Path(__file__).resolve().parents[4]
SCRIPT_DIR = REPO_ROOT / "rtl/mini/script" sys.path.insert(0, str(REPO_ROOT))
INTEGRATION_DIR = REPO_ROOT / "rtl/mini/integration" from scripts.rtl.filelist import FileList, atomic_write, parse_filelists # noqa: E402
sys.path.insert(0, str(SCRIPT_DIR)) from rtl.mini.script.generate_filelist import generate_all # noqa: E402
sys.path.insert(0, str(INTEGRATION_DIR)) from rtl.mini.integration.generate_user_extensions import generate as generate_user_extensions # noqa: E402
from filelist import FileList, atomic_write, parse_filelists # noqa: E402
from generate_filelist import generate_all # noqa: E402
from generate_user_extensions import generate as generate_user_extensions # noqa: E402
INCLUDE_RE = re.compile(r'^\s*`include\s+"([^"]+)"') INCLUDE_RE = re.compile(r'^\s*`include\s+"([^"]+)"')
@@ -69,6 +66,20 @@ def configured_filelist(
*, *,
require_files: bool = True, require_files: bool = True,
) -> FileList: ) -> FileList:
if getattr(args, "soc", "MINI") == "TINY":
directory = getattr(args, "source_filelist_dir", None)
if directory is None:
raise ValueError("Tiny export requires --source-filelist-dir")
names = ("def.fl", "inc.fl", "commonip.fl", "clusterip.fl", "ip.fl",
"tech.fl", "core_hazard3.fl", "top.fl")
filelist = parse_filelists((directory / name for name in names), require_files=require_files)
if "+define+RETROSOC_SOC__TINY" not in filelist.defines:
raise ValueError("Tiny export requires Tiny source lists")
if "+define+SYNTHESIS" not in filelist.defines:
filelist.defines.append("+define+SYNTHESIS")
return filelist
if args.dynamic_core_filelist is None or args.dynamic_ip_filelist is None:
raise ValueError("Mini export requires dynamic core and IP filelists")
names: list[str | Path] = [] names: list[str | Path] = []
for name in ( for name in (
"memory_map_filelist", "memory_map_filelist",
@@ -271,7 +282,7 @@ def metadata_file(value: str) -> tuple[Path, Path]:
def parse_args() -> argparse.Namespace: def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Export configured retroSoC RTL sources") parser = argparse.ArgumentParser(description="Export configured retroSoC RTL sources")
parser.add_argument("mode", choices=("tar", "sv")) parser.add_argument("mode", choices=("tar", "sv"))
parser.add_argument("--soc", default="MINI", choices=("MINI",)) parser.add_argument("--soc", default="MINI", choices=("MINI", "TINY"))
parser.add_argument("--pdk", default="IHP130", choices=("IHP130", "ICS55", "SKY130", "GF180")) parser.add_argument("--pdk", default="IHP130", choices=("IHP130", "ICS55", "SKY130", "GF180"))
parser.add_argument("--simu", default="VCS", choices=("VCS", "VERILATOR", "IVERILOG")) parser.add_argument("--simu", default="VCS", choices=("VCS", "VERILATOR", "IVERILOG"))
parser.add_argument("--have-pll", action="store_true") parser.add_argument("--have-pll", action="store_true")
@@ -285,18 +296,19 @@ def parse_args() -> argparse.Namespace:
parser.add_argument( parser.add_argument(
"--dynamic-core-filelist", "--dynamic-core-filelist",
type=Path, type=Path,
required=True, required=False,
) )
parser.add_argument( parser.add_argument(
"--dynamic-ip-filelist", "--dynamic-ip-filelist",
type=Path, type=Path,
required=True, required=False,
) )
parser.add_argument("--memory-map-filelist", type=Path) parser.add_argument("--memory-map-filelist", type=Path)
parser.add_argument("--soc-topology-filelist", type=Path) parser.add_argument("--soc-topology-filelist", type=Path)
parser.add_argument("--user-extensions-filelist", type=Path) parser.add_argument("--user-extensions-filelist", type=Path)
parser.add_argument("--pin-map-filelist", type=Path) parser.add_argument("--pin-map-filelist", type=Path)
parser.add_argument("--archinfo-incdir", type=Path) parser.add_argument("--archinfo-incdir", type=Path)
parser.add_argument("--source-filelist-dir", type=Path)
parser.add_argument("--librelane-safe", action="store_true") parser.add_argument("--librelane-safe", action="store_true")
parser.add_argument("--metadata-file", type=metadata_file, action="append", default=[]) parser.add_argument("--metadata-file", type=metadata_file, action="append", default=[])
parser.add_argument("--output-dir", type=Path, default=REPO_ROOT / "export") parser.add_argument("--output-dir", type=Path, default=REPO_ROOT / "export")
@@ -312,15 +324,16 @@ def main() -> int:
with tempfile.TemporaryDirectory(prefix="retrosoc-filelists-") as temporary: with tempfile.TemporaryDirectory(prefix="retrosoc-filelists-") as temporary:
generated_dir = Path(temporary) generated_dir = Path(temporary)
user_extensions_dir = generated_dir / "user_extensions" user_extensions_dir = generated_dir / "user_extensions"
generate_all(generated_dir, build_defines(args)) if args.soc == "MINI":
if args.user_extensions_filelist is None: generate_all(generated_dir, build_defines(args))
if args.soc == "MINI" and args.user_extensions_filelist is None:
generate_user_extensions( generate_user_extensions(
REPO_ROOT / "rtl/mini/integration/user_extensions.json", REPO_ROOT / "rtl/mini/integration/user_extensions.json",
user_extensions_dir, user_extensions_dir,
) )
filelist = configured_filelist(args, generated_dir, user_extensions_dir) filelist = configured_filelist(args, generated_dir, user_extensions_dir)
if args.mode == "sv": if args.mode == "sv":
output = export_dir / "retrosoc_asic_sources.sv" output = export_dir / ("retrosoc_tiny_asic_sources.sv" if args.soc == "TINY" else "retrosoc_asic_sources.sv")
write_single_sv(filelist, output, librelane_safe=args.librelane_safe) write_single_sv(filelist, output, librelane_safe=args.librelane_safe)
else: else:
output = write_tar(filelist, export_dir, args.soc, dict(args.metadata_file)) output = write_tar(filelist, export_dir, args.soc, dict(args.metadata_file))
+1 -1
View File
@@ -98,7 +98,7 @@ APU_BLOCK_STA_RESULT := $(APU_BLOCK_STA)/result-sta.json
APU_BLOCK_SYN_RESULT := $(APU_BLOCK_SYN)/result-synth.json APU_BLOCK_SYN_RESULT := $(APU_BLOCK_SYN)/result-synth.json
$(APU_BLOCK_FL): $(APU_BLOCK_EXPORTER) $(FILELIST_TEMPLATES) \ $(APU_BLOCK_FL): $(APU_BLOCK_EXPORTER) $(FILELIST_TEMPLATES) \
$(RTL_PATH)/script/generate_filelist.py $(RTL_PATH)/script/filelist.py $(RTL_PATH)/script/generate_filelist.py $(ROOT_PATH)/scripts/rtl/filelist.py
@mkdir -p $(APU_BLOCK_FL_DIR) @mkdir -p $(APU_BLOCK_FL_DIR)
python3 $(APU_BLOCK_EXPORTER) --output-dir $(APU_BLOCK_FL_DIR) \ python3 $(APU_BLOCK_EXPORTER) --output-dir $(APU_BLOCK_FL_DIR) \
$(foreach define,$(DEF_LIST),--define $(define)) $(foreach define,$(DEF_LIST),--define $(define))
+3 -3
View File
@@ -48,7 +48,7 @@ endif
# active DEF_LIST but without the SoC generated-include chain, so the block # active DEF_LIST but without the SoC generated-include chain, so the block
# flow does not depend on the HP/pin-map generation stamps. # flow does not depend on the HP/pin-map generation stamps.
$(GA2D_BLOCK_FL_DIR)/.stamp: $(RTL_PATH)/script/generate_filelist.py \ $(GA2D_BLOCK_FL_DIR)/.stamp: $(RTL_PATH)/script/generate_filelist.py \
$(RTL_PATH)/script/filelist.py \ $(ROOT_PATH)/scripts/rtl/filelist.py \
$(sort $(wildcard $(ROOT_PATH)/rtl/filelist/pdk_*.fl) \ $(sort $(wildcard $(ROOT_PATH)/rtl/filelist/pdk_*.fl) \
$(wildcard $(RTL_PATH)/filelist/*.fl) $(RTL_PATH)/lint.msg) $(wildcard $(RTL_PATH)/filelist/*.fl) $(RTL_PATH)/lint.msg)
$(FLOW_PYTHON) $(RTL_PATH)/script/generate_filelist.py \ $(FLOW_PYTHON) $(RTL_PATH)/script/generate_filelist.py \
@@ -57,11 +57,11 @@ $(GA2D_BLOCK_FL_DIR)/.stamp: $(RTL_PATH)/script/generate_filelist.py \
@touch $@ @touch $@
$(GA2D_BLOCK_FLIST): $(GA2D_BLOCK_FL_DIR)/.stamp $(MEMORY_MAP_STAMP) $(GA2D_BLOCK_FLIST): $(GA2D_BLOCK_FL_DIR)/.stamp $(MEMORY_MAP_STAMP)
@python3 $(RTL_PATH)/script/comb.py \ @python3 $(ROOT_PATH)/scripts/rtl/comb.py \
-f $(GA2D_BLOCK_FL_DIR)/def.fl \ -f $(GA2D_BLOCK_FL_DIR)/def.fl \
-f $(MEMORY_MAP_FILELIST) \ -f $(MEMORY_MAP_FILELIST) \
-f $(GA2D_BLOCK_FL_DIR)/ga2d_block.fl --output $@ -f $(GA2D_BLOCK_FL_DIR)/ga2d_block.fl --output $@
@python3 $(RTL_PATH)/script/filelist_deps.py -f $@ \ @python3 $(ROOT_PATH)/scripts/rtl/filelist_deps.py -f $@ \
--extra $(MEMORY_MAP_FILELIST) \ --extra $(MEMORY_MAP_FILELIST) \
--target $(GA2D_BLOCK_NETLIST) --output $(GA2D_BLOCK_DEPFILE) --target $(GA2D_BLOCK_NETLIST) --output $(GA2D_BLOCK_DEPFILE)
+10 -9
View File
@@ -19,18 +19,19 @@
# Directories # Directories
# directory of the path to the last called Makefile (this one) # directory of the path to the last called Makefile (this one)
YOSYS_DIR := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST))))) YOSYS_DIR := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST)))))
YOSYS_BUILD := $(SYN_BUILD_ROOT) YOSYS_BUILD := $(SYN_BUILD_ROOT)
YOSYS_OUT := $(YOSYS_BUILD)/out YOSYS_OUT := $(YOSYS_BUILD)/out
YOSYS_TMP := $(YOSYS_BUILD)/tmp YOSYS_TMP := $(YOSYS_BUILD)/tmp
YOSYS_RPT := $(YOSYS_BUILD)/rpt YOSYS_RPT := $(YOSYS_BUILD)/rpt
YOSYS_TIMEOUT ?= 10800 YOSYS_TIMEOUT ?= 10800
include $(YOSYS_DIR)/synth_config.mk include $(YOSYS_DIR)/synth_config.mk
ifeq ($(origin YOSYS_TARGET_PERIOD_PS),undefined) ifeq ($(origin YOSYS_TARGET_PERIOD_PS),undefined)
YOSYS_TARGET_PERIOD_PS := $(shell python3 $(ROOT_PATH)/scripts/yosys_period.py \ YOSYS_TARGET_PERIOD_PS := $(shell python3 $(ROOT_PATH)/scripts/yosys_period.py \
--domains $(ROOT_PATH)/rtl/mini/integration/clock_reset_domains.json) --domains $(RTL_PATH)/integration/clock_reset_domains.json \
--domain $(if $(filter TINY,$(SOC)),system,hp))
ifeq ($(strip $(YOSYS_TARGET_PERIOD_PS)),) ifeq ($(strip $(YOSYS_TARGET_PERIOD_PS)),)
$(error Failed to derive YOSYS_TARGET_PERIOD_PS from the clock/reset inventory) $(error Failed to derive YOSYS_TARGET_PERIOD_PS from the clock/reset inventory)
endif endif
@@ -49,8 +50,8 @@ YOSYS_SCRIPTS := $(wildcard $(YOSYS_DIR)/script/*) $(YOSYS_DIR)/synth_config.mk
-include $(YOSYS_DEPFILE) -include $(YOSYS_DEPFILE)
$(SV_FLIST): $(MPW_VARIANT_STAMP) $(FILELIST_STAMP) $(SV_FLIST): $(MPW_VARIANT_STAMP) $(FILELIST_STAMP)
@python3 $(RTL_PATH)/script/comb.py $(RTL_FLIST) --output $(SV_FLIST) @python3 $(ROOT_PATH)/scripts/rtl/comb.py $(RTL_FLIST) --output $(SV_FLIST)
@python3 $(RTL_PATH)/script/filelist_deps.py $(RTL_FLIST) --target $(NETLIST) \ @python3 $(ROOT_PATH)/scripts/rtl/filelist_deps.py $(RTL_FLIST) --target $(NETLIST) \
--output $(YOSYS_DEPFILE) --output $(YOSYS_DEPFILE)
gen_synth_filelist: $(SV_FLIST) gen_synth_filelist: $(SV_FLIST)
@@ -79,4 +80,4 @@ $(NETLIST_DEBUG): $(NETLIST)
synth_clean: synth_clean:
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(YOSYS_BUILD) python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(YOSYS_BUILD)
.PHONY: gen_synth_filelist synth_clean synth .PHONY: gen_synth_filelist synth_clean synth
+9
View File
@@ -12,6 +12,15 @@ These edits do not advance `mini.json.source_revision` or relabel an existing
PDF. Review and commit the implementation snapshot before producing a matching PDF. Review and commit the implementation snapshot before producing a matching
new PDF; the source-drift check remains mandatory. new PDF; the source-drift check remains mandatory.
## Tiny Gen1 datasheet
The independent [Tiny Gen1 publication](datasheets/tiny/README.md) uses the same
visual language with Tiny-only configuration, register/IRQ/pad extraction,
fourteen IP chapters and its own source/structure/evidence checks. Run
`python3 publications/build_tiny_datasheet.py setup`, then `build` or `check`.
Its output and `latest-tiny` record are separate; the existing Mini entrypoint,
templates, configuration, review and delivered PDF are unchanged.
## Mini datasheet ## Mini datasheet
The prototype chip brand is **Rill(清溪)**. Its bilingual label replaces the The prototype chip brand is **Rill(清溪)**. Its bilingual label replaces the
+2 -2
View File
@@ -146,9 +146,9 @@ def collect_data(config: dict[str, Any], *, check_snapshot: bool = True) -> dict
product = profiles["profile"] product = profiles["profile"]
if product.get("MINI_MODE") != "PRODUCT" or product.get("HAVE_HP") != "YES": if product.get("MINI_MODE") != "PRODUCT" or product.get("HAVE_HP") != "YES":
raise ValueError("datasheet requires the reviewed dual-hart PRODUCT profile") raise ValueError("datasheet requires the reviewed dual-hart PRODUCT profile")
address = load_generator("datasheet_memory", "rtl/mini/address_map/generate_memory_map.py") address = load_generator("datasheet_memory", "scripts/rtl/generate_memory_map.py")
topology = load_generator("datasheet_topology", "rtl/mini/integration/generate_soc_topology.py") topology = load_generator("datasheet_topology", "rtl/mini/integration/generate_soc_topology.py")
pinmap = load_generator("datasheet_pins", "rtl/mini/pin_map/generate_pin_map.py") pinmap = load_generator("datasheet_pins", "scripts/rtl/generate_pin_map.py")
reset, regions, _ = address.read_map(ROOT / MAP, int(product["SRAM_SIZE_KIB"])) reset, regions, _ = address.read_map(ROOT / MAP, int(product["SRAM_SIZE_KIB"]))
_, _, _, gpio, _, _, irqs, policies = topology.read_topology(ROOT / TOPOLOGY, ROOT / MAP) _, _, _, gpio, _, _, irqs, policies = topology.read_topology(ROOT / TOPOLOGY, ROOT / MAP)
pads, _ = pinmap.read_map(ROOT / PINS) pads, _ = pinmap.read_map(ROOT / PINS)
+240
View File
@@ -0,0 +1,240 @@
#!/usr/bin/env python3
"""Prepare, build and verify the independent Tiny Gen1 publication."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
from datetime import datetime, timezone
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from publications.tiny_reference import BOOK, MANAGED, collect, read, validate_configuration # noqa: E402
from publications.package_reference import directory_hashes, package_records, validate_package_closure # noqa: E402
from publications.structure_reference import validate_structure # noqa: E402
from publications.report_changes import repository_footer_pages # noqa: E402
from scripts.dependency_lock import load_lock # noqa: E402
from scripts.install_toolchain import safe_extract # noqa: E402
from scripts.setup_helpers import atomic_write, download_file, ensure_git_repo, sha256 # noqa: E402
CACHE = ROOT / ".cache/retrosoc/publications"
LATEST = CACHE / "latest-tiny"
def write(path: Path, value) -> None:
atomic_write(path, json.dumps(value, indent=2, ensure_ascii=False) + "\n")
def assets(lock: dict) -> dict:
for name in MANAGED:
spec = lock["sources"][name]
location = ROOT / spec["destination"]
if not (location / ".git").exists():
raise ValueError(f"Tiny managed input absent: {name}; run setup")
revision = subprocess.check_output(["git", "-C", str(location), "rev-parse", "HEAD"], text=True).strip()
dirty = subprocess.check_output(["git", "-C", str(location), "status", "--porcelain"], text=True).strip()
if revision != spec["revision"] or dirty:
raise ValueError(f"Tiny managed input differs from its lock: {name}")
media = ROOT / lock["sources"]["publication_media"]["destination"]
manifest = read(media / "assets.json")
for path, expected in manifest["files"].items():
if sha256(media / path) != expected:
raise ValueError(f"Tiny media digest mismatch: {path}")
validate_package_closure(ROOT, package_records(lock))
return manifest
def prepare(lock: dict) -> None:
for name in sorted(MANAGED):
spec = lock["sources"][name]
ensure_git_repo(spec["url"], ROOT / spec["destination"], spec["revision"], update=False)
for package in package_records(lock):
archive = CACHE / "downloads" / f"{package['name']}-{package['version']}.tar.gz"
download_file(package["url"], archive, package["sha256"], update=False)
destination = ROOT / package["destination"]
manifest = destination / ".manifest.json"
if manifest.is_file() and read(manifest) == {"archive": package["sha256"], "files": directory_hashes(destination)}:
continue
if destination.exists():
raise ValueError("Existing package cache differs from its lock; review the cache before replacing it")
destination.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(dir=destination.parent) as temp:
unpacked = Path(temp) / "content"
unpacked.mkdir()
safe_extract(archive, unpacked)
write(unpacked / ".manifest.json", {"archive": package["sha256"], "files": directory_hashes(unpacked)})
unpacked.rename(destination)
assets(lock)
def inputs(data: dict) -> dict[str, str]:
paths = set(data["source_paths"])
paths.update(p.relative_to(ROOT).as_posix() for p in (ROOT / BOOK).rglob("*") if p.suffix in {".typ", ".json"})
paths.update(p.relative_to(ROOT).as_posix() for p in (ROOT / "publications/examples/tiny").glob("*.c"))
paths.update({"publications/build_tiny_datasheet.py", "publications/check_tiny_examples.py", "tests/test_publication_tiny.py", "publications/tiny_reference.py", "publications/register_reference.py",
"publications/waveform_reference.py", "publications/structure_reference.py", "publications/package_reference.py",
"publications/report_changes.py", "publications/page_reference.py", "scripts/rtl/generate_memory_map.py",
"scripts/rtl/generate_pin_map.py", "scripts/check_clock_reset_domains.py"})
return {path: sha256(ROOT / path) for path in sorted(paths)}
def typst_binary(explicit: str | None, lock: dict) -> str:
executable = explicit or os.environ.get("TYPST") or shutil.which("typst")
if not executable:
raise ValueError("Typst not found; use --typst")
version = subprocess.check_output([executable, "--version"], text=True).strip()
if not version.startswith("typst " + lock["publication_tools"]["typst"]["version"] + " "):
raise ValueError("Tiny requires the locked Typst version")
return executable
def validate_output(out: Path, config: dict) -> Path:
out = out.resolve()
if not out.is_relative_to((ROOT / "build").resolve()):
raise ValueError("Tiny outputs must be inside build/")
previous = out / "manifest.json"
if previous.is_file() and read(previous).get("document", {}).get("document_id") != config["document_id"]:
raise ValueError("Tiny output cannot overwrite another product's build record")
return out
def validate_rendered_registers(data: dict, items: list) -> int:
expected = {(family, r["key"]) for family, value in data["registers"].items() for r in value["registers"]}
actual = [(r["family"], r["key"]) for r in items if isinstance(r, dict) and r.get("kind") == "tiny-register"]
if len(actual) != len(set(actual)) or set(actual) != expected:
raise ValueError("Tiny PDF omitted or duplicated a register definition")
return len(actual)
def build(config: dict, executable: str, out: Path | None = None) -> Path:
lock = load_lock()
media = assets(lock)
data = collect(config)
hashes = inputs(data)
signature = hashlib.sha256(json.dumps(hashes, sort_keys=True).encode()).hexdigest()[:12]
out = out or ROOT / "build" / f"datasheet-tiny-{datetime.now(timezone.utc):%Y-%m-%d-%H-%M}-{signature}"
out = validate_output(out, config)
out.mkdir(parents=True, exist_ok=True)
write(out / "data.json", data)
pdf = out / config["filename"]
epoch = int(datetime.fromisoformat(config["date"]).replace(tzinfo=timezone.utc).timestamp())
options = ["--root", str(ROOT), "--font-path", "publications/media/fonts", "--ignore-system-fonts",
"--package-path", str(CACHE / "packages"), "--package-cache-path", str(CACHE / "packages"),
"--input", "data=/" + (out / "data.json").relative_to(ROOT).as_posix(), "--creation-timestamp", str(epoch)]
result = subprocess.run([executable, "compile", config["entrypoint"], str(pdf), *options], cwd=ROOT, text=True, capture_output=True, encoding="utf-8")
atomic_write(out / "typst.log", result.stdout + result.stderr)
if result.returncode or "warning:" in result.stderr:
raise ValueError("Tiny Typst build failed or warned:\n" + result.stderr)
query = '(headings:query(heading).map(h=>(level:h.level,outlined:h.outlined,title:h.body,label:repr(h.at("label",default:none)),page:h.location().page())),items:query(metadata).map(it=>it.value))'
result = subprocess.run([executable, "eval", query, "--in", config["entrypoint"], *options], cwd=ROOT, text=True, capture_output=True, encoding="utf-8")
if result.returncode or "warning:" in result.stderr:
raise ValueError("Tiny layout query failed:\n" + result.stderr)
layout = json.loads(result.stdout)
validate_structure(read(ROOT / BOOK / "structure-contract.json"), layout["headings"], data["chapter_index"])
validate_rendered_registers(data, layout["items"])
write(out / "document-structure.json", layout["headings"])
write(out / "layout.json", layout["items"])
write(out / "ip-pages.json", [r for r in layout["items"] if isinstance(r, dict) and r.get("kind") in {"ip-start", "ip-end"}])
write(out / "waveform-audit.json", data["waveform_audit"])
diagrams = [r for r in layout["items"] if isinstance(r, dict) and r.get("kind") == "tiny-diagram"]
write(out / "diagram-inventory.json", diagrams)
manifest = dict(document=config, inputs=hashes, assets=media, pdf_sha256=sha256(pdf),
publication_checkout=dict(head=subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip(), path=str(ROOT)),
typst=lock["publication_tools"]["typst"]["version"], media_revision=lock["sources"]["publication_media"]["revision"],
packages={p["identity"]: p["sha256"] for p in package_records(lock)},
document_structure_sha256=sha256(out / "document-structure.json"), diagram_inventory_sha256=sha256(out / "diagram-inventory.json"))
write(out / "manifest.json", manifest)
atomic_write(LATEST, str(out) + "\n")
print(f"Built {pdf}")
return pdf
def check_pdf(pdf: Path, data: dict) -> dict:
from pypdf import PdfReader
import pdfplumber
manifest = read(pdf.parent / "manifest.json")
if sha256(pdf) != manifest["pdf_sha256"]:
raise ValueError("Tiny PDF digest does not match manifest")
for path, digest in manifest["inputs"].items():
if sha256(ROOT / path) != digest:
raise ValueError(f"Tiny input changed after build: {path}")
reader = PdfReader(pdf)
if reader.metadata.title != data["document"]["title"] or data["document"]["document_id"] not in reader.metadata.keywords:
raise ValueError("Tiny PDF identity mismatch")
if repository_footer_pages(reader) != list(range(1, len(reader.pages) + 1)):
raise ValueError("Tiny repository footer missing")
layout = read(pdf.parent / "ip-pages.json")
register_count = validate_rendered_registers(data, read(pdf.parent / "layout.json"))
starts = [r["id"] for r in layout if r["kind"] == "ip-start"]
ends = [r["id"] for r in layout if r["kind"] == "ip-end"]
if starts != ends or starts != [r["id"] for r in data["chapter_index"]]:
raise ValueError("Tiny IP chapter coverage missing")
if not reader.outline:
raise ValueError("Tiny bookmarks missing")
internal = 0
for page in reader.pages:
if not (page.extract_text() or "").strip():
raise ValueError("Tiny PDF contains a blank/raster-only page")
for annotation in page.get("/Annots", []):
item = annotation.get_object()
destination = item.get("/Dest", item.get("/A", {}).get("/D"))
if destination is not None:
if isinstance(destination, list) and (not destination or destination[0].get_object().get("/Type") != "/Page"):
raise ValueError("Tiny link has no page destination")
internal += 1
for ref in page["/Resources"].get("/Font", {}).get_object().values():
font = ref.get_object()
child = font.get("/DescendantFonts", [font])[0].get_object()
descriptor = child.get("/FontDescriptor", {}).get_object()
if not any(key in descriptor for key in ("/FontFile", "/FontFile2", "/FontFile3")):
raise ValueError("Tiny font is not embedded")
with pdfplumber.open(pdf) as rendered:
for page in rendered.pages:
for char in page.chars:
if char["text"].strip() and char["size"] < 8.45:
raise ValueError(f"Tiny undersized text on page {page.page_number}: {char['text']}")
footer = page.crop((0, page.height - 45, page.width, page.height)).extract_text() or ""
if f"{page.page_number} / {len(rendered.pages)}" not in footer:
raise ValueError("Tiny printed footer numbering mismatch")
result = dict(pages=len(reader.pages), internal_links=internal, ip_chapters=len(starts),
pdf_sha256=manifest["pdf_sha256"], fonts_embedded=True, register_definitions=register_count, source_inputs=len(manifest["inputs"]))
write(pdf.parent / "check-report.json", result)
print("Tiny PDF checks passed:", result)
return result
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("command", choices=["setup", "build", "check"])
parser.add_argument("--config", type=Path, default=ROOT / BOOK / "tiny.json")
parser.add_argument("--typst")
parser.add_argument("--output-dir", type=Path)
parser.add_argument("--pdf", type=Path)
parser.add_argument("--source-only", action="store_true")
args = parser.parse_args()
try:
config, lock = read(args.config), load_lock()
validate_configuration(config)
if args.command == "setup":
prepare(lock)
elif args.command == "build":
build(config, typst_binary(args.typst, lock), args.output_dir)
else:
data = collect(config)
if not args.source_only:
assets(lock)
check_pdf(args.pdf or Path(LATEST.read_text().strip()) / config["filename"], data)
print("Tiny source checks passed: configuration, 14 IPs, registers, maps, interrupts, pads and waveforms")
except (OSError, ValueError, KeyError, subprocess.CalledProcessError) as error:
parser.exit(1, f"tiny datasheet: {error}\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+1 -1
View File
@@ -20,7 +20,7 @@ def check_examples(compiler: str) -> int:
subprocess.run( subprocess.run(
[ [
sys.executable, sys.executable,
str(ROOT / "rtl/mini/address_map/generate_memory_map.py"), str(ROOT / "scripts/rtl/generate_memory_map.py"),
"--map", "--map",
str(ROOT / "rtl/mini/address_map/memory_map.json"), str(ROOT / "rtl/mini/address_map/memory_map.json"),
"--output-dir", "--output-dir",
+38
View File
@@ -0,0 +1,38 @@
"""Compile Tiny publication examples with Tiny-generated SDK metadata only."""
from __future__ import annotations
import argparse
from pathlib import Path
import subprocess
import sys
import tempfile
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.generate_tiny import generate_bindings, read_topology # noqa: E402
from scripts.rtl.generate_memory_map import generate # noqa: E402
def check_examples(compiler: str) -> int:
with tempfile.TemporaryDirectory(prefix="retrosoc-tiny-examples-") as location:
generated = Path(location)
generate_bindings(read_topology(ROOT / "rtl/tiny/integration/soc_topology.json", ROOT / "rtl/tiny/address_map/memory_map.json"), generated)
generate(ROOT / "rtl/tiny/address_map/memory_map.json", generated / "memory_map", "YES", 128)
includes = [ROOT / "crt/include", generated / "include", generated / "memory_map/include"]
includes += [ROOT / f"rtl/managed/clusterip/{name}/sw/include" for name in ("archinfo", "pwm", "rtc", "wdg")]
command = [compiler, "-std=c11", "-ffreestanding", "-Wall", "-Wextra", "-Werror", "-fsyntax-only", "-DRS_SOC_TINY=1"]
for path in includes:
command += ["-I", str(path)]
examples = sorted((ROOT / "publications/examples/tiny").glob("*.c"))
if len(examples) != 13:
raise ValueError("Tiny example inventory changed; review the complete set")
for path in examples:
subprocess.run([*command, str(path)], check=True, cwd=ROOT)
print(f"Tiny SDK examples: {len(examples)} syntax checks passed")
return len(examples)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--cc", default="gcc")
check_examples(parser.parse_args().cc)
@@ -8451,13 +8451,13 @@
], ],
"sources": [ "sources": [
"rtl/mini/top/retrosoc.sv", "rtl/mini/top/retrosoc.sv",
"rtl/mini/top/onchip_ram.sv" "rtl/ip/memory/onchip_ram.sv"
], ],
"instance_source": { "instance_source": {
"file": "rtl/mini/top/retrosoc.sv", "file": "rtl/mini/top/retrosoc.sv",
"name": "u_onchip_ram", "name": "u_onchip_ram",
"module": "onchip_ram", "module": "onchip_ram",
"declaration_file": "rtl/mini/top/onchip_ram.sv" "declaration_file": "rtl/ip/memory/onchip_ram.sv"
} }
}, },
{ {
@@ -8495,7 +8495,7 @@
} }
], ],
"sources": [ "sources": [
"rtl/mini/top/onchip_ram.sv" "rtl/ip/memory/onchip_ram.sv"
] ]
} }
], ],
@@ -8562,11 +8562,11 @@
"style": "direct", "style": "direct",
"bindings": [ "bindings": [
{ {
"file": "rtl/mini/top/onchip_ram.sv", "file": "rtl/ip/memory/onchip_ram.sv",
"text": ".data_i(s_mem_wdata[31:0])" "text": ".data_i(s_mem_wdata[31:0])"
}, },
{ {
"file": "rtl/mini/top/onchip_ram.sv", "file": "rtl/ip/memory/onchip_ram.sv",
"text": ".data_i(s_mem_wdata[63:32])" "text": ".data_i(s_mem_wdata[63:32])"
} }
], ],
@@ -10929,13 +10929,13 @@
], ],
"sources": [ "sources": [
"rtl/mini/top/retrosoc.sv", "rtl/mini/top/retrosoc.sv",
"rtl/mini/top/onchip_ram.sv" "rtl/ip/memory/onchip_ram.sv"
], ],
"instance_source": { "instance_source": {
"file": "rtl/mini/top/retrosoc.sv", "file": "rtl/mini/top/retrosoc.sv",
"name": "u_onchip_ram", "name": "u_onchip_ram",
"module": "onchip_ram", "module": "onchip_ram",
"declaration_file": "rtl/mini/top/onchip_ram.sv" "declaration_file": "rtl/ip/memory/onchip_ram.sv"
} }
}, },
{ {
@@ -1430,10 +1430,10 @@
"sram": { "sram": {
"document": "docs/ip/onchip-sram.md", "document": "docs/ip/onchip-sram.md",
"defines": [ "defines": [
"rtl/mini/top/onchip_ram_define.svh" "rtl/ip/memory/onchip_ram_define.svh"
], ],
"rtl": [ "rtl": [
"rtl/mini/top/onchip_ram_reg.sv" "rtl/ip/memory/onchip_ram_reg.sv"
], ],
"parameters": { "parameters": {
"Present": 1, "Present": 1,
@@ -58,7 +58,7 @@ the existing non-coherent buffer protocol. LP and HP configuration differences d
two harts into a symmetric cache-coherent SMP platform. two harts into a symmetric cache-coherent SMP platform.
#block(above:rhythm.metadata-before,below:rhythm.metadata-after,breakable:false)[ #block(above:rhythm.metadata-before,below:rhythm.metadata-after,breakable:false)[
#set text(size:9pt) #set text(size:9pt)
#source("rtl/mini/top/mgmt_core_wrapper.sv",title:"LP instance") · #source("rtl/ip/core/mgmt_core_wrapper.sv",title:"LP instance") ·
#source("scripts/vexiiriscv/GenerateRetroSocHp.scala",title:"HP configuration/PMA") · #source("scripts/vexiiriscv/GenerateRetroSocHp.scala",title:"HP configuration/PMA") ·
#source("scripts/generate_vexiiriscv.py",title:"Artifact manifest") · #source("scripts/generate_vexiiriscv.py",title:"Artifact manifest") ·
#source("app/ports/linux/linux/retrosoc_hp.dts",title:"HP platform properties") #source("app/ports/linux/linux/retrosoc_hp.dts",title:"HP platform properties")
@@ -41,7 +41,7 @@ The debug replacement sets the stack and calls main, without the generic PSRAM i
relocation or BSS loop. The XPI flash-loader replacement sets the stack, clears BSS and calls relocation or BSS loop. The XPI flash-loader replacement sets the stack, clears BSS and calls
main; if main returns it executes EBREAK and loops. Those compact entries are tied to their main; if main returns it executes EBREAK and loops. Those compact entries are tied to their
application/linker assumptions and do not establish a universal alternative startup sequence. application/linker assumptions and do not establish a universal alternative startup sequence.
#source-note("rtl/mini/mk/software.mk",title:"Generic CRT, conditional IRQ objects and application CRT replacement") #source-note("rtl/mk/software.mk",title:"Generic CRT, conditional IRQ objects and application CRT replacement")
#source-note("crt/arch/riscv/startup.S",title:"Actual generic LP entry and PSRAM-ready loop") #source-note("crt/arch/riscv/startup.S",title:"Actual generic LP entry and PSRAM-ready loop")
#source-note("app/apps/debug/startup.S",title:"Dedicated debug entry") #source-note("app/apps/debug/startup.S",title:"Dedicated debug entry")
#source-note("app/apps/xpi_flash_loader/startup.S",title:"Dedicated SRAM flash-loader entry") #source-note("app/apps/xpi_flash_loader/startup.S",title:"Dedicated SRAM flash-loader entry")
@@ -13,7 +13,7 @@
"rtl/mini/top/apb4_periph.sv", "rtl/mini/top/apb4_periph.sv",
"rtl/mini/top/apb4_system.sv", "rtl/mini/top/apb4_system.sv",
"rtl/mini/top/memory_pad_mux.sv", "rtl/mini/top/memory_pad_mux.sv",
"rtl/mini/top/mgmt_debug_wrapper.sv", "rtl/ip/core/mgmt_debug_wrapper.sv",
"rtl/mini/top/retrosoc.sv", "rtl/mini/top/retrosoc.sv",
"rtl/mini/top/soc_clock_reset_subsystem.sv", "rtl/mini/top/soc_clock_reset_subsystem.sv",
"rtl/mini/top/soc_data_plane.sv" "rtl/mini/top/soc_data_plane.sv"
@@ -168,7 +168,7 @@
"ips": [], "ips": [],
"rotate": false, "rotate": false,
"clock": { "clock": {
"file": "rtl/mini/top/mgmt_debug_wrapper.sv", "file": "rtl/ip/core/mgmt_debug_wrapper.sv",
"name": "u_jtag_rst_sync", "name": "u_jtag_rst_sync",
"module": "rst_sync", "module": "rst_sync",
"port": "clk_i", "port": "clk_i",
@@ -132,7 +132,7 @@
"crt/include/retrosoc/hal/onchip_sram.h", "crt/include/retrosoc/hal/onchip_sram.h",
"docs/ip/onchip-sram.md", "docs/ip/onchip-sram.md",
"publications/examples/sram.c", "publications/examples/sram.c",
"rtl/mini/top/onchip_ram_reg.sv" "rtl/ip/memory/onchip_ram_reg.sv"
], ],
"tests": [ "tests": [
"tests/test_onchip_sram_register_parity.py" "tests/test_onchip_sram_register_parity.py"
@@ -1065,7 +1065,7 @@
"applies_to": "Reviewed generic LP CRT; dedicated debug and XPI-loader startups have separate initialization contracts.", "applies_to": "Reviewed generic LP CRT; dedicated debug and XPI-loader startups have separate initialization contracts.",
"sources": [ "sources": [
"crt/arch/riscv/startup.S", "crt/arch/riscv/startup.S",
"rtl/mini/mk/software.mk" "rtl/mk/software.mk"
] ]
} }
], ],
@@ -3253,7 +3253,7 @@
"rtl/managed/clusterip/common/rtl/utils/register.sv", "rtl/managed/clusterip/common/rtl/utils/register.sv",
"rtl/mini/top/clock_config_controller.sv", "rtl/mini/top/clock_config_controller.sv",
"rtl/mini/top/hp_lifecycle_controller.sv", "rtl/mini/top/hp_lifecycle_controller.sv",
"rtl/mini/top/mgmt_core_wrapper.sv", "rtl/ip/core/mgmt_core_wrapper.sv",
"rtl/mini/top/resource_controller.sv", "rtl/mini/top/resource_controller.sv",
"rtl/mini/top/soc_clock_reset_subsystem.sv", "rtl/mini/top/soc_clock_reset_subsystem.sv",
"scripts/generate_vexiiriscv.py", "scripts/generate_vexiiriscv.py",
@@ -5065,7 +5065,7 @@
"crt/src/service/test.c", "crt/src/service/test.c",
"publications/datasheets/mini.json", "publications/datasheets/mini.json",
"rtl/mini/integration/soc_topology.json", "rtl/mini/integration/soc_topology.json",
"rtl/mini/mk/software.mk", "rtl/mk/software.mk",
"scripts/build_hp_linux.py", "scripts/build_hp_linux.py",
"scripts/regress.py", "scripts/regress.py",
"app/ports/linux/init" "app/ports/linux/init"
@@ -5073,17 +5073,17 @@
"bindings": [ "bindings": [
{ {
"id": "csr-selection", "id": "csr-selection",
"file": "rtl/mini/mk/software.mk", "file": "rtl/mk/software.mk",
"text": "ifeq ($(HAVE_CSR),YES)\nDEF_VAL += -DCSR_ENABLE\nendif" "text": "ifeq ($(HAVE_CSR),YES)\nDEF_VAL += -DCSR_ENABLE\nendif"
}, },
{ {
"id": "irq-objects", "id": "irq-objects",
"file": "rtl/mini/mk/software.mk", "file": "rtl/mk/software.mk",
"text": "ifeq ($(HAVE_CSR),YES)\nCRT_SRCS += $(ROOT_PATH)/crt/arch/riscv/system_irq.S\nCRT_SRCS += $(ROOT_PATH)/crt/src/core/system_irq_handler.c\nCRT_SRCS += $(ROOT_PATH)/crt/src/core/irq.c" "text": "ifeq ($(HAVE_CSR),YES)\nCRT_SRCS += $(ROOT_PATH)/crt/arch/riscv/system_irq.S\nCRT_SRCS += $(ROOT_PATH)/crt/src/core/system_irq_handler.c\nCRT_SRCS += $(ROOT_PATH)/crt/src/core/irq.c"
}, },
{ {
"id": "app-override", "id": "app-override",
"file": "rtl/mini/mk/software.mk", "file": "rtl/mk/software.mk",
"text": "ifneq ($(strip $(APP_CRT_SRCS)),)\nCRT_SRCS := $(APP_CRT_SRCS)\nendif" "text": "ifneq ($(strip $(APP_CRT_SRCS)),)\nCRT_SRCS := $(APP_CRT_SRCS)\nendif"
}, },
{ {
@@ -5953,8 +5953,8 @@
"rtl/mini/top/fabric_monitor.sv", "rtl/mini/top/fabric_monitor.sv",
"rtl/mini/top/hp_subsystem.sv", "rtl/mini/top/hp_subsystem.sv",
"rtl/mini/top/memory_pad_mux.sv", "rtl/mini/top/memory_pad_mux.sv",
"rtl/mini/top/mgmt_debug_wrapper.sv", "rtl/ip/core/mgmt_debug_wrapper.sv",
"rtl/mini/top/onchip_ram.sv", "rtl/ip/memory/onchip_ram.sv",
"rtl/mini/top/rcu.sv", "rtl/mini/top/rcu.sv",
"rtl/mini/top/resource_controller.sv", "rtl/mini/top/resource_controller.sv",
"rtl/mini/top/retrosoc.sv", "rtl/mini/top/retrosoc.sv",
+49
View File
@@ -0,0 +1,49 @@
# Tiny Gen1 publication
This directory owns the English **retroSoC Tiny Gen1 / Rill (清溪)** datasheet,
its independent v0.1 DRAFT identity, 14 IP chapters and 39 frozen structural
entries. It is a publication adapter, not an RTL or SDK register generator.
The source of truth is `rtl/tiny`, `configs/ci/ihp130-tiny.mk`, the active shared
RTL/SDK and the locked Common/Hazard3/peripheral repositories. The reviewed
SHA belongs in [tiny.json](tiny.json). Address, IRQ and pad mappings are read
from Tiny's canonical inputs. The register profiles select only applicable
families; the owned SYSCTRL and ARCHINFO overrides are checked separately.
## Build and check
From the repository root, with the pinned publication Python dependencies and
Typst version available:
```sh
python3 publications/build_tiny_datasheet.py setup
python3 publications/build_tiny_datasheet.py check --source-only
python3 publications/build_tiny_datasheet.py build --typst /path/to/locked/typst
python3 publications/build_tiny_datasheet.py check --pdf build/<tiny-variant>/retrosoc-tiny-gen1-datasheet.pdf
python3 publications/check_tiny_examples.py --cc gcc
python3 -m pytest -q tests/test_publication_tiny.py tests/test_publication_registers.py tests/test_publication_waveforms.py
```
The optional `--config` selects a Tiny configuration file. Outputs belong to
`build/datasheet-tiny-<UTC-date-time>-<input-hash>/`. The only latest-build marker
written is `.cache/retrosoc/publications/latest-tiny`; Mini markers and products
are not replaced. Font/package caches are shared and hash-checked. Tiny setup
requires only the managed inputs it uses, not MPW, VexiiRiscv or accelerator data.
## Content and validation boundary
- [Layout supplement](style.md) defines the Tiny use of the approved Mini visual language.
- [Content review](content-review.md) records retained coverage and known gaps.
- [Chapter review](chapter-review.md) binds chapter/IP coverage to the delivered PDF.
- [Structure contract](structure-contract.json) freezes entry order and labels independently of page count.
- [Source contract](source-contract.json) binds numerical maps and critical behavior.
- [Evidence](evidence.json) distinguishes dated repository narratives from matching executed reports.
The broad validation report and PDF hash accompany each build. Verify links,
source/parameter consistency, character sizes, outlines, complete register/IP
coverage and representative color/grayscale rendering. Repeat the build offline
and compare PDF bytes. New test counts must come from the current run.
The 24 MHz configuration is not timing-qualified. Publication checks do not
establish simulation, physical, board or silicon qualification. Do not repair
RTL, relax checks, or change warning/metric baselines as a document workaround.
+591
View File
@@ -0,0 +1,591 @@
[
{
"id": "xpi",
"family": "xpi",
"title": "XPI Universal Controller",
"depth": 3,
"regions": [
"FLASH",
"APB4_XPI",
"XPI"
],
"irqs": [
"xpi"
],
"summary": "Four-chip-select SDR serial-memory controller; pin-level boot uses NOR on NSS0.",
"features": [
"Four chip selects; 16 programmable LUT sequences.",
"Single, dual and quad SDR phases; mapped reads plus indirect PIO/DMA and status polling.",
"64-word TX and RX staging FIFOs; device-specific commands and timing remain software responsibilities."
],
"paragraphs": [
"The Flash alias at address zero and XPI aperture route to the same command engine. They are address windows, not two independent memories. The boot setup is the implemented NOR profile on NSS0; other chip selects are not qualified board configurations.",
"Memory reads and indirect operations share the controlled transaction engine. A write to a mapped Flash address is not a Flash program command. Execute erase/program service from SRAM with all users of the affected memory quiesced.",
"LUT phase widths, dummy cycles, clock divider and device/page bounds must match the selected NOR. Unsupported modes, FIFO starvation and device timeout require status inspection and bounded recovery. The generic controller does not imply DDR, HyperBus or arbitrary-device compatibility."
],
"steps": [
"Identify the actual NOR and configure the supported slot, timing and LUT while idle.",
"Keep the executing service and its working state in SRAM before erase/program.",
"Configure source/destination ranges and the supported DMA request, then start and use a bounded wait.",
"Inspect engine/FIFO/device status, verify completion and contents as required before making an updated image executable."
],
"nodes": [
"APB registers / LUT",
"Mapped read frontend",
"Transaction / SDR PHY",
"TX / RX FIFOs",
"NSS[3:0] / SCK / DQ[3:0]"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"docs/ip/xpi.md",
"rtl/ip/storage/xpi_reg.sv",
"rtl/ip/storage/xpi_pkg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "sram",
"family": "sram",
"title": "On-Chip SRAM",
"depth": 3,
"regions": [
"SRAM",
"APB4_SRAM"
],
"irqs": [],
"summary": "128 KiB on-chip SRAM; AXI32 data and one APB discovery/counter window.",
"features": [
"32 banks of 4 KiB in the selected profile.",
"32-bit AXI data, one-bit ID; byte writes and supported memory bursts.",
"SRAM code, data, BSS and stack; reset does not erase memory."
],
"paragraphs": [
"The Tiny top overrides the reusable SRAM to 128 KiB and 32-bit data. The native memory aperture is 0x30000000 through 0x3001FFFF. APB registers describe the integrated configuration and observations; they are not another storage aperture.",
"Technology macros are selected by the IHP130 profile. Logical bank capacity is not a claim of timing closure, yield or physical area. Preserve byte-lane and natural alignment rules, and keep transfers within the decoded aperture.",
"Reset clears control state, not SRAM contents. Startup copies initialized sections from Flash and clears BSS explicitly. Stack placement at the SRAM end does not establish a measured maximum stack demand."
],
"steps": [
"Probe memory size, bank count and data width before using a linked image.",
"Compare ELF/MAP section sizes, buffers and stack margin against the actual 128 KiB region.",
"Use naturally aligned memory transfers and preserve ownership while DMA is active.",
"Read/reset counters according to their register semantics; reset is not data sanitization."
],
"nodes": [
"AXI32 adapter",
"Bank / byte selection",
"32 x 4 KiB SRAM",
"APB discovery / counters"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"docs/ip/onchip-sram.md",
"rtl/ip/memory/onchip_ram_reg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "gpio",
"family": "gpio",
"title": "General-Purpose Input/Output (GPIO)",
"depth": 3,
"regions": [
"APB4_GPIO",
"APB4_GPIO_ADMIN"
],
"irqs": [
"gpio"
],
"summary": "32 physical GPIOs with management and restricted-user register windows.",
"features": [
"Atomic set, clear and toggle outputs; mode, output-enable and open-drain control.",
"Per-pin interrupts and filtering; alternate functions from the Tiny pin map.",
"Both windows address the same pads, not two GPIO banks."
],
"paragraphs": [
"GPIO administration controls pad mode, filtering, masks and locks. The restricted window exposes only permitted data/event operations. The user-access mask starts clear; grant only the pins required by the application.",
"Tiny retains only its implemented alternate functions. Unavailable alternatives drive neither peripheral requests nor pads. UART TX/RX and XPI use dedicated pads and are not inferred from a Mini pin assignment.",
"Before changing a driven alternate function, disable its producer, release output enable and use the controller guard/ownership sequence. Electrical drive settings require a reviewed pad/board specification; a register field is not a voltage rating."
],
"steps": [
"Confirm the Tiny alternate-function map and external circuit.",
"Prepare output data before enabling the driver; set open-drain/pull behavior only as supported by the pad.",
"Clear observed event causes before enabling the intended interrupt mask.",
"Use atomic aliases for concurrent updates and apply irreversible locks only after validation."
],
"nodes": [
"APB user / admin",
"Mode / mask / locks",
"Data / event / filter",
"Alternate-function mux",
"32 shared GPIO pads"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"docs/ip/gpio.md",
"rtl/ip/peripheral/gpio_reg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "dma",
"family": "dma",
"title": "Direct Memory Access (DMA)",
"depth": 3,
"regions": [
"APB4_DMA"
],
"irqs": [
"dma"
],
"summary": "Four channel contexts share one AXI32 master; memory-mapped endpoints only.",
"features": [
"Four channels; direct transfers and 64-byte linked TCDs.",
"32-bit transfers, up to 16 beats, 32-word FIFO per channel.",
"Request mask 0x07F9; stream modes are disabled in Tiny."
],
"paragraphs": [
"Channel conventions are 0 UART0, 1 I2C0, 2 I2C1 and 3 bulk/XPI. They are software allocation conventions for one shared engine. Reserve a channel and all buffers through completion or confirmed abort.",
"Only request 0 and requests 3 through 10 are enabled. UART1 has no DMA request binding. Request enum names for omitted peripherals in the shared SDK do not establish Tiny hardware support; invalid kinds/requests must be rejected.",
"The fabric admits only one read or write transaction globally. CPU and DMA contend for that admission; burst length times bus width is not sustained throughput. A software wait timeout does not prove the engine is idle.",
"TCDs remain 64 bytes and require 64-byte alignment. Stride fields and y_count do not advertise 2D transfers: current validation requires zero strides and at most one row. Do not modify a descriptor or data buffer while hardware may still consume it.",
"The CAPABILITY maximum-burst field currently casts the configured 16 beats to four bits and therefore reads zero. The implemented parameter and SDK limit remain 16. Treat this as a discovery-encoding limitation; do not infer a zero-length engine or a qualified performance result from that field."
],
"steps": [
"Check channel < 4, MM-to-MM kind, 32-bit width, nonzero bounded length and an enabled request.",
"Prepare aligned descriptors/buffers and publish them with the required memory ordering.",
"Configure, start, then wait with a bounded software budget; distinguish submission from completion.",
"On failure capture error address/response and progress, request abort, and confirm terminal/drained state before reuse."
],
"nodes": [
"APB / 4 channel state",
"TCD fetch / validation",
"4 x 32-word FIFOs",
"Shared AXI32 master",
"Memory / paced I/O windows"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"docs/ip/dma.md",
"rtl/ip/peripheral/dma_reg.sv",
"rtl/ip/peripheral/dma_pkg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "timer",
"family": "timer",
"title": "General-Purpose Timers (TIM0, TIM1)",
"depth": 3,
"regions": [
"APB4_TIM0",
"APB4_TIM1"
],
"irqs": [
"timer0",
"timer1"
],
"summary": "Two 32-bit timer instances with prescalers and interrupt support.",
"features": [
"Free-running, periodic and one-shot modes.",
"16-bit prescalers and two compare channels per instance.",
"Debug-freeze integration; no additional advanced timer."
],
"paragraphs": [
"TIM0 and TIM1 share one register definition but have separate base addresses and IRQ bits. Program the selected timer against the actual 24 MHz system clock, not the CLINT microsecond timebase.",
"A delay API has both a requested time interval and a separate software polling budget. A zero polling budget or timeout is not proof that a previously running timer stopped.",
"Use documented status/W1C handling and capture event causes before rearming. Debug halt may stop counting when configured; it does not prevent all register access or provide a system-wide freeze."
],
"steps": [
"Stop the selected timer before changing prescale/reload/compare values.",
"Compute timing from its functional clock and check representable range.",
"Clear stale events, configure interrupt selection and start.",
"Acknowledge observed events, recheck pending state and verify stop on recovery."
],
"nodes": [
"APB register bank",
"Prescale / counter",
"Compare / reload",
"Event / debug-freeze"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"docs/ip/timer.md",
"rtl/ip/peripheral/timer_reg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "pwm",
"family": "pwm",
"title": "Pulse Width Modulation (PWM)",
"depth": 3,
"regions": [
"APB4_PWM"
],
"irqs": [
"pwm"
],
"summary": "Two internal timers and four waveform output channels with capture/fault control.",
"features": [
"Four PWM outputs, two capture inputs and fault/synchronization routes.",
"Shadow configuration and documented update boundaries.",
"Functional clock parameter is 24 MHz in Tiny."
],
"paragraphs": [
"PWM outputs and capture/fault inputs are selected through Tiny GPIO alternate functions. The reusable block has timer/operator state, not an unrestricted programmable I/O processor.",
"Shadow values become active at the configured update event. Writing a duty field alone does not guarantee an immediate glitch-free transition. Establish safe output levels before enabling external power circuitry.",
"Capture entries and overflow status require coherent reads and explicit acknowledgement. Fault handling and debug-halt behavior must be checked with the actual pin route; no electrical frequency limit is qualified here."
],
"steps": [
"Probe ABI and use the actual system clock in timing calculations.",
"Keep outputs inactive while programming timer/operator/channel state.",
"Latch shadow values at the selected boundary, then enable output routes.",
"On fault, preserve status and disable/recover outputs before clearing the cause."
],
"nodes": [
"APB / shadow registers",
"Timer / operator update",
"4 waveform channels",
"Capture / fault / IRQ"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"rtl/managed/clusterip/pwm/doc/datasheet.md",
"rtl/managed/clusterip/pwm/rtl/pwm_reg.sv",
"rtl/managed/clusterip/pwm/rtl/pwm_pkg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "rtc",
"family": "rtc",
"title": "Real-Time Clock (RTC)",
"depth": 3,
"regions": [
"APB4_RTC"
],
"irqs": [
"rtc"
],
"summary": "64-bit seconds, 1/256-second subsecond time, alarms and wake status.",
"features": [
"Two alarms, periodic wake and calibration controls.",
"Atomic time snapshot and command/response interface.",
"Tiny clocks the RTC from the system clock, not a separate sleep crystal."
],
"paragraphs": [
"The managed RTC engine is configured for 24 MHz and is wired to the same system clock as the MCU. Its separate logical clock/reset ports do not establish an independent low-power timing source.",
"Time reads use the documented snapshot/command sequence. Poll command completion before assuming a write has taken effect. Alarm interrupt and wake signals are separate; SYSCTRL exposes both live and sticky wake observations.",
"Battery-backed retention, oscillator accuracy, calendar drift and deep-sleep operation remain unspecified. A digital counter resolution is not a measured time accuracy."
],
"steps": [
"Probe the engine and configure it against the actual clock.",
"Set time through the bounded command API and verify completion.",
"Configure alarm/periodic selections and clear prior causes before enabling events.",
"Read snapshots consistently and capture wake/IRQ causes before acknowledgement."
],
"nodes": [
"APB command bridge",
"Time / calibration",
"Alarm / periodic logic",
"Snapshot / IRQ / wake"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"rtl/managed/clusterip/rtc/doc/datasheet.md",
"rtl/managed/clusterip/rtc/rtl/rtc_reg.sv",
"rtl/managed/clusterip/rtc/rtl/rtc_pkg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "wdg",
"family": "wdg",
"title": "Watchdog (WDG)",
"depth": 3,
"regions": [
"APB4_WDG"
],
"irqs": [
"watchdog_early_warning"
],
"summary": "Window watchdog with early warning and whole-system reset request.",
"features": [
"24 MHz system-clock reference in Tiny.",
"Two-key service, window checking and retained reset-cause observation.",
"Eight watchdog-clock reset-request cycles in this integration."
],
"paragraphs": [
"Configure timing and window policy while inactive, then start and service using the prescribed key sequence. An early or malformed service can request reset just as an expired timeout can.",
"Tiny routes the watchdog request into system reset generation. Its own reset/retention boundary is distinct from the reset it requests; the acceptance application checks retained reset_count on reboot.",
"Because the watchdog uses the same system clock, it does not guarantee recovery from a failed system clock or provide independent deep-sleep supervision. The current integration is not a safety certificate."
],
"steps": [
"Probe status and inspect retained reset cause before starting a new diagnostic campaign.",
"Program prescaler, timeout, window and warning policy while inactive.",
"Start once and service inside the allowed window with bounded checks.",
"After reset distinguish watchdog reboot from fresh power-on and retain diagnostic evidence."
],
"nodes": [
"APB command bridge",
"Prescale / window counter",
"Service-key checker",
"Warning / retained cause / reset"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"rtl/managed/clusterip/wdg/doc/datasheet.md",
"rtl/managed/clusterip/wdg/rtl/wdg_reg.sv",
"rtl/managed/clusterip/wdg/rtl/wdg_pkg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "uart0",
"family": "uart",
"title": "Management UART (UART0)",
"depth": 3,
"regions": [
"APB4_UART0"
],
"irqs": [
"uart0"
],
"summary": "Primary console UART with 64-byte transmit and receive FIFOs.",
"features": [
"Fractional baud generation, 5-8 data bits, parity, break and diagnostics.",
"Dedicated TX/RX pads; UART0 DMA requests available.",
"RTS/CTS require the selected GPIO alternate routes."
],
"paragraphs": [
"The shared rs_uart_* convenience HAL targets UART0. It is the normal Tiny diagnostic console. Configure baud against 24 MHz and the permitted fractional divider range; sampling margin remains a board/peer requirement.",
"FIFO acceptance is not completed wire transmission. A UART write timeout can leave a transmitted/queued prefix. Preserve protocol framing and inspect RX error flags instead of blindly retrying an entire message.",
"The polling budget is consumed while the FIFO blocks progress and is shared across the call. It is not a duration in milliseconds and cannot bound a stalled bus access."
],
"steps": [
"Configure baud, framing and optional flow-control routing before enabling the peer.",
"Clear stale error/event state and use bounded FIFO operations.",
"Reserve channel 0 when using UART0 DMA, and follow buffer lifetime rules.",
"Wait for the required queue/serializer completion boundary before changing framing or disabling clocks."
],
"nodes": [
"APB / baud registers",
"64-byte TX FIFO / serializer",
"RX sampler / 64-byte FIFO",
"IRQ / error / DMA requests"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"docs/ip/uart.md",
"rtl/ip/serial/uart_reg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "uart1",
"family": "uart",
"title": "Second UART (UART1)",
"depth": 3,
"regions": [
"APB4_UART1"
],
"irqs": [
"uart1"
],
"summary": "Second UART instance with dedicated TX/RX and its own interrupt.",
"features": [
"Same UART v3 register ABI and 64-byte FIFOs.",
"PIO/IRQ integration; no Tiny UART1 DMA request route.",
"CTS input is tied permissive; no dedicated RTS/CTS pad pair."
],
"paragraphs": [
"UART1 has its own base and core IRQ bit 26. The generic UART0 helper must not be mistaken for an instance-selecting UART1 API. Use the correct base/register contract or the documented Tiny loopback acceptance source.",
"The instance reuses the UART FIFO, divider, framing and error semantics. Tiny ties its CTS input low and leaves the DMA stall outputs unconnected to requests; shared register fields do not create the omitted routing.",
"Loopback acceptance is a source-level test facility. It does not qualify an external baud rate, cable, voltage domain or Linux driver."
],
"steps": [
"Select the UART1 base and confirm framing/divider settings before use.",
"Use PIO or core IRQ bit 26; do not allocate a UART0 DMA selector for UART1.",
"Handle RX error entries and use bounded TX/RX waits.",
"Confirm serializer idle before changing framing, then clear only captured interrupt causes."
],
"nodes": [
"UART1 APB bank",
"64-byte TX / RX FIFOs",
"Framing / fractional divider",
"Dedicated TX/RX / IRQ26"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"docs/ip/uart.md",
"rtl/ip/serial/uart_reg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "i2c",
"family": "i2c",
"title": "Inter-Integrated Circuit (I2C0, I2C1)",
"depth": 3,
"regions": [
"APB4_I2C0",
"APB4_I2C1"
],
"irqs": [
"i2c0",
"i2c1"
],
"summary": "Two controller-mode I2C instances with command/receive queues.",
"features": [
"7-bit/10-bit addressing, repeated START and clock stretching.",
"Sixteen-entry command and RX queues; arbitration and recovery reporting.",
"DMA pacing uses channels 1 and 2 by software convention."
],
"paragraphs": [
"I2C0 and I2C1 share the register layout but have separate base addresses, GPIO alternate routes and IRQ bits. They are controllers, not target/slave peripherals. Board pull-ups and compatible voltage levels are required.",
"The queued command grammar and FIFO limits define how START, data, repeated START and STOP are submitted. A NACK is an observed protocol outcome; do not treat command FIFO acceptance as a successful bus transaction.",
"Use bounded transaction and recovery waits. Arbitration loss, stuck-low lines or clock stretching require inspection and a bus-safe recovery sequence rather than concurrent driving or automatic infinite retry."
],
"steps": [
"Select the bus, verify its GPIO route and calculate timing from 24 MHz.",
"Ensure the bus is released, clear stale events and configure supported address mode.",
"Submit the documented command sequence and inspect terminal/NACK/arbitration status.",
"Recover and release lines before retrying; preserve timeout/error context."
],
"nodes": [
"APB / timing / status",
"16-entry command queue",
"START / shift / STOP engine",
"RX queue / IRQ / DMA pacing"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"docs/ip/i2c.md",
"rtl/ip/serial/i2c_reg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "sysctrl",
"family": "sysctrl",
"title": "System Controller (SYSCTRL)",
"depth": 3,
"regions": [
"APB4_SYSCTRL"
],
"irqs": [],
"summary": "Tiny-owned control and first-fault/acceptance register bank.",
"features": [
"Sticky first-fault address, master, response and saturating event count.",
"CPU/DMA wait-counter snapshot; live/sticky RTC wake status.",
"First full-word valid TEST_STATUS result remains sticky until reset."
],
"paragraphs": [
"Only the decoded Tiny offsets below are implemented. Compatibility identity reads do not create selectable cores, a PLL or an application processor. Other offsets fail; writes to read-only locations, unaligned accesses and partial writes return APB error.",
"FAULT_STATUS captures the first pending event. Clearing pending permits a subsequent fault record; FAULT_COUNT counts events independently and saturates. Capture address/master/response before clearing.",
"PERF_CTRL controls wait counters and snapshots. Read low/high words from the captured snapshot, not from independently changing live values. A wait count is not end-to-end throughput.",
"TEST_STATUS accepts a full-word write with VALID set only while the prior result is invalid. Reserved input bits are masked; the terminal result is not UART text. A later result cannot overwrite the first valid one before reset."
],
"steps": [
"Use the Tiny identity before choosing control operations.",
"Read and preserve pending fault status/address/master/detail before W1C acknowledgement.",
"Enable/capture CPU and DMA wait counters through PERF_CTRL and then read both snapshot words.",
"For automated acceptance write VALID, PASS and result code once; an earlier failure cannot be replaced by a later pass."
],
"nodes": [
"APB alignment / strobe gate",
"Tiny offset decoder",
"First-fault / snapshots",
"Sticky TEST_STATUS / RTC wake"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"rtl/tiny/top/tiny_sysctrl.sv",
"docs/ip/tiny-soc.md"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "clint",
"family": "clint",
"title": "Local Interrupt Controller (CLINT)",
"depth": 3,
"regions": [
"APB4_CLINT"
],
"irqs": [
"clint_software",
"clint_timer"
],
"summary": "Machine software interrupt and 64-bit timer for hart slot zero.",
"features": [
"Core interrupt bits 0 and 1 are local software/timer causes.",
"1 MHz timebase derived from the 24 MHz system clock.",
"64-bit time and compare are accessed as RV32 words."
],
"paragraphs": [
"Tiny has one management hart and uses hart slot zero. CLINT is distinct from the external interrupt vector and does not provide a PLIC.",
"Read time using a coherent high/low/high retry sequence. Update compare with the documented safe word order so an intermediate value does not trigger an unintended interrupt.",
"The 1 MHz timebase supports timing measurements/deadlines, while peripheral dividers still use their own 24 MHz engine clock. A software busy-loop count is not this timebase."
],
"steps": [
"Use slot zero and the CLINT base from the generated Tiny map.",
"Read coherent time through the SDK helper.",
"Mask/prepare the timer cause and update compare in safe word order.",
"Handle and rearm the appropriate local cause; do not subtract two to form an external IRQ ordinal."
],
"nodes": [
"APB word accesses",
"64-bit time counter",
"Hart0 compare / MSIP",
"Local software / timer IRQ"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"docs/ip/clint.md",
"rtl/ip/peripheral/clint_reg.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
},
{
"id": "archinfo",
"family": "archinfo",
"title": "Architecture Information (ARCHINFO)",
"depth": 3,
"regions": [
"APB4_ARCHINFO"
],
"irqs": [],
"summary": "Read-only Tiny identity and build/configuration discovery.",
"features": [
"Tiny SOC_ID 0x54494E59; 128 KiB SRAM and 24 MHz reference clock.",
"Topology 0x20200001, features 0x00007FFE and technology 0x02010082.",
"No provisioned unique device ID is advertised."
],
"paragraphs": [
"The owned Tiny adapter replaces the managed block's fixed SoC identity while retaining its register ABI. BUILD_ID, CONFIG_ID and BUILD_STATUS are supplied by generated build metadata; they must not be published as default zero values for a built image.",
"Read and validate Tiny identity before interpreting product-specific capabilities or selecting a firmware layout. The build/profile identity is distinct from a silicon lot/serial number.",
"Device-ID inputs are tied invalid/read-disabled. Their register offsets do not establish a provisioned unique identity, secure key storage or production traceability."
],
"steps": [
"Read discovery through the SDK and validate the build identity.",
"Confirm Tiny SOC_ID, one-hart topology, 128 KiB memory and active capabilities.",
"Reject mismatched images/configurations before hardware initialization.",
"Read device ID only when qualified and enabled; this integration does not supply it."
],
"nodes": [
"APB read-only ABI",
"Build / config metadata",
"Tiny identity override",
"Capability / device-ID gate"
],
"sources": [
"rtl/tiny/top/retrosoc_tiny.sv",
"rtl/managed/clusterip/archinfo/doc/datasheet.md",
"rtl/managed/clusterip/archinfo/rtl/archinfo_reg.sv",
"rtl/tiny/top/tiny_archinfo.sv"
],
"evidence": "Source reviewed; current-commit matching raw qualification report not supplied."
}
]
@@ -0,0 +1,86 @@
[
{
"id": "xpi",
"family": "xpi",
"title": "XPI Universal Controller",
"depth": 3
},
{
"id": "sram",
"family": "sram",
"title": "On-Chip SRAM",
"depth": 3
},
{
"id": "gpio",
"family": "gpio",
"title": "General-Purpose Input/Output (GPIO)",
"depth": 3
},
{
"id": "dma",
"family": "dma",
"title": "Direct Memory Access (DMA)",
"depth": 3
},
{
"id": "timer",
"family": "timer",
"title": "General-Purpose Timers (TIM0, TIM1)",
"depth": 3
},
{
"id": "pwm",
"family": "pwm",
"title": "Pulse Width Modulation (PWM)",
"depth": 3
},
{
"id": "rtc",
"family": "rtc",
"title": "Real-Time Clock (RTC)",
"depth": 3
},
{
"id": "wdg",
"family": "wdg",
"title": "Watchdog (WDG)",
"depth": 3
},
{
"id": "uart0",
"family": "uart",
"title": "Management UART (UART0)",
"depth": 3
},
{
"id": "uart1",
"family": "uart",
"title": "Second UART (UART1)",
"depth": 3
},
{
"id": "i2c",
"family": "i2c",
"title": "Inter-Integrated Circuit (I2C0, I2C1)",
"depth": 3
},
{
"id": "sysctrl",
"family": "sysctrl",
"title": "System Controller (SYSCTRL)",
"depth": 3
},
{
"id": "clint",
"family": "clint",
"title": "Local Interrupt Controller (CLINT)",
"depth": 3
},
{
"id": "archinfo",
"family": "archinfo",
"title": "Architecture Information (ARCHINFO)",
"depth": 3
}
]
@@ -0,0 +1,49 @@
# Tiny Gen1 v0.1 DRAFT chapter review
Source: `9c706ba0ff1da1028c299cfff573010a86dfd7ce`. PDF: **204 pages**, SHA-256 `44d2b6140395ab3adea6e1ca64ce72ba65fa4727213bfca136a6e47010958d4a`.
All **39 frozen entries / 14 IP chapters** are reviewed below. Register, address, IRQ and pad values follow the Tiny implementation. The dated evidence narrative is not promoted to a current-commit report. See [content review](content-review.md), [layout rules](style.md) and the delivered validation record.
| # | Chapter / page | Coverage and implementation | Basis | Recommendation |
| --- | --- | --- | --- | --- |
| 1 | Product Brief / 1 | Covered. Rill Tiny Gen1 identity and source-backed overview; prototype badge and timing qualification kept separate. | [tiny.json](../../../publications/datasheets/tiny/tiny.json), [ihp130-tiny.mk](../../../configs/ci/ihp130-tiny.mk) | Keep the two-column cover, logo reservation and partial Functional mark; do not promote it from host test counts. |
| 2 | Features / 1 | Covered. Rill Tiny Gen1 identity and source-backed overview; prototype badge and timing qualification kept separate. | [tiny.json](../../../publications/datasheets/tiny/tiny.json), [ihp130-tiny.mk](../../../configs/ci/ihp130-tiny.mk) | Keep the two-column cover, logo reservation and partial Functional mark; do not promote it from host test counts. |
| 3 | Configuration and Scope / 2 | Covered. IHP130, 128 KiB, 24 MHz target, four DMA channels; hardware RV32IMC versus default RV32IM firmware. | [ihp130-tiny.mk](../../../configs/ci/ihp130-tiny.mk), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Use the committed profile and identity; keep deferred interfaces and physical claims excluded. |
| 4 | Reading and Evidence Conventions / 2 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. |
| 5 | System Architecture / 17 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. |
| 6 | Processor and Integration / 17 | Covered. One RV32IMC MCU hart, A disabled, direct AHB-Lite/AXI32 adapter and debug reset boundary. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [mgmt_core_wrapper.sv](../../../rtl/ip/core/mgmt_core_wrapper.sv) | Retain actual wrapper parameters; do not infer firmware ISA flags from the hardware C extension. |
| 7 | Clock, Reset and Operating State / 18 | Covered. System and asynchronous JTAG domains; RTC/WDG share the system clock. | [clock_reset_domains.json](../../../rtl/tiny/integration/clock_reset_domains.json), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Obtain CDC/RDC and clock/power evidence separately; do not claim independent sleep timing. |
| 8 | Bus Transactions, Arbitration and Errors / 19 | Covered. One globally active transaction, round-robin acceptance, saved ownership, bounded legal bursts and error/drain behavior. | [tiny_axi4_fabric.sv](../../../rtl/tiny/top/tiny_axi4_fabric.sv), [axi4_error_slave.sv](../../../rtl/ip/interconnect/axi4_error_slave.sv) | Keep local IP capability separate from end-to-end fabric admission; no unlimited liveness guarantee. |
| 9 | Memory Map and Access Conventions / 21 | Covered. Twenty canonical address windows, including alias versus physical-capacity distinctions. | [memory_map.json](../../../rtl/tiny/address_map/memory_map.json) | Reconcile every source-map change; do not use unmapped holes as scratch memory. |
| 10 | Interrupt and DMA Request Routing / 22 | Covered. Fourteen allocated core causes, thirty external positions, enabled DMA request map and channel conventions. | [soc_topology.json](../../../rtl/tiny/integration/soc_topology.json), [dma_math.c](../../../crt/src/hal/dma_math.c) | Keep core input bits, external ordinals, CSR positions and DMA selectors distinct. |
| 11 | Peripherals / 23 | Covered. Fourteen IP chapters and 337 source-extracted register definitions, with instance-specific routing. | [catalog.json](../../../publications/datasheets/tiny/catalog.json), [tiny_reference.py](../../../publications/tiny_reference.py) | Update source inventory, frozen register keys and rendered-coverage checks together after reviewed ABI changes. |
| 12 | XPI Universal Controller / 24-40 | Covered. Four-chip-select SDR serial-memory controller; pin-level boot uses NOR on NSS0. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [xpi.md](../../../docs/ip/xpi.md), [xpi_reg.sv](../../../rtl/ip/storage/xpi_reg.sv) | Inspect engine/FIFO/device status, verify completion and contents as required before making an updated image executable. |
| 13 | On-Chip SRAM / 41-46 | Covered. 128 KiB on-chip SRAM; AXI32 data and one APB discovery/counter window. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [onchip-sram.md](../../../docs/ip/onchip-sram.md), [onchip_ram_reg.sv](../../../rtl/ip/memory/onchip_ram_reg.sv) | Keep Tiny fabric restrictions despite local WRAP/FIXED flags; reset does not initialize SRAM. |
| 14 | General-Purpose Input/Output (GPIO) / 47-62 | Covered. 32 physical GPIOs with management and restricted-user register windows. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [gpio.md](../../../docs/ip/gpio.md), [gpio_reg.sv](../../../rtl/ip/peripheral/gpio_reg.sv) | Use atomic aliases for concurrent updates and apply irreversible locks only after validation. |
| 15 | Direct Memory Access (DMA) / 63-76 | Covered. Four channel contexts share one AXI32 master; memory-mapped endpoints only. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [dma.md](../../../docs/ip/dma.md), [dma_reg.sv](../../../rtl/ip/peripheral/dma_reg.sv) | Retain the explicit zero-readback burst-field limitation and 16-beat implementation limit; a future ABI/RTL fix is separate work. Confirm drain before reusing buffers. |
| 16 | General-Purpose Timers (TIM0, TIM1) / 77-83 | Covered. Two 32-bit timer instances with prescalers and interrupt support. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [timer.md](../../../docs/ip/timer.md), [timer_reg.sv](../../../rtl/ip/peripheral/timer_reg.sv) | Acknowledge observed events, recheck pending state and verify stop on recovery. |
| 17 | Pulse Width Modulation (PWM) / 84-107 | Covered. Two internal timers and four waveform output channels with capture/fault control. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [datasheet.md](../../../rtl/managed/clusterip/pwm/doc/datasheet.md), [pwm_reg.sv](../../../rtl/managed/clusterip/pwm/rtl/pwm_reg.sv) | On fault, preserve status and disable/recover outputs before clearing the cause. |
| 18 | Real-Time Clock (RTC) / 108-120 | Covered. 64-bit seconds, 1/256-second subsecond time, alarms and wake status. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [datasheet.md](../../../rtl/managed/clusterip/rtc/doc/datasheet.md), [rtc_reg.sv](../../../rtl/managed/clusterip/rtc/rtl/rtc_reg.sv) | Read snapshots consistently and capture wake/IRQ causes before acknowledgement. |
| 19 | Watchdog (WDG) / 121-129 | Covered. Window watchdog with early warning and whole-system reset request. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [datasheet.md](../../../rtl/managed/clusterip/wdg/doc/datasheet.md), [wdg_reg.sv](../../../rtl/managed/clusterip/wdg/rtl/wdg_reg.sv) | After reset distinguish watchdog reboot from fresh power-on and retain diagnostic evidence. |
| 20 | Management UART (UART0) / 130-140 | Covered. Primary console UART with 64-byte transmit and receive FIFOs. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [uart.md](../../../docs/ip/uart.md), [uart_reg.sv](../../../rtl/ip/serial/uart_reg.sv) | Wait for the required queue/serializer completion boundary before changing framing or disabling clocks. |
| 21 | Second UART (UART1) / 141-142 | Covered. Second UART instance with dedicated TX/RX and its own interrupt. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [uart.md](../../../docs/ip/uart.md), [uart_reg.sv](../../../rtl/ip/serial/uart_reg.sv) | Confirm serializer idle before changing framing, then clear only captured interrupt causes. |
| 22 | Inter-Integrated Circuit (I2C0, I2C1) / 143-154 | Covered. Two controller-mode I2C instances with command/receive queues. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [i2c.md](../../../docs/ip/i2c.md), [i2c_reg.sv](../../../rtl/ip/serial/i2c_reg.sv) | Recover and release lines before retrying; preserve timeout/error context. |
| 23 | System Controller (SYSCTRL) / 155-162 | Covered. Tiny-owned control and first-fault/acceptance register bank. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [tiny_sysctrl.sv](../../../rtl/tiny/top/tiny_sysctrl.sv), [tiny-soc.md](../../../docs/ip/tiny-soc.md) | For automated acceptance write VALID, PASS and result code once; an earlier failure cannot be replaced by a later pass. |
| 24 | Local Interrupt Controller (CLINT) / 163-166 | Covered. Machine software interrupt and 64-bit timer for hart slot zero. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [clint.md](../../../docs/ip/clint.md), [clint_reg.sv](../../../rtl/ip/peripheral/clint_reg.sv) | Handle and rearm the appropriate local cause; do not subtract two to form an external IRQ ordinal. |
| 25 | Architecture Information (ARCHINFO) / 167-175 | Covered. Read-only Tiny identity and build/configuration discovery. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [datasheet.md](../../../rtl/managed/clusterip/archinfo/doc/datasheet.md), [archinfo_reg.sv](../../../rtl/managed/clusterip/archinfo/rtl/archinfo_reg.sv) | Read device ID only when qualified and enabled; this integration does not supply it. |
| 26 | Pins and Board Connections / 176 | Covered. Fifty-two logical signal pads and all thirty-two GPIO alternate-function rows. | [pin_map.json](../../../rtl/tiny/pin_map/pin_map.json), [soc_topology.json](../../../rtl/tiny/integration/soc_topology.json) | Supply package/board mapping and electrical constraints before treating these as physical pins. |
| 27 | Electrical, Package and Reliability Boundaries / 179 | Covered; physical evidence needed. Explicit missing-evidence table; no fabricated limits or package identifiers. | [tiny-soc-verification.md](../../../docs/ip/tiny-soc-verification.md), [evidence.json](../../../publications/datasheets/tiny/evidence.json) | Attach conditions, units, Min/Typ/Max meaning and matching characterization before publishing numerical ratings. |
| 28 | Software / 180 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. |
| 29 | Reset, Boot and Linker Layout / 180 | Covered. Tiny startup excludes PSRAM setup and relocates initialized code/data to SRAM with BSS/stack initialization. | [startup.S](../../../crt/arch/riscv/startup.S), [ld2_all_sram.lds](../../../crt/linker/ld2_all_sram.lds) | Use actual ELF/MAP and measured/worst-case stack demand; placement is not a memory-use measurement. |
| 30 | SDK, API Completion and Polling / 181 | Covered. Public SDK namespace and Tiny composition, operation-specific completion and bounded polling semantics. | [dma.c](../../../crt/src/hal/dma.c), [sysctrl.c](../../../crt/src/hal/sysctrl.c), [check_tiny_examples.py](../../../publications/check_tiny_examples.py) | Confirm each API completion state and distinguish timeout from idle or rollback. |
| 31 | DMA Descriptors and Buffer Lifetime / 182 | Covered. Seventeen C descriptor members, 64-byte layout/alignment, control positions, bounded HAL chain walking and buffer lifetime. | [dma.h](../../../crt/include/retrosoc/hal/dma.h), [dma_math.c](../../../crt/src/hal/dma_math.c), [dma.c](../../../crt/src/hal/dma.c) | Keep one-descriptor HAL submission/result writeback distinct from an autonomous queue; retain zero-burst default and unsupported-field qualifications. |
| 32 | Acceptance and Diagnostics / 184 | Covered. Source-bound acceptance stages, sticky terminal result and retained watchdog-reset early-pass branch. | [tiny.c](../../../app/apps/ci_smoke/tiny.c), [mcu_acceptance.S](../../../app/apps/ci_smoke/mcu_acceptance.S) | Control initial reset_count and preserve full simulator verdict; a warm reboot alone does not replay every test. |
| 33 | Implementation and Evidence / 185 | Covered. Independent Tiny setup/build/check flow; no Mini/HP/accelerator prerequisite. | [build_tiny_datasheet.py](../../../publications/build_tiny_datasheet.py), [generate_tiny.py](../../../scripts/generate_tiny.py), [tiny-soc-verification.md](../../../docs/ip/tiny-soc-verification.md) | Record actual tool/profile/stage outcomes and preserve missing-input failures. |
| 34 | Reproducible Development Flow / 185 | Covered. Independent Tiny setup/build/check flow; no Mini/HP/accelerator prerequisite. | [build_tiny_datasheet.py](../../../publications/build_tiny_datasheet.py), [generate_tiny.py](../../../scripts/generate_tiny.py), [tiny-soc-verification.md](../../../docs/ip/tiny-soc-verification.md) | Record actual tool/profile/stage outcomes and preserve missing-input failures. |
| 35 | Verification Status and Qualification Gaps / 186 | Covered. Dated repository narratives retained; slow-corner setup WNS -455.18 ns is not 24 MHz closure. | [tiny-soc-verification.md](../../../docs/ip/tiny-soc-verification.md) | Obtain exact-commit raw reports; keep failed, skipped and partial campaigns distinct. |
| 36 | Appendix: Global Register Index / 187 | Covered. Instance-qualified global lookup for every extracted register group and repeated index. | [register-profiles.json](../../../publications/datasheets/tiny/register-profiles.json), [register-contract.json](../../../publications/datasheets/tiny/register-contract.json) | Check unique expanded addresses and canonical register links after any map/group change. |
| 37 | Document Control / 203 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. |
| 38 | Revision History / 203 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. |
| 39 | Sources, License and Contact / 204 | Covered. Complete source identity, dependency/font/package hashes, document ID and author/contact retained. | [tiny.json](../../../publications/datasheets/tiny/tiny.json), [dependencies.lock.json](../../../dependencies/dependencies.lock.json) | Ship the matching manifest and raw validation report; document dates are not measurement dates. |
Every row is source/document review. Matching current physical/board/silicon qualification remains unavailable; host publication tests do not change that state.
@@ -0,0 +1,31 @@
# Tiny Gen1 v0.1 DRAFT content review
The publication describes the independent IHP130 Tiny profile: one RV32IMC
Hazard3 with A disabled, RV32IM default firmware, 128 KiB SRAM, 24 MHz target,
four DMA channels and AXI32/APB4. Rill (清溪) is the selected prototype brand.
The font/palette/grid are inherited as design rules; product facts are not
inherited from Mini's descriptions or qualification records.
| Area | Review and publication decision | Follow-up boundary |
| --- | --- | --- |
| CPU and startup | Publish the actual C/M extensions, disabled A, CSR-enabled firmware and Tiny guard around PSRAM setup. | Keep compiler target separate from implemented ISA; a reset vector is not a completed boot. |
| Fabric | Document one globally active CPU/DMA read or write, saved ownership, naturally aligned bounded bursts and terminal response/drain. | Do not claim arbitrary AXI4 features, independent read/write concurrency or unrestricted liveness. |
| Address/IRQ/pads | Derive all 20 regions, 14 allocated core causes and 52 logical signal pads; retain 32 GPIO alternate entries. | Logical pads are not package pin numbers or a board schematic. |
| IP/register scope | Fourteen chapters, thirteen unique register families; Tiny SYSCTRL is decoded independently and ARCHINFO overrides the SoC identity. | Reject missing/extra decode and defaults belonging to other products. |
| DMA | Four channels, request mask 0x07F9, no stream modes, 64-byte descriptors and source-derived field layout. | **Discovery discrepancy:** the four-bit maximum-burst field reads zero after truncating 16. Publish that value and the actual 16-beat limit separately; propose a future ABI/RTL review, do not change RTL here. |
| SRAM | 32 x 4 KiB banks, four data bytes, no reset erase. | Local FIXED/WRAP capability bits do not override the narrower Tiny fabric subset. |
| UART/I2C/timers | Reused family ABI with actual instances, routing and 24 MHz engine clock. UART1 is PIO/IRQ without a Tiny DMA route. | Generic UART HAL example is UART0; peripheral command acceptance is not necessarily completed I/O. |
| Supervision | RTC and watchdog share the system clock; WDG requests system reset and retains its own reset observation. | No independent sleep clock, battery-backed retention or clock-failure watchdog guarantee. |
| Acceptance | The application source performs CPU, fault, DMA, timer, RTC and I/O checks, then watchdog reboot. | A nonzero pre-existing watchdog reset count takes an early pass branch; controlled initial state is required. |
| Verification | Retain the dated repository narrative, including slow-corner setup WNS -455.18 ns at a 41.666666667 ns constraint. | Raw matching artifacts are not present in this publication workspace; no current-commit qualification is promoted. |
| Electrical/package/power | Preserve explicit missing-evidence tables and required measurement context. | No numerical production limits, die area, thermal rating or silicon claim is invented. |
The existing Tiny verification document reports an originally uncommitted run
and a later path-migration follow-up. Neither automatically qualifies the source
SHA selected for this document. The final publication validation report records
only checks executed for this build; hardware campaign repair is separate work.
The [chapter review](chapter-review.md) covers all frozen entries and gives
source/evidence/maintenance advice. Full registers and software guidance are
retained even when a board or physical result is unavailable. No Mini document,
version, review or PDF is updated by this addition.
+358
View File
@@ -0,0 +1,358 @@
#import "style.typ": *
#import "diagrams.typ": architecture, flow, tcd-diagram
#import "waveforms.typ": timing
#import "ip-reference.typ": reference
#show: template
#cover(evaluation:((name:"Available",fill:"full"),(name:"Functional",fill:"left-half"),(name:"Reproduced",fill:"empty"),(name:"Tapedout",fill:"empty")))[
#text(9pt,weight:"semibold",fill:gold)[PRODUCT DATASHEET / #doc.status]
#v(rhythm.cover-gap)
#text(30pt,weight:"semibold")[retroSoC Tiny]
#linebreak()
#text(23pt,weight:"semibold",fill:gold)[Gen1]
#v(rhythm.cover-gap)
#text(13pt)[An Open-Source, Single-Core RISC-V MCU]
#v(rhythm.cover-meta-gap)
#grid(columns:(1fr,1fr),
[#text(font:("Inter","Noto Sans CJK SC"),weight:"bold",top-edge:0.7275390625em)[#doc.brand_name(#doc.brand_name_zh)] · v#doc.version \ #doc.date],
align(right)[#doc.author#link("mailto:"+doc.author_email)[(#doc.author_email)] \ #doc.maintainer])
#v(rhythm.cover-rule-gap)
#line(length:100%,stroke:1.1pt+gold)
= Product Brief
== Features
#columns(2,gutter:rhythm.cover-gutter)[
#set par(leading:rhythm.cover-leading,spacing:rhythm.cover-spacing)
#set list(spacing:rhythm.cover-list-spacing)
*Compute and debug*
- One Hazard3 RISC-V hart: *RV32IMC*, A disabled.
- Default RV32IM firmware; IRQ/CSR support enabled.
- JTAG debug with coordinated hart reset.
*Memory and interconnect*
- *128 KiB SRAM*, 32 banks of 4 KiB.
- Native AXI32 memory paths and one APB4 island.
- CPU and DMA share one active transaction globally.
- XPI NOR boot and SRAM-resident application layout.
*DMA and control*
- *Four DMA channels*, 32-bit transfers, up to 16 beats.
- Direct and 64-byte linked-descriptor operation.
- Sticky first-fault records and CPU/DMA wait snapshots.
- Sticky automated firmware acceptance result.
#colbreak()
*Wired connectivity*
- 32 GPIOs with atomic output aliases and interrupts.
- Two FIFO UARTs; dedicated TX/RX pads.
- Two controller-mode I2C instances with 7/10-bit addressing.
- Four-chip-select XPI with single/dual/quad SDR phases.
*Timing and supervision*
- Configured *24 MHz* system clock; no PLL.
- Two 32-bit general timers; four PWM outputs.
- 1 MHz CLINT timebase; local software/timer interrupts.
- RTC alarms/wake and window watchdog on the system clock.
*Development boundary*
- Freestanding SDK and Tiny acceptance application.
- IHP130 macro/behavioral integration and simulation flows.
- No external RAM, wireless, HP core or multimedia accelerator.
]
#v(1fr)
#note(below:0pt)[This DRAFT describes an implemented *prototype*. The 24 MHz configuration is a functional target,
*not a timing-qualified operating point*. Electrical, package, thermal, power and silicon qualification remain unprovided.
The partial Functional mark does not establish complete protocol or physical qualification.]
]
#pagebreak()
== Configuration and Scope <tiny-configuration>
The main reference is #code(doc.profile). *Tiny is an independent wired MCU integration*; it is not a selectable Mini mode.
The reviewed implementation is identified by #code(doc.source_revision). An interface name, shared SDK declaration or mapped
offset does not establish an optional feature. Identify the product before using an inherited register or API name.
#ds-table("configuration",[Tiny reference configuration],([Item],[Selected value],[Boundary]),(
([Product],[Tiny Gen1 / IHP130],[Prototype; one management MCU hart.]),
([Hardware / firmware],[RV32IMC, A disabled / RV32IM],[Compressed execution is implemented; the default compiler target omits C.]),
([Clock],[24 MHz; CLINT 1 MHz],[Functional targets; no PLL or qualified maximum-frequency rating.]),
([Memory],[128 KiB SRAM],[External NOR for boot; no external RAM initialization dependency.]),
([Bus / DMA],[AXI32/APB4; four channels],[One active read or write globally; memory-mapped endpoints only.]),
([Firmware],code("APP=bringup; HAVE_CSR=YES; LINK_TYPE=ld2_all_sram"),[Tiny-specific acceptance entry point.])),widths:(0.8fr,1.55fr,1.75fr))
=== Implemented scope and deferred capabilities
The 14 IP chapters cover every decoded region and all allocated interrupt causes. UART and I2C instances retain distinct
addresses and wiring. The GPIO user/administration windows are views of one pad bank. A restricted register window is not
a claim of a separate security domain.
Atomics, wireless, external RAM, USB, SDIO, standalone general SPI, I2S, CAN, ADC, accelerators, authenticated boot,
RTOS ports and independent deep-sleep timing are outside this release. Additional XPI chip selects remain available at
the interface, but the initial pin-level NOR acceptance uses NSS0; other populated-device configurations require evidence.
#source-note("docs/ip/tiny-soc.md",title:"Frozen Tiny requirements and non-goals")
== Reading and Evidence Conventions
Addresses are byte addresses; register offsets are relative to their named instance/group. *Source reviewed*, *test available*,
*executed result* and *physical qualification* are separate states. Source links target the reviewed commit or locked managed
input. Diagrams describe digital structure and ordering; they do not specify measured latency or board timing.
Use @tiny-addresses for memory windows, @tiny-irq for interrupt numbering, @tiny-register-index for address-first register
lookup, @tiny-boot for startup, @tiny-diagnostics for terminal results and @tiny-evidence for verification boundaries.
The document uses the approved Mini visual language; all product facts and source inventories here belong to Tiny.
#pagebreak()
#contents(depth:3)
#pagebreak()
#figure-directory(tables:true)
#pagebreak()
#figure-directory()
#pagebreak()
= System Architecture
== Processor and Integration <tiny-core>
Hazard3 is hart zero. The shared wrapper selects the C and M extensions and disables A for Tiny. Its AHB-Lite bus terminates
at a direct AXI32 adapter. JTAG supports the implemented halt/resume, register and system-bus debug path; debug hart reset
waits for the adapter to become idle instead of abandoning an accepted transaction.
The system has no application hart, MMU/Linux platform, hardware cache-coherent multicore organization or product extension
slots. CPU-visible SRAM, XPI and APB routes are the implemented interconnect. The single-hart architecture does not remove
the need for software/DMA buffer ownership and fences.
#architecture()
#source-note("rtl/tiny/top/retrosoc_tiny.sv",title:"Tiny product integration and parameters")
#source-note("rtl/ip/core/mgmt_core_wrapper.sv",title:"CPU ISA, AHB adapter and debug wrapper")
== Clock, Reset and Operating State <tiny-clock>
CPU, AXI, APB, RTC and watchdog share the configured 24 MHz system clock. CLINT derives a 1 MHz tick. JTAG has its own
external clock and managed asynchronous debug crossing. Other asynchronous external inputs use their specified synchronizers;
the absence of a system-bus clock crossing is not complete CDC/RDC signoff.
#ds-table("clocks",[Declared Tiny clock/reset domains],([Domain],[Clock],[Reset],[Reset primitive]),
data.clocks.map(c=>(c.name,code(c.clock),code(c.reset),code(c.reset_primitive))),widths:(0.65fr,1.1fr,1.5fr,0.9fr))
External reset is asynchronously asserted and synchronously released. The watchdog requests a whole-system restart while
retaining its own reset-cause information. Debug hart reset has a narrower scope. SRAM contents are *not initialized by reset*;
the runtime explicitly establishes initialized data and BSS.
There is no dynamic frequency switching, independent sleep clock or guaranteed retention mode in this release. A watchdog
sharing the failed system clock cannot promise recovery from that clock failure. The existing RTL timing target must not be
advertised as a qualified voltage/frequency operating point.
#source-note("rtl/tiny/integration/clock_reset_domains.json",title:"Clock/reset inventory and debug crossings")
== Bus Transactions, Arbitration and Errors <tiny-bus>
The two initiators are CPU and DMA. They share *one active read or write transaction globally*. The fabric saves source,
direction, address and attributes, forwards the request, and retains ownership until the terminal B or RLAST handshake.
Read/write request arbitration is round-robin; it is not a throughput guarantee or an unrestricted liveness guarantee.
Memory transfers support naturally aligned 1/2/4-byte beats and up to 16 INCR beats. Single-beat FIXED is also admitted.
The complete address span must remain within one decoded target and one 4 KiB page without overflow. MMIO accepts only
one beat. The addressed register can impose stricter width/strobe rules; Tiny SYSCTRL requires aligned full-word writes.
#ds-table("bus-rules",[Transfer outcome and software consequence],([Condition],[Result],[Required handling]),(
([Legal SRAM access],[Target response with saved ID/owner],[Do not reuse DMA buffers before completion.]),
([XPI memory aperture],[Mapped reads; target controls memory-write rejection],[Programming uses the indirect command interface.]),
([Illegal size/burst/lock/alignment/span],[SLVERR responder],[Correct the request; preserve diagnostic context.]),
([Unmapped target],[DECERR responder],[Treat the address as unavailable, not scratch memory.]),
([Unsupported APB offset/write],[PSLVERR, propagated as a bus error],[Do not retry an unsupported control indefinitely.])),widths:(1.1fr,1.4fr,1.65fr))
AW and W are independent. W is backpressured until the saved write address reaches its target; another requester cannot
take over accepted write data. Rejected reads return the advertised response beat count, and rejected writes drain their
accepted data before B. Backpressure requires valid payloads to remain stable.
#timing("axi4",[Representative AXI read response and backpressure; Tiny uses 32-bit data and one-bit ID.])
#timing("apb4",[APB4 setup/access sequence with a wait state; address and write data remain stable.])
#source-note("rtl/tiny/top/tiny_axi4_fabric.sv",title:"Saved ownership, admission and terminal handshake")
#source-note("rtl/ip/interconnect/axi4_error_slave.sv",title:"Error response and write-drain implementation")
== Memory Map and Access Conventions <tiny-addresses>
#ds-table("memory-map",[Canonical Tiny address windows],([Region],[Base],[Last address],[Size]),
data.regions.map(r=>(code(r.symbol),code(r.base_hex),code(r.end_hex),r.size_label)),widths:(1.2fr,1fr,1fr,0.65fr))
The Flash alias and XPI aperture identify memory routing, not fitted NOR capacity. The selected acceptance device and slot
configuration remain separate. Only SRAM is the application RAM in this profile. Unsupported holes and omitted peripheral
addresses must not be probed speculatively or reused as memory.
Register tables specify local offsets, access, reset and side effects. *W1C acknowledgement uses an explicit mask*;
read-modify-write can clear unrelated pending events. A live value or producer-dependent field need not equal its reset
assignment when software later reads it. Use coherent snapshots for multiword counters.
== Interrupt and DMA Request Routing <tiny-irq>
#ds-table("irq-map",[Tiny interrupt wiring],([Source],[Core bit],[External ordinal],[Producer]),
data.interrupts.map(r=>(r.name,str(r.core_bit),if r.core_bit < 2 {[Local]} else {str(r.core_bit - 2)},code(r.signal))),widths:(1.05fr,0.5fr,0.7fr,1.95fr))
Core bits 0/1 are CLINT software/timer causes. The other 30 positions feed Hazard3 external interrupt inputs;
*external ordinal equals core bit minus two*. All omitted producers are tied zero. A register index, DMA selector and
interrupt number are different namespaces. Core bits here number the wrapper input vector, not architectural CSR bit positions.
#ds-table("dma-routes",[Enabled DMA requests and software channel conventions],([Selector],[Endpoint],[Channel convention]),(
([0],[Software-paced memory transfer],[3 bulk, or explicitly reserved free channel]),
([3 / 4],[XPI transmit / receive],[3]),([5 / 6],[UART0 transmit / receive],[0]),
([7 / 8],[I2C0 transmit / receive],[1]),([9 / 10],[I2C1 transmit / receive],[2])),widths:(0.7fr,2fr,1.4fr))
UART1 uses PIO/IRQ. The shared SDK names other requests and channels, but Tiny must reject unavailable selections.
The current DMA transfer validator accepts 32-bit MM-to-MM work; stream modes are disabled. Serialize shared channel
conventions rather than assuming that each API owns a permanently independent engine.
#source-note("rtl/tiny/integration/soc_topology.json",title:"Interrupt and GPIO bindings")
#source-note("crt/src/hal/dma_math.c",title:"Tiny DMA kind/request validation")
== Peripherals
Each IP starts on a new page. Repeated instances retain separate address/IRQ entries; their common register layout is
documented once. The fields below are extracted from the active common implementation with Tiny parameter overrides;
SYSCTRL and the ARCHINFO identity override are bound to Tiny-owned RTL.
#for entry in data.catalog {reference(entry)}
== Pins and Board Connections <tiny-pins>
The following is a *logical signal-pad inventory*, not a package pin numbering or an approved PCB schematic. Pad cells,
power/ground pins, voltage domains and package bonding require the selected physical implementation.
#ds-table("pads",[Tiny logical signal pads],([Signal],[RTL direction],[Pad kind]),
data.pads.map(p=>(code(p.name),p.direction,p.kind.replace("_"," "))),widths:(2fr,0.8fr,1.2fr))
#let gpio-mode(mode) = {
let terms=mode.inputs + if mode.do.contains("'") {()} else {(mode.do,)}
if terms.len()==0 {[Not routed]} else {code(terms.join(" / "))}
}
#ds-table("gpio-alternates",[Tiny GPIO alternate-function routes],([GPIO],[ALT0],[ALT1]),
data.gpio.map(g=>(str(g.pin),gpio-mode(g.alt0),gpio-mode(g.alt1))),widths:(0.4fr,1.85fr,1.85fr))
Disable producers and release output enables before changing a live pad route. I2C and open-drain signals require external
pull-ups and compatible levels. Dedicated UART and XPI pins are not reassigned through the GPIO mux. Verify complete
pin groups, reset drive state and external clock availability before enabling a device.
== Electrical, Package and Reliability Boundaries
This source-level publication supplies *no numerical production electrical ratings*. TBD means unavailable evidence,
not zero, unlimited range or permission to select a convenient value. Functional clock names are not independent power rails.
#ds-table("physical-gaps",[Evidence required before physical product claims],([Area],[Required evidence],[Current state]),(
([Supply / absolute maximum],[Pad/core rails, sequencing, injection/ESD limits, approved conditions],[TBD]),
([AC/DC timing],[PVT, loading, clock reference edge, setup/hold and input/output limits],[TBD]),
([Package / pinout],[Die pad and package pin mapping, dimensions and bonding],[TBD]),
([Thermal / power],[Package/board/test conditions, workloads, voltage and measured results],[TBD]),
([Reliability / ordering],[Qualification records, traceable device revisions and order codes],[TBD])),widths:(1fr,2.5fr,0.5fr))
No package photograph, fabricated part number, regulatory mark or silicon speed grade is inferred from the digital profile.
The documented STA deficit remains visible in @tiny-evidence; it is not an operating specification.
= Software
== Reset, Boot and Linker Layout <tiny-boot>
Reset enters the Flash alias at #code(data.reset_address). The Tiny runtime excludes the generic PSRAM delay/setup branches.
With CSR support selected it masks interrupts and installs the direct trap entry, initializes registers/stack, copies code
and data when load/run addresses differ, clears BSS, performs the configured premain work, and enters main.
#flow("boot",(
[Reset / JTAG state established · Fetch from NOR alias],
[Mask interrupts; establish trap entry and register/stack state],
[Tiny skips PSRAM setup · Copy initialized code/data into SRAM],
[Clear SRAM BSS · Perform configured premain initialization],
[Enter Tiny application · Handle terminal result or watchdog restart]
),[Tiny ordinary startup: source order, not a guaranteed boot-time measurement.])
The `ld2_all_sram` layout places runtime code, data, BSS and stack in SRAM with initialized material loaded from Flash.
Memory-map capacity, load-image bytes, runtime static occupation and stack high-water demand are different quantities.
Inspect the actual ELF/MAP and reserve buffers plus stack/scratch margin before deployment. No matching size/high-water
measurement is supplied by this datasheet build.
#source-note("crt/arch/riscv/startup.S",title:"Tiny conditional startup and relocation")
#source-note("crt/linker/ld2_all_sram.lds",title:"Flash load / SRAM runtime linker layout")
== SDK, API Completion and Polling
Consume public headers under #code("<retrosoc/...>") and the #code("rs_") namespace. The Tiny build selects product-specific
application composition and generated address/IRQ/capability metadata. This does not generate replacement IP register ABIs.
Unsupported PLL, application-hart or selectable-core APIs return an error before MMIO.
*A successful API return describes that API's completion boundary*: a start command can be accepted before its operation
finishes. FIFO acceptance is not completed serial transmission. A timeout can leave work active and buffers owned by a
device. Capture state and complete abort/drain before reuse.
Software timeout arguments are polling budgets, not milliseconds. CPU/compiler work and bus stalls affect elapsed time;
a budget cannot bound a register read that never returns. Hardware counters and device-specific timeouts use their own
documented clock units. Use CLINT or another appropriate timebase when an elapsed-time deadline is required.
== DMA Descriptors and Buffer Lifetime <tiny-dma-format>
#tcd-diagram()
#ds-table("tcd-fields",[Source-derived DMA C descriptor layout],([Byte offset],[Field],[Bytes],[Role]),
data.tcd.map(f=>(code("0x"+upper(str(f.offset,base:16))),code(f.name),str(f.bytes),f.role)),widths:(0.75fr,1.7fr,0.45fr,1.1fr))
The descriptor is 64 bytes and each descriptor address is 64-byte aligned. Tiny permits only the implemented transfer kind,
request, increment and width combinations. Stride fields remain zero and row count is at most one. A field in the ABI is
not evidence of cyclic, 2D or stream support. HAL result fields do not imply that hardware directly writes every result word.
#ds-table("tcd-control",[Descriptor control positions and portable Tiny use],([Bits],[Field],[Meaning / restriction]),(
([0],[VALID],[Must be set for a submitted descriptor.]),
([1 / 2],[SRC_INC / DST_INC],[Increment the corresponding memory address; use the endpoint's required mode.]),
([3 / 4],[CRC_ENABLE / CRC_FINAL],[Select the implemented CRC processing/final comparison.]),
([5 / 6],[INT_DONE / INT_ERROR],[Declared SDK flags; use the channel event-enable/IRQ registers rather than assuming per-descriptor IRQ gating.]),
([10:8],[KIND],[Tiny admits MM-to-MM value zero only.]),
([15:12],[REQUEST],[Zero or 3 through 10 in the current Tiny request map.]),
([17:16],[PRIORITY],[Two-bit software priority value.]),
([24:20],[BURST],[0 selects the 16-beat default in descriptor submission; 1 through 16 select a bound. Direct configuration requires 1 through 16.]),
([Other bits],[Reserved / unused],[Use zero for compatibility; a field declaration does not establish an extra operation.])),widths:(0.6fr,1.05fr,2.5fr))
The current HAL chain helper submits one descriptor at a time, waits, copies result registers into the descriptor and then
follows #code("next_ptr"), bounded by #code("max_tcds"). It is not an autonomous circular-queue API. On a timeout or error,
the helper records observed progress but the caller must still confirm abort/drain before releasing memory.
Reserve channel, descriptor and payload storage through completion or confirmed recovery. Publish CPU-produced input with
the required fence before starting the non-CPU master. Tiny has no application-hart cache handoff protocol; importing the
Mini HP cache-clean service would describe nonexistent hardware.
#flow("dma-recovery",([Validate channel / request / bounds and reserve storage],
[Publish descriptor/input and configure while idle], [Start and wait for the required completion boundary],
[On failure: capture fault/progress, abort, confirm drain], [Release or reuse storage only after ownership is safe]),
[DMA lifetime and recovery ordering. A polling timeout is not an idle acknowledgement.])
#source-note("crt/include/retrosoc/hal/dma.h",title:"Handwritten descriptor and public HAL ABI")
#source-note("crt/src/hal/dma_math.c",title:"Descriptor validation and unsupported combinations")
== Acceptance and Diagnostics <tiny-diagnostics>
Both #code("bringup") and #code("ci_smoke") select the Tiny acceptance entry point. A controlled first boot with a clear
watchdog reset count performs the tests below, then deliberately requests watchdog reboot. A subsequent boot with a nonzero
retained reset count takes the early successful terminal path. *A pre-existing watchdog reset count is not proof that this
boot executed every test.* Record initial state, image, profile and the final simulator result.
#ds-table("acceptance-codes",[Tiny application failure codes from the reviewed main sequence],([Code],[Stage]),(
([1],[Primary UART initialization]),([2],[Watchdog status read]),([3],[Tiny build/topology and SRAM discovery]),
([4],[Compressed execution, A-disabled ISA and exception handler setup]),([5],[Atomic/unmapped probes and first fault]),
([6],[Memory/UART DMA]),([7],[CLINT progression and timer interrupt]),([8],[RTC, PWM discovery and unsupported-control rejection]),
([9],[Watchdog configuration/start]),([10],[GPIO, UART1 loopback and I2C NACK checks])),widths:(0.4fr,3.8fr))
The first valid full-word SYSCTRL TEST_STATUS write is sticky. Bit 31 is valid, bit 0 pass and bits 15:8 the result code.
The testbench must emit #code("SIM_TEST_PASS") and the command must succeed without failure/timeout markers. UART startup
text is diagnostic only. The compact assembly netlist boot test has a narrower scope than the full SDK image.
#source-note("app/apps/ci_smoke/tiny.c",title:"Acceptance order, reset-count branch and terminal codes")
#source-note("app/asm/tiny_boot.S",title:"Separate compact netlist boot test")
= Implementation and Evidence
== Reproducible Development Flow
Start from the committed IHP130 Tiny profile. Use the existing locked dependency setup and doctor flow, then build the
Tiny firmware and selected simulator. The publication pipeline itself does not perform an EDA campaign.
#code-block(raw("make CONFIG=configs/ci/ihp130-tiny.mk setup\nmake CONFIG=configs/ci/ihp130-tiny.mk doctor\nmake CONFIG=configs/ci/ihp130-tiny.mk firmware sim\nmake CONFIG=configs/ci/ihp130-tiny.mk SIMU=IVERILOG firmware sim\nmake CONFIG=configs/ci/ihp130-tiny.mk regress-pr",block:true))
Tiny source lists exclude Mini product RTL, RIB/RIBP, application-CPU generation and accelerator inputs. Common RTL and
Hazard3 remain revision-locked. Keep generated files in the selected build variant; missing unrelated accelerator corpora
must not become prerequisites for the Tiny publication.
== Verification Status and Qualification Gaps <tiny-evidence>
Readiness is *prototype*. #data.evidence.boundary
#ds-table("evidence",[Dated repository verification record and publication boundary],([Area],[What the record reports],[Interpretation]),
data.evidence.records.map(r=>(r.at(0),r.at(1),r.at(2))),widths:(0.8fr,1.7fr,1.65fr))
No matching current-commit raw qualification report is attached. The documented setup WNS of *−455.18 ns* and TNS of
*−13,049,600 ns* describe the dated IHP130 slow-library analysis at a 41.666666667 ns clock constraint, with a reset-distribution deficit. They must not become a positive
24 MHz timing claim. Subsequent source-path migration has its own bounded follow-up record; it does not rerun all earlier stages.
Publication tests establish document/source consistency and layout, not electrical compliance, full protocol coverage or
silicon operation. Future pass records must identify exact source/profile, tool/model versions, executed stage, raw report
and result. Keep skipped, failed and unfinished work visible.
#source-note("docs/ip/tiny-soc-verification.md",title:"Historical evidence, timing deficit and remaining work")
= Appendix: Global Register Index <tiny-register-index>
Use the instance-qualified address below, then follow the canonical register reference. GPIO common discovery offsets
exist in both views. Repeated groups retain explicit indices; gaps are not additional registers. Addressability does not
override the register's access, alignment, strobe or side-effect rules.
#for entry in data.catalog {
if entry.id=="uart1" {minor-title("UART1 - shared UART register ABI")} else {minor-title(entry.title)}
let ref=data.registers.at(entry.family)
for symbol in entry.regions.filter(s=>s.starts-with("APB4_")) {
let region=data.regions.find(r=>r.symbol==symbol)
let records=()
for group in ref.groups {
let applies=entry.family!="gpio" or group.id=="common" or (group.id=="user" and symbol=="APB4_GPIO") or (group.id=="admin" and symbol=="APB4_GPIO_ADMIN")
if applies {
for instance in range(group.count) {
for reg in ref.registers.filter(r=>r.group==group.id) {
let address=region.base+group.base+instance*group.stride+reg.offset
records.push((code("0x"+upper(str(address,base:16))),link(label("reg-"+entry.family+"-"+reg.key),code(reg.name)),
if group.count>1 {str(instance)} else {[—]},reg.access))
}
}
}
}
ds-table("index-"+symbol,[#symbol instance addresses],([Absolute address],[Register],[Index],[Access]),records,widths:(1fr,1.9fr,0.4fr,0.85fr))
}
}
= Document Control
== Revision History
#ds-table("revision",[Document revision history],([Date],[Version],[Change]),
((doc.date,[v#doc.version #doc.status],[Initial Tiny Gen1 complete reference; independent source extraction and Tiny-qualified integration.]),),widths:(0.8fr,0.85fr,2.5fr))
== Sources, License and Contact <publication-provenance>
Hardware/software snapshot: #code(doc.source_revision). This document's source manifest records the exact technical files,
publication files, managed revisions and fonts/packages used for the build. The PDF digest, chapter review and actual
validation record accompany the output; the document date is not a hardware measurement date.
The publication uses locked Typst 0.15.1, existing Inter/Fira Code/Noto fonts and the project's managed vector/waveform packages.
The prototype brand is Rill; this identity does not establish an orderable or qualified silicon product.
The project retains its Mulan PSL v2 license and third-party component/font licenses. Obtain the current source and license
inventory from #link("https://github.com/retroSoC/retroSoC")[the project repository].
Contact: #link("mailto:"+doc.author_email)[#doc.author (#doc.author_email)]. Maintainer: #doc.maintainer.
+84
View File
@@ -0,0 +1,84 @@
#import "@preview/cetz:0.5.2" as cetz
#import "@preview/bytefield:0.0.8" as bf
#import "style.typ": *
#let diagram(id, body, caption) = block(width:100%,breakable:false)[
#context metadata((kind:"tiny-diagram",id:id,page:here().page()))
#figure(body,caption:caption)
]
#let schematic(nodes, routes, height:90) = {
set text(font:"Inter",size:9pt,weight:"regular",fill:ink)
cetz.canvas(length:1mm,{
import cetz.draw: rect, line, content
rect((0,0),(172,-height),stroke:none)
for path in routes {
line(..path.map(p=>(p.at(0),-p.at(1))),stroke:0.65pt+ink,mark:(end:"stealth"))
}
for node in nodes {
let (x,y,w,h,label)=node
rect((x,-y),(x+w,-y - h),fill:gray,stroke:0.6pt+ink)
content((x+w/2,-y - h/2),block(width:(w - 4)*1mm)[
#set text(font:"Inter",size:9pt,weight:"regular")
#set par(leading:2pt,spacing:0pt)
#align(center,label)
])
}
})
}
#let architecture() = diagram("tiny-architecture",schematic((
(5,4,65,19,[Hazard3 · hart 0 \ RV32IMC · A disabled]),
(104,4,63,19,[JTAG / debug transport \ External TCK domain]),
(5,37,65,15,[AHB-Lite → AXI32 adapter]),
(104,37,63,15,[Four-channel DMA \ AXI32 master]),
(30,69,112,19,[Tiny AXI32 fabric \ One active transaction globally]),
(0,110,40,22,[128 KiB SRAM \ 32 × 4 KiB]),
(44,110,40,22,[XPI read path \ Flash alias / NOR]),
(88,110,40,22,[APB4 bridge \ 16 targets]),
(132,110,40,22,[Error responders \ DECERR / SLVERR]),
),(
((104,13),(70,13)),((37,23),(37,37)),
((37,52),(37,61),(60,61),(60,69)),((135,52),(135,61),(112,61),(112,69)),
((45,88),(45,99),(20,99),(20,110)),((70,88),(70,103),(64,103),(64,110)),
((103,88),(103,103),(108,103),(108,110)),((127,88),(127,99),(152,99),(152,110)),
),height:134),[Tiny Gen1 integration. System logic runs from the configured 24 MHz clock; JTAG is asynchronous.])
#let ip-diagram(entry) = {
let children=entry.nodes.slice(1)
let positions=if children.len()<=3 {((1,40,54,20),(59,40,54,20),(117,40,54,20))}
else {((5,39,68,20),(99,39,68,20),(5,76,68,20),(99,76,68,20))}
let nodes=((50,0,72,20,entry.nodes.first()),)
let routes=()
for (i,name) in children.enumerate() {
let (x,y,w,h)=positions.at(i)
nodes.push((x,y,w,h,name))
// Control reaches each functional group independently; no invented FIFO-to-FIFO chain.
let branch=if children.len()<=3 or i < 2 {29} else {68}
routes.push(((86,20),(86,branch),(x+w/2,branch),(x+w/2,y)))
}
diagram("ip-"+entry.id,schematic(nodes,routes,height:if children.len()>3 {98} else {62}),
[#entry.title functional groups. Arrows show control reach, not every internal data wire or a cycle schedule.])
}
#let flow(id, items, caption) = {
let nodes=items.enumerate().map(((i,item))=>(16,i*23,140,16,item))
let paths=range(items.len()-1).map(i=>((86,i*23+16),(86,(i+1)*23)))
diagram(id,schematic(nodes,paths,height:items.len()*23-5),caption)
}
#let tcd-diagram() = {
let names=("next_ptr":"Next","source":"Source","destination":"Destination","byte_count":"Byte count",
"source_stride":"Src stride","destination_stride":"Dst stride","y_count":"Rows","reserved":"Rsvd",
"control":"Control","crc_expected":"CRC expect","crc_seed":"CRC seed","crc_result":"CRC result",
"status":"Status","bytes_done":"Bytes done","error_status":"Error","reserved_tail":"Reserved","reserved_tail2":"Reserved")
let fields=()
for field in data.tcd {
if calc.rem(field.offset,16)==0 {fields.push(bf.note(left)[#("0x"+upper(str(field.offset,base:16)))])}
fields.push(bf.bytes(field.bytes,fill:if field.role=="reserved" {gray} else if field.role=="HAL result" {rgb("#E7EFF6")} else {pale-gold})[#{names.at(field.name)}])
}
diagram("dma-tcd",{
set text(font:"Inter",size:9pt)
show: bf.bf-config.with(row-height:27pt,field-font-size:9pt,note-font-size:9pt,header-font-size:9pt,stroke:0.6pt+ink)
bf.bytefield(bpr:128,msb:right,pre:(36pt,),post:(),
bf.bitheader(0,[0],32,[4],64,[8],96,[12],numbers:none,angle:0deg,text-size:9pt),..fields)
},[DMA TCD: four 16-byte rows. Gold: configuration; blue: HAL result fields; gray: reserved.])
}
@@ -0,0 +1,29 @@
{
"status": "prototype",
"current_commit_reports": [],
"historical_source": "docs/ip/tiny-soc-verification.md",
"historical_date": "2026-09-25",
"boundary": "Repository-reported local results from an originally uncommitted implementation; raw matching artifacts are not supplied in this publication workspace. Not rerun or promoted to current-commit qualification.",
"records": [
[
"RTL and software",
"Repository records directed fabric/SYSCTRL tests and pin-level NOR firmware acceptance in Verilator/Icarus.",
"Reported historical functional evidence, not a new publication-run result."
],
[
"Netlist",
"Compact assembly SRAM/pad boot test is recorded; the full C acceptance netlist campaign remains incomplete.",
"Do not substitute the compact netlist test for complete SDK acceptance."
],
[
"IHP130 STA",
"Recorded setup WNS -455.18 ns and TNS -13,049,600 ns; dominant reset-distribution path.",
"24 MHz is a functional target, not timing-qualified operation."
],
[
"Physical and silicon",
"No matching place/route, PVT/MMMC, CDC/RDC signoff, board characterization or silicon/power report.",
"No numerical electrical, package, thermal or safety qualification claimed."
]
]
}
@@ -0,0 +1,156 @@
#import "style.typ": *
#import "diagrams.typ": ip-diagram
#import "waveforms.typ": timing
#let inline(value) = {
// A deliberately limited inline grammar: explicit strong spans and raw code.
// Code is opaque, including any literal asterisks it contains.
let bold = false
for (i,part) in value.split("`").enumerate() {
if calc.rem(i,2)==1 { code(part) } else {
for (j,piece) in part.split("**").enumerate() {
if j>0 { bold = not bold }
if bold { strong(piece) } else { piece }
}
}
}
}
#let subhead(ip, name, depth) = [
#{
show heading: it => heading-layout(it,before:rhythm.minor-before,after:rhythm.minor-after)
heading(level:depth+1,numbering:none,outlined:false,bookmarked:true,name)
}#label("detail-"+ip+"-"+lower(name).replace(" ","-"))
]
#let prose-blocks(ip, section-title, blocks) = {
for (n,item) in blocks.enumerate() {
if item.kind=="heading" {
minor-title(inline(item.text))
} else if item.kind=="code" {
if item.language not in ("mermaid","dot") {
code-block(raw(item.text,block:true),breakable:true)
}
} else if item.kind=="table" {
ds-table(ip+"-"+str(n),section-title,
item.headers.map(inline),item.rows.map(r=>r.map(inline)))
} else if item.kind=="list" {
let bodies=item.items.enumerate().map(((index,entry))=>{
let content=prose-blocks(ip+"-"+str(n)+"-"+str(index),section-title,entry.blocks)
if item.ordered {enum.item(entry.number,content)} else {list.item(content)}
})
if item.ordered {enum(..bodies)} else {list(..bodies)}
} else { par(inline(item.text)) }
}
}
#let prose-sections(ip, sections, depth) = {
for (index,section) in sections.enumerate() {
if sections.len()>1 { minor-title(section.title) }
prose-blocks(ip+"-"+str(index),section.title,section.blocks)
}
}
#let bit-layout(register) = {
let fields=register.fields.sorted(key:f=>-f.msb)
set text(size:9pt)
set par(leading:rhythm.small-leading,spacing:0pt)
let cells = ()
for high in (31,15) {
cells += range(high,high - 16,step:-1).map(b=>align(center,str(b)))
cells += fields.enumerate().filter(((n,f))=>f.lsb<=high and f.msb>=high - 15).map(((n,f))=>{
let span=calc.min(f.msb,high)-calc.max(f.lsb,high - 15)+1
let text=if f.name.len()<=span*4 {f.name} else {"F"+str(n+1)}
table.cell(colspan:span,fill:if f.name=="Reserved" {gray} else {pale-gold},align(center,text))
})
}
table(columns:range(16).map(n=>1fr),
stroke:table-stroke,inset:rhythm.bits-inset,..cells)
}
#let register-section(family, depth) = {
let reference=data.registers.at(family)
subhead(family,"Register Summary",depth)
[Offsets are byte offsets within the named register group. Repeated groups use the base,
stride and instance range below. Access descriptions include register-specific restrictions.
A numeric reset identifies stored or fixed state. Dynamic marks live producer/integration
state; apply the domain, validity and snapshot rules before consuming it. Not retained marks
a command or data port, rather than a persistent configuration word.]
for group in reference.groups {
let registers=reference.registers.filter(r=>r.group==group.id)
if registers.len()==0 { continue }
minor-title(group.title)
if group.count>1 {
par([Instance offset: #code("0x"+str(group.base,base:16)) + n × #code("0x"+str(group.stride,base:16)),
n = 0…#(group.count - 1). Local offsets below are added to this instance offset.])
}
{
// Register-name links keep their blue color and destination, without decoration.
show underline: omit-link-underline
ds-table(family+"-summary-"+group.id,group.title,
([Offset],[Register],[Access],[Reset],[Description]),
registers.map(r=>(code("0x"+str(r.offset,base:16)),link(label("reg-"+family+"-"+r.key),code(r.name)),r.access,inline(r.reset),inline(r.description))),
widths:(0.55fr,1.6fr,0.6fr,1.1fr,2.6fr))
}
}
subhead(family,"Register Description",depth)
[Bit-layout cells refer to the numbered field rows below each diagram. Reserved bits must
be handled as specified by the register; register access checks still apply to the full word.]
for register in reference.registers {
context metadata((kind:"tiny-register",family:family,key:register.key,page:here().page()))
block(above:rhythm.register-before,below:rhythm.metadata-after,breakable:false,sticky:true)[
#show heading: it => heading-layout(it,before:0pt,after:rhythm.register-after)
#set text(size:9pt)
#set par(leading:rhythm.small-leading,spacing:rhythm.small-spacing)
#heading(level:depth+2,numbering:none,outlined:false,bookmarked:true,register.name)
#label("reg-"+family+"-"+register.key)
#text(9pt,fill:muted)[#register.group · offset #code("0x"+str(register.offset,base:16)) · #register.access · reset #inline(register.reset)]
#linebreak()
#source(register.source,title:"RTL definition",line:register.line)
#if register.c_source!=none { [ · ]; source(register.c_source,title:"C ABI / HAL") }
]
par(inline(register.description))
block(breakable:false,above:rhythm.bits-space,below:rhythm.bits-space,bit-layout(register))
let fields=register.fields.sorted(key:f=>-f.msb)
ds-table("fields-"+family+"-"+register.key,[#register.name field descriptions],
([\#],[Bits],[Field],[Reset],[Description]),
fields.enumerate().map(((n,f))=>("F"+str(n+1),str(f.msb)+if f.msb!=f.lsb {":"+str(f.lsb)} else {""},code(f.name),inline(f.reset),inline(f.description))),
widths:(0.25fr,0.5fr,1.1fr,0.75fr,2.3fr))
if register.at("notes",default:"")!="" { par(inline(register.notes)) }
}
}
#let reference(entry) = {
pagebreak(weak:true)
context metadata((kind:"ip-start",id:entry.id,page:here().page()))
[#heading(level:3,entry.title)#label(entry.id)]
ip(entry.id)
minor-title("Features and functional organization")
list(..entry.features.map(inline))
ip-diagram(entry)
minor-title("Functional description and integration boundary")
for paragraph in entry.paragraphs {par(inline(paragraph))}
minor-title("Protocol and representative timing")
[The common AXI/APB rules in @tiny-bus and this instance's access rules both apply.
*Example waveforms show ordering and stable handshakes, not guaranteed cycle latency or electrical timing.*]
timing(if entry.family in data.waveforms {entry.family} else {"apb4"},[#entry.title representative transaction.],instance:entry.id)
if entry.id=="uart1" {
minor-title("Shared register reference")
[Use the @uart0 register definition with the UART1 base and IRQ from this chapter.
*Do not use the UART0 convenience HAL or DMA route as a UART1 binding.*]
} else {register-section(entry.family,3)}
minor-title("Software sequence and recovery")
enum(..entry.steps.map(inline))
if entry.id!="uart1" {
let api=data.apis.at(entry.family)
if api.len()>0 {ds-table("api-"+entry.id,[#entry.title selected SDK interfaces],([Function],[Declaration]),
api.map(a=>(code(a.name),code(a.signature))),widths:(1fr,3fr))}
if entry.family in data.examples {
code-block(raw(data.examples.at(entry.family),block:true,lang:"c"),breakable:true)
text(9pt,fill:muted)[Syntax-checked API fragment; clocks, pins, memory and arguments must already be valid.
Submission is not necessarily transfer completion.]
}
}
source-note(data.registers.at(entry.family).document,title:"Register and detailed interface contract")
context metadata((kind:"ip-end",id:entry.id,page:here().page()))
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,365 @@
{
"xpi": {
"main.ID": 0,
"main.VERSION": 4,
"main.CAPABILITY": 8,
"main.CTRL": 12,
"main.STATUS": 16,
"main.COMMAND": 20,
"main.ERROR_STATE": 24,
"main.ERROR_ADDR": 28,
"main.ERROR_INFO": 32,
"main.INTR_STATE": 36,
"main.INTR_ENABLE": 40,
"main.INTR_STATUS": 44,
"main.INTR_TEST": 48,
"main.DMA_CTRL": 52,
"main.FIFO_CTRL": 56,
"main.FIFO_STATUS": 60,
"main.TXDATA": 64,
"main.RXDATA": 68,
"main.INDIRECT_ADDR": 72,
"main.INDIRECT_COUNT": 76,
"main.INDIRECT_CFG": 80,
"main.POLL_CFG": 84,
"main.POLL_MASK": 88,
"main.POLL_MATCH": 92,
"main.POLL_INTERVAL": 96,
"main.POLL_TIMEOUT": 100,
"main.PERF_CTRL": 104,
"main.PERF_AXI_READ_BYTES": 108,
"main.PERF_AXI_WRITE_BYTES": 112,
"main.PERF_PHY_BYTES": 116,
"main.PERF_COMMANDS": 120,
"main.PERF_STALL_CYCLES": 124,
"main.CONFIG_LOCK": 128,
"slot.CTRL": 0,
"slot.DEVICE_SIZE": 4,
"slot.SEQ_CFG": 8,
"slot.TIMING": 12,
"slot.TIMEOUT": 16,
"slot.BOUNDARY": 20,
"lut.LUT_WORD": 0
},
"sram": {
"main.IP_ID": 0,
"main.IP_VERSION": 4,
"main.CAPABILITY": 8,
"main.MEMORY_BYTES": 12,
"main.BANK_COUNT": 16,
"main.BANK_BYTES": 20,
"main.PERF_READ_REQUESTS": 32,
"main.PERF_WRITE_REQUESTS": 36,
"main.PERF_READ_BEATS": 40,
"main.PERF_WRITE_BEATS": 44,
"main.PERF_STALL_CYCLES": 48,
"main.PERF_ERROR_RESPONSES": 52
},
"gpio": {
"user.DATA_IN": 0,
"user.DATA_OUT": 4,
"user.OUT_SET": 8,
"user.OUT_CLEAR": 12,
"user.OUT_TOGGLE": 16,
"user.INTR_STATE": 20,
"user.INTR_STATUS": 24,
"user.INTR_ENABLE": 28,
"user.INTR_ENABLE_SET": 32,
"user.INTR_ENABLE_CLEAR": 36,
"admin.DATA_IN": 0,
"admin.DATA_OUT": 4,
"admin.OUT_SET": 8,
"admin.OUT_CLEAR": 12,
"admin.OUT_TOGGLE": 16,
"admin.OUTPUT_ENABLE": 20,
"admin.OE_SET": 24,
"admin.OE_CLEAR": 28,
"admin.OE_TOGGLE": 32,
"admin.OPEN_DRAIN": 36,
"admin.INPUT_CMOS": 40,
"admin.PULL_UP": 44,
"admin.PULL_DOWN": 48,
"admin.ALT_ENABLE": 52,
"admin.ALT_SELECT": 56,
"admin.USER_SELECT": 60,
"admin.USER_LOCK": 64,
"admin.USER_STATUS": 68,
"admin.USER_ACCESS_MASK": 72,
"admin.INTR_RISE_ENABLE": 76,
"admin.INTR_FALL_ENABLE": 80,
"admin.INTR_HIGH_ENABLE": 84,
"admin.INTR_LOW_ENABLE": 88,
"admin.INTR_ENABLE": 92,
"admin.INTR_STATE": 96,
"admin.INTR_STATUS": 100,
"admin.INTR_TEST": 104,
"admin.FILTER_ENABLE": 108,
"admin.FILTER_DIV": 112,
"admin.FILTER_COUNT": 116,
"admin.CONFIG_LOCK": 120,
"common.PAD_CAPABILITY": 244,
"common.IP_VERSION": 248,
"common.CAPABILITY": 252
},
"dma": {
"main.IP_ID": 0,
"main.IP_VERSION": 4,
"main.CAPABILITY": 8,
"main.GLOBAL_CTRL": 12,
"main.GLOBAL_STATUS": 16,
"main.IRQ_STATE": 20,
"main.IRQ_ENABLE": 24,
"main.IRQ_TEST": 28,
"main.ERROR_SUMMARY": 32,
"main.REQUEST_STATUS": 36,
"ch.CTRL": 0,
"ch.CFG": 4,
"ch.SRC_ADDR": 8,
"ch.DST_ADDR": 12,
"ch.BYTE_COUNT": 16,
"ch.REQUEST_SEL": 20,
"ch.BURST_CFG": 24,
"ch.EVENT_ENABLE": 28,
"ch.STATUS": 32,
"ch.EVENT_STATUS": 36,
"ch.ERROR_STATUS": 40,
"ch.ERROR_ADDR": 44,
"ch.CURRENT_SRC": 48,
"ch.CURRENT_DST": 52,
"ch.REMAINING": 56,
"ch.BYTES_DONE": 60,
"ch.STALL_CYCLES_LO": 64,
"ch.STALL_CYCLES_HI": 68,
"ch.TCD_HEAD": 72,
"ch.TCD_COUNT": 76,
"ch.CRC_EXPECTED": 80,
"ch.CRC_RESULT": 84
},
"timer": {
"main.CTRL": 0,
"main.LOAD": 4,
"main.VALUE": 8,
"main.BGLOAD": 12,
"main.PRESCALE": 16,
"main.COMPARE0": 20,
"main.COMPARE1": 24,
"main.STATUS": 28,
"main.INTR_STATE": 32,
"main.INTR_ENABLE": 36,
"main.INTR_STATUS": 40,
"main.INTR_TEST": 44,
"main.IP_VERSION": 248,
"main.CAPABILITY": 252
},
"pwm": {
"main.CTRL": 0,
"main.STATUS": 4,
"main.COMMAND": 8,
"main.SAFETY_LOCK": 12,
"main.FAULT_CTRL": 16,
"main.FAULT_SAFE": 20,
"main.FAULT_STATUS": 24,
"main.FAULT_CLEAR": 28,
"main.INTR_STATE": 32,
"main.INTR_ENABLE": 36,
"main.INTR_TEST": 40,
"main.OUTPUT_STATUS": 44,
"main.CLOCK_HZ": 48,
"main.IP_ID": 244,
"main.IP_VERSION": 248,
"main.CAPABILITY": 252,
"main.CAPTURE_CTRL": 1024,
"main.CAPTURE_DIVIDER": 1028,
"main.CAPTURE_COUNTER": 1032,
"main.CAPTURE_STATUS": 1036,
"main.CAPTURE0_CTRL": 1040,
"main.CAPTURE0_STATUS": 1044,
"main.CAPTURE0_DATA": 1048,
"main.CAPTURE1_CTRL": 1056,
"main.CAPTURE1_STATUS": 1060,
"main.CAPTURE1_DATA": 1064,
"timer.CTRL": 0,
"timer.DIVIDER": 4,
"timer.PERIOD": 8,
"timer.PHASE": 12,
"timer.COUNTER": 16,
"timer.ACTIVE_DIVIDER": 20,
"timer.ACTIVE_PERIOD": 24,
"timer.STATUS": 28,
"channel.CTRL": 0,
"channel.PHASE": 4,
"channel.DUTY": 8,
"channel.ACTION": 12,
"channel.FORCE": 16,
"channel.FADE_CTRL": 20,
"channel.FADE_TARGET": 24,
"channel.FADE_STEP": 28,
"channel.FADE_INTERVAL": 32,
"channel.FADE_STATUS": 36,
"channel.ACTIVE_PHASE": 40,
"channel.ACTIVE_DUTY": 44,
"channel.GAMMA_INDEX": 48,
"channel.GAMMA_TARGET": 52,
"channel.GAMMA_STEP": 56,
"channel.GAMMA_INTERVAL": 60,
"operator.CTRL": 0,
"operator.DEADTIME": 4,
"operator.CARRIER": 8,
"operator.STATUS": 12
},
"rtc": {
"main.CTRL": 0,
"main.STATUS": 4,
"main.COMMAND": 8,
"main.COMMAND_STATUS": 12,
"main.TIME_LOAD_LO": 16,
"main.TIME_LOAD_HI": 20,
"main.TIME_LOAD_SUBSEC": 24,
"main.SNAPSHOT_TIME_LO": 28,
"main.SNAPSHOT_TIME_HI": 32,
"main.SNAPSHOT_SUBSEC": 36,
"main.SECOND_CYCLES": 40,
"main.CALIB_PPM": 44,
"main.ALARM0_TIME_LO": 48,
"main.ALARM0_TIME_HI": 52,
"main.ALARM0_SUBSEC": 56,
"main.ALARM1_TIME_LO": 60,
"main.ALARM1_TIME_HI": 64,
"main.ALARM1_SUBSEC": 68,
"main.ALARM_ENABLE": 72,
"main.PERIOD_RELOAD": 76,
"main.PERIOD_CTRL": 80,
"main.EVENT_RAW": 84,
"main.EVENT_CLEAR": 88,
"main.EVENT_TEST": 92,
"main.INTR_ENABLE": 96,
"main.INTR_STATE": 100,
"main.WAKE_ENABLE": 104,
"main.WAKE_STATE": 108,
"main.CLOCK_HZ": 240,
"main.IP_ID": 244,
"main.IP_VERSION": 248,
"main.CAPABILITY": 252
},
"wdg": {
"main.CTRL": 0,
"main.STATUS": 4,
"main.PRESCALE": 8,
"main.TIMEOUT": 12,
"main.WINDOW_MIN": 16,
"main.EARLY_WARNING": 20,
"main.CONFIG": 24,
"main.CONFIG_LOCK": 28,
"main.SERVICE": 32,
"main.COMMAND_STATUS": 36,
"main.COUNT_SNAPSHOT": 40,
"main.EVENT_RAW": 44,
"main.INTR_STATE": 48,
"main.INTR_ENABLE": 52,
"main.INTR_TEST": 56,
"main.RESET_CAUSE": 60,
"main.RESET_COUNT_SNAPSHOT": 64,
"main.CLOCK_HZ": 68,
"main.IP_ID": 244,
"main.IP_VERSION": 248,
"main.CAPABILITY": 252
},
"uart": {
"main.BAUD_INT": 0,
"main.BAUD_FRAC": 4,
"main.LINE_CTRL": 8,
"main.CTRL": 12,
"main.TXDATA": 16,
"main.RXDATA": 20,
"main.STATUS": 24,
"main.FIFO_LEVEL": 28,
"main.FIFO_CTRL": 32,
"main.TX_WATERMARK": 36,
"main.RX_WATERMARK": 40,
"main.RX_TIMEOUT_BITS": 44,
"main.ERROR_STATUS": 48,
"main.INTR_STATE": 52,
"main.INTR_ENABLE": 56,
"main.INTR_STATUS": 60,
"main.INTR_TEST": 64,
"main.DMA_CTRL": 68,
"main.FLOW_CTRL": 72,
"main.RTS_WATERMARK": 76,
"main.IP_VERSION": 248,
"main.CAPABILITY": 252
},
"i2c": {
"main.CTRL": 0,
"main.SCL_TIMING": 4,
"main.START_TIMING": 8,
"main.DATA_TIMING": 12,
"main.STOP_TIMING": 16,
"main.FILTER": 20,
"main.STRETCH_TIMEOUT": 24,
"main.BUS_IDLE_TIMEOUT": 28,
"main.COMMAND_TIMEOUT": 32,
"main.TARGET_ADDR": 36,
"main.DATA_CMD": 40,
"main.RXDATA": 44,
"main.STATUS": 48,
"main.FIFO_LEVEL": 52,
"main.COMMAND": 56,
"main.CMD_WATERMARK": 60,
"main.RX_WATERMARK": 64,
"main.ERROR_STATUS": 68,
"main.INTR_STATE": 72,
"main.INTR_ENABLE": 76,
"main.INTR_STATUS": 80,
"main.INTR_TEST": 84,
"main.LINE_STATE": 88,
"main.IP_VERSION": 248,
"main.CAPABILITY": 252
},
"clint": {
"msip.MSIP": 0,
"compare.MTIMECMP": 16384,
"compare.MTIMECMP_HI": 16388,
"main.MTIME": 49144,
"main.MTIMEH": 49148
},
"archinfo": {
"main.COMPONENT_ID": 0,
"main.VENDOR_ID": 4,
"main.SOC_ID": 8,
"main.SOC_REVISION": 12,
"main.BUILD_ID_LO": 16,
"main.BUILD_ID_HI": 20,
"main.CONFIG_ID": 24,
"main.BUILD_STATUS": 28,
"main.REFERENCE_CLOCK": 32,
"main.SRAM_BYTES": 36,
"main.TOPOLOGY": 40,
"main.FEATURES0": 44,
"main.TECHNOLOGY": 48,
"main.DEVICE_ID_STATUS": 52,
"main.DEVICE_ID0": 56,
"main.DEVICE_ID1": 60,
"main.DEVICE_ID2": 64,
"main.DEVICE_ID3": 68,
"main.IP_VERSION": 248,
"main.CAPABILITY": 252
},
"sysctrl": {
"main.CORESEL": 0,
"main.IPSEL": 4,
"main.FAULT_STATUS": 16,
"main.FAULT_ADDR": 20,
"main.FAULT_COUNT": 24,
"main.USER_CORE_RESET": 32,
"main.USER_CORE_STATUS": 36,
"main.FAULT_MASTER": 40,
"main.FAULT_DETAIL": 44,
"main.PERF_CTRL": 64,
"main.PERF_MGMT_WAIT_LO": 68,
"main.PERF_MGMT_WAIT_HI": 72,
"main.PERF_DMA_WAIT_LO": 84,
"main.PERF_DMA_WAIT_HI": 88,
"main.TEST_STATUS": 132,
"main.RTC_WAKE_STATUS": 136
}
}
@@ -0,0 +1,746 @@
{
"xpi": {
"document": "docs/ip/xpi.md",
"defines": [
"rtl/ip/storage/xpi_define.svh"
],
"rtl": [
"rtl/ip/storage/xpi_reg.sv",
"rtl/ip/storage/xpi_pkg.sv"
],
"parameters": {},
"groups": [
{
"id": "main",
"title": "Global XPI registers",
"pattern": "APB4_XPI__(?!SLOT_|LUT_).+",
"strip": "^APB4_XPI__",
"base": 0,
"stride": 0,
"count": 1
},
{
"id": "slot",
"title": "Chip-select slots",
"pattern": "APB4_XPI__SLOT_.+",
"strip": "^APB4_XPI__SLOT_",
"base": 256,
"stride": 32,
"count": 4,
"exclude": [
"APB4_XPI__SLOT_BASE",
"APB4_XPI__SLOT_STRIDE"
]
},
{
"id": "lut",
"title": "LUT words",
"pattern": "(?!)",
"strip": "^",
"base": 512,
"stride": 4,
"count": 64
}
],
"field_aliases": {
"IRQ_STATE": [
"APB4_XPI__IRQ_"
],
"IRQ_ENABLE": [
"APB4_XPI__IRQ_"
],
"IRQ_TEST": [
"APB4_XPI__IRQ_"
],
"INTR_STATE": [
"XPI_INTR_"
],
"INTR_ENABLE": [
"XPI_INTR_"
],
"INTR_STATUS": [
"XPI_INTR_"
]
},
"extra": [
{
"group": "lut",
"name": "LUT_WORD",
"offset": 0,
"access": "RW / locked",
"reset": "Boot LUT; other sequences STOP",
"description": "Two 16-bit instructions. Sequence n begins at word 4*n; PC7 must terminate or jump on CS. Bits 9:8 of each halfword are reserved.",
"fields": [
{
"name": "OPERAND0",
"lsb": 0,
"msb": 7,
"description": "Instruction operand.",
"reset": "Register reset applies",
"expression": "reviewed semantic field"
},
{
"name": "Reserved",
"lsb": 8,
"msb": 9,
"description": "Reserved; preserve zero when writing.",
"reset": "0",
"expression": "reserved"
},
{
"name": "PADS0",
"lsb": 10,
"msb": 11,
"description": "0 = one pad, 1 = two pads, 2 = four pads; 3 is invalid.",
"reset": "Register reset applies",
"expression": "reviewed semantic field"
},
{
"name": "OPCODE0",
"lsb": 12,
"msb": 15,
"description": "0 STOP, 1 CMD, 2 ADDR, 3 MODE, 4 DUMMY, 5 TX, 6 RX, 7 JUMP_ON_CS.",
"reset": "Register reset applies",
"expression": "reviewed semantic field"
},
{
"name": "OPERAND1",
"lsb": 16,
"msb": 23,
"description": "Instruction operand.",
"reset": "Register reset applies",
"expression": "reviewed semantic field"
},
{
"name": "Reserved",
"lsb": 24,
"msb": 25,
"description": "Reserved; preserve zero when writing.",
"reset": "0",
"expression": "reserved"
},
{
"name": "PADS1",
"lsb": 26,
"msb": 27,
"description": "0 = one pad, 1 = two pads, 2 = four pads; 3 is invalid.",
"reset": "Register reset applies",
"expression": "reviewed semantic field"
},
{
"name": "OPCODE1",
"lsb": 28,
"msb": 31,
"description": "0 STOP, 1 CMD, 2 ADDR, 3 MODE, 4 DUMMY, 5 TX, 6 RX, 7 JUMP_ON_CS.",
"reset": "Register reset applies",
"expression": "reviewed semantic field"
}
]
}
],
"support": [
"rtl/ip/storage/apb4_xpi.sv",
"rtl/ip/storage/xpi_clkgen.sv",
"rtl/ip/storage/xpi_core.sv",
"rtl/ip/storage/xpi_if.sv",
"rtl/ip/storage/xpi_indirect.sv",
"rtl/ip/storage/xpi_mm.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"sram": {
"document": "docs/ip/onchip-sram.md",
"defines": [
"rtl/ip/memory/onchip_ram_define.svh"
],
"rtl": [
"rtl/ip/memory/onchip_ram_reg.sv"
],
"parameters": {
"Present": 1,
"CapacityKiB": 128,
"BankCount": 32,
"BankBytes": 4096,
"DataBytes": 4
},
"groups": [
{
"id": "main",
"title": "SRAM management",
"pattern": "APB4_ONCHIP_RAM__.+",
"strip": "^APB4_ONCHIP_RAM__",
"base": 0,
"stride": 0,
"count": 1
}
],
"default_access": "RO",
"support": [],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"gpio": {
"document": "docs/ip/gpio.md",
"defines": [
"rtl/ip/peripheral/gpio_define.svh"
],
"rtl": [
"rtl/ip/peripheral/gpio_reg.sv"
],
"parameters": {
"PinNum": 32,
"NumPins": 32,
"GPIO_NUM": 32
},
"groups": [
{
"id": "user",
"title": "User window",
"pattern": "APB4_GPIO_USER_.+",
"strip": "^APB4_GPIO_USER_",
"base": 0,
"stride": 0,
"count": 1,
"section": "User window"
},
{
"id": "admin",
"title": "Management window",
"pattern": "APB4_GPIO_ADMIN_.+",
"strip": "^APB4_GPIO_ADMIN_",
"base": 0,
"stride": 0,
"count": 1,
"section": "Management window"
},
{
"id": "common",
"title": "Common discovery registers",
"pattern": "APB4_GPIO_(PAD_CAPABILITY|IP_VERSION|CAPABILITY)",
"strip": "^APB4_GPIO_",
"base": 0,
"stride": 0,
"count": 1
}
],
"support": [
"rtl/ip/peripheral/apb4_gpio.sv",
"rtl/ip/peripheral/gpio_core.sv",
"rtl/ip/peripheral/gpio_if.sv",
"rtl/ip/peripheral/user_gpio_if.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"dma": {
"document": "docs/ip/dma.md",
"defines": [
"rtl/ip/peripheral/dma_define.svh"
],
"rtl": [
"rtl/ip/peripheral/dma_reg.sv",
"rtl/ip/peripheral/dma_pkg.sv"
],
"parameters": {
"NumChannels": 4,
"ChannelIndexWidth": 2,
"EnableStreams": 0,
"DataWidth": 32,
"MaxBurstBeats": 16
},
"groups": [
{
"id": "main",
"title": "Register bank",
"pattern": "APB4_DMA__(?!CH_).+",
"strip": "^APB4_DMA__",
"base": 0,
"stride": 0,
"count": 1
},
{
"id": "ch",
"title": "DMA channel registers",
"pattern": "APB4_DMA__CH_.+",
"strip": "^APB4_DMA__CH_",
"base": 256,
"stride": 128,
"count": 4,
"exclude": [
"APB4_DMA__CH_BASE",
"APB4_DMA__CH_STRIDE",
"APB4_DMA__CH_BASE_OFFSET"
],
"doc_name_prefix": "CH_"
}
],
"field_aliases": {
"EVENT_ENABLE": [
"APB4_DMA__EVENT_"
],
"EVENT_STATUS": [
"APB4_DMA__EVENT_"
],
"STATUS": [
"APB4_DMA__STATUS_"
]
},
"support": [
"rtl/ip/peripheral/apb4_dma.sv",
"rtl/ip/peripheral/dma_axi4_master.sv",
"rtl/ip/peripheral/dma_core.sv",
"rtl/ip/peripheral/dma_req_if.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"timer": {
"document": "docs/ip/timer.md",
"defines": [
"rtl/ip/peripheral/timer_define.svh"
],
"rtl": [
"rtl/ip/peripheral/timer_reg.sv"
],
"parameters": {},
"groups": [
{
"id": "main",
"title": "Register bank",
"pattern": "APB4_TIMER_.+",
"strip": "^APB4_TIMER_",
"base": 0,
"stride": 0,
"count": 1
}
],
"field_aliases": {
"INTR_STATE": [
"TIMER_INTR_"
],
"INTR_ENABLE": [
"TIMER_INTR_"
],
"INTR_STATUS": [
"TIMER_INTR_"
],
"INTR_TEST": [
"TIMER_INTR_"
],
"ERROR_STATUS": [
"TIMER_ERROR_"
]
},
"support": [
"rtl/ip/peripheral/apb4_timer.sv",
"rtl/ip/peripheral/timer_core.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"pwm": {
"document": "rtl/managed/clusterip/pwm/doc/datasheet.md",
"defines": [
"rtl/managed/clusterip/pwm/rtl/pwm_define.svh"
],
"rtl": [
"rtl/managed/clusterip/pwm/rtl/pwm_reg.sv",
"rtl/managed/clusterip/pwm/rtl/pwm_pkg.sv"
],
"parameters": {
"PCLK_HZ": 24000000,
"RTC_CLOCK_HZ": 24000000,
"WDG_CLOCK_HZ": 24000000,
"FIFO_DEPTH": 8
},
"groups": [
{
"id": "main",
"title": "Register bank",
"pattern": "PWM_(?!TIMER_)(?!CHANNEL_)(?!OPERATOR_).+_OFFSET",
"strip": "^PWM_",
"base": 0,
"stride": 0,
"count": 1
},
{
"id": "timer",
"title": "TIMER registers",
"pattern": "PWM_TIMER_.+",
"strip": "^PWM_TIMER_",
"base": 256,
"stride": 64,
"count": 2,
"exclude": [
"PWM_TIMER_BASE",
"PWM_TIMER_STRIDE",
"PWM_TIMER_BASE_OFFSET"
],
"section": "Timer Blocks"
},
{
"id": "channel",
"title": "CHANNEL registers",
"pattern": "PWM_CHANNEL_.+",
"strip": "^PWM_CHANNEL_",
"base": 512,
"stride": 64,
"count": 4,
"exclude": [
"PWM_CHANNEL_BASE",
"PWM_CHANNEL_STRIDE",
"PWM_CHANNEL_BASE_OFFSET"
],
"section": "Channel Blocks"
},
{
"id": "operator",
"title": "OPERATOR registers",
"pattern": "PWM_OPERATOR_.+",
"strip": "^PWM_OPERATOR_",
"base": 768,
"stride": 64,
"count": 2,
"exclude": [
"PWM_OPERATOR_BASE",
"PWM_OPERATOR_STRIDE",
"PWM_OPERATOR_BASE_OFFSET"
],
"section": "Operator Blocks"
}
],
"field_aliases": {
"INTR_STATE": [
"PWM_INTR_"
],
"INTR_ENABLE": [
"PWM_INTR_"
],
"INTR_TEST": [
"PWM_INTR_"
],
"CAPTURE0_CTRL": [
"PWM_CAPTURE_CH_"
],
"CAPTURE1_CTRL": [
"PWM_CAPTURE_CH_"
]
},
"support": [
"rtl/managed/clusterip/pwm/rtl/apb4_pwm.sv",
"rtl/managed/clusterip/pwm/rtl/pwm_capture.sv",
"rtl/managed/clusterip/pwm/rtl/pwm_carrier.sv",
"rtl/managed/clusterip/pwm/rtl/pwm_channel.sv",
"rtl/managed/clusterip/pwm/rtl/pwm_core.sv",
"rtl/managed/clusterip/pwm/rtl/pwm_deadtime.sv",
"rtl/managed/clusterip/pwm/rtl/pwm_if.sv",
"rtl/managed/clusterip/pwm/rtl/pwm_tickgen.sv",
"rtl/managed/clusterip/pwm/rtl/pwm_timer.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"rtc": {
"document": "rtl/managed/clusterip/rtc/doc/datasheet.md",
"defines": [
"rtl/managed/clusterip/rtc/rtl/rtc_define.svh"
],
"rtl": [
"rtl/managed/clusterip/rtc/rtl/rtc_reg.sv",
"rtl/managed/clusterip/rtc/rtl/rtc_pkg.sv"
],
"parameters": {
"PCLK_HZ": 24000000,
"RTC_CLOCK_HZ": 24000000,
"WDG_CLOCK_HZ": 24000000,
"FIFO_DEPTH": 8
},
"groups": [
{
"id": "main",
"title": "Register bank",
"pattern": "RTC_.+_OFFSET",
"strip": "^RTC_",
"base": 0,
"stride": 0,
"count": 1
}
],
"field_aliases": {
"INTR_STATE": [
"RTC_INTR_"
],
"INTR_ENABLE": [
"RTC_INTR_"
],
"INTR_TEST": [
"RTC_INTR_"
]
},
"support": [
"rtl/managed/clusterip/rtc/rtl/apb4_rtc.sv",
"rtl/managed/clusterip/rtc/rtl/rtc_core.sv",
"rtl/managed/clusterip/rtc/rtl/rtc_if.sv",
"rtl/managed/clusterip/rtc/rtl/rtc_tickgen.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"wdg": {
"document": "rtl/managed/clusterip/wdg/doc/datasheet.md",
"defines": [
"rtl/managed/clusterip/wdg/rtl/wdg_define.svh"
],
"rtl": [
"rtl/managed/clusterip/wdg/rtl/wdg_reg.sv",
"rtl/managed/clusterip/wdg/rtl/wdg_pkg.sv"
],
"parameters": {
"PCLK_HZ": 24000000,
"RTC_CLOCK_HZ": 24000000,
"WDG_CLOCK_HZ": 24000000,
"FIFO_DEPTH": 8
},
"groups": [
{
"id": "main",
"title": "Register bank",
"pattern": "WDG_.+_OFFSET",
"strip": "^WDG_",
"base": 0,
"stride": 0,
"count": 1
}
],
"field_aliases": {
"INTR_STATE": [
"WDG_INTR_"
],
"INTR_ENABLE": [
"WDG_INTR_"
],
"INTR_TEST": [
"WDG_INTR_"
]
},
"support": [
"rtl/managed/clusterip/wdg/rtl/apb4_wdg.sv",
"rtl/managed/clusterip/wdg/rtl/wdg_core.sv",
"rtl/managed/clusterip/wdg/rtl/wdg_if.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"uart": {
"document": "docs/ip/uart.md",
"defines": [
"rtl/ip/serial/apb4_uart_define.svh"
],
"rtl": [
"rtl/ip/serial/uart_reg.sv"
],
"parameters": {
"TxFifoDepth": 64,
"RxFifoDepth": 64,
"TxFifoLogDepth": 6,
"RxFifoLogDepth": 6
},
"groups": [
{
"id": "main",
"title": "Register bank",
"pattern": "APB4_UART_.+",
"strip": "^APB4_UART_",
"base": 0,
"stride": 0,
"count": 1
}
],
"field_aliases": {
"LINE_CTRL": [
"UART_LINE_"
],
"CTRL": [
"UART_CTRL_"
],
"STATUS": [
"UART_STATUS_"
],
"FIFO_CTRL": [
"UART_FIFO_CTRL_"
],
"RXDATA": [
"UART_RXDATA_"
],
"ERROR_STATUS": [
"UART_ERROR_"
],
"INTR_STATE": [
"UART_INTR_"
],
"INTR_ENABLE": [
"UART_INTR_"
],
"INTR_STATUS": [
"UART_INTR_"
],
"INTR_TEST": [
"UART_INTR_"
],
"DMA_CTRL": [
"UART_DMA_"
],
"FLOW_CTRL": [
"UART_FLOW_"
]
},
"support": [
"rtl/ip/serial/apb4_uart.sv",
"rtl/ip/serial/apb4_uart_rx.sv",
"rtl/ip/serial/apb4_uart_tx.sv",
"rtl/ip/serial/uart_baudgen.sv",
"rtl/ip/serial/uart_core.sv",
"rtl/ip/serial/uart_flow_ctrl.sv",
"rtl/ip/serial/uart_if.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"i2c": {
"document": "docs/ip/i2c.md",
"defines": [
"rtl/ip/serial/apb4_i2c_define.svh"
],
"rtl": [
"rtl/ip/serial/i2c_reg.sv"
],
"parameters": {},
"groups": [
{
"id": "main",
"title": "Register bank",
"pattern": "APB4_I2C_.+",
"strip": "^APB4_I2C_",
"base": 0,
"stride": 0,
"count": 1
}
],
"field_aliases": {
"INTR_STATE": [
"I2C_INTR_"
],
"INTR_ENABLE": [
"I2C_INTR_"
],
"INTR_STATUS": [
"I2C_INTR_"
],
"INTR_TEST": [
"I2C_INTR_"
],
"ERROR_STATUS": [
"I2C_ERROR_"
]
},
"support": [
"rtl/ip/serial/apb4_i2c.sv",
"rtl/ip/serial/i2c_core.sv",
"rtl/ip/serial/i2c_filter.sv",
"rtl/ip/serial/i2c_if.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"clint": {
"document": "docs/ip/clint.md",
"defines": [
"rtl/ip/peripheral/clint_define.svh"
],
"rtl": [
"rtl/ip/peripheral/clint_reg.sv"
],
"parameters": {
"HartNum": 1
},
"groups": [
{
"id": "msip",
"title": "Per-hart software interrupt",
"pattern": "APB4_CLINT_MSIP",
"strip": "^APB4_CLINT_",
"base": 0,
"stride": 4,
"count": 1
},
{
"id": "compare",
"title": "Per-hart timer comparator",
"pattern": "APB4_CLINT_MTIMECMP",
"strip": "^APB4_CLINT_",
"base": 0,
"stride": 8,
"count": 1
},
{
"id": "main",
"title": "Shared 64-bit time",
"pattern": "APB4_CLINT_MTIMEH?",
"strip": "^APB4_CLINT_",
"base": 0,
"stride": 0,
"count": 1
}
],
"extra": [
{
"group": "compare",
"name": "MTIMECMP_HI",
"offset": 16388,
"access": "RW",
"reset": "0xFFFFFFFF",
"description": "High word of the per-hart 64-bit machine timer comparator.",
"fields": [
{
"name": "VALUE",
"lsb": 0,
"msb": 31,
"description": "Comparator bits 63:32.",
"reset": "Register reset applies",
"expression": "reviewed field"
}
]
}
],
"support": [
"rtl/ip/peripheral/apb4_clint.sv",
"rtl/ip/peripheral/clint_core.sv",
"rtl/ip/peripheral/clint_if.sv",
"rtl/ip/peripheral/clint_timebase.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
},
"archinfo": {
"document": "rtl/managed/clusterip/archinfo/doc/datasheet.md",
"defines": [
"rtl/managed/clusterip/archinfo/rtl/archinfo_define.svh"
],
"rtl": [
"rtl/managed/clusterip/archinfo/rtl/archinfo_reg.sv"
],
"parameters": {
"REFERENCE_CLOCK_HZ": 24000000,
"SRAM_BYTES": 131072,
"TOPOLOGY": 538968065,
"FEATURES0": 32766,
"TECHNOLOGY": 33620098
},
"groups": [
{
"id": "main",
"title": "Register bank",
"pattern": "ARCHINFO_.+_OFFSET",
"strip": "^ARCHINFO_",
"base": 0,
"stride": 0,
"count": 1
}
],
"default_access": "RO",
"support": [
"rtl/managed/clusterip/archinfo/rtl/apb4_archinfo.sv"
],
"memory_map": "rtl/tiny/address_map/memory_map.json"
}
}
@@ -0,0 +1,182 @@
{
"regions": {
"FLASH": [
0,
16777216
],
"APB4_GPIO": [
268435456,
4096
],
"APB4_UART0": [
268439552,
4096
],
"APB4_TIM0": [
268443648,
4096
],
"APB4_TIM1": [
268447744,
4096
],
"APB4_I2C0": [
268460032,
4096
],
"APB4_XPI": [
268472320,
4096
],
"APB4_DMA": [
268476416,
4096
],
"APB4_SYSCTRL": [
268480512,
4096
],
"APB4_CLINT": [
268566528,
65536
],
"APB4_I2C1": [
268505088,
4096
],
"APB4_GPIO_ADMIN": [
268517376,
4096
],
"APB4_SRAM": [
268529664,
4096
],
"APB4_UART1": [
268533760,
4096
],
"APB4_ARCHINFO": [
536870912,
4096
],
"APB4_PWM": [
536879104,
4096
],
"APB4_RTC": [
536887296,
4096
],
"APB4_WDG": [
536891392,
4096
],
"SRAM": [
805306368,
131072
],
"XPI": [
1342177280,
268435456
]
},
"interrupts": {
"clint_software": 0,
"clint_timer": 1,
"uart0": 2,
"timer0": 3,
"timer1": 4,
"i2c0": 7,
"xpi": 9,
"pwm": 11,
"rtc": 13,
"watchdog_early_warning": 14,
"gpio": 18,
"i2c1": 19,
"dma": 20,
"uart1": 26
},
"sources": [
"rtl/ip/core/mgmt_core_wrapper.sv",
"rtl/ip/core/mgmt_debug_wrapper.sv",
"rtl/ip/core/mgmt_debug_reset.sv",
"rtl/ip/interconnect/ahbl2axi4.sv",
"crt/arch/riscv/startup.S",
"crt/linker/ld2_all_sram.lds",
"crt/src/hal/dma_math.c",
"crt/src/hal/sysctrl.c",
"crt/src/core/archinfo.c",
"app/apps/ci_smoke/tiny.c",
"app/apps/ci_smoke/mcu_acceptance.S",
"app/apps/bringup/tiny.mk",
"app/apps/ci_smoke/tiny.mk",
"scripts/generate_tiny.py",
"tests/test_tiny.py"
],
"bindings": [
{
"file": "rtl/tiny/top/tiny_axi4_fabric.sv",
"text": [
"(s_selected_addr.len <= 8'd15)",
"(s_last_addr[31:12] == s_selected_addr.addr[31:12])",
"s_accept = (s_state_q == Idle) && s_selected_valid;",
"Response: if (s_terminal) s_state_d = Idle;",
"s_tgt_d = s_protocol_legal ? s_decoded_target : 3'd4;",
"assign s_protocol_legal = !s_selected_addr.lock && (s_selected_addr.size <= 3'd2) &&\n ((s_selected_addr.addr & ((32'd1 << s_selected_addr.size) - 32'd1)) == 32'd0) &&\n ((s_selected_addr.burst == 2'b01) ||\n ((s_selected_addr.burst == 2'b00) && (s_selected_addr.len == 8'd0))) &&\n (s_selected_addr.len <= 8'd15) && !s_last_addr[32] &&\n (s_last_addr[31:12] == s_selected_addr.addr[31:12]) &&\n (decode_target(\n s_last_addr[31:0]\n ) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0));",
"assign s_terminal = (s_state_q == Response) &&\n (s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) :\n (s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q]));"
]
},
{
"file": "crt/src/hal/dma_math.c",
"text": [
"#ifdef RS_SOC_TINY\n if (config->kind != RS_DMA_KIND_MM_TO_MM) {\n return false;\n }\n#endif"
]
},
{
"file": "crt/arch/riscv/startup.S",
"text": [
"#ifndef RS_SOC_TINY"
]
},
{
"file": "rtl/tiny/top/retrosoc_tiny.sv",
"text": [
".RTC_CLOCK_HZ(24_000_000)",
".WDG_CLOCK_HZ(24_000_000)",
".RESET_PULSE_CYCLES(8)",
"assign u_uart1_if.cts_n_i = 1'b0;"
]
},
{
"file": "rtl/ip/peripheral/dma_reg.sv",
"text": [
"`APB4_DMA__CAPABILITY:\n s_read_data = {\n 1'b0,\n (EnableStreams ? 3'd3 : 3'd0),\n 4'(MaxBurstBeats),\n 8'(DataWidth),\n 8'(NumChannels),\n 7'd0,\n 1'b1\n };"
]
},
{
"file": "crt/include/retrosoc/hal/dma.h",
"text": [
"#define RS_DMA_TCD_VALID UINT32_C(0x00000001)",
"#define RS_DMA_TCD_SRC_INC UINT32_C(0x00000002)",
"#define RS_DMA_TCD_DST_INC UINT32_C(0x00000004)",
"#define RS_DMA_TCD_CRC_ENABLE UINT32_C(0x00000008)",
"#define RS_DMA_TCD_CRC_FINAL UINT32_C(0x00000010)",
"#define RS_DMA_TCD_INT_DONE UINT32_C(0x00000020)",
"#define RS_DMA_TCD_INT_ERROR UINT32_C(0x00000040)",
"#define RS_DMA_TCD_KIND_SHIFT 8U",
"#define RS_DMA_TCD_REQUEST_SHIFT 12U",
"#define RS_DMA_TCD_PRIORITY_SHIFT 16U",
"#define RS_DMA_TCD_BURST_SHIFT 20U"
]
},
{
"file": "crt/src/hal/dma.c",
"text": [
"if (burst == UINT32_C(0)) {\n burst = RS_DMA_MAX_BURST_BEATS;\n }",
"RS_DMA_CH_REG(channel, RS_DMA_CH_REG_TCD_COUNT) = UINT32_C(1);",
"current = (rs_dma_tcd_t *)(uintptr_t)current->next_ptr;"
]
}
]
}
@@ -0,0 +1,217 @@
{
"schema_version": 1,
"state": "frozen",
"ip_ids": [
"xpi",
"sram",
"gpio",
"dma",
"timer",
"pwm",
"rtc",
"wdg",
"uart0",
"uart1",
"i2c",
"sysctrl",
"clint",
"archinfo"
],
"entries": [
{
"level": 1,
"title": "Product Brief",
"anchor": null
},
{
"level": 2,
"title": "Features",
"anchor": null
},
{
"level": 2,
"title": "Configuration and Scope",
"anchor": "tiny-configuration"
},
{
"level": 2,
"title": "Reading and Evidence Conventions",
"anchor": null
},
{
"level": 1,
"title": "System Architecture",
"anchor": null
},
{
"level": 2,
"title": "Processor and Integration",
"anchor": "tiny-core"
},
{
"level": 2,
"title": "Clock, Reset and Operating State",
"anchor": "tiny-clock"
},
{
"level": 2,
"title": "Bus Transactions, Arbitration and Errors",
"anchor": "tiny-bus"
},
{
"level": 2,
"title": "Memory Map and Access Conventions",
"anchor": "tiny-addresses"
},
{
"level": 2,
"title": "Interrupt and DMA Request Routing",
"anchor": "tiny-irq"
},
{
"level": 2,
"title": "Peripherals",
"anchor": null
},
{
"level": 3,
"title": "XPI Universal Controller",
"anchor": "xpi"
},
{
"level": 3,
"title": "On-Chip SRAM",
"anchor": "sram"
},
{
"level": 3,
"title": "General-Purpose Input/Output (GPIO)",
"anchor": "gpio"
},
{
"level": 3,
"title": "Direct Memory Access (DMA)",
"anchor": "dma"
},
{
"level": 3,
"title": "General-Purpose Timers (TIM0, TIM1)",
"anchor": "timer"
},
{
"level": 3,
"title": "Pulse Width Modulation (PWM)",
"anchor": "pwm"
},
{
"level": 3,
"title": "Real-Time Clock (RTC)",
"anchor": "rtc"
},
{
"level": 3,
"title": "Watchdog (WDG)",
"anchor": "wdg"
},
{
"level": 3,
"title": "Management UART (UART0)",
"anchor": "uart0"
},
{
"level": 3,
"title": "Second UART (UART1)",
"anchor": "uart1"
},
{
"level": 3,
"title": "Inter-Integrated Circuit (I2C0, I2C1)",
"anchor": "i2c"
},
{
"level": 3,
"title": "System Controller (SYSCTRL)",
"anchor": "sysctrl"
},
{
"level": 3,
"title": "Local Interrupt Controller (CLINT)",
"anchor": "clint"
},
{
"level": 3,
"title": "Architecture Information (ARCHINFO)",
"anchor": "archinfo"
},
{
"level": 2,
"title": "Pins and Board Connections",
"anchor": "tiny-pins"
},
{
"level": 2,
"title": "Electrical, Package and Reliability Boundaries",
"anchor": null
},
{
"level": 1,
"title": "Software",
"anchor": null
},
{
"level": 2,
"title": "Reset, Boot and Linker Layout",
"anchor": "tiny-boot"
},
{
"level": 2,
"title": "SDK, API Completion and Polling",
"anchor": null
},
{
"level": 2,
"title": "DMA Descriptors and Buffer Lifetime",
"anchor": "tiny-dma-format"
},
{
"level": 2,
"title": "Acceptance and Diagnostics",
"anchor": "tiny-diagnostics"
},
{
"level": 1,
"title": "Implementation and Evidence",
"anchor": null
},
{
"level": 2,
"title": "Reproducible Development Flow",
"anchor": null
},
{
"level": 2,
"title": "Verification Status and Qualification Gaps",
"anchor": "tiny-evidence"
},
{
"level": 1,
"title": "Appendix: Global Register Index",
"anchor": "tiny-register-index"
},
{
"level": 1,
"title": "Document Control",
"anchor": null
},
{
"level": 2,
"title": "Revision History",
"anchor": null
},
{
"level": 2,
"title": "Sources, License and Contact",
"anchor": "publication-provenance"
}
]
}
+40
View File
@@ -0,0 +1,40 @@
# Tiny datasheet layout supplement
Tiny follows the reviewed [Mini visual language](../style.md), but owns an
independent [style implementation](style.typ). This addition does not edit,
import or reflow Mini's template. Changes to Tiny must preserve the publication
isolation boundary rather than inserting fake Mini/HP data into its model.
Use A4 portrait, 19 mm horizontal margins, a 172 mm body, 10.5 pt Inter body,
9 pt tables/source links and Fira Code identifiers, and the existing gold/gray
palette. Headings, table continuation notices, Contents hierarchy, figure/table
directories and underlined links follow the approved layout. Short critical
phrases use true Inter Bold, not an extra font-weight increment. Code remains
regular. Continuation notices retain the existing 8.5 pt exception.
The cover title is `retroSoC Tiny` (30 pt) / `Gen1` (23 pt). The 13 pt positioning
line is `An Open-Source, Single-Core RISC-V MCU`. The Rill bilingual metadata,
version/date, author email and maintainer retain the established two-line grid.
Keep the right-hand title area empty for a logo. The Chinese label uses the
locked Noto CJK Bold and the same cap-edge compensation as the approved cover.
Available is full pale gold; Functional uses a vertical left-half fill;
Reproduced/Tapedout are empty. Marks remain manually controlled prototype status,
not a score derived from a document build or historical test count.
Every IP starts on a new page. Shared UART layouts have one canonical reference
with instance-specific address, IRQ and software qualifications. GPIO views and
DMA/PWM repeated groups preserve exact base/stride/index formulas. A constant
zero capability field must not be mislabeled reserved when it has defined
semantics, such as Tiny's truncated DMA maximum-burst discovery field.
Use native Typst/CeTZ vector structure diagrams and the existing locked waveform
renderer. Keep labels at least 9 pt; Tiny has no need to invent compact bus CDC
boxes. Explain diagram abstraction: functional control reach is not a complete
netlist, and representative waveforms are not measured cycle timings. Register
bit layouts and the source-derived 64-byte DMA diagram must match their tables.
Keep implementation, configuration, available tests, executed results and
physical qualification separate. In particular, preserve the negative timing
record and external-device qualification gaps. Record full source/asset hashes,
font/package revisions, PDF digest, page map and actual verification results.
+372
View File
@@ -0,0 +1,372 @@
// Tiny-owned layout snapshot of the approved Mini style; Mini is not imported or modified.
#let ink = rgb("#292C31")
#let gold = rgb("#A38442")
#let pale-gold = rgb("#F4EFE4")
#let gray = rgb("#F1F2F3")
#let rule = rgb("#D5D6D8")
#let muted = rgb("#62666C")
#let link-color = rgb("#245A81")
#let inline-code-color = rgb("#B12146")
#let table-stroke = 0.6pt + black
#let mono = "FiraCode Nerd Font"
#let data = json(sys.inputs.at("data"))
#let doc = data.document
#let ip-chapters = json("chapter-index.json")
// RM0486-inspired spacing, adapted to the retained Inter sizes. Inter's
// cap-height-to-baseline box is 7.639 pt at 10.5 pt and 6.548 pt at 9 pt.
// Thus these leading values yield 12.6 / 10.8 pt baseline pitches, rather
// than treating Typst's `leading` as the whole distance between baselines.
#let rhythm = (
body-leading: 4.96pt, body-spacing: 11.26pt,
small-leading: 4.25pt, small-spacing: 8.25pt,
heading-before: (28pt,24pt,18pt,12pt,10pt),
heading-after: (18pt,10pt,8pt,6pt,5pt),
heading-gap: 8pt, minor-before: 12pt, minor-after: 6pt,
register-before: 12pt, register-after: 4pt,
list-spacing: 7.96pt, list-indent: 0pt, list-body-indent: 8pt,
figure-space: 12pt, figure-caption: 6pt, table-caption: 4pt,
table-inset: (x:6pt,y:5pt), table-notes: 6pt,
continuation-size: 8.5pt, continuation-gap: 2pt,
code-space: 8pt, code-inset: 8pt,
note-space: 12pt, note-inset: 10pt,
metadata-before: 4pt, metadata-after: 6pt,
bits-space: 6pt, bits-inset: (x:3pt,y:5pt),
toc-leading: 9.09pt, toc-indent: 14pt, toc-group-before: 12pt, toc-group-after: 4pt,
header-gap: 4pt,
cover-leading: 4.96pt, cover-spacing: 3.675pt, cover-list-spacing: 7.96pt,
cover-heading-before: (12pt,12pt), cover-heading-after: 6pt,
cover-gap: 3pt, cover-meta-gap: 4pt, cover-rule-gap: 2pt,
cover-gutter: 7mm, cover-list-indent: 0.5em,
diagram-leading: 0.4em, inventory-leading: 0.35em,
)
#let heading-layout(it, before: none, after: none) = {
if it.outlined and it.numbering != none and it.level <= 2 {
context {
let preceding = query(selector(heading).before(it.location(),inclusive:false)).filter(h=>h.outlined and h.numbering!=none)
let chapters = preceding.filter(h=>h.level==1)
let product-brief = if it.level==1 {it.body==[Product Brief]}
else {chapters.len()>0 and chapters.last().body==[Product Brief]}
// Chapter openers and their first section form one heading group.
// Subsequent sections break independently, even when their text is short.
let chapter-opener = it.level==2 and preceding.len()>0 and preceding.last().level==1
if not product-brief and not chapter-opener {pagebreak(weak:true)}
}
}
let index = calc.min(it.level - 1,4)
let sizes = (21pt,16pt,13pt,11pt,10.5pt)
block(above: if before == none {rhythm.heading-before.at(index)} else {before},
below: if after == none {rhythm.heading-after.at(index)} else {after}, sticky:true)[
#set text(size:sizes.at(index),weight:"semibold")
#set par(leading:rhythm.body-leading,spacing:0pt)
#if it.numbering == none { it.body } else {
grid(columns:(auto,1fr),column-gutter:rhythm.heading-gap,
text(fill:gold,counter(heading).display(it.numbering)),it.body)
}
]
}
#let minor-title(body) = block(above:rhythm.minor-before,below:rhythm.minor-after,sticky:true,strong(body))
// Only this editing round emits active ranges; earlier markers remain historical.
#let current-change(id) = id.starts-with(doc.change_prefix) or id in (
"contents","table-directory","figure-directory")
#let change-start(id, title, category:"modified") = context {
if current-change(id) {metadata((
kind:"publication-change-start",id:id,title:title,category:category,page:here().page()))}
}
#let change-end(id) = context {
if current-change(id) {metadata((kind:"publication-change-end",id:id,page:here().page()))}
}
#let code-block(body, breakable:false) = block(above:rhythm.code-space,below:rhythm.code-space,
width:100%,inset:rhythm.code-inset,fill:gray,breakable:breakable)[
#set par(leading:rhythm.small-leading,spacing:rhythm.small-spacing)
#body
]
#let artifact-mark(state) = {
assert(state in ("empty", "left-half", "full"), message:"Unknown artifact mark")
box(width:3mm,height:3mm)[
#place(top + left,circle(radius:1.5mm,fill:white,stroke:none))
#if state=="left-half" {
place(top + left,box(width:1.5mm,height:3mm,clip:true)[
#circle(radius:1.5mm,fill:pale-gold,stroke:none)
])
} else if state=="full" {
place(top + left,circle(radius:1.5mm,fill:pale-gold,stroke:none))
}
#place(top + left,circle(radius:1.5mm,fill:none,
stroke:0.6pt + if state=="empty" {ink} else {gold}))
]
}
#let artifact-evaluation(states) = rect(width:92mm,height:10mm,radius:1.5mm,
fill:white,stroke:0.6pt + ink,inset:(x:2mm,y:1mm))[
#set text(size:9pt,weight:"regular",fill:ink)
#set par(leading:rhythm.small-leading,spacing:0pt)
#align(center + horizon)[
#grid(columns:(1fr,1fr,1fr,1fr),row-gutter:2pt,align:center,
..states.map(state=>state.name),
..states.map(state=>artifact-mark(state.fill)),
)
]
]
#let cover(body, evaluation:none) = {
show heading: it => {
if it.level==1 and it.body==[Product Brief] and evaluation!=none {
block(width:100%,above:rhythm.cover-heading-before.first(),
below:rhythm.cover-heading-after,sticky:true)[
#grid(columns:(1fr,92mm),column-gutter:4mm,row-gutter:2pt,
align(horizon,heading-layout(it,before:0pt,after:0pt)),
artifact-evaluation(evaluation),
[], align(center,text(9pt,weight:"regular",fill:black)[Artifact Evaluation]),
)
]
} else {
heading-layout(it,
before:rhythm.cover-heading-before.at(calc.min(it.level - 1,1)),
after:rhythm.cover-heading-after)
}
}
body
}
#let omit-link-underline(it) = it.body
#let repository-footer-link() = context {
show underline: omit-link-underline
show link: set text(fill:link-color,weight:"regular")
let body=text(font:"Inter",size:9pt,weight:"regular",fill:link-color)[https://github.com/retroSoC/retroSoC]
link("https://github.com/retroSoC/retroSoC",body)
}
#let contents(depth:5) = {
set text(size:9.5pt,fill:ink)
show link: set text(fill:ink)
show underline: omit-link-underline
set par(leading:rhythm.toc-leading,spacing:rhythm.toc-leading)
set outline(indent:rhythm.toc-indent)
show outline.entry: it => context {
let peers = query(heading).filter(h=>h.outlined and h.level==it.level and h.numbering!=none)
let widths = peers.map(h=>measure(text(weight:if it.level==1 {"bold"} else {"regular"},
numbering(h.numbering,..counter(heading).at(h.location())))).width)
let prefix-width = calc.max(0pt,..widths)
let prefix = if it.prefix()==none {none} else if it.level==1 {strong(it.prefix())} else {it.prefix()}
// Scope emphasis by the section ancestor, not by a hard-coded chapter number.
let ancestors = query(selector(heading).before(it.element.location(),inclusive:true)).filter(h=>h.level<=2)
let peripheral = ancestors.len()>0 and ancestors.last().body==[Peripherals]
let inner = if it.level==1 {
[#strong(it.body())#h(0.5em)#box(width:1fr,it.fill)#h(0.5em)#it.page()]
} else if peripheral {
// Keep parentheses, separators and qualifiers such as "partial" regular.
show regex("\\([^()]*\\)"): group => {
show regex("\\b[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*\\b"): strong
group
}
it.inner()
} else {it.inner()}
block(above:rhythm.toc-leading + if it.level==1 {rhythm.toc-group-before} else {0pt},
below:rhythm.toc-leading + if it.level==1 {rhythm.toc-group-after} else {0pt},breakable:false)[
#link(it.element.location(),it.indented(
if prefix==none {none} else {box(width:prefix-width,prefix)},inner,gap:rhythm.heading-gap))
]
}
outline(title:[Contents],depth:depth)
}
#let figure-directory(tables:false) = context {
set text(font:"Inter",size:9.5pt,fill:ink,weight:"regular")
show link: set text(fill:ink)
show underline: omit-link-underline
set par(leading:rhythm.toc-leading,spacing:rhythm.toc-leading)
set outline(indent:0pt)
show outline: set heading(bookmarked:true)
// Include every numbered non-table figure, including native vector diagrams.
// Continuation notices and unnumbered register layouts are not figure entries.
let included(f) = (f.kind==table)==tables and f.numbering!=none and f.caption!=none
let prefix(entry) = [#(entry.prefix()).]
let peers = query(figure).filter(included)
let widths = peers.map(f=>measure(prefix(outline.entry(1,f))).width)
let prefix-width = calc.max(0pt,..widths)
show outline.entry: it => {
if included(it.element) {
block(above:rhythm.toc-leading,below:rhythm.toc-leading,breakable:false)[
#link(it.element.location(),it.indented(
box(width:prefix-width,prefix(it)),it.inner(),gap:rhythm.heading-gap))
]
}
}
outline(title:if tables {[List of tables]} else {[List of figures]},target:figure)
}
#let inline-code(body) = context {
// Link scopes already select link-color; keep that semantic cue and underline.
set text(fill:if text.fill==link-color {link-color} else {inline-code-color})
body
}
#let code(value) = inline-code(text(font: mono, size: 9pt, weight:"regular",
value.replace("_", "_\u{200b}").replace(".", ".\u{200b}")))
#let source(path, title: "Interface and implementation reference", line:none) = {
// Publication metadata can postdate the frozen hardware commit. Route readers
// to the delivered manifest instead of constructing a nonexistent old blob.
if path.starts-with("publications/") {
return link(<publication-provenance>,text(size:9pt,title))
}
let managed = data.managed_sources.find(s=>path.starts-with(s.destination + "/"))
let url = if managed == none {
"https://github.com/retroSoC/retroSoC/blob/" + doc.source_revision + "/" + path
} else {
managed.url.trim(".git", at:end) + "/blob/" + managed.revision + "/" + path.slice(managed.destination.len()+1)
}
if line!=none { url += "#L"+str(line) }
link(url, text(size: 9pt, title))
}
#let source-note(path, title:"Interface and implementation reference", line:none) = block(
above:rhythm.metadata-before,below:rhythm.metadata-after,breakable:false)[
#set par(leading:rhythm.small-leading,spacing:rhythm.small-spacing)
#source(path,title:title,line:line)
]
#let note(body, title: "Integration note", below: rhythm.note-space) = block(
width: 100%, inset: rhythm.note-inset, fill: pale-gold, stroke: (left: 2pt + gold),
above: rhythm.note-space, below: below, breakable: false,
)[#text(weight: "semibold", title) #linebreak() #body]
#let tbd(body) = note(body, title: "TBD - specification not available")
#let placeholder(title, height: 35mm) = block(
width: 100%, height: height, stroke: 0.7pt + rule, fill: gray,
inset: rhythm.note-inset, above: rhythm.note-space, below: rhythm.note-space, breakable: false,
)[#align(center + horizon)[#text(weight: "semibold", title) \
#text(9pt, fill: muted)[TBD - awaiting reviewed engineering data]]]
// Frame each row at its outer edges without adding internal vertical rules.
// Repeated header rows supply the top edge on every continued table segment.
#let table-rules(columns, header-row:0) = (x,y) => (
top: if y==header-row {table-stroke} else {none},
bottom: table-stroke,
left: if x==0 {table-stroke} else {none},
right: if x==columns - 1 {table-stroke} else {none},
)
#let continuation-notice() = layout(size => {
let body = block(width:100%,above:0pt,below:0pt)[
#set text(size:rhythm.continuation-size,weight:"regular",fill:black,
top-edge:"ascender",bottom-edge:"descender")
#set par(leading:4pt,spacing:0pt)
(continued)
]
let extent = measure(body,width:size.width)
block(width:100%,above:rhythm.table-caption,below:0pt,
inset:(bottom:rhythm.continuation-gap))[
#block(width:extent.width,height:extent.height,above:0pt,below:0pt,body)<table-continuation-region>
]
})
#let ds-table(key, caption, headers, rows, widths: auto, notes: none) = {
let anchor = label("start-" + key)
figure(
kind: table, supplement: [Table], caption: caption,
block(breakable: rows.len() > 8, width: 100%)[
#metadata(none)#anchor
#set text(size: 9pt)
#set par(leading: rhythm.small-leading, spacing: rhythm.small-spacing)
#table(
columns: if widths == auto { headers.len() } else { widths },
inset: rhythm.table-inset,
stroke: table-rules(headers.len(),header-row:1),
fill: (x, y) => if y == 1 { gray } else { none },
align: left + horizon,
table.header(repeat: true,
table.cell(colspan: headers.len(), inset: 0pt,
stroke:(top:none,bottom:table-stroke,left:none,right:none),fill: white)[
#context if here().page() > query(anchor).first().location().page() {
continuation-notice()
}
],
..headers.map(h => table.cell(fill: gray, text(weight: "semibold", h))),
),
..rows.map(row => row.map(v => table.cell(breakable: false, v))).flatten(),
)
#if notes != none { block(above: rhythm.table-notes, breakable: false, text(9pt, fill: muted, notes)) }
],
)
}
#let ip(id) = {
let entry = data.catalog.find(e => e.id == id)
let windows = data.regions.filter(r => r.symbol in entry.regions)
let irqs = data.interrupts.filter(i => i.name in entry.irqs)
block(above: rhythm.metadata-before, below: rhythm.metadata-after, breakable: false, sticky: true)[
#entry.summary
#set text(size: 9pt)
#set par(leading: rhythm.small-leading, spacing: rhythm.small-spacing)
#table(columns: (1.3fr, 1fr, 0.7fr), inset: rhythm.table-inset,
stroke: table-rules(3),
table.header(..([Address window],[Base address],[Size]).map(h=>
table.cell(fill:gray,text(weight:"semibold",h)))),
..windows.map(r => (code(r.symbol), code(r.base_hex), r.size_label)).flatten(),
)
#block(above: rhythm.metadata-before)[
*Core IRQ bit:* #if irqs.len() == 0 { [No dedicated source.] } else {
irqs.map(i => str(i.core_bit) + " (" + i.name.replace("_", " ") + ")").join("; ")
}
]
#for (n, p) in entry.sources.enumerate() {
source(p, title: if n == 0 { "Detailed contract / source" } else { "Additional source" })
if n < entry.sources.len() - 1 { [ · ] }
}
]
}
#let template(body) = {
set document(title: doc.title, author: doc.author,
keywords: ("retroSoC", "Tiny", "Gen1", "datasheet", doc.document_id, "v"+doc.version, doc.status))
set text(font: "Inter", size: 10.5pt, fill: ink, lang: "en", weight: "regular")
show strong: it => text(weight:"bold",it.body)
set par(justify: false, leading: rhythm.body-leading, spacing: rhythm.body-spacing,first-line-indent:0pt)
set list(indent:rhythm.list-indent,body-indent:rhythm.list-body-indent,spacing:rhythm.list-spacing)
set enum(indent:rhythm.list-indent,body-indent:rhythm.list-body-indent,spacing:rhythm.list-spacing)
set page(paper: "a4", margin: (x: 19mm, top: 21mm, bottom: 20mm),
header: context {
let hs = query(heading.where(level: 1)).filter(h => h.location().page() <= here().page())
// Heading locations avoid the extra layout pass needed by page-valued metadata.
// A later chapter/section heading closes the preceding IP's header scope.
let navigation=query(selector(heading.where(level:1)).or(heading.where(level:2),
..ip-chapters.map(chapter=>label(chapter.id))))
.filter(h=>h.location().page()<=here().page())
let active=if navigation.len()==0 {none} else {
ip-chapters.find(chapter=>label(chapter.id)==navigation.last().fields().at("label",default:none))
}
set text(size: 9pt, fill: muted)
grid(columns: (1fr, 1fr), [#doc.brand_name · #doc.title], align(right)[
#if active!=none { upper(active.id) } else if hs.len() > 0 { hs.last().body } else { [Product datasheet] }
])
v(rhythm.header-gap)
line(length: 100%, stroke: 0.6pt + gold)
},
footer: context {
set text(size: 9pt, fill: muted)
set par(leading:0pt,spacing:0pt)
line(length: 100%, stroke: 0.35pt + rule)
v(rhythm.header-gap)
grid(columns: (1fr, auto, 1fr),
repository-footer-link(),
[#counter(page).display() / #counter(page).final().first()],
align(right)[#doc.date],
)
},
)
set heading(numbering: "1.1.1.1.1", outlined: true)
show heading: heading-layout
show raw: set text(font: mono, size: 9pt, weight:"regular")
show raw.where(block:false): inline-code
show link: set text(fill:link-color)
show link: it => underline(stroke:0.4pt + link-color,offset:2pt,it)
show ref: it => {
if it.element!=none and it.element.func()==heading and it.form=="normal" {
set text(fill:link-color)
show underline: omit-link-underline
it
} else {it}
}
set figure(gap: rhythm.figure-caption)
show figure: set block(above:rhythm.figure-space,below:rhythm.figure-space,breakable:false)
set figure.caption(position:top)
show figure.where(kind: table): set figure(gap:rhythm.table-caption)
show figure.where(kind: table): set block(breakable: true)
show figure.caption: set text(size: 9pt, fill: black)
show figure.caption: it => block(above:0pt,below:0pt,sticky:it.position==top,it)
body
}
+17
View File
@@ -0,0 +1,17 @@
{
"title": "retroSoC Tiny Gen1",
"document_id": "RS-TINY-DS",
"brand_name": "Rill",
"brand_name_zh": "清溪",
"version": "0.1",
"status": "DRAFT",
"date": "2026-09-25",
"author": "Yuchi Miao",
"author_email": "miaoyuchi@ict.ac.cn",
"maintainer": "ECOS Team, ICT, CAS",
"source_revision": "9c706ba0ff1da1028c299cfff573010a86dfd7ce",
"profile": "configs/ci/ihp130-tiny.mk",
"change_prefix": "tiny-v01-",
"entrypoint": "publications/datasheets/tiny/datasheet.typ",
"filename": "retrosoc-tiny-gen1-datasheet.pdf"
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,56 @@
#import "@preview/jogs:0.2.4": compile-js, call-js-function
#import "style.typ": ink, gold, muted, data, rhythm
#let wave-bytecode=compile-js(read(data.wave_renderer))
// Thread the palette index through nested WaveDrom groups without mutating
// the source dictionary. Codes 2-9 and '=' differ only in bus fill color.
#let color-signals(signals, start: 0) = {
let next = start
let colored = ()
for signal in signals {
if type(signal) == array {
let (group, index) = color-signals(signal, start: next)
colored.push(group)
next = index
} else if type(signal) == dictionary and signal.at("wave", default: "").contains(regex("[=2-9]")) {
let row = signal
row.wave = row.wave.replace(regex("[=2-9]"), str(3 + calc.rem(next, 4)))
colored.push(row)
next += 1
} else {
colored.push(signal)
}
}
(colored, next)
}
#let color-wave-source(source) = {
let colored = source
if "signal" in source { colored.signal = color-signals(source.signal).first() }
colored
}
// Keep the pinned SVG renderer and its geometry; override only valid bus fills.
// Signal names and edges remain readable independently of color.
#let wave-svg(key) = {
let entry = data.waveforms.at(key)
let svg = call-js-function(wave-bytecode,"wavy",json.encode(color-wave-source(entry.source)))
svg = svg.replace(regex("font-family:[^;]+;"), "font-family:Inter;")
svg = svg.replace(regex("font-family=\"[^\"]+\""), "font-family=\"Inter\"")
svg = svg.replace(regex("(?i)#(?:0041c4|0000ff|00f)\\b"), "#292C31")
svg = svg.replace("fill:blue", "fill:#292C31")
svg.replace("</style>", "text{font-weight:400}.info{font-weight:400;font-style:normal}.s8{fill:#F4EFE4}.s9{fill:#E7EFF6}.s10{fill:#E8F1EA}.s11{fill:#EEEAF5}</style>")
}
#let timing(key, caption, instance:none) = {
let entry = data.waveforms.at(key)
block(width:100%,above:rhythm.figure-space,below:rhythm.figure-space,breakable:false)[
#context metadata((kind:"tiny-diagram",id:"wave-"+if instance==none {key} else {instance},page:here().page()))
#show figure: set block(above:0pt,below:0pt)
#figure(image(bytes(wave-svg(key))), caption: caption)
#block(width:100%,above:rhythm.metadata-before,below:0pt)[
#set par(leading:rhythm.small-leading,spacing:rhythm.small-spacing)
#align(center,text(9pt,fill:muted,entry.note))
]
]
}
+10
View File
@@ -0,0 +1,10 @@
#include <retrosoc/core/archinfo.h>
rs_status_t example_archinfo(rs_archinfo_t *info)
{
rs_status_t result = rs_archinfo_read(info);
if (result != RS_OK) {
return result;
}
return rs_archinfo_validate_build(info);
}
+6
View File
@@ -0,0 +1,6 @@
#include <retrosoc/hal/clint.h>
rs_status_t example_clint(uint64_t *time)
{
return rs_clint_get_time(time);
}
+10
View File
@@ -0,0 +1,10 @@
#include <retrosoc/hal/dma.h>
rs_status_t example_dma(uint32_t channel, const rs_dma_config_t *config)
{
rs_status_t result = rs_dma_configure(channel, config);
if (result != RS_OK) {
return result;
}
return rs_dma_start(channel);
}
+6
View File
@@ -0,0 +1,6 @@
#include <retrosoc/hal/gpio.h>
rs_status_t example_gpio(uint32_t pin, const rs_gpio_config_t *config)
{
return rs_gpio_configure(pin, config);
}
+6
View File
@@ -0,0 +1,6 @@
#include <retrosoc/hal/i2c.h>
rs_status_t example_i2c(rs_i2c_bus_t bus, uint32_t source_clock_hz, uint32_t bus_hz)
{
return rs_i2c_init(bus, source_clock_hz, bus_hz);
}
+6
View File
@@ -0,0 +1,6 @@
#include <retrosoc/hal/pwm.h>
rs_status_t example_pwm(void)
{
return rs_pwm_probe();
}
+6
View File
@@ -0,0 +1,6 @@
#include <retrosoc/hal/rtc.h>
rs_status_t example_rtc(void)
{
return rs_rtc_probe();
}
Loaded 100 of 253 files, more files were not shown because too many files have changed in this diff. Show more