321 lines
15 KiB
Plaintext
321 lines
15 KiB
Plaintext
{
|
||
"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\">数据手册 · User Manual</div>',\n",
|
||
" f'<ul class=\"feature-list\">{feat_li}</ul>',\n",
|
||
" f'<div class=\"cover-meta\"><p>版本 1.0 · {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
|
||
} |