拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于FPGA的Z80单板计算机设计与实现:从软核到完整系统

1. 项目缘起为什么要在FPGA上复刻Z80 SBC作为一名老派的嵌入式开发者和复古计算爱好者我总对那些经典的8位微处理器架构抱有特殊的情感。Zilog Z80这颗诞生于上世纪70年代的CPU驱动了无数经典的计算机和游戏机比如TRS-80、ZX Spectrum、MSX甚至是任天堂的Game Boy。它的指令集简洁而强大是理解计算机底层运作的绝佳标本。然而时至今日想要亲手焊接一块Z80单板计算机SBC并不容易。原装的Z80芯片虽然还能买到但配套的ROM、RAM、地址锁存器、时钟电路等外围芯片要么已经停产要么价格不菲更别提复杂的PCB设计和焊接过程了。这让我萌生了一个想法能不能用一种更现代、更灵活的方式来“复活”Z80系统答案就是FPGA。FPGA现场可编程门阵列本质上是一块空白的数字电路我们可以通过硬件描述语言如Verilog或VHDL在它内部“绘制”出我们想要的任何数字电路包括一个完整的Z80 CPU及其外围系统。我手头正好有一块搭载了Altera Cyclone IV EP4CE6 FPGA的开发板成本低廉资源也足够。于是一个清晰的目标诞生了在这块FPGA上从零开始构建一个完整的、可运行的Z80单板计算机系统。这不仅仅是简单的模拟而是用数字逻辑在硅片上真实地重构一个计算机系统你可以把它理解为一台“虚拟的硬件”。这个项目的魅力在于它横跨了硬件设计、计算机体系结构和软件编程多个领域。你不仅需要理解Z80的时序、总线周期还要设计内存映射、IO接口最后甚至要为这个“新机器”编写引导程序和应用程序。整个过程充满了挑战但每一步的成功都让人成就感十足。接下来我将详细拆解我是如何一步步实现这个目标的其中包含大量的设计决策、调试技巧和踩坑经验。2. 核心架构设计从CPU内核到完整系统在FPGA上构建一个完整的计算机系统远不止是放一个CPU核那么简单。你需要构建一个围绕CPU的、能够协同工作的“数字世界”。我的目标是构建一个最小化但功能完整的SBC其核心架构如下图所示在脑海中构想Z80 CPU作为核心通过三态总线与一块ROM存放监控程序、一块RAM存放用户程序和数据以及一个简单的并行IO接口相连。整个系统的运行依赖于一个精准的时钟源和复位电路。2.1 Z80 CPU软核的选择与集成首先也是最核心的部分就是Z80 CPU本身。在FPGA里我们有几种选择从头开始用Verilog编写一个Z80这是一个巨大的工程需要对Z80的微架构和每条指令的时序有极其深刻的理解虽然极具教育意义但不符合我快速构建一个可运行系统的目标。使用开源的Z80兼容软核这是最务实的选择。经过调研我选择了由T80项目衍生的一个版本。它是一个经过充分验证、代码质量较高的开源IP核用VHDL编写完全兼容Z80指令集。将软核集成到项目中第一步是将其转换成你的设计工具我使用的是Quartus Prime Lite可用的模块。对于VHDL编写的T80核我需要创建一个对应的Verilog封装模块Wrapper以便在我的顶层Verilog设计中实例化它。这个封装模块主要的工作就是定义与T80核所有信号的对等接口。关键信号包括clk和reset_n时钟和低电平有效的复位信号。address[15:0]16位地址总线可寻址64KB空间。data_in[7:0]和data_out[7:0]分离的8位数据输入和输出总线。注意Z80实际是双向数据总线但T80核将其分开了这简化了外部总线仲裁的设计。wr_n,rd_n,mreq_n,iorq_n控制总线信号分别表示写使能、读使能、存储器请求和IO请求均为低电平有效。wait_n,int_n,nmi_n等待、中断和不可屏蔽中断信号。注意不同的Z80软核在接口细节上可能有差异。例如有些核可能使用双向的data[7:0]端口。务必仔细阅读所选软核的文档或代码注释理解其接口时序和要求。2.2 存储器子系统的设计与地址解码一个最简单的Z80系统需要两种存储器非易失性的ROM用于存放固件监控程序以及易失性的RAM供程序运行时使用。在FPGA内部我们可以用Block RAM块RAM资源来模拟这两种存储器。地址空间规划 我采用了经典的分段映射方式简单而有效。0x0000 - 0x1FFF(8KB): 分配给ROM。这是Z80复位后开始取指令的地址PC从0x0000开始所以必须把启动代码放在这里。0x8000 - 0xFFFF(32KB): 分配给RAM。将RAM放在高地址区域是一种常见做法为未来扩展低地址空间的ROM或内存映射IO留出余地。地址解码器 CPU每次发出地址系统都需要判断这个地址是访问ROM、RAM还是IO设备。我使用一个简单的组合逻辑解码器来实现。// 简单的地址解码逻辑示例 wire rom_select (address 16‘h0000) (address 16’h1FFF) !mreq_n; wire ram_select (address 16‘h8000) (address 16’hFFFF) !mreq_n; wire io_select (address[7:0] 8’h00) !iorq_n; // 例如解码IO端口0x00双向数据总线处理 由于T80核提供了分离的数据输入输出线而外部存储器是双向端口我需要一个总线控制器。其核心逻辑是当CPU执行写操作时wr_n为低将data_out驱动到总线上当执行读操作时rd_n为低将对应设备的数据送到CPU的data_in上其他时候CPU的数据输出端应呈高阻态。// 双向总线控制逻辑简化示意 wire [7:0] rom_data_out; wire [7:0] ram_data_out; wire [7:0] io_data_out; // 数据输入多路选择 assign data_in (rom_select rd_n) ? rom_data_out : (ram_select rd_n) ? ram_data_out : (io_select rd_n) ? io_data_out : 8‘hFF; // 默认值 // 数据输出使能及写入 always (posedge clk) begin if (rom_select !wr_n) begin // ROM通常不可写这里忽略或产生错误信号 end if (ram_select !wr_n) begin ram_data_in data_out; // 将CPU数据写入RAM ram_we 1‘b1; end else begin ram_we 1’b0; end // ... 类似处理IO写入 end2.3 时钟与复位电路的设计考量时钟Z80的原始时钟频率范围很广。为了稳妥起见我最初使用了一个通过FPGA内部PLL生成的2MHz时钟。这个频率足够低能确保所有逻辑稳定工作也方便用逻辑分析仪观察信号。在验证系统稳定后可以逐步提高时钟频率。Cyclone IV FPGA内部的逻辑完全可以跑在几十MHz但Z80软核和内存访问逻辑可能会成为瓶颈需要后续优化。复位可靠的复位至关重要。我设计了一个上电复位电路结合一个按键手动复位。在Verilog中我通常使用一个计数器来产生一个足够长的复位脉冲例如持续16个时钟周期以确保CPU和所有状态机都能稳定地进入初始状态。reg [15:0] reset_counter 16‘hFFFF; wire sys_reset_n (reset_counter 16’h0000); always (posedge clk or posedge external_reset) begin if (external_reset) begin reset_counter 16‘hFFFF; end else if (reset_counter ! 16’h0000) begin reset_counter reset_counter - 1; end end3. 外设接口与“看见”系统运行一个没有输入输出的计算机就像没有显示器的电脑我们无法与之交互。为了验证CPU是否真的在运行程序我必须添加最基本的外设。3.1 并行IOPIO接口的实现我实现了一个极其简单的8位并行输出端口映射到IO地址0x00。CPU向这个端口写入的数据会被锁存并输出到FPGA的引脚上我就可以用这些引脚来驱动LED灯。这是最直观的调试方式。reg [7:0] output_port; always (posedge clk) begin if (io_select !wr_n) begin // 当IO写操作且地址匹配时 output_port data_out; // 锁存CPU写入的数据 end end assign led_pins output_port; // 连接到FPGA板载LED同时我也可以实现一个简单的8位输入端口映射到0x01连接到拨码开关或按键上这样CPU就能读取外部状态。3.2 监控程序Monitor的编写与烧录硬件搭建好了还需要软件才能让它“活”起来。我需要在ROM里放置一段监控程序。这段程序通常用Z80汇编语言编写它的功能包括初始化设置栈指针SP。主循环通过串口后续可扩展或我们刚刚实现的LED输出端口提供一个简单的用户接口。例如让LED循环闪烁或者根据开关状态改变LED模式。我使用了一个开源的Z80汇编器如zasm或vasm来将汇编代码编译成二进制机器码。然后这个二进制文件需要被转换成Quartus能够识别的存储器初始化文件格式.mif或.hex。关键步骤编写一个简单的LED闪烁汇编程序。ORG 0000h ; 程序从地址0开始即ROM起始地址 LD SP, 0FFFFh ; 设置栈指针到RAM顶端 MAIN_LOOP: LD A, 55h ; 加载模式01010101到A寄存器 OUT (00h), A ; 输出到IO端口0x00我们的LED端口 CALL DELAY ; 调用延时子程序 LD A, 0AAh ; 加载模式10101010 OUT (00h), A CALL DELAY JP MAIN_LOOP ; 跳回循环开始 DELAY: LD BC, 0FFFFh ; 延时循环 DELAY_LOOP: DEC BC LD A, B OR C JP NZ, DELAY_LOOP RET用汇编器生成.bin文件。使用工具如bin2mif或编写脚本将.bin文件转换为.mif文件。在Quartus中将ROM IP核的初始化内容指向这个.mif文件。编译整个FPGA工程生成.sof文件通过JTAG下载到FPGA中。3.3 调试手段逻辑分析仪与嵌入式逻辑当系统不工作时调试是最大的挑战。我强烈推荐使用FPGA开发工具内置的逻辑分析仪功能如Quartus的SignalTap II。它可以实时捕获FPGA内部任何信号的波形就像一台示波器。我设置了SignalTap来捕获关键信号clk,reset_n,address,data_in,data_out,mreq_n,rd_n,wr_n以及rom_select和ram_select。通过观察波形我可以清楚地看到复位后CPU是否开始从0x0000地址取指mreq_n和rd_n是否在地址为0时有效数据总线上是否出现了预期的指令码来自ROMCPU是否执行了OUT指令iorq_n和wr_n是否在对应IO地址时有效LED输出寄存器的值是否按预期变化实操心得在调试初期将时钟频率设得非常低比如1Hz然后单步观察SignalTap波形是理解系统行为、定位问题的最有效方法。问题往往出在地址解码错误、时序冲突如读写信号与数据稳定时间不匹配或复位逻辑不可靠上。4. 系统验证、优化与功能扩展当LED按照汇编程序的设计开始闪烁时那一刻的兴奋难以言表。但这只是第一步一个完整的SBC还需要更强大的功能和稳定性。4.1 基础功能验证与稳定性测试首先我编写了更复杂的测试程序例如RAM测试向RAM区域写入一系列已知模式如0xAA 0x55 递增序列然后再读回比较通过LED或后续的串口报告结果。这验证了RAM读写和数据总线通路是否正确。中断测试Z80的中断机制非常经典。我设计了一个简单的定时器中断源可以用FPGA内部的计数器实现。验证CPU能否正确响应中断跳转到中断服务程序ISR并在ISR中操作LED最后用RETI指令返回。稳定性测试则包括长时间运行测试程序以及在各种时钟频率下从1MHz逐步提升到10MHz甚至更高运行观察是否会出现偶发性错误。这能排查时序约束Timing Constraints是否设置正确。在Quartus中需要通过.sdc文件为时钟信号和关键输入输出设置时序约束工具才能进行静态时序分析STA确保电路在指定频率下能稳定工作。4.2 添加UART串行通信接口LED和开关的交互能力太弱。为了能与SBC进行真正的“对话”添加一个UART通用异步收发传输器接口是必须的。我实现了一个简单的UART发送器映射到另一个IO端口例如0x02。这样监控程序就可以将字符发送到PC通过串口调试助手如Putty、Tera Term显示出来。实现思路在FPGA内部创建一个波特率发生器例如生成9600bps的时钟和一个并串转换的状态机。当CPU向UART数据端口写入一个字节时状态机自动将其加上起始位、停止位按位串行输出到FPGA的某个引脚再通过一个USB转TTL串口模块连接到电脑。有了输出自然也要有输入。再实现一个UART接收器CPU可以轮询状态端口或通过中断来读取接收到的字符。这样一来一个最简单的命令行交互界面就成为可能电脑发送字符Z80程序接收并回显甚至可以实现简单的内存查看、修改命令这才像一个真正的“监控程序”。4.3 内存映射与高级外设构想基本的SBC搭建完成后FPGA的灵活性优势就凸显出来了。你可以发挥创意添加各种“硬核”外设而这些在原版Z80时代可能需要多块芯片才能实现。视频输出可以设计一个VGA或复合视频控制器将一块FPGA RAM作为显存。Z80程序通过读写特定内存区域内存映射IO来绘制图形或显示文字。这相当于在Z80系统中集成了一个强大的显卡。音频输出用PWM或Delta-Sigma调制生成简单的方波或更复杂的音频通过IO端口控制频率和音量重现8位机的经典音效。SD卡接口通过SPI协议实现一个SD卡读写器为你的Z80 SBC提供一个海量的外部存储系统可以用来加载更大的程序或数据。与软核“套娃”你甚至可以在FPGA里再实例化一个更现代的处理核如RISC-V让Z80和RISC-V通过共享内存或消息队列进行通信构建一个异构多核系统这纯粹是为了探索和学习的乐趣。5. 项目总结与核心收获回顾整个“Z80 SBC on a Cyclone IV FPGA”项目它远不止是让一个老CPU重新运行起来。它是一个完整的、从门电路级别向上构建计算机系统的实践。在这个过程中我深刻体会到了几个关键点首先对计算机体系结构的理解从抽象变得具体。地址总线、数据总线、控制总线不再是书本上的三条线而是你代码里的一组组信号它们的每一次跳变都对应着CPU的一次具体操作。内存映射、IO编址、中断响应这些概念都变成了可观察、可调试的逻辑行为。其次硬件描述语言设计是“并行思维”的锻炼。与编写顺序执行的软件不同你需要时刻考虑所有逻辑模块是同时工作的对时序的要求极其严格。一个always块里的非阻塞赋值和阻塞赋值用错就可能导致整个系统行为异常。再者调试能力至关重要。在看不见摸不着的FPGA内部逻辑分析仪SignalTap是你的眼睛。学会设置触发条件、解读波形图是定位问题的唯一途径。常见的坑包括未初始化的寄存器、组合逻辑环路、时序违例以及地址解码的重叠或漏洞。最后这是一个充满成就感的创造过程。从一堆代码和配置文件开始到最终LED灯按照你几十年前设计的指令集闪烁再到通过串口与你亲手编写的监控程序对话这种将想法逐层变为现实的感觉是单纯购买成品开发板无法比拟的。这个项目就像一个微缩的计算机研发过程。它适合所有对计算机底层感兴趣的人无论是想重温经典架构的老程序员还是希望夯实数字逻辑和体系结构基础的学生。所需的投入主要是一块入门级的FPGA开发板 Cyclone IV系列的很便宜和你的时间与耐心。当你成功点亮第一个LED并看到它背后那一整套协同工作的数字逻辑时你就会明白这台在FPGA里运行的Z80不仅仅是一个模拟器它是一个真正属于你的、由你定义的数字生命。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门