computer system foundationCSF 2026
言語入門 / 02.2

アセンブリ
言語入門

機械語と高級言語をつなぐ命令表現。CPU シミュレーターで使う命令形式と、レジスタ・メモリ・制御フローの関係を押さえる。

所要:20–30分 難易度:★☆☆ 前提:なし
01

アセンブリ言語とは何か

CPU が分かる言語を、人間が読める形で書いたものである。プログラミング言語には抽象度の階段があり、アセンブリはその一番下の近くにいる。

層をクリック → 右に詳細

アプリ
高水準言語
低水準言語
アセンブリ言語
機械語(0と1)

上ほど抽象度が高く人間に近い / 下ほど機械に近い

Python のプログラム
a = 12
b = 7
result = a + b
print(result)   # → 19 と表示

人間が読み書きしやすい。でも CPU はそのまま実行できない。

同じ処理のアセンブリ
LOAD  A, 12   ; A = 12
LOAD  B, 7    ; B = 7
ADD   A, B    ; A = A + B → 19
OUT   A       ; 19 と表示
HALT           ; 終了

CPU への指示書き。1行 = 1命令 = CPU が1回やることである。

アセンブリは CPU の動作を直接コントロールする。 「どのレジスタに何を入れるか」「どのメモリアドレスを使うか」を自分で指定するので、 CPU の中で何が起きているかが手に取るように分かる。
02

レジスタ ― CPU の「手元メモ帳」

計算のたびにメモリへ読み書きしていては遅すぎる。だから CPU の中に、超高速で小さな置き場を持っている。

このシミュレーターのレジスタ
A
汎用
B
汎用
C
汎用
D
汎用
PC
プログラムカウンタ
フラグ
Zゼロフラグ
N負フラグ
Cキャリーフラグ
汎用レジスタ A・B・C・D

どんな計算にも使える「変数箱」。8ビット(0〜255 の整数)を入れられる。
例:LOAD A, 42 → A の箱に 42 を入れる。

PC(プログラムカウンタ)

次に実行する命令の番号を常に指している特殊レジスタ。通常は 0→1→2→… と自動的に増えるが、JMP 命令で強制的に変えられる。

フラグ(Z / N / C)

直前の演算結果の「性質」を記録する1ビットのスイッチ。CMP の後で JEQ / JGT が参照する。

フラグ1 になる条件
Z結果が 0(=同じ値を比較したとき)
N結果が負(Rx < Ry のとき)
C加算で桁あふれ(255 を超えた)
03

メモリ ― 「引き出しだな」

レジスタより遅いけれど、たくさん置ける。このシミュレーターでは 16 マスある。

RAM(16 バイト)
0x00
?
0x01
?
0x02
?
0x03
?
0x04
?
0x05
?
0x06
?
0x07
?
0x08
?
0x09
?
0x0A
?
0x0B
?
0x0C
?
0x0D
?
0x0E
?
0x0F
?

STORE A, 0x00 → アドレス 0x00 に A の値を保存

アドレスとは

メモリの各マスには住所番号(アドレス)がついている。0x000x0F は16進数の表記で、10進数の 0〜15 のことである。

STORE と LOAD の使い分け
LOAD  A, 42       ; 即値 → レジスタ
STORE A, 0x00   ; レジスタ → メモリ保存
LOAD  B, [0x00]  ; メモリ → レジスタ読出し

このシミュレーターでは LOAD B, [0x00] と角括弧で書くとメモリから読み込む。

レジスタとメモリの違い: レジスタは CPU のにある超高速な一時置き場(4個だけ)。メモリはもう少し遅いけれど 16 マスある。 計算はレジスタで行い、保存したいときだけメモリを使うのが基本パターンである。
04

メモリ階層構造

レジスタとメモリの間には、実は何段階もの中間層がある。各層をクリックすると速度・容量・用途が開く。 (詳しくは第3章の 03.3 主記憶 で扱う。)

