简介:《FPGA功能设计说明书》是一份面向FPGA研发团队的标准化Word文档模板,用于编制功能设计阶段的说明文档,帮助设计人员、项目经理和评审人员围绕算法、架构、接口等核心要素达成一致。压缩包内仅含一份 Word 文档(33KB),内容为可直接编辑的模板,预置了封面、修改历史、目录,以及“概述”“功能描述”“测试计划”“参考资料”四大章节;其中“概述”细分为目的、功能概述、参考源、术语表,“功能描述”进一步列出使用的算法、设计架构、与其他部分之间的接口等小节,并涵盖数字信号处理、高速数据传输、接口控制等典型功能,结构完整,适合作为团队开发文档的统一蓝本。目前已有860人学习下载,FPGA项目组可以在立项或设计评审前快速生成规范文档,减少因描述不一致造成的沟通和返工成本。借助模板中的术语表和参考源模块,团队还能统一专业术语口径,并轻松填写各模块的算法说明、RTL架构、接口协议及测试计划,为后续仿真验证和系统集成提供可追溯的设计依据。
1. FPGA功能设计说明书:把模糊需求翻译成可验证行为的那张纸
FPGA功能设计说明书,听起来像评审才用得上的文档,实际上是最省时间的调试工具。接手过一个用Verilog写的图像采集模拟项目,需求邮件里只有一句话:把Sensor数据存进DDR3。代码写了一版又一版,联调时才发现前端模块用高电平表示帧有效,后端模块按低电平判断,接口一接上就丢帧。后来先把所有功能行为写成FPGA功能设计说明书再动RTL,返工明显减少。这份文档的价值,是把模糊需求翻译成接口、状态机、时序、寄存器这些可逐条核对的行为描述。FPGA开发、驱动对接、技术负责人三类人,分别能从接口表、寄存器表、资源预算三个角度读到关键信息。
2. FPGA功能设计说明书卡在需求与RTL之间:三种文档、三类读者、六个必答问题
2.1 规格书、功能设计说明书、详细设计文档:三层文档的分工边界
好多团队的文档管理是断层的:上游有一份需求说明,下游直接就是代码,中间没有一张纸描述"FPGA内部打算怎么行为"。这会产生两种翻车现场:一种是RTL编码者各自按理解实现,集成时接口对不上,凭联调一遍遍试错;另一种是硬着头皮一上来就写详细设计文档,把流水级、寄存器级微架构全部写死,需求一改整个文档作废,从此团队不再信任文档,连该写的也不写了。
常见做法是维护三层递进的文档。需求规格说明书回答"系统要做什么",是黑盒视角,读者主要是系统工程师;功能设计说明书回答"FPGA内部怎么划分模块、信号怎么走、行为时序是什么",是灰盒视角,读者覆盖架构、RTL、验证和驱动工程师;详细设计文档回答"每个模块的RTL怎么实现",是白盒视角,读者基本只有该模块的编码和维护者。功能设计说明书的价值在于把前者的"做什么"翻译成后者的"怎么行为",它不碰微架构,但必须覆盖接口、状态、时序、时钟复位、资源预算这些能支撑整条开发链的维度。
| 文档 | 回答的问题 | 视角 | 典型读者 | 变更频率 |
|---|---|---|---|---|
| 需求规格说明书 | 系统要做什么 | 黑盒 | 系统工程师 | 低频 |
| 功能设计说明书 | 模块怎么划分、行为与时序是什么 | 灰盒 | 架构、RTL、验证、驱动 | 中频 |
| 详细设计文档 | 模块内部怎么实现 | 白盒 | 模块编码与维护者 | 高频 |
在真实项目里,我习惯按"一个功能模块一页"组织功能设计说明书:每个主要模块占一页,页内固定四个板块——功能概述、接口信号表、行为描述、异常处理。每页控制在横向一屏能读完的篇幅,评审时一页页过,没过完的章节不允许进入编码阶段。这个规则同时约束了模块划分粒度:某个模块的功能页超过一屏就该继续拆,某个模块连两个接口都没有就该考虑合并进相邻模块。
2.2 三类读者和各自的阅读路径:说明书不是只给编码者看的
功能设计说明书经常被误写成"给RTL编码者的手册",通篇讲状态机,寄存器表却只有两行。真正拿到这份文档的人至少有三类,阅读路径完全不同:RTL编码者从接口信号表和状态机章节开始,关心位宽、时钟域、转移条件;验证工程师从功能编号清单和异常处理表开始,关心哪些场景需要造用例;驱动工程师直接翻寄存器表和中断行为,关心偏移地址、访问属性、清零方式;技术负责人则直奔时钟复位与资源预算章节,关心能不能在当前器件上收敛时序、资源够不够放。
| 读者 | 最先打开的两个章节 | 说明书写不好时的后果 |
|---|---|---|
| RTL编码者 | 接口信号表、状态机与时序 | 模块边界对不上,靠联调赌 |
| 验证工程师 | 功能清单、异常处理表 | 测试覆盖不了真实场景 |
| 驱动工程师 | 寄存器表、中断与复位流程 | 驱动按猜测编程,跑不通 |
| 技术负责人 | 时钟结构、资源与时序预算 | 选型错误,后期换器件 |
因此,写每一章之前先想清楚"这一章给谁用",再决定信息粒度。接口表写给RTL和验证,就一定要带时钟域和有效电平;寄存器表写给驱动,就一定要带位域和复位值;异常表写给验证,就一定要写到可判定的触发条件。为读者定粒度,是说明书不变成废纸的前提。
2.3 六个必答问题与文档章节映射:写正文前的自检清单
写正文前,我习惯先对着六个问题自检一次,每个问题对应说明书里的固定章节。六个问题都落到文档且能互相引用,骨架就算立住了。
第一,功能清单:核心路径和旁路功能分别是什么,对应功能编号清单章节。第二,接口定义:对内对外有哪些信号,位宽、方向、有效电平、时钟域分别是什么,对应接口信号表章节。第三,行为时序:关键路径的输入输出延迟、握手时序、帧或字对齐方式如何,对应状态机与关键时序章节。第四,状态与异常:有哪些稳定状态,异常进入哪个状态、如何恢复,对应异常处理表章节。第五,时钟复位:时钟域如何组织、复位如何同步、哪些信号跨时钟域,对应时钟与复位章节。第六,资源与约束:资源占用预估、时序目标、引脚约束需求,对应资源与时序预算章节。
这六个问题和章节的映射关系,可以直接做成文档开头的导航表,评审时逐项打勾,能显著减少"章节写得漂亮但内容没对齐需求"的情况。接下来两章就按这个骨架,讲接口表、寄存器表、状态机、时钟复位和异常处理的具体落笔方式。
3. 搭出功能设计说明书的骨架:模块划分、接口信号表与寄存器表怎么落笔
3.1 顶层模块划分的两种思路:先划数据流,再单独拎出控制
顶层模块划分最忌一上来就按功能堆模块,堆到二十个,文档维护成本直接失控。我惯用的顺序是先画数据流主干:从输入接口到处理再到存储与输出接口,把链路划成若干段;然后把这些链路中与控制、配置、状态上报相关的部分单独拎出来,形成一个控制平面;最后逐段检查时钟域归属,凡是跨域的信号单独标注,为后文的跨时钟域清单留好位置。
以一套典型的图像采集模拟项目为例,顶层会分成五个模块:Sensor输入接口、图像预处理、存储器控制器封装、配置管理(寄存器堆)、上位机接口。模块清单在文档里直接给一张表,列五个字段:模块名、职责、时钟域、主要对外接口、依赖的其它模块。
| 模块名 | 职责 | 时钟域 | 主要对外接口 | 依赖模块 |
|---|---|---|---|---|
| sensor_if | Sensor时序解析与数据对齐 | clk_pix | pix_data、pix_valid | 无 |
| img_pre | 预处理与帧格式转换 | clk_sys | frame_valid、frame_data | sensor_if |
| mem_wrap | DDR读写封装与FIFO管理 | clk_mem | wr_cmd、rd_data | img_pre |
| cfg_reg | 寄存器堆与配置桥接 | clk_sys | 配置总线 | 无 |
| host_if | 与上位机命令交互 | clk_sys | uart收发 | cfg_reg |
模块划分粒度有一条经验标准:一个模块的功能页超过横向一屏就继续拆,一个模块只有输入没有输出就检查是不是漏了接口,一个模块同时被三个以上模块依赖时,要考虑是不是该拆出公共子模块。粒度合适时,说明书的章节结构会和代码的目录结构大体对齐,评审才好逐模块对照。
3.2 接口信号表:七个字段一列都不能少,少一个就埋一个雷
接口信号表是功能设计说明书里被引用最多的部分:RTL拿它查位宽和时钟域,验证拿它写激励,驱动拿它确认电平极性。我一般固定用七个字段:信号名、方向、位宽、所属时钟域、有效电平或时序关系、功能说明、来源去向模块。多一个字段嫌累赘,少一个字段大概率出问题。局部示例:
| 信号名 | 方向 | 位宽 | 时钟域 | 有效电平/时序 | 功能说明 | 来源/去向 |
|---|---|---|---|---|---|---|
| pix_clk | 输入 | 1 | — | 上升沿采样 | Sensor像素时钟 | 外部引脚 |
| pix_data | 输入 | 16 | pix_clk | 与pix_valid同时有效 | Bayer原始数据 | sensor_if→img_pre |
| pix_valid | 输入 | 1 | pix_clk | 高有效 | 像素有效标志 | sensor_if→img_pre |
| frame_sync | 输入 | 1 | pix_clk | 高有效一个周期 | 帧起始标志 | sensor_if→img_pre |
| ddr_wr_ready | 输入 | 1 | clk_mem | 高有效 | 写通道可接收新请求 | mem_wrap→img_pre |
写这张表要注意两点。一是"所属时钟域"必须写,跨时钟域信号要在该行后标注同步方式,比如"两级触发器同步"或"经异步FIFO",否则后文时钟与复位章节会无从引用。二是"有效电平/时序"禁止写"正常时拉高"这种描述,要写成"高有效一个周期""下降沿后数据有效"这类可判定表述。
提示:接口表定稿后再动RTL顶层,顺序反了就会两头改,改完文档改代码,改完代码又觉得文档不对。
接口表定稿后,我习惯用脚本把它转录成Verilog头文件或SystemVerilog包的信号定义,让RTL从同一份定义里引用,而不是各写各的。手动转录也可以,但转录完要逐行评审一次,重点查位宽和方向,这两项最容易在转录时被看走眼。
3.3 寄存器地址表:驱动工程师最依赖的一页,写不好就返工
寄存器表是说明书里最容易写坏的部分。常见的坏法有两种:一种只有地址和名字,没有位域、复位值、访问属性;另一种把寄存器表写成了内存映射表,地址排得整齐,但每个位的含义只有作者自己知道。驱动工程师拿到这两种表,要么不敢写代码,要么写出来是错的。
我建议每行寄存器至少包含六项:偏移地址、寄存器名、位域定义、访问属性、复位值、功能说明。访问属性用行业通行的简写:RO只读、RW读写、W1C写1清零、RC读后清零、WO只写。位域定义写成[bit]加含义,保留位要明确标注"写无效,读返回0"。示例:
| 偏移地址 | 寄存器名 | 位域定义 | 属性 | 复位值 | 说明 |
|---|---|---|---|---|---|
| 0x00 | CTRL | [1] soft_rst [0] en | RW | 0x00 | 总控制寄存器 |
| 0x04 | STATUS | [1] fifo_ovf [0] busy | RO | 0x00 | 状态寄存器 |
| 0x08 | FRAME_CNT | [31:0] 帧计数 | RC | 0x00 | 读后自动清零 |
| 0x0C | BUF_ADDR | [31:0] 起始地址 | RW | 0x00 | 采集数据存放地址 |
属性字段最容易被忽略的是W1C和RC的副作用:W1C意味着软件写0不会清除标志,RC意味着读操作本身会改变寄存器状态,这两条不写清楚,驱动联调时一定会来回折腾。寄存器表后还要留一段专门描述中断行为:中断源在哪个状态置位、经过哪条路径上报、软件如何清除、清除是否有时序要求,驱动写中断处理看的就是这段。
最后提一个管理习惯:寄存器章节表头固定写"寄存器版本"和"变更日期",表尾放变更记录,每条写明版本、变更内容、影响范围。寄存器表改版走和代码一样的评审流程,改完必须通知驱动侧,不能只丢一个新文件过去,这是血泪经验换来的。
4. 把行为边界写死:状态机、时钟复位与异常处理在文档里的落地写法
4.1 状态机文档化:用状态-事件表代替大段叙述
功能设计说明书里的状态机描述,最怕两种写法。一种只画一张状态转移图,箭头旁边写满"收到数据后""等待完成"这类带解释空间的话,不同人读出来的条件粒度不一样;另一种写五百字叙述,状态之间谁触发谁要读者自己脑补。这两种都会让编码者写出不一致的逻辑,到验证阶段才暴露。
可靠的做法是状态-事件表加转移条件。每个转移一行,条件写成可判定的逻辑表达式,能对应到接口信号表里的某个信号。示例:
| 当前状态 | 转移条件 | 下一状态 | 进入下一状态时的动作 |
|---|---|---|---|
| IDLE | start=1且fifo_empty=1 | RUN | 拉高busy,使能数据通路 |
| RUN | frame_cnt == 预设值且fifo_ovf=0 | DONE | 拉低busy,置位frame_done |
| RUN | fifo_ovf=1 | ABORT | 拉低busy,置位fifo_ovf标志 |
| ABORT | 软件写clr=1 | IDLE | 清空FIFO,复位帧计数 |
这个表有两条硬约束。第一,转移条件里出现的每个信号都要能在接口信号表或数据通路内部信号清单里找到,找不到说明条件写虚了。第二,每个状态必须写明进入该状态时的动作,以及在该状态内部持续维持的输出逻辑是哪些。这两类动作要分开列,编码者才知道哪些输出进状态转移分支、哪些输出进默认分支,避免把持续动作误写成一次性动作。
状态机的编码方式,比如用one-hot还是二进制,属于详细设计文档的范畴,功能设计说明书不用写。说明书要管的是行为:什么条件下离开当前状态、离开时做什么、进去后维持什么,这三件事写清楚,编码方式留给实现者自己决定。
4.2 时钟与复位:三个必须在文档里定死的决策
时钟复位章节不需要长,但三个决策必须定死,写不清楚的后果往往是综合后时序不收敛,或者跨时钟域仿真随机失败。第一个是时钟来源与频率,列一张时钟域清单,包含频率、来源(外部引脚、PLL锁相环、接口恢复时钟)、服务的主要模块。第二个是复位策略,建议直接写明"异步复位、同步释放",并给出复位同步器的位置:每个时钟域入口放一个同步器,复位释放沿与目标时钟上升沿对齐。第三个是跨时钟域信号清单,逐信号写清楚同步手段:单bit控制信号用两级触发器,多bit数据用异步FIFO,慢变多bit信号用握手,计数器跨域用格雷码。
| 信号名 | 源时钟域 | 目标时钟域 | 同步方式 | 风险等级 |
|---|---|---|---|---|
| frame_sync | clk_pix | clk_sys | 两级触发器 | 低 |
| buf_addr | clk_sys | clk_mem | 异步FIFO | 中 |
| fifo_depth | clk_mem | clk_sys | 格雷码计数 | 高 |
注意:复位域清单必须与时钟域清单一一对应,每个时钟域都要有明确的复位信号来源。评审时拿这两张表逐模块核对,专门抓"模块例化了全局复位却绕过同步器"的隐患。
写这张表时要顺带把需求侧的假设写进去:哪个时钟是输入参考、哪个PLL输出允许失锁、参考时钟丢失时系统行为如何。需求假设不写,验证阶段会出现"时钟抖动超规格导致功能异常"却没人能回答"这算不算缺陷"的争论。
4.3 异常与边界场景:把"不会发生"写进文档也是内容
只写正常路径的说明书,评审时很容易被一句话问倒:"如果FIFO满了怎么办?"与其现场拍脑袋,不如在文档里固定一张异常处理表,把能预见的非正常场景全部列出来。表格三列:异常场景、系统行为、恢复方式。
| 异常场景 | 系统行为 | 恢复方式 |
|---|---|---|
| Sensor输出分辨率切换 | 检测到参数跳变,暂停采集并置位cfg_err | 驱动重新初始化Sensor |
| DDR写FIFO达到高水位 | 拉低写使能,缓存剩余数据 | 水位回落后自动恢复 |
| 配置接口访问保留地址 | 向总线返回错误响应,不产生内部副作用 | 无需人工干预 |
| 复位释放时输入信号未稳定 | 复位同步器保证先进入确定状态 | 无需人工干预 |
写异常表有一个原则:不确定的行为不要留白。先写成"待确认",在评审时专项讨论并补全,比留白安全得多。留白的危害在于两个人会各自猜一种行为,等集成时才发现猜得不一样,返工成本远高于评审时多花半小时。异常表写完后,验证工程师可以直接从表中抽取边界用例,这比从叙述文字里找场景可靠得多,也能避免"说明书写了但测试没覆盖"的脱节。
5. FPGA功能设计说明书常见问题排查:五个坑的现象、原因与对策
5.1 接口信号表与顶层例化各写各的,仿真通过联调挂
现象:文档里pix_data是16位,RTL顶层例化写成12位,各模块独立仿真都通过,一集成就报位宽不匹配,重新综合加联调整整浪费一天。
原因:信号表是文档作者手工维护的,顶层是编码者手工写的,两边没有强制对应关系;改位宽时往往只改了一处,另一处留着旧值不变。
解决:把接口信号表当作唯一事实来源。先定表再写代码,定稿后用脚本生成头文件或包定义,RTL直接包含它。如果项目里代码已经存在,反过来从顶层文件生成信号清单,与文档逐项比对,差异项单独列进评审。这个习惯能消除一多半接口类返工。
5.2 状态机只画图不写条件,两个编码者实现出两种行为
现象:两个开发者看同一张状态转移图,一个把超时判在RUN状态内部,另一个把超时做成独立分支,测试结果对不上,验证组花两天定位才找到差异来源。
原因:图形化表达对转移条件没有约束,箭头上的简练描述在不同人眼里语义不同。"超时"到底指计数超过多少、使能条件是什么,图上不写,理解就分叉。
解决:强制用状态-事件表,条件必须写到信号级。把"超时"写成"timeout_cnt >= 1024且timeout_en=1",把"数据准备好"写成"data_valid=1且fifo_empty=0"。编码开始前,拿这张表做一次内部评审,逐条确认条件里的信号都能在信号表里找到。
5.3 复位策略只写电平不写释放时序,跨时钟域逻辑莫名跑飞
现象:文档写了"异步复位、低有效",编码者随手在模块内直接用全局复位,复位释放时跨时钟域寄存器状态不确定,仿真偶尔出现跑飞,重新跑一遍又通过,表现为典型时序玄学。
原因:复位策略只回答了"电平"没回答"时序",没说复位同步器放哪里,也没说释放沿怎么对齐,编码者只能按自己的习惯处理。
解决:文档写清楚三件事:每个时钟域一个复位同步器,释放沿对齐目标时钟上升沿,提供一张与时钟域清单一一对应的复位域清单。评审时逐模块核对每个复位信号是否经过同步器,这类跑飞大多能提前暴露。
5.4 寄存器表改版不记版本,驱动按旧地址读写半天没反应
现象:新版本把CTRL的使能位从bit0挪到bit1,文档表格更新了但版本号没动,驱动照旧写bit0,配置半天不生效,现象表现为"写进去了好像没写"。
原因:寄存器表是功能设计说明书里变更最频繁的部分,没有版本记录和变更历史,下游读者无法判断自己手里的表是不是最新的。
解决:寄存器表表头固定写"寄存器版本"和"适用日期",表尾加变更记录,每条写明版本、变更内容、影响范围。改版走和代码一样的评审流程,改完主动通知驱动侧,更新寄存器头文件并确认驱动侧同步,别只丢一个新文件过去。
5.5 评审只看目录不看行为描述,关键时序问题没人算过
现象:评审会开一小时,大家翻了一遍文档,提的全是格式和错别字意见;真正关键的输入延迟、握手时序没人动笔核对,到项目后期时序不收敛才回头改需求。
原因:评审没有清单,默认文档是走形式,注意力放在排版而不是行为正确性上,行为章节反而没人深究。
解决:给评审配一张按章节的检查清单。状态机章节问:每个转移条件是否对应信号表?是否有不可达状态?进入动作与持续动作是否区分?时钟复位章节问:每个时钟域是否都有复位域?跨时钟域信号是否逐条列出并标注同步方式?异常处理章节问:FIFO满、访问越界、参考时钟丢失有没有行为描述?清单逐项过完,评审结论才允许签同意。
6. 让功能设计说明书真正被用起来:三条进阶使用习惯与一次模拟验收
文档写完不叫完成,被持续使用才叫完成。三条进阶做法是性价比最高的投入,第一条是给功能编号。每个功能需求从FR-01开始编号,验收行为写在编号旁边,验证用例用TC编号回指功能编号,形成需求到用例的追踪链。需求变更时,顺着编号能找到所有受影响的模块页和验证用例,改动影响一目了然。
第二条是文档与代码同库管理。功能设计说明书的源文件放进和RTL同一个版本库,提交代码涉及接口、寄存器或状态机变更时,提交说明里必须带上文档修订记录。这一条能挡住"代码改了文档没改"的脱节问题。
第三条是编码前做一次模拟验收。请一个没参与设计的新同事,只依据说明书实现一个最小的旁路模块,比如配置寄存器的读写通路。他照着文档做出来的模块能通过自测,说明信息粒度够;如果他频繁追问"这里到底什么意思",说明还有含糊点,立即补掉。我那个图像采集模拟项目动RTL前做了这次验收,结果发现状态机表少了两个异常恢复条件,当场补掉,省了之后整整一周的联调返工。
这条教训我踩过不止一次:写文档时总觉得"这里大家都懂",但"大家都懂"恰恰是最靠不住的假设。功能设计说明书里含糊的地方,最后都会变成联调现场的口头沟通,然后再变成下一轮返工。把行为写到信号级,把待确认提前摊开,文档才真正值钱。希望帮到你。
本文还有配套的精品资源,点击获取