iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0

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的狀況。

一樣的確認測試全部通過,送出。
https://ithelp.ithome.com.tw/upload/images/20231006/20162436dpeHGCKYRG.png


碎碎念:I指令已經差不多完成了,只剩下一些system相關的指令,以及64bit特別的擴充,完成之後應該就要有能力跑之前介紹的riscv-test了。


上一篇
Day 20 - Load/Store實作,用來與記憶體互動的指令們
下一篇
Day 22 - RV64I,64bit架構底下特有的指令實作
系列文
從零開始的RISC-V ISA Simulator (Another Little RISC-V ISA Simulator)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言