我们之前介绍的处理器/模拟器都是计算模型
真实的计算机如何与物理世界交互?
本次课内容:
你的NEMU/NPC只能跑cpu-tests, 具体地, 只能
连程序结束都要靠运行时环境
虽然理论上已经能解决所有可计算问题
hanoi(64, 'A', 'B', 'C')
如果要输出 “Hello World”, 还需要什么?
用户需要通过设备来使用计算机
设备 = 电气部分 + 数字部分(设备控制器)
控制设备工作的部件
可以看成一个将处理器发出的二进制命令翻译成电气信号的部件
这块主板很老了, 还没有PCI-e(2003年)
把芯片的端口信号引出来
版图 ===生产=> 裸片 ===封装=> 芯片
芯片引脚上的信号通过板卡走线连到设备
老主板通常配备芯片组: 北桥, 南桥
现在芯片集成度大幅提升
与CPU相关的3个重要功能:
还可能有很多其他部件:
对CPU来说, 这些寄存器就是设备功能的抽象
Xilinx UART 16550 v2.0 LogiCORE IP Product Guide
开发者(例如SoC工程师)只需RTFM即可开发底层软件, 无需了解设备的电气特性
为了让CPU指定访问哪个设备寄存器, 需要给它们编号
回顾: 在满足程序的可观测行为(observable behavior of the program, C99 5.1.2.3节第6点)一致性的前提下, 编译器可以进行任意优化
新问题: 访问设备会改变其状态, 从而影响程序行为
只好把锅甩给程序员了:
volatile
标识,
告诉编译器这个访问要严格执行
如何从状态机模型理解输入输出?
挑战: 设备的状态受物理世界的影响, 难以建模
折中: 通过引入状态机的不确定性, 仅对I/O指令的行为进行建模:
老规矩, 加个抽象层: IOE (I/O Extension)
// abstract-machine/am/include/amdev.h
AM_DEVREG( 1, UART_CONFIG, RD, bool present);
AM_DEVREG( 2, UART_TX, WR, char data);
AM_DEVREG( 3, UART_RX, RD, char data);
AM_DEVREG( 4, TIMER_CONFIG, RD, bool present, has_rtc);
AM_DEVREG( 5, TIMER_RTC, RD, int year, month, day, hour, minute, second);
AM_DEVREG( 6, TIMER_UPTIME, RD, uint64_t us);
AM_DEVREG( 7, INPUT_CONFIG, RD, bool present);
AM_DEVREG( 8, INPUT_KEYBRD, RD, bool keydown; int keycode);
AM_DEVREG( 9, GPU_CONFIG, RD, bool present, has_accel; int width, height, vmemsz);
AM_DEVREG(10, GPU_STATUS, RD, bool ready);
AM_DEVREG(11, GPU_FBDRAW, WR, int x, y; void *pixels; int w, h; bool sync);
AM_DEVREG(12, GPU_MEMCPY, WR, uint32_t dest; void *src; int size);
AM_DEVREG(13, GPU_RENDER, WR, uint32_t root);
AM_DEVREG(14, AUDIO_CONFIG, RD, bool present; int bufsize);
AM_DEVREG(15, AUDIO_CTRL, WR, int freq, channels, samples);
AM_DEVREG(16, AUDIO_STATUS, RD, int count);
AM_DEVREG(17, AUDIO_PLAY, WR, Area buf);
AM_DEVREG(18, DISK_CONFIG, RD, bool present; int blksz, blkcnt);
AM_DEVREG(19, DISK_STATUS, RD, bool ready);
AM_DEVREG(20, DISK_BLKIO, WR, bool write; void *buf; int blkno, blkcnt);
AM_DEVREG(21, NET_CONFIG, RD, bool present);
AM_DEVREG(22, NET_STATUS, RD, int rx_len, tx_len);
AM_DEVREG(23, NET_TX, WR, Area buf);
AM_DEVREG(24, NET_RX, WR, Area buf);
AM上的程序通过ioe_write(UART_TX, &data);
往串口写入数据
最简单的设备, 可传输1bit数据, 如拨码开关/LED灯
但由于在模拟/仿真环境中不便使用, AM的IOE未提供GPIO的抽象
简单的设备, 可双向传输字符数据
RS232接口很少见了, 一般在板卡上放一个转换芯片出USB接口
AM的IOE屏蔽大量细节,
只提供UART_TX
和UART_RX
两个抽象寄存器
很简单的输入设备, 本质是一个靠时钟信号驱动的计数器
AM的IOE只提供TIMER_RTC
和TIMER_UPTIME
两个抽象寄存器
常见的输入设备, 通过电容变化识别按键情况
AM的IOE只提供KEYBRD
抽象寄存器
KEY_NONE
, 省掉状态寄存器常见的输出设备, 将像素编码转换为RGB模拟信号在屏幕上显示
AM的IOE提供5个抽象寄存器, 目前只使用其中两个
GPU_CONFIG
- 可读出屏幕大小GPU_FBDRAW
- 往显存写入矩形区域的像素信息实现刚才的IOE API就可以运行游戏!
在x86-nemu上运行超级玛丽
第三期 “一生一芯”低配版超级玛丽: 将不同颜色的像素转换成字符
用RTL实现设备的工作量并不小: 总线协议 + 设备控制器
不过如果只是想让CPU跑游戏, 让仿真环境支持一下就可以了
有同学一上来写流水线, 但只能跑几条指令或几个小程序
更科学的方式是写单元测试, 但大部分同学都不愿意去写的 😂
while (1) {
wait_for_next_frame(); // TIMER_UPTIME
process_key(); // KEYBRD
process_game_logic(); // TRM
update_screen(); // GPU_FBDRAW
}
这些真的可以通过TRM + IOE来实现!
GPU - 专业渲染, 让CPU专注计算