NiosIIでSDRAMを追加する際の設定ポイント

# この記事の概要

Intel FPGA搭載のDe0-Nanoボードにて、NiosIIへSDRAMを追加する際の設定について、試して理解したことを書き残します。

:::note info
使用したもの
開発環境：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

![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/0e789dc6-e8f9-e47c-4f9a-5feee3cf88b9.png)

### NiosIIプラットフォームへPLLを組み込む
IP Catalogから、下記のツリーを辿ってIPをNiosIIプラットフォームへ追加します。

* 「Basic Functions」→「Clocks; PLLs and Resets」→「PLL」→「ALTPLL Intel FPGA IP」

追加したらPLLを設定。

* 入力クロックは50MHz
![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/bb2d62a9-d4c9-2bcf-0feb-84c19e5c1a89.png)
* リセット信号は入力しない
![図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/0650dfd7-7215-0e05-abfa-6ffd2d34fa6b.png)
* 逓倍出力したいクロックは100MHz
![図4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/5fd13191-d6af-6d50-7196-0632fb2b6212.png)
* もう一つの逓倍出力したいクロックは100MHzで位相を3000ps早める。（位相をずらす理由は後述）
 * 位相を遅らせる方をプラスとして設定するため、早める方向はマイナスとなります。
![図5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/3a390eb7-b9b4-a406-2ff3-7cdd4e228c43.png)

次に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として外部出力しておきましたが、この後特に使う用途は無いのであまり気にせずに。。。）
![図6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/7fc4b586-8c69-8928-f37d-b1caad10172e.png)

### NiosIIプラットフォームへSDRAMコントローラーを組み込む
IP Catalogから、下記のツリーを辿ってIPをNiosIIプラットフォームへ追加します。

* 「Memory Interfaces and Controlls」→「SDRAM」→「SDRAM Controller Intel FPGA IP」

追加したらDE0-Nano Development Boardに搭載されたSDRAM(品名：IS42S16160G-7TLI)に合わせた設定を入力。

* 「Memory Profile」タブはSDRAMのデータシートを見ればすぐに入力できる内容ばかりです。
![図7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/72cefce6-dd89-82e4-3a82-30ca7ae48207.png)

* 一方で「Timing」タブに設定する数値は何が適切なのか解らずじまいでした。仕方なくDE0-Nano Development Board付属のサンプルプロジェクトの値を拝借してみました。
 * SDRAMの仕様をしっかり理解していればデータシートから読み取れると思うので、ここは今後の勉強テーマです。

![図8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/b29211d4-c47e-d2f1-da07-9e2fa51e270f.png)

最後にAvalonバス上の接続を行います。

* new_sdram_controller_0のwireは、Export列をダブルクリックして、外部との入出力ができるようにしておきます。
 * この信号が物理的なSDRAMとつながる信号線です。
![図9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/63bd3586-b201-eade-9ebe-7c5a8a89611d.png)

こうしてできたNiosIIプラットフォームを、最上位階層にてインスタンスして、論理合成、配置配線を実施します。

```verilog:最上位階層
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バス上のレジスタリードライトとなっています。

```c:SDRAM読み書きチェック
#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は絶対パスで関連ファイルが記録されていることに起因するポカミスが多発したというのが概要ですが・・。後日、この辺を整理して記事にしたいと思います。