構造化プログラム

先週は、アドレッシングの話で、#、!、$が飛交い、少し
混乱していた哲さん。今週も、また様々な記号に悩まされる
ような気がして、足取りが少し重いのです。しかし、ロボは
元気いっぱいで、哲さんを引っ張っていきます。腹を決めて
今日も哲二さんの部屋のドアをノックするのでした。
哲  :こんちは。先週は、アドレッシングの話だったなあ。
      今週も同じかと思うと、少し気が重いぞ。
啓二:そうかなあ、慣れの問題だと思うけれど。
      文系なんだから、言葉や記号については、違和感ないだろう。
哲  :いや、文章や日常使い慣れていることなら、問題ない。が、マイコン
      のアセンブリ言語ときたら、やたら決め事が多くてさ。
啓二:まあ、コンピュータは融通が利かないから、しょうがないなあ。
      アセンブリ言語である程度話を進めるが、苦しいようなら、Cに変えるよ。
哲  :その方がありがたい。ところで、今日の話は何だ。
啓二:構造化プログラムだよ。機械に近いレベルのアセンブリ言語だと、構造を
      しっかり造ってからコーディングしないと、滅茶苦茶になってしまう。
      そこで、構造化プログラムの技法を、最大限に利用するのさ。
哲  :そうか。構造化プログラムでなくて、オブジェクト指向プログラムには
      まだまだ遠いのかな。
啓二:やってやれないことはないが、オブジェクト指向プログラムはメモリを
      大量に消費するから、マイコン向きではないのさ。
      マウスとの情報交換に、オブジェクト指向プログラムは、大げさだし、
      構造化プログラムで充分さ。何でも、オブジェクト指向でプログラム
      する必要はない。F1カーを、渋滞の道で走らせるようなこともない。
      まず、質問だ。適正プログラムとは、何だ。
哲  :適正プログラムだって。それは、入口1カ所、出口1カ所の構造を
      もったプログラムのことだ。


啓二:次の質問だ。適正プログラムの中で利用されている制御構造は、何だ。
哲  :順次(sequence)、選択(selection)、反復(repeatition)の3つだ。


啓二:じゃあ、構造化定理は、どういうものだ。簡単に、述べよ。
哲  :適正プログラムは、順次、選択、反復の3制御構造だけで記述可能である。
      こんな程度かな。これが、アセンブリ言語でプログラムすることと関係が
      あるのか。
啓二:大あり食い虫だ。順次、選択、反復の3つを利用してプログラムすると
      非常にわかりやすいのさ。冗長な部分を、減らせることもある。
哲  :そうか。それならば、何か例で示してくれよ。
啓二:いいよ。それじゃ、ハードウエアをテストするプログラムを作ってみよう。
      これに、構造化プログラムを適用してみるか。
      まずは、出力から調べてみることにしよう。
哲  :そんな単純なことに、構造化プログラムを適用するのは、大げさじゃないか。
啓二:アセンブリ言語で、複雑な内容を作成しても、今のお前じゃ消化不良になる
      だろう。だから、簡単な例で説明するんだよ。
哲  :何か、騙されているような気がしないでも。
啓二:騙してなんかない。ポート2の4ビットにLEDを接続していると仮定して
      LEDを順次点灯する。要するに、半田付けが正しいかを確認してみる。
哲  :じゃあ、まず仕様を作るか。


      これで、どうだ。
啓二:いいんじゃない。最初にすべきことは、ポート2を出力に設定すること。
      そして、全LEDを消灯しておくことだ。ただし、ポートの方向初期化
      と値設定は、Appliletにパラメータを渡せば、必要なコードを生成して
      くれる。だから、不要だ。
哲  :VC++のWizardみたいだなあ。
啓二:あれよりも賢いさ。次に、どう動かすかだ。
哲  :ええっと、4つのLEDを右端から左端に点灯していき、戻ってくる。
      これを繰返せばいいな。
啓二:入口1カ所、出口1カ所にするには、どうしたらいいんだ。
哲  :入口では、右端のLEDを点灯して、他は消灯だな。
      0を点灯、1を消灯とすると、0001をポート2に出力する。
      右端LEDの一つ左を点灯しておいて、出口に向かう。
