かずの不定期便ブログ

備忘録代わりに書きます

FPGA Sipeed Tang Nano 9Kを使ってSDカードアクセス(その1。下調べとハード設計編)

リーズナブルFPGAシリーズのTang Nano 9Kを使ってTFカードスロットにアクセスしました(その1。下調べとハード設計編)

picotinyへSDカードアクセスメニューを追加した

SDカードへアクセスするための下準備

  1. SDカードへアクセスするためのドライバ(ソフト)
  2. ドライバを動かすCPU(ハード)
  3. TFカードスロット(SD)へアクセスするためのインターフェース(ハード)
  4. ドライバやドライバを動かすための上位層のアプリケーションをコンパイルするための環境(ソフト)

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のwikifirmwareは最近のバージョンのコンパイラでは動かす事が出来ませんでした(コンパイルは通るが動かない)。
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のバスタイミング

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の端子名とボードの端子を紐づけます。

TFカード周り

IO Constraintsでの端子との紐づけ。Pull upありにしましたが不要かもしれないです。

IO constraints

IOの設定が終わったら上書き保存後、Place &Routeをダブルクリックし配置配線を行います。

終了したらProgram Deviceをダブルクリックし、programmerを立ち上げてtang nanoへbit streamを書き込みます。
この時点ではSPIを動かすfirmwareflashへ書き込んでいないので、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


過去記事
spend-carefree.hatenablog.com