Jump to content
zezba9000

SDCC PIC16 C compiler?

Recommended Posts

Has anyone every tried using the SDCC C compiler for the Intellivision?

 

If so does it work well, whats missing etc?

 

Also is PIC16 binary compatible with the CP1600 cpus?

Edited by zezba9000

Share this post


Link to post
Share on other sites

I don't think there's a CP1610 backend for the SDCC compiler. The website certainly doesn't discuss its existence.

 

I did start work on a GCC backend once upon a time, but I didn't get very far before $DAYJOB swamped me. I based it on the MSP430 target, which is probably the closest conceptually to the CP1610. (Both start with a PDP11 and strip it down.)

 

I do have a working port of GNU binutils and have even managed to make an ELF version of Tag Along Todd (complete with a libsdk1600.a and a proper linking step).

 

The assembly syntax is noticeably different than AS1600—it's closer to Mattel's syntax viewed through the lens of AT&T syntax—and the binutils port is not as mature, so I haven't actually shared it with anyone yet. That, and I don't have a clean way to convert ELF to other common Intellivision ROM formats, nor an ELF loader in jzIntv or the LTO Flash GUI. I view those as necessary components in the tool chain.

 

Example code from the GAS version of CP1610 assembly:

.

            .sect   ".text._fillmem"
_clrscr:    mov     #0x00F0,    r1
            mov     #0x0200,    r4
_fillzero:  clr     r0
_fillmem:   mov     r0,         @r4++
            dec     r1
            bneq    _fillmem
            jr      r5

.

The corresponding AS1600 syntax:

.

CLRSCR      PROC
            MVII    #$0F0,  R1
            MVII    #$200,  R4
FILLZERO    CLRR    R0              ; Start out with R0 zeroed for FILLZERO
FILLMEM     [email protected]    R0,     R4      ; Store R0 out at R4, and move along
            DECR    R1              ; Keep going until our count runs out
            BNEQ    FILLMEM
            JR      R5              ; Return to the caller.
            ENDP

.

I actually like the GAS syntax better. GAS is not terribly user friendly though.

 

My hope was to eventually get the binutils toolchain mature enough—possibly adding support for bankswitching via the linker's support for overlays—so I could help migrate IntyBASIC over to it and solve the "memory map problem." In parallel, I'd hoped to get GCC working at least passably well before I discussed the project's existence.

 

I've been burned by announcing projects before they're ready to ship, and so I wasn't going to disclose the existence of this one. But... may as well now.

 

My GCC port is based on GCC 7.2, so it's reasonably fresh. I doubt many of the enhancements in 8.x and 9.x will benefit the Intellivision greatly. If someone with serious dev chops wants to help me get a baseline release over the line, I can check in what I have and arrange for SVN access. Perhaps having a second set of eyes on the project would help draw some of my focus to getting it done. I know having Steve Orth on LTO Flash! was a huge help getting that over the line and shipped.

 

I would love to have a C compiler for the Intellivision, but I only have so many hours in the day, and I keep letting myself get distracted. :-P

  • Like 3

Share this post


Link to post
Share on other sites

BTW, one thing that makes CP1610 unique: sizeof(char) == sizeof(int) == sizeof(void *) == 1, and CHAR_BIT == 16.

 

The fact that char is 16 bits on Intellivision is what stopped me from trying to write an LLVM backend instead of GCC. LLVM looks more approachable, but clang doesn't support CHAR_BIT != 8 except with out of tree patches that are also massively out of date.

 

From SDCC's webpage, it seems their definition of char is fixed at 8 bits. If they don't support a 16-bit char as an option, it's going to have a difficult time on the Intellivision.

Edited by intvnut

Share this post


Link to post
Share on other sites

One last clarification: The PIC16 is a different CPU from the CP1600. The PIC1650 was invented as a I/O coprocessor to augment the CP1600's weak I/O capabilities. The two have very different instruction sets.

Share this post


Link to post
Share on other sites

