テストファースト
早速、Register
構造体のプロパティを実装したいところですが、その前に一つやっておくとよいことがあります。それは「テストプロジェクトの作成」です。
「テストファースト」を知っているでしょうか。簡単に説明すると、「テストプログラムを先に作って点検しながら実装していく」ことを指します(「テスト駆動開発」とも言います)。
今回のように煩雑なものを実装する場合は、特に適している開発スタイルです。余計な作業が増えるという印象があるかもしれませんが、現実的には開発時間の大半をデバッグに費やすので、結果として作業時間を減らすことにつながります。急がば廻れですね。論より証拠。ひとまず筆者を信じて、レジスタの実装の前にテストプロジェクトを作ってください。
では作業を始めましょう。最初は「TestProject」という名のコンソールアプリケーションを「VirtualCPU」ソリューションに追加してください。次に、[プロジェクト]-[参照の追加]-[プロジェクト]-[VirtualCPU]で参照を追加します。下記のソースコードを始めからある「Module.vb」ファイルにコピーし、最後に「TestProject」をスタートアッププロジェクトに設定します。
Imports VirtualCPU Module Module1 Sub Main() Dim rand As Random = New Random() For i As Integer = 0 To 99 If RegisterTest(rand.Next(0, Integer.MaxValue)) = False Then Console.WriteLine( _ "Register構造体が正しく実装されておりません。" & _ "エラーメッセージをよく読んで訂正してください") Exit Sub End If Next End Sub Function RegisterTest(ByVal value As UInteger) As Boolean Dim result As Boolean = True Dim reg As Register = New Register() '-------------------------読み取りテスト------------------------- '正しい値を用意 Dim fullValue As UInteger = value Dim xValue As UShort = CUShort(value And CType(&HFFFF, UInteger)) Dim val As UInteger = value And CType(&HFF00, UInteger) Dim hiValue As Byte = CByte((xValue And CUShort(&HFF00)) >> 8) Dim lowValue As Byte = CByte(xValue And CUShort(&HFF)) 'ダブルワード(32ビット)レジスタ値のチェック reg.ValueU32 = value If fullValue <> reg.ValueU32 Then Console.WriteLine("{0}が正しい値ですが{1}が返されました" & _ "ValueU32プロパティのGetのロジックが間違っています。", _ fullValue, reg.ValueU32) result = False End If 'ワード(16ビット)レジスタ値のチェック If xValue <> reg.ValueU16 Then Console.WriteLine("{0}が正しい値ですが{1}が返されました" & _ "ValueU16プロパティのGetのロジックが間違っています。", _ xValue, reg.ValueU16) result = False End If 'バイト(8ビット)レジスタ値のチェック If hiValue <> reg.ValueHighByte Then Console.WriteLine("{0}が正しい値ですが{1}が返されました" & _ "ValueHighByteプロパティのGetのロジックが間違っています。", _ hiValue, reg.ValueHighByte) result = False End If 'バイト(8ビット)レジスタ値のチェック If lowValue <> reg.ValueLowByte Then Console.WriteLine("{0}が正しい値ですが{1}が返されました" & _ "ValueLowByteプロパティのGetのロジックが間違っています。", _ lowValue, reg.ValueLowByte) result = False End If '-------------------------書き込みテスト------------------------- 'ワード(16ビット)レジスタ値のチェック Dim rand16 As Random = New Random() xValue = rand16.Next(0, UShort.MaxValue) reg.ValueU16 = xValue If xValue <> reg.ValueU16 Then Console.WriteLine("{0}が正しい値ですが{1}が返されました" & _ "ValueU16プロパティのSetのロジックが間違っています。", _ xValue, reg.ValueU16) result = False End If 'バイト(8ビット)レジスタ値のチェック Dim randbyte As Random = New Random() hiValue = randbyte.Next(0, Byte.MaxValue) reg.ValueHighByte = hiValue If hiValue <> reg.ValueHighByte Then Console.WriteLine("{0}が正しい値ですが{1}が返されました" & _ "ValueHighByteプロパティのSetのロジックが間違っています。", _ hiValue, reg.ValueHighByte) result = False End If 'バイト(8ビット)レジスタ値のチェック lowValue = randbyte.Next(0, Byte.MaxValue) reg.ValueLowByte = lowValue If lowValue <> reg.ValueLowByte Then Console.WriteLine("{0}が正しい値ですが{1}が返されました" & _ "ValueLowByteプロパティのSetのロジックが間違っています。", _ lowValue, reg.ValueLowByte) result = False End If Return result End Function End Module
プログラムの内容が分からなくても、気にせず実行してみてください。まだ実装が未完成なので、エラーメッセージが表示されたことでしょう。徐々にこのテストプログラムでエラーが表示されないように、Register
構造体の足りない部分を実装して、開発を進めて行きます。
レジスタ構造体の実装について
先ほどのレジスタ構造体の実装について、よく分からなかった印象を持つ方が多いかと思いますが、実際それほど難しいことは行ってはいません。順を追って説明していくので、安心して読み進めてください。
一番大きなポイントは「ビットごとの論理積の求め方」です。
ビットごとの論理積を求める
「ビットごとの論理積」とは、2つのビットを見て両方が1(True)の場合のみ1(True)、その他は0(False)を返す演算のことです。今回の場合は全桁のビットに対して論理積を求めなくてはなりません。VB.NETでは「Andのビット処理演算子」を使用してそれを実現します。
ここで先程述べたレジスタの例を思い出しましょう。EAXに2,359,271,448(16進数で8C9F A018)を代入して、AXの値を40,984(16進数でA018)にするにはどうすれば良いでしょうか? それは、無視したい桁を0、そのまま残したい桁を1にして、ビットごとの論理積を求めれば良いのです。
1000 1100 1001 1111 1010 0000 0001 1000(16進数で8C9F A018) AND 0000 0000 0000 0000 1111 1111 1111 1111(16進数で0000 FFFF) = 0000 0000 0000 0000 1010 0000 0001 1000(16進数で0000 A018)
論理積では、片方の値が「0」の場合、もう片方の値にかかわらず「0」が返され、片方の値が「1」の場合、もう片方の値がそのまま返される性質を利用しています。
では、早速Windows付属の電卓で計算をしてみましょう。[スタート]-[すべてのプログラム]-[アクセサリ]-[電卓]で電卓を起動し、[表示]-[関数電卓]を選択して、[16進]を選択します。先程の値「8C9FA018」を入力して、[And]ボタンを押し、「FFFF」を入力して[=]ボタンを押すと、答えが「A018」になるはずです。
ALも計算してみましょう。先ほどと同様の手順で「FFFF」の代わりに「FF」とすれば、答えが「18」になったと思います。後は簡単ですね――と言いたいところですが、AHだけ注意が必要です。
AHの算出法
AHはALと同じく1バイトのレジスタですが、普通にFF00とAnd演算しただけでは正しい答えが得られません。それはAHが1ワード(2バイト)の左の部分だからです。少し難しいので、実際に電卓で計算しながらマスターしましょう。
まずは「8C9FA018」を入力してください。次に[And]ボタンを押し、「FF00」を入力して[=]ボタンを押してください。答えが「A000」になったはずです。でも右端の「00」は不要なので削除したいですね(求めたい値は「A0」)。
VBでこれを実現するには「右シフト演算子(>>)」を使用します。右シフトを行うと、連続した(2進数の)ビットに対し、指定した桁数分だけ右にずらすことができます(空いた左の余白は、値が負の場合「1」、それ以外は「0」で埋められる)。ここでは無視したい8ビット分右にシフトすれば欲しい数値が得られます。
以上で、Register
構造体のプロパティのGet部分のロジックを実装することができます。次はSet部分について説明します。