Posts Issued on November 7, 2023

Pongの開発 (2)

posted by sakurai on November 7, 2023 #691

サウンドROMデータの作成

以下のコマンドにより、Vivadoの読めるCOEファイルを作成します。

echo 'memory_initialization_radix=16;' > srom.coe
echo -n 'memory_initialization_vector=' >> srom.coe
cat s?o.wav | \od -An -t x1 -v >> srom.coe
echo ';' >> srom.coe

サウンドステートマシン

サウンドステートマシンは以前作成したものを流用します。Space Invadersの開発の際には4多重音のため、4個のサウンドステートマシンを使用しましたが、Pongは単純なので1個で十分です。そのため自分と他人のサウンドコードを見分ける必要がないため、キューへの書き込みを示す!emptyで起動します。またミキサーも無くなるため、従来後段のミキサーに入れていた符号拡張と桁調整を本モジュールに組み込みました。

以下にソースコードを示します。コメントは一部ChatGPTにより作成してもらいました。

// 波形ファイルを読み込み、オーディオDACにサウンドデータを出力するFSMの定義

import StmtFSM.*;  // FSMを生成するためのユーティリティモジュールのインポート

// サウンドイベントと無音を表すマクロ定義
`define SOUND1    1    // 発射音
`define SOUND2    2    // パドルとの衝突音
`define SOUND3    3    // 壁との衝突音
`define SOUND4    4    // アウトの際の音
`define NULL      'h80 // 無音を表す値(8ビットPCMで中間値)

// 必要な型定義
typedef UInt#(13) Addr_t;  // メモリアドレス用の13ビット符号なし整数
typedef UInt#(8) Data_t;   // 8ビットデータ用の符号なし整数
typedef Bit#(16) Sound_t;  // 16ビット符号付PCMサウンドデータ
typedef Bit#(3) Code_t;    // サウンドコード

// FSMのインターフェース定義。外部からアクセスするためのメソッドが定義されています。
interface FSM_ifc;
   method Action sound(Code_t code);              // 音声コードを示す入力メソッド
   method Action rom_data(Data_t indata);         // ROMからのデータ入力メソッド
   method Action sync(Bool lrclk);               // 同期信号を処理するための入力メソッド
   method Action empty(Bool flag);               // FIFOが空を表す入力メソッド
   method Addr_t rom_address();                  // 現在のROMアドレスの出力メソッド
   method Sound_t sdout();                        // 音声出力データの出力メソッド
   method Bool soundon();                         // 音声が再生中かどうかを示す出力メソッド
   method Bool fifo_ren();                        // FIFOの読み出し要求の出力メソッド
endinterface

(* synthesize,always_ready,always_enabled *)
module mkSoundFSM(FSM_ifc);

// 内部ワイヤとレジスタの定義
   
Wire#(Code_t) code <- mkWire, // コードを格納するワイヤと現在のコードを保持するレジスタ
              current <- mkRegU;
Wire#(Bool) lrclk <- mkWire;  // 左右のクロック同期用のワイヤ
Reg#(Data_t) romdata <- mkRegU; // ROMから読み込まれたデータを保持するレジスタ
Reg#(Data_t) dout <- mkReg(`NULL); // データ出力用のレジスタ(初期値は無音)
Reg#(UInt#(32)) workd <- mkRegU;  // 32ビット作業用データレジスタ
Reg#(UInt#(13)) dcount <- mkRegU; // 再生カウント用の13ビットレジスタ
Reg#(Addr_t) worka <- mkRegU, // アドレス計算用の作業用アドレスレジスタ
                         romaddr <- mkRegU, // ROMのアドレスレジスタ
                         addr <- mkRegU; // 出力用アドレスレジスタ
Reg#(UInt#(8)) ii <- mkReg(0); // ループカウンタ用の8ビットレジスタ
Reg#(Bool) son <- mkReg(False), // サウンド再生中フラグ用のレジスタ
                     sonEarly <- mkReg(False), // 早期サウンド開始フラグ用のレジスタ
                     ren <- mkReg(False),  // FIFO読み込み要求フラグ用のレジスタ
                     emptyf <- mkReg(True); // FIFOが空かどうかを示すフラグ用のレジスタ

   // subfunctions
   //   READ MEM  サブ関数:メモリからの読み出し
   //     input:  worka
   //     output: romdata;
   //
   function Stmt readmem;
      return (seq
         addr <= worka;
         delay(2);
      endseq);
   endfunction

   //   READ COUNT    サブ関数:カウント読み出し
   //     input:  romaddr
   //     output: (romaddr,...,romaddr+3) => dcount;
   //             romaddr + 4 => romaddr;
   //
   function Stmt readcount;
      return (seq
         workd <= 0;
         for (ii <= 0; ii <= 3; ii <= ii + 1) seq
            worka <= romaddr + extend(3-ii);
            readmem;
            if (ii == 3) dcount <= truncate(workd<<8) | extend(romdata);
            else workd <= workd<<8 | extend(romdata);
         endseq
         romaddr <= romaddr + 4;
      endseq);
   endfunction
      
   //  Mainloop    メインループの定義
   //
   Stmt main = seq
      while(True) seq
         action
            dout <= `NULL;
            sonEarly <= False;
            son <= False;
            ren <= False;
         endaction
         await(!emptyf);
         action
            ren <= True; // consume 1 entry of Q
            current <= code;
         endaction
         await(emptyf);
         ren <= False;
         // Sync to LRCLK 
         //
         await(lrclk);
         await(!lrclk);
         delay(4);

         // Format decoding
         //
         action    
            case (current)
              `SOUND1:  romaddr <=   0 + 16;
              `SOUND2:  romaddr <=  1610 + 16;
              `SOUND3:  romaddr <=  (1610 + 900) + 16;
              `SOUND4:  romaddr <=  (1610 + 900 + 872) +16;
            endcase
         endaction
         readcount;
         romaddr <= romaddr + extend(dcount) + 4;
      
         readcount;
         romaddr <= romaddr - 1;

         // play loop
         while (dcount != 0) seq
            // Play 0
            if (sonEarly == False) seq
            // 1cycle目
               readmem;
               action
                  sonEarly <= True;
                  son <= False;
                  dout <= `NULL;
               endaction

            endseq else seq
            // 2cycle目以降
               readmem;
               action
                  son <= True;
                  dout <= romdata;
               endaction
            endseq // if

            delay(11);
            action
               romaddr <= romaddr + 1;
               worka <= romaddr + 1;
               dcount <= dcount - 1;
            endaction
         endseq // while(!終了条件)
      endseq // while(True)
   endseq; // Stmt

   mkAutoFSM(main);     // FSMを生成し実行
   
   method Action sound(Code_t incode);
      code <= incode;
   endmethod
   method Action rom_data(Data_t indata);
      romdata <= indata;
   endmethod
   method Addr_t rom_address();
      return addr;
   endmethod
   method Sound_t sdout(); 
      let bdout = pack(dout);     // 現在のオーディオデータ(dout)をパックし、16ビットのデータ(bdout)に変換します。
      let s =  ~bdout[7]; // 8ビット目(MSB)を反転させてサインビット(s)を生成します。
      return {{s,s},bdout[6:0],{7'h0}}; // オーディオデータをsignedに変換します。
   endmethod
   method Bool soundon();
      return son;
   endmethod
   method Action sync(Bool inlrclk);
      lrclk <= inlrclk;
   endmethod
   method Bool fifo_ren();
      return ren;
   endmethod
   method Action empty(Bool flag);
      emptyf <= flag;
   endmethod

endmodule: mkSoundFSM

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