The other year, a talk at a C++ conference gained lots of attention when the compiler was used more or less as a macro assembler in order to write a Commodore 64 program. Several people who are not even into retro programming thought this was a cool way to build a C64 program using a modern (well, to which extent C++ is modern) language and a compiler. Personally I found it all quite a bit convoluted as it didn't seem to me like he actually could write anything but trivial C++ programs which then through a series of steps were converted into 6502 machine code.

 

I'm sure something similar could be done with the CP-1610 as well, though I think the end result would be just as convoluted, primitive and proof-of-concepty as the one presentation which I'm too lazy to look up and link to but I'm sure anyone interested in the topic could find quite easily.

Share this post


Link to post
Share on other sites

I've considered writing a "metaprogramming class library", where basically you construct your code from the metaprogramming classes, and then running the program generates the executable.

 

The idea being: You could express the high level logic and connections between various parts of the code by connecting the objects in various ways. Likewise, if you did have any values you needed to compute as part of the "compilation" process (e.g. reduce bitmaps down to actual bit patterns) you could do that as part of the executable.

 

For pure connections between executable blocks, there's a clear model—you can bind objects together with references or pointers. For example, suppose you need to configure a controller dispatch object:

.

// Assume HandleDisc, HandleAction, HandleKeypad are all
// IntvFunc objects that represent a function that will
// be compiled to Intellivision code.  Assume ControllerDispatch
// is an object that will result in a dispatch table, and
// and that could be registered with a GameEngine object.

auto ctrl_disp = ControllerDispatch(HandleDisc, 
                                    HandleAction, 
                                    HandleKeypad);

.

For conditional and looping constructs, though, you'd have to invent objects that capture the notion of a conditional, as opposed to just using "if", "for", "while", "switch". You have the domain-specific language (DSL) built out of all the objects, and then you have C++ around it. It seems pretty convoluted. It could be very powerful, but you'd have to be rather committed to the process to stick with it, and to keep the layers straight in your head.

 

The code that takes this mass of objects you've instantiated and linked, and then reduces them to code, is still a compiler back end. You've just used C++ as a parser and metaprogramming language.

 

So, while I've played around with this idea in the past, I've mostly rejected it. Note that I have written DSLs like this in Perl. They can be rather powerful, but they're ultimately rather specialized.

Share this post


Link to post
Share on other sites

BTW, one thing that makes CP1610 unique: sizeof(char) == sizeof(int) == sizeof(void *) == 1, and CHAR_BIT == 16.

 

The fact that char is 16 bits on Intellivision is what stopped me from trying to write an LLVM backend instead of GCC. LLVM looks more approachable, but clang doesn't support CHAR_BIT != 8 except with out of tree patches that are also massively out of date.

 

From SDCC's webpage, it seems their definition of char is fixed at 8 bits. If they don't support a 16-bit char as an option, it's going to have a difficult time on the Intellivision.

 

I have a .NET IL to C89 translator I'm experimenting with for targeting cc65, SDCC, GCC, etc, etc with. Currently just using C++ as a starting target to see what issues I run into with .NET opcodes BUT it might be feasible to get a C# / .NET IL subset outputting to "CP1610" assembler instead of just translating to C89. I mean I could always translate to IntyBASIC too but not sure thats the best solution for performance.

 

Not all .NET / C# features have to be supported on old low member hardware like this, such as try/catch.

 

If you ever get any C compiler working for "CP1610" I would be interested. You might look to see if the SDCC does support different char sizes. I'm pretty sure I saw a while back that it could. Might be the easiest option.

Edited by zezba9000

Share this post


Link to post
Share on other sites

You might look to see if the SDCC does support different char sizes. I'm pretty sure I saw a while back that it could. Might be the easiest option.

 

I took a look at the 'port' structure that describes object sizes. They're all in multiples of bytes. I don't see anywhere to indicate how many bits char has. The compiler internals Wiki page—which presumably would explain this—is MIA.

 

ISTR when I looked at SDCC around several years ago, it had support for different char sizes. All of the currently supported targets have 8-bit char, though, and I don't see a facility for changing that. If you can find one, please let me know. SDCC does have a lot less surface area than GCC.

Share this post


Link to post
Share on other sites

 

