iT邦幫忙

0

RISC-V on Rust 從零開始(9) - 實作memory model

CPU指令可以分成兩大類,一是操作CPU內部暫存器的算術邏輯指令,一是存取記憶體,也就是所謂的load/store指令。要模擬算術邏輯指令,用先前所定義的Core資料結構即可,因為內部已經包含了暫存器,而load/store指令則需要與core外部的元件溝通,常見的有l1 cache、l2 cache、bus等,目前為了簡化實作,先統一由一個memory model來代表core外部的系統。memory model很單純,就是一塊可以讀寫的記憶體,以64位元系統為例,位址空間就是0 ~ 2**64-1這麼大。

memory model中使用HashMap來存放資料,此處引入block的概念,將整個記憶體空間以32 bytes為單位進行分割,HashMap的key為block base address,value為長度32的u8陣列,當CPU存取某個address時,memory model會先查看此address所屬的block是否存在,是的話直接從HashMap中取得資料,否則創建新的block加入HashMap。使用HashMap的好處是可以動態的增加記憶體空間,效能方面雖然比array或vector略差,但可以有很好的彈性。

首先實作寫1 byte的邏輯:

pub fn write_byte(&mut self, addr: AddressType, value: u8) {
    let block_base = addr & (!(0x1f as AddressType));
    let block_offset = addr - block_base;
    if !self.data.contains_key(&block_base) {
        self.data.insert(block_base, [0; 32]); // 插入新block
    }

    let block = self.data.get_mut(&block_base).unwrap();
    block[block_offset as usize] = value;
}

pub fn read_byte(&mut self, addr: AddressType) -> u8 {
    let block_base = addr & (!(0x1f as AddressType));
    let block_offset = addr - block_base;
    if !self.data.contains_key(&block_base) {
        self.data.insert(block_base, [0; 32]);
    }

    let block = self.data.get(&block_base).unwrap();
    block[block_offset as usize]
}

由於存取memory的長度不是固定的,因此需要實作一個比較general的interface:

fn access_memory(&mut self, payload: &mut Payload) {
    match payload.op {
        MemoryOperation::READ => {
            for i in 0..payload.data.len() {
                payload.data[i] = self.read_byte(payload.addr + i as AddressType);
            }
        }
        MemoryOperation::WRITE => {
            for i in 0..payload.data.len() {
                self.write_byte(payload.addr + i as AddressType, payload.data[i]);
            }
        }
        MemoryOperation::INVALID => panic!("Invalid mem op"),
    }
}

此處的payload類似網路的封包,實際上硬體也是利用類似的方式互相溝通。

接著core就可以呼叫access_memory來讀寫記憶體:

fn write_memory(&mut self, address: AddressType, data: &[u8]) {
    let mut payload = Payload {
        addr: address,
        data: data.to_vec(),
        op: MemoryOperation::WRITE,
    };
    self.mem_if.as_mut().unwrap().access_memory(&mut payload);
}

fn read_memory(&mut self, address: AddressType, data: &mut [u8]) {
    let mut payload = Payload {
        addr: address,
        data: data.to_vec(),
        op: MemoryOperation::READ,
    };
    self.mem_if.as_mut().unwrap().access_memory(&mut payload);

    for i in 0..data.len() {
        data[i] = payload.data[i];
    }
}

有了memory model之後,就可以來實作load/store指令了。完整程式碼可以參考:rv-sim


尚未有邦友留言

立即登入留言