Bladeren bron

Interrupts and Timing

Changed timing logic, now clock cycles sectioned in epochs and each are
epochs are delayed by OS reducing cpu usage.
Added interrupt support and GPIO and simulation of unbiased circuit.
Min 4 jaren geleden
bovenliggende
commit
c88a11ae10

+ 7 - 4
emulator/Makefile

@@ -8,17 +8,17 @@ all: MSP430 SERVER _msp430emu.so
 
 MSP430 : main.o utilities.o emu_server.o registers.o memspace.o debugger.o disassembler.o \
 	register_display.o decoder.o flag_handler.o formatI.o formatII.o formatIII.o \
-	usci.o port1.o packet_queue.o bcm.o timer_a.o
-	g++ -o MSP430 launcher.o emu_server.o utilities.o registers.o memspace.o debugger.o disassembler.o \
+	usci.o port1.o packet_queue.o bcm.o timer_a.o interrupts.o
+	g++ -o MSP430 launcher.o emu_server.o utilities.o registers.o memspace.o debugger.o disassembler.o interrupts.o \
 	register_display.o decoder.o flag_handler.o formatI.o formatII.o formatIII.o usci.o port1.o bcm.o timer_a.o packet_queue.o \
 	-lreadline -lwebsockets -lpthread -lrt -lssl -lcrypto;
 
 _msp430emu.so: py_functions.o py_interface.o utilities.o registers.o memspace.o debugger.o disassembler.o \
 	register_display.o decoder.o flag_handler.o formatI.o formatII.o formatIII.o \
-	usci.o port1.o bcm.o timer_a.o
+	usci.o port1.o bcm.o timer_a.o interrupts.o
 	$(CC) $(LLFLAGS) py_functions.o py_interface.o utilities.o registers.o memspace.o debugger.o disassembler.o \
 	register_display.o decoder.o flag_handler.o formatI.o formatII.o formatIII.o usci.o port1.o bcm.o timer_a.o \
-	-o _msp430emu.so -shared
+	interrupts.o -o _msp430emu.so -shared
 
 
 main.o : main.c
@@ -63,6 +63,9 @@ formatII.o : devices/cpu/formatII.c
 formatIII.o : devices/cpu/formatIII.c
 	$(CC) $(CCFLAGS) -c devices/cpu/formatIII.c
 
+interrupts.o : devices/cpu/interrupts.c
+	$(CC) $(CCFLAGS) -c devices/cpu/interrupts.c
+
 bcm.o : devices/peripherals/bcm.c
 	$(CC) $(CCFLAGS) -c devices/peripherals/bcm.c
 

+ 4 - 7
emulator/debugger/debugger.c

@@ -96,21 +96,18 @@ bool exec_cmd (Emulator *emu, char *line, int len)
   // Display disassembly of N at HEX_ADDR: dis [N] [HEX_ADDR] //
   else if ( !strncasecmp("disas", cmd, sizeof "disas") ||
 	    !strncasecmp("dis", cmd, sizeof "dis") ||
-	    !strncasecmp("disassemble", cmd, sizeof "disassemble")) 
-    {
+	    !strncasecmp("disassemble", cmd, sizeof "disassemble")) {
       uint16_t start_addr = cpu->pc;
       uint32_t num = 10; 
       
       ops = sscanf(line, "%s %u %X", bogus1, &bogus2, &bogus3);
       
       if (ops == 2) {
-	sscanf(line, "%s %u", bogus1, &num);
+	    sscanf(line, "%s %u", bogus1, &num);
       }
       else if (ops == 3) {
-	sscanf(line, "%s %u %X", bogus1, &num, 
-		     (unsigned int *)&start_addr);
+	    sscanf(line, "%s %u %X", bogus1, &num, (unsigned int *)&start_addr);
       }
-      
       disassemble(emu, start_addr, num);
     }
 
@@ -124,7 +121,7 @@ bool exec_cmd (Emulator *emu, char *line, int len)
       
       // Is it a direct address or an adress in a register being spec'd 
       if (str[0] >= '0' && str[0] <= '9') {
-	sscanf(str, "%X", (unsigned int *) &start_addr);
+	    sscanf(str, "%X", (unsigned int *) &start_addr);
       }
       else if (str[0] == '%' || str[0] == 'r' || str[0] == 'R')
       {

+ 20 - 6
emulator/devices/cpu/formatI.c

@@ -668,14 +668,24 @@ void decode_formatI(Emulator *emu, uint16_t instruction, bool disassemble)
        * No status bits affected
        */
       case 0xC:{
-
         if (bw_flag == EMU_WORD) {
-          *destination_addr &= (uint16_t) ~source_value;
+          // If calling __bic_SR_register_on_exit
+          if (destination_offset == 0x0023) {
+            uint16_t sr = sr_to_value(emu);
+            set_sr_value(emu, sr & ~source_value);
+          } else {
+            *destination_addr &= (uint16_t) ~source_value;
+          }
         }
         else if (bw_flag == EMU_BYTE) {
-          *(uint8_t *) destination_addr &= (uint8_t) ~source_value;	
+          // If calling __bic_SR_register_on_exit
+          if (destination_offset == 0x0023) {
+            uint16_t sr = sr_to_value(emu);
+            set_sr_value(emu, sr & (~source_value | 0xFF00));
+          } else {
+            *(uint8_t *) destination_addr &= (uint8_t) ~source_value;
+          }
         }
-        
         break;
       }
 
@@ -683,8 +693,12 @@ void decode_formatI(Emulator *emu, uint16_t instruction, bool disassemble)
        *
        */
       case 0xD:{
-
-        if (bw_flag == EMU_WORD) {
+        // If calling __bis_SR_register
+        if (destination_offset == 0x0023) {
+            uint16_t sr = sr_to_value(emu);
+            set_sr_value(emu, sr | source_value);
+        }
+        else if (bw_flag == EMU_WORD) {
           *destination_addr |= (uint16_t) source_value;
         }
         else if (bw_flag == EMU_BYTE) {

+ 8 - 4
emulator/devices/cpu/formatII.c

@@ -307,11 +307,11 @@ void decode_formatII(Emulator *emu, uint16_t instruction, bool disassemble)
       uint16_t *stack_address = get_stack_ptr(emu);
     
       if (bw_flag == EMU_WORD) {
-	*stack_address = source_value;
+        *stack_address = source_value;
       }
       else if (bw_flag == EMU_BYTE) {
-	*stack_address &= 0xFF00; /* Zero out bottom half for pushed byte */
-	*stack_address |= (uint8_t) source_value;
+        *stack_address &= 0xFF00; /* Zero out bottom half for pushed byte */
+        *stack_address |= (uint8_t) source_value;
       }
 
       break;
@@ -335,7 +335,11 @@ void decode_formatII(Emulator *emu, uint16_t instruction, bool disassemble)
   
       //# RETI Return from interrupt: Pop SR then pop PC
     case 0x6:{
-       
+      set_sr_value(emu, *get_stack_ptr(emu));
+      cpu->sp += 2;
+      cpu->pc = *get_stack_ptr(emu);
+      cpu->sp += 2;
+//      printf("RETI 0x%04X 0x%04X\n", cpu->pc, sr_to_value(emu));
       break;
     }
     default:{

+ 31 - 0
emulator/devices/cpu/interrupts.c

@@ -0,0 +1,31 @@
+
+#include "interrupts.h"
+
+void service_interrupt(Emulator *emu, uint16_t cause) {
+    Cpu *cpu = emu->cpu;
+    if(!cpu->sr.GIE) return;
+    if((cpu->interrupt == NULL_VECTOR) || (cause > cpu->interrupt)) {
+        cpu->interrupt = cause;
+    }
+    return;
+}
+
+void handle_interrupts(Emulator *emu) {
+    Cpu *cpu = emu->cpu;
+    if(cpu->interrupt != NULL_VECTOR) {
+        cpu->sp -= 2;
+        uint16_t *stack_address = get_stack_ptr(emu);
+        *stack_address = cpu->pc;
+
+        cpu->sp -= 2;
+        stack_address = get_stack_ptr(emu);
+        *stack_address = sr_to_value(emu);
+        // ISV memory space (0xFFE0) + cause
+        cpu->pc = *get_addr_ptr(0xFFE0 + cpu->interrupt);
+        cpu->sr.GIE = 0;
+        cpu->sr.CPUOFF = 0;
+        cpu->sr.OSCOFF = 0;
+        cpu->sr.SCG1 = 0;
+        cpu->interrupt = NULL_VECTOR;
+    }
+}

+ 29 - 0
emulator/devices/cpu/interrupts.h

@@ -0,0 +1,29 @@
+
+
+#ifndef _INTERRUPTS_H_
+#define _INTERRUPTS_H_
+
+// From msp430g2553.h version 1.2
+#define TRAPINT_VECTOR      (0x0000)  /* 0xFFE0 TRAPINT */
+#define PORT1_VECTOR        (0x0004)  /* 0xFFE4 Port 1 */
+#define PORT2_VECTOR        (0x0006)  /* 0xFFE6 Port 2 */
+#define ADC10_VECTOR        (0x000A)  /* 0xFFEA ADC10 */
+#define USCIAB0TX_VECTOR    (0x000C)  /* 0xFFEC USCI A0/B0 Transmit */
+#define USCIAB0RX_VECTOR    (0x000E)  /* 0xFFEE USCI A0/B0 Receive */
+#define TIMER0_A1_VECTOR    (0x0010)  /* 0xFFF0 Timer0_A CC1, TA0 */
+#define TIMER0_A0_VECTOR    (0x0012)  /* 0xFFF2 Timer0_A CC0 */
+#define WDT_VECTOR          (0x0014) /* 0xFFF4 Watchdog Timer */
+#define COMPARATORA_VECTOR  (0x0016) /* 0xFFF6 Comparator A */
+#define TIMER1_A1_VECTOR    (0x0018) /* 0xFFF8 Timer1_A CC1-4, TA1 */
+#define TIMER1_A0_VECTOR    (0x001A) /* 0xFFFA Timer1_A CC0 */
+#define NMI_VECTOR          (0x001C) /* 0xFFFC Non-maskable */
+#define RESET_VECTOR        (0x001E) /* 0xFFFE Reset [Highest Priority] */
+
+#define NULL_VECTOR         (0xFFFF) /* Used by emulator indicate no interrupt */
+
+#include "../utilities.h"
+
+void service_interrupt(Emulator *emu, uint16_t cause);
+void handle_interrupts(Emulator *emu);
+
+#endif

+ 2 - 0
emulator/devices/cpu/registers.c

@@ -42,6 +42,8 @@ void initialize_msp_registers(Emulator *emu)
   cpu->r4 = cpu->r5 = cpu->r6 = cpu->r7 = cpu->r8 = 
     cpu->r9 = cpu->r10 = cpu->r11 = cpu->r12 = cpu->r13 = 
     cpu->r14 = cpu->r15 = 0;
+
+  cpu->interrupt = NULL_VECTOR;
 }
 
 void update_register_display (Emulator *emu) 

+ 1 - 0
emulator/devices/cpu/registers.h

@@ -127,6 +127,7 @@ typedef struct Cpu {
   Usci *usci;
   Bcm *bcm;
   Timer_a *timer_a;
+  uint16_t interrupt;
 } Cpu;
 
 uint16_t sr_to_value (Emulator *emu);

+ 30 - 0
emulator/devices/peripherals/port1.c

@@ -296,6 +296,7 @@ void handle_port_1 (Emulator *emu)
         p->IE_3 = true;
 
         if (*p->_IFG & 0x08) {
+            if(!p->IFG_3) service_interrupt(emu, PORT1_VECTOR);
             p->IFG_3 = true;
         }
         else {
@@ -305,6 +306,9 @@ void handle_port_1 (Emulator *emu)
     else {
         p->IE_3 = false;
     }
+    if (!p->DIR_3 & p->OUT_3 & p->REN_3) {
+
+    }
 
     ///////////////////////////////////////////////////////////////
 
@@ -432,6 +436,30 @@ void handle_port_1 (Emulator *emu)
     else {
         p->IE_7 = false;
     }
+
+    // New P1IN value
+
+    // Natural pull-up configuration
+    uint8_t value = (~*p->_DIR) & *p->_REN & *p->_OUT;
+
+    // Pins raised due to external bias
+    value |= (p->EXT_EN & p->EXT_DIR);
+
+    // Pins lowered due to external bias
+    value &= ~(p->EXT_EN & ~(p->EXT_DIR));
+
+    if(*p->_IN != value) {
+//        printf("Changing PIN 0x%02X -> 0x%02X\n", *p->_IN, value);
+        // Compute with edge trigger
+        uint8_t ifg = *p->_IE & (((~*p->_IN) & value & (~*p->_IES)) | (*p->_IN & (~value) & *p->_IES));
+        *p->_IN = value;
+        if(ifg > 0 && ifg != *p->_IFG) {
+            //printf("Interrupt %02X^%02X %02X\n", ifg, *p->_IFG, (ifg ^ *p->_IFG));
+            *p->_IFG |= ifg;
+            service_interrupt(emu, PORT1_VECTOR);
+        }
+    }
+
 }
 
 void setup_port_1 (Emulator *emu)
@@ -459,6 +487,8 @@ void setup_port_1 (Emulator *emu)
     *(p->_SEL2 = (uint8_t *) get_addr_ptr(SEL2_VLOC)) = 0;
     *(p->_REN  = (uint8_t *) get_addr_ptr(REN_VLOC))  = 0;
 
+    p->EXT_EN = 0;
+    p->EXT_DIR = 0;
   
     p->DIR_0 = false; p->OUT_0 = false; p->IFG_0 = false; 
     p->IE_0 = false; p->SEL_0 = false; p->SEL2_0 = false;

+ 4 - 0
emulator/devices/peripherals/port1.h

@@ -36,6 +36,10 @@ struct Port_1 {
   uint8_t *_SEL2; /* r/w     PUC reset */
   uint8_t *_REN;  /* r/w     PUC reset */
 
+  uint8_t EXT_EN; // External applied, high Z if false
+  uint8_t EXT_DIR; // External direction
+  float EXT_VOLT[8]; // External voltage
+
   // Peripherals activation flags (for emulator)
   bool DIR_0, OUT_0, IFG_0, IE_0, SEL_0, SEL2_0, REN_0;
   bool DIR_1, OUT_1, IFG_1, IE_1, SEL_1, SEL2_1, REN_1;

+ 1 - 0
emulator/devices/utilities.h

@@ -26,6 +26,7 @@
 
 #include "cpu/registers.h"
 #include "memory/memspace.h"
+#include "cpu/interrupts.h"
 
 void reg_num_to_name(uint8_t source_reg, char *reg_name);
 int16_t *get_reg_ptr(Emulator *emu, uint8_t reg);

+ 15 - 9
emulator/python/py_functions.c

@@ -49,9 +49,14 @@ void reset_emu() {
 
 void set_reg(uint8_t reg_type, uint8_t value) {
     if(emuInst == NULL) return;
+    Port_1 *p = emuInst->cpu->p1;
     switch(reg_type) {
-    case SET_REG_P1_IN:
-        *emuInst->cpu->p1->_IN = value;
+    case SET_REG_P1_EN:
+    p->EXT_EN = value;
+    break;
+    case SET_REG_P1_DIR:
+    p->EXT_DIR = value;
+    break;
     }
 }
 
@@ -229,7 +234,7 @@ void start_emu(char *file) {
     setup_port_1(emuInst);
     setup_usci(emuInst);
 
-    print_console(emuInst, " [MSP430 Emulator]\n Copyright (C) 2020 Rudolf Geosits (rgeosits@live.esu.edu)\n");
+    print_console(emuInst, "[MSP430 Emulator]\n Copyright (C) 2020 Rudolf Geosits (rgeosits@live.esu.edu)\n");
 
     load_bootloader(0x0C00);
     if(load_firmware(emuInst, file, 0xC000) == 0) {
@@ -253,15 +258,16 @@ void start_emu(char *file) {
 
             // Handle Breakpoints
             //handle_breakpoints(emuInst);
-
-            // Instruction Decoder
-            decode(emuInst, fetch(emuInst), EXECUTE);
-
-            // Handle Peripherals
-            handle_bcm(emuInst);
+            if(!cpu->sr.CPUOFF) {
+                // Instruction Decoder
+                decode(emuInst, fetch(emuInst), EXECUTE);
+                // Handle Peripherals
+                handle_bcm(emuInst);
+            }
             handle_timer_a(emuInst);
             handle_port_1(emuInst);
             handle_usci(emuInst);
+            handle_interrupts(emuInst);
 
             counter++;
             if(counter > 500) {

+ 2 - 2
emulator/python/py_functions.h

@@ -8,8 +8,8 @@
 #include <Python.h>
 
 
-#define SET_REG_P1_IN 0x05
-#define SET_REG_P2_IN 0x06
+#define SET_REG_P1_EN 0x05
+#define SET_REG_P1_DIR 0x06
 
 #define GET_REG_BCM 0x03
 //#define GET_REG_GP 0x04

+ 1 - 4
msp430emu/__main__.py

@@ -5,8 +5,5 @@ import os
 if __name__ == '__main__':
     load = None
     if len(sys.argv) >= 2:
-        if os.path.exists(sys.argv[1]):
-            load = sys.argv[1]
-        else:
-            print(f"File '{sys.argv[1]}' does not exist")
+        load = sys.argv[1]
     emulator.run(load)

+ 156 - 68
msp430emu/emulator.py

@@ -1,10 +1,12 @@
-from threading import Thread, Event
+from threading import Thread, Event, RLock
+import queue
 from os import path
 import sys
 import _msp430emu
 from . import version
 import wx
 from wx.adv import RichToolTip
+import time
 
 source_dir = path.dirname(path.realpath(__file__))
 
@@ -39,12 +41,13 @@ class Emulator:
     ]
     REG_NAMES_USCI = ['UCA0CTL0', 'UCA0CTL1', 'UCA0BR0', 'UCA0BR1', 'UCA0MCTL', 'UCA0STAT',
                       'UCA0RXBUF', 'UCA0TXBUF', 'UCA0ABCTL', 'UCA0IRTCTL', 'UCA0IRRCTL', 'IFG2'
-    ]
+                      ]
 
     def __init__(self, load=None, callback=None):
         # self.process = Popen([path.join(emu_dir, 'MSP430'), str(ws_port)], stdout=PIPE, stderr=PIPE)
         # self.ws_port = ws_port
         # self._start_ws()
+
         self.load = load
         self.started = False
         self.callback = callback
@@ -52,12 +55,11 @@ class Emulator:
         _msp430emu.on_serial(self._on_serial)
         _msp430emu.on_console(self._on_console)
         # _msp430emu.on_control(self._on_control)
-
         if self.load is not None:
-            self.process = Thread(target=self._start_emu, daemon=False)
-            self.process.start()
+            self.load_file(self.load)
 
     def _on_serial(self, s):
+        print("received " + s)
         self._cb(self.EVENT_SERIAL, s)
 
     def _on_console(self, s):
@@ -71,6 +73,14 @@ class Emulator:
         if self.started:
             _msp430emu.cmd(cmd)
 
+    def write_serial(self, value):
+        try:
+            # self._serial_queue.put_nowait(value)
+            cmd_val = value.replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
+            self._cb(self.EVENT_CONSOLE, f"[UART TX] {cmd_val}\n")
+        except queue.Full:
+            self._cb(self.EVENT_CONSOLE, "UART TX queue is full, this value will not be sent\n")
+
     def get_port1_regs(self):
         if self.started:
             return _msp430emu.get_regs(0x05)
@@ -87,10 +97,14 @@ class Emulator:
         if self.started:
             return _msp430emu.get_regs(0x08)
 
-    def set_port1_in(self, value):
+    def set_port1_en(self, value):
         if 255 >= value >= 0 and self.started:
             return _msp430emu.set_regs(0x05, value)
 
+    def set_port1_dir(self, value):
+        if 255 >= value >= 0 and self.started:
+            return _msp430emu.set_regs(0x06, value)
+
     def reset(self):
         if self.started:
             _msp430emu.reset()
@@ -99,14 +113,31 @@ class Emulator:
         print("starting emulator...")
         self.started = True
         _msp430emu.init(self.load)
+        self.started = False
         print("stopping emulator...")
 
+    def _serial_writer(self):
+        pass
+        # while self.started:
+        #     message = self._serial_queue.get()
+        #     if message is None:
+        #         break
+        #     _msp430emu.write_serial(message)
+        #     self._serial_queue.task_done()
+        # print("stopping writer...")
+
     def load_file(self, fname):
         self.close()
+        if not path.exists(fname):
+            self._cb(self.EVENT_CONSOLE, f"Firmware file '{fname}' does not exist\n")
+            return
         print("loading " + fname)
         self.load = fname
-        self.process = Thread(target=self._start_emu, daemon=False)
-        self.process.start()
+        # self._serial_queue = queue.Queue(maxsize=2)
+        self._process = Thread(target=self._start_emu, daemon=False)
+        # self._writer = Thread(target=self._serial_writer, daemon=False)
+        self._process.start()
+        # self._writer.start()
 
     def _cb(self, ev, data):
         if callable(self.callback):
@@ -125,60 +156,79 @@ class Emulator:
 
     def close(self):
         if self.started:
+            self.started = False
             try:
                 _msp430emu.stop()
             except SystemError:
                 print("Failed gradually stop emulator")
-            self.process.join(2)
+            # if self._serial_queue.empty():
+            #     self._serial_queue.put_nowait(None)
+            # self._writer.join(1)
+            self._process.join(1)
 
 
 class EmulatorWindow(wx.Frame):
-    def __init__(self, parent, title, load=None):
+    def __init__(self, parent, title, load=None, update_fps=25):
         wx.Frame.__init__(self, parent, title=title)
+        self.update_delay = 1 / update_fps
+
         self.control = wx.TextCtrl(self, size=wx.Size(400, 450),
                                    style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_DONTWRAP)
         self.control.Hide()
 
         self.control.WriteText("Initialising Emulator..\n")
+
         self.load = load
         self.emu = Emulator(load=self.load, callback=self.callback)
 
         self.serial = wx.TextCtrl(self, size=wx.Size(400, 450), style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_DONTWRAP)
         self.serial_input = wx.TextCtrl(self)
-
         self.statusBar = self.CreateStatusBar()  # A Statusbar in the bottom of the window
 
         file_menu = wx.Menu()
-        menuFile = file_menu.Append(wx.ID_OPEN, "&Firmware", " Open firmware")
-        self.Bind(wx.EVT_MENU, self.OnOpen, menuFile)
-        menuReload = file_menu.Append(wx.ID_RESET, "Reload", " Reopen the same firmware file")
-        self.Bind(wx.EVT_MENU, self.OnLoad, menuReload)
-        # menuReset = file_menu.Append(wx.ID_CLOSE_ALL, "&Reset", " Reset Emulator")
-        # self.Bind(wx.EVT_MENU, self.RestartEmulator, menuReset)
-        menuAbout = file_menu.Append(wx.ID_ABOUT, "About", "About")
-        self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
-        menuExit = file_menu.Append(wx.ID_EXIT, "E&xit", " Terminate the program")
-        self.Bind(wx.EVT_MENU, self.OnClose, menuExit)
-        self.Bind(wx.EVT_CLOSE, self.OnExit)
-
         view_menu = wx.Menu()
-        self.view_console = view_menu.AppendCheckItem(101, "View Console", "Show/Hide Emulator debug console")
-        self.view_regs_port1 = view_menu.AppendCheckItem(102, "Port1 Registers", "Show/Hide Emulator Port1 register table")
-        self.view_regs_bcm = view_menu.AppendCheckItem(103, "BCM Registers", "Show/Hide Emulator BCM register table")
-        self.view_regs_timer_a = view_menu.AppendCheckItem(104, "TimerA Registers", "Show/Hide Emulator Timer A register table")
-        self.view_regs_usci = view_menu.AppendCheckItem(105, "USCI Registers", "Show/Hide Emulator USCI register table")
-        self.Bind(wx.EVT_MENU, self.ToggleConsole, self.view_console)
-        self.Bind(wx.EVT_MENU, self.ToggleRegisters, self.view_regs_port1)
-        self.Bind(wx.EVT_MENU, self.ToggleRegisters, self.view_regs_bcm)
-        self.Bind(wx.EVT_MENU, self.ToggleRegisters, self.view_regs_timer_a)
-        self.Bind(wx.EVT_MENU, self.ToggleRegisters, self.view_regs_usci)
+        debug_menu = wx.Menu()
+
+        self.menu_navigation = {
+            "&Firmware": (file_menu, 0, wx.ID_OPEN, "Open firmware", self.OnOpen),
+            "&Reload\tCtrl+R": (file_menu, 0, wx.ID_RESET, "Reopen the same firmware file", self.OnLoad),
+            "About": (file_menu, 0, wx.ID_ABOUT, "About", self.OnAbout),
+            "E&xit": (file_menu, 0, wx.ID_EXIT, " Terminate the program", self.OnClose),
+
+            "View Console": (view_menu, 1, wx.NewId(), "Show/Hide Emulator debug console", self.ToggleConsole),
+            "Port1 Registers": (view_menu, 1, wx.NewId(), "Show/Hide Emulator Port1 register table", self.ToggleRegisters),
+            "BCM Registers": (view_menu, 1, wx.NewId(), "Show/Hide Emulator BCM register table", self.ToggleRegisters),
+            "TimerA Registers": (view_menu, 1, wx.NewId(), "Show/Hide Emulator TimerA register table", self.ToggleRegisters),
+            "USCI Registers": (view_menu, 1, wx.NewId(), "Show/Hide Emulator USCI register table", self.ToggleRegisters),
+            "Clear serial": (view_menu, 0, wx.NewId(), "Clean text in serial window", lambda e: self.serial.Clear()),
+            "Clear console": (view_menu, 0, wx.NewId(), "Clean text in console window", lambda e: self.control.Clear()),
+
+            "Step\tCtrl+N": (debug_menu, 0, wx.NewId(), "Step single instruction", lambda e: self.emu.send_command("step")),
+            "Registers": (debug_menu, 0, wx.NewId(), "Print registers in console", lambda e: self.emu.send_command("regs")),
+        }
+
+        for name, (menu, typ, wx_id, desc, action) in self.menu_navigation.items():
+            if typ == 0:
+                button = menu.Append(wx_id, name, desc)
+            elif typ == 1:
+                button = menu.AppendCheckItem(wx_id, name, desc)
+            else:
+                continue
+            self.Bind(wx.EVT_MENU, action, button)
 
         menuBar = wx.MenuBar()
         menuBar.Append(file_menu, "&File")
         menuBar.Append(view_menu, "&View")
-
+        menuBar.Append(debug_menu, "&Debug")
         self.SetMenuBar(menuBar)
 
+        accel_tbl = wx.AcceleratorTable([
+            (wx.ACCEL_CTRL, ord('R'), wx.ID_RESET),
+            (wx.ACCEL_CTRL, ord('N'), self.menu_navigation["Step\tCtrl+N"][2]),
+        ])
+        self.SetAcceleratorTable(accel_tbl)
+        self.Bind(wx.EVT_CLOSE, self.OnExit)
+
         self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
         self.btn_start_emu = wx.Button(self, -1, "Start")
         self.Bind(wx.EVT_BUTTON, self.OnStart, self.btn_start_emu)
@@ -200,10 +250,10 @@ class EmulatorWindow(wx.Frame):
         self.sizer = wx.BoxSizer(wx.VERTICAL)
 
         self.sizer3 = wx.BoxSizer(wx.HORIZONTAL)
-        self.btn_start_emu = wx.Button(self, -1, "Send")
-        self.Bind(wx.EVT_BUTTON, self.SendSerial, self.btn_start_emu)
+        self.btn_send_serial = wx.Button(self, -1, "Send")
+        self.Bind(wx.EVT_BUTTON, self.SendSerial, self.btn_send_serial)
         self.sizer3.Add(self.serial_input, 1)
-        self.sizer3.Add(self.btn_start_emu, 0)
+        self.sizer3.Add(self.btn_send_serial, 0)
 
         self.sizer0 = wx.BoxSizer(wx.VERTICAL)
         self.sizer0.Add(self.serial, 1, wx.EXPAND)
@@ -234,18 +284,23 @@ class EmulatorWindow(wx.Frame):
         self.Show()
 
         self.btn_key.Disable()
+        self.btn_send_serial.Disable()
+
         self.emu_paused = True
         self.timer_running = Event()
         self.timer = Thread(target=self.OnTimer)
         self.timer.start()
 
-        if self.load is None:
+        self.serial_buffer = ""
+        self.serial_buffer_lock = RLock()
+
+        if not self.emu.started:
             self.serial_input.Disable()
             self.serial.Disable()
             self.btn_rst.Disable()
-            self.btn_key.Disable()
             self.btn_start_emu.Disable()
             self.btn_stop_emu.Disable()
+            self.statusBar.SetStatusText("Open firmware file to start simulation")
         else:
             self.statusBar.SetStatusText("Press start to run emulation")
 
@@ -253,7 +308,10 @@ class EmulatorWindow(wx.Frame):
         if event == Emulator.EVENT_CONSOLE:
             wx.CallAfter(self.control.AppendText, data)
         elif event == Emulator.EVENT_SERIAL:
-            wx.CallAfter(self.serial.AppendText, data)
+            self.serial_buffer_lock.acquire()
+            self.serial_buffer += data
+            self.serial_buffer_lock.release()
+            # wx.CallAfter(self.serial.AppendText, data)
         # elif event == Emulator.EVENT_GPIO:
         #     self.diagram.port1[data // 2] = data % 2 == 0
         #     wx.CallAfter(self.diagram.Refresh)
@@ -286,14 +344,17 @@ class EmulatorWindow(wx.Frame):
         if self.load is None:
             return
         self.emu.load_file(self.load)
-        self.diagram.power = False
+        self.diagram.reset()
         self.btn_key.Disable()
+        self.btn_send_serial.Disable()
         self.emu_paused = True
+        if not self.emu.started:
+            self.statusBar.SetStatusText("Open firmware file to start simulation")
+            return
 
         self.serial_input.Enable()
         self.serial.Enable()
         self.btn_rst.Enable()
-        self.btn_key.Enable()
         self.btn_start_emu.Enable()
         self.btn_stop_emu.Enable()
         self.statusBar.SetStatusText("Press start to run emulation")
@@ -301,19 +362,22 @@ class EmulatorWindow(wx.Frame):
     def OnTimer(self):
         while 1:
             try:
-                if self.timer_running.wait(0.04):
+                if self.timer_running.wait(self.update_delay):
                     break
             except TimeoutError:
                 pass
             if self.emu_paused:
                 continue
-            ports = self.emu.get_port1_regs()
-            if ports is not None:
-                for i in range(8):
-                    self.diagram.port1[i] = (ports[0] >> i) & 1 == 1
-            if not self.btn_key_down and ports[0] & 8 and ports[7] & 8 and not ports[1] & 8:
-                self.emu.set_port1_in(8)  # P1.3 high
-            wx.CallAfter(self.diagram.Refresh)
+            port1 = self.emu.get_port1_regs()
+            if port1 is not None and self.diagram.port1 != port1[0]:
+                self.diagram.port1 = port1[0]
+                wx.CallAfter(self.diagram.Refresh)
+
+            self.serial_buffer_lock.acquire()
+            if len(self.serial_buffer) > 0:
+                wx.CallAfter(self.serial.AppendText, self.serial_buffer)
+                self.serial_buffer = ""
+            self.serial_buffer_lock.release()
             wx.CallAfter(self.registers.update_values)
 
     def OnPause(self, e):
@@ -322,9 +386,9 @@ class EmulatorWindow(wx.Frame):
         self.diagram.Refresh()
         self.emu.get_port1_regs()
         self.btn_key.Disable()
+        self.btn_send_serial.Disable()
         self.emu_paused = True
 
-
     def OnStart(self, e):
         if self.load is None:
             self.OnOpen(e)
@@ -334,6 +398,7 @@ class EmulatorWindow(wx.Frame):
             self.diagram.power = True
             self.diagram.Refresh()
             self.btn_key.Enable()
+            self.btn_send_serial.Enable()
             self.emu_paused = False
 
     def OnClose(self, e):
@@ -369,32 +434,32 @@ class EmulatorWindow(wx.Frame):
             tip.ShowFor(self.btn_key)
         else:
             self.btn_key_down = True
-            self.emu.set_port1_in(0)  # P1.3 low
+            self.emu.set_port1_en(8)  # P1.3 to ground
         e.Skip()
 
     def OnMouseUp(self, e):
         if self.btn_key_down:
-            self.emu.set_port1_in(8)  # P1.3 high
+            self.emu.set_port1_en(0)  # P1.3 to high z
             self.btn_key_down = False
         e.Skip()
 
     def OnKeyReset(self, e):
-        self.diagram.port1 = [False, False, False, False, False, False, False, False]
+        self.diagram.reset()
         self.emu.reset()
 
     def SendSerial(self, e):
         text = self.serial_input.GetValue()
-        print(text)
+        self.emu.write_serial(text)
         self.serial_input.Clear()
 
     def ToggleRegisters(self, e):
-        if e.Id == self.view_regs_port1.Id:
+        if e.Id == self.menu_navigation["Port1 Registers"][2]:
             panel = self.registers.panel_port1
-        elif e.Id == self.view_regs_bcm.Id:
+        elif e.Id == self.menu_navigation["BCM Registers"][2]:
             panel = self.registers.panel_bmc
-        elif e.Id == self.view_regs_timer_a.Id:
+        elif e.Id == self.menu_navigation["TimerA Registers"][2]:
             panel = self.registers.panel_timer_a
-        elif e.Id == self.view_regs_usci.Id:
+        elif e.Id == self.menu_navigation["USCI Registers"][2]:
             panel = self.registers.panel_usci
         else:
             return
@@ -412,7 +477,7 @@ class RegisterPanel(wx.Panel):
         self.box = wx.BoxSizer(wx.HORIZONTAL)
         self.emu = emu
         self.regs_port1 = {name: None for name in emu.REG_NAMES_PORT1}
-        self.regs_bcm = {name: None for name in emu.REG_NAMES_BCM}
+        self.regs_bcm = {name: None for name in emu.REG_NAMES_BCM + ["MCLK"]}
         self.regs_timer_a = {name: None for name in emu.REG_NAMES_TIMER_A}
         self.regs_usci = {name: None for name in emu.REG_NAMES_USCI}
 
@@ -457,17 +522,38 @@ class RegisterPanel(wx.Panel):
         self.Fit()
         self.Layout()
 
+    def _map(self, func, panel):
+        values = func()
+        if values is None:
+            return None
+        formatted = [f"{value:08b}" for value in values]
+        if panel == self.panel_bmc:
+            freq = "x"
+            if values[0] == 96 and values[1] == 135:
+                freq = "1.03"
+            elif values[0] == 0b11010001 and values[1] == 0b10000110:
+                freq = "1.00"
+            elif values[0] == 0b10010010 and values[1] == 0b10001101:
+                freq = "8.00"
+            elif values[0] == 0b10011110 and values[1] == 0b10001110:
+                freq = "12.0"
+            elif values[0] == 0b10010101 and values[1] == 0b10001111:
+                freq = "16.0"
+            formatted.append(freq + " MHz")
+        return formatted
+
     def update_values(self):
         for panel, _, regs, emu_func in self.__struc:
             if not panel.IsShown():
                 continue
-            values = emu_func()
+            values = self._map(emu_func, panel)
             if values is None:
                 continue
             if len(values) != len(regs):
                 continue
             for i, reg in enumerate(regs.values()):
-                reg.SetValue(f"{values[i]:08b}")
+                if reg.GetValue() != values[i]:
+                    reg.SetValue(values[i])
 
 
 class AboutWindow(wx.Frame):
@@ -494,8 +580,6 @@ class AboutWindow(wx.Frame):
             self.vbox.Add(stext, 0, wx.EXPAND)
 
 
-
-
 class DrawRect(wx.Panel):
     """ class MyPanel creates a panel to draw on, inherits wx.Panel """
     RED = wx.Colour(255, 0, 0, wx.ALPHA_OPAQUE)
@@ -507,13 +591,17 @@ class DrawRect(wx.Panel):
         # self.SetBackgroundColour("white")
         self.Bind(wx.EVT_PAINT, self.OnPaint)
         self.power = False
-        self.port1 = [False, False, False, False, False, False, False, False]
+        self.port1 = 0
         self.image = wx.Bitmap(path.join(source_dir, "msp430.png"), wx.BITMAP_TYPE_PNG)
 
+    def reset(self):
+        self.power = False
+        self.port1 = 0
+        wx.CallAfter(self.Refresh)
+
     def OnPaint(self, evt):
         """set up the device context (DC) for painting"""
         self.dc = wx.PaintDC(self)
-
         self.dc.DrawBitmap(self.image, 0, 0, True)
         if self.power:
             self.dc.SetPen(wx.Pen(self.GREEN, style=wx.TRANSPARENT))
@@ -521,13 +609,13 @@ class DrawRect(wx.Panel):
             # set x, y, w, h for rectangle
             self.dc.DrawRectangle(39, 110, 8, 15)
 
-        if self.port1[6]:
+        if self.port1 & 1 << 6:
             self.dc.SetPen(wx.Pen(self.GREEN, style=wx.TRANSPARENT))
             self.dc.SetBrush(wx.Brush(self.GREEN, wx.SOLID))
             # set x, y, w, h for rectangle
             self.dc.DrawRectangle(83, 356, 8, 15)
 
-        if self.port1[0]:
+        if self.port1 & 1:
             self.dc.SetPen(wx.Pen(self.RED, style=wx.TRANSPARENT))
             self.dc.SetBrush(wx.Brush(self.RED, wx.SOLID))
             self.dc.DrawRectangle(70, 356, 8, 15)

+ 5 - 0
readme.md

@@ -27,6 +27,11 @@ List of features that are added:
 - [ ] UART Serial input
 - [x] Reset button
 - [x] Button for P1.3
+- [x] Interrupts
+- [ ] Timer A
+- [ ] PWM Signals
+- [x] Unbiased circuit simulation (due to internal pull-up/down resistors)
+- [ ] Watchdog Timer
 - [ ] GPIO Oscilloscope
 - [ ] Port 2 GPIO
 - [ ] Invalid register configuration checks

+ 1 - 0
setup.py

@@ -14,6 +14,7 @@ emulator_files = [
     'emulator/devices/cpu/formatI.c',
     'emulator/devices/cpu/formatII.c',
     'emulator/devices/cpu/formatIII.c',
+    'emulator/devices/cpu/interrupts.c',
     'emulator/devices/peripherals/bcm.c',
     'emulator/devices/peripherals/timer_a.c',
     'emulator/devices/peripherals/usci.c',