+jedimatt42 Posted February 1, 2019 Share Posted February 1, 2019 Here is my take on bank switching in cartridge space: https://github.com/jedimatt42/tigcc-cartbanking/ And here is some work in progress using it: https://github.com/jedimatt42/tipicmd -M@ 2 Quote Link to comment Share on other sites More sharing options...
ralphb Posted February 2, 2019 Share Posted February 2, 2019 I'm still having problems of making my own programs run. . #include <system.h> #include <conio.h> #include <string.h> // from libc99, for later #define printf cprintf #define getchar cgetc int main(int argc, char * argv[]) { set_text(); charsetlc(); textcolor(COLOR_WHITE); bgcolor(COLOR_DKBLUE); cursor(1); printf("HELLO WORLD\n"); printf("HELLO GCC\n"); halt(); } . I'm compiling with: . arcturus ~/ti99/sdd99/demo/ > make test /home/ralph/ti99/gcc/bin/tms9900-gcc -I/home/ralph/ti99/gcc/include/libti99 -I/h ome/ralph/ti99/gcc/libc99/include -c test.c -std=c99 -O2 -s -fno-builtin -o test .o /home/ralph/ti99/gcc/bin/tms9900-ld test.o /home/ralph/ti99/gcc/libti99/crt0_ea5 .o --section-start .text=a000 --section-start .data=2080 -M -L/home/ralph/ti99/g cc/lib -lc -lti99 -o test.elf > ea5.map /home/ralph/ti99/gcc/bin/elf2ea5 test.elf test.bin . But when I run this test.bin (an EA5 file), I only get a yellow screen, and a crash. The testlib program in libti99 works, though. Am I missing a crucial step here? Also, why is this very simple program 4K in size? Quote Link to comment Share on other sites More sharing options...
ralphb Posted February 2, 2019 Share Posted February 2, 2019 OK, I found even that bug, I had to swap test.o and crt0_ea5.o for the link step. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted February 2, 2019 Share Posted February 2, 2019 (edited) <snip> Also, why is this very simple program 4K in size? The simple program has included some pretty sophisticated code. printf is actually a small interpreter that understands not just quoted text but also all the escape codes and numeric formatting for different data types. printf is not too big but it typically calls a lot of other routines but here is a printf example: /* Copyright (C) 1991-2018 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see <http://www.gnu.org/licenses/>. */ #include <libioP.h> #include <stdarg.h> #include <stdio.h> #undef printf /* Write formatted output to stdout from the format string FORMAT. */ /* VARARGS1 */ int __printf (const char *format, ...) { va_list arg; int done; va_start (arg, format); done = vfprintf (stdout, format, arg); va_end (arg); return done; } #undef _IO_printf ldbl_strong_alias (__printf, printf); /* This is for libg++. */ ldbl_strong_alias (__printf, _IO_printf); I don't know the TI GCC code but to give you an example of what it can become see this monster: https://code.woboq.org/userspace/glibc/stdio-common/vfprintf.c.html Edited February 2, 2019 by TheBF Quote Link to comment Share on other sites More sharing options...
Tursi Posted February 3, 2019 Share Posted February 3, 2019 (edited) Am I missing a crucial step here? Also, why is this very simple program 4K in size? When you start getting curious about what is taking up space in your program, that's when you start to learn about the MAP file. I'm assuming that will be "ea5.map" in your build there. It describes the layout of all the memory the linker assigned -- all the program, all the data, and all the variables are laid out in there. Takes a bit to learn to read it, but it's worth it. (Edit: but the biggest reason is likely pulling in conio, particularly cprintf as noted. While I wrote my own conio rather than just porting a standard one (as I had to in order to be PD), it's still doing a fair bit of work and cprintf will include a lot of the other support functions. If you drop conio and use putstring() instead (from vdp.h), it will be a lot smaller. It will be smaller still if you can do without carriage returns and scrolling and just use writestring() (also from vdp.h). That said, if you DO want the features of conio, there's no harm in it, and it won't grow much further. Edited February 3, 2019 by Tursi 1 Quote Link to comment Share on other sites More sharing options...
tschak909 Posted February 3, 2019 Share Posted February 3, 2019 If you want to see an example of a larger program that uses libti99: https://github.com/tschak909/platoterm99 -Thom 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted February 3, 2019 Share Posted February 3, 2019 If you want to see an example of a larger program that uses libti99: https://github.com/tschak909/platoterm99 -Thom I am curious. How big is the final binary? (I don't have GCC for TI-99) Quote Link to comment Share on other sites More sharing options...
+arcadeshopper Posted February 3, 2019 Share Posted February 3, 2019 I am curious. How big is the final binary? (I don't have GCC for TI-99) Pretty small two files one 9k and one 6k ea5 load Sent from my LG-H872 using Tapatalk 1 Quote Link to comment Share on other sites More sharing options...
tschak909 Posted February 6, 2019 Share Posted February 6, 2019 Is there a possible compiler bug, here? I have a piece of code, that works across a bunch of other compilers, some of them gcc, some others... It is code that processes incoming character set data, and shrinks the character set on the fly using image processing techniques (essentially re-plotting pixels against sets of tables, split across two different algorithms that are selected dependent on pixel density)... ...however on the TI, I am getting corrupted character set data, for some reason, on both TIPI and RS232 targets, so it's not mangled input (from eager translation) causing the issue... Here is the original code, the relevant function is terminal_char_load() ... /** * PLATOTerm64 - A PLATO Terminal for the Commodore 64 * Based on Steve Peltz's PAD * * Author: Thomas Cherryhomes <thom.cherryhomes at gmail dot com> * * terminal.c - Terminal state functions */ /* Some functions are intentionally stubbed. */ #pragma warn(unused-param, off) #include <stdbool.h> #include <string.h> #include "terminal.h" #include "screen.h" #include "protocol.h" /** * ASCII Features to return in Features */ #define ASC_ZFGT 0x01 #define ASC_ZPCKEYS 0x02 #define ASC_ZKERMIT 0x04 #define ASC_ZWINDOW 0x08 /** * protocol.c externals */ extern CharMem CurMem; extern padBool TTY; extern padBool ModeBold; extern padBool Rotate; extern padBool Reverse; extern DispMode CurMode; extern padBool FlowControl; /** * screen.c externals */ extern unsigned char CharWide; extern unsigned char CharHigh; extern padPt TTYLoc; extern unsigned char already_started; #define FONTPTR(a) (((a << 1) + a) << 1) // Temporary PLATO character data, 8x16 matrix static unsigned char char_data[]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; static unsigned char BTAB[]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}; // flip one bit on (OR) static unsigned char BTAB_5[]={0x08,0x10,0x10,0x20,0x20,0x40,0x80,0x80}; // flip one bit on for the 5x6 matrix (OR) static unsigned char TAB_0_5[]={0x05,0x05,0x05,0x04,0x04,0x04,0x03,0x03,0x02,0x02,0x01,0x01,0x01,0x00,0x00,0x00}; static unsigned char TAB_0_5i[]={0x00,0x00,0x00,0x01,0x01,0x01,0x02,0x02,0x03,0x03,0x04,0x04,0x04,0x05,0x05,0x05}; static unsigned char TAB_0_4[]={0x00,0x00,0x01,0x02,0x02,0x03,0x03,0x04}; // return 0..4 given index 0 to 7 static unsigned char PIX_THRESH[]={0x03,0x02,0x03,0x03,0x02, // Pixel threshold table. 0x03,0x02,0x03,0x03,0x02, 0x02,0x01,0x02,0x02,0x01, 0x02,0x01,0x02,0x02,0x01, 0x03,0x02,0x03,0x03,0x02, 0x03,0x02,0x03,0x03,0x02}; static unsigned char PIX_WEIGHTS[]={0x00,0x00,0x00,0x00,0x00, // Pixel weights 0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00}; static unsigned char TAB_0_25[]={0,5,10,15,20,25}; // Given index 0 of 5, return multiple of 5. static unsigned char pix_cnt; // total # of pixels static unsigned char curr_word; // current word static unsigned char u,v; // loop counters extern unsigned char fontm23[768]; /** * terminal_init() * Initialize terminal state */ void terminal_init(void) { terminal_set_tty(); } /** * terminal_initial_position() * Set terminal initial position after splash screen. */ void terminal_initial_position(void) { TTYLoc.x=0; TTYLoc.y=100; // Right under splashscreen. } /** * terminal_set_tty(void) - Switch to TTY mode */ void terminal_set_tty(void) { if (already_started) screen_clear(); TTY=true; ModeBold=padF; Rotate=padF; Reverse=padF; CurMem=M0; /* CurMode=ModeRewrite; */ CurMode=ModeWrite; /* For speed reasons. */ CharWide=8; CharHigh=16; TTYLoc.x = 0; // leftmost coordinate on screen TTYLoc.y = 495; // Top of screen - one character height } /** * terminal_set_plato(void) - Switch to PLATO mode */ void terminal_set_plato(void) { TTY=false; screen_clear(); } /** * terminal_get_features(void) - Inquire about terminal ASCII features */ unsigned char terminal_get_features(void) { return ASC_ZFGT; /* This terminal can do Fine Grained Touch (FGT) */ } /** * terminal_get_type(void) - Return the appropriate terminal type */ unsigned char terminal_get_type(void) { return 12; /* ASCII terminal type */ } /** * terminal_get_subtype(void) - Return the appropriate terminal subtype */ unsigned char terminal_get_subtype(void) { return 1; /* ASCII terminal subtype IST-III */ } /** * terminal_get_load_file(void) - Return the appropriate terminal loadfile (should just be 0) */ unsigned char terminal_get_load_file(void) { return 0; /* This terminal does not load its resident from the PLATO system. */ } /** * terminal_get_configuration(void) - Return the terminal configuration */ unsigned char terminal_get_configuration(void) { return 0x40; /* Touch panel is present. */ } /** * terminal_get_char_address(void) - Return the base address of the character set. */ unsigned short terminal_get_char_address(void) { return 0x3000; /* What the? Shouldn't this be 0x3800? */ } /** * terminal_mem_read - Read a byte of program memory. * not needed for our terminal, but must * be decoded. */ padByte terminal_mem_read(padWord addr) { return (0xFF); } /** * terminal_mem_load - Write a byte to non-character memory. * not needed for our terminal, but must be decoded. */ void terminal_mem_load(padWord addr, padWord value) { /* Not Implemented */ } /** * Mode5, 6, and 7 are basically stubbed. */ void terminal_mode_5(padWord value) { } void terminal_mode_6(padWord value) { } void terminal_mode_7(padWord value) { } /** * terminal_ext_allow - External Input allowed. Not implemented. */ void terminal_ext_allow(padBool allow) { /* Not Implemented */ } /** * terminal_set_ext_in - Set which device to get input from. * Not implemented */ void terminal_set_ext_in(padWord device) { } /** * terminal_set_ext_out - Set which device to send external data to. * Not implemented */ void terminal_set_ext_out(padWord device) { } /** * terminal_ext_in - get an external input from selected device. * Not implemented. */ padByte terminal_ext_in(void) { return 0; } /** * terminal_ext_out - Send an external output to selected device * Not implemented. */ void terminal_ext_out(padByte value) { } void memset(char* buf, char val, short len) { for (int i=0;i<len;++i) buf[i]=val; } /** * terminal_char_load - Store a character into the user definable * character set. */ void terminal_char_load(padWord charnum, charData theChar) { // Clear char data. memset(char_data,0,sizeof(char_data)); memset(PIX_WEIGHTS,0,sizeof(PIX_WEIGHTS)); memset(&fontm23[FONTPTR(charnum)],0,6); pix_cnt=0; // Transpose character data. for (curr_word=0;curr_word<8;curr_word++) { for (u=16; u-->0; ) { if (theChar[curr_word] & 1<<u) { pix_cnt++; PIX_WEIGHTS[TAB_0_25[TAB_0_5[u]]+TAB_0_4[curr_word]]++; char_data[u^0x0F&0x0F]|=BTAB[curr_word]; } } } // Determine algorithm to use for number of pixels. // Algorithm A is used when roughly half of the # of pixels are set. // Algorithm B is used either when the image is densely or sparsely populated (based on pix_cnt). if ((54 <= pix_cnt) && (pix_cnt < 85)) { // Algorithm A - approx Half of pixels are set for (u=6; u-->0; ) { for (v=5; v-->0; ) { if (PIX_WEIGHTS[TAB_0_25[u]+v] >= PIX_THRESH[TAB_0_25[u]+v]) fontm23[FONTPTR(charnum)+u]|=BTAB[v]; } } } else if ((pix_cnt < 54) || (pix_cnt >= 85)) { // Algorithm B - Sparsely or heavily populated bitmaps for (u=16; u-->0; ) { if (pix_cnt >= 85) char_data[u]^=0xFF; for (v=8; v-->0; ) { if (char_data[u] & (1<<v)) { fontm23[FONTPTR(charnum)+TAB_0_5i[u]]|=BTAB_5[v]; } } } if (pix_cnt >= 85) { for (u=6; u-->0; ) { fontm23[FONTPTR(charnum)+u]^=0xFF; fontm23[FONTPTR(charnum)+u]&=0xF8; } } } } Which gets translated into the following 9900 assembler: pseg even def terminal_initial_position terminal_initial_position li r1, TTYLoc clr *r1+ li r2, >64 mov r2, *r1 b *r11 .size terminal_initial_position, .-terminal_initial_position even def terminal_get_features terminal_get_features li r1, >100 b *r11 .size terminal_get_features, .-terminal_get_features even def terminal_get_type terminal_get_type li r1, >C00 b *r11 .size terminal_get_type, .-terminal_get_type even def terminal_get_subtype terminal_get_subtype li r1, >100 b *r11 .size terminal_get_subtype, .-terminal_get_subtype even def terminal_get_load_file terminal_get_load_file clr r1 b *r11 .size terminal_get_load_file, .-terminal_get_load_file even def terminal_get_configuration terminal_get_configuration li r1, >4000 b *r11 .size terminal_get_configuration, .-terminal_get_configuration even def terminal_get_char_address terminal_get_char_address li r1, >3000 b *r11 .size terminal_get_char_address, .-terminal_get_char_address even def terminal_mem_read terminal_mem_read seto r1 b *r11 .size terminal_mem_read, .-terminal_mem_read even def terminal_mem_load terminal_mem_load b *r11 .size terminal_mem_load, .-terminal_mem_load even def terminal_mode_5 terminal_mode_5 b *r11 .size terminal_mode_5, .-terminal_mode_5 even def terminal_mode_6 terminal_mode_6 b *r11 .size terminal_mode_6, .-terminal_mode_6 even def terminal_mode_7 terminal_mode_7 b *r11 .size terminal_mode_7, .-terminal_mode_7 even def terminal_ext_allow terminal_ext_allow b *r11 .size terminal_ext_allow, .-terminal_ext_allow even def terminal_set_ext_in terminal_set_ext_in b *r11 .size terminal_set_ext_in, .-terminal_set_ext_in even def terminal_set_ext_out terminal_set_ext_out b *r11 .size terminal_set_ext_out, .-terminal_set_ext_out even def terminal_ext_in terminal_ext_in clr r1 b *r11 .size terminal_ext_in, .-terminal_ext_in even def terminal_ext_out terminal_ext_out b *r11 .size terminal_ext_out, .-terminal_ext_out even def memset memset mov r3, r3 jlt L38 jeq L38 clr r4 L37 mov r1, r5 a r4, r5 movb r2, *r5 inc r4 c r4, r3 jne L37 L38 b *r11 .size memset, .-memset even def terminal_char_load terminal_char_load dect r10 mov r9, *r10 li r3, char_data L42 clr r4 movb r4, *r3+ ci r3, char_data+16 jne L42 li r3, PIX_WEIGHTS L43 clr r5 movb r5, *r3+ ci r3, PIX_WEIGHTS+30 jne L43 mov r1, r12 a r1, r12 a r1, r12 a r12, r12 mov r12, r4 ai r4, fontm23 clr r1 L44 mov r4, r3 a r1, r3 clr r7 movb r7, *r3 inc r1 ci r1, >6 jne L44 clr r6 movb r7, r5 seto r4 li r8, >F00 L48 li r3, >1000 L72 ai r3, >FF00 cb r3, r4 jeq L75 L46 movb r3, r0 srl r0, 8 mov *r2, r1 abs r0 jeq $+4 sra r1, 0 andi r1, >1 abs r1 jeq L72 ai r5, >100 mov r0, r7 movb @TAB_0_5(r7), r1 srl r1, 8 movb @TAB_0_25(r1), r1 srl r1, 8 movb @TAB_0_4(r6), r7 srl r7, 8 a r7, r1 li r9, >100 ab r9, @PIX_WEIGHTS(r1) movb r3, r1 xor r8, r1 srl r1, 8 socb @BTAB(r6), @char_data(r1) movb r5, r7 ai r3, >FF00 cb r3, r4 jne L46 L75 inc r6 inct r2 ci r6, >8 jeq L47 movb r7, r5 jmp L48 L47 movb r3, @u movb r7, @pix_cnt li r1, >800 movb r1, @curr_word movb r5, r2 ai r2, >CA00 ci r2, >1EFF jh L49 li r3, >400 li r2, >500 seto r5 li r6, >FE00 jmp L53 L76 movb r3, r4 ai r4, >FF00 cb r4, r6 jeq L52 movb r3, r2 movb r4, r3 L53 li r1, >500 srl r2, 8 mov r2, r4 ai r4, TAB_0_25 a r12, r2 ai r2, fontm23 L73 ai r1, >FF00 cb r1, r5 jeq L76 movb r1, r8 srl r8, 8 movb *r4, r7 srl r7, 8 a r8, r7 cb @PIX_WEIGHTS(r7), @PIX_THRESH(r7) jl L73 socb @BTAB(r8), *r2 jmp L73 L52 movb r1, @u movb r1, @v L62 mov *r10+, r9 b *r11 b @L77 L49 li r1, >F00 li r8, >5400 seto r6 jmp L58 L79 ai r1, >FF00 cb r1, r2 jeq L78 L58 cb r5, r8 jle L54 movb r1, r2 srl r2, 8 inv @char_data(r2) L54 li r2, >800 movb r1, r3 srl r3, 8 mov r3, r7 ai r7, char_data ai r3, TAB_0_5i L74 ai r2, >FF00 cb r2, r6 jeq L79 movb r2, r0 srl r0, 8 movb *r7, r4 srl r4, 8 abs r0 jeq $+4 sra r4, 0 andi r4, >1 abs r4 jeq L74 movb *r3, r4 srl r4, 8 a r12, r4 mov r0, r9 socb @BTAB_5(r9), @fontm23(r4) jmp L74 L78 movb r1, @u movb r1, @v ci r5, >54FF jle L62 li r2, >500 movb r2, r1 seto r4 jmp L61 L80 movb r1, r2 L61 srl r1, 8 a r12, r1 ai r1, fontm23 movb *r1, r3 inv r3 andi r3, >F800 movb r3, *r1 movb r2, r1 ai r1, >FF00 cb r1, r4 jne L80 movb r1, @u mov *r10+, r9 b *r11 L77 .size terminal_char_load, .-terminal_char_load even def terminal_set_plato terminal_set_plato clr @TTY b @screen_clear .size terminal_set_plato, .-terminal_set_plato even def terminal_set_tty terminal_set_tty dect r10 mov r11, *r10 movb @already_started, @already_started jeq L84 bl @screen_clear L84 li r1, >1 mov r1, @TTY clr @ModeBold clr @Rotate clr @Reverse clr @CurMem clr @CurMode li r1, >810 movb r1, @CharWide swpb r1 movb r1, @CharHigh clr @TTYLoc li r1, >1EF mov r1, @TTYLoc+2 mov *r10+, r11 b *r11 .size terminal_set_tty, .-terminal_set_tty even def terminal_init terminal_init b @terminal_set_tty .size terminal_init, .-terminal_init cseg even char_data bss 16 even PIX_WEIGHTS bss 30 pseg .type TAB_0_5, @object .size TAB_0_5, 16 TAB_0_5 byte 5 byte 5 byte 5 byte 4 byte 4 byte 4 byte 3 byte 3 byte 2 byte 2 byte 1 byte 1 byte 1 byte 0 byte 0 byte 0 .type TAB_0_25, @object .size TAB_0_25, 6 TAB_0_25 byte 0 byte 5 byte 10 byte 15 byte 20 byte 25 .type TAB_0_4, @object .size TAB_0_4, 8 TAB_0_4 byte 0 byte 0 byte 1 byte 2 byte 2 byte 3 byte 3 byte 4 .type BTAB, @object .size BTAB, 8 BTAB byte -128 byte 64 byte 32 byte 16 byte 8 byte 4 byte 2 byte 1 .type PIX_THRESH, @object .size PIX_THRESH, 30 PIX_THRESH byte 3 byte 2 byte 3 byte 3 byte 2 byte 3 byte 2 byte 3 byte 3 byte 2 byte 2 byte 1 byte 2 byte 2 byte 1 byte 2 byte 1 byte 2 byte 2 byte 1 byte 3 byte 2 byte 3 byte 3 byte 2 byte 3 byte 2 byte 3 byte 3 byte 2 .type TAB_0_5i, @object .size TAB_0_5i, 16 TAB_0_5i byte 0 byte 0 byte 0 byte 1 byte 1 byte 1 byte 2 byte 2 byte 3 byte 3 byte 4 byte 4 byte 4 byte 5 byte 5 byte 5 .type BTAB_5, @object .size BTAB_5, 8 BTAB_5 byte 8 byte 16 byte 16 byte 32 byte 32 byte 64 byte -128 byte -128 cseg even pix_cnt bss 1 even curr_word bss 1 even u bss 1 even v bss 1 ref TTYLoc ref CharHigh ref CharWide ref CurMode ref CurMem ref Reverse ref Rotate ref ModeBold ref TTY ref screen_clear ref already_started ref fontm23 ...anything hinky? -Thom Quote Link to comment Share on other sites More sharing options...
tschak909 Posted February 9, 2019 Share Posted February 9, 2019 I'm definitely seeing bugs when doing bitwise manipulations with variables that are unsigned chars, I suspect there is some data type promotion to int happening, as changing my padRGB elements for red green and blue from unsigned char to int suddenly made my color mapping code work properly... ...character set loading is still messed up... /** * terminal_char_load - Store a character into the user definable * character set. */ void terminal_char_load(padWord charnum, charData theChar) { // Clear char data. memset(char_data,0,sizeof(char_data)); memset(PIX_WEIGHTS,0,sizeof(PIX_WEIGHTS)); memset(&fontm23[FONTPTR(charnum)],0,6); pix_cnt=0; // Transpose character data. for (curr_word=0;curr_word<8;curr_word++) { for (u=16; u-->0; ) { if (theChar[curr_word] & 1<<u) { pix_cnt++; PIX_WEIGHTS[TAB_0_25[TAB_0_5[u]]+TAB_0_4[curr_word]]++; char_data[u^0x0F&0x0F]|=BTAB[curr_word]; } } } // Determine algorithm to use for number of pixels. // Algorithm A is used when roughly half of the # of pixels are set. // Algorithm B is used either when the image is densely or sparsely populated (based on pix_cnt). if ((54 <= pix_cnt) && (pix_cnt < 85)) { // Algorithm A - approx Half of pixels are set for (u=6; u-->0; ) { for (v=5; v-->0; ) { if (PIX_WEIGHTS[TAB_0_25[u]+v] >= PIX_THRESH[TAB_0_25[u]+v]) fontm23[FONTPTR(charnum)+u]|=BTAB[v]; } } } else if ((pix_cnt < 54) || (pix_cnt >= 85)) { // Algorithm B - Sparsely or heavily populated bitmaps for (u=16; u-->0; ) { if (pix_cnt >= 85) char_data[u]^=0xFF; for (v=8; v-->0; ) { if (char_data[u] & (1<<v)) { fontm23[FONTPTR(charnum)+TAB_0_5i[u]]|=BTAB_5[v]; } } } if (pix_cnt >= 85) { for (u=6; u-->0; ) { fontm23[FONTPTR(charnum)+u]^=0xFF; fontm23[FONTPTR(charnum)+u]&=0xF0; } } } } If I comment out the calls which XOR char_data[], then character set loading MOSTLY works, (but sparsely populated bitmaps do not get correctly transformed, because the algorithm removes too many pixels). I'm really frustrated here, because i'm wondering if I need to give myself a crash course in compiler design to fix this... -Thom Quote Link to comment Share on other sites More sharing options...
+jedimatt42 Posted February 24, 2019 Share Posted February 24, 2019 (Edit: This issue has since been fixed in patch 1.19) So, I'm doing fun stuff with bank switching cartridge development...I'm using libti99 ( and my own code reproduces the same issue )the strlen function, and many others, test for 0 by using the movb *Rx,*Rx idiom... This creates the crazy situation where I cannot naturally get the strlen for a string in ROM. As the test for 0 writes to the rom address which triggers the bankswitching behavior, and then everything goes nuts.I set a breakpoint in classic99: >(6010-7FFF) and sure enough one of these movb to rom happens, my bank gets switched, and I'm in off the rails.I have a couple work-arounds : in this instance, I can copy the string from ROM to RAM first.. or I can go rewrite my copy of libti99's strlen in asm to avoid the compiler's choices.Basically, use of the movb idiom for zero-tests is dangerous given conventional hardware.Anyone know of an elegant workaround? compiler flag?I'm two patches behind (so I'll proceed to patch now), but the change-log for 1.17 doesn't indicate anything regarding this, and I didn't see an entry for 1.18. -M@ 4 Quote Link to comment Share on other sites More sharing options...
+jedimatt42 Posted February 24, 2019 Share Posted February 24, 2019 Still happens in patch 1.18. No reason to have thought it wouldn't. Interestingly this only occurs 3 places in all the libti99 code: twice in strlen, and once in bmputs It happens a bunch in the testlib for libti99... might not be that expensive to abandon the idiom... I assume this is used because it's only a single word instruction. -M@ Quote Link to comment Share on other sites More sharing options...
+jedimatt42 Posted February 24, 2019 Share Posted February 24, 2019 The cleanest work-around I've found is to declare: volatile char zero = 0; and then test for that instead of literal zero. -M@ Quote Link to comment Share on other sites More sharing options...
+TheBF Posted February 25, 2019 Share Posted February 25, 2019 The cleanest work-around I've found is to declare: volatile char zero = 0; and then test for that instead of literal zero. -M@ That's cool. Does it force the compiler to emit a "compare immediate" instruction instead of MOV *Rx,*Rx ? Quote Link to comment Share on other sites More sharing options...
+jedimatt42 Posted February 25, 2019 Share Posted February 25, 2019 ARGGG!!! The 'quote' button on Atariage doesn't work, and posting doesn't work 80% of the time... ---- Comparing against a variable causes a 'C', instead of the movb... It can't be a CI because the value isn't known at the time of assembly... That's why I started with volatile, to make sure the optimizer doesn't make assumptions about the value. -M@ Quote Link to comment Share on other sites More sharing options...
+TheBF Posted February 25, 2019 Share Posted February 25, 2019 Comparing against a variable causes a 'C', instead of the movb... It can't be a CI because the value isn't known at the time of assembly... That's why I started with volatile, to make sure the optimizer doesn't make assumptions about the value. -M@ Ah yes. That makes sense. So would it also work if you used a #define myzero = 0; ( This might emit a CI instruction? Maybe?) I don't use C so I am just curious about how you have to think when coding in C. Quote Link to comment Share on other sites More sharing options...
+jedimatt42 Posted February 25, 2019 Share Posted February 25, 2019 No, a #define won't work as it is a text substitution before compilation. It would leave a literal 0 in the code whenever used, and the optimizer would not be influenced in the direction I want. all the #___ stuff is actually part of a pre-processor language 'The C Pre Processor' (CPP) not to be confused with c++.. Like running a bunch of awk / sed scripts on your code before compiling it. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted February 25, 2019 Share Posted February 25, 2019 I see. Feels like playing piano with gloves on. Quote Link to comment Share on other sites More sharing options...
insomnia Posted February 28, 2019 Author Share Posted February 28, 2019 I saw there was a lot of impact to the problem of using a "movb" instruction to test for zero, so I rushed out a patch to fix this. Jedimatt42 has already written a good description of the problem, so I won't repeat him here. I also realized that there was no changelog entry for 1.18, so I fixed that too. I also changed the installer code to remove the need for a "tree" command, and we now automatically build libgcc. Tschack909, I tried to help you out, but I don't have enough information to help you out. I looked at the assembly, and I couldn't find anything that looked like a problem. So here's the changes for the latest patch: Use the "ci" instruction when testing for 16-bit zero values Use the combination of "jeq" and "cb" to test for 8-bit zero values The "ci" test is pretty self explanatory so there's not really much to say about that. However, I wanted to talk a little more about the 8-bit test since I thought it was kinda neat. Assume we have a value in r1 we want to test for zero. This is the code that the compiler will now emit: >1300 jeq 0 >9801 fffe cb @$-1, r1 The "jeq" instruction is effectively a no-op. Since it's more likely for some random instruction to result in a non-zero value, we save two clocks by not taking the jump. In either case, we will proceed to the "cb" instruction. The "cb" instruction is using the zero of the "jeq" instruction as one of its arguments, and we test against the value in r1 as expected. So by using this combo, we have a bytewise test for zero that's only two bytes and 8 clocks longer than a "ci" instruction. Neat! At any rate, thanks for finding a bug, and let me know if you find any problems. 7 Quote Link to comment Share on other sites More sharing options...
pnr Posted March 3, 2019 Share Posted March 3, 2019 I'm not sure I understand the solution chosen, probably because I misunderstand the context. When testing an int variable for zero, why not use something like "mov *Rx,r0" - assuming that R0 is a scratch register? Or "mov *Rx, @0" - assuming location 0 is ROM? For a char variable "movb *Rx,r0" / "movb *Rx,@0" should work, no? In the alternative, I don't quite see the need for generating the dummy jump on each occasion. Would it not make more sense to define a zero word in the start up code (e.g. named "czero") and generate a "cb @czero,R1" instead? As said, I probably misunderstand the context. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted March 3, 2019 Share Posted March 3, 2019 (edited) I'm not sure I understand the solution chosen, probably because I misunderstand the context. When testing an int variable for zero, why not use something like "mov *Rx,r0" - assuming that R0 is a scratch register? Or "mov *Rx, @0" - assuming location 0 is ROM? For a char variable "movb *Rx,r0" / "movb *Rx,@0" should work, no? In the alternative, I don't quite see the need for generating the dummy jump on each occasion. Would it not make more sense to define a zero word in the start up code (e.g. named "czero") and generate a "cb @czero,R1" instead? As said, I probably misunderstand the context. Your turn of phrase sounds like you might use Forth or are familiar with it. ("define a zero word") I asked a similar question and the reply is #492 "No, a #define won't work as it is a text substitution before compilation. It would leave a literal 0 in the code whenever used, and the optimizer would not be influenced in the direction I want." I read into that, that C does not make it easy to create a constant with a value of zero that GCC will use in an optimal manner in this case. I think genius of the solution is that it creates a byte with the value "zero" for free that is one byte of the JMP instruction. The NOOP instruction machine code is >1000. With that you can see where the "zero" is hiding. You could put a DATA statement before the compare, with zero in it but then you need to "JMP" over the DATA statement to get to the CB. :-) So "borrowing" the zero byte hidden inside the JMP instruction, gives you what you need for free. The experts can weigh in on the accuracy of my explanation. Edited March 3, 2019 by TheBF Quote Link to comment Share on other sites More sharing options...
Tursi Posted March 3, 2019 Share Posted March 3, 2019 He wasn't talking about using a #define so much as just ensuring the startup code includes a zero value that can be used. I actually had a similar patch back in the very early days where I used two bytes of scratchpad just to have a zero to compare against - but this required the runtime to set it up (and nothing else to corrupt it). It wouldn't hurt to have a defined zero value in the final generated code that could be used for compares, as clever as the jeq solution is. DEF START _CZERO DATA >0000 * program enters here START LWPI >8300 * do a bunch of work here * compare byte at R0 to zero CB *R0,@_CZERO Like that. 1 Quote Link to comment Share on other sites More sharing options...
ralphb Posted March 12, 2019 Share Posted March 12, 2019 My apologies if this has been covered here before, but how can I implement a single function in assembly and link it with the rest? Not inlining (God forbid!), but a self-contained function. What is the calling convention? I looked at some generated functions, but only see a lot of registers moving around ... Quote Link to comment Share on other sites More sharing options...
+jedimatt42 Posted March 12, 2019 Share Posted March 12, 2019 My apologies if this has been covered here before, but how can I implement a single function in assembly and link it with the rest? Not inlining (God forbid!), but a self-contained function. What is the calling convention? I looked at some generated functions, but only see a lot of registers moving around ... There is an enlightening thread somewhere on bank switching that details the calling convention the most I've seen..... I've probably been learning assembly mostly from looking at the generated code when my C goes wrong.. R10 is used as the stack pointer... BL is used to call a function. and RT to return (b *R11) when a function is called it expects arguments on registers R1, R2, R3, etc... until they don't fit... An .asm file that defines an address to code can be called as a function, but you'll have to declare a prototype in C for that symbol... so for a function in a .asm referenced as 'myasm' that takes zero arguments, and returns none: extern void myasm(); If you want to be able to pass arguments in a few registers, expect them in order starting with R1... not sure how many it'll use... upto R1 through R9 I think, after that things get put on the stack by the caller. extern void myasm(int argR1, int argR2, int argR3); It seems that since the instruction set prefers the most significant byte for char type args, that passing an 8 bit type will be in the high side of a register... extern void myasm(char argR1); // will put the 8 bit value in R1 << 8 (if that makes sense) return value is returned in R1. Same rules for 8 bit values or 16 bit values.. extern int myasm(); I believe it is the responsibility of the function to 'pop' the stack for any 'pushing' it has done... So R10 should be restored if moved... the stack grows down... so pushing is subtracting 2 from the R10, and storing the value in that location. pop is read, and increment. Things get more complicated if you have a large number of arguments and the system resorts to the stack to pass them. -M@ Quote Link to comment Share on other sites More sharing options...
+chue Posted March 12, 2019 Share Posted March 12, 2019 (edited) My apologies if this has been covered here before, but how can I implement a single function in assembly and link it with the rest? Not inlining (God forbid!), but a self-contained function. What is the calling convention? I looked at some generated functions, but only see a lot of registers moving around ... I'm sure it's possible to link in a "naked" assembly function, but I offer an alternative - write a wrapper method in C that calls your assembly function. The function would contain inline assembly to conform to your assembly function's calling convention. Or inline your assembly function inside of an "empty" wrapper C call. That way you don't have to worry about calling convention. Edited March 12, 2019 by chue Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.