TimerOCD (Timer Output Compare Drivers) - Targeting the Spartan-6 XC6SLX9-2C (Numato Mimas Spartan-6 FPGA Development board), this project interfaces the FPGA to a microprocessor via SPI and provides an array of 512 high-speed (up to 1.5625MHz timer clock speed) 16-bit Timer Output Compares to drive appropriate output signals.
The original project was developed to work with a Dual-Resonance Solid-State Tesla Coil (DRSSTC) as a MIDI Interrupter. It provides generation of up to 256 simultaneous notes per left/right channel (512 total) and makes use of the Block RAM and DSP slices for doing Note Frequency/Power lookup and interpolation.
As configured, it is designed to run with a 100 MHz input clock and by making use of the Dual-Port memory structure and interlacing memory read/write operations and updating 4 timers at once (odd/even pairs and left/right pairs), it can achieve a timer frequency scaling of 1:64 or a timer clock of 1.5625 MHz -- updating all 512 timers within a single timer clock tick (800 million memory reads and 800 million memory writes per second). Impressive considering the "throughput" of this FPGA is around 71MHz.
Originally, I implemented the timer compares on little ATmega328 micros, which could only do 6 per processor with a 250kHz timer clock. After progressing in development, my project ultimately needed an absolute minimum of 32 to 64 per left/right channel. While I could parallel those processors (which worked fairly well when I prototyped it with four of them), it was more expensive and higher power consumption than a single chip solution.
I couldn't find any off-the-shelf timer compare chips that were high frequency. There are chips that can do around 64 or 128 PWM and/or output compares, but they were limited to around 2kHz and are designed for LED drivers. I needed a minimum of 250kHz (or the more appropriate 1.5625MHz clock) to get full utilization of the 16-bit timers for the music note frequency range of the MIDI band.
So I developed this FPGA code (in VHDL) to handle all of the timer compares and drive dual fiber optic transmitter outputs for my Tesla Coils -- two identical half-bridge zero-current-switching dual-resonance solid-state coils.
I'm sharing this project here because it's also useful for all sorts of Timer/PWM applications, not just a MIDI interrupter. The outputs can be easily remapped to utilize any of the output pins on the FPGA, rather than the dual 256-input 'OR' gate that's used for the interrupter. In addition to the 8 LEDs and 4 switches on the aforementioned FPGA Development board (which are controllable via SPI in this code), this design only needs the 4 SPI pins and 1 Reset pin (which is technically optional since it can be reset via SPI command). The rest of the pins are free to be outputs, of which I'm presently using 2 (left and right outputs).
The SPI commands to the FPGA can be addressed either in Note/Power (Key/Velocity) mode which utilizes the lookup tables for interpolation and Timer On/Off value calculations or it can be addressed directly with the Timer On/Off values to use.
It's been prototyped and tested and is working flawlessly, at least with a SPI clock of up to 5.25 MHz, which is what I'm using on the STM32F405 processor that I have driving this FPGA.
While I haven't done a full/extensive set of tests of every mode and I/O combination (for example, it should work with all SPI Clock Polarity/Phase configurations but has only been tested with CPOL=0, CPHA=0), the design is considered complete and stable and will be fully integrated with my MIDI interrupter once I've finished the code for the STM32F405 and do a PCB board turn to bring it all together into a final device.
The code was developed on a Linux workstation using the Qt Creator IDE. A .config file for Qt Creator is provided. I have also included the Makefile to compile and simulate in ghdl/gtkwave. The overall project was synthesized and IP Core Blocks generated by the Xilinx ISE Webpack 14.7. I have also included the lookup tables for music note generation for the DRSSTC MIDI Interrupter as .coe files, but these can be easily swapped out for any lookup table desired and/or the timers can be run in direct On-Time/Off-Time mode.
Please let me know if I overlooked any necessary files by mail to dwhisnant at dewtronics dot com.
The SPI Interface functions as a slave device with the following stream:
Master must send MSbit First:
Input (9 bytes per data message transfer or 1 byte per command message transfer):
* 8-bit Command/Address Byte:
C7..C0 :
C7..C0 : Timer Address for Read and Write operations
C7 C6 C5 C4 C3 C2 C1 C0
-- -- -- -- -- -- -- --
| | | | | | | |
| | +--+--+--+--+--+----- Timer Address 0-63 0x00-0x3F (000000-111111) (For Data Mode)
| | Commands (for Command Mode):
| | 000000 = Reset (performs power-on reset equivalent) on Write (Read has no effect)
| | 000001 = RESERVED
| | 000010 = Switch to Note/Power/PitchBend Run mode (initial default) on Write (Read has no effect)
| | 000011 = Switch to Timer On/Off Compare Value Run mode on Write (Read has no effect)
| | 000100 = Bank Select 0 (Notes 0-63) for future transfers on Write (Read has no effect)
| | 000101 = Bank Select 1 (Notes 64-127) for future transfers on Write (Read has no effect)
| | 000110 = Bank Select 2 (Notes 128-191) for future transfers on Write (Read has no effect)
| | 000111 = Bank Select 3 (Notes 192-255) for future transfers on Write (Read has no effect)
| | 001xxx = TBD/RESERVED
| | Note: on 00xxxx Commands (above) and with Timer Address in the Data Mode the Concurrent
| | Read Nybble with the command send will be as follows:
| | 0 m b b
| | - - - -
| | | | | |
| | | | +-+-- Currently Selected Bank
| | | | 00 = Bank 0
| | | | 01 = Bank 1
| | | | 10 = Bank 2
| | | | 11 = Bank 3
| | | |
| | | +------ RunMode : 0 = Note/Power/PitchBend, 1 = Timer On/Off Compare Values
| | |
| | +-------- Reserved for 001xxx command, TBD, will be sent as 0
| |
| | 01xxxx = Read Push Button Switch 0-3 Status (Read Nybble is concurrent with sending command byte, Write has no effect)
| | 10xxxx = Read/Set LEDs 0-3 (Read Nybble is concurrent with sending command byte)
| | 11xxxx = Read/Set LEDs 4-7 (Read Nybble is concurrent with sending command byte)
| |
| +----------------------- Command/Data Select (0=Data, 1=Command)
|
+-------------------------- R/W Mode Select
0 = Write new value to TimerOCD and concurrently Read present value from TimerOCD
1 = Read present value from TimerOCD (Data received during read operation is ignored)
Note/Power/PitchBend Run Mode
-----------------------------
* 16-Bit : 7-Bit Note/Key and 7-Bit Power/Velocity for Left Channel: (Data Mode Only)
0,N6..N0,0,P6..P0 :
0 N6 N5 N4 N3 N2 N1 N0
-- -- -- -- -- -- -- --
| | | | | | | |
| +--+--+--+--+--+--+-- Note/Key 0-127 0x00-0x7F (0000000-1111111) - Left Channel
|
+----------------------- Unused
0 P6 P5 P4 P3 P2 P1 P0
-- -- -- -- -- -- -- --
| | | | | | | |
| +--+--+--+--+--+--+-- Power/Velocity 0-127 0x00-0x7F (0000000-1111111) - Left Channel
|
+----------------------- Unused
* 16-Bit : 14-Bit Pitch Bend for Left Channel: (Data Mode Only)
0,0,B13..B0 :
0 0 B13 B12 B11 B10 B9 B8 B7 B6 B5 B4 B3 B2 B1 B0
-- -- --- --- --- --- -- -- -- -- -- -- -- -- -- --
| | | | | | | | | | | | | | | |
| | +---+---+---+---+--+--+--+--+--+--+--+--+--+-- Pitch Bend 0000/2000(NoBend)/3FFF - Left Channel
| |
+--+------------------------------------------------ Unused
* 16-Bit : 7-Bit Note/Key and 7-Bit Power/Velocity for Right Channel: (Data Mode Only)
0,N6..N0,0,P6..P0 :
0 N6 N5 N4 N3 N2 N1 N0
-- -- -- -- -- -- -- --
| | | | | | | |
| +--+--+--+--+--+--+-- Note/Key 0-127 0x00-0x7F (0000000-1111111) - Right Channel
|
+----------------------- Unused
0 P6 P5 P4 P3 P2 P1 P0
-- -- -- -- -- -- -- --
| | | | | | | |
| +--+--+--+--+--+--+-- Power/Velocity 0-127 0x00-0x7F (0000000-1111111) - Right Channel
|
+----------------------- Unused
* 16-Bit : 14-Bit Pitch Bend for Right Channel: (Data Mode Only)
0,0,B13..B0 :
0 0 B13 B12 B11 B10 B9 B8 B7 B6 B5 B4 B3 B2 B1 B0
-- -- --- --- --- --- -- -- -- -- -- -- -- -- -- --
| | | | | | | | | | | | | | | |
| | +---+---+---+---+--+--+--+--+--+--+--+--+--+-- Pitch Bend 0000/2000(NoBend)/3FFF - Right Channel
| |
+--+------------------------------------------------ Unused
** A Note Value of 0 or Power Level of 0 is considered "OFF" and will disable the timer compare channel
Timer On/Off Value Run Mode
---------------------------
* 16-Bit : Timer ON Value for Left Channel: (Data Mode Only)
T15..T0 :
T15 T14 T13 T12 T11 T10 T9 T8 T7 T6 T5 T4 T3 T2 T1 T0
--- --- --- --- --- --- -- -- -- -- -- -- -- -- -- --
| | | | | | | | | | | | | | | |
+---+---+---+---+---+---+--+--+--+--+--+--+--+--+--+-- Timer ON Value 0x0000 - 0xFFFF - Left Channel
* 16-Bit : Timer OFF Value for Left Channel: (Data Mode Only)
T15..T0 :
T15 T14 T13 T12 T11 T10 T9 T8 T7 T6 T5 T4 T3 T2 T1 T0
--- --- --- --- --- --- -- -- -- -- -- -- -- -- -- --
| | | | | | | | | | | | | | | |
+---+---+---+---+---+---+--+--+--+--+--+--+--+--+--+-- Timer OFF Value 0x0000 - 0xFFFF - Left Channel
* 16-Bit : Timer ON Value for Right Channel: (Data Mode Only)
T15..T0 :
T15 T14 T13 T12 T11 T10 T9 T8 T7 T6 T5 T4 T3 T2 T1 T0
--- --- --- --- --- --- -- -- -- -- -- -- -- -- -- --
| | | | | | | | | | | | | | | |
+---+---+---+---+---+---+--+--+--+--+--+--+--+--+--+-- Timer ON Value 0x0000 - 0xFFFF - Right Channel
* 16-Bit : Timer OFF Value for Right Channel: (Data Mode Only)
T15..T0 :
T15 T14 T13 T12 T11 T10 T9 T8 T7 T6 T5 T4 T3 T2 T1 T0
--- --- --- --- --- --- -- -- -- -- -- -- -- -- -- --
| | | | | | | | | | | | | | | |
+---+---+---+---+---+---+--+--+--+--+--+--+--+--+--+-- Timer OFF Value 0x0000 - 0xFFFF - Right Channel
** Writing either Timer On or Timer Off to 0 is considered "OFF" and will disable the timer compare channel
-----------------------------------------------------------------------------------------
tmrMemBlkLeftXX/tmrMemBlkRightXX Memory Mapping:
Each Configured as 256 x 16-bit True Dual-Port RAM Memory
Port A = All Read/Write processing (xferdataproc only)
Port B = All Read-Only processing (tmrupdproc only)
(Address is 8-bits)
A7 A6 A5 A4 A3 A2 A1 A0
-- -- -- -- -- -- -- --
| | | | | | | |
| | +--+--+--+--+--+------------ Subtimer Index (0-63) 0x00-0x3F
| |
+--+--+--------------------------- Entry Selection:
00 = Note and Power Data (as sent/received to/from SPI) (Read/Written on SPI side) (Note in MSB, Power in LSB)
01 = Pitch Bend Value (as sent/received to/from SPI) (Read/Written on SPI side) (right-justified)
10 = Timer ON Compare Value (Calculated during SPI receive and written then, Read on signal generation timer processing)
11 = Timer OFF Compare Value (Calculated during SPI receive and written then, Read on signal generation timer processing)
Block Mapping:
--------------
tmrMemBlkLeft00/tmrMemBlkRight00 - Holds notes 0-63
tmrMemBlkLeft01/tmrMemBlkRight01 - Holds notes 64-127
tmrMemBlkLeft02/tmrMemBlkRight02 - Holds notes 128-191
tmrMemBlkLeft03/tmrMemBlkRight03 - Holds notes 192-255
tmrCTMemBlkLeftXX/tmrCTMemBlkRightXX Memory Mapping:
Each Configured as 64 x 16-bit Simple Dual-Port RAM Memory
Port A = All Read/Write processing (used exclusively by tmrupdproc)
(Address is 6-bits)
A5 A4 A3 A2 A1 A0
-- -- -- -- -- --
| | | | | |
+--+--+--+--+--+------- Subtimer Index (0-63) 0x00-0x3F
tmrCTMemBlkLeft00/tmrCTMemBlkRight00 - Holds notes 0-63
tmrCTMemBlkLeft01/tmrCTMemBlkRight01 - Holds notes 64-127
tmrCTMemBlkLeft02/tmrCTMemBlkRight02 - Holds notes 128-191
tmrCTMemBlkLeft03/tmrCTMemBlkRight03 - Holds notes 192-255
-----------------------------------------------------------------------------------------
cmpFreqMemBlk Memory Mapping:
Timer Output Compare FREQ Values
(OFF Values) = (FREQ Value) - (ON Value)
Configured as 128 x 16-bit Dual Port ROM Memory
(Address is 7-bits)
A6 A5 A4 A3 A2 A1 A0
-- -- -- -- -- -- --
| | | | | | |
+--+--+--+--+--+--+------ Note Value 0-127 (0 is reserved for output disabled compare value of 0)
-----------------------------------------------------------------------------------------
cmpOnMemBlk Memory Mapping:
Timer Output Compare ON Values
Configured as 16384 x 16-bit Dual Port ROM Memory
(Address is 14-bits)
A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0
--- --- --- --- -- -- -- -- -- -- -- -- -- --
| | | | | | | | | | | | | |
| | | | | | | +--+--+--+--+--+--+------ Note Value 0-127 (0 is reserved for output disabled compare value of 0)
| | | | | | |
+---+---+---+---+--+--+--------------------------- Power Value 0-127 (0 is reserved for output disabled compare value of 0)
-----------------------------------------------------------------------------------------
For PitchBend calculations, the top-two bits (bit B13 and B12) of the PitchBend determine which note
compare stores are used in the PitchBend calculation as follows:
B13 B12
--- ---
0 0 -- Use Note-2 and Note-1
0 1 -- Use Note-1 and Note
1 0 -- Use Note and Note+1
1 1 -- Use Note+1 and Note+2
Notes in the extremes of the Note values (i.e. outside of the meaningful
88 note piano range where our compares wrap anyway) are excluded from
the pitch bend calculations
-----------------------------------------------------------------------------------------