diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/peripherals/eeprom.spec.ts | 8 | ||||
| -rw-r--r-- | src/peripherals/timer.spec.ts | 253 | ||||
| -rw-r--r-- | src/utils/test-utils.ts | 2 |
3 files changed, 128 insertions, 135 deletions
diff --git a/src/peripherals/eeprom.spec.ts b/src/peripherals/eeprom.spec.ts index 75cd5ea..487ce41 100644 --- a/src/peripherals/eeprom.spec.ts +++ b/src/peripherals/eeprom.spec.ts @@ -63,7 +63,7 @@ describe('EEPROM', () => { it('should not erase the memory when writing if EEPM1 is high', () => { // We subtract 0x20 to translate from RAM address space to I/O register space - const { program } = asmProgram(` + const { program, instructionCount } = asmProgram(` ; register addresses _REPLACE TWSR, ${EECR - 0x20} _REPLACE EEARL, ${EEARL - 0x20} @@ -85,7 +85,7 @@ describe('EEPROM', () => { eepromBackend.memory[9] = 0x0f; // high four bits are cleared const runner = new TestProgramRunner(cpu, eeprom); - runner.runInstructions(program.length); + runner.runInstructions(instructionCount); // EEPROM was 0x0f, and our program wrote 0x55. // Since write (without erase) only clears bits, we expect 0x05 now. @@ -197,7 +197,7 @@ describe('EEPROM', () => { describe('EEPROM erase', () => { it('should only erase the memory when EEPM0 is high', () => { // We subtract 0x20 to translate from RAM address space to I/O register space - const { program } = asmProgram(` + const { program, instructionCount } = asmProgram(` ; register addresses _REPLACE TWSR, ${EECR - 0x20} _REPLACE EEARL, ${EEARL - 0x20} @@ -219,7 +219,7 @@ describe('EEPROM', () => { eepromBackend.memory[9] = 0x22; const runner = new TestProgramRunner(cpu, eeprom); - runner.runInstructions(program.length); + runner.runInstructions(instructionCount); expect(eepromBackend.memory[9]).toEqual(0xff); }); diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts index 8e1949d..3a7d1d9 100644 --- a/src/peripherals/timer.spec.ts +++ b/src/peripherals/timer.spec.ts @@ -1,8 +1,7 @@ import { CPU } from '../cpu/cpu'; -import { avrInstruction } from '../cpu/instruction'; -import { assemble } from '../utils/assembler'; -import { AVRTimer, timer0Config, timer1Config, timer2Config } from './timer'; +import { asmProgram, TestProgramRunner } from '../utils/test-utils'; import { PinOverrideMode } from './gpio'; +import { AVRTimer, timer0Config, timer1Config, timer2Config } from './timer'; // CPU registers const R1 = 1; @@ -56,22 +55,12 @@ const CS10 = 1; const CS21 = 2; const CS22 = 4; -describe('timer', () => { - let cpu: CPU; - - beforeEach(() => { - cpu = new CPU(new Uint16Array(0x1000)); - }); - - function loadProgram(...instructions: string[]) { - const { bytes, errors } = assemble(instructions.join('\n')); - if (errors.length) { - throw new Error('Assembly failed: ' + errors); - } - cpu.progBytes.set(bytes, 0); - } +// opcodes +const nopOpCode = '0000'; +describe('timer', () => { it('should update timer every tick when prescaler is 1', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.data[TCCR0B] = CS00; // Set prescaler to 1 cpu.cycles = 1; @@ -81,6 +70,7 @@ describe('timer', () => { }); it('should update timer every 64 ticks when prescaler is 3', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.data[TCCR0B] = CS01 | CS00; // Set prescaler to 64 cpu.cycles = 64; @@ -90,6 +80,7 @@ describe('timer', () => { }); it('should not update timer if it has been disabled', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.data[TCCR0B] = 0; // No prescaler (timer disabled) cpu.cycles = 100000; @@ -99,6 +90,7 @@ describe('timer', () => { }); it('should set TOV if timer overflows', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0xff); timer.tick(); @@ -111,6 +103,7 @@ describe('timer', () => { }); it('should set TOV if timer overflows in FAST PWM mode', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0xff); timer.tick(); @@ -125,6 +118,7 @@ describe('timer', () => { }); it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0xff); timer.tick(); @@ -141,6 +135,7 @@ describe('timer', () => { }); it('should not generate an overflow interrupt when global interrupts disabled', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0xff); timer.tick(); @@ -155,6 +150,7 @@ describe('timer', () => { }); it('should not generate an overflow interrupt when TOIE0 is clear', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0xff); timer.tick(); @@ -169,6 +165,7 @@ describe('timer', () => { }); it('should set OCF0A flag when timer equals OCRA', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x10); timer.tick(); @@ -183,6 +180,7 @@ describe('timer', () => { }); it('should clear the timer in CTC mode if it equals to OCRA', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x10); timer.tick(); @@ -198,6 +196,7 @@ describe('timer', () => { }); it('should set OCF0B flag when timer equals OCRB', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x10); timer.tick(); @@ -212,6 +211,7 @@ describe('timer', () => { }); it('should generate Timer Compare A interrupt when TCNT0 == TCNTA', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x20); timer.tick(); @@ -229,6 +229,7 @@ describe('timer', () => { }); it('should not generate Timer Compare A interrupt when OCIEA is disabled', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x20); timer.tick(); @@ -245,6 +246,7 @@ describe('timer', () => { }); it('should generate Timer Compare B interrupt when TCNT0 == TCNTB', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x20); timer.tick(); @@ -264,24 +266,23 @@ describe('timer', () => { it('should not increment TCNT on the same cycle of TCNT write (issue #36)', () => { // At the end of this short program, R17 should contain 0x31. Verified against // a physical ATmega328p. - const program = [ - 'LDI r16, 0x1', // TCCR0B = 1 << CS00; - 'OUT 0x25, r16', - 'LDI r16, 0x30', // TCNT <- 0x30 - 'OUT 0x26, r16', - 'NOP', - 'IN r17, 0x26', // r17 <- TCNT - ]; - loadProgram(...program); + const { program, instructionCount } = asmProgram(` + LDI r16, 0x1 ; TCCR0B = 1 << CS00; + OUT 0x25, r16 + LDI r16, 0x30 ; TCNT <- 0x30 + OUT 0x26, r16 + NOP + IN r17, 0x26 ; r17 <- TCNT + `); + const cpu = new CPU(program); const timer = new AVRTimer(cpu, timer0Config); - for (let i = 0; i < program.length; i++) { - avrInstruction(cpu); - timer.tick(); - } + const runner = new TestProgramRunner(cpu, timer); + runner.runInstructions(instructionCount); expect(cpu.data[R17]).toEqual(0x31); }); it('timer2 should count every 256 ticks when prescaler is 6 (issue #5)', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer2Config); cpu.data[TCCR2B] = CS22 | CS21; // Set prescaler to 256 @@ -295,120 +296,107 @@ describe('timer', () => { }); it('should update TCNT as it is being read by a 2-cycle instruction (issue #40)', () => { - const program = [ - 'LDI r16, 0x1', // TCCR0B = 1 << CS00; - 'OUT 0x25, r16', - 'LDI r16, 0x0', // TCNT0 <- 0x30 - 'OUT 0x26, r16', - 'NOP', - `LDS r1, 0x46`, // r1 <- TCNT0 (2 cycles) - ]; - loadProgram(...program); + const { program, instructionCount } = asmProgram(` + LDI r16, 0x1 ; TCCR0B = 1 << CS00 + OUT 0x25, r16 + LDI r16, 0x0 ; TCNT0 <- 0x30 + OUT 0x26, r16 + NOP + LDS r1, 0x46 ; r1 <- TCNT0 (2 cycles) + `); + const cpu = new CPU(program); const timer = new AVRTimer(cpu, timer0Config); - for (let i = 0; i < program.length; i++) { - avrInstruction(cpu); - timer.tick(); - } + const runner = new TestProgramRunner(cpu, timer); + runner.runInstructions(instructionCount); expect(cpu.data[R1]).toEqual(2); }); describe('Phase-correct PWM mode', () => { it('should count up to TOP, down to 0, and then set TOV flag', () => { - const program = [ - // Set waveform generation mode (WGM) to PWM, Phase Correct, top OCR0A - 'LDI r16, 0x1', // TCCR0A = 1 << WGM00; - 'OUT 0x24, r16', - 'LDI r16, 0x9', // TCCR0B = (1 << WGM02) | (1 << CS00); - 'OUT 0x25, r16', - 'LDI r16, 0x3', // OCR0A = 0x3; - 'OUT 0x27, r16', - 'LDI r16, 0x2', // TCNT0 = 0x2; - 'OUT 0x26, r16', - ]; - const nops = [ - 'NOP', // TCNT0 will be 3 - 'NOP', // TCNT0 will be 2 - 'NOP', // TCNT0 will be 1 - 'NOP', // TCNT0 will be 0 - 'NOP', // TCNT0 will be 1 (end of test) - ]; - loadProgram(...program, ...nops); + const { program, lines, instructionCount } = asmProgram(` + ; Set waveform generation mode (WGM) to PWM, Phase Correct, top OCR0A + LDI r16, 0x1 ; TCCR0A = 1 << WGM00; + OUT 0x24, r16 + LDI r16, 0x9 ; TCCR0B = (1 << WGM02) | (1 << CS00); + OUT 0x25, r16 + LDI r16, 0x3 ; OCR0A = 0x3; + OUT 0x27, r16 + LDI r16, 0x2 ; TCNT0 = 0x2; + OUT 0x26, r16 + + NOP ; TCNT0 will be 3 + NOP ; TCNT0 will be 2 + NOP ; TCNT0 will be 1 + NOP ; TCNT0 will be 0 + NOP ; TCNT0 will be 1 (end of test) + `); + const nopCount = lines.filter((line) => line.bytes == nopOpCode).length; + const cpu = new CPU(program); const timer = new AVRTimer(cpu, timer0Config); - - for (let i = 0; i < program.length; i++) { - avrInstruction(cpu); - timer.tick(); - } + const runner = new TestProgramRunner(cpu, timer); + runner.runInstructions(instructionCount - nopCount); expect(cpu.readData(TCNT0)).toEqual(2); - avrInstruction(cpu); - timer.tick(); + runner.runInstructions(1); expect(cpu.readData(TCNT0)).toEqual(3); - avrInstruction(cpu); - timer.tick(); + runner.runInstructions(1); expect(cpu.readData(TCNT0)).toEqual(2); - avrInstruction(cpu); - timer.tick(); + runner.runInstructions(1); expect(cpu.readData(TCNT0)).toEqual(1); expect(cpu.data[TIFR0] & TOV0).toEqual(0); - avrInstruction(cpu); - timer.tick(); + runner.runInstructions(1); expect(cpu.readData(TCNT0)).toEqual(0); expect(cpu.data[TIFR0] & TOV0).toEqual(TOV0); - avrInstruction(cpu); - timer.tick(); + runner.runInstructions(1); expect(cpu.readData(TCNT0)).toEqual(1); }); it('should clear OC0A when TCNT0=OCR0A and counting up', () => { - const program = [ - // Set waveform generation mode (WGM) to PWM, Phase Correct - 'LDI r16, 0x81', // TCCR0A = (1 << COM0A1) || (1 << WGM01); - 'OUT 0x24, r16', - 'LDI r16, 0x1', // TCCR0B = (1 << CS00); - 'OUT 0x25, r16', - 'LDI r16, 0xfe', // OCR0A = 0xfe; - 'OUT 0x27, r16', - 'LDI r16, 0xfd', // TCNT0 = 0xfd; - 'OUT 0x26, r16', - ]; - const nops = [ - 'NOP', // TCNT0 will be 0xfe - 'NOP', // TCNT0 will be 0xff - 'NOP', // TCNT0 will be 0xfe again (end of test) - ]; - loadProgram(...program, ...nops); + const { program, lines, instructionCount } = asmProgram(` + ; Set waveform generation mode (WGM) to PWM, Phase Correct + LDI r16, 0x81 ; TCCR0A = (1 << COM0A1) || (1 << WGM01); + OUT 0x24, r16 + LDI r16, 0x1 ; TCCR0B = (1 << CS00); + OUT 0x25, r16 + LDI r16, 0xfe ; OCR0A = 0xfe; + OUT 0x27, r16 + LDI r16, 0xfd ; TCNT0 = 0xfd; + OUT 0x26, r16 + + NOP ; TCNT0 will be 0xfe + NOP ; TCNT0 will be 0xff + NOP ; TCNT0 will be 0xfe again (end of test) + `); + + const cpu = new CPU(program); const timer = new AVRTimer(cpu, timer0Config); // Listen to Port D's internal callback const gpioCallback = jest.fn(); cpu.gpioTimerHooks[PORTD] = gpioCallback; - for (let i = 0; i < program.length; i++) { - avrInstruction(cpu); - timer.tick(); - } + const nopCount = lines.filter((line) => line.bytes == nopOpCode).length; + const runner = new TestProgramRunner(cpu, timer); + runner.runInstructions(instructionCount - nopCount); + expect(cpu.readData(TCNT0)).toEqual(0xfd); expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b); gpioCallback.mockClear(); - avrInstruction(cpu); - timer.tick(); + runner.runInstructions(1); expect(cpu.readData(TCNT0)).toEqual(0xfe); expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Clear, 0x2b); gpioCallback.mockClear(); - avrInstruction(cpu); - timer.tick(); + runner.runInstructions(1); expect(cpu.readData(TCNT0)).toEqual(0xff); expect(gpioCallback).not.toHaveBeenCalled(); - avrInstruction(cpu); - timer.tick(); + runner.runInstructions(1); expect(cpu.readData(TCNT0)).toEqual(0xfe); expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b); }); @@ -416,6 +404,7 @@ describe('timer', () => { describe('16 bit timers', () => { it('should increment 16-bit TCNT by 1', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1H, 0x22); // TCNT1 <- 0x2233 cpu.writeData(TCNT1, 0x33); // ... @@ -432,6 +421,7 @@ describe('timer', () => { }); it('should set OCF0A flag when timer equals OCRA (16 bit mode)', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1H, 0x10); // TCNT1 <- 0x10ee cpu.writeData(TCNT1, 0xee); // ... @@ -448,6 +438,7 @@ describe('timer', () => { }); it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1H, 0x3); // TCNT1 <- 0x3ff cpu.writeData(TCNT1, 0xff); // ... @@ -466,6 +457,7 @@ describe('timer', () => { }); it('should reset the timer once it reaches ICR value in mode 12', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1H, 0x50); // TCNT1 <- 0x500f cpu.writeData(TCNT1, 0x0f); // ... @@ -482,6 +474,7 @@ describe('timer', () => { }); it('should not update the high byte of TCNT if written after the low byte (issue #37)', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1, 0x22); cpu.writeData(TCNT1H, 0x55); @@ -492,6 +485,7 @@ describe('timer', () => { }); it('reading from TCNT1H before TCNT1L should return old value (issue #37)', () => { + const cpu = new CPU(new Uint16Array(0x1000)); const timer = new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1H, 0xff); cpu.writeData(TCNT1, 0xff); @@ -507,42 +501,41 @@ describe('timer', () => { }); it('should toggle OC1B on Compare Match', () => { - const program = [ - // Set waveform generation mode (WGM) to Normal, top 0xFFFF - 'LDI r16, 0x10', // TCCR1A = (1 << COM1B0); - 'STS 0x80, r16', - 'LDI r16, 0x1', // TCCR1B = (1 << CS00); - 'STS 0x81, r16', - 'LDI r16, 0x0', // OCR1BH = 0x0; - 'STS 0x8B, r16', - 'LDI r16, 0x4a', // OCR1BL = 0x4a; - 'STS 0x8A, r16', - 'LDI r16, 0x0', // TCNT1H = 0x0; - 'STS 0x85, r16', - 'LDI r16, 0x49', // TCNT1L = 0x49; - 'STS 0x84, r16', - ]; - const nops = [ - 'NOP', // TCNT1 will be 0x49 - 'NOP', // TCNT1 will be 0x4a - ]; - loadProgram(...program, ...nops); + const { program, lines, instructionCount } = asmProgram(` + ; Set waveform generation mode (WGM) to Normal, top 0xFFFF + LDI r16, 0x10 ; TCCR1A = (1 << COM1B0); + STS 0x80, r16 + LDI r16, 0x1 ; TCCR1B = (1 << CS00); + STS 0x81, r16 + LDI r16, 0x0 ; OCR1BH = 0x0; + STS 0x8B, r16 + LDI r16, 0x4a ; OCR1BL = 0x4a; + STS 0x8A, r16 + LDI r16, 0x0 ; TCNT1H = 0x0; + STS 0x85, r16 + LDI r16, 0x49 ; TCNT1L = 0x49; + STS 0x84, r16 + + NOP ; TCNT1 will be 0x49 + NOP ; TCNT1 will be 0x4a + `); + + const cpu = new CPU(program); const timer = new AVRTimer(cpu, timer1Config); // Listen to Port B's internal callback const gpioCallback = jest.fn(); cpu.gpioTimerHooks[PORTB] = gpioCallback; - for (let i = 0; i < program.length; i++) { - avrInstruction(cpu); - timer.tick(); - } + const nopCount = lines.filter((line) => line.bytes == nopOpCode).length; + const runner = new TestProgramRunner(cpu, timer); + runner.runInstructions(instructionCount - nopCount); + expect(cpu.readData(TCNT1)).toEqual(0x49); expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Enable, 0x25); gpioCallback.mockClear(); - avrInstruction(cpu); - timer.tick(); + runner.runInstructions(1); expect(cpu.readData(TCNT1)).toEqual(0x4a); expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Toggle, 0x25); }); diff --git a/src/utils/test-utils.ts b/src/utils/test-utils.ts index dfb6b32..ca483f3 100644 --- a/src/utils/test-utils.ts +++ b/src/utils/test-utils.ts @@ -7,7 +7,7 @@ export function asmProgram(source: string) { if (errors.length) { throw new Error('Assembly failed: ' + errors); } - return { program: new Uint16Array(bytes.buffer), lines }; + return { program: new Uint16Array(bytes.buffer), lines, instructionCount: lines.length }; } export class TestProgramRunner { |
