A loop is nothing more than a jump to code that has already run, so that it runs again. This jump can be conditional, so that the loop only runs a limited number of times, or unconditional, so that the loop runs forever.
Code:
MoveSprite:
inc $0203
jmp MoveSprite
The code above is an infinite loop, that will move the sprite forever. However, since it doesn't wait for VBlank before moving the sprite again, you won't be able to see every step of the movement. The sprite will move ridiculously fast. To fix this, you have to wait for VBlank before moving the sprite again. A good way to wait for VBlank is to wait for a flag that is changed in the NMI to change (which will happen when the NMI fires, at the start of VBlank):
Code:
MoveSprite:
inc $0203
WaitVBlank:
lda VBlankFlag
cmp VBlankFlag
beq WaitVBlank
jmp MoveSprite
NMI:
inc VBlankFlag
rti
Not included in the code above is the actual sprite DMA, which is obviously required to display sprites. The DMA can go right after the wait for VBlank (right before the JMP) or in the NMI itself. Both ways will work.
If you want to move the sprite up to a certain point and stop, you can't have an infinite loop, you have to give it a stop condition. In this case, the condition is that the sprite reaches a certain X coordinate:
Code:
MoveSprite:
inc $0203
WaitVBlank:
lda VBlankFlag
cmp VBlankFlag
beq WaitVBlank
lda $0203
cmp #$80
bne MoveSprite
Forever:
jmp Forever
NMI:
inc VBlankFlag
rti
This will move the sprite all the way to coordinate 128, 1 pixel per frame, and then it will stop. For simple movements this kind of solution is acceptable, but for more complex animations you need something more robust, like a scripting system.