目次
前
次
シーケンサ
シーケンサは、ステートマシンとも呼ばれます。
CPLD/FPGAで、マイクロプロセッサのような動作をさせる
場合に利用します。
最近は、マイクロコンピュータのチップや開発環境が安価に
入手できますが、ハードウエアで作られたシーケンサに絶対
勝てない部分があります。
マイクロコンピュータは命令を取出し、解釈し、実行します。
1命令を1クロックで実行できないので、パイプライン処理や
いろいろな工夫で1命令=1クロックに近づけています。でも
絶対に1命令=1クロックにはできません。
シーケンサを利用すると、1命令=1クロックは簡単に実現でき
ます。
例えて言えば、自転車で充分な速度なのに、フェラーリのF1マシンを
利用して渋滞した道路を走らせているのが、マイクロコンピュータの
プログラムでやっていることです。
マイクロコンピュータで1命令を実行する間に、シーケンサは
最低でも4命令ほど処理してしまいます。
この高速性がもたらす恩恵は、以下です。
- 同じクロックならば、シーケンサの方が実行する命令は多い
- 並列動作で、低い周波数クロックでも(1命令)<(1クロック)を実現できる
- 低い周波数クロックで動作すれば、電力消費量が減る
今の時代は、低消費電力で動くことが要求されます。
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の設計、開発では、バイナリーカウンタは使わない
ようにします。それは、次の短所があるからです。
- ハザードを発生させやすい
- 同時変化のビットがあるため、エネルギー利用効率が悪い
- 出力をデコードするのに、多数のゲートを使う
ハザードが発生するとノイズになり、並列で動いている他の
ブロックの誤動作を誘発したりします。
同時変化のビットがあると、LからHになるときに、エネルギー
を使うため、電源の容量を最大エネルギーまで引き上げないと、
正しい動作を保証できません。大量のエネルギーを利用できると
しても、同時変化のビットが多いと配線に存在するリアクタンス
分により局所部でハイパスフィルタ、ローパスフィルタが作られ
外部に不要な電磁波を撒き散らすこともあります。
出力をデコードするには、目的の信号を生成するために多数の
ゲートが必要になることもあります。ゲート数が増えると、1
チップに入れられる回路ブロック数が減ります。
バイナリーカウンタの短所は、他のジョンソン、グレイの
2タイプのカウンタの長所になります。
グレイコードカウンタは、コード変換を考えるのが面倒なので、
考えやすいジョンソンカウンタを利用します。
ジョンソンカウンタの長所は、以下です。
- 1周期の間に、特定の信号の変化は1回のみ
- 同時に変化する信号はない
- 出力値のデコードがしやすい
- シフトレジスタに細工をすれば実現できる
当然、ジョンソンカウンタにも短所はあります。
- カウンタ中のフリップフロップの利用効率が低い
- マイナーコード(ステートに利用しない値)の処理が必要
これらの短所が問題になるのは、カウンタに利用するフリップ
フロップ数が3〜5個になったときです。
フリップフロップ数が5個であれば、ジョンソンカウンタが現わす
ステート数は10個になります。LSI内部で、10個のステートを
利用することは、殆どありません。人間が同時に考えられる状態の
個数は、最大7という限界があるので、ステート数は最大でも7に
します。
普通は、シーケンサに利用するフリップフロップの個数は4程度に
抑えます。4ビットのジョンソンカウンタで表現できる状態数は、
8になります。状態数が8以上になるような場合は、シーケンサを
2つに分割して、親シーケンサと子シーケンサにします。
この階層構造を作りながら、階層数が7以下になるようにします。
シーケンサによる連接、選択、繰返しの実現
大学のソフトウエア工学の講義で、すべての適正プログラムは
次の3つの組合せで実現できると習いました(構造化定理)。
- 連接(sequence)
- 選択(selection)
- 繰返し(repetition)
シーケンサは、プログラム言語の処理を回路動作に
置換するので、当然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つの親シーケンスが複数
の子シーケンスを管理することも可能になります。
目次
前
次