Sowas erledigst du entweder mit Warteschleifen, oder mit der Timerhardware und einem Interrupt.
Ich poste mal meine Standardschleife:

Code:
;---Warte 10xakku mikrosekunden, für 12MHz---------------------------------------------------------
;---Nutzt TEMP1 und TEMP2 als Zähler---------------------------------------------------------------
WAIT_10US	
	MOVWF 	TEMP1
WAIT1
	MOVLW 	D'6'
	MOVWF 	TEMP2		
WAIT2	
	DECF 	TEMP2
	BTFSS 	STATUS,Z
	GOTO 	WAIT2
	
	NOP
	DECF 	TEMP1
	BTFSS	STATUS,Z
	GOTO 	WAIT1
	NOP		
	RETURN
;--------------------------------------------------------------------------------------------------

Um diese Schleife für 4Mhz anzupassen, musst du die Zahlenwerte ändern. Ausserdem brauchst du 2 Variablen, die die Zählstände beinhalten, hier TEMP1 und TEMP2. Durch Änderung der Zahlenwerte kannst du natürlich auch eine viel längere Schleife machen, aber weil wir nur Byte-Variablen haben, kann eine Schleife maximal 256x durchlaufen werden.
Ich hab hier eine innere Schleife, die bei WAIT2 bis zum GOTO WAIT2 geht, und eine äußere Schleife von WAIT1 bis GOTO WAIT1.
Die NOP Befehle habe ich als Finetouning eingebaut, um auf möglichst genau die gewünschte Zeit zu kommen.
Du kannst auch noch eine dritte Schleife um alles setzen und auch ne vierte, um die Wartezeit zu verlängern.
Im Code schreibst du dann nurnoch

MOVLW D'100'
CALL WAIT_10ms

und er macht alles selbst, bis nach der gewünschten Zeit (im Beispiel 100x10ms, also 1sek) das Unterprogramm durch ist und es weiter geht.

Mit dem MPLAB-Simulator kann man sowas übrigens hervorragend testen, weil er direkt anzeigen kann, wie lange man von einem Befehl zum nächsten braucht.
Wenn du mehr zu den Timer-Interrupts wissen willst, schau mal bei Sprut, ich hab mir alles über diese Seite beigebracht.