Showing posts with label SDCC. Show all posts
Showing posts with label SDCC. Show all posts

Tuesday, September 22, 2015

Calling C From Bank 1

One of my goals is to be able to separately compile a program that runs in banks 1 and 2 while the system manages from banks 0 and 3. Since I don't want to have duplicates of the c library, I need a way to let a the compiler use the c library and driver functions that are in bank 0. So this morning, I coded up a system to do that.

I added a crt1.s which is the crt start up for bank 1. It doesn't need to deal with interrupts, and some basic start up code, for example. It just needs to prepare the stack pointer, deal with it's C initialization, and then start main. At the end of main, it has to return to the caller in bank 0.

I haven't tested any of this, but the basic plan is to have a vector of jumps at the top of bank 0. A version will sit at 0x3f11. The vector is jumps to functions in bank 0.

For example. If I want to call printf from the compiled code for bank 1, it will link the vector which will have a:
_printf::
 jp 0
compiled into a location somewhere in the 3f11-3ffd range. Specifically at 0x4000 - sizeof(struct c_vector) + offsetof(struct c_vector, printf).

Because of the way the intel hex programmer works, this will not be burnt into the flash because it is outside the 0x4000-0x7fff range. So a call to printf will go to that location where the jp 0 is. But there won't be a jp 0 there. What will be there is a jp _printf placed there by the compiler that compiled the bank 0 code. The code compiled for bank 1 will, as a result, call the printf compiled separately for bank 0. Of course, the struct c_vector and the jp's have to align perfectly.

