72 lines
7.3 KiB
Python
72 lines
7.3 KiB
Python
from pathlib import Path
|
||
from docx import Document
|
||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||
from docx.shared import Cm, Pt
|
||
from docx.oxml import OxmlElement
|
||
from docx.oxml.ns import qn
|
||
|
||
DOC = Path(r"D:\Project\spinal_stim\详细设计\spinal_stim_FPGA详细设计文档2.docx")
|
||
DIAG = Path(r"D:\Project\spinal_stim\pl\spinal_stim_v2\tmp\fpga_doc2\submodule_diagrams")
|
||
|
||
def shade(cell, fill="D9E2F3"):
|
||
tcPr = cell._tc.get_or_add_tcPr(); shd = OxmlElement("w:shd"); shd.set(qn("w:fill"), fill); tcPr.append(shd)
|
||
|
||
def h(doc, text, level):
|
||
import re
|
||
text = re.sub(r"^\d+(?:\.\d+)*\s+", "", text)
|
||
return doc.add_paragraph(text, style=f"Heading {level}")
|
||
|
||
def body(doc, text):
|
||
p = doc.add_paragraph(text, style="Normal")
|
||
p.paragraph_format.first_line_indent = Cm(0.74)
|
||
p.paragraph_format.line_spacing = 1.2
|
||
return p
|
||
|
||
def caption(doc, text):
|
||
p = doc.add_paragraph(text, style="Caption"); p.alignment = WD_ALIGN_PARAGRAPH.CENTER; return p
|
||
|
||
def table(doc, headers, rows):
|
||
t = doc.add_table(rows=1, cols=len(headers)); t.style="Table Grid"; t.alignment=WD_TABLE_ALIGNMENT.CENTER; t.autofit=False
|
||
for c, val in zip(t.rows[0].cells, headers): c.text=str(val); shade(c)
|
||
for row in rows:
|
||
cells=t.add_row().cells
|
||
for c,val in zip(cells,row): c.text=str(val)
|
||
return t
|
||
|
||
mods = [
|
||
("eeg_top(EEG采集顶层)", "eeg_config、ads1299_spi、ads1299_if、ads1299_mod、ads1299_arrange、eeg_upload", "clk=100 MHz;SPI 时钟由 SPI_CLK_DIV=3 分频;BRAM 配置在 clk 域采样;AXI record 在 clk 域输出。", [("bram_addr/din/en/we", "I", "BRAM_CTRL_1配置读写"),("drdy[1:0]", "I", "ADS1299数据就绪"),("cs/sclk/mosi", "O", "两组采集SPI"),("miso[1:0]", "I", "SPI返回数据"),("axis_record_tdata/valid/last", "O", "24 bit通道数据流")]),
|
||
("emg_top(EMG采集顶层)", "emg_config、ads1299_spi、ads1299_if、ads1299_mod、eeg_upload", "clk=100 MHz;SPI 配置事务与采样事务共享状态机;RESET#、START 在配置阶段产生。", [("bram_addr/din/en/we", "I", "BRAM_CTRL_2配置读写"),("drdy_n", "I", "差分前端就绪"),("reset_n/start", "O", "前端复位和转换启动"),("cs/sclk/mosi/miso", "I/O", "ADS1299 SPI"),("axis_record_tdata/valid/last", "O", "EMG数据流")]),
|
||
("upload_top(上传封包)", "upload_config、upload_sort、upload_protocol_ql、upload_protocol_intan", "clk=100 MHz;输入与输出均采用 AXI4-Stream 握手;超时计数以 CLK_FREQ 参数换算。", [("record_tdata/valid/last", "I", "采集小包"),("pre_message/ch_mapping2pc", "I", "帧头和通道映射"),("upload_ch", "I", "通道选择位图"),("upload_tdata/valid/last", "O", "封装后字节流"),("interrupt", "O", "上传事件")]),
|
||
("eeg_emg_upload(EEG/EMG合帧)", "EEG FIFO、EMG FIFO、CRC16、选择状态机", "clk=100 MHz;EEG/EMG 输入为 8 bit 流;stop_sample 异步于数据功能但在 clk 域清零状态。", [("m_axis_eeg_tdata/valid/last", "I", "EEG帧"),("m_axis_emg_tdata/valid/last", "I", "EMG帧"),("eeg_emg_sel", "I", "四种组合模式"),("*_send_package_num", "I", "输出字节数"),("eeg_emg_tdata/valid/last", "O", "合帧输出")]),
|
||
("cur_mon_top(电流监测顶层)", "cur_mon_config、ad7689_dual_ctrl、ad7689_dual_if、acq_collect、cur_monitor", "clk=100 MHz;CNV/SCK 由计数器产生;采样结果经过 FIFO 后进入滤波和阈值比较。", [("cnv_en/clear_int", "I", "采样使能和清除故障"),("cnv/sck/mosi", "O", "AD7689控制"),("miso", "I", "AD7689结果"),("flt_state/cur_rt", "O", "故障与实时值"),("axi_i_out/wr/tlast", "O", "透明DMA流")]),
|
||
("rhs2116_top(刺激控制)", "transmit_config、trans_timer、sine_gen、trans_distribute、transmit_if、stim_monitor、stim_event_collect", "clk=100 MHz;SPI_TRANS_PERIOD=1500 ns;DDS相位累加和定时器均在 clk 域执行。", [("stim BRAM", "I", "波形/通道/寄存器配置"),("axi_str_txd", "I", "刺激命令流"),("stim_en/cs/sclk/mosi", "O", "RHS2116 SPI"),("miso", "I", "器件回读"),("stim_event/axi_str_rxd", "O", "事件和监控流")]),
|
||
("channel_switch(通道开关)", "channel_switch、adg1414_if、spi_full_duplex_master", "clk=100 MHz;PS 的通道选择先同步,再串行移位到模拟开关;更新完成后锁存输出。", [("ch_sel/ch_en", "I", "通道选择"),("spi_sync/sclk/mosi", "O", "ADG1414/ADGS5414接口"),("miso", "I", "可选回读"),("sw_state", "O", "当前开关状态")]),
|
||
("trigger_top(触发检测)", "trigger_detection、trigger_top", "clk=100 MHz;输入触发先经过同步器,再由边沿/码型检测状态机处理。", [("trigger_i", "I", "外部触发输入"),("rst", "I", "触发逻辑复位"),("event_code", "O", "触发事件编码"),("interrupt", "O", "触发中断"),("axis_trigger", "O", "触发数据流")]),
|
||
]
|
||
|
||
def main():
|
||
d=Document(DOC)
|
||
h(d, "子模块详细设计补充", 1)
|
||
body(d, "本节按 DTCD PRO-S FPGA 详细设计模板的子模块格式补充说明。每个模块均包含(1)流程图;(2)处理说明;(3)数据流;(4)时钟说明;(5)接口信号。图中方框表示 RTL 状态或处理单元,箭头表示有效数据/控制方向;状态说明与代码中的状态机、计数器和 FIFO 条件对应。")
|
||
for idx,(name, children, clock, ports) in enumerate(mods,1):
|
||
h(d, f"{idx} {name}", 2)
|
||
h(d, "1 流程图", 3)
|
||
d.add_picture(str(DIAG/(name.split("(")[0]+"_flow.png")), width=Cm(16.0)); caption(d, f"图 {idx}-1 {name}流程图")
|
||
body(d, "状态说明:IDLE 等待配置或输入有效;CONFIG 完成寄存器/器件初始化;RUN 在有效握手或 DRDY 条件下处理数据;END 输出 TLAST、锁存状态并返回 IDLE。出现 stop_sample、复位或错误条件时优先回到 IDLE 并清空本模块计数器/FIFO。")
|
||
h(d, "2 处理说明", 3)
|
||
d.add_picture(str(DIAG/(name.split("(")[0]+"_state.png")), width=Cm(16.0)); caption(d, f"图 {idx}-2 {name}处理状态转换图")
|
||
body(d, f"处理流程:{children}。首先由配置寄存器装载工作参数;随后等待输入有效(采集模块为 DRDY,上传模块为 TVALID,刺激模块为命令 FIFO,开关模块为选择更新);处理单元按计数器推进,在边界条件产生有效/结束标志;最后将结果送往下一级 FIFO、AXI 流或状态寄存器。")
|
||
h(d, "3 数据流", 3)
|
||
d.add_picture(str(DIAG/(name.split("(")[0]+"_data.png")), width=Cm(16.0)); caption(d, f"图 {idx}-3 {name}数据流图")
|
||
body(d, "数据流说明:配置面从 PS BRAM/GPIO/SPI 进入;数据面由外部器件或上一级 AXI-Stream 进入,经本模块寄存器、状态机、FIFO 和校验逻辑处理后输出。跨模块边界使用 valid/ready 或显式 FIFO,TLAST 只在包/帧最后一个字节有效。")
|
||
h(d, "4 时钟说明", 3); body(d, clock)
|
||
h(d, "5 接口信号", 3)
|
||
table(d, ["信号", "方向", "功能说明"], ports)
|
||
h(d, "5.1 下一级子模块", 4)
|
||
body(d, "本模块下一级 RTL 子模块为:" + children + "。下一级模块均在同一时钟域内以同步时序逻辑实现,接口由上一级统一提供复位和使能;其中 SPI/ADC 接口模块负责器件边沿约束,FIFO/封包模块负责数据域隔离。")
|
||
d.save(DOC)
|
||
print(DOC)
|
||
|
||
if __name__=="__main__": main()
|