1. 项目概述:从原理图到数字逻辑的直观构建
在FPGA开发的入门阶段,很多朋友会直接从Verilog HDL代码开始,这当然是一条主流且强大的路径。但有时候,尤其是在教学演示或者理解底层数字电路结构时,回归最原始的“原理图输入”方式,反而能带来更直观、更深刻的认识。今天要聊的这个“模60计数器”项目,就是一个绝佳的切入点。它本身是一个经典的数字电路设计,常用于时钟的秒、分计数。我们这次不写一行Verilog代码,而是完全在Quartus II这样的EDA工具里,通过拖拽逻辑门、触发器等符号,像搭积木一样把它画出来,然后综合、仿真、下载到FPGA板卡上运行。
这听起来可能有点“复古”,但其价值不容小觑。对于初学者而言,它能帮你清晰地建立起“代码描述”与“实际硬件电路”之间的桥梁。当你用Verilog写一句reg [5:0] cnt;时,你知道它在FPGA内部对应着什么吗?通过原理图设计,你能亲眼看到这6个触发器(Flip-Flop)是如何级联,组合逻辑是如何连接它们的时钟、复位和使能端的。对于有经验的工程师,重温原理图输入,也能在调试复杂时序问题时,帮助你更直观地分析关键路径和扇出。这个项目适合所有希望夯实数字电路基础、理解FPGA设计本质的开发者,无论你是刚接触FPGA的学生,还是想深入理解综合后电路的工程师。
2. 模60计数器的核心需求与设计思路拆解
2.1 功能定义与边界条件
模60计数器,顾名思义,就是一个循环计数从0到59,然后归零的计数器。这是六十进制计数最直接的应用。我们需要明确它的具体行为:
- 计数范围:0, 1, 2, ..., 58, 59, 0, 1...
- 时钟驱动:在时钟信号(例如一个1Hz的脉冲)的每个上升沿(或下降沿,需统一),计数器状态更新一次。
- 同步复位:需要一个复位信号,当复位有效时,在下一个时钟边沿到来时,计数器被清零(同步复位比异步复位在FPGA中更常见且推荐)。
- 使能信号(可选但推荐):一个计数使能信号。当使能有效时,计数器才响应时钟进行计数;否则保持当前值。这增加了控制的灵活性。
- 进位输出:当计数器从59跳变到0时,产生一个高电平脉冲(通常为一个时钟周期宽),作为进位信号,用于驱动更高位的计数器(例如,秒计数器的进位驱动分计数器)。
在原理图设计中,我们需要用最基本的逻辑单元来实现上述功能。这迫使我们思考:一个计数器在硬件层面究竟是如何工作的?
2.2 方案选型:同步计数与异步计数
计数器主要有同步和异步两种实现方式。
- 异步计数器(纹波计数器):低位触发器的输出作为高位的时钟。这种设计简单,但存在“纹波”效应,即各级触发器不是同时翻转,导致瞬时状态错误和更低的最高工作频率。在高速或可靠性要求高的场合不推荐。
- 同步计数器:所有触发器的时钟端连接在同一时钟信号上,状态的翻转是同步的。计数逻辑通过组合电路产生每个触发器的下一状态(D端输入)。这种方式速度快,工作可靠,是FPGA设计中的标准做法。
毫无疑问,我们的模60计数器将采用同步设计。FPGA内部的触发器资源丰富,且全局时钟网络性能优异,非常适合构建大型同步电路。
2.3 原理图输入的优势与挑战
选择原理图输入来完成这个设计,主要基于以下几点考量:
- 直观性:电路结构一目了然。你可以看到每一个D触发器、每一个与门/或门/非门,以及它们之间的连线关系。这对于理解“位”与“硬件”的对应关系至关重要。
- 教学价值:它强迫设计者从布尔方程和状态转移的角度思考问题,而不是直接调用高级的
always @(posedge clk)块。这对于打好数字电路基础功非常有帮助。 - 对综合结果的理解:当你以后用Verilog写计数器并查看RTL Viewer(原理图视图)时,你会更容易理解工具综合出来的网络结构。
当然,挑战也很明显:
- 效率低下:对于复杂设计,画原理图远比写代码耗时,且不易维护和修改。
- 抽象层次低:需要手动处理每一位的逻辑,容易出错。
- 可移植性差:原理图与EDA工具绑定较深。
因此,这个项目的目的不是取代HDL,而是通过一种“笨办法”来获得对硬件本质的深刻洞察。
3. 核心模块分解与原理图符号准备
在Quartus II中开始画图之前,我们必须把模60计数器这个“黑盒子”分解成可实现的硬件模块。
3.1 计数器位宽确定与触发器选择
计数到59(3Bin Hex,111011in Bin),需要6个二进制位(因为2^5=32<60, 2^6=64>60)。所以我们至少需要6个触发器来存储计数状态Q[5..0]。
在FPGA的原理图库中,最常用的触发器是D触发器(DFF),带有时钟(CLK)、异步或同步复位(CLRN/SCLR)、使能(ENA)和数据输入(D)、数据输出(Q)端。我们将选择带同步清零(SCLR)和时钟使能(ENA)的D触发器(例如,搜索dffe或DFF with Sync Clear and Enable)。同步清零意味着清零信号需要在时钟有效边沿到来时才起作用,这符合我们的设计规范,也能避免异步复位带来的潜在时序问题。
3.2 次态逻辑生成:组合电路的核心
这是设计中最关键的部分。对于同步计数器,每个D触发器在下一个时钟周期的值(即D端输入),取决于当前所有触发器的输出值(当前状态)和计数逻辑。 对于最简单的二进制同步计数器,次态逻辑是:D[i] = Q[i] ^ (Q[i-1] & Q[i-2] & ... & Q[0]),即低位全为1时,高位翻转。 但我们是模60,不是模64。所以当计数到59(Q = 6‘b111011)时,下一个状态必须是0(Q = 6‘b000000),而不是60(6‘b111100)。
因此,我们需要一个组合逻辑电路,其输入是当前状态Q[5..0],输出是下一个状态D[5..0]。这个电路的逻辑功能可以描述为:
- 一般情况下(当前值
< 59),计数器执行二进制加法:D = Q + 1。 - 特殊情况下(当前值
== 59),计数器归零:D = 0。
在原理图中实现“加1”和“条件归零”,我们需要使用加法器(Adder)和比较器(Comparator)符号,或者用更底层的门电路搭建。为了教学清晰,我们采用模块化方法:
- 一个6位二进制加法器:输入为
Q[5..0]和6‘b000001,输出为Sum[5..0]和进位Cout。 - 一个等于59比较器:输入为
Q[5..0],当Q == 6‘b111011时,输出eq_59信号为高。 - 一个多路选择器(MUX):用
eq_59作为选择信号。当eq_59=0(未到59)时,选择加法器输出Sum作为D;当eq_59=1(已到59)时,选择常数6‘b000000作为D。
3.3 进位信号生成
进位信号carry_out应该在计数器从59变为0的时钟周期内有效。因此,carry_out的逻辑可以简化为:carry_out = eq_59 & ena。这意味着,在当前时钟周期已经是59且使能有效的情况下,下一个时钟沿将发生归零,同时产生进位脉冲。我们可以用与门实现。
4. 在Quartus II中的原理图绘制实操
现在,我们进入动手环节。假设你已经安装好Quartus II并创建了一个新项目。
4.1 创建原理图文件与添加符号
- 新建文件:在项目中,选择
File -> New,然后选择Design Files下的Block Diagram/Schematic File,点击OK。 - 打开符号库:在原理图编辑器的空白处双击,会弹出符号对话框 (
Symbol)。确保库路径正确,通常Libraries会列出quartus/libraries/下的各种库。 - 寻找核心符号:
- 触发器:在
primitives/storage路径下,找到dffe(D Flip-Flop with Enable)。这个符号具有D,CLK,ENA,CLRN(异步清零,我们暂时不用),PRN(异步置位,不用)和Q端口。我们需要的是同步清零,但dffe没有同步清零端。一种方法是利用使能端和D端实现同步清零:当清零信号有效时,将D端输入强制为0。另一种方法是搜索dffeas,它可能包含同步清零(SCLR)端。这里我们采用第一种更通用的方法。 - 逻辑门:在
primitives/logic下,找到and2,or2,not,xor等。 - 加法器:在
megafunctions/arithmetic下,找到lpm_add_sub。这是一个参数化加减法器。添加它时,会启动MegaWizard插件,配置为6位无符号加法,仅需要dataa和datab输入,result输出。 - 比较器:在
megafunctions/arithmetic下,找到lpm_compare。配置为6位宽度,比较类型选择 “A == B”,A端口连接计数器输出Q,B端口连接常数59(111011)。 - 多路选择器:在
megafunctions/gates下,找到lpm_mux。配置为2个6位数据输入,1个6位数据输出,1位选择线。 - 输入/输出端口:在
primitives/pin下,找到input和output符号。
- 触发器:在
4.2 绘制电路连接图
- 放置并排列元件:将6个
dffe并排放置,代表Q[5]到Q[0]。将lpm_add_sub(加法器)、lpm_compare(比较器)、lpm_mux(MUX)以及所需的逻辑门放置在合适位置。 - 连接时钟、复位和使能:
- 放置一个
input符号,命名为clk。用导线将其连接到所有6个dffe的CLK端口。 - 放置一个
input符号,命名为rst_n(低电平有效复位)。为了实现同步清零,我们不能直接连到CLRN。我们需要用组合逻辑:当rst_n为低时,让MUX的选择信号强制为‘1’(选择常数0),或者生成一个控制信号,在复位时屏蔽加法器输出并强制D为0。这里采用一个与门:ena_actual = ena & rst_n。将ena_actual连接到所有dffe的ENA端。同时,将rst_n信号也接入到控制MUX选择的逻辑中(见下文)。 - 放置一个
input符号,命名为ena(计数使能)。将其连接到上述与门的一个输入端。
- 放置一个
- 构建数据通路:
- 将6个触发器的
Q端输出引出,形成总线Q[5..0](在Quartus中,可以通过选中多个节点,然后右键 -> Group -> Create Group来创建总线)。 - 将总线
Q[5..0]连接到加法器的dataa端口。将常数1(6‘b000001)连接到datab端口。加法器的result输出命名为sum[5..0]。 - 将总线
Q[5..0]连接到比较器的dataa端口。将常数59(6‘b111011)连接到datab端口。比较器的aeb(A Equals B)输出即为eq_59信号。 - 将
sum[5..0]连接到MUX的data0x输入。创建一个常数0(6‘b000000)连接到MUX的data1x输入。 - MUX选择信号的生成:这是实现同步复位和模60归零的关键。选择信号
sel的逻辑应为:sel = (~rst_n) | eq_59。即,当复位有效(rst_n=0)或者当前计数等于59时,MUX选择常数0通道(data1x);否则选择加法结果通道(data0x)。用一个或门即可实现。 - 将MUX的6位输出
result[5..0]分别连接到6个dffe的D输入端。
- 将6个触发器的
- 生成进位输出:
- 进位
carry_out = eq_59 & ena_actual。用一个与门实现,其输出连接到一个output符号,命名为co。
- 进位
- 创建输出端口:将总线
Q[5..0]连接到一个output符号,命名为cnt_out[5..0]。
注意:在原理图中直接处理多位宽的总线连接时,务必注意总线命名和位宽的匹配。Quartus的连线工具有时会出现连接错误,务必在连接后通过
View -> Show Line Segments as Bus等选项检查连线是否为粗线(总线),并双击连线确认其属性。
4.3 设置引脚与编译
- 保存原理图:保存为
.bdf文件。 - 分析与综合:点击
Processing -> Start -> Start Analysis & Synthesis。工具会检查原理图的语法和连接错误。如果有错,根据提示信息在Message窗口中查找并修改。 - 引脚分配:根据你的FPGA开发板,在
Assignments -> Pin Planner中,将clk,rst_n,ena,cnt_out[5..0],co分配到具体的物理引脚上。例如,clk连接到板载时钟引脚,cnt_out连接到LED灯或数码管段选引脚用于显示。 - 全编译:点击
Processing -> Start Compilation进行全编译,包括综合、布局布线、时序分析和生成编程文件。
5. 功能仿真验证与问题排查
设计完成后,必须通过仿真来验证其功能是否正确。我们使用Quartus II自带的University Program VWF工具或ModelSim进行仿真。
5.1 创建测试激励波形
- 新建波形文件:
File -> New -> University Program VWF。 - 添加信号:将
clk,rst_n,ena,cnt_out,co添加到波形窗口。 - 设置时钟:将
clk设置为一个周期性的方波,例如周期20ns(50MHz频率)。 - 编辑激励:
- 初始阶段,设置
rst_n为低电平(0),保持几个时钟周期,然后拉高(1)。 - 将
ena设置为高电平(1),使能计数。 - 让仿真运行足够多的时钟周期,至少覆盖0到59两个完整的循环。
- 初始阶段,设置
5.2 观察仿真结果与分析
运行仿真后,你需要重点观察:
- 复位功能:在
rst_n变低后的第一个时钟上升沿,cnt_out是否被同步清零?co是否也为0? - 计数序列:
ena有效后,cnt_out是否在每个时钟上升沿从0开始递增:0,1,2,...? - 模60特性:当
cnt_out显示为59(3B或111011)时,下一个时钟沿是否跳变为0? - 进位信号:仅在计数器从59跳变到0的那个时钟周期,
co是否产生了一个高电平脉冲?脉冲宽度是否为一个时钟周期? - 使能控制:你可以尝试在计数过程中将
ena拉低,观察计数器是否暂停计数。
5.3 常见问题与排查技巧实录
在实际绘制和仿真中,你几乎一定会遇到一些问题。以下是一些典型问题及解决思路:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
编译错误:Can‘t resolve multiple constant drivers | 同一信号线被多个输出驱动。 | 检查原理图,确保没有两个输出引脚(如两个逻辑门的输出)直接连接到同一根线上。使用总线时,确保没有位宽不匹配的连接。 |
| 仿真结果:计数器不计数,一直为0 | 1. 使能信号ena未有效连接或始终为0。2. D触发器D端输入恒为0(MUX选择信号 sel可能恒为1)。3. 时钟信号未正确连接。 | 1. 在仿真波形中检查ena信号。2. 在仿真中添加中间信号(如 sel,sum,eq_59)进行观察,看逻辑是否正确。3. 检查原理图中 clk网络是否连接到所有DFF。 |
| 仿真结果:计数到59后不归零,变成60 | 比较器逻辑错误或MUX选择逻辑错误。 | 1. 检查比较器常数B是否设置为59(6‘b111011)。2. 检查 eq_59信号在计数为59时是否为高。3. 检查 sel信号逻辑 `(~rst_n) |
仿真结果:进位信号co持续为高或宽度不对 | 进位生成逻辑错误。 | 检查co = eq_59 & ena_actual逻辑。co应该只在eq_59和ena_actual同时为高的时钟周期内为高。确保ena_actual是ena & rst_n。 |
时序警告:Critical Warning: Timing requirements not met | 组合逻辑路径过长(特别是从Q到D,经过加法器、比较器、MUX),导致建立时间/保持时间违例。 | 1. 对于低速时钟(如1Hz),此警告可忽略。 2. 对于高速时钟,需要考虑流水线或寄存器打拍。但在原理图层面优化较难,这正体现了HDL描述行为、工具优化结构的优势。 |
实操心得:原理图仿真调试比代码调试更直观,但也更繁琐。强烈建议将关键内部节点(如
sum,eq_59,sel)也添加到仿真波形中。这样,当最终输出cnt_out错误时,你可以像调试硬件电路一样,一级一级往前追溯,看问题出在加法器、比较器还是MUX上。另外,Quartus的Netlist Viewers -> RTL Viewer功能可以在综合后生成一个更接近实际门级网表的原理图,与你手绘的进行对比,是排查连接错误的利器。
6. 硬件下载测试与扩展思考
编译和仿真通过后,就可以将生成的.sof文件下载到FPGA开发板进行实测。
6.1 硬件验证要点
- 资源占用查看:编译报告里会显示用了多少个逻辑单元(LEs)、寄存器。我们的设计应该占用6个寄存器(6个DFF)和若干组合逻辑(加法器、比较器等)。
- 上板验证:将
cnt_out连接到6个LED灯上(二进制显示),或者通过译码器连接到数码管上(十进制显示)。给一个低频时钟(如1Hz),观察LED的闪烁规律或数码管数字是否从00到59循环变化。按下复位键,观察是否清零。 - 进位信号测试:将
co信号连接到另一个LED,观察它是否每隔60个时钟周期闪烁一次。
6.2 设计扩展与变体
掌握了基本模60计数器后,你可以尝试以下变体,在原理图上进行修改,加深理解:
- 可加载的计数器:增加一个
load信号和data[5:0]输入。当load有效时,在下一个时钟沿将data加载到计数器中。这需要在MUX前增加一个选择逻辑。 - 可逆计数器:增加一个
up_down信号,控制计数器递增或递减。这需要将加法器替换为可加减的lpm_add_sub,并根据方向控制datab是+1还是-1(二进制补码形式的-1即6‘b111111)。归零和进位的条件也需要相应修改。 - 异步清零版本:尝试使用D触发器的异步清零端
CLRN。直接将rst_n连接过去。思考这与同步清零在仿真波形和实际硬件行为上会有什么不同?(提示:异步清零立即生效,不等待时钟沿)。
通过这个从原理图开始构建模60计数器的完整流程,我们绕了一圈,回到了数字逻辑的起点。你会发现,用Verilog写一句if (cnt == 59) cnt <= 0; else cnt <= cnt + 1;是多么简洁高效,而综合工具在背后默默完成了所有门级电路的优化和映射。这次“笨拙”的原理图之旅,目的就是让你看清这简洁背后隐藏的复杂与精妙。下次当你编写或调试Verilog代码时,脑海中能浮现出这些触发器、门电路和连线的画面,这将极大地提升你对硬件设计本质的把握能力。