CalicoCPU でガッテンRの再現

[自作CPU「CalicoCPU」](https://elchika.com/article/f38275fc-1f46-4e77-bc30-c172971ecf0f/)で「ガッテンR」の再現をしてみた。

# ガッテンR

「ガッテンR」とは、プロジェクト59 タケ様がかつて頒布していたキットである。

[『ガッテンR』 ⑥　キット頒布について - プロジェクト59　：　キット頒布、電子工作　『ユルハム派』](http://project59.blog.fc2.com/blog-entry-619.html)

以下の仕様となっているようである。

* ボタンを押して離すと、モールス信号の「R」の音が鳴り、LEDが光る。
* 光るLEDの色は、ボタンを押すたびに緑と赤が交互に切り替わる。
* スイッチを切り替えると、「R」のかわりに「FB」の音を鳴らすモードにできる。

# 今回の仕様

* 5kHzのクロックで実行する。
* 以下のポートに信号を出力する。(ビットは0-origin)
  * PORT0のビット1にモールス信号の音声を出力する。
  * PORT0のビット2に緑のLEDの信号を出力する。(HIGH：点灯、LOW：消灯)
  * PORT0のビット3に赤のLEDの信号を出力する。(HIGH：点灯、LOW：消灯)
* 以下のポートから信号の入力を受け付ける。(ビットは0-origin)
  * PORT1のビット2にボタンを接続する。(HIGH：押された、LOW：押されていない)
  * PORT1のビット3に出力内容の切り替えスイッチを接続する。(HIGH：「FB」、LOW：「R」)

なお、本家「ガッテンR」では音声の出力中にボタンを押すとまた最初から音声が出力されるが、今回は音声の出力中のボタン入力は無視する。

# プログラム

以下のプログラムは、[MikeAssembler](https://mikecat.github.io/mike_assembler/) でアセンブルできる。

```
target calico

; PORT0
;   1 : sound
;   2 : green LED
;   3 : red LED

; PORT1
;   2 : trigger button
;   3 : select switch

; A : work / length of sound
; B : work
; C : status
; D : return address

	MOVI A, 0xE
	DRIVE 0, A
	MOVI C, 8
	MOVI3 D, main_loop
	JNZ D, D

	; A = 0 : 25 cycles, A = 1 : 75 cycles
make_sound:
	; 14 instructions (clocks) before make_sound_loop
	NOT A
	ADDI A, 1
	MOVI3 B, 50
	AND B, A
	MOVI3 A, 25
	ADD A, B
	MOVI3 B, make_sound_loop
make_sound_loop:
	ADDI C, 2
	OUT 0, C
	NOP
	NOP
	NOP
	ADDI C, -2
	OUT 0, C
	NOP
	ADDI A, -1
	JNZ A, B
	; 6 instructions (clocks) before make_sound_wait_loop
	MOVI3 A, 50 - 5
	MOVI3 B, make_sound_wait_loop
make_sound_wait_loop:
	NOP
	NOP
	NOP
	ADDI A, -1
	JNZ A, B
	NOP
	JNZ D, D

main_loop:
	; turn off LED
	MOVI A, 0
	OUT 0, A
	; toggle which LED to turn on
	MOVI B, 0xC
	XOR C, B, A
	; wait for the button to be pressed
	MOVI B, 4
	MOVI3 D, wait_button_press
wait_button_press:
	IN A, 1
	NAND A, B
	ADDI A, 5
	JNZ A, D
	; wait for the button to be released
	MOVI D, wait_button_release
wait_button_release:
	IN A, 1
	MOVI B, 4
	AND B, A
	JNZ B, D
	; check the status of the switch
	MOVI D, play_fb
	MOVI B, 8
	AND B, A
	; if switch input is HIGH, go to play_fb
	; otherwise, go to play_r (fall through)
	JNZ B, D

play_r:
	MOVI A, 0
	MOVI B, make_sound
	JAL D, B
	MOVI A, 1
	MOVI B, make_sound
	JAL D, B
	MOVI A, 0
	MOVI B, make_sound
	JAL D, B

	MOVI D, main_loop
	JNZ D, D

play_fb:
	MOVI A, 0
	MOVI B, make_sound
	JAL D, B
	MOVI A, 0
	MOVI B, make_sound
	JAL D, B
	MOVI A, 1
	MOVI B, make_sound
	JAL D, B
	MOVI A, 0
	MOVI B, make_sound
	JAL D, B

	; delay 50 cycles (500 clocks)
	MOVI3 A, 250 - 3
	MOVI3 B, play_fb_wait
play_fb_wait:
	ADDI A, -1
	JNZ A, B

	MOVI A, 1
	MOVI B, make_sound
	JAL D, B
	MOVI A, 0
	MOVI B, make_sound
	JAL D, B
	MOVI A, 0
	MOVI B, make_sound
	JAL D, B
	MOVI A, 0
	MOVI B, make_sound
	JAL D, B

	MOVI D, main_loop
	JNZ D, D
```

以下は、このプログラムのアセンブル結果であるHEXデータである。

```
:100000002E16A8EBC300DF02316347724246220777
:1000100039056E7700B292000000BE92003F1723B0
:10002000073D6247750000003F1700DF20126C0893
:100030000682068264EEC3001806351FE0FCCF1866
:100040006442465FE5C7F76842465F2067D521678F
:10005000D52067D5EBC3DF2067D52067D52167D5CD
:100060002067D52802006747793F172167D52067A9
:0A007000D52067D52067D5EBC3DF6C
:00000001FF
```

# 解説

## 初期化 (make_sound 前)

出力ポートの設定をし、Cレジスタに光らせるLEDの情報を置く。

## make_sound

Aレジスタの値(0または1)に応じた長さの音声を出力し、その後待ち時間を入れる。
音声は、1周期を10クロックとし、Aレジスタが0のときは25周期、1のときは75周期出力する。
すなわち、モールス信号の端点の時間を250クロックとする。
待ち時間は、その他の処理も含めて端点1個分 (250クロック) になるように調整した。

## main_loop

音声の出力処理が終わると、処理がここに移る。
そこで、LEDを消灯させ、次に点灯させるLED (Cレジスタ) をXORを用いて切り替える。

次に、ボタンが押される (ボタンに接続されたビットが1になる) のを待つ。
ボタンに接続されたビットは、ビット2である。
マスクとのANDを取ったあと、ボタンが押されているとき `0000 0100`、ボタンが押されていないとき`0000 0000` となる。
ボタンが押されているときの値をビット反転する (実際はNAND命令1個でここまでを行う) と、`1111 1011` となる。
これに5を足すと和は0になるので、JNZ命令によるジャンプが行われず、次の処理に移る。
ボタンが押されていないときの値をビット反転すると `1111 1111` となり、これに5を足しても和は0にならず、JNZ命令によるジャンプが行われ、待機を継続する。

その次に、ボタンが離される (ボタンに接続されたビットが0になる) のを待つ。
これは、単純にJNZ命令を用い、マスクした結果が0になるまで待つ。

ボタンが押されて離されたら、出力内容を切り替える信号に応じた出力処理に移る。

## play_r / play_fb

プログラムとして埋め込んだ出力パターンを用い、それぞれ「R」および「FB」のモールス信号を出力する。
このとき、Cレジスタの内容を出力するので、LEDも点灯させる。
「FB」の出力においては、途中に文字の区切りがあるので、
make_sound による1短点分の待機に加え、さらに2短点分の待機を入れ、合計で3短点分の待機をする。

出力処理が完了したら、main_loop に移る。

# 実行結果

PORT0に [2-way Buzzer Board](https://elchika.com/article/c55891ed-5a5f-4355-bf36-8a9f5ca6cd37/#h_2-way%20Buzzer%20Board) および [Super Light RGB LED Module](https://www.elecrow.com/super-light-rgb-led-module-p-652.html) を以下のように接続した。(0-origin)

* ビット1 : 2-way Buzzer Board の manual
* ビット2 : Super Light RGB LED Module の G
* ビット3 : Super Light RGB LED Module の R

また、PORT1の下位ビットに [Pmod ENC](https://digilent.com/shop/pmod-enc-rotary-encoder/) を接続した。

<iframe width="560" height="315" src="https://www.youtube.com/embed/iWaUoBOKq0c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>