C#の演算子オーバーロードを用いてフリップフロップ回路をシミュレートします。
下準備
C#のプリミティブ型であるbool型で論理積や論理和を再現するとなると「A = true | false」などのようになってしまいます。 そこで、新たに「X = A + B * C」の様な記述ができるExBooleanというbool型を拡張(?)した型を用意し、それを用いることにします。 このExBoolean型は + 演算子で論理和、 * 演算子で論理積、 ~ 演算子で論理否定を演算するように設計します。
using System;
namespace FlipFlop
{
// 拡張ブール型
public struct ExBoolean
{
private bool m_Value;
// コンストラクタ
public ExBoolean( bool Value )
{
m_Value = Value;
}
// コンストラクタ
public ExBoolean( int Value )
{
m_Value = ( Value == 0 ) ? false : true;
}
// 値を取得
public bool Value
{
get { return m_Value; }
}
// 真か
public bool IsTrue()
{
return m_Value ? true : false;
}
// 偽か
public bool IsFalse()
{
return m_Value ? false : true;
}
// 論理和
public static ExBoolean operator +( ExBoolean op1, ExBoolean op2 )
{
return new ExBoolean( op1.Value | op2.Value );
}
// 論理積
public static ExBoolean operator *( ExBoolean op1, ExBoolean op2 )
{
return new ExBoolean( op1.Value & op2.Value );
}
// 論理否定
public static ExBoolean operator ~( ExBoolean op )
{
return new ExBoolean( !op.Value );
}
// 暗黙のキャスト ( int to ExBoolean )
public static implicit operator ExBoolean ( int op )
{
return new ExBoolean( op );
}
// 暗黙のキャスト ( bool to ExBoolean )
public static implicit operator ExBoolean ( bool op )
{
return new ExBoolean( op );
}
// ToString
public override string ToString()
{
return m_Value ? "1 (True)" : "0 (False)";
}
public string ToString( string format )
{
if ( format == "B" )
{
// ブール型で表記
return m_Value ? "True" : "False";
}
else if ( format == "I" )
{
// 数値で表記
return m_Value ? "1" : "0";
}
else
{
// 標準
return m_Value ? "1 (True)" : "0 (False)";
}
}
}
}
これ以降はこのExBoolean型を用いて論理演算を行うことにします。 実際にちゃんと動作するかを確認するため、真理値表を出力させておきます。
using System;
namespace FlipFlop
{
class FlipFlopMain
{
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
static void Main()
{
ExBoolean a, b, x;
// 論理和の真理値表
Console.WriteLine( "論理和の真理値表" );
Console.WriteLine( "X = A + B" );
Console.WriteLine( " A B | X" );
Console.WriteLine( "--------+----" );
for ( int left = 0; left < 2; left++ )
{
for ( int right = 0; right < 2; right++ )
{
a = (ExBoolean)left;
b = (ExBoolean)right;
x = a + b;
Console.WriteLine( " {0} {1} | {2}", a.ToString("I") , b.ToString("I"), x.ToString("I") );
}
}
// 論理積の真理値表
Console.WriteLine( "論理積の真理値表" );
Console.WriteLine( "X = A * B" );
Console.WriteLine( " A B | X" );
Console.WriteLine( "--------+----" );
for ( int left = 0; left < 2; left++ )
{
for ( int right = 0; right < 2; right++ )
{
a = (ExBoolean)left;
b = (ExBoolean)right;
x = a * b;
Console.WriteLine( " {0} {1} | {2}", a.ToString("I") , b.ToString("I"), x.ToString("I") );
}
}
// 論理否定の真理値表
Console.WriteLine( "論理否定の真理値表" );
Console.WriteLine( "X = ~A" );
Console.WriteLine( " A | X" );
Console.WriteLine( "----+----" );
for ( int op = 0; op < 2; op++ )
{
a = (ExBoolean)op;
x = ~a;
Console.WriteLine( " {0} | {1}", a.ToString("I") , x.ToString("I") );
}
}
}
}
論理和の真理値表 X = A + B A B | X --------+---- 0 0 | 0 0 1 | 1 1 0 | 1 1 1 | 1 論理積の真理値表 X = A * B A B | X --------+---- 0 0 | 0 0 1 | 0 1 0 | 0 1 1 | 1 論理否定の真理値表 X = ~A A | X ----+---- 0 | 1 1 | 0 Press any key to continue
RS型フリップフロップ
シミュレーションを行う前に、ここではRS型フリップフロップをクラス化してみます。 ここでフリップフロップについて簡単に説明しておくと、フリップフロップとは論理回路における基本的な素子で、入力があるまで現在の状態を保持し、入力があった時点でその状態が変化するという素子です。 これは一つの「状態」を表現できるということで、1Bitの記憶容量を持つといえます。 実際、半導体メモリはこのフリップフロップ素子を応用したものです。
RS型フリップフロップ(RS-FF)は二つの入力端R(Reset),S(Set)をもち、Q及び~Qを出力端に持つフリップフロップです。 RS-FFについて、入力に対する次の状態および動作を表にしたものが次の表です。

