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マクロを配線するのに便利です。
が、ユーザーロジックを含めて配線するとデバッグ時にユーザー回路のポートを修正すると手間が掛かります。