Posts Tagged with "Design"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.

FM-7 ROM吸出し器の改版 (8)

posted by sakurai on May 26, 2020 #263

タイマーの設定

今回はTimer2を用いて割り込みをかけるので、Timer2の設定を行います。表261.1にある組み合わせであればどれでも良いのですが、ここでは62.5 usecの割込みを入れ、割込みルーチン内で4回の*REFCKを発行するものとします。

まず、図263.1のようにモジュール設定でIntterrupt moduleを選択し(①)、右の画面でTMR2をイネーブル(②)とします。

図%%.1
図263.1 Interrupt module

最後にタイマーの設定を行います。モジュール設定でTMR2を選択(①)します。周期に62.5 usと記入(②)します。プリスケーラ、ポストスケーラとも1:1(③)とします。割込み何回に一回コールバックするかを設定(④)します。

図%%.2
図263.2 TMR2 module

コード生成

これで設定が完了したので、Generateでコード生成を行います。様々なCソースやヘッダファイルが生成されます。例えば、device.cが生成され、その内容は、

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection Bits->INTOSC oscillator: I/O function on CLKIN pin
#pragma config WDTE = OFF    // Watchdog Timer Enable->WDT disabled
#pragma config PWRTE = OFF    // Power-up Timer Enable->PWRT disabled
#pragma config MCLRE = ON    // MCLR Pin Function Select->MCLR/VPP pin function is MCLR
#pragma config CP = OFF    // Flash Program Memory Code Protection->Program memory code protection is disabled
#pragma config BOREN = ON    // Brown-out Reset Enable->Brown-out Reset enabled
#pragma config CLKOUTEN = OFF    // Clock Out Enable->CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin
// CONFIG2
#pragma config WRT = OFF    // Flash Memory Self-Write Protection->Write protection off
#pragma config STVREN = ON    // Stack Overflow/Underflow Reset Enable->Stack Overflow or Underflow will cause a Reset
#pragma config BORV = LO    // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (Vbor), low trip point selected.
#pragma config LPBOR = OFF    // Low-Power Brown Out Reset->Low-Power BOR is disabled
#pragma config LVP = ON    // Low-Voltage Programming Enable->Low-voltage programming enabled

となっています。次にinterrupt_manager.cの内容は、

#include "interrupt_manager.h"
#include "mcc.h"
void __interrupt() INTERRUPT_InterruptManager (void)
{
    // interrupt handler
    if(INTCONbits.PEIE == 1)
    {
        if(PIE1bits.TMR2IE == 1 && PIR1bits.TMR2IF == 1)
        {
            TMR2_ISR();
        } 
        else
        {
            //Unhandled Interrupt
        }
    }      
    else
    {
        //Unhandled Interrupt
    }
}

のように、TMR2_ISR()を呼び出しています。mcc.cは各種初期化関数の集合であり、

#include "mcc.h"
void SYSTEM_Initialize(void)
{
    PIN_MANAGER_Initialize();
    OSCILLATOR_Initialize();
    WDT_Initialize();
    TMR2_Initialize();
}
void OSCILLATOR_Initialize(void)
{
    // SCS FOSC; IRCF 16MHz_HF; 
    OSCCON = 0x78;
    // SBOREN disabled; BORFS disabled; 
    BORCON = 0x00;
}
void WDT_Initialize(void)
{
    // WDTPS 1:65536; SWDTEN OFF; 
    WDTCON = 0x16;
}

となっています。pin_manager.cは、

#include "pin_manager.h"
void PIN_MANAGER_Initialize(void)
{
    /**
    LATx registers
    */
    LATA = 0x00;
    /**
    TRISx registers
    */
    TRISA = 0x32;
    /**
    ANSELx registers
    */
    ANSELA = 0x00;
    /**
    WPUx registers
    */
    WPUA = 0x00;
    OPTION_REGbits.nWPUEN = 1;
    /**
    APFCONx registers
    */
    APFCON = 0x00;
}

