4.制御端子

本章では制御内部端子制御入力端子制御出力端子について解説します。
また、制御端子内に限り記述可能なseqブロックについて解説します。

NSLではinput,output,inoutなどのデータの流れとは別に、制御の流れ(path)を記述します。
制御端子は制御の流れを記述する信号の事で、内部・入力・出力の3種類があります。
モジュール内部の制御端子が制御内部端子
モジュール外部から入ってくる制御端子が制御入力端子
モジュール外部へ出ていく制御端子が制御出力端子です。

制御端子は、“ファンクション”という要素とセットで使います。
制御端子は、モジュール内に存在する”ファンクション”をモジュールの内外から起動します。
また、ファンクションは動作記述を集約したものを指します。

4-1.制御内部端子

制御内部端子は、ファンクションをモジュール内部から起動する制御端子です。
したがって、モジュールの外から制御内部端子を起動することはできません。
制御内部端子の宣言方法は以下になります。

func_self 制御内部端子名

また、制御の起動に仮引数を持つことができます。
仮引数は以下の様に記述します。

func_self 制御内部端子名(仮引数, 仮引数2, … 仮引数x)

制御内部端子の仮引数は内部端子に限ります。
()の中に示す内部端子が仮引数になり、起動時の実引数の値を転送します。

また、言語仕様上制御とデータの区別を付けるために、
起動はC言語などの関数呼び出しと同様の文法を用います。
以下が制御内部端子の起動方法です。

制御内部端子名()

そして、制御内部端子の起動時に実引数を持たせる場合は、
以下の様に記述します。

制御内部端子名(実引数, 実引数2, … 実引数x)

複数の制御内部端子名を指定する時には’,’で区切って指定します。
また、制御内部端子に対応した動作の集合”ファンクション”を
定義コマンド”function”を用いて記述します。

function 制御端子名 アクション記述

まず、制御内部端子の働きを例題を交えて解説します。
次の例題22を見てみましょう。

【例題22.制御内部端子使用例】
declare ex22 {
   input a[4],b[4] ;
   output f[4] ;
}
module ex22 {
   reg r1=0, r2=0, r3=0 ;
   func_self exec() ; //式1

   r1:=0b1 ;
   r2:=r1 ;
   r3:=r2 ;

   if(~r3 & r2 & r1 ) exec() ; //式2

   func exec f = a + b ; //式3
}

例題22は制御内部端子の使用例です。
式1では、制御入力端子execの宣言を行っています。
式2は、if条件文が真の場合execを呼び出すアクションです。
そして、式3がexecのファンクションアクションで、
execが呼び出されたら、fにa,bの加算結果を代入するというアクションです。

例題22のシミュレーション結果を見てみましょう。

では次に、制御内部端子の引数を用いた使用例を見てみましょう。

【例題23.制御内部端子使用例(引数)】
declare ex23 {
   output f[4] ;
}
module ex23 {
   wire w1[4], w2[4] ;
   reg r1=0, r2=0, r3=0 ;
   func_self exec_add(w1,w2) ; //式1

   r1:=0b1 ;
   r2:=r1 ;
   r3:=r2 ;

   if(~r3 & r2 & r1 ) exec_add(0x8, 0x4) ; //式2

   function exec_add f = w1 + w2 ; //式3
}

例題23は引数のついた制御内部端子の使用例です。
式1は、仮引数として内部端子w1,w2をつけて制御内部端子exec_addを宣言しています。
式2は、if条件文が真の場合、exec_addを0×8,0×4の実引数をつけて呼び出しています。
式2のexec_addにつけた実引数は、同クロック内でexec_addの仮引数に代入します。
また式2からexec_addを呼び出したのと同クロック内で、式3を実行します。
式3はexec_addのファンクションアクションを記述しています。
式3は、exec_addを呼び出したらw1とw2の加算結果をfに出力するという意味のアクションです。

動作の確認をしてみましょう。以下に論理シミュレーション結果を示します。

