前回 の続き。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.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ハンドブック 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
。
# 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
。
# 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
。
# 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