feat(spi): implement DMA V2.2 paced FIFO integration

Add five-bit SPI DMA request support, capability discovery, source-qualified pacing, descriptor cancellation guards, and focused P2 evidence. Keep Tiny and Mini product instances default-disabled until product integration.

Validation: focused SPI-P2, DMA parity, SPI parity, and SPI-P1 checks passed (53 passed, 10 deselected).

Full product integration, post-edit full Pytest, and physical qualification remain pending.
This commit is contained in:
maksyuki committed 2026-10-21 09:16:07 +08:00
1 parent 4b3553faa7
commit 3b9b83657a
28 files changed
+807 -85

No files matched your search

+5
View File
@@ -541,6 +541,11 @@ spi-p1-lint spi-p1-rtl spi-p1-formal spi-p1-synth spi-p1-netsim spi-p1-sta spi-p
$(if $(SPI_P1_COMMON_ROOT),--common-variant-root $(SPI_P1_COMMON_ROOT),) \
$(if $(filter spi-p1-formal,$@),$(if $(SPI_P1_FORMAL_REUSE_ROOT),--formal-reuse-root $(SPI_P1_FORMAL_REUSE_ROOT),),)
.PHONY: spi-p2-lint spi-p2-rtl spi-p2-report
spi-p2-lint spi-p2-rtl spi-p2-report: manifest
$(PYTHON) $(ROOT_PATH)/scripts/spi_p2.py $(patsubst spi-p2-%,%,$@) \
--variant-root $(VARIANT_ROOT) --pdk $(PDK)
.PHONY: crypto-p0-constants crypto-p0-rtl crypto-p0-baseline crypto-p0-report
crypto-p0-constants: manifest
$(PYTHON) $(ROOT_PATH)/scripts/crypto_p0.py constants --variant-root $(VARIANT_ROOT)
+4
View File
@@ -41,6 +41,8 @@ typedef enum {
RS_DMA_REQUEST_DVP_RX = 11,
RS_DMA_REQUEST_CRYPTO_IN = 12,
RS_DMA_REQUEST_CRYPTO_OUT = 13,
RS_DMA_REQUEST_SPI_TX = 16,
RS_DMA_REQUEST_SPI_RX = 17,
} rs_dma_request_t;
typedef enum {
@@ -135,10 +137,12 @@ rs_status_t rs_dma_session_release(rs_dma_session_t *session);
#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_REQUEST_HIGH UINT32_C(0x02000000)
#define RS_DMA_TCD_PRIORITY_SHIFT 16U
#define RS_DMA_TCD_BURST_SHIFT 20U
rs_status_t rs_dma_config_validate(uint32_t channel, const rs_dma_config_t *config);
rs_status_t rs_dma_supports_request(rs_dma_request_t request);
rs_status_t rs_dma_configure(uint32_t channel, const rs_dma_config_t *config);
rs_status_t rs_dma_tcd_validate(uint32_t channel, const rs_dma_tcd_t *tcd);
rs_status_t rs_dma_submit_tcd(uint32_t channel, rs_dma_tcd_t *tcd, rs_timeout_t timeout);
+2
View File
@@ -15,6 +15,8 @@
#define RS_DMA_REG_IRQ_TEST UINT32_C(0x01C)
#define RS_DMA_REG_ERROR_SUMMARY UINT32_C(0x020)
#define RS_DMA_REG_REQUEST_STATUS UINT32_C(0x024)
#define RS_DMA_REG_SUPPORTED_REQUESTS UINT32_C(0x028)
#define RS_DMA_IP_VERSION_V22 UINT32_C(0x00020002)
#define RS_DMA_CH_BASE UINT32_C(0x100)
#define RS_DMA_CH_STRIDE UINT32_C(0x080)
+29
View File
@@ -44,11 +44,37 @@ static bool rs_dma_channel_valid(uint32_t channel) {
return channel < RS_DMA_CHANNEL_COUNT;
}
rs_status_t rs_dma_supports_request(rs_dma_request_t request) {
uint32_t version;
uint32_t supported;
uint32_t bit;
if ((uint32_t)request < 16U) {
return RS_OK;
}
if ((uint32_t)request >= 32U) {
return RS_ENOTSUP;
}
version = RS_DMA_REG(RS_DMA_REG_IP_VERSION);
if (version < RS_DMA_IP_VERSION_V22) {
return RS_ENOTSUP;
}
supported = RS_DMA_REG(RS_DMA_REG_SUPPORTED_REQUESTS);
bit = UINT32_C(1) << (uint32_t)request;
return ((supported & bit) != UINT32_C(0)) ? RS_OK : RS_ENOTSUP;
}
static rs_status_t rs_dma_configure_owned(uint32_t channel, const rs_dma_config_t *config,
const rs_dma_session_t *owner) {
uint32_t channel_config;
uint32_t saved;
if ((channel >= RS_DMA_CHANNEL_COUNT) || (config == NULL)) {
return RS_EINVAL;
}
if (rs_dma_supports_request(config->request) != RS_OK) {
return RS_ENOTSUP;
}
if (rs_dma_config_validate(channel, config) != RS_OK) {
return RS_EINVAL;
}
@@ -207,6 +233,9 @@ rs_status_t rs_dma_submit_tcd_chain(uint32_t channel, rs_dma_tcd_t *first, uint3
}
config.kind = (rs_dma_transfer_kind_t)((control >> RS_DMA_TCD_KIND_SHIFT) & UINT32_C(0x7));
config.request = (rs_dma_request_t)((control >> RS_DMA_TCD_REQUEST_SHIFT) & UINT32_C(0xF));
if ((control & RS_DMA_TCD_REQUEST_HIGH) != UINT32_C(0)) {
config.request = (rs_dma_request_t)((uint32_t)config.request | UINT32_C(0x10));
}
config.source = (uintptr_t)current->source;
config.destination = (uintptr_t)current->destination;
config.byte_count = current->byte_count;
+37 -5
View File
@@ -19,13 +19,24 @@ static bool rs_dma_address_valid(uintptr_t address, uint32_t byte_count, bool in
static bool rs_dma_request_valid(const rs_dma_config_t *config) {
#ifdef RS_SOC_TINY
if (((uint32_t)config->request >= 16U) ||
((UINT32_C(0x39FF) & (UINT32_C(1) << (uint32_t)config->request)) == 0U)) {
const uint32_t request_mask = UINT32_C(0x000339FF);
if (((uint32_t)config->request >= 32U) ||
((request_mask & (UINT32_C(1) << (uint32_t)config->request)) == 0U)) {
return false;
}
#endif
switch (config->kind) {
case RS_DMA_KIND_MM_TO_MM:
#ifdef RS_SOC_TINY
if ((config->request == RS_DMA_REQUEST_SPI_TX) ||
(config->request == RS_DMA_REQUEST_SPI_RX)) {
return ((config->byte_count % sizeof(uint32_t)) == 0U) &&
(((config->request == RS_DMA_REQUEST_SPI_TX) && config->source_increment &&
!config->destination_increment) ||
((config->request == RS_DMA_REQUEST_SPI_RX) && !config->source_increment &&
config->destination_increment));
}
#endif
return (config->request == RS_DMA_REQUEST_SOFTWARE) ||
((config->request >= RS_DMA_REQUEST_XPI_TX) &&
(config->request <= RS_DMA_REQUEST_I2C1_RX));
@@ -96,14 +107,18 @@ 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);
request = (tcd->control >> RS_DMA_TCD_REQUEST_SHIFT) & UINT32_C(0xF);
if ((tcd->control & RS_DMA_TCD_REQUEST_HIGH) != UINT32_C(0)) {
request |= UINT32_C(0x10);
}
burst = (tcd->control >> RS_DMA_TCD_BURST_SHIFT) & UINT32_C(0x1F);
#ifdef RS_SOC_TINY
if ((UINT32_C(0x39FF) & (UINT32_C(1) << request)) == 0U) {
const uint32_t request_mask = UINT32_C(0x000339FF);
if ((request >= 32U) || ((request_mask & (UINT32_C(1) << request)) == 0U)) {
return RS_ENOTSUP;
}
#endif
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_SPI_RX) || (burst > RS_DMA_MAX_BURST_BEATS) ||
((tcd->source & UINT32_C(0x3)) != UINT32_C(0)) ||
((tcd->destination & UINT32_C(0x3)) != UINT32_C(0))) {
return RS_EINVAL;
@@ -113,11 +128,28 @@ rs_status_t rs_dma_tcd_validate(uint32_t channel, const rs_dma_tcd_t *tcd) {
return RS_EINVAL;
}
if ((kind == (uint32_t)RS_DMA_KIND_MM_TO_MM) &&
!((request == (uint32_t)RS_DMA_REQUEST_SOFTWARE) ||
!(
#ifdef RS_SOC_TINY
(request == (uint32_t)RS_DMA_REQUEST_SPI_TX) ||
(request == (uint32_t)RS_DMA_REQUEST_SPI_RX) ||
#endif
(request == (uint32_t)RS_DMA_REQUEST_SOFTWARE) ||
((request >= (uint32_t)RS_DMA_REQUEST_XPI_TX) &&
(request <= (uint32_t)RS_DMA_REQUEST_I2C1_RX)))) {
return RS_EINVAL;
}
#ifdef RS_SOC_TINY
if ((kind == (uint32_t)RS_DMA_KIND_MM_TO_MM) && (request == (uint32_t)RS_DMA_REQUEST_SPI_TX) &&
(((tcd->control & RS_DMA_TCD_SRC_INC) == UINT32_C(0)) ||
((tcd->control & RS_DMA_TCD_DST_INC) != UINT32_C(0)))) {
return RS_EINVAL;
}
if ((kind == (uint32_t)RS_DMA_KIND_MM_TO_MM) && (request == (uint32_t)RS_DMA_REQUEST_SPI_RX) &&
(((tcd->control & RS_DMA_TCD_SRC_INC) != UINT32_C(0)) ||
((tcd->control & RS_DMA_TCD_DST_INC) == UINT32_C(0)))) {
return RS_EINVAL;
}
#endif
if ((kind == (uint32_t)RS_DMA_KIND_MM_TO_STREAM) &&
(!((request == (uint32_t)RS_DMA_REQUEST_I2S_TX) ||
(request == (uint32_t)RS_DMA_REQUEST_CRYPTO_IN)) ||
+67
View File
@@ -0,0 +1,67 @@
# SPI-P2 DMA V2.2 Paced FIFO Integration
This document records the SPI-P2 implementation boundary from [spi.md](spi.md)
and [dma.md](dma.md). The shared DMA source now understands the V2.2 request
encoding and support-register shape, while committed Tiny and Mini product
instances remain V2.0/default-disabled for SPI until full product integration.
The standalone `spi_dma_pacing` block owns source-qualified TX/RX credit
reservation, exactly-once commit and terminal retirement. A reserved credit
remains busy through the corresponding AXI response; CPU accesses, a second
channel, rebind and FIFO reset cannot consume it. The block reports binding
conflict, disabled-admission and DMA-error faults without adding a DMA master,
channel, stream endpoint, SRAM or pad.
## Implemented boundary
- `dma_pkg.sv` exposes five-bit request IDs 16/17 while preserving IDs 0..15.
- `dma_reg.sv` supports `IP_VERSION` parameterization and read-only
`SUPPORTED_REQUESTS@0x028`; request status is 32 bits.
- `dma_core.sv` accepts a 32-bit request mask and decodes TCD request as
`{control[25], control[15:12]}`. The channel state preserves all five
request bits, and SPI direct/TCD jobs enforce the fixed-MMIO increment
direction only when `EnableSpi` is enabled. Product instances retain their
explicit V2.0 masks and versions until integration enables V2.2.
- SDK headers and math/submit paths carry the high request bit. Tiny software
accepts the SPI request shape only after `rs_dma_supports_request()` reads
`IP_VERSION` and `SUPPORTED_REQUESTS`; V2.0 returns `RS_ENOTSUP` before the
unsupported offset is read. No undefined compile-time V2.2 macro is required;
Mini and other non-Tiny validation paths retain their existing rejection.
- `spi_dma_pacing.sv` is included in the standalone SPI source boundary and
feeds the SPI wrapper's existing credit lifecycle. It is not wired into a
product fabric, GPIO/RCU path or DMA request allocator.
Descriptor cancellation blocks new fetches, including a same-cycle abort,
treats an in-flight descriptor read and parse as channel ownership, clears
pending descriptor state on abort completion, and prevents an aborted descriptor
from activating. The formal harness proves these assertions, and the dedicated
TCD cancellation cover passes under the retained Mini formal evidence root.
The focused execution passed 53 SPI/DMA/parity checks, 10 SPI-P1 standalone
protocol fixtures, the existing DMA register/native tests, DMA formal
prove/BMC/cover on the Mini-compatible V2.0 configuration, and the host C
suite. The SPI-P2 lint and pacing fixture also pass under ICS55. These results
validate the ABI and pacing boundary; they do not claim product V2.2 capability
or replace the unrun full-product gates.
## Focused validation
The new `tests/test_spi_p2.py` checks the request split, five-bit channel
storage, SPI direction guards, support ABI, product default-disable policy,
stream-status disable behavior and the credit pacing fixture. `spi_dma_p2_tb` covers
admission, commit, retirement, binding collision and disabled admission. The
existing DMA register/native tests and all SPI-P1 standalone fixtures remain
affected compatibility checks.
A prior full Pytest run passed 2089 tests with one optional PDF retrieval skip;
that run predates the latest shared-DMA cancellation and five-bit-state edits.
The post-edit full Pytest campaign remains an explicit unrun gate. The latest
Mini DMA formal prove passed with the TCD abort assertions; the existing DMA
cover and dedicated TCD cancellation cover completed separately under the same
formal build root.
Use a fresh timestamp and explicit TINY/PDK selection. The P2 entrypoints are
`spi-p2-lint`, `spi-p2-rtl` and `spi-p2-report`; generated records belong below
`build/<variant>/meta/spi-p2/`. No phase acceptance, product DMA capability,
physical qualification or SPI-P3 authorization follows from the focused
fixture.
+47 -1
View File
@@ -66,6 +66,52 @@ dependency version, Git index or branch was changed. Independent read-only
review and explicit human acceptance remain required; readiness is still
`prototype` and all phase-acceptance flags remain false.
## SPI-P2 implementation checkpoint (2026-10-19)
The current `dev` commits implement the first shared-DMA V2.2 boundary:
five-bit request constants, TCD bit25 decode, parameterized IP version and
32-bit support/request status, plus the standalone SPI credit pacing block.
Committed Tiny and Mini instances retain V2.0/default-disabled SPI support;
this is deliberate compatibility evidence, not integrated SPI capability.
Focused P2 checks pass: DMA register/native tests, the SPI-P1 standalone
fixtures and six P2 checks covering request encoding, five-bit channel state,
default-disable policy, stream-status disable policy and credit admission/
commit/retirement. Descriptor
fetch/parse cancellation is guarded in the source and covered by the current
DMA abort assertions; descriptor-specific dynamic coverage remains unrun. Real SPI request ports,
source-origin transport through Tiny bridges, firmware discovery and product
routing remain later integration work. `phase_accepted` stays false and no
later phase is authorized.
Current focused evidence: 53 SPI/DMA/parity checks, DMA register/native tests,
SPI-P1 pure checks, P2 pacing lint and RTL fixtures, DMA formal prove/BMC/cover
and host C all pass. A prior pre-cancellation-fix full Pytest campaign passed
2089 tests with one optional PDF retrieval skip (`pypdf` unavailable); the full
campaign is intentionally unrun again after the latest shared-DMA source edits.
Publication programming validation now deliberately publishes only executable
DMA requests 0..15; SPI16/17 remain future V2.2 ABI constants and are not
treated as current product routes. The prior full-suite test log and JUnit are under
`build/ics55-tiny-2026-10-19-23-20-5f3eb7711d42/meta/spi-p2/validation-final/`.
The latest source-bound P2 lint/RTL records are under
`build/ics55-tiny-2026-10-20-22-23-5f3eb7711d42/meta/spi-p2/`; the compatibility
Tiny lint is under
`build/ihp130-tiny-2026-10-20-09-20-b00f46e87763/lint/verilator/`, and the
post-edit Mini DMA formal record is under
`build/ihp130-2026-10-20-09-13-c30fb9412aa3/formal/dma/`.
The latest Mini DMA formal prove/cover build is under
`build/ihp130-2026-10-20-22-44-c30fb9412aa3/formal/dma/`; the extended prove
command passed with the current stream-status RTL and TCD cancellation
assertions. The standard 120-second wrapper timed out; the dedicated TCD
cancellation cover remains passing under the retained formal root.
P2 remains incomplete for phase acceptance: full V2.2 block synthesis/netlist/
STA, Tiny/Mini firmware integration, real source-origin CDC/fabric wiring,
SPI request allocation, the full post-fix Pytest campaign and product
regressions are unrun. Product instances
retain V2.0/default-disabled SPI.
## SPI-P1 implementation checkpoint (2026-10-18, historical)
The approved standalone implementation starts from clean
@@ -286,7 +332,7 @@ configured verdict; never substitute Mini coverage for missing Tiny support.
| --- | --- |
| SPI-P0 | Documentation freeze complete; link/anchor, command dry-run, phase/package/ALT structure and whitespace checks passed on 2026-10-04 |
| SPI-P1 | Not started; standalone RTL/model, CSR mirror, BFM and synthesis evidence absent |
| SPI-P2 | Not started; V2.2 request/credit/cancellation implementation and shared compatibility evidence absent |
| SPI-P2 | Historical freeze entry; current implementation and evidence are recorded in the SPI-P2 checkpoint above |
| SPI-P3 | Not started; actual routing/source qualification/GPIO/RCU/HAL integration absent |
| SPI-P4 | Not started; panel adapter, pixel oracle, snapshot/save and performance evidence absent |
| SPI-P5 | Not started; SPI/PIO-inclusive physical, Pad/package and silicon qualification absent |
+8 -2
View File
@@ -177,9 +177,15 @@ def validate_programming(spec: dict, ids: set[str], root: Path, revision: str) -
sdk = constants((root / "crt/include/retrosoc/hal/dma.h").read_text(), "RS_DMA_REQUEST_")
rtl = constants((root / "rtl/ip/peripheral/dma_pkg.sv").read_text(), "DMA_REQUEST_", rtl=True)
routes = spec["dma_routes"]
if {r["sdk"] for r in routes} != set(sdk) or {r["rtl"] for r in routes} != set(rtl):
# Future extension request IDs are part of the shared ABI while their
# product wiring is intentionally absent from the current publication.
# Publish only the executable legacy 0..15 route inventory; later phases
# add their own source-bound route rows when the endpoint is wired.
published_sdk = {name for name, number in sdk.items() if number < 16}
published_rtl = {name for name, number in rtl.items() if number < 16}
if {r["sdk"] for r in routes} != published_sdk or {r["rtl"] for r in routes} != published_rtl:
raise ValueError("DMA route coverage mismatch")
if len(routes) != len(sdk) or len({r["sdk"] for r in routes}) != len(routes):
if len(routes) != len(published_sdk) or len({r["sdk"] for r in routes}) != len(routes):
raise ValueError("duplicate DMA request mapping")
for row in routes:
if sdk[row["sdk"]] != rtl[row["rtl"]]:
+3
View File
@@ -25,6 +25,9 @@ specification or reserved route does not add it to the current RTL/filelists.
The [SPI-P1 standalone implementation](../../docs/ip/spi-p1.md) is isolated
from product filelists. Its DMA capability remains clear; actual DMA V2.2,
Tiny routing, lifecycle and SDK integration belong to P2/P3.
The [SPI-P2 pacing boundary](../../docs/ip/spi-p2.md) adds shared-DMA request
encoding and a standalone credit block; committed products remain
default-disabled until complete cancellation and product admission evidence.
The [PPALite contract](../../docs/ip/ppalite.md) specifies a future camera-inline
PCLK processor with RAW bypass, small FIFOs and existing DVP_RX11/DMA2 output.
+17 -5
View File
@@ -14,8 +14,11 @@ module apb4_dma #(
parameter int NumChannels = 4,
parameter int MaxBurstBeats = 16,
parameter int FifoDepth = 16,
parameter logic [15:0] RequestMask = 16'h3fff,
parameter bit EnableStreams = 1'b1
parameter logic [31:0] RequestMask = 32'h0000_3fff,
parameter logic [31:0] SupportedRequests = RequestMask,
parameter logic [31:0] IpVersion = 32'h0002_0000,
parameter bit EnableStreams = 1'b1,
parameter bit EnableSpi = 1'b0
) (
// verilog_format: off -- integration ports retain established peripheral endpoint names.
output logic lifecycle_idle_o,
@@ -70,13 +73,20 @@ module apb4_dma #(
logic [$clog2(NumChannels)-1:0] s_first_err_channel;
logic [ 8:0] s_first_err_stat;
logic [ 15:0] s_first_err_addr_hi;
logic [ 15:0] s_req_stat;
logic [ 31:0] s_req_stat;
logic [ 31:0] s_supported_req;
assign s_supported_req = SupportedRequests &
(EnableSpi ? 32'h0003_ffff : 32'h0000_3fff) &
(EnableStreams ? 32'h0003_ffff :
(EnableSpi ? 32'h0003_07f9 : 32'h0000_07f9));
dma_reg #(
.NumChannels (NumChannels),
.DataWidth (DataWidth),
.MaxBurstBeats(MaxBurstBeats),
.EnableStreams(EnableStreams)
.MaxBurstBeats (MaxBurstBeats),
.EnableStreams (EnableStreams),
.IpVersion (IpVersion),
.SupportedRequests(SupportedRequests)
) u_dma_reg (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
@@ -119,11 +129,13 @@ module apb4_dma #(
.first_error_status_i (s_first_err_stat),
.first_error_addr_hi_i(s_first_err_addr_hi),
.request_status_i (s_req_stat),
.supported_requests_i (s_supported_req),
.irq_o (irq_o)
);
dma_core #(
.RequestMask (RequestMask),
.EnableSpi (EnableSpi),
.EnableStreams(EnableStreams),
.AddrWidth (AddrWidth),
.DataWidth (DataWidth),
+79 -33
View File
@@ -19,8 +19,9 @@ module dma_core #(
parameter int MaxBurstBeats = 16,
parameter int FifoDepth = 16,
parameter bit EnableCrc = 1'b1,
parameter logic [15:0] RequestMask = 16'h3fff,
parameter bit EnableStreams = 1'b1
parameter logic [31:0] RequestMask = 32'h0000_3fff,
parameter bit EnableStreams = 1'b1,
parameter bit EnableSpi = 1'b0
) (
// verilog_format: off -- channel vectors are kept aligned with the register-bank ABI.
input logic clk_i,
@@ -62,7 +63,7 @@ module dma_core #(
output logic [ChannelIndexWidth-1:0] first_error_channel_o,
output logic [8:0] first_error_status_o,
output logic [15:0] first_error_addr_hi_o,
output logic [15:0] request_status_o,
output logic [31:0] request_status_o,
output logic xpi_xfer_done_o,
dma_req_if.dut req,
axi4_if.master axi4,
@@ -86,7 +87,8 @@ module dma_core #(
logic [ NumChannels-1:0][ 31:0] s_cfg_src_addr;
logic [ NumChannels-1:0][ 31:0] s_cfg_dst_addr;
logic [ NumChannels-1:0][ 31:0] s_cfg_byte_count;
logic [ NumChannels-1:0][ 3:0] s_cfg_request;
logic [ NumChannels-1:0][ 31:0] s_cfg_req_raw;
logic [ NumChannels-1:0][ 4:0] s_cfg_req;
logic [ NumChannels-1:0][ 4:0] s_cfg_burst;
logic [ NumChannels-1:0] s_cfg_crc_enable;
logic [ NumChannels-1:0] s_cfg_crc_final;
@@ -105,7 +107,7 @@ module dma_core #(
logic [ NumChannels-1:0] s_evt_half_q;
logic [ NumChannels-1:0] s_evt_err_q;
logic [ NumChannels-1:0][ 2:0] s_kind_q;
logic [ NumChannels-1:0][ 3:0] s_req_q;
logic [ NumChannels-1:0][ 4:0] s_req_q;
logic [ NumChannels-1:0][ 1:0] s_priority_q;
logic [ NumChannels-1:0][ 4:0] s_burst_q;
logic [ NumChannels-1:0] s_src_increment_q;
@@ -155,7 +157,7 @@ module dma_core #(
logic [ NumChannels-1:0][ 31:0] s_fifo_wdata;
logic [ NumChannels-1:0][ 31:0] s_fifo_rdata;
logic [ 15:0] s_req_ready;
logic [ 31:0] s_req_ready;
logic [ NumChannels-1:0] s_start_valid;
logic [ NumChannels-1:0][ 3:0] s_start_err_code;
@@ -377,8 +379,8 @@ module dma_core #(
always_comb begin
s_req_ready = '0;
s_req_ready[DMA_REQUEST_SOFTWARE] = 1'b1;
s_req_ready[DMA_REQUEST_I2S_TX] = req.i2s_tx_proc;
s_req_ready[DMA_REQUEST_I2S_RX] = req.i2s_rx_proc;
s_req_ready[DMA_REQUEST_I2S_TX] = EnableStreams && req.i2s_tx_proc;
s_req_ready[DMA_REQUEST_I2S_RX] = EnableStreams && req.i2s_rx_proc;
s_req_ready[DMA_REQUEST_QSPI_TX] = req.qspi_tx_proc;
s_req_ready[DMA_REQUEST_QSPI_RX] = req.qspi_rx_proc;
s_req_ready[DMA_REQUEST_UART_TX] = req.uart_tx_proc;
@@ -387,9 +389,11 @@ module dma_core #(
s_req_ready[DMA_REQUEST_I2C0_RX] = req.i2c0_rx_proc;
s_req_ready[DMA_REQUEST_I2C1_TX] = req.i2c1_tx_proc;
s_req_ready[DMA_REQUEST_I2C1_RX] = req.i2c1_rx_proc;
s_req_ready[DMA_REQUEST_DVP_RX] = 1'b1;
s_req_ready[DMA_REQUEST_CRYPTO_IN] = req.crypto_in_proc;
s_req_ready[DMA_REQUEST_CRYPTO_OUT] = req.crypto_out_proc;
s_req_ready[DMA_REQUEST_DVP_RX] = EnableStreams;
s_req_ready[DMA_REQUEST_CRYPTO_IN] = EnableStreams && req.crypto_in_proc;
s_req_ready[DMA_REQUEST_CRYPTO_OUT] = EnableStreams && req.crypto_out_proc;
s_req_ready[DMA_REQUEST_SPI_TX] = EnableSpi && RequestMask[DMA_REQUEST_SPI_TX];
s_req_ready[DMA_REQUEST_SPI_RX] = EnableSpi && RequestMask[DMA_REQUEST_SPI_RX];
end
assign request_status_o = s_req_ready;
@@ -405,7 +409,8 @@ module dma_core #(
s_cfg_src_addr[channel] = src_addr_i[(channel*32)+:32];
s_cfg_dst_addr[channel] = dst_addr_i[(channel*32)+:32];
s_cfg_byte_count[channel] = byte_count_i[(channel*32)+:32];
s_cfg_request[channel] = request_sel_i[(channel*32)+:4];
s_cfg_req_raw[channel] = request_sel_i[(channel*32)+:32];
s_cfg_req[channel] = s_cfg_req_raw[channel][4:0];
s_cfg_burst[channel] = burst_cfg_i[(channel*32)+:5];
end
end
@@ -415,7 +420,8 @@ module dma_core #(
s_tcd_fetch_valid = 1'b0;
for (int unsigned channel = 0; channel < NumChannels; channel++) begin
if (s_tcd_fetch_pending_q[channel] && s_busy_q[channel] &&
!s_tcd_read_owner_valid_q && !s_axi_read_busy) begin
!s_abort_q[channel] && !abort_i[channel] && !s_err_q[channel] &&
!s_tcd_read_owner_valid_q && !s_tcd_parse_q && !s_axi_read_busy) begin
if (!s_tcd_fetch_valid || (s_priority_q[channel] >
s_priority_q[s_tcd_fetch_selected])) begin
s_tcd_fetch_selected = ChannelIndexWidth'(channel);
@@ -434,14 +440,14 @@ module dma_core #(
if ((other != channel) && s_busy_q[other]) begin
if ((s_cfg_kind[channel] == DMA_KIND_MM_TO_STREAM) &&
(s_kind_q[other] == DMA_KIND_MM_TO_STREAM) &&
(s_req_q[other] == s_cfg_request[channel]) &&
((s_cfg_request[channel] == DMA_REQUEST_I2S_TX) ||
(s_cfg_request[channel] == DMA_REQUEST_CRYPTO_IN))) begin
(s_req_q[other] == s_cfg_req[channel]) &&
((s_cfg_req[channel] == DMA_REQUEST_I2S_TX) ||
(s_cfg_req[channel] == DMA_REQUEST_CRYPTO_IN))) begin
endpoint_busy = 1'b1;
end
if ((s_cfg_kind[channel] == DMA_KIND_STREAM_TO_MM) &&
(s_kind_q[other] == DMA_KIND_STREAM_TO_MM) &&
(s_req_q[other] == s_cfg_request[channel])) begin
(s_req_q[other] == s_cfg_req[channel])) begin
endpoint_busy = 1'b1;
end
end
@@ -449,7 +455,9 @@ module dma_core #(
s_start_valid[channel] = 1'b1;
s_start_err_code[channel] = DMA_ERROR_CONFIG;
if (!RequestMask[s_cfg_request[channel]] ||
if ((s_cfg_req_raw[channel][31:5] != 27'd0) ||
(s_cfg_req[channel] > DMA_REQUEST_SPI_RX) ||
!RequestMask[s_cfg_req[channel]] ||
(!EnableStreams && (s_cfg_kind[channel] != DMA_KIND_MM_TO_MM)) ||
(s_cfg_width[channel] != DMA_WIDTH_32) || (s_cfg_byte_count[channel] == 32'd0) ||
(s_cfg_burst[channel] == 5'd0) ||
@@ -462,11 +470,19 @@ module dma_core #(
if ((s_cfg_src_addr[channel] == 32'd0) || (s_cfg_dst_addr[channel] == 32'd0) ||
(s_cfg_src_addr[channel][1:0] != 2'b00) ||
(s_cfg_dst_addr[channel][1:0] != 2'b00) ||
(s_cfg_request[channel] == DMA_REQUEST_I2S_TX) ||
(s_cfg_request[channel] == DMA_REQUEST_I2S_RX) ||
(s_cfg_request[channel] == DMA_REQUEST_DVP_RX) ||
(s_cfg_request[channel] == DMA_REQUEST_CRYPTO_IN) ||
(s_cfg_request[channel] == DMA_REQUEST_CRYPTO_OUT)) begin
(s_cfg_req[channel] == DMA_REQUEST_I2S_TX) ||
(s_cfg_req[channel] == DMA_REQUEST_I2S_RX) ||
(s_cfg_req[channel] == DMA_REQUEST_DVP_RX) ||
(s_cfg_req[channel] == DMA_REQUEST_CRYPTO_IN) ||
(s_cfg_req[channel] == DMA_REQUEST_CRYPTO_OUT) ||
((s_cfg_req[channel] >= 5'd14) &&
(s_cfg_req[channel] < DMA_REQUEST_SPI_TX)) ||
(!EnableSpi && ((s_cfg_req[channel] == DMA_REQUEST_SPI_TX) ||
(s_cfg_req[channel] == DMA_REQUEST_SPI_RX))) ||
((s_cfg_req[channel] == DMA_REQUEST_SPI_TX) &&
(!s_cfg_src_increment[channel] || s_cfg_dst_increment[channel])) ||
((s_cfg_req[channel] == DMA_REQUEST_SPI_RX) &&
(s_cfg_src_increment[channel] || !s_cfg_dst_increment[channel]))) begin
s_start_valid[channel] = 1'b0;
s_start_err_code[channel] =
((s_cfg_src_addr[channel][1:0] != 2'b00) ||
@@ -480,8 +496,8 @@ module dma_core #(
(s_cfg_src_addr[channel][1:0] != 2'b00) ||
(s_cfg_byte_count[channel][1:0] != 2'b00) ||
!s_cfg_src_increment[channel] ||
((s_cfg_request[channel] != DMA_REQUEST_I2S_TX) &&
(s_cfg_request[channel] != DMA_REQUEST_CRYPTO_IN)) || endpoint_busy) begin
((s_cfg_req[channel] != DMA_REQUEST_I2S_TX) &&
(s_cfg_req[channel] != DMA_REQUEST_CRYPTO_IN)) || endpoint_busy) begin
s_start_valid[channel] = 1'b0;
s_start_err_code[channel] =
((s_cfg_src_addr[channel][1:0] != 2'b00) ||
@@ -495,9 +511,9 @@ module dma_core #(
(s_cfg_dst_addr[channel][1:0] != 2'b00) ||
(s_cfg_byte_count[channel][1:0] != 2'b00) ||
!s_cfg_dst_increment[channel] ||
((s_cfg_request[channel] != DMA_REQUEST_I2S_RX) &&
(s_cfg_request[channel] != DMA_REQUEST_DVP_RX) &&
(s_cfg_request[channel] != DMA_REQUEST_CRYPTO_OUT)) ||
((s_cfg_req[channel] != DMA_REQUEST_I2S_RX) &&
(s_cfg_req[channel] != DMA_REQUEST_DVP_RX) &&
(s_cfg_req[channel] != DMA_REQUEST_CRYPTO_OUT)) ||
endpoint_busy) begin
s_start_valid[channel] = 1'b0;
s_start_err_code[channel] =
@@ -938,6 +954,10 @@ module dma_core #(
for (int unsigned channel = 0; channel < NumChannels; channel++) begin
s_read_owns_channel[channel] = s_read_owner_valid_q &&
(s_read_owner_q == ChannelIndexWidth'(channel));
s_read_owns_channel[channel] |= s_tcd_read_owner_valid_q &&
(s_tcd_read_owner_q == ChannelIndexWidth'(channel));
s_read_owns_channel[channel] |= s_tcd_parse_q &&
(s_tcd_read_owner_q == ChannelIndexWidth'(channel));
s_write_owns_channel[channel] = s_write_owner_valid_q &&
(s_write_owner_q == ChannelIndexWidth'(channel));
s_stream_tx_valid[channel] = s_i2s_tx_channel_valid &&
@@ -1144,7 +1164,7 @@ module dma_core #(
if (s_start_valid[channel]) begin
s_busy_q[channel] <= 1'b1;
s_kind_q[channel] <= s_cfg_kind[channel];
s_req_q[channel] <= s_cfg_request[channel];
s_req_q[channel] <= s_cfg_req[channel];
s_priority_q[channel] <= s_cfg_priority[channel];
s_burst_q[channel] <= s_cfg_burst[channel];
s_src_increment_q[channel] <= s_cfg_src_increment[channel];
@@ -1272,7 +1292,10 @@ module dma_core #(
if (s_tcd_parse_q) begin
s_tcd_parse_q <= 1'b0;
if (!RequestMask[s_tcd_word7_q[s_tcd_read_owner_q][15:12]] ||
if (s_abort_q[s_tcd_read_owner_q]) begin
s_tcd_loaded_q[s_tcd_read_owner_q] <= 1'b0;
end else if (!RequestMask[{s_tcd_word7_q[s_tcd_read_owner_q][25],
s_tcd_word7_q[s_tcd_read_owner_q][15:12]}] ||
(!EnableStreams && (s_tcd_word7_q[s_tcd_read_owner_q][10:8] != DMA_KIND_MM_TO_MM)) ||
s_err_q[s_tcd_read_owner_q] ||
(s_tcd_word7_q[s_tcd_read_owner_q][0] == 1'b0) ||
@@ -1282,7 +1305,26 @@ module dma_core #(
((s_tcd_word0_q[s_tcd_read_owner_q] != 32'd0) &&
(s_tcd_word0_q[s_tcd_read_owner_q][5:0] != 6'd0)) ||
(s_tcd_word7_q[s_tcd_read_owner_q][10:8] > DMA_KIND_STREAM_TO_MM) ||
(s_tcd_word7_q[s_tcd_read_owner_q][15:12] > DMA_REQUEST_CRYPTO_OUT) ||
({s_tcd_word7_q[s_tcd_read_owner_q][25],
s_tcd_word7_q[s_tcd_read_owner_q][15:12]} > DMA_REQUEST_SPI_RX) ||
((s_tcd_word7_q[s_tcd_read_owner_q][25] == 1'b0) &&
(s_tcd_word7_q[s_tcd_read_owner_q][15:12] >= 4'd14) &&
(s_tcd_word7_q[s_tcd_read_owner_q][10:8] == DMA_KIND_MM_TO_MM)) ||
((!EnableSpi) &&
(({s_tcd_word7_q[s_tcd_read_owner_q][25],
s_tcd_word7_q[s_tcd_read_owner_q][15:12]} == DMA_REQUEST_SPI_TX) ||
({s_tcd_word7_q[s_tcd_read_owner_q][25],
s_tcd_word7_q[s_tcd_read_owner_q][15:12]} == DMA_REQUEST_SPI_RX))) ||
((({s_tcd_word7_q[s_tcd_read_owner_q][25],
s_tcd_word7_q[s_tcd_read_owner_q][15:12]} == DMA_REQUEST_SPI_TX) &&
((s_tcd_word7_q[s_tcd_read_owner_q][10:8] != DMA_KIND_MM_TO_MM) ||
!s_tcd_word7_q[s_tcd_read_owner_q][1] ||
s_tcd_word7_q[s_tcd_read_owner_q][2])) ||
(({s_tcd_word7_q[s_tcd_read_owner_q][25],
s_tcd_word7_q[s_tcd_read_owner_q][15:12]} == DMA_REQUEST_SPI_RX) &&
((s_tcd_word7_q[s_tcd_read_owner_q][10:8] != DMA_KIND_MM_TO_MM) ||
s_tcd_word7_q[s_tcd_read_owner_q][1] ||
!s_tcd_word7_q[s_tcd_read_owner_q][2]))) ||
(((s_tcd_word7_q[s_tcd_read_owner_q][10:8] == DMA_KIND_MM_TO_STREAM) ||
(s_tcd_word7_q[s_tcd_read_owner_q][10:8] == DMA_KIND_STREAM_TO_MM)) &&
(s_tcd_word3_q[s_tcd_read_owner_q][1:0] != 2'b00)) ||
@@ -1297,7 +1339,9 @@ module dma_core #(
s_dst_base_q[s_tcd_read_owner_q] <= s_tcd_word2_q[s_tcd_read_owner_q];
s_len_q[s_tcd_read_owner_q] <= s_tcd_word3_q[s_tcd_read_owner_q];
s_kind_q[s_tcd_read_owner_q] <= s_tcd_word7_q[s_tcd_read_owner_q][10:8];
s_req_q[s_tcd_read_owner_q] <= s_tcd_word7_q[s_tcd_read_owner_q][15:12];
s_req_q[s_tcd_read_owner_q] <= {
s_tcd_word7_q[s_tcd_read_owner_q][25], s_tcd_word7_q[s_tcd_read_owner_q][15:12]
};
s_priority_q[s_tcd_read_owner_q] <= s_tcd_word7_q[s_tcd_read_owner_q][17:16];
s_burst_q[s_tcd_read_owner_q] <=
(s_tcd_word7_q[s_tcd_read_owner_q][24:20] == 5'd0)
@@ -1314,7 +1358,6 @@ module dma_core #(
: s_tcd_word9_q[s_tcd_read_owner_q];
s_stream_tx_stop_q[s_tcd_read_owner_q] <= 1'b0;
s_suspended_q[s_tcd_read_owner_q] <= 1'b0;
s_abort_q[s_tcd_read_owner_q] <= 1'b0;
s_done_q[s_tcd_read_owner_q] <= 1'b0;
s_read_issued_q[s_tcd_read_owner_q] <= '0;
s_write_issued_q[s_tcd_read_owner_q] <= '0;
@@ -1618,6 +1661,9 @@ module dma_core #(
s_abort_q[channel] <= 1'b0;
s_suspend_req_q[channel] <= 1'b0;
s_stream_tx_stop_q[channel] <= 1'b1;
s_tcd_fetch_pending_q[channel] <= 1'b0;
s_tcd_loaded_q[channel] <= 1'b0;
s_tcd_mode_q[channel] <= 1'b0;
s_err_code_q[channel] <= DMA_ERROR_ABORT;
end
end
+1
View File
@@ -22,6 +22,7 @@
`define APB4_DMA__IRQ_TEST 12'h01C
`define APB4_DMA__ERROR_SUMMARY 12'h020
`define APB4_DMA__REQUEST_STATUS 12'h024
`define APB4_DMA__SUPPORTED_REQUESTS 12'h028
`define APB4_DMA__CH_BASE 12'h100
`define APB4_DMA__CH_STRIDE 12'h080
+16 -14
View File
@@ -15,20 +15,22 @@ package dma_pkg;
localparam logic [1:0] DMA_WIDTH_32 = 2'd2;
localparam logic [3:0] DMA_REQUEST_SOFTWARE = 4'd0;
localparam logic [3:0] DMA_REQUEST_I2S_TX = 4'd1;
localparam logic [3:0] DMA_REQUEST_I2S_RX = 4'd2;
localparam logic [3:0] DMA_REQUEST_QSPI_TX = 4'd3;
localparam logic [3:0] DMA_REQUEST_QSPI_RX = 4'd4;
localparam logic [3:0] DMA_REQUEST_UART_TX = 4'd5;
localparam logic [3:0] DMA_REQUEST_UART_RX = 4'd6;
localparam logic [3:0] DMA_REQUEST_I2C0_TX = 4'd7;
localparam logic [3:0] DMA_REQUEST_I2C0_RX = 4'd8;
localparam logic [3:0] DMA_REQUEST_I2C1_TX = 4'd9;
localparam logic [3:0] DMA_REQUEST_I2C1_RX = 4'd10;
localparam logic [3:0] DMA_REQUEST_DVP_RX = 4'd11;
localparam logic [3:0] DMA_REQUEST_CRYPTO_IN = 4'd12;
localparam logic [3:0] DMA_REQUEST_CRYPTO_OUT = 4'd13;
localparam logic [4:0] DMA_REQUEST_SOFTWARE = 5'd0;
localparam logic [4:0] DMA_REQUEST_I2S_TX = 5'd1;
localparam logic [4:0] DMA_REQUEST_I2S_RX = 5'd2;
localparam logic [4:0] DMA_REQUEST_QSPI_TX = 5'd3;
localparam logic [4:0] DMA_REQUEST_QSPI_RX = 5'd4;
localparam logic [4:0] DMA_REQUEST_UART_TX = 5'd5;
localparam logic [4:0] DMA_REQUEST_UART_RX = 5'd6;
localparam logic [4:0] DMA_REQUEST_I2C0_TX = 5'd7;
localparam logic [4:0] DMA_REQUEST_I2C0_RX = 5'd8;
localparam logic [4:0] DMA_REQUEST_I2C1_TX = 5'd9;
localparam logic [4:0] DMA_REQUEST_I2C1_RX = 5'd10;
localparam logic [4:0] DMA_REQUEST_DVP_RX = 5'd11;
localparam logic [4:0] DMA_REQUEST_CRYPTO_IN = 5'd12;
localparam logic [4:0] DMA_REQUEST_CRYPTO_OUT = 5'd13;
localparam logic [4:0] DMA_REQUEST_SPI_TX = 5'd16;
localparam logic [4:0] DMA_REQUEST_SPI_RX = 5'd17;
localparam logic [3:0] DMA_ERROR_NONE = 4'd0;
localparam logic [3:0] DMA_ERROR_CONFIG = 4'd1;
+11 -6
View File
@@ -15,7 +15,9 @@ module dma_reg #(
parameter bit EnableStreams = 1'b1,
parameter int ChannelIndexWidth = (NumChannels > 1) ? $clog2(NumChannels) : 1,
parameter int DataWidth = 32,
parameter int MaxBurstBeats = 16
parameter int MaxBurstBeats = 16,
parameter logic [31:0] IpVersion = 32'h0002_0000,
parameter logic [31:0] SupportedRequests = 32'h0000_3fff
) (
// verilog_format: off -- APB4/configuration and status vector groups follow the register ABI.
input logic clk_i,
@@ -58,12 +60,12 @@ module dma_reg #(
input logic [ChannelIndexWidth-1:0] first_error_channel_i,
input logic [8:0] first_error_status_i,
input logic [15:0] first_error_addr_hi_i,
input logic [15:0] request_status_i,
input logic [31:0] request_status_i,
input logic [31:0] supported_requests_i,
output logic irq_o
// verilog_format: on
);
localparam logic [31:0] IpId = 32'h444D_4134;
localparam logic [31:0] IpVersion = 32'h0002_0000;
localparam logic [31:0] ChannelBase = {20'd0, `APB4_DMA__CH_BASE};
localparam logic [31:0] ChannelStride = {20'd0, `APB4_DMA__CH_STRIDE};
@@ -133,7 +135,8 @@ module dma_reg #(
(s_offset == `APB4_DMA__IRQ_ENABLE) ||
(s_offset == `APB4_DMA__IRQ_TEST) ||
(s_offset == `APB4_DMA__ERROR_SUMMARY) ||
(s_offset == `APB4_DMA__REQUEST_STATUS);
(s_offset == `APB4_DMA__REQUEST_STATUS) ||
(s_offset == `APB4_DMA__SUPPORTED_REQUESTS);
assign s_channel_valid = (s_offset_ext >= ChannelBase) &&
(s_offset_ext < (ChannelBase + (NumChannels * ChannelStride)));
assign s_channel_index = ChannelIndexWidth'((s_offset_ext - ChannelBase) >> 7);
@@ -176,7 +179,8 @@ module dma_reg #(
if (s_write && s_global_valid &&
((s_offset == `APB4_DMA__IP_ID) || (s_offset == `APB4_DMA__IP_VERSION) ||
(s_offset == `APB4_DMA__CAPABILITY) || (s_offset == `APB4_DMA__GLOBAL_STATUS) ||
(s_offset == `APB4_DMA__REQUEST_STATUS))) begin
(s_offset == `APB4_DMA__REQUEST_STATUS) ||
(s_offset == `APB4_DMA__SUPPORTED_REQUESTS))) begin
s_access_err = 1'b1;
end
if (s_write && s_channel_valid &&
@@ -373,7 +377,8 @@ module dma_reg #(
{{(3 - ChannelIndexWidth) {1'b0}}, first_error_channel_i},
first_error_valid_i
};
`APB4_DMA__REQUEST_STATUS: s_read_data = {16'd0, request_status_i};
`APB4_DMA__REQUEST_STATUS: s_read_data = request_status_i;
`APB4_DMA__SUPPORTED_REQUESTS: s_read_data = supported_requests_i;
default: begin
if (s_channel_valid) begin
unique case (s_channel_offset)
+34 -10
View File
@@ -1,5 +1,7 @@
// Standalone SPI-P1 shell. No product route or integrated DMA capability is implied.
module apb4_spi (
// Standalone SPI shell. DMA pacing is local; product route and fabric origin remain external.
module apb4_spi #(
parameter bit EnableDma = 1'b1
) (
input logic clk_i,
rst_n_i,
input logic [31:0] clock_hz_i,
@@ -30,15 +32,36 @@ module apb4_spi (
spi_ctrl_if u_ctrl ();
spi_pkg::configuration_t s_cfg;
logic [31:0] s_claim, s_ready;
logic s_handback, s_credit_fault;
logic s_handback, s_credit_fault, s_reg_credit_fault, s_pacing_fault;
logic s_fifo_pending;
logic [1:0] s_credit_pending;
logic [1:0] s_credit_pending, s_pacing_pending, s_reg_credit_pending;
logic [1:0] s_credit_valid, s_credit_retire, s_credit_commit;
logic [3:0] s_dma_stat;
logic s_unused;
assign s_cfg = u_ctrl.cfg;
// Only personality bits feed the GPIO guard; other fields feed the core/reg blocks.
assign s_unused = ^s_cfg;
assign s_dma_stat = dma_status_i | {s_credit_pending, 2'd0};
assign s_credit_fault = s_reg_credit_fault | s_pacing_fault;
assign s_credit_pending = s_pacing_pending | s_reg_credit_pending;
spi_dma_pacing u_dma_pacing (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (EnableDma),
.tx_binding_i (s_cfg.tx_binding),
.rx_binding_i (s_cfg.rx_binding),
.dma_start_pending_i(dma_start_pending_i),
.dma_status_i (dma_status_i),
.dma_error_i (dma_error_i),
.credit_valid_i (credit_valid_i),
.credit_retire_i (credit_retire_i),
.credit_commit_i (s_credit_commit),
.credit_valid_o (s_credit_valid),
.credit_retire_o (s_credit_retire),
.credit_pending_o (s_pacing_pending),
.dma_status_o (s_dma_stat),
.credit_fault_o (s_pacing_fault),
.busy_o ()
);
spi_gpio_guard u_gpio_guard (
.session_i (s_cfg.session),
.user_select_i (user_select_i),
@@ -59,18 +82,19 @@ module apb4_spi (
.origin_i (origin_i),
.request_i (request_i),
.channel_i (channel_i),
.credit_valid_i (credit_valid_i),
.credit_retire_i (credit_retire_i),
.credit_valid_i (s_credit_valid),
.credit_retire_i (s_credit_retire),
.dma_status_i (s_dma_stat),
.dma_start_pending_i(dma_start_pending_i),
.credit_commit_o (credit_commit_o),
.credit_pending_o (s_credit_pending),
.credit_commit_o (s_credit_commit),
.credit_pending_o (s_reg_credit_pending),
.fifo_pending_o (s_fifo_pending),
.credit_fault_o (s_credit_fault),
.credit_fault_o (s_reg_credit_fault),
.irq_o (irq_o),
.apb4 (apb4),
.ctrl (u_ctrl)
);
assign credit_commit_o = s_credit_commit;
spi_master_core u_master_core (
.apb_pending_i (s_fifo_pending),
.clk_i (clk_i),
+53
View File
@@ -0,0 +1,53 @@
// SPI-P2 source-qualified fixed-MMIO credit pacing.
// A reservation is held from DMA admission until terminal response retirement.
module spi_dma_pacing (
input logic clk_i,
rst_n_i,
input logic enable_i,
input logic [31:0] tx_binding_i,
rx_binding_i,
input logic [ 1:0] dma_start_pending_i,
input logic [ 3:0] dma_status_i,
input logic dma_error_i,
input logic [ 1:0] credit_valid_i,
credit_retire_i,
input logic [ 1:0] credit_commit_i,
output logic [ 1:0] credit_valid_o,
credit_retire_o,
output logic [ 1:0] credit_pending_o,
output logic [ 3:0] dma_status_o,
output logic credit_fault_o,
busy_o
);
logic [1:0] s_reserved_d, s_reserved_q;
logic s_binding_conflict;
logic s_unused;
assign s_binding_conflict = tx_binding_i[31] && rx_binding_i[31] &&
(tx_binding_i[2:0] == rx_binding_i[2:0]);
assign s_unused = ^{tx_binding_i[30:3], rx_binding_i[30:3]};
assign credit_valid_o = enable_i ? credit_valid_i & ~s_reserved_q : 2'd0;
assign credit_retire_o = credit_retire_i;
assign credit_pending_o = s_reserved_q | credit_valid_o;
assign dma_status_o = dma_status_i | {credit_pending_o, 2'd0};
assign busy_o = (dma_status_o != 4'd0) || (dma_start_pending_i != 2'd0) ||
(credit_pending_o != 2'd0);
assign credit_fault_o = (!enable_i && ((credit_valid_i != 2'd0) ||
(credit_commit_i != 2'd0))) || s_binding_conflict || dma_error_i || (s_unused && 1'b0);
always_comb begin
s_reserved_d = s_reserved_q;
s_reserved_d = s_reserved_d & ~credit_retire_i;
s_reserved_d = s_reserved_d | credit_commit_i;
if (!enable_i && (credit_retire_i == 2'd0)) s_reserved_d = '0;
end
dffr #(
.DATA_WIDTH(2)
) u_reserved (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_reserved_d),
.dat_o (s_reserved_q)
);
endmodule
+52 -6
View File
@@ -49,6 +49,13 @@ module dma_formal_design (
output logic [ 1:0] tx_channel,
output logic [ 31:0] tx_channel_bytes_done,
output logic [ 31:0] tx_channel_len,
output logic [ 1:0] scenario,
output logic tcd_mode,
output logic tcd_read_owner_valid,
output logic tcd_parse,
output logic tcd_loaded,
output logic tcd_fetch_pending,
output logic tcd_abort,
output logic awvalid,
output logic awready,
output logic awid,
@@ -134,6 +141,8 @@ module dma_formal_design (
logic [NumChannels*32-1:0] s_byte_count_i;
logic [NumChannels*32-1:0] s_request_sel_i;
logic [NumChannels*32-1:0] s_burst_cfg_i;
logic [NumChannels*32-1:0] s_tcd_head_i;
logic [NumChannels*32-1:0] s_tcd_count_i;
logic [ NumChannels-1:0] s_start_i;
logic [ NumChannels-1:0] s_abort_i;
logic [ 5:0] s_cycle_q;
@@ -161,10 +170,23 @@ module dma_formal_design (
logic s_write_error_q;
logic s_tx_stalled_once_q;
logic s_tcd_read_q;
function automatic logic [31:0] tcd_word(input logic [4:0] beat_i);
unique case (beat_i)
5'd0: tcd_word = 32'd0;
5'd1: tcd_word = 32'h1000_1000;
5'd2: tcd_word = 32'h1000_2000;
5'd3: tcd_word = 32'd16;
5'd7: tcd_word = 32'h0001_0003;
default: tcd_word = 32'd0;
endcase
endfunction
(* anyconst *)logic [ 1:0] f_scenario;
(* anyconst *)logic [ 31:0] f_read_data;
(* anyconst *)logic [ 31:0] f_rx_data;
(* anyconst *)logic f_tcd_mode;
(* anyconst *)logic [31:0] f_read_data;
(* anyconst *)logic [31:0] f_rx_data;
(* anyconst *)logic f_rx_last;
dma_req_if req ();
@@ -230,6 +252,8 @@ module dma_formal_design (
s_byte_count_i = '0;
s_request_sel_i = '0;
s_burst_cfg_i = '0;
s_tcd_head_i = '0;
s_tcd_count_i = '0;
s_ch_cfg_i[0+:32] = {22'd0, 2'd0, 1'b1, 1'b1, DMA_WIDTH_32, 1'b0, DMA_KIND_MM_TO_MM};
s_src_addr_i[0+:32] = 32'h1000_0FF0;
@@ -256,12 +280,25 @@ module dma_formal_design (
s_byte_count_i[96+:32] = 32'd8;
s_request_sel_i[96+:32] = {28'd0, DMA_REQUEST_SOFTWARE};
s_burst_cfg_i[96+:32] = 32'd2;
if (f_tcd_mode) begin
s_tcd_head_i[0+:32] = 32'h2000_0000;
s_tcd_count_i[0+:32] = 32'd1;
end
end
always_comb begin
s_start_i = '0;
s_abort_i = '0;
if (rst_n_i) begin
if (f_tcd_mode) begin
if (s_cycle_q == 6'd0) begin
s_start_i[0] = 1'b1;
end
if (u_dut.s_tcd_fetch_pending_q[0] || u_dut.s_tcd_read_owner_valid_q ||
u_dut.s_tcd_parse_q) begin
s_abort_i[0] = 1'b1;
end
end else
unique case (f_scenario)
2'd0: begin
if (s_cycle_q == 6'd0) begin
@@ -374,8 +411,8 @@ module dma_formal_design (
.global_reset_i (1'b0),
.global_error_clear_i (1'b0),
.ch_cfg_i (s_ch_cfg_i),
.tcd_head_i ('0),
.tcd_count_i ('0),
.tcd_head_i (s_tcd_head_i),
.tcd_count_i (s_tcd_count_i),
.crc_expected_i ('0),
.src_addr_i (s_src_addr_i),
.dst_addr_i (s_dst_addr_i),
@@ -446,6 +483,13 @@ module dma_formal_design (
assign write_start_channel = u_dut.s_write_rr_selected;
assign tx_channel_valid = u_dut.s_i2s_tx_channel_valid;
assign tx_channel = u_dut.s_i2s_tx_channel;
assign scenario = f_scenario;
assign tcd_mode = f_tcd_mode;
assign tcd_read_owner_valid = u_dut.s_tcd_read_owner_valid_q;
assign tcd_parse = u_dut.s_tcd_parse_q;
assign tcd_loaded = u_dut.s_tcd_loaded_q[0];
assign tcd_fetch_pending = u_dut.s_tcd_fetch_pending_q[0];
assign tcd_abort = u_dut.s_abort_q[0];
always_comb begin
write_owner_bytes_done = 32'd0;
@@ -563,6 +607,7 @@ module dma_formal_design (
s_read_beat_q <= 5'd0;
s_read_error_q <= 1'b0;
s_read_data_q <= 32'd0;
s_tcd_read_q <= 1'b0;
end else begin
if (axi4.arvalid && !axi4.arready) begin
s_read_stalled_once_q <= 1'b1;
@@ -572,14 +617,15 @@ module dma_formal_design (
s_read_beats_q <= {1'b0, axi4.arlen[3:0]} + 1'b1;
s_read_beat_q <= 5'd0;
s_read_error_q <= axi4.araddr[31:28] == 4'hD;
s_read_data_q <= f_read_data;
s_tcd_read_q <= axi4.araddr == 32'h2000_0000;
s_read_data_q <= (axi4.araddr == 32'h2000_0000) ? tcd_word(5'd0) : f_read_data;
end
if (axi4.rvalid && axi4.rready) begin
if (axi4.rlast) begin
s_read_active_q <= 1'b0;
end else begin
s_read_beat_q <= s_read_beat_q + 1'b1;
s_read_data_q <= f_read_data;
s_read_data_q <= s_tcd_read_q ? tcd_word(s_read_beat_q + 1'b1) : f_read_data;
end
end
end
+29 -2
View File
@@ -53,6 +53,13 @@ module dma_formal;
wire [ 1:0] tx_channel;
wire [ 31:0] tx_channel_bytes_done;
wire [ 31:0] tx_channel_len;
wire [ 1:0] scenario;
wire tcd_mode;
wire tcd_read_owner_valid;
wire tcd_parse;
wire tcd_loaded;
wire tcd_fetch_pending;
wire tcd_abort;
wire awvalid;
wire awready;
wire awid;
@@ -180,6 +187,13 @@ module dma_formal;
.tx_channel (tx_channel),
.tx_channel_bytes_done (tx_channel_bytes_done),
.tx_channel_len (tx_channel_len),
.scenario (scenario),
.tcd_mode (tcd_mode),
.tcd_read_owner_valid (tcd_read_owner_valid),
.tcd_parse (tcd_parse),
.tcd_loaded (tcd_loaded),
.tcd_fetch_pending (tcd_fetch_pending),
.tcd_abort (tcd_abort),
.awvalid (awvalid),
.awready (awready),
.awid (awid),
@@ -462,10 +476,13 @@ module dma_formal;
if (arvalid) begin
assert (arid == 1'b0);
assert (arsize == 3'd2);
assert (arlen <= 8'd3);
assert (arlen <= 8'd15);
assert ((arburst == 2'b00) || (arburst == 2'b01));
assert (araddr[1:0] == 2'b00);
if (arburst == 2'b00) begin
if (araddr == 32'h2000_0000) begin
assert (arburst == 2'b01);
assert (arlen == 8'd15);
end else if (arburst == 2'b00) begin
assert (arlen == 8'd0);
end else begin
assert ({1'b0, araddr[11:0]} + (({5'd0, arlen} + 13'd1) << 2) <= 13'd4096);
@@ -517,6 +534,16 @@ module dma_formal;
end
end
if (tcd_mode) begin
if (tcd_abort) begin
assert (!tcd_loaded);
assert (!tcd_fetch_pending);
end
if (f_past_valid && $past(tcd_parse) && $past(tcd_abort)) begin
assert (!tcd_loaded);
end
end
if (write_owner_valid && wvalid) begin
assert (!fifo_empty[write_owner]);
end
+39
View File
@@ -0,0 +1,39 @@
module dma_tcd_cancel_formal;
(* gclk *)reg clk_i;
wire rst_n_i;
wire [3:0] start_i;
wire [3:0] abort_i;
wire [3:0] aborted;
wire tcd_mode;
wire tcd_read_owner_valid;
wire tcd_parse;
wire tcd_loaded;
wire tcd_fetch_pending;
wire tcd_abort;
dma_formal_design u_design (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (start_i),
.abort_i (abort_i),
.aborted (aborted),
.tcd_mode (tcd_mode),
.tcd_read_owner_valid(tcd_read_owner_valid),
.tcd_parse (tcd_parse),
.tcd_loaded (tcd_loaded),
.tcd_fetch_pending (tcd_fetch_pending),
.tcd_abort (tcd_abort)
);
always @(posedge clk_i) begin
if (rst_n_i) begin
assume (tcd_mode);
if (tcd_abort) begin
assert (!tcd_loaded);
assert (!tcd_fetch_pending);
end
cover (tcd_fetch_pending && abort_i[0]);
cover (aborted[0] && !start_i[0]);
end
end
endmodule
+18
View File
@@ -1,6 +1,24 @@
{
"schema_version": 1,
"targets": [
{
"name": "spi-p2",
"paths": ["rtl/ip/serial/spi_dma_pacing.sv", "rtl/ip/peripheral/dma_core.sv",
"rtl/ip/peripheral/dma_reg.sv", "rtl/ip/peripheral/apb4_dma.sv"],
"status": "prototype",
"baseline_revision": "4b3553faa7dfeb2603d717904a2c1fbf79f168f9",
"configuration_digest": null,
"configuration_profiles": ["configs/ci/ics55-tiny.mk", "configs/ci/ihp130-tiny.mk"],
"required_evidence": [],
"synthesis_intent": {
"registers": "DMA V2.2 parameterized version/support bitmap with preserved V2.0 product defaults",
"reset": "credit reservations clear on reset; descriptor cancellation retains ownership until responses drain",
"clock_enable": "PCLK credit pacing; existing DMA AXI crossing unchanged",
"memory": "no new storage; existing DMA FIFOs and descriptor state only",
"pipeline": "admission credit, FIFO commit, terminal response retirement; product SPI grant remains disabled"
},
"waivers": []
},
{
"name": "spi-p1",
"paths": ["rtl/ip/serial/apb4_spi.sv", "rtl/ip/serial/spi_reg.sv",
+1
View File
@@ -268,6 +268,7 @@
"apb4_spi.sv",
"spi_ctrl_if.sv",
"spi_define.svh",
"spi_dma_pacing.sv",
"spi_gpio_guard.sv",
"spi_master_core.sv",
"spi_phase_ctrl.sv",
+4
View File
@@ -32,6 +32,10 @@ and simulation verdicts under the selected Tiny build variant. It does not
instantiate SPI in Tiny or implement central-DMA pacing. See the
[SPI-P1 runbook](../docs/ip/spi-p1.md) for scope and pending qualification.
`spi_p2.py` runs the focused V2.2 credit-pacing fixture and lint boundary.
The product DMA remains V2.0/default-disabled until shared integration is
qualified; this entrypoint does not wire Tiny SPI.
`generate_apu_kws_rtl_constants.py` also emits the frozen APU-P9 APUC image
and 15-bank layout manifest. `apu_kws_coeff.py` owns the independent APUC 1.0
schema and release validator; generated assets remain below the selected
+1 -1
View File
@@ -26,7 +26,7 @@ WRAPPER = ROOT / f"physical/smoke/syn/yosys/{TOP}.sv"
SOURCES = [COMMON / "interface/apb4_if.sv", COMMON / "utils/register.sv",
ROOT / "rtl/ip/util/soc_common_fifo.sv",
*[IP / f"{name}.sv" for name in (
"spi_pkg", "spi_ctrl_if", "spi_gpio_guard", "spi_phase_ctrl",
"spi_pkg", "spi_ctrl_if", "spi_gpio_guard", "spi_phase_ctrl", "spi_dma_pacing",
"spi_word_packer", "spi_master_core", "spi_reg", "apb4_spi")], WRAPPER]
INCLUDES = [COMMON, COMMON / "utils", IP, ROOT / "tests/rtl"]
CASES = ("spi_pin_checker_tb", "spi_core_tb", "spi_apb_tb", "spi_lifecycle_tb", "spi_packer_tb")
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Focused SPI-P2 DMA V2.2 evidence driver; product integration stays disabled."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import atomic_write # noqa: E402
SOURCES = [ROOT / "rtl/ip/serial/spi_dma_pacing.sv",
ROOT / "rtl/ip/peripheral/dma_define.svh", ROOT / "rtl/ip/peripheral/dma_pkg.sv",
ROOT / "rtl/ip/peripheral/dma_core.sv", ROOT / "rtl/ip/peripheral/dma_reg.sv",
ROOT / "rtl/ip/peripheral/apb4_dma.sv", ROOT / "tests/rtl/spi_dma_pacing_tb.sv",
ROOT / "tests/test_spi_p2.py", ROOT / "scripts/spi_p2.py",
ROOT / "crt/include/retrosoc/hal/dma.h",
ROOT / "crt/include/retrosoc/hal/dma_regs.h",
ROOT / "crt/src/hal/dma.c", ROOT / "crt/src/hal/dma_math.c",
ROOT / "rtl/mini/formal/dma_formal.sv",
ROOT / "rtl/mini/formal/dma_formal_props.sv",
ROOT / "rtl/mini/formal/dma_tcd_cancel_props.sv"]
PACING_TB = ROOT / "tests/rtl/spi_dma_pacing_tb.sv"
def digest(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("stage", choices=("lint", "rtl", "report"))
parser.add_argument("--variant-root", type=Path, required=True)
parser.add_argument("--pdk", choices=("ICS55", "IHP130"), required=True)
args = parser.parse_args()
variant = args.variant_root.resolve()
variant.relative_to(ROOT / "build")
manifest = json.loads((variant / "meta/manifest.json").read_text())
if manifest["configuration"]["SOC"] != "TINY" or manifest["configuration"]["PDK"] != args.pdk:
raise ValueError("profile/PDK mismatch")
directory = variant / "meta/spi-p2" / args.stage
if directory.exists():
raise ValueError(f"existing SPI-P2 attempt: {directory}")
directory.mkdir(parents=True)
source = {str(path.relative_to(ROOT)): digest(path) for path in SOURCES}
record = {"phase": "SPI-P2", "stage": args.stage, "status": "running",
"source": source, "manifest": manifest, "phase_accepted": False}
try:
common = ROOT / "rtl/managed/clusterip/common/rtl"
if args.stage == "rtl":
command = ["iverilog", "-g2012", "-s", "spi_dma_pacing_tb", "-o",
str(directory / "spi_dma_pacing_tb"), f"-I{common}", f"-I{common / 'utils'}",
str(common / "utils/register.sv"), str(SOURCES[0]), str(PACING_TB)]
result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, check=False)
(directory / "compile.log").write_text(result.stdout + result.stderr)
if result.returncode != 0:
raise RuntimeError("SPI-P2 pacing compile failed")
run = subprocess.run(["vvp", str(directory / "spi_dma_pacing_tb")], cwd=ROOT,
capture_output=True, text=True, check=False)
(directory / "sim.log").write_text(run.stdout + run.stderr)
if run.returncode != 0 or "SPI_P2_PASS spi_dma_pacing_tb" not in run.stdout:
raise RuntimeError("SPI-P2 pacing fixture failed")
elif args.stage == "lint":
result = subprocess.run(["verilator", "--lint-only", "--Wall", "-Wno-fatal",
"-Wno-DECLFILENAME", "--top-module",
"spi_dma_pacing", f"-I{common}", str(common / "utils/register.sv"),
str(SOURCES[0])], cwd=ROOT,
capture_output=True, text=True, check=False)
(directory / "lint.log").write_text(result.stdout + result.stderr)
if result.returncode != 0:
raise RuntimeError("SPI-P2 pacing lint failed")
record["status"] = "passed"
except (OSError, RuntimeError) as error:
record.update(status="failed", error=str(error))
print(error, file=sys.stderr)
return 1
record["artifacts"] = {str(path): digest(path) for path in directory.rglob("*") if path.is_file()}
atomic_write(directory / "record.json", json.dumps(record, indent=2, sort_keys=True) + "\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+3
View File
@@ -130,6 +130,9 @@ components and incompatible historical inventories. The
`spi-p1-rtl` and backend targets retain separate qualification artifacts;
they are not a Tiny SPI firmware integration result.
`test_spi_p2.py` covers the V2.2 request split, support-register ABI,
product-default disable policy and source-qualified credit pacing fixture.
Historical Tiny publication fixtures also need the main repository history
and dependency commit objects named by their archived locks. After ordinary
dependency setup, run `python3 tests/publication_history.py prepare` before
+48
View File
@@ -0,0 +1,48 @@
`timescale 1ns / 1ps
module spi_dma_pacing_tb;
logic clk_i = 0, rst_n_i = 0, enable_i = 1, dma_error_i = 0;
logic [31:0] tx_binding_i = 32'h80000003, rx_binding_i = 32'h80000002;
logic [1:0] dma_start_pending_i = 0, dma_status_i = 0;
logic [1:0] credit_valid_i = 0, credit_retire_i = 0, credit_commit_i = 0;
logic [1:0] credit_valid_o, credit_retire_o, credit_pending_o;
logic [3:0] dma_status_o;
logic credit_fault_o, busy_o;
always #5 clk_i = !clk_i;
spi_dma_pacing u_dut (.*);
task automatic tick;
@(posedge clk_i);
#1;
endtask
initial begin
repeat (2) tick();
rst_n_i = 1;
tick();
credit_valid_i = 2'b01;
#1;
if (credit_valid_o != 2'b01 || credit_pending_o != 2'b01) $fatal(1, "credit admission");
credit_commit_i = 2'b01;
tick();
credit_valid_i = 0;
credit_commit_i = 0;
#1;
if (credit_pending_o != 2'b01 || credit_valid_o != 0 || !busy_o) $fatal(1, "credit commit");
credit_retire_i = 2'b01;
tick();
credit_retire_i = 0;
if (credit_pending_o != 0 || busy_o) $fatal(1, "credit retire");
tx_binding_i = 32'h80000002;
#1;
if (!credit_fault_o) $fatal(1, "binding conflict");
tx_binding_i = 32'h80000003;
enable_i = 0;
credit_valid_i = 2'b10;
#1;
if (!credit_fault_o || credit_valid_o != 0) $fatal(1, "disabled admission");
$display("SPI_P2_PASS spi_dma_pacing_tb");
$finish;
end
initial begin
#1000;
$fatal(1, "SIM_TEST_TIMEOUT spi_dma_pacing_tb");
end
endmodule
+1
View File
@@ -54,6 +54,7 @@ def test_dma_register_offsets_match_rtl() -> None:
"APB4_DMA__IRQ_TEST": "RS_DMA_REG_IRQ_TEST",
"APB4_DMA__ERROR_SUMMARY": "RS_DMA_REG_ERROR_SUMMARY",
"APB4_DMA__REQUEST_STATUS": "RS_DMA_REG_REQUEST_STATUS",
"APB4_DMA__SUPPORTED_REQUESTS": "RS_DMA_REG_SUPPORTED_REQUESTS",
"APB4_DMA__CH_BASE": "RS_DMA_CH_BASE",
"APB4_DMA__CH_STRIDE": "RS_DMA_CH_STRIDE",
"APB4_DMA__CH_CTRL": "RS_DMA_CH_REG_CTRL",
+114
View File
@@ -0,0 +1,114 @@
"""Focused SPI-P2 DMA V2.2 and credit-pacing checks."""
from __future__ import annotations
import re
import shutil
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_dma_v22_abi_and_product_defaults():
define = (ROOT / "rtl/ip/peripheral/dma_define.svh").read_text()
pkg = (ROOT / "rtl/ip/peripheral/dma_pkg.sv").read_text()
regs = (ROOT / "crt/include/retrosoc/hal/dma_regs.h").read_text()
core = (ROOT / "rtl/ip/peripheral/dma_core.sv").read_text()
tiny = (ROOT / "rtl/tiny/top/retrosoc_tiny.sv").read_text()
assert "APB4_DMA__SUPPORTED_REQUESTS" in define
assert "RS_DMA_REG_SUPPORTED_REQUESTS" in regs
assert "DMA_REQUEST_SPI_TX = 5'd16" in pkg
assert "DMA_REQUEST_SPI_RX = 5'd17" in pkg
assert "s_tcd_word7_q[s_tcd_read_owner_q][25]" in core
assert "logic [ NumChannels-1:0][ 4:0] s_req_q" in core
assert "!EnableSpi && ((s_cfg_req[channel] == DMA_REQUEST_SPI_TX)" in core
assert "s_tcd_parse_q &&" in core
assert ".RequestMask (16'h39ff)" in tiny
assert ".EnableSpi" not in tiny
def test_dma_v22_tcd_request_split_is_handwritten_and_stable():
c_header = (ROOT / "crt/include/retrosoc/hal/dma.h").read_text()
c_math = (ROOT / "crt/src/hal/dma_math.c").read_text()
c_dma = (ROOT / "crt/src/hal/dma.c").read_text()
assert "RS_DMA_TCD_REQUEST_HIGH" in c_header
assert "request |= UINT32_C(0x10)" in c_math
assert "RS_DMA_TCD_REQUEST_HIGH" in c_dma
def test_dma_v22_hal_mask_and_discovery_order():
c_math = (ROOT / "crt/src/hal/dma_math.c").read_text()
c_dma = (ROOT / "crt/src/hal/dma.c").read_text()
core = (ROOT / "rtl/ip/peripheral/dma_core.sv").read_text()
apb = (ROOT / "rtl/ip/peripheral/apb4_dma.sv").read_text()
assert "UINT32_C(0x000339FF)" in c_math
assert "RS_DMA_V22" not in c_math
assert c_dma.index("rs_dma_supports_request(config->request)") < c_dma.index(
"rs_dma_config_validate(channel, config)"
)
assert "s_cfg_req[channel] > DMA_REQUEST_SPI_RX" in core
assert "s_cfg_req_raw[channel][31:5] != 27'd0" in core
assert "32'h0003_ffff" in apb
assert "EnableStreams ?" in apb
def test_dma_stream_status_is_disabled_with_streams_off():
core = (ROOT / "rtl/ip/peripheral/dma_core.sv").read_text()
for request in (
"DMA_REQUEST_I2S_TX",
"DMA_REQUEST_I2S_RX",
"DMA_REQUEST_DVP_RX",
"DMA_REQUEST_CRYPTO_IN",
"DMA_REQUEST_CRYPTO_OUT",
):
match = re.search(rf"s_req_ready\[{request}\]\s*=\s*([^;]+);", core)
assert match is not None
assert "EnableStreams" in match.group(1)
def test_dma_v21_descriptor_cancel_guards_are_structural():
core = (ROOT / "rtl/ip/peripheral/dma_core.sv").read_text()
formal = (ROOT / "rtl/mini/formal/dma_formal_props.sv").read_text()
harness = (ROOT / "rtl/mini/formal/dma_formal.sv").read_text()
tcd_props = (ROOT / "rtl/mini/formal/dma_tcd_cancel_props.sv").read_text()
fetch = core[core.index("s_tcd_fetch_pending_q[channel]"):core.index("always_comb begin", core.index("s_tcd_fetch_pending_q[channel]"))]
parse_start = core.index("if (s_tcd_parse_q)")
parse = core[parse_start:core.index("if (s_axi_read_beat_valid && s_read_owner_valid_q)", parse_start)]
ownership = core[core.index("s_read_owns_channel = '0"):core.index("always_ff @(posedge clk_i", core.index("s_read_owns_channel = '0"))]
assert "!s_abort_q[channel]" in fetch
assert "!abort_i[channel]" in fetch
assert "!s_tcd_parse_q" in fetch
assert "if (s_abort_q[s_tcd_read_owner_q])" in parse
assert "s_tcd_parse_q &&" in ownership
assert "s_tcd_fetch_pending_q[channel] <= 1'b0" in core
assert "tcd_read_owner_valid" in formal
assert "tcd_word" in harness
assert "cover (tcd_fetch_pending && abort_i[0])" in tcd_props
assert "assume (tcd_mode)" in tcd_props
def test_dma_v22_old_version_rejection_precedes_support_read():
c_dma = (ROOT / "crt/src/hal/dma.c").read_text()
start = c_dma.index("rs_status_t rs_dma_supports_request")
body = c_dma[start:c_dma.index("static rs_status_t rs_dma_configure_owned", start)]
assert body.index("version = RS_DMA_REG(RS_DMA_REG_IP_VERSION)") < body.index(
"supported = RS_DMA_REG(RS_DMA_REG_SUPPORTED_REQUESTS)"
)
assert "version < RS_DMA_IP_VERSION_V22" in body
def test_spi_dma_pacing_fixture(tmp_path):
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
assert iverilog and vvp
common = ROOT / "rtl/managed/clusterip/common/rtl"
sim = tmp_path / "spi_dma_pacing_tb"
subprocess.run([
iverilog, "-g2012", "-s", "spi_dma_pacing_tb", "-o", str(sim),
f"-I{common}", f"-I{common / 'utils'}", str(common / "utils/register.sv"),
str(ROOT / "rtl/ip/serial/spi_dma_pacing.sv"),
str(ROOT / "tests/rtl/spi_dma_pacing_tb.sv")],
check=True, capture_output=True, text=True)
run = subprocess.run([vvp, str(sim)], check=True, capture_output=True, text=True)
assert "SPI_P2_PASS spi_dma_pacing_tb" in run.stdout
assert not re.search(r"FAILED|FATAL|SIM_TEST_TIMEOUT", run.stdout + run.stderr)