If that description was a little complicated, have a look at the code (recall it's not tested yet). It's in progress.

cvector.h:

#ifndef INCLUDE_CVECTORS_H
#define INCLUDE_CVECTORS_H

#ifndef _SDCC_MALLOC_TYPE_MLH
#define _SDCC_MALLOC_TYPE_MLH
#endif

#ifndef __SDCC_BROKEN_STRING_FUNCTIONS
#define __SDCC_BROKEN_STRING_FUNCTIONS
#endif

#ifndef __SDCC_z80
#define __SDCC_z80
#endif

#ifndef _Bool
#define _Bool unsigned char
#endif

#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include "ringBuffer.h"


struct c_vector
{
    // c_ext
    uint8_t jp4e;
    void (*gets_length)(int maxlen);
    uint8_t jp4d;
    void(*gets_echoSuppress)(bool suppressEcho);

    // idle
    uint8_t jp4c;
    void(*idle)(void(*pIdleFn)(void* p), void* p);

    // interrupt
    uint8_t jp4b;
    void(*di)();
    uint8_t jp4a;
    void(*ei)();

    // gpio
    uint8_t jp49;
    uint8_t(*gpioReadDirection)();
    uint8_t jp48;
    void(*gpioWriteDirection)(uint8_t direction, uint8_t mask);
    uint8_t jp447;
    uint8_t(*gpioReadOutLevel)();
    uint8_t jp46;
    void(*gpioWriteLevel)(uint8_t out, uint8_t mask);
    uint8_t jp45;
    void(*gpioWriteBitLevel)(uint8_t bit, bool level);
    uint8_t jp44;
    uint8_t(*gpioReadInLevel)();

    // spi
    uint8_t jp43;
    uint8_t(*spiExchange)(uint8_t b, uint8_t flags, uint8_t count);

    // tick
    uint8_t jp42;
    uint32_t(*getTicks)();

    // timer
    uint8_t jp41;
    void(*timerInit)();
    uint8_t jp40;
    int(*timerGetAvailableTimerId)();
    uint8_t jp3f;
    void(*timerFree)(int timerId);
    uint8_t jp3e;
    void(*timerSet)(int timerId, uint32_t interval, bool repeat, bool start, void(*cb)(int timer));
    uint8_t jp3d;
    void(*timerGet)(int timerId, uint32_t* pTrigger, uint32_t* pInterval, bool* pRepeat, bool* pRunning, void(**pCb)(int timer));
    uint8_t jp3c;
    void(*timerStop)(int timerId);
    uint8_t jp3b;
    void(*timerRun)(int timerId);
    uint8_t jp3a;
    void(*timerResetInterval)(int timerId);

    // uart
    uint8_t jp39;
    bool(*uartConfig)(uint8_t configLow);
    uint8_t jp38;
    void(*uartSupressRTS)(bool always, bool exceptRecv);
    uint8_t jp37;
    uint8_t(*uartErrorReadAndClear)(uint8_t clearMask);
    uint8_t jp36;
    RB_INT_TYPE(*send)(uint8_t* pByte, RB_INT_TYPE count);
    uint8_t jp35;
    RB_INT_TYPE(*recv)(uint8_t* pByte, RB_INT_TYPE count);
    uint8_t jp34;
    void(*pend)(void(*pIdleFn)(void* p), void* p);

    // stdio
    uint8_t jp33;
    char(*getchar)();
    uint8_t jp32;
    void(*putchar)(char c);
    uint8_t jp31;
    int(*puts)(const char* s);
    uint8_t jp30;
    char* (*gets)(char* s);
    uint8_t jp2f;
    int(*printf)(char* format, ...);
    uint8_t jp2e;
    int(*vprintf)(char* format, va_list ap);
    uint8_t jp2d;
    int(*sprintf)(char* dest, char* format, ...);
    uint8_t jp2c;
    int(*vsprintf)(char* dest, char* format, va_list ap);

    // stdlib
    uint8_t jp2b;
    void* (*malloc)(size_t size);
    uint8_t jp2a;
    void* (*calloc)(size_t size, size_t count);
    uint8_t jp29;
    void* (*realloc)(void* old, size_t newSize);
    uint8_t jp28;
    void(*free)(void* m);


    // string
    uint8_t jp27;
    void* (*memcpy)(void* dest, const void* src, size_t n);
    uint8_t jp26;
    void* (*memmove)(void *dest, const void *src, size_t n);
    uint8_t jp25;
    char* (*strcpy)(char* dest, const char* src);
    uint8_t jp24;
    char* (*strncpy)(char* dest, const char* src, size_t n);
    uint8_t jp23;
    char* (*strcat)(char* dest, const char* src);
    uint8_t jp22;
    char* (*strncat)(char* dest, const char* src, size_t n);
    uint8_t jp21;
    int(*memcmp)(const void* s1, const void* s2, size_t n);
    uint8_t jp20;
    int(*strcmp)(const char* s1, const char* s2);
    uint8_t jp1f;
    int(*strncmp)(const char* s1, const char* s2, size_t n);
    uint8_t jp1e;
    size_t(*strxfrm)(char *dest, const char* src, size_t n);
    uint8_t jp1d;
    void* (*memchr)(const void *s, int c, size_t n);
    uint8_t jp1c;
    char* (*strchr)(const char *s, char c); /* c should be int according to standard. */
    uint8_t jp1b;
    size_t(*strcspn)(const char *s, const char *reject);
    uint8_t jp1a;
    char* (*strpbrk)(const char *s, const char *accept);
    uint8_t jp19;
    char* (*strrchr)(const char *s, char c); /* c should be int according to standard. */
    uint8_t jp18;
    size_t(*strspn)(const char *s, const char *accept);
    uint8_t jp17;
    char* (*strstr)(const char* haystack, const char *needle);
    uint8_t jp16;
    char* (*strtok)(char* str, const char * delim);
    uint8_t jp15;
    void* (*memset)(void *s, unsigned char c, size_t n); /* c should be int according to standard. */
    uint8_t jp14;
    size_t(*strlen)(const char *s);

    // stdlib
    uint8_t jp13;
    int(*atoi)(const char* s);
    uint8_t jp12;
    long(*atol)(const char* s);
    uint8_t jp11;
    int(*rand)();
    uint8_t jp10;
    void(*srand)(unsigned int seed);
    uint8_t jp0f;
    int(*abs)(int i);
    uint8_t jp0e;
    long(*labs)(long i);

    // ctype
    uint8_t jp0d;
    int(*isblank)(int c);
    uint8_t jp0c;
    int(*isdigit)(int c);
    uint8_t jp0b;
    int(*islower)(int c);
    uint8_t jp0a;
    int(*isupper)(int c);
    uint8_t jp09;
    int(*isalnum)(int c);
    uint8_t jp08;
    int(*isalpha)(int c);
    uint8_t jp07;
    int(*iscntrl)(int c);
    uint8_t jp06;
    int(*isgraph)(int c);
    uint8_t jp05;
    int(*isprint)(int c);
    uint8_t jp04;
    int(*ispunct)(int c);
    uint8_t jp03;
    int(*isspace)(int c);
    uint8_t jp02;
    int(*isxdigit)(int c);
    uint8_t jp01;
    int(*tolower)(int c);
    uint8_t jp00;
    int(*toupper)(int c);

    // version --corresponds to 0x3ffe
    uint16_t _cVectorVersion;
};

#endif



c_vector.c:

#include "c_vector.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include "menuDriver.h"
#include "c_ext.h"
#include "interrupt.h"
#include "gpio.h"
#include "spi.h"
#include "tick.h"
#include "idle.h"
#include "timer.h"
#include "uart.h"
#include "diag.h"

#ifndef __SDCC
#define __at(x)
#endif

void* proxy_memcpy(void* dest, const void* src, size_t n)
{
    return memcpy(dest, src, n);
}

void* proxy_memmove(void *dest, const void *src, size_t n)
{
    return memmove(dest, src, n);
}

char* proxy_strcpy(char* dest, const char* src)
{
    return strcpy(dest, src);
}

char* proxy_strncpy(char* dest, const char* src, size_t n)
{
    return strncpy(dest, src, n);

}

char* proxy_strchr(const char *s, char c)
{
    return strchr(s, c);
}

void* proxy_memset(void *s, unsigned char c, size_t n)
{
    return memset(s, c, n);
}
#define JP_OPCODE (uint8_t)(0xc3)

const struct c_vector __at(0x4000 - sizeof(struct c_vector)) C_VECTOR = {
 JP_OPCODE,
    gets_length,
    JP_OPCODE,
    gets_echoSuppress,

    JP_OPCODE,
    idle,

    JP_OPCODE,
    di,
    JP_OPCODE,
    ei,

    JP_OPCODE,
    gpioReadDirection,
    JP_OPCODE,
    gpioWriteDirection,
    JP_OPCODE,
    gpioReadOutLevel,
    JP_OPCODE,
    gpioWriteLevel,
    JP_OPCODE,
    gpioWriteBitLevel,
    JP_OPCODE,
    gpioReadInLevel,

    JP_OPCODE,
    spiExchange,

    JP_OPCODE,
    getTicks,

    JP_OPCODE,
    timerInit,
    JP_OPCODE,
    timerGetAvailableTimerId,
    JP_OPCODE,
    timerFree,
    JP_OPCODE,
    timerSet,
    JP_OPCODE,
    timerGet,
    JP_OPCODE,
    timerStop,
    JP_OPCODE,
    timerRun,
    JP_OPCODE,
    timerResetInterval,

    JP_OPCODE,
    uartConfig,
    JP_OPCODE,
    uartSupressRTS,
    JP_OPCODE,
    uartErrorReadAndClear,
    JP_OPCODE,
    send,
    JP_OPCODE,
    recv,
    JP_OPCODE,
    pend,

    JP_OPCODE,
    getchar,
    JP_OPCODE,
    putchar,
    JP_OPCODE,
    puts,
    JP_OPCODE,
    gets,
    JP_OPCODE,
    printf,
    JP_OPCODE,
    vprintf,
    JP_OPCODE,
    sprintf,
    JP_OPCODE,
    vsprintf,

    JP_OPCODE,
    malloc,
    JP_OPCODE,
    calloc,
    JP_OPCODE,
    realloc,
    JP_OPCODE,
    free,

    JP_OPCODE,
    proxy_memcpy,
    JP_OPCODE,
    proxy_memmove,
    JP_OPCODE,
    proxy_strcpy,
    JP_OPCODE,
    proxy_strncpy,
    JP_OPCODE,
    strcat,
    JP_OPCODE,
    strncat,
    JP_OPCODE,
    memcmp,
    JP_OPCODE,
    strcmp,
    JP_OPCODE,
    strncmp,
    JP_OPCODE,
    strxfrm,
    JP_OPCODE,
    memchr,
    JP_OPCODE,
    proxy_strchr,
    JP_OPCODE,
    strcspn,
    JP_OPCODE,
    strpbrk,
    JP_OPCODE,
    strrchr,
    JP_OPCODE,
    strspn,
    JP_OPCODE,
    strstr,
    JP_OPCODE,
    strtok,
    JP_OPCODE,
    proxy_memset,
    JP_OPCODE,
    strlen,

    JP_OPCODE,
    atoi,
    JP_OPCODE,
    atol,
    JP_OPCODE,
    rand,
    JP_OPCODE,
    srand,
    JP_OPCODE,
    abs,
    JP_OPCODE,
    labs,

    JP_OPCODE,
    isblank,
    JP_OPCODE,
    isdigit,
    JP_OPCODE,
    islower,
    JP_OPCODE,
    isupper,
    JP_OPCODE,
    isalnum,
    JP_OPCODE,
    isalpha,
    JP_OPCODE,
    iscntrl,
    JP_OPCODE,
    isgraph,
    JP_OPCODE,
    isprint,
    JP_OPCODE,
    ispunct,
    JP_OPCODE,
    isspace,
    JP_OPCODE,
    isxdigit,
    JP_OPCODE,
    tolower,
    JP_OPCODE,
    toupper,

    0x0101
};

const struct MenuItem menuItems[] =
{
 // Banking

 // Flash

 // Running

    {
 NULL,
 NULL
    }
};

void main()
{
    ShowByte(0x00);
    ShowByte(0xff);

    diagInit();
    DiagDir(1);
    tickInit();
    uartInit();
    gpioWriteLevel(0x00, 0x08);
    gpioWriteDirection(0x08, 0x08);
    uartSupressRTS(false, true);

    while (!uartConfig(0 | (uint8_t)UART_BAUD_9600))
    {
        // do nothing
    }

    ei();

    runMenu();
}

crt1.s:

;;

 .module crt1

 .globl _main

 .area _HEADER (ABS)
; This next section must remain in sync with cvectors.h
; This will be part of the intelhex file but will be ignored by the intel hex programmer
; because it is out of the range

; inspect crt1.rel and ensure _cVectorVersion looks like this:
; S _cVectorVersion Def3FFE
; if it is not, adjust this .org accordingly and update _cVectorVersion to reflect a new C vector
 .org 0x3f11
_gets_length::
 jp 0
_gets_echoSuppress::
 jp 0
_idle::
 jp 0
_di::
 jp 0
_e::
 jp 0
_gpioReadDirection::
 jp 0
_gpioWriteDirection::
 jp 0
_gpioReadOutLevel::
 jp 0
_gpioWriteLevel::
 jp 0
_gpioWriteBitLevel::
 jp 0
_gpioReadInLevel::
 jp 0
_spiExchange::
 jp 0
_getTicks::
 jp 0
_timerInit::
 jp 0
_timerGetAvailableTimerId::
 jp 0
_timerFree::
 jp 0
_timerSet::
 jp 0
_timerGet::
 jp 0
_timerStop::
 jp 0
_timerRun::
 jp 0
_timerResetInterval::
 jp 0
_uartConfig::
 jp 0
_uartSupressRTS::
 jp 0
_uartErrorReadAndClear::
 jp 0
_send::
 jp 0
_recv::
 jp 0
_pend::
 jp 0
_getchar::
 jp 0
_putchar::
 jp 0
_puts::
 jp 0
_gets::
 jp 0
_printf::
 jp 0
_vprintf::
 jp 0
_sprintf::
 jp 0
_vsprintf::
 jp 0
_malloc::
 jp 0
_calloc::
 jp 0
_realloc::
 jp 0
_free::
 jp 0
_memcpy::
 jp 0
_memmove::
 jp 0
_strcpy::
 jp 0
_strncpy::
 jp 0
_strcat::
 jp 0
_strncat::
 jp 0
_memcmp::
 jp 0
_strcmp::
 jp 0
_strncmp::
 jp 0
_strxfrm::
 jp 0
_memchr::
 jp 0
_strchr::
 jp 0
_strcspn::
 jp 0
_strpbrk::
 jp 0
_strrchr::
 jp 0
_strspn::
 jp 0
_strstr::
 jp 0
_strtok::
 jp 0
_memset::
 jp 0
_strlen::
 jp 0
_atoi::
 jp 0
_atol::
 jp 0
_rand::
 jp 0
_randSeed::
 jp 0
_abs::
 jp 0
_labs::
 jp 0
_isblank::
 jp 0
_isdigit::
 jp 0
_islower::
 jp 0
_isupper::
 jp 0
_isalnum::
 jp 0
_isalpha::
 jp 0
_iscntrl::
 jp 0
_isgraph::
 jp 0
_isprint::
 jp 0
_ispunct::
 jp 0
_isspace::
 jp 0
_isxdigit::
 jp 0
_tolower::
 jp 0
_toupper::
 jp 0
_cVectorVersion::
 .dw 0x0101

 ; 4000-4020 unused for now since intel Hex might overlap 4000

 ;; required c vector version the applet expects
 ;; C program must provide a const uint16_t  CVECTOR_VERSION = 0x0101; // adjust accordingly
 .globl _CVECTOR_VERSION

 .org 0x4020
 .dw _CVECTOR_VERSION
 
 ;; pointer to name of the applet
 ;; C program must provide a const char* const APPLET_NAME = "applet name here";
 .globl _APPLET_NAME
 .org 0x4022
 .dw _APPLET_NAME

 ;; pointer to name of the applet
 ;; C program must provide a const char* const APPLET_NAME = "applet name here";
 .globl _APPLET_TIMESTAMP
 .org 0x4024
 .dw _APPLET_TIMESTAMP

 ;; applet indicator--is these aren't 1,2,3,4, then the flash block is not an applet
 .org 0x4026
 .dw 0x0102
 .dw 0x0304

 .org 0x4030
init:
 ;; Set stack pointer directly above top of memory.
 ld hl, #0
 add hl, sp
 ld sp,#0xc000
 ;; Create a space to be used by bank switcher
 push hl

 ;; store the SP from the code that called this
 push hl

 ;; Initialise global variables
 call gsinit

 call _main

 ; return to code that called us
 pop hl
 ld sp, hl

 ret

 ;; Ordering of segments for the linker.
 .area _HOME
 .area _CODE
 .area _INITIALIZER
 .area   _GSINIT
 .area   _GSFINAL

 .area _DATA
 .area _INITIALIZED
 .area _BSEG
 .area   _BSS
 .area   _HEAP

 .area   _CODE

 .area   _GSINIT
gsinit::
 ld bc, #l__INITIALIZER
 ld a, b
 or a, c
 jr Z, gsinit_next
 ld de, #s__INITIALIZED
 ld hl, #s__INITIALIZER
 ldir
gsinit_next:

 .area   _GSFINAL
 ret


Here is how it's used. I define APPLET_NAME, APPLET_TIMESTAMP, and CVECTOR_VERSION, and main(). Next it is liniked with a Makefile that links with crt1 instead of crt0 and that uses --code-seg and --data-seg of 0x4020 and 0x8000.

Here is an example that I'm working toward.
clock.c:

#include <stdio.h>
#include "uart.h"
#include "timer.h"

const char* const APPLET_NAME = "clock";
const char* const APPLET_TIMESTAMP = __TIMESTAMP__;
const uint16_t CVECTOR_VERSION = 0x0101;

int clockId = -1;
uint8_t hour;
uint8_t min;
uint8_t sec;

void showClock()
{
    while (sec > 59)
    {
        sec = 0;
        min++;
    }
    while (min > 59)
    {
        min = 0;
        hour++;
    }
    while (hour > 12)
    {
        hour = 1;
    }
    printf("%02hd:%02hd:%02hd\r\n", hour, min, sec);
}

void clockCB(int timerId)
{
    timerId;
    sec++;
    showClock();
}

void main()
{
    char c;
    bool go = false;

    uartSupressRTS(false, false);

    if (clockId == -1)
    {
        clockId = timerGetAvailableTimerId();
    }
    if (clockId == -1)
    {
        return;
    }

    timerSet(clockId, 1000, true, false, clockCB);

    puts("'h' for hour, 'm' for minutes 'g' to go, 's' to stop");
    clockCB(clockId);
    while (!go)
    {
        c = getchar();
        switch (c)
        {
        case 'h':
            hour++;
            break;
        case 'm':
            min++;
            break;
        case 'M':
            min += 10;
            break;
        case 'g':
            timerRun(clockId);
            go = true;
            break;
        case 's':
            timerStop(clockId);
            go = true;
            break;
        }
        showClock();
    }

}


I'll need a menu function that switches a bank 1 and 2 in and then jumps to 0x4020. Also a menu item that lists all eligible banks with bank name and c vector version.

Sunday, September 13, 2015

Menu Doesn't Work Yet

My program that just does gets and puts what was sent worked. But not my latest effort to get a menu program up and running. It doesn't even seem to get to main().

Hmmm... I noticed that the compiler is inserting it's own mult and div functions which it needs for atoi. Those were being located in RAM. That turned out to because I removed the _HOME section (among others) from crt0.s when I was trying to get things working. Now, I added it back in and those automatically added arithmetic functions are going to the right place.

Then, all of a sudden the flash programmer started failing. I traced that to the fact I was using the start rather than the iterator in the for loop that erases the sectors in the Arduino, so it was just erasing the first sector over and over. Which was fine when my program fit into one sector.... Literal growing pains.

And.....I seem to be in main() now executing an endless loop of basic pin manipulation that is appearing on the scope as hoped..... Whew!

By the way, I added a "C extension" which lets me specify a maximum buffer length for gets. If the user goes too high, it putchars a BEL. That will either beep or make puTTY flash.

And, now the puts works, but my printf with a %s is spamming a zero. Since the string's first character is 0, that's progress.....

There, fixed that bug.....

Now, I'm getting my first menu item spammed over and over....

That was easy enough to fix.....

And now it shows the menu and takes a value. But it didn't seem to execute the code it's supposed to.....

OK, my prompt code actually returns the zero based index and I was decrementing it....

And one more bug in my prompt interpreter.....

And voila!


Mark Hamann's Z80 Computer

Version: 0.1 beta
Menu

1) dump text
> 1
Read: 0
Executing...

