Инкрементатор кода Грея

Каждый компонент, включённый в библиотеку, объявляется путём создания подкласса InstanceFactory из пакета com.cburch.logisim.instance. Этот подкласс содержит весь необходимый для работы элемента код.

(Здесь описывается API актуальной версии программы. Вы можете встретить библиотеки, разработанные для старых версий Logisim, в которых компоненты определялись через два класса: один наследовал Component, другой — ComponentFactory. В версии 2.3.0 был введён гораздо более простой API InstanceFactory, а старый подход теперь считается устаревшим).

Большинство классов, необходимых для создания библиотек компонентов, определены в трёх основных пакетах:

com.cburch.logisim.instance

Содержит классы, непосредственно связанные с определением логики работы компонентов, включая InstanceFactory, InstanceState, InstancePainter и Instance.

com.cburch.logisim.data

Содержит классы для работы с данными, связанными с компонентами, такие как класс Bounds для представления ограничивающих прямоугольников элементов или класс Value для работы со значениями сигналов в проводниках.

com.cburch.logisim.tools

Содержит вспомогательные классы для описания самой структуры библиотек.

О коде Грея

Прежде чем двигаться дальше, кратко опишем код Грея, на котором основаны все примеры этого раздела. Это описание не является обязательным для понимания принципов построения библиотек, поэтому при желании (особенно если вы уже знакомы с кодом Грея) вы можете сразу перейти к разделу с исходным кодом ниже.

Код Грея — это способ кодирования (названный в честь Фрэнка Грея) для перебора последовательностей из $n$-битных слов, при котором на каждом шаге изменяется состояние ровно одного бита. В качестве примера ниже приведена таблица перебора 4-битных значений кода Грея.

0000
0001
0011
0010
       0110
0111
0101
0100
       1100
1101
1111
1110
       1010
1011
1001
1000

В каждом значении подчёркнут тот бит, который изменится при переходе к следующему значению последовательности. Например, вслед за 0000 идёт значение 0001, в котором инвертирован младший бит, поэтому этот младший бит подчёркнут.

Среди встроенных компонентов Logisim-evolution нет элементов для работы с кодом Грея, однако разработчики цифровой аппаратуры нередко находят его весьма полезным. Одним из ярких примеров использования кода Грея является его применение при разметке осей в картах Карно.

Класс GrayIncrementer

Ниже представлен минимальный пример кода, описывающий базовую структуру компонента. Данный элемент является инкрементатором, который принимает на входе многобитное значение и формирует на выходе следующее по порядку значение кода Грея.

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;

/** Этот компонент принимает многобитное значение на входе и выдаёт значение, следующее за ним
 * в коде Грея. Например, при входном значении 0100 на выходе будет 1100. */
class GrayIncrementer extends InstanceFactory {
    /* Обратите внимание, что здесь нет переменных экземпляра класса. Создаётся только один
     * экземпляр этой фабрики, который управляет всеми размещёнными на схеме элементами данного типа.
     * Любая информация, относящаяся к конкретным экземплярам на холсте, должна храниться
     * в их атрибутах. Для GrayIncrementer каждый экземпляр имеет настраиваемую разрядность (bit width),
     * поэтому мы добавим соответствующий атрибут. */

    /** Конструктор настраивает фабрику компонента. */
    GrayIncrementer() {
        super("Gray Code Incrementer");
        
        /* Здесь мы настраиваем список атрибутов для GrayIncrementer. В данном случае у нас
         * есть только один атрибут — разрядность (ширина шины) — со значением по умолчанию 4.
         * Класс StdAttr определяет несколько стандартных атрибутов, включая StdAttr.WIDTH.
         * Рекомендуется использовать стандартные атрибуты StdAttr везде, где это уместно:
         * благодаря этому пользователь сможет выделить несколько разнородных компонентов на схеме
         * и изменить их общие атрибуты (например, разрядность) одновременно для всех. */
        setAttributes(new Attribute[] { StdAttr.WIDTH },
                new Object[] { BitWidth.create(4) });
        
        /* Метод setOffsetBounds определяет габариты рамки компонента относительно его якоря (точки привязки).
         * Здесь мы выбираем размер компонента 30x30 пикселей и привязываем его к основному выходу
         * (что типично для Logisim-evolution), который располагается по центру правой стороны.
         * Таким образом, верхний левый угол ограничивающего прямоугольника смещён на 30 пикселей влево
         * и на 15 пикселей вверх относительно положения мыши. */
        setOffsetBounds(Bounds.create(-30, -15, 30, 30));
        
        /* Порты представляют собой точки подключения проводников к компоненту. При создании порта
         * указывается его положение относительно якоря компонента, тип порта (вход, выход или двунаправленный)
         * и разрядность порта. Разрядность может быть как константой (например, 1), так и значением
         * атрибута (как в данном случае). */
        setPorts(new Port[] {
                new Port(-30, 0, Port.INPUT, StdAttr.WIDTH),
                new Port(0, 0, Port.OUTPUT, StdAttr.WIDTH),
            });
    }

    /** Вычисляет текущее значение на выходе компонента. Этот метод вызывается каждый раз,
     * когда на любом из входов изменяется уровень сигнала; он также может вызываться
     * при других системных событиях, даже если нет явных причин ожидать изменения выхода. */
    public void propagate(InstanceState state) {
        // Сначала считываем текущее значение на входе. Обратите внимание, что в вызове
        // setPorts выше входной порт был передан под индексом 0 в массиве портов,
        // поэтому мы запрашиваем порт с индексом 0.
        Value in = state.getPort(0);
        
        // Вычисляем новое значение на выходе. Мы вынесли эту логику в отдельный статический метод,
        // так как аналогичные вычисления пригодятся и для других элементов нашей библиотеки.
        Value out = nextGray(in);
        
        // Передаём вычисленный сигнал на выходной порт. Первый параметр равен 1, так как
        // в массиве портов выход находится под индексом 1. Второй параметр — это передаваемое
        // значение. Третий параметр определяет задержку элемента (gate delay) — количество шагов
        // моделирования, через которое изменится выход после изменения входа.
        state.setPort(1, out, out.getWidth() + 1);
    }

    /** Описывает внешний вид конкретного экземпляра компонента на холсте. */
    public void paintInstance(InstancePainter painter) {
        // Класс InstancePainter содержит несколько встроенных удобных методов отрисовки,
        // которые мы здесь и используем. В более сложных случаях вы можете запросить
        // объект Graphics (painter.getGraphics) для рисования на холсте напрямую.
        painter.drawRectangle(painter.getBounds(), "G+1");
        painter.drawPorts();
    }
    
    /** Вычисляет следующее по порядку значение кода Грея вслед за prev. Этот статический
     * метод выполняет битовые операции и работает исключительно с классами Value и BitWidth. */
    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; // вычислить чётность x
        ct = (ct >> 8) ^ ct;
        ct = (ct >> 4) ^ ct;
        ct = (ct >> 2) ^ ct;
        ct = (ct >> 1) ^ ct;
        if((ct & 1) == 0) { // если чётность чётная, переключаем младший бит
            x = x ^ 1;
        } else { // иначе переключаем бит непосредственно над последней единицей
            int y = x ^ (x & (x - 1)); // сначала вычисляем положение последней единицы
            y = (y << 1) & bits.getMask();
            x = (y == 0 ? 0 : x ^ y);
        }
        return Value.createKnown(bits, x);
    }
}

Одного этого класса недостаточно для сборки полноценной библиотеки; вам также необходимо объявить специальный класс библиотеки (Library Class), описание которого приведено на следующей странице.

Далее: Класс Library.