FPGA实战:用Z80与8051软核构建可运行BASIC的复古计算机
1. 项目概述在FPGA上复活经典8位计算机如果你和我一样对上世纪七八十年代那些经典的8位计算机架构——比如Zilog Z80和Intel 8051——抱有浓厚的兴趣同时又对现代FPGA技术着迷那么这个项目绝对会让你兴奋。它不是一个简单的仿真器而是用一块实实在在的Intel MAX 10 FPGA开发板通过硬件描述语言HDL重新“铸造”出这两颗传奇CPU的软核并围绕它们构建出完整的、可以运行真实汇编和BASIC程序的单板计算机SBC。这就像用今天的先进制造工艺去复刻一台老式收音机的核心电路不仅能听还能亲手触摸和修改每一个“零件”。这个项目的核心价值在于“贯通”。它不仅仅是为了怀旧更是为了学习。通过从零开始或者说从已有的成熟软核开始在FPGA内部搭建一个包含CPU、内存、总线和外设的完整系统你能深刻理解计算机体系结构中最本质的“冯·诺依曼”或“哈佛”架构是如何在硅片上实现的。对于FPGA初学者这是从点亮LED到构建复杂数字系统的绝佳跳板对于嵌入式开发者这是透视单片机内部运作机制的X光机对于复古计算爱好者这是一台可以无限魔改的“时光机”。我选择Intel MAX 10 FPGA作为平台原因很实际它成本亲民开发工具链Quartus Prime成熟片上资源逻辑单元、存储块足够容纳这两个8位CPU及其基本外设而且引脚丰富便于扩展。下面我们就来深入拆解这个项目的设计思路、实现细节并分享我在复现过程中踩过的坑和总结的经验。2. 核心架构设计与软核选型考量2.1 为何是Z80和8051在开始动手之前首先要问为什么选择Z80和8051市面上开源的CPU软核很多从简单的8位到复杂的RISC-V。选择这两款首先是出于其历史地位和教育意义。Z80是早期个人电脑如ZX Spectrum, CP/M系统的心脏其指令集清晰寻址方式丰富是理解CISC复杂指令集架构的活教材。8051则是嵌入式领域的“常青树”其哈佛架构程序与数据存储器分开、特殊的SFR特殊功能寄存器和位寻址空间是单片机教学的基石。其次生态成熟。经过几十年的发展围绕这两款处理器的开源软核实现如T80 for Z80, 8051兼容核已经非常稳定和优化有大量的文档、示例程序和社区支持。这能让我们把精力集中在系统集成和应用上而不是从头调试一个CPU核。2.2 系统整体框图与模块划分无论是Z80 SBC还是8051 SBC其核心架构思想是一致的以CPU软核为中心通过总线或直接连接与各个外设模块通信。在FPGA内部我们可以用硬件逻辑精确地定义这些模块和它们之间的时序。Z80 SBC 典型架构CPU核心采用开源的T80软核。它完整实现了Z80指令集包括未公开的指令并且是同步设计易于在FPGA中集成。地址解码器这是系统的“交通警察”。Z80发出的16位地址线需要通过这个模块解码决定是访问ROM、RAM还是某个外设如UART、视频控制器。例如可以规划地址空间0x0000-0x1FFF为8KB ROM0x8000-0x8FFF为4KB RAM0xF0-0xF3为UART数据/状态端口。存储器ROM用于存放监控程序Monitor或BASIC解释器。在FPGA中通常用片上存储器M9K初始化成ROM或者从外部Flash加载。RAM作为系统的工作内存存放用户程序和变量。同样使用FPGA的M9K块实现。外设控制器UART实现串口通信用于与PC终端交互。需要实现波特率发生器、发送/接收移位寄存器。这是调试和交互的生命线。视频输出根据开发板资源可以是简单的VGA文本模式控制器。它从显存RAM中划出的一块区域读取字符转换成VGA时序信号输出。键盘接口连接PS/2键盘将扫描码转换为ASCII码供CPU读取。时钟与复位为整个系统提供稳定的时钟和可靠的复位信号。MAX10板载有晶振可以直接使用或通过内部PLL倍频/分频。8051 SBC 架构特点8051是哈佛架构其程序存储器ROM和数据存储器RAM在物理上是分开的地址空间独立。因此其架构略有不同CPU核心选用一个兼容Intel 8051指令集的开源软核。需要注意其是否是“12T”或“1T”核心即每条指令需要的时钟周期数这直接影响性能。程序存储器CODE存放BASIC-52解释器或用户程序。通常映射到独立的ROM空间。内部数据存储器IDATA128字节的片上RAM用于存放工作寄存器、堆栈和用户变量。外部数据存储器XDATA项目中使用的是16KB或32KB的外部RAM通过并行或SPI接口扩展用于存放更大的用户程序和数据。外设8051核通常已集成UART、定时器/计数器等基本外设。我们需要做的是将核的I/O端口P0, P1等映射到FPGA的物理引脚连接LED、按键等。对于I2C等高级功能需要额外实现IP核并挂接到总线上。注意在FPGA中实现这些模块本质上是用Verilog或VHDL描述它们的数字电路行为。例如一个简单的地址解码器可能就是一个大的case语句根据输入地址产生不同的片选信号。理解每个模块的时序要求如Z80的M1周期、读写时序8051的ALE信号是正确连接它们的关键。3. 硬件平台搭建与工程配置详解3.1 Intel MAX 10 FPGA开发板选型与资源评估我使用的是一块MAX 10 10M08SCM153C8G型号的开发板。我们简单算一下账看资源是否够用逻辑单元LEs约8000个。一个优化后的Z80 T80核大约消耗1200-1500个LEs一个8051核大约消耗1000-2000个LEs取决于实现。地址解码、UART、简单VGA控制器等外设模块每个可能在几百到一千LEs。所以同时实现两套系统资源紧张但分别实现绰绰有余。嵌入式存储器M9K这是实现RAM和ROM的关键。每块M9K是9Kbit实际8Kb数据1Kb校验。8KB ROM需要8块M9K8 * 8Kb 64Kb 8KB4KB RAM需要4块M9K。MAX 10 10M08型号通常有30多块M9K完全满足需求。锁相环PLL用于从板载12MHz晶振生成系统所需的各种时钟频率如50MHz for CPU, 25MHz for VGA。用户I/O充足用于连接串口、VGA、PS/2、LED和按键。实操心得在选择具体型号时一定要预留至少20%-30%的逻辑和存储资源余量以备调试和未来功能扩展比如增加声音芯片SD卡接口。资源利用率超过80%后布局布线的难度和时序收敛的风险会显著增加。3.2 Quartus Prime工程创建与关键设置新建工程打开Quartus Prime我用的18.1标准版与项目原作者一致避免兼容性问题指定工程目录、顶层实体名如z80_sbc_top。添加设计文件将下载的源码中所有.v或.vhd文件添加到工程。注意文件之间的编译顺序确保底层模块先于顶层模块被分析。器件选择在Device设置中精确选择你的FPGA型号如10M08SCM153C8G。这一步错了后续的引脚分配和编程都会失败。配置未使用引脚在Device - Device and Pin Options - Unused Pins中将未使用的引脚设置为As input tri-stated。这是一个非常重要的安全设置可以防止悬空引脚产生振荡电流导致芯片发热或不稳定。编译设置在Analysis Synthesis Settings中可以适当提高优化级别如Balanced或Performance但初期调试建议用Balanced以缩短编译时间。3.3 引脚分配与物理连接这是将FPGA内部逻辑与外部世界连接起来的一步。需要仔细阅读开发板原理图。时钟输入找到板载晶振连接的FPGA引脚如PIN_E1在Quartus的Pin Planner中将该引脚分配给顶层模块的clk_12m输入信号并指定其I/O Standard为3.3-V LVTTL。复位按键分配一个按键引脚给reset_n信号注意是低电平有效还是高电平有效并在代码中做相应的去抖动处理。UART找到板载USB转串口芯片如CH340连接的FPGA引脚将txd和rxd信号分配过去。VGA根据VGA接口的R、G、B各可能需要2-4位、HSYNC、VSYNC信号分配到对应的FPGA引脚。LED/按键分配剩余的I/O。踩坑记录我曾因为将VGA的HSYNC信号错误地分配到了一个被银行Bank电压配置为2.5V的引脚上而VGA接口需要3.3V电平导致显示器无法识别信号。务必在Pin Planner中检查每个引脚的I/O Bank和推荐的I/O Standard确保电平匹配。4. Z80单板计算机的深入实现与调试4.1 T80软核集成与内存映射配置项目使用了成熟的T80软核。将其集成到顶层模块中主要工作是正确连接其总线接口t80s cpu_core ( .RESET_n (~reset), // 复位低有效 .CLK (clk_sys), // 系统时钟如50MHz .WAIT_n (1‘b1), // 等待请求这里直接拉高表示不等待 .INT_n (1’b1), // 中断暂时不用 .NMI_n (1‘b1), // 非屏蔽中断暂时不用 .BUSRQ_n (1’b1), // 总线请求暂时不用 .M1_n (m1_n), // 机器周期1可用于外设识别 .MREQ_n (mreq_n), // 存储器请求 .IORQ_n (iorq_n), // I/O请求 .RD_n (rd_n), // 读信号 .WR_n (wr_n), // 写信号 .RFSH_n (rfsh_n), // 刷新信号DRAM相关此处可忽略 .HALT_n (halt_n), // 暂停状态输出 .BUSAK_n (busak_n), // 总线响应输出 .A (addr_bus), // 16位地址总线 .DI (data_in), // 8位数据输入 .DO (data_out) // 8位数据输出 );内存映射是设计的核心。我们需要编写一个地址解码模块根据addr_bus,mreq_n,iorq_n等信号产生各个存储器和外设的片选信号。always (*) begin rom_cs_n 1‘b1; ram_cs_n 1’b1; uart_cs_n 1‘b1; // ... 其他片选默认无效 if (!mreq_n) begin // 存储器访问 if (addr_bus 16‘h0000 addr_bus 16’h1FFF) // 8KB ROM rom_cs_n 1‘b0; else if (addr_bus 16’h8000 addr_bus 16‘h8FFF) // 4KB RAM ram_cs_n 1’b0; end if (!iorq_n) begin // I/O访问 if (addr_bus[7:0] 8‘hF0) // UART数据端口 uart_cs_n 1’b0; // ... 其他I/O端口 end end数据总线仲裁CPU、ROM、RAM、UART的数据输出线需要连接到一起data_in但同一时刻只能有一个设备驱动总线。需要通过三态门或选择器控制assign data_in (!rom_cs_n !rd_n) ? rom_data_out : (!ram_cs_n !rd_n) ? ram_data_out : (!uart_cs_n !rd_n) ? uart_data_out : 8‘hFF; // 默认上拉值4.2 串口UART通信模块的实现与测试一个最简单的UART发送器可以用一个移位寄存器实现。接收器稍复杂需要过采样来寻找起始位中点。波特率生成系统时钟50MHz要产生9600bps的波特率时钟。分频系数 50,000,000 / 9600 ≈ 5208。我们需要一个计数器计数到5208/2时产生一个采样脉冲。发送过程当CPU向UART数据端口写入时启动发送状态机。依次送出起始位0、8位数据位LSB first、停止位1。每个位占用一个波特率时钟周期。接收过程检测到rxd线从高变低起始位启动接收状态机。在每位的中点采样数据位存入移位寄存器直到收齐停止位将数据放入接收缓冲区并置位状态寄存器的“数据就绪”位。调试技巧初期可以不用连接PC串口而是写一个“回环测试”程序。让CPU通过UART发送一个固定字符如‘A’同时将UART的发送线txd直接连接到接收线rxd。在代码中CPU发送后延迟一段时间再去读取UART数据看是否收到相同的‘A’。这是验证UART底层逻辑是否正常的最快方法。4.3 监控程序Monitor与BASIC解释器加载一个“裸”的CPU上电后PC指针指向0x0000它需要执行指令。因此ROM的0x0000地址必须存放有效的机器码。通常这里存放一个简单的监控程序Monitor它初始化堆栈设置串口然后等待用户输入命令。更复杂的情况是直接存放一个BASIC解释器如Microsoft BASIC 4.7b。如何将程序放入ROM在Quartus中我们可以创建一个ROM的IP核使用M9K并指定一个.hex或.mif文件作为其初始化内容。这个文件就是我们的监控程序或BASIC解释器的二进制机器码。如何得到这个文件找到Z80的监控程序或BASIC的汇编源码项目开源库中通常提供。使用交叉汇编工具如zasm或sjasmplus在PC上将其汇编成二进制文件.bin。使用工具如bin2hex将.bin转换成Quartus可识别的.hex格式。在Quartus中为ROM IP核指定这个.hex文件路径。实操心得确保汇编时指定的起始地址ORG指令与你在FPGA中为ROM分配的地址空间完全一致。例如ROM物理地址从0x0000开始那么汇编源码的第一条指令就必须ORG 0000H。4.4 运行与交互从汇编到BASIC当一切就绪编译生成.sof文件并下载到FPGA后打开PC上的串口终端软件如Putty、Tera Term设置正确的COM口、波特率9600, 8N1。给FPGA开发板上电或按下复位键。终端上应该会打印出监控程序或BASIC的提示符如“”或“OK”。此时你可以输入汇编指令进行机器码级的调试或者直接输入BASIC程序就像操作一台真正的80年代电脑一样。项目提供的BASIC计数程序OUT 145, 255-I就是一个很好的测试。它通过I/O端口控制外部LED如果连接了的话你可以看到LED的亮灭规律变化。更复杂的ASCII Art程序则考验了系统的浮点运算BASIC解释器软件实现和输出性能。5. 8051单板计算机的实现差异与要点5.1 哈佛架构与存储器接口设计8051软核的实现与Z80有显著不同。最大的区别在于其哈佛架构程序存储器ROM和数据存储器RAM有独立的地址空间和控制信号。程序存储器接口8051核通过psen_n程序存储使能信号来读取指令。我们需要设计一个ROM控制器当psen_n有效且地址在范围内时将对应地址的指令数据放到data_bus上。数据存储器接口分为内部RAM128字节直接集成在核内和外部RAM通过rd_n,wr_n,ale等信号访问。对于外部RAM我们需要实现一个类似于Z80系统中的总线仲裁和读写控制器。关键点很多开源8051软核为了简化会将外部RAM也映射到与程序存储器相同的物理RAM块但使用不同的控制信号区分。在FPGA中这意味着一块M9K存储器需要支持两套独立的访问接口一套给psen_n一套给rd_n/wr_n这可以通过双端口RAM IP核来实现。5.2 BASIC-52解释器的集成与时钟配置项目提供了两个预配置的镜像核心区别在于集成的BASIC-52解释器版本和时钟频率。8kB ROM 32kB RAM 50MHz使用较基础的BASIC-52 1.1版本不支持I2C。时钟通过MAX10内部PLL从12MHz倍频到50MHz性能极高。16kB ROM 16kB RAM 11.0592MHz使用功能更全的BASIC-52 1.31版本支持I2C扩展。时钟频率设为11.0592MHz这是一个非常经典的频率因为它可以精确地分频出标准的串口波特率如9600, 115200保证串口通信无误差。如何选择如果你需要连接I2C设备如EEPROM、传感器必须选择11.0592MHz的版本。如果追求极致的BASIC程序运行速度如计算密集型图形50MHz版本优势明显。需要注意的是BASIC-52中的XTAL变量必须设置为实际的系统频率单位Hz其内部延时循环和串口波特率计算依赖于此值。5.3 I/O端口映射与外围设备驱动8051的P0、P1、P2、P3端口在软核中通常是作为一组输出寄存器存在。我们需要在顶层模块中将这些寄存器输出到FPGA的物理引脚。// 假设8051核输出一个8位寄存器 port1_out always (posedge clk) begin if (reset) begin led_reg 8‘hFF; // LED低电平点亮初始全灭 end else begin led_reg ~port1_out; // 取反后驱动LED end end assign led_pins led_reg; // 连接到板载LED项目中的LED流水灯程序就是通过BASIC语言向PORT1寄存器写入不同的值来实现的。PORT1 0FFH.XOR.LED这行代码利用了8051 BASIC的位操作功能实现了LED的走马灯效果。6. 项目调试、问题排查与性能优化6.1 常见问题与诊断流程在实现这类项目时问题可能出在任何环节。以下是一个系统性的排查思路系统完全无反应串口无输出检查电源和时钟用示波器测量FPGA的供电电压和时钟输入引脚确认是否有稳定的12MHz时钟信号。检查复位电路确保复位信号在上电后有一个从低到高的稳定跳变。可以在代码中让一个测试LED随复位信号闪烁进行肉眼判断。检查编译报告查看Quartus的“Compilation Report”确认没有严重的时序违规Timing Violation特别是建立时间Setup Time和保持时间Hold Time。使用SignalTap II逻辑分析仪这是FPGA调试的利器。在代码中嵌入SignalTap抓取CPU的地址总线、数据总线、控制总线如mreq_n,rd_n以及关键模块的使能信号。看看上电后PC指针是否从0x0000开始递增是否发出了第一个ROM读请求。串口有乱码或数据错误确认波特率检查终端软件、UART模块的波特率设置是否完全一致。计算分频系数时是否考虑了系统时钟误差。检查时序用SignalTap抓取UART的txd信号测量位宽。在50MHz系统时钟下9600bps的位宽应该是50,000,000 / 9600 ≈ 5208个时钟周期。如果偏差较大说明波特率发生器设计有误。电平转换确认FPGA引脚的电平标准3.3V LVTTL与USB转串口芯片的电平是否匹配。有些老式串口是RS-232电平±12V需要电平转换芯片。BASIC程序运行异常或死机内存映射错误这是最常见的原因。CPU试图访问一个没有设备响应的地址总线会浮空读回的数据不确定导致程序跑飞。用SignalTap确认CPU的访问地址是否都在你定义的ROM、RAM和外设地址范围内。堆栈溢出Z80和8051的堆栈都是向低地址生长的。如果初始化时堆栈指针SP设置得太靠近RAM底部或者程序递归/中断调用太深就会破坏RAM中的程序或数据。确保SP初始化在RAM区域的较高地址如0x8FFF for Z80。中断冲突如果项目启用了中断但中断服务程序ISR没有正确编写或中断向量表地址错误也会导致不可预测的行为。初期建议禁用所有中断。6.2 性能优化与扩展思路当基本系统运行稳定后可以考虑优化和扩展提升CPU性能时钟频率在满足时序约束的前提下尝试通过PLL提高系统时钟频率。MAX 10在50-100MHz范围内通常能稳定运行。使用缓存为ROM或RAM添加一个简单的高速缓存Cache可以显著减少CPU等待时间尤其对于循环密集的BASIC程序效果明显。扩展外设PS/2键盘实现一个PS/2解码模块将扫描码转换为ASCII码并通过中断或查询方式告知CPU实现真正的键盘输入。VGA图形模式在文本模式基础上实现一个位图模式的帧缓冲区让BASIC能够绘制像素图形。SD卡存储通过SPI接口连接SD卡实现文件系统让BASIC程序可以保存和加载。声音输出增加一个简单的PWM音频模块让计算机能发出蜂鸣声甚至播放简单的音乐。软件生态丰富除了BASIC可以尝试移植更复杂的系统比如为Z80移植一个精简版的CP/M操作系统或者为8051移植一个微型的RTOS如FreeRTOS。这将把项目从单板计算机推向更接近实际应用的操作系统层面。这个项目的魅力在于它既是一个对计算机历史的致敬也是一个绝佳的现代数字系统设计实践平台。从一颗软核CPU开始到最终构建出一个能与人交互的完整系统每一步都充满了挑战和乐趣。当你第一次在终端上看到自己用BASIC写的程序控制着LED闪烁或者在VGA显示器上显示出字符时那种成就感是无可比拟的。它打通了软件与硬件之间的那层抽象让你真正理解了从按键到屏幕显示电流与代码是如何协同工作的。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2622838.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!