目次

シーケンサ

 シーケンサは、ステートマシンとも呼ばれます。

 CPLD/FPGAで、マイクロプロセッサのような動作をさせる
 場合に利用します。

 最近は、マイクロコンピュータのチップや開発環境が安価に
 入手できますが、ハードウエアで作られたシーケンサに絶対
 勝てない部分があります。

 マイクロコンピュータは命令を取出し、解釈し、実行します。
 1命令を1クロックで実行できないので、パイプライン処理や
 いろいろな工夫で1命令=1クロックに近づけています。でも
 絶対に1命令=1クロックにはできません。

 シーケンサを利用すると、1命令=1クロックは簡単に実現でき
 ます。

 例えて言えば、自転車で充分な速度なのに、フェラーリのF1マシンを
 利用して渋滞した道路を走らせているのが、マイクロコンピュータの
 プログラムでやっていることです。

 マイクロコンピュータで1命令を実行する間に、シーケンサは
 最低でも4命令ほど処理してしまいます。

 この高速性がもたらす恩恵は、以下です。

 今の時代は、低消費電力で動くことが要求されます。

 CPLD/FPGA内部のシーケンサを活用して、低周波数で動作する
 低消費電力の電子回路を作ることを目指しましょう。


シーケンサの使い方

 シーケンサを、どう使うのかは1990年はじめ頃は  雑誌で特集を組まれましたが、最近は見かけません。  プログラムでやってしまう方が楽だからでしょう。  自分のように回路図からLSI設計に入った技術者にとっては  プログラムを組む方が面倒に思うこともあります。  夜になったら、駐車場への進入方向をLEDの点滅で指示する  装置があります。これをプログラムとシーケンサで実現すると  どうなるかを、例として説明します。  Cのプログラムでは、次のようにすると思います。 switch ( state ) { case 0 : led_out = 0x0f ; /* turn off all LEDs */ state = 0 ; break ; case 1 : state = 1 ; if ( trigger == 1 ) { state++ ; } break ; case 2 : led_out = 0x0e ; /* turn on LED0 */ state = 3 ; break ; case 3 : led_out = 0x0d ; /* turn on LED1 */ state = 4 ; break ; case 4 : led_out = 0x0b ; /* turn on LED2 */ state = 5 ; break ; case 5 : led_out = 0x0e ; /* turn on LED3 */ state = 6 ; break ; case 6 : state = 0 ; /* return first state */ break ; default : state = 0 ; break ; }  変数triggerは外部割込みで設定し、上に記述した内容を  タイマー割込みで実現するという、簡単な処理です。  ちょっとしたコツを掴むと、誰でもシーケンサを記述できます。  シーケンサで、同じ内容を実現してみます。  ソースコードを、フローチャートにします。  ステートに値を割当てます。  割当てには、ジョンソンカウンタを利用できるように  コードを考えます。ジョンソンカウンタは、1クロック  ごとに、出力は1ビットだけ変化する性質があります。  フローチャットから、シーケンサは次のHDLになります。 process( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCURRENT <= "000" ; iNEXT <= "000" ; elsif rising_edge( CLOCK ) then iCURRENT <= iNEXT ; case conv_integer(iCURRENT) is when 0 => -- turn off all LEDs iNEXT <= "001" ; when 1 => -- judge if ( iTRG = '1' ) then iNEXT <= "011" ; else iNEXT <= "001" ; end if ; when 3 => -- turn on LED0 iNEXT <= "111" ; when 7 => -- turn on LED1 iNEXT <= "110" ; when 6 => -- turn on LED2 iNEXT <= "100" ; when 4 => -- turn on LED3 iNEXT <= "000" ; when 5 => -- illegal iNEXT <= "000" ; when 2 => -- illegal iNEXT <= "110" ; when others => iNEXT <= "000" ; end case ; end if ; end process;  VHDLのシーケンサ記述は、レジスタを2つ用意してクロック  ごとに、内容を入れ替えるようにします。  上の記述では、Warningが出てしまいます。  状態の割当ては、VHDLの処理系列に任せてしまいます。  通常は、ジョンソンカウンタかグレイコードカウンタを  ベースに、処理系が適切に選択してくれます。 type stype is (S0,S1,S2,S3,S4,S5,S6) signal led_state : std_logic_vector(3 downto 0); process( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCURRENT <= S0 ; led_state <= "0000" ; elsif rising_edge( CLOCK ) then case conv_integer(iCURRENT) is when S0 => -- turn off all LEDs led_state <= "0000" ; iCURRENT <= S1 ; when S1 => -- judge if ( iTRG = '1' ) then iCURRENT <= S2 ; end if ; when S2 => -- turn on LED0 led_state <= "0001" ; iCURRENT <= S3 ; when S3 => -- turn on LED1 led_state <= "0010" ; iCURRENT <= S4 ; when S4 => -- turn on LED2 led_state <= "0100" ; iCURRENT <= S5 ; when S5 => -- turn on LED3 led_state <= "1000" ; iCURRENT <= S0 ; when others => iCURRENT <= S0 ; end case ; end if ; end process;  74シリーズのような汎用ロジックICを利用した場合の回路は  次のようになります。  プログラミング言語のソースコードと異なり、レジスタ1個で  記述できないので、違和感があります。  高級言語のソースコードではわかりませんが、アセンブリ言語  のコードにしてみると、同じ様なことをしています。  このシーケンサは、ラウンドロビンと呼ばれる方式になります。  ラウンドロビンでは、一度トリガーを与えると、すべての処理が  終了するまで一気呵成に動作します。  シーケンサは、トリガーの与え方を工夫するとプログラムのように  次の3形態で、全動作を記述できます。  この3形態の処理は、後で説明します。  シーケンサをジョンソンカウンタベースにすると、LEDの点灯は  単純なデコーダの記述になります(0出力で点灯)。 LEDS(0) <= '0' when (iCURRENT = "011") else '1' ; LEDS(1) <= '0' when (iCURRENT = "111") else '1' ; LEDS(2) <= '0' when (iCURRENT = "110") else '1' ; LEDS(3) <= '0' when (iCURRENT = "100") else '1' ;  シーケンサ内でled_stateを利用している場合は、以下とします。 LEDS <= led_state ;  74シリーズのような汎用ロジックICを利用した場合の回路は  次のようになります。  トリガーは、エッジ検出、レベル検出どちらにも対応できます。  レベル検出の場合には、次のようにシフトレジスタを利用します。 process( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSREG <= "111" ; elsif rising_edge( CLOCK ) then iSREG <= iSREG(2 downto 1) & TRIGGER ; end if ; end process; iTRG <= '1' when ( iSREG = "000" ) else '0' ;  74シリーズのような汎用ロジックICを利用した場合の回路は  次のようになります。  エッジ検出にするには、一部を変更するだけで対応できます。 process( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSREG <= "111" ; elsif rising_edge( CLOCK ) then iSREG <= iSREG(2 downto 1) & TRIGGER ; end if ; end process; iTRG <= '1' when ( iSREG = "110" ) else '0' ;  レベル検出もエッジ検出も、ノイズに強くするために利用します。  マイクロコンピュータを利用する場合にも、外部割込み、タイマー  割込みを使えば、比較的簡単に記述できますが、割込みを実現する  方法やタイマー割込みの周期設定等で調べる等、実際に動かすまで  には煩雑な作業が必要です。プログラムで実現する故の問題もあり  ます。変数のサイズを間違えているとか、タイプミスをしている等  のデバッグが必要にもなります。  ちなみに、自分でAVR用プログラム作成とHDL作成で、どれほどの所要  時間になるかを試してみました。   プログラム作成所要時間 43分   HDL作成所要時間     12分  プログラミング言語を利用すると、マイクロコンピュータのハード上  の制約をクリアする必要があります。細工をするだけ、長くなります。  同じことを実現するのに、利用している1ビットレジスタの数を指標に  することがあります。その指標でみると、CPLD/FPGAで利用している1  レジスタ数は、マイクロコンピュータで利用している個数より、はるか  に少なくなります。  XC9536で説明のための回路を作成してみたところ、レジスタ数は10個  でした。レジスタは1ビット単位で、全体で10ビットでした。  Cのソースコードでは、変数stateで8ビットですが、割込みを使えば  そのためのレジスタが最低4ビットが必要になり、最低でも12ビット  になります。タイマー割込みを利用すると、カウンタを使うので、8  ビットのレジスタを1個消費します。  このように、どんなに工夫しても、マイクロコンピュータのプログラム  で実現する処理では、ハードウエアのシーケンサに勝てません。  これは、消費電力を低減できることに結びつきます。エコという点  でみると、マイクロコンピュータよりもCPLD/FPGAを利用する方に  分があることがわかると思います。  ちなみに、説明のために考えた装置はPLC(Programmable Logic Controller)  と呼ぶ機器で実現することが殆どです。PLCも、シーケンサと呼ばれます。  PLCの内部はマイクロコンピュータを利用していますが、シーケンサ製造メーカ  の一部の機種では、CPLD/FPGAを使っていることもあります。

シーケンサのベースカウンタ選定

 シーケンサは、カウンタを利用して実現します。  HDLを利用すると、プログラム言語と似たカタチで記述できます。  記述は簡単ですが、シーケンサを構成するベースカウンタに何を  利用するかで、高速動作ができるか、ハザードなしで実現できる  かが決まります。  ベースカウンタの選別を間違うと、シーケンサがトラブルの主  原因に成り下がることもあります。  カウンタは、次の3種類に限定されます。  カウンタには、他にリプルカウンタがありますが、  CPLD/FPGAは、同期カウンタを利用する構成とする  ため、使いません。  実現が最も簡単なのは、バイナリーカウンタで次に  ジョンソンカウンタ、更にグレイコードカウンタと  なります。  LSIの設計、開発では、バイナリーカウンタは使わない  ようにします。それは、次の短所があるからです。
  1. ハザードを発生させやすい
  2. 同時変化のビットがあるため、エネルギー利用効率が悪い
  3. 出力をデコードするのに、多数のゲートを使う
 ハザードが発生するとノイズになり、並列で動いている他の  ブロックの誤動作を誘発したりします。  同時変化のビットがあると、LからHになるときに、エネルギー  を使うため、電源の容量を最大エネルギーまで引き上げないと、  正しい動作を保証できません。大量のエネルギーを利用できると  しても、同時変化のビットが多いと配線に存在するリアクタンス  分により局所部でハイパスフィルタ、ローパスフィルタが作られ  外部に不要な電磁波を撒き散らすこともあります。  出力をデコードするには、目的の信号を生成するために多数の  ゲートが必要になることもあります。ゲート数が増えると、1  チップに入れられる回路ブロック数が減ります。  バイナリーカウンタの短所は、他のジョンソン、グレイの  2タイプのカウンタの長所になります。  グレイコードカウンタは、コード変換を考えるのが面倒なので、  考えやすいジョンソンカウンタを利用します。  ジョンソンカウンタの長所は、以下です。  当然、ジョンソンカウンタにも短所はあります。  これらの短所が問題になるのは、カウンタに利用するフリップ  フロップ数が3〜5個になったときです。  フリップフロップ数が5個であれば、ジョンソンカウンタが現わす  ステート数は10個になります。LSI内部で、10個のステートを  利用することは、殆どありません。人間が同時に考えられる状態の  個数は、最大7という限界があるので、ステート数は最大でも7に  します。  普通は、シーケンサに利用するフリップフロップの個数は4程度に  抑えます。4ビットのジョンソンカウンタで表現できる状態数は、  8になります。状態数が8以上になるような場合は、シーケンサを  2つに分割して、親シーケンサと子シーケンサにします。  この階層構造を作りながら、階層数が7以下になるようにします。

シーケンサによる連接、選択、繰返しの実現

 大学のソフトウエア工学の講義で、すべての適正プログラムは  次の3つの組合せで実現できると習いました(構造化定理)。  シーケンサは、プログラム言語の処理を回路動作に  置換するので、当然3つの処理を実現できます。 連接  フローチャートで記述すると、単純に処理を縦に並べるだけです。  このような処理は、クロックごとにシーケンサに状態値を  出力させるだけで実現できます。 when 3 => -- next state : 7 iNEXT <= "111" ; when 7 => -- next state : 6 iNEXT <= "110" ; when 6 => -- next state : 4 iNEXT <= "100" ;  typeで、状態を定義していれば、次の記述にします。 when S3 => -- next state iCURRENT <= S4 ; when S4 => -- next state iCURRENT <= S5 ; when S6 => -- next state iCURRENT <= S7 ; 選択  フローチャートで記述すると、判定に相当します。  選択は判定を利用するので、該当する状態でif文により、次の状態  をレジスタに設定します。 when 1 => -- judge if ( iTRG = '1' ) then iNEXT <= "011" ; -- next state : 6 else iNEXT <= "100" ; -- next state : 4 end if ;  typeで、状態を定義していれば、次の記述にします。 when S1 => -- judge if ( iTRG = '1' ) then iCURRENT <= S2 ; else iCURRENT <= S4 ; end if ; 繰返し  繰返しは、2つのシーケンサで実現します。  親シーケンサで、子シーケンサの起動と回数を管理します。  子シーケンサは、トリガーを貰い、動作を開始します。  全動作を終了したならば、フラグを立てて通知します。 -- master sequencer process( nRESET , CLOCK , iSFLG ) begin if ( nRESET = '0' ) then iMCURRENT <= "000" ; iMNEXT <= "000" ; iCNT <= "000" ; iSTRG <= '0' ; elsif rising_edge( CLOCK ) then iMCURRENT <= iMNEXT ; case conv_integer(iMCURRENT) is when 0 => -- turn off all LEDs if ( iTRG = '1' ) then iMNEXT <= "001" ; iCNT <= "101" ; -- set counter else iMNEXT <= "000" ; iSTRG <= '0' ; end if ; when 1 => -- transfer iMNEXT <= "011" ; iSTRG <= '1' ; -- set trigger when 3 => -- transfer iSTRG <= '0' ; -- reset trigger iMNEXT <= "111" ; when 7 => -- turn on LED1 -- counter decrement if ( iSFLG = '1' ) then iCNT <= iCNT - '1' ; else iCNT <= iCNT ; end if ; -- judge wait if ( iCNT = "000" ) then iMNEXT <= "110" ; else iSTRG <= '1' ; iMNEXT <= "011" ; end if ; when 6 => -- turn on LED2 iMNEXT <= "100" ; when 4 => -- turn on LED3 iMNEXT <= "000" ; when 5 => -- illegal iMNEXT <= "000" ; when 2 => -- illegal iMNEXT <= "110" ; when others => iMNEXT <= "000" ; end case ; end if ; end process; -- slave sequencer process( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSCURRENT <= "00" ; iSNEXT <= "00" ; iSFLG <= '0' ; elsif rising_edge( CLOCK ) then iSCURRENT <= iSNEXT ; case conv_integer(iSCURRENT) is when 0 => -- wait trigger if ( iSTRG = '1' ) then iSNEXT <= "01" ; else iSNEXT <= "00" ; iSFLG <= '0' ; end if ; when 1 => -- wait iSNEXT <= "11" ; when 3 => -- wait iSNEXT <= "10" ; when 2 => -- set flag iSFLG <= '1' ; iSNEXT <= "00" ; when others => iSNEXT <= "00" ; end case ; end if ; end process;  typeで状態を定義すると、次のように記述できます。 type stype is (S0,S1,S2,S3,S4,S5,S6) ; signal iMCURRENT : stype ; signal iCNT : std_logic_vector(4 downto 0); -- master sequencer process( nRESET , CLOCK , iSFLG ) begin if ( nRESET = '0' ) then iMCURRENT <= S0 ; iSTRG <= '0' ; elsif rising_edge( CLOCK ) then iMCURRENT <= S0 ; case iMCURRENT is when S0 => -- turn off all LEDs if ( iTRG = '1' ) then iMCURRENT <= S1 ; -- set counter iCNT <= "11111" ; end if ; when S1 => -- transfer iSTRG <= '1' ; -- set trigger iMCURRENT <= S2 ; when S2 => -- transfer iMCURRENT <= S3 ; iSTRG <= '0' ; -- set trigger when S3 => -- turn on LED1 if ( iSFLG = '1' ) then iCNT <= iCNT(3 downto 0) & '0' ; end if ; if ( conv_integer( iCNT ) = 0 ) then iMCURRENT <= S4 ; end if ; when S4 => -- turn on LED2 iMCURRENT <= S5 ; when S5 => -- turn on LED3 iMCURRENT <= S0 ; when others => iMCURRENT <= S0 ; end case ; end if ; end process; type sstype is (SS0,SS1,SS2,SS3) signal iSCURRENT : sstype ; -- slave sequencer process( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSCURRENT <= SS0 ; iSFLG <= '0' ; elsif rising_edge( CLOCK ) then case iSCURRENT is when SS0 => -- wait trigger iSFLG <= '0' ; if ( iSTRG = '1' ) then iSCURRENT <= SS1 ; end if ; when SS1 => -- wait iSCURRENT <= SS2 ; when SS2 => -- wait iSCURRENT <= SS3 ; when SS3 => -- set flag iSFLG <= '1' ; iSCURRENT <= SS0 ; when others => iSCURRENT <= SS0 ; end case ; end if ; end process;  CPLD/FPGA内部では、各ブロックは並列に動作できるので  トリガーとフラグを利用し、同期を取ります。  フローチャートで見てわかるように、2つのシーケンスは  独立して動いています。  プログラミング言語のコードであれば、サブルーチン側と呼び  出し側に主従関係がありますが、ハードウエアの回路は並列で  動作しているので、主従関係はありません。  さらに、子シーケンサは1つとは限りません。  複数の子シーケンスを利用すれば、1つの親シーケンスが複数  の子シーケンスを管理することも可能になります。

目次 inserted by FC2 system