通常我们想要的组件本质上不完全是组合的——也就是说,我们希望组件有一些内存。 定义此类组件有一个重要的微妙之处:您不能让组件本身存储状态,因为单个组件可以在同一电路中出现多次。 它不能直接在电路中多次出现,但如果它出现在多次使用的子电路中,则可以多次出现。
解决方案是创建一个新类来表示对象的当前状态,并通过父电路的状态将其实例与组件关联起来。 在此示例中,实现了边沿触发的 4 位格雷码计数器,除了如图所示的
InstanceFactory
子类之外,我们还定义了一个
CounterData
类来表示计数器的状态 之前。
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; /** Represents the state of a counter. */ class CounterData implements InstanceData, Cloneable { /** Retrieves the state associated with this counter in the circuit state, * generating the state if necessary. */ public static CounterData get(InstanceState state, BitWidth width) { CounterData ret = (CounterData) state.getData(); if(ret == null) { // If it doesn't yet exist, then we'll set it up with our default // values and put it into the circuit state so it can be retrieved // in future 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; } /** The last clock input value observed. */ private Value lastClock; /** The current value emitted by the counter. */ private Value value; /** Constructs a state with the given values. */ public CounterData(Value lastClock, Value value) { this.lastClock = lastClock; this.value = value; } /** Returns a copy of this object. */ public Object clone() { // We can just use what super.clone() returns: The only instance variables are // Value objects, which are immutable, so we don't care that both the copy // and the copied refer to the same Value objects. If we had mutable instance // variables, then of course we would need to clone them. try { return super.clone(); } catch(CloneNotSupportedException e) { return null; } } /** Updates the last clock observed, returning true if triggered. */ public boolean updateClock(Value value) { Value old = lastClock; lastClock = value; return old == Value.FALSE && value == Value.TRUE; } /** Returns the current value emitted by the counter. */ public Value getValue() { return value; } /** Updates the current value emitted by the counter. */ public void setValue(Value value) { this.value = value; } }
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; /** Manufactures a simple counter that iterates over the 4-bit Gray Code. This * example illustrates how a component can maintain its own internal state. All * of the code relevant to state, though, appears in CounterData class. */ class SimpleGrayCounter extends InstanceFactory { private static final BitWidth BIT_WIDTH = BitWidth.create(4); // Again, notice how we don't have any instance variables related to an // individual instance's state. We can't put that here, because only one // SimpleGrayCounter object is ever created, and its job is to manage all // instances that appear in any circuits. 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) { // Here I retrieve the state associated with this component via a helper // method. In this case, the state is in a CounterData object, which is // also where the helper method is defined. This helper method will end // up creating a CounterData object if one doesn't already exist. 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); // (You might be tempted to determine the counter's current value // via state.getPort(1). This is erroneous, though, because another // component may be pushing a value onto the same point, which would // "corrupt" the value found there. We really do need to store the // current value in the instance.) } public void paintInstance(InstancePainter painter) { painter.drawBounds(); painter.drawClock(0, Direction.EAST); // draw a triangle on port 0 painter.drawPort(1); // draw port 1 as just a dot // Display the current counter value centered within the rectangle. // However, if the context says not to show state (as when generating // printer output), then skip this. 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); } } }
Next: Gray Code Counter .