Procesory – działanie, emulacja, przykłady

Tworząc emulator komputera Atari XL/XE w Turbo Pascal 5.5 trzeba od czegoś zacząć. Wydaje się oczywiste, że chyba powinno zacząć się od tego, co najważniejsze, czyli od procesora. Dopiero potem kolejne elementy systemu komputerowego, aż uda się sporządzić cały komputer z części zbudowanych samodzielnie na wzór oryginalnych układów elektronicznych.

Zacząć trzeba od czegoś, tak, czy inaczej, no to na początek: CPU.

Trzeba sobie przygotować prostą procedurę w Turbo Pascal 5.5 realizującą wszystkie rozkazy CPU 6502 Atari XL/XE.

Wstępnie procedura powinna być prosta w wersji najprostszej wprost, czyli lista rozkazów zaprogramowanych i wybór rozkazu do wykonania i gotowe.

Po próbach przyspieszenia takiej prostej procedury standardowej w kwestiach programistycznych, można powoli zacząć budować procesor działający na bazie swojego rodzaju ‚dekodera’ rozkazów, który nie jest prostym wyborem numeru rozkazu z listy dostępnych rozkazów – sprawdzając każdy możliwy numer po kolei, aż do znalezienia prawidłowego numeru rozkazu do wykonania – można to jakoś przyspieszyć, oczywiste – stąd dochodzi się etapami do metody szybszej niż prostej sekwencyjnej wprost, dobry początek:

function do_IR :byte;
begin
inc_pc;
{  decode; }

if reg_IR and  15 = 10 then { grupa rozkazow -1010 }

case reg_IR of

{ rozkazy nieistniejace z grupy: 26, 58, 90, 122, 218, 250 }

{ASL}   10  : begin
reg_p.c:=reg_A and 128 shr 7;
reg_A:=reg_A shl 1 and 254;
set_nz;
end;

{LSR}    74  : begin
reg_p.c:=reg_A and 1;
reg_A:=reg_A shr 1;
end;

{ROL}    42  : begin
reg_p.o:=reg_A and 128;
reg_A:=(reg_A shl 1) or reg_p.c;
reg_p.c:=reg_p.o;
set_nz;
end;

{ROR}   106 : begin
reg_p.o:=reg_A and 1;
reg_A:=(reg_A shr 1) or (reg_p.c shl 7);
reg_p.c:=reg_p.o;
set_nz;
end;

{TXA}    138 : begin
reg_A  :=  reg_X;
set_nz;
end;

{TXS}    154 : reg_SP   :=  reg_X;

{TAX}    170 : begin
reg_X  :=  reg_A;
set_nz;
end;

{TSX}    186 : begin
reg_X  :=  reg_SP;
set_Xnz;
end;

{DEX}    202 : begin
reg_X:=reg_X-1;
set_Xnz;
end;

{NOP}    234 : ; { instrukcja pusta: 2 takty }

end

else

if reg_IR and 15 = 8 then { grupa rozkazow -1000 }

case reg_IR of

{CLC}    24  : reg_p.c:=0;

{CLD}    216 : reg_p.d:=0;

{CLI}    88  : reg_p.i:=0;

{CLV}    184 : reg_p.v:=0;

{DEY}    136 : begin
reg_Y:=reg_Y-1;
set_Ynz;
end;

{INX}    232 : begin
reg_X:=reg_X+1;
set_Xnz;
end;

{INY}    200 : begin
reg_Y:=reg_Y+1;
set_Ynz;
end;

{PHA}    72  : begin
reg_BAH:=1; reg_BAL:=reg_SP; DB:=reg_A; memw;
reg_SP:=reg_SP-1;
end;

{PHP}    8   : begin
reg_BAH:=1; reg_BAL:=reg_SP; DB:=bit8_reg_p; memw;
reg_SP:=reg_SP-1;
end;

{PLA}    104 : begin
reg_SP:=reg_SP+1; reg_BAH:=1; reg_BAL:=reg_SP; mem;
reg_A:=DB;
set_nz;
end;

{PLP}    40  : begin
reg_SP:=reg_SP+1; reg_BAH:=1; reg_BAL:=reg_SP; mem;
bit64_reg_p;
reg_p:=reg64_p;
end;

{SEC}    56  : reg_p.c:=1;

{SED}    248 : reg_p.d:=1;

{SEI}    120 : reg_p.i:=1;

{TAY}    168 : begin
reg_Y  :=  reg_A;
set_nz;
end;

{TYA}    152 : begin
reg_A  :=  reg_Y;
set_nz;
end;
end

else

case reg_IR of { grupa pozostałych rozkazow bez argumentu }

{BRK}    0 : begin
inc_pc;
reg_p.b:=1;
RAM[1,reg_SP]:=reg_PCL; reg_SP:=reg_SP-1;
RAM[1,reg_SP]:=reg_PCH; reg_SP:=reg_SP-1;
RAM[1,reg_SP]:=bit8_reg_p; reg_SP:=reg_SP-1;
reg_PCL:=RAM[num_of_pages-1,num_of_cells-2];
reg_PCH:=RAM[num_of_pages-11,num_of_cells-1];
end;