のように、ピン(ポート)の設定です。最後にtmr2.cはtimer2の各種関数の集合であり、左下のNavigatorウインドウに生成された関数一覧が確認できます。ここでは、

  • TMR2_CallBack()
  • TMR2_DefaultInterruptHandler()
  • TMR2_ISR()
  • TMR2_Initialize()
  • TMR2_InterruptHandler
  • TMR2_LoadPeriodRegister(uinit8_t periodVal)
  • TMR2_ReadTimer()
  • TMR2_SetInterruptHandler(void(*InterruptHandler)()
  • TMR2_StartTimer()
  • TMR2_StopTimer()
  • TMR2_WriteTimer(uint8_t timerVal)

が生成されました。interrupt_managerからTMR2_ISR()がコールされ、そこからTMR2_CallBack()がコールされ、そこではTMR_InterruptHandler()がコールされます。

ISR

以下のコメント部が示すように、TMR2_DefaultInterruptHandlerに割込み処理を書くようです。以下に内容を示します。

void TMR2_DefaultInterruptHandler(void){
    // add your TMR2 interrupt custom code
    // or set custom function using TMR2_SetInterruptHandler()
}

前稿での割込み処理内容は

もしZ80W=Lなら

  1. Rfreq=H、Xrefck=Hを出力 //リフレッシュ要求
  2. Rfgnt=Hを待つ
  3. Rfreq=H、Xrefck=Lを出力
  4. Rfreq=H、Xrefck=Hを出力 (3, 4を4回繰り返す)
  5. Rfreq=L、Xrefck=Hを出力
  6. リターン

であったので、そのままプログラムします。以下のピン名から始まる各種の関数はマクロとして、生成されたpin_manager.hで定義されています。

void TMR2_DefaultInterruptHandler(void){
    if (Z80W_GetValue() == 0) {
        XREFCK_SetHigh();
        RFREQ_SetHigh();
        while (RFGNT_GetValue() == 0);
        XREFCK_SetLow();  XREFCK_SetHigh();
        XREFCK_SetLow();  XREFCK_SetHigh();
        XREFCK_SetLow();  XREFCK_SetHigh();
        XREFCK_SetLow();  XREFCK_SetHigh();
        RFREQ_SetLow();
    }
}

TMR2_Initialize()の内容は、

void TMR2_Initialize(void)
{
    // Set TMR2 to the options selected in the User Interface
    // PR2 249; 
    PR2 = 0xF9;
    // TMR2 0; 
    TMR2 = 0x00;
    // Clearing IF flag before enabling the interrupt.
    PIR1bits.TMR2IF = 0;
    // Enabling TMR2 interrupt.
    PIE1bits.TMR2IE = 1;
    // Set Default Interrupt Handler
    TMR2_SetInterruptHandler(TMR2_DefaultInterruptHandler);
    // T2CKPS 1:1; T2OUTPS 1:1; TMR2ON on; 
    T2CON = 0x04;
}

のようになっています。また、main()は、

#include "mcc_generated_files/mcc.h"
/*
                         Main application
 */
void main(void)
{
    // initialize the device
    SYSTEM_Initialize();
    // When using interrupts, you need to set the Global and Peripheral Interrupt Enable bits
    // Use the following macros to:
    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();
    // Enable the Peripheral Interrupts
    INTERRUPT_PeripheralInterruptEnable();
    while (1)
    {
        // Add your application code
    }
}

のように、初期設定がされているため、以下のようにタイマーの開始を追加します。while ループ内の処理はありません。

    TMR2_StartTimer();

左矢前のブログ 次のブログ右矢

FM-7 ROM吸出し器の改版 (7)

posted by sakurai on May 25, 2020 #262

改版基板

JLCPCBにオーダーしていた基板が届きました。今回は製造に2.95日、配送が3.11日のほぼ6日で届きました。また、費用は基板が2 USD、配送料込で17.95 USDと格安でした。

図%%.1
図262.1 FM-7 Intruder V5のボード

PICの割込み処理

開発環境のプラグインであるMCC(MPLAB Code Configurator)をインストールし、MCCで設定していきます。いきなりソースコードを書くよりも分かりやすいためです。インストールや設定の仕方の参考となるサイトには例えばここがあります。

左側のProject ResourcesのSystem Module(①)を選択し、クロック周波数(②)やWDT(③)等を設定します。

  • Oscillator Select: INTOSC oscillator: I/O function on CLKIN pin
  • System Clock Select: FOSC
  • Internal Clock: 16MHz_HF
  • WDT: disabled

図%%.2
図262.2 System設定

次にポートの設定です。回路図では図262.3のようにポートをアサインしたので、これに基づいてプログラム上のポート設定を行います。

図%%.3
図262.3 PIC周りの回路図
ポート(ピン)の一覧です。
  • RA0: RFREQ (Output)
  • RA2: XREFCK (Output)
  • RA4: RFGNT (Input)
  • RA5: Z80W (Input)

図262.4のPin Module(①)を選択し、Grid View(②)にて、GPIOの方向を指定します。さらに、右上のスプレッドシートの表に信号名(③)を書きこみます。

図%%.4
図262.4 Pin設定

PIC12F1501の参考資料(魚拓)があったので、貼り付けておきます。ただし、MCCに全てを任せてしまったので、見ることはありませんでした。


左矢前のブログ 次のブログ右矢

FM-7 ROM吸出し器の改版 (6)

posted by sakurai on May 20, 2020 #261

Arduino自身によるリフレッシュ

DRAMリフレッシュ手法を再考しました。PICでの割込みもArduinoでの割込みも、性能は別として論理的には同じように動作するはずです。そこで、PICで実装する前に、試行としてArduinoに割込みを入れてリフレッシュする方法を検討します。DRAMのリフレッシュタイミング制約によれば16.5 usecに1回リフレッシュパルスを入れる必要があります。Arduino 日本語リファレンスを探したのですが、マイクロ秒で割込みを入れられる関数は無いようでした。このMsTimer2だと、1 msecで割込みを入れて、その代わり、一回の割込みで64回のリフレッシュパルスを発行することになります。

その後、FlexTimer2という、より自由度の高いタイマーが見つかったので、こちらを試します。DRAMのリフレッシュタイミング制約から以下の表261.1を作成しました。これに従い、実験によりタイマを決定します。

表261.1 FlexTimer2の解像度とリフレッシュ回数の対応
解像度 1割込みのリフレッシュ回数 周期
64,000 1 15.6 usec
32,000 2 31.3 usec
16,000 4 62.5 usec
8,000 8 125 usec
4,000 16 250 usec
2,000 32 500 usec
1,000 64 1 msec
500 128 2 msec

既存の基板のポートに対して、D44からZ80コネクタB18(*REFCK)に1本ジャンパー線を配線します。

#define XREFCK 44

割込みをイニシャライズルーチンsetup()で設定します。ここでは解像度を8,000として、割込みを1/8,000 sec=125 usec毎にかけ、refresh関数を呼び出します。

FlexiTimer2::set(1, 1.0/8000, refresh); // call every 125usec "ticks"

また、リフレッシュルーチンは以下のとおりです。

  void refresh() {
    digitalWrite(XREFCK, LOW); 
    digitalWrite(XREFCK, HIGH); 
    digitalWrite(XREFCK, LOW); 
    digitalWrite(XREFCK, HIGH); 
    digitalWrite(XREFCK, LOW); 
    digitalWrite(XREFCK, HIGH); 
    digitalWrite(XREFCK, LOW); 
    digitalWrite(XREFCK, HIGH); 
    digitalWrite(XREFCK, LOW); 
    digitalWrite(XREFCK, HIGH); 
    digitalWrite(XREFCK, LOW); 
    digitalWrite(XREFCK, HIGH); 
  }

このスケッチを実行した波形を図261.1に示します。

図%%.1
図261.1 *REFCKの波形
本来であれば表261.1より、125 usec毎に*REFCKは8回必要ですが、Arduinoの速度が遅いため6回しか発行できていません。8回発行すると、割込み処理だけで能力の限界となります。割込み周期を倍にすると、必要な*REFCK数が倍増するので結局変わりません。

評価プログラムを用いた実験結果は、熱はかけていないものの288秒で打ち切るまでノーエラーだったので、リフレッシュ回路は一応OKと判断します。ただし、DRAMリフレッシュのマージンに頼った実力OKの方式です。能力の大方がリフレッシュという効率も悪い方式なので、PICで改善することを期待します。


左矢前のブログ 次のブログ右矢

FM-7 ROM吸出し器の改版 (5)

posted by sakurai on May 19, 2020 #260

Arduinoのアクセス法の改善

ArduinoのリファレンスにはdigitalRead()、digitalWrite()という1ピンの入出力しかなく、パラレル入出力が無かったため、過去記事のようなアクセス法を取っていました。検索したところ、Arduino 日本語リファレンスのポート操作(その魚拓)でパラレル入出力が見つかったため、アクセス法を切り替えることにします。そのためポートアサインを以下のように変更します。

図%%.1
図260.1 Arduino Mega 2560 Proの端子図

図%%.2
図260.2 Arduino Mega 2560 Proの端子図(図260.1の右列グリーン枠部分)
図260.2のピンク色のアサイン表が示すように、アドレスはPORTF, PORTK、データはPORTF、制御信号はパラレルの必要は無いですが、PORTCにアサインし直しました。

上記サイトには、

DDRとPORTレジスタは読み書き両方が可能です。PINレジスタは読み取り専用です。

以下はレジスタを表す変数の名前のリストです。

DDRD: ポートD方向レジスタ
PORTD: ポートDデータレジスタ
PIND: ポートD入力レジスタ(読み取り専用)

とのことですが、実際には

DDRD: ポートD方向レジスタ
PORTD: ポートD出力レジスタ
PIND: ポートD入力レジスタ

となっているようです。DDRにより入出力をビット毎に切り替えられ、0が入力、1が出力となっています。 これがあまり表に出てきていない理由は移植性からのようです。digitalRead()のほうが移植性が高いとのことですが、元々8bitのパラレルデータを1bitずつ8回シフトして8bitデータとして使うのは、あまりにも馬鹿げています。


左矢前のブログ 次のブログ右矢

FM-7 ROM吸出し器の改版 (4)

posted by sakurai on May 18, 2020 #259

DRAMのリフレッシュタイミング

本来は基板設計の前にタイミングを検討しますが、開発環境と合わせて基板を紹介したため、後になっています。まずMB8265-15のデータシートを入手します。以下にZ80によるリフレッシュ手法である$\overline{\text{RFSH}}$リフレッシュタイミングを示します。

図%%.1
図259.1$\overline{\text{RFSH}}$リフレッシュタイミング

表259.1 MB8265-15の動的特性
Parameter Symbol MB8265-15
Min Max
$\overline{\text{RFSH}}\text{ Set Up Time Referenced to }\overline{\text{RAS}}$ $\text{t}_\text{FSR}$ 100 -
$\overline{\text{RAS}}\text{ to }\overline{\text{RFSH}}\text{ Delay}$ $\text{t}_\text{RFD}$ 100 -
$\overline{\text{RFSH}}\text{ Cycle Time}$ $\text{t}_\text{FC}$ 270 -
$\overline{\text{RFSH}}\text{ Pulse Width}$ $\text{t}_\text{FP}$ 150 -
$\overline{\text{RFSH}}\text{ Inactive Time}$ $\text{t}_\text{FI}$ 100 -

PICのプログラム

DRAMのタイミング要求からPICのプログラムを検討します。PICの書き込み自体もArduino(Z80側)にやらせることもできそうですが、動作しない等のトラブルの可能性を考え、今回はPicKit4を用いたIn Circuit Programmingを行います。PicKit4上で書き込み、書き込み済みのPICをボード上のソケットにはめ込むことにしました。

PICのプログラムは基本的には前稿のようになります。Xrefckを4回アサートする場合、PICに対して16KHzでタイマー割り込みをかけ、以下のisr (interrupt service routine)を実行します。

初期化

  1. 各種レジスタ設定。
  2. Rfreq=L、Xrefck=Hを出力(ネゲート)。
  3. Z80Wを監視し、Z80WがLのときにのみ割り込み許可。

割込みルーチン(ISR)

  1. Rfreq=H、Xrefck=Hを出力 //リフレッシュ要求
  2. Rfgnt=Hを待つ
  3. Rfreq=H、Xrefck=Lを出力
  4. Rfreq=H、Xrefck=Hを出力 (3, 4を4回繰り返す)
  5. Rfreq=L、Xrefck=Hを出力
  6. リターン

1命令1サイクルでないため、命令数のカット&トライが必要です。

Arduino(Z80側)のプログラム

一方、Arduino(Z80側)ではPICのRfreqを監視し、Rfgntを発行することでバス調停を行います。

過去記事には、

Z80カードは、FM-7の内部バスに対して、別のバスマスタを使用可能にするものであり、メインCPUである6809から$FD05のLSBを1にすれば、メインCPUにHALTがかかり、外部バスマスタであるZ80による内部バスアクセスが可能になるものです

ということから、

  1. \$FD05に1を書き込み、6809にHALTを要求する。FM-7内部では*GH($\text{Go}/\overline{\text{Halt}}$, 負論理のHALT信号)がアサートされ、6809がBA=BS=Hとし、バスを明け渡す。
  2. Rfgnt=H (初期値)
  3. アクセス前に
     ・Rfreq=Lを待つ (リフレッシュ優先のため)。
     ・Rfgnt=Lとする。
  4. アクセス(アドレス出力、データ出力、EB/QB出力、データ入力)
  5. アクセスの終了時に
     ・Rfgnt=Hとする。
  6. 終了時にはアドレスに\$FD05をセットし、EB/QBをアサートせずに6809にバスを明け渡す。

となります。このうち3, 5が今回追加したアクセス法です。このように、お互いに待つ場合はデッドロックの可能性があるため、背理法により無いことを証明しておきます。

  • Aruduinoが待つ場合は、上記3.からRfgnt=Hとした上でPICを待つ
  • 上記からデッドロックがあるとすれば、Rfgnt=Hのとき、PICがRfgnt=Lを待つ場合
  • ところが、PICが待つのはRfgnt=Hであることから、矛盾、よってデッドロックは存在しない

左矢前のブログ 次のブログ右矢

FM-7 ROM吸出し器の改版 (3)

posted by sakurai on May 15, 2020 #258

PICの開発環境

早速、開発環境とCコンパイラをインストールし、PICプログラマを購入しました。

対象となるPICマイコンは、秋月電子でも入手可能なPIC12F1501-I/Pとします。

図%%.2
図258.2 PICマイコン

Eagleによる設計

PICマイコンをプログラマブルタイマーとして使用した回路図V5を図258.3に示します。回路は多いようですが、ほとんどがコネクタであり、Arduinoが1個と今回追加した8pinのICが全てです。他はZ80コネクタ、ロジアナ用ピンヘッダ、PICプログラム用コネクタから構成されます。

図%%.3
図258.3 FM-7IntruderV5の回路図
以前に格安PCB業者を調べましたが、今回は最安だったJLCPCBにオーダーしました。5枚で送料込みで17.95 USDです。ここは安いだけでなく、製造も速くて2~3日で製造します。
図%%.4
図258.4 FM-7IntruderV5の基板図

左矢前のブログ 次のブログ右矢

FM-7 ROM吸出し器の改版 (2)

posted by sakurai on May 14, 2020 #257

DRAMの"化け"は故障とみなせる

試みに、DRAMのリフレッシュを停めると、どの程度ビット化けが起きるかを実験してみました。 注意点として、データチェックのためにリードアウトすると、リフレッシュとなってしまうということがあります。そのため、1sec毎に故障数をチェックするのではだめで、リフレッシュ停止期間を1secずつ伸ばして行き、壊れているデータの数を計数します。リフレッシュを停止しただけなので、永久故障ではないのですが、機能停止を広い意味で故障として扱うため、ここでは故障(ソフトエラー)と呼ぶことにします。

図%%.1
図257.1 リフレッシュ停止時間に対する故障割合、及び故障密度

図257.1左がリフレッシュ停止時間(sec)に対する故障バイト数の試験数に対する割合(累積故障確率関数)、図257.1右がリフレッシュ停止時間(sec)に対する1秒当たりの故障数(確率密度関数)です。このグラフはジャンクション温度により非常に変化し、当然温度が高いほうが故障しやすいわけです。

この室温では2分間でほぼ全数のバイトが化けていることになります。グラフでは分かりにくいですが、漸近値が99.2%付近となっています。100%にならない理由は、DRAMはリフレッシュが停まると\$00または\$FFになり易く、たまたま正解値が\$00または\$FFだったためと思われます。一致する確率は2/256なので、故障個数の最大は、試験バイト数\$6000個に対して$(1-\frac{2}{256})=99.2186\%$の24,384個になるはずです。実データでは139秒後に24,384個故障し、確率的な一致を除いた最大数になりました。

リフレッシュ試験

DRAM化けの試験プログラムはそのままリフレッシュ回路の試験に使用できます。リフレッシュを停止すると、正規分布を持つ確率密度に従いソフトエラーが起こります。リフレッシュ回路を追加して、さらに厳しい条件となるようにドライヤー等でDRAMを加熱し、この試験プログラムを用いて、2分程度エラーが確認されなければリフレッシュ回路はOKと判断できます。非同期DRAMのタイミングは複雑であり、RASのタイミングが規格割れしただけでソフトエラーを起こすので、試験としてはこれだけでなく、様々なタイミング切り替えを実施するほうが良いと思います。


左矢前のブログ 次のブログ右矢

FM-7 ROM吸出し器の改版

posted by sakurai on May 13, 2020 #256

FM-7 ROM吸出し器にDRAMリフレッシュ回路を追加

過去記事において、表記のボードを開発しましたが、DRAMのリフレッシュを失念していました。そこで、前回のオールソフトウエア(?)回路に対して、ハードウエアでリフレッシュ回路を追加します。バイトアクセスはオールソフトウエアで可能なことを実証できたのですが、DRAMリフレッシュは2msec以内に128カラムアドレスにアクセスが必要、という制約があるため、ソフトウエアでは厳しいと思われます。従って、ハードウエアで構成しますが、タイミング回路なので意外に回路規模を必要とします。せっかくArduino 1個で済ませたので、最小の回路構成としたいところです。

アナログタイマーIC 555

最初に思いついたのは小型のタイマーICである555です。ところがこれはアナログICであり、時定数設定のためにRやCを複数必要とします。さらに、リフレッシュ周期だけでなくリフレッシュパルス幅を確保しようとすると、555の2個入りを使用する必要があるだけでなく、外付けCRも倍の数になり、シンプルになりません。設計してみたものの嫌になりました。

図%%.1
図256.1 NE555回路図

プログラマブルタイマーPIC

そこで、プログラマブルタイマーで検討したところ、PICマイコンを使用すれば、開発環境その他は必要となりますが、8pinのIC1個で行けそうです。デジタルのほうがシンプルで、かつ調整も無くて好きです。

図%%.2
図256.2 バス権調停信号図

PICのリフレッシュアルゴリズム

リフレッシュアルゴリズムは以下のように考えています。

  • 6809が\$FD05に1を書き、Z80W信号をLにします(Z80W=$6809/\overline{\text{Z80}}$)。
  • ArduinoはPICからのバス権の要求RFREQが無ければ、RFGNTをLにしてバスを獲得します。バスアクセスはQB/EBをアサートすることで行います(RFGNT=$\text{PIC}/\overline{\text{Arduino}}$)。
  • Z80W信号がLの際に、PICから周期的にRFREQをHとしてバス権要求をArduinoに通知します。
  • Arduinoはバスアクセス中であれば終了後にバス権を放し、PICにRFGNTをHとして通知します。
  • PICはRFGNTがHであればリフレッシュ可能と判断し、*REFCKをLとします。
  • バス調停のオーバヘッドを削減するため、PICは*REFCKは4回アサートします。その代わり、リフレッシュ周期は1/4の16KHzとなります。
  • リフレッシュが終了すると、PICはRFREQをLにします。
  • ArduinoはPICがバス権を放したと判断してRFGNTをLにしてバスを獲得します。

図%%.3
図256.3 バス権調停タイミングチャート

左矢前のブログ 次のブログ右矢

posted by sakurai on May 12, 2020 #255

過去ブログの、BSVによるスペースインベーダーの再設計の記事#234~#239, #254をまとめてQiitaに投稿しました。さらに考察を加えています。

BSV (Bluespec SystemVerilog)によるスペースインベーダーの再設計

過去ブログ記事でUltra96ボードを用いた、VerilogHDLによるSpace Invadersゲームの作成を投稿しましたが、その続きです。

図%%.1
図255.1 Qiitaの投稿記事

左矢前のブログ 次のブログ右矢

BSVの設計トライアル (21)

posted by sakurai on May 11, 2020 #254

ゲームFSMのアルゴリズム

トライアルの結果、BSVによるゲームFSMが完成しました。過去記事のステートベースのサウンドステートマシンと異なり、ステート分解をしていないため、rule文を一切使用していません。全てbsc(Bluespec Compiler)の、StmtFSMライブラリにステート管理を任せました。

基本的にはCで記述するようにゲームが記述できることが分かりました。例えば、弾の移動及び衝突判定、衝突処理(爆発マーク)、爆発マーク消去等のアルゴリズムを考えると、自弾、敵弾共にアルゴリズムは共通で、疑似コードで書けば、

    if (弾爆発タイマ >= 1) {   // 弾爆発中
        弾爆発タイマ++;
        if (弾爆発タイマ == MAX) {
            弾削除;            // 論理的な消去
            弾爆発マーク消去;   // 物理的な消去
            弾爆発タイマ停止;
        }
    } else {
        if (弾が出ていない and 弾生成条件) {
            弾生成処理;
            弾発射音;     // 自弾のみ
        }
        if (弾存在) {
            衝突判定;
            if (対象物) {  // 自弾の場合はインベーダ及びUFO、敵弾の場合は自機
                弾削除;          // 論理的な消去
                対象物ステート <= 爆発;
                対象物爆発タイマ <= 0;
            } else if (上下ハズレ || ベース || 弾) { // 弾:自弾の場合は敵弾、敵弾の場合は自弾
                弾マーク消去;
                弾爆発マーク;
                弾爆発タイマ <= 1;
            } else {        // 衝突していない場合
                弾を進める;
            }
        }
    }

一方、対象物は、

    if (対象物ステート == 爆発) {
        if (対象物爆発タイマ==0) {
            対象物爆発タイマ <= 1;
            対象物爆発音;
            対象物爆発マーク;
        } else {
            対象物爆発タイマ++;
            if (対象物爆発タイマ == MAX) {
               対象物削除;          // 論理的な消去
               対象物爆発マーク消去; // 物理的な消去
            }
        }
    }

のようになりますが、StmtFSMを使うと、このようなシーケンスをクロック毎のステートに分解しなくて記述できます。

インベーダのタイミング

某所で質問があったので、タイミングについて解説します。基本の1 tickは1/60秒で、その中で、インベーダ1匹、敵弾全弾、自機、自弾、UFO、スコア等の処理を行います。以下は実際のBSVのメインループのコードです。

     while (game_flag) seq // メインループ
        for (noy <= 0; noy < `Inv_TateS; noy <= noy + 1) seq  // インベーダの行処理
           for (nox <= 0; nox < `Inv_YokoS; nox <= nox + 1) seq // インベーダの列処理
              if (inv_s[nox][noy]) seq // インベーダが生きてれば
                 ivader;      // インベーダ処理
                 gun;         // 自機処理
                 bullet;      // 自弾処理
                 for (idx <= 0; idx < extend(max); idx <= idx + 1) seq
                    invBullet(idx);  // 敵弾全弾処理
                 endseq
                 ufo;         // UFO処理
                 scores;      // スコア表示
                 endJudge;    // 終了判定
                 counter <= counter + 1;  // tickカウンタ++
                 wait_timer;  // インナーループを1/60secにするウエイト
              endseq
           endseq
        endseq
     endseq
     gameOver;  // ゲームオーバー表示

1tick=1/60secの間に、インベーダ1匹(2ピクセル移動)の処理に対して、自機(1ピクセル移動)、敵弾(1ピクセル移動)、自弾(4ピクセル移動)の処理が行われます。インベーダは初期に55匹存在するので、1/55倍のスピードで始まりますが、最終的に1倍のスピードになります。従って、インベーダを倒すたびにインベーダ全体は速くなり、一方その他の速度は変わらないわけです。

FPGAでの実装では1 tick内にインベーダ全体を移動することは可能ですし、そのような実装も見ますが、ゲーム性が変わってしまいます。具体的には、インベーダ全体の速度が次第に速くならなかったり、後ろのインベーダを撃つことができなくなります。

例えば、インベーダゲームのレインボーは、後ろのインベーダを撃つことにより出現します。インベーダは残りが一匹になると左へは2ピクセルずつ右には3ピクセルずつ移動します。下2段のインベーダは、左右2ピクセルまでの移動では跡が残らない図形になっていますが、3ピクセルだと跡が消えずに残ります。もちろん今回の実装でもレインボーを体験できます。

ゲームFSMの完成

図254.1は、BSVで再設計したゲームFSMにより動作する、インベーダーゲームの動画です。過去記事に書いたように、サウンドが4ch同時発声と高品質になりました。

図%%.1
図254.1 ゲームFSMの完成

左矢前のブログ 次のブログ右矢


ページ: