ISO/IEC C++ China Unofficial

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 1859|回复: 54

容错率较高的FC模拟器的编写

[复制链接]

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
发表于 2016-3-13 11:32:37 | 显示全部楼层 |阅读模式
本帖最后由 moecmks 于 2016-3-18 14:30 编辑

突然发现原先还弄过这个东西,想写一个简单的基础入门
一个简单的具有基本功能的fc模拟器,只支持mapper0。。
半路弃坑不写希望大家不要打我

点评

膜 0 0  发表于 2016-3-17 14:15

评分

参与人数 3威望 +9 贡献 +9 收起 理由
岩川黑鬼 + 5 + 5 OTL
nadesico19 + 2 + 2 模拟器实现棒棒滴!
LH_Mouse + 2 + 2 拜。

查看全部评分

#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-13 11:33:07 | 显示全部楼层
最终效果大概就是附件那样。。。
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-13 12:39:10 | 显示全部楼层
本帖最后由 moecmks 于 2016-5-9 18:45 编辑

首先,先说简单说一下nes的构造
==========================================
cpu:ntsc 2a03, pal 2A07
==========================================
6502的变种, 6502是一款8bit的cpu.基本是当时文曲星的标配
我所知的改动如下:削减了BCD模式
削减了DCP,ISB等复杂的指令.调整了若干指令的运行周期
废除了BRK软中断
在$2000开始的地方加了8个PPU图形控制端口,全是单字节之后一直镜像到 $3FFF
在$4000开始的地方增加了APU波形声道控制和dmc调制
========================================
interrupt NMI/IRQ/RESET
========================================
NMI[$FFFA]
相当重要的中断,ntsc下每秒60次
此中断处理着整个图形的刷新 ...
整个游戏的处理大约可以描述成一下形式
init ....
wait_nmi:
jmp wait_nmi
nmi_proc:
当nmi中断触发的时候进入nmi_proc处理逻辑
IRQ[$FFFE]
软件中断一般交由用户控制, 可被屏蔽
RESET[$FFFC]
优先级最高的中断,用于读取/复位,触发时候转到相应的初始地址[相当于_tMainStartup]

#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-13 12:54:20 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-13 13:23 编辑

============ 手柄输入 ====================
1P被$4016控制着
2P为$4017
==============================================
手柄[$4016]可能是整个模拟中最简单的了
手柄[$4016]可以抽象成一个按键信息缓冲, 一个移位计数器此端口在读写时候分别有着不同的行为
=================$4016 read======================
每次从按键信息缓冲读出的信息是比特流[非0即1], 读完之后移位计数器+1
要读基本的按键信息就需要读8次
附按键bit流图片
读取 #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
按键
A键
B键
选择
开始
忽略
信号
0
0
0
0

=================$4016 write======================
写1复位移位计数器,此时表示已经准备数据并且加锁 ...写0解锁, 输出按键信息
==============================================
joypad.c
  1. // == < <span style="background-color: rgb(255, 255, 255);">joypad.c</span> > ========================================================
  2. // Provide base nes input settings
  3. //  

  4. #define JOYPAD_A        0x01
  5. #define JOYPAD_B        0x02
  6. #define JOYPAD_SELECT   0x04
  7. #define JOYPAD_START    0x08
  8. #define JOYPAD_UP       0x10
  9. #define JOYPAD_DOWN     0x20
  10. #define JOYPAD_LEFT     0x40
  11. #define JOYPAD_RIGHT    0x80

  12. static char g_keyinfo;
  13. static char g_shift;
  14. static char g_lock;

  15. char read4016 (void)
  16. {
  17.     char bit_stream;
  18.     if (!g_lock)
  19.     {
  20.       bit_stream = (g_keyinfo >> g_shift) & 1;
  21.       g_shift += 1;
  22.       return bit_stream;
  23.     }
  24. }
  25. void write4016 (char input)
  26. {
  27.     if (input == 0)
  28.       g_lock = 0;
  29.     else if (input == 1)
  30.     {
  31.       g_lock = 1;
  32.       g_shift= 0;
  33.     }
  34. }
  35. void joypad_set_a(void)                 { g_keyinfo |= JOYPAD_A; }
  36. void joypad_set_b(void)                 { g_keyinfo |= JOYPAD_B; }
  37. void joypad_set_select(void)         { g_keyinfo |= JOYPAD_SELECT; }
  38. void joypad_set_start(void)         { g_keyinfo |= JOYPAD_START; }
  39. void joypad_set_up(void)                 { g_keyinfo |= JOYPAD_UP; }
  40. void joypad_set_down(void)                 { g_keyinfo |= JOYPAD_DOWN; }
  41. void joypad_set_left(void)                 { g_keyinfo |= JOYPAD_LEFT; }
  42. void joypad_set_right(void)         { g_keyinfo |= JOYPAD_RIGHT; }
  43. void joypad_clr_a(void)                 { g_keyinfo &= ~JOYPAD_A; }
  44. void joypad_clr_b(void)                 { g_keyinfo &= ~JOYPAD_B; }
  45. void joypad_clr_select(void)         { g_keyinfo &= ~JOYPAD_SELECT; }
  46. void joypad_clr_start(void)         { g_keyinfo &= ~JOYPAD_START; }
  47. void joypad_clr_up(void)                 { g_keyinfo &= ~JOYPAD_UP; }
  48. void joypad_clr_down(void)                 { g_keyinfo &= ~JOYPAD_DOWN; }
  49. void joypad_clr_left(void)                 { g_keyinfo &= ~JOYPAD_LEFT; }
  50. void joypad_clr_right(void)         { g_keyinfo &= ~JOYPAD_RIGHT; }
复制代码
6502读取伪代码
  1. LDA #$1      
  2. STA $4016   ;   写 1 复位设备    此时设备以及准备好并且上锁  
  3. LDA #$0   
  4. STA $4016   ;   解锁  获取输入设备数据流 此时可以开始获取数据bit流  
  5.   
  6. LDA $4016   ;   第1次读 获取     A键          的状态   
  7. LDA $4016   ;   第2次读 获取     B键          的状态  
  8. LDA $4016   ;   第3次读 获取     Select键 的状态  
  9. LDA $4016   ;   第4次读 获取     Start键      的状态  
  10. LDA $4016   ;   第5次读 获取     上键      的状态  
  11. LDA $4016   ;   第6次读 获取 下键      的状态  
  12. LDA $4016   ;   第7次读 获取 左键      的状态  
  13. LDA $4016   ;   第8次读    获取  右键      的状态
复制代码

输入大致是如此, 最简单的一部分,应该没什么难点吧 ....



#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-13 21:20:14 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-14 13:25 编辑

=================================================
cpu-registers
=================================================
2a03有6个寄存器 A, X, Y
                                P, S, PC
除了PC寄存器外其余全是单字节
PC就是x86中的eip代码指针,不用多说,Uknow, 16位寻址范围 0x0000-0xFFFF(64K)
S是栈指针, 无符号数 也就是说[reg+FF]指示的是reg+0xFF的内存地址,而不是reg-1
由于是单字节所以栈只能寻址到单页的内容, [256个字节为1页], nes选择0x100的地址作为栈基址, [栈范围就在0x100-0x200]
这个就做过程调用的context, 因为没有直接imm-num寻址支持.一般不用来寻址常用变量
P是标志寄存器[PSW], 用来处理算术进位/状态设置以及各种分支
各个位域标志如下所示
C_FLAG                equ 01h                                ; 1: carry?
Z_FLAG                equ 02h                                ; 1: zero
I_FLAG                equ 04h                                ; 1: intterrupt enable
D_FLAG                equ 08h                                ; 1: dec mode nes did not use this mode
B_FLAG                equ 10h                                ; 1: software intterrupt flag
R_FLAG                equ 20h                                ; 1: this flag is reserved
V_FLAG                equ 40h                                ; 1: overflow flag
N_FLAG                equ 80h                                ; 1: negative flag

X, Y, A
这三个寄存器就是编程中常常直接使用的寄存器
A为累加器, (eax)
X,Y为计数器.在一些高级寻址模式可用作变址, 有种异种esi, edi的味道 .
在这种简陋的cpu上, 每个寄存器的分工都是异常明确的 .
反例如现在流行的cpu, 基本每个通用寄存器都支持[reg+offset(8bit/32bit)], ALU逻辑运算
X, Y寄存器缺失很多指令的高级寻址模式, 所以使用的时候不太方便. A寄存器的自由度最高
=================================================
cpu-addressing
=================================================
由于8位cpu的局限性
相比现在cpu实在太弱了[取值操作全是单字节,寻址一次取一个字节]
因为不支持直接 [reg+imm num offset] 寻址.
而派生了许多寻址模式, 以满足任意偏移的寻址  
有些甚至现在来看都是难以理解的  
先从最简单的讲起吧 ...
// ========== <立即数寻址[IM]> ======================
mode:<op> imm8
sample: cmp a, #$5
                adc x, #$2
               
两个字节, 很简单
不说都懂也是一般也是该族操作中指令周期最少的[不需要访问内存]
常用作一些常量赋值, 比较
// ========== <零页寻址[ZD]> ========================
mode:<op> imm8
两个字节
零页即0x0000-0x00FF的地址
寻址[imm8]得值,然后与op运算
这个指令周期也很少, 用的比较多.由于是单字节偏移相当于廉价的栈内存寻址,扮演着nes中cache的功能
// ========== <零页X寻址[ZX]> ========================
同上, 不过是[imm8+X]得值,然后op运算
指令周期一般比基础的零页多了1周期
// ========== <零页Y寻址[ZY]> ========================
同上, [imm8+Y]得值,然后op运算
// ========== <绝对寻址[ABS]> ========================
mode: <op> addr-LO addr-HI
三字节码, 寻址[imm16(addr-HI * 256 + addr-LO)]得值,然后与op运算
// ========== <绝对X寻址[ABSX]> ========================
mode: <op> addr-LO addr-HI
三字节码, 寻址[imm16(addr-HI * 256 + addr-LO + X)]得值,然后与op运算
需要注意的是当跨页的时候会额外增加一个周期[PS:跨页注释见下]
// ========== <绝对Y寻址[ABSY]> ========================
同上
// --------------- 跨页 --------------------------------
如: imm16 = 0x00FF, X = 2
绝对X寻址后 0x0101, 此时发生跨页[256字节为1页]
// -------- 剩下的两种比较难以理解 ---------------------
// ========== <先变址X然后间接寻址[XIR]> ===============
mode: <op> imm8
两个字节
首先, imm8 + X 得到 addr8temp[单字节]
此时, 再用addr8temp在零页寻址两次得到addr16real
再次寻址addr16real得到单字节值, 然后OP之
由于多次取内存, 该寻址类型一般是族指令中周期消耗最高的  
// ========== <先间接寻址后变址Y[IRY]> =================
mode: <op> imm8
两个字节
首先, 用imm8在零页寻址两次得到addr16temp
此时, Y + addr16temp得到addr16real
再次寻址addr16real得到单字节值, 然后OP之
略荀彧XIR, 注意的是要检测跨页伪代码大致如下, 暂时忽略跨页
  1. #ifdef _MSC_VER
  2. #include "inttypes.h"
  3. #else
  4. #include <inttypes.h>
  5. #endif


  6. struct _2a03_cpu
  7. {
  8.   uint8_t a;
  9.   uint8_t x;
  10.   uint8_t y;
  11.   uint8_t p;
  12.   uint8_t s;
  13.   uint16_t pc;
  14.   uint8_t unused8[4];
  15.   uint16_t unused16[4];
  16.   uint8_t ram[0x0800];
  17. };

  18. static struct _2a03_cpu cpu;

  19. #define A cpu.a // A
  20. #define X cpu.x // X
  21. #define Y cpu.y // Y
  22. #define S cpu.s // S
  23. #define P cpu.p // P
  24. #define PC cpu.pc // PC
  25. #define A8_T cpu.unused8[0] // for ZERO X, Y. addr8_temp
  26. #define A8_R cpu.unused8[1] // for ZERO PAGE. addr8_real
  27. #define ALU8_V cpu.unused8[2] // for operate
  28. #define VALUE cpu.unused8[3]  // for get value from address/imm
  29. // We always use the little-endian CPU, isn't it?
  30. #define A16_T cpu.unused16[0] // for ABS X, Y, IRY. addr16_temp
  31. #define A16_T_LO ((uint8_t *)&cpu.unused16[0])[0] // for ABS X, Y, IRY
  32. #define A16_T_HI ((uint8_t *)&cpu.unused16[0])[1] // for ABS X, Y, IRY
  33. #define A16_R cpu.unused16[1] // for ASB, ABS X, Y, IRY, XIR. addr16_real
  34. #define A16_R_LO ((uint8_t *)&cpu.unused16[1])[0] // for ASB, ABS X, Y, IRY, XIR
  35. #define A16_R_HI ((uint8_t *)&cpu.unused16[1])[1]// for ASB, ABS X, Y, IRY, XIR
  36. #define ALU16_V cpu.unused8[2] // for operate
  37. #define ALU16_V_LO ((uint8_t *)&cpu.unused16[2])[0]  // for operate
  38. #define ALU16_V_HI ((uint8_t *)&cpu.unused16[2])[1] // for operate
  39. #define RAM cpu.ram // 2k ram
  40. #define MMU_READ mm_read // mmu read
  41. #define MMU_WRITE mm_write // mmu write

  42. // ============== < extern symbols > ====================
  43. extern uint8_t MMU_READ(uint16_t address);
  44. extern void MMU_WRITE(uint16_t address, uint8_t value);

  45. // ------------ < addressing mode > ---------------------

  46. // <IM>
  47. void IM(void)
  48. {
  49.   VALUE = MMU_READ(PC);
  50.   
  51.   PC += 1;
  52. }
  53. // <ZD>
  54. void ZD(void)
  55. {
  56.   A8_R = MMU_READ(PC);
  57.   
  58.   VALUE = RAM[A8_R]; // ram[offset]
  59.   
  60.   PC += 1;
  61. }
  62. // <ZX>
  63. void ZX(void)
  64. {
  65.   A8_T = MMU_READ(PC);
  66.   A8_R = A8_T + X; // ram[x+offset]
  67.   
  68.   VALUE = RAM[A8_R];
  69.   
  70.   PC += 1;
  71. }
  72. // <ZY>
  73. void ZY(void)
  74. {
  75.   A8_T = MMU_READ(PC);
  76.   A8_R = A8_T + Y; // ram[y+offset]
  77.   
  78.   VALUE = RAM[A8_R];
  79.   
  80.   PC += 1;
  81. }
  82. // <ABS>
  83. void __ABS(void)
  84. {
  85.   A16_R_LO = MMU_READ(PC);
  86.   A16_R_HI = MMU_READ(PC + 1);
  87.   
  88.   VALUE = MMU_READ(A16_R); // global mem[offset16]
  89.   
  90.   PC += 2;
  91. }
  92. // <ABSX>
  93. void __ABSX(void)
  94. {
  95.   A16_T_LO = MMU_READ(PC);
  96.   A16_T_HI = MMU_READ(PC + 1);
  97.   A16_R = A16_T + X; // global mem[x+offset16]
  98.   
  99.   VALUE = MMU_READ(A16_R);
  100.   
  101.   PC += 2;
  102. }
  103. // <ABSY>
  104. void __ABSY(void)
  105. {
  106.   A16_T_LO = MMU_READ(PC);
  107.   A16_T_HI = MMU_READ(PC + 1);
  108.   A16_R = A16_T + Y; // global mem[y+offset16]
  109.   
  110.   VALUE = MMU_READ(A16_R);
  111.   
  112.   PC += 2;
  113. }
  114. // <XIR>
  115. void XIR(void)
  116. {
  117.   A8_T = MMU_READ(PC);
  118.   A8_R = A8_T + X;
  119.   
  120.   A16_R_LO = RAM[A8_R];
  121.   A16_R_HI = RAM[A8_R + 1];
  122.   
  123.   VALUE = MMU_READ(A16_R);
  124.   
  125.   PC += 1;
  126. }
  127. // <IRY>
  128. void IRY(void)
  129. {
  130.   A8_R = MMU_READ(PC);
  131.   
  132.   A16_T_LO = RAM[A8_R];
  133.   A16_T_HI = RAM[A8_R + 1];
  134.   
  135.   A16_R = A16_T + Y;
  136.   
  137.   VALUE = MMU_READ(A16_R);
  138.   
  139.   PC += 1;
  140. }
复制代码




#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-14 16:43:21 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-16 12:28 编辑

========================================================
cpu-instructions
========================================================
2a03都是一些简单的指令, 乘法都没有
基本都是x86指令的弱化版,不过也有稍有出入的指令
大致说一下各种类型的指令以及算法
//////////////// 加载 - LDA/LDX/LDY ////////////////////
// flags effect:Z/N                                                                          
// reg <- imm8/mem                                                                                                                                                  
////////////////////////////////////////////////////////  
A        A9: ACC                                2 cycles
A        A5: ZERO PAGE                3 cycles
A        B5: ZERO PAGE X                4 cycles
A        AD: ABS                                4 cycles
A        BD: ABS X                        4 cycles (crossing page ++ cycles)
A        B9: ABS Y                        4 cycles (crossing page ++ cycles)
A        A1: X INDIRECT                6 cycles
A        B1: INDIRECT Y                5 cycles (crossing page ++ cycles)        
X        A2: X REG                        2 cycles
X        A6: ZERO PAGE                3 cycles
X        B6: ZERO PAGE Y                4 cycles
X        AE: ABS                                4 cycles
X        BE: ABS Y                        4 cycles (crossing page ++ cycles)
Y        A0: Y REG                        2 cycles
Y        A4: ZERO PAGE                3 cycles
Y        B4: ZERO PAGE X                4 cycles
Y        AC: ABS                                4 cycles
Y        BC: ABS X                        4 cycles (crossing page ++ cycles)
//////////////// 写回 - STA/STX/STY ////////////////////
// flags effect:no effect                                                         
// mem <- reg                                                                                                                                                   
////////////////////////////////////////////////////////
A        85: ZERO PAGE                3 cycles
A        95: ZERO PAGE X                4 cycles
A        80: ABS                                4 cycles
A        90: ABS X                        5 cycles
A        99: ABS Y                        5 cycles
A        81: X INDIRECT                6 cycles
A        91: INDIRECT Y                6 cycles        
X        86: ZERO PAGE                3 cycles
X        96: ZERO PAGE Y                4 cycles
X        8E: ABS                                4 cycles
Y        84: ZERO PAGE                3 cycles
Y        94: ZERO PAGE X                4 cycles
Y        8C: ABS                                4 cycles
/////////////////////  PSB        ////////////////////////////
// all is 2 cycles                                                                                                                           
////////////////////////////////////////////////////////
18: // CLC , clr c_flag
D8: // CLD , clr d_flag
58: // CLI , clr i_flag
B8: // CLV , clr v_flag
38: // SEC , set c_flag
F8: // SED , set d_flag
78: // SEI , set i_flag
/////////////////// JCC ////////////////////////////////
// 分支跳转, 单字节有符号偏移 全是2cycles                                
// 在跳转发生之后, 会额外增加1cycles                                 
////////////////////////////////////////////////////////
90:         BCC C clr JMP               
D0:         BNE Z clr JMP        
10:         BPL N clr JMP        
50:         BVC V clr JMP        
B0:         BCS C set JMP        
F0:         BEQ Z set JMP        
30:         BMI N set JMP
70:         BVS V set JMP                                                               
////////// 原子 - DEC/DEX/DEY/INC/INX/INY //////////////
// flags effect:Z/N                                                                          
// ++[--] reg[mem]                                                                          
////////////////////////////////////////////////////////
<DEC>
A        C6: ZERO PAGE                5 cycles
A        D6: ZERO PAGE X                6 cycles
A        CE: ABS                                6 cycles
A        DE: ABS X                        7 cycles
X        CA: REG                                2 cycles
Y        88: REG                                2 cycles
<INC>
A        E6: ZERO PAGE                5 cycles
A        F6: ZERO PAGE X                6 cycles
A        EE: ABS                                6 cycles
A        FE: ABS X                        7 cycles
X        E8: REG                                2 cycles
Y        C8: REG                                2 cycles
///////////////// 逻辑 - ORA/EOR/AND ///////////////////
// flags effect:Z/N                                                                                                                          
// 取imm8/mem的值之后                                                                 
// ORA/EOR/AND 分别对应                                                                  
//A |=        ^=        &= mem/imm8                                                                  
////////////////////////////////////////////////////////
<ORA>
09: #$                                2 cycles
05: ZERO PAGE                3 cycles
15: ZERO PAGE X                4 cycles
0D: ABS                                4 cycles
1D: ABS X                        4 cycles (crossing page ++ cycles)
19: ABS Y                        4 cycles (crossing page ++ cycles)
01: X INDIRECT                6 cycles
11: INDIRECT Y                4 cycles (crossing page ++ cycles)
<EOR>
49: #$                                2 cycles
45: ZERO PAGE                3 cycles
55: ZERO PAGE X                4 cycles
4D: ABS                                4 cycles
5D: ABS X                        4 cycles (crossing page ++ cycles)
59: ABS Y                        4 cycles (crossing page ++ cycles)
41: X INDIRECT                6 cycles
51: INDIRECT Y                4 cycles (crossing page ++ cycles)
<AND>
29: #$                                2 cycles
25: ZERO PAGE                3 cycles
35: ZERO PAGE X                4 cycles
2D: ABS                                4 cycles
3D: ABS X                        4 cycles (crossing page ++ cycles)
39: ABS Y                        4 cycles (crossing page ++ cycles)
21: X INDIRECT                6 cycles
31: INDIRECT Y                4 cycles (crossing page ++ cycles)

点评

ヲ隐约想起了当年做毕业设计给 CPU 写虚拟机。  发表于 2016-3-14 19:33
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-16 12:25:12 | 显示全部楼层
/////////////// 栈 - PHP/PLP/PHA/PLA //////////////////
// flags only PLA effect:Z/N                          
// 只能被 P/A 寄存器使用                              
// 出栈入栈跟x86相反                                   
// PUSH 存储然后栈指针S减去1                          
// POP  栈指针S加上1偏移再取值                        
///////////////////////////////////////////////////////
OP48:  PHA  // PUSH A 3 cycles                  
OP08:  PHP  // PUSH P 3 cycles
OP68:  PLA  // POP A 4 cycles
OP28:  PLP  // POP P 4 cycles
////////////////// 算术 - ADC/SBC //////////////////////
// flags effect:Z/N/V/C                              
// 2a03只有两个算术指令,都是带进位C的类似x86的adc/sbb
// adc与x86的一样, sbc相反当c为0的时候需要多减去1,   
// 进位设置则不需要额外减1,[sbc标志检测也是反的]      
// 可以通过用clc/sec 设置/复位C标志模拟add, sub      
////////////////////////////////////////////////////////
<ADC>
69: #$              2 cycles
65: ZERO PAGE       3 cycles
75: ZERO PAGE X     4 cycles
6D: ABS             4 cycles
7D: ABS X           4 cycles (crossing page ++ cycles)
79: ABS Y           4 cycles (crossing page ++ cycles)
61: X INDIRECT      6 cycles
71: INDIRECT Y      4 cycles (crossing page ++ cycles)
<SBC>
EB: #$              2 cycles (unofficial)
E9: #$              2 cycles
E5: ZERO PAGE       3 cycles
F5: ZERO PAGE X     4 cycles
ED: ABS             4 cycles
FD: ABS X           4 cycles (crossing page ++ cycles)
F9: ABS Y           4 cycles (crossing page ++ cycles)
E1: X INDIRECT      6 cycles
F1: INDIRECT Y      4 cycles (crossing page ++ cycles)
////////////// 移位 - ASL/LSR/ROL/ROR //////////////////
// flags effect:Z/N/C                                 
// A/mem移位                                          
// 前两个移位不带c_flag, 后两个带                     
// 跟x86一样, 不过一次指令只能移一位                  
// ASL/ROL向左, LSR/ROR向右                           
////////////////////////////////////////////////////////
<ASL>
0A: ACC             2 cycles
06: ZERO PAGE       5 cycles
16: ZERO PAGE X     6 cycles
0E: ABS             6 cycles
1E: ABS X           7 cycles
<LSR>
4A: ACC             2 cycles
46: ZERO PAGE       5 cycles
56: ZERO PAGE X     6 cycles
4E: ABS             6 cycles
5E: ABS X           7 cycles
<ROL>
2A: ACC             2 cycles
26: ZERO PAGE       5 cycles
36: ZERO PAGE X     6 cycles
2E: ABS             6 cycles
3E: ABS X           7 cycles
<ROR>
6A: ACC             2 cycles
66: ZERO PAGE       5 cycles
76: ZERO PAGE X     6 cycles
6E: ABS             6 cycles
7E: ABS X           7 cycles
/////////////// 比较 - CMP/CPX/CPY /////////////////////
// flags effect:Z/N/V/C                              
// same as SBC[set c_flag], not set v_flag                           
////////////////////////////////////////////////////////
A   C9: #$              2 cycles
A   C5: ZERO PAGE       3 cycles
A   D5: ZERO PAGE X     4 cycles
A   CD: ABS             4 cycles
A   DD: ABS X           4 cycles (crossing page ++ cycles)
A   D9: ABS Y           4 cycles (crossing page ++ cycles)
A   C1: X INDIRECT      6 cycles
A   D1: INDIRECT Y      5 cycles (crossing page ++ cycles)
X   E0: #$              2 cycles
X   E4: ZERO PAGE       3 cycles
X   EC: ABS             4 cycles
Y   C0: #$              2 cycles
Y   C4: ZERO PAGE       3 cycles
Y   CC: ABS             4 cycles
/////////////// 测试 - BIT /////////////////////////////
// flags effect:N/V/Z                                 
// bit A, src                                          
// 仅仅设置标志                                       
//  Algorithm:                                       
//              clr v_flag, n_flag, z_flag            
//              p.v_flag = src & 0x40 ? set : reset   
//              p.n_flag = src & 0x80 ? set : reset   
//              p.z_flag =(src & A)   ? set : reset   
////////////////////////////////////////////////////////
24: ZERO PAGE       3 cycles
2C: ABS             4 cycles
///////////// 过程 - JMP/JSR/RTS/RTI ///////////////////
// <jmp-4C> 4c imm8-lo imm8-hi (三字节)    3 cycles   
// 绝对寻址跳转 PC-LO = mm_read(pc)                  
//              PC-HI = mm_read(pc+1)                 
// <jmp-6C> 6c imm8-lo imm8-hi (三字节)    5 cycles   
// 间接寻址跳转                                       
// 根据6c后面两个字节做地址寻址两次得出相应的16位偏移
// jmp6c有个穿页BUG                                    
// 举个栗子:
//  $2500: 33
//  ...
//  $25FF: 76
//  $2600: 89
//  $7574: 6C FF 25
//  PC <- $7574
// 初始PC为7574, 根据6C获得指令类型jmp-6c
// 之后拿出后两个字节作为间接寻址所用地址25FF
// 先寻址25FF得到76作地位地址
// 再寻址2600得到89作高位地址
// 那么PC = 8976 理想情况下是如此
// 不过因为25FF-2600而发生了穿页,从而导致高位地址不会增加而低位间接地址为回退为0
// 所以当穿页发生实际是如下步骤提取
// 先寻址--FF的地址得到低位
// 再寻址--00的地址作为高位
// 所以此次寻址得到的偏移为3376
// <JSR-20> 20 imm8-lo imm8-hi (三字节) 6 cycles
// 相当于x86的call, 只有绝对寻址一种方式.对应操作码20  
// 保存下一条指令的PC-1的指针[word]入栈[先入高位]
// <RTS-60> 60 (单字节) 6 cycles
// 相当于ret 弹出栈顶PC, 之后PC加1
// <RTI-40> 40 (单字节) 6 cycles
// 从中断返回, 栈顶先弹出P, 然后弹出PC, 此PC不要再加1了
////////////////////////////////////////////////////////
/////////////// reg to reg - BIT /////////////////////////////
//; trans REG to REG
//; tXN
//;        X source REG
//;   N target REG
//; TXS not set Z-N flag (only this.. other must set Z-N flag)...
//; cycles all 2 cycles ...  
//                AA: ; TAX
//                8A: ; TXA       
//                A8: ; TAY
//                98: ; TYA       
//                BA: ; TSX               
//                9A: ; TXS (RTOR only this not set flag)
////////////////////////////////////////////////////////
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-16 12:31:13 | 显示全部楼层
指令表大全 http://codepad.org/2mhmsEMV
IM为立即数
RA/RX/RY为寄存器A/X/Y
AB/AX/AY 为绝对值类寻址
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-17 12:13:53 | 显示全部楼层
  1. // == < cpu.c > ========================================================
  2. // easy way to emu 2a03 chip
  3. //  

  4. #ifdef _MSC_VER
  5. #include "inttypes.h"
  6. #else
  7. #include <inttypes.h>
  8. #endif


  9. struct _2a03_cpu
  10. {
  11.   uint8_t a;
  12.   uint8_t x;
  13.   uint8_t y;
  14.   uint8_t p;
  15.   uint8_t s;
  16.   uint16_t pc;
  17.   uint8_t unused8[8];
  18.   uint16_t unused16[4];
  19.   uint8_t ram[0x0800];
  20.   int32_t dma_cycles;
  21.   int32_t int_signal;
  22.   int32_t int_pending;
  23.   // IRQ_LIST irq_pending_list
  24. };

  25. static struct _2a03_cpu cpu;


  26. #define IRQ_FLAG 1
  27. #define NMI_FLAG 2
  28. #define A cpu.a // A
  29. #define X cpu.x // X
  30. #define Y cpu.y // Y
  31. #define S cpu.s // S
  32. #define P cpu.p // P
  33. #define PC cpu.pc // PC
  34. #define PC_LO ((uint8_t *)&cpu.pc)[0] // PC low 8 bit
  35. #define PC_HI ((uint8_t *)&cpu.pc)[1] // PC high 8 bit
  36. #define A8T cpu.unused8[0] // for ZERO X, Y. addr8_temp
  37. #define A8R cpu.unused8[1] // for ZERO PAGE. addr8_real
  38. #define ALU8V cpu.unused8[2] // for operate
  39. #define VALUE cpu.unused8[3]  // for get value from address/imm
  40. #define X_FIELD cpu.unused8[4] // opcode low 4 bit
  41. #define Y_FIELD cpu.unused8[5] // opcode high 4 bit
  42. #define SX_FIELD cpu.unused8[6] // X_FIELD's sign bit
  43. #define SY_FIELD cpu.unused8[7] // Y_FIELD's sign bit
  44. // We always use the little-endian CPU, isn't it?
  45. #define A16T cpu.unused16[0] // for ABS X, Y, IRY. addr16_temp
  46. #define A16T_LO ((uint8_t *)&cpu.unused16[0])[0] // for ABS X, Y, IRY
  47. #define A16T_HI ((uint8_t *)&cpu.unused16[0])[1] // for ABS X, Y, IRY
  48. #define A16R cpu.unused16[1] // for ASB, ABS X, Y, IRY, XIR. addr16_real
  49. #define A16R_LO ((uint8_t *)&cpu.unused16[1])[0] // for ASB, ABS X, Y, IRY, XIR
  50. #define A16R_HI ((uint8_t *)&cpu.unused16[1])[1]// for ASB, ABS X, Y, IRY, XIR
  51. #define ALU16V cpu.unused8[2] // for operate
  52. #define ALU16V_LO ((uint8_t *)&cpu.unused16[2])[0]  // for operate
  53. #define ALU16V_HI ((uint8_t *)&cpu.unused16[2])[1] // for operate
  54. #define FPRG ((uint8_t *)&cpu.unused16[3])[0] // first opcode
  55. #define RAM cpu.ram // 2k ram
  56. #define MMU_READ mm_read // mmu read
  57. #define MMU_WRITE mm_write // mmu write
  58. #define C_FLAG (1 << 0)
  59. #define Z_FLAG (1 << 1)
  60. #define I_FLAG (1 << 2)
  61. #define D_FLAG (1 << 3)
  62. #define B_FLAG (1 << 4)
  63. #define R_FLAG (1 << 5)
  64. #define V_FLAG (1 << 6)
  65. #define N_FLAG (1 << 7)
  66. #define NMI_VECTOR  0xFFFA      // NMI
  67. #define RES_VECTOR  0xFFFC      // Reset
  68. #define IRQ_VECTOR  0xFFFE      // IRQ
  69. #define STACK(x) cpu.ram[0x100+(x)]
  70. #define CLR_FLAG(x) \
  71. do {                \
  72. P &= ~(x);         \
  73. } while(0)         
  74. #define SET_FLAG(x) \
  75. do {                \
  76. P |= (x);          \
  77. } while(0)         
  78. #define RESET_ZN(x) \
  79. do {                \
  80. CLR_FLAG(N_FLAG|Z_FLAG); \
  81. P |= zn_table[(x)];      \
  82. } while(0)         
  83.    
  84. typedef void(*__iiq_lea)(void);
  85. typedef void(*__iiq_read)(void);

  86. // ============== < extern symbols > ====================
  87. extern uint8_t MMU_READ(uint16_t address);
  88. extern void MMU_WRITE(uint16_t address, uint8_t value);

  89. // ------------------- table ------------------------
  90. static const uint8_t zn_table[256] =
  91. {
  92.   2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  93.   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  94.   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  95.   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   
  96.   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  97.   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  98.   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  99.   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

  100.   128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
  101.   128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
  102.   128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
  103.   128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
  104.   128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
  105.   128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
  106.   128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
  107.   128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128
  108. };
  109. static const uint8_t cpu_cycles[256] =
  110. {
  111. /*--  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F   --*/
  112. /*0*/ 2, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, /*0*/
  113. /*1*/ 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, /*1*/
  114. /*2*/ 6, 6, 0, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6, /*2*/
  115. /*3*/ 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, /*3*/
  116. /*4*/ 6, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6, /*4*/
  117. /*5*/ 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, /*5*/
  118. /*6*/ 6, 6, 0, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6, /*6*/
  119. /*7*/ 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, /*7*/
  120. /*8*/ 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, /*8*/
  121. /*9*/ 2, 6, 0, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5, /*9*/
  122. /*A*/ 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, /*A*/
  123. /*B*/ 2, 5, 0, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4, /*B*/
  124. /*C*/ 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, /*C*/
  125. /*D*/ 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, /*D*/
  126. /*E*/ 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, /*E*/
  127. /*F*/ 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7  /*F*/
  128. /*--  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F   --*/
  129. };
  130. // ------------------ < addressing > --------------------------------
  131. static void L_IM(void) { }
  132. static void L_ZD(void) { A16R_LO = MMU_READ(PC++); A16R_HI = 0; }
  133. static void L_ZX(void) { A16T_LO = MMU_READ(PC++); A16R_HI = 0; A16R_LO = A16T_LO + X; }
  134. static void L_ZY(void) { A16T_LO = MMU_READ(PC++); A16R_HI = 0; A16R_LO = A16T_LO + Y; }
  135. static void L_AB(void) { A16R_LO = MMU_READ(PC++); A16R_HI = MMU_READ(PC++); }
  136. static void L_AX(void) { A16T_LO = MMU_READ(PC++); A16T_HI = MMU_READ(PC++); A16R = A16T + X; }
  137. static void L_AY(void) { A16T_LO = MMU_READ(PC++); A16T_HI = MMU_READ(PC++); A16R = A16T + Y; }
  138. static void L_XI(void) { A16T_LO = MMU_READ(PC++); A16T_HI = A16T_LO + X;    A16R_LO = RAM[A16T_HI]; A16R_HI = RAM[A16T_HI+1]; }
  139. static void L_IY(void) { A16T_LO = MMU_READ(PC++); A16T_HI = RAM[A16T_LO+1]; A16T_LO = RAM[A16T_LO]; A16R    = A16T + Y; }
  140. static void   IM(void) { VALUE = MMU_READ(PC++); }
  141. static void   ZD(void) { L_ZD(); VALUE = MMU_READ(A16R); }
  142. static void   ZX(void) { L_ZX(); VALUE = MMU_READ(A16R); }
  143. static void   ZY(void) { L_ZY(); VALUE = MMU_READ(A16R); }
  144. static void   AB(void) { L_AB(); VALUE = MMU_READ(A16R); }
  145. static void   AX(void) { L_AX(); VALUE = MMU_READ(A16R); }
  146. static void   AY(void) { L_AY(); VALUE = MMU_READ(A16R); }
  147. static void   XI(void) { L_XI(); VALUE = MMU_READ(A16R); }
  148. static void   IY(void) { L_IY(); VALUE = MMU_READ(A16R); }
  149. static __iiq_lea  call_lea[9]  = { L_XI, L_ZD, L_IM, L_AB, L_IY, L_ZX, L_AY, L_AX, L_ZY };
  150. static __iiq_read call_read[9] = {   XI,   ZD,   IM,   AB,   IY,   ZX,   AY,   AX,   ZY };


  151. void set_dma_cycles(int32_t cycles, int forced)
  152. {
  153.   if (forced)
  154.       
  155.     cpu.dma_cycles = cycles;
  156.    
  157.   else
  158.       
  159.     cpu.dma_cycles+= cycles;
  160. }

  161. int _nmi_pending_status (void) { return (cpu.int_pending & NMI_FLAG); }
  162. int _irq_pending_status (void) { return (cpu.int_pending & IRQ_FLAG); }
  163. void _nmi_pending_set (void) { cpu.int_pending |= NMI_FLAG; }
  164. void _irq_pending_set (void) { cpu.int_pending |= IRQ_FLAG; }
  165. void _nmi_pending_clr (void) { cpu.int_pending &= ~NMI_FLAG; }
  166. void _irq_pending_clr (void) { cpu.int_pending &= ~IRQ_FLAG; }
  167. int _nmi_signal_status (void) { return (cpu.int_signal & NMI_FLAG); }
  168. int _irq_signal_status (void) { return (cpu.int_signal & IRQ_FLAG); }
  169. void _nmi_signal_set (void) { cpu.int_signal |= NMI_FLAG; }
  170. void _irq_signal_set (void) { cpu.int_signal |= IRQ_FLAG; }
  171. void _nmi_signal_clr (void) { cpu.int_signal &= ~NMI_FLAG; }
  172. void _irq_signal_clr (void) { cpu.int_signal &= ~IRQ_FLAG; }
  173. void fast_irq_proc (void)
  174. {
  175.   
  176. }
  177. void fast_nmi_proc (void)
  178. {
  179.   
  180. }
