(DE0-CV) FPGAマガジンのRISC-VをDE0-CVに実装する

FPGAマガジン No.18でRISC-Vを実装していたので自分もやってみる。

FPGAマガジンだとVerilogで記述しているけど、自分はVHDLで記述する。
なおかつ実装するのはDE0-CV。

ソースコードは紙面に載っているだけでサポートとかでの提供はないっぽい?
紙面を読みながらまずはfmrv32im_decode.vを実装してみる。

まずはentity

entity fmrv32im_decode is
    port (
        RST_N           : in    std_logic;
        CLK             : in    std_logic;

        -- インストラクション・コード
        INST_CODE       : in    std_logic_vector(31 downto 0);

        -- レジスタ番号
        RD_NUM          : out   std_logic_vector( 4 downto 0);
        RS1_NUM         : out   std_logic_vector( 4 downto 0);
        RS2_NUM         : out   std_logic_vector( 4 downto 0);

        -- イミディエイト
        IMM             : out   std_logic_vector(31 downto 0);

        -- 命令
        INST            : in    t_INST
    );
end entity;

INSTはズラッと並べるのもしんどいのでpackageファイルでrecodeタイプ宣言している。 こんな感じ。

    type t_INST is record
        INST_LUI            : std_logic;
        INST_AUIPC          : std_logic;
        INST_JAL            : std_logic;
        INST_JALR           : std_logic;
--------------------------------------------
--      略
--------------------------------------------
        INST_DIV            : std_logic;
        INST_DIVU           : std_logic;
        INST_REM            : std_logic;
        INST_REMU           : std_logic;
    end record;

次は命令タイプの判別とイミディエイトの生成部分

    -- イミディエイト判別
    sIMM.i_type <= JUDGE_I(INST_CODE);
    sIMM.r_type <= JUDGE_R(INST_CODE);
    sIMM.s_type <= JUDGE_S(INST_CODE);
    sIMM.b_type <= JUDGE_B(INST_CODE);
    sIMM.u_type <= JUDGE_U(INST_CODE);
    sIMM.j_type <= JUDGE_J(INST_CODE);
    c0_type     <= '0';
    
    -- イミディエイト生成
    process (CLK, RST_N) is
    begin
        if RST_N = '0' then
            IMM <= (others => '0');
        elsif rising_edge(CLK) then
            -- 各イミディエイトとtype判定結果をandすることで、
            -- 判定したtypeのイミディエイトのみ残して他をマスクしている。
            -- 多bit and 1bit というのはVHDL-2008の構文
            -- (多bitすべてに1bitをandする回路になる)
            IMM <= ( (IMM_I(INST_CODE) and sIMM.i_type)
                  or (IMM_S(INST_CODE) and sIMM.s_type)
                  or (IMM_B(INST_CODE) and sIMM.b_type)
                  or (IMM_U(INST_CODE) and sIMM.u_type)
                  or (IMM_J(INST_CODE) and sIMM.j_type) );
        end if;
    end process;

sIMMもpackageで定義したrecordタイプの信号。

    type t_IMM is record
        r_type : std_logic;
        i_type : std_logic;
        s_type : std_logic;
        b_type : std_logic;
        u_type : std_logic;
        j_type : std_logic;
    end record;

JUDGE_*()はfunction。 FPGAマガジンの実装と違い、こんな感じでそのタイプに属するOPCODEを判定してORしている。
OPCODEもpackageで定義したrecordタイプ。

    function JUDGE_R(
        INST_CODE : std_logic_vector(31 downto 0)
    ) return STD_LOGIC is
        variable O : std_logic_vector(4 downto 0);
    begin
        O := INST_CODE(6 downto 2);  -- OPCODE
        return ( O ?= OPCODE.OP_ADD or O ?= OPCODE.OP_SUB or O ?= OPCODE.OP_SLT or O ?= OPCODE.OP_SLTU
              or O ?= OPCODE.OP_AND or O ?= OPCODE.OP_OR  or O ?= OPCODE.OP_XOR
              or O ?= OPCODE.OP_SLL or O ?= OPCODE.OP_SRL or O ?= OPCODE.OP_SRA
             );
    end function;

ちなみにR-TYPEだとOPCODEぜんぶ同じ。論理合成で消える。
命令ぜんぶ書いたほうがわかりやすいかな。。と思ってこんな実装にしたけど、記述ミスでバグが入りそう……。
あとで作り直すかも。

?=ってのはVHDL-2008の構文。

イミディエイト生成箇所は、IMM_*()というfunctionで各イミディエイトを抽出して、イミディエイト判定結果で
andしてマスクするという作りにした。

イミディエイト抽出しているfunctionはこんな感じ。
RISC-V仕様書のChapter2, 2.2 Base Instruction Formatに書いてあるとおりに作った。

    -- Create Immediate for I_TYPE
    --  31          11 10        5 4         1        0
    --  -- inst[31] -- inst[30:25] inst[24:21] inst[20]
    function IMM_I(
        INST_CODE : std_logic_vector(31 downto 0)
    ) return std_logic_vector(31 downto 0) is
        variable tmp : std_logic_vector(31 downto 0);
    begin
        for i in 31 downto 11 loop
            tmp(i) := INST_CODE(31);
        end loop;
        tmp(10 downto  5) := INST_CODE(30 downto 25);
        tmp( 4 downto  1) := INST_CODE(24 downto 21);
        tmp(           0) := INST_CODE(          20);
        return tmp;
    end function;

レジスタ番号生成部はFPGAマガジンそのまま。
上の処理と整合性を取るならこれもfunction化すべきかも。

    -- レジスタ番号生成
    RD_NUM  <= INST_CODE(11 downto  7) when (sIMM.r_type or sIMM.i_type or sIMM.u_type or sIMM.j_type or c0_type)
        else (others => '0');
    RS1_NUM <= INST_CODE(19 downto 15) when (sIMM.r_type or sIMM.i_type or sIMM.s_type or sIMM.b_type)
        else (others => '0');
    RS2_NUM <= INST_CODE(24 downto 20) when (sIMM.r_type or sIMM.s_type or sIMM.b_type)
        else (others => '0');

あとは各種ファンクション生成部を作るだけ。
ただ、これが面倒そう。ゴリゴリ書くのは嫌なのでどうにか工夫したい。