啓二:これで、適正プログラムのカタチにはなった。次に、中をどう構成するか。
      順次、選択、反復のみで実現するんだ。
哲  :半田付けが確実かどうかを調べるんだから、繰返し、即ち反復だな。
      なら、出口と入口を結んで、ループを構成すればいい。
      フローチャートもどきを作ってみるか。
            初期化
            ポート2に、0001を出力
            ポート2に、0010を出力
            ポート2に、0100を出力
            ポート2に、1000を出力
            ポート2に、0100を出力
            ポート2に、0010を出力
            もどる
啓二:フローチャートもどきにすると、順次と反復だけを適用しているなあ。
      選択も使ってみようぜ。
哲  :選択を使うならば、変数が必要になるな。変数を0〜5の範囲で動かして
      変数値に対応したビットパターンを取出す。それをポート2に出力すると
      できあがるな。
啓二:どうやら、構造化プログラムになったようじゃあないか。
      もう少しフローを美しくしてみよう。
哲  :そうだな。順次、選択、反復を利用したカタチにしたいよな。
          step 0  変数iに対応したビットパターンを取出す
          step 1  変数iを+1する
          step 2  変数iが6ならば、0とする
          step 3  step 0に戻る
啓二:ここまで出来れば、ゆっくりとアセンブリ言語で記述する。
哲  :わからないことがあるんだ。いいかな。
啓二:何だ。
哲  :まず、ビットパターンを確保するには、どう書けばいいのかな。
啓二:データセグメント内に、ディレイティブを使って、列挙すればいい。
      C言語の配列では
          unsigned char ledtab[6] = { 1,2,4,8,4,2 };

      アセンブリ言語では、こんな風にだ。

          DSEG
      LEDTAB: DB 0001b ; 0
              DB 0010b ; 1
              DB 0100b ; 2
              DB 1000b ; 3
              DB 0100b ; 4
              DB 0010b ; 5

      データセグメントの宣言は、DSEGを利用する。この後は、新たにセグメント
      を定義するまで、有効になる。
      ラベルLEDTABで、ビットパターンテーブルのエントリポイントを指定する。
哲  :「:」(コロン)は、ラベルならば、必須なのか。
啓二:そうだよ。これ以降は、DB(Define Byte)で、1つ1つビットパターンを
      書きこんでいけばいい。「;」(セミコロン)から右は、コメントとして
      アセンブラは無視する。だから、エントリポイントからのオフセットを
      コメントにしておいた。
哲  :数字列の最後にある「b」は、2進であることを示すんだな。
啓二:そう。先週も話したが、hが16進、bが2進、何もつけないと10進になる。
      4ビットして定義しなかったけれど、上位は、アセンブラが補足するんだ。
哲  :わからないことの2つ目。変数は、メモリ上にラベルを利用して確保するのか。
啓二:今回は、必要ないよ。レジスタを変数として利用すればいい。
哲  :そういう方法もあるんだ。アセンブリ言語でも、結構柔軟性があるなあ。
啓二:少なくともプログラミング言語なんだから、それくらいの柔軟性がないとね。
      そうそう78kの場合、算術、論理演算に利用するレジスタは、Aレジスタ
      に限定していると考えた方がよい。命令の一覧表があれば、一目でわかるが
      ないから、注意しておくよ。それじゃ、コーディングしてみるか。
哲  :よし、やってみるか。
          step 0  変数iに対応したビットパターンを取出す
      この部分のコーディングになるな。ビットパターンは、テーブルに入っている
      から、テーブルのアドレスを設定する必要があるなあ。

          MOVW HL,#LEDTAB

      としておけば、いいんだな。
      で、変数iは、レジスタBに割当てるとすると、HLにBの値を加算する。
      Bは、0〜5までの範囲だから、レジスタLにBの値を加えればいいんだ。
      おい、加算命令ってADDか。
啓二:そうだよ。それで、やってみなよ。
哲  :簡単、簡単。ADD L,Bでどうだ。
啓二:ブー!ADD命令の転送先は、レジスタAでないとならない。
哲  :そんじゃ、一度レジスタLの値をレジスタAに転送して処理するんだ。面倒。

          MOV A,L
          ADD A,B
          MOV L,A

      これでいいだろう。
