CODINGROM ★ Alle Wege führen ins ROM ★

Alle Wege führen ins ROM (CPC Amstrad International)
★ Ce texte vous est présenté dans sa version originale ★ 
 ★ This text is presented to you in its original version ★ 
 ★ Este texto se presenta en su versión original ★ 
 ★ Dieser Text wird in seiner Originalfassung präsentiert ★ 

Einsteigen ohne Probleme

Nichts geht im CPC ohne das Betriebssystem. Diese 16 KByte umfassende Sammlung von Unterprogrammen in Maschinensprache hat insbesondere die Aufgabe, den Kontakt zu den Peripheriegeräten herzustellen, also zum Bildschirm, zum Drucker, zur Tastatur, zum Soundchip und so weiter. Dem Programmierer bleibt es dadurch erspart, sich spezielle Hardware-Kenntnisse anzueignen. Will er ein Zeichen an den Drucker ausgeben, so ruft er einfach die entsprechende Betriebssystem-Routine auf, ohne sich Gedanken um irgendwelche Portadressen zu machen.

Auch der BASIC-Interpreter benutzt das Betriebssystem als Arbeitsgrundlage. Bei einem DRAW-Kommando ist es zwar die Aufgabe des CPC-BASICs, dieses Kommando richtig zu interpretieren und die nachfolgenden Parameter zu lesen. Diese werden dann jedoch einer Betriebssystem-Routine übergeben, die für die Ausführung zuständig ist. Im CPC herrscht also eine genau definierte Arbeitsteilung.

I can hear you callin'...

Von BASIC aus kann man die Betriebs-system-Routinen per CALL-Befehl aufrufen. Probieren Sie z.B. einmal CALL &BB18. Danach herrscht zunächst Sendepause, der Cursor bleibt verschwunden. Wenn Sie eine beliebige Taste drücken, erscheint er jedoch wieder: Das war nämlich eine Routine, die auf einen beliebigen Tastendruck wartet. Dieser CALL ersetzt also die übliche BASIC-Konstruktion

WHILE INKEY$ = "" : WEND

Oder angenommen, Sie haben etwas zu wüst mit den Farben herumgespielt und sind schließlich bei blauer Schrift auf blauem Hintergrund gelandet. Was nun? Probieren Sie einfach mal im Blindflug CALL &BC02, wodurch die INK-Farben wieder auf die Startwerte zurückgesetzt werden. Und hier ein Special für CPC 464-Besitzer: Mit CALL &BC6E können Sie den Kassettenmotor ein- und mit CALL &BC71 wieder ausschalten, wenn die PLAY-Taste gedrückt ist.

Nützlich ist weiterhin CALL &BB81, um während eines laufenden Programms den Cursor erscheinen zu lassen. Die Abfrage der Cursortasten und die Steuerung mit LOCATE müssen Sie jedoch selbst programmieren, da sich das BASIC während einer Pro-grammausführung nicht im Eingabemodus befindet. Mit CALL &BB84 wird der Cursor wieder ausgeschaltet. Wenn Sie sich noch an die in Heft 2/88 erläuterte Speicheraufteilung des CPC erinnern, sollten Sie allerdings bei den Adressen stutzig werden, die wir hier benutzen. Das Betriebssystem befindet sich im unteren ROM, also im Adreß-bereich von &0000 bis &3FFF. Unsere CALL-Adressen liegen jedoch ganz woanders, nämlich zwischen &BB00 und &BDFF im RAM des CPC.

Immer auf dem Sprung

In der Tat haben wir mit unseren CALLs nicht das Betriebssystem direkt aufgerufen, sondern sogenannte 'Sprungvektoren'. Diese können Sie sich als eine Ansammlung spezieller GOTO-Befehle vorstellen, die dann den eigentlichen Sprung ins Betriebssystem ausführen. Dieser Umweg ist aus zwei wichtigen Gründen notwendig. Erstens ist zum Zeitpunkt des Aufrufs das untere ROM noch durch das im gleichen Adreßbereich liegende RAM verdeckt und muß erst aktiviert werden, was durch eine sehr raffinierte Programmierung dieser Sprungbefehle erreicht wird. Zweitens mußten die Entwickler des CPC eventuelle Änderungen im Betriebssystem berücksichtigen. Angenommen, es wird ein neuer Programmteil eingebaut (wie etwa die FILL-Routine beim 664/6128), so verschieben sich alle nachfolgenden Einsprungadressen. Programme, die direkt das ROM aufrufen, würden dann hoffnungslos baden gehen, da keine Adresse mehr stimmt! Mit den Sprungvektoren ist das jedoch kein Problem. Ihre Lage bleibt in allen CPC-Versio-nen unverändert, nur das Sprungziel dieser 'Spezial-GOTOs' ist unterschiedlich. Auf diese Weise läßt sich übrigens auch das Diskettenlaufwerk nachträglich in den CPC 464 integrieren: Alle Sprungvektoren für den Kassettenbetrieb werden auf das Floppy-ROM umgebogen. Dadurch kann man testen, in welcher Konfiguration der Rechner betrieben wird:

IF PEEK(&BC77)=223 THEN PRINT “Disk“ ELSE PRINT “Cassette"

Wer nun allerdings glaubt, daß sich über die Betriebssystem-Einsprünge noch zahlreiche bislang unbekannte Möglichkeiten aus dem Rechner hervorzaubern lassen, wird eine Enttäuschung erleben. Das Locomotive-BA-SIC nutzt das Betriebssystem nämlich sehr gut aus, die meisten Funktionen stehen bereits als BASIC-Befehle zur Verfügung. Weiterhin erfordern die meisten CALLs noch zusätzlich die Übergabe von Zahlenwerten (Parameter) , was in BASIC nicht ohne weiteres realisierbar ist.

Interne Register...

Um zu verstehen, wie diese Parameterübergabe vor sich geht, müssen wir uns etwas mit dem Innenleben des Z 80 beschäftigen. Neben den 64 KByte RAM des CPC verfügt der Prozessor nämlich noch über ein paar interne Speicherstellen, die seinen Arbeitsplatz darstellen. Ein typischer Arbeitszyklus sieht mb allgemeinen so aus:

  1. Lade irgendwelche Werte aus dem Speicher in die internen Register.
  2. Mache etwas damit.
  3. Schreibe die Ergebnisse in den Speicher zurück.

Die Register können entweder 8-Bit oder 16-Bit-Werte aufnehmen und werden mit den Buchstaben A...F, H. L, SP, IX und IY bezeichnet. Einige haben dabei eine sehr spezielle Bedeutung:

Das A-Register (auch Akku oder Akkumulator genannt) ist 8 Bit breit und nimmt bevorzugt Werte auf, mit denen irgendwelche Rechenoperationen durchgeführt werden sollen. Im F-Re-gister (Flagregister) stehen dagegen keine Zahlenwerte. Hier hat jedes einzelne Bit eine besondere Bedeutung und signalisiert, was bei der vorhergehenden Operation passiert ist. Ein bestimmtes Bit ist z.B. immer dann gesetzt, wenn das Ergebnis einer Operation gleich Null ist (Zero-Flag), ein anderes, wenn sich beim Rechnen ein Übertrag ergeben hat (Carry-Flag). In Maschinensprache kann man die 'Flaggensignale' abfragen und im Programmablauf entsprechend reagieren.

Die 8-Bit-Register B,C,D,E,H und L finden als allgemeine Datenregister Verwendung, können jedoch bei bestimmten Maschinenbefehlen auch spezielle Aufgaben übernehmen. So dient das B-Register z.B. als Zähler bei Programmschleifen, ähnlich wie die Laufvariable beim FOR...NEXT in BASIC. Falls man auf Maschinenebene mit Zahlen arbeitet, die den 8-Bit-Bereich überschreiten, so kann man diese Register paarweise zu den Doppelregistern BC, DE und HL zusammenfassen. Insbesondere das HL-Regi-ster steht dann als eine Art 16-Bit-Akkumulator für Rechenoperationen zur Verfügung, wobei das höherwertige Byte ('Highbyte') in H und das niederwertige Byte ('Lowbyte') in L steht — deshalb ist es auch mit diesen beiden Buchstaben bezeichnet worden.

Die Register SP (StackPointer), IX und IY (Indexregister) sind 16 Bit breit und enthalten bevorzugt irgendwelche Speicheradressen. Ihre Bedeutung wird in den kommenden Folgen noch genau erklärt werden. Wie Sie aus diesen Angaben entnehmen können, ist der Z 80 also schon ein halber 16-Bit-Prozessor. Ganz hat er diesen Rang allerdings nicht verdient, da der Datentransfer zwischen den Registern und dem Speicher immer nur in 8-Bit-Portionen stattfindet.
...und wie man an sie herankommt