4-2.制御入力端子

制御入力端子は、モジュールの外部から入ってくる制御端子です。
したがって、モジュールの内部から起動することはできません。
制御入力端子の宣言方法は以下になります。

func_in 制御入力端子名

次に、引数は以下の様に記述します。

func_self 制御端子名(仮引数1, 仮引数2, … 仮引数X)

()の中に示す内部端子が仮引数になり、起動時の実引数を転送します。

制御入力端子を起動するには、外部のモジュールから制御入力端子に働きかける必要があります。
制御入力端子の起動方法については5章2節サブモジュールの解説(制御端子)を参照して下さい。

また、制御入力端子もファンクションを記述する必要があります。
ファンクションの記述方法は制御内部端子と同じく以下になります。

function 制御端子名 アクション記述

次の例題24を見てみましょう。

【例題24.制御入力端子使用例】
declare ex24 {
   input a, b ;
   output f ;

   func_in exec_and ; //式1
   func_in exec_or ;  //式2
   func_in exec_xor ; //式3
}
module ex24 {
   func exec_and f = a & b ; //式4
   func exec_or f = a | b ;  //式5
   func exec_xor f = a ^ b ; //式6
}

この例題24は”制御入力端子の宣言”, “制御端子のファンクションアクション”を有するモジュールになります。
式1,2,3はそれぞれ制御入力端子exec_and,exec_or,exec_xorの宣言です。
対して式4,5,6はexec_and,exec_or,exec_xorのファンクションアクションです。
式4はexec_andを呼出したらfにa,bの論理積を出力するというアクションです。
式5はexec_orを呼出したらfにa,bの論理和を出力するというアクションです。
式6はexec_xorを呼出したらfにa,bの排他的論理和を出力するというアクションです。

以下に論理シミュレーション結果を示します。

●制御入力端子による束線信号の制御
制御入力端子は1ビットの信号線ですが、制御対象のデータは束線でも構いません。
次の例題25を見てみましょう。

【例題25.制御入力端子使用例2】
declare ex25 {
   input a[8],b[8] ;
   output f[8] ;

   func_in exec_add ; //式1
}
module ex25 {
   func exec_add f = a + b ; //式2
}

例題25は、束線信号を用いた制御入力端子の使用例です。
式1で、制御入力端子exec_addを宣言しています。
また、式2でfにa,bの加算結果を代入しています。
このように、束線信号の時も単線の時と同じように使うことができます。

同じく、動作の確認をしてみましょう。
以下に論理シミュレーション結果を示します。

4-3.制御出力端子

制御出力端子は、モジュールの外に出力する制御端子です。
制御出力端子の宣言方法は以下になります。

func_out 制御出力端子名

また、制御出力端子には制御内部端子と同様に仮引数を定義することができます。
仮引数を入れた制御出力端子の宣言方法は以下になります。

func_out 制御出力端子名(仮引数1, 仮引数2, … 仮引数X)

また、制御出力端子はモジュール外部に出力する制御端子なので、
ファンクションアクションを記述する必要はありません。

次の例題26に制御出力端子の使用例を示します。

【例題26.制御出力端子使用例】
declare ex26 {
   input a[4], b[4], c ;
   output f[4] ;

   func_out done(f) ; //式1
}
module ex26 {

	if(c) done(a&b) ; //式2
}

例題26は制御出力端子の使用例です。
式1は制御出力端子doneに仮引数fをつけて宣言しています。
式2はif条件文が真の場合、doneに実引数a&bをつけて呼び出しています。

動作の確認をしてみましょう。
以下に論理シミュレーション結果を示します。

4-4.seqブロックの解説

seqブロックは制御端子の中に限り記述可能なブロックです。
seqブロックは、ブロック内で記述した上から順に一文ずつアクションを実行する構文です。
seqブロックの記述方法は以下になります。

seq {
   単位アクション1
   単位アクション2
   単位アクション3
       :
       :
}

