Mercurial > repos > simple16
comparison src/cpu.c @ 0:7e44f7d5810b
Initial commit. CPU working well enough for simple hello world program.
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Tue, 22 Mar 2016 22:44:02 -0700 |
parents | |
children | 6204c81e2933 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:7e44f7d5810b |
---|---|
1 #include <stdint.h> | |
2 #include <stdlib.h> | |
3 #include <string.h> | |
4 #include <stdio.h> | |
5 #include "cpu.h" | |
6 | |
7 enum { | |
8 EXCEPTION_INTERRUPT_0, | |
9 EXCEPTION_INTERRUPT_1, | |
10 EXCEPTION_UNALIGNED_READ, | |
11 EXCEPTION_INVALID_INSTRUCTION | |
12 }; | |
13 | |
14 enum { | |
15 STATE_NEED_FETCH, | |
16 STATE_NORMAL, | |
17 STATE_EXCEPTION_START | |
18 }; | |
19 | |
20 #define STATUS_INT0_ENABLE 1 | |
21 #define STATUS_INT1_ENABLE 2 | |
22 #define FLAG_Z 4 | |
23 #define FLAG_C 8 | |
24 #define FLAG_N 16 | |
25 | |
26 #define REG_PC 14 | |
27 #define REG_SR 15 | |
28 | |
29 cpu* alloc_cpu(uint32_t clock_divider, uint32_t num_regions, memory_region *regions) | |
30 { | |
31 size_t alloc_size = sizeof(cpu) + sizeof(memory_region) * num_regions; | |
32 cpu *context = malloc(alloc_size); | |
33 memset(context, 0, alloc_size); | |
34 context->clock_inc = clock_divider; | |
35 context->num_mem_regions = num_regions; | |
36 memcpy(context->mem_regions, regions, num_regions*sizeof(memory_region)); | |
37 | |
38 return context; | |
39 } | |
40 | |
41 uint16_t cpu_read_16(cpu *context, uint16_t address) | |
42 { | |
43 context->cycles += context->clock_inc; | |
44 if (address & 1) { | |
45 context->exception = EXCEPTION_UNALIGNED_READ; | |
46 context->state = STATE_EXCEPTION_START; | |
47 return 0xFFFF; | |
48 } | |
49 memory_region *cur = context->mem_regions; | |
50 for (memory_region *end = cur + context->num_mem_regions; cur < end; cur++) | |
51 { | |
52 if (address >= cur->start && address <= cur->end && (cur->flags & MEM_READ)) { | |
53 return cur->base[address - cur->start] << 8 | cur->base[address - cur->start + 1]; | |
54 } | |
55 } | |
56 return 0xFFFF; | |
57 } | |
58 | |
59 uint8_t cpu_read_8(cpu *context, uint16_t address) | |
60 { | |
61 context->cycles += context->clock_inc; | |
62 memory_region *cur = context->mem_regions; | |
63 for (memory_region *end = cur + context->num_mem_regions; cur < end; cur++) | |
64 { | |
65 if (address >= cur->start && address <= cur->end && (cur->flags & MEM_READ)) { | |
66 return cur->base[address - cur->start]; | |
67 } | |
68 } | |
69 return 0xFF; | |
70 } | |
71 | |
72 void cpu_write_16(cpu *context, uint16_t address, uint16_t value) | |
73 { | |
74 context->cycles += context->clock_inc; | |
75 if (address & 1) { | |
76 context->exception = EXCEPTION_UNALIGNED_READ; | |
77 context->state = STATE_EXCEPTION_START; | |
78 return; | |
79 } | |
80 memory_region *cur = context->mem_regions; | |
81 for (memory_region *end = cur + context->num_mem_regions; cur < end; cur++) | |
82 { | |
83 if (address >= cur->start && address <= cur->end && (cur->flags & MEM_WRITE)) { | |
84 cur->base[address - cur->start] = value >> 8; | |
85 cur->base[address - cur->start + 1] = value; | |
86 break; | |
87 } | |
88 } | |
89 } | |
90 | |
91 void cpu_write_8(cpu *context, uint16_t address, uint8_t value) | |
92 { | |
93 context->cycles += context->clock_inc; | |
94 memory_region *cur = context->mem_regions; | |
95 for (memory_region *end = cur + context->num_mem_regions; cur < end; cur++) | |
96 { | |
97 if (address >= cur->start && address <= cur->end && (cur->flags & MEM_WRITE)) { | |
98 cur->base[address - cur->start] = value; | |
99 break; | |
100 } | |
101 } | |
102 } | |
103 | |
104 uint16_t cpu_read_port(cpu *context, uint8_t port) | |
105 { | |
106 port &= 0xF; | |
107 if (context->port_handlers[port].read) { | |
108 return context->port_handlers[port].read(context, port); | |
109 } | |
110 return 0xFFFF; | |
111 } | |
112 | |
113 void cpu_write_port(cpu *context, uint8_t port, uint16_t value) | |
114 { | |
115 port &= 0xF; | |
116 if (context->port_handlers[port].write) { | |
117 context->port_handlers[port].write(context, port, value); | |
118 } | |
119 } | |
120 | |
121 void fetch_instruction(cpu *context) | |
122 { | |
123 context->prefetch = cpu_read_16(context, context->regs[REG_PC]); | |
124 context->regs[REG_PC] += 2; | |
125 context->state = STATE_NORMAL; | |
126 } | |
127 | |
128 void vector_fetch(cpu *context) | |
129 { | |
130 context->exception_pc = context->regs[REG_PC] - 2; | |
131 context->exception_sr = context->regs[REG_SR]; | |
132 context->regs[REG_SR] &= ~(STATUS_INT0_ENABLE | STATUS_INT1_ENABLE); | |
133 context->regs[REG_PC] = cpu_read_16(context, context->vector_base + context->exception); | |
134 context->state = STATE_NEED_FETCH; | |
135 } | |
136 | |
137 uint16_t sign_extend(uint16_t val) | |
138 { | |
139 if (val & 0x80) { | |
140 return val | 0xFF00; | |
141 } | |
142 return val; | |
143 } | |
144 | |
145 void update_flags_arith(cpu *context, uint32_t result) | |
146 { | |
147 context->regs[REG_SR] &= ~(FLAG_N|FLAG_C|FLAG_Z); | |
148 if (!(result & 0xFFFF)) { | |
149 context->regs[REG_SR] |= FLAG_Z; | |
150 } | |
151 if (result &= 0x8000) { | |
152 context->regs[REG_SR] |= FLAG_N; | |
153 } | |
154 if (result &= 0x10000) { | |
155 context->regs[REG_SR] |= FLAG_C; | |
156 } | |
157 } | |
158 | |
159 void update_flags_bitwise(cpu *context, uint32_t result) | |
160 { | |
161 context->regs[REG_SR] &= ~(FLAG_N|FLAG_Z); | |
162 if (!(result & 0xFFFF)) { | |
163 context->regs[REG_SR] |= FLAG_Z; | |
164 } | |
165 if (result &= 0x8000) { | |
166 context->regs[REG_SR] |= FLAG_N; | |
167 } | |
168 } | |
169 | |
170 void run_bcc(cpu *context, uint8_t condition, uint8_t a, uint8_t b) | |
171 { | |
172 | |
173 uint8_t doit = 0; | |
174 switch (condition) | |
175 { | |
176 case COND_ALWAYS: | |
177 doit = 1; | |
178 break; | |
179 case COND_NEVER: | |
180 break; | |
181 case COND_ZERO: | |
182 doit = context->regs[REG_SR] & FLAG_Z; | |
183 break; | |
184 case COND_NZERO: | |
185 doit = !(context->regs[REG_SR] & FLAG_Z); | |
186 break; | |
187 case COND_NEG: | |
188 doit = context->regs[REG_SR] & FLAG_N; | |
189 break; | |
190 case COND_POS: | |
191 doit = !(context->regs[REG_SR] & FLAG_N); | |
192 break; | |
193 case COND_CARRY: | |
194 doit = context->regs[REG_SR] & FLAG_C; | |
195 break; | |
196 case COND_NCARRY: | |
197 doit = context->regs[REG_SR] & FLAG_C; | |
198 break; | |
199 case COND_GREATER: | |
200 //not zero and not carry | |
201 doit = !(context->regs[REG_SR] & FLAG_Z) || !(context->regs[REG_SR] & FLAG_C); | |
202 break; | |
203 case COND_LEQ: | |
204 //zero or carry | |
205 doit = (context->regs[REG_SR] & FLAG_Z) || (context->regs[REG_SR] & FLAG_C); | |
206 break; | |
207 default: | |
208 context->exception = EXCEPTION_INVALID_INSTRUCTION; | |
209 context->state = STATE_EXCEPTION_START; | |
210 return; | |
211 } | |
212 | |
213 if (doit) { | |
214 context->regs[REG_PC] += sign_extend(a << 4 | b) * 2; | |
215 context->state = STATE_NEED_FETCH; | |
216 } | |
217 } | |
218 | |
219 uint16_t format_immediate_bitwise(uint16_t val) | |
220 { | |
221 if (val & 8) { | |
222 val |= 0xFFF0; | |
223 } | |
224 return val; | |
225 } | |
226 | |
227 uint16_t format_immediate(uint16_t val) | |
228 { | |
229 val = format_immediate_bitwise(val); | |
230 if (!val) { | |
231 val = 8; | |
232 } | |
233 return val; | |
234 } | |
235 | |
236 void run_single_reg(cpu *context, uint8_t dst, uint8_t op) | |
237 { | |
238 switch(op) | |
239 { | |
240 case RETI: | |
241 context->regs[dst] = context->exception_ur; | |
242 context->regs[REG_PC] = context->exception_pc; | |
243 context->regs[REG_SR] = context->exception_sr; | |
244 context->state = STATE_NEED_FETCH; | |
245 return; | |
246 case TRAP: | |
247 context->state = STATE_EXCEPTION_START; | |
248 context->exception = context->regs[dst]; | |
249 return; | |
250 case TRAPI: | |
251 context->state = STATE_EXCEPTION_START; | |
252 context->exception = dst; | |
253 return; | |
254 case GETEPC: | |
255 context->regs[dst] = context->exception_pc; | |
256 break; | |
257 case SETEPC: | |
258 context->exception_pc = context->regs[dst]; | |
259 break; | |
260 case GETESR: | |
261 context->regs[dst] = context->exception_sr; | |
262 break; | |
263 case SETESR: | |
264 context->exception_sr = context->regs[dst]; | |
265 break; | |
266 case GETEUR: | |
267 context->regs[dst] = context->exception_ur; | |
268 break; | |
269 case SETEUR: | |
270 context->exception_ur = context->regs[dst]; | |
271 break; | |
272 case GETENUM: | |
273 context->regs[dst] = context->exception; | |
274 break; | |
275 case SETENUM: | |
276 context->exception = context->regs[dst]; | |
277 break; | |
278 default: | |
279 context->state = STATE_EXCEPTION_START; | |
280 context->exception = EXCEPTION_INVALID_INSTRUCTION; | |
281 return; | |
282 } | |
283 if (dst == REG_PC) { | |
284 context->state = STATE_NEED_FETCH; | |
285 } | |
286 } | |
287 | |
288 void run_single_source(cpu *context, uint8_t dst, uint8_t a, uint8_t op) | |
289 { | |
290 uint32_t tmp; | |
291 uint8_t shift; | |
292 switch(op) | |
293 { | |
294 case MOVE: | |
295 context->regs[dst] = context->regs[a]; | |
296 break; | |
297 case NEG: | |
298 tmp = -context->regs[a]; | |
299 context->regs[dst] = tmp; | |
300 update_flags_arith(context, tmp); | |
301 break; | |
302 case NOT: | |
303 context->regs[dst] = ~context->regs[a]; | |
304 update_flags_bitwise(context, context->regs[dst]); | |
305 break; | |
306 case CMP: | |
307 tmp = context->regs[dst] - context->regs[a]; | |
308 update_flags_arith(context, tmp); | |
309 return; | |
310 case CALL: | |
311 context->regs[dst] = context->regs[REG_PC] - 2; | |
312 context->regs[REG_PC] = context->regs[a]; | |
313 context->state = STATE_NEED_FETCH; | |
314 return; | |
315 case SWAP: | |
316 tmp = context->regs[dst]; | |
317 context->regs[dst] = context->regs[a]; | |
318 context->regs[a] = tmp; | |
319 if (a == REG_PC) { | |
320 context->state = STATE_NEED_FETCH; | |
321 return; | |
322 } | |
323 break; | |
324 case IN: | |
325 context->regs[dst] = cpu_read_port(context, context->regs[a]); | |
326 break; | |
327 case OUT: | |
328 cpu_write_port(context, context->regs[a], context->regs[dst]); | |
329 return; | |
330 case INI: | |
331 context->regs[dst] = cpu_read_port(context, a); | |
332 break; | |
333 case OUTI: | |
334 cpu_write_port(context, a, context->regs[dst]); | |
335 return; | |
336 case ADDI: | |
337 tmp = context->regs[dst] + format_immediate(a); | |
338 context->regs[dst] = tmp; | |
339 update_flags_arith(context, tmp); | |
340 break; | |
341 case ANDI: | |
342 context->regs[dst] = context->regs[dst] & format_immediate_bitwise(a); | |
343 update_flags_bitwise(context, context->regs[dst]); | |
344 break; | |
345 case ORI: | |
346 context->regs[dst] = context->regs[dst] | format_immediate_bitwise(a); | |
347 update_flags_bitwise(context, context->regs[dst]); | |
348 break; | |
349 case LSI: | |
350 shift = a & 7; | |
351 if (!shift) { | |
352 shift = 8; | |
353 } | |
354 if (a & 8) { | |
355 tmp = context->regs[dst] >> shift; | |
356 tmp |= (context->regs[dst] >> (shift - 1)) << 16 & 0x10000; | |
357 } else { | |
358 tmp = context->regs[dst] << (a & 7); | |
359 } | |
360 context->regs[dst] = tmp; | |
361 update_flags_arith(context, tmp); | |
362 break; | |
363 case ASRI: | |
364 shift = a; | |
365 if (!shift) { | |
366 shift = 16; | |
367 } | |
368 tmp = context->regs[dst]; | |
369 if (tmp & 0x8000) { | |
370 tmp |= 0xFFFF0000; | |
371 } | |
372 tmp = tmp >> shift & 0xFFFF; | |
373 tmp |= (context->regs[dst] >> (context->regs[shift] - 1)) << 16 & 0x10000; | |
374 context->regs[dst] = tmp; | |
375 update_flags_arith(context, tmp); | |
376 break; | |
377 case SINGLE_REG: | |
378 run_single_reg(context, dst, a); | |
379 return; | |
380 } | |
381 if (dst == REG_PC) { | |
382 context->state = STATE_NEED_FETCH; | |
383 } | |
384 } | |
385 | |
386 char * mnemonics[] = { | |
387 "ldim", "ldimh", "ld8", "ld16", "str8", "str16", "add", "adc", "and", "or", "xor", "lsl", "lsr", "asr", "bcc", "single" | |
388 }; | |
389 | |
390 void run_instruction(cpu *context) | |
391 { | |
392 uint16_t instruction = context->prefetch; | |
393 fetch_instruction(context); | |
394 uint8_t dst = instruction >> 12; | |
395 uint8_t a = instruction >> 8 & 0xF; | |
396 uint8_t b = instruction >> 4 & 0xF; | |
397 uint8_t op = instruction & 0xF; | |
398 uint32_t tmp; | |
399 switch (op) | |
400 { | |
401 case LDIM: | |
402 context->regs[dst] = sign_extend(a << 4 | b); | |
403 break; | |
404 case LDIMH: | |
405 context->regs[dst] &= 0xFF; | |
406 context->regs[dst] |= a << 12 | b << 8; | |
407 break; | |
408 case LD8: | |
409 context->regs[dst] = cpu_read_8(context, context->regs[a] + context->regs[b]); | |
410 break; | |
411 case LD16: | |
412 context->regs[dst] = cpu_read_16(context, context->regs[a] + context->regs[b]); | |
413 break; | |
414 case STR8: | |
415 cpu_write_8(context, context->regs[a] + context->regs[b], context->regs[dst]); | |
416 return; | |
417 case STR16: | |
418 cpu_write_16(context, context->regs[a] + context->regs[b], context->regs[dst]); | |
419 return; | |
420 case ADD: | |
421 tmp = context->regs[a] + context->regs[b]; | |
422 context->regs[dst] = tmp; | |
423 update_flags_arith(context, tmp); | |
424 break; | |
425 case ADC: | |
426 tmp = context->regs[a] + context->regs[b] + (context->regs[REG_SR] & FLAG_C ? 1 : 0); | |
427 context->regs[dst] = tmp; | |
428 update_flags_arith(context, tmp); | |
429 break; | |
430 case AND: | |
431 context->regs[dst] = context->regs[a] & context->regs[b]; | |
432 update_flags_bitwise(context, context->regs[dst]); | |
433 break; | |
434 case OR: | |
435 context->regs[dst] = context->regs[a] | context->regs[b]; | |
436 update_flags_bitwise(context, context->regs[dst]); | |
437 break; | |
438 case XOR: | |
439 context->regs[dst] = context->regs[a] ^ context->regs[b]; | |
440 update_flags_bitwise(context, context->regs[dst]); | |
441 break; | |
442 case LSL: | |
443 tmp = context->regs[a] << context->regs[b]; | |
444 context->regs[dst] = tmp; | |
445 update_flags_arith(context, tmp); | |
446 break; | |
447 case LSR: | |
448 tmp = context->regs[a] >> context->regs[b]; | |
449 tmp |= (context->regs[a] >> (context->regs[b] - 1)) << 16 & 0x10000; | |
450 context->regs[dst] = tmp; | |
451 update_flags_arith(context, tmp); | |
452 break; | |
453 case ASR: | |
454 tmp = context->regs[a]; | |
455 if (tmp & 0x8000) { | |
456 tmp |= 0xFFFF0000; | |
457 } | |
458 tmp = tmp >> context->regs[b] & 0xFFFF; | |
459 tmp |= (context->regs[a] >> (context->regs[b] - 1)) << 16 & 0x10000; | |
460 context->regs[dst] = tmp; | |
461 update_flags_arith(context, tmp); | |
462 break; | |
463 case BCC: | |
464 run_bcc(context, dst, a, b); | |
465 return; | |
466 case SINGLE_SOURCE: | |
467 run_single_source(context, dst, a, b); | |
468 return; | |
469 } | |
470 if (dst == REG_PC) { | |
471 context->state = STATE_NEED_FETCH; | |
472 } | |
473 } | |
474 | |
475 void run_cpu(cpu *context, uint32_t target_cycle) | |
476 { | |
477 while (context->cycles < target_cycle) | |
478 { | |
479 switch (context->state) | |
480 { | |
481 case STATE_NEED_FETCH: | |
482 fetch_instruction(context); | |
483 break; | |
484 case STATE_NORMAL: | |
485 run_instruction(context); | |
486 break; | |
487 case STATE_EXCEPTION_START: | |
488 vector_fetch(context); | |
489 break; | |
490 } | |
491 } | |
492 } |