Address (hex)? 0
Length (hex)? 20
000: c3 69 0 ff ff ff ff ff  c3 72 c ff ff ff ff ff .i...... .r......
c3 83 c ff ff ff ff ff  c3 94 c ff ff ff ff ff ........ ........
Done!

Mark Hamann's Z80 Computer

Version: 0.1 beta
Menu

1) dump text
>


So I've got a couple of issues to take care of, but from having the program not working a few hours ago to this isn't too bad.

Time to publish this post....

Saturday, September 12, 2015

Taming The Make System

I've never enjoyed gnu make. Most people I know don't, but it's a necessary evil. So I took some time out to finally get it into some shape. So, now I have a new SW structure. Here is my new directory structure:


And here are my makefiles and includes:

SW/Makefile:


SW_DIR= $(shell pwd | sed 's/\/cygdrive\/c\//C:\\\\/' | sed 's/\//\\\\/g')
SW_DIR_CYG= $(shell pwd)
CFLAGS_Z80=-mz80
CFLAGS_GENERIC=-c -DNDEBUG
AFLAGS=-o
LFLAGS= -mz80 --no-std-crt0 --nostdlib --code-loc 0x100 --data-loc 0x8000
INCLUDES=-I${SW_DIR}/include -I${SW_DIR}/app/include
INCLUDES_CYG=-I${SW_DIR_CYG}/include -I${SW_DIR_CYG}/app/include
TOOLDIR=/cygdrive/c/Program\ Files/SDCC/bin
LIBDIR=lib

MAKEARGS="CFLAGS_Z80=${CFLAGS_Z80}" "CFLAGS_GENERIC=${CFLAGS_GENERIC}" "INCLUDES=${INCLUDES}" "AFLAGS=${AFLAGS}" "TOOLDIR=${TOOLDIR}" "LIBDIR=../${LIBDIR}" "BASEDIR=.." "LFLAGS=${LFLAGS}" "INCLUDES_CYG=${INCLUDES_CYG}"


# This makefile is the top level makefile that calls down to all the submakes
# It will get the current directory, basic flags, and then drill down
# includes are 
#

#

.PHONY: all c bsp clean depends clean_bsp clean_app clean_c clean_lib

all:
 cd app ; ${MAKE} all ${MAKEARGS} "DIRS=test" "PROG=mainRW"
# cd app ; ${MAKE} all ${MAKEARGS} "DIRS=menu" "PROG=menu"

c:
 cd c_src ; ${MAKE} all ${MAKEARGS}

bsp:
 cd bsp ; ${MAKE} all ${MAKEARGS} "DIRS=crt0  diag  flash  gpio  interrupt  membank  spi  tick  uart" "LIB=bsp"
 cd app ; ${MAKE} all ${MAKEARGS} "DIRS=idle intelHex ringBuffer timer" "LIB=bsp"

clean: clean_bsp clean_app clean_c clean_lib

clean_bsp:
 cd bsp ; ${MAKE} clean ${MAKEARGS} "DIRS=crt0  diag  flash  gpio  interrupt  membank  spi  tick  uart" "LIB=bsp"

clean_app:
 cd app ; ${MAKE} clean ${MAKEARGS} "DIRS=idle intelHex ringBuffer  test  timer menu test" "LIB=bsp"