复制代码
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-17 12:14:55 | 显示全部楼层
  1. int32_t exec_2a03(int32_t dispatch_cycles)
  2. {
  3.   static const uint8_t indirect_type[8] =
  4.   {
  5.     N_FLAG,
  6.     N_FLAG,
  7.     V_FLAG,
  8.     V_FLAG,
  9.     C_FLAG,
  10.     C_FLAG,
  11.     Z_FLAG,
  12.     Z_FLAG
  13.   };
  14.   static const uint8_t mapper_type[8] =
  15.   {
  16.     0,
  17.     N_FLAG,
  18.     0,
  19.     V_FLAG,
  20.     0,
  21.     C_FLAG,
  22.     0,
  23.     Z_FLAG
  24.   };
  25.   int32_t cur_cycles_count;  
  26.   uint8_t t;
  27.   uint8_t *p;
  28.   
  29.   t = A;
  30.   p =&A;
  31.   
  32.   cur_cycles_count = 0;
  33.   
  34.   while (cur_cycles_count < dispatch_cycles) // remain cycles to do frame ?
  35.   {
  36.     if (cpu.dma_cycles > 0) // DMA active ?
  37.     {
  38.       if (cpu.dma_cycles >= (dispatch_cycles - cur_cycles_count)) // remain cycles burning out ?
  39.       {
  40.         cpu.dma_cycles -= (dispatch_cycles - cur_cycles_count);
  41.         return dispatch_cycles; // ready to next frame
  42.       }
  43.       else // dma buring out
  44.       {         
  45.         cur_cycles_count += cpu.dma_cycles;
  46.         cpu.dma_cycles = 0; // dma cycles over  
  47.       }
  48.       
  49.       FPRG = MMU_READ(PC++); // fetch first opcode
  50.       
  51.       X_FIELD = FPRG & 0x0F;
  52.       Y_FIELD =(FPRG & 0xF0) >> 4;
  53.       SX_FIELD = X_FIELD >> 3;
  54.       SY_FIELD = Y_FIELD >> 3;
  55.       
  56.       switch (FPRG)
  57.       {
  58.       // LOAD
  59.         case 0xBE: // LDX-AY
  60.           X_FIELD-= 4;
  61.           goto _tsa;
  62.         case 0xB6: // LDX-ZY
  63.           X_FIELD = 16;
  64.           goto _tsa;
  65.         case 0xA2: // LDX-IM
  66.           X_FIELD = 9;
  67.         case 0xA6: // LDX-ZD
  68.         case 0xAE: // LDX-AB
  69.         _tsa:
  70.           p = &Y;
  71.           goto ld_entry;
  72.         case 0xA0: // LDY-IM
  73.           X_FIELD = 9;
  74.         case 0xA4: // LDY-ZD
  75.         case 0xB4: // LDY-ZX
  76.         case 0xAC: // LDY-AB
  77.         case 0xBC: // LDY-AX
  78.           p = &Y;
  79.         case 0xA1: // LDA-XI
  80.         case 0xB1: // LDA-IY
  81.         case 0xA5: // LDA-ZD
  82.         case 0xB5: // LDA-ZX
  83.         case 0xA9: // LDA-IM
  84.         case 0xB9: // LDA-AY
  85.         case 0xAD: // LDA-AB
  86.         case 0xBD: // LDA-AX
  87.         
  88.         ld_entry:
  89.         
  90.           call_read[((Y_FIELD & 1) << 2) + (X_FIELD >> 2)]();
  91.    
  92.           p[0] = VALUE;
  93.    
  94.           RESET_ZN(VALUE);
  95.         
  96.         break;
  97.       // WRITE
  98.         case 0x96: // STX-ZY
  99.           X_FIELD = 16;
  100.         case 0x86: // STX-ZD
  101.         case 0x8E: // STX-AB
  102.           t = X;
  103.           goto st_entry;
  104.         case 0x84: // STY-ZD
  105.         case 0x94: // STY-ZX
  106.         case 0x8C: // STY-AB
  107.           t = Y;
  108.         case 0x81: // STA-XI
  109.         case 0x91: // STA-IY
  110.         case 0x85: // STA-ZD
  111.         case 0x95: // STA-ZX
  112.         case 0x99: // STA-AY
  113.         case 0x8D: // STA-AB
  114.         case 0x9D: // STA-AX

  115.         st_entry:
  116.         
  117.           call_lea[((Y_FIELD & 1) << 2) + (X_FIELD >> 2)]();

  118.           MMU_WRITE(A16R, t);
  119.         
  120.         break;
  121.       // ARITH
  122.         case 0x61: // ADC-XI
  123.         case 0x71: // ADC-IY
  124.         case 0x65: // ADC-ZD
  125.         case 0x75: // ADC-ZX
  126.         case 0x69: // ADC-IM
  127.         case 0x79: // ADC-AY
  128.         case 0x6D: // ADC-AB
  129.         case 0x7D: // ADC-AX
  130.         case 0xE1: // SBC-XI
  131.         case 0xF1: // SBC-IY
  132.         case 0xE5: // SBC-ZD
  133.         case 0xF5: // SBC-ZX
  134.         case 0xE9: // SBC-IM
  135.         case 0xF9: // SBC-AY
  136.         case 0xED: // SBC-AB
  137.         case 0xFD: // SBC-AX        
  138.         
  139.           CLR_FLAG (N_FLAG | C_FLAG | Z_FLAG | V_FLAG);
  140.          
  141.           call_read[((Y_FIELD & 1) << 2) + (X_FIELD >> 2)]();
  142.   
  143.           if (SY_FIELD)
  144.           {
  145.           // SBC
  146.             ALU16V = ((uint16_t)A) - (uint16_t)VALUE - (uint16_t)((P & C_FLAG) ^ 1);
  147.   
  148.             P |= (((A ^ ALU16V_LO) & 0x80) & ((A ^ VALUE) & 0x80)) ? V_FLAG : 0;  
  149.   
  150.             P |= (ALU16V < 100) ? C_FLAG : 0;
  151.           }
  152.           else
  153.           {
  154.           // ADC
  155.             ALU16V = ((uint16_t)A) + (uint16_t)VALUE + (uint16_t)(P & C_FLAG);
  156.    
  157.             P |= ((~((A ^ VALUE) & 0x80)) & ((A ^ ALU16V_LO) & 0x80)) ? V_FLAG : 0;
  158.   
  159.             P |= ALU16V_HI;  
  160.           }
  161.   
  162.           P |= zn_table[ALU16V_LO];
  163.   
  164.           A  = ALU16V_LO;      
  165.         
  166.         break;
复制代码
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-17 12:15:12 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-23 11:24 编辑
  1.       
  2.       // LOGIC
  3.         case 0x01: // ORA-XI
  4.         case 0x11: // ORA-IY
  5.         case 0x05: // ORA-ZD
  6.         case 0x15: // ORA-ZX
  7.         case 0x09: // ORA-IM
  8.         case 0x19: // ORA-AY
  9.         case 0x0D: // ORA-AB
  10.         case 0x1D: // ORA-AX
  11.         case 0x21: // AND-XI
  12.         case 0x31: // AND-IY
  13.         case 0x25: // AND-ZD
  14.         case 0x35: // AND-ZX
  15.         case 0x29: // AND-IM
  16.         case 0x39: // AND-AY
  17.         case 0x2D: // AND-AB
  18.         case 0x3D: // AND-AX
  19.         case 0x41: // EOR-XI
  20.         case 0x51: // EOR-IY
  21.         case 0x45: // EOR-ZD
  22.         case 0x55: // EOR-ZX
  23.         case 0x49: // EOR-IM
  24.         case 0x59: // EOR-AY
  25.         case 0x4D: // EOR-AB
  26.         case 0x5D: // EOR-AX   
  27.         
  28.           call_read[((Y_FIELD & 1) << 2) + (X_FIELD >> 2)]();
  29.          
  30.           if (Y_FIELD > 3) // EOR
  31.             A ^= VALUE;
  32.           else if (Y_FIELD > 1) // AND
  33.             A &= VALUE;
  34.           else // ORA
  35.             A |= VALUE;
  36.          
  37.           RESET_ZN(A);      
  38.         
  39.         break;
  40.      // CMP
  41.         case 0xC0: // CPY-IM
  42.           X_FIELD = 9;
  43.         case 0xC4: // CPY-ZD
  44.         case 0xCC: // CPY-AB
  45.           t = Y;
  46.           goto cmp_enrty;
  47.          
  48.         case 0xE0: // CPX-IM
  49.           X_FIELD = 9;
  50.         case 0xE4: // CPX-ZD
  51.         case 0xEC: // CPX-AB
  52.           t = X;
  53.         case 0xC1: // CMP-XI
  54.         case 0xD1: // CMP-IY
  55.         case 0xC5: // CMP-ZD
  56.         case 0xD5: // CMP-ZX
  57.         case 0xC9: // CMP-IM
  58.         case 0xD9: // CMP-AY
  59.         case 0xCD: // CMP-AB
  60.         case 0xDD: // CMP-AX   
  61.         
  62.         cmp_enrty:
  63.         
  64.           call_read[((Y_FIELD & 1) << 2) + (X_FIELD >> 2)]();
  65.   
  66.           CLR_FLAG (N_FLAG | C_FLAG | Z_FLAG);
  67.   
  68.           ALU16V = ((uint16_t)t) - (uint16_t)VALUE;

  69.           P |= (ALU16V < 100 ? C_FLAG : 0);
  70.   
  71.           P |= zn_table[ALU16V_LO];
  72.          
  73.         break;
  74.      
  75.      // SHIFT
  76.         case 0x06: // ASL-ZD
  77.         case 0x16: // ASL-ZX
  78.         case 0x0A: // ASL_A
  79.         case 0x0E: // ASL-AB
  80.         case 0x1E: // ASL-AX
  81.         case 0x26: // ROL-ZD
  82.         case 0x36: // ROL-ZX
  83.         case 0x2A: // ROL_A
  84.         case 0x2E: // ROL-AB
  85.         case 0x3E: // ROL-AX
  86.         case 0x46: // LSR-ZD
  87.         case 0x56: // LSR-ZX
  88.         case 0x4A: // LSR_A
  89.         case 0x4E: // LSR-AB
  90.         case 0x5E: // LSR-AX
  91.         case 0x66: // ROR-ZD
  92.         case 0x76: // ROR-ZX
  93.         case 0x6A: // ROR_A
  94.         case 0x6E: // ROR-AB
  95.         case 0x7E: // ROR-AX
  96.         
  97.           ALU8V = P & C_FLAG & ((Y_FIELD & 2) >> 1); // save old c, either destroy ?
  98.   
  99.           CLR_FLAG (N_FLAG | C_FLAG | Z_FLAG);
  100.   
  101.           if (X_FIELD == 0x0A)
  102.             VALUE = A;
  103.           else
  104.             call_read[((Y_FIELD & 1) << 2) + (X_FIELD >> 2)]();

  105.           if (Y_FIELD < 4) // left
  106.           {
  107.             P |= (VALUE >> 7);
  108.    
  109.             VALUE <<= 1;
  110.             VALUE  |= ALU8V;
  111.           }
  112.           else // right
  113.           {
  114.             P |= (VALUE & C_FLAG);
  115.             
  116.             VALUE >>= 1;
  117.             VALUE  |= (ALU8V << 7);
  118.           }
  119.           RESET_ZN(VALUE);
  120.          
  121.           if (X_FIELD == 0x0A)
  122.             A = VALUE;
  123.           else
  124.             MMU_WRITE(A16R, VALUE);     
  125.         
  126.         break;
  127.         
  128.      // ATOM
  129.      
  130.         case 0xE8: // INX
  131.           VALUE = ++X; goto __atom_out;
  132.         case 0xCA: // DEX
  133.           VALUE = --X; goto __atom_out;
  134.         case 0xC8: // INY
  135.           VALUE = ++Y; goto __atom_out;
  136.         case 0x88: // DEY
  137.           VALUE = --Y; goto __atom_out;
  138.         case 0xC6: // DEC-ZD
  139.         case 0xD6: // DEC-ZX
  140.         case 0xCE: // DEC-AB
  141.         case 0xDE: // DEC-AX   
  142.         case 0xE6: // INC-ZD
  143.         case 0xF6: // INC-ZX
  144.         case 0xEE: // INC-AB
  145.         case 0xFE: // INC-AX
  146.         
  147.           call_read[((Y_FIELD & 1) << 2) + (X_FIELD >> 2)]();
  148.    
  149.           if (Y_FIELD > 0x0D)
  150.             VALUE++;
  151.           else
  152.             VALUE--;

  153.           MMU_WRITE(A16R, VALUE);
  154.          
  155.     __atom_out:
  156.           RESET_ZN(VALUE);  

  157.         break;  
  158.         
  159.      // JCC
  160.         
  161.         case 0x90: // BCC C clr JMP 100 (4)
  162.         case 0xD0: // BNE Z clr JMP 110 (6)
  163.         case 0x10: // BPL N clr JMP 000 (0)
  164.         case 0x50: // BVC V clr JMP 010 (2)
  165.         case 0xB0: // BCS C set JMP 101 (5)
  166.         case 0xF0: // BEQ Z set JMP 111 (7)
  167.         case 0x30: // BMI N set JMP 001 (1)
  168.         case 0x70: // BVS V set JMP 011 (3)
  169.         
  170.           ALU8V = FPRG >> 5;

  171.           if (indirect_type[ALU8V] ^ mapper_type[ALU8V])
  172.           {
  173.             // jcc entry, + 1 cycles
  174.   
  175.             ALU8V = MMU_READ(PC++);
  176.   
  177.   
  178.             PC += (int8_t)ALU8V;
  179.           }
  180.           else
  181.               
  182.             PC += 1;
  183.         
  184.         break;

  185.      // PSB
  186.         
  187.         case 0x18: P &= ~C_FLAG; break;
  188.         case 0xD8: P &= ~D_FLAG; break;
  189.         case 0xB8: P &= ~V_FLAG; break;
  190.         case 0x38: P |=  C_FLAG; break;
  191.         case 0xF8: P |=  D_FLAG; break;
  192.         case 0x78: P |=  I_FLAG; break;
  193.         case 0x58:
  194.         
  195.           P &= ~I_FLAG;
  196.         
  197.           if (_irq_pending_status() && (cur_cycles_count < dispatch_cycles) && !_nmi_signal_status())
  198.           {
  199.             _irq_pending_clr();
  200.             _irq_signal_clr();
  201.         
  202.             STACK(S--) = PC_HI;
  203.             STACK(S--) = PC_LO;
  204.             STACK(S--) = P;
  205.         
  206.             SET_FLAG(I_FLAG);
  207.         
  208.             PC_LO = MMU_READ(IRQ_VECTOR);
  209.             PC_HI = MMU_READ(IRQ_VECTOR+1);
  210.         
  211.             cur_cycles_count += 7;  
  212.           }
  213.         
  214.         break;      
  215.         
  216.      // REG GAY
  217.         
  218.         case 0xAA: X = A; RESET_ZN(A); break;
  219.         case 0x8A: A = X; RESET_ZN(A); break;
  220.         case 0xA8: Y = A; RESET_ZN(A); break;
  221.         case 0x98: A = Y; RESET_ZN(A); break;
  222.         case 0xBA: X = S; RESET_ZN(X); break;
  223.         case 0x9A: S = X;
  224.         
  225.      // STACK
  226.      
  227.         case 0x28: P = STACK(++S); break;
  228.         case 0x68: A = STACK(++S); RESET_ZN(A); break;
  229.         case 0x08: STACK(S--) = P; break;
  230.         case 0x48: STACK(S--) = A; break;
  231.         
  232.      // BIT
  233.         case 0x24:
  234.         case 0x2C:
  235.         
  236.           call_read[((Y_FIELD & 1) << 2) + (X_FIELD >> 2)]();
  237.   
  238.           CLR_FLAG (N_FLAG | V_FLAG | Z_FLAG);  
  239.   
  240.           P |= (VALUE & 0xC0);
  241.   
  242.           P |= (VALUE & A) ? Z_FLAG : 0;   
  243.          
  244.         break;
  245.         
  246.      // PROC
  247.         
  248.         case 0x4C:
  249.         case 0x6C:
  250.         case 0x60:
  251.         case 0x40:
  252.         case 0x20:
  253.           if ((FPRG & 0x0C) || (FPRG == 0x20)) // jmp/jsr
  254.           {
  255.             A16T_LO = MMU_READ(PC++);
  256.             A16T_HI = MMU_READ(PC);
  257.    
  258.             PC += (X_FIELD >> 3);
  259.    
  260.             if (FPRG == 0x6C) // jmp6c
  261.             {
  262.               if ((A16T & 0xFF) != 0xFF)
  263.               {
  264.                 A16R_LO = MMU_READ(A16T);
  265.                 A16R_HI = MMU_READ(A16T+1);
  266.    
  267.                 PC = A16R;
  268.    
  269.                 break;
  270.               }            
  271.               A16R_LO = MMU_READ(A16T);
  272.               A16R_HI = MMU_READ(A16T-255);
  273.    
  274.               PC = A16R;
  275.             }
  276.             else if (FPRG == 0x4C) // jmp4c
  277.             {
  278.               PC = A16T;
  279.             }
  280.             else // JSR
  281.             {
  282.               STACK(S--) = PC_HI;
  283.               STACK(S--) = PC_LO;

  284.               PC = A16T;
  285.             }
  286.           }
  287.           else // rts/rti
  288.           {
  289.             uint8_t i = ((Y_FIELD & 2) ^ 2) >> 1;
  290.         
  291.             if (i)
  292.               P = RAM[257+S];
  293.       
  294.             A16R_LO = RAM[257+S+i];
  295.             A16R_HI = RAM[258+S+i];
  296.         
  297.             S += (2+i);
  298.         
  299.             PC = A16R + (i^1);
  300.           }  
  301.          
  302.     case 0xEA: // NOP
  303.    
  304.         break;
  305.         
  306.     default:
  307.    
  308.         ((char *)t)[0] = -1;
  309.         
  310.         break;
  311.         
  312.       }
  313.       
  314.       cur_cycles_count += cpu_cycles[FPRG];
  315.       
  316.       
  317.       if (_nmi_signal_status() && (cur_cycles_count < dispatch_cycles))
  318.       {
  319.         _nmi_signal_clr();
  320.         
  321.         STACK(S--) = PC_HI;
  322.         STACK(S--) = PC_LO;
  323.         STACK(S--) = P;
  324.         
  325.         SET_FLAG(I_FLAG);
  326.         
  327.         PC_LO = MMU_READ(NMI_VECTOR);
  328.         PC_HI = MMU_READ(NMI_VECTOR+1);
  329.         
  330.         cur_cycles_count += 7;
  331.         
  332.         if (_irq_signal_status())
  333.         {
  334.           _irq_pending_set();
  335.           _irq_signal_clr();
  336.         }
  337.       }
  338.       if (_irq_signal_status() && !(P & I_FLAG) && (cur_cycles_count < dispatch_cycles))
  339.       {
  340.         _irq_signal_clr();
  341.         
  342.         STACK(S--) = PC_HI;
  343.         STACK(S--) = PC_LO;
  344.         STACK(S--) = P;
  345.         
  346.         SET_FLAG(I_FLAG);
  347.         
  348.         PC_LO = MMU_READ(IRQ_VECTOR);
  349.         PC_HI = MMU_READ(IRQ_VECTOR+1);
  350.         
  351.         cur_cycles_count += 7;
  352.       }
  353.     }
  354.   }
  355.   return cur_cycles_count;
  356. }
