トランジスタ技術2020年5月号の4ビットCPU1738をCPLD「MAX V」+ 回路図エディター + 74シリーズで作る

#1. 概要
『トランジスタ技術』2020年5月号に紹介されている4ビットCPU「CPU1738」をCPLD「MAX V \(5M240ZT100C5N\)」で作ってみる。MAX Vのブレークアウトボードは https://www.azumino-denshi.com/SHOP/AZMMAXVT0RE240.html を使った。

テキストは、ディスクリートのFETで構成した数種類の基本ゲートを無数に組み合わせているが、ここでは、『CPUの創りかた』\(マイナビ出版\)と同じく、同期カウンター74161、全加算器74283、Dフリップフロップ7474を利用して楽をする。

クロックジェネレーターおよびROMはマイコンATmega328Pで代用する。

#2. 全体の構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/9b0c40e1-63a2-9e6a-c886-41b79dd6b27c.png)

#3. 構成要素ごとにCPU部を作ってゆく
Quartus Prime Lite Editionを使って回路図エディターで入力する。

参考:
後閑哲也, \(2019\), 「74シリーズで始めるフルディジタル電子工作\[準備編\], 『トランジスタ技術』2019年12月号\(CQ出版\), pp.60ff.

##3.1 Aレジスタを作る
『CPUの創りかた』と同じく同期カウンター74161を使う。テキストに合わせてLOADをHi有意にしただけである。
<details><summary>`接続図を見る/隠す`</summary><div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/dc2c2a79-49d3-044f-0548-8361a1f62733.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/e5b037d6-34e8-b679-f106-2a67cde91c03.png)
</div></details>

##3.2 Bレジスタを作る
Aレジスタの出力をトライステートにし、それをBレジスタとする。

###3.2.1 4連のトライステートバッファーを作る
<details><summary>`接続図を見る/隠す`</summary><div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/2cb10fa0-ff96-edd9-f9bc-ef3bffec662a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/b4df5bc3-4d03-8b69-f677-e6c07b1a96e0.png)
</div></details>

###3.2.2 Aレジスタの出力をトライステートにしてそれをBレジスタとする
<details><summary>`接続図を見る/隠す`</summary><div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/23f9cd76-86ce-6032-e65a-a9a3d319ae46.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/80df0269-a995-0603-ea3d-5efe129d2ee7.png)
</div></details>

##3.3 プログラムカウンターを作る
PCも『CPUの創りかた』と同じく74161を使う。
<details><summary>`接続図を見る/隠す`</summary><div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/cfc1eb70-7840-ee30-7e08-050c0e912f7e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/6cfd13b4-73a6-d43e-bee0-94d787752911.png)
</div></details>

##3.4 INポートを作る
(3.2.1)の4連トライステートバッファーをそのまま使う。

##3.5 OUTポートを作る
(3.1)のAレジスタをそのまま使う。

##3.6 加減算器を作る
『CPUの創りかた』と同じく全加算器74283を使うが、ここではそれを加減算器化し、さらにZeroフラグ、Carryフラグも保持できるようにする。

###3.6.1 マルチプレクサを作る
<details><summary>`接続図を見る/隠す`</summary><div>
AS信号で加算(Add)か減算(Sub)かを選ぶ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/0d8a2704-445f-1a8f-2f46-521e61405f07.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/c4f9bc52-8cd3-ff40-8bce-d7609165f837.png)
</div></details>

###3.6.2 それを4つ並べる
<details><summary>`接続図を見る/隠す`</summary><div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/275bb52d-7698-9203-ead9-2b64b8833717.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/52ff058a-6ab8-83d5-4918-d27ec5e76699.png)
</div></details>

###3.6.3 全部組み合わせて加減算器にする
<details><summary>`接続図を見る/隠す`</summary><div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/76af0150-2833-50b3-93ca-8028ac1c8cb0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/ef228c2d-47b4-6d54-4d70-ee9390375002.png)
</div></details>

##3.7 ステートマシンを作る
<details><summary>`接続図を見る/隠す`</summary><div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/642a18fe-13dd-a899-26ef-4636c7d238f6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/af5d6bf7-9e3d-e3dc-fc73-6d98e13f5bf6.png)
</div></details>

##3.8 命令デコーダーを作る
テキストどおりに基本ゲートだけで構成した。
<details><summary>`接続図を見る/隠す`</summary><div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/af7fdaae-99cb-d4a6-7a18-9aa6a00c0f71.png)
</div></details>

##3.9 全部組み合わせてCPU1738にする
<details><summary>`接続図を見る/隠す`</summary><div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/1da73fc2-108a-5bab-15c9-ea239cd25777.png)
</div></details>

#4. クロックジェネレーター & ROM部
クロックジェネレーターとROMはどちらもATmega328Pでまかなう。Atmel Studio 7でビルドする。

