ステートマシンの型にはムーアとミーリがある。
ステートマシン出力の定義が自分のなかでふわっとしているので、次のように定義する(自分なりの定義)。
ステートマシンはステートマシン入力、ステート信号、ステートマシン出力から構成される。
ステートマシン出力は、HDL記述上ステート信号と同じalways文の中で生成し、データパスの制御用に使われる信号である。
機能的に表現すると、データパス(ステートマシン外部)で使うためのステート信号である。
よって、ステート信号と等価なものであり、ステート信号をステートマシン記述から出力するのであれば、ステート信号もステートマシン出力である。
ステート信号はステートマシン自身の遷移を制御するための信号で、ステートマシン出力はデータパスを制御するための信号なので、目的は異なるが信号の生成元は同じである。ステート信号をデータパス制御用に使うのが効率的であればそれでよい。ただし、例えば(いわゆる)next_state信号をステートマシン出力として使う場合、データパスではif (next_state == STEP2)のようにバスのコンペア回路で使うことが想定され、ステート信号がある程度多きなバスで多くのデータパスで使用される場合、配線遅延が大きくなることが懸念される。ステート信号のバスをすべて使わなくても、ステート信号よりも小さな1ビットなどの信号で制御できるのであればその方がタイミング設計上有利なため、ステート信号(およびミーリ型の場合はステートマシン入力)をデコードした信号を作り、それをステートマシン出力として使う。
このalwaysがクロック同期であっても組み合わせ回路であってもステートマシン出力と呼ぶ(データパスの制御に使うステート信号であるという目的で律速される)。結局ここは考え方、抑え方の問題なので重要ではないのかもしれない。ただ、ミーリ型の出力をF/Fで切ってから使うステートマシンというのもスタイル上存在するため、ステートマシン出力の定義がふわっとしていると、作成する信号がステートマシン出力なのかデータパスなのかが曖昧になってしまい、仕様書上の表現もふわっとしてしまうため、明確にしておきたい。つまり、ミーリ型の出力をF/Fで切ったスタイルの場合、この(同じalwaysのなかで)F/Fで切った出力をステートマシン出力と呼ぶ。
それと、ステートマシン出力とは、ステートマシンを反映した制御信号で、ステート信号、またはステート信号とステートマシン入力のみから生成される信号である。
ステートマシン入力以外の信号を組み合わせた信号もステートマシン出力と呼ぶとすると、ステートマシンのalwaysから出力された信号(ステートマシン出力)をつかって生成した制御信号もすべてステートマシン出力?ということになってしまい、また仕様定義がぼやけてしまうため。
HDL記述上もこのあたりの考えがふわっとしていると、ステートマシンとその他の信号が仕様書と合わなくなってしまうので注意したい。コーディングスタイルとしても、ステートマシンとデータパスは分離することが推奨されている。
Point
ステートマシン出力とは・・・
- HDL記述上、ステート信号と同じalwaysの中で生成し、データパスの制御用に使われる信号。
- 機能上は、ステート信号はステートマシン自身の遷移を制御するための信号で、ステートマシン出力はデータパスを制御するための信号。
- ステートマシンを反映した制御信号で、ステート信号、またはステート信号とステートマシン入力のみから生成される信号。
- ムーア型の場合、ステートマシン出力はステート信号から一意に作られる組み合わせ回路出力である。
- ミーリ型の場合、ステートマシン出力にステートマシン入力が使われる。
- ミーリ型の場合、F/Fで切られている場合と切られていない場合がある。切られている場合、制御信号はステートマシン入力から1クロック遅れたタイミングとなる。切られていない場合、ステートマシン入力がステートマシン出力に抜けるパスが形成されるので、タイミングパスが長くなりやすい。
ムーア型の特徴
ムーアは、ステートマシンとステートマシン出力が一対一のもの。記述上はステートマシンを示すcase分の直下にかかれ、if分による分岐には含ませない。出力をF/Fで切ったミーリ型同様、この制御信号を使う回路はステートマシン入力信号の変化から1クロック遅れて(ステート信号の変化に同期して)動作することになるので、1クロック遅れても良い回路に採用できる。ムーア型の制御信号はステート信号生成時点でタイミングパスが切られているので、(スループットではなく)STA上有利で高速化しやすい。
ミーリ型の特徴
ミーリ型は、ステートマシン入力がステートマシン出力にスルーするパスが存在するもの。ただし、ミーリ型の構成でステートマシン出力をF/Fで切ったものもミーリ型に含める。ムーア型の場合、ステート信号の変化から1クロック後にデータパスを変化させることになるが、ミーリ型はステートマシン入力がステートマシン出力に透過されるので、一つのステート信号に対して複数の状態を持つことができ、ステート信号の変化ど同時にデータパスを変化させることができるので、回路の動作速度(スループット)上有利である。半面、ステートマシン入力のパスが長くなる可能性があり、STA上は不利になりやすい。
ミーリ型の書き方
ステートマシン信号とその他の信号を別のalways文に分離して記述する。
ステートマシン出力をF/Fで切りなおす場合、ステートマシン信号と一緒に生成すれば良いのでalways文を分ける必要性はないが、分けたほうが利点があると思う。
ステートマシン入力以外も組み合わせたい信号がある場合、alwaysを分けないとそれもステートマシン信号と一緒に書かなければならなくなるが、前述のステートマシン出力の定義に照らし合わせるとこの信号はステートマシン出力とは呼べず、ステートマシンとステートマシン以外の信号を一緒に書くことになってしまい、設計スタイル上もよろしくないことになる。
ステートマシン出力は組み合わせ回路で記述しておけば、それを使った信号はF/Fで切り直したときにステートマシン信号と同じタイミングで変化させることができるし、ステートマシン出力を使う先が多岐に及んだとしてもそれぞれのブロックで分けて記述することもできるので、使いまわしやすくなる。ただし、タイミングパスは遅延を多く含むので、パスが長くならないよう気をつけて運用するよう気を付けること。
State Machine
`timescale 1ns/1ns
/////////////////////////////////////////////////////////////////
//
// State Machine
//
/////////////////////////////////////////////////////////////////
module CAR_STATE (
input logic CLK,
input logic RESET,
input logic UP,
input logic DOWN,
output logic GEAR,
output logic SHIFT
);
typedef enum logic [1:0] {
IDLE_STATE = 2'b00,
LOW_STATE = 2'b01,
TOP_STATE = 2'b10
} gear_t;
gear_t current_st, next_st, MSTATE;
logic MGEAR;
logic MSHIFT;
always @(posedge CLK) begin
if (RESET) begin
current_st <= IDLE_STATE;
end
else begin
current_st <= next_st;
end
end
always_comb begin
case (current_st)
IDLE_STATE : begin
MSTATE = IDLE_STATE;
MGEAR = 1'b0;
MSHIFT = 1'b0;
if (UP) begin
next_st = LOW_STATE;
GEAR = 1'b1;
SHIFT = 1'b0;
end else begin
next_st = IDLE_STATE;
GEAR = 1'b0;
SHIFT = 1'b0;
end
end
LOW_STATE : begin
MSTATE = LOW_STATE;
MGEAR = 1'b1;
MSHIFT = 1'b0;
if (UP) begin
next_st = TOP_STATE;
GEAR = 1'b1;
SHIFT = 1'b1;
end else if (DOWN) begin
next_st = IDLE_STATE;
GEAR = 1'b0;
SHIFT = 1'b0;
end else begin
next_st = LOW_STATE;
GEAR = 1'b1;
SHIFT = 1'b0;
end
end
TOP_STATE : begin
MSTATE = TOP_STATE;
MGEAR = 1'b1;
MSHIFT = 1'b1;
if (DOWN) begin
next_st = LOW_STATE;
GEAR = 1'b1;
SHIFT = 1'b0;
end else begin
next_st = TOP_STATE;
GEAR = 1'b1;
SHIFT = 1'b1;
end
end
endcase
end
endmodule
next_state, GEAR, SHIFTはミーリ型。
MSTATE, MGEAR, MSHIFTはムーア型。
Data Path
/////////////////////////////////////////////////////////////////
//
// CAR object
//
/////////////////////////////////////////////////////////////////
module CAR (
input logic CLK,
input logic RESET,
input logic DRIVE,
input logic SHIFT,
output logic [7:0] METER
);
parameter UNIT = 2'b01;
always @(posedge CLK) begin
if (RESET) begin
METER <= 8'h00;
end else if (DRIVE) begin
if (!SHIFT) begin
METER <= METER + UNIT;
end else begin
METER <= METER + UNIT * 2;
end
end else begin
METER <= METER;
end
end
endmodule
ミーリ型のステートマシン出力を使ったデータパス。
DRIVEはステートマシン出力と他の信号(START*)から生成した信号。DRIVEはTOP Moduleで定義している。
SHIFTはそのままステートマシン出力を使用。
TOP Module
/////////////////////////////////////////////////////////////////
//
// TOP Module
//
/////////////////////////////////////////////////////////////////
module top (
input logic CLK,
input logic RESET,
input logic START1,
input logic START2,
input logic UP,
input logic DOWN,
input logic READADR,
output logic [7:0] READOUT
);
parameter NORMAL = 2'b01;
parameter FAST = 2'b10;
logic gear;
logic SHIFT;
logic [7:0] fcMeter;
logic [7:0] scMeter;
wire drive1 = START1 & gear;
wire drive2 = START2 & gear;
CAR_STATE carState (.*, .GEAR(gear));
CAR #(.UNIT(NORMAL)) familyCar (.*, .DRIVE(drive1), .METER(fcMeter));
CAR #(.UNIT(FAST)) superCar (.*, .DRIVE(drive2), .METER(scMeter));
always @(posedge CLK) begin
if (RESET) begin
READOUT <= 8'd0;
end else if (READADR == 1'b0) begin
READOUT <= fcMeter;
end else if (READADR == 1'b1) begin
READOUT <= scMeter;
end
end
endmodule
Test Bench
/////////////////////////////////////////////////////////////////
//
// Test Bench
//
/////////////////////////////////////////////////////////////////
module top_bentch;
parameter P_DELAY = 10;
parameter P_HCYCLE = 50;
parameter adrFamilyCar = 1'b0;
parameter adrSuperCar = 1'b1;
integer mcd;
logic CLK;
logic RESET;
logic START1;
logic START2;
logic UP;
logic DOWN;
logic READADR;
logic [7:0] READOUT;
always begin
CLK <= 1'b0;
#(P_HCYCLE) CLK <= 1'b1;
#(P_HCYCLE);
end
top car_top (.*);
/////////////////////////////////////////////////////////////////
task CLK_DELAY;
@(posedge CLK);
// #(P_HCYCLE * 2);
endtask
task READ;
input addr;
string name;
case (addr)
1'b0 : name = "family car";
1'b1 : name = "super car";
default : name = "undefined";
endcase
CLK_DELAY(); READADR = addr;
CLK_DELAY(); $fdisplay(mcd, "Meter[%s] = %h",name, READOUT);
endtask
/////////////////////////////////////////////////////////////////
task gearUp;
UP = 1'b1;
CLK_DELAY(); UP = 1'b0;
endtask
/////////////////////////////////////////////////////////////////
task gearDown;
DOWN = 1'b1;
CLK_DELAY(); DOWN = 1'b0;
endtask
/////////////////////////////////////////////////////////////////
initial begin
mcd = $fopen("fsm2.log","w");
$fmonitor(mcd, "READOUT : %h", READOUT);
@(posedge CLK); RESET = 1'b1;
CLK_DELAY(); RESET = 1'b0; // release
START1 = 1'b0;
START2 = 1'b0;
UP = 1'b0;
DOWN = 1'b0;
READADR = 1'b0;
repeat(2) CLK_DELAY();
CLK_DELAY(); START1 = 1;
CLK_DELAY(); gearUp();
gearUp();
gearDown();
gearDown();
READ(adrFamilyCar);
CLK_DELAY(); START1 = 0;
CLK_DELAY(); START2 = 1;
CLK_DELAY(); gearUp();
gearUp();
gearDown();
gearDown();
READ(adrSuperCar);
CLK_DELAY(); START2 = 0;
repeat(2) CLK_DELAY();
$finish();
$fclose(mcd);
end
endmodule
ミーリ型のステートマシン出力であるGEAR, SHIFTは、ステートマシン入力のUPと同じタイミングで変化しており、ステートマシン信号current_stと同じタイミングでデータパスが確定している。
ムーア型のステートマシン出力であるMGEAR, MSHIFTは、ステートマシン信号であるMSTATEと同じタイミングで変化しているので、このステートマシン出力を使うデータパスは1クロック遅れて確定する。