第十七讲:Z80汇编语言(任天堂游戏机和德州仪器计算器)

程序员小x大约 8 分钟汇编语言

第十七讲:Z80汇编语言(任天堂游戏机和德州仪器计算器)

本节我们将学习另外一个系列的CPU,Z80。Z80 CPU起源于8080CPU, 8080CPU是现在Intel CPU的鼻祖。

Z80 CPU最著名的用途就是任天堂Gameboy和TI图形计算器。 Z80的目的是与Intel 8080二进制兼容,即操作码相同。这意味着为Intel 8080编写的软件可以在Z80上运行而无需重新编译),但由于版权问题,寄存器和指令名称不同。

Z80

寄存器

Z80 有 8 个或 5 个通用寄存器,具体取决于您如何看待事物:

  • 寄存器 AFBCDEHL 可以单独用作 8 位寄存器
  • 寄存器对BCDEHL可用作16位寄存器,其中BBC 的高字节,而 C 是低字节。
  • 有点奇怪的是,A 寄存器可以与标志寄存器 F 组合起来作为 16 位寄存器 AF
  • 寄存器 IXIY 是 16 位索引寄存器,与内存操作数一起使用。 HL 类似地用于内存操作数。

x86 一样,SP 寄存器指向堆栈顶部,PC 寄存器指向当前指令(请注意,这与 x86 的 IP 寄存器指向下一条指令不同)。F寄存器包含标志:符号、零、奇偶校验/溢出、满进位。AF 寄存器可以通过几条指令组合成一个 16 位寄存器 AF

Z80有一个中断向量寄存器I,其中包含中断向量表的地址,这个设计很有先见之明;在这个时代的Intel CPU中,中断向量的地址是固定在内存中的。

与 x86 不同,Z80 有一组影子寄存器;这是一组附加寄存器 A'F'B'C'D'E'H'L'

您无法直接访问这些寄存器,但可以使用一条指令交换主寄存器组和影子寄存器组。

这提供了一种简单但有限的方法来保存所有寄存器的值,然后在以后恢复它们(但是,与堆栈不同,您只能保存一组寄存器值)。

指令集

大多数 Z80 汇编器使用的语法更像 AT&T 语法而不是 Intel 语法。这意味着指令具有以下形式

   INSTRUCTION DESTINATION, SOURCE

内存的操作写在括号中。

你可以在这里查找到完整的Z80指令集: Z80指令集open in new window

mov 已重命名为 ld("加载"),但它可用于将值写入寄存器或内存。例如:

    ld  a, 5             ; Set register A = 5
    ld  b, 7             ; Set register B = 7
    add a, b             ; Set A = A + B
    ld [Output], a       ; Write A into address Output

(一些 Z80 汇编器将内存操作数写在括号 () 中,而其他汇编器(例如我们将要使用的汇编器)则使用更熟悉的 [])。

add 指令只能使用 ahlixiy 作为其目标。内存操作数可以使用立即地址,或者您可以执行类似的操作

ld [hl], a ; Write A into the address contained in HL

如果要在内存操作数中使用立即地址加寄存器,则寄存器必须是 IXIY

ld [Array + ix], a   ; Write A into Array[ix]

与 x86 一样,ld 的两个操作数必须具有相同的大小:均为 8 位或均为 16 位。根据需要,立即操作数将提升为 16 位。

Z80 指令的大小为 1 到 3 个字节。

条件跳转

有两种条件跳转指令:

    jp CC, Target           ; 条件绝对跳转

    jr CC, Target           ; 条件相对跳转

其中 CC 是条件代码。两者的主要区别是jp更灵活(可以使用更多条件,速度更快),而jr更有限(条件更少,速度更慢,只能在程序内跳转±128字节)但只占用2个字节程序而不是 jp 的 3 个字节。

更专门的条件跳转指令是 djnz,类似于 x86 中的loop,它代表"递减,如果非零则跳转"

 djnz Target          ; Decrement B, jump to Target if B ≠ 0

函数

call与 x86 一样,用于调用函数,它压入 PC+3,然后跳转到函数地址。 (请记住,PC 包含当前指令的地址;即 call 指令本身。call 指令占用 3 个字节,因此下一条指令是 PC+3。)类似地,ret 通过弹出 PC 和跳向它。

