tang-nano-9kで動くpicotinyのfirmware.cを読む

# リポジトリ

https://github.com/sipeed/TangNano-9K-example

# 説明
- 使用されるライブラリ
- 使用される構造体
- 定義された定数
- 定義された関数
- mainの動き

## 使用されるライブラリ
- stdint.h
    - uint8_t, uint32_t型がよく使用されます
    - uint8_tはbufferやdata[256]のように使用されます
    - uint32_tはcyclesの管理に使用されます
- stdbool.h
    - bool型
    - uint32_t cmd_benchmark(bool verbose, uint32_t *insts_p)でのみ使用される
   
## 使用される構造体
```c
typedef struct
{
    volatile uint32_t DATA;
    volatile uint32_t CLKDIV;
} PICOUART;
```
- DATA: UARTデータレジスタ, 送受信する文字がいる

> （同期式のシリアル通信には対応していない）非同期式のシリアル信号とパラレル信号の変換をする部品
> https://wa3.i-3-i.info/word12982.html

- CLKDIV: UARTclock分周レジスタ, UART通信のボーレートがいる

> UARTにはクロック信号が無いため、通信をする前にあらかじめ送受信するデバイス間で、通信速度(ボーレート)を決めておきます
> https://www.bing.com/search?q=UART通信のボーレート&qs=n&form=QBRE&sp=-1&lq=0&pq=clock%E3%80%80分周%E3%80%80とは&sc=1-11&sk=&cvid=D38FAF15F082482EA1A7D244E98648D9&ghsh=0&ghacc=0&ghpl=


```c
typedef struct
{
    volatile uint32_t OUT;
    volatile uint32_t IN;
    volatile uint32_t OE;
} PICOGPIO;
```
- OUT: GPIOピンの出力値を設定
- IN: GPIOピンの入力値を取得
- OE: GPIOピンの出力機能の有効/無効を設定

> GPIOは"General-purpose input/output"の略で、「汎用入出力」を意味する。
> https://ja.wikipedia.org/wiki/GPIO

```c
typedef struct
{
    union
    {
        volatile uint32_t REG;
        volatile uint16_t IOW;
        struct
        {
            volatile uint8_t IO;
            volatile uint8_t OE;
            volatile uint8_t CFG;
            volatile uint8_t EN;
        };
    };
} PICOQSPI;
```
- REG: 32ビットのワードに対応する変数
- IOW: 16ビットのワードに対応する変数
- struct: ワードを4つの8ビットのフィールド（IO、OE、CFG、EN）に分解するビットフィールド
    - IO: QSPIのIO機能を設定する
    - OE: QSPIのOE(Output Enable)機能を設定する
    - CFG: QSPIのCFG(Configuration)機能を設定する
    - EN: QSPIのEN(Enable)機能を設定する
- この構造体は上記3つのいずれかで用いられる   

> Quad SPI（クアッド・エスピーアイ）は、通信機能の1つです。Quad SPI フラッシュメモリなどのシリアルメモリにアクセスする機能です。ユーザーは、外部のシリアルメモリとマイコンのインタフェースの設計が容易になり、ピン数を節約することができます。
> https://edn.itmedia.co.jp/edn/articles/1511/18/news018.html

>共用体を使うことで、32ビットのレジスタを異なるビットフィールドに分解して使ったり、16ビットの特定の範囲にアクセスしたりすることが可能になります。
> 共用体のメンバーは、全て同じアドレスを共有しているため、共用体のサイズはそのメンバーの中で最も大きいものに合わせられます。つまり、共用体が占めるメモリ領域は、その中で最も大きなメンバーのサイズに一致します。他のメンバーがより小さいサイズの場合は、余白が発生します。

