//..............................................................................
//
// SPI protocol decoder
//

import "Decoder.jnc"

//..............................................................................

class SpiDecoder: Decoder {
protected:
	enum Defaults {
		BigEndian = true,
		DataBits  = 8,
	}

	enum Code {
		Padding         = 0xff,
		BufferOverflow  = 0xaa,
		Start           = 0,
		MosiDataHi,     // 1
		MosiDataLo,     // 2
		MisoDataHi,     // 3
		MisoDataLo,     // 4
		Stop,           // 5
	}

	enum State {
		Idle,
		MosiDataHi,
		MisoDataHi,
		MosiDataLo,
	}

protected:
	State m_state;
	uint_t m_bitOffset;
	uint16_t m_mosi;
	uint16_t m_miso;

	uchar_t* m_miso0;
	uchar_t thin* m_misoEnd;
	uint_t* m_incompleteBitCount; // as to avoid "lifting" allocations

public:
	uint_t m_dataBits;
	bool m_isBigEndian;
	bool m_isFlipMosiMiso;

public:
	construct(log.Writer* logWriter);

	override reset();

	override decode(
		uint64_t timestamp,
		void const thin* p0,
		size_t size
	);
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

SpiDecoder.construct(log.Writer* logWriter) {
	basetype.construct(logWriter);
	m_incompleteBitCount = new uint_t;
	m_miso0 = m_buffer.m_p + m_buffer.m_size / 2;
	m_misoEnd = m_miso0 + m_buffer.m_size / 2;
	m_dataBits = Defaults.DataBits;
	m_isBigEndian = Defaults.BigEndian;
}

void SpiDecoder.reset() {
	m_state = State.Idle;
	m_bitOffset = 0;
	m_mosi = 0;
	m_miso = 0;
}

void SpiDecoder.decode(
	uint64_t timestamp,
	void const thin* p0,
	size_t size
) {
	uchar_t const thin* p = p0;
	uchar_t const thin* end = p + size;
	uchar_t thin* mosi = m_buffer.m_p;
	uchar_t thin* miso = m_miso0;

	for (; p < end; p++) {
		uchar_t c = *p;

		switch (c) {
		case Code.Padding:
			continue;

		case Code.BufferOverflow:
			m_logWriter.write(timestamp, I2cSpiTapLogRecordCode.FpgaBufferOverflow);
			continue;
		}

		uchar_t code = (c & 0xf0) >> 4;
		switch (code) {
		case Code.Start:
		case Code.Stop:
			size = mosi - (uchar_t*)m_buffer.m_p;
			if (size) {
				memmove(m_buffer.m_p + size, m_miso0, size);
				m_logWriter.write(timestamp, log.StdRecordCode.TxRx, m_buffer.m_p, 2 * size);
				mosi = m_buffer.m_p;
				miso = m_miso0;
			}

			uint_t incompleteBitCount = m_bitOffset + (c & 0x03);
			if (incompleteBitCount) {
				*m_incompleteBitCount = incompleteBitCount;
				m_logWriter.write(timestamp, I2cSpiTapLogRecordCode.IncompleteByte, m_incompleteBitCount, sizeof(uint_t));
			}

			m_logWriter.write(
				timestamp,
				code == Code.Start ?
					I2cSpiTapLogRecordCode.SpiStart :
					I2cSpiTapLogRecordCode.SpiStop,
				null,
				0
			);

			m_state = State.Idle;
			m_bitOffset = 0;
			m_mosi = 0;
			m_miso = 0;
			break;

		case Code.MosiDataHi:
			if (m_state >= State.MosiDataHi)
				writeLogUnexpectedCode(timestamp, code, m_state);

			m_mosi |= (c & 0x0f) << m_bitOffset;
			m_state = State.MosiDataHi;
			break;

		case Code.MisoDataHi:
			if (m_state != State.MosiDataHi)
				writeLogUnexpectedCode(timestamp, code, m_state);

			m_miso |= (c & 0x0f) << m_bitOffset;
			m_bitOffset += 4;
			m_state = State.MisoDataHi;
			break;

		case Code.MosiDataLo:
			if (m_state != State.MisoDataHi)
				writeLogUnexpectedCode(timestamp, code, m_state);

			m_mosi |= (c & 0x0f) << m_bitOffset;
			m_state = State.MosiDataLo;
			break;

		case Code.MisoDataLo:
			if (m_state != State.MosiDataLo)
				writeLogUnexpectedCode(timestamp, code, m_state);

			m_miso |= (c & 0x0f) << m_bitOffset;
			m_bitOffset += 4;
			m_state = State.Idle;
			break;
		}

		if (m_bitOffset < m_dataBits)
			continue;

		if (m_isFlipMosiMiso) {
			uint16_t t = m_mosi;
			m_mosi = m_miso;
			m_miso = t;
		}

		switch (m_dataBits) {
		case 4:
			*mosi++ = m_mosi;
			*miso++ = m_miso;
			break;

		case 8:
			if (m_isBigEndian) {
				m_mosi = swapNibble8(m_mosi);
				m_miso = swapNibble8(m_miso);
			}

			*mosi++ = m_mosi;
			*miso++ = m_miso;
			break;

		default: // 12, 16
			if (m_isBigEndian)
				if (m_dataBits == 12) {
					m_mosi = swapNibble12(m_mosi);
					m_miso = swapNibble12(m_miso);
				} else {
					m_mosi = swapNibble16(m_mosi);
					m_miso = swapNibble16(m_miso);
				}

			*(uint16_t bigendian thin*)mosi = m_mosi;
			*(uint16_t bigendian thin*)miso = m_miso;
			mosi += 2;
			miso += 2;
		}

		m_bitOffset = 0;
		m_mosi = 0;
		m_miso = 0;

		if (miso < m_misoEnd)
			continue;

		size = mosi - (uchar_t*)m_buffer.m_p;
		memmove(m_buffer.m_p + size, m_miso0, size);
		m_logWriter.write(timestamp, log.StdRecordCode.TxRx, m_buffer.m_p, 2 * size);
		mosi = m_buffer.m_p;
		miso = m_miso0;
	}

	size = mosi - (uchar_t*)m_buffer.m_p;
	if (size) {
		memmove(m_buffer.m_p + size, m_miso0, size);
		m_logWriter.write(timestamp, log.StdRecordCode.TxRx, m_buffer.m_p, 2 * size);
		mosi = m_buffer.m_p;
		miso = m_miso0;
	}
}

//..............................................................................

uint8_t swapNibble8(uint8_t x) {
	return (x << 4) | (x >> 4);
}

uint16_t swapNibble12(uint16_t x) {
	return ((x & 0x00f) << 8) | (x & 0x0f0) | ((x & 0xf00) >> 8);
}

uint16_t swapNibble16(uint16_t x) {
	return (x << 12) | ((x & 0x00f0) << 4) | (x >> 12) | ((x & 0x0f00) >> 4);
}

//..............................................................................