{JSR}    32  : begin
inc_pc;
reg_BAH:=1; reg_BAL:=reg_SP;
DB:=reg_PCH; memw; reg_SP:=reg_SP-1; reg_BAL:=reg_SP;
DB:=reg_PCL; memw; reg_SP:=reg_SP-1;
set_pc(-1);
get_data_ir; reg_BAL:=DB;
get_data_ir; reg_BAH:=DB;
reg_PCL:=reg_BAL;
reg_PCH:=reg_BAH;
end;

{RTI}    64  : begin
reg_SP:=reg_SP+1; DB:=RAM[1,reg_SP]; bit64_reg_p;
reg_SP:=reg_SP+1; reg_PCL:=RAM[1,reg_SP];
reg_SP:=reg_SP+1; reg_PCH:=RAM[1,reg_SP];
end;

{RTS}    96  : begin
reg_SP:=reg_SP+1; reg_PCL:=RAM[1,reg_SP];
reg_SP:=reg_SP+1; reg_PCH:=RAM[1,reg_SP];
inc_pc;
end;
else

begin

get_data_ir;

if reg_IR and 1 = 1 then { grupa -1 z jednym argumentem }

case reg_IR of
{ADC} 105 : begin
bcd_plus;
set_DBnzc;
reg_A:=reg_tmp;
end;

101 : begin
reg_BAH:=0; reg_BAL:=DB; mem;
bcd_plus;
set_DBnzc;
reg_A:=reg_tmp;
end;

117 : begin
adr_zx; mem;
bcd_plus;
set_DBnzc;
reg_A:=reg_tmp;
end;

97  : begin
adr_preX; mem;
bcd_plus;
set_DBnzc;
reg_A:=reg_tmp;
end;

113 : begin
adr_postY; mem;
bcd_plus;
set_DBnzc;
reg_A:=reg_tmp;
end;

{AND}  41  : begin
reg_A:=reg_A and DB;
set_nz;
end;

37  : begin
reg_BAH:=0; reg_BAL:=DB; mem;
reg_A:=reg_A and DB;
set_nz;
end;

53  : begin
adr_zx; mem;
reg_A:=reg_A and DB;
set_nz;
end;

33  : begin
adr_preX; mem;
reg_A:=reg_A and DB;
set_nz;
end;

49  : begin
adr_postY; mem;
reg_A:=reg_A and DB;
set_nz;
end;

{CMP}    201 : set_Anzc;

197 : begin
reg_BAH:=0; reg_BAL:=0; mem;
set_Anzc
end;

213 : begin
adr_zx; mem;
set_Anzc
end;

193 : begin
adr_preX; mem;
set_Anzc
end;

209 : begin
adr_postY; mem;
set_Anzc
end;

{EOR}    73  : begin
reg_A:=reg_A xor DB;
set_nz;
end;
69  : begin
reg_BAH:=0; reg_BAL:=DB; mem;
reg_A:=reg_A xor DB;
set_nz;
end;

85  : begin
adr_zx; mem;
reg_A:=reg_A xor DB;
set_nz;
end;

65  : begin
adr_preX; mem;
reg_A:=reg_A xor DB;
set_nz;
end;

81  : begin
adr_postY; mem;
reg_A:=reg_A xor DB;
set_nz;
end;

{LDA}      169 : begin
reg_A:=DB;
set_nz;
end;

165 : begin
reg_BAH:=0; reg_BAL:=DB; mem;
reg_A:=DB;
set_nz;
end;

181 : begin
adr_zx; mem;
reg_A:=DB;
set_nz;
end;

161 : begin
adr_preX; mem;
reg_A:=DB;
set_nz;
end;

177 : begin
adr_postY; mem;
reg_A:=DB;
set_nz;
end;

{ORA}    9   : begin
reg_A:=reg_A or DB;
set_nz;
end;

5   : begin
reg_BAH:=0; reg_BAL:=DB; mem;
reg_A:=reg_A or DB;
set_nz;
end;

21  : begin
adr_zx; mem;
reg_A:=reg_A or DB;
set_nz;
end;

1   : begin
adr_preX; mem;
reg_A:=reg_A or DB;
set_nz;
end;

17  : begin
adr_postY; mem;
reg_A:=reg_A or DB;
set_nz;
end;

{SBC}    233 : begin
bcd_min;
set_DBnnzc;
reg_A:=reg_tmp;
end;

229 : begin
reg_BAH:=0; reg_BAL:=DB; mem;
bcd_min;
set_DBnnzc;
reg_A:=reg_tmp;
end;

245 : begin
adr_zx; mem;
bcd_min;
set_DBnnzc;
reg_A:=reg_tmp;
end;

