多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

可复位D触发器设计方法:异步清零的RTL实现与时序约束

可复位D触发器设计方法:异步清零的RTL实现与时序约束 1. 项目背景与设计目标为什么可复位才是常态需求刚接触数字电路的同学往往有个误解以为D触发器就是把D端数据在时钟沿打一拍这么简单。确实教科书上的教学触发器大多只画了D、CLK、Q三个引脚但真实工程里你几乎找不到裸奔的D触发器。无论是寄存器堆、状态机、计数器还是总线缓冲几乎每个触发器都配备了复位端区别只是复位信号接到哪个逻辑层、怎么接。这个项目的标题是可复位D触发器设计方法从零实现带异步清零功能拆开来看其实就是两件事一是把D触发器的基本行为从零搭出来二是在这个基础上引入异步清零能力。很多人说异步清零不就是加一个AND门吗真这么干你会被时序分析工具追着打。这里的水比想象中深包括复位释放时的恢复时间、复位树的扇出平衡、以及异步复位在DFT可测试性设计里怎么处理等等。我这次要做的是带异步复位端的D触发器所谓异步指的是复位信号不依赖时钟沿只要复位有效信号拉起来输出立即被清零不用等下一个时钟沿。这种设计在系统级复位、上电初始化、紧急关断这类场景里几乎是刚需——你总不能等一个周期才让系统停下来。除了异步清零之外这个项目还希望做成一个可复用的模块也就是端口参数化、复位极性可配、具备基本的仿真测试平台。后续无论是做RISC-V内核的寄存器堆、UART的波特率发生器、还是简单的按键消抖状态机都可以直接调这个模块不用重写。我会用Verilog HDL实现整个设计仿真工具用开源的Icarus Verilog加GTKWave综合约束那边给出SDC的核心写法最后把异步复位的时序检查要点讲清楚。整个项目从原理到代码到验证到约束一条链路走完。2. 触发器内部结构拆解从锁存器到带复位端的边沿触发DFF2.1 先说清楚D触发器到底在干嘛D触发器Data Flip-Flop的本质是一个边沿敏感的存储单元。它只在时钟的上升沿或下降沿采样D端输入其余时间输出保持不变。这个边沿敏感特性是所有同步时序电路能够稳定工作的基石——数据在时钟沿统一拍板避免组合逻辑的毛刺直接穿透到寄存器输出。很多人分不清锁存器Latch和触发器Flip-Flop的区别。锁存器是电平敏感使能信号有效期间输出跟随输入变化触发器是边沿敏感只在时钟跳变的那一瞬间采样。两者在ASIC和FPGA里的综合策略完全不同锁存器在FPGA里通常不推荐使用因为它会破坏时序收敛而且消耗的LUT资源也比寄存器多。从实现细节看一个标准的CMOS D触发器内部通常包含主从两级锁存结构主锁存器在时钟低电平期间采样输入从锁存器在时钟高电平期间导通并锁存数据这样的级联结构保证了时钟沿到来时输出只会跳变一次不会产生输入直通现象。这个主从级联的思想正是后面理解复位信号如何插入内部节点的关键。2.2 复位功能为什么要分同步和异步复位端的实现方式决定了整个系统的复位策略。同步复位Synchronous Reset是指复位信号只在时钟有效沿到来时才被采样也就是说复位不发生即时作用要等下一个时钟沿。异步复位Asynchronous Reset则不受时钟控制复位信号一有效输出立刻被拉低或者拉高取决于极性。两者各有利弊但有一点必须明确异步复位不是随手加一个复位输入那么简单它引入了一套完整独立的时序约束体系。异步复位信号在FPGA内部通常走全局复位网络它和时钟之间需要满足恢复时间Recovery Time和移除时间Removal Time要求类比一下就是复位信号也不能在时钟沿附近乱跳它的安静窗口由恢复时间决定。2.3 从标准D触发器到可复位版本的门级改造从门级电路来看给D触发器加异步清零最直接的做法是把复位信号引入到主从锁存器的置位/复位端上。以经典的传输门主从D触发器为例主锁存器内部的两个反相器交叉耦合节点分别引出到NAND门的输入端复位信号用低有效极性接入就能实现异步清零。用Verilog描述这种结构时代码极简很多人几个周期就写完了module dff_async_rst ( input wire clk, input wire rst_n, // 低有效异步复位 input wire d, output reg q ); always (posedge clk or negedge rst_n) begin if (!rst_n) q 1b0; else q d; end endmodule这段代码背后的逻辑远不止语法层面。敏感列表里的or negedge rst_n体会了异步二字。如果只写posedge clk综合器会把它实现成同步复位寄存器如果把negedge rst_n去掉但if (!rst_n)还在那就会综合出莫名其妙的逻辑甚至报出信号不在敏感列表中的警告。2.4 复位极性怎么选低有效为什么是主流工程领域99%的复位信号都设计成低有效也就是rst_n。这个惯例有几个现实原因早期电路的电源轨和逻辑极性里低有效复位便于实现上电自动复位RC充电到阈值后复位自动释放此外低有效信号在故障时候更容易做成默认安全状态线断了、驱动掉了输入悬空在多数逻辑家族里会表现为高电平低有效复位就会一直处于复位状态系统停在已知安全的初始态。FPGA原语里的全局复位端口、ASIC标准单元库里的复位端绝大多数也默认低有效。这是行业惯例不是某家厂商拍脑袋定的。做新模块时建议遵循这个惯例除非有明确理由例如IP核要求高有效复位。3. 从零实现完整RTL设计同步逻辑与复位逻辑的代码分工3.1 代码整体框架设计接下来是实际动手环节。我这次设计的模块除了最基本的异步清零D触发器之外再加一个输出使能可控的功能这样模块在寄存器堆、总线外设中作为通用存储单元时更有实战价值。完整代码结构如下module dff_async_rst #( parameter DATA_WIDTH 8 )( input wire clk, input wire rst_n, input wire load_en, // 写使能低电平保持 input wire [DATA_WIDTH-1:0] d, output reg [DATA_WIDTH-1:0] q ); // 异步复位低有效 always (posedge clk or negedge rst_n) begin if (!rst_n) begin q {DATA_WIDTH{1b0}}; end else if (load_en) begin q d; end // 不需要else分支load_en为0时保持q不变 end endmodule这个模块的参数部分用#()定义了位宽默认8位。之所以把位宽参数化是为了在实例化时可以直接配成1位、32位、64位不需要改动内部逻辑。load_en信号是后加的写使能在不做使能处理时D触发器每个时钟沿都会采样这可能会导致数据被意外覆盖。使能信号的本质是ATE时钟门控工程上常见做法是把使能判断放在else if里而不是在时钟路径上做门控——后者会引入毛刺风险是时序收敛的大忌。3.2 异步复位分支的编码风格与陷阱这里的if (!rst_n)分支和else if (load_en)分支综合工具会映射成复位端和时钟使能端。注意顺序复位优先级最高它的if必须放在最前面。如果调换顺序把load_en放在if的第一分支综合器会生成带门控的复位逻辑行为也完全变了——不再是异步复位而是使能优先。有些同学喜欢在always块里同时使用和这是不允许的。时序逻辑必须统一使用非阻塞赋值。刚开始学数字电路的人容易犯阻塞赋值导致仿真结果与综合前仿真不一致的错原因就是在always块结束时才统一更新模拟了寄存器的边沿锁存语义而立即赋值则会把电路推断成组合逻辑链综合后行为可能完全对不上。3.3 多比特寄存器组的复位策略当位宽大于1时每个bit的复位行为是否完全一致这直接关系到芯片上电时的初始化可靠性。比如8位寄存器复位时全部清零这在绝大多数场景里是最安全的初值。但也有例外某些状态编码里需要用1作为非法态或初始态。这时候就不能笼统地用{DATA_WIDTH{1b0}}而要配合参数化初值来写parameter RESET_VAL 0; // 复位初值可配 q RESET_VAL[DATA_WIDTH-1:0]这个设计看着简单实际使用中大有讲究。FPGA的综合工具本来就可以推断寄存器上电初值通过INIT属性但ASIC流程里上电初值并不可靠必须依赖复位信号把寄存器打到已知状态。所以这个参数化的RESET_VAL一方面服务于仿真初值一方面用于复位后的稳定态设计。3.4 竞争冒险检查异步复位释放时的亚稳态风险异步复位最大的坑不在复位拉低的过程而在复位释放的瞬间。如果复位信号在时钟有效沿附近刚好从低变高释放此时触发器的内部节点正处于建立/保持时间窗口的边界输出可能进入亚稳态——输出既不是0也不是1持续一段时间才稳定下来。解决办法业内已经非常成熟异步复位、同步释放。也就是复位信号先进一级同步器把异步释放变成时钟沿对齐的同步释放从根上避开释放竞争。这个技术几乎每个数字工程师都会遇到后面验证章节我会给出对应的代码和说明。在RTL设计阶段就把它考虑进去能省掉后续大量的时序分析烦恼。4. 同步清零与异步清零的取舍结构、代码与时序对比4.1 两种写法的直接对比先放两种最常见写法的代码方便对照。同步清零版本module dff_sync_rst ( input wire clk, input wire rst_n, input wire d, output reg q ); always (posedge clk) begin if (!rst_n) q 1b0; else q d; end endmodule异步清零版本就是前面那段带or negedge rst_n的代码。两者从RTL语法上看只是敏感列表的差别但综合后的电路结构和时序约束差异巨大。4.2 电路结构层面的差异同步复位的清零逻辑被综合在D输入端的组合逻辑路径上等价于MUX的0号输入接了常数0复位信号参与数据路径的组合逻辑。这带来的影响是数据路径上的门级延迟会变大尤其在使用复位使能load_en的场景里MUX级数还可能进一步增加拉高建立时间需求。异步复位则直接控制寄存器的CLR引脚不占用数据路径的资源。从时序收敛角度看异步复位可以让数据路径保持干净不受复位逻辑干扰。这也是很多高速设计宁可付出亚稳态同步代价也要用异步复位的原因。然而异步复位并非没有代价复位信号变成了一个类似时钟的全局信号需要做时钟树综合级的平衡。如果复位端在芯片各个角落到达时间不一致可能出现部分寄存器已经释放、另一部分还锁在复位态的情况这正是复位释放不同步问题的来源。4.3 什么时候选同步、什么时候选异步工程选型不是凭喜好。我给几个自己常用的判断标准高速数据中心总线、高性能CPU流水线寄存器优先用同步复位因为频率高异步复位树的skew控制代价太大。MCU和SoC的外设寄存器、电源管理模块、看门狗复位逻辑优先用异步复位因为上电时序不确定需要立即复位。FPGA设计Xilinx/Intel功能上两者都能实现但FPGA的寄存器原语自带高有效/低有效异步复位端用异步复位不额外消耗逻辑资源反而更自然。涉及跨时钟域的复位比如来自慢时钟域管理模块的复位必须经过异步复位同步释放电路才能安全使用。我需要特别强调一点同步复位也可能产生毛刺。如果复位信号从组合逻辑产生且毛刺刚好落在时钟沿附近它会被采样到造成误复位。而异步复位只要毛刺幅度够大也会直接触发复位。所以复位信号本身建议尽可能是寄存器直接输出的干净信号这在后续的系统级设计中极其重要。4.4 复位树上的经验记录在实际工程中我曾经遇到过一次复位释放不同步导致的系统偶发启动异常。板卡上电后概率性地出现外设初始化失败但内核正常运行的现象。排查过程并不愉快逻辑分析仪抓到的是内核早已退出复位开始执行指令某个外设寄存器还在复位状态导致外设初始化序列失败。根因很简单该外设的复位信号由CPLD输出的异步复位信号分发给多个芯片但到达路径长短不一PCBA上做了绕线补偿后仍然存在几百ps级偏差。在几百兆赫兹的时钟下这个偏差已经是数个时钟周期级别的窗口于是复位释放时序不再对齐。这就是异步复位释放窗口问题的经典表现。从那之后我在所有异步复位设计里强制加入异步复位同步释放宁可多两个触发器也不赌复位时序。下面这段代码是我在多个项目里复用的复位同步器模块module rst_sync ( input wire clk, input wire arst_n, // 原始异步复位低有效 output wire rst_n // 同步释放后的复位 ); reg rst_n_r1; reg rst_n_r2; always (posedge clk or negedge arst_n) begin if (!arst_n) begin rst_n_r1 1b0; rst_n_r2 1b0; end else begin rst_n_r1 1b1; rst_n_r2 rst_n_r1; end end assign rst_n rst_n_r2; endmodule它的工作原理异步复位拉低时两个寄存器都被立即复位输出为0整个系统处于复位态。当异步复位释放为高时第一个寄存器在首个时钟沿置1第二个寄存器在下一个时钟沿跟随置1所以系统真正脱离复位是在第二个时钟沿之后恰好完成了异步置位、同步释放。两级寄存器还能打掉亚稳态释放竞争的风险基本消除。5. 仿真验证与测试平台设计覆盖正常、复位置位、毛刺抵抗三类场景5.1 测试平台的整体结构写RTL不仿真等于没做。这个项目的测试平台用Icarus Verilog跑波形查看用GTKWave全部开源可复现。测试平台的核心思路是覆盖三类场景正常数据写入与保持、复位清零行为、复位释放竞争下的稳定性。module tb_dff_async_rst; reg clk; reg rst_n; reg load_en; reg [7:0] d; wire [7:0] q; dff_async_rst #(.DATA_WIDTH(8)) u_dut ( .clk (clk), .rst_n (rst_n), .load_en(load_en), .d (d), .q (q) ); // 时钟生成周期10ns占空比50% initial clk 1b0; always #5 clk ~clk; // 测试主流程 initial begin $dumpfile(tb_dff_async_rst.vcd); $dumpvars(0, tb_dff_async_rst); rst_n 1b1; load_en 1b0; d 8h00; // 复位阶段拉低复位验证异步清零 #5; rst_n 1b0; #7; // 不复位必须在时钟沿验证异步特性 if (q ! 8h00) $display([FAIL] 异步复位未立即生效q%02h, q); else $display([PASS] 异步复位立即生效); // 释放复位进入工作状态 #5; rst_n 1b1; // 数据写入使能拉高期待下一时钟沿采样 #4; load_en 1b1; d 8hA5; #20; if (q 8hA5) $display([PASS] 数据写入正确); else $display([FAIL] 数据写入错误q%02h, q); // 保持测试使能拉低即使D变化输出也不变 load_en 1b0; d 8h5A; #15; if (q 8hA5) $display([PASS] 使能低电平保持数据); else $display([FAIL] 使能未生效q%02h, q); // 复位干扰测试时钟上升沿附近复位释放 // 故意让rst_n在时钟附近变化观察输出是否亚稳态 #3; rst_n 1b0; #4; rst_n 1b1; #50; $display(TEST FINISHED); $finish; end // 实时监测当下一个有效沿到来时检查D和Q的关系 always (posedge clk) begin if (rst_n load_en) if (d ! q) $display([WARN] 时钟沿采样点 q%02h d%02h, q, d); end endmodule这段测试平台有几个细节需要解释。复位拉低后我故意没有等时钟沿就检查输出验证的就是异步复位的立即可见特性。若用同步复位这步检查会直接失败。数据写入的检查等待了20ns跨越了两个时钟周期确保数据稳定后再比对。5.2 GTKWave上的波形检查要点打开VCD文件后重点看这几处波形复位拉低后q是否立即变成0与时钟沿无关。复位释放后的第一个时钟沿q是否仍然保持0而不是立刻去采样D。这是同步释放电路才会有的行为。写入使能拉高后q在时钟上升沿跟随d变化此前保持不变。我习惯再把load_en的信号路径放大看毛刺。真实场景中使能信号往往来自地址译码器的组合逻辑输出毛刺概率不低。如果使能信号直接驱动时钟使能端口毛刺会在非有效时钟沿被忽略问题不大但如果是门控时钟毛刺可能直接造成误采样。这也是我坚持使用时钟使能而非门控时钟的根本原因。5.3 复位释放竞争的仿真模拟复位释放竞争是最难仿真的场景之一因为亚稳态本身不能精准建模仿真的0/1二值逻辑无法呈现真实硅片的连续电压过渡。但我们可以通过时序脚本扫描复位释放与时钟沿的相对时间观察是否有数据违反的窗口。更工程化的做法是直接在后仿阶段加入时序反标用标准延时格式SDF文件跑真实的setup/hold检查。RTL仿真阶段能做的是逻辑上验证复位同步释放电路是否生效。将复位释放放在时钟沿前后几百ps内反复跑回归如果输出始终稳定不出现X态至少说明代码逻辑上没有问题。至于真正的亚稳态概率已经超出RTL仿真的能力范围需要交给静态时序分析或者专门的可靠性分析工具。6. 时序约束与静态时序分析实战SDC编写与复位相关的检查项6.1 建时钟与复位约束的基本盘RTL跑通只是第一步。FPGA工程要给时序工具写约束ASIC流程要写SDC。很多初学者在这里翻车仿真全对上板就跑飞代码完全一样问题出在没约束。对带异步复位的触发器来说约束的重点有三块主时钟定义、复位相关约束、异步信号的set_false_path/set_clock_groups处理。以Xilinx Vivado为例主时钟约束如下create_clock -name sys_clk -period 10.000 -waveform {0.000 5.000} [get_ports clk]这句的意思是创建一个10ns周期的系统时钟上升沿在0ns下降沿在5ns。如果你用的是内部PLL或MMCM的输出则要从PLL输出引脚上建时钟而不是顶级输入端口。复位方面异步复位信号需要在时序工具里标记为异步以禁止工具对它做建立/保持时间的检查。在SDC里通常用set_false_path配合set_async_group处理。特别要说明的是加了sync释放级的复位信号最终进入寄存器CLR端的那一级已经变成同步信号它和主时钟理论上需要检查最后一个同步寄存器的输出沿不过实际上多数工具会自动处理只要保证同步寄存器本身满足时序即可。set_false_path -from [get_ports rst_n]这一句会让时序工具完全忽略rst_n引脚到所有寄存器的时序路径。这是对的因为异步复位信号本来就不需要满足基于主时钟的建立/保持时间。但要注意这条命令会把rst_n到同步释放电路的那一段也ignore掉所以更精细的做法是先针对同步释放路径设置例外声明再对原始复位端口设置false path。项目简单时直接全部忽略问题也不大但在复杂SoC中建议仔细区分。6.2 复位释放后恢复时间检查的等效设计如果你使用了异步复位、同步释放时序工具看到的复位树最终汇入两级同步寄存器随后正常的寄存器时钟使能逻辑接管。对这两级同步寄存器还需要增加对复位信号的恢复/移除时间检查吗严格来说需要。set_false_path -from rst_n会让工具完全忽略复位释放的时刻但复位释放确实可能发生在时钟沿附近导致同步器第一级寄存器进入亚稳态。这个问题在结构上通过级联两级寄存器缓解工具层面通常建议使用ASIC库的复位同步器单元如RST_SYNC_CELL这类单元内部做了特殊的时序建模工具会自动检查它的RECOVERY/REMOVAL时间。FPGA里没有这么细的单元但可以用下列方法等效处理# 假设rst_sync实例名rst_sync_inst其输出rst_n_sync # 对同步器输出的复位树建立约束确保它经过BUFG到各个寄存器 set_property CLOCK_BUFFER_TYPE BUFG [get_nets rst_n_sync]把同步后的复位信号放到全局时钟网络上能显著降低复位到达各个寄存器的skew。FPGA里的BUFG就是干这个的很多新人忽略这一步导致复位信号走普通布线不同寄存器复位释放时间差异能达到纳秒级这在低速设计里不明显在高速设计里是个隐患。6.3 异步复位设计的时序报告解读静态时序分析跑完可以直接看复位相关路径的报告。重点检查复位端口到各个寄存器的CLR引脚是否被完全忽略报告里不应有这条路径的违规。同步释放电路的两级寄存器之间的路径setup/hold不能有违规因为这会直接影响复位释放的质量。数据路径上出现setup violation时先看是不是时钟约束太紧如果加set_false_path后数据路径违例反而消失说明约束之间互相干扰了。异步复位还有一类常见问题是时钟偏移与复位偏移的叠加。理想情况下所有寄存器在同一时钟沿同时采样复位同时释放各寄存器状态一致。实际PCB和FPGA内部走线不同时钟到达每个寄存器的时间有偏差clock skew复位到达每个寄存器的时间也有偏差reset skew两者的差值如果与数据路径延迟不匹配就可能在复位释放后出现某些寄存器先工作、某些还处于复位态逻辑状态错乱。工具能在ASIC流程里帮助我们做复位树的平衡FPGA流程里用BUFG全局资源网已经能覆盖大多数情况。7. 工程落地中的常见坑与排查清单7.1 综合结果与预期不符警惕寄存器被优化掉一个容易踩的坑是异步复位D触发器写好后综合时发现部分寄存器被优化掉了尤其在FPGA里。原因往往是复位初值恰好和上电默认值相同或者输出信号没有被完整驱动。比如一个寄存器复位为0初始值也是0且它的输出只驱动另一个寄存器综合器可能会把它折叠成一条直连通路。排查方法是看综合报告里的资源利用率和不被优化的寄存器列表如果发现寄存器数量比预期少先检查设计是否有死代码或未被使用的输出。还有一种常见情况是load_en始终为常高或常低综合工具会把使能逻辑优化成常量寄存器行为退化这在仿真时看不出来。7.2 复位信号的毛刺抑制与去抖在MCU/嵌入式环境里复位信号常来自按键、电源监控芯片和看门狗。这些来源的复位信号质量参差不齐外部毛刺可能导致系统误复位。硬件上通常加RC滤波软件/逻辑上可以做窗口检测——只承认持续一定时间的低电平才当作复位。对FPGA而言复位信号进FPGA前建议先经过IBUF再进同步释放电路同步器本身天然有去掉窄毛刺的能力。若复位信号来自板级要控制到达FPGA引脚的走线长度避免高速开关信号耦合出毛刺这也是很多板卡上复位走线被特意加粗、拉开间距的原因。7.3 跨时钟域复位传递的常见错误多时钟域系统里常见一个错误把A时钟域的复位信号直接接到B时钟域的寄存器复位端结果B时钟域寄存器在复位释放后A时钟域的复位释放沿相对B时钟域是异步的B域寄存器也面临亚稳态风险。正确的做法是每个时钟域都需要独立的复位同步释放电路把原始异步复位信号分别同步到各自的时钟域。// 每个时钟域一套rst_sync实例 rst_sync u_rst_sync_b ( .clk (clk_b), .arst_n (global_arst_n), .rst_n (rst_n_b) ); rst_sync u_rst_sync_c ( .clk (clk_c), .arst_n (global_arst_n), .rst_n (rst_n_c) );global_arst_n全局异步复位可以同时拉低两个域的复位关键在释放时各域按自己的时钟沿对齐释放。这个结构相当于把共享的异步复位变成了各域独享的同步复位释放是处理多时钟域复位的标准姿势。7.4 我个人的调试建议调试异步复位问题先用逻辑分析仪抓复位信号本身的波形。很多时候问题的根源不在RTL而在外部复位源本身——电压跌落、毛刺、抖动等硬件层面的问题。RTL仿真再完美也模拟不了真实复位信号的脏。遇到过好几次明明代码没问题的case最后都发现是板级复位信号受干扰。其次可以用一段简单的计数器上电测试逻辑来验证复位功能上电后计数器从0开始如果复位异常计数器跳到随机值大概率就是复位树或者复位释放有问题。这个方法在FPGA排障中屡试不爽。再一个小建议复位信号命名规范统一。rst_n、reset_n、ARST_N在一个工程里混用到了后期脚本处理阶段会非常痛苦工具脚本按信号名复用约束时很容易漏掉某一条。从第一天就把复位信号命名规则定下来一处定义全局引用能省大量排障时间。8. 扩展思路异步置位、可配置极性与IP化封装8.1 从清零到置位同一结构的不同玩法这次做的是异步清零但很多时候需要异步置位——比如状态机非法态检测后直接把状态寄存器置为某个特定值。把q 0改成q {DATA_WIDTH{1b1}}就能实现异步置位结构原理完全相同。还可以把复位值参数化实现类似复位到非零初值的灵活模块module dff_async_rst_val #( parameter DATA_WIDTH 8, parameter RST_VALUE 8h01 )( input wire clk, input wire rst_n, input wire [DATA_WIDTH-1:0] d, output reg [DATA_WIDTH-1:0] q ); always (posedge clk or negedge rst_n) begin if (!rst_n) q RST_VALUE[DATA_WIDTH-1:0]; else q d; end endmodule这种带初值配置的复位寄存器常用于配置寄存器组、中断状态寄存器等需要恢复默认配置的场景。8.2 高有效复位的兼容写法如果外部IP核要求高有效复位Verilog写法也需要调整。敏感列表里写posedge rst主逻辑用if (rst)清零。不过我不建议工程中混用统一低有效对团队协作更友好。必要时在顶层做一次极性转换保证内部逻辑统一用低有效复位。8.3 模块化封装的考量把可复位D触发器做成参数化模块是为了让它在更大设计中可复用。除了位宽、初值、极性这几个参数还可以考虑增加输入数据同步级数因为很多外设数据总线与系统时钟域不同步。把这些参数都收敛到一个模块里后续在多个项目里调用就很顺手了。从设计方法学角度说这个项目麻雀虽小五脏俱全RTL设计、仿真验证、时序约束、复位可靠性处理一条链路完整体验下来是理解数字电路从代码到芯片完整流程的好切入点。我之前带新人的时候也经常把这个模块作为第一个实战任务走通这个流程后再往复杂设计走心里就有底了。
返回列表