clean_c:
 cd c_src; ${MAKE} clean ${MAKEARGS}

clean_lib:
 cd lib ; rm -f *.lib; rm -f *.rel
 
depends:
 cd app ; ${MAKE} depends ${MAKEARGS}

test:
 cd bsp; pwd; make test ${MAKEARGS}



SW/common.inc:


COMMON=INCUDED

MAKEARGS="CFLAGS_Z80=${CFLAGS_Z80}" "CFLAGS_GENERIC=${CFLAGS_GENERIC}" "INCLUDES=${INCLUDES}" "AFLAGS=${AFLAGS}" "TOOLDIR=${TOOLDIR}" "LIBDIR=../${LIBDIR}" "BASEDIR=../${BASEDIR}"


SW/subdirs.inc:


SUBDIRS=INCUDED

.PHONY: all clean

all:
 echo ${DIRS}
 @for i in ${DIRS} ; do \
        cd $$i; \
  make all ${MAKEARGS}; \
  cd ..; \
    done

clean:
 echo ${DIRS}
 @for i in ${DIRS} ; do \
        cd $$i; \
  make clean ${MAKEARGS}; \
  cd ..; \
    done


SW/generate.inc:


CC=${TOOLDIR}/sdcc.exe
AS=${TOOLDIR}/sdasz80.exe
AR=${TOOLDIR}/sdcclib.exe

C_SOURCES=$(wildcard *.c)
S_SOURCES=$(wildcard *.s)
REL_C=$(patsubst %.c,%.rel, ${C_SOURCES})
REL_S=$(patsubst %.s,%.rel ,${S_SOURCES})

all: ${REL_C} ${REL_S}

# - keeps it from crashing out if no *.d file
-include *.d

clean:
 rm -f *.d
 rm -f *.lib
 rm -f *.rel
 rm -f *.asm
 rm -f *.lst
 rm -f *.sym

%.rel : %.s
 ${AS} -g ${AFLAGS} $<
 ${AR} r ${LIBDIR}/${LIB}.lib $@

%.rel : %.c
 ${CC} ${CFLAGS_Z80} ${CFLAGS_GENERIC} ${INCLUDES} $<
 ${AR} r ${LIBDIR}/${LIB}.lib $@

%.d : %.c
 gcc ${CFLAGS_GENERIC} ${INCLUDES_CYG} -MM $< > $*.d


The Makefiles found in directories with no code:


include ${BASEDIR}/common.inc
include ${BASEDIR}/subdirs.inc


The Makefiles found in directories with code:


include ${BASEDIR}/common.inc
include ${BASEDIR}/generate.inc


SW/bsp/crt0 has a special makefile since crt0 isn't archived but copied to SW/lib:


include ${BASEDIR}/common.inc

# This makefile is unique because the crt0 isn't archived. Rather it must be the first item in the list of rel/ar given to the linker
CC=${TOOLDIR}/sdcc.exe
AS=${TOOLDIR}/sdasz80.exe
AR=${TOOLDIR}/sdcclib.exe

C_SOURCES=$(wildcard *.c)
S_SOURCES=$(wildcard *.s)
REL_C=$(patsubst %.c,%.rel, ${C_SOURCES})
REL_S=$(patsubst %.s,%.rel ,${S_SOURCES})

all: ${REL_C} ${REL_S}

clean:
 rm -f *.d
 rm -f *.lib
 rm -f *.rel
 rm -f *.asm
 rm -f *.lst
 rm -f *.sym

%.rel : %.s
 ${AS} -g ${AFLAGS} $<
# ${AR} rc ${LIBDIR}/${LIB} $@
 cp crt0.rel ${LIBDIR}/

%.rel : %.c
 ${CC} ${CFLAGS_Z80} ${CFLAGS_GENERIC} ${INCLUDES} $<
# ${AR} rc ${LIBDIR}/${LIB}.ar $@

And the common C directory also has a special Makefile:


include ${BASEDIR}/common.inc

CC=${TOOLDIR}/sdcc.exe
AS=${TOOLDIR}/sdasz80.exe
AR=${TOOLDIR}/sdcclib.exe


C_SOURCES= _calloc.c _divslong.c _divulong.c _free.c _heap.c _itoa.c _ltoa.c _malloc.c _memchr.c _memcmp.c _memcpy.c _memset.c _modslong.c _modulong.c _mullong.c _realloc.c _startup.c _strcat.c _strchr.c _strcmp.c _strcspn.c _strncat.c _strncmp.c _strncpy.c _strpbrk.c _strrchr.c _strspn.c _strstr.c _strtok.c assert.c atoi.c atol.c errno.c gets.c isalnum.c isalpha.c isblank.c iscntrl.c isdigit.c isgraph.c islower.c isprint.c ispunct.c isspace.c isupper.c isxdigit.c labs.c puts.c rand.c sprintf.c strxfrm.c time.c tolower.c toupper.c vprintf.c 

# _divschar.c _modschar.c _mulschar.c _setjmp.c _strcpy.c abs.c _memmove.c _strlen.c _modsint.c _moduint.c _mulint.c _divsint.c _divuint.c

C_LONG_LONG_SOURCES= _divslonglong.c _divulonglong.c _modslonglong.c _modulonglong.c _mullonglong.c _rlslonglong.c _rlulonglong.c _rrslonglong.c _rrulonglong.c atoll.c

C_FLOAT_SOURCES= _atof.c _fs2schar.c _fs2sint.c _fs2slong.c _fs2uchar.c _fs2uint.c _fs2ulong.c _fsadd.c _fscmp.c _fsdiv.c _fseq.c _fsget1arg.c _fsget2args.c _fsgt.c _fslt.c _fsmul.c _fsneq.c _fsnormalize.c _fsreturnval.c _fsrshift.c _fssub.c _fsswapargs.c _logexpf.c _schar2fs.c _sint2fs.c _slong2fs.c _uchar2fs.c _uint2fs.c _ulong2fs.c acosf.c asincosf.c asinf.c atan2f.c atanf.c ceilf.c cosf.c coshf.c cotf.c expf.c fabsf.c floorf.c frexpf.c ldexpf.c log10f.c logf.c modff.c powf.c sincosf.c sincoshf.c sinf.c sinhf.c sqrtf.c tancotf.c tanf.c tanhf.c

REL_C_SOURCES=$(patsubst %.c,%.rel, ${C_SOURCES})
REL_C_LONG_LONG_SOURCES=$(patsubst %.c,%.rel, ${C_LONG_LONG_SOURCES})
REL_C_FLOAT_SOURCES=$(patsubst %.c,%.rel, ${C_FLOAT_SOURCES})
REL_S=$(patsubst %.s,%.rel ,${S_SOURCES})

.PHONY: all z80

all: c_lib float_lib longlong_lib z80

z80:
 cd z80; make all ${MAKEARGS} "LIB=c"

c_lib: ${REL_C_SOURCES} ${REL_S}
 ${AR} r ${LIBDIR}/c.lib ${REL_C_SOURCES} ${REL_S}
 
float_lib: ${REL_C_FLOAT_SOURCES}
 ${AR} r ${LIBDIR}/float.lib ${REL_C_FLOAT_SOURCES}
 
longlong_lib: ${REL_C_LONG_LONG_SOURCES}
 ${AR} r ${LIBDIR}/longlong.lib ${REL_C_LONG_LONG_SOURCES}
 
%.rel : %.s
 ${AS} -g ${AFLAGS} $<

%.rel : %.c
 ${CC} ${CFLAGS_Z80} ${CFLAGS_GENERIC} ${INCLUDES} $<

clean:
 rm -f *.d
 rm -f *.lib
 rm -f *.rel
 rm -f *.asm
 rm -f *.lst
 rm -f *.sym
 cd z80; make clean ${MAKEARGS}


And this is how I make a clean build of the program I'm working with:


$> make clean
$> make all


It's not perfect. It's probably not even good. But it's better than the ad hoc Makefiles I had before because it's more scalable and extensible.

Monday, September 7, 2015

Workflow

I have a pretty good workflow.

It's basically a cycle of:

  1. Determine where in the code I want to test
  2. Add function call that shows me bytes on the scope (drop gpio 2 and output byte in SPI bus)
  3. Build
  4. Burn flash (about a minute)
  5. Reset and grab trace
  6. Inspect scope output to see what the code did
  7. Make any necessary fixes

A lot of the uncertainty I had is dissipating with each successive change that brings me closer to having a working UART driver.


Sunday, September 6, 2015

Bug Reported And __critical Purged

I reported the bug just now. It's at https://sourceforge.net/p/sdcc/bugs/2416/.

