2011年11月24日木曜日

アセンブラを作る

既存のプロセッサならアセンブラは用意されていると思います.自分で仕様を決めたりして独自命令セットになっている場合の話.方法は色々ありますが,とりあえず自分のやった方針だけ.スマートではないです.

方針

  1. アセンブリコードを1行ずつ読み込む.
  2. 読み込んだコードからコメントと空白行を除去し,中間ファイルに出力.
  3. 中間ファイルから1行ずつ読み込んでラベルを探し,出現する行数とセットで記録する.
  4. もう一度中間ファイルから1行ずつ読み込んで機械語へ変換する.分岐命令では3.で記録したラベル情報を参照する.

出来上がったあとで思ったこと

アセンブラを書いたあとの講義で,パイプラインプロセッサの命令スケジューリングを行う課題が出ました.そのとき先生の用意したコードを見て思ったことです.

  • 中間ファイルに出力する必要はない.命令を格納する構造体を作っておけばよい.
  • 最初の読み込み段階でラベルのリストアップも同時に行うべきだった.
  • ラベルの記録に連想配列が使えたらもっと楽だった.

余談

  • 先生から「スクリプト言語を習得したほうが後々便利.C系とスクリプト系を一つずつ使えれば,ちょこっとプログラム組むぐらいのことにはほとんど対応できる.」と言われました.
  • テキストエディタの置換機能を使って,アセンブラとして動作するマクロを作っている人もいました.その手があったか….一本取られた感じ.

2011年11月22日火曜日

ラプラス変換を使って一般解を求める

2階の微分方程式を想定.考え方は単純で,A, Bを任意定数として y'(0)=A, y(0)=B と設定するだけ.あとは普通にラプラス変換を使って解けばよい.

10進から2進への変換

※ 私の発想ではありません.どこかで見かけただけです.

考え方

  1. 変換したい10進数と比較用の数値1,カウンタ変数を用意する.カウンタは0で初期化.
  2. 10進数と比較用数値のANDをとる.結果が0でなければカウンタの示すビットに1を立てる.
  3. 比較用数値を1ビット左へシフト.カウンタをインクリメントする.
  4. すべてのビットが確定するまで2, 3を繰り返す.

10進から2進へ変換できる理由

  • 10進といってもコンピュータ内部では2進表現であり,これを文字として書き出したいだけ.つまり各ビットごとに1か0かを判定すればよい.
  • あるビットを調べたければ,そこだけ1, 残りを0で埋めたものとのANDをとる.(他のビットをマスクする)
  • 条件判定式は0かそうでないかを見る.
    ex. while(1)でも,while(100)でも同じこと.

変換指定子に%bとかあればいいのに.

改行コードについて

改行コードは歴史的経緯からCR, LF, CR+LFの3種類ある.ASCIIではCRに0x0D(\r), LFに0x0A(\n)が割り当てられている.3つのうちどれが出てくるかは処理系によるので,予想できないとして扱うべきかと.

注意事項とか

  • シリアル通信などで改行を扱うときは注意.万全を期すならバイナリモードで出力するべきらしい.でも"\r\n"で動くんだからそれでもいいと思う.Linuxでしか使わないプログラムだったりとかすると割とどうでもいいのかもしれない.
  • Cの文字列探索で改行を調べるとき,"\n"だけを調べると処理系によって結果が変わる.無限ループが発生したりする."\r", "\n", "\r\n"のすべてを調べること.

termios.hについて

termios.hはLinux標準のシリアル通信用Cライブラリ.シリアル通信用の構造体termiosが定義してある.termios構造体にはメンバとして通信速度やキャラクタ長,ストップビットなどの通信設定が格納される.シリアルポートに相当するファイルは "/dev/ttyS0" とか,USB-シリアル変換を使ってると "/dev/ttyUSB0" になったりする.変換はだいたいPL-2303チップで,Linuxだと標準でドライバを持ってることが多い.要するに刺すだけでudevが対応するファイルを生成してくれて,使えるようになる.

簡単な使用手順

  1. シリアルポートに相当するファイルをopen()で開く.fopen()じゃなく,低水準入出力を使う.そのためファイルポインタじゃなく,ファイルディスクリプタが返ってくる.こいつはint型.エラーのときは負の値になってる.
    ※ この時点で失敗するときはだいたいパーミッションが原因.上記仮想ファイルのパーミッションを666とかに設定しとくこと.パーミッションは再起動とか変換アダプタの抜き差しでリセットされる(ファイルが再生成されるから)ので,その都度設定するか,udevの設定ファイルをいじくっておく.
  2. tcgetattr()を使って現在の通信設定を適当に宣言したtermios構造体に退避する.
  3. 2.で使ったやつとは別のtermios構造体に必要な設定を行う.メンバは単に値を代入するものと,各ビットがフラグとなっているものがある.詳細は"termios 設定"とかで調べる.
  4. tcflush()でポートをクリアしてから,tcsetattr()で設定を適用する.
  5. write()で出力する.
  6. read()で応答を読み込む.接続機器の設定によるけど,応答の末尾1, 2文字は多分改行コード.これは'\0'に置き換えた方が幸せだと思う.
  7. tcsetattr()で退避しておいた設定を復帰させてから,close()でファイルを閉じる.

その他注意事項とかメモとか

  • カノニカルモードと非カノニカルモードがあるけど,カノニカルモードの動作がよく分からなかった.というか,カノニカルモードで安定して動作させることができなかった….
  • パーミッション周りは自分のようなLinux初心者がよくはまると思う.まずは権限を疑うこと.
  • termios構造体のメンバのビットを操作して設定する,というのがちょっと分かりにくかった.マイコンのプログラミングやってるとすぐに把握できるんじゃないかと思う.
  • 通信するだけならすでにツールがいろいろあるので,そっちを使った方が幸せ.