复制代码


少讲了很多东西,源代码也少写了很多, 稍微补上点...
DMA[$4014] 这是PPU 精灵使用的一个拷贝方式, 速度比传统的拷贝快得多... 每次拷贝写一个字节到$4014
引起 cpu地址ss00~ssFF字节的内容传送到PPU单元, ss为写入的字节, 一次拷贝消耗514cycles 相当于4~5根扫描线的延迟
NMI-IRQ中断的执行过程
清除当前中断信号
设置I标志屏蔽其他中断
以P, PC-HI, PC-LO的顺序入栈
跳转到相应中断向量的地址
先取得IRQ/NMI_VECTOR值的作PC低位
再取IRQ/NMI_VECTOR+1的值作PC高位
之后加上中断消耗的周期(cycles为7)
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-17 12:30:00 | 显示全部楼层
少讲了很多东西,源代码也少写了很多, 稍微补上点...
DMA[$4014] 这是PPU 精灵使用的一个拷贝方式, 速度比传统的拷贝快得多... 每次拷贝写一个字节到$4014
引起 cpu地址ss00~ssFF字节的内容传送到PPU单元, ss为写入的字节, 一次拷贝消耗514cycles 相当于4~5根扫描线的延迟
NMI-IRQ中断的执行过程
清除当前中断信号
设置I标志屏蔽其他中断
以P, PC-HI, PC-LO的顺序入栈
跳转到相应中断向量的地址
先取得IRQ/NMI_VECTOR值的作PC低位
再取IRQ/NMI_VECTOR+1的值作PC高位
之后加上中断消耗的周期(cycles为7)



#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-17 12:31:05 | 显示全部楼层
CPU部分差不多就是这样
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-17 12:38:57 | 显示全部楼层
====================== PPU ======================
这个有点复杂 , 要花费很多时间 ...
首先作为一个NES游戏编码人员, 控制NES图形除了扩展的设备外的最基本的手段即使用8个PPU端口



#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-17 13:00:33 | 显示全部楼层
首先预热一下, 减少一下阅读困难
PPU
主要有以下的原件
八个PPU IO
OAM[这个就是精灵,只有64个可以使用]
屏幕缓冲[传说中的作为背景出现の卷轴]
精灵调色板
背景调色板
4014 精灵DMA

#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

1

主题

16

帖子

142

积分

注册会员

Rank: 2

威望
0
经验
126
贡献
0
发表于 2016-3-17 17:13:34 | 显示全部楼层
本帖最后由 iyzsong 于 2016-3-21 18:51 编辑

虽然很想学,但是并不能看不懂,这根本不是基础入门吧

moecmks 发表于 2016-3-17 20:56
嘛, 不理解欢迎质疑. 欢迎提意见和高质量搬砖. 源代码是写的草率点, 反正帖子可以编辑.以后会慢慢变成熟肉 ...

知道啦  舔舔舔

以下记录我的尝试过程
----------------------------
CPU 那么复杂,一看就晕了,我还是先搞清楚模拟器所支持的 iNES 文件格式。
根据 http://wiki.nesdev.com/w/index.php/INES ,由游戏卡带 dump 成的 nes 文件包含:

1. 16 字节的文件头
2. trainer (一些卡带复制工具所造成历史遗留问题,不用管它了)
3. PRG ROM,程序代码,映射到 CPU 的 $8000-$FFFF
4. CHR ROM,图像数据,会映射到 PPU 吧...
5. PlayChoice INST-ROM (没听过,不管它)
6. PlayChoice PROM (不管它)

文件头主要指定了卡带的 mapper 编号(所采用的基板类型)、ROM 的大小,以及是否有电池和 RAM 。
iNES 的2.0 版本才会用到后 8 字节,这里我也无视了。

PRG RAM 用于游戏存档,卡带有电池的话就不会丢失,映射到 CPU 的 $6000-$7FFF,iNES 会默认都有 8K。
(UxROM没有?没有的话大概不会往这里写东西。)

CHR RAM (先不管...)

然后参照 higan ,先把 PRG ROM 和 CHR ROM 从 nes 文件提取出来:
https://github.com/iyzsong/my-prototypes/blob/master/_5/ines.cxx


然后是 CPU ,我先了解一下都有什么指令。
根据 650x Programming Manual
http://65xx.com/Products/Programming_and_Hardware-Manuals/
有 151 种不同的指令,包括 56 种操作 (mnemonic) 和 13 种寻址模式。

半个反汇编工具:
https://github.com/iyzsong/my-prototypes/blob/master/_5/asm.scm


这个可以自己造 PC.... http://wilsonminesco.com/6502primer/