Now I have to figure out how to handle critical sections.

For now, just two functions--di() and ei(). They will use a global variable uint8_t interruptDisableReferenceCount. If it's 0, interrupts are enabled. It will initialize to 1.

I also dealt with the __interrupt bug where it doesn't generate ei before reti.

Here is my interrupt.c file for now:

#include "tick.h"
#include "uart.h"
#include "membank.h"

/*
; for crt0.s
.globl _do_RST_00H
.globl _do_RST_08H
.globl _do_RST_10H
.globl _do_RST_01H
.globl _do_RST_20H
.globl _do_RST_28H
.globl _do_RST_30H
.globl _do_RST_38H
.globl _do_NMI
*/

uint8_t interruptDisableRefCount = 1;

void do_RST_00H() 
{
}

void do_RST_08H()
{
}

void do_RST_10H()
{
}

void do_RST_18H()
{
}

void do_RST_20H()
{
}

void do_RST_28H()
{
}

void do_RST_30H()
{
}

// Has to be naked because __interrupt fails to generate the ei before the reti
void do_RST_38H() __naked
{
    __asm__("push af");
    __asm__("push bc");
    __asm__("push de");
    __asm__("push hl");
    __asm__("push ix");
    __asm__("push iy");

#ifdef INT_SAVE_ALT_REG
    __asm__("exx");
    __asm__("push af");
    __asm__("push bc");
    __asm__("push de");
    __asm__("push hl");
    __asm__("push ix");
    __asm__("push iy");
#endif

    //tickISR();
    uartISR();

#ifdef INT_SAVE_ALT_REG
    __asm__("pop iy");
    __asm__("pop ix");
    __asm__("pop hl");
    __asm__("pop de");
    __asm__("pop bc");
    __asm__("pop af");
    __asm__("exx");
#endif

    __asm__("pop iy");
    __asm__("pop ix");
    __asm__("pop hl");
    __asm__("pop de");
    __asm__("pop bc");
    __asm__("pop af");
    __asm__("ei");
    __asm__("reti");
}

void do_NMI() __interrupt __critical
{
    //nmiISR();
}

void di() __naked
{
    __asm__("push af");
    __asm__("ld a, (_interruptDisableRefCount)");
    __asm__("or a");
    __asm__("jr nz, skip_di");
    __asm__("di");
    __asm__("skip_di:");
    __asm__("inc a");
    __asm__("ld (_interruptDisableRefCount), a");
    __asm__("pop af");
    __asm__("ret");
}

void ei() __naked
{
    __asm__("push af");
    __asm__("ld a, (_interruptDisableRefCount)");
    __asm__("dec a");
    __asm__("ld (_interruptDisableRefCount), a");
    __asm__("or a");
    __asm__("jr nz, skip_ei");
    __asm__("pop af");
    __asm__("ret");
    __asm__("skip_ei:");
    __asm__("pop af");
    __asm__("ei");
    __asm__("ret");
}



I have also purged __critical from my code and placed di() and ei() where I need it.

Saturday, September 5, 2015

SDCC Z80 Port Has Issues With __critical and __interrupt

I went to check the mailing list of the SDCC project. There was a bug for the __critical keyword when it is used with a block. Apparently that was fixed long ago. My issue is when it's used on a function declaration.

But I also read that the __interrupt doesn't generate correct code. It is supposed to end with ei, reti. But it leaves off the ei part.

So I'll do my own critical sections with reference counting and my own prolog and epilog for the interrupt.

Bug In SDCC __critical Makes Compiler Lose Track of Stack Top

Back in the distant past when I was just learning C, I often convinced myself that the compiler had a bug and my C 101 code was correct. Of course, I was wrong 100% of the time.

Well, today, I found one. This one took a while to find because my computer doesn't have communication yet. I have to debug by loading code and then looking at waveforms on a 2 channel scope.

To aid in my debugging I created a new function:

.area _CODE

