W6502cpu的汇编

简单学习一下平常常见的红白机的cup编译原理

一个40年前的老汇编语言🤣

1.基本实践

这里没有找到6502的汇编模拟器,所以根据这里的网页版的模拟器进行学习Easy 6502 (codediy.github.io)

(注意记得科学上网)

LDA #$c0  ;加载十六进制$c0到寄存器A
TAX       ;将寄存器A的值传递给寄存器X
INX       ;将寄存器X的值递增
ADC #$c4 ;将十六机制的$c4加到寄存器A
BRK       ;Break跳出执行

1.标识含义

1.6502中以 $开始的数字表示为16进制(hex)格式

2.6502中以#开头的十六进制是一个数字字面量

3.其他的十六进制数字都表示内存的地址

4.显示器模拟器使用内存的$0200$05ff保存对应的像素值

十六进制 $00$0f代表16种颜色 ($00 是黑色,$01 是白色)

2.通过debugger单步调试,查看具体的运行逻辑(勾选上调试Debugger开始调试)

1.点击Step,执行第一个指令

发现:

A 从$00 变为了 $01

PC= 从 0600 变为了 0602

分析:

汇编代码 LDA #$01 加载十六进制$01到寄存器(下面详解)A

2.再次点击Step 执行第二个指令

发现:

显示模拟器的第一个像素变为了白色

分析:

第二条指令STA 将寄存器A的值 storing the value $01 存储到内存$0200表示在第一个像素绘制白色

接下来点击4次Step观察寄存器A的值(原理相同)

尝试:

  1. 试着修改前3个像素的颜色
  2. 试着修改内存中的最后一个像素的颜色 (内存地址是) $05ff
  3. 试着绘制更多的像素

2.寄存器与标志位

1.A, XY 寄存器

A 称为累加器(accumulator)

每个寄存器保存一个字节(byte),也就是8位二进制

大多数操作是操作A,X,Y3个寄存器

2.SP表示栈(stack)指针(栈下面详解)

现在只需要知道SP每次都以字节的大小增减

3.PC程序计数器

表示程序执行的指令位置(可以看做汇编脚本的行数, js模拟器中代码从内存的$0600开始,所以默认 PC 开始值就是$0600)

4.最后是CPU的标志位

每个标志位(flag)使用一个二进制(0或者1),使用一个字节(8个二进制)保存了所有的标志位(flag) .

标志位根据前一条指令的执行结果进行赋值

3.指令集

1.分析指令

LDA #$c0  ;加载十六进制$c0到寄存器A
TAX       ;将寄存器A的值传递给寄存器X
INX       ;将寄存器X的值递增
ADC #$c4 ;将十六机制的$c4加到寄存器A
BRK       ;Break跳出执行

单步调试,观察寄存器 A和寄存器X

ADC #$c4指令中变化异常:

正常情况下$c4 + $c0 应该得到 $184

然而cpu只是将寄存器A设置为 $84

异常原因:

$184已经超出了一个寄存器能够容纳的最大的值$FF寄存器只保存了小于$FF的内容

cpu并没有计算错误,如果仔细观察可以发现进位标志位(carry flag)被设置为1

这就是加法中进位标志位的用法

2.分析指令

LDA #$80
STA $01
ADC $01

1.比较ADC #$01 and ADC $01的区别

前一个将值 $01累加到寄存器 A

后一个将内存中 $01 累加到寄存器A

汇编编译,然后打开监控器Monitor

2.汇编编译,然后打开监控器Monitor ,单步调试指令

监视器:

用来观察指令运行时内存中的值

STA $01 将寄存器A的值转存到内存中$01,

然后 ADC $01 将内存中的值$01 累加到寄存器 A

$80 + $80应该是 $100,超过最大值进位

寄存器 A 设置为 $00 进位标志位置1( 另外零标志位(zero flag)也置1)

3.指令合集

6502.org: Tutorials and Aids

Obelisk.me.uk

3.分支

6502汇编包含多个分支指令,都是基于标志位的状态进行判断,实例中以BNE(Branch on not equal)进行说明

LDX #$08
decrement:
DEX
STX $0200
CPX #$03
BNE decrement
STX $0201
BRK

1.加载 $08 到寄存器 X . 接着是一个label声明.用来作为跳转标记

2.自增寄存器X, 然后存储到内存中 $0200 (the top-left pixel),并与 $03进行比较. CPX 指令比较寄存器 X 与另一个值.如果相等标志位 Z设置为 1, 否则设置为00

3.BNE decrement,会根据标志位 Z 是否为 0进行跳转.也就是直到指令CPX得到相等的结果为1,然后将寄存器 X 存储到 $0201, 最后结束

在汇编语言中,经常使用label作为分支跳转标记.代码编译后label将转换为相对地址

因此分支指令可以前进或者后退,然后只能在256bytes使用branch分支指令,如果需要跳转更远的就需要jump指令.

4.寻址模式Addressing modes

内存寻址原理:

6502使用16bit地址总线,着处理器可以访问到 65536(2的16次方) bytes的内存

通常使用十六进制 $0000 - $ffff表示

内存寻址的方式:

可以使用内存监控器观察内存.其中的设置值都是十六进制表示 ,比如观察 $c000, enter c000 开始的 10 个十六进制

1.绝对地址: $c000

使用绝对地址时,所有的内存地址都可以作为参数使用. 比如:

STA $c000 ;存储寄存器A的值到$c000内存处

2.零页定位: $c0

所有地址都支持绝对地址的指令(包括jump指令)都可以接受一个单字节地址参数, 这种地址称为零页地址缩写.仅仅用来访问开始的256bytes内存

3.零页,X: $c0,X

从零页偏移寄存器 X 的内存地址.比如:

LDX #$01   ;X is $01 寄存器x设置为01
LDA #$aa   ;A is $aa
STA $a0,X ;Store the value of A at memory location $a1 存储到$a1
INX       ;Increment X
STA $a0,X ;Store the value of A at memory location $a2 存储到$a2

4.零页,Y: $c0,Y

等价于上面的,不过只能用来寄存器X的操作 LDXSTX.

5.绝对地址,X 和 绝对地址,Y: $c000,X,$c000,Y

是分页的绝对地址版本.比如:

LDX #$01
STA $0200,X ;Store the value of A at memory location $0201 存储到$0201

6.立即寻址: #$c0

立即值不涉及内存操作,只是操作具体数值. 比如, LDX #$01 加载数值 $01 到寄存器 XLDX $01加载内存地址$01中的值到寄存器 $01 into the X

7.相对寻址: $c0 (or label)

相对寻址通常在分支指令中使用,接受一个单byte地址.

编译并打印 Hexdump查看汇编代码

8.Implicit

一些指令不操作内存地址,比如 (e.g. INX – 递增寄存器 X ). 表示操作指定的隐式地址.

9.Indirect(间接寻址): ($c000)

例如:

LDA #$01
STA $f0
LDA #$cc
STA $f1
JMP ($00f0) ;dereferences to $cc01

上面的例子中, $f0 存储的值 $01$f1 存储的值 $cc. 指令JMP ($f0) 接受$f0$f1 (中的$01$cc) 然后组成一个绝对地址 $cc01, 试着单步调试 JMP指令,后面的 Jumping会再次说明.

10.索引间接寻址Indexed indirect: ($c0,X)

寄存器间接寻址, 根据$c0和寄存器 X 的值组成的绝对地址寻址,比如:

LDX #$01
LDA #$05
STA $01
LDA #$07
STA $02
LDY #$0a
STY $0705
LDA ($00,X)

A->0102(0705).X->01(0705)

11.间接索引Indirect indexed: ($c0),Y

更为负责的寻址方式,注意字节序

LDY #$01
LDA #$03
STA $01 ;$01->#$03
LDA #$07
STA $02 ;$02->#$07
LDX #$0a
STX $0704 ;$0704->#$0a
LDA ($01),Y ($01)

5.调用栈

1.作用

用来进行存储后进先出的值

包含push,pull(pop)2个操作

2.理解

栈的当前深度使用栈指针寄存器保存

栈保存在内存的 $0100$01ff

栈指针初始化为$ff, 指向内存的$01ff

等压入byte值时则变为 $fe, 也就是 $01fe.

3.操作指令

有2个栈操作指令 PHA and PLA, “push accumulator” and “pull accumulator”.

压入累加器值到stack.弹窗stack顶部值到累加器

LDX #$00
LDY #$00
firstloop:
TXA
STA $0200,Y
PHA
INX
INY
CPY #$10
BNE firstloop ;loop until Y is $10
secondloop:
PLA
STA $0200,Y
INY
CPY #$20 ;loop until Y is $20
BNE secondloop

寄存器X 保存像素颜色,寄存器 Y 保存像素位置.绘制像素.

6.跳转Jumping

与分支指令功能相似,有2点不同。首先, jumps是无条件执行的, 还有,接受的是two-byte绝对地址. 小的代码程序中不需要操心后者.大型程序中jump是唯一的远距离跳转方式.

1.JMP

JMP 无条件跳转,比如:

LDA #$03
JMP there
BRK
BRK
BRK
there:
STA $0200

2.JSR/RTS

JSRRTS (“jump to subroutine” and “return from subroutine”) 经常一起使用. JSR 用来从当前位置跳转到另一个位置. RTS 返回到跳转前的位置.类似于函数调用与返回.

实现机制是,指令 JSR将下一条指令的地址压入栈,然后跳转到目标. RTS则弹出栈中的位置,跳转到对应指令 例如:

JSR init
JSR loop
JSR end

init:
LDX #$00
RTS

loop:
INX
CPX #$05
BNE loop
RTS

end:
BRK

7.创建一个贪吃蛇游戏

就不说了,看原文吧

Easy 6502 (codediy.github.io)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