Tang Nano 9KにマトリクスLED

　GowinのFPGAが載ったTang Nano 9K（Sipeed）でマトリクスLEDを使ってみる．
雑誌（Interface誌2022年12月号）の記事とSystemVerilogコードを参考に，ユニバーサル基板にコンパクトに実装してみた．

## 材料
![1667152801860.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/0d90893d-c209-cbe0-f9c0-89e30cf32b71.jpeg)

- Aliexpressで買った安いユニバーサル基板
- ダブルのピンソケット
- ロングのピンソケット
- マトリクスLED OSL641505-BB
- ロータリ・エンコーダ
- Tang Nano 9K

## はんだ付け
　ユニバーサル基板にはダブルのピンソケットを実装した．
　それぞれ隣のピンを短絡してあるので，Tang Nano 9Kを載せた状態で，全ピンをとなり（外側）のピンソケットから引き出せる．
![1667153279085.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/d5f70327-0afe-cef6-9d69-66fca68b5f75.jpeg)
![1667153279095.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/ca62fd8c-26d7-257f-c3ce-ccef94756fc9.jpeg)
左下はロータリ・エンコーダの配線

## マトリクスLEDは2階建て
　マトリクスLEDは，ロングのピンソケットを下駄にしてTang Nano 9Kの上部に配置した．

![1667152801846.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/4d09551d-d83e-0c62-6e61-405091eadcb8.jpeg)

![1667152801854.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/ee57f82e-89f5-bcd4-c9a0-6e8794265795.jpeg)
ロータリ・エンコーダも そのうち何かに使おう…


![cad.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/91aa6c95-92fc-1a14-40f9-5d75fca9383b.png)
マトリクスLEDに加えて，ステッピング・モータ駆動回路も追加した


## 物理制約ファイル

```top.cst
IO_LOC "anode[0]" 35;
IO_PORT "anode[0]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "anode[1]" 72;
IO_PORT "anode[1]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "anode[2]" 73;
IO_PORT "anode[2]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "anode[3]" 33;
IO_PORT "anode[3]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "anode[4]" 75;
IO_PORT "anode[4]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "anode[5]" 34;
IO_PORT "anode[5]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "anode[6]" 42;
IO_PORT "anode[6]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "anode[7]" 51;
IO_PORT "anode[7]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;

IO_LOC "cathode[0]" 30;
IO_PORT "cathode[0]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "cathode[1]" 41;
IO_PORT "cathode[1]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "cathode[2]" 77;
IO_PORT "cathode[2]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "cathode[3]" 40;
IO_PORT "cathode[3]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "cathode[4]" 70;
IO_PORT "cathode[4]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "cathode[5]" 76;
IO_PORT "cathode[5]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "cathode[6]" 71;
IO_PORT "cathode[6]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;
IO_LOC "cathode[7]" 74;
IO_PORT "cathode[7]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16;

IO_LOC "clk" 52;
IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=UP;
```


## シェーダーも書いたよ
　ダイナミック・ドライブでLEDを駆動しているので，それに合わせて1行（8ドット）ごとに，シェーダーによって1行分の絵（点灯画素）を決めています．従って，バッファを持つ必要がありません．
　時間で，ひし形の大きさを変更しています．

![s1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/6633b9ae-0c20-c030-5fac-adfeda614866.png)
![s2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/aafdede0-f977-70e5-682c-b947d02f87b7.png)
![s3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/8afbadd4-709c-e356-3fd1-7e0b3cc84669.png)



```verilog
省略

  assign col = row_data;
  reg [2:0] diameter;

  function shader(input [4:0] x, input [4:0] y);
    if(x > 3) x = x - 3;
    else x = 3 - x;
    if(y > 3) y = y - 3;
    else y = 3 - y;
    if((x + y) >= (diameter + 'd1)) shader = 0;
    else shader = 1;
  endfunction

  // Increment row_cnt @ overflow.
  reg [7:0] i;
  reg [7:0] row_data;
  reg [8:0] next_pattern;

  always_ff @ (posedge clk) begin
    if (overflow) begin
      row_cnt <= row_cnt + 'd1;
      for(i=0; i<8; i++)begin
        row_data[i] = shader(i, row_cnt);
      end
      if(row_cnt == 'd7) begin
        next_pattern <= next_pattern + 'd1;
        if(next_pattern == 0) begin
          if(diameter == 5) begin
            diameter <= 0;
          end else begin
            diameter <= diameter + 'd1;
          end
        end
      end
    end
  end

省略
```

## ロータリ・エンコーダも使ったよ
 別冊付録のP15のコードをベースに，LEDマトリクスに ひし形を表示しました．ロータリ・エンコーダを回すと ひし形の大きさが変わります．
This is rotary encoder exercise.
If you rotate rotary encoder, size of diamond shape will be changed.

