Vivado-HLSの勉強をしてみたいのだけれども、何か題材をもってデザインを作ってみる方が良いと思う。
そこで、簡単なモデルでも良いのでCPUを作ってみることにした。 最終的にはRISC-Vを実行させてみたいのだけれども、まずは簡単なモデルから動かすということで、実験してみる。 Vivado-HLSはC言語だから、テストベンチなども作りやすいはずだ。
こういった、Vivado-HLSを使ったデザイン開発には多くのメリットがあると思う。 CPUなどの性能をも取られる分野でも、
という訳で、簡単なモデルを作ってみた。
CPUなので、命令バスとデータバスを用意している。
void cpu_hls (const uint32_t inst_mem[1024], uint32_t data_mem[1024])
内部にCPUモデルを持っており、レジスタなどの状態を含んでいる。
... class rv32 { uint32_t reg32[32]; public: inst_rv32_t decode_inst (uint32_t inst);
さらに、いくつかの補助関数をクラスのメンバ関数として挿入した。
inst_rv32_t decode_inst (uint32_t inst); uint8_t get_rs1_addr (uint32_t inst) { return ((inst >> 15) & 0x1f); } uint8_t get_rs2_addr (uint32_t inst) { return ((inst >> 20) & 0x1f); } uint8_t get_rd_addr (uint32_t inst) { return ((inst >> 7) & 0x1f); } uint32_t read_reg(uint8_t addr) { return reg32[addr]; } void write_reg(uint8_t addr, uint32_t data) { reg32[addr] = data; }
デコーダは命令を解析して、必要なOpcodeを生成するだけだ。とりあえず、ADD, LOAD, STOREだけ。
inst_rv32_t rv32::decode_inst (uint32_t inst) { switch(inst & 0x3f) { case 0x33 : return ADD; case 0x03 : return LW; case 0x23 : return SW; case 0xff : return NOP; // default : return NOP; } }
デコード結果に基づいて命令を実行するループを定義する。
do { inst = inst_mem[addr]; dec_inst = u_rv32.decode_inst(inst); uint32_t reg_data; uint8_t rs1 = u_rv32.get_rs1_addr (inst); uint8_t rs2 = u_rv32.get_rs2_addr (inst); uint8_t rd = u_rv32.get_rd_addr (inst); switch (dec_inst) { case LW : { uint32_t addr = rs1 + ((inst >> 20) & 0xfff); reg_data = mem_access(LOAD, rs1, addr, data_mem); u_rv32.write_reg(rd, reg_data); break; } case ADD : { reg_data = u_rv32.read_reg(rs1) + u_rv32.read_reg(rs2); u_rv32.write_reg(rd, reg_data); break; } ...
まずはテストをしなければならないのだが、これだけではテストパタンも流せないので、とりあえずはVivado-HLSによるVerilogの生成を行ってみる。
directives.tcl
として以下を定義した。つまり、命令バスとデータバスは1つのバスとして扱うことにする。
set_directive_interface -mode s_axilite -bundle slv0 "cpu_hls" set_directive_interface -mode s_axilite -bundle slv0 "cpu_hls" inst_mem set_directive_interface -mode s_axilite -bundle slv0 "cpu_hls" data_mem
これでVivado-HLSでVerilogファイルを生成してみる。
cpu_hls/normal/syn/verilog/cpu_hls.v
module cpu_hls ( ap_clk, ap_rst_n, s_axi_slv0_AWVALID, s_axi_slv0_AWREADY, s_axi_slv0_AWADDR, s_axi_slv0_WVALID, s_axi_slv0_WREADY, s_axi_slv0_WDATA, s_axi_slv0_WSTRB, s_axi_slv0_ARVALID, s_axi_slv0_ARREADY, s_axi_slv0_ARADDR, s_axi_slv0_RVALID, s_axi_slv0_RREADY, s_axi_slv0_RDATA, s_axi_slv0_RRESP, s_axi_slv0_BVALID, s_axi_slv0_BREADY, s_axi_slv0_BRESP, interrupt ); ... cpu_hls_slv0_s_axi #( .C_S_AXI_ADDR_WIDTH( C_S_AXI_SLV0_ADDR_WIDTH ), .C_S_AXI_DATA_WIDTH( C_S_AXI_SLV0_DATA_WIDTH )) cpu_hls_slv0_s_axi_U( .AWVALID(s_axi_slv0_AWVALID), .AWREADY(s_axi_slv0_AWREADY), .AWADDR(s_axi_slv0_AWADDR), .WVALID(s_axi_slv0_WVALID), .WREADY(s_axi_slv0_WREADY), ...
ちゃんとバスが1つにまとめられている。