_ShowByte::
 ld iy,#2
 add iy,sp
 in a, (#0x80)
 res 2, a
 out (#0x80), a
 ld a,0 (iy)
 rlca
 rlca
 rlca
 rlca
 and a,#0x0F
 or a, #0xA0
 out (#0x83), a
 ld a, #0x28
 out (#0x84), a
 ld a,0 (iy)
 rlca
 rlca
 rlca
 rlca
 and a,#0xf0
 or a, #0x05
 out (#0x83), a
 ld a, #0x28
 out (#0x84), a
 in a, (#0x80)
 nop
 nop
 nop
 set 2, a
 out (#0x80), a
 ret


This drops GPIO2 (which must be made an output elsewhere) and then outputs a byte with a leading 0xa nibble and a trailing 0x5 nibble. I did this because I need one probe on GPIO 2 and the other on the data, so I can't see the clock line. (I might be able to use the REF feature of the scope though...) Anyway, with the prototype extern void ShowByte(uint8_t); I can output a byte that the scope can see. Now, main() calls putchar() which calls send(). And the argument seemed to be getting corrupted at send(). Why? I looked at the uart.asm file and found the reason:


;uart.c:151: bool send(uint8_t b) __critical
; ---------------------------------
; Function send
; ---------------------------------
_send::
 ld a,i
 di
 push af
;uart.c:153: ShowByte(b);
 ld hl, #2+0
 add hl, sp
 ld a, (hl)
 push af
 inc sp
 call _ShowByte
 inc sp

This was generated by the compiler. And it's wrong wrong wrong!

The first three instructions handle the __critical part. They store the interrupt register into a, disable interrupts, and push af onto the stack so that the interrupts can be restored at the end of the call.

The next part tries to get the argument. In theory, the argument should be at the top of the stack, just on the other side of the return address. Or sp+2. And indeed we see hl gets 2 plus sp and then we load that into a, push af, inc the stack pointer because it's just a byte, and then calls ShowByte.

But, this function is __critical. The stack isn't the 8 bit argument followed by the 16 bit return address. It's the 8 bit argument, followed by the return address, followed by 16 bits of interrupt restore data. I'm not getting my argument--I'm getting the lower part of the return address!

Wow! I need to rethink how I do critical sections! I took off the __critical and rebuilt. Here is that snippet:

; ---------------------------------
; Function send
; ---------------------------------
_send::
;uart.c:153: ShowByte(b);
 ld hl, #2+0
 add hl, sp
 ld a, (hl)
 push af
 inc sp
 call _ShowByte
 inc sp


Except for the missing interrupt disable, it's the same--it gets the byte 2 bytes back. This one works in my testing. I'm getting the right byte on my scope.

I'm wondering if that +0 is supposed to account for the push af but it's failing to update... I gotta figure out a game plan. And then submit a bug report if one isn't already there on this.

Friday, September 4, 2015

Friday Night Engineering Done

Well, I made progress in figuring out the tricks to use my low-end $350 scope to quickly find the communications I'm looking for. That's good. But I see stuff in the waveforms that don't correspond to what I'm sending in my C code.

Of course, it's likely that they do and that the error is in the creator. But I'm so baffled that I'm going to look at the C super close one last time and then start to look at the assembly since I'm not sure I trust it....

Actually, first, I'll make everything simpler--just one step per operation. No |= or &= or +=. I swear the C, if correctly compiled, would not make the waveforms I'm seeing....

BTW, I don't know why it took me so long to buy the scope. I could not possibly get this far without it. It is a lifesaver. I knew it going in. Maybe I wasn't sure I'd get to the point of needing it. But, boy, how I need it. It's not the best scope I've ever used. but it's pretty good. Just the Rigol 50MHz 2 channel scope. I do recommend it to anyone on a budget. It gets the job done.

Today I was in Digital Lab II, also known as Liberty Bar on 15th in Seattle. It's a lounge bar in the evening, but a pretty nice cafe in the morning. Pretty nice because it's quiet, most of the patrons are regulars, many people are working from there because it has rock solid WiFi, and it has the lounge table where I can set up my mobile lab. Most people who work there are using either paper or their laptops. But I think nothing of bringing in my mobile lab in Lululemon bags, and starting to work. I joke that one of these days, I'll solder there, but I never will.

A guy who had come in for coffee and obviously didn't know much about how electronics works stopped by because he was quite intrigued by my setup and I explained what I was doing and what the scope does and how to look at the waveforms and I showed him the C and I showed him the waveform and I tried to show him why they didn't correspond as I expected them to. It's funny, because, as an electrical engineer, I have very specific definitions for words like "voltage" and "current" and lay people sort of use the terms interchangeably and rely on context to guide their interlocutor to some vague notion of what they are asking.

I am trying to figure out this problem with all this new code that I'm trying to get running for the first time. I went to a number of interviews at companies after having been out of embedded design for 4 years thanks to leukemia and didn't wow them because it was, frankly, 4 years since I touched this stuff and they have applicants coming from other jobs doing this. But, this project is doing exactly what I'd hoped it would do. It's getting my mind back in. It's proving to me that I can still do what I used to be able to do. And do it well. Embedded engineering is hard, but it's fun. Solving problems is what I do, what I enjoy doing, and what I want to do.

I look forward to getting back into it.

Progress...Slow and Steady

It seems everytime I power up the board, there is a new issue. This morning it was a loose wire wrap in the pullups. But later, I seemed to have the same problem. I have a wrap around a copper wire that is not connecting between the bare copper wire and the wrapping wire. So, time for some posts.

But, when it does work, it sort of works. I have a simple program that just outputs a string with puts and then uses gets in a loop. Of course, it doesn't do the puts properly, but I see data moving between the SPI/GPIO CPLD and the MAX 3110E. So the UART driver code is getting executed. It's just not configuring properly yet. I did fix a few bugs in the the GPIO and UART initialization. Just have to fix enough of the rest to get through this boot strapping phase.

A flash write takes about a minute for 4 driver modules, the main with the puts and gets, and the C library functions of only putchar, puts, getchar, and gets. So I'll be happy to have the bank switching, flash programming, and intel hex capabilities.

Also, I applied what I learned about get the poorly documented toolchain from the linux virtual machine to my usual Windows environment and now it works without just sitting there error-messagelessly.

So it's definite progress!

Thursday, September 3, 2015

I Think It Links...

With a lot of trial and error and some hints from one Glenn Neidermeier at http://pushpopmov.blogspot.com/2011/03/sdcc-makefile.html, who left a record of travelling a similar path in 2011, I think I finally have my code linking and going to the right place. I was using the sdld directly, followed by attempts to use sdldz80. But the correct thing is to just use sdcc -mz80. That means you lose the linker options which I tried passing with the -Wl to no avail.... Undocumented complex tools are a pain to work with. /sigh

But the important part is that it's now sort of working. And that means I can start testing my hardware much more thoroughly.

If all goes well, it will run as soon as I load it up.... Of course, that's not too likely. Which means I'll be using my oscilloscope and scaring the patrons at my favorite cafe tomorrow morning.

VirtualBox is Working and Toolchain is Compiled

I upgraded VirtualBox on my Surface Pro 3 today. Instead of just crashing on launch, it now runs. Windows 10 is still not officially supported, but so far it seems to be running. I have it running Mint 17.1. I did apt-get install flex, bison, texinfo, subversion, git, gcc, g++, libboost-graph-dev, and binutils. But it seems to get to the end. I ran ./configure with the --disable-pic14-port and --disable-pic16-port. That way it doesn't require I pull in the PIC stuff. And it did install properly.

This is the basic operation. # is super-user stuff, $ is user stuff. Of course, YMMV. Or probably YMWV (your mileage will vary).

# apt-get install bison
# apt-get install flex
# apt-get install subversion
# apt-get install git
# apt-get install binutils
# apt-get install gcc
# apt-get install g++
# apt-get install libboost-graph-dev
# apt-get install texinfo
$ cd ~
$ svn checkout svn://svn.code.sf.net/p/sdcc/code/trunk sdcc-3.5.2
$ cd sdcc-3.5.2/sdcc
$ ./configure --disable-pic14-port and --disable-pic16-port
$ make
# cd ~/sdcc-3.5.2/sdcc
# make install

Let me know if I'm missing any steps....

Update--just in case you're interested, the tools in ~/sdcc-3.5.2/sdccd/bin/ can be run in gdb. So if something goes awry, I have a fighting chance of diagnosing the problem.

Fun With Makefiles and SDCC

My choice was getting the SDCC toolchain working with Eclipse or dealing with makefiles. And I decided to go with makefiles. I sort of know how to use them--at least I know enough to know how they work and what they look like when they are correct. I just don't know the details on the repurposed punctuation. There is nothing in software development quite as unelegant as make. Shell scripts and Perl are bad, but make... Ugh!

So, I'm starting to build up a makefile system. I have a top level one where I can do all my CFLAGS settings and pass them down.

My first problem was that sdcc.exe wasn't finding my include files. I was getting the address with:

SW_DIR= $(shell pwd)
INCLUDES=-I${SW_DIR}/drivers -I${SW_DIR}/app

I'm running this in cygwin, so the directory looks like /cygdrive/c/users/Mark/Documents/Projects/Z80/SW/

Well, it turns out, I need to pass a Windows style directory, so I updated SW_DIR to:

SW_DIR= $(shell pwd | sed 's/\/cygdrive\/c\//C:\\\\/' | sed 's/\//\\\\/g')

So that fixed that.

I'm on my way to having an actual compiled ihx.

I've decided to start dirt simple. Just direct access to the drivers without the C runtime. I'll know it's working because gets out of the box in the toolchain echoes the incoming characters. So if I turn off local echo on puTTY and see the characters I type, I'll know. I'll also output a string at the beginning.

....


And it compiles and links. Nothing is in the right place yet.  But getting closer.....

.....

Now that I'm trying to pass possibly correct values to the linker, it just sits there and does nothing... I have to ctrl-C out.

Grrrr.....


Wednesday, September 2, 2015

Mostly Compiles!

I have now compiled all the drivers, all the C library, and the menu application. I still need putchar, getchar, maybe something connected to system ticks as that should be available in the C library. I also need to hookup the interrupts in crt0.s to INT# and NMI#. Finally, I gotta figure out how to tell SDCC that RAM starts at 0x8000.

Also, since this my bring-up of a board that has had only some testing, I need to come up with a scheme where I can know how far it's getting using GPIO outs that I can monitor on the scope.

Actually, what I really need first is something that programs flash natively. Maybe put that in a bank of flash and use a GPIO to switch to it.... Because I can use the Arduino to program the flash (and indeed will at the beginning, but it's not fast.

I have figure out how to make sure stuff fits and stuff goes into the right place. I noticed that some asserts are causing strings to go into memory which is likely a luxury I don't want, though I'm not compiling with the debug flag, so I don't know why the asserts are even being generated. Lot's of little things with this toolchain because it targets super small targets so has pretty minimal libraries.

I also have to figure out better organization. I have makefiles in directories. I need a better way to tie them together. I'm not a big makefile fan, so I only sort of know how-- maybe export vars as environment variables and call submakes or something like that. Recursive make is evil, but it's easier than Eclipse.

And if I get sick of all that, I still have the Pygments and Jekyll stuff to work on....

Critical Naked Interrupts and Unpromoted Characters -- In SDCC

I decided to compile a test file just to see exactly what __critical, __naked, and __interrupt do. Here are the results.

__critical causes the prolog to insert:

 ld a,i
 di
 push af

which saves the interrupt register to into the accumulator and then pushes it onto the stack.

The epilog is:

 pop af
 ret PO
 ei
 ret

which is curious because what's that return on parity odd? Well, if the interrupts were already disabled when calling di, they shouldn't be reenabled. It would be possible to use a reference count to keep track of nested __critical functions, but this works as well. When the ld a,i is executed, PO gets the current enabled/disabled state. If the P flag is PO, then the interrupts were disabled coming and won't be enabled.

Is this better than a reference count? If it's used lightly, cetainly. It's a total of 7 bytes with 2 bytes overhead per nested call. A reference count would be 6 bytes, with how many every bytes are needed to store the count and use the count.

__naked causes the prolog and epilog to not generate. The code is generated according to the calling convention. But no registers are saved or restored, no return is generated, and function level

__critical doesn't take effect because that inserts the critical code into the prolog and epilogs. 

__interrupt causes function to save and restore AF, BC, DE, HL, and IY. The return is reti.

 __naked __interrupt is equivalent to __naked since __interrupt only affects the prolog and epilog. 

__critical __interrupt is used to emit the retn instead of reti for returning from a non-maskable interrupt.

Other testing also shows that the __critical { ... } works as expected, putting the same di, ei, jp PO into the code itself.

Finally, a note on integer promotion of function arguments. SDCC doesn't follow the C standard of promoting chars to ints. And thank goodness! Because although an int is 16 bits, the Z-80 works best with 8 bit data, so coercing it to 16 bits when it can be treated as 8 bit data would be counter productive.

For reference, the test.c file is:

#include <stdio.h>

int main(char* argv[], int argc)
{
    argc; argv;
    printf("Hello, World!");
    return 0;
}

int test0(int a, int b)
{
 return a+b;
}
int test0c(int a, int b) __critical
{
 return a+b;
}
int test0n(int a, int b) __naked
{
 return a+b;
}
int test0cn(int a, int b) __critical __naked
{
 return a+b;
}

void test0i() __interrupt
{
 __asm__("nop");
}
void test0in() __interrupt __naked
{
 __asm__("nop");
}
void test0ic() __interrupt __critical
{
 __asm__("nop");
}

And the test.asm file is:

;--------------------------------------------------------
; File Created by SDCC : free open source ANSI-C Compiler
; Version 3.5.2 #9283 (MINGW64)
; This file was generated Wed Sep 02 15:19:10 2015
;--------------------------------------------------------
 .module test
 .optsdcc -mz80
 
;--------------------------------------------------------
; Public variables in this module
;--------------------------------------------------------
 .globl _test0ic
 .globl _test0in
 .globl _test0i
 .globl _test0cn
 .globl _test0n
 .globl _test0c
 .globl _test0
 .globl _main
 .globl _printf
;--------------------------------------------------------
; special function registers
;--------------------------------------------------------
;--------------------------------------------------------
; ram data
;--------------------------------------------------------
 .area _DATA
;--------------------------------------------------------
; ram data
;--------------------------------------------------------
 .area _INITIALIZED
;--------------------------------------------------------
; absolute external ram data
;--------------------------------------------------------
 .area _DABS (ABS)
;--------------------------------------------------------
; global & static initialisations
;--------------------------------------------------------
 .area _HOME
 .area _GSINIT
 .area _GSFINAL
 .area _GSINIT
;--------------------------------------------------------
; Home
;--------------------------------------------------------
 .area _HOME
 .area _HOME
;--------------------------------------------------------
; code
;--------------------------------------------------------
 .area _CODE
;test.c:3: int main(char* argv[], int argc)
; ---------------------------------
; Function main
; ---------------------------------
_main::
;test.c:6: printf("Hello, World!");
 ld hl,#___str_0
 push hl
 call _printf
 pop af
;test.c:7: return 0;
 ld hl,#0x0000
 ret
___str_0:
 .ascii "Hello, World!"
 .db 0x00
;test.c:10: int test0(int a, int b)
; ---------------------------------
; Function test0
; ---------------------------------
_test0::
;test.c:12: return a+b;
 ld hl,#4
 add hl,sp
 ld iy,#2
 add iy,sp
 ld a,0 (iy)
 add a, (hl)
 ld d,a
 ld a,1 (iy)
 inc hl
 adc a, (hl)
 ld e,a
 ld l, d
 ld h, e
 ret
;test.c:14: int test0c(int a, int b) __critical
; ---------------------------------
; Function test0c
; ---------------------------------
_test0c::
 ld a,i
 di
 push af
;test.c:16: return a+b;
 ld hl,#4
 add hl,sp
 ld iy,#2
 add iy,sp
 ld a,0 (iy)
 add a, (hl)
 ld d,a
 ld a,1 (iy)
 inc hl
 adc a, (hl)
 ld e,a
 ld l, d
 ld h, e
 pop af
 ret PO
 ei
 ret
;test.c:18: int test0n(int a, int b) __naked
; ---------------------------------
; Function test0n
; ---------------------------------
_test0n::
;test.c:20: return a+b;
 ld hl,#4
 add hl,sp
 ld iy,#2
 add iy,sp
 ld a,0 (iy)
 add a, (hl)
 ld d,a
 ld a,1 (iy)
 inc hl
 adc a, (hl)
 ld e,a
 ld l, d
 ld h, e
;test.c:22: int test0cn(int a, int b) __critical __naked
; ---------------------------------
; Function test0cn
; ---------------------------------
_test0cn::
;test.c:24: return a+b;
 ld hl,#4
 add hl,sp
 ld iy,#2
 add iy,sp
 ld a,0 (iy)
 add a, (hl)
 ld d,a
 ld a,1 (iy)
 inc hl
 adc a, (hl)
 ld e,a
 ld l, d
 ld h, e
;test.c:27: void test0i() __interrupt
; ---------------------------------
; Function test0i
; ---------------------------------
_test0i::
 push af
 push bc
 push de
 push hl
 push iy
;test.c:29: __asm__("nop");
 nop
 pop iy
 pop hl
 pop de
 pop bc
 pop af
 reti
;test.c:31: void test0in() __interrupt __naked
; ---------------------------------
; Function test0in
; ---------------------------------
_test0in::
;test.c:33: __asm__("nop");
 nop
;test.c:35: void test0ic() __interrupt __critical
; ---------------------------------
; Function test0ic
; ---------------------------------
_test0ic::
 push af
 push bc
 push de
 push hl
 push iy
;test.c:37: __asm__("nop");
 nop
 pop iy
 pop hl
 pop de
 pop bc
 pop af
 retn
 .area _CODE
 .area _INITIALIZER
 .area _CABS (ABS)


Monday, August 31, 2015

Driver Work, Menu, and First Compile

This morning, I worked on coding drivers. Once the UART one is finished, I'll be able to start testing them.

I also worked on a menu system that I will use at the beginning. The menu system will allow faster programming, flash facilities, memory dump, flash erase, bank switching, and eventually running of programs in other banks.

Finally, I got an entire program to compile and link. I'm not sure stuff is going into the right places yet, but it linked even though I used the sdcclib facility rather than the sdar archiver which seg faults when I try to use it. The map file looks reasonable, and there is a lot of data in the .ihx file. So I think it is actually linking. It has my custom crt0.s. I think I need to specify that RAM starts at 0x8000 with a linker command. But I'm close, anyway. The whole system needs refinement, but I'll be there before too long.

I also have to get the final HW configured. That means, tying the UART IRQ to INT and connecting the timer CPLD to the SPI SCK, Din, and GPIO[1], and INT. Those last ones will be soldered to the CPLD pins and then wire wrapped on the other end. I also should solder in a ground post or two for the oscilloscope probes.

Saturday, August 29, 2015

Notes

Sometimes coming out of the BUSREQ mode, the process restarts somewhere else. So I have to figure out why. Not that it's that bad. Bus everything should go Hi-Z before the BUSREQ goes high. I should ensure that happens.

I'm not sure why I have to issue a "fp reset' after a 'fp pins 1'. This issue is possibly related to the issue above.

I was working in 9600 baud on the serial port. My test to see if 115.2 kbaud worked. Yay!!! So I'll switch to that. Twelve times faster. I will connect the RTS and CTS lines and use HW flow control. I'm not sure my little Z-80 can keep up with 115.5k otherwise.

I'm absorbing the nuances of the SDCC toolchain. It's an open-source project so it has the pluses (within my budget) and minuses (hard to use) that one would expect.

I wanted to compile the SDCC toolchain. That required the boost library which is huuuuge. Eventually after figuring out user property sheets in VS 13 I got it to compile. But the sdar is not included. And it gives me the segmentation fault.

I could run this all in linux in VirtualBox but as of now, VirtualBox doesn't work on Windows 10 and I upgraded to Windows 10. So now I'm trying to get it running on my cloud server. Learning wget and installing bison, flex, boost, g++, gputils, texinfo (for binutils) ... Tech'in ain't easy.

[Update:] Not sure how much fruit this is bearing. My cloud server, being shared, is configured to have no swap file. The RAM is the RAM. And I'm apparently running out of memory. I
tried to remove the -pipe flag in a makefile, but that didn't work...

Back to try to compile in Windows with Cygwin....

Friday, August 28, 2015

SDCC And The Z-80

I'm not exactly sure exactly how I'm supposed to use the Z-80 code that ships with SDCC. There is a set of C library source code with processor specific directories. I can link against it, and the linker doesn't complain about missing putchar and getchar. I can define my own putchar and it doesn't complain about multiply defined putchar. This is not what I'd expect.

There is seemingly no makefile that would use these to create libraries. So I copied them to my local area and wrote my own makefiles to make my own libraries. I used the assembler and compiler to create a 'z80.lib' in the z80 directory. Then a crt.lib, float.lib, and longlong.lib in the src directory.

This approach presented some questions.
sdar gives a segmentation fault--so I'm using the deprecated sdcclib
some functions are multiply defined--mostly low level basic arithmetic. Why?
some functions are completely unimplemented--mostly the char is.* functions. Why?
there are several printf options, none of which seeming appropriate

So my plan to get this working is to:
add the implementation for the is.* functions
add an implementation for  __print_format
add getchar and putchar

This should give me a version of C that will work for my purposes. I'll leave out longlong and float.

I also have to look into something I saw. A developer of a graphing calculator said on the SDCC forum, I think, the Z-80 compiler stopped creating correct code after some checkin. So I'll have to consider pulling that last version from SVN and compiling that.

Or look into other toolchains. Or fixing it if it's broken and contributing to the sdcc project.

I also don't know how to do something that I'd like to do. Namely, I want to have a C runtime in bank 0 and then an application in bank 1. So I want the bank 1 application to access the C runtime code in bank 0 without actually linking it. That might involve an assembly file that defines a bunch of stubs that forward to the actual code-- just a set of jumps that jump the appropriate code in bank 0 or maybe to a vector in bank 0 so that I can replace the code in bank0 without needing to recompile the applications.

Just stuff I'm thinking about....

Monday, August 24, 2015

Installing Eclipse and EclipseSDCC

I had to add Java to machine to allow me to use the Rackspace console to upgrade my cloudserver. So I have Java. I never liked Eclipse, but I like text makefiles even less. So I decided to see if I can use the lesser of two evils. I'm loading Eclipse now. I also loaded EclipseSDCC which is a plugin that should make it easy to use the SDCC toolchain in projects. There is a site at http://www.embeddedcraft.org/free8051.html which I am referring to as well.

So my first problem, some error 13. Googling led me to the conclusion that I need to install the JDK. That seemed to work. I can launch Eclipse.

Now to add the plugin. Just some drag/drop from a ZIP file.

Let's launch again, shall we....

OK, I'm going to go ahead and do the tutorial just because the last time I tried to use Eclipse it was just an exercise in frustration.

The first part of the tutorial assumed I know what views and perspectives are. I don't, so I clicked on a link to Views. But how to go back? There is no back button. ... Or is there. Above the text, I see the tops of some itty bitty buttons. One looks like the of an arrow pointing left. I click it. It goes back. Eclipse Help doesn't render properly on my Windows 10 Surface Pro 3. Grrr... Did I mention that Eclipse was an exercise in frustration?

Did I jump ahead in the tutorial? It doesn't work.  OK, I tried to run the program from the GIUT command line and the command line tells me cygwin1.dll and cygstdc__-6.dll are missing from my computer. Cygwin Bash runs it fine. So I guess it's a PATH thing. I won't be debugging my Z-80 code in Eclipse anyway.

I'm tired of the tutorial. So I'll just see if I can do a Z-80 project.... I can see the MCS-51 project under Others... I expect to see. Hopefully I can convert it to a Z80 project. Also, there is a toolchain under C, but not under C++.

And, I get "Project cannot be created" "Reason:" "Internal Error:", and when I click details "java.lang.NullPointerException". One solution was to copy the "os" folder from one file to another, but that didn't work. I suppose I could install a 32 bit version of Eclipse....

Did I mention that Eclipse is an exercise in frustration? Makefiles are starting to look pretty nice about now.....



Monday, August 17, 2015

Z-80 Code Generation with SDCC

I started looking at how sdcc compiles C for the Z-80. In order to write assembly for the processor, I need to knwo the calling convention. After all, I need to write putchar() and getchar() to use the GPIO/SPI CPLD to talk to the MAX3110E.

The calling convention is that arguments are pushed onto the stack from right to left. Return values are returned in L for 8 bit, HL for 16 bit, and DEHL for 32 bit return values.

The other part that's important to know is who saves registers. The default is that the caller saves them. The problem is when the caller calls a small function that doens't use registers. In this case, it would be nice to let the the callee save the registers. One way of doing this is using the --callee-saves function-list or --callee-saves-all. Also the __naked option can be used on the function definition. Finally, #pragma callee_saves can be used. Except, I didn't notice any of that working. SO right now, I'm concluding that it is not supported on the Z-80 port. So, I'll just assume it's always caller saved and the functions can just use any register (except IX).

I'm not sure why some functions use IX and some don't. But it probably doesn't really matter too much. I just have to know how to use IX if I want and how not to if I don't.

Here is some sample C code to see how the calling convention works.


int plus(int a, int b, int c)
{
    int sum;
    sum = a + b + c;
    return sum;
}


int plusplus(int d, int e, int f)
{
    int sum = 0;
    int i;
    for (i = 0; i < 5; i++)
    {
        sum += plus(d, e, f);
    }
    return sum;
}


Here is the assembly.  The <-- notes are mine.

;--------------------------------------------------------
; File Created by SDCC : free open source ANSI-C Compiler
; Version 3.5.2 #9283 (MINGW64)
; This file was generated Mon Aug 17 20:12:13 2015
;--------------------------------------------------------
 .module argtest
 .optsdcc -mz80
 
;--------------------------------------------------------
; Public variables in this module
;--------------------------------------------------------
 .globl _plusplus
 .globl _plus
;--------------------------------------------------------
; special function registers
;--------------------------------------------------------
;--------------------------------------------------------
; ram data
;--------------------------------------------------------
 .area _DATA
;--------------------------------------------------------
; ram data
;--------------------------------------------------------
 .area _INITIALIZED
;--------------------------------------------------------
; absolute external ram data
;--------------------------------------------------------
 .area _DABS (ABS)
;--------------------------------------------------------
; global & static initialisations
;--------------------------------------------------------
 .area _HOME
 .area _GSINIT
 .area _GSFINAL
 .area _GSINIT
;--------------------------------------------------------
; Home
;--------------------------------------------------------
 .area _HOME
 .area _HOME
;--------------------------------------------------------
; code
;--------------------------------------------------------
 .area _CODE
;argtest.c:1: int plus(int a, int b, int c)
; ---------------------------------
; Function plus
; ---------------------------------
_plus::
;argtest.c:4: sum = a + b + c;
 ld hl,#4 <-- No IX. Why?
 add hl,sp
 ld iy,#2
 add iy,sp
 ld a,0 (iy)
 add a, (hl)
 ld d,a
 ld a,1 (iy)
 inc hl
 adc a, (hl)
 ld e,a
 ld a,d
 ld hl,#6
 add hl,sp
 add a, (hl)
 ld d,a
 ld a,e
 inc hl
 adc a, (hl)
 ld h,a
 ld l, d
;argtest.c:5: return sum;
 ret
;argtest.c:9: int plusplus(int d, int e, int f)
; ---------------------------------
; Function plusplus
; ---------------------------------
_plusplus::
 call ___sdcc_enter_ix <-- sets up ix -- see below
;argtest.c:11: int sum = 0;
;argtest.c:13: for (i = 0; i < 5; i++)
 ld hl,#0x0000
 ld e,l
 ld d,h
00102$:
;argtest.c:15: sum += plus(d, e, f);
 push hl  <-- caller save HL for for _plus (sum)
 push de  <-- caller save DE for for _plus (i)
 ld c,8 (ix)
 ld b,9 (ix)
 push bc
 ld c,6 (ix)
 ld b,7 (ix)
 push bc
 ld c,4 (ix)
 ld b,5 (ix)
 push bc
 call _plus
 pop af <-- throw away passed parameter d
 pop af <-- throw away passed parameter e
 pop af <-- throw away passed parameter f
 ld c,l <-- return value low
 ld b,h <-- return value high
 pop de <-- restore caller saved registers
 pop hl <-- restore caller saved registers
 add hl,bc
;argtest.c:13: for (i = 0; i < 5; i++)
 inc de
 ld a,e
 sub a, #0x05
 ld a,d
 rla
 ccf
 rra
 sbc a, #0x80
 jr C,00102$
;argtest.c:17: return sum;
 pop ix
 ret <-- HL already has sum
 .area _CODE
 .area _INITIALIZER
 .area _CABS (ABS)



This is part of the library:


;--------------------------------------------------------------------------
;  crtenter.s
;
;  Copyright (C) 2015, Alan Cox, Philipp Klaus Krause
;
;  This library is free software; you can redistribute it and/or modify it
;  under the terms of the GNU General Public License as published by the
;  Free Software Foundation; either version 2, or (at your option) any
;  later version.
;
;  This 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 General Public License for more details.
;
;  You should have received a copy of the GNU General Public License
;  along with this library; see the file COPYING. If not, write to the
;  Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
;   MA 02110-1301, USA.
;
;  As a special exception, if you link this library with other files,
;  some of which are compiled with SDCC, to produce an executable,
;  this library does not by itself cause the resulting executable to
;  be covered by the GNU General Public License. This exception does
;  not however invalidate any other reasons why the executable file
;   might be covered by the GNU General Public License.
;--------------------------------------------------------------------------

 .area   _CODE

 .globl ___sdcc_enter_ix

; Factor out some start of function code to reduce code size

___sdcc_enter_ix:
 pop hl ; return address
 push ix ; save frame pointer
 ld ix, #0
 add ix, sp ; set ix to the stack frame
 jp (hl) ; and return