225  : begin
adr_preX; mem;
bcd_min;
set_DBnnzc;
reg_A:=reg_tmp;
end;

241 : begin
adr_postY; mem;
bcd_min;
set_DBnnzc;
reg_A:=reg_tmp;
end;

133 : begin
reg_BAH:=0; reg_BAL:=DB;
DB:=reg_A; memw;
end;

149 : begin
adr_zx;
DB:=reg_A; memw;
end;

129 : begin
adr_preX;
DB:=reg_A; memw;
end;

{ STA } 145 : begin
adr_postY;
DB:=reg_A; memw;
end;

else

begin { grupa -1 z dwoma argumentami }

reg_BAL:=DB;
get_data_ir; reg_BAH:=DB;

case reg_IR of

109 : begin
mem;
bcd_plus;
set_DBnzc;
reg_A:=reg_tmp;
end;

125 : begin
set_ab(reg_X); mem;
bcd_plus;
set_DBnzc;
reg_A:=reg_tmp;
end;

121 : begin
set_ab(reg_Y); mem;
bcd_plus;
set_DBnzc;
reg_A:=reg_tmp;
end;

45  : begin
mem;
reg_A:=reg_A and DB;
set_nz;
end;

61  : begin
set_ab(reg_X); mem;
reg_A:=reg_A and DB;
set_nz;
end;

57  : begin
set_ab(reg_Y); mem;
reg_A:=reg_A and DB;
set_nz;
end;

205 : begin
mem;
set_Anzc
end;

221 : begin
set_ab(reg_X); mem;
set_Anzc
end;

217 : begin
set_ab(reg_Y); mem;
set_Anzc
end;

77  : begin
mem;
reg_A:=reg_A xor DB;
set_nz;
end;

93  : begin
set_ab(reg_X); mem;
reg_A:=reg_A xor DB;
set_nz;
end;

89  : begin
set_ab(reg_Y); mem;
reg_A:=reg_A xor DB;
set_nz;
end;

173 : begin
mem;
reg_A:=DB;
set_nz;
end;

189 : begin
set_ab(reg_X); mem;
reg_A:=DB;
set_nz;
end;

185 : begin
set_ab(reg_Y); mem;
reg_A:=DB;
set_nz;
end;

13  : begin
mem;
reg_A:=reg_A or DB;
set_nz;
end;

29  : begin
set_ab(reg_X); mem;
reg_A:=reg_A or DB;
set_nz;
end;

25  : begin
set_ab(reg_Y); mem;
reg_A:=reg_A or DB;
set_nz;
end;

237 : begin
mem;
bcd_min;
set_DBnnzc;
reg_A:=reg_tmp;;
end;

253 : begin
set_ab(reg_X); mem;
bcd_min;
set_DBnnzc;
reg_A:=reg_tmp;
end;

249 : begin
set_ab(reg_Y); mem;
bcd_min;
set_DBnnzc;
reg_A:=reg_tmp;
end;

{STA}    141 : begin
DB:=reg_A; memw;
end;

157 : begin
set_ab(reg_X);
DB:=reg_A; memw;
end;

153 : begin
set_ab(reg_Y);
DB:=reg_A; memw;
end;
else;

end
end
end

else

case reg_IR of { grupa -0 z jednym argumentem }

6   : begin
reg_BAH:=0; reg_BAL:=DB; mem;
set_DBc;
DB:=DB shl 1 and 254; memw;
set_nz;
end;

22  : begin
adr_zx; mem;
set_DBc;
DB:=DB shl 1 and 254; memw;
set_nz;
end;

{BCC}   144 : begin
if reg_p.c=0 then
if DB>127 then set_pc(DB-256)
else set_PC(DB);
end;

{BCS}    176 : begin
if reg_p.c=1 then
if DB>127 then set_pc(DB-256)
else set_pc(DB);
end;

{BEQ}    240 : begin
if reg_p.z=1 then
if DB>127 then set_pc(DB-256)
else set_pc(DB);
end;

36  : begin
reg_BAH:=0; reg_BAL:=DB; mem;
set_nzv;
reg_A:=reg_A and DB;
end;

{BMI}    48  : begin
if reg_p.n=1 then
if DB>127 then set_pc(DB-256)
else set_pc(DB);
end;

{BNE}    208 : begin
if reg_p.z=0 then
if DB>127 then set_pc(DB-256)
else set_pc(DB);
end;

{BPL}    16  : begin
if reg_p.n=0 then
if DB>127 then set_pc(DB-256)
else set_pc(DB);
end;

{BVC}    80  : begin
if reg_p.v=0 then
if DB>127 then set_pc(DB – 256)
else set_pc(DB);
end;

{BVS}    112 : begin
if reg_p.v=1 then
if DB>127 then set_pc(DB – 256)
else set_pc(DB);
end;

{CPX}    224 : set_Xnzc;

228 : begin
reg_BAH:=0; reg_BAL:=DB; mem;
set_Xnzc;
end;

