{
 "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
}