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指令的解碼
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特有的指令。