RISC-VをKV260に組み込む
RISC-Vは、オープンソースのCPUです。
次世代CPUの有力候補で、カスタマイズできるのが大きな特徴になっています。
第6回AIエッジコンテストでの課題で、RISCVを搭載することになりました。
ただ、CPUをFPGAに搭載するのにはハードルも高いです。
今回は、オープンソースのVexRISCVを使って、FPGAボード、KV260に搭載する方法をご紹介します。
FPGAにおけるRISCVの位置づけ。
RISCVはオープンソースのCPUです。命令セットが定義されていて、それに対してのCPUの実装は自由になっています。実質的にフリーライセンスなので、そのため多くの発表例があります。
ハードウェアの実装はIC化すると言うのも一つの例ですが、FPGAに実装している例も多いです。
今回のはRISCVを使えば、どの実装でもいいのですが、評価の高かったVexRISCVを使用して、実装します。
実装の方法
KV260に向けてRISCVの実装をご紹介します。VexRISCVを使った実装です。
- VexRISCVのGithubからデータを取り込みます。
- VexRISCVの開発環境を整える。
- パラメータを設定、コンパイル
- Vitis向けにヘッダー設計
- Vitis アクセラレータに組み込む
- RISC-Vのソフトウェアコンパイル
- Vitisのアプリケーションを組み込む
- KV260で実行
今回、ソフトウェアの実機動作を含めて、全部紹介したかったのですが、実装を進める途中のシミュレーションで未解決の問題見つかりまして見つかりまして、一部はご紹介のみになります。
6番までは出来ています。
また、改めて、ブログの方を更新させていただきます。
事前準備
予め必要なものがあります。
KV260用のVitis プラットフォーム。
RISC-Vを組み込むために必要になります。
ブログ記事を参照にして、作成することが出来ます。
KV260 SDカード
実機動作できる、SDカードイメージが必要です。今回はPetalinux2022.1のバージョンを使っています。
# RISC-V の 組み込みの仕方
VexRISCV は次のページに用意されています。VexRISCVはSpinalHDLで作成されています。このまま、KV260の開発環境に持ってくることは出来ません。HDL(Verilog)に変更する必要があります。
基本的には、ここに書いてあるページのとおりに実行すれば、RISCVのVerilogソースが出来上がります。
KV260に組み込むには、インターフェースの都合から、さらに追加のソースコードが必要になります。
VexRiscvをダウンロードする。
1.1 Ubutu のターミナルを開けて、ワークスペースに移動します。今回は AIEDGEにしました。
cd ~/AIEDGE
1.2 Gitコマンドを使って、VexRISCVのソースコードをダウンロードします。
git clone --recursive https://github.com/SpinalHDL/VexRiscv.git
1.3 ダウンロードしたファイルを確認します。
ll VexRiscv
VexRISCVの開発環境を整える。
JAVA 8を使用しています。他のバージョンのJAVAを使っていても、切り替えが出来ます。またはコンパイラーまでの紹介になります。
2.1 下記をコマンドラインで順番に実行します。JAVAとspinalHDLの開発環境をインストールします。
# JAVA JDK 8
sudo add-apt-repository -y ppa:openjdk-r/ppa
sudo apt-get update
sudo apt install openjdk-8-jdk -y
sudo update-alternatives --config java
sudo update-alternatives --config javac
# Install SBT - https://www.scala-sbt.org/
echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" | sudo tee /etc/apt/sources.list.d/sbt.list
echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | sudo tee /etc/apt/sources.list.d/sbt_old.list
curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add
sudo apt update
sudo apt install sbt
パラメータを設定、コンパイル
ソースファイルはSpinalHDLで書かれています。ただし、XILINXのVitisでは、SpinalHDLは使えません。Verilogか、VHDLに変更する必要があります。幸い、コンパイラーがVerilogを出力しますので、それをもとにして、使用します。ただし、パラメータはSpinalHDL側で変更したほうが望ましいです。
3.1 ソースファイルは、VexRiscv/src/main/scala/vexriscv/demo の中にあります。
今回は、GenFull.scala を使って、実行します。
3.2 必要に応じてパラメータの設定を行います。今回はリセットベクタを0番地にしたいので、Geditなどで、で、GenFull.scaleを開き、14行目以降に、ResetVectorを挿入して実行しました。
なお、詳しいパラメータの説明は次のところに、書いてあります。
new IBusCachedPlugin(
resetVector = 0x00000000l,
prediction = DYNAMIC,
3.3 コンパイルをします。今回はGenFullで行います。各種パラメータは出来ますので、好みに合わせて、好きなタイプのコンパイルを行います。VexRiscv.v が出来上がります。
sbt "runMain vexriscv.demo.GenFull"
Vitis向けにヘッダー設計
出来上がった、Verlogファイルがそのまま使用できればいいのですが、KV260に合わせるためには、インターフェースを追加する必要がああります。
4.1 ファイルを作成します。ソースファイルは折りたたんだ先にありますので、開いてからコピーしてください。ファイル名は、riscv_example.svで保存します。
Verilogソースを作成します。
// This is a generated file. Use and modify at your own risk.
////////////////////////////////////////////////////////////////////////////////
// default_nettype of none prevents implicit wire declaration.
`default_nettype none
module riscv_example #(
parameter integer C_M00_AXI_ADDR_WIDTH = 64,
parameter integer C_M00_AXI_DATA_WIDTH = 32,
parameter integer C_M01_AXI_ADDR_WIDTH = 64,
parameter integer C_M01_AXI_DATA_WIDTH = 32
)
(
// System Signals
input wire ap_clk ,
input wire ap_rst_n ,
// AXI4 master interface m00_axi
output wire m00_axi_awvalid,
input wire m00_axi_awready,
output wire [C_M00_AXI_ADDR_WIDTH-1:0] m00_axi_awaddr ,
output wire [8-1:0] m00_axi_awlen ,
output wire m00_axi_wvalid ,
input wire m00_axi_wready ,
output wire [C_M00_AXI_DATA_WIDTH-1:0] m00_axi_wdata ,
output wire [C_M00_AXI_DATA_WIDTH/8-1:0] m00_axi_wstrb ,
output wire m00_axi_wlast ,
input wire m00_axi_bvalid ,
output wire m00_axi_bready ,
output wire m00_axi_arvalid,
input wire m00_axi_arready,
output wire [C_M00_AXI_ADDR_WIDTH-1:0] m00_axi_araddr ,
output wire [8-1:0] m00_axi_arlen ,
input wire m00_axi_rvalid ,
output wire m00_axi_rready ,
input wire [C_M00_AXI_DATA_WIDTH-1:0] m00_axi_rdata ,
input wire m00_axi_rlast ,
// AXI4 master interface m01_axi
output wire m01_axi_awvalid,
input wire m01_axi_awready,
output wire [C_M01_AXI_ADDR_WIDTH-1:0] m01_axi_awaddr ,
output wire [8-1:0] m01_axi_awlen ,
output wire m01_axi_wvalid ,
input wire m01_axi_wready ,
output wire [C_M01_AXI_DATA_WIDTH-1:0] m01_axi_wdata ,
output wire [C_M01_AXI_DATA_WIDTH/8-1:0] m01_axi_wstrb ,
output wire m01_axi_wlast ,
input wire m01_axi_bvalid ,
output wire m01_axi_bready ,
output wire m01_axi_arvalid,
input wire m01_axi_arready,
output wire [C_M01_AXI_ADDR_WIDTH-1:0] m01_axi_araddr ,
output wire [8-1:0] m01_axi_arlen ,
input wire m01_axi_rvalid ,
output wire m01_axi_rready ,
input wire [C_M01_AXI_DATA_WIDTH-1:0] m01_axi_rdata ,
input wire m01_axi_rlast ,
// Control Signals
input wire ap_start ,
output wire ap_idle ,
output wire ap_done ,
output wire ap_ready ,
input wire [32-1:0] reset_riscv ,
input wire [32-1:0] interrupt_riscv,
input wire [32-1:0] ABS_ADDRESS ,
input wire [32-1:0] SAMPLE ,
input wire [64-1:0] dBus ,
input wire [64-1:0] iBus
);
timeunit 1ps;
timeprecision 1ps;
///////////////////////////////////////////////////////////////////////////////
// Local Parameters
///////////////////////////////////////////////////////////////////////////////
// Large enough for interesting traffic.
localparam integer LP_DEFAULT_LENGTH_IN_BYTES = 16384;
localparam integer LP_NUM_EXAMPLES = 2;
///////////////////////////////////////////////////////////////////////////////
// Wires and Variables
///////////////////////////////////////////////////////////////////////////////
(* KEEP = "yes" *)
logic areset = 1'b0;
logic ap_start_r = 1'b0;
logic ap_idle_r = 1'b1;
logic ap_start_pulse ;
logic [LP_NUM_EXAMPLES-1:0] ap_done_i ;
logic [LP_NUM_EXAMPLES-1:0] ap_done_r = {LP_NUM_EXAMPLES{1'b0}};
logic [32-1:0] ctrl_xfer_size_in_bytes = LP_DEFAULT_LENGTH_IN_BYTES;
logic [32-1:0] ctrl_constant = 32'd1;
// RISC V address
logic dBus_cmd_valid;
logic dBus_cmd_ready;
logic dBus_cmd_payload_wr;
logic dBus_cmd_payload_uncached;
logic [31:0] dBus_cmd_payload_address;
logic [31:0] dBus_cmd_payload_data;
logic [3:0] dBus_cmd_payload_mask;
logic [2:0] dBus_cmd_payload_size;
logic dBus_cmd_payload_last;
logic dBus_rsp_valid;
logic dBus_rsp_payload_last;
logic [31:0] dBus_rsp_payload_data;
logic dBus_rsp_payload_error;
logic timerInterrupt;
logic externalInterrupt;
logic softwareInterrupt;
logic debug_bus_cmd_valid;
logic debug_bus_cmd_ready;
logic debug_bus_cmd_payload_wr;
logic [7:0] debug_bus_cmd_payload_address;
logic [31:0] debug_bus_cmd_payload_data;
logic [31:0] debug_bus_rsp_data;
logic debug_resetOut;
logic iBus_cmd_valid;
logic iBus_cmd_ready;
logic [31:0] iBus_cmd_payload_address;
logic [2:0] iBus_cmd_payload_size;
logic iBus_rsp_valid;
logic [31:0] iBus_rsp_payload_data;
logic iBus_rsp_payload_error;
logic clk;
//logic reset;
logic debugReset;
// ADDRESS
logic dBus_addflg;
logic iBus_addflg;
logic reset_0;
logic reset_1;
///////////////////////////////////////////////////////////////////////////////
// Begin RTL
///////////////////////////////////////////////////////////////////////////////
// Register and invert reset signal.
always @(posedge ap_clk) begin
areset <= ~ap_rst_n;
end
// create pulse when ap_start transitions to 1
always @(posedge ap_clk) begin
begin
ap_start_r <= ap_start;
end
end
assign ap_start_pulse = ap_start & ~ap_start_r;
assign reset_0 = ~ap_start | reset_riscv[0];
assign reset_1 = ~ap_start | reset_riscv[1];
// ap_idle is asserted when done is asserted, it is de-asserted when ap_start_pulse
// is asserted
always @(posedge ap_clk) begin
if (areset) begin
ap_idle_r <= 1'b1;
end
else begin
ap_idle_r <= ap_done ? 1'b1 :
ap_start_pulse ? 1'b0 : ap_idle;
end
end
assign ap_idle = ap_idle_r;
// Done logic
always @(posedge ap_clk) begin
if (areset) begin
ap_done_r <= '0;
end
else begin
ap_done_r <= (ap_done) ? '0 : ap_done_r | ap_done_i;
end
end
assign ap_done = &ap_done_r;
// Ready Logic (non-pipelined case)
assign ap_ready = ap_done;
assign m00_axi_awvalid = (dBus_cmd_valid && dBus_cmd_payload_wr && !dBus_addflg) ? 1:0;
always_ff @(posedge ap_clk or negedge ap_rst_n) begin : proc_dbus_addflg
if(~ap_rst_n) begin
dBus_addflg <= 0;
end else if(m00_axi_awvalid && m00_axi_awready) begin
dBus_addflg <= 1;
end else if(m00_axi_arvalid && m00_axi_arready) begin
dBus_addflg <= 1;
end else if (!dBus_cmd_valid) begin
dBus_addflg <= 0;
end
end
assign m00_axi_awaddr = ( dBus_cmd_payload_address > ABS_ADDRESS) ? dBus_cmd_payload_address : dBus_cmd_payload_address + dBus[31:0];
assign m00_axi_awlen = (dBus_cmd_payload_size == 3'd2) ? 8'd1 :
(dBus_cmd_payload_size == 3'd3) ? 8'd2 :
(dBus_cmd_payload_size == 3'd4) ? 8'd3 :
(dBus_cmd_payload_size == 3'd5) ? 8'd4 :
(dBus_cmd_payload_size == 3'd6) ? 8'd5 :
(dBus_cmd_payload_size == 3'd7) ? 8'd6 : 8'd0;
assign m00_axi_wvalid = dBus_cmd_valid;
assign m00_axi_wdata = dBus_cmd_payload_data;
assign m00_axi_wstrb = dBus_cmd_payload_mask;
assign m00_axi_wlast = dBus_cmd_payload_last;
assign m00_axi_bready = 1'b1;
assign m00_axi_arvalid = (dBus_cmd_valid && !dBus_cmd_payload_wr && !dBus_addflg) ? 1:0;
assign m00_axi_araddr = ( dBus_cmd_payload_address > ABS_ADDRESS) ? dBus_cmd_payload_address : dBus_cmd_payload_address + dBus[31:0];
assign m00_axi_arlen = (dBus_cmd_payload_size == 3'd2) ? 8'd1 :
(dBus_cmd_payload_size == 3'd3) ? 8'd2 :
(dBus_cmd_payload_size == 3'd4) ? 8'd3 :
(dBus_cmd_payload_size == 3'd5) ? 8'd4 :
(dBus_cmd_payload_size == 3'd6) ? 8'd5 :
(dBus_cmd_payload_size == 3'd7) ? 8'd6 : 8'd0;
assign m00_axi_rready = 1'b1;
assign m01_axi_awvalid = 1'b0;
assign m01_axi_awaddr = 32'b0;
assign m01_axi_awaddr = ( iBus_cmd_payload_address > ABS_ADDRESS) ? iBus_cmd_payload_address : iBus_cmd_payload_address + iBus[31:0];
assign m01_axi_wdata = 32'b0;
assign m01_axi_wstrb = 4'b0;
assign m01_axi_wlast = 1'b0;
assign m01_axi_bready = 1'b1;
assign m01_axi_arvalid = (iBus_cmd_valid && !iBus_addflg) ? 1:0;
always_ff @(posedge ap_clk or negedge ap_rst_n) begin : proc_ibus_addflg
if(~ap_rst_n) begin
iBus_addflg <= 0;
end else if(m01_axi_arvalid && m01_axi_arready) begin
iBus_addflg <= 1;
end else if (!iBus_cmd_valid) begin
iBus_addflg <= 0;
end
end
assign m01_axi_araddr = iBus_cmd_payload_address;
assign m01_axi_arlen = (iBus_cmd_payload_size == 3'd2) ? 8'd1 :
(iBus_cmd_payload_size == 3'd3) ? 8'd2 :
(iBus_cmd_payload_size == 3'd4) ? 8'd3 :
(iBus_cmd_payload_size == 3'd5) ? 8'd4 :
(iBus_cmd_payload_size == 3'd6) ? 8'd5 :
(iBus_cmd_payload_size == 3'd7) ? 8'd6 : 8'd0;
assign m01_axi_rready = 1'b1;
assign dBus_cmd_ready = m00_axi_wready;
assign dBus_rsp_valid = m00_axi_rvalid | m00_axi_bvalid;
assign dBus_rsp_payload_last = m00_axi_rlast;
assign dBus_rsp_payload_data = m00_axi_rdata;
assign dBus_rsp_payload_error = 0;
assign timerInterrupt = interrupt_riscv[0];
assign externalInterrupt = interrupt_riscv[1];
assign softwareInterrupt = interrupt_riscv[2];
assign debug_bus_cmd_valid = 0;
assign debug_bus_cmd_payload_wr = 0;
assign debug_bus_cmd_payload_address = 0;
assign debug_bus_cmd_payload_data = 0;
assign iBus_cmd_ready = m01_axi_arready;
assign iBus_rsp_valid = m01_axi_rvalid;
assign iBus_rsp_payload_data = m01_axi_rdata;
assign iBus_rsp_payload_error = 0;
assign clk = ap_clk;
assign ap_done_i = (dBus_cmd_valid && dBus_cmd_payload_wr && (dBus_cmd_payload_address == 32'hFFFFFFF0))? 2'b11:0;
VexRiscv riscv(
.dBus_cmd_valid(dBus_cmd_valid),
.dBus_cmd_ready(dBus_cmd_ready),
.dBus_cmd_payload_wr(dBus_cmd_payload_wr),
.dBus_cmd_payload_uncached(dBus_cmd_payload_uncached),
.dBus_cmd_payload_address(dBus_cmd_payload_address),
.dBus_cmd_payload_data(dBus_cmd_payload_data),
.dBus_cmd_payload_mask(dBus_cmd_payload_mask),
.dBus_cmd_payload_size(dBus_cmd_payload_size),
.dBus_cmd_payload_last(dBus_cmd_payload_last),
.dBus_rsp_valid(dBus_rsp_valid),
.dBus_rsp_payload_last(dBus_rsp_payload_last),
.dBus_rsp_payload_data(dBus_rsp_payload_data),
.dBus_rsp_payload_error(dBus_rsp_payload_error),
.timerInterrupt(timerInterrupt),
.externalInterrupt(externalInterrupt),
.softwareInterrupt(softwareInterrupt),
.debug_bus_cmd_valid(debug_bus_cmd_valid),
.debug_bus_cmd_ready(debug_bus_cmd_ready),
.debug_bus_cmd_payload_wr(debug_bus_cmd_payload_wr),
.debug_bus_cmd_payload_address(debug_bus_cmd_payload_address),
.debug_bus_cmd_payload_data(debug_bus_cmd_payload_data),
.debug_bus_rsp_data(debug_bus_rsp_data),
.debug_resetOut(debug_resetOut),
.iBus_cmd_valid(iBus_cmd_valid),
.iBus_cmd_ready(iBus_cmd_ready),
.iBus_cmd_payload_address(iBus_cmd_payload_address),
.iBus_cmd_payload_size(iBus_cmd_payload_size),
.iBus_rsp_valid(iBus_rsp_valid),
.iBus_rsp_payload_data(iBus_rsp_payload_data),
.iBus_rsp_payload_error(iBus_rsp_payload_error),
.clk(clk),
.reset(reset_0),
.debugReset(reset_1)
);
endmodule : riscv_example
`default_nettype wire
Vitis アクセラレータに組み込む
作成したファイルをVitisプラットフォームに組み込みます。ここでは、新しいアプリケーションプロジェクトを作って、VexRiscV.vを組み込む方法をご紹介します。
5.1 アプリケーションプロジェクトを作成します。。Vitisのメニューから File → New → Application Projectを実行します。
5.2 アプリケーションプロジェクトを作成する画面が出てきます。 そのままNextを押します。
5.3 kv260_pfmを選択します。Nextを押します。
5.4 プロジェクト名をきめます。今回は riscv にしました。
5.5 sysrootを設定します。ここは、共通イメージから解凍したものを使います。次の位置をして、入力してください。
/home/*****/AIEDGE/xilinx-zynqmp-common-v2022.1/sysroots/cortexa72-cortexa53-xilinx-linux
5.6 アプリケーションのタイプを入力します。今回はEmpty Applicationを選びます。
終わったらFinishをクリックします。
5.7 Vexriscv は、Verilogで提供されています。このソースコードをVitisに合うように変更する必要があります。Vitisには、RTL WizartdというツールででVerilogの雛形を作ってくれます。、Verilog等が接続できるようになっています。メニューから XILINX→Launch RTL Kernel Wizard で、riscv_kernels を選択します。(出てこないときもあります)
5.8 作成するための画面が出てきます。そのままNextを押します。
5.9 作成するRTLカーネルと名前等を決めます。今回はriscvにしました。Nextをクリックします。
5.10 レジスタポートの設定を行います。。今回は4ポート作成しました。reset_riscv interrupt_riscv ABS_ADDRESS SAMPLE です。それぞれ名前を入力したください。
ここでの名前は次のソースコードに反映されます。
5.11 AXIポートの設定を行います。RISCVからメモリーにアクセスするために使用できます。
Widthは32ビットを使うので、4バイトになります。
5.12 AXISの設定画面が出てきます。今回は使わないので、そのままOKになります。
5.13 確認の画面が出てきます。OKボタンをクリックします。
ある程度時間が立って、Vivadoが立ち上がります。ここで、追加のソースコードを実行します。
5.14 VexRisc.vのソースコードを追加します。 ソースコードの上の+ボタンをおして、Add Sourceを選択します。
5.15 ソースファイルを選択することが出来ます。
今回は、Add or Create Design Sourcesを選びます
5.16 ソースファイルを追加しますので、Add Filesを追加します。
5.17 VexRiscv.vを探して入力します。3.3で作成しました。VrexRiscvのフォルダーの下にあります。/home/****/AIEDGE/VexRiscv です。
5.18 riscv_example.svを4.1で作ったものと置き換えます。VexRiscv.vとの接続を行うためです。ファイルをダブルクリックして、その部分を全選択して、置き換えます。保存を忘れないようにしてください。
5.19 全部終わりましたら、RTLカーネルを作成します。Generate RTL Kernelをクリックします。これはVitisで使える用のまとめる役目があります。
5.20 どのような形で渡すかを書いています。ここではソースコードのみを渡します。Sorce-olny Kernelを選択します。
5.21 開いているVivadoを閉じるかを聞いてきます。このまま閉じてください。Vitisに戻ります。
5.22 今のままでは、まだ、VitisでハードウェアIPの認識がありません。IPの認識をさせるために、riscv_kernels の設定を行います。IPの追加を行います。右上のActive build Configration は Hardwareに設定の選択してください。
5.23 どのファイルをトップファイルにして、IP化するかを聞かれます。今回はriscvを選択します。
5.24 コンパイルをします。 riscv_system をクリックして、ハンマーアイコンをクリックします。
これで、kv260で動く、ハードウェア構成が出来上がります。
AIエッジコンテスト参加者は、ここまでIPを作りまして、AIの方と組み合わせれば、ハードウェアの構成は週作成できます。
この使い方は、Vitis AIの作り方の方で、ご紹介させていただきます。
RISC-Vのクロスコンパイラー環境、コンパイル
RISCVはコンパイルは、CPUなので、コンパイラーが必要になります。知名度が高いため、検索すれば、かなり使い方は探し出さます。
今回はGNUのツールチェンを使いました。
詳しくは整理して、追加します。
Vitisのアプリケーションを組み込む
KV260では、何かしらの方法で、RISCVが動くプログラムを用意しないといけません。
今回は、Vitisで作成できる、アプリケーションでRISCVの動作を管理します。
クロスコンパイラーでSRECという形式でファイルが作成できます。それを取り込んでメモリーを介して、RISCVにデータをお渡しします。
ブログ発表までに間に合わせたかったのですが、デバッグがうまくいかず、
後日詳細を掲載させていただきます。
KV260で実行
作成した、ファイルをKV260で動かします。
必要なファイルはriscv.xclbin と、Vitisプラットフォーム作りでで作成した、pl.dtbo,shell.json です。ここも整理して、後日詳細を掲載させていただきます。
# お詫び
AIエッジコンテストのため、まだ途中でしたが、ブログ公開をさせていただきました。
一部、動作に不具合点があって、中途半端になってしまいました。
後日、追加しますので、ご了承ください。