FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

Vivado-HLSを使って高位合成でCPUを作ってみる(9. HLSでAXIのバイトイネーブルはどう制御する?)

Vivado-HLSを使って簡単なRISC-V CPUを作ってみている。

現在の悩みどころはメモリアクセスの制御だ。CPUはワードアクセス以外に、ハーフワード、バイトアクセスなども発生する。 Vivado-HLSのモジュールのインタフェースはAXIだけれども、C言語でこれをどのように記述すればよいのか、悩みどころだ。

たとえば、最小単位がバイトアクセスだからと言って、

int8_t mem[MEM_SIZE];

mem[addr << 2 + 0] = (word >> 0)& 0xff;
mem[addr << 2 + 1] = (word >> 8) & 0xff;
mem[addr << 2 + 2] = (word >> 16) & 0xff;
mem[addr << 2 + 3] = (word >> 24) & 0xff;

などと書いていては、非常に効率が悪いしバイトアクセスが4回発生して性能に影響する。 どうにかして、Vivado-HLSの生成するHDLでAXIのバイトアクセスを制御したい。

とりあえず、現状ではメモリアクセスモジュールのAXIインタフェースで、WSTRBが固定値になっている。ここを変えたいんだー。

...
    .I_WVALID(mem_WVALID),
    .I_WREADY(mem_WREADY),
    .I_WDATA(grp_read_reg_fu_948_ap_return),
    .I_WID(1'd0),
    .I_WUSER(1'd0),
    .I_WLAST(1'b0),
    .I_WSTRB(4'd15),
...

いろいろ調査したのだが、まだ満足した結論には至っていない。 とりあえず、Vivado-HLSのネイティブっぽそうな信号単位であるap_int<>ap_uint<>を導入してデザインを書き直した。

-#include <stdint.h>
+#include <ap_int.h>

-typedef int32_t  XLEN_t   ;
-typedef uint32_t UXLEN_t  ;
-typedef uint8_t  RegAddr_t;
-typedef uint32_t Inst_t   ;
-typedef uint16_t Addr_t   ;
+typedef ap_int<32>  XLEN_t   ;
+typedef ap_uint<32> UXLEN_t  ;
+typedef ap_uint<8>  RegAddr_t;
+typedef ap_uint<32> Inst_t   ;
+typedef ap_uint<16> Addr_t   ;
...
     case SRA : {
-      XLEN_t reg_data = read_reg(m_rs1) >> (uint32_t)read_reg(m_rs2);
+      uint8_t shamt = read_reg(m_rs2) & 0x01f;
+      XLEN_t reg_data = read_reg(m_rs1) >> shamt;
       write_reg(m_rd, reg_data);
       break;
     }

あとは、ap_int<>, ap_uint<>を使ったデザインはfprintfで出力するときも明示的なキャストが必要らしい。

 #ifndef __SYNTHESIS__
-      fprintf(m_cpu_log, "x%02d <= %08x\n", addr, data);
+      fprintf(m_cpu_log, "x%02d <= %08x\n", static_cast<uint32_t>(addr), static_cast<uint32_t>(data));
 #endif // _SYNTHESIS

これで32ビットのAXIバスをCPUに接続した。さてバイトアクセスをどうするか。

ロード命令は32bitロードして残りは捨てればよい。ストア命令は、部分更新となるため、どうしても一度元となるデータを取り込む必要がある。

苦肉の策で、とりあえず現時点では32bitよりも小さいストアの時は、ストア先の32bitワードを一度ロードすることにした。

@@ -465,19 +462,21 @@ XLEN_t rv32_cpu::mem_access (memtype_t op, uint32_t data, uint32_t addr, AccSize
 #endif // __SYNTHESIS__
         switch(size) {
           case SIZE_BYTE  : {
-            m_data_mem[addr] = data;
+            XLEN_t tmp_word = m_data_mem[addr >> 2];
+            tmp_word = (tmp_word & ~(0xff << ((addr & 0x03) * 8))) |
+                       ((data & 0xff) << ((addr & 0x03) * 8));
+            m_data_mem[addr >> 2] = data;
             break;
           }

いやあ、これはダメな気がするな... もう少し賢い実装方法がないのか、調査中...

f:id:msyksphinz:20190226233836p:plain