```verilog
module top (
  input wire clk,
  input wire sw1,
  input wire rotary_a,
  input wire rotary_b,
  input wire rotary_sw,
  output wire [7:0] anode,
  output wire [7:0] cathode
);

省略

  reg [3:0] diameter;
  wire debounced_rotary_a;
  wire debounced_rotary_b;
  logic overflow; // top モジュールへ移した

  matrix_led_driver inst_0 (
    .clk  (clk),
    .sw1  (sw1),
    .row  (row),
    .col  (col),
    .diameter (diameter),
    .overflow (overflow)
  );

//rotary encoderのチャタリング除去 ;debounce for rotary encoder
  sw_debounce deb_rotary_a(.clock_in(overflow), .sw_in(rotary_a), .sw_out(debounced_rotary_a));
  sw_debounce deb_rotary_b(.clock_in(overflow), .sw_in(rotary_b), .sw_out(debounced_rotary_b));

//rotaty encoder cw/ccw
  always @(negedge debounced_rotary_a)begin
    if(debounced_rotary_b)begin
      diameter <= diameter - 'd1;
    end else begin
      diameter <= diameter + 'd1;
    end
  end

// こちらのClockは topモジュールに移した
  timer #(
    .COUNT_MAX (3000)
  ) inst_1
省略
endmodule

module matrix_led_driver (
  input  wire       clk,
  input  wire sw1,
  output wire [7:0] row,
  output wire [7:0] col,
  input  wire [3:0] diameter,
  input  wire overflow
);

省略

  wire debounced_sw1;

  reg [1:0] dimming_counter;
  reg [1:0] light_level;
  reg light_pulse;
  wire dim_clk;

  assign row = ('b00000001 << row_cnt) * light_pulse;

// ボード上のSw1　チャタリング除去 ; debounce for SW1
  sw_debounce deb_sw1(.clock_in(clk), .sw_in(sw1), .sw_out(debounced_sw1));

// SW1押すとLEDの明るさ変更 ;when sw1 was pushed, tone of LED will be changed
  always @(posedge debounced_sw1) begin
    light_level <= light_level + 2'd1;
  end

  assign col = row_data;

  function shader(input [4:0] x, input [4:0] y);
    if(x > 3) x = x - 3;
    else x = 3 - x;
    if(y > 3) y = y - 3;
    else y = 3 - y;
    if((x + y) >= (diameter + 'd1)) shader = 0;
    else shader = 1;
  endfunction

  reg [3:0] i;
  reg [7:0] row_data;

  always @(posedge dim_clk)begin
    dimming_counter <= dimming_counter + 'd1;
    if (dimming_counter <= light_level) begin
      light_pulse <= 1'b1;
    end else begin
      light_pulse <= 1'b0;
    end
  end

  always_ff @ (posedge clk) begin
    if (overflow) begin
      row_cnt <= row_cnt + 'd1;

      for(i=0; i<8; i++)begin
        row_data[i] = shader(i, row_cnt);
      end
    end
  end

// LED4階調制御用のclock追加  ;add clock for controling LED tone.
  timer #(
    .COUNT_MAX (250)
  ) dim_clock (
    .clk      (clk),
    .overflow (dim_clk)
  );

省略

endmodule
```

## ライフゲームも
　基本は普通のライフゲームに準じています．
　独自ルールはライフを4段階にしたところです．
　ただし，周囲8マスが全て埋まった場合は，即死にしています．
　隣接3で誕生，4以上でライフ-1，2未満でライフ-1，8で即死です．
　フィールドの大きさは，ハードコートされていて16×8になっています．
　判定上，上と下，右と左はつながっています．

### カーネル

```verilog

  reg [1:0] [15:0] [7:0] [1:0] frame_buffer;  // 表・裏バッファ，　横16マス，　縦8マス，　ライフ4段階
  reg current_frame;

省略

  task life();
    reg [4:0] x;
    reg [3:0] y;

    for(x=0; x < 16; x++)begin
      for(y=0; y < 8; y++)begin
        automatic reg [3:0] around=0;
        if(frame_buffer[current_frame][x[3:0]-4'd1][y[2:0]-3'd1] !=0) around = around + 'd1;
        if(frame_buffer[current_frame][x[3:0]-4'd1][y]           !=0) around = around + 'd1;
        if(frame_buffer[current_frame][x[3:0]-4'd1][y[2:0]+3'd1] !=0) around = around + 'd1;
        if(frame_buffer[current_frame][x]          [y[2:0]-3'd1] !=0) around = around + 'd1;
        if(frame_buffer[current_frame][x]          [y[2:0]+3'd1] !=0) around = around + 'd1;
        if(frame_buffer[current_frame][x[3:0]+4'd1][y[2:0]-3'd1] !=0) around = around + 'd1;
        if(frame_buffer[current_frame][x[3:0]+4'd1][y]           !=0) around = around + 'd1;
        if(frame_buffer[current_frame][x[3:0]+4'd1][y[2:0]+3'd1] !=0) around = around + 'd1;

        if(frame_buffer[current_frame][x][y] != 0)begin
          if(around > 7)begin
            frame_buffer[current_frame + 1'd1][x][y] = 2'd0;
          end else if(around > 3 || around < 2)begin
            frame_buffer[current_frame + 1'd1][x][y] = frame_buffer[current_frame][x][y] - 2'd1;
          end else begin
            frame_buffer[current_frame + 1'd1][x][y] = frame_buffer[current_frame][x][y];
          end
        end else begin
          if(around == 3)begin
            frame_buffer[current_frame + 1'd1][x][y] = 2'd3;
          end else begin
            frame_buffer[current_frame + 1'd1][x][y] = 2'd0;
          end
        end
      end
    end
    current_frame = current_frame + 'd1;
  endtask
省略
```