简介:面向北京邮电大学数字逻辑课程设计的一份FPGA工程代码包,适合正在学习时序逻辑、状态机或硬件描述语言的电子类学生参考。项目涵盖电子钟与药片装瓶两个典型实验,涉及计数器、分频器、译码器、米利型和摩尔型状态机以及引脚锁定等关键设计环节,源码包含多种硬件描述语言实现。资源共五百三十七个文件,压缩包约四点四四兆字节,以硬件描述语言源文件为主,并配有引脚约束文件、比特流文件、脚本、报告及仿真日志,基本覆盖从编写、综合、仿真到下载验证的完整流程。目录结构按工程组织,便于对照学习引脚分配与布局布线细节。目前已有1179人下载学习,对希望快速上手FPGA数字系统设计与排错的读者有较高参考价值。
1. 拆开压缩包之后,先别急着写代码:课程设计代码包里到底装了些什么
大学里做数字逻辑课程设计,最典型的场景就是:老师布置一个题目,比如数字钟、频率计、流水灯控制器,或者交通灯状态机,然后你就开始从Verilog语法书的第一章啃到最后一章,最后综合出一坨能跑但不知道哪里会有问题的代码。如果你手里正好有一份“北邮数字逻辑课程设计FPGA代码.zip”这样的资源,第一反应肯定是解压,然后想看看里面有没有能直接抄的模块。但说实话,拿到代码包之后的核心动作不应该是“找代码”,而是先搞清楚这个项目的整体架构——这也是FPGA开发和单片机开发最大的区别之一:FPGA里没有“顺序执行”这个概念,所有模块都是并行的,所以代码包的结构直接反映了设计者的思维方式。
一个完整、规范的数电课设代码包,通常应该包含这么几类东西:
| 文件类型 | 常见扩展名 | 作用 |
|---|---|---|
| Verilog源码 | .v | 核心逻辑,包括顶层模块、分频模块、状态机、显示驱动等 |
| 仿真文件 | .tb.v / _test.v | testbench,用来在电脑上模拟信号变化,验证逻辑 |
| 约束文件 | .ucf / .xdc | 把FPGA芯片引脚映射到开发板上的具体物理管脚 |
| 工程文件 | .xise / .qpf / .xpr | 对应ISE、Quartus或Vivado的工程配置 |
| 设计文档 | .doc / .pdf | 模块划分、状态转移图、管脚分配表 |
| 比特流文件 | .bit / .sof / .svf | 已经综合布线后的下载文件 |
如果你打开压缩包发现里面有这六类东西,恭喜你,这份代码的完成度应该不低。如果压缩包里只有一堆光秃秃的.v文件,那也别慌,只要顶层模块写得清晰,一样可以快速重建整个工程。
拿到代码之后,我建议的阅读顺序是:
- 先读README或者设计文档(如果有的话),搞清楚这个课设到底是做什么功能的。像“北邮数字逻辑课程设计”这类项目,通常题目是固定在几个方向里的:数字频率计、数字秒表、多功能电子钟、简易计算器、交通灯控制器、波形发生器。这些题目虽然功能各异,但底层模块高度相似,都是“分频 + 计数 + 状态机 + 显示”四件套。
- 再读顶层模块(top module),因为顶层模块会把所有子模块的实例化关系、信号连接关系列得明明白白。读懂了顶层,你就知道整个项目的血液是怎么流动的。
- 最后再去抠子模块的内部逻辑。子模块一般包括时钟分频器、按键消抖、计数器、译码器、扫描显示驱动等。
很多初学者拿到代码包后的第一反应是“怎么这么多文件?”,然后就晕了。其实换个角度想,FPGA的模块化设计就跟装修房子一样:顶层模块是房子的户型图,分频模块是配电箱,状态机是智能家居控制中心,显示驱动是各个房间的灯光开关。你不需要同时关注每一个灯泡是怎么接线的,你只需要知道每一路开关控制哪一盏灯就够了。
2. 把代码跑起来的第一步:环境重建和引脚约束的那些坑
FPGA开发最让人头疼的往往不是逻辑设计本身,而是“代码明明写对了,为什么就是不出结果”。这种情况十有八九出在工程环境重建和引脚约束上。
北邮数电课设用的开发板,我见过的基本上以Xilinx Spartan系列和Altera/Intel Cyclone系列为主,不同年级、不同实验室,板子型号差别很大。代码包里的约束文件如果对应的是某一块特定的板子,你直接换到另一块板子上编译,最典型的结果就是:综合通过、实现通过、生成比特流通过,但下载到板子上之后,LED灯不亮、数码管全灭、按键完全没反应。
原因很简单——引脚约束是FPGA开发里最“物理世界”的一环。Verilog代码里写的signal_a,在数字逻辑层面只是一个抽象的线网;但在真实芯片里,它必须通过IO Buffer(输入输出缓冲器)连接到芯片的某个物理管脚,再由这个管脚通过PCB走线连到开发板上的某个LED、按键或数码管段码引脚。你代码里写的clk信号,如果约束文件里给的是板载50MHz有源晶振的引脚编号,那没问题;如果给的是某个按键对应的引脚编号,那你的主时钟永远等不到边沿。
所以,拿到别人的代码包之后,第一件要做的事不是编译,而是打开约束文件,对照自己手里的开发板原理图,逐行检查这些关键引脚:
- 系统时钟引脚(通常叫clk或sys_clk):确认晶振频率是50MHz、100MHz还是12MHz,代码里分频系数是按多少MHz算的。这里有一个非常经典的坑:很多教程代码默认50MHz,但你的板子是27MHz或12MHz,那么所有定时类的功能,比如秒表计时、频率计闸门时间,全部会按错误的比例跑。表现出来就是:仿真全对,上板时间全乱。
- 复位按键引脚:有的代码用高电平复位,有的用低电平复位,这由板载按键的接法决定。板上按键一端接3.3V、一端接FPGA引脚,那按下时给到FPGA的就是高电平;如果按键一端接地,按下时给到的就是低电平。别小看这点,我见过一个同学,把低有效复位的代码配到高有效复位的板子上,结果整个模块永远处于复位状态,数码管永远显示0000,排查了两天才发现是复位极性反了。
- 数码管位选和段选引脚:位选决定哪一位数码管点亮,段选决定这一位显示什么数字。这两组引脚数量多(位选4~8根,段选7~8根),而且每块板子的排列顺序都可能不同,出问题概率最高。特别是段选信号,有的板子数码管是共阳极(段选低电平点亮),有的是共阴极(段选高电平点亮),代码里的译码逻辑如果写反了,显示出来的数字全是你认识它但它不认识你的状态。
如果你手里的开发板跟原代码包不是同一块,我的经验是:先不要急着用别人写好的约束文件。自己新建一个约束文件,从原理图上一根一根查引脚编号,写下几根关键引脚,然后编译下载,先实现“下载后LED能随按键闪烁”这个最小目标。等这一套通了,再逐步把其余引脚补上。这样做看似多花了一个小时,实际上帮你省下后面至少一天的调试时间。
另外还有一类环境坑,跟FPGA厂商工具链的版本有关。老的课程设计代码如果是用ISE 14.7写的,你拿Vivado 2020.1去打开工程,大概率会提示工程文件版本不兼容。这种情况下别在兼容性上死磕,正确的处理方法是:在Vivado里新建一个空工程,选择对应型号的FPGA芯片,然后把所有.v源文件、约束文件、仿真文件重新添加进去。Verilog是文本文件,语法层面基本是通用的,只要代码风格不是太老(比如还在用已经废弃的`timescale写法),Vivado都能正常综合。反过来,如果是高版本工程文件想用低版本工具打开,那就基本无解了——厂商工具一般不做向下兼容。
3. 核心模块逐个看:分频器、状态机和数码管扫描是怎么串起来的
代码包看得多了你就会发现,不管题目怎么变,数字逻辑课程设计里的核心代码都绕不开这几个模块。这一节我挑三个最关键、也是最容易在写代码时翻车的模块,结合代码包里常见的具体实现,把背后的设计逻辑讲清楚。
3.1 分频器:从50MHz得到1Hz,不是简单除一下就行
分频器是FPGA里最基础的时序逻辑,但它的写法里藏着一个很重要的设计思想:什么时候用计数器直接分频,什么时候用锁相环(PLL)分频。
课程设计阶段,99%的情况都是直接用计数器分频。比如系统时钟是50MHz,想要一个1Hz的秒脉冲,那就计数25_000_000个时钟周期后翻转一次电平。代码如下:
reg [24:0] cnt; reg clk_1hz; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt <= 25'd0; clk_1hz <= 1'b0; end else if (cnt == 25'd24_999_999) begin cnt <= 25'd0; clk_1hz <= ~clk_1hz; end else begin cnt <= cnt + 1'b1; end end这里我用了cnt == 25'd24_999_999作为翻转条件。为什么不是25_000_000?因为计数器是从0开始数的,25MHz的计数范围0~24_999_999正好是25_000_000个周期,50MHz时钟下高电平25ms、低电平25ms,合起来一个周期就是50ms,也就是20Hz。要得到1Hz,需要数50_000_000个周期,代码里就要写成cnt == 50'd49_999_999。这个“减1”的问题,是新手最容易写错的地方,而且仿真时因为时间短不容易暴露,上板之后才露馅。
还有一点,这种用计数器翻转产生的时钟,在FPGA内部叫“门控时钟”(gated clock)。在真正的工程实践中,门控时钟是要尽量避免的,因为时钟信号经过逻辑资源会产生偏斜,影响时序收敛。正确的做法是用厂商提供的PLL/IP核来生成分频时钟。但对于课程设计来说,板子上的LED和数码管对时钟偏斜完全不敏感,门控时钟完全够用,也更容易理解。如果你追求代码“可进大厂”的规范程度,可以用PLL,但课设答辩时,直接讲计数器分频的原理,反而更容易得分——因为老师知道这是你自己写的。
3.2 状态机:课设里的“大脑”,建议所有状态都用独热码
数字逻辑课设里,状态机主要用在两类场景:一是控制类,比如交通灯的红绿黄切换、自动售货机的投币找零流程;二是时序类,比如数码管动态扫描的刷新流程、按键消抖的稳定判定流程。
状态机的写法有两大流派:一段式、两段式和三段式。课程设计代码里,最推荐的是三段式。理由很简单:一段式把所有逻辑写在一个always块里,代码短,但状态转移和输出逻辑混在一起,调试时改一处动全身;两段式把状态转移和输出分开,已经好很多;三段式再把输出逻辑单独用组合逻辑或寄存器输出,结构最清晰,也最容易在答辩时跟老师讲明白。
再看一个很实用的细节——状态编码。状态机的编码方式主要有二进制编码(如S0=2'b00, S1=2'b01, S2=2'b10)、格雷码和独热码(one-hot,如S0=4'b0001, S1=4'b0010, S2=4'b0100)。课程设计里,我强烈建议用独热码。
原因有三点:
- 独热码每个状态只有一个bit为1,状态判断逻辑不需要复杂的译码电路,组合逻辑面积更小。
- 综合工具对独热码的优化很成熟,时序表现更稳定。
- 调试时看波形一眼就能认出当前状态——哪个bit为1就是哪个状态。
代价是状态寄存器位宽更大,几个状态无所谓,十几个状态也还在可接受范围内。所以课设这种几十个状态以内的场景,独热码是性价比最高的选择。
localparam IDLE = 4'b0001; localparam COUNT = 4'b0010; localparam STOP = 4'b0100; localparam DISP = 4'b1000; reg [3:0] current_state; reg [3:0] next_state; always @(posedge clk or negedge rst_n) begin if (!rst_n) current_state <= IDLE; else current_state <= next_state; end always @(*) begin case (current_state) IDLE: next_state = start ? COUNT : IDLE; COUNT: next_state = stop ? STOP : (pause ? IDLE : COUNT); STOP: next_state = start ? COUNT : DISP; DISP: next_state = IDLE; default: next_state = IDLE; endcase end唯一要记住的是:独热码的case语句必须写default分支,否则综合时会生成多余的锁存器,而且未定义的状态直接导致行为不可控。
3.3 数码管动态扫描:刷新率决定你会不会看到鬼影
数码管动态扫描是FPGA课设里最经典的显示方案。原理很简单:同一时刻只点亮一位数码管,通过快速循环扫描位选,利用人眼的视觉暂留效应,让人觉得四位数码管同时都在亮。
但这里有一个非常关键的系统性参数:扫描频率。
扫描频率太慢,人眼会感觉到闪烁;扫描频率太快,单个数码管的导通时间太短,亮度会显著下降,而且可能会出现“串位”——即某一位数码管还没完全熄灭,下一位已经点亮了,看起来就像数字后面拖了个鬼影。
工程上常用的扫描频率范围是500Hz到1kHz之间。也就是说,四位数码管,每秒钟完整循环刷新500到1000次。这个范围是怎么定的?人眼对闪烁的感知临界频率大约在50Hz到60Hz,低于这个值会明显看到闪烁;而四位数码管共享一个扫描周期的话,每位点亮的占空比只有1/4,如果扫描频率刚好是60Hz,那实际每位的刷新率只有15Hz,闪得没法看。所以要做四位扫描,总刷新率至少要200Hz以上才能保证每位达到50Hz的感知刷新率,实际操作中取500Hz到1kHz,留足裕量。
对应到50MHz系统时钟,扫描周期就是50ms到100ms。这里我给出一个参考计数器设计:
reg [16:0] scan_cnt; always @(posedge clk or negedge rst_n) begin if (!rst_n) scan_cnt <= 17'd0; else if (scan_cnt == 17'd99_999) // 1ms scan_cnt <= 17'd0; else scan_cnt <= scan_cnt + 1'b1; end1ms产生一次位选切换信号,四位数码管轮流点亮,每个周期4ms,刷新率250Hz,完全够用。如果你的板子晶振不是50MHz,记得相应调整这个计数值。
数码管显示的另一个坑是数据同步。比如你用一个计数器计秒,计数值从59变为00的那一刻,正好赶上扫描信号切到最高位,那最高位可能显示旧数据,其余位显示新数据,产生一帧错乱。严格来说这属于跨时钟域问题(计数器的更新和扫描的更新在同一个时钟域里,其实不算真正的跨时钟域,但存在数据采样时序问题)。解决办法是把显示数据寄存器在切换前锁存一份,让扫描逻辑永远从缓存寄存器取数,而不是直接读计数器的实时值。代码包里如果做到了这一步,说明原作者的功底相当扎实,值得你好好学习。
4. 仿真与验证:不要只点一下“Run Simulation”就完事
很多同学拿到代码包后,走完“新建工程、添加文件、综合、生成比特流、下载”这条流水线,看到板子上有反应就以为大功告成了。但真正的调试才刚刚开始。FPGA开发里有一句老话:能在板子上跑起来的代码,不等于没有bug的代码。尤其课程设计这种规模的项目,如果只做上板验证不做仿真验证,你根本不知道代码的边界行为是什么。
4.1 testbench该怎么写才有价值
代码包里一般都会带testbench文件,但质量参差不齐。很多课设的testbench就做了两件事:给个时钟、给个复位,然后跑200个周期就停了。这种仿真只能证明代码“基本能跑”,完全覆盖不到边界条件。
真正的testbench应该至少做到三件事:
- 生成正确的时钟和复位信号,并且复位信号要有释放过程。复位不能一开始就是1或0,而要经历“有效——保持一段时间——释放”的过程,用来验证异步复位的同步释放逻辑。
- 通过task或initial块,模拟真实按键输入。比如你要测试秒表的暂停功能,就应该在仿真中让按键信号在特定时刻拉高、保持若干毫秒、再拉低,模拟一次完整的物理按键操作。这里还要注意:真实的物理按键是有抖动的,所以testbench里最好也模拟抖动——先拉高,然后连续翻转几次,最后稳定在高电平。这样你的按键消抖模块才能在仿真阶段就被验证到。
- 主动检查输出,而不是人眼盯着波形图。用
$display或assert来判定输出是否符合预期,仿真结束后自动打印结果。这样做的好处是,回归测试时可以自动确认“新改动没有破坏旧功能”。
4.2 实用仿真技巧:用虚拟时钟和文件输出做“半自动验证”
我们以频率计为例,假设你要测量一个1kHz的待测信号。频率计的核心逻辑是:在固定闸门时间(比如1s)内对输入的待测信号进行计数,计数值就是频率值。仿真时如果你真的等仿真器跑完1秒钟,那在计算机上会非常慢。怎么办?两个办法。
第一个办法是缩短闸门时间。把设计里的闸门时间参数化,比如定义一个parameterGATE_CYCLES,仿真时把它设成100个时钟周期,上板前改回1s对应值。这样测试一个流程只需要几微秒的仿真时间。
第二个办法是直接用force命令将内部信号强行拉高拉低,快速跳转到指定状态。比如测秒表暂停功能,正常操作需要“按键按下—消抖—状态机跳转”,一整套流程在仿真里要跑几万个周期。用force直接修改按键信号,配合wait语句等待状态机翻转,几行代码就能完成。
再提一个很多人不知道的技巧:仿真时可以用$writemem和$readmemh配合外部文件。比如你要测试一个简易UART接收模块,可以把testbench的串行输入数据从文件里读进来;要把仿真结果记录下来,可以定期把内部状态写入文件。这样即使仿真时长很长,你也能事后分析。
4.3 上板调试的正确姿势:ILA/VIO不是洪水猛兽
课程设计答辩时,老师最爱问的一个问题是:“你代码跑通了,你怎么证明它跑通了?”如果你说“我看了板子,功能正常”,其实是很弱的回答。因为FPGA是个并行系统,板子上看到的往往是多个模块协同工作的最终结果,中间任何一环出问题,最后的现象可能都一样:灯不亮、数字不对。
正确的做法是使用片上逻辑分析仪。Xilinx平台叫ILA(Integrated Logic Analyzer),Intel平台叫SignalTap。这东西本质上就是把你FPGA内部的几个关键信号,按照设定的触发条件,实时抓取到芯片内部的BRAM里,然后通过JTAG回传到电脑上显示波形。
使用ILA时有三点建议:
- 不要等代码全部写完才加ILA。最理想的做法是:添加ILA核,把顶层模块里的关键信号拖进去,布好探针,编译下载,运行一整轮,采集波形,分析,修改代码,重复。这样每一轮修改都有波形数据作为依据,而不是靠猜。
- ILA的采样深度不要设得太大。默认的1024或2048个采样点通常够用,太大的采样深度会占用BRAM资源,影响综合后的最高频率。
- 触发条件要选好。比如你要抓取秒表的暂停瞬间,就把触发条件设为“按键按下信号上升沿”,这样ILA会从按下前几十个周期开始记录,一直记录到按下后的几十个周期。这样能清楚看到:按键信号进入、消抖模块的计时输出、状态机的跳转、计数器的停止,一整条因果链。
在课程设计这个层面,你会用ILA看波形,就比90%的同组同学(可能包括你的答辩老师预期水平)高出一截了。因为这意味着你不是“代码写完就交付”,而是做了真正的硬件调试。
5. 课程设计代码里最常见的五个隐藏Bug,以及怎么避免
这一节的内容是我在看了不知道多少份课设代码、以及自己复现FPGA项目时总结出来的高频故障点。每个都对应着具体的代码写法和背后的电路原理,踩过坑的人应该一看就懂。
5.1 复位信号“重名”问题:同步复位还是异步复位
很多代码的模块里既有rst_n参数,又有clk参数,然后每个always块第一行写的是:
always @(posedge clk or negedge rst_n) begin if (!rst_n) ...这是标准的异步复位写法。异步复位的优点是不依赖时钟就能立刻让电路回到初始状态,缺点是对复位毛刺非常敏感。如果你的复位信号是按键直接接的,按下和释放的瞬间会产生机械抖动——也就是会出现若干次非常窄的毛刺——这些毛刺如果刚好作用在寄存器复位端,会导致寄存器被不可控地复位。
解决办法有两种:一是加一个按键消抖模块,把消抖后的信号作为复位信号;二是把复位方式改成同步复位,即只在时钟上升沿检查复位信号:
always @(posedge clk) begin if (!rst_n) ...同步复位对毛刺免疫,因为毛刺不会刚好落在时钟边沿上。代价是复位信号必须保持足够长的时间(至少一个时钟周期),否则复位可能没生效。二者各有利弊,但课程设计里面,我更推荐“消抖后的按键信号 + 异步复位”组合。原因很简单,物理按键按下时,除了机械抖动,还会有弹跳造成的一段浮空区,这会影响上电后的初始状态。用异步复位,可以保证只要复位键一按,电路立刻回零,不依赖时钟是否稳定。
5.2 阻塞赋值与非阻塞赋值混用:仿真没问题,上板就翻车
这是Verilog最著名的坑,没有之一。简单说:时序逻辑里(带时钟上升沿的always块)必须用非阻塞赋值<=;组合逻辑里(always @(*))用阻塞赋值=。两者混用会导致仿真行为和综合后行为不一致。
具体例子:很多人为了图省事,在同一个always块里既给计数器赋值又给标志位赋值,并且混用了=和<=。仿真时因为仿真器按顺序执行,看起来逻辑是对的;但综合工具会把它综合成一个带多个输出的触发器和一堆组合逻辑,真实电路里的时序关系和仿真完全不同。结果就是:ModelSim里仿真波形完美,下载到板子上,计数器一位一位地乱跳。
解决办法只有一条:严格执行“时序逻辑用<=,组合逻辑用=”这条规则。这是一个习惯问题,但只要养成了,能帮你避开数电课设从仿真到上板的绝大多数时序翻车。
5.3 状态机默认状态缺失
之前提到独热码状态机的case语句必须写default,其实这个规则对所有类型的状态机都适用。如果不写default,当状态寄存器因为干扰进入一个未定义状态时,状态机会卡死在那个状态,而且这个错误在仿真阶段很难暴露,因为仿真时状态变量只会取到代码里赋过的值。
让状态机进入非法状态的方法其实很多:全局复位信号不稳定、组合逻辑的竞争冒险、时序违规都可能导致。所以default分支的作用是“兜底”,让任何非法状态都能自动回到IDLE或某个安全状态。
5.4 计数器位宽不足
假设你要设计一个闹钟,用24小时制,小时计数器最大到23。如果你声明reg [3:0] hour,那取值范围是0~15,等到hour计数到10时会溢出回0,闹钟显示就乱了。必须用reg [4:0] hour才能覆盖0~23。
同样的道理,分频计数器很容易溢出。50MHz是50_000_000,用十进制换算成二进制是26位(2^25 = 33_554_432,2^26 = 67_108_864),所以至少要声明reg [25:0] cnt,也就是26位。很多代码写成reg [15:0] cnt,时钟跑到65535就回绕了,分频结果完全不对。这种错在仿真时如果时间设置得不够长也看不出来,但上板一定会出问题。
给个判断位宽的标准公式:计数上限为N(0~N-1),需要的位宽是$clog2(N),也就是以2为底向上取整。50MHz对应N=50_000_000,位宽就是$clog2(50_000_000) = 26。
5.5 跨时钟域信号没有打拍同步
如果你的项目里有多个时钟源,比如系统时钟50MHz,SPI接口的时钟1MHz(有些高级课设题目会涉及),那么两个时钟域之间传信号时必须做同步处理。最常见也最实用的做法是:对跨时钟域的单bit信号打两拍(即用目标时钟域的寄存器连续采两拍)。
reg sync_1, sync_2; always @(posedge clk) begin sync_1 <= spi_cs_n; sync_2 <= sync_1; end第一拍消除亚稳态,第二拍给出稳定的同步信号。课程设计里如果涉及按键输入和模块间通信,强烈建议在每个输入信号进模块的第一时间就做一次打拍同步。这跟消抖是两回事——消抖针对的是机械抖动,打拍针对的是跨时钟域的亚稳态。两者可以串联使用:输入信号——打拍同步——消抖——进入状态机。
6. 把课程设计代码吃透之后,下一步能往哪走
如果你已经把代码包里的每个模块都过了一遍,仿真也做了,上板也通了,那这份课设的学习价值其实才刚刚开始。我建议你做三件事,每一件都能让这次课程设计的含金量上一个台阶。
第一,挑一个模块,自己从零写一遍。比如把原来别人写的分频器删掉,自己用PLL IP核重新实现一遍,对比两者的功耗、资源和时序;或者自己写一个UART发送模块,把板子上的数据通过串口传到电脑上。这个过程能让你把“会读代码”升级成“会写代码”。
第二,给现有设计加一个“超纲”的功能。比如原来的课设是秒表,你可以加一个暂停时的数据保持功能;原来是交通灯,你可以加一个根据车流量自动调整绿灯时长的简单逻辑。加功能的过程会逼迫你去学更深的东西——比如状态机扩展、参数化设计、甚至简单的SOC概念。
第三,把代码里的关键模块做成可复用的IP。比如把按键消抖做成一个独立的模块,以后所有项目都能直接调用。很多FPGA工程师的“个人代码库”就是这样一点一点积累起来的,这才是代码包真正的价值——你不只是在做一次课设,你是在建立自己的硬件设计能力体系。
我自己的体会是,FPGA学习最忌讳的是“重仿真轻上板”或者“重上板轻仿真”。两者必须结合起来:仿真是为了让你在没上板前就能发现逻辑错误,上板是为了让你确认仿真没覆盖到的物理世界细节。课程设计只是起点,把这份代码吃透,你下一步就可以去尝试更复杂的项目,比如图像采集、以太网通信或者RISC-V软核处理器。那时候你会发现,数字逻辑课设里学的分频、状态机、显示驱动这些基本功,真的全都用得上。
本文还有配套的精品资源,点击获取