I took a look at the 'port' structure that describes object sizes. They're all in multiples of bytes. I don't see anywhere to indicate how many bits char has. The compiler internals Wiki page—which presumably would explain this—is MIA.

 

ISTR when I looked at SDCC around several years ago, it had support for different char sizes. All of the currently supported targets have 8-bit char, though, and I don't see a facility for changing that. If you can find one, please let me know. SDCC does have a lot less surface area than GCC.

 

I see char size declared in this structure for SDCC.

/** Basic type sizes */
struct
{
int char_size;
int short_size;
int int_size;
int long_size;
int longlong_size;
int near_ptr_size; // __near
int far_ptr_size; // __far
int ptr_size; // generic
int funcptr_size;
int banked_funcptr_size;
int bit_size;
int float_size;
}
s;
Edited by zezba9000

Share this post


Link to post
Share on other sites

 

 

I see char size declared in this structure for SDCC.

/** Basic type sizes */
struct
{
int char_size;
int short_size;
int int_size;
int long_size;
int longlong_size;
int near_ptr_size; // __near
int far_ptr_size; // __far
int ptr_size; // generic
int funcptr_size;
int banked_funcptr_size;
int bit_size;
int float_size;
}
s;

 

 

Correct, but those sizes are specified in bytes.

 

It's not entirely clear to me how char_size is supposed to work. I suppose I'd have to dig into the internals to see if char_size = 2 actually translates to CHAR_BIT = 16, and sizeof(char) = sizeof(int) = sizeof(void *) = 1.

 

Judging by the code, I don't think it will do the right thing. I think it'll end up with sizeof(char) = sizeof(int) = sizeof(void *) = 2. I'm basing it on how it evaluates sizeof:

.

    case SIZEOF:               /* evaluate without code generation */
      {
        /* change the type to a integer */
        struct dbuf_s dbuf;
        int size = getSize (tree->right->ftype);

        dbuf_init (&dbuf, 128);
        dbuf_printf (&dbuf, "%d", size);
        if (!size && !IS_VOID (tree->right->ftype))
          werrorfl (tree->filename, tree->lineno, E_SIZEOF_INCOMPLETE_TYPE);
        tree->type = EX_VALUE;
        tree->opval.val = constVal (dbuf_c_str (&dbuf));
        dbuf_destroy (&dbuf);
        tree->right = tree->left = NULL;
        TETYPE (tree) = getSpec (TTYPE (tree) = tree->opval.val->type);

        return tree;
      }

.

The getSize function will return 2 for char if I set char_size = 2.

 

Also, the code for pointer arithmetic will do the wrong thing as well, since it'll multiply indices by 2. Relevant parts from SDCCicode.c:

.

  /* if left is a pointer then size */
  if (IS_PTR (ltype) || IS_ARRAY (ltype))
    {
      unsigned int ptrSize;
      isarray = left->isaddr;
      nBytes = getSize (ltype->next);   // <--- would return 2 even for char
...
      if (nBytes != 1)
        {
          size = operandFromLit (nBytes);
          SPEC_USIGN (getSpec (operandType (size))) = 1;
          indexUnsigned = IS_UNSIGNED (getSpec (operandType (right)));
          if (!indexUnsigned && ptrSize > INTSIZE)
            {
              SPEC_LONG (getSpec (operandType (size))) = 1;
              SPEC_CVAL (getSpec (operandType (size))).v_ulong = nBytes;
            }
          right = geniCodeMultiply (right, size, (ptrSize >= INTSIZE) ? RESULT_TYPE_INT : RESULT_TYPE_CHAR);

.

So... I'd say SDCC does not support CHAR_BIT = 16.

Share this post


Link to post
Share on other sites

I'm a little lost in what you're saying.

"sizeof(char) = sizeof(int) = sizeof(void *) = 1" looks like you're saying a ptr is equal to one byte?

 

1) Intellivision has 524B of ram which means you need a ptr size of 16bits / 2bytes (not 1 byte / 8 bits) [Atari 2600 only has 128B of ram and could use 1 byte for ptrs].

2) Your line "// <--- would return 2 even for char" is what you want as the ptr size of any type must be 2 bytes.

 

