Assembly Cursus 5

CADitmaal behandelen we twee manieren om door een programma te springen. De equivalenten van BASIC’s GOTO en GOSUB…RETURN passeren de revu. Deze zullen vaak gebruikt gaan worden en het is daarom essentieel dat je ze goed kent en begrijpt…

CursusAssembly Deel1 hdr

Computer: Commodore 64ACDatum: 2012
Type: Programmeertaal
Door: Addy van Ladesteijn

Sprongen JMP & JSR
Als je programma’s schrijft, ontkom je er niet aan om zo af en toe een sprong terug of naar voren te maken. Vergelijkbaar met BASIC heeft Assembly deze natuurlijk ook.  Maar Assembly heeft ook een eigen register dat bijhoudt welke regel momenteel wordt uitgevoerd. Na dit deel zul je dus naast de reeds bekende A, Y en X registers ook kennis gemaakt hebben met het programcounter (PC) register!

Er zijn maar weinig programma’s die alle opdrachten van voren naar achteren afwerken zonder ergens te springen of te vertakken. In dit deel behandelen we twee technieken van verspringen. Ook het register dat die de sprongen controleert en bijhoudt laten we kort de revu passeren.

Onvoorwaardelijke sprongen
Een onvoorwaardelijke sprong doet precies wat zijn naam zegt; spring naar een ander deel van het programma zonder verder ergens naar te kijken. Er zijn dus geen voorwaarden aan verbonden. Sprongen zijn met name handig om bepaalde stukken code te hergebruiken. Door bijvoorbeeld een stuk muziek wat in het geheugen staat op verschillende momenten aan te kunnen roepen. Er bestaan slechts twee onvoorwaardelijke 6510 opdrachten:

JMP - JuMP
JSR - Jump naar SubRoutine

BASIC vertaling van GOTO = JMP
In BASIC kon je met behulp van het GOTO commando door je hele programma heen springen, naar voren en naar achteren. In Assembly gebruiken we hier JMP voor. We kunnen het JMP commando het beste demonstreren aan de hand van een voorbeeld (startadres is 828):

OPCODE OPERAND
LDAIM #1 (Laad 1 in register A)
JMP 834 (Spring naar geheugenadres 834)
RTS (Spring terug - in dit geval terug naar BASIC)
STA 1024 (Zet de inhoud van A op adres 1024)
STA 55296 (Zet de inhoud van A op adres 55296)
JMP 833 (Spring naar geheugenadres 833)
END

Wat hier gebeurt is dat er direkt naar de STA 1024 gesprongen wordt, over de RTS heen. Bovenstaande voorbeeld is natuurlijk weinig zinvol, maar dient louter ter demonstratie van het JMP commando. Het zou bijvoorbeeld gebruikt kunnen worden om later een stukje coding “in te voegen”. Vergeet niet dat het voor de snelheid niet uitmaakt waar je regels staan. Je kunt zonder snelheidsverlies van hot naar hen springen. Bovendien waren de editors vroeger niet zo gebruikersvriendelijk, en het was vaak makkelijker/sneller om achteraf een sprong te maken naar extra coding in plaats van coding tussen te voegen.
ACZoals je aan dit schema hiernaast kunt zien is dat precies wat we met het voorbeeld aantonen. Dit (stroom)schema is een grafische vertaling van het Assembly programma hierboven. Dit laat duidelijk zien hoe het verloop van de code is.

Het programma drukt overigens een witte “A” af in de linker bovenhoek, maar na Deel 4 had je dat natuurlijk al zelf gezien.

Als je dus het JMP commando gebruikt, zul je precies moeten vertellen waarheen er gesprongen moet worden, er moet een adres opgegeven worden. Ook om weer terug te komen op de originele plek zul je dus een JMP commando moeten gebruiken. Het heen springen zal mischien niet zo schokkend zijn, maar hoe kom je nu weer precies terug op de regel na je originele sprong? Met andere woorden, hoe weet je dat je naar geheugenadres 833 moet springen zoals in ons voorbeeld?

Adressen berekenen
Alle OPCODES hebben een bepaalde grote, ze nemen precies 1 byte in beslag. De OPERAND neemt ook geheugen in beslag, alleen de bepaling daarvan is iets ingewikkelder. Dit is een kwestie van weten of opzoeken, raak dus niet in paniek. Als je een geheugenadres in een OPERAND gebruikt (zoals bij JMP en STA) kost dat 2 bytes extra omdat een geheugenadres getallen kan bevatten die groter zijn dan de 255 die in één byte passen. (1 byte = 8 bits en kan maximaal de waarde 255 bevatten weten we uit Deel 1) Ons programma begint op geheugenadres 828. Voor het gemak zullen we de geheugen adressen voor de coding zetten:

ADRES OPCODE OPERAND GEBRUIKT
828 LDAIM #1    (828 plus 1 byte voor "#1")               828 t/m 829
830 JMP 834     (830 plus 2 bytes voor het adres "834")   830 t/m 832
833 RTS         (geen operant, dus geen extra bytes)      833 t/m 833
834 STA 1024    (834 plus 2 bytes voor het adres "1024")  834 t/m 836
837 STA 55296   (837 plus 2 bytes voor het adres "55296") 837 t/m 839
840 JMP 833     (840 plus 2 bytes voor het adres "833")   840 t/m 842
END

