Gray Code Incrementer
Each component included in a library is defined by creating a subclass of
InstanceFactory
found in the
com.cburch.logisim.instance
package. This subclass has all
the code involved
(Here we're describing the API for the current version of Logisim.
You may find some libraries developed for older versions of Logisim, in
which components were developed by defining two classes, one extending
Component
and another extending ComponentFactory
.
Version 2.3.0 introduced the much simpler InstanceFactory
API; the
older technique is deprecated.)
Three Logisim packages define most of the classes relevant to defining component libraries.
com.cburch.logisim.instance
Contains classes specifically related to defining components, including the
InstanceFactory
,InstanceState
,InstancePainter
, andInstance
classes.com.cburch.logisim.data
Contains classes related to data elements associated with components, such as the
Bounds
class for representing bounding rectangles or theValue
class for representing values that can exist on a wire.com.cburch.logisim.tools
Contains classes related to the library definition.
About Gray codes
Before we go on, let me briefly describe the Gray code on which these examples are based. It's not really important to understanding how these examples work, so you can safely skip to the code below if you wish - particularly if you already know Gray codes.
Gray code is a technique (named after Frank Gray) for iterating through n-bit sequences with only one bit changed for each step. As an example, consider the 4-bit Gray code listed below.
0000
0001
0011
00100110
0111
0101
01001100
1101
1111
11101010
1011
1001
1000
Each value has the bit underlined that will change for the next value in the sequence. For example, after 0000 comes 0001, in which the final bit has been toggled, so the final bit is underlined.
Logisim's built-in components don't include anything working with Gray codes. But electronics designers find Gray codes useful sometimes. One particularly notable instance of Gray codes is along the axes in Karnaugh maps.
GrayIncrementer
This is a minimal example illustrating the essential elements to defining a component. This particular component is an incrementer, which takes an multibit input and produces the next Gray code following it in sequence.
package com.cburch.gray; import com.cburch.logisim.data.Attribute; import com.cburch.logisim.data.BitWidth; import com.cburch.logisim.data.Bounds; import com.cburch.logisim.data.Value; 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.instance.StdAttr; /** This component takes a multibit input and outputs the value that follows it * in Gray Code. For instance, given input 0100 the output is 1100. */ class GrayIncrementer extends InstanceFactory { /* Note that there are no instance variables. There is only one instance of * this class created, which manages all instances of the component. Any * information associated with individual instances should be handled * through attributes. For GrayIncrementer, each instance has a "bit width" * that it works with, and so we'll have an attribute. */ /** The constructor configures the factory. */ GrayIncrementer() { super("Gray Code Incrementer"); /* This is how we can set up the attributes for GrayIncrementers. In * this case, there is just one attribute - the width - whose default * is 4. The StdAttr class defines several commonly occurring * attributes, including one for "bit width." It's best to use those * StdAttr attributes when appropriate: A user can then select several * components (even from differing factories) with the same attribute * and modify them all at once. */ setAttributes(new Attribute[] { StdAttr.WIDTH }, new Object[] { BitWidth.create(4) }); /* The "offset bounds" is the location of the bounding rectangle * relative to the mouse location. Here, we're choosing the component to * be 30x30, and we're anchoring it relative to its primary output * (as is typical for Logisim), which happens to be in the center of the * east edge. Thus, the top left corner of the bounding box is 30 pixels * west and 15 pixels north of the mouse location. */ setOffsetBounds(Bounds.create(-30, -15, 30, 30)); /* The ports are locations where wires can be connected to this * component. Each port object says where to find the port relative to * the component's anchor location, then whether the port is an * input/output/both, and finally the expected bit width for the port. * The bit width can be a constant (like 1) or an attribute (as here). */ setPorts(new Port[] { new Port(-30, 0, Port.INPUT, StdAttr.WIDTH), new Port(0, 0, Port.OUTPUT, StdAttr.WIDTH), }); } /** Computes the current output for this component. This method is invoked * any time any of the inputs change their values; it may also be invoked in * other circumstances, even if there is no reason to expect it to change * anything. */ public void propagate(InstanceState state) { // First we retrieve the value being fed into the input. Note that in // the setPorts invocation above, the component's input was included at // index 0 in the parameter array, so we use 0 as the parameter below. Value in = state.getPort(0); // Now compute the output. We've farmed this out to a helper method, // since the same logic is needed for the library's other components. Value out = nextGray(in); // Finally we propagate the output into the circuit. The first parameter // is 1 because in our list of ports (configured by invocation of // setPorts above) the output is at index 1. The second parameter is the // value we want to send on that port. And the last parameter is its // "delay" - the number of steps it will take for the output to update // after its input. state.setPort(1, out, out.getWidth() + 1); } /** Says how an individual instance should appear on the canvas. */ public void paintInstance(InstancePainter painter) { // As it happens, InstancePainter contains several convenience methods // for drawing, and we'll use those here. Frequently, you'd want to // retrieve its Graphics object (painter.getGraphics) so you can draw // directly onto the canvas. painter.drawRectangle(painter.getBounds(), "G+1"); painter.drawPorts(); } /** Computes the next gray value in the sequence after prev. This static * method just does some bit twiddling; it doesn't have much to do with * Logisim except that it manipulates Value and BitWidth objects. */ static Value nextGray(Value prev) { BitWidth bits = prev.getBitWidth(); if(!prev.isFullyDefined()) return Value.createError(bits); int x = prev.toIntValue(); int ct = (x >> 16) ^ x; // compute parity of x ct = (ct >> 8) ^ ct; ct = (ct >> 4) ^ ct; ct = (ct >> 2) ^ ct; ct = (ct >> 1) ^ ct; if((ct & 1) == 0) { // if parity is even, flip 1's bit x = x ^ 1; } else { // else flip bit just above last 1 int y = x ^ (x & (x - 1)); // first compute the last 1 y = (y << 1) & bits.getMask(); x = (y == 0 ? 0 : x ^ y); } return Value.createKnown(bits, x); } }
This example by itself is not enough to create a working JAR file; you must also provide a Library class, as illustrated on the next page.
Next: Library Class.