3.1.3.  Pulse Width Modulator

3.1.3.1 Specification Overview

The second module chosen for modeling was the PWM_8B6C, a Pulse Width Modulator, incorporated in the HC12 micro controller family. A PWM is used e. g. for engine control, which needs periodic pulses for ignition, etc.

The PWM used here, according to the specification [CO98], features six independent channels for pulse generation with programmable period and duty cycle, and a counter. The channels can be enabled and disabled separately; the same applies to the duty pulse polarity. Period and duty cycle are double buffered, which means their values can be changed without directly affecting the current pulse, the change takes effect at the end of the effective period, or if the channel is disabled. A register exists for specifying whether a particular channel’s pulse output should be left or center aligned. It is possible to pool the six 8-bit channels to three 16-bit channels, which increases resolution. Four clock sources (A, B, SA, SB), based on two clocks, provide for a wide range of frequencies.


PIC

Figure 3.3: PWM_8B6C Block Diagram, [CO98, p. 14, Fig. 1-1]

Before going through the register map, the clock concept has to be discussed. Of course, all four clocks depend on a system clock, as there is no external trigger input port in the PWM module. The two clocks, A and B, use a prescaler, which divides the system clock by 1, 2, 4, 8, 16, 32, 64, or 128. The prescaler can be specified separately for the two clocks, using two 3-bit values, fed into a multiplexer that selects the appropriate prescaler. The two other clocks, SA and SB, take the prescaled clocks, A and B, as input, and divide them further with a re-loadable 8-bit counter, then finally divide them by 2, which means SA and SB apply a divisor of 2, 4, 6, 8, ..., 512 to the prescaled clocks. Clocks A resp. SA are used for channels 0, 1, 4 and 5; clocks B resp. SB for channels 2 and 3.

The register map comprises 32 registers, each 8-bit wide. PWME enables and disables the channels, PWMPOL selects the polarity, and PWMCLK specifies if clock A/B or SA/SB should be used. PWMPRCLK avails bits 0-2 and 4-6 to pick the prescaler of the clocks. The pulse outputs can be left or center aligned; register PWMCAE selects one of the alternatives. The most important bits in PWMCTL indicate if channels 0 and 1, 2 and 3 or 4 and 5 should be concatenated to a 16-bit channel. The divisors for clocks SA and SB are stored in PWMSCLA/B, whereas a zero as value is interpreted as 256. The next registers are six registers, named PWMCNT0-5, that count the clock cycles during pulse generation. PWMPER0-5 specify the period length and PWMDTY0-5 give the duty cycles. Of course, the value of a PWMDTYx register cannot be greater than the according PWMPERx contents. The last register, PWMSDN, controls shutdown functions. All remaining registers are either reserved or test registers.

The significant external pins include of course six pins ipp_pwm_do0–5 for the pulse output. Additionally there is an extra pin for each channel that signals if the channel is enabled; the pins are named ipp_port_en0-5. Besides the interrupt pin, some extra pins are specified, but not used during normal operation.

A pulse signal in left aligned mode is generated as follows: At the beginning of the period, the output pin is set to the value in the according bit of the PWMPOL register. The counter PWMCNTx is incremented on each rising clock edge – whatever clock is used as source – and compared to the values in PWMDTYx and PWMPERx. When the counter reaches the value in PWMDTYx, the output pin is inverted, and then is hold on the level until the counter reaches the value in PWMPERx. Here the output pin is set to the original value, and the counter PWMCNTx reset to 0.


PIC

Figure 3.4: PWM Left Aligned Output, [CO98, p. 40, Fig. 4-3]

The process for center aligned pulse generation is slightly different: The effective period is twice as long as in left aligned mode. The first half of the period matches the above one. If the counter reaches the PWMPERx value, the pin retains its level, and the counter begins to count backwards. When it passes the PWMDTYx value while it is decremented, now for the second time, the pin changes the level. When the counter gets to 0, again the count direction swaps and a new period begins.


PIC

Figure 3.5: PWM Center Aligned Output, [CO98, p. 41, Fig. 4-5]

The 16-bit mode works similar to the 8-bit functionality. As mentioned before, the PWMCNT, PWMDTY and PWMPER registers are grouped into pairs, the even registers (0, 2, 4) act as the high bytes, the odd registers (1, 3, 5) as the low bytes of the 16-bit values. The configuration for polarity, clock selection, alignment, and the output pins is done with the odd numbered bits and pins, as seen in Table  3.3 on page 202.


Table 3.3: Concatenation Mode Summary, [CO98, p. 44, Tab. 4-2]
CONxx PWMEx PPOLx PCLKx CAEx PWMx Output
CON45 PWME5 PPOL5 PCLK5 CAE5 PWM5
CON23 PWME3 PPOL3 PCLK3 CAE3 PWM3
CON01 PWME1 PPOL1 PCLK1 CAE1 PWM1

Exactly the same specification exists for an eight channel PWM; the two additional channels are associated to clocks B/SB. The register map increases then 6 bytes in size, and there are no longer unused bits in PWME, PWMPOL, PWMCAE, PWMCLK and PWMCTL.

