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

9. Compression in PASM with TDD

Sridhar Anandakrishnan1 
(1)
Department of Geosciences, University Park, Pennsylvania, USA
 
Let’s convert our Steim compressor to PASM using TDD. The specification in Chapter 5 is written for a “traditional” language where the compression routine is called by the main program (as in our Spin example). As we want to implement the compression routine in PASM, we will start a new cog and will have to perform handshaking , as it is called, between the two cogs as follows (Figure 9-1):
../images/459910_1_En_9_Chapter/459910_1_En_9_Fig1_HTML.gif
Figure 9-1

Diagram showing the handshaking between the Spin cog and the PASM cog

As shown in Figure 9-1, the main Spin cog will set myns to a nonzero value, which will trigger the PASM cog to perform the compression. When the PASM cog finishes its work, it will set myncompr to a nonzero value, which signals the main cog that the compression is complete.

Let’s look at the “file view” and “cog view” of the project. The Spin code is split between the main file (steim_pasm_Demo) and the actual compressor file (steim_pasm), shown in Figure 9-2. The main file and the compressor file are related as shown in Figure 9-2: the main or driver file includes the worker file using the OBJ keyword.
../images/459910_1_En_9_Chapter/459910_1_En_9_Fig2_HTML.gif
Figure 9-2

The file-level view of the compression program, with the driver file on the left and the worker file on the right

The cog view of the project is (in part) as follows: cog0 runs main and the Spin methods in steim_pasm . cog1 runs the PASM code for the compression and decompression (Figure 9-3). The PASM code is triggered by changes in hub memory (when myns is set to a nonzero value).
../images/459910_1_En_9_Chapter/459910_1_En_9_Fig3_HTML.gif
Figure 9-3

The cog-level view of the compression program

The files for this program are on GitHub.
../images/459910_1_En_9_Chapter/459910_1_En_9_Figa_HTML.jpg

9.1 Overall Flowchart

Figure 9-4 shows the flowchart for the PASM cog. When N > 0 (myns), the cog will read and process a sample. Every 16th sample, it will write out a compression code long. Upon completion, it will write out the last compression code long. It will set N = 0 and write out the number of bytes used in the compression, N c (myncompr).
../images/459910_1_En_9_Chapter/459910_1_En_9_Fig4_HTML.gif
Figure 9-4

Flowchart for compression of samples

9.2 Test 1: Passing nsamps and ncompr

Let’s write a test for a small part of this: setting myncompr to non-negative. The test will do the following:
  1. 1.

    The calling cog (main) will set the number of samples (myns in the COMPR object) to the number of samples in sampsBuf.

     
  2. 2.

    Upon completion of compression, the STEIM PASM cog will set the number of compressed bytes (myncompr) to a non-negative number.

     

9.2.1 Spin Code

Listing 9-1 shows the Spin code . Add it to main.

 1  PUB MAIN
 2  ...
 3
 4    TEST_THAT_NCOMPR_IS_SET_TO_NONNEGATIVE
 5
 6  ...
 7  PUB TEST_THAT_NCOMPR_IS_SET_TO_NONNEGATIVE | nc, t0
 8  " test methods are rarely commented - the name should be
 9  " explanatory ...
10    nsamps := 1
11    nc := COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf)
12    t0 := nc => 0
13    TDD.ASSERT_TRUTHY (t0, string("Test that the compression cog sets ncompr => 0"))
Listing 9-1

Spin Code

The test is relatively simple . The test method in main sets nsamps to 1 and calls COMPR.COMPRESS. Within the COMPRESS object, myns is set to 1, which triggers the compression.

9.2.2 PASM Code

Listing 9-2 shows the START code that launches the PASM cog, the COMPRESS code, and the PASM code from the previous chapter with the handshaking discussed in detail.

 1  ...
 2  PUB START
 3      STOP
 4  '   myns <> 0 controls when the compression is started
 5      myns := 0
 6      ccogid := cognew(@STEIM, @myns)
 7      return ccogid
 8
 9  PUB COMPRESS(psampsBuf, ns, ...)
