© Sridhar Anandakrishnan 2018
Sridhar AnandakrishnanPropeller Programminghttps://doi.org/10.1007/978-1-4842-3354-2_14

14. Programming with C and PASM

Sridhar Anandakrishnan
(1)
Department of Geosciences, University Park, Pennsylvania, USA
 
In this chapter, we will look at the combination of using C for the main cog (and any other cogs that are not required to be fast) and PASM for the critical cog that must run as fast as possible. For this mode, we must use the PAR method of passing variables between the main cog and the compression cog (in pure-C programming, we use shared variables, but that isn’t possible here). Hitching together fast and slow components hasbeen done before - see Figure 14-1 for an example!
In summary, we will do the following:
  • In the C program, we will create a block of memory (using a struct) that contains the variables we want to pass to the PASM cog. Because we use a struct, the variables are in contiguous memory.
  • We create a Spin/PASM file that has one Spin method (START) that calls cognew to start a PASM cog. The address of the struct is passed to this cog in PAR.
  • Now the C program and the PASM cog can interact through the variables in the struct.

14.1 Compression with C and PASM

Create a new project in SimpleIDE called compr_c_pasm and create two files: compr_c_pasm.c and compr.spin.

14.1.1 C Code for Main Cog

Listing 14-1 shows the contents of the PASM file.
 1  /* libraries */
 2  #include <stdio.h>
 3  #include <propeller.h>
 4
 5  /* defines */
 6
 7  // compression constants
 8  #define NSAMPS_MAX 128
 9
