Berkeley Socket API and Python Device?

Qspice new update
08/15/2025 Added some documentation for Berkeley Socket API.
08/15/2025 Implemented a Berkeley Socket API.
08/15/2025 Added some documentation for the SPI and I²C APIs.

Qspice can work with Python now ?!
I have a simple question: what can these actually do? Is it only an implementation of simulation devices from Python, or can it potentially control real-world devices? I don’t quite understand the concept of what the “Socket API” can do. (Or in simpler terms, I don’t know what that is).

There are demos. I haven’t had a chance to look at them yet…

–robert

Kelvin, I’m still working out the exact details but I think I can broadly describe what Mike has done. As always, I could be wrong. :wink:

Note that I don’t think Mike is finished yet. I’m hoping for some documentation on the QSpice API calls and code template generation. Fingers crossed.

Berkeley Sockets API

The Sockets API provides a standard interface that applications can use to exchange data over a network. Clients use the API to establish a connection to a server (using an IP address and port), send and receive arbitrary packets of binary data, detect connection failures, etc. Servers use the API to receive connection requests, receive and send arbitrary packets of data, etc.

The QSpice APIs – Client Side

[ See Mike’s demo code in the “C:\Program Files\QSPICE\Examples” folder. Open in File Explorer to see all of the files. ]

What Mike has done is to implement the Sockets API calls into QSpice. This hides some of the API complexity from users. That is, the user specifies the IP address and port and asks QSpice to establish the connection to the server. The user then populates a buffer with data and asks QSpice to send the data to the server. The user then asks QSpice to get the server response which is returned in a buffer.

Comparing the demo schematics, the only difference is the string attribute:

  • SocketAPI.qsch: “char *server=localhost:1024/SquarerService.exe”
  • PythonAPI.qsch: “char *server=localhost:1024/SquareService.py”

Both schematics call squarer.dll. The squarer.cpp does several things:

  • Calls the QSpice BerkeleySocket() API passing the string attribute to QSpice. QSpice will attempt to open a connection to localhost (127.0.0.1) on port 1024. If the connection fails, QSpice will attempt to start SquarerService.exe or SquareService.py and retry the connection request. (Starting the server is not part of the Sockets API – it’s just a convenience that Mike added.)

  • In each of the standard callbacks (the evaluation function, MaxExtStepSize(), and Trunc()), there is a call to QSpice’s ConfigureBuffer() method. The main thing to note is that the second parameter is different for each standard callback. This is used on the server end to select which of the three operations to perform. Then the buffer data is populated with input port values (if relevant) and sent to the server (SocketSend()). Then the server response is received and port data is set from response data (SocketRecv()).

To be clear: squarer.dll is just a stub. It packages up data from QSpice, sends it to the server, gets the response, and finishes up responding to QSpice (setting output port states, returning MaxExtStepSize() value, or setting Trunc() timestep).

I’m still working out some things about the buffer data. It’s not obvious to me how to send arbitrary data (e.g., a string attribute). But I’m sure that it’s possible.

I’m also uncertain about where to store per-instance data. Obviously, the server will need to have it so, logically, it should be stored there and not stored in the stub per-instance data. Otherwise, it would need to be passed to the server for each server request adding to the packet size. Anyway, I need to do further investigation…

Server Side

Mike has provided example server code: SquarerService.cpp and SquareService.py.

Compare the *.cpp and *.py code. The *.cpp is much more complicated because C/C++ has no built-in networking support. It uses the Sockets API to implement the various server functions. Python has built-in networking support so the code is much simpler.

But both of these do the same thing:

  • Listen for a connection request and accept the request.
  • Go into a loop until the client drops the connection.

In the loop:

  • Receive a client message.
  • Use the first byte of the message to determine the message request type (evaluation function, MaxExtStepSize(), or Trunc() request).
  • Perform the requested function.
  • Send a message to the client with the function result.

As mentioned above, I’m not sure that per-instance data can be added here, at least in the C++ version. (I’m not a Python user so no idea about that version.) I expected to see connections to be spun off into separate processes/threads with their own thread-local variables…


OK, I’ll stop here. Much more to learn so, if anyone has additional information or corrections, please share.

–robert

1 Like

OK, Mike has added template generation and Help documentation. Sweet.

Here’s a bit more information to get folk started.

The DLL Client Stub & Server Template Code

To generate template code, you create the hierarchical DLL block as before. Add/configure ports. Add a string attribute for the server name, port, and program (*.exe or *.py). I think that this must be the second string attribute (after the DLL name) so do this before adding other string attributes. (Note that additional string attributes are not directly supported – see below.) For example:

  • For C++ server: “char *server=localhost:1024/SquarerService.exe”
  • For Python server: “char *server=localhost:1024/SquareService.py”

If you have multiple schematic instances of the DLL block, you may want to omit the port part (e.g., “:1024”) to let QSpice generate unique ports. Otherwise, I think you must make the port specification unique in each instance.

Right-click the block for the expanded template generation menu.

Click “Create C++ Client DLL” to generate the DLL stub code. Likewise, click the “Create … Server Template” to match the server type specified in the second string attribute.

