-
Notifications
You must be signed in to change notification settings - Fork 4
/
interface.py
289 lines (247 loc) · 14 KB
/
interface.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#encoding: utf-8
schematic = """
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ●●●●●●●● ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Clock: ● ┃ ┃┃┃┃┃┃┃┃──┃ Program counter: ●●●● (dec) ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃┃┃┃┃┃┃┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃┃┃┃┃┃┃┃ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Mem. Addr.: ●●●● (dec) ┠──┃┃┃┃┃┃┃┃──┨ "A" Register: ●●●●●●●● (dec) ┃
┗━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┛ ┃┃┃┃┃┃┃┃ ┗━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┓ ┃┃┃┃┃┃┃┃ ┏━━━━━━━━━━━┷━━━━━━━━━┓ ┏━━━━━━━━━━┓
┃ RAM: ●●●●●●●● (dec) ┠──┃┃┃┃┃┃┃┃──┨ ALU: ●●●●●●●● (dec) ┠─┨Flags: ●● ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃┃┃┃┃┃┃┃ ┗━━━━━━━━━━━┯━━━━━━━━━┛ ┃ ZC ┃
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃┃┃┃┃┃┃┃ │ ┗━━━━━━━━━━┛
┃ Instr. Reg: ●●●●●●●● (dec) ┠──┃┃┃┃┃┃┃┃ ┏━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━┓
┃ (asm) ┃ ┃┃┃┃┃┃┃┃──┨ "B" Register: ●●●●●●●● (dec) ┃
┗━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┛ ┃┃┃┃┃┃┃┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┓ ┃┃┃┃┃┃┃┃ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Micro Step: ●●● ●●●●● (dec) ┃ ┃┃┃┃┃┃┃┃──┨ Output: -dec (unsigned) ┃
┃ ROM addr.: ●●●●●●●●●●● (dec) ┠─┐┃┃┃┃┃┃┃┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │ ┃ Control: ●●●●●●●●●●●●●●●● ┃
┃ Memory contents ┃ │ ┃ HMRRIIAAΣSBOCCJF ┃
┠────────────────────────────────┨ └──────────┨ LIIOOIIOOUIIEO I ┃
┃ 00 ┃ ┃ T ┃
┃ 01 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┃ 02 ┃
┃ 03 ┃
┃ 04 ┃ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ 05 ┃ ┃ Keyboard commands ┃
┃ 06 ┃ ┠───────────────────────────────┨
┃ 07 ┃ ┃ Space: start/stop clock ┃
┃ 08 ┃ ┃ →: step clock ┃
┃ 09 ┃ ┃ ←: step clock backwards ┃
┃ 10 ┃ ┃ ↑: increase clock speed ┃
┃ 11 ┃ ┃ ↓: decrease clock speed ┃
┃ 12 ┃ ┃ o: toggle output mode ┃
┃ 13 ┃ ┃ r: reset system ┃
┃ 14 ┃ ┃ Enter: run until next instr. ┃
┃ 15 ┃ ┃ ESC: quit ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
"""
import curses
import sys
from time import time, sleep
import microcode
from assembler import disassemble
def init(stdscr):
"""Perform initialization of the console user interface.
Parameters
----------
stdscr : curses screen
The curses screen object as created by curses.wrapper().
"""
# Check minimal screen size
if curses.LINES < 39 or curses.COLS < 84:
raise RuntimeError('Your terminal window should be at least 39x84.')
# Setup the colors
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK)
curses.init_pair(5, curses.COLOR_YELLOW, curses.COLOR_BLACK)
# Hide the cursor
curses.curs_set(0)
# Clear the screen and draw the schematic as defined at the top of this
# file as a background. We will draw stuff on top of this later in the
# update() function.
stdscr.clear()
stdscr.addstr(0, 0, schematic, curses.color_pair(1))
def update(stdscr, state):
"""Update the console user interface.
Parameters
----------
stdscr : curses screen
The curses screen object as created by curses.wrapper().
state : state
The state of the machine as defined in simulator.py.
"""
def draw_leds(row, col, num, color=2, n=8, dec=True):
"""Draw a row of LEDs displaying a number.
The number is hown in binary and optionally also in decimal.
Parameters
----------
row : int
The row on the screen to draw the LEDs on.
col : int
The column on the screen to draw the LEDs on.
num : int
The number to display using the LEDs
color : int
The color-pair to draw the LEDs in when active. See the color-pairs
defined in the init() function above.
n : int
The number of LEDs to use to display the number. Defaults to 8.
dec : bool
Whether to also display the number in decimal. Defaults to True.
"""
for i in range(n):
led_color = color if (num >> i) & 1 else 1
stdscr.addstr(row, col + n - 1 - i, '●', curses.color_pair(led_color))
if dec:
stdscr.addstr(row, col + n, f' ({num:03d})', curses.color_pair(1))
# Bus
draw_leds(1, 39, num=state.bus, n=8, color=5, dec=False)
# Clock
draw_leds(2, 13, num=state.clock, n=1, color=4, dec=False)
# Program counter
draw_leds(2, 68, num=state.reg_program_counter, n=4, color=3)
# Memory address
draw_leds(5, 17, num=state.reg_memory_address, n=4, color=5)
# "A" register
draw_leds(5, 65, num=state.reg_a, n=8, color=2)
# RAM
draw_leds(8, 17, num=state.memory[state.reg_memory_address], n=8, color=2)
# ALU
draw_leds(8, 56, num=state.alu, n=8, color=2)
# Flags register
draw_leds(8, 81, num=state.reg_flags, n=2, color=3, dec=False)
# Instruction register
draw_leds(11, 17, num=state.reg_instruction >> 4, n=4, color=4)
draw_leds(11, 21, num=state.reg_instruction & 0x0f, n=4, color=5)
# Print the assembler instruction (clear the line first)
stdscr.addstr(12, 17, ' ', curses.color_pair(1))
stdscr.addstr(12, 17, f'({disassemble(state.reg_instruction)})', curses.color_pair(1))
# "B" register
draw_leds(12, 65, num=state.reg_b, n=8, color=2)
# Output register
if state.output_signed_mode:
# Convert 8bit twos-complement number to a Python signed integer
out = state.reg_output
if out & 0x80:
out = (out ^ 0xff) - 1
stdscr.addstr(15, 59, f'{out:04d} (signed) ', curses.color_pair(2))
else:
stdscr.addstr(15, 59, f'{state.reg_output:04d} (unsigned)', curses.color_pair(2))
# Microinstruction step
draw_leds(15, 17, num=state.microinstruction_counter, n=3, color=2, dec=False)
stdscr.addstr(15, 27, f'({state.microinstruction_counter:03d})', curses.color_pair(1))
step = 0b11111
step -= 0b10000 >> state.microinstruction_counter
draw_leds(15, 21, num=step, n=5, color=3, dec=False)
# Microcode EEPROM address
draw_leds(16, 17, num=state.rom_address, n=11, color=5)
# Control lines
draw_leds(18, 60, num=state.control_signals, n=16, color=4, dec=False)
# Memory contents
for address, contents in enumerate(state.memory_human_readable):
color = curses.color_pair(1)
if address == state.reg_program_counter:
color = curses.color_pair(3)
if address == state.reg_memory_address:
color = curses.color_pair(5)
# Blank the line before drawing memory contents
stdscr.addstr(21 + address, 4, ' ', color)
stdscr.addstr(21 + address, 5, contents, color)
# Halt message
if state.control_signals & microcode.HLT:
print_message(stdscr, 'System halted.')
# Do the actual drawing to the screen
stdscr.refresh()
def print_message(stdscr, msg):
stdscr.move(39, 0)
stdscr.clrtoeol()
stdscr.addstr(39, 0, msg, curses.color_pair(1))
stdscr.refresh()
def handle_keypresses(stdscr, simulator):
"""Handle user keypresses.
Parameters
----------
simulator : Simulator
The simulator objet running the show.
"""
try:
c = stdscr.getch()
if c == ord(' '):
simulator.clock_automatic = not simulator.clock_automatic
if simulator.clock_automatic:
print_message(stdscr, 'Started clock.')
else:
print_message(stdscr, 'Stopped clock.')
elif c == curses.KEY_RIGHT:
print_message(stdscr, 'Stepping clock.')
simulator.step()
elif c == curses.KEY_LEFT:
print_message(stdscr, 'Stepping clock backwards.')
simulator.state.revert()
elif c == curses.KEY_UP:
simulator.clock_speed *= 2
print_message(stdscr, f'Increased clock to {simulator.clock_speed} Hz.')
elif c == curses.KEY_DOWN:
simulator.clock_speed /= 2
if simulator.clock_speed < 0:
simulator.clock_speed = 0
print_message(stdscr, f'Decreased clock to {simulator.clock_speed} Hz.')
elif c == ord('\n'):
simulator.step()
while (simulator.state.microinstruction_counter > 0 or not simulator.state.clock) and not simulator.state.control_signals & microcode.HLT:
simulator.step()
print_message(stdscr, 'Stepping clock until we reach next instruction.')
elif c == ord('o'):
simulator.state.output_signed_mode = not simulator.state.output_signed_mode
update(stdscr, simulator.state)
elif c == ord('r'):
simulator.reset()
elif c == 27 or c == ord('q') or c == 3:
sys.exit(0)
except curses.error as e:
# No key pressed
stdscr.move(38, 0)
stdscr.clrtoeol()
stdscr.addstr(38, 0, str(e))
def run_interface(stdscr, simulator):
"""Main function to run the simulator with its console user interface.
Parameters
----------
stdscr : curses screen
The curses screen object as created by curses.wrapper().
simulator : Simulator
The 8-bit breadboard CPU simulator.
"""
init(stdscr)
# Start simulation and UI loop. This loop only terminates when the ESC
# key is pressed, which is detected inside the handle_keypresses()
# function.
while True:
update(stdscr, simulator.state)
if simulator.clock_automatic:
wait_time = (0.5 / simulator.clock_speed) - (time() - simulator.last_clock_time)
if wait_time > 0.1:
curses.halfdelay(int(10 * wait_time))
handle_keypresses(stdscr, simulator)
simulator.step()
else:
curses.nocbreak()
if wait_time > 0:
sleep(wait_time)
curses.nocbreak()
stdscr.nodelay(True)
handle_keypresses(stdscr, simulator)
simulator.step()
else:
curses.cbreak()
handle_keypresses(stdscr, simulator)
# When we reach the end of the program, set the clock to manual
# mode so we don't keep generating useless system states.
if simulator.state.control_signals & microcode.HLT:
simulator.clock_automatic = False
update(stdscr, simulator.state)