In the previous chapter, we programmed the Propeller in the large memory model where the code was all stored in hub memory and instructions were downloaded to a cog one at a time for execution. This exacts a performance penalty. If the code you want to run on a cog will fit entirely within the cog (496 longs), then you can write that code in such a way that it is copied to the cog all at once and runs on the cog natively.

Figure 13-1
A rotary snow plough used for clearing track. From “The World’s Work, Volume 1,” WH Page, AW Page, Doubleday, 1901. Available for free download from Google Books
https://books.google.com/books?id=688YPNQ5HNwC
.
13.1 Cog-C Mixed Mode Programming
Here we will write the main cog in the same way as in the previous chapter but will place the compression code in a separate file whose extension is cogc. This special extension will signal the compiler that the code in that file will be copied to a cog all at once.
In SimpleIDE start a new project named
compr_cogc
and create three files.
- compr_cogc.c is where the code for the main cog resides.
- compr.cogc is where the code for the compression cog resides.
- compr_cogc.h is a header file that contains common variable and constant declarations.
13.1.1 Main Cog Code
Listing 13-1 shows that the main cog code
is similar to the code in the previous chapter with a few significant changes.
1 #include <stdio.h>
2 #include <propeller.h>
3 // ADD THIS >>>
4 #include " compr_cogc.h"
5 // <<<
6
7 /* defines */
8
9 /* global variables */
10
11 // ADD THIS >>>
12 // reserved space to be passed to startComprCog
13 struct cogmem_t {
14 unsigned int stack[STACK_SIZE_BYTES >> 2];
15 volatile struct locker_t locker ;
16 } cogmem ;
17 // <<<
18
19 // shared vars
20 volatile int nsamps ;
21 volatile int ncompr ;
22 volatile int sampsBuf[NSAMPS_MAX];
23 volatile char packBuf[NSAMPS_MAX <<2]; // 128 * 4
24 volatile int comprCodesBuf[NSAMPS_MAX >>4]; // 128 / 16
25
26 // ADD THIS >>>
27 int startComprCog(volatile void *p) {
28 extern unsigned int _load_start_compr_cog [];
29 return cognew(_load_start_compr_cog, p);
30 }
31 // >>>
32
33 // DELETE THE comprCog FUNCTION
34 ...
35
36 // in main ...
37
38 ...
39
40 printf (" starting main \n");
41
42 // CHANGE TO THIS >>>
43 comprCogId = startComprCog (&cogmem.locker);
44 // <<<
45
46 if(comprCogId < 0) {
47 printf (" error starting compr cog \n");
48 while (1) {;}
49 }
50
51 ...
Listing 13-1
Contents of compr_cogc.c Showing Modifications from Previous Chapter
- Lines 13–16: Declare a structure struct cogmem_t { ... } cogmem with two members: an array named stack and another structure named struct locker_t locker. The cogmem structure will keep the memory for the stack and for the locker in adjacent space. The address of the locker struct will be passed to the new cog in the PAR register. Note that in this example we won’t be using the locker but still define it because the kernel expects this arrangement (stack followed by the locker variables). The definition of struct locker_t is in the header file compr_cogc.h.1 /* define the struct for passing data via PAR to the cog -- UNUSED */2 struct locker_t {3 };
- Lines 27–30: A function that starts the compression cog. This makes reference to the “magical” address location of _load_start_compr_cog. This form of this variable is always as follows:_load_start_<COGCFILENAME>_cog, where COGCFILENAME is the name of the file that contains the cog code (in our case, that file name is compr.cogc, so we put compr) in the name.The cognew function takes two arguments: that magical variable and the pointer p passed to the function. p points to the struct locker_t locker memory and will be placed in PAR. In this example, it is unused, but we still invoke it in all its messy glory so that if you ever want to use it, you know how!
- Line 43: We start the cog by calling the startComprCog function. It takes one variable, which is the address of the struct locker_t variable in the cogmem structure: &cogmem.locker.
When the main cog runs, it will call comprCogStart with the address of the start of the locker memory block (&cogmem.locker). Remember, the & operator returns the address of the variable, and cogmem.locker refers to the variable locker in the struct cogmem. Yes, I know it is messy, but if you copy this pattern slavishly, it should work.
13.1.2 Compression Cog-C Code
The compression code
is placed in its own file, compr.cogc, as shown in Listing 13-2. (You must have the cogc extension.) Whatever name you choose for this file is what shows up in the cognew command _load_start_compr_cog.
1 #include "compr_cogc.h"
2 #include <propeller.h>
3
4 // shared vars
5 extern volatile int nsamps ;
6 extern volatile int ncompr ;
7 extern volatile int sampsBuf[NSAMPS_MAX];
8 extern volatile char packBuf[NSAMPS_MAX <<2]; // 128 * 4
9 extern volatile int comprCodesBuf[NSAMPS_MAX >>4]; // 128 / 16
10
11 /* cog code - compr
12 use nsamps and ncompr to signal with main cog
13 start compression when nsamps != 0
14 signal completion with ncmopr > 0
15 signal error with ncompr = 0
16 compress sampsBuf to packBuf
17 populate comprCodesBuf
18 - args : pointer to memory space PAR - UNUSED
19 - return : none
20 */
21
22 void main (struct locker_t *p) {
23 int i, nc, nbytes, codenum, codeshift, code ;
24 int diff, adiff ;
25
26 while (1) {
27 if (nsamps == 0) {
28 continue ; // loop continuously while nsamps is 0
29 } else {
30
31 // perform the compression here
32 if (nsamps > NSAMPS_MAX || nsamps < -NSAMPS_MAX) {
33 ncompr = 0; // signal error
34 nsamps = 0;
35 continue ;
36 }
37 for(i=0; i< nsamps ; i++) {
38 if(i ==0) { // first samp
39 memcpy (packBuf, (char *) sampsBuf, 3);
40 nc = 3;
41 } else {
42 diff = sampsBuf[i] - sampsBuf[i -1];
43 adiff = abs(diff);
44 if (adiff < TWO_BYTES) {
45 nbytes = 1;
46 code = CODE08 ;
47 } else if (adiff < THREE_BYTES) {
48 nbytes = 2;
49 code = CODE16 ;
50 } else {
51 nbytes = 3;
52 code = CODE24 ;
53 }
54 // copy the correct number of bytes from diff
55 // to packBuf
56 memcpy (packBuf +nc, (char *) diff, nbytes);
57 nc += nbytes ;
58 }
59 }
60 ncompr = nc; // signal completion
61 nsamps = 0; // prevent another cycle from starting
62 }
63 }
64 }
Listing 13-2
Contents of compr.cogc
- Lines 5–9: These are variables that are shared between the main cog and this cog. The extern qualifier tells the compiler not to reserve new space but that this variable has been defined in another file. From now on, any reference to, for example, nsamps will reference the same variable as in the main cog (where it was originally defined). The volatile qualifier tells the compiler that even if it looks like this variable isn’t used, don’t optimize it away. It could be modified in, for example, the main cog.
- Line 22: The cog code is placed in its own main function that should never return. It takes one argument, a pointer to a memory block (this is the equivalent of PAR). In this example, this is unused because we prefer to use the shared variables.
Everything else is the same as in the previous chapter.
13.1.3 Header File compr_cogc.h
Listing 13-3 shows the contents of compr_cogc.h.
1 // size of stack in bytes
2 #define STACK_SIZE_BYTES 200
3 // compression constants
4 #define NSAMPS_MAX 128
5 #define CODE08 0b01
6 #define CODE16 0b10
7 #define CODE24 0b11
8 #define TWO_BYTES 0x7F // any diff values greater than this are 2 bytes
9 #define THREE_BYTES 0 x7FF // diff valus greater than this are 3 bytes
10
11 /* define the struct for passing data via PAR to the cog -- UNUSED */
12 struct locker_t {
13 };
Listing 13-3
Contents of compr_cogc.h
This is the front matter that is used by both the main cog and the compression cog (both have the line #include "compr_cogc.h"). The struct locker_t declaration is empty because it is a placeholder that we don’t use.
13.1.4 Running the Cog-C Code
Make sure that you have chosen LMM Main Ram as your memory model on the Project Options tab. (Even though you have chosen LMM, because the compr.cogc file has a .cogc extension, it will be compiled in Cog-C mode and copied to a new cog and run in that cog natively.)
starting main
started compression cog 1
done... nsamps = 0, ncompr = 3
samp0 = EFCDAB, packBuf = AB CD EF
nsamps = 0, ncompr = 384
samp0 = 989680, packBuf = 80 96 98
dt = 42064
Let’s compare the running speed to the other ways of compressing 128 longs. See Table 13-1.
Table 13-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 |
Clearly, programming in Cog-C mode improves speed by about a factor of three to four over LMM C mode. As you can see, PASM is still the fastest method, but Cog-C is no slouch!
13.2 Summary
We can start a new cog that is running in Cog-C mode, in which the C code is compiled to PASM and copied in its entirety to a cog and run there. Of course, the compiled code must be less than 496 longs (the size of cog memory).
- In the main file, we must set aside a chunk of memory for the stack and the “locker” (struct cogmem_t); a template for the main file is in Listing 13-4.
- Define shared variables that will be available to all cogs (volatile int x).
- Define a function that starts the cog (int startMyCogC(...)).
- Create a file named myCogC.cogc that contains the code to be loaded to the new cog; a template for the cog-C file is in Listing 13-5.
- In the main file, call the startMyCogC function.
- In the .cogc file, refer to the shared variables using extern volatile int x so the cog can use them.
1 // in main .c
2 #define STACK_SIZE_INTS 50
3
4 // put this in a mycogc .h file (common to this file and mycogc .cogc)
5 // #include " mycogc .h"
6 struct cogmem_t {
7 unsigned int stack[STACK_SIZE_INTS];
8 volatile struct locker_t locker ;
9 };
10
11 // declare the cog memory for the new cog
12 // there must be a different memory space for each cog
13 struct cogmem_t myCogCMem ;
14
15 // shared with all cogs
16 volatile int variable1 ;
17 volatile int variable2 ;
18 volatile int dataArray[100];
19
20 // start the new cog ...
21 int startMyCogC(volatile void * parptr) {
22 extern unsigned int _load_start_myCogC_cog[];
23 return cognew(_load_start_myCogC_cog, parptr);
24 }
25
26 int main () {
27 int myCogCId ;
28 // start the new cog . the address of locker is passed to
29 // the cognew command as parptr
30 myCogCId = startMyCogC(&myCogCMem . locker);
31 ...
32 while (1) {
33 ...
34 // access variable1, variable2, dataArray [] in main cog
35 }
36 }
Listing 13-4
Contents of main.c
for Cog-C Mixed Mode
1 // contents of myCogC .cogc
2
3 // put this in a mycogc .h file and
4 // # include " mycogc .h"
5 struct cogmem_t {
6 unsigned int stack [STACK_SIZE_INTS];
7 volatile struct locker_t locker ;
8 };
9
10 // any variables declared in another file
11 // must be set as `` extern '' so that the
12 // compiler knows where to find them.
13 extern volatile int variable1 ;
14 extern volatile int variable2 ;
15 extern volatile int dataArray [100];
16
17 // the code that will run in the new cog
18 // must be small enough to fit in 496 longs.
19 // the compiler will complain if it is too big
20 void main(struct locker_t *p)
21 while (1) {
22 ...
23 // access variable1, variable2, and dataArray [] in 2nd cog
24 }
25 }
Listing 13-5
Contents of the .cogc File