Das Problem bei den Betriebssystem-CALLs ist nun, daß die zu übergebenden Werte in bestimmten internen Registern erwartet werden, und diese lassen sich von BASIC aus nicht ohne weiteres erreichen. Damit Sie trotzdem etwas Erfahrung mit diesen Betriebssystem-Aufrufen und den Prozessor-Registern sammeln können, möchten wir Ihnen in dieser Folge mit Listing 1 ein kleines Werkzeug zur Verfügung stellen. Es besteht aus zwei Unterprogrammen, die in eigene Programme integriert werden können und das Laden der Register direkt von BASIC aus erlauben.

Natürlich geht das nicht ohne etwas Maschinencode. Das Unterprogramm ab Zeile 20000 stellt deshalb einen typischen BASIC-Lader dar, der den Code ab Adresse &A000 im Speicher ablegt. Die Festlegung der Startadresse in Zeile 20040 kann bei Bedarf geändert werden, da das Maschinenprogramm relokatibel ist, sich also frei im Speicher verschieben läßt.

Das Unterprogramm ab Zeile 10000 dient dazu, ein beliebiges Maschinenprogramm aufzurufen (also auch eine Betriebssystem-Routine) und vorher die Prozessorregister mit bestimmten Werten zu versorgen. Dabei wird eine sehr spezielle Möglichkeit des CPC-BASICs benutzt, mit der man die Adresse einer Variablen im Speicher ermitteln kann. Das geschieht, indem man dem Variablennamen das Zeichen voranstellt. Probieren Sie einfach mal folgendes:

var% = 0: PRINT @var%

Der Variablen var wird hier der Wert Null zugewiesen, damit sie überhaupt erst einmal im Speicher existiert. Wendet man diese sogenannte 'Variablen-pointer-Funktion' auf eine noch nicht definierte Variable an, so erhält man nur die Fehlermeldung 'lmproper Argument'. Mitunter ist es sehr von Vorteil, die Adresse einer Variablen im Speicher zu kennen, da man dann direkt mit PEEK oder POKE auf sie zugreifen kann, wie das folgende Beispiel demonstriert:

var%=0: POKE @var%,77: PRINT var%

Das Prozent-Zeichen hinter der Variablen sorgt dafür, daß der CPC sie als Ganzzahl-Variable (Integer) behandelt, da die Struktur einer Fließkommavariablen ziemlich kompliziert und für unsere Zwecke ungeeignet ist. Integer-Variablen sind dagegen sehr einfach aufgebaut. Sie belegen zwei Byte (also 16 Bit) im Speicher, und zwar zuerst das
Lowbyte (der niederwertige Teil) und dann das Highbyte an der darauffolgenden Speicheradresse. Um den Variableninhalt komplett auszulesen, müßte man also z.B. so vorgehen:

var%=456:
PRINT PEEK(@var%)+256*PEEK (@var%+1)

Der Inhalt des Highbytes ist wegen der internen Zahlendarstellung also 256-mal so viel wert wie das Lowbyte. Natürlich ist es viel einfacher, sich eine Variable direkt mit PRINT ausgeben zu lassen. Diese Beispiele sollen auch nur das Prinzip demonstrieren. Will man sich Low- und Highbyte jedoch separat anschauen oder sogar die Variable von einem Maschinenprogramm aus verwenden, so ist der Weg über die Variablenadresse außerordentlich praktisch. Nun zurück zu dem Unterprogramm ab Zeile 10000: Vor dem Aufruf müssen Sie einige Integer-Variablen mit den Werten belegen, die in die Prozessor-Register übertragen werden sollen. Die Variablennamen wurden in Analogie zu den Registerbezeichnungen gewählt: a% entspricht dem A-Register, flags% spricht für sich selbst, bc%, de% und hl% sind die bereits erwähnten Registerpaare und ix % bzw. iy % die beiden Indexregister.

Daß wir hier außer dem Akku die Register gleich paarweise ansprechen, liegt darin begründet, daß die BASIC-Integervariablen ebenfalls 16 Bit breit sind. Will man die Register B bis L einzeln beschicken, so muß man Low- und Highbyte der Variablen getrennt ansprechen. Der erste Buchstabe entspricht immer dem Highbyte. Möchte man also das B-Register mit dem Wert 20 belegen, so geht das einfach mit bc% = 256*20. Das kann jedoch Ärger geben, wenn der Wert größer als 127 ist, weil wir dann auch das Bit ansprechen, das normalerweise für das Vorzeichen reserviert ist, und damit einen Over-flow-Error provozieren. Helfen kann man sich jedoch mit der UNT-Funk-tion, die diese Sperre aufhebt: bc% = UNT(256*180) wäre z.B. in einem solchen Fall das Gegebene.


Einige Calls nehmen dem Programmierer einiges an zusätzlicher Arbeit ab.

Etwas Grafik gefällig?

Neben der Registerbelegung braucht unser Unterprogramm allerdings auch noch die Adresse des aufzurutenden Maschinenprogramms. Dazu dient die Variable adr%. Probieren wir es gleich anhand eines Beispiels aus, und zwar mit der Betriebssystem-Routine, die für das Füllen eines rechteckigen Bildschirmbereiches mit einer Farbe zuständig ist. Aufgerufen wird sie mit CALL &BC44 und verlangt folgende Werte in den Registern:

H: Linke Grenze der Box L: obere Grenze D: rechte Grenze E: untere Grenze A: Farb-Füllbyte Die Grenzen werden in Textkoordinaten angegeben, ähnlich wie beim WINDOW-Befehl. Das Farb-Füllbyte entspricht allerdings nicht den gewohnten PEN-oder INK-Nummern, sondern ist ein spezielles Bitmuster, das direkt in den Bildschirmspeicher befördert wird. Wie sich das auswirkt, können Sie gleich selbst experimentell herausfinden; es erlaubt Effekte, die ansonsten in BASIC nur sehr umständlich zu erreichen wären. Wenn Sie das Listing mit den beiden Unterprogrammen bereits abgetippt haben, so können Sie jetzt folgende Zeilen hinzufügen:

10 GOSUB 20000: REM Initialisierung
20 REM Linke und obere Grenze:
30 hl% = 256 * 5 + 5
40 REM Rechte und untere Grenze:
50 de% = 256 * 10 + 10
60 REM Das Farb-Füllbyte:
70 a% = 100
80 REM Und jetzt die Aufrufadresse:
90 adr% = &BC44
100 GOSUB 10000:REM Alles abschicken
110 END

Wenn Sie dieses Programm starten (bitte vorsichtshalber erst abspeichern!), sollte sich ein Rechteck mit einem aparten Streifenmuster auf dem Bildschirm zeigen, und zwar mit der linken oberen Ecke bei (5,5) und der rechten unteren Ecke bei (10,10) in Textkoordinaten. Der Wirkung des Farb-Füllbytes kommen Sie am ehesten auf die Spur, wenn Sie mit diesem Programm in MODE 2 arbeiten und den 8-Bit-Wert binär angeben, also etwa so:

70 a% = &X11001100 In diesem MODE entspricht nämlich jedes gesetzte Bit einem Bildpunkt in der Vordergrundfarbe. In MODE 1 und 0 wird die Angelegenheit durch die verschiedenen Farben dagegen sehr kompliziert, so daß wir weitere Erklärungen auf eine spätere Folge verschieben müssen. Probieren Sie aber einfach etwas herum und verändern Sie die Koordinaten und das Farb-Byte. Besonders in MODE 0 können Sie auf diese Weise interessante Farbeffekte erzeugen. Beispielsweise könnte man auch die Zeilen 20 bis 100 in eine Programmschleife einpacken, in der die Parameter für die Prozessor-Register laufend verändert werden und ständig neue Muster erzeugen - doch das bleibt Ihrer Fantasie überlassen!

Mit diesem Anwendungsbeispiel vor Augen läßt sich nun leicht überblicken, wie das Unterprogramm ab Zeile 10000 aufgebaut ist. Die Zeilen 10050 bis 10060 überprüfen zur Sicherheit, ob das Maschinenprogramm korrekt initialisiert und eine Aufrufadresse angegeben wurde, damit nicht aus Versehen ein Reset des Rechners ausgelöst wird und alle eingetippten Zeilen verlorengehen. Wenn alles korrekt läuft, können Sie diese Zeilen entfernen, da sie ansonsten nur den Betrieb aufhalten. In den Zeilen 10070 bis 10080 wird mit Hilfe der Adreßfunktion der Inhalt der Variablen adr% direkt in das Maschinenprogramm hineingePOKEd. Wenn Sie einen Blick auf den BASIC-Lader werfen, so finden Sie in der Zeile 20190 die Code-Kombination CD 00 00. Der hexadezimale Code CD steht für den Maschinenbefehl CALL, der die gleiche Wirkung wie in BASIC hat. Die nachfolgenden zwei Bytes geben immer die Sprungadresse an. Natürlich bleibt es hier nicht bei den beiden Nullbytes. Genau an dieser Stelle wird närrüich nachträglich die von Ihnen angegebene Aufrufadresse eingesetzt.

Die Zeile 10090 macht auf den ersten Blick einen sehr unsinnigen Eindruck. Sie stellt jedoch sicher, daß alle in der nächsten Zeile benutzten Variablenadressen bereits existieren, um ein 'lm-proper Argument' zu verhindern. Damit bleibt es Ihnen erspart, vor dem Aufruf des Unterprogramms 10000 alle Register-Variablen mit Werten vorzubelegen, auch wenn Sie sie gar nicht benötigen. In der Zeile 10100 wird schließlich von der Möglichkeit Gebrauch gemacht, an den CALL-Befehl eine Reihe Parameter anzuhängen, die dann auf Maschinenebene ausgewertet werden können. Auf diese Weise erfährt das Maschinenprogramm die Adressen der Variablen, so daß es deren Inhalt wie gefordert in die Prozessorre-gister einsortieren kann.

Zunächst scheint nicht ganz klar, warum hier der Umweg über die Variablenadressen gewählt wurde. Wäre es nicht direkter und einfacher gewesen, sofort den Inhalt der Variablen dem Maschinenprogramm zu übergeben? Im Prinzip schon — doch dann wäre eine weitere interessante Möglichkeit verlorengegangen. Unser Maschinenprogramm kann nämlich nicht nur die Prozessorregister mit dem Inhalt der BASIC-Variablen laden, sondern auch nach der Ausführung des gewünschten CALLs die Registerbelegung wieder dorthin zurückschreiben. Und dazu muß es natürlich wissen, wo sich die Variablen befinden.

Dazu gleich wieder ein Beispiel. Angenommen, Sie wollen in einem BASIC-Programm abfragen, welcher MODE zur Zeit eingeschaltet ist. Da Ihr Programm nicht einfach einen Blick auf den Bildschirm werfen kann, um dies festzustellen, ist hier guter Rat zunächst teuer. Es existiert jedoch im Betriebssystem ein kleines Unterprogramm, das nach dem Aufruf über die Adresse &BC11 den aktuellen MODE im A-Register zurückgibt. Ergänzen Sie die beiden Unterprogramme einfach durch folgende Zeilen:

10 GOSUB 20000:REM Initialisierung
20 adr$ = &BC11: REM Aufrufadresse
30 GOSUB 10000
40 PRINT “Der aktuelle MODE ist ";a%
50 END

Der geklaute Editor

Und zum Abschluß noch ein besonderes Bonbon: Wäre es nicht fein, wenn man den Editor, mit dem Sie Ihre BA-SlC-Programme eingeben, als Bestandteil eigener Programme einsetzen könnte? Listing 2 zeigt, wie's gemacht wird. Der Editor kann nämlich ebenfalls per CALL aufgerufen werden. Allerdings erwartet er im HL-Register-paar die Adresse eines Eingabepuffers von 256 Byte Länge, in dem alle Zeichen zwischengespeichert werden. Weiterhin kann sich in diesem Puffer bereits vor dem Aufruf eine Zeichenkette befinden, die dann angezeigt und für beliebige Veränderungen freigegeben wird. Zu beachten ist dabei nur, daß diese Zeichenkette immer mit einem Nullbyte abgeschlossen werden muß, damit der Editor weiß, wo sie zu Ende ist. In dem Listing spielt sich nun Folgendes ab: In Zeile 70 wird die Startadresse des Puffers festgelegt; wir benutzen hier einen Bereich, der im CPC ohnehin für diesen Zweck vorgesehen ist. Ab Zeile 80 wird eine Stringvariable erzeugt und deren Inhalt Zeichen für Zeichen mit POKE in den Puffer befördert, einschließlich einer Null als Endmarkierung.

