In my implementation I simply set my run loop to break out if last_pc == cpu.pc.
Thus an HLT instruction is virtually equivalent to an instruction setting the PC back to PC itself, which, unless an outside force sets PC or [PC] to something else (think shared memory between multiple CPUs), is an unbreakable infinite loop.