3.1.3.2 Implementation

The model implements the full specification, and turns its attention to performance. This is much more complicated than in the ATD module, because here we have to deal with four different clocks and six completely independent channels. As an example: The PWMCNTx registers contain the current counter value, so the program that runs on the Star12, can determine the progress of a period. A solution to this could be, that an event is generated for each PWM clock cycle, to update the values. But this would lead to excessive event scheduling – possibly every system clock cycle the PWM could produce an event. It is quite obvious that this way may result in poor performance, especially because it is also possible, that no one ever reads the register contents. So the right solution is to start the calculation, when the output signals have to be swapped, and schedule an event for this, and do not care about the counter register. If that is accessed at any time, its value can be calculated dynamically from the system cycle count; the model only has to know when the particular period has started.

The following piece of code shows, how a cycle counter is calculated on demand. The value dTimestamp[x], used to determine when a period has started, is calculated on each period start. See comments for details.

 
1// read access to PWMCNTx. 
2void pwm: :ReadCheck_PWMCNTx(motUINT08 channel) 
3{                    // calculate value on read to save events 
4  motOffsetT o f f s e t ; 
5  motCyclesT cycles ; 
6  motGetCycles(&offset , &cycles) ;            // get current simulation time 
7  if (bConcatenate[channel>>1])              // in concatenation mode? 
8  { 
9     channel = ((channel+1) % 2) + channel ;// calc . e f f e c t i v e channel 
10     if ( reg_PWME & (1<<channel))             // channel enabled ? 
11     { 
12        motUINT16 tmp = static_cast<motUINT16>( (cycles 
13           - dTimestamp[ channel ] ) / iClocks [ channel ] ) ; 
14        if (dTimestamp[ channel ] >= 0)         // already started? 
15        { 
16           if (tmp < iValuePER[ channel ] )      // the concatenated period 
17           { 
18             reg_PWMCNT[ channel ]   = static_cast<motUINT08>(tmp) ; 
19             reg_PWMCNT[channel-1] = static_cast<motUINT08>(tmp >> 8) ; 
20           } else {          // in CAE mode counter may run backwards 
21             reg_PWMCNT[ channel ]   = 
22             static_cast<motUINT08>(2*iValuePER[ channel ] - tmp) ; 
23             reg_PWMCNT[channel-1] = static_cast<motUINT08>((2*static_cast<int>( iValuePER[ channel ] )-tmp) >> 8) ; 
24           } 
25           SetReg08(reg_PWMCNT[ channel ]  , const_PWMCNT0+channel) ; 
26           SetReg08(reg_PWMCNT[channel -1] , const_PWMCNT0+channel-1); 
27        } 
28     } 
29  } else {                                            // no concatenation 
30     if ( reg_PWME & (1<<channel)) 
31     { 
32        motUINT08 tmp = static_cast<motUINT08>((cycles - dTimestamp[ channel ] )/iClocks [ channel ] ) ; 
33        if (dTimestamp[ channel ] >= 0)         // already started? 
34        { 
35           if (tmp < reg_PWMPER[ channel ] )     // cnt may run backwards 
36             reg_PWMCNT[ channel ] = tmp; 
37           else 
38             reg_PWMCNT[ channel ] = 2* static_cast<int>(reg_PWMPER[ channel ] )-tmp; 
39        } 
40        SetReg08(reg_PWMCNT[ channel ] , pwm: :const_PWMCNT0+channel) ; 
41     } 
42  } 
43}
Listing 3.2: Checking Routine for PWM

This may look expensive, but remember this code is only executed, when really a read access to a PWMCNTx register occurs. In line 13 you may notice the comment “already started”, which might be confusing at first sight, because the check whether the channel is enabled, takes place in line 10. But if a channel is enabled, it might be necessary to wait for the clock edge of the particular clock. So the channel may be enabled, though the pulse generation has not started yet. The branch beginning in line 19 shows the extra code necessary for center aligned pulses: The effective period is twice as long as usual, and the counter may run backwards. In most functions there are no discrete branches for 8-bit and 16-bit mode. Period and duty registers are not called directly, but through intermediate values, to ensure transparency for 8-bit and 16-bit concatenated mode of operation. But here, the PWMCNTx registers are actually written, so their values have to be calculated explicitly. In lines 23, 24 and 38 you clearly see that there are the so-called external registers, which are accessed by Octopus, and internal variables having the same values for faster access. This technique also allows a very simple implementation of the double buffering of PWMPERx and PWMDTYx: The Octopus registers are only read, when buffers, pursuant to the specification, are exchanged.

Another difficulty is, that the model should cover two PWM modules. This also is more difficult than in the ATD, because there the additional registers were at the end of the register map, so it is possible to program them statically and then mask the unused part out. Here, the variable part of registers is not at the end of the map, but interleaved. So the module must dynamically create the register map, including the pointers to the access handler functions, which must exist for the biggest possible number of channels, e. g. eight times. Of course these handler functions do nothing more than to invoke a generic handler function for that access type and the type of register.

 
1static motBoolT OnReadCheck_PWMCNT0 ( motBusTokenT* Token){ 
2  ((pwm*)motGetModelData())->ReadCheck_PWMCNTx(0) ; 
3  return motT; 
4}; 
5static motBoolT OnReadCheck_PWMCNT1 ( motBusTokenT* Token){ 
6  ((pwm*)motGetModelData())->ReadCheck_PWMCNTx(1) ; 
7  return motT; 
8};
Listing 3.3: PWM generic read check routines

