news 2026/9/7 4:58:26

FPGA软核实战:DE2板上运行OC8051并点亮LED

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA软核实战:DE2板上运行OC8051并点亮LED

简介:面向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 World":CPU能正常复位、能从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 Blaster,Quartus Programmer里选JTAG模式就能识别。

2.3 程序放哪、数据放哪、SFR怎么处理

存储规划是整个设计的重点。程序存储器我做了8KB,16位地址线只用低13位,复位向量0x0000放第一条跳转指令,这样SDCC编译出来的固件可以直接加载。数据存储器就是标准8051的256字节内部RAM,地址0x00到0x7F是通用RAM,0x80到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 (8'hFF), .p1_in (8'hFF), .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 0
LEDR[1]PIN_AF23红色LED 1
LEDR[2]PIN_AB21红色LED 2
LEDR[3]PIN_AC22红色LED 3
LEDR[4]PIN_AD22红色LED 4
LEDR[5]PIN_AD23红色LED 5
LEDR[6]PIN_AD24红色LED 6
LEDR[7]PIN_AE25红色LED 7

时序约束只用加一条时钟约束,新建一个Synopsys Design Constraints文件:

create_clock -period 20.000 -name clk [get_ports {CLOCK_50}]

然后全编译。编译完成后,打开Programmer,选择USB Blaster,Mode选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.0sp1

4.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编译命令。这看起来是小事,但三个月后你回头再想跑这个工程时,这套记录能帮你省下整整一个下午。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 4:57:27

构建Lojban工具链:livla管道式架构与文本解析实践

简介&#xff1a;这套面向Lojban语言学习者和开发者的多功能工具组合&#xff0c;整合了解析器、搜索界面、词典软件与IRC机器人等组件&#xff0c;可通过Docker或podman快速部署到本地环境。压缩包内共2000个文件、约44.38MB&#xff0c;其中1613个mp3音频构成丰富的发音素材库…

作者头像 李华
网站建设 2026/9/7 4:57:19

EFT测试整改全攻略:从IEC 61000-4-4标准到共模干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 4:55:03

地平线征程芯片累计量产突破1500万片:国产智驾芯片如何炼成

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 4:53:42

用Vim宏编程从零实现康威生命游戏:寄存器与文本缓冲区的极致实践

如果你只在 Vim 里用宏做过“批量加注释”或“把单词统一替换”&#xff0c;那你可能还没有真正见过宏编程的杀伤力。 这篇文章要完成一个看起来像行为艺术、实际上非常训练底层功夫的任务&#xff1a;不装任何插件&#xff0c;不写 Python 脚本&#xff0c;只用 Vim 的宏寄存…

作者头像 李华
网站建设 2026/9/7 4:53:42

用Python仿真理解调制解调:从AM、FM到BPSK误码率

通信技术发展至今已经超过一百年。从最早的有线电报&#xff0c;到后来的无线电广播&#xff0c;再到今天的 5G 移动通信和卫星互联网&#xff0c;设备形态和协议栈换了无数代&#xff0c;但通信系统要解决的核心问题始终只有一个&#xff1a;让信息可靠、高效地穿越信道&#…

作者头像 李华