I'd like to reinforce the point @Boris made: do not use dynamic predicates.
By far the cleanest solution is to use state variables to carry around the current state of the simulated machine. Because of Prolog's single-assignment characteristic, you will always have pairs of these: state before and state after. For registers and memory, the state is best represented as a table which maps register names (or memory addresses) to values. A stack can simply be kept as a list. For example:
main :-
Stack0 = [],
Regs0 = [eax-0, ebx-0, ecx-0, edx-0],
Code = [movi(3,eax), add(eax,7), push(eax), pop(ecx)],
sim_code(Code, Regs0, RegsN, Stack0, StackN),
write(RegsN), nl, write(StackN), nl.
% simulate a sequence of instructions
sim_code([], Regs, Regs, Stack, Stack).
sim_code([Instr|Instrs], Regs0, RegsN, Stack0, StackN) :-
sim_instr(Instr, Regs0, Regs1, Stack0, Stack1),
sim_code(Instrs, Regs1, RegsN, Stack1, StackN).
% simulate one instruction
sim_instr(movi(Value,Reg), Regs0, RegsN, Stack, Stack) :-
update(Regs0, Reg, _Old, Value, RegsN).
sim_instr(add(Reg,Value), Regs0, RegsN, Stack, Stack) :-
update(Regs0, Reg, Old, New, RegsN),
New is Old+Value.
sim_instr(push(Reg), Regs, Regs, Stack, [Val|Stack]) :-
lookup(Regs, Reg, Val).
sim_instr(pop(Reg), Regs0, RegsN, [Val|Stack], Stack) :-
update(Regs0, Reg, _Old, Val, RegsN).
%sim_instr(etc, ...).
% simple key-value table (replace with more efficient library predicates)
lookup([K-V|KVs], Key, Val) :-
( Key==K -> Val=V ; lookup(KVs, Key, Val) ).
update([K-V|KVs], Key, Old, New, KVs1) :-
( Key==K ->
Old = V, KVs1 = [K-New|KVs]
;
KVs1 = [K-V|KVs2],
update(KVs, Key, Old, New, KVs2)
).
In practice, you should replace my simple table implementation (lookup/3, update/5) with an efficient hash or tree-based version. These are not standardised, but you can usually find one among the libraries that come with your Prolog system.