iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
Software Development

從零開始的RISC-V ISA Simulator (Another Little RISC-V ISA Simulator)系列 第 20

Day 20 - Load/Store實作,用來與記憶體互動的指令們

  • 分享至 

  • xImage
  •  

Load/Store指令

Load/Store指令是RISC-V中用來與記憶體互動的指令,又會根據每次讀取或寫入的大小,是否為有號數不同,分成"L/SB (Load/Store byte)","L/SH (Load/Store half)","L/SW (Load/Store word)"及有號數的LBU與LSU。(為什麼Store指令不需要無號版本呢,因為讀到暫存內時要考慮接下來使用該暫存內值的形式,但寫回去時可以理解為將值放入,後續使用時會是有號無號則是視當下場景所需),我們將上述訊息整理成下表。

指令 行為
lb 將一個byte的值讀出,sign extension後放到暫存內。
lh 將兩個byte的值讀出,sign extension後放到暫存內。
lw 將四個byte的值讀出,sign extension後放到暫存內。
lbu 將一個byte的值讀出放到暫存內。
lhu 將兩個byte的值讀出放到暫存內。
sb 將暫存的值從最低位開始寫一個byte到記憶體內。
sh 將暫存的值從最低位開始寫兩個byte到記憶體內。
sw 將暫存的值從最低位開始寫四個byte到記憶體內。

Load/Store指令的解碼

https://ithelp.ithome.com.tw/upload/images/20231005/20162436KSWEJqa4hZ.png

Load/Store指令的解碼格式如上圖,其中Load指令為I-type,會將暫存內的值讀出來,加上imm之後得出一個位址,再對該位址做讀取。而Store指令為S-type,會將暫存內的值讀出來,加上[31:20]及[11:7]拼湊出的imm後得出一個位址,再對該為只做寫入。

Load/Store測試

一樣的,我們先從測試程式開始寫起,在測試Load指令時,我們在記憶體位址0x4000的地方寫入一個0xffffffff88888888,再分別透過不同指令做讀取,測試程式如下。

TEST(ISATESTSuite, LB_0x40000_0xffffffff88888888)
{
    ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test
    uint64_t* memory64  = (uint64_t*)ALISS::memory;

    memory64[0x40000 / 8] = 0xffffffff88888888;
    ALISS::reg[10] = 0x0;
    ALISS::reg[11] = 0x40100;

    uint32_t insn = 0xf0058503; // LB a0 -256(a1)
    ALISS::ID_EX_WB(insn);

    EXPECT_EQ(ALISS::reg[10], -120 ); //load -120;
    free(ALISS::memory);
}

TEST(ISATESTSuite, LBU_0x40000_0xffffffff88888888)
{
    ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test
    uint64_t* memory64  = (uint64_t*)ALISS::memory;

    memory64[0x40000 / 8] = 0xffffffff88888888;
    ALISS::reg[10] = 0x0;
    ALISS::reg[11] = 0x40100;

    uint32_t insn = 0xf005c503; // LBU a0 -256(a1)
    ALISS::ID_EX_WB(insn);

    EXPECT_EQ(ALISS::reg[10], 0x88 ); //load 0x88;
    free(ALISS::memory);
}

TEST(ISATESTSuite, LH_0x40000_0xffffffff88888888)
{
    ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test
    uint64_t* memory64  = (uint64_t*)ALISS::memory;

    memory64[0x40000 / 8] = 0xffffffff88888888;
    ALISS::reg[10] = 0x0;
    ALISS::reg[11] = 0x40100;

    uint32_t insn = 0xf0059503; // LH a0 -256(a1)
    ALISS::ID_EX_WB(insn);

    EXPECT_EQ(ALISS::reg[10], -30584 ); //load -30584;
    free(ALISS::memory);
}

TEST(ISATESTSuite, LHU_0x40000_0xffffffff88888888)
{
    ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test
    uint64_t* memory64  = (uint64_t*)ALISS::memory;

    memory64[0x40000 / 8] = 0xffffffff88888888;
    ALISS::reg[10] = 0x0;
    ALISS::reg[11] = 0x40100;

    uint32_t insn = 0xf005d503; // LHU a0 -256(a1)
    ALISS::ID_EX_WB(insn);

    EXPECT_EQ(ALISS::reg[10], 0x8888 ); //load 34952;
    free(ALISS::memory);
}

TEST(ISATESTSuite, LW_0x40000_0xffffffff88888888)
{
    ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test
    uint64_t* memory64  = (uint64_t*)ALISS::memory;

    memory64[0x40000 / 8] = 0xffffffff88888888;
    ALISS::reg[10] = 0x0;
    ALISS::reg[11] = 0x40100;

    uint32_t insn = 0xf005a503; // LHU a0 -256(a1)
    ALISS::ID_EX_WB(insn);

    EXPECT_EQ(ALISS::reg[10], -2004318072 ); //load -2004318072;
    free(ALISS::memory);
}