入力 | 次の状態 | 動作 | |
---|---|---|---|
S | R | Qn+1 | |
0 | 0 | Qn | 不変 |
0 | 1 | 0 | リセット |
1 | 0 | 1 | セット |
1 | 1 | - | (禁止) |
この図の下に書いてある式は特性方程式といいます。 式が示すとおり、以前の状態と、入力される値によって次の状態が定まります。 また、RS-FFでは R, Sともに1を入力することを禁止しています。 ただ、これは現実の問題であって、プログラム上では特に問題ありません。 一応プログラム中でもこの入力は禁止することにします。 これらをふまえて実際にRS-FFをクラスかしたものが次のコードです。
using System;
namespace FlipFlop
{
// RS-FF, RS型フリップフロップ
public class RSFlipFlop
{
private ExBoolean m_Q;
private ExBoolean m_S;
private ExBoolean m_R;
// 出力Q
public ExBoolean Q
{
get { return m_Q; }
}
// 入力S
public ExBoolean S
{
get { return m_S; }
}
// 入力R
public ExBoolean R
{
get { return m_R; }
}
// コンストラクタ
public RSFlipFlop()
{
m_Q = 0;
m_S = 0;
m_R = 0;
}
// セット
public void Set( ExBoolean S, ExBoolean R )
{
// S = 1, R = 1の入力は禁止
if ( S.IsTrue() && R.IsTrue() ) return;
m_Q = S + ~R * m_Q;
m_S = S;
m_R = R;
}
}
}
現在の状態Qはm_Qに保持させ、プロパティQで参照できます。 m_S及びm_Rは最後に入力されたS及びRを保持するためのものなのですが、フリップフロップの動作には無関係です。 実際に入力を与えるためのメソッドがSet()です。 特性方程式そのままなので簡単に理解できるかと思います。
つぎに、このクラスの動作を検証するコードを記述します。 実行結果は動作通りのものになっているはずです。
using System;
namespace FlipFlop
{
public class FlipFlopMain
{
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
static void Main()
{
RSFlipFlop rsff = new RSFlipFlop();
Console.WriteLine( "Current state" );
Console.WriteLine( "Q = {0}", rsff.Q );
rsff.Set( 0, 1 );
Console.WriteLine( "Set: S = {0}, R = {1} --> Q = {2}", rsff.S, rsff.R, rsff.Q );
rsff.Set( 1, 0 );
Console.WriteLine( "Set: S = {0}, R = {1} --> Q = {2}", rsff.S, rsff.R, rsff.Q );
rsff.Set( 0, 0 );
Console.WriteLine( "Set: S = {0}, R = {1} --> Q = {2}", rsff.S, rsff.R, rsff.Q );
}
}
}
Current state Q = 0 (False) Set: S = 0 (False), R = 1 (True) --> Q = 0 (False) Set: S = 1 (True), R = 0 (False) --> Q = 1 (True) Set: S = 0 (False), R = 0 (False) --> Q = 1 (True) Press any key to continue
続いて、RS-FF以外の様々なフリップフロップを説明・クラス化していきます。
JK型、T型、D型フリップフロップ
まず、JK型フリップフロップですが、これはRS-FFとは異なり、J=K=1の入力が許可されたものです。 また、このときJK-FFの状態は反転します。

