ISO/IEC C++ China Unofficial

 找回密码
 立即注册
搜索
查看: 353|回复: 31

水文 - 一个容错率较高GBA模拟器的实现

[复制链接]

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
发表于 2019-6-27 10:31:40 | 显示全部楼层 |阅读模式
之前貌似写过一篇关于FC模拟器的漏文, 之后搁置
原因: 当时是写模拟器的时候水平处于一个完全不开化的状态
      : 貌似别的模拟器更有意思
      : NES真的麻烦, 扩展设备乱七八糟, 特别是音频芯片, 可能会比较难写, 我真是怕了
      : 知乎某人已经写过了, 写的比我好得多, 珠玉在前, 就不继续献丑了
      : 准备写这篇文章的这个程序我已经完成了60%了, 不慌, 可以慢慢写 应该能做到有始有终(?)

这篇文章大部分内容和思想, 方法来自以下 -
GBATEK http://problemkaputt.de/gbatek.htm
GBA Programming Manual v1.1.pdf 南美任天堂官方手册, 有兴趣可以自己搜索下载pdf
Jonathan Harbour - Programming GBA.pdf 市面唯一一部出售的关于GBA编程的书籍
tonc.pdf http://coranac.com/tonc/text/
arm_arm.pdf https://www.scss.tcd.ie/~waldroj/3d1/arm_arm.pdf
DDI0210B.pdf http://infocenter.arm.com/help/topic/com.arm.doc.ddi0210c/DDI0210B.pdf
CowBiteSpec https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm#OAM%20(sprites)
DUI0068.pdf http://infocenter.arm.com/help/topic/com.arm.doc.dui0068b/DUI0068.pdf
VBA, VBA-M, mGBA 以及其他不知名开源模拟器的源代码
有兴趣可以直接去看原文/源码

前置知识 -
         稍微的操作系统的知识, 了解中断是个啥..
         稍微的c语言编程, 有c++和汇编更好
         稍微的信号处理知识, 会写简单的可调属性(频率, 占空比)的方波发生器就行了
         稍微的图形处理知识, 线性代数以及会调用一些简单的平台blit贴图知识即可
         稍微懂点就行了, 因为我也只是稍微懂点

狗屁说完, 开始正文


LGD是TI8冠军!-2018.8.24 16:30留
回复

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 10:53:59 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 10:59 编辑

就不扯什么乱七八糟的历史了
GBA的关于GBA渲染工作的原件包括如下几个大元件

CPU   -    ARM7TDMI, ARM v4t 版本的芯片
GPU   -    没名字,以后就叫他 AGB-GPU
APU   -    没名字,以后就叫他 AGB-APU
DMA   -    快速设备拷存, 4个,每一个都有一种可被用作特殊用途的用法 (GPU光栅操作, 以及配合定时器进行音频传输)
TIMER   -    定时器, 也是4个
RS串口   -    暂时不讲, 因为现在很少有人联网玩GBA游戏了
输入控制器   -    就是那几个按钮, ABLR 方向键, 开始选择
GammPAK卡带以及外围设备   -     (EEPROM, FLASH 以及RTC, 光学传感器等)
BIOS

一个个讲, 在此之前说一下GBA的内存布局
GBA可寻址到28位的地址空间(0~0x10000000)

00000000-01FFFFFF - BIOS, 总线宽度32位, 这部分前32K是BIOS程序的放置处, 注意BIOS不可写. 32K往上读将读不到数据.

02000000-02FFFFFF - 256K RAM 总线宽度32位, 衔接在主板上的256K内存, 他的读写性能要比卡带内存好一点(常规).
        读写前256K这部分区域外的地址会被镜像到256K内存内. (addr &= 0x3FFFF)

03000000-03FFFFFF - 32K RAM 总线宽度32位, 芯片内部的32K内存, 他的读写性能是最好的, 很多时候卡带会把代码拷贝到此内存执行..
        读写前32K这部分区域外的地址会被镜像到32K内存内. (addr &= 0x7FFF)

04000000-04FFFFFF - 内存IO 总线宽度32位, 芯片内部的内存IO空间(前1K空间有数据, 以及少部分IO在2K之内),
       此处只有一个特殊镜像, 以后会说明.

05000000-05FFFFFF - BG/SP调色板 总线宽度16位, 前1K为有效数据, 读写之后超出范围的地址会被镜像 (addr &= 0x3FF)
       前512字节为BG调色板, 后512字节为SP调色板

06000000-06FFFFFF - GPU显存 总线宽度16位, 前96K为有效数据, 通常而言前64K给卷轴用, 后32K给精灵用
      读写之后超出范围的地址会被镜像, 这部分镜像不是单纯的线性镜像映射

首先每0x20000镜像一次,
在这0x20000里面, 前0x17FFF都是镜像0x06000000 ~ 0x06017FFF,
最后这32K映射镜像灵 0x06010000~0x06017FFF
如图----------------------------------------------------------------------------------------------------------------------
0x00000 | 0x08000 | 0x10000       |            0x18000            |            0x20000
--------------------------------------------------------------------------------------------------------------------------
0x20000 | 0~0xFFFF      64K Bg卷轴         |  0x10000~0x17FFF 32K精灵      |   0x18000 ~ 0x1FFFF 32K精灵镜像
--------------------------------------------------------------------------------------------------------------------------
0x20000 | 0~0xFFFF      64K Bg卷轴         |  0x10000~0x17FFF 32K精灵      |   0x18000 ~ 0x1FFFF 32K精灵镜像
--------------------------------------------------------------------------------------------------------------------------
0x20000 | 0~0xFFFF      64K Bg卷轴         |  0x10000~0x17FFF 32K精灵      |   0x18000 ~ 0x1FFFF 32K精灵镜像
--------------------------------------------------------------------------------------------------------------------------
0x20000 | 0~0xFFFF      64K Bg卷轴         |  0x10000~0x17FFF 32K精灵      |   0x18000 ~ 0x1FFFF 32K精灵镜像
--------------------------------------------------------------------------------------------------------------------------
0x20000 | 0~0xFFFF      64K Bg卷轴         |  0x10000~0x17FFF 32K精灵      |   0x18000 ~ 0x1FFFF 32K精灵镜像
--------------------------------------------------------------------------------------------------------------------------

07000000-07FFFFFF - 精灵属性内存 总线宽度32位, 前1K为有效数据, 读写之后超出范围的地址会被镜像 (addr &= 0x3FF)
08000000-09FFFFFF - GBA卡带内存 总线宽度16位 - Wait State 0
0A000000-0BFFFFFF - GBA卡带内存 总线宽度16位 - Wait State 1
0C000000-0DFFFFFF - GBA卡带内存 总线宽度16位 - Wait State 2

这三部分的32MB内存读写的都是一样的数据, 常规状态不可写, 对于Flash卡带, EEPROM 是可写的 (跟NES的mapper类似)
其中区别在于后面的Wait State,
每部分区域的WaitState状态都可以被设置, (其中最主要的用途是用来访问慢速设备).....

0E000000-0FFFFFFF 电池, 总线宽度为8位, 前64K有效, 读写之后的做镜像, 卡带的SRAM, 主要用来保存数据/存档
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:08:19 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 11:18 编辑

---------------------------------------------------------CPU概览---------------------------------------------------------------------
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:09:05 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 11:15 编辑

GBA有两颗CPU核心
一颗ARM7TDMI芯片, 32位RISC芯片, 主频率 16.78MHZ,
一颗Z80改版夏普LR35902芯片常态频率 4.19MHZ, 可变频至8.38MHZ
LR35902芯片是辅助芯片, 主要用来兼容GBC/GB游戏解

ARM7TDMI是一块很有趣的芯片,

{ ====== 跟主流的X86等CISC芯片相比有如下"特点" . =========== }
1. <通用寄存器的增加>
2. <ALU指令不访存, 读写内存需要专门的指令>
3. <ALU可以同时计算和移位>
4. <同时存在两套独立的指令集, 常规ARM7指令集和THUMB指令集>
5. <未对齐的访存可能出现异常行为>

{ ====== ARM7TDMI使用的数据类型 (数据类型跟主流的x86不太一样). ======== }
字(WORD):32位数据类型, uint32_t/int32_t
半字(HALFWORD):16位数据类型, uint16_t/int16_t
字节(BYTE):8位数据类型, uint8_t/int8_t
在ARM7TDMI中, 所有的寄存器都是32位的,
所有的ALU也大多只有32位的操作

{ ====== ARM7TDMI中的内存对齐 ======== }
在ARM7中,
Word基于4字节对齐,
HalfWord基于2字节对齐,
Byte不需要对齐
对齐的既是数据, 也是指令
ARM7指令基于4字节对齐,
Thumb指令基于2字节对齐.
未对齐的访问基本算是UB行为, 这部分UB行为有些会有很奇怪但是游戏会利用的feature..

{ ====== GBA使用小端格式 ======== }
所以所有讨论都是基于小端.
你可能需要点基本的ARM7编程知识,
和一些简单的C语言/计算机底层知识(比如汇编...emmm..) 的才能看懂CPU的部分内容

