模块详细设计文档DDD模板:RTL 模块设计说明书的可复制骨架
写 RTL 的人很多,写设计文档的人很少。但团队里真正让模块「可交接、可评审、可回归」的,恰恰是那份详细设计文档(DDD,Detailed Design Document)。本篇给出一模块详细设计文档DDD模板的完整可复制文本,八个槽位逐一说明填写要点,并以一个 AXI-Stream FIFO 为例给出摘录示范。配合 RTL 设计流程总览 与 FIFO 与仲裁器基础 一起读,效果最好。
本文要回答的问题
- 一份合格的 RTL 模块详细设计文档应该包含哪些槽位?
- 每个槽位写什么、写到多细才算够?
- 有没有一份可以直接复制进项目仓库的 Markdown 模板?
- 以真实模块(AXI-Stream FIFO)为例,各槽位填出来长什么样?
一、这份模板是什么、什么时候用
DDD 是介于「架构规格书」和「代码」之间的那一层文档:架构书说系统有哪些模块、彼此怎么连;DDD 说这一个模块内部怎么实现。它的典型使用场景有四个:
- 设计评审:编码前把接口、状态机、时序约定摆上桌,让验证和系统组先挑错,改文档比改 RTL 便宜得多。
- 交接与维护:半年后别人(或你自己)改这个模块,先看 DDD 再看代码。
- 验证对齐:验证组按 DDD 的接口表和异常处理章节写 testbench 和覆盖率点。
- 里程碑存档:tapeout 前的 sign-off 材料之一。
上面这张卡说明一件事:DDD 是「编码前最后一次便宜的改错机会」。跳过它直接写代码,接口分歧会在联调阶段才暴露,代价翻十倍。
二、完整可复制的模板文本
把下面这段整体复制到你的项目仓库(比如 docs/ddd/<module_name>.md),逐槽替换尖括号内容即可:
# <模块名> 详细设计文档(DDD)
- 版本:v0.1 日期:<YYYY-MM-DD> 作者:<姓名>
- 对应架构书章节:<链接或章节号> 状态:草稿 / 评审中 / 已基线
## 1. 功能描述
<一段话:这个模块做什么、不做什么、在系统里的上下游是谁>
## 2. 接口信号表
| 信号名 | 方向 | 位宽 | 时钟域 | 复位值 | 时序/握手约定 | 说明 |
|---|---|---|---|---|---|---|
| <clk> | input | 1 | - | - | - | 主时钟 |
## 3. 内部框图
<贴一张框图:数据通路 + 控制通路,标注子模块边界>
## 4. 状态机
<状态列表 + 状态转移条件;无状态机则写「纯流水/组合,无 FSM」>
## 5. 关键时序图
<对最易误解的 1-2 个场景画波形:如握手反压、满空切换>
## 6. 参数与配置
| 参数名 | 类型 | 默认值 | 取值范围 | 影响 |
|---|---|---|---|---|
## 7. 异常处理
| 场景 | 检测方式 | 模块行为 | 对外表现 |
|---|---|---|---|
| <如:上游在空时读> | <空标志> | <忽略/报错> | <信号/中断> |
## 8. 面积与性能预估
- 目标频率:<MHz> 关键路径:<哪一级到哪一级>
- 预估资源:<LUT/FF/BRAM 量级> 依据:<同类模块综合数据>三、八个槽位的填写要点
功能描述控制在三到五句话,必须写「不做什么」——边界外推是评审翻车的重灾区。一个好的写法是「本模块负责 A,把 B 转成 C;不负责 D(由上游模块保证)」。接口信号表是全文最重要的一张表,方向/位宽/时钟域/复位值/握手约定五列缺一不可;写握手约定时直接引用协议条款(如 AXI-Stream 的「tvalid 不依赖 tready」),不要写「详见代码」。时钟域一栏在多时钟模块里尤其关键,跨域信号要单独标出并注明同步方式。内部框图只画数据通路和控制通路两条线,子模块超过六个就该拆图;框图的价值是让人十秒钟看懂数据从哪进、经过哪几级、从哪出,不是复刻网表。状态机槽位即使模块无 FSM 也要显式写一句「无 FSM」,否则评审不知道你是忘了还是真没有;有 FSM 的模块要列出每个状态的停留条件和转移条件,而不是只贴一张圈和箭头的图。关键时序图只画最易误解的场景,一两个就够,常见候选是握手反压、满空切换、复位释放后的第一拍;波形画法可以参考 Verilog 同步 FIFO 实验 里的读法。参数与配置要标每个参数改了会连锁影响什么(位宽、深度、时序余量),参数之间有约束的(如深度必须是 2 的幂)也要写明。异常处理用表而不是散文,每个场景给出「检测—行为—对外表现」三段式,验证组会直接拿这张表写断言。面积与性能预估写量级和依据即可,它不是承诺,是让后端和系统组提前有数;写不出依据就写「待首次综合后回填」,空着比瞎填好。
四、填写示范:AXI-Stream FIFO 摘录
下面以深度 16、数据宽 32 位的 AXI-Stream FIFO 为例,给出三个关键槽位的填法(AXI 变体的场景差异见 AXI4-Full 与 AXI4-Stream 适用场景):
接口信号表(摘录):
| 信号名 | 方向 | 位宽 | 时钟域 | 复位值 | 时序/握手约定 | 说明 |
|---|---|---|---|---|---|---|
| aclk | input | 1 | - | - | - | 主时钟 |
| aresetn | input | 1 | - | - | 低有效,同步释放 | 复位 |
| s_axis_tdata | input | 32 | aclk | - | tvalid 拉高期间保持稳定 | 上游数据 |
| s_axis_tvalid | input | 1 | aclk | - | 不依赖 tready | 上游有效 |
| s_axis_tready | output | 1 | aclk | 0 | 满时拉低 | 反压上游 |
| m_axis_tdata | output | 32 | aclk | 0 | tvalid 拉高期间稳定 | 下游数据 |
| m_axis_tvalid | output | 1 | aclk | 0 | 空时拉低 | 下游有效 |
| m_axis_tready | input | 1 | aclk | - | - | 下游反压 |
状态机槽位:本模块无 FSM,控制为纯组合逻辑——写指针在 s_axis_tvalid && s_axis_tready 时自增,读指针在 m_axis_tvalid && m_axis_tready 时自增,满/空由读写指针比较产生(状态机类模块的写法见 有限状态机入门)。
异常处理(摘录):
| 场景 | 检测方式 | 模块行为 | 对外表现 |
|---|---|---|---|
| 空时下游读 | empty 标志 | tvalid 保持 0,不产生读指针翻转 | m_axis_tvalid=0 |
| 满时上游写 | full 标志 | tready 保持 0,写指针不翻转 | s_axis_tready=0 |
| 复位中 | aresetn=0 | 指针清零,tready 拉 0 | 输出全 0 |
五、修改示范:把 FIFO 模板改成带 FSM 的 SPI 控制器
模板不是只能填 FIFO。假设要写一个 SPI 主控制器的 DDD,对比 FIFO 范例,需要动四处:
- 接口表换协议列:AXI-Stream 那套握手约定换成 SPI 的模式约定——
CPOL/CPHA由参数决定,片选cs_n的时序列写「传输全程拉低,帧间至少拉高半个 sclk 周期」。 - 状态机槽位从「无 FSM」变成真正的状态机:至少 IDLE、SHIFT、DONE 三态,逐个写转移条件——IDLE 在
start有效时进 SHIFT;SHIFT 在移位计数到WIDTH-1时进 DONE;DONE 停留一拍回 IDLE。 - 关键时序图换成 SPI 波形:画 sclk 与 mosi/miso 的建立保持关系,标注采样沿(CPHA=0 时第一个沿采样)。
- 异常表换场景:如「传输中 start 再次拉高」→ 检测方式为状态非 IDLE 时 start 有效 → 行为为忽略 → 对外表现为 busy 信号保持 1。
改动逻辑是一样的:协议列、状态机、时序图、异常表四处跟着模块性质走,功能描述、参数表、面积预估三个槽位的写法不变。
六、使用注意事项
- 先评后写:模板填到 v0.1 就拉评审,别等「写完美了」再给人看;评审改的是文档,不是代码。
- 表优先于散文:接口、异常、参数能用表就用表,散文段落只留给功能描述。
- 与代码同步演化:RTL 改了接口,当天更新 DDD 并升小版本号;文档落后于代码等于没有文档。
- 一个模块一份:不要把三个模块塞进一份 DDD,交接时没人读得完。
- 预估不是承诺:面积与性能槽位写量级和依据,避免被当成签核数据追责。
- 接口表是契约:评审通过后接口表即冻结,后续改动要走变更记录(哪怕只在文档头部加一行「v0.2:tready 复位值从 1 改 0」),让验证组能追溯。
- 异常表宁多勿漏:评审时多问几个「如果……会怎样」,每个答案都落进表里;这张表越全,联调阶段的惊喜越少。
总结
模块详细设计文档DDD模板的核心是八个槽位:功能描述、接口信号表、内部框图、状态机、关键时序图、参数与配置、异常处理、面积与性能预估。本文给出了完整可复制的 Markdown 骨架、逐槽填写要点,以及 AXI-Stream FIFO 的接口表/状态机/异常处理三段摘录示范。使用要点只有一句话:编码前填到 v0.1 拉评审,之后与代码同步演化——DDD 是设计流程里最便宜的改错机会,别浪费它。