Scripting language
Overview
Starting with version 2.17.03.20 LogicCircuit comes with scripting language. It uses one of the most popular one - Python. Because LogicCircuit is using Microsoft .NET framework the actual implementation of Python is IronPython.
With version 2.24.02.27 the Python upgraded to version 3.
There is a lot of resources on the Internet to get help with IronPython and Python in general.
If you are new to Python you can start with https://www.learnpython.org/
more advanced users can use https://ironpython-test.readthedocs.io/en/latest/reference/
To start using IronPython in LogicCircuit you open console by clicking menu Tools/IronPython Console.
The console let you type python expressions and get results. For starters type:
print("hello, world!")
after standard Python prompt ">>>" and hit Enter. You will see python printing the string. To make things a bit more interactive type:
print("hello, {0}".format(input("Enter your name: ")))
This will try to replace {0} with result of function input. Which in turn will print out prompt for you to type your name. Once you type it and hit Enter you will be greeted.
You can define a function. Just type:
def Greet(name):
and hit enter. You will see that Python did not execute your command. Instead it showed you three dots as a new prompt. That because your statement is incomplete. Just enter another text:
print("hello {0}".format(name))
Please notice that you should prepend the print with a few spaces or tabs. Hit Enter and you will see another three dots prompt. This time just hit enter. This will inform console that you are done entering multiline statement. Now you have your function created and you can call it. Just type:
Greet("place your name here")
You will see Python greeting you.
Of course it is inconvenient to create functions this way. So you can just open your favorite text editor and create as many functions there as you want. In order to run your file, you will need to type in console:
exec(open(r"C:\location\file.py").read())
Please note letter r that prepending the string with the path. This will require to avoid need for doubling the slashes.
Once you have your script in a file you will probably run it more than once. Console remembers your commands and you can press up and down arrows on your keyboard to cycle through history of your commands.
If your script is executing for too long you can terminate it if press Ctrl+C. This will also cancel multiline expression mode. However, if you select something in console it will do normal copy to clipboard operation like in any other editor. If you hit Esc it will clean any text you have typed so far after prompt.
When you are typing in console it can help you by inserting suggestions on the global variables or members of object you have typed so far. To make console insert suggestion just start typing and hit Tab key on your keyboard. Console will try to append suggestions that match the text you typed. If you hit Tab multiple times the previous suggestion will be replaced with new match, cycling through all matching items.
Using script in LogicCircuit
The main reason for the scripting language in LogicCircuit is to let you test your circuits. The test script will set inputs of the circuit to the desired values, evaluate the circuit and read values from outputs. This is actually very similar to what truth table is doing, but in script you can test circuits with a state.
Let’s start with very simple circuit:
This circuit just repeats it’s input. To test it we will need to create a tester object. You can type right in the console or in the file and run it in console the following:
tester = App.CreateTester("Repeater")
Now variable tester contains object you can use to test the circuit.
In order to set value to some input you will need to execute the following statement:
tester.SetInput("x", 1)
The first parameter string x is the name of the input you want to set. The second is the value. Please note the value should fit into bits of the pin. For example, if you have 1-bit pin and will try to set it value to anything but 0 or 1 you will see the error:
After you set all the desired values in the input pins of your circuit you can run it.
tester.Evaluate()
This function will return True if successful and False if there is an oscillation occurred.
Once you have successfully Run the circuit you will need to read values of output pins.
tester.GetOutput("q")
This will get value of output pin q.
Reading of evaluated values in some cases might be more complicated. If your circuit can produce high impedance values, the function GetOutput will not work and output error message:
As the message suggests you should use GetStateOutput function instead in such cases. This function will return 2 bits for every bit of the output pin. The state of this two bits are:
- 0 - the output bit in the high impedance state
- 1 - the output bit in 0 state
- 2 - the output bit in 1 state
It is up to your script to analyze all bits of your output returned by this function. Python provides bitwise operations you can use for that.
Currently you cannot set values with high impedance bits into input pins. If you need to test your circuit with some inputs in such state, you can create a test bed circuit use Tri state gates connected to the circuit you want to test and then in script actually test the test bed circuit instead.
For some real example of script testing circuit you can download samples, open Digital Clock project and run DigitalClockTest.py script.
Other useful functionality.
So the main entry point to access LogicCircuit functionality in your script is App class.
Besides CreateTester method you can call App.ClearConsole() to clear IronPython console.
To access your circuit, you start with App.Editor property. This property returns editor of current project. The editor has bunch of other properties and methods you can use. For example you might want to get name of current project - App.Editor.Project.Name or file currently open - App.Editor.File in order to ensure the project you are testing is the right one.
You can access the main window of LogicCircuit via App.Editor.Mainframe. For example you can put a text in the status bar:
App.Editor.Mainframe.Status = "hello, world!"
Some properties and methods of any window are accessible from the script directly like the status text, but some are not so easy to use. The reason is in Windows any screen object must be accessed only from the execution thread that it was created on. You are running IronPython scripts on its own thread and hence if you try access UI from script directly you will get error message:
It is even possible that you will be able to perform some window operations, but later when you try to navigate to different circuit from UI you will get that same error message. That is because some UI element were created on script thread now and not accessible from UI thread. In such case you can reopen the file.
In order to bypass this problem, you will need to run parts of your script that accessing UI on thread that is owning Window of LogicCircuit. The easiest way to do this is to create a function without arguments and pass it to App.Dispatch() method. Here is the example of function you can create to turn power on and off from script:
def Power(state): def Set(): App.Editor.Power=state App.Dispatch(Set)
Now you can safely call function Power with 1 or 0 argument to turn on or off the power of your circuit.
Here is another fun code snippet for you. Just paste it in your console, hit Enter and enjoy the Matrix.
clr.AddReference("PresentationCore") clr.AddReference("PresentationFramework") def PlayMatrix(): con = System.Windows.Window.GetWindow(System.Windows.Input.Keyboard.FocusedElement).Content con.Background = System.Windows.Media.Brushes.Black con.Foreground = System.Windows.Media.Brushes.GreenYellow App.Dispatch(PlayMatrix)
And speaking of Matrix you should never run any scripts from unknown source. It can harm your computer, stole your private information and so on.
Transactions
In LogicCircuit all the schematics elements are stored in transactional memory. For more details on this you can read this article: Lock free parallel programming: transactions, snapshots, and change descriptions.
As a result, if you want to modify anything you will need to do this inside a transaction. Here is an example of how to change name of the project from your script. First let’s create a function:
def SetProjectName(name): def Set(): App.Editor.Project.Name = name App.InTransaction(Set)
Now you can call it with desired name:
SetProjectName("hello, world")
Now you can open properties of your project via menu Circuit/Project and see the new name.
One of the biggest benefits of using transactional memory is the undo and redo operations are naturally supported. So after you change the name of the project via script you can undo it via menu Edit/Undo, and then redo it again with menu Edit/Redo.
Of course inside of one transaction you can change as many things as you want and all of them can be undone with just one menu click. On the other hand, the series of transactions can be undone in a series of undo actions.
As you probably figure out some changes when they bubbling up to UI will result thread issue errors, so you better do transaction inside of dispatch.
Please note when you are doing some long running operation on UI thread you won’t be able to abort the script with Ctrl+C as this is happening on UI thread and it is busy with your script. So avoid long running operations dispatched.
If this all sounds too complicated don’t warry, if your goal is just test your circuit you can do it with simple App.CreateTester function without any dispatcher or transactional gimmicks.