diff options
Diffstat (limited to 'src/peripherals/spi.spec.ts')
| -rw-r--r-- | src/peripherals/spi.spec.ts | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/src/peripherals/spi.spec.ts b/src/peripherals/spi.spec.ts new file mode 100644 index 0000000..1bb099f --- /dev/null +++ b/src/peripherals/spi.spec.ts @@ -0,0 +1,222 @@ +import { CPU } from '../cpu/cpu'; +import { AVRSPI, spiConfig } from './spi'; +import { asmProgram, TestProgramRunner } from '../utils/test-utils'; + +const FREQ_16MHZ = 16e6; + +// CPU registers +const R17 = 17; +const SREG = 95; + +// SPI Registers +const SPCR = 0x4c; +const SPSR = 0x4d; +const SPDR = 0x4e; + +// Register bit names +const SPR0 = 1; +const SPR1 = 2; +const CPOL = 4; +const CPHA = 8; +const MSTR = 0x10; +const DORD = 0x20; +const SPE = 0x40; +const SPIE = 0x80; +const WCOL = 0x40; +const SPIF = 0x80; +const SPI2X = 1; + +describe('SPI', () => { + it('should correctly calculate the frequency based on SPCR/SPST values', () => { + const cpu = new CPU(new Uint16Array(1024)); + const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + + // Values in this test are based on Table 19-5 in the datasheet, page 177: + // http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf + + // Standard SPI speed: + cpu.writeData(SPSR, 0); + cpu.writeData(SPCR, 0); + expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 4); + cpu.writeData(SPCR, SPR0); + expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 16); + cpu.writeData(SPCR, SPR1); + expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 64); + cpu.writeData(SPCR, SPR1 | SPR0); + expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 128); + + // Double SPI speed: + cpu.writeData(SPSR, SPI2X); + cpu.writeData(SPCR, 0); + expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 2); + cpu.writeData(SPCR, SPR0); + expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 8); + cpu.writeData(SPCR, SPR1); + expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 32); + cpu.writeData(SPCR, SPR1 | SPR0); + expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 64); + }); + + it('should correctly report the data order (MSB/LSB first), based on SPCR value', () => { + const cpu = new CPU(new Uint16Array(1024)); + const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + + cpu.writeData(SPCR, 0); + expect(spi.dataOrder).toBe('msbFirst'); + + cpu.writeData(SPCR, DORD); + expect(spi.dataOrder).toBe('lsbFirst'); + }); + + it('should correctly report the SPI mode, based on SPCR value', () => { + const cpu = new CPU(new Uint16Array(1024)); + const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + + // Values in this test are based on Table 2 in the datasheet, page 174. + cpu.writeData(SPCR, 0); + expect(spi.spiMode).toBe(0); + + cpu.writeData(SPCR, CPHA); + expect(spi.spiMode).toBe(1); + + cpu.writeData(SPCR, CPOL); + expect(spi.spiMode).toBe(2); + + cpu.writeData(SPCR, CPOL | CPHA); + expect(spi.spiMode).toBe(3); + }); + + it('should indicate slave/master operation, based on SPCR value', () => { + const cpu = new CPU(new Uint16Array(1024)); + const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + + expect(spi.isMaster).toBe(false); + + cpu.writeData(SPCR, MSTR); + expect(spi.isMaster).toBe(true); + }); + + it('should call the `onTransfer` callback when initiating an SPI trasfer by writing to SPDR', () => { + const cpu = new CPU(new Uint16Array(1024)); + const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + spi.onTransfer = jest.fn(); + + cpu.writeData(SPCR, SPE | MSTR); + cpu.writeData(SPDR, 0x8f); + + expect(spi.onTransfer).toHaveBeenCalledWith(0x8f); + }); + + it('should ignore SPDR writes when the SPE bit in SPCR is clear', () => { + const cpu = new CPU(new Uint16Array(1024)); + const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + spi.onTransfer = jest.fn(); + + cpu.writeData(SPCR, MSTR); + cpu.writeData(SPDR, 0x8f); + + expect(spi.onTransfer).not.toHaveBeenCalled(); + }); + + it('should transmit a byte successfully (integration)', () => { + // Based on code example from section 19.2 of the datasheet, page 172 + const { program } = asmProgram(` + ; register addresses + _REPLACE SPCR, ${SPCR - 0x20} + _REPLACE SPDR, ${SPDR - 0x20} + _REPLACE SPSR, ${SPSR - 0x20} + _REPLACE DDR_SPI, 0x4 ; PORTB + + SPI_MasterInit: + ; Set MOSI and SCK output, all others input + LDI r17, 0x28 + OUT DDR_SPI, r17 + + ; Enable SPI, Master, set clock rate fck/16 + LDI r17, 0x51 ; (1<<SPE)|(1<<MSTR)|(1<<SPR0) + OUT SPCR, r17 + + SPI_MasterTransmit: + LDI r16, 0xb8 ; byte to transmit + OUT SPDR, r16 + + Wait_Transmit: + IN r16, SPSR + SBRS r16, 7 + RJMP Wait_Transmit + + ; Now read the result into r17 + IN r17, SPDR + BREAK + `); + + const cpu = new CPU(program); + const spi = new AVRSPI(cpu, spiConfig, 16e6); + + let byteReceivedFromAsmCode: number | null = null; + + spi.onTransfer = (value) => { + byteReceivedFromAsmCode = value; + return 0x5b; // we copy this byte to + }; + + const runner = new TestProgramRunner(cpu, spi); + runner.runToBreak(); + + // 16 cycles per clock * 8 bits = 128 + expect(cpu.cycles).toBeGreaterThanOrEqual(128); + + expect(byteReceivedFromAsmCode).toEqual(0xb8); + expect(cpu.data[R17]).toEqual(0x5b); + }); + + it('should set the WCOL bit in SPSR if writing to SPDR while SPI is already transmitting', () => { + const cpu = new CPU(new Uint16Array(1024)); + const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + + cpu.writeData(SPCR, SPE | MSTR); + cpu.writeData(SPDR, 0x50); + spi.tick(); + expect(cpu.readData(SPSR) & WCOL).toEqual(0); + + cpu.writeData(SPDR, 0x51); + expect(cpu.readData(SPSR) & WCOL).toEqual(WCOL); + }); + + it('should clear the SPIF bit and fire an interrupt when SPI transfer completes', () => { + const cpu = new CPU(new Uint16Array(1024)); + const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + + cpu.writeData(SPCR, SPE | SPIE | MSTR); + cpu.writeData(SPDR, 0x50); + cpu.data[SREG] = 0x80; // SREG: I------- + + // At this point, write shouldn't be complete yet + cpu.cycles += 10; + spi.tick(); + expect(cpu.pc).toEqual(0); + + // 100 cycles later, it should (8 bits * 8 cycles per bit = 64). + cpu.cycles += 100; + spi.tick(); + expect(cpu.data[SPSR] & SPIF).toEqual(0); + expect(cpu.pc).toEqual(0x22); // SPI Ready interrupt + }); + + it('should should only update SPDR when tranfer finishes (double buffering)', () => { + const cpu = new CPU(new Uint16Array(1024)); + const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + spi.onTransfer = jest.fn(() => 0x88); + + cpu.writeData(SPCR, SPE | MSTR); + cpu.writeData(SPDR, 0x8f); + + cpu.cycles = 10; + spi.tick(); + expect(cpu.readData(SPDR)).toEqual(0); + + cpu.cycles = 32; // 4 cycles per bit * 8 bits = 32 + spi.tick(); + expect(cpu.readData(SPDR)).toEqual(0x88); + }); +}); |
