1. 为什么非要做一台“有自己的FPGA芯片组”的家用电脑
如果你问我,一个平时做嵌入式的人在家里折腾的第二台电脑应该怎么选,我的答案大概率是:别买,自己造。这里说的“自己造”不是装机,而是把传统主板上的CPU、内存控制器、显示控制器、中断控制器这一整串芯片,全部用硬件描述语言在FPGA里重写一遍,再配上一个自己选的处理器软核,最终做成一台自带FPGA芯片组的家用电脑。这就是我们折腾了大半年的 Kromi One。
Kromi One 这台机器的定位,从一开始就不是“跑分工具”,而是一个“整块主板逻辑都可以用硬件描述语言改写”的实验平台。我们项目里说的芯片组,指的是传统PC里北桥和南桥所承担的那些角色:DRAM控制器、显示控制器、中断控制器、定时器、串口和键盘接口。这些原本固化在硅片里的硬件,现在全部变成了FPGA内部一个一个的Verilog模块。好处很明显:某个模块写崩了不用换板子,重新生成一版bitstream就能继续改;更重要的是,每一个读写信号都可以拉出来看,调试和教学价值远超一台普通电脑。
如果你只是想要一台日常上网或写文档的机器,这篇内容对你没有意义。但如果你对“电脑是怎么工作”这件事有执念,想亲手把一台完整计算机从无到有做出来,那这篇文章讲的路线、取舍和坑,应该能帮你省下大量重复摸索的时间。适合的人群包括:做过一些FPGA开发但没碰过系统级设计的硬件爱好者,喜欢复古计算、想让电脑回到“按下电源键能看见固件自检”那种状态的玩家,以及想通过一个真实项目理解总线、中断、DMA、内存时序这些概念的嵌入式开发者。
1.1 现成方案缺什么
我最早的想法其实很朴素:买一块现成的开发板,里面已经带了CPU、DDR颗粒和视频输出,不就是一台电脑吗?后来发现完全不是一回事。开发板给你的是一套“别人设计好的电脑”,你能改的只有上面几个外设接口的驱动代码,CPU怎么取指、内存怎么刷新、像素怎么扫描,这些你通通看不见,也改不了。普通PC更是如此,北桥在南桥里藏着,南桥在封装底下藏着,只有厂商愿意给你的那部分寄存器是可见的。
Kromi One 想解决的问题就是“可见性”。我们要求整机所有关键路径都能用逻辑分析仪直接观测:CPU发起读请求之后,总线仲裁器在哪个周期放行,内存控制器在哪个状态等待tRCD,显示控制器什么时候从显存取走一行像素,这些如果发生在商店里买来的主板里,你是永远不知道的。而在自己的FPGA芯片组里,每个信号都是代码里的一根wire,只要你想看,随时可以把它引到测试引脚上。
当然,这条路不是没有代价。把CPU、总线、内存控制器和显示控制器全部塞进一颗FPGA,意味着所有系统级问题都会同时涌过来:复位时序、时钟域、总线仲裁、外设中断优先级、软件启动流程,哪一环没接好整机就点不亮。它比写一个单纯的LED流水灯复杂两个数量级,但比读十本计算机组成原理教材都更有用。
1.2 项目目标和验收标准
为了避免项目无限期拖延,我们在动工前先写了一份非常具体的验收清单。很多DIY项目烂尾,不是因为热情不够,而是因为没有“什么时候算做完”的标准。Kromi One 的初始指标定得很克制:
- CPU:32位RISC-V软核,目标主频50MHz,至少支持RV32I整数指令集和基本中断;
- 内存:16MB SDRAM,CPU可随机读写,带宽不拖累CPU主频太多;
- 显示:VGA输出,640x480@60Hz,8位色,显存独立编址;
- 输入:PS/2键盘接口,能稳定识别标准键盘;
- 存储:通过串口或SD卡加载用户程序,不需要操作系统;
- 软件:能运行交叉编译的C程序,并带一个简单的监控终端和BASIC解释器。
验收方式也写得很具体:加电后1秒内屏幕出现启动Logo;从串口输入一段C语言写的小程序能编译后load进内存再run起来;用键盘操作贪吃蛇游戏,帧率不低于20fps;最后用一颗分形计算程序把CPU跑满,测出主频、访存延时这些关键数字。所有验收项都完成之后,这个项目才算是真正结束,而不是“代码能综合就算成功”。
这份验收标准后来帮了大忙。因为每完成一个小项,整机就向“可用电脑”靠近一步,成就感是持续的。而且它逼着我们把软件栈和硬件一起考虑,而不是做出一块只会点灯的FPGA板子。
2. 整体架构:CPU、总线与芯片组的分工
硬件项目最容易犯的错误是一上来就写代码。Kromi One 在写第一个模块之前,花了两周时间把整体架构确定下来。这里的架构不是说“CPU选哪个”“内存用多大”,而是明确每个模块在系统中的角色、它们之间通过什么协议通信、地址空间怎么划分。把这件事想清楚,后续所有调试都会顺利很多。
2.1 一个FPGA如何身兼数职
Kromi One 的核心逻辑全部运行在一颗中端FPGA里,外部只有SDRAM颗粒、时钟晶振、VGA接口、PS/2接口、串口转换芯片和电源电路。传统主板上由北桥、南桥分管的活,在FPGA内部被拆成了下面这些独立模块:
- 处理器软核:负责取指令、译码、执行,是整个系统的大脑;
- 总线互连:把CPU的读写请求路由到不同外设,同时处理地址译码;
- 内存控制器:管理SDRAM的初始化、读写、自动刷新和刷新仲裁;
- 显示控制器:周期性地从显存读取像素数据,生成VGA行场同步信号;
- 键盘控制器:解码PS/2信号,把扫描码放进FIFO并触发中断;
- 定时器:提供系统时钟节拍,这也是调度和游戏帧率控制的基础;
- UART控制器:用于串口调试和文件加载;
- Boot ROM:用FPGA内部Block RAM实现,上电后CPU从这里开始执行。
这些模块之间没有采用复杂的片上网络,而是用了一条极简的32位总线。早期版本试过完整AXI协议,后来发现对这台机器来说太重了。AXI的通道多、握手规则多,虽然通用性好,但每个外设都要适配五组通道,调试时波形图看起来密密麻麻。最终我们退回到类似经典存储器映射总线的风格:CPU发地址、读/写使能、写数据,外设准备好后拉回一个应答信号。总线主设备暂时只有CPU一个,所以不需要做总线仲裁,这极大降低了第一版的难度。
2.2 地址空间映射与设备编址
芯片组最重要的一个决策,是给每个设备分配哪一段地址。Kromi One 的地址空间是32位,但我们只用低32MB,剩余部分统一返回错误。地址映射表定下来之后基本不再改动,因为所有软件的绝对地址都依赖它。
| 地址范围 | 设备 | 说明 |
|---|---|---|
| 0x00000000 - 0x0000FFFF | Boot ROM | 64KB,FPGA内部存储器,上电执行 |
| 0x00010000 - 0x0001FFFF | UART、定时器 | 串口数据寄存器、状态寄存器、定时器计数 |
| 0x00020000 - 0x0002FFFF | 键盘控制器 | 键盘FIFO、状态寄存器 |
| 0x00030000 - 0x0003FFFF | 中断控制器 | 中断状态、使能、清除寄存器 |
| 0x00100000 - 0x00FFFFFF | SDRAM | 16MB内存,程序和数据主存储区 |
| 0x01000000 - 0x01003FFF | 显存 | 16KB,640x480@8位色对应一帧画面 |
| 0x02000000 - 0x0200000F | 显示控制寄存器 | 显存基地址、颜色索引、光标位置 |
这个表格看起来简单,但它体现了芯片组设计里很关键的一点:CPU和外设通过统一的地址视图通信,而不是每个设备各自定义一套私有接口。写显示控制寄存器可以和写UART寄存器用同一套load/store指令,软件层不需要关心外设是在片内还是片外,这正是传统芯片组设计思想的延续。
2.3 为什么不把整颗SoC放进一个FPGA
很多人会问:FPGA里已经有CPU、有控制器,为什么还要叫“芯片组”?直接做成一整颗SoC不是更现代吗?技术上确实可以,但我们在实际项目里发现,把CPU和所有外设揉成一个紧密耦合的SoC,会带来两个问题。
第一是调试难度增加。SoC内部的模块之间往往有大量直连信号,一旦总线时序出了问题,你很难判断是CPU核的问题还是外设控制器的问题。Kromi One 刻意在CPU和所有外设之间保持一条清晰、简单的总线。这条总线就像主板上的PCI总线一样,任何模块接上去,遵循同样的地址译码和握手时序。用逻辑分析仪抓总线波形,就能一眼看出是谁没有响应,谁的数据线驱动冲突。
第二是实现边界更清晰。芯片组的思路更适合渐进式开发:第一版先点亮CPU和SDRAM,第二版加显示控制器,第三版加键盘和中断,每一次都是独立的子系统验收。如果一开始就把所有模块综合进一颗大SoC,单个模块出错时很难定位,而且综合时间会变得很长,迭代效率反而低。所以Kromi One 虽然物理上是一颗FPGA,逻辑上仍然是一块“分模块芯片组”,只是把传统主板上的分立的芯片都搬到了同一片可编程逻辑里。
3. 芯片组里最难啃的几块:内存、显示和输入
系统架构定了之后,真正的硬骨头才开始。整个项目里工作量最大、调试周期最长的不是CPU,而是芯片组的三个部分:SDRAM控制器、显示控制器和键盘输入处理。这三块直接面对物理世界,时序错一拍,表现出来的就是随机死机、花屏、按键丢字,而且问题非常难复现。
3.1 SDRAM控制器:状态机不是唯一难点
Kromi One 选择SDRAM而不是DDR,是经过认真考虑的。SDRAM颗粒的时序相对简单,单沿采样,数据对齐容易,低速运行下的可靠性远高于DDR。DDR3为了带宽引入差分时钟和八位预取,在50MHz的CPU下完全是浪费,还给板级布线增加很多麻烦。16MB的SDRAM对一台复古风格家用电脑来说已经足够,甚至还能富余出不少空间做RAM Disk。
SDRAM控制器的难点不在状态机,而在“对所有时序参数负责”。上电后需要等待100us稳定期,然后依次执行预充电、自动刷新、模式寄存器设置;之后每次读写都要遵守tRCD、tRP、tRC这些参数;每隔7.8125us必须插入一次自动刷新,否则电容上的电荷会漏光。这些参数不是写代码时拍脑袋定的,而是要看具体SDRAM颗粒的数据手册。
我们第一版控制器是把所有参数硬编码成状态,结果换了一颗不同速度等级的颗粒之后,偶尔会出现数据错误。后来重构成了参数化模块:
localparam CL = 2'd3; // CAS Latency,按颗粒手册配置 localparam TRCD = 8'd2; // RAS to CAS Delay localparam TRP = 8'd2; // Precharge to Active localparam TRC = 8'd7; // Active to Active Refresh localparam TRFC = 8'd9; // Refresh cycle time状态机的核心跳转并不复杂,无非是IDLE、ACT、READ、WRITE、PRECHARGE、REFRESH这几个状态,难的是每个状态停留的最小周期数和刷新请求的仲裁。我们的做法是让刷新计数器独立运行,计数器溢出后拉高refresh_pending信号。CPU请求来时,如果refresh_pending为高,优先处理刷新;否则才响应CPU。这个简单的优先级策略避免了刷新超时,代价是CPU偶尔要多等几个周期,实测下来完全可接受。
3.2 视频输出:为什么选VGA而不是HDMI
显示控制器是Kromi One 最直观的部分,也是第一次让这台机器看起来“像一台电脑”。做视频输出时,很多朋友劝我直接上HDMI,一根线连显示器多方便。但HDMI牵涉到TMDS编码、差分信号和高速收发器,在不能确认FPGA底层资源的前提下,调试成本会很高。VGA虽然老,但是协议极其简单,三个模拟颜色通道加行场同步,任何示波器都能直接测。
640x480@60Hz的标准时序是像素时钟25.175MHz,我们的FPGA主时钟是50MHz,所以用PLL生成25MHz即可。普通LCD对25MHz和25.175MHz之间的差异完全不敏感,实际显示不会偏色或偏移。水平方向每行总共800个像素周期,其中640个有效;垂直方向总共525行,其中480个有效。显示控制器的核心就是一个行计数器和一个场计数器,到了有效区域就从显存读像素,否则输出消隐电平。
为了把数字像素信号变成VGA模拟信号,我们用了一组简单的电阻网络做DAC。RGB各8位输出,通过R-2R电阻分压成模拟电平。颜色精度不需要非常高,家用电脑的8位色足够。真正的坑在同步信号极性:不同显示器要求的行场同步极性不同,标准VGA时序通常要求HSync和VSync都是负极性,但有些老显示器需要正极性。最后我们在显示控制寄存器里留了一个极性切换位,驱动起来更方便。
3.3 键盘与鼠标:从物理信号到可读数据
PS/2协议虽然古老,但实际写控制器时远比想象中麻烦。键盘在按下和释放时都会发送扫描码,释放时会先发0xF0前缀,然后才是真正的键码。键码分两套:IBM标准的第一套和第二套。我们只处理第二套,然后在软件里翻译成ASCII,这样更接近现代键盘的默认行为。
物理层上,PS/2时钟线约10-16kHz,每个数据位在时钟下降沿被读取。一次传输共11位:1位起始位、8位数据位、1位奇偶校验位、1位停止位。我们需要在FPGA里对时钟信号做滤波,防止机械抖动和噪声造成误触发。滤波方法很简单:用系统时钟对PS/2时钟连续采样,出现连续8个高电平或低电平才认为信号真正变化。
控制器把接收到的11位拼装成一个完整字节后,不是直接给CPU读,而是写入一个小型FIFO。按下键盘上不存在的修饰键、长按重复发送,这些都由软件处理。CPU则通过中断知道键盘产生了新数据。这样设计的好处是键盘事件不会因为CPU正在处理显示刷新而丢失,FIFO深度给到16字节,足够覆盖最极端的连击速度。
4. CPU核心选型:自研软核还是现成开源IP
芯片组是躯壳,CPU是心脏。这一部分决策直接决定了整台机器能跑什么软件、性能上限在哪里、以及调试手段有多复杂。Kromi One 在CPU选型上经历了三轮非常激烈的讨论,我们甚至差点走回自研指令集的老路。
4.1 三条路线的评估
当时摆在面前的有三条路线:完全自研一个32位CPU核、选一个成熟的开源RISC-V软核、购买商业IP核。三条路线我分别列了一张表来打分。
| 路线 | 优点 | 缺点 | 我们的评估 |
|---|---|---|---|
| 完全自研 | 每根信号都理解,想改哪里改哪里 | 验证工作量巨大,流水线bug难查 | 适合教学,不适合做整机 |
| 开源RISC-V软核 | 指令集成熟,工具链完善,社区资料多 | 需要花时间理解总线接口和中断行为 | 最终选择 |
| 商业IP核 | 性能强、文档全 | 授权费用高,底层不透明 | 家用电脑项目没必要 |
我们最终选择了一个成熟的RV32IM开源软核,然后在它外面包了一层自己写的总线桥。RISC-V最吸引人的地方不是架构多先进,而是有完整的GCC工具链和C库支持。这意味着我们可以用正常的C语言写应用,而不是为了配合某个三流指令集去手写汇编。芯片组的自研路线已经很难了,CPU如果连编译器都要自己写,整个项目大概率会烂尾。
4.2 流水线、中断控制器和调试口
选好软核只是开始,真正的工作量在对接。开源软核通常默认配了一套简单的存储器接口,但把它接到我们的地址映射总线上,需要处理时序转换、读等待和中断。Kromi One 的CPU没有缓存,直接访问SDRAM和Boot ROM。SDRAM的读等待周期长,一次读请求从发出到拿到数据大约需要十几个周期,CPU流水线在这一刻就会停顿。这个速度对于50MHz主频的软核来说不算太差,但意味着性能瓶颈很大程度在访存而不是流水线本身。
中断控制器的设计也花了很大功夫。系统里有定时器中断、键盘中断和UART接收中断,共三个中断源。RISC-V软核提供了机器模式中断入口,但只有一个外部中断引脚,所以我们需要在外部做中断仲裁:
- 中断控制器维护一个中断状态寄存器;
- 每个中断源在设备内部拉高后,控制器把对应位置1;
- 如果对应使能位为高,则向CPU发送外部中断请求;
- CPU响应中断后在服务程序里读取状态寄存器,确定是哪个设备触发;
- 服务程序处理完后写清除寄存器,中断控制器再做优先级判断。
调试手段方面,我们放弃了传统JTAG调试器,因为软核的调试模块会占用大量逻辑资源。取而代之的是两个非常原始但有效的工具:一个片上逻辑分析仪IP,主要用来抓总线波形;另一个是UART printf,在关键代码路径里打印状态。等系统基本稳定后再考虑接调试器,前期能用串口输出来验证逻辑就够了。
4.3 没有浮点单元怎么办
RV32IM里的M代表整数乘除法扩展,没有浮点单元。写C代码时如果直接使用float、double运算,编译器会自动链接软浮点库,把浮点运算翻译成一系列整数指令。Kromi One 跑一个简单的圆周率计算,秒级可以完成,这对家用电脑的复古定位完全够用。
但软浮点的性能很差,一个double乘法可能要几十条指令。所以涉及图像计算、物理模拟这些场景时,我们尽量用定点数代替。分形计算程序就是一个典型例子,把浮点坐标乘上65536转成16.16定点数,迭代时用整数乘法和移位完成,速度比软浮点快好几倍。这个经验后来也写进了项目文档:Kromi One 的应用开发,先考虑用整数解决问题,再考虑对不对浮点优化。
5. 硬件调试实录:三次差点劝退的问题
Kromi One 的调试过程是整个项目最大的财富,也是我决定写这篇文章的直接原因。网上很多教程都会展示成功后的跑分和画面,但不会告诉你中间踩过哪些能把人逼疯的坑。我挑三个最有代表性的问题,按根因、排查过程和最终解法完整讲一遍。
5.1 复位信号进城前的毛刺
第一次把整机综合完上电,我们期待屏幕上出现Logo,实际看到的是完全随机的内容,有时候屏幕全白,有时候花成一片,但多按几次复位键偶尔又能正常。一开始怀疑显示控制器有问题,后来用逻辑分析仪抓CPU复位引脚,发现一个非常隐蔽的问题。
复位按键和RC复位电路产生的复位信号是异步的,它直接进FPGA引脚后,被多个模块当成同步复位使用。CPU内部复位释放的时机和SDRAM控制器复位释放的时机可能差几个周期,导致CPU已经开始取指,内存控制器还没准备好。更糟糕的是,按键抖动会让复位信号连续跳变几次。解决办法分两层:硬件上在复位引脚加一个小电容滤波;软件上在FPGA内部做两级同步:
reg rst_sync_1, rst_sync_2; always @(posedge clk or negedge ext_rst_n) begin if (!ext_rst_n) begin rst_sync_1 <= 1'b0; rst_sync_2 <= 1'b0; end else begin rst_sync_1 <= 1'b1; rst_sync_2 <= rst_sync_1; end end assign rst_n = rst_sync_2;同时,所有模块统一使用同一个内部复位信号,不允许各模块直接使用引脚上的异步复位。这个改动之后,随机死机的频率骤降到几乎为零。经验是:FPGA系统里,复位信号必须当作一个严肃的时钟域信号处理,而不是简单的“拉低就复位”。
5.2 SDRAM偶尔读错一个数
第二个问题更隐蔽。内存读写测试程序跑了几万次都正确,但连续运行半小时后,偶尔会出现一个字节错误。这种偶发性bug最折磨人,因为它不遵循任何可复现规律。
我首先怀疑是SDRAM刷新逻辑有竞争。于是把刷新周期从每7.8us缩短到每7us,问题依然存在。接着怀疑是数据总线上的多比特翻转,但错误只发生在某几个固定地址附近,不是随机散布。最后,我把逻辑分析仪挂在地址线上,抓到了一根地址线的信号在CPU读请求期间发生了一次毛刺,持续不到1ns。
根因是FPGA内部走线延迟造成地址总线建立时间不足。CPU发出地址后,地址总线经过组合逻辑和走线,到达SDRAM控制器的寄存器时,刚好赶在时钟上升沿附近,导致控制器有时采到旧地址,有时采到新地址。硬件上很难改变综合工具对布局的结果,软件上我们做了一层可靠缓解:在CPU请求到SDRAM控制器之间插入一级地址寄存器。让地址先在系统时钟域被稳定锁存,再传递给SDRAM控制逻辑。这样就保证了控制器采样时地址是稳定不变的值。
同时也整理了内存测试的代码:先写walking 1和walking 0,再写全0、全1、0xAA、0x55,最后用伪随机数连续读写。任何一项错了都能立刻定位到是哪根数据线或地址线的问题。
5.3 显示花屏其实和显示模块无关
显示画面的花屏问题更迷惑人。字符合法、Logo可以显示,但画面经常像被横向撕开一样,上半部分和下半部分错位。看起来是行同步出了问题,但用示波器测HSync波形,脉冲宽度和频率都正确。
真正的原因在显存的数据通路。CPU时钟是50MHz,显示控制器的像素时钟是25MHz,两个时钟域共用同一个显存。CPU正在写显存时,如果显示控制器同时读显存,初版设计用一个简单复用器切换读写端口,切换瞬间如果CPU或显示控制器正在处理数据,就可能读到一半的数据。纵向上看就是某一行像素的地址错位。
解决方法是把显存改成双端口结构,或者至少在两个读端口之间加异步FIFO做缓冲。一个写端口给CPU,一个读端口给显示控制器,各自用自己的时钟采样,彻底切断跨时钟域路径。改完之后花屏现象立刻消失。这让我意识到,在芯片组设计里,跨时钟域问题不是某个模块的附加题,而是从架构阶段就要考虑的主料。
6. 软件栈、第一个程序和真实体验
硬件全部打通之后,Kromi One 还只是一块能跑测试程序的FPGA板子。真正让它成为“家用电脑”的,是那一套从Boot ROM到用户程序的软件栈。整台机器的最终体验,其实是硬件和软件共同定义的。
6.1 引导、交叉编译与监控终端
Kromi One 的启动流程非常接近老式电脑。FPGA配置完成后,CPU从Boot ROM的起始地址执行,Boot ROM里放着一小段固件。这段固件的任务很简单:初始化串口和SDRAM,显示启动Logo,然后进入监控终端循环。监控终端不是操作系统,更像一个简化版的BIOS命令行:
- mem 命令:读取指定地址的内存内容;
- load 命令:从串口接收二进制镜像到内存指定地址;
- run 命令:跳转到指定地址开始执行;
- cls 命令:清屏;
- time 命令:读取定时器计数。
交叉编译工具链用的是标准RISC-V GCC。写一个C程序、编译、烧录的过程比现代开发流程多了些手工感,但也正是这台电脑的魅力所在:
riscv64-unknown-elf-gcc -march=rv32im -mabi=ilp32 -O2 -nostdlib \ -T k1.ld hello.c -o hello.elf riscv64-unknown-elf-objcopy -O binary hello.elf hello.bin编译出的二进制文件通过串口发送给Kromi One,监控终端接收完就能 run。没有操作系统、没有动态链接库,每一个字节都清清楚楚知道放在哪里,这种掌控感在现代开发环境里几乎找不到。
6.2 能跑起来的程序:从Hello World到贪吃蛇
第一个在Kromi One上正常运行的程序是往屏幕显示一行字符串,但这已经足够让人兴奋。屏幕出现文字的那一刻,这台机器才算真正“醒”过来。接下来我们逐步把键盘、定时器和中断驱动起来,写出了几个更有意思的应用。
贪吃蛇游戏是Kromi One的第一个完整应用。键盘中断负责接收方向键扫描码,定时器中断负责以固定间隔更新游戏状态,显示控制器负责把游戏画面刷新到屏幕上。没有文件系统,游戏二进制直接通过串口加载。整个程序用C语言写的,逻辑通顺,用了中断但没有任务调度,就是一个裸机循环。
C语言写的Hello World不算挑战,真正让机器有分量的是BASIC解释器。我们在PC上写了一个很小的BASIC解释器,可以解析LET、PRINT、IF、GOTO、FOR等基础语句,然后也编译成RISC-V二进制跑在机器上。用户可以在监控终端输入BASIC程序,直接执行。这就让Kromi One具备了老式家用电脑“开机就能编程”的核心特质,而不只是一块跑固件的开发板。
6.3 实测数据、资源占用和下一步扩展
项目收尾时,我们对Kromi One做了完整的性能测试,把关键数字都记录在案。
| 项目 | 实测结果 | 备注 |
|---|---|---|
| CPU主频 | 50MHz | 软核稳定运行 |
| SDRAM读延时 | 约14个CPU周期 | 包含地址锁存和CAS等待 |
| 分形计算 | 256级迭代100x100区域约1.6秒 | 16.16定点数实现 |
| 显示刷新 | 640x480@60Hz | 显存双端口,无撕裂 |
| 整机功耗 | 5V约1.2A | 无风扇,散热靠底板 |
| FPGA资源占用 | 约60%逻辑单元 | 仍有空间集成声卡等 |
资源占用没有到极限,因此我们已经列了下一步计划:用PWM加低通滤波器做一个简单的音频输出,实现按键蜂鸣音和简单音乐;给SD卡写一个小文件系统,让机器可以从卡上直接加载程序文件;再往后希望能跑一个更完整的Forth语言解释器,进一步增强手工编程的乐趣。对我个人来说,Kromi One最珍贵的不是最终跑出了什么分数,而是它证明了“芯片组”并不是一个只能写在教科书里的抽象概念。当你在逻辑分析仪上看到CPU发起的读请求被内存控制器稳稳接住,看到显示控制器从显存取出一行像素变成屏幕上的画面,那种对计算机系统的理解,和读十遍原理图都不太一样。如果你也在琢磨自己造一台电脑,我的建议很简单:先把地址映射表和验收标准写清楚,然后选一个最小可行版本死磕到底。别怕工程量大,只要每一步都能被看见、被验证,再大的系统也能一点点拼出来。