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

no data

\"\n", " cols = cols or list(rows[0].keys())\n", " h = [\"\"]\n", " for c in cols: h.append(f\"\")\n", " h.append(\"\")\n", " for r in rows:\n", " h.append(\"\")\n", " for c in cols: h.append(f\"\")\n", " h.append(\"\")\n", " h.append(\"
{eh(c)}
{eh(str(r.get(c,\\\"\\\")))}
\")\n", " return \"\\n\".join(h)\n", "\n", "def render_bga_grid(raw):\n", " if len(raw) < 2: return \"

no data

\"\n", " ncols = len(raw[0]) - 1\n", " h = ['']\n", " for c in range(1, ncols+1): h.append(f'')\n", " h.append(\"\")\n", " for r in raw[1:]:\n", " if not r: continue\n", " h.append(f'')\n", " for c in range(1, ncols+1):\n", " val = r[c] if c < len(r) else \"\"\n", " h.append(f'')\n", " h.append(\"\")\n", " h.append(\"
{c}
{eh(r[0])}{eh(val)}
\")\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 \"任意\"\n", " if v == \"NA\": return \"-\"\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\":'RW',\"RO\":'RO',\"WC\":'WC'}\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'

{mn} - {descs.get(mn,\"\")}

')\n", " h.append(f\"

{len(entries)} 个寄存器/存储段

\")\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'

{sn.upper()}

')\n", " h.append(f'

地址:{eh(addr)} 权限:{perm_b(perm)} 描述:{eh(sd)}

')\n", " if fields:\n", " h.append('')\n", " for f in fields:\n", " b = f.get(\"Bits\",[])\n", " h.append(f'')\n", " h.append(\"
BitsFieldResetRangeDescription
{bits_str(b)}{eh(f.get(\"FieldName\",\"\"))}{eh(f.get(\"ResetValue\",\"\"))}{rng_str(f.get(\"Range\"))}{eh(f.get(\"FieldDescription\",\"\"))}
\")\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'
@import not found: {import_path}
'\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'

unsupported: {import_path}

'\n", " except Exception as e:\n", " import traceback\n", " return f'
@import error: {e}
{traceback.format_exc()}
'\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\"]*>(.*?)\", 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\"]*>(.*?)\", 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\"
  • {f}
  • \" for f in FEATURES)\n", "COVER = \"\".join([\n", " '
    ',\n", " '
    Quantum Readout SoC
    ',\n", " '

    RBPU16
    读出基带处理芯片

    ',\n", " '
    数据手册 · User Manual
    ',\n", " f'
      {feat_li}
    ',\n", " f'

    版本 1.0 · {DATE}

    {AUTHOR}

    ',\n", " \"
    \",\n", "])\n", "\n", "toc_items = [f'
  • {c[\"number\"]} {eh(c[\"title\"])}
  • ' for c in chapters]\n", "TOC = '

    目录

      ' + \"\\n\".join(toc_items) + \"
    \"\n", "SIDEBAR = '\"\n", "ch_html = \"\\n\".join(f'
    {c[\"content\"]}
    ' for c in chapters)\n", "\n", "FULL_HTML = \"\\n\".join([\n", " \"\",\n", " '',\n", " \"\",\n", " '',\n", " '',\n", " f\"{TITLE} {SUBTITLE}\",\n", " f'',\n", " \"\",\n", " \"\",\n", " '',\n", " COVER, TOC, ch_html,\n", " '
    ',\n", " \"

    修订历史

    \",\n", " '',\n", " \"\",\n", " f\"\",\n", " \"
    版本日期修订内容作者
    1.0{DATE}初版发布{AUTHOR}
    \",\n", " SIDEBAR,\n", " \"\",\n", " \"\",\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 }