## 定義された定数
- QSPI0 ((PICOQSPI *)0x81000000): 0x81000000に対応するアドレスに存在するQSPIコントローラーへのポインタ
- GPIO0 ((PICOGPIO *)0x82000000): 0x82000000に対応するアドレスに存在するGPIOコントローラーへのポインタ
- UART0 ((PICOUART *)0x83000000): 0x83000000に対応するアドレスに存在するUARTコントローラーへのポインタ
- FLASHIO_ENTRY_ADDR ((void *)0x80000054): 0x80000054に対応するアドレスに存在する関数へのポインタ/ このポインタを使用してそのアドレスにある関数を呼び出すことができる？
```c
#define FLASHIO_ENTRY_ADDR ((void *)0x80000054)
void (*spi_flashio)(uint8_t *pdata, int length, int wren) = FLASHIO_ENTRY_ADDR;
```
- これらを用いて対応するハードウェア機能を使用する。おそらくボードの設計でこの値が決められているからほかのボードだとうまく動かない。
## 定義された関数
- putchar(int c)
    - 引数として渡された文字をUART経由で出力
    - 各print関数またはbenchmark関数からアクセスされる
    - もし文字が改行文字（'\n'）なら、それを改行文字の前に必要なキャリッジリターン（'\r'）として出力
- print(const char *p)
    - 引数として渡された文字列をputchar()関数を使用してUART経由で出力
- print_hex(uint32_t v, int digits)
    - 32ビット整数を16進数表記として出力
    - digits引数に指定された桁数を持つように、先頭のゼロを省略して出力
- print_dec(uint32_t v)
    - 32ビット整数を10進数表記として出力
    - 10以上の整数を一桁ずつ出力する(10以上の整数を一桁ずつ出力する的な)
- char getchar_prompt(char *prompt)
    - 引数として渡された文字列をUART経由で出力
    - ユーザーからのコンソール入力を受け取り、文字を返す
    - ユーザーが入力するまで、コンソールにプロンプトが表示されたまま待機する
- char getchar()
    - getchar_prompt(NULL)を呼び出すことで、プロンプトなしでコンソールから文字を取得
    - getchar_prompt()はここから唯一呼ばれる
- cmd_set_crm(int on)
    - 引数として渡された値に応じて、QSPIコントローラーのCRM(Command-Read-Mode)をON/OFFに切り替え
- int cmd_get_crm()
    - QSPIコントローラーのCRM(command-Read-Mode)がONかどうかを取得
- cmd_set_dspi(int on)
    - 引数として渡された値に応じて、QSPIコントローラーのDSPI(Dual-SPI)をON/OFFに切り替え
- int cmd_get_dspi()
    - QSPIコントローラーのDSPI(Dual-SPI)がONかどうかを取得
- void cmd_read_flash_id()
    - QSPIコントローラーを使用して、接続されたSPIフラッシュデバイスのIDを取得
    - 読み取ったIDは16進数表記で出力
- uint32_t cmd_benchmark(bool verbose, uint32_t *instns_p)
    - ハードウェアのベンチマークを実行
    - verbose引数がtrueの場合、結果を詳細に表示
    - instns_p引数が非NULLの場合、命令数を格納するポインタを渡し、ベンチマークの結果として命令数を返す
- void cmd_benchmark_all()
    - 異なるモードでcmd_benchmark()関数を実行して、性能を比較
- void main()
    - ユーザーがコンソールからの入力を行うための繰り返しループ
    - コンソールにプロンプトを表示し、ユーザーが入力したコマンドに応じて適切な関数を呼び出し
- void irqCallback()  
    - プログラム中では割り込み処理は実装されていないため、この関数の本体は空
  
## mainの動き
1. UARTの初期化: シリアル通信を行うUART（ユニバーサル・アシンクロナス・リシーバー・トランスミッター）のクロック分周を設定して初期化する。
1. GPIOの初期化: OEとOUTの全bitに1をたてる
1. QSPIコントローラーの設定: QSPIコントローラーを起動
1. タイトルの表示: こういうロゴって手作りなのかな
1. LED操作: GPIOピンを使用して、一連のLEDの点灯と消灯を繰り返し
1. ユーザーとの対話的なコンソール操作: while (1)ループを通じて、ユーザーと対話的なコンソール操作を行う

