rbpu_datasheet/build.ipynb

321 lines
15 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# RBPU16 数据手册\n",
"\nRun All → 生成自包含 HTML可直接发送给他人。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import sys, os, csv, json, re, subprocess, base64, mimetypes\n",
"from datetime import datetime\n",
"from pathlib import Path\n",
"from html import escape\n",
"\n",
"try:\n",
" import markdown as md_lib\n",
"except ImportError:\n",
" subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"markdown\", \"Pygments\", \"-q\"])\n",
" import markdown as md_lib\n",
"\n",
"BASE = Path(os.getcwd()).resolve()\n",
"print(f\" {BASE.name}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Build"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ============================================================\n",
"# Data loaders\n",
"# ============================================================\n",
"\n",
"def csv_dicts(path):\n",
" rows = []\n",
" with open(path, encoding=\"utf-8\") as f:\n",
" for row in csv.DictReader(f):\n",
" rows.append({k.strip(): v.strip() if v else \"\" for k, v in row.items()})\n",
" return rows\n",
"\n",
"def csv_raw(path):\n",
" rows = []\n",
" with open(path, encoding=\"utf-8\") as f:\n",
" for row in csv.reader(f): rows.append([c.strip() for c in row])\n",
" return rows\n",
"\n",
"pins = csv_dicts(BASE / \"data\" / \"pin_name.csv\")\n",
"pin_loc_raw = csv_raw(BASE / \"data\" / \"pin_loc.csv\")\n",
"with open(BASE / \"data\" / \"ids.json\", encoding=\"utf-8\") as f:\n",
" ids = json.load(f)\n",
"reg_modules = ids.get(\"Modules\", {})\n",
"print(f\" {len(pins)} pins, {len(reg_modules)} reg modules\")\n",
"\n",
"# ============================================================\n",
"# Table renderers\n",
"# ============================================================\n",
"\n",
"def eh(s): return escape(str(s))\n",
"\n",
"def render_table(rows, cols=None):\n",
" if not rows: return \"<p><em>no data</em></p>\"\n",
" cols = cols or list(rows[0].keys())\n",
" h = [\"<table><thead><tr>\"]\n",
" for c in cols: h.append(f\"<th>{eh(c)}</th>\")\n",
" h.append(\"</tr></thead><tbody>\")\n",
" for r in rows:\n",
" h.append(\"<tr>\")\n",
" for c in cols: h.append(f\"<td>{eh(str(r.get(c,\\\"\\\")))}</td>\")\n",
" h.append(\"</tr>\")\n",
" h.append(\"</tbody></table>\")\n",
" return \"\\n\".join(h)\n",
"\n",
"def render_bga_grid(raw):\n",
" if len(raw) < 2: return \"<p><em>no data</em></p>\"\n",
" ncols = len(raw[0]) - 1\n",
" h = ['<table class=\"compact pin-grid\"><thead><tr><th></th>']\n",
" for c in range(1, ncols+1): h.append(f'<th class=\"text-center\">{c}</th>')\n",
" h.append(\"</tr></thead><tbody>\")\n",
" for r in raw[1:]:\n",
" if not r: continue\n",
" h.append(f'<tr><th class=\"text-center\">{eh(r[0])}</th>')\n",
" for c in range(1, ncols+1):\n",
" val = r[c] if c < len(r) else \"\"\n",
" h.append(f'<td class=\"text-center text-mono\" style=\"font-size:6pt\">{eh(val)}</td>')\n",
" h.append(\"</tr>\")\n",
" h.append(\"</tbody></table>\")\n",
" return \"\\n\".join(h)\n",
"\n",
"def render_registers(data):\n",
" order = [\"SYS_REG\",\"SYS_ANA\",\"SYS_PLL\",\"DAQ_REG\",\"DAQ_PAR\",\"DAQ_FLT\",\"AWG_REG\",\"TRIG_CTRL\",\"PUMP_REG\",\"MIXER_REG\"]\n",
" descs = {\"SYS_REG\":\"系统控制\",\"SYS_ANA\":\"模拟配置\",\"SYS_PLL\":\"PLL 配置\",\"DAQ_REG\":\"DAQ 控制\",\"DAQ_PAR\":\"DAQ 参数\",\"DAQ_FLT\":\"DAQ 滤波器\",\"AWG_REG\":\"AWG 控制\",\"TRIG_CTRL\":\"触发控制\",\"PUMP_REG\":\"Pump 控制\",\"MIXER_REG\":\"混频器控制\"}\n",
" def bits_str(b):\n",
" if not b: return \"\"\n",
" return f\"[{b[0]}]\" if len(b)==1 or b[0]==b[-1] else f\"[{b[0]}:{b[-1]}]\"\n",
" def rng_str(r):\n",
" if not r or not r.get(\"value\"): return \"\"\n",
" t, v = r.get(\"type\",\"str\"), r.get(\"value\",\"\")\n",
" if t == \"str\":\n",
" if v == \"ANY\": return \"<em>任意</em>\"\n",
" if v == \"NA\": return \"<em>-</em>\"\n",
" return eh(str(v))\n",
" if t == \"list\" and isinstance(v,list): return f\"{v[0]}~{v[1]}\"\n",
" if t == \"set\" and isinstance(v,list): return \", \".join(str(x) for x in v)\n",
" return eh(str(v))\n",
" def perm_b(p):\n",
" m = {\"RW\":'<span class=\"perm-rw\">RW</span>',\"RO\":'<span class=\"perm-ro\">RO</span>',\"WC\":'<span class=\"perm-wc\">WC</span>'}\n",
" return m.get(p, eh(p))\n",
" h = []\n",
" for mn in order:\n",
" if mn not in data: continue\n",
" entries = data[mn]\n",
" h.append(f'<h3 id=\"reg-{mn.lower()}\">{mn} - {descs.get(mn,\"\")}</h3>')\n",
" h.append(f\"<p>{len(entries)} 个寄存器/存储段</p>\")\n",
" for sn, seg in entries.items():\n",
" addr = seg.get(\"OffsetAddress\",\"\"); perm = seg.get(\"Permission\",\"\")\n",
" sd = seg.get(\"SegDescription\",\"\"); fields = seg.get(\"Fields\",[])\n",
" h.append(f'<h4 id=\"reg-{sn}\">{sn.upper()}</h4>')\n",
" h.append(f'<p class=\"reg-summary\"><strong>地址:</strong><code>{eh(addr)}</code> <strong>权限:</strong>{perm_b(perm)} <strong>描述:</strong>{eh(sd)}</p>')\n",
" if fields:\n",
" h.append('<table class=\"compact\"><thead><tr><th style=\"width:11%\">Bits</th><th style=\"width:18%\">Field</th><th style=\"width:14%\">Reset</th><th style=\"width:12%\">Range</th><th>Description</th></tr></thead><tbody>')\n",
" for f in fields:\n",
" b = f.get(\"Bits\",[])\n",
" h.append(f'<tr><td class=\"bits\">{bits_str(b)}</td><td class=\"text-mono\">{eh(f.get(\"FieldName\",\"\"))}</td><td class=\"reset-val\">{eh(f.get(\"ResetValue\",\"\"))}</td><td>{rng_str(f.get(\"Range\"))}</td><td>{eh(f.get(\"FieldDescription\",\"\"))}</td></tr>')\n",
" h.append(\"</tbody></table>\")\n",
" h.append(\"\")\n",
" return \"\\n\".join(h)\n",
"\n",
"# ============================================================\n",
"# @import handler\n",
"# ============================================================\n",
"\n",
"IMPORT_RE = re.compile(r'^@import\\s+\"([^\"]+)\"\\s*$', re.MULTILINE)\n",
"\n",
"def handle_import(import_path):\n",
" fp = (BASE / import_path).resolve()\n",
" if not fp.exists():\n",
" return f'<div class=\"warning\">@import not found: {import_path}</div>'\n",
" fn = fp.name.lower(); sf = fp.suffix.lower()\n",
" try:\n",
" if \"pin_loc\" in fn: return render_bga_grid(csv_raw(fp))\n",
" if \"seg_define\" in fn:\n",
" segs, prev = [], \"\"\n",
" for r in csv_dicts(fp):\n",
" func = r.get(\"功能划分\",\"\")\n",
" if func == \"^\": func = prev\n",
" else: prev = func\n",
" segs.append({\"功能划分\":func,\"子模块\":r.get(\"子模块\",\"\"),\"开始地址\":r.get(\"开始地址\",\"\"),\"大小\":r.get(\"大小\",\"\")})\n",
" return render_table(segs, [\"功能划分\",\"子模块\",\"开始地址\",\"大小\"])\n",
" if sf == \".csv\": return render_table(csv_dicts(fp))\n",
" if sf == \".json\":\n",
" with open(fp, encoding=\"utf-8\") as f: return render_registers(json.load(f))\n",
" if sf == \".md\": return process_chapter(fp.read_text(encoding=\"utf-8\"))\n",
" return f'<p><em>unsupported: {import_path}</em></p>'\n",
" except Exception as e:\n",
" import traceback\n",
" return f'<div class=\"warning\">@import error: {e}<pre>{traceback.format_exc()}</pre></div>'\n",
"\n",
"def process_chapter(md_text):\n",
" processed = IMPORT_RE.sub(lambda m: handle_import(m.group(1)), md_text)\n",
" return md_lib.markdown(processed,\n",
" extensions=[\"tables\",\"fenced_code\",\"codehilite\",\"toc\",\"nl2br\",\"sane_lists\"],\n",
" extension_configs={\"codehilite\":{\"css_class\":\"highlight\",\"guess_lang\":False}})\n",
"\n",
"# ============================================================\n",
"# Process chapters\n",
"# ============================================================\n",
"\n",
"chapter_names = []\n",
"with open(BASE / \"project.yaml\", encoding=\"utf-8\") as f:\n",
" in_ch = False\n",
" for line in f:\n",
" line = line.rstrip()\n",
" if line.startswith(\"chapters:\"): in_ch = True; continue\n",
" if in_ch:\n",
" m = re.match(r\"^\\s*-\\s+(.+)$\", line)\n",
" if m: chapter_names.append(m.group(1).strip())\n",
" elif line and not line.startswith(\" \"): in_ch = False\n",
"\n",
"chapters = []\n",
"for i, name in enumerate(chapter_names):\n",
" fp = BASE / \"chapters\" / name\n",
" if not fp.exists(): continue\n",
" print(f\" [{i+1}] {name}\")\n",
" content = process_chapter(fp.read_text(encoding=\"utf-8\"))\n",
" m = re.search(r\"<h1[^>]*>(.*?)</h1>\", content)\n",
" chapters.append({\"id\":f\"ch{i+1}\",\"number\":str(i+1),\"title\":m.group(1) if m else fp.stem,\"content\":content})\n",
"\n",
"apd = BASE / \"chapters\" / \"appendix\"\n",
"if apd.exists():\n",
" for j, fp in enumerate(sorted(apd.glob(\"*.md\"), key=lambda p: p.name)):\n",
" label = chr(65+j)\n",
" print(f\" [appendix/{label}] {fp.name}\")\n",
" content = process_chapter(fp.read_text(encoding=\"utf-8\"))\n",
" m = re.search(r\"<h1[^>]*>(.*?)</h1>\", content)\n",
" chapters.append({\"id\":f\"app{label}\",\"number\":f\"附录 {label}\",\"title\":m.group(1) if m else fp.stem,\"content\":content})\n",
"\n",
"# ============================================================\n",
"# CSS (local)\n",
"# ============================================================\n",
"\n",
"css_path = BASE / \"doc_builder\" / \"themes\" / \"datasheet.css\"\n",
"CSS = css_path.read_text(encoding=\"utf-8\") if css_path.exists() else \"body{font-family:sans-serif}\"\n",
"\n",
"# ============================================================\n",
"# Assemble\n",
"# ============================================================\n",
"\n",
"TITLE = \"RBPU16 读出基带处理芯片\"\n",
"SUBTITLE = \"数据手册\"\n",
"AUTHOR = \"郭成\"\n",
"DATE = datetime.now().strftime(\"%Y-%m-%d\")\n",
"FEATURES = [\"DAC 12 GSPS\",\"ADC 6 GSPS\",\"16 Qubit 并行读出\",\"AWG 任意波形\",\"DAQ 数据采集\",\"片上 PLL\",\"LVDS 高速接口\",\"SPI 配置接口\"]\n",
"\n",
"feat_li = \"\".join(f\"<li>{f}</li>\" for f in FEATURES)\n",
"COVER = \"\".join([\n",
" '<div class=\"cover-page\">',\n",
" '<div class=\"product-family\">Quantum Readout SoC</div>',\n",
" '<h1>RBPU16<br>读出基带处理芯片</h1>',\n",
" '<div class=\"subtitle\">数据手册 &middot; User Manual</div>',\n",
" f'<ul class=\"feature-list\">{feat_li}</ul>',\n",
" f'<div class=\"cover-meta\"><p>版本 1.0 &middot; {DATE}</p><p>{AUTHOR}</p></div>',\n",
" \"</div>\",\n",
"])\n",
"\n",
"toc_items = [f'<li class=\"toc-h1\"><a href=\"#{c[\"id\"]}\">{c[\"number\"]} {eh(c[\"title\"])}</a></li>' for c in chapters]\n",
"TOC = '<div class=\"toc\"><h2>目录</h2><ul class=\"toc-list\">' + \"\\n\".join(toc_items) + \"</ul></div>\"\n",
"SIDEBAR = '<nav class=\"screen-toc\"><strong>目录</strong><ul>' + \"\\n\".join(toc_items) + \"</ul></nav>\"\n",
"ch_html = \"\\n\".join(f'<div class=\"chapter\" id=\"{c[\"id\"]}\">{c[\"content\"]}</div>' for c in chapters)\n",
"\n",
"FULL_HTML = \"\\n\".join([\n",
" \"<!DOCTYPE html>\",\n",
" '<html lang=\"zh-CN\">',\n",
" \"<head>\",\n",
" '<meta charset=\"utf-8\">',\n",
" '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">',\n",
" f\"<title>{TITLE} {SUBTITLE}</title>\",\n",
" f'<meta name=\"author\" content=\"{AUTHOR}\">',\n",
" \"<style>\", CSS, \"</style>\",\n",
" \"</head>\",\n",
" '<body class=\"auto-numbering\">',\n",
" COVER, TOC, ch_html,\n",
" '<div class=\"page-break\"></div>',\n",
" \"<h1>修订历史</h1>\",\n",
" '<table class=\"revision compact\">',\n",
" \"<thead><tr><th>版本</th><th>日期</th><th>修订内容</th><th>作者</th></tr></thead>\",\n",
" f\"<tbody><tr><td>1.0</td><td>{DATE}</td><td>初版发布</td><td>{AUTHOR}</td></tr></tbody>\",\n",
" \"</table>\",\n",
" SIDEBAR,\n",
" \"</body>\",\n",
" \"</html>\",\n",
"])\n",
"\n",
"# ============================================================\n",
"# Embed images as base64\n",
"# ============================================================\n",
"\n",
"print(\"\\n Embedding images...\")\n",
"SRC_RE = re.compile(r'src=\"(\\.\\./assets/[^\"]+)\"')\n",
"mimetypes.init()\n",
"\n",
"def embed_image(match):\n",
" rel_path = match.group(1)\n",
" img_path = (BASE / \"chapters\" / rel_path).resolve()\n",
" if not img_path.exists():\n",
" img_path = (BASE / rel_path).resolve()\n",
" if not img_path.exists():\n",
" return match.group(0)\n",
" mime, _ = mimetypes.guess_type(str(img_path))\n",
" if not mime: mime = \"image/png\"\n",
" with open(img_path, \"rb\") as f:\n",
" b64 = base64.b64encode(f.read()).decode(\"ascii\")\n",
" return f'src=\"data:{mime};base64,{b64}\"'\n",
"\n",
"FULL_HTML = SRC_RE.sub(embed_image, FULL_HTML)\n",
"\n",
"out = BASE / \"output\"\n",
"out.mkdir(exist_ok=True)\n",
"html_path = out / f\"{TITLE}_{SUBTITLE}.html\"\n",
"html_path.write_text(FULL_HTML, encoding=\"utf-8\")\n",
"print(f\"\\n {html_path.name} ({html_path.stat().st_size/1024:.0f} KB)\")\n",
"print(f\" Self-contained - ready to share.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Done\n",
"\n报告在 `output/` 目录,可直接发送给他人。"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.9.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}