10  ...
11    myns := 0
12    myncompr := 0
13
14    ' this will start the compression
15    myns := ns
16
17    ' when ncompr is non -zero, the compression is complete
18    repeat until myncompr > 0
19    return myncompr
20  ...
21
22  STEIM org 0
23    ' copy the param addresses
24    mov _cnsPtr, par
25    mov _cncomprPtr, par
26    add _cncomprPtr, #4
27
28  : mainLoop
29     ' the signal for starting the compression is when ns <> 0
30     rdlong _cns, _cnsPtr wz
31     if_z jmp #:mainLoop
32
33     ' set myns to zero first so we don't start another cycle ...
34     mov _cns, #0
35     wrlong _cns, _cnsPtr
36
37     ' signal completion
38     mov _cncompr, #3
39     wrlong _cncompr, _cncomprPtr
40
41     ' wait for another compression request
42     jmp #:mainLoop
Listing 9-2

PASM Code

The START function is called once by the driver file, and that starts the PASM cog. The COMPRESS function can be called any number of times, and it communicates with the (running) PASM cog via the value of myns. When the COMPRESS function sets myns := ns, that sets myns to 1. The running PASM cog is continually monitoring myns (via the rdlong in the steim cog that sets _cns). Because of the wz effect of the rdlong, the instruction will set Z to 0 when _cns is nonzero. Now, instead of jumping back around to :mainLoop, control will pass to the next instructions:

' set myns to zero first so we don't start another cycle...
mov _cns, #0
wrlong _cns, _cnsPtr
mov _cncompr, #3
wrlong _cncompr, _cncomprPtr
' wait for another compression request
jmp #:mainLoop

Next, we set _ cns to zero and write that back to hub memory (to myns) so that when we go back up to :mainLoop, we don’t immediately start another compression cycle. Finally, we set the variable _cncompr to 3 and then write that value to hub memory (to myncompr) with the wrlong instruction.

In the Spin code in COMPRESS, the following statement will loop at that line continuously until myncompr is greater than zero, which it will be soon, when the PASM code does its wrlong!

repeat until myncompr > 0

At that point, the Spin code will continue to the next instruction.

return myncompr

Back in the calling function, nc will be set to 3, the value t0 will be true, and the ASSERT_TRUTHY call will print OK.

nc := COMPR.COMPRESS(@sampsBuf, nsamps, @packBuf, @comprCodeBuf)
t0 := nc => 0

9.3 Test 2: Packing Sample 0

In the previous section, we passed nsamps to the steim cog, which signals the start of the compression process. In this section, we will actually compress sample 0 and populate packBuf and comprCodeBuf. If you recall from the specification and from the Spin code examples, the three low bytes of sampsBuf[0] are placed in packBuf, and the code for a 3-byte compression is placed in the low 2 bits of comprCodeBuf[0].

9.3.1 Spin Code

To read from sampsBuf and write to the other two arrays, we need to pass their addresses to the steim cog. Listing 9-3 shows the modified calling routine, with three new tests that check whether sample 0 is packed correctly, whether ncompr is set correctly (to 3), and whether comprCodeBuf[0] is set correctly to COMPR.CODE24.

 1  PUB MAIN
 2  ...
 3
 4    TEST_THAT_SAMP0_IS_PACKED_PROPERLY
 5    TEST_THAT_SAMP0_SETS_NCOMPR_TO_3
 6    TEST_THAT_SAMP0_SETS_COMPRCODE_TO_CODE24
 7
 8  ...
 9  PUB TEST_THAT_SAMP0_IS_PACKED_PROPERLY | t0, nc
10    sampsBuf[0] := $AB_CD_EF
11    nsamps := 1
12    nc := COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf)
13    t0 := (packBuf[0] == sampsBuf[0] & $FF)
14    t0 &= (packBuf[1] == sampsBuf[0] >> 8 & $FF)
15    t0 &= (packBuf[2] == sampsBuf[0] >> 16 & $FF)
16    TDD.ASSERT_TRUTHY(t0, string(" Test that samp0 is packed correctly into packbuf "))
17
18  PUB TEST_THAT_SAMP0_SETS_NCOMPR_TO_3 | t0, nc
19    sampsBuf[0] := $AB_CD_EF
20    nsamps := 1
21    nc := COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf )
22    t0 := nc == 3
23    TDD.ASSERT_TRUTHY(t0, string("Test that samp0 sets ncompr =3"))
24
25  PUB TEST_THAT_SAMP0_SETS_COMPRCODE_TO_CODE24 | t0
26    sampsBuf[0] := $AB_CD_EF
27    nsamps := 1
28    nc := COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf)
29    t0 := comprCodeBuf[0] == COMPR#CODE24
30    TDD.ASSERT_TRUTHY(t0, string("Test that samp0 sets comprCodeBuf to CODE24"))
Listing 9-3

