修复渲染问题

This commit is contained in:
guocheng 2026-07-28 16:31:02 +08:00
parent 37ba99c570
commit bbe972e192
14 changed files with 302 additions and 73 deletions

View File

@ -38,6 +38,6 @@ jobs:
for f in output/*.html; do for f in output/*.html; do
echo "上传: $(basename "$f")" echo "上传: $(basename "$f")"
curl -sS --retries 3 -u "${WEBDAV_USER}:${WEBDAV_PASS}" \ curl -sS --retries 3 -u "${WEBDAV_USER}:${WEBDAV_PASS}" \
-T "$f" "${WEBDAV_URL}/$(basename "$f")" -T "$f" "${WEBDAV_URL}/docs/$(basename "$f")"
done done
echo "部署完成" echo "部署完成"

View File

@ -3,7 +3,7 @@
<!-- CI/CD 状态徽章 — 请将下面的 URL 中的 gitea.example.com 和仓库路径替换为你的实际地址 --> <!-- CI/CD 状态徽章 — 请将下面的 URL 中的 gitea.example.com 和仓库路径替换为你的实际地址 -->
[![Build Status](http://114.214.202.87:9030/readout/readout_program/actions/workflows/deploy.yml/badge.svg)](http://114.214.202.87:9030/readout/readout_program/actions) [![Build Status](http://114.214.202.87:9030/readout/readout_program/actions/workflows/deploy.yml/badge.svg)](http://114.214.202.87:9030/readout/readout_program/actions)
> 📖 **在线文档**: [https://gc-home.top/manual-doc-readout/](https://gc-home.top/manual-doc-readout/) > 📖 **在线文档**: [https://gc-home.top/docs/manual-doc-readout/](https://gc-home.top/docs/manual-doc-readout/)
本项目为 ez-Q 2.5 读出子系统的编程控制模型文档。采用 **"文档即代码" (Docs as Code)** 工作方式, 本项目为 ez-Q 2.5 读出子系统的编程控制模型文档。采用 **"文档即代码" (Docs as Code)** 工作方式,
通过 Markdown 纯文本写作、Git 版本控制和 Python 构建管道生成自包含 HTML 报告。 通过 Markdown 纯文本写作、Git 版本控制和 Python 构建管道生成自包含 HTML 报告。

View File

@ -60,9 +60,7 @@ ez-Q 2.5 ASIC平台读出子系统由读出基带板、读出混频板和读出
当软件需要对不同通道编程时其通过ip地址指定机箱、通过槽位号指定板卡、 当软件需要对不同通道编程时其通过ip地址指定机箱、通过槽位号指定板卡、
通过扩展地址指定同一个板卡内的多个通道、通过地址指定一个通道内的不同配置项。 通过扩展地址指定同一个板卡内的多个通道、通过地址指定一个通道内的不同配置项。
```
索引基地址定义详见[附录A. IDS寄存器索引表](#a-ids)的 [A.1 地址空间映射](#a1) 节。 索引基地址定义详见[附录A. IDS寄存器索引表](#a-ids)的 [A.1 地址空间映射](#a1) 节。
```
本文所述的三类编程通道具体定义如下: 本文所述的三类编程通道具体定义如下:
* ACQ 编程通道定义从混频板`rf_in`输入端口到基带板卡内部DAQ模块 * ACQ 编程通道定义从混频板`rf_in`输入端口到基带板卡内部DAQ模块

View File

@ -5,7 +5,7 @@ ACQ通道利用MCU产生32位的码字来控制读出行为32位的操控码
|比特位|名字|功能描述| |比特位|名字|功能描述|
|:-|:-|:-| |:-|:-|:-|
|[31:16]|QUBIT_EN| 16个量子比特使能高电平使能对应位量子比特| |[31:16]|QUBIT_EN| 16个量子比特使能高电平使能对应位量子比特|
|[15]|COUNT_SAVE_EN| 态计数据存储使能, 高电平使能态计数存储| |[15]|COUNT_SAVE_EN| 态计数据存储使能, 高电平使能态计数存储|
|[14]|STATE_SAVE_EN| 态数据存储使能, 高电平使能态数据存储| |[14]|STATE_SAVE_EN| 态数据存储使能, 高电平使能态数据存储|
|[13]|IQ_SAVE_EN| 解模数据存储使能, 高电平使能解模数据存储| |[13]|IQ_SAVE_EN| 解模数据存储使能, 高电平使能解模数据存储|
|[12]|WAVE_SAVE_EN| 波形数据存储使能, 高电平使能波形数据存储| |[12]|WAVE_SAVE_EN| 波形数据存储使能, 高电平使能波形数据存储|

View File

@ -23,7 +23,7 @@ $$ \text{Qubit }N\text{ DAQ\_PAR 基地址} = \mathtt{0x00500000} + N \times \ma
|:-|:-|:-|:-| |:-|:-|:-|:-|
|`0x00`|dds_fpw|[31:12] fcw|解模载波频率控制字:$F_c = fcw / 2^{20} \times F_s$| |`0x00`|dds_fpw|[31:12] fcw|解模载波频率控制字:$F_c = fcw / 2^{20} \times F_s$|
|^|^|[11:0] pcw|解模载波相位控制字:$\phi_c = pcw / 2^{12} \times 2\pi$| |^|^|[11:0] pcw|解模载波相位控制字:$\phi_c = pcw / 2^{12} \times 2\pi$|
|`0x04`|mtf_idx|[31:16] addr|权重数据起始地址(单位:时钟周期| |`0x04`|mtf_idx|[31:16] addr|权重数据起始地址(单位:时钟周期|
|^|^|[15:0] len|权重数据样本长度(单位:时钟周期)| |^|^|[15:0] len|权重数据样本长度(单位:时钟周期)|
|`0x08`|line0_ab|[31:16] a0|直线0 斜率系数 a16位有符号数| |`0x08`|line0_ab|[31:16] a0|直线0 斜率系数 a16位有符号数|
|^|^|[15:0] b0|直线0 斜率系数 b16位有符号数| |^|^|[15:0] b0|直线0 斜率系数 b16位有符号数|

View File

@ -104,8 +104,8 @@ mtf_idx 的 addr 和 len 单位为时钟周期ASIC 平台每周期 8 采样
|地址|寄存器|字段|值|说明| |地址|寄存器|字段|值|说明|
|:-|:-|:-|:-|:-| |:-|:-|:-|:-|:-|
|`0x00400044`|func_ctrl|iq_scale [3]|`0`|IQ 结果截位 [27:8]| |`0x00400044`|func_ctrl|iq_scale [3]|`0`|IQ 结果截位 [27:8]|
|`0x00400044`|func_ctrl|two_sta_en [2]|`0`|三态读出模式| |^|^|two_sta_en [2]|`0`|三态读出模式|
|`0x00400044`|func_ctrl|const_en [7]|`0`|使用查找表权重(非常数权重)| |^|^|const_en [7]|`0`|使用查找表权重(非常数权重)|
|`0x00400080`|mtf_idx_q0|addr [31:16] / len [15:0]|fallback 值|q0 匹配滤波器索引(查找表模式的 fallback| |`0x00400080`|mtf_idx_q0|addr [31:16] / len [15:0]|fallback 值|q0 匹配滤波器索引(查找表模式的 fallback|
|`0x004000C0`|dds_fpw_q0|fcw [31:12] / pcw [11:0]|fallback 值|q0 DDS参数查找表模式的 fallback| |`0x004000C0`|dds_fpw_q0|fcw [31:12] / pcw [11:0]|fallback 值|q0 DDS参数查找表模式的 fallback|

View File

@ -10,7 +10,7 @@ AWG模块仅使用码字指令的低13位其余位保留
|[10]|NCO_CLR_EN| NCO清零使能, 高有效| |[10]|NCO_CLR_EN| NCO清零使能, 高有效|
|[9]|MARK_EN| 标记脉冲输出使能, 高有效| |[9]|MARK_EN| 标记脉冲输出使能, 高有效|
|[8]|PUMP_EN| 泵浦脉冲输出使能, 高有效| |[8]|PUMP_EN| 泵浦脉冲输出使能, 高有效|
|[7:0]|WAVE_ID| 波形编号, 最大索256个波形输出| |[7:0]|WAVE_ID| 波形编号, 最大索256个波形输出|
- 码字比特[12]用于禁用波形输出通过使能该比特可以仅输出Pump或者Marker脉冲而不输出该指令码字[7:0]索引的波形。 - 码字比特[12]用于禁用波形输出通过使能该比特可以仅输出Pump或者Marker脉冲而不输出该指令码字[7:0]索引的波形。
- 码字比特[11]用于使能动态波形输出功能该功能直接使用wave_ctrl寄存器中来索引波形暂不使用该功能 - 码字比特[11]用于使能动态波形输出功能该功能直接使用wave_ctrl寄存器中来索引波形暂不使用该功能

View File

@ -15,9 +15,9 @@ EXC-Pump通道的全部寄存器可以被SPI和MCU同时访问
- `wave_ctrl`波形输出直接控制,可直接从波形仓库取采样点输出。 - `wave_ctrl`波形输出直接控制,可直接从波形仓库取采样点输出。
- 高16位为索引地址单位是时钟周期 - 高16位为索引地址单位是时钟周期
- 低16为为索引长度,单位是时钟周期 - 低16位为索引长度,单位是时钟周期
- `amplitude`调制幅度控制字 - `amplitude`调制幅度控制字
- 高16为幅度控制字acw范围-32768~32767归一化幅度$Amp = acw/2^{14}$ - 高16为幅度控制字acw范围-32768~32767归一化幅度$Amp = acw/2^{14}$
- `frequency`调制频率控制 - `frequency`调制频率控制
- 32比特频率控制字fcw$F_{nco}= fcw/2^{32}*F_s$,其中$F_s$是输出采样率。 - 32比特频率控制字fcw$F_{nco}= fcw/2^{32}*F_s$,其中$F_s$是输出采样率。
- `phase`调制相位控制 - `phase`调制相位控制

View File

@ -6,4 +6,4 @@
* 波形仓库的容量为128 KB在FPGA平台和ASIC平台下最大分别支持16 us和10 us波形输出。 * 波形仓库的容量为128 KB在FPGA平台和ASIC平台下最大分别支持16 us和10 us波形输出。
对于EXC输出频率$F_{out}$例如6.7 GHz在本振为$F_{LO}$(例如5.5GHz)本振频率下, 对于EXC输出频率$F_{out}$例如6.7 GHz在本振为$F_{LO}$(例如5.5GHz)本振频率下,
则存储区描绘的基带波形频率$F_{s}$为1.2 GHz $F_s = F_{out} - F_{LO}$。 则存储区描绘的基带波形频率$F_{IF}$为1.2 GHz $F_{IF} = F_{out} - F_{LO}$。

View File

@ -150,7 +150,7 @@ MCU程序加载到 MCU_INS`0x00700000`触发后MCU执行程序并生成
|:-|:-|:-|:-|:-| |:-|:-|:-|:-|:-|
|1|`0x00B00000`|AWG_WVE|波形采样点|256 × 16 bit| |1|`0x00B00000`|AWG_WVE|波形采样点|256 × 16 bit|
|2|`0x00A00000`|AWG_IDX[0]|addr / len|`0x0000` / `0x0020`| |2|`0x00A00000`|AWG_IDX[0]|addr / len|`0x0000` / `0x0020`|
|3|`0x00900034`|amplitude|acw|`8192`| |3|`0x00900034`|amplitude|acw|`16384`|
|3|`0x00900038`|frequency|fcw|`0`| |3|`0x00900038`|frequency|fcw|`0`|
|3|`0x0090003C`|phase|pcw|`0`| |3|`0x0090003C`|phase|pcw|`0`|
|3|`0x00900044`|func_ctrl|awg_mode, mix_mode, intp_sel|`0x0`| |3|`0x00900044`|func_ctrl|awg_mode, mix_mode, intp_sel|`0x0`|

View File

@ -213,26 +213,49 @@ def process_image_attrs(text):
# ---------- KaTeX 预处理 ---------- # ---------- KaTeX 预处理 ----------
# 公式占位符存储preprocess → postprocess 传递)
_MATH_PLACEHOLDERS = {}
def preprocess_katex(text): def preprocess_katex(text):
""" """
Markdown 中的 LaTeX 公式包装为原始 HTML Markdown 中的 LaTeX 公式替换为唯一占位符
防止 Markdown 解析器错误解释公式中的 _ * 等字符 防止 Markdown 解析器错误解释公式中的 _ * 等字符
占位符在 postprocess_katex 中还原为 HTML 包装的公式
""" """
_MATH_PLACEHOLDERS.clear()
# 块级公式 $$...$$ # 块级公式 $$...$$
def protect_display(match): def protect_display(match):
latex = match.group(1) latex = match.group(1).strip()
return f'<div class="math-display">$${latex}$$</div>' idx = len(_MATH_PLACEHOLDERS)
key = f"\x00MATH_DISPLAY_{idx}\x00"
_MATH_PLACEHOLDERS[key] = f'<div class="math-display">$${latex}$$</div>'
return key
text = re.sub(r'\$\$\s*(.+?)\s*\$\$', protect_display, text, flags=re.DOTALL) text = re.sub(r'\$\$\s*(.+?)\s*\$\$', protect_display, text, flags=re.DOTALL)
# 行内公式 $...$ # 行内公式 $...$
def protect_inline(match): def protect_inline(match):
latex = match.group(1) latex = match.group(1)
return f'<span class="math-inline">${latex}$</span>' idx = len(_MATH_PLACEHOLDERS)
key = f"\x00MATH_INLINE_{idx}\x00"
_MATH_PLACEHOLDERS[key] = f'<span class="math-inline">${latex}$</span>'
return key
text = re.sub(r'(?<!\d)\$([^$\s].*?[^$\s])\$(?!\d)', protect_inline, text) text = re.sub(r'(?<!\d)\$([^$\s].*?[^$\s])\$(?!\d)', protect_inline, text)
return text return text
def postprocess_katex(html):
"""将占位符还原为 HTML 包装的 LaTeX 公式。"""
result = html
for key, replacement in _MATH_PLACEHOLDERS.items():
result = result.replace(key, replacement)
return result
# ---------- 自定义代码块渲染 ---------- # ---------- 自定义代码块渲染 ----------
FENCED_CODEBLOCK_OPEN_RE = re.compile(r'^```(\w+)(?:\s+[^\n]*)?$') FENCED_CODEBLOCK_OPEN_RE = re.compile(r'^```(\w+)(?:\s+[^\n]*)?$')
@ -292,8 +315,11 @@ def render_custom_codeblocks_md(text, renderers):
# ^ : 与上方单元格合并rowspan # ^ : 与上方单元格合并rowspan
# < : 与左侧单元格合并colspan本单元格被吸收 # < : 与左侧单元格合并colspan本单元格被吸收
# > : 与右侧单元格合并colspan右侧单元格被吸收 # > : 与右侧单元格合并colspan右侧单元格被吸收
# 策略:展平时将合并标记替换为被合并单元格的内容, # 策略:展平时将合并标记替换为被合并单元格的内容并附加哨兵标记,
# 再由 postprocess_table_rowspan 检测重复内容生成 rowspan/colspan。 # 再由 postprocess_table_rowspan 根据哨兵标记生成 rowspan/colspan。
# 仅显式标记了 ^ / < / > 的单元格才会参与合并,避免跨逻辑组的过度合并。
MERGE_MARKER = "<!--MPEMERGE-->"
# 表格行: | cell | cell | ... | # 表格行: | cell | cell | ... |
TABLE_ROW_RE = re.compile(r'^\|.+\|$') TABLE_ROW_RE = re.compile(r'^\|.+\|$')
@ -387,22 +413,26 @@ def _flatten_mpe_table(table_lines):
for row in parsed: for row in parsed:
for col in range(num_cols - 2, -1, -1): for col in range(num_cols - 2, -1, -1):
if row[col].strip() == ">": if row[col].strip() == ">":
row[col] = row[col + 1] row[col] = MERGE_MARKER + row[col + 1]
# 第 2 遍:处理 <(左→右,复制左侧单元格内容) # 第 2 遍:处理 <(左→右,复制左侧单元格内容)
for row in parsed: for row in parsed:
for col in range(1, num_cols): for col in range(1, num_cols):
if row[col].strip() == "<": if row[col].strip() == "<":
row[col] = row[col - 1] row[col] = MERGE_MARKER + row[col - 1]
# 第 3 遍:处理 ^(上→下,复制上方单元格内容 # 第 3 遍:处理 ^(上→下,复制上方单元格内容,清除旧哨兵避免重复
prev_cells = _split_table_cells(header) prev_cells = _split_table_cells(header)
while len(prev_cells) < num_cols: while len(prev_cells) < num_cols:
prev_cells.append("") prev_cells.append("")
for row in parsed: for row in parsed:
for col in range(num_cols): for col in range(num_cols):
if row[col].strip() == "^": if row[col].strip() == "^":
row[col] = prev_cells[col] if col < len(prev_cells) else row[col] content = prev_cells[col] if col < len(prev_cells) else row[col]
# 剥离 prev_cells 中已有的哨兵标记,避免链式累积
if content.startswith(MERGE_MARKER):
content = content[len(MERGE_MARKER):]
row[col] = MERGE_MARKER + content
prev_cells = list(row) prev_cells = list(row)
# 重建表格行 # 重建表格行
@ -423,10 +453,11 @@ TBODY_RE = re.compile(r'(<tbody>.*?</tbody>)', re.DOTALL)
def postprocess_table_rowspan(html): def postprocess_table_rowspan(html):
""" """
扫描 HTML 表格的 <tbody>将连续内容相同的单元格合并 扫描 HTML 表格的 <tbody>将带有 MERGE_MARKER 哨兵的单元格合并
- 同一列上下连续相同 rowspan - 同一列上下连续相同且下方含哨兵 rowspan
- 同一行左右连续相同 colspan - 同一行左右连续相同且右侧含哨兵 colspan
preprocess_mpe_tables 配合MPE 标记展平后产生重复内容此处恢复为视觉合并 preprocess_mpe_tables 配合仅显式标记了 ^ / < / > 的单元格生成哨兵
避免跨逻辑组的过度合并
带有 class="no-rowspan" <table> 会被跳过不进行合并处理 带有 class="no-rowspan" <table> 会被跳过不进行合并处理
注意仅处理 <tbody> 内的 <tr>Python markdown tables 扩展的输出格式 注意仅处理 <tbody> 内的 <tr>Python markdown tables 扩展的输出格式
@ -467,16 +498,23 @@ def postprocess_table_rowspan(html):
colspan = [[1] * num_cols for _ in range(num_rows)] colspan = [[1] * num_cols for _ in range(num_rows)]
# ---- 计算 rowspan逐列扫描 ---- # ---- 计算 rowspan逐列扫描 ----
# 仅当下方单元格带 MERGE_MARKER 哨兵且内容匹配时才合并
for col in range(num_cols): for col in range(num_cols):
row = 0 row = 0
while row < num_rows: while row < num_rows:
count = 1 count = 1
r = row + 1 r = row + 1
while r < num_rows: while r < num_rows:
if (row_cells[r][col]["text"] == row_cells[row][col]["text"] below_text = row_cells[r][col]["text"]
and row_cells[row][col]["text"] != ""): above_text = row_cells[row][col]["text"]
# 下方单元格必须带哨兵标记,且去除哨兵后与上方内容一致
if (below_text.startswith(MERGE_MARKER)
and below_text[len(MERGE_MARKER):] == above_text
and above_text != ""):
count += 1 count += 1
covered[r][col] = True covered[r][col] = True
# 将合并单元格的内容统一为去除哨兵的版本
row_cells[r][col]["text"] = above_text
r += 1 r += 1
else: else:
break break
@ -494,11 +532,16 @@ def postprocess_table_rowspan(html):
count = 1 count = 1
c = col + 1 c = col + 1
while c < num_cols: while c < num_cols:
right_text = row_cells[row][c]["text"]
left_text = row_cells[row][col]["text"]
# 右侧单元格必须带哨兵标记,且去除哨兵后与左侧内容一致
if (not covered[row][c] if (not covered[row][c]
and row_cells[row][c]["text"] == row_cells[row][col]["text"] and right_text.startswith(MERGE_MARKER)
and row_cells[row][col]["text"] != ""): and right_text[len(MERGE_MARKER):] == left_text
and left_text != ""):
count += 1 count += 1
covered[row][c] = True covered[row][c] = True
row_cells[row][c]["text"] = left_text
c += 1 c += 1
else: else:
break break
@ -808,6 +851,9 @@ def main():
# Markdown → HTML # Markdown → HTML
chapter_html = markdown_to_html(text) chapter_html = markdown_to_html(text)
# KaTeX 还原(占位符 → HTML 包装的公式)
chapter_html = postprocess_katex(chapter_html)
# 图片 base64 内嵌 # 图片 base64 内嵌
base_dirs = [ch_path.parent, ASSETS_DIR, PROJECT_ROOT] base_dirs = [ch_path.parent, ASSETS_DIR, PROJECT_ROOT]
chapter_html = embed_images(chapter_html, base_dirs) chapter_html = embed_images(chapter_html, base_dirs)

View File

@ -0,0 +1,185 @@
"""IDS 数据共享模块。
提供 JSON 加载和 HTML 表格渲染函数
render_ids_acq_map / render_ids_exc_map / render_ids_daq_reg / render_ids_awg_reg 复用
"""
import json
from pathlib import Path
from collections import OrderedDict
# 输出顺序(先列出的模块排在前面)
ACQ_MODULE_ORDER = ["MCU_INS", "MCU_DAT", "DAQ_REG", "DAQ_PAR", "DAQ_FLT"]
EXC_MODULE_ORDER = ["MCU_INS", "MCU_DAT", "AWG_REG", "AWG_IDX", "AWG_WVE"]
# ---------- 数据加载 ----------
def _load_ids(filepath: str) -> dict:
"""加载 IDS JSON 文件并返回解析后的 dict。"""
path = Path(filepath)
if not path.exists():
raise FileNotFoundError(f"IDS 文件不存在: {path}")
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
# ---------- HTML 工具 ----------
def _td(text: str, **kwargs) -> str:
"""生成 <td> 单元格,支持 rowspan / style 等属性。"""
attrs = " ".join(f'{k}="{v}"' for k, v in kwargs.items() if v is not None)
return f"<td {attrs}>{text}</td>" if attrs else f"<td>{text}</td>"
def _th(text: str) -> str:
return f"<th>{text}</th>"
def _bit_range_str(bits: list) -> str:
"""将 [15, 8] 或 [7] 转为位段字符串。"""
if len(bits) == 1:
return str(bits[0])
return f"{bits[0]}:{bits[-1]}"
# ---------- 映射表渲染 ----------
def render_mapping_acq_table(data: dict) -> str:
"""渲染 msmt_acq 通道模块基地址表。"""
mi = data.get("MappingInfo", {}).get("msmt_acq", {})
return _render_mapping_table(mi, ACQ_MODULE_ORDER, "ACQ")
def render_mapping_exc_table(data: dict) -> str:
"""渲染 msmt_exc 通道模块基地址表。"""
mi = data.get("MappingInfo", {}).get("msmt_exc", {})
return _render_mapping_table(mi, EXC_MODULE_ORDER, "EXC")
def _render_mapping_table(mapping: dict, module_order: list, label: str) -> str:
"""通用的通道模块基地址渲染。"""
rows = []
# 收集所有模块名
all_modules = set()
for ch_info in mapping.values():
all_modules.update(ch_info.get("Modules", {}).keys())
# 按 module_order 排序,未在列表中的放最后
ordered = [m for m in module_order if m in all_modules]
ordered += sorted(all_modules - set(module_order))
for mod_name in ordered:
for ch in sorted(mapping.keys(), key=int):
info = mapping[ch]
modules = info.get("Modules", {})
if mod_name not in modules:
continue
m = modules[mod_name]
exaddr = info.get("Exaddr", "")
cid = info.get("Cid", "")
rows.append(
"<tr>"
f"<td>ch{ch}</td>"
f"<td>{exaddr}</td>"
f"<td>{cid}</td>"
f"<td><code>{mod_name}</code></td>"
f"<td><code>{m.get('StartAddress', '')}</code></td>"
f"<td>{m.get('Size', '')}</td>"
f"<td>{m.get('Info', '')}</td>"
"</tr>"
)
if not rows:
return "<p>(无映射数据)</p>"
header = (
"<tr>"
"<th>通道</th><th>Exaddr</th><th>Cid</th>"
"<th>模块</th><th>起始地址</th><th>大小</th><th>说明</th>"
"</tr>"
)
return f"<table>{header}{''.join(rows)}</table>"
# ---------- 寄存器表渲染 ----------
def render_daq_reg_table(data: dict) -> str:
"""渲染 DAQ_REG 寄存器汇总表 + 位域明细。"""
regs = data.get("Modules", {}).get("DAQ_REG", {})
return _render_reg_table(regs, "DAQ_REG")
def render_awg_reg_table(data: dict) -> str:
"""渲染 AWG_REG 寄存器汇总表 + 位域明细。"""
regs = data.get("Modules", {}).get("AWG_REG", {})
return _render_reg_table(regs, "AWG_REG")
def _render_reg_table(regs: dict, label: str) -> str:
"""通用寄存器表渲染(汇总表 + 每位域明细)。"""
if not regs:
return "<p>(无寄存器数据)</p>"
# 按偏移地址排序
sorted_regs = sorted(regs.values(), key=lambda r: int(r.get("OffsetAddress", "0x0"), 16))
# ---- 汇总表 ----
summary_rows = []
for r in sorted_regs:
name = list(regs.keys())[list(regs.values()).index(r)]
offset = r.get("OffsetAddress", "")
perm = r.get("Permission", "")
desc = r.get("SegDescription", "")
summary_rows.append(
"<tr>"
f"<td><strong>{name}</strong></td>"
f"<td><code>{offset}</code></td>"
f"<td>{perm}</td>"
f"<td>{desc}</td>"
"</tr>"
)
summary = (
"<table>"
"<tr><th>寄存器名</th><th>偏移地址</th><th>权限</th><th>描述</th></tr>"
f"{''.join(summary_rows)}"
"</table>"
)
# ---- 位域明细表(仅含字段数 > 0 的寄存器) ----
detail_parts = []
for r in sorted_regs:
fields = r.get("Fields", [])
if not fields:
continue
name = list(regs.keys())[list(regs.values()).index(r)]
offset = r.get("OffsetAddress", "")
field_rows = []
for f in fields:
bits = f.get("Bits", [])
field_name = f.get("FieldName", "")
fdesc = (f.get("FieldDescription", "") or "").replace("\n", "<br>")
reset = f.get("ResetValue", "")
field_rows.append(
"<tr>"
f"<td><code>[{_bit_range_str(bits)}]</code></td>"
f"<td><strong>{field_name}</strong></td>"
f"<td>{reset}</td>"
f"<td>{fdesc}</td>"
"</tr>"
)
if field_rows:
detail_parts.append(
f"<h4>{name}{offset}</h4>"
"<table>"
"<tr><th>位段</th><th>字段名</th><th>复位值</th><th>描述</th></tr>"
f"{''.join(field_rows)}"
"</table>"
)
if detail_parts:
return f"{summary}<br>{''.join(detail_parts)}"
return summary