
简介面向FPGA学习者与数字逻辑课程设计者这份压缩包提供了基于Altera DE2开发板的OC8051软核处理器点灯实验完整方案解决如何将开源8051核移植到DE2并驱动LED的问题。压缩包共1042个文件以Quartus工程文件.qpf、.qsf、Verilog/VHDL源码.v、.vhd、TDF文件、MIF初始化文件及SOF/POF配置文件为主同时包含大量仿真波形与工程备份文件整体约19.07MB。已有1026人学习下载。包内资源包括详细说明文档与完整Quartus工程文档专门介绍了OC8051内部ROM的修改方法并附带一段LED灯测试程序可通过该程序初始化ROM使OC8051在DE2上直接运行点灯任务。读者不仅能快速复现实验还能借此理解CPU软核的存储器初始化流程与DE2平台外设接线方式适合作为FPGA入门或计算机组成原理课程实验的参考。1. 为什么放着Nios II不用非要在DE2里塞一颗8051看到“DE2上使用OC8051运行点灯程序”这个标题先别急着觉得复古。我最初在DE2上折腾OC8051起因很朴素手里那块Cyclone II开发板在吃灰丢掉又可惜真去跑Nios II又得开SOPC Builder、配置Qsys一堆自动生成的东西像个黑盒。点灯虽然简单但我想看的是“CPU取指、译码、写寄存器”这套流程真的在一块FPGA里跑起来。OC8051是OpenCores社区里一个用Verilog实现的8051兼容软核。它没有Cache没有分支预测甚至不少指令要多个周期才能执行完但它有一个很实在的优点DE2上的Cyclone II EP2C35塞下它绰绰有余综合完只占几千个LE。C代码里那个P1寄存器、0x90这个SFR地址全都看得见摸得着。跑一遍“点灯”等于是把计算机组成原理课上那些概念亲手在硬件里验证了一遍。1.1 软核不是“软件”而是用逻辑拼出来的CPU软核软核很多人听到这个词就以为是一个跑在FPGA上的软件程序实际不是这么回事。软核是一段硬件描述语言代码综合之后会变成FPGA里的查找表、触发器和布线资源。你可以把FPGA看作一块毛坯房软核就是你自己用砖头砌出来的一个小户型。而DE2板上那颗Cyclone II就是这块毛坯房的地基。OC8051这类8位软核放在今天性能当然不够看但它足够简单、足够透明。打开oc8051_decoder.v你能看到一条MOV指令是怎么被译码的打开oc8051_sfr.v你能看到特殊功能寄存器是怎么被读写的。这种透明感是Nios II给不了的。Nios II当然更强大但它的生成流程会替你包一层“封装”你想看内部逻辑得先去翻那一堆自动生成的BSP代码。1.2 为什么偏偏是OC8051它到底能干什么8051是很多人学习单片机的启蒙架构教材多、参考资料多、C编译器成熟。OC8051的作用是把这颗老芯片重新用Verilog“翻译”了一遍于是你可以在任何一块足够大的FPGA里随时重建一个8051环境。对DE2来说最经典的入门验证就是点灯让CPU启动从ROM里取出第一条指令执行对P1口的写操作LED亮起来证明整条链路是通的。这个项目适合两类人。一类是学FPGA但一直没搞懂CPU内部结构的人另一类是学嵌入式想看看“8051在硬件层面是什么样子”的人。知识点虽然不新但它的价值在于动手从RTL源码、C代码、编译链到上板验证整个过程没有黑盒。2. 开工前的三件事Quartus版本、源码清点、编译链装齐OC8051这个项目不太挑工具但DE2这块板子很挑版本。Cyclone II是Altera很早的器件Quartus新版本早就把它踢出支持列表了。我建议直接装Quartus II 13.0sp1这是最后一个完整支持Cyclone II的版本既能写Verilog也能用SignalTap II调试整个流程不用来回切换工具。版本选错后面连器件型号都选不到更别说综合下载了。2.1 Quartus版本与USB-Blaster驱动Quartus II 13.0sp1是2013年左右的版本安装包比较大但好在稳定。装完之后第一件事是确认USB-Blaster驱动是否识别。DE2板上的下载器就是USB-Blaster如果驱动没装好Programmer窗口里永远是空的。Windows系统下可以在设备管理器里手动指定驱动路径路径一般在Quartus安装目录下的drivers文件夹里。这块不算难但它是整个流程里最容易让人卡在第一步的地方。2.2 OC8051源码的清点与版本差异从OpenCores官网下载oc8051工程包解压后不要急着往Quartus里拖先把目录结构看清楚。常见会有doc、rtl、bench这样几个目录核心Verilog文件一般在rtl/verilog下面典型包括oc8051_top.v、oc8051_alu.v、oc8051_control.v、oc8051_decoder.v、oc8051_ram.v、oc8051_rom.v、oc8051_sfr.v这些。不同版本文件命名略有差异但主体结构差不多。我特别想提醒一件事OC8051这个项目已经很多年没怎么维护了代码风格偏老有些仿真专用的文件在上板综合时根本用不到比如原本用于测试的ROM模型、带$fopen/$display语句的仿真文件。建工程的时候只添加核心RTL文件把bench目录整个跳过。否则仿真文件里的系统函数会在综合时报错或者被Quartus当成普通逻辑处理白白增加工作量。2.3 8051编译链SDCC还是Keil C51点灯程序很小选哪个编译器都行。SDCC是完全免费的Linux下sudo apt install sdcc一条命令装好Windows下也有安装包。Keil C51是老牌商业工具工程配置比较顺手Keil生成HEX文件需要在Options for Target的Output选项卡里勾选Create HEX File。我个人更推荐SDCC因为它命令行干净编译点灯程序这种小工程不需要图形界面而且最后生成的Intel HEX格式和Quartus的初始化文件能很好配合。SDCC编译方式也简单后面章节我会直接给出命令。3. 程序怎么进FPGAHEX与片上ROM的完整关系很多第一次接触软核的人会卡在这里C代码编译出来的HEX文件到底怎么变成FPGA里的程序这得从8051的存储器结构说起。8051是典型的哈佛结构指令放在程序存储器ROM里数据放在数据存储器RAM里两者分开。OC8051取指时会用16位的rom_addr信号去ROM对应地址读一个字节回来交给译码器处理。3.1 程序ROM的深度别一上来就建64KB点灯程序编译出来的HEX通常只有几百字节但8051的地址总线是16位的理论寻址空间是64KB。新手最容易犯的错就是在Quartus里直接建一个64KB的ROM——然后资源直接爆炸。DE2上的Cyclone II EP2C35内部块RAM总共也就一百个左右M4K一个M4K是4Kb换算下来64KB的8位ROM大约需要一百多个M4K根本放不下。正确做法是先看HEX文件的最后一条有效记录地址。比如程序实际只到0x01FF那就建深度2048的ROM8位数据总线总共只占4个M4K。点灯程序留出2KB空间已经非常宽裕以后想加点复杂的逻辑也有余量。3.2 两种加载方式MIF文件与Verilog $readmemh把HEX变成ROM初始化内容有两条路。第一条是用Quartus的IP Catalog生成一个ROM核选择单端口ROM位宽8、深度2048然后在初始化文件那里加载MIF文件。MIF文件是Quartus自己的存储器初始化格式长这样WIDTH8; DEPTH2048; ADDRESS_RADIXHEX; DATA_RADIXHEX; CONTENT BEGIN 0000:02; 0001:01; 0002:02; END;第二种方式更省事直接用Verilog内嵌一个二维reg数组配合$readmemh把HEX读进去reg [7:0] rom [0:2047]; initial $readmemh(led.hex, rom);Quartus对这个写法支持得不错综合时会自动把rom数组推断成块RAM。需要注意$readmemh的路径问题最好把led.hex放在工程根目录路径不对综合不会报错但下载到板上程序是空的。3.3 Intel HEX转MIF的一个小脚本思路如果坚持用MIF方式又不想手工敲每一行的数据可以写个Python小脚本做转换。解析Intel HEX的核心逻辑并不复杂每行以冒号开头后面是长度、地址、类型、数据和校验和。只需要收集类型为00的数据记录按地址填入字典最后输出成MIF格式就行。重点留意地址偏移HEX里的地址就是最终ROM地址不需要再额外加偏移但如果你用了带起始地址的链接脚本一定要确认生成HEX的基地址是不是0x0000。4. 把点灯程序接到DE2的LEDSFR映射、顶层模块与引脚约束程序能进ROM了接下来就是软核上板最关键的部分CPU写P1口FPGA怎么知道该把哪个LED点亮。4.1 先写一个最精简的C点灯程序用SDCC写点灯程序头文件是8051.h代码很短#include 8051.h void delay(unsigned int t) { unsigned int i; while (t--) { for (i 0; i 500; i); } } void main(void) { unsigned char v 0x01; while (1) { P1 v; delay(50); if (v 0x80) v 0x01; else v 1; } }这段代码逻辑很简单P1口循环输出0x01、0x02、0x04……到0x80实现流水灯效果。编译命令sdcc led.c packihx led.ihx led.hex生成led.hex后可以文本打开看一眼确认第一行不是全零说明程序内容已经生成。注意8051.h里已经声明了P1这个sfr寄存器所以编译期不会出问题真正要花心思的是硬件部分怎么把这个P1写操作“接”出来。4.2 方案一从SFR总线里把P1口抠出来OC8051内部访问SFR时会有一组和SFR读写相关的控制信号不同版本信号名有差异常见的是sfr_wr、sfr_addr、sfr_data_out。P1口在8051里的SFR地址是0x90你需要写一个小的地址译码模块监听这组信号always (posedge clk or negedge rst_n) begin if (!rst_n) led_r 8h00; else if (sfr_wr sfr_addr 8h90) led_r sfr_data_out; end不过有个现实问题某些OC8051版本并没有把这组SFR访问信号完整引到oc8051_top顶层而是封装在oc8051_sfr.v内部。这时候要么去改源码把信号引出来要么换方案二。我最早折腾的时候就是被这个“信号没引出来”卡了一晚上。如果你下载的版本同样没有sfr_wr端口不用纠结直接看4.3。4.3 方案二走XDATA外部总线更通用也更省事我后来更推荐的方式是让C程序访问外部XDATA地址在硬件端监听CPU的MOVX写总线。8051的外部数据总线一般有mem_wr、mem_addr、mem_data_out这样一组信号OC8051为了支持外扩RAM基本都会保留这组接口。C代码改成这样#define LED_PORT (*(volatile unsigned char __xdata *)0x8000) void main(void) { unsigned char v 0x01; while (1) { LED_PORT v; delay(50); if (v 0x80) v 0x01; else v 1; } }硬件端则监听地址0x8000上的写操作always (posedge clk or negedge rst_n) begin if (!rst_n) led_r 8h00; else if (mem_wr mem_addr 16h8000) led_r mem_data_out; end这个方法的好处是只要OC8051的MOVX指令能跑通外部总线就一定能用。8051访问XDATA天然就是走这套接口的你不再需要关心SFR总线到底有没有引出来。无论是SDCC还是Keil对XDATA地址访问都有成熟支持移植性很好。4.4 顶层模块的关键片段顶层模块里除了CPU和地址译码还需要处理时钟和复位。我习惯先把50MHz分频到6MHz左右降低时序压力复位则用一个计数器做上电复位避免按键复位的毛刺影响reg [2:0] clk_div; wire clk clk_div[2]; always (posedge clk_50m or negedge key0_n) begin if (!key0_n) clk_div 3d0; else clk_div clk_div 1b1; end reg [19:0] rst_cnt; reg rst_n; always (posedge clk or negedge key0_n) begin if (!key0_n) begin rst_cnt 20d0; rst_n 1b0; end else if (rst_cnt) begin rst_n 1b1; end else begin rst_cnt rst_cnt 1b1; rst_n 1b0; end end引脚约束方面DE2的时钟是CLOCK_50复位可以用KEY0LED就用LEDR。这些引脚的具体编号不用自己记Quartus里通过Assignments菜单导入DE2官方Demo工程里的引脚分配文件或者用Pin Planner手动点一遍。最忌讳的是自己瞎猜引脚号压错一个pin现象就是灯怎么都不亮但你又不知道问题出在程序还是引脚。5. 上板实测灯不亮的排查顺序和几个隐蔽的坑前面流程全部走通下载SOF文件后理论上LEDR0开始逐个点亮。如果灯没亮先别急着怀疑代码按下面这个顺序排查。5.1 先隔离问题不接CPU先把LED链路调通我个人的习惯是第一次上板先不接OC8051直接在顶层里写一行assign led_r 8b10101010;烧进去看一下LED是不是亮成间隔模式。如果这一步都不对说明引脚分配、下载链路、LED电路本身有问题跟软核一毛钱关系没有。这一步做完再换成CPUROM的完整设计能省掉一大半的排查时间。5.2 灯不亮的典型故障链路如果固定值能亮接上OC8051之后不亮问题基本就锁在几个点第一ROM初始化文件没加载进去。$readmemh的路径不对或者MIF文件的地址范围小于HEX中最大地址都会让CPU从0x0000取到的指令是空数据。可以用Quartus里的RTL Viewer查看ROM模块确认是否有初始化标志。更彻底的办法是先用ModelSim跑一次行为仿真直接看PC寄存器有没有走动。第二复位极性搞反了。OC8051的复位信号在不同版本里可能是高有效也可能是低有效接反了CPU永远停在复位状态。看源码中复位逻辑比对着改顶层。第三XDATA地址判断差了一位。比如C代码写0x8000但顶层判断0x8001。这类问题隐蔽性很强因为逻辑看起来完全正确但8051的XDATA总线地址在时序上可能延后一拍或者经过锁存后变化。用SignalTap II抓一下mem_addr一眼就能看出来。第四分频时钟没跑起来。DE2的50MHz时钟直接给OC8051理论上也能跑但老代码时序余量不大我遇到过一跑就乱、一降频就稳的情况。分频到6.25MHz之后整个系统稳定得多点灯这种程序完全不需要高速。5.3 一个值得养成的调试习惯最后分享一个我自己的习惯在ROM里固定写一条跳转指令比如地址0x0000处放一条LJMP 0x0100然后用SignalTap II抓PC信号。如果复位后PC能从0x0000跳到0x0100说明取指、ROM、复位全部正常。这时候再跑点灯程序写不过去只能说明是C语言编译或者外设映射的问题。这个分步验证的思路比直接烧完整点灯程序再瞎猜要省时间得多。软核移植这件事难点往往不是Verilog语法而是“信号明明存在但你没找到它或者找错了位置”。OC8051这种老工程更是这样。把ROM、复位、时钟、地址四条线理清楚点灯程序跑起来其实也就是一个下午的事。后续你还可以给它加串口、加定时器甚至用AXI总线把它包起来当个协处理器玩起点都是这份点灯工程。本文还有配套的精品资源点击获取