HW1 和 Lab1 已发布。
假设你已经熟练使用各种 C 语言机制 (并没有)
本次课程
IOCCC'11 best self documenting program
puts(usage: calculator 11/26+222/31
+~~~~~~~~~~~~~~~~~~~~~~~~calculator-\
! 7.584,367 )
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
! clear ! 0 ||l -x l tan I (/) |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
! 1 | 2 | 3 ||l 1/x l cos I (*) |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
! 4 | 5 | 6 ||l exp l sqrt I (+) |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
! 7 | 8 | 9 ||l sin l log I (-) |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(0
);
人类不可读版 (STFW: clockwise/spiral rule)
void (*signal(int sig, void (*func)(int)))(int);
人类可读版
typedef void (*sighandler_t)(int);
sighandler_t signal(int, sighandler_t);
Programs are meant to be read by humans and only incidentally for computers to execute. — D. E. Knuth
(程序首先是拿给人读的,其次才是被机器执行。)
宏观
微观
假想的数字逻辑电路
X
, Y
, ...)你会如何设计?
int X = 0, Y = 0;
X1 = !X && Y;
Y1 = !X && !Y;
X = X1; Y = Y1;
#define FORALL_REGS(_) _(X) _(Y)
#define LOGIC X1 = !X && Y; \
Y1 = !X && !Y;
#define DEFINE(X) static int X, X##1;
#define UPDATE(X) X = X##1;
#define PRINT(X) printf(#X " = %d; ", X);
int main() {
FORALL_REGS(DEFINE);
while (1) { // clock
FORALL_REGS(PRINT); putchar('\n'); sleep(1);
LOGIC;
FORALL_REGS(UPDATE);
}
}
Good
Bad
等等……FPGA?
存储系统
寄存器: PC, R0 (RA), R1, R2, R3 (8-bit)
内存: 16字节 (按字节访问)
指令集
7 6 5 4 3 2 1 0
mov [0 0 0 0] [ rt] [ rs]
add [0 0 0 1] [ rt] [ rs]
load [1 1 1 0] [ addr ]
store [1 1 1 1] [ addr ]
有 “计算机系统” 的感觉了?
存储模型:内存 + 寄存器 (包含 PC)
理论上,任何计算机系统都是这样的
#include <stdint.h>
#define NREG 4
#define NMEM 16
typedef uint8_t u8; // 没用过 uint8_t?
u8 pc = 0, R[NREG], M[NMEM] = { ... };
uint8_t == unsigned char
unsigned int
避免潜在的 UB-fwrapv
可以强制有符号整数溢出为 wraparound给寄存器名字?
#define NREG 4
u8 R[NREG], pc; // 有些指令是用寄存器名描述的
#define RA 1 // BUG: 数组下标从0开始
...
enum { RA, R1, ..., PC };
u8 R[] = {
[RA] = 0, // 这是什么语法??
[R1] = 0,
...
[PC] = init_pc,
};
#define pc (R[PC]) // 把 PC 也作为寄存器的一部分
#define NREG (sizeof(R) / sizeof(u8))
软件里有很多隐藏的 dependencies (一些额外的、代码中没有体现和约束的 “规则”)
NREG
...)// breaks when adding a register
#define NREG 5 // 隐藏假设max{RA, RB, ... PC} == (NREG - 1)
// breaks when changing register size
#define NREG (sizeof(R) / sizeof(u8)) // 隐藏假设寄存器是8-bit
// never breaks
#define NREG (sizeof(R) / sizeof(R[0])) // 但需要R的定义
// even better (why?)
enum { RA, ... , PC, NREG }
CPU_state
struct CPU_state {
};
// C is not C++
// cannot declare "CPU_state state";
#define reg_l(index) (cpu.gpr[check_reg_index(index)]._32)
#define reg_w(index) (cpu.gpr[check_reg_index(index)]._16)
#define reg_b(index) (cpu.gpr[check_reg_index(index) & 0x3]._8[index >> 2])
对于复杂的情况,struct/union 是更好的设计
check_reg_index
)?在时钟信号驱动下,根据 $(M,R)$ 更新系统的状态
RISC 处理器 (以及实际的 CISC 处理器实现):
M[R[PC]]
的一条指令最重要的就是实现 idex()
int main() {
while (!is_halt(M[pc])) {
idex();
}
}
void idex() {
if ((M[pc] >> 4) == 0) {
R[(M[pc] >> 2) & 3] = R[M[pc] & 3];
pc++;
} else if ((M[pc] >> 4) == 1) {
R[(M[pc] >> 2) & 3] += R[M[pc] & 3];
pc++;
} else if ((M[pc] >> 4) == 14) {
R[0] = M[M[pc] & 0xf];
pc++;
} else if ((M[pc] >> 4) == 15) {
M[M[pc] & 0xf] = R[0];
pc++;
}
}
是否好一些?
void idex() {
u8 inst = M[pc++];
u8 op = inst >> 4;
if (op == 0x0 || op == 0x1) {
int rt = (inst >> 2) & 3, rs = (inst & 3);
if (op == 0x0) R[rt] = R[rs];
else if (op == 0x1) R[rt] += R[rs];
}
if (op == 0xe || op == 0xf) {
int addr = inst & 0xf;
if (op == 0xe) R[0] = M[addr];
else if (op == 0xf) M[addr] = R[0];
}
}
typedef union inst {
struct { u8 rs : 2, rt: 2, op: 4; } rtype;
struct { u8 addr: 4, op: 4; } mtype;
} inst_t;
#define RTYPE(i) u8 rt = (i)->rtype.rt, rs = (i)->rtype.rs;
#define MTYPE(i) u8 addr = (i)->mtype.addr;
void idex() {
inst_t *cur = (inst_t *)&M[pc];
switch (cur->rtype.op) {
case 0b0000: { RTYPE(cur); R[rt] = R[rs]; pc++; break; }
case 0b0001: { RTYPE(cur); R[rt] += R[rs]; pc++; break; }
case 0b1110: { MTYPE(cur); R[RA] = M[addr]; pc++; break; }
case 0b1111: { MTYPE(cur); M[addr] = R[RA]; pc++; break; }
default: panic("invalid instruction at PC = %x", pc);
}
}
Union / bit fields
typedef union inst {
struct { u8 rs : 2, rt: 2, op: 4; } rtype;
struct { u8 addr: 4, op: 4; } mtype;
} inst_t;
指针
inst_t *cur = (inst_t *)&M[pc];
// cur->rtype.op
// cur->mtype.addr
// ...
如何管理 “更大” 的项目 (YEMU)?
yemu.h
- 寄存器名;必要的声明yemu.c
- 数据定义、主函数idex.c
- 译码执行Makefile
- 编译脚本 (能实现增量编译)enum { RA, ... , NREG }
inst_t *cur = (inst_t *)&M[pc]
am-kernels/litenes
fceux-am
src/boards
)QEMU
永远不要停止对好代码的追求
再编下去就要单身一辈子了