「CPU의 창작」을 CPLD 「MAX V」+ 회로도 에디터 + 74 시리즈로 만든다

15036 단어 전자 공작

개요



「CPU의 창조 방법」(와타나베 이루, 마이 네비게이션 출판)에 해설되고 있는 4비트 CPU 「TD4」를 CPLD 「MAX V (5M240ZT100C5N)」로 만들어 본다. IO는 3.3V로 하고, 클록 제너레이터(1Hz, 10Hz, 수동 클록) 및 ROM은 마이크로 컴퓨터 ATmega328P로 대용한다.

전체 구성





TD4부



TD4 내용은 원본과 완전히 동일하다. 개발 툴에는 Quartus Prime Lite Edition을 사용한다. HDL이 아닌 회로도 편집기로 입력했다. 배선이 엉망이 되는 곳은 심볼화해 정리했다. MAX V의 브레이크 아웃 보드는 htps //w w. 아즈미노 덴시. 코 m/쇼 P/아 Z마마 XVT0레240. HTML을 사용했다.

참고:
후한 테츠야, (2019), "74 시리즈로 시작하는 풀 디지털 전자 공작 [준비편], "트랜지스터 기술"2019 년 12 월호 (CQ 출판), pp.60ff.
回路入力例を見る/隠す


클록 제너레이터 & 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


위 프로그램을 실행하는 곳





파일 세트



(정리 중)

좋은 웹페이지 즐겨찾기