~1 cy
レジスタ(CPU 内)
数十バイト
CPU チップの中に物理的に存在する最速の記憶域。 このシミュレーターの A・B・C・D・PC がまさにレジスタである。 容量は極めて小さいが、演算は1クロックサイクル以内で完了する。
~4 cy
L1 キャッシュ
32–64 KB / コア
CPU コアごとに持つ最小・最速のキャッシュ。「よく使う命令・データ」をここに置いてメモリアクセスを減らする。 命令用(L1-I)とデータ用(L1-D)に分かれていることが多い。
~12 cy
L2 キャッシュ
256 KB–2 MB / コア
L1 より大きく、L1 に無いデータを L2 から探す(キャッシュミス時)。 多くの CPU では L1・L2 はコアごと専用、L3 は複数コアで共有される。
~40 cy
L3 キャッシュ(LLC)
4–64 MB(共有)
Last Level Cache。全コアで共有する大容量キャッシュ。 L3 をミスするとメインメモリへのアクセスが発生し、大きなレイテンシが生じる。 Apple M4 は最大 72 MB の System Level Cache を搭載している。
~100 ns
メインメモリ(RAM)
4–128 GB
03 節で扱った「引き出しだな」がここである。実行中のデータ・スタック・ヒープはすべてここに乗る。 電源を切ると揮発する(データが消える)ため、SSD への保存が必要である。
~100 µs
SSD / NVMe フラッシュ
256 GB–8 TB
不揮発性ストレージ。電源を切ってもデータが残る。OS・アプリ・ファイルはここに保存され、起動時に RAM へ読み込まれる。 NVMe(PCIe 接続)は SATA SSD より約5〜10倍高速である。
~8 ms
HDD(磁気ディスク)
1–20 TB
回転するディスクに磁気で記録する。レイテンシは SSD の100倍以上。 大容量・低コストなので、バックアップやアーカイブ用途では今も現役である。機械的な可動部があるので衝撃に弱い。
速度と容量のトレードオフ
速度容量単価
レジスタ~1 cy~512 B最高
L1~4 cy32–64 KB非常に高
L2~12 cy256 KB–2 MB
L3~40 cy4–64 MB
RAM~100 ns4–128 GB
SSD~100 µs256 GB–8 TB非常に低
HDD~8 ms1–20 TB最低
CPU はまず L1 → L2 → L3 → RAM の順に探す。見つからないことをキャッシュミスと呼び、 下の層へ降りるたびに大幅に遅くなる。プログラムの局所性(近いアドレスを繰り返し使う性質)が、キャッシュ効率に直結する。
このページとの関係

02 節のレジスタ(A/B/C/D)が最上位の超高速層、03 節のRAM(0x00〜0x0F)が STORE / LOAD でアクセスするメインメモリ層である。キャッシュは CPU が自動的に管理するので、アセンブリから直接は操作しない。

05

命令セット(ISA)全解説

このシミュレーターの命令は12個だけである。すべて例つきで見ていく。

データ転送命令

LOAD Rx, 値

レジスタ Rx に即値(数字)を入れる。

LOAD A, 10       ; A ← 10
LOAD C, 255      ; C ← 255(最大値)
LOAD Rx, [addr]

メモリのアドレス addr から値を読み込む

STORE A, 0x00   ; まず保存
LOAD  B, [0x00]  ; B ← mem[0x00]
STORE Rx, addr

Rx の値をメモリアドレス addr に保存する。

LOAD  A, 42
STORE A, 0x03   ; mem[3] = 42
MOV Rx, Ry

Ry の値を Rx にコピーする。Ry は変わらない。

LOAD A, 99
MOV  B, A       ; B = 99(A も 99 のまま)

算術命令

ADD Rx, Ry

Rx = Rx + Ry

LOAD A, 3
LOAD B, 5
ADD  A, B   ; A = 8
SUB Rx, Ry

Rx = Rx − Ry

LOAD A, 10
LOAD B, 3
SUB  A, B   ; A = 7
MUL Rx, Ry

Rx = Rx × Ry(8ビットの下位)

LOAD A, 6
LOAD B, 7
MUL  A, B   ; A = 42
注意:結果は必ず最初のレジスタ(Rx)に書き込まれる。 ADD A, B の後、A の値は変わるが B は変わらない。

比較・分岐命令

「もし〜なら」を実現するために使う。CMP で比較 → JEQ / JGT で分岐という流れが基本である。

CMP Rx, Ry

Rx と Ry を比較してフラグを更新する。値は変わらない。

Rx = RyZ = 1
Rx < RyN = 1
Rx > RyZ = 0, N = 0
JMP label

無条件で label の行へジャンプする(ループに使う)。

loop:
  ADD  A, B
  JMP  loop   ; 永遠にループ
JEQ label

Z = 1(直前の比較が「等しい」)なら label へ。

CMP  A, B
JEQ  equal   ; A == B ならジャンプ
JGT label

N = 0 かつ Z = 0(直前の比較が「より大きい」)なら label へ。

CMP  A, B
JGT  bigger  ; A > B ならジャンプ

出力・終了命令

OUT Rx

Rx の値を出力欄に表示する。Python の print() に相当。

LOAD A, 42
OUT  A       ; 「42」と出力
HALT

プログラム終了。必ず最後に書くこと。

OUT  A
HALT           ; ← これがないと暴走する
06

サンプルプログラムを1行ずつ読む

シミュレーターに用意されている3つの例を、実際に追いかけてみましょう。

