GX Works3でラベルやFBを使うとコンパイルしたコードが実行されます。
ではどのようなコードに変換されるのでしょうか。
コンパイルされたコードを見る方法
GX Works2ではこのようにしてコンパイル後のデータを観測することができます。
ではGX Works3の場合はどうやって見ることができるのでしょうか。
まずSTやFBでプログラムを書いてコンパイルします。
この説明ではFB/FUNともにわかりやすいように、下記のような回路にしています。
IN1 + IN2 = OUT3をするだけです。

GX Simulator3に転送して実行します。
GX Works3とGX Simulator3でプログラムの比較を行います。
比較するとこのような画面になります。
ここで見るのはプログラムファイル、FBファイル、FUNファイルです。
下記はプログラムファイルの場合です。
LFBCALL、FUNCALLのような、普段のラダーでは見かけない命令が出てきます。
LV:xxはローカルラベルの領域を表す表示です。
FBの場合は入力値をインスタンスのラベル領域に書き込み、LFBCALLで本体を呼び出します。演算後は出力ラベルの値を読み出します。
FUNの場合は入力値をTMP領域に書き込み、FUNCALLで本体を呼び出します。戻ってきたらTMP領域から出力値を読み出します。
インスタンスの有無で何が変わるのかというと、インスタンスがあると値をFBごとに記憶することができます。
例えばFBではタイマー、カウンター、パルス、自己保持を利用することができ、演算結果の記憶をすることが可能です。FBごとに専用の記憶エリアをシステムが割り当ててくれるからです。
一方FUNにはインスタンスがなく、入力値で演算して結果を返します。FUN内のローカルラベルの値は、次の呼び出しには保持されません。
なおマニュアルにはインスタンスに何が含まれどういう配置かまで詳しく書かれています。
SH-081225 MELSEC iQ-R プログラミングマニュアル(プログラム設計編)

FBのマクロ型・サブルーチン型
上記の例はFBをサブルーチン型でコンパイルしています。そもそもマクロ型・サブルーチン型とはなんでしょうか。
サブルーチン型ではFBFILEにFBの本体を格納して呼び出します。マクロ型では呼び出し元に同じロジックを埋め込みます。では確認してみましょう。
FBFILEから対象のFBのロジックが消え、呼び出し元に演算するロジックが埋め込まれていることがわかります。
FUNにはFBのようなマクロ型への切り替えがないため、変わりません。
どちらを選ぶべきか
同じFBを何か所も呼び出す場合、サブルーチン型では本体を共用できるため、プログラムメモリを節約しやすくなります。またMX-Rの場合はサブルーチン型しか選べません。
また、マクロ型で下記のようにFB外から引数ラベルを直接参照すると、意図した値を読めない場合があります。FB外で値を使う場合は、引数に接続した実デバイスやラベルを参照します。サブルーチン型では下記のような使い方でも正しく動作します。
SH-081225 MELSEC iQ-R プログラミングマニュアル(プログラム設計編)