Om dus over (voorbij) RTS te springen, springen we van geheugenadres 830 naar 834. We springen hierbij dus over adres 833 waar RTS staat. Zoals eerder gezegd, gewoon opzoeken, zo zijn we allemaal begonnen. Geloof het of niet, na een tijdje wordt het tweede natuur. Ik wil bovenstaande ook nog in een Assembly Machinetaal vergelijking gieten:

ADRES ASSEMBLY MACHINETAAL
828          LDAIM #1         A9 01
830          JMP   834        4C 42 03
833          RTS              60
834          STA   1024       8D 00 04
837          STA   55296      8D 00 D8
840          JMP   833        4C 41 03

Als je onder het kopje Machinetaal kijkt, bevat de eerste kolom de OPCODE, de tweede en derde kolom bevat de OPERANT. In machinetaal kun je duidelijk zien dat “LDAIM #1” twee bytes in beslag neemt (adres 828 en 829) en “JMP 834” drie bytes (830, 831 en 832) in beslag neemt. Als je naar de Machinetaal listing kijkt, zie je overigens ook dat ons gehele programma 15 bytes in beslag neemt.

BASIC vertaling van GOSUB…RETURN = JSR…RTS
De BASIC kenners onder ons kunnen zich nog een sprong herinneren. Naast de GOTO kennen we namenlijk ook nog een GOSUB … RETURN opdracht in BASIC. In Assembly gebruiken we hiervoor JSR…RTS. Het grote voordeel is dat je bij het terugspringen niet een adres hoeft op te geven (en berekenen), de RTS maakt gebruik van de verderop besproken programmateller om te bepalen waar naartoe teruggesprongen moet worden.

We maken weer gebruik van hetzelfde voorbeeld, maar ditmaal gebruiken we JSR en RTS.

OPCODE OPERAND
LDAIM #1   (Laad 1 in register A)
JSR 834    (Spring naar geheugenadres 834)
RTS        (Spring terug - in dit geval terug naar BASIC)
STA 1024   (Zet de inhoud van A op adres 1024)
STA 55296  (Zet de inhoud van A op adres 55296)
RTS        (Spring terug naar de regel na JSR 834 - in dit geval RTS)
END

ACHiernaast laten we bovenstaande nog even in een programma stroomschema zien. De JSR instructie biedt dus de mogelijkheid om naar kleine stukjes programma’s (subroutines) te springen vanuit je hoofd programma. De RTS instructie springt telkens uit de huidige subroutine terug naar de bovenliggende programma vanwaar het werd aangeroepen.

Gebruik je RTS in je hoofdprogramma, dan snapt de 6510 processor dat je terug wilt springen naar BASIC.

Oplettende lezers zien trouwens ook dat ons JSR…RTS voorbeeld slechts 13 bytes groot is (2 bytes kleiner dan ons JMP voorbeeld) omdat het RTS commando geen OPERAND code nodig heeft voor een adres van twee bytes groot.

De Programmateller
Zoals we bij het laatste voorbeeld zagen, wist de processor zelf naar waar hij terug moest springen bij de RTS instructie. Dit betekent dus, dat de computer ergens bijhoudt op welke plek hij in een programma is. Dit is het moment waarop we de programmateller (programcounter of PC) uit de kast trekken.

De PC is een 16-bits register dat het adres bevat van het volgende commando dat moet worden uitgevoerd. Iedere keer als de PC een byte uit het geheugen haalt wordt hij met 1 verhoogd. Op deze manier blijft hij altijd naar de volgende geheugenlocatie met de bijbehorende instructie wijzen. Maak je geen zorgen, een voorbeeld zal dit verhaal verduidelijken. Ons programma begon op adres 828.

OPCODE OPERAND PC voor instructie PC na instructie
828 LDAIM #1   828 830 830
JSR            834 830 834 ...

De PC weet dus altijd waar je bent in je programma. De methode die de PC gebruikt noemen we stapelen, waar we later bij het bespreken van het stapelgeheugen (STACKs) op terugkomen. Wat belangrijk is om voor nu te begrijpen, is (1) dat het PC register bestaat en (2) dat het dient om tijdens de uitvoering van je programma bij te houden op welke “regel” (eigenlijk adres) het programma zich bevindt.

Kort samenvattend is het duidelijk dat de nummering die je bij de BASIC programma’s gebruikt geenzins overeenkomt met de nummering in Assembly. Waar je in BASIC gewoon nummers kunt verzinnen, moet je bij Assembly bewust zijn van het feit dat dit adressen zijn. Later, als we Macro’s gaan gebruiken wordt dit wat makkelijker. We kennen nu twee manieren om door het programma te springen. De JMP gebruik je in feite om voorbij stukken programma te springen, terwijl je de JSR…RTS combinatie gebruikt om een tijdelijk “uitstapje” te maken met het voornemen later weer verder te gaan waar je gebleven was. Het programmateller register houdt voor ons bij welke regel er uitgevoerd gaat worden om ons wat denkwerk te besparen.