Basic was natuurlijk dé taal om de computer te leren bedienen. Voor velen is het daarbij gebleven en dat is natuurlijk geen probleem. Maar wie er wat meer tijd en moeite voor nam, kon zich de taal der computers meester maken. Ik heb het hier natuurlijk over Machinetaal. Omdat Machinetaal voor velen (inclusief mijzelf) een te grote stap was, gebruikte je het tweede beste; Assmebleertaal of verkort Assembly. Het lijkt misschien erg moeilijk, maar iedereen kan het. Iedereen die het programmeren begrijpt en zijn of haar mouwen op wil stropen. De kennis was voor mij ook weer helemaal weggezakt en ik ben de boeken in gedoken naar fragmenten die in mijn ogen het beste uitleg boden. Deze stukken heb ik vertaald naar het Nederlands en ik hoop dat er mensen door geïnspireerd raken en met Assembly aan de slag gaan! Deze Assembly introductie behandelt de basis vaardigheden van deze interessante en uitdagende taal.
In het eerste deel van deze cursus, wil ik graag de basis kennis verversen of (nog mooier) vernieuwen! Ik behandel in dit deel de verschillen tussen BASIC, Assembly en Machinetaal. Ook zal ik de onderliggende hardware-componenten kort bespreken en het geheugen van de C64 de revu laten passeren. Omdat ik ergens moet beginnen, en de C64 de meest verkochte computer is, zal de focus van deze cursus op deze computer liggen. Veel van de behandelde onderdelen zijn echter voor meerdere computers uitwisselbaar. Bij het leren van Assembly is het hebben van een gedegen BASIC (of andere programmeerkennis een pré). Ik haal vaak BASIC programma's aan om bepaalde zaken te vergelijken.
BASIC, Assembly of Machinetaal
Dit zijn de drie meest voorkomende (maar niet alle) programmeersmaken voor de Commodore 64. De meesten van ons hebben het eerst kennis gemaakt met BASIC, en velen hebben zich deze taal redelijk eigen gemaakt. En daar is niets mis mee, hij is simpel en doeltreffend. Maar hij is daarnaast ook tergend langzaam vergeleken met Assembly of Machinetaal. (Honderden malen langzamer!) Daarnaast is een BASIC programma eigenlijk een verkapt Machinetaal programma. Want wat gebeurt er als je een BASIC programma start? Dit programma wordt regel voor regel vertaald naar Machinetaal, wat gelijk de reden voor de vertraging is. Sommige BASIC commando's zijn in Machinetaal soms aardig wat regels code. Laten we eens een voorbeeld geven van een programma in alle drie de uitvoeringen:
BASIC | ASSEMBLY | 6502 MACHINETAAL |
|
LDXIM 1 STX 900 LDAIM 2 ADC 900 STA 901 RTS |
|
Bovenstaande versies van hetzelfde programma laten duidelijk zien dat Assembly de tussenstap is tussen BASIC en Machinetaal. De BASIC versie zal voor bijna niemand geheimen vertonen, terwijl de laatste voor bijna niemand (op dit punt) te begrijpen is. Machinetaal is de taal van de computer, maar dermate onmogelijk om te programmeren dat men Assembly als tussenstap ontwikkeld heeft, en gelukkig maar! En terwijl het BASIC hier niet alleen simpeler maar ook kleiner lijkt, kosten Assembly programma's veel minder geheugen! In feite zijn Assembly instructies (zoals LDA, RTS, STX) gewoon mnemonics voor de Machinetaal instructies, om het voor ons dus makkelijker te maken.
Als je naar de Assembly- en Machinetaalregels hierboven kijkt, kun je ook zien dat de instructie LDXIM vertaald wordt in Machinetaal met A2. De waarde "1" in diezelfde regel wordt in Machinetaal "01". Ook de instructie STX laat zich vertalen in "8E" en de waarde "900" wordt in Machinetaal "84 03" (dat is Hexadecimaal voor 900 - geen zorgen, dit komt later aan de orde). Het enige wat ik hiermee duidelijk wil maken is dat elke Assembly-instructie een eigen uniek geheugenadres heeft in Machine taal. Dit laat zien hoe dicht Assembly de daadwerkelijke Machinetaal benadert. Ook kan een Assembly instructie maar één ding. Voor elke handeling zul je een instructie (of commando) moeten gebruiken.
Alle computers bevatten een groot aantal electronische schakelingen (circuits). Deze hebben twee standen: "Aan" of "Uit". Technici zullen je vertellen dat "Aan" meestal betekent dat er stroom staat op de schakeling en "Uit" betekent geen stroom. Er zit op deze digitale schakelingen geen dimensie (een beetje aan of een beetje uit): hij is helemaal "Aan" of helemaal "Uit". Het woord "binair" betekent dan ook "gebasseerd op twee," en alles wat in de computer gebeurt is gebaseerd op twee mogelijkheden: aan of uit. We kunnen deze condities op meerdere manieren verwoorden:
ON of OFF
TRUE of FALSE
YES of NO
1 OF 0
Moeten we BASIC helemaal vergeten?
Het antwoord hierop is volmondig "Nee!" Vaak wordt BASIC en Assembly gezamelijk gebruikt. Assembly voor de snelle rekenkracht gecombineert met BASIC voor het programmeergemak. Je zult merken dat we met het BASIC commando SYS onze Assembly programma's zullen starten. Daarnaast wil ik nog even twee krachtige BASIC commando's aanhalen die direct in het geheugen kunnen kijken (PEEKen) of er wat in kunnen stoppen (POKEn). Deze zullen we gedurende deze cursus vaak tegenkomen en het is belangrijk dat je deze twee broers goed kent.
POKE 53281,0 (Stopt een nul in geheugenadres 53281)
PRINT PEEK(53281) (Toont de inhoud van geheugenadres 53281)
Deze commando's gebruiken we om bepaalde resultaten te kunnen bekijken of beïnvloeden. BASIC zullen we dus gebruiken om onze programma's te starten, bekijken en gedeeltelijk te veranderen tijdens onze experimenten! Een groot voordeel van BASIC is dat je nagenoeg geen kennis nodig hebt van de onderliggende hardwarecomponenten van je C64. Wat er onder de motorkap gebeurt bij het draaien van een BASIC programma zal menig programmeur een zorg wezen, en zo hoort het ook. Bij Assembly ligt dat echter iets anders. Bij Assembly zul je toch een bepaalde basiskennis nodig hebben over het brein van je computer, de 6510 Microprocessor. We zullen de processor moeten leren begrijpen om uiteindelijk zijn taal te kunnen spreken.
De Componenten van de Commodore 64
Onze Commodore bestaat in feite uit vier componenten, die allen met elkaar verbonden zijn, elkaar gebruiken en nodig hebben.
- (Micro)Processor: Dit is de 6510 MOS chip die het brein vormt van de C64. Deze chip stuurt alle andere componenten aan. De PET, CBM en VIC-20 gebruiken een microprocessor chip genaamd 6502. De Commodore B series gebruiken een 6509 chip en de Commodore PLUS/4 gebruikt een 7501. Al deze chips zijn gelijk en er zijn meer chips in dezelfde familie met nummers als 6504; allen werken ze op dezelfde basisprincipes en we zullen naar deze groep refereren als 650x. Hij is verantwoordelijk voor het verplaatsen van data in het geheugen, het uitvoeren van berekeningen en het nemen van beslissingen aan de hand van bepaalde gebeurtenissen (events).
- RAM: Random Access Memory. Dit is het lees- en schrijfgeheugen, waar we de programma's opslaan die we schrijven, samen met de waardes die het programma gebruikt. We kunnen informatie opslaan in RAM, en we mogen informatie ophalen op elk gegeven moment.
- ROM: Read Only Memory. Dit is waar de vaste routines opgeslagen worden in de computer. We mogen niets opslaan in ROM; de inhoud ervan zijn vastgezet toen de ROM gemaakt werd. We zullen wel programma onderdelen (subroutines) gebruiken, die in het ROM opgeslagen zijn, die speciale taken voor ons afhandelen zoals invoer en het tonen van karakters op het scherm.
- IA: Interface Adapter chips. Dit is in feite geen echt geheugen zoals we gewend zijn. Deze chips hebben adressen gekregen voor de adres bus, waardoor we deze chips "memory-mapped" devices noemen. Informatie kan naar deze chips geschreven worden, of eruit gelezen worden, maar de informatie is feitelijk niet in deze chips opgeslagen. IA chips bevatten functies zoals: input/output (I/O) koppelingen (interfaces) die fungeren als verbinding met de echte wereld; timing devices; die de control systemen kunnen en mogen onderbreken en soms gespecialiseerde functies, zoals de video control of geluid creatie. IA chips komen een een breed scala ontwerpen, inclusief de:
- PIA (Peripheral Interface Adapter)
- VIA (Versatile Interface Adapter)
- CIA (Complex Interface Adapter)
- VIC (Video Interface Chip)
- SID (Sound Interface Device)
De Adres Bus
Het is vrij normaal voor een groep chips om samen gebruikt te worden. De lijnen op de printplaat lopen van de ene microchip naar de andere, en van daaruit weer naar de volgende. Als een groep verbindingslijnen op een printplaat samen gebruikt worden om aan verscheidene punten te verbinden spreken we van een Bus (of soms ook Buss genoemd). Zoals eerder gezegd, werken de bovengenoemde componenten samen. Dit samenwerken doen ze via deze bussen.
Laten we eens naar een voorbeeld kijken van een bus gebruikt op de processorchip. Een 650x chip heeft weinig ingebouwde opslagcapaciteit. Om een instructie te krijgen of een berekening uit te voeren moet de 650x informatie oproepen (call) uit het geheugen - data opgeslagen in andere chips. De 650x stuurt een "call" naar alle verbonden geheugenchips met een vraag voor informatie. Hij doet dit door het sturen van voltages op een groep van zestien verbindingen die de "address bus" heet. Elk van deze zestien draden kunnen een voltage bevatten, of niet; deze combinatie van signalen (wel of geen voltage) noemen we een adres.
Elke geheugenchip is verbonden met de address bus. Elke chip leest het adres, de combinaties van voltages gestuurd door de processor. Enkel en alleen één chip zegt, "Dat ben ik!". Met andere woorden, het specifieke adres zorgt ervoor dat de benodigde chip geselecteerd wordt; de chip maakt zich klaar om met de 650x processor te communiceren. Alle andere chips negeren de oproep en zullen niet meedoen in het proces.
De Data Bus
Als de 650x processor eenmaal een adres over de address bus gestuurd heeft en deze herkend is door een geheugenchip, kan data stromen tussen het geheugen en de processor. De data is acht bits; communicatie verloopt dus via acht draden. Het kan er bijvoorbeeld als volgt uitzien:
%01011011
De data kan elke kant op stromen. Ik bedoel, de 650x kan lezen uit de geheugenchip, in zo'n geval plaatst de geheugenchip informatie op de data bus die gelezen wordt door de processor. Aan de andere kant zou de processor kunnen schrijven naar de geheugenchip. In dit geval plaatst de 650x informatie op de data bus, en de geselecteerde chip ontvang deze data en slaat deze op. Als je naar het plaatje in de linkerkantlijn kijkt, zie je dat alle andere chips nog altijd aangesloten zitten op de databus, maar ze zijn niet geselecteerd en daarom negeren zij de informatie. De address bus bevat nog een aantal extra draden (de control bus genoemd) die controle uitvoeren over zulke zaken als data timing en de kant waar de data heenstroomt: lezen of schrijven.
Het Geheugen
Een (geheugen)adres kun je zien als twee delen. Een deel selecteert een specifieke chip. Het andere deel van het adres selecteert een bepaald deel van het geheugen in de chip. Ik snap het, tijd voor een voorbeeld: In onze Commodore 64, zet het adres $D020 (decimaal 53280) de kleur van de rand van ons scherm. Het eerste deel van het adres ($D0) selecteert de video chip (VIC); het laatste deel van het adres (20) selecteert het deel van de chip dat de kleuren beheert. We hebben allemaal wel eens gePOKEd met 53280 of 53281.
Microprocessor Registers
In de 650x chip bestaan verscheidene opslaggebieden die we registers noemen. Hoewel ze informatie bevatten, kunnen we ze niet beschouwen als "geheugen" omdat ze geen adres hebben. Zes van deze registers zijn voor ons van belang, in het kort zijn dit:
- PC (16 bits): De programmateller (counter) vertelt waar de volgende instructie te vinden is. Hij houdt dus de volgende instructie van je programma bij.
- A, X en Y (8 bits): Deze registers bevatten gegevens (data).
- SR: Het Status Register, ook wel PSW (processor status word). Dit register vertelt over het resultaat van recent testen, data bewaring enz. (Hierover later meer)
- SP: De Stack Pointer houdt de tijdelijke geheugen opslagplaatsen bij.
We zullen deze registers later in detail bespreken. Op dit moment is het belangrijk dat je weet dat ze bestaan en we concentreren ons nu op de PC (Program Counter).
Program Counter
In BASIC houdt de computer bij welke regel er aan de beurt is om uitgevoerd te worden. In Assembly gebruiken we hiervoor de Program Counter (PC). In de PC staat het adres van de eerstvolgende instructie die uitgevoerd moet worden. Stel dat de 650x gestopt is (geen gemakkelijke opgave), en dat er een bepaald adres, bijvoorbeeld $1234, in de PC bestaat. Het moment dat we de processor starten, zal dat adres op de adres bus gezet worden om te lezen, en de processor zal de PC met een waarde verhogen. Dus, de inhoud van adres $1234 wordt opgeroepen, en de PC gaat vervolgens naar $1235. Elke informatie die op de data bus terecht komt, zal gelezen worden als een instructie.
De microprocessor heeft nu de instructie, die de processor iets laat doen. De aktie wordt uitgevoerd, en dit hele proces wordt herhaald voor de volgende instructie. Met andere woorden, adres $1235 wordt naar het geheugen gestuurd en de PC zal verhoogd worden naar $1236.
Zoals je misschien al gezien hebt werkt de processor op de zelfde wijze als de meeste programmeertalen: een instructie wordt uitgevoerd, waarna de computer doorgaat met de volgende instructie, en de volgende, enzovoort. We zouden de volgorde van uitvoer kunnen veranderen door middel van een "sprong" of een "vertakking" naar een nieuwe lokatie, maar normaliter is het de ene instructie na de andere.
Data Registers A, X EN Y
Elk van deze registers zou gebruikt kunnen worden om acht bits data te bevatten of te manipuleren. We mogen informatie van het geheugen in A, X of Y laden en we mogen informatie opslaan in het geheugen vanuit elk van de registers A, X of Y. Zowel het laden (Load) als opslaan (Store) zijn kopieer akties. Als je bijvoorbeeld A laadt (LDA) vanuit geheugenadres $2345, maak je in feite een kopie vande inhoud van $2345 in register A; maar $2345 bevat nog steeds de originele waarde. Aan de andere kant, als je de gegevens van register Y in geheugenadres $3456 opslaat (STY), vul je het geheugen op $3456 met de inhoud van het register Y. Ditmaal blijft Y onveranderd.
De processor is echter niet in staat om informatie direct van het ene geheugenadres naar het andere te verplaatsen. Daarom moet deze informatie via de A, X of Y registers verlopen; we laden de informatie van het oude adres en slaan het op in het nieuwe adres. Verderop zullen deze drie registers geheel eigen identiteiten aannemen. Het A register wordt de accumulator genoemd omdat we daarmee optellen en aftrekken. Voor dit moment beschouwen we ze als uitwisselbaar; we mogen elke van deze registers vullen of lezen.
Werken met Nummers en Getallenstelsels
Het is niet het meeste leuke onderdeel (voor velen zelfs het saaiste) van het werken met machinetaal, maar we komen er niet onder uit. Het is erg handig dat je weet hoe de C64 werkt, met processor, adresbus, registers enz. Maar het is helaas noodzakelijk dat je de verschillen tussen de getallenstelsels Binair, Hexadecimaal en Decimaal kent. Dit is nodig voor de geheugenverwijzingen die we gaan maken. In BASIC zeg je gewoon dat een variabele een waarde krijgt (LET A=10). In Machinetaal moet je daarnaast precies vertellen waar je deze waarde in het geheugen wegstopt. Daarnaast is het nodig dat je weet waar het over gaat als we het in deze cursus bijvoorbeeld hebben over %01101001 of $D502.
Decimaal
Laten we met de makkelijkste beginnen, het bij iedereen bekende decimale stelsel. Ontstaan omdat wij mensen (de meeste dan) aan ons lijf tien vingers hebben. Met dit stelsel kun je voorbij tien tellen maar ook lager dan 1. In dit systeem zijn de getallen aan de linkerkant tien maal groter dan de getallen aan de rechterkant. Neem het nummer 66. De eerste zes heeft een waarde die tien keer groter is dan de tweede zes. Het decimale systeem noemen we het plaats-waarde getalstelsel. Tien is in dit stelsel het zogenaamde grondtal. Andere stelsel hebben een ander grondtal, maar volgen dit zelfde patroon als het decimaile stelsel, dat wil zeggen de plaatsen hebben naar links toe een waarde die steeds het grondtal keer zo groot is.
Binair
De computer werkt voornamelijk elektronisch en werkt daarom beter als hij maar twee verschillende toestanden hoeft te herkennen, namelijk aan of uit, of "1" of "0", en gebruikt daarom het Binaire stelsel met grondtal van 2. Omdat je 6510 processor een 8bit systeem is, kan hij maximaal acht posities bevatten. In dit stelsel bestaat ieder getal uit nul-len en één-en, of electronisch nul volt (uit) en een paar volt (aan). Om verder te tellen dan 1 past het binaire stelsel ook de plaatswaardenotie toe, waarbij de vermenigvuldigingsfactor het grondtal 2 is. Het getal 101 representeert binair 4 + 0 + 1 = 5. (zie kantlijn). Je snapt gelijk de verwarring met bijvoorbeeld het cijfer 101. Bedoelen we nu het decimale honderdenéén of het binaire 5? Vandaar de notatie %101 voor binaire getallen. De Commodore 64 gebruikt acht-bits registers of geheugenplaatsen en kunnen dus binair getallen verwerken tot %11111111. (Dat zijn dus opgeteld decimaal 255 - zie kantlijn). En op deze manier kunnen we dus een binair getal omrekenen naar decimaal. Nog één voorbeeld, we zetten het binaire getal %10100111 om naar decimaal.
Dat is dus (1x1)+(1x2)+(1x4)+(0x8)+(0x16)+(1x32)+(0x64)+(0x128) = decimaal 167
Hexadecimaal
Hoewel het binaire stelsel erg praktisch is voor de computer, is het voor ons mensen niet zo makkelijk te begrijpen. Ook worden we binair gehandicapt omdat we niet verder dan %11111111 (decimaal 255) kunnen. Dus is er naar een compromis gezocht. En een die gelijk met zestien getallen kan werken, in plaats van de binaire acht!
Programmeurs hebben een oplossing gevonden met een systeem wat nummert van $0 tot $FF. Je ziet gelijk dat dit hexadecimale stelsel aanteduid wordt met een "$" teken. Laten we kijken hoe we met dit systeem werken door een 8bit nummer (%01010101) in twee delen op te splitsen die men 'nibbles' genoemd heeft.
%0101 en 0101
Laten we vervolgens kijken hoe veel we nogmaals de drie stelsels naast elkaar zetten in de kantlijn. Als we nu bij de Hexadecimale waardes de drie (overbodige) nullen weglaten, houden we het volgende over:
Binair | Hexadecimaal |
%0000 |
$0 |
%0001 |
$1 |
%0010 |
$2 |
%0011 |
$3 |
%0100 |
$4 |
%0101 |
$5 |
%0110 |
$6 |
%0111 |
$7 |
%1000 |
$8 |
%1001 |
$9 |
%1010 |
$A |
%1011 |
$B |
%1100 |
$C |
%1101 |
$D |
%1110 |
$E |
%1111 |
$F |
Zoals je nu al kunt zien kan een Hexadecimaal systeem een hele nibble met één enkel getal aanduiden! Om binair %10101010 aan te duiden volstaat Hexadecimaal dus met $AA. Omdat een 8bit groepering, ookwel byte genoemd, bestaat uit twee nibbles, kunnen we zien wat de maximale getalgrootte is, dat een hexidecimaal getal in een enkele byte kan proppen:
%1111 1111 = %11111111
$F $F = $FF
$FFFF = 65535
Zoals eerder vermeld, zitten er meer dan 64000 bytes in onze Commodore 64. Een getalstelsel dat bijhoudt wat in elke byte van de computer aanwezig is met slechts twee getallen is efficienter dan een systeem dat daar vijf getallen voor nodig heeft - zoals het decimale stelsel.
Ik kan me heel goed voorstellen dat je niet alles begrijpt wat ik over de getallenstelsels e.d. verteld heb. Wees gerust, dat hoeft op dit punt ook nog niet! Dit komt vanzelf met ervaring. Het gaat erom dat je snapt dat je het voordeel inzit van het Hexadecimale stelsel boven het binaire en decimale. Er zijn boeken die uitvoerig ingaan op het omzetten van deze getallen. Ik zie daar het nut niet van in omdat je dit op een rekenmachine, vele internetsites of zelfs in je assembler programma (vaak automatisch) kunt doen. Als je een tijdje gewerkt hebt met Hexidecimale en Decimale nummers wordt het vanzelf makkelijker.
Even Herhalen...
De C64 bevat 64 kilobytes. Een kilobyte is 1024 bytes Dus dit betekent dat onze computer een RAM geheugen heeft van 64 x 1024 bytes. De processor vertaalt de voltages op verschillende componenten als AAN of UIT. Deze AAN/UIT waarde wordt opgeslagen in een 8bit getal - een byte genaamd. Deze byte wordt aangeduid door een twee-getallig Hexadecimaal nummer.
Ik heb in dit deel de basis er erg snel doorheen gejaagd. Het gaat er om dat je de terminologie een keer gezien hebt, je in grote lijnen weet hoe de Commodore 64 werkt, en begrijpt waarom het Hexadecimale stelsel in het leven geroepen is. In het volgende deel zal ga ik sommige onderdelen rustig herhalen en voorzien van simpele Assembly voorbeelden. De kennis die ik hier met jullie deel, komt uit verschillende boeken. Ik gebruik er een stuk of tien door elkaar heen, omdat deze boeken allen hun sterke en zwakke kanten hebben. Het is dus niet mijn verhaal, maar een eervolle samenraping van de in mijn ogen beste delen van verschillende boeken. Ik hoop dat jullie met mij meegaan op deze reis en het Assembly eigen willen maken. Mocht je vragen hebben, kun je me altijd mailen op info@retroguys.nl. Alle mailtjes zullen beantwoord worden en op- en/of aanmerkingen zijn tevens van harte welkom. Tot het volgende deel waar we de registers uitvoerig zullen behandelen!