## 各所各所にいる初見で分からなかった処理
- main関数の頭で実行される分周の計算でなぜ-2しているのか
```c
UART0->CLKDIV = CLK_FREQ / UART_BAUD - 2;
```
今もわからないけど恐らく仕様
- ```__asm__```って何？
    - インラインアセンブリ(アセンブリ言語のコード)をC言語のコード内に直接埋め込むための指示子
- ```volatile```って何？
    - 変数の最適化を抑制する修飾子
> 通常、Cコンパイラはコードを最適化する際に変数の読み書きの順序を変更したり、不要な読み込みを省略したりする場合があります。
しかし、ハードウェアレジスタや外部デバイスなど、ハードウェアと直接やり取りする変数は、最適化を抑制する必要があります。そうしないと、予期せぬ動作やエラーが発生する可能性があります。
volatileキーワードを変数宣言につけることで、その変数への読み書きが最適化されず、常にメモリ上の実際の値を参照するようになります。

# おわりに
簡単なプログラムはこれで実行できるようになりました。プログラムで経過した命令数やサイクル数をとれるので面白かったです。


<details><summary>firmware.c</summary>
<div>

```c
#include <stdint.h>
#include <stdbool.h>

// a pointer to this is a null pointer, but the compiler does not
// know that because "sram" is a linker symbol from sections.lds.
extern uint32_t sram;

typedef struct {
    volatile uint32_t DATA;
    volatile uint32_t CLKDIV;
} PICOUART;

typedef struct {
    volatile uint32_t OUT;
    volatile uint32_t IN;
    volatile uint32_t OE;
} PICOGPIO;

typedef struct {
    union {
        volatile uint32_t REG;
        volatile uint16_t IOW;
        struct {
            volatile uint8_t IO;
            volatile uint8_t OE;
            volatile uint8_t CFG;
            volatile uint8_t EN; 
        };
    };
} PICOQSPI;

#define QSPI0 ((PICOQSPI*)0x81000000)
#define GPIO0 ((PICOGPIO*)0x82000000)
#define UART0 ((PICOUART*)0x83000000)

#define FLASHIO_ENTRY_ADDR ((void *)0x80000054)

void (*spi_flashio)(uint8_t *pdata, int length, int wren) = FLASHIO_ENTRY_ADDR;

int putchar(int c)
{	
    if (c == '\n')
        UART0->DATA = '\r';
    UART0->DATA = c;
    
    return c;
}

void print(const char *p)
{
    while (*p)
        putchar(*(p++));
}

void print_hex(uint32_t v, int digits)
{
    for (int i = 7; i >= 0; i--) {
        char c = "0123456789abcdef"[(v >> (4*i)) & 15];
        if (c == '0' && i >= digits) continue;
        putchar(c);
        digits = i;
    }
}

void print_dec(uint32_t v)
{
    if (v >= 100) {
        print(">=100");
        return;
    }

    if      (v >= 90) { putchar('9'); v -= 90; }
    else if (v >= 80) { putchar('8'); v -= 80; }
    else if (v >= 70) { putchar('7'); v -= 70; }
    else if (v >= 60) { putchar('6'); v -= 60; }
    else if (v >= 50) { putchar('5'); v -= 50; }
    else if (v >= 40) { putchar('4'); v -= 40; }
    else if (v >= 30) { putchar('3'); v -= 30; }
    else if (v >= 20) { putchar('2'); v -= 20; }
    else if (v >= 10) { putchar('1'); v -= 10; }

    if      (v >= 9) { putchar('9'); v -= 9; }
    else if (v >= 8) { putchar('8'); v -= 8; }
    else if (v >= 7) { putchar('7'); v -= 7; }
    else if (v >= 6) { putchar('6'); v -= 6; }
    else if (v >= 5) { putchar('5'); v -= 5; }
    else if (v >= 4) { putchar('4'); v -= 4; }
    else if (v >= 3) { putchar('3'); v -= 3; }
    else if (v >= 2) { putchar('2'); v -= 2; }
    else if (v >= 1) { putchar('1'); v -= 1; }
    else putchar('0');
}

char getchar_prompt(char *prompt)
{
    int32_t c = -1;

    uint32_t cycles_begin, cycles_now, cycles;
    __asm__ volatile ("rdcycle %0" : "=r"(cycles_begin));

    if (prompt)
        print(prompt);

    // if (prompt)
        // GPIO0->OUT = ~0;
        // reg_leds = ~0;

    while (c == -1) {
        __asm__ volatile ("rdcycle %0" : "=r"(cycles_now));
        cycles = cycles_now - cycles_begin;
        if (cycles > 12000000) {
            if (prompt)
                print(prompt);
            cycles_begin = cycles_now;
            // if (prompt)
                // GPIO0->OUT = ~GPIO0->OUT;
                // reg_leds = ~reg_leds;
        }
        c = UART0->DATA;
    }
    // if (prompt)
        // GPIO0->OUT = 0;
        // reg_leds = 0;
    return c;
}

char getchar()
{
    return getchar_prompt(0);
}

#define QSPI_REG_CRM  0x00100000
#define QSPI_REG_DSPI 0x00400000

void cmd_set_crm(int on)
{
    if (on) {
        QSPI0->REG |= QSPI_REG_CRM;
    } else {
        QSPI0->REG &= ~QSPI_REG_CRM;
    }
}

int cmd_get_crm() {
    return QSPI0->REG & QSPI_REG_CRM;
}

void cmd_set_dspi(int on)
{
    if (on) {
        QSPI0->REG |= QSPI_REG_DSPI;
    } else {
        QSPI0->REG &= ~QSPI_REG_DSPI;
    }
}

int cmd_get_dspi() {
    return QSPI0->REG & QSPI_REG_DSPI;
}

void cmd_read_flash_id()
{
    int pre_dspi = cmd_get_dspi();

    cmd_set_dspi(0);
    
    uint8_t buffer[4] = { 0x9F, /* zeros */ };
    spi_flashio(buffer, 4, 0);

    for (int i = 1; i <= 3; i++) {
        putchar(' ');
        print_hex(buffer[i], 2);
    }
    putchar('\n');

    cmd_set_dspi(pre_dspi);
}

// --------------------------------------------------------

uint32_t cmd_benchmark(bool verbose, uint32_t *instns_p)
{
    uint8_t data[256];
    uint32_t *words = (void*)data;

    uint32_t x32 = 314159265;

    uint32_t cycles_begin, cycles_end;
    uint32_t instns_begin, instns_end;
    __asm__ volatile ("rdcycle %0" : "=r"(cycles_begin));
    __asm__ volatile ("rdinstret %0" : "=r"(instns_begin));

    for (int i = 0; i < 20; i++)
    {
        for (int k = 0; k < 256; k++)
        {
            x32 ^= x32 << 13;
            x32 ^= x32 >> 17;
            x32 ^= x32 << 5;
            data[k] = x32;
        }

        for (int k = 0, p = 0; k < 256; k++)
        {
            if (data[k])
                data[p++] = k;
        }

        for (int k = 0, p = 0; k < 64; k++)
        {
            x32 = x32 ^ words[k];
        }
    }

    __asm__ volatile ("rdcycle %0" : "=r"(cycles_end));
    __asm__ volatile ("rdinstret %0" : "=r"(instns_end));

    if (verbose)
    {
        print("Cycles: 0x");
        print_hex(cycles_end - cycles_begin, 8);
        putchar('\n');

        print("Instns: 0x");
        print_hex(instns_end - instns_begin, 8);
        putchar('\n');

        print("Chksum: 0x");
        print_hex(x32, 8);
        putchar('\n');
    }

    if (instns_p)
        *instns_p = instns_end - instns_begin;

    return cycles_end - cycles_begin;
}

void cmd_benchmark_all()
{
    uint32_t instns = 0;

    print("default        ");

    cmd_set_dspi(0);
    cmd_set_crm(0);

    print(": ");
    print_hex(cmd_benchmark(false, &instns), 8);
    putchar('\n');

    print("dspi-");
    print_dec(0);
    print("         ");

    cmd_set_dspi(1);

    print(": ");
    print_hex(cmd_benchmark(false, &instns), 8);
    putchar('\n');

    print("dspi-crm-");
    print_dec(0);
    print("     ");

    cmd_set_crm(1);

    print(": ");
    print_hex(cmd_benchmark(false, &instns), 8);
    putchar('\n');

    print("instns         : ");
    print_hex(instns, 8);
    putchar('\n');
}

volatile int i;
// --------------------------------------------------------

#define CLK_FREQ        25175000
#define UART_BAUD       115200

void main()
{
    UART0->CLKDIV = CLK_FREQ / UART_BAUD - 2;

    GPIO0->OE = 0x3F;
    GPIO0->OUT = 0x3F;

    cmd_set_crm(1);
    cmd_set_dspi(1);

    print("\n");
    print("  ____  _          ____         ____\n");
    print(" |  _ \\(_) ___ ___/ ___|  ___  / ___|\n");
    print(" | |_) | |/ __/ _ \\___ \\ / _ \\| |\n");
    print(" |  __/| | (_| (_) |__) | (_) | |___\n");
    print(" |_|   |_|\\___\\___/____/ \\___/ \\____|\n");
    print("\n");
    print("        On Lichee Tang Nano-9K\n");
    print("\n");

    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x01;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x02;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x04;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x08;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x10;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x20;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x00;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F;
    for ( i = 0 ; i < 10000; i++);

    while (1)
    {
        print("\n");
        print("Select an action:\n");
        print("\n");
        print("   [1] Toggle led 1\n");
        print("   [2] Toggle led 2\n");
        print("   [3] Toggle led 3\n");
        print("   [4] Toggle led 4\n");
        print("   [5] Toggle led 5\n");
        print("   [6] Toggle led 6\n");
        print("   [F] Get flash mode\n");
        print("   [I] Read SPI flash ID\n");
        print("   [S] Set Single SPI mode\n");
        print("   [D] Set DSPI mode\n");
        print("   [C] Set DSPI+CRM mode\n");
        print("   [B] Run simplistic benchmark\n");
        print("   [A] Benchmark all configs\n");
        
        for (int rep = 10; rep > 0; rep--)
        {
            print("\n");
            print("IO State: ");
            print_hex(GPIO0->IN, 8);
            print("\n");

            print("\n");

            print("Command> ");
            char cmd = getchar();
            if (cmd > 32 && cmd < 127)
                putchar(cmd);
            print("\n");

            switch (cmd)
            {
            case 'F':
            case 'f':
                print("\n");
                print("SPI State:\n");
                print("  DSPI ");
                if ( cmd_get_dspi() )
                    print("ON\n");
                else
                    print("OFF\n");
                print("  CRM  ");
                if ( cmd_get_crm() )
                    print("ON\n");
                else
                    print("OFF\n");

                break;

            case 'I':
            case 'i':
                cmd_read_flash_id();
                break;

            case 'S':
            case 's':
                cmd_set_dspi(0);
                cmd_set_crm(0);
                break;

            case 'D':
            case 'd':
                cmd_set_crm(0);
                cmd_set_dspi(1);
                break;

            case 'C':
            case 'c':
                cmd_set_crm(1);
                cmd_set_dspi(1);
                break;

            case 'B':
            case 'b':
                cmd_benchmark(1, 0);
                break;

            case 'A':
            case 'a':
                cmd_benchmark_all();
                break;

            case '1':
                GPIO0->OUT ^= 0x00000001;
                break;

            case '2':
                GPIO0->OUT ^= 0x00000002;
                break;

            case '3':
                GPIO0->OUT ^= 0x00000004;
                break;

            case '4':
                GPIO0->OUT ^= 0x00000008;
                break;

            case '5':
                GPIO0->OUT ^= 0x00000010;
                break;

            case '6':
                GPIO0->OUT ^= 0x00000020;
                break;

            default:
                continue;
            }
        }
    }
}

void irqCallback() {

}

```　
</div></details>

gowin ideでfsファイルを書き込む方法もあれば、こうやってmakeして作ったvファイルをpythonで書き込む方法もあるんですね