Driver File Testing Code

9.3.2 Memory Layout of Arrays and Parameters

As in the previous section, we now need to pass the addresses of sampsBuf, packBuf, and comprCodeBuf as well to the steim cog.

Modify the VAR declaration for myns and myncompr to now include three new variables: sampsBufAddr, packBufAddr, and comprCodeBufAddr. These variables are all listed one after the other, so they occupy successive long locations.

There is one crucial difference, however, between myns and sampsBufAddr: the number stored at location @myns/0x14 is the actual value of myns (1). The number stored at sampsBufAddr/0x1C is the address of the location where sampsBuf lives in hub memory; here I have put in some arbitrary number as an example (0x104). In Figure 9-5, I show the layout of hub memory with the longs following @myns (myncompr, sampsBufAddr, etc.). The array sampsBuf itself is in a different part of memory, but its address is in sampsBufAddr, which will be made available to the PASM cog. You need to do this “indirect addressing” when you want to pass the address of an array that is stored elsewhere.
../images/459910_1_En_9_Chapter/459910_1_En_9_Fig5_HTML.gif
Figure 9-5

Memory layout in hub showing the arrangement of variables that will be passed to the PASM cog. sampsBufAddr contains the address to the first sample of sampsBuf, which is shown further along in memory.

The COMPRESS method gets those values because the calling routine passes @sampsBuf (the address of sampsBuf), and this is similar for the other two arrays.

Those three addresses are stored in the variables sampsBufAddr, packBufAddr, and comprCodeBufAddr, and when myns is set to nonzero, the steim cog will start the compression, using those addresses.

VAR
  long myns, myncompr, sampsBufAddr, packBufAddr, comprCodeBufAddr
PUB COMPRESS(psampsBuf, ns, ppackBuf, pcomprCodeBuf) : ncompr
  sampsBufAddr := psampsBuf
  packBufAddr := ppackBuf
  comprCodeBufAddr := pcomprCodeBuf
  ' this will start the compression
  myns := ns

9.3.3 PASM Code

In the PASM code, we have already looked at how to access myns; now let’s look at how to access sampsBuf[0] using indirect addressing (Listing 9-4).

 1  ...
 2  : mainLoop
 3     ' the signal for starting the compression is when ns <> 0
 4     rdlong _cns, _cnsPtr wz
 5     if_z jmp #:mainLoop
 6
 7     ' get the array start addresses
 8     mov r0, par
 9     add r0, #8
10     rdlong _csampsbufPtr, r0
11
12     mov r0, par
13     add r0, #12
14     rdlong _cpackbufPtr, r0
15
16     mov r0, par
17     add r0, #16
18     rdlong _ccomprcodebufPtr, r0
19
20     call #GET_SAMPLE
21     call #HANDLE_SAMP0
22
23     ' set myns to zero first so we don't start another cycle ...
24     mov _cns, #0
25     wrlong _cns, _cnsPtr
26     ' signal completion
27     wrlong _cncompr, _cncomprPtr
Listing 9-4

Indirect Addressing in PASM to Read from an Array

  • Lines 8– 9 : Copy the contents of PAR (the address of myns) to a temporary variable r0 and add 8 to it. Now r0 will have the address of the location that has the address of sampsBuf (0x1C).

  • Line 10: The rdlong gets that address so that _csampsBufPtr is set to 0x104 (the address of the sampsBuf array in the hub).

We then copy the contents of the long at 0x1C to csampsbufPtr: csampsbufPtr = 0x104. We now have the location of sampsBuf[0]. We go through a similar procedure for the other two arrays, packBuf and comprCodeBuf.

9.3.4 Subroutines in PASM

