Marcel raises an important point, and this point is one of the things I eluded to when I said "there is no best way" to do this from Verilog-A in Xyce, and one of the reasons I pointed you at the Wang/Roychowdhury paper (
https://arxiv.org/abs/1605.04897) in the hopes that you might try reformulating your model to be well-posed and without memory states instead. By applying the guidance in that paper, you'd avoid this issue entirely and get a model that is robust and applicable to all analysis types rather than being tied to transient analysis.
Xyce could, in fact, take multiple nonlinear solver steps in the course of a single time step. Each solver step will call the main analog block of your model, each of which would get the same value from "$realtime". Since your analog block saves the previous time value at the end of the block, that means your deltat values for all nonlinear solver steps other than the first step of a timestep will be zero. So in fact, there is not only not a "best" way to do this from Verilog-A, there isn't even really a good way.
While we continue to assert that the best approach for you is to use a different formulation of your model, since you have decided to go ahead and code your model in C++ directly, there is a way to handle this issue using the Xyce device package capabilities.
Rather than saving "time_last" at the end of the "updateIntermediateVars" block (which is where Xyce/ADMS would put all of the code it generates from your Verilog-A main analog block, and which is called for each nonlinear solve step), you can instead save this state in the "acceptStep" method of the model. acceptStep is only called after a time step is accepted by the time integrator, so it is only called once per time step, and only when that step succeeds. This is the place to save the current time into time_last.
Very few devices in Xyce use this method, because only a few devices store time-dependent state like this (the lossless transmission line uses it, and the code may be found in src/DeviceModelPKG/OpenModels/src/N_DEV_TRA.C and its associated header file). acceptStep is defined in the Device::Instance base class as a no-op virtual method, but specific devices can override the base class method and cause it to do something.
So in your case, should you choose to implement your existing model as-is in C++ without reformulating it, you would declare time_last as a variable in the instance class, initialize it to zero in the Instance::processParams function (where Xyce/ADMS would have put your @(initial_instance) code), use time_last without resetting it in updateIntermediateVars, and implement acceptStep in your device model. Save the current time into time_last in your models acceptStep method.
There is no way in Xyce/ADMS to specify that some computations should be performed in acceptStep rather than in updateIntermediateVars (or even a way of specifying it in Verilog-A, which is deliberately designed to hide simulator details like this from the model developer), so this sort of thing can't be handled well from Verilog-A models.