3"""Render parsed RA8 pin lists into the files under ``docs/pinouts/``.
5Two shapes are produced. A per-variant plain-text reference carries three
6views of one ball map -- the physical grid as the package is drawn, the full
7alternate-function table per ball, and a port-name index for the common
8"which ball is P409" lookup. A Markdown index resolves any of the 64 part
9numbers to its variant file.
11Nothing here reads a PDF or decides a fact; it is handed parsed rows and
12formats them. Used by ``gen_pinouts.py``; see that file for the whole
16from __future__
import annotations
20from pathlib
import Path
22sys.path.insert(0, str(Path(__file__).resolve().parent))
23from pinout_model
import (
57def wrap_cell(value: str, width: int) -> list[str]:
58 """Wrap a slash-separated function list to ``width``, breaking on '/'.
60 Alternate-function lists are slash-separated, so a break after a slash
61 reads naturally and never splits a signal name unless one name alone
62 exceeds the column -- in which case it is split rather than allowed to
63 push the table out of alignment.
67 pieces = [p +
"/" for p
in value.split(
"/")]
68 pieces[-1] = pieces[-1][:-1]
71 for raw_piece
in pieces:
73 if line
and len(line) + len(piece) > width:
76 while len(piece) > width:
80 out.append(piece[:width])
88def render_grid(rows: list[dict], column: tuple) -> list[str]:
89 """Render the physical ball grid, top view, one cell per ball."""
92 ball = row[
"balls"][column]
94 placed[ball] = row[
"port"]
or row[
"power"]
or "-"
96 letters = sorted({b.rstrip(
"0123456789")
for b
in placed}, key=
lambda r: (len(r), r))
97 numbers = sorted({int(b.lstrip(
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
for b
in placed})
98 width = max(*(len(v)
for v
in placed.values()), *(len(str(n))
for n
in numbers))
99 pad = max(len(letter)
for letter
in letters)
101 out = [
" " * pad +
" " +
" ".join(str(n).center(width)
for n
in numbers)]
102 for letter
in letters:
103 cells = [placed.get(f
"{letter}{num}",
"").center(width)
for num
in numbers]
104 out.append(f
"{letter:<{pad}} " +
" ".join(cells).rstrip())
108def _variant_header(group: Group, package: str, mipi: bool, balls: int, io_pins: int) -> list[str]:
109 """The provenance block every variant file opens with."""
110 pkg = PACKAGES[package]
111 table = group.sip_table
if pkg.sip
else group.std_table
112 kind =
"SiP" if pkg.sip
else "Standard"
114 f
"{group.name} group pinout -- LFBGA {pkg.balls}-pin, "
115 f
"{'with' if mipi else 'without'} MIPI DSI/CSI"
123 "GENERATED FILE -- DO NOT EDIT BY HAND.",
124 " Regenerate with: python3 scripts/gen/gen_pinouts.py",
126 f
"Source : {group.name} Group Datasheet {group.doc_id}, section 1.7",
127 f
' "Pin Lists", Table {table} "Pin list for the {kind} product",',
128 f
' column "BGA{pkg.balls}{"" if mipi else " without MIPI"}".',
130 f
"Package : {pkg.renesas} ({pkg.jeita})",
131 f
" LFBGA {pkg.balls}-pin, {pkg.body}",
133 f
"I/O port pins : {io_pins}",
134 f
"MIPI DSI/CSI : {'available' if mipi else 'not available'}",
136 lines += textwrap.wrap(
139 initial_indent=
"Group : ",
140 subsequent_indent=
" ",
145def _variant_parts(parts: list[Part], package: str, mipi: bool) -> list[str]:
146 """The list of part numbers that share this ball map."""
147 matching = [p
for p
in parts
if p.package == package
and p.mipi == mipi]
149 f
"Part numbers using this ball map ({len(matching)}):",
151 f
" {'Part number':<16} {'Cores':<7} {'Code memory':<32} {'Junction temp':<14}".rstrip(),
152 f
" {'-' * 16} {'-' * 7} {'-' * 32} {'-' * 14}",
156 f
" {part.number:<16} {part.cores:<7} "
157 f
"{MRAM_SIZES[part.mram]:<32} {TEMP_GRADES[part.temp]:<14}"
164 "Every part number above has an identical ball map; they",
165 "differ only in core count, memory size and temperature",
166 "grade. See docs/pinouts/README.md for the full matrix.",
171def _function_table(rows: list[dict], column: tuple) -> list[str]:
172 """Section 2: every ball with every alternate function it offers."""
173 lines = [RULE,
"2. Alternate functions per ball", RULE,
""]
174 lines += [f
" {key.upper():<7}{FIELD_HEADINGS[key]}" for key
in COLUMN_ORDER]
177 ' A field reads "-" when the ball offers nothing in that',
178 " category. Suffixes _A/_B/_C are the datasheet's pin-",
179 ' candidate variants; "-DS" marks a deep-standby-capable',
184 header = f
"{'BALL':<{TABLE_WIDTHS['ball']}}" +
"".join(
185 f
"{k.upper():<{TABLE_WIDTHS[k] + 1}}" for k
in COLUMN_ORDER
187 lines += [header.rstrip(),
"-" * len(header.rstrip())]
190 cells = {k: wrap_cell(row[k], TABLE_WIDTHS[k])
for k
in COLUMN_ORDER}
191 for i
in range(max(len(v)
for v
in cells.values())):
192 ball = row[
"balls"][column]
if i == 0
else ""
193 text = f
"{ball:<{TABLE_WIDTHS['ball']}}"
194 for key
in COLUMN_ORDER:
195 piece = cells[key][i]
if i < len(cells[key])
else ""
196 text += f
"{piece:<{TABLE_WIDTHS[key] + 1}}"
197 lines.append(text.rstrip())
201def _port_index(io_pins: list[dict], column: tuple) -> list[str]:
202 """Section 3: port name -> ball, the lookup people actually run."""
205 "3. I/O port pin index",
208 f
"{len(io_pins)} port pins, in port order.",
211 by_port = sorted(io_pins, key=
lambda r: port_key(r[
"port"]))
212 for i
in range(0, len(by_port), PORTS_PER_LINE):
213 chunk = by_port[i : i + PORTS_PER_LINE]
215 " " +
" ".join(f
"{r['port']:<5} {r['balls'][column]:<4}" for r
in chunk).rstrip()
221 group: Group, package: str, mipi: bool, rows: list[dict], parts: list[Part]
223 """Render one variant's whole reference file."""
224 column = (package, mipi)
226 (r
for r
in rows
if r[
"balls"][column]
is not None),
227 key=
lambda r: ball_key(r[
"balls"][column]),
229 io_pins = [r
for r
in mine
if r[
"port"]]
231 lines = _variant_header(group, package, mipi, len(mine), len(io_pins))
232 lines += _variant_parts(parts, package, mipi)
235 "1. Ball grid (top view)",
238 "Each cell names the I/O port pin, or the power/system",
239 "function for balls that are not port pins. Blank = no ball.",
242 lines += render_grid(rows, column)
244 lines += _function_table(mine, column)
245 lines += _port_index(io_pins, column)
246 return "\n".join(lines) +
"\n"
249def _index_intro() -> list[str]:
251 "# RA8 pinout reference",
253 "Ball maps for every orderable RA8D2 and RA8P1 part number, parsed",
254 'out of section 1.7 "Pin Lists" of the two group datasheets by',
255 "`scripts/gen/gen_pinouts.py`. **These files are generated -- edit the",
256 "generator, not the output.** `gen_pinouts.py --check` runs in CI, so",
257 "a datasheet revision that moves a ball cannot land without the",
258 "reference moving with it.",
260 "## Which file do I want?",
262 "A part number's ball map is fixed by exactly two of its fields: the",
263 "**package** and whether its **feature set** bonds out MIPI DSI/CSI",
264 "(`B` and `K` do; `A` and `J` do not). Memory size, core count and",
265 "temperature grade never move a ball, so the 64 part numbers below",
266 "collapse onto 12 ball maps.",
271def _index_variant_table(variants: list) -> list[str]:
273 "| Group | Package | MIPI DSI/CSI | Balls | I/O | Pinout file |",
274 "|---|---|---|---|---|---|",
276 for group, package, mipi, filename, balls, io_pins, _
in variants:
277 pkg = PACKAGES[package]
279 f
"| {group} | LFBGA {pkg.balls}{' (SiP)' if pkg.sip else ''} | "
280 f
"{'yes' if mipi else 'no'} | {balls} | {io_pins} | "
281 f
"[`{filename}`]({filename}) |"
286def _index_part_table(parts: list) -> list[str]:
288 "## Part number -> ball map",
290 "Decoded from the part-numbering scheme (Figure 1.2 of either",
291 "datasheet), cross-checked against the printed product list.",
293 "| Part number | Group | Cores | MIPI | Code memory | SRAM[^sram] "
294 "| Junction temp | Package | Pinout file |",
295 "|---|---|---|---|---|---|---|---|---|",
297 for part, sram, filename
in parts:
298 pkg = PACKAGES[part.package]
300 f
"| `{part.number}` | {part.group} | {part.cores} | "
301 f
"{'yes' if part.mipi else 'no'} | {MRAM_SIZES[part.mram]} | "
302 f
"{sram} | {TEMP_GRADES[part.temp]} | "
303 f
"LFBGA {pkg.balls}{' SiP' if pkg.sip else ''} | "
304 f
"[`{filename}`]({filename}) |"
309 "[^sram]: SRAM is the one column here that is not pinout data and not read",
310 " per-part: the Function Comparison table merges it across columns, giving",
311 " 1792 KB for the single-core feature sets (`A`, `B`) and 1664 KB for the",
312 " dual-core ones (`J`, `K`), the latter spending 128 KB on the CM33 TCM.",
313 " Every SiP part is dual-core. Take it as orientation and confirm against",
314 " the datasheet before sizing anything against it.",
319def _index_scheme() -> list[str]:
321 "## Reading a part number",
324 "R 7 K A 8 D 2 A D L C AB",
325 " | | | | | | +-- package: AB=LFBGA224 AC=LFBGA289 AJ=LFBGA303",
326 " | | | | | +----- quality grade: C=standard S=SiP",
327 " | | | | +------- junction temp: L=0..95C D=-40..105C",
328 " | | | +--------- code memory: D=512KB F=1MB R=1MB+4MB S=1MB+8MB",
329 " | | +----------- feature set: A/B single core, J/K dual core;",
330 " | | B/K bond out MIPI DSI/CSI, A/J do not",
331 " +-+------------- group: D2=RA8D2, P1=RA8P1",
334 "The leading `R7K`/`R7J` also encodes the memory technology (`K`=MRAM,",
335 "`J`=MRAM+flash SiP) and so tracks the quality-grade and package",
336 "fields; the generator rejects a part number where the three",
342def _index_sources() -> list[str]:
343 lines = [
"## Sources",
"",
"| Group | Datasheet | Committed as |",
"|---|---|---|"]
345 f
"| {group.name} | {group.doc_id} | `{group.pdf.relative_to(REPO_ROOT)}` |"
351 "The Hardware User's Manual, not the datasheet, is the authority on",
352 "*register* programming for any of these pins -- see",
353 "`docs/reference/README.md`. The datasheet is the authority on which",
354 "ball carries which function, which is what these files record.",
359def render_index(data: dict) -> str:
360 """Render `docs/pinouts/README.md`, the part-number -> file resolver."""
361 lines = _index_intro()
362 lines += _index_variant_table(data[
"variants"])
363 lines += _index_part_table(data[
"parts"])
364 lines += _index_scheme()
365 lines += [
"## Pin compatibility between the two groups",
""]
366 lines += data[
"compat"]
368 lines += _index_sources()
369 return "\n".join(lines) +
"\n"