{CPY}    192 : set_Ynzc;

196 : begin
reg_BAH:=0; reg_BAL:=DB; mem;
set_Ynzc;
end;

198 : begin
reg_BAH:=0; reg_BAL:=DB; mem;
DB:=DB-1; memw;
set_DBnz;
end;

214 : begin
adr_zx; mem;
DB:=DB-1; memw;
set_DBnz;
end;

230 : begin
reg_BAH:=0; reg_BAL:=DB; mem;
DB:=DB+1; memw;
set_DBnz;
end;

246 : begin
adr_zx; mem;
DB:=DB+1; memw;
set_DBnz;
end;

{JMP}    76  : begin
reg_tmp:=DB;
get_data_ir; reg_PCH:=DB; reg_PCL:=reg_tmp;
end;

{LDX}    162 : begin
reg_X:=DB;
set_Xnz;
end;

166 : begin
reg_BAH:=0; reg_BAL:=DB; mem;
reg_X:=DB;
set_Xnz;
end;

182 : begin
adr_zy; mem;
reg_X:=DB;
set_Xnz;
end;

{LDY}    160 : begin
reg_Y:=DB;
set_Ynz;
end;

164 : begin
reg_BAH:=0; reg_BAL:=DB; mem;
reg_Y:=DB;
set_Ynz;
end;

180 : begin
adr_zx; mem;
reg_Y:=DB;
set_Ynz;
end;

70  : begin
reg_p.c:=DB and 1;
DB:=DB shr 1; memw;
set_DBnz;
end;

86  : begin
adr_zx; mem;
reg_p.c:=DB and 1;
DB:=DB shr 1; memw;
set_DBnz;
end;

38  : begin
reg_BAH:=0; reg_BAL:=DB; mem;
reg_p.o:=DB and 128;
DB:=DB shl 1 or reg_p.c; memw;
reg_p.c:=reg_p.o;
set_DBnz;
end;

54  : begin
adr_zx; mem;
reg_p.o:=DB and 128;
DB:=(DB shl 1) or reg_p.c; memw;
reg_p.c:=reg_p.o;
set_DBnz;
end;

102  : begin
reg_BAH:=0; reg_BAL:=DB; mem;
reg_p.o:=DB and 1;
DB:=(DB shr 1) or (reg_p.c shl 7); memw;
reg_p.c:=reg_p.o;
set_DBnz;
end;

118  : begin
adr_zx; mem;
reg_p.o:=DB and 1;
DB:=(DB shr 1) or (reg_p.c shl 7); memw;
reg_p.c:=reg_p.o;
set_DBnz;
end;

134 : begin
reg_BAH:=0; reg_BAL:=DB;
DB:=reg_X;  memw;
end;

150 : begin
adr_zy;
DB:=reg_X;  memw;
end;

132 : begin
reg_BAH:=0; reg_BAL:=DB;
DB:=reg_Y; memw;
end;

148 : begin
adr_zx;
DB:=reg_Y;  memw;
end;

else

begin { grupa -0 z dwoma argumentami }

reg_BAL:=DB;
get_data_ir; reg_BAH:=DB;

case reg_IR of

14  : begin
mem;
set_DBc;
DB:=DB shl 1 and 254; memw;
set_nz;
end;

30  : begin
set_ab(reg_X); mem;
set_DBc;
DB:=DB shl 1 and 254; memw;
set_nz;
end;

{BIT}    44  : begin
mem;
set_nzv;
reg_A:=reg_A and DB;
end;

236 : begin
mem;
set_Xnzc;
end;

204 : begin
mem;
set_Ynzc;
end;

{DEC}    206 : begin
mem;
DB:=DB-1; memw;
set_DBnz;
end;

222 : begin
set_ab(reg_X); mem;
DB:=DB-1; memw;
set_DBnz;
end;

{INC}   238 : begin
mem;
DB:=DB+1; memw;
set_DBnz;
end;

254 : begin
set_ab(reg_X); mem;
DB:=DB+1; memw;
set_DBnz;
end;

108 : begin
mem;
reg_tmp:=DB; set_ab(1); mem;
reg_PCH:=DB; reg_PCL:=reg_tmp;
end;

174 : begin
mem;
reg_X:=DB;
set_Xnz;
end;

190 : begin
set_ab(reg_Y); mem;
reg_X:=DB;
set_Xnz;
end;

172 : begin
mem;
reg_Y:=DB;
set_Ynz;
end;

188 : begin
set_ab(reg_X); mem;
reg_Y:=DB;
set_Ynz;
end;

78  : begin
mem;
reg_p.c:=DB and 1;
DB:=DB shr 1; memw;
set_DBnz;
end;

94  : begin
set_ab(reg_X); mem;
reg_p.c:=DB and 1;
DB:=DB shr 1; memw;
set_DBnz;
end;

