0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

『CPUの創りかた』をCPLD「MAX V」+ 回路図エディター + 74シリーズで作る

Last updated at Posted at 2020-10-25

概要

『CPUの創りかた』(渡波郁、マイナビ出版)に解説されている4ビットCPU「TD4」をCPLD「MAX V (5M240ZT100C5N)」で作ってみる。IOは3.3 Vとし、クロックジェネレーター(1 Hz, 10 Hz, 手動クロック)およびROMはマイコンATmega328Pで代用する。

全体の構成

image.png

TD4部

TD4の中身はテキストとまったく同じである。開発ツールにはQuartus Prime Lite Editionを使う。HDLではなく回路図エディターで入力した。配線がごちゃごちゃするところはシンボル化してまとめた。MAX Vのブレークアウトボードは https://www.azumino-denshi.com/SHOP/AZMMAXVT0RE240.html を使った。

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

`回路入力例を見る/隠す`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/39b8d55e-509e-758f-3e61-cb0581d01c2b.png)

クロックジェネレーター & ROM部

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

TD4_external.cpp
# include "TD4_external.h"

/*
ニモニック(のようなもの)一覧:
MOV_A(Im) : AレジスタにImを転送
MOV_B(Im) : BレジスタにImを転送
MOV_AB    : AレジスタにBレジスタを転送
MOV_BA    : BレジスタにAレジスタを転送
ADD_A(Im) : AレジスタにImを加算
ADD_B(Im) : BレジスタにImを加算
IN_A      : 入力ポートからAレジスタへ転送
IN_B      : 入力ポートからBレジスタへ転送
OUT(Im)   : 出力ポートへImを転送
OUT_B     : 出力ポートへBレジスタを転送
JMP(Im)   : Im番地へジャンプ
JNC(Im)   : Cフラグが1ではないときにIm番地へジャンプ
*/

int main(void){
	// アセンブル(のようなこと)をしてROM (に見立てた配列)へ格納する。
	_[0] = OUT_B;
	_[1] = ADD_B(1);
	_[2] = JMP(0);

	// アドレスの変化に応じてROM (に見立てた配列)からデータが出力できるようにする。
	// 最初のクロックが立ち上がる前のこの時点で0番地のデータが出力される。
	enable_rom();

	// PD2のスイッチ入力に応じてPD5から手動クロックが出力できるようにする。
	enable_manual_clock();

	// PD7から10Hzを、PD6から1Hzを出力する。
	enable_1Hz_10Hz_clock();

	// PD2のスイッチ入力をディバウンスしてPD5から出力する(押してH出力、放してL出力)。
	while(1) debounce_PD2_to_PD5();

	return 0;
}
`ヘッダーファイルを見る/隠す`
# ifndef TD4_EXTERNAL_H_
# define TD4_EXTERNAL_H_

# define F_CPU 8000000UL

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

# define MOV_A(Im) ( 3 << 4 | ((Im) & 0xF)) // AレジスタにImを転送
# define MOV_B(Im) ( 7 << 4 | ((Im) & 0xF)) // BレジスタにImを転送
# define MOV_AB    ( 1 << 4)                // AレジスタにBレジスタを転送
# define MOV_BA    ( 4 << 4)                // BレジスタにAレジスタを転送
# define ADD_A(Im) ( 0 << 4 | ((Im) & 0xF)) // AレジスタにImを加算
# define ADD_B(Im) ( 5 << 4 | ((Im) & 0xF)) // BレジスタにImを加算
# define IN_A      ( 2 << 4)                // 入力ポートからAレジスタへ転送
# define IN_B      ( 6 << 4)                // 入力ポートからBレジスタへ転送
# define OUT(Im)   (11 << 4 | ((Im) & 0xF)) // 出力ポートへImを転送
# define OUT_B     ( 9 << 4)                // 出力ポートへBレジスタを転送
# define JMP(Im)   (15 << 4 | ((Im) & 0xF)) // Im番地へジャンプ
# define JNC(Im)   (14 << 4 | ((Im) & 0xF)) // Cフラグが1ではないときにIm番地へジャンプ

volatile uint8_t _[16]; // 命令を格納しておく配列。
volatile uint8_t switch_changed = 0;
volatile int8_t counter = 10;

ISR(INT0_vect){         // 手動クロック用のタクトスイッチが押されるか放されるかしたら、
	switch_changed = 1; // そのフラグを立てる
}
ISR(PCINT1_vect){           // アドレスが変化したら、
	PORTB = _[PINC & 0x0F]; // そのアドレスに応じたデータを出力する。
}
ISR(TIMER1_COMPA_vect){
	PORTD ^= (1 << PD7); // 20Hzの頻度でPD7出力をトグルする。結局10Hzの方形波が出力される。
	if(--counter <= 0){  // その方形波を10分周してPD6から出力する。結局1Hzの方形波が出力される。
		PORTD ^= (1 << PD6);
		counter = 10;
	}
}

/*
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();	
}
void debounce_PD2_to_PD5(void){
	if(switch_changed){                              // PD2に接続したスイッチが変化したら、
		switch_changed = 0;
		_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を内部プルアップする。★実際にTD4に接続するときは内部プルアップは不要。

	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_1Hz_10Hz_clock(void){
	DDRD   |= (1 << PD6)|(1 << PD7); // クロックを出力する端子を指定する。
	TCCR1B |= (1 << WGM12);          // タイマー1をCTCモードで動かす。
	TCCR1B |= (1 << CS11);           // 8分周してタイマー1でカウントする。
	OCR1A   = 49999;                 // コンペア値。(F_CPU/8分周)/(10Hz*2)-1 = 49999
	TIMSK1 |= (1 << OCIE1A);         // タイマー1のコンペアマッチ割り込みを有効にする。
	sei();
}

/*テキストの3分15秒ラーメンタイマー(クロックは1Hz)
_[ 0] = OUT(0b0111); // LEDを3つ点灯
_[ 1] = ADD_A(1);
_[ 2] = JNC(1);      // 16回ループ
_[ 3] = ADD_A(1);
_[ 4] = JNC(3);      // 16回ループ
_[ 5] = OUT(0b0110); // LEDを2つ点灯
_[ 6] = ADD_A(1);
_[ 7] = JNC(6);      // 16回ループ
_[ 8] = ADD_A(1);
_[ 9] = JNC(8);      // 16回ループ
_[10] = OUT(0b0000);
_[11] = OUT(0b0100);
_[12] = ADD_A(1);
_[13] = JNC(10);     // LED点滅を16回ループ
_[14] = OUT(0b1000); // LEDを1つ点灯
_[15] = JMP(15);     // ここにとどまる。
*/

/*テキストのLEDちかちか
_[0] = OUT(0b0011);
_[1] = OUT(0b0110);
_[2] = OUT(0b1100);
_[3] = OUT(0b1000);
_[4] = OUT(0b1000);
_[5] = OUT(0b1100);
_[6] = OUT(0b0110);
_[7] = OUT(0b0011);
_[8] = OUT(0b0001);
_[9] = JMP(0);
*/

/* 外部入力をそのまま外部出力する。
_[0] = IN_B;
_[1] = OUT_B;
_[2] = JMP(0);
*/

/* 0~15まで1ずつインクリメントする、を繰り返す。
_[0] = OUT_B;
_[1] = ADD_B(1);
_[2] = JMP(0);
*/

# endif

上のプログラムを実行しているところ

image.png

ファイル一式

(整理中)

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?