设备和输入输出
你已经在NEMU和NPC上都实现TRM了, 下一步当然是让它们支持输入输出了.
在NEMU中实现输入输出
根据PA讲义完成PA2阶段3, 直到你看到如下提示框:
温馨提示
PA2到此结束...
NPC中的输入输出
对于RISC-V架构, 输入输出是通过MMIO来实现的. 有了基于DPI-C的内存读写函数, 目前我们不必修改RTL代码就可以为NPC实现输入输出了! 我们只需要在这两个函数中对地址的范围进行简单的判断, 就可以将来自NPC的访存请求重定向到正确的设备了. 硬件上的MMIO是基于总线来实现的, 我们将来再实现真正的MMIO.
关于设备, 我们在这里不直接采用NEMU的设备模型, 而是为NPC在仿真环境中实现一套与将来流片SoC相近的设备模型. 实现总线后, 我们再来基于总线实现RTL版本的设备. 这时候, AM中IOE抽象的作用就体现出来了: NEMU和NPC的设备地址和设备模型都有所不同, 但经过抽象之后, 它们都可以运行同一份红白机模拟器的源代码, 更多地, AM上的所有程序都不必为不同的运行环境编写不同的代码.
在NPC中运行超级玛丽
大家已经完成了支持RV32IM和外设的NEMU, 并在NEMU上成功运行了超级玛丽. 同样, 我们也可以在RV32E的NPC上运行超级玛丽, 不过这首先需要实现串口和时钟.
我们知道RISC-V处理器通过MMIO访问外设, 例如在NEMU中串口会映射到0xa00003f8
. 类似地, 我们也可以在NPC的仿真环境中实现简单的串口和时钟. 在上一节中提到, 我们通过DPI-C方式让NPC调用读写函数pmem_read()
和pmem_write()
来访问内存. 和NEMU一样, 我们可以在这两个函数中添加若干判断来实现MMIO的功能, 伪代码如下所示:
extern "C" int pmem_read(int raddr) {
// 总是读取地址为`raddr & ~0x3u`的4字节并返回
if (raddr == 时钟地址) { 返回当前时间 }
...
}
extern "C" void pmem_write(int waddr, int wdata, char wmask) {
// 总是往地址为`waddr & ~0x3u`的4字节按写掩码`wmask`写入`wdata`
// `wmask`中每比特表示`wdata`中1个字节的掩码,
// 如`wmask = 0x3`代表只写入最低2个字节, 内存中的其它字节保持不变
if (waddr == 串口地址) { putchar(...) }
...
}
在实现串口和时钟之后, 你可以运行相应的AM程序来测试, 并尝试在NPC上运行字符版本的超级玛丽.
为NPC添加串口和时钟
- 在NPC仿真环境中实现串口的输出功能, 并运行hello程序.
- 在NPC仿真环境中实现时钟, 并运行
am-tests
的real-time clock test
测试. 可以基于系统时间来实现时钟的功能, 在C语言中与系统时间相关的库是什么, 以及如何获取系统时间, 就交给你来STFW了. - 运行字符版本的红白机模拟器.
若你有兴趣还可以仿照NEMU实现VGA, 并在NPC上运行图形版本的超级玛丽.
运行图形版本的超级玛丽
- 在NPC仿真环境中实现VGA外设, 运行video测试.
- 运行图形版本的红白机模拟器.