Carsten Kelling 1997 ([EMail senden]); letzte Änderung: 17. September 1997


4.8 Der Simulationsansatz

Die folgenden Abschnitte beschreiben den im Rahmen dieser Arbeit für die Demonstrations-Applets entworfenen Simulationsansatz. Anhand von Beispielen werden zwei Varianten entwickelt und diskutiert. Da es aus didaktischen Gründen notwendig ist, Simulationsschritte auch wieder zurücknehmen zu können, ergeben sich besondere Anforderungen an den Simulator.

Nehmen wir einmal an, man habe sich folgende Aufgabe gestellt:

Zu simulieren sei der Mikroprozessor Xyz-Logics Typ 123 zusammen mit einem RAM, das den gemeinsamen Programm- und Datenspeicher darstellt. Der Typ 123 besteht aus n1 Registern, n2 Bussen und einer ALU.

Damit liegt das weitere Vorgehen nur zum Teil auf der Hand. Fest steht, welche Komponenten aus dem Rechner-Baukasten man benötigt: n1 Instanzen von Register16 (oder abgeleiteten Klassen), n2 Instanzen von SimpleBus, eine von ALU und eine von EditableMemory (siehe 4.1 Komponenten des Rechner-Baukastens). In den obigen Abschnitten des Kapitels konnte man lesen und lernen, wie man die Komponenten erzeugt und auf dem Bildschirm anordnet (Abschnitt 4.4). Dort wurde auch beschrieben, wie diese Werte zugewiesen bekommen und Werte wieder preisgeben, sowie wie die ALU neue Werte erzeugt (Abschnitt 4.5). Man weiß außerdem, wie Vorgänge visualisiert werden können (Abschnitt 4.6) und hat sich die Maschinensprache des Typ 123 angeeignet.

Nun steht aber dennoch die Antwort auf die Fragen aus, wie man den Typ 123 simulieren soll und wie kleine Schritte man dabei noch darstellen soll.

Es ist wichtig, zu verstehen, daß der Rechner-Baukasten den Programmierer auf keinen Simulationsansatz und keinen bestimmten Algorithmus festlegt. Der Programmierer kann sich frei im Rahmen der durch setValue()/getValue(), calculate() und activate()/deactivate() abgesteckten Möglichkeiten bewegen. Die im folgenden vorgestellten zwei Ansätze sollen nur einen gangbaren und erprobten Weg erläutern, auf manches Problem hinweisen und können als Grundlage eigener Lösungen dienen.

4.8.1 Der Ansatz SI/SS/DS

Dieses war der erste Ansatz für ein Simulationsmodell während der Entwicklung der Demonstrations-Applets; sein Name ergibt sich aus den drei Methoden singleInstruction(), singleStep() und demonstrate(), auf denen es beruht und die im folgenden erklärt werden.

Nehmen wir ohne Beschränkung der Allgemeinheit an, daß das Steuerwerk des Typ 123 als endlicher Automat realisiert sei, statt als Mikrocode-ROM. Der Typ 123 kenne die drei Maschinenbefehle Load, Store und Add; jeder Maschinenbefehl durchlaufe einen dreistufigen Zyklus FETCH-DECODE-EXECUTE durchläuft. FETCH und DECODE sind für alle Befehle gleich und auf je einen Zustand des Automaten beschränkt, die Ausführungsphase benötigt unterschiedlich viele Zustände.

Wenn man davon ausgeht, daß die Anzahl der Automatenzustände minimiert wurde, bietet es sich an, den Zustandsübergang des Steuerwerksautomaten als kleinsten noch zu simulierenden Schritt zu wählen (gleichgültig, ob innerhalb eines Taktes der realen Hardware ein kompletter Maschinensprachebefehl, einer der drei Zyklen oder nur ein Zustand des Automaten durchlaufen wird). Dieser vermutete kleinste notwendige Simulationsschritt und der kleinste Schritt, der für die Erklärung, i.e. Demonstration, der Vorgänge notwendig ist, sind jedoch nicht identisch. Der Java-Code zur Simulierung des FETCH-Zyklus könnte z.B. so aussehen:

	ram.setAddress(programCounter.getValue());     // RAM mit Wert von PC
    	                                           // adressieren
	instructionRegister.setValue(ram.getValue());  // Befehlswort aus RAM in Reg.
    	                                           // laden

Diese beiden Codezeilen auf zwei Simulationsschritte zu verteilen, ist unnötig, die Wahl der Größe des kleinsten Simulationsschrittes offenbar angemessen; die Erklärung eines FETCH-Vorganges für den menschlichen Betrachter aber wird z.B. in dem VNR-Applet in vier Demonstrationsschritten vorgenommen:

  1. Der Adreßbus wird durch laufende Punkte optisch so dargestellt, daß klar wird, daß sich Daten vom Programmzähler zum RAM bewegen. Gleichzeitig erscheint ein Hilfetext, der diesen Vorgang ("Adressierung des Hauptspeichers") erläutert.
  2. Die durch die gerade angelegte Adresse bezeichnete Speicherstelle im RAM wird rot hervorgehoben, der Speicher "arbeitet". Ein entsprechender Hilfetext erscheint.
  3. Auf dem Datenbus bewegen sich Punkte vom RAM zum Instruktionsregister. Der Hilfetext erläutert, daß das RAM nun den Datenbus mit dem angeforderten Wert "treibt".
  4. Das Instruktionsregister verfärbt sich rot und ändert den angezeigten Wert auf denselben, der auch im RAM rot markiert ist. Der Hilfetext erklärt, daß das Instruktionsregister nach einer relevanten Taktflanke seinen neuen Wert vom Datenbus übernommen hat.

