When I'm developing a control system, first I model the system to be controlled in Modelica. Modelica is is a specialized language for simulation of physical systems. OpenModellica is a great open source modelica tool and there are commercial tools as well. The Modelica language specification includes an external interface that's essential for this project. If you new to Modellica I highly recommend Modellica by Example by Dr. Michael Tiller. The full text is available for free online here.
Next I model the controller. At this point I'm trying to figure out the general form of the control loop, if the desired control of the system is even possible ect. Many controllers can be modeled in the Modelica language or even using blocks in the modelica standard library. The modelica language is an equation based language and it's optimized for modelling physical systems but it's very capable of modelling the behavior of the controller as well. I have modeled fairly complex controllers using the modelica language. It's also possible to use modelica external functions to augment or fully implement the controller model at this stage.
When it's time to prototype the controller in hardware, assuming the control algorithm to be implemented is to run on a FPGA, I implement the controller in my hardware description language of choice, verilog. (If you're looking for a way to learn verilog I highly recommend https://alchitry.com/pages/verilog). Once the controller is implemented or even not all the way but implemented enough to be at the point where's is helpful to have some physics to interact with, it's time to do the coupling.
Icarus verilog is an open source verilog simulation tool. Cocotb uses Icarus as a back end (and can use other simuation tools as well...). As stated on it's project website, 'Cocotb is a coroutine based cosimulation library for writing VHDL and Verilog testbenches in Python.' Cocotb gives you a nice enviromnent where you can interact with the verilog controller and also communicate to the modellica simulation.
Basically you need a modellica tool (for example OpenModellica), Cocotb and a simulation backend (Icarus verilog for example). On the modelica side you create an external object that will handle sending and receiving data to and from cocotb and create a block to be inserted into the system model that replaces the modelica version of the controller. Beej's Guide to Network Programming will get you up to speed on to make the modelica external object and cocotb talk over sockets. There is a client and a server. In the past I have made the cocotb part the server and the modelica simulation part the client but in the future I think I will do it the other way because generally the cocotb needs be restarted to make changes more often than the modelica simulation. Doing it this way it will be possible to make shut down the controller simulation and make changes while the modelica simulation stays running.
The sequence of computations and communications are illustrated here:
This coupling method is pretty good for controllers that sample data from sensors. The main limitation of this coupling method is that during each time the hdl simulation (icarus and cocotb) runs it must be known when sensor data must be acquired from the modelica model next. Consider the example where the controller triggers an analog to digital conversion. The time of the ADC conversion is determined by the controller logic so the controller simulation can run up until the trigger and then switch to the physics simulation which will then run up until the time of the ACD conversion. All good here :) Now consider an analog signal determined by some physics in the modelica model connected to a comparitor in turn connected to a pin on the FPGA. This is an input to the controller that can't be...