10  /* global variables */
11  // reserved space to be passed to startComprPASMCog
12  volatile struct locker_t {
13    int nsamps ;
14    int ncompr ;
15    int *psampsBuf ;
16    unsigned char *ppackBuf ;
17    int *pcomprCodesBuf ;
18  } locker ;
19
20  volatile int sampsBuf[NSAMPS_MAX];
21  volatile unsigned char packBuf[NSAMPS_MAX <<2]; // 128 * 4
22  volatile int comprCodesBuf[NSAMPS_MAX >>4]; // 128 / 16
23
24  int startComprPASMCog(unsigned int * parptr) {
25    extern unsigned int binary_compr_dat_start[];
26    return cognew(binary_compr_dat_start, parptr);
27  }
28
29
30  /* main cog - initializes variables and starts new cogs.
31   * don 't exit - start infinite loop as the last thing.
32   */
33  int main(void)
34  {
35    int comprCogId = -1;
36    int i;
37    unsigned int t0;
38
39    locker.nsamps = 0;
40    locker.ncompr = -1;
41    locker.psampsBuf = sampsBuf ;
42    locker.ppackBuf = packBuf ;
43    locker.pcomprCodesBuf = comprCodesBuf ;
44
45    printf (" starting main \n");
46
47    comprCogId = startComprPASMCog (&locker);
48    if(comprCogId < 0) {
49      printf (" error starting compr cog \n");
50      while (1) {;}
51    }
52
53    printf(" started compression cog %d\n", comprCogId);
54
55    /* start the compression cog by setting nsamps to 1 */
56    sampsBuf [0] = 0 xEFCDAB ;
57    locker.nsamps = 1; //= cogmem.locker.nsamps = 1;
58
59    /* wait until the compression cog sets ncompr to a non -neg number */
60    while(locker.ncompr < 0) {
61      ;
62    }
63
64    printf(" done ... nsamps = %d, ncompr = %d\n", locker.nsamps, locker.ncompr);
65    printf(" samp0 = %x, packBuf = %x %x %x\n", sampsBuf [0], packBuf [0], packBuf [1], packBuf [2]) ;
66
67    while (1)
68    {
69      ;// do nothing
70    }
71  }
Listing 14-1
Contents of ch12/compr_c_pasm.c
A459910_1_En_14_Fig1_HTML.jpg
Figure 14-1
Rapid Transit, Washington, GA 1903. Photographer JW Stephenson. Library of Congress archives ( http://www.loc.gov/pictures/item/2012646774 ).
  • Lines 12–18: Declare a structure that contains all the variables to be passed to the PASM cog. By placing them in a struct, you ensure that they will remain contiguous and in order. The PASM cog finds variables by knowing their address relative to the start of the memory location passed to it in PAR. So, the PASM cog expects nsamps to be at the start of that memory block and ncompr to be 4 bytes past that.
  • Lines 15–17: Here we place the addresses for the three arrays. psampsBuf is the address of sampsBuf and so on. Pointers in C are 4 bytes long, so the three pointers are at the correct locations that the PASM cog expects. The expression int *p says that p is a pointer to an int array.
  • Lines 20–21: The arrays themselves are declared here. They are also qualified to be volatile so the compiler won’t inadvertently remove them.
  • Lines 24–27: This is the function that starts the PASM cog. It has a “magic” invocation as well. binary_compr_dat_start is the address in memory of the code to be written to the cog. The variable has the form binary_<PASMFILENAME>_dat_start, where PASMFILENAME has a .spin extension (in our case, compr.spin).
  • Lines 39–43: The lockers are populated. A member of a struct is accessed via locker.nsamps, and so on. The address of an array, such as sampsBuf[], can be obtained by simply mentioning the name of the array, as in locker.psampsBuf = sampsBuf and so on.
  • Line 47: Here we call the function that starts the cog and pass the address of the beginning of the locker: &locker. The address of the first member of a struct is the same as the address of the struct variable; thus, the address of nsamps is given by &locker (remember, the & operator returns the address of the variable).

14.1.2 PASM Code

The PASM code that will run in the new cog is placed in a file with the .spin extension (see Listing 14-2). The file name will appear in the extern variable binary_compr_dat_start in the main code.
 1
 2  PUB START (locker)
 3    cognew (@STEIM, locker)
 4
 5
 6  DAT 'steim
 7  ''
 8  ''
 9
10  STEIM org 0
11    ' copy the param addresses
12    mov _cnsPtr, par
13    mov _cncomprPtr, par
14    add _cncomprPtr, #4
15  ...
Listing 14-2
Contents of compr.spin
  • Lines 2–3: Define a public method named START that calls the Spin method cognew. As in regular Spin code, this has two arguments: the address of the PASM code we want to run (in this case @STEIM) and the memory location to place in PAR (in this case locker). When the main C cog starts the cog with the following, the memory location parptr is passed to this START method and then passed on to the Spin cognew method here.
    cognew(binary_compr_dat_start, parptr)
  • Lines 6–15: This code is lifted directly from the PASM code in Chapter 9.
Running this code results in compression speeds almost identical to the PASM case (as it should, this is really the same code running in the same way). See Table 14-1.
Table 14-1
Comparison of Compression Speeds with Cog-C Mode Added
Language
Number of Counts to Compress 128 Samples (Smaller Is Better)
Spin code
1.5 million counts
PASM code
22,000 counts
C code (LMM only)
150,000 counts
Cog-C mode
42,000 counts
C and PASM code
22,000 counts

14.2 Summary

You can start a pure-PASM cog if, for example, you have some tested and fast code that you want to reuse. To do so, you must have two files: one in C and one in Spin and PASM. The C program will create a block of memory (in the hub). That memory location is passed to the Spin method, which passes it to the PASM cog.
The main C file in Listing 14-3 has a template for creating the memory (struct locker_t ... locker;) and calling the Spin method START in the file fastCog.spin (Listing 14-4): cognew(binary_fastCog_dat_start, parptr);.
 1  // Contents of main.c
 2
 3  // the address of the locker struct is equal to the address of the
 4  // first field in the struct. That address is passed to the
 5  // PASM cog in PAR. The PASM cog will determine the addresses
 6  // of the other variables; they are in consecutive memory locations
 7  struct locker_t {
 8    int variable1 ;
 9    int variable2 ;
10    int * array ; // this is a pointer to an array
11  } locker ;
12
13  // start the cog
14  int startFastCog(unsigned int * parptr) {
15    extern unsigned int binary_fastCog_dat_start [];
16    return cognew(binary_fastCog_dat_start, parptr);
17  }
18
19  // this array is only available to the main cog
20  // in order to pass it to the PASM cog, copy it 's
21  // address to the locker
22  volatile int dataArray [100];
23
24  int main () {
25    int fastCogId ;
26    locker.variable1 =42; // populate the locker
27    locker.variable2 =12;
28    locker.array = dataArray ; // copy the address of the array to the locker
29    // start the PASM cog
30    fastCogId = startFastCog (& locker);
31
32    // from here on, locker.variable1, etc may be modified by fastCog
33    while (1) {
34      ;
35    }
36  }
37  // main.c ends here
Listing 14-3
Template for C/PASM: C File
The Spin/PASM file (Listing 14-4) has a single Spin method (START) whose argument is the address of memory (in the hub) that we want to share with the PASM cog. This address is used as the second argument to the cognew call that launches the PASM cog (and, therefore, that address is placed in PAR). In the PASM cog, we can modify memory in the hub that the C program is, presumably, interested in.
 1  ' the contents of fastCog.spin
 2  ' locker is the address of the " locker " variable from the main cog
 3  PUB START(locker)
 4    cognew(@FASTCOG, locker)
 5
 6  DAT 'fastcog
 7  FASTCOG org 0
 8    mov _var1Ptr, par ' par contains the address of locker, which
 9                       ' is also the address of the first field, variable1
10    mov _var2Ptr, par
11    mov _arrayPtr, par
12    add _var2Ptr, #4 ' variable2 is 4 bytes after variable1
13    add _arrayPtr, #8 ' the * address * of the array is 8 bytes after variable1
14  ...
15  ' and we 're off to the races
16    rdlong _cVar1, _var1Ptr ' get C's locker.variable1
17    add _cVar1, #42         ' do something
18    wrlong _cVar1, _var1Ptr ' write it back
19
20    ...
21  _var1Ptr res 1
22  _var2Ptr res 1
23  _arrayPtr res 1
24  _cVar1 res 1
25  FIT 496
26  'fastCog.spin ends here
Listing 14-4
Template for C/PASM: PASM File