Atlas-SoC hack 事始め

[前回](http://qiita.com/kwi/items/17cba964bedf4b02e5de) の続き。FPGA 初めてで ATLAS-SOC から始めたのですが、「FPGA マガジン 12号」の巻頭特集の次の記事がちょうど ATLAS-SOC の記事だったので、それをなぞることを練習問題に進めてみました。私の中では伝説の回となる「[FPGA マガジン 12号](http://www.kumikomi.net/fpga/contents/0012.php)」です。

手元の環境では、ツールチェインは 15.1 を使っています。

記事では「Makefile のステップを途中で止めて～」という手順を使っていましたが、最初右も左もわからず、うまく動作させられませんでした。どうにも再現性が良くなくて、私が他人にうまく説明できないので、最初から流せるように整備することを目標にしてみました。

リポジトリは GHRD を fork した https://github.com/hkwi/atlas-soc-ghrd.git です。

```
git clone https://github.com/hkwi/atlas-soc-ghrd.git
cd atlas-soc-ghrd
```


# LED 配線替え

最初の例題は LED の制御線を変更するものでした。

GHRD のファイルは入れ子構成になっていて、一番外側は Quartus のプロジェクトファイルで、`hdl_src/ghrd_top.v` が中身になっています。`ghrd_top.v` の中では `soc_system` クラスの `soc_inst` インスタンス等が生成されています。`soc_system` は QSYS で作成されています。

```
 +---Quartus---+
 | ghrd_top.v  |       +---QSYS-------+
 |  soc_systen ------->| soc_system.* |
 |  :          |       |  foo         |
 +-------------+       |  bar         |
                       |  :           |
                       +--------------+
```


最初の LED の配線を変えて実行するところは、`hdl_src/ghrd_top.v` の `fpga_led_pio` をどこに配線するかで変わります。HPS から期待している信号線は `fpga_led_internal` ですが、その接続を解除します。その代わりに fpga 上で実装したカウンタの出力を LED に繋ぎ変えます。

```
git checkout fpga_mag_12
make scrub_clean all
```

# 配列同士の積を求めるモジュール

次に紙面上では魔法のように追加されていた `simple_pio` モジュールですが、同等の手順を tcl で実行できるようにしました。`soc_system` に組み込むので、QSYS で扱えるようにしないといけません。`ip/simple_pio` ディレクトリの下に `simple_pio.v` と `simple_pio_hw.tcl` を配置すれば QSYS で認識されるようです。「[Quartusハンドブック](https://www.altera.co.jp/products/design-software/fpga-design/quartus-prime/support.html) Qsys コンポーネントの作成」を参照。

配線やピンの export は `scripts/qsys_default_components.tcl` に追記しています。それに伴って `soc_system` の pin I/O が変わるので、`ghrd_top.v` 側も変更します。

`onmemory_chip2_0_s2` への入力手順が最初ちんぷんかんぷんだったのですが、Avalon-MM の仕様？に沿っているということのようで、幾つか固定値は module instance 生成部分で指定して、それ以外を ghrd_top module 内の variable や network に結び付ければよいようです(*1)。`chipselect` が使われていて、これは `write` と `chipselect` が両方オンだと書き込まれるということだったので、`chipselect` は常時オンにしてあります。

```
git checkout fpga_mag_12_2
make scrub_clean all
```

紙面では詳しく書かれていませんが、Linux(HPS) から見たときには、`simple_pio` との通信は `lightweight HPS-to-FPGA bridge` で `0xff200000` が使われ、`onchip_memory2_0.s1` との通信は `HPS-to-FPGA bridge` で `0xc0000000` が使われることのようです。

つまり C のプログラムは次のようなものが使われているはずです。

あらかじめ `onchip_memory` に値を用意するプログラム `ocm_write` 。

```C:ocm_write.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char* argv[]){
 int fd = open("/dev/mem", O_RDWR|O_SYNC);
 if (fd < 0){
  printf("cannot open /dev/mem\n");
  return -1;
 }
 volatile unsigned int *ocm;
 unsigned int addr = 0xc0000000;
 unsigned int len = 3 * 4 * 1024;
 ocm = (unsigned int)mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, addr);
 if (gpio==MAP_FAILED){
  printf("cannot mmap\n");
  close(fd);
  return -1;
 }
 int d;
 for(d=0; d<1024; d++){
  ocm[d] = ocm[d+1024] = d;
 }
 munmap((void*)ocm, len);
 close(fd);
 return 0;
}
```

`onchip_memory` に保持されている値を表示するプログラム `ocm_read` 。

```C:ocm_read.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char* argv[]){
 int fd = open("/dev/mem", O_RDWR|O_SYNC);
 if (fd < 0){
  printf("cannot open /dev/mem\n");
  return -1;
 }
 volatile unsigned int *ocm;
 unsigned int addr = 0xc0000000;
 unsigned int len = 3 * 4 * 1024;
 ocm = (unsigned int)mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, addr);
 if (gpio==MAP_FAILED){
  printf("cannot mmap\n");
  close(fd);
  return -1;
 }
 int d;
 unsigned int base;
 printf("-- A\n");
 base = 0x000; printf("%08x:", base); for(d=0; d<8; d++){ printf(" %08x", ocm[d+base]); } printf("\n");
 base = 0x3f8; printf("%08x:", base); for(d=0; d<8; d++){ printf(" %08x", ocm[d+base]); } printf("\n");
 printf("-- B\n");
 base = 0x400; printf("%08x:", base); for(d=0; d<8; d++){ printf(" %08x", ocm[d+base]); } printf("\n");
 base = 0x7f8; printf("%08x:", base); for(d=0; d<8; d++){ printf(" %08x", ocm[d+base]); } printf("\n");
 printf("-- C (= A * B)\n");
 base = 0x800; printf("%08x:", base); for(d=0; d<8; d++){ printf(" %08x", ocm[d+base]); } printf("\n");
 base = 0xbf8; printf("%08x:", base); for(d=0; d<8; d++){ printf(" %08x", ocm[d+base]); } printf("\n");
 
 munmap((void*)ocm, len);
 close(fd);
 return 0;
}
```

fpga 上に実装された回路を起動させる入力信号プログラム `ctrl` 。

```C:ctrl.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char* argv[]){
 int fd = open("/dev/mem", O_RDWR|O_SYNC);
 if (fd < 0){
  printf("cannot open /dev/mem\n");
  return -1;
 }
 volatile unsigned char *gpio;
 unsigned int addr = 0xff200000;
 unsigned int len = 0x20000;
 gpio = (unsigned char*)mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, addr);
 if (gpio==MAP_FAILED){
  printf("cannot mmap\n");
  close(fd);
  return -1;
 }
 gpio[0] = (unsigned char)atoi(argv[1]);
 int d;
 while((d = gpio[0]) != 0){
  printf("status = %08x\n", d);
 }
 munmap((void*)gpio, len);
 close(fd);
 return 0;
}
```


# 補足

*)1 の補足。

仕様書に載っている通り、モジュールのインスタンス化での接続対象は、`reg` でも `wire` でも定数でも大丈夫だそうです。よくあるチュートリアルで記載のあるものは見つけられずに悩んでいました。

```
module ex2 ();

reg [7:0] vara;
wire [31:0] netb;

example_cls example_inst (
  .pina  (vara),
  .pinb  (netb),
  .pinc  (4'b1111)
)

endmodule
```