The simplest thing to do is probably just ask in a SDCC repo issue so a developer can give an answer to this question.

To bad SDCC wasn't on GitHub which has a much better community.

 

If SDCC supports changing the size of an "int" it seems like it should support changing the size of a "char".

Share this post


Link to post
Share on other sites

In the C language definition a char is by definition 1 byte, and the number of bits in that byte are defined by CHAR_BIT. It's generally best for this type to match the minimal addressable unit on the hardware, which on the Intellivision is a 16-bit word. If you wanted to support pointers to 8-bit values, you would need to invent a new pointer type that's larger than 16 bits—or lose access to half the address map and stay at 16 bits—and do some moderately expensive work to access memory at the char size.

 

So, yeah, sizeof(char) = sizeof(int) = sizeof(void *) = 1 does mean that a pointer and an int are both one "byte" (in the C language definition sense), but that "byte" also happens to be 16 bits. C requires sizeof(char) = 1, and all other type sizes are multiples of that.

 

Looking elsewhere in SDCC, it appears to assume sizeof(int) > sizeof(char) in its implementation of the integer promotion rules. I suspect we'd encounter a cascade of issues if we were the first target to set char_size != 1.

 

I don't agree with this statement:

 

If SDCC supports changing the size of an "int" it seems like it should support changing the size of a "char".

 

Consider that Clang / LLVM allow changing the size of int, but they don't support CHAR_BIT != 8. There are out-of-tree patches to add that support, but the mainline Clang / LLVM do not have this support.

Edited by intvnut

Share this post


Link to post
Share on other sites

So whats the best C practice here then? Seems like it might just be better to leave char as 1 byte BUT make wchar_t the recommended character object to use thats 2 bytes for text.

Share this post


Link to post
Share on other sites

So whats the best C practice here then? Seems like it might just be better to leave char as 1 byte BUT make wchar_t the recommended character object to use thats 2 bytes for text.

 

The usual way to do this is to define 1 byte as 16 bits. That's what other word-addressable architectures do. C builds everything out of bytes, and wants each byte to be individually addressable. (That way, you can write functions such as memcpy and memset.)

 

So, you're really best off matching the size of its byte to the machine. On the Intellivision, C's byte would be 16 bits.

Edited by intvnut

Share this post


Link to post
Share on other sites

 

The usual way to do this is to define 1 byte as 16 bits. That's what other word-addressable architectures do. C builds everything out of bytes, and wants each byte to be individually addressable. (That way, you can write functions such as memcpy and memset.)

 

So, you're really best off matching the size of its byte to the machine. On the Intellivision, C's byte would be 16 bits.

 

Are you sure this is correct?

IntyBASIC supports 8bit and 16bit types to match the hardware. If you do this then byte/char = 16bit you now need a __int8 type to replace what most ppl use char for.

Share this post


Link to post
Share on other sites

 

Are you sure this is correct?

IntyBASIC supports 8bit and 16bit types to match the hardware. If you do this then byte/char = 16bit you now need a __int8 type to replace what most ppl use char for.

 

IntyBASIC doesn't have C's pointer and memory layout model. Instead, it has a particular model of 8 bit and 16 bit values, going so far as to force each to allocate in the specific type of hardware memory that can hold them.

 

In IntyBASIC, it's impossible to represent something like this from C:

.

struct {
    int8_t a, b;
    int16_t c;
};

.

where all three values are stored contiguously in memory. IntyBASIC puts all 8 bit variables in 8 bit RAM, and 16 bit variables in 16 bit RAM.

 

Also, C lets you do this:

.

int x, y, i;
char *cx, *cy;

cx = (char *)&x;
cy = (char *)&y;

for (i = 0; i < sizeof(x); i++) {
    *cy++ = *cx++;
}

.

That's not possible in IntyBASIC's model. You can't point to the two 8-bit halves of a 16-bit value, as each value exists at only one address.

 