The function motGetModelData resolves to a pointer to the particular model instance. These callbacks execute the function presented above. To allow dynamic register definition, pointers to these callbacks are collected in an array, so are the register names.

 
1motBoolT (* const pwm: :OnReadCheck_PWMCNT[ ] ) (motBusTokenT*) = 
2{ 
3  pwm: :OnReadCheck_PWMCNT0, pwm: :OnReadCheck_PWMCNT1, 
4  pwm: :OnReadCheck_PWMCNT2, pwm: :OnReadCheck_PWMCNT3, 
5  pwm: :OnReadCheck_PWMCNT4, pwm: :OnReadCheck_PWMCNT5, 
6  pwm: :OnReadCheck_PWMCNT6, pwm: :OnReadCheck_PWMCNT7 
7}; 
8 
9const char* pwm: :names_PWMCNT[ ] = {"PWMCNT0" , "PWMCNT1" , "PWMCNT2" , 
10  "PWMCNT3" , "PWMCNT4" , "PWMCNT5" , "PWMCNT6" , "PWMCNT7"};
Listing 3.4: PWM check callback routines

Of course the same technique applies for PWMDTYx and PWMPERx registers. The name and function pointers for each register are assembled in a structure, and merged to an array of register structures. This array is then passed to the base class, which will later use the names and callbacks.

A very complicated case was the fact that scaler and prescaler for the clocks can be changed while channels are enabled. Although the specification warns of doing this, because of possible pulse truncations, I had to model the behavior. Additionally there raises the problem of outdated events: If a pulse is generated, an event is always pending. If now the scaler is changed, this event becomes outdated. But Octopus provides no possibility to un-schedule events, so it has to be filtered out when the simulation time reaches that point. The following piece of code shows a part of the Eval callback, where the event filtering is done.

 
motCyclesT modulo = fmod( 
  CurrentCycles - ModelInstance->dTimestamp[ channel ] , 
  ModelInstance->iClocksPerPeriod [ channel ] 
) ; 
 
int motUINT16 iState = static_cast<int>( 
  CurrentCycles - ModelInstance->dTimestamp[ channel ] ) / 
  ModelInstance-> iClocksPerPeriod [ channel ] ;
Listing 3.5: PWM eval callback

The remainder of the division offset cycles by cycles per clock shows, if this event was released before a change of scale. It has to be 0 to be current.

Of course, for the PWM, there are the same debugging aids like for the ATD model. If any of the registers PWMPOL, PWMCLK, PWMCAE, PWMPRCLK, PWMSCLA/B are written to, while channels are enabled, a message is generated that warns of possible pulse truncation. There are also warnings for accesses to registers that have no effect or are erroneous, when concatenated mode is active.

Again, there are model manager commands that show the state of the module. Besides registers and ports, the state shows:


in>pt octo pwm state clock  
 
pwm internal module state  
channel(s) enabled:    0 1 2 3 4 5  
concatenated channels: 4&5  
channel 0 uses clock  A  
channel 1 uses clock  A  
channel 2 uses clock  B  
channel 3 uses clock  B  
channel 4 uses clock  A  
channel 5 uses clock  A  
 
pwm current sample rate  
clock frequency:          25.000000 MHz  
clock A prescaler:        1  
clock B prescaler:        8  
clock SA scale:           2*2  
clock SB scale:           2*1  
clock A rate:             25.000000 MHz  
clock B rate:             3.1250000 MHz  
clock SA rate:            6.2500000 MHz  
clock SB rate:            3.1250000 MHz  
 
in>


Figure 3.6: Hiwave sample output for PWM

A sample output of the Hiwave command line interface looks as in Figure  3.6 on page 208. The model can be configured with respect to the number of channels. It is possible to have 2, 4, 6 or 8 channels. Although the two existing specifications are only for 6 and 8 channels, it was no additional effort to allow also 2 and 4 channels. So the model can be used for four different PWM modules.

3.1.3.3 Testing

For testing purposes, the VCD dumper, integrated in Octopus, was used. All test cases produce some output. The VCD files then are analyzed graphically, as the expense for rating them automatically would have been too big. As VCD viewer a free tool named GTKwave (see appendix  4 on page 289) was used.

Again the test cases are C programs, compiled and executed automatically. They perform the following test cases, accompanied with some screen shots.


pict

Figure 3.7: PWM basic functional test output


pict

Figure 3.8: PWM prescaler decrease test output


pict

Figure 3.9: PWM re-enabling channels test output

For some test cases, of course, it is necessary to count cycles, but in most cases it becomes immediately obvious if a test case was passed or not.

The PWM module consists of about 1,900 lines of code, additionally about 800 lines of test code.