FPGA Sipeed Tang Nano 9Kを使ってSDカードアクセス(その1。下調べとハード設計編)
リーズナブルFPGAシリーズのTang Nano 9Kを使ってTFカードスロットにアクセスしました(その1。下調べとハード設計編)
SDカードへアクセスするための下準備
- SDカードへアクセスするためのドライバ(ソフト)
- ドライバを動かすCPU(ハード)
- TFカードスロット(SD)へアクセスするためのインターフェース(ハード)
- ドライバやドライバを動かすための上位層のアプリケーションをコンパイルするための環境(ソフト)
SDカードへのアクセスを実現するには、おおよそ上記の具材を揃えないといけないです。
TangNano9KはTFカードスロットが実装されているので、動かすためのサンプルが準備されていると楽なのにと思うところはありますが、いたし方ありません。
CPUの準備
Sipeedのwikiに記載のある"Examples of PicoRV"を参考にします。
wikiのページ
PicoRV on nano 9K - Sipeed Wiki
このexampleでは、picorv32というriscvコアが使われていて、本コアにいくつかの周辺回路を付加してpicotinyというシステムが作られています。このシステムにTFカードスロットを駆動するインターフェースを追加して、アクセスを実現することにします。
exampleのソースコードは以下のgithubにあります。
github.com
SDカードにアクセスするためのドライバ
以下のラズパイpicoでfatfsを実装する記事がとても参考になりました。この記事がないと途方に暮れてたと思います。感謝です。
qiita.com
TFカードスロット(SD)へアクセスするためのインターフェース
上記のfatfsを実装する記事によるとSDへはSPIを介してアクセスしています。調べてみるとSDカードにはSPIでアクセス可能なモードが準備されているようです。SPIであれば簡単に実装できそうです。
とはいえ、自分で作成するのも大変なので、以下にあるSPIのverilogコードを利用しました。CSがないSPI_Master.vを利用しました。
Copyright (c) 2019 russell-merrick
github.com
SPIにはCLK出力の極性などいくつか動作モードがあるようです。以下のサイトによるとSDカードへアクセスする際はMode0(CPHA=0,CPOL=0)とのこと。上記SPIのverilogコードではparameterでモードを設定できるようになっています。Mode0であれば、overrideせずにそのまま利用が可能です。
下のドキュメントのMCUとCARDの接続図はTangNano9Kのボードのピンアサインと論理情報の紐づけにも役に立ちました。
elm-chan.org
picoRV32のクロスコンパイル環境の構築
wsl2環境のubuntu20で環境を整えました。
ソースからコンパイルするのではなく、コンパイル済みのバイナリを利用します。
Sipeedのwikiのfirmwareは最近のバージョンのコンパイラでは動かす事が出来ませんでした(コンパイルは通るが動かない)。
Makefileに記載のコンパイラのパス名から(下記),検索して探すことにしました
RISCV_PATH ?= D:/gnu-mcu-eclipse-riscv-none-gcc-8.2.0
Release xPack GNU RISC-V Embedded GCC v8.2.0-3.1 · xpack-dev-tools/riscv-none-embed-gcc-xpack · GitHub
ここから、xpack-riscv-none-embed-gcc-8.2.0-3.1-linux-x64.tgz
をダウンロードして任意のdirへ展開するだけです。私はホーム直下にriscvのdirを作成し、そこへ展開しました
MakefileのRISCV_PATHを以下の様に修正しました
RISCV_PATH ?= ~/riscv/riscv-none-embed-gcc/8.2.0-3.1
ハード設計
exampleの構成
改造する前に、exampleの構成を確認します。
picotiny +- u_pll (Gowin_rPLL) +- u_div_5 (Gowin_CLKDIV) +- u_Reset_Sync (Reset_Sync) +- u_picorv32 (picorv32) +- u_PicoMem_SRAM_8KB_7 (PicoMem_SRAM_8KB) +- u_PicoMem_Mux_1_4_8 (PicoMem_Mux_1_4) +- u_PicoMem_Mux_1_4_picop (PicoMem_Mux_1_4) +- u_PicoMem_SPI_Flash_18 (PicoMem_SPI_Flash) +- u_boot_sram (PicoMem_BOOT_SRAM_8KB) +- u_PicoMem_GPIO (PicoMem_GPIO) +- u_PicoMem_UART (PicoMem_UART) +- u_hdmi (svo_hdmi_top)
階層構造は上記の様になっています。
私の方で理解した各モジュールの役割を簡単に記載すると、
- Gowin_rPLL
27MHzクリスタルから、逓倍して126MHzのクロックを作っています。
- Gowin_CLKDIV
PLL出力126MHzを5分周して25.2MHzのクロックを作っています。本クロックがriscvコア、周辺回路のクロックになっています。
- picorv32
riscvコアです。
- PicoMem_SRAM_8KB
データRAMとして利用されています。(bss,heap,stackなど)
- PicoMem_Mux_1_4
2つインスタンスされていますが、picorv32からのバスアクセスをアドレスに応じてセレクトします。後ほどアドレスマップを記載します。
- PicoMem_SPI_Flash
内蔵flashへアクセスするためのSPI I/Fです。
- PicoMem_BOOT_SRAM_8KB
ブートコードがrtlに仕込まれています(合成時に確定する)。
- PicoMem_GPIO
GPIOはボード上のLEDを制御するための仕組みになります。
- PicoMem_UART
UARTを介してPCと通信が可能で、また入出力の文字がHDMIで表示されるようにもなっています(PCのUART端末とHDMI両方に表示されるようになっている)
- svo_hdmi_top
HDMIは冒頭の写真のようにバックグラウンドに画像が表示されておりUARTを介してのやりとりがoverlayされて文字が表示される仕組みです。
アドレスマップ
wikiのREADMEに記載ありますが、
アドレス | 割り当て |
---|---|
0x00000000 - 0x007FFFFF | 8MiB SPI Flash XIP |
0x40000000 - 0x40001FFF | 8KiB SRAM |
0x80000000 - 0x8FFFFFFF | PicoPeripherals |
0x80000000 - 0x80001FFF | 8KiB BootROM |
0x81000000 - 0x8100000F | SPI Flash Config / Bitbang IO |
0x82000000 - 0x8200000F | GPIO |
0x83000000 - 0x8300000F | UART |
0xC0000000 - 0xFFFFFFFF | Reserved(Wishbone) |
バス構成
バススイッチはboot_sram,GPIO,UART,FLASHのcfgに関しては2段経由しています。1段目のスイッチで0x80000000 - 0x8FFFFFFFの領域のアクセス(s2)が2段目へ伝わるようになっており、2段目でどの周辺へのアクセスするかを決めてます。
picorv32のバスタイミング
mem_valid & mem_ready =1 でバスアクセスが成立します。
mem_strb[3:0]が全ビット0でreadサイクルを示します。4bitのwstrbのうち、どこかが1の場合にライトサイクルを示し、mem_wdata[31:0]のLSBから8bit毎に順にmem_wstrb[0]→ mem_wstrb[3]が対応しています。
写真には載せていませんがPicoMem_Muxで行われているバスのスイッチは、単にアドレスのデコードのみで行われていて、clockでタイミングを取るなどの措置は行われていません。(つまりMux回路には一切FFは入っていません)
改造内容
まずSPIを組み込むアドレス領域を決めなければなりません。1段目のpicop領域(S2)は変えずに、2段目のバススイッチを4分岐から5分岐へ変更する事でSPI領域を作ります。以下の様なアドレスマップへ変更しました。
改造後のアドレスマップ
アドレス | 割り当て |
---|---|
0x00000000 - 0x007FFFFF | 8MiB SPI Flash XIP |
0x40000000 - 0x40001FFF | 8KiB SRAM |
0x80000000 - 0x8FFFFFFF | PicoPeripherals |
0x80000000 - 0x80001FFF | 8KiB BootROM |
0x81000000 - 0x8100000F | SPI Flash Config / Bitbang IO |
0x82000000 - 0x8200000F | GPIO |
0x83000000 - 0x8300000F | UART |
0x84000000 - 0x84000FFF | SPI(追加) |
0xC0000000 - 0xFFFFFFFF | Reserved(Wishbone) |
2段目のバススイッチを5ポートへ変更
モジュール名をPicoMem_Mux_1_5と変更します。
バススイッチの論理はpicoperiphal.vにあるので、モジュールPicoMem_Mux_1_4をコピーしてPicoMem_Mux_1_5を作成し、5ポート目のpicos4_xxx入出力端子を追加します。
論理は以下の様になります。
module PicoMem_Mux_1_5 #( parameter PICOS0_ADDR_BASE = 32'h0000_0000, parameter PICOS0_ADDR_MASK = 32'hC000_0000, parameter PICOS1_ADDR_BASE = 32'h2000_0000, parameter PICOS1_ADDR_MASK = 32'hC000_0000, parameter PICOS2_ADDR_BASE = 32'h4000_0000, parameter PICOS2_ADDR_MASK = 32'hC000_0000, parameter PICOS3_ADDR_BASE = 32'h8000_0000, parameter PICOS3_ADDR_MASK = 32'hC000_0000, parameter PICOS4_ADDR_BASE = 32'ha000_0000, parameter PICOS4_ADDR_MASK = 32'hC000_0000 ) ( input picos0_ready, input [31:0] picos0_rdata, input picos1_ready, input [31:0] picos1_rdata, input picom_valid, input [31:0] picom_addr, input [31:0] picom_wdata, input [3:0] picom_wstrb, input picos2_ready, input [31:0] picos2_rdata, input picos3_ready, input [31:0] picos3_rdata, input picos4_ready, input [31:0] picos4_rdata, output picos0_valid, output [31:0] picos0_addr, output [31:0] picos0_wdata, output [3:0] picos0_wstrb, output picos1_valid, output [31:0] picos1_addr, output [31:0] picos1_wdata, output [3:0] picos1_wstrb, output picom_ready, output [31:0] picom_rdata, output picos2_valid, output [31:0] picos2_addr, output [31:0] picos2_wdata, output [3:0] picos2_wstrb, output picos3_valid, output [31:0] picos3_addr, output [31:0] picos3_wdata, output [3:0] picos3_wstrb, output picos4_valid, output [31:0] picos4_addr, output [31:0] picos4_wdata, output [3:0] picos4_wstrb ); wire picos0_match = ~|((picom_addr ^ PICOS0_ADDR_BASE) & PICOS0_ADDR_MASK); wire picos1_match = ~|((picom_addr ^ PICOS1_ADDR_BASE) & PICOS1_ADDR_MASK); wire picos2_match = ~|((picom_addr ^ PICOS2_ADDR_BASE) & PICOS2_ADDR_MASK); wire picos3_match = ~|((picom_addr ^ PICOS3_ADDR_BASE) & PICOS3_ADDR_MASK); wire picos4_match = ~|((picom_addr ^ PICOS4_ADDR_BASE) & PICOS4_ADDR_MASK); wire picos0_sel = picos0_match; wire picos1_sel = picos1_match & (~picos0_match); wire picos2_sel = picos2_match & (~picos0_match) & (~picos1_match); wire picos3_sel = picos3_match & (~picos0_match) & (~picos1_match) & (~picos2_match); wire picos4_sel = picos4_match & (~picos0_match) & (~picos1_match) & (~picos2_match) & (~picos3_match); // master assign picom_rdata = picos0_sel ? picos0_rdata : picos1_sel ? picos1_rdata : picos2_sel ? picos2_rdata : picos3_sel ? picos3_rdata : picos4_sel ? picos4_rdata : 32'b0; assign picom_ready = picos0_sel ? picos0_ready : picos1_sel ? picos1_ready : picos2_sel ? picos2_ready : picos3_sel ? picos3_ready : picos4_sel ? picos4_ready : 1'b0; // slave 0 assign picos0_valid = picom_valid & picos0_sel; assign picos0_addr = picom_addr; assign picos0_wdata = picom_wdata; assign picos0_wstrb = picom_wstrb; // slave 1 assign picos1_valid = picom_valid & picos1_sel; assign picos1_addr = picom_addr; assign picos1_wdata = picom_wdata; assign picos1_wstrb = picom_wstrb; // slave 2 assign picos2_valid = picom_valid & picos2_sel; assign picos2_addr = picom_addr; assign picos2_wdata = picom_wdata; assign picos2_wstrb = picom_wstrb; // slave 3 assign picos3_valid = picom_valid & picos3_sel; assign picos3_addr = picom_addr; assign picos3_wdata = picom_wdata; assign picos3_wstrb = picom_wstrb; // slave 4 assign picos4_valid = picom_valid & picos4_sel; assign picos4_addr = picom_addr; assign picos4_wdata = picom_wdata; assign picos4_wstrb = picom_wstrb; endmodule
SPI回路の設計
SPI回路自体は先ほど紹介したrussell-merrickさんのをそのまま流用するのですが、ドライバが作りやすいように、送信fifo8段、受信fifo8段、SPIの状態及びfifoの状態を示すフラグを実装します。
またpicorv32のバスに直にぶら下げられるようにバスインターフェースも一緒に実装する事にします。
本バスからSPIのCS出力を制御できるようにCS制御レジスタも設けます。
フラグ関連のレジスタは本バスから制御できるようにします。これらの回路はSPI回路本体の上位階層で作ります。
レジスタ構成
アドレス | 機能 | ||
---|---|---|---|
0x00 | control0 | 未実装 | 分周2-254(偶数のみ) |
0x04 | control1 | 未実装 | SPIのモード設定 |
0x08 | txdata | 送信データ | |
0x0c | rxdata | 受信データ | |
0x10 | Status register | 各種フラグ | |
0x14 | CS出力 | bit0 |
0x10 | Status register(RO) | |
---|---|---|
bit0 | SPI_BSY | SPI 送信or受信動作中 |
bit1 | SPI_TFE | txfifo empty 送信fifoが空 |
bit2 | SPI_TFF | txfifo almostfull 送信fifoはフル |
bit3 | SPI_RNE | rxfifo not empty 受信fifoは空ではない |
bit4 | SPI_RF | rxfifo full 受信fifoはフル |
SPIバスタイミング
picorv32とのインターフェースはライトサイクルはno-wait。リードサイクルは1waitとしました。
SPIラッパーモジュール(spi_top.v)
`timescale 1ns/1ps // SPI_Master.v // に対し // wish bone if 化 // tx_fifo, rx_fifo 8段 // spi clock 生成用分周器 // spi 動作モード // module spi_top #(parameter SPI_MODE = 0, parameter CLKS_PER_HALF_BIT = 2, parameter MAX_BYTES_PER_CS = 2, parameter CS_INACTIVE_CLKS = 1) ( input wire rstn, input wire clk, // 126MHz clk // wish bone input wire [11:0] spi_mem_addr, input wire [31:0] spi_mem_wdata, output reg [31:0] spi_mem_rdata, input wire [3:0] spi_mem_wstrb, input wire spi_mem_valid, output wire spi_mem_ready, // SPI master i/f output wire o_SPI_Clk, input wire i_SPI_MISO, output wire o_SPI_MOSI, output reg o_SPI_CS_n ); reg [7:0] i_TX_Byte; reg i_TX_DV; wire o_TX_Ready; wire o_RX_DV; wire [7:0] o_RX_Byte; reg [7:0] txdata_fifo[0:7]; reg [7:0] rxdata_fifo[0:7]; reg mem_w_ready; wire mem_r_ready; reg RSTATE; reg [3:0] txdata_wpt; reg [3:0] txdata_rpt; reg [3:0] rxdata_wpt; reg [3:0] rxdata_rpt; wire txdata_wen; wire txfifo_almostfull; wire txdata_ready; wire txfifo_empty; // status系 reg SPI_BSY; reg SPI_TFE; // txfifo empty reg SPI_TFF; // txfifo almost full reg SPI_RNE; // rxfifo not empty reg SPI_RF; // rxfifo full assign spi_mem_ready = mem_r_ready & mem_w_ready ; // Instantiate Master SPI_Master #(.SPI_MODE(SPI_MODE), .CLKS_PER_HALF_BIT(CLKS_PER_HALF_BIT) ) SPI_Master_Inst ( // Control/Data Signals, .i_Rst_L(rstn), // FPGA Reset .i_Clk(clk), // FPGA Clock // TX (MOSI) Signals .i_TX_Byte(i_TX_Byte), // Byte to transmit .i_TX_DV(i_TX_DV), // Data Valid Pulse .o_TX_Ready(o_TX_Ready), // Transmit Ready for Byte // RX (MISO) Signals .o_RX_DV(o_RX_DV), // Data Valid pulse (1 clock cycle) .o_RX_Byte(o_RX_Byte), // Byte received on MISO // SPI Interface .o_SPI_Clk(o_SPI_Clk), .i_SPI_MISO(i_SPI_MISO), .o_SPI_MOSI(o_SPI_MOSI) ); // ------------------------------------ // txfifo wpt // ------------------------------------ assign txdata_wen = (spi_mem_addr==12'h008)& spi_mem_valid&(|spi_mem_wstrb)&mem_w_ready; always@(negedge rstn or posedge clk) if(~rstn) txdata_wpt <= 4'h0; else if(txdata_wen) txdata_wpt <= txdata_wpt + 4'h1; // ------------------------------------ // txdata_rpt // ------------------------------------ always@(negedge rstn or posedge clk) if(~rstn) txdata_rpt <= 4'h0; else if(i_TX_DV) txdata_rpt <= txdata_rpt + 4'h1; // ------------------------------------- // txfifo_almostfull // txfifoがフルになる一つ手前でアサート // fullでもアサートされる // ------------------------------------- assign txfifo_almostfull = ((txdata_wpt-txdata_rpt)==4'h7) | ((txdata_wpt-txdata_rpt)==4'h8) ; // ------------------------------------- // txdata_ready // txfifoあり // ------------------------------------- assign txdata_ready = (|(txdata_wpt-txdata_rpt)); // ------------------------------------- // txfifo_empty // ------------------------------------- assign txfifo_empty = (txdata_wpt == txdata_rpt); // ------------------------------------- // mem_w_ready // write アクセス // ready信号 // ------------------------------------- always@(negedge rstn or posedge clk) if(~rstn) mem_w_ready <= 1'b1; else mem_w_ready <= ~txfifo_almostfull; // ------------------------------------- // txdata_fifo // ------------------------------------- always@(posedge clk) if(txdata_wen) txdata_fifo[txdata_wpt[2:0]] <= spi_mem_wdata[7:0]; // ------------------------------------- // i_TX_Byte (送信データ) // i_TX_DV (送信enable 1shot) // ------------------------------------- always@(posedge clk) if(o_TX_Ready&txdata_ready) i_TX_Byte <= txdata_fifo[txdata_rpt[2:0]]; always@(negedge rstn or posedge clk) if(~rstn) i_TX_DV <= 1'b0; else if(i_TX_DV) i_TX_DV <= 1'b0; else if(o_TX_Ready&txdata_ready) i_TX_DV <= 1'b1; // ------------------------------------- // SPI_BSY // ------------------------------------- always@(negedge rstn or posedge clk) if(~rstn) SPI_BSY <= 1'b0; else if(o_RX_DV & txfifo_empty) SPI_BSY <= 1'b0; else if(i_TX_DV & txdata_ready) SPI_BSY <= 1'b1; // ------------------------------------- // SPI_TFE // txfifo empty // ------------------------------------- always@(negedge rstn or posedge clk) if(~rstn) SPI_TFE <= 1'b1; else SPI_TFE <= txfifo_empty; always@(negedge rstn or posedge clk) if(~rstn) SPI_TFF <= 1'b0; else SPI_TFF <= txfifo_almostfull; // ------------------------------------- // SPI_RNE // rxfifo not empty // ------------------------------------- always@(negedge rstn or posedge clk) if(~rstn) SPI_RNE <= 1'b0; else if(o_RX_DV) SPI_RNE <= 1'b1; else if(rxdata_wpt==rxdata_rpt) SPI_RNE <= 1'b0; always@(negedge rstn or posedge clk) if(~rstn) SPI_RF <= 1'b0; else SPI_RF <= (rxdata_wpt[3]^rxdata_rpt[3])& (rxdata_wpt[2:0]==rxdata_rpt[2:0]); // ------------------------------------ // rxdata_wpt, rpt // rptはレジスタリードでupdate // ------------------------------------ always@(negedge rstn or posedge clk) if(~rstn) rxdata_wpt <= 4'h0; else if(o_RX_DV) rxdata_wpt <= rxdata_wpt + 4'h1; // read busタイミング wire read_bus; wire read_rxdata_bus; wire read_status_bus; assign read_bus = spi_mem_valid & (spi_mem_wstrb==4'h0); assign read_rxdata_bus = (spi_mem_addr==12'h00c)& spi_mem_valid & (spi_mem_wstrb==4'h0); assign read_status_bus = (spi_mem_addr==12'h010)& spi_mem_valid & (spi_mem_wstrb==4'h0); always@(negedge rstn or posedge clk) if(~rstn) rxdata_rpt <= 4'h0; else if( read_rxdata_bus & (~RSTATE) ) rxdata_rpt <= rxdata_rpt + 4'h1; // ------------------------------------ // mem_r_ready (read側 wishbone ready) // ------------------------------------ assign mem_r_ready = ~(read_bus & (~RSTATE)); // ------------------------------------ // RSTATE (read bus timing 後半サイクル) // ------------------------------------ always@(negedge rstn or posedge clk) if(~rstn) RSTATE <= 1'b0; else if(RSTATE) RSTATE <= 1'b0; else if(read_bus) RSTATE <= 1'b1; // ------------------------------------- // rxdata_fifo // ------------------------------------- always@(posedge clk) if(o_RX_DV) rxdata_fifo[rxdata_wpt[2:0]] <= o_RX_Byte; // ------------------------------------- // bus // spi_mem_rdata // ------------------------------------- always@(posedge clk) if(read_rxdata_bus&(~RSTATE)) spi_mem_rdata <= {24'h000000,rxdata_fifo[rxdata_rpt[2:0]]}; else if(read_status_bus&(~RSTATE)) spi_mem_rdata <= {24'h000000,3'h0,SPI_RF,SPI_RNE,SPI_TFF,SPI_TFE,SPI_BSY}; // ------------------------------------- // o_SPI_CS_n // CS出力 // ------------------------------------- wire write_CSREG_bus; assign write_CSREG_bus = (spi_mem_addr==12'h014)& spi_mem_valid & (spi_mem_wstrb[0]==1'b1); always@(negedge rstn or posedge clk) if(~rstn) o_SPI_CS_n <= 1'b1; else if(write_CSREG_bus) o_SPI_CS_n <= spi_mem_wdata[0]; endmodule
picotiny.vの改造
インスタンス名 u_PicoMem_Mux_1_4_picopを先ほど作成した1→5ポート回路へ差し替えて、spi_topのインスタンスを追加します。
さらに入出力端子はTFカードスロットと繋げるSPIの入出力端子を追加します。
picotiny.v
`timescale 1ns/1ps module picotiny ( input clk, input resetn, output tmds_clk_n, output tmds_clk_p, output [2:0] tmds_d_n, output [2:0] tmds_d_p, output flash_clk, output flash_csb, inout flash_mosi, inout flash_miso, input ser_rx, output ser_tx, inout [6:0] gpio, output o_SPI_Clk, input i_SPI_MISO, output o_SPI_MOSI, output o_SPI_CS_n ); wire sys_resetn; wire mem_valid; wire mem_ready; wire [31:0] mem_addr; wire [31:0] mem_wdata; wire [3:0] mem_wstrb; wire [31:0] mem_rdata; wire spimemxip_valid; wire spimemxip_ready; wire [31:0] spimemxip_addr; wire [31:0] spimemxip_wdata; wire [3:0] spimemxip_wstrb; wire [31:0] spimemxip_rdata; wire sram_valid; wire sram_ready; wire [31:0] sram_addr; wire [31:0] sram_wdata; wire [3:0] sram_wstrb; wire [31:0] sram_rdata; wire picop_valid; wire picop_ready; wire [31:0] picop_addr; wire [31:0] picop_wdata; wire [3:0] picop_wstrb; wire [31:0] picop_rdata; wire wbp_valid; wire wbp_ready; wire [31:0] wbp_addr; wire [31:0] wbp_wdata; wire [3:0] wbp_wstrb; wire [31:0] wbp_rdata; wire spimemcfg_valid; wire spimemcfg_ready; wire [31:0] spimemcfg_addr; wire [31:0] spimemcfg_wdata; wire [3:0] spimemcfg_wstrb; wire [31:0] spimemcfg_rdata; wire brom_valid; wire brom_ready; wire [31:0] brom_addr; wire [31:0] brom_wdata; wire [3:0] brom_wstrb; wire [31:0] brom_rdata; wire gpio_valid; wire gpio_ready; wire [31:0] gpio_addr; wire [31:0] gpio_wdata; wire [3:0] gpio_wstrb; wire [31:0] gpio_rdata; wire uart_valid; wire uart_ready; wire [31:0] uart_addr; wire [31:0] uart_wdata; wire [3:0] uart_wstrb; wire [31:0] uart_rdata; wire spi_sd_valid; wire spi_sd_ready; wire [31:0] spi_sd_addr; wire [31:0] spi_sd_wdata; wire [3:0] spi_sd_wstrb; wire [31:0] spi_sd_rdata; wire clk_p; wire clk_p5; wire pll_lock; // clk = 27MHz // localparam PLL_IDIV = 3 - 1; // 0~63 // localparam PLL_FBDIV = 14- 1; // 0~63 nano-9k 1080p // localparam PLL_ODIV = 4; // 2, 4, 8, 16, 32, 48, 64, 80, 96, 112, 128 // clkout = (27x14)/3 = 126MHz // f_CLKOUT = f_CLKIN * FBDIV / IDIV, 3.125~600MHz // f_VCO = f_CLKOUT * ODIV, 400~1200MHz // = 126*4 = 504MHz // f_PFD = f_CLKIN / IDIV = f_CLKOUT / FBDIV, 3~400MHz // = 27 / 3 = 126/14 = 9 Gowin_rPLL u_pll ( .clkin(clk), .clkout(clk_p5), // .clkoutd(clk_p), .lock(pll_lock) ); Gowin_CLKDIV u_div_5 ( .clkout(clk_p), // 25.2MHz .hclkin(clk_p5), .resetn(pll_lock) ); Reset_Sync u_Reset_Sync ( .resetn(sys_resetn), .ext_reset(resetn & pll_lock), .clk(clk_p) ); picorv32 #( .PROGADDR_RESET(32'h8000_0000) ) u_picorv32 ( .clk(clk_p), .resetn(sys_resetn), .trap(), .mem_valid(mem_valid), .mem_instr(), .mem_ready(mem_ready), .mem_addr(mem_addr), // out .mem_wdata(mem_wdata), // out .mem_wstrb(mem_wstrb), // out .mem_rdata(mem_rdata), // in .irq(32'b0), .eoi() ); PicoMem_SRAM_8KB u_PicoMem_SRAM_8KB_7 ( .resetn(sys_resetn), .clk(clk_p), .mem_s_valid(sram_valid), .mem_s_ready(sram_ready), .mem_s_addr(sram_addr), .mem_s_wdata(sram_wdata), .mem_s_wstrb(sram_wstrb), .mem_s_rdata(sram_rdata) ); // S0 0x0000_0000 -> SPI Flash XIP // S1 0x4000_0000 -> SRAM // S2 0x8000_0000 -> PicoPeriph // S3 0xC000_0000 -> Wishbone PicoMem_Mux_1_4 u_PicoMem_Mux_1_4_8 ( // slave i/f .picom_valid(mem_valid), // in .picom_ready(mem_ready), // out .picom_addr(mem_addr), // in 32bit .picom_wdata(mem_wdata), // in .picom_wstrb(mem_wstrb), // in .picom_rdata(mem_rdata), // out 32bit // master i/f .picos0_valid(spimemxip_valid), // out .picos0_ready(spimemxip_ready), // in .picos0_addr(spimemxip_addr), // out .picos0_wdata(spimemxip_wdata), // out .picos0_wstrb(spimemxip_wstrb), // out .picos0_rdata(spimemxip_rdata), // in .picos1_valid(sram_valid), .picos1_ready(sram_ready), .picos1_addr(sram_addr), .picos1_wdata(sram_wdata), .picos1_wstrb(sram_wstrb), .picos1_rdata(sram_rdata), .picos2_valid(picop_valid), .picos2_ready(picop_ready), .picos2_addr(picop_addr), .picos2_wdata(picop_wdata), .picos2_wstrb(picop_wstrb), .picos2_rdata(picop_rdata), .picos3_valid(wbp_valid), .picos3_ready(wbp_ready), .picos3_addr(wbp_addr), .picos3_wdata(wbp_wdata), .picos3_wstrb(wbp_wstrb), .picos3_rdata(wbp_rdata) ); // S0 0x8000_0000 -> BOOTROM // S1 0x8100_0000 -> SPI Flash // S2 0x8200_0000 -> GPIO // S3 0x8300_0000 -> UART // S4 0x8400_0000 -> SPI SDcard PicoMem_Mux_1_5 #( .PICOS0_ADDR_BASE(32'h8000_0000), .PICOS0_ADDR_MASK(32'h0F00_0000), .PICOS1_ADDR_BASE(32'h8100_0000), .PICOS1_ADDR_MASK(32'h0F00_0000), .PICOS2_ADDR_BASE(32'h8200_0000), .PICOS2_ADDR_MASK(32'h0F00_0000), .PICOS3_ADDR_BASE(32'h8300_0000), .PICOS3_ADDR_MASK(32'h0F00_0000), .PICOS4_ADDR_BASE(32'h8400_0000), .PICOS4_ADDR_MASK(32'h0F00_0000) ) u_PicoMem_Mux_1_4_picop ( .picom_valid(picop_valid), .picom_ready(picop_ready), .picom_addr(picop_addr), .picom_wdata(picop_wdata), .picom_wstrb(picop_wstrb), .picom_rdata(picop_rdata), .picos0_valid(brom_valid), .picos0_ready(brom_ready), .picos0_addr(brom_addr), .picos0_wdata(brom_wdata), .picos0_wstrb(brom_wstrb), .picos0_rdata(brom_rdata), .picos1_valid(spimemcfg_valid), .picos1_ready(spimemcfg_ready), .picos1_addr(spimemcfg_addr), .picos1_wdata(spimemcfg_wdata), .picos1_wstrb(spimemcfg_wstrb), .picos1_rdata(spimemcfg_rdata), .picos2_valid(gpio_valid), .picos2_ready(gpio_ready), .picos2_addr(gpio_addr), .picos2_wdata(gpio_wdata), .picos2_wstrb(gpio_wstrb), .picos2_rdata(gpio_rdata), .picos3_valid(uart_valid), .picos3_ready(uart_ready), .picos3_addr(uart_addr), .picos3_wdata(uart_wdata), .picos3_wstrb(uart_wstrb), .picos3_rdata(uart_rdata), .picos4_valid(spi_sd_valid), .picos4_ready(spi_sd_ready), .picos4_addr(spi_sd_addr), .picos4_wdata(spi_sd_wdata), .picos4_wstrb(spi_sd_wstrb), .picos4_rdata(spi_sd_rdata) ); PicoMem_SPI_Flash u_PicoMem_SPI_Flash_18 ( .clk (clk_p), .resetn (sys_resetn), .flash_csb (flash_csb), .flash_clk (flash_clk), .flash_mosi (flash_mosi), .flash_miso (flash_miso), .flash_mem_valid (spimemxip_valid), .flash_mem_ready (spimemxip_ready), .flash_mem_addr (spimemxip_addr), .flash_mem_wdata (spimemxip_wdata), .flash_mem_wstrb (spimemxip_wstrb), .flash_mem_rdata (spimemxip_rdata), .flash_cfg_valid (spimemcfg_valid), .flash_cfg_ready (spimemcfg_ready), .flash_cfg_addr (spimemcfg_addr), .flash_cfg_wdata (spimemcfg_wdata), .flash_cfg_wstrb (spimemcfg_wstrb), .flash_cfg_rdata (spimemcfg_rdata) ); PicoMem_BOOT_SRAM_8KB u_boot_sram ( .resetn(sys_resetn), .clk(clk_p), .mem_s_valid(brom_valid), .mem_s_ready(brom_ready), .mem_s_addr(brom_addr), .mem_s_wdata(brom_wdata), .mem_s_wstrb(brom_wstrb), .mem_s_rdata(brom_rdata) ); PicoMem_GPIO u_PicoMem_GPIO ( .resetn(sys_resetn), .io(gpio), .clk(clk_p), .busin_valid(gpio_valid), .busin_ready(gpio_ready), .busin_addr(gpio_addr), .busin_wdata(gpio_wdata), .busin_wstrb(gpio_wstrb), .busin_rdata(gpio_rdata) ); PicoMem_UART u_PicoMem_UART ( .resetn(sys_resetn), .clk(clk_p), .mem_s_valid(uart_valid), .mem_s_ready(uart_ready), .mem_s_addr(uart_addr), .mem_s_wdata(uart_wdata), .mem_s_wstrb(uart_wstrb), .mem_s_rdata(uart_rdata), .ser_rx(ser_rx), .ser_tx(ser_tx) ); assign wbp_ready = 1'b1; wire svo_term_valid; assign svo_term_valid = (uart_valid && uart_ready) & (~uart_addr[2]) & uart_wstrb[0]; svo_hdmi_top u_hdmi ( .clk(clk_p), .resetn(sys_resetn), // video clocks .clk_pixel(clk_p), .clk_5x_pixel(clk_p5), .locked(pll_lock), .term_in_tvalid( svo_term_valid ), .term_out_tready(), .term_in_tdata( uart_wdata[7:0] ), // output signals .tmds_clk_n(tmds_clk_n), .tmds_clk_p(tmds_clk_p), .tmds_d_n(tmds_d_n), .tmds_d_p(tmds_d_p) ); spi_top #( .SPI_MODE( 0), .CLKS_PER_HALF_BIT(2), .MAX_BYTES_PER_CS( 2), .CS_INACTIVE_CLKS(1) ) u_spi_top ( .rstn(sys_resetn), .clk(clk_p), // 126MHz/5 clk // wish bone .spi_mem_addr(spi_sd_addr[11:0]), .spi_mem_wdata(spi_sd_wdata), .spi_mem_rdata(spi_sd_rdata), .spi_mem_wstrb(spi_sd_wstrb), .spi_mem_valid(spi_sd_valid), .spi_mem_ready(spi_sd_ready), // SPI master i/f .o_SPI_Clk(o_SPI_Clk), // .i_SPI_MISO(i_SPI_MISO), .i_SPI_MISO(i_SPI_MISO), .o_SPI_MOSI(o_SPI_MOSI), .o_SPI_CS_n(o_SPI_CS_n) ); endmodule
合成、配置配線
GOWIN FPGA Designerを立ち上げて、wikiからダウンロードしたexampleのprojectフォルダからpicotiny.gprjを選択します。
また、hwフォルダには作成したSPI回路及び修正したファイルpicotiny.v,picoperipheral.vを忘れずにコピー更新して下さい。
そうしてdesignタブでverilog files→Add filesで追加したファイル名(SPI関連)を指定して、
processタブで合成します。
その後、FloorPlannerをダブルクリックして、I/O ConstraintsタブでSPI端子とボードの端子を紐づけます。
tangnano9kのボード設計図を見ます。
Sipeedのwikiページにあるtang nano 9k→9. Hardware files→Schematic→Tang_Nano_9k_3672_Schematic.pdf を見ます。
https://dl.sipeed.com/shareURL/TANG/Nano%209K/2_Schematic
TFカードフォルダと結線されている端子名を確認し、picotiny.vのSPIの端子名とボードの端子を紐づけます。
IO Constraintsでの端子との紐づけ。Pull upありにしましたが不要かもしれないです。
IOの設定が終わったら上書き保存後、Place &Routeをダブルクリックし配置配線を行います。
終了したらProgram Deviceをダブルクリックし、programmerを立ち上げてtang nanoへbit streamを書き込みます。
この時点ではSPIを動かすfirmwareをflashへ書き込んでいないので、boot後、製品購入時に書きこんであった内容のプログラムが動きます。
このプログラムは、exampleのpicotinyシステムです。書き込んだ記憶がないので、あらかじめexampleの内容で動かせるように書き込まれて出荷されているのだろうか?
当初、このexampleを動かした時、内容がよく分かってなかったので、bitstream書き込み時に、一緒に書き込まれたプログラムだと勘違いしてしまいました。
フラッシュに書き込むファームはexampleに付属のpythonプログラムを使って書き込みます。次のソフトウェア編で記載します。
参考にさせていただいたサイト
PicoRV on nano 9K - Sipeed Wiki
TangNano-9K-example/picotiny at main · sipeed/TangNano-9K-example · GitHub
ラズパイPicoでFatFsを使う - Qiita
GitHub - nandland/spi-master: SPI Master for FPGA - VHDL and Verilog
Copyright (c) 2019 russell-merrick
MMC/SDCの使いかた
Release xPack GNU RISC-V Embedded GCC v8.2.0-3.1 · xpack-dev-tools/riscv-none-embed-gcc-xpack · GitHub
UG286-1.9.5J_Gowin Clockユーザーガイド.pdf
Tang_Nano_9K_3672_schematic.pdf
DS117-2.9.3E_GW1NR series of FPGA Products DataSheet.pdf
下調べとハード設計編は以上になります。
次はソフト設計編に続きます。
spend-carefree.hatenablog.com