UPDATE:
De gehele firmware van de Cola2 herzien. Dit was nodig omdat ik begonnen was aan de firmware zonder te weten waar de knelpunten zaten. Nu ik dat wél weet, kan ik de VHDL code gerichter schrijven en optimaliseren. Dit is een blok schematische weergave van de firmware:
Er is flink wat veranderd. De packer/RLE compressor engine werkt nu op 200MHz, waar deze eerst synchroon met de FT601 interface werkte (100MHz). Dit is gedaan om sample rates van boven de 100MHz toe te staan. Omdat dit de theoretische bandbreedte van de USB3.0 chip overstijgt, zitten er een aantal gotcha's aan verbonden. De uiteindelijke gemiddelde bandbreedte mag niet boven de 100MHz komen, dus het RLE compressie gedeelte moet er voor zorgen dat de data gecomprimeerd word. Dat betekend dus niet te veel verandering aan de input samples.
Grote verandering nummer 2: Ik werk nu met een sample rate opgewekt door een Fractional PLL. Dit houdt in dat deze continue variabel kan zijn tussen de (zeg) 500Hz en de 100MHz, mogelijk daarboven zelfs. De PLL wekt een frequentie op van 50-100MHz, en een 32 bit klokdeler converteert deze naar een mogelijk nog lagere sample rate.
Door al deze verschillende frequenties moeten er dual clock FIFO registers tussen al deze blokken komen. Op zich geen probleem, maar de latency van de DCFIFO is 1 meer dan de SCFIFO. De RLE compressor en de FT601 interface moest hierop aangepast worden. De Packer heeft nu een "spinup" fase waarin er een READ signaal naar de sample FIFO gestuurd is, maar de data nog niet beschikbaar is. Dit heeft consequenties voor de mate waarin de samples uitgelezen kunnen worden. Ik noem dit even de "Ingress Ratio".
Bij de 96bit/25MHz ("single") mode, gebruik ik 4 32-bit woorden op de USB interface voor 1 sample. Op het moment dat een sample beschikbaar is, gaat de State Machine in de Packer/Compressor van:
- Idle -> Spinup -> Pack3 -> Pack2 -> Pack1 -> Pack0 -> Idle...
Als er nog meer samples beschikbaar zijn, kan ik dit detecteren in de Pack1 fase, waardoor de spinup fase van de volgende sample eigenlijk de Pack0 is van de hudige sample. Van Pack0 kan ik dan gaan naar:
- Pack0 -> Pack3 -> Pack2 -> Pack1 -> Pack0...
Echter, als ik pas in de Pack0 fase een nieuwe sample heb, kan ik dus vanuit pack0 naar Spinup (ipv Pack3). Ik moet immers wachten totdat de nieuwe sample op mijn datalijnen staat:
- Pack0 -> Spinup -> Pack3 -> Pack2 -> Pack1 -> Pack0...
De Pack3 fase is interessant, want het hangt af van de vorige sample wat ik hier ga decoderen. Als bit 95-48 hetzelfde is als de vorige sample, hoef ik deze niet de coderen naar de USB interface.
- Pack3 -> Pack0...
Als daarbij bit 47-24 ook hetzelfde is, kan ik nog meer weglaten. Normaliter MOET ik bit 23-0 coderen, om een sample aan te geven, maar door de RLE compressie hoef ik zelfs dit niet te doen: In plaats daarvan verhoog ik gewoon de RLE counter (tenzij deze op 127 staan, dan MOET ik dus wel een USB data woord versturen). Ik kán dus van Pack3 terug naar Spinup. Het is zo dat ik de Packer/Compressor engine meteen weer vrij heb voor een volgend sample, maar ik moet wachten op de 1 clock latency van mijn FIFO. Vanaf Pack3 kan ik dus niet direct naar een volgende Pack3 sample, maar ik ben verplicht naar de Spinup fase te gaan:
- Pack3 -> Spinup -> Pack3...
Door al deze State Machine Hocus Pocus heb ik dus een Ingress Ratio van 25% minimaal tot 50% maximaal. Dit houdt in dat ik een theoretisch maximale sample rate heb van 25MHz tot 50MHz. Door middel van RLE echter kan dit nog hoger worden, afhankelijk of ge ingangs bitpatroon gunstig is.
In de 48bit/50MHz zit ik op een een Ingress Ratio van Fixed 50%. Dit komt omdat ik maximaal 2 USB words nodig heb om 1 sample te beschijven. Dit kan ook niet hoger worden omdat ik ook 1 clockpuls moet wachten op een volgend sample (FIFO's...) Altijd 50% dus.
Voor 24bit/100MHz heb ik 1 32-bit USB word nodig per sample. Echter, door de spinup-fase van mijn compressor kan het zijn dat ik een klokpuls moet wachten. Een Ingress Ratio van 50-100% dus.
Nóg een belangrijke verandering in de firmware: Ik heb nu een embedded MCU. Ik heb gekozen voor de NEO430, een softcore MSP430-code compatible MCU. Op deze manier heb ik een degelijke C-compiler beschikbaar, en is alles lekker open source. I like it! De NEO430 core werkt op 50MHz, omdat de &^%$&^#& (Insert PC correct wording here) PLL reconfigurator voor de Fractional PLL aan te sturen niet erg veel harder gaat. Dit wonder van IP technologie is van Intel (Altera) zelf. Ik heb gekeken hoe deze core werkt, om het misschien wat sneller te coderen:
Ja weet je wat? Laat ook maar. Ik ben tevreden met de 50MHz.
Alle communicatie van en naar de NEO430 gaat over een Wishbone interface. Deze open-source address en data bus is lekker simpel te implementeren, en al gauw had ik dan ook interfaces geschreven voor configuratie van de ingangs- en uitgangs 128x128 MUX, GPIO voor de LED aan te sturen, Packer/Compressor mode in te stellen, USB communicatie en alle 96 bits aan ingang en uitgang direct aan te spreken.
Via de USB interface kan ik een nieuw programma in de Logic Analyzer flashen, woordoor ik een digital pattern generator kan maken, of een Eprom programmer of... De mogelijkheden zijn eindeloos. (tot 48KB code/ 12KB RAM).
Over optimalisatie gesproken. Ik heb het deel dat werkt op 200MHz binnen in de FPGA zo klein mogelijk gehouden. Op die manier heb ik de grootste kans dit juist gecompiled wordt. Ook heb ik in de instellingen van Quartus gegraven, en ik ben achter een belangrijke optimalisatie instelling gestuit:
Optimize for SSN. SSN staat voor Simultaneous Switching Noise, en dat is signaal integriteits vermindering als gevolg dat heel veel transistors in je ontwerp tegelijkertijd moeten schakelen. De voeding heeft op dat moment niet genoeg capaciteit om dit betrouwbaar te doen. Optimize for SSN zorgt er (denk ik) voor dat kritieke delen van je ontwerp verdeeld worden over meerdere VCCINT pinnen van je FPGA, en dus ook over meerdere ontkoppel- en bulk capaciteiten op je print. Het gevolg hiervan is een veel betere stabiliteit van je ontwerp. Pro tip dus!
Als laatste grote wijziging is de Trigger Engine. Eerdere opmerking van Rew was dat een pre-trigger capture van een paar (honderd?) samples eigenlijk niet toereikend is. Zo veel data opslag heb ik niet in mijn FPGA!
De oplossing is dan ook: Ik laat de FPGA alleen de trigger DETECTIE doen, en de computer de opslag. De Trigger engine geeft een signaal op het moment dat een trigger event is gedetecteerd, en de Packer/Compressor zorgt er voor dat deze in de datastroom naar de computer toe wordt verwerkt. Op die manier kun je een 2 seconde capture doen, met een half uur aan pre-trigger. Zorg gewoon dat je Terabytes aan SSD opslag beschikbaar hebt. Ik kan triggeren op:
- Hoog signaalniveau (1)
- Laag signaalniveau (0)
- Opgaande flank
- Neergaande flank
- Op- of neergaande flank (Either)
- Don't care (geen trigger)
Dit voor elk van de 96 bits te selecteren. Erg geavanceerde trigger opties zijn er dus vooralsnog niet, maar je kunt er toch wel wát mee.