而測試Store指令時,我們只要反過來進行即可,先將a0裡面的值寫為0xffffffff88888888,再透過Store指令寫回去。

TEST(ISATESTSuite, SB_0x40000_0xffffffff88888888)
{
    ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test

    ALISS::reg[10] = 0xffffffff88888888;
    ALISS::reg[11] = 0x40100;

    uint64_t* memory64  = (uint64_t*)ALISS::memory;
    memory64[0x40000 / 8 ] =  0x0;

    uint32_t insn = 0xf0a58023; // SB a0 -256(a1)
    ALISS::ID_EX_WB(insn);

    EXPECT_EQ(memory64[0x40000 /  8], 0x88 ); //store 88 to memory;
    free(ALISS::memory);
}

TEST(ISATESTSuite, SH_0x40000_0xffffffff88888888)
{
    ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test

    ALISS::reg[10] = 0xffffffff88888888;
    ALISS::reg[11] = 0x40100;

    uint64_t* memory64  = (uint64_t*)ALISS::memory;
    memory64[0x40000 / 8 ] =  0x0;
 
    uint32_t insn = 0xf0a59023; // SB a0 -256(a1)
    ALISS::ID_EX_WB(insn);

    EXPECT_EQ(memory64[0x40000 / 8], 0x8888 ); //store 8888 to memory;
    free(ALISS::memory);
}

TEST(ISATESTSuite, SW_0x40000_0xffffffff88888888)
{
    ALISS::memory = (uint8_t *)std::malloc(4 * 1024 * 1024); //4MB size for test

    ALISS::reg[10] = 0xffffffff88888888;
    ALISS::reg[11] = 0x40100;

    uint64_t* memory64  = (uint64_t*)ALISS::memory;
    memory64[0x40000 / 8 ] =  0x0;
 
    uint32_t insn = 0xf0a5a023; // SB a0 -256(a1)
    ALISS::ID_EX_WB(insn);

    EXPECT_EQ(memory64[0x40000 / 8], 0x88888888 ); //store 88888888 to memory;
    free(ALISS::memory);
}

Load/Store指令實作

Load指令的[6:0]為0x3,Store指令的[6:0]為0x23,並且同樣的透過func[3:0]來區分不同種類的load或store,實作上只要特別注意store的imm要怎麼產生即可,我們可以重複利用先前load elf時實作的get_mem function,完整實作如下。

            case 0x3:  //LOAD
            {
                uint64_t rd = ((insn >> 7) & 0x1f);
                uint64_t rs1 = ((insn  >> 15) & 0x1f);
                uint64_t imm = ((insn >> 20) & 0xfff);
                switch ((insn >> 12) & 7)
                {
                    case 0: //LB
                    {
                        reg[rd] = sext(get_mem_b(reg[rs1] + sext(imm,12)),8);
                        break;
                    }
                    case 1:  //LH
                    {
                        reg[rd] = sext(get_mem_h(reg[rs1] + sext(imm,12)),16);
                        break;
                    }
                    case 2: //LW
                    {
                        reg[rd] = sext(get_mem_w(reg[rs1] + sext(imm,12)),32);
                        break;
                    }
                    case 4: //LBU
                    {
                        reg[rd] = get_mem_b(reg[rs1] + sext(imm,12));
                        break;
                    }
                    case 5:  //LHU
                    {
                        reg[rd] = get_mem_h(reg[rs1] + sext(imm,12));
                        break;
                    }
                    default:
                    {
                         printf("Illegal instruction");
                         printf("%x\n",insn);
                         break;
                    }

                }
                break;
            }
            case 0x23: //STORE
            {
                uint64_t rs1 = ((insn  >> 15) & 0x1f);
                uint64_t rs2 = ((insn  >> 20) & 0x1f);

                uint64_t imm = ( ( insn & 0xfe000000 ) >> 20 ) | // [11:5]
                               ( ( insn >> 7 ) & 0x1f ); //[4:0]
                switch ((insn >> 12) & 7)
                {
                    case 0: //SB
                    {
                        set_mem_b(reg[rs1] + sext(imm,12), (uint8_t)reg[rs2]);
                        break;
                    }
                    case 1:  //SH
                    {
                        set_mem_h(reg[rs1] + sext(imm,12), (uint16_t)reg[rs2]);
                        break;
                    }
                    case 2: //SW
                    {
                        set_mem_w(reg[rs1] + sext(imm,12), (uint32_t)reg[rs2]);
                        break;
                    }
                    default:
                    {
                         printf("Illegal instruction");
                         printf("%x\n",insn);
                         break;
                    }

                }

                break;
            }

確認測試完成,送出。


碎碎念 : Load/Store指令完成,RISC-V I-extension也快要完成了~ 明天來補齊剩下的指令以及RV64特有的指令。


上一篇
Day-19 RISC-V CTRL Instruction implement
下一篇
Day 21 - CSR指令,軟體與硬體的溝通
系列文
從零開始的RISC-V ISA Simulator (Another Little RISC-V ISA Simulator)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言