As in other languages, you can define a subroutine when there is code that is often repeated or simply to keep your code modular and organized. In this case, I define two subroutines: GET_SAMPLE and HANDLE_SAMP0.

Subroutines are defined by enclosing them between two labels: SUBROUTINE_NAME and SUBROUTINE_NAME_ret. In addition, the second label (SUBROUTINE_NAME_ret) should be immediately followed by the PASM instruction ret. There are no formal arguments or parameters for the subroutine. Rather, the subroutine is in the same scope as the calling code. All variables are available and can be read and modified. Therefore, it is important to be clear on which variables are needed by the subroutine and which are modified. In Listing 9-5, I show the GET_SAMPLE and HANDLE_SAMP0 subroutines. The comments at the start show which variables are read and which are modified.

 1  GET_SAMPLE
 2  " read a sample from sampsBuf
 3  " modifies samp
 4  " increments sampsbufPtr to next sample
 5    rdlong _csamp, _csampsbufPtr
 6    add _csampsbufPtr, #4
 7  GET_SAMPLE_ret ret
 8
 9  HANDLE_SAMP0
10  " write the three bytes of samp to packbuf
11  " write code24 to comprcodebuf [0]
12  " destroys samp
13  " modifies ncompr
14
15    mov r0, #3
16  :s0loop
17     wrbyte _csamp, _cpackbufPtr
18     add _cpackbufPtr, #1
19     shr _csamp, #8
20     djnz r0, #:s0loop
21     ' loop terminates here
22     mov _cncompr, #3
23     mov _ccomprcode, _ccode24
24     wrlong _ccomprcode, _ccomprcodebufPtr
25  HANDLE_SAMP0_ret ret
Listing 9-5

Examples of subroutines, with comments showing variables that are used and modified

GET_SAMPLE is straightforward. It reads a long from the current index of sampBuf (initially 0) and increments the index to point at the next value in sampsBuf.

HANDLE_ SAMP0 takes that sample and writes the low 3 bytes back to packBuf. The following sequence is like a repeat 3 in Spin or a for loop in C. Set r0 to the number of times you want to loop and, at the end of the loop, decrement it by 1 and test for when it is equal to 0 (djnz r0, #:s0loop says “decrement r0 and jump to s0loop if r0 is not zero”). After three times, the loop terminates, and the instructions following djnz are executed.

  mov r0, #3
:s0loop
  <<do something>>
  djnz r0, #:s0loop
<<here after 3 iteration>>

The “do something” part is where the 3 bytes of sampsBuf[0] are copied to packBuf. The instruction wrbyte _csamp, _cpackbufPtr will copy the lowest byte of _csamp to the current address in _cpackbufPtr. The next instruction, add _cpackbufPtr, #1, will add the literal value 1 to the address _cpackbufPtr. This increments the index of packBuf. The next and final instruction in the loop, shr _csamp, #8, shifts the contents of the variable _csamp right by 8 bits (in other words, shifts the low byte out and moves the next higher byte into the low byte position). Finally, the instruction djnz r0, #:s0loop will decrement r0 by 1 and loop to :s0loop if r00.

The first time through the loop, the low byte (bits 0–7) of _csamp is copied to packBuf[0]. The second time (after the increment of _cpackbufPtr and the shift right by 8 bits of _csamp), the second byte (the original bits 8–15 of _csamp) is copied to packBuf[1]. The third time, the third byte of _csamp is copied to packBuf[2]. In the process, _csamp is destroyed—and we note that in the comments for the subroutine so that the calling routine knows not to use _ csamp again.

9.3.5 Testing the Compression of Sample 0

Let’s run our tests (including running our previous test). If these succeed, we are confident that the array addresses are being passed correctly.

Compression
mainCogId:     0
comprCogId:     2
Test that the compression cog sets ncompr => 0
...ok
Test that sample 0 is properly packed to packBuf
...ok
Test that compressor sets ncompr correctly for sample 0
...ok
Test that compressor sets compression code correctly for sample 0
...ok
Tests Run: 4
Tests  Passed:  4
Tests Failed: 0

9.4 Packing Differences for Latter Samples

Now that we know how to access the arrays, we can proceed with compressing all the samples by forming differences and packing those differences in packBuf based on their length.

Here is the PASM code in Listing 9-6. Here we add code to handle all the samples and to set the compression codes correctly.

 1     ...
 2     mov r0, par
 3     add r0, #16
 4     rdlong _ccomprcodebufPtr, r0
 5
 6     mov _cj, #0 ' j-th samp
 7     ' there are 16 codes in each code word
 8     ' there are NSAMPS_LONG /16 code longs (e.g., 8 codelongs for 128 samps)
 9     ' samps 0-15 have their codes in _ccodebufptr [0],
10     ' samps 16 -31 have their codes in _ccodebufptr [1], etc
11     ' _ccodebitidx is the location within a long (0, 2, 4, ... 30)
12     ' _ccodelongidx is the idx of the long in the code array
13     mov _ccodebitidx, #0
14     mov _ccodelongidx, #0
15
16     call #GET_SAMPLE
17     mov _cprev, _csamp ' save sample for diff
18     call #HANDLE_SAMP0
19     add _ccodebitidx, #2
20
21     sub _cns, #1 wz
22     if_z jmp #:done
23
24  : loopns
25     call #GET_SAMPLE
26     call #HANDLE_SAMPJ
27     mov _cprev, _csamp
28     add _cj, #1
29
30     add _ccodebitidx, #2
31     test _ccodebitidx, #31 wz
32     if_nz jmp #:samelong
33
34     wrlong _ccomprcode, _ccomprcodebufptr
35     add _ccomprcodebufptr, #4
36     mov _ccomprcode, #0
37
38  : samelong
39     djnz _cns, #:loopns
40
41  :done
42    ' wait for another compression request - zero out myns
43    ' so we don't immediately start another compression cycle
44    wrlong _cns, _cnsPtr
45    ' signal completion
46    wrlong _cncompr, _cncomprPtr
47    jmp #:mainLoop
48  ...
49  HANDLE_SAMPJ
50  " form difference between j and j-1 samps
51  " determine byte - length of diff
52  " save diff to packbuf
53  " increment ncompr appropriately
54  " modify comprcode appropriately
55
56  HANDLE_SAMPJ_ret ret
Listing 9-6

Changes to the PASM code to handle all the samples and to set the compression codes correctly

We have added these variables :
  • _cj: The current sample number.

  • _ccodelongidx: The index into the array comprCodeBuf where the current sample’s code will be stored.

  • _ccodebitidx: The bit location within the long where the code will be stored.

  • _cprev and _cdiff: The previous sample and the difference between the current and previous samples.

After initializing these variables (lines 6–14), we handle the special case of sample 0 (lines 16–18). Here we add the instruction mov _cprev, _csamp before the subroutine call HANDLE_SAMP0. Remember, that subroutine destroys _csamp, so if we want to use it to form the difference, we must save it. Next, we check for whether there is only one sample, and if so, we are done (lines 20–21): subtract 1 from _cns (the number of samples) and set the Z flag if the result is 0 (that is the effect of wz). If Z is set, jump to the code to finalize the compression (done) when the myncompr variables in hub memory are set to the correct values (which signals the main cog that the compression has completed).

If there is more than one sample to process, continue and process those samples in lines 23–38.
  • Lines 24–25: Get the next sample and process it (we’ll look at HANDLE_SAMPJ in a moment).

  • Lines 26–27: Save the sample for the next loop and increment j.

  • Lines 29–31: The bit index moves up by 2, and we check whether we need to move to the next comprCodeBuf long. The instruction test_ccodebitidx, #31 wz will set Z if ccodebitidx is equal to 32 (31=%0001 1111 and 32=%0010 0000; the bitwise AND of the two numbers is 0, which will set Z to 1) The instruction test is like and, but doesn’t save the result; it only affects the flags. If Z is not set, then we are still within this comprCodeBuf long, and we jump around the subsequent code.

  • Lines 33–35: New comprCodeBuf long. Write the completed long to hub memory and increment the pointer to point to the next long.

OK, now let’s look at HANDLE_SAMPJ , shown in Listing 9-7. Here we take the difference between the two samples and determine if that number would fit in one, two, or three bytes and handle packBuf and comprCodeBuf accordingly.

 1  HANDLE_SAMPJ
 2  " form the difference diff=csamp -cprev
 3  " if |diff| < 127, write 1 byte of diff
 4  " if |diff| < 32767, write 2 bytes of diff
 5  " else write three bytes of diff.
 6  " ccode, ccodebitidx changed
 7  " packbufptr incremented by 1,2, or 3
 8
 9    mov _cdiff, _csamp
10    sub _cdiff, _cprev
11
12    ' write a byte and check if more need to be written
13    ' repeat as necessary
14    ' r0 - running count of number of bytes used by diff
15    ' r1 - compr code - updated as more bytes are used
16    ' r2 - abs value of cdiff
17    wrbyte _cdiff, _cpackBufptr
18    add _cpackBufptr, #1
19    mov r0, #1
20    mov r1, _ccode08
21    ' is -127 < cdiff < 127
22    abs r2, _cdiff
23    cmp r2, _onebyte wc,wz
24    if_c_or_z jmp #:donej
25
26    ' write 2nd byte
27    shr _cdiff, #8
28    wrbyte _cdiff, _cpackBufptr
29    add _cpackBufptr, #1
30    add r0, #1
31    mov r1, _ccode16
32    ' is -32K < cdiff < 32k
33    cmp r2, _twobyte wc,wz
34    if_c_or_z jmp #:donej
35
36    ' must be 3 bytes long ...
37    shr _cdiff, #8
38    wrbyte _cdiff, _cpackBufptr
39    add _cpackBufptr, #1
40    add r0, #1
41    mov r1, _ccode24
42
43  :donej
44    add _cncompr, r0 ' add number of bytes seen here to ncompr
45    rol r1, _ccodebitidx
46    or _ccode, r1
47
48  HANDLE_SAMPJ_ret ret
49  ...
50  _onebyte long $7F
51  _twobyte long $7F_FF
Listing 9-7

Subroutine to form the difference between two samples and to update packBuf and comprCodeBuf depending on the size of the difference

  • Lines 9–10: Form the difference diff = samp - prev.

  • Lines 17–24: Write the low byte of diff to packBuf and set the code temporarily to CODE08. Check if ||δ j || < 127: cmp r2, _onebyte wc,wz. The constant _onebyte is 127, and wz says to set Z if r2 is equal to 127; wc says to set C if r2 is less than 127. if_c_or_z jmp #:donej says to jump to donej if C or Z is set.

  • Lines 26–41: If r2 is greater than 127, then write the second byte of diff; check again if that is all we need to do. If not, write the third byte of diff.

  • Lines 43–46: r0 has the number of bytes of diff (1, 2, or 3). Add it to _cncompr. r1 has the compression code (CODE08, CODE16, or CODE24). Shift it to the correct location (rol means “rotate left”) and set those two bits of _ccode (with an or instruction).

9.4.1 Testing Compressing Two Samples!

The following are the tests for the new code to test the code for compression (Listing 9-8). Hopefully the names of the methods and the informational string (in TDD.ASSERT_TRUTHY) are self-explanatory. Each testing method tests a small piece of functionality and should be re-run whenever changes are made to the code.

 1  PUB MAIN
 2  ...
 3    TEST_THAT_SAMP1_IS_PROPERLY_PACKED_ONE_BYTE
 4    TEST_THAT_SAMP1_IS_PROPERLY_PACKED_TWO_BYTES
 5    TEST_THAT_SAMP1_IS_PROPERLY_PACKED_THREE_BYTES
 6    TEST_THAT_SAMP1_SETS_COMPRCODE_CORRECTLY
 7    TEST_THAT_SAMP1_SETS_COMPRCODE_CORRECTLY_TWO_BYTES
 8  ...
 9  PRI TEST_THAT_SAMP1_IS_PROPERLY_PACKED_ONE_BYTE | t0, nc, d
10    nsamps := 2
11    d := 42
12    sampsBuf[0] := $AB_CD_EF
13    sampsBuf[1] := sampsBuf[0] + d ' diff will be < 127
14    nc := COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf)
15    t0 := nc <> -1 & (packBuf[3] == d)
16    TDD.ASSERT_TRUTHY (t0, string("Test that sample 1 is properly packed to packBuf (1 byte)"))
17
18  PRI TEST_THAT_SAMP1_IS_PROPERLY_PACKED_TWO_BYTES | t0, nc,d
19    nsamps := 2
20    d := 314
21    sampsBuf[0] := $AB_CD_EF
22    sampsBuf[1] := sampsBuf[0] + d ' diff will be less < 32k but > 127
23    nc :=COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf)
24    t0 := nc <> -1 & (packBuf [3] == d & $FF) & (packBuf [4] == d >> 8 & $FF)
25    TDD.ASSERT_TRUTHY (t0, string("Test that sample 1 is properly packed to packBuf (two bytes)"))
26
27  PRI TEST_THAT_SAMP1_SETS_COMPRCODE_CORRECTLY | t0, nc
28    nsamps := 2
29    sampsBuf[0] := $AB_CD_EF
30    sampsBuf[1] := $AB_CD_EF + $42
31    nc := COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf)
32    t0 := nc <> -1 & (comprCodeBuf [1] & %1111 == (COMPR#CODE08 <<2) | (COMPR # CODE24))
33    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets compression code correctly for sample 1"))
34
35  PRI TEST_THAT_SAMP1_SETS_COMPRCODE_CORRECTLY_TWO_BYTES | t0, nc
36    nsamps := 2
37    sampsBuf[0] := $AB_CD_EF
38    sampsBuf[1] := $AB_CD_EF + $42_42
39    nc :=COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf)
40    t0 := nc <> -1 & (comprCodeBuf [1] & %1111 == (COMPR# CODE16 << 2) | (COMPR # CODE24))
41    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets compression code correctly for sample 1 (2 bytes)"))
Listing 9-8

Some of the tests that exercise different parts of the compression code

Compression
mainCogId: 0
comprCogId: 2
Test that the compression cog sets ncompr => 0
...ok
Test that sample 0 is properly packed to packBuf
...ok
Test that compressor sets ncompr correctly for sample 0
...ok
Test that compressor sets compression code correctly for sample 0
...ok
Test that sample 1 is properly packed to packBuf (1 byte)
...ok
Test that sample 1 is properly packed to packBuf (two bytes)
...ok
Test that compressor sets compression code correctly for sample 1
...ok
Test that compressor sets compression code correctly for sample 1 (2 bytes)
...ok
Tests Run: 8
Tests Passed: 8
Tests Failed: 0

9.4.2 Test Compressing an Arbitrary Number of Samples

Now that we have tested the cases of two samples being packed correctly, let’s see if an arbitrary number of samples are packed correctly. Remember, the compression codes are written two bits at a time; the compression codes for samples 0–15 are stored in comprCodeBuf[0] and for sample 16 into comprCodeBuf[1]. We need to exercise the code in as many “edge” cases as possible. Here are the most basic ones: 16 samples, 17 samples, and 127 samples. This is not an exhaustive test but will give us some confidence that we are packing the bytes correctly and writing the compression codes correctly. Now that we know that the first and second sample are handled correctly, let’s write tests that walk through compressing the whole array, including testing for “edge cases” where problems often occur (Listing 9-9).

 1  PUB MAIN
 2  ...
 3    TEST_THAT_SAMP15_PACKS_PROPERLY
 4    TEST_THAT_SAMP16_PACKS_PROPERLY
 5    TEST_THAT_SAMP127_PACKS_PROPERLY
 6  ...
 7  PRI TEST_THAT_SAMP15_PACKS_PROPERLY | t0, nc, i, d
 8    nsamps := 16
 9    longfill(@sampsBuf, 0, 16)
10
11    sampsBuf[14] := 12
12    d := -42
13    sampsBuf[15] := 12 + d
14    nc := COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf)
15    repeat i from 0 to nc -1
16      UARTS.HEX(DEBUG, packBuf [i], 2)
17      UARTS.PUTC(DEBUG, SPACE)
18
19    t0 := nc == 18
20    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets nc correctly for samp 15"))
21    t0 := comprCodeBuf[0] >> 30 == %01
22    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets compr code correctly for samp 15"))
23    t0 := packBuf [nc -1] == d & $FF
24    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets compr code correctly for samp 15"))
25
26  PRI TEST_THAT_SAMP16_PACKS_PROPERLY | t0, nc, i, d
27    nsamps := 17
28    longfill(@sampsBuf, 0, 17)
29
30    sampsBuf[15] := 12
31    d := -42
32    sampsBuf[16] := 12 + d
33    nc := COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf)
34    t0 := nc == 19
35    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets nc correctly for samp 16"))
36    t0 := comprCodeBuf[1] & %11 == %01 '
37    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets compr code correctly for samp 16"))
38    t0 := packBuf [nc -1] == d & $FF
39    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets compr code correctly for samp 16"))
40
41  PRI TEST_THAT_SAMP127_PACKS_PROPERLY | t0, nc, i, d
42    nsamps := 128
43    longfill(@sampsBuf, 0, 128)
44
45    sampsBuf[126] := 12
46    d := -42
47    sampsBuf[127] := 12 + d
48    nc := COMPR.COMPRESS (@sampsBuf, nsamps, @packBuf, @comprCodeBuf )
49    t0 := nc == 130
50    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets nc correctly for samp 127"))
51    t0 := comprCodeBuf[7] >> 30 == %01 '
52    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets compr code correctly for samp 127"))
53    t0 := packBuf [nc -1] == d & $FF
54    TDD.ASSERT_TRUTHY (t0, string("Test that compressor sets compr code correctly for samp 127"))
Listing 9-9

Testing that more than two samples can be compressed correctly

In all these tests, we zero out sampsBuf and then set the last two samples to known values. We run the compression and make sure the number of compressed bytes is correct and that the packed array and compression code array have the correct values.

9.5 Success?

Did we speed things up? By how much?

nc= 382
dt= 29264
dt (ms) ~ 0

The Spin version took 1.5 million clocks, and the PASM version takes 29,000 clocks. This is a factor of 50 speedup. (Our original estimate was for 25,000 clocks in the PASM version, so that’s not bad.)

Let’s do a more comprehensive set of tests by writing a decompressor in the next chapter.

9.6 Summary

In this chapter, we showed how to pass an array to a PASM cog. When a new cog is launched, the address (in the hub) of a variable can be stored in the PAR register, which the new cog can use. To pass arrays, we need another level of indirection! The address at the start of the array is stored in a memory location. The address of that memory location is passed to the PASM cog in PAR (Listing 9-10 has a template that you can modify for new programs). PASM requires that we pay attention to every detail of the computation and build the “scaffolding” of our program from the ground up, much as is shown in Figure 9-6 for a railroad bridge used by Union Army during the Civil War.

 1  VAR
 2    long dataArray[100]
 3    long dataArrayPtr
 4
 5  PUB MAIN
 6    ' store the address of start of dataArray in dataArrayPtr
 7    dataArrayPtr := @dataArray
 8    ' pass the address of dataArrayPtr to the new cog in PAR
 9    cognew(@MYARRCOG, @dataArrayPtr)
10
11  DAT
12  MYARRCOG org 0
13    ' par has the address of dataArrayPtr, which is copied to r0
14    mov r0, par
15    ' doing a rdlong from that address gets the address of the start
16    ' of dataArray
17    rdlong _cdataArrPtr, r0
18
19    ' doing a rdlong from _cdataArrPtr gets the first element of dataArray
20    rdlong _cdata, _cdataArrPtr
21  ...
22    ' increment to the next element of dataArray and get it ...
23    add _cdataArrPtr, #4
24    rdlong _cdata, _dataArrPtr
Listing 9-10

Template for passing parameters to a PASM cog that uses both methods discussed in this chapter

../images/459910_1_En_9_Chapter/459910_1_En_9_Fig6_HTML.jpg
Figure 9-6

Railroad bridge across Potomac Creek, 1863 or 1864. “That man Haupt has built a bridge across Potomac Creek, about 400 feet long and nearly 100 feet high, over which loaded trains are running every hour and, upon my word, gentlemen, there is nothing in it but beanpoles and cornstalks!” —Abraham Lincoln, May 23, 1862. Library of Congress, ppmsca.11749.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.234.62