If you define char as 8 bits on the Intellivision, you will need to invent a pointer type that lets you address the upper and lower halves of 16-bit values in 16-bit memory. If you want to make use of 8-bit RAM for arrays of char or structures with char at adjacent addresses, that pointer type will probably need to be encoded non-linearly. By that, I mean one range of values gets treated differently than the others. If you use the 16-bit model on the 8-bit RAM, you'd see memory that had 0x00 in every second byte.

 

The more natural model for the Intellivision, IMHO, is to match the char to the minimal addressable unit (a 16-bit location), and just use 16-bit math everywhere. The 8-bit memory on the Intellivision is really 16-bit memory that masks the upper 8 bits to 0. You will need to treat it specially from C, as you wouldn't be able to store native C structures there if you want to use a low-overhead native pointer type.

 

Now, SDCC does have a richer range of pointer types than a plain C compiler, and so you could conceive of pointer types for 8-bit vs. 16-bit memory, and try to use that to help optimize. If nothing else, it would enable you to store 16-bit values in 8-bit RAM. But you still have overhead now for accessing 16-bit memory, because you need to accommodate separate addresses for each of the 8-bit halves of the 16-bit value.

 

The Intellivision's CPU isn't a bad fit for C, but its memory system is unless you largely ignore its 8-bit memory and force C programmers to treat it specially.

Edited by intvnut

Share this post


Link to post
Share on other sites

FWIW, the tension here is between level of abstraction vs. control. I lean toward exposing more of the hardware and giving more control. It's possible to construct a C run time environment with a higher level of abstraction—and thus support for 8-bit bytes and the quirky non-uniform memory system—but that comes with higher cost and less control.

Edited by intvnut

Share this post


Link to post
Share on other sites

 

IntyBASIC doesn't have C's pointer and memory layout model. Instead, it has a particular model of 8 bit and 16 bit values, going so far as to force each to allocate in the specific type of hardware memory that can hold them.

 

In IntyBASIC, it's impossible to represent something like this from C:

.

struct {
    int8_t a, b;
    int16_t c;
};

...

 

So looking at Wikipedia I see "524B RAM, 932B graphics RAM". Which memory pool is used for 8bit addressing and which is used for 16bit addressing?

 

Also with your struct example, couldn't that struct be put into the 8bit memory just not 16bit memory (even if a bad idea you could address all the objects in the struct)? Or does this still not work because the 8 bit memory space is addressed in 16 bit steps?

 

Just thinking out loud here but if I was to output .NET IL structs to CP1610 ASM you could in theory use C#'s attributes to mark what memory pool you want to put the struct in. If the struct wasn't compatible with the 16 bit memory space you would just get compiler errors. In short if SDCC / C supported attributes you could do the same thing.

Edited by zezba9000

Share this post


Link to post
Share on other sites

 

So looking at Wikipedia I see "524B RAM, 932B graphics RAM". Which memory pool is used for 8bit addressing and which is used for 16bit addressing?

 

Also with your struct example, couldn't that struct be put into the 8bit memory just not 16bit memory (even if a bad idea you could address all the objects in the struct)? Or does this still not work because the 8 bit memory space is addressed in 16 bit steps?

 

Memory is a mess in the Intellivision world. Welcome aboard!

 

post-14113-0-08368600-1546643771_thumb.jpg

 

In the Master Component, there is a pool of 8-bit RAM at locations $0100 - $01EF. The memory at $0200 - $035F is 16-bit. ($0200 - $02EF is BACKTAB.) The Graphics RAM (GRAM) at $3800 - $39FF is 8-bit and only accessible during vertical retrace or when the display is blanked.

 

The ECS adds 2K of 8-bit RAM at $4000 - $47FF.

 

The Keyboard Component has 16K of 10 bit RAM at $8000 - $BFFF.

 

The Chess cartridge has 1K of 8-bit RAM at $D000 - $D3FF.

 

My JLP board design adds 8000 words of 16-bit RAM at $8040 - $9F7F.

 

The Intellicart lets you add more 16-bit RAM through much of the memory map.

 

The CC3 allows you the same flexibility as the Intellicart, and also lets you create 8-bit RAM.

 

LTO Flash! lets you add both 8-bit and 16-bit RAM, and adds bankswitching to get to the full 1MB of RAM it provides.

 

