FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Vivado-HLSを使って高位合成でCPUを作ってみる(1. メモリのRead/Writeのモデルを作成)

Vivado-HLSで高位合成からCPUを作ってみたい。 高位合成でCPUを作るというのはネタとしてはイマイチだけれども、高位合成がどういうものか、目標をもって試してみるには良い題材だと思うので試してみている。

前回はとりあえずメモリにアクセスしてデータをフェッチしたり書き込んだりするモデルを作った。 次は、本格的にCPUっぽいものを作っていく。

目標としてはRISC-VのISSに近い、C++の実装からVivado-HLSを通じて高位合成でハードウェアを生成することだ。 C++ISSを作った場合、プロセッサのモデルをクラス化し、そこからいろんなメソッドを追加するような構成になる(と思う)。

そこで、まずは一般的なCPUのクラスを作成し、そこにレジスタ、メモリアクセスのメソッド、システムレジスタアクセス用のサブクラスを追加していく。

f:id:msyksphinz:20190213005116p:plain
高位合成で使用するCPUのファイル群の構成
class rv32_cpu {
  XLEN_t m_reg32[32];
  rv32_csr m_rv32_csr;
  uint32_t *m_data_mem;

public:
  rv32_cpu (uint32_t *data_mem);
...

m_rv32_csrCSRレジスタをまとめたクラスだ。m_reg32[32]汎用レジスタ群となる。

まず、命令をフェッチしてデコードする。

  do {
    inst = inst_mem[addr];
    dec_inst = u_rv32_cpu.decode_inst(inst);

デコード論理はこんな感じ。非常にしょぼい。

inst_rv32_t rv32_cpu::decode_inst (uint32_t inst)
{
  switch(inst & 0x3f) {
    case 0x33 : return ADD;
    case 0x03 : return LW;
    case 0x23 : return SW;
    case 0xff : return NOP;   //
    case 0x73 :
      switch ((inst >> 12) & 0x07) {
        case 0b001 : return CSRRW  ;
        case 0b010 : return CSRRS  ;
        case 0b011 : return CSRRC  ;

次に、オペランドを取り出す。

    RegAddr_t rs1 = u_rv32_cpu.get_rs1_addr (inst);
    RegAddr_t rs2 = u_rv32_cpu.get_rs2_addr (inst);
    RegAddr_t rd  = u_rv32_cpu.get_rd_addr  (inst);
    uint16_t  csr_addr = (inst >> 16) & 0x0ffff;

次にデコード結果に基づき実行を行う。メモリアクセスについては、mem_access関数に集約し、data_mem配列にアクセスすることで実現している。

    switch (dec_inst) {
      case CSRRW  : {
        reg_data = u_rv32_cpu.csrrw (csr_addr, rs1);
        u_rv32_cpu.write_reg(rd, reg_data);
        break;
      }
      case CSRRS  : {
        reg_data = u_rv32_cpu.csrrs (csr_addr, rs1);
...
      case LW  : {
        uint32_t addr = rs1 + ((inst >> 20) & 0xfff);
        reg_data = u_rv32_cpu.mem_access(LOAD, rs1, addr, data_mem);
        u_rv32_cpu.write_reg(rd, reg_data);
        break;
      }
      case ADD : {
        reg_data = u_rv32_cpu.read_reg(rs1) + u_rv32_cpu.read_reg(rs2);
        u_rv32_cpu.write_reg(rd, reg_data);
...

という訳で、これらのCPUのモデルをクラス化した。

github.com

これでVivado-HLSを実行してみると、なるほど、ちゃんと合成が終了したようだ。 C++のクラスも扱えるし、ポインタも行ける。 Vivado-HLSはすごいなあ。

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,
...
f:id:msyksphinz:20190213005435p:plain