fix(publications): align DVP and PSRAM source bindings

Follow the shared PSRAM model path and bind the DVP DMA selector together
with its Tiny/Mini definition, preserving Mini bulk-channel semantics.

Describe the 129-bit configuration/epoch CDC packet, 128-bit payload
extraction and applied register using the existing RTL. Complete isolated
fixture dependencies and add negative checks for channel, width, slice
and register-bypass drift without relaxing source validation.

The final publication selection passes 163 tests with one optional skip;
the full final worktree passes 2031 tests with the same pypdf skip.
Retain publication identity and strict source-drift checks. No RTL, PDF
generation, source-baseline refresh or physical qualification is included.
This commit is contained in:
maksyuki committed 2026-10-18 20:51:47 +08:00
1 parent aaeaef6fa1
commit 1d5959680e
4 files changed
+261 -59

No files matched your search

+207 -56
View File
@@ -8899,7 +8899,7 @@
"id": "reg",
"title": "APB4 registers\ncapture / stream control",
"x": 0.0,
"y": 7.0,
"y": 10.5,
"w": 4.0,
"h": 1.8,
"ports": [
@@ -8922,24 +8922,49 @@
}
},
{
"id": "cfg",
"title": "Configuration CDC\nsystem to pixel",
"id": "cfg_packet",
"title": "Packet assembly\n128-bit CFG + epoch",
"x": 6.3,
"y": 7.0,
"y": 10.5,
"w": 4.0,
"h": 1.8,
"ports": [
{
"id": "in",
"direction": "in",
"width": 128,
"side": "west"
},
{
"id": "out",
"direction": "out",
"width": 129,
"side": "east"
}
],
"sources": [
"rtl/ip/multimedia/axi4s_dvp.sv"
]
},
{
"id": "cfg",
"title": "Configuration CDC\n129-bit epoch packet",
"x": 12.6,
"y": 10.5,
"w": 4.0,
"h": 1.8,
"ports": [
{
"id": "p0",
"direction": "in",
"width": 128,
"width": 129,
"side": "west"
},
{
"id": "p1",
"direction": "out",
"width": 128,
"side": "east"
"width": 129,
"side": "south"
}
],
"sources": [
@@ -8953,10 +8978,67 @@
"declaration_file": "rtl/managed/clusterip/common/rtl/cdc/cdc_2phase.sv"
}
},
{
"id": "cfg_split",
"title": "Epoch / payload split\npacket[127:0]",
"x": 12.6,
"y": 7.0,
"w": 4.0,
"h": 1.8,
"ports": [
{
"id": "in",
"direction": "in",
"width": 129,
"side": "north"
},
{
"id": "out",
"direction": "out",
"width": 128,
"side": "west"
}
],
"sources": [
"rtl/ip/multimedia/axi4s_dvp.sv"
]
},
{
"id": "applied",
"title": "Applied configuration\npixel-domain register",
"x": 6.3,
"y": 7.0,
"w": 4.0,
"h": 1.8,
"ports": [
{
"id": "in",
"direction": "in",
"width": 128,
"side": "east"
},
{
"id": "out",
"direction": "out",
"width": 128,
"side": "west"
}
],
"sources": [
"rtl/ip/multimedia/axi4s_dvp.sv",
"rtl/managed/clusterip/common/rtl/utils/register.sv"
],
"instance_source": {
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"name": "u_applied_config",
"module": "dffer",
"declaration_file": "rtl/managed/clusterip/common/rtl/utils/register.sv"
}
},
{
"id": "core",
"title": "Pixel-domain core\nRGB / YUV packing",
"x": 12.6,
"x": 0.0,
"y": 7.0,
"w": 4.0,
"h": 1.8,
@@ -8965,7 +9047,7 @@
"id": "p1",
"direction": "in",
"width": 128,
"side": "west"
"side": "east"
},
{
"id": "p2",
@@ -8994,7 +9076,7 @@
{
"id": "pads",
"title": "Camera inputs\nPCLK / VSYNC / HREF / D8",
"x": 12.6,
"x": 0.0,
"y": 3.5,
"w": 4.0,
"h": 1.8,
@@ -9028,7 +9110,7 @@
"id": "p4",
"direction": "out",
"width": 42,
"side": "west"
"side": "east"
}
],
"sources": [
@@ -9043,7 +9125,7 @@
{
"id": "sink",
"title": "PIO / stream sink\ncentral DMA endpoint",
"x": 0.0,
"x": 12.6,
"y": 3.5,
"w": 4.0,
"h": 1.8,
@@ -9052,7 +9134,7 @@
"id": "p4",
"direction": "in",
"width": 42,
"side": "east"
"side": "west"
}
],
"sources": [
@@ -9063,7 +9145,7 @@
"edges": [
{
"from": "reg.p0",
"to": "cfg.p0",
"to": "cfg_packet.in",
"kind": "control",
"label": "CFG / 128",
"style": "direct",
@@ -9074,21 +9156,7 @@
},
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"text": ".src_data_i(s_cfg_sys)"
}
],
"signals": [
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"scope": "axi4s_dvp",
"symbol": "s_cfg_sys",
"bits": 128
},
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"scope": "axi4s_dvp",
"symbol": "s_cfg_sys",
"bits": 128
"text": ".src_data_i({!s_cfg_epoch_q, s_cfg_sys})"
}
],
"instance_ports": [
@@ -9096,28 +9164,129 @@
"node": "reg",
"port": "cfg_o",
"expression": "s_cfg_sys"
},
}
],
"signals": [
{
"node": "cfg",
"port": "src_data_i",
"expression": "s_cfg_sys"
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"scope": "axi4s_dvp",
"symbol": "s_cfg_sys",
"bits": 128
}
]
},
{
"from": "cfg.p1",
"to": "core.p1",
"from": "cfg_packet.out",
"to": "cfg.p0",
"kind": "control",
"label": "CFG / 128",
"label": "CFG + EPOCH / 129",
"style": "direct",
"bindings": [
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"text": ".dst_data_o(s_cfg_pclk_q)"
"text": "cdc_2phase #(.DATA_WIDTH(129)) u_dvp_config_cdc"
},
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"text": ".src_data_i({!s_cfg_epoch_q, s_cfg_sys})"
}
],
"instance_ports": [
{
"node": "cfg",
"port": "src_data_i",
"expression": "{!s_cfg_epoch_q, s_cfg_sys}"
}
],
"signals": []
},
{
"from": "cfg.p1",
"to": "cfg_split.in",
"kind": "control",
"label": "PACKET / 129",
"style": "direct",
"bindings": [
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"text": ".dst_data_o(s_cfg_packet_pclk)"
}
],
"instance_ports": [
{
"node": "cfg",
"port": "dst_data_o",
"expression": "s_cfg_packet_pclk"
}
],
"signals": [
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"scope": "axi4s_dvp",
"symbol": "s_cfg_packet_pclk",
"bits": 129
}
]
},
{
"from": "cfg_split.out",
"to": "applied.in",
"kind": "control",
"label": "PAYLOAD / 128",
"style": "direct",
"bindings": [
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"text": "dffer #(.DATA_WIDTH(128)) u_applied_config"
},
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"text": ".dat_i(s_cfg_packet_pclk[127:0])"
},
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"text": ".en_i(s_cfg_v_pclk && s_cfg_rdy_pclk && !s_cfg_apply_pclk_q)"
}
],
"instance_ports": [
{
"node": "applied",
"port": "dat_i",
"expression": "s_cfg_packet_pclk[127:0]"
}
],
"signals": []
},
{
"from": "applied.out",
"to": "core.p1",
"kind": "control",
"label": "APPLIED CFG / 128",
"style": "direct",
"bindings": [
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"text": ".dat_o(s_cfg_pclk_q)"
},
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"text": ".cfg_i(s_cfg_pclk_q)"
},
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"text": ".cfg_valid_i(s_cfg_apply_pclk_q)"
}
],
"instance_ports": [
{
"node": "applied",
"port": "dat_o",
"expression": "s_cfg_pclk_q"
},
{
"node": "core",
"port": "cfg_i",
"expression": "s_cfg_pclk_q"
}
],
"signals": [
@@ -9126,24 +9295,6 @@
"scope": "axi4s_dvp",
"symbol": "s_cfg_pclk_q",
"bits": 128
},
{
"file": "rtl/ip/multimedia/axi4s_dvp.sv",
"scope": "axi4s_dvp",
"symbol": "s_cfg_pclk_q",
"bits": 128
}
],
"instance_ports": [
{
"node": "cfg",
"port": "dst_data_o",
"expression": "s_cfg_pclk_q"
},
{
"node": "core",
"port": "cfg_i",
"expression": "s_cfg_pclk_q"
}
]
},
@@ -9241,7 +9392,7 @@
]
}
],
"note": "Configuration crosses into the pixel domain; 42-bit pixel/event payloads return through a warm-flush CDC FIFO. PIO or AXI4-Stream consumes the same captured payload. No direct DVP-to-JPEG connection is implied."
"note": "The configuration CDC carries 128 configuration bits plus an epoch. Its payload is latched in the pixel domain before application to the core; command, cancellation and acknowledgement control is not expanded here. 42-bit pixel/event payloads return through a warm-flush CDC FIFO. PIO or AXI4-Stream consumes the same captured payload. No direct DVP-to-JPEG connection is implied."
},
"jpeg": {
"nodes": [
@@ -1640,7 +1640,11 @@
"bindings": [
{
"file": "crt/src/hal/dvp.c",
"text": "rs_dma_configure(RS_DMA_CHANNEL_BULK"
"text": "rs_dma_configure(RS_DVP_DMA_CHANNEL"
},
{
"file": "crt/src/hal/dvp.c",
"text": "#ifdef RS_SOC_TINY\n#define RS_DVP_DMA_CHANNEL RS_DMA_CHANNEL_DVP\n#else\n#define RS_DVP_DMA_CHANNEL RS_DMA_CHANNEL_BULK\n#endif"
}
]
},
@@ -2043,7 +2047,7 @@
"silicon": "Not established",
"reports": [],
"sources": [
"rtl/mini/dv/model/ESP_PSRAM64H.sv",
"rtl/model/ESP_PSRAM64H.sv",
"tests/test_psram.py",
"docs/ip/psram.md"
]
+26 -1
View File
@@ -26,7 +26,8 @@ def data():
@pytest.fixture
def source_tree(tmp_path):
for relative in dr.dependencies(SPEC):
circuits = {**SPEC["circuits"], **read_json(ROOT / SPEC["circuit_catalog"])}
for relative in dr.dependencies({**SPEC, "circuits": circuits}):
target = tmp_path / relative
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(ROOT / relative, target)
@@ -40,6 +41,30 @@ def replace(root: Path, relative: str, old: str, new: str) -> None:
path.write_text(source.replace(old, new), encoding="utf-8")
@pytest.mark.parametrize(("before", "after"), [
(".DATA_WIDTH(129)", ".DATA_WIDTH(128)"),
("s_cfg_packet_pclk[127:0]", "s_cfg_packet_pclk[128:1]"),
(".cfg_i (s_cfg_pclk_q)",
".cfg_i (s_cfg_packet_pclk[127:0])"),
])
def test_dvp_configuration_diagram_rejects_packet_or_latch_drift(monkeypatch, before, after):
circuit = read_json(ROOT / "publications/datasheets/diagram-circuits.json")["dvp"]
dr.validate_circuit(ROOT, circuit)
read_text = Path.read_text
wrapper = ROOT / "rtl/ip/multimedia/axi4s_dvp.sv"
def drift(path, *args, **kwargs):
text = read_text(path, *args, **kwargs)
if path.resolve() == wrapper:
assert text.count(before) == 1
return text.replace(before, after)
return text
monkeypatch.setattr(Path, "read_text", drift)
with pytest.raises(ValueError, match="binding changed"):
dr.validate_circuit(ROOT, circuit)
def test_representative_inventory_uses_actual_widths_and_addresses(data):
value = data["system_reference"]["illustrations"]
assert sum(f["bits"] for f in value["dma_tcd"]) == 512
+22
View File
@@ -96,6 +96,28 @@ def test_channel_coverage_cannot_drop_the_boot_channel(spec):
pr.collect_programming(ROOT, spec, IDS, REFERENCE["source_revision"])
@pytest.mark.parametrize(("before", "after"), [
("rs_dma_configure(RS_DVP_DMA_CHANNEL,", "rs_dma_configure(RS_DMA_CHANNEL_CRYPTO_IN,"),
("#else\n#define RS_DVP_DMA_CHANNEL RS_DMA_CHANNEL_BULK",
"#else\n#define RS_DVP_DMA_CHANNEL RS_DMA_CHANNEL_CRYPTO_IN"),
])
def test_dvp_bulk_binding_rejects_channel_drift(spec, monkeypatch, before, after):
"""A real HAL call or Mini selector change must invalidate its published role."""
read_text = Path.read_text
dvp = ROOT / "crt/src/hal/dvp.c"
def drift(path, *args, **kwargs):
text = read_text(path, *args, **kwargs)
if path.resolve() == dvp:
assert text.count(before) == 1
return text.replace(before, after)
return text
monkeypatch.setattr(Path, "read_text", drift)
with pytest.raises(ValueError, match="programming binding changed: bulk"):
check(spec)
def test_mini_channel_selection_excludes_inactive_tiny_roles():
header = (ROOT / "crt/include/retrosoc/hal/dma.h").read_text()
selected = pr.mini_channel_enum(header)