Amit a PRIMO hardverről tudni kell
(ha
valaki emulátort szeretne készíteni)
Az alábbi dokumentumban összefoglalom azokat a hardver
jellegzetességeket, amelyeket ismernie kell annak, aki PRIMO
emulátort szeretne készíteni. A dokumentum
saját tapasztalataimon alapul, illetve a primoemu-ban
megvalósított egy-egy funkcióra is, ezért
meglehetősen szubjektív. Ha bizonyos funkciók
emulációját saját szempontomból
fontosabbnak tartottam, vagy netán mellőztem, azt
külön jelzem majd.
Processzor
A PRIMO processzora eredetileg egy Z80 kompatíbilis NDK
gyártmányú chip volt, amelynek névleges
órajele 2,5 Mhz. Ettől eltekintve a processzor minden
szempontból egy szabványos Z80-nak tekinthető, az
emulátorhoz szükséges tehát egy teljes Z80
processzor emulátor, amely emulálja a Z80 ciklusait
és utasításait, természetesen a nem
dokumentált utasításokat is.
Memória
A többféle típus miatt a memóriák
felosztása eltérő volt, itt most csak az A64-el
foglalkozom, amely 64K címezhető memóriát
tartalmazott az alábbi (kicsit szubjektív)
felosztás szerint:
0000–3FFF: 16K rendszer ROM, itt
található az A64 ROM image
4000–BFFF: 32K RAM terület.
C000-FFFF: 16K RAM terület, amely a
képernyőterületet tartalmazza.
Képernyő
A PRIMO két egymástól független képlap
kezelésére volt képes. A kép
felbontása 256x192
pixel, tehát 192
rasztersor, soronként 32 bájt.
A képterület cím-folytonos volt, azaz, az első sor
első bájtja a 0. képbájt, az első sor
utolsó bájta a 31. képbájt, a
második sor első bájtja a 32. képbájt,
és így tovább. A képterület ezek
szerint 192x32, azaz 6144
bájtot igényel. Az elsődleges képterület a
memória legutolsó 6K-s része E800-FFFF-ig. A másodlagos
képlap 8K-val lejjebb helyezkedik el, azaz a C800-DFFF tartományban. A
két lap között a KI-1 I/O port egy bitjével
lehetett váltani (lásd később).
Általános I/O
A Z80 64K I/O területet tud kezeli, ebből a PRIMO-ban csak az
alsó 256 I/O portot definiálják, ezt a
területet 4 db 64bájt hosszú
címterületként lehet kezelni mind inputként,
mind outputként.
I/O Címterület Kimenetek Bemenetek
00-3F KI-1 port (általános out bitek) BE-1 port (általános in bitek)
40-7F KI-2 port (CBM soros busz out) BE-2 port (CBM soros busz in)
80-BF Nem használt tartomány Nem használt tartomány
C0-FF Nem használt tartomány Nem használt tartomány
Bemenetek
A BE-1 port (IN 00-3F) a
rendszer általános input bitjeit tartalmazza, ezek
közül néhányat mindenképp meg kell
valósítani az emuláció során.
7. Bit: nincs használva
6. Bit: nincs használva
5. Bit: képkioltás, azaz a
vertikális visszafutás jelzése. Az eredeti PRIMO-n
e bit 1 értéke jelezte az elektronsugár
visszafutását. A PAL frekvenciák miatt ott ez egy
50Hz-es jel volt, viszont, ha VGA kártyán
emuláljuk a PRIMO képernyőjét és
megvalósítjuk ezt a bitet, a VGA frissítési
frekvenciái miatt ez 70Hz lesz.
Implementálását javaslom, hiszen
néhány alkalmazás használhatja a bitet a
képszinkronizáláshoz. A VGA rendelkezik olyan I/O
porttal, amelyen keresztül a visszafutás
detektálható (03DA, 3. bit).
4. Bit: BE-2/4 bit. A PRIMO rendszerbuszára
kivezetett általános I/O vonal.
Implementálása nem létszükséglet, ha
valaki mégis meg szeretné valósítani, a
nyomtató port valamelyik bemeneti bitjét lehet erre
felhasználni.
3. Bit: BE-2/3 bit. A PRIMO rendszerbuszára
kivezetett általános I/O vonal.
Implementálása nem létszükséglet, ha
valaki mégis meg szeretné valósítani, a
nyomtató port valamelyik bemeneti bitjét lehet erre
felhasználni.
2. Bit: Magnó adatbemenet. Mivel a primoemu
PTP magnófájlokat használt, én nem
implementáltam, de az előző két esethez hasonlóan
nem lehetetlen megoldani a nyomtatóportra csatlakoztatott
megfelelő illesztő segítségével, ha valaki direkt
magnó olvasást szeretne.
1. Bit: RESET nyomógomb állapota. A
PRIMO RESET nyomógombjának állapotát
tükrözi. Valójában ez a nyomógomb nem
RESET gomb, hanem egy egyszerű bementi bit, amelyet NMI alatt
kérdez le a rendszer. Mivel az NMI kikapcsolható,
ezért az alkalmazások deaktiválni tudják
ezt a gombot. Az implementáció során valamely
speciális billentyű lenyomása válthatja ki ennek a
bitnek a beállítását.
0. Bit: Billentyűzet bemenet. A megcímzett
gomb lenyomott/felengedett állapotát mutatja.
A BE-1 port 1-5 bitjei a 00…3F portok olvasásakor
egyenértékűek, a 0. bit viszont a címzett
billentyű állapotát adja vissza. A billentyűkhöz
tartozó I/O címeket a „PRIMO szoftver füzetek”
tartalmazza. Az implementáció során az
alábbiakat javaslom: az 1-5 biteket egy változó
(byte, unsigned char) tartalmazhatja, míg a billentyűzet
állapotát egy 64 elemű szintén bájtokat
tartalmazó tömb tárolhatja. Ezt a tömböt a
PC billentyűzet megszakítása folyamatosan
frissíti, így a Z80 IN utasítás
emulációjakor csupán ki kell venni a megfelelő
elemet a tömbből és összeállítani a
bementi bájtot.
A BE-2 port (IN 40..7F) az
opcionális CBM (Commodore) soros busz vonalainak
állapota, illetve a léptetőregiszteres botkormány
olvasható be. Én ezt a primoemu-ban nem
implementáltam, de szükséges lehet a
megvalósítása, ha valaki disk vagy joy
emulációt is szeretne.
7. Bit: nincs használva
6. Bit: nincs használva
5. Bit: SCLK
4. Bit: SDATA
3. Bit: SRQ
2. Bit: Botkormány adatbemenet, 2. bit
1. Bit: ATN
0. Bit: Botkormány adatbemenet, 1. bit
Kimenetek
A KI-1 port (OUT 00..3F) a
rendszer működése szempontjából fontos
biteket tartalmaz, majdnem mindegyik megvalósítása
szükséges az emulátorhoz.
7. Bit: NMI engedélyezés
(1=engedélyezve). Az implementáció során
ezt a bitet kell figyelnie a rendszernek, és ha
értéke 0, nem kell Z80 NMI megszakítást
generálni. Implementálása szükséges.
6. Bit: Botkormány léptetése. A
primoemu-ban nincs joy emuláció, ezért nem
implementáltam.
5. Bit: Magnó távvezérlés
vagy V24/2. Eredetileg a magnót vezérelte, de
általános kimeneti bitként is
használható volt. Implementálása nem
szükségszerű, de megvalósítható a
nyomtatóport valamely kimeneti bitjével.
4. Bit: Hanggenerátor, speaker. Ez
tulajdonképpen a PRIMO hangja,
megvalósítása mindenképp ajánlott.
Szerencsére a PC speaker ki-be kapcsolható nagyon
hasonló módon, mint a PRIMO hangszóró,
ezért a megvalósítás nem nehéz.
3. Bit: Képernyő lapkijelölés. A
két létező képlap közül választja
ki a láthatót. A BASIC ezt a bitet 1-re
állítja, ilyenkor az E800-FFFF terület
látható. Az emulátornak érdemes két
logikai képpel dolgoznia, és a bit
megváltozásakor ezek közül a megfelelőt
kiválasztani. A primoemu a fizikai VGA kép
kezdőcímét változtatja meg, ezáltal a
képváltás gyors. (Gyorsabb, mintha a teljes
képet kellene újrarajzolni.)
2. Bit: Magnó távvezérlés
vagy V24/1. Eredetileg a magnót vezérelte, de
általános kimeneti bitként is
használható volt. Implementálása nem
szükségszerű, de megvalósítható a
nyomtatóport valamely kimeneti bitjével.
1. Bit: Mangó kimenet jelszint
vezérlés 1.
0. Bit: Magnó kimenet jelszint
vezérlés 2. A két alsó bit a magnó
kimenet jelszintjét határozta meg (lásd a szoftver
füzetek F-40 oldalán). Mivel a primoemu PTP
magnófájlokat használ, én nem
implementáltam, de ez is megoldható a nyomtató
csatlakozón keresztül.
A KI-2 port (OUT 40..7F)
az opcionális CBM (Commodore) soros busz vonalainak
állapotát határozza meg. A primoemu-ban nincs
megvalósítva.
7. Bit: nincs használva
6. Bit: nincs használva
5. Bit: SCLK
4. Bit: SDATA
3. Bit: nincs használva
2. Bit: SRQ
1. Bit: ATN
0. Bit: nincs használva
A soros busz vonalait az eszközök nyitott kollektoros
kapukkal hajtják meg, ez azt jelenti, hogy a buszra
csatlakoztatott bármely eszköz képes a
kommunikáció kezdeményezésére.
További információkat a CBM soros buszról a
Commodore dokumentációkban, illetve a PRIMO hardver
és szoftver füzetekben találunk. Mint
látható, a KI-2 és BE-2 portokat csak akkor kell
implementálnunk, ha disk és/vagy joystick
emulációt is akarunk.
Billentyűzet
Ahogy BE-1 portnál már leírtam, a PRIMO úgy
kezeli a billentyűzetet, hogy minden gombot külön
portcíme n érhetünk el. A BE-1 port
címtartomány lekérdezésekor a beolvasott
adat 0. bitje adja vissza a megcímzett billentyű
állapotát. Az eredeti (A típusú)
billentyűzet kapacitív működési elve miatt a ROM-ban
található lekérdező rutin egy billentyűt csak
akkor tekint lenyomottnak, ha azt 128 lekérdezés
után is aktívnak találja. Ez a megoldás az
emulátor esetében azt eredményezi, hogy az
emulált Z80 IN utasítást igen sokszor fogja
végrehajtani a kód- Ha tehát az IN
utasítás megvalósítása kicsit
lassú, jelentősen ronthatja az emulátor
teljesítményét.
A primoemu-ban több olyan ROM patch is található,
amelyek átírják az eredeti ROM rutinokat,
ezáltal bizonyos funkciók módosulnak. Ezek
közül az egyik az, hogy a sorozatos
lekérdezések számát a ROM-ban kisebbre
vettem, hogy a IN utasítások kevesebbszer
hajtódjanak végre. (Lásd a szoftver
füzetekben az F-71 oldalon: „gomb sokszoros
lekérdezése”)
Egyéb
Magnófájlok: A
primo PTP fájl formátumát korábban
már közzétettem, így annak
felépítése ismert. A magnófájlok
kezeléséhez az szükséges, hogy az eredeti ROM
rutinokat néhány helyen módosítsuk,
így azok nem akarnak majd biteket beolvasni az I/O
portokról, hanem a megfelelő külső szervizrutint
hívják, amelyek a betöltés illetve az
adatmentés megfelelő elemi lépéseit
megvalósítják. A magnófájlokban
elhelyezett eredeti PRIMO programok formátuma
egyébként egyezik a szoftver füzetekben ismertetett
formátummal, a különbség csupán annyi,
hogy a PTP fájlok nem tartalmaznak szinkronizáló
mezőket.
Képfrissítés:
Az emuláció elég fontos kérdése a
képfrissítés. Ez természetesen
számtalan ötlettel megoldható, én itt most a
primoemu-ban alkalmazott módszert ismertetem. Mivel az
emulátor közvetlenül a VGA kártya
képmemóriájába ír, és a PRIMO
képfelépítés viszonylag egyszerű,
ezért nincs valódi frissítő rutin, hanem minden
egyes Z80 memóriaíráskor megvizsgálom, hogy
a célbájt képbájt-e, és ha igen,
akkor a VGA memóriát is módosítom. (Van egy WriteByte függvény,
amelyet minden memóriaíró Z80
utasítás rutinja meghív, ha
szükséges.) A Z80 processzor
emulációnál a memória írás
egyébként is speciális eset, hiszen meg kell
vizsgálni, hogy az írandó bájt ROM
területre vagy képernyő területre esik-e.
Különleges Z80
kódsorozatok: Az emulációból
való kilépéshez — például amikor egy
magnófájl kezelő szervizrutin hívása
szükséges — olyan illegális Z80 kódokat
használok fel, amelyek nem szerepelnek az eredeti
utasításkészletben. Például az ED 00
kód nem valódi Z80 utasítás. Ez
tehát egy külső rutint hív meg, amely elvégzi
a megfelelő műveleteket. Például egy bájt
magnóra írásakor (szoftver füzetek, F-89
oldal) egy ilyen speciális kódon keresztül jut el a
vezérlés a PTP kezelő rutinhoz, ami kiírja a
bájtot a PTP fájlba.
NMI emuláció: ez
a rész igazán egyszerű. A PC timer
megszakítását beállítottam, hogy
másodpercenként 50 megszakítást adjon,
és ez a kiszolgáló rutin egy falg-et billent be,
név szerint az NMIflag-et, ez jelzi a Z80 emulátor
számára, hogy NMI történt.
Természetesen itt figyelembe kell venni, hogy a KI-1 port NMI
engedélyező bitje milyen állapotban van.
Emulációs ciklus:
A megfelelő beállítások után
(képernyőmód, ROM patch-ek, stb.) az emulátor fő
működtető ciklusa valami ilyesmi:
while (!quit) {
Z80fetch; // végrehajtunk egy Z80 utasítást
Z80delay; // várakozunk, azaz lassítjuk az emulátort
PC_keyboard_check; // ellenőrizzük a speciális billentyűket, például menü, kilépés stb.
}
Amíg a főciklus látszólag egyszerű, a
háttérben azért történik
egy-két dolog: a timer megszakítás
generálja (ha engedélyezett) az NMI
megszakításokat a Z80 számára, a
billentyűzet megszakítás pedig frissíti a
billentyű táblázatot, amit a Z80 IN
utasítás kiolvashat. Speciális billentyű
lenyomása esetében a PC_keyboard_check
hívja a menükezelőt, vagy az egyéb rutinokat.
Példák az eredeti
forrásból
A KI-1 és BE-1 szervizrutinok. Ezeket az
eljárásokat hívja a Z80 emulátor OUT
és IN utasítása.
procedure KI1out; assembler;
asm
{ dx : portszam, cl : data }
cmp cl,KI1
jz @quit
mov KI1,cl { a 7. bit maszkolja az NMI-t }
{ hangszoro vezerles }
@speaker:
in al,$61
test cl,$10
jz @spk_off
@spk_on:
or al,$03
out $61,al
jmp @setPAGE
@spk_off:
and al,$FC
out $61,al
{ keplap valtas }
@setPAGE:
xor bx,bx
test cl,$08
jnz @sp1
test tvout,3
jnz @sp0
mov bx,80*240
jmp @sp1
@sp0:
mov bx,80*200
@sp1:
mov ScrnStart,bx
mov al,0ch
mov dx,03d4h
mov ah,bh
out dx,ax
mov ah,bl
inc al
out dx,ax
@quit:
mov ioexec,1
end;
procedure BE1in; assembler;
asm
{ dx : portszam, cl : data }
lea bx,Keys
mov al,dl { billentyu sorszam 0..63 }
and al,$3F
xlat { kivesszuk a tablazat megfelelo byte-jat }
and al,$01 { csak a 0. bit szamit }
mov cl,[Keys+64].byte { RESET nyomogomb }
shl cl,1
and cl,$02
or cl,al
mov dx,03dah { vertikalis visszafutas bit }
in al,dx
and al,8
shl al,2
or cl,al
or cl,11011100b { a nem implementalt biteket magasra allitjuk }
mov BE1,cl
mov ioexec,1
end;
Képbájt írása közvetlen módon.
Ezt a kiegészítő rutint hívja a Z80
memóriaírás.
{ E800-FFFF }
procedure WriteScreenByte0; assembler;
asm
{ di - address, cl - data }
mov ES,SegA000
sub di,[ScrTop1]
add di,di
mov di,[di+offset ScrnAdrTab]
xor bx,bx
mov bl,cl
shl bx,1
add bx,offset sbw
mov cx,[bx]
mov ES:[di],cx
@quit:
end;
Joco