Wenn der jeweilige Demonstrationsschritt in einer Variable demonstrationStep abgelegt wird, könnte der Java-Code für die Erläuterung damit so abgefaßt werden:

switch (demonstrationStep)
{
  case 1:
    abus.activate("programCounter", "ram");
    break;
  case 2:
      // spätestens hier notwendig: ram.setAddress(programCounter.getValue());
    ram.activate();
    break;
  case 3:
    dbus.activate("ram", "instructionRegister");
    break;
  case 4:
      // spätestens hier notwendig: instructionRegister.setValue(ram.getValue());
    instructionRegister.activate();
}

setHelpText(simulationStep, demonstrationStep);

Selbstverständlich kann es je nach vermuteter Vorbildung der Leser und technischem Sachverhalt, auf den abgezielt wird (z.B. Ausbreiten von Binärwerten auf dem Bus, Zeitverhalten), notwendig sein, noch mehr Demonstrationsschritte vorzusehen.

Die Frage ist nun, wann man die Simulationsschritte relativ zu den Demonstrationsschritten durchführt - im Code oben ist angegeben, wann die entsprechenden Simulationsschritte spätestens ausgeführt werden müssen. Weil aber setValue() und setAddress() das Aussehen einer Rechnerkomponente von sich aus nicht verändern (siehe 4.5 Wertzuweisung und Berechnung), dürfen die Simulationsschritte auch früher, separat abgearbeitet werden.

So kam es im Ansatz SI/SS/DS zu einer Aufteilung des Java-Codes auf die Methoden singleStep() und demonstrate(); erstere führt die eigentliche Simulation durch, letztere erzeugt die Demonstrationsschritte. In jedem Simulationsschritt wird nur einmal, ganz zu Beginn, der Code in singleStep() ausgeführt. Danach "decken" Aufrufe von demonstrate() die Veränderungen schrittweise "auf". Zusätzlich zu demonstrationStep enthält eine Variable c_state (current state) dabei den gegenwärtigen Zustand des Steuerwerksautomaten, die Variable n_state enthält den Folgezustand. Die möglichen Werte von c_state und n_state werden als Integer-Konstanten definiert und wären bei dem Typ 123 neben den obligatorischen FETCH und DECODE z.B., LOAD, STORE1, STORE2 oder ADD. Das Demonstrations-Applet zur Arbeitsweise eines Registers hingegen unterscheidet die Zustände READ, WRITE und RESET.

Wir können nun die beiden Methoden für den Typ 123 codieren:

final static int FETCH = 1;
final static int DECODE = 2;
final static int LOAD = 3;
... (weitere Zustandswerte)


private int c_state, n_state = FETCH;
private int demonstrationStep = 1;
private boolean demonstrationReady = true;


public void singleStep()
{
  if (demonstrationReady)
  {
    c_state = n_state;
    demonstrationStep = 1;


    switch (c_state)
    {
      case FETCH:
        ram.setAddress(programCounter.getValue());
        instructionRegister.setValue(ram.getValue());
        break;
      case DECODE:
        // kein Simulationscode für das Decodieren notwendig,
        // aber hier Hochzählen des Programmzählers
        programCounter.setValue(programCounter.getValue() + 1);
        break;
      case LOAD:
        ram.setAddress(instructionRegister.getValue() & 255);
        // 16 Allzweckregister ansprechbar, codiert in Bits 8 bis 11
        int target = (instructionRegister.getValue() >> 8) & 15;
        register[target].setValue(ram.getValue());
        break;
      ... (weitere case-Fälle)
    }  // Ende von switch (c_state)
  }
  //else              Absichtlich nicht!
  //  demonstrate();
}


public void demonstrate()
{
  if (demonstrationReady)
  {
    demonstrationReady = false;
    setHelpText(c_state, 99);
  }
  else

{

    switch (c_state)
    {
      case FETCH:
        switch (demonstrationStep)
        {
          case 1:
            abus.activate("programCounter", "ram");
            break;
          case 2:
            ram.activate();
            break;
          case 3:
            dbus.activate("ram", "instructionRegister");
            break;
         case 4:
           instructionRegister.activate();
           demonstrationReady = true;
       }

break;

      case DECODE:
        programCounter.update();
        demonstrationReady = true;

break;

      case LOAD:
        switch (demonstrationStep)
        {
          case 1:
            instructionRegister.activate(1);  // rechtes Byte hervorheben
            abus.activate("instructionRegister", "ram");
            break;
          case 2:
            ram.activate();
            break;
          case 3:
            String strTarget = "register" +
                               (instructionRegister.getValue() >> 8) & 15;
            dbus.activate("ram", strTarget);
            break;
         case 4:
           int target = (instructionRegister.getValue() >> 8) & 15;
           register[target].activate();
           demonstrationReady = true;
        }
        break;
      ... (weitere case-Fälle)
    }  // Ende von switch (c_state)

    setHelpText(c_state, demonstrationStep);
  }
}

Bei dieser Art der Formulierung der beiden Methoden muß bei jedem Mausklick auf "" lediglich

	singleStep();
	demonstrate();

aufgerufen werden. Der boolesche Wert demonstrationReady sorgt dafür, daß der Simulationscode in singleStep(), wie oben beschrieben, nur ganz zu Beginn des Simulationsschrittes ausgeführt wird. In diesem Fall übernimmt zuerst c_state den Wert von n_state; danach folgt in singleStep() nur noch eine große case-Anweisung, die die Zustandsübergangslogik des Steuerwerkes simuliert. Ein entsprechendes case befindet sich auch in demonstrate. Dort muß aber in den meisten Fällen noch weiter nach dem Wert von demonstrationStep unterschieden werden.

Um es noch einmal zu sagen: Diese Reihenfolge von Simulations- und Demonstrationsanweisungen war nur möglich, weil die Komponenten Änderungen ihrer gespeicherten Werte und der sich bei der ALU aus einer Rechnung ergebenden flags nicht sofort - auch nicht bei Aufruf von paint() - anzeigen, sondern erst nach einem activate() oder update().

Der Ansatz SI/SS/DS hatte obendrein den großen Vorteil, daß man die Aufrufe von demonstrate() einfach fortlassen konnte, sofern dafür gesorgt war, daß demonstrationReady galt. Trotzdem simulierte er noch korrekt, aber schneller, weil ohne Bildschirmausgabe.

Das führt uns zu dem dritten namensgebenden Bestandteil des Ansatzes, singleInstruction(), der stellvertretend für alle Methoden steht, die durch mehrfache Aufrufe von singleStep(), ohne demonstrate(), größere Simulationsschritte zurücklegen als singleStep() selbst. Nach einem Klick auf "" wird ein kompletter Maschinenbefehl des Typ 123 oder VNR durch singleInstruction() simuliert, das solange singleStep() ohne demonstrate() aufruft, bis c_state wieder FETCH etc. als Wert hat; anschließend muß über Aufrufe von update() nur einmal die Summe aller Wertänderungen sichtbar gemacht werden (bei der Simulation des Registers sind singleInstruction() und singleStep() identisch). Entsprechend definiert man die Methode goUntilBreakpoint() ("").

Nach einer Weile wurde jedoch offenbar, daß der Simulationsansatz SI/SS/DS mehr Schwächen als Stärken hat:

		ram.setAddress(ireg.getValue() & 255);
		jreg.setValue(ram.getValue());
		ram.setAddress(jreg.getValue());
		akku.setValue(ram.getValue());

4.8.2 Der Ansatz SI/DS

Dieser Ansatz entstand aus SI/SS/DS durch Zusammenfassen von singleStep() und demonstrate() in demonstrate(), das nun also sowohl die Simulations- als auch die Demonstrationsarbeit durchführt. Im letzten Abschnitt war ja zu sehen, daß der angenommene kleinste notwendige Simulationsschritt in einigen Fällen doch nicht fein genug gewählt war. So kann ein einfaches Anweisungspaar wie

	ram.setAddress(ireg.getValue() & 255);
	jreg.setValue(ram.getValue());

zwar auf einmal ausgeführt und durch ein ram.activate() nachher die richtige Adresse im RAM hervorgehoben werden, bereits bei den Anweisungen

	ram.setAddress(ireg.getValue() & 255);
	jreg.setValue(ram.getValue());
	// ram.activate();
	ram.setAddress(jreg.getValue());
	akku.setValue(ram.getValue());
	// ram.activate();

aber muß spätestens an den angegebenen Stellen ram.activate() aufgerufen werden, um beide angelegten Adressen zu sehen. Bevor man nun aber in singleStep() und demonstrate() zwei case-Anweisungen anlegt, die beide nach c_state und demonstrationStep unterscheiden, faßt man diese lieber an einem Ort zusammen.

Das "SI" im Namen des Simulationsansatzes steht wieder für singleInstruction() und andere Methoden die, jetzt durch Aufrufe von demonstrate(), größere Simulationsschritte durchführen.

Die Methode demonstrate() besteht natürlich weiterhin hauptsächlich aus einer case-Anweisung, die nach c_state unterscheidet und für jeden möglichen Wert von c_state desweiteren nach dem aktuellen Demonstrationsschritt; bei jeder zulässigen Kombination von c_state und Demonstrationsschritt kann nun aber entweder nur Demonstrationscode oder Simulationscode, gefolgt von Demonstrationscode, ausgeführt werden. Am Ende von demonstrate() wird der Demonstrationsschritt erhöht. Zu Beginn wird überprüft, ob der Demonstrationsschritt den Maximalwert des gerade vorgeführten Vorganges erreicht hat; falls ja, findet ein Zustandsübergang bei c_state statt.

4.8.2.1 lock() und unlock()

Ein Geschwindigkeitsnachteil wird durch die neuen Methoden lock() und unlock() vermieden: Eine Komponente, die per lock() "verriegelt" wurde, ignoriert Aufrufe an Demonstrationsmethoden wie activate(). Messungen haben keinen spürbaren Unterschied zwischen dem SI/SS/DS-Modell und SI/DS ergeben, sofern zu Beginn von singleInstruction() oder goUntilBreakpoint() alle Komponenten "verriegelt" und am Ende wieder "aufgeschlossen" werden.

4.8.2.2 Ein längeres Beispiel: Der Von-Neumann-Rechner

Um die in den vorherigen Abschnitten dieser Arbeit vermittelten Details über die Programmierung der Komponenten des Rechner-Baukastens im Überblick zu zeigen, folgt an dieser Stelle ein längeres Beispiel. Es handelt sich um einen Auszug aus der Methode demonstrate() des aus 3.3.2 bekannten Von-Neumann-Rechner-Applets. An ihr läßt sich gut erkennen, wie ein größeres Problem mit den Mitteln des Rechner-Baukastens gelöst werden kann und sie beweist, daß das Verhalten der Demonstrations-Applets tatsächlich auf Simulation, und nicht auf vorgefertigten Animationen oder gar Zauberei, beruht.

Zum Aufbau dieser Methode: Wie bei den kurzen Beispielen zur Simulation des Typ 123 wird auch der VNR nach dem SI/DS-Modell simuliert und sein Steuerwerk als endlicher Automat realisiert. Diese Automat findet sich in der case-Anweisung, die nach den in Rechner definierten Konstanten FETCH, DECODE, LDA_MEM etc. unterscheidet und einen Großteil von demonstrate() ausmacht. Um auch Demonstrationsschritte zu implementieren, muß in jedem einzelnen Fall zusätzlich nach den Werten von demonstrationStep unterschieden werden.

Gut ist auch die Verzahnung von Simulations- und Demonstrationsbefehlen zu sehen - die Simulationsbefehle stehen jeweils zu Beginn und sind nach rechts eingerückt. Wenn, für schnelle Simulation größerer Schritte, die Demonstrationsbefehle nicht benötigt werden, wird wie oben beschrieben die Methode lock() aller Komponenteninstanzen benutzt:

public void demonstrate(boolean doRepaint)
{
  if (demonstrationReady == true)
  {
    if (doRepaint == true)
    {
      deactivateAll();
      setHelpText(c_state, 99);
    }

    if (c_state == FETCH_DECODE)
        pc.update();

    if (c_state == FETCH_DECODE)
    {
      outToTracelnS(")\n  --Decode (1)");
    }
    else
      outToTracelnS(")");

    c_state = n_state;

    demonstrationReady = false;
    demonstrationStep = 1;
  }
  else
  {
    switch (c_state)
    {
      case FETCH:
      case FETCH_DECODE:
        switch (demonstrationStep)
        {
          case 1:
              outToTraceS("  --Fetch (");
              bufferComputer();
            ram.deactivate();
            abus.activate("pc", "start");
            break;
          case 2:
              ram.setAddress(pc.getValue());
            ram.activate();
            break;
          case 3:
            dbus.activate("start", "ireg");
            break;
          case 4:
              ireg.setValue(ram.getValue());
              if (showDecodeCycle == true)
                n_state = DECODE;
              else
              {
                c_state = FETCH_DECODE;
                pc.setValue(pc.getValue() + 1);
                n_state = ireg.getValue() / 256;
              }
            ireg.activate();
            demonstrationReady = true;
        }
        break;
      case DECODE:
        switch (demonstrationStep)
        {
          case 1:
              outToTraceS("  --Decode, ");
              outToTraceS("naechster Befehl: '"
                          + SimpleMemory.toOpcode(ireg.getValue(),
                                                  ireg.ZWEI_HOCH_BITWIDTH) 
                          + " (");
              pc.setValue(pc.getValue() + 1);
              n_state = ireg.getValue() / 256;
            pc.activate();
            demonstrationReady = true;
        }
        break;
      case LDA_MEM:
        switch (demonstrationStep)
        {
          case 1:
              outToTrace("--LDA " + (ireg.getValue() & 255, 16), " (");
              ram.setAddress(ireg.getValue() & 255);
            ireg.activate(1);
            abus.activate("ireg", "start");
            break;
          case 2:
            ram.activate();
            break;
          case 3:
            dbus.activate("start", "aluOutput");
            aluOutputBus.activate("end", "akku");
            break;
          case 4:
              akku.setValue(ram.getValue());
              n_state = FETCH;
            akku.activate();
            demonstrationReady = true;
        }
        break;
      case LDA_MEM_INDIR:
        switch (demonstrationStep)
        {
          case 1:
              outToTrace("--LDA (" + (ireg.getValue() & 255, 16)
                         + ")", "Adresse2: " + (jreg.getValue() & 255, 16) + " (");
              ram.setAddress(ireg.getValue() & 255);
            ireg.activate(1);
            abus.activate("ireg", "start");
            break;
          case 2:
            ram.activate();
            break;
          case 3:
            dbus.activate("start", "jreg");
            break;
          case 4:
              jreg.setValue(ram.getValue());
            jreg.activate();
            break;
          case 5:
              ram.setAddress(jreg.getValue() & 255);
            deactivateAll();
            abus.activate("jreg", "start");
            break;
          case 6:
            ram.activate();
            break;
          case 7:
            dbus.activate("start", "aluOutput");
            aluOutputBus.activate("end", "akku");
            break;
          case 8:
              akku.setValue(ram.getValue());
              n_state = FETCH;
            akku.activate();
            demonstrationReady = true;
        }
        break;
      case LDA_ABSOL:
        switch (demonstrationStep)
        {
          case 1:
              outToTrace("--LDA #" + (ireg.getValue() & 255, 16), " (");
            ireg.activate(1);
            dbus.activate("ireg", "aluOutput");
            aluOutputBus.activate("end", "akku");
            break;
          case 2:
              akku.setValue(ireg.getValue() & 255);
              n_state = FETCH;
            akku.activate();
            demonstrationReady = true;
        }
        break;
      case STA_ABSOL:
        ...
        break;
      case STA_MEM:
        ...
        break;
      case ADD_MEM:
      case SUB_MEM:
      case MUL_MEM:
      case DIV_MEM:
      case AND_MEM:
      case OR_MEM:
      case XOR_MEM:
        switch(demonstrationStep)
        {
          case 1:
              if (c_state == ADD_MEM)
                aluOpcode = "--ADD ";
              else if (c_state == SUB_MEM)
                aluOpcode = "--SUB ";
              else if (c_state == DIV_MEM)
                ...
              outToTrace(aluOpcode + (ireg.getValue() & 255), " (");
              ram.setAddress(ireg.getValue() & 255);
            ireg.activate(1);
            abus.activate("ireg", "start");
            break;
          case 2:
            ram.activate();
            break;
          case 3:
            dbus.activate("start", "aluRightInput");
            aluLeftInputBus.activate("akku", "end");
            break;
          case 4:
              aluResult = alu.calculate(c_state, akku.getValue(), ram.getValue());
            alu.activate();
            break;
          case 5:
            aluOutputBus.activate("start", "akku");
            break;
          case 6:
              akku.setValue(aluResult);
              n_state = FETCH;
            aluLeftInputBus.deactivate();
            akku.activate();
            demonstrationReady = true;
        }
        break;
      case INC:
      case DEC:
        switch(demonstrationStep)
        {
          case 1:
              if (c_state == INC)
                outToTrace("--INC", " (");
              else if (c_state == DEC)
                outToTrace("--DEC", " (");
              else
                outToTrace("--NOT", " (");
            aluLeftInputBus.activate("akku", "end");
            break;
          case 2:
              aluResult = alu.calculate(c_state, akku.getValue());
            alu.activate();
            break;
          case 3:
            aluOutputBus.activate("start", "akku");
            break;
          case 4:
              akku.setValue(aluResult);
              n_state = FETCH;
            aluLeftInputBus.deactivate();
            akku.activate();
            demonstrationReady = true;
        }
        break;
      case SHL:
      case SHR:
        ...
        break;
      case JMP_ABSOL:
      case JZE_ABSOL:
      case JNZ_ABSOL:
      case JLE_ABSOL:
        ...  (siehe die nächsten vier Befehle)
        break;
      case JMP_MEM:
      case JZE_MEM:
      case JNZ_MEM:
      case JLE_MEM:
        n_state = FETCH;

        if (demonstrationStep == 1)
          switch(c_state)
          {
            case JMP_MEM:
              outToTrace("--JMP " + (ireg.getValue() & 255), " (");
              break;
            case JZE_MEM:
              if (alu.getFlag("zero") == true)
                outToTrace("--JZE " + (ireg.getValue() & 255), " (");
              else
              {
                outToTrace("--nicht ausgeführter bedingter Sprung: JZE " 
                           + (ireg.getValue() & 255), " (");
                c_state = JZE_NOT_TAKEN;
                demonstrationReady = true;
              }
              break;
            case JNZ_MEM:
              if (alu.getFlag("zero") == false)
                outToTrace("--JNZ " + (ireg.getValue() & 255), " (");
              else
              {
                outToTrace("--nicht ausgeführter bedingter Sprung: JNZ " 
                           + (ireg.getValue() & 255), " (");
                c_state = JNZ_NOT_TAKEN;
                demonstrationReady = true;
              }
              break;
            case JLE_MEM:
              if ((alu.getFlag("zero") == true) || (alu.getFlag("less") == true))
                outToTrace("--JLE " + (ireg.getValue() & 255), " (");
              else
              {
                outToTrace("--nicht ausgeführter bedingter Sprung: JLE " 
                           + (ireg.getValue() & 255), " (");
                c_state = JLE_NOT_TAKEN;
                demonstrationReady = true;
              }
              break;
          }

        if (demonstrationReady == false)  // Sprung wird durchgeführt
          switch(demonstrationStep)
          {
            case 1:
              ireg.activate(1);
              abus.activate("ireg", "start");
              break;
            case 2:
              ram.activate();
              break;
            case 3:
              dbus.activate("start", "pc");
              break;
            case 4:
                ram.setAddress(ireg.getValue() & 255);
                pc.setValue(ram.getValue());
              pc.activate();
              demonstrationReady = true;
          }
        break;
      case IN_MEM:
        switch(demonstrationStep)
        {
          case 1:
              outToTrace("--IN " + (ireg.getValue() & 255), " (");
              ram.setAddress(ireg.getValue() & 255);
            ireg.activate(1);
            abus.activate("ireg", "start");
            break;
          case 2:
            dbus.activate("inOut", "start");
            break;
          case 3:
              ram.setValue((int) (Math.random()*Math.pow(2, (double) BITWIDTH)));
              n_state = FETCH;
            ram.activate();
            demonstrationReady = true;
        }
        break;
      case OUT_MEM:
        ...
        break;
      case NOP:
          outToTrace("--NOP", " (");
      case JZE_NOT_TAKEN:
      case JNZ_NOT_TAKEN:
      case JLE_NOT_TAKEN:
          n_state = FETCH;
        demonstrationReady = true;
        break;
      case UNKNOWN_COMMAND:
      default:
          out("--- Unbekannter Opcode! ---");
          c_state = UNKNOWN_COMMAND;
          n_state = FETCH;
        demonstrationReady = true;
        break;
    }
    if (doRepaint == true)
      setHelpText(c_state, demonstrationStep);

    if (demonstrationStep == 1)
      outToTraceS(Integer.toString(demonstrationStep));
    else
      outToTraceS(", " + demonstrationStep);

    demonstrationStep++;
  }
} /* end demonstrate */

4.9 Pfade und benötigte Dateien

Wer die "Lernmaterialien zur technischen Informatik" über das world wide web benutzt, kann davon ausgehen (und muß sich darauf verlassen), daß alle benötigten Dateien sich auf dem Web-Server an der richtigen Stelle befinden.

Wer die "Lernmaterialien" für schnelleren Zugriff auf eine lokale Festplatte kopieren möchte, muß lediglich darauf achten, den gesamten Verzeichnisbaum inklusive aller Unterverzeichnisse zu kopieren. Dabei ist die Wurzel des benötigten Verzeichnisbaumes dasjenige Verzeichnis, in dem die Datei "Inhalt.html" liegt; es werden keine Dateien aus "höher" gelegenen Verzeichnissen gebraucht.

Für diejenigen, die weiter an den "Lernmaterialien" arbeiten möchten und den Rechner-Baukasten dabei verwenden, wird im folgenden die verwendete Verzeichnisstruktur erklärt; "Inhalt.html" befindet sich in dem in Abbildung 35 gezeigten Ordner "Lernmaterialien".



Abbildung 35: Verzeichnisstruktur der "Lernmaterialien"

Anmerkungen und Erklärungen hierzu:

Abschnitt 4.10 Ausführen eines Applets erläutert, wie man HTML-Dokumente um Applets erweitert und wie diese ihren Code in dem Verzeichnis "Klassen" finden.

Wer Applets unter Zuhilfenahme des Rechner-Baukastens, aber nicht unbedingt als Teil der "Lernmaterialien", entwickelt, braucht deren Klassen nicht notwendigerweise im Verzeichnis "Klassen" abzulegen. Er muß seinen Applets lediglich mitteilen, wo sie den Bytecode des Rechner-Baukastens finden; dazu gibt es die Umgebungsvariable CLASSPATH, welche eine durch Semikola getrennte Liste von Verzeichnisnamen enthält. Die Java-VMs, auch die der bekannten Browser, suchen Klassen in allen in CLASSPATH stehenden Verzeichnissen. Das bedeutet: In CLASSPATH muß auch der volle Name des Verzeichnisses oberhalb von "ckelling" stehen und (falls die beiden nicht identisch sind) der des Verzeichnisses oberhalb von "symantec" - im obigen Beispiel wäre das etwa "C:\Java\Lernmaterialien\Klassen" bzw. "/Java/Lernmaterialien/Klassen". Sobald nämlich beispielsweise zum ersten Mal eine Instanz von ckelling.baukasten.ALU erzeugt werden soll, sucht die VM die Datei ALU.class, aber eben nicht in den Verzeichnissen, so wie sie in CLASSPATH stehen, sondern in diesen Verzeichnissen, erweitert um "\ckelling\baukasten" bzw. "/ckelling/baukasten".

4.10 Ausführen eines Applets

Wie in der Einleitung ausgeführt, sind Applets Java-Programme, die in der VM eines Browsers ausgeführt werden. Um ein Applet zu starten muß man den Browser lediglich eine HTML-Seite anzeigen lassen, die spezielle sogenannte tags enthält.

Ein HTML-Dokument ist zunächst nur eine ASCII-Textdatei. Alle besondere Funktionalität, also Auszeichnung von Text, Überschriften, Tabellen und natürlich eingebundene Bilder und Hyperlinks (und vieles mehr) wird mit tags bewirkt, die die allgemeine Form

	<Name Parameterliste>

haben. Die meisten tags umschließen wie Klammern einen bestimmten Bereich, so daß es zu der "öffnenden" Variante <Name> auch die "schließende" </Name> gibt. Um nun ein Applet bei Aufruf einer HTML-Seite zu starten, muß auf dieser Seite das tag <APPLET> mit entsprechenden Parametern stehen.

4.10.1 Integration in HTML-Seiten

Ein sehr einfaches tag für ein Applet auf einer HTML-Seite könnte lauten:

	<APPLET CODE="MyApplet.class" WIDTH=200 HEIGHT=100>
	</APPLET>

Sobald diese Seite angezeigt wird, wird das Applet MyClass gestartet. An der Stelle im Text, wo sich das tag befindet, bekommt das Applet eine 200 mal 100 Bildpunkte große Darstellungsfläche eingeräumt (standardmäßig grau ausgefüllt).

Die Klassendatei MyApplet.class muß sich in diesem Beispiel im selben Verzeichnis, gleichgültig ob lokal oder auf einem Web-Server, befinden, in dem auch die HTML-Datei steht. Damit es möglich ist, Klassendateien in einem eigenen (Unter-)Verzeichnisbaum unterzubringen, so wie es auch bei den "Lernmaterialien zur technischen Informatik" geschehen ist (siehe 4.9 Pfade und benötigte Dateien), gibt es die Angabe CODEBASE; die einfachste Form, ein Applet zu dem Thema "Von-Neumann-Rechner" aufzurufen lautet damit so:

	<APPLET CODE="ckelling.baukasten.Von_Neumann_Rechner.class"
    	    CODEBASE="Klassen" WIDTH=510 HEIGHT=490>
	</APPLET>

Nota bene: Die Datei Von_Neumann_Rechner.class befindet sich, von der aufrufenden HTML-Datei aus gesehen, natürlich nicht in dem Unterverzeichnis "Klassen", sondern in "Klassen/ckelling/ baukasten", die Angabe in CODEBASE wird automatisch um weitere Unterverzeichnisse für den Package-Namen ergänzt. Alle anderen Varianten, das <APPLET>-tag zu formulieren, sind spätestens dann zum Scheitern verurteilt, wenn Von_Neumann_Rechner weitere Klassen aus dem Rechner-Baukasten nachlädt.

4.10.2 Unterstützte Parameter

Alle Applets, die auf der Klasse Rechner fußen, werten bei einem Aufruf von initialize() etliche Parameter aus, über die sich Aussehen und Verhalten eines Applets bereits vor dessen Start einstellen lassen. Diese Parameter müssen innerhalb des <APPLET>-tags angegeben werden, wobei Groß-/Kleinschreibung ignoriert wird:

	<APPLET CODE=...>
	  <PARAM name=program      value="bubblesort">
	  <PARAM name=showOpcodes  value="false">
	</APPLET>

Man beachte, daß die Angabe hinter value (der Wert des Parameters) immer in Anführungsstrichen stehen muß.

Die folgende Tabelle führt alle unterstützten Parameter auf; dieselben Informationen liefert auch ein Aufruf der Methode

	public String[][] getParameterInfo()

aus Rechner zurück. Diese Methode sollte angepaßt werden, falls in einem eigenen Applet neue Parameter hinzugekommen sind. Sie wird beispielsweise von appletviewer aufgerufen, ihr Rückgabewert wird dort unter dem Menüpunkt "Info" angezeigt (zusammen mit der Ausgabe von getAppletInfo()).

Name

Typ

Bedeutung

Standardwert, Bedeutung

program String Applets mit einem RAM: "Programm" (es kann sich auch um Testwerte handeln), das in das RAM geladen werden soll. Allgemein: Wert, auf den Rechner.PROGRAM gesetzt wird. Per initRam(PROGRAM) kann eine Instanz von EditableMemory dieses "Programm" dann annehmen. ""

RAM mit 0xff füllen.
manualredraw boolean Allgemein: Wert, den Rechner.manualRedraw annimmt. Bei Verwendung von SimControl (s. 4.12.7): Die beiden Knöpfe "Repaint" und "Update" anzeigen. "false"
expandnumbers boolean Wert, den Rechner.expandNumbers annimmt; ist dieser true, erweitern Komponenten angezeigte Zahlen um führende Nullen, entsprechend ihrer Bitbreite ("ff" wird in einem 24 Bit-Register zu "0000ff") "true"

"0012" statt "12" anzeigen.
numberbase int (dezimal) Wert, den Rechner.NUMBERBASE annimmt; dieser stellt die Basis für alle in den Komponenten angezeigten Zahlen dar. "16"

Hexadezimalzahlen anzeigen
fontsize int (dezimal) Stellt die Größe in "Punkt" der Schrift SMALLFONT ein; diese ist bei dem Standard 12 identisch mit NORMALFONT. Mißt der Bildschirm weniger als 800·600 Punkte, wird der Parameter ignoriert und als 10 angenommen. Alle Komponenten benutzen SMALLFONT für angezeigte Werte. "12" für große, "10" für kleine Bildschirme
programmchoice boolean Allgemein: Wert, den Rechner.programChoice annimmt. Bei Verwendung von SimControl (s. 4.12.7): Ein Auswahlfeld für verschiedene Speicherinhalte zeigen. "false"
showopcodes boolean Allgemein: Wert, den Rechner.showOpcodes annimmt. Instanzen von EditableMemory zeigen Opcodes statt Zahlen an. "false"

Zahlen statt Opcodes zeigen
showdecode boolean Allgemein: Wert, den Rechner.showDecodeCycle annimmt. VNR- und Speicherhierarchie-Applet: Die Decodierungsphase der Befehlsabarbeitung anzeigen. "true"
buttonopcode

buttonopcodes
boolean Allgemein: Wert, den Rechner.buttonOpcodes annimmt. Bei Verwendung von SimControl (s. 4.12.7): Auswahlknopf "Opcodes anzeigen" erzeugen, mit dem Rechner.showOpcodes gesetzt werden kann. "true"
buttondecode boolean Allgemein: Wert, den Rechner.buttonDecode annimmt. Bei Verwendung von SimControl (s. 4.12.7): Auswahlknopf "DECODE-Phase zeigen" erzeugen, mit dem Rechner.showDecodeCycle gesetzt werden kann. "true"
buttonfast boolean Allgemein: Wert, den Rechner.buttonFast annimmt. Bei Verwendung von SimControl (s. 4.12.7): Knöpfe , und erzeugen. "true"
showinfotips boolean Allgemein: Wert, den Rechner.showInfoTips annimmt. Ermöglicht es, bei Implementation von showInfoTip() (s. 4.7.2), kleine Hilfetexte an dem Mauszeiger anzuzeigen. "true"
editableflags int (binär) Allgemein: Wert, den Rechner.EditableFlags annimmt; mit dieser Variable läßt sich codieren, welche von bis zu 31 Komponenten per Maus editierbar sein sollen.

VNR-Applet: Sollen RAM, Programmzähler, Befehlsreg., Hilfsreg., Akku (in dieser Reihenfolge) editierbar sein?
"11...11"

Alle Komponenten editierbar
Tabelle 17: HTML-Parameter, die von Rechner bei Aufruf von initialize() ausgewertet werden.

4.10.3 Beispiel-HTML für den Von-Neumann-Rechner

Als Abschluß dieser Erläuterungen folgt hier die HTML-Beschreibung, die den Von-Neumann-Rechner in 3.3.2.5 "ohne Einschränkungen" laufen läßt:

<APPLET CODE="ckelling.baukasten.Von_Neumann_Rechner.class"
        CODEBASE="Klassen" WIDTH=510 HEIGHT=490>

	<PARAM name=program value="bubblesort">
	<PARAM name=manualredraw value="false">
	<PARAM name=programchoice value="true">
	<PARAM name=buttonfast value="true">
	<PARAM name=showopcodes value="false">
	<PARAM name=buttonopcodes value="true">
	<PARAM name=buttondecode value="true">
	<PARAM name=showdecode value="false">
	<PARAM name=editableflags value="11111">
	
	<BLOCKQUOTE>
		<HR>
		Hier sollte die Ausgabe des Java-<I>Applets</I> &quot;Von-Neumann-Rechner
		(ohne Einschr&auml;nkungen)&quot; erscheinen. Wenn Sie statt dessen diesen
		Text sehen, unterst&uuml;tzt ihr Browser kein Java!
		<HR>
	</BLOCKQUOTE>
</APPLET>

Der Abschnitt innerhalb der <BLOCKQUOTE>-Klammern ist ein Alternativtext (<HR> erzeugt eine horizontale Linie), der zu sehen ist, falls der Browser keine Applets unterstützt oder diese Unterstützung abgeschaltet wurde. Er erscheint allerdings nicht, falls Applets zwar ausgeführt werden können, das Starten des Applets jedoch scheitert; dann hilft ein Blick in die Java-Konsole (sofern vorhanden), um zu erkennen, ob beispielsweise Klassen nicht gefunden wurden.

Weitere Beispiele für HTML-Code und -Parameter finden sich in 3.3.2 Simulation des Von-Neumann-Rechners jeweils nach den Abbildungen der Applets. An diesen kann man gut erkennen, wie die Fähigkeiten des Applets sukzessive über Parameter eingeschaltet werden.

4.11 Demonstrations-Applets

Die folgende Aufzählung enthält alle im Rahmen dieser Arbeit erstellten Demonstrations-Applets in alphabetischer Reihenfolge. Zu jedem Applet werden dabei alle benötigten Klassen aus dem Rechner-Baukasten aufgeführt. Dabei sind Mehrfachnennungen, auch der erst im nächsten Abschnitt beschriebenen Klassen, möglich.

Die in Kapitel 3 vorgestellten "Lernmaterialien" enthalten unter der vorläufigen URL

	http://tech-www.informatik.uni-hamburg.de/Students/1kelling/DA/Inhalt.html

Verweise auf alle Applets abgesehen von dem Editor. Dieser ist vorläufig unter

	http://tech-www.informatik.uni-hamburg.de/Students/1kelling/DA/Editor.html

zu finden.

Alle genannten Applets leiten sich von Rechner ab und verwenden dadurch folgende Klassen:


Carsten Kelling 1997 ([EMail senden]); letzte Änderung: 17. September 1997