声明:本汇编语言及其汇编开发环境已被淘汰,这个教程只是面向古计算机爱好者的,Merlin的资料也有很多,完全可以拿来学(只不过全英你看不看得懂那是另一回事了)。因此,这篇教程只是我用来闲暇消磨时间写的了,至少这比天天玩游戏强得多。
一、什么是Merlin?
Merlin 32是一个在Windows、Linux和Mac OS X下运行的多通道交叉汇编程序,针对6502系列中的8位处理器(如6502和65c02)和16位65c816处理器。与Glen Bredon的Merlin 16+语法兼容,包括对宏、预处理器、逻辑表达式、条件运算、变量、循环、LABLE的支持等。
Merlin 32可以构建固定位置的目标代码或可重定位的可执行文件(OMF v2.1),可以在16位Apple IIgs操作系统上找到,如Prodos 16或GS/OS(S16、Exe、CDA、NDA、FST、PIF、库、工具……)。也是Brutal Deluxe跨开发工具项目的一部分,此项目是Windows(和其他)平台上可用的全套实用程序,用于创建新的Apple IIgs软件:65c816汇编程序、65c816反汇编程序、65c816模拟器、图形文件转换器、资源捕获器。。。
用途
在上世纪80年代欧美地区的IT圈中,此时主流高级语言也只不过是BASIC。其效率与速度非常拉跨,况且那个年代的CPU主频仅是几兆赫兹(MHz),若进行游戏、软件开发。那是及其的缓慢,此时程序员正经历着两种局面——一个是简单但性能实在糟糕的BASIC,一个是繁琐但能几乎完全利用机能的汇编语言(汇编本身并不复杂,只是繁琐和冗杂这一点是最讨厌的)。那个时候编程,也就只能是这样,如果想开发优质的东西你就必须会汇编,要学汇编你得有个汇编器。
人们的眼光还算不错,市场上几款汇编器要不就是BUG多要不就是其他乱七八糟的东西(那统称为BUG也可以吧大概)。而Merlin作为以前最主流的汇编器。诸如《DONKEY KONG(大金刚)》,《MARIO BRODS(马里奥兄弟)》等从NES或街机移植过来的游戏程序也是均由Merlin编写。80年代的NES红白机的游戏开发环境中也包括Apple II,配套的开发包自然也包括Merlin。而外网中Merlin汇编器相比于其他汇编器也是最常出现的,这也说明了当年Merlin的在IT圈内的火爆。但是在国内完全没有任何关于此汇编器的资料,我们当年接触过计算机的人屈指可数,编程更是少之又少,更不用说汇编了,因此几乎没人认识这个东西。
如今,Merlin已移植到现代系统中,就不再需要用模拟器或虚拟机去编写代码了。这样进行NES游戏等开发操作也就方便了不少(呃……是这样的吗?方便了吗?为什么不用CC65这些东西?)
如何运行到这些机器?(简略教程)
1、FC / NES / 红白机
2、SFC / SNES / 超级任天堂
3、Commodore 64 / Commodore 128
4、Apple II
- 准备四个软件:任意一个代码文本编辑器、Merlin汇编器、CiderPress、Apple II模拟器
- 写好程序,用Merlin汇编好后,会生成一个没有任何扩展名的文件。这个文件就是你的目标文件。
- 命名为此格式:<文件名>#XXYYYY.bin。其中,<文件名>是在Apple DOS上显示的文件名,XX是文件类型,YYYY是起始地址。
- 打开CiderPress,选择[File] -> New -> Disk image...新建一个FileSystem(文件系统)为DOS 3.3的磁盘,其他默认不用改。
- 选择[Action] -> Add File(或点击菜单栏上从左往右数第六个按钮),把你的bin文件导入进去。
- 此时你创建的磁盘已经导入你的Bin文件,那个磁盘可以用Apple II模拟器打开了。#1磁盘驱动器导入你创建的磁盘,在终端输入“CATALOG”,找到你的文件,最后输入“BRUN <你的文件名>”即可运行你的程序。
二、Merlin的使用
1、命令列表
在【命令提示符】窗口中运行Merlin32.exe,会显示如下文字:
C:\AppleIIgs>Merlin32.exe
Merlin32.exe v 1.0 (c) Brutal Deluxe 2011-2021
Using:Merlin32.exe [-V] <macro_folder_path> <source_file_path>.
在Using中表面了此汇编器的用法:
语法: Merlin32.exe [-V] <宏文件路径> <源文件路径>
例子: Merlin32.exe -V c:\Merlin\Library c:\Source\Cogito\Cogito.s
一、如需汇编单个.asm文件,则把<宏文件路径>与<源文件路径>都为.asm文件及其所在路径
例子: Merlin32.exe "c:\AsmProj\test.asm" "c:\AsmProj\test.asm"
- 参数1:[-V] 可选可不选,设置会生成一个文本文件输出装配过程
- 参数2:<宏文件路径> 包含所有宏定义文件
- 参数3:<源文件路径> 要组装的主要源文件(或链接文件)的路径
汇编过程中,会输出以下装配信息:
C:\AppleIIgs\Merlin\>Merlin32.exe -V C:\AppleIIgs\Merlin\Library C:\AppleIIgs\Source\Cogito\Cogito.s
Merlin32.exe v 1.0, (c) Brutal Deluxe 2011-2021
+ Assemble project files... - 组装项目文件...
o Loading Sources files... - 加载源文件
- Cogito.s
- Cogito.Main.s
- Cogito.Bout.s
o Loading Macro files... - 加载宏文件
- Int.Macs.s
- Locator.Macs.s
- Mem.Macs.s
- Misc.Macs.s
- Sound.Macs.s
- Tool220.Macs.s
- Util.Macs.s
o Check for duplicated Macros... - 检查重复宏
o Decoding lines types... - 解码行类型
o Process local/variable Labels... - 处理局部变量/标签
o Process Asterisk lines... - 处理星号"*"行
o Build External table... - 构建外部表
o Build Equivalence table... - 构建等价表
o Build Variable table... - 构建变量表
o Process Equivalence values... - 处理等价值
o Replace Lup with code...
o Replace Macros with Code...
o Process MX directives...
o Process Conditional directives...
o Build Label table...
o Check for duplicated Labels...
o Check for unknown Source lines...
o Check for Dum lines...
o Compute Operand Code size...
o Compute Operand Data size...
o Compute Line address...
o Build Code Line...
o Check for Err lines...
o Build Data Line...
o Build Object Code...
+ Link project files...
o Build OMF output file...
=> Creating OMF file 'C:\AppleIIgs\Source\Cogito\Cogito'
+ Create Output Text file...
=> Creating Output file 'C:\AppleIIgs\Source\Cogito\Cogito_Output.txt'
一切正常的话,会输出一个二进制文件(无扩展名),如果设置-V参数,则会生成一个构造过程的文本文件。通过这个输出文件,可以检查预处理器(替换宏、扩展 Lups、解析局部标签、计算表达式等)、汇编器(寻址、寄存器大小、目标代码等)和链接器(多个ORG、重定位处理等)的工作。
2、输出文本解析
假设我们输入了如下程序:
;【示例程序】
START ORG $0300
LDY #$00
LDX #$06
LOOP LDA $6000,X
JSR $FCA8
LDA $C030
DEY
BNE LOOP
DEX
BPL LOOP
BRK
将此文件保存为Program.asm,送至Merlin32汇编后(带-V)会输出如下文本:
------+-------------------+-------------+----+---------+------+-----------------------+-------------------------------------------------------------------
Line | # File Line | Line Type | MX | Reloc | Size | Address Object Code | Source Code
------+-------------------+-------------+----+---------+------+-----------------------+-------------------------------------------------------------------
1 | 1 114.asm 1 | Directive | 11 | | 0 | 00/8000 | START ORG $0300
2 | 1 114.asm 2 | Code | 11 | | 2 | 00/0300 : A0 00 | LDY #$00
3 | 1 114.asm 3 | Code | 11 | | 2 | 00/0302 : A2 06 | LDX #$06
4 | 1 114.asm 4 | Code | 11 | | 3 | 00/0304 : BD 00 60 | LOOP LDA $6000,X
5 | 1 114.asm 5 | Code | 11 | | 3 | 00/0307 : 20 A8 FC | JSR $FCA8
6 | 1 114.asm 6 | Code | 11 | | 3 | 00/030A : AD 30 C0 | LDA $C030
7 | 1 114.asm 7 | Code | 11 | | 1 | 00/030D : 88 | DEY
8 | 1 114.asm 8 | Code | 11 | | 2 | 00/030E : D0 F4 | BNE LOOP
9 | 1 114.asm 9 | Code | 11 | | 1 | 00/0310 : CA | DEX
10 | 1 114.asm 10 | Code | 11 | | 2 | 00/0311 : 10 F1 | BPL LOOP
11 | 1 114.asm 11 | Code | 11 | | 1 | 00/0313 : 00 | BRK
------+-------------------+-------------+----+---------+------+-----------------------+-------------------------------------------------------------------
- Line(行号): 代码行号(从1到N)
- # File Line(文件与行号):源文件编号
- Line Type(行代码类型):源代码行的类型:NOP、注释、指令、等效、宏、代码或数据
- MX(累加器/索引寄存器大小):分辨Merlin32处理的是8-Bit代码还是16-Bit代码,MX 值通常由 MX 指令或 SEP/REP 操作码修改
- Reloc(重定位):对重定位的代码进行移位等操作的字节数
- Size(大小):每个代码的字节数
- Address(地址):总线地址(16位),若使用ORG指令,则从第一个地址开始。
- Object Code(目标代码):你的代码用机器码的表示
- Source Code(源代码):你的代码
3、限制
在上一个版本(Merlin16+)中由于内存实在极为有限,因此诞生了以下规则:
- 源文件不可大于64KB
- 在源文件中,每一行字符数不可大于255个
- 在源文件中,每个标签(LABLE)字符数不可大于26个
- 操作数部分字符数不可大于80个(例如应用标签)
- 外部文件的数量限制为 255
- 宏可嵌套大于15个深度
- 条件不可嵌套到8个深度
- 符号表限制为 4096 个长度 < 12 的符号和 2048 个长度 < 12 的符号
Merlin 32可忽略此限制,但如果需要移植到汇编器的上一个版本(Merlin16,有些机器只能用Merlin16)确保程序符合以上述规则。
4、语法
本节是6502汇编语言中必须涉及到的一点,在源代码中,分为四个列:
- LABEL(标签):在汇编中,指的是程序的地址,要想应用或转移至此地址可用此标签。标签可以是分支位置的标签、新宏的名称、变量的名称等。
- OPCODE(操作码):指令中对CPU指定的操作,伪指令中指对编译器的操作,支持65c816操作码。
- OPRAND(操作数):OPCODE(操作码)所要进行操作的对象。可以是立即数,标签(地址)以及宏参数。
- COMMENT(注释):以分号“;”开头,编译器会完全忽略分号“;”后面的注释。
Merlin 32对标签、宏、操作数、变量、等式等区分大小写……操作码写LDA或lda是可以的,但是PushLong和pushlong则不是同一个宏。
5、格式
每一列可以用空白字符(如空格或Tab制表符)来隔开,如图所示:
在用Windows的文本编辑器中,不用担心缩进的问题,只需在单独的列中添加几个空格或制表符即可,注意标签字符数,规范整齐的代码更能让人阅读。
6、数据表示
在6502汇编语言中都必然会出现诸如"#"、"$"之类的符号,这些符号都用于表示此操作数。下面是有关这些符号的解析。
6-1 立即数
带有井号"#"的表示立即数,没有符号的话,一般是立即数或者是地址,但大部分情况下汇编器有可能会识别不出(因为不清楚这个操作数是立即数还是指地址还是什么)。但对于只需要一种操作数(数据或地址)的操作码,如REP、PEA、JSR、MVN、STA……就不需要添加"#"了。
6-2 进制
LDA #0 ; 十进制立即数
LDA #$2000 ; 十六进制立即数
LDA #%11110000 ; 二进制立即数
LDA 0 ; 十进制地址
LDA $2000 ; 十六进制地址
LDA %00100000 ; 二进制地址
- ' $ '美元符号代表十六进制(与Intel架构不同,Intel中' 0x '才是表示十六进制)
- ' % '百分号代表二进制(Intel中' 0b '表示二进制)
- 前面什么都不写表示十进制
6-3 分割字节
某些操作数表达式可能会大于累加器大小的值,通过在' # '后面使用一些运算符("<",">","^"):
6-4 字符串
在Merlin中,字符串是一组用但引号(')或双引号(“)括起来的ASCII字符组合。
48 65 6C 6C 6F ASC 'Hello' ; 使用单引号, 最高位置0 (标准ASCII)
C8 E5 EC EC EF ASC "Hello" ; 使用双引号, 最高位置1 (文本屏幕)
7、标签
标签可以在没有任何操作码的情况下使用。这种情况下这个标签的地址值与下一行相同。
一般标签都是全局标签,以右中括号" ] "或冒号" : "开头的标签则是局部标签(又称本地标签),局部标签是不能在宏内部或与ENT/EXT指令一起使用的。
以右中括号" ] "开头的局部标签只能用于向后分支,循环会使用向后面的代码中最近的同名局部标签。例如:
LDX #$00
]LOOP LDA TABLE1,X ; 第1行局部标签
BEQ NEXT
INX
BRA ]LOOP ; 转移到第1行局部标签
NEXT LDY #$00
]LOOP LDA TABLE2,Y ; 第2行局部标签
BEQ END
INY
BRA ]LOOP ; 转移到第2行局部标签
END RTS
以冒号" : "开头的局部标签向前向后都能用,范围仅在全局标签之内。
BEGIN CPX #$A0 ; :LOOP局部标签定义在全局标签BEGIN和END之内
BEQ :LOOP
LDX #$00
:LOOP LDA TABLE1,X
BEQ END
INX
BRA :LOOP
END RTS
8、表达式
Merlin支持表达式计算,所支持的符号有:
< = > # 小于、等于、大于、不等于
+ - 加、减
* / 乘、除(整数)
& . ! 与、或、异或
- 一元否定
1024+$FF ; 1024 + 255 = 1279
"K"-"A"+1 ; Ascii K - Ascii A + 1 = $CB - $C1 + 1 = 11
LABEL+2 ; LABEL - 2
LABEL2-LABEL1 ; LABEL2 - LABEL1 = 两个LABLE之间的字节数
*-2 ; 当前地址 - 2
#$9F&"A" ; $9F AND $C1 = $81 (Control-A)
LABEL1/LABEL2 ; 0 当 LABEL1 < LABEL2, 1 当 LABEL1 >= LABEL2
9、变量
变量名称区分大小写,并且始终以右中括号" ] "开头。主要用于宏和循环。
; 声明变量
]LINE = $2000 ; 第一条地址是 $E1/2000
; 更改变量
]LINE = ]LINE+160 ; 下一行
DA ]LINE
不允许正向引用变量,因此请在使用变量之前先定义变量。
三、Merlin伪指令
伪指令是汇编不可缺少的一部分,不同汇编器伪指令也不同。6502与65c816指令集、寻址方式等不在此处赘述,本节仅讨论伪指令及其作用,每小节最底下是程序例子。
1、数据定义类
数据定义类中有关定义字符串的伪指令是服务于Apple II平台的汇编环境,如INV,FLS等指令,因为这些指令定义的字符串的以Apple II的ASCII字符映射表来分配的。
EQU EQUivalence - 等于
用于定义常量,为各个值赋予名称。不允许正向引用,因此在使用常量之前(大多数时候在程序开始时)先定义常量,可用等于号“=”代替。
HEX define HEXadecimal data - 定义十六进制数据
此指令不必在标注十六进制符号‘$’,用于标定十六进制数。可用逗号或空格隔开,其个数不得是奇数,例如:
00 01 02 03 HEX 00010203
00 01 02 03 HEX 00,01,02,03
00 01 02 03 HEX 0001,0203
不能这样: HEX 000,102,03
DFB或DB DeFine Byte - 定义字节
操作数即是数据,以字节为主。用逗号隔开,支持进制符号。除非使用>符号(获取高字节),否则始终取表达式的低字节。
DA或DW Define Address or Define Word - 定义地址/定义字
操作数即是数据,以两个字节(一个字)为主。用逗号隔开,支持进制符号。默认低字节。
ADR Define ADdRess - 3 bytes - 定义三字节地址
操作数即是数据,以三个字节为主。用逗号隔开,支持进制符号。默认低字节。
ADRL Define Long ADdRess - 4 bytes - 定义双字地址
操作数即是数据,以四个字节(双字)为主。用逗号隔开,支持进制符号。默认低字节。
DS Define Storage - 定义存储区
为数据提供字节保留空间(设为$00),可提供两个操作数。第一个操作数表示要填充空间的数量,反斜杠"\"表示填充至下一个页(255字节)的边界。第二个操作数是要填充的值(省略第二个操作数默认为$00)
ASC define ASCii text - 定义ASCII码数据
此伪指令用于定义ASCII码字符或字符串数据
DCI Dextral Character Inverter - 数字字符转换
这将在目标代码中放置一个分隔的ASCII字符串,最后一个字符的高位与其他字符相反
INV define INVerse text - 定义反转文本
反色仅适用于大写与个别符号,文本会以反色的形式向目标代码发送字符串数据
FLS define FLaShing text - 定义闪烁文本
闪烁仅适用于大写与个别符号,文本会以闪烁的形式向目标代码发送字符串数据
REV define REVerse text - 定义反向文本
此伪指令会以相反的顺序向目标代码发送字符串数据
STR define STRing with leading length byte - 带长度前导字节定义字符串
此伪指令在字符串数据的首部给出字符串所用字节数,即字符串的长度。
STRL define Long STRing with leading length word - 带长度前导字定义字符串
此伪指令在字符串数据的首部给出字符串所用字(WORD)数,即字符串的长度。
2、程序控制类
LUP Loop - 循环
仅一个操作数,用于规定循环次数,例如:
LUP 10
ASL
则表示循环ASL十次。如果想要标签的程序循环,则在标签后打上“@”号,例如:
LUP 3
LOOP@ INC $004C
BNE LOOP@
LOOP则会循环执行3次。
DO...ELSE...FIN / IF...ELSE...FIN Condition - 条件
有时候程序会在不同型号的机器上运行,我们的程序要根据机子的配置、型号等因素做出合理的安排与处理,根据不同情况构建不同的代码(6502/65c02处理器、8bit/16bit环境、ROM/RAM上下文、宏内部代码……)在Merlin 32中使用条件伪操作码有两种方法:DO...ELSE...FIN与IF...ELSE...FIN。
ORG ORiGin - 起始地址
3、宏类
MAC Macro - 宏
EOM或"<<<" End of Macro - 结束宏
USE USE - 使用宏定义
4、文件类
PUT
PUTBIN
DSK
SAV
LNK
TYP
5、格式化类
LST
EXP
PAU
PAG
AST
SKP
TR
6、其他类
DUM
DEND
END
CHK
DAT
ERR