diff --git a/src/main/java/de/neemann/digital/core/element/Keys.java b/src/main/java/de/neemann/digital/core/element/Keys.java index 07a0ad89c..b6a84121f 100644 --- a/src/main/java/de/neemann/digital/core/element/Keys.java +++ b/src/main/java/de/neemann/digital/core/element/Keys.java @@ -762,4 +762,11 @@ public final class Keys { */ public static final Key MIDI_PROG_CHANGE = new Key<>("midiProgChange", false); + + public static final Key TRANSISTORS = + new Key.KeyInteger("transistors", 0) + .setMin(0) + .setSecondary(); + + } diff --git a/src/main/java/de/neemann/digital/draw/elements/Stats.java b/src/main/java/de/neemann/digital/draw/elements/Stats.java new file mode 100644 index 000000000..d2648f430 --- /dev/null +++ b/src/main/java/de/neemann/digital/draw/elements/Stats.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2019 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.draw.elements; + +import de.neemann.digital.core.arithmetic.Add; +import de.neemann.digital.core.arithmetic.Comparator; +import de.neemann.digital.core.arithmetic.Sub; +import de.neemann.digital.core.basic.*; +import de.neemann.digital.core.element.ElementAttributes; +import de.neemann.digital.core.element.ElementTypeDescription; +import de.neemann.digital.core.element.Key; +import de.neemann.digital.core.element.Keys; +import de.neemann.digital.core.flipflops.FlipflopD; +import de.neemann.digital.core.io.Const; +import de.neemann.digital.core.io.In; +import de.neemann.digital.core.io.Out; +import de.neemann.digital.core.memory.RAMDualPort; +import de.neemann.digital.core.memory.RAMSinglePort; +import de.neemann.digital.core.memory.ROM; +import de.neemann.digital.core.memory.Register; +import de.neemann.digital.core.switching.NFET; +import de.neemann.digital.core.switching.PFET; +import de.neemann.digital.core.wiring.*; +import de.neemann.digital.draw.library.ElementLibrary; +import de.neemann.digital.draw.library.ElementNotFoundException; +import de.neemann.digital.lang.Lang; + +import javax.swing.event.TableModelListener; +import javax.swing.table.TableModel; +import java.util.*; + +public class Stats { + private static ArrayList RELEVANT_KEYS = new ArrayList<>(); + + static { + RELEVANT_KEYS.add(Keys.INPUT_COUNT); + RELEVANT_KEYS.add(Keys.BITS); + RELEVANT_KEYS.add(Keys.ADDR_BITS); + RELEVANT_KEYS.add(Keys.SELECTOR_BITS); + } + + private static HashMap TRANSISTORS = new HashMap<>(); + + static { + add(NFET.DESCRIPTION, ElementAttributes::getBits); + add(PFET.DESCRIPTION, ElementAttributes::getBits); + + add(NAnd.DESCRIPTION, attr -> attr.get(Keys.INPUT_COUNT) * attr.getBits() * 2); + add(And.DESCRIPTION, attr -> (attr.get(Keys.INPUT_COUNT) * 2 + 2) * attr.getBits()); + add(NOr.DESCRIPTION, attr -> attr.get(Keys.INPUT_COUNT) * attr.getBits() * 2); + add(Or.DESCRIPTION, attr -> (attr.get(Keys.INPUT_COUNT) * 2 + 2) * attr.getBits()); + add(XOr.DESCRIPTION, attr -> (attr.get(Keys.INPUT_COUNT) * 8) * attr.getBits()); + add(XNOr.DESCRIPTION, attr -> (attr.get(Keys.INPUT_COUNT) * 8 + 1) * attr.getBits()); + add(Not.DESCRIPTION, attr -> 2 * attr.getBits()); + add(Driver.DESCRIPTION, attr -> 6 * attr.getBits()); + add(DriverInvSel.DESCRIPTION, attr -> 6 * attr.getBits()); + add(DriverInvSel.DESCRIPTION, attr -> 6 * attr.getBits()); + add(Multiplexer.DESCRIPTION, attr -> { + int sel = attr.get(Keys.SELECTOR_BITS); + final int andInputs = sel + 1; + final int orInputs = 1 << sel; + return sel * 2 + ((1 << sel) * (andInputs * 2 + 2) + (orInputs * 2 + 2)) * attr.getBits(); + }); + add(Demultiplexer.DESCRIPTION, attr -> { + int sel = attr.get(Keys.SELECTOR_BITS); + final int andInputs = sel + 1; + return sel * 2 + (1 << sel) * (andInputs * 2 + 2) * attr.getBits(); + }); + add(Decoder.DESCRIPTION, attr -> { + int sel = attr.get(Keys.SELECTOR_BITS); + return sel * 2 + (1 << sel) * (sel * 2 + 2) * attr.getBits(); + }); + add(FlipflopD.DESCRIPTION, attr -> 26 * attr.getBits()); + add(Register.DESCRIPTION, attr -> 30 * attr.getBits()); + + + add(Add.DESCRIPTION, attr -> 28 * attr.getBits()); + add(Sub.DESCRIPTION, attr -> 28 * attr.getBits()); + add(Comparator.DESCRIPTION, attr -> 38 * attr.getBits()); + final TransistorCalculator ram = attr -> 40 * attr.get(Keys.ADDR_BITS) + (1 << attr.get(Keys.ADDR_BITS)) * attr.getBits() * 6; + add(RAMDualPort.DESCRIPTION, ram); + add(RAMSinglePort.DESCRIPTION, ram); + add(ROM.DESCRIPTION, attr -> 40 * attr.get(Keys.ADDR_BITS) + (1 << attr.get(Keys.ADDR_BITS)) * attr.getBits()); + } + + private static void add(ElementTypeDescription description, TransistorCalculator tc) { + TRANSISTORS.put(description.getName(), tc); + } + + private final ElementLibrary library; + private TreeMap map; + + public Stats(ElementLibrary library) { + this.library = library; + map = new TreeMap<>(); + } + + public Stats add(Circuit circuit) throws ElementNotFoundException { + for (VisualElement ve : circuit.getElements()) { + if (ve.equalsDescription(In.DESCRIPTION) || + ve.equalsDescription(Out.DESCRIPTION) || + ve.equalsDescription(Const.DESCRIPTION) || + ve.equalsDescription(Tunnel.DESCRIPTION) || + ve.equalsDescription(Splitter.DESCRIPTION) || + ve.equalsDescription(Clock.DESCRIPTION)) + continue; + + ElementAttributes attr = ve.getElementAttributes(); + ElementTypeDescription description = library.getElementType(ve.getElementName()); + add(description, attr); + } + return this; + } + + private void add(ElementTypeDescription description, ElementAttributes attr) throws ElementNotFoundException { + int transistors = 0; + Circuit childCircuit = null; + if (description instanceof ElementLibrary.ElementTypeDescriptionCustom) { + ElementLibrary.ElementTypeDescriptionCustom c = (ElementLibrary.ElementTypeDescriptionCustom) description; + childCircuit = c.getCircuit(); + transistors = childCircuit.getAttributes().get(Keys.TRANSISTORS); + if (transistors > 0) + childCircuit = null; + } else { + TransistorCalculator tr = TRANSISTORS.get(description.getName()); + if (tr != null) + transistors = tr.transistors(attr); + } + + EntryKey key = new EntryKey(description, attr, transistors); + Entry e = map.get(key); + if (e == null) { + e = new Entry(key); + map.put(key, e); + } + e.addOne(); + + if (childCircuit != null) + add(childCircuit); + } + + public TableModel getTableModel() { + final ArrayList entries = new ArrayList<>(map.size()); + int tr = 0; + for (Entry e : map.values()) { + entries.add(e); + tr += e.getTransistors(); + } + final int transistors = tr; + entries.add(new Row() { + @Override + public int getCount() { + return 0; + } + + @Override + public String getDescription() { + return Lang.get("stat_sum"); + } + + @Override + public int getTransistorsEach() { + return 0; + } + + @Override + public int getTransistors() { + return transistors; + } + }); + + return new MyTableModel(entries); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + int transistors = 0; + for (Entry e : map.values()) { + sb.append(e.toString()); + sb.append("\n"); + transistors += e.getTransistors(); + } + if (transistors > 0) + sb.append(transistors).append(" transistors total"); + return sb.toString(); + } + + private static class EntryKey implements Comparable { + private final String name; + private final int transistors; + private final HashMap map; + + private EntryKey(ElementTypeDescription description, ElementAttributes attr, int transistors) { + this.name = description.getTranslatedName(); + this.transistors = transistors; + this.map = new HashMap<>(); + for (Key k : description.getAttributeList()) { + if (RELEVANT_KEYS.contains(k)) + map.put(k, attr.get(k)); + } + } + + @Override + public String toString() { + StringBuilder sb = getDescriptionInt(); + if (transistors > 0) + sb.append("; ").append(transistors).append(" transistors each"); + return sb.toString(); + } + + StringBuilder getDescriptionInt() { + StringBuilder sb = new StringBuilder(name); + if (!map.isEmpty()) { + sb.append(" ("); + boolean first = true; + for (Key k : RELEVANT_KEYS) { + Object v = map.get(k); + if (v != null) { + if (first) + first = false; + else + sb.append(", "); + sb.append(k.toString()); + sb.append(":"); + sb.append(v); + } + } + sb.append(")"); + } + return sb; + } + + public String getDescription() { + return getDescriptionInt().toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EntryKey entryKey = (EntryKey) o; + return transistors == entryKey.transistors && + name.equals(entryKey.name) && + map.equals(entryKey.map); + } + + @Override + public int hashCode() { + return Objects.hash(name, transistors, map); + } + + @Override + public int compareTo(EntryKey entryKey) { + int c = name.compareTo(entryKey.name); + if (c != 0) return c; + for (Key k : RELEVANT_KEYS) { + c = check(k, entryKey); + if (c != 0) return c; + } + return 0; + } + + private int check(Key key, EntryKey entryKey) { + Object a = map.get(key); + Object b = entryKey.map.get(key); + if (a == null & b == null) return 0; + if (a instanceof Long) + return Long.compare((Long) a, (Long) b); + if (a instanceof Integer) + return Integer.compare((Integer) a, (Integer) b); + if (a instanceof String) + return ((String) a).compareTo((String) b); + return 0; + } + + } + + private static final class Entry implements Row { + + private final EntryKey key; + private int count; + + private Entry(EntryKey key) { + this.key = key; + } + + private void addOne() { + count++; + } + + @Override + public String toString() { + return count + " x " + key; + } + + @Override + public int getCount() { + return count; + } + + @Override + public String getDescription() { + return key.getDescription(); + } + + @Override + public int getTransistorsEach() { + return key.transistors; + } + + @Override + public int getTransistors() { + return count * key.transistors; + } + } + + private interface TransistorCalculator { + int transistors(ElementAttributes attr); + } + + private interface Row { + + int getCount(); + + String getDescription(); + + int getTransistorsEach(); + + int getTransistors(); + } + + private static class MyTableModel implements TableModel { + private final List entries; + + private MyTableModel(List entries) { + this.entries = entries; + } + + @Override + public int getRowCount() { + return entries.size(); + } + + @Override + public int getColumnCount() { + return 4; + } + + @Override + public String getColumnName(int i) { + switch (i) { + case 0: + return Lang.get("stat_number"); + case 1: + return Lang.get("stat_part"); + case 2: + return Lang.get("stat_transistors"); + default: + return Lang.get("stat_transistorsTotal"); + } + } + + @Override + public Class getColumnClass(int i) { + if (i == 1) + return String.class; + return Integer.class; + } + + @Override + public boolean isCellEditable(int i, int i1) { + return false; + } + + @Override + public Object getValueAt(int row, int col) { + Row r = entries.get(row); + switch (col) { + case 0: + return check(r.getCount()); + case 1: + return r.getDescription(); + case 2: + return check(r.getTransistorsEach()); + default: + return check(r.getTransistors()); + } + } + + private Object check(int i) { + if (i > 0) return i; + return null; + } + + @Override + public void setValueAt(Object o, int row, int col) { + } + + @Override + public void addTableModelListener(TableModelListener tableModelListener) { + } + + @Override + public void removeTableModelListener(TableModelListener tableModelListener) { + } + } + +} diff --git a/src/main/java/de/neemann/digital/gui/Main.java b/src/main/java/de/neemann/digital/gui/Main.java index 0df32975b..6e3e6c751 100644 --- a/src/main/java/de/neemann/digital/gui/Main.java +++ b/src/main/java/de/neemann/digital/gui/Main.java @@ -378,6 +378,19 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS }); treeCheckBox.setAccelerator(KeyStroke.getKeyStroke("F5")); + ToolTipAction stats = new ToolTipAction(Lang.get("menu_stats")) { + @Override + public void actionPerformed(ActionEvent actionEvent) { + try { + Stats stats = new Stats(library); + stats.add(circuitComponent.getCircuit()); + new StatsDialog(Main.this, stats.getTableModel()).setVisible(true); + } catch (ElementNotFoundException e) { + new ErrorMessage(Lang.get("msg_couldNotCreateStats")).addCause(e).show(Main.this); + } + } + }.setToolTip(Lang.get("menu_stats_tt")); + if (Settings.getInstance().get(Keys.SETTINGS_DEFAULT_TREESELECT)) SwingUtilities.invokeLater(treeCheckBox::doClick); @@ -395,6 +408,7 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS view.add(treeCheckBox); view.addSeparator(); view.add(viewHelp.createJMenuItem()); + view.add(stats.createJMenuItem()); } /** diff --git a/src/main/java/de/neemann/digital/gui/StatsDialog.java b/src/main/java/de/neemann/digital/gui/StatsDialog.java new file mode 100644 index 000000000..2cb9fd8c9 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/StatsDialog.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.gui; + +import de.neemann.digital.lang.Lang; +import de.neemann.gui.Screen; + +import javax.swing.*; +import javax.swing.table.TableColumnModel; +import javax.swing.table.TableModel; +import java.awt.*; + +/** + * Dialog used to show the circuits stats + */ +public class StatsDialog extends JDialog { + + /** + * Creates a new instance + * + * @param frame the parent frame + * @param model the table model + */ + public StatsDialog(Frame frame, TableModel model) { + super(frame, Lang.get("menu_stats")); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + + final JTable table = new JTable(model); + getContentPane().add(new JScrollPane(table)); + final TableColumnModel columnModel = table.getColumnModel(); + final int fontSize = Screen.getInstance().getFontSize(); + columnModel.getColumn(1).setPreferredWidth(fontSize * 60); + table.setPreferredScrollableViewportSize(new Dimension(fontSize * 70, fontSize * 20)); + + pack(); + setLocationRelativeTo(frame); + } +} diff --git a/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java b/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java index 795b58c49..e7733cb35 100644 --- a/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java +++ b/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java @@ -77,6 +77,7 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe ATTR_LIST.add(Keys.SHOW_DATA_GRAPH_MICRO); ATTR_LIST.add(Keys.PRELOAD_PROGRAM); ATTR_LIST.add(Keys.PROGRAM_TO_PRELOAD); + ATTR_LIST.add(Keys.TRANSISTORS); } /** diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index 0056c1d32..5235a234f 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -1319,6 +1319,8 @@ Sind evtl. die Namen der Variablen nicht eindeutig? Programmwechsel erlauben Fügt einen weiteren Eingang PC hinzu. Wird dieser Eingang auf High gesetzt, wird mit dem Wert am Eingang N das Programm (Instrument) gewechselt. + Transistorzahl + Anzahl der erforderlichen Transistoren. Wird für die Schaltungsstatistik verwendet. Null bedeutet, dass die Zahl automatisch bestimmt wird. Leitung eingefügt. Aus Zwischenablage eingefügt. @@ -1542,6 +1544,15 @@ Sind evtl. die Namen der Variablen nicht eindeutig? Ein- und Ausgänge benennen Für alle unbenannten Ein- und Ausgänge eine Bezeichnung setzen. + + Schaltungsstatistik + Zeigt eine Liste der verwendeten Komponenten + Zahl + Bauteil + Trans. + Trans. total + Summe + Fehler beim Öffnen einer PDF-Datei! <h1>Digital</h1>Ein einfacher Simulator für digitale Schaltkreise. diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index 542b41448..bcc707336 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -1309,6 +1309,10 @@ Adds a new input PC. If this input is set to high, the value at input N is used to change te program (instrument). + Transistors + Number of transistors required. Used for circuit statistics. + Zero means that the number is determined automatically. + Inserted wire. Insert from clipboard. Value ''{0}'' in component ''{1}'' modified. @@ -1528,6 +1532,14 @@ Label Inputs and Outputs Set a label to all inputs and outputs without a label. + Circuit Statistics + Shows a list of used components. + Number + Component + Trans. + Trans. total + Sum + <h1>Digital</h1>A simple simulator for digital circuits. Written by H. Neemann in 2016-2019.