Does that work? Can I simulate the PCB hardware in the FPGA and "hook" it into the C based emulator?
Sure. It's just a matter of putting the right components together. Generally, this subject runs under "Co-Simulation" and hasn't obviously got much popularity, due to the costs of such simulation tools in the past, neither does it seem to be taught much in the adacemic places I have been involved with.
What I started with was GHDL, for which I hacked some extensions (ghdlex) to implement virtual UARTs, USB ports, etc. or LCD screens. Nowadays I'm deploying CXXRTL compilates from RTL in two ways: either as Co-Simulation module from Python or for import into the Renode CPU simulator. The latter for instance allows to create a particular SoC peripheral I/O (memory mapped registers, peripheral interfaces, etc) and run it alongside a selected CPU from the Renode library via those virtual interfaces. For example, I've built a virtual communication radio setup with LCD and radio chip mockup to test firmware interactively against outside signals in an asynchronous, non-deterministic way. The Python co-simulation part again is more of the deterministic sort to carry out unit or regression tests against external models. The next stage is where the AI fun comes in, such as customized agents being allowed to download created images into an FPGA and test register bits through JTAG at free will. For data exchange, the virtual FX2 chip (USB FIFO) does then exactly the same as its simulated counter part.
For the academic side of it, I've played with Jupyter Notebooks a while ago to allow remote-players to verify virtual hardware against their models, for instance a JPEG encoder cosimulation (
https://gist.github.com/hackfin/0b124e280c42f5099aa7d6464cf19ee9).
This turned out an extremely powerful method, compared with the classical rather tedious testing using legacy tools.
Plus, it's easy to turn working Python prototypes into real hardware and have them verified kinda automatically. So the C/C++ part of the game is merely glue between APIs, which you also don't have to deal much anymore using Cython.
As you're already familiar with Verilator, you might find it straightforward to integrate CXXRTL into your setup and just call the .step() method from your main loop to get maximum fine grained control over your interaction with the simulation.