Ab Zeile 130 folgt dann die bereits bekannte Prozedur: Die Pufferadresse kommt ins HL-Registerpaar und die Aufrufadresse wird festgelegt, bevor dann die Post mit GOSUB 10000 abgeht. Beachten Sie bitte die Hinweise für die verschiedenen CPC-Typen und ändern Sie das Listing gegebenenfalls ab! Nach dem Aufruf des Editors wird der Pufferinhalt auf dem Bildschirm angeboten, so wie Sie es von dem BASIC-Kommando EDIT her gewohnt sind. Sie können also mit dem Cursor herumwandern, Zeichen einfügen und löschen, den Copy-Cursor benutzen und die Eingabe mit RETURN bzw. ENTER abschließen.

Danach erfolgt die Rückkehr ins BASIC-Programm. Die Zeilen ab 190 übertragen den (womöglich geänderten) Pufferinhalt wieder in die String-variable a$, und zur Kontrolle wird der Text abschließend noch einmal mit PRINT ausgegeben. Wenn Sie das Ganze ein bißchen modifizieren, in eine Programmschleife einpacken und noch ein PRINT#8, a$ für die Druckerausgabe hinzufügen, dann haben Sie Ihren CPC bereits in eine komfortable Schreibmaschine verwandelt! Als zusätzliches Übungsmaterial finden Sie hier noch eine Tabelle mit den Ein-/Aussprungbedingungen für vier weitere Betriebssystem-CALLs. In der nächten Folge wird es nämlich ernst: Was wir hier noch recht umständlich von BASIC aus erledigt haben, wird demnächst ohne Umschweife in Maschinensprache programmiert! Außerdem werden wir Ihnen das dazu nötige Werkzeug in Form eines Assemblers zur Verfügung stellen. Doch keine Angst: Wenn Sie in dieser Serie bis jetzt konsequent 'am Ball' geblieben sind, wird der Übergang zur Maschinenprogrammierung nur noch ein kleiner Schritt für Sie sein.

Matthias Uphoff/cd , CPCAI

★ PUBLISHER: CPC Amstrad International
★ AUTHOR: Matthias Uphoff
 

★ AMSTRAD CPC ★ DOWNLOAD ★

Type-in/Listing:
» Alle  Wege  fuehren  ins  ROM    (CPC  Amstrad  International)    GERMANDATE: 2021-08-08
DL: 128
TYPE: ZIP
SiZE: 4Ko
NOTE: 40 Cyls
.HFE: Χ

★ AMSTRAD CPC ★ A voir aussi sur CPCrulez , les sujets suivants pourront vous intéresser...

Lien(s):
» Coding » L'assembleur en Douceur (6/x) : Les modes d'adressage (Micro-Mag)
» Coding » Clefs1 28 Adr Lowrom 464
» Coding » Clefs1 59 - Tables des Valeurs Chromatique
» Coding Src's » Dump ROM Multiface II (Grimware)
» Coding » Eprom Programming (Amstrad Computer User)
» Coding » Au Coeur du 6128 - La ROM (CPC Revue)
Je participe au site:
» Vous avez des infos personnel, des fichiers que nous ne possédons pas concernent ce programme ?
» Vous avez remarqué une erreur dans ce texte ?
» Aidez-nous à améliorer cette page : en nous contactant via le forum ou par email.

CPCrulez[Content Management System] v8.7-desktop/c
Page créée en 678 millisecondes et consultée 666 fois

L'Amstrad CPC est une machine 8 bits à base d'un Z80 à 4MHz. Le premier de la gamme fut le CPC 464 en 1984, équipé d'un lecteur de cassettes intégré il se plaçait en concurrent  du Commodore C64 beaucoup plus compliqué à utiliser et plus cher. Ce fut un réel succès et sorti cette même années le CPC 664 équipé d'un lecteur de disquettes trois pouces intégré. Sa vie fut de courte durée puisqu'en 1985 il fut remplacé par le CPC 6128 qui était plus compact, plus soigné et surtout qui avait 128Ko de RAM au lieu de 64Ko.