; ; Self-modifying code ; https://en.wikipedia.org/wiki/Self-modifying_code ; .ORG 100H ; CP/M programs start address JP START ; Go to program start ; ; Variable storage space ; Stack1 .DW 0 ; Place to save old stack Sbot .DS 64 ; Temp stack for us to use ; ; Constants ; STOP .EQU $-1 ; Top of our stack BDOS .EQU 5 ; Address of BDOS entry ; ; Peripheral I/O adresses ; LEDS .EQU 00H ; Digital I/O board PIO1_A_D .EQU 068H ; Z80 PIO #1 PIO1_A_C .EQU 06AH PIO1_B_D .EQU 069H PIO1_B_C .EQU 06BH PIO2_A_D .EQU 06CH ; Z80 PIO #2 PIO2_A_C .EQU 06EH PIO2_B_D .EQU 06DH PIO2_B_C .EQU 06FH PIO3_A_D .EQU 0F0H ; Z80 PIO #3 PIO3_A_C .EQU 0F2H PIO3_B_D .EQU 0F1H PIO3_B_C .EQU 0F3H PIO4_A_D .EQU 0F4H ; Z80 PIO #4 PIO4_A_C .EQU 0F6H PIO4_B_D .EQU 0F5H PIO4_B_C .EQU 0F7H MSG_START .DB "Self-modifying code example", 13, 10, 0 CRLF .DB 13, 10, 0 ; ; Start of code segment ; START: ; ------------------------------------------------------------ ; Starting sequence for CP/M programs LD HL, 0000H ; HL = 0 ADD HL, SP ; HL = SP LD (Stack1), HL ; Save original stack LD HL, STOP ; HL = address of new stack LD SP, HL ; Stack pointer = our stack ; ------------------------------------------------------------ ; Initialize Z80 PIO's LD A, 0FH ; Control word for PIO, set output mode OUT (PIO1_A_C), A ; PIO #1 port A OUT (PIO1_B_C), A ; PIO #1 port B OUT (PIO2_A_C), A ; PIO #2 port A OUT (PIO2_B_C), A ; PIO #2 port B OUT (PIO3_A_C), A ; PIO #3 port A OUT (PIO3_B_C), A ; PIO #3 port B OUT (PIO4_A_C), A ; PIO #4 port A OUT (PIO4_B_C), A ; PIO #4 port B ; Turn all LED's OFF XOR A ; LD A, 0 -> XOR A OUT (LEDS), A OUT (PIO1_A_D), A OUT (PIO1_B_D), A OUT (PIO2_A_D), A OUT (PIO2_B_D), A OUT (PIO3_A_D), A OUT (PIO3_B_D), A OUT (PIO4_A_D), A OUT (PIO4_B_D), A ; ------------------------------------------------------------ ; Self-modifying code LD HL, MSG_START ; MSG_START HL = address of string CALL SEND_STR_CON ; Send string in (HL) to console LD A, 255 ; Number of cycles LD HL, label01 + 1 ; Address of 'CP n' instruction argument to HL LD B, (HL) LD C, PIO2_A_D OUT (C), H ; Send address of modified code to LED's LD C, PIO2_B_D OUT (C), L label00: INC B LD (HL), B ; Modify 'CP n' LD C, LEDS OUT (C), B LD C, PIO1_A_D OUT (C), B ; Send current counter status to LEDs's LD C, B PUSH AF PUSH BC PUSH DE PUSH HL CALL PRINTC POP HL POP DE POP BC POP AF label01: CP 00H ; Value is modified in every cycle CALL DELAY JR NZ, label00 ; LED's OFF LD A, 0 OUT (LEDS), A ; Send A to Digital I/O board ; ------------------------------------------------------------ ; Finish and return to CP/M LD HL, (Stack1) ; HL = entry stack address LD SP, HL ; SP = value on entry RET ; Return control back to CP/M ;-------------------------------------------------------------------------------------- ; DELAY: ; Routine to add a delay ; PUSH AF PUSH BC PUSH DE LD C, 5 DELAY0: LD D, 0FFH DELAY1: LD B, 0FFH DELAY2: NOP DJNZ DELAY2 DEC D JR NZ, DELAY1 DEC C JR NZ, DELAY0 POP DE POP BC POP AF RET ;-------------------------------------------------------------------------------------- ; ; Send zero-terminated string in (HL) to console ; SEND_STR_CON: LD A, (HL) ; Read string char OR A ; Set CPU flags RET Z ; If char = 0 done CALL CONOUT_A ; Send char in A to console INC HL ; Point to next char JR SEND_STR_CON ; Do next char ;-------------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------------- ; ; Routine to print C register as ASCII decimal ; PRINTC: EX AF, AF' ; Save A and F, use AF' for this routine PUSH BC ; In C is character to print EXX ; Save other rgisters POP BC ; POP C' with character to print LD A, C CALL DTOA3D ; Split A register into B, D and E ; Print first digit in B LD A, B CP '0' ; Don't bother printing leading '0' JR Z, PRINTC3 CALL CONOUT_A ; Send char in A to console PRINTC3: ; Print second digit in D LD A, D ; Second digit will be tested for '0' CP '0' ; Don't bother printing leading '0' JR NZ, PRINT_DIGIT2 LD A, B ; First digit will be tested for '0' CP '0' ; Don't bother printing leading '0' JR Z, PRINTC2 LD A, D ; Put back second digit to A PRINT_DIGIT2: CALL CONOUT_A ; Send char in A to console PRINTC2: LD A, E ; Print third digit in E CALL CONOUT_A ; Send char in A to console LD A, 13 CALL CONOUT_A LD A, 10 CALL CONOUT_A EX AF, AF' ; Restore A and F EXX ; Restore other registers RET ;-------------------------------------------------------------------------------------- ; ; Binary to ASCII Decimal, in: A, out: BDE ; DTOA3D: LD B, '0' ; Starting from ASCII '0' DEC B ; Because we are incrementing in the loop LD D, 100 ; Want base 100 please AND A ; Clear C flag DTOA3DLOOP: INC B ; Increase the number of hundreads SUB D ; Take away one unit of hundread from A JR NC, DTOA3DLOOP ; If A still hasn't gone negative, do another ADD A, D ; Decreased it too much, put it back LD D, '0' ; Starting from ASCII '0' DEC D ; Because we are inc'ing in the loop LD E, 10 ; Want base 10 please AND A ; Clear C Flag DTOA2DLOOP: INC D ; Increase the number of tens SUB E ; Take away one unit of ten from A JR NC, DTOA2DLOOP ; If A still hasn't gone negative, do another ADD A, E ; Decreased it too much, put it back ADD A, '0' ; Convert to ASCII LD E, A ; Stick remainder in E RET ;-------------------------------------------------------------------------------------- ; ; Send the character in A to the console ; CONOUT_A: PUSH BC PUSH DE PUSH HL LD E, A LD C, 2 ; BDOS function 2 (C_WRITE) - Console output CALL BDOS ; Call BDOS function - Entered with C=2, E=ASCII character POP HL POP DE POP BC RET ;-------------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------------- .END