FPGA软核实战:DE2板上运行OC8051并点亮LED 📅 发布时间:2026/9/7 4:58:31 👁 浏览次数: 简介面向FPGA与嵌入式学习者这套基于Altera DE2开发板的OC8051点灯实验资源完整演示了如何在DE2硬件平台上集成可综合的8051软核处理器并通过自带LED测试程序验证运行效果。整个压缩包共1042个文件、约19.07MB以完整的Quartus工程为主体包含大量cdb、hdb、tdf等工程数据库与设计文件同时有v、vhd源码文件、mif/hex存储器初始化文件以及asm汇编示例能清晰对应FPGA开发的完整流程。配套说明文档详细讲解了修改OC8051 ROM的具体方法并给出可初始化ROM的LED测试程序下载到DE2后无需额外改动即可直接点灯文档同时涵盖Quartus工程操作思路便于对照练习。该资源包已有1026人学习工程结构完整、步骤可跟做适合课程设计、毕业设计或自学OC8051处理器核移植、ROM配置与DE2开发流程的初学者和进阶者是研究软核CPU在FPGA上运行的实用参考。1. 这块板子和这个软核到底能折腾出什么最近把柜子里那块老DE2板又翻出来了。DE2用的是Altera Cyclone II系列FPGA具体型号EP2C35F672C6板上最显眼的是一排拨码开关、一排红色LED、一排绿色LED外加一个50MHz有源晶振和板载USB Blaster下载器。这块板子在很多高校的数字逻辑课上都出现过但大多数时候只是跑跑现成的VGA、数码管演示Demo。我这次想让它干点正经事把OpenCores上的开源8051软核OC8051跑起来在上面运行一个最经典的点灯程序。OC8051是一套用Verilog写的、兼容Intel 8051指令集的软核源码。它的意义不在于性能——标准8051的一个机器周期是12个时钟周期跑在50MHz下也就4MIPS出头——而在于全套RTL都是开放的指令译码、ALU、取指单元、存储器接口这些部件清清楚楚摆在眼前。点灯程序虽然只有几行代码但它是嵌入式世界里名副其实的Hello WorldCPU能正常复位、能从ROM里取到指令、能完成加法跳转、能把数据写到SFR寄存器、IO引脚能翻转。任何一环出了问题灯都不会按预期闪问题范围也被天然缩小了。DE2这块板子对OC8051来说算是大材小用了。整个8051软核加上8KB程序ROM、256字节数据RAM总共消耗的资源在Cyclone II上只是九牛一毛。但正因为DE2资源充裕初学者可以完全不用操心资源优化专心把CPU运行的链路搞明白。而且DE2板载的USB Blaster自带JTAG下载不需要额外买下载器插上USB线就能干活。唯一的麻烦是Cyclone II这个器件太老新版本Quartus已经不收了这个后面专门说。做这个项目之前建议你先明确目标不是要做一个能用的单片机替代品而是通过最小系统理解一条C语句是怎么变成FPGA内部的一堆信号翻转的。理解了这一层后面再去玩Nios II、RISC-V软核或者去调总线时序都会顺很多。这个项目适合三类人学过Verilog但没跑过软核的学生、想搞懂8051内部结构的嵌入式爱好者、以及手里正好有一块DE2吃灰想再利用的工程师。2. 系统架构与工具链选型2.1 一个最小的OC8051系统由什么构成OC8051从OpenCores下载下来后源码里其实已经带了一个可仿真的最小系统。从设计思路上看它把CPU核心和存储器分开了oc8051_top是核心oc8051_rom_top是程序存储器oc8051_ram_top是数据存储器加特殊功能寄存器。ROM和RAM都挂在核心的存储器总线上通过片选信号区分访问对象。这个结构和真实8051的哈佛结构一致——程序从ROM里取数据在RAM里读写两者地址空间独立。对于FPGA实现来说这套结构最大的好处是ROM和RAM都可以直接映射到Cyclone II内部的M4K块存储器上。8051的架构决定了它不需要外部扩展总线只要把核心顶层实例化再接上这个板子上的LED就能跑。写到P1口的指令最终会通过内部存储器总线送到oc8051_ram_top里的SFR模块更新P1锁存器然后把p1_out信号引到FPGA引脚上LED就亮了。整个过程不需要额外的总线桥、不需要地址译码逻辑清清爽爽。2.2 工具链的坑Quartus版本和SDCC选型先说最影响心情的一个坑Cyclone II器件在Quartus Prime 18.0之后就被彻底移除了新装的正版软件里根本找不到EP2C35这个器件。老老实实用Quartus II 13.0sp1这是支持Cyclone II的最后一个靠谱版本网上到处都有下载装完也不需要额外授权Web Edition就够用。你要是手头还有更老版本的Quartus 9.1也能用但13.0sp1对USB Blaster驱动的适配更好建议直接上13.0sp1。固件编译工具我选了SDCC它是开源的8051 C编译器命令行方式工作配合Makefile可以自动化编译。有人会问为什么不用Keil C51原因很简单Keil的免费评估版有2KB代码限制虽然点灯程序不到2KB但后面一旦加功能就麻烦而且Keil在Linux和macOS下用起来费劲。SDCC对标准8051的支持很完整P0到P3、各种SFR在头文件里都定义好了编译点灯程序毫无压力。下载器直接用DE2板载的USB BlasterQuartus Programmer里选JTAG模式就能识别。2.3 程序放哪、数据放哪、SFR怎么处理存储规划是整个设计的重点。程序存储器我做了8KB16位地址线只用低13位复位向量0x0000放第一条跳转指令这样SDCC编译出来的固件可以直接加载。数据存储器就是标准8051的256字节内部RAM地址0x00到0x7F是通用RAM0x80到0xFF是SFR区OC8051的oc8051_ram_top模块已经把这两个区域统一管理了写P1口本质上就是往地址0x90写一个字节。这里有个细节值得注意SDCC默认会把全局变量放在内部RAM的data段点灯程序里那几个循环变量、模式表数组占用的空间很小256字节绰绰有余。但如果程序里有比较长的字符串或者大数组就要考虑用code关键字把它放到程序ROM里或者启用外部XRAM。点灯这关还用不到XRAM不过理解了这个存储模型后面做串口打印、跑复杂算法时就不会一头雾水。3. 完整实操从固件到下载的全流程3.1 第一步写出点灯C程序并用SDCC编译先写固件。最简单的方式是让P1口输出交替翻转LED全亮全灭间隔靠软件延时。代码如下#include 8051.h void delay(void) { unsigned int i; for (i 0; i 50000; i) ; } void main(void) { P1 0x00; while (1) { P1 0xFF; delay(); P1 0x00; delay(); } }编译命令很简单sdcc -mcs51 --code-size 8192 -o blink.hex blink.c编译完成后会生成blink.hex这就是要加载到FPGA内部ROM里的固件。SDCC生成的hex是Intel HEX格式Quartus的ROM初始化可以直接认。有一点要注意SDCC会自动链接一段启动代码在地址0x0000处放一条跳转指令然后跳到C语言初始化代码所以你不必自己写启动汇编。为了效果更好看一点我把固件改成流水灯。这里用了一个code数组让它在查表时走程序存储器的MOVC指令顺便验证ROM访问没有问题#include 8051.h static unsigned char code pattern[8] { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; void delay(unsigned int n) { unsigned int i; while (n--) { for (i 0; i 2000; i) ; } } void main(void) { unsigned char idx 0; while (1) { P1 pattern[idx]; idx (idx 1) 0x07; delay(30); } }提示code关键字在SDCC里表示把数据放到程序ROM空间。点灯程序虽然不用它也能跑但加上之后能验证OC8051的MOVC取指路径是否正常这个能力后面做字符点阵、查表运算时很关键。3.2 第二步搭Quartus工程、接顶层模块打开Quartus II 13.0sp1新建工程器件选Cyclone II下的EP2C35F672C6。把OC8051源码里的oc8051_top.v、oc8051_fetch.v、oc8051_decoder.v、oc8051_exec.v、oc8051_alu.v、oc8051_ram_top.v加进工程。注意源码里可能有include全局宏定义文件比如oc8051_timescale.v一并加进来否则编译会报一堆找不到宏的错。顶层模块我写了这样一个de2_top.v例化CPU核心、RAM并用Quartus的ROM IP例化程序存储器module de2_top( input wire CLOCK_50, input wire [3:0] KEY, output wire [7:0] LEDR ); wire reset_n; wire [7:0] p1_out; wire [15:0] mem_addr; wire [7:0] mem_data_w; wire [7:0] mem_data_r; wire mem_wr, mem_rd; assign reset_n KEY[0]; assign LEDR p1_out; oc8051_top u_cpu ( .clk (CLOCK_50), .rst (~reset_n), .p1_out (p1_out), .p0_in (8hFF), .p1_in (8hFF), .mem_addr(mem_addr), .mem_data_w(mem_data_w), .mem_data_r(mem_data_r), .mem_wr (mem_wr), .mem_rd (mem_rd) ); oc8051_ram_top u_ram ( .clk (CLOCK_50), .addr (mem_addr[7:0]), .data_w (mem_data_w), .data_r (mem_data_r), .wr (mem_wr), .rd (mem_rd) ); endmodule不同的OC8051版本端口名可能略有差异比如存储器总线上可能还有mem_cs、mem_oen之类的信号遇到报错就对照源码顶层文件把端口名改过来即可。核心的几个信号就是地址、写数据、读数据、写使能、读使能理解了这些改端口不是难事。3.3 第三步ROM初始化文件处理这是整个项目里最容易卡住的一步。OC8051参考设计里的oc8051_rom_top是一个用case语句穷举地址的组合逻辑ROM直接拿它来用的话得把hex转成Verilog case语句文件程序一大编译会非常慢而且调试时要反复重新生成烦得很。我的做法是不使用参考设计里的rom_top改用一个由MegaWizard生成的altsyncram只读存储器IP容量设成8192字节初始化文件直接指定第3.1步编译出来的blink.hex。这样Quartus会把hex内容直接烧进M4K块存储器的初始化数据里干净利落。关键配置只有一个在MegaWizard里把Read Output的寄存器选项关掉让ROM输出是组合逻辑直通。如果开了输出寄存读数据会晚一个时钟周期OC8051的取指流水节奏就对不上了程序要么乱跑要么直接卡死。Cyclone II的M4K块存储器组合读出时间大约在7到11纳秒50MHz时钟周期是20纳秒时序上没问题但为了保险起见我在时序约束里把CLOCK_50约束为20ns并且对存储器读数据路径做了set_max_delay检查实测下来非常稳。如果不用MegaWizard也可以用Verilog直接写一个带$readmemh的ROM模块但要注意$readmemh加载的是纯十六进制文本不是Intel HEX格式得先用脚本把blink.hex转成每行一个字节的mem文件。相比之下altsyncram直接认Intel HEX省一步转换强烈推荐。3.4 第四步引脚约束、时序约束与编译下载引脚约束我直接在Quartus的Assignments菜单里做的也可以改qsf文件。DE2板卡手册里有全部引脚对照表我这次用到的引脚分别是信号DE2引脚说明CLOCK_50PIN_N2板载50MHz晶振KEY[0]PIN_G26复位按键按下为低LEDR[0]PIN_AE23红色LED 0LEDR[1]PIN_AF23红色LED 1LEDR[2]PIN_AB21红色LED 2LEDR[3]PIN_AC22红色LED 3LEDR[4]PIN_AD22红色LED 4LEDR[5]PIN_AD23红色LED 5LEDR[6]PIN_AD24红色LED 6LEDR[7]PIN_AE25红色LED 7时序约束只用加一条时钟约束新建一个Synopsys Design Constraints文件create_clock -period 20.000 -name clk [get_ports {CLOCK_50}]然后全编译。编译完成后打开Programmer选择USB BlasterMode选JTAG加载生成的.sof文件点击Start下载。DE2板载的USB Blaster驱动在Quartus II 13.0sp1里是自动识别的插上USB线就能看到设备。注意下载完后FPGA先按复位键再松手顺序不能反。我一开始下载完没按复位灯完全不亮还以为是ROM加载失败排查了半天才发现是复位信号一直处于复位状态。KEY按下是低电平顶层里rst接的是~reset_n所以按下按键等于拉高复位。4. 调试实录常见问题与排查方法4.1 问题速查表跑这个项目过程中我自己踩过、也帮别人排查过不少问题整理成一张速查表建议截图存一份现象可能原因处理方法下载后灯完全不亮复位没释放、固件没加载、引脚没约束按一下KEY[0]复位检查alignsncram的init文件路径核对qsf引脚灯常亮不闪烁延时循环被优化掉或ROM里取的指令异常检查SDCC优化等级volatile变量仿真看PC值灯乱闪、没有规律ROM读时序不匹配确认altsyncram没开输出寄存器降低时钟频率到25MHz试编译报找不到宏缺oc8051_timescale等全局文件把源码目录下所有.v文件都加进工程编译报非法引脚引脚名写错或qsf重复约束对照DE2手册重新核对引脚名Quartus找不到器件Quartus版本太新换Quartus II 13.0sp14.2 几个印象深刻的坑与解决思路第一个坑是ROM输出寄存器问题。我第一次图省事MegaWizard里保留了默认的输出寄存器结果下载后LED是全灭的用SignalTap抓信号才发现ROM读出来的数据永远慢一拍8051的取指状态机彻底错乱。这事的教训是使用任何一个现成的存储器IP必须先搞清楚它的读延迟是几个周期不能想当然。第二个坑是SDCC的优化把延时函数里的循环变成了空操作。我最初用的是for(i 0; i 50000; i);开启--opt-code-speed之后SDCC居然把整个循环干掉了灯变成常亮。解决办法是把延时参数改成volatile或者在循环体里加一个空语句。点灯程序本身无所谓但这个坑在以后写驱动时经常遇到值得记住。第三个坑是复位方向。DE2的KEY按下是低电平这是板卡硬件决定的但OC8051的复位是高有效很多人第一次都栽在这。建议在顶层模块里把复位逻辑写得直白一点加个注释说明按下按键等于复位省得下次翻代码时还要重新想一遍。4.3 一个能省掉一半调试时间的建议上板之前强烈建议先在ModelSim里把整个设计仿真一遍。OC8051源码自带testbench你只需要把ROM的初始化数据换成自己编译的hex然后跑波形重点看三个信号取指地址mem_addr、ROM读出的指令数据、以及P1端口的p1_out。如果仿真里p1_out能按预期翻转那硬件部分大概率没问题上板之后就算灯不亮也是引脚约束或者复位的问题排查范围一下就缩小了。ModelSim和Quartus II 13.0sp1的联动是现成的在Quartus里可以直接启动RTL Simulation。仿真速度很快点灯程序跑个几百微秒就够观察好几个翻转周期了。我自己的习惯是仿真过了再上板上板只看两个东西灯闪不闪、闪的节奏对不对。这样做的效率最高。5. 如果想让效果更直观可以做的小扩展点灯跑通了之后别急着收工。DE2板上还有数码管、拨码开关、蜂鸣器稍微扩展一下就能变成一个更完整的软核外设实验平台。最简单的扩展方式是把SW拨码开关接到OC8051的P0口把LED输出改成根据拨码状态点亮对应位这样就验证了输入回路的兼容性——8051的IO口是准双向口读引脚之前要先写1这个细节在OC8051里同样存在值得亲手试一下。再进一步可以把P1接到DE2的7段数码管译码器上让CPU在循环里刷新数字显示。这需要写一个查表译码器把0到9的段码表用code数组放在ROM里正好复习一遍MOVC查表操作。运行效果比LED流水灯醒目得多在实验室里展示也更像样。我个人在实际调试中的体会是OC8051这类开源软核最大的价值不是能跑而是能拆开看。点灯程序跑到这里硬核CPU里那些抽象的概念——取指、译码、写寄存器、查表取数——全都变成了你在QuestaSim里能逐周期追踪的波形。接下来不管你是想给它加串口、加定时器还是干脆换成RISC-V软核再玩一遍思路都是相通的先把最小系统跑通再一个模块一个模块往上加每加一个都回到点灯这个基准测试确认没有把原来的功能弄坏。最后再分享一个小技巧把工程里用到的所有文件、脚本、hex固件归档到一个目录写个README记下Quartus版本和SDCC编译命令。这看起来是小事但三个月后你回头再想跑这个工程时这套记录能帮你省下整整一个下午。本文还有配套的精品资源点击获取