Compare commits
No commits in common. "0feba0b5e1e52980e747b455f01e4887bd45808c" and "8159e34981e5e41978575616ab792365734e32fb" have entirely different histories.
0feba0b5e1
...
8159e34981
|
|
@ -1,7 +1 @@
|
||||||
# Build output
|
读出子系统编程控制模型.html
|
||||||
output/
|
|
||||||
|
|
||||||
# Python
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
*.pyo
|
|
||||||
108
CLAUDE.md
108
CLAUDE.md
|
|
@ -1,108 +0,0 @@
|
||||||
# CLAUDE.md — ez-Q 2.5 读出子系统编程控制模型
|
|
||||||
|
|
||||||
## 项目概述
|
|
||||||
|
|
||||||
本项目为 ez-Q 2.5 量子测控系统读出子系统的编程控制模型文档。采用 **"文档即代码" (Docs as Code)** 工作方式:
|
|
||||||
Markdown 纯文本写作 + Git 版本控制 + Python 构建管道 → 自包含 HTML 报告。
|
|
||||||
|
|
||||||
## 文档即代码约定
|
|
||||||
|
|
||||||
### 目录结构
|
|
||||||
```
|
|
||||||
project.yaml # 项目元数据与章节列表
|
|
||||||
chapters/ # Markdown 章节源文件(唯一编辑目标)
|
|
||||||
assets/ # 图片资源
|
|
||||||
data/ # 结构化数据源(CSV/YAML/JSON,通过 @import 引用)
|
|
||||||
doc_builder/ # Python 构建工具
|
|
||||||
build.py # 构建入口
|
|
||||||
templates/ # HTML 模板
|
|
||||||
themes/ # CSS 样式
|
|
||||||
renderers/ # 自定义渲染器(@import / 代码块渲染)
|
|
||||||
processors/ # 数据处理器(供渲染器复用)
|
|
||||||
checks/ # 检查脚本(构建时自动运行)
|
|
||||||
output/ # 构建产物(gitignore)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 文件命名规范
|
|
||||||
- 章节文件: `{序号}-{英文slug}.md`(如 `04-02-acq-codeword.md`)
|
|
||||||
- 子章节用二级编号: `{章}-{节}-{slug}.md`(如 `05-03-exc-wavetable.md`)
|
|
||||||
- 图片文件: 语义化命名(如 `readout_ro.png`),统一放在 `assets/`
|
|
||||||
|
|
||||||
### 标题层级
|
|
||||||
- 每章开头使用 `#` (H1)
|
|
||||||
- 节使用 `##` (H2)
|
|
||||||
- 子节使用 `###` (H3)
|
|
||||||
- **禁止在子章节文件中使用 H1**,确保拼接后层级正确
|
|
||||||
|
|
||||||
### 图片规范
|
|
||||||
- **从 chapters/ 引用项目根目录 assets/**: ``
|
|
||||||
- 此路径同时兼容标准 Markdown 预览和构建时的 base64 内嵌
|
|
||||||
- **禁止绝对路径**(尤其是 Windows 盘符路径如 `D:/code/...`)
|
|
||||||
- 构建时自动内嵌为 base64,生成自包含 HTML
|
|
||||||
|
|
||||||
### 交叉引用
|
|
||||||
- 内部引用: `详见 [标题锚点](#标题锚点)`
|
|
||||||
- 外部引用: `[文档名](path/to/doc.md)`
|
|
||||||
|
|
||||||
### 非标准 Markdown 扩展
|
|
||||||
本项目采用 docs-as-code skill 规范的非标准扩展(构建时生效,Markdown 预览中可忽略):
|
|
||||||
- `@import "../data/file.csv"` — 将数据文件或 Markdown 注入当前章节
|
|
||||||
- `@import "../data/file.csv" using render_custom` — 使用 `doc_builder/renderers/render_custom.py` 渲染
|
|
||||||
- `{w=50%}` — 图片属性控制(预留)
|
|
||||||
- 自定义代码块渲染器:`doc_builder/renderers/render_<lang>.py`
|
|
||||||
|
|
||||||
## 构建流程
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 安装依赖(首次)
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# 构建 HTML
|
|
||||||
python doc_builder/build.py
|
|
||||||
|
|
||||||
# 输出: output/<标题>.html
|
|
||||||
```
|
|
||||||
|
|
||||||
## 文件组织表
|
|
||||||
|
|
||||||
| 章节 | 文件 | 内容 |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| §1 | `chapters/01-changelog.md` | 修订记录 |
|
|
||||||
| §2 | `chapters/02-preface.md` | 前言(目的、范围、术语等) |
|
|
||||||
| §3 | `chapters/03-overview.md` | 编程控制模型概述 |
|
|
||||||
| §4 | `chapters/04-00-acq-model.md` | ACQ 通道编程模型(数据路径) |
|
|
||||||
| §4.1 | `chapters/04-01-acq-downconversion.md` | 下变频电路配置 |
|
|
||||||
| §4.2 | `chapters/04-02-acq-codeword.md` | ACQ 码字功能定义 |
|
|
||||||
| §4.3 | `chapters/04-03-acq-registers.md` | ACQ 寄存器功能定义 |
|
|
||||||
| §4.4 | `chapters/04-04-acq-matched-filter.md` | 匹配滤波器 |
|
|
||||||
| §4.5 | `chapters/04-05-acq-data-processing.md` | 采集数据处理 |
|
|
||||||
| §4.6 | `chapters/04-06-acq-pipeline-demo.md` | ACQ 全流程控制示例 |
|
|
||||||
| §5 | `chapters/05-00-exc-model.md` | EXC-Pump 编程模型(数据路径) |
|
|
||||||
| §5.1 | `chapters/05-01-exc-codeword.md` | EXC 码字功能定义 |
|
|
||||||
| §5.2 | `chapters/05-02-exc-registers.md` | 寄存器功能定义 |
|
|
||||||
| §5.3 | `chapters/05-03-exc-wavetable.md` | 波形索引表定义 |
|
|
||||||
| §5.4 | `chapters/05-04-exc-waveform-store.md` | 波形仓库定义 |
|
|
||||||
| §5.5 | `chapters/05-05-exc-upconversion.md` | EXC 上变频电路配置 |
|
|
||||||
| §5.6 | `chapters/05-06-pump-config.md` | Pump 模拟电路配置 |
|
|
||||||
| §5.7 | `chapters/05-07-exc-pipeline-demo.md` | EXC-Pump 全流程控制示例 |
|
|
||||||
|
|
||||||
## 编辑工作流
|
|
||||||
|
|
||||||
| 修改内容 | 编辑目标 | 构建方式 |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| 正文内容 | `chapters/*.md` | `python doc_builder/build.py` |
|
|
||||||
| 章节顺序 | `project.yaml` → `chapters:` 列表 | 同上 |
|
|
||||||
| 图片 | `assets/` | 同上(自动内嵌) |
|
|
||||||
| 结构化数据 | `data/*.csv` / `data/*.yaml` / `data/*.json` | 同上(通过 @import 注入) |
|
|
||||||
| 渲染器逻辑 | `doc_builder/renderers/*.py` | 同上(自动发现) |
|
|
||||||
| 数据处理逻辑 | `doc_builder/processors/*.py` | 同上(自动发现) |
|
|
||||||
| 检查规则 | `doc_builder/checks/*.py` | 同上(构建时自动运行) |
|
|
||||||
| HTML 样式 | `doc_builder/themes/*.css` | 同上 |
|
|
||||||
| HTML 模板 | `doc_builder/templates/report.html` | 同上 |
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. 本项目是 **硬件寄存器级编程手册**,包含大量位域表格和时序说明
|
|
||||||
2. 平台差异(FPGA vs ASIC)使用代码块标注
|
|
||||||
3. 数学公式使用 `$...$`(行内)和 `$$...$$`(块级)LaTeX 语法,构建时自动保护公式不被 Markdown 转义破坏
|
|
||||||
4. 编辑以 `chapters/` 下的文件为准,项目根目录无旧版 MPE 兼容文件
|
|
||||||
72
README.md
72
README.md
|
|
@ -1,69 +1,7 @@
|
||||||
# 读出子系统编程控制模型
|
# 读出子系统编程模型
|
||||||
|
本项目为ez-Q 2.5 读出子系统的编程控制模型,预期用户通过阅读本文档能够通过软件操控读出系统开展实验。
|
||||||
<!-- CI/CD 状态徽章 — 请将下面的 URL 中的 gitea.example.com 和仓库路径替换为你的实际地址 -->
|
|
||||||
[](http://114.214.202.87:9030/readout/readout_program/actions)
|
|
||||||
|
|
||||||
> 📖 **在线文档**: [https://gc-home.top/manual-doc-readout/](https://gc-home.top/manual-doc-readout/)
|
|
||||||
|
|
||||||
本项目为 ez-Q 2.5 读出子系统的编程控制模型文档。采用 **"文档即代码" (Docs as Code)** 工作方式,
|
|
||||||
通过 Markdown 纯文本写作、Git 版本控制和 Python 构建管道生成自包含 HTML 报告。
|
|
||||||
|
|
||||||
**主要内容**
|
**主要内容**
|
||||||
|
* RI通道编程
|
||||||
- EXC 通道编程(激励生成发送)
|
* RO通道编程
|
||||||
- ACQ 通道编程(回波采集处理)
|
* Pump通道编程
|
||||||
- Pump 通道编程(泵浦信号控制)
|
|
||||||
|
|
||||||
## 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
project.yaml # 项目配置(标题、作者、版本、章节列表)
|
|
||||||
chapters/ # Markdown 章节源文件
|
|
||||||
assets/ # 图片资源
|
|
||||||
data/ # 结构化数据源(CSV/YAML/JSON,通过 @import 引用)
|
|
||||||
doc_builder/ # Python 构建工具
|
|
||||||
build.py # 构建入口
|
|
||||||
templates/ # HTML 模板(含 A4 封面)
|
|
||||||
themes/ # CSS 样式(屏幕 + 打印)
|
|
||||||
renderers/ # 自定义渲染器(@import / 代码块)
|
|
||||||
processors/ # 数据处理器
|
|
||||||
checks/ # 检查脚本
|
|
||||||
output/ # 构建产物(.gitignore)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
### 构建 HTML 报告
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 安装依赖(首次)
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# 2. 构建
|
|
||||||
python doc_builder/build.py
|
|
||||||
|
|
||||||
# 3. 打开 output/<标题>.html 即可浏览
|
|
||||||
```
|
|
||||||
|
|
||||||
### 编辑文档
|
|
||||||
|
|
||||||
- 修改 `chapters/` 下的 `.md` 文件
|
|
||||||
- 添加/删除/重新排序章节:编辑 `project.yaml` 的 `chapters` 列表
|
|
||||||
- 修改样式:编辑 `doc_builder/themes/report.css`
|
|
||||||
- 修改页面布局:编辑 `doc_builder/templates/report.html`
|
|
||||||
- 图片放在 `assets/` 目录,章节中通过 `../assets/xxx.png` 引用
|
|
||||||
|
|
||||||
### 非标准 Markdown 扩展
|
|
||||||
|
|
||||||
本项目支持以下扩展语法(仅在构建时生效):
|
|
||||||
|
|
||||||
- **`@import "path"`** — 将数据文件或 Markdown 注入当前章节
|
|
||||||
- **`@import "path" using render_xxx`** — 使用自定义渲染器
|
|
||||||
- **`{w=50%}`** — 图片属性控制
|
|
||||||
|
|
||||||
### 编辑器推荐
|
|
||||||
|
|
||||||
- VS Code + Markdown 预览
|
|
||||||
- Typora
|
|
||||||
- Obsidian
|
|
||||||
- 任何支持 Markdown 的编辑器
|
|
||||||
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 686 KiB |
|
|
@ -1,7 +0,0 @@
|
||||||
# 1. 修订记录
|
|
||||||
|
|
||||||
|版本|修订日期|修订原因|修订内容|修订人|
|
|
||||||
|:-|:-|:-|:-|:-|
|
|
||||||
|v0.1|2025/10/27|统一格式|初始版本|郭成|
|
|
||||||
|v0.2|2025/10/31|评审意见|内容补充|郭成|
|
|
||||||
|v0.3|2026/07/27|用户反馈|内容梳理|郭成|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# 2. 前言
|
|
||||||
|
|
||||||
## 2.1. 目的与范围
|
|
||||||
|
|
||||||
本文档的目的是介绍读出子系统激励产生和采集处理相关控制,
|
|
||||||
该文档适用于ez-Q 2.5 FPGA/ASIC平台读出子系统编程。
|
|
||||||
本文档作为开放文档供大家阅读。
|
|
||||||
|
|
||||||
## 2.2. 阅读对象
|
|
||||||
|
|
||||||
本文档的预期读者是驱动开发工程师、使用本系统的终端用户以及对该芯片工作原理感兴趣的读者。
|
|
||||||
|
|
||||||
## 2.3. 文档概述
|
|
||||||
|
|
||||||
本文档首先介绍了测控系统总体的编程对象和规范,
|
|
||||||
针对读出ACQ/EXC/Pump三种类型通道对应的ACQ/EXC-Pump编程模型进行了详细介绍。
|
|
||||||
|
|
||||||
## 2.4. 引用文档
|
|
||||||
|
|
||||||
|文档编号|标题|版本|
|
|
||||||
|:-|:-|:-|
|
|
||||||
|-|读出子系统历史无关配置集.md|V1.0|
|
|
||||||
|-|读出子系统IDS表.xls|V1.0|
|
|
||||||
|ez-Q 2.5-SDD-04|ez-Q 2.5 测控系统研制项目指令集设计报告_V1.0.docx|V1.0|
|
|
||||||
|
|
||||||
## 2.5. 术语定义
|
|
||||||
|
|
||||||
|名字|全称|解释|
|
|
||||||
|:-|:-|:-|
|
|
||||||
|ACQ|Acquisition|读出回波采集处理通道|
|
|
||||||
|EXC|Excitation|读出激励生成发送通道|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
# 3. 编程控制模型概述
|
|
||||||
|
|
||||||
## 3.1. 测控系统编程概述
|
|
||||||
|
|
||||||
超导量子计算机利用微波信号来驱动量子比特和读出量子比特状态,量子比特不同类型的操作依赖不同类型的信号来控制。
|
|
||||||
ez-Q 2.5测控系统包含5种物理通道,对应5类硬件接口,分别是ACQ、EXC、Pump、XYZ/Reset和ZCP通道;
|
|
||||||
系统具有7种控制信号,对应7种编程对象,分别是EXC、ACQ、Pump、XY、Reset、Z和ZCP信号;
|
|
||||||
系统具有4种控制模型,对应4种编程类型,分别是ACQ、EXC-Pump、XY/Reset和Z/ZCP模型,分类关系如下表所示。
|
|
||||||
|
|
||||||
|5种物理通道|7种控制对象|4种控制模型|
|
|
||||||
|:-:|:-:|:-:|
|
|
||||||
|XYZ通道|XY 信号|XY/Reset控制模型|
|
|
||||||
|^|Reset信号|^|
|
|
||||||
|^|Z信号|Z/ZCP控制模型|
|
|
||||||
|ZCP通道|ZCP信号|Z/ZCP控制模型|
|
|
||||||
|ACQ通道|ACQ(RO)信号|ACQ控制模型|
|
|
||||||
|EXC通道|EXC(RI)信号|EXC-Pump控制模型|
|
|
||||||
|Pump通道|Pump信号|^|
|
|
||||||
|
|
||||||
- 测控系统中的每种控制模型实现的功能都是通过以下四类数据进行定义,
|
|
||||||
- 通道微控制器码字指令
|
|
||||||
- 通道寄存器配置数据
|
|
||||||
- 通道SRAM配置数据
|
|
||||||
- 通道模拟电路配置
|
|
||||||
|
|
||||||
- 测控系统的寄存器、配置数据和模拟电路配置返回数据统一采用**大端字节序**。
|
|
||||||
|
|
||||||
通道寄存器配置数据支持微控制器实时修改,从而让通道的输入/输出控制具备动态控制能力。
|
|
||||||
ACQ和EXC-Pump通道配置寄存器定义参考[读出子系统IDS表.xls](TODO)的`DAQ_REG`和`AWG_REG`页。
|
|
||||||
|
|
||||||
本文档通过对不同编程模型下码字指令、通道寄存器配置数据、通道SRAM配置数据和通道模拟电路配置进行介绍,
|
|
||||||
旨在让用户掌握对读出激励信号的产生和采集信号处理的编程方法。
|
|
||||||
其中码字指令通过MCU来产生,MCU的编程关键参数如下:
|
|
||||||
|
|
||||||
* ACQ通道和EXC-Pump通道使用**相同MCU**;
|
|
||||||
* MCU分别使用**16 KB**的ITCM和DTCM;
|
|
||||||
* FPGA平台MCU主时钟频率为**250 MHz**,
|
|
||||||
* ASIC平台MCU主钟频率暂定**750 MHz**;
|
|
||||||
* MCU以固定的**3个时钟周期每指令**的速度运行;
|
|
||||||
* MCU通过**0x100000**地址访问MCU数据空间;
|
|
||||||
* MCU通道**0x200000**地址访问通道配置寄存器空间;
|
|
||||||
|
|
||||||
针对具体的指令定义和使用方法,
|
|
||||||
读者可查看[《量子编程指令集》](TODO)了解相关信息。
|
|
||||||
|
|
||||||
## 3.2. 读出子系统编程
|
|
||||||
|
|
||||||
ez-Q 2.5 ASIC平台读出子系统由读出基带板、读出混频板和读出泵浦板三个硬件组成
|
|
||||||
- 三个板卡以PXIe板卡的形式安装在机箱中位置相邻的三个槽位上
|
|
||||||
- 混频板的槽位号比基带板大1,泵浦板槽位号比基带板槽位号小1
|
|
||||||
|
|
||||||
因此可以通过仅指定读出基带板槽位号来定位混频板和泵浦板槽位号,
|
|
||||||
读出子系统硬件架构如下图所示。
|
|
||||||
- 读出基带板负责产生、输出、采集和处理基带信号以及为泵浦通道产生使能信号;
|
|
||||||
- 读出混频板自身产生一个本振信号,用于实现基带信号与射频信号之间的转换;
|
|
||||||
- 而读出泵浦板负责产生一个指定功率频率的单音信号,并在外部使能信号控制下输出;
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
当软件需要对不同通道编程时,其通过ip地址指定机箱、通过槽位号指定板卡、
|
|
||||||
通过扩展地址指定同一个板卡内的多个通道、通过地址指定一个通道内的不同配置项。
|
|
||||||
|
|
||||||
```
|
|
||||||
索引基地址由《读出子系统IDS表.xls》的mapping页定义,本文不对地址翻译进行赘述。
|
|
||||||
```
|
|
||||||
|
|
||||||
本文所述的三类编程通道具体定义如下:
|
|
||||||
* ACQ 编程通道定义从混频板`rf_in`输入端口到基带板卡内部DAQ模块;
|
|
||||||
* EXC 编程通道定义为从基带板内部AWG模块混频板到`rf_out`接口;
|
|
||||||
* Pump 编程通道定义为从基带板卡内部AWG模块到泵浦板`pump_out`接口;
|
|
||||||
|
|
||||||
ASIC和FPGA平台具有以下区别:
|
|
||||||
* ez-Q 2.5 FPGA平台读出子系统混频板和泵浦板复用同一个硬件板卡。
|
|
||||||
* ez-Q 2.5 FPGA平台将读出基带芯片RBPU和FPGA集成在一个FPGA中。
|
|
||||||
* ez-Q 2.5 FPGA平台使用外部商用ADC、DAC和PLL来替换RBPU的对应功能。
|
|
||||||
* ez-Q 2.5 FPGA平台ADC、DAC采样率为4 Gsps, ASIC平台暂定为6 Gsps
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
## 4.1. 下变频电路配置
|
|
||||||
|
|
||||||
来自量子比特RO端口的射频信号需要经过前端模拟电路处理后才能够被读出基带处理单元采集处理。
|
|
||||||
前端下变频电路包括增益和本振,这里需要注意同一个混频板上四个通道上下变频使用同一个本振信号。
|
|
||||||
|
|
||||||
|配置名|描述|
|
|
||||||
|:-:|:-:|
|
|
||||||
|lo_freq|变频本振频率设置|
|
|
||||||
|mix_gain|下变频增益设置|
|
|
||||||
|
|
||||||
```
|
|
||||||
目前ez-Q 2.5 FPGA平台暂不支持下变频增益
|
|
||||||
mix_gain 设置;仅支持 lo_freq 变频本振频率设置
|
|
||||||
```
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
## 4.3. ACQ寄存器功能定义
|
|
||||||
|
|
||||||
ACQ通道寄存器的地址空间可以被SPI和MCU同时访问,
|
|
||||||
因此用户可通过SPI或者MCU设置参数,建议:
|
|
||||||
```
|
|
||||||
静态参数通过SPI配置完成就保持不变,
|
|
||||||
需要动态控制的参数通过MCU来实时更新。
|
|
||||||
```
|
|
||||||
|
|
||||||
|名字|功能描述|
|
|
||||||
|:-|:-|
|
|
||||||
|function|DAQ功能控制,控制运行模式等|
|
|
||||||
|sample_depth|波形采样深度控制,单位是时钟周期个数|
|
|
||||||
|mtf_idx_q[15:0]|解模参数控制,分别对应16个频点|
|
|
||||||
|dds_fpw_q[15:0]|解模频率相位控制,分别对应16个频点|
|
|
||||||
|
|
||||||
- sample_depth用于控制波形采集模式下采集波形的长度,单位是时钟周期;
|
|
||||||
- mtf_idx_q在使能寄存器控制情况下,用于直接索引匹配滤波器权重/系数;
|
|
||||||
- 高16比特对应索引地址,单位是时钟周期
|
|
||||||
- 低16比特对应索引长度,单位是时钟周期
|
|
||||||
- dds_fpw_q在使能寄存器控制情况下,用于控制解模载波的频率和相位;
|
|
||||||
- 高20位对应载波频率控制字fcw,$F_{c}=fcw/2^{20}*sample\\_rate$
|
|
||||||
- 低12位对应载波相位控制字pcw,$\\phi_{c} = pcw/2^{12}*2*\\pi$
|
|
||||||
- function用来控制整个读出基带板数据处理行为;
|
|
||||||
|
|
||||||
function功能寄存器与实验控制相关的具体控制位包括:
|
|
||||||
|
|
||||||
|比特位|名字|功能描述|
|
|
||||||
|:-|:-|:-|
|
|
||||||
|[15:8]|WEIGHT_IQ| 常数权重值,8比特有符号数|
|
|
||||||
|[7]|CONST_EN| 常数权重使能, 高有效|
|
|
||||||
|[6:4]|STEP_CTRL| 计算权重模式步长控制|
|
|
||||||
|[3]|IQ_SCALE| 解模动态范围设置|
|
|
||||||
|[2]|TWO_STA_EN| 两态读出使能, 高电平使能|
|
|
||||||
|
|
||||||
* WEIGHT_IQ 常数权重值,可用于长时间解模
|
|
||||||
* CONST_EN 常数权重使能
|
|
||||||
* `CONST_EN=0`时,权重数据通过`mtf_idx_q`来索引得到
|
|
||||||
* `CONST_EN=1`来启用常量权重功能,此时权重值为`WEIGHT_IQ`。
|
|
||||||
* STEP_CTRL 权重点步长控制
|
|
||||||
- `STEP_CTRL==0` 1个权重数据点可对应1个时钟周期采样点。
|
|
||||||
- `STEP_CTRL==1` 1个权重数据点可对应2个时钟周期采样点。
|
|
||||||
- `STEP_CTRL==2` 1个权重数据点可对应4个时钟周期采样点。
|
|
||||||
- `STEP_CTRL==3` 1个权重数据点可对应8个时钟周期采样点。
|
|
||||||
- `STEP_CTRL==4` 1个权重数据点可对应16个时钟周期采样点。
|
|
||||||
* IQ_SCALE 解模结果截断位置控制
|
|
||||||
- `IQ_SCALE =0` IQ结果截位[27:8]
|
|
||||||
- `IQ_SCALE =1` IQ结果截位[31:12]
|
|
||||||
* TWO_STA_EN 两态读出使能
|
|
||||||
- `TWO_STA_EN==0` 默认三态读出
|
|
||||||
- `TWO_STA_EN==1` 使能二态读出
|
|
||||||
|
|
||||||
```
|
|
||||||
ez-Q 2.5 FPGA平台采用系数直读模式,因此不支持系数生产相关功能。
|
|
||||||
即在ez-Q 2.5 FPGA平台下,dds_fpw 寄存器无实际效果;
|
|
||||||
function寄存器WEIGHT_IQ,CONST_EN, STEP_CTRL 位无实际效果
|
|
||||||
```
|
|
||||||
解模完成后,可以通过`TWO_STA_EN`来控制是否启动2态判定,
|
|
||||||
当`TWO_STA_EN=1`时,可以只配置一组直线方程系数进行态判断。
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
## 4.4. 匹配滤波器
|
|
||||||
|
|
||||||
匹配滤波器在FPGA/ASIC两种平台下由于资源不同实现的形式也不同,区别如下:
|
|
||||||
* 由于ASIC多计算少存储,因而ASIC平台使用系数计算模式以节省存储资源。
|
|
||||||
* 由于FPGA 多存储少计算,因此FPGA平台使用系数直读模式以节省DSP资源。
|
|
||||||
|
|
||||||
### 4.4.1. 读出参数存储
|
|
||||||
|
|
||||||
系数计算模式下,匹配滤波器的系数由DDS生成的载波乘以权重参数得到。
|
|
||||||
- 载波的频率和相位由dds_pfd控制,其高20为作为频率控制字,低12位作为相位控制字。
|
|
||||||
- 权重参数的选择由mtf_idx控制,其高16位作为地址,低16位作为长度。
|
|
||||||
- mtf_idx索引得到的权重数据颗粒度是权重数据点,一个数据点可对应多个周期采样点;
|
|
||||||
- 寄存器dds_pfw_q 和mtf_idx_q 的详细定义与查找表中定义保持相同。
|
|
||||||
|
|
||||||
读出系统的的读出参数存储格式如下图所示。
|
|
||||||
FPGA平台下的系数直读模式只使用`Ctrl`部分中的数据,并忽略`dds_pfw`控制字.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 4.4.2. 匹配滤波器系数
|
|
||||||
|
|
||||||
ez-Q 2.5 FPGA平台使用系数直读模式,其需要额外的存储空间来配置匹配滤波器系数。
|
|
||||||
- 其中mtf_idx的含义从索引权重数据变为索引匹配滤波器系数。
|
|
||||||
- 匹配滤波器系数索引的粒度是时钟周期,
|
|
||||||
- 在FPGA平台下每个时钟周期对应16个采样点数据。
|
|
||||||
- 匹配滤波器系数采样点采用8比特数据位宽,因此1个周期数据位宽为128 bit,
|
|
||||||
|
|
||||||
匹配滤波器系数存储结构如下图所示。
|
|
||||||
- 匹配滤波器的I和Q数据分开存储,每个比特的I、Q数据容量分别为16 KB。
|
|
||||||
- I路数据偏移地址为x*32KB,Q路数据偏移地址为x*32KB+16 KB,其中x为Qubit序号,范围为0~15。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
## 4.5. 采集数据处理
|
|
||||||
|
|
||||||
ACQ通道采集的数据以流的形式按照先后顺序缓存,
|
|
||||||
缓存到设定数据量后或者计时器超时后数据被打包成帧上传,
|
|
||||||
上位机驱动解帧后以流模式将数据返回给调用接口。
|
|
||||||
当采集多种数据时,用户需要维护采集数据的顺序,
|
|
||||||
从而解析返回数据的含义。
|
|
||||||
建议一个实验仅仅采集一种类型数据以简化数据处理。
|
|
||||||
|
|
||||||
### 4.5.1. 采集波形数据
|
|
||||||
|
|
||||||
波形数据为模拟信号经ADC量化和符号转换后结果,
|
|
||||||
用8比特二进制补码表示,范围对应-128~127;
|
|
||||||
|
|
||||||
* ASIC平台1个时钟周期对应8个采样点(8个字节)
|
|
||||||
* FPGA平台1个时钟周期对应16个采样点(16字节)
|
|
||||||
* 数据采用大端字节序,数据遵循先采先到顺序
|
|
||||||
|
|
||||||
### 4.5.2. 采集IQ数据
|
|
||||||
|
|
||||||
解模计算公式如下:
|
|
||||||
$$
|
|
||||||
I = \\sum_{i=1}^{m}{\\sum_{j=1}^n{\\frac{S_{i,j} * iw_{i,j} * \\cos(\\omega_i t+\\phi_i)}{scale_i} }}
|
|
||||||
$$
|
|
||||||
$$
|
|
||||||
Q = \\sum_{i=1}^{m}{\\sum_{j=1}^n{\\frac{S_{i,j} * qw_{i,j} * \\sin(\\omega_i t+\\phi_i)}{scale_i} }}
|
|
||||||
$$
|
|
||||||
|
|
||||||
* $m$表示解模次数,$n$表示解模的采样点个数;
|
|
||||||
* $S_{i,j}$为输入波形的第i次解模第j个采样点,8比特有符号数
|
|
||||||
* $iw_{i,j}$、$qw_{i,j}$为i、q权重的第i次解模第j个采样点,8比特有符号数
|
|
||||||
* $\\omega_i$、$\\phi_i$分别为第i次解模的频率和相位
|
|
||||||
* $scale_i$为第i次解模的范围选择,可设置为1或者16
|
|
||||||
|
|
||||||
解模频率应设置为输入波形对应频率一遍使得解模结果频率为0,
|
|
||||||
解模结果相位等于解模设置相位设置减去输入波形相位。
|
|
||||||
若解模相位为0,则结果相位等于波形相位取反。
|
|
||||||
解模求和结果I、Q分别用32比特二进制补码表示,解模的结果存储格式如下:
|
|
||||||
|
|
||||||
* 1个完整的数据包含I,Q(8字节)
|
|
||||||
* I先到达,Q后到达
|
|
||||||
* I, Q数据采用大端字节,都占用4个字节
|
|
||||||
|
|
||||||
### 4.5.3. 采集态数据
|
|
||||||
|
|
||||||
* 1个完整数据包含16个qubit态信息(4字节)
|
|
||||||
* 每个量子比特态信息用2个比特表示
|
|
||||||
* 高位表示高序号,即32比特对应{q15,q14,...,q0};
|
|
||||||
|
|
||||||
### 4.5.4. 采态计数数据
|
|
||||||
|
|
||||||
* 1个完整数据包含4个计数器数据(16字节)
|
|
||||||
* 4个计数数据顺序为0态计数器、1态计数器、2态计数器、3态计数器
|
|
||||||
* 计数器采用大端字节序,为32比特无符号数
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
## 5.2. 寄存器功能定义
|
|
||||||
|
|
||||||
EXC-Pump通道的全部寄存器可以被SPI和MCU同时访问,
|
|
||||||
用户可以根据需要决定使用SPI还是MCU来控制寄存器的值。
|
|
||||||
|
|
||||||
|名字|功能描述|
|
|
||||||
|:-|:-|
|
|
||||||
|wave_ctrl|寄存器索引波形,包含地址和长度信息|
|
|
||||||
|amplitude|波形输出调制幅度|
|
|
||||||
|Frequency|调制载波频率|
|
|
||||||
|Phase|调制载波相位|
|
|
||||||
|Function|AWG工作模式定义|
|
|
||||||
|pump_ctrl|pump使能脉冲控制|
|
|
||||||
|mark_ctrl|标记使能脉冲控制|
|
|
||||||
|
|
||||||
- `wave_ctrl`波形输出直接控制,可直接从波形仓库取采样点输出。
|
|
||||||
- 高16位为索引地址,单位是时钟周期
|
|
||||||
- 低16为为索引长度,单位是时钟周期
|
|
||||||
- `amplitude`调制幅度控制字
|
|
||||||
- 高16为幅度控制字acw,范围0~16384,归一化幅度$Amp = acw/2^{16}$
|
|
||||||
- `frequency`调制频率控制
|
|
||||||
- 32比特频率控制字fcw,$F_{nco}= fcw/2^{32}*F_s$,其中$F_s$是输出采样率。
|
|
||||||
- `phase`调制相位控制
|
|
||||||
- 高16位相位控制字pcw: $\\phi_{nco} = pcw/2^{16}*2*\\pi$。
|
|
||||||
- `funciton`寄存器用于设置AWG的工作模式
|
|
||||||
- `pump_ctrl`泵浦脉冲使能控制
|
|
||||||
- 高16位控制输出延迟时钟周期,范围(1~65535)
|
|
||||||
- 低16位控制附加持续时钟周期,范围(1~65535)
|
|
||||||
- `mark_ctrl`标记脉冲使能控制
|
|
||||||
- 高16位控制输出延迟时钟周期,范围(1~65535)
|
|
||||||
- 低16位控制脉冲持续时钟周期,范围(1~65535)
|
|
||||||
|
|
||||||
function的详细控制如下所示:
|
|
||||||
|
|
||||||
|比特位|名字|功能描述|
|
|
||||||
|:-|:-|:-|
|
|
||||||
|[3]|INTP_SEL| 插值模式选择,1:半带插值,0:邻近插值|
|
|
||||||
|[2]|MIX_MODE| 混频模式选择,1:混频模式,0:基带模式|
|
|
||||||
|[1:0]|AWG_MODE| AWG模式, 00:直出,01:调制,10:Hilbert,11:NCO|
|
|
||||||
|
|
||||||
* `INTP_SEL`在FPGA平台下受限于DSP资源,只能设置为邻近插值模式。
|
|
||||||
* `MIX_MODE`用于直接输出射频信号,由于采用了模拟混频方案,仅使用基带模式。
|
|
||||||
* `AWG_MODE`设置AWG模式,实验选用直出模式或者调制模式,其余两种模式用于调试。
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
## 5.3. 波形索引表定义
|
|
||||||
|
|
||||||
波形查找表的深度为256条(1 kB),每个条目的位宽为32比特,
|
|
||||||
其通过8比特的波形id来索引输出波形的参数(地址和长度),最大支持256种不同的输出波形。
|
|
||||||
|
|
||||||
索引表格式定义如下图所示: wave_id是mcu产生的码字,其可以作为地址索引波形控制参数。
|
|
||||||
波形控制参数包括波形地址`addr`和波形长度`len`参数,颗粒度是时钟周期。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
## 5.4. 波形仓库定义
|
|
||||||
|
|
||||||
* ASIC平台下,每个时钟周期对应8个采样点,采样点个数需要为8的整数倍,数据更新率为6 Gsps,每个采样点持续时间为167 皮秒(6 GS/s)。
|
|
||||||
* 在FPGA平台下,每个时钟周期对应16个采样点,采样点个数需要为16的整数倍,数据更新率为 4 GSps,每个采样点持续时间为250皮秒。
|
|
||||||
* 每个采样点为16比特的二进制补码数据;
|
|
||||||
* 波形仓库的容量为128 KB,在FPGA平台和ASIC平台下最大分别支持16 us和10 us波形输出。
|
|
||||||
|
|
||||||
对于EXC输出频率$F_{out}$(例如6.7 GHz),在本振为$F_{LO}$(例如5.5GHz)本振频率下,
|
|
||||||
则存储区描绘的基带波形频率$F_{s}$为1.2 GHz ,$F_s = F_{out} - F_{LO}$。
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
## 5.5. EXC上变频电路配置
|
|
||||||
|
|
||||||
来自读出基带板输出端口的中频信号需要经过混频板上变频电路处理后才能够被发送到量子芯片。
|
|
||||||
前端上变频电路包括增益和本振,这里需要注意同一个混频板上四个通道上下变频使用同一个本振信号。
|
|
||||||
目前ez-Q 2.5 FPGA平台硬件暂不支持变频增益设置。
|
|
||||||
|
|
||||||
|配置名|描述|
|
|
||||||
|:-:|:-:|
|
|
||||||
|lo_freq|变频本振频率设置|
|
|
||||||
|
|
||||||
* lo_freq:32比特整数,范围[5400000, 5600000]单位是kHz
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
## 5.6. Pump模拟电路配置
|
|
||||||
|
|
||||||
对处理器Pump通道的编程包括模拟电路配置项和使能配置项。模拟电路用于将Pump通道配置输出一个指定功率和频率的微波信号,而使能配置项用于控制微波信号的实时开关。
|
|
||||||
|
|
||||||
Pump输出时,需要使能输出并配置好输出频率和功率,以下是具体配置项。
|
|
||||||
|
|
||||||
|配置名|描述|
|
|
||||||
|:-|:-|
|
|
||||||
|pump_freq|pump信号输出频率|
|
|
||||||
|pump_power|pump信号输出功率|
|
|
||||||
|pump_enable|pump信号输出使能|
|
|
||||||
|
|
||||||
* pump_freq: 32比特整数,范围[7000000, 9000000], 单位是kHz
|
|
||||||
* pump_power: 32比特整数,范围[-1100, -300], 具体映射关系取决于硬件
|
|
||||||
* pump_enable: 0x11 为使能,0x00为关闭
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
# doc_builder — Docs as Code 构建工具包
|
|
||||||
|
|
@ -1,815 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Docs as Code — 构建入口
|
|
||||||
|
|
||||||
读取 project.yaml → 逐章节处理 @import → Markdown → HTML → 图片内嵌
|
|
||||||
→ 组装 → 注入锚点 → 提取目录 → 模板渲染 → 自包含 HTML 报告。
|
|
||||||
|
|
||||||
用法:
|
|
||||||
python doc_builder/build.py
|
|
||||||
|
|
||||||
输出:
|
|
||||||
output/<标题>.html (自包含 HTML,可离线分发)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import csv
|
|
||||||
import importlib.util
|
|
||||||
import io
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from datetime import date
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import markdown
|
|
||||||
import yaml
|
|
||||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|
||||||
|
|
||||||
# ---------- 路径配置 ----------
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
||||||
CHAPTERS_DIR = PROJECT_ROOT / "chapters"
|
|
||||||
ASSETS_DIR = PROJECT_ROOT / "assets"
|
|
||||||
OUTPUT_DIR = PROJECT_ROOT / "output"
|
|
||||||
BUILDER_DIR = PROJECT_ROOT / "doc_builder"
|
|
||||||
TEMPLATES_DIR = BUILDER_DIR / "templates"
|
|
||||||
THEMES_DIR = BUILDER_DIR / "themes"
|
|
||||||
RENDERERS_DIR = BUILDER_DIR / "renderers"
|
|
||||||
CHECKS_DIR = BUILDER_DIR / "checks"
|
|
||||||
|
|
||||||
# ---------- 配置加载 ----------
|
|
||||||
|
|
||||||
def load_config():
|
|
||||||
path = PROJECT_ROOT / "project.yaml"
|
|
||||||
if not path.exists():
|
|
||||||
raise FileNotFoundError("找不到 project.yaml")
|
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
|
||||||
config = yaml.safe_load(f) or {}
|
|
||||||
|
|
||||||
config.setdefault("title", "未命名文档")
|
|
||||||
config.setdefault("subtitle", "")
|
|
||||||
config.setdefault("author", "")
|
|
||||||
config.setdefault("version", "")
|
|
||||||
config.setdefault("doc_type", "技术文档")
|
|
||||||
config.setdefault("logo", "")
|
|
||||||
config.setdefault("lang", "zh-CN")
|
|
||||||
config.setdefault("date", date.today().isoformat())
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- 插件发现 ----------
|
|
||||||
|
|
||||||
def load_plugin_modules(directory):
|
|
||||||
"""扫描目录下的 *.py 文件并导入为模块字典。"""
|
|
||||||
modules = {}
|
|
||||||
if not directory.exists():
|
|
||||||
return modules
|
|
||||||
for py_file in sorted(directory.glob("*.py")):
|
|
||||||
if py_file.name.startswith("_"):
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
spec = importlib.util.spec_from_file_location(py_file.stem, py_file)
|
|
||||||
if spec is None or spec.loader is None:
|
|
||||||
continue
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
modules[py_file.stem] = mod
|
|
||||||
print(f" 已加载: {py_file.stem}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" 警告: 加载 {py_file.name} 失败: {e}")
|
|
||||||
return modules
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- @import 处理 ----------
|
|
||||||
|
|
||||||
IMPORT_RE = re.compile(r'^@import\s+"([^"]+)"(?:\s+using\s+(\S+))?\s*$', re.MULTILINE)
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_import_path(raw, base_dir):
|
|
||||||
path = Path(raw)
|
|
||||||
if path.is_absolute():
|
|
||||||
return path
|
|
||||||
return (base_dir / path).resolve()
|
|
||||||
|
|
||||||
|
|
||||||
def default_data_renderer(filepath):
|
|
||||||
"""默认数据文件渲染:CSV → HTML 表格,YAML/JSON → 代码块。"""
|
|
||||||
ext = filepath.suffix.lower()
|
|
||||||
if ext == ".csv":
|
|
||||||
with open(filepath, newline="", encoding="utf-8") as f:
|
|
||||||
reader = csv.reader(f)
|
|
||||||
rows = list(reader)
|
|
||||||
if not rows:
|
|
||||||
return ""
|
|
||||||
lines = ["<table>"]
|
|
||||||
lines.append("<tr>" + "".join(f"<th>{c}</th>" for c in rows[0]) + "</tr>")
|
|
||||||
for row in rows[1:]:
|
|
||||||
lines.append("<tr>" + "".join(f"<td>{c}</td>" for c in row) + "</tr>")
|
|
||||||
lines.append("</table>")
|
|
||||||
return "".join(lines)
|
|
||||||
elif ext in (".yaml", ".yml"):
|
|
||||||
with open(filepath, "r", encoding="utf-8") as f:
|
|
||||||
data = yaml.safe_load(f) or {}
|
|
||||||
return f"<pre><code>{yaml.dump(data, allow_unicode=True)}</code></pre>"
|
|
||||||
elif ext == ".json":
|
|
||||||
with open(filepath, "r", encoding="utf-8") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
return f"<pre><code>{json.dumps(data, ensure_ascii=False, indent=2)}</code></pre>"
|
|
||||||
else:
|
|
||||||
content = filepath.read_text(encoding="utf-8")
|
|
||||||
return f"<pre><code>{content}</code></pre>"
|
|
||||||
|
|
||||||
|
|
||||||
def render_with_module(renderers, renderer_name, filepath):
|
|
||||||
"""调用指定渲染器。"""
|
|
||||||
if renderer_name not in renderers:
|
|
||||||
raise ValueError(f"找不到指定渲染器:{renderer_name}")
|
|
||||||
func = getattr(renderers[renderer_name], "render", None)
|
|
||||||
if not callable(func):
|
|
||||||
raise ValueError(f"{renderer_name} 没有 render(content: str) -> str 函数")
|
|
||||||
return func(str(filepath))
|
|
||||||
|
|
||||||
|
|
||||||
def rewrite_image_paths(text, source_dir):
|
|
||||||
"""把被导入 Markdown 中的相对图片路径改为绝对路径,供后续 base64 嵌入。"""
|
|
||||||
def repl(match):
|
|
||||||
alt = match.group(1)
|
|
||||||
src = match.group(2)
|
|
||||||
if src.startswith(("http://", "https://", "data:")) or Path(src).is_absolute():
|
|
||||||
return match.group(0)
|
|
||||||
abs_path = (source_dir / src).resolve()
|
|
||||||
return f''
|
|
||||||
return re.sub(r'!\[([^\]]*)\]\(([^)]+)\)', repl, text)
|
|
||||||
|
|
||||||
|
|
||||||
def process_imports(text, base_dir, renderers, _imported=None):
|
|
||||||
"""扫描并替换 @import 指令。支持 .md 注入和数据文件导入。"""
|
|
||||||
if _imported is None:
|
|
||||||
_imported = set()
|
|
||||||
|
|
||||||
def repl(match):
|
|
||||||
raw = match.group(1)
|
|
||||||
renderer_name = match.group(2)
|
|
||||||
target = resolve_import_path(raw, base_dir)
|
|
||||||
|
|
||||||
# 外部 Markdown 导入
|
|
||||||
if raw.endswith(".md"):
|
|
||||||
if target in _imported:
|
|
||||||
raise RuntimeError(f"检测到循环 @import:{target}")
|
|
||||||
_imported.add(target)
|
|
||||||
if not target.exists():
|
|
||||||
raise FileNotFoundError(f"找不到要导入的 Markdown 文件:{target}")
|
|
||||||
md = target.read_text(encoding="utf-8")
|
|
||||||
md = rewrite_image_paths(md, target.parent)
|
|
||||||
md = process_imports(md, target.parent, renderers, _imported)
|
|
||||||
return md
|
|
||||||
|
|
||||||
# 数据导入
|
|
||||||
if not target.exists():
|
|
||||||
raise FileNotFoundError(f"找不到要导入的数据文件:{target}")
|
|
||||||
if renderer_name:
|
|
||||||
renderer_name = renderer_name.removesuffix(".py")
|
|
||||||
return render_with_module(renderers, renderer_name, target)
|
|
||||||
return default_data_renderer(target)
|
|
||||||
|
|
||||||
return IMPORT_RE.sub(repl, text)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- 图片属性 {w=50%} ----------
|
|
||||||
|
|
||||||
IMAGE_ATTR_RE = re.compile(r'!\[([^\]]*)\]\(([^)]+)\)\{([^}]*)\}')
|
|
||||||
|
|
||||||
|
|
||||||
def parse_attrs(attr_str):
|
|
||||||
attrs = {}
|
|
||||||
for part in attr_str.split(","):
|
|
||||||
part = part.strip()
|
|
||||||
if "=" in part:
|
|
||||||
k, v = part.split("=", 1)
|
|
||||||
attrs[k.strip()] = v.strip()
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
def process_image_attrs(text):
|
|
||||||
"""处理 {w=50%} 图片属性语法,转换为 HTML img 标签。"""
|
|
||||||
def repl(match):
|
|
||||||
alt = match.group(1)
|
|
||||||
src = match.group(2)
|
|
||||||
attr_str = match.group(3)
|
|
||||||
attrs = parse_attrs(attr_str)
|
|
||||||
style = ""
|
|
||||||
if "w" in attrs:
|
|
||||||
style = f'width:{attrs["w"]};'
|
|
||||||
cls = attrs.get("class", "")
|
|
||||||
cls_attr = f' class="{cls}"' if cls else ""
|
|
||||||
style_attr = f' style="{style}"' if style else ""
|
|
||||||
return f'<img src="{src}" alt="{alt}"{cls_attr}{style_attr} />'
|
|
||||||
return IMAGE_ATTR_RE.sub(repl, text)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- KaTeX 预处理 ----------
|
|
||||||
|
|
||||||
def preprocess_katex(text):
|
|
||||||
"""
|
|
||||||
将 Markdown 中的 LaTeX 公式包装为原始 HTML,
|
|
||||||
防止 Markdown 解析器错误解释公式中的 _ * 等字符。
|
|
||||||
"""
|
|
||||||
# 块级公式 $$...$$
|
|
||||||
def protect_display(match):
|
|
||||||
latex = match.group(1)
|
|
||||||
return f'<div class="math-display">$${latex}$$</div>'
|
|
||||||
text = re.sub(r'\$\$\s*(.+?)\s*\$\$', protect_display, text, flags=re.DOTALL)
|
|
||||||
|
|
||||||
# 行内公式 $...$
|
|
||||||
def protect_inline(match):
|
|
||||||
latex = match.group(1)
|
|
||||||
return f'<span class="math-inline">${latex}$</span>'
|
|
||||||
text = re.sub(r'(?<!\d)\$([^$\s].*?[^$\s])\$(?!\d)', protect_inline, text)
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- 自定义代码块渲染 ----------
|
|
||||||
|
|
||||||
FENCED_CODEBLOCK_OPEN_RE = re.compile(r'^```(\w+)(?:\s+[^\n]*)?$')
|
|
||||||
|
|
||||||
|
|
||||||
def render_custom_codeblocks_md(text, renderers):
|
|
||||||
"""把有对应渲染器的 fenced code block 替换为 HTML。"""
|
|
||||||
lines = text.splitlines(keepends=True)
|
|
||||||
out_lines = []
|
|
||||||
i = 0
|
|
||||||
while i < len(lines):
|
|
||||||
line = lines[i]
|
|
||||||
m = FENCED_CODEBLOCK_OPEN_RE.match(line)
|
|
||||||
if not m:
|
|
||||||
out_lines.append(line)
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
lang = m.group(1)
|
|
||||||
renderer_name = f"render_{lang.replace('-', '_')}"
|
|
||||||
if renderer_name not in renderers:
|
|
||||||
out_lines.append(line)
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
func = getattr(renderers[renderer_name], "render", None)
|
|
||||||
if not callable(func):
|
|
||||||
out_lines.append(line)
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 收集到闭合 fence
|
|
||||||
start = i + 1
|
|
||||||
j = start
|
|
||||||
while j < len(lines) and lines[j].strip() != '```':
|
|
||||||
j += 1
|
|
||||||
if j >= len(lines):
|
|
||||||
out_lines.append(line)
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
content = "".join(lines[start:j]).rstrip("\n")
|
|
||||||
rendered = func(content)
|
|
||||||
if not rendered.endswith("\n"):
|
|
||||||
rendered += "\n"
|
|
||||||
out_lines.append(rendered)
|
|
||||||
# 跳过 fence 后的换行
|
|
||||||
if j + 1 < len(lines) and lines[j + 1] == "\n":
|
|
||||||
i = j + 2
|
|
||||||
else:
|
|
||||||
i = j + 1
|
|
||||||
|
|
||||||
return "".join(out_lines)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- MPE 表格合并(^ / < / > 语法 → 展平为标准 Markdown) ----------
|
|
||||||
# ^ : 与上方单元格合并(rowspan)
|
|
||||||
# < : 与左侧单元格合并(colspan,本单元格被吸收)
|
|
||||||
# > : 与右侧单元格合并(colspan,右侧单元格被吸收)
|
|
||||||
# 策略:展平时将合并标记替换为被合并单元格的内容,
|
|
||||||
# 再由 postprocess_table_rowspan 检测重复内容生成 rowspan/colspan。
|
|
||||||
|
|
||||||
# 表格行: | cell | cell | ... |
|
|
||||||
TABLE_ROW_RE = re.compile(r'^\|.+\|$')
|
|
||||||
|
|
||||||
|
|
||||||
def preprocess_mpe_tables(text):
|
|
||||||
"""
|
|
||||||
将 Markdown Preview Enhanced 风格的单元格合并标记展平为标准 Markdown:
|
|
||||||
^ → 替换为同列上一行的内容(纵向合并)
|
|
||||||
< → 替换为同行左侧的内容(横向合并)
|
|
||||||
> → 替换为同行右侧的内容(横向合并)
|
|
||||||
展平后交给标准 Markdown 渲染器,再由 postprocess_table_rowspan 恢复合并。
|
|
||||||
"""
|
|
||||||
lines = text.split("\n")
|
|
||||||
out = []
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
while i < len(lines):
|
|
||||||
line = lines[i]
|
|
||||||
|
|
||||||
if not (TABLE_ROW_RE.match(line) and i + 1 < len(lines) and _is_separator(lines[i + 1])):
|
|
||||||
out.append(line)
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
table_lines = [line]
|
|
||||||
i += 1
|
|
||||||
table_lines.append(lines[i])
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
while i < len(lines) and TABLE_ROW_RE.match(lines[i]):
|
|
||||||
table_lines.append(lines[i])
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
has_merge = any(_has_merge_cell(tl) for tl in table_lines[2:])
|
|
||||||
|
|
||||||
if has_merge:
|
|
||||||
out.extend(_flatten_mpe_table(table_lines))
|
|
||||||
else:
|
|
||||||
out.extend(table_lines)
|
|
||||||
|
|
||||||
return "\n".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def _is_separator(line):
|
|
||||||
"""判断是否为表格分隔行: |---|:---|...| 或 MPE 风格 |:-:|"""
|
|
||||||
return bool(re.match(r'^\|[\s:]*-+[\s:]*\|', line))
|
|
||||||
|
|
||||||
|
|
||||||
def _has_merge_cell(row_line):
|
|
||||||
"""判断表格行是否包含 MPE 合并标记(^, <, >)。"""
|
|
||||||
cells = _split_table_cells(row_line)
|
|
||||||
return any(c.strip() in ("^", "<", ">") for c in cells)
|
|
||||||
|
|
||||||
|
|
||||||
def _split_table_cells(row_line):
|
|
||||||
"""将 | a | b | c | 拆分为 ['a', 'b', 'c']。"""
|
|
||||||
stripped = row_line.strip()
|
|
||||||
if stripped.startswith("|"):
|
|
||||||
stripped = stripped[1:]
|
|
||||||
if stripped.endswith("|"):
|
|
||||||
stripped = stripped[:-1]
|
|
||||||
return [c.strip() for c in stripped.split("|")]
|
|
||||||
|
|
||||||
|
|
||||||
def _flatten_mpe_table(table_lines):
|
|
||||||
"""
|
|
||||||
将含 MPE 合并标记(^ < >)的表格展平为标准 Markdown。
|
|
||||||
多遍扫描:>(右→左)→ <(左→右)→ ^(上→下),
|
|
||||||
每遍将标记替换为被合并方向的内容。
|
|
||||||
展平后由 postprocess_table_rowspan 检测重复内容生成 rowspan/colspan。
|
|
||||||
"""
|
|
||||||
header = table_lines[0]
|
|
||||||
sep = table_lines[1]
|
|
||||||
data_rows = table_lines[2:]
|
|
||||||
|
|
||||||
# 解析所有数据行
|
|
||||||
parsed = [_split_table_cells(tl) for tl in data_rows]
|
|
||||||
if not parsed:
|
|
||||||
return [header, sep]
|
|
||||||
|
|
||||||
# 统一列宽(以表头为准)
|
|
||||||
num_cols = len(_split_table_cells(header))
|
|
||||||
for i, row in enumerate(parsed):
|
|
||||||
if len(row) < num_cols:
|
|
||||||
row.extend([""] * (num_cols - len(row)))
|
|
||||||
elif len(row) > num_cols:
|
|
||||||
print(f" [警告] 表格第 {i + 1} 行有 {len(row)} 列,超过表头 {num_cols} 列,多余列被忽略")
|
|
||||||
|
|
||||||
# 第 1 遍:处理 >(右→左,复制右侧单元格内容)
|
|
||||||
for row in parsed:
|
|
||||||
for col in range(num_cols - 2, -1, -1):
|
|
||||||
if row[col].strip() == ">":
|
|
||||||
row[col] = row[col + 1]
|
|
||||||
|
|
||||||
# 第 2 遍:处理 <(左→右,复制左侧单元格内容)
|
|
||||||
for row in parsed:
|
|
||||||
for col in range(1, num_cols):
|
|
||||||
if row[col].strip() == "<":
|
|
||||||
row[col] = row[col - 1]
|
|
||||||
|
|
||||||
# 第 3 遍:处理 ^(上→下,复制上方单元格内容)
|
|
||||||
prev_cells = _split_table_cells(header)
|
|
||||||
while len(prev_cells) < num_cols:
|
|
||||||
prev_cells.append("")
|
|
||||||
for row in parsed:
|
|
||||||
for col in range(num_cols):
|
|
||||||
if row[col].strip() == "^":
|
|
||||||
row[col] = prev_cells[col] if col < len(prev_cells) else row[col]
|
|
||||||
prev_cells = list(row)
|
|
||||||
|
|
||||||
# 重建表格行
|
|
||||||
flattened = [header, sep]
|
|
||||||
for row in parsed:
|
|
||||||
flattened.append("| " + " | ".join(row[:num_cols]) + " |")
|
|
||||||
|
|
||||||
return flattened
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- HTML 表格后处理(连续相同单元格 → rowspan / colspan) ----------
|
|
||||||
# 依赖 Python markdown "tables" 扩展生成 <tbody> 包裹数据行。
|
|
||||||
|
|
||||||
TD_RE = re.compile(r'<td([^>]*)>(.*?)</td>', re.DOTALL)
|
|
||||||
TR_RE = re.compile(r'<tr>(.*?)</tr>', re.DOTALL)
|
|
||||||
TBODY_RE = re.compile(r'(<tbody>.*?</tbody>)', re.DOTALL)
|
|
||||||
|
|
||||||
|
|
||||||
def postprocess_table_rowspan(html):
|
|
||||||
"""
|
|
||||||
扫描 HTML 表格的 <tbody>,将连续内容相同的单元格合并:
|
|
||||||
- 同一列上下连续相同 → rowspan
|
|
||||||
- 同一行左右连续相同 → colspan
|
|
||||||
与 preprocess_mpe_tables 配合:MPE 标记展平后产生重复内容,此处恢复为视觉合并。
|
|
||||||
|
|
||||||
注意:仅处理 <tbody> 内的 <tr>(Python markdown tables 扩展的输出格式)。
|
|
||||||
"""
|
|
||||||
def merge_tbody(match):
|
|
||||||
tbody = match.group(1)
|
|
||||||
rows = TR_RE.findall(tbody)
|
|
||||||
if len(rows) < 2:
|
|
||||||
return tbody
|
|
||||||
|
|
||||||
# 解析所有单元格
|
|
||||||
row_cells = []
|
|
||||||
for row_html in rows:
|
|
||||||
cells = []
|
|
||||||
for m in TD_RE.finditer(row_html):
|
|
||||||
cells.append({"attrs": m.group(1).strip(), "text": m.group(2).strip()})
|
|
||||||
row_cells.append(cells)
|
|
||||||
|
|
||||||
if not row_cells:
|
|
||||||
return tbody
|
|
||||||
|
|
||||||
num_cols = max(len(rc) for rc in row_cells) if row_cells else 0
|
|
||||||
num_rows = len(row_cells)
|
|
||||||
if num_cols == 0:
|
|
||||||
return tbody
|
|
||||||
|
|
||||||
# covered[r][c]:该单元格已被 rowspan 或 colspan 覆盖,渲染时跳过
|
|
||||||
covered = [[False] * num_cols for _ in range(num_rows)]
|
|
||||||
rowspan = [[1] * num_cols for _ in range(num_rows)]
|
|
||||||
colspan = [[1] * num_cols for _ in range(num_rows)]
|
|
||||||
|
|
||||||
# ---- 计算 rowspan(逐列扫描) ----
|
|
||||||
for col in range(num_cols):
|
|
||||||
row = 0
|
|
||||||
while row < num_rows:
|
|
||||||
if col >= len(row_cells[row]):
|
|
||||||
row += 1
|
|
||||||
continue
|
|
||||||
count = 1
|
|
||||||
r = row + 1
|
|
||||||
while r < num_rows:
|
|
||||||
if (col < len(row_cells[r])
|
|
||||||
and row_cells[r][col]["text"] == row_cells[row][col]["text"]
|
|
||||||
and row_cells[row][col]["text"] != ""):
|
|
||||||
count += 1
|
|
||||||
covered[r][col] = True
|
|
||||||
r += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if count > 1:
|
|
||||||
rowspan[row][col] = count
|
|
||||||
row = r # 跳过已被当前 rowspan 覆盖的行
|
|
||||||
|
|
||||||
# ---- 计算 colspan(逐行扫描,跳过已覆盖单元格) ----
|
|
||||||
for row in range(num_rows):
|
|
||||||
col = 0
|
|
||||||
while col < len(row_cells[row]):
|
|
||||||
if covered[row][col]:
|
|
||||||
col += 1
|
|
||||||
continue
|
|
||||||
count = 1
|
|
||||||
c = col + 1
|
|
||||||
while c < num_cols and c < len(row_cells[row]):
|
|
||||||
if (not covered[row][c]
|
|
||||||
and row_cells[row][c]["text"] == row_cells[row][col]["text"]
|
|
||||||
and row_cells[row][col]["text"] != ""):
|
|
||||||
count += 1
|
|
||||||
covered[row][c] = True
|
|
||||||
c += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if count > 1:
|
|
||||||
colspan[row][col] = count
|
|
||||||
col = c # 跳过已被当前 colspan 覆盖的列
|
|
||||||
|
|
||||||
# ---- 生成带 rowspan / colspan 的 HTML ----
|
|
||||||
new_rows = []
|
|
||||||
for row in range(num_rows):
|
|
||||||
new_cells = []
|
|
||||||
for col in range(num_cols):
|
|
||||||
if covered[row][col] or col >= len(row_cells[row]):
|
|
||||||
continue
|
|
||||||
cell = row_cells[row][col]
|
|
||||||
rs = rowspan[row][col]
|
|
||||||
cs = colspan[row][col]
|
|
||||||
attrs = cell["attrs"]
|
|
||||||
if rs > 1:
|
|
||||||
attrs += f' rowspan="{rs}"'
|
|
||||||
if cs > 1:
|
|
||||||
attrs += f' colspan="{cs}"'
|
|
||||||
new_cells.append(f'<td{attrs}>{cell["text"]}</td>')
|
|
||||||
new_rows.append("<tr>" + "".join(new_cells) + "</tr>")
|
|
||||||
|
|
||||||
return "<tbody>" + "".join(new_rows) + "</tbody>"
|
|
||||||
|
|
||||||
return TBODY_RE.sub(merge_tbody, html)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- Markdown → HTML ----------
|
|
||||||
|
|
||||||
def markdown_to_html(text):
|
|
||||||
md = markdown.Markdown(extensions=[
|
|
||||||
"extra",
|
|
||||||
"tables",
|
|
||||||
"fenced_code",
|
|
||||||
"toc",
|
|
||||||
])
|
|
||||||
return md.convert(text)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- 图片 base64 内嵌(HTML 级别) ----------
|
|
||||||
|
|
||||||
IMG_SRC_RE = re.compile(r'<img([^>]*?)src=["\']([^"\']+)["\']([^>]*)>', re.IGNORECASE)
|
|
||||||
|
|
||||||
MIME_TABLE = {
|
|
||||||
".png": "image/png",
|
|
||||||
".jpg": "image/jpeg",
|
|
||||||
".jpeg": "image/jpeg",
|
|
||||||
".gif": "image/gif",
|
|
||||||
".svg": "image/svg+xml",
|
|
||||||
".webp": "image/webp",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def guess_mime(ext):
|
|
||||||
return MIME_TABLE.get(ext.lower(), "application/octet-stream")
|
|
||||||
|
|
||||||
|
|
||||||
def embed_images(html, base_dirs):
|
|
||||||
"""扫描 HTML 中的 img 标签,将本地图片替换为 base64 data URI。"""
|
|
||||||
def resolve_src(src):
|
|
||||||
if Path(src).is_absolute():
|
|
||||||
path = Path(src)
|
|
||||||
if path.exists():
|
|
||||||
return path
|
|
||||||
return None
|
|
||||||
for base in base_dirs:
|
|
||||||
path = base / src
|
|
||||||
if path.exists():
|
|
||||||
return path
|
|
||||||
return None
|
|
||||||
|
|
||||||
def repl(match):
|
|
||||||
prefix = match.group(1)
|
|
||||||
src = match.group(2)
|
|
||||||
suffix = match.group(3)
|
|
||||||
|
|
||||||
if src.startswith(("http://", "https://", "data:")):
|
|
||||||
return match.group(0)
|
|
||||||
|
|
||||||
path = resolve_src(src)
|
|
||||||
if path is None:
|
|
||||||
print(f" [警告] 找不到图片,保留原路径:{src}")
|
|
||||||
return match.group(0)
|
|
||||||
try:
|
|
||||||
mime = guess_mime(path.suffix)
|
|
||||||
data = path.read_bytes()
|
|
||||||
b64 = base64.b64encode(data).decode("ascii")
|
|
||||||
return f'<img{prefix}src="data:{mime};base64,{b64}"{suffix}>'
|
|
||||||
except Exception as e:
|
|
||||||
print(f" [警告] 图片 base64 编码失败({src}):{e}")
|
|
||||||
return match.group(0)
|
|
||||||
|
|
||||||
return IMG_SRC_RE.sub(repl, html)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- 目录与锚点 ----------
|
|
||||||
|
|
||||||
def slugify(text):
|
|
||||||
"""生成 HTML 锚点 ID。"""
|
|
||||||
anchor = re.sub(r'[^\w\s一-鿿-]', '', text)
|
|
||||||
return anchor.strip().replace(" ", "-")[:50]
|
|
||||||
|
|
||||||
|
|
||||||
def generate_toc(html):
|
|
||||||
"""从 HTML 中提取 H1-H4 标题,生成目录。"""
|
|
||||||
toc = []
|
|
||||||
for m in re.finditer(r'<h([1-4])[^>]*>(.*?)</h\1>', html, re.DOTALL):
|
|
||||||
level = int(m.group(1))
|
|
||||||
text = re.sub(r'<.*?>', '', m.group(2)).strip()
|
|
||||||
toc.append({"level": level, "text": text, "anchor": slugify(text)})
|
|
||||||
return toc
|
|
||||||
|
|
||||||
|
|
||||||
def inject_anchors(html):
|
|
||||||
"""为所有 H1-H4 标签注入 id 属性,使侧边栏目录可跳转。"""
|
|
||||||
def repl(match):
|
|
||||||
level = match.group(1)
|
|
||||||
attrs = match.group(2)
|
|
||||||
inner = match.group(3)
|
|
||||||
anchor = slugify(re.sub(r'<.*?>', '', inner).strip())
|
|
||||||
return f'<h{level} id="{anchor}"{attrs}>{inner}</h{level}>'
|
|
||||||
return re.sub(
|
|
||||||
r'<h([1-4])([^>]*)>(.*?)</h\1>',
|
|
||||||
repl,
|
|
||||||
html,
|
|
||||||
flags=re.DOTALL,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- Logo 处理 ----------
|
|
||||||
|
|
||||||
def find_logo(logo_path_str):
|
|
||||||
"""定位 logo 文件;需在 project.yaml 明确指定 logo 字段。"""
|
|
||||||
if not logo_path_str:
|
|
||||||
return None
|
|
||||||
path = Path(logo_path_str)
|
|
||||||
if path.is_absolute():
|
|
||||||
return path
|
|
||||||
return PROJECT_ROOT / path
|
|
||||||
|
|
||||||
|
|
||||||
def embed_logo(logo_path, max_width=400):
|
|
||||||
"""压缩 logo 并返回 base64 data URI。"""
|
|
||||||
if logo_path is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
from PIL import Image
|
|
||||||
except ImportError:
|
|
||||||
print(" 提示: 未安装 Pillow,跳过 logo 处理")
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
img = Image.open(logo_path)
|
|
||||||
w, h = img.size
|
|
||||||
if w > max_width:
|
|
||||||
ratio = max_width / w
|
|
||||||
img = img.resize((max_width, int(h * ratio)), Image.Resampling.LANCZOS)
|
|
||||||
|
|
||||||
ext = logo_path.suffix.lower()
|
|
||||||
if ext == ".svg":
|
|
||||||
data = logo_path.read_bytes()
|
|
||||||
b64 = base64.b64encode(data).decode("ascii")
|
|
||||||
return f"data:image/svg+xml;base64,{b64}"
|
|
||||||
|
|
||||||
buf = io.BytesIO()
|
|
||||||
if img.mode in ("RGBA", "P"):
|
|
||||||
img.save(buf, format="PNG", optimize=True)
|
|
||||||
mime = "image/png"
|
|
||||||
else:
|
|
||||||
img = img.convert("RGB")
|
|
||||||
img.save(buf, format="JPEG", optimize=True, quality=90)
|
|
||||||
mime = "image/jpeg"
|
|
||||||
b64 = base64.b64encode(buf.getvalue()).decode("ascii")
|
|
||||||
return f"data:{mime};base64,{b64}"
|
|
||||||
except Exception as e:
|
|
||||||
print(f" 警告: logo 处理失败: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- 模板渲染 ----------
|
|
||||||
|
|
||||||
def inline_css():
|
|
||||||
"""读取主题 CSS。"""
|
|
||||||
css_path = THEMES_DIR / "report.css"
|
|
||||||
if css_path.exists():
|
|
||||||
return css_path.read_text(encoding="utf-8")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def render_template(config, content, toc, logo_data_uri=None):
|
|
||||||
env = Environment(
|
|
||||||
loader=FileSystemLoader(TEMPLATES_DIR),
|
|
||||||
autoescape=select_autoescape(["html", "xml"]),
|
|
||||||
)
|
|
||||||
template = env.get_template("report.html")
|
|
||||||
css = inline_css()
|
|
||||||
return template.render(
|
|
||||||
title=config["title"],
|
|
||||||
subtitle=config["subtitle"],
|
|
||||||
author=config["author"],
|
|
||||||
version=config["version"],
|
|
||||||
doc_type=config["doc_type"],
|
|
||||||
logo=logo_data_uri,
|
|
||||||
date=config["date"],
|
|
||||||
content=content,
|
|
||||||
toc=toc,
|
|
||||||
css=css,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- 检查脚本 ----------
|
|
||||||
|
|
||||||
def run_checks(checks):
|
|
||||||
issues = []
|
|
||||||
for name, module in checks.items():
|
|
||||||
func = getattr(module, "check", None)
|
|
||||||
if not callable(func):
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
result = func(str(PROJECT_ROOT))
|
|
||||||
if result:
|
|
||||||
issues.extend(result)
|
|
||||||
except Exception as e:
|
|
||||||
print(f" 警告: 检查脚本 {name} 执行失败: {e}")
|
|
||||||
if issues:
|
|
||||||
print("检查发现问题:")
|
|
||||||
for item in issues:
|
|
||||||
print(f" - {item}")
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- 主入口 ----------
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("=== Docs as Code 构建 ===")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# 0. 加载配置
|
|
||||||
config = load_config()
|
|
||||||
print(f"[配置] {config['title']} — {config['version']}")
|
|
||||||
print(f" 章节数: {len(config.get('chapters', []))}")
|
|
||||||
|
|
||||||
# 0a. 加载插件
|
|
||||||
print("[插件] 加载扩展模块 ...")
|
|
||||||
renderers = load_plugin_modules(RENDERERS_DIR)
|
|
||||||
checks = load_plugin_modules(CHECKS_DIR)
|
|
||||||
|
|
||||||
# 0b. 运行检查
|
|
||||||
if config.get("checks", True) and checks:
|
|
||||||
print("[检查] 运行检查脚本 ...")
|
|
||||||
run_checks(checks)
|
|
||||||
|
|
||||||
# 1. 逐章节处理
|
|
||||||
chapters = config.get("chapters", [])
|
|
||||||
if not chapters:
|
|
||||||
sys.exit("错误: project.yaml 中未定义 chapters 列表")
|
|
||||||
|
|
||||||
html_parts = []
|
|
||||||
for ch_file in chapters:
|
|
||||||
ch_path = CHAPTERS_DIR / ch_file
|
|
||||||
if not ch_path.exists():
|
|
||||||
print(f" 警告: 章节文件不存在,跳过: {ch_file}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
text = ch_path.read_text(encoding="utf-8")
|
|
||||||
|
|
||||||
# @import 处理
|
|
||||||
text = process_imports(text, ch_path.parent, renderers)
|
|
||||||
|
|
||||||
# 图片属性处理
|
|
||||||
text = process_image_attrs(text)
|
|
||||||
|
|
||||||
# MPE 表格合并预处理(^ → rowspan)
|
|
||||||
text = preprocess_mpe_tables(text)
|
|
||||||
|
|
||||||
# KaTeX 预处理
|
|
||||||
text = preprocess_katex(text)
|
|
||||||
|
|
||||||
# 自定义代码块渲染
|
|
||||||
text = render_custom_codeblocks_md(text, renderers)
|
|
||||||
|
|
||||||
# Markdown → HTML
|
|
||||||
chapter_html = markdown_to_html(text)
|
|
||||||
|
|
||||||
# 图片 base64 内嵌
|
|
||||||
base_dirs = [ch_path.parent, ASSETS_DIR, PROJECT_ROOT]
|
|
||||||
chapter_html = embed_images(chapter_html, base_dirs)
|
|
||||||
|
|
||||||
html_parts.append(chapter_html)
|
|
||||||
|
|
||||||
full_html = "\n\n".join(html_parts)
|
|
||||||
print(f" HTML 总字符数: {len(full_html)}")
|
|
||||||
|
|
||||||
# 2. 注入锚点 + 提取目录
|
|
||||||
full_html = inject_anchors(full_html)
|
|
||||||
full_html = postprocess_table_rowspan(full_html)
|
|
||||||
toc = generate_toc(full_html)
|
|
||||||
print(f" 目录条目数: {len(toc)}")
|
|
||||||
|
|
||||||
# 3. Logo 处理
|
|
||||||
logo_path = find_logo(config.get("logo", ""))
|
|
||||||
logo_data_uri = embed_logo(logo_path) if logo_path else None
|
|
||||||
if logo_data_uri:
|
|
||||||
print(" logo: 已内嵌")
|
|
||||||
else:
|
|
||||||
print(" logo: 未配置,封面将不显示 logo")
|
|
||||||
|
|
||||||
# 4. 模板渲染 + 输出
|
|
||||||
output_filename = re.sub(r'[^\w\-.]', '_', config["title"]) + ".html"
|
|
||||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
output_path = OUTPUT_DIR / output_filename
|
|
||||||
|
|
||||||
final_html = render_template(config, full_html, toc, logo_data_uri)
|
|
||||||
output_path.write_text(final_html, encoding="utf-8")
|
|
||||||
print(f"\n构建完成:{output_path}")
|
|
||||||
print(f"文件大小: {output_path.stat().st_size:,} 字节")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# checks — 检查脚本
|
|
||||||
# 文件命名: *.py (除 __init__.py 外)
|
|
||||||
# 函数签名: check(project_dir: str) -> list[str]
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# processors — 数据处理器
|
|
||||||
# 文件命名: *.py (除 __init__.py 外)
|
|
||||||
# 函数签名: load(filepath: Path) -> Any
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# renderers — 自定义渲染器
|
|
||||||
# 文件命名: render_<name>.py
|
|
||||||
# 函数签名: render(content: str) -> str
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{ title }}</title>
|
|
||||||
|
|
||||||
{# KaTeX 数学公式支持 #}
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.css">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/contrib/auto-render.min.js"></script>
|
|
||||||
|
|
||||||
<style>{{ css | safe }}</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="layout">
|
|
||||||
<nav class="sidebar" id="toc-sidebar">
|
|
||||||
<h2>目 录</h2>
|
|
||||||
<ul>
|
|
||||||
{% for item in toc %}
|
|
||||||
<li class="toc-level-{{ item.level }}">
|
|
||||||
<a href="#{{ item.anchor }}">{{ item.text }}</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="main">
|
|
||||||
<div class="cover">
|
|
||||||
{% if logo %}
|
|
||||||
<img class="cover-logo" src="{{ logo }}" alt="logo">
|
|
||||||
{% endif %}
|
|
||||||
<div class="cover-doc-type">{{ doc_type }}</div>
|
|
||||||
<h1 class="cover-title">{{ title }}</h1>
|
|
||||||
{% if subtitle %}<p class="cover-subtitle">{{ subtitle }}</p>{% endif %}
|
|
||||||
<div class="cover-meta">
|
|
||||||
<table>
|
|
||||||
{% if author %}<tr><td>作者</td><td>{{ author }}</td></tr>{% endif %}
|
|
||||||
{% if version %}<tr><td>版本</td><td>{{ version }}</td></tr>{% endif %}
|
|
||||||
<tr><td>日期</td><td>{{ date }}</td></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<main class="content">
|
|
||||||
{{ content | safe }}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# KaTeX 自动渲染 #}
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
renderMathInElement(document.body, {
|
|
||||||
delimiters: [
|
|
||||||
{left: "$$", right: "$$", display: true},
|
|
||||||
{left: "$", right: "$", display: false}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,328 +0,0 @@
|
||||||
/* ============================================
|
|
||||||
Docs as Code — 屏幕与打印样式
|
|
||||||
严格对齐 docs-as-code skill 参考实现
|
|
||||||
============================================ */
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--primary: #0d47a1;
|
|
||||||
--secondary: #1565c0;
|
|
||||||
--accent: #42a5f5;
|
|
||||||
--light: #e3f2fd;
|
|
||||||
--text: #212121;
|
|
||||||
--muted: #616161;
|
|
||||||
--bg: #f0f2f5;
|
|
||||||
--sidebar-w: 260px;
|
|
||||||
--content-w: 210mm;
|
|
||||||
--a4-h: 297mm;
|
|
||||||
--cover-px: 25mm;
|
|
||||||
}
|
|
||||||
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
|
|
||||||
html { scroll-behavior: smooth; }
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: "Noto Sans SC", "Microsoft YaHei", "PingFang SC", sans-serif;
|
|
||||||
font-size: 11pt;
|
|
||||||
line-height: 1.8;
|
|
||||||
color: var(--text);
|
|
||||||
background: var(--bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 整体布局:左侧目录 + 右侧主内容 ---- */
|
|
||||||
.layout {
|
|
||||||
display: flex;
|
|
||||||
max-width: calc(var(--sidebar-w) + var(--content-w));
|
|
||||||
margin: 0 auto;
|
|
||||||
background: #fff;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
width: var(--sidebar-w);
|
|
||||||
height: 100vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background: #fafbfc;
|
|
||||||
border-right: 1px solid #e0e0e0;
|
|
||||||
padding: 24px 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar h2 {
|
|
||||||
font-size: 13pt;
|
|
||||||
color: var(--primary);
|
|
||||||
border-bottom: 2px solid var(--primary);
|
|
||||||
padding-bottom: 8px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar ul { list-style: none; padding: 0; }
|
|
||||||
|
|
||||||
.sidebar li {
|
|
||||||
padding: 4px 0;
|
|
||||||
font-size: 9.5pt;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar li a {
|
|
||||||
color: #555;
|
|
||||||
text-decoration: none;
|
|
||||||
display: block;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 3px 8px;
|
|
||||||
transition: all 0.15s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar li a:hover {
|
|
||||||
color: var(--primary);
|
|
||||||
background: var(--light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar .toc-level-2 { padding-left: 12px; }
|
|
||||||
.sidebar .toc-level-3 { padding-left: 24px; }
|
|
||||||
.sidebar .toc-level-4 { padding-left: 36px; }
|
|
||||||
|
|
||||||
.main {
|
|
||||||
flex: 1;
|
|
||||||
max-width: var(--content-w);
|
|
||||||
min-width: 0;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 封面(A4) ---- */
|
|
||||||
.cover {
|
|
||||||
width: 100%;
|
|
||||||
height: var(--a4-h);
|
|
||||||
min-height: var(--a4-h);
|
|
||||||
padding: 35mm var(--cover-px) 50mm;
|
|
||||||
background: #fff;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
page-break-after: always;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-logo {
|
|
||||||
max-width: 320px;
|
|
||||||
max-height: 90px;
|
|
||||||
height: auto;
|
|
||||||
width: auto;
|
|
||||||
margin: 0;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-doc-type {
|
|
||||||
font-size: 13pt;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--secondary);
|
|
||||||
letter-spacing: 0.5em;
|
|
||||||
text-indent: 0.5em;
|
|
||||||
margin-top: 15mm;
|
|
||||||
margin-bottom: 15mm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-title {
|
|
||||||
font-family: "Noto Sans SC", "Microsoft YaHei", "PingFang SC", sans-serif;
|
|
||||||
font-size: 26pt;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #fff;
|
|
||||||
background: var(--primary);
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 0 calc(-1 * var(--cover-px)) 0;
|
|
||||||
padding: 0.4em var(--cover-px);
|
|
||||||
width: calc(100% + 2 * var(--cover-px));
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-subtitle {
|
|
||||||
font-size: 16pt;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--secondary);
|
|
||||||
margin-top: 8mm;
|
|
||||||
margin-bottom: 18mm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-meta {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 130mm;
|
|
||||||
margin-top: auto;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-meta table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: 11pt;
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-meta td {
|
|
||||||
padding: 2.5mm 4mm;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-meta td:first-child {
|
|
||||||
text-align: center;
|
|
||||||
color: #9e9e9e;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-meta td:last-child {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 正文 ---- */
|
|
||||||
.content {
|
|
||||||
padding: 2cm 2.5cm 3cm;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 正文排版 ---- */
|
|
||||||
h1 {
|
|
||||||
font-family: "Noto Serif SC", "SimSun", serif;
|
|
||||||
font-size: 18pt;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--primary);
|
|
||||||
border-bottom: 2px solid var(--primary);
|
|
||||||
padding-bottom: 0.3em;
|
|
||||||
margin-top: 1.8em;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-family: "Noto Serif SC", "SimSun", serif;
|
|
||||||
font-size: 14pt;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--secondary);
|
|
||||||
border-bottom: 1px solid var(--light);
|
|
||||||
padding-bottom: 0.2em;
|
|
||||||
margin-top: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 12pt;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303f9f;
|
|
||||||
margin-top: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 { font-size: 11pt; font-weight: 600; }
|
|
||||||
|
|
||||||
p { margin: 0.6em 0; }
|
|
||||||
|
|
||||||
strong { color: var(--primary); }
|
|
||||||
|
|
||||||
ul, ol { padding-left: 2em; margin: 0.6em 0; }
|
|
||||||
li { margin: 0.25em 0; }
|
|
||||||
|
|
||||||
/* ---- 表格 ---- */
|
|
||||||
table {
|
|
||||||
font-size: 10pt;
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
background: var(--primary);
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 500;
|
|
||||||
text-align: center;
|
|
||||||
padding: 8px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 6px 12px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr:nth-child(even) { background: #f8f9fa; }
|
|
||||||
|
|
||||||
/* ---- 代码 ---- */
|
|
||||||
pre {
|
|
||||||
background: #f5f5f5;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: "JetBrains Mono", "Cascadia Code", "Consolas", monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
background: #f0f0f0;
|
|
||||||
padding: 1px 4px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code { background: none; padding: 0; }
|
|
||||||
|
|
||||||
/* ---- 图片(无阴影) ---- */
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
margin: 1em auto;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- KaTeX 公式 ---- */
|
|
||||||
.katex-display {
|
|
||||||
margin: 0.75em 0;
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 响应式 ---- */
|
|
||||||
@media screen and (max-width: 1100px) {
|
|
||||||
:root { --cover-px: 15mm; }
|
|
||||||
.layout { flex-direction: column; }
|
|
||||||
.sidebar {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
position: relative;
|
|
||||||
border-right: none;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
.main { max-width: none; }
|
|
||||||
.content { padding: 1.5cm; }
|
|
||||||
.cover {
|
|
||||||
width: calc(100% - 2rem);
|
|
||||||
height: auto;
|
|
||||||
min-height: auto;
|
|
||||||
padding: 15mm;
|
|
||||||
margin: 1rem auto;
|
|
||||||
}
|
|
||||||
.cover-title { font-size: 22pt; }
|
|
||||||
.cover-subtitle { font-size: 14pt; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 打印 ---- */
|
|
||||||
@media print {
|
|
||||||
body { background: #fff; }
|
|
||||||
.layout { display: block; max-width: none; }
|
|
||||||
.sidebar { display: none; }
|
|
||||||
.main { max-width: none; }
|
|
||||||
.cover {
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
min-height: 100vh;
|
|
||||||
page-break-after: always;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
max-width: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
28
project.yaml
28
project.yaml
|
|
@ -1,28 +0,0 @@
|
||||||
# ez-Q 2.5 读出子系统编程控制模型
|
|
||||||
title: ez-Q 2.5 读出子系统编程控制模型
|
|
||||||
subtitle:
|
|
||||||
author: 郭成
|
|
||||||
version: V0.3
|
|
||||||
doc_type: 用户手册
|
|
||||||
logo: assets/logo.png # 封面 logo 路径(需明确指定;不指定则无 logo)
|
|
||||||
lang: zh-CN
|
|
||||||
|
|
||||||
chapters:
|
|
||||||
- 01-changelog.md
|
|
||||||
- 02-preface.md
|
|
||||||
- 03-overview.md
|
|
||||||
- 04-00-acq-model.md
|
|
||||||
- 04-01-acq-downconversion.md
|
|
||||||
- 04-02-acq-codeword.md
|
|
||||||
- 04-03-acq-registers.md
|
|
||||||
- 04-04-acq-matched-filter.md
|
|
||||||
- 04-05-acq-data-processing.md
|
|
||||||
- 04-06-acq-pipeline-demo.md
|
|
||||||
- 05-00-exc-model.md
|
|
||||||
- 05-01-exc-codeword.md
|
|
||||||
- 05-02-exc-registers.md
|
|
||||||
- 05-03-exc-wavetable.md
|
|
||||||
- 05-04-exc-waveform-store.md
|
|
||||||
- 05-05-exc-upconversion.md
|
|
||||||
- 05-06-pump-config.md
|
|
||||||
- 05-07-exc-pipeline-demo.md
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# ez-Q 2.5 读出子系统编程控制模型 构建依赖
|
|
||||||
# 安装: pip install -r requirements.txt
|
|
||||||
PyYAML>=6.0
|
|
||||||
markdown>=3.5
|
|
||||||
pymdown-extensions>=10.0
|
|
||||||
Jinja2>=3.1
|
|
||||||
Pygments>=2.16
|
|
||||||
Pillow>=10.0
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
## 5.1. 码字功能定义
|
|
||||||
|
|
||||||
AWG模块仅使用码字指令的低13位,其余位保留,
|
AWG模块仅使用码字指令的低13位,其余位保留,
|
||||||
控制码字功能定义如下表所示。
|
控制码字功能定义如下表所示。
|
||||||
|
|
||||||
|
|
@ -17,4 +15,4 @@ AWG模块仅使用码字指令的低13位,其余位保留,
|
||||||
- 码字比特[10]用作内部NCO清零信号,该功能可以在调制输出模式下,复位NCO的累积相位;
|
- 码字比特[10]用作内部NCO清零信号,该功能可以在调制输出模式下,复位NCO的累积相位;
|
||||||
- 码字比特[9]用于触发Marker脉冲输出,该脉冲能够触发外部仪器,可协同控制与外部芯片或者仪器,方便调试;
|
- 码字比特[9]用于触发Marker脉冲输出,该脉冲能够触发外部仪器,可协同控制与外部芯片或者仪器,方便调试;
|
||||||
- 码字比特[8]用于触发发出Pump脉冲输出,该脉冲可用于使能外部开关,避免Pump信号持续输出加热制冷机;
|
- 码字比特[8]用于触发发出Pump脉冲输出,该脉冲可用于使能外部开关,避免Pump信号持续输出加热制冷机;
|
||||||
- 码字比特[7:0]用作波形索引,总共可以索引256种波形,能够实现多个比特不同排列组合下的读取操作;
|
- 码字比特[7:0]用作波形索引,总共可以索引256种波形,能够实现多个比特不同排列组合下的读取操作;
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
# 5. 处理器EXC-Pump编程模型
|
|
||||||
|
|
||||||
EXC-Pump通道的编程包括:
|
EXC-Pump通道的编程包括:
|
||||||
MCU的指令、MCU的数据、控制寄存器、波形索性表、波形仓库和模拟电路配置6类数据。
|
MCU的指令、MCU的数据、控制寄存器、波形索性表、波形仓库和模拟电路配置6类数据。
|
||||||
- MCU指令+MCU数据可用于编程发出触发码字以及实时修改控制寄存器;
|
- MCU指令+MCU数据可用于编程发出触发码字以及实时修改控制寄存器;
|
||||||
|
|
@ -9,7 +7,7 @@ MCU的指令、MCU的数据、控制寄存器、波形索性表、波形仓库
|
||||||
|
|
||||||
当前模拟电路仅需配置Pump参数,下图是EXC-Pump通道数字部分的编程控制模型。
|
当前模拟电路仅需配置Pump参数,下图是EXC-Pump通道数字部分的编程控制模型。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
1. EXC-Pump通道的波形输出由AWG模块MCU发出的码字触发。
|
1. EXC-Pump通道的波形输出由AWG模块MCU发出的码字触发。
|
||||||
对于输出波形而言,MCU发出的码字定义波形的索引ID,
|
对于输出波形而言,MCU发出的码字定义波形的索引ID,
|
||||||
|
|
@ -26,10 +24,11 @@ MCU的指令、MCU的数据、控制寄存器、波形索性表、波形仓库
|
||||||
- NCO Only模式:可以输出连续波形,方便连接外部仪器上进行测试,用于芯片本身性能的测试。
|
- NCO Only模式:可以输出连续波形,方便连接外部仪器上进行测试,用于芯片本身性能的测试。
|
||||||
- 希尔伯特虚部模式:输出波形经过希尔伯特变换后的正交部分,仅用于调试。
|
- 希尔伯特虚部模式:输出波形经过希尔伯特变换后的正交部分,仅用于调试。
|
||||||
|
|
||||||
|
|
||||||
最后数字信号经过DAC转换成基带信号,基带信号再和外部本振信号模拟混频后输出读出激励波形。
|
最后数字信号经过DAC转换成基带信号,基带信号再和外部本振信号模拟混频后输出读出激励波形。
|
||||||
读出芯片不同模式输出的频响曲线如下图所示:
|
读出芯片不同模式输出的频响曲线如下图所示:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
为了兼容混频输出和射频直出两种工作模式以及在FPGA和ASIC平台上实现,半带滤波器和MIX模块都支持旁路功能,因此最终波形输出支持NRZ、MIX、HBNRZ和HBMIX四种模式。
|
为了兼容混频输出和射频直出两种工作模式以及在FPGA和ASIC平台上实现,半带滤波器和MIX模块都支持旁路功能,因此最终波形输出支持NRZ、MIX、HBNRZ和HBMIX四种模式。
|
||||||
|
|
||||||
|
|
@ -51,4 +50,4 @@ ez-Q 2.5 FPGA平台的Pump通道需要提前配置频率、功率和输出使能
|
||||||
当EXC-Pump通道由于上位机强制中断程序或者MCU配置了错误指令而产生异常时,
|
当EXC-Pump通道由于上位机强制中断程序或者MCU配置了错误指令而产生异常时,
|
||||||
为了清除EXC-Pump通道的异常状态,
|
为了清除EXC-Pump通道的异常状态,
|
||||||
EXC-Pump通道提供了软复位功能,支持通过调用驱动API来执行软复位操作,
|
EXC-Pump通道提供了软复位功能,支持通过调用驱动API来执行软复位操作,
|
||||||
其能够使得状态机从异常中恢复,从而使得下一次历史无关配置项能够正确执行。
|
其能够使得状态机从异常中恢复,从而使得下一次历史无关配置项能够正确执行。
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
## 4.2. ACQ码字功能定义
|
|
||||||
|
|
||||||
ACQ通道通过产生32位的码字来控制读出行为,32位的操控码字功能定义如下表所示。
|
ACQ通道通过产生32位的码字来控制读出行为,32位的操控码字功能定义如下表所示。
|
||||||
|
|
||||||
|比特位|名字|功能描述|
|
|比特位|名字|功能描述|
|
||||||
|
|
@ -47,4 +45,4 @@ ACQ通道通过产生32位的码字来控制读出行为,32位的操控码字
|
||||||
* 在一个指令中同时使能了态计数清零、态计数和计数存储时,顺序为:
|
* 在一个指令中同时使能了态计数清零、态计数和计数存储时,顺序为:
|
||||||
1. 先执行态计数清零
|
1. 先执行态计数清零
|
||||||
1. 再执行态计数
|
1. 再执行态计数
|
||||||
1. 最后执行计数结果存储
|
1. 最后执行计数结果存储
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
# 4. 处理器ACQ通道编程模型
|
|
||||||
|
|
||||||
来自量子芯片RO端口的射频信号首先经过混频板模拟电路调理后进入读出基带处理单元采集处理。
|
来自量子芯片RO端口的射频信号首先经过混频板模拟电路调理后进入读出基带处理单元采集处理。
|
||||||
混频板模拟调理电路主要负责将输入功率和频率的信号转换成基带板卡能够处理的中频信号,
|
混频板模拟调理电路主要负责将输入功率和频率的信号转换成基带板卡能够处理的中频信号,
|
||||||
对模拟调理电路的编程包括变频增益控制和变频本振频率控制。
|
对模拟调理电路的编程包括变频增益控制和变频本振频率控制。
|
||||||
|
|
@ -14,7 +12,7 @@
|
||||||
|
|
||||||
目前FPGA平台模拟电路无需配置,下图是FPGA平台ACQ通道数字部分编程控制模型。
|
目前FPGA平台模拟电路无需配置,下图是FPGA平台ACQ通道数字部分编程控制模型。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 射频信号经过混频板下变频后进入到ADC
|
- 射频信号经过混频板下变频后进入到ADC
|
||||||
- ADC采集的原始波形数据①从图中右侧端口输入,接着输入到解模模块中
|
- ADC采集的原始波形数据①从图中右侧端口输入,接着输入到解模模块中
|
||||||
|
|
@ -29,4 +27,4 @@
|
||||||
当RO通道由于上位机强制中断或者MCU配置了错误指令而产生异常时,
|
当RO通道由于上位机强制中断或者MCU配置了错误指令而产生异常时,
|
||||||
可以通过调用驱动API函数执行复位操作,
|
可以通过调用驱动API函数执行复位操作,
|
||||||
其能够将状态机从异常中复位(同时也复位部分寄存器的默认值),
|
其能够将状态机从异常中复位(同时也复位部分寄存器的默认值),
|
||||||
从而使得下一次历史无关配置项能够正确执行。
|
从而使得下一次历史无关配置项能够正确执行。
|
||||||
|
|
@ -0,0 +1,379 @@
|
||||||
|
---
|
||||||
|
export_on_save:
|
||||||
|
html: true
|
||||||
|
|
||||||
|
html:
|
||||||
|
toc: true
|
||||||
|
embed_local_images: true
|
||||||
|
embed_svg: true
|
||||||
|
|
||||||
|
title: 读出子系统历史无关功能配置项
|
||||||
|
author: 郭成
|
||||||
|
date:
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1. 修订记录
|
||||||
|
|版本|修订日期|修订原因|修订内容|修订人|
|
||||||
|
|:-|:-|:-|:-|:-|
|
||||||
|
|v0.1|2025/10/27|统一格式|初始版本|郭成|
|
||||||
|
|v0.2|2025/10/31|评审意见|内容补充|郭成|
|
||||||
|
|
||||||
|
# 2. 前言
|
||||||
|
## 2.1. 目的与范围
|
||||||
|
本文档的目的是介绍读出芯片激励产生和采集处理相关控制,
|
||||||
|
该文档适用于ez-Q 2.5 FPGA/ASIC平台读出子系统编程。
|
||||||
|
本文档作为开放文档供大家阅读。
|
||||||
|
|
||||||
|
## 2.2. 阅读对象
|
||||||
|
本文档的预期读者是所有使用本芯片的用户以及对该芯片工作原理感兴趣的读者。
|
||||||
|
|
||||||
|
## 2.3. 文档概述
|
||||||
|
本文档首先介绍了测控系统总体的编程对象和规范,
|
||||||
|
针对读出ACQ/EXCT/Pump三种类型通道对应的ACQ/EXCT-Pump编程模型进行了详细介绍。
|
||||||
|
|
||||||
|
## 2.4. 引用文档
|
||||||
|
|文档编号|标题|版本|
|
||||||
|
|:-|:-|:-|
|
||||||
|
|-|读出子系统历史无关配置集.md|V1.0|
|
||||||
|
|-|读出子系统IDS表.xls|V1.0|
|
||||||
|
|ez-Q 2.5-SDD-04|ez-Q 2.5 测控系统研制项目指令集设计报告_V1.0.docx|V1.0|
|
||||||
|
|
||||||
|
## 2.5. 术语定义
|
||||||
|
|名字|全称|解释|
|
||||||
|
|:-|:-|:-|
|
||||||
|
|ACQ|Acquisition|读出回波采集处理通道|
|
||||||
|
|EXCT|Excitation|读出激励生成发送通道|
|
||||||
|
|
||||||
|
# 3. 编程控制模型概述
|
||||||
|
|
||||||
|
## 3.1. 1 测控系统编程概述
|
||||||
|
超导量子计算机利用微波信号来驱动量子比特和读出量子比特状态,量子比特不同类型的操作依赖不同类型的信号来控制。
|
||||||
|
ez-Q 2.5测控系统包含5种物理通道,对应5类硬件接口,分别是ACQ、EXCT、Pump、XYZ/Reset和ZCP通道;
|
||||||
|
系统具有7种控制信号,对应7种编程对象,分别是EXCT、ACQ、Pump、XY、Reset、Z和ZCP信号;
|
||||||
|
系统具有4种控制模型,对应4种编程类型,分别是ACQ、EXCT-Pump、XY/Reset和Z/ZCP模型,分类关系如下表所示。
|
||||||
|
|
||||||
|
|5种物理通道|7种控制对象|4种控制模型|
|
||||||
|
|:-:|:-:|:-:|
|
||||||
|
|XYZ通道|XY 信号|XY/Reset控制模型|
|
||||||
|
|^|Reset信号|^|
|
||||||
|
|^|Z信号|Z/ZCP控制模型|
|
||||||
|
|ZCP通道|ZCP信号|Z/ZCP控制模型|
|
||||||
|
|ACQ通道|ACQ(RO)信号|ACQ控制模型|
|
||||||
|
|EXCT通道|EXCT(RI)信号|EXCT-Pump控制模型|
|
||||||
|
|Pump通道|Pump信号|^|
|
||||||
|
|
||||||
|
- 测控系统中的每种控制模型实现的功能都是通过以下四类数据进行定义,
|
||||||
|
- 通道微控制器码字指令
|
||||||
|
- 通道寄存器配置数据
|
||||||
|
- 通道SRAM配置数据
|
||||||
|
- 通道模拟电路配置
|
||||||
|
|
||||||
|
- 测控系统的寄存器、配置数据和模拟电路配置返回数据统一采用**大端字节序**。
|
||||||
|
|
||||||
|
通道寄存器配置数据支持微控制器实时修改,从而让通道的输入/输出控制具备动态控制能力。
|
||||||
|
ACQ和EXCT-Pump通道配置寄存器定义参考[读出子系统IDS表.xls](TODO)的`DAQ_REG`和`AWG_REG`页。
|
||||||
|
ACQ的配置数据定义参考
|
||||||
|
|
||||||
|
本文档通过对不同编程模型下码字指令、通道寄存器配置数据、通道SRAM配置数据和通道模拟电路配置进行介绍,
|
||||||
|
旨在让用户掌握对读出激励信号的产生和采集信号处理的编程方法。
|
||||||
|
其中码字指令通过MCU来产生,MCU的编程关键参数如下:
|
||||||
|
|
||||||
|
* ACQ通道和EXCT-Pump通道使用**相同MCU**;
|
||||||
|
* MCU分别使用**16 KB**的ITCM和DTCM;
|
||||||
|
* FPGA平台MCU主时钟频率为**250 MHz**,
|
||||||
|
* ASIC平台MCU主钟频率暂定**750 MHz**;
|
||||||
|
* MCU以固定的**3个时钟周期每指令**的速度运行;
|
||||||
|
* MCU通过**0x100000**地址访问MCU数据空间;
|
||||||
|
* MCU通道**0x200000**地址访问通道配置寄存器空间;
|
||||||
|
|
||||||
|
针对具体的指令定义和使用方法,
|
||||||
|
读者可查看[《量子编程指令集》](TODO)了解相关信息。
|
||||||
|
|
||||||
|
|
||||||
|
## 3.2. 2 读出子系统编程
|
||||||
|
ez-Q 2.5 ASIC平台读出子系统由读出基带板、读出混频板和读出泵浦板三个硬件组成
|
||||||
|
- 三个板卡以PXIe板卡的形式安装在机箱中位置相邻的三个槽位上
|
||||||
|
- 混频板的槽位号比基带板大1,泵浦板槽位号比基带板槽位号小1
|
||||||
|
|
||||||
|
因此可以通过仅指定读出基带板槽位号来定位混频板和泵浦板槽位号,
|
||||||
|
读出子系统硬件架构如下图所示。
|
||||||
|
- 读出基带板负责产生、输出、采集和处理基带信号以及为泵浦通道产生使能信号;
|
||||||
|
- 读出混频板自身产生一个本振信号,用于实现基带信号与射频信号之间的转换;
|
||||||
|
- 而读出泵浦板负责产生一个指定功率频率的单音信号,并在外部使能信号控制下输出;
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
当软件需要对不同通道编程时,其通过ip地址指定机箱、通过槽位号指定板卡、
|
||||||
|
通过扩展地址指定同一个板卡内的多个通道、通过地址指定一个通道内的不同配置项。
|
||||||
|
|
||||||
|
```
|
||||||
|
索引基地址由《读出子系统IDS表.xls》的mapping页定义,本文不对地址翻译进行赘述。
|
||||||
|
```
|
||||||
|
|
||||||
|
本文所述的三类编程通道具体定义如下:
|
||||||
|
* ACQ 编程通道定义从混频板`rf_in`输入端口到基带板卡内部DAQ模块;
|
||||||
|
* EXCT 编程通道定义为从基带板内部AWG模块混频板到`rf_out`接口;
|
||||||
|
* Pump 编程通道定义为从基带板卡内部AWG模块到泵浦板`pump_out`接口;
|
||||||
|
|
||||||
|
ASIC和FPGA平台具有以下区别:
|
||||||
|
* ez-Q 2.5 FPGA平台读出子系统混频板和泵浦板复用同一个硬件板卡。
|
||||||
|
* ez-Q 2.5 FPGA平台将读出基带芯片RBPU和FPGA集成在一个FPGA中。
|
||||||
|
* ez-Q 2.5 FPGA平台使用外部商用ADC、DAC和PLL来替换RBPU的对应功能。
|
||||||
|
* ez-Q 2.5 FPGA平台ADC、DAC采样率为4 Gsps, ASIC平台暂定为6 Gsps
|
||||||
|
|
||||||
|
# 4. 处理器ACQ通道编程模型
|
||||||
|
|
||||||
|
@import "ro_datapath.md"
|
||||||
|
|
||||||
|
## 4.1. 下变频电路配置
|
||||||
|
|
||||||
|
来自量子比特RO端口的射频信号需要经过前端模拟电路处理后才能够被读出基带处理单元采集处理。
|
||||||
|
前端下变频电路包括增益和本振,这里需要注意同一个混频板上四个通道上下变频使用同一个本振信号。
|
||||||
|
|
||||||
|
|配置名|描述|
|
||||||
|
|:-:|:-:|
|
||||||
|
|lo_freq|变频本振频率设置|
|
||||||
|
|mix_gain|下变频增益设置|
|
||||||
|
|
||||||
|
```
|
||||||
|
目前ez-Q 2.5 FPGA平台暂不支持下变频增益
|
||||||
|
mix_gain 设置;仅支持 lo_freq 变频本振频率设置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4.2. ACQ码字功能定义
|
||||||
|
@import "ro_codeword.md"
|
||||||
|
|
||||||
|
## 4.3. ACQ寄存器功能定义
|
||||||
|
ACQ通道寄存器的地址空间可以被SPI和MCU同时访问,
|
||||||
|
因此用户可通过SPI或者MCU设置参数,建议:
|
||||||
|
```
|
||||||
|
静态参数通过SPI配置完成就保持不变,
|
||||||
|
需要动态控制的参数通过MCU来实时更新。
|
||||||
|
```
|
||||||
|
|
||||||
|
|名字|功能描述|
|
||||||
|
|:-|:-|
|
||||||
|
|function|DAQ功能控制,控制运行模式等|
|
||||||
|
|sample_depth|波形采样深度控制,单位是时钟周期个数|
|
||||||
|
|mtf_idx_q[15:0]|解模参数控制,分别对应16个频点|
|
||||||
|
|dds_fpw_q[15:0]|解模频率相位控制,分别对应16个频点|
|
||||||
|
|
||||||
|
- sample_depth用于控制波形采集模式下采集波形的长度,单位是时钟周期;
|
||||||
|
- mtf_idx_q在使能寄存器控制情况下,用于直接索引匹配滤波器权重/系数;
|
||||||
|
- 高16比特对应索引地址,单位是时钟周期
|
||||||
|
- 低16比特对应索引长度,单位是时钟周期
|
||||||
|
- dds_fpw_q在使能寄存器控制情况下,用于控制解模载波的频率和相位;
|
||||||
|
- 高20位对应载波频率控制字fcw,$F_{c}=fcw/2^{20}*sample\_rate$
|
||||||
|
- 低12位对应载波相位控制字pcw,$\phi_{c} = pcw/2^{12}*2*\pi$
|
||||||
|
- function用来控制整个读出基带板数据处理行为;
|
||||||
|
|
||||||
|
function功能寄存器与实验控制相关的具体控制位包括:
|
||||||
|
|
||||||
|
|比特位|名字|功能描述|
|
||||||
|
|:-|:-|:-|
|
||||||
|
|[15:8]|WEIGHT_IQ| 常数权重值,8比特有符号数|
|
||||||
|
|[7]|CONST_EN| 常数权重使能, 高有效|
|
||||||
|
|[6:4]|STEP_CTRL| 计算权重模式步长控制|
|
||||||
|
|[3]|IQ_SCALE| 解模动态范围设置|
|
||||||
|
|[2]|TWO_STA_EN| 两态读出使能, 高电平使能|
|
||||||
|
|
||||||
|
* WEIGHT_IQ 常数权重值,可用于长时间解模
|
||||||
|
* CONST_EN 常数权重使能
|
||||||
|
* `CONST_EN=0`时,权重数据通过`mtf_idx_q`来索引得到
|
||||||
|
* `CONST_EN=1`来启用常量权重功能,此时权重值为`WEIGHT_IQ`。
|
||||||
|
* STEP_CTRL 权重点步长控制
|
||||||
|
- `STEP_CTRL==0` 1个权重数据点可对应1个时钟周期采样点。
|
||||||
|
- `STEP_CTRL==1` 1个权重数据点可对应2个时钟周期采样点。
|
||||||
|
- `STEP_CTRL==2` 1个权重数据点可对应4个时钟周期采样点。
|
||||||
|
- `STEP_CTRL==3` 1个权重数据点可对应8个时钟周期采样点。
|
||||||
|
- `STEP_CTRL==4` 1个权重数据点可对应16个时钟周期采样点。
|
||||||
|
* IQ_SCALE 解模结果截断位置控制
|
||||||
|
- `IQ_SCALE =0` IQ结果截位[27:8]
|
||||||
|
- `IQ_SCALE =1` IQ结果截位[31:12]
|
||||||
|
* TWO_STA_EN 两态读出使能
|
||||||
|
- `TWO_STA_EN==0` 默认三态读出
|
||||||
|
- `TWO_STA_EN==1` 使能二态读出
|
||||||
|
|
||||||
|
```
|
||||||
|
ez-Q 2.5 FPGA平台采用系数直读模式,因此不支持系数生产相关功能。
|
||||||
|
即在ez-Q 2.5 FPGA平台下,dds_fpw 寄存器无实际效果;
|
||||||
|
function寄存器WEIGHT_IQ,CONST_EN, STEP_CTRL 位无实际效果
|
||||||
|
```
|
||||||
|
解模完成后,可以通过`TWO_STA_EN`来控制是否启动2态判定,
|
||||||
|
当`TWO_STA_EN=1`时,可以只配置一组直线方程系数进行态判断。
|
||||||
|
|
||||||
|
## 4.4. 匹配滤波器
|
||||||
|
匹配滤波器在FPGA/ASIC两种平台下由于资源不同实现的形式也不同,区别如下:
|
||||||
|
* 由于ASIC多计算少存储,因而ASIC平台使用系数计算模式以节省存储资源。
|
||||||
|
* 由于FPGA 多存储少计算,因此FPGA平台使用系数直读模式以节省DSP资源。
|
||||||
|
|
||||||
|
### 4.4.1. 读出参数存储
|
||||||
|
|
||||||
|
|
||||||
|
系数计算模式下,匹配滤波器的系数由DDS生成的载波乘以权重参数得到。
|
||||||
|
- 载波的频率和相位由dds_pfd控制,其高20为作为频率控制字,低12位作为相位控制字。
|
||||||
|
- 权重参数的选择由mtf_idx控制,其高16位作为地址,低16位作为长度。
|
||||||
|
- mtf_idx索引得到的权重数据颗粒度是权重数据点,一个数据点可对应多个周期采样点;
|
||||||
|
- 寄存器dds_pfw_q 和mtf_idx_q 的详细定义与查找表中定义保持相同。
|
||||||
|
|
||||||
|
读出系统的的读出参数存储格式如下图所示。
|
||||||
|
FPGA平台下的系数直读模式只使用`Ctrl`部分中的数据,并忽略`dds_pfw`控制字.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 4.4.2. 匹配滤波器系数
|
||||||
|
|
||||||
|
ez-Q 2.5 FPGA平台使用系数直读模式,其需要额外的存储空间来配置匹配滤波器系数。
|
||||||
|
- 其中mtf_idx的含义从索引权重数据变为索引匹配滤波器系数。
|
||||||
|
- 匹配滤波器系数索引的粒度是时钟周期,
|
||||||
|
- 在FPGA平台下每个时钟周期对应16个采样点数据。
|
||||||
|
- 匹配滤波器系数采样点采用8比特数据位宽,因此1个周期数据位宽为128 bit,
|
||||||
|
|
||||||
|
匹配滤波器系数存储结构如下图所示。
|
||||||
|
- 匹配滤波器的I和Q数据分开存储,每个比特的I、Q数据容量分别为16 KB。
|
||||||
|
- I路数据偏移地址为x*32KB,Q路数据偏移地址为x*32KB+16 KB,其中x为Qubit序号,范围为0~15。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 4.5. 采集数据处理
|
||||||
|
ACQ通道采集的数据以流的形式按照先后顺序缓存,
|
||||||
|
缓存到设定数据量后或者计时器超时后数据被打包成帧上传,
|
||||||
|
上位机驱动解帧后以流模式将数据返回给调用接口。
|
||||||
|
当采集多种数据时,用户需要维护采集数据的顺序,
|
||||||
|
从而解析返回数据的含义。
|
||||||
|
建议一个实验仅仅采集一种类型数据以简化数据处理。
|
||||||
|
|
||||||
|
### 4.5.1. 采集波形数据
|
||||||
|
波形数据为模拟信号经ADC量化和符号转换后结果,
|
||||||
|
用8比特二进制补码表示,范围对应-128~127;
|
||||||
|
|
||||||
|
* ASIC平台1个时钟周期对应8个采样点(8个字节)
|
||||||
|
* FPGA平台1个时钟周期对应16个采样点(16字节)
|
||||||
|
* 数据采用大端字节序,数据遵循先采先到顺序
|
||||||
|
|
||||||
|
### 4.5.2. 采集IQ数据
|
||||||
|
解模计算公式如下:
|
||||||
|
$$
|
||||||
|
I = \sum_{i=1}^{m}{\sum_{j=1}^n{\frac{S_{i,j} * iw_{i,j} * cos(\omega_i t+\phi_i)}{scale_i} }}
|
||||||
|
$$
|
||||||
|
$$
|
||||||
|
Q = \sum_{i=1}^{m}{\sum_{j=1}^n{\frac{S_{i,j} * qw_{i,j} * sin(\omega_i t+\phi_i)}{scale_i} }}
|
||||||
|
$$
|
||||||
|
|
||||||
|
* $m$表示解模次数,$n$表示解模的采样点个数;
|
||||||
|
* $S_{i,j}$为输入波形的第i次解模第j个采样点,8比特有符号数
|
||||||
|
* $iw_{i,j}$、$qw_{i,j}$为i、q权重的第i次解模第j个采样点,8比特有符号数
|
||||||
|
* $\omega_i$、$\phi_i$分别为第i次解模的频率和相位
|
||||||
|
* $scale_i$为第i次解模的范围选择,可设置为1或者16
|
||||||
|
|
||||||
|
|
||||||
|
解模频率应设置为输入波形对应频率一遍使得解模结果频率为0,
|
||||||
|
解模结果相位等于解模设置相位设置减去输入波形相位。
|
||||||
|
若解模相位为0,则结果相位等于波形相位取反。
|
||||||
|
解模求和结果I、Q分别用32比特二进制补码表示,解模的结果存储格式如下:
|
||||||
|
|
||||||
|
* 1个完整的数据包含I,Q(8字节)
|
||||||
|
* I先到达,Q后到达
|
||||||
|
* I, Q数据采用大端字节,都占用4个字节
|
||||||
|
|
||||||
|
### 4.5.3. 采集态数据
|
||||||
|
* 1个完整数据包含16个qubit态信息(4字节)
|
||||||
|
* 每个量子比特态信息用2个比特表示
|
||||||
|
* 高位表示高序号,即32比特对应{q15,q14,...,q0};
|
||||||
|
|
||||||
|
### 4.5.4. 采态计数数据
|
||||||
|
* 1个完整数据包含4个计数器数据(16字节)
|
||||||
|
* 4个计数数据顺序为0态计数器、1态计数器、2态计数器、3态计数器
|
||||||
|
* 计数器采用大端字节序,为32比特无符号数
|
||||||
|
|
||||||
|
# 5. 处理器EXCT-Pump编程模型
|
||||||
|
|
||||||
|
@import "ri_datapath.md"
|
||||||
|
|
||||||
|
## 5.1. 1 码字功能定义
|
||||||
|
@import "ri_codeword.md"
|
||||||
|
|
||||||
|
## 5.2. 2 寄存器功能定义
|
||||||
|
EXCT-Pump通道的全部寄存器可以被SPI和MCU同时访问,
|
||||||
|
用户可以根据需要决定使用SPI还是MCU来控制寄存器的值。
|
||||||
|
|名字|功能描述|
|
||||||
|
|:-|:-|
|
||||||
|
|wave_ctrl|寄存器索引波形,包含地址和长度信息|
|
||||||
|
|amplitude|波形输出调制幅度|
|
||||||
|
|Frequency|调制载波频率|
|
||||||
|
|Phase|调制载波相位|
|
||||||
|
|Function|AWG工作模式定义|
|
||||||
|
|pump_ctrl|pump使能脉冲控制|
|
||||||
|
|mark_ctrl|标记使能脉冲控制|
|
||||||
|
|
||||||
|
- `wave_ctrl`波形输出直接控制,可直接从波形仓库取采样点输出。
|
||||||
|
- 高16位为索引地址,单位是时钟周期
|
||||||
|
- 低16为为索引长度,单位是时钟周期
|
||||||
|
- `amplitude`调制幅度控制字
|
||||||
|
- 高16为幅度控制字acw,范围0\~16384,归一化幅度$Amp = acw/2^{16}$
|
||||||
|
- `frequency`调制频率控制
|
||||||
|
- 32比特频率控制字fcw,$F_{nco}= fcw/2^{32}*F_s$,其中$F_s$是输出采样率。
|
||||||
|
- `phase`调制相位控制
|
||||||
|
- 高16位相位控制字pcw: $\phi_{nco} = pcw/2^{16}*2*\pi$。
|
||||||
|
- `funciton`寄存器用于设置AWG的工作模式
|
||||||
|
- `pump_ctrl`泵浦脉冲使能控制
|
||||||
|
- 高16位控制输出延迟时钟周期,范围(1~65535)
|
||||||
|
- 低16位控制附加持续时钟周期,范围(1~65535)
|
||||||
|
- `mark_ctrl`标记脉冲使能控制
|
||||||
|
- 高16位控制输出延迟时钟周期,范围(1~65535)
|
||||||
|
- 低16位控制脉冲持续时钟周期,范围(1~65535)
|
||||||
|
|
||||||
|
function的详细控制如下所示:
|
||||||
|
|比特位|名字|功能描述|
|
||||||
|
|:-|:-|:-|
|
||||||
|
|[3]|INTP_SEL| 插值模式选择,1:半带插值,0:邻近插值|
|
||||||
|
|[2]|MIX_MODE| 混频模式选择,1:混频模式,0:基带模式|
|
||||||
|
|[1:0]|AWG_MODE| AWG模式, 00:直出,01:调制,10:Hilbert,11:NCO|
|
||||||
|
|
||||||
|
* `INTP_SEL`在FPGA平台下受限于DSP资源,只能设置为邻近插值模式。
|
||||||
|
* `MIX_MODE`用于直接输出射频信号,由于采用了模拟混频方案,仅使用基带模式。
|
||||||
|
* `AWG_MODE`设置AWG模式,实验选用直出模式或者调制模式,其余两种模式用于调试。
|
||||||
|
|
||||||
|
## 5.3. 3 波形索引表定义
|
||||||
|
波形查找表的深度为256条(1 kB),每个条目的位宽为32比特,
|
||||||
|
其通过8比特的波形id来索引输出波形的参数(地址和长度),最大支持256种不同的输出波形。
|
||||||
|
|
||||||
|
索引表格式定义如下图所示: wave_id是mcu产生的码字,其可以作为地址索引波形控制参数。
|
||||||
|
波形控制参数包括波形地址`addr`和波形长度`len`参数,颗粒度是时钟周期。
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 5.4. 4 波形仓库定义
|
||||||
|
* ASIC平台下,每个时钟周期对应8个采样点,采样点个数需要为8的整数倍,数据更新率为6 Gsps,每个采样点持续时间为167 皮秒(6 GS/s)。
|
||||||
|
* 在FPGA平台下,每个时钟周期对应16个采样点,采样点个数需要为16的整数倍,数据更新率为 4 GSps,每个采样点持续时间为250皮秒。
|
||||||
|
* 每个采样点为16比特的二进制补码数据;
|
||||||
|
* 波形仓库的容量为128 KB,在FPGA平台和ASIC平台下最大分别支持16 us和10 us波形输出。
|
||||||
|
|
||||||
|
对于EXCT输出频率$F_{out}$(例如6.7 GHz),在本振为$F_{LO}$(例如5.5GHz)本振频率下,
|
||||||
|
则存储区描绘的基带波形频率$F_{s}$为1.2 GHz ,$F_s = F_{out} - F_{LO}$。
|
||||||
|
|
||||||
|
|
||||||
|
## 5.5. 5 EXCT上变频电路配置
|
||||||
|
来自读出基带板输出端口的中频信号需要经过混频板上变频电路处理后才能够被发送到量子芯片。
|
||||||
|
前端上变频电路包括增益和本振,这里需要注意同一个混频板上四个通道上下变频使用同一个本振信号。
|
||||||
|
目前ez-Q 2.5 FPGA平台硬件暂不支持变频增益设置。
|
||||||
|
|配置名|描述|
|
||||||
|
|:-:|:-:|
|
||||||
|
|lo_freq|变频本振频率设置|
|
||||||
|
|
||||||
|
* lo_freq:32比特整数,范围[5400000, 5600000]单位是kHz
|
||||||
|
|
||||||
|
## 5.6. 6 Pump模拟电路配置
|
||||||
|
对处理器Pump通道的编程包括模拟电路配置项和使能配置项。模拟电路用于将Pump通道配置输出一个指定功率和频率的微波信号,而使能配置项用于控制微波信号的实时开关。
|
||||||
|
|
||||||
|
Pump输出时,需要使能输出并配置好输出频率和功率,以下是具体配置项。
|
||||||
|
|配置名|描述|
|
||||||
|
|:-|:-|
|
||||||
|
|pump_freq|pump信号输出频率|
|
||||||
|
|pump_power|pump信号输出功率|
|
||||||
|
|pump_enable|pump信号输出使能|
|
||||||
|
|
||||||
|
* pump_freq: 32比特整数,范围[7000000, 9000000], 单位是kHz
|
||||||
|
* pump_power: 32比特整数,范围[-1100, -300], 具体映射关系取决于硬件
|
||||||
|
* pump_enable: 0x11 为使能,0x00为关闭
|
||||||
Loading…
Reference in New Issue