STはどのようにコンパイルされるのか
下記のようなコードをFBに貼り付けて呼び出します。
(* ===== 代入・算術演算 ===== *)
D0 := 2;
D1 := 10;
D2 := D1 + 3;
D3 := D1 - 3;
D4 := D1 * 3;
D5 := D1 / 3; // 整数同士なので結果は3
D6 := D1 MOD 3; // 余りは1
(* ===== 比較・論理演算 ===== *)
M0 := (D1 >= 5) AND (D1 < 20);
M1 := NOT M0;
M2 := M0 OR M1;
(* ===== IF / ELSIF / ELSE ===== *)
IF D1 < 0 THEN
D7 := -1;
ELSIF D1 = 0 THEN
D7 := 0;
ELSE
D7 := 1;
END_IF;
(* ===== CASE:単一値・複数値・範囲 ===== *)
CASE D0 OF
0:
D8 := 100;
1, 2:
D8 := 200;
3..5:
D8 := 300;
ELSE
D8 := -1;
END_CASE;
(* ===== FOR:1~5を加算 ===== *)
D20 := 0;
FOR D10 := 1 TO 5 BY 1 DO
D20 := D20 + D10;
END_FOR;
// D20 = 15
(* ===== WHILE:条件を先に判定 ===== *)
D11 := 0;
WHILE D11 < 3 DO
D11 := D11 + 1;
END_WHILE;
// D11 = 3
(* ===== REPEAT:処理後に終了条件を判定 ===== *)
D12 := 0;
REPEAT
D12 := D12 + 1;
UNTIL D12 >= 3
END_REPEAT;
// D12 = 3
(* ===== EXIT:ループを途中で抜ける ===== *)
D21 := 0;
FOR D13 := 1 TO 10 BY 1 DO
IF D13 > 3 THEN
EXIT;
END_IF;
D21 := D21 + D13;
END_FOR;
// D21 = 6
(* ===== 関数呼出し ===== *)
D30 := ABS(-10);
// D30 = 10
(* ===== RETURN:現在の処理を終了 ===== *)
D31 := 0;
IF D0 = 2 THEN
RETURN;
END_IF;
D31 := 999; // この例では実行されない
NOP K4
STPUSH
_TMALLOC H57
(* ===== 代入・算術演算 ===== *)
LD SM400
MOV K2 D0
MOV K10 D1
+ D1 K3 D2
- D1 K3 D3
* D1 K3 TMP:48
MOV TMP:48 D4
/ D1 K3 TMP:48
MOV TMP:48 D5
/ D1 K3 TMP:48
MOV TMP:49 D6
(* ===== 比較・論理演算 ===== *)
LD>= D1 K5
OUT TMP:56.0
LD< D1 K20
OUT TMP:55.0
LD TMP:56.0
AND TMP:55.0
OUT M0
LD SM400
CMLB M0 M1
LD M0
OR M1
OUT M2
(* ===== IF / ELSIF / ELSE ===== *)
LD SM400
IF
LD< D1 K0
OUT TMP:56.0
LD TMP:56.0
THEN
MOV K-1 D7
ELSIF
LD= D1 K0
OUT TMP:55.0
LD TMP:55.0
THEN
MOV K0 D7
ELSE
LD SM400
MOV K1 D7
ENDIF
(* ===== CASE:単一値・複数値・範囲 ===== *)
LD SM400
CASE
MOV D0 TMP:51
CASEOF
CASEIF
LD= TMP:51 K0
OUT TMP:56.0
LD TMP:56.0
CASETHEN
MOV K100 D8
CASEIF
LD= TMP:51 K1
OUT TMP:55.0
LD= TMP:51 K2
OUT TMP:56.0
LD TMP:55.0
OR TMP:56.0
OUT TMP:54.0
LD TMP:54.0
CASETHEN
MOV K200 D8
CASEIF
LD<= K3 TMP:51
OUT TMP:55.0
LD<= TMP:51 K5
OUT TMP:56.0
LD TMP:55.0
AND TMP:56.0
OUT TMP:54.0
LD TMP:54.0
CASETHEN
MOV K300 D8
CASEELSE
LD SM400
MOV K-1 D8
ENDCASE
(* ===== FOR:1~5を加算 ===== *)
LD SM400
MOV K0 D20
STFOR
MOV K1 D10
FORBY
LD SM400
+ D10 K1 D10
FORTO
LD<= D10 K5
OUT TMP:55.0
LD TMP:55.0
FORDO
+ D20 D10 D20
ENDFOR
(* ===== WHILE:条件を先に判定 ===== *)
LD SM400
MOV K0 D11
WHILE
LD< D11 K3
OUT TMP:56.0
LD TMP:56.0
WHILEDO
+ D11 K1 D11
ENDWHILE
(* ===== REPEAT:処理後に終了条件を判定 ===== *)
LD SM400
MOV K0 D12
REPEAT
LD SM400
+ D12 K1 D12
UNTIL
LD>= D12 K3
OUT TMP:54.0
LD TMP:54.0
ENDREPEAT
(* ===== EXIT:ループを途中で抜ける ===== *)
LD SM400
MOV K0 D21
STFOR
MOV K1 D13
FORBY
LD SM400
+ D13 K1 D13
FORTO
LD<= D13 K10
OUT TMP:55.0
LD TMP:55.0
FORDO
IF
LD> D13 K3
OUT TMP:56.0
LD TMP:56.0
THEN
EXIT
ENDIF
LD SM400
+ D21 D13 D21
ENDFOR
(* ===== 関数呼出し ===== *)
LD SM400
MOV K-10 TMP:53
AND< K-10 K0
NEG TMP:53
LD SM400
MOV TMP:53 D30
(* ===== RETURN:現在の処理を終了 ===== *)
MOV K0 D31
IF
LD= D0 K2
OUT TMP:54.0
LD TMP:54.0
THEN
CJ GLP0
ENDIF
LD SM400
MOV K999 D31
GLP0
_TMFREE H57
STPOP
FBRET
すると、このような命令が出てきます。
STPUSH STPOP _TMALLOC _TMFREE FBRET
IF THEN ELSIF ELSE ENDIF
CASE CASEOF CASEIF CASETHEN CASEELSE ENDCASE
STFOR FORBY FORTO FORDO ENDFOR
WHILE WHILEDO ENDWHILE
REPEAT UNTIL ENDREPEAT
EXIT
GX Works2では、STの分岐やループをポインタを使ったジャンプで制御していました。
そのため、STで分岐やループを多く書くと大量のポインタを消費していました。
今回のiQ-Rの結果では、IFやWHILEなどに対応する命令が使われており、分岐やループごとにポインタを消費しないので気にせず書くことができます。
この例ではRETURNの部分だけがポインタを使っており、CJ GLP0でFB末尾の終了処理へジャンプしています。
まとめ
GX Works3でも、シミュレータとの照合結果からコンパイル後の命令を見ることができました。
FBのマクロ型とサブルーチン型を切り替えると、同じ回路でもロジックが格納される場所や呼び出し方が変わることがわかります。
STも、すべてが普段使うラダー命令に置き換わるわけではなく、IFやWHILEなどに対応する命令が使われていました。
LFBCALLやFUNCALLなど、普段のラダーでは見かけない命令が出てくるのも面白いところです。
FBやSTは書いたとおりに動けば中身を気にする機会は少ないですが、コンパイル後の命令を見ると、引数の受け渡しや処理の流れを追うことができます。
気になった処理を小さなプログラムにして照合してみると、GX Works3が裏で何をしているのか見えてきます。