{ ====== 寄存器/中断/Thumb, ARM7指令 概览======== }
    (*  寄存器***)
    (*   ---------- *)
      能同时被使用的有r0-r15, 还有一个CPSR, 如果在非系统/用户模式下还有一个SPSR寄存器
      在这些寄存器中,
      部分寄存器全模式下只有一份, 比如(r0-r7, r15和CPSR)
      部分寄存器在每个不同模式可能会有一份独立的寄存器, 此时使用寄存器就是当前模式下的寄存器
      比如在管理模式你用r13就是管理模式的r13, 在用户模式, 你用的就是用户模式下的r13,
      同名寄存器在不同模式是相互独立的寄存器 (如果此模式存在备份寄存器的情况下...)
      这些寄存器都是32位的

      r0-r7: 这八个寄存器全模式都只有一份, 独一无二的杂鱼寄存器.没什么特别用处, 基本是编程控制计算使用的主力寄存器
      r8-r12: 这五个寄存器除了快速中断模式(FIQ)有一份独立的寄存器, 其余模式共享一份r8-r12,
                 这是为了快速中断特意扩展的保持寄存器集, 然而GBA不适用FIQ模式,
               好像许多ARM下的操作系统也不支持这中断模式, 所以除了FIQ外, 其余模式下此寄存器集的性质等同于r0-r7

      r13-r14: 这两个寄存器每个模式都有一份独立的寄存器,
               r13是栈指针,
               r14是调用返回寄存器 (LR LINK)

      r15: 也就是PC程序指针, 独一份
      CPSR: 独一份
      SPSR: 除了系统/用户模式, 之外的模式都有独立的SPSR寄存器

    (*  调用返回寄存器r14 (LR LINK)*********)
    (*----------*_*)
      大家应该多少懂点C语言的函数调用8?
      比如这个简单大家都知道的MessageBoxA 函数
      反汇编成x86

      push MB_OK
      push "Hello World"
      push "Win32SDK"
      push nullptr
      call MessageBoxA
      cmp eax, 32

      这个call调用会把下一条指令的地址 (cmp eax, 32)轧入栈中
      过程结束后再ret 弹栈恢复PC 返回到 cmp eax, 32 执行
      而ARM7不会把下一条指令轧入栈中, 而是保持在 r14中
      r14基本就是起上述例子相等的作用
      除了方便外, 另一个用这条指令的理由是流水线的执行会导致你手动计算很难取到\
      当前指令导致的下一条指令地址, 比如在ARM7,
      由于流水线的关系, 你在执行当前指令的时候实际PC以及指向了下面两条指令的地址处了
      不同版本可能会有不同的流水线指令偏移, 这么写方便在各种不同版本共用取址,

    (*  CPSR/ SPSR (程序控制状态寄存器)*********)
    (*----------*_*)
      SPSR是CPSR的备份, 在中断发生处理器切换模式时, CPSR会自动拷贝到前往模式的SPSR中
      在ARM7中, CPSR的32位根据功能不同被分为不同的位域

      d0-d4:      这五位用来指示当前所处的模式, 共7种,

                  10000 用户模式 (USER)
                  10001 快速中断模式 (FIQ)
                  10010 中断模式 (IRQ)
                  10011 管理模式 (Supervisor)
                  10111 中止模式 (Abort)
                  11011 未定义模式 (Undef)
                  11111 系统模式 (System)

                  FIQ, IRQ都很蛤理解, 管理模式是使用软件中断SWI指令/芯片重启可以进入
                  未定义模式当执行到没有被定义的机器码的时候会触发该中断(异常)
                  中止模式用于访问到无效的内存地址和未对齐的地址(指令地址/内存地址)
                  在特权模式下, 也可以手动使用指令切换模式
                  注意在系统/用户模式下是没有SPSR的


      d5:         thumb标志
                  此标志:=1表示接下来将会执行thumb指令, 否则则是标准的arm7指令

      d6:         FIQ抑制标志, :=1 抑制FIQ中断
      d7:         IRQ抑制标志, :=1 抑制IRQ中断

      剩下的则是

      d31:        Z标志 结果为零 := 1, 否则为0
      d30:        N标志 结果负数 := 1, 否则为0
      d29:        C标志 结果进位 := 1, 否则为0
      d28:        V标志 结果溢出 := 1, 否则为0
      这些标志是用来判断指令执行的结果的, 等同与X86的PSW
      需要注意的是, 当类 减法指令执行时, 如果产生进位则C为0, 否则C为1     
      好了, ARM7中, CPSR就这么多东西, ,如下 --

      d31 - d28                d7                         d6           d5                 d4 d3 d2 d1 d0
      N Z C V                IRQ抑制标识            FIQ抑制标识    thumb标识               模式5位

    (*  中断-
    (*----------*_*)
      ARM7有如下几种中断

      Reset ;; 设备复位
      Data Abort ;;数据访问异常
      FIQ ;; 快速中断
      IRQ ;; 通用中断
      Prefetch Opcode ;; 指令预取异常
      Undef /SWI ;; 未定义指令, 软件中断

      优先级从高到低
      这些中断大同小异, 有着大致相同的执行套路, 各自可能又有微小的差别
      在GBA内部的BIOS中, 如果设备启用的了Debug功能, 不被处理的异常可能会由BIOS转交给卡带
      内部地址的 9FE2000h/9FFC000h处理
      否则, BIOS会直接返回

      大致套路:中断发生时, 把当前CPSR扔进目标模式的SPSR里,

      然后禁止中断,
      把下一条指令,(有的要加上一个微小的偏移)得地址
      扔进目标模式r14中
      设置CPSR的模式位域, 清除T标志使以后执行的指令总是ARM7指令
      设置PC为具体中断的中断地址, 中断调用完成, 中断最小延迟为5个时钟周期, 最大为29个时钟周期


    (*  具体中断的具体措施 (伪代码摘自ARM芯片手册卷 arm_arm.pdf::A2.6 Exceptions)-
    (*----------*_*)
      < Reset> ;; 设备复位
      CPSR[4:0] = 0b10011
      CPSR[5] = 0
      CPSR[6] = 1
      CPSR[7] = 1
      PC = 0x00000000

      < Undef> ;; 未定义指令异常
      R14_und = 下一条即将执行的指令地址
      SPSR_und = CPSR
      CPSR[4:0] = 0b11011
      CPSR[5] = 0
      CPSR[7] = 1
      PC = 0x00000004

      <SWI> ;; 软件中断
      R14_svc = SWI指令的下一条指令地址
      SPSR_svc = CPSR
      CPSR[4:0] = 0b10011
      CPSR[5] = 0
      CPSR[7] = 1
      PC = 0x00000008

      <IRQ> ;; 通用中断
      R14_irq = 下一条即将执行的指令地址+4
      SPSR_irq = CPSR
      CPSR[4:0] = 0b10010
      CPSR[5] = 0
      CPSR[7] = 1
      PC = 0x00000018

      < FIQ> ;; 快速中断
      R14_fiq = 下一条即将执行的指令地址+4
      SPSR_fiq = CPSR
      CPSR[4:0] = 0b10001
      CPSR[5] = 0
      CPSR[6] = 1
      CPSR[7] = 1
      PC = 0x0000001C

      <ABORT1>指令访问异常
      R14_abt = 下一条即将执行的指令地址 + 4
      SPSR_abt = CPSR
      CPSR[4:0] = 0b10111
      CPSR[5] = 0
      CPSR[7] = 1
      PC = 0x0000000C

      <ABORT2>内存访问异常
      R14_abt = 下一条即将执行的指令地址 + 8
      SPSR_abt = CPSR
      CPSR[4:0] = 0b10111
      CPSR[5] = 0
      CPSR[7] = 1
      PC = 0x00000010

      下一条即将执行的指令地址基于当前指令执行完成, 下一条指令执行之前的时间点,
      中断如何返回请参阅后面章节
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:14:22 | 显示全部楼层
    (*  Thumb, ARM7指令
    (*----------*_*)
      CPSR的T标志指示即将执行ARM7 /Thum指令集
      ARM7增加了许多通用寄存器, 除了读写和少部分指令可以访问内存,
      其余主要ALU计算指令的 目标和源操作数只能为 寄存器,
      减少了指令编码压力, 改善指令的并发性,
      ARM7 / Thumb指令各有特点.
      ARM7和Thumb指令都是定长编码, ARM是四个字节, Thumb是两个字节
      其中, ARM7所有指令都可以条件执行, 可使用所有的寄存器.
      Thumb模式只能使用r0-r7 SP, CPSR, SPSR PC, 和LR寄存器, 部分指令可以控制其他高位寄存器
      两者指令集也有区别, 后面将会详细讲解.
   
    (*  三级流水线/时钟周期类型
    (*----------*_*)
      ARM具有三级指令流水线,
      在讲流水线之前, 先了解一下CPU的执行流程.
      
      CPU会在当前PC指针处获取一个操作码(Opcode)
      然后对该操作码的各个位域进行识别, 从而知道这是哪一条指令
      知道哪条指令了, 就进行执行操作
      从内存中取操作码叫取指 (fetch)
      对该操作码的各个位域进行识别叫译码 (decode)
      就进行执行操作叫执行 (exec)
      
      理想情况下, 每个步骤一个时钟周期.老的CPU就是单流水线, 操作全部串行.
      后来人们变聪明了, 发现这些部件在某段执行区间内可以并发执行而不会破坏程序逻辑
      取指的时候可以同时执行和译码, 这下nb了, 许多指令都变成单指令周期,
      理想情况可以提升三倍效率, 然而许多情况效率完全无法最大化, 多多少少会有效率损失
      比如在无cache或者cache miss的情况下, 访存可能有额外的waitState
      很多时候清洗流水线可能还会导致拖慢程序流程, 非单周期指令的总线周期冲突等等...
      但是总的来说速度应该是稳中有升的
      
      流水线执行流程
      当流水线第一个时钟周期, 只有取指(因为此时没有可用的操作码可供解码, 更谈不上执行了).
      当流水线第二个时钟周期, 只有取指, 解码 (此时第一个时钟周期获得的操作码被解码).
      当流水线第三个时钟周期, 取指, 解码, 执行操作一气呵成, 以后每个周期都是 取指, 解码, 执行并发执行
   
      我们来模拟一下流水线的执行流程(ARM7状态下, 非thumb, 单周期指令).
      初始状态 流水线基址 PC:= 0x1000
      Clks1前: PC:= 0x1000
      Clks1执行: 取指0x1000, PC增加
      Clks1后: RawOp1000, PC:= 0x1004
      Clks2前: RawOp1000, PC:= 0x1004
      Clks2执行: 解码RawOp1000, 取指0x1004, , PC增加
      Clks2后: RawOp1004, RipeOp1000, PC:= 0x1008
      Clks3前: RawOp1004, RipeOp1000, PC:= 0x1008
      Clks3执行: 执行RipeOp1000, 解码RawOp1004, 取指0x1008, PC增加
      Clks3后: RawOp1008, RipeOp1004, PC:= 0x100C
      ClksN前: RawOp[BASE_PC+(N-2)*4] , RipeOp[BASE_PC+(N-3)*4], PC:= BASE_PC+(N-1)*4
      ClksN执行: 执行RipeOp[BASE_PC+(N-3)*4], 解码RawOp[BASE_PC+(N-2)*4], 取BASE_PC+(N-1)*4, PC增加
      ClksN后: RawOp[BASE_PC+(N-1)*4], RipeOp[BASE_PC+(N-2)*4], PC:= BASE_PC+N*4
   
      可以观察到, Clks3开始指令流就可以并发了
      即使不是单指令周期,
      ARM7内部指令流序列也会保证, 在每条指令执行前, PC到了PC+8/+4的地方
      这其实也暗示了, 管道里至少有一条指向当前执行下一条指令的等待被译码的RawOp
      执行后, RawOp变RipeOp, 新的RawOp被取指, 如此循环...
      
      你可以看到, CLks3开始指令的执行才变得有规律起来
      当流水线被清洗, 流水线时钟会重新开始计时直到第三个时钟周期, 又可以并发执行.
      当那些会改变PC的指令被执行时, 流水线会被清洗, 中断执行也会清洗流水线
      
    (* 时钟周期类型
    (*----------*_*)
      ARM7内部有四个周期类型,
      根据各个信号电平不同可以划分为以下4个
      
      为了简单, 给出总线信号的两个关联信号类型
      
      SEQ信号:序列化信号 电平高有效, 指示接下来的访问为连续线性内存访问
      nMREQ: 内存需求信号, 电平低有效, 指示接下来发生内存访问
      
      非连续周期(N) nMREQ:0, SEQ:0 :
      连续周期(S) nMREQ:0, SEQ:1 :
      
      非线性顺序访问内存导致的周期叫非连续周期
      线性顺序访问内存导致的周期叫连续周期
      
      举个例子:当前PC:=1000,
      下一条指令发生为无条件跳转到2000
      这时候PC不是本身的1000, 也不是接下来的1000后的线性地址1004开始的一个byte/halfword/word,
      内存访问的连续性发生了突变, 则是N周期
      N-S周期除了反映访问内存的情况外, 还会影响GBA卡带的WaitState状态,
      
      在非连续周期ARM会延长更多的MCLK时钟周期访问卡带, 浪费更多的时间取数据/指令.
      内部周期(I) nMREQ:1, SEQ:1 :
      CPU在执行内部操作, 这个时候不会发生内存访问, 卡带会预取缓存
      协处理器周期(C) nMREQ:1, SEQ:1 ::
      不管, GBA不处理协处理器
      每个周期类型占用一个时钟周期
      
      更多关于ARM7TDMI总线的资料, 请参阅CPU手册, 或者上海交大PPT文档的ARM7TDMI总线接口
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:18:33 | 显示全部楼层
---------------------------------------------------------CPU-ARM7指令---------------------------------------------------------------------
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:23:21 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 11:29 编辑

(* 访存指令 LDR/STR/LDM/STM/SWP
(*----------*_*)
  LDR:         从内存中读word或者 halfword,byte,无符号/符号扩展成word
  LDM:         指定地址多次连续读取进指定寄存器集, 寄存器入栈
  STM:         指定寄存器集连续写进内存, 寄存器出栈
  SWP:         寄存器值和指定内存地址的值交换

  LDR/STR就不用多说了, 读写内存是最基本的内存访问手段

  LDM/STM有两个用处,  1是保持指定的上下文寄存器,
                       2是单指令拷存(memcpy)减少代码尺寸.

  SWP, 类似与XCHG, 相似的是, 这条指令也是一条原子指令,
               提供对高级同步原语的底层支持

    (* 标准访存指令 LDR/STR/LDRB/STRB
    (*++++++++++*_*)        
      LDR,      读内存, 读得时候有五种类型 Word, 无符号 Halfword/byte, 后者会无符号扩展为Word放入Rd寄存器
                                                   有符号 Halfword/byte, 会符号扩展为Word放入Rd寄存器
      STR,      写内存, 这就简单多了, Word/HalfWord/Byte 三种写入形式
                机器码编码把LDR Word/Byte STR Word/Byte编码成一类, 其余的另外自成一码.寻址方式也是有部分不同.

      STR/LDR唯一区别是 Rd, LDR计算好地址把数据读到Rd, STR则是把Rd的数据写进内存      

      寻址计算有两种方式:

      <立即数寻址>   三个操作数 Rd, Rn, -/+Imm12
                       Rn+正负12位立即数地址范围 例子 LDRB Rd, [Rn, #960], 取Rn+960的一个字节
                                            例子 LDREQ Rd, [Rn, #4],l 上次执行结果测试是否相等, 相等取Rn+4的一个Word

      <寄存器移位>   五个操作数 Rd/Rn/Rm/ShiftImm5/Shift 移位类型

                      对于寄存器移位 有如下公式

                      地址:= Rn+Index
                      Index:= -/+ Rm 移位类型 5位移位立即数

                      移位类型有五种

                      LSL              逻辑左移
                      LSR              逻辑右移
                      ASR              算术右移
                      ROR              循环右移
                      RRX              逻辑右移1位, C位进入 MSB(第31位), 此时没有立即数操作数, 实际机器码编码中
                                                                     RRX是 ROR Imm5等于0的情况下发生的特殊指令
      关于移位类型和Imm5移位操作数

            当Imm5等于0的时候, 会发生很多诡异的移位行为
            想想也情有可原, Imm5是固定编码, 即-程序员是知道自己写了Imm5移位量的这个滑稽的无意义操作的.
            但是谁没事写移位量为0的移位给自己添堵呢?有啥用呢, 所以ARM就充分利用了这部分编码

            对于 LSL Rm还是Rm, 形如 Op Rd, Rn, Rm这样指令是没有额外的编码空间的, 所以把它编码成 Op Rd, Rn, Rm, LSL #0的形式
            对于 LSR Rm此时为0
            对于 ASR 测试Rm的第31位, 如果为1那么Rm:= 0xFFFFFFFF否则为0
            对于 ROR 此时执行RRX指令

      寻址方式 <后索引 | 前索引>

            后索引寻址: 直接用Rn作为访问地址, 访存完成后照寻址计算规则执行指令操作, 把计算的得到地址写回Rn
            前索引寻址: 照寻址计算规则执行指令操作获得地址, 访存完成后可决定地址是否写回Rn

            前索引和后索引类似于+i和i+的区别
            前索引寻址表示形式就是通常写法, 后面加个感叹号表示写回基地址Rn

            后索引寻址表示形式 LDR Rd, [Rn], #
            前索引寻址表示形式 LDR Rd, [Rn, #]

            前索引的几个例子:

            LDRNEB R1, [R0, #2]!
            LDR R8, [R0, R2,LSL #2]!
            STREQB R3, [R0]!
            后索引的几个例子:
            LDRB R3, [R0]
            STRHSB R3, [R0], #-0x17
            LDREQB R3, [R0], R2,RRX      

      T后缀声言:    指示访存是在用户模式下,即使是特权模式
                     注意, 不能和前索引配合使用
                     这个后缀主要用来验权, 通常是不使用的
                     比如你在用户模式触发了非法访存(可能是内存页保护),
                     当你在异常处修复了这个错误
                     LDRT用于模拟在用户模式下验证访存是否正常

    (* LDR/STR/LDRB/STRB 机器码
    (***********_*)     
      27 26 25 24 23 22 21 20    19 - 16     15 - 12           11 10 9 8 7 6 5 4 3 2 1 0
       0  1  I  P  U  B  W  L       Rn         Rd                   Offset (d11-d0)

      Rd: LDR读取加载到Rd, STR从Rd写入
      Rn:索引地址
      I ? 寄存器移位: 12位立即数
      U ? 正偏移 :负偏移
      B ? byte : word
      L ? 读LDR :写STR

      W和P联合指示

      W:0 P:0 后索引寻址写回
      W:0 P:1 前索引寻址不写回
      W:1 P:0 后索引寻址写回+T后缀声言
      W:1 P:1 前索引寻址写回         

      <寄存器移位>
      d11-d7: 移位立即数 (5bit 0-31)
      d6-d5:移位类型 0: LSL 1: LSR 2:ASR 3:ROR RRX:ROR移位量为0
      d4:0
      d3-d0:Rm

      立即数寻址都是常规套路, 不写了.

      LSL-                      常规操作
      LSR-
                                #Imm5bit = 0 索引为0
                                #Imm5bit!= 0 常规操作
      ASR-
                                #Imm5bit = 0 && Rm[31] = 1, 索引为0xFFFFFFFF
                                #Imm5bit = 0 && Rm[31]!= 1, 索引为0
                                #Imm5bit!= 0 常规操作
      ROR-
                                #Imm5bit = 0 RRX
                                #Imm5bit!= 0 常规操作   

    (* 标准访存指令 LDRH/STRH/LDRSB/LDRSH
    (*++++++++++*_*)   

      跟上面差不多, 12位立即数变成8位
      没有寄存器移位, 只有简单的Rn+/-Rm
      其余的都是一样的. 哦,还一点不一样,不能用T声言后缀     

    (* LDRH/STRH/LDRSB/LDRSH 机器码 1,用于寻址立即数.
    (***********_*)   
     27 26 25 24 23 22 21 20        19 - 16      15 - 12    11 10 9 8   7 6 5 4   3 2 1 0
      0  0  0  P  U  1  W  L           Rn           Rd       Offset1    1 S H 1   Offset2 (d22位那是数字1不是字母I)

      L:读写控制位?LDR:STR
      S:符号控制位?有符号:无符号
      H:half控制位?half:byte
      这几个标志位组合很混乱,在ARM7TDMI中只有如下几种情况
      (注意!!! S|H绝对不会为0,这也是这条指令可以被无二义识别的关键因素)

      L = 0, S = 0, H = 1 STRH halfword
      L = 1, S = 0, H = 1 LDRH
      L = 1, S = 1, H = 0 LDRSB
      L = 1, S = 1, H = 1 LDRSH

      halfword/byte会根据有无符号按movzx/movsx方式扩展成32位
      其余的ldr也是一样
      P 同上
      U 同上
      W: 当P= 0, 必须为0, 因为此模式无T后缀声言
      当P= 1, 同上
      Offset1, Offset2组成一个八位的偏移, 配合U位, 可寻址+/-256范围内的数据
      Offset1 <- HI
      Offset2 <- LO
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:29:21 | 显示全部楼层
        (* LDRH/STRH/LDRSB/LDRSH 机器码 2,用于寻址寄存器
        (***********_*)  
          27 26 25 24 23 22 21 20    19 - 16        15 - 12   11 10 9 8 7 6 5 4    3 2 1 0
           0  0  0  P  U  0  W  L       Rn             Rd      0  0 0 0 1 S H 1       Rm
          标志位同上, Rm是32位 全偏移
         
        (* 栈操作访存指令 LDM/STM
        (*++++++++++*_*)  
          LDM LDR Multi 的缩写, STM同理
          一个寄存器集出栈, 一个入栈
          不像X86的PUSH/ POP ..
          LDM/STM有着更加丰富的栈操作玩法
         
          栈增长方式可以往下, 也就是进栈一个寄存器, 地址-4(一个寄存器四个字节(Word))
          也可以往上, 也就是进栈一个寄存器, 地址+4(一个寄存器四个字节(Word))
         
          对于提交的基地址Rn,
          LDM/STM操作的时候也可以选择包括这一个地址或者在这个地址+/-4的位置开始栈操作
          基地址偏移选择和栈增长方式都有各种两种操作, 组合起来就能有四种操作方式
         
          1. 栈方向往下- 基址包括 DA ED (指令声言)
          2. 栈方向往下- 基址排除 DB FD (指令声言)
          3. 栈方向往上- 基址包括 IA EA (指令声言)
          4. 栈方向往上- 基址排除 IB FA (指令声言)
         
          这四种方式STM/ LDM都能用, 不怕没有你想用的.
          栈方向排斥的一对, 和基址排斥的一对可以组成一对 PUSH/ POP来保持上下文
          栈方向和基址相同的, 可以组成拷存例程减少代码尺寸, 十分灵活.
         
          有人可能会问了? 栈操作方向相反, 为啥基址也要排斥呢?
          举个基地址操作相同的例子 LDM使用类型1, STM使用类型3, 栈方向相反, 皆为基址包括
          SP:=1000 STM你推一个寄存器进去 基址包括, 写进1000, SP+=4
          SP:=1004
          你要拿出来, 你用类型3,LDM去取, 基址包括, 取1004, SP-=4
          SP:=1000, 很明显取错地方了, 用基址排除, 先减-4, 1000,取1000这次终于正常啦
          基地址操作相同, 取/写总会有一个单元的偏移, 所以要基址排斥
         
          四种类型有着更利于人理解的名字
          STMIB 地址先增而后完成操作 STMFA 满递增堆栈
          STMIA 完成操作而后地址递增 STMEA 空递增堆栈
          STMDB 地址先减而后完成操作 STMFD 满递减堆栈
          STMDA 完成操作而后地址递减 STMED 空递减堆栈
         
          同行的表示同一种操作方式, 名字不同而已
         
          LDM/STM 语法
         
          ACCESS:= LDM | STM
          ADDRM:= DA|DB|IA|IB|ED|FD|EA|FA
          WB:= !(WriteBack Rn) | null
          AC:= ^(Same as User Mode)
          #:=Literal link
          ACCESS#Cond#ADDRM Rn WB, {寄存器集合表达式} AC
         
          Rn提供基地址
          ^表示操作的寄存器集都是用的用户模式下的寄存器集合
          !感叹号表示用于栈计算的地址最后会被写回Rn
          寄存器集合表达式怎么写呢?
          简单地一个可以直接这么写 {R3}, 例子 LDMEQIA R0, {R3}
          多个可以这么写Rstart-Rend, 多个非连续段寄存器用,链接起来
          例子, LDMEQIA R0, {R3-R15}
          STMEQED R0!, {R0, R2-R3, R4-R10, R12, R13, R14-R15}^
         
        (* LDM/STM 机器码
        (***********_*)
          27 26 25 24 23 22 21 20    19 - 16        15 - 12 11 10 9 8 7 6 5 4 3 2 1 0
           1  0  0  P  U  S  W  L       Rn               Register List (d15-d0) 寄存器栈操作
           
          L?LDM(POP):STM(PUSH)
          Rn:出栈/入栈用的基址, 不一定要是r13 (sp).
          U?升栈:降栈PUSH/POP 升PUSH/POP由低变高, 降PUSH/POP地址由高变低
          Register List:寄存器列表, 按d15指示r15-> d0指示r0的序列排列, 1指示当前位置的寄存器被PUSH/POP
          W?更新基址Rn:不更新
          P?不包括当前地址, 会按照当前栈方向先上升一个单元:包括
          S?对于LDM&&当前Register List包含r15, SPSR拷贝进CPSR
          S?对于STM || (LDM&&当前Register List不包含r15), 拷贝进用户模式下的通用寄存器
          无论何种方式,
          寄存器集在内存低到高总是按照R0-R15增长方向排列的 (此测试基于CodeWarrior for ARM Developer Suite IDE)
         
          读取PC会对齐一个Word地址 (&-4)
          当Rn不对齐时, 也会强制对齐一个Word,
          如果此时再次写回Rn, 这个写回的地址却不会是对齐的 (此测试基于CodeWarrior for ARM Developer Suite IDE)
          Reglist 与写回Base 寄存器Rn 冲突时的UB行为
          对于LDM 写回不发生, 此测试基于 CodeWarrior IDE
          对于STM, 如果是最低有效位, 则写入Rn的原始值, 否则会写回计算完成的Rn地址, 此测试基于 CodeWarrior IDE
         
          <STM && R15>
          当STM中写入R15-PC时, 会轧入R15+12(这个R15是当前这条指令的地址)的值,
          此测试基于CodeWarrior 4.25, STR也是如此
          基于 Keil MDK-Lite 4.70.0 会轧入0
         
        (* 原子交换 SWP.
        (*++++++++++*_*)  
          语法:SWP[B] Cond Rd Rm [Rn]
          Rn:地址
          Rm:将要写进内存的值
          Rd:读出的值写进Rd
         
        (* SWP 机器码
        (***********_*)
         
          27 26 25 24 23 22 21 20   19 - 16    15 - 12    11 10 9 8 7 6 5 4   3 2 1 0
            0 0  0  1  0  B  0  0      Rn         Rd       0  0 0 0 1 0 0 1     Rm 数据交换
            
          B:1?交换8bit:交换32bit
          Rd:内存区域加载到此寄存器
          Rn:内存地址
          Rm:写入内存的字节/32bit
         
          对齐测试:对于读:=LDR, 对于写:=STR, 8位写入的时候只写入八位数据, 读取的时候会8bit零扩展为32位
          上述所有访存指令都不影响标志位
          访存指令在修改PC的时候会刷新当前CPU流水线, 至此, 标准ARM7的访存指令解释结束
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:30:59 | 显示全部楼层
(* 标准ALU指令 ADD/SUB/ADC/SBC/RSB/RSC /CMP/CMN/TST/TEQ/MOV/MVN /BIC/AND/ORR/EOR
(*----------*_*)
  ADD/SUB 正常加减 举个例子 ADD Rd, Rn, Rm (Rm+Rn放进Rd寄存器中)
  ADC/SBC 带进位加减 SBC是结果进位C清零
  RSB/RSC 反序减法(RSC带进位) RSB Rd, Rn, Rm (Rm-Rn放进Rd寄存器中)
  CMP/CMN 行为类似ADD/SUB, CMP对应ADD, CMN对应SUB.但是不保持结果, 用于测试执行结果
  AND 操作数相与
  ORR 操作数相或
  EOR 操作数异或
  TST 操作数相与不保持结果, 用于测试执行结果
  TEQ 操作数异或不保持结果, 用于测试执行结果
  BIC 操作数与第二个操作数取反相与
  MOV 寄存器之间互相拷贝
  MVN 寄存器取反拷贝
  除了CMP/CMN/TST/TEQ外都能设置是否影响标志位, 而这几个都会影响标志位, 无论是否设置.
  
  
  这些ALU指令都有一个统称的Oprand2, 一共12位
  这个Oprand2根据不同需求会被分别解释成三种类型的寻址计算
  
  1. 8位立即数+4位偶数移位数 扩展成32位立即数,            注意,这个移位是循环右移, 并且也可能因为设置影响C位
  
  2. 5位移位立即数+移位类型+Rm,          这个跟上面LDR用的移位是一样的, 不同的是某些指令设置影响标志位时,
                                                            移位进位会进入C位 (Rs移位也如此)
                                                            这个C位是否影响标志位取决于所执行的指令是否影响C位,
                                                            如果本身也影响将会忽略移位的C位进位
                                                            当然ALU指令没有设置影响标志也不会测试C位, Rs移位也是一样
  3. Rs移位寄存器+移位类型+Rm
                                                            差不多, 跟上面一样, 移位量取最低的一个字节, 多了对>=32移位量的判断,
                                                            使用Rs移位会增加一个I周期
  具体情况如下, 只写特殊情况了, 常规操作不写了,大家应该都懂的:
  对于所有Rs移位 Rs:=0, 都是 Operand2 = Rm, C 不变
  
  LSL-#Imm5bit
                           #Imm5bit = 0 Operand2 = Rm, 不影响C位
  LSL-Rs
                            Rs = 32 Operand2 = 0, C:= Rm bit0
                            Rs > 32 Operand2 = 0, C := 0
  LSR-#Imm5bit
                           #Imm5bit = 0 Operand2 = 0, C:= Rm bit31
  LSR-Rs
                            Rs = 32 Operand2 = 0, C:= Rm bit31
                            Rs > 32 Operand2 = 0, C := 0
  ASR-#Imm5bit
                           #Imm5bit = 0 Operand2 = (Rm bit31)? 0 :0xFFFFFFFF:, C:= Rm bit31
  ASR-Rs
                            Rs = 32 Operand2 = 0, C:= Rm bit31
                            Rs > 32 Operand2 = (Rm bit31)? 0 :0xFFFFFFFF:, C:= Rm bit31
  ROR-#Imm5bit
                           #Imm5bit = 0 RRX
  ROR-Rs
                          if Rslo5bit = 0 Operand2 = Rm, C:= Rm bit31
                           else 基本操作, 此模式只使用低5位.

  Operand2 3种形式格式
  
  1. 八位循环移位+ROR Operand2:= Imm32:= Imm8 >> (Shift*2)
  2. Shift+Imm5 Operand2:= Rm ShiftType ShiftImm5bit
  3. Shift+Rs Operand2:= Rm ShiftType Rs's Byte

  各种形式都举几个例子哈!, Oprand2具体形式位域编码见后面机器码表
  
  1. 八位移位+ROR         MOV R1, #0x80000000
  2. Shift+Imm5            MOV R1, R0, LSL #2
                            SBC R1, R0, RRX
  2. Shift+Rs             MOV R1, R0, LSL R2
                          MOVS R1, R0, LSL R2 ;; MOVS, S表示同时设置标志位
                          
  ``````````````` ADD/SUB/ADC/SBC/RSB/RSC/CMP/CMN
  这几个操作数都是相加,相减
  对于 保持结果到Rd的指令有 Rd, Rn, Operand2 形式的操作
  对于 比较不保持结果的指令 不使用Rd位域, 即使他是有位域编码存在, 只有 Rn, Operand2 形式的操作
  对于 比较不保持结果的指令设置S与否始终会设置标志位 .
  这些指令都会影响NZCV标志位 (如果设置了S标志位)
  对于带进位减法指令, 当C位为0, 会额外减去一个1, 跟带进位加法相反!, thumb指令的alu也是如此
  
  ``````````````` AND/ORR/EOR/TST/TEQ
  这几个操作数都是逻辑操作
  对于 保持结果到Rd的指令有 Rd, Rn, Operand2 形式的操作
  对于 比较不保持结果的指令 不使用Rd位域, 即使他是有位域编码存在, 只有 Rn, Operand2 形式的操作
  对于 比较不保持结果的指令设置S与否始终会设置标志位 .
  这些指令都会影响NZ标志位 (如果设置了S标志位, C位看移位类型了, 否则不影响)
  
  ``````````````` MOV/MVN/BIC
  这几个操作数都是寄存器之间传输操作
  都是 Rd, Rn, Operand2 形式的操作
  这些指令都会影响NZ标志位 (如果设置了S标志位, C位看移位类型了, 否则不影响)
  
  
  (* 主ALU 机器码
  (***********_*)
   27 26 25   24 23 22 21   20     19 - 16     15 - 12       11 10 9 8 7 6 5 4 3 2 1 0
    0  0  I      Opcode      S        Rn          Rd                 Operand2
   
  oprand2: 编码1 (I =1) : 4位移位+8位数据 d0-d7 8位立即数, d8-d11 4位偶数移位数
  编码2 (I =0 && d4=0) : 寄存器移位 5位移位量(d11-d7)+2位移位类型(d6-d5)+4位寄存器索引(d3-d0)
  编码3 (I =0 && d4=1 && d7 = 0) :
  寄存器RS移位 RS寄存器索引(d11-d8)+2位移位类型(d6-d5)+4位寄存器索引(d3-d0)
  
  Opcode指示具体的 ALU/逻辑处理指令 - 操作模式
  
  0: AND Rd,Rn,Operand2
  1: EOR Rd,Rn,Operand2
  2: SUB Rd,Rn,Operand2
  3: RSB 反序 Sub Rd,Rn,Operand2
  4: ADD Rd,Rn,Operand2
  5: ADC Rd,Rn,Operand2
  6: SBC Rd,Rn,Operand2
  7: RSC 反序 Sbc Rd,Rn,Operand2
  8: TST Rn,Operand2 (S位域必为1, 同时如果是Rs/Imm5移位寻址会测试移位C来设置CPSR的进位状态位)
  9: TEQ XOR不保持结果 Rn,Operand  (S位域必为1, 同时如果是Rs/Imm5移位寻址会测试移位C来设置CPSR的进位状态位)
  A: CMP Rn,Operand2 (S位域必为1)
  B: CMN A+B不保持结果 Rn,Operand2 (S位域必为1)
  C: ORR OR Rd,Rn,Operand2
  D: MOV Rd,Operand2
  E: BIC 位清理 Rd,Rn,Operand2, rd:= rd & ~rn
  F: MVN 取反MOV Rd,Operand2 rd := ~rm
  
  SUB/ADD/ADC/SBC/CMP/CMN/RSB/RSC 设置NZCV
  EOR/ORR/AND/TST/TEQ/BIC/MVN/MOV 设置NZC-(C由移位Operand2获得)
  
  S表示是否设置CPSR标志位, 1设置0不设置
  
  对于ALU操作中具有Rd参数且设置S标志的指令且Rd=PC的情况下
  会把当前模式下的SPSR缓存PSR拷贝到CPSR切换模式, 此细节一般用于从中断返回.
  举几个中断返回的例子,
  未定义指令: MOVS PC, LR
  预取异常: SUBS PC, LR, #4 (中断时候加上的微小的, 无用的偏移量, 需要减去,才能返回到正确的地址)
  访存异常: SUBS PC, LR, #8 (中断时候加上的微小的, 无用的偏移量, 需要减去,才能返回到正确的地址)
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:34:30 | 显示全部楼层
(* 乘法/乘加/长乘/长乘加法/符号长乘/符号长乘加法 及机器码
(*----******_*)

  MUL语法: Rd, Rm, Rs (乘法)
  MLA语法: Rd, Rm, Rs, Rn (乘加)
  UMULL语法 RdLo, RdHi, Rm, Rs (长乘)
  UMLAL语法 RdLo, RdHi, Rm, Rs (长乘加法)
  SMULL语法 RdLo, RdHi, Rm, Rs (符号长乘)
  SMLAL语法 RdLo, RdHi, Rm, Rs (符号长乘加法)
  
  乘法很简单, 直接放机器码了
  27 26 25 24 23 22 21 20 19 - 16           15 - 12     11 10 9 8    7 6 5 4      3 2 1 0
   0  0  0  0  0  0  A  S    Rd               Rn            Rs       1 0 0 1        Rm       乘法
   0  0  0  0  1  U  A  S    RdHi             RdLo          Rs       1 0 0 1        Rm       长乘
  
  各乘法- 简要算法:
  UMULL <- RdHi:RdLo := Rm*Rs
  USMULA <- RdHi: Lo += Rm*Rs
  SMULL <- RdHi:RdLo := Rm*Rs
  SMULA <- RdHi:RdLo += Rm*Rs
  
(* 指令跳转 B/BL/BX 及机器码
(*----******_*)
  B/BL语法: BL标号
  BX语法: BX寄存器
  编码1
  27 26 25 24           23 22 21 20 19 - 16 15 - 12 11 10 9 8 7 6 5 4 3 2 1 0
   1  0  1  L                            Offset (d23-d0)                            B/BL 分支指令
   
  L:?下一条指令的PC(在V4版本芯片里这个地址为PC-4)会驻留在r14中:
  Offset是24位有符号偏移, 这个偏移需要符号扩展成30位再乘以4才是正在的偏移,
  PC在加上这段偏移,才是实际的跳转地址, (注意这里的PC,是当前执行指令处+8的PC)
  
  编码2
  27 26 25 24 23 22 21 20 19 -  16  15 -  12    11 10 9 8 7 6 5 4   3 2 1 0
   0  0  0  1  0  0  1  0  1 1 1 1   1 1 1 1     1  1 1 1 0 0 0 1     Rn            BX分支交换指令
   
  Rn[0]进Thumb标志位
  PC := Rn[0] & 0xFFFFFFFE
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:35:13 | 显示全部楼层
(* CPSR/SPSR读写/SWI软件中断指令 及机器码
(*----******_*)  
  读写CPSR/SPSR
  
  MRS是读PSR指令, 读指令只能读到寄存器里面
  MSR是写PSR指令, 写指令可以把立即数/寄存器的值写入PSR寄存器
  
  MRS语法:
  PSR:= CPSR|SPSR
  MRS#Cond Rd, PSR
  
  例子:
  MRSEQ R0, CPSR
  
  MSR语法
  PSR:= CPSR|SPSR
  Field:= c|x|s|f (field的声言可以不重复叠加, 并且顺序无关)
  SRC:=8位位图+偶数移位立即数|寄存器
  MSR#Cond PSR#_#Field, SRC
  
  例子:
  MSR CPSR_xfsc, R0
  MSR CPSR_xfcs, R0
  MSR CPSR_x, R0
  MSR SPSR_x, R0
  
  掩码x:扩展位
  掩码c:控制位
  掩码s:状态位
  掩码f:标志位
  
  对于写CPSR, 在用户模式只能修改标志位(掩码f), 在特权模式, 除了thumb标识无法修改外, 其余全可以根据标识需求符修改指定的位域.
  对于写SPSR, 无论何种模式, 所有位域皆可以修改.
  对于读SPSR/CPSR, 则是正常读取完整的Block块,
  (其实对于写CPSR, 标准ARM7TDMI芯片是不允许修改中断抑制标志的, GBA大概是改了一下吧. 详见 ARM手册 4.1.39 MSR指令的详细解释)
  CPSR/SPSR 的d4在操作过后总是会自动设置为1 .
  对于MSR写CPSR引起的切换模式, 会交换当前模式寄存器, 但不会发生当前CPSR保存到切换到的模式SPSR中. (此细节待测试).
  
  MRS机器码
  -***********
  27 26 25 24 23 22 21 20 19 - 16 15 - 12 11 10 9 8 7 6 5 4 3 2 1 0
   0  0  0  1  0  R  0  0   1111    Rd     0  0 0 0 0 0 0 0 0 0 0 0
  
  MSR立即数机器码
  -***********
  27 26 25 24 23 22 21 20 19 - 16 15 - 12 11 10 9 8 7 6 5 4 3 2 1 0
   0  0  1  1  0  R  1  0   MASK     1111 rotate_imm 8_bit_immediate
  MSR寄存器机器码
  -***********
  27 26 25 24 23 22 21 20 19 - 16 15 - 12 11 10 9 8 7 6 5 4   3 2 1 0
   0  0  0  1  0  R  1  0   MASK     1111  0  0 0 0 0 0 0 0      Rm
  
  I对于PSR传输指示: 0->寄存器模式 1->立即数模式 (对于MRS总是为0,
  因为在ARM7中不存在 mrs imm8, cpsr/spsr这样的指令).
  d22指示加载/读取 0? CPSR:SPSR
  d21指示 0? MRS : MSR, 对于MSR写PSR寄存器,只能以位域掩码的形式写入,
  读取则是完整的读取整个PSR寄存器
  对于MRS, d12-d15指示了PSR加载到哪个寄存器
  对于MSR 有两种模式-I=0寄存器模式 d3-d0指示要写入CPSR/SPSR的源寄存器
  I=1立即数模式 d7-d0指示八位位图立即数, d8-d11指示供移动偶数位的4位移位量
  
  可供MSR使用的掩码, 可组和使用:
  
  d19 标志位 Bit 31-24
  d18 状态位 Bit 23-16           (你不能动这个位域, 实质性的写入不会改变位域的内容,
                                   ARM7这个位域不用.., >=V6版本包含Simd浮点条件标记等..)
  d17 扩展位 Bit 15-8            (你不能动这个位域, 实质性的写入不会改变位域的内容,
                                   ARM7这个位域不用.., >=V6版本包含条件状态/大小端控制等..)
  d16 控制位 Bit 7-0            
  
  软件中断, 在GBA, 这用于调用BIOS提供的软件中断
  语法 SWI Imm24
  27 26 25 24          23 22 21 20 19 - 16 15 - 12 11 10 9 8 7 6 5 4 3 2 1 0
   1  1  1  1                    SWI Number (d23-d0) 软件中断
   
  具体的中断功能将在后面章节讲解
  至此所有标准的ARM7指令讲解完毕
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:39:29 | 显示全部楼层
---------------------------------------------------------CPU-THUMB指令---------------------------------------------------------------------
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:42:37 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 11:43 编辑


  (* 访存指令 LDR/STR/PUSH/POP/STMIA/LDMIA
  (*----******_*)  
    访存指令 LDR/STR/PUSH/POP/STMIA/LDMIA
    LDR/STR上面讲过了
    PUSH是DB类型栈操作
    POP则是IA类型栈操作
    STMIA/LDMIA 是用来拷存的.
    (* ------- LDR/STR ----- *)
      ``````````````` LDR/STR
      语法1:       LDR Rd, [Rn, #imm5*4]
                 LDRH Rd, [Rn, #imm5*2]
                 LDRB Rd, [Rn, #imm5*1]
                 STR Rd, [Rn, #imm5*4]
                 STRH Rd, [Rn, #imm5*2]
                 STRB Rd, [Rn, #imm5*1]

      对于imm5*n的立即数偏移,必须对齐当前尺寸, 大部分编译器/汇编器也会限制这些语法(-4/-2/-1)

      语法2:       OP Rd, [Rn, Rm]
                 OP :  = LDR|STR|LDRH|LDRSH|STRH|LDRB|LDRSB|STRB

      语法3:       LDR Rd, [pc, #imm8*4]
                 LDR Rd, label (这根LDR Rd, [pc, #imm8*4]一样的,只是语法不同, 其实暗示了此指令只能是正偏移, 只能加载指令后面的标号, 而且要对齐一个Word)
                 LDR Rd, [sp, #imm8*4]
                 STR Rd, [sp, #imm8*4]

      LDR/STR imm5机器码
      15 14 13 12 11    10 9 8 7 6    5 4 3   2 1 0
       0  1  1  B  L     Offset5        Rn     Rd 读写立即数偏移
       1  0  0  0  L     Offset5        Rn     Rd 读写halfword

      L?LDR:  STR
      B?指示字节:  指示字

      LDR/STR Rn, Rm 机器码
      15 14 13 12 11 10 9    8 7 6    5 4 3    2 1 0
       0  1  0  1  L  B 0     Rm        Rn      Rd 读写相对+偏移
       0  1  0  1  H  S 1     Rm        Rn      Rd 读写符号字节/halfword

      L?LDR:  STR
      B?字节:  Word

      H=0 S=0 STRH
      H=0 S=1 LDRSB
      H=1 S=0 LDRH
      H=1 S=1 LDRSH

      LDR/STR SP/PC, #imm8*4 机器码
      15 14 13 12 11     10 9      8 7 6 5 4 3 2 1 0
       0  1  0  0  1       Rd            Offset8 读PC相对偏移
      15 14 13 12 11     10 9      8 7 6 5 4 3 2 1 0
       1  0  0  1  L       Rd            Offset8 读写SP偏移
      L?LDR:  STR
    (* ------- PUSH/POP ----- *)
      15 14 13 12 11 10 9 8        7 6 5 4 3 2 1 0
       1  0  1  1  L  1 0 R             Rlist              Push/Pop 寄存器
      L?POP:  PUSH
      Rlist:  寄存器列表, 跟ARM7的一样, 不同的是只有r0-r7可以入栈
      R要配合L使用, 对于PUSH, R:  =1会额外PUSH LR寄存器, 对于POP, R:  =1会额外POP到 PC里
      计算完成的地址会写回SP (废话....)
    (* ------- LDMIA/STMIA ----- *)
      15 14 13 12 11   10 9 8    7 6 5 4 3 2 1 0
       1  1  0  0  L     Rn           Rlist         寄存器栈操作
      L?LDMIA:  STMIA
      注意计算完成获得的地址一定会写回Rn
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:45:47 | 显示全部楼层

  (* THUMB 主ALU指令, 机器码没有S标志位, 但是一定会设置标志...
  (*----******_*)
      15 14 13 12 11 10      9 8 7 6      5 4 3         2 1 0
       0  1  0  0  0  0        Op(4bit)   Rm/Rs         Rd/Rn
      
      Op:  0000<- AND Rd, Rm NZ-
          0001<- EOR Rd, Rm NZ-
          0010<- LSL Rd, Rs NZC-
          0011<- LSR Rd, Rs NZC-
          0100<- ASR Rd, Rs NZC-
          0101<- ADC Rd, Rm NZCV
          0110<- SBC Rd, Rm NZCV
          0111<- ROR Rd, Rs NZC-
          1000<- TST Rn, Rm NZ-
          1001<- NEG Rd, Rm NZCV, 等同于 rd: = 0-rm
          1010<- CMP Rn, Rm NZCV
          1011<- CMN Rn, Rm NZCV
          1100<- ORR Rd, Rm NZ-
          1101<- MUL Rd, Rm NZ-
          1110<- BIC Rd, Rm NZ-
          1111<- MVN Rd, Rm NZ-
         
  (* THUMB 扩展ALU指令
  (*----******_*)      
    机器码1
    15 14 13    12 11   10 9 8 7 6   5 4 3   2 1 0
      0 0  0      Op      Offset5     Rm      Rd
      
    Op:  00 <- LSL
        01 <- LSR
        10 <- ASR
        11 <- NULL, 没有,或者是另外的指令
    影响 NZC-
   
    机器码2
    15 14 13 12 11 10     9      8 7 6             5 4 3     2 1 0
     0  0  0  1  1  I    Op      Rm/offset3          Rn       Rd
     
    I?3位立即数: 寄存器
    OP?Sub: Add
    影响 NZCV
         
    机器码3
    15 14 13    12 11   10 9 8   7 6 5 4 3 2 1 0
     0  0  1      Op     Rd/Rn    Offset8 (d7-d0)
     
    Op: 00<- MOV NZ-
       01<- CMP NZCV
       10<- ADD NZCV
       11<- SUB NZCV  
      
    机器码4
    15 14 13 12 11 10   9 8   7  6   5 4 3    2 1 0
     0  1  0  0  0  1   Op    H1 H2    Rm     Rd/Rn
     
    Op:  00 <- ADD Rd, Rm - 不影响标志位
        01 <- CMP Rn, Rm - NZCV
        10 <- MOV Rd, Rm - 不影响标志位
        11 <- BX Rm
        
    H1是Rn的高位, 和3位Rn变成4位寻址r0-r15
    H2是Rm的高位, 和3位Rm变成4位寻址r0-r15
      
    机器码5 ADD Rd, SP/PC*Imm8*4
    15 14 13 12 11    10 9 8    7 6 5 4 3 2 1 0
     1  0  1  0 SP       Rd        Imm8
    SP?: SP: PC
   
    机器码6 ADD/SUB SP, Imm7*4
    15 14 13 12 11 10 9 8 7   6 5 4 3 2 1 0
     1  0  1  1  0  0 0 0 S         Imm7
    S?: SUB: ADD
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:46:17 | 显示全部楼层
  (* THUMB 条件分支
  (*----******_*)   
    15 14 13 12          11 10 9 8      7 6 5 4 3 2 1 0
     1  1  0  1          Cond(4bit)         SImm8                 B Cond 条件分支
    寻址计算 PC: =PC+ movsx(Imm8)<<1
   
    15 14 13 12 11          10 9 8 7 6 5 4 3 2 1 0
     1  1  1  0  0                  Offset11                      B无条件分支
    寻址计算 PC: =PC+ movsx(Imm11)<<1
   
    15 14 13 12 11                    10 9 8 7 6 5 4 3 2 1 0
     1  1  1  1  H                           Imm11                BL长偏移分支
     
    长偏移被编码成两条指令
    H=0是第一条指令
    LR = PC + (movsx(SImm11) << 12)
   
    H=1是第二条指令
    PC = LR + (Imm11 << 1)
    LR = 下一条指令地址 | 1
   
    15 14 13 12 11 10 9 8  7  6        5 4 3          2 1 0 BX Rm
     0  1  0  0  0  1 1 1 H1 H2          Rm            Rd/Rn
     
    实际上扩展ALU指令机器码4 OP: =11情况
    Rm注意是Rm不是Rn, Rm的0位进T标志位域
    然后PC: = Rm & -2
   
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:47:26 | 显示全部楼层
---------------------------------------------------------CPU-指令周期概览---------------------------------------------------------------------
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:47:45 | 显示全部楼层
{ ====== 指令周期概览 ======== }
    ALU指令 S +I for SHIFT(Rs) +S + N if R15 written
    MSR, MRS S
    LDR S+N+I +S +N if R15 loaded
    STR 2N
    LDM nS+N+I +S +N if R15 loaded
    STM (n-1)S+2N
    SWP S+2N+I
    B,BL 2S+N
    SWI, trap 2S+N
    MUL S+mI
    MLA S+(m+1)I
    MULL S+(m+1)I
    MLAL S+(m+2)I
    S: 连续周期
    N: 非连续周期
    I: 内部周期
   
    乘法的m周期计算:
    对于乘法实际就是移位相加的组合
    乘法周期的计算主要依赖于Rs移位寄存器的内部字节信息.
    按照 d31 -> d0的方向 拆分成 四个字节
   
    Block3 - Block2 - Block1 - Block0
   
    if ((int32_t) Rs < 0)
    Rs = ~Rs;
    Block 3- 1全为0 则 m: = 1
    Block 3- 2全为0 则 m: = 2
    Block 3为0 则 m: =3
    全部不为0, 那么m: = 4
   
    对于取指, 实际需要的时间与当前设备的cache状态有关
    缓存线内部有相应的cache数据, 则不需要额外的访存时间,
    缓存脱靶时需要的时间 (WaitState) 依赖GBA内部IO指定的WaitState和当前周期类型
    注意这个缓存只适用于取指(指令cache)
   
    对于STR/STM, 他的最后一个周期总是指示下一条指令的预取是N周期.
   
{ ====== 未对齐的读写访问UB行为 ======== }
    一般情况下都是下降对齐地址在取值, 当然, 也会有特殊情况
   
    对于 未对齐的LDR: 来说,先下降内存对齐4字节, 然后取出的值: = 值 ROR循环移位 (address & 3) * 8 位
    对于 未对齐的LDRH: 来说,先下降内存对齐2字节, 然后取出的值: = 值 ROR循环移位  8 位 (例子 r0 : = r1 ror 8)
    对于 未对齐的LDRSH: 来说,取出的值: = 取未对齐的原始地址单字节有符号扩展为32位
    SWP[B]指令的未对齐行为, 同LDR[B]/ STR[B]
   
    第一条引用于ARM手册 arm_arm.pdf : :  A4.1.23 LDR Alignment 章节解释
    剩下的引用于GBA模拟器no cashGBA的GBATEK的ARM章节. (因为在官方手册中没找到记载, 直接用UB行为敷衍了事了)
{ ====== 资料参考. =========== }
    ARM Developer Suite Assembler Guide (DUI0068.pdf, ARM通用汇编编程手册, 提供快速的指令参考, 用户层面的参考书)
    ARM7TDMI Technical Reference Manual (DDI0210B.pdf, 此文档包含详细的ARM7TDMI指令周期时序, 和简略的操作码表)
    ARM Architecture Reference Manual (arm_arm.pdf, 这个是最全的芯片细节解释, 提供最详细的操作码机器码解码流程)
    上海交大 ARM7TDMI总线接口 (不错的中文教程).
    NO CASH GBA模拟器文档 GBATEK (关于未对齐的指令解释很详细)
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 11:54:07 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 12:06 编辑

---------------------------------------------------------ARM7-THUMB指令码表---------------------------------------------------------------------

图表是我在adobe pdf里编辑的,凑合着看吧,

注意GBA所使用的ARM7废弃了浮点处理指令, 所以我们可以不用去管浮点指令的解码
貌似忘了说 thumb 模式的swi软中断指令了, 跟arm7一样, 只是操作位数变短了, 机器码在表里,不多说了




LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 12:12:01 | 显示全部楼层
ARM7TDMI源码实现见附件, 应该没啥大问题..之后就说GPU, GPU讲完就简单啦

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 12:14:53 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 14:19 编辑

---------------------------------------------------------GPU-卷轴---------------------------------------------------------------------
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 14:03:26 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 14:12 编辑

GPU复杂的很
首先 GBA从渲染对象可分为 卷轴(命名表) 和OAM (精灵)
GBA有四个卷轴渲染器, 一个128个可供渲染精灵
支持 Alpha混合, 增亮, 变暗, 马赛克, 透明像素, 垂直水平翻转等等一些基本的图形效果
对于整个渲染屏幕来说, 还可以把当前渲染对象分成两份, 进行裁剪和镂空
以及支持简单的2D仿射来模拟简单的 "3D"渲染
这些机能对于现在大部分2D游戏来说都已经够用了,
唯二的缺点是CPU,和颜色, GBA只支持16位颜色和调色板模式

先说卷轴渲染器 (BG0 ~ BG3), 卷轴渲染器支持6种模式渲染(mode 0 ~ mode 5), 其中大部分游戏只用到 mode 0 ~ mode 2

! mode 0, BG 0 ~ BG3 皆可以使用, 每个卷轴都可以做四分屏渲染 (512 * 512), 可切换 8位, 4位像素渲染模式
! mode 1, BG 0 ~ BG1 做标准渲染, BG2 做仿射变换渲染, BG 3不使用
! mode 2, BG2, BG3 做仿射变换渲染, BG 0, BG1 不使用

卷轴模式也就这三种比较难以理解, 其余的模式非常好理解, 先不讲
类似于GBA, NES这些Console游戏都非常节省内存,
大家都知道图像是2D游戏内的大头数据,
而这些数据如果颜色有限,可以用调色板索引节省内存, 对吧?
对于Console游戏, 可能会根据某种线性关系再次压缩调色板索引位数
所以Console游戏的容量往往都很小

<命名表> 对于 mode 0 ~ mode 2 卷轴渲染来说
显存里面有个叫命名表(nametable) 的东西, 这个命名表是一块大的内存的集合,
每两个字节(仿射BG一个字节)代表着命名表的一个item
item 存载着 一个叫Tile ID的东西和一些其他的属性
这个Tile ID是一个索引编号, 每一个索引编号, 可以寻址到一个 8 * 8 的chr 字符像素组

这个字符像素组, 对于 8位调色板像素来说是64个字节为一组, 4位则是32个字节
对于标准渲染来说, 一个命名表的尺寸为 32 * 32个 Item, 可以有 1 (256 * 256), 2 (512 * 256水平镜像, 256 * 512 垂直镜像), 4个命名表 (四分屏镜像)
对于仿射渲染来说, 只有一个命名表, 这个命名表为正矩形, 长宽可有, 16 (128), 32(256), 64(512), 128 (1024) 个item
屏幕渲染会根据这些命名表的item里的 Tile ID, 把当前尺寸的命名表寻址到的像素全部着色,
然后根据可被设置的滚动X, Y POS裁剪成240 * 160 (GBA屏幕的尺寸)显示出来

实际处理当中我们只需要根据滚动位置渲染240 * 160 像素就行了
卷轴在到达边界会回滚至0处, 请注意


命名表双字节 Item 属性

d9- d0 tile 号码,
d10- 水平反转致能
d11- 垂直反转致能
d15-d12- 4位调色板 bank, 适用于4位tile渲染模式, 这个bank是是BG调色板16*bank的对齐地址16位的调色板的首地址, 用来配合4位chr寻址颜色

命名表单字节 Item 属性
8位全部代码 tile id

<chr表>
chr 表
chr 存放着可被命名表索引的实际的像素堆,
每个命名表链接着一个在显存内的像素堆的首地址,
然后以 8位 64个字节为一个item, 4位32个字节为一个item的偏移存放
对于8位调色板, 像素布列如图
........     0~ 7
........     8~15
.
.
........     56~63


对于4位调色板, 像素布列如图
........     0~3 字节低位为左, 高位为右
........     4~7
.
.
........     28~31

寻址:= chr首地址 *tile id * 64 or 32
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 14:12:27 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 14:15 编辑

(* 仿射变换卷轴 *)
GBA的GPU提供简单的仿射变换功能

主要由以下10个参数设置 (这八个参数全是有符号的并且低8位都不是整数(用来模拟小数))
            dx (PA), int16_t
            dmx (PB), int16_t
            dy (PC), int16_t
            dmy (PC), int16_t
            ref_x, int28_t (d27位为符号位, 下面以此类推)
            ref_y, int28_t  
            dmx_total, int28_t  
            dmy_total, int28_t  
            dx_temp, int28_t
            dy_temp, int28_t
            
            在 VBLANK开始的时候
            dmx_total := ref_x
            dmy_total := ref_y   

            之后从扫描线 0~159间的每次扫描线切换到下一根扫描线的时候
            dx_temp = dmx_total
            dy_temp = dmx_total           
            dmx_total += dmx
            dmy_total += dmy           

            然后每次水平扫描的时候每渐进一个像素点,
            dx_temp += dx
            dy_temp += dy
            这个dx_temp, dy_temp把低8位非整数右移掉就是当前渲染的卷轴落点范围
            
            就这么简单,
            注意, 非VBLANK期间写入 ref_x, ref_y也会使相应的单个 dmx_total/dmy_total更新 (dmx_total := ref_x/ dmy_total := ref_y)VBLANK, 下面在解释,你可以理解成一个关键的契机,时间序列点
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 17:06:15 | 显示全部楼层
---------------------------------------------------------OAM---------------------------------------------------------------------
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 17:07:36 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 17:09 编辑

(* 精灵(OAM), 精灵有块在显存0x6010000处的32K固定字符表, 配合精灵的Tile-ID寻址到实际的像素 *)      
        GBA的精灵也是十分复杂, 并且也可以切换渲染模式-
        精灵的最小像素单元也是一个8*8的Tile,
        但是精灵有能像"胶水"一样把多个Tile块拼合在一起显示的能力.
        一个精灵可以显示多种尺寸的Tile合集,
   (* 精灵的TILE-ID寻址*)
        精灵只有4位精灵和8位精灵两种
        精灵的属性内存只提供一个Tile块合集的最上角POS(0, 0)的精灵 TileID.
        对于精灵的水平方向寻址:4位精灵每次加上此行TileID基地址+1
                                8位精灵每次加上此行TileID基地址+2
                                
        行TileID基地址寻址有两种模式可被GPU随意切换-
                       固定步长32寻址 ->无论8位/4位, 每次切换到下一个完整的Block8 Tile渲染时, 都随当前行基地址+= 32
                                如图:
                                       
                                8位模式固定位长寻址: TILE-ID := 16, 16*16
                                16 | 18
                                48 | 50
                                          
                                4位模式固定位长寻址: TILE-ID := 16, 16*16
                                16 | 17
                                48 | 49                                       
  
                       自身连续寻址: 举个例子:
                                一个32*16的4位精灵, TildID := 55
                                55 | 56 | 57 | 58
                                59 | 60 | 61 | 62           
   (* 精灵的CHR寻址*)               
        有了Tile ID, 便可寻址到具体的像素字符表
        无论像素位数是4还是8位, 都是一个4位Tile的完整Block增量, 这个跟BG Tile寻址不一样(+32)
        比如对于寻址 TildID:= 277的Tile, 4位只要写 277, 但是8位就要变成277*2
        对于8位Tile寻址, 最后一位会被忽略
   (* 精灵的完整寻址例子*)   
        举个例子:=
               8位模式固定位长寻址: TILE-ID := 99, 32*16
                           
               | 99    | 101    |
               | 99+32 | 101 +32| ---> | A | B |
                                       | C | D |
                                       
               对于A处的寻址, TildID := 99, 因为是8位忽略最低位, := 98
               TilePixel_8_8_Start := 0x6010000 + 98*32
               对于B处的寻址, TildID := 101, 因为是8位忽略最低位, := 100
               TilePixel_8_8_Start := 0x6010000 + 100*32
               ...
               ...
               以此类推
               
    (* 精灵的仿射*)         
           精灵也有仿射功能,
           
             精灵仿射的坐标系如下:
                   Y Negtive
                  /|\
                  
                   |
                   |
X Negtive <-  -----|-----  -> X Postive
                   |\
                   | \_____________ Origin (0, 0) 精灵的中心点
                  
                  \|/
                   Y Postive
                    
             对于精灵仿射只有 dx, dmx, dy, dmy 这四个参数使用.
             这四个参数跟BG用的那四个参数是一样的...
             BG实际是用了这个坐标系右下角的象限进行仿射变换的.
             对于GBA游戏机来说, 精灵的渲染坐标确定遵循以下公式
            
              x0, y0    旋转中心点, 也即是精灵的中心点坐标
              x1, y1    当前需要扫描线渲染的坐标
              x2, y2    实际经过仿射变换获得的坐标
              A, B, C, D  dx, dmx, dy, dmy
              
              x2 = A(x1-x0) + B(y1-y0) + x0
              y2 = C(x1-x0) + D(y1-y0) + y0
            
              这个公式其实很简单, 其实寻址都是相对于中心点向量带符号偏移(水平, 垂直方向) 的比例系数
            
              游戏机就会根据这个公式计算当前坐标仿射过后的实际的像素落点,
              超过范围不会被处理, 比如在 POS (8, 8)处的一个32*32的精灵
              经过计算落点在 (166, 166)处, 超过本身的(8~39, 8~39)范围, 忽略处理这个像素
              如果落点在这个范围, 怎么计算这个像素呢,
              减去初始偏移即可, 比如落点27, 16
                                     X偏移:= 27-8 := 19/8 := 2..3
                                     Y偏移:= 16-8 := 8/8 := 1...0
                                     那么就会寻址 POS (2, 1)内8*8Tile POS(3.0)的像素....
                                     以此类推
    (* 双倍尺寸精灵*)                              
               这个所谓的双倍是对于仿射精灵来说的, 是双倍的画布模式, 不是原始像素放大两倍.
               举个例子, 比如一个正矩形, 可被渲染区域就是整个矩形的区域,
               你把它旋转45度, 就会有部分像素跑到区域外不被显示了, 怎么办?
               我想显示这部分像素怎么办呢? 把显示画布区域调大点不就行了?
               双倍精灵就是这个意思.
               对于双倍精灵来说, 中线点X, Y坐标会被增加一个水平, 垂直尺寸一半的尺寸.
               然后两个方向各扩展一半画布尺寸,然后按照正常的仿射套路渲染
    (* 双倍尺寸精灵BUG *)         
               当启用双倍精灵时, 对于此精灵, 如果有像素处于128~160这个范围, 这个范围的像素不会被显示.
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 17:11:14 | 显示全部楼层
(* GBA的窗口功能 ------------------------------ *)
   GBA的窗口很麻烦, 有三种窗口, 精灵窗口, "镂空"窗口, 标准窗口0, 1 (其中后两种实际是一种)...
   三种可以一起开, 优先级为先渲染 精灵窗口, 然后是标准窗口1, 最后标准窗口0
   只要开了窗口功能, "镂空"窗口一定会存在, 并且通常的渲染都不可用, 请注意
   "镂空"窗口, 这个窗口会渲染在精灵窗口, 精灵窗口外区域的像素 ...
   
   窗口有这么几个功能 1.裁剪, 标准窗口可以设置裁剪范围区域显示像素.
                       2.复合使用BG, 每个窗口都能使用整个渲染的BG0~3和精灵输出层...
                       3.遮罩镂空效果
   
   标准窗口就是通常意义上的裁剪窗口, 没啥好说的,
   精灵窗口有点意思, 会在当前窗口整个精灵输出层选择几个精灵来作为影子标记(编程人员可控的),本身不显示像素
   这部分精灵的非透明像素会设置影子标记,
   然后当前精灵窗口的BG, 精灵输出层的渲染, 只会在当前设置了影子标记的坐标画,渲染像素.
   
(* GBA的BG优先级 ------------------------------ *)
   BG0~BG3 都可设置优先级
   优先级高的总是最后被画
   0优先级最高, 3最低.
   如果优先相同, 那么BG0优先级最大, BG1...BG2... BG3优先级最小...
   精灵也有优先级,
   比如精灵优先级为3, 那么他会画在也只能画在优先级为3的BG画布上, 画不到优先级为2的BG上.
   比如精灵优先级为1, 那么他能画在优先级3~1的画布上, 画不到优先级为0的画布上, 以此类推
   对于精灵优先级相同, 第一个精灵精灵0优先级最大, ....越往后优先级越小.
   
(* GBA的简单像素滤波 ------------------------------ *)
   (像素滤波就三种种 -- Alpha混合和亮度调整,以及马赛克效果)
   Alpha混合 := 饱和相加 ( 每5位颜色分量*可编程分量系数A/16, 每5位颜色分量*可编程分量系数B/16)
   对于像素Alpha, 如果底下的像素是透明的,
   那么这个像素会以原始形态写入, 不进行Alpha处理 (这点很重要, 有的游戏不模拟此feature会明显画面异常)
   可编程分量系数 := 0~16
   
   亮度增加 := 饱和相加 ( (31-每5位颜色分量)*可编程分量系数/16, 每5位颜色分量)
   亮度减少 := 饱和 ( 每5位颜色分量*(16-可编程分量系数/16) )
   
   (* ALPHA的具体细节 ------------------------------ *)
      Alpha混合需要两个BG层, 1st作为上层混合
                              2nd作为下层混合
      优先级也必须被适配.
      
      这个应该能懂吧? GPU内部有个寄存器可以被设置选择哪些BG输出层作为 1st
                                                        哪些BG输出层作为 1nd
                                                        当然精灵输出层也可以被选择...
                                                        
                              每一个 1st都能对应多个2nd BG卷轴Alpha混合, 反过来也是                              
                                                        
                举个例子: 1st : bg0, bg2, bg3
                          2nd : bg1
                          
                bg 渲染优先级:=   bg1, bg0, bg2, bg3
               
                先渲染bg1, 这个时候bg1被渲染完成, 底部是bg1的像素.
                然后渲染bg0, 刚好这个时候bg0被选中为1st 上层混合.底层是bg1,刚好2nd掩码中bg1也被选中了
                进行混合, 此时bg0渲染完毕, 此时底部是bg0的像素,
                开始渲染下一个BG, bg2, 再次进行Alpha测试, bg2被1st选中, 可惜底部的bg0没有被2nd选中, Alpha测试失败.
                此时底部是bg2的像素, 开始渲染bg3, 很失望, bg3也是测试失败, 不会进行Alpha混合.
               
                Alpha多择         1st : bg0, bg2, bg3
                                  2nd : bg1, bg2, bg3
                          
                bg 渲染优先级:=   bg1, bg0, bg2, bg3
               
                先渲染bg1, 这个时候bg1被渲染完成, 底部是bg1的像素.
                然后渲染bg0, 刚好这个时候bg0被选中为1st 上层混合.底层是bg1,刚好2nd掩码中bg1也被选中了
                进行混合, 此时bg0渲染完毕, 此时底部是bg0的像素,
                开始渲染下一个BG, bg2, 再次进行Alpha测试, bg2被1st选中, 底部的bg0也被2nd选中, Alpha测试成功,进行Alpha混合.
                此时底部是bg2的像素, 开始渲染bg3, 2nd:bg2, 1st:bg3也都被选中继续进行Alpha混合
(* GBA的马赛克 ------------------------------ *)         
                马赛克很简单, 应该都会吧, 就是每隔几个像素插值一次.. 不多说了.
(* GBA的屏幕渲染流程 ------------------------------ *)
                GBA在渲染开始的时候会先填充颜色, (backdrop)
                这个颜色就是BG_pal[0]的颜色,注意这部分颜色全是透明的
                然后根据是否开启窗口, 开了就进行窗口渲染那一套逻辑渲染,
                否则就是常规渲染
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 17:13:27 | 显示全部楼层
---------------------------------------------------------GPU IO映射---------------------------------------------------------------------
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 17:14:42 | 显示全部楼层
(* GBA的GPU IO端口映射--此部分信息大多摘自GBATEK文档---------------------------- *)
  4000000h  2    R/W  DISPCNT   GPU主控
  4000002h  2    R/W  -         颜色分量交换
  4000004h  2    R/W  DISPSTAT  GPU状态
  4000006h  2    R    VCOUNT    GPU线帧
  4000008h  2    R/W  BG0CNT    BG0 分控
  400000Ah  2    R/W  BG1CNT    BG1 分控
  400000Ch  2    R/W  BG2CNT    BG2 分控
  400000Eh  2    R/W  BG3CNT    BG3 分控
  4000010h  2    W    BG0HOFS   BG0 X-滚动起点
  4000012h  2    W    BG0VOFS   BG0 Y-滚动起点
  4000014h  2    W    BG1HOFS   BG1 X-滚动起点
  4000016h  2    W    BG1VOFS   BG1 Y-滚动起点
  4000018h  2    W    BG2HOFS   BG2 X-滚动起点
  400001Ah  2    W    BG2VOFS   BG2 Y-滚动起点
  400001Ch  2    W    BG3HOFS   BG3 X-滚动起点
  400001Eh  2    W    BG3VOFS   BG3 Y-滚动起点
  4000020h  2    W    BG2PA     BG2 仿射参数PA (dx)
  4000022h  2    W    BG2PB     BG2 仿射参数PB (dmx)
  4000024h  2    W    BG2PC     BG2 仿射参数PC (dy)
  4000026h  2    W    BG2PD     BG2 仿射参数PD (dmy)
  4000028h  4    W    BG2X      BG2 仿射引用点X (ref_x)
  400002Ch  4    W    BG2Y      BG2 仿射引用点Y (ref_y)
  4000030h  2    W    BG3PA     BG3 仿射参数PA (dx)
  4000032h  2    W    BG3PB     BG3 仿射参数PB (dmx)
  4000034h  2    W    BG3PC     BG3 仿射参数PC (dy)
  4000036h  2    W    BG3PD     BG3 仿射参数PD (dmy)
  4000038h  4    W    BG3X      BG3 仿射引用点X (ref_x)
  400003Ch  4    W    BG3Y      BG3 仿射引用点Y (ref_y)
  4000040h  2    W    WIN0H     标准窗口 0 水平裁剪设置
  4000042h  2    W    WIN1H     标准窗口 1 水平裁剪设置
  4000044h  2    W    WIN0V     标准窗口 0 垂直裁剪设置
  4000046h  2    W    WIN1V     标准窗口 1 垂直裁剪设置
  4000048h  2    R/W  WININ     窗口win0, win1 BG选择设置
  400004Ah  2    R/W  WINOUT    精灵, 镂空窗口 BG选择设置
  400004Ch  2    W    MOSAIC    马萨克尺寸设置
  4000050h  2    R/W  BLDCNT    像素滤波设置
  4000052h  2    R/W  BLDALPHA  Alpha混合使用的分量
  4000054h  2    W    BLDY      亮度调整分量
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 17:17:52 | 显示全部楼层
  (* 4000000h  2    R/W  DISPCNT   GPU主控---- *)
  0-2   图形模式                (6, 7是UB行为)
  3     机器类型指示            (用来只是当前处于何种模式 0:= GBA, 1:GBC)
  4     帧缓冲选择              (帧缓冲选择, 适用于模式4, 模式5)
  5     HBlank时序自由访问显存致能  (1=可以)
  6     OAM-TILE寻址方式 (0=精灵OAM-Tile寻址使用固定步长32, 1=精灵OAM-Tile寻址使用自身连续步长)
  7     强制变白           (无论何时都能访问显存, 当前渲染线直接变白)
  8     BG0显示开关  (0=关, 1=开)
  9     BG1显示开关  (0=关, 1=开)
  10    BG2显示开关  (0=关, 1=开)
  11    BG3显示开关  (0=关, 1=开)
  12    精灵输出层显示开关  (0=关, 1=开)
  13    标准窗口0 启用开关   (0=关, 1=开)
  14    标准窗口1 启用开关   (0=关, 1=开)
  15    精灵窗口 启用开关   (0=关, 1=开)

  (* 4000002h  2    R/W  -         颜色分量交换---- *)
  输出交换 rgb分量的b位和r位, 通常情况不使用, 好像适用于外部设备?不太清楚.
  基本没几个模拟器会管这个东西,.
  
  (* 4000004h  2    R/W  DISPSTAT  GPU状态  ---- *)
  0     V-BLANK标志   (只读) (1=VBlank) (此标志会在渲染线160.226的时候被设置)
  1     H-Blank标志   (只读) (1=HBlank) (每条扫描线的HBlank阶段都会被设置)
  2     线帧扫描匹配测试标志 (只读) (1=Match)  (当前扫描线被匹配会设置, 否则清除)   
  3     V-BLANK 发生IRQ 中断致能 (可读写)
  4     H-Blank 发生IRQ 中断致能 (可读写)
  5     线帧扫描匹配测试成功 中断致能       (可读写)
  6     不使用
  7     不使用
  8-15  线帧扫描匹配设置行      (可设置0.227)                            (可读写)
      
      GPU一帧要扫描228根扫描线,
      扫完扫描线会回滚到0的地方继续回扫,继续新的一帧
      
      CPU的频率是,  16.78MHz
      LCD一秒刷新 59.727次,
      很容易得出刷新一帧数需要的CPU 时钟周期:= 16780000/59.727 := 280944.96626316
      分给228条扫描线:= 280944.96626316/228 := 1232.2147643121,
      也就是每条扫描线可跑这么时钟周期的CPU指令数,
      其中前960个Ticks是正常扫描线渲染周期, 剩下的就是HBlank渲染周期.
      HBlank实质是电子枪回扫至下一行起点的过程,
      VBlank是电子枪回扫至光栅器原点的过程.
      前160扫描线全是可视扫描线, 后面的几十条, 实际上是不会进行实际操作的,
      就是电子枪回扫至原点的VBlank过程, 这个时候访问显存资源是最安全的.
      
      GPU内部有一个线帧扫描计数器, 会记录当前扫描线的行数.
      这个线帧扫描计数器在每次HBlank周期都会更新,
      然后匹配测试当前设置的线帧扫描匹配设置行, 如果匹配也会设置 线帧扫描匹配测试标志
      
      HBlank-VBlank阶段, 线帧扫描测试成功都可设置是否触发IRQ中断,
      注意啊, 这个触发IRQ中断, 只是设置GBA设备的IF位.仅此而已
      
  (* 4000006h  2    R    VCOUNT    GPU线帧  ---- *)   
      0-7 指示当前扫描线, 其余位不用, 这个扫描线计数器在每条扫描线的H-Blank阶段会更新.
      
  (* 4000008h  2    R/W  BG0CNT    BG0 分控  ---- *)   
  (* 400000Ah  2    R/W  BG1CNT    BG1 分控  ---- *)   
  (* 400000Ch  2    R/W  BG2CNT    BG2 分控  ---- *)   
  (* 400000Eh  2    R/W  BG3CNT    BG3 分控  ---- *)   
      0-1   BG优先级设置, 2位范围0~3
      2-3   字符表基地址, 2位范围0~3, 每个单元16K, 从显存0x6000000开始的
      4-5   不使用.
      6     当前马赛克致能                1?允许:不允许
      7     颜色模式       (0=4位调色板Bank+Tile4位, 1=256位调色板)
      8-12  命名表基地址     (0-31, 每个单元2KB, 从显存0x6000000开始的)
      13    BG2/BG3: 仿射坐标溢出屏幕处理 (0=透明,不显示, 1=回滚)
      14-15 命名表视频缓冲尺寸 (0-3)
      
      Value  标准模式      旋转模式
      0      256x256 (2K)   128x128   (256 bytes)
      1      512x256 (4K)   256x256   (1K)
      2      256x512 (4K)   512x512   (4K)
      3      512x512 (8K)   1024x1024 (16K)
  
  (* 4000010h  2    W    BG0HOFS   BG0 X-滚动起点  ---- *)   
  (* 4000012h  2    W    BG0VOFS   BG0 Y-滚动起点  ---- *)   
  (* 4000014h  2    W    BG1HOFS   BG1 X-滚动起点  ---- *)   
  (* 4000016h  2    W    BG1VOFS   BG1 Y-滚动起点  ---- *)   
  (* 4000018h  2    W    BG2HOFS   BG2 X-滚动起点  ---- *)   
  (* 400001Ah  2    W    BG2VOFS   BG2 Y-滚动起点  ---- *)   
  (* 400001Ch  2    W    BG3HOFS   BG3 X-滚动起点  ---- *)   
  (* 400001Eh  2    W    BG3VOFS   BG3 Y-滚动起点  ---- *)   
      前9位可用, 范围0~511
      每个BG渲染器各自的标准滚动设置, 注意如果是仿射旋转BG, 会忽略此参数使用仿射参数.
      
  (* 4000020h  2    W    BG2PA     BG2 仿射参数PA (dx) ---- *)   
  (* 4000022h  2    W    BG2PB     BG2 仿射参数PB (dmx) ---- *)   
  (* 4000024h  2    W    BG2PC     BG2 仿射参数PC (dy) ---- *)   
  (* 4000026h  2    W    BG2PD     BG2 仿射参数PD (dmy) ---- *)   
  (* 4000028h  4    W    BG2X      BG2 仿射引用点X (ref_x) ---- *)   
  (* 400002Ch  4    W    BG2Y      BG2 仿射引用点Y (ref_y) ---- *)   
  (* 4000030h  2    W    BG3PA     BG3 仿射参数PA (dx) ---- *)   
  (* 4000032h  2    W    BG3PB     BG3 仿射参数PB (dmx) ---- *)   
  (* 4000034h  2    W    BG3PC     BG3 仿射参数PC (dy) ---- *)   
  (* 4000036h  2    W    BG3PD     BG3 仿射参数PD (dmy) ---- *)   
  (* 4000038h  4    W    BG3X      BG3 仿射引用点X (ref_x) ---- *)   
  (* 400003Ch  4    W    BG3Y      BG3 仿射引用点Y (ref_y) ---- *)   
       仿射BG那段讲了, 这里不再赘述.多余的位也不会使用啦,
      
  (* 4000040h  2    W    WIN0H     标准窗口 0 水平裁剪设置 ---- *)
  (* 4000042h  2    W    WIN1H     标准窗口 1 水平裁剪设置 ---- *)
  (* 4000044h  2    W    WIN0V     标准窗口 0 垂直裁剪设置 ---- *)
  (* 4000046h  2    W    WIN1V     标准窗口 1 垂直裁剪设置 ---- *)
        每个Word的低位都是 Right/bottom,高位则是Left/top
        水平方向如果left>right或者right >240都会被解释成right:= 240
        垂直方向如果top>bottom或者bottom >160都会被解释成bottom:= 160      
        注意设置的right/bottom的值, 实际上只会渲染到 POS-1的地方
        比如left := 0 right := 240, 那么只会渲染POS 0 ~239
  
  (* 4000048h  2    R/W  WININ     窗口win0, win1 BG选择设置 ---- *)
        0-3   标准窗口 0 BG0-BG3 选择显示位域 1:显示
        4     精灵输出层 1:显示
        5     标准窗口0 像素滤波启用 0:不启用 比如Alpha混合/亮度调整不会发生, 不影响马赛克
        
        8-11   标准窗口 1 BG0-BG3 选择显示位域 1:显示
        12     精灵输出层 1:显示
        13     标准窗口1 像素滤波启用 0:不启用
        其余位域不使用
        
  (* 400004Ah  2    R/W  WINOUT    精灵, 镂空窗口 BG选择设置 ---- *)
        0-3   镂空窗口 BG0-BG3 选择显示位域 1:显示
        4     镂空窗口精灵输出层 1:显示
        5     镂空窗口 像素滤波启用 0:不启用
        
        8-11   精灵窗口 BG0-BG3 选择显示位域 1:显示
        12     精灵窗口精灵输出层 1:显示
        13     精灵窗口 像素滤波启用 0:不启用  
        其余位域不使用
        
  (* 400004Ch  2    W    MOSAIC    马萨克尺寸设置 ---- *)
        0-3   BG 马赛克水平映射  
        4-7   BG 马赛克垂直映射
        8-11  OBJ 马赛克水平映射
        12-15 OBJ 马赛克垂直映射
        
        当参数被设置为0的时候当前的类型,向量的马赛克滤波将不会发生.
        
        举个例子:当马赛克水平映射设置成7
        那么每隔7+1个像素就会插值取得一个当前block整数处的像素来填充block余数处的像素, 每次到达block整数处更新一次这个值
        
        比如 POS 0.(7+1) . 无余数, block整数处, 取 POS (0)的像素.
             POS 1.(7+1) . 1 余数, 填充 POS (0)的像素
             .
             POS 8.(7+1) . 0无余数, block整数处, 取 POS (8)的像素.
             .
             以此类推
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 17:18:11 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 17:19 编辑

  (* 4000050h  2    R/W  BLDCNT    像素滤波设置     ---- *)
        (* 1st选择, 这个对于非Alpha, 也表示选择该层---*)
        0     BG0 1st
        1     BG1 1st
        2     BG2 1st
        3     BG3 1st
        4     OBJ 1st
        5     BD  1st
        
        6-7   像素滤波选择 (0-3, see below)
               0 = 无像素滤波
               1 = Alphe混合
               2 = 亮度调整-变白
               3 = 亮度调整-变暗
               
       (* 2nd选择, 这个对于非Alpha混合是不使用的---*)
        8     BG0 2nd
        9     BG1 2nd
        10    BG2 2nd
        11    BG3 2nd
        12    OBJ 2nd
        13    BD  2nd
        
        其余位不使用
        
  (* 4000052h  2    R/W  BLDALPHA  Alpha混合使用的分量     ---- *)   
        0-4   1st Alpha分量 (1st Target) (0.16 = 0/16.16/16, 17.31=16/16)
        8-12  2nd Alpha分量 (2nd Target) (0.16 = 0/16.16/16, 17.31=16/16)
        
        其余位不使用
        
  (* 4000054h  2    W    BLDY      亮度调整分量     ---- *)      
        前四位有用, 跟Alpha一样的设置, 0~31, 大于16则是16
        
(* 精灵OAM所使用的结构 ---- *)
   OAM只有1K字节, 有128只精灵
   所以这么一算, 1024/128 := 8个字节
   每个精灵的属性是按照uint16_t 的尺寸安排的,
   也就是每个精灵有四个单元的 attr字,
   其中最后一个字是用来凑成仿射参数, 后面在讲, 先解释前三个属性字.
   
    (* attr0 -*)
   
    0-7   精灵的Y坐标           (0-255, 注意超过255会回滚到0显示)
    8     仿射精灵致能  (1:启用 0:标准精灵渲染)
     启用仿射精灵那么d9就是双倍精灵致能 (1:启用)
     标准渲染d9则是渲染致能 (1:不渲染此精灵 0:渲染)
    10-11 OBJ Mode  (0=标准渲染, 1=Alpha混合, 2=精灵窗口阴影标志精灵, 3=UB值)
                       如果此处设置Alpha混合, 将会忽略BLDCNT的像素滤波类型参数, 强制启用Alpha混合
                       
    12    OBJ 马赛克             1:启用
    13    8位/4位指示        (0=4位+调色板Bank 1=256位调色板)
    14-15 OBJ Shape              (0=Square,1=Horizontal,2=Vertical,3=Prohibited)
   
    (* attr1 -*)
   
    0-8   X-Coordinate           (0-511, 注意超过511会回滚到0显示)
    启用仿射精灵:
      9-13  仿射参数选择 (0-31)
    标准渲染:
      9-11  不使用
      12    水平翻转      (0=Normal, 1=翻转)
      13    垂直反转      (0=Normal, 1=翻转)
    14-15 精灵尺寸 (配合attr0的d14-d15可确认精灵的尺寸)
   
     尺寸  Square   Horizontal  Vertical
      0     8x8      16x8        8x16
      1     16x16    32x8        8x32
      2     32x32    32x16       16x32
      3     64x64    64x32       32x64

    (* attr2 -*)

    0-9   Tile ID
    10-11 精灵优先级
    12-15 调色板Bank, 适用于4位Tile渲染.
   
    (* 精灵的仿射参数dx, dmx, dy, dmy 选择 ---*)
    attr1的d9-d13 可选择0~31的仿射bank
   
    每一个bank开始的第1个OAM的attr3就是dx
                     第2个OAM的attr3就是dmx
                     第3个OAM的attr3就是dy
                     第4个OAM的attr3就是dmy
   
    每四个完整的OAM属性字节结构块(也就是32个字节一个Bank) 是一个完整的Bank  
LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

4

主题

104

帖子

353

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
222
贡献
9
 楼主| 发表于 2019-6-27 17:39:04 | 显示全部楼层
本帖最后由 moecmks 于 2019-6-27 17:47 编辑

GPU实现代码 https://pastebin.com/hdsvs1hT附渲染效果图,


至此,GPU 大致讲解完毕

LGD是TI8冠军!-2018.8.24 16:30留
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|ISO/IEC C++ China Unofficial

GMT+8, 2019-11-21 09:16 , Processed in 0.102201 second(s), 22 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表