code: /**
   * Copyright (c) 2004-2005, Regents of the University of California
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * Redistributions of source code must retain the above copyright notice,
   * this list of conditions and the following disclaimer.
   *
   * Redistributions in binary form must reproduce the above copyright
   * notice, this list of conditions and the following disclaimer in the
   * documentation and/or other materials provided with the distribution.
   *
   * Neither the name of the University of California, Los Angeles nor the
   * names of its contributors may be used to endorse or promote products
   * derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  package avrora.sim.mcu;
  
  import avrora.sim.RWRegister;
  import avrora.sim.Simulator;
  import avrora.sim.state.BooleanView;
  import avrora.sim.clock.Clock;
  
  /**
   * The <code>Timer16Bit</code> class emulates the functionality and behavior of a 16-bit timer on the
   * Atmega128. It has several control and data registers and can fire up to six different interrupts
   * depending on the mode that it has been put into. It has three output compare units and one input
   * capture unit. UNIMPLEMENTED: input capture unit.
   *
   * @author Daniel Lee
   */
  public abstract class Timer16Bit extends AtmelInternalDevice {
  
      // Timer/Counter Modes of Operations
      public static final int MODE_NORMAL = 0;
      public static final int MODE_PWM_PHASE_CORRECT_8_BIT = 1;
      public static final int MODE_PWM_PHASE_CORRECT_9_BIT = 2;
      public static final int MODE_PWM_PHASE_CORRECT_10_BIT = 3;
      public static final int MODE_CTC_OCRnA = 4;
      public static final int MODE_FASTPWM_8_BIT = 5;
      public static final int MODE_FASTPWM_9_BIT = 6;
      public static final int MODE_FASTPWM_10_BIT = 7;
      public static final int MODE_PWM_PNF_ICRn = 8;
      public static final int MODE_PWM_PNF_OCRnA = 9;
      public static final int MODE_PWN_PHASE_CORRECT_ICRn = 10;
      public static final int MODE_PWN_PHASE_CORRECT_OCRnA = 11;
      public static final int MODE_CTC_ICRn = 12;
      // 13 is reserved
      public static final int MODE_FASTPWM_ICRn = 14;
      public static final int MODE_FASTPWM_OCRnA = 15;
  
  
      public static final int MAX = 0xffff;
      public static final int BOTTOM = 0x0000;
  
      final RegisterSet.Field ICESn_flag;
      
      public class InputCapturePin implements BooleanView {
          boolean level;
          private ValueSetListener listener;
  
          public boolean getValue() {
              return level;
          }
  
          public void setValue(boolean v) {
              if (v != level) {
                  level = v;
                  // the ICESn (input capture edge select) bit determines if a rising (1) or falling (0) edge is used as trigger
                  if ((ICESn_flag.value == 1) == level)
                    captureInput();
                  if (listener != null) {
                      listener.onValueSet(this, v);
                  }
              }
          }
  
          public void setValueSetListener(ValueSetListener listener) {
              this.listener = listener;
          }
      }
  
      /**
       * The <code>OutputCompareUnit</code> class represents an output compare unit that is
       * connected to the timer. The output compare unit functions by continually comparing the
       * count of the timer to a particular value, and signalling a match when complete.
       */
      class OutputCompareUnit {
          final BufferedRegister OCRnXH_reg;
          final BufferedRegister OCRnXL_reg;
          final OCRnxPairedRegister OCRnX_reg;
          final AtmelMicrocontroller.Pin outputComparePin;
          final RegisterSet.Field mode;
          final RegisterSet.Field force;
          final char unit;
          final int flagBit;
          final ATMegaFamily.FlagRegister flagReg;
  
          OutputCompareUnit(Microcontroller m, RegisterSet rset, char c, int fb, ATMegaFamily.FlagRegister fr) {
              unit = c;
              OCRnXH_reg = new BufferedRegister();
              OCRnXL_reg = new BufferedRegister();
              OCRnX_reg = new OCRnxPairedRegister(OCRnXH_reg, OCRnXL_reg);
              outputComparePin = (AtmelMicrocontroller.Pin)m.getPin("OC"+n+unit);
              mode = rset.getField("COM"+n+c);
              force = rset.installField("FOC"+n+c, new FOC_Field());
              flagBit = fb;
              flagReg = fr;
  
              installIOReg("OCR"+n+unit+"H", new OCRnxTempHighRegister(OCRnXH_reg));
              installIOReg("OCR"+n+unit+"L", OCRnX_reg);
          }
  
          class FOC_Field extends RegisterSet.Field {
              public void update() {
                  if ( value == 1 ) {
                      if ( read16(TCNTnH_reg, TCNTnL_reg) == read() ) {
                          output();
                      }
                  }
                  // TODO: reset the value to 0
                  //set(0);
              }
          }
  
          void forceCompare(int count) {
              if ( count == read() ) {
                  output();
                  // note: interrupts are not posted when the compare is forced
              }
          }
  
          void compare(int count) {
              if ( count == read() ) {
                  output();
                  flagReg.flagBit(flagBit);
              }
          }
  
          void flush() {
              OCRnXH_reg.flush();
              OCRnXL_reg.flush();
          }
  
          private void output() {
              // read the bits in the control register for compare mode
              // TODO: this is not correct for all modes...
              switch (mode.value) {
                  case 1:
                      outputComparePin.write(!outputComparePin.read()); // toggle
                      break;
                  case 2:
                      outputComparePin.write(false);
                      break;
                  case 3:
                      outputComparePin.write(true);
                      break;
              }
          }
  
          int read() {
              return read16(OCRnXH_reg, OCRnXL_reg);
          }
      }
  
      final RWRegister TCNTnH_reg; // timer counter registers
      final TCNTnRegister TCNTnL_reg;
      final PairedRegister TCNTn_reg;
  
      final OutputCompareUnit[] compareUnits;
      final Simulator.Event[] tickers;
  
      final RWRegister highTempReg;
  
      final RWRegister ICRnH_reg; // input capture registers
      final RWRegister ICRnL_reg;
      final PairedRegister ICRn_reg;
  
      Simulator.Event ticker;
  
      final RegisterSet.Field WGMn;
      final RegisterSet.Field CSn;
  
      final InputCapturePin inputCapturePin;
  
      long period;
  
      boolean blockCompareMatch;
  
      protected final Clock externalClock;
      Clock timerClock;
  
      // information about registers and flags that specifies
      // which specific registers this 16-bit timer interacts with
  
      final int n; // number of timer. 1 for Timer1, 3 for Timer3
  
      // these are the offsets on registers corresponding to these flags
      int OCIEnA;
      int OCIEnB;
      int OCIEnC;
      int TOIEn;
      int TOVn;
      int OCFnA;
      int OCFnB;
      int OCFnC;
      int ICFn;
  
      int inputCaptureInterrupt;
  
      // general timer/count interrupt flag and mask register
      protected ATMegaFamily.FlagRegister xTIFR_reg;
      protected ATMegaFamily.MaskRegister xTIMSK_reg;
      // for OCIE1C and OCF1C a different register can be set in ATMega128
      protected ATMegaFamily.FlagRegister cTIFR_reg;
      protected ATMegaFamily.MaskRegister cTIMSK_reg;
  
      protected int[] periods;
  
      // This method should be overloaded to initialize the above values.
      protected abstract void initValues();
  
      protected Timer16Bit(int n, int numUnits, AtmelMicrocontroller m) {
          super("timer"+n, m);
          this.n = n;
  
          RegisterSet rset = m.getRegisterSet();
  
          initValues();
  
          WGMn = rset.installField("WGM"+n, new RegisterSet.Field() {
             public void update() {
                 resetTicker(tickers[value]);
             }
          });
          CSn = rset.installField("CS"+n, new RegisterSet.Field() {
              public void update() {
                  resetPeriod(periods[value]);
              }
          });
  
          inputCaptureInterrupt = m.getProperties().getInterrupt("TIMER"+n+" CAPT");
          inputCapturePin = new InputCapturePin();
  
          highTempReg = new RWRegister();
  
          compareUnits = new OutputCompareUnit[numUnits];
          newOCU(0, numUnits, m, rset, 'A', OCFnA, xTIFR_reg);
          newOCU(1, numUnits, m, rset, 'B', OCFnB, xTIFR_reg);
          newOCU(2, numUnits, m, rset, 'C', OCFnC, cTIFR_reg);  // OCFnC can have a different register!
  
          TCNTnH_reg = new RWRegister();
          TCNTnL_reg = new TCNTnRegister();
          TCNTn_reg = new PairedRegister(TCNTnH_reg, TCNTnL_reg);
  
          ICRnH_reg = new RWRegister();
          ICRnL_reg = new RWRegister();
          ICRn_reg = new PairedRegister(ICRnL_reg, ICRnH_reg);
  
          ICESn_flag = rset.getField("ICES"+n);
          
          externalClock = m.getClock("external");
          timerClock = mainClock;
  
          installIOReg("TCNT"+n+"H", highTempReg);
          installIOReg("TCNT"+n+"L", TCNTn_reg);
  
          installIOReg("ICR"+n+"H", highTempReg);
          installIOReg("ICR"+n+"L", ICRn_reg);
  
          tickers = new Simulator.Event[16];
          installTickers();
      }
  
      private void installTickers() {
          Timer16Bit.OutputCompareUnit ocA = compareUnits[0];
          Timer16Bit.BufferedRegister ocrah = ocA.OCRnXH_reg;
          Timer16Bit.BufferedRegister ocral = ocA.OCRnXL_reg;
          tickers[MODE_NORMAL] = new Mode_Normal();
          tickers[MODE_PWM_PHASE_CORRECT_8_BIT] = new Mode_PWMPhaseCorrect(0xff, null, null);
          tickers[MODE_PWM_PHASE_CORRECT_9_BIT] = new Mode_PWMPhaseCorrect(0x1ff, null, null);
          tickers[MODE_PWM_PHASE_CORRECT_10_BIT] = new Mode_PWMPhaseCorrect(0x3ff, null, null);
          tickers[MODE_CTC_OCRnA] = new Mode_CTC(ocrah, ocral);
          tickers[MODE_FASTPWM_8_BIT] = new Mode_FastPWM(0xff, null, null);
          tickers[MODE_FASTPWM_9_BIT] = new Mode_FastPWM(0x1ff, null, null);
          tickers[MODE_FASTPWM_10_BIT] = new Mode_FastPWM(0x3ff, null, null);
          tickers[MODE_PWM_PNF_ICRn] = new Mode_PWM_PNF(ICRnH_reg, ICRnL_reg);
          tickers[MODE_PWM_PNF_OCRnA]  = new Mode_PWM_PNF(ocrah, ocral);
          tickers[MODE_PWN_PHASE_CORRECT_ICRn] = new Mode_PWMPhaseCorrect(0, ICRnH_reg, ICRnL_reg);
          tickers[MODE_PWN_PHASE_CORRECT_OCRnA] = new Mode_PWMPhaseCorrect(0, ocrah, ocral);
          tickers[MODE_CTC_ICRn] = new Mode_CTC(ICRnH_reg, ICRnL_reg);
          tickers[13] = new Mode_Reserved();
          tickers[MODE_FASTPWM_ICRn] = new Mode_FastPWM(0, ICRnH_reg, ICRnL_reg);
          tickers[MODE_FASTPWM_OCRnA] = new Mode_FastPWM(0, ocrah, ocral);
      }
  
      public BooleanView getInputCapturePin() {
          return inputCapturePin;
      }
  
      void captureInput() {
          ICRnL_reg.write(TCNTnL_reg.value);
          ICRnH_reg.write(TCNTnH_reg.value);
          xTIFR_reg.flagBit(ICFn);
          interpreter.getInterruptTable().post(inputCaptureInterrupt);
      }
  
      void newOCU(int unit, int numUnits, Microcontroller m, RegisterSet rset, char uname, int fb, ATMegaFamily.FlagRegister fr) {
          if ( unit < numUnits ) {
              compareUnits[unit] = new OutputCompareUnit(m, rset, uname, fb, fr);
          }
      }
  
  
      /**
       * Flags the overflow interrupt for this timer.
       */
      protected void overflow() {
          /*if (devicePrinter != null) {
              boolean enabled = xTIMSK_reg.readBit(TOIEn);
              devicePrinter.println("Timer" + n + ".overFlow (enabled: " + enabled + ')' + "  ");
          }*/
          // set the overflow flag for this timer
          xTIFR_reg.flagBit(TOVn);
      }
  
      /**
       * The <code>PairedRegister</code> class exists to implement the shared temporary register for the
       * high byte of the 16-bit registers corresponding to a 16-bit timer. Accesses to the high byte of
       * a register pair should go through this temporary byte. According to the manual, writes to the
       * high byte are stored in the temporary register. When the low byte is written to, both the low
       * and high byte are updated. On a read, the temporary high byte is updated when a read occurs on
       * the low byte. The PairedRegister should be installed in place of the low register. Reads/writes
       * on this register will act accordingly on the low register, as well as initiate a read/write on
       * the associated high register.
       */
      protected class PairedRegister extends RWRegister {
          RWRegister high;
          RWRegister low;
  
          PairedRegister(RWRegister high, RWRegister low) {
              this.high = high;
              this.low = low;
          }
  
          public void write(byte val) {
              low.write(val);
              high.write(highTempReg.read());
          }
  
          public byte read() {
              highTempReg.write(high.read());
              return low.read();
          }
  
      }
  
      /**
       * The normal 16-bit read behavior described in the doc for PairedRegister does not apply for the
       * OCRnx registers. Reads on the OCRnxH registers are direct.
       */
      protected class OCRnxPairedRegister extends PairedRegister {
          OCRnxPairedRegister(RWRegister high, RWRegister low) {
              super(high, low);
          }
  
          public byte read() {
              return low.read();
          }
      }
  
      /**
       * See doc for OCRnxPairedRegister.
       */
      protected class OCRnxTempHighRegister extends RWRegister {
          RWRegister register;
  
          OCRnxTempHighRegister(RWRegister register) {
              this.register = register;
          }
  
          public void write(byte val) {
              highTempReg.write(val);
          }
  
          public byte read() {
              return register.read();
          }
      }
  
      /**
       * Overloads the write behavior of this class of register in order to implement compare match
       * blocking for one timer period.
       */
      protected class TCNTnRegister extends RWRegister {
          /* expr of the blockCompareMatch corresponding to
           * this register in the array of boolean flags.  */
          public void write(byte val) {
              value = val;
              blockCompareMatch = true;
          }
  
      }
  
      private void resetPeriod(int nPeriod) {
          if (nPeriod == 0) {
              // disable the timer.
              //if (devicePrinter != null) devicePrinter.println("Timer" + n + " disabled");
              if (ticker != null) timerClock.removeEvent(ticker);
          } else {
              // enable the timer.
              //if (devicePrinter != null)
              //    devicePrinter.println("Timer" + n + " enabled: period = " + nPeriod + " mode = " + WGMn.value);
              if (ticker != null) timerClock.removeEvent(ticker);
              ticker = tickers[WGMn.value];
              period = nPeriod;
              timerClock.insertEvent(ticker, period);
          }
      }
  
      public void resetTicker(Simulator.Event e) {
          if (ticker != null) simulator.removeEvent(ticker);
          ticker = e;
          simulator.insertEvent(e, period);
      }
  
      /**
       * In PWN modes, writes to the OCRnx registers are buffered. Specifically, the actual write is
       * delayed until a certain event (the counter reaching either TOP or BOTTOM) specified by the
       * particular PWN mode. BufferedRegister implements this by writing to a buffer register on a
       * write and reading from the buffered register in a read. When the buffered register is to be
       * updated, the flush() method should be called.
       */
      protected class BufferedRegister extends RWRegister {
          final RWRegister register;
  
          protected BufferedRegister() {
              this.register = new RWRegister();
          }
  
          public void write(byte val) {
              super.write(val);
              int mode = WGMn.value;
              if (mode == MODE_NORMAL || mode == MODE_CTC_OCRnA
                      || mode == MODE_CTC_ICRn) {
                  flush();
              }
          }
  
          public byte readBuffer() {
              return super.read();
          }
  
          public byte read() {
              return register.read();
          }
  
          protected void flush() {
              register.write(value);
          }
      }
  
      protected class Mode_Reserved implements Simulator.Event {
          public void fire() {
              // do nothing in the reserved mode.
          }
      }
  
      protected class Mode_Normal implements Simulator.Event {
          public void fire() {
              int ncount = read16(TCNTnH_reg, TCNTnL_reg);
              tickerStart(ncount);
              if (ncount >= MAX) {
                  overflow();
                  ncount = BOTTOM;
              }
              else {
                  ncount++;
              }
              tickerFinish(this, ncount);
          }
      }
  
      protected class Mode_CTC implements Simulator.Event {
          protected final RWRegister compareRegHigh;
          protected final RWRegister compareRegLow;
  
          public Mode_CTC(RWRegister compareRegH, RWRegister compareRegL) {
              compareRegHigh = compareRegH;
              compareRegLow = compareRegL;
          }
  
          public void fire() {
              // TODO: set OCFnA/ICFn flag when OCRnA/ICRn define TOP and TOP is reached
              int ncount = read16(TCNTnH_reg, TCNTnL_reg);
              tickerStart(ncount);
              if (ncount >= MAX) {
                  // OCRn/ICRn == MAX, then overflow is handled as in normal mode
                  overflow();
                  ncount = BOTTOM;
              }
              else if (compareRegHigh != null && ncount == read16(compareRegHigh, compareRegLow)) {
                  ncount = BOTTOM;
              }
              else {
                  ncount++;
              }
              tickerFinish(this, ncount);
          }
      }
  
      protected class Mode_FastPWM implements Simulator.Event {
          protected final int top;
          protected final RWRegister compareRegHigh;
          protected final RWRegister compareRegLow;
  
          protected Mode_FastPWM(int t, RWRegister compareRegH, RWRegister compareRegL) {
              top = t;
              compareRegHigh = compareRegH;
              compareRegLow = compareRegL;
          }
          public void fire() {
              // TODO: set OCFnA/ICFn flag when OCRnA/ICRn define TOP and TOP is reached
              int ncount = read16(TCNTnH_reg, TCNTnL_reg);
              tickerStart(ncount);
              int top = this.top;
              if (compareRegHigh != null) {
                  top = read16(compareRegHigh, compareRegLow);
              }
              if (ncount == top) {
                  ncount = BOTTOM;
                  overflow();
                  flushOCRnx();
              }
              else if (ncount >= MAX) {
                  overflow(); // ??? not clear according to the spec if there is an overflow here
                  ncount = BOTTOM;
              }
              else {
                  ncount++;
              }
              tickerFinish(this, ncount);
          }
      }
  
      protected class Mode_PWM_PNF implements Simulator.Event {
          protected byte increment = 1;
          protected final RWRegister compareRegHigh;
          protected final RWRegister compareRegLow;
  
          protected Mode_PWM_PNF(RWRegister compareRegH, RWRegister compareRegL) {
              compareRegHigh = compareRegH;
              compareRegLow = compareRegL;
          }
          public void fire() {
              // TODO: set OCFnA/ICFn flag when OCRnA/ICRn define TOP and TOP is reached
              int ncount = read16(TCNTnH_reg, TCNTnL_reg);
              tickerStart(ncount);
              
              int compare = MAX;
              if (compareRegHigh != null) {
                  compare = read16(compareRegHigh, compareRegLow);
              }
              
              if (ncount == compare) {
                  increment = -1;
              }
              else if (ncount <= BOTTOM) {
                  overflow();
                  flushOCRnx();
                  increment = 1;
                  ncount = BOTTOM;
              }
              else if (ncount >= MAX) {  // this is not defined in the spec
                  ncount = BOTTOM-1;
              }
              ncount += increment;
              tickerFinish(this, ncount);
          }
      }
  
      protected class Mode_PWMPhaseCorrect implements Simulator.Event {
          protected byte increment = 1;
          protected final int top;
          protected final RWRegister compareRegHigh;
          protected final RWRegister compareRegLow;
  
          protected Mode_PWMPhaseCorrect(int t, RWRegister compareRegH, RWRegister compareRegL) {
              top = t;
              compareRegHigh = compareRegH;
              compareRegLow = compareRegL;
          }
          public void fire() {
              int ncount = read16(TCNTnH_reg, TCNTnL_reg);
              tickerStart(ncount);
            
              int top = this.top;
              if (compareRegHigh != null) {
                  top = read16(compareRegHigh, compareRegLow);
              }
              
              if (ncount == top) {
                  increment = -1;
                  flushOCRnx();
              }
              else if (ncount <= BOTTOM) {
                  overflow();
                  increment = 1;
                  ncount = BOTTOM;
              }
              else if (ncount >= MAX) {  // this is not defined in the spec
                  ncount = BOTTOM-1;
              }
              ncount += increment;
              tickerFinish(this, ncount);
          }
      }
  
      private void tickerStart(int count) {
          // the compare match should be performed in any case.
          if (!blockCompareMatch) {
              for ( int cntr = 0; cntr < compareUnits.length; cntr++ )
                  compareUnits[cntr].compare(count);
          }
      }
      
      private void tickerFinish(Simulator.Event ticker, int ncount) {
          write16(ncount, TCNTnH_reg, TCNTnL_reg);
          // previous write sets the compare, so reset it now
          blockCompareMatch = false;
  
          if (period != 0) timerClock.insertEvent(ticker, period);
      }
  
      private void flushOCRnx() {
          for ( int cntr = 0; cntr < compareUnits.length; cntr++ )
              compareUnits[cntr].flush();
      }
  }
  
  