from myhdl import Signal, Simulation, StopSimulation, always, always_comb, always_seq, delay, enum, intbv, traceSignals # self-explanatory constants HIGH = 1 LOW = 0 ops_map = { '+': 'INC', '-': 'DEC', '>': 'INC_DP', '<': 'DEC_DP', ',': 'READ', '.': 'WRITE', '[': 'OPEN_LOOP', ']': 'CLOSE_LOOP' } valid_ops = ops_map.keys() ops = enum(*(ops_map.values())) def bf_to_native(program): ''' 'program' is a string. Any non-BF characters are pruned. ''' return [ops.__getattribute__(ops_map[x]) for x in filter(lambda x: x in valid_ops, program)] # BF spec requires 30000 bytes of data memory bf_data = [0 for i in xrange(30000)] # Our program - "Hello World!\n" to output bf_program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++." bf_program = bf_to_native(bf_program) bf_input, bf_output = [], [] def clock_driver(clk, we): ''' Symmetrical clock signal, high then low then repeat, update/change every iteration of the sim. We also set we (write enable) low when the clock goes low, regardless of whether it's already high or not. ''' @always(delay(1)) def _drive_clock(): if clk: we.next = LOW clk.next = not clk return _drive_clock def read_data(dp, dbus): @always_comb def _read_data(): dbus.next = bf_data[dp] return _read_data def write_data(dp, dbus, we): ''' Write data byte to memory at DP whenever the write enable signal goes from high to low. ''' @always_seq(we.negedge, reset=None) def _write_data(): bf_data[dp] = int(dbus.val) # Need to get a COPY of the data, not a ref! (int() usage) return _write_data def instruction_advance(ip, sd, clk): ''' Update the instruction pointer on every falling clock edge. ''' @always_seq(clk.negedge, reset=None) def _advance_instruction(): if sd: ip.next = ip + 1 else: ip.next = ip - 1 return _advance_instruction def instruction_read(ip, ibus): ''' Read instruction from program at instruction pointer onto ibus. Stop ("exit") if we've reached the end of the program. ''' @always_comb def _read_instruction(): if ip == len(bf_program): raise StopSimulation ibus.next = bf_program[ip] return _read_instruction def process(ip, ibus, dp, dbus, we, se, sd, sa, clk): ''' Meat. Execute the instruction on ibus. ''' @always_seq(clk.posedge, reset=None) def _process(): if ibus == ops.OPEN_LOOP: if not se and 0 == dbus: # New forward scan se.next = HIGH sd.next = HIGH sa.next = 1 elif se: # Existing scan if sd: # Forwards sa.next = sa + 1 else: sa.next = sa - 1 if 1 == sa: # This is the droid you are looking for se.next = LOW # For finalizing backward scans, we need to reset SD # so that regular instruction advancing moves *forwards* sd.next = HIGH elif ibus == ops.CLOSE_LOOP: if not se and 0 != dbus: # New backward scan se.next = HIGH sd.next = LOW sa.next = 1 elif se: # Existing scan if not sd: # Backwards sa.next = sa + 1 else: sa.next = sa - 1 if 1 == sa: # This is the droid you are looking for se.next = LOW elif not se: if ibus == ops.INC: dbus.next = dbus + 1 we.next = HIGH elif ibus == ops.DEC: dbus.next = dbus - 1 we.next = HIGH elif ibus == ops.INC_DP: dp.next = dp + 1 elif ibus == ops.DEC_DP: dp.next = dp - 1 elif ibus == ops.READ: dbus.next = ord(bf_input.pop(0)) # consume head of input array we.next = HIGH elif ibus == ops.WRITE: bf_output.append(int(dbus.val)) return _process def cpu(): # Boolean signals clk, se, we = [Signal(bool(0)) for i in range(3)] sd = Signal(bool(1)) # Byte signals ip, dp, sa = [Signal(0) for i in range(3)] # Buses ibus = Signal(bf_program[0]) dbus = Signal(intbv(bf_data[0], min=0, max=255)) # Instantiate all our hardware components... clock = clock_driver(clk, we) data_reader = read_data(dp, dbus) data_writer = write_data(dp, dbus, we) op_advancer = instruction_advance(ip, sd, clk) op_reader = instruction_read(ip, ibus) op_process = process(ip, ibus, dp, dbus, we, se, sd, sa, clk) return clock, data_reader, data_writer, op_advancer, op_reader, op_process cpu_instance = traceSignals(cpu) Simulation(cpu_instance).run() # This is pure high-level python so we get/see human-readable output (ASCII bytes) print(''.join(chr(x) for x in bf_output[:12]))