CPUよもやま話(レジスタとクロック)
前回までに「レジスタ」という単語が何回か出てきましたが、レジスタが何者なのかについてはほとんど書いていなかったので、ここらで書いておこうと思います。
なぜCPUの中にレジスタが必要なのか?
CPUの中にはデータを一旦保持するためのレジスタがいくつか入っています。で、なぜこのレジスタが必要なのかということなんですが・・・・・・料理にでも例えてみようと思います。
料理をする際は、食材をまな板の上に置いて包丁で切ったりすると思います(空中でも出来なくはないですが、置く場所がないととても不便です)。
食材は順次、冷蔵庫から取り出します。が、冷蔵庫に無いものはスーパーに買いに行くことになります。
ということで、こんな感じで例えてみました。
- 食材:データ
- まな板:レジスタ
- 冷蔵庫:メモリ
- スーパー:HDD
出来上がった料理は冷蔵庫に入れることもできるし、冷蔵庫からスーパーに売りに行くこともできます。・・・・・・って、そんなスーパーあるかい(@_@;)
この例え話なら
- スーパー:CD-ROMまたはDVD-ROM
のほうがしっくりくるかもしれない。どっちにしても分かりにくかった人は無視して下さい。
レジスタの正体はDフリップフロップ
先ほども書きましたが、レジスタはデータを一旦保持するためのものです。データを保持するもので思い出されるのはフリップフロップですが、レジスタにはDフリップフロップというものが使われていたりします。
Dフリップフロップは、クロックが立ち上がった瞬間の入力Dを出力Qに保持(次にクロックが立ち上がるまで)します。
真理値表
クロックに関係なく出力Qを保持したいのなら、下のように出力Qを入力Dへ直結します。
Dフリップフロップを、必要なbit数並べたものがレジスタです。
ADD A,B はどう実現するのか?
前回、アセンブリ言語について少し触れましたが、例えば ADD A,B のような処理はどのように行われるのでしょうか?
ADD A,B の処理内容は
です。
回路図で書いてみましょう。(仮に、4bitCPUとする)
このように、レジスタA・レジスタBの出力を加算器の入力に接続し、加算器の出力をレジスタAの入力に戻します。レジスタBは値を保持するように、出力を入力に戻します。
次に動作を実験してみます。あらかじめ、レジスタAに1010、レジスタBに0101が保持されている状態から
ADD A,B
ADD A,B
を(つまり、ADD A,Bを2回)、Logisimでシミュレートしてみます。目で追えるように、クロックを0.25Hzで動作させるので刮目してご覧下さい。
解説
- 1行目のADD A,Bで1010+0101=1111となり、レジスタAの入力側に戻ります。
- 次のクロックで1111がレジスタAに保持されます。同時に、2行目のADD A,Bが実行されます。1111+0101=10100となるため、加算器の4ビット目から桁上がりが出力されています。
- 次のクロックで、0100(オーバーフローした出鱈目な値)がレジスタAに保持され、桁上がりはCフラグとして保持されます。
Cフラグを保持するのもDフリップフロップなので、クロックの立ち上がりで値を保持します。このフラグを参照して条件分岐させるなら、目的のフラグが保持されたクロックの立ち上がり(先のソースコードであれば直後の3行目)で実行する必要があります。クロックが進めば目的のフラグではなくなってしまうからです。
さて、少し脱線しましたが、これでADD A,Bを実行できる回路が分かりました。しかし、これでは回路が固定であるため、永遠にADD A,BしかできないCPUになってしまっています。
なので、転送元と転送先を切り替えることができる回路を追加しなければなりません。さらに言えば、その切り替えをプログラムで行えるようにする必要がある訳です。
(例えば、下のような命令ごとに回路を繋ぎかえできるようにしなければならない)
MOV A,B レジスタAのデータをレジスタBに転送
MOV B,A レジスタBのデータをレジスタAに転送
ADD A,B レジスタAとレジスタBの値を加算してから、レジスタAに転送
ADD B,A レジスタAとレジスタBの値を加算してから、レジスタBに転送
MOV A,A レジスタAのデータをレジスタAに転送(要するに自己保持)
MOV B,B レジスタBのデータをレジスタBに転送(要するに自己保持)
Dフリップフロップの中身
Dフリップフロップの実際の内部回路は複雑なので、単純化した図で表します。(本の受け売り)
クロックが0の時は、前段と後段では縁が切れ、後段だけで出力を自己保持しています。入力に変化があっても出力は保持されます。
クロックが1の時は、前段と後段が繋がります。前段で自己保持した出力を後段は素通しします。前段と入力とは縁が切れているので、入力に変化があっても出力は保持されます。
ここで、素朴な疑問・・・・・・。クロックが1になる瞬間の入力を出力に保持する訳ですが、クロックが1になると同時に(加算器などから)逆の値が入力にフィードバックされたらどうなるでしょう?1だと思ってたけどやっぱり0で・・・・・・、でもそれなら逆の逆の値がフィードバックされて・・・・・・堂々巡り?( ̄q ̄;)
という間抜けな考えをいだくのは私だけだと思いますが、この心配は要りません。
Dフリップフロップの入力から出力には遅延があり、さらに加算器の入力から出力にも遅延があり、さらに言えば電子が配線の中を移動する時間もあるので、厳密には同時ではありません。クロックの立ち上がりから少し遅れて、入力へのフィードバックがあると思えば良いのです。この時間は、人間の感覚からすれば限りなくゼロに近いけれど、ゼロではないということですね。
CPUよもやま話(アセンブリ言語で命令のイメージを掴もう)
今回は、CPUに対しての命令がどのように行われるのか見てみましょう。
アセンブリ言語とは?
皆さんご存知のとおりコンピューターの中で情報は、0と1で表されます。0と1で表現されたプログラムは機械語と呼ばれます。しかし、命令の内容が0と1の羅列では人間にはかなり分かりにくいものです。
そんなとき、アセンブリ言語を用いて説明すると比較的分かりやすくすることができます。何故かというと、アセンブリ言語のソースコードの1行は機械語の1命令に相当し、かつ機械語の内容を(人間が理解・記述しやすいように簡略化した英単語や記号の組み合わせで)そのまま表現しているからです。
例えば、こんなのがあります。
MOV A,B
これは、レジスタBのデータをレジスタAに転送せよ という命令です。
オペランドにはレジスタだけではなく、メモリ→レジスタ、レジスタ→メモリ、即値→レジスタ・・・・・・等とすることもできます。(CPUがそのような命令を実装していることが前提)
※即値とはコード中に直接書かれた値のことです。イミディエイトデータとも呼びます。
他にも、こんなのがあります。
ADD A,B レジスタAとレジスタBの値を加算して、レジスタAに転送
NOT A レジスタAの値を論理否定して、レジスタAに転送
JMP [番地] 指定番地へ無条件でジャンプ
IN A I/Oの入力ポートのデータをAレジスタに転送
OUT A Aレジスタの値をI/Oの出力ポートに転送
等々。ただし、繰り返しになりますがCPUがそのような命令を実装していれば、です(例えば、MOVでメモリからメモリへ転送したくても、それに該当する機械語〔回路〕が存在しなければ実行することはできない)。次回以降、機械語の正体を説明する際に、ポイントになるので軽く覚えておいて下さい。
アセンブリ言語で書かれたソースコードは、アセンブラというソフトウェアで機械語に変換して実行します。(今回はイメージを掴むためにアセンブリ言語を持ち出しているだけなので、変換うんぬんは気にしなくてもよいです)
さて、命令がどのようにCPUで実行されているか、高水準言語とアセンブリ言語を比較してイメージを掴んでみましょう。今回は扱う命令やデータがすべて1バイト(8bit)であるものとします。
順次進行の仕組み(プログラムカウンタとは?)
基本的にプログラムはメモリの小さい番地から大きい番地へ順に実行されます。これをプログラムの基本三構造のうち順次進行と呼びます。プログラムカウンタには、次に実行される命令が入ったメモリ番地が格納されています。まず、プログラムの開始位置(メモリ番地)がセットされ、順次、カウントアップしていきます。命令は、プログラムカウンタの値を参照してメモリから命令レジスタに取り出されるという寸法です。
試しに算術演算を順次進行で行ってみましょう。
(高水準言語で表現した場合)
a = 10
b = 6
c = a + b
(アセンブリ言語で表現した場合)
MOV [0],10 即値の10をメモリの0番地に転送
MOV [1],6 即値の6をメモリの1番地に転送
MOV A,[0] メモリの0番地の値をAレジスタに転送
MOV B,[1] メモリの1番地の値をBレジスタに転送
ADD A,B レジスタAとレジスタBの値を加算して、レジスタAに転送
MOV [n],A Aレジスタの値をメモリのn番地に転送
END ソースコードの終わり(OSに戻る)
※大カッコで囲んだ数値はメモリの番地として扱われます。(大カッコがなければ即値として扱われます)
※メモリの番地は説明のために適当に割り当てたものです。
高水準言語であればコンパクトに書けることを、アセンブリ言語では長々とした命令になってしまいます。アセンブリ言語(≒機械語)で複数の命令に渡る処理を、人間に分かりやすく・抽象的に記述できるようにしたものが高水準言語です。って、説明不要ですね(@_@;)
繰り返しの仕組み
次に、基本三構造のうちの繰り返しをやってみましょう。アセンブリ言語でJMPのオペコードを使うと、プログラムカウンタの値を強制的に変更し、プログラムの流れを変更することができます。試しに先の順次進行のコードを無限ループさせてみましょう(処理の内容に意味はありません)。
(高水準言語で表現した場合)
jp:
a = 10
b = 6
c = a + b
GoTo jp
(アセンブリ言語で表現した場合)
MOV [0],10 即値の10をメモリの0番地に転送
MOV [1],6 即値の6をメモリの1番地に転送
MOV A,[0] メモリの0番地の値をAレジスタに転送
MOV B,[1] メモリの1番地の値をBレジスタに転送
ADD A,B レジスタAとレジスタBの値を加算して、レジスタAに転送
MOV [n],A Aレジスタの値をメモリのn番地に転送
JMP [2] 指定アドレスをプログラムカウンタにセット
END ソースコードの終わり(OSに戻る)
条件分岐の仕組み
最後に、基本三構造のうちの条件分岐をやってみましょう。レジスタAとBの加算結果が15を超えるのなら、何もせずに処理を終了します。
(高水準言語で表現した場合)
a = 10
b = 6
If a + b <= 15 Then
c = a + b
End If
(アセンブリ言語で表現した場合)
MOV [0],10 即値の10をメモリの0番地に転送
MOV [1],6 即値の6をメモリの1番地に転送
MOV A,[0] メモリの0番地の値をAレジスタに転送
MOV B,[1] メモリの1番地の値をBレジスタに転送
ADD A,B レジスタAとレジスタBの値を加算して、レジスタAに転送
JC [9] キャリーフラグが1なら指定アドレスをプログラムカウンタにセット
MOV [n],A Aレジスタの値をメモリのn番地に転送
END ソースコードの終わり(OSに戻る)
前々回の加算器を思い出して下さい。ADD A,Bは、10(1010)+6(0110)=16(10000)で、4ビット目が桁上がりするので、これをキャリーフラグにセットしています。
これを応用して、例えばレジスタAがn以上なら条件分岐 という処理を次のようにするができます。
ADD A,9 レジスタAの値に(16-n)の値を加算する(この場合、7以上でキャリーが発生する)
JC [番地] キャリーフラグが1なら指定アドレスをプログラムカウンタにセット
JCは(Jump if Carryの略)はキャリーフラグが1ならジャンプします。逆にキャリーフラグが0ならジャンプするJNC(Jump if Not Carry)というのもあります。
参照するフラグレジスタの種類によって、他にもいろいろな条件付ジャンプがあります。
回数指定のループも条件付ジャンプで作ることができます。やり方は簡単なので、ぜひ各自で考えてみて下さいね!(←手抜き)
記事を書くにあたって、以下の書籍を参考にしています。
- 作者: 渡波郁
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2003/10/01
- メディア: 単行本(ソフトカバー)
- 購入: 35人 クリック: 445回
- この商品を含むブログ (193件) を見る
プログラムはなぜ動くのか 第2版 知っておきたいプログラムの基礎知識
- 作者: 矢沢久雄
- 出版社/メーカー: 日経ソフトウエア
- 発売日: 2007/04
- メディア: 単行本(ソフトカバー)
- 購入: 45人 クリック: 646回
- この商品を含むブログ (74件) を見る
なお、今回使用したアセンブリ言語は、あくまで説明用の架空のものです。
CPUよもやま話(情報を書き込む・記憶する・読み出す)
今回は、情報がどのようにして記憶(メモリー)されているのかを調べてみたいと思います。
皆さんご存知のとおりコンピューターの中で情報は、0と1で表されます。コンピューターは電気回路なので、電圧が低い状態を0、電圧が高い状態を1としています(回路設計者のさじ加減で逆になることもありますが)。
メインメモリーなどでは、電源が供給されている限りは、書き込まれた情報を記憶していますが、どのようにして0と1の状態を記憶しているのでしょうか?
フリップフロップとは?
下の回路を見て下さい。これはフリップフロップと呼ばれるのもです(正確にはRS型フリップフロップです。専門家の方には当たり前過ぎるシロモノですが・・・・・・)
S(Set)に入力された情報がQとして出力され、入力が無くなっても自己保持するという性質の回路です。自己保持されたQの出力を解除するには、R(Reset)に入力を与えます。
真理値表
表中の「禁止」はそのような使い方をすると壊れる、という意味ではなく、通常そのような使い方をしないという意味です(意図的に使う場面もあるらしいですが)。
もうお気づきだと思いますが、この回路一つで1bitの情報を記憶することができます。
このように1bitの情報を記憶するための回路構成をメモリーセルと呼びます。
言い換えれば、1bitの情報を記憶することができるのであれば、必ずしもフリップフロップである必要もないということです。
(例えば、コンデンサの充電/放電を、1/0として扱うなど)
ただし、今回は基本的(原始的)でかつ分かりやすいフリップフロップを用いて話を進めていきたいと思います。
1bitの情報を記憶する回路を理解できたのであれば、あとはそれを必要なbit数並べればメインメモリーができます。
なんて簡単!
メインメモリっぽいものを作ってみる
メインメモリーは、一般的には1バイト(8bit)ずつに区切って並べられ、区切りごとにアドレス(番地)が割り振られています。
先ほど作ったフリップフロップを部品化(図中のFF)して、Logisimでメインメモリっぽいものを作ってみましょう。
随分ごちゃごちゃしてますね。メモリーセルが縦一列に1バイト分並んでいるので、4GBのSRAMを作るなら、これを約40億列並べる必要がある訳です。
配線は、縦にアドレスバスが共用されており、横にデータバスが共用されています。このように配線したものをマトリクスと呼びます。
配線を共用せずに、各bitごとに配線を引き出すと、8×40億の端子が必要になるので、この方式にたどり着くのはごく自然な流れですね・・・・・・。
実際の製品では書込側と読出側の端子も共用されています。(書込みモード/読出しモードを切り替えて使用する)
回路は若干(大層?)オリジナルな部分がありますが、あくまで原理を説明するためのモデルなので我慢して下さい。
実験として、0番地を指定して01010101を書込み、次に1番地を指定して10101010を書込み、最後にn番地に11111111を書込みします。書込みが終わったら、読出しモードにして0番地を出力、次に1番地を出力、最後にn番地を出力してみます。
見づらいかもしれませんが、FFの右上(出力Q)が明るい緑色であれば、1を記憶している状態です。(0に該当する出力が、ところどころE〔Error:信号がHでもLでもない状態〕になっていますがそこは無視して下さい。)
本当ならアドレスは二進数(00000000~FFFFFFFF等)で指定されるので、これを各アドレスバスへスイッチングする回路も搭載する必要があります。
CPUの片鱗を見てみる
さて、メモリーの原理っぽいものが理解できたところで、最後にメモリーの情報を読み出してCPU内で演算し、結果をメモリーに書き込むという流れを見て今回は終わりたいと思います。
まず、何らかのプログラム言語で
a=85
b=170
c=a+b
と、記述されたものを実行した時、CPUとメモリーの間ではどのようなことが行われるのでしょうか?(変数の型は仮に1バイトとする)
次の図を見て下さい。
※変数aは0番地、変数bは1番地、変数cはn番地に割り当てられたものとします。これは説明のために適当に割り当てたものです。
まず、85と170の二進数値がそれぞれの番地に格納されます。次にこの85と170がレジスタに転送され、ALUで加算が行われます。加算結果は255となり、これがレジスタに上書きされ、その後、メモリーの該当番地に格納されます。(どのレジスタがどのように使用されるかはコンパイラの気分次第です)
見たまんまなので説明の必要はなかったかもしれませんが、ここでレジスタという謎の人物が登場してきましたね(これも中身はフリップフロップです。詳細は次回以降に)。
CPUの片鱗が少しだけ見えた気がしますが、実はまだまだ謎の部分もあります。データの読み書きや、演算の命令は誰が(何が)出すのか?これらはどのようなタイミングで実行されていくのか?などなど。
次回以降、その辺りの謎の部分も少しずつ紐解いていきたいと思います。
CPUよもやま話(論理ゲートを使って加算器を作ってみよう)
前回はトランジスタを利用した基本的な論理ゲートであるAND、OR、NOTの作り方を紹介しました。今回は、その論理ゲートをさらに組み合わせて加算器なるものを作ってみましょう。専門家の方には初歩的過ぎるかもしれませんが、そのあたりはご容赦下さい。
その前に、加算器ってなーに?って言う人もいるかもしれないので簡単に説明しておきます。
加算器とは、二進数と二進数を足し算(算術的加算)して、二進数で計算結果を出力するための回路です。例えば、0101と0101を入力として与えたなら、1010を出力します。(10進数での、5+5=10と同義)
よく混同されがちなのが、論理演算です。論理演算は入力に対して真(1)か偽(0)を出力するだけなので算術的意味はありません。具体的に論理和と算術的加算の違いを見てみましょう。
まずは、論理和から
次に、算術的加算
このように、結果は違います。
しかし両者はまったく1ミリも無関係なのではなく、算術的演算は、論理演算を応用してテクニカルに実装されているのです。
?(゜ρ゜)?
安心して下さい、その辺を順を追って説明してみようと思います。
半加算器とは?
まずは桁上がりをどう実装するか?を考えなくてはいけません。
そこで考案されたのが、この半加算器です。(AとBは同じ桁の入力、Sはその桁の合計値出力、Cは桁上がり出力です)
真理値表
AとBが1ならばこの桁は0になり、桁上がりが発生するというものです。論理回路をシミュレートできる「Logisim」を使って真理値表どおりに動作させてみたので、見比べてみて下さい。
繰り返しになりますが、 AとBの両方が1であった場合Sが0、Cが1になるようになっています。つまりその桁の算術的加算をこのようなAND、OR、NOTの組み合わせで実現することができます。(XORを使う場合もあり)
全加算器とは?
さて、半加算器では、上位桁への桁上がり出力はできるものの、下位桁からの桁上がりを受け付ける回路がありません。そこで考案されたのが、半加算器2つとORを組み合わせて作られた全加算器です。(AとBは同じ桁の入力、Xは下位桁からの桁上がり入力、Sはその桁の合計値出力、Cは桁上がり出力です)
真理値表
Logisimのアニメーションと真理値表を見比べてみて下さい。
このように下位桁からの桁上がりを含めた算術的加算を、半加算器2個とORで実現しています。
4ビットの加算器を作ってみる
勘のいい人なら半加算器と全加算器を組み合わせて、任意ビット(桁)の加算器を作れることに気付いたかもしれません。
最下位ビットには下位ビットからの桁上がり入力がない半加算器を置き、上位ビットには残り必要な個数の全加算器を置きます。半加算器の桁上がり出力(C)を次のビットの全加算器の桁上がり入力(X)に接続し、さらにその全加算器の桁上がり出力(C)を次のビットの全加算器の桁上がり入力(X)に接続し・・・・・・というふうに半加算器1個+全加算器3個を組み合わせれば4ビットの加算器になります。回路図中のHA(Half adder)は半加算器、FA(Full adder)は全加算器を表します。どちらも先ほどLogisimで作った半加算器と全加算器を部品化してLogisimで再利用したものです。(Logisimは一枚絵で回路を作るだけでなく、別途作成した回路を部品化して別の回路で再利用することができます。さらに再利用したものを含む回路をも部品化して別の回路で再利用できる・・・・・・プログラミングで言えばクラスとクラスの継承のような使い方ができるので便利です!)
入力Bを0101で固定し、入力Aを0000→0001→0011→0111→1111というふうに変化させたときの出力Sは次のようになります。
最後のケースは4ビット目が桁上がりしていますが、桁上がりを入力する5ビット目がないのでオーバーフローしています。
任意ビットの加算器を作るのなら、このように半加算器1個+全加算器n個で実現することができます。
さて、今回紹介した加算器は最も原始的な構造のものと言えるでしょう。この他に加算器を応用した減算器、加算器の動作を高速化するためのキャリー先読み・キャリー予測・・・・・・etc.これらの仕組みはCPUの中のALU(演算装置)の基本となるものです。この辺も機会があれば、記事にしてみようと思います。
既に紹介済みですが、論理回路作成と動作確認はフリーソフトの「Logisim」を使用しました。感電や半田ごてによる火傷の心配がないので手軽に遊ぶことができます。
CPUよもやま話(トランジスタと論理回路)
どんな高水準プログラミング言語で書かれたプログラムであっても最終的には機械語に変換され、電気信号となってCPUで処理されます。CPUの中ではレジスタやALUなどが電気信号を処理しますが、じゃあレジスタやALUはそもそも何で構成されているんでしょうか?
自分の作ったプログラムが電気信号となって回路をどのように駆け巡っているのか想像できれば、プログラムに対する愛着もさらに深まるというものです。
今回は、CPUを構成する最小単位といえる「トランジスタ」について話してみようと思います。
トランジスタはP型半導体とN型半導体を組み合わせて作られた半導体素子です。組み合わせ方によって、PNP型とNPN型がありますが現在はNPN型が主流になっているそうです。
導体や絶縁体なら何となく知ってるけど、半導体って何?美味しいの?(゜ρ゜)っていう人がいるかも知れませんね。
いや、いませんね。
トランジスタ(NPN型)ってこんなのです。
通常はコレクターエミッタ間には電流が流れませんが、ベースーエミッタ間に微小の電流を流す(ベースーエミッタ間に電圧を加える)と、コレクターエミッタ間が導通になります。図記号で表現すると下のようになります。
上の図で分かりにくい場合は、下のように簡易的に考えても良いと思います。ベースがスイッチをON・OFFしているようなイメージです。
こんな感じです。
このように電流を流したり、流さなかったりすることができるものが半導体なのです。
(電子の動きについて説明するとややこしくなるので割愛)
他にも、半導体素子にはダイオード・サイリスタ・ツェナーダイオード・発光ダイオード・フォトカプラ等がありますが、それぞれが適材適所で活用されています。
このうちトランジスタのスイッチング作用は電子計算機にとって、かなり重要な役割を果たすことになります。
専門外の人でも一度ぐらいは見たことがあるかもしれませんが、トランジスタってこんなヤツで、CPUの中にはこれのさらにさらにさらに小さいバージョンが数億個入っているそうです。
トランジスタキット,SODIAL(R)600x 15値x 40件トランジスタTO-92詰め合わせボックスキット 新しい
- 出版社/メーカー: SODIAL
- メディア: その他
- この商品を含むブログを見る
で、 このトランジスタを使って何ができるのかというと、上手く組み合わせることで論理回路を作ることができるのです。
ANDとかORとかNOTのあれです。
実際にどうやって作るのかやってみましょう。
※デジタル信号に置き換えてイメージしやすいように、電源は1Vで供給されているものとします。1Vが「1」、0Vが「0」です。
AND回路の作り方
これはANDの図記号
これをトランジスタで作るとこうなります(※少し簡略化しています)。
AとBが直列に繋がっているので、AとBに電圧を印加した時だけCに1Vの電圧が現れます。
真理値表
A | B | C |
---|---|---|
0 | 0 | 0 |
1 | 0 | 0 |
0 | 1 | 0 |
1 | 1 | 1 |
OR回路の作り方
これはORの図記号
これをトランジスタで作るとこうなります(※少し簡略化しています)。
AとBが並列に繋がっているので、AまたはBのいずれか(もしくは両方)に電圧を印加した時にCに1Vの電圧が現れます。
真理値表
A | B | C |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 1 |
NOT回路の作り方
これはNOTの図記号
これをトランジスタで作るとこうなります(※少し簡略化しています)。
AがOFFの時は抵抗の電圧降下がほぼ無視できるので、Bに1Vが現れます。AがONの時は抵抗の電圧降下によって、Bに0Vが現れます。
真理値表
A | B |
---|---|
0 | 1 |
1 | 0 |
電圧降下について
電圧降下という言葉に馴染みがない人のために少し補足します。電圧降下とは回路中に存在する抵抗の両端に電位差が生じる現象のことです。今回登場した回路は、抵抗がひとつに見えますがトランジスタも抵抗のひとつとして計算してみましょう(導通の時は0Ω、非道通の時は∞Ωとして考える)。NOT回路を例にすると、コレクタ側にある抵抗をR1、トランジスタの抵抗をR2とした場合、R1の電圧降下とR2の電圧降下は次のように求めることができます。
E=1V、R1=1Ω(適当)とします。
トランジスタが導通(ON)状態であれば、
- V1=1/(1+0)×1=1V
- V2=0/(1+0)×1=0V
トランジスタが非導通(OFF)状態であれば、
- V1=1/(1+∞)×1≒0V
- V2=∞/(1+∞)×1≒1V
です。
ブレッドボードなどを使って自分で電子工作してみるのも楽しいかもしれません。
仕組みが何とな~くイメージできれば、もうトランジスタのことは忘れても大丈夫です。これで論理回路の機能を抽象化!(気が向いたらたまに思い出してあげて下さい)
次回以降、CPUの他の構成部品などについても書いていきたいなと思っています。
作図に以下のフリー素材を使用させていただきました。
Option Compare Text でマッチする文字列を全部調べてみた(Tips-15)
今回は以下の記事の続きです。
Option Compare Textを宣言して文字列比較を行った場合、文字コードが同じもの以外にどんな文字列が類縁の文字としてマッチするのか・・・・・・。前回は比較サンプルが少なく不十分だったので、今回は文字コード0000~FFFFの全組み合わせを漏れなく調べてみました。
方法は、エクセルシートの1行目と1列目に文字列を配置し、行と列の全組み合わせを比較します(文字コードが制御文字や不使用領域であっても無関係に比較します)。比較の結果がTrueであれば、行と列が交差する位置に○をつけるという寸法です。
コードは後述しますが、文字列の生成~文字列の比較~シートへの結果出力まで、すべてVBAで行います。
基本的には、このように右下がりに○が付きますが、表示を縮小していくと○の重複箇所が見えてきます。
何がマッチして、何がマッチしないのか・・・・・・。○をフィルタリングすることで確認することが可能になります。4294967296通りの組み合わせを掲載することはできないので、記事内ではいくつか抜粋して紹介することにします。
半角の 1
半角、上付き、下付き、丸数字、全角にマッチします。
丸数字の違いがわかりづらいのでちょっと拡大してみます。
よく見ると二重丸だったり、文字の太さが違ったりしていますね。
半角の A
半角大文字、半角小文字、下付きの大文字、上付きの大文字、上付きの小文字、下付きの小文字、全角大文字、全角小文字にマッチします。
平仮名の あ
平仮名、全角片仮名、半角片仮名にマッチします。
平仮名の ゑ
平仮名、全角片仮名にマッチします。同じ読みの「え」や「エ」にはマッチしません。
ローマ数字の Ⅳ
ローマ数字の大文字と小文字にマッチします。
ギリシャ文字の α
ギリシャ文字の大文字と小文字にマッチします。見た目は似ているけどアルファベットのAにはマッチしません(半角の Aで、915行目がマッチしていないことからも分かります)。
特殊文字
同じ文字コードの文字のみマッチします。(電話は白と黒で別物扱いだし、ゆきだるまは他にも数種類あるけれどマッチしていません)
漢字
同じ文字コードと同じ意味(?)の韓国文字がマッチする。これからは덟렗と名乗ることにします(嘘)
さて・・・・・・。
こんなことをしても何の得にもならないので、試してみようという酔狂な人はいないと思いますが、コードを掲載しておきます(使い捨てなので雑ですけど)。エクセルシートの列数が足りないので、全部調べる場合は5シートに分けて5つのプロシージャを実行する必要があります。まっさらなシートをアクティブにした状態でプロシージャを実行すると、文字列の生成→文字列の比較→シートへの結果出力までを自動で行います。(結果を見やすくするための罫線の描画やオートフィルターの適用はプログラムに含まれていません)
文字コード0000~3FFEまでと0000~FFFFの比較
Option Compare Text
Sub 文字コード3FFEまで()
Dim 行配列(2 To 65537) As String
Dim 列配列(2 To 16384) As String
行 = 2
列 = 2
Application.ScreenUpdating = False
For Unicode = &H0 To &H7FFF
If Unicode < &H3FFF Then
行配列(行) = ChrW(Unicode)
列配列(列) = ChrW(Unicode)
行 = 行 + 1
列 = 列 + 1
Else
行配列(行) = ChrW(Unicode)
行 = 行 + 1
End If
Next Unicode
For Unicode = &H8000 To &HFFFF 'ここからマイナス符号付き
行配列(行) = ChrW(Unicode)
行 = 行 + 1
Next Unicode
Range(Cells(1, 2), Cells(1, 16384)).Value = 列配列
Range(Cells(2, 1), Cells(65537, 1)).Value = WorksheetFunction.Transpose(行配列)
For 行 = 2 To 65537
For 列 = 2 To 16384
If 行配列(行) <> "" And 列配列(列) <> "" Then
If 行配列(行) = 列配列(列) Then
Cells(行, 列).Value = "○"
End If
End If
Next 列
Application.StatusBar = "比較中..." & 行 & "/" & "65537行"
DoEvents
Next 行
Application.StatusBar = False
End Sub
文字コード3FFF~7FFDまでと0000~FFFFの比較
Option Compare Text
Sub 文字コード7FFDまで()
Dim 行配列(2 To 65537) As String
Dim 列配列(2 To 16384) As String
行 = 2
列 = 2
Application.ScreenUpdating = False
For Unicode = &H0 To &H7FFF
If Unicode >= &H3FFF And Unicode < &H7FFE Then
行配列(行) = ChrW(Unicode)
列配列(列) = ChrW(Unicode)
行 = 行 + 1
列 = 列 + 1
Else
行配列(行) = ChrW(Unicode)
行 = 行 + 1
End If
Next Unicode
For Unicode = &H8000 To &HFFFF 'ここからマイナス符号付き
行配列(行) = ChrW(Unicode)
行 = 行 + 1
Next Unicode
Range(Cells(1, 2), Cells(1, 16384)).Value = 列配列
Range(Cells(2, 1), Cells(65537, 1)).Value = WorksheetFunction.Transpose(行配列)
For 行 = 2 To 65537
For 列 = 2 To 16384
If 行配列(行) <> "" And 列配列(列) <> "" Then
If 行配列(行) = 列配列(列) Then
Cells(行, 列).Value = "○"
End If
End If
Next 列
Application.StatusBar = "比較中..." & 行 & "/" & "65537行"
DoEvents
Next 行
Application.StatusBar = False
End Sub
文字コード7FFE~BFFCまでと0000~FFFFの比較
Option Compare Text
Sub 文字コードBFFCまで()
Dim 行配列(2 To 65537) As String
Dim 列配列(2 To 16384) As String
行 = 2
列 = 2
Application.ScreenUpdating = False
For Unicode = &H0 To &H7FFF
If Unicode >= &H7FFE And Unicode <= &H7FFF Then
行配列(行) = ChrW(Unicode)
列配列(列) = ChrW(Unicode)
行 = 行 + 1
列 = 列 + 1
Else
行配列(行) = ChrW(Unicode)
行 = 行 + 1
End If
Next Unicode
For Unicode = &H8000 To &HFFFF 'ここからマイナス符号付き
If Unicode >= &H8000 And Unicode < &HBFFD Then
行配列(行) = ChrW(Unicode)
列配列(列) = ChrW(Unicode)
行 = 行 + 1
列 = 列 + 1
Else
行配列(行) = ChrW(Unicode)
行 = 行 + 1
End If
Next Unicode
Range(Cells(1, 2), Cells(1, 16384)).Value = 列配列
Range(Cells(2, 1), Cells(65537, 1)).Value = WorksheetFunction.Transpose(行配列)
For 行 = 2 To 65537
For 列 = 2 To 16384
If 行配列(行) <> "" And 列配列(列) <> "" Then
If 行配列(行) = 列配列(列) Then
Cells(行, 列).Value = "○"
End If
End If
Next 列
Application.StatusBar = "比較中..." & 行 & "/" & "65537行"
DoEvents
Next 行
Application.StatusBar = False
End Sub
文字コードBFFD~FFFBまでと0000~FFFFの比較
Option Compare Text
Sub 文字コードFFFBまで()
Dim 行配列(2 To 65537) As String
Dim 列配列(2 To 16384) As String
行 = 2
列 = 2
Application.ScreenUpdating = False
For Unicode = &H0 To &H7FFF
行配列(行) = ChrW(Unicode)
行 = 行 + 1
Next Unicode
For Unicode = &H8000 To &HFFFF 'ここからマイナス符号付き
If Unicode >= &HBFFD And Unicode < &HFFFC Then
行配列(行) = ChrW(Unicode)
列配列(列) = ChrW(Unicode)
行 = 行 + 1
列 = 列 + 1
Else
行配列(行) = ChrW(Unicode)
行 = 行 + 1
End If
Next Unicode
Range(Cells(1, 2), Cells(1, 16384)).Value = 列配列
Range(Cells(2, 1), Cells(65537, 1)).Value = WorksheetFunction.Transpose(行配列)
For 行 = 2 To 65537
For 列 = 2 To 16384
If 行配列(行) <> "" And 列配列(列) <> "" Then
If 行配列(行) = 列配列(列) Then
Cells(行, 列).Value = "○"
End If
End If
Next 列
Application.StatusBar = "比較中..." & 行 & "/" & "65537行"
DoEvents
Next 行
Application.StatusBar = False
End Sub
文字コードFFFB~FFFFまでと0000~FFFFの比較
Option Compare Text
Sub 文字コードFFFFまで()
Dim 行配列(2 To 65537) As String
Dim 列配列(2 To 16384) As String
行 = 2
列 = 2
Application.ScreenUpdating = False
For Unicode = &H0 To &H7FFF
行配列(行) = ChrW(Unicode)
行 = 行 + 1
Next Unicode
For Unicode = &H8000 To &HFFFF 'ここからマイナス符号付き
If Unicode >= &HFFFC And Unicode <= &HFFFF Then
行配列(行) = ChrW(Unicode)
列配列(列) = ChrW(Unicode)
行 = 行 + 1
列 = 列 + 1
Else
行配列(行) = ChrW(Unicode)
行 = 行 + 1
End If
Next Unicode
Range(Cells(1, 2), Cells(1, 16384)).Value = 列配列
Range(Cells(2, 1), Cells(65537, 1)).Value = WorksheetFunction.Transpose(行配列)
For 行 = 2 To 65537
For 列 = 2 To 16384
If 行配列(行) <> "" And 列配列(列) <> "" Then
If 行配列(行) = 列配列(列) Then
Cells(行, 列).Value = "○"
End If
End If
Next 列
Application.StatusBar = "比較中..." & 行 & "/" & "65537行"
DoEvents
Next 行
Application.StatusBar = False
End Sub
注:私のPCでは1プロシージャ20分程度かかります。
オブジェクトはメモリの中でどうなってるの?(Tips-14)
注:今回のお話は VBA にそっくりそのまま適用できるかわかりません。一般的なお話として読んでいただければ嬉しいです。
前回、メモリ内部を「オブジェクト」とだけ表現しました。
せっかくなのでメモリ内部をもう少し拡大して観察したいと思います。
メモリ内は主にスタック領域、メソッドエリア(静的領域)、ヒープ領域に分けて管理されます。
クラスをNew する
まず、New するところから始めます。
今回はクラスの実体化という表現はやめて、クラスのインスタンス化という表現にします。(意味は同じです)
New でメモリのヒープ領域に、クラスで定義されたインスタンス変数の容量を確保します。ここで確保された容量をインスタンスと呼びます。
New した数だけ確保(生成)できます。
この、インスタンスをオブジェクトとも呼びます。
場合によっては別のニュアンスで使い分けされることもありますが、本記事の趣旨ではないので割愛します。
(@_@;)「あれ?メソッドとプロパティはどこ行った?」
安心して下さい。
ちゃんとメモリ内のメソッドエリアと呼ばれる場所にロードされます。
コード情報はインスタンスが複数あっても、1クラスにつき1つしかロードされません。同じクラスの各インスタンスから呼び出されて使いまわされるイメージです。
別のクラスBなどから生成したインスタンスがあっても、そこからはクラスAのコード情報は呼び出せません。逆もまた然りです。
変数でインスタンスを参照する
Set 変数 で、インスタンスを代入します。
といってもデータをまるまる代入するのではなく、インスタンスが存在する場所の先頭アドレスを参照します。
ちなみに図の例では変数をクラスA型で宣言しているので、クラスAから生成したインスタンス以外を参照させようとするとエラーになります。
メソッドを呼び出す
変数1から、メソッドを呼び出してみましょう。
今さら言う必要もないかもしれませんが、メソッドを呼び出すときは
- 対象のインスタンスを参照している変数名.メソッド名(引数)
とします。
これによって先頭アドレス:XXXXXXXX に格納されたインスタンスが処理対象であることが特定できます。
あわせて、そのインスタンスに紐付けられたメソッドが呼び出されます。(別のクラスBなどに同じ名前のメソッド名があったとしても呼び出されない)
メソッドが実行され、必要に応じてインスタンスやローカル変数への読み書きも行われます。
各変数について整理しときましょう。
- 同じクラス内に限りどこからでもアクセスできる。
- 参照されている限り存在する。
- 容量の大きいデータを取り扱うことができる。
ローカル変数
- メソッド内からのみアクセスできる。
- メソッドのルーチンが終了したら破棄される。
メソッドの処理によって何らかの結果が返されます。
メインコードからはインスタンスに対して直接読み書きができないので、メソッドやプロパティが窓口となってやり取りが行われます。
処理の結果は数値や文字列だったり、別のクラスのインスタンス化だったり、画面上の表示変化だったり、様々なものがあるだろうと思います。
その辺はメソッドがどのように作られているかによって変わります。
メソッドが終了したら
スタック領域にあった、引数やローカル変数は破棄されます。
インスタンスへの参照は続いているため、アドレス情報は残されます。(と思われる)
プロシージャが終了したら
インスタンスへの参照はなくなり、アドレス情報・インスタンス・コード情報は破棄されます。
最後に
VBAでは特にメインコード以外は Black box に近く、内部を意識しなくてもプログラミングができるようになっています。逆にいうと、それだけ各オブジェクトの機能の抽象度が高く、部品としての独立性が高くなっているんだともいえます。(Workbookオブジェクト、Worksheetオブジェクト、Rangeオブジェクト・・・・・・等々は New すらしなくても使える)
今回はこの Black box 側を強調して描きたかったので、メインコードは標準モジュール内で動いているかのような書き方をしましたが、実際はメインコードもコンパイル(ソースコード→中間コードに一括変換→機械語に順次変換)されて、静的領域にロードされています。
と、ここまで偉そうに書きましたが実際に自分の目で内部動作を見たわけではありません。メモリダンプを解析する技術があって、実際に自分の目で見たのならもっと自信を持って書けるんですが。
以下の本が参考になります。