例題27はseqブロックの記述方法の例です。
例題27を見てみましょう。

【例題27.seqブロック例】
declare ex27 {
    input	a[4], b[4] ;
    output	f[4] ;
    func_in	exec(a, b) ;
}
module ex27 {
    reg	r1[4] = 0, r2[4] = 0, r3[4] = 0 ;

    func exec seq{
        r1 := a | b ;       //式1
        r2 := r1 & 0b1010 ; //式2
        r3 := r1 ^ r2 ;     //式3
        f = r3 ;            //式4
    }

} // end module

例題27のseqブロックは、制御入力端子execを呼び出すと順にアクションを実行します。
まず、モジュールの起動時に式1を実行し、2クロック目に式2を実行します。
そして、3クロック目に式3と、1クロックずつ一文を実行します。

例題27のシミュレーション結果を見てみましょう。

●forブロックの解説
forブロックseqブロックの中に限り使用できる構文で、条件付きループを表す構文です。
以下の例がforブロックの記述方法です。

for (ループ変数初期値; ループ変数条件式; ループ変数変化値) {
単位アクション1
単位アクション2
単位アクション3
:
:
}

また、forブロックのアクションが起動する順序を以下に示します。

0,forブロックの初期値に書いたアクションを実行する
1,条件式が真の場合2へ、偽の場合はアクションを終了する
2,forブロックのアクション記述を上から順に1文ずつ実行する
3,変化値を更新して1に戻る

forブロック内の動作例として以下の例題16を見てみましょう。

【例題28.forブロック例】
declare ex28 {
    output f[4] ;
    func_in exec_for() ;
}
module ex28 {
    reg cnt[4] = 0 ;
    reg r1[4] = 0;

    func exec_for seq {
        for(cnt:=0;cnt<10;cnt++){  //式1
            r1 := r1 + 0x1 ;       //式2
            f = r1 ;               //式3
        }
        r1 := 0xf ;                //式4
   }
}

forブロックは、条件式の判定に1クロック使ってからforブロック内のアクションを実行します。
例題28の動きを追っていきましょう。
まず、ex28の制御入力端子exec_forを呼び出すと、seqブロックが起動します。
seqブロックに入った後は、式1のforブロックの初期値を実行して条件式を判定します。
forブロック実行時の条件は真なので、forブロック内のアクションを順番に実行します。
式2では、レジスタr1に1を加算して、r1に書き戻しています。
次に、式3で、データ出力端子fにr1の値を出力しています。
forブロックが終端まで来ると、式1の変化値が実行した後に再度条件式の判定を行います。
この時、条件式が真であれば動作を実行し、偽であればforブロックを終了します。

例題28では、cntの値が9を超えるとforブロックがブレイクするように設計しています。
forブロックがブレイクした場合、seqブロックが順序アクションを行って式4が実行されます。
式4ではレジスタr1に0xfを書き込むというアクションを行います。

例題28のシミュレーションは以下になります。

●whileブロックの解説
whileブロックseqブロックの中に限り使用できる構文で、
forと同じく条件付きループを表す構文です。

whileブロックの記述方法は以下になります。

while (条件式) {
単位アクション1
単位アクション2
単位アクション3
:
:
}

また、whileブロックの動作は以下になります。

1,条件式が真の場合2へ、偽の場合はアクションを終了する
2,whileブロックのアクション記述を上から順に一文ずつ実行する
3,1に戻る

whileブロックの例として以下の例題29を見てみましょう。

【例題29.whileブロック例】
declare ex29 {
   input a ;
   output f[4] ;
   func_in exec_while(a) ;
}
module ex29 {
   reg r1[4] = 0 ;
   
   function exec_while seq {
      while(a) {           //式1
         r1 := r1 + 0x1 ;  //式2
         f = r1 ;          //式3
      }
      
      r1 := 0xf ;   //式4
   }
}

