FPGA勉強中です
・Cyclone 10 LP Evaluation Kit で EPCQ128A を Nios II から直接読み書きする仕組みを検証しました。
・実際にセクタ消去 → 1バイト書き込み → 読み出し検証を行い、Flash のユーザ領域アクセスが正常に動作することを確認しました。
前回のベース
・前回使用したnios2のサンプルの記事です。
・それでは本題です
FPGA側の作業
・ベースは、前回のnios2のhelloworldサンプルを使います。
Platform Designerの設定
IPを追加する
前回のnios2サンプルと同じIP構成で、epcq_controller2を追加します。
IPカタログよりEPCQで検索して出てくる「epcq_controller2」を追加します。
IPを配線する
nios2のサンプルと同じ配線に対して、EPCQ Controller IIとの接続を追加します。
・EPCQ Controller II の clockをシステムクロックclk_0.clk(50MHz)と接続
・EPCQ Controller II の reset をシステムリセット接続
・EPCQ Controller II の avl_csr をNios II の data_master に接続
・EPCQ Controller II の avl_mem をNios II の data_master に接続
・EPCQ Controller II の csrとmemのオフセットアドレスを変更する。(競合していたため)
オンチップメモリのサイズを拡張しておく
・nios2サンプルでは16KBで問題なかったですが、EPCQ Controllerを追加するとサイズを超えてしまうようです。
・16KB⇒32KBに変更します。

その他設定スクショ
・ベクタ設定は、オンチップメモリに設定しました。

・nios2は「e」でOK

・EPCQ ControllerのデバイスタイプはEPCQ128Aを選択

