Posts Tagged with "Design"

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

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” *)

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

プロセスが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で記述しました。


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

FM-7 ROM吸出し器の製作 (5)

posted by sakurai on August 29, 2019 #160

サブシステムのROM吸出しの際に、サブシステムの動作確認をするために、Arduinoからランダムなボックスフィルを実行してみました。いかにもFBASICで描画しているように見えますが、Z80カードのエミュレーションをArduinoがやっています。その様子を図160.1に示します。

図%%.1
図160.1 サブシステムの動作

以下の動作を実行するスケッチを作成しました。

  • \$FD05のMSBが0になるのを待つ
  • \$FD05のMSBを1に(サブシステムをHALT)、LSBを1に(メインシステムをHALTしZ80側に)するーつまり\$FD05に\$81をwrite
  • 共有メモリにLineコマンド及びランダムな座標パラメータを書き込む
  • \$FD05のMSBを0に(サブシステムHALTの解除)、LSBを1に(メインシステムをZ80側のまま)するーつまり\$FD05に\$01をwrite
  • 最初に戻りループ

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

FM-7 ROM吸出し器の製作 (4)

posted by sakurai on August 28, 2019 #159

メインシステムROMの吸出しは非常に単純で、アドレスをセットして、データをリードするだけでしたが、サブシステムのROMはサブシステム側に依頼しないと読み出せません。従って、前に述べたように、メンテナンスコマンド(いわゆるYAMAUCHIコマンド$\dagger$)を用いてブロックコピーし、分割読み出しすることになります。これは、サブシステムのROMが10,240バイトもあるのに比べて、窓である共有メモリがわずか128バイトしかなく、コマンドにいくらか使用するため、データ転送用の窓を64バイトとしているためです。そのため、この転送を160回繰り返します。64バイト毎に16進ダンプを行います。

FM-7/8 Subシステムメンテナンスコマンドのページ(魚拓)を参考にスケッチを作成します。作成したスケッチをここに置きました。

図%%.1
図159.1 サブROMコピーの動作図

いちいちサブシステムを止めてコマンドを(2度)発行してダンプを行うので、かなり面倒でしたが、このスケッチにより得られたダンプ画面の最初と最後を示します。

d800 00 00 00 00 00 00 00 00 70 40 70 1a 7a 0e 0a 0a
d810 70 40 70 12 74 08 14 22 70 40 70 42 74 08 14 22
d820 70 40 70 40 7e 04 04 04 e0 80 e0 8c f2 12 12 0d
d830 20 50 70 52 54 18 14 12 70 48 70 4c 74 04 04 07
:
ffc0 02 10 ae a4 10 9f 47 bd b5 9c bd 9b 35 be 01 82
ffd0 20 d3 be 01 84 ae 02 20 a9 bd 02 69 34 ff 86 00
ffe0 1f 8b 10 ff 03 16 10 fe 03 16 bd 9b 35 86 2a 8d
fff0 e0 00 e0 00 e0 00 fd ac e0 6e e0 00 fe bf e0 00

同じくネットで入手したSubrom_c.romと比較したところ、全てのバイトが一致しました。若干疑問なのは、スクラッチから作成したと言われているROMデータが実ROMデータを全て一致するということがあるのだろうかということです。とりあえず、実機から抜き出したROMデータを使用している分には問題が無いので、気にしないことにします。

$\dagger$山内さんは入社した時の課内ソフトウェアグループのサブシステムチームのリーダでした。


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

FM-7 ROM吸出し器の製作 (3)

posted by sakurai on August 27, 2019 #158

Arduino Mega 2560 Proの端子図を図158.1及び図158.2に示します。

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

図%%.2
図158.2 Arduino Mega 2560 Proの端子図(右側の端子)

アドレス、データ、制御信号を全てソフトコントロールしています。スケッチはここに置きました。ただし、初版からデータピンの位置を変更しているため、ボードの版数に合わせて適宜修正する必要があります。

これにより吸い出したfbasic30 ROM(\$8000~\$FBFF)生データの一部を示します。

8000 8e 80 13 ce 01 f9 c6 0a bd 85 97 8e 80 26 c6 0a
8010 7e 85 97 06 80 1d 80 5a 09 80 1d 80 77 a0 a0 a0
8020 a0 a0 a0 a0 a0 a0 06 80 30 80 5a 01 80 71 80 77
8030 43 48 41 49 ce 45 52 41 53 c5 4c 4c 49 53 d4 4c
:
:
fbc0 e1 7e 9b 50 7e 8d d1 7e af 1a 7e 96 63 7e b2 34
fbd0 7e b0 ee 7e af 11 7e cc 37 7e af 97 7e ce dc 7e
fbe0 ce df 7e b0 44 7e e3 4d 7e da f9 7e da ef 7e d4
fbf0 df 7e c2 8c 7e c6 7a 7e da f6 f1 7d 86 84 84 8b

このままCOEファイルに変換するか、あるいはバイナリにすれば、前稿のスクリプトが使えますが、簡単なのでこのままawkによりCOE化することにします。

ネットで入手したFM-7エミュレータ用のROMデータと実機から吸い上げた生データを比較したところ、fbasic30.coeは全てのバイトが一致しました。 一方boot.coeは末尾の2 lineが不一致となりました。エミュレータ用のROMでは0xffとなっていますが、実機のROMは割込みベクタが置かれているためにデータが入っています。

エミュレータ用ROMの末尾32バイト:

ffe0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
fff0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

実機ROMの末尾32バイト:

ffe0 ff ff ff ff ff ff 01 04 ff ff ff ff ff ff ff ff
fff0 ff ff 01 d1 01 d4 01 e0 01 dd 01 d7 01 da fe 00

ところが、「FM-7ユーザーズマニュアルシステム仕様」を見ると、\$FFE0~\$FFFFはベクトル領域となっており、boot ROMではないため、これで全てのバイトが一致しました。

末尾32バイトはベクトル領域というRAM領域でジャンプテーブルとなっています。飛び先のRAMの内容を含めて表にまとめます。

図%%.3
図158.3 割込みベクタまとめ表
FIRQ割込みハンドラは\$C953から、IRQ割込みハンドラは\$D2FCからのようです。本カードにより(メインCPUを停止し)\$AAを書き込んだところ、以下のようになったため、\$FFE0~\$FFFDはRAM、\$FFFE~$\$FFFFはROMのようです。

ffe0 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa
fff0 aa aa aa aa aa aa aa aa aa aa aa aa aa aa fe 00


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

FM-7 ROM吸出し器の製作 (2)

posted by sakurai on August 26, 2019 #157

ArduinoとPCはUSBケーブルで接続し、それを通じてプログラムするのですが、そのケーブルを通じて通信することができます。具体的にはArduino側でprintln()関数を実行すると、PC側でArduino IDEの機能であるシリアルモニターを立ち上げておけば、データを表示することができるので、これを利用します。ちなみに、Arduino IDEの機能であるシリアルプロッターという便利なものがあり、Arduino側でデータを出力すれば、IDE側でグラフをリアルタイムで書いてくれ、オートスケールまでしてくれます。

面倒なのはArduino側で並列データの概念が無く、bitの入出力しかないため、アドレスのライトはbitに分解し、データのリードはbitを統合してやる必要があることです。以下にパラシリ、シリパラ変換を示します。

16ビットアドレスの分解(パラ⇒シリ)

int apb = 54;
boolean b;
unsigned address = 0x8000;
unsigned ad;
 :
ad = address;
for (int i = 0; i < 16; i++) {
 b = ad & 1;
 digitalWrite(apb+i, b);
 ad = ad >> 1;
}

このコードの動作図を図157.1に示します。

図%%.1
図157.1 パラシリ動作図解

8ビットデータの組み上げ(シリ⇒パラ)

int dpb = 39;
boolean b;
unsigned data;
 :
data = 0;
for (int i = 0; i < 8; i++) {
 data = data << 1;
 b = digitalRead(dpb-i);
 data = data | b;
}

このコードの動作図を図157.2に示します。

図%%.2
図157.2 シリパラ動作図解

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


ページ: