Compteur de codes Gray simple

Souvent, nous voulons des composants qui ne sont pas exclusivement de nature combinatoire - c'est-à-dire que nous voulons que le composant ait une certaine mémoire. La définition de ces composants comporte une subtilité importante : Vous ne pouvez pas faire en sorte que le composant lui-même stocke l'état, parce qu'un composant individuel peut apparaître plusieurs fois dans le même circuit. Il ne peut pas apparaître plusieurs fois directement dans un circuit, mais il peut apparaître plusieurs fois s'il apparaît dans un sous-circuit qui est utilisé plusieurs fois.

La solution consiste à créer une nouvelle classe pour représenter l'état actuel de l'objet, et d'en associer des instances au composant par l'intermédiaire de l'état du circuit parent. The solution is to create a new class for representing the object's current state, and to associate instances of this with the component through the parent circuit's state. Dans cet exemple, qui met en œuvre un compteur de code Gray 4 bits déclenché par front, nous définissons une classe CounterData pour représenter l'état du compteur, en plus de la sous-classe InstanceFactory, comme illustré précédemment. L'objet CounterData mémorise à la fois la valeur actuelle du compteur et la dernière entrée d'horloge vue (pour détecter les fronts montants).

CounterData

package com.cburch.gray;

import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.InstanceData;
import com.cburch.logisim.instance.InstanceState;

/** Représente l'état d'un compteur. */
class CounterData implements InstanceData, Cloneable {
    
        /** Récupère l'état associé à ce compteur dans l'état du circuit,
          * générer l'etat si nécessaire. */
    public static CounterData get(InstanceState state, BitWidth width) {
        CounterData ret = (CounterData) state.getData();
        if(ret == null) {
                    /** S'il n'existe pas encore, nous le configurerons avec nos valeurs 
                      * par défaut et le mettrons à l'état de circuit afin qu'il puisse 
                      * être récupéré lors de futures propagations. */
            ret = new CounterData(null, Value.createKnown(width, 0));
            state.setData(ret);
        } else if(!ret.value.getBitWidth().equals(width)) {
            ret.value = ret.value.extendWidth(width.getWidth(), Value.FALSE);
        }
        return ret;
    }

    /** La dernière valeur d'entrée d'horloge observée. */
    private Value lastClock;
    
    /** La valeur courante émise par le compteur. */
    private Value value;

    /** Construit un état avec les valeurs données. */
    public CounterData(Value lastClock, Value value) {
        this.lastClock = lastClock;
        this.value = value;
    }

    /** Renvoie une copie de cet objet. */
    public Object clone() {
        /** Nous pouvons simplement utiliser ce que super.clone() renvoie : 
          * Les seules variables d'instance sont les objets Value, qui sont 
          * immuables, de sorte que nous ne nous soucions pas que la copie 
          *     et le copié fassent référence aux mêmes objets Value. 
          *     Si nous avions des variables d'instance mutables, nous aurions 
          *     bien sûr besoin de les cloner. */
        try { return super.clone(); }
        catch(CloneNotSupportedException e) { return null; }
    }
    
    /** Met à jour la dernière horloge observée, en renvoyant true si elle 
          * est déclenchée. */
    public boolean updateClock(Value value) {
        Value old = lastClock;
        lastClock = value;
        return old == Value.FALSE && value == Value.TRUE;
    }
    
    /** Renvoie la valeur courante émise par le compteur.  */
    public Value getValue() {
        return value;
    }
    
    /** Met à jour la valeur courante émise par le compteur. */
    public void setValue(Value value) {
        this.value = value;
    }
}

SimpleCounter

package com.cburch.gray;

import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.StringUtil;

/** Fabrique un simple compteur qui itère sur le code Gray de 4 bits. 
    Cet exemple illustre comment un composant peut maintenir son propre état interne. 
    Tout le code relatif à l'état apparaît dans la classe CounterData.. */
    class SimpleGrayCounter extends InstanceFactory {
    private static final BitWidth BIT_WIDTH = BitWidth.create(4);
    
    // Encore une fois, remarquez que nous n'avons pas de variables 
    // d'instance liées à l'état d'une instance individuelle. Nous ne pouvons pas 
    // mettre cela ici, parce qu'un seul objet SimpleGrayCounter est créé,
    // et son travail est de gérer toutes les instances qui apparaissent 
    // dans n'importe quel circuit.
    
    public SimpleGrayCounter() {
        super("Gray Counter (Simple)");
        setOffsetBounds(Bounds.create(-30, -15, 30, 30));
        setPorts(new Port[] {
                new Port(-30, 0, Port.INPUT, 1),
                new Port(  0, 0, Port.OUTPUT, BIT_WIDTH.getWidth()),
        });
    }

    public void propagate(InstanceState state) {
        // Ici, je récupère l'état associé à ce composant via une 
        // méthode helper. Dans ce cas, l'état se trouve dans un objet CounterData,
        // qui est également l'endroit où la méthode d'aide est définie. Cette 
        // méthode d'aide finira par créer un objet CounterData s'il n'en existe 
        // pas déjà un.
        CounterData cur = CounterData.get(state, BIT_WIDTH);

        boolean trigger = cur.updateClock(state.getPort(0));
        if(trigger) cur.setValue(GrayIncrementer.nextGray(cur.getValue()));
        state.setPort(1, cur.getValue(), 9);
        
        // (On pourrait être tenté de déterminer la valeur 
        // actuelle du compteur via state.getPort(1). Mais c'est une erreur, 
        // car un autre composant peut pousser une valeur sur le même point, 
        // ce qui "corromprait" la valeur trouvée à cet endroit. Nous avons 
        // vraiment besoin de stocker la valeur courante dans l'instance).
    }

    public void paintInstance(InstancePainter painter) {
        painter.drawBounds();
        painter.drawClock(0, Direction.EAST); // dessiner un triangle sur le port 0
        painter.drawPort(1); // dessiner le port 1 comme un simple point
        
        // Afficher la valeur actuelle du compteur centrée dans le rectangle.  
        // Cependant, si le contexte indique qu'il ne faut pas afficher l'état 
        // (comme lors de la génération d'une sortie imprimante), ignorez cette
        // étape.
        if(painter.getShowState()) {
            CounterData state = CounterData.get(painter, BIT_WIDTH);
            Bounds bds = painter.getBounds();
            GraphicsUtil.drawCenteredText(painter.getGraphics(),
                    StringUtil.toHexString(BIT_WIDTH.getWidth(), state.getValue().toIntValue()),
                    bds.getX() + bds.getWidth() / 2,
                    bds.getY() + bds.getHeight() / 2);
        }
    }
}

Suite : Gray Code Counter.