Posts Issued in September, 2025

posted by sakurai on September 4, 2025 #1015

ChatGPTのコードがあまり良くないので、次の考え方で整理してコードを修正します。

元の処理はbusyを内部演奏状態に使用すると同時に外部にbusyを見せていたので、プリエンプティブではなかった。この2つを分離し、playを内部演奏状態とし、外部には受取りの1サイクルのみbusyを立てるようにしてください。

以下に変更したコードを示します。

package Consumer4;

interface Consumer4Ifc;
   method Action consume(UInt#(4) x);
endinterface

module mkConsumer4 (Consumer4Ifc);

   Reg#(UInt#(4)) cnt      <- mkReg(0);     // 0-14 を数える 4bit で十分
   Reg#(Bool)     busy     <- mkReg(False); // ハンドシェーク停止用 1 サイクル
   Reg#(Bool)     play     <- mkReg(False); // 15 サイクル演奏フラグ
   Reg#(UInt#(4)) lastVal  <- mkRegU;       // 取り込んだコード
   
   /* 15 サイクル演奏タイマ */
   rule timer (play);
      if (cnt == 14) begin
         cnt  <= 0;
         play <= False;          // 演奏終了
      end
      else
         cnt <= cnt + 1;
   endrule
   
   /* busy は 1 サイクル後に自動クリア */
   rule clear_busy (busy);
      busy <= False;
   endrule

  /* 受信メソッド
      ガード if (!busy) を付けることで
      can_fire = !busy が自動生成される */
   method Action consume(UInt#(4) x) if (!busy);
      lastVal <= x;                   // EN=1 サイクルでラッチ
      $display("%0t: consume = %h", $time, x);
      busy <= True;              // 1 サイクルだけbusyを上げる
      play <= True;              // 演奏開始
      cnt  <= 0;
   endmethod
endmodule
endpackage

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

posted by sakurai on September 3, 2025 #1014

前の記事で、ChatGPTのコード中に、busyなら受け付けないとあったのが気になったので確認します。

start_playに疑義があります。忙しかったらやらないのではプリエンプションを実装とはいえないのでは。

「ready は常に True」はハンドシェークの意味を成しません。

Producer が validをパルスで出したい場合、同サイクルにreadyが1でなければ 転送が成立しません。 そこで Consumer 側は「受け取った直後だけready=0に落とす」ことで 1 サイクル確実にハンドシェークを区切ります。

修正版 mkConsumer4_Preemptive

module mkConsumer4_Preemptive (ConsumerRaw);
   Reg#(Bool) busy       <- mkReg(False);   // 今まさに演奏中か
   Reg#(Bool) hasNewCmd  <- mkReg(False);   // 未処理コマンド保持
   Reg#(UInt#(4)) last   <- mkRegU;
   Reg#(Bool) ready_reg  <- mkReg(True);    // 出力する ready

   /* Producer から毎サイクル呼ばれるメソッド
      - always_ready/always_enabled: 条件文を書かない */
   (* always_ready, always_enabled *)
   method Action soundPreempt(UInt#(4) d);
      // 受信したサイクルで ready = 0 に落とす
      ready_reg  <= False;
      last       <= d;          // 最新コードを保持
      hasNewCmd  <= True;
   endmethod

   /* ready 出力メソッドは最後に置く */
   method Bool rdy_o();
      return ready_reg;
   endmethod

   /* 受信直後の次サイクルで ready を 1 に戻す */
   rule raise_ready (!ready_reg);
      ready_reg <= True;
   endrule

   /* 新コマンドがあればいつでも演奏を開始(busy を更新) */
   rule start_play (hasNewCmd);
      $display("%0t play %h", $time, last);
      busy       <= True;
      hasNewCmd  <= False;
   endrule

   /* ここでは 1 サイクルで演奏完了とする簡易例 */
   rule finish_play (busy && ready_reg);
      busy <= False;
   endrule
endmodule
 

挙動

サイクル Producer.valid Consumer.ready 動作
t 1 1 転送成立 → Consumer.ready を 0 へ
t+1 0 0→1 ready が復活。
busy が上がり演奏開始
busy中 1 1 新 valid が来れば上書きし即プリエンプト

このようにreadyを 1 サイクルだけ落とすことで

  • ハンドシェーク単位は厳密に 1 サイクル
  • それでもbusy中にいつでも新コードを受理し、即プリエンプション

が両立します。


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

posted by sakurai on September 2, 2025 #1013

前の記事でChatGPTの回答があたかもalways_ready, always_enable疑似命令を付加すればalways_ready, always_enableとなるように聞こえたので、確認します。

このように、属性だけ変えても中身を正しく書かないとエラーになるのでは?

always_ready / always_enabled を使うときの注意

  1. 属性の意味

    • always_ready : can_fire が常に True と解釈される
    • always_enabled: ルールが発火したら必ずメソッドも実行される
  2. メソッド本体にはガードを書かない

    • if や case で条件分岐するとコンパイルエラーになる
    • 無条件でレジスタへ書き込むだけにする
(* always_ready, always_enabled *)
method Action soundPreempt(Code_t d);
   lastCode  <= d;        // 無条件で最新コードを保持
   hasNewCmd <= True;     // 新コマンド到着フラグ
endmethod
 
  1. 忙しいかどうかの判定は別ルールで行う
rule start_play (!busy && hasNewCmd);
   current    <= lastCode;
   busy       <= True;
   hasNewCmd  <= False;
endrule
  1. 上位モジュールはガード無しで毎サイクル呼んでよい
rule send_cmd;
   sndFsm.soundPreempt(latestCode);   // guard を付けない
endrule

これで busy の間に新しいコードが来ても、旧コードは上書きされて演奏されない。プリエンプションの目的を達成できます。


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