RPGツクールと数学のブログ

RPGツクールと数学についてのブログです。

C++で論理回路のシミュレーション

過去ブログの転載です。

論理回路のシミュレーションをプログラミングで行うための支援ライブラリを作りました。

GitHub - fermiumbay/LogicSimulator: 論理回路シミュレータ

そういうクラスを作っただけで画面も何も出ないですけど、部品を作って組み合わせるという、ホントに回路を組み立てるような感覚で作ることができます。

論理計算を行うだけであれば、単に計算式を記述して答えを出させれば済みますが、このライブラリは素子を登録してそれぞれ接続し、回路を作ることが出来るのが特徴です。計算式に基づいて出力値が出るのではなく、出力値を得たい素子の値からたどって計算を行います。素子を接続して実装することから、順序回路のような循環している回路も作成できます。

本ライブラリはC++用です。(C++11以降)

導入

上のリンクからLogicParts.hとLogicParts.cppをダウンロードし、cppファイルをコンパイルするソースに入れてください。使用するときはLogicParts.hをインクルードします。

f:id:fermiumbay13:20190802022036p:plain

例えば上のような回路を実装する場合は次のように記述します。

#include <iostream>
using namespace std;

#include "LogicParts.h"
using namespace LogicParts;

int main() {
    auto A = Terminal();    // 端子Aの定義
    auto B = Terminal();    // 端子Bの定義
    auto C = And(Or(A, B), Nand(A, B));    // 端子Cに論理回路を接続

    A->set(0);    // 端子Aに入力値0をセット
    B->set(1);    // 端子Bに入力値1をセット

    cout << C->get() << endl;    // 端子Cの値を出力
}

最初に各端子を定義して論理回路を作ります。

変数の型は面倒なのでautoで宣言していますが、BaseParts*でもOKです。後は端子にset関数で入力値をセットして、出力値を得たい端子のget関数を呼べばOKです。上の例はXor回路なので、A=Bのとき0、A≠Bのとき1が返ります。

半加算器

f:id:fermiumbay13:20190802022048p:plain

上図は半加算器HAの例です。出力が2つありますが、同様にして回路を作れます。

#include <iostream>
using namespace std;

#include "LogicParts.h"
using namespace LogicParts;

int main() {
    auto A = Terminal();    // 端子Aの定義
    auto B = Terminal();    // 端子Bの定義
    auto C = And(A, B);    // 端子CにAnd素子を接続
    auto S = Xor(A, B);    // 端子SにXor素子を接続

    A->set(0);    // 端子Aに入力値0をセット
    B->set(1);    // 端子Bに入力値1をセット

    cout << C->get() << endl;    // 端子Cの値を出力
    cout << S->get() << endl;    // 端子Sの値を出力
}

このHAの部分だけを一つの部品として、次のように関数を作っておくと便利です。

#include <iostream>
using namespace std;

#include "LogicParts.h"
using namespace LogicParts;

// 半加算器(A, B) = (C, S)
vector<BaseParts*> HA(BaseParts* A, BaseParts* B) {
    auto C = And(A, B);    // 端子CにAnd素子を接続
    auto S = Xor(A, B);    // 端子SにXor素子を接続
    return { C, S };    // 出力端子の配列を返す
}

int main() {
    auto A = Terminal();    // 端子Aの定義
    auto B = Terminal();    // 端子Bの定義
    auto D = HA(A, B);    // 端子DにA, Bを接続したHAを接続
    auto C = D[0];    // A, Bを接続したHAの出力C
    auto S = D[1];    // A, Bを接続したHAの出力S

    A->set(0);    // 端子Aに入力値0をセット
    B->set(1);    // 端子Bに入力値1をセット

    cout << C->get() << endl;    // 端子Cの値を出力
    cout << S->get() << endl;    // 端子Sの値を出力
}

全加算器

f:id:fermiumbay13:20190802022100p:plain

HAを接続して全加算器FAを作ります。

#include <iostream>
using namespace std;

#include "LogicParts.h"
using namespace LogicParts;

// 半加算器(A, B) = (C, S)
vector<BaseParts*> HA(BaseParts* A, BaseParts* B) {
    auto C = And(A, B);    // 端子CにAnd素子を接続
    auto S = Xor(A, B);    // 端子SにXor素子を接続
    return{ C, S };    // 出力端子の配列を返す
}

// 全加算器(A, B, prevC) = (C, S)
vector<BaseParts*> FA(BaseParts* A, BaseParts* B, BaseParts* prevC) {
    auto D = HA(A, B);    // HAの出力を端子群Dとして定義
    auto E = HA(D[1], prevC);    // HAの出力を端子群Eとして定義
    auto C = Or(D[0], E[0]);    // HAの出力とOr素子を接続して端子Cに接続
    auto S = E[1];    // HAの出力を端子Sに接続
    return{ C, S };    // 出力端子の配列を返す
}

int main() {
    auto A = Terminal();    // 端子Aの定義
    auto B = Terminal();    // 端子Bの定義
    auto prevC = Terminal();    // 端子prevCの定義
    auto C = FA(A, B, prevC)[0];    // A, Bを接続したFAの出力C
    auto S = FA(A, B, prevC)[1];    // A, Bを接続したFAの出力S

    A->set(1);    // 端子Aに入力値1をセット
    B->set(1);    // 端子Bに入力値1をセット
    prevC->set(1);    // 端子prevCに入力値1をセット

    cout << C->get() << endl;    // 端子Cの値を出力
    cout << S->get() << endl;    // 端子Sの値を出力
}

一旦部品を作ってしまえば取り扱いが楽になります。

RSフリップフロップ

f:id:fermiumbay13:20190802022113p:plain

入力端子を循環させれば順序回路も作れます。

#include <iostream>
using namespace std;

#include "LogicParts.h"
using namespace LogicParts;

// RSフリップフロップ
vector<BaseParts*> RS_FF(BaseParts* S, BaseParts* R) {
    auto Q = Nand(Not(S), 0);    // 端子Q
    auto notQ = Nand(Not(R), Q);    // 端子notQ
    Q->input[1] = notQ;    // 端子Qの入力端子の一つにnotQをセット

    return{ Q, notQ };    // 出力端子の配列を返す
}

int main() {
    auto S = Terminal();    // 端子Sの定義
    auto R = Terminal();    // 端子Rの定義
    auto Q = RS_FF(S, R)[0];    // 出力QをA, Bを接続したHAの出力C

    S->set(0); R->set(0); cout << Q->get() << endl;    // 保持
    S->set(1); R->set(0); cout << Q->get() << endl;    // Q=1にセット
    S->set(0); R->set(0); cout << Q->get() << endl;    // 保持
    S->set(0); R->set(1); cout << Q->get() << endl;    // Q=0にセット
    S->set(0); R->set(0); cout << Q->get() << endl;    // 保持
}

notQが未定義の状態でQ = Nand(Not(S), notQ)とは出来ませんから、上記のように、一旦notQの部分は0(未接続)としておいて、notQを作ってからQの入力端子にnotQをセットする、という方法で接続しています。

うまいように設計してやれば、大規模な論理回路も作れそうですね!