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