エラーが出ないことを確認して「Generate HDL ⇒ 閉じます」
Quartusの設定
設定を1か所だけ確認
・Assignments⇒device⇒Device and Pin Options で [Use Configuration Device]にチェック
・EPCQ128Aを選択します。
トップファイル
・niosのサンプルとほとんど同じです
module NiosII_Hw_Dev_C10 (
// Reset and Clocks
input C10_CLK50M,
input C10_RESETN,
// LED and Push Button
input [3:0] USER_PB,
output [3:0] USER_LED
);
wire [2:0] led;
// Push button to turn on LED
assign USER_LED[0] = USER_PB[0];
// LED ON represent counter binary value of 1
assign USER_LED[3:1] = ~led;
// Instantiate nios2e
nios2e u0 (
.clk_clk (C10_CLK50M),
.reset_reset_n (C10_RESETN),
.led_external_connection_export (led)
);
endmodule
フルコンパイル、オブジェクトの作成
・フルコンパイルしてsofを作成します。
・sofからjicを作成します。
・jicを評価ボードに書き込みます。
ファーム側の作成
続いてnios2に載せるファームを作ります。
フォルダ構成
・以下のフォルダ構成になるようにsoftware以下のフォルダを作成します。
top_project/
├─ nios2e.sopcinfo
└─ software/
├─ nios_write_test/
└─ nios_write_test_bsp/
BSPを生成
・BSPフォルダに移動
cd /cygdrive/d/workspace/cyc10/nios_hello_read/top_project/software/nios_read_test_bsp
・BSPを生成
nios2-bsp hal . \
../../nios2e.sopcinfo \
--cpu-name nios2e \
--set hal.enable_small_c_library true \
--set hal.enable_reduced_device_drivers true \
--set hal.max_file_descriptors 4
・コマンド nios2-bsp-editorでGUIで設定してもOKです。
アプリ側 Makefile 生成
・フォルダに移動します。
cd ../nios_write_test
・フォルダにコードを置きます。
・サンプルコードは、EPCQのセクターに対して、消去前データ確認⇒消去⇒消去後データ確認⇒書き込み⇒書き込み後データ確認を行うサンプルコードになります。
#include <stdio.h>
#include "system.h"
#include "io.h"
#include "alt_types.h"
#include "altera_epcq_controller2_regs.h"
/* ==== EPCQ Controller base addresses (system.h に記載) ==== */
#define EPCQ_CSR_BASE EPCQ_CONTROLLER2_0_AVL_CSR_BASE
#define EPCQ_MEM_BASE EPCQ_CONTROLLER2_0_AVL_MEM_BASE
/* ==== テスト設定 ==== */
#define TEST_SECTOR_OFFSET 0x00400000u /* EPCQ 内物理オフセット (64KB境界) */
#define TEST_DATA_LEN 256 /* 書き込むバイト数 */
/* ---- プロトタイプ ---- */
static void wait_for_ready(void);
static void epcq_erase_sector(alt_u32 offset);
static void epcq_write_byte(alt_u32 offset, alt_u8 data);
static void dump_bytes(const char *title, alt_u32 offset, int len);
static int verify_pattern(alt_u32 offset, int len);
/* ===================================================================== */
/* メイン */
/* ===================================================================== */
int main(void)
{
printf("===== EPCQ R/W TEST START =====\n");
printf("CSR Base: 0x%08X\n", (unsigned int)EPCQ_CSR_BASE);
printf("MEM Base: 0x%08X\n", (unsigned int)EPCQ_MEM_BASE);
printf("Test offset: 0x%08X (mapped = 0x%08X)\n",
(unsigned int)TEST_SECTOR_OFFSET,
(unsigned int)(EPCQ_MEM_BASE + TEST_SECTOR_OFFSET));
/* 事前状態をダンプ */
dump_bytes("Before ERASE", TEST_SECTOR_OFFSET, 16);
/* セクタ消去 */
printf("\n-- Sector ERASE --\n");
epcq_erase_sector(TEST_SECTOR_OFFSET);
dump_bytes("After ERASE", TEST_SECTOR_OFFSET, 16);
/* テストパターン書き込み (0x00,0x01,...0xFF) */
printf("\n-- BYTE PROGRAM (0..255) --\n");
for (alt_u32 i = 0; i < TEST_DATA_LEN; i++) {
epcq_write_byte(TEST_SECTOR_OFFSET + i, (alt_u8)i);
}
/* 書き込み後の先頭16バイトダンプ */
dump_bytes("After PROGRAM", TEST_SECTOR_OFFSET, 16);
/* 全 256 バイトを検証 */
int ok = verify_pattern(TEST_SECTOR_OFFSET, TEST_DATA_LEN);
if (ok) {
printf("\n[RESULT] Verify OK : pattern 0x00..0xFF matched.\n");
} else {
printf("\n[RESULT] Verify NG : data mismatch detected.\n");
}
printf("===== EPCQ R/W TEST END =====\n");
return ok ? 0 : 1;
}
/* ===================================================================== */
/* EPCQ ビジー待ち */
/* ===================================================================== */
static void wait_for_ready(void)
{
alt_u32 status;
while (1) {
status = IORD_32DIRECT(EPCQ_CSR_BASE,
ALTERA_EPCQ_CONTROLLER2_STATUS_REG);
if ((status & ALTERA_EPCQ_CONTROLLER2_STATUS_WIP_MASK) == 0) {
break; /* WIPビットが0になれば完了 */
}
/* 適当にウェイト (CPU負荷軽減用、無くても動くが一応入れておく) */
for (volatile int i = 0; i < 1000; i++) {
/* nop */
}
}
}
/* ===================================================================== */
/* セクタ消去 */
/* offset : EPCQ内物理オフセット (0x10000境界推奨) */
/* ===================================================================== */
static void epcq_erase_sector(alt_u32 offset)
{
/* 1. Write Enable */
IOWR_32DIRECT(EPCQ_CSR_BASE,
ALTERA_EPCQ_CONTROLLER2_MEM_OP_REG,
ALTERA_EPCQ_CONTROLLER2_MEM_OP_WRITE_ENABLE_CMD);
wait_for_ready();
/* 2. Sector Erase
MEM_OP レジスタの [31:8] にアドレス上位を詰める仕様なので、
(offset >> 8) を 0xFFFFFF00 マスクで与える */
IOWR_32DIRECT(EPCQ_CSR_BASE,
ALTERA_EPCQ_CONTROLLER2_MEM_OP_REG,
ALTERA_EPCQ_CONTROLLER2_MEM_OP_SECTOR_ERASE_CMD |
((offset >> 8) & 0xFFFFFF00));
wait_for_ready();
}
/* ===================================================================== */
/* 1バイト書き込み */
/* offset : EPCQ内物理オフセット */
/* data : 書き込む1バイト */
/* ===================================================================== */
static void epcq_write_byte(alt_u32 offset, alt_u8 data)
{
/* Write Enable */
IOWR_32DIRECT(EPCQ_CSR_BASE,
ALTERA_EPCQ_CONTROLLER2_MEM_OP_REG,
ALTERA_EPCQ_CONTROLLER2_MEM_OP_WRITE_ENABLE_CMD);
wait_for_ready();
/* メモリマップ経由で1バイト書き込み */
IOWR_8DIRECT(EPCQ_MEM_BASE + offset, 0, data);
/* 書き込み完了待ち */
wait_for_ready();
}
/* ===================================================================== */
/* 先頭 n バイトをダンプ */
/* ===================================================================== */
static void dump_bytes(const char *title, alt_u32 offset, int len)
{
printf("\n%s (offset 0x%08X):\n", title, (unsigned int)offset);
for (int i = 0; i < len; i++) {
alt_u8 v = IORD_8DIRECT(EPCQ_MEM_BASE + offset + i, 0);
printf("%02X ", (unsigned int)v);
}
printf("\n");
}
/* ===================================================================== */
/* 0x00,0x01,... のパターン検証 */
/* ===================================================================== */
static int verify_pattern(alt_u32 offset, int len)
{
for (int i = 0; i < len; i++) {
alt_u8 v = IORD_8DIRECT(EPCQ_MEM_BASE + offset + i, 0);
if (v != (alt_u8)i) {
printf(" MISMATCH at +0x%08X : read=0x%02X, expect=0x%02X\n",
offset + i, (unsigned int)v, (unsigned int)(alt_u8)i);
return 0;
}
}
return 1;
}
・Makefile を生成します
nios2-app-generate-makefile \
--bsp-dir ../nios_write_test_bsp \
--src-files nios_write_test.c
・その後makeでビルドすると、elfが生成されます。
elfをダウンロード
・jicを書いた後、電源を再投入しておきます。
・別のターミナルを「nios2-terminal」コマンドで立ち上げておきます。
・以下のコマンドで、正常に書き込めたら別ターミナルに結果が出力されます。
nios2-download -r -g nios_write_test.elf
・オプションの-rはリセット、-gは動作開始の意味です。2つつけるのがポイントです。
・以下のように表示されれば、消去⇒書き込み⇒読み出しが意図通りできたことがわかります。
===== EPCQ R/W TEST START =====
CSR Base: 0x10000000
MEM Base: 0x11000000
Test offset: 0x00400000 (mapped = 0x11400000)
Before ERASE (offset 0x00400000):
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
-- Sector ERASE --
After ERASE (offset 0x00400000):
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
-- BYTE PROGRAM (0..255) --
After PROGRAM (offset 0x00400000):
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
[RESULT] Verify OK : pattern 0x00..0xFF matched.
===== EPCQ R/W TEST END =====
まとめ
・Cyclone 10 LP Evaluation Kit で EPCQ128A を Nios II から直接読み書きする仕組みを検証しました。
・Platform Designer で EPCQ Controller II を構成し、CSR/MEMアドレスを合わせて Nios II から制御できました。
・実際にセクタ消去 → 1バイト書き込み → 読み出し検証を行い、Flash のユーザ領域アクセスが正常に動作することを確認しました。
・EPCQ128A を FPGAコンフィグ兼ユーザデータ領域として扱えることがわかりました。今後は、FPGA書き換えなどをやっていこうと思います。

