nao-milkの経験ブログ

25年間の半導体エンジニア経験で知り得た内容を記載したブログです。

FRAMにアクセス (FPGA Top作成編)

f:id:nao-milk:20210419174248p:plain
Platform DesignerとFPGA Topの作成になります。

FPGAのTOP構成

FPGAのTOP構成は以下となります。(Quartusを抜粋)

f:id:nao-milk:20210425103817p:plain
TOP構成

ソースコードは以下の通りです。
尚、FRAM I/FブロックのPRM_???は固定値にしています。本来はブートシーケンスが制御することを想定しているため、FPGAでは簡易的に外部からの指示に応じて、メモリ内に保存するような記述にしています。

module TOP(
    input   wire        RESET_N     ,   // @@ リセット
    input   wire        CLOCK       ,   // @@ クロック
    output  wire        FRAM_WP_N   ,   // @@ FRAM ライトプロテクト[0:プロテクト]
    output  wire        FRAM_CS_N   ,   // @@ FRAM SPI チップセレクト
    output  wire        FRAM_SCLK   ,   // @@ FRAM SPI シリアルクロック
    output  wire        FRAM_MOSI   ,   // @@ FRAM SPI シリアル出力
    input   wire        FRAM_MISO   ,   // @@ FRAM SPI シリアル入力
    input   wire        SW_SLAVE    ,   // @@ Master/Slave切替 [0:Master]
    input   wire        SW_GO       ,   // @@ 処理イネーブル
    output  wire        LED_LOCK        // @@ LED Lock
);

    // @@
    // @@ クロック/リセット生成
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    wire        cpu_rst_n;
    wire        cpu_clk  ;
    wire        sys_rst_n;
    wire        sys_clk  ;

    NML_CLKRST UnCLKRST(
        .PAD_RST_N  (RESET_N        ),  // input   wire        PAD_RST_N   ,   // @@ 入力リセット
        .PAD_CLK    (CLOCK          ),  // input   wire        PAD_CLK     ,   // @@ 入力クロック
        .CPU_RST_N  (cpu_rst_n      ),  // output  reg         CPU_RST_N   ,   // @@ CPU用 リセット
        .CPU_CLK    (cpu_clk        ),  // output  wire        CPU_CLK     ,   // @@ CPU用 クロック[50MHz]
        .SYS_RST_N  (sys_rst_n      ),  // output  reg         SYS_RST_N   ,   // @@ System用 リセット
        .SYS_CLK    (sys_clk        ),  // output  wire        SYS_CLK     ,   // @@ System用 クロック[200MHz]
        .PLL_LOCK   (LED_LOCK       )   // output  wire        PLL_LOCK        // @@ PLL LOCK
    );

    // @@
    // @@ CPU
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    wire        fram_ami_wen;
    wire [31:0] fram_ami_add;
    wire [ 3:0] fram_ami_ben;
    wire [ 7:0] fram_ami_wdt;
    wire        fram_asi_wen;
    wire        fram_asi_ren;
    wire [ 5:0] fram_asi_add;
    wire [31:0] fram_asi_wdt;
    wire        fram_asi_wit;
    wire        fram_asi_rvl;
    wire [31:0] fram_asi_rdt;
    wire        fram_cpu_irq;

    NML_CPU UnCPU(
        .clk_clk            (cpu_clk                ),  // input  wire        clk_clk,            //     clk.clk
        .irq_irq            (fram_cpu_irq           ),  // input  wire [0:0]  irq_irq,            //     irq.irq
        .fram_write         (fram_asi_wen           ),  // output wire        fram_write,         //        .write
        .fram_read          (fram_asi_ren           ),  // output wire        fram_read,          //        .read
        .fram_address       (fram_asi_add           ),  // output wire [5:0]  fram_address,       //        .address
        .fram_writedata     (fram_asi_wdt           ),  // output wire [31:0] fram_writedata,     //        .writedata
        .fram_waitrequest   (fram_asi_wit           ),  // input  wire        fram_waitrequest,   //    fram.waitrequest
        .fram_readdatavalid (fram_asi_rvl           ),  // input  wire        fram_readdatavalid, //        .readdatavalid
        .fram_readdata      (fram_asi_rdt           ),  // input  wire [31:0] fram_readdata,      //        .readdata
        .fram_burstcount    (/** OPEN **/           ),  // output wire [0:0]  fram_burstcount,    //        .burstcount
        .fram_byteenable    (/** OPEN **/           ),  // output wire [3:0]  fram_byteenable,    //        .byteenable
        .fram_debugaccess   (/** OPEN **/           ),  // output wire        fram_debugaccess,   //        .debugaccess
        .fram_wp_export     (FRAM_WP_N              ),  // output wire        fram_wp_export,     // fram_wp.export
        .param_clken        (1'b1                   ),  // input  wire        param_clken,        //        .clken
        .param_chipselect   (1'b1                   ),  // input  wire        param_chipselect,   //        .chipselect
        .param_write        (   fram_ami_wen        ),  // input  wire        param_write,        //        .write
        .param_address      (   fram_ami_add[11: 2] ),  // input  wire [9:0]  param_address,      //   param.address
        .param_byteenable   (   fram_ami_ben        ),  // input  wire [3:0]  param_byteenable,   //        .byteenable
        .param_writedata    ({4{fram_ami_wdt}}      ),  // input  wire [31:0] param_writedata,    //        .writedata
        .param_readdata     (/** OPEN **/           ),  // output wire [31:0] param_readdata,     //        .readdata
        .port_a_export      (                       ),  // output wire [7:0]  port_a_export,      //  port_a.export
        .port_b_export      (                       ),  // output wire [7:0]  port_b_export,      //  port_b.export
        .reset_reset_n      (cpu_rst_n              )   // input  wire        reset_reset_n       //   reset.reset_n
    );

    // @@
    // @@ FRAM I/F
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    NML_FRAMIF UnFRAMIF(
        .CPU_RST_N  (cpu_rst_n      ),  // input   wire        CPU_RST_N   ,   // @@ CPU I/F用 リセット
        .CPU_CLK    (cpu_clk        ),  // input   wire        CPU_CLK     ,   // @@ CPU I/F用 クロック
        .SYS_RST_N  (sys_rst_n      ),  // input   wire        SYS_RST_N   ,   // @@ システム動作用 リセット
        .SYS_CLK    (sys_clk        ),  // input   wire        SYS_CLK     ,   // @@ システム動作用 クロック
        .MST_SLV    (SW_SLAVE       ),  // input   wire        MST_SLV     ,   // @@ Avalon-MM Master/Slave切替 [0:Master]
        .PRM_ENB    (SW_GO          ),  // input   wire        PRM_ENB     ,   // @@ 処理イネーブル [1→0:OFF 0→1:ON]
        .PRM_DIV    (4'd0           ),  // input   wire [ 3:0] PRM_DIV     ,   // @@ シリアルクロック周波数設定
        .PRM_POL    (1'b0           ),  // input   wire        PRM_POL     ,   // @@ シリアルクロック極性設定
        .PRM_TAK    (2'd0           ),  // input   wire [ 1:0] PRM_TAK     ,   // @@ シリアルデータ取り込みタイミング設定
        .PRM_ACS    (21'd8192       ),  // input   wire [20:0] PRM_ACS     ,   // @@ アクセスバイト数 (最大値:1,048,579)
        .PRM_OTC    (2'd3           ),  // input   wire [ 1:0] PRM_OTC     ,   // @@ 出力バイト数 (最大値:3)
        .PRM_MSK    (1'b0           ),  // input   wire        PRM_MSK     ,   // @@ 割り込みマスク
        .PRM_CLR    (1'b0           ),  // input   wire        PRM_CLR     ,   // @@ 割り込みクリア
        .PRM_STA    (32'h0000_0000  ),  // input   wire [31:0] PRM_STA     ,   // @@ Avalon-MM(Master側) ライトベースアドレス
        .PRM_CMD    (8'h03          ),  // input   wire [ 7:0] PRM_CMD     ,   // @@ RAMコマンド
        .PRM_ADD    (24'h00_0000    ),  // input   wire [23:0] PRM_ADD     ,   // @@ FRAMアドレス
        .SAV_WEN    (fram_asi_wen   ),  // input   wire        SAV_WEN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ライトイネーブル
        .SAV_REN    (fram_asi_ren   ),  // input   wire        SAV_REN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードイネーブル
        .SAV_ADD    (fram_asi_add   ),  // input   wire [ 5:0] SAV_ADD     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リード/ライトイネーブル
        .SAV_WDT    (fram_asi_wdt   ),  // input   wire [31:0] SAV_WDT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ライトデータ
        .SAV_WIT    (fram_asi_wit   ),  // output  wire        SAV_WIT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ウェイトリクエスト
        .SAV_RVL    (fram_asi_rvl   ),  // output  wire        SAV_RVL     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードデータイネーブル
        .SAV_RDT    (fram_asi_rdt   ),  // output  wire [31:0] SAV_RDT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードデータ
        .MAV_WEN    (fram_ami_wen   ),  // output  wire        MAV_WEN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトイネーブル
        .MAV_ADD    (fram_ami_add   ),  // output  wire [31:0] MAV_ADD     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトアドレス
        .MAV_BEN    (fram_ami_ben   ),  // output  wire [ 3:0] MAV_BEN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) バイトイネーブル
        .MAV_WDT    (fram_ami_wdt   ),  // output  wire [ 7:0] MAV_WDT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトデータ
        .MAV_WIT    (1'b0           ),  // input   wire        MAV_WIT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ウェイトリクエスト
        .CPU_IRQ    (fram_cpu_irq   ),  // output  wire        CPU_IRQ     ,   // @@ [CPU_CLK↑同期] 割り込み信号
        .MON_ENB    (/** OPEN **/   ),  // output  wire        MON_ENB     ,   // @@ [CPU_CLK↑同期] 動作モニタ [0:停止中  1:動作中]
        .FRAM_CS_N  (FRAM_CS_N      ),  // output  wire        FRAM_CS_N   ,   // @@ [SYS_CLK↑同期] FRAM SPI チップセレクト
        .FRAM_SCLK  (FRAM_SCLK      ),  // output  wire        FRAM_SCLK   ,   // @@ [SYS_CLK↑同期] FRAM SPI シリアルクロック
        .FRAM_MOSI  (FRAM_MOSI      ),  // output  wire        FRAM_MOSI   ,   // @@ [SYS_CLK↑同期] FRAM SPI シリアル出力
        .FRAM_MISO  (FRAM_MISO      ),  // input   wire        FRAM_MISO   ,   // @@ [SYS_CLK↑同期] FRAM SPI シリアル入力
        .FRAM_OUTE  (/** OPEN **/   )   // output  wire        FRAM_OUTE       // @@ [SYS_CLK↑同期] FRAM SPI 出力イネーブル[1:出力 0:Hi-Z]
    );

endmodule

Platform Designer

QuartusのPlatform DesignerでNiosIIやメモリなどを配置します。
ただし、PLLやユーザーブロック(FRAM I/Fブロック)は配置せず、FPGAトップに配置します。

これはFPGAトップからRTLシミュレーションする時に、Platform Designerで生成したRTLの配線を検索するのが大変なことと、バグがあった時に修正に手間がかかるためです。
また、PLLを外に置くことにより、クロックの追加やリセット回路の変更を容易にすることが目的です。

私個人の考えとして、Platform Designerで作成する所は、変更が少ない所を配置したいと考えています。
また、Qsysファイルにはタイムスタンプがあり、このブロックを変更すると、NiosのBSPを再実行する必要があるためです。

ブロック構成図

ブロック構成を以下に示します。
※Platform Designerで作成するブロックは、モジュール「NML_CPU」になります。

f:id:nao-milk:20210425104235p:plain
ブロック構成

※モジュールNML_FRAMIFのverilogソースは、以下をご参照ください。
nao-milk.hatenablog.com

System Contents

f:id:nao-milk:20210425104658p:plain
Contents

アドレスマップ

CPUから見たアドレスマップを以下に示します。

f:id:nao-milk:20210425105015p:plain
アドレスマップ

Quartusに読み込ませるファイル

Platform Designerの[Generate HDL]でVerilogコードが生成されます。
その時、フォルダ「モジュール名\synthesis」に"モジュール名.qip"が出来上がります。
このファイルをQuartusに読み込ませます。
"モジュール名.qsys"を読み込ませると、合成時にGenerate HDLもするため、タイムスタンプが変わり、NiosのBSPにも影響があり面倒なので、私はqipを読み込ませています。

クロック/リセット生成

PLLを配置し、リファレンスクロック(25MHz)から50MHzと200MHzクロックを生成し、クロックドメインに対応したリセット信号を生成します。

ソースコードは以下の通りです。

// **
// **   クロック/リセット生成
// **
// *****************************************************************************
module NML_CLKRST(
    input   wire        PAD_RST_N   ,   // @@ 入力リセット
    input   wire        PAD_CLK     ,   // @@ 入力クロック
    output  reg         CPU_RST_N   ,   // @@ CPU用 リセット
    output  wire        CPU_CLK     ,   // @@ CPU用 クロック[50MHz]
    output  reg         SYS_RST_N   ,   // @@ System用 リセット
    output  wire        SYS_CLK     ,   // @@ System用 クロック[200MHz]
    output  wire        PLL_LOCK        // @@ PLL LOCK
);

    // @@
    // @@ PLL Macro
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    NML_CLKRST_PLL UnPLL(
        .refclk     ( PAD_CLK   ),  // input  wire  refclk,   //  refclk.clk
        .rst        (~PAD_RST_N ),  // input  wire  rst,      //   reset.reset
        .outclk_0   ( CPU_CLK   ),  // output wire  outclk_0, // outclk0.clk
        .outclk_1   ( SYS_CLK   ),  // output wire  outclk_1, // outclk1.clk
        .locked     ( PLL_LOCK  )   // output wire  locked    //  locked.export
    );

    // @@
    // @@ リセット回路
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg  [ 1:0] cpu_rst_sft;
    reg  [ 1:0] sys_rst_sft;

    always @( posedge CPU_CLK or negedge PLL_LOCK ) begin
        if( ~PLL_LOCK )                 {CPU_RST_N,cpu_rst_sft} <= {1'b0,2'b00};
        else                            {CPU_RST_N,cpu_rst_sft} <= {cpu_rst_sft,1'b1};
    end

    always @( posedge SYS_CLK or negedge PLL_LOCK ) begin
        if( ~PLL_LOCK )                 {SYS_RST_N,sys_rst_sft} <= {1'b0,2'b00};
        else                            {SYS_RST_N,sys_rst_sft} <= {sys_rst_sft,1'b1};
    end

endmodule

最後に

Platform DesignerはIPマクロを配線するのに便利です。
が、ユーザーロジックを含めて配線するとデバッグ時にユーザー回路のポートを修正すると手間が掛かります。

FRAMにアクセス (シミュレーション編)

f:id:nao-milk:20210419163451p:plain
FRAM I/FブロックをトップとしたRTLシミュレーションになります。
ここでは、テストベンチ記述を記載します。
尚、言語はSystemVerilogとなります。

FRAM I/Fブロックの仕様及びソースコードは以下を参照ください。
nao-milk.hatenablog.com
nao-milk.hatenablog.com

対向モデル

FRAM I/Fブロックの対向モデル(FRAMデバイス)は、シミュレーションモデルがベンダーで提供されていますので、それを入手してください。

テストベンチ

テストベンチは、FRAM I/FブロックとFRAMモデルを配置し、クロック生成及びタスクを準備し、テストシナリオを記述します。
尚、レジスタなどは構造体にした方が便利なため、記述はSystemVerilogにしています。

ヘッダファイル

レジスタの構造体とアドレス 及び よく使う処理をタスク化しています。

// --
// --   レジスタパック
// -------------------------------------------------------------------
typedef struct  packed  {   // ** 処理イネーブル
    reg  [31: 1]    Non ;       // 未使用
    reg             ENB ;       // 処理イネーブル
} Register_ENB;
typedef struct  packed  {   // ** 制御パラメータ
    reg  [31: 7]    Non ;       // 未使用
    reg  [ 6: 5]    TOK ;       // 取り込みタイミング
    reg             POL ;       // シリアルクロック極性
    reg  [ 3: 0]    DIV ;       // シリアルクロック分周
} Register_CNT;
typedef struct  packed  {   // ** アクセスバイト数
    reg  [31:21]    Non ;       // 未使用
    reg  [20: 0]    ACS ;       // アクセスバイト数
} Register_ACS;
typedef struct  packed  {   // ** 出力バイト数
    reg  [31:21]    Non ;       // 未使用
    reg  [20: 0]    OTC ;       // 出力バイト数
} Register_OTC;
typedef struct  packed  {   // ** 割り込みステータス
    reg  [31: 1]    Non ;       // 未使用
    reg             IRS ;       // 割り込みステータス
} Register_IRS;
typedef struct  packed  {   // ** 割り込みマスク
    reg  [31: 1]    Non ;       // 未使用
    reg             IRM ;       // 割り込みマスク
} Register_IRM;
typedef struct  packed  {   // ** バッファリード/ライトアドレス
    reg  [31:21]    Non ;       // 未使用
    reg  [20: 0]    BAD ;       // バッファリード/ライトアドレス
} Register_BAD;
typedef struct  packed  {   // ** バッファリード/ライトデータ
    reg  [31: 8]    Non ;       // 未使用
    reg  [ 7: 0]    BDT ;       // バッファリード/ライトデータ
} Register_BDT;

struct  {
    reg  [31:0]     ENB = 'h00;
    reg  [31:0]     CNT = 'h04;
    reg  [31:0]     ACS = 'h08;
    reg  [31:0]     OTC = 'h0C;
    reg  [31:0]     IRS = 'h10;
    reg  [31:0]     IRM = 'h14;
    reg  [31:0]     BAD = 'h18;
    reg  [31:0]     BDT = 'h1C;
} REG_ADDR;
struct  {
    Register_ENB    ENB = 32'h0000_0000;
    Register_CNT    CNT = 32'h0000_0000;
    Register_ACS    ACS = 32'h0000_0000;
    Register_OTC    OTC = 32'h0000_0000;
    Register_IRS    IRS = 32'h0000_0000;
    Register_IRM    IRM = 32'h0000_0001;
    Register_BAD    BAD = 32'h0000_0000;
    Register_BDT    BDT = 32'h0000_0000;
} REG_DATA;

// --
// --   処理タスク
// -------------------------------------------------------------------

// ++
// ++   書き込みイネーブル ラッチ設定
// +++++++++++++++++++++++++++++++++++++++++++
task SetWREN();
    begin
        $display("\n===== %m (Enable Write) =====");
        REG_DATA.ACS.ACS    = 0;    WrCPU(REG_ADDR.ACS,REG_DATA.ACS);   // アクセスバイト数
        REG_DATA.OTC.OTC    = 0;    WrCPU(REG_ADDR.OTC,REG_DATA.OTC);   // 出力バイト数
        REG_DATA.IRM.IRM    = 1;    WrCPU(REG_ADDR.IRM,REG_DATA.IRM);   // 割り込みマスク
        REG_DATA.IRS.IRS    = 1;    WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス クリア
                                    RdCPU(REG_ADDR.IRS,REG_DATA.IRS);
                                    $display(" >>> Read : %p",REG_DATA.IRS);
        REG_DATA.BAD.BAD    = 0;    WrCPU(REG_ADDR.BAD,REG_DATA.BAD);   // バッファアドレス
        REG_DATA.BDT.BDT    ='h06;  WrCPU(REG_ADDR.BDT,REG_DATA.BDT);   // バッファデータ
        REG_DATA.ENB.ENB    = 1;    WrCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                    RdCPU(REG_ADDR.ENB,REG_DATA.ENB);
        while(REG_DATA.ENB.ENB===1) begin
            repeat(20)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);
        end
        REG_DATA.IRS.IRS    = 1;    WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス クリア
        REG_DATA.IRM.IRM    = 0;    WrCPU(REG_ADDR.IRM,REG_DATA.IRM);   // 割り込みマスク 解除
    end
endtask

テストベンチトップ

テストシナリオでは、リセット解除後からマスタモード → スレーブモードのリード系 → スレーブモードのライト系の動作を行い、各コマンドの発行を行っています。
また、FRAMのサイズも 256K×8bit分アクセスするテストを行っています。
最終的にすべて実行し、変数errorが0であれば、問題無しとしています。
尚、レジスタの初期値確認や割り込み動作などは、別のシナリオで確認しています。

データ比較は、
リード(FRAMデバイス → FRAM I/Fブロック)時、FRAMデバイス内の配列とAvalon-MM Master/Slave I/Fで取得したデータが等しいか確認します。
ライト(FRAM I/Fブロック → FRAMデバイス)時、Avalon-MM Slave I/Fブロックにライトしたデータが、FRAMデバイス内の配列と等しいか確認します。

`timescale  1ns/1ps

module  TB_TOP;
integer     PERIOD      = 0;
reg         CPU_RST_N   = 0;    // @@ CPU I/F用 リセット
reg         CPU_CLK     = 0;    // @@ CPU I/F用 クロック
reg         SYS_RST_N   = 0;    // @@ システム動作用 リセット
reg         SYS_CLK     = 0;    // @@ システム動作用 クロック
reg         MST_SLV     = 0;    // @@ Avalon-MM Master/Slave切替 [0:Master]
reg         PRM_ENB     = 0;    // @@ 処理イネーブル [1→0:OFF 0→1:ON]
reg  [ 3:0] PRM_DIV     = 0;    // @@ シリアルクロック周波数設定
reg         PRM_POL     = 0;    // @@ シリアルクロック極性設定
reg  [ 1:0] PRM_TAK     = 0;    // @@ シリアルデータ取り込みタイミング設定
reg  [20:0] PRM_ACS     = 0;    // @@ アクセスバイト数 (最大値:1,048,579)
reg  [ 1:0] PRM_OTC     = 0;    // @@ 出力バイト数 (最大値:3)
reg         PRM_MSK     = 1;    // @@ 割り込みマスク
reg         PRM_CLR     = 0;    // @@ 割り込みクリア
reg  [31:0] PRM_STA     = 0;    // @@ Avalon-MM(Master側) ライトベースアドレス
reg  [ 7:0] PRM_CMD     = 0;    // @@ RAMコマンド
reg  [23:0] PRM_ADD     = 0;    // @@ FRAMアドレス
reg         SAV_WEN     = 0;    // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ライトイネーブル
reg         SAV_REN     = 0;    // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードイネーブル
reg  [ 5:0] SAV_ADD     = 0;    // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リード/ライトイネーブル
reg  [31:0] SAV_WDT     = 0;    // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ライトデータ
reg         MAV_WIT     = 0;    // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ウェイトリクエスト
wire        SAV_WIT     ;       // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ウェイトリクエスト
wire        SAV_RVL     ;       // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードデータイネーブル
wire [31:0] SAV_RDT     ;       // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードデータ
wire        MAV_WEN     ;       // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトイネーブル
wire [31:0] MAV_ADD     ;       // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトアドレス
wire [ 3:0] MAV_BEN     ;       // @@ [CPU_CLK↑同期] Avalon-MM(Master側) バイトイネーブル
wire [ 7:0] MAV_WDT     ;       // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトデータ
wire        CPU_IRQ     ;       // @@ [CPU_CLK↑同期] 割り込み信号
wire        MON_ENB     ;       // @@ [CPU_CLK↑同期] 動作モニタ [0:停止中  1:動作中]
wire        FRAM_CS_N   ;       // @@ [SYS_CLK↑同期] FRAM SPI チップセレクト
wire        FRAM_SCLK   ;       // @@ [SYS_CLK↑同期] FRAM SPI シリアルクロック
wire        FRAM_MOSI   ;       // @@ [SYS_CLK↑同期] FRAM SPI シリアル出力
wire        FRAM_MISO   ;       // @@ [SYS_CLK↑同期] FRAM SPI シリアル入力
wire        FRAM_OUTE   ;       // @@ [SYS_CLK↑同期] FRAM SPI 出力イネーブル[1:出力 0:Hi-Z]

// ++ タイミング設定
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Clock波形
real        tcCPU_CLK   = 20.000;
real        tpCPU_CLK   =  5.000;
real        tnCPU_CLK   = 15.000;
real        tcSYS_CLK   =  5.000;
real        tpSYS_CLK   =  1.000;
real        tnSYS_CLK   =  3.500;

// FRAM モデル間遅延
defparam    UnDlyFRAM_CS_N.NsStep= 0 ,UnDlyFRAM_CS_N.PsStep=  0;
defparam    UnDlyFRAM_SCLK.NsStep= 0 ,UnDlyFRAM_SCLK.PsStep=  0;
defparam    UnDlyFRAM_MOSI.NsStep= 0 ,UnDlyFRAM_MOSI.PsStep=  0;
defparam    UnDlyFRAM_MISO.NsStep= 0 ,UnDlyFRAM_MISO.PsStep=  0;

// --
// -- テストシナリオ
// -------------------------------------------------------------------
`define MASTER_MODE
`define SLAVE_MODE_READ
`define SLAVE_MODE_WRITE

reg  [ 7:0] PMEM[0:262144];
integer     error = 0;
`include    "TB_HEAD.sv"
always @( posedge CPU_CLK ) begin
    if( (MAV_WEN===1'b1) & (MAV_WIT==0) ) begin
        if( MAV_BEN[0]===1'b1 ) PMEM[MAV_ADD-PRM_STA] <= MAV_WDT;
        if( MAV_BEN[1]===1'b1 ) PMEM[MAV_ADD-PRM_STA] <= MAV_WDT;
        if( MAV_BEN[2]===1'b1 ) PMEM[MAV_ADD-PRM_STA] <= MAV_WDT;
        if( MAV_BEN[3]===1'b1 ) PMEM[MAV_ADD-PRM_STA] <= MAV_WDT;
    end
    MAV_WIT <= $random;
end

defparam    FRAM.hasSN = 1;
integer     ei;
initial begin
    #0;
    repeat(10)  @(PERIOD);
                @(posedge CPU_CLK);     #1  CPU_RST_N   = 1;
                @(posedge SYS_CLK);     #1  SYS_RST_N   = 1;

    // @@
    // @@   Master Mode
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
`ifdef  MASTER_MODE
    repeat(20)  @(PERIOD);
    MST_SLV = 0;                // @@ Avalon-MM Master/Slave切替 [0:Master]

    // ++ [ 初期設定 ] ++
    PRM_STA = 32'h8000_0000;    // @@ Avalon-MM(Master側) ライトベースアドレス
    PRM_DIV = 0;                // @@ シリアルクロック周波数設定
    PRM_POL = 0;                // @@ シリアルクロック極性設定
    PRM_TAK = 0;                // @@ シリアルデータ取り込みタイミング設定
    // ++ [ 割り込み設定 ] ++
    repeat( 5)  @(PERIOD)   PRM_CLR = 1;    // @@ 割り込みクリア
    PRM_CLR = 0;
    PRM_MSK = 0;                // @@ 割り込みマスク

    // ++ デバイスID読み出し
    // +++++++++++++++++++++++++++++++++++++++++++
    FRAM.Device_ID[0] = 'h80;
    FRAM.Device_ID[1] = 'h40;
    FRAM.Device_ID[2] = 'h20;
    FRAM.Device_ID[3] = 'h10;
    FRAM.Device_ID[4] = 'h08;
    FRAM.Device_ID[5] = 'h04;
    FRAM.Device_ID[6] = 'h02;
    FRAM.Device_ID[7] = 'h01;
    FRAM.Device_ID[8] = 'hA5;
    repeat( 5)  @(PERIOD);  $display("\n===== Read Device ID =====");
                            PRM_ACS = 9;    // @@ アクセスバイト数 (最大値:1,048,579)
                            PRM_OTC = 0;    // @@ 出力バイト数 (最大値:3)
                            PRM_CMD = 'h9F; // @@ RAMコマンド
                            PRM_ADD = 0;    // @@ FRAMアドレス
    repeat( 5)  @(PERIOD);  PRM_ENB = 0;    // @@ 処理イネーブル [1→0:OFF 0→1:ON]
                            PRM_ENB = 1;
    wait(CPU_IRQ===1'b1);   $display(" >>> CPU_IRQ=1");
    repeat( 5)  @(PERIOD);
    repeat( 5)  @(PERIOD)   PRM_CLR = 1;    // @@ 割り込みクリア
                            PRM_CLR = 0;
    repeat( 5)  @(PERIOD);  PRM_ENB = 0;    // @@ 処理イネーブル [1→0:OFF 0→1:ON]
    repeat(10)  @(PERIOD);
    for(ei=0;ei<9;ei=ei+1)begin
        $display(" >>> Check : [%h] => %h , %h",ei[7:0],PMEM[ei],FRAM.Device_ID[ei]);
        if(PMEM[ei]!==FRAM.Device_ID[ei])   error = error + 1;
    end
    $display(" %m >> Error : %d",error);

    // ++ シリアル番号読み出し
    // +++++++++++++++++++++++++++++++++++++++++++
    FRAM.Serial_No[0] = 'hFF^'h80;
    FRAM.Serial_No[1] = 'hFF^'h40;
    FRAM.Serial_No[2] = 'hFF^'h20;
    FRAM.Serial_No[3] = 'hFF^'h10;
    FRAM.Serial_No[4] = 'hFF^'h08;
    FRAM.Serial_No[5] = 'hFF^'h04;
    FRAM.Serial_No[6] = 'hFF^'h02;
    FRAM.Serial_No[7] = 'hFF^'h01;
    repeat( 5)  @(PERIOD);  $display("\n===== Read Serial No =====");
                            PRM_ACS = 8;    // @@ アクセスバイト数 (最大値:1,048,579)
                            PRM_OTC = 0;    // @@ 出力バイト数 (最大値:3)
                            PRM_CMD = 'hC3; // @@ RAMコマンド
                            PRM_ADD = 0;    // @@ FRAMアドレス
    repeat( 5)  @(PERIOD);  PRM_ENB = 0;    // @@ 処理イネーブル [1→0:OFF 0→1:ON]
                            PRM_ENB = 1;
    wait(CPU_IRQ===1'b1);   $display(" >>> CPU_IRQ=1");
    repeat( 5)  @(PERIOD);
    repeat( 5)  @(PERIOD)   PRM_CLR = 1;    // @@ 割り込みクリア
                            PRM_CLR = 0;
    repeat( 5)  @(PERIOD);  PRM_ENB = 0;    // @@ 処理イネーブル [1→0:OFF 0→1:ON]
    repeat(10)  @(PERIOD);
    for(ei=0;ei<8;ei=ei+1)begin
        $display(" >>> Check : [%h] => %h , %h",ei[7:0],PMEM[ei],FRAM.Serial_No[ei]);
        if(PMEM[ei]!==FRAM.Serial_No[ei])   error = error + 1;
    end
    $display(" %m >> Error : %d",error);

    // ++ 特殊セクタ読み出し
    // +++++++++++++++++++++++++++++++++++++++++++
    for(ei=0;ei<256;ei=ei+1)    FRAM.Smem[ei] = $random;
    repeat( 5)  @(PERIOD);  $display("\n===== Special Sector Read =====");
                            PRM_ACS = 256+4-1;  // @@ アクセスバイト数 (最大値:1,048,579)
                            PRM_OTC =     4-1;  // @@ 出力バイト数 (最大値:3)
                            PRM_CMD = 'h4B;     // @@ RAMコマンド
                            PRM_ADD = 'h000000; // @@ FRAMアドレス
    repeat( 5)  @(PERIOD);  PRM_ENB = 0;        // @@ 処理イネーブル [1→0:OFF 0→1:ON]
                            PRM_ENB = 1;
    wait(CPU_IRQ===1'b1);   $display(" >>> CPU_IRQ=1");
    repeat( 5)  @(PERIOD);
    repeat( 5)  @(PERIOD)   PRM_CLR = 1;    // @@ 割り込みクリア
                            PRM_CLR = 0;
    repeat( 5)  @(PERIOD);  PRM_ENB = 0;    // @@ 処理イネーブル [1→0:OFF 0→1:ON]
    repeat(10)  @(PERIOD);
    for(ei=0;ei<256;ei=ei+1)begin
        $display(" >>> Check : [%h] => %h , %h",ei[7:0],PMEM[ei],FRAM.Smem[ei]);
        if(PMEM[ei]!==FRAM.Smem[ei])    error = error + 1;
    end
    $display(" %m >> Error : %d",error);

    // ++ 読み出し動作
    // +++++++++++++++++++++++++++++++++++++++++++
    for(ei=0;ei<262144;ei=ei+1) FRAM.mem[ei] = $random;
    repeat( 5)  @(PERIOD);  $display("\n===== Read Data from Memory =====");
                            PRM_ACS = 262144+4-1;   // @@ アクセスバイト数 (最大値:1,048,579)
                            PRM_OTC =        4-1;   // @@ 出力バイト数 (最大値:3)
                            PRM_CMD = 'h03;         // @@ RAMコマンド
                            PRM_ADD = 'h000000;     // @@ FRAMアドレス
    repeat( 5)  @(PERIOD);  PRM_ENB = 0;            // @@ 処理イネーブル [1→0:OFF 0→1:ON]
                            PRM_ENB = 1;
    wait(CPU_IRQ===1'b1);   $display(" >>> CPU_IRQ=1");
    repeat( 5)  @(PERIOD);
    repeat( 5)  @(PERIOD)   PRM_CLR = 1;    // @@ 割り込みクリア
                            PRM_CLR = 0;
    repeat( 5)  @(PERIOD);  PRM_ENB = 0;    // @@ 処理イネーブル [1→0:OFF 0→1:ON]
    repeat(10)  @(PERIOD);
    for(ei=0;ei<262144;ei=ei+1)begin
        $display(" >>> Check : [%h] => %h , %h",ei[7:0],PMEM[ei],FRAM.mem[ei]);
        if(PMEM[ei]!==FRAM.mem[ei])    error = error + 1;
    end
    $display(" %m >> Error : %d",error);
`endif

    // @@
    // @@   Slave Mode (Read)
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
`ifdef  SLAVE_MODE_READ
    repeat(20)  @(PERIOD);
    MST_SLV = 1;                // @@ Avalon-MM Master/Slave切替 [0:Master]

    // ++ [ 初期設定 ] ++
    PRM_STA = 32'h8000_0000;    // @@ Avalon-MM(Master側) ライトベースアドレス
    PRM_DIV = 15;               // @@ シリアルクロック周波数設定
    PRM_POL = 1;                // @@ シリアルクロック極性設定
    PRM_TAK = 3;                // @@ シリアルデータ取り込みタイミング設定
    // ++ [ 割り込み設定 ] ++
    repeat( 5)  @(PERIOD)   PRM_CLR = 1;    // @@ 割り込みクリア
    PRM_CLR = 1;
    PRM_MSK = 1;                // @@ 割り込みマスク

    // ++
    // ++   制御レジスタの設定
    // +++++++++++++++++++++++++++++++++++++++++++
    REG_DATA.CNT.TOK    = 0;    // 取り込みタイミング
    REG_DATA.CNT.POL    = 1;    // シリアルクロック極性
    REG_DATA.CNT.DIV    = 0;    // シリアルクロック分周
    REG_DATA.ACS.ACS    = 0;    // アクセスバイト数
    REG_DATA.OTC.OTC    = 0;    // 出力バイト数
    REG_DATA.IRS.IRS    = 1;    // 割り込みステータス
    REG_DATA.IRM.IRM    = 0;    // 割り込みマスク
    REG_DATA.BAD.BAD    = 0;    // バッファリード/ライトアドレス
    REG_DATA.BDT.BDT    = 0;    // バッファリード/ライトデータ
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.CNT,REG_DATA.CNT);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.ACS,REG_DATA.ACS);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.OTC,REG_DATA.OTC);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.IRS,REG_DATA.IRS);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.IRM,REG_DATA.IRM);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BAD,REG_DATA.BAD);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,REG_DATA.BDT);

    // ++ デバイスID読み出し
    // +++++++++++++++++++++++++++++++++++++++++++
    for(ei=0;ei<9;ei=ei+1)  FRAM.Device_ID[ei] = $random;
    repeat( 5)  @(PERIOD);  $display("\n===== Read Device ID =====");
    REG_DATA.ACS.ACS    = 9;    // アクセスバイト数
    REG_DATA.OTC.OTC    = 0;    // 出力バイト数
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.ACS,REG_DATA.ACS);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.OTC,REG_DATA.OTC);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BAD,'h00000000);     // バッファアドレス
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,'h9F);           // バッファデータ
    for(ei=0;ei<9;ei=ei+1)  WrCPU(REG_ADDR.BDT,'hFF);           // バッファデータ[リード領域の初期化]
    REG_DATA.ENB.ENB = 1;   WrCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                            $display(" >>> Read : %p",REG_DATA.ENB);
    wait(CPU_IRQ===1'b1);   $display(" >>> CPU_IRQ=1");         // 割り込み待ち
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                            $display(" >>> Read : %p",REG_DATA.ENB);
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                            $display(" >>> Read : %p",REG_DATA.IRS); 
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータスクリア
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                            $display(" >>> Read : %p - Clear",REG_DATA.IRS); 
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BAD,'h00000001);     // バッファアドレス
    for(ei=0;ei<9;ei=ei+1) begin
        repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.BDT,REG_DATA.BDT);
        $display(" >>> Check : [%h] => %h , %h",ei[23:0],REG_DATA.BDT.BDT,FRAM.Device_ID[ei]);
        if(REG_DATA.BDT.BDT!==FRAM.Device_ID[ei])   error = error + 1;
    end
    $display(" %m >> Error : %d",error);

    // ++ シリアル番号読み出し
    // +++++++++++++++++++++++++++++++++++++++++++
    for(ei=0;ei<8;ei=ei+1)  FRAM.Serial_No[ei] = $random;
    repeat( 5)  @(PERIOD);  $display("\n===== Read Serial No =====");
    REG_DATA.ACS.ACS    = 8;    // アクセスバイト数
    REG_DATA.OTC.OTC    = 0;    // 出力バイト数
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.ACS,REG_DATA.ACS);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.OTC,REG_DATA.OTC);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BAD,'h00000000);     // バッファアドレス
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,'hC3);           // バッファデータ
    for(ei=0;ei<8;ei=ei+1)  WrCPU(REG_ADDR.BDT,'hFF);           // バッファデータ[リード領域の初期化]
    REG_DATA.ENB.ENB = 1;   WrCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                            $display(" >>> Read : %p",REG_DATA.ENB);
    wait(CPU_IRQ===1'b1);   $display(" >>> CPU_IRQ=1");         // 割り込み待ち
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                            $display(" >>> Read : %p",REG_DATA.ENB);
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                            $display(" >>> Read : %p",REG_DATA.IRS); 
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータスクリア
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                            $display(" >>> Read : %p - Clear",REG_DATA.IRS); 
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BAD,'h00000001);     // バッファアドレス
    for(ei=0;ei<8;ei=ei+1) begin
        repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.BDT,REG_DATA.BDT);
        $display(" >>> Check : [%h] => %h , %h",ei[23:0],REG_DATA.BDT.BDT,FRAM.Serial_No[ei]);
        if(REG_DATA.BDT.BDT!==FRAM.Serial_No[ei])   error = error + 1;
    end
    $display(" %m >> Error : %d",error);

    // ++ 特殊セクタ読み出し
    // +++++++++++++++++++++++++++++++++++++++++++
    for(ei=0;ei<256;ei=ei+1)    FRAM.Smem[ei] = $random;
    repeat( 5)  @(PERIOD);  $display("\n===== Special Sector Read =====");
    REG_DATA.ACS.ACS    = 256+4-1;  // アクセスバイト数
    REG_DATA.OTC.OTC    = 4-1;      // 出力バイト数
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.ACS,REG_DATA.ACS);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.OTC,REG_DATA.OTC);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BAD,'h00000000);     // バッファアドレス
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,'h4B);           // バッファデータ
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,'h00);           // バッファデータ
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,'h00);           // バッファデータ
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,'h00);           // バッファデータ
    REG_DATA.ENB.ENB = 1;   WrCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                            $display(" >>> Read : %p",REG_DATA.ENB);
    wait(CPU_IRQ===1'b1);   $display(" >>> CPU_IRQ=1");         // 割り込み待ち
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                            $display(" >>> Read : %p",REG_DATA.ENB);
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                            $display(" >>> Read : %p",REG_DATA.IRS); 
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータスクリア
    repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                            $display(" >>> Read : %p - Clear",REG_DATA.IRS); 
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BAD,'h00000004);     // バッファアドレス
    for(ei=0;ei<256;ei=ei+1) begin
        repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.BDT,REG_DATA.BDT);
        $display(" >>> Check : [%h] => %h , %h",ei[23:0],REG_DATA.BDT.BDT,FRAM.Smem[ei]);
        if(REG_DATA.BDT.BDT!==FRAM.Smem[ei])    error = error + 1;
    end
    $display(" %m >> Error : %d",error);

    // ++ 読み出し動作
    // +++++++++++++++++++++++++++++++++++++++++++
    for(ei=0;ei<262144;ei=ei+1)    FRAM.mem[ei] = $random;
    repeat( 5)  @(PERIOD);  $display("\n===== Read Data from Memory =====");
    begin : MemRead
        ei = 0;
        while( ei < 262144 ) begin
            REG_DATA.ACS.ACS    = 2048+4-1; // アクセスバイト数
            REG_DATA.OTC.OTC    =      4-1; // 出力バイト数
            repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.ACS,REG_DATA.ACS);
            repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.OTC,REG_DATA.OTC);
            repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BAD,'h00000000);     // バッファアドレス
            repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,'h03);           // バッファデータ
            repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,ei[23:16]);      // バッファデータ
            repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,ei[15: 8]);      // バッファデータ
            repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,ei[ 7: 0]);      // バッファデータ
            REG_DATA.ENB.ENB = 1;   WrCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
            repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                    $display(" >>> Read : %p",REG_DATA.ENB);
            wait(CPU_IRQ===1'b1);   $display(" >>> CPU_IRQ=1");         // 割り込み待ち
            repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                    $display(" >>> Read : %p",REG_DATA.ENB);
            repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                                    $display(" >>> Read : %p",REG_DATA.IRS); 
            repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータスクリア
            repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                                    $display(" >>> Read : %p - Clear",REG_DATA.IRS); 
            repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BAD,'h00000004);     // バッファアドレス
            repeat(2048) begin
                repeat( 5)  @(PERIOD);  RdCPU(REG_ADDR.BDT,REG_DATA.BDT);
                $display(" >>> Check : [%h] => %h , %h",ei[23:0],REG_DATA.BDT.BDT,FRAM.mem[ei]);
                if(REG_DATA.BDT.BDT!==FRAM.mem[ei]) error = error + 1;
                ei = ei + 1;
            end
            $display(" %m >> Error : %d",error);
        end
    end
`endif

    // @@
    // @@   Slave Mode (Write)
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
`ifdef  SLAVE_MODE_WRITE
    repeat(20)  @(PERIOD);
    MST_SLV = 1;                // @@ Avalon-MM Master/Slave切替 [0:Master]

    // ++   制御レジスタの設定
    // +++++++++++++++++++++++++++++++++++++++++++
    REG_DATA.CNT.TOK    = 0;    // 取り込みタイミング
    REG_DATA.CNT.POL    = 1;    // シリアルクロック極性
    REG_DATA.CNT.DIV    = 0;    // シリアルクロック分周
    REG_DATA.ACS.ACS    = 0;    // アクセスバイト数
    REG_DATA.OTC.OTC    = 0;    // 出力バイト数
    REG_DATA.IRS.IRS    = 1;    // 割り込みステータス
    REG_DATA.IRM.IRM    = 0;    // 割り込みマスク
    REG_DATA.BAD.BAD    = 0;    // バッファリード/ライトアドレス
    REG_DATA.BDT.BDT    = 0;    // バッファリード/ライトデータ
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.CNT,REG_DATA.CNT);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.ACS,REG_DATA.ACS);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.OTC,REG_DATA.OTC);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.IRS,REG_DATA.IRS);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.IRM,REG_DATA.IRM);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BAD,REG_DATA.BAD);
    repeat( 5)  @(PERIOD);  WrCPU(REG_ADDR.BDT,REG_DATA.BDT);

    // ++ ステータス レジスタの読み出し
    // +++++++++++++++++++++++++++++++++++++++++++
    ei =0;
    repeat( 5)  @(PERIOD);  $display("\n===== Read Status Register =====");
    REG_DATA.ACS.ACS    = 1;    WrCPU(REG_ADDR.ACS,REG_DATA.ACS);   // アクセスバイト数
    REG_DATA.OTC.OTC    = 0;    WrCPU(REG_ADDR.OTC,REG_DATA.OTC);   // 出力バイト数
    REG_DATA.IRM.IRM    = 1;    WrCPU(REG_ADDR.IRM,REG_DATA.IRM);   // 割り込みマスク
    REG_DATA.IRS.IRS    = 1;    WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス クリア
                                RdCPU(REG_ADDR.IRS,REG_DATA.IRS);
                                $display(" >>> Read : %p",REG_DATA.IRS);
    REG_DATA.BAD.BAD    = 0;    WrCPU(REG_ADDR.BAD,REG_DATA.BAD);   // バッファアドレス
    REG_DATA.BDT.BDT    ='h05;  WrCPU(REG_ADDR.BDT,REG_DATA.BDT);   // バッファデータ
    REG_DATA.ENB.ENB    = 1;    WrCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                RdCPU(REG_ADDR.ENB,REG_DATA.ENB);
    while(REG_DATA.ENB.ENB===1) begin
        repeat(20)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);
    end
    REG_DATA.IRM.IRM    = 0;    WrCPU(REG_ADDR.IRM,REG_DATA.IRM);   // 割り込みマスク 解除
    REG_DATA.BAD.BAD    = 1;    WrCPU(REG_ADDR.BAD,REG_DATA.BAD);   // バッファアドレス
    repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.BDT,REG_DATA.BDT);   // バッファデータ
    $display(" >>> Check : [%h] => %h , %h",ei[23:0],REG_DATA.BDT.BDT,FRAM.realStatusRegister);
    if(REG_DATA.BDT.BDT!==FRAM.realStatusRegister)  error = error + 1;
    $display(" %m >> Error : %d",error);

    // ++ シリアル番号の書き込み
    // +++++++++++++++++++++++++++++++++++++++++++
    for(ei=0;ei<8;ei=ei+1)      PMEM[ei] = $random;
    repeat( 5)  @(PERIOD);      $display("\n===== Write Serial Number =====");
    SetWREN();                  repeat( 5)  @(PERIOD);
    REG_DATA.ACS.ACS    = 8;    WrCPU(REG_ADDR.ACS,REG_DATA.ACS);   // アクセスバイト数
    REG_DATA.OTC.OTC    = 8;    WrCPU(REG_ADDR.OTC,REG_DATA.OTC);   // 出力バイト数
    REG_DATA.BAD.BAD    = 0;    WrCPU(REG_ADDR.BAD,REG_DATA.BAD);   // バッファアドレス
    repeat( 5)  @(PERIOD);      WrCPU(REG_ADDR.BDT,'hC2);           // バッファデータ
    for(ei=0;ei<8;ei=ei+1)      WrCPU(REG_ADDR.BDT,PMEM[ei]);       // バッファデータ
    REG_DATA.ENB.ENB = 1;       WrCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
    repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                $display(" >>> Read : %p",REG_DATA.ENB);
    wait(CPU_IRQ===1'b1);       $display(" >>> CPU_IRQ=1");         // 割り込み待ち
    repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                $display(" >>> Read : %p",REG_DATA.ENB);
    repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                                $display(" >>> Read : %p",REG_DATA.IRS); 
    repeat( 5)  @(PERIOD);      WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータスクリア
    repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                                $display(" >>> Read : %p - Clear",REG_DATA.IRS); 
    for(ei=0;ei<8;ei=ei+1) begin
        $display(" >>> Check : [%h] => %h , %h",ei[23:0],PMEM[ei],FRAM.Serial_No[ei]);
        if(PMEM[ei]!==FRAM.Serial_No[ei])   error = error + 1;
    end
    $display(" %m >> Error : %d",error);

    // ++ 特殊セクタの書き込み
    // +++++++++++++++++++++++++++++++++++++++++++
    for(ei=0;ei<256;ei=ei+1)    PMEM[ei] = $random;
    repeat( 5)  @(PERIOD);      $display("\n===== Special Sector Write =====");
    SetWREN();                  repeat( 5)  @(PERIOD);
    REG_DATA.ACS.ACS = 256+4-1; WrCPU(REG_ADDR.ACS,REG_DATA.ACS);   // アクセスバイト数
    REG_DATA.OTC.OTC = 256+4-1; WrCPU(REG_ADDR.OTC,REG_DATA.OTC);   // 出力バイト数
    REG_DATA.BAD.BAD = 0;       WrCPU(REG_ADDR.BAD,REG_DATA.BAD);   // バッファアドレス
    REG_DATA.BDT.BDT = 'h42;    WrCPU(REG_ADDR.BDT,REG_DATA.BDT);   // バッファデータ
    REG_DATA.BDT.BDT = 'h00;    WrCPU(REG_ADDR.BDT,REG_DATA.BDT);   // バッファデータ
    REG_DATA.BDT.BDT = 'h00;    WrCPU(REG_ADDR.BDT,REG_DATA.BDT);   // バッファデータ
    REG_DATA.BDT.BDT = 'h00;    WrCPU(REG_ADDR.BDT,REG_DATA.BDT);   // バッファデータ
    for(ei=0;ei<256;ei=ei+1)    WrCPU(REG_ADDR.BDT,PMEM[ei]);       // バッファデータ
    REG_DATA.ENB.ENB = 1;       WrCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
    repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                $display(" >>> Read : %p",REG_DATA.ENB);
    wait(CPU_IRQ===1'b1);       $display(" >>> CPU_IRQ=1");         // 割り込み待ち
    repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                $display(" >>> Read : %p",REG_DATA.ENB);
    repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                                $display(" >>> Read : %p",REG_DATA.IRS); 
    repeat( 5)  @(PERIOD);      WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータスクリア
    repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                                $display(" >>> Read : %p - Clear",REG_DATA.IRS); 
    for(ei=0;ei<256;ei=ei+1) begin
        $display(" >>> Check : [%h] => %h , %h",ei[23:0],PMEM[ei],FRAM.Smem[ei]);
        if(PMEM[ei]!==FRAM.Smem[ei])    error = error + 1;
    end
    $display(" %m >> Error : %d",error);

    // ++ メモリ データの書き込み
    // +++++++++++++++++++++++++++++++++++++++++++
    for(ei=0;ei<262144;ei=ei+1) PMEM[ei] = $random;
    repeat( 5)  @(PERIOD);      $display("\n===== Write Data to Memory =====");
    begin : Write
        integer     wi;
        ei = 0;
        wi = 0;
        while(ei < 262144 ) begin
            SetWREN();                  repeat( 5)  @(PERIOD);
            REG_DATA.ACS.ACS = 2048+4-1;WrCPU(REG_ADDR.ACS,REG_DATA.ACS);   // アクセスバイト数
            REG_DATA.OTC.OTC = 2048+4-1;WrCPU(REG_ADDR.OTC,REG_DATA.OTC);   // 出力バイト数
            REG_DATA.BAD.BAD = 0;       WrCPU(REG_ADDR.BAD,REG_DATA.BAD);   // バッファアドレス
            REG_DATA.BDT.BDT = 'h02;    WrCPU(REG_ADDR.BDT,REG_DATA.BDT);   // バッファデータ
                                        WrCPU(REG_ADDR.BDT,ei[23:16]);      // バッファデータ
                                        WrCPU(REG_ADDR.BDT,ei[15: 8]);      // バッファデータ
                                        WrCPU(REG_ADDR.BDT,ei[ 7: 0]);      // バッファデータ
            repeat(2048) begin
                                        WrCPU(REG_ADDR.BDT,PMEM[wi]);       // バッファデータ
                                        wi = wi + 1;
            end
            REG_DATA.ENB.ENB = 1;       WrCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
            repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                        $display(" >>> Read : %p",REG_DATA.ENB);
            wait(CPU_IRQ===1'b1);       $display(" >>> CPU_IRQ=1");         // 割り込み待ち
            repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                        $display(" >>> Read : %p",REG_DATA.ENB);
            repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                                        $display(" >>> Read : %p",REG_DATA.IRS); 
            repeat( 5)  @(PERIOD);      WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータスクリア
            repeat( 5)  @(PERIOD);      RdCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス
                                        $display(" >>> Read : %p - Clear",REG_DATA.IRS); 
            repeat(2048) begin
                $display(" >>> Check : [%h] => %h , %h",ei[23:0],PMEM[ei],FRAM.mem[ei]);
                if(PMEM[ei]!==FRAM.mem[ei]) error = error + 1;
                ei = ei + 1;
            end
        end
    end
    $display(" %m >> Error : %d",error);

    // ++ ステータス レジスタ書き込み
    // +++++++++++++++++++++++++++++++++++++++++++
    ei  = 0;                    PMEM[ei] = 'hFF;
    repeat( 5)  @(PERIOD);  $display("\n===== Write Status Register =====");
    SetWREN();                  repeat( 5)  @(PERIOD);
    REG_DATA.ACS.ACS    = 1;    WrCPU(REG_ADDR.ACS,REG_DATA.ACS);   // アクセスバイト数
    REG_DATA.OTC.OTC    = 1;    WrCPU(REG_ADDR.OTC,REG_DATA.OTC);   // 出力バイト数
    REG_DATA.IRM.IRM    = 1;    WrCPU(REG_ADDR.IRM,REG_DATA.IRM);   // 割り込みマスク
    REG_DATA.IRS.IRS    = 1;    WrCPU(REG_ADDR.IRS,REG_DATA.IRS);   // 割り込みステータス クリア
                                RdCPU(REG_ADDR.IRS,REG_DATA.IRS);
                                $display(" >>> Read : %p",REG_DATA.IRS);
    REG_DATA.BAD.BAD    = 0;    WrCPU(REG_ADDR.BAD,REG_DATA.BAD);   // バッファアドレス
    REG_DATA.BDT.BDT    ='h01;  WrCPU(REG_ADDR.BDT,REG_DATA.BDT);   // バッファデータ
    repeat( 5)  @(PERIOD);      WrCPU(REG_ADDR.BDT,PMEM[0]);        // バッファデータ
    REG_DATA.ENB.ENB    = 1;    WrCPU(REG_ADDR.ENB,REG_DATA.ENB);   // 処理イネーブル
                                RdCPU(REG_ADDR.ENB,REG_DATA.ENB);
    while(REG_DATA.ENB.ENB===1) begin
        repeat(20)  @(PERIOD);  RdCPU(REG_ADDR.ENB,REG_DATA.ENB);
    end
    REG_DATA.IRM.IRM    = 0;    WrCPU(REG_ADDR.IRM,REG_DATA.IRM);   // 割り込みマスク 解除
    PMEM[ei] = PMEM[ei] & 'hCC;
    $display(" >>> Check : [%h] => %h , %h",ei[23:0],PMEM[0],FRAM.realStatusRegister);
    if(PMEM[0]!==FRAM.realStatusRegister)  error = error + 1;
    $display(" %m >> Error : %d",error);

`endif
    repeat(20)  @(PERIOD);
    $finish();
end

// --
// -- Clock生成
// -------------------------------------------------------------------
    initial begin
        forever begin
            fork
                #(tpCPU_CLK)    CPU_CLK = 1;
                #(tnCPU_CLK)    CPU_CLK = 0;
                #(tcCPU_CLK)    PERIOD  = PERIOD+1;
            join
        end
    end
    initial begin
        forever begin
            fork
                #(tpSYS_CLK)    SYS_CLK = 1;
                #(tnSYS_CLK)    SYS_CLK = 0;
                #(tcSYS_CLK);
            join
        end
    end

// --
// --   ターゲットのインスタンス
// -------------------------------------------------------------------
    wire        dlyFRAM_CS_N;   DlyBuf  UnDlyFRAM_CS_N(.A(FRAM_CS_N),.Y(dlyFRAM_CS_N) );
    wire        dlyFRAM_SCLK;   DlyBuf  UnDlyFRAM_SCLK(.A(FRAM_SCLK),.Y(dlyFRAM_SCLK) );
    wire        dlyFRAM_MOSI;   DlyBuf  UnDlyFRAM_MOSI(.A(FRAM_MOSI),.Y(dlyFRAM_MOSI) );
    wire        dlyFRAM_MISO;   DlyBuf  UnDlyFRAM_MISO(.A(FRAM_MISO),.Y(dlyFRAM_MISO) );

    NML_FRAMIF TARGET(
        .CPU_RST_N  (CPU_RST_N      ),  // input   wire        CPU_RST_N   ,   // @@ CPU I/F用 リセット
        .CPU_CLK    (CPU_CLK        ),  // input   wire        CPU_CLK     ,   // @@ CPU I/F用 クロック
        .SYS_RST_N  (SYS_RST_N      ),  // input   wire        SYS_RST_N   ,   // @@ システム動作用 リセット
        .SYS_CLK    (SYS_CLK        ),  // input   wire        SYS_CLK     ,   // @@ システム動作用 クロック
        .MST_SLV    (MST_SLV        ),  // input   wire        MST_SLV     ,   // @@ Avalon-MM Master/Slave切替 [0:Master]
        .PRM_ENB    (PRM_ENB        ),  // input   wire        PRM_ENB     ,   // @@ 処理イネーブル [1→0:OFF 0→1:ON]
        .PRM_DIV    (PRM_DIV        ),  // input   wire [ 3:0] PRM_DIV     ,   // @@ シリアルクロック周波数設定
        .PRM_POL    (PRM_POL        ),  // input   wire        PRM_POL     ,   // @@ シリアルクロック極性設定
        .PRM_TAK    (PRM_TAK        ),  // input   wire [ 1:0] PRM_TAK     ,   // @@ シリアルデータ取り込みタイミング設定
        .PRM_ACS    (PRM_ACS        ),  // input   wire [20:0] PRM_ACS     ,   // @@ アクセスバイト数 (最大値:1,048,579)
        .PRM_OTC    (PRM_OTC        ),  // input   wire [ 1:0] PRM_OTC     ,   // @@ 出力バイト数 (最大値:3)
        .PRM_MSK    (PRM_MSK        ),  // input   wire        PRM_MSK     ,   // @@ 割り込みマスク
        .PRM_CLR    (PRM_CLR        ),  // input   wire        PRM_CLR     ,   // @@ 割り込みクリア
        .PRM_STA    (PRM_STA        ),  // input   wire [31:0] PRM_STA     ,   // @@ Avalon-MM(Master側) ライトベースアドレス
        .PRM_CMD    (PRM_CMD        ),  // input   wire [ 7:0] PRM_CMD     ,   // @@ RAMコマンド
        .PRM_ADD    (PRM_ADD        ),  // input   wire [23:0] PRM_ADD     ,   // @@ FRAMアドレス
        .SAV_WEN    (SAV_WEN        ),  // input   wire        SAV_WEN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ライトイネーブル
        .SAV_REN    (SAV_REN        ),  // input   wire        SAV_REN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードイネーブル
        .SAV_ADD    (SAV_ADD        ),  // input   wire [ 5:0] SAV_ADD     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リード/ライトイネーブル
        .SAV_WDT    (SAV_WDT        ),  // input   wire [31:0] SAV_WDT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ライトデータ
        .SAV_WIT    (SAV_WIT        ),  // output  wire        SAV_WIT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ウェイトリクエスト
        .SAV_RVL    (SAV_RVL        ),  // output  wire        SAV_RVL     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードデータイネーブル
        .SAV_RDT    (SAV_RDT        ),  // output  wire [31:0] SAV_RDT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードデータ
        .MAV_WEN    (MAV_WEN        ),  // output  wire        MAV_WEN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトイネーブル
        .MAV_ADD    (MAV_ADD        ),  // output  wire [31:0] MAV_ADD     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトアドレス
        .MAV_BEN    (MAV_BEN        ),  // output  wire [ 3:0] MAV_BEN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) バイトイネーブル
        .MAV_WDT    (MAV_WDT        ),  // output  wire [ 7:0] MAV_WDT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトデータ
        .MAV_WIT    (MAV_WIT        ),  // input   wire        MAV_WIT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ウェイトリクエスト
        .CPU_IRQ    (CPU_IRQ        ),  // output  wire        CPU_IRQ     ,   // @@ [CPU_CLK↑同期] 割り込み信号
        .MON_ENB    (MON_ENB        ),  // output  wire        MON_ENB     ,   // @@ [CPU_CLK↑同期] 動作モニタ [0:停止中  1:動作中]
        .FRAM_CS_N  (   FRAM_CS_N   ),  // output  reg         FRAM_CS_N   ,   // @@ [SYS_CLK↑同期]   SPI チップセレクト
        .FRAM_SCLK  (   FRAM_SCLK   ),  // output  reg         FRAM_SCLK   ,   // @@ [SYS_CLK↑同期]   SPI シリアルクロック
        .FRAM_MOSI  (   FRAM_MOSI   ),  // output  reg         FRAM_MOSI   ,   // @@ [SYS_CLK↑同期]   SPI シリアル出力
        .FRAM_MISO  (dlyFRAM_MISO   ),  // input   wire        FRAM_MISO   ,   // @@ [SYS_CLK↑同期]   SPI シリアル入力
        .FRAM_OUTE  (   FRAM_OUTE   )   // output  reg         FRAM_OUTE       // @@ [SYS_CLK↑同期]   SPI 出力イネーブル[1:出力 0:Hi-Z]
    );

    reg         FRAM_VDD = 0;
    initial begin   #1; FRAM_VDD=1; end
    FRAM_SPI FRAM(
        .CSB    (dlyFRAM_CS_N   ),
        .SCK    (dlyFRAM_SCLK   ),
        .SI     (dlyFRAM_MOSI   ),
        .SO     (   FRAM_MISO   ),
        .WPB    (1'b1           ),
        .VDD    (   FRAM_VDD    )
    );

// --
// --   Task
// -------------------------------------------------------------------
    task WrCPU(
        input   [31:0]  Addr,
        input   [31:0]  Data
    );
        reg             Comp;
        begin
            Comp = 0;
            @(posedge CPU_CLK) begin    SAV_WEN<=1; SAV_ADD<=Addr; SAV_WDT<=Data;   end
            while( Comp == 0 ) begin
                @(posedge CPU_CLK) begin
                    if( SAV_WIT===1'b0 ) begin
                        SAV_WEN<=0; SAV_ADD<=0; SAV_WDT<=0;
                        Comp<=1;
                    end
                end
                #1;
            end
        end
    endtask
    task RdCPU(
        input   [31:0]  Addr,
        output  [31:0]  Data
    );
        reg             Comp;
        begin
            Comp = 0;
            @(posedge CPU_CLK) begin    SAV_REN<=1; SAV_ADD<=Addr;  end
            while( Comp == 0 ) begin
                @(posedge CPU_CLK) begin
                    if( SAV_WIT===1'b0 ) begin
                        SAV_REN<=0; SAV_ADD<=0;
                    end
                    if( SAV_RVL===1'b1 ) begin
                        Data <= SAV_RDT;
                        Comp <= 1;
                    end
                end
                #1;
            end
        end
    endtask

endmodule

module DlyBuf
#(
    parameter   NsStep = 1,
    parameter   PsStep = 1
)
(
    input   wire    A ,
    output  wire    Y
);

    wire    [NsStep:0] SigNs;
    wire    [PsStep:0] SigPs;

    assign  SigNs[0] = A;
    generate
        genvar  ins;

        if( NsStep != 0 ) begin
            for(ins=1;ins<=NsStep;ins=ins+1) begin
                DlyBufCoreNs UnDlyNs(.A(SigNs[ins-1]),.Y(SigNs[ins]) );
            end
        end
    endgenerate

    assign  SigPs[0] = SigNs[NsStep];
    generate
        genvar  ips;

        if( PsStep != 0 ) begin
            for(ips=1;ips<=PsStep;ips=ips+1) begin
                DlyBufCorePs UnDlyPs(.A(SigPs[ips-1]),.Y(SigPs[ips]) );
            end
        end
    endgenerate

    assign  Y = SigPs[PsStep];

endmodule

module DlyBufCoreNs(input wire A, output wire Y);   assign #(1.000) Y = A;  endmodule
module DlyBufCorePs(input wire A, output wire Y);   assign #(0.001) Y = A;  endmodule

シミュレーション

ModelSimを用いて、シミュレーションを行います。
実行時のトップモジュールは、"TB_TOP"となり、RAM及びFIFOを配置しているため、Libraryをaltera_mf_verにして実行する必要があります。

実行結果は、error=0となり、データ比較に関してはエラーが0になりました。
また、アサーションとかもありますが、今回は割愛します。

シミュレーション波形

シミュレーションを実行すると以下のような波形が確認できます。
※入力信号MAV_WITは、テストベンチから常にランダムな波形を入力しています。

マスタモード デバイスID読み出し

f:id:nao-milk:20210420174952p:plain

マスタモード 特殊セクタ読み出し

f:id:nao-milk:20210420184703p:plain

マスタモードの全体

f:id:nao-milk:20210420184917p:plain
256KBアクセスで、約41msで終了します。

全ての波形を貼り付けると量が大きくなるため、割愛いたします。

ご興味のある方は、前回のソースコードと今回のテストベンチをコピペしてご利用ください。

FRAMにアクセス (Verilogコーディング編)

f:id:nao-milk:20210419152452p:plain


FRAM I/Fのverilogコーディングとなります。
ブロック仕様は以下を参照ください。
nao-milk.hatenablog.com

FRAM I/Fブロック構成

ブロック構成は以下のようになります。

f:id:nao-milk:20210416124855p:plain
ブロック図

ソースコード

verilog-HDLのソースコードを以下より示します。

FRAM I/Fブロックトップ

CPUとのI/F用クロック(入力信号CPU_CLK)は50MHz、内部動作用クロック(入力信号SYS_CLK)は200MHzとなります。
クロック乗せ換えは、処理イネーブル(???_reg_enb)と処理イネーブル クリアパルス(???_reg_fin)のみ行い、その他の信号は動作前(処理イネーブルが"1"になる前)に確定しているため、set_false_path指定します。

// **
// **   FRAM I/F
// **
// *****************************************************************************
module NML_FRAMIF(
    input   wire        CPU_RST_N   ,   // @@ CPU I/F用 リセット
    input   wire        CPU_CLK     ,   // @@ CPU I/F用 クロック
    input   wire        SYS_RST_N   ,   // @@ システム動作用 リセット
    input   wire        SYS_CLK     ,   // @@ システム動作用 クロック
    input   wire        MST_SLV     ,   // @@ Avalon-MM Master/Slave切替 [0:Master]
    input   wire        PRM_ENB     ,   // @@ 処理イネーブル [1→0:OFF 0→1:ON]
    input   wire [ 3:0] PRM_DIV     ,   // @@ シリアルクロック周波数設定
    input   wire        PRM_POL     ,   // @@ シリアルクロック極性設定
    input   wire [ 1:0] PRM_TAK     ,   // @@ シリアルデータ取り込みタイミング設定
    input   wire [20:0] PRM_ACS     ,   // @@ アクセスバイト数 (最大値:1,048,579)
    input   wire [ 1:0] PRM_OTC     ,   // @@ 出力バイト数 (最大値:3)
    input   wire        PRM_MSK     ,   // @@ 割り込みマスク
    input   wire        PRM_CLR     ,   // @@ 割り込みクリア
    input   wire [31:0] PRM_STA     ,   // @@ Avalon-MM(Master側) ライトベースアドレス
    input   wire [ 7:0] PRM_CMD     ,   // @@ RAMコマンド
    input   wire [23:0] PRM_ADD     ,   // @@ FRAMアドレス
    input   wire        SAV_WEN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ライトイネーブル
    input   wire        SAV_REN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードイネーブル
    input   wire [ 5:0] SAV_ADD     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リード/ライトイネーブル
    input   wire [31:0] SAV_WDT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ライトデータ
    output  wire        SAV_WIT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  ウェイトリクエスト
    output  wire        SAV_RVL     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードデータイネーブル
    output  wire [31:0] SAV_RDT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Slave側)  リードデータ
    output  wire        MAV_WEN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトイネーブル
    output  wire [31:0] MAV_ADD     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトアドレス
    output  wire [ 3:0] MAV_BEN     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) バイトイネーブル
    output  wire [ 7:0] MAV_WDT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ライトデータ
    input   wire        MAV_WIT     ,   // @@ [CPU_CLK↑同期] Avalon-MM(Master側) ウェイトリクエスト
    output  wire        CPU_IRQ     ,   // @@ [CPU_CLK↑同期] 割り込み信号
    output  wire        MON_ENB     ,   // @@ [CPU_CLK↑同期] 動作モニタ [0:停止中  1:動作中]
    output  wire        FRAM_CS_N   ,   // @@ [SYS_CLK↑同期] FRAM SPI チップセレクト
    output  wire        FRAM_SCLK   ,   // @@ [SYS_CLK↑同期] FRAM SPI シリアルクロック
    output  wire        FRAM_MOSI   ,   // @@ [SYS_CLK↑同期] FRAM SPI シリアル出力
    input   wire        FRAM_MISO   ,   // @@ [SYS_CLK↑同期] FRAM SPI シリアル入力
    output  wire        FRAM_OUTE       // @@ [SYS_CLK↑同期] FRAM SPI 出力イネーブル[1:出力 0:Hi-Z]
);

    parameter   P_BUF_ADBIT = 12    ;   // @@ バッファのアドレスbit幅
    parameter   P_BUF_SIZE  = 4095  ;   // @@ バッファサイズ (サイス-1)
    wire        asi_reg_enb,spi_reg_enb;
    wire [ 3:0] asi_reg_div,spi_reg_div;
    wire        asi_reg_pol,spi_reg_pol;
    wire [ 1:0] asi_reg_tak,spi_reg_tak;
    wire [20:0] asi_reg_acs,spi_reg_acs;
    wire [20:0] asi_reg_otc,spi_reg_otc;
    wire        asi_reg_fin,spi_reg_fin;
    wire        asi_buf_wen,spi_buf_wen;
    wire        asi_buf_ren,spi_buf_ren;
    wire [20:0] asi_buf_add,spi_buf_add;
    wire [ 7:0] asi_buf_wdt,spi_buf_wdt;
    wire        asi_buf_rvl,spi_buf_rvl;
    wire [ 7:0] asi_buf_rdt,spi_buf_rdt;
    wire        asi_buf_fcr;
    wire        asi_ami_str;
    wire        ami_buf_emp;
    wire        ami_buf_rvl;
    wire [ 7:0] ami_buf_rdt;
    wire        ami_buf_ren;

    // @@
    // @@ Avalon-MM Slave I/F
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    NML_FRAMIF_ASI
    #(
        .P_BUF_ADBIT    (P_BUF_ADBIT    ),  // parameter   P_BUF_ADBIT = 12    ,   // @@ バッファのアドレスbit幅
        .P_BUF_SIZE     (P_BUF_SIZE     )   // parameter   P_BUF_SIZE  = 4095      // @@ バッファサイズ (サイス-1)
    )
    UnASI(
        .RESET_N        (CPU_RST_N      ),  // input   wire        RESET_N     ,   // @@ システムリセット
        .CLOCK          (CPU_CLK        ),  // input   wire        CLOCK       ,   // @@ システムクロック
        .MST_SLV        (MST_SLV        ),  // input   wire        MST_SLV     ,   // @@ FRAMIF周辺 Avalon-MM Master/Slave切替 [0:Master]
        .PRM_ENB        (PRM_ENB        ),  // input   wire        PRM_ENB     ,   // @@ FRAMIF周辺 処理イネーブル [1→0:OFF 0→1:ON]
        .PRM_DIV        (PRM_DIV        ),  // input   wire [ 3:0] PRM_DIV     ,   // @@ FRAMIF周辺 シリアルクロック周波数設定
        .PRM_POL        (PRM_POL        ),  // input   wire        PRM_POL     ,   // @@ FRAMIF周辺 シリアルクロック極性設定
        .PRM_TAK        (PRM_TAK        ),  // input   wire [ 1:0] PRM_TAK     ,   // @@ FRAMIF周辺 シリアルデータ取り込みタイミング設定
        .PRM_ACS        (PRM_ACS        ),  // input   wire [20:0] PRM_ACS     ,   // @@ FRAMIF周辺 アクセスバイト数 (最大値:1,048,579)
        .PRM_OTC        (PRM_OTC        ),  // input   wire [ 1:0] PRM_OTC     ,   // @@ FRAMIF周辺 出力バイト数 (最大値:3)
        .PRM_MSK        (PRM_MSK        ),  // input   wire        PRM_MSK     ,   // @@ FRAMIF周辺 割り込みマスク
        .PRM_CLR        (PRM_CLR        ),  // input   wire        PRM_CLR     ,   // @@ FRAMIF周辺 割り込みクリア
        .AVA_WEN        (SAV_WEN        ),  // input   wire        AVA_WEN     ,   // @@ Avalon-MM ライトイネーブル
        .AVA_REN        (SAV_REN        ),  // input   wire        AVA_REN     ,   // @@ Avalon-MM リードイネーブル
        .AVA_ADD        (SAV_ADD        ),  // input   wire [ 5:0] AVA_ADD     ,   // @@ Avalon-MM リード/ライトイネーブル
        .AVA_WDT        (SAV_WDT        ),  // input   wire [31:0] AVA_WDT     ,   // @@ Avalon-MM ライトデータ
        .AVA_WIT        (SAV_WIT        ),  // output  wire        AVA_WIT     ,   // @@ Avalon-MM ウェイトリクエスト
        .AVA_RVL        (SAV_RVL        ),  // output  reg         AVA_RVL     ,   // @@ Avalon-MM リードデータイネーブル
        .AVA_RDT        (SAV_RDT        ),  // output  reg  [31:0] AVA_RDT     ,   // @@ Avalon-MM リードデータ
        .REG_ENB        (asi_reg_enb    ),  // output  reg         REG_ENB     ,   // @@ レジスタ  処理イネーブル
        .REG_DIV        (asi_reg_div    ),  // output  reg  [ 3:0] REG_DIV     ,   // @@ レジスタ  シリアルクロック分周
        .REG_POL        (asi_reg_pol    ),  // output  reg  [ 1:0] REG_POL     ,   // @@ レジスタ  SPI Mode (0 又は 3のみ受付可能)
        .REG_TAK        (asi_reg_tak    ),  // output  reg  [ 1:0] REG_TAK     ,   // @@ レジスタ  シリアルデータ取り込みタイミング
        .REG_ACS        (asi_reg_acs    ),  // output  reg  [20:0] REG_ACS     ,   // @@ レジスタ  アクセスバイト数(設定値+1)
        .REG_OTC        (asi_reg_otc    ),  // output  reg  [20:0] REG_OTC     ,   // @@ レジスタ  出力バイト数(設定値+1)
        .REG_FIN        (asi_reg_fin    ),  // input   wire        REG_FIN     ,   // @@ レジスタ  処理イネーブル クリアパルス
        .BUF_WEN        (asi_buf_wen    ),  // output  wire        BUF_WEN     ,   // @@ バッファ  ライトイネーブル
        .BUF_REN        (asi_buf_ren    ),  // output  wire        BUF_REN     ,   // @@ バッファ  リードイネーブル
        .BUF_ADD        (asi_buf_add    ),  // output  reg  [20:0] BUF_ADD     ,   // @@ バッファ  リード/ライトアドレス
        .BUF_WDT        (asi_buf_wdt    ),  // output  wire [ 7:0] BUF_WDT     ,   // @@ バッファ  ライトデータ
        .BUF_RVL        (asi_buf_rvl    ),  // input   wire        BUF_RVL     ,   // @@ バッファ  リードデータ有効パルス
        .BUF_RDT        (asi_buf_rdt    ),  // input   wire [ 7:0] BUF_RDT         // @@ バッファ  リードデータ
        .BUF_FCR        (asi_buf_fcr    ),  // output  reg         BUF_FCR     ,   // @@ バッファ   FIFOクリア(レベル信号)
        .AMI_STR        (asi_ami_str    ),  // output  reg         AMI_STR     ,   // @@ Master     開始パルス
        .CPU_IRQ        (CPU_IRQ        ),  // output  reg         CPU_IRQ     ,   // @@ 転送完了割り込み信号(レベル割り込み)
        .MON_ENB        (MON_ENB        )   // output  reg         MON_ENB         // @@ 動作モニタ [0:停止中  1:動作中]
    );

    // @@
    // @@ Avalon-MM Master I/F
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    NML_FRAMIF_AMI UnAMI(
        .RESET_N        (CPU_RST_N      ),  // input   wire        RESET_N     ,   // @@ システムリセット
        .CLOCK          (CPU_CLK        ),  // input   wire        CLOCK       ,   // @@ システムクロック
        .PRM_STA        (PRM_STA        ),  // input   wire [31:0] PRM_STA     ,   // @@ Avalon-MM(Master側) ライトベースアドレス
        .AMI_STR        (asi_ami_str    ),  // input   wire        AMI_STR     ,   // @@ Master     開始パルス
        .EMP_AMI        (ami_buf_emp    ),  // input   wire        EMP_AMI     ,   // @@ FRAMIF_AMI用 FIFOエンプティ
        .RVL_AMI        (ami_buf_rvl    ),  // input   wire        RVL_AMI     ,   // @@ FRAMIF_AMI用 リードデータ有効パルス
        .RDT_AMI        (ami_buf_rdt    ),  // input   wire [ 7:0] RDT_AMI     ,   // @@ FRAMIF_AMI用 リードデータ
        .REN_AMI        (ami_buf_ren    ),  // output  reg         REN_AMI     ,   // @@ FRAMIF_AMI用 リードリクエスト
        .AVA_WEN        (MAV_WEN        ),  // output  reg         AVA_WEN     ,   // @@ Avalon-MM(Master側) ライトイネーブル
        .AVA_ADD        (MAV_ADD        ),  // output  reg  [31:0] AVA_ADD     ,   // @@ Avalon-MM(Master側) ライトアドレス
        .AVA_BEN        (MAV_BEN        ),  // output  reg  [ 3:0] AVA_BEN     ,   // @@ Avalon-MM(Master側) バイトイネーブル
        .AVA_WDT        (MAV_WDT        ),  // output  reg  [ 7:0] AVA_WDT     ,   // @@ Avalon-MM(Master側) ライトデータ
        .AVA_WIT        (MAV_WIT        )   // input   wire        AVA_WIT         // @@ Avalon-MM(Master側) ウェイトリクエスト
    );

    // @@
    // @@ クロック乗せ換え
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

    // ++ Buffer
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    NML_FRAMIF_BUF
    #(
        .P_BUF_ADBIT    (P_BUF_ADBIT    )   // parameter   P_BUF_ADBIT = 12                    // @@ バッファのアドレスbit幅
    )
    UnBUF(
        .CPU_RST_N      (CPU_RST_N      ),  // input   wire        RST_REG_N   ,   // @@ CPU I/F用 リセット
        .CPU_CLK        (CPU_CLK        ),  // input   wire        CLK_REG     ,   // @@ CPU I/F用 クロック
        .SYS_RST_N      (SYS_RST_N      ),  // input   wire        RST_SPI_N   ,   // @@ システム動作用 リセット
        .SYS_CLK        (SYS_CLK        ),  // input   wire        CLK_SPI     ,   // @@ システム動作用 クロック
        .MST_SLV        (MST_SLV        ),  // input   wire        MST_SLV     ,   // @@ FRAMIF周辺   Avalon-MM Master/Slave切替 [0:Master]
        .PRM_CMD        (PRM_CMD        ),  // input   wire [ 7:0] PRM_CMD     ,   // @@ FRAMIF周辺   FRAMコマンド
        .PRM_ADD        (PRM_ADD        ),  // input   wire [23:0] PRM_ADD     ,   // @@ FRAMIF周辺   FRAMアドレス
        .BUF_FCR        (asi_buf_fcr    ),  // input   wire        BUF_FCR     ,   // @@ バッファ     FIFOクリア(レベル信号)
        .REN_AMI        (ami_buf_ren    ),  // input   wire        REN_AMI     ,   // @@ FRAMIF_AMI用 リードリクエスト
        .EMP_AMI        (ami_buf_emp    ),  // output  wire        EMP_AMI     ,   // @@ FRAMIF_AMI用 FIFOエンプティ
        .RVL_AMI        (ami_buf_rvl    ),  // output  reg         RVL_AMI     ,   // @@ FRAMIF_AMI用 リードデータ有効パルス
        .RDT_AMI        (ami_buf_rdt    ),  // output  wire [ 7:0] RDT_AMI     ,   // @@ FRAMIF_AMI用 リードデータ
        .WEN_ASI        (asi_buf_wen    ),  // input   wire        WEN_ASI     ,   // @@ FRAMIF_ASI用 ライトイネーブル
        .REN_ASI        (asi_buf_ren    ),  // input   wire        REN_ASI     ,   // @@ FRAMIF_ASI用 リードイネーブル
        .ADD_ASI        (asi_buf_add    ),  // input   wire [20:0] ADD_ASI     ,   // @@ FRAMIF_ASI用 リード/ライトアドレス
        .WDT_ASI        (asi_buf_wdt    ),  // input   wire [ 7:0] WDT_ASI     ,   // @@ FRAMIF_ASI用 ライトデータ
        .RVL_ASI        (asi_buf_rvl    ),  // output  reg         RVL_ASI     ,   // @@ FRAMIF_ASI用 リードデータ有効パルス
        .RDT_ASI        (asi_buf_rdt    ),  // output  wire [ 7:0] RDT_ASI     ,   // @@ FRAMIF_ASI用 リードデータ
        .WEN_SPI        (spi_buf_wen    ),  // input   wire        WEN_SPI     ,   // @@ FRAMIF_SPI用 ライトイネーブル
        .REN_SPI        (spi_buf_ren    ),  // input   wire        REN_SPI     ,   // @@ FRAMIF_SPI用 リードイネーブル
        .ADD_SPI        (spi_buf_add    ),  // input   wire [20:0] ADD_SPI     ,   // @@ FRAMIF_SPI用 リード/ライトアドレス
        .WDT_SPI        (spi_buf_wdt    ),  // input   wire [ 7:0] WDT_SPI     ,   // @@ FRAMIF_SPI用 ライトデータ
        .RVL_SPI        (spi_buf_rvl    ),  // output  reg         RVL_SPI     ,   // @@ FRAMIF_SPI用 リードデータ有効パルス
        .RDT_SPI        (spi_buf_rdt    )   // output  reg  [ 7:0] RDT_SPI         // @@ FRAMIF_SPI用 リードデータ
    );

    // ++ Register
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    NML_FRAMIF_EXC #(.P_WIDTH( 1),.P_INIT( 1'b0)) UnEXC_ENB(.RI(CPU_RST_N),.CI(CPU_CLK),.DI(asi_reg_enb),
                                                            .RO(SYS_RST_N),.CO(SYS_CLK),.DO(spi_reg_enb)  );
    assign  spi_reg_div   = asi_reg_div;
    assign  spi_reg_pol   = asi_reg_pol;
    assign  spi_reg_tak   = asi_reg_tak;
    assign  spi_reg_acs   = asi_reg_acs;
    assign  spi_reg_otc   = asi_reg_otc;
    NML_FRAMIF_EXC #(.P_WIDTH( 1),.P_INIT( 1'b0)) UnEXC_FIN(.RO(CPU_RST_N),.CO(CPU_CLK),.DO(asi_reg_fin),
                                                            .RI(SYS_RST_N),.CI(SYS_CLK),.DI(spi_reg_fin)  );


    // @@
    // @@ SPI I/F
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    NML_FRAMIF_SPI
    #(
        .P_WAITC        (3'd7           ),  // parameter   P_WAITC     = 3'd7  ,   // @@ REG_ENB↑からアクセス開始までの時間
        .P_FINWT        (4'd8           )   // parameter   P_FINWT     = 4'd8      // @@ REG_FINのHighパルス幅
    )
    UnSPI(
        .RESET_N        (SYS_RST_N      ),  // input   wire        RESET_N     ,   // @@ システムリセット
        .CLOCK          (SYS_CLK        ),  // input   wire        CLOCK       ,   // @@ システムクロック              [最大周波数=200MHz]
        .FRAM_CS_N      (FRAM_CS_N      ),  // output  reg         FRAM_CS_N   ,   // @@ FRAM SPI  チップセレクト
        .FRAM_SCLK      (FRAM_SCLK      ),  // output  reg         FRAM_SCLK   ,   // @@ FRAM SPI  シリアルクロック    [最大周波数= 50MHz]
        .FRAM_MOSI      (FRAM_MOSI      ),  // output  reg         FRAM_MOSI   ,   // @@ FRAM SPI  シリアル出力
        .FRAM_MISO      (FRAM_MISO      ),  // input   wire        FRAM_MISO   ,   // @@ FRAM SPI  シリアル入力
        .FRAM_OUTE      (FRAM_OUTE      ),  // output  reg         FRAM_OUTE   ,   // @@ FRAM SPI  出力イネーブル      [1:出力 0:Hi-Z]
        .REG_ENB        (spi_reg_enb    ),  // input   wire        REG_ENB     ,   // @@ レジスタ  処理イネーブル
        .REG_DIV        (spi_reg_div    ),  // input   wire [ 3:0] REG_DIV     ,   // @@ レジスタ  シリアルクロック分周
        .REG_POL        (spi_reg_pol    ),  // input   wire        REG_POL     ,   // @@ レジスタ  シリアルクロック極性
        .REG_TAK        (spi_reg_tak    ),  // input   wire [ 1:0] REG_TAK     ,   // @@ レジスタ  シリアルデータ取り込みタイミング
        .REG_ACS        (spi_reg_acs    ),  // input   wire [20:0] REG_ACS     ,   // @@ レジスタ  アクセスバイト数(設定値+1)
        .REG_OTC        (spi_reg_otc    ),  // input   wire [20:0] REG_OTC     ,   // @@ レジスタ  出力バイト数(設定値+1)
        .REG_FIN        (spi_reg_fin    ),  // output  reg         REG_FIN     ,   // @@ レジスタ  処理イネーブル クリアパルス
        .BUF_WEN        (spi_buf_wen    ),  // output  reg         BUF_WEN     ,   // @@ バッファ  ライトイネーブル
        .BUF_REN        (spi_buf_ren    ),  // output  reg         BUF_REN     ,   // @@ バッファ  リードイネーブル
        .BUF_ADD        (spi_buf_add    ),  // output  reg  [20:0] BUF_ADD     ,   // @@ バッファ  リード/ライトアドレス
        .BUF_WDT        (spi_buf_wdt    ),  // output  reg  [ 7:0] BUF_WDT     ,   // @@ バッファ  ライトデータ
        .BUF_RVL        (spi_buf_rvl    ),  // input   wire        BUF_RVL     ,   // @@ バッファ  リードデータ有効パルス
        .BUF_RDT        (spi_buf_rdt    )   // input   wire [ 7:0] BUF_RDT         // @@ バッファ  リードデータ
    );

endmodule

Avalon-MM Slave I/F

レジスタの配置、Bufferブロックの内蔵RAMのリード/ライト制御を行います。

レジスタは、入力信号MST_SLV[0:Master 1:Salve]に応じて、入力信号PRM_???の入力値 又はCPUからライトされた値を保存します。

// **
// **   Avalon-MM Slave I/F for FRAM
// **
// *****************************************************************************
module NML_FRAMIF_ASI
#(
    parameter   P_BUF_ADBIT = 12    ,   // @@ バッファのアドレスbit幅
    parameter   P_BUF_SIZE  = 4095      // @@ バッファサイズ (サイス-1)
)(
    input   wire        RESET_N     ,   // @@ システムリセット
    input   wire        CLOCK       ,   // @@ システムクロック
    input   wire        MST_SLV     ,   // @@ FRAMIF周辺 Avalon-MM Master/Slave切替 [0:Master]
    input   wire        PRM_ENB     ,   // @@ FRAMIF周辺 処理イネーブル [1→0:OFF 0→1:ON]
    input   wire [ 3:0] PRM_DIV     ,   // @@ FRAMIF周辺 シリアルクロック周波数設定
    input   wire        PRM_POL     ,   // @@ FRAMIF周辺 シリアルクロック極性設定
    input   wire [ 1:0] PRM_TAK     ,   // @@ FRAMIF周辺 シリアルデータ取り込みタイミング設定
    input   wire [20:0] PRM_ACS     ,   // @@ FRAMIF周辺 アクセスバイト数 (最大値:1,048,579)
    input   wire [ 1:0] PRM_OTC     ,   // @@ FRAMIF周辺 出力バイト数 (最大値:3)
    input   wire        PRM_MSK     ,   // @@ FRAMIF周辺 割り込みマスク
    input   wire        PRM_CLR     ,   // @@ FRAMIF周辺 割り込みクリア
    input   wire        AVA_WEN     ,   // @@ Avalon-MM  ライトイネーブル
    input   wire        AVA_REN     ,   // @@ Avalon-MM  リードイネーブル
    input   wire [ 5:0] AVA_ADD     ,   // @@ Avalon-MM  リード/ライトイネーブル
    input   wire [31:0] AVA_WDT     ,   // @@ Avalon-MM  ライトデータ
    output  wire        AVA_WIT     ,   // @@ Avalon-MM  ウェイトリクエスト
    output  reg         AVA_RVL     ,   // @@ Avalon-MM  リードデータイネーブル
    output  reg  [31:0] AVA_RDT     ,   // @@ Avalon-MM  リードデータ
    output  reg         REG_ENB     ,   // @@ レジスタ   処理イネーブル
    output  reg  [ 3:0] REG_DIV     ,   // @@ レジスタ   シリアルクロック分周
    output  reg         REG_POL     ,   // @@ レジスタ   SPI Mode (0 又は 3のみ受付可能)
    output  reg  [ 1:0] REG_TAK     ,   // @@ レジスタ   シリアルデータ取り込みタイミング
    output  reg  [20:0] REG_ACS     ,   // @@ レジスタ   アクセスバイト数(設定値+1)
    output  reg  [20:0] REG_OTC     ,   // @@ レジスタ   出力バイト数(設定値+1)
    input   wire        REG_FIN     ,   // @@ レジスタ   処理イネーブル クリアパルス
    output  wire        BUF_WEN     ,   // @@ バッファ   ライトイネーブル
    output  wire        BUF_REN     ,   // @@ バッファ   リードイネーブル
    output  reg  [20:0] BUF_ADD     ,   // @@ バッファ   リード/ライトアドレス
    output  wire [ 7:0] BUF_WDT     ,   // @@ バッファ   ライトデータ
    input   wire        BUF_RVL     ,   // @@ バッファ   リードデータ有効パルス
    input   wire [ 7:0] BUF_RDT     ,   // @@ バッファ   リードデータ
    output  reg         BUF_FCR     ,   // @@ バッファ   FIFOクリア(レベル信号)
    output  reg         AMI_STR     ,   // @@ Master     開始パルス
    output  reg         CPU_IRQ     ,   // @@ 転送完了割り込み信号(レベル割り込み)
    output  reg         MON_ENB         // @@ 動作モニタ [0:停止中  1:動作中]
);

    // @@
    // @@ 外部パラメータの取り込み  (動作中に変更しないため、False Path)
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg         in_slv;
    reg         in_enb;
    reg  [ 3:0] in_div;
    reg         in_pol;
    reg  [ 1:0] in_tak;
    reg  [20:0] in_acs;
    reg  [ 1:0] in_otc;
    reg         in_msk;
    reg         in_clr;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            in_slv <=  1'd0;
            in_enb <=  1'd0;
            in_div <=  4'd0;
            in_pol <=  1'd0;
            in_tak <=  2'd0;
            in_acs <= 21'd0;
            in_otc <=  2'd0;
            in_msk <=  1'd0;
            in_clr <=  1'd0;
        end
        else begin
            in_slv <= MST_SLV;
            in_enb <= PRM_ENB;
            in_div <= PRM_DIV;
            in_pol <= PRM_POL;
            in_tak <= PRM_TAK;
            in_acs <= PRM_ACS;
            in_otc <= PRM_OTC;
            in_msk <= PRM_MSK;
            in_clr <= PRM_CLR;
        end
    end

    reg         mst_enbl;
    reg         mst_enbl_dly1;
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            mst_enbl        <= 1'b0;
            mst_enbl_dly1   <= 1'b0;
        end
        else begin
            mst_enbl        <= ( ~in_slv ) ?    in_enb : 1'b0;
            mst_enbl_dly1   <= mst_enbl;
        end
    end
    wire        mst_enbl_set1   = ( ( mst_enbl) & (~mst_enbl_dly1) );
    wire        mst_enbl_set0   = ( (~mst_enbl) & ( mst_enbl_dly1) );

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                  AMI_STR <= 1'b0;
        else                            AMI_STR <= mst_enbl_set1;
    end
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                  BUF_FCR <= 1'b1;
        else                            BUF_FCR <= ~mst_enbl;
    end

    // @@
    // @@ Avalon-MM I/F
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg         in_ava_wen;
    reg         in_ava_ren;
    reg  [ 5:0] in_ava_add;
    reg  [31:0] in_ava_wdt;
    wire        ot_ava_wit;
    wire        ot_ava_rvl;
    wire [31:0] ot_ava_rdt;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            in_ava_wen <= 1'b0;
            in_ava_ren <= 1'b0;
            in_ava_add <= 6'h00;
            in_ava_wdt <= 32'd0;
        end
        else begin
            in_ava_wen <= AVA_WEN;
            in_ava_ren <= AVA_REN;
            in_ava_add <= AVA_ADD;
            in_ava_wdt <= AVA_WDT;
        end
    end
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            AVA_RVL <= 1'b0;
            AVA_RDT <= 32'd0;
        end
        else begin
            AVA_RVL <= ot_ava_rvl;
            AVA_RDT <= ot_ava_rdt;
        end
    end
    assign  AVA_WIT = ( AVA_REN & ot_ava_wit );

    // ++ アクセス制御
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg         acc_wen;
    reg         acc_ren;
    wire [ 3:0] acc_add = in_ava_add[5:2];
    wire [31:0] acc_wdt = in_ava_wdt;
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            acc_wen <= 1'b0;
            acc_ren <= 1'b0;
        end
        else begin
            acc_wen <= ( AVA_WEN & (~in_ava_wen) ) ?   1'b1 : 1'b0;
            acc_ren <= ( AVA_REN & (~in_ava_ren) ) ?   1'b1 : 1'b0;
        end
    end

    // @@
    // @@ レジスタ/バッファ制御
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    // ++ レジスタマップ
    // ++
    // ++   0x00    : 処理イネーブル                    [   0]=                 REG_ENB
    // ++   0x04    : 制御パラメータ                    [ 6:0]={REG_TAK,REG_POL,REG_DIV}
    // ++   0x08    : アクセスバイト数                  [20:0]=                 REG_ACS
    // ++   0x0C    : 出力バイト数                      [20:0]=                 REG_OTC
    // ++   0x10    : 割り込みステータス                [   0]=転送完了
    // ++   0x14    : 割り込みマスク                    [   0]=転送完了マスク   [1:マスク]
    // ++   0x18    : バッファリード/ライトアドレス
    // ++   0x1C    : バッファリード/ライトデータ
    // ++   0x20-3F : 予約(アクセス禁止)

    // ++ リード/ライト制御 (デコード)
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    wire        set_enb = ( in_slv & acc_wen & (acc_add==4'h0) );
    wire        set_cnt = ( in_slv & acc_wen & (acc_add==4'h1) );
    wire        set_acs = ( in_slv & acc_wen & (acc_add==4'h2) );
    wire        set_otc = ( in_slv & acc_wen & (acc_add==4'h3) );
    wire        set_irs = ( in_slv & acc_wen & (acc_add==4'h4) & acc_wdt[0] );
    wire        set_irm = ( in_slv & acc_wen & (acc_add==4'h5) );
    wire        set_bad = ( in_slv & acc_wen & (acc_add==4'h6) );
    wire        set_bdt = ( in_slv & acc_wen & (acc_add==4'h7) );

    wire        get_enb = ( acc_ren & (acc_add==4'h0) );
    wire        get_cnt = ( acc_ren & (acc_add==4'h1) );
    wire        get_acs = ( acc_ren & (acc_add==4'h2) );
    wire        get_otc = ( acc_ren & (acc_add==4'h3) );
    wire        get_irs = ( acc_ren & (acc_add==4'h4) );
    wire        get_irm = ( acc_ren & (acc_add==4'h5) );
    wire        get_bad = ( acc_ren & (acc_add==4'h6) );
    wire        get_bdt = ( acc_ren & (acc_add==4'h7) );

    // ++ バッファ制御
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    assign  BUF_WEN = set_bdt;
    assign  BUF_REN = get_bdt;
    assign  BUF_WDT = acc_wdt[7:0];
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                  BUF_ADD <= 21'd0;
        else if( set_bad )              BUF_ADD <= in_ava_wdt[20:0];
        else if( set_bdt | get_bdt )    BUF_ADD <= BUF_ADD + 21'd1;
    end

    // ++ レジスタライト制御
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg         reg_irs;    // 割り込みステータス
    reg         reg_irm;    // 割り込みマスク
    wire [31:0] reg_clp = ( acc_wdt > P_BUF_SIZE ) ?    P_BUF_SIZE : acc_wdt;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                  REG_ENB <= 1'b0;
        else if( REG_FIN )              REG_ENB <= 1'b0;
        else if( mst_enbl_set0 )        REG_ENB <= 1'b0;
        else if( mst_enbl_set1 )        REG_ENB <= 1'b1;
        else if( set_enb )              REG_ENB <= acc_wdt[0];
    end
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                  reg_irs <= 1'b0;
        else if( ~in_slv & in_clr )     reg_irs <= 1'b0;
        else if( set_irs )              reg_irs <= 1'b0;
        else if( REG_FIN )              reg_irs <= 1'b1;
    end

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                  MON_ENB <= 1'b0;
        else                            MON_ENB <= REG_ENB;
    end
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                  CPU_IRQ <= 1'b0;
        else                            CPU_IRQ <= ( reg_irm ) ?    1'b0 : reg_irs;
    end

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            REG_TAK <=  2'd0;
            REG_POL <=  1'd0;
            REG_DIV <=  4'd0;
            REG_ACS <= 21'd0;
            REG_OTC <= 21'd0;
            reg_irm <=  1'b1;
        end
        else begin
            REG_TAK <= (~in_slv) ?         in_tak   : (set_cnt) ?   acc_wdt[ 6: 5] : REG_TAK;
            REG_POL <= (~in_slv) ?         in_pol   : (set_cnt) ?   acc_wdt[    4] : REG_POL;
            REG_DIV <= (~in_slv) ?         in_div   : (set_cnt) ?   acc_wdt[ 3: 0] : REG_DIV;
            REG_ACS <= (~in_slv) ?         in_acs   : (set_acs) ?   reg_clp[20: 0] : REG_ACS;
            REG_OTC <= (~in_slv) ?  {19'd0,in_otc}  : (set_otc) ?   acc_wdt[20: 0] : REG_OTC;
            reg_irm <= (~in_slv) ?         in_msk   : (set_irm) ?   acc_wdt[    0] : reg_irm;
       end
    end

    // ++ レジスタリード制御
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    assign  ot_ava_rvl  = ( acc_ren & (~get_bdt) ) | BUF_RVL;
    assign  ot_ava_rdt  = ( ( get_enb ) ?   { {31{1'b0}},                 REG_ENB  } : 32'd0 )
                        | ( ( get_cnt ) ?   { {25{1'b0}},{REG_TAK,REG_POL,REG_DIV} } : 32'd0 )
                        | ( ( get_acs ) ?   { {11{1'b0}},                 REG_ACS  } : 32'd0 )
                        | ( ( get_otc ) ?   { {11{1'b0}},                 REG_OTC  } : 32'd0 )
                        | ( ( get_irs ) ?   { {31{1'b0}},                 reg_irs  } : 32'd0 )
                        | ( ( get_irm ) ?   { {31{1'b0}},                 reg_irm  } : 32'd0 )
                        | ( ( get_bad ) ?   { {11{1'b0}},                 BUF_ADD  } : 32'd0 )
                        | ( ( BUF_RVL ) ?   { {24{1'b0}},                 BUF_RDT  } : 32'd0 )
                        ;
    assign  ot_ava_wit  = ~ot_ava_rvl;

endmodule

Avalon-MM Master I/F

Bufferブロック内のFIFOからのエンプティ信号(入力信号EMP_AMI)がNot Empty(EMP_AMI="0")になると動作を開始します。
シーケンス管理は、内部信号seq[1:0]で行います。

  1. 内部信号seq=0:FIFO Not Empty待ち
  2. 内部信号seq=1:FIFO リードデータ取得待ち
  3. 内部信号seq=2:Avalon-MM Wait信号解除待ち

各状態の遷移時に、FIFOにリードイネーブルを生成し、Avaon-MMの制御信号をアサートします。

// **
// **   Avalon-MM Master I/F for FRAM
// **
// *****************************************************************************
module NML_FRAMIF_AMI(
    input   wire        RESET_N     ,   // @@ システムリセット
    input   wire        CLOCK       ,   // @@ システムクロック
    input   wire [31:0] PRM_STA     ,   // @@ Avalon-MM(Master側) ライトベースアドレス
    input   wire        AMI_STR     ,   // @@ Master     開始パルス
    input   wire        EMP_AMI     ,   // @@ FRAMIF_AMI用 FIFOエンプティ
    input   wire        RVL_AMI     ,   // @@ FRAMIF_AMI用 リードデータ有効パルス
    input   wire [ 7:0] RDT_AMI     ,   // @@ FRAMIF_AMI用 リードデータ
    output  reg         REN_AMI     ,   // @@ FRAMIF_AMI用 リードリクエスト
    output  reg         AVA_WEN     ,   // @@ Avalon-MM(Master側) ライトイネーブル
    output  reg  [31:0] AVA_ADD     ,   // @@ Avalon-MM(Master側) ライトアドレス
    output  reg  [ 3:0] AVA_BEN     ,   // @@ Avalon-MM(Master側) バイトイネーブル
    output  reg  [ 7:0] AVA_WDT     ,   // @@ Avalon-MM(Master側) ライトデータ
    input   wire        AVA_WIT         // @@ Avalon-MM(Master側) ウェイトリクエスト
);

    // @@
    // @@ シーケンス
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg  [ 1:0] seq;
    wire        seq_str = ( (seq==2'd0) & (~EMP_AMI) );     // FIFO にデータ格納
    wire        seq_get = ( (seq==2'd1) & ( RVL_AMI) );     // FIFO のデータ取得
    wire        seq_end = ( (seq==2'd2) & (~AVA_WIT) );     // Avalon-MM 完了

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                  seq <= 2'd0;
        else if( AMI_STR | seq_end )    seq <= 2'd0;
        else if( seq_str | seq_get )    seq <= seq + 2'd1;
        else                            seq <= seq;
    end

    // @@
    // @@ バッファ制御
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                  REN_AMI <= 1'd0;
        else                            REN_AMI <= seq_str;
    end

    // @@
    // @@ Avalon-MM制御
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg  [31:0] add;
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                  add <= 32'd0;
        else if( AMI_STR )              add <= PRM_STA;
        else if( seq_get )              add <= add + 32'd1;
        else                            add <= add;
    end

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            AVA_WEN <=  1'd0;
            AVA_ADD <= 32'd0;
            AVA_BEN <=  4'd0;
            AVA_WDT <=  8'd0;
        end
        else if( seq_end ) begin
            AVA_WEN <=  1'd0;
            AVA_ADD <= 32'd0;
            AVA_BEN <=  4'd0;
            AVA_WDT <=  8'd0;
        end
        else if( seq_get ) begin
            AVA_WEN <=  1'd1;
            AVA_ADD <=  add;
            AVA_BEN <=  (add[1:0]==2'd0) ?  4'b0001 :
                        (add[1:0]==2'd1) ?  4'b0010 :
                        (add[1:0]==2'd2) ?  4'b0100 : 4'b1000;
            AVA_WDT <=  RDT_AMI;
        end
        else begin
            AVA_WEN <= AVA_WEN;
        end
    end

endmodule

Buffer

Quartusで生成したRAMマクロとFIFOマクロを配置します。
このマクロでCPU_CLK(50MHz)⇔SYS_CLK(200MHz)の乗せ換えを行います。

RAMは、Avalon-MM Slave I/FブロックとSPI I/Fブロックの送受信データを保存するためのメモリであり、FIFOはSPI I/Fブロックで取得したデータをAvalon-MM Master I/Fブロックへ送るためのメモリとなります。

入力信号MST_SLVの入力値に応じて、SPI I/Fブロックからのライト動作をRAMにするか、FIFOにするか切り替えます。

リードデータ有効パルス(出力信号RVL_AMI,RVL_ASI,RVL_SPI)は、リードデータラッチ側でリードデータ遅延を意識しなくても良いように、Bufferブロックでリードデータ取り込み用パルスを生成します。

// **
// **   Buffer for FRAM
// **
// *****************************************************************************
module NML_FRAMIF_BUF
#(
    parameter   P_BUF_ADBIT = 12        // @@ バッファのアドレスbit幅
)(
    input   wire        CPU_RST_N   ,   // @@ CPU I/F用 リセット
    input   wire        CPU_CLK     ,   // @@ CPU I/F用 クロック
    input   wire        SYS_RST_N   ,   // @@ システム動作用 リセット
    input   wire        SYS_CLK     ,   // @@ システム動作用 クロック
    input   wire        MST_SLV     ,   // @@ FRAMIF周辺   Avalon-MM Master/Slave切替 [0:Master]
    input   wire [ 7:0] PRM_CMD     ,   // @@ FRAMIF周辺   FRAMコマンド
    input   wire [23:0] PRM_ADD     ,   // @@ FRAMIF周辺   FRAMアドレス
    input   wire        BUF_FCR     ,   // @@ バッファ     FIFOクリア(レベル信号)
    input   wire        REN_AMI     ,   // @@ FRAMIF_AMI用 リードリクエスト
    output  wire        EMP_AMI     ,   // @@ FRAMIF_AMI用 FIFOエンプティ
    output  reg         RVL_AMI     ,   // @@ FRAMIF_AMI用 リードデータ有効パルス
    output  wire [ 7:0] RDT_AMI     ,   // @@ FRAMIF_AMI用 リードデータ
    input   wire        WEN_ASI     ,   // @@ FRAMIF_ASI用 ライトイネーブル
    input   wire        REN_ASI     ,   // @@ FRAMIF_ASI用 リードイネーブル
    input   wire [20:0] ADD_ASI     ,   // @@ FRAMIF_ASI用 リード/ライトアドレス
    input   wire [ 7:0] WDT_ASI     ,   // @@ FRAMIF_ASI用 ライトデータ
    output  reg         RVL_ASI     ,   // @@ FRAMIF_ASI用 リードデータ有効パルス
    output  wire [ 7:0] RDT_ASI     ,   // @@ FRAMIF_ASI用 リードデータ
    input   wire        WEN_SPI     ,   // @@ FRAMIF_SPI用 ライトイネーブル
    input   wire        REN_SPI     ,   // @@ FRAMIF_SPI用 リードイネーブル
    input   wire [20:0] ADD_SPI     ,   // @@ FRAMIF_SPI用 リード/ライトアドレス
    input   wire [ 7:0] WDT_SPI     ,   // @@ FRAMIF_SPI用 ライトデータ
    output  reg         RVL_SPI     ,   // @@ FRAMIF_SPI用 リードデータ有効パルス
    output  reg  [ 7:0] RDT_SPI         // @@ FRAMIF_SPI用 リードデータ
);

    // @@
    // @@ 外部パラメータの取り込み  (動作中に変更しないため、False Path)
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg         in_mode_sys;
    reg  [ 7:0] in_comm_sys;
    reg  [23:0] in_addr_sys;

    always @( posedge SYS_CLK or negedge SYS_RST_N ) begin
        if( ~SYS_RST_N ) begin
            in_mode_sys <= 1'b0;
            in_comm_sys <= 8'd0;
            in_addr_sys <= 24'd0;
        end
        else begin
            in_mode_sys <= MST_SLV;
            in_comm_sys <= PRM_CMD;
            in_addr_sys <= PRM_ADD;
        end
    end

    // @@
    // @@ マスタモード用 (FIFOを配置)
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

    // ++ SYS_CLK同期 ライト側
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg         fifo_wreq_sys;
    reg  [ 7:0] fifo_wdat_sys;

    always @( posedge SYS_CLK or negedge SYS_RST_N ) begin
        if( ~SYS_RST_N ) begin
            fifo_wreq_sys <= 1'b0;
            fifo_wdat_sys <= 8'd0;
        end
        else begin
            fifo_wreq_sys <= ( ~in_mode_sys ) ?     WEN_SPI : 1'b0;
            fifo_wdat_sys <= ( ~in_mode_sys ) ?     WDT_SPI : 8'd0;
        end
    end

    // ++ FIFO
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    NML_FRAMIF_FIFO UnFIFO(
        .wrclk      (SYS_CLK        ),  // input           wrclk;
        .wrreq      (fifo_wreq_sys  ),  // input           wrreq;
        .data       (fifo_wdat_sys  ),  // input   [ 7:0]  data;
        .wrempty    (/** OPEN **/   ),  // output          wrempty;
        .wrfull     (/** OPEN **/   ),  // output          wrfull;
        .wrusedw    (/** OPEN **/   ),  // output  [ 9:0]  wrusedw;
        .aclr       (BUF_FCR        ),  // input           aclr;
        .rdclk      (CPU_CLK        ),  // input           rdclk;
        .rdreq      (REN_AMI        ),  // input           rdreq;
        .q          (RDT_AMI        ),  // output  [ 7:0]  q;
        .rdempty    (EMP_AMI        ),  // output          rdempty;
        .rdfull     (/** OPEN **/   ),  // output          rdfull;
        .rdusedw    (/** OPEN **/   )   // output  [ 9:0]  rdusedw;
    );

    // ++ CPU_CLK同期 リード側
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg  [ 1:0] tmp_ami;
    always @( posedge CPU_CLK or negedge CPU_RST_N ) begin
        if( ~CPU_RST_N )                {RVL_AMI,tmp_ami} <= {1'b0,2'b0};
        else                            {RVL_AMI,tmp_ami} <= {tmp_ami,REN_AMI};
    end

    // @@
    // @@ スレーブモード用 (RAMを配置)
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

    // ++ SYS_CLK同期 A側
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg         ram_wen_sys;
    reg         ram_ren_sys;
    reg  [20:0] ram_add_sys;
    reg  [ 7:0] ram_wdt_sys;
    wire [ 7:0] ram_rdt_sys;
    reg  [ 1:0] ram_tmp_sys;

    always @( posedge SYS_CLK or negedge SYS_RST_N ) begin
        if( ~SYS_RST_N ) begin
            ram_wen_sys <= 1'b0;
            ram_ren_sys <= 1'b0;
            ram_add_sys <= 21'd0;
            ram_wdt_sys <= 8'd0;
        end
        else begin
            ram_wen_sys <= ( in_mode_sys ) ?    WEN_SPI : 1'b0;
            ram_ren_sys <=                      REN_SPI;
            ram_add_sys <=                      ADD_SPI;
            ram_wdt_sys <=                      WDT_SPI;
        end
    end
    always @( posedge SYS_CLK or negedge SYS_RST_N ) begin
        if( ~SYS_RST_N )                RDT_SPI <= 8'd0;
        else if( in_mode_sys )          RDT_SPI <= ram_rdt_sys;
        else if( ram_ren_sys ) begin
            RDT_SPI <= ((ram_add_sys==21'd0) ?  in_comm_sys[ 7: 0] : 8'd0)
                    |  ((ram_add_sys==21'd1) ?  in_addr_sys[23:16] : 8'd0)
                    |  ((ram_add_sys==21'd2) ?  in_addr_sys[15: 8] : 8'd0)
                    |  ((ram_add_sys==21'd3) ?  in_addr_sys[ 7: 0] : 8'd0);
        end
        else                            RDT_SPI <= RDT_SPI;
    end
    always @( posedge SYS_CLK or negedge SYS_RST_N ) begin
        if( ~SYS_RST_N )            {RVL_SPI,ram_tmp_sys} <= {1'b0,2'b0};
        else                        {RVL_SPI,ram_tmp_sys} <= {ram_tmp_sys,ram_ren_sys};
    end

    // ++ RAM
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    NML_FRAMIF_RAM UnRAM01(
        .clock_a    (SYS_CLK                        ),  // input           clock_a;
        .wren_a     (ram_wen_sys                    ),  // input           wren_a;
        .rden_a     (ram_ren_sys                    ),  // input           rden_a;
        .address_a  (ram_add_sys[P_BUF_ADBIT-1:0]   ),  // input   [11:0]  address_a;
        .data_a     (ram_wdt_sys                    ),  // input   [ 7:0]  data_a;
        .q_a        (ram_rdt_sys                    ),  // output  [ 7:0]  q_a;
        .clock_b    (CPU_CLK                        ),  // input           clock_b;
        .wren_b     (WEN_ASI                        ),  // input           wren_b;
        .rden_b     (REN_ASI                        ),  // input           rden_b;
        .address_b  (ADD_ASI[P_BUF_ADBIT-1:0]       ),  // input   [11:0]  address_b;
        .data_b     (WDT_ASI                        ),  // input   [ 7:0]  data_b;
        .q_b        (RDT_ASI                        )   // output  [ 7:0]  q_b;
    );

    // ++ CPU_CLK同期 B側
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg  [ 1:0] ram_tmp_asi;

    always @( posedge CPU_CLK or negedge CPU_RST_N ) begin
        if( ~CPU_RST_N )            {RVL_ASI,ram_tmp_asi} <= {1'b0,2'b0};
        else                        {RVL_ASI,ram_tmp_asi} <= {ram_tmp_asi,REN_ASI};
    end

endmodule

SPI I/F

設定値に応じて、FRAMのSPI制御信号を生成します。
SPI出力(送信時)は、Bufferブロックからリードデータを取得し、SPI出力します。
SPI入力(受信時)は、シリアルデータを8bitパラレルに変換し、Bufferブロックにライトします。

主な内部構成は以下の通りです。

f:id:nao-milk:20210419132755p:plain
SPI I/Fブロック 内部構成
// **
// **   SPI I/F for FRAM
// **
// *****************************************************************************
module NML_FRAMIF_SPI
#(
    parameter   P_WAITC     = 3'd7  ,   // @@ REG_ENB↑からアクセス開始までの時間
    parameter   P_FINWT     = 4'd8      // @@ REG_FINのHighパルス幅
)
(
    input   wire        RESET_N     ,   // @@ システムリセット
    input   wire        CLOCK       ,   // @@ システムクロック              [最大周波数=200MHz]
    output  reg         FRAM_CS_N   ,   // @@ FRAM SPI  チップセレクト
    output  reg         FRAM_SCLK   ,   // @@ FRAM SPI  シリアルクロック    [最大周波数= 50MHz]
    output  reg         FRAM_MOSI   ,   // @@ FRAM SPI  シリアル出力
    input   wire        FRAM_MISO   ,   // @@ FRAM SPI  シリアル入力
    output  reg         FRAM_OUTE   ,   // @@ FRAM SPI  出力イネーブル      [1:出力 0:Hi-Z]
    input   wire        REG_ENB     ,   // @@ レジスタ  処理イネーブル
    input   wire [ 3:0] REG_DIV     ,   // @@ レジスタ  シリアルクロック分周
    input   wire        REG_POL     ,   // @@ レジスタ  シリアルクロック極性
    input   wire [ 1:0] REG_TAK     ,   // @@ レジスタ  シリアルデータ取り込みタイミング
    input   wire [20:0] REG_ACS     ,   // @@ レジスタ  アクセスバイト数(設定値+1)
    input   wire [20:0] REG_OTC     ,   // @@ レジスタ  出力バイト数(設定値+1)
    output  reg         REG_FIN     ,   // @@ レジスタ  処理イネーブル クリアパルス
    output  reg         BUF_WEN     ,   // @@ バッファ  ライトイネーブル
    output  reg         BUF_REN     ,   // @@ バッファ  リードイネーブル
    output  reg  [20:0] BUF_ADD     ,   // @@ バッファ  リード/ライトアドレス
    output  reg  [ 7:0] BUF_WDT     ,   // @@ バッファ  ライトデータ
    input   wire        BUF_RVL     ,   // @@ バッファ  リードデータ有効パルス
    input   wire [ 7:0] BUF_RDT         // @@ バッファ  リードデータ
);

    // @@
    // @@ 処理イネーブル
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg         enb_d1;
    wire        init;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              enb_d1  <= 1'b0;
        else                                        enb_d1  <= REG_ENB;
    end
    assign  init    = ( REG_ENB & (~enb_d1) ) ?     1'b1 : 1'b0;

    // @@
    // @@ 開始ウェイト (バッファの最初のデータ読み期間)
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg         wit_enbl;
    reg  [ 2:0] wit_cont;
    wire        wit_term;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              wit_enbl <= 1'b0;
        else if( ~REG_ENB )                         wit_enbl <= 1'b0;
        else if( wit_term )                         wit_enbl <= 1'b0;
        else if( init )                             wit_enbl <= 1'b1;
        else                                        wit_enbl <= wit_enbl;
    end
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              wit_cont <= 3'h0;
        else if( ~REG_ENB )                         wit_cont <= 3'h0;
        else if( ~wit_enbl )                        wit_cont <= 3'h0;
        else                                        wit_cont <= wit_cont + 3'h1;
    end
    assign  wit_term= ( wit_enbl & (wit_cont==P_WAITC) ) ?  1'b1 : 1'b0;

    // @@
    // @@ アクセス制御
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg         acc_enbl;
    wire        acc_term;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              acc_enbl <= 1'b0;
        else if( ~REG_ENB )                         acc_enbl <= 1'b0;
        else if( acc_term )                         acc_enbl <= 1'b0;
        else if( wit_term )                         acc_enbl <= 1'b1;
        else                                        acc_enbl <= acc_enbl;
    end

    reg  [ 2:0] end_cont;
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              end_cont <= 3'd0;
        else if( ~REG_ENB )                         end_cont <= 3'd0;
        else if( acc_term )                         end_cont <= 3'd1;
        else if( end_cont!=3'd0 )                   end_cont <= end_cont + 3'd1;
        else                                        end_cont <= end_cont;
    end

    // @@
    // @@ シリアルクロック周波数
    // @@
    // @@   SPI_SCLK周波数 = CLOCK周波数 / ((REG_DIV+1)*4)
    // @@   例)
    // @@       CLOCK周波数 = 200MHz
    // @@       REG_DIV     = 0
    // @@       SPI_SCLK周波数 => 50MHz
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg  [ 6:0] sck_cont;
    wire [ 6:0] sck_cycl    = {1'b0,REG_DIV,2'b11};
    wire        sck_pose;
    wire        sck_nege;

    // ++ Division
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              sck_cont <= 7'h00;
        else if( ~REG_ENB )                         sck_cont <= 7'h00;
        else if( ~acc_enbl | acc_term )             sck_cont <= 7'h00;
        else if(  sck_nege )                        sck_cont <= 7'h00;
        else                                        sck_cont <= sck_cont + 7'h01;
    end
    assign  sck_pose= ( sck_cont=={1'b0,sck_cycl[6:1]} ) ?  1'b1 : 1'b0;
    assign  sck_nege= ( sck_cont==      sck_cycl       ) ?  1'b1 : 1'b0;

    // @@
    // @@ 管理カウンタ
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

    // ++ Bit Count
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg  [ 2:0] bit_cont;
    wire        bit_term;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              bit_cont <= 3'd0;
        else if( ~REG_ENB )                         bit_cont <= 3'd0;
        else if( ~acc_enbl | acc_term )             bit_cont <= 3'd0;
        else if(  sck_nege )                        bit_cont <= bit_cont + 3'd1;
        else                                        bit_cont <= bit_cont;
    end
    assign  bit_term= ( bit_cont==3'd7 ) ?          sck_nege : 1'b0;

    // ++ Byte Count
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg  [20:0] byt_cont;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              byt_cont <= 21'd0;
        else if( ~REG_ENB )                         byt_cont <= 21'd0;
        else if( ~acc_enbl | acc_term )             byt_cont <= 21'd0;
        else if(  bit_term )                        byt_cont <= byt_cont + 21'd1;
        else                                        byt_cont <= byt_cont;
    end
    assign  acc_term= ( byt_cont==REG_ACS ) ?       bit_term : 1'b0;

    // @@
    // @@ SPI 入出力切替
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg         spi_flag;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              spi_flag <= 1'b0;
        else if( ~REG_ENB )                         spi_flag <= 1'b0;
        else if( wit_term )                         spi_flag <= 1'b1;
        else if( ~acc_enbl | acc_term )             spi_flag <= 1'b0;
        else if( bit_term & (byt_cont==REG_OTC) )   spi_flag <= 1'b0;
        else                                        spi_flag <= spi_flag;
    end

    // @@
    // @@ バッファ制御
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg  [ 7:0] rbf_temp;
    reg         rbf_flag;
    wire        rbf_enbl;
    wire [20:0] rbf_addr;
    reg         wbf_flag;
    wire        wbf_enbl;
    wire [20:0] wbf_addr;
    wire [ 7:0] wbf_data;

    // ++ Enable generation
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    wire [20:0] prm_oct = REG_OTC - 21'd1;
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              rbf_flag <= 1'b0;
        else if( ~REG_ENB )                         rbf_flag <= 1'b0;
        else if( wit_term ) begin
            if( REG_OTC==21'd0 )                    rbf_flag <= 1'b0;
            else                                    rbf_flag <= 1'b1;
        end
        else if( ~acc_enbl | acc_term )             rbf_flag <= 1'b0;
        else if( bit_term & (byt_cont==prm_oct) )   rbf_flag <= 1'b0;
        else                                        rbf_flag <= rbf_flag;
    end
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              wbf_flag <= 1'b0;
        else if( ~REG_ENB )                         wbf_flag <= 1'b0;
        else if( ~acc_enbl | acc_term )             wbf_flag <= 1'b0;
        else if( bit_term & (byt_cont==REG_OTC) )   wbf_flag <= 1'b1;
        else                                        wbf_flag <= wbf_flag;
    end
    assign  rbf_enbl= ( rbf_flag & acc_enbl & sck_pose & (bit_cont==3'd3) );

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              BUF_WEN <= 1'b0;
        else if( ~REG_ENB )                         BUF_WEN <= 1'b0;
        else                                        BUF_WEN <= wbf_enbl;
    end
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              BUF_REN <= 1'b0;
        else if( ~REG_ENB )                         BUF_REN <= 1'b0;
        else                                        BUF_REN <= init | rbf_enbl;
    end

    // ++ Address generation
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    assign  rbf_addr= byt_cont + 21'd1;
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              BUF_ADD <= 21'd0;
        else if( ~REG_ENB )                         BUF_ADD <= 21'd0;
        else                                        BUF_ADD <= ( rbf_enbl ) ?   rbf_addr :
                                                               ( wbf_enbl ) ?   wbf_addr : 21'd0;
    end

    // ++ S/P conversion
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              rbf_temp <= 8'h00;
        else if( ~REG_ENB )                         rbf_temp <= 8'h00;
        else if( wbf_flag )                         rbf_temp <= 8'h00;
        else if( BUF_RVL )                          rbf_temp <= BUF_RDT;
        else                                        rbf_temp <= rbf_temp;
    end
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              BUF_WDT <= 8'h00;
        else if( ~REG_ENB )                         BUF_WDT <= 8'h00;
        else                                        BUF_WDT <= ( wbf_enbl ) ?   wbf_data : 8'h00;
    end

    // @@
    // @@ SPI I/F 信号
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

    // ++ SPI signal generation
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    wire        bas_cs_n;
    wire        bas_sclk;
    reg         bas_mosi;
    reg  [ 6:0] bas_temp;

    assign  bas_cs_n= ~( REG_ENB & acc_enbl );
    assign  bas_sclk=  ( bas_cs_n ) ?                           REG_POL :
                       ( sck_cont>{1'b0,sck_cycl[6:1]} ) ?      1'b1 : 1'b0;
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              {bas_mosi,bas_temp} <= 8'h00;
        else if( ~REG_ENB )                         {bas_mosi,bas_temp} <= rbf_temp;
        else if( (~acc_enbl) | bit_term )           {bas_mosi,bas_temp} <= rbf_temp;
        else if(  sck_nege )                        {bas_mosi,bas_temp} <= {bas_temp,1'b0};
        else                                        {bas_mosi,bas_temp} <= {bas_mosi,bas_temp};
    end

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            FRAM_CS_N <= 1'b1;
            FRAM_SCLK <= 1'b0;
            FRAM_MOSI <= 1'b0;
            FRAM_OUTE <= 1'b0;
        end
        else begin
            FRAM_CS_N <= (~wit_term) & bas_cs_n & (~((~end_cont[2]) & (end_cont[1:0]!=2'b00)));
            FRAM_SCLK <= bas_sclk;
            FRAM_MOSI <= bas_mosi & acc_enbl & (~wbf_flag);
            FRAM_OUTE <= spi_flag;
        end
    end

    // ++ SPI 入力信号
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg         in_miso;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              in_miso <= 1'b0;
        else                                        in_miso <= FRAM_MISO;
    end

    // ++ Capture timing
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg         cap_jdge,cap_jdtm;
    reg  [20:0] cap_jdad;
    reg         cap_enbl,cap_entm;
    reg  [20:0] cap_enad;
    reg  [ 6:0] cap_temp;
    reg         cap_vlid;
    reg  [20:0] cap_addr;
    reg  [ 7:0] cap_data;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            cap_jdtm <= 1'b0;
            cap_jdge <= 1'b0;
            cap_jdad <= 21'd0;
        end
        else if( ~REG_ENB ) begin
            cap_jdtm <= 1'b0;
            cap_jdge <= 1'b0;
            cap_jdad <= 21'd0;
        end
        else begin
            cap_jdtm <= wbf_flag & bit_term;
            cap_jdge <= wbf_flag & sck_nege;
            cap_jdad <= byt_cont;
        end
    end

    // -- 取り込み遅延
    reg         cap_jdtm_dly1,cap_jdtm_dly2,cap_jdtm_dly3;
    reg         cap_jdge_dly1,cap_jdge_dly2,cap_jdge_dly3;
    reg  [20:0] cap_jdad_dly1,cap_jdad_dly2,cap_jdad_dly3;
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            cap_jdtm_dly1 <= 1'b0;
            cap_jdge_dly1 <= 1'b0;
            cap_jdad_dly1 <= 21'd0;
            cap_jdtm_dly2 <= 1'b0;
            cap_jdge_dly2 <= 1'b0;
            cap_jdad_dly2 <= 21'd0;
            cap_jdtm_dly3 <= 1'b0;
            cap_jdge_dly3 <= 1'b0;
            cap_jdad_dly3 <= 21'd0;
        end
        else if( ~REG_ENB ) begin
            cap_jdtm_dly1 <= 1'b0;
            cap_jdge_dly1 <= 1'b0;
            cap_jdad_dly1 <= 21'd0;
            cap_jdtm_dly2 <= 1'b0;
            cap_jdge_dly2 <= 1'b0;
            cap_jdad_dly2 <= 21'd0;
            cap_jdtm_dly3 <= 1'b0;
            cap_jdge_dly3 <= 1'b0;
            cap_jdad_dly3 <= 21'd0;
        end
        else begin
            cap_jdtm_dly1 <= cap_jdtm;
            cap_jdge_dly1 <= cap_jdge;
            cap_jdad_dly1 <= cap_jdad;
            cap_jdtm_dly2 <= cap_jdtm_dly1;
            cap_jdge_dly2 <= cap_jdge_dly1;
            cap_jdad_dly2 <= cap_jdad_dly1;
            cap_jdtm_dly3 <= cap_jdtm_dly2;
            cap_jdge_dly3 <= cap_jdge_dly2;
            cap_jdad_dly3 <= cap_jdad_dly2;
        end
    end
    // -------------------------------------------

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            cap_entm <= 1'b0;
            cap_enbl <= 1'b0;
            cap_enad <= 21'd0;
        end
        else if( ~REG_ENB ) begin
            cap_entm <= 1'b0;
            cap_enbl <= 1'b0;
            cap_enad <= 21'd0;
        end
        else begin
            case( REG_TAK )
                2'd1    : begin
                    cap_entm <= cap_jdtm_dly1;
                    cap_enbl <= cap_jdge_dly1;
                    cap_enad <= cap_jdad_dly1;
                end
                2'd2    : begin
                    cap_entm <= cap_jdtm_dly2;
                    cap_enbl <= cap_jdge_dly2;
                    cap_enad <= cap_jdad_dly2;
                end
                2'd3    : begin
                    cap_entm <= cap_jdtm_dly3;
                    cap_enbl <= cap_jdge_dly3;
                    cap_enad <= cap_jdad_dly3;
                end
                default : begin
                    cap_entm <= cap_jdtm;
                    cap_enbl <= cap_jdge;
                    cap_enad <= cap_jdad;
                end
            endcase
        end
    end

    // ++ P/S conversion
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              cap_temp <= 7'h00;
        else if( ~REG_ENB )                         cap_temp <= 7'h00;
        else if( cap_enbl )                         cap_temp <= {cap_temp[5:0],in_miso};
        else                                        cap_temp <= cap_temp;
    end
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N ) begin
            cap_vlid <= 1'b0;
            cap_addr <= 21'd0;
            cap_data <= 8'h00;
        end
        else begin
            cap_vlid <= cap_entm;
            cap_addr <= cap_enad;
            cap_data <= {cap_temp,in_miso};
        end
    end

    assign  wbf_enbl= cap_vlid;
    assign  wbf_addr= cap_addr;
    assign  wbf_data= cap_data;

    // @@
    // @@ レジスタ I/F
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    reg  [ 3:0] fin_cont;

    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              fin_cont <= 4'd0;
        else if( fin_cont==P_FINWT )                fin_cont <= 4'd0;
        else if( end_cont==3'd7 )                   fin_cont <= 4'd1;
        else if( fin_cont!=4'd0 )                   fin_cont <= fin_cont + 4'd1;
        else                                        fin_cont <= fin_cont;
    end
    always @( posedge CLOCK or negedge RESET_N ) begin
        if( ~RESET_N )                              REG_FIN <= 1'b0;
        else if( fin_cont==P_FINWT )                REG_FIN <= 1'b0;
        else if( end_cont==3'd7 )                   REG_FIN <= 1'b1;
        else                                        REG_FIN <= REG_FIN;
    end

endmodule

このブロックは200MHzで動作します。
21bitのbyteカウンタは、以下の経路をset_false_path指定します。

  1. byt_cont[20:0] → byt_cont[20:0]
  2. byt_cont[20:0] → BUF_ADD[20:0]

これは、+1した値をラッチするもので、1クロック以内で確定する必要のないものとなります。
起点byt_cont[20:0]となる経路を以下に示します。

f:id:nao-milk:20210419140934p:plain
経路 (起点:byt_cont[20:0])

内部信号byt_cont[20:0]は最小32クロック後に+1加算され、BUF_ADD[20:0]も13クロック以内に+1した値が確定していれば良いようになります。
タイミング図を以下に示します。

f:id:nao-milk:20210419141125p:plain
タイミング図(起点:byt_cont[20:0])

Clock transfer

2段FFによるクロック乗せ換えとなります。
パラメータでデータ幅と初期値が設定可能になっていますが、これは流用性を考慮しています。
尚、レジスタ値用の乗せ換えとなります。

// **
// **   Clock transfer for FRAM
// **
// *****************************************************************************
module NML_FRAMIF_EXC
#(
    parameter   P_WIDTH = 32        ,   // データ幅
    parameter   P_INIT  = 32'd0         // 初期値
)
(
    input   wire                RI  ,   // @@ 入力データ用 リセット
    input   wire                CI  ,   // @@ 入力データ用 クロック
    input   wire [P_WIDTH-1:0]  DI  ,   // @@ 入力データ
    input   wire                RO  ,   // @@ 出力データ用 リセット
    input   wire                CO  ,   // @@ 出力データ用 クロック
    output  reg  [P_WIDTH-1:0]  DO      // @@ 出力データ
);

    reg  [P_WIDTH-1:0]  in_d;
    reg  [P_WIDTH-1:0]  as_d;

    always @( posedge CI or negedge RI ) begin
        if( ~RI )                       in_d <= P_INIT;
        else                            in_d <= DI;
    end

    always @( posedge CO or negedge RO ) begin
        if( ~RO )                       as_d <= P_INIT;
        else                            as_d <= in_d;
    end

    always @( posedge CO or negedge RO ) begin
        if( ~RO )                       DO <= P_INIT;
        else                            DO <= as_d;
    end

endmodule

マクロ生成

RAMマクロ

Bufferブロック内のRAMマクロの生成内容は、以下の通りとなります。

f:id:nao-milk:20210419143520p:plain
RAMマクロ

FIFOマクロ

f:id:nao-milk:20210419143937p:plain
FIFOマクロ

メモリサイズ

FRAM I/Fブロックで使用するブロックメモリサイズは、以下となります。

  • FIFO:M10K Block = 1
  • RAM:M10K Block = 4

FIFOは、BlockRAM=1となるギリギリまでのサイズ、512Word×8bit分取っています。
実際は、そこまでのサイズは必要としませんが、余裕をもっています。

RAMは、4096Word×8bitとしています。

シミュレーション波形

FRAM I/FブロックをTOPとしたRTLシミュレーション波形を以下に示します。
マスタモードとスレーブモードの波形となり、「デバイスID読み出し」の場合のシミュレーション波形となります。

マスタモード

f:id:nao-milk:20210419145925p:plain
シミュレーション波形 (マスタモード)

スレーブモード

f:id:nao-milk:20210419150758p:plain
シミュレーション波形 (スレーブモード)

最後に

設計では、どこを「set_false_path」にするかも検討する必要があります。
どのタイミングで変化するのか、どれくらいまでに値が確定しておけば良いのか、把握する必要があります。
また、STA結果も、set_false_path指定した起点終点間の遅延量も確認する必要があります。

FRAMにアクセス (ブロック仕様編)

f:id:nao-milk:20210417123935p:plain

FRAM(強誘電体RAM)をアクセスするためのFPGAをテーマに、FPGAを設計するまでの一連の工程を記載して行こうと思います。

  1. ブロック仕様
  2. Verilog-HDLによるコーディング
  3. シミュレーション(RTLシミュレーション)
  4. Platform DesignerとFPGA Topの作成
  5. 論理合成とインプリメント
  6. NiosIIのコーディング
  7. NiosIIを含めたシミュレーション

 

本記事では、まずはブロックの仕様を記載します。

(後半に、FRAMとインターフェイスを行うSPIブロックのVerilogコードも記載しています。)

※FRAMのモデルは「CY15B102QN」となります。

 (ちょうど、verilogのシミュレーションモデルがあったので。。。)

 

【背景】

昔の案件でFRAMにアクセスする機能が必要で、しかも、アクセス時間を短くしたかったため、バーストアクセスができるIPを探したのですが、見当たらなかったため、verilogで作りました。

その時は、必要な機能に限定して作成しましたが、今回は依頼されたものではないので、私の好きなように作ろうと思います。

ポイントとしては、色々なコマンド(Opcode)が発行でき、且つ ブートローダとして使えそうな機能も考慮してしみました。

 

 

 

概要

FRAM(CY15B102QN)デバイスに対し、リード/ライトを行います。

リード/ライト方法はSPIプロトコルで行います。

 

以下にFRAMデバイスの通信フォーマット例を示します。

 

f:id:nao-milk:20210416131425p:plain

FRAMデバイス リード/ライト例


機能

【CPU I/F (Avalon-MM)】

  1. MasterとSlaveに分かれ、入力信号MST_SLV(0:Master 1:Slave)により選択が可能。
  2. Masterを選択した場合、入力信号PRM_???に従って、FRAMから取得したデータをAvalon-MM(Master側)のブロックから出力します。
  3. Slaveを選択した場合、Avalon-MM(Slave側)でCPUから設定されたレジスタ値に従い、アクセスを行います。
  4. FRAMへの転送が完了すると、割り込みを発生します。尚、割り込みマスクが可能であり、割り込みステータスに"1"を書き込むことでクリアします。(割り込み信号はレベル信号)

 

【FRAMデバイスI/F (SPIプロトコル)】

  1. アクセスバイト数(バーストサイズ)は、レジスタREG_ASC[20:0]又は入力信号PRM_ASC[20:0]で設定が可能。尚、スレーブモード時の最大アクセスバイト数は内蔵RAMサイズに依存します。
  2. 出力(送信)バイト数は、レジスタREG_OTC又は入力信号PRM_OTC[1:0]で設定が可能。
  3. シリアルクロック(出力信号FRAM_SCLK)は内部クロックSYS_SLK[200MHz]を分周して生成し、最大50MHz(SYS_CLKの1/4分周)を出力。
  4. シリアルクロックの周波数は、レジスタREG_DIV[3:0]又は入力信号PRM_DIV[3:0]で設定が可能。
  5. 入力シリアルデータ(入力信号FRAM_MISO)は、SYS_CLK↑に同期してラッチし、レジスタREG_TAK[1:0]又は入力信号PRM_TAK[1:0]により、取り込みタイミングを調整することが可能。

 

ブロック構成

FRAMとI/Fを行うブロック(ブロック名:NML_FRAMIF)の構成図を以下に示します。

※CPUブロックは50MHzで動作します。

※TOPは、FPGAのTOPを示します。

 

f:id:nao-milk:20210416124855p:plain

FRAM I/F ブロック構成

入出力信号一覧

FRAM I/Fブロックの入出力信号の一覧を以下に示します。

f:id:nao-milk:20210417112745p:plain

入出力信号一覧

入出力タイミング

FRAM I/Fブロックの入出力タイミングを以下に示します。

 

FRAM I/F信号

タイミング波形とレジスタ設定値との関係を以下に示します。

f:id:nao-milk:20210416151845p:plain

入出力タイミング (FRAMデバイス用 SPI I/F信号タイミング)

※出力信号FRAM_OUTEは、FRAMへのシリアルデータを出力しない場合はHi-Zしたいことも想定して補足信号としてあります。

 

FRAMデータ取り込みタイミング

シリアルクロックの最大周波数は50MHz[最小周期は20ns]となるため、FFから出力してFRAMへ届くまでの時間(シリアルクロックが届くまでの時間) と 届いてからFRAM I/Fブロックの入力FFまでの届く時間の合計が20ns以上の場合もあると考え、基準取り込みポイントから最大3クロック遅延させて取り込むことが可能です。

以下に経路遅延イメージを示します。また、取り込みタイミング図も示します。

f:id:nao-milk:20210416154102p:plain

FRAMデータ取得までの経路遅延イメージ

 

f:id:nao-milk:20210416151959p:plain

入出力タイミング (FRAMデバイス用 SPI取り込みタイミング)

 

Avalon-MM(Slave側)

リード時は、出力データを準備するためにWait信号(出力信号SAV_WIT)をアサートします。

また、レジスタと内蔵RAMをリードする場合でWait信号のアサート期間が変わります。

 

f:id:nao-milk:20210416154722p:plain

入出力タイミング(Avalon-MM Slave)

 

Avalon-MM(Master側)

wait信号(入力信号MAV_WIT)のアサート期間中は出力を保持します。

f:id:nao-milk:20210416155022p:plain

入出力タイミング(Avalon-MM Master)

 

動作説明

FRAM I/Fブロックの動作に関して説明します。

 

マスタモード(入力信号MST_SLV=0)

マスタモードは入力信号による制御を可能とし、FRAMからの取得したデータをAvalon-MM Masterとしてライト制御します。

尚、このモードはFRAMのリード系コマンド(READ,SSRDなど)のみを想定しています。

  1. 入力信号PRM_DIV、PRM_POL、PRM_TAK、PRM_ACS、PRM_OTC、PRM_STA、PRM_CMD、PRM_ADDを設定します。
  2. PRM_ENBを"0"から"1"に設定します。これにより、動作を開始します。
  3. FRAMへコマンド(PRM_CMDの内容)発行を行い、PRM_OTC[1:0]に応じてアドレス発行(PRM_ADDの内容)を行います。
  4. FRAMから取得したシリアルデータを8bitパラレルデータに変換し、FIFOに格納します。
  5. FIFOがNot Emptyになると、Avalon-MM I/F(Master側)がFIFOをリードし、Avalon-MMライト動作を行います。尚、ライトアドレスは入力信号PRM_STAを加算して出力します。
  6. FRAMへのアクセスが完了すると、出力信号CPU_IRQをアサートします。尚、PRM_CLRを"1"にすることでクリアします。

 ※動作中は、出力信号MON_ENBが"1"になります。

 

スレーブモード(入力信号MST_SLV=1)

CPUからのレジスタ設定に応じて動作を行い、内蔵RAMに格納されたデータをFRAMへ出力し、FRAMから出力したデータを内蔵RAMに格納します。

従って、1回のFRAMへのリード又はライトはRAMサイズに依存します。

(内蔵RAMのサイズを大きくすることで、アクセスバイト数は大きくできます)

尚、内蔵RAMサイズは4096バイトとしています。

  1. レジスタに初期設定を行います。(REG_DIV、REG_POL、REG_TAK、REG_ACS、REG_OTC)
  2. FRAMへ出力したいデータをCPUから内蔵RAMへ格納します。(コマンドやアドレス、ライトデータなど)
  3. 処理イネーブルを"1"にします(レジスタREG_ENB)。これにより、動作を開始します。
  4. FRAMへの出力時は内蔵RAMからデータを取得してFRAMへ出力します。またFRAMから取得したデータは、8bitのパラレルデータに変換してRAMへ保存します。
  5. FRAMへのアクセスが完了すると、出力信号CPU_IRQをアサートします。尚、レジスタREG_ENBも自動的に"0"になるため、割り込みマスクを行い、レジスタREG_ENBの状態をポーリングすることにより、動作中か否かを知ることが可能です。
  6. CPUがFRAMから取得したデータを内蔵RAMから読み出す。(レジスタREG_BDT)

 

内蔵RAMとFRAMアクセスの関係

スレーブモード時の内蔵RAMとFRAM SPI通信のデータ構成の関係を以下に示します。

尚、FRAMアクセスの最初はCommand(Opcode)の出力となります。

 

f:id:nao-milk:20210416171505p:plain

内蔵RAMとFRAM SPI通信との関係

 ※Command= "SSWR"と"SSRD"は特殊セクタアクセスとなり、256バイト以上のアクセスを行った場合は、特殊セクタの先頭アドレスに戻るため、グレーにしています。

 

マスタモード時は、内蔵RAMを介さず、取得したデータをそのままAvalon-MM Master側でFRAM I/Fブロック外部にライトします。

 

レジスタ詳細

FRAM I/Fのレジスタ詳細を以下に示します。

f:id:nao-milk:20210417200626p:plain


f:id:nao-milk:20210416175005p:plain

f:id:nao-milk:20210416175103p:plain

 

f:id:nao-milk:20210416175203p:plain

 設定例

FRAM SPI通信のバイト数設定例を以下に示します。

 スレーブモード

f:id:nao-milk:20210416175515p:plain

設定例 (スレーブモード)

 マスタモード

f:id:nao-milk:20210416175634p:plain

設定例 (マスタモード)

 

SPI I/Fのソースコード(記述:verilog-HDL)

FRAM I/Fの内部ブロックSPI I/F(モジュール名:NML_FRAMIF_SPI)のソースコードを以下に示します。

 

入出力信号の宣言と処理イネーブルがアサートされ内蔵RAMから最初のデータ(Command)をリードするための期間を生成します。

f:id:nao-milk:20210416180839p:plain

 

SPI通信期間を示すasc_enblを生成し、またアクセス終了後のWait期間を生成します。

f:id:nao-milk:20210416181007p:plain

 

シリアルクロックの生成、bitカウンタとByteカウンタを生成します。

尚、Byteカウンタは、CY16B102以上のデバイスでも対応できるよう、余分にbit幅をとっています。

f:id:nao-milk:20210416181057p:plain

 

SPI通信の出力と入力を示すフラグを生成し、内蔵RAMのアクセス制御を行います。

f:id:nao-milk:20210416181145p:plain

 

ベースとなるFRAM制御信号(base_*)を生成し、FFでラッチして出力します。

これにより、FPGAの出力FFを使用します。

f:id:nao-milk:20210416181231p:plain

 

FRAMからの入力シリアルデータラッチ(in_miso)し、基準取り込みタイミング信号を生成します。(入力FFを使用します)

f:id:nao-milk:20210416181400p:plain

 

基準取り込みタイミングを遅延します。

f:id:nao-milk:20210416181456p:plain

 

レジスタREG_TAKの設定値に応じて、取り込みタイミングを選択し、シリアルデータをパラレルデータに変換します。

変換したデータは、上記BUF_WDTでラッチします。

f:id:nao-milk:20210416181540p:plain

 

FRAMアクセス完了後、レジスタREG_ENBを"0"にするための信号REG_FINを生成します。

尚、この信号は後段ブロックでCPU_CLK(50MHz)で使用するため、High幅をSYS_CLKの8クロック分にしています。(CPU_CLKの2クロック分)

f:id:nao-milk:20210416181619p:plain

 

使用ツール

  • 言語:verilog-HDL(ハードウェア用)、C言語(ソフトウェア用[NiosII用])
  • シミュレータ:ModelSim Intel FPGA Starter Edition 10.5b
  • 論理合成/インプリメント:Quartus Prime 18.1 Lite Edition

 

最後に

マスタモードとスレーブモードに分けたのは、試作を意図しています。

FRAMへのライトとリードも、マスタモードで行うことも可能ですが、リードだけに止めました。

機能試作や基板試作時は、スレーブモードを使ってFRAMに対して色々なコマンドを発行して、機能検討や基板変更を考えると思います。

その為、なるべく自由度が高いと思われるスレーブモードを入れました。

 

Pythonで画像描画

f:id:nao-milk:20210408013747p:plain
Pythonを独学して3日目ですが、数式からハートマークを作成する画像描画プログラムを作成してみました。

修正履歴

facebookである方より、アドバイス頂いたので修正しました。
※アドバイスありがとうございました。

[アドバイス内容]
 pow()なのか**なのか統一しません?
 つーか、なんで混在しているんですか?
 違いはわかっていますか?

[修正内容]
 「ソースコード」の章に記述した「HeartSymbol()」内のべき乗を「**」に統一しました。
 尚、
 混在した理由は、べき乗の演算を「**」にすべきか、
 「pow」にするべきかを迷っており、そのまま投稿してしまいました。

[新たな発見]
今回のアドバイスで「べき乗」は、「**」と「pow」と「math.pow」があることが分かりました。
この3つの演算の演算速度を測りました。(time.perf_counter_ns()で計測)
 「**」 :2,103,908,740 ns (2.103秒)
 「pow」:2,153,195,800 ns (2.153秒)
 「math.pow」:2,123,512,380 ns (2.123秒)
 ※各5回実行して、平均した秒数となります。
今回の修正は、実行速度の速い「**」に統一しました。

[新たな疑問]
dis()を使ってコードを見たのですが、「math.pow」が一番多いのに、なぜ速度が2番目に速いのか?

本文の構成(目次)は、以下のようになります。
(出力画像は、下の方に添付しています。)

Window構成

勉強を始めて3日目なので、簡単になりますがWindowは以下のような構成になります。

f:id:nao-milk:20210408101706p:plain
Window構成

メインフレームの初期サイズは、640x640になります。

ソース構成

ソース構成は、以下の通りです。

f:id:nao-milk:20210408103516p:plain
ソース構成

演算式

ハートマークを生成するための演算式は、以下の通りです。
私がキレイと思うハートマークを作るため、変数xとyに係数を掛けています。

f:id:nao-milk:20210408103841p:plain
演算式

動作説明

プログラムの主な動作について説明します。
ソースコードは、後述でそのまま添付してしています。

1. 下地を生成

変数xとyの値(座標)からとして色成分(範囲:0~255)を置いていくため、オブジェクトPhotoImage()を使って512×512の下地を作っています。
これにより、メソッドput()を使用してxとy座標に色を設定していきます。

オブジェクトPhotoImageは、PNGなどのファイルを読み込み使用するみたいですが、幅と高さを指定するだけだと、画像を置くための下地を作ってくれるみたいでした。
tkinterの__init__.plを見ながら施行錯誤しました。

2. 画像生成

出力画像サイズを512×512としたため、X方向とY方向の位置を管理する変数pxとpyを準備しています。
また、
演算式ではX及びY座標を-32~32の範囲にしたかったため、変数txとtyで実際の座標位置を管理しています。
従って、座標精度(座標点の間隔)は0.125となります。(精度=64÷512)

変数txとtyから上記演算式の計算を行い(関数:HeartSymbol)、算出値の整数部を画素値(色値)としています。
ただし、
算出値が255を超える場合は255とし(関数:ClipValue)、"255-算出値"の値をR成分に設定しています。
※中心(座標x,y=0,0)に行くほど算出値が0になる性質の式のため、255から算出値を引いています。
 (ハートマークなので、中心に近づくほど「赤色」が良いかな?と思った次第です)
 尚、G成分及び及びB成分は0にしています。背景色が黒、ハートマークは赤となり、
 中心に近づくほど、赤色が濃くなっていきます。

3. Windowの表示

メインフレームCanvas領域(512×512)を作成し、上記「2.画像生成」で作成した画像を乗せています。
また、
ユーザーがメインフレームサイズを小さくした場合でも、視覚範囲で画像が見れるようスクロールバーを追加しました。

ファイル保存

画像表示だけでなくファイルにも保存したかったため、メソッドwriteを使ってPNG形式で保存しています。

補足)

オブジェクトPhotoImage()に「幅」と「高さ」のみを設定すると、データタイプは「photo」になりました。(おそらく、初期値だと思っています。)
RGBの設定がしたかったため、そのままにしていますが、「bitmap」(2値画像)にする方法までは確認していません。

ソースコード

ソースコードを以下に示します。
モジュール「csv」をインポートしていますが、演算結果などをExcelに保存して確認したかったために使用しました。下記ソース内では、モジュールcsvを使用したコードは削除しています。

import math
import tkinter as tk
import csv

#--
#-- 関数
#-----------------------------------------------------------

# @@ 座標からハートマークを生成する。
# @@
# @@    f(x,y) = (x*a)^2 + ((y*b) - ((x*c)^2)^(1/3))^2
# @@        a,b,c : 係数
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
def HeartSymbol(
        PointX      ,   # X座標
        PointY          # Y座標
    ):
    # べき乗を「**」に統一
    #########################################
    ResVal = (PointX*0.60)**2 + (PointY*0.75 - (((PointX*1.5)**2)**(1/3)))**2

#    ResVal = pow( PointX*0.60                                ,2) +    \
#             pow((PointY*0.75 - pow(pow((PointX*1.5),2),1/3)),2)

#    ResVal = math.pow( PointX*0.60                                          ,2) +    \
#             math.pow((PointY*0.75 - math.pow(math.pow((PointX*1.5),2),1/3)),2)
    return  ResVal

# @@ クリップ処理
# @@  最大値から最小値内のValueを返す
# @@  変数Valueが最大値以上、最大値を返す。
# @@  変数Valueが最小値未満、最小値を返す。
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
def ClipValue(
        Value       ,   # 入力値
        MaxVal      ,   # 最大値
        MinVal          # 最小値
    ):
    if(     Value > MaxVal ):   ResVal = MaxVal
    elif(   Value < MinVal ):   ResVal = MinVal
    else:                       ResVal = Value

    return  ResVal

#--
#-- Window 生成
#-----------------------------------------------------------
class WindowApp(tk.Frame):
    def __init__(self,
            master      = None          ,   # メインWindow
            title       = "Nao-Milk"    ,   # Windowタイトル
            width       = 64            ,   # Canvasの幅
            height      = 32            ,   # Canvasの高さ
            filename    = "play.png"        # 出力画像ファイル名 
        ):
        super().__init__(master)
        self.pack()
        self.master.title(title)
        self.master.geometry("640x640")

        # [ Create Screen ]
        self.image = tk.PhotoImage(width=width,height=height)   #++ 下地作成
        self.sizex = self.image.width()                         #++ 下地の幅(確認)
        self.sizey = self.image.height()                        #++ 下地の高さ(確認)
        self.type  = self.image.type()                          #++ 下地のType(確認)
                                                                #++   [e.g. "photo" or "bitmap".]
        print(" --> W x H = ",self.sizex, self.sizey , "type[",self.type ,"]")

        # [ Generate Image ]  R成分のみ演算
        #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        HalfTX = 32.0;                                          #++ X方向 座標領域の半分
        HalfTY = 32.0;                                          #++ Y方向 座標領域の半分

        HalfPX = int(self.sizex/2);     HalfPY = int(self.sizey/2);     #++ 出力領域の半分
        StartX = HalfTX*-1;             StartY = HalfTY* 1;             #++ 座標の開始位置
        ResolX = (1/HalfPX*HalfTX)* 1;  ResolY = (1/HalfPY*HalfTY)*-1   #++ 座標間隔

        ty = StartY
        for py in range(0,self.sizey):
            tx = StartX
            for px in range(0,self.sizex):
                # ----- 演算 -----
                fr = HeartSymbol(tx,ty)
                fg = 0
                fb = 0
                r = 255-ClipValue(math.floor(fr),255,0)         #++ R成分
                g =     ClipValue(math.floor(fg),255,0)         #++ G成分
                b =     ClipValue(math.floor(fb),255,0)         #++ B成分

                # ----- 出力位置に色を設定 -----
                color = "#" + format(r, '02x') + format(g, '02x') + format(b, '02x')
                self.image.put(color,to=(px,py))

                tx = tx + ResolX

            ty = ty + ResolY

        #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        # [ display graphical elements ]
        self.canvas = tk.Canvas(self.master,width=self.sizex,height=self.sizey,bg="black")
        self.canvas.create_image(0,0,image=self.image,anchor=tk.NW)

        # [ Scrollbar ]
        bar_y = tk.Scrollbar(self.master, orient=tk.VERTICAL)
        bar_x = tk.Scrollbar(self.master, orient=tk.HORIZONTAL)
        bar_y.pack(side=tk.RIGHT , fill=tk.Y)
        bar_x.pack(side=tk.BOTTOM, fill=tk.X)
        bar_y.config(command=self.canvas.yview)
        bar_x.config(command=self.canvas.xview)
        self.canvas.config(yscrollcommand=bar_y.set, xscrollcommand=bar_x.set)
        self.canvas.config(scrollregion=(0, 0, self.sizex, self.sizey))
        self.canvas.pack(anchor=tk.NW, side=tk.LEFT)
        
        # [ File Save ]
        self.image.write(filename=filename)

#--
#-- Main
#-----------------------------------------------------------
if __name__ == "__main__":
    Frame1 = tk.Tk()                                # ++ Toplevel widget
    WinAp1 = WindowApp(
                master      = Frame1            ,   # ++ メインWindow
                title       = "Nao-Milk Play2"  ,   # ++ Windowタイトル
                width       = 512               ,   # ++ Canvasの幅
                height      = 512               ,   # ++ Canvasの高さ
                filename    = "play2.png"           # ++ 出力画像ファイル名
            )
    WinAp1.mainloop()

出力画像

プログラムを実行して生成した画像を以下に示します。

f:id:nao-milk:20210408012848p:plain
出力画像

実行環境

最後に

全てのモジュールが分かってないため、もっとスムーズなやり方があるかもしれませんが、今の知識ではここまででした。
尚、「やりたいこと」の思考からネットで「記述例」を調べ参考にしつつ、モジュールのソースも見ながら、試行錯誤しながら作成してみました。
(各モジュールの気持ち[意図]を知るため。)

Instagramでは、緑のハート、青のハートも載せています。
https://www.instagram.com/nao_nari0504/