この記事の概要
Intel FPGA搭載のDe0-Nanoボードにて、NiosIIへSDRAMを追加する際の設定について、試して理解したことを書き残します。
使用したもの
開発環境:Windows10Professional
開発ツール:Quartus Prime 18.1 Lite Edition
ターゲットボード:DE0-Nano Development Board
参考書:小林優『改定2版 FPGAボードで学ぶ組込みシステム開発入門[Intel FPGA編]』技術評論社
結論
NiosIIプラットフォームへPLLを組み込む
- 使用IP:「Basic Functions」→「Clocks; PLLs and Resets」→「PLL」→「ALTPLL Intel FPGA IP」
- PLLから2つのクロックを出力する
- 一つはAvalonバスクロックとして使う
- もう一つの出力クロックをSDRAMへの物理的クロックとして使う
NiosIIプラットフォームへSDRAMコントローラーを組み込む
- 使用IP:「Memory Interfaces and Controlls」→「SDRAM」→「SDRAM Controller Intel FPGA IP」
- DE0-Nano Development Boardに搭載されたSDRAMに合わせた設定を入力する
- 「Memory Profile」タブはSDRAMのデータシートを見ればすぐに入力できる
- 「Timing」タブはよくわからないので、DE0-Nano Development Board付属のサンプルプロジェクトの値を拝借
NiosIIソフトウェアでSDRAMへの読み書きを実装する
- ソフトウェアから見るとAvalonバススレーブのレジスタへの読み書きに過ぎない
詳細
まずはPlatform Designerにて、基本的な構成のNiosIIプラットフォームまで構築したものとします。
基本的な構成とは下記のモジュールで構成された必要最低限のシステムのこととします。
- clk_0
- nios2_gen2_0
- onchip_memory2_0
- jtag_uart_0
- sysid_qsys_0
NiosIIプラットフォームへPLLを組み込む
IP Catalogから、下記のツリーを辿ってIPをNiosIIプラットフォームへ追加します。
- 「Basic Functions」→「Clocks; PLLs and Resets」→「PLL」→「ALTPLL Intel FPGA IP」
追加したらPLLを設定。
- 入力クロックは50MHz
- リセット信号は入力しない
- 逓倍出力したいクロックは100MHz
- もう一つの逓倍出力したいクロックは100MHzで位相を3000ps早める。(位相をずらす理由は後述)
- 位相を遅らせる方をプラスとして設定するため、早める方向はマイナスとなります。
次にNiosIIプラットフォームのクロックの付替えを行います。
元々clk_0のclkをAvalonバスへ供給していましたが、それを全部外します。外したclkはaltpll_0のinclk_interfaceへ繋ぎ、altpll_0のc0出力をAvalonバスへ供給します。これでAvalonバスのクロックはPLLで設定した逓倍出力100MHzに置き換わりまし
た。
また、PLLで設定したもう一つの逓倍出力100MHzで位相3000ps早めたものは、altpll_0_c1として外部出力にし、SDRAMへの物理的クロックとします。(ついでにPLLのフェーズロック信号もaltpll_0_locked_conduitとして外部出力しておきましたが、この後特に使う用途は無いのであまり気にせずに。。。)
NiosIIプラットフォームへSDRAMコントローラーを組み込む
IP Catalogから、下記のツリーを辿ってIPをNiosIIプラットフォームへ追加します。
- 「Memory Interfaces and Controlls」→「SDRAM」→「SDRAM Controller Intel FPGA IP」
追加したらDE0-Nano Development Boardに搭載されたSDRAM(品名:IS42S16160G-7TLI)に合わせた設定を入力。
-
一方で「Timing」タブに設定する数値は何が適切なのか解らずじまいでした。仕方なくDE0-Nano Development Board付属のサンプルプロジェクトの値を拝借してみました。
-
SDRAMの仕様をしっかり理解していればデータシートから読み取れると思うので、ここは今後の勉強テーマです。
最後にAvalonバス上の接続を行います。
こうしてできたNiosIIプラットフォームを、最上位階層にてインスタンスして、論理合成、配置配線を実施します。
module DE0nano
(
// CLOCK
input CLOCK_50,
// LED
output[7:0] LED,
// KEY
input [1:0] KEY,
// SW
input [3:0] SW,
// SDRAM
output[12:0] DRAM_ADDR,
output[1:0] DRAM_BA,
output DRAM_CAS_N,
output DRAM_CKE,
output DRAM_CLK,
output DRAM_CS_N,
inout [15:0] DRAM_DQ,
output[1:0] DRAM_DQM,
output DRAM_RAS_N,
output DRAM_WE_N,
// EPCS
output EPCS_ASDO,
input EPCS_DATA0,
output EPCS_DCLK,
output EPCS_NCSO,
// Accelerometer and EEPROM
output G_SENSOR_CS_N,
input G_SENSOR_INT,
output I2C_SCLK,
inout I2C_SDAT,
// ADC
output ADC_CS_N,
output ADC_SADDR,
output ADC_SCLK,
input ADC_SDAT,
// 2x13 GPIO Header
inout [12:0] GPIO_2,
input [2:0] GPIO_2_IN,
// GPIO_0, GPIO_0 connect to GPIO Default
inout [33:0] GPIO_0,
input [1:0] GPIO_0_IN,
// GPIO_1, GPIO_1 connect to GPIO Default
inout [33:0] GPIO_1,
input [1:0] GPIO_1_IN
);
//-----------------------------------------------------------------------------
// CPU SubSystemのインスタンス
cpuss u0 (
.clk_clk (CLOCK_50),
.reset_reset_n (KEY[0]),
.altpll_0_locked_conduit_export (LED[7]),
.altpll_0_c1_clk (DRAM_CLK),
.new_sdram_controller_0_wire_addr (DRAM_ADDR),
.new_sdram_controller_0_wire_ba (DRAM_BA),
.new_sdram_controller_0_wire_cas_n(DRAM_CAS_N),
.new_sdram_controller_0_wire_cke (DRAM_CKE),
.new_sdram_controller_0_wire_cs_n (DRAM_CS_N),
.new_sdram_controller_0_wire_dq (DRAM_DQ),
.new_sdram_controller_0_wire_dqm (DRAM_DQM),
.new_sdram_controller_0_wire_ras_n(DRAM_RAS_N),
.new_sdram_controller_0_wire_we_n (DRAM_WE_N)
);
//=============================================================================
endmodule
NiosIIソフトウェアでSDRAMへの読み書きを実装する
Eclipseを起動してBSPとアプリケーションのソフトウェアをビルドします。
アプリケーションのソフトウェアはこんな感じです。SDRAMへの読み書きチェックをして、デバッグコンソールへエラーの有無を表示するだけのお試しソフトです。
こちらを見てもらえればおわかりいただけると思いますが、SDRAMへのアクセスも、単なるAvalonバス上のレジスタリードライトとなっています。
#include "system.h"
#include "io.h"
#include "sys/alt_cache.h"
#include "sys/alt_stdio.h"
#define MEMSIZE 0x02000000
int main() {
//---- to check SDRAM ----
unsigned i;
unsigned long err=0;
unsigned int i1;
unsigned int i2;
unsigned int i3;
unsigned int ary[]={
0x47BD0F6A,
0x1E806C95,
0x2FDC3A5C
};
// SDRAMへ書き込み:1BYTEずつ
for(i=0; i<MEMSIZE-3; i=i+3){
IOWR_8DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i , (unsigned char)ary[0]);
IOWR_8DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+1, (unsigned char)ary[1]);
IOWR_8DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+2, (unsigned char)ary[2]);
}
alt_dcache_flush_all();
// SDRAMから読み出し:1BYTEずつ
err=0;
for(i=0; i<MEMSIZE-3; i=i+3){
i1=IORD_8DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i );
i2=IORD_8DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+1);
i3=IORD_8DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+2);
if ( i1!=(ary[0]&0xFF) ) err|= 0x11;
if ( i2!=(ary[1]&0xFF) ) err|=(0x12<<8);
if ( i3!=(ary[2]&0xFF) ) err|=(0x13<<16);
}
alt_printf("err=0x%x\n",err);
// SDRAMへ書き込み:2BYTEずつ
for(i=0; i<MEMSIZE-6; i=i+6){
IOWR_16DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i , (unsigned short)ary[0]);
IOWR_16DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+2, (unsigned short)ary[1]);
IOWR_16DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+4, (unsigned short)ary[2]);
}
alt_dcache_flush_all();
// SDRAMから読み出し:2BYTEずつ
err=0;
for(i=0; i<MEMSIZE-6; i=i+6){
i1=IORD_16DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i );
i2=IORD_16DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+2);
i3=IORD_16DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+4);
if ( i1!=(ary[0]&0xFFFF) ) err|= 0x21;
if ( i2!=(ary[1]&0xFFFF) ) err|=(0x22<<8);
if ( i3!=(ary[2]&0xFFFF) ) err|=(0x23<<16);
}
alt_printf("err=0x%x\n",err);
// SDRAMへ書き込み:4BYTEずつ
for(i=0; i<MEMSIZE-12; i=i+12){
IOWR_32DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i , (unsigned long)ary[0]);
IOWR_32DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+4, (unsigned long)ary[1]);
IOWR_32DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+8, (unsigned long)ary[2]);
}
alt_dcache_flush_all();
// SDRAMから読み出し:4BYTEずつ
err=0;
for(i=0; i<MEMSIZE-12; i=i+12){
i1=IORD_32DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i );
i2=IORD_32DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+4);
i3=IORD_32DIRECT(NEW_SDRAM_CONTROLLER_0_BASE, i+8);
if ( i1!=ary[0] ) err|= 0x31;
if ( i2!=ary[1] ) err|=(0x32<<8);
if ( i3!=ary[2] ) err|=(0x33<<16);
}
alt_printf("err=0x%x\n",err);
//---- end check SDRAM ----
while(1){
// do nothing ...
}
return 0;
}
あとがき
この記事ではEclipseを起動してBSPとアプリケーションのソフトウェアをビルドします、とさらっと述べましたが、ここでかなりハマりました。Eclipseは絶対パスで関連ファイルが記録されていることに起因するポカミスが多発したというのが概要ですが・・。後日、この辺を整理して記事にしたいと思います。