入力 | 次の状態 | 動作 | |
---|---|---|---|
J | K | Qn+1 | |
0 | 0 | Qn | 不変 |
0 | 1 | 0 | リセット |
1 | 0 | 1 | セット |
1 | 1 | ~Qn | 反転 |
クラス化したものを以下に示しますが、検証は省略します。
using System;
namespace FlipFlop
{
// JK-FF, JK型フリップフロップ
public class JKFlipFlop
{
private ExBoolean m_Q;
private ExBoolean m_J;
private ExBoolean m_K;
// 出力Q
public ExBoolean Q
{
get { return m_Q; }
}
// 入力J
public ExBoolean J
{
get { return m_J; }
}
// 入力K
public ExBoolean K
{
get { return m_K; }
}
// コンストラクタ
public JKFlipFlop()
{
m_Q = 0;
m_J = 0;
m_K = 0;
}
// セット
public void Set( ExBoolean J, ExBoolean K )
{
m_Q = ~K * m_Q + J * ~m_Q;
m_J = J;
m_K = K;
}
}
}
続いて、T型フリップフロップです。 これはRS-FFやJK-FFとは違い、入力端が一つしかありません。 T-FFは入力Tが 1 になる度に状態が反転します。

入力 | 次の状態 | 動作 |
---|---|---|
T | Qn+1 | |
0 | Qn | 不変 |
1 | ~Qn | 反転 |
クラス化したものを以下に示しますが、例によって検証は省略します。
using System;
namespace FlipFlop
{
// T-FF, T型フリップフロップ
public class TFlipFlop
{
private ExBoolean m_Q;
private ExBoolean m_T;
// 出力Q
public ExBoolean Q
{
get { return m_Q; }
}
// 入力T
public ExBoolean T
{
get { return m_T; }
}
// コンストラクタ
public TFlipFlop()
{
m_Q = 0;
m_T = 0;
}
// セット
public void Set( ExBoolean T )
{
m_Q = ~T * m_Q + T * ~m_Q;
m_T = T;
}
}
}
最後に、D型フリップフロップです。 これは次に入力があるまで、現在の状態を保持します。 つまり、実際には1Bitのメモリとして動作します。 (そのため極端にいえばD-FFはクラス化するまでもなく、ブール型の変数一つあれば同等の機能を得ることができます。)

入力 | 次の状態 | 動作 |
---|---|---|
T | Qn+1 | |
0 | 0 | リセット |
1 | 1 | セット |
形式的にD-FFもクラス化していますが、事実上ExBoolean型の動作と変わりません。
using System;
namespace FlipFlop
{
// D-FF, D型フリップフロップ
public class DFlipFlop
{
private ExBoolean m_Q;
private ExBoolean m_D;
// 出力Q
public ExBoolean Q
{
get { return m_Q; }
}
// 入力D
public ExBoolean D
{
get { return m_D; }
}
// コンストラクタ
public DFlipFlop()
{
m_Q = 0;
m_D = 0;
}
// セット
public void Set( ExBoolean D )
{
m_Q = D;
m_D = D;
}
}
}
RS型フリップフロップの動作をシミュレートする
シミュレートするといっても、クラスが完成した時点でほとんどシミュレートできたようなものなのですが、いまいち目に見える動作というのがないのでフリップフロップの状態を視覚化できるようにしてみます。
次のソースコードは、今までに説明したRS-FFのクラスを用い、フォーム上に二つの入力波形と、フリップフロップの出力波形の三つの波形を、横軸に時間軸をとって表示させるものです。 また、入力の変化によってどのように状態が変化するかを見るために、チェックボックスで入力値を変えられるようにしています。 このコードをコピペするだけでちゃんと動作すると思います(もちろん先ほどのクラスもプロジェクトに存在するものとします)。
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace FlipFlop
{
/// <summary>
/// Form1 の概要の説明です。
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.ComponentModel.IContainer components = null;
public Form1()
{
//
// Windows フォーム デザイナ サポートに必要です。
//
InitializeComponent();
//
// TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。
//
}
/// <summary>
/// 使用されているリソースに後処理を実行します。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// デザイナ サポートに必要なメソッドです。このメソッドの内容を
/// コード エディタで変更しないでください。
/// </summary>
private void InitializeComponent()
{
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
this.ClientSize = new System.Drawing.Size(432, 310);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
}
#endregion
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
// 最大分割数
private const int divisionMax = 50;
// RS-FF
private RSFlipFlop rsff;
// 値の状態推移
private Queue transitionQ;
private Queue transitionInput1;
private Queue transitionInput2;
// 波形出力用ピクチャボックス
private PictureBox pictureBoxSignal;
// 波形出力用タイマー
private Timer timerSignal;
// 入力信号用チェックボックス
private CheckBox checkBoxInput1;
private CheckBox checkBoxInput2;
// フォーム読込時
private void Form1_Load(object sender, System.EventArgs e)
{
// フリップフロップ
rsff = new RSFlipFlop();
// 値の状態推移
transitionQ = new Queue( divisionMax );
for ( int i = 0; i < divisionMax; i++ )
{
transitionQ.Enqueue( rsff.Q );
}
transitionInput1 = new Queue( divisionMax );
for ( int i = 0; i < divisionMax; i++ )
{
transitionInput1.Enqueue( rsff.S );
}
transitionInput2 = new Queue( divisionMax );
for ( int i = 0; i < divisionMax; i++ )
{
transitionInput2.Enqueue( rsff.R );
}
// フォーム
this.ClientSize = new Size( 340, 250 );
this.Text = "FlipFlop";
this.FormBorderStyle = FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
// pictureBoxSignal
pictureBoxSignal = new PictureBox();
pictureBoxSignal.Location = new Point( 10, 10 );
pictureBoxSignal.Size = new Size( 320, 200 );
pictureBoxSignal.Paint += new PaintEventHandler( this.pictureBoxSignal_Paint );
this.Controls.Add( pictureBoxSignal );
// timerSignal
timerSignal = new Timer();
timerSignal.Interval = 200;
timerSignal.Tick += new EventHandler( this.timerSignal_Tick );
timerSignal.Start();
// checkBoxInput
checkBoxInput1 = new CheckBox();
checkBoxInput1.Text = "入力1";
checkBoxInput1.Checked = false;
checkBoxInput1.Location = new Point( 50, 220 );
checkBoxInput1.Size = new Size( 100, 20 );
checkBoxInput1.CheckedChanged += new EventHandler( this.checkBoxInput_CheckedChanged );
checkBoxInput2 = new CheckBox();
checkBoxInput2.Text = "入力2";
checkBoxInput2.Checked = false;
checkBoxInput2.Location = new Point( 170, 220 );
checkBoxInput2.Size = new Size( 100, 20 );
checkBoxInput2.CheckedChanged += new EventHandler( this.checkBoxInput_CheckedChanged );
this.Controls.Add( checkBoxInput1 );
this.Controls.Add( checkBoxInput2 );
}
// チェックボックスのチェック状態が変化したとき
private void checkBoxInput_CheckedChanged( object sender, EventArgs e )
{
// 各チェックボックスのチェック状態を信号のありなしと解釈
ExBoolean S = checkBoxInput1.Checked;
ExBoolean R = checkBoxInput2.Checked;
rsff.Set( S, R );
}
// 描画処理呼び出し
private void pictureBoxSignal_Paint( object sender, PaintEventArgs e )
{
RefreshSignal( e.Graphics );
}
// 波形出力用タイマー
private void timerSignal_Tick( object sender, EventArgs e )
{
// 状態推移を更新
transitionQ.Dequeue();
transitionQ.Enqueue( rsff.Q );
transitionInput1.Dequeue();
transitionInput1.Enqueue( rsff.S );
transitionInput2.Dequeue();
transitionInput2.Enqueue( rsff.R );
// 信号波形を描画
Graphics g = pictureBoxSignal.CreateGraphics();
RefreshSignal( g );
g.Dispose();
}
// 信号波形を描画する
private void RefreshSignal( Graphics g )
{
if ( g == null ) return;
// バッファを作成
Bitmap bBuff = new Bitmap( pictureBoxSignal.Width, pictureBoxSignal.Height );
Graphics gBuff = Graphics.FromImage( (Image)bBuff );
// 黒で背景をクリア
gBuff.Clear( Color.Black );
float waveWidth = pictureBoxSignal.Width / (float)divisionMax;
float x = pictureBoxSignal.Width;
float y = 20.0f;
ExBoolean valLast = new ExBoolean( false );
// Qの波形を出力
gBuff.DrawString( "Signal of Q", Control.DefaultFont, Brushes.LightGreen, 0, y + 35.0f );
foreach ( ExBoolean val in transitionQ )
{
if ( val.IsTrue() )
{
if ( valLast.IsFalse() ) gBuff.DrawLine( Pens.LightGreen, x, y + 30.0f, x, y );
gBuff.DrawLine( Pens.LightGreen, x, y, x - waveWidth, y);
}
else
{
if ( valLast.IsTrue() ) gBuff.DrawLine( Pens.LightGreen, x, y + 30.0f, x, y );
gBuff.DrawLine( Pens.LightGreen, x, y + 30.0f, x - waveWidth, y + 30.0f );
}
x -= waveWidth;
valLast = val;
}
// 入力1の波形を出力
x = pictureBoxSignal.Width;
y = 100.0f;
valLast = new ExBoolean( false );
gBuff.DrawString( "Signal of input 1", Control.DefaultFont, Brushes.Orange, 0, y + 25.0f );
foreach ( ExBoolean val in transitionInput1 )
{
if ( val.IsTrue() )
{
if ( valLast.IsFalse() ) gBuff.DrawLine( Pens.Orange, x, y + 20.0f, x, y );
gBuff.DrawLine( Pens.Orange, x, y, x - waveWidth, y);
}
else
{
if ( valLast.IsTrue() ) gBuff.DrawLine( Pens.Orange, x, y + 20.0f, x, y );
gBuff.DrawLine( Pens.Orange, x, y + 20.0f, x - waveWidth, y + 20.0f );
}
x -= waveWidth;
valLast = val;
}
// 入力2の波形を出力
x = pictureBoxSignal.Width;
y = 140.0f;
valLast = new ExBoolean( false );
gBuff.DrawString( "Signal of input 2", Control.DefaultFont, Brushes.Yellow, 0, y + 25.0f );
foreach ( ExBoolean val in transitionInput2 )
{
if ( val.IsTrue() )
{
if ( valLast.IsFalse() ) gBuff.DrawLine( Pens.Yellow, x, y + 20.0f, x, y );
gBuff.DrawLine( Pens.Yellow, x, y, x - waveWidth, y);
}
else
{
if ( valLast.IsTrue() ) gBuff.DrawLine( Pens.Yellow, x, y + 20.0f, x, y );
gBuff.DrawLine( Pens.Yellow, x, y + 20.0f, x - waveWidth, y + 20.0f );
}
x -= waveWidth;
valLast = val;
}
// バッファをコピー
g.DrawImage( bBuff, 0, 0 );
// バッファをディスポーズ
gBuff.Dispose();
bBuff.Dispose();
}
}
}

実行すると上の図のような感じになります。 チェックボックスをクリックして入力信号に変化を与えると、フリップフロップの状態も変化します。 実際のフリップフロップと同じ動作をするか、確認してみて下さい。 このコードではRS-FFだけをシミュレートしましたが、少し変えるだけでJK-FFやT- FFもシミュレートできるようになるので試しにやってみてください。