A 4-bit port and array of 1-bit ports match in a non-concistent manner

Technical questions regarding the XTC tools and programming with XMOS.
User avatar
aclassifier
Respected Member
Posts: 507
Joined: Wed Apr 25, 2012 8:52 pm

A 4-bit port and array of 1-bit ports match in a non-concistent manner

Post by aclassifier »

I came across this when I was going to get the lib_spi to run on a startKIT. The problem is with the CS-pins that are mapped onto an array of 1-bit ports in the original, but on the startKIT there is one CS and one EN mapped as first and second bit of a 4-bit port. When I disregarded the EN pin the code ran fine with the CS bit going low and high like it should.

But when I made a branch of the code and added an spi_master_2 (aside: full code at [1]. This code takes care of both CS and EN bits, so it uses masks instead of 1-bit ports) I saw the situation. It’s like this (below).

The first bit of a 4-bit port is compatible with the first 1-bit port of the 1-bit port array. But there it correctly stops: the second bit of a 4-bit port is not compatible with the second 1-bit port of the 1-bit port array. From this follows that it should not have been allowed to send the 4-bit port instead of the array of 1-bit ports.

The type checker should, as far as I can see, not have allowed the compilation below. It even works for that first bit!

The semantics of p_ss[0] <: 1; with XS1_PORT_4C outputs 0001 binary but then it’s completely undefined, as with p_ss[1] <: 1; (but this latter is not compilable as I am not able to initialise p_ss for NUM_SPI_SLAVES 2 in the XS1_PORT_4C case. That’s fine! What is not fine it compiles and runs for NUM_SPI_SLAVES 1 in the XS1_PORT_4C case.

Defining PORT_PARAM_2 makes sense!

Code: Select all

#include <platform.h>
#include <xs1.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <iso646.h>
#include <xccompat.h> // REFERENCE_PARAMs

// Example from lib_spi:
void spi_master (
        out port p_ss[num_slaves],
        static const size_t num_slaves)
{
    for(unsigned i=0;i<num_slaves;i++)
        p_ss[i] <: 1;
}

// #define XMOS_10848_PORT_PARAM_2

#ifdef XMOS_10848_PORT_PARAM_2
    #define NUM_SPI_SLAVES 2
    out port p_ss[NUM_SPI_SLAVES] = {XS1_PORT_1A, XS1_PORT_1B};
#else
    #define NUM_SPI_SLAVES 1
    out port p_ss[NUM_SPI_SLAVES] = {XS1_PORT_4C};
#endif

int main() {
    par {
        spi_master (p_ss, NUM_SPI_SLAVES);
    }
    return 0;
}
I posted this as an internal Issue first but now it's here and at [2]

[1] http://www.teigfam.net/oyvind/home/wp-c ... d_teig.zip
[2] http://www.teigfam.net/oyvind/home/tech ... ent_manner
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
User avatar
infiniteimprobability
Verified
XCore Legend
Posts: 1149
Joined: Thu May 27, 2010 10:08 am

Post by infiniteimprobability »

I'm afraid that's how it is - A port is a resource which is accessed via a 32b register value. A 1b port (in unbuffered mode) maps bit 0 and the pin and all else are don't care.
However, when in buffered mode, the port is serialised and the 32b read/write accesses the transfer registers (which is loaded/written by the shift register).

So while the IP won't mind if you use a different width port for simple I/O (as long as the bit used is correct), anything using a buffered port will not work where the expected and actual port widths differ.
For example, reading a 1b buffered port will give you the last 32 pin values every 32 clocks whereas reading a 4b port will give you the last 8, 4b pin values every 8 clocks etc.

I do see your point about type checking - even though it's valid (and sometimes useful) in the architecture to swap resource types (you can even swap channels and ports at asm level which is GREAT for testing), for the most part, a type check on the port width may be useful.
Engineer at XMOS
User avatar
infiniteimprobability
Verified
XCore Legend
Posts: 1149
Joined: Thu May 27, 2010 10:08 am

Post by infiniteimprobability »

As you have seen, an array of 1b ports != a 4b port. I guess the compiler *could* reason that a 4b port can behave a bit like multiple 1b ports for the simple case (all output or input, simple I/O) but the machine code would be very different from that of multiple 1b ports. Also, if you wanted bi-directional bits you'd struggle because a port has only a single direction bit, no matter how wide.

XMOS ports differ quite a lot from general MCUs in this respect where a port is a bit in a memory mapped register.

This is partly the reason lib_gpio was written. It can slice a wide port into single 1b ports (assuming they are all inputs or outputs) each with thier own control interface. While it seems a little verbose, the compiler does do a reasonable job of boiling it down to inline code with locks on the shared resource, even though the lib_gpio task looks like it's own thread (which it is not because it's distributed).

lib_uart uses this for example... I can see a case for lib_spi being modified to use lib_gpio for the CS lines. It would not work though for the clock or MISO/MOSI lines as the port hardware is needed to get any sort of useful performance
Engineer at XMOS
User avatar
aclassifier
Respected Member
Posts: 507
Joined: Wed Apr 25, 2012 8:52 pm

Post by aclassifier »

This is so interesting! Wouldn't it really be stronger than "a type check on the port width may be useful" since it now works in the first bit but not on the rest? Either it should not be allowed or there should be some type conversion scheme that would allow it? Then I can't see how a type conversion will take care of all the properties of full 1-bit ports onto an array of just bits. After all a port is more like an N-bit usart, and an array of bits won't map onto an array of usarts.

I didn't know about lib_gpio! When look at these libraries I am so impressed by what is possible! Are the port pins used in map in gpio taken out of the port placemenet error check? I assume not, but then I don't see how the compiler understands the map placement list versus an "XS1_PORT_4C"? But I can still use out port p_ss[NUM_SPI_SLAVES] = {XS1_PORT_4C}; plus other pins, and then use the lib_gpio map for "the rest" of the pins? I assume yes, and assume that this is possible because of the precedence rules for ports? lib_gpio seems to use a 32b port as its "base" port?
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/