Posts Tagged with "Design"

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

Graphic Controlerの再設計

完成したIP diagramを510.1に示します。3個のサブモジュールをまとめたので、graphic階層はなくなりました。

図%%.1
図510.1 BSVによるグラフィックコントローラ

BSVソース

GraphicFSM.bsv:

import StmtFSM::*;

`define HD         800
`define HFP        16
`define HSP        80
`define HBP        160
`define HO        `HFP+`HSP+`HBP
`define HL        `HD+`HO

`define VD         600
`define VFP        1
`define VSP        3
`define VBP        21
`define VO        `VFP+`VSP+`VBP
`define VL        `VD+`VO

`define EHD        512
`define EVD        512

`define HW         11 // log2(1056)=10.04439
`define VW         10 // log2(628)=9.294621

typedef Bit#(16) Addr_t;

interface GraphicFSM_ifc;
   method Bool xhs();
   method Bool xvs();
   method Addr_t address();
   (* prefix="" *)
   method Action idata(Bit#(4) indata);
   (* prefix="" *)
   method Action expl(Bool exp);
   method Bit#(1) rd();
   method Bit#(1) gd();
   method Bit#(1) bd();
   endinterface

(* synthesize,always_ready,always_enabled *)
module mkGraphicFSM(GraphicFSM_ifc);

   Reg#(UInt#(`HW)) x <- mkRegU;
   Reg#(UInt#(`VW)) y <- mkRegU;
   Reg#(Bool) in_xhs <- mkReg(False),
             in_xvs <- mkReg(False),
              in_hdt <- mkReg(False),
              in_vdt <- mkReg(False);
   UInt#(`HW) ehoff = (`HD-`EHD)/2;
   UInt#(`VW) evoff = (`VD-`EVD)/2;
   Reg#(Bit#(4)) in_data <-mkRegU;
   Reg#(Bool) in_exp <- mkReg(False);

   //  Mainloop
   //
   Stmt main = seq
      while(True) seq
//       for (y <= 0; y < `VL; y <= y+1) seq
         y <= 0;
         while (y < `VL) seq
//          for (x <= 0; x < `HL; x <= x+1) action  --- for consumes two cycles, then we like to use while
            x <= 0;
            while (x < `HL) action
               if (((`HD+`HFP)<=x)&&(x<(`HD+`HFP+`HSP))) in_xhs <= False;
               else in_xhs <= True;
               if ((ehoff<=x)&&(x<ehoff+`EHD)) in_hdt <= True;
               else in_hdt <= False;
               x <= x + 1;
               if (((`VD+`VFP)<=y)&&(y<(`VD+`VFP+`VSP))) in_xvs <= False;
               else in_xvs <= True;
               if ((evoff<=y)&&(y<evoff+`EVD)) in_vdt <= True;
               else in_vdt <= False;
            endaction // for -> while
            y <= y + 1;
         endseq // for -> while
         $display("%3d %3d", y, x);
      endseq // while(True)
   endseq; // Stmt

   Bit#(`HW)xx = pack(x-ehoff)>>1;
   Bit#(`VW)yy = pack(y-evoff)>>1;
   Bit#(8)xxx = truncate(xx);
   Bit#(8)yyy = truncate(yy);
   Bit#(16) in_addr = {yyy, xxx};
   Bool in_dt = in_hdt && in_vdt;
   Bit#(1) in_rd = !in_exp ? in_data[2] & pack(in_dt) : (in_data[2] | in_data[1] | in_data[0]) & pack(in_dt);
   Bit#(1) in_gd = !in_exp ? in_data[1] & pack(in_dt) : 1'b0;
   Bit#(1) in_bd = !in_exp ? in_data[0] & pack(in_dt) : 1'b0;

   mkAutoFSM(main);

   method Bool xhs();
      return in_xhs;
   endmethod
   method Bool xvs();
      return in_xvs;
   endmethod
   method Addr_t address();
      return in_addr;
   endmethod
   method Action idata(Bit#(4) indata);
      in_data <= indata;
   endmethod
   method Action expl(Bool exp);
      in_exp <= exp;
   endmethod
   method Bit#(1) rd();
      return in_rd;
   endmethod
   method Bit#(1) gd();
      return in_gd;
   endmethod
   method Bit#(1) bd();
      return in_bd;
   endmethod

endmodule: mkGraphicFSM
  • actionからendactionまでは1サイクル実行です。
  • verilogと同様、"<="はノンブロッキング代入でDFFが、"="はブロッキング代入で組み合わせ回路がそれぞれ生成されます。

当初、水平のオフセットを表す定数EHOFFは、上記のような

UInt#(`HW) ehoff = (`HD-`EHD)/2;

という変数ではなく、define文により

`define EHOFF      (`HD-`EHD)/2

のように定義していたのですが、defineの中でカッコや乗除算は使用できないようなので、変数としました。

ところが、生成されたVerilogを確認したところ、bscの最適化により定数となっており、レジスタは存在しませんでした。結論として、マクロで定数定義してもレジスタ宣言しても、オーバヘッドは変わりません。


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

posted by sakurai on September 15, 2022 #509

Graphic Controlerの再設計

引き続き、従来設計ではVerilogで設計していたものを勉強の目的からBSVに置き換えます。Graphic ControllerはVRAMへアドレスを出力し、VRAMデータを読み出し、また水平同期、垂直同期、表示期間等のタイミング信号を作成するモジュールです。

Verilogによる設計(過去記事)では、水平カウンタ、垂直カウンタを別々に設け、水平のタイミングデコーダと垂直のタイミングデコーダにより同期信号等を作成していました。また、自機が破壊された場合に全画面を赤色表示にするモジュールを図414.2のように、後段に接続していました。また、VRAMデータ4bitのうちRGBを表す3bitを取り出すために、xisliceモジュールを用いていました。

図414.2
図414.2 従来のグラフィックコントローラ階層図

今回BSVで再設計するにあたり、3個に分かれていたモジュール構成を1個にまとめます。

アルゴリズム説明

Graphics.bsvの中心部分:

         y <= 0;
         while (y < `VL) seq
//       for (y <= 0; y < `VL; y <= y+1) seq
            x <= 0;
//          for (x <= 0; x < `HL; x <= x+1) action  --- for consumes two cycles, then we like to use while
            while (x < `HL) action
               if (((`HD+`HFP)<x)&&(x<=(`HD+`HFP+`HSP))) in_xhs <= False;
               else in_xhs <= True;
               if ((ehoff<x)&&(x<=ehoff+`EHD)) in_hdt <= True;
               else in_hdt <= False;
               x <= x + 1;
               if (((`VD+`VFP)<y)&&(y<=(`VD+`VFP+`VSP))) in_xvs <= False;
               else in_xvs <= True;
               if ((evoff<y)&&(y<=evoff+`EVD)) in_vdt <= True;
               else in_vdt <= False;
            endaction // for -> while
            y <= y + 1;
         endseq // for -> while

このように、y方向とx方向の2次元方向にドットクロックを数えます。コメントされている行のように、本来for文を2重で回したいのですが、資料事例で学ぶ BSVからの引用の図509.1に示すように、Stmt文内のfor文は2サイクルかかることに注意します。

図%%.1
図509.1 for文とwhile文

一方、while文は初期化に1サイクルかかるものの、インナーループでのチェックとアクションを1サイクルで実行できます。

for文のインナーループが2サイクルになるということは、2倍の周波数でFSMを駆動しなければならないことになります。現行では49.5MHzなので2倍では99MHzとなり、FPGAの上限に近くなってしまいます。

念のため99MHzで動作するfor文を用いたケースを合成し、正常動作を確認しましたが、タイミングクロージャや発熱等を考えると、回路はなるべく低速で回した方が望ましいです。

一方whileループであればインナーループが1サイクルで良いため、一旦for文で書いてから等価なwhile文に書き換えます。


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

posted by sakurai on September 14, 2022 #508

アルゴリズム説明

アルゴリズムの中心部分を説明します。一度に変換すると、bscによるデータサイズの推定がうまく行かなかったため、ステップに分解してデータタイプのヒントを与えています。ステップに分解しても結局組み合わせ回路が合成されるため、回路オーバヘッドはありません。

      Bit#(8) xa = a[15:8];

横方向アドレスであるxaは縦方向アドレス(上位8bit)をそのまま使用します。

      Int#(10) yax = 256 - signExtend(unpack(a[7:0]));

一方、縦方向アドレスyaは横方向アドレス(下位8bit)をunpackにより整数化し、符号拡張した上で256から引きます。

      Bit#(8) ya = truncate(pack(yax));

その後unpackによりビットベクターとし、最後にtruncateで8bitベクターに戻します。データタイプ変換関数はここに掲載されています。

      if (sel) return b;
      else if (sw) return {ya, xa};
      else return a; // normal state

最後にselがtrueならb(メモリダンプFSMによるアドレス)、falseでswがtrueならxとyの入れ替え&yの反転(90°回転)、falseならオリジナルのa(ゲームFSMによるアドレス)を選択します。

実行結果

図508.1に実行結果を示します。首を左に90度傾けた上で正しく実行できました。

図%%.2
図508.1 実行結果

変更後のソース

変更したMux.bsv:

typedef Bit#(16) Addr_t;

interface Mux_ifc;
   (* prefix="" *)
   method Addr_t outp(Bool sw, Bool sel, Addr_t a, Addr_t b);
endinterface

(* synthesize, always_ready = "outp", no_default_clock, no_default_reset *)
module mkMux(Mux_ifc);
    method Addr_t outp(Bool sw, Bool sel, Addr_t a, Addr_t b);
      Bit#(8) xa = a[15:8];
      Int#(10) yax = 256 - signExtend(unpack(a[7:0]));
      Bit#(8) ya = truncate(pack(yax));
      if (sel) return b;
      else if (sw) return {ya, xa};
      else return a; // normal state
   endmethod
endmodule

変更箇所はこのアドレスの縦横入れ替えと、VRAM初期値のデータの縦横入れ替えの2点となります。後者はプログラムでも可能ですが、csvに変換した上でexcelのコピーのオプションの行列の入れ替え機能で実施しました。⇒その後、VRAM初期値に依存せずに縦横入れ替えを実施できるように、描画を追加しました。

追記:後継記事を掲載しました。


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

posted by sakurai on September 12, 2022 #507

画面の縦横の入れ替え

Landscapeのモニタを使用しているため、Space Invadersは正立していますが、本来から比べると縦方向に縮んでいます。これを修正するには画面の縦と横を入れ替えます。VRAMの読み出しアドレスのxとyを入れ替えれば良いはずです。

過去記事のVRAMのアドレス周りの図403.1を見ると、アドレスマルチプレクサが流用できそうです。これはFSMからのアドレスをメモリダンプ中にスイッチするものです。

図%%.1
図403.1 VRAMモジュール

元のMux.bsv:

typedef Bit#(16) Addr_t;

interface Mux_ifc;
   (* prefix="" *)
   method Addr_t outp(Bool sel, Addr_t a, Addr_t b);
endinterface

(* synthesize, always_ready = "outp", no_default_clock, no_default_reset *)
module mkMux(Mux_ifc);
   method Addr_t outp(Bool sel, Addr_t a, Addr_t b);
      if (sel) return b;
      else     return a;
   endmethod
endmodule

このソースにおいて、中心部分はセレクタであり、

      if (sel) return b;
      else     return a;

このようにselがtrueならb(メモリダンプFSMによるアドレス)、falseならa(ゲームFSMによるアドレス)を選択していました。

これに対し、図507.1のように新に1bitのスイッチを加え、スイッチがONのときにはゲームFSMによるアドレスの縦と横を入れ替えるように設計変更します。

図%%.1
図507.1 アドレス生成器の改造

具体的には通常時のアドレスa系のx座標アドレス(下位8bit)とy座標アドレス(上位8bit)を入れ替え、さらにy座標は上下反転します。上下反転は、256からアドレスを引くことで実現します。

$$ \begin{eqnarray} \left\{ \begin{array}{l} x&\Leftarrow&y \\ y&\Leftarrow&256 - x \end{array} \right. \end{eqnarray} $$


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

posted by sakurai on September 9, 2022 #505

OneStageソース

前稿で述べたルールの前後にインタフェース、レジスタ宣言、メソッド宣言を加えてソースが完成しました。

interface Fifo_ifc;
(* prefix="" *)
   method Action if_write_enable((* port="wr_en" *)Bool wen);
(* prefix="rd" *)
   method Action if_read_enable0(Bool en0);
(* prefix="rd" *)
   method Action if_read_enable1(Bool en1);
(* prefix="rd" *)
   method Action if_read_enable2(Bool en2);
(* prefix="rd" *)
   method Action if_read_enable3(Bool en3);
(* result="empty" *)
   method Bool if_empty();
endinterface

(* synthesize, always_ready, always_enabled *)
module mkOneStage(Fifo_ifc);

   Reg#(Bool) in_wen <- mkReg(False);
   Reg#(Bool) in_ren0 <- mkReg(False);
   Reg#(Bool) in_ren1 <- mkReg(False);
   Reg#(Bool) in_ren2 <- mkReg(False);
   Reg#(Bool) in_ren3 <- mkReg(False);
   Reg#(Bool) in_empty <- mkReg(True);

   rule rule_write (in_wen && in_empty);
     in_empty <= False;
   endrule
   rule rule_read ((in_ren0 || in_ren1 || in_ren2 || in_ren3) && !in_empty);
     in_empty <= True;
   endrule

   method Action if_write_enable(Bool wen);
      in_wen <= wen;
   endmethod
   method Action if_read_enable0(Bool en0);
      in_ren0 <= en0;
   endmethod
   method Action if_read_enable1(Bool en1);
      in_ren1 <= en1;
   endmethod
   method Action if_read_enable2(Bool en2);
      in_ren2 <= en2;
   endmethod
   method Action if_read_enable3(Bool en3);
      in_ren3 <= en3;
   endmethod
   method Bool if_empty();
      return in_empty;
   endmethod

endmodule: mkOneStage

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

posted by sakurai on September 9, 2022 #506

ブロック図

旧版(verilog版)のブロック構成と、BSVで再設計したブロック構成を示します。再設計の際にグルーロジックを取り込んだため、4個のモジュールが1個になりhandshake階層をなくすことができました。

図415.4
図415.4 旧版(verilog版)OneStageブロック図

図%%.1
図506.1 新版(BSV版)OneStageブロック図


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

posted by sakurai on September 8, 2022 #504

OneStageの変更

OneStageとはGameFSMとSoundFSMのインタフェースを行うモジュールです。元々FIFOだったのですが、FIFOの段数だけサウンドがレコードされていき、遅延して演奏されるようになったので、リアルタイム性のため1段のFIFO(=One Stage FIFO)としました。

前回Verilogで設計したものを今回BSVに移植します。ロジック的には大したことのないモジュールでVerilogで書いても工数はあまり変わりません。

図%%.1
図504.1 ステート遷移図

図504.1にステート遷移図を示します。1段のキューを模擬する動作であり、

  • emptyかつwriteの場合は!empty (=full)に遷移します。
  • !empty (=full)かつreadの場合はemptyに遷移します。

通常、GameFSMが書き込むことにより!emptyとなった場合は、演奏途中でもemptyを見ているため、SoundFSMはすぐに読み出します。すぐにreadを返すためまたemptyとなります。

例外は自機増加音で、プリエンプション禁止であるため、!emptyとなってもreadが返りません。従って演奏が終わるまで!emptyのままとなります。出力側はemptyを見ているため、この場合は出力が待たされ取りこぼしが防止されます。

BSVでの記述

これはステートマシンなので、以前に示したように、

  • ステートベース設計
  • シーケンスベース設計

の2通りの設計手法があります。自動FSMによるシーケンスベース設計も可能ですが、あまりにも簡単なステートマシンなので、ステート遷移のルールを直に記述します。ren信号はread enableの意味です。

図504.1をBSVのルール文で書くと

rule rule_write (in_wen && in_empty);
   in_empty <= False;
endrule
rule rule_read ((in_ren0 || in_ren1 || in_ren2 || in_ren3) && !in_empty);
   in_empty <= True;
endrule

となります。

  • write_enable (in_wen)はキューへの書き込みのためのGameFSMからの出力です。
  • read_enable (in_ren0~3)はキューからの引き取りのためのSoundFSMからの出力ですが、SoundFSMが4種類あるので、4つの信号となります。

別々のルールにおいて、同一資源であるin_emptyに書き込むのが若干気になります。スケジューリング時点でエラーが出るかもしれないと思いましたが、出ませんでした。これは、2つのルールがそれぞれemtpyと!emptyでガードされており、ルールに重なりが無いので、同一リソースへのライトハザードが起きないためでしょう。


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

posted by sakurai on September 7, 2022 #503

BSV (Bluespec SystemVerilog)を用いたサウンドFSMのシーケンスベースによる再設計」と題する記事をQiitaに投稿しました。

図%%.1
図503.1 Qiita投稿

前回のBSVによる設計ではGameFSMとSoundFSMで敢えて異なる設計手法としましたが、今回はSoundFSMもシーケンスベース設計に変更しました。その理由は、ステートベース設計ではステート分解を人力で行うため、高級言語のメリットがあまり出ないためです。さらに、同じ機能をステートベース設計とシーケンスベース設計とで設計してみて、結果の違いを見たかったためです。


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

posted by sakurai on September 6, 2022 #502

BSVソース

完成したBSVのソースを貼り付けます。1つのソースで4種のFSMを合成し分けているため、やや複雑になっています。

SoundFSM.bsv:

import StmtFSM::*;

`define SOUND1_ON     1        // 自弾発射音_ON
`define SOUND2_ON     2        // 自機爆発音_ON
`define SOUND3_ON     3        // インベーダ爆発音_ON
`define SOUND4_ON     4        // インベーダ歩行音1_ON
`define SOUND5_ON     5        // インベーダ歩行音2_ON
`define SOUND6_ON     6        // インベーダ歩行音3_ON
`define SOUND7_ON     7        // インベーダ歩行音4_ON
`define SOUND8_ON     8        // UFO爆発音_ON
`define SOUND9_ON     9        // 自機増加音_ON
`define SOUND10_ON   10        // UFO飛行音_ON
`define SOUND10_OFF  11        // UFO飛行音_OFF
`define NULL         'h80
`define COND_FSM0 !emptyf && (code == `SOUND1_ON || code == `SOUND2_ON || code == `SOUND9_ON)
`define COND_FSM1 !emptyf && (code == `SOUND3_ON)
`define COND_FSM2 !emptyf && (code == `SOUND4_ON || code == `SOUND5_ON || code == `SOUND6_ON || code == `SOUND7_ON)
`define COND_FSM3 !emptyf && (code == `SOUND8_ON || code == `SOUND10_ON || code == `SOUND10_OFF)

typedef UInt#(15) Addr_t;
typedef UInt#(8) Data_t;
typedef Bit#(4) Code_t;

interface FSM_ifc;
   method Action sound(Code_t code);
   method Action rom_data(Data_t indata);
   method Action sync(Bool lrclk);
   method Action empty(Bool flag);
   method Addr_t rom_address();
   method Data_t sdout();
   method Bool soundon();
   method Bool fifo_ren();
endinterface

 (* synthesize,always_ready,always_enabled *)
`ifdef FSM0
module mkSoundFSM0(FSM_ifc);
`elsif FSM1
module mkSoundFSM1(FSM_ifc);
`elsif FSM2
module mkSoundFSM2(FSM_ifc);
`elsif FSM3
module mkSoundFSM3(FSM_ifc);
`endif

   Wire#(Code_t) code <- mkWire,
                   current <- mkRegU;
   Wire#(Bool) lrclk <- mkWire;
   Reg#(Data_t) romdata <- mkRegU,
                   data <- mkRegU,
                   dout <- mkReg(`NULL);
   Reg#(UInt#(32)) workd <- mkRegU;
   Reg#(UInt#(15)) dcount <- mkRegU;
   Reg#(Addr_t) worka <- mkRegU,
                   romaddr <- mkRegU,
                   addr <- mkRegU;
   Reg#(UInt#(8)) ii <- mkReg(0);
   Reg#(Bool) son <- mkReg(False),
                   sonEarly <- mkReg(False),
                   ren <- mkReg(False),
                   emptyf <- mkReg(True);
`ifdef FSM3
    Reg#(Bool) fUFO <- mkReg(False);
`endif

   // subfunctions
   //   READ MEM
   //     input:  worka
   //     output: romdata;
   function Stmt readmem;
      return (seq
         addr <= worka;
         noAction;
         data <= romdata;
      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

   Stmt main = seq
      while(True) seq
         action
            dout <= `NULL;
            sonEarly <= False;
            son <= False;
            ren <= False;
         endaction
`ifdef FSM0
         await(`COND_FSM0);
         action
            ren <= True;
            current <= code;
         endaction
`elsif FSM1
         await(`COND_FSM1);
         action
            ren <= True;
            current <= code;
         endaction
`elsif FSM2
         await(`COND_FSM2);
         action
            ren <= True;
            current <= code;
         endaction
`elsif FSM3
         await(`COND_FSM3 || fUFO);
         if (`COND_FSM3) action
            fUFO <= (code == `SOUND10_ON);
            ren <= True;
            current <= code;
         endaction else if (fUFO) action
            current <= `SOUND10_ON;
        endaction
`endif
         await(emptyf);
         ren <= False;
`ifdef FSM3
         if (code == `SOUND10_OFF) continue;
`endif
         await(lrclk);
         await(!lrclk);
         delay(4);

         action    
            case (current)
`ifdef FSM0
               `SOUND1_ON:  romaddr <=     0 + 16;
               `SOUND2_ON:  romaddr <=  3422 + 16;
               `SOUND9_ON:  romaddr <= 16150 + 16;
`elsif FSM1
               `SOUND3_ON:  romaddr <=     0 + 16;
`elsif FSM2
               `SOUND4_ON:  romaddr <=     0 + 16;
               `SOUND5_ON:  romaddr <=  1266 + 16;
               `SOUND6_ON:  romaddr <=  2836 + 16;
               `SOUND7_ON:  romaddr <=  4406 + 16;
`elsif FSM3
               `SOUND8_ON:  romaddr <=     0 + 16;
               `SOUND10_ON: romaddr <= 25968 + 16;
`endif
            endcase
         endaction
         readcount;
         romaddr <= romaddr + extend(dcount) + 4;

         readcount;
         romaddr <= romaddr - 1;

         while (!((dcount == 0) || 
`ifdef FSM0
            (`COND_FSM0 && current !=`SOUND9_ON))) seq
`elsif FSM1
            (`COND_FSM1)))seq
`elsif FSM2
            (`COND_FSM2))) seq
`elsif FSM3
            (`COND_FSM3))) seq
`endif
            if (sonEarly == False) seq
               readmem;
               action
                  sonEarly <= True;
                  son <= False;
                  dout <= `NULL;
               endaction
            endseq else seq
               readmem;
               action
                  son <= True;
                  dout <= romdata;
               endaction
            endseq

            delay(11);
            action
               romaddr <= romaddr + 1;
               worka <= romaddr + 1;
               dcount <= dcount - 1;
            endaction
         endseq
`ifdef FSM3
         if ((code == `SOUND8_ON || code == `SOUND10_OFF) && !emptyf) fUFO <= False;
`endif
      endseq
   endseq;

   mkAutoFSM(main);

   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 Data_t sdout();
      return dout;
   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

`ifdef FSM0
endmodule: mkSoundFSM0
`elsif FSM1
endmodule: mkSoundFSM1
`elsif FSM2
endmodule: mkSoundFSM2
`elsif FSM3
endmodule: mkSoundFSM3
`endif

これをverilogに合成するには、FSM0であれば、

bsc -verilog -D FSM0 SoundFSM.bsv

のようにマクロ定義により行います。


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

posted by sakurai on September 5, 2022 #501

サウンドデータ修正手順

ゲームサウンドの改良のため、サウンドROMの内容を修正します。サウンドROMの作成法は記載していなかったので、その備忘のためでもあります。以下に手順の大略を示します。

  1. オリジナルサウンドを(http://www.classicgaming.cc/classics/space-invaders/sounds)で入手する。Player shootingとKilled Space Invaderのサウンドの表示が誤って入れ替わっているようです。
  2. Windows上のサウンドツールである Audacityにより適宜音量を調整する。そのままだと歩行音とのバランスが悪く歩行音が聞こえにくいため、歩行音以外の音量を下げて平均化する。ただし後述のように、頻繁に鳴る音でなければ多少大き目でも良い。
  3. waveを8bitに圧縮変換する。コマンドは

ffmpeg -i input.wav -ac 1 -ar 11025 -acodec pcm_u8 -fflags +bitexact -flags:v +bitexact -flags:a +bitexact output.wav

ここで、拡張子に.wavが必要。

  1. サウンド毎のwaveバイナリが作成されたので、チャネル毎にwaveをcatでまとめ、ROMイメージのバイナリファイルを作成する。
     Ch.0 --- Code 1, 2, 9
     Ch.1 --- Code 3
     Ch.2 --- Code 4, 5, 6, 7
     Ch.3 --- Code 8, 10
  2. wcによりエントリアドレスを調べておく。エントリアドレスは、直前のROM累積サイズ+16。
  3. make_coe(以下)コマンドにより、バイナリからVivadoの読めるCOEファイルに変換する。

export input=\$1.bin
export output=\$1.coe
echo 'memory_initialization_radix=16;' > \$output
echo -n 'memory_initialization_vector=' >> \$output
od -An -t x1 -v < \$1.bin >> \$output
echo ';' >> $output

修正点

  • Code2 (自機爆発音) ---- 若干延長してフェードアウトエンベロープを加えた。

図%%.1
図501.1 修正後の自機爆発音

  • Code1 (自機弾発射音), 3 (インベーダ爆発音), 9 (自機増加音) ---- 歩行音とのバランスで音量を下げすぎたため、音量を若干上げた。

頻繁に鳴る音、例えばインベーダ破壊音が大きめだとうるさく感じますが、まれな音、例えば自機増加音や自機破壊音は大きめでも問題ないことがわかりました。ゲームバランスと言われますが、音量にもバランスがあるようです。

修正後のサウンドROM構造

表501.1 ROM構成表
Channel Code Sound Start Size [bytes] Entry=Start+16
自機音
チャネル(#0)
1 自機弾発射音 0 3,422 0+16
2 自機爆発音 3,422 16,150 3,422+16
9 自機増加音 16,150 5,500 16,150+16
合計 [bytes] (32KB ROM使用率) 21,650 (66%)
インベーダ音
チャネル(#1)
3 インベーダ爆発音 0 4,622 0+16
合計 [bytes] (32KB ROM使用率) 4,622 (14%)
インベーダ音
チャネル(#2)
4 インベーダ歩行音 1 0 1,266 0+16
5 インベーダ歩行音 2 1,266 1,570 1,266+16
6 インベーダ歩行音 3 2,836 1,570 2,836+16
7 インベーダ歩行音 4 4,406 2,180 4,406+16
合計 [bytes] (32KB ROM使用率) 6,586 (20%)
UFO音
チャネル(#3)
8 UFO爆発音 0 25,968 0+16
10 UFO飛行音 25,968 1,846 25,968+16
合計 [bytes] (32KB ROM使用率) 27,814 (85%)

ROMのエントリポイントは上記のとおりStartアドレスから+16となります。


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


ページ: