diff --git a/distribution/ReleaseNotes.txt b/distribution/ReleaseNotes.txt index a494497c6..b95fe04cf 100644 --- a/distribution/ReleaseNotes.txt +++ b/distribution/ReleaseNotes.txt @@ -1,6 +1,7 @@ Release Notes HEAD, planned as v0.31 +- Added a run command to the cli to run circuit headless - Main open dialog is able to open FSM and Truth Tables - FSM editor highlights the current transition - Adds drivers with inverted output diff --git a/src/main/java/de/neemann/digital/cli/Main.java b/src/main/java/de/neemann/digital/cli/Main.java index dc17ff211..fb447ee5b 100644 --- a/src/main/java/de/neemann/digital/cli/Main.java +++ b/src/main/java/de/neemann/digital/cli/Main.java @@ -20,6 +20,7 @@ public class Main extends Muxer { addCommand(new CommandLineTester.TestCommand()); addCommand(new SVGExport()); addCommand(new StatsExport()); + addCommand(new Runner()); } /** diff --git a/src/main/java/de/neemann/digital/cli/Runner.java b/src/main/java/de/neemann/digital/cli/Runner.java new file mode 100644 index 000000000..77035da4f --- /dev/null +++ b/src/main/java/de/neemann/digital/cli/Runner.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 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.cli; + +import de.neemann.digital.FileLocator; +import de.neemann.digital.cli.cli.Argument; +import de.neemann.digital.cli.cli.BasicCommand; +import de.neemann.digital.cli.cli.CLIException; +import de.neemann.digital.core.Model; +import de.neemann.digital.core.ModelEventType; +import de.neemann.digital.core.element.ElementAttributes; +import de.neemann.digital.core.element.Keys; +import de.neemann.digital.core.wiring.Clock; +import de.neemann.digital.draw.elements.Circuit; +import de.neemann.digital.draw.model.ModelCreator; +import de.neemann.digital.draw.model.RealTimeClock; +import de.neemann.digital.gui.ProgramMemoryLoader; +import de.neemann.digital.lang.Lang; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +/** + * Is used to run a circuit headless + */ +public class Runner extends BasicCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(Runner.class); + private final Argument digFile; + + /** + * Creates the run command + */ + public Runner() { + super("run"); + digFile = addArgument(new Argument<>("dig", "", false)); + } + + @Override + protected void execute() throws CLIException { + try { + final CircuitLoader circuitLoader = new CircuitLoader(digFile.get(), false); + final Circuit circuit = circuitLoader.getCircuit(); + + long time = System.currentTimeMillis(); + ModelCreator modelCreator = new ModelCreator(circuit, circuitLoader.getLibrary()); + Model model = modelCreator.createModel(true); + + time = System.currentTimeMillis() - time; + LOGGER.debug("model creation: " + time + " ms, " + model.getNodes().size() + " nodes"); + + ArrayList clocks = model.getClocks(); + if (clocks.size() == 0) + throw new CLIException(Lang.get("cli_run_noClock"), null); + + ScheduledThreadPoolExecutor timerExecutor = new ScheduledThreadPoolExecutor(1); + + int threadRunnerCount = 0; + boolean realTimeClockRunning = false; + for (Clock c : clocks) { + int frequency = c.getFrequency(); + if (frequency > 0) { + final RealTimeClock realTimeClock = new RealTimeClock(model, c, timerExecutor, null); + if (realTimeClock.isThreadRunner()) threadRunnerCount++; + realTimeClockRunning = true; + } + } + if (threadRunnerCount > 1) + throw new CLIException(Lang.get("err_moreThanOneFastClock"), null); + + if (!realTimeClockRunning) { + throw new CLIException(Lang.get("cli_run_noClock"), null); + } + + ElementAttributes settings = circuit.getAttributes(); + if (settings.get(Keys.PRELOAD_PROGRAM)) { + File romHex = new FileLocator(settings.get(Keys.PROGRAM_TO_PRELOAD)) + .setBaseFile(new File(digFile.get())) + .locate(); + new ProgramMemoryLoader(romHex, settings.get(Keys.BIG_ENDIAN_SETTING)) + .preInit(model); + } + + model.addObserver(event -> { + if (event.getType() == ModelEventType.POSTCLOSED) { + timerExecutor.shutdownNow(); + } + }, ModelEventType.POSTCLOSED); + + model.init(); + } catch (Exception e) { + throw new CLIException(Lang.get("cli_errorRunningCircuit"), e); + } + } +} diff --git a/src/main/java/de/neemann/digital/draw/model/RealTimeClock.java b/src/main/java/de/neemann/digital/draw/model/RealTimeClock.java index 582047aa1..4790b6d5f 100644 --- a/src/main/java/de/neemann/digital/draw/model/RealTimeClock.java +++ b/src/main/java/de/neemann/digital/draw/model/RealTimeClock.java @@ -92,7 +92,7 @@ public class RealTimeClock implements ModelStateObserverTyped { RealTimeRunner(int delay) { FrequencyCalculator frequencyCalculator; - if (frequency > 2000) + if (frequency > 2000 && status != null) frequencyCalculator = new FrequencyCalculator(status, frequency); else frequencyCalculator = null; @@ -120,10 +120,13 @@ public class RealTimeClock implements ModelStateObserverTyped { ThreadRunner() { thread = new Thread(() -> { LOGGER.debug("thread start"); - FrequencyCalculator frequencyCalculator = new FrequencyCalculator(status, frequency); + FrequencyCalculator frequencyCalculator = null; + if (status != null) + frequencyCalculator = new FrequencyCalculator(status, frequency); while (!Thread.interrupted()) { model.modify(() -> output.setValue(1 - output.getValue())); - frequencyCalculator.calc(); + if (frequencyCalculator != null) + frequencyCalculator.calc(); } }); thread.setDaemon(true); diff --git a/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java b/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java index ac4ef9402..13347c970 100644 --- a/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java +++ b/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java @@ -31,9 +31,10 @@ public class ProgramMemoryLoader implements ModelModifier { /** * Creates a new rom modifier * - * @param romHex the file to load + * @param romHex the file to load + * @param bigEndian reads the file in big endian mode */ - ProgramMemoryLoader(File romHex, boolean bigEndian) { + public ProgramMemoryLoader(File romHex, boolean bigEndian) { this.romHex = romHex; this.bigEndian = bigEndian; } diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index feeb9d0e5..911f20365 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -1813,6 +1813,11 @@ Sind evtl. die Namen der Variablen nicht eindeutig? Fehler bei der Erzeugung der CSV Datei! + Kein aktivierter Takt in der Schaltung gefunden! + Fehler beim Starten der Schaltung! + Start eine Schaltung headless. + Der Dateiname der Schaltung. + Fenster Über Digital Analyse diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index 22cf3e448..3d136ef8a 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -1793,6 +1793,11 @@ Error while creating the stats file! + No running clock found in circuit! + Error running the circuit! + Runs a circuit headless. + File name of the circuit. + Windows About Analysis