46  : begin
mem;
reg_p.o:=DB and 128;
DB:=DB shl 1 or reg_p.c; memw;
reg_p.c:=reg_p.o;
set_DBnz;
end;

62  : begin
set_ab(reg_X); mem;
reg_p.o:=DB and 128;
DB:=DB shl 1 or reg_p.c; memw;
reg_p.c:=reg_p.o;
set_DBnz;
end;

110  : begin
mem;
reg_p.o:=DB and  1;
DB:=(DB shr 1) or (reg_p.c shl 7); memw;
reg_p.c:=reg_p.o;
set_DBnz;
end;

126  : begin
set_ab(reg_X); mem;
reg_p.o:=DB and 1;
DB:=(DB shr 1) or (reg_p.c shl 7); memw;
reg_p.c:=reg_p.o;
set_DBnz;
end;

{STX}    142 : begin
DB:=reg_X; memw;
end;

{STY}    140 : begin
DB:=reg_Y;  memw;
end;

else;

end;
end;
end;
end;
end;

{

Bloki rozkazow CPU 6502:

rozkazy_IO_CPU_6502  (16 rozkazow)     – przesyłanie danych
rozkazy_DP_CPU_6502  (15 rozkazow)     – przetwarzanie danych
rozkazy_JP_CPU_6502  (16 rozkazow)     – sprawdzenia, odgalezienia i skoki
rozkazy_CR_CPU_6502  ( 9 rozkazow)     – sterowanie

w sumie 56 rozkazow, dostepne 13 trybow adresowania

}

set_IR;

do_IR:=0;

end;

Forma ‚dekodera’ wynika z zastosowanego sposobu znajdowania właściwego numeru rozkazu CPU do wykonania przez funkcję do_IR, jak widać w kodzie programu przykładowego w Turbo Pascal 5.5, celem ułożenia rozkazów w formie prezentowanej było zaoszczędzenie sprawdzania wszystkich numerów rozkazów po kolei, poprzez skok do grupy rozkazów, w której znajduje się rozkaz poszukiwany, a co za tym idzie pomijając sprawdzanie dużej ilości rozkazów, które nie byłyby poszukiwanymi numerami, drugim celem było zaoszczędzenie kodu procedury, grupując rozkazy według ilości argumentów (bez argumentu, z jednym argumentem, lub z dwoma argumentami) – dzięki temu pobranie danej dla rozkazu następuje PRZED rozpoczęciem poszukiwania konkretnego rozkazu do wykonania. Element problemowy nieakceptowalny ‚dekodera’ przykładowego: przy pobraniu jednej danej dla rozkazu, sprawdzane są wszystkie rozkazy jednoargumentowe, mimo, że rozkaz może być dwuargumentowy, czyli strata pracy procedury, a co za tym idzie – brak ekonomii – czyli do poprawki, oczywiste.

Ekonomia w informatyce musi być zawsze zbilansowana, jednak zawsze jedynie i wyłącznie 100%. Oczywistości. W przykładzie podanym, zasada ekonomii w informatyce nie jest spełniona, czyli forma niedozwolona, jednak dużo szybsza niż sekwencyjne wyszukiwanie rozkazów, oczywiste.

Czyli zadanie: znaleźć najszybszą metodę znajdowania numeru rozkazu CPU do wykonania.

Pomysł: Drzewo Binarne:

1. 56 rozkazów – numer rozkazu analizowany ‚bitcounterem’ od najmłodszego do najstarszego bitu numeru rozkazu, przy każdym rozpoznaniu bitu => skierowanie analizatora do mniejszej grupy rozkazów o połowę => wynik procedury to 6 sprawdzeń (always const) do znalezienia numeru rozkazu.

2. rodzaj adresowania z pozostałych bitów numeru rozkazu analogicznie.

Wychodzi najszybszy istniejący standardowy dekoder dla rozkazów CPU dla architektur procesorów omawianych:

- ustawienie rozkazu do wykonania (6 sprawdzeń bitów) => GOTOWY.

- ustawienie danych dla rozkazu (4 sprawdzenia bitów, pobranie danych) => GOTOWY.

Jak zmieścić 10 bitów na ośmiu bitach? Pewnie istnieje sposób.

CDN.


Teraz testowanie CPU 6502:

- wszystkie rozkazy

- tryby adresowania

- wszystkie możliwe stany rejestrów, znaczników i elementów istotnych dla pracy i dla efektów pracy CPU

Przykładowe programy w kodzie asemblerowym dla CPU 6502 – do wykonania przez procedurę realizującą emulację procesora 6502 Atari XL/XE – z analizą stanów rejestrów, znaczników, pamięci, etc. – w każdym kroku działania CPU emulowanego – na bazie programów podawanych dla CPU do wykonania – można znaleźć w książce ‚Asembler 6502′ (Jan Ruszczyc, wyd. SOETO, Warszawa 1987).