例1 足し算(12 + 7)
LOAD  A, 12   ; 1  A = 12
LOAD  B, 7    ; 2  B = 7
ADD   A, B    ; 3  A = A+B = 19
OUT   A       ; 4  「19」出力
HALT           ; 5  終了
1レジスタ A に 12 を入れる。「変数 a = 12」と同じ意味。
2レジスタ B に 7 を入れる。
3ADD A, B → A = 12 + 7 = 19。B は 7 のまま変わらない。
4OUT で A の値(19)を画面に表示。
5HALT でプログラム終了。CPU が止まる。
例2 ループ(1〜5 の合計 = 15)
LOAD  A, 0     ; 1  sum = 0
LOAD  B, 1     ; 2  i = 1
LOAD  C, 5     ; 3  limit = 5
loop:
  ADD   A, B    ; 4  sum += i
  LOAD  D, 1   ; 5  D = 1
  ADD   B, D   ; 6  i += 1
  CMP   B, C   ; 7  i > 5?
  JGT   done   ; 8  なら終了へ
  JMP   loop   ; 9  繰り返す
done:
  OUT   A       ; 10 「15」出力
  HALT
1–3A=合計、B=カウンター、C=上限(5)を初期化。Python の sum=0; i=1; limit=5 に相当。
4sum += i — A に B を足す。
5–6B を1増やす(インクリメント)。D=1 を一時レジスタとして使う。
7–8CMP B, C で B と 5 を比較。B > 5 なら JGT done で出口へ。
9まだ B ≤ 5 なので JMP loop で先頭に戻る。
この形(初期化 → ループ本体 → インクリメント → 条件チェック → JMP)が、 for ループの基本形である。
例3 条件分岐(max(15, 20) = 20)
LOAD  A, 15    ; 1  A = 15
LOAD  B, 20    ; 2  B = 20
CMP   A, B     ; 3  A > B?
JGT   a_win   ; 4  A>B なら a_win へ
OUT   B        ; 5  B が大きい → 出力
HALT
a_win:
  OUT  A        ; A が大きい → 出力
  HALT
1–2比較する2つの値をロードする。
3CMP A, B → 15 − 20 = −5 → N フラグが立つ(負)、Z は 0。
4JGT a_win — N=1 なのでジャンプしない(A は B より大きくない)。
5ジャンプしなかったので次の行 OUT B を実行 → 20 を出力。
if-else の基本形: CMP → ジャンプ命令 → else ブロック → HALT → label: → if ブロック → HALT
07

よく使うパターン集

コピーして改造すればすぐ使えるテンプレートである。

カウンターループ

for i in range(1, N+1) に相当

LOAD  B, 1      ; i = 1(開始値)
LOAD  C, N      ; 上限 N に変更
LOAD  D, 1      ; インクリメント用
loop:
  ; ← ここにループ処理
  ADD   B, D   ; i++
  CMP   B, C
  JGT   done
  JMP   loop
done:
  HALT
if A > B / else

大きいほうを使う分岐

CMP   A, B
JGT   a_bigger  ; A>B ならジャンプ
  ; B が大きい場合
  OUT  B
  HALT
a_bigger:
  ; A が大きい場合
  OUT  A
  HALT
値をメモリに退避・復元

レジスタが足りないときのテクニック

LOAD  A, 42
STORE A, 0x00    ; A を保存

; A は別の計算に使う
LOAD  A, 100

; 後で復元
LOAD  B, [0x00]  ; B ← 42(復元)
if A == B

等しいかどうかの分岐(JEQ を使う)

LOAD  A, 7
LOAD  B, 7
CMP   A, B
JEQ   equal     ; A == B
  ; 等しくない場合
  HALT
equal:
  ; 等しい場合
  OUT  A
  HALT
08

よくあるミスと対処法

HALT を書き忘れる

最後に HALT がないと、命令リストの外を読み続けて予期しない動作になる。必ず最後の行に HALT を入れましょう。

ADD の結果の向きを間違える

ADD A, B は「A を更新する」命令である。B には何も書き込まれない。「B に A を足した結果を入れたい」なら ADD B, A と書く。

ラベル名のスペルミス

loop: と定義して JMP lop と書くと「ラベルが見つからない」エラーになる。定義と参照のスペルを一致させましょう。

CMP なしで JEQ / JGT を使う

フラグは直前の演算の結果である。JEQ / JGT の直前には必ず CMP を置きましょう。

コメントを活用する

; 以降はコメントとして無視される。各行に「何をしているか」を書いておくと、後で見直したときに理解しやすくなる。

1ステップ実行で確認する

シミュレーターの「1ステップ」ボタンを使えば、1命令ずつ実行してレジスタの変化を確認できる。バグを見つけるのに最適である。

準備できたら、シミュレーターへ

実際に命令を入力して、CPU がどう動くか体感してみましょう。
1命令が Fetch → Decode → Execute → Memory → Write-back と進む実行サイクルが、1段ずつ光って見える。
メモリに触らない命令が Memory 段を素通りするところも観察できる。

CPU シミュレーターを開く →