噫,我好像看偏了
总之 CPU 的引脚是这个样子的:
http://wiki.nesdev.com/w/index.p ... _signal_description
  1.               .---\/---.
  2. AD1 <- |01  40| -- +5V
  3. AD2 <- |02  39| -> OUT0
  4. /RST-> |03  38| -> OUT1
  5. A00 <- |04  37| -> OUT2
  6. A01 <- |05  36| -> /OE1
  7. A02 <- |06  35| -> /OE2
  8. A03 <- |07  34| -> R/W
  9. A04 <- |08  33| <- /NMI
  10. A05 <- |09  32| <- /IRQ
  11. A06 <- |10  31| -> M2
  12. A07 <- |11  30| <- TST (usually GND)
  13. A08 <- |12  29| <- CLK
  14. A09 <- |13  28| <> D0
  15. A10 <- |14  27| <> D1
  16. A11 <- |15  26| <> D2
  17. A12 <- |16  25| <> D3
  18. A13 <- |17  24| <> D4
  19. A14 <- |18  23| <> D5
  20. A15 <- |19  22| <> D6
  21. GND -- |20  21| <> D7
  22.               `--------'
复制代码

D0-D7,8 位的数据总线,IO 都通过它进行。
(+5V 电源,GND 是接地)
输入信号:
  CLK,主时钟,21477272 Hz 的方波
  RST, NMI, IRQ, 触发中断
  TST, 测试用?(不用管它)
输出信号:
  AD1, AD2, 音频,脉冲、三角波和 DPCM 啥的
  A00-A15, 16位地址总线
  OUT0-OUT2,接到控制器(没看明白干啥的)
  OE1, OE2,接控制器,控制启用和禁用
  R/W,该时钟周期的读写状态,低(0?)时是写
  M2,反应CPU状态的时钟信号(还没具体理解)

CPU 的时钟频率是 21477272 / 12 = 1789773 Hz,一个周期内必然从数据总线进行一次读或着写。
不同指令(1-3字节)的执行需要的周期数不等(上面代码里的 cpu_cycles),执行的时候(exec_2a03):
# dispatch_cycles 没看明白,调用的初始值是什么?#
获取 opcode => FPRG = MMU_READ(PC); PC++;
解码 opcode => switch (FPRG) { ... }
执行 opcode => case ...,期间会调用 MMU_READ 读取操作数,调用 MMU_WRITE 来写内存(或其他设备)。
发现中断的话就把 PC 设为中断向量所存的地址
# 最后返回实际花了多少个周期?#

我还没想好怎么写 (先看着,不写了...)

传进去的实际是每次一行扫描线所耗费的CPU理想周期, 返回的实际消耗的周期数, 如当前分配115个周期此时你刚好走完114个周期

OK,之前不知道 scanline 是什么意思。

根据 http://wiki.nesdev.com/w/index.php/Clock_rate
NTSC 制式(我们玩的小霸王都是PAL的吧)的 NES:
PPU 的时钟频率,Fp = 21477272 / 4 = 5369318 Hz
屏幕刷新的频率, Fv = 60.0998 Hz
每帧的扫描线数,Ns = 262 (可见的只有240根,分辨率256x240)
每帧需要的PPU周期数,Np = Ns * 341 = 341 × 261 + 340.5 = 89341.5 (奇数帧会少用一个周期)
其中: Fv = Np * (1 / Fp)

# 所以 NTSC 制式的 PPU 扫描一行对应 CPU 的周期数就是 341/3 = 113.67 ?#

点评

bingo  发表于 2016-3-21 21:20
整个过程就是不断执行取指令解码,然后根据具体的消耗周期和扫描线补正周期偏移  发表于 2016-3-21 12:52
,当下一次执行了一条7周期的指令,额外消耗掉6个周期会被放置到下一帧的扫描中  发表于 2016-3-21 12:48
传进去的实际是每次一行扫描线所耗费的CPU理想周期, 返回的实际消耗的周期数, 如当前分配115个周期此时你刚好走完114个周期  发表于 2016-3-21 12:47
啊哈哈, 因为寄存器/相对寻址太简单了我直接没讲,你看我写的CPU伪代码里面实际没少写的.  发表于 2016-3-20 11:06
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-17 20:56:51 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-17 21:04 编辑
iyzsong 发表于 2016-3-17 17:13
虽然很想学,但是并不能看不懂,这根本不是基础入门吧

嘛, 不理解欢迎质疑. 欢迎提意见和高质量搬砖<不许骂人>. 源代码是写的草率点, 反正帖子可以编辑.以后会慢慢变成熟肉的.
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-17 20:59:42 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-19 12:30 编辑

================= PPU 显存 =================

  范围         尺寸   描述

$0000-$0FFF $1000   图案表0
$1000-$1FFF $1000   图案表1
$2000-$23BF $03C0   命名表0
$23C0-$23FF $0040   属性表0
$2400-$27BF $03C0   命名表1
$27C0-$27FF $0040   属性表1
$2800-$2BBF $03C0   命名表2
$2BC0-$2BFF $0040   属性表2
$2C00-$2FBF $03C0   命名表3
$2FC0-$2FFF $0040   属性表3

$3000-$3EFF $0F00   $2000-$2EFF的镜像
$3F00-$3F0F $0010   背景调色板
$3F10-$3F1F $0010   精灵调色板
$3F20-$3FFF $00E0   调色板的镜像
$4000-$7FFF $4000   前16K的镜像


这部分很乱,很绕.很绕,很乱.。。...。。。。.....
nes中一个像素只需要4bits,也即是半个字节即可表示
真实的NES只有2张属性表加上命名表[以下简称REAL_NT, REAL_NT0, REAL_NT1].为了满足以上四张属性表命名表
就把这两张真实的属性表命名表再镜像一次
主要有以下镜像模式
单屏幕镜像.四张命名表用的全是一块REAL_NT[还么见过用过此模式的游戏, 知道的请告诉我]
水平镜像.一些赛车游戏就是用的此映射模式  

垂直镜像.超级玛丽
四分屏.另外两张在扩展设备里面, 不讨论这些

伪代码描述:

val vram_bank_ptr_2000 // nt/at 0
val vram_bank_ptr_2400 // nt/at 1
val vram_bank_ptr_2800 // nt/at 2
val vram_bank_ptr_2C00 // nt/at 3

hori_mode:
    vram_bank_ptr_2000 = REAL_NT0
    vram_bank_ptr_2400 = REAL_NT0
    vram_bank_ptr_2800 = REAL_NT1
    vram_bank_ptr_2C00 = REAL_NT1   
vert_mode:
    vram_bank_ptr_2000 = REAL_NT0
    vram_bank_ptr_2400 = REAL_NT1
    vram_bank_ptr_2800 = REAL_NT0
    vram_bank_ptr_2C00 = REAL_NT1

首先灌输一下命名表/属性表の概念
一块命名表+属性表在屏幕中就相当于一块256*240的像素缓冲
两者功能有区别
命名表主要是用来寻址图案表的Tile
属性表主要是用来为Tile着色, 当然从图案表里得到的Tile本省就有两位颜色, 不过一般情况下这两位颜色是固定的
属性表可以动态改变. 比如如下效果, 在不改变调色板的情况下只要改变属性表字节就可以了




相同的Tile可以渐变成不同颜色, 省去了额外的图元空间

NES只有64种颜色, 只有四个色阶见图

[nes_color.png]
============== <背景[BG]/精灵[SP]调色板> =====================================================================================
我不愿把此叫做调色板, 更多的像是真实的nes64pal[NES真实由64种颜色组成的RGB数组,简称PAL_NES64(见上图)]的索引[简称SPPAL/BGPAL]
注意这是变量,是可以被修改替换de

uchar BGPAL[16]
uchar SPPAL[16]

加起来共32个
BGPAL[0]和SPPAL[0]其实是同一个, 作为透明色存在.
写其中一个会映射到另一个
以4为倍数的色板
BGPAL[4/8/12]
SPPAL[4/8/12]
都是默认不能使用的, 如果要使用直接用BGPAL[0]透明色索引代替

由于每个色板最大索引为16, 所以只需要4位来保存一个rgb像素的信息
寻址到一个真实的RGB的过程:

根据类型找出BGPAL/SPPAL , TYPEPAL<-BGPAL or SPPAL[这个暂且不管怎么找的]
获得TYPEPAL[tile-4bit]的值, 这个值是对应于PAL_NES64的索引, 根据这个索引就能获得真实的RGB pixel[这是可以直接被输出的A0R8G8B8像素]

RGB_PIXEL = PAL_NES64[TYPEPAL[tile-4bit]]

============== <图案表> ======================================================================================================
有两个, 每个4K. 一个给背景[BG]用, 一个给精灵[SP]用[具体的设置请看后面IO端口介绍]
每个4K图案表里面储存255个Tile的低两位颜色, Tile是NES图形的基本单元.
一个完整的,显示在NES屏幕[记住是nes屏幕不是PC屏幕]上的像素点实际上不是真正的RGB像素。而是以4bit索引值寻址的调色板索引
低两位由图案表提供, 高两位由属性表提供
一个Tile的尺寸是8*8[这是NES使用的最基本的马赛克图像]
这个4K怎么来的?
由256[单字节寻址到的最大索引] * (8 * 8) [tile size] / 4 [图案表中一个像素只要表示低两位就ok了, 一个Tile 8 * 8, 64个像素] = 4096[4K]
============== <命名表/属性表> ================================================================================================
这个基本就是主屏幕缓冲了, 写进去的是图案表里面Tile的ID[具体哪块图案表可以被PPUIO控制]
NES屏幕尺寸是256*240 完整情况下要铺满整个屏幕就需要32*30个Tile[一个Tile的尺寸8*8嘛~,对不?], 每个Tile的ID只要一个字节
32*30就是960个字节, 再看看上面的映射表, 一块命名表加上属性表刚好1024字节, 剩下的64个字节就给了属性表
64个字节的属性表跟960个字节的命名表
平均下来一个字节的属性表要被 4 * 4 个Tile共享 [2 * 2 个Tile 就是 16 * 16 个像素]
其中一个属性表字节映射如下图

attr_byte & 0x03 被映射到左上角 2*2 个 Tile 的高两位
attr_byte & 0x0C 被映射到右上角 2*2 个 Tile 的高两位
attr_byte & 0x30 被映射到左下角 2*2 个 Tile 的高两位
attr_byte & 0xC0 被映射到右下角 2*2 个 Tile 的高两位
一块命名表+属性表就能得到一块完整的256*240的4位NES色板索引
然后通过色板索引查表得到真正可用的显示在PC上的RGB像素


#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-18 14:32:12 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-18 23:38 编辑

=========================================================================           
    关于寻址图案表:
=========================================================================
    先算一下完整的一个Tile对应得图案表的字节数
    一个 8*8 Pixels, 由于图案表只需要低两位即可
    那么只需要16个字节
    各分8个字节作为低两位的高低位[记住是低两位的]
    前八个字节作为一个完整的Tile[8*8Pixels]所有NES像素低两位的低位
    后八个字节作为一个完整的Tile[8*8Pixels]所有NES像素低两位的高位
    前后各八个字节虽然映射位不同但是纵横映射方式是一样的
    横映射一个字节反序映射到像素, 最高位映射[该行]的第一个像素[即x0yn].最低位映射[该行]的最后一个像素[x7yn]
    纵映射就是正常的顺序映射, 第一个字节对应第一行, 第二个字节对应第二行, ... 至第八行     图中 A是最低位, H是最高位
   
    因为一个Tile需要16个字节, 所以. 完整的寻址一个Tile就是以Tile Id * 16 刚刚好, 不然可能会牵扯到不必要的字节

    取得一个完整的tile[8*8 pixels]的低两位的伪码

    ref get_tile_low2bit (val tile_id)
    {

      ref base_pattern
      ref tile_low2bit_line[8][8]
      val pattern_lo
      val pattern_hi

      // 1. 检测当前图案表落点[$0000 or $1000, 暂时不关心如何查找]
      base_pattern = get_pattern_block_address // $0000 or $1000[前4K或者后4K]

      // 2. 对应当前Tile ID 在图案表的初始地址的公式 = 当前图案表落点 + Tile ID * 16
      base_pattern = base_pattern + tile_id * 16

      // 3. 根据当前Tile ID 在图案表的初始地址在PPU显存查找相应字节, 暂时不关心如何查找
      pattern_lo = FETCH_VRAM(base_pattern); // 得到该Tile的低八个字节
      pattern_hi = FETCH_VRAM(base_pattern+8); // + 8 寻址该Tile的高八个字节
      // 取得Tile的第一行
        tile_low2bit_line[0][0] = (pattern_lo & 0x80) >> 7 | (pattern_hi & 0x80) >> 6;
        tile_low2bit_line[0][1] = (pattern_lo & 0x40) >> 6 | (pattern_hi & 0x40) >> 5;  
        tile_low2bit_line[0][2] = (pattern_lo & 0x20) >> 5 | (pattern_hi & 0x20) >> 4;
        tile_low2bit_line[0][3] = (pattern_lo & 0x10) >> 4 | (pattern_hi & 0x10) >> 3;
        tile_low2bit_line[0][4] = (pattern_lo & 0x08) >> 3 | (pattern_hi & 0x08) >> 2;      
        tile_low2bit_line[0][5] = (pattern_lo & 0x04) >> 2 | (pattern_hi & 0x04) >> 1;  
        tile_low2bit_line[0][6] = (pattern_lo & 0x02) >> 1 | (pattern_hi & 0x02) >> 0;  
        tile_low2bit_line[0][7] = (pattern_lo & 0x01) >> 0 | (pattern_hi & 0x01) << 1;
      pattern_lo = FETCH_VRAM(base_pattern+1);
      pattern_hi = FETCH_VRAM(base_pattern+8+1);
      // 取得Tile的第二行
        tile_low2bit_line[1][0] = (pattern_lo & 0x80) >> 7 | (pattern_hi & 0x80) >> 6;
        tile_low2bit_line[1][1] = (pattern_lo & 0x40) >> 6 | (pattern_hi & 0x40) >> 5;  
        tile_low2bit_line[1][2] = (pattern_lo & 0x20) >> 5 | (pattern_hi & 0x20) >> 4;
        tile_low2bit_line[1][3] = (pattern_lo & 0x10) >> 4 | (pattern_hi & 0x10) >> 3;
        tile_low2bit_line[1][4] = (pattern_lo & 0x08) >> 3 | (pattern_hi & 0x08) >> 2;      
        tile_low2bit_line[1][5] = (pattern_lo & 0x04) >> 2 | (pattern_hi & 0x04) >> 1;  
        tile_low2bit_line[1][6] = (pattern_lo & 0x02) >> 1 | (pattern_hi & 0x02) >> 0;  
        tile_low2bit_line[1][7] = (pattern_lo & 0x01) >> 0 | (pattern_hi & 0x01) << 1;            
      pattern_lo = FETCH_VRAM(base_pattern+2);
      pattern_hi = FETCH_VRAM(base_pattern+8+2);
      // 取得Tile的第三行
        tile_low2bit_line[2][0] = (pattern_lo & 0x80) >> 7 | (pattern_hi & 0x80) >> 6;
        tile_low2bit_line[2][1] = (pattern_lo & 0x40) >> 6 | (pattern_hi & 0x40) >> 5;  
        tile_low2bit_line[2][2] = (pattern_lo & 0x20) >> 5 | (pattern_hi & 0x20) >> 4;
        tile_low2bit_line[2][3] = (pattern_lo & 0x10) >> 4 | (pattern_hi & 0x10) >> 3;
        tile_low2bit_line[2][4] = (pattern_lo & 0x08) >> 3 | (pattern_hi & 0x08) >> 2;      
        tile_low2bit_line[2][5] = (pattern_lo & 0x04) >> 2 | (pattern_hi & 0x04) >> 1;  
        tile_low2bit_line[2][6] = (pattern_lo & 0x02) >> 1 | (pattern_hi & 0x02) >> 0;  
        tile_low2bit_line[2][7] = (pattern_lo & 0x01) >> 0 | (pattern_hi & 0x01) << 1;      
      ...
      pattern_lo = FETCH_VRAM(base_pattern+7);
      pattern_hi = FETCH_VRAM(base_pattern+8+7);
      // 取得Tile的第八行
        tile_low2bit_line[7][0] = (pattern_lo & 0x80) >> 7 | (pattern_hi & 0x80) >> 6;
        tile_low2bit_line[7][1] = (pattern_lo & 0x40) >> 6 | (pattern_hi & 0x40) >> 5;  
        tile_low2bit_line[7][2] = (pattern_lo & 0x20) >> 5 | (pattern_hi & 0x20) >> 4;
        tile_low2bit_line[7][3] = (pattern_lo & 0x10) >> 4 | (pattern_hi & 0x10) >> 3;
        tile_low2bit_line[7][4] = (pattern_lo & 0x08) >> 3 | (pattern_hi & 0x08) >> 2;      
        tile_low2bit_line[7][5] = (pattern_lo & 0x04) >> 2 | (pattern_hi & 0x04) >> 1;  
        tile_low2bit_line[7][6] = (pattern_lo & 0x02) >> 1 | (pattern_hi & 0x02) >> 0;  
        tile_low2bit_line[7][7] = (pattern_lo & 0x01) >> 0 | (pattern_hi & 0x01) << 1;            

    }

#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-18 20:09:14 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-18 20:17 编辑

@iyzsong
你那是NES2.0吧, ines1.0的第八个字节里面有个标志位 = 10b 就打开后面的八个字节启用NES2.0
后面主要就是把原始的mapper/PROM/CHRROM 从八位扩容到12位 [子mapper那个我也不清楚基本没人用...] 然后增加制式描述和PRAM/CRAM 数量描述, 还有专门用来描述磁碟机PPU的 ..
磁碟机需要额外的BIOS加载, PlayChoice-10这个大概也要吧, 没玩过PlayChoice-10~
CHRRAM就是CRAM这个我记得是跟PROM连一起的需要的时候从PROM里面拷贝到VRAM
UxROM没有PRAM

mapper另论[那部分我也不太熟], 在PROM, VROM装到CPU, PPU内存处之后跳到RESET向量处取PC, 然后该干嘛干嘛, [CPU取指解码通过对应APU/PPU IO与APU/PPU交互数据状态, PPU根据当前各个寄存器,时序,扫描线绘制渲染图元, APU也是一样, 输出各种波形[两个矩形, 一个噪声,三角]



文件头主要指定了卡带的 mapper 编号(所采用的基板类型)、ROM 的大小,以及是否有电池和 RAM 。
iNES 的2.0 版本才会用到后 8 字节,这里我也无视了。

PRG RAM 用于游戏存档,卡带有电池的话就不会丢失,映射到 CPU 的 $6000-$7FFF,iNES 会默认都有 8K。
(UxROM没有?没有的话大概不会往这里写东西。)

CHR RAM (先不管...)

然后参照 higan ,先把 PRG ROM 和 CHR ROM 从 nes 文件提取出来:
https://github.com/iyzsong/my-prototypes/blob/master/_5/ines.cxx


下一步就是 CPU 吧 o_o
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-18 23:13:13 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-19 12:23 编辑

=====================================================================
精灵OAM
=====================================================================
精灵简而言之, 就是一块256字节的数组
细分之, 4字节一组, 共64个精灵


每一个精灵有四个字节如下结构
struct oam
{
  BYTE Y_POS_IN_SCREEN
  BYTE TILE_ID
  BYTE ATTR [D7:垂直翻转致能 D6:水平翻转致能, D5:后台精灵致能 D0-D1:颜色的高两位精灵不使用属性表]
  BYTE X_POS_IN_SCREEN
}
struct oam oam_array[64];

基本的精灵是 8*8 Pixels 也就是一个Tile, 可以由改变相应的PPUIO寄存器变成8*16的,除了尺寸变化外寻址图案表的方式也会改变

除了第三个字节外都没好讲的看看名字就知道了, 有一点要注意的是精灵数据被延迟了一根扫描线.
也就是说当真正渲染时候精灵Y_POS_IN_SCREEN还要再加上1


你仔细注意下超级玛丽等游戏就会发现他的脚是踩在砖头里面的, 而不是站在上面
我不知道是否是Nintendo的恶趣味, 当然延迟的效果是显而易见的
=====================================================================
精灵OAM-属性
=====================================================================
D7:垂直翻转致能 D6:水平翻转致能

看名字就知道了, 可以一起用.翻转不改变原先的坐标

在8*16尺寸精灵下垂直翻转:每个Tile个各垂直翻转一次然后交换彼此的位置

这玩意是用来节省图元的, 比如向下面这样, 垂直翻转暂时没找到图片

D5:后台精灵致能
后台精灵是只能在当前背景为透明色的时候才能被画上去.前台则是当前精灵的像素不是透明色就可以画上去

D0-D1 因为不靠属性表,所以自己准备高两位颜色

=====================================================================
精灵OAM寻址图案表
=====================================================================   
8*8根据当前PPUIO决定是前4K还是后4K,之后跟命名表,属性表一样的方法
8*16使用另外一套寻址公式, 不靠PPUIO决定图案表落点
规则如下[摘自nesdev喂鸡]
/*  8*16 Sprite Tile ID
**  76543210
**  ||||||||
**  |||||||+- Bank ($0000 or $1000) of tiles
**  +++++++-- Tile number of top of sprite (0 to 254; bottom half gets the next tile)
*/
D0 == 0 ? $0000 : $1000 (见注释),得到落点后就把别管了,把D0当做是0吧[除了寻址图案表外此位不涉及具体的表内寻址刚刚脑残说错了,只要做是0就可以了,别去清除]
剩下的D1-D7就是具体的Tile ID, 双尺寸精灵的高Tile总是偶数, 下一个Tile就是低位的Tile(即Top_Tile_Addr+16)忘了说, 64个精灵0号最高, 63最低的优先级排列, 最低的先被画上去.然后如果跟其他精灵重叠那么低优先级的就被压在底下不显示

================零号精灵碰撞==============================================
即索引为0的精灵在所有的不为0的像素中如果刚好像素的背景颜色也同样不是透明颜色则设置这个标志
这个标志被用来设置分割屏幕

>>>>>>>>>>>>>> 预热结束 <<<<<<<<<<<<<<




#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-19 12:24:07 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-19 12:27 编辑

介绍PPUIO端口,

$2000<CTRL>

这个寄存器只能写

D0-D1:用来选择命名表页

%10 ($2800) %11 ($2C00)
%00 ($2000) %01 ($2400)

这个是配合画面滚动的, 设置了初始滚动的页面

D2:VRAM垂直写水平写, 留到后面

D3:设置精灵[SP]图案表的地址 1=$1000, 0=$0000
D4:设置背景[BG]图案表的地址 1=$1000, 0=$0000
D5:选择精灵尺寸 1=8*16 0=8*8
D6:主/副任务模式致能, 没在NES里使用, 不管
D7:VBLANK中断致能, 非常重要. 1=use NMI, 0=disable NMI. 关了这个即等于禁了NMI中断, 把屏幕给关了
在VBLANK之后会根据是否有此标志进行/忽略处理NMIRountine

$2001<MASK>只能写


D0:显示彩色/单色, 0=彩色, 1=单色这里的单色完全就是灰阶图.
实际输出的时候只要把当前调色板里实际的PAL_NES64索引与&0x30即可.因为灰阶总是在每个色阶的开始处[每16个颜色为一阶]
D1:背景切断掩码, 为0切断当前屏幕左边8*240的背景像素, [用透明色填充]
D2:精灵切断掩码, 为0切断当前屏幕左边8*240的精灵像素, 没有精灵落在此范围则忽略
D3:BG致能, 为0则关闭背景显示[整个背景用透明色填充]
D4:SP致能, 为0则关闭精灵显示
D5-D7:配合调色板用的.暂时不用在意,许多NES模拟器源代码中也没看到他们实现过

$2002<STATUS> 2002只能读
低五位不管, 暂时没用
D5:SP溢出致能 1=当前扫描线精灵大于8个
D6:SP命中致能 1=击中了0号精灵
D7:查询VBLANK状态 等于1表示PPU在VBLANK状态.当访问过后VBLANK标志会被清除,同时复位$2005,$2006[2005写水平,2006写高位,共享一个切换器]

$2003/$2004 这两个是专门的为精灵设置的
前者只能写, 设置当前精灵256字节数组的读写的索引,单字节
后者可读写, 每次读/写后索引加上1
伪码:
IO03:03_index = write_value;
IO04 read: return oam256byte[03_index++]
     write: oam256byte[03_index++] = write_value
这两个端口基本没人用,主流的都用DMA拷贝[$4014]
     
最后三个2005, 2006, 2007很重要

2005是设置当前滚动的x,y偏移的, 一共两个字节.写两次,只能写

首先明白一点无论怎么滚动实际只输出256*240的像素,X,Y方向的命名表切换如图
第一次切换器切换到低位写水平方向[初始选择的命名表(由$2000<CTRL>D0-D1控制)x 0~255的偏移].
第二次切换器切换到高位写垂直方向[初始选择的命名表y 0~239, 大于239的一般做负滚动]
超出一个命名表会自动切换到另一个

写入的字节可以解释为

HGFE DCBA
D3-D7[HGFED]:初始选择的命名表(由$2000<CTRL>D0-D1控制)初始x=0/y=0时偏移的Tile, 最大是31
D0-D2[CBA]:这是D3-D7寻址到的初始8*8Pixels的Tile的内部偏移, 偏移最大是7
此寄存器配合$2000<CTRL>D0-D1选择页表就可以实现卷轴的滚动.
初始滚动页面由$2000<CTRL>D0-D1给出
页内滚动偏移由$2005给出
根据具体的偏移寻址到对应得Tile,以及Tile内部的偏移,查找相应的属性表和图案表得出最终的4bit NES颜色索引查表输出像素
遇到页边界就做好切换页面的准备.

2006只能写, 你要知道到现在都没讲怎么把数据写到显存里面.2006和2007就是做这种事情
两者配合可以访问所有显存地址的内容.只能写,也是写两次
2006是VRAM地址寄存器, 写两次第一次写高6位, 第二次写低8位
好了,这就是2006的全部

2007既可以读,又可以写,读/写之后每次io2006_vram_address都会加上1或者32[取决于$2000<CTRL>的D2位]

就是简单的读写显存数据.

唯一要注意的是读的时候
07里面有个字节缓冲会保留上一次读的数据
当此次读的时候如果不是访问调色板项的话会直接返回这个字节缓冲
然后把这次读到的真实数据存在字节缓冲里面等待给下次调用赋值

所以你要读写显存2B08处的字节时
需要先设置2006显存访问地址为2B08
然后读取两次
伪码:

lda $2002 ; 复位2006,第一次写为高位
lda #$2B
sta $2006
lda #$08
sta $2006 ; 设置vram地址2B08完成
lda $2007 ; 开始读, 第一次读的只是一个无用的缓冲数据, 直接丢弃, 此时$2B08的数据被存储到字节缓冲区域
lda $2007 ; 第二次, 把上次的存储到字节缓冲区域的数据读入, 此刻读入的字节才是$2B08的数据
rts      


#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-20 11:00:00 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-20 11:25 编辑

给出部分伪代码:
  1. // == < ppu.c > ========================================================================================
  2. // 简易粗糙的实现一个简单的FC PPU, 有很多有待商榷的地方,暂时忽略, 只支持基本的mapper0.总之比较渣
  3. // 首先给出帖子的地址 http://cpp.ra2diy.com/thread-53-1-1.html

  4. #ifdef _MSC_VER
  5. #include "inttypes.h"
  6. #else
  7. #include <inttypes.h>
  8. #endif

  9. // 首先定义一连串基本的PPU变量
  10. struct rgb_s {
  11.   uint8_t blue;
  12.   uint8_t green;
  13.   uint8_t red;
  14.   uint8_t unused;
  15. };
  16. // 为了直观, 直接把各个IO的位提升为字节/字, bool类型直接用uint8_t代替
  17. struct io00_s {
  18.   uint16_t nametable_page; // D0-D1: 命名表页选择
  19.   uint8_t vram_inc32; // D2: 32 or 1
  20.   uint16_t sp_base_address; // D3:精灵使用的图案表页($0000 or $1000)
  21.   uint16_t bg_base_address; // D4:背景使用的图案表页($0000 or $1000)
  22.   uint8_t sprite_size16; // D5:8*8 or 8*16
  23.   // uint8_t master_mode; // D6:ppu主从模式
  24.   uint8_t power_on_nmi_at_vblank; // D7:允许发生NMI中断在Vblank期间
  25. };
  26. struct io01_s {
  27.   uint8_t use_gray_mode; // D0: 灰阶致能
  28.   uint8_t use_bg_clip; // D1: BG裁剪致能
  29.   uint8_t use_sp_clip; // D2: SP裁剪致能
  30.   uint8_t bg_visible; // D3: BG致能
  31.   uint8_t sp_visible; // D4: SP致能  
  32.   uint8_t emphasize_bit; // D5-D7: 暂时不讨论
  33. };
  34. struct io02_s {
  35.   uint8_t unused; // D0-D4: 暂时不讨论
  36.   uint8_t sprite_overflow; // D5: 精灵溢出检测
  37.   uint8_t sprite0_hit; // D6: 0号精灵是否击中
  38.   uint8_t in_vblank; // D7: ppu是否在vblank状态
  39. };
  40. struct io0304_s {
  41.   uint8_t oam_index03; // 03精灵[oam]初始索引
  42.   uint8_t oam_inc04; // 04精灵自增寄存器
  43. };
  44. struct io050607_s {
  45.   uint8_t toggle_0506; // 切换器 0506共享一个切换
  46.   uint8_t scroll_x; // 05内部水平滚动寄存器
  47.   uint8_t scroll_y; // 05内部垂直滚动寄存器
  48.   uint8_t vram_address_lo; // 06内部vram地址低位字节
  49.   uint8_t vram_address_hi; // 06内部vram地址高位字节
  50.   uint8_t io07_temp_buf; // 07内部的字节缓冲
  51. };
  52. struct sprite_s {
  53.   uint8_t y; // 精灵在屏幕的Y坐标, 别忘了有一根扫描线延迟
  54.   uint8_t tile_id; // 精灵的tile id 用来寻址在图案表的低两位
  55.   uint8_t attr; // 精灵的各种属性, 垂直/水平翻转.后备精灵致能.nes颜色高两位
  56.   uint8_t x; // 精灵在屏幕的X坐标
  57. };

  58. struct ppu_s {
  59.   struct io00_s _2000; // IO2000 CTRL
  60.   struct io01_s _2001; // IO2001 MASK
  61.   struct io02_s _2002; // IO2002 STATUS
  62.   struct io0304_s _2003_04; // IO2003/2004 OAMADDR/OAMDATA
  63.   struct io050607_s _2005_06_07; // IO2005/2006/2007 SCROLL/VRAMADDR/VRAMDATA
  64.   
  65.   uint8_t temp8; // 临时值
  66.   uint16_t temp16; // 临时值
  67.   uint32_t temp32; // 临时值
  68.   
  69.   uint8_t vram_2048b[2048]; // 真实的2K VRAM[两张命名表+属性表, 里面存的都是背景卷轴/字模数据]
  70.   uint8_t bg_pal[16]; // 背景调色板
  71.   uint8_t sp_pal[16]; // 精灵调色板
  72.   uint16_t cur_scanlines; // 当前PPU扫描线索引
  73.   
  74.   struct sprite_s oam_64[64]; // 64只精灵, 即256个字节
  75.   
  76.   uint8_t cur_line_attr[256]; // 当前行扫描线的像素属性, 不为0则表示不是透明像素共256个刚好表示当前扫描线行的256个像素
  77.   // 这个是用来做零号精灵的击中标志, 在一些不用精灵显示字模的游戏上用来分割屏幕
  78.   
  79.   uint8_t *vram_banks[12]; // 每个bank实际指的都是显存中的1K地址
  80.   // 这个是为了mapper而设计的动态指针切换, 在通常模式下每个bank指向1K数据
  81.   // bank[0]~bank[7] 给了两个4K的图案表.后面4K则给了4块被映射的命名表加上属性表
  82.   // cpu的具体实现也是bank来动态切换PRG, 指针数组相当巧妙的运用 [当然cpu我没写全,后面会补上或者推翻重写]
  83.   // bank号  释义                    显存地址:
  84.   // 0-3     = 前4K图案表               $0000-$0FFF
  85.   // 4-7     = 后4K图案表               $1000-$1FFF
  86.   // 8       = 第一块命名表和属性表      $2000-$23FF
  87.   // 9       = 第二块命名表和属性表      $2400-$27FF
  88.   // A       = 第三块命名表和属性表     $2800-$2BFF
  89.   // B       = 第四块命名表和属性表     $2C00-$2FFF
  90. };
  91. // 这个是真实64个NES颜色对应得RGB像素BG/SP调色板得到的索引来这查次表就可以得到真实的像素输出了
  92. static const struct rgb_s RGB_PAL[64] =
  93. {   
  94.   {0x7F,0x7F,0x7F,0x00},{0xB0,0x00,0x20,0x00},{0xB8,0x00,0x28,0x00},{0xA0,0x10,0x60,0x00},
  95.   {0x78,0x20,0x98,0x00},{0x30,0x10,0xB0,0x00},{0x00,0x30,0xA0,0x00},{0x00,0x40,0x78,0x00},
  96.   {0x00,0x58,0x48,0x00},{0x00,0x68,0x38,0x00},{0x00,0x6C,0x38,0x00},{0x40,0x60,0x30,0x00},
  97.   {0x80,0x50,0x30,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},
  98.   {0xBC,0xBC,0xBC,0x00},{0xF8,0x60,0x40,0x00},{0xFF,0x40,0x40,0x00},{0xF0,0x40,0x90,0x00},
  99.   {0xC0,0x40,0xD8,0x00},{0x60,0x40,0xD8,0x00},{0x00,0x50,0xE0,0x00},{0x00,0x70,0xC0,0x00},
  100.   {0x00,0x88,0x88,0x00},{0x00,0xA0,0x50,0x00},{0x10,0xA8,0x48,0x00},{0x68,0xA0,0x48,0x00},
  101.   {0xC0,0x90,0x40,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},
  102.   {0xFF,0xFF,0xFF,0x00},{0xFF,0xA0,0x60,0x00},{0xFF,0x80,0x50,0x00},{0xFF,0x70,0xA0,0x00},
  103.   {0xFF,0x60,0xF0,0x00},{0xB0,0x60,0xFF,0x00},{0x30,0x78,0xFF,0x00},{0x00,0xA0,0xFF,0x00},
  104.   {0x20,0xD0,0xE8,0x00},{0x00,0xE8,0x98,0x00},{0x40,0xF0,0x70,0x00},{0x90,0xE0,0x70,0x00},
  105.   {0xE0,0xD0,0x60,0x00},{0x60,0x60,0x60,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},
  106.   {0xFF,0xFF,0xFF,0x00},{0xFF,0xD0,0x90,0x00},{0xFF,0xB8,0xA0,0x00},{0xFF,0xB0,0xC0,0x00},
  107.   {0xFF,0xB0,0xE0,0x00},{0xE8,0xB8,0xFF,0x00},{0xB8,0xC8,0xFF,0x00},{0xA0,0xD8,0xFF,0x00},
  108.   {0x90,0xF0,0xFF,0x00},{0x80,0xF0,0xC8,0x00},{0xA0,0xF0,0xA0,0x00},{0xC8,0xFF,0xA0,0x00},
  109.   {0xF0,0xFF,0xA0,0x00},{0xA0,0xA0,0xA0,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00}
  110. };

  111. // 为了省事, 直接弄成静态私有变量
  112. static struct ppu_s g_ppu;
  113. // PPU做有用功, 最终是输出一块 256*240 的A0R8G8B8[位深为32,废弃A位]的像素块
  114. // 这块像素块可以被不同的图形API输出[GL/GDI/DDRAW等]
  115. static uint32_t rgb_pixels_out[256*240*4];

  116. // 首先来写最简单的函数-获取此块像素的引用
  117. /**
  118. * get_ppu_out_pixels:
  119. * @info: 获取由PPU4bit颜色索引查表得到的可被通用的A0R8G8B8的像素块
  120. *
  121. * Returns: 此块像素的引用
  122. */
  123. void *get_ppu_out_pixels (void)
  124. {
  125.   return &rgb_pixels_out[0];
  126. }
  127. /**
  128. * set_nametable_mirror:
  129. * @info: 设置镜像类型, 接受一个uint8_t参数, 0表示垂直镜像, 1表示水平镜像
  130. *
  131. * Returns: 无
  132. */
  133. void set_nametable_mirror (uint8_t is_vert_mirror)
  134. {
  135.   if (!is_vert_mirror)
  136.   {
  137.     g_ppu.vram_banks[8] = &g_ppu.vram_2048b[0]; // $2000->first 1K vram
  138.     g_ppu.vram_banks[9] = &g_ppu.vram_2048b[0]; // $2400->first 1K vram
  139.     g_ppu.vram_banks[10]= &g_ppu.vram_2048b[1024]; // $2800->second 1K vram
  140.     g_ppu.vram_banks[11]= &g_ppu.vram_2048b[1024]; // $2C00->second 1K vram
  141.   }
  142.   else
  143.   {
  144.     g_ppu.vram_banks[8] = &g_ppu.vram_2048b[0]; // $2000->first 1K vram
  145.     g_ppu.vram_banks[9] = &g_ppu.vram_2048b[1024]; // $2400->second 1K vram
  146.     g_ppu.vram_banks[10]= &g_ppu.vram_2048b[0]; // $2800->first 1K vram
  147.     g_ppu.vram_banks[11]= &g_ppu.vram_2048b[1024]; // $2C00->second 1K vram
  148.   }
  149. }
  150. /**
  151. * get_vram_byte:
  152. * @info: 读指定显存, 接受一个显存地址[uint16_t]
  153. *
  154. * Returns: 显存的值
  155. */
  156. uint8_t read_vram_byte (uint16_t addr)
  157. {
  158.   // 这个实际不需要写成函数但是为了可读性...
  159.   // addr >> 10 就是除以1024获取当前地址所在的bank索引
  160.   // addr & 0x3FF就是取得当前索引bank里面的1K范围的字节
  161.   return g_ppu.vram_banks[addr >> 10][addr & 0x03FF];
  162. }
  163. /**
  164. * write_vram_byte:
  165. * @info: 写指定显存, 参数1为预备写入的值[uint8_t], 参数2为一个显存地址[uint16_t]
  166. *
  167. * Returns: NONE
  168. */
  169. void write_vram_byte (uint8_t value, uint16_t addr)
  170. {
  171.   // 同上这次换成写
  172.   g_ppu.vram_banks[addr >> 10][addr & 0x03FF] = value;
  173. }
复制代码
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-20 11:00:27 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-20 11:20 编辑
  1. /**
  2. * read_ppu_io:
  3. * @info: 读指定PPU IO寄存器, 接受一个IO地址[uint16_t]
  4. *
  5. * Returns: NONE
  6. */
  7. uint8_t read_ppu_io (uint16_t addr)
  8. {
  9.   // 好了我们要开始读PPU寄存器了 ... 你仔细看看之前窝说的..就会发现只有2002, 2004, 2007可以读取
  10.   // 由于分支较少, 没必要用switch case
  11.   addr &= 0x07; // io端口是分布在CPU地址上面的, 连着一坨镜像, [与7是为了除去镜像骚扰见帖子3L]
  12.   
  13.   if (addr == 2) // $2002 STATUS 读此寄存器会清楚VBLANK标志/复位0506切换[见PPUIO端口那楼]
  14.   {
  15.     g_ppu.temp8 = g_ppu._2002.unused & 0x1F; // D0-D4
  16.     g_ppu.temp8|= g_ppu._2002.sprite_overflow ? 0x20 : 0x00; // D5 SP溢出致能
  17.     g_ppu.temp8|= g_ppu._2002.sprite0_hit     ? 0x40 : 0x00; // D6 SP击中致能
  18.     g_ppu.temp8|= g_ppu._2002.in_vblank       ? 0x80 : 0x00; // D7 VBLANK状态检测
  19.    
  20.     g_ppu._2002.in_vblank &= 0x7F; // 清除VLBANK状态
  21.     g_ppu._2005_06_07.toggle_0506 = 0; // 复位0506切换器
  22.   }
  23.   else if (addr == 4) // $2004 OAMDATA 读OAM数据, 读完自增
  24.   {
  25.     g_ppu.temp8 = ((uint8_t *)&g_ppu.oam_64[0].y)[g_ppu._2003_04.oam_index03 + g_ppu._2003_04.oam_inc04++];
  26.   }
  27.   else if (addr == 7) // $2007 VRAMDATA 读显存, 显存的地址存在$2006里面
  28.   {
  29.     // 先读取之前的07字节缓冲
  30.     g_ppu.temp8 = g_ppu._2005_06_07.io07_temp_buf;
  31.     // 然后把2006设置的vram地址高地位合并
  32.     g_ppu.temp32 = g_ppu._2005_06_07.vram_address_lo;
  33.     g_ppu.temp32|= (((uint32_t)g_ppu._2005_06_07.vram_address_hi) << 8);
  34.     // 加上当前vram的自增类型[32 or 1]得到新的vram地址
  35.     g_ppu.temp16 = g_ppu.temp32 & 0x3FFF; // 限制镜像,得到的temp16是没自增前的老的vram_addr用来寻址显存
  36.     g_ppu.temp32+= g_ppu._2000.vram_inc32 ? 32 : 1;
  37.     // temp32得到的新的vram的地址再写回$2006的高地位
  38.     g_ppu._2005_06_07.vram_address_lo = (uint8_t)(g_ppu.temp32 & 0x00FF);
  39.     g_ppu._2005_06_07.vram_address_hi = (uint8_t)(g_ppu.temp32 & 0x3F00); // 限制镜像
  40.     // 开始分析vram地址[temp16], 此时的temp16由于扫除了镜像的限制落点肯定在0x0000~0x3FFF
  41.     if (g_ppu.temp16 > 0x2FFF)
  42.     {
  43.       // 此分支有两种可能性, 一是落点在$3000~$3EFF, 这段是$2000-$2EFF的镜像
  44.       //                     二是落点在$3F00~$3FFF. 这段便是调色板和调色板镜像的地址
  45.       // 首先测试是否是调色板区域, 如果是则直接返回调色板数据, 不管io07缓冲
  46.       if (g_ppu.temp16 > 0x3EFF)
  47.       {
  48.         // 此区域则是调色板以及其镜像的区域
  49.         // 色板镜像映射模式 [QONM LKJI HGFE DCBA > 3F1F]
  50.         // address QONM LKJI HGFE DCBA [A-low, Q-high]
  51.         // 当E为0时就是BG色板/及其镜像的范围, 否则则是SP
  52.         
  53.         if (0x0000 == (g_ppu.temp16 & 0x0010)) // 说明是BG色板
  54.         {
  55.           return g_ppu.bg_pal[g_ppu.temp16 & 0x0F]; // &15 扫除镜像干扰
  56.         }
  57.         else // 否则是SP色板
  58.         {
  59.           return g_ppu.sp_pal[g_ppu.temp16 & 0x0F]; // &15 扫除镜像干扰
  60.         }
  61.       }
  62.       else
  63.       {
  64.         // $3000~$3EFF, 这段是$2000-$2EFF的镜像
  65.         g_ppu.temp16 -= 0x1000;
  66.       }
  67.     }
  68.     // 好了到此处就不可能是调色板的数据了, 更新当前的io07缓冲
  69.     g_ppu._2005_06_07.io07_temp_buf = read_vram_byte (g_ppu.temp16);
  70.   }
  71.   else
  72.   {
  73.     ; // 错误处理, 读了不该读的寄存器
  74.   }
  75.   return g_ppu.temp8;
  76. }
  77. /**
  78. * write_ppu_io:
  79. * @info: 写指定PPUIO寄存器, 参数1为预备写入的值[uint8_t], 参数2为一个地址[uint16_t]
  80. *
  81. * Returns: NONE
  82. */
  83. void write_ppu_io (uint8_t value, uint16_t addr)
  84. {
  85.   addr &= 0x07; // io端口是分布在CPU地址上面的, 连着一坨镜像, [与7是为了除去镜像骚扰见帖子3L]
  86.   
  87.   switch (addr)
  88.   {
  89.   case 0x00: // PPU CTRL
  90.     g_ppu._2000.nametable_page  =((value & 0x03) << 10) + 0x2000; // D0-D1: 命名表页选择
  91.     g_ppu._2000.vram_inc32      = (value & 0x04) ? 32 : 1; // D2: 32 or 1
  92.     g_ppu._2000.sp_base_address = (value & 0x08) ? 0x1000: 0x0000; // D3:精灵使用的图案表页($0000 or $1000)
  93.     g_ppu._2000.bg_base_address = (value & 0x10) ? 0x1000: 0x0000; // D4:背景使用的图案表页($0000 or $1000)   
  94.     g_ppu._2000.sprite_size16   = (value & 0x20) ? 1 : 0; // D5:sprite 8*8 or 8*16
  95.     g_ppu._2000.power_on_nmi_at_vblank = value & 0x80 ? 1 : 0; // D7:允许发生NMI中断在Vblank期间   
  96.   break;
  97.   
  98.   case 0x01: // PPU MASK
  99.     g_ppu._2001.use_gray_mode = (value & 0x01) ? 1 : 0; // D0: 灰阶致能
  100.     g_ppu._2001.use_bg_clip   = (value & 0x02) ? 1 : 0; // D1: BG裁剪致能
  101.     g_ppu._2001.use_sp_clip   = (value & 0x04) ? 1 : 0; // D2: SP裁剪致能
  102.     g_ppu._2001.bg_visible    = (value & 0x08) ? 1 : 0; // D3: BG致能
  103.     g_ppu._2001.sp_visible    = (value & 0x10) ? 1 : 0; // D4: SP致能  
  104.     g_ppu._2001.emphasize_bit = (value & 0xE0); // D5-D7: 暂时不讨论
  105.   break;
  106.   
  107.   case 0x03: // OAMADDR
  108.     g_ppu._2003_04.oam_index03 = value;
  109.         g_ppu._2003_04.oam_inc04 = 0;
  110.   break;
  111.   
  112.   case 0x04: // OAMDATA
  113.    ((uint8_t *)&g_ppu.oam_64[0].y)[g_ppu._2003_04.oam_index03 + g_ppu._2003_04.oam_inc04++] = value;
  114.   break;
  115.   
  116.   case 0x05: // SCROLL
  117.   
  118.     if (g_ppu._2005_06_07.toggle_0506 == 0) // 第一次写, 水平偏移[X]
  119.         g_ppu._2005_06_07.scroll_x = value;
  120.     else // 第二次写, 垂直偏移[Y]
  121.         g_ppu._2005_06_07.scroll_y = value;     
  122.    
  123.     // 切换 ...
  124.     g_ppu._2005_06_07.toggle_0506 = !g_ppu._2005_06_07.toggle_0506;
  125.    
  126.   break;
  127.   
  128.   case 0x06: // VRAMADDR
  129.     if (g_ppu._2005_06_07.toggle_0506 == 0) // 第一次写, 高6位
  130.         g_ppu._2005_06_07.vram_address_hi = value & 0x3F;
  131.     else // 第二次写, 低8位
  132.         g_ppu._2005_06_07.vram_address_lo = value & 0xFF;      
  133.    
  134.     // 切换 ...
  135.     g_ppu._2005_06_07.toggle_0506 = !g_ppu._2005_06_07.toggle_0506;  
  136.   break;
  137.   
  138.   case 0x07: // VRAMDATA
  139.     // 把2006设置的vram地址高地位合并
  140.     g_ppu.temp32 = g_ppu._2005_06_07.vram_address_lo;
  141.     g_ppu.temp32|= (((uint32_t)g_ppu._2005_06_07.vram_address_hi) << 8);
  142.     // 加上当前vram的自增类型[32 or 1]得到新的vram地址
  143.     g_ppu.temp16 = g_ppu.temp32 & 0x3FFF; // 限制镜像,得到的temp16是没自增前的老的vram_addr用来寻址显存
  144.     g_ppu.temp32+= g_ppu._2000.vram_inc32 ? 32 : 1;
  145.     // temp32得到的新的vram的地址再写回$2006的高地位
  146.     g_ppu._2005_06_07.vram_address_lo = (uint8_t)(g_ppu.temp32 & 0x00FF);
  147.     g_ppu._2005_06_07.vram_address_hi = (uint8_t)(g_ppu.temp32 & 0x3F00); // 限制镜像
  148.     // 开始分析vram地址[temp16], 此时的temp16由于扫除了镜像的限制落点肯定在0x0000~0x3FFF
  149.     if (g_ppu.temp16 > 0x2FFF)
  150.     {
  151.       // 此分支有两种可能性, 一是落点在$3000~$3EFF, 这段是$2000-$2EFF的镜像
  152.       //                     二是落点在$3F00~$3FFF. 这段便是调色板和调色板镜像的地址
  153.       if (g_ppu.temp16 > 0x3EFF)
  154.       {
  155.         // 此区域则是调色板以及其镜像的区域
  156.         // 色板镜像映射模式 [QONM LKJI HGFE DCBA > 3F1F]
  157.         // address QONM LKJI HGFE DCBA [A-low, Q-high]
  158.         // 当E为0时就是BG色板/及其镜像的范围, 否则则是SP
  159.         
  160.         if (!(g_ppu.temp16 & 0x000F)) // SP或BG0号色板/镜像[BG/SP共享一块透明色调色板]
  161.         {
  162.           g_ppu.bg_pal[0] = g_ppu.sp_pal[0] = value & 63; // 限制调色板界限64
  163.         }
  164.         else if (0x0000 == (g_ppu.temp16 & 0x0010)) // 说明是BG色板
  165.         {
  166.           g_ppu.bg_pal[g_ppu.temp16 & 0x0F] = value & 63; // &15 扫除镜像干扰
  167.         }
  168.         else // 否则是SP色板
  169.         {
  170.           g_ppu.sp_pal[g_ppu.temp16 & 0x0F] = value & 63; // &15 扫除镜像干扰
  171.         }
  172.         
  173.         break;
  174.       }
  175.       else
  176.       {
  177.         // $3000~$3EFF, 这段是$2000-$2EFF的镜像
  178.         g_ppu.temp16 -= 0x1000;
  179.       }
  180.     }
  181.     // 好了到此处就不可能是调色板的数据了
  182.     write_vram_byte (value, g_ppu.temp16);
  183.     break;
  184.    
  185.       default:
  186.   // 错误处理, 写了不该写的
  187.     break;
  188.   }
  189. }
复制代码

#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

0

主题

10

帖子

45

积分

新手上路

Rank: 1

威望
1
经验
32
贡献
1
发表于 2016-3-20 13:55:38 | 显示全部楼层
支持!
回复

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-21 21:44:21 | 显示全部楼层
PPU/CPU同步
首先, 给出一部分PPU/CPU的信息

基频(Base clock)21477270.0Hz
CPU主频(Cpu clock)1789772.5Hz
总扫描线数(Total scanlines)262
扫描线总周期(Scanline total cycles)1364(15.75KHz)
水平扫描周期(H-Draw cycles)1024
水平空白周期(H-Blank cycles)340
帧率(Frame rate)60(59.94Hz)

PPU主频没记错的是基频/4
而CPU主频又是PPU频率的三分之一

首先可以算出PPU频率是5369317.5(基频21477270/4)
这个PPU频率又要分给60帧/平均每帧分到89488.625
又要分给262根扫描线,那么每根扫描线就是341.55963740458
由于CPU只是PPU的1/3算出来是113.85321246819

所以PPU每扫描一根扫描线, CPU就运行113.85321246819 ticks
[这个小数点跟别人算的后面有点出入, 一般不精确模拟直接取113好了]

PPU扫描线前0~239根扫描线是可视扫描线,产生真实的像素.[这里以0位初始点讨论,不是1]
240~261中都不显示实际像素.
在241有个非常重要的刷新标志-VBLANK[垂直消隐]
在这段时间写像素是绝对不会出现撕裂情况的,电子枪还没回到原点
此时一个重要的中断,NMI会被调用[如果没禁止NMI]
直到最后一根扫描线261来临.VBLANK标志都会被设置.[当然此时读2002还是会被清除]
到了261清除VBLANK和零号精灵击中标志
至此, PPU的一帧结束.




#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-21 22:37:51 | 显示全部楼层
  1. /**
  2. * render_bg:
  3. * @info: 根据当前扫描线行渲染一根扫描线的BG
  4. *
  5. * Returns: NONE
  6. */

  7. void render_bg (void)
  8. {
  9.   uint8_t suffuse_line_attr[256+8];
  10.   static uint32_t suffuse_rgb_slot[(256+8)*4];
  11.   uint16_t poll_x;
  12.   uint16_t poll_y;
  13.   uint16_t pattern_addr;
  14.   uint16_t pattern_base_addr_mix; // 初始BG图案表基址 + Tile内Y偏移, 在一行扫描中这两个基本不会变
  15.   uint8_t pattern_hi;
  16.   uint8_t pattern_lo;
  17.   uintptr_t scan_count; // 总是每次扫描33个tile
  18.   uint8_t attr_bits; // 属性表里高两位的NES颜色
  19.   uint16_t nt_addr; // 命名表地址
  20.   uint16_t attr_addr;
  21.   uint8_t nes_4bits_color[8];
  22.   uintptr_t mini_offset_x; // Tile内X偏移
  23.   uint16_t tile_id; // 8bit, 写成16位省的后面强制转换
  24.   
  25.   // 计算初始化命名表,属性表地址.以及各种数据
  26.   
  27.   poll_x    = g_ppu._2005_06_07.scroll_x >> 3; // 取得当前页面命名表滚动初始X滚动偏移
  28.   poll_y    = g_ppu._2005_06_07.scroll_y >> 3; // 取得当前页面命名表滚动初始Y滚动偏移
  29.   nt_addr   = g_ppu._2000.nametable_page + poll_x + (poll_y << 5);
  30.   attr_addr = 0x23C0 | (nt_addr & 0x0C00) | (poll_x >> 2) | ((poll_y & 0xFC) << 1);
  31.   mini_offset_x         = g_ppu._2005_06_07.scroll_x & 0x07;
  32.   pattern_base_addr_mix = g_ppu._2000.nametable_page + (g_ppu._2005_06_07.scroll_y & 0x07);
  33.   g_ppu.offset_x_attr_line = mini_offset_x;
  34.   
  35.   //
  36.   // 根据当前初始属性表地址获得2位颜色
  37.   // 属性表一个字节, 分给一个4*4Tile的各个2*2Tile
  38.   //
  39.   
  40.   if (!(poll_y & 2))
  41.   {
  42.     if (!(poll_x & 2))
  43.     {
  44.       attr_bits = (read_vram_byte (attr_addr) & 0x03) << 2; // y0x0 左上, 分配D0-D1
  45.     }
  46.     else
  47.     {
  48.       attr_bits = (read_vram_byte (attr_addr) & 0x0C) << 0; // y0x1 右上, 分配D2-D3
  49.     }
  50.   }
  51.   else
  52.   {
  53.     if (!(poll_x & 2))
  54.     {
  55.       attr_bits = (read_vram_byte (attr_addr) & 0x30) >> 2; // y1x0 左下, 分配D4-D5
  56.     }
  57.     else
  58.     {
  59.       attr_bits = (read_vram_byte (attr_addr) & 0xC0) >> 4; // y1x1 右下, 分配D6-D7
  60.     }
  61.   }
  62.   //
  63.   // 主循环 扫描33个Tile的的像素[(256+8)*1], 模拟电子枪的过程   
  64.   // 仔细想一下.在Tile内X偏移不为0de时候.
  65.   // 首/尾扫描的Tile都是残缺, 不完整的
  66.   // 具体来说,是这样. 请看好了
  67.   // 0 ------------------- [1.....31] --------------------- 32
  68.   // 在0[也就是第一个Tile是扫描了8-Tile内X偏移的像素], 在尾部是扫描了Tile内X偏移的像素
  69.   // 中间完整的31个8*8的Tile, 加上首尾两个残缺的Tile的才组成了256*1的一行BG扫描线
  70.   // 所以在残缺模式下, 你要测试首尾的落点是否在输出区间内
  71.   // 这个很麻烦, 为了照顾这种首尾偏移不对齐的情况...
  72.   // 最简单就是你需要每次扫描33个Tile然后根据得到了具体的33个缓存的RGB像素槽[256+8个像素]
  73.   // 然后根据偏移截断首尾部分多余的像素放入真正的像素缓冲...[这个偏移是指具体的Tile内X偏移]
  74.   // 方法有点儿2, 但看起来相对容易
复制代码
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-21 22:38:25 | 显示全部楼层

  1.   
  2.   for (scan_count = 0; scan_count != 33; scan_count++)
  3.   {
  4.     // 首先根据当前的命名表地址查找该地址的里面的Tile Id
  5.     tile_id = (uint16_t)read_vram_byte (nt_addr);

  6.     // 根据取得Tile Id获得在图案表的地址加上对应得内部Y tile_id偏移
  7.     // 获取当前要扫描Tile的具体扫描地址以及Tile内Y偏移
  8.         
  9.     pattern_addr = read_vram_byte (pattern_base_addr_mix + tile_id * 16);

  10.     // 获取当前Tile内部 偏移为init_y_in_tile的八个像素NES颜色的低两位
  11.     pattern_lo = read_vram_byte (pattern_addr);
  12.     pattern_hi = read_vram_byte (pattern_addr + 8);   
  13.    
  14.     // 分解当前扫描线图案表像素, 得到低2位NES像素
  15.     nes_4bits_color[0] = ((pattern_lo & 0x80) >> 7) | ((pattern_hi & 0x80) >> 6);
  16.     nes_4bits_color[1] = ((pattern_lo & 0x40) >> 6) | ((pattern_hi & 0x40) >> 5);   
  17.     nes_4bits_color[2] = ((pattern_lo & 0x20) >> 5) | ((pattern_hi & 0x20) >> 4);
  18.     nes_4bits_color[3] = ((pattern_lo & 0x10) >> 4) | ((pattern_hi & 0x10) >> 3);
  19.     nes_4bits_color[4] = ((pattern_lo & 0x08) >> 3) | ((pattern_hi & 0x08) >> 2);      
  20.     nes_4bits_color[5] = ((pattern_lo & 0x04) >> 2) | ((pattern_hi & 0x04) >> 1);   
  21.     nes_4bits_color[6] = ((pattern_lo & 0x02) >> 1) | ((pattern_hi & 0x02) >> 0);   
  22.     nes_4bits_color[7] = ((pattern_lo & 0x01) >> 0) | ((pattern_hi & 0x01) << 1);                                                                                                                                                                                                      
  23.   
  24.     // 标记像素1非透明,0透明, 这个给设置0号精灵碰撞使用, 非常重要.没得话某些游戏会卡死在NMI
  25.     // 低两位为0, 那么直接输出透明色, 因为当低两位为0. 那么调色板肯定为4的倍数,此时应该用透明色, 透明标志也被设置
  26.     // 得到的标记放到临时的标记缓冲
  27.    
  28.     suffuse_line_attr[scan_count * 8 + 0] = nes_4bits_color[0] ? 1 : 0;
  29.     suffuse_line_attr[scan_count * 8 + 1] = nes_4bits_color[1] ? 1 : 0;
  30.     suffuse_line_attr[scan_count * 8 + 2] = nes_4bits_color[2] ? 1 : 0;
  31.     suffuse_line_attr[scan_count * 8 + 3] = nes_4bits_color[3] ? 1 : 0;
  32.     suffuse_line_attr[scan_count * 8 + 4] = nes_4bits_color[4] ? 1 : 0;
  33.     suffuse_line_attr[scan_count * 8 + 5] = nes_4bits_color[5] ? 1 : 0;
  34.     suffuse_line_attr[scan_count * 8 + 6] = nes_4bits_color[6] ? 1 : 0;
  35.     suffuse_line_attr[scan_count * 8 + 7] = nes_4bits_color[7] ? 1 : 0;   
  36.   
  37.     // 是透明色的话啥都不做[或0啥也不做], 否则合并属性表高两位颜色
  38.     // 此时完整4位NES颜色索引寻址完毕
  39.     nes_4bits_color[0] |= (nes_4bits_color[0] ? attr_bits : 0);
  40.     nes_4bits_color[1] |= (nes_4bits_color[1] ? attr_bits : 0);
  41.     nes_4bits_color[2] |= (nes_4bits_color[2] ? attr_bits : 0);
  42.     nes_4bits_color[3] |= (nes_4bits_color[3] ? attr_bits : 0);
  43.     nes_4bits_color[4] |= (nes_4bits_color[4] ? attr_bits : 0);
  44.     nes_4bits_color[5] |= (nes_4bits_color[5] ? attr_bits : 0);
  45.     nes_4bits_color[6] |= (nes_4bits_color[6] ? attr_bits : 0);
  46.     nes_4bits_color[7] |= (nes_4bits_color[7] ? attr_bits : 0);  
  47.    
  48.     // 首先测试是否开启灰阶致能, 开启了就&0x30,否则啥也不做[&0x3F是为了限制输出范围在64内]
  49.     // 然后查表得出实际像素放入临时RGB缓冲
  50.     suffuse_rgb_slot[scan_count * 8 + 0] = ((uint32_t *)&RGB_PAL[g_ppu._2001.use_gray_mode ? (g_ppu.bg_pal[nes_4bits_color[0]] & 0x30) : (g_ppu.bg_pal[nes_4bits_color[0]] & 0x3F)].blue)[0];
  51.     suffuse_rgb_slot[scan_count * 8 + 1] = ((uint32_t *)&RGB_PAL[g_ppu._2001.use_gray_mode ? (g_ppu.bg_pal[nes_4bits_color[1]] & 0x30) : (g_ppu.bg_pal[nes_4bits_color[1]] & 0x3F)].blue)[0];
  52.     suffuse_rgb_slot[scan_count * 8 + 2] = ((uint32_t *)&RGB_PAL[g_ppu._2001.use_gray_mode ? (g_ppu.bg_pal[nes_4bits_color[2]] & 0x30) : (g_ppu.bg_pal[nes_4bits_color[2]] & 0x3F)].blue)[0];
  53.     suffuse_rgb_slot[scan_count * 8 + 3] = ((uint32_t *)&RGB_PAL[g_ppu._2001.use_gray_mode ? (g_ppu.bg_pal[nes_4bits_color[3]] & 0x30) : (g_ppu.bg_pal[nes_4bits_color[3]] & 0x3F)].blue)[0];
  54.     suffuse_rgb_slot[scan_count * 8 + 4] = ((uint32_t *)&RGB_PAL[g_ppu._2001.use_gray_mode ? (g_ppu.bg_pal[nes_4bits_color[4]] & 0x30) : (g_ppu.bg_pal[nes_4bits_color[4]] & 0x3F)].blue)[0];
  55.     suffuse_rgb_slot[scan_count * 8 + 5] = ((uint32_t *)&RGB_PAL[g_ppu._2001.use_gray_mode ? (g_ppu.bg_pal[nes_4bits_color[5]] & 0x30) : (g_ppu.bg_pal[nes_4bits_color[5]] & 0x3F)].blue)[0];
  56.     suffuse_rgb_slot[scan_count * 8 + 6] = ((uint32_t *)&RGB_PAL[g_ppu._2001.use_gray_mode ? (g_ppu.bg_pal[nes_4bits_color[6]] & 0x30) : (g_ppu.bg_pal[nes_4bits_color[6]] & 0x3F)].blue)[0];
  57.     suffuse_rgb_slot[scan_count * 8 + 7] = ((uint32_t *)&RGB_PAL[g_ppu._2001.use_gray_mode ? (g_ppu.bg_pal[nes_4bits_color[7]] & 0x30) : (g_ppu.bg_pal[nes_4bits_color[7]] & 0x3F)].blue)[0];

  58.     // 此时一个Tile扫描完毕, 为了扫描下一个Tile.要提前计算好命名表和属性表的地址
  59.     // 命名表很简单直接+1就可以了.但是注意, 可能会跨页
  60.     // poll_x就是用来处理跨页的. 当poll_x等于32的时候说明下一次刚好穿过了一个命名表
  61.     // 那么就要切换到另一个, 此种情况下只要切换水平命名表即可
  62.    
  63.     ++nt_addr;
  64.     ++poll_x;
  65.    
  66.     if (poll_x == 32)
  67.     {
  68.       poll_x = 0; // 复位到初始位置
  69.       nt_addr ^= 0x0400; // 切换水平命名表
  70.       attr_addr ^= 0x0400; // 同时切换属性表
  71.       nt_addr -= 0x0020; // 复位到初始位置
  72.       attr_addr -= 0x0007; // 复位到初始位置
  73.     }
  74.     else if (!(poll_x & 3)) // 属性表一个字节分4*4tile, 当越过4个Tile需要下一个字节属性表字节
  75.     {
  76.       attr_addr++;
  77.     }
  78.    
  79.     if (!(poll_y & 2))
  80.     {
  81.       if (!(poll_x & 2))
  82.       {
  83.         attr_bits = (read_vram_byte (attr_addr) & 0x03) << 2; // y0x0 左上, 分配D0-D1
  84.       }
  85.       else
  86.       {
  87.         attr_bits = (read_vram_byte (attr_addr) & 0x0C) << 0; // y0x1 右上, 分配D2-D3
  88.       }
  89.     }
  90.     else
  91.     {
  92.       if (!(poll_x & 2))
  93.       {
  94.         attr_bits = (read_vram_byte (attr_addr) & 0x30) >> 2; // y1x0 左下, 分配D4-D5
  95.       }
  96.       else
  97.       {
  98.         attr_bits = (read_vram_byte (attr_addr) & 0xC0) >> 4; // y1x1 右下, 分配D6-D7
  99.       }
  100.     }   
  101.   }
  102.   // 扫描完毕根据偏移计算真正的256像素缓冲
  103.   // 同时检测是否开启BG裁剪
  104.   // 之后放入真正的像素缓冲
  105.   
  106.   if (g_ppu._2001.use_bg_clip)
  107.   {
  108.     // 左边八个像素填充透明色/清除颜色标记
  109.     for (scan_count = 0; scan_count != 8; scan_count++)
  110.     {
  111.       suffuse_rgb_slot [mini_offset_x+scan_count] = ((uint32_t *)&RGB_PAL[g_ppu.bg_pal[0]].blue)[0];
  112.           suffuse_line_attr[mini_offset_x+scan_count] = 0;
  113.     }
  114.   }
  115.   
  116.   // 拷贝真实RGB缓冲区域/属性
  117.   memcpy (&rgb_pixels_out[g_ppu.cur_scanlines * 256 * 4], &suffuse_rgb_slot [mini_offset_x], 256 * 4);
  118.   memcpy (&g_ppu.cur_line_attr[0],                        &suffuse_line_attr[mini_offset_x], 256 * 1);
  119. }
复制代码
#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-21 22:40:57 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-21 23:00 编辑


上面的函数大致是根据当前扫描线行数,来扫描具体的BG
配合着这幅图来看, 红线至紫线的区间内就是实际扫描的256个像素.黄线则是命名表分割线
  uint8_t suffuse_line_attr[256+8];
  static uint32_t suffuse_rgb_slot[(256+8)*4];

两个都是临时的缓冲, 前者是背景属性标记, 后者则是当前的256+8个RGB像素
之后根据具体的偏移放进真正的缓冲
Tile内X偏移则是指红线落入得Tile内的左边像素偏移[范围是0~7]
正常情况下每次扫描33个tile,根据偏移补正放入RGB缓冲
  nt_addr   = g_ppu._2000.nametable_page + poll_x + (poll_y << 5);
  attr_addr = 0x23C0 | (nt_addr & 0x0C00) | (poll_x >> 2) | ((poll_y & 0xFC) << 1);

属性表地址的计算你们一定感觉很奇怪 首先给出0x23C0这个属性表基地址
因为attr一个字节分给4*4个tile, 所以当前的位置要除以4才能取得初始偏移
一共64个字节你算一下, 是6位
Y占高3位[Y的三位0~7,分给列的Tile的0~31,实际到29就可以了.因为是256*240的屏幕]
X占低3位[X的三位0~7,分给横的Tile的0~31]
平均下来刚刚好, 一个字节分给4*4个Tile
下面根据具体的偏移取出2位属性表颜色
开始进入扫描33个tile的循环





#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

3

主题

74

帖子

283

积分

中级会员

Rank: 3Rank: 3

威望
9
经验
182
贡献
9
 楼主| 发表于 2016-3-21 23:05:19 | 显示全部楼层
本帖最后由 moecmks 于 2016-3-23 11:21 编辑
  1.     ++nt_addr;
  2.     ++poll_x;
  3.    
  4.     if (poll_x == 32)
  5.     {
  6.       poll_x = 0; // 复位到初始位置
  7.       nt_addr ^= 0x0400; // 切换水平命名表
  8.       attr_addr ^= 0x0400; // 同时切换属性表
  9.       nt_addr -= 0x0020; // 复位到初始位置
  10.       attr_addr -= 0x0007; // 复位到初始位置
  11.     }
  12.     else if (!(poll_x & 3)) // 属性表一个字节分4*4tile, 当越过4个Tile需要下一个字节属性表字节
  13.     {
  14.       attr_addr++;
  15.     }
复制代码



下面的扫描tile循环中比较有难点的当poll_x==32的时候是刚刚好跨页, 此时切换水平命名表,进入另一块命名表初始的X坐标这个时候的X一般都是为0[Y不变,此函数始终是在扫描一条Y扫描线]
切换的方式比较奇特, ^0x400
因为只会在水平方向切换
0x2000[D10-D11:00]/0x2400[D10-D11:01]
0x2800[D10-D11:10]/0x2C00[D10-D11:11]
仔细观查D10-D11,水平方向上切换只会是D10改变.所以切换到另一张直接^0x400就可以了
属性表也是这样切换.然后在-0x20/0x07复位到当前命名表/属性表的初始地址.[前者nt_addr先加了1,肯定所以要-0x20.后者没有提前加所以只要-0x07即可,为什么还要再减去这些?
举个例子:当前nt_addr为201F, +1变成2020此时穿页了.切换页面变成2420,这不是我们想要的结果所以再减去0x20一整块256[32个tile的长度]才会回到新的命名表的原点,属性表也是这样.]

突然发现CPU那篇错误的还是非常多的,暂时先不管了.
这是水平方向的扫描,当电子枪往走向下一个Y方向的扫描线时候
因为是256*240的像素实际只有32*30个Tile, Y方向剩下de了2行32个Tile给了属性表
所以Y方向的到30得时候需要切换页面, ^0x800就可以
void do_scanline (void)
{
  do_cur_scanline();
  // 此行扫描完毕
  // 电子枪往下移动一行扫描线
  if (scroll_mini_y == 7) // 当前是在Tile内Y偏移为7的地方,那么下次就会是在一个新的Tile为0的地方
  {
        scroll_mini_y = 0; // 复位
        if (scroll_y == 29) // 此时Y方向在当前命名表的29的地方[进入此分支到29的话实际刚刚好水平垂直方向穿过一个命名表
                                            // 那么下一次就会在新的命名表Y的初始地址, 即Y=0
        {
                page_name ^= 0x800;// 切换命名表
                scroll_y = 0; // 复位
        }
        else
        {
          if (scroll_y == 31)
          {
                  // 这个分支是为了满足负滚动 ...此时的状态可能会夹杂着属性表数据
                  // 只要scroll_y = 0; // 复位 即可
          }
          else
          {
                 // 这是正常的分支.刚刚好穿过一个Tile
                 scroll_y ++;
          }
        }
  }
  else
  {
        // 说明该Tile还没扫描完毕.
        scroll_mini_y ++;
  }
  
}上诉是电子枪垂直方向的移动过程
水平方向更简单了保存一开始的水平方向命名表数据和X,Tile内的X偏移在每一次电子枪垂直方向补正下恢复之前的这些数据
为了简单的表示这种关系,很多80%的模拟器都有着两个变量loopy_t/loopy_v.
      // ------------ < loopy_v/loopy_t > ------------------
      //
      //   bit dispatch  
      //   XDDD CCBB BBBA AAAA
      //
      // D15[X]:discard, no use
      // D10-D11[CC]:nametable page index
      // D12-D14[DDD]:mini Y index/mini X index for loopy_x
      // D9-D5[BBBBB]:Y index
      // D0-D4[AAAAA]:X index
      //
      // write into 2005 value dispatch
      // hori[X]:BBBB BAAA B:X index  A:mini X index
      // vert[Y]:BBBB BAAA B:Y index  A:mini Y index
      // ------------ < loopy_v/loopy_t > ------------------
loopy_v也被&2006共享,[不知道这是不是有意为之的,我写的时候是将两者分开的]
在扫描线刚开始的把loopy_t赋值给loopy_v[这个loopy_t用来接收2000的命名表页面索引,2005的滚动信息,的值赋值给loopy_v]此时的loopy_v就是当前屏幕的电子枪地址, loopy_t保存了X方向的命名表数据/X偏移/Tile内偏移.在扫描过程中扫描完一条线的时候就用loopy_t复位X方向数据.然后进行下一行的扫描
      




#if !idppc
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
        long i;
        float x2
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2017-12-18 09:30 , Processed in 0.074063 second(s), 23 queries , XCache On.

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

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