@@ -246,6 +246,7 @@ describe('timer', () => {
246246 cpu . tick ( ) ;
247247 cpu . cycles = 2 ;
248248 cpu . tick ( ) ;
249+ expect ( cpu . readData ( TCNT0 ) ) . toEqual ( 0 ) ;
249250 expect ( cpu . data [ TIFR0 ] & ( OCF0A | OCF0B ) ) . toEqual ( OCF0A | OCF0B ) ;
250251 expect ( cpu . pc ) . toEqual ( 0 ) ;
251252 expect ( cpu . cycles ) . toEqual ( 2 ) ;
@@ -417,7 +418,7 @@ describe('timer', () => {
417418 const { program, instructionCount } = asmProgram ( `
418419 LDI r16, 0x1 ; TCCR0B = 1 << CS00;
419420 OUT 0x25, r16
420- LDI r16, 0x30 ; TCNT <- 0x30
421+ LDI r16, 0x30 ; TCNT0 <- 0x30
421422 OUT 0x26, r16
422423 NOP
423424 IN r17, 0x26 ; r17 <- TCNT
@@ -495,6 +496,152 @@ describe('timer', () => {
495496 expect ( cpu . readData ( R17 ) ) . toEqual ( 2 ) ;
496497 } ) ;
497498
499+ describe ( 'Fast PWM mode' , ( ) => {
500+ it ( 'should set OC0A on Compare Match, clear on Bottom (issue #78)' , ( ) => {
501+ const { program, labels } = asmProgram ( `
502+ LDI r16, 0xfc ; TCNT0 = 0xfc;
503+ OUT 0x26, r16
504+ LDI r16, 0xfe ; OCR0A = 0xfe;
505+ OUT 0x27, r16
506+ ; WGM: Fast PWM, enable OC0A mode 3 (set on Compare Match, clear on Bottom)
507+ LDI r16, 0xc3 ; TCCR0A = (1 << COM0A1) | (1 << COM0A0) | (1 << WGM01) | (1 << WGM00);
508+ OUT 0x24, r16
509+ LDI r16, 0x1 ; TCCR0B = 1 << CS00;
510+ OUT 0x25, r16
511+
512+ NOP ; TCNT is now 0xfd
513+ beforeMatch:
514+ NOP ; TCNT is now 0xfe (Compare Match)
515+ afterMatch:
516+ NOP ; TCNT is now 0xff
517+ beforeBottom:
518+ NOP ; TCNT is now 0x00 (BOTTOM)
519+ afterBottom:
520+ NOP
521+ ` ) ;
522+
523+ const cpu = new CPU ( program ) ;
524+ new AVRTimer ( cpu , timer0Config ) ;
525+
526+ // Listen to Port D's internal callback
527+ const gpioCallback = jest . fn ( ) ;
528+ cpu . gpioTimerHooks [ PORTD ] = gpioCallback ;
529+
530+ const runner = new TestProgramRunner ( cpu ) ;
531+
532+ runner . runToAddress ( labels . beforeMatch ) ;
533+ expect ( cpu . readData ( TCNT0 ) ) . toEqual ( 0xfd ) ;
534+ expect ( gpioCallback ) . toHaveBeenCalledTimes ( 1 ) ;
535+ expect ( gpioCallback ) . toHaveBeenCalledWith ( 6 , PinOverrideMode . Enable , 0x2b ) ; // OC0A: Enable
536+ gpioCallback . mockClear ( ) ;
537+
538+ runner . runToAddress ( labels . afterMatch ) ;
539+ expect ( cpu . readData ( TCNT0 ) ) . toEqual ( 0xfe ) ;
540+ expect ( gpioCallback ) . toHaveBeenCalledTimes ( 1 ) ;
541+ expect ( gpioCallback ) . toHaveBeenCalledWith ( 6 , PinOverrideMode . Set , 0x2b ) ; // OC0A: Set
542+ gpioCallback . mockClear ( ) ;
543+
544+ runner . runToAddress ( labels . beforeBottom ) ;
545+ expect ( cpu . readData ( TCNT0 ) ) . toEqual ( 0xff ) ;
546+ expect ( gpioCallback ) . toHaveBeenCalledTimes ( 0 ) ;
547+ gpioCallback . mockClear ( ) ;
548+
549+ runner . runToAddress ( labels . afterBottom ) ;
550+ expect ( cpu . readData ( TCNT0 ) ) . toEqual ( 0x0 ) ;
551+ expect ( gpioCallback ) . toHaveBeenCalledTimes ( 1 ) ;
552+ expect ( gpioCallback ) . toHaveBeenCalledWith ( 6 , PinOverrideMode . Clear , 0x2b ) ; // OC0A: Clear
553+ } ) ;
554+
555+ it ( 'should toggle OC0A on Compare Match when COM0An = 1 (issue #78)' , ( ) => {
556+ const { program, labels } = asmProgram ( `
557+ LDI r16, 0xfc ; TCNT0 = 0xfc;
558+ OUT 0x26, r16
559+ LDI r16, 0xfe ; OCR0A = 0xfe;
560+ OUT 0x27, r16
561+ ; WGM: Fast PWM, enable OC0A mode 1 (Toggle)
562+ LDI r16, 0x43 ; TCCR0A = (1 << COM0A0) | (1 << WGM01) | (1 << WGM00);
563+ OUT 0x24, r16
564+ LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
565+ OUT 0x25, r16
566+
567+ NOP ; TCNT is now 0xfd
568+ beforeMatch:
569+ NOP ; TCNT is now 0xfe (Compare Match, TOP)
570+ afterMatch:
571+ NOP ; TCNT is now 0
572+ afterOverflow:
573+ NOP
574+ ` ) ;
575+
576+ const cpu = new CPU ( program ) ;
577+ new AVRTimer ( cpu , timer0Config ) ;
578+
579+ // Listen to Port D's internal callback
580+ const gpioCallback = jest . fn ( ) ;
581+ cpu . gpioTimerHooks [ PORTD ] = gpioCallback ;
582+
583+ const runner = new TestProgramRunner ( cpu ) ;
584+
585+ runner . runToAddress ( labels . beforeMatch ) ;
586+ expect ( cpu . readData ( TCNT0 ) ) . toEqual ( 0xfd ) ;
587+ expect ( gpioCallback ) . toHaveBeenCalledTimes ( 1 ) ;
588+ expect ( gpioCallback ) . toHaveBeenCalledWith ( 6 , PinOverrideMode . Enable , 0x2b ) ; // OC0A: Enable
589+ gpioCallback . mockClear ( ) ;
590+
591+ runner . runToAddress ( labels . afterMatch ) ;
592+ expect ( cpu . readData ( TCNT0 ) ) . toEqual ( 0xfe ) ;
593+ expect ( gpioCallback ) . toHaveBeenCalledTimes ( 1 ) ;
594+ expect ( gpioCallback ) . toHaveBeenCalledWith ( 6 , PinOverrideMode . Toggle , 0x2b ) ; // OC0A: Toggle
595+ gpioCallback . mockClear ( ) ;
596+
597+ runner . runToAddress ( labels . afterOverflow ) ;
598+ expect ( cpu . readData ( TCNT0 ) ) . toEqual ( 0 ) ;
599+ expect ( gpioCallback ) . toHaveBeenCalledTimes ( 0 ) ;
600+ } ) ;
601+
602+ it ( 'should leave OC0A disconnected when COM0An = 1 and WGM02 = 0 (issue #78)' , ( ) => {
603+ const { program, labels } = asmProgram ( `
604+ LDI r16, 0xfc ; TCNT0 = 0xfc;
605+ OUT 0x26, r16
606+ LDI r16, 0xfe ; OCR0A = 0xfe;
607+ OUT 0x27, r16
608+ ; WGM: Fast PWM mode 7, enable OC0A mode 1 (Toggle)
609+ LDI r16, 0x43 ; TCCR0A = (1 << COM0A0) | (1 << WGM01) | (1 << WGM00);
610+ OUT 0x24, r16
611+ LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
612+ OUT 0x25, r16
613+
614+ beforeClearWGM02:
615+ LDI r16, 0x01 ; TCCR0B = (1 << CS00);
616+ OUT 0x25, r16
617+
618+ afterClearWGM02:
619+ NOP
620+ ` ) ;
621+
622+ const cpu = new CPU ( program ) ;
623+ new AVRTimer ( cpu , timer0Config ) ;
624+
625+ // Listen to Port D's internal callback
626+ const gpioCallback = jest . fn ( ) ;
627+ cpu . gpioTimerHooks [ PORTD ] = gpioCallback ;
628+
629+ const runner = new TestProgramRunner ( cpu ) ;
630+
631+ // First, run with the bit set and assert that the Pin Override was enabled (OC0A connected)
632+ runner . runToAddress ( labels . beforeClearWGM02 ) ;
633+ expect ( gpioCallback ) . toHaveBeenCalledTimes ( 1 ) ;
634+ expect ( gpioCallback ) . toHaveBeenCalledWith ( 6 , PinOverrideMode . Enable , 0x2b ) ;
635+ gpioCallback . mockClear ( ) ;
636+
637+ // Now clear WGM02 and observe that Pin Override was disabled (OC0A disconnected)
638+ runner . runToAddress ( labels . afterClearWGM02 ) ;
639+ expect ( gpioCallback ) . toHaveBeenCalledTimes ( 1 ) ;
640+ expect ( gpioCallback ) . toHaveBeenCalledWith ( 6 , PinOverrideMode . None , 0x2b ) ;
641+ gpioCallback . mockClear ( ) ;
642+ } ) ;
643+ } ) ;
644+
498645 describe ( 'Phase-correct PWM mode' , ( ) => {
499646 it ( 'should count up to TOP, down to 0, and then set TOV flag' , ( ) => {
500647 const { program, instructionCount } = asmProgram ( `
@@ -575,6 +722,71 @@ describe('timer', () => {
575722 expect ( gpioCallback ) . toHaveBeenCalledWith ( 6 , PinOverrideMode . Set , 0x2b ) ;
576723 } ) ;
577724
725+ it ( 'should toggle OC0A when TCNT0=OCR0A and COM0An=1 (issue #78)' , ( ) => {
726+ const { program, labels } = asmProgram ( `
727+ LDI r16, 0xfe ; OCR0A = 0xfe; // <- TOP value
728+ OUT 0x27, r16
729+ ; Set waveform generation mode (WGM) to PWM, Phase Correct (mode 5)
730+ LDI r16, 0x41 ; TCCR0A = (1 << COM0A0) | (1 << WGM00);
731+ OUT 0x24, r16
732+ LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
733+ OUT 0x25, r16
734+ LDI r16, 0xfd ; TCNT0 = 0xfd;
735+ OUT 0x26, r16
736+
737+ beforeMatch:
738+ NOP ; TCNT0 will be 0xfe
739+ afterMatch:
740+ NOP
741+ ` ) ;
742+
743+ const cpu = new CPU ( program ) ;
744+ new AVRTimer ( cpu , timer0Config ) ;
745+
746+ // Listen to Port D's internal callback
747+ const gpioCallback = jest . fn ( ) ;
748+ cpu . gpioTimerHooks [ PORTD ] = gpioCallback ;
749+
750+ const runner = new TestProgramRunner ( cpu ) ;
751+
752+ runner . runToAddress ( labels . beforeMatch ) ;
753+ expect ( cpu . readData ( TCNT0 ) ) . toEqual ( 0xfd ) ;
754+ expect ( gpioCallback ) . toHaveBeenCalledWith ( 6 , PinOverrideMode . Enable , 0x2b ) ;
755+ gpioCallback . mockClear ( ) ;
756+
757+ runner . runToAddress ( labels . afterMatch ) ;
758+ expect ( cpu . readData ( TCNT0 ) ) . toEqual ( 0xfe ) ;
759+ expect ( gpioCallback ) . toHaveBeenCalledWith ( 6 , PinOverrideMode . Toggle , 0x2b ) ;
760+ gpioCallback . mockClear ( ) ;
761+ } ) ;
762+
763+ it ( 'should leave OC0A disconnected TCNT0=OCR0A and COM0An=1 in WGM mode 1 (issue #78)' , ( ) => {
764+ const { program, instructionCount } = asmProgram ( `
765+ LDI r16, 0xfe ; OCR0A = 0xfe; // <- TOP value
766+ OUT 0x27, r16
767+ ; Set waveform generation mode (WGM) to PWM, Phase Correct (mode 1)
768+ LDI r16, 0x41 ; TCCR0A = (1 << COM0A0) | (1 << WGM00);
769+ OUT 0x24, r16
770+ LDI r16, 0x01 ; TCCR0B = (1 << CS00);
771+ OUT 0x25, r16
772+ LDI r16, 0xfd ; TCNT0 = 0xfd;
773+ OUT 0x26, r16
774+ ` ) ;
775+
776+ const cpu = new CPU ( program ) ;
777+ new AVRTimer ( cpu , timer0Config ) ;
778+
779+ // Listen to Port D's internal callback
780+ const gpioCallback = jest . fn ( ) ;
781+ cpu . gpioTimerHooks [ PORTD ] = gpioCallback ;
782+
783+ const runner = new TestProgramRunner ( cpu ) ;
784+ runner . runInstructions ( instructionCount ) ;
785+
786+ // Assert that the pin callback wasn't called (thus it's disconnected)
787+ expect ( gpioCallback ) . not . toHaveBeenCalled ( ) ;
788+ } ) ;
789+
578790 it ( 'should not miss Compare Match when executing multi-cycle instruction (issue #79)' , ( ) => {
579791 const { program, instructionCount } = asmProgram ( `
580792 LDI r16, 0x10 ; OCR0A = 0x10; // <- TOP value
@@ -793,7 +1005,7 @@ describe('timer', () => {
7931005 expect ( gpioCallback ) . toHaveBeenCalledWith ( 2 , PinOverrideMode . Toggle , 0x25 ) ;
7941006 } ) ;
7951007
796- it ( 'should only update OCR0A when TCNT0 =BOTTOM in PWM Phase/Frequency Correct mode (issue #76)' , ( ) => {
1008+ it ( 'should only update OCR1A when TCNT1 =BOTTOM in PWM Phase/Frequency Correct mode (issue #76)' , ( ) => {
7971009 const { program, instructionCount } = asmProgram ( `
7981010 LDI r16, 0x0 ; OCR1AH = 0x0;
7991011 STS 0x89, r16
0 commit comments