Nachdem der Prototyp nun zuverlässig funktioniert, habe ich das Design des ersten Releases fertiggestellt. Es übernimmt die Funktionen des Prototyps, verzichtet aber auf nicht notwendige Debug-Steckverbinder und ‑Schnittstellen. Das Tang Primer 25K Basis-Board wird nicht mehr unterstützt, sondern lediglich das Tang Primer 25K Core-Board. Dieses Core-Board muß dann extern auf einem Basis-Board programmiert und umgesteckt werden. Physikalische serielle Schnittstellen wie USB, Bluetooth und RS485 sind entfallen. Bei Bedarf müssen sie anderweitig angeschlossen werden. RxD und TxD werden als 3.3V-TTL-Signale zur Verfügung gestellt.
Der Fehler des Prototyps mit dem Leckstrom in den Komparatoren wurde mit einem Operationsverstärker (hoffentlich) ausgebügelt. Das Board geht in Kürze in Produktion.
Ansichten und Design-Dateien
Die 3D-Ansichten…
Gamma Messgerät, Oberseite mit aufgestecktem FPGA-BoardGamma Messgerät, Oberseite ohne FPGA-BoardGamma Messgerät, Unterseite
Die Dämpfungsglieder am Eingang sind jetzt für eine maximale Eingangsleistung von 27 dBm (500 mW) ausgelegt. Dem negativen Eingang der Komparatoren ist nun jeweils ein rauscharmer Präzisions-OpAmp vom Typ OPA376 als Spannungsfolger vorgeschaltet. Der sollte die Spannungsverschiebung durch den Eingangsleckstrom der Komparatoren kompensieren. Für OpAmp und Komparatoren wären auch jeweils Varianten mit zwei Einheiten in einem Gehäuse verfügbar. Um eine bestmögliche Entkopplung der beiden Kanäle zu erreichen, wurde auf deren Einsatz aber verzichtet.
J7 dient der Spannungsversorgung und der seriellen Kommunikation mit der Außenwelt. Hier kann zu Testzwecken ein USB-Seriell-Konverter direkt angesteckt werden. Um einen gewissen Schutz gegen Verpolung zu erreichen, sind in die Datenleitungen serielle Widerstände von 330 Ω eingefügt. Die Spannungsversorgung ist mit einer Thermosicherung und einer TVS-Diode gesichert. J3 und J6 stellen ein paar Signale des FPGAs zur Verfügung. Wenn alles auf Anhieb funktioniert, sind sie unnötig.
Die Kanäle wurden jetzt mit Port1 und Port2 bezeichnet. Sie sind ja gleichartig aufgebaut und die Software entscheidet, welcher Port die vorlaufende und welcher die rücklaufende Spannung aufnimmt.
Koppler V1.7
Anders als ursprünglich geplant wurde der Koppler nicht auf dieses Board integriert. Stattdessen habe ich einen passenden Koppler entworfen, nunmehr die Version 1.7, der den passenden Portabstand hat, so daß er direkt an das Messgerät angeschraubt werden kann. Hier die 3D-Ansicht:
Für J3 und J4 werden tatsächlich männliche SMA-Schraubverbinder montiert, die aber hier mangels 3D-Modell nicht dargestellt sind. Der Abstand ist passend zum Messgerät gewählt.
Für die Trafos wird hier RG400 Koaxkabel mit blauem Außenmantel eingesetzt. Es hat etwa den gleichen Außendurchmesser wie RG58, nutzt aber PTFE als Isolator und einen temperaturfesten Außenmantel. Es brutzelt daher beim Löten nicht gleich weg.
Erfahrungen zum Bau der Trafos
Zwischenzeitlich habe ich einige weitere Versuchsaufbauten mit den vorherigen Kopplervarianten gemacht. Die habe ich bisher nicht hier dokumentiert. Es sieht im Moment so aus, daß die Variante mit FT 50–61 Ringkern und nur 20 Windungen den besten Frequenzgang von 160m bis in den UKW-Bereich hat. Die Koppeldämpfung beträgt 26 dB (1:20²) was für ein stärkeres Signal am Messeingang sorgt und gleichzeitig den Verlust gering hält (250 mW Verlust bei 100 Watt Senderausgangsleistung). Der einzige kleine Nachteil ist der, daß aufgrund der relativ niedrigen Induktivität der induktive Widerstand der Spulen bei 1,8 MHz grenzwertig ist. Hier kann man weiter optimieren und entweder ein paar wenige Windungen mehr aufbringen (22~24) oder doch wieder auf den FT 50–43 Ringkern zurückgreifen. Höhere Windungszahlen würde ich in beiden Fällen vermeiden. Sie treiben die Induktivität hoch und verringern die Selbstresonanzfrequenz. Beides ist für die 4‑m- und 6‑m-Bänder ungünstig.
Hier die Meßergebnisse ohne weiteren Kommentar:
Der im vorigen Beitrag bereits angekündigte RTL-Code für das FPGA und die zugehörige Firmware sind jetzt funktionsfähig und soweit, daß man beides vorzeigen kann. Es ist noch der eine oder andere Testcode enthalten, daher ist beides noch nicht als „Release“ gekennzeichnet. Außerdem soll es noch ein paar Änderungen in der nächsten Revision der Leiterplatte geben.
Nach ein paar grundlegenden Bemerkungen zum Messprinzip wird nachfolgend zuerst der Verilog-Code beschrieben, anschließend die Software und zum Abschluss werden ein paar Beispielmessungen mit einem Signalgenerator gezeigt. Am Ende des Beitrags folgen noch ein paar Anmerkungen zum PicoRV32-Risc-V-Core.
Das verwendete Tool zum Erstellen des FPGAs ist der Gowin FPGA Designer V1.9.11.01. Für die Softwareerstellung wird GMD (Gowin for Embedded C/C++ Developers), Version: 2025.01 verwendet.
Als Referenz soll hier zunächst nochmal das Blockdiagramm aus dem vorigen Beitrag gezeigt werden, weil sich die nachfolgenden Erklärungen darauf beziehen:
Das Messprinzip
Die vor- und rücklaufenden Analogsignale werden in den Komparatoren digitalisiert und anschließend im FPGA digital ausgewertet. Hier ist exemplarisch ein Timingdiagramm für ein 10-MHz-Signal und eine Abtastung mit 200 MHz gezeigt:
Timing Diagramm der Phasenmessung
Oben wird das vorlaufende Signal und darunter das reflektierte und um 90° phasenverschobene rücklaufende Signal gezeigt. Die dritte und vierte Kurve zeigen jeweils das digitalisierte Signal und danach ist das exklusiv-veroderte Phasendifferenzsignal dargestellt, aus dem die Phasenverschiebung bestimmt wird. Das Abtastsignal hat eine deutlich höhere Frequenz, in diesem Fall 200 MHz.
Ein Messintervall dauert typischerweise zwischen 10 und 100 ms. Während dieses Intervalls werden verschiedene Zähler unter bestimmten Bedingungen mit dem Takt des Abtastsignals hochgezählt und am Ende des Messintervalls von der CPU ausgelesen und ausgewertet. Beim Start des nächsten Messintervalls werden diese Zähler automatisch wieder gelöscht und starten bei null.
Phase Difference Counter PDC
Alle Zähler sind im Modul „Reflection_Measurement“ in der Datei „RefMeas.v“ definiert. Der Phase Difference Counter PDC wird immer dann inkrementiert, wenn das Phasendifferenzsignal auf high liegt, wenn also beide Eingangssignale verschieden sind. Am Ende des Messintervalls zeigt das Verhältnis des PDC-Zählerstandes zur Gesamtanzahl der Takte während des Intervalls also die Phasenverschiebung zwischen den Eingangssignalen an.
Edge Counter EC
Die Phasenmessung mit dem Phase Difference Counter liefert ein zweideutiges Ergebnis, denn eine negative Phasenverschiebung führt zu demselben Zählerstand, wie eine positive Verschiebung. Diese Mehrdeutigkeit löst der Edge Counter EC auf. Dieser Zähler wird dann inkrementiert, wenn das rücklaufende Signal bei einer steigenden Flanke des vorlaufenden Signals low ist. Ein weiterer gleichartiger Zähler wird dann inkrementiert, wenn das vorlaufende Signal bei einer steigenden Flanke des rücklaufenden Signals low ist. Am Ende der Messung trifft die CPU eine Mehrheitsentscheidung und bestimmt so das Vorzeichen der Phasenverschiebung. Bei 0° und 180° kommt es hier zu Unsicherheiten, die aber im Rahmen der Messgenauigkeit liegen.
Frequency Counter FC1 und FC2
Die beiden Frequenzzähler FC1 und FC2 werden immer dann inkrementiert, wenn eine steigende Flanke auf dem jeweiligen Eingangssignal festgestellt wurde. Aus dem Zählerstand am Ende der Messung bestimmt die CPU dann die jeweilige Frequenz.
Diese Frequenzen sind naturgemäß gleich, weil das rücklaufende Signal ja vom vorlaufenden Signal abgeleitet ist. Bei nahezu idealer Anpassung geht aber der Pegel des reflektierten Signals immer weiter gegen null und die gemessene Frequenz genauso. Der Vergleich dieser Frequenzen ist daher ein Maß für die Zuverlässigkeit der Phasenmessung. Weichen sie (deutlich) voneinander ab, ist die Phasenmessung mit den oben genannten Registern ungültig.
High und Low Counter
Im unten vorgestellten Design sind noch vier weitere Zähler implementiert, die im Blockdiagramm nicht gezeigt sind und zukünftig entfallen sollen: F‑High, F‑Low, R‑High und R‑Low. Die High-Zähler werden inkrementiert, wenn das entsprechende Eingangssignal bei steigender Flanke des Abtasttaktes high ist, die (redundanten) Low-Zähler dann, wenn das Signal low ist. Wie im Beitrag „Gamma-Messgerät – Nachtrag zu den Messungen“ beschrieben wurde, gibt es beim Prototyp einen Offset in der Vergleichsspannung der Komparatoren. Das führt dazu, daß die Umschaltpegel der Komparatoren vom Nulldurchgang der Eingangssignale verschoben ist. Der Effekt ist nachfolgend etwas übertrieben dargestellt:
Timing Diagramm der Phasenmessung, Fehler in der Schaltschwelle (übertrieben dargestellt)
Als Folge davon sind die erkannten positiven Halbwellen kürzer als die negativen Halbwellen. Dieser Fehler ist bei hohen Eingangspegeln gering, wird aber immer höher, je weiter die Pegel sinken. Am Ende des Messintervalls zeigen die Zählerstände also die Dauer der high- und low-Phasen an, also die Qualität des jeweiligen Signals.
Weitere Funktionsblöcke im FPGA
Intervall Timer T1
T1 ist ein einfacher Intervall Timer, der automatisch nach einer einstellbaren Zeit einen CPU-Interrupt generiert. Er wird im Beispielcode auf 10 ms eingestellt. Anders als der in der CPU eingebaute Timer, muß er nach dem Auslösen eines Interrupts nicht wieder neu initialisiert werden. Damit wird also unabhängig von der Interrupt-Antwortzeit ein festes zeitliches Raster erzeugt, zumindest solange keine Interrupts verloren gehen.
Asynchroner Transmitter ATx
In der CPU können zwei verschiedene UARTs angelegt werden, ein „simple UART“ und ein „WB UART“. Keines davon ist besonders gut dokumentiert, so daß es mir nicht auf Anhieb gelungen ist, einen Interrupt auszulösen, wenn der Sendepuffer leer ist. Da zum Senden der Messwerte nur ein Transmitter nötig ist, schien es mir einfacher, einen eigenen asynchronen Sender zu schreiben. Ich habe ihn ATx genannt, nicht UART, weil er weder universell ist noch einen Empfänger beinhaltet. Immerhin ist er gepuffert und löst einen Interrupt aus, sobald der Puffer ins Schieberegister übertragen wird. Die Baudrate ist von der CPU einstellbar. Es wird ein Stoppbit generiert und Parity ist nicht unterstützt.
Der RTL Code
Die oben im Blockdiagramm gezeigten und anschließend erklärten Funktionen wurden in Verilog geschrieben und für das FPGA synthetisiert. Hier ist das gesamte komprimierte Gowin_V1.9.11.01 FPGA Designer Projekt inklusive der vom IP Core Generator erzeugten Module. Mit welchen Parametern diese Module erzeugt wurden steht in src/readme.txt.
Das Top-Level Design findet sich in der Datei „Antennenkoppler.v“. Hier werden die FPGA-Pins definiert, die auf dem Prototypenboard angeschlossen sind. Außer den generierten Modulen werden die Module „Reflection_Measurement“, „ATx“ und „IntervalTimer“ instanziiert. Deren Quellcode findet sich in den entsprechenden Dateien im selben Verzeichnis.
Das Top-Level-Modul generiert die Reset-Signale für die CPU und die Reflexionsmessung, sowie die Signale für das Wishbone-Memory-Interface. Es definiert auch die Signale für die Adress-Decodierung. Weitere Erläuterungen finden sich in den jeweiligen Quelltexten.
Außerdem instanziiert das Top-Level-Modul die PLL, die aus dem Eingangstakt von 50 MHz den Abtasttakt von 200 MHz und den CPU-Takt von 20 MHz erzeugt.
Der IP Core Generator
Exemplarisch soll hier die Erzeugung des Gowin PicoRV32 CPU-Cores mit dem IP Core Generator gezeigt werden. Der IP Core Generator wird entweder unter „Tools“-„IP Core Generator“ gestartet oder durch Anklicken des zugehörigen Icons mit den drei Würfeln auf der Tools-Leiste. Er meldet sich mit einem Angebot verschiedener Hard- und Soft-Module:
Der Gowin Core Generator
Hier wählt man unter Soft IP-Core den Gowin_PicoRV32 mit einem Doppelklick aus. Es wird eine Eingabemaske zu Konfiguration des gewünschten Funktionsumfangs gestartet:
PicoRV32 CPU Konfiguration
Durch Doppelklick auf die jeweilige Funktionsgruppe kann diese ausgewählt (grün hinterlegt) oder wieder entfernt werden. Bei konfigurierbaren Elementen öffnet sich ein entsprechendes Menü. Starten wir mit der Konfiguration der CPU:
Gowin PicoRV32 CORE
In diesem Dialog wird der CPU-Core konfiguriert.
Konfiguration des PicoRV32 CPU-Cores
Man kann zunächst in engen Grenzen den verwendeten Befehlssatz auswählen. Immer ausgewählt ist der RV32I Integer Befehlssatz, optional kann man die komprimierten Instruktionen (RV32C) und die Unterstützung für Integer-Multiplikation und Division (RV32M) zu- oder abwählen. Darüberhinaus sind einige Features wählbar, die den Funktionsumfang und die Performance beeinflussen, was jeweils zugunsten oder zuungunsten der Anzahl verwendeter Logikgatter führt. Da es bei dem hier vorgestellten System weder Einschränkungen bei den Logikgattern noch bei der Performance gibt, lassen wir alles auf den oben gezeigten Default-Einstellungen.
ITCM
Mit der etwas ungewöhnlichen Abkürzung ITCM wird der Befehlsspeicher für den Prozessor konfiguriert und sein Inhalt festgelegt.
Instruction Memory
Der Befehlsspeicher kann 8 kB, 16 kB, 32 kB oder 64 kB groß sein. Für dieses Projekt belassen wir es bei dem Default-Wert von 32 kB.
Als nächstes muß der Boot-Mode ausgewählt werden. Bei den ersten beiden Modi wird ein SPI-Interface generiert, über das das Programm beim Start aus dem externen Flash in das ITCM geladen wird. Damit ist die Softwareentwicklung quasi unabhängig von der Hardwareentwicklung. Hier ist es im Moment allerdings einfacher, die Software bereits in den FPGA-Bitstream einzumassieren. Dazu wählt man „MCU boot and run in ITCM“ und trägt den Namen der zu verwendenten Hex-Datei ein. Das hat allerdings den Nachteil, daß die Software bereits bei Generierung des Bitstreams vorhanden sein und dieser bei jeder Softwareänderung neu generiert werden muß.
DTCM
Die Konfiguration des Datenspeichers erfolgt analog zu der des Instruktionsspeichers.
Data Memory
Der Datenspeicher wird beim Starten durch die CPU initialisiert. Ein Hex-File wie beim Instruktionsspeicher entfällt daher.
WB UART
Das Wishbone UART wird tatsächlich in dem hier vorgestellten Projekt nicht mehr verwendet, aber aus historischen Gründen sind die Signale noch definiert. Daher muß es enabled werden, um Fehlermeldungen zu vermeiden. Es wird in einer zukünftigen Version entweder wieder benutzt oder ganz entfernt.
WB SPI Master
Das SPI Master Interface wird zur Kommunikation mit dem externen AD-Wandler ADS1118 benötigt. Es wird enabled und folgendermaßen konfiguriert:
Wishbone SPI Master
Das SPI-Interface wird mit dem CPU-Takt von 20 MHz betrieben und der AD-Wandler darf mit höchstens 4 MHz getaktet werden. Um genügend Marge zu haben, wird er tatsächlich mit 2 MHz betrieben, was einem CLOCK_SEL=4 entspricht (SPICLK = CPUCLK/(2*(CLOCK_SEL+1))). Shift-Richtung, Clock Phase und Polarität sind vom ADC vorgegeben und müssen wie hier gezeigt gewählt werden, die Kommunikation erfolgt mit einer Datenlänge von 32 Bit. Die anderen Parameter bleiben auf ihren Default-Werten.
Open WB Interface
Abschließend wird noch das Open Wishbone Interface enabled. Es wird für das Lesen und Schreiben der Register in den oben genannten Funktionsblöcken benötigt.
Andere CPU Funktionsblöcke
Simple UART, Open AHB Interface, WB I2C Master, WB SPI Slave, WB GPIO und ADV SPI-Flash werden hier nicht benutzt und dementsprechend disabled.
Generieren der konfigurierten CPU
Nachdem nun alle Blöcke wie gewünscht konfiguriert sind, kann nun die CPU generiert werden. Vor dem Klicken auf „ok“ sollte aber noch der Haken bei „Use Gowin PicoRV32 as top module“ abgewählt werden, denn die CPU ist hier ein Sub-Modul.
PicoRV32 CPU Konfiguration – fertig konfiguriert
Nach dem Klicken auf „ok“ wird noch nachgefragt, ob das existierende Verzeichnis überschrieben werden darf, falls es aus vorherigen Durchgängen bereits existiert. Wir klicken auf „yes“ und nun hat er erstmal 30 Sekunden zu tun. Dann wird nachgefragt, ob die neu generierten Dateien zum Projekt hinzugefügt werden sollen. Das sollte man beim ersten mal mit „yes“ beantworten, sonst muß man sie anschließend manuell hinzufügen. Bei weiteren Durchgängen ist die Antwort egal, denn sie sind ja schon Teil des Projekts.
Synthese, Placement & Routing
Jetzt ist das Projekt bereit zur Synthese. Dabei werden die Logikfunktionen aufgelöst und das gesamte Projekt in Logikgatter umgewandelt. Wenn das fehlerfrei durchläuft, folgt anschließend das Placement & Routing. Dabei werden die Pins und Gatter an die berechneten Positionen platziert und elektrisch miteinander verbunden.
Wenn die Synthese für ein definiertes Projekt verwendet werden soll, dann muß man vor dem Placement zumindest die angeschlossenen Pins im Floorplanner zuweisen. Nicht angeschlossene Pins bekommen eine Default-Konfiguration (meist Input mit pull-up Widerständen). Nicht im Floorplanner zugewiesene Signale werden irgendwohin gelegt, wo es dem Placement genehm ist. Das kann man bei ersten Versuchen machen, wenn es noch keine Leiterplatte gibt. Das automatische Placement kann sogar eine Hilfe bei der Pinauswahl für das Layout sein.
Synthese, Placement & Routing benötigen für dieses Projekt auf meinem PC etwa 2 Minuten. Im Verlauf werden mehrere Berichte erstellt, die man sich gelegentlich anschauen sollte. Sie können insbesondere bei Fehlern sehr hilfreich sein. Auch die Timing Analyse sollte man nicht ignorieren. Hier werden die maximal möglichen Taktfrequenzen angezeigt und zwar in rot, wenn die Vorgaben nicht getroffen werden. Gerade in der Entwicklungsphase kann man mal ein Auge zudrücken, wenn die Abweichungen gering sind und beispielsweise nur 195 MHz statt 200 MHz erreicht werden, wir arbeiten ja schließlich unter definierten Bedingungen und bei Zimmertemperatur. Sobald das Modul aber für den Außenbereich released wird und damit dem gesamten Temperaturbereich und womöglich Spannungsschwankungen ausgeliefert ist, würde ich keine roten Meldungen mehr tolerieren. Im ungünstigsten Moment fällt dann das Modul aus und man hat keinen Hinweis, was schiefgelaufen sein könnte.
Es müsste direkt übersetzbar sein, aber gegebenenfalls müssen Pfade angepasst werden.
Das eigentliche Hauptprogramm steht in der Datei „RiscV_Phasenmessung.c“. Im „library“ Verzeichnis befinden sich die Quellcodes, die von Gowin zur Verfügung gestellt wurden, einige von ihnen wurden für dieses Projekt adaptiert, insbesondere die Interrupt-Funktionen in irq.c und irq.h.
Das Hauptprogramm
Im Hauptprogramm werden nach der Initialisierung lokaler Variablen als erstes die verwendeten Peripherieelemente im FPGA initialisiert. Der asynchrone Sender wird auf 115,2 kBd eingestellt, der Intervalltimer auf 10 ms und ein Messzyklus wird gestartet. Anschließend werden die Interrupts freigegeben.
Der Intervalltimer
Der Intervalltimer erzeugt alle 10 ms einen Interrupt, der die Funktion user1_int() aufruft. Dort wird im wesentlichen der nächste ADC Messzyklus gestartet und das Ergebnis der vorherigen Messung gelesen. Der SPI-Transfer zum ADC läuft automatisch und die CPU muß nicht auf dessen Ergebnisse warten. Sie werden einfach bei der nächsten Gelegenheit nach 10 ms abgeholt. Um die Ausführungszeit des Interrupt-Handlers kurz zu halten, werden die Messergebnisse nicht konvertiert, sondern als Rohdaten abgespeichert.
Die Phasenmessung
Die Phasenmessung erfolgt wie oben beschrieben im FPGA. Sie wird durch Schreiben des Controlregisters gestartet und läuft dann automatisch ab. Der Status der Messung wird im Statusregister angezeigt, das hier der Einfachheit halber gepollt wird. Ist die Messung beendet, werden die Ergebnisse ausgelesen, formatiert und die serielle Übertragung wird gestartet. Der Datentransfer nach dem ersten Byte erfolgt interruptgesteuert. Direkt nach dem Start der Übertragung wird der nächste Messzyklus gestartet.
Die Formatierung der Ergebnisse ist im Moment in lesbarer Form gehalten, damit man sie direkt in einem Terminalprogramm beobachten kann. Daher wurde auch auf einen Zeilenvorschub verzichtet, es wird also jedesmal die vorherige Zeile überschrieben. Das wird in einem endgültigen System sicherlich adaptiert.
Beispielmessungen
Nachfolgend sollen einige Beispielmessungen z.Zt. noch mit reiner Textausgabe dokumentiert werden.
Messung ohne Eingangssignale
Dieser Block zeigt die gesamte Textausgabe nach einem Reset. Die folgenden Beispiele zeigen jeweils nur die Messung.
-------- Reflection Measurement, under development, date: Jan 8 2026, time: 12:39:23 -------- CPU clock: 20.000 MHz - Sampling clock: 200.000 MHz - Measurement interval: 25 ms Ff: 0.000 MHz, Fr: 0.000 MHz, Delta_P: +0.00°, High_f: 0.00%, High_r: 0.00%, ADC0=2 mV, ADC1=3303 mV, ADC2=398 mV, ADC3=381 mV, T=+23,750 °C
ADC1 zeigt die Versorgungsspannung in Millivolt, T die aktuelle Temperatur des AD-Wandlers. Es sind keine Eingangssignale angelegt, daher ist die Frequenz 0 Hz. An den Ausgängen der logarithmischen Verstärker liegen knapp 400 mV an.
Hier wurde absichtlich eine krumme Frequenz von 21.104 MHz gewählt, denn mitunter ergeben sich bei ausgesuchten Eingangswerten zufällig die gewünschten korrekten Ergebnisse. Die Frequenzen werden korrekt gemessen und es zeigt sich eine Phasenverschiebung von +0.99°, was im Rahmen der Messgenauigkeit liegt. Die an den LogAmps gemessenen Pegel liegen bei 1,87 V mit einer Abweichung von etwa 10 mV. High_f und High_r geben die relative High-Zeit des jeweiligen Signals während der Messperiode an. Sie liegen etwas unter 50%, wodurch die Messung bei diesem Eingangspegel schon unzuverlässig wird.
Wir bleiben bei der krummen Frequenz und erhöhen den Pegel des vorlaufenden Signals auf +20 dBm und die Phasenverschiebung auf 100°. Die Frequenzmessung bleibt korrekt und die Qualität der vorlaufenden Signals verbessert sich auf 49.78%, also nahezu auf den Idealwert.
Jetzt wird auch der Pegel des rücklaufenden Signals auf +20 dBm erhöht. Die Qualität beider Signale nähert sich dem Idealwert von 50% und die gemessene Phasenverschiebung liegt nun 2° neben dem tatsächlichen Wert. Beide LogAmps zeigen nun etwa 2,37 V Ausgangsspannung an.
Der Vorlauf-Pegel liegt nun wieder bei 0 dBm, der Rücklauf-Pegel bei ‑15 dBm und die Phasenverschiebung wurde am Signalgenerator auf ‑100° eingestellt. Die Frequenzmessungen sind nach wie vor korrekt, aber die Qualität des rücklaufenden Signals sinkt auf unter 30%. Es wird also ein erheblicher Teil der positiven Halbwelle abgeschnitten. Trotzdem weicht die gemessene Phasenverschiebung nur um gut 3° von der tatsächlichen Verschiebung ab.
Der Rücklauf-Pegel liegt jetzt bei ‑19 dBm. Die Frequenzmessungen sind nach wie vor korrekt, aber die Qualität des rücklaufenden Signals sinkt auf unter 16%.
Der Rücklauf-Pegel liegt jetzt bei ‑20 dBm, nur 1 dB weniger als bei der vorigen Messung. Das Signal wird nicht mehr erkannt, die Frequenz sinkt auf 0 Hz und die Qualität auf 0%. Kanal 3 des ADC zeigt noch 1,358 V, es liegt also noch ein hinreichend starkes Signal an.
Das ist genau das Problem, das einer Nachbesserung bedarf und im nächsten Redesign behoben werden soll.
Anmerkungen zur PicoRV32 CPU
Im FPGA wird eine PicoRV32 CPU synthetisiert. Das ist ein 32-bit-Mikroprozessor mit einer Risc-V-Architektur. Zum Erstellen des RTL-Codes wird der „IP Core Generator“ des Gowin FPGA Designers benutzt. Alternativ kann der gesamte Verilog-Code auch von GitHub heruntergeladen werden. Damit hat man alle Freiheiten, inklusive der Adaption des Codes, aber eben auch alle Last der korrekten Konfiguration. Weitere Informationen zur Risc-V-Architektur findet man auf deren Website.
PicoRV32 Designziele
PicoRV32 ist für die Synthese für ein FPGA optimiert. Dazu soll der Ressourcenverbrauch gering sein, die Rechenleistung ist von untergeordneter Bedeutung. Im hier verwendeten FPGA wird bei der Standardkonfiguration der CPU ungefähr ein drittel der vorhandenen Logikgatter benötigt.
PicoRV32 Performance
Fälschlicherweise wird der Begriff RISC oft mit „eine Instruktion pro Takt“ gleichsetzt, was zwar oft stimmt, aber nicht die eigentliche Bedeutung ist. RISC bedeutet nur, daß der Befehlssatz auf solche Operationen reduziert ist, die ohne zeitlich unbestimmte Pipeline-Locks ausgeführt werden können. Die Pipeline darf durchaus mehrere Takte für eine Instruktion benötigen, sie darf aber nicht beispielsweise durch Wait-States beim Zugriff auf andere Ressourcen auf unbestimmte Zeit blockiert werden. Es sind nur solche Instruktionen erlaubt, die das sicherstellen. Damit fallen insbesondere die Befehle weg, die Daten im Speicher direkt modifizieren, wie es bei CISC-Prozessoren üblich ist. Für Operationen dieser Art wird in einem RISC Prozessor zunächst der Speicherwert in ein Register gelesen, dort modifiziert und anschließend zurückgeschrieben (Load/Store-Architektur). Während des Lese- oder Schreibzugriffs kann die CPU prinzipiell weiterarbeiten, was eine höhere Rechenleistung ermöglicht. In einer einfach gehaltenen CPU darf man Optimierungen dieser Art allerdings nicht erwarten. Auch die etwas komplexeren mathematischen Operationen Multiplikation und Division benötigen einige Dutzend Taktzyklen.
Dhrystone Benchmark Ergebnisse
Die PicoRV32-CPU benötigt beim Dhrystone Benchmark 4,1 Takte pro Instruktion, was 0,516 DMIPS/MHz entspricht. Das ist für RISC-Verhältnisse nicht überwältigend, aber in Anbetracht des geringen Ressourcenverbrauch doch erstaunlich gut. Auf jeden Fall ist es für die typischen embedded-Anwendungen mehr als ausreichend. Eine Taktfrequenz von bis zu 50 MHz soll möglich sein, hier sind 20 MHz eingestellt und völlig ausreichend.
Interrupt Handling
Ein 32-bit RISC-Prozessor hat oft (aber nicht immer) 32 Register zur allgemeinen Verwendung. Das gilt auch für den PicoRV32. Das ist für viele Anwendungsfälle großzügig und bequem, hat aber für Interrupt-Handler den Nachteil, daß beim Start des Handlers alle Register außer R0 gesichert und am Ende wiederhergestellt werden müssen. Das dauert seine Zeit. Für die hier verwendete Variante RV32IMC wurden typischerweise 700 Takte zwischen dem Interrupt-Request und dem Interrupt-Acknowledge gemessen. In dieser Zeit ist der Interrupt-Handler gestartet, hat die Register gesichert und springt zum Interrupt-Code des Anwenders. Dessen Ausführungszeit, das Wiederherstellen der Register und der Rücksprung in das unterbrochene Programm kommt zur gesamten Ausführungszeit noch dazu.
Es gibt eine RV32E-Version, die dieses Problem verringert, indem sie nur 16 Register unterstützt. Sie wird allerdings vom „IP Core Generator“ nicht angeboten.
Der Betrieb mit einem 10-MHz-Takt konnte für den asynchronen seriellen Sender ATx interruptgesteuert keinen kontinuierlichen Datenstrom bei 115 kBd erreichen. Erst durch Takterhöhung auf 20 MHz wurde das möglich. Das liegt natürlich auch daran, daß in jedem Interrupt nur ein Byte in den Sender geschrieben wird. Mit einem Fifo aus mehreren Bytes ließe sich das beheben.
Probleme
Einen schwerwiegenden Fehler im Interrupt-Handling konnte ich lokalisieren, wenngleich noch nicht beheben. Zu Beginn der sprintf-Funktion und ihren Derivaten wird die variabel lange Parameterliste ausgewertet. Das wird mit va_start() bewerkstelligt und ist ungewöhnlich, weil bei fast allen anderen C‑Funktionen die Anzahl der Parameter beim Aufruf bekannt ist. Wenn zwischen dem Funktionsaufruf und va_start() ein Interrupt akzeptiert wird (z.B. der Timer-Interrupt), dann passieren schlimme Dinge. Als Workaround werden daher vor dem Aufruf alle Interrupts disabled und nach der Ausführung von va_start() wieder enabled. Das Problem muß natürlich zügig analysiert und behoben werden, aber für den Moment muß der Workaround reichen.
Nachtrag (07.02.2026)
Die Ursache des hier beschriebenen Problems ist gefunden und behoben. Es ist ein Fehler in dem von Gowin angepassten Startup code in der Datei start.S und betrifft den Interrupt Handler. Vor dem Rücksprung in das unterbrochene Programm wird der Inhalt des x31 (=t6) Registers überschrieben. Das ist ein temporäres Register, dessen Inhalt in vielen Fällen nicht relevant ist, daher geht es meist gut. Es hat auch nichts mit der printf-Funktion zu tun, außer daß es da schnell auffällt.
jal t6… springt unnötigerweise in eine C‑Funktion und speichert die Rücksprungadresse in t6. Ein Rücksprung erfolgt aber nicht, denn es ist kein Funktionsaufruf sondern die Rückkehr zum unterbrochenen Programm. Das erledigt die retirq Instruktion.
Sehr hilfreich bei der Fehlersuche war der Originalcode des PicoRV32 und das dazugehörige readme.md-File.
Next Steps…
Ich werde in den nächsten Wochen ein Redesign der Leiterplatte angehen. Dabei soll das Problem der Digitalisierung der Eingangssignale behoben und die Leiterplatte deutlich verkleinert werden, denn die vielen Testmöglichkeiten des Prototypen sind dann nicht mehr nötig.
Bis die Leiterplatte dann auf dem Tisch liegt, soll die PC-Software so angepasst werden, daß der Datenstrom analysiert und das Messergebnis im Smith-Diagramm dargestellt wird.
Anlässlich meiner Überlegungen zur Messung des komplexen Reflexionsfaktors oder allgemeiner eines Gamma-Messgerätes hatte ich mich auf die Suche nach aktuellen CPLDs oder FPGAs begeben, die heutzutage für Amateurbudgets in Einzelstückzahlen erhältlich sind. Da man dafür sowieso nicht besonders viele Pins braucht, wäre ein weiterer Wunsch, ein kleines QFP-Gehäuse, das man möglichst noch von Hand löten kann. Ein 44- oder 64-Pin QFP mit 0,65 mm Pin-Pitch, notfalls 0,5 mm, wäre also bevorzugt. Am nächsten kam dem ein 5M160ZE64 von Altera, die inzwischen wieder von Intel geschieden sind. Es hat aber 0,4 mm Pinabstand und das traue ich mir nicht mehr zu, von Hand zuverlässig zu löten.
Bei dieser Suche stieß ich auf FPGAs der chinesischen Firma Gowin, die mir sehr interessant erschienen. Die ebenfalls chinesische Firma Sipeed setzt diese FPGA auf ihren Tang-Boards ein, die zu sehr günstigen Preisen über die bekannten Plattformen gekauft werden können.
Die benötigten Entwicklungswerkzeuge für die Software- und FPGA-Entwicklung sind kostenlos von der Gowin-Website herunterzuladen. Sowohl diese Werkzeuge wie auch die Datenblätter der Bauteile sind absolut „State-of-the-Art“, geschrieben oder gegengelesen und korrigiert von englischen Muttersprachlern. Kein Vergleich zu dem, was vor 40 Jahren aus Japan kam. Die Software-Tools basieren auf Eclipse und die FPGA-Tools sind selbst geschrieben. Wer schonmal mit Quartus gearbeitet hat, wird sich schnell zurechtfinden.
Die SW-Tools setzen auf den GNU-Tools auf und unterstützen sowohl den ARM Cortex als auch die RISC‑V Architektur. Die FPGA-Tools unterstützen Verilog 95, Verilog 2001, System-Verilog 2017 und VHDL.
Ich habe mir die genannten Entwicklungswerkzeuge installiert und zwei Tang Nano 4K-Boards, ein Tang Primer 25K Dock Base Board (enthält ein Core Board) und dazu ein weiteres Core Board besorgt und damit ein paar Wochen herumgespielt.
Zum Einstieg will ich nachfolgend kurz meine Erfahrungen beschreiben. Wer selber einsteigen will, wird nicht an der Original-Dokumentation der oben verlinkten Seiten vorbeikommen. Zur Einarbeitung wird auch je nach Vorkenntnissen ein gewisser Zeitaufwand notwendig sein, den ich niemandem abnehmen kann.
Tang Nano 4K
Tang Nano 4K ist ein Board der Größe 60 mm x 22,86 mm, das als zentralen Chip den GW1NSR-LV4C einsetzt.
Tang Nano 4K auf einem Steckbrett
Das Board ist offensichtlich ursprünglich für Grafikanwendungen entwickelt worden, denn es hat sowohl einen HDMI-Anschluß für einen Monitor als auch ein DVP-Interface zum Anschluß einer Kamera. Außerdem hat es einen 32 MBit Flash-Speicher und ein USB-Interface sowie zwei Taster und eine LED. Viele Signale des GW1NSR-LV4C sind auf eine der beiden 22-poligen Stiftleisten herausgeführt, man muß aber stets darauf achten, daß sie nicht schon anderweitig für die Kamera oder den Monitor benutzt sind. Außerdem haben sie unterschiedliche Signalpegel, mal 1,8 V, mal 2,5 V und mal 3,3 V. Das schränkt die Anwendungsfälle erheblich ein, aber als Starterboard zum Einarbeiten in die Entwicklungswerkzeuge ist es dennoch sehr hilfreich.
Tang Nano 4K ist ohne Kamera für etwa 15 Euro und inklusive HDMI Kamera für etwa 20 Euro über Aliexpress zu beziehen. Wem das zu teuer ist, der kann sich auch den Tang Nano 1K mal anschauen, der etwa 8 Euro kostet.
GW1NSR-LV4C
Der GW1NSR-LV4C ist der zentrale Chip auf dem Tang Nano 4K-Board. Er hat als Hard-Core einen ARM Cortex-M3 an Bord (80 MHz max), sowie ein FPGA mit 4608 LUT4 Zellen. Das sind „look-up Tabellen“ mit jeweils vier Eingängen und einem Ausgang. Sie können vier Eingangssignale in beliebiger Weise verknüpfen und daraus das Ausgangssignal erzeugen. Für weitere Details der Architektur konsultiere man das Datenblatt. Allerdings ist die interne Architektur für den Anwender transparent. Man muß nicht unbedingt wissen, wie der Chip intern funktioniert, die Tools übernehmen das sozusagen auf Knopfdruck.
Weitere interessante Baugruppen auf dem Chip sind 180 kBit SRAM, 256 kBit Flash-Speicher, 64 MBit PSRAM, 64 MBit HyperRAM und 32 MBit NOR Flash. Außerdem hat er 16 18 bit x 18 bit Multiplizierer, zwei PLLs (clkout 480 MHz max) und zwei DLLs eingebaut, sowie einen Oszillator (125 MHz typ.) mit +/-5% Toleranz. Die vier I/O‑Bänke können jeweils mit Spannungen zwischen 1,2 V und 3,3 V betrieben werden.
Hier ein Foto mit angeschlossenem Logikanalysator für Testzwecke.
Tang Nano 4K mit angeschlossenem USB-Logikanalysator
Die Kommunikation mit dem PC erfolgt über die USB-Buchse auf der rechten Seite, die gleichzeitig die Spannungsversorgung des Boards bereitstellt.
Tang Primer 25K
Viel interessanter als der Tang Nano ist für mich der Tang Primer 25K. Dabei handelt es sich um zwei separate Leiterplatten, das „Dock Base Board“ und das aufgesteckte „Core Board“. Das Core Board enthält das eigentliche FPGA vom Typ GW5A-LV25MG121NC1/I0, einen 50 MHz Quarzoszillator, die Spannungsversorgung und ein 64 MBit NOR-Flash zum Speichern des FPGA-Inhalts inklusive gegebenenfalls des Codes für eine im FPGA implementierte Softcore-CPU. Auch die Tang Primer Boards sind über Aliexpress bestellbar. Der aktuelle Preis (2025) liegt bei etwa 20 Euro für das Core-Board und bei etwa 30 Euro für das Basisboard inklusive einem Core-Board.
Tang Primer 25K Core-Board
Das Core-Board hat eine Größe von nur 23 mm x 18 mm. Hier ein Größenvergleich mit einer SMA-Buchse:
Tang Primer 25K Core Board von oben, SMA-Buchse zum GrößenvergleichTang Primer 25K Core Board von unten, SMA-Buchse zum Größenvergleich
Die Verbindung mit dem Basisboard oder einem selbst zu bauenden Applikationsboard erfolgt über zwei jeweils 60-polige Subminiatur Steckverbinder aus der DF40-Serie von Hirose. Sie haben einen Pinabstand von 0,4 mm und damit ist ein zuverlässiges „Selberlöten“ nicht mehr möglich. Man wird also auf einen Bestücker zurückgreifen müssen und glücklicherweise hat JLCPCB die passenden Sockel in seinem Lagerbestand.
Das Core-Board benötigt eine Versorgungsspannung von 3,6 V bis 5,5 V und erzeugt daraus mit mehreren Schaltreglern alle benötigten weiteren Spannungen: 0,9 V, 1,2 V, 1,8 V, 2,5 V und 3,3 V. Die 0,9 V und die 1,2 V werden nur zur Versorgung des FPGAs benötigt und die 1,8/2,5/3,3V werden auf die Steckverbinder geführt. Auch die I/O‑Spannungsversorgungen der sechs GPIO-Bänke werden auf die Steckverbinder geführt, so daß das Basisboard diese Bänke über Leiterbahnbrücken mit der benötigten Spannung versorgen kann, ohne sie selbst erzeugen zu müssen.
Tang Primer 25K Base-Board
Das Tang Primer Base-Board stellt eine handhabbare Hardware-Entwicklungsumgebung zur Verfügung.
Tang Primer 25K Base Board mit aufgestecktem Core-Board
Über die USB-Buchse auf der rechten Seite wird das Board mit einem Host-PC verbunden und mit der Betriebsspannung versorgt. Über diese USB-Buchse ist das FPGA direkt programmierbar und alternativ kann der Bitstream in den Flash-Speicher geladen werden, aus dem das FPGA sich nach dem Einschalten selbst konfiguriert.
Außerdem sind viele Signale des FPGAs auf 100-mil-Stift- und Buchsenleisten herausgeführt. Damit sind Testaufbauten wie dieser hier leicht möglich.
Tang Primer 25K Base Board mit aufgestecktem Core-Board und Testaufbau
Hier ist ein Steckboard mit zwei aufgesteckten SMA-Buchsen gezeigt, das jeweils einen 50 Ω Abschlußwiderstand enthält und dann mit möglichst kurzen Drähten an die Stiftleiste verbunden wird. Auf die SMA-Buchsen sind BNC-Adapter aufgeschraubt, an die ein Signalgenerator angeschlossen wird. Auch der oben schon gezeigte Logikanalysator wird hier wieder verwendet. Das Kabel, das nach rechts oben abgeht, führt zu einem USB-RS232-Konverter. Er ist einer der simplen Art, der nur RxD, TxD und +5V zur Verfügung stellt. Er funktioniert gleichwohl hervorragend und wird regelmäßig problemlos mit 115 kBd betrieben.
Das GW5A-LV25MG121NC1/I0-FPGA
Das hier verwendete Gowin-FPGA vom Typ GW5A hat 23040 logische Einheiten, bestehend aus jeweils einer Lookup-Tabelle mit vier Eingängen und einem Flipflop. Außerdem sind 56 SRAM-Blöcke mit insgesamt 1008 KBit und zusätzlich noch 180 KBit verteiltes SRAM implementiert. Darüberhinaus gibt es 28 DSP-Blöcke, jeweils bestehend aus mehreren Hardware-Multiplizierern und ‑Addierern sowie einer 48-bit-ALU. Sechs PLLs sorgen für verschiedene benötigte Taktraten. Die Referenzeingangsfrequenz muß zwischen 19 MHz und 800 MHz liegen und die VCO-Frequenz liegt zwischen 800 MHz und 1600 MHz. Aus der eingestellten VCO-Frequenz wird durch Frequenzteiler die benötigte Ausgangsfrequenz erzeugt.
FPGA-Entwicklung
Als Software-Entwicklungsumgebung wird das weitverbreitete Eclipse mit GNU-Tools eingesetzt. Das soll hier nicht weiter besprochen werden, detaillierte Hilfe gibt es im Internet. Wer, so wie ich, bisher noch nicht damit gearbeitet hat, wird sich schnell zurechtfinden, sollte aber „ein paar Tage“ zur Einarbeitung veranschlagen. Die Gowin-Dokumentation ist auch hier sehr hilfreich, insbesondere wenn es um die Konfiguration für die verwendete Zielarchitektur geht, also ARM Cortex oder RiscV.
Der Gowin FPGA-Designer unterstützt Verilog 95, Verilog 2001, System-Verilog 2017 und VHDL. Die Logiksynthese läuft sehr schnell, Placement und Routing (P&R) braucht naturgemäß etwas länger. Ein kompletter Durchlauf für ein Design mit einem PicoRV32-Softcore, UART und Wishbone-Bus, einer PLL und einiger 32-bit Register und Glue-Logik dauert etwa 1 Minute. Ein Simulator ist nicht vorhanden, aber man kann auf kostenlose Simulatoren anderer Entwicklungswerkzeuge ausweichen. In meinem Fall ist das Modelsim, das bei der Intel/Altera Entwicklungsumgebung dabei ist.
Der IP Core Generator soll noch kurz erwähnt werden. Er ist Teil des Gowin FPGA Designers und gestattet die Konfiguration einiger vordefinierter Bausteine. Damit wird beispielsweise der RiscV-Softcore, die PLLs oder die DSPs konfiguriert. Hier die Konfiguration des CPU-Cores:
Konfiguration des 32-bit Pico-RiscV CPU Cores mit dem Gowin IP Core Generator
Für den RiscV-Core wählt man außer dem Befehlssatz auch die anzuschließenden Speicher und das Bus-Interface (AHB oder Wishbone). Außerdem sind einige für Microcontroller typische Peripherals wählbar: GPIOs, UART, I2C-Master, SPI Master oder Slave oder SPI-Flash-Interface. Das sieht dann so aus:
Konfiguration des CPU-Subsystems mit dem Gowin IP Core Generator
Ich habe damit jetzt einige Wochen herumgespielt und bin außerordentlich zufrieden. Die Tools arbeiten sehr stabil und zuverlässig, keinerlei unerklärliches Fehlverhalten, das dann nach Neustart des PCs genauso unerklärlich wieder weg ist. Das bin ich bei anderen Embedded Entwicklungsumgebungen anders gewohnt, irgendwas ist ja immer. Auch das Herunterladen des Bitstreams zum FPGA funktioniert zuverlässig und ohne Murren. Wenn etwas schiefging, wusste ich bisher immer, woran es lag, meistens an mir.
Mit dem oben schon gezeigten Testaufbau habe ich einen digitalen Phasenvergleicher gebaut, der mit den zugegeben sauberen Signale eines Testgenerators Phasenverschiebungen zwischen zwei Signalen von 0,1° auflösen kann. Die Abweichung vom tatsächlichen Wert lag nach meinen Beobachtungen zwischen 1° und 2°, was an dem provisorischen Aufbau liegen kann. Eine Anwendung für diesen Phasenmesser könnte ein Gamma-Meßgerät sein, dessen Prototyp ich hier schonmal vorgestellt, aber bisher nicht verwirklicht habe.
Ich werde noch etwas optimieren, sowohl Verilog- als auch C‑Code noch etwas bereinigen und dann die Ergebnisse hier vorstellen.