Programy zaprezentowane w książce są bardzo użyteczne do testowania CPU 6502 w całym zakresie oczekiwanym – wystarczy kody asemblerowe programów zawartych w książce zamienić na wartości kodu maszynowego dla CPU 6502, żeby móc prosto podać wartości proste dla przykładowej funkcji do_IR (emulującej pracę CPU) do wykonania.

Przykłady programów wykorzystanych z książki ‚Asembler 6502′ z przykładowymi efektami działania programów, oraz kody maszynowe programów na 7-mej stronie pamięci RAM Atari XL/XE zadeklarowanej w Turbo Pascal 5.5:

antic1.cpu – s. 169

Kod asemblerowy programu:

LDA #21
LDY #0
CYKL STA (58),Y
INY
BNE CYKL

Wynik działania programu: umieszczenie znaku ‚A’ w 256 kolejnych miejscach w pamięci RAM – do wyświetlenia w trybie tekstowym GRAPHICS 0 Atari BASIC – znak umieszczony jako bajt w obszarze pamięci obrazu dla trybu GRAPHICS 0 (ANTIC IR mode $02)

antic1-cpu antic1-cpu-source-code

Jak widać w kodzie maszynowym, wartość $21 z oryginalnego programu w asemblerze została zastąpiona wartością 65 dec.

Podgląd obszaru pamięci obrazu dla GRAPHICS 0 po wykonaniu programu:

antic1-cpu-effect01 antic1-cpu-effect02

Jak widać wartości 65 dec znajdujące się w pamięci RAM w obszarze pamięci ekranu dla trybu tekstowego GRAPHICS 0 rozpoznawane są przez procedurę realizującą funkcje ANTIC jako numery ATASCII znaków wprost do wyświetlenia na ekranie. W rzeczywistości kody wewnętrzne znaków, którymi operuje ANTIC są inne, niż ATASCII, stąd procedura ANTIC wymaga poprawienia w postaci nie konwertowania kodów ATASCII na kody wewnętrzne:

ch:=ATASCII_char_number;

if ch>127 then ch:=ch-128; { pominięcie znaczenia numeru zestawu znaków CHARSET }

if ch<$20 then ch:=ch+$40 else if ch<$60 then ch:=ch-$20;

Wtedy podanie wartości $21 dla ANTIC jako znaku do wyświetlenia spowoduje wyświetlenie znaku o numerze $21 z właściwego zestawu znaków CHARSET1 lub CHARSET2, czyli przykładowo znak ‚A’, bez odejmowania $20 od bajtu o wartości 65 dec.

Proste, oczywiste.

Podgląd stanu rejestrów procesora i znaczników po wykonaniu programu:

antic1-cpu-effect03


antic2.cpu

Kod asemblerowy programu:

Wynik działania programu: umieszczenie w pamięci ekranu dla trybu GRAPHICS 0 256-ciu kolejnych znaków.

antic2-cpu antic2-cpu-source-code

Jak widać program dla CPU był wykonany przed uruchomieniem ANTIC i przed inicjalizacją pamięci ekranu dla trybu GRAPHICS 0 dla widoku startowego typowego dla ATARI BASIC. Nadal procedura ANTIC konwertuje wartości bajtów z pamięci ekranu na kody wewnętrzne, wykonując pracę zbędną, zmieniającą wynik pracy programu na inny, niż oczekiwany / prawidłowy.

Podgląd stanu rejestrów procesora i znaczników po wykonaniu programu:

antic2-cpu-flags-regs


sum1.cpu – s. 53

Kod asemblerowy programu:

DOD1 CLD
LDA #27
CLC
ADC #31
STA B

Wynik działania programu: suma dwóch liczb wpisana do adresu na 7-mej stronie pamięci. Wynik pozostaje w rejestrze akumulatora.

Kod maszynowy programu:

sum1-cpu-src-code

Stany rejestrów procesora i znaczników po wykonaniu programu:

sum1-cpu-flags-regs

Strona 7-ma pamięci RAM Atari XL/XE po wykonaniu programu (wynik działania programu, oraz kod programu):

sum1-cpu-effect


Skoro proste programiki w kodzie maszynowym jakoś działają, można poszukać bardziej skomplikowanych, ciekawszych, bardziej złożonych programów, żeby sprawdzić, czy CPU działa poprawnie.

Najpierw sprawdzenie obszaru pamięci ROM, gdzie powinien znajdować się program SELF TEST Atari XL/XE.

ROM Atari XL-XE pg228

Czy w obszarze pamięci na 228 stronie pamięci Atari XL/XE znajduje się oczekiwany program SELF TEST?

Adres skoku do SELF TEST: $E471.

Kod maszynowy jest widoczny (od adresu 58481 dec).

Kod asemblerowy programu:

JMP 35 242
JMP 27 241
JMP 37 241
JMP 233 239
JMP 93 239

Czyli nie bardzo wiadomo, co robi ta procedura SELF TEST, oprócz tego, że skacze do innych procedur. Potrzebny jest na początek podgląd pierwszej procedury od adresu: $F223 (242dec*256+35dec), czyli tam, gdzie skacze JMP 35 242:

ROM Atari XL-XE pg242

Kod asemblerowy procedury spod adresu $F223:

LDX 242
LDY 240
JSR 133 243
JSR 48 242
JMP 42 242

 

Na razie tyle wystarczy do dalszej analizy, bo JSR jest pod-procedurą, która powinna prawdopodobnie wrócić do procedury po ‚się’ wykonaniu, czyli potem druga pod-procedura i skok do innej procedury, pod adres [242:42], potem nie wiadomo, co dalej, zobaczymy analizując po kolei. Najpierw pod-procedura spod adresu [243:133]:

ROM Atari XL-XE pg243

LDY 241
TXA
LDX 0
STA[157] 68 3
TYA
STA[157] 69 3
LDA 9
STA[157] 66 3
LDA 255
STA[157] 72 3
JMP 86 228
RTS

 

Pierwsza pod-procedura wstępnie gotowa, trzeba sprawdzić, gdzie skacze JMP 86 228 i co robi procedura pod tym adresem, wtedy pod-procedura będzie gotowa w pełni.

 

Potem następne procedury analogicznie (wszystkie instrukcje JMP z głównej procedury SELF TEST widocznej na stronie 228 pamięci Atari XL/XE).

[..]

Po ustawieniu rejestru Program Counter na adres skoku do pierwszej instrukcji programu SELF TEST, czyli ($E471), teoretycznie program z ROM spod adresu wskazanego powinien zostać wykonany przez CPU.

Zobaczymy, etapami.

Najpierw ustawienie początkowe rejestrów CPU ogólnego przeznaczenia, Program Counter, znaczniki, etc. i można uruchomić CPU.

Na początek próbnie:

PC:=$e471; SP:=255; A:=0; X:=0; Y:=0; znaczniki też na zero (0);

Teraz RUN

[]

SELF TEST 01

Dziwne, ale genialne, bo coś już widać. A na dodatek, co najgenialniejsze, czego można by się spodziewać, ten ekran ciągle się zmienia, procedury SELF TESTU ciągle coś robią, zmieniają, skaczą i efekty widać na ekranie non-stop.

Przykład innego screenu z pracy SELF TEST:

SELF TEST 02

Ważne w tych zmianach ekranu, czy w działaniu SELF TEST jest to, że te zmiany zauważone realizowane przez SELF TEST w ciągłej pracy jakichś procedur są sensowne – w konkretnym miejscu, realizowane sekwencyjnie w jakiś sposób sensowny.

Czyli można uznać, że SELF TEST działa, hura.

Teraz trzeba przeanalizować pracę SELF TEST krok po kroku, żeby wiedzieć, dlaczego nie pojawił się znany obrazek SELF TEST, którego się oczekuje, znając pracę oryginalnego komputer Atari XL/XE.

Trzeba klikać CPU krokowo, instrukcja, za instrukcją i patrzeć, co powinno się dziać, a co się dzieje i czy dzieje się prawidłowo, jak powinno, czy inaczej i dlaczego => i trzeba to naprawiać i uzupełniać, aż pojawi się SELF TEST.

No to w drogę. Długa praca pewnie czeka, ale trzeba zrobić, aż zadziała.

[..]


Po namyśle, wnioski na dalszą pracę przy testowaniu CPU6502 Atari XL/XE wydające się bardziej rozumne i sensowniejsze:

1. Przetestować wszystkie programy asemblerowe zawarte w książce ‚Asembler 6502′.

Jeśli wszystkie programy zadziałają prawidłowo, można planować dalszą pracę testowania:

2. Znajdowanie procedur w ROM Atari XL/XE i uruchamianie tych procedur.

Jeśli procedury w ROM będą się wykonywały prawidłowo, powinno to oznaczać, że nie tylko CPU Atari XL/XE działa prawidłowo, ale również układy ANTIC, POKEY, wszystko, co powinno, cały komputer Atari XL/XE i powinno to być widać i słychać i powinno działać jak prawdziwy Atari XL/XE, czyli emulator komputera Atari XL/XE byłby gotowy i działający.

To dobry sposób.

Czyli READY.

Teraz po kolei:

Ad. 1).


Najpierw ciekawsze programiki, które coś pokażą i gotowe.

chara.cpu – s. 69

Kod asemblerowy programu:

LDA $41
JSR $F2B0

Wynik działania programu: wypisanie znaku ‚A’ na ekranie w trybie tekstowym GRAPHICS 0.

chara-cpu-effect

Ślicznie. Działa genialnie. Taki efekt był oczekiwany. Sukces i radość, oczywiste.

Kod maszynowy programu:

chara-cpu-src-code

Podgląd stanu rejestrów procesora i znaczników po wykonaniu programu:

chara-cpu-regs-flags

W efektach działania programu widzianych na znacznikach i rejestrach można już się zastanowić, czy powinno być, jak widać.

