Posts Tagged with "FPGA"

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

BSVの設計トライアル

posted by sakurai on April 7, 2020 #234

スペースインベーダーのモジュールの入れ替え

過去にスペースインベーダーをRTLで開発しました。今回はBSVのトライアルとして既存のverilogモジュールの入れ替えを実施します。小さめのFSMであるサウンドFSMを入れ替えますが、合わせて仕様も高級なものにしようと思います。RTLで記述したときは、サウンドチャネルは1個で、優先度判定を行いサウンド割込みを行いました。今回は、サウンドチャネルを複数持って、サウンド重畳を行うことを考えます。そのため、複数の音を出し音量を平均化するような、サウンドミックス回路を新設します。

サウンドチャネルの決定

さて、音は以下の10種類ですが、同時発生を考えて、自機音、インベーダ音1, 2、UFO音の4チャネルとします。また、サウンド演奏中に割込みが入るため、プリエンプティブに設計する必要があります。

表234.1 音データとサウンドチャネルの関係
サウンドの説明 CODE番号 サウンドチャネル バイト数 先頭オフセット+16
ON OFF
自機弾発射音 1 自機音チャネル(#0) 4,318 0 + 16
自機爆発音 2 8,776 4,318 + 16
自機増加音 9 5,500 13,094 + 16
インベーダ爆発音 3 インベーダ音チャネル(#1) 4,622 0 + 16
インベーダ歩行音1 4 インベーダ音チャネル(#2) 1,266 0 + 16
インベーダ歩行音2 5 1,570 1,266 + 16
インベーダ歩行音3 6 1,570 2,836 + 16
インベーダ歩行音4 7 2,180 4,406 + 16
UFO爆発音 8 UFO音チャネル(#3) 25,968 0 + 16
UFO飛行音 10 16 + 10 1,846 25,968 + 16

変更前後のブロック図

現状のRTL設計でのブロック図をVivadoで表示したものを図234.1に示します。

図%%.1
図234.1 現サウンドシステムブロック図

新仕様のブロック図は図234.2のようになります。色付けの部分が新規設計モジュールです。

図%%.2
図234.2 新サウンドシステムブロック図

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

Emacs Verilog Mode

posted by sakurai on April 6, 2020 #233

ルール

大変便利なemacs verilog modeですが、最上位でうまく行かなかったので調査したところ、以下のようなことが分かりました。

中間階層で、下位モジュールインタフェースをパススルーして上位に上げる場合のルールは、

  • /*AUTOINPUT*/及び/*AUTOOUTPUT*/

最上位(テストベンチのように)で、信号がその階層内で留まる場合のルールは、

  • /*AUTOREGINPUT*/及び/*AUTOWIRE*/

と記述します。

ちなみに、 /*AUTOINPUT*//*AUTOREGINPUT*/が同時に存在する場合は /*AUTOINPUT*/が勝ち、/*AUTOOUTPUT*//*AUTOWIRE*/が同時に存在する場合は、/*AUTOWIRE*/が勝つようです。

実施結果

まず中間階層に相当する、原始のverilog fileを示します。下位モジュールの信号を上位に上げる場合なので、/*AUTOINPUT*/及び/*AUTOOUTPUT*/と記述し、さらにインターフェース部分には/*AUTOARG*/と記述します。

図%%.1
図233.1 中間階層原始ファイル

これに対してC-c C-aを実行すると、以下のように補完されます。

図%%.2
図233.2 中間階層補完後ファイル

次にテストベンチ相当の、原始のverilog fileを示します。下位モジュールからの信号はこの階層内で留まるため、/*AUTOREGINPUT*/及び/*AUTOWIRE*/と記述します。

図%%.3
図233.3 最上位原始ファイル

これに対してC-c C-aを実行すると、以下のように補完されます。

図%%.4
図233.4 最上位補完後ファイル

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

BSV(Bluespec SystemVerilog) (5)

posted by sakurai on April 3, 2020 #232

BSVによるテストベンチの記述

続いてテストベンチもBSVで記述していきます。シーケンシャルな記述が必要なので、自動FSMを利用します。以下にBSV(ファイル名Tb.bsv)を示します。テストベンチにクロックとリセットの記述が無いことに注意してください。

seqブロックの内部では、ソフトウェアと似ており、一行1クロックで順番に実行されます。内部的にはbscにより自動ステートマシンが生成され、一行ずつ順番に実行されます。

テストベンチファイル:Tb.bsv

import FibOne::*;
import StmtFSM::*;
    
(* synthesize *)
module mkTb();
    
   Fib_ifc fibo <- mkFibOne();
   int x = fibo.read();

   Stmt test = seq
      while (x < 10000)
         $display("%0d", x);
      $display("%0d", x);
      $finish;
   endseq;
    
   mkAutoFSM(test);
    
endmodule

コンパイルとBluesimシミュレーションの実行

このテストベンチをコンパイルし、シミュレーションを実行します。以下は下位モジュールが変更されていた場合ですが、自動的に下位モジュールもコンパイルされます。

$ **bsc -sim -u Tb.bsv**
checking package dependencies
compiling ./FibOne.bsv
code generation for mkFibOne starts
Elaborated module file created: mkFibOne.ba
compiling Tb.bsv
code generation for mkTb starts
Elaborated module file created: mkTb.ba
All packages are up to date.

bluesimシミュレーションを実行します。

$ bsc -sim -e mkTb -o mkTb.exec
Bluesim object created: mkTb.{h,o}
Bluesim object created: mkFibOne.{h,o}
Bluesim object created: model_mkTb.{h,o}
Simulation shared library created: mkTb.exec.so
Simulation executable created: mkTb.exec
$ ./mkTb.exec
          1
          1
          2
          3
          5
          8
         13
         21
         34
         55
         89
        144
        233
        377
        610
        987
       1597
       2584
       4181
       6765
      10946

Verilogテストベンチと同様の結果が得られました。

Verilogシミュレーションの実行

確認のため、verilogシミュレーションを実施してみます。BSVではクロックとリセットは書かなくてもシミュレーション時に補完されるのですが、verilogシミュレーション時には無いため、マニュアルで追加する必要があります。それらの記述は前稿のテストベンチを参考にします。

以下にテストベンチからverilogコードを生成し(ファイル名mkTb.v)ます。

$ bsc -verilog Tb.bsv
Verilog file created: mkTb.v

そのファイルにinclude文を追加します。場所は、initial begein endの間です。includeするファイルにはクロックとリセットの動作をverilogで記述します。その理由は、Bluesimの場合は外部から自動的にクロックとリセットが印加されるのに比べて、verilog simでは陽に与える必要があるからです。

テストベンチファイル:mkTb.v(部分)

    initial
    begin
      running = 1'h0;
      start_reg = 1'h0;
      start_reg_1 = 1'h0;
      state_can_overlap = 1'h0;
      state_fired = 1'h0;
      state_mkFSMstate = 3'h2;
      `include "initial.v"    <----ここ
    end

initial.vの中身を示します。クロックとリセットの記述がされています。

インクルードファイル:initial.v

       force RST_N = 1'b0;
       #30;
       force RST_N = 1'b1;
    end
       
    initial begin
       force CLK = 1'b0;
       forever begin
       #5 force CLK = ~CLK;
       end

修正したテストベンチmkTb.vとモジュールを合わせてverilogシミュレーションを行います。

$ bsc -verilog FibOne.bsv
Verilog file created: mkFibOne.v
$ iverilog mkTb.v mkFibOne.v -o mkFibOne.exev
mkTb.v:220: tgt-vvp sorry: procedural continuous assignments are not yet fully supported. The RHS of this assignment will only be evaluated once, at the time the assignment statement is executed.
$ ./mkFibOne.exev
          1
          1
          2
          3
          5
          8
         13
         21
         34
         55
         89
        144
        233
        377
        610
        987
       1597
       2584
       4181
       6765
      10946

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

BSV(Bluespec SystemVerilog) (4)

posted by sakurai on April 1, 2020 #231

モジュールインタフェースの引出し

Vivadoにより合成をかける場合に、前稿のモジュールmkFibOneのように出力の信号が無いと、何もしないものとみなされ最適化され、全てのロジックが削除されてしまいます。従って、mkFibOneモジュールに、計算した値を出力するインタフェースを設ける修正を行います。合わせて、テストベンチ記述がモジュール内に混入されていたものを、テストベンチに移します。

BSVではモジュールとインタフェースは明確に分離されています。修正したファイル(ファイル名FibOne.bsv)を示します。前述のように\$displayや\$finishはテストベンチへ移しました。

モジュールファイル:FibOne.bsv

interface Fib_ifc;
   method int read();
endinterface

(* synthesize, always_ready, always_enabled *)
module mkFibOne(Fib_ifc);
   Reg#(int) this_fib <- mkReg(0);
   Reg#(int) next_fib <- mkReg(1);

   rule fib;
      this_fib <= next_fib;
      next_fib <= this_fib + next_fib;  // note that this uses stale this_fib
   endrule: fib

   method int read();
      return this_fib;
   endmethod

endmodule: mkFibOne

ここで、

       Reg#(int) this_fib <- mkReg(0);

これは前稿で2行になっていたレジスタのインスタンシエーションの省略記法で、

    Interface_type identifier <- module_name;

という文法を持ちます。この意味は、「Interface_typeの型を持つインタフェースを提供するモジュールmodule_nameをインスタンス化し、インタフェースのハンドルidentifierを取得する」ということです。

Verilogの生成

BSVプログラムに対して、bscによりverilog生成を実行すれば、

$ bsc -verilog FibOne.bsv
Verilog file created: mkFibOne.v

合成されたverilog(ファイル名mkFibOne.v)は以下のようになります。

モジュールファイル:mkFibOne.v

//
// Generated by Bluespec Compiler (build 38534dc)
//
// On Wed Mar 25 09:12:14 JST 2020
//
//
// Ports:
// Name                         I/O  size props
// read                           O    32 reg
// CLK                            I     1 clock
// RST_N                          I     1 reset
//
// No combinational paths from inputs to outputs
//
//

`ifdef BSV_ASSIGNMENT_DELAY
`else
  `define BSV_ASSIGNMENT_DELAY
`endif

`ifdef BSV_POSITIVE_RESET
  `define BSV_RESET_VALUE 1'b1
  `define BSV_RESET_EDGE posedge
`else
  `define BSV_RESET_VALUE 1'b0
  `define BSV_RESET_EDGE negedge
`endif

module mkFibOne(CLK,
        RST_N,

        read);
  input  CLK;
  input  RST_N;

  // value method read
  output [31 : 0] read;

  // signals for module outputs
  wire [31 : 0] read;

  // register next_fib
  reg [31 : 0] next_fib;
  wire [31 : 0] next_fib$D_IN;
  wire next_fib$EN;

  // register this_fib
  reg [31 : 0] this_fib;
  wire [31 : 0] this_fib$D_IN;
  wire this_fib$EN;

  // value method read
  assign read = this_fib ;

  // register next_fib
  assign next_fib$D_IN = this_fib + next_fib ;
  assign next_fib$EN = 1'd1 ;

  // register this_fib
  assign this_fib$D_IN = next_fib ;
  assign this_fib$EN = 1'd1 ;

  // handling of inlined registers

  always@(posedge CLK)
  begin
    if (RST_N == `BSV_RESET_VALUE)
      begin
        next_fib <= `BSV_ASSIGNMENT_DELAY 32'd1;
    this_fib <= `BSV_ASSIGNMENT_DELAY 32'd0;
      end
    else
      begin
        if (next_fib$EN) next_fib <= `BSV_ASSIGNMENT_DELAY next_fib$D_IN;
    if (this_fib$EN) this_fib <= `BSV_ASSIGNMENT_DELAY this_fib$D_IN;
      end
  end

  // synopsys translate_off
  `ifdef BSV_NO_INITIAL_BLOCKS
  `else // not BSV_NO_INITIAL_BLOCKS
  initial
  begin
    next_fib = 32'hAAAAAAAA;
    this_fib = 32'hAAAAAAAA;
  end
  `endif // BSV_NO_INITIAL_BLOCKS
  // synopsys translate_on
endmodule  // mkFibOne

テストベンチ

修正したVerilogテストベンチtbmkFibOne.vを以下に示します。前稿ではクロックとリセットを下位モジュールに供給するだけだったのを、データ表示とシミュレーションの停止機能をモジュールから移動しています。

テストベンチファイル:tbmkFibOne.v

`timescale 1ns/1ps
 
module tb_mkFibOne;
   /*AUTOREGINPUT*/
   // Beginning of automatic reg inputs (for undeclared instantiated-module inputs)
   reg          CLK;            // To mkFibOne of mkFibOne.v
   reg          RST_N;          // To mkFibOne of mkFibOne.v
   // End of automatics
   /*AUTOWIRE*/
   // Beginning of automatic wires (for undeclared instantiated-module outputs)
   wire [31:0]      read;           // From mkFibOne of mkFibOne.v
   // End of automatics
   mkFibOne mkFibOne (/*AUTOINST*/
              // Outputs
              .read     (read[31:0]),
              // Inputs
              .CLK      (CLK),
              .RST_N        (RST_N));
   
   initial begin
      RST_N = 1'b0;
      #30;
      RST_N = 1'b1;
      forever begin
       if (CLK) $display("%0d", read);
       if (read > 10000) $finish;
       #10;
      end
   end
   
   initial begin
      CLK = 1'b0;
      forever begin
     #5 CLK = ~CLK;
      end
   end
   
   initial begin
      $dumpfile("tbmkFibOne.vcd");
      $dumpvars(0,mkFibOne);
   end
   
endmodule 

Verilogシミュレーション

準備ができたので、iverilogによるverilogシミュレーションを実行します。

$ iverilog tbmkFibOne.v mkFibOne.v -o mkFibOne.exev
$ ./mkFibOne.exev
VCD info: dumpfile tbmkFibOne.vcd opened for output.
         0
         1
         1
         2
         3
         5
         8
        13
        21
        34
        55
        89
       144
       233
       377
       610
       987
      1597
      2584
      4181
      6765
     10946

前稿と同様の結果になりました。

Vivadoによるシミュレーション

VivadoのIPインテグレータにより、生成されたverilogを取り込み、IPインテグレータ上で最上位のテストベンチを作成します。先に作成したテストベンチ(ファイル名tbmkFibOne.v)はシミュレーション用で合成できないため、IPインテグレータで上位を作成します。図の左からZynqのPS部、クロックリセット生成、対象回路となっています。

図%%.1
図231.1 IP Integratorの回路図

vivado上でbehaviorシミュレーションを実施した結果です。iverilogの結果と同一になっています。

図%%.2
図231.2 Vivadoによる波形

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

BSV(Bluespec SystemVerilog) (3)

posted by sakurai on March 31, 2020 #230

BSV(Bluespec SystemVerilog)によるシミュレーション

bscとは、Bluespecが最近オープンソース化したコンパイラです。詳しくは、ここを見てください。

いろいろと調整した結果、bscがインストールできました。早速、以下のようなフィボナッチモジュール(ファイル名FibOne.bsv)をコンパイルしてみます。これは、フィボナッチ数列を発生するモジュールで、前の値に次の値を加算することを繰り返すものです。

モジュールファイル:FibOne.bsv

(* synthesize *)
module mkFibOne();
   // register containing the current Fibonacci value
   Reg#(int) this_fib();              // interface instantiation
   mkReg#(0) this_fib_inst(this_fib); // module instantiation
   // register containing the next Fibonacci value
   Reg#(int) next_fib();
   mkReg#(1) next_fib_inst(next_fib);

   rule fib;  // predicate condition always true, so omitted
      this_fib <= next_fib;
      next_fib <= this_fib + next_fib;  // note that this uses stale this_fib
      $display("%0d", this_fib);
      if ( this_fib > 10000 ) $finish(0) ;
  endrule: fib
endmodule: mkFibOne

モジュール内にテストベンチのような\$displayや$finishの記述があるので、これだけでテストが可能です(が、モジュール設計としては良くないので、後で外します)。

BSVプログラムの説明

以下の部分は、現在の値this_fibと次の値next_fibを保持するレジスタのインスタンスです。

   // register containing the current Fibonacci value
   Reg#(int) this_fib();              // interface instantiation
   mkReg#(0) this_fib_inst(this_fib); // module instantiation
   // register containing the next Fibonacci value
   Reg#(int) next_fib();
   mkReg#(1) next_fib_inst(next_fib);

次のように、ruleブロックにアルゴリズム計算ルールを記述します。

      this_fib <= next_fib;
      next_fib <= this_fib + next_fib;  // note that this uses stale this_fib

コメントにも書いているように、this_fibとnext_fibは同時に変更されるので、それぞれ、直前の値を読み込み、同時に値を更新します。

Bluesimによるシミュレーション

コンパイル及びシミュレーションモデル生成(リンク)の2段階で行います。太字が入力部分です。

$ bsc -sim  FibOne.bsv
Elaborated module file created: mkFibOne.ba
$ bsc -sim -e mkFibOne -o mkFibOne.exec
Bluesim object created: mkFibOne.{h,o}
Bluesim object created: model_mkFibOne.{h,o}
Simulation shared library created: mkFibOne.exec.so
Simulation executable created: mkFibOne.exec

mkFibOne.execというbluesimの実行ファイルが生成されたので起動します。

$ ./mkFibOne.exec
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946

Verlogの生成

次に、確認のためにverilogシミュレーションを実行します。まずbscにより、モジュールを合成可能なVerilogコードにコンパイルします。

$  bsc -verilog FibOne.bsv
Verilog file created: mkFibOne.v

生成されたファイル名はモジュール名+".v"(mkFibOne.v)となります。

モジュールファイル:mkFibOne.v

//
// Generated by Bluespec Compiler (build 38534dc)
//
// On Mon Mar 23 06:33:47 JST 2020
//
//
// Ports:
// Name                         I/O  size props
// CLK                            I     1 clock
// RST_N                          I     1 reset
//
// No combinational paths from inputs to outputs
//
//

`ifdef BSV_ASSIGNMENT_DELAY
`else
   `define BSV_ASSIGNMENT_DELAY
`endif

`ifdef BSV_POSITIVE_RESET
   `define BSV_RESET_VALUE 1'b1
   `define BSV_RESET_EDGE posedge
`else
   `define BSV_RESET_VALUE 1'b0
   `define BSV_RESET_EDGE negedge
`endif

module mkFibOne(CLK,
                RST_N);
   input  CLK;
   input  RST_N;

   // register next_fib_inst
   reg [31 : 0] next_fib_inst;
   wire [31 : 0] next_fib_inst$D_IN;
   wire next_fib_inst$EN;

   // register this_fib_inst
   reg [31 : 0] this_fib_inst;
   wire [31 : 0] this_fib_inst$D_IN;
   wire this_fib_inst$EN;

   // register next_fib_inst
   assign next_fib_inst$D_IN = this_fib_inst + next_fib_inst ;
   assign next_fib_inst$EN = 1'd1 ;

   // register this_fib_inst
   assign this_fib_inst$D_IN = next_fib_inst ;
   assign this_fib_inst$EN = 1'd1 ;

   // handling of inlined registers

   always@(posedge CLK)
      begin
         if (RST_N == `BSV_RESET_VALUE)
            begin
               next_fib_inst <= `BSV_ASSIGNMENT_DELAY 32'd1;
               this_fib_inst <= `BSV_ASSIGNMENT_DELAY 32'd0;
            end
         else
            begin
               if (next_fib_inst$EN)
                  next_fib_inst <= `BSV_ASSIGNMENT_DELAY next_fib_inst$D_IN;
               if (this_fib_inst$EN)
                  this_fib_inst <= `BSV_ASSIGNMENT_DELAY this_fib_inst$D_IN;
            end
      end

   // synopsys translate_off
   `ifdef BSV_NO_INITIAL_BLOCKS
   `else // not BSV_NO_INITIAL_BLOCKS
   initial
   begin
      next_fib_inst = 32'hAAAAAAAA;
      this_fib_inst = 32'hAAAAAAAA;
   end
   `endif // BSV_NO_INITIAL_BLOCKS
   // synopsys translate_on

   // handling of system tasks

   // synopsys translate_off
   always@(negedge CLK)
   begin
      #0;
      if (RST_N != `BSV_RESET_VALUE) $display("%0d", $signed(this_fib_inst));
      if (RST_N != `BSV_RESET_VALUE)
      if ((this_fib_inst ^ 32'h80000000) > 32'h80002710) $finish(32'd0);
   end
   // synopsys translate_on
endmodule  // mkFibOne

テストベンチの作成

bluesimは暗黙のクロックやリセットが動作するため、テストベンチ無しでもシミュレーションが実行できました。一方verilogではそのような機能は無いので以下のようなテストベンチ(ファイル名tbmkFibOne.v)を用意します。

テストベンチ中の/AUTO〇〇/という記述は、emacsのverilog modeによるインスタンスやポートの自動生成の機能です。C-c C-aにより、面倒なポートリストやインスタンス部分が自動生成できます。テストベンチでは、モジュールへの入力用に/AUTOREGINPUT/と、モジュールからの出力用に/AUTOWIRE/を指定しておきます。

テストベンチファイル:tbmkFibOne.v

`timescale 1ns/1ps
 
module tb_mkFibOne;
   /*AUTOREGINPUT*/
   // Beginning of automatic reg inputs (for undeclared instantiated-module inputs)
   reg          CLK;            // To mkFibOne of mkFibOne.v
   reg          RST_N;          // To mkFibOne of mkFibOne.v
   // End of automatics
   /*AUTOWIRE*/
   mkFibOne mkFibOne (/*AUTOINST*/
              // Inputs
              .CLK      (CLK),
              .RST_N        (RST_N));
   
   initial begin
      RST_N = 1'b0;
      #30;
      RST_N = 1'b1;
   end
   
   initial begin
      CLK = 1'b0;
      forever begin
     #5;
     CLK = ~CLK;
      end
   end
   
   initial begin
      $dumpfile("tbmkFibOne.vcd");
      $dumpvars(0,mkFibOne);
   end
   
endmodule 

Verilogシミュレーションの実行

iverilogにより実行ファイルmkFibOne.exevを生成し、シミュレーションを実行すると、同じ結果となりました。

$ iverilog tbmkFibOne.v mkFibOne.v -o mkFibOne.exev
$ ./mkFibOne.exev 
VCD info: dumpfile tbmkFibOne.vcd opened for output.
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946

波形ビュワーであるgtkwaveを起動します。verilogシミュレーションで生成したVCDファイルを指定します。

$ gtkwave -f tbmkFibOne.vcd
GTKWave Analyzer v3.3.111 (w)1999-2020 BSI

[0] start time.
[520000] end time.

verilogシミュレーションの波形をgtkwaveで表示します。

図%%.1
図230.1 GTKWaveによる波形

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

BSV(Bluespec SystemVerilog) (2)

posted by sakurai on March 30, 2020 #229

BSV(Bluespec SystemVerilog)の解説

詳細はこちら(魚拓)

図229.1のような、3つのプロセスがレジスタx, yを更新する回路を設計することを考えます。この回路には、次のルール1から3までの、3つの仕様があります。

  • ルール1: cond2がアサートされると、プロセス2が起動しy--します。
  • ルール2: プロセス2が起動しないとき、かつcond1がアサートされると、プロセス1が起動しy++し、かつ同時にx--します。
  • ルール3: プロセス1が起動しないとき、かつcond0がアサートされると、プロセス0が起動しx++します。

補足するとルール2の場合、プロセス1が起動するときy++, x--は同時に起きるアトミックな演算です。つまり、プロセス1によるx--のみ、またはy++のみの実行は仕様違反となります。

図%%.1
図229.1 3つの並行プロセスにより書き換えられる2つのレジスタx, y
Verilogではステートに注目して記述します。
    always @ (posedge CLK) begin
        if (cond2)
            y <= y - 1;
        else if (cond1) begin
            y <= y + 1;
            x <= x - 1;
        end else if (cond0)
            x <= x + 1;
    end

このように記述したくなりますが、これだとcond2のアサート時にはxは書き換えられません。cond2アサートかつcond0アサートの場合は、ルール1とルール3が有効となるため、y--かつx++となるべきです。バグの原因は、優先順位(競合関係)が直接無いプロセス2と0に優先順位を持ち込んだことと考えて、alwaysの中を以下のように修正します。

        if (cond2)
            y <= y - 1;
        else if (cond1) begin
            y <= y + 1;

        if (cond1) begin
            x <= x - 1;
        end else if (cond0)
            x <= x + 1;

すると、今度は、プロセス1がプロセス0に勝ち、かつプロセス2に負けた時に、アトミック性が成立しません。ルール2が半分だけ実行されてしまいます。実はプロセス2と1は無関係ではなく、プロセス1を経由して関係が有ったのです。

ひとつの考え方としては、ハードでは並行に条件判定をするので、if else if 等とせずに、3本のif文を並べます。さらに外部信号cond1,2,3,でレジスタ更新を行うのではなく、プロセスの起動信号に注目します。具体的には、プロセス2の起動はcond2であるから、

    cond2

がプロセス2の起動条件です。プロセス1の起動は、そうではなく(!cond2)、かつ、cond1であるから、

    !cond2 && cond1

がプロセス1の起動条件です。プロセスの起動は、そうではなく(!(!cond2 && cond1))、かつ、cond0であるから、

    !(!cond2 && cond1) && cond0

がプロセス0の起動条件です。これらより、以下の記述が得られます。

        if (cond2)
            y <= y - 1;

        if (!cond2 && cond1) begin
            y <= y + 1;
            x <= x - 1;
        end 

        if (!(!cond2 && cond1) && cond0))
            x <= x + 1;

verilogでは優先順位を信号の条件で表すことで、複雑になっています。後からプロセスの優先順位が変わると、ハードコードされた条件文の修正が大変なことになります。

一方、VSBでは、レジスタの更新ルールがそのまま記述できます。

    rule proc0 (cond0);
        x <= x + 1;
    endrule

    rule proc1 (cond1);
        y <= y + 1;
        x <= x - 1;
    endrule

    rule proc2 (cond2);
        y <= y - 1;
    endrule
    (* descending_urgency = “proc2, proc1, proc0” *)

プロセスの優先順位と信号の条件が分離されているため、プロセスの優先順位が変わっても、コメントの修正のみでコードの修正はありません。

それでは、bscにより合成したverilogコードではどうなっているかというと、

    assign x$EN = WILL_FIRE_RL_proc0 || WILL_FIRE_RL_proc1 ;
    assign y$EN = WILL_FIRE_RL_proc1 || cond2 ;

    assign WILL_FIRE_RL_proc0 = cond0 && !WILL_FIRE_RL_proc1 ;
    assign WILL_FIRE_RL_proc1 = cond1 && !cond2 ;

レジスタxが変更されるのはx$ENがtrueの時、つまり、

    (cond0 && !(cond1 && !cond2)) || (cond1 && !cond2)

の時であり、レジスタyが変更されるのはy$ENがtrueの時、つまり、

    (cond1 && !cond2) || cond2

前述のverilogのifの中の条件と等価であることが分かります。

プロセスが3つ程度だとそれほど有難みがわかりませんが、これにプロセス3を一つ追加して、4つの優先順位を付けると、図229.2の赤字の修正のように、非常に複雑になります。

図%%.2
図229.2 4つの並行プロセスにより書き換えられる2つのレジスタx, y

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

BSV(Bluespec SystemVerilog)

posted by sakurai on March 12, 2020 #220

BSV(Bluespec SystemVerilog)の注意点

Verilogコードで以下のような回路を設計することを考えます。

reg [31:0] burst_count, dest_burst_size ;
reg        burst_in_progress ;
...
always @(posedge clk)    // burst_in_progress_stop 
begin
   if (burst_in_progress && (burst_count==dest_burst_size))
       burst_in_progress <= False;
   ...
end 

always @(posedge clk)    // burst_count;
begin  
   burst_count <= burst_in_progress ? burst_count+1 : 0;
   ...
end

2個の同期FFグループがあり、最初のFFでは、バースト転送中信号を作成しています。初期状態が不定ですが、なんらかの信号によりburst_in_progressがtrue(=バースト転送中)を示した後は、基本的にバーストを継続します。バーストカウントが規定されたサイズだけ転送したら、バースト転送中をfalseにしてバースト転送を停止させます。

次のFFグループはバーストカウントを計算するFFであり、バースト転送中がtrueの時は1クロック毎に+1だけカウントし、バースト転送中がfalseになったらカウントを0にします。

図220.1に、このverilogコードを図示します。

図%%.1
図220.1 verilogコードの図化
FF間でお互いの情報を使用していますが、クロック同期であり発振することのない、問題の無い回路です。

このコードはBSVでは以下のようになりそうです。

Reg#(Data_t) dest_burst_size <- mkReg (32'h0) ;
Reg#(Data_t) burst_count     <- mkReg (0) ;
Reg#(Bool) burst_in_progress  <-  mkReg (False) ;

rule burst_in_progress_stop (burst_in_progress && (burst_count==dest_burst_size));
   burst_in_progress <= False;
   ...
endrule

rule burst_counting ;
   burst_count <= burst_in_progress ? burst_count + 1 : 0;
   ...
endrule

ところが、これは最初のruleにおいてburst_countがreadされ、burst_in_progressがwriteされます。一方、2番目のruleにおいて、burst_in_progressがreadされ、burst_countがwriteされます。ruleをスキャンする順番により競合が起きます。

これは、ルールが同時に発火するのではないため、順番によって結果が異なるためです。これを避けるには、一つのruleの中に入れれば良いとのことです。このようにすれば変更の同時性が保証されるため、正しく直前のデータを参照することになります。

Reg#(Data_t) dest_burst_size <- mkReg (32'h0) ;
Reg#(Data_t) burst_count     <- mkReg (0) ;
Reg#(Bool) burst_in_progress  <- mkReg (False) ;

rule burst_in_progress_stop (burst_in_progress) ;
   burst_in_progress <= burst_count != dest_burst_size ;
   burst_count <= (burst_count != dest_burst_size)  ? burst_count+1 : 0;      
   ...
endrule

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

6809 IPの修正 (2)

posted by sakurai on October 3, 2019 #162

6809 IPは、Verilogステートマシン設計されているため、Vivadoのロジックシミュレータでステート毎に値をチェックして行きました。飛び先である\$FF12を用意されているものの、PCに書き込まれていないことが判りました。とは言え、JMP命令は正しくデコードされ、内部信号のop_JMP(JMP命令であることを示すデコード信号)は正しく出力されています。

図%%.1
図162.1 修正前の6809CPUの動作

ステート遷移を修正し、レジスタ書き込みで終わっている箇所をジャンプステートに修正し、さらに、PCへの書き込みアサートを追加しました。

ステートの遷移は、修正前が、0f,16, 17, 18, 19, 11, 12, (09=次の命令フェッチ)のようになっているのに対して、修正後は0f, 16, 17, 18, 19, 1b, (09=次の命令フェッチ)のように遷移し、かつ、オレンジ色で示したk_write_pc信号がアサートされ、new_pcである\$FF12\$FF12がPCにセットされています。結果として正しく\$FF12にジャンプしています。

図%%.2
図162.2 修正後の6809CPUの動作

修正前は誤ってステートが、\$11(SEQ_GRAL_ALU), \$12(SEQ_GRAL_WBACK)と、レジスタ書き込みのステート遷移となっているのに対して、修正後は\$1b(SEQ_JMP_LOAD_PC)と、JMP命令のステートに遷移しています。

例えばメインフレームのような大型機をどのようにテストしているかと言えば、もちろん単体命令をひとつづつテストすることも行いますが、キャッシュ、仮想記憶、ページングの記憶階層があります。そのためのタイミングによるバグの可能性があるので、ランダム試験を実施します。命令を乱数で発生し、ただし不正命令とはならないように制約をかけ、ソフトシミュレータで正解値を取得します。これを論理シミュレータにかけ、結果値であるレジスタの値やメモリの値が正しいかをチェックします。メモリの値はひとつずつチェックすることなく、領域のチェックサムを取ることで確認します。

さらにこれを発展させ、実機にランダムプログラム生成プログラムを載せて、実行時に正解値を生成しチェックします。一見バグ値どうしを比較してOKとなりそうですが、いろいろと条件を変え、命令をページクロスさせて前半と後半でキャッシュやページのヒットミスを変えることで、演算値が変わることがあり得ます。これはプロセッサがパイプライン動作していることから、バグによってはタイミングによって結果が変わることがありうるためです。

ということで、メインフレーム並みの信頼性を保証するなら、上記のようなテストを実施しなければなりません。


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

6809 IPの修正

posted by sakurai on September 27, 2019 #161

FM=7のハードウェアエミュレーションをFPGAで実行するプロジェクトを遂行していましたが、FM-7のROMイメージもZ80カードI/Fを用いることにより、すんなり抜き出すことができました。

それをVivadoに持ち込むにあたって、COEファイルという形式に変換し、OpenCoresからダウンロードした6809を動かしたのですが、動作しないようです。実機の動作と比較するために、実機にはロジックアナライザを接続しました。

前記事のArduino-Z80I/FカードにはFM-7の内部バスが現れているので、実はArduinoは動かさなくても、ロジックアナライザを接続すれば、メインCPUである6809の動作が把握できます。ロジックアナライザも昔は数百万円したかと思いますが、私の購入したものは17,000円くらいでした。これで32CH 200K 400MSa/sですから、アドレス、データ、主要制御信号を観測するのに十分です。

●Arduinoマイコンが870円(送料別) https://www.banggood.com/RobotDyn-Mega-2560-PRO-Embed-CH340G-ATmega2560-16AU-Development-Module-Board-With-Pin-Headers-For-Arduino-p-1397734.html

●ボード作成が5枚で4.9 USD https://www.fusionpcb.jp/

●コネクタは少々高くて743円(送料別) https://jp.rs-online.com/web/p/pcb-headers/6020498/

●ロジックアナライザは消耗品ではないので高いですが、それでも17,299円 https://www.banggood.com/Hantek-4032L-Logic-Analyzer-32Channels-USB-Oscilloscope-Handheld-2G-Memory-Depth-Osciloscopio-Portat-p-1376105.html

のように比較的気軽に試すことができます。

さて、このような環境でFPGAのシミュレーションを実行したら、おかしな点を見つけました。具体的にはJMP命令が動作しません。著者に連絡を取り、報告したところ、「It doesn't surprise me that a couple (or more) things do not work.」とのことで、自分でなんとかするしかなさそうです。FM-7システムの中で6809をデバッグするのは大変なので、新たに図のような単体テスト環境を作成しました。

図%%.1
図161.1 6809 IP単体試験回路図

6809 CPUに、リセット、クロック、ROMを接続しただけです。RAMはありません。このROMに以下のようなテストプログラムを置き、実行させたところ、

FF00         start  org   \$ff00
           
FF00 8EFF12         ldx   #lab1
           ;    jmp   0,x
FF03 6E00          fcb   \$6e,\$00
           
FF05 8EFF15     lab2  ldx   #lab3
FF08 6E01          jmp   1,x
               
FF0A 8EFF12     lab4  ldx   #lab1
FF0D 6E84          jmp   ,x
FF0F 7EFF0F         jmp *
               
FF12 7EFF05     lab1  jmp   lab2
FF15 12       lab3  nop
FF16 7EFF0A         jmp   lab4
           
FFFE             org   \$fffe
FFFE FF00          fdb   start
           ;
0000             end

最初の\$FF03番地のJMPを、JMPせずに実行してしまいます。JMP命令でもインデックスアドレシングのみ動作しないようです。ちなみに、JMP ,XとJMP 0,Xは動作は同じですが、オペコードは異なります(\$6E,\$00と\$6E,\$84)。が、動作が同じであるため、JMP ,Xと書いてもJMP 0,Xのオペコードがアセンブラから出力されるため、やむなく上記のようにFCBで記述しました。


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

6809の割込み動作 (8)

posted by sakurai on August 23, 2019 #155

IRQ/FIRQが正常動作したので、メインCPUとサブCPUの協調動作を実行してみます。

  1. メインCPU

    • サブCPUのreadyを待つ
    • サブCPUをhaltする
    • 共有メモリにデータを書き込む
    • 共有メモリの先頭のMSB=0で指示とする
    • サブCPUのhaltを解除する
  2. サブCPU

    • リセット後サブCPUの状態はハード的にbusy
    • サブCPUの状態をreadyにする
    • 共有メモリの先頭のMSB==0となるのを待つ
    • サブCPUの状態をbusyにする
    • 共有メモリの内容をデコードする

図%%.1
図155.1 協調動作フローチャート

このフローに基づき、協調動作のシミュレーションを行ったところ、正しく動作しました。


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


ページ: