# Simulation Microprocessor HC680 -fictive- 8 bit # ------ sehr UNVOLLSTÄNDIG - NOCH IN BEARBEITUNG ------ import tkinter as tk from tkinter import ttk, font WINDOW_BG = "#eeeed3" WINDOW_W = 1012 WINDOW_H = 710 DEFAULT_FONT_FAMILY = "Lucide Console" DEFAULT_FONT_SIZE = 10 DEFAULT_ROW_HEIGHT = 18 DEFAULT_HEADER_HEIGHT = 18 def create_font(family=DEFAULT_FONT_FAMILY, size=DEFAULT_FONT_SIZE): try: return font.Font(family=family, size=size) except Exception: return font.Font(family=DEFAULT_FONT_FAMILY, size=DEFAULT_FONT_SIZE) def clamp(v): return max(0, min(255, int(v))) def rgb_to_hex(r,g,b): return f"#{clamp(r):02x}{clamp(g):02x}{clamp(b):02x}" def draw_round_rect(canvas, x1, y1, x2, y2, r, fill="", outline=""): """ gefülltes, abgerundetes Rechteck r = Radius der Ecken (in Pixel). vier Viertel-Kreise (create_arc mit style='pieslice') + drei Rechtecke, """ # Mittleres Rechteck (vertikal erweitert) canvas.create_rectangle(x1 + r, y1, x2 - r, y2, fill=fill, outline="") # Linkes / rechtes Rechteck (horizontal erweitert) canvas.create_rectangle(x1, y1 + r, x2, y2 - r, fill=fill, outline="") # vier Eck-Kreise (Viertelkreise) canvas.create_arc(x1, y1, x1 + 2*r, y1 + 2*r, start=90, extent=90, style='pieslice', fill=fill, outline="") canvas.create_arc(x2 - 2*r, y1, x2, y1 + 2*r, start=0, extent=90, style='pieslice', fill=fill, outline="") canvas.create_arc(x1, y2 - 2*r, x1 + 2*r, y2, start=180, extent=90, style='pieslice', fill=fill, outline="") canvas.create_arc(x2 - 2*r, y2 - 2*r, x2, y2, start=270, extent=90, style='pieslice', fill=fill, outline="") if outline: # oben, unten, links, rechts Linien verbinden die Ecken canvas.create_line(x1 + r, y1, x2 - r, y1, fill=outline) canvas.create_line(x1 + r, y2, x2 - r, y2, fill=outline) canvas.create_line(x1, y1 + r, x1, y2 - r, fill=outline) canvas.create_line(x2, y1 + r, x2, y2 - r, fill=outline) # und die vier Bogen-Konturen canvas.create_arc(x1, y1, x1 + 2*r, y1 + 2*r, start=90, extent=90, style='arc', outline=outline) canvas.create_arc(x2 - 2*r, y1, x2, y1 + 2*r, start=0, extent=90, style='arc', outline=outline) canvas.create_arc(x1, y2 - 2*r, x1 + 2*r, y2, start=180, extent=90, style='arc', outline=outline) canvas.create_arc(x2 - 2*r, y2 - 2*r, x2, y2, start=270, extent=90, style='arc', outline=outline) """ Allgemeine GridBox-Klasse, orientiert an XProfan Create("GridBox", ...) - Spalten-Definitionen "Header;Format;Width;..." - pixel-positioniert (place) - Methoden: setfont, set_data, clear, show, hide, on_row_click - scrollbar, update_cell/get_cell """ class GridBox: """ - setfont(tkinter.font.Font) - set_data(list_of_rows) -- list_of_rows: list of lists/tuples of strings - clear() - show() - hide() - on_row_click(callback) -- callback(index, row_values) - update_cell(row, col, text) - get_cell(row, col) """ def __init__(self, parent, coldef_str, style, x, y, width, height, row_height=DEFAULT_ROW_HEIGHT, header_height=DEFAULT_HEADER_HEIGHT, scrollable=False): """ parent: tkinter widget coldef_str: "Col1;0;80;Col2;2;100;..." (header;format;width)* format: 0=left, 1=right, 2=center style: Ignoriert x,y,width,height: pixel position + size (place geometry) scrollable: if True, vertical scrollbar is added and canvas width reduced """ self.parent = parent self.col_def = self._parse_coldef(coldef_str) self.style = style self.x, self.y, self.width, self.height = x, y, width, height self.row_height = row_height self.header_height = header_height self.scrollable = scrollable # Frame + Canvas (pixel exact) self.frame = tk.Frame(parent, bg=WINDOW_BG) self.frame.place(x=self.x, y=self.y, width=self.width, height=self.height) # Scrollbar width reserved if scrollable self._sb_width = 15 if self.scrollable else 0 self.canvas = tk.Canvas(self.frame, bg="#ffffff", highlightthickness=0) self.canvas.place(x=0, y=0, width=self.width - self._sb_width, height=self.height) self.vsb = None if self.scrollable: self.vsb = ttk.Scrollbar(self.frame, orient="vertical", command=self.canvas.yview) self.vsb.place(x=self.width - self._sb_width, y=0, width=self._sb_width, height=self.height) self.canvas.configure(yscrollcommand=self.vsb.set) # Scroll bindings self.canvas.bind("", self._on_mousewheel) self.canvas.bind("", self._on_mousewheel) self.canvas.bind("", self._on_mousewheel) # font & data self._font = create_font() self._data = [] # list of rows (each row: list of strings) self._row_count = 0 # drawing caches self._text_ids = {} # {(row_idx, col_idx): canvas_text_id} self._sel_rect = None # click handling self._click_callback = None self.canvas.bind("", self._on_click) # initial draw of header only self._draw_header() self._visible = True def _parse_coldef(self, s): """ Parse the column definition string. Expected repeating groups: Header;Format;Width Format numeric: 0 left, 1 right, 2 center Returns list of dicts: [{'title':..., 'align':0/1/2, 'width':int}, ...] """ cols = [] if not s: return cols tokens = [t for t in s.split(";") if t != ""] i = 0 while i < len(tokens): title = tokens[i] if i < len(tokens) else "" # next token format (0/1/2) fmt = 0 wid = 80 if i + 1 < len(tokens): tok = tokens[i+1] if tok.isdigit(): fmt = int(tok) else: fmt = 0 if i + 2 < len(tokens) and tokens[i+2].isdigit(): wid = int(tokens[i+2]) cols.append({'title': title, 'align': fmt if fmt in (0,1,2) else 0, 'width': wid}) i += 3 return cols # -------- drawing header & empty body ---------- def _draw_header(self): self.canvas.delete("all") total_w = sum(c['width'] for c in self.col_def) # draw header background area self.canvas.create_rectangle(0, 0, total_w, self.header_height, fill="#ffffff", outline="") # column vertical lines & header text x = 0 for ci, col in enumerate(self.col_def): # text tx = x + 4 ty = self.header_height / 2 self.canvas.create_text(tx, ty, anchor="w", text=col['title'], font=self._font) # vertical separator self.canvas.create_line(x + col['width'], 0, x + col['width'], self.height, fill="#cfcfcf") x += col['width'] # horizontal line under header self.canvas.create_line(0, self.header_height, total_w, self.header_height, fill="#cfcfcf") # -------- public API ---------- def setfont(self, tkfont): """tkfont: tkinter.font.Font object (or tuple)""" if isinstance(tkfont, font.Font) or hasattr(tkfont, "actual"): self._font = tkfont else: # allow tuple ("Family", size) try: fam, size = tkfont self._font = create_font(family=fam, size=size) except Exception: self._font = create_font() # redraw header & data self._draw_header() if self._data: self._draw_rows(0, len(self._data)-1) def set_data(self, rows): """ rows: list of row-lists (each row must have <= number of columns) Strings will be used as-is. """ self._data = [list(map(str, r)) for r in rows] self._row_count = len(self._data) self._draw_rows(0, self._row_count - 1) def clear(self): self._data = [] self._row_count = 0 self._text_ids.clear() self._draw_header() # reset scrollregion self.canvas.configure(scrollregion=(0,0,0,0)) if self.vsb: self.vsb.set(0,0) def show(self): if not self._visible: self.frame.place(x=self.x, y=self.y, width=self.width, height=self.height) self._visible = True def hide(self): if self._visible: self.frame.place_forget() self._visible = False def on_row_click(self, callback): """callback(index, row_values) - register click callback""" self._click_callback = callback # -------- small helpers for cell update/retrieval ---------- def update_cell(self, row_idx, col_idx, text): """Update internal data and canvas text for a single cell (minimal).""" # ensure internal data is present while row_idx >= len(self._data): self._data.append([""] * len(self.col_def)) row = self._data[row_idx] while col_idx >= len(row): row.append("") row[col_idx] = str(text) self._row_count = len(self._data) # update canvas text if exists tid = self._text_ids.get((row_idx, col_idx)) if tid: self.canvas.itemconfigure(tid, text=str(text)) else: # fallback: redraw rows self._draw_rows(0, self._row_count - 1) def get_cell(self, row_idx, col_idx): if row_idx < 0 or row_idx >= len(self._data): return "" row = self._data[row_idx] if col_idx < 0 or col_idx >= len(row): return "" return str(row[col_idx]) # -------- internal drawing of rows ---------- def _draw_rows(self, start_row, end_row): # wipe row text items # keep header lines; easiest: delete all and redraw header + rows self._text_ids.clear() self.canvas.delete("all") self._draw_header() total_w = sum(c['width'] for c in self.col_def) # compute total height for scrollregion total_h = self.header_height + max(0, self._row_count) * self.row_height self.canvas.configure(scrollregion=(0, 0, total_w, total_h)) # draw each row for r in range(self._row_count): y0 = self.header_height + r * self.row_height y1 = y0 + self.row_height # thin horizontal separator self.canvas.create_line(0, y1, total_w, y1, fill="#e0e0e0") # draw each column text (if provided) x = 0 for ci, col in enumerate(self.col_def): text = "" if r < len(self._data): if ci < len(self._data[r]): text = self._data[r][ci] # compute text x anchor depending on alignment if col['align'] == 1: # right tx = x + col['width'] - 4 anchor = "e" elif col['align'] == 2: # center tx = x + col['width'] / 2 anchor = "c" else: # left tx = x + 4 anchor = "w" ty = y0 + self.row_height / 2 tid = self.canvas.create_text(tx, ty, text=text, font=self._font, anchor=anchor) self._text_ids[(r, ci)] = tid x += col['width'] # -------- click -> determine row ---------- def _on_click(self, event): # calculate which row was clicked (account for scrolling) y = self.canvas.canvasy(event.y) if y < self.header_height: return row_index = int((y - self.header_height) // self.row_height) if 0 <= row_index < self._row_count: # optional visual: draw selection rect (minimal) # remove old if self._sel_rect: self.canvas.delete(self._sel_rect) total_w = sum(c['width'] for c in self.col_def) y0 = self.header_height + row_index * self.row_height y1 = y0 + self.row_height self._sel_rect = self.canvas.create_rectangle(0, y0, total_w, y1, fill="#dfe8ff", outline="") # raise texts for tid in self._text_ids.values(): self.canvas.tag_raise(tid) # call callback if self._click_callback: # provide a copy of row values vals = self._data[row_index] if row_index < len(self._data) else [] self._click_callback(row_index, list(vals)) # -------- mouse wheel handler ---------- def _on_mousewheel(self, event): # Linux if hasattr(event, "num") and event.num in (4,5): if event.num == 4: self.canvas.yview_scroll(-1, "units") else: self.canvas.yview_scroll(1, "units") else: # Windows / macOS: event.delta multiples of 120 try: delta = int(event.delta / 120) except Exception: delta = 0 if delta: # reverse sign for natural scrolling self.canvas.yview_scroll(-delta, "units") #................................................................................. def main(): root = tk.Tk() root.configure(bg=WINDOW_BG) root.geometry(f"{WINDOW_W}x{WINDOW_H}") #root.resizable(False, False) root.title("Simulation Processor HC680 -fictional- 8 Bit Data/Addresses V 1.0.8") # ==>> !!!!! Fonts noch exakter nach XProfanVersion ausrichten / anpassen !!!!!!!!!!!!!!!!!!!!!!!!!! # nfont = create_font( ) # normal font bei XProfan sfont = create_font(DEFAULT_FONT_FAMILY, 9.5) nfont = create_font("Arial", 10.5) c = tk.Canvas(root, bg="#eeeed3", highlightthickness=0) c.pack(fill="both", expand=True) b_red = 238 b_green = 238 b_blue = 211 backgrdc = rgb_to_hex(b_red, b_green, b_blue) chipc = rgb_to_hex(b_red + 15, b_green + 15, b_blue + 15) # wird intern geclamped tk.Label(root, text='Random Access Memory - 256 Byte RAM', bg="#eeeed3", font = nfont).place(x=0, y=0, width=290, height=18) shorthlp = tk.Button(root, text="quick quide", font=nfont ).place(x=387, y=0, width=109, height=17) # command ... tk.Label(root, text=' I/O-System Input MGA-Display Output ', bg="#eeeed3", font = nfont)\ .place(x=500, y=0, width=290, height=18) tk.Label(root, text='I/O-Protocol', bg="#eeeed3").place(x=870, y=0, width=100, height=18) shio = tk.Checkbutton(root, text='').place(x=970, y=0, width=18, height=18) tk.Label(root, text='CACHE', bg="#eeeed3").place(x=380, y=684, width=60, height=18) shcache = tk.Checkbutton(root, text='').place(x=440, y=684, width=18, height=18) # ..... I/O system ..... x1, y1, x2, y2, r = 500, 18, 876, 104, 12 draw_round_rect(c, x1, y1, x2, y2, r, fill=chipc, outline="black") # innerer Frame um Widgets zu platzieren: padding = 4 inner_w = (x2 - x1) - 2*padding inner_h = (y2 - y1) - 2*padding inner_frame = tk.Frame(root, bg=chipc, bd=0, highlightthickness=0, relief="flat") c.create_window(x1 + padding + inner_w/2, y1 + padding + inner_h/2, window=inner_frame, width=inner_w, height=inner_h) tk.Label(inner_frame, text="Input:", bg=chipc).place(x=14, y=0) # ..... CPU ..... x1, y1, x2, y2, r = 500, 125, 876, 450, 12 draw_round_rect(c, x1, y1, x2, y2, r, fill=chipc, outline="black") # innerer Frame um Widgets zu platzieren padding = 4 inner_w = (x2 - x1) - 2*padding inner_h = (y2 - y1) - 2*padding inner_frame = tk.Frame(root, bg=chipc, bd=0, highlightthickness=0, relief="flat") c.create_window(x1 + padding + inner_w/2, y1 + padding + inner_h/2, window=inner_frame, width=inner_w, height=inner_h) tk.Label(inner_frame, text="Data register", bg=chipc).place(x=10, y=0) tk.Label(inner_frame, text="D0", bg=chipc).place(x=0, y=17) tk.Label(root, text="I/O-Prot. delete", bg=WINDOW_BG, font=nfont).place(x=880, y=450, width=100, height=18) clrio = tk.Button(root, text="X", font=nfont ).place(x=986, y=450, width=17, height=17) tk.Label(root, text='delete RAM/Flags', bg="#eeeed3").place(x=50, y=684, width=100, height=18) xram = tk.Button(root, text="X", font=nfont ).place(x=150, y=684, width=17, height=17) tk.Label(root, text='delete Flags', bg="#eeeed3").place(x=230, y=684, width=80, height=18) xflag = tk.Button(root, text="X", font=nfont ).place(x=320, y=686, width=17, height=17) tk.Label(root, text="assembler /programming", bg=WINDOW_BG, font=nfont).place(x=500, y=476, width=170, height=18) tk.Label(root, text="ASSEMBLER-COMMAND ", bg=WINDOW_BG, font=nfont).place(x=500, y=510, width=170, height=18) tk.Label(root, text="program | data:", bg=WINDOW_BG, font=nfont).place(x=690, y=473, width=170, height=18) tk.Label(root, text="Startadr. from to | from to", bg=WINDOW_BG, font=nfont).place(x=680, y=489, width=170, height=18) tk.Label(root, text="store load ", bg=WINDOW_BG, font=nfont).place(x=880, y=494, width=100, height=18) ramkopf = "Address;0;75;hex;2;36;Content;2;83;Command/Value;0;116;FLAG;0;44;" cachekopf = " ;2;16;CACHE;2;80" iokopf = ">In;2;48;Out>;2;52" RAM = GridBox(root, ramkopf, 0, 4, 20, 379, 664, scrollable=True) RAM.setfont(nfont) rows = [] for a in range(256): addr_bin = f"{a:08b}" addr_hex = f"{a:02X}" content_bin = "00000000" command = "NOP" # ==>> !!!!! muss für Zeilen ab 80hex später noch angepasst werden !!!!!!!!!!!!!!!!!!!!!!!!!!!!! flag = "" # ==>> !!!!! muss für Zeilen ab F5hex später noch angepasst werden !!!!!!!!!!!!!!!!!!!!!!!!!!!!! rows.append([ addr_bin, addr_hex, content_bin, command, flag]) RAM.set_data(rows) cache_grid = GridBox(root, cachekopf, 0, 388, 330, 105, 352) cache_grid.setfont(nfont) cache_grid.set_data([["", "", ""]] * 16) # cache_grid.hide() # ==>> !!!!!später mit Checkbox einblenden !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! io_grid = GridBox(root, iokopf, 0, 880, 18, 125, 432) io_grid.setfont(sfont) io_grid.set_data([["", ""]] * 32) # io_grid.hide() # ==>> !!!!!später mit Checkbox einblenden !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # mnemonics = [ "NOP", "CMP", "SWD", "SWM", "MUL", "DIV", "PSA", "POA", "JSR", "RET", "JIN", "JIZ", "JMP", "LDC", "## ", # ## only two characters + space "INP", "OUT", "PSH", "POP", "SSR", "GSR", "BTS", "SWN", "SHL", "SHR", "ROL", "ROR", "CLR", "INC", "DEC", "NOT", "AND", "OR ", # OR only two characters + space "ADD", "SUB", "MOV", "LOD", "STO", "RCL", "CPY", "STP" ] # normalize to 3 chars # mnemonics = [m[:3].upper().ljust(3) for m in mnemonics] operands = [ ".IA.", ".+A.", ".D0.", ".D1.", ".A0.", ".A1.", ".SR.", ".SP.", "[D0]", "[D1]", "[A0]", "[A1]", "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111", ".00.", ".01.", ".10.", ".11." ] # operands = [op[:4].upper().ljust(4) for op in operands] tk.Label(root, text="Adr Op Code Mnemonic", bg=WINDOW_BG, font=nfont).place(x=500, y=529, width=170, height=18) var_addr = tk.StringVar(value="00") #ent_addr = ttk.Entry(root, textvariable=var_addr, width=4, state="readonly", font=nfont).place(x=500, y=548, width=30, height=18) ent_addr = tk.Entry(root, textvariable=var_addr, width=4, state="readonly", font=nfont, bd=0, relief="flat", highlightthickness=0,\ readonlybackground="#efefef").place(x=507, y=548, width=30, height=18) var_opcode = tk.StringVar(value="+to do+") ent_opcode = tk.Entry(root, textvariable=var_opcode, width=12, state="readonly", font=nfont, bd=0, relief="flat", highlightthickness=0,\ readonlybackground="#efefef").place(x=540, y=548, width=66, height=18) var_mn = tk.StringVar() # ==>> Schrift: Abstand der Zeilen zu groß, muss angepasst werden !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! cb_mn = ttk.Combobox(root, values=mnemonics, textvariable=var_mn, width=6, state="normal", font=sfont, height=len(mnemonics)) cb_mn.place(x=600, y=545, width=60, height=20) tk.Label(root, text="Op. 1 Op. 2", bg=WINDOW_BG, font=nfont).place(x=660, y=529, width=150, height=18) var_op1 = tk.StringVar() # ==>> Schrift: Abstand der Zeilen zu groß, muss angepasst werden !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! cb_op1 = ttk.Combobox(root, values=operands, textvariable=var_op1, width=8, state="normal", font=sfont, height=len(operands)) cb_op1.place(x=675, y=545, width=55, height=20) var_op2 = tk.StringVar() cb_op2 = ttk.Combobox(root, values=operands, textvariable=var_op2, width=8, state="normal", font=nfont, height=len(operands)) cb_op2.place(x=735, y=545, width=55, height=20) current_row = {'idx': None} # normalization helpers def norm_mn(s): s = (s or "").strip().upper() return (s + " ")[:3] def norm_op(s): s = (s or "").strip().upper() return (s + "....")[:4] def build_command_from_boxes(): mn = norm_mn(var_mn.get()) o1 = norm_op(var_op1.get()) o2 = norm_op(var_op2.get()) return f"{mn} {o1} {o2}" def on_combobox_changed(event=None): idx = current_row['idx'] if idx is None: return newcmd = build_command_from_boxes() RAM.update_cell(idx, 3, newcmd) # Command/Value is column index 3 # update compact opcode display without spaces ent_opcode_var = newcmd.replace(" ", "") var_opcode.set(ent_opcode_var) # Bind combobox events (selection & focus out & Enter) cb_mn.bind("<>", on_combobox_changed) cb_op1.bind("<>", on_combobox_changed) cb_op2.bind("<>", on_combobox_changed) cb_mn.bind("", on_combobox_changed) cb_op1.bind("", on_combobox_changed) cb_op2.bind("", on_combobox_changed) cb_mn.bind("", on_combobox_changed) cb_op1.bind("", on_combobox_changed) cb_op2.bind("", on_combobox_changed) # RAM click callback: parse Command/Value and fill comboboxes def on_ram_click(idx, values): current_row['idx'] = idx var_addr.set(f"{idx:02X}") cmd = "" if len(values) > 3: cmd = values[3] # try split first parts = cmd.split() if len(parts) >= 3: mn = norm_mn(parts[0]) o1 = norm_op(parts[1]) o2 = norm_op(parts[2]) else: # fallback substring positions (0:3, 4:8, 9:13) cmd_padded = (cmd + " " * 13)[:13] mn = norm_mn(cmd_padded[0:3]) o1 = norm_op(cmd_padded[4:8]) o2 = norm_op(cmd_padded[9:13]) var_mn.set(mn) var_op1.set(o1) var_op2.set(o2) var_opcode.set(cmd.replace(" ", "")) RAM.on_row_click(on_ram_click) root.mainloop() if __name__ == "__main__": main()