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; }
いやあ、これはダメな気がするな... もう少し賢い実装方法がないのか、調査中...