```cpp
#include "CPU1738_external.h"

/*
ニモニック(のようなもの)一覧:
※ジャンプ命令は1クロック、それ以外は2クロック。HALTは状態を保持して停止。
LD_A(Data)    : A ← [Data]
LD_B(Data)    : B ← [Data]
LD_AB         : A ← B
LD_BA         : B ← A

ADD_AB        : A ← A + B
SUB_AB        : A ← A - B
ADD_A(Data)   : A ← A + [Data]
SUB_A(Data)   : A ← A - [Data]

OUT_A         : OUT ← A
OUT_B         : OUT ← B
OUT(Data)     : OUT ← [Data]
IN_A          : A ← IN

JUMP(Address) : PC ← [Address]
JNC(Address)  : PC ← [Address] if NOT Carry
JNZ(Address)  : PC ← [Address] if NOT Zero

HALT          : HALT
*/

int main(void){
	// アセンブル(のようなこと)をしてROM (に見立てた配列)へ格納する。
	// テキストのリスト1のプログラム
	// 1 + 2 + 3 + 4 + 5 = 15を計算する。
	_[0] = LD_A(1);
	_[1] = OUT_A;
	_[2] = ADD_A(2);
	_[3] = OUT_A;
	_[4] = ADD_A(3);
	_[5] = OUT_A;
	_[6] = ADD_A(4);
	_[7] = OUT_A;
	_[8] = ADD_A(5);
	_[9] = OUT_A;
	_[10] = LD_A(0);
	_[11] = LD_A(0);
	_[12] = LD_A(0);
	_[13] = LD_A(0);
	_[14] = LD_A(0);
	_[15] = OUT(0);


	// CPU1738用の制御回路を有効化する。
	// 第1引数: PD7から出力するクロック周波数Hz。範囲は8～32767Hz。
	// 第2引数: それを何分周してPD6から出力するか。範囲は1～65535分周。
	// ※第2引数を指定しない場合はPD6から1Hzが出力される。
	enable_CPU1738_external(10);


	return 0;
}
```

<details><summary>`ヘッダーファイルを見る/隠す`</summary><div>

```cpp
#ifndef CPU1738_EXTERNAL_H_
#define CPU1738_EXTERNAL_H_

#define F_CPU 8000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

// ジャンプ命令は1クロック、それ以外は2クロック。HALTは状態を保持して停止。
#define LD_A(Data)    (0 << 4 | ((Data) & 0xF))     // A ← [Data]
#define LD_B(Data)    (1 << 4 | ((Data) & 0xF))     // B ← [Data]
#define LD_AB         (2 << 4)                      // A ← B
#define LD_BA         (3 << 4)                      // B ← A

#define ADD_AB        (4 << 4)                      // A ← A + B
#define SUB_AB        (5 << 4)                      // A ← A - B
#define ADD_A(Data)   (6 << 4 | ((Data) & 0xF))     // A ← A + [Data]
#define SUB_A(Data)   (7 << 4 | ((Data) & 0xF))     // A ← A - [Data]

#define OUT_A         ( 8 << 4)                     // OUT ← A
#define OUT_B         ( 9 << 4)                     // OUT ← B
#define OUT(Data)     (10 << 4 | ((Data) & 0xF))    // OUT ← [Data]
#define IN_A          (11 << 4)                     // A ← IN

#define JUMP(Address) (12 << 4 | ((Address) & 0xF)) // PC ← [Address]
#define JNC(Address)  (13 << 4 | ((Address) & 0xF)) // PC ← [Address] if NOT Carry
#define JNZ(Address)  (14 << 4 | ((Address) & 0xF)) // PC ← [Address] if NOT Zero

#define HALT          (15 << 4)                     // HALT


volatile bool Switch_Changed = false;
ISR(INT0_vect){            // 手動クロック用のタクトスイッチが押されるか放されるかしたら、
    Switch_Changed = true; // そのフラグを立てる
}

volatile uint8_t _[16];     // 命令を格納しておく配列。
ISR(PCINT1_vect){           // アドレスが変化したら、
    PORTB = _[PINC & 0x0F]; // そのアドレスに応じたデータを出力する。
}

volatile uint16_t Prescale;    // 高速クロックを何分周して低速クロックにするか。
volatile uint16_t Counter = 0;
ISR(TIMER1_COMPA_vect){
	PORTD ^= (1 << PD7);       // 高速クロックの倍の頻度でPD7出力をトグルする。
	if(++Counter >= Prescale){ // その方形波を何分周かして低速クロックとしてPD6から出力する。
		PORTD ^= (1 << PD6);
		Counter = 0;
	}
}


void enable_reset(void){
    DDRD  |=  (1 << PD3); // PD3から!リセット信号を出力することにする。
    PORTD &= ~(1 << PD3); // 最初に!リセット信号をLにしておいて、
    _delay_ms(100 - 65);  // 適当にリセット時間を設けて、(65msはATmega328P自体のリセットからの遅延時間)
    PORTD |= (1 << PD3);  // リセット期間が明けたら!リセット信号をHに戻す。
}
void enable_manual_clock(void){
    DDRD  |=  (1 << PD5);   // PD5から手動クロックを出力することにする。 
    PORTD &= ~(1 << PD5);   // 最初はLを出力しておく。
    DDRD  &= ~(1 << PD2);   // PD2をスイッチ入力にする。
    PORTD |=  (1 << PD2);   // PD2を内部プルアップする。
    EICRA |=  (1 << ISC00); // INT0 (PD2)が変化したときに割り込み要求を出すことにする。
    EIMSK |=  (1 << INT0);  // 外部割り込みINT0を有効にする。
    sei();  
}
inline void debounce_PD2_to_PD5(void){
    if(Switch_Changed){                              // PD2に接続したスイッチが変化したら、
        Switch_Changed = false;
        _delay_ms(5);                                // バウンスの収まるまで待ってから、
        if(PIND & (1 << PD2)){PORTD &= ~(1 << PD5);} // スイッチが放されていたらPD5からLを出力し、
        else                 {PORTD |=  (1 << PD5);} // スイッチが押されていたらPD5からHを出力する。
    }
}
void enable_rom(void){
    DDRC  &= ~0x0F; // PC3:0にアドレスを入力することにする。
    //PORTC |=  0x0F; // PC3:0を内部プルアップする。※実際にCPU1738に接続するときは内部プルアップは不要。

    DDRB  = 0xFF; // PB7:0から命令を出力することにする。
    PORTB = _[0]; // 最初のクロックが立ち上がる前に0番地の命令を出力しておく。

    // PCINT11:8 (PC3:0)からピン変化割り込みをかけることにする。
    PCICR  |= (1 << PCIE1);
    PCMSK1 |= ((1 << PCINT11)|(1 << PCINT10)|(1 << PCINT9)|(1 << PCINT8));
    sei();
}
void enable_Hi_Lo_clock(uint16_t freq_hi){
    DDRD   |= (1 << PD6) | (1 << PD7); // 自動クロックを出力する端子を指定する。
    TCCR1B |= (1 << WGM12);            // タイマー1をCTCモードで動かす。
    TCCR1B |= (1 << CS11);             // 8分周してタイマー1でカウントする。
    OCR1A   = (F_CPU/8)/(freq_hi*2)-1; // コンペア値。(F_CPU/8分周)/(高速クロック周波数*2)-1
    TIMSK1 |= (1 << OCIE1A);           // タイマー1のコンペアマッチ割り込みを有効にする。
    sei();
}


// 全部まとめる
// 第1引数: PD7から出力するクロック周波数Hz。範囲は8～32767Hz。
// 第2引数: それを何分周してPD6から出力するか。範囲は1～65535分周。
// ※第2引数を指定しない場合はPD6から1Hzが出力される。
void enable_CPU1738_external(uint16_t freq_hi, uint16_t prescale){
	Prescale = prescale;
	enable_rom();
	enable_reset();
	enable_manual_clock();
	enable_Hi_Lo_clock(freq_hi);
	while(true) debounce_PD2_to_PD5();
}
void enable_CPU1738_external(uint16_t freq_hi){ // 分周比を指定しない場合
	enable_CPU1738_external(freq_hi, freq_hi);
}


/*動作確認*/

/*テキストのリスト1 加算プログラム(ループなし)
1 + 2 + 3 + 4 + 5 = 15を計算する。
	_[0] = LD_A(1);
	_[1] = OUT_A;
	_[2] = ADD_A(2);
	_[3] = OUT_A;
	_[4] = ADD_A(3);
	_[5] = OUT_A;
	_[6] = ADD_A(4);
	_[7] = OUT_A;
	_[8] = ADD_A(5);
	_[9] = OUT_A;
	_[10] = LD_A(0);
	_[11] = LD_A(0);
	_[12] = LD_A(0);
	_[13] = LD_A(0);
	_[14] = LD_A(0);
	_[15] = OUT(0);

*/

/*
	_[0] = OUT_A;
	_[1] = ADD_A(1); // 1ずつインクリメントして、
	_[2] = JNC(0);
	_[3] = SUB_A(1); // 1ずつデクリメントして、
	_[4] = OUT_A;
	_[5] = JNZ(3);
	_[6] = JUMP(0);  // を繰り返す。
*/

#endif
```
</div></details>

#5. 上のプログラムを実行しているところ
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/24bdedad-aa3d-9e83-50a4-38585c3ec067.png)](https://youtu.be/r_5EyGPTd0Q)

#6. ファイル一式
(整理中)