.TITLE CVT - GENERALIZED BASE CONVERSION ROUTINE .IDENT /3.1/ .ENABL LC ;+ ; ; Description: ; ; This program accepts ASCII ("), DECIMAL, OCTAL (#), RADIX-50 (%) or ; HEXIDECIMAL ($) numbers and displays that number in the other radixes. ; ; Examples: ; ; >CVT 0 ; Decimal Word/Bytes Octal Word/Bytes Hexadecimal Radix-50 ASCII EBCDIC ; ------------------ ---------------- ----------- -------- ----- ------ ; 00000 000,000 #000000 #000,000 $0000 % NUL,NUL NUL,NUL ; >CVT 65,66 ; Decimal Word/Bytes Octal Word/Bytes Hexadecimal Radix-50 ASCII EBCDIC ; ------------------ ---------------- ----------- -------- ----- ------ ; 16961 065,066 #041101 #101,102 $4241 %JXA A,B , ; ;- .ENABL AMA .NLIST BEX .MCALL ALUN$, DIR$, GMCR$, QIOW$, EXIT$S ; ; Local equates. ; TILUN == 5 ; LUN FOR TI0: TIEFN == 5 ; EFN FOR TI0: CR == 15 ; CARRIAGE RETURN LF == 12 ; LINE FEED TAB == 11 ; HORIZONTAL TAB BUFSIZ == 100. ; BUFFER SIZE IN WORDS ; ; ASCII messages and buffers. ; PR: .ASCII /CVT> / PRL = .-PR HELP:: .BYTE , .ASCII %This program is used to convert Decimal, Octal, Hexadecimal, Radix-50,% .BYTE , .ASCII %ASCII, and EBCDIC to the equivalent in other radices. Either bytes,% .BYTE , .ASCII %a word, a range, or wildcard can be specified.% .BYTE ,,, .ASCII %Valid commands are:% .ASCII \nnn = decimal number, #nnn = octal number, $nnn = Hexadecimal,\ .BYTE , .ASCII \"xx = ASCII string, 'x = ASCII byte, %xxx = Radix-50 string,\ .BYTE , .ASCII \~xx = EBCDIC string, * = wildcard (displays the entire table).\ .BYTE ,,, .ASCII %Examples:% .ASCII \CVT> 65,66 - Specified bytes are in decimal.\ .ASCII \ #41101 - Specified word is in octal.\ .ASCII \ ~A-Z - Displays EBCDIC A-Z inclusive.\ HELPL = .-HELP .EVEN ASC:: .RAD50 /NULSOHSTXETXEOTENQACKBELBS HT LF VT / .RAD50 /FF CR SO SI DLEDC1DC2DC3DC4NAKSYNETB/ .RAD50 /CANEM SUBESCFS GS RS US SP / EBC:: .RAD50 /NULSOHSTXETXFF HT LC DEL SMMVT / .RAD50 /FF CR SO SI DLEDC1DC2TM RESNL BS IL / .RAD50 /CANEM CC CU1IFSIGSIRSIUSDS SOSFS / .RAD50 /BYPLF ETBESC SM CU2 ENQACKBEL/ .RAD50 / SYN PN RS UC EOT CU3/ .RAD50 /DC4NAK SUBSP / OBUF: .ASCII %Decimal Word/Bytes Octal Word/Bytes Hexadecimal % .ASCII %Radix-50 ASCII EBCDIC% .ASCII %------------------ ---------------- ----------- % .ASCII %-------- ----- ------% OBUFL = .-OBUF BUF:: .BLKW BUFSIZ ; CONVERTED OUTPUT BUFFER ; ; Directive Parameter Blocks. ; GMCR:: GMCR$ ; GET COMMAND LINE DPB TIASN:: ALUN$ TILUN,TI,0 ; ASSIGN LUN FOR TI0: ATTACH: QIOW$ IO.ATT,TILUN,TIEFN ; ATTACH TERMINAL DPB DETACH: QIOW$ IO.DET,TILUN,TIEFN ; DETACH TERMINAL DPB WRTHLP: QIOW$ IO.WVB,TILUN,TIEFN,,TIOSB,, GETNUM: QIOW$ IO.RPR,TILUN,TIEFN,,TIOSB,, CVTQIO: QIOW$ IO.WVB,TILUN,TIEFN,,TIOSB,, GETCHA: QIOW$ SF.GMC,TILUN,TIEFN,,TIOSB,, ; GET CTRL/O STATUS CTRLO: .WORD TC.CTS ; CONTROL/O STATUS TIOSB:: .BLKW 2 ; TERMINAL I/O STATUS BLOCK LIMIT:: .WORD 0 ; HIGH LIMIT TO LIST WILD:: .BYTE 0 ; WILDCARD SPECIFIED <> 0 = TRUE INTER:: .BYTE 0 ; INTERACTIVE FLAG <> 0 = TRUE RANGE:: .BYTE 0 ; RANGE WAS SPEIFIED <> 0 = TRUE .EVEN ;+ ; ; Main line code. ; ;- CVT:: DIR$ #TIASN ; ASSIGN LUN TO TI0: DIR$ #ATTACH ; AND ATTACH IT DIR$ #GMCR ; GET COMMAND LINE BCS READ ; IF CS, NO LINE MOV $DSW,R0 ; COPY THE BYTE COUNT BEQ READ ; IF EQ, PROMPT USER MOV #GMCR+G.MCRB,R5 ; START OF COMMAND MOV R0,R1 ; COPY THE BYTE COUNT ; Get MCR line returns task name then command as "CVT nnn". ; The next routine skips past the "CVT " I hope. 10$: CMPB (R5)+,#40 ; FIND A SPACE ? BEQ DOIT ; IF EQ, YES DEC R1 ; ADJUST BYTE COUNT BGT 10$ ; IF GT, MORE TO GO ; Read the number to convert from the user. READ:: CLRB INTER ; INITIALIZE FLAG DIR$ #GETNUM ; REQUEST THE NUMBER MOV #GMCR+G.MCRB,R5 ; POINT TO INPUT BUFFER CMPB TIOSB,#IE.EOF&377 ; WANT TO EXIT ? BNE 20$ ; IF NE, NO EXIT$S ; EXIT TO SYSTEM 20$: INCB INTER ; SHOW WE ARE INTERACTIVE MOV TIOSB+2,R0 ; GET THE BYTE COUNT BEQ READ ; IF EQ, NULL LINE ; Got the number, now check the base and convert it. DOIT:: ADD #GMCR+G.MCRB,R0 ; POINT TO THE TERMINATOR CLRB (R0) ; CONVERT IT TO A NULL MOV #OBUF,CVTQIO+Q.IOPL ; SETUP HEADER ADDRESS MOV #OBUFL,CVTQIO+Q.IOPL+2 ; AND THE BYTE COUNT CLRB WILD ; PRESUME NOT WILDCARD CLRB RANGE ; PRESUME NO RANGE CMPB #'#,(R5) ; OCTAL SPECIFIED ? BEQ OCTAL ; IF EQ, YES CMPB #'%,(R5) ; RADIX-50 SPECIFIED ? BEQ RAD50 ; IF EQ, YES CMPB #'",(R5) ; ASCII WORD SPECIFIED ? BEQ ASCII ; IF EQ, YES CMPB #'',(R5) ; ASCII BYTE SPECIFIED ? BEQ ASCII1 ; IF EQ, YES CMPB #'$,(R5) ; HEXIDECIMAL SPECIFIED ? BEQ HEX ; IF EQ, YES CMPB #'~,(R5) ; EBCDIC BYTES SPECIFIED ? BEQ EBCDIC ; IF EQ, YES CMPB #'*,(R5) ; WILDCARD SPECIFIED ? BNE 10$ ; NO, CHECK FOR HELP INCB WILD ; SHOW WILDCARD SPECIFIED CLR R1 ; START AT THE NUMBER 0 MOV #377,LIMIT ; SET TO HIGHEST LIMIT JMP FORMAT ; AND FORMAT THE BUFFER 10$: CMPB (R5),#'0 ; POSSIBLE NUMBER ? BLT 20$ ; IF LT, NO CMPB (R5),#'9 ; REALLY A NUMBER ? BLE DECIMAL ; IF LE, YES 20$: DIR$ #WRTHLP ; NO, GIVE USER SOME HELP JMP READ ; AND PROMPT AGAIN ; Decimal ASCII number specified (i.e., xxxxx or xxx,xxx) ; $CDTB - Convert ASCII decimal number to binary. DECIMAL:: MOV R5,R0 ; COPY BUFFER ADDRESS CALL $CDTB ; CONVERT DECIMAL ASCII TO BINARY CMPB #'-,R2 ; SPECIFYING A RANGE ? BNE 10$ ; IF NE, NO INCB RANGE ; SHOW RANGE SPECIFIED BR 20$ ; AND CONTINUE ... 10$: CMPB #',,R2 ; ANOTHER NUMBER ? BNE FORMAT ; IF NE, NO 20$: MOV R1,-(SP) ; SAVE BINARY # ON STACK CALL $CDTB ; CONVERT SECOND NUMBER ASH #8.,R1 ; SHIFT TO HIGH BYTE ADD (SP)+,R1 ; ADD IN LOW BYTE BR FORMAT ; BR TO FORMAT OUTPUT ; Octal ASCII number specified (i.e., #xxxxxx or #xxx,xxx) ; $COTB - Convert ASCII octal number to binary. OCTAL:: INC R5 ; POINT PAST '#' MOV R5,R0 ; COPY BUFFER ADDRESS CALL $COTB ; CONVERT OCTAL ASCII TO BINARY CMPB #'-,R2 ; SPECIFYING A RANGE ? BNE 10$ ; IF NE, NO INCB RANGE ; SHOW RANGE SPECIFIED BR 20$ ; AND CONTINUE ... 10$: CMPB #',,R2 ; ANOTHER NUMBER ? BNE FORMAT ; IF NE, NO 20$: MOV R1,-(SP) ; SAVE BINARY # ON STACK CALL $COTB ; CONVERT SECOND NUMBER ASH #8.,R1 ; SHIFT TO HIGH BYTE ADD (SP)+,R1 ; ADD TO FIRST NUMBER BR FORMAT ; BR TO FORMAT OUTPUT ; Radix-50 ASCII string specified (i.e., %ABC) ; $CAT5B - Convert ASCII with blanks to RADIX-50. RAD50:: INC R5 ; POINT PAST '%' MOV R5,R0 ; COPY BUFFER ADDRESS MOV #1,R1 ; PERIOD IS OK CALL $CAT5B ; CONVERT ASCII TO RADIX-50 BR FORMAT ; BR TO FORMAT OUTPUT ; ASCII string specified (i.e., "AB) ASCII:: INC R5 ; POINT PAST '"' CLR -(SP) ; CLEAR STORAGE AREA MOVB (R5)+,(SP) ; STORE THE FIRST BYTE CMPB #'-,(R5) ; IS A RANGE SPECIFIED ? BNE 10$ ; IF NE, NO INC R5 ; YES, POINT PAST '-' INCB RANGE ; SHOW RANGE SPECIFIED 10$: MOVB (R5)+,1(SP) ; STORE THE SECOND BYTE MOV (SP)+,R1 ; COPY BOTH CHARACTERS BR FORMAT ; AND BR TO FORMAT OUTPUT ; ASCII byte specified (i.e., 'A) ASCII1:: CLRB 2(R5) ; MAKE SECOND BYTE NULL BR ASCII ; TREAT LIKE ASCII WORD ; Hexidecimal ASCII number specified (i.e., $FFFF) HEX:: INC R5 ; POINT PAST '$' CLR R1 ; CLEAR ALL BITS MOV #4,R2 ; SET LOOP COUNT 10$: CMPB (R5),#40 ; CHARACTER LESS THAN SPACE ? BLO 30$ ; IF LO, YES ASH #4,R1 ; SHIFT LEFT 4 BITS ? MOVB (R5)+,R0 ; GET ASCII CHARACTER CMP #'A,R0 ; IS ALPHABETIC ? BHI 20$ ; IF HI, YES SUB #7,R0 ; SET LOOP COUNT 20$: SUB #'0,R0 ; SUBTRACT OUT BASE BIS R0,R1 ; SAVE THESE BITS SOB R2,10$ ; LOOP TILL DONE 30$: BR FORMAT ; GO FORAMT THE BUFFER ; EBCDIC bytes specified (i.e., ~AB) EBCDIC:: INC R5 ; POINT PAST '~' MOVB (R5)+,R2 ; GET ASCII BYTE TO CONVERT BIC #^C377,R2 ; CLEAR SIGN EXTEND (IF ANY) CLR -(SP) ; CLEAR STORAGE AREA MOVB A2ETBL(R2),(SP) ; CONVERT THE FIRST BYTE CMPB #'-,(R5) ; IS A RANGE SPECIFIED ? BNE 10$ ; IF NE, NO INC R5 ; YES, POINT PAST '-' INCB RANGE ; SHOW RANGE SPECIFIED 10$: MOVB (R5)+,R2 ; GET NEXT BYTE TO CONVERT BIC #^C377,R2 ; CLEAR SIGN EXTEND (IF ANY) MOVB A2ETBL(R2),1(SP) ; CONVERT THE SECOND BYTE MOV (SP)+,R1 ; COPY NUMBER TO CONVERT BR FORMAT ; AND FORMAT OUTPUT BUFFER ;+ ; ; FORMAT - Format the output buffer. ; ; This routine takes a binary number and converts it to the ; various radixes and stores them in the output buffer. ; ; Inputs: ; R1 = The binary number to convert. ; ;- FORMAT:: DIR$ #CVTQIO ; OUTPUT THE HEADER MOV #BUF,CVTQIO+Q.IOPL ; SET NEW OUTPUT ADDRESS TSTB RANGE ; WAS A RANGE SPECIFIED ? BEQ LOOP ; IF EQ, NO MOV R1,-(SP) ; COPY THE NUMBER CMPB (SP),1(SP) ; RANGE REVERSED ? BLOS 10$ ; IF LOS, NO SWAB (SP) ; YES, HIGH # TO HIGH BYTE 10$: CLR LIMIT ; INITIALIZE LIMIT BISB 1(SP),LIMIT ; SAVE THE HIGH NUMBER CLR R1 ; INITIALIZE START # BISB (SP),R1 ; COPY THE LOW NUMBER TST (SP)+ ; CLEAN THE STACK INCB WILD ; NOW TREAT LIKE WILDCARD LOOP:: ; LOOP HERE FOR WILDCARD MOV #BUFSIZ,R0 ; NUMBER OF WORDS MOV #BUF,R5 ; IN OUTPUT BUFFER ; Fill the entire output buffer with spaces so all we need to do is ; skip over bytes for our spacing. 10$: MOV #" ,(R5)+ ; FILL WITH SPACES SOB R0,10$ ; LOOP TILL DONE MOV #BUF,R0 ; SETUP OUTPUT ADDRESS ; *** Decimal Word/Bytes *** ; ; $CBDMG - Convert Binary to Decimal Magnitude. MOV R1,-(SP) ; SAVE THE WORD VALUE ADD #2,R0 ; SKIP 2 SPACES MOV #1,R2 ; REQUEST LEADING ZEROS CALL $CBDMG ; CONVERT BINARY TO DECIMAL ADD #2,R0 ; SKIP TWO SPACES CLR R1 ; INIT NUMBER TO CONVERT BISB (SP),R1 ; GET LOW ORDER BYTE MOV #015012,R2 ; RADIX=10.,LEADING ZEROS,WIDTH=3 CALL $CBTA ; CONVERT BINARY TO DECIMAL MOVB #',,(R0)+ ; MOVE IN DELIMETER CLR R1 ; INIT NUMBER TO CONVERT BISB 1(SP),R1 ; NOW GET HIGH BYTE MOV #015012,R2 ; RADIX=10.,LEADING ZEROS,WIDTH=3 CALL $CBTA ; CONVERT BINARY TO DECIMAL ADD #4,R0 ; SKIP 4 SPACES ; *** Octal Word/Bytes *** ; ; $CBOMG - Convert Binary to Octal Magnitude (field width 6). ; $CBTMG - Convert Binary to Octal Magnitude (field width 3). MOVB #'#,(R0)+ ; SHOW OCTAL NUMBER MOV (SP),R1 ; GET WORD TO CONVERT MOV #1,R2 ; REQUEST LEADING ZEROS CALL $CBOMG ; CONVERT BINARY TO OCTAL ADD #1,R0 ; SKIP 1 SPACE MOVB #'#,(R0)+ ; SHOW OCTAL NUMBERS CLR R1 ; CLEAR ALL BITS BISB (SP),R1 ; GET THE LOW BYTE MOV #1,R2 ; REQUEST LEADING ZEROS CALL $CBTMG ; CONVERT BINARY TO OCTAL MOVB #',,(R0)+ ; MOVE IN DELIMETER CLR R1 ; CLEAR ALL BITS BISB 1(SP),R1 ; NOW GET THE HIGH BYTE MOV #1,R2 ; REQUEST LEADING ZEROS CALL $CBTMG ; CONVERT BINARY TO OCTAL ADD #5,R0 ; SKIP 5 SPACES ; *** Hexadecimal Word *** ; ; $CBTA - Convert Binary to Hexadecimal ASCII. MOVB #'$,(R0)+ ; SHOW HEXIDECIMAL NUMBER MOV (SP),R1 ; GET NUMBER TO CONVERT MOV #8720.,R2 ; RADIX=16.,LEADING ZEROS,WIDTH=4 CALL $CBTA ; CONVERT TO HEXADECIMAL ADD #7,R0 ; SKIP 7 SPACES ; *** Radix-50 *** ; ; $C5TA - Convert Radix-50 to ASCII. MOVB #'%,(R0)+ ; SHOW RADIX-50 NUMBER MOV (SP),R1 ; GET NUMBER TO CONVERT CALL $C5TA ; CONVERT RADIX-50 TO ASCII ADD #3,R0 ; SKIP 3 SPACES ; *** ASCII Equivalent *** MOV #2,R4 ; NUMBER OF ASCII BYTES ; MOVB #'",(R0)+ ; SHOW ASCII CHARACTERS MOVB (SP),R1 ; COPY THE FIRST BYTE BIC #^C377,R1 ; CLEAR SIGN EXTEND (IF ANY) 15$: CMPB R1,#40 ; SPACE OR CONTROL CHAR ? BLOS 20$ ; IF LOS, YES (SPECIAL) CMPB R1,#177 ; IS IT A DELETE ? BEQ 23$ ; IF EQ, YES (SPECIAL) BHI 25$ ; IF HI, NON-EXISTENT CMP R4,#2 ; IS THIS FIRST BYTE ? BNE 17$ ; IF NE, NO (SPACE AFTER) ADD #2,R0 ; PAD ON LEFT WITH 2 SPACES 17$: MOVB R1,(R0)+ ; MOVE IN ASCII CHARACTER CMPB R4,#2 ; IS THIS FIRST BYTE ? BEQ 30$ ; IF EQ, YES (ALREADY SPACED) ADD #2,R0 ; NO, SPACE AFTER BYTE BR 30$ ; BR TO CONTINUE 20$: ASL R1 ; SET WORD INDEX MOV ASC(R1),R1 ; GET RADIX-50 EQUIVALENT 22$: CALL $C5TA ; CONVERT RADIX-50 TO ASCII BR 30$ ; BR TO CONTINUE 23$: MOV #^RDEL,R1 ; SET RADIX-50 DELETE BR 22$ ; AND BR TO CONVERT IT 25$: ADD #3,R0 ; USE 3 SPACES FOR NON-EXISTENT ; Get second ASCII byte. 30$: DEC R4 ; DECREMENT COUNT BEQ 100$ ; IF EQ, WE'RE DONE MOVB 1(SP),R1 ; COPY THE SECOND BYTE BIC #^C377,R1 ; CLEAR SIGN EXTEND (IF ANY) MOVB #',,(R0)+ ; SEPARATE BYTES WITH COMMA BR 15$ ; PROCESS THIS BYTE ; *** EBCDIC equivalent *** 100$: ADD #2,R0 ; SKIP 2 SPACES CLR -(SP) ; STORE EBCDIC BYTES HERE MOVB 2(SP),R2 ; GET FIRST BYTE TO CONVERT BIC #^C377,R2 ; CLEAR SIGN EXTEND (IF ANY) MOV R2,R3 ; PRESUME CONTROL CHARACTER BIS #200,R3 ; SHOW SPECIAL CHARACTER CMP R2,#100 ; IS IT A CONTROL CHARACTER ? BLOS 102$ ; IF LOS, YES MOVB E2ATBL(R2),R3 ; GET EBCDIC EQUIVALENT CMPB R2,#340 ; DID WE CONVERT BACKSLASH ? BEQ 102$ ; IF EQ, YES (LEAVE ALONE) CMPB R3,#134 ; CONVERTED BYTE A BACKSLASH ? BNE 102$ ; IF NE, NO (LEAVE ALONE) MOVB #40,R3 ; YES, NO TRANSLATION 102$: MOVB R3,(SP) ; SAVE IT ON THE STACK MOVB 3(SP),R2 ; GET SECOND BYTE TO CONVERT BIC #^C377,R2 ; CLEAR SIGN EXTEND (IF ANY) MOV R2,R3 ; PRESUME CONTROL CHARACTER BIS #200,R3 ; SHOW SPECIAL CHARACTER CMP R2,#100 ; IS IT A CONTROL CHARACTER ? BLOS 104$ ; IF LOS, YES MOVB E2ATBL(R2),R3 ; GET EBCDIC EQUIVALENT CMPB R2,#340 ; DID WE CONVERT BACKSLASH ? BEQ 104$ ; IF EQ, YES (LEAVE ALONE) CMPB R3,#134 ; CONVERTED BYTE A BACKSLASH ? BNE 104$ ; IF NE, NO (LEAVE ALONE) MOVB #40,R3 ; YES, NO TRANSLATION 104$: MOVB R3,1(SP) ; STORE THE HIGH BYTE MOV #2,R4 ; NUMBER OF EBCDIC BYTES MOVB (SP),R1 ; COPY THE FIRST BYTE BIC #^C377,R1 ; CLEAR SIGN EXTEND (IF ANY) 115$: TSTB R1 ; IS THIS A SPECIAL CHARACTER ? BMI 120$ ; IF MI, YES CMP R4,#2 ; IS THIS FIRST BYTE ? BNE 117$ ; IF NE, NO (SPACE AFTER) ADD #2,R0 ; PAD ON LEFT WITH 2 SPACES 117$: MOVB R1,(R0)+ ; MOVE IN ASCII CHARACTER CMPB R4,#2 ; IS THIS FIRST BYTE ? BEQ 130$ ; IF EQ, YES (ALREADY SPACED) ADD #2,R0 ; NO, SPACE AFTER BR 130$ ; BR TO CONTINUE 120$: BIC #200,R1 ; CLEAR SPECIAL BIT ASL R1 ; SET WORD INDEX MOV EBC(R1),R1 ; GET RADIX-50 EQUIVALENT 122$: CALL $C5TA ; CONVERT RADIX-50 TO ASCII BR 130$ ; BR TO CONTINUE 125$: ADD #3,R0 ; USE 3 SPACES FOR NON-EXISTENT ; Get second ASCII byte. 130$: DEC R4 ; DECREMENT COUNT BEQ DONE ; IF EQ, WE'RE DONE MOVB 1(SP),R1 ; COPY THE SECOND BYTE BIC #^C377,R1 ; CLEAR SIGN EXTEND (IF ANY) MOVB #',,(R0)+ ; SEPARATE BYTES WITH COMMA BR 115$ ; PROCESS THIS BYTE ; Done formatting output buffer, write it to the terminal. DONE:: TST (SP)+ ; CLEAN EBCDIC FROM STACK CLRB CTRLO+1 ; CLEAR CONTROL/O STATUS DIR$ #GETCHA ; NOW GET CTRL/O STATUS BITB #2,CTRLO+1 ; DID USER TYPE CTRL/O ? BNE CVTOUT ; IF NE, YES (EXIT) SUB #BUF,R0 ; CALCULATE THE BYTE COUNT MOV R0,CVTQIO+Q.IOPL+2 ; ADD IT TO HEADER COUNT DIR$ #CVTQIO ; NOW OUTPUT THE BUFFER TSTB WILD ; WILDCARD SPECIFIED ? BEQ CVTOUT ; IF EQ, NO (EXIT) INC (SP) ; ADVANCE TO NEXT NUMBER MOV (SP)+,R1 ; CONVERT THIS NUMBER NEXT CMP R1,LIMIT ; REACH LIMIT YET ? BGT CVTOUT ; IF GT, YES (FINISHED) JMP LOOP ; AND DO NEXT NUMBER CVTOUT:: TSTB INTER ; ARE WE RUNNING INTERACTIVE ? BEQ 10$ ; IF EQ, NO JMP READ ; YES, READ NEXT NUMBER 10$: DIR$ #DETACH ; DETACH THE TERMINAL EXIT$S ; EXIT TO SYSTEM .END CVT