例題29の動きを追っていきましょう。
ex29の制御入力端子exec_whileを呼び出すと、seqブロックが起動してwhileブロックが開始します。
whileブロックが起動するとまず、条件式の判定を行います。
この時、条件式の判定に1クロック使用します。
条件式が真ならば、whileブロック内のアクションを実行し、
偽ならばアクションを一度も実行せずに終了します。
ex29の場合、式1がwhileブロックの条件式です。
この式1の場合は、データ入力端子に0b1の信号が来る場合に真となり、
0b0の信号が来る場合には偽と判定します。

whileブロックの条件式が真の場合、式2を実行します。
式2は、r1の値に0x1を加算してr1に書き戻すというアクションです。

次に式3の、r1の値をfに出力するという動作を実行します。
そして、whileブロックの終端まで来ると、再び条件式の判定を行います。
この時、真であればwhileブロックを実行し続け、偽であれば終了します。

ex29の場合、条件式判定時にaが0b1である限りループを実行するように設計しています。
while文が終了したら、式4のr1に0xfを書き込むというアクションを実行します。

以下の例題29のシミュレーション結果を見てみましょう。

●ラベルの解説
seqブロック内では順序アクションを行いますが、
繰り返しアクションを指定する場合、任意の場所まで戻る必要があります。
このような繰り返しアクションを補助する構文として、ラベルという構文があります。
seqブロック内でラベルを定義すると、ラベル位置までgotoで移動することができます。
ラベルは使用するseqブロック内で宣言を行うことが必要です。
ラベルの宣言方法は以下のとおりです。

label_name ラベル名

seqブロック中でのラベルの定義は以下のように記述します。

ラベル名 :

そして、ラベルの位置に移動する場合は同一のseqブロックの中で、以下のように記述します。

goto ラベル名 ;

goto文を用いた処理の遷移に場合は1クロック使用します。
ラベルの記述例は以下の通りです。

seq {
label_name ラベル名1, ラベル名2

単位アクション1
goto ラベル名2
ラベル名1 :
単位アクション2
単位アクション3
ラベル名2 :
単位アクション4
goto ラベル名1
}

seqブロックの記述例として記述例題30を見てみましょう。

【例題30.goto文の例】
declare ex30 {
    output f[4] ;
    func_in exec_label ;
}
module ex30 {

    reg r1[4] = 0 ;

    function exec_label seq {
        label_name  label1, label2 ; //式1

			goto label2 ;               //式2
        label1 :
            r1 := r1 + 0x1 ;         //式3      
        label2 :
            if(r1 < 0x5) goto label1 ;  //式4

        f = r1 ;                     //式5
    }
}

ex30では、制御入力信号exec_labelを呼び出すとseqブロックが起動します。
ラベルは、seqブロックのアクション実行の順番を任意の場所に移動する為の構文です。
例題30では、labrl1,label2の2つのラベルを式1で宣言しています。
seqブロックのラベル自体は、何のアクションも表していません。
そのため、例題30ではseqブロックが起動した場合、始めに式2を実行します。
式2はラベルlabel2に移動するという意味の単位アクションの為、
式2の次に実行するのはラベルlabel2直後の式4です。
式4は、条件式が真の場合、ラベルlabel1に移動するというアクションをif文で表しています。
ラベルlabel1に飛ぶと、直後の式3を実行します。
式3は、r1に0x1を加算してr1に書き戻すという単位アクションを表しています。
式3を実行した後、順序アクションで、もう一度式4を実行します。
式4が偽である場合、次の順序アクションである式5を実行します。
式5はfにr1を出力する単位アクションを表しています。

では、例題30のシミュレーション結果を見てみましょう。

●返り値
制御端子には返り値(return value)を設定することができます。
返り値は制御端子の起動時に、指定の値が返ってくる構文です。
返り値は制御端子を起動した同一クロック内で値を返します。

それでは、制御内部端子、制御入力端子、制御出力端子の順に解説します。

はじめに、制御内部端子の返り値は以下のように書きます。

