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.