啓二:ピンポン。HLレジスタペアが、変数iに対応したビットパターンを格納した
      アドレスをもつから、データを取出しておかないとならない。
      それは、MOV A,[HL]とする。
      次に、ポート2に出力するには、MOV P2,Aと書けばいい。
      じゃあ、全体を書いてみる。
哲  :よし。
          step 0  変数iに対応したビットパターンを取出す

          MOVW HL,#LEDTAB
          MOV  A,L
          ADD  A,B
          MOV  L,A
          MOV  A,[HL]
          MOV  P2,A

啓二:あっと、忘れていた。回路図を見て、0を出力して点灯ならば、ビットを反転
      しないとならない。ビット反転には、排他的論理和を利用する。ポート2へと
      出力する前にビット反転する。XOR A,#0fhとすればよい。
哲  :じゃあ、この命令を入れて、全体は、こうだな。

          MOVW HL,#LEDTAB
          MOV  A,L         ; HL <- HL + offset 
          ADD  A,B
          MOV  L,A
          MOV  A,[HL]      ; get bit pattern
          XOR  A,#0fh      ; invert
          MOV  P2,A

啓二:次の処理は、変数のインクリメントだな。
      先週話したように、便利なincがある。これは、レジスタBでも使える。
哲  :よし。
          step 1  変数iを+1する

          INC  B

      となるなあ。
      次は
          step 2  変数iが6ならば、0とする
      で、6と一致するかどうかか。これもレジスタAに入れて
      比較しないとならないのかな。
啓二:そうだ。比較には、cmp命令を使う。
哲  :それならば、こうするか。
          step 2  変数iが6ならば、0とする

          MOV  A,B         ; A <- B 
          CMP  A,#6

      比較命令のときは、内部で減算しているようだな。
      ああ、そうか、ここでPSWのZフラグを利用するんだな。
啓二:そういうことだ。Zフラグの値で、分岐先を決めてしまう。
      分岐先は、ラベルを利用して、そこに移動するんだ。
      分岐命令は、一致したときZフラグがセットされるから、BZを
      不一致のときは、BNZを利用する。
      ラベルは、アドレスにつけた名札だから、$記号を前につけて指定する。
      Bは、Branchの頭文字からとったんだ。
哲  :じゃあ、こんな風にするのか。

          MOV  A,B         ; A <- B 
          CMP  A,#6
          BNZ  $LOOP_END
      LOOP_END:

啓二:うまい、うまい。でも、レジスタBをクリアする命令がないぞ。
哲  :おっと、分岐命令に気を取られていた。

          MOV  A,B         ; A <- B 
          CMP  A,#6
          BNZ  $LOOP_END
          MOV  B,#0        ; B <- 0
      LOOP_END:

啓二:レジスタBの値が確定したところで、元に戻ればいい。
      これもラベルで、戻る場所を指定する。

          MOV  A,B         ; A <- B 
          CMP  A,#6
          BNZ  $LOOP_END
          MOV  B,#0        ; B <- 0
      LOOP_END:
          BR   $LOOP_START

哲  :全体をまとめてみるか。

          MOVW HL,#LEDTAB
          ;
      LOOP_START:
          MOV  A,L         ; HL <- HL + offset 
          ADD  A,B
          MOV  L,A
          MOV  A,[HL]      ; get bit pattern
          XOR  A,#0fh      ; invert
          MOV  P2,A
          ;
          INC  B
          ;
          MOV  A,B         ; A <- B 
          CMP  A,#6
          BNZ  $LOOP_END
          MOV  B,#0        ; B <- 0
      LOOP_END:
          BR   $LOOP_START

啓二:ラベルLOOP_STARTが入口、ラベルLOOP_ENDが出口。確かに、
      適正プログラムになっている。で、順次、選択、反復もある。
      今日は、ここまでにだな。
哲  :疲れたビー。ビールが飲みたい。
啓二:まったく、もう。お前、体にではなく、頭に栄養を入れろよ。

戻る