- wskaźnik stosu 252 zamiast 255 => do sprawdzenia, dlaczego, w tym podgląd strony pamięci ze stosem, żeby zobaczyć jakie tam są wartości

- rejestr akumulatora ma wartość 65 dec – super, bo to kod ATASCII znaku ‚A’ – można sprawdzić inne znaki (inne wartości w rejestrze akumulatora), żeby zobaczyć, czy program działa jak by się można domyślać / spodziewać, łącznie ze stanem rejestru akumulatora oczekiwanym po zakończeniu działania programu dla innych wartości podanych na ten rejestr w kodzie programu

- znacznik B ustawiony, bo program został prawdopodobnie przerwany ‚ręcznie’ przez emulator po zakończeniu procedury z ROM spod adresu [242:176] i powrocie do głównego programu na wartość ‚0’ wpisaną w rejestr IR (Instruction Register) dla PC=1797 dec, lub widząc rejestr PC=0 można się domyślać, że coś się zresetowało i ustawiło PC na 0 zamiast pozostać na następnej instrukcji po JSR z programu wykonanego => do sprawdzenia, w tym znalezienie procedury OUTCHAR pod adresem [242:176] w ROM i zobaczenie jak działa i jak powinna działać krok po kroku

Po sprawdzeniu i spowodowaniu, że wszystkie rejestry i znaczniki i układy Atari XL/XE pozostają we właściwych stanach po zakończeniu programu, można uznać, że działa i można sprawdzać następne programiki.


Następne programiki nasuwają się samoistnie jako pomysł, co sprawdzić następne, skoro poprzednie ładnie zadziałało, czyli:

1. Procedura z ROM, która pobiera znak z klawiatury w połączeniu z procedurą poprzednią sprawdzoną, czyli wyświetlenie znaku na ekranie => po umieszczeniu GETCHAR i OUTCHAR w krótkim programiku w asemblerze w pętli, czyli ze skokiem na początek programiku po wykonaniu OUTCHAR – powinna wyjść jakaś ‚maszyna do pisania’ => byłoby to jednocześnie sprawdzenie, czy działa klawiatura, czyli wklepywanie znaków wszystkich, które można wklepać na klawiaturze z obserwowaniem, jak wyświetlają się na ekranie w GRAPHICS 0, byłoby to sprawdzenie działania znaków sterujących również, przy okazji widać będzie ustawienia marginesów, zachowanie kursora, mnóstwo rzeczy można by przetestować w krótkim programiku typu ‚maszyna do pisania’ na dwóch procedurach z ROM: GETCHAR i OUTCHAR. Czyli dobry pomysł. Na dodatek wyjście z programiku może być obsługiwane jakimś konkretnym klawiszem typu ‚Esc’ lub ‚SHIFT+0′, lub dowolnie, ważne, że można przetestować jednocześnie jakąś instrukcję typu sprawdzenia / porównania jak CMP, CPX, itp. lub inne jakieś. Czyli same korzyści praktyczne trwałe. Super.

2. Jeśli wszystko będzie działać poprawnie, można sprawdzać następne programiki z tej książki ‚Asembler 6502‚.


getchar.cpu

Kod asemblerowy programu:

Wynik działania programu: oczekiwanie na naciśnięcie klawisza na klawiaturze oraz zwrot numeru naciśniętego klawisza do rejestrów / adresów właściwych

typewrit.cpu

Kod asemblerowy programu:

Wynik działania programu: ‚maszyna do pisania’ – oczekiwanie na naciśnięcie klawisza na klawiaturze i wyświetlenie na ekranie w trybie tekstowym znaku naciśniętego klawisza lub wykonanie znaku sterującego – powtarzanie czynności GETCHAR=>OUTCHAR do momentu naciśnięcia na klawiaturze klawisza ‚Esc’

[..]


Analogicznie realizuje się programowo pracę procesorów podobnych do przykładowego, m.in. komputerów takich jak ZX Spectrum, Commodore 64, Amstrad, Atari 520ST, Amiga, XT/AT/386/486/Pentium, inne, etc. wszystkie możliwe potencjalnie, nie tylko znane istniejące, oczywistości.

Analogicznie dotyczy dowolnych maszyn, mechanizmów, urządzeń, zasad, algorytmów, etc. nie tylko elektronicznych, oczywiste.


Dostosowanie 8-bitowego CPU 6502 Atari XL/XE do pracy w architekturze 64-bit jest kwestią zamiany rejestrów 8-bitowych na 64-bitowe, nic więcej, oczywiste.

Proste.

Czyli CPU 64-bit READY.


Literatura:

1. Asembler 6502 / Jan Ruszczyc ; Stołeczny Ośrodek Elektronicznej Techniki Obliczeniowej., Warszawa, Wydaw. SOETO, 1987.


Materiały uzupełniające:

ANTIC i GTIA Atari XL/XE oraz inne układy wizyjne / graficzne