在 FPGA 中使用 W65C832
内容
在 FPGA 中使用 W65C832
发布时间:2024 年 10 月 16 日
简介
早在 80 年代,Western Design Center (WDC) 就创建了 6502 CPU 的 16 位版本,称为 W65C816。 我认为它是为 Apple IIgs 计算机创建的,尽管它也用于 Super Nintendo。 看起来他们有一个 32 位芯片 W65C832 的数据表。 我决定在 FPGA 中做一个 Verilog 版本。 我更新了 naken_asm,所以它有一个 .65832 指令,允许像 lda 这样的立即数的值有一个 .l 修饰符,这样它现在就可以进行 32 位值的操作。 指令集能够访问 16MB 的 RAM。 FPGA 本身具有少量块 RAM,这个 w65c832 内核将其用作内存底部的 4k RAM(用于零/直接页),4k 作为 ROM,以及 4k 作为 Winbond W25Q128J flash 芯片的页面。 稍后在本页中会有更多介绍。 Verilog 源代码可在 GitHub 上获得,并使用常规开源 FPGA 工具构建:yosys
、nextpnr-ice40
、icepack
和 iceFUNprog
。 我最近添加了对 Tang Nano 20k 板(也应该适用于 9k 型号)的支持,使用相同的基本工具集:yosys
、nextpnr-himbaechel
、gowin_pack
和 openFPGAloader
。 Makefile
展示了它们的构建方式。 希望能从中产生一个更大的项目。 Joe Davisson 使用这个内核用汇编创建了一个非常棒的图形演示,以及一个 XMODEM 引导加载程序,这样程序可以通过 UART 上传,而不是构建到 Verilog 中:Joe's W65C832 demo。
@mikekohn.net 上的相关项目 | FPGA: | FPGA VGA, Nexys2, Glow In The Dark Memory, Intel 8008, F100-L, RISC-V, x86 / 68000, MIPS, MSP430, PowerPC, W65C832 ---|---
65C832
芯片本身仍然有 3 个寄存器:
A (8/16/32 bit accumulator) X (8/16/32 bit index x) Y (8/16/32 bit index y)
以及 6502 的一些内部寄存器和一些较新的 65C816 寄存器:
SP (16 bit stack pointer) PC (16 bit program counter) DR (Direct Register - 65C816) DRB (Data Bank Register - 65C816) PRB (Program Bank Register to extend PC to 24 bit - 65C816)
还有一个状态寄存器(由于某种原因称为 P):
P: 7 6 5 4 3 2 1 0
N V M X D I Z C
N negative (如果结果的第 7 位被设置则设置)
V overflow
M X / break
D decimal
I interrupt disable
Z zero (如果 ALU 结果为 0 则设置)
C carry (如果 ALU 结果需要第 8 位则设置)
还有另外两个 1 位寄存器,称为 E16 和 E8,它们选择 CPU 的运行模式。 它们不能直接访问,而是通过 XCE 指令访问,该指令根据 CPU 是否处于 8 位仿真模式而表现不同。
M flag 设置 A 和内存提取的大小。
X flag 设置 X 和 Y 的大小。
// E16 E8 M X A X,Y Mode
// 0 0 0 0 16 32 W65C832 Native
// 0 0 0 1 16 8 W65C832 Native
// 0 0 1 0 8 32 W65C832 Native
// 0 0 1 1 8 8 W65C832 Native
// 0 1 0 0 32 32 W65C832 Native
// 0 1 0 1 32 8 W65C832 Native
// 0 1 1 0 8 32 W65C832 Native
// 0 1 1 1 8 8 W65C832 Native
// 1 0 0 0 16 16 W65C816 Emulation
// 1 0 0 1 16 8 W65C816 Emulation
// 1 0 1 0 8 16 W65C816 Emulation
// 1 0 1 1 8 8 W65C816 Emulation
// 1 1 1 BRK 8 8 W65C02 Emulation
E16 和 E8 改变仿真模式。 在启动时,所有 3 个标志都为 1,而 BRK 被忽略。 要从 W6502 仿真切换到 W65C816 仿真:
clc
xce
在 W65C816 模式下,xce(将 C 与 E8 交换)指令变为 xfe(将 C 与 E8 交换,并将 V 与 E16 交换)指令。 因此,要在 W65C816 模式下更改为 W65C832 模式:
sec
clv
xce
Winbond W25Q128
为了增加更多的 ROM 区域,此实现可以使用 Winbond W25Q128 16MB flash 芯片。 内核使用的内存类似于虚拟内存。 访问任何 0xc000 或更高的内存地址将触发 CPU 暂停,同时将 4k 的 flash 从 flash 芯片传输到 RAM。 此时,可以立即访问该 4k 页面。 如果读取的内存位置不在当前加载的页面中,CPU 将再次暂停,并读取下一页。
要对 flash 进行编程,可以使用 CH341A 以及名为 flashrom 的程序将 .bin 文件传输到芯片。 用于将 out.bin 写入 flash 然后将其读回 rom.bin 文件以确保其正常工作的命令:
flashrom -p ch341a_spi -c W25Q128.V..M -w out.bin
flashrom -p ch341a_spi -c W25Q128.V..M -r rom.bin
这是带有 ZIF 插座中 flash ROM 的 CH341A 编程器的图片:
flash 中 0xc000 以下的任何内存都将被忽略,但 0xc000 及以上的任何内容都将直接映射到 CPU 中。 要访问 flash 内存中的内存位置 0x20003,可以使用以下代码:
lda.b #2
pha
plb
lda 0x0003
这些指令将累加器设置为 2,使用 pha 将其推送到堆栈,使用 plb 将其从堆栈拉到 dbr(数据组寄存器),并使用 lda 0x0003 从有效地址 0x20003 中拉取。
可以使 CPU 在页面被分页移出时将页面写回 flash ROM,但这似乎不是很实用。
内存映射
此 W65C832 的实现有 4 个内存库。 如果存在 Winbond W25Q128JV,则库 3 以及高达 16MB 的所有内存将以 4k 为单位分页到 RAM 中(以及移出)。
Bank 0: RAM (4096 bytes)
Bank 1: ROM (4096 bytes from rom.txt)
Bank 2: Peripherals
Bank 3: Wondbond W25Q128JV Flash (filling up to 16MB).
启动时,芯片将从 Bank 1 执行代码。如果在重置时按下程序选择按钮,代码将从 Bank 3 中的位置 0xc000 开始。
外围设备区域包含以下内容:
0x8000: input from push button
0x8001: SPI TX
0x8002: SPI RX
0x8003: SPI CTRL
0x8008: ioport0 output (in my test case only 1 pin is connected)
0x8009: MIDI note value (60-96) to play a tone on the speaker or 0 to stop
0x800a: iport1
0x800b: UART TX buffer
0x800c: UART RX buffer (reading clears out rx_ready)
0x800d: UART CTRL - bit 1: rx_ready, bit 0: tx_busy
可以通过廉价的 USB-UART 电缆访问 UART。 电缆必须仅具有 3.3v 的逻辑电平。 下图显示了它的连接方式。
Joe Davisson 在 UART 上有一个可用的引导加载程序(也适用于 EasySXB),因此更改软件不需要重新编程 FPGA 或 flash/eeprom 芯片。
说明
即使只有 65C816,寄存器模式(8 位或 16 位)也可能具有挑战性。 在处理 PANCAKE-ROM 项目时,我在编写一些内存位置时被 CPU 的模式所迷惑。 内存操作被假定为 16 位模式,但实际上是 8 位模式。 65C832 使情况更加糟糕。
在处理 test/lcd.asm 时,它会在按下按钮时闪烁 LED 并在 LCD 显示器上绘制 Mandelbrot,有时我会忘记在使用立即数时使用正确的修饰符:
0x0000: a9 05 lda.b #0x0005
0x0002: a9 05 00 lda.w #0x0005
0x0005: a9 05 00 00 00 lda.l #0x0005
如果 CPU 处于 8 位模式并且使用了 lda.l,它将只读取 0xa9 和 0x05 来加载 A,然后执行它没有读入的立即数的 0x00 部分 (brk)。
当试图将 P(状态寄存器)加载到 A 中时,我也被困住了。在 32 位模式下,我做了:
php
pla
这推送了 P 寄存器的 1 个字节,并将 4 个字节弹出到 A 中。
在反汇编代码时,反汇编器永远不知道 CPU 处于什么模式(8、16、32 位),因此对 65C816 和 65C832 代码进行反汇编几乎是不可能的。
子程序也必须对 CPU 当前所处的模式敏感。 在 lcd.asm 中,子程序将推送 P(标志)寄存器并在离开子程序时弹出状态。 不好的是 e16 和 e8 不是标志的一部分,因此保存它们要复杂一些。 使用 65C816,这并不是什么大问题,一旦脱离 6502 仿真模式,只需使用 sep 和 rep,A 和 X/Y 都可以在所有模式下运行,无需弄乱 e8。 在 65C832 中并非如此。
在 65C832 Verilog 内部,我被一些指令重用其他指令的操作码空间所困扰。 因此,6502 中的每个 8 位操作码都可以分为 3 个部分:aaa bbb cc。 前 3 位 (aaa) 是一种操作,bbb 部分通常是寻址模式,而 cc 某种程度上区分了不同类型的操作码。 我被困住的一个指令是“bit”指令。 如果 cc 为 00,则如果操作 (aaa) 为 001,则它是用于寻址模式的“bit”测试指令:立即数、零页、绝对、带 x 的零页和带 x 的绝对。 我没有意识到的是(直到我在 lcd.asm 的一个错误上花费了太长时间)是 bit #imm 与 c=01, aaa=001 重叠,这应该是 sta 指令。 如果它是 sta 指令,并且寻址模式 bbb=010(立即数)有点无用,因此 bit #imm(100_010_01 或 0x89)被放置在该操作码空间中。
在 LCD 显示器上绘制 Mandelbrot 的 test/lcd.asm 是从 F100-L FPGA 版本移植的。 它使用 6.10 的定点数学(6 位整数和 10 位小数),因此大多数 Mandelbrot 代码都在 16 位累加器模式下运行。 它在进行乘法运算时切换到 32 位模式,并在将结果右移 10 次后切换回 16 位模式。 该代码练习了相当多的指令集,包括 stz。
65C832 Mandelbrot 大约需要 1m8s 才能生成。
图片
上图显示了如何连接 UART 电缆。 为防止损坏 FPGA,这必须是 3.3v 逻辑电缆。 不需要 5v 红线,可以保持未连接状态。 绿线是 TX 发送,连接到 FPGA 上的 G3(RX 接收引脚)。 白线是 USB 端的 RX 接收,连接到 H3(TX 发送引脚)。
这是 IceFUN,其中包含运行 lcd.asm 演示的 W65C832 verilog 代码。
这是一个 Tang Nano 20k,带有相同的(针对该板进行了稍微修改的)w65c832 内核。 核心上运行的程序写入 SparkFun LCD 显示器。 该内核仍然建立在相同的开源工具上,但使用了一些针对 GoWin 芯片的特定工具:yosys
、nextpnr-himbaechel
和 openFPGAloader
。
差异
在撰写本文时,有一些东西需要改进或需要实施。 这些最终可能会完成:
- 指令时序与规范不符。
- 未实现十进制模式。
- 6502 仿真模式允许更新的指令工作。
可能还有我忘记的其他内容。
源代码
git clone https://github.com/mikeakohn/w65c832.git
版权所有 1997-2025 - Michael Kohn