CSR
CSR全名為control system register,是軟體用來與系統溝通的暫存器,在整個RISC-V系統中有許許多多的CSR,每個CSR內各自的欄位功能有所不同,也有對應的權限設定。而我們這邊只會實現特定幾顆要處理中斷及Linux開機過程中會使用到的CSR,關於CSR可以參考 肉克斯超人的不懂 CSR 那就放棄吧等文章。今天主要要做的是與CSR溝通的指令,而不會去講到CSR本身,值得一提的是,CSR指令並不是RISC-V I extension的一部分,而是獨立在Zicsr的extension,因此如果在編譯工具鍊的時候有調整ISA的話要確認這件事情,才能正確編出CSR指令。
CSR指令主要有以下幾種,我們先隨意創建一個CSR並對他作操作,用來測試CSR指令正確。
指令 | 行為 |
---|---|
csrrw |
將CSR的值放到暫存,並將另一暫存的值放到CSR內。 |
csrrs |
將CSR的值讀出放到暫存,將另一暫存內對應bit為1的值當作mask將讀出值set後寫回csr內。 |
csrrc |
將CSR的值讀出放到暫存,將另一暫存內對應bit為1的值當作mask將讀出值clear掉後寫回csr內。 |
csrrwi |
將CSR的值放到暫存,並將立即值放到CSR內。 |
csrrsi |
將CSR的值讀出放到暫存,將立即值內對應bit為1的值當作mask將讀出值set後寫回csr內。 |
csrrci |
將CSR的值讀出放到暫存,將立即值內對應bit為1的值當作mask將讀出值clear掉後寫回csr內。 |
其中csrrs和csrrc的概念可能比較不容易懂,但可以這樣理解:CSR的世界裡每個bit都代表不同的功能,很多時候我們只是想要操作其中一個bit,例如mstatus的[3]是用來控制MIE(Machine interrupt enable),我們想要操作這個bit的開關但不想要去碰到其他的bit的時候,就可以用csrrs開啟,csrrc關掉。
CSR指令的測試
結合前述,其實CSR指令在實現上只是對某個變數作操作而已,比較麻煩的地方在於不同CSR的物理意義要怎麼表現在模擬器內。
我們實現以下測試碼,對於編碼為0x7cc的CSR進行操作,並設定csr 0x7cc原本的值是0x1010101010101010,如下:
TEST(ISATESTSuite, csrrw_0x7cc_0x1234)
{
ALISS::csr[0x7cc] = 0x1010101010101010;
ALISS::reg[10] = 0x0;
ALISS::reg[11] = 0x1234;
uint32_t insn = 0x7cc59573; // csrrw a0 0x7cc a1
ALISS::ID_EX_WB(insn);
EXPECT_EQ(ALISS::csr[0x7cc], 0x1234 ); //csr write to 1234;
EXPECT_EQ(ALISS::reg[10], 0x1010101010101010 ); //reg is ori value;
}
TEST(ISATESTSuite, csrrs_0x7cc_0x101)
{
ALISS::csr[0x7cc] = 0x1010101010101010;
ALISS::reg[10] = 0x0;
ALISS::reg[11] = 0x101;
uint32_t insn = 0x7cc5a573; // csrrs a0 0x7cc a1
ALISS::ID_EX_WB(insn);
EXPECT_EQ(ALISS::csr[0x7cc], 0x1010101010101111 ); //csrset [1], [9];
EXPECT_EQ(ALISS::reg[10], 0x1010101010101010 ); //reg is ori value;
}
TEST(ISATESTSuite, csrrc_0x7cc_0x1010)
{
ALISS::csr[0x7cc] = 0x1010101010101010;
ALISS::reg[10] = 0x0;
ALISS::reg[11] = 0x1010;
uint32_t insn = 0x7cc5b573; // csrrc a0 0x7cc a1
ALISS::ID_EX_WB(insn);
EXPECT_EQ(ALISS::csr[0x7cc], 0x1010101010100000 ); //csr clear [4], [12];
EXPECT_EQ(ALISS::reg[10], 0x1010101010101010 ); //reg is ori value;
}
TEST(ISATESTSuite, csrrwi_0x7cc_0x2)
{
ALISS::csr[0x7cc] = 0x1010101010101010;
ALISS::reg[10] = 0x0;
uint32_t insn = 0x7cc15573; // csrrwi x10, 0x7cc, 2
ALISS::ID_EX_WB(insn);
EXPECT_EQ(ALISS::csr[0x7cc], 0x2 ); //csr write to 2;
EXPECT_EQ(ALISS::reg[10], 0x1010101010101010 ); //reg is ori value;
}
TEST(ISATESTSuite, csrrsi_0x7cc_0x1)
{
ALISS::csr[0x7cc] = 0x1010101010101010;
ALISS::reg[10] = 0x0;
uint32_t insn = 0x7cc0e573; // csrrsi x10, 0x7cc, 1
ALISS::ID_EX_WB(insn);
EXPECT_EQ(ALISS::csr[0x7cc], 0x1010101010101011 ); //csrset [1];
EXPECT_EQ(ALISS::reg[10], 0x1010101010101010 ); //reg is ori value;
}
TEST(ISATESTSuite, csrrci_0x7cc_0x10)
{
ALISS::csr[0x7cc] = 0x1010101010101010;
ALISS::reg[10] = 0x0;
uint32_t insn = 0x7cc87573; // csrrci a0 0x7cc 0x10
ALISS::ID_EX_WB(insn);
EXPECT_EQ(ALISS::csr[0x7cc], 0x1010101010101000 ); //csr clear [4];
EXPECT_EQ(ALISS::reg[10], 0x1010101010101010 ); //reg is ori value;
}
實作上很簡單,set跟clear只要分別做|運算以及&~運算即可,因此我們實現程式碼如下:
case 0x73: //SYSTEM
{
uint64_t csrno = ((insn >> 20) & 0xfff); //[31:20] = csrno
uint64_t rs1 = ((insn >> 15) & 0x1f);
uint64_t rd = ((insn >> 7) & 0x1f);
switch ((insn >> 12) & 7)
{
case 1: //csrrw
{
reg[rd] = csr[csrno];
csr[csrno] = reg[rs1];
break;
}
case 2: //csrrs
{
reg[rd] = csr[csrno];
csr[csrno] = reg[rd] | reg[rs1];
break;
}
case 3: //csrrc
{
reg[rd] = csr[csrno];
csr[csrno] = reg[rd] & ~reg[rs1];
break;
}
case 5: //csrrwi
{
reg[rd] = csr[csrno];
csr[csrno] = rs1;
break;
}
case 6: //csrrsi
{
reg[rd] = csr[csrno];
csr[csrno] = reg[rd] | rs1;
break;
}
case 7: //csrrci
{
reg[rd] = csr[csrno];
csr[csrno] = reg[rd] & ~rs1;
break;
}
default:
{
printf("Illegal instruction");
printf("%x\n",insn);
break;
}
}
break;
}
可以觀察到如csrrw對上csrrwi,運算本身都一樣,區別只在於一般的csrrw將讀出來的[19:15]作為register的編號使用,而csrrwi則將讀出來的值當成unsigned的imm,這邊的imm不需要做sext,筆者猜測原因是csr通常是每個bit的意義不同,不太會有全部當成一個完整的數字因此需要sext的狀況。
一樣的確認測試全部通過,送出。
碎碎念:I指令已經差不多完成了,只剩下一些system相關的指令,以及64bit特別的擴充,完成之後應該就要有能力跑之前介紹的riscv-test了。