aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUri Shaked2020-08-01 15:47:35 +0300
committerUri Shaked2020-08-01 15:47:35 +0300
commitb0bfe885f7f53d6ae0ff03977b4cd9f2b3eb856b (patch)
tree00c2dce9476c7c412b2d81445249d681ef368994
parentchore(deps): npm audit fix (diff)
downloadavr8js-b0bfe885f7f53d6ae0ff03977b4cd9f2b3eb856b.tar.gz
avr8js-b0bfe885f7f53d6ae0ff03977b4cd9f2b3eb856b.tar.bz2
avr8js-b0bfe885f7f53d6ae0ff03977b4cd9f2b3eb856b.zip
test(timer): use TestProgramRunner
-rw-r--r--src/peripherals/eeprom.spec.ts8
-rw-r--r--src/peripherals/timer.spec.ts253
-rw-r--r--src/utils/test-utils.ts2
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 {
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.