How is the code written in ARM7 compatible with ARM9?
A processor is said to be code-compatible with another if their instruction set is compatible. That is all there is to it.

Now, instruction sets can be made compatible whatever their pipeline architecture is. It is right that pipelining can have a consequence on the instructions if you only target execution speed and core silicon area, but there are always workarounds if you need to ensure some compatibility with some existing processor. It may complexify the core, but there is always a way. Look at how the architecture evolved from the 8086 to the newest Pentiums. Yet old code can still be executed.

Regarding the Von Neumann / Harvard differences, it can also be made to have minimal impact if the code and data busses actually end up to the same physical memory blocks with the same adresses (which is the case on all ARM implementations I have seen, except maybe for peripherals memory zones). There may be an impact on corner cases like the need to call specific instructions when the code is self-modifying, but in normal cases, you won't notice.

Regarding memory management, that is another story. This has an impact on the OS level. The MMU is like an additional peripheral, whose configuration has an impact on memory layout, but it doesn't change the instruction set. An algorithm is coded the same way whether there is an MMU or not.