Note: The DLL stub filename is taken from the 1st string attribute. The server code filename is taken from the 2nd string attribute.

DLL Block

You must, of course, compile the DLL stub. It’s C++ code and doesn’t change depending upon the server type. As generated, it passes input port data to the server and sets output port data from the server response message.

Unless you need some custom functionality (e.g., passing non-port data), you should not need to modify this code. (See below.)

If you change the DLL block (add/remove ports, etc.), you should regenerate the DLL stub template code, server template code, reapply changes, and recompile.

Passing Non-Port Data

The generated client stub and server code does not pass anything other than port data. If you want to pass String Attribute data, you’ll need to modify the stub and server code.

The stub code does not include most of the extra “extern ‘C’ __declspec(dllexport)” definitions that are generated but the basic C++ DLL template – stuff like *StepNumber, *NumberSteps, *InstanceName, *ForKeeps, etc. The server code also does not provide this data.

All of the above can be done but that discussion is for another day…

Server Code

The generated server code has comments where you need to add the code for component-specific evaluation, MaxExtStepSize(), and Trunc() functions.

If your component needs per-instance data, simply add global variables in the server code. (QSpice launches a separate instance of the server for each schematic component instance. Global variables aren’t shared between server instances.)

Use-Case Considerations

So, when should you use this fancy new Berkeley Sockets API? The following are my thoughts which (as always) may be irrelevant or incorrect.

  • If you want to use Python code for component blocks, this is an easy way to do it.
  • You can write a server in another language of your choosing. You could, for example, create a server program in Java.
  • If you want to write C++ component code, there might be no advantage. It adds inter-process data communication overhead and you lose direct access to String Attributes and the “extern ‘C’” functions/data. I’m not sure there’s an upside unless there’s a need to run the server on another (faster) machine.
  • In all cases, if you want to pass data beyond the input/output port values, you’ll need to write additional code for both the DLL stub and server.

Sorry for the length – just trying to get information out there before I forget. (To be fair, there’s more so it could have been longer. :roll_eyes:)

If anyone has questions or corrections, please jump in.

–robert

----- Edit -----

The String Attribute that names the server (e.g., char *server=localhost:1024/TestSvc.exe) need not be the second String Attribute. QSpice looks for an attribute with char *server="...". If “server” is not found, it will warn that it cannot create templates. (As stated before, there’s no support for passing other String Attributes to the server without modifying the client and server code.)

Your efforts help us get started with this new topic.

A quick note in case I didn’t cover this clearly: You can specify the IP address and port in the string attribute:

  • SocketAPI.qsch: “char *server=192.168.1.155:1024/SquarerService.exe”
  • PythonAPI.qsch: “char *server=192.168.1.155:1024/SquareService.py”

This assumes that you know your PC’s IP address. You can get that from Windows Network Settings but, if your router allocates the address to your PC dynamically using DHCP, then it can change. Also, if you share your project, the address will almost certainly be different on another machine.

For simplicity, I suggest that you use “localhost” or equivalently “127.0.0.1” for the IP address. This ensures that it runs on the current machine. For example:

  • SocketAPI.qsch: “char *server=localhost:1024/SquarerService.exe”
  • PythonAPI.qsch: “char *server=localhost:1024/SquareService.py”

You can also omit the colon/port number and let QSpice select the port. Regardless, it’s possible that you’ll get an error or QSpice may hang if the port is used by another program. I’m uncertain how to identify unused ports so, for now, maybe just try another random port number between 1024 and 65535

The above assumes that you want to run the server locally. If you need to run the server somewhere else, well, please share your methods and results.

–robert

Another cool feature!

I’ve written something similar using pipes for inter-process communication.

My application is to have an ATE (tester) block in the circuit alongside a DUT (device under test) . I send commands to the ATE block to control the input stimuli and to measure the outputs so that I can simulate final test.

FYI, Mike has added demo code for Rust and Java servers.

For Rust, the server is simply a *.exe so no changes to the component “server=” attribute parsing was required. (A server written any any language that produces an *.exe can be created/used.)

For Java, server=[host:port]/ServerName.java will start the server with the equivalent of “java ServerName.java” on the command line. This compiles the *.java in memory and launches it.

Alternatively, you can use server=[host:port]/ServerName.jar which is equivalent to the command line “java -jar ServerName.jar.” The JAR file must have been manually created before running the sim. See comments in the Java demo source code.

One annoyance: QSpice can’t launch a server on another host. If you wanted to run the server on a different host, you’d need to start it manually on that host for each component server=hostaddress:port/. The template server code will, however, terminate the server when the client connection ends so you’d have to restart the server manually before the next simulation. Anyway, it’s not convenient for the more general non-local host server…

I’m working on a more general non-local host that works more like an HTTP server, i.e., accepts multiple client connections at a fixed host:port address. More on that if it works out…

–robert

FYI, an example of a multi-client, multi-threaded server is now available on the dev branch of my QSpice GitHub repository. Look for C-Block Basics #12.

–robert

Update: I added a Python server version today.

–robert