与 x86 不同,Z80 有条件返回指令:

ret p               ; Return if positive (sign flag = 0)

可用条件有 C/NC(进位设置/未设置)、M/P(符号 = 1/0)、Z/NZ(零 = 1/0)、PE/PO(奇偶校验 = 1/2)。这些与条件跳转指令 jp CC 所使用的条件相同.

pushpop 可用于将寄存器压入堆栈或从堆栈中弹出。只有16位寄存器可以压入/弹出,所以如果你想压入A,你必须结合标志寄存器来压入它:

 push af

块传输

Intel的处理还没有字符串的指令,也没有字符串重复前缀。但Z80在器块传输操作码中确实有这些指令的最小形式。这些允许将数据块从 I/O 端口或内存复制到 I/O 端口或内存(允许内存到内存传输)。

块传输函数使用 HL 作为要读/写的内存地址,使用 B 作为计数。 INI 指令(输入和增量)从给定的 I/O 端口读取 16 位,将其复制到 HL 指向的地址,递增 HL,并递减 B。所有块传输指令都遵循以下模式:

RGBDS 汇编器工具

我们将使用RGBDSopen in new window 工具来为GameBoy汇编代码。

虽然架构不同,但 RGBASM 支持的汇编语法与我们熟悉的 YASM/Intel 语法非常相似。主要区别是:

  • 十六进制值以$开头,而不是0x
  • 二进制值以开头,而不是0b
  • GameBoy图形化可以直接使用符号嵌入
    `0123
    
    由于GameBoy上的每个像素都可以是四种灰度之一,因此使用值0到3。每个值都被编码为位对 (00, 01, 10, 11),然后打包成一个字节。因此,上面对应于字节%00011011$0f55
  • 与YASM一样,RGBASM允许对涉及常量和算术运算符的表达式进行汇编时计算。
  • SECTION伪指令存在,但唯一有效的部分是ROM0(盒式 ROM 组 0)和 ROMx(盒式 ROM 组 x)。如果您需要更多控制,SECTION 指令支持用于指定节地址的附加选项。
  • 与 YASM 中一样,标签从行首开始,以 : 结束。局部标签以 . 开头。标签必须以一行的第一个字符开始。
  • 由于标签必须从一行的第一列开始,因此指令必须缩进至少一个空格或制表符。
  • 与 YASM 中一样,equ 可用于定义常量。但是,符号名称后面不能跟 : (在 YASM 中, : 是可选的):
    Addr       equ         0xff40
    
  • Z80 汇编传统上在 () 中写入内存操作数,而 RGBASM 使用 Intel 风格的 []。
  • 对于 ldi 系列指令(加载和增量),RGBASM 支持两种语法。通常的方式:
    ldi     [hl], 0            ; Write 0 into address hl, increment hl
    
    和替代语法
    ld      [hli], 0           ; Write 0 into address hl, increment hl
    
    您可以在此处open in new window找到有关 RGBASM 语法的完整文档

GameBoy模拟器的HelloWorld程序

GameBoy 硬件缺乏任何类型的文本输出到屏幕,因此我们实际上无法打印“Hello, world!”。此外,它甚至不允许直接访问屏幕上的各个像素;相反,它使用平铺屏幕模式:屏幕分为多个图块,每个图块为 8x8 像素。图块集存储在内存中,屏幕上的每个图块都映射到该集合中的一个图块。这比存储屏幕的整个(像素)内容节省了大量的内存:

160 × 144 × 2 = 46080 bits = 5760 bytes

(160/8) × (14‌4/8) × 8 = 2880 bits = 360 bytes

TI-83 图形计算器模拟器上的 Hello World

许多 TI 图形计算器运行 Z80 处理器,因此我们可以为它们编写 Z80 程序集。与 GameBoy 不同,TI 计算器运行的是原厂Z80,没有任何修改。

我们将使用 Pasmo 汇编器和 Oysterpac 工具将原始二进制数据“打包”到 TI 计算器可执行文件中。两步过程只是首先将我们的源代码组装成二进制文件:

pasmo file.asm file.bin

然后在其上运行 Oysterpac 以生成打包的可执行文件: oysterpac file.bin file.83p

附录

原文链接:

https://staffwww.fullcoll.edu/aclifton/cs241/lecture-z80-assembly.htmlopen in new window

Loading...