This repository has been archived on 2026-08-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
ciciec2026/doc/reports/defense/build_defense_ppt.py
2026-07-15 18:22:17 +08:00

864 lines
38 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Build the defense deck from the official CICIEC PPT template."""
from __future__ import annotations
import sys
from io import BytesIO
from pathlib import Path
from zipfile import ZipFile
try:
from pptx import Presentation
except ModuleNotFoundError:
sys.path.insert(0, "/tmp/codex_pptx")
from pptx import Presentation
from PIL import Image
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_SHAPE
from pptx.enum.text import MSO_ANCHOR, PP_ALIGN
from pptx.util import Inches, Pt
ROOT = Path(__file__).resolve().parents[3]
HERE = Path(__file__).resolve().parent
SYSTEM = ROOT / "doc/reports/system"
MLKEM = ROOT / "doc/reports/mlkem_ip"
TEMPLATE = HERE / "ppt_template.pptx"
OUTPUT = HERE / "defense_presentation.pptx"
W = 13.333
H = 7.5
# Template typefaces and palette.
FONT = "HarmonyOS Sans SC"
TITLE_FONT = "Alimama ShuHeiTi Bold"
LATIN_FONT = "Arial"
INK = "202533"
MUTED = "667085"
BLUE = "142DA8"
ACCENT_BLUE = "4874CB"
ORANGE = "EE822F"
YELLOW = "F2BA02"
GREEN = "75BD42"
TEAL = "30C0B4"
RED = "E54C5E"
WHITE = "FFFFFF"
PALE_BLUE = "EDF3FF"
PALE_ORANGE = "FFF3E9"
PALE_TEAL = "EAF9F7"
PALE_GRAY = "F5F7FA"
LINE = "D7DEEA"
def color(value: str) -> RGBColor:
return RGBColor.from_string(value)
with ZipFile(TEMPLATE) as archive:
MEDIA = {
name.rsplit("/", 1)[-1]: archive.read(name)
for name in archive.namelist()
if name.startswith("ppt/media/") and not name.endswith("/")
}
prs = Presentation(str(TEMPLATE))
while len(prs.slides):
sld_id = prs.slides._sldIdLst[0]
prs.part.drop_rel(sld_id.rId)
del prs.slides._sldIdLst[0]
blank = prs.slide_layouts[6]
def add_template_image(slide, media_name, x, y, w, h):
return slide.shapes.add_picture(
BytesIO(MEDIA[media_name]), Inches(x), Inches(y), Inches(w), Inches(h)
)
def add_rect(slide, x, y, w, h, fill=WHITE, line=None, radius=False):
shape_type = MSO_SHAPE.ROUNDED_RECTANGLE if radius else MSO_SHAPE.RECTANGLE
shape = slide.shapes.add_shape(shape_type, Inches(x), Inches(y), Inches(w), Inches(h))
shape.fill.solid()
shape.fill.fore_color.rgb = color(fill)
if line:
shape.line.color.rgb = color(line)
shape.line.width = Pt(1)
else:
shape.line.fill.background()
if radius:
try:
shape.adjustments[0] = 0.06
except Exception:
pass
return shape
def add_text(
slide,
text,
x,
y,
w,
h,
size=17,
fill=INK,
bold=False,
font=FONT,
align=PP_ALIGN.LEFT,
valign=MSO_ANCHOR.TOP,
margin=0.02,
line_spacing=1.05,
):
shape = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
frame = shape.text_frame
frame.clear()
frame.margin_left = frame.margin_right = Inches(margin)
frame.margin_top = frame.margin_bottom = Inches(margin)
frame.vertical_anchor = valign
para = frame.paragraphs[0]
para.text = text
para.alignment = align
para.space_after = Pt(0)
para.line_spacing = line_spacing
para.font.name = font
para.font.size = Pt(size)
para.font.bold = bold
para.font.color.rgb = color(fill)
return shape
def add_rich(slide, runs, x, y, w, h, align=PP_ALIGN.LEFT):
shape = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
frame = shape.text_frame
frame.clear()
frame.margin_left = frame.margin_right = Inches(0.02)
frame.margin_top = frame.margin_bottom = Inches(0.02)
para = frame.paragraphs[0]
para.alignment = align
for text, size, bold, fill, font in runs:
run = para.add_run()
run.text = text
run.font.name = font
run.font.size = Pt(size)
run.font.bold = bold
run.font.color.rgb = color(fill)
return shape
def add_bullets(slide, items, x, y, w, h, size=15, fill=INK, gap=5, accent=BLUE):
shape = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
frame = shape.text_frame
frame.clear()
frame.margin_left = frame.margin_right = Inches(0.02)
frame.margin_top = frame.margin_bottom = Inches(0.02)
for index, item in enumerate(items):
para = frame.paragraphs[0] if index == 0 else frame.add_paragraph()
para.text = "• " + item
para.font.name = FONT
para.font.size = Pt(size)
para.font.color.rgb = color(fill)
para.space_after = Pt(gap)
para.line_spacing = 1.06
return shape
def add_arrow(slide, x, y, w, h, fill=BLUE):
shape = slide.shapes.add_shape(MSO_SHAPE.RIGHT_ARROW, Inches(x), Inches(y), Inches(w), Inches(h))
shape.fill.solid()
shape.fill.fore_color.rgb = color(fill)
shape.line.fill.background()
return shape
def add_line(slide, x, y, w, h=0.02, fill=LINE):
return add_rect(slide, x, y, w, h, fill)
def add_image_contain(slide, path: Path, x, y, w, h, frame=True):
if frame:
add_rect(slide, x - 0.03, y - 0.03, w + 0.06, h + 0.06, WHITE, LINE)
with Image.open(path) as image:
iw, ih = image.size
scale = min(w / iw, h / ih)
pw, ph = iw * scale, ih * scale
return slide.shapes.add_picture(
str(path), Inches(x + (w - pw) / 2), Inches(y + (h - ph) / 2), Inches(pw), Inches(ph)
)
def set_cell(cell, text, size=12, bold=False, fill=INK, bg=None, align=PP_ALIGN.LEFT):
if bg:
cell.fill.solid()
cell.fill.fore_color.rgb = color(bg)
frame = cell.text_frame
frame.clear()
frame.margin_left = frame.margin_right = Inches(0.07)
frame.margin_top = frame.margin_bottom = Inches(0.03)
frame.vertical_anchor = MSO_ANCHOR.MIDDLE
para = frame.paragraphs[0]
para.text = text
para.alignment = align
para.font.name = LATIN_FONT if "seed" in text else FONT
para.font.size = Pt(size)
para.font.bold = bold
para.font.color.rgb = color(fill)
def add_table(slide, rows, x, y, w, h, widths=None, size=12):
shape = slide.shapes.add_table(len(rows), len(rows[0]), Inches(x), Inches(y), Inches(w), Inches(h))
table = shape.table
if widths:
for index, width in enumerate(widths):
table.columns[index].width = Inches(width)
for r, row in enumerate(rows):
for c, value in enumerate(row):
set_cell(
table.cell(r, c),
value,
size=size,
bold=r == 0,
fill=WHITE if r == 0 else INK,
bg=BLUE if r == 0 else (PALE_GRAY if r % 2 == 0 else WHITE),
align=PP_ALIGN.CENTER if c else PP_ALIGN.LEFT,
)
return table
def add_metric(slide, value, label, x, y, w, accent=BLUE, suffix=""):
add_rich(
slide,
[(value, 24, True, accent, TITLE_FONT), (suffix, 13, False, accent, LATIN_FONT)],
x,
y,
w,
0.45,
PP_ALIGN.CENTER,
)
add_text(slide, label, x, y + 0.48, w, 0.45, 11, MUTED, align=PP_ALIGN.CENTER)
def add_number_badge(slide, number, x, y, fill=BLUE):
add_rect(slide, x, y, 0.47, 0.47, fill, radius=True)
add_text(slide, number, x, y + 0.08, 0.47, 0.22, 12, WHITE, True, font=LATIN_FONT, align=PP_ALIGN.CENTER)
def new_content_slide(title, number, subtitle=None):
slide = prs.slides.add_slide(blank)
add_template_image(slide, "image1.png", 0, 0, W, H)
add_template_image(slide, "image2.png", 0.28, -0.02, 1.82, 1.12)
add_text(slide, title, 2.4, 0.48, 8.55, 0.55, 24, BLUE, True, TITLE_FONT, PP_ALIGN.CENTER)
add_rect(slide, 6.17, 1.17, 0.98, 0.07, ORANGE)
if subtitle:
add_text(slide, subtitle, 10.9, 0.53, 1.9, 0.28, 9.5, MUTED, align=PP_ALIGN.RIGHT)
add_text(slide, str(number), 12.35, 6.98, 0.72, 0.3, 9, MUTED, font=LATIN_FONT, align=PP_ALIGN.LEFT)
return slide
def new_preview_slide(number, label):
slide = prs.slides.add_slide(blank)
add_template_image(slide, "image1.png", 0, 0, W, H)
add_template_image(slide, "image2.png", 0.3, -0.02, 1.82, 1.12)
add_text(slide, "作品重要亮点预览页", 3.2, 0.48, 6.95, 0.56, 25, BLUE, True, TITLE_FONT, PP_ALIGN.CENTER)
add_rect(slide, 6.17, 1.17, 0.98, 0.07, ORANGE)
add_text(slide, label, 10.85, 0.53, 1.95, 0.28, 10, ORANGE, True, align=PP_ALIGN.RIGHT)
add_text(slide, str(number), 12.35, 6.98, 0.72, 0.3, 9, MUTED, font=LATIN_FONT, align=PP_ALIGN.LEFT)
return slide
# 1. Cover
slide = prs.slides.add_slide(blank)
add_template_image(slide, "image3.png", 0, 0, W, H)
add_template_image(slide, "image4.png", 5.93, 0.65, 1.48, 1.48)
add_text(slide, "第十届全国大学生集成电路创新创业大赛", 1.15, 2.2, 11.05, 0.48, 24, BLUE, True, TITLE_FONT, PP_ALIGN.CENTER)
add_text(slide, "基于龙芯SoC的后量子密码与矩阵运算加速系统", 1.1, 2.82, 11.15, 0.78, 30, BLUE, True, TITLE_FONT, PP_ALIGN.CENTER)
add_rect(slide, 4.2, 3.9, 4.93, 0.62, BLUE, radius=True)
add_text(slide, "龙芯中科杯 · 系统设计答辩", 4.33, 4.05, 4.67, 0.26, 16, WHITE, True, align=PP_ALIGN.CENTER)
add_text(slide, "作品编号:CICC1008439 · 团队:碳基生物", 4.25, 5.25, 4.85, 0.34, 13, INK, True, align=PP_ALIGN.CENTER)
add_text(slide, "证据版本 d456463 · 2026-07-15", 4.4, 5.72, 4.55, 0.3, 11, MUTED, align=PP_ALIGN.CENTER)
# 2. Team introduction
slide = new_content_slide("团队介绍:围绕完整系统形成协同分工", 2, "团队与项目")
add_text(slide, "团队名称:碳基生物", 0.88, 1.48, 4.2, 0.36, 16, BLUE, True)
roles = [
(0.82, BLUE, PALE_BLUE, "系统架构与SoC集成", ["OpenLA500平台集成", "AXI、DMA与多时钟域", "SRAM控制器优化"]),
(4.52, TEAL, PALE_TEAL, "密码算法与RTL实现", ["全参数ML-KEM架构", "Keccak、NTT与多项式算子", "常量时间解封装"]),
(8.22, ORANGE, PALE_ORANGE, "软件验证、测试与文档", ["板级驱动与测试程序", "分层仿真和证据采集", "系统报告与答辩材料"]),
]
for x, accent, pale, title, items in roles:
add_rect(slide, x, 2.08, 3.35, 3.72, pale, accent, radius=True)
add_text(slide, title, x + 0.18, 2.38, 2.99, 0.36, 17, accent, True, TITLE_FONT, PP_ALIGN.CENTER)
add_bullets(slide, items, x + 0.32, 3.12, 2.72, 1.85, size=13, gap=10)
add_rect(slide, 1.0, 6.18, 11.15, 0.55, PALE_GRAY, LINE)
add_text(slide, "以统一接口、可重复测试和版本化证据贯通硬件、软件与文档。", 1.2, 6.34, 10.75, 0.24, 13, INK, True, align=PP_ALIGN.CENTER)
# 3. Contents
slide = prs.slides.add_slide(blank)
add_template_image(slide, "image3.png", 0, 0, W, H)
add_text(slide, "目录", 0.82, 0.48, 1.6, 0.55, 29, BLUE, True, TITLE_FONT)
add_text(slide, "CONTENTS", 0.91, 1.15, 1.5, 0.22, 11, MUTED, font=LATIN_FONT)
contents = [
("1", "项目与系统方案", "开发过程、设计目标与系统数据流", BLUE),
("2", "自主计算IP", "Matmul流水与全参数ML-KEM", ORANGE),
("3", "验证与实现结果", "分层验证、资源时序与最终性能", TEAL),
("4", "心得与后续工作", "工程认识、设计取舍与优化路线", GREEN),
]
for i, (num, title, sub, accent) in enumerate(contents):
y = 1.75 + i * 1.2
add_rect(slide, 2.25, y, 0.58, 0.58, accent)
add_text(slide, num, 2.25, y + 0.13, 0.58, 0.24, 15, WHITE, True, align=PP_ALIGN.CENTER)
add_text(slide, title, 3.18, y - 0.02, 4.35, 0.34, 17, INK, True)
add_text(slide, sub, 3.18, y + 0.42, 5.1, 0.3, 12, MUTED)
# 4. Preview: architecture and contributions
slide = new_preview_slide(4, "系统方案")
add_rect(slide, 0.78, 1.52, 7.75, 4.8, WHITE, LINE)
add_image_contain(slide, SYSTEM / "system_arch.png", 0.94, 1.7, 7.43, 4.45, frame=False)
add_text(slide, "OpenLA500 + DMA数据面 + 三个自主计算IP", 0.95, 6.4, 7.4, 0.3, 13, BLUE, True, align=PP_ALIGN.CENTER)
highlights = [
("01", "硬件自治", "五千组 Matmul 任务一次启动完成", BLUE),
("02", "存储优化", "SRAM连续突发达到1 beat/cycle", ORANGE),
("03", "全参数密码", "单比特流覆盖三等级、三操作", TEAL),
("04", "异步协同", "三时钟域通过全五通道AXI FIFO连接", GREEN),
]
for i, (num, title, desc, accent) in enumerate(highlights):
y = 1.62 + i * 1.18
add_number_badge(slide, num, 8.85, y, accent)
add_text(slide, title, 9.48, y - 0.02, 2.55, 0.3, 15, accent, True)
add_text(slide, desc, 9.48, y + 0.37, 2.85, 0.48, 11.5, INK)
add_rect(slide, 8.78, 6.25, 3.72, 0.58, PALE_BLUE, BLUE)
add_text(slide, "全系统 0 DSP · 三时钟域零违例", 8.92, 6.42, 3.44, 0.24, 12, BLUE, True, align=PP_ALIGN.CENTER)
# 5. Preview: measured results
slide = new_preview_slide(5, "关键结果")
metrics = [
("4.911765", "Matmul 5000组最终时间", BLUE, " ms"),
("827.80", "FFT DMA相对软件加速", ORANGE, "×"),
("48.95–53.33", "ML-KEM DMA相对软件加速", TEAL, "×"),
("0", "DSP48E1使用量", GREEN, " DSP"),
]
for i, (value, label, accent, suffix) in enumerate(metrics):
x = 0.72 + i * 3.12
add_rect(slide, x, 1.62, 2.77, 1.2, WHITE, accent, radius=True)
add_metric(slide, value, label, x + 0.12, 1.77, 2.53, accent, suffix)
add_table(
slide,
[
["验证对象", "测试规模", "结果"],
["ML-KEM主路径KAT", "3等级 × 3操作 × 1000", "9000 / 9000"],
["Decaps隐式拒绝", "3等级 × 100", "300 / 300"],
["ML-KEM板级诊断", "3等级 × 3路径 × 100", "900次零失败"],
["Matmul最终评测", "5000组 · seed a3772ec8", "4.911765 ms · 满分"],
["FFT板级验证", "CPU / DMA各3次", "误差Re=1、Im=2"],
],
0.92,
3.18,
7.2,
3.05,
widths=[2.25, 2.9, 2.05],
size=11.5,
)
add_rect(slide, 8.5, 3.18, 3.88, 3.05, PALE_ORANGE, ORANGE, radius=True)
add_text(slide, "实现质量", 8.82, 3.48, 3.2, 0.34, 18, ORANGE, True, TITLE_FONT, PP_ALIGN.CENTER)
add_metric(slide, "55,066", "Slice LUT · 41.16%", 8.85, 4.04, 1.45, BLUE)
add_metric(slide, "29.5", "BRAM Tile · 8.08%", 10.48, 4.04, 1.45, TEAL)
add_metric(slide, "88,843", "时序端点", 8.85, 5.2, 1.45, GREEN)
add_metric(slide, "+0.070", "全系统最小WNS / ns", 10.48, 5.2, 1.45, ORANGE)
# 6. Development process
slide = new_content_slide("开发过程:以端到端证据驱动迭代", 6, "项目与系统方案")
stages = [
("1", "基础SoC跑通", "完成处理器取指、串口及总线外设联调", BLUE),
("2", "计算IP接入", "实现Matmul、CRC32、FFT与全参数ML-KEM", TEAL),
("3", "数据通路优化", "引入DMA端点、双Bank调度和SRAM连续突发", ORANGE),
("4", "时钟与复位收敛", "完成PLL分域、AXI CDC和复位顺序验证", GREEN),
("5", "分层验证闭环", "建立叶模块、IP、SoC与板级四层证据", BLUE),
("6", "最终性能迭代", "压缩启动与稳态空闲,Matmul评测达到100分", ORANGE),
]
for i, (num, title, desc, accent) in enumerate(stages):
col, row = i % 3, i // 3
x, y = 0.78 + col * 4.12, 1.55 + row * 2.35
add_rect(slide, x, y, 3.68, 1.75, WHITE, accent, radius=True)
add_number_badge(slide, num, x + 0.2, y + 0.25, accent)
add_text(slide, title, x + 0.88, y + 0.23, 2.55, 0.3, 15, accent, True)
add_text(slide, desc, x + 0.25, y + 0.82, 3.18, 0.68, 11.2, INK, align=PP_ALIGN.CENTER)
if i < 2 or 3 <= i < 5:
add_arrow(slide, x + 3.75, y + 0.74, 0.25, 0.17, accent)
add_rect(slide, 1.0, 6.35, 11.15, 0.44, PALE_TEAL, TEAL)
add_text(slide, "每轮迭代同时检查功能正确性、总线可观测性、时序与板级端到端时间。", 1.22, 6.48, 10.7, 0.2, 12.5, INK, True, align=PP_ALIGN.CENTER)
# 7. Contest requirements
slide = new_content_slide("赛题要求与设计响应", 7, "项目与系统方案")
add_text(slide, "赛题不是孤立IP设计,而是完整SoC中的端到端实现。", 0.82, 1.48, 8.3, 0.34, 13, MUTED)
add_table(
slide,
[
["赛题要求 / 工程约束", "本系统的设计响应"],
["基于OpenLA500构建完整SoC", "双主八从AXI架构;CPU、系统、ML-KEM三个异步时钟域"],
["5000组4×4矩阵乘法,禁止DSP", "全LUT乘加、双Bank序列器、在线CRC32与快速通道"],
["实现FIPS 203 ML-KEM", "单比特流覆盖三等级、三操作;KAT与隐式拒绝验证"],
["提高端到端性能", "四通道DMA、端点传输、SRAM连续突发与硬件自主调度"],
],
0.85,
1.98,
11.6,
3.85,
widths=[4.0, 7.6],
size=13,
)
add_rect(slide, 0.9, 6.15, 11.5, 0.58, PALE_TEAL, TEAL)
add_text(slide, "答辩主线:赛题约束 → 系统架构 → 数据流与自主IP → 分层验证 → 实测结果", 1.1, 6.32, 11.1, 0.24, 14, INK, True, align=PP_ALIGN.CENTER)
# 8. Architecture and ownership
slide = new_content_slide("总体架构与自主设计边界", 8, "项目与系统方案")
add_image_contain(slide, SYSTEM / "system_arch.png", 0.7, 1.48, 8.0, 5.15)
add_rect(slide, 9.02, 1.5, 3.45, 3.78, WHITE, TEAL, radius=True)
add_text(slide, "自主实现", 9.25, 1.78, 3.0, 0.34, 18, TEAL, True, TITLE_FONT, PP_ALIGN.CENTER)
add_bullets(
slide,
[
"四通道DMA与加速器端点",
"Matmul、批量序列器、CRC32",
"1024点Radix-2² SDF FFT",
"全参数ML-KEM硬件IP",
"两级AXI跨时钟域桥",
"SRAM控制器突发流水化",
],
9.28,
2.35,
2.95,
2.55,
size=13.2,
gap=5,
)
add_rect(slide, 9.02, 5.5, 3.45, 1.1, PALE_GRAY, LINE, radius=True)
add_text(slide, "赛事基础平台", 9.25, 5.72, 2.95, 0.28, 14, MUTED, True, align=PP_ALIGN.CENTER)
add_text(slide, "OpenLA500、板级SRAM器件、UART及基础外设", 9.25, 6.08, 2.95, 0.36, 11, INK, align=PP_ALIGN.CENTER)
# 9. Data movement, clocks and SRAM
slide = new_content_slide("数据搬运、SRAM与三时钟域协同", 9, "项目与系统方案")
clock_info = [
(0.8, BLUE, "CPU域 · 33.333 MHz", "OpenLA500 / Cache / AXI主端"),
(5.0, TEAL, "系统域 · 50 MHz", "Crossbar / DMA / SRAM / Matmul / FFT"),
(9.2, ORANGE, "ML-KEM域 · 76.923 MHz", "AXI前端 / Keccak / NTT / FSM"),
]
for x, accent, title, desc in clock_info:
add_rect(slide, x, 1.5, 3.3, 0.85, WHITE, accent, radius=True)
add_text(slide, title, x + 0.1, 1.68, 3.1, 0.28, 13.5, accent, True, align=PP_ALIGN.CENTER)
add_text(slide, desc, x + 0.1, 2.02, 3.1, 0.2, 9.5, MUTED, align=PP_ALIGN.CENTER)
add_arrow(slide, 4.26, 1.78, 0.5, 0.25, BLUE)
add_arrow(slide, 8.46, 1.78, 0.5, 0.25, TEAL)
add_text(slide, "AXI双时钟FIFO", 3.84, 2.34, 1.35, 0.2, 9, MUTED, align=PP_ALIGN.CENTER)
add_text(slide, "AXI双时钟FIFO", 8.04, 2.34, 1.35, 0.2, 9, MUTED, align=PP_ALIGN.CENTER)
add_rect(slide, 0.82, 2.85, 5.7, 3.45, WHITE, BLUE, radius=True)
add_text(slide, "硬件数据通路", 1.05, 3.08, 5.2, 0.3, 17, BLUE, True, TITLE_FONT, PP_ALIGN.CENTER)
flow = [("CPU配置", BLUE), ("DMA", TEAL), ("ExtRAM", YELLOW), ("加速器", ORANGE)]
x = 1.05
for index, (name, accent) in enumerate(flow):
add_rect(slide, x, 3.72, 1.03, 0.58, PALE_GRAY, accent)
add_text(slide, name, x, 3.88, 1.03, 0.22, 11, accent, True, align=PP_ALIGN.CENTER)
if index < 3:
add_arrow(slide, x + 1.08, 3.9, 0.35, 0.18, accent)
x += 1.43
add_text(slide, "CPU只配置任务,DMA、SRAM与加速器完成批量搬运和计算。", 1.02, 4.65, 5.25, 0.5, 12, INK, align=PP_ALIGN.CENTER)
add_text(slide, "复位顺序:sys → mlkem → cpu;释放前AXI valid保持为0。", 1.02, 5.42, 5.25, 0.38, 11, MUTED, align=PP_ALIGN.CENTER)
add_rect(slide, 6.82, 2.85, 5.65, 3.45, WHITE, ORANGE, radius=True)
add_text(slide, "SRAM连续突发优化", 7.05, 3.08, 5.2, 0.3, 17, ORANGE, True, TITLE_FONT, PP_ALIGN.CENTER)
add_text(slide, "优化前", 7.12, 3.63, 0.75, 0.24, 10.5, MUTED, True)
for i in range(4):
accent = BLUE if i % 2 == 0 else "E3E7EF"
add_rect(slide, 8.08 + i * 0.72, 3.58, 0.42, 0.36, accent)
add_text(slide, "优化后", 7.12, 4.18, 0.75, 0.24, 10.5, ORANGE, True)
for i in range(4):
add_rect(slide, 8.08 + i * 0.48, 4.13, 0.42, 0.36, TEAL)
add_metric(slide, "200", "理论单向带宽", 7.15, 4.82, 2.0, ORANGE, " MB/s")
add_metric(slide, "1", "无反压连续读写", 9.52, 4.82, 2.0, TEAL, " beat/cycle")
add_text(slide, "读数据寄存保持R通道稳定;Bank选择随事务锁存。", 7.12, 5.9, 5.0, 0.32, 11, INK, align=PP_ALIGN.CENTER)
# 10. Matmul
slide = new_content_slide("Matmul:一次启动完成5000组流水任务", 10, "自主计算IP")
add_image_contain(slide, SYSTEM / "matmul_dataflow_gantt.png", 0.65, 1.5, 9.55, 5.3)
add_rect(slide, 10.45, 1.5, 2.02, 5.3, WHITE, BLUE, radius=True)
add_text(slide, "关键节拍", 10.65, 1.78, 1.62, 0.3, 17, BLUE, True, TITLE_FONT, PP_ALIGN.CENTER)
for y, value, label, accent in [
(2.26, "128 B", "每组A/B读取", BLUE),
(3.18, "≈22 cyc", "单Bank乘加延迟", GREEN),
(4.10, "2×6 B", "每元素CRC更新", ORANGE),
(5.02, "0 B", "快速模式结果写回", TEAL),
]:
add_text(slide, value, 10.66, y, 1.62, 0.3, 18, accent, True, TITLE_FONT, PP_ALIGN.CENTER)
add_text(slide, label, 10.66, y + 0.34, 1.62, 0.26, 10, MUTED, align=PP_ALIGN.CENTER)
add_text(slide, "最后一组退休后仍等待CRC FIFO排空,再产生DONE。", 10.7, 6.03, 1.55, 0.5, 10.5, INK, align=PP_ALIGN.CENTER)
# 11. ML-KEM architecture
slide = new_content_slide("ML-KEM:单比特流覆盖三等级与三操作", 11, "自主计算IP")
add_image_contain(slide, MLKEM / "mlkem_modules.png", 0.65, 1.48, 8.05, 4.8)
add_rect(slide, 9.0, 1.5, 3.45, 3.95, WHITE, TEAL, radius=True)
add_text(slide, "运行时配置", 9.25, 1.8, 2.95, 0.3, 18, TEAL, True, TITLE_FONT, PP_ALIGN.CENTER)
add_metric(slide, "3", "512 / 768 / 1024", 9.27, 2.28, 1.3, TEAL)
add_metric(slide, "3", "KeyGen / Encaps / Decaps", 10.78, 2.28, 1.35, ORANGE)
add_bullets(
slide,
[
"参数在start时锁存",
"存储按KMAX=4静态分配",
"控制、算子、BRAM三级组织",
"公钥/私钥/密文支持AXI突发窗口",
"无需按安全等级重新综合",
],
9.28,
3.38,
2.95,
1.72,
size=11.8,
gap=4,
)
add_rect(slide, 0.82, 6.36, 11.47, 0.46, PALE_TEAL, TEAL)
add_text(slide, "KeyGen 生成 ek/dk", 1.45, 6.49, 2.45, 0.2, 12, BLUE, True, align=PP_ALIGN.CENTER)
add_arrow(slide, 4.02, 6.5, 0.48, 0.17, BLUE)
add_text(slide, "Encaps 生成 c/ss", 4.62, 6.49, 2.45, 0.2, 12, TEAL, True, align=PP_ALIGN.CENTER)
add_arrow(slide, 7.18, 6.5, 0.48, 0.17, TEAL)
add_text(slide, "Decaps 恢复或拒绝 ss", 7.78, 6.49, 3.05, 0.2, 12, ORANGE, True, align=PP_ALIGN.CENTER)
# 12. ML-KEM key designs
slide = new_content_slide("ML-KEM关键设计:复用、并行与常量时间", 12, "自主计算IP")
panels = [
(0.72, BLUE, PALE_BLUE, "共享Keccak"),
(4.52, TEAL, PALE_TEAL, "双蝶形NTT"),
(8.32, ORANGE, PALE_ORANGE, "Decaps复用"),
]
for x, accent, pale, title in panels:
add_rect(slide, x, 1.55, 3.5, 4.95, pale, accent, radius=True)
add_text(slide, title, x + 0.2, 1.84, 3.1, 0.34, 18, accent, True, TITLE_FONT, PP_ALIGN.CENTER)
# Shared Keccak panel
for i, name in enumerate(["G/H/J哈希", "SampleNTT", "CBD采样"]):
add_rect(slide, 0.98, 2.55 + i * 0.62, 1.15, 0.38, WHITE, LINE)
add_text(slide, name, 0.98, 2.66 + i * 0.62, 1.15, 0.16, 9.5, INK, align=PP_ALIGN.CENTER)
add_arrow(slide, 2.22, 2.67 + i * 0.62, 0.36, 0.16, BLUE)
add_rect(slide, 2.65, 2.83, 1.25, 1.0, WHITE, BLUE)
add_text(slide, "单一\nKeccak-f[1600]", 2.72, 3.08, 1.12, 0.45, 11, BLUE, True, align=PP_ALIGN.CENTER)
add_text(slide, "避免两份额外实例\n约节省10.6k LUT", 1.05, 4.85, 2.82, 0.6, 13, BLUE, True, align=PP_ALIGN.CENTER)
# NTT panel
add_rect(slide, 4.92, 2.6, 1.1, 1.3, WHITE, TEAL)
for i in range(4):
add_rect(slide, 5.08, 2.75 + i * 0.24, 0.78, 0.14, TEAL if i % 2 == 0 else ACCENT_BLUE)
add_arrow(slide, 6.15, 3.08, 0.4, 0.2, TEAL)
add_rect(slide, 6.65, 2.6, 1.08, 1.3, WHITE, TEAL)
add_text(slide, "BU0\nBU1", 6.72, 2.99, 0.94, 0.42, 14, TEAL, True, align=PP_ALIGN.CENTER)
add_text(slide, "4路BRAM无冲突供数\n读-算-写三级调度", 4.95, 4.28, 2.75, 0.58, 12, INK, align=PP_ALIGN.CENTER)
add_text(slide, "4 coeff/cycle · 0 DSP", 4.98, 5.14, 2.72, 0.32, 15, TEAL, True, align=PP_ALIGN.CENTER)
# Decaps panel
for i, (name, accent) in enumerate([("D1–D5 解密恢复", BLUE), ("D6 复用Encaps", TEAL), ("D7 常量时间比较", ORANGE)]):
add_rect(slide, 8.7, 2.5 + i * 0.82, 2.75, 0.5, WHITE, accent)
add_text(slide, name, 8.82, 2.64 + i * 0.82, 2.51, 0.2, 11, accent, True, align=PP_ALIGN.CENTER)
if i < 2:
down = slide.shapes.add_shape(MSO_SHAPE.DOWN_ARROW, Inches(9.88), Inches(3.04 + i * 0.82), Inches(0.38), Inches(0.2))
down.fill.solid(); down.fill.fore_color.rgb = color(accent); down.line.fill.background()
add_text(slide, "Accept与Reject路径\n总周期数完全相等", 8.86, 5.25, 2.44, 0.52, 13, ORANGE, True, align=PP_ALIGN.CENTER)
add_text(slide, "设计取舍:以资源共享和运行时可配置性换取更低的面积与部署复杂度。", 1.0, 6.68, 11.3, 0.28, 13, INK, True, align=PP_ALIGN.CENTER)
# 13. Verification
slide = new_content_slide("四层验证:从算子正确到板级闭环", 13, "验证与实现结果")
bands = [
(1.55, 1.35, 8.25, "板级系统", "真实FPGA、CPU软件、DMA与串口日志", ORANGE),
(2.55, 1.95, 7.05, "SoC集成", "AXI解码、CDC、复位释放、SRAM取指", TEAL),
(3.55, 2.55, 5.85, "IP集成", "Matmul / FFT / DMA / ML-KEM端到端", BLUE),
(4.55, 3.15, 4.65, "叶模块", "Keccak、NTT、蝶形、FIFO、CRC32", GREEN),
]
for y, x, width, title, desc, accent in bands:
add_rect(slide, x, y, width, 0.7, WHITE, accent, radius=True)
add_text(slide, title, x + 0.18, y + 0.17, 1.2, 0.24, 13, accent, True)
add_text(slide, desc, x + 1.45, y + 0.16, width - 1.65, 0.26, 11.5, INK, align=PP_ALIGN.CENTER)
add_rect(slide, 9.88, 1.55, 2.5, 4.75, PALE_ORANGE, ORANGE, radius=True)
add_text(slide, "验证结果", 10.15, 1.82, 1.96, 0.3, 17, ORANGE, True, TITLE_FONT, PP_ALIGN.CENTER)
for i, (value, label, accent) in enumerate([
("9000", "主路径KAT", BLUE),
("300", "隐式拒绝向量", ORANGE),
("900", "ML-KEM板级运行", TEAL),
("100%", "Matmul最终得分", GREEN),
("6", "FFT板级测试", YELLOW),
]):
y = 2.28 + i * 0.76
add_text(slide, value, 10.05, y, 0.95, 0.28, 16, accent, True, TITLE_FONT, PP_ALIGN.RIGHT)
add_text(slide, label, 11.12, y + 0.03, 1.05, 0.26, 10, INK)
add_rect(slide, 1.0, 5.75, 8.55, 0.58, PALE_TEAL, TEAL)
add_text(slide, "算法逐字节比对;板级以零失败、CRC匹配和误差阈值为判据。", 1.18, 5.92, 8.2, 0.24, 12.5, INK, True, align=PP_ALIGN.CENTER)
add_text(slide, "全部通过", 9.98, 6.48, 2.25, 0.36, 20, GREEN, True, TITLE_FONT, PP_ALIGN.CENTER)
# 14. Resources and timing
slide = new_content_slide("实现结果:资源可控,三个时钟域全部收敛", 14, "验证与实现结果")
add_rect(slide, 0.78, 1.5, 5.72, 4.85, WHITE, BLUE, radius=True)
add_text(slide, "FPGA资源利用", 1.05, 1.78, 5.18, 0.3, 17, BLUE, True, TITLE_FONT, PP_ALIGN.CENTER)
resource_data = [
("Slice LUT", 41.16, "55,066", BLUE),
("Registers", 11.58, "31,000", TEAL),
("BRAM Tile", 8.08, "29.5", YELLOW),
("DSP48E1", 0.0, "0", ORANGE),
]
for i, (label, percent, absolute, accent) in enumerate(resource_data):
y = 2.45 + i * 0.75
add_text(slide, label, 1.02, y, 1.1, 0.24, 11, INK)
add_rect(slide, 2.2, y + 0.02, 2.55, 0.22, "E5E9F1")
if percent:
add_rect(slide, 2.2, y + 0.02, 2.55 * percent / 50, 0.22, accent)
add_text(slide, f"{percent:.2f}%", 4.85, y - 0.02, 0.68, 0.26, 10.5, accent, True, align=PP_ALIGN.RIGHT)
add_text(slide, absolute, 5.6, y - 0.02, 0.55, 0.26, 10, MUTED, align=PP_ALIGN.RIGHT)
add_text(slide, "xc7a200tfbg676-1 · 布局布线100%完成", 1.1, 5.72, 5.08, 0.28, 11.5, MUTED, align=PP_ALIGN.CENTER)
add_rect(slide, 6.82, 1.5, 5.62, 4.85, WHITE, TEAL, radius=True)
add_text(slide, "布局布线后时序", 7.08, 1.78, 5.1, 0.3, 17, TEAL, True, TITLE_FONT, PP_ALIGN.CENTER)
add_table(
slide,
[
["时钟域", "频率", "Setup WNS", "端点"],
["cpu_clk", "33.333 MHz", "+0.737 ns", "17,197"],
["sys_clk", "50.000 MHz", "+0.125 ns", "22,307"],
["mlkem_clk", "76.923 MHz", "+0.070 ns", "31,646"],
],
7.08,
2.35,
5.1,
2.35,
widths=[1.15, 1.35, 1.35, 1.25],
size=10.5,
)
add_metric(slide, "88,843", "时序端点", 7.15, 5.0, 1.45, TEAL)
add_metric(slide, "0", "建立/保持违例", 8.95, 5.0, 1.45, GREEN)
add_metric(slide, "+0.070", "全系统最小WNS / ns", 10.72, 5.0, 1.45, ORANGE)
add_rect(slide, 1.0, 6.58, 11.2, 0.35, PALE_BLUE, BLUE)
add_text(slide, "ML-KEM独立高速时钟域隔离了系统总线布线压力。", 1.2, 6.66, 10.8, 0.18, 11.5, BLUE, True, align=PP_ALIGN.CENTER)
# 15. Performance
slide = new_content_slide("最终性能:Matmul满分与端到端加速", 15, "验证与实现结果")
for i, (value, label, accent, suffix) in enumerate([
("4.911765", "Matmul 5000组最终时间", BLUE, " ms"),
("827.80", "FFT DMA相对软件加速", ORANGE, "×"),
("48.95–53.33", "ML-KEM DMA相对软件加速", TEAL, "×"),
]):
x = 0.85 + i * 4.05
add_rect(slide, x, 1.5, 3.55, 1.05, WHITE, accent, radius=True)
add_metric(slide, value, label, x + 0.15, 1.66, 3.25, accent, suffix)
add_rect(slide, 0.78, 2.92, 7.2, 3.55, WHITE, ORANGE, radius=True)
add_text(slide, "ML-KEM三路径完整往返加速比", 1.02, 3.17, 6.72, 0.3, 16, ORANGE, True, TITLE_FONT, PP_ALIGN.CENTER)
levels = ["512", "768", "1024"]
cpu = [13.04, 13.84, 15.05]
dma = [48.95, 50.41, 53.33]
for i, level in enumerate(levels):
y = 3.74 + i * 0.72
add_text(slide, f"ML-KEM-{level}", 1.0, y + 0.16, 1.15, 0.22, 10.5, INK)
add_rect(slide, 2.25, y, cpu[i] / 60 * 4.65, 0.23, BLUE)
add_rect(slide, 2.25, y + 0.31, dma[i] / 60 * 4.65, 0.23, ORANGE)
add_text(slide, f"{cpu[i]:.2f}×", 2.33 + cpu[i] / 60 * 4.65, y - 0.02, 0.62, 0.2, 8.5, BLUE, True)
add_text(slide, f"{dma[i]:.2f}×", 2.33 + dma[i] / 60 * 4.65, y + 0.29, 0.62, 0.2, 8.5, ORANGE, True)
add_rect(slide, 2.15, 6.04, 0.15, 0.15, BLUE)
add_text(slide, "CPU寄存器路径", 2.4, 6.02, 1.25, 0.2, 9, MUTED)
add_rect(slide, 3.9, 6.04, 0.15, 0.15, ORANGE)
add_text(slide, "DMA窗口路径", 4.15, 6.02, 1.25, 0.2, 9, MUTED)
add_rect(slide, 8.28, 2.92, 4.15, 3.55, PALE_TEAL, TEAL, radius=True)
add_text(slide, "结果解释", 8.55, 3.18, 3.6, 0.3, 16, TEAL, True, TITLE_FONT, PP_ALIGN.CENTER)
add_bullets(
slide,
[
"Matmul CRC32 0x0417d761匹配",
"FFT DMA延迟296.73 μs,误差Re=1、Im=2",
"ML-KEM-512 DMA往返约1.605 ms",
"SRAM连续突发理论带宽200 MB/s",
],
8.55,
3.78,
3.55,
1.85,
size=12,
gap=6,
)
add_text(slide, "随机种子0xa3772ec8 · 最终得分100.00%", 8.55, 5.92, 3.55, 0.34, 10.5, ORANGE, True, align=PP_ALIGN.CENTER)
# 16. Lessons learned
slide = new_content_slide("项目心得:从单点优化走向系统工程", 16, "心得与后续工作")
lessons = [
("端到端优先", "计算核延迟不是唯一指标;启动、搬运、调度和校验共同决定评测时间。", BLUE),
("先正确再加速", "先建立CRC、状态寄存器和分层测试等可观测手段,再压缩流水空闲。", ORANGE),
("取舍必须显式", "Keccak复用、双蝶形NTT和零DSP策略是在面积、吞吐与部署复杂度间的选择。", TEAL),
("证据需要版本化", "报告、软件、RTL和实测日志必须共享同一数据基线,才能保证结论可复现。", GREEN),
]
for i, (title, desc, accent) in enumerate(lessons):
col, row = i % 2, i // 2
x, y = 0.82 + col * 6.0, 1.55 + row * 2.45
add_rect(slide, x, y, 5.55, 1.85, WHITE, accent, radius=True)
add_number_badge(slide, f"0{i+1}", x + 0.25, y + 0.28, accent)
add_text(slide, title, x + 0.95, y + 0.27, 4.2, 0.32, 16, accent, True)
add_text(slide, desc, x + 0.35, y + 0.9, 4.85, 0.58, 12, INK, align=PP_ALIGN.CENTER)
add_rect(slide, 1.0, 6.38, 11.15, 0.42, PALE_ORANGE, ORANGE)
add_text(slide, "最终满分来自软硬件协同,而不是单一模块参数的堆叠。", 1.2, 6.5, 10.75, 0.2, 13, INK, True, align=PP_ALIGN.CENTER)
# 17. Future work
slide = new_content_slide("后续工作:沿性能、验证与工程化继续演进", 17, "心得与后续工作")
future = [
(BLUE, "ML-KEM并行度", "增加Keccak并行路径,扩展NTT蝶形单元并评估面积收益比。"),
(ORANGE, "存储专项验证", "补充SRAM随机反压、跨Bank边界与持续带宽微基准。"),
(TEAL, "实现与功耗证据", "完善I/O约束,使用SAIF/VCD活动率回标提高功耗估计可信度。"),
(GREEN, "版本与回归体系", "统一RTL、软件、测试向量和报告指标,形成自动化回归基线。"),
]
for i, (accent, title, desc) in enumerate(future):
y = 1.48 + i * 1.22
add_number_badge(slide, f"0{i+1}", 0.92, y, accent)
add_text(slide, title, 1.65, y + 0.07, 2.15, 0.3, 15, accent, True)
add_text(slide, desc, 3.92, y + 0.06, 7.85, 0.4, 13, INK)
add_line(slide, 0.95, y + 0.76, 11.15, 0.012, LINE)
add_rect(slide, 1.0, 6.45, 11.15, 0.35, PALE_TEAL, TEAL)
add_text(slide, "目标:在不牺牲标准完整性和可验证性的前提下继续提高系统吞吐。", 1.2, 6.55, 10.75, 0.18, 12.5, INK, True, align=PP_ALIGN.CENTER)
# 18. Summary
slide = new_content_slide("总结:完成从算法IP到板级系统的闭环", 18, "总结与展望")
summary = [
("01", "系统", "构建CPU、DMA、存储与三个计算加速器协同的异构SoC", BLUE),
("02", "数据流", "以SRAM连续突发、DMA端点和双Bank调度降低搬运开销", ORANGE),
("03", "密码IP", "单比特流覆盖ML-KEM三等级、三操作和常量时间拒绝", TEAL),
("04", "工程验证", "零DSP、三个时钟域收敛,并通过仿真与板级分层验证", GREEN),
]
for i, (num, tag, desc, accent) in enumerate(summary):
y = 1.52 + i * 1.08
add_number_badge(slide, num, 0.95, y, accent)
add_text(slide, tag, 1.65, y + 0.08, 1.0, 0.28, 15, accent, True)
add_text(slide, desc, 2.75, y + 0.08, 8.95, 0.36, 14, INK)
add_line(slide, 0.95, y + 0.72, 11.2, 0.012, LINE)
add_rect(slide, 0.92, 6.02, 11.3, 0.62, PALE_ORANGE, ORANGE)
add_rich(
slide,
[
("最终结果:", 12.5, True, INK, FONT),
("5000", 12.5, True, INK, LATIN_FONT),
("组 Matmul 用时4.911765 ms · CRC32匹配 · 得分100.00%", 12.5, True, INK, FONT),
],
1.12,
6.2,
10.9,
0.3,
PP_ALIGN.CENTER,
)
# 19. End
slide = prs.slides.add_slide(blank)
add_template_image(slide, "image3.png", 0, 0, W, H)
add_template_image(slide, "image4.png", 5.92, 0.7, 1.48, 1.48)
add_text(slide, "感谢聆听", 3.45, 2.55, 6.45, 0.75, 34, BLUE, True, TITLE_FONT, PP_ALIGN.CENTER)
add_rect(slide, 4.63, 3.65, 4.08, 0.72, BLUE, radius=True)
add_text(slide, "Q&A", 4.75, 3.83, 3.84, 0.3, 22, WHITE, True, LATIN_FONT, PP_ALIGN.CENTER)
add_text(slide, "中国集成电路未来之星从这里起步!", 3.2, 5.2, 6.95, 0.42, 17, INK, True, align=PP_ALIGN.CENTER)
# 20. Backup: address space
slide = new_content_slide("备用:地址空间与软件控制流程", 20, "答疑备用")
add_table(
slide,
[
["AXI从端", "基地址", "功能"],
["S0", "0x1c00_0000", "BaseRAM / ExtRAM,共8 MB"],
["S5", "0x1f30_0000", "DMA与矩阵批量序列器"],
["S6", "0x1f40_0000", "FFT / ML-KEM共享窗口"],
["S7", "0x1f50_0000", "Matmul控制与数据Bank"],
],
0.8,
1.55,
6.2,
3.75,
widths=[1.0, 2.0, 3.2],
size=12,
)
add_rect(slide, 7.4, 1.55, 4.8, 3.75, WHITE, TEAL, radius=True)
add_text(slide, "软件控制流程", 7.7, 1.83, 4.2, 0.3, 17, TEAL, True, TITLE_FONT, PP_ALIGN.CENTER)
for i, text in enumerate(["写配置寄存器", "配置DMA窗口", "启动硬件", "轮询done", "读取结果 / CRC"]):
y = 2.35 + i * 0.56
add_rect(slide, 8.05, y, 3.5, 0.38, PALE_GRAY, TEAL if i < 3 else BLUE)
add_text(slide, text, 8.15, y + 0.1, 3.3, 0.17, 10.5, INK, True, align=PP_ALIGN.CENTER)
add_rect(slide, 0.92, 5.75, 11.25, 0.62, PALE_TEAL, TEAL)
add_text(slide, "快速路径中CPU不参与逐组Matmul调度;ML-KEM DMA路径只进行少量批量窗口配置。", 1.15, 5.93, 10.8, 0.26, 12.5, INK, True, align=PP_ALIGN.CENTER)
# 21. Backup: KeyGen timing
slide = new_content_slide("备用:ML-KEM-512 KeyGen逐状态周期分解", 21, "答疑备用")
states = [
("G哈希", 27, YELLOW),
("SampleNTT矩阵A", 3213, ORANGE),
("CBD采样", 1229, TEAL),
("NTT", 4172, BLUE),
("多项式基乘", 2096, GREEN),
("密钥编码", 2081, RED),
]
for i, (name, cycles, accent) in enumerate(states):
y = 1.52 + i * 0.72
add_text(slide, name, 0.9, y + 0.1, 1.55, 0.22, 11, INK)
add_rect(slide, 2.5, y + 0.1, 6.45, 0.25, "E5E9F1")
add_rect(slide, 2.5, y + 0.1, 6.45 * cycles / 4500, 0.25, accent)
add_text(slide, f"{cycles:,} cyc", 9.1, y + 0.06, 1.0, 0.25, 10, accent, True, align=PP_ALIGN.RIGHT)
add_rect(slide, 10.45, 1.55, 2.0, 4.3, PALE_ORANGE, ORANGE, radius=True)
add_text(slide, "主要瓶颈", 10.7, 1.88, 1.5, 0.3, 16, ORANGE, True, TITLE_FONT, PP_ALIGN.CENTER)
add_text(slide, "NTT\n30.2%", 10.73, 2.5, 1.45, 0.65, 19, BLUE, True, align=PP_ALIGN.CENTER)
add_text(slide, "SampleNTT\n23.3%", 10.68, 3.55, 1.55, 0.65, 16, ORANGE, True, align=PP_ALIGN.CENTER)
add_text(slide, "两者合计\n53.5%", 10.72, 4.65, 1.48, 0.65, 15, INK, True, align=PP_ALIGN.CENTER)
add_rect(slide, 0.92, 6.25, 11.25, 0.58, PALE_ORANGE, ORANGE)
add_text(slide, "优化优先级:增加采样路径Keccak并行度,其次扩展NTT蝶形单元数量。", 1.12, 6.42, 10.85, 0.24, 12.5, INK, True, align=PP_ALIGN.CENTER)
# 22. Backup: tradeoffs
slide = new_content_slide("备用:设计取舍与优化路线", 22, "答疑备用")
add_table(
slide,
[
["维度", "当前选择", "优势", "代价"],
["安全等级", "运行时k=2/3/4", "单比特流部署", "地址与控制逻辑增加"],
["Keccak", "单核时分复用", "节省约10.6k LUT", "状态间无法重叠"],
["NTT", "双蝶形、4 coeff/cycle", "BRAM无冲突、0 DSP", "吞吐低于多BU实现"],
["控制", "30状态硬连线FSM", "行为清晰、易验证", "串行调度限制性能"],
],
0.72,
1.48,
11.85,
3.62,
widths=[1.4, 3.05, 3.35, 4.05],
size=11.3,
)
add_text(slide, "优化路线", 0.92, 5.42, 1.45, 0.3, 17, ORANGE, True, TITLE_FONT)
roadmap = [
("1", "双Keccak或专用采样核", "预计KeyGen下降15%–20%", ORANGE),
("2", "扩展多BU NTT", "提高至8 coeff/cycle", TEAL),
("3", "完善约束与活动率回标", "提高时序与功耗结论置信度", BLUE),
]
for i, (num, title, desc, accent) in enumerate(roadmap):
x = 0.95 + i * 4.05
add_number_badge(slide, num, x, 5.88, accent)
add_text(slide, title, x + 0.62, 5.86, 2.95, 0.28, 12, accent, True)
add_text(slide, desc, x + 0.62, 6.23, 2.95, 0.4, 10.5, INK)
prs.save(OUTPUT)
print(f"Wrote {OUTPUT} from {TEMPLATE}")