LoginSignup
1
1

More than 5 years have passed since last update.

Atlas-SoC hack 事始め

Last updated at Posted at 2017-06-20

前回 の続き。FPGA 初めてで ATLAS-SOC から始めたのですが、「FPGA マガジン 12号」の巻頭特集の次の記事がちょうど ATLAS-SOC の記事だったので、それをなぞることを練習問題に進めてみました。私の中では伝説の回となる「FPGA マガジン 12号」です。

手元の環境では、ツールチェインは 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.vfpga_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.vsimple_pio_hw.tcl を配置すれば QSYS で認識されるようです。「Quartusハンドブック 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 が使われていて、これは writechipselect が両方オンだと書き込まれるということだったので、chipselect は常時オンにしてあります。

git checkout fpga_mag_12_2
make scrub_clean all

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

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

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

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

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

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
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1