func_self 関数名 (引数):返り値端子名

制御内部端子の引数と返り値は内部端子のみ記述できます。
返り値の設定はファンクションアクションの中で行い、以下のように書きます。

return ( 値 )

制御内部端子をつかった返り値の例題4-10を見てみましょう。

例題4-10)制御内部端子の返り値
declare funcs_return {
}
module funcs_return {
    reg trigger[3] = 0 ;
    reg sum[4] = 0 ;
    wire a[4], b[4], c[4];
    func_self exec_add(a, b) : c ;      //内部端子a,bを引数に、cを返り値に設定して制御内部端子を宣言

    trigger := { trigger[1:0], 0b1 } ;

    if(trigger == 0b011) sum := exec_add(0b0011, 0b0100) ;  //制御内部端子を起動

    func exec_add {
        return (a + b) ;               //返り値にa+bを設定
    }
}

例題4-10は制御内部端子をつかった加算です。
例題では制御内部端子"exec_add"を起動して、加算結果をレジスタ"sum"に書き込んでいます。
"exec_add"には"0b0011","0b0100"を引数に渡しており、加算結果の"0b0111"が返り値として出力されます。

では、例題4-10をシミュレーションしてみましょう。

つぎに、制御入力端子の返り値は以下のように書きます。

func_in 関数名 (引数):返り値端子名

制御入力端子の引数はデータ入力端子のみ記述でき、返り値はデータ出力端子のみ記述が可能です。
制御内部端子と同じように、返り値はファンクションアクション内で設定します。

制御入力端子をつかった返り値の例題4-11を見てみましょう。

例題4-11)制御入力端子の返り値
declare funci_return {
    input inA[4] ;
    input inB[4] ;
    output outF[4] ;

    func_in exec_sub(inA, inB) : outF ; //inA,inBを引数に、outFを返り値に設定して制御入力端子を宣言
}
module funci_return {

    func exec_sub {
        return (inA - inB) ;            //返り値にinA-inBを設定
    }
}

例題4-11は制御入力端子"exec_sub"に引数を渡しています。
引数の差を返り値として出力するモジュールです。

では例題4-11をシミュレーションしてみましょう。

制御出力端子の返り値は以下のように書きます。

func_out 関数名(引数):返り値端子名

制御出力端子の引数はデータ出力端子のみ記述できます。
また、返り値はデータ入力端子のみ記述できます。
制御内部端子と同じように、返り値はファンクションアクション内で設定します。

制御出力端子での返り値の例題を見てみましょう。

例題4-12)制御出力端子の返り値
declare test_sub {
    input ack ;
    output outA[4], outB[4] ;
    func_out exec(outA, outB) : ack ;    //"outA","outB"を引数に、"ack"を返り値に設定して
                                         //制御出力端子"exec"を宣言
}
module test_sub {
    reg trigger[4] = 0 ;
    reg end_flag = 0 ;

    trigger := { trigger[2:0], 0b1 } ;

    if(trigger==0b0111) {
        exec(0b0011, 0b1100) ;          //制御出力端子"exec"を起動
    }

    if(ack) end_flag := 0b1 ;
}

declare funco_return {
    //NULL
}
module funco_return {
    test_sub U_SUB ;
    reg result[4] = 0 ;

    func U_SUB.exec {                       //"exec"のファンクションアクション
        result := U_SUB.outA + U_SUB.outB ; //"outA"と"outB"の加算結果をresultに格納
        return (1) ;                        //返り値"1"を出力
    }
}

例題4-12は上位モジュール"funco_return"、下位モジュール"test_sub"で構成されています。
制御出力端子"test_sub"は引数をつけて宣言しています。
そして、"test_sub"のファンクションアクションは"funco_return"内で記述しています。
最後に、"test_sub"へ返り値が戻るまで、これらの動作は同一クロック内で成立します。

それでは、例題4-12をシミュレーションしてみましょう。