All of it is addressed in 16-bit steps. The 8-bit RAM just zeros out bits 15:8.

Edited by intvnut

Share this post


Link to post
Share on other sites

 

Memory is a mess in the Intellivision world. Welcome aboard!

 

attachicon.gifwelcome_aboard.jpg

 

In the Master Component, there is a pool of 8-bit RAM at locations $0100 - $01EF. The memory at $0200 - $035F is 16-bit. ($0200 - $02EF is BACKTAB.) The Graphics RAM (GRAM) at $3800 - $39FF is 8-bit and only accessible during vertical retrace or when the display is blanked.

 

The ECS adds 2K of 8-bit RAM at $4000 - $47FF.

 

The Keyboard Component has 16K of 10 bit RAM at $8000 - $BFFF.

 

The Chess cartridge has 1K of 8-bit RAM at $D000 - $D3FF.

 

My JLP board design adds 8000 words of 16-bit RAM at $8040 - $9F7F.

 

The Intellicart lets you add more 16-bit RAM through much of the memory map.

 

The CC3 allows you the same flexibility as the Intellicart, and also lets you create 8-bit RAM.

 

LTO Flash! lets you add both 8-bit and 16-bit RAM, and adds bankswitching to get to the full 1MB of RAM.

 

LOL omg.

 

I got a LTO Flash! Which sounds like the best device.

 

Another just thinking out loud here BUT what if instead of making a C compiler you were to just use C# subset with the Roslyn compiler that ONLY allowed structs, no GC etc.

This way you get the same features as C but with a much richer tool set like attributes etc to deal with these special case memory situation the Intellivision has?

Edited by zezba9000

Share this post


Link to post
Share on other sites

I know how to do all the C# Roslyn bits just don't know any of the ASM parts.

A C# subset might ironically be a better simpler to use lang for this console.

Edited by zezba9000

Share this post


Link to post
Share on other sites

Oh, and let's not forget ROM, which is at least 10 bits, and with Intellicart, CC3, JLP, and LTO Flash, is up to 16 bits wide. It's useful to have pointers to ROM.

Share this post


Link to post
Share on other sites

I got a LTO Flash! Which sounds like the best device.

 

I certainly tried to make it so. :-)

 

 

Another just thinking out loud here BUT what if instead of making a C compiler you were to just use C# subset with the Roslyn compiler that ONLY allowed structs, no GC etc.

This way you get the same features as C but with a much richer tool set like attributes etc to deal with these special case memory situation the Intellivision has?

 

I'm not really familiar with C#. I'm mostly a Linux guy with a Mac laptop that uses Windows as rarely as possible.

 

That said, if you don't need to rigidly adhere to C semantics, and instead can construct a bespoke language that better understands the contours of the Intellivision, you can build something with cleaner semantics. IntyBASIC is an example of this. If only it weren't BASIC. (I loathe BASIC, especially the lack of scoping.)

 

One advantage of sticking to C is that you stand a chance of adapting an existing optimizing compiler. IntyBASIC has limited optimization. Many of the optimizations it implements are quite effective. It still can be easily outclassed by hand-coded assembler.

 

 

I know how to do all the C# Roslyn bits just don't know any of the ASM parts.

A C# subset might ironically be a better simpler to use lang for this console.

 

The CP1610 assembly language is not that difficult. It's actually one of the most straightforward assembly languages I've learned. (Others include TMS9900, 6502, 8051, 8086, PIC24H, and all of the assembly languages I used and help develop at my previous $DAYJOB.)

Share this post


Link to post
Share on other sites

I think one benefit of having a language that is not following some kind of standard is that people can't expect to simply throw existing source code on the compiler and out comes a working program. If you advertise a C compiler for the Intellivision, some people will assume it works like a C compiler for any other target. A language like IntyBASIC may sound like any other BASIC but it doesn't take many minutes to figure out it is its own language, just with heritage of the old BASIC languages. Although there was an ANSI standard, BASIC also has never been quite